2020-09-19 17:25:05 +03:00
/*
* Copyright ( c ) 2018 - 2020 , Andreas Kling < kling @ serenityos . org >
2022-03-29 16:45:26 +03:00
* Copyright ( c ) 2020 - 2022 , Itamar S . < itamar8910 @ gmail . com >
2022-02-15 23:28:01 +03:00
* Copyright ( c ) 2020 - 2022 , the SerenityOS developers .
2020-09-19 17:25:05 +03:00
*
2021-04-22 11:24:48 +03:00
* SPDX - License - Identifier : BSD - 2 - Clause
2020-09-19 17:25:05 +03:00
*/
# include "HackStudioWidget.h"
# include "Debugger/DebugInfoWidget.h"
# include "Debugger/Debugger.h"
# include "Debugger/DisassemblyWidget.h"
2021-02-13 13:22:48 +03:00
# include "Dialogs/NewProjectDialog.h"
2020-09-19 17:25:05 +03:00
# include "Editor.h"
# include "EditorWrapper.h"
# include "FindInFilesWidget.h"
# include "Git/DiffViewer.h"
# include "Git/GitWidget.h"
# include "HackStudio.h"
# include "HackStudioWidget.h"
# include "Locator.h"
# include "Project.h"
2021-04-10 17:38:11 +03:00
# include "ProjectDeclarations.h"
2020-09-19 17:25:05 +03:00
# include "TerminalWrapper.h"
2021-05-22 15:14:49 +03:00
# include "ToDoEntries.h"
2022-02-14 16:08:24 +03:00
# include <AK/JsonParser.h>
2021-01-31 21:06:41 +03:00
# include <AK/LexicalPath.h>
2020-09-19 17:25:05 +03:00
# include <AK/StringBuilder.h>
2021-05-09 20:09:30 +03:00
# include <Kernel/API/InodeWatcherEvent.h>
2021-10-06 17:53:43 +03:00
# include <LibConfig/Client.h>
2020-09-19 17:25:05 +03:00
# include <LibCore/Event.h>
# include <LibCore/EventLoop.h>
# include <LibCore/File.h>
2021-04-28 17:01:14 +03:00
# include <LibCore/FileWatcher.h>
2020-09-19 17:25:05 +03:00
# include <LibDebug/DebugSession.h>
2021-06-12 00:33:39 +03:00
# include <LibDesktop/Launcher.h>
2020-09-19 17:25:05 +03:00
# include <LibGUI/Action.h>
# include <LibGUI/ActionGroup.h>
# include <LibGUI/Application.h>
# include <LibGUI/BoxLayout.h>
# include <LibGUI/Button.h>
2021-02-13 13:22:48 +03:00
# include <LibGUI/Dialog.h>
2021-01-02 13:59:55 +03:00
# include <LibGUI/EditingEngine.h>
2020-09-19 17:25:05 +03:00
# include <LibGUI/FilePicker.h>
2021-10-06 17:01:52 +03:00
# include <LibGUI/FontPicker.h>
2020-09-19 17:25:05 +03:00
# include <LibGUI/InputBox.h>
2020-10-26 14:13:32 +03:00
# include <LibGUI/ItemListModel.h>
2020-09-19 17:25:05 +03:00
# include <LibGUI/Label.h>
# include <LibGUI/Menu.h>
2021-04-13 17:18:20 +03:00
# include <LibGUI/Menubar.h>
2020-09-19 17:25:05 +03:00
# include <LibGUI/MessageBox.h>
2021-09-24 19:31:08 +03:00
# include <LibGUI/ModelEditingDelegate.h>
2021-01-02 13:59:55 +03:00
# include <LibGUI/RegularEditingEngine.h>
2020-09-19 17:25:05 +03:00
# include <LibGUI/Splitter.h>
# include <LibGUI/StackWidget.h>
2021-07-02 03:39:17 +03:00
# include <LibGUI/Statusbar.h>
2020-09-19 17:25:05 +03:00
# include <LibGUI/TabWidget.h>
# include <LibGUI/TableView.h>
# include <LibGUI/TextBox.h>
# include <LibGUI/TextEditor.h>
2021-04-13 17:18:20 +03:00
# include <LibGUI/Toolbar.h>
# include <LibGUI/ToolbarContainer.h>
2020-09-19 17:25:05 +03:00
# include <LibGUI/TreeView.h>
2021-01-02 13:59:55 +03:00
# include <LibGUI/VimEditingEngine.h>
2020-09-19 17:25:05 +03:00
# include <LibGUI/Widget.h>
# include <LibGUI/Window.h>
2022-04-09 10:28:38 +03:00
# include <LibGfx/Font/FontDatabase.h>
2021-03-19 14:06:32 +03:00
# include <LibGfx/Palette.h>
2021-07-09 12:14:57 +03:00
# include <LibThreading/Mutex.h>
2021-05-22 19:47:42 +03:00
# include <LibThreading/Thread.h>
2020-09-19 17:25:05 +03:00
# include <LibVT/TerminalWidget.h>
# include <fcntl.h>
# include <spawn.h>
# include <stdio.h>
2021-04-28 17:01:14 +03:00
# include <sys/stat.h>
2020-09-19 17:25:05 +03:00
# include <sys/types.h>
# include <sys/wait.h>
# include <unistd.h>
namespace HackStudio {
2021-11-19 17:13:07 +03:00
HackStudioWidget : : HackStudioWidget ( String path_to_project )
2021-10-06 17:01:52 +03:00
: m_editor_font ( read_editor_font_from_config ( ) )
2020-09-19 17:25:05 +03:00
{
set_fill_with_background_color ( true ) ;
set_layout < GUI : : VerticalBoxLayout > ( ) ;
layout ( ) - > set_spacing ( 2 ) ;
open_project ( path_to_project ) ;
2021-04-13 17:18:20 +03:00
auto & toolbar_container = add < GUI : : ToolbarContainer > ( ) ;
2020-09-19 17:25:05 +03:00
auto & outer_splitter = add < GUI : : HorizontalSplitter > ( ) ;
2022-02-20 17:50:44 +03:00
outer_splitter . layout ( ) - > set_spacing ( 4 ) ;
2020-10-26 14:13:32 +03:00
auto & left_hand_splitter = outer_splitter . add < GUI : : VerticalSplitter > ( ) ;
2022-02-20 17:50:44 +03:00
left_hand_splitter . layout ( ) - > set_spacing ( 6 ) ;
2020-12-30 03:23:32 +03:00
left_hand_splitter . set_fixed_width ( 150 ) ;
2021-04-10 17:38:11 +03:00
create_project_tab ( left_hand_splitter ) ;
2020-09-19 17:25:05 +03:00
m_project_tree_view_context_menu = create_project_tree_view_context_menu ( ) ;
2020-10-26 14:13:32 +03:00
create_open_files_view ( left_hand_splitter ) ;
2020-09-19 17:25:05 +03:00
m_right_hand_splitter = outer_splitter . add < GUI : : VerticalSplitter > ( ) ;
m_right_hand_stack = m_right_hand_splitter - > add < GUI : : StackWidget > ( ) ;
2020-12-10 20:59:03 +03:00
// Put a placeholder widget front & center since we don't have a file open yet.
m_right_hand_stack - > add < GUI : : Widget > ( ) ;
2020-09-19 17:25:05 +03:00
m_diff_viewer = m_right_hand_stack - > add < DiffViewer > ( ) ;
m_editors_splitter = m_right_hand_stack - > add < GUI : : VerticalSplitter > ( ) ;
Userland+LibGUI: Add shorthand versions of the Margins constructor
This allows for typing [8] instead of [8, 8, 8, 8] to specify the same
margin on all edges, for example. The constructors follow CSS' style of
specifying margins. The added constructors are:
- Margins(int all): Sets the same margin on all edges.
- Margins(int vertical, int horizontal): Sets the first argument to top
and bottom margins, and the second argument to left and right margins.
- Margins(int top, int vertical, int bottom): Sets the first argument to
the top margin, the second argument to the left and right margins,
and the third argument to the bottom margin.
2021-08-17 03:11:38 +03:00
m_editors_splitter - > layout ( ) - > set_margins ( { 3 , 0 , 0 } ) ;
2022-03-08 03:30:21 +03:00
add_new_editor_tab_widget ( * m_editors_splitter ) ;
2020-09-19 17:25:05 +03:00
2022-03-08 03:30:21 +03:00
m_switch_to_next_editor_tab_widget = create_switch_to_next_editor_tab_widget_action ( ) ;
2020-09-19 17:25:05 +03:00
m_switch_to_next_editor = create_switch_to_next_editor_action ( ) ;
m_switch_to_previous_editor = create_switch_to_previous_editor_action ( ) ;
2022-03-08 03:30:21 +03:00
m_remove_current_editor_tab_widget_action = create_remove_current_editor_tab_widget_action ( ) ;
2020-09-19 17:25:05 +03:00
m_remove_current_editor_action = create_remove_current_editor_action ( ) ;
m_open_action = create_open_action ( ) ;
m_save_action = create_save_action ( ) ;
2021-08-12 18:07:39 +03:00
m_save_as_action = create_save_as_action ( ) ;
2021-02-13 13:22:48 +03:00
m_new_project_action = create_new_project_action ( ) ;
2020-09-19 17:25:05 +03:00
create_action_tab ( * m_right_hand_splitter ) ;
2022-03-08 03:30:21 +03:00
m_add_editor_tab_widget_action = create_add_editor_tab_widget_action ( ) ;
2020-09-19 17:25:05 +03:00
m_add_editor_action = create_add_editor_action ( ) ;
m_add_terminal_action = create_add_terminal_action ( ) ;
m_remove_current_terminal_action = create_remove_current_terminal_action ( ) ;
m_locator = add < Locator > ( ) ;
m_terminal_wrapper - > on_command_exit = [ this ] {
m_stop_action - > set_enabled ( false ) ;
} ;
2022-03-19 14:47:18 +03:00
m_open_project_configuration_action = create_open_project_configuration_action ( ) ;
2020-09-19 17:25:05 +03:00
m_build_action = create_build_action ( ) ;
m_run_action = create_run_action ( ) ;
m_stop_action = create_stop_action ( ) ;
m_debug_action = create_debug_action ( ) ;
initialize_debugger ( ) ;
create_toolbar ( toolbar_container ) ;
2021-05-09 20:09:30 +03:00
2021-07-02 03:39:17 +03:00
m_statusbar = add < GUI : : Statusbar > ( 3 ) ;
2022-02-24 02:46:42 +03:00
m_statusbar - > segment ( 1 ) . set_mode ( GUI : : Statusbar : : Segment : : Mode : : Auto ) ;
m_statusbar - > segment ( 2 ) . set_mode ( GUI : : Statusbar : : Segment : : Mode : : Fixed ) ;
auto width = font ( ) . width ( " Ln 0000, Col 000 " ) + font ( ) . max_glyph_width ( ) ;
m_statusbar - > segment ( 2 ) . set_fixed_width ( width ) ;
update_statusbar ( ) ;
GUI : : Application : : the ( ) - > on_action_enter = [ this ] ( GUI : : Action & action ) {
auto text = action . status_tip ( ) ;
if ( text . is_empty ( ) )
text = Gfx : : parse_ampersand_string ( action . text ( ) ) ;
m_statusbar - > set_override_text ( move ( text ) ) ;
} ;
GUI : : Application : : the ( ) - > on_action_leave = [ this ] ( GUI : : Action & ) {
m_statusbar - > set_override_text ( { } ) ;
} ;
2021-07-02 03:39:17 +03:00
2021-05-09 20:09:30 +03:00
auto maybe_watcher = Core : : FileWatcher : : create ( ) ;
if ( maybe_watcher . is_error ( ) ) {
warnln ( " Couldn't create a file watcher, deleted files won't be noticed! Error: {} " , maybe_watcher . error ( ) ) ;
} else {
m_file_watcher = maybe_watcher . release_value ( ) ;
m_file_watcher - > on_change = [ this ] ( Core : : FileWatcherEvent const & event ) {
if ( event . type ! = Core : : FileWatcherEvent : : Type : : Deleted )
return ;
if ( event . event_path . starts_with ( project ( ) . root_path ( ) ) ) {
String relative_path = LexicalPath : : relative_path ( event . event_path , project ( ) . root_path ( ) ) ;
handle_external_file_deletion ( relative_path ) ;
} else {
handle_external_file_deletion ( event . event_path ) ;
}
} ;
}
2022-01-07 18:23:50 +03:00
m_project_builder = make < ProjectBuilder > ( * m_terminal_wrapper , * m_project ) ;
2022-03-19 14:53:02 +03:00
project ( ) . model ( ) . set_should_show_dotfiles ( Config : : read_bool ( " HackStudio " , " Global " , " ShowDotfiles " , false ) ) ;
2020-09-19 17:25:05 +03:00
}
void HackStudioWidget : : update_actions ( )
{
auto is_remove_terminal_enabled = [ this ] ( ) {
auto widget = m_action_tab_widget - > active_widget ( ) ;
if ( ! widget )
return false ;
2021-07-04 12:08:46 +03:00
if ( " TerminalWrapper " sv ! = widget - > class_name ( ) )
2020-09-19 17:25:05 +03:00
return false ;
if ( ! reinterpret_cast < TerminalWrapper * > ( widget ) - > user_spawned ( ) )
return false ;
return true ;
} ;
m_remove_current_editor_action - > set_enabled ( m_all_editor_wrappers . size ( ) > 1 ) ;
m_remove_current_terminal_action - > set_enabled ( is_remove_terminal_enabled ( ) ) ;
}
void HackStudioWidget : : on_action_tab_change ( )
{
update_actions ( ) ;
2021-05-10 13:53:32 +03:00
if ( auto * active_widget = m_action_tab_widget - > active_widget ( ) ) {
if ( is < GitWidget > ( * active_widget ) )
static_cast < GitWidget & > ( * active_widget ) . refresh ( ) ;
}
2020-09-19 17:25:05 +03:00
}
2022-02-14 16:08:24 +03:00
Vector < String > HackStudioWidget : : read_recent_projects ( )
{
auto json = Config : : read_string ( " HackStudio " , " Global " , " RecentProjects " ) ;
AK : : JsonParser parser ( json ) ;
auto value_or_error = parser . parse ( ) ;
if ( value_or_error . is_error ( ) )
return { } ;
auto value = value_or_error . release_value ( ) ;
if ( ! value . is_array ( ) )
return { } ;
Vector < String > paths ;
for ( auto & json_value : value . as_array ( ) . values ( ) ) {
if ( ! json_value . is_string ( ) )
return { } ;
paths . append ( json_value . as_string ( ) ) ;
}
return paths ;
}
2022-04-01 20:58:27 +03:00
void HackStudioWidget : : open_project ( String const & root_path )
2020-09-19 17:25:05 +03:00
{
2021-08-03 22:59:10 +03:00
if ( warn_unsaved_changes ( " There are unsaved changes, do you want to save before closing current project? " ) = = ContinueDecision : : No )
return ;
2020-12-10 20:59:03 +03:00
if ( chdir ( root_path . characters ( ) ) < 0 ) {
2020-09-19 17:25:05 +03:00
perror ( " chdir " ) ;
exit ( 1 ) ;
}
2021-08-01 07:00:19 +03:00
if ( m_project ) {
2021-08-02 22:14:15 +03:00
close_current_project ( ) ;
2021-08-01 07:00:19 +03:00
}
2020-12-10 20:59:03 +03:00
m_project = Project : : open_with_root_path ( root_path ) ;
2021-02-23 22:42:32 +03:00
VERIFY ( m_project ) ;
2020-09-19 17:25:05 +03:00
if ( m_project_tree_view ) {
m_project_tree_view - > set_model ( m_project - > model ( ) ) ;
m_project_tree_view - > update ( ) ;
}
2021-11-19 17:13:07 +03:00
if ( m_git_widget & & m_git_widget - > initialized ( ) ) {
2021-12-31 21:18:08 +03:00
m_git_widget - > change_repo ( root_path ) ;
2021-08-03 02:01:07 +03:00
m_git_widget - > refresh ( ) ;
}
2020-09-19 17:25:05 +03:00
if ( Debugger : : is_initialized ( ) ) {
2021-04-12 05:03:42 +03:00
auto & debugger = Debugger : : the ( ) ;
debugger . reset_breakpoints ( ) ;
debugger . set_source_root ( m_project - > root_path ( ) ) ;
2020-09-19 17:25:05 +03:00
}
2021-08-12 21:56:54 +03:00
for ( auto & editor_wrapper : m_all_editor_wrappers )
2021-12-31 21:18:08 +03:00
editor_wrapper . set_project_root ( m_project - > root_path ( ) ) ;
2021-08-27 16:26:18 +03:00
m_locations_history . clear ( ) ;
m_locations_history_end_index = 0 ;
2021-09-24 19:31:08 +03:00
m_project - > model ( ) . on_rename_successful = [ this ] ( auto & absolute_old_path , auto & absolute_new_path ) {
file_renamed (
LexicalPath : : relative_path ( absolute_old_path , m_project - > root_path ( ) ) ,
LexicalPath : : relative_path ( absolute_new_path , m_project - > root_path ( ) ) ) ;
} ;
2022-02-14 16:08:24 +03:00
auto recent_projects = read_recent_projects ( ) ;
recent_projects . remove_all_matching ( [ & ] ( auto & p ) { return p = = root_path ; } ) ;
recent_projects . insert ( 0 , root_path ) ;
if ( recent_projects . size ( ) > recent_projects_history_size )
recent_projects . shrink ( recent_projects_history_size ) ;
Config : : write_string ( " HackStudio " , " Global " , " RecentProjects " , JsonArray ( recent_projects ) . to_string ( ) ) ;
update_recent_projects_submenu ( ) ;
2020-09-19 17:25:05 +03:00
}
2021-03-31 03:54:39 +03:00
Vector < String > HackStudioWidget : : selected_file_paths ( ) const
2020-09-19 17:25:05 +03:00
{
Vector < String > files ;
m_project_tree_view - > selection ( ) . for_each_index ( [ & ] ( const GUI : : ModelIndex & index ) {
2021-03-31 03:54:39 +03:00
String sub_path = index . data ( ) . as_string ( ) ;
GUI : : ModelIndex parent_or_invalid = index . parent ( ) ;
while ( parent_or_invalid . is_valid ( ) ) {
sub_path = String : : formatted ( " {}/{} " , parent_or_invalid . data ( ) . as_string ( ) , sub_path ) ;
parent_or_invalid = parent_or_invalid . parent ( ) ;
}
files . append ( sub_path ) ;
2020-09-19 17:25:05 +03:00
} ) ;
return files ;
}
2022-04-01 20:58:27 +03:00
bool HackStudioWidget : : open_file ( String const & full_filename , size_t line , size_t column )
2020-09-19 17:25:05 +03:00
{
2021-02-20 13:16:32 +03:00
String filename = full_filename ;
if ( full_filename . starts_with ( project ( ) . root_path ( ) ) ) {
filename = LexicalPath : : relative_path ( full_filename , project ( ) . root_path ( ) ) ;
}
2021-04-15 19:42:40 +03:00
if ( Core : : File : : is_directory ( filename ) | | ! Core : : File : : exists ( filename ) )
return false ;
2021-02-02 00:05:14 +03:00
2021-05-01 13:04:19 +03:00
if ( ! active_file ( ) . is_empty ( ) ) {
2020-11-03 15:27:05 +03:00
// Since the file is previously open, it should always be in m_open_files.
2021-05-01 13:04:19 +03:00
VERIFY ( m_open_files . find ( active_file ( ) ) ! = m_open_files . end ( ) ) ;
auto previous_open_project_file = m_open_files . get ( active_file ( ) ) . value ( ) ;
2020-11-03 15:27:05 +03:00
// Update the scrollbar values of the previous_open_project_file and save them to m_open_files.
previous_open_project_file - > vertical_scroll_value ( current_editor ( ) . vertical_scrollbar ( ) . value ( ) ) ;
previous_open_project_file - > horizontal_scroll_value ( current_editor ( ) . horizontal_scrollbar ( ) . value ( ) ) ;
2020-10-26 14:13:32 +03:00
}
2020-11-03 15:27:05 +03:00
RefPtr < ProjectFile > new_project_file = nullptr ;
if ( auto it = m_open_files . find ( filename ) ; it ! = m_open_files . end ( ) ) {
new_project_file = it - > value ;
} else {
2021-09-08 21:36:34 +03:00
new_project_file = m_project - > create_file ( filename ) ;
2020-11-03 15:27:05 +03:00
m_open_files . set ( filename , * new_project_file ) ;
2020-10-26 14:13:32 +03:00
m_open_files_vector . append ( filename ) ;
2021-05-09 20:09:30 +03:00
if ( ! m_file_watcher . is_null ( ) ) {
auto watch_result = m_file_watcher - > add_watch ( filename , Core : : FileWatcherEvent : : Type : : Deleted ) ;
if ( watch_result . is_error ( ) ) {
warnln ( " Couldn't watch '{}' " , filename ) ;
}
2021-04-28 17:01:14 +03:00
}
2021-05-25 17:13:19 +03:00
m_open_files_view - > model ( ) - > invalidate ( ) ;
2020-09-19 17:25:05 +03:00
}
2021-08-27 16:26:18 +03:00
current_editor ( ) . on_cursor_change = nullptr ; // Disable callback while we're swapping the document.
2020-11-03 15:27:05 +03:00
current_editor ( ) . set_document ( const_cast < GUI : : TextDocument & > ( new_project_file - > document ( ) ) ) ;
2021-03-19 14:06:32 +03:00
if ( new_project_file - > could_render_text ( ) ) {
current_editor_wrapper ( ) . set_mode_displayable ( ) ;
} else {
current_editor_wrapper ( ) . set_mode_non_displayable ( ) ;
}
2020-11-03 15:27:05 +03:00
current_editor ( ) . horizontal_scrollbar ( ) . set_value ( new_project_file - > horizontal_scroll_value ( ) ) ;
current_editor ( ) . vertical_scrollbar ( ) . set_value ( new_project_file - > vertical_scroll_value ( ) ) ;
2021-12-09 01:28:12 +03:00
if ( current_editor ( ) . editing_engine ( ) - > is_regular ( ) )
current_editor ( ) . set_editing_engine ( make < GUI : : RegularEditingEngine > ( ) ) ;
else if ( current_editor ( ) . editing_engine ( ) - > is_vim ( ) )
current_editor ( ) . set_editing_engine ( make < GUI : : VimEditingEngine > ( ) ) ;
else
VERIFY_NOT_REACHED ( ) ;
2020-10-26 14:13:32 +03:00
2021-07-04 00:33:57 +03:00
set_edit_mode ( EditMode : : Text ) ;
2020-09-19 17:25:05 +03:00
2021-05-01 13:04:19 +03:00
String relative_file_path = filename ;
if ( filename . starts_with ( m_project - > root_path ( ) ) )
relative_file_path = filename . substring ( m_project - > root_path ( ) . length ( ) + 1 ) ;
2020-12-10 20:59:03 +03:00
2020-09-19 17:25:05 +03:00
m_project_tree_view - > update ( ) ;
2021-05-01 13:04:19 +03:00
current_editor_wrapper ( ) . set_filename ( filename ) ;
2022-03-08 03:30:21 +03:00
update_current_editor_title ( ) ;
2020-09-19 17:25:05 +03:00
current_editor ( ) . set_focus ( true ) ;
2021-07-02 03:39:17 +03:00
2021-08-27 16:26:18 +03:00
current_editor ( ) . on_cursor_change = [ this ] { on_cursor_change ( ) ; } ;
2022-04-15 19:37:24 +03:00
current_editor ( ) . on_change = [ this ] { update_window_title ( ) ; } ;
2021-08-03 22:40:42 +03:00
current_editor_wrapper ( ) . on_change = [ this ] { update_gml_preview ( ) ; } ;
2021-08-27 16:18:00 +03:00
current_editor ( ) . set_cursor ( line , column ) ;
2021-07-28 22:58:16 +03:00
update_gml_preview ( ) ;
2021-04-15 19:42:40 +03:00
return true ;
2020-09-19 17:25:05 +03:00
}
2021-08-01 15:01:49 +03:00
void HackStudioWidget : : close_file_in_all_editors ( String const & filename )
{
m_open_files . remove ( filename ) ;
m_open_files_vector . remove_all_matching (
[ & filename ] ( String const & element ) { return element = = filename ; } ) ;
for ( auto & editor_wrapper : m_all_editor_wrappers ) {
Editor & editor = editor_wrapper . editor ( ) ;
String editor_file_path = editor . code_document ( ) . file_path ( ) ;
String relative_editor_file_path = LexicalPath : : relative_path ( editor_file_path , project ( ) . root_path ( ) ) ;
if ( relative_editor_file_path = = filename ) {
if ( m_open_files_vector . is_empty ( ) ) {
editor . set_document ( CodeDocument : : create ( ) ) ;
editor_wrapper . set_filename ( " " ) ;
} else {
auto & first_path = m_open_files_vector [ 0 ] ;
auto & document = m_open_files . get ( first_path ) . value ( ) - > code_document ( ) ;
editor . set_document ( document ) ;
editor_wrapper . set_filename ( first_path ) ;
}
}
}
m_open_files_view - > model ( ) - > invalidate ( ) ;
}
2022-03-08 03:30:21 +03:00
GUI : : TabWidget & HackStudioWidget : : current_editor_tab_widget ( )
{
VERIFY ( m_current_editor_tab_widget ) ;
return * m_current_editor_tab_widget ;
}
GUI : : TabWidget const & HackStudioWidget : : current_editor_tab_widget ( ) const
{
VERIFY ( m_current_editor_tab_widget ) ;
return * m_current_editor_tab_widget ;
}
2020-09-19 17:25:05 +03:00
EditorWrapper & HackStudioWidget : : current_editor_wrapper ( )
{
2021-02-23 22:42:32 +03:00
VERIFY ( m_current_editor_wrapper ) ;
2020-09-19 17:25:05 +03:00
return * m_current_editor_wrapper ;
}
2021-08-27 16:26:18 +03:00
EditorWrapper const & HackStudioWidget : : current_editor_wrapper ( ) const
{
VERIFY ( m_current_editor_wrapper ) ;
return * m_current_editor_wrapper ;
}
2020-09-19 17:25:05 +03:00
GUI : : TextEditor & HackStudioWidget : : current_editor ( )
{
return current_editor_wrapper ( ) . editor ( ) ;
}
2021-08-27 16:26:18 +03:00
GUI : : TextEditor const & HackStudioWidget : : current_editor ( ) const
{
return current_editor_wrapper ( ) . editor ( ) ;
}
2020-09-19 17:25:05 +03:00
void HackStudioWidget : : set_edit_mode ( EditMode mode )
{
if ( mode = = EditMode : : Text ) {
m_right_hand_stack - > set_active_widget ( m_editors_splitter ) ;
} else if ( mode = = EditMode : : Diff ) {
m_right_hand_stack - > set_active_widget ( m_diff_viewer ) ;
} else {
2021-02-23 22:42:32 +03:00
VERIFY_NOT_REACHED ( ) ;
2020-09-19 17:25:05 +03:00
}
m_right_hand_stack - > active_widget ( ) - > update ( ) ;
}
NonnullRefPtr < GUI : : Menu > HackStudioWidget : : create_project_tree_view_context_menu ( )
{
2022-03-07 06:19:48 +03:00
m_new_file_actions . append ( create_new_file_action ( " &C++ Source File " , " /res/icons/16x16/filetype-cplusplus.png " , " cpp " ) ) ;
m_new_file_actions . append ( create_new_file_action ( " C++ &Header File " , " /res/icons/16x16/filetype-header.png " , " h " ) ) ;
m_new_file_actions . append ( create_new_file_action ( " &GML File " , " /res/icons/16x16/filetype-gml.png " , " gml " ) ) ;
2022-03-07 06:44:23 +03:00
m_new_file_actions . append ( create_new_file_action ( " P&ython Source File " , " /res/icons/16x16/filetype-python.png " , " py " ) ) ;
m_new_file_actions . append ( create_new_file_action ( " Ja&va Source File " , " /res/icons/16x16/filetype-java.png " , " java " ) ) ;
m_new_file_actions . append ( create_new_file_action ( " C Source File " , " /res/icons/16x16/filetype-c.png " , " c " ) ) ;
m_new_file_actions . append ( create_new_file_action ( " &JavaScript Source File " , " /res/icons/16x16/filetype-javascript.png " , " js " ) ) ;
2022-03-07 06:19:48 +03:00
m_new_file_actions . append ( create_new_file_action ( " HT&ML File " , " /res/icons/16x16/filetype-html.png " , " html " ) ) ;
m_new_file_actions . append ( create_new_file_action ( " C&SS File " , " /res/icons/16x16/filetype-css.png " , " css " ) ) ;
2022-03-07 06:44:23 +03:00
m_new_file_actions . append ( create_new_file_action ( " &PHP File " , " /res/icons/16x16/filetype-php.png " , " php " ) ) ;
m_new_file_actions . append ( create_new_file_action ( " &Wasm File " , " /res/icons/16x16/filetype-wasm.png " , " wasm " ) ) ;
m_new_file_actions . append ( create_new_file_action ( " &INI File " , " /res/icons/16x16/filetype-ini.png " , " ini " ) ) ;
m_new_file_actions . append ( create_new_file_action ( " JS&ON File " , " /res/icons/16x16/filetype-json.png " , " json " ) ) ;
m_new_file_actions . append ( create_new_file_action ( " Mark&down File " , " /res/icons/16x16/filetype-markdown.png " , " md " ) ) ;
2021-07-30 23:06:47 +03:00
2022-03-07 06:19:48 +03:00
m_new_plain_file_action = create_new_file_action ( " Plain &File " , " /res/icons/16x16/new.png " , " " ) ;
2021-07-30 23:06:47 +03:00
2020-09-19 17:25:05 +03:00
m_open_selected_action = create_open_selected_action ( ) ;
2022-05-26 01:26:08 +03:00
m_open_selected_in_new_tab_action = create_open_selected_in_new_tab_action ( ) ;
2021-06-12 00:33:39 +03:00
m_show_in_file_manager_action = create_show_in_file_manager_action ( ) ;
2021-07-30 23:06:47 +03:00
2021-01-31 21:06:41 +03:00
m_new_directory_action = create_new_directory_action ( ) ;
2020-09-19 17:25:05 +03:00
m_delete_action = create_delete_action ( ) ;
2021-09-24 19:31:08 +03:00
m_tree_view_rename_action = GUI : : CommonActions : : make_rename_action ( [ this ] ( GUI : : Action const & ) {
m_project_tree_view - > begin_editing ( m_project_tree_view - > cursor_index ( ) ) ;
} ) ;
2020-09-19 17:25:05 +03:00
auto project_tree_view_context_menu = GUI : : Menu : : construct ( " Project Files " ) ;
2021-07-30 23:06:47 +03:00
2022-03-07 06:19:48 +03:00
auto & new_file_submenu = project_tree_view_context_menu - > add_submenu ( " N&ew... " ) ;
2021-07-30 23:06:47 +03:00
for ( auto & new_file_action : m_new_file_actions ) {
new_file_submenu . add_action ( new_file_action ) ;
}
2022-02-09 05:49:58 +03:00
new_file_submenu . set_icon ( Gfx : : Bitmap : : try_load_from_file ( " /res/icons/16x16/new.png " ) . release_value_but_fixme_should_propagate_errors ( ) ) ;
2021-07-30 23:06:47 +03:00
new_file_submenu . add_action ( * m_new_plain_file_action ) ;
new_file_submenu . add_separator ( ) ;
new_file_submenu . add_action ( * m_new_directory_action ) ;
2020-09-19 17:25:05 +03:00
project_tree_view_context_menu - > add_action ( * m_open_selected_action ) ;
2022-05-26 01:26:08 +03:00
project_tree_view_context_menu - > add_action ( * m_open_selected_in_new_tab_action ) ;
2021-06-12 00:33:39 +03:00
project_tree_view_context_menu - > add_action ( * m_show_in_file_manager_action ) ;
2021-09-24 19:31:08 +03:00
// TODO: Cut, copy, duplicate with new name...
2020-09-19 17:25:05 +03:00
project_tree_view_context_menu - > add_separator ( ) ;
2021-09-24 19:31:08 +03:00
project_tree_view_context_menu - > add_action ( * m_tree_view_rename_action ) ;
2020-09-19 17:25:05 +03:00
project_tree_view_context_menu - > add_action ( * m_delete_action ) ;
return project_tree_view_context_menu ;
}
2021-07-30 23:06:47 +03:00
NonnullRefPtr < GUI : : Action > HackStudioWidget : : create_new_file_action ( String const & label , String const & icon , String const & extension )
2020-09-19 17:25:05 +03:00
{
2021-11-06 18:25:29 +03:00
return GUI : : Action : : create ( label , Gfx : : Bitmap : : try_load_from_file ( icon ) . release_value_but_fixme_should_propagate_errors ( ) , [ this , extension ] ( const GUI : : Action & ) {
2020-09-19 17:25:05 +03:00
String filename ;
2022-05-13 15:10:27 +03:00
if ( GUI : : InputBox : : show ( window ( ) , filename , " Enter name of new file: " , " Add new file to project " ) ! = GUI : : InputBox : : ExecResult : : OK )
2020-09-19 17:25:05 +03:00
return ;
2021-03-31 03:54:39 +03:00
2021-07-30 23:06:47 +03:00
if ( ! extension . is_empty ( ) & & ! filename . ends_with ( String : : formatted ( " .{} " , extension ) ) ) {
filename = String : : formatted ( " {}.{} " , filename , extension ) ;
}
2021-03-31 03:54:39 +03:00
auto path_to_selected = selected_file_paths ( ) ;
String filepath ;
if ( ! path_to_selected . is_empty ( ) ) {
VERIFY ( Core : : File : : exists ( path_to_selected . first ( ) ) ) ;
LexicalPath selected ( path_to_selected . first ( ) ) ;
String dir_path ;
if ( Core : : File : : is_directory ( selected . string ( ) ) )
dir_path = selected . string ( ) ;
else
dir_path = selected . dirname ( ) ;
filepath = String : : formatted ( " {}/ " , dir_path ) ;
}
filepath = String : : formatted ( " {}{} " , filepath , filename ) ;
auto file = Core : : File : : construct ( filepath ) ;
2021-05-12 12:26:43 +03:00
if ( ! file - > open ( ( Core : : OpenMode ) ( Core : : OpenMode : : WriteOnly | Core : : OpenMode : : MustBeNew ) ) ) {
2021-03-31 03:54:39 +03:00
GUI : : MessageBox : : show ( window ( ) , String : : formatted ( " Failed to create '{}' " , filepath ) , " Error " , GUI : : MessageBox : : Type : : Error ) ;
2020-09-19 17:25:05 +03:00
return ;
}
2021-03-31 03:54:39 +03:00
open_file ( filepath ) ;
2020-09-19 17:25:05 +03:00
} ) ;
}
2021-01-31 21:06:41 +03:00
NonnullRefPtr < GUI : : Action > HackStudioWidget : : create_new_directory_action ( )
{
2022-03-07 06:08:34 +03:00
return GUI : : Action : : create ( " &Directory... " , { Mod_Ctrl | Mod_Shift , Key_N } , Gfx : : Bitmap : : try_load_from_file ( " /res/icons/16x16/mkdir.png " ) . release_value_but_fixme_should_propagate_errors ( ) , [ this ] ( const GUI : : Action & ) {
2021-01-31 21:06:41 +03:00
String directory_name ;
2022-05-13 15:10:27 +03:00
if ( GUI : : InputBox : : show ( window ( ) , directory_name , " Enter name of new directory: " , " Add new folder to project " ) ! = GUI : : InputBox : : ExecResult : : OK )
2021-01-31 21:06:41 +03:00
return ;
2021-03-31 03:54:39 +03:00
auto path_to_selected = selected_file_paths ( ) ;
if ( ! path_to_selected . is_empty ( ) ) {
LexicalPath selected ( path_to_selected . first ( ) ) ;
String dir_path ;
if ( Core : : File : : is_directory ( selected . string ( ) ) )
dir_path = selected . string ( ) ;
else
dir_path = selected . dirname ( ) ;
directory_name = String : : formatted ( " {}/{} " , dir_path , directory_name ) ;
}
2021-01-31 21:06:41 +03:00
auto formatted_dir_name = LexicalPath : : canonicalized_path ( String : : formatted ( " {}/{} " , m_project - > model ( ) . root_path ( ) , directory_name ) ) ;
int rc = mkdir ( formatted_dir_name . characters ( ) , 0755 ) ;
if ( rc < 0 ) {
GUI : : MessageBox : : show ( window ( ) , " Failed to create new directory " , " Error " , GUI : : MessageBox : : Type : : Error ) ;
return ;
}
} ) ;
}
2020-09-19 17:25:05 +03:00
NonnullRefPtr < GUI : : Action > HackStudioWidget : : create_open_selected_action ( )
{
2022-03-07 06:19:48 +03:00
auto open_selected_action = GUI : : Action : : create ( " &Open " , [ this ] ( const GUI : : Action & ) {
2021-03-31 03:54:39 +03:00
auto files = selected_file_paths ( ) ;
2020-09-19 17:25:05 +03:00
for ( auto & file : files )
open_file ( file ) ;
} ) ;
2022-02-09 05:49:58 +03:00
open_selected_action - > set_icon ( Gfx : : Bitmap : : try_load_from_file ( " /res/icons/16x16/open.png " ) . release_value_but_fixme_should_propagate_errors ( ) ) ;
2020-09-19 17:25:05 +03:00
open_selected_action - > set_enabled ( true ) ;
return open_selected_action ;
}
2022-05-26 01:26:08 +03:00
NonnullRefPtr < GUI : : Action > HackStudioWidget : : create_open_selected_in_new_tab_action ( )
{
auto open_selected_in_new_tab_action = GUI : : Action : : create ( " Open in New &Tab " , [ this ] ( const GUI : : Action & ) {
auto files = selected_file_paths ( ) ;
for ( auto & file : files ) {
add_new_editor ( * m_current_editor_tab_widget ) ;
open_file ( file ) ;
}
} ) ;
open_selected_in_new_tab_action - > set_icon ( Gfx : : Bitmap : : try_load_from_file ( " /res/icons/16x16/open.png " ) . release_value_but_fixme_should_propagate_errors ( ) ) ;
open_selected_in_new_tab_action - > set_enabled ( true ) ;
return open_selected_in_new_tab_action ;
}
2021-06-12 00:33:39 +03:00
NonnullRefPtr < GUI : : Action > HackStudioWidget : : create_show_in_file_manager_action ( )
{
2022-03-07 06:19:48 +03:00
auto show_in_file_manager_action = GUI : : Action : : create ( " Show in File &Manager " , [ this ] ( const GUI : : Action & ) {
2021-06-12 00:33:39 +03:00
auto files = selected_file_paths ( ) ;
for ( auto & file : files )
Desktop : : Launcher : : open ( URL : : create_with_file_protocol ( m_project - > root_path ( ) , file ) ) ;
} ) ;
show_in_file_manager_action - > set_enabled ( true ) ;
show_in_file_manager_action - > set_icon ( GUI : : Icon : : default_icon ( " app-file-manager " ) . bitmap_for_size ( 16 ) ) ;
return show_in_file_manager_action ;
}
2020-09-19 17:25:05 +03:00
NonnullRefPtr < GUI : : Action > HackStudioWidget : : create_delete_action ( )
{
auto delete_action = GUI : : CommonActions : : make_delete_action ( [ this ] ( const GUI : : Action & ) {
2021-03-31 03:54:39 +03:00
auto files = selected_file_paths ( ) ;
2020-09-19 17:25:05 +03:00
if ( files . is_empty ( ) )
return ;
String message ;
if ( files . size ( ) = = 1 ) {
2021-03-31 03:54:39 +03:00
LexicalPath file ( files [ 0 ] ) ;
message = String : : formatted ( " Really remove {} from disk? " , file . basename ( ) ) ;
2020-09-19 17:25:05 +03:00
} else {
2020-12-10 20:59:03 +03:00
message = String : : formatted ( " Really remove {} files from disk? " , files . size ( ) ) ;
2020-09-19 17:25:05 +03:00
}
auto result = GUI : : MessageBox : : show ( window ( ) ,
message ,
" Confirm deletion " ,
GUI : : MessageBox : : Type : : Warning ,
GUI : : MessageBox : : InputType : : OKCancel ) ;
2022-05-13 15:10:27 +03:00
if ( result = = GUI : : MessageBox : : ExecResult : : Cancel )
2020-09-19 17:25:05 +03:00
return ;
for ( auto & file : files ) {
2021-03-02 08:18:59 +03:00
struct stat st ;
if ( lstat ( file . characters ( ) , & st ) < 0 ) {
2020-09-19 17:25:05 +03:00
GUI : : MessageBox : : show ( window ( ) ,
2021-03-02 08:18:59 +03:00
String : : formatted ( " lstat ({}) failed: {} " , file , strerror ( errno ) ) ,
2020-09-19 17:25:05 +03:00
" Removal failed " ,
GUI : : MessageBox : : Type : : Error ) ;
break ;
}
2021-03-02 08:18:59 +03:00
bool is_directory = S_ISDIR ( st . st_mode ) ;
2022-02-10 19:42:53 +03:00
if ( auto result = Core : : File : : remove ( file , Core : : File : : RecursionMode : : Allowed , false ) ; result . is_error ( ) ) {
2021-03-02 08:18:59 +03:00
auto & error = result . error ( ) ;
if ( is_directory ) {
GUI : : MessageBox : : show ( window ( ) ,
2021-11-07 03:31:00 +03:00
String : : formatted ( " Removing directory {} from the project failed: {} " , error . file , static_cast < Error const & > ( error ) ) ,
2021-03-02 08:18:59 +03:00
" Removal failed " ,
GUI : : MessageBox : : Type : : Error ) ;
} else {
GUI : : MessageBox : : show ( window ( ) ,
2021-11-07 03:31:00 +03:00
String : : formatted ( " Removing file {} from the project failed: {} " , error . file , static_cast < Error const & > ( error ) ) ,
2021-03-02 08:18:59 +03:00
" Removal failed " ,
GUI : : MessageBox : : Type : : Error ) ;
}
}
2020-09-19 17:25:05 +03:00
}
2021-11-30 18:12:49 +03:00
} ,
m_project_tree_view ) ;
2020-09-19 17:25:05 +03:00
delete_action - > set_enabled ( false ) ;
return delete_action ;
}
2021-02-13 13:22:48 +03:00
NonnullRefPtr < GUI : : Action > HackStudioWidget : : create_new_project_action ( )
{
2022-03-07 06:08:34 +03:00
return GUI : : Action : : create ( " &Project... " , Gfx : : Bitmap : : try_load_from_file ( " /res/icons/16x16/hackstudio-project.png " ) . release_value_but_fixme_should_propagate_errors ( ) , [ this ] ( const GUI : : Action & ) {
2021-02-13 13:22:48 +03:00
auto dialog = NewProjectDialog : : construct ( window ( ) ) ;
dialog - > set_icon ( window ( ) - > icon ( ) ) ;
auto result = dialog - > exec ( ) ;
2022-05-13 15:10:27 +03:00
if ( result = = GUI : : Dialog : : ExecResult : : OK & & dialog - > created_project_path ( ) . has_value ( ) )
2021-02-13 13:22:48 +03:00
open_project ( dialog - > created_project_path ( ) . value ( ) ) ;
} ) ;
}
2022-03-08 03:30:21 +03:00
NonnullRefPtr < GUI : : Action > HackStudioWidget : : create_remove_current_editor_tab_widget_action ( )
2020-09-19 17:25:05 +03:00
{
2022-03-08 03:30:21 +03:00
return GUI : : Action : : create ( " Switch to Next Editor Group " , { Mod_Alt | Mod_Shift , Key_Backslash } , [ this ] ( auto & ) {
if ( m_all_editor_tab_widgets . size ( ) < = 1 )
return ;
auto tab_widget = m_current_editor_tab_widget ;
while ( tab_widget - > children ( ) . size ( ) > 1 ) {
auto active_wrapper = tab_widget - > active_widget ( ) ;
tab_widget - > remove_tab ( * active_wrapper ) ;
m_all_editor_wrappers . remove_first_matching ( [ & active_wrapper ] ( auto & entry ) { return entry = = active_wrapper ; } ) ;
}
tab_widget - > on_tab_close_click ( * tab_widget - > active_widget ( ) ) ;
} ) ;
}
void HackStudioWidget : : add_new_editor_tab_widget ( GUI : : Widget & parent )
{
auto tab_widget = GUI : : TabWidget : : construct ( ) ;
2020-09-19 17:25:05 +03:00
if ( m_action_tab_widget ) {
2022-03-08 03:30:21 +03:00
parent . insert_child_before ( tab_widget , * m_action_tab_widget ) ;
2020-09-19 17:25:05 +03:00
} else {
2022-03-08 03:30:21 +03:00
parent . add_child ( tab_widget ) ;
}
m_current_editor_tab_widget = tab_widget ;
m_all_editor_tab_widgets . append ( tab_widget ) ;
tab_widget - > set_reorder_allowed ( true ) ;
if ( m_all_editor_tab_widgets . size ( ) > 1 ) {
for ( auto & widget : m_all_editor_tab_widgets )
widget . set_close_button_enabled ( true ) ;
2020-09-19 17:25:05 +03:00
}
2022-03-08 03:30:21 +03:00
tab_widget - > on_change = [ & ] ( auto & widget ) {
auto & wrapper = static_cast < EditorWrapper & > ( widget ) ;
set_current_editor_wrapper ( wrapper ) ;
current_editor ( ) . set_focus ( true ) ;
} ;
tab_widget - > on_middle_click = [ ] ( auto & widget ) {
auto & wrapper = static_cast < EditorWrapper & > ( widget ) ;
wrapper . on_tab_close_request ( wrapper ) ;
} ;
tab_widget - > on_tab_close_click = [ ] ( auto & widget ) {
auto & wrapper = static_cast < EditorWrapper & > ( widget ) ;
wrapper . on_tab_close_request ( wrapper ) ;
} ;
add_new_editor ( * m_current_editor_tab_widget ) ;
}
void HackStudioWidget : : add_new_editor ( GUI : : TabWidget & parent )
{
auto & wrapper = parent . add_tab < EditorWrapper > ( " (Untitled) " ) ;
parent . set_active_widget ( & wrapper ) ;
if ( parent . children ( ) . size ( ) > 1 | | m_all_editor_tab_widgets . size ( ) > 1 )
parent . set_close_button_enabled ( true ) ;
2021-12-09 01:28:12 +03:00
auto previous_editor_wrapper = m_current_editor_wrapper ;
2020-09-19 17:25:05 +03:00
m_current_editor_wrapper = wrapper ;
m_all_editor_wrappers . append ( wrapper ) ;
2022-03-08 03:30:21 +03:00
wrapper . editor ( ) . set_focus ( true ) ;
wrapper . editor ( ) . set_font ( * m_editor_font ) ;
wrapper . set_project_root ( m_project - > root_path ( ) ) ;
wrapper . editor ( ) . on_cursor_change = [ this ] { on_cursor_change ( ) ; } ;
wrapper . on_change = [ this ] { update_gml_preview ( ) ; } ;
2021-08-12 18:12:42 +03:00
set_edit_mode ( EditMode : : Text ) ;
2021-12-09 01:28:12 +03:00
if ( previous_editor_wrapper & & previous_editor_wrapper - > editor ( ) . editing_engine ( ) - > is_regular ( ) )
2022-03-08 03:30:21 +03:00
wrapper . editor ( ) . set_editing_engine ( make < GUI : : RegularEditingEngine > ( ) ) ;
2021-12-09 01:28:12 +03:00
else if ( previous_editor_wrapper & & previous_editor_wrapper - > editor ( ) . editing_engine ( ) - > is_vim ( ) )
2022-03-08 03:30:21 +03:00
wrapper . editor ( ) . set_editing_engine ( make < GUI : : VimEditingEngine > ( ) ) ;
2021-12-09 01:28:12 +03:00
else
2022-03-08 03:30:21 +03:00
wrapper . editor ( ) . set_editing_engine ( make < GUI : : RegularEditingEngine > ( ) ) ;
wrapper . on_tab_close_request = [ this , & parent ] ( auto & tab ) {
parent . deferred_invoke ( [ this , & parent , & tab ] {
set_current_editor_wrapper ( tab ) ;
parent . remove_tab ( tab ) ;
m_all_editor_wrappers . remove_first_matching ( [ & tab ] ( auto & entry ) { return entry = = & tab ; } ) ;
if ( parent . children ( ) . is_empty ( ) ) {
m_switch_to_next_editor_tab_widget - > activate ( ) ;
m_editors_splitter - > remove_child ( parent ) ;
m_all_editor_tab_widgets . remove_first_matching ( [ & parent ] ( auto & entry ) { return entry = = & parent ; } ) ;
if ( m_current_editor_tab_widget - > children ( ) . size ( ) = = 1 )
m_current_editor_tab_widget - > set_close_button_enabled ( false ) ;
}
if ( parent . children ( ) . size ( ) = = 1 & & m_all_editor_tab_widgets . size ( ) < = 1 )
parent . set_close_button_enabled ( false ) ;
update_actions ( ) ;
} ) ;
} ;
2020-09-19 17:25:05 +03:00
}
2022-03-08 03:30:21 +03:00
NonnullRefPtr < GUI : : Action > HackStudioWidget : : create_switch_to_next_editor_tab_widget_action ( )
2020-09-19 17:25:05 +03:00
{
2022-03-08 03:30:21 +03:00
return GUI : : Action : : create ( " Switch to Next Editor Group " , { Mod_Ctrl | Mod_Shift , Key_T } , [ this ] ( auto & ) {
if ( m_all_editor_tab_widgets . size ( ) < = 1 )
2020-09-19 17:25:05 +03:00
return ;
2022-03-08 03:30:21 +03:00
Vector < GUI : : TabWidget & > tab_widgets ;
m_editors_splitter - > for_each_child_of_type < GUI : : TabWidget > ( [ & tab_widgets ] ( auto & child ) {
tab_widgets . append ( child ) ;
2020-09-19 17:25:05 +03:00
return IterationDecision : : Continue ;
} ) ;
2022-03-08 03:30:21 +03:00
for ( size_t i = 0 ; i < tab_widgets . size ( ) ; + + i ) {
if ( m_current_editor_tab_widget . ptr ( ) = = & tab_widgets [ i ] ) {
if ( i = = tab_widgets . size ( ) - 1 )
m_current_editor_tab_widget = tab_widgets [ 0 ] ;
2020-09-19 17:25:05 +03:00
else
2022-03-08 03:30:21 +03:00
m_current_editor_tab_widget = tab_widgets [ i + 1 ] ;
auto wrapper = static_cast < EditorWrapper * > ( m_current_editor_tab_widget - > active_widget ( ) ) ;
set_current_editor_wrapper ( wrapper ) ;
current_editor ( ) . set_focus ( true ) ;
break ;
2020-09-19 17:25:05 +03:00
}
}
} ) ;
}
2022-03-08 03:30:21 +03:00
NonnullRefPtr < GUI : : Action > HackStudioWidget : : create_switch_to_next_editor_action ( )
{
return GUI : : Action : : create ( " Switch to &Next Editor " , { Mod_Ctrl , Key_E } , [ this ] ( auto & ) {
m_current_editor_tab_widget - > activate_next_tab ( ) ;
} ) ;
}
2020-09-19 17:25:05 +03:00
NonnullRefPtr < GUI : : Action > HackStudioWidget : : create_switch_to_previous_editor_action ( )
{
2021-04-10 11:19:25 +03:00
return GUI : : Action : : create ( " Switch to &Previous Editor " , { Mod_Ctrl | Mod_Shift , Key_E } , [ this ] ( auto & ) {
2022-03-08 03:30:21 +03:00
m_current_editor_tab_widget - > activate_previous_tab ( ) ;
2020-09-19 17:25:05 +03:00
} ) ;
}
NonnullRefPtr < GUI : : Action > HackStudioWidget : : create_remove_current_editor_action ( )
{
2022-02-09 05:49:58 +03:00
return GUI : : Action : : create ( " &Remove Current Editor " , { Mod_Alt | Mod_Shift , Key_E } , Gfx : : Bitmap : : try_load_from_file ( " /res/icons/hackstudio/remove-editor.png " ) . release_value_but_fixme_should_propagate_errors ( ) , [ this ] ( auto & ) {
2020-09-19 17:25:05 +03:00
if ( m_all_editor_wrappers . size ( ) < = 1 )
return ;
2022-03-08 03:30:21 +03:00
auto tab_widget = m_current_editor_tab_widget ;
auto * active_wrapper = tab_widget - > active_widget ( ) ;
VERIFY ( active_wrapper ) ;
tab_widget - > on_tab_close_click ( * active_wrapper ) ;
2020-09-19 17:25:05 +03:00
update_actions ( ) ;
} ) ;
}
NonnullRefPtr < GUI : : Action > HackStudioWidget : : create_open_action ( )
{
2021-11-06 18:25:29 +03:00
return GUI : : Action : : create ( " &Open Project... " , { Mod_Ctrl | Mod_Shift , Key_O } , Gfx : : Bitmap : : try_load_from_file ( " /res/icons/16x16/open.png " ) . release_value_but_fixme_should_propagate_errors ( ) , [ this ] ( auto & ) {
2021-08-02 16:12:09 +03:00
auto open_path = GUI : : FilePicker : : get_open_filepath ( window ( ) , " Open project " , m_project - > root_path ( ) , true ) ;
2020-09-19 17:25:05 +03:00
if ( ! open_path . has_value ( ) )
return ;
open_project ( open_path . value ( ) ) ;
update_actions ( ) ;
} ) ;
}
NonnullRefPtr < GUI : : Action > HackStudioWidget : : create_save_action ( )
{
2021-04-10 11:19:25 +03:00
return GUI : : CommonActions : : make_save_action ( [ & ] ( auto & ) {
2021-05-01 13:04:19 +03:00
if ( active_file ( ) . is_empty ( ) )
2021-08-12 18:09:24 +03:00
m_save_as_action - > activate ( ) ;
2020-09-19 17:25:05 +03:00
2022-02-10 02:21:09 +03:00
// NOTE active_file() could still be empty after a cancelled save_as_action
if ( ! active_file ( ) . is_empty ( ) )
current_editor_wrapper ( ) . save ( ) ;
2020-09-19 17:25:05 +03:00
if ( m_git_widget - > initialized ( ) )
m_git_widget - > refresh ( ) ;
} ) ;
}
2021-08-12 18:07:39 +03:00
NonnullRefPtr < GUI : : Action > HackStudioWidget : : create_save_as_action ( )
{
return GUI : : CommonActions : : make_save_as_action ( [ & ] ( auto & ) {
auto const old_filename = current_editor_wrapper ( ) . filename ( ) ;
LexicalPath const old_path ( old_filename ) ;
Optional < String > save_path = GUI : : FilePicker : : get_save_filepath ( window ( ) ,
old_filename . is_null ( ) ? " Untitled " : old_path . title ( ) ,
old_filename . is_null ( ) ? " txt " : old_path . extension ( ) ,
Core : : File : : absolute_path ( old_path . dirname ( ) ) ) ;
if ( ! save_path . has_value ( ) ) {
return ;
}
2021-08-12 23:31:21 +03:00
String const relative_file_path = LexicalPath : : relative_path ( save_path . value ( ) , m_project - > root_path ( ) ) ;
2021-08-12 23:34:01 +03:00
if ( current_editor_wrapper ( ) . filename ( ) . is_null ( ) ) {
current_editor_wrapper ( ) . set_filename ( relative_file_path ) ;
} else {
for ( auto & editor_wrapper : m_all_editor_wrappers ) {
if ( editor_wrapper . filename ( ) = = old_filename )
editor_wrapper . set_filename ( relative_file_path ) ;
}
}
2021-08-12 18:07:39 +03:00
current_editor_wrapper ( ) . save ( ) ;
2021-09-08 21:36:34 +03:00
auto new_project_file = m_project - > create_file ( relative_file_path ) ;
2021-08-12 23:31:21 +03:00
m_open_files . set ( relative_file_path , * new_project_file ) ;
2021-08-12 23:42:48 +03:00
m_open_files . remove ( old_filename ) ;
2021-08-12 23:31:21 +03:00
m_open_files_vector . append ( relative_file_path ) ;
2021-08-12 23:42:48 +03:00
m_open_files_vector . remove_all_matching ( [ & old_filename ] ( auto const & element ) { return element = = old_filename ; } ) ;
2021-08-13 14:11:04 +03:00
update_window_title ( ) ;
2022-03-08 03:30:21 +03:00
update_current_editor_title ( ) ;
2021-08-22 20:38:05 +03:00
m_project - > model ( ) . invalidate ( ) ;
update_tree_view ( ) ;
2021-08-12 18:07:39 +03:00
} ) ;
}
2020-09-19 17:25:05 +03:00
NonnullRefPtr < GUI : : Action > HackStudioWidget : : create_remove_current_terminal_action ( )
{
2022-02-09 05:49:58 +03:00
return GUI : : Action : : create ( " Remove &Current Terminal " , { Mod_Alt | Mod_Shift , Key_T } , Gfx : : Bitmap : : try_load_from_file ( " /res/icons/hackstudio/remove-terminal.png " ) . release_value_but_fixme_should_propagate_errors ( ) , [ this ] ( auto & ) {
2020-09-19 17:25:05 +03:00
auto widget = m_action_tab_widget - > active_widget ( ) ;
if ( ! widget )
return ;
2021-01-02 00:46:17 +03:00
if ( ! is < TerminalWrapper > ( widget ) )
2020-09-19 17:25:05 +03:00
return ;
2021-01-02 00:46:17 +03:00
auto & terminal = * static_cast < TerminalWrapper * > ( widget ) ;
if ( ! terminal . user_spawned ( ) )
2020-09-19 17:25:05 +03:00
return ;
2021-01-02 00:46:17 +03:00
m_action_tab_widget - > remove_tab ( terminal ) ;
2020-09-19 17:25:05 +03:00
update_actions ( ) ;
} ) ;
}
2022-03-08 03:30:21 +03:00
NonnullRefPtr < GUI : : Action > HackStudioWidget : : create_add_editor_tab_widget_action ( )
{
return GUI : : Action : : create ( " Add New Editor Group " , { Mod_Ctrl | Mod_Alt , Key_Backslash } ,
[ this ] ( auto & ) {
add_new_editor_tab_widget ( * m_editors_splitter ) ;
update_actions ( ) ;
} ) ;
}
2020-09-19 17:25:05 +03:00
NonnullRefPtr < GUI : : Action > HackStudioWidget : : create_add_editor_action ( )
{
2021-04-10 11:19:25 +03:00
return GUI : : Action : : create ( " Add New &Editor " , { Mod_Ctrl | Mod_Alt , Key_E } ,
2022-02-09 05:49:58 +03:00
Gfx : : Bitmap : : try_load_from_file ( " /res/icons/hackstudio/add-editor.png " ) . release_value_but_fixme_should_propagate_errors ( ) ,
2020-09-19 17:25:05 +03:00
[ this ] ( auto & ) {
2022-03-08 03:30:21 +03:00
add_new_editor ( * m_current_editor_tab_widget ) ;
2020-09-19 17:25:05 +03:00
update_actions ( ) ;
} ) ;
}
NonnullRefPtr < GUI : : Action > HackStudioWidget : : create_add_terminal_action ( )
{
2021-04-10 11:19:25 +03:00
return GUI : : Action : : create ( " Add New &Terminal " , { Mod_Ctrl | Mod_Alt , Key_T } ,
2022-02-09 05:49:58 +03:00
Gfx : : Bitmap : : try_load_from_file ( " /res/icons/hackstudio/add-terminal.png " ) . release_value_but_fixme_should_propagate_errors ( ) ,
2020-09-19 17:25:05 +03:00
[ this ] ( auto & ) {
2020-11-10 13:47:51 +03:00
auto & terminal_wrapper = m_action_tab_widget - > add_tab < TerminalWrapper > ( " Terminal " ) ;
2022-02-28 12:13:09 +03:00
terminal_wrapper . on_command_exit = [ & ] ( ) {
deferred_invoke ( [ this ] ( ) {
m_action_tab_widget - > remove_tab ( * m_action_tab_widget - > active_widget ( ) ) ;
} ) ;
} ;
2020-11-10 13:47:51 +03:00
reveal_action_tab ( terminal_wrapper ) ;
2020-09-19 17:25:05 +03:00
update_actions ( ) ;
2020-11-10 13:47:51 +03:00
terminal_wrapper . terminal ( ) . set_focus ( true ) ;
2020-09-19 17:25:05 +03:00
} ) ;
}
void HackStudioWidget : : reveal_action_tab ( GUI : : Widget & widget )
{
2022-06-12 23:35:17 +03:00
if ( m_action_tab_widget - > effective_min_size ( ) . height ( ) . as_int ( ) < 200 )
2020-12-30 03:23:32 +03:00
m_action_tab_widget - > set_fixed_height ( 200 ) ;
2020-09-19 17:25:05 +03:00
m_action_tab_widget - > set_active_widget ( & widget ) ;
}
NonnullRefPtr < GUI : : Action > HackStudioWidget : : create_debug_action ( )
{
2021-11-06 18:25:29 +03:00
return GUI : : Action : : create ( " &Debug " , Gfx : : Bitmap : : try_load_from_file ( " /res/icons/16x16/debug-run.png " ) . release_value_but_fixme_should_propagate_errors ( ) , [ this ] ( auto & ) {
2021-02-20 01:46:54 +03:00
if ( ! Core : : File : : exists ( get_project_executable_path ( ) ) ) {
2020-10-08 14:41:36 +03:00
GUI : : MessageBox : : show ( window ( ) , String : : formatted ( " Could not find file: {}. (did you build the project?) " , get_project_executable_path ( ) ) , " Error " , GUI : : MessageBox : : Type : : Error ) ;
2020-09-19 17:25:05 +03:00
return ;
}
if ( Debugger : : the ( ) . session ( ) ) {
GUI : : MessageBox : : show ( window ( ) , " Debugger is already running " , " Error " , GUI : : MessageBox : : Type : : Error ) ;
return ;
}
2020-12-10 20:59:03 +03:00
2020-09-19 17:25:05 +03:00
Debugger : : the ( ) . set_executable_path ( get_project_executable_path ( ) ) ;
2021-12-20 23:42:03 +03:00
m_terminal_wrapper - > clear_including_history ( ) ;
// The debugger calls wait() on the debugee, so the TerminalWrapper can't do that.
auto ptm_res = m_terminal_wrapper - > setup_master_pseudoterminal ( TerminalWrapper : : WaitForChildOnExit : : No ) ;
if ( ptm_res . is_error ( ) ) {
perror ( " setup_master_pseudoterminal " ) ;
return ;
}
Debugger : : the ( ) . set_child_setup_callback ( [ this , ptm_res ] ( ) {
return m_terminal_wrapper - > setup_slave_pseudoterminal ( ptm_res . value ( ) ) ;
} ) ;
2021-05-22 19:47:42 +03:00
m_debugger_thread = Threading : : Thread : : construct ( Debugger : : start_static ) ;
2020-09-19 17:25:05 +03:00
m_debugger_thread - > start ( ) ;
2021-04-14 00:01:30 +03:00
m_stop_action - > set_enabled ( true ) ;
2021-07-11 18:26:16 +03:00
m_run_action - > set_enabled ( false ) ;
2021-07-11 18:38:38 +03:00
for ( auto & editor_wrapper : m_all_editor_wrappers ) {
editor_wrapper . set_debug_mode ( true ) ;
}
2020-09-19 17:25:05 +03:00
} ) ;
}
void HackStudioWidget : : initialize_debugger ( )
{
Debugger : : initialize (
2021-01-06 23:00:53 +03:00
m_project - > root_path ( ) ,
2022-04-01 20:58:27 +03:00
[ this ] ( PtraceRegisters const & regs ) {
2021-02-23 22:42:32 +03:00
VERIFY ( Debugger : : the ( ) . session ( ) ) ;
2020-09-19 17:25:05 +03:00
const auto & debug_session = * Debugger : : the ( ) . session ( ) ;
2021-08-02 01:07:23 +03:00
auto source_position = debug_session . get_source_position ( regs . ip ( ) ) ;
2020-09-19 17:25:05 +03:00
if ( ! source_position . has_value ( ) ) {
2021-08-02 01:07:23 +03:00
dbgln ( " Could not find source position for address: {:p} " , regs . ip ( ) ) ;
2020-09-19 17:25:05 +03:00
return Debugger : : HasControlPassedToUser : : No ;
}
2021-01-06 23:00:53 +03:00
dbgln ( " Debugger stopped at source position: {}:{} " , source_position . value ( ) . file_path , source_position . value ( ) . line_number ) ;
2020-09-19 17:25:05 +03:00
2021-08-30 13:43:28 +03:00
deferred_invoke ( [ this , source_position , & regs ] {
m_current_editor_in_execution = get_editor_of_file ( source_position . value ( ) . file_path ) ;
if ( m_current_editor_in_execution )
m_current_editor_in_execution - > editor ( ) . set_execution_position ( source_position . value ( ) . line_number - 1 ) ;
m_debug_info_widget - > update_state ( * Debugger : : the ( ) . session ( ) , regs ) ;
m_debug_info_widget - > set_debug_actions_enabled ( true ) ;
m_disassembly_widget - > update_state ( * Debugger : : the ( ) . session ( ) , regs ) ;
HackStudioWidget : : reveal_action_tab ( * m_debug_info_widget ) ;
} ) ;
2022-02-11 18:57:10 +03:00
Core : : EventLoop : : wake_current ( ) ;
2020-09-19 17:25:05 +03:00
return Debugger : : HasControlPassedToUser : : Yes ;
} ,
[ this ] ( ) {
2021-08-30 13:43:28 +03:00
deferred_invoke ( [ this ] {
2020-09-19 17:25:05 +03:00
m_debug_info_widget - > set_debug_actions_enabled ( false ) ;
2021-04-14 00:01:30 +03:00
if ( m_current_editor_in_execution )
2020-09-19 17:25:05 +03:00
m_current_editor_in_execution - > editor ( ) . clear_execution_position ( ) ;
2021-08-30 13:43:28 +03:00
} ) ;
2022-02-11 18:57:10 +03:00
Core : : EventLoop : : wake_current ( ) ;
2020-09-19 17:25:05 +03:00
} ,
[ this ] ( ) {
2021-08-30 13:43:28 +03:00
deferred_invoke ( [ this ] {
2021-04-14 00:01:30 +03:00
m_debug_info_widget - > set_debug_actions_enabled ( false ) ;
if ( m_current_editor_in_execution )
m_current_editor_in_execution - > editor ( ) . clear_execution_position ( ) ;
2020-09-19 17:25:05 +03:00
m_debug_info_widget - > program_stopped ( ) ;
m_disassembly_widget - > program_stopped ( ) ;
2021-04-14 00:01:30 +03:00
m_stop_action - > set_enabled ( false ) ;
2021-07-11 18:26:16 +03:00
m_run_action - > set_enabled ( true ) ;
2021-04-14 00:04:46 +03:00
m_debugger_thread . clear ( ) ;
2021-07-11 18:38:38 +03:00
for ( auto & editor_wrapper : m_all_editor_wrappers ) {
editor_wrapper . set_debug_mode ( false ) ;
}
2020-09-19 17:25:05 +03:00
HackStudioWidget : : hide_action_tabs ( ) ;
GUI : : MessageBox : : show ( window ( ) , " Program Exited " , " Debugger " , GUI : : MessageBox : : Type : : Information ) ;
2021-08-30 13:43:28 +03:00
} ) ;
2022-02-11 18:57:10 +03:00
Core : : EventLoop : : wake_current ( ) ;
2020-09-19 17:25:05 +03:00
} ) ;
}
2022-04-01 20:58:27 +03:00
String HackStudioWidget : : get_full_path_of_serenity_source ( String const & file )
2020-09-19 17:25:05 +03:00
{
auto path_parts = LexicalPath ( file ) . parts ( ) ;
2021-11-19 17:13:07 +03:00
while ( ! path_parts . is_empty ( ) & & path_parts [ 0 ] = = " .. " ) {
path_parts . remove ( 0 ) ;
}
2020-09-19 17:25:05 +03:00
StringBuilder relative_path_builder ;
relative_path_builder . join ( " / " , path_parts ) ;
constexpr char SERENITY_LIBS_PREFIX [ ] = " /usr/src/serenity " ;
LexicalPath serenity_sources_base ( SERENITY_LIBS_PREFIX ) ;
2020-10-08 14:41:36 +03:00
return String : : formatted ( " {}/{} " , serenity_sources_base , relative_path_builder . to_string ( ) ) ;
2020-09-19 17:25:05 +03:00
}
2022-04-01 20:58:27 +03:00
String HackStudioWidget : : get_absolute_path ( String const & path ) const
2021-11-19 17:13:07 +03:00
{
// TODO: We can probably do a more specific condition here, something like
// "if (file.starts_with("../Libraries/") || file.starts_with("../AK/"))"
if ( path . starts_with ( " .. " ) ) {
return get_full_path_of_serenity_source ( path ) ;
}
return m_project - > to_absolute_path ( path ) ;
}
2022-04-01 20:58:27 +03:00
RefPtr < EditorWrapper > HackStudioWidget : : get_editor_of_file ( String const & filename )
2020-09-19 17:25:05 +03:00
{
2021-04-29 22:46:15 +03:00
String file_path = filename ;
2020-09-19 17:25:05 +03:00
2021-04-29 22:46:15 +03:00
if ( filename . starts_with ( " ../ " ) ) {
file_path = get_full_path_of_serenity_source ( filename ) ;
2020-09-19 17:25:05 +03:00
}
2021-04-15 19:42:40 +03:00
if ( ! open_file ( file_path ) )
return nullptr ;
2020-09-19 17:25:05 +03:00
return current_editor_wrapper ( ) ;
}
String HackStudioWidget : : get_project_executable_path ( ) const
{
2020-12-10 20:59:03 +03:00
// FIXME: Dumb heuristic ahead!
// e.g /my/project => /my/project/project
2020-09-19 17:25:05 +03:00
// TODO: Perhaps a Makefile rule for getting the value of $(PROGRAM) would be better?
2021-06-29 17:46:16 +03:00
return String : : formatted ( " {}/{} " , m_project - > root_path ( ) , LexicalPath : : basename ( m_project - > root_path ( ) ) ) ;
2020-09-19 17:25:05 +03:00
}
2022-01-07 18:23:50 +03:00
void HackStudioWidget : : build ( )
2020-09-19 17:25:05 +03:00
{
2022-01-07 18:23:50 +03:00
auto result = m_project_builder - > build ( active_file ( ) ) ;
if ( result . is_error ( ) ) {
GUI : : MessageBox : : show ( window ( ) , String : : formatted ( " {} " , result . error ( ) ) , " Build failed " , GUI : : MessageBox : : Type : : Error ) ;
}
2020-09-19 17:25:05 +03:00
}
2022-01-07 18:23:50 +03:00
void HackStudioWidget : : run ( )
2020-09-19 17:25:05 +03:00
{
2022-01-07 18:23:50 +03:00
auto result = m_project_builder - > run ( active_file ( ) ) ;
if ( result . is_error ( ) ) {
GUI : : MessageBox : : show ( window ( ) , String : : formatted ( " {} " , result . error ( ) ) , " Run failed " , GUI : : MessageBox : : Type : : Error ) ;
}
2020-09-19 17:25:05 +03:00
}
void HackStudioWidget : : hide_action_tabs ( )
{
2020-12-30 03:23:32 +03:00
m_action_tab_widget - > set_fixed_height ( 24 ) ;
2020-09-19 17:25:05 +03:00
} ;
Project & HackStudioWidget : : project ( )
{
return * m_project ;
}
2022-03-08 03:30:21 +03:00
void HackStudioWidget : : set_current_editor_tab_widget ( RefPtr < GUI : : TabWidget > tab_widget )
{
m_current_editor_tab_widget = tab_widget ;
}
2020-09-19 17:25:05 +03:00
void HackStudioWidget : : set_current_editor_wrapper ( RefPtr < EditorWrapper > editor_wrapper )
{
m_current_editor_wrapper = editor_wrapper ;
2021-08-13 14:10:58 +03:00
update_window_title ( ) ;
2022-03-08 03:30:21 +03:00
update_current_editor_title ( ) ;
2021-08-22 20:38:05 +03:00
update_tree_view ( ) ;
2022-03-08 03:30:21 +03:00
set_current_editor_tab_widget ( static_cast < GUI : : TabWidget * > ( m_current_editor_wrapper - > parent ( ) ) ) ;
update_statusbar ( ) ;
2020-09-19 17:25:05 +03:00
}
2021-09-24 19:31:08 +03:00
void HackStudioWidget : : file_renamed ( String const & old_name , String const & new_name )
{
auto editor_or_none = m_all_editor_wrappers . first_matching ( [ & old_name ] ( auto const & editor ) {
return editor - > filename ( ) = = old_name ;
} ) ;
if ( editor_or_none . has_value ( ) ) {
( * editor_or_none ) - > set_filename ( new_name ) ;
( * editor_or_none ) - > set_name ( new_name ) ;
}
if ( m_open_files . contains ( old_name ) ) {
VERIFY ( m_open_files_vector . remove_first_matching ( [ & old_name ] ( auto const & file ) { return file = = old_name ; } ) ) ;
m_open_files_vector . append ( new_name ) ;
ProjectFile * f = m_open_files . get ( old_name ) . release_value ( ) ;
m_open_files . set ( new_name , * f ) ;
m_open_files . remove ( old_name ) ;
m_open_files_view - > model ( ) - > invalidate ( ) ;
}
if ( m_file_watcher - > is_watching ( old_name ) ) {
VERIFY ( ! m_file_watcher - > remove_watch ( old_name ) . is_error ( ) ) ;
VERIFY ( ! m_file_watcher - > add_watch ( new_name , Core : : FileWatcherEvent : : Type : : Deleted ) . is_error ( ) ) ;
}
2022-03-08 03:30:21 +03:00
update_window_title ( ) ;
update_current_editor_title ( ) ;
2021-09-24 19:31:08 +03:00
}
2021-04-10 17:38:11 +03:00
void HackStudioWidget : : configure_project_tree_view ( )
2020-09-19 17:25:05 +03:00
{
m_project_tree_view - > set_model ( m_project - > model ( ) ) ;
2021-03-02 17:25:03 +03:00
m_project_tree_view - > set_selection_mode ( GUI : : AbstractView : : SelectionMode : : MultiSelection ) ;
2021-09-24 19:31:08 +03:00
m_project_tree_view - > set_editable ( true ) ;
m_project_tree_view - > aid_create_editing_delegate = [ ] ( auto & ) {
return make < GUI : : StringModelEditingDelegate > ( ) ;
} ;
2020-12-10 20:59:03 +03:00
for ( int column_index = 0 ; column_index < m_project - > model ( ) . column_count ( ) ; + + column_index )
2021-04-05 12:57:47 +03:00
m_project_tree_view - > set_column_visible ( column_index , false ) ;
2020-12-10 20:59:03 +03:00
2021-04-05 12:57:47 +03:00
m_project_tree_view - > set_column_visible ( GUI : : FileSystemModel : : Column : : Name , true ) ;
2020-09-19 17:25:05 +03:00
m_project_tree_view - > on_context_menu_request = [ this ] ( const GUI : : ModelIndex & index , const GUI : : ContextMenuEvent & event ) {
if ( index . is_valid ( ) ) {
m_project_tree_view_context_menu - > popup ( event . screen_position ( ) , m_open_selected_action ) ;
}
} ;
m_project_tree_view - > on_selection_change = [ this ] {
m_open_selected_action - > set_enabled ( ! m_project_tree_view - > selection ( ) . is_empty ( ) ) ;
2021-07-16 18:09:45 +03:00
auto selections = m_project_tree_view - > selection ( ) . indices ( ) ;
auto it = selections . find_if ( [ & ] ( auto selected_file ) {
return access ( m_project - > model ( ) . full_path ( selected_file . parent ( ) ) . characters ( ) , W_OK ) = = 0 ;
} ) ;
bool has_permissions = it ! = selections . end ( ) ;
2021-10-10 02:00:32 +03:00
m_tree_view_rename_action - > set_enabled ( has_permissions ) ;
2021-07-17 17:50:57 +03:00
m_delete_action - > set_enabled ( has_permissions ) ;
2020-09-19 17:25:05 +03:00
} ;
m_project_tree_view - > on_activation = [ this ] ( auto & index ) {
2021-02-23 01:00:57 +03:00
auto full_path_to_file = m_project - > model ( ) . full_path ( index ) ;
open_file ( full_path_to_file ) ;
2020-09-19 17:25:05 +03:00
} ;
}
2020-10-26 14:13:32 +03:00
void HackStudioWidget : : create_open_files_view ( GUI : : Widget & parent )
{
m_open_files_view = parent . add < GUI : : ListView > ( ) ;
auto open_files_model = GUI : : ItemListModel < String > : : create ( m_open_files_vector ) ;
m_open_files_view - > set_model ( open_files_model ) ;
2020-10-26 16:57:52 +03:00
m_open_files_view - > on_activation = [ this ] ( auto & index ) {
2020-10-26 14:13:32 +03:00
open_file ( index . data ( ) . to_string ( ) ) ;
} ;
}
2020-09-19 17:25:05 +03:00
void HackStudioWidget : : create_toolbar ( GUI : : Widget & parent )
{
2021-04-13 17:18:20 +03:00
auto & toolbar = parent . add < GUI : : Toolbar > ( ) ;
2021-07-30 23:06:47 +03:00
toolbar . add_action ( * m_new_plain_file_action ) ;
2021-01-31 21:06:41 +03:00
toolbar . add_action ( * m_new_directory_action ) ;
2020-09-19 17:25:05 +03:00
toolbar . add_action ( * m_save_action ) ;
toolbar . add_action ( * m_delete_action ) ;
toolbar . add_separator ( ) ;
toolbar . add_action ( GUI : : CommonActions : : make_cut_action ( [ this ] ( auto & ) { current_editor ( ) . cut_action ( ) . activate ( ) ; } ) ) ;
toolbar . add_action ( GUI : : CommonActions : : make_copy_action ( [ this ] ( auto & ) { current_editor ( ) . copy_action ( ) . activate ( ) ; } ) ) ;
toolbar . add_action ( GUI : : CommonActions : : make_paste_action ( [ this ] ( auto & ) { current_editor ( ) . paste_action ( ) . activate ( ) ; } ) ) ;
toolbar . add_separator ( ) ;
toolbar . add_action ( GUI : : CommonActions : : make_undo_action ( [ this ] ( auto & ) { current_editor ( ) . undo_action ( ) . activate ( ) ; } ) ) ;
toolbar . add_action ( GUI : : CommonActions : : make_redo_action ( [ this ] ( auto & ) { current_editor ( ) . redo_action ( ) . activate ( ) ; } ) ) ;
toolbar . add_separator ( ) ;
toolbar . add_action ( * m_build_action ) ;
toolbar . add_separator ( ) ;
toolbar . add_action ( * m_run_action ) ;
toolbar . add_action ( * m_stop_action ) ;
toolbar . add_separator ( ) ;
toolbar . add_action ( * m_debug_action ) ;
}
NonnullRefPtr < GUI : : Action > HackStudioWidget : : create_build_action ( )
{
2021-11-06 18:25:29 +03:00
return GUI : : Action : : create ( " &Build " , { Mod_Ctrl , Key_B } , Gfx : : Bitmap : : try_load_from_file ( " /res/icons/16x16/build.png " ) . release_value_but_fixme_should_propagate_errors ( ) , [ this ] ( auto & ) {
2021-05-01 13:42:07 +03:00
if ( warn_unsaved_changes ( " There are unsaved changes, do you want to save before building? " ) = = ContinueDecision : : No )
return ;
2020-09-19 17:25:05 +03:00
reveal_action_tab ( * m_terminal_wrapper ) ;
2022-01-07 18:23:50 +03:00
build ( ) ;
2020-09-19 17:25:05 +03:00
m_stop_action - > set_enabled ( true ) ;
} ) ;
}
NonnullRefPtr < GUI : : Action > HackStudioWidget : : create_run_action ( )
{
2021-11-06 18:25:29 +03:00
return GUI : : Action : : create ( " &Run " , { Mod_Ctrl , Key_R } , Gfx : : Bitmap : : try_load_from_file ( " /res/icons/16x16/program-run.png " ) . release_value_but_fixme_should_propagate_errors ( ) , [ this ] ( auto & ) {
2020-09-19 17:25:05 +03:00
reveal_action_tab ( * m_terminal_wrapper ) ;
2022-01-07 18:23:50 +03:00
run ( ) ;
2020-09-19 17:25:05 +03:00
m_stop_action - > set_enabled ( true ) ;
} ) ;
}
void HackStudioWidget : : create_action_tab ( GUI : : Widget & parent )
{
m_action_tab_widget = parent . add < GUI : : TabWidget > ( ) ;
2020-12-30 03:23:32 +03:00
m_action_tab_widget - > set_fixed_height ( 24 ) ;
2020-09-19 17:25:05 +03:00
m_action_tab_widget - > on_change = [ this ] ( auto & ) {
on_action_tab_change ( ) ;
static bool first_time = true ;
if ( ! first_time )
2020-12-30 03:23:32 +03:00
m_action_tab_widget - > set_fixed_height ( 200 ) ;
2020-09-19 17:25:05 +03:00
first_time = false ;
} ;
m_find_in_files_widget = m_action_tab_widget - > add_tab < FindInFilesWidget > ( " Find in files " ) ;
2021-05-22 15:14:49 +03:00
m_todo_entries_widget = m_action_tab_widget - > add_tab < ToDoEntriesWidget > ( " TODO " ) ;
2021-12-20 23:25:52 +03:00
m_terminal_wrapper = m_action_tab_widget - > add_tab < TerminalWrapper > ( " Console " , false ) ;
2020-09-19 17:25:05 +03:00
m_debug_info_widget = m_action_tab_widget - > add_tab < DebugInfoWidget > ( " Debug " ) ;
2021-11-19 17:13:07 +03:00
m_debug_info_widget - > on_backtrace_frame_selection = [ this ] ( Debug : : DebugInfo : : SourcePosition const & source_position ) {
open_file ( get_absolute_path ( source_position . file_path ) , source_position . line_number - 1 ) ;
} ;
2020-09-19 17:25:05 +03:00
m_disassembly_widget = m_action_tab_widget - > add_tab < DisassemblyWidget > ( " Disassembly " ) ;
2021-12-31 21:18:08 +03:00
m_git_widget = m_action_tab_widget - > add_tab < GitWidget > ( " Git " , m_project - > root_path ( ) ) ;
2022-04-01 20:58:27 +03:00
m_git_widget - > set_view_diff_callback ( [ this ] ( auto const & original_content , auto const & diff ) {
2020-09-19 17:25:05 +03:00
m_diff_viewer - > set_content ( original_content , diff ) ;
set_edit_mode ( EditMode : : Diff ) ;
} ) ;
2021-07-28 22:58:16 +03:00
m_gml_preview_widget = m_action_tab_widget - > add_tab < GMLPreviewWidget > ( " GML Preview " , " " ) ;
2021-05-22 15:14:49 +03:00
ToDoEntries : : the ( ) . on_update = [ this ] ( ) {
m_todo_entries_widget - > refresh ( ) ;
} ;
2020-09-19 17:25:05 +03:00
}
2021-04-10 17:38:11 +03:00
void HackStudioWidget : : create_project_tab ( GUI : : Widget & parent )
{
m_project_tab = parent . add < GUI : : TabWidget > ( ) ;
m_project_tab - > set_tab_position ( GUI : : TabWidget : : TabPosition : : Bottom ) ;
2021-05-10 13:58:49 +03:00
auto & tree_view_container = m_project_tab - > add_tab < GUI : : Widget > ( " Files " ) ;
tree_view_container . set_layout < GUI : : VerticalBoxLayout > ( ) ;
Userland+LibGUI: Add shorthand versions of the Margins constructor
This allows for typing [8] instead of [8, 8, 8, 8] to specify the same
margin on all edges, for example. The constructors follow CSS' style of
specifying margins. The added constructors are:
- Margins(int all): Sets the same margin on all edges.
- Margins(int vertical, int horizontal): Sets the first argument to top
and bottom margins, and the second argument to left and right margins.
- Margins(int top, int vertical, int bottom): Sets the first argument to
the top margin, the second argument to the left and right margins,
and the third argument to the bottom margin.
2021-08-17 03:11:38 +03:00
tree_view_container . layout ( ) - > set_margins ( 2 ) ;
2021-05-10 13:58:49 +03:00
m_project_tree_view = tree_view_container . add < GUI : : TreeView > ( ) ;
2021-04-10 17:38:11 +03:00
configure_project_tree_view ( ) ;
2021-05-10 13:58:49 +03:00
auto & class_view_container = m_project_tab - > add_tab < GUI : : Widget > ( " Classes " ) ;
class_view_container . set_layout < GUI : : VerticalBoxLayout > ( ) ;
Userland+LibGUI: Add shorthand versions of the Margins constructor
This allows for typing [8] instead of [8, 8, 8, 8] to specify the same
margin on all edges, for example. The constructors follow CSS' style of
specifying margins. The added constructors are:
- Margins(int all): Sets the same margin on all edges.
- Margins(int vertical, int horizontal): Sets the first argument to top
and bottom margins, and the second argument to left and right margins.
- Margins(int top, int vertical, int bottom): Sets the first argument to
the top margin, the second argument to the left and right margins,
and the third argument to the bottom margin.
2021-08-17 03:11:38 +03:00
class_view_container . layout ( ) - > set_margins ( 2 ) ;
2021-05-10 13:58:49 +03:00
m_class_view = class_view_container . add < ClassViewWidget > ( ) ;
2021-04-10 17:38:11 +03:00
ProjectDeclarations : : the ( ) . on_update = [ this ] ( ) {
m_class_view - > refresh ( ) ;
} ;
}
2022-02-14 16:08:24 +03:00
void HackStudioWidget : : update_recent_projects_submenu ( )
{
if ( ! m_recent_projects_submenu )
return ;
m_recent_projects_submenu - > remove_all_actions ( ) ;
auto recent_projects = read_recent_projects ( ) ;
if ( recent_projects . size ( ) < = 1 ) {
auto empty_action = GUI : : Action : : create ( " Empty... " , [ ] ( auto & ) { } ) ;
empty_action - > set_enabled ( false ) ;
m_recent_projects_submenu - > add_action ( empty_action ) ;
return ;
}
for ( size_t i = 1 ; i < recent_projects . size ( ) ; i + + ) {
auto project_path = recent_projects [ i ] ;
m_recent_projects_submenu - > add_action ( GUI : : Action : : create ( recent_projects [ i ] , [ this , project_path ] ( auto & ) {
open_project ( project_path ) ;
} ) ) ;
}
}
2021-07-21 22:21:03 +03:00
void HackStudioWidget : : create_file_menu ( GUI : : Window & window )
2020-09-19 17:25:05 +03:00
{
2021-07-21 22:21:03 +03:00
auto & file_menu = window . add_menu ( " &File " ) ;
2022-03-07 06:08:34 +03:00
2022-03-07 06:19:48 +03:00
auto & new_submenu = file_menu . add_submenu ( " &New... " ) ;
2022-03-07 06:08:34 +03:00
new_submenu . add_action ( * m_new_project_action ) ;
new_submenu . add_separator ( ) ;
for ( auto & new_file_action : m_new_file_actions ) {
new_submenu . add_action ( new_file_action ) ;
}
new_submenu . set_icon ( Gfx : : Bitmap : : try_load_from_file ( " /res/icons/16x16/new.png " ) . release_value_but_fixme_should_propagate_errors ( ) ) ;
new_submenu . add_action ( * m_new_plain_file_action ) ;
new_submenu . add_separator ( ) ;
new_submenu . add_action ( * m_new_directory_action ) ;
2021-04-10 11:19:25 +03:00
file_menu . add_action ( * m_open_action ) ;
2022-03-07 06:19:48 +03:00
m_recent_projects_submenu = & file_menu . add_submenu ( " Open &Recent " ) ;
2022-03-01 05:02:45 +03:00
m_recent_projects_submenu - > set_icon ( Gfx : : Bitmap : : try_load_from_file ( " /res/icons/16x16/open-recent.png " ) . release_value_but_fixme_should_propagate_errors ( ) ) ;
2022-02-14 16:08:24 +03:00
update_recent_projects_submenu ( ) ;
2021-04-10 11:19:25 +03:00
file_menu . add_action ( * m_save_action ) ;
2021-08-12 18:07:39 +03:00
file_menu . add_action ( * m_save_as_action ) ;
2021-04-10 11:19:25 +03:00
file_menu . add_separator ( ) ;
file_menu . add_action ( GUI : : CommonActions : : make_quit_action ( [ ] ( auto & ) {
2020-09-19 17:25:05 +03:00
GUI : : Application : : the ( ) - > quit ( ) ;
} ) ) ;
}
2021-07-21 22:21:03 +03:00
void HackStudioWidget : : create_edit_menu ( GUI : : Window & window )
2020-09-19 17:25:05 +03:00
{
2021-07-21 22:21:03 +03:00
auto & edit_menu = window . add_menu ( " &Edit " ) ;
2021-11-06 18:25:29 +03:00
edit_menu . add_action ( GUI : : Action : : create ( " &Find in Files... " , { Mod_Ctrl | Mod_Shift , Key_F } , Gfx : : Bitmap : : try_load_from_file ( " /res/icons/16x16/find.png " ) . release_value_but_fixme_should_propagate_errors ( ) , [ this ] ( auto & ) {
2020-09-19 17:25:05 +03:00
reveal_action_tab ( * m_find_in_files_widget ) ;
m_find_in_files_widget - > focus_textbox_and_select_all ( ) ;
} ) ) ;
2020-10-26 14:20:03 +03:00
edit_menu . add_separator ( ) ;
2021-04-10 11:19:25 +03:00
auto vim_emulation_setting_action = GUI : : Action : : create_checkable ( " &Vim Emulation " , { Mod_Ctrl | Mod_Shift | Mod_Alt , Key_V } , [ this ] ( auto & action ) {
2021-12-09 01:28:12 +03:00
if ( action . is_checked ( ) ) {
for ( auto & editor_wrapper : m_all_editor_wrappers )
editor_wrapper . editor ( ) . set_editing_engine ( make < GUI : : VimEditingEngine > ( ) ) ;
} else {
for ( auto & editor_wrapper : m_all_editor_wrappers )
editor_wrapper . editor ( ) . set_editing_engine ( make < GUI : : RegularEditingEngine > ( ) ) ;
}
2021-01-02 13:59:55 +03:00
} ) ;
vim_emulation_setting_action - > set_checked ( false ) ;
edit_menu . add_action ( vim_emulation_setting_action ) ;
2022-03-19 14:47:18 +03:00
edit_menu . add_separator ( ) ;
edit_menu . add_action ( * m_open_project_configuration_action ) ;
2020-09-19 17:25:05 +03:00
}
2021-07-21 22:21:03 +03:00
void HackStudioWidget : : create_build_menu ( GUI : : Window & window )
2020-09-19 17:25:05 +03:00
{
2021-07-21 22:21:03 +03:00
auto & build_menu = window . add_menu ( " &Build " ) ;
2020-09-19 17:25:05 +03:00
build_menu . add_action ( * m_build_action ) ;
build_menu . add_separator ( ) ;
build_menu . add_action ( * m_run_action ) ;
build_menu . add_action ( * m_stop_action ) ;
build_menu . add_separator ( ) ;
build_menu . add_action ( * m_debug_action ) ;
}
2021-07-21 22:21:03 +03:00
void HackStudioWidget : : create_view_menu ( GUI : : Window & window )
2020-09-19 17:25:05 +03:00
{
2021-04-10 11:19:25 +03:00
auto hide_action_tabs_action = GUI : : Action : : create ( " &Hide Action Tabs " , { Mod_Ctrl | Mod_Shift , Key_X } , [ this ] ( auto & ) {
2020-09-19 17:25:05 +03:00
hide_action_tabs ( ) ;
} ) ;
2021-04-10 11:19:25 +03:00
auto open_locator_action = GUI : : Action : : create ( " Open &Locator " , { Mod_Ctrl , Key_K } , [ this ] ( auto & ) {
2020-09-19 17:25:05 +03:00
m_locator - > open ( ) ;
} ) ;
2021-10-29 00:14:15 +03:00
auto show_dotfiles_action = GUI : : Action : : create_checkable ( " S&how Dotfiles " , { Mod_Ctrl , Key_H } , [ & ] ( auto & checked ) {
project ( ) . model ( ) . set_should_show_dotfiles ( checked . is_checked ( ) ) ;
2022-03-19 14:53:02 +03:00
Config : : write_bool ( " HackStudio " , " Global " , " ShowDotfiles " , checked . is_checked ( ) ) ;
2021-10-29 00:14:15 +03:00
} ) ;
2022-03-19 14:53:02 +03:00
show_dotfiles_action - > set_checked ( Config : : read_bool ( " HackStudio " , " Global " , " ShowDotfiles " , false ) ) ;
2020-09-19 17:25:05 +03:00
2021-07-21 22:21:03 +03:00
auto & view_menu = window . add_menu ( " &View " ) ;
2020-09-19 17:25:05 +03:00
view_menu . add_action ( hide_action_tabs_action ) ;
view_menu . add_action ( open_locator_action ) ;
2021-10-29 00:14:15 +03:00
view_menu . add_action ( show_dotfiles_action ) ;
2022-03-07 06:08:34 +03:00
m_toggle_semantic_highlighting_action = create_toggle_syntax_highlighting_mode_action ( ) ;
view_menu . add_action ( * m_toggle_semantic_highlighting_action ) ;
2021-02-26 15:19:19 +03:00
view_menu . add_separator ( ) ;
m_wrapping_mode_actions . set_exclusive ( true ) ;
2021-04-10 11:19:25 +03:00
auto & wrapping_mode_menu = view_menu . add_submenu ( " &Wrapping Mode " ) ;
m_no_wrapping_action = GUI : : Action : : create_checkable ( " &No Wrapping " , [ & ] ( auto & ) {
2021-02-26 15:19:19 +03:00
for ( auto & wrapper : m_all_editor_wrappers )
wrapper . editor ( ) . set_wrapping_mode ( GUI : : TextEditor : : WrappingMode : : NoWrap ) ;
} ) ;
2021-04-10 11:19:25 +03:00
m_wrap_anywhere_action = GUI : : Action : : create_checkable ( " Wrap &Anywhere " , [ & ] ( auto & ) {
2021-02-26 15:19:19 +03:00
for ( auto & wrapper : m_all_editor_wrappers )
wrapper . editor ( ) . set_wrapping_mode ( GUI : : TextEditor : : WrappingMode : : WrapAnywhere ) ;
} ) ;
2021-04-10 11:19:25 +03:00
m_wrap_at_words_action = GUI : : Action : : create_checkable ( " Wrap at &Words " , [ & ] ( auto & ) {
2021-02-26 15:19:19 +03:00
for ( auto & wrapper : m_all_editor_wrappers )
wrapper . editor ( ) . set_wrapping_mode ( GUI : : TextEditor : : WrappingMode : : WrapAtWords ) ;
} ) ;
m_wrapping_mode_actions . add_action ( * m_no_wrapping_action ) ;
m_wrapping_mode_actions . add_action ( * m_wrap_anywhere_action ) ;
m_wrapping_mode_actions . add_action ( * m_wrap_at_words_action ) ;
wrapping_mode_menu . add_action ( * m_no_wrapping_action ) ;
wrapping_mode_menu . add_action ( * m_wrap_anywhere_action ) ;
wrapping_mode_menu . add_action ( * m_wrap_at_words_action ) ;
m_no_wrapping_action - > set_checked ( true ) ;
2021-11-06 18:25:29 +03:00
m_editor_font_action = GUI : : Action : : create ( " Editor &Font... " , Gfx : : Bitmap : : try_load_from_file ( " /res/icons/16x16/app-font-editor.png " ) . release_value_but_fixme_should_propagate_errors ( ) ,
2021-10-06 17:01:52 +03:00
[ & ] ( auto & ) {
auto picker = GUI : : FontPicker : : construct ( & window , m_editor_font , false ) ;
2022-05-13 15:10:27 +03:00
if ( picker - > exec ( ) = = GUI : : Dialog : : ExecResult : : OK ) {
2021-10-06 17:01:52 +03:00
change_editor_font ( picker - > font ( ) ) ;
}
} ) ;
view_menu . add_action ( * m_editor_font_action ) ;
2020-09-19 17:25:05 +03:00
view_menu . add_separator ( ) ;
2022-03-08 03:30:21 +03:00
view_menu . add_action ( * m_add_editor_tab_widget_action ) ;
2020-09-19 17:25:05 +03:00
view_menu . add_action ( * m_add_editor_action ) ;
view_menu . add_action ( * m_remove_current_editor_action ) ;
view_menu . add_action ( * m_add_terminal_action ) ;
view_menu . add_action ( * m_remove_current_terminal_action ) ;
2021-08-27 16:26:18 +03:00
view_menu . add_separator ( ) ;
create_location_history_actions ( ) ;
view_menu . add_action ( * m_locations_history_back_action ) ;
view_menu . add_action ( * m_locations_history_forward_action ) ;
2022-03-29 16:45:26 +03:00
auto search_action = GUI : : Action : : create ( " &Search " , { Mod_Ctrl , Key_F } , [ this ] ( auto & ) {
current_editor_wrapper ( ) . search_action ( ) ;
} ) ;
view_menu . add_action ( search_action ) ;
2020-09-19 17:25:05 +03:00
}
2021-07-21 22:21:03 +03:00
void HackStudioWidget : : create_help_menu ( GUI : : Window & window )
2020-09-19 17:25:05 +03:00
{
2021-07-21 22:21:03 +03:00
auto & help_menu = window . add_menu ( " &Help " ) ;
help_menu . add_action ( GUI : : CommonActions : : make_about_action ( " Hack Studio " , GUI : : Icon : : default_icon ( " app-hack-studio " ) , & window ) ) ;
2020-09-19 17:25:05 +03:00
}
NonnullRefPtr < GUI : : Action > HackStudioWidget : : create_stop_action ( )
{
2021-11-06 18:25:29 +03:00
auto action = GUI : : Action : : create ( " &Stop " , Gfx : : Bitmap : : try_load_from_file ( " /res/icons/16x16/program-stop.png " ) . release_value_but_fixme_should_propagate_errors ( ) , [ this ] ( auto & ) {
2021-04-14 00:01:30 +03:00
if ( ! Debugger : : the ( ) . session ( ) ) {
2022-03-02 17:16:53 +03:00
if ( auto result = m_terminal_wrapper - > kill_running_command ( ) ; result . is_error ( ) )
warnln ( " {} " , result . error ( ) ) ;
2021-04-14 00:01:30 +03:00
return ;
}
Debugger : : the ( ) . stop ( ) ;
2020-09-19 17:25:05 +03:00
} ) ;
action - > set_enabled ( false ) ;
return action ;
}
2021-07-21 22:21:03 +03:00
void HackStudioWidget : : initialize_menubar ( GUI : : Window & window )
2020-09-19 17:25:05 +03:00
{
2021-07-21 22:21:03 +03:00
create_file_menu ( window ) ;
create_edit_menu ( window ) ;
create_build_menu ( window ) ;
create_view_menu ( window ) ;
create_help_menu ( window ) ;
2020-09-19 17:25:05 +03:00
}
2021-07-02 03:39:17 +03:00
void HackStudioWidget : : update_statusbar ( )
{
StringBuilder builder ;
if ( current_editor ( ) . has_selection ( ) ) {
String selected_text = current_editor ( ) . selected_text ( ) ;
auto word_count = current_editor ( ) . number_of_selected_words ( ) ;
builder . appendff ( " Selected: {} {} ({} {}) " , selected_text . length ( ) , selected_text . length ( ) = = 1 ? " character " : " characters " , word_count , word_count ! = 1 ? " words " : " word " ) ;
}
2022-02-24 02:46:42 +03:00
m_statusbar - > set_text ( 0 , builder . to_string ( ) ) ;
m_statusbar - > set_text ( 1 , current_editor_wrapper ( ) . editor ( ) . code_document ( ) . language_name ( ) ) ;
m_statusbar - > set_text ( 2 , String : : formatted ( " Ln {}, Col {} " , current_editor ( ) . cursor ( ) . line ( ) + 1 , current_editor ( ) . cursor ( ) . column ( ) ) ) ;
2021-07-02 03:39:17 +03:00
}
2022-04-01 20:58:27 +03:00
void HackStudioWidget : : handle_external_file_deletion ( String const & filepath )
2021-04-28 17:01:14 +03:00
{
2021-08-01 15:01:49 +03:00
close_file_in_all_editors ( filepath ) ;
2021-04-28 17:01:14 +03:00
}
2021-08-02 03:11:42 +03:00
void HackStudioWidget : : stop_debugger_if_running ( )
2020-09-26 11:23:49 +03:00
{
if ( ! m_debugger_thread . is_null ( ) ) {
2021-04-14 00:01:30 +03:00
Debugger : : the ( ) . stop ( ) ;
2020-10-08 14:41:36 +03:00
dbgln ( " Waiting for debugger thread to terminate " ) ;
2021-01-01 06:58:30 +03:00
auto rc = m_debugger_thread - > join ( ) ;
if ( rc . is_error ( ) ) {
warnln ( " pthread_join: {} " , strerror ( rc . error ( ) . value ( ) ) ) ;
2020-10-08 14:41:36 +03:00
dbgln ( " error joining debugger thread " ) ;
2020-09-26 11:23:49 +03:00
}
}
}
2021-08-02 22:14:15 +03:00
void HackStudioWidget : : close_current_project ( )
{
m_editors_splitter - > remove_all_children ( ) ;
2022-06-20 20:06:31 +03:00
m_all_editor_tab_widgets . clear ( ) ;
2021-08-02 22:14:15 +03:00
m_all_editor_wrappers . clear ( ) ;
2022-06-20 20:06:31 +03:00
add_new_editor_tab_widget ( * m_editors_splitter ) ;
2021-08-02 22:14:15 +03:00
m_open_files . clear ( ) ;
m_open_files_vector . clear ( ) ;
m_find_in_files_widget - > reset ( ) ;
m_todo_entries_widget - > clear ( ) ;
m_terminal_wrapper - > clear_including_history ( ) ;
stop_debugger_if_running ( ) ;
update_gml_preview ( ) ;
}
2021-08-02 03:11:42 +03:00
HackStudioWidget : : ~ HackStudioWidget ( )
{
stop_debugger_if_running ( ) ;
}
2022-04-01 20:58:27 +03:00
HackStudioWidget : : ContinueDecision HackStudioWidget : : warn_unsaved_changes ( String const & prompt )
2021-05-01 13:42:07 +03:00
{
if ( ! any_document_is_dirty ( ) )
return ContinueDecision : : Yes ;
auto result = GUI : : MessageBox : : show ( window ( ) , prompt , " Unsaved changes " , GUI : : MessageBox : : Type : : Warning , GUI : : MessageBox : : InputType : : YesNoCancel ) ;
2022-05-13 15:10:27 +03:00
if ( result = = GUI : : MessageBox : : ExecResult : : Cancel )
2021-05-01 13:42:07 +03:00
return ContinueDecision : : No ;
2022-05-13 15:10:27 +03:00
if ( result = = GUI : : MessageBox : : ExecResult : : Yes ) {
2021-05-01 13:42:07 +03:00
for ( auto & editor_wrapper : m_all_editor_wrappers ) {
2021-09-01 17:11:56 +03:00
if ( editor_wrapper . editor ( ) . document ( ) . is_modified ( ) ) {
2021-05-01 13:42:07 +03:00
editor_wrapper . save ( ) ;
}
}
}
return ContinueDecision : : Yes ;
}
bool HackStudioWidget : : any_document_is_dirty ( ) const
{
2021-09-01 17:11:56 +03:00
return any_of ( m_all_editor_wrappers , [ ] ( auto & editor_wrapper ) {
return editor_wrapper . editor ( ) . document ( ) . is_modified ( ) ;
} ) ;
2021-05-01 13:42:07 +03:00
}
2021-07-28 22:58:16 +03:00
void HackStudioWidget : : update_gml_preview ( )
{
auto gml_content = current_editor_wrapper ( ) . filename ( ) . ends_with ( " .gml " ) ? current_editor_wrapper ( ) . editor ( ) . text ( ) : " " ;
m_gml_preview_widget - > load_gml ( gml_content ) ;
}
2021-08-22 20:38:05 +03:00
void HackStudioWidget : : update_tree_view ( )
{
auto index = m_project - > model ( ) . index ( m_current_editor_wrapper - > filename ( ) , GUI : : FileSystemModel : : Column : : Name ) ;
if ( index . is_valid ( ) ) {
m_project_tree_view - > expand_all_parents_of ( index ) ;
m_project_tree_view - > set_cursor ( index , GUI : : AbstractView : : SelectionUpdate : : Set ) ;
}
}
2021-08-13 14:10:58 +03:00
void HackStudioWidget : : update_window_title ( )
{
2022-03-08 03:30:21 +03:00
window ( ) - > set_title ( String : : formatted ( " {} - {} - Hack Studio " , m_current_editor_wrapper - > filename_title ( ) , m_project - > name ( ) ) ) ;
2022-04-15 19:37:24 +03:00
window ( ) - > set_modified ( any_document_is_dirty ( ) ) ;
2022-03-08 03:30:21 +03:00
}
void HackStudioWidget : : update_current_editor_title ( )
{
current_editor_tab_widget ( ) . set_tab_title ( current_editor_wrapper ( ) , current_editor_wrapper ( ) . filename_title ( ) ) ;
2021-08-13 14:10:58 +03:00
}
2021-08-27 16:26:18 +03:00
void HackStudioWidget : : on_cursor_change ( )
{
update_statusbar ( ) ;
if ( current_editor_wrapper ( ) . filename ( ) . is_null ( ) )
return ;
auto current_location = current_project_location ( ) ;
if ( m_locations_history_end_index ! = 0 ) {
auto last = m_locations_history [ m_locations_history_end_index - 1 ] ;
if ( current_location . filename = = last . filename & & current_location . line = = last . line )
return ;
}
// Clear "Go Forward" locations
VERIFY ( m_locations_history_end_index < = m_locations_history . size ( ) ) ;
m_locations_history . remove ( m_locations_history_end_index , m_locations_history . size ( ) - m_locations_history_end_index ) ;
m_locations_history . append ( current_location ) ;
constexpr size_t max_locations = 30 ;
if ( m_locations_history . size ( ) > max_locations )
m_locations_history . take_first ( ) ;
m_locations_history_end_index = m_locations_history . size ( ) ;
update_history_actions ( ) ;
}
void HackStudioWidget : : create_location_history_actions ( )
{
2021-11-06 18:25:29 +03:00
m_locations_history_back_action = GUI : : Action : : create ( " Go Back " , { Mod_Alt | Mod_Shift , Key_Left } , Gfx : : Bitmap : : try_load_from_file ( " /res/icons/16x16/go-back.png " ) . release_value_but_fixme_should_propagate_errors ( ) , [ this ] ( auto & ) {
2021-08-27 16:26:18 +03:00
if ( m_locations_history_end_index < = 1 )
return ;
auto location = m_locations_history [ m_locations_history_end_index - 2 ] ;
- - m_locations_history_end_index ;
m_locations_history_disabled = true ;
open_file ( location . filename , location . line , location . column ) ;
m_locations_history_disabled = false ;
update_history_actions ( ) ;
} ) ;
2021-11-06 18:25:29 +03:00
m_locations_history_forward_action = GUI : : Action : : create ( " Go Forward " , { Mod_Alt | Mod_Shift , Key_Right } , Gfx : : Bitmap : : try_load_from_file ( " /res/icons/16x16/go-forward.png " ) . release_value_but_fixme_should_propagate_errors ( ) , [ this ] ( auto & ) {
2021-08-27 16:26:18 +03:00
if ( m_locations_history_end_index = = m_locations_history . size ( ) )
return ;
auto location = m_locations_history [ m_locations_history_end_index ] ;
+ + m_locations_history_end_index ;
m_locations_history_disabled = true ;
open_file ( location . filename , location . line , location . column ) ;
m_locations_history_disabled = false ;
update_history_actions ( ) ;
} ) ;
m_locations_history_forward_action - > set_enabled ( false ) ;
}
2022-03-19 14:47:18 +03:00
NonnullRefPtr < GUI : : Action > HackStudioWidget : : create_open_project_configuration_action ( )
{
return GUI : : Action : : create ( " Project Configuration " , Gfx : : Bitmap : : try_load_from_file ( " /res/icons/16x16/settings.png " ) . release_value ( ) , [ & ] ( auto & ) {
auto parent_directory = LexicalPath : : dirname ( Project : : config_file_path ) ;
auto absolute_config_file_path = LexicalPath : : absolute_path ( m_project - > root_path ( ) , Project : : config_file_path ) ;
if ( ! Core : : File : : exists ( absolute_config_file_path ) ) {
if ( Core : : File : : exists ( parent_directory ) & & ! Core : : File : : is_directory ( parent_directory ) ) {
GUI : : MessageBox : : show_error ( window ( ) , String : : formatted ( " Cannot create the '{}' directory because there is already a file with that name " , parent_directory ) ) ;
return ;
}
mkdir ( LexicalPath : : absolute_path ( m_project - > root_path ( ) , parent_directory ) . characters ( ) , 0755 ) ;
auto file = Core : : File : : open ( absolute_config_file_path , Core : : OpenMode : : WriteOnly ) ;
file . value ( ) - > write (
" { \n "
" \" build_command \" : \" your build command here \" , \n "
" \" run_command \" : \" your run command here \" \n "
" } \n " ) ;
file . value ( ) - > close ( ) ;
}
open_file ( Project : : config_file_path ) ;
} ) ;
}
2021-08-27 16:26:18 +03:00
HackStudioWidget : : ProjectLocation HackStudioWidget : : current_project_location ( ) const
{
return ProjectLocation { current_editor_wrapper ( ) . filename ( ) , current_editor ( ) . cursor ( ) . line ( ) , current_editor ( ) . cursor ( ) . column ( ) } ;
}
void HackStudioWidget : : update_history_actions ( )
{
if ( m_locations_history_end_index < = 1 )
m_locations_history_back_action - > set_enabled ( false ) ;
else
m_locations_history_back_action - > set_enabled ( true ) ;
if ( m_locations_history_end_index = = m_locations_history . size ( ) )
m_locations_history_forward_action - > set_enabled ( false ) ;
else
m_locations_history_forward_action - > set_enabled ( true ) ;
}
2021-10-06 17:01:52 +03:00
RefPtr < Gfx : : Font > HackStudioWidget : : read_editor_font_from_config ( )
{
2021-10-06 17:53:43 +03:00
auto font_family = Config : : read_string ( " HackStudio " , " EditorFont " , " Family " , " Csilla " ) ;
auto font_variant = Config : : read_string ( " HackStudio " , " EditorFont " , " Variant " , " Regular " ) ;
auto font_size = Config : : read_i32 ( " HackStudio " , " EditorFont " , " Size " , 10 ) ;
auto font = Gfx : : FontDatabase : : the ( ) . get ( font_family , font_variant , font_size ) ;
if ( font . is_null ( ) )
return Gfx : : FontDatabase : : the ( ) . default_fixed_width_font ( ) ;
return font ;
2021-10-06 17:01:52 +03:00
}
void HackStudioWidget : : change_editor_font ( RefPtr < Gfx : : Font > font )
{
m_editor_font = move ( font ) ;
for ( auto & editor_wrapper : m_all_editor_wrappers ) {
editor_wrapper . editor ( ) . set_font ( * m_editor_font ) ;
}
2021-10-06 17:53:43 +03:00
Config : : write_string ( " HackStudio " , " EditorFont " , " Family " , m_editor_font - > family ( ) ) ;
Config : : write_string ( " HackStudio " , " EditorFont " , " Variant " , m_editor_font - > variant ( ) ) ;
Config : : write_i32 ( " HackStudio " , " EditorFont " , " Size " , m_editor_font - > presentation_size ( ) ) ;
2021-10-06 17:01:52 +03:00
}
2021-11-19 17:13:07 +03:00
void HackStudioWidget : : open_coredump ( String const & coredump_path )
{
open_project ( " /usr/src/serenity " ) ;
m_mode = Mode : : Coredump ;
m_coredump_inspector = Coredump : : Inspector : : create ( coredump_path , [ this ] ( float progress ) {
window ( ) - > set_progress ( progress * 100 ) ;
} ) ;
window ( ) - > set_progress ( 0 ) ;
if ( m_coredump_inspector ) {
m_debug_info_widget - > update_state ( * m_coredump_inspector , m_coredump_inspector - > get_registers ( ) ) ;
reveal_action_tab ( * m_debug_info_widget ) ;
}
}
2021-12-03 10:38:03 +03:00
void HackStudioWidget : : for_each_open_file ( Function < void ( ProjectFile const & ) > func )
{
for ( auto & open_file : m_open_files ) {
func ( * open_file . value ) ;
}
}
2022-02-07 09:49:07 +03:00
NonnullRefPtr < GUI : : Action > HackStudioWidget : : create_toggle_syntax_highlighting_mode_action ( )
{
auto action = GUI : : Action : : create_checkable ( " &Semantic Highlighting " , Gfx : : Bitmap : : try_load_from_file ( " /res/icons/16x16/filetype-cplusplus.png " ) . release_value_but_fixme_should_propagate_errors ( ) , [ this ] ( auto & action ) {
for ( auto & editor_wrapper : m_all_editor_wrappers )
editor_wrapper . editor ( ) . set_semantic_syntax_highlighting ( action . is_checked ( ) ) ;
} ) ;
return action ;
}
2022-02-12 12:20:25 +03:00
bool HackStudioWidget : : semantic_syntax_highlighting_is_enabled ( ) const
{
return m_toggle_semantic_highlighting_action - > is_checked ( ) ;
}
2020-09-19 17:25:05 +03:00
}