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 >
2023-07-28 04:12:40 +03:00
* Copyright ( c ) 2023 - 2024 , Abhishek R . < raturiabhi1000 @ 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 "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>
2021-04-28 17:01:14 +03:00
# include <LibCore/FileWatcher.h>
2022-12-18 21:36:23 +03:00
# include <LibCore/System.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>
2023-03-21 18:35:30 +03:00
# include <LibFileSystem/FileSystem.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 <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 {
2023-12-16 17:19:34 +03:00
ErrorOr < NonnullRefPtr < HackStudioWidget > > HackStudioWidget : : create ( ByteString path_to_project )
2020-09-19 17:25:05 +03:00
{
2022-12-16 03:57:49 +03:00
auto widget = TRY ( adopt_nonnull_ref_or_enomem ( new ( nothrow ) HackStudioWidget ) ) ;
2020-09-19 17:25:05 +03:00
2022-12-16 03:57:49 +03:00
widget - > m_editor_font = widget - > read_editor_font_from_config ( ) ;
widget - > set_fill_with_background_color ( true ) ;
2023-02-17 00:07:06 +03:00
widget - > set_layout < GUI : : VerticalBoxLayout > ( GUI : : Margins { } , 2 ) ;
2020-09-19 17:25:05 +03:00
2022-12-16 03:57:49 +03:00
auto & toolbar_container = widget - > add < GUI : : ToolbarContainer > ( ) ;
auto & outer_splitter = widget - > 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 ) ;
2022-08-30 14:38:20 +03:00
left_hand_splitter . set_preferred_width ( 150 ) ;
2022-12-18 04:48:17 +03:00
widget - > m_project_tree_view_context_menu = TRY ( widget - > create_project_tree_view_context_menu ( ) ) ;
2020-09-19 17:25:05 +03:00
2022-12-16 03:57:49 +03:00
widget - > m_right_hand_splitter = outer_splitter . add < GUI : : VerticalSplitter > ( ) ;
widget - > m_right_hand_stack = widget - > m_right_hand_splitter - > add < GUI : : StackWidget > ( ) ;
2020-12-10 20:59:03 +03:00
2022-12-22 14:55:11 +03:00
TRY ( widget - > create_action_tab ( * widget - > m_right_hand_splitter ) ) ;
widget - > open_project ( path_to_project ) ;
widget - > create_project_tab ( left_hand_splitter ) ;
widget - > create_open_files_view ( left_hand_splitter ) ;
2020-12-10 20:59:03 +03:00
// Put a placeholder widget front & center since we don't have a file open yet.
2022-12-16 03:57:49 +03:00
widget - > m_right_hand_stack - > add < GUI : : Widget > ( ) ;
2020-12-10 20:59:03 +03:00
2022-12-16 03:57:49 +03:00
widget - > m_diff_viewer = widget - > m_right_hand_stack - > add < DiffViewer > ( ) ;
2020-09-19 17:25:05 +03:00
2022-12-16 03:57:49 +03:00
widget - > m_editors_splitter = widget - > m_right_hand_stack - > add < GUI : : VerticalSplitter > ( ) ;
widget - > m_editors_splitter - > layout ( ) - > set_margins ( { 3 , 0 , 0 } ) ;
widget - > add_new_editor_tab_widget ( * widget - > m_editors_splitter ) ;
2020-09-19 17:25:05 +03:00
2022-12-16 03:57:49 +03:00
widget - > m_switch_to_next_editor_tab_widget = widget - > create_switch_to_next_editor_tab_widget_action ( ) ;
widget - > m_switch_to_next_editor = widget - > create_switch_to_next_editor_action ( ) ;
widget - > m_switch_to_previous_editor = widget - > create_switch_to_previous_editor_action ( ) ;
2020-09-19 17:25:05 +03:00
2022-12-16 03:57:49 +03:00
widget - > m_remove_current_editor_tab_widget_action = widget - > create_remove_current_editor_tab_widget_action ( ) ;
2022-12-18 04:48:17 +03:00
widget - > m_remove_current_editor_action = TRY ( widget - > create_remove_current_editor_action ( ) ) ;
widget - > m_open_action = TRY ( widget - > create_open_action ( ) ) ;
2022-12-16 03:57:49 +03:00
widget - > m_save_action = widget - > create_save_action ( ) ;
widget - > m_save_as_action = widget - > create_save_as_action ( ) ;
2022-12-18 04:48:17 +03:00
widget - > m_new_project_action = TRY ( widget - > create_new_project_action ( ) ) ;
2020-09-19 17:25:05 +03:00
2022-12-16 03:57:49 +03:00
widget - > m_add_editor_tab_widget_action = widget - > create_add_editor_tab_widget_action ( ) ;
2022-12-18 04:48:17 +03:00
widget - > m_add_editor_action = TRY ( widget - > create_add_editor_action ( ) ) ;
widget - > m_add_terminal_action = TRY ( widget - > create_add_terminal_action ( ) ) ;
widget - > m_remove_current_terminal_action = TRY ( widget - > create_remove_current_terminal_action ( ) ) ;
2020-09-19 17:25:05 +03:00
2022-12-16 03:57:49 +03:00
widget - > m_locator = widget - > add < Locator > ( ) ;
2020-09-19 17:25:05 +03:00
2022-12-16 03:57:49 +03:00
widget - > m_terminal_wrapper - > on_command_exit = [ widget ] {
widget - > m_stop_action - > set_enabled ( false ) ;
2020-09-19 17:25:05 +03:00
} ;
2022-12-18 04:48:17 +03:00
widget - > m_open_project_configuration_action = TRY ( widget - > create_open_project_configuration_action ( ) ) ;
widget - > m_build_action = TRY ( widget - > create_build_action ( ) ) ;
widget - > m_run_action = TRY ( widget - > create_run_action ( ) ) ;
widget - > m_stop_action = TRY ( widget - > create_stop_action ( ) ) ;
widget - > m_debug_action = TRY ( widget - > create_debug_action ( ) ) ;
2020-09-19 17:25:05 +03:00
2022-12-16 03:57:49 +03:00
widget - > initialize_debugger ( ) ;
2020-09-19 17:25:05 +03:00
2022-12-16 03:57:49 +03:00
widget - > create_toolbar ( toolbar_container ) ;
2021-05-09 20:09:30 +03:00
2022-12-16 03:57:49 +03:00
widget - > m_statusbar = widget - > add < GUI : : Statusbar > ( 3 ) ;
widget - > m_statusbar - > segment ( 1 ) . set_mode ( GUI : : Statusbar : : Segment : : Mode : : Auto ) ;
widget - > m_statusbar - > segment ( 2 ) . set_mode ( GUI : : Statusbar : : Segment : : Mode : : Fixed ) ;
2023-04-11 19:39:26 +03:00
auto width = widget - > font ( ) . width ( " Ln 0,000 Col 000 " sv ) + widget - > font ( ) . max_glyph_width ( ) ;
2022-12-16 03:57:49 +03:00
widget - > m_statusbar - > segment ( 2 ) . set_fixed_width ( width ) ;
widget - > update_statusbar ( ) ;
2022-02-24 02:46:42 +03:00
2022-12-16 03:57:49 +03:00
GUI : : Application : : the ( ) - > on_action_enter = [ widget ] ( GUI : : Action & action ) {
2023-06-04 13:10:25 +03:00
widget - > m_statusbar - > set_override_text ( action . status_tip ( ) ) ;
2022-02-24 02:46:42 +03:00
} ;
2022-12-16 03:57:49 +03:00
GUI : : Application : : the ( ) - > on_action_leave = [ widget ] ( GUI : : Action & ) {
widget - > m_statusbar - > set_override_text ( { } ) ;
2022-02-24 02:46:42 +03:00
} ;
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 {
2022-12-16 03:57:49 +03:00
widget - > m_file_watcher = maybe_watcher . release_value ( ) ;
widget - > m_file_watcher - > on_change = [ widget ] ( Core : : FileWatcherEvent const & event ) {
2021-05-09 20:09:30 +03:00
if ( event . type ! = Core : : FileWatcherEvent : : Type : : Deleted )
return ;
2022-12-16 03:57:49 +03:00
if ( event . event_path . starts_with ( widget - > project ( ) . root_path ( ) ) ) {
2023-12-16 17:19:34 +03:00
ByteString relative_path = LexicalPath : : relative_path ( event . event_path , widget - > project ( ) . root_path ( ) ) ;
2022-12-16 03:57:49 +03:00
widget - > handle_external_file_deletion ( relative_path ) ;
2021-05-09 20:09:30 +03:00
} else {
2022-12-16 03:57:49 +03:00
widget - > handle_external_file_deletion ( event . event_path ) ;
2021-05-09 20:09:30 +03:00
}
} ;
}
2022-01-07 18:23:50 +03:00
2022-12-16 03:57:49 +03:00
widget - > project ( ) . model ( ) . set_should_show_dotfiles ( Config : : read_bool ( " HackStudio " sv , " Global " sv , " ShowDotfiles " sv , false ) ) ;
return widget ;
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
}
2023-12-16 17:19:34 +03:00
Vector < ByteString > HackStudioWidget : : read_recent_projects ( )
2022-02-14 16:08:24 +03:00
{
2022-07-11 20:32:29 +03:00
auto json = Config : : read_string ( " HackStudio " sv , " Global " sv , " RecentProjects " sv ) ;
2022-02-14 16:08:24 +03:00
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 { } ;
2023-12-16 17:19:34 +03:00
Vector < ByteString > paths ;
2022-02-14 16:08:24 +03:00
for ( auto & json_value : value . as_array ( ) . values ( ) ) {
if ( ! json_value . is_string ( ) )
return { } ;
paths . append ( json_value . as_string ( ) ) ;
}
return paths ;
}
2023-12-16 17:19:34 +03:00
void HackStudioWidget : : open_project ( ByteString 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 ;
2024-01-15 20:50:48 +03:00
auto absolute_root_path = FileSystem : : absolute_path ( root_path ) . release_value_but_fixme_should_propagate_errors ( ) ;
if ( auto result = Core : : System : : chdir ( absolute_root_path ) ; result . is_error ( ) ) {
2024-01-11 19:55:49 +03:00
warnln ( " Failed to open project: {} " , result . release_error ( ) ) ;
2020-09-19 17:25:05 +03:00
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
}
2024-01-15 20:50:48 +03:00
m_project = Project : : open_with_root_path ( absolute_root_path ) ;
2021-02-23 22:42:32 +03:00
VERIFY ( m_project ) ;
2022-12-22 14:58:10 +03:00
m_project_builder = make < ProjectBuilder > ( * m_terminal_wrapper , * 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 ( ) ;
}
2022-12-22 14:55:11 +03:00
if ( m_git_widget - > initialized ( ) ) {
2024-01-15 20:50:48 +03:00
m_git_widget - > change_repo ( absolute_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 )
2023-03-06 16:17:01 +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 ( ) ;
2024-01-15 20:50:48 +03:00
recent_projects . remove_all_matching ( [ & ] ( auto & p ) { return p = = absolute_root_path ; } ) ;
recent_projects . insert ( 0 , absolute_root_path ) ;
2022-02-14 16:08:24 +03:00
if ( recent_projects . size ( ) > recent_projects_history_size )
recent_projects . shrink ( recent_projects_history_size ) ;
2023-12-16 17:19:34 +03:00
Config : : write_string ( " HackStudio " sv , " Global " sv , " RecentProjects " sv , JsonArray ( recent_projects ) . to_byte_string ( ) ) ;
2022-02-14 16:08:24 +03:00
update_recent_projects_submenu ( ) ;
2020-09-19 17:25:05 +03:00
}
2023-12-16 17:19:34 +03:00
Vector < ByteString > HackStudioWidget : : selected_file_paths ( ) const
2020-09-19 17:25:05 +03:00
{
2023-12-16 17:19:34 +03:00
Vector < ByteString > files ;
2020-09-19 17:25:05 +03:00
m_project_tree_view - > selection ( ) . for_each_index ( [ & ] ( const GUI : : ModelIndex & index ) {
2023-12-16 17:19:34 +03:00
ByteString sub_path = index . data ( ) . as_string ( ) ;
2021-03-31 03:54:39 +03:00
GUI : : ModelIndex parent_or_invalid = index . parent ( ) ;
while ( parent_or_invalid . is_valid ( ) ) {
2023-12-16 17:19:34 +03:00
sub_path = ByteString : : formatted ( " {}/{} " , parent_or_invalid . data ( ) . as_string ( ) , sub_path ) ;
2021-03-31 03:54:39 +03:00
parent_or_invalid = parent_or_invalid . parent ( ) ;
}
files . append ( sub_path ) ;
2020-09-19 17:25:05 +03:00
} ) ;
return files ;
}
2023-12-16 17:19:34 +03:00
bool HackStudioWidget : : open_file ( ByteString const & full_filename , size_t line , size_t column )
2020-09-19 17:25:05 +03:00
{
2023-12-16 17:19:34 +03:00
ByteString filename = full_filename ;
2021-02-20 13:16:32 +03:00
if ( full_filename . starts_with ( project ( ) . root_path ( ) ) ) {
filename = LexicalPath : : relative_path ( full_filename , project ( ) . root_path ( ) ) ;
}
2023-03-21 18:35:30 +03:00
if ( FileSystem : : is_directory ( filename ) | | ! FileSystem : : exists ( filename ) )
2021-04-15 19:42:40 +03:00
return false ;
2021-02-02 00:05:14 +03:00
2022-09-12 23:32:27 +03:00
auto editor_wrapper_or_none = m_all_editor_wrappers . first_matching ( [ & ] ( auto & wrapper ) {
return wrapper - > filename ( ) = = filename ;
} ) ;
if ( editor_wrapper_or_none . has_value ( ) ) {
set_current_editor_wrapper ( editor_wrapper_or_none . release_value ( ) ) ;
2024-01-19 15:56:05 +03:00
current_editor ( ) . set_cursor_and_focus_line ( line , column ) ;
2022-09-12 23:32:27 +03:00
return true ;
2022-11-30 14:20:04 +03:00
} else if ( active_file ( ) . is_empty ( ) & & ! current_editor ( ) . document ( ) . is_modified ( ) & & ! full_filename . is_empty ( ) ) {
// Replace "Untitled" blank file since it hasn't been modified
2022-09-12 23:32:27 +03:00
} else {
add_new_editor ( * m_current_editor_tab_widget ) ;
}
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
2023-12-16 17:19:34 +03:00
ByteString relative_file_path = filename ;
2021-05-01 13:04:19 +03:00
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 ( ) ; } ;
2024-01-19 15:56:05 +03:00
current_editor ( ) . set_cursor_and_focus_line ( 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
}
2023-12-16 17:19:34 +03:00
void HackStudioWidget : : close_file_in_all_editors ( ByteString const & filename )
2021-08-01 15:01:49 +03:00
{
m_open_files . remove ( filename ) ;
m_open_files_vector . remove_all_matching (
2023-12-16 17:19:34 +03:00
[ & filename ] ( ByteString const & element ) { return element = = filename ; } ) ;
2021-08-01 15:01:49 +03:00
for ( auto & editor_wrapper : m_all_editor_wrappers ) {
2023-03-06 16:17:01 +03:00
Editor & editor = editor_wrapper - > editor ( ) ;
2023-12-16 17:19:34 +03:00
ByteString editor_file_path = editor . code_document ( ) . file_path ( ) ;
ByteString relative_editor_file_path = LexicalPath : : relative_path ( editor_file_path , project ( ) . root_path ( ) ) ;
2021-08-01 15:01:49 +03:00
if ( relative_editor_file_path = = filename ) {
if ( m_open_files_vector . is_empty ( ) ) {
editor . set_document ( CodeDocument : : create ( ) ) ;
2023-03-06 16:17:01 +03:00
editor_wrapper - > set_filename ( " " ) ;
2021-08-01 15:01:49 +03:00
} else {
auto & first_path = m_open_files_vector [ 0 ] ;
auto & document = m_open_files . get ( first_path ) . value ( ) - > code_document ( ) ;
editor . set_document ( document ) ;
2023-03-06 16:17:01 +03:00
editor_wrapper - > set_filename ( first_path ) ;
2021-08-01 15:01:49 +03:00
}
}
}
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 ( ) ;
}
2022-12-18 04:48:17 +03:00
ErrorOr < NonnullRefPtr < GUI : : Menu > > HackStudioWidget : : create_project_tree_view_context_menu ( )
2020-09-19 17:25:05 +03:00
{
2022-12-18 04:48:17 +03:00
TRY ( m_new_file_actions . try_append ( TRY ( create_new_file_action ( " &C++ Source File " , " /res/icons/16x16/filetype-cplusplus.png " , " cpp " ) ) ) ) ;
TRY ( m_new_file_actions . try_append ( TRY ( create_new_file_action ( " C++ &Header File " , " /res/icons/16x16/filetype-header.png " , " h " ) ) ) ) ;
TRY ( m_new_file_actions . try_append ( TRY ( create_new_file_action ( " &GML File " , " /res/icons/16x16/filetype-gml.png " , " gml " ) ) ) ) ;
TRY ( m_new_file_actions . try_append ( TRY ( create_new_file_action ( " P&ython Source File " , " /res/icons/16x16/filetype-python.png " , " py " ) ) ) ) ;
TRY ( m_new_file_actions . try_append ( TRY ( create_new_file_action ( " Ja&va Source File " , " /res/icons/16x16/filetype-java.png " , " java " ) ) ) ) ;
TRY ( m_new_file_actions . try_append ( TRY ( create_new_file_action ( " C Source File " , " /res/icons/16x16/filetype-c.png " , " c " ) ) ) ) ;
TRY ( m_new_file_actions . try_append ( TRY ( create_new_file_action ( " &JavaScript Source File " , " /res/icons/16x16/filetype-javascript.png " , " js " ) ) ) ) ;
TRY ( m_new_file_actions . try_append ( TRY ( create_new_file_action ( " HT&ML File " , " /res/icons/16x16/filetype-html.png " , " html " ) ) ) ) ;
TRY ( m_new_file_actions . try_append ( TRY ( create_new_file_action ( " C&SS File " , " /res/icons/16x16/filetype-css.png " , " css " ) ) ) ) ;
TRY ( m_new_file_actions . try_append ( TRY ( create_new_file_action ( " &PHP File " , " /res/icons/16x16/filetype-php.png " , " php " ) ) ) ) ;
TRY ( m_new_file_actions . try_append ( TRY ( create_new_file_action ( " &Wasm File " , " /res/icons/16x16/filetype-wasm.png " , " wasm " ) ) ) ) ;
TRY ( m_new_file_actions . try_append ( TRY ( create_new_file_action ( " &INI File " , " /res/icons/16x16/filetype-ini.png " , " ini " ) ) ) ) ;
TRY ( m_new_file_actions . try_append ( TRY ( create_new_file_action ( " JS&ON File " , " /res/icons/16x16/filetype-json.png " , " json " ) ) ) ) ;
TRY ( m_new_file_actions . try_append ( TRY ( create_new_file_action ( " Mark&down File " , " /res/icons/16x16/filetype-markdown.png " , " md " ) ) ) ) ;
2021-07-30 23:06:47 +03:00
2022-12-18 04:48:17 +03:00
m_new_plain_file_action = TRY ( create_new_file_action ( " Plain &File " , " /res/icons/16x16/new.png " , " " ) ) ;
2021-07-30 23:06:47 +03:00
2022-12-18 04:48:17 +03:00
m_open_selected_action = TRY ( create_open_selected_action ( ) ) ;
2021-06-12 00:33:39 +03:00
m_show_in_file_manager_action = create_show_in_file_manager_action ( ) ;
2022-06-29 18:20:31 +03:00
m_copy_relative_path_action = create_copy_relative_path_action ( ) ;
2022-06-29 18:24:44 +03:00
m_copy_full_path_action = create_copy_full_path_action ( ) ;
2021-07-30 23:06:47 +03:00
2022-12-18 04:48:17 +03:00
m_new_directory_action = TRY ( 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 ( ) ) ;
} ) ;
2023-08-07 12:12:38 +03:00
auto project_tree_view_context_menu = GUI : : Menu : : construct ( " Project Files " _string ) ;
2021-07-30 23:06:47 +03:00
2023-08-14 10:04:41 +03:00
auto new_file_submenu = project_tree_view_context_menu - > add_submenu ( " N&ew... " _string ) ;
2021-07-30 23:06:47 +03:00
for ( auto & new_file_action : m_new_file_actions ) {
2023-08-14 10:04:41 +03:00
new_file_submenu - > add_action ( new_file_action ) ;
2021-07-30 23:06:47 +03:00
}
2023-01-20 22:06:05 +03:00
auto icon = TRY ( Gfx : : Bitmap : : load_from_file ( " /res/icons/16x16/new.png " sv ) ) ;
2023-08-14 10:04:41 +03:00
new_file_submenu - > set_icon ( icon ) ;
new_file_submenu - > add_action ( * m_new_plain_file_action ) ;
new_file_submenu - > add_separator ( ) ;
new_file_submenu - > add_action ( * m_new_directory_action ) ;
2021-07-30 23:06:47 +03:00
2020-09-19 17:25:05 +03:00
project_tree_view_context_menu - > add_action ( * m_open_selected_action ) ;
2021-06-12 00:33:39 +03:00
project_tree_view_context_menu - > add_action ( * m_show_in_file_manager_action ) ;
2022-06-29 18:20:31 +03:00
project_tree_view_context_menu - > add_action ( * m_copy_relative_path_action ) ;
2022-06-29 18:24:44 +03:00
project_tree_view_context_menu - > add_action ( * m_copy_full_path_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 ;
}
2023-12-16 17:19:34 +03:00
ErrorOr < NonnullRefPtr < GUI : : Action > > HackStudioWidget : : create_new_file_action ( ByteString const & label , ByteString const & icon , ByteString const & extension )
2020-09-19 17:25:05 +03:00
{
2023-01-20 22:06:05 +03:00
auto icon_no_shadow = TRY ( Gfx : : Bitmap : : load_from_file ( icon ) ) ;
2022-12-18 04:48:17 +03:00
return GUI : : Action : : create ( label , icon_no_shadow , [ this , extension ] ( const GUI : : Action & ) {
2023-04-16 23:02:07 +03:00
String filename ;
2023-05-22 20:07:09 +03:00
if ( GUI : : InputBox : : show ( window ( ) , filename , " Enter a name: " sv , " New File " sv ) ! = GUI : : InputBox : : ExecResult : : OK )
2020-09-19 17:25:05 +03:00
return ;
2021-03-31 03:54:39 +03:00
2023-12-16 17:19:34 +03:00
if ( ! extension . is_empty ( ) & & ! AK : : StringUtils : : ends_with ( filename , ByteString : : formatted ( " .{} " , extension ) , CaseSensitivity : : CaseSensitive ) ) {
2023-04-16 23:02:07 +03:00
filename = String : : formatted ( " {}.{} " , filename , extension ) . release_value_but_fixme_should_propagate_errors ( ) ;
2021-07-30 23:06:47 +03:00
}
2021-03-31 03:54:39 +03:00
auto path_to_selected = selected_file_paths ( ) ;
2023-12-16 17:19:34 +03:00
ByteString filepath ;
2021-03-31 03:54:39 +03:00
if ( ! path_to_selected . is_empty ( ) ) {
2023-03-21 18:35:30 +03:00
VERIFY ( FileSystem : : exists ( path_to_selected . first ( ) ) ) ;
2021-03-31 03:54:39 +03:00
LexicalPath selected ( path_to_selected . first ( ) ) ;
2023-12-16 17:19:34 +03:00
ByteString dir_path ;
2021-03-31 03:54:39 +03:00
2023-03-21 18:35:30 +03:00
if ( FileSystem : : is_directory ( selected . string ( ) ) )
2021-03-31 03:54:39 +03:00
dir_path = selected . string ( ) ;
else
dir_path = selected . dirname ( ) ;
2023-12-16 17:19:34 +03:00
filepath = ByteString : : formatted ( " {}/ " , dir_path ) ;
2021-03-31 03:54:39 +03:00
}
2023-12-16 17:19:34 +03:00
filepath = ByteString : : formatted ( " {}{} " , filepath , filename ) ;
2021-03-31 03:54:39 +03:00
2023-02-09 05:02:46 +03:00
auto file_or_error = Core : : File : : open ( filepath , Core : : File : : OpenMode : : Write | Core : : File : : OpenMode : : MustBeNew ) ;
2022-12-18 21:36:23 +03:00
if ( file_or_error . is_error ( ) ) {
2023-12-16 17:19:34 +03:00
GUI : : MessageBox : : show_error ( window ( ) , ByteString : : formatted ( " Failed to create '{}': {} " , filepath , file_or_error . 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
} ) ;
}
2022-12-18 04:48:17 +03:00
ErrorOr < NonnullRefPtr < GUI : : Action > > HackStudioWidget : : create_new_directory_action ( )
2021-01-31 21:06:41 +03:00
{
2023-01-20 22:06:05 +03:00
auto icon = TRY ( Gfx : : Bitmap : : load_from_file ( " /res/icons/16x16/mkdir.png " sv ) ) ;
2022-12-18 04:48:17 +03:00
return GUI : : Action : : create ( " &Directory... " , { Mod_Ctrl | Mod_Shift , Key_N } , icon , [ this ] ( const GUI : : Action & ) {
2023-04-16 23:02:07 +03:00
String directory_name ;
2023-05-22 20:07:09 +03:00
if ( GUI : : InputBox : : show ( window ( ) , directory_name , " Enter a name: " sv , " New Directory " sv ) ! = 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 ( ) ) ;
2023-12-16 17:19:34 +03:00
ByteString dir_path ;
2021-03-31 03:54:39 +03:00
2023-03-21 18:35:30 +03:00
if ( FileSystem : : is_directory ( selected . string ( ) ) )
2021-03-31 03:54:39 +03:00
dir_path = selected . string ( ) ;
else
dir_path = selected . dirname ( ) ;
2023-04-16 23:02:07 +03:00
directory_name = String : : formatted ( " {}/{} " , dir_path , directory_name ) . release_value_but_fixme_should_propagate_errors ( ) ;
2021-03-31 03:54:39 +03:00
}
2023-12-16 17:19:34 +03:00
auto formatted_dir_name = LexicalPath : : canonicalized_path ( ByteString : : formatted ( " {}/{} " , m_project - > model ( ) . root_path ( ) , directory_name ) ) ;
2021-01-31 21:06:41 +03:00
int rc = mkdir ( formatted_dir_name . characters ( ) , 0755 ) ;
if ( rc < 0 ) {
2022-07-11 20:32:29 +03:00
GUI : : MessageBox : : show ( window ( ) , " Failed to create new directory " sv , " Error " sv , GUI : : MessageBox : : Type : : Error ) ;
2021-01-31 21:06:41 +03:00
return ;
}
} ) ;
}
2022-12-18 04:48:17 +03:00
ErrorOr < NonnullRefPtr < GUI : : Action > > HackStudioWidget : : create_open_selected_action ( )
2020-09-19 17:25:05 +03:00
{
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 ) ;
} ) ;
2023-01-20 22:06:05 +03:00
auto icon = TRY ( Gfx : : Bitmap : : load_from_file ( " /res/icons/16x16/open.png " sv ) ) ;
2022-12-18 04:48:17 +03:00
open_selected_action - > set_icon ( icon ) ;
2020-09-19 17:25:05 +03:00
open_selected_action - > set_enabled ( true ) ;
return open_selected_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 )
2022-09-29 02:30:58 +03:00
Desktop : : Launcher : : open ( URL : : create_with_file_scheme ( m_project - > root_path ( ) , file ) ) ;
2021-06-12 00:33:39 +03:00
} ) ;
show_in_file_manager_action - > set_enabled ( true ) ;
2022-07-11 20:32:29 +03:00
show_in_file_manager_action - > set_icon ( GUI : : Icon : : default_icon ( " app-file-manager " sv ) . bitmap_for_size ( 16 ) ) ;
2021-06-12 00:33:39 +03:00
return show_in_file_manager_action ;
}
2022-06-29 18:20:31 +03:00
NonnullRefPtr < GUI : : Action > HackStudioWidget : : create_copy_relative_path_action ( )
{
auto copy_relative_path_action = GUI : : Action : : create ( " Copy &Relative Path " , [ this ] ( const GUI : : Action & ) {
auto paths = selected_file_paths ( ) ;
VERIFY ( ! paths . is_empty ( ) ) ;
2023-12-16 17:19:34 +03:00
auto paths_string = ByteString : : join ( ' \n ' , paths ) ;
2022-06-29 18:20:31 +03:00
GUI : : Clipboard : : the ( ) . set_plain_text ( paths_string ) ;
} ) ;
copy_relative_path_action - > set_enabled ( true ) ;
2022-07-11 20:32:29 +03:00
copy_relative_path_action - > set_icon ( GUI : : Icon : : default_icon ( " hard-disk " sv ) . bitmap_for_size ( 16 ) ) ;
2022-06-29 18:20:31 +03:00
return copy_relative_path_action ;
}
2022-06-29 18:24:44 +03:00
NonnullRefPtr < GUI : : Action > HackStudioWidget : : create_copy_full_path_action ( )
{
auto copy_full_path_action = GUI : : Action : : create ( " Copy &Full Path " , [ this ] ( const GUI : : Action & ) {
auto paths = selected_file_paths ( ) ;
VERIFY ( ! paths . is_empty ( ) ) ;
2023-12-16 17:19:34 +03:00
Vector < ByteString > full_paths ;
2022-06-29 18:24:44 +03:00
for ( auto & path : paths )
full_paths . append ( get_absolute_path ( path ) ) ;
2023-12-16 17:19:34 +03:00
auto paths_string = ByteString : : join ( ' \n ' , full_paths ) ;
2022-06-29 18:24:44 +03:00
GUI : : Clipboard : : the ( ) . set_plain_text ( paths_string ) ;
} ) ;
copy_full_path_action - > set_enabled ( true ) ;
2022-07-11 20:32:29 +03:00
copy_full_path_action - > set_icon ( GUI : : Icon : : default_icon ( " hard-disk " sv ) . bitmap_for_size ( 16 ) ) ;
2022-06-29 18:24:44 +03:00
return copy_full_path_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 ;
2023-12-16 17:19:34 +03:00
ByteString message ;
2020-09-19 17:25:05 +03:00
if ( files . size ( ) = = 1 ) {
2021-03-31 03:54:39 +03:00
LexicalPath file ( files [ 0 ] ) ;
2023-12-16 17:19:34 +03:00
message = ByteString : : formatted ( " Really remove \" {} \" from disk? " , file . basename ( ) ) ;
2020-09-19 17:25:05 +03:00
} else {
2023-12-16 17:19:34 +03:00
message = ByteString : : formatted ( " Really remove \" {} \" files from disk? " , files . size ( ) ) ;
2020-09-19 17:25:05 +03:00
}
auto result = GUI : : MessageBox : : show ( window ( ) ,
message ,
2023-05-22 20:07:09 +03:00
" Confirm Deletion " sv ,
2020-09-19 17:25:05 +03:00
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 ( ) ,
2023-12-16 17:19:34 +03:00
ByteString : : formatted ( " lstat ({}) failed: {} " , file , strerror ( errno ) ) ,
2023-05-22 20:07:09 +03:00
" Removal Failed " sv ,
2020-09-19 17:25:05 +03:00
GUI : : MessageBox : : Type : : Error ) ;
break ;
}
2021-03-02 08:18:59 +03:00
bool is_directory = S_ISDIR ( st . st_mode ) ;
2023-03-21 18:35:30 +03:00
if ( auto result = FileSystem : : remove ( file , FileSystem : : RecursionMode : : Allowed ) ; result . is_error ( ) ) {
2021-03-02 08:18:59 +03:00
auto & error = result . error ( ) ;
if ( is_directory ) {
GUI : : MessageBox : : show ( window ( ) ,
2023-12-16 17:19:34 +03:00
ByteString : : formatted ( " Removing directory \" {} \" from the project failed: {} " , file , error ) ,
2023-05-22 20:07:09 +03:00
" Removal Failed " sv ,
2021-03-02 08:18:59 +03:00
GUI : : MessageBox : : Type : : Error ) ;
} else {
GUI : : MessageBox : : show ( window ( ) ,
2023-12-16 17:19:34 +03:00
ByteString : : formatted ( " Removing file \" {} \" from the project failed: {} " , file , error ) ,
2023-05-22 20:07:09 +03:00
" Removal Failed " sv ,
2021-03-02 08:18:59 +03:00
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 ;
}
2022-12-18 04:48:17 +03:00
ErrorOr < NonnullRefPtr < GUI : : Action > > HackStudioWidget : : create_new_project_action ( )
2021-02-13 13:22:48 +03:00
{
2023-01-20 22:06:05 +03:00
auto icon = TRY ( Gfx : : Bitmap : : load_from_file ( " /res/icons/16x16/hackstudio-project.png " sv ) ) ;
2022-12-18 04:48:17 +03:00
return GUI : : Action : : create (
" &Project... " , icon ,
[ this ] ( const GUI : : Action & ) {
if ( warn_unsaved_changes ( " There are unsaved changes. Would you like to save before creating a new project? " ) = = ContinueDecision : : No )
return ;
// If the user wishes to save the changes, this occurs in warn_unsaved_changes. If they do not,
// we need to mark the documents as clean so open_project works properly without asking again.
for ( auto & editor_wrapper : m_all_editor_wrappers )
2023-03-06 16:17:01 +03:00
editor_wrapper - > editor ( ) . document ( ) . set_unmodified ( ) ;
2022-12-18 04:48:17 +03:00
auto dialog = NewProjectDialog : : construct ( window ( ) ) ;
dialog - > set_icon ( window ( ) - > icon ( ) ) ;
auto result = dialog - > exec ( ) ;
2021-02-13 13:22:48 +03:00
2022-12-18 04:48:17 +03:00
if ( result = = GUI : : Dialog : : ExecResult : : OK & & dialog - > created_project_path ( ) . has_value ( ) )
open_project ( dialog - > created_project_path ( ) . value ( ) ) ;
} ) ;
2021-02-13 13:22:48 +03:00
}
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 )
2023-03-06 16:17:01 +03:00
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 )
{
2023-08-07 12:12:38 +03:00
auto & wrapper = parent . add_tab < EditorWrapper > ( " (Untitled) " _string ) ;
2022-03-08 03:30:21 +03:00
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 ) ;
2023-02-19 19:47:18 +03:00
wrapper . editor ( ) . set_wrapping_mode ( m_wrapping_mode ) ;
2022-03-08 03:30:21 +03:00
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 ] {
2024-01-12 20:23:59 +03:00
tab . editor ( ) . document ( ) . unregister_client ( tab . editor ( ) ) ;
2022-03-08 03:30:21 +03:00
set_current_editor_wrapper ( tab ) ;
parent . remove_tab ( tab ) ;
m_all_editor_wrappers . remove_first_matching ( [ & tab ] ( auto & entry ) { return entry = = & tab ; } ) ;
2022-09-13 09:42:29 +03:00
if ( parent . children ( ) . is_empty ( ) & & m_all_editor_tab_widgets . size ( ) > 1 ) {
2022-03-08 03:30:21 +03:00
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 ; } ) ;
}
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
} ) ;
}
2022-12-18 04:48:17 +03:00
ErrorOr < NonnullRefPtr < GUI : : Action > > HackStudioWidget : : create_remove_current_editor_action ( )
2020-09-19 17:25:05 +03:00
{
2023-01-20 22:06:05 +03:00
auto icon = TRY ( Gfx : : Bitmap : : load_from_file ( " /res/icons/hackstudio/remove-editor.png " sv ) ) ;
2022-12-18 04:48:17 +03:00
return GUI : : Action : : create ( " &Remove Current Editor " , { Mod_Alt | Mod_Shift , Key_E } , icon , [ 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 ( ) ;
} ) ;
}
2023-07-28 04:12:40 +03:00
ErrorOr < NonnullRefPtr < GUI : : Action > > HackStudioWidget : : create_toggle_open_file_in_single_click_action ( )
{
return GUI : : Action : : create_checkable ( " &Open File with Single Click " , [ this ] ( auto & ) {
m_project_tree_view - > set_activates_on_selection ( ! m_project_tree_view - > activates_on_selection ( ) ) ;
} ) ;
}
2022-12-18 04:48:17 +03:00
ErrorOr < NonnullRefPtr < GUI : : Action > > HackStudioWidget : : create_open_action ( )
2020-09-19 17:25:05 +03:00
{
2023-01-20 22:06:05 +03:00
auto icon = TRY ( Gfx : : Bitmap : : load_from_file ( " /res/icons/16x16/open.png " sv ) ) ;
2022-12-18 04:48:17 +03:00
return GUI : : Action : : create ( " &Open Project... " , { Mod_Ctrl | Mod_Shift , Key_O } , icon , [ this ] ( auto & ) {
2023-05-22 20:07: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 ) ;
2023-05-13 14:08:04 +03:00
auto suggested_path = FileSystem : : absolute_path ( old_path . string ( ) ) . release_value_but_fixme_should_propagate_errors ( ) ;
2023-12-16 17:19:34 +03:00
Optional < ByteString > save_path = GUI : : FilePicker : : get_save_filepath ( window ( ) ,
2023-10-10 14:30:58 +03:00
old_filename . is_empty ( ) ? " Untitled " sv : old_path . title ( ) ,
old_filename . is_empty ( ) ? " txt " sv : old_path . extension ( ) ,
2023-05-13 14:08:04 +03:00
suggested_path ) ;
2021-08-12 18:07:39 +03:00
if ( ! save_path . has_value ( ) ) {
return ;
}
2023-12-16 17:19:34 +03:00
ByteString const relative_file_path = LexicalPath : : relative_path ( save_path . value ( ) , m_project - > root_path ( ) ) ;
2023-10-10 14:30:58 +03:00
if ( current_editor_wrapper ( ) . filename ( ) . is_empty ( ) ) {
2021-08-12 23:34:01 +03:00
current_editor_wrapper ( ) . set_filename ( relative_file_path ) ;
} else {
for ( auto & editor_wrapper : m_all_editor_wrappers ) {
2023-03-06 16:17:01 +03:00
if ( editor_wrapper - > filename ( ) = = old_filename )
editor_wrapper - > set_filename ( relative_file_path ) ;
2021-08-12 23:34:01 +03:00
}
}
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
} ) ;
}
2022-12-18 04:48:17 +03:00
ErrorOr < NonnullRefPtr < GUI : : Action > > HackStudioWidget : : create_remove_current_terminal_action ( )
2020-09-19 17:25:05 +03:00
{
2023-01-20 22:06:05 +03:00
auto icon = TRY ( Gfx : : Bitmap : : load_from_file ( " /res/icons/hackstudio/remove-terminal.png " sv ) ) ;
2022-12-18 04:48:17 +03:00
return GUI : : Action : : create ( " Remove &Current Terminal " , { Mod_Alt | Mod_Shift , Key_T } , icon , [ 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 ( ) ;
} ) ;
}
2022-12-18 04:48:17 +03:00
ErrorOr < NonnullRefPtr < GUI : : Action > > HackStudioWidget : : create_add_editor_action ( )
2020-09-19 17:25:05 +03:00
{
2023-01-20 22:06:05 +03:00
auto icon = TRY ( Gfx : : Bitmap : : load_from_file ( " /res/icons/hackstudio/add-editor.png " sv ) ) ;
2021-04-10 11:19:25 +03:00
return GUI : : Action : : create ( " Add New &Editor " , { Mod_Ctrl | Mod_Alt , Key_E } ,
2022-12-18 04:48:17 +03:00
icon ,
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 ( ) ;
} ) ;
}
2022-12-18 04:48:17 +03:00
ErrorOr < NonnullRefPtr < GUI : : Action > > HackStudioWidget : : create_add_terminal_action ( )
2020-09-19 17:25:05 +03:00
{
2023-01-20 22:06:05 +03:00
auto icon = TRY ( Gfx : : Bitmap : : load_from_file ( " /res/icons/hackstudio/add-terminal.png " sv ) ) ;
2021-04-10 11:19:25 +03:00
return GUI : : Action : : create ( " Add New &Terminal " , { Mod_Ctrl | Mod_Alt , Key_T } ,
2022-12-18 04:48:17 +03:00
icon ,
2020-09-19 17:25:05 +03:00
[ this ] ( auto & ) {
2023-08-07 12:12:38 +03:00
auto & terminal_wrapper = m_action_tab_widget - > add_tab < TerminalWrapper > ( " Terminal " _string ) ;
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 )
2022-08-30 14:38:20 +03:00
m_action_tab_widget - > set_preferred_height ( 200 ) ;
2020-09-19 17:25:05 +03:00
m_action_tab_widget - > set_active_widget ( & widget ) ;
}
2022-12-18 04:48:17 +03:00
ErrorOr < NonnullRefPtr < GUI : : Action > > HackStudioWidget : : create_debug_action ( )
2020-09-19 17:25:05 +03:00
{
2023-01-20 22:06:05 +03:00
auto icon = TRY ( Gfx : : Bitmap : : load_from_file ( " /res/icons/16x16/debug-run.png " sv ) ) ;
2022-12-18 04:48:17 +03:00
return GUI : : Action : : create ( " &Debug " , icon , [ this ] ( auto & ) {
2023-03-21 18:35:30 +03:00
if ( ! FileSystem : : exists ( get_project_executable_path ( ) ) ) {
2023-12-16 17:19:34 +03:00
GUI : : MessageBox : : show ( window ( ) , ByteString : : formatted ( " Could not find file: {}. (did you build the project?) " , get_project_executable_path ( ) ) , " Error " sv , GUI : : MessageBox : : Type : : Error ) ;
2020-09-19 17:25:05 +03:00
return ;
}
if ( Debugger : : the ( ) . session ( ) ) {
2022-07-11 20:32:29 +03:00
GUI : : MessageBox : : show ( window ( ) , " Debugger is already running " sv , " Error " sv , GUI : : MessageBox : : Type : : Error ) ;
2020-09-19 17:25:05 +03:00
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 ( ) ;
2022-09-10 00:53:53 +03:00
// The debugger calls wait() on the debuggee, so the TerminalWrapper can't do that.
2021-12-20 23:42:03 +03:00
auto ptm_res = m_terminal_wrapper - > setup_master_pseudoterminal ( TerminalWrapper : : WaitForChildOnExit : : No ) ;
if ( ptm_res . is_error ( ) ) {
2024-01-11 19:55:49 +03:00
warnln ( " Failed to set up master pseudoterminal: {} " , ptm_res . release_error ( ) ) ;
2021-12-20 23:42:03 +03:00
return ;
}
2023-02-09 21:26:53 +03:00
Debugger : : the ( ) . set_child_setup_callback ( [ this , ptm_res = ptm_res . release_value ( ) ] ( ) {
return m_terminal_wrapper - > setup_slave_pseudoterminal ( ptm_res ) ;
2021-12-20 23:42:03 +03:00
} ) ;
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 ) {
2023-03-06 16:17:01 +03:00
editor_wrapper - > set_debug_mode ( true ) ;
2021-07-11 18:38:38 +03:00
}
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
2023-02-18 14:15:25 +03:00
GUI : : Application : : the ( ) - > event_loop ( ) . deferred_invoke ( [ this , source_position , & regs ] {
2021-08-30 13:43:28 +03:00
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 ) ;
2023-02-19 23:52:52 +03:00
m_debug_info_widget - > set_debug_actions_enabled ( true , DebugInfoWidget : : DebugActionsState : : DebuggeeStopped ) ;
2021-08-30 13:43:28 +03:00
m_disassembly_widget - > update_state ( * Debugger : : the ( ) . session ( ) , regs ) ;
HackStudioWidget : : reveal_action_tab ( * m_debug_info_widget ) ;
} ) ;
2023-02-18 14:15:25 +03:00
GUI : : Application : : the ( ) - > event_loop ( ) . wake ( ) ;
2020-09-19 17:25:05 +03:00
return Debugger : : HasControlPassedToUser : : Yes ;
} ,
[ this ] ( ) {
2023-02-18 14:15:25 +03:00
GUI : : Application : : the ( ) - > event_loop ( ) . deferred_invoke ( [ this ] {
2023-02-19 23:52:52 +03:00
m_debug_info_widget - > set_debug_actions_enabled ( true , DebugInfoWidget : : DebugActionsState : : DebuggeeRunning ) ;
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
} ) ;
2023-02-18 14:15:25 +03:00
GUI : : Application : : the ( ) - > event_loop ( ) . wake ( ) ;
2020-09-19 17:25:05 +03:00
} ,
[ this ] ( ) {
2023-02-18 14:15:25 +03:00
GUI : : Application : : the ( ) - > event_loop ( ) . deferred_invoke ( [ this ] {
2023-02-19 23:52:52 +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 )
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 ) {
2023-03-06 16:17:01 +03:00
editor_wrapper - > set_debug_mode ( false ) ;
2021-07-11 18:38:38 +03:00
}
2020-09-19 17:25:05 +03:00
HackStudioWidget : : hide_action_tabs ( ) ;
2022-07-11 20:32:29 +03:00
GUI : : MessageBox : : show ( window ( ) , " Program Exited " sv , " Debugger " sv , GUI : : MessageBox : : Type : : Information ) ;
2021-08-30 13:43:28 +03:00
} ) ;
2023-02-18 14:15:25 +03:00
GUI : : Application : : the ( ) - > event_loop ( ) . wake ( ) ;
2023-02-19 22:48:45 +03:00
} ,
2023-02-19 23:12:07 +03:00
[ ] ( float progress ) {
2023-02-19 22:48:45 +03:00
if ( GUI : : Application : : the ( ) - > active_window ( ) )
GUI : : Application : : the ( ) - > active_window ( ) - > set_progress ( progress * 100 ) ;
2020-09-19 17:25:05 +03:00
} ) ;
}
2023-12-16 17:19:34 +03:00
ByteString HackStudioWidget : : get_full_path_of_serenity_source ( ByteString 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 ;
2022-07-11 23:10:18 +03:00
relative_path_builder . join ( ' / ' , path_parts ) ;
2020-09-19 17:25:05 +03:00
constexpr char SERENITY_LIBS_PREFIX [ ] = " /usr/src/serenity " ;
LexicalPath serenity_sources_base ( SERENITY_LIBS_PREFIX ) ;
2023-12-16 17:19:34 +03:00
return ByteString : : formatted ( " {}/{} " , serenity_sources_base , relative_path_builder . to_byte_string ( ) ) ;
2020-09-19 17:25:05 +03:00
}
2023-12-16 17:19:34 +03:00
ByteString HackStudioWidget : : get_absolute_path ( ByteString 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/"))"
2022-07-11 20:32:29 +03:00
if ( path . starts_with ( " .. " sv ) ) {
2021-11-19 17:13:07 +03:00
return get_full_path_of_serenity_source ( path ) ;
}
return m_project - > to_absolute_path ( path ) ;
}
2023-12-16 17:19:34 +03:00
RefPtr < EditorWrapper > HackStudioWidget : : get_editor_of_file ( ByteString const & filename )
2020-09-19 17:25:05 +03:00
{
2023-12-16 17:19:34 +03:00
ByteString file_path = filename ;
2020-09-19 17:25:05 +03:00
2022-07-11 20:32:29 +03:00
if ( filename . starts_with ( " ../ " sv ) ) {
2021-04-29 22:46:15 +03:00
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 ( ) ;
}
2023-12-16 17:19:34 +03:00
ByteString HackStudioWidget : : get_project_executable_path ( ) const
2020-09-19 17:25:05 +03:00
{
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?
2023-12-16 17:19:34 +03:00
return ByteString : : 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 ( ) ) {
2023-12-16 17:19:34 +03:00
GUI : : MessageBox : : show ( window ( ) , ByteString : : formatted ( " {} " , result . error ( ) ) , " Build Failed " sv , GUI : : MessageBox : : Type : : Error ) ;
2022-06-20 19:14:00 +03:00
m_build_action - > set_enabled ( true ) ;
m_stop_action - > set_enabled ( false ) ;
} else {
m_stop_action - > set_enabled ( true ) ;
2022-01-07 18:23:50 +03:00
}
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 ( ) ) {
2023-12-16 17:19:34 +03:00
GUI : : MessageBox : : show ( window ( ) , ByteString : : formatted ( " {} " , result . error ( ) ) , " Run Failed " sv , GUI : : MessageBox : : Type : : Error ) ;
2022-06-20 19:14:00 +03:00
m_run_action - > set_enabled ( true ) ;
m_stop_action - > set_enabled ( false ) ;
} else {
m_stop_action - > set_enabled ( true ) ;
2022-01-07 18:23:50 +03:00
}
2020-09-19 17:25:05 +03:00
}
void HackStudioWidget : : hide_action_tabs ( )
{
2022-08-30 14:38:20 +03:00
m_action_tab_widget - > set_preferred_height ( 24 ) ;
2023-07-08 05:48:11 +03:00
}
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 )
{
2023-01-11 06:12:40 +03:00
if ( m_current_editor_wrapper )
m_current_editor_wrapper - > editor ( ) . hide_autocomplete ( ) ;
2020-09-19 17:25:05 +03:00
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-12-24 21:49:32 +03:00
update_toolbar_actions ( ) ;
2022-03-08 03:30:21 +03:00
set_current_editor_tab_widget ( static_cast < GUI : : TabWidget * > ( m_current_editor_wrapper - > parent ( ) ) ) ;
2022-09-12 23:32:27 +03:00
m_current_editor_tab_widget - > set_active_widget ( editor_wrapper ) ;
2022-03-08 03:30:21 +03:00
update_statusbar ( ) ;
2020-09-19 17:25:05 +03:00
}
2023-12-16 17:19:34 +03:00
void HackStudioWidget : : file_renamed ( ByteString const & old_name , ByteString const & new_name )
2021-09-24 19:31:08 +03:00
{
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 ) {
2023-03-21 18:35:30 +03:00
return FileSystem : : can_delete_or_move ( m_project - > model ( ) . full_path ( selected_file ) ) ;
2021-07-16 18:09:45 +03:00
} ) ;
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 > ( ) ;
2023-12-16 17:19:34 +03:00
auto open_files_model = GUI : : ItemListModel < ByteString > : : create ( m_open_files_vector ) ;
2020-10-26 14:13:32 +03:00
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 ) {
2023-12-16 17:19:34 +03:00
open_file ( index . data ( ) . to_byte_string ( ) ) ;
2020-10-26 14:13:32 +03:00
} ;
}
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 ( ) ;
2022-12-24 21:49:32 +03:00
m_cut_button = toolbar . add_action ( current_editor ( ) . cut_action ( ) ) ;
m_copy_button = toolbar . add_action ( current_editor ( ) . copy_action ( ) ) ;
m_paste_button = toolbar . add_action ( current_editor ( ) . paste_action ( ) ) ;
2020-09-19 17:25:05 +03:00
toolbar . add_separator ( ) ;
2022-10-02 00:21:35 +03:00
toolbar . add_action ( GUI : : CommonActions : : make_undo_action ( [ this ] ( auto & ) { current_editor ( ) . undo_action ( ) . activate ( ) ; } , m_editors_splitter ) ) ;
toolbar . add_action ( GUI : : CommonActions : : make_redo_action ( [ this ] ( auto & ) { current_editor ( ) . redo_action ( ) . activate ( ) ; } , m_editors_splitter ) ) ;
2020-09-19 17:25:05 +03:00
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 ) ;
}
2022-12-18 04:48:17 +03:00
ErrorOr < NonnullRefPtr < GUI : : Action > > HackStudioWidget : : create_build_action ( )
2020-09-19 17:25:05 +03:00
{
2023-01-20 22:06:05 +03:00
auto icon = TRY ( Gfx : : Bitmap : : load_from_file ( " /res/icons/16x16/build.png " sv ) ) ;
2022-12-18 04:48:17 +03:00
return GUI : : Action : : create ( " &Build " , { Mod_Ctrl , Key_B } , icon , [ this ] ( auto & ) {
2023-11-01 16:47:23 +03:00
if ( m_auto_save_before_build_or_run ) {
if ( ! save_file_changes ( ) )
return ;
} else {
if ( warn_unsaved_changes ( " There are unsaved changes, do you want to save before building? " ) = = ContinueDecision : : No )
return ;
}
2021-05-01 13:42:07 +03:00
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
} ) ;
}
2022-12-18 04:48:17 +03:00
ErrorOr < NonnullRefPtr < GUI : : Action > > HackStudioWidget : : create_run_action ( )
2020-09-19 17:25:05 +03:00
{
2023-01-20 22:06:05 +03:00
auto icon = TRY ( Gfx : : Bitmap : : load_from_file ( " /res/icons/16x16/program-run.png " sv ) ) ;
2022-12-18 04:48:17 +03:00
return GUI : : Action : : create ( " &Run " , { Mod_Ctrl , Key_R } , icon , [ this ] ( auto & ) {
2023-11-01 16:47:23 +03:00
if ( m_auto_save_before_build_or_run ) {
if ( ! save_file_changes ( ) )
return ;
} else {
if ( warn_unsaved_changes ( " There are unsaved changes, do you want to save before running? " ) = = ContinueDecision : : No )
return ;
}
2023-10-31 22:49:46 +03:00
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
} ) ;
}
2022-12-16 04:00:56 +03:00
ErrorOr < void > HackStudioWidget : : create_action_tab ( GUI : : Widget & parent )
2020-09-19 17:25:05 +03:00
{
m_action_tab_widget = parent . add < GUI : : TabWidget > ( ) ;
2022-08-30 14:38:20 +03:00
m_action_tab_widget - > set_preferred_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 )
2022-08-30 14:38:20 +03:00
m_action_tab_widget - > set_preferred_height ( 200 ) ;
2020-09-19 17:25:05 +03:00
first_time = false ;
} ;
2023-08-07 12:12:38 +03:00
m_find_in_files_widget = m_action_tab_widget - > add_tab < FindInFilesWidget > ( " Find " _string ) ;
2023-08-08 05:26:17 +03:00
m_todo_entries_widget = m_action_tab_widget - > add_tab < ToDoEntriesWidget > ( " TODO " _string ) ;
m_terminal_wrapper = m_action_tab_widget - > add_tab < TerminalWrapper > ( " Console " _string , false ) ;
2022-12-16 04:00:56 +03:00
auto debug_info_widget = TRY ( DebugInfoWidget : : create ( ) ) ;
2023-09-14 10:29:47 +03:00
m_action_tab_widget - > add_tab ( debug_info_widget , " Debug " _string ) ;
2022-12-16 04:00:56 +03:00
m_debug_info_widget = debug_info_widget ;
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 ) ;
} ;
2023-08-07 12:12:38 +03:00
m_disassembly_widget = m_action_tab_widget - > add_tab < DisassemblyWidget > ( " Disassembly " _string ) ;
2023-08-08 05:26:17 +03:00
m_git_widget = m_action_tab_widget - > add_tab < GitWidget > ( " Git " _string ) ;
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 ) ;
} ) ;
2023-08-07 12:12:38 +03:00
m_gml_preview_widget = m_action_tab_widget - > add_tab < GMLPreviewWidget > ( " GML Preview " _string , " " ) ;
2021-05-22 15:14:49 +03:00
ToDoEntries : : the ( ) . on_update = [ this ] ( ) {
m_todo_entries_widget - > refresh ( ) ;
} ;
2022-12-16 04:00:56 +03:00
return { } ;
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 > ( ) ;
2023-10-27 23:24:15 +03:00
m_project_tab - > set_tab_position ( TabPosition : : Bottom ) ;
2021-05-10 13:58:49 +03:00
2023-08-08 05:26:17 +03:00
auto & tree_view_container = m_project_tab - > add_tab < GUI : : Widget > ( " Files " _string ) ;
2024-01-16 17:11:58 +03:00
tree_view_container . set_layout < GUI : : VerticalBoxLayout > ( ) ;
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 ( ) ;
2023-08-08 05:26:17 +03:00
auto & class_view_container = m_project_tab - > add_tab < GUI : : Widget > ( " Classes " _string ) ;
2024-01-16 17:11:58 +03:00
class_view_container . set_layout < GUI : : VerticalBoxLayout > ( ) ;
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 ) {
2023-05-22 20:07:09 +03:00
auto empty_action = GUI : : Action : : create ( " (No recently open files) " , [ ] ( auto & ) { } ) ;
2022-02-14 16:08:24 +03:00
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 ) ;
} ) ) ;
}
}
2022-12-18 04:48:17 +03:00
ErrorOr < void > HackStudioWidget : : create_file_menu ( GUI : : Window & window )
2020-09-19 17:25:05 +03:00
{
2023-08-14 11:44:42 +03:00
auto file_menu = window . add_menu ( " &File " _string ) ;
2022-03-07 06:08:34 +03:00
2023-08-14 11:44:42 +03:00
auto new_submenu = file_menu - > add_submenu ( " &New... " _string ) ;
2023-08-14 10:04:41 +03:00
new_submenu - > add_action ( * m_new_project_action ) ;
new_submenu - > add_separator ( ) ;
2022-03-07 06:08:34 +03:00
for ( auto & new_file_action : m_new_file_actions ) {
2023-08-14 10:04:41 +03:00
new_submenu - > add_action ( new_file_action ) ;
2022-03-07 06:08:34 +03:00
}
2022-12-18 04:48:17 +03:00
{
2023-01-20 22:06:05 +03:00
auto icon = TRY ( Gfx : : Bitmap : : load_from_file ( " /res/icons/16x16/new.png " sv ) ) ;
2023-08-14 10:04:41 +03:00
new_submenu - > set_icon ( icon ) ;
2022-12-18 04:48:17 +03:00
}
2023-08-14 10:04:41 +03:00
new_submenu - > add_action ( * m_new_plain_file_action ) ;
new_submenu - > add_separator ( ) ;
new_submenu - > add_action ( * m_new_directory_action ) ;
2022-03-07 06:08:34 +03:00
2023-08-14 11:44:42 +03:00
file_menu - > add_action ( * m_open_action ) ;
m_recent_projects_submenu = file_menu - > add_submenu ( " Open &Recent " _string ) ;
2022-12-18 04:48:17 +03:00
{
2023-01-20 22:06:05 +03:00
auto icon = TRY ( Gfx : : Bitmap : : load_from_file ( " /res/icons/16x16/open-recent.png " sv ) ) ;
2022-12-18 04:48:17 +03:00
m_recent_projects_submenu - > set_icon ( icon ) ;
}
2022-02-14 16:08:24 +03:00
update_recent_projects_submenu ( ) ;
2023-08-14 11:44:42 +03:00
file_menu - > add_action ( * m_save_action ) ;
file_menu - > add_action ( * m_save_as_action ) ;
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 ( ) ;
} ) ) ;
2022-12-18 04:48:17 +03:00
return { } ;
2020-09-19 17:25:05 +03:00
}
2022-12-18 04:48:17 +03:00
ErrorOr < void > HackStudioWidget : : create_edit_menu ( GUI : : Window & window )
2020-09-19 17:25:05 +03:00
{
2023-08-14 11:44:42 +03:00
auto edit_menu = window . add_menu ( " &Edit " _string ) ;
2023-01-20 22:06:05 +03:00
auto icon = TRY ( Gfx : : Bitmap : : load_from_file ( " /res/icons/16x16/find.png " sv ) ) ;
2023-08-14 11:44:42 +03:00
edit_menu - > add_action ( GUI : : Action : : create ( " &Find in Files... " , { Mod_Ctrl | Mod_Shift , Key_F } , icon , [ 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
2023-08-14 11:44:42 +03:00
edit_menu - > add_separator ( ) ;
2020-10-26 14:20:03 +03:00
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 )
2023-03-06 16:17:01 +03:00
editor_wrapper - > editor ( ) . set_editing_engine ( make < GUI : : VimEditingEngine > ( ) ) ;
2021-12-09 01:28:12 +03:00
} else {
for ( auto & editor_wrapper : m_all_editor_wrappers )
2023-03-06 16:17:01 +03:00
editor_wrapper - > editor ( ) . set_editing_engine ( make < GUI : : RegularEditingEngine > ( ) ) ;
2021-12-09 01:28:12 +03:00
}
2021-01-02 13:59:55 +03:00
} ) ;
vim_emulation_setting_action - > set_checked ( false ) ;
2023-08-14 11:44:42 +03:00
edit_menu - > add_action ( vim_emulation_setting_action ) ;
2022-03-19 14:47:18 +03:00
2023-11-01 16:47:23 +03:00
auto auto_save_before_build_or_run_action = GUI : : Action : : create_checkable ( " &Auto Save before Build or Run " , [ this ] ( auto & action ) {
m_auto_save_before_build_or_run = action . is_checked ( ) ;
Config : : write_bool ( " HackStudio " sv , " Global " sv , " AutoSaveBeforeBuildOrRun " sv , m_auto_save_before_build_or_run ) ;
} ) ;
m_auto_save_before_build_or_run = Config : : read_bool ( " HackStudio " sv , " Global " sv , " AutoSaveBeforeBuildOrRun " sv , false ) ;
auto_save_before_build_or_run_action - > set_checked ( m_auto_save_before_build_or_run ) ;
edit_menu - > add_action ( auto_save_before_build_or_run_action ) ;
2023-08-14 11:44:42 +03:00
edit_menu - > add_separator ( ) ;
edit_menu - > add_action ( * m_open_project_configuration_action ) ;
2022-12-18 04:48:17 +03:00
return { } ;
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
{
2023-08-14 11:44:42 +03:00
auto build_menu = window . add_menu ( " &Build " _string ) ;
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 ) ;
2020-09-19 17:25:05 +03:00
}
2022-12-18 04:48:17 +03:00
ErrorOr < 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-07-11 20:32:29 +03:00
Config : : write_bool ( " HackStudio " sv , " Global " sv , " ShowDotfiles " sv , checked . is_checked ( ) ) ;
2021-10-29 00:14:15 +03:00
} ) ;
2022-07-11 20:32:29 +03:00
show_dotfiles_action - > set_checked ( Config : : read_bool ( " HackStudio " sv , " Global " sv , " ShowDotfiles " sv , false ) ) ;
2020-09-19 17:25:05 +03:00
2023-08-14 11:44:42 +03:00
auto view_menu = window . add_menu ( " &View " _string ) ;
view_menu - > add_action ( hide_action_tabs_action ) ;
view_menu - > add_action ( open_locator_action ) ;
view_menu - > add_action ( show_dotfiles_action ) ;
2022-12-18 04:48:17 +03:00
m_toggle_semantic_highlighting_action = TRY ( create_toggle_syntax_highlighting_mode_action ( ) ) ;
2023-08-14 11:44:42 +03:00
view_menu - > add_action ( * m_toggle_semantic_highlighting_action ) ;
2023-07-28 04:12:40 +03:00
m_toggle_view_file_in_single_click_action = TRY ( create_toggle_open_file_in_single_click_action ( ) ) ;
2023-08-14 11:44:42 +03:00
view_menu - > add_action ( * m_toggle_view_file_in_single_click_action ) ;
view_menu - > add_separator ( ) ;
2021-02-26 15:19:19 +03:00
m_wrapping_mode_actions . set_exclusive ( true ) ;
2023-08-14 11:44:42 +03:00
auto wrapping_mode_menu = view_menu - > add_submenu ( " &Wrapping Mode " _string ) ;
2021-04-10 11:19:25 +03:00
m_no_wrapping_action = GUI : : Action : : create_checkable ( " &No Wrapping " , [ & ] ( auto & ) {
2023-02-19 19:47:18 +03:00
m_wrapping_mode = GUI : : TextEditor : : WrappingMode : : NoWrap ;
2021-02-26 15:19:19 +03:00
for ( auto & wrapper : m_all_editor_wrappers )
2023-03-06 16:17:01 +03:00
wrapper - > editor ( ) . set_wrapping_mode ( GUI : : TextEditor : : WrappingMode : : NoWrap ) ;
2021-02-26 15:19:19 +03:00
} ) ;
2021-04-10 11:19:25 +03:00
m_wrap_anywhere_action = GUI : : Action : : create_checkable ( " Wrap &Anywhere " , [ & ] ( auto & ) {
2023-02-19 19:47:18 +03:00
m_wrapping_mode = GUI : : TextEditor : : WrappingMode : : WrapAnywhere ;
2021-02-26 15:19:19 +03:00
for ( auto & wrapper : m_all_editor_wrappers )
2023-03-06 16:17:01 +03:00
wrapper - > editor ( ) . set_wrapping_mode ( GUI : : TextEditor : : WrappingMode : : WrapAnywhere ) ;
2021-02-26 15:19:19 +03:00
} ) ;
2021-04-10 11:19:25 +03:00
m_wrap_at_words_action = GUI : : Action : : create_checkable ( " Wrap at &Words " , [ & ] ( auto & ) {
2023-02-19 19:47:18 +03:00
m_wrapping_mode = GUI : : TextEditor : : WrappingMode : : WrapAtWords ;
2021-02-26 15:19:19 +03:00
for ( auto & wrapper : m_all_editor_wrappers )
2023-03-06 16:17:01 +03:00
wrapper - > editor ( ) . set_wrapping_mode ( GUI : : TextEditor : : WrappingMode : : WrapAtWords ) ;
2021-02-26 15:19:19 +03:00
} ) ;
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 ) ;
2023-08-14 10:04:41 +03:00
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 ) ;
2021-02-26 15:19:19 +03:00
2023-02-19 19:47:18 +03:00
switch ( m_wrapping_mode ) {
case GUI : : TextEditor : : NoWrap :
m_no_wrapping_action - > set_checked ( true ) ;
break ;
case GUI : : TextEditor : : WrapAtWords :
m_wrap_at_words_action - > set_checked ( true ) ;
break ;
case GUI : : TextEditor : : WrapAnywhere :
m_wrap_anywhere_action - > set_checked ( true ) ;
break ;
}
2021-02-26 15:19:19 +03:00
2023-01-20 22:06:05 +03:00
auto icon = TRY ( Gfx : : Bitmap : : load_from_file ( " /res/icons/16x16/app-font-editor.png " sv ) ) ;
2023-05-22 20:07:09 +03:00
m_editor_font_action = GUI : : Action : : create ( " Change &Font... " , icon ,
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 ( ) ) ;
}
} ) ;
2023-08-14 11:44:42 +03:00
view_menu - > add_action ( * m_editor_font_action ) ;
2021-10-06 17:01:52 +03:00
2023-08-14 11:44:42 +03:00
view_menu - > add_separator ( ) ;
view_menu - > add_action ( * m_add_editor_tab_widget_action ) ;
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
2023-08-14 11:44:42 +03:00
view_menu - > add_separator ( ) ;
2021-08-27 16:26:18 +03:00
2022-12-18 04:48:17 +03:00
TRY ( create_location_history_actions ( ) ) ;
2023-08-14 11:44:42 +03:00
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
2023-08-14 11:44:42 +03:00
view_menu - > add_separator ( ) ;
2022-08-06 02:29:58 +03:00
2023-08-14 11:44:42 +03:00
view_menu - > add_action ( GUI : : CommonActions : : make_fullscreen_action ( [ & ] ( auto & ) {
2022-08-06 02:29:58 +03:00
window . set_fullscreen ( ! window . is_fullscreen ( ) ) ;
} ) ) ;
2022-12-18 04:48:17 +03:00
return { } ;
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
{
2023-08-14 11:44:42 +03:00
auto help_menu = window . add_menu ( " &Help " _string ) ;
help_menu - > add_action ( GUI : : CommonActions : : make_command_palette_action ( & window ) ) ;
2023-09-13 22:34:46 +03:00
help_menu - > add_action ( GUI : : CommonActions : : make_about_action ( " Hack Studio " _string , GUI : : Icon : : default_icon ( " app-hack-studio " sv ) , & window ) ) ;
2020-09-19 17:25:05 +03:00
}
2022-12-18 04:48:17 +03:00
ErrorOr < NonnullRefPtr < GUI : : Action > > HackStudioWidget : : create_stop_action ( )
2020-09-19 17:25:05 +03:00
{
2023-01-20 22:06:05 +03:00
auto icon = TRY ( Gfx : : Bitmap : : load_from_file ( " /res/icons/16x16/program-stop.png " sv ) ) ;
2022-12-18 04:48:17 +03:00
auto action = GUI : : Action : : create ( " &Stop " , icon , [ 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 ;
}
2022-12-18 04:48:17 +03:00
ErrorOr < void > HackStudioWidget : : initialize_menubar ( GUI : : Window & window )
2020-09-19 17:25:05 +03:00
{
2022-12-18 04:48:17 +03:00
TRY ( create_file_menu ( window ) ) ;
TRY ( create_edit_menu ( window ) ) ;
2021-07-21 22:21:03 +03:00
create_build_menu ( window ) ;
2022-12-18 04:48:17 +03:00
TRY ( create_view_menu ( window ) ) ;
2021-07-21 22:21:03 +03:00
create_help_menu ( window ) ;
2022-12-18 04:48:17 +03:00
return { } ;
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 ( ) ) {
2023-12-16 17:19:34 +03:00
ByteString selected_text = current_editor ( ) . selected_text ( ) ;
2021-07-02 03:39:17 +03:00
auto word_count = current_editor ( ) . number_of_selected_words ( ) ;
2023-04-11 19:39:26 +03:00
builder . appendff ( " Selected: {:'d} {} ({:'d} {}) " , selected_text . length ( ) , selected_text . length ( ) = = 1 ? " character " : " characters " , word_count , word_count ! = 1 ? " words " : " word " ) ;
2021-07-02 03:39:17 +03:00
}
2023-06-04 11:24:38 +03:00
m_statusbar - > set_text ( 0 , builder . to_string ( ) . release_value_but_fixme_should_propagate_errors ( ) ) ;
m_statusbar - > set_text ( 1 , String : : from_utf8 ( Syntax : : language_to_string ( current_editor_wrapper ( ) . editor ( ) . code_document ( ) . language ( ) . value_or ( Syntax : : Language : : PlainText ) ) ) . release_value_but_fixme_should_propagate_errors ( ) ) ;
m_statusbar - > set_text ( 2 , String : : formatted ( " Ln {:'d} Col {:'d} " , current_editor ( ) . cursor ( ) . line ( ) + 1 , current_editor ( ) . cursor ( ) . column ( ) ) . release_value_but_fixme_should_propagate_errors ( ) ) ;
2021-07-02 03:39:17 +03:00
}
2023-12-16 17:19:34 +03:00
void HackStudioWidget : : handle_external_file_deletion ( ByteString 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 ( ) ;
}
2023-12-16 17:19:34 +03:00
HackStudioWidget : : ContinueDecision HackStudioWidget : : warn_unsaved_changes ( ByteString const & prompt )
2021-05-01 13:42:07 +03:00
{
if ( ! any_document_is_dirty ( ) )
return ContinueDecision : : Yes ;
2023-05-22 20:07:09 +03:00
auto result = GUI : : MessageBox : : show ( window ( ) , prompt , " Unsaved Changes " sv , GUI : : MessageBox : : Type : : Warning , GUI : : MessageBox : : InputType : : YesNoCancel ) ;
2021-05-01 13:42:07 +03:00
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 ) {
2023-11-01 16:47:23 +03:00
if ( ! save_file_changes ( ) )
return ContinueDecision : : No ;
2021-05-01 13:42:07 +03:00
}
return ContinueDecision : : Yes ;
}
2023-11-01 16:47:23 +03:00
bool HackStudioWidget : : save_file_changes ( )
{
for ( auto & editor_wrapper : m_all_editor_wrappers ) {
if ( editor_wrapper - > editor ( ) . document ( ) . is_modified ( ) ) {
if ( ! editor_wrapper - > save ( ) )
return false ;
}
}
return true ;
}
2021-05-01 13:42:07 +03:00
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 ) {
2023-03-06 16:17:01 +03:00
return editor_wrapper - > editor ( ) . document ( ) . is_modified ( ) ;
2021-09-01 17:11:56 +03:00
} ) ;
2021-05-01 13:42:07 +03:00
}
2021-07-28 22:58:16 +03:00
void HackStudioWidget : : update_gml_preview ( )
{
2022-07-11 20:32:29 +03:00
auto gml_content = current_editor_wrapper ( ) . filename ( ) . ends_with ( " .gml " sv ) ? current_editor_wrapper ( ) . editor ( ) . text ( ) . view ( ) : " " sv ;
2021-07-28 22:58:16 +03:00
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 ) ;
}
}
2022-12-24 21:49:32 +03:00
void HackStudioWidget : : update_toolbar_actions ( )
{
m_copy_button - > set_action ( current_editor ( ) . copy_action ( ) ) ;
m_paste_button - > set_action ( current_editor ( ) . paste_action ( ) ) ;
m_cut_button - > set_action ( current_editor ( ) . cut_action ( ) ) ;
}
2021-08-13 14:10:58 +03:00
void HackStudioWidget : : update_window_title ( )
{
2023-12-16 17:19:34 +03:00
window ( ) - > set_title ( ByteString : : 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 ( )
{
2023-12-16 17:19:34 +03:00
current_editor_tab_widget ( ) . set_tab_title ( current_editor_wrapper ( ) , String : : from_byte_string ( current_editor_wrapper ( ) . filename_title ( ) ) . release_value_but_fixme_should_propagate_errors ( ) ) ;
2021-08-13 14:10:58 +03:00
}
2021-08-27 16:26:18 +03:00
void HackStudioWidget : : on_cursor_change ( )
{
update_statusbar ( ) ;
2023-10-10 14:30:58 +03:00
if ( current_editor_wrapper ( ) . filename ( ) . is_empty ( ) )
2021-08-27 16:26:18 +03:00
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 ( ) ;
}
2022-12-18 04:48:17 +03:00
ErrorOr < void > HackStudioWidget : : create_location_history_actions ( )
2021-08-27 16:26:18 +03:00
{
2022-12-18 04:48:17 +03:00
{
2023-01-20 22:06:05 +03:00
auto icon = TRY ( Gfx : : Bitmap : : load_from_file ( " /res/icons/16x16/go-back.png " sv ) ) ;
2022-12-18 04:48:17 +03:00
m_locations_history_back_action = GUI : : Action : : create ( " Go Back " , { Mod_Alt | Mod_Shift , Key_Left } , icon , [ this ] ( auto & ) {
if ( m_locations_history_end_index < = 1 )
return ;
2021-08-27 16:26:18 +03:00
2022-12-18 04:48:17 +03:00
auto location = m_locations_history [ m_locations_history_end_index - 2 ] ;
- - m_locations_history_end_index ;
2021-08-27 16:26:18 +03:00
2022-12-18 04:48:17 +03:00
m_locations_history_disabled = true ;
open_file ( location . filename , location . line , location . column ) ;
m_locations_history_disabled = false ;
2021-08-27 16:26:18 +03:00
2022-12-18 04:48:17 +03:00
update_history_actions ( ) ;
} ) ;
}
2021-08-27 16:26:18 +03:00
2022-12-18 04:48:17 +03:00
{
2023-01-20 22:06:05 +03:00
auto icon = TRY ( Gfx : : Bitmap : : load_from_file ( " /res/icons/16x16/go-forward.png " sv ) ) ;
2022-12-18 04:48:17 +03:00
m_locations_history_forward_action = GUI : : Action : : create ( " Go Forward " , { Mod_Alt | Mod_Shift , Key_Right } , icon , [ this ] ( auto & ) {
if ( m_locations_history_end_index = = m_locations_history . size ( ) )
return ;
2021-08-27 16:26:18 +03:00
2022-12-18 04:48:17 +03:00
auto location = m_locations_history [ m_locations_history_end_index ] ;
+ + m_locations_history_end_index ;
2021-08-27 16:26:18 +03:00
2022-12-18 04:48:17 +03:00
m_locations_history_disabled = true ;
open_file ( location . filename , location . line , location . column ) ;
m_locations_history_disabled = false ;
2021-08-27 16:26:18 +03:00
2022-12-18 04:48:17 +03:00
update_history_actions ( ) ;
} ) ;
}
2021-08-27 16:26:18 +03:00
m_locations_history_forward_action - > set_enabled ( false ) ;
2022-12-18 04:48:17 +03:00
return { } ;
2021-08-27 16:26:18 +03:00
}
2022-12-18 04:48:17 +03:00
ErrorOr < NonnullRefPtr < GUI : : Action > > HackStudioWidget : : create_open_project_configuration_action ( )
2022-03-19 14:47:18 +03:00
{
2023-01-20 22:06:05 +03:00
auto icon = TRY ( Gfx : : Bitmap : : load_from_file ( " /res/icons/16x16/settings.png " sv ) ) ;
2022-12-18 04:48:17 +03:00
return GUI : : Action : : create ( " Project Configuration " , icon , [ & ] ( auto & ) {
2022-03-19 14:47:18 +03:00
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 ) ;
2023-12-16 17:19:34 +03:00
ByteString formatted_error_string_holder ;
2022-12-18 21:36:23 +03:00
auto save_configuration_or_error = [ & ] ( ) - > ErrorOr < void > {
2023-03-21 18:35:30 +03:00
if ( FileSystem : : exists ( absolute_config_file_path ) )
2022-12-18 21:36:23 +03:00
return { } ;
2023-03-21 18:35:30 +03:00
if ( FileSystem : : exists ( parent_directory ) & & ! FileSystem : : is_directory ( parent_directory ) ) {
2023-12-16 17:19:34 +03:00
formatted_error_string_holder = ByteString : : formatted ( " Cannot create the '{}' directory because there is already a file with that name " , parent_directory ) ;
2023-04-27 20:51:19 +03:00
return Error : : from_string_view ( formatted_error_string_holder . view ( ) ) ;
2022-03-19 14:47:18 +03:00
}
2022-12-18 21:36:23 +03:00
auto maybe_error = Core : : System : : mkdir ( LexicalPath : : absolute_path ( m_project - > root_path ( ) , parent_directory ) , 0755 ) ;
if ( maybe_error . is_error ( ) & & maybe_error . error ( ) . code ( ) ! = EEXIST )
2023-02-09 21:26:53 +03:00
return maybe_error . release_error ( ) ;
2022-03-19 14:47:18 +03:00
2023-02-09 05:02:46 +03:00
auto file = TRY ( Core : : File : : open ( absolute_config_file_path , Core : : File : : OpenMode : : Write ) ) ;
2023-03-01 17:37:45 +03:00
TRY ( file - > write_until_depleted (
2022-03-19 14:47:18 +03:00
" { \n "
" \" build_command \" : \" your build command here \" , \n "
" \" run_command \" : \" your run command here \" \n "
2022-12-18 21:36:23 +03:00
" } \n " sv . bytes ( ) ) ) ;
return { } ;
} ( ) ;
if ( save_configuration_or_error . is_error ( ) ) {
2023-12-16 17:19:34 +03:00
GUI : : MessageBox : : show_error ( window ( ) , ByteString : : formatted ( " Saving configuration failed: {}. " , save_configuration_or_error . error ( ) ) ) ;
2022-12-18 21:36:23 +03:00
return ;
2022-03-19 14:47:18 +03:00
}
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
2023-02-20 21:03:59 +03:00
RefPtr < Gfx : : Font const > HackStudioWidget : : read_editor_font_from_config ( )
2021-10-06 17:01:52 +03:00
{
2023-02-28 19:57:46 +03:00
auto font_family = Config : : read_string ( " HackStudio " sv , " EditorFont " sv , " Family " sv ) ;
auto font_variant = Config : : read_string ( " HackStudio " sv , " EditorFont " sv , " Variant " sv ) ;
auto font_size = Config : : read_i32 ( " HackStudio " sv , " EditorFont " sv , " Size " sv ) ;
2021-10-06 17:53:43 +03:00
2023-09-05 21:19:33 +03:00
auto font = Gfx : : FontDatabase : : the ( ) . get ( MUST ( FlyString : : from_deprecated_fly_string ( font_family ) ) , FlyString : : from_deprecated_fly_string ( font_variant ) . release_value_but_fixme_should_propagate_errors ( ) , font_size ) ;
2021-10-06 17:53:43 +03:00
if ( font . is_null ( ) )
return Gfx : : FontDatabase : : the ( ) . default_fixed_width_font ( ) ;
return font ;
2021-10-06 17:01:52 +03:00
}
2023-02-20 21:03:59 +03:00
void HackStudioWidget : : change_editor_font ( RefPtr < Gfx : : Font const > font )
2021-10-06 17:01:52 +03:00
{
m_editor_font = move ( font ) ;
for ( auto & editor_wrapper : m_all_editor_wrappers ) {
2023-03-06 16:17:01 +03:00
editor_wrapper - > editor ( ) . set_font ( * m_editor_font ) ;
2021-10-06 17:01:52 +03:00
}
2022-07-11 20:32:29 +03:00
Config : : write_string ( " HackStudio " sv , " EditorFont " sv , " Family " sv , m_editor_font - > family ( ) ) ;
Config : : write_string ( " HackStudio " sv , " EditorFont " sv , " Variant " sv , m_editor_font - > variant ( ) ) ;
Config : : write_i32 ( " HackStudio " sv , " EditorFont " sv , " Size " sv , m_editor_font - > presentation_size ( ) ) ;
2021-10-06 17:01:52 +03:00
}
2023-05-27 15:18:19 +03:00
void HackStudioWidget : : open_coredump ( StringView coredump_path )
2021-11-19 17:13:07 +03:00
{
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 ) ;
}
}
2023-02-18 14:14:19 +03:00
void HackStudioWidget : : debug_process ( pid_t pid )
{
open_project ( " /usr/src/serenity " ) ;
Debugger : : the ( ) . set_pid_to_attach ( pid ) ;
m_debugger_thread = Threading : : Thread : : construct ( Debugger : : start_static ) ;
m_debugger_thread - > start ( ) ;
m_stop_action - > set_enabled ( true ) ;
m_run_action - > set_enabled ( false ) ;
for ( auto & editor_wrapper : m_all_editor_wrappers ) {
2023-03-06 16:17:01 +03:00
editor_wrapper - > set_debug_mode ( true ) ;
2023-02-18 14:14:19 +03:00
}
}
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-12-18 04:48:17 +03:00
ErrorOr < NonnullRefPtr < GUI : : Action > > HackStudioWidget : : create_toggle_syntax_highlighting_mode_action ( )
2022-02-07 09:49:07 +03:00
{
2023-01-20 22:06:05 +03:00
auto icon = TRY ( Gfx : : Bitmap : : load_from_file ( " /res/icons/16x16/filetype-cplusplus.png " sv ) ) ;
2022-12-18 04:48:17 +03:00
auto action = GUI : : Action : : create_checkable ( " &Semantic Highlighting " , icon , [ this ] ( auto & action ) {
2022-02-07 09:49:07 +03:00
for ( auto & editor_wrapper : m_all_editor_wrappers )
2023-03-06 16:17:01 +03:00
editor_wrapper - > editor ( ) . set_semantic_syntax_highlighting ( action . is_checked ( ) ) ;
2022-02-07 09:49:07 +03:00
} ) ;
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
}