2020-09-19 17:25:05 +03:00
/*
* Copyright ( c ) 2018 - 2020 , Andreas Kling < kling @ serenityos . org >
* Copyright ( c ) 2020 , Itamar S . < itamar8910 @ gmail . com >
2021-05-09 20:09:30 +03:00
* Copyright ( c ) 2020 - 2021 , the SerenityOS developers .
2020-09-19 17:25:05 +03:00
*
2021-04-22 11:24:48 +03:00
* SPDX - License - Identifier : BSD - 2 - Clause
2020-09-19 17:25:05 +03:00
*/
# include "HackStudioWidget.h"
# include "Debugger/DebugInfoWidget.h"
# include "Debugger/Debugger.h"
# include "Debugger/DisassemblyWidget.h"
2021-02-13 13:22:48 +03:00
# include "Dialogs/NewProjectDialog.h"
2020-09-19 17:25:05 +03:00
# include "Editor.h"
# include "EditorWrapper.h"
# include "FindInFilesWidget.h"
# include "Git/DiffViewer.h"
# include "Git/GitWidget.h"
# include "HackStudio.h"
# include "HackStudioWidget.h"
# include "Locator.h"
# include "Project.h"
2021-04-10 17:38:11 +03:00
# include "ProjectDeclarations.h"
2020-09-19 17:25:05 +03:00
# include "TerminalWrapper.h"
2021-05-22 15:14:49 +03:00
# include "ToDoEntries.h"
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>
2020-09-19 17:25:05 +03:00
# include <LibCore/ArgsParser.h>
# include <LibCore/Event.h>
# include <LibCore/EventLoop.h>
# include <LibCore/File.h>
2021-04-28 17:01:14 +03:00
# include <LibCore/FileWatcher.h>
2020-09-19 17:25:05 +03:00
# include <LibDebug/DebugSession.h>
2021-06-12 00:33:39 +03:00
# include <LibDesktop/Launcher.h>
2020-09-19 17:25:05 +03:00
# include <LibGUI/Action.h>
# include <LibGUI/ActionGroup.h>
# include <LibGUI/Application.h>
# include <LibGUI/BoxLayout.h>
# include <LibGUI/Button.h>
2021-02-13 13:22:48 +03:00
# include <LibGUI/Dialog.h>
2021-01-02 13:59:55 +03:00
# include <LibGUI/EditingEngine.h>
2020-09-19 17:25:05 +03:00
# include <LibGUI/FilePicker.h>
# 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-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>
# 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>
2020-12-29 20:25:13 +03:00
# include <LibGfx/FontDatabase.h>
2021-03-19 14:06:32 +03:00
# include <LibGfx/Palette.h>
2021-05-22 19:47:42 +03:00
# include <LibThreading/Lock.h>
# include <LibThreading/Thread.h>
2020-09-19 17:25:05 +03:00
# include <LibVT/TerminalWidget.h>
# include <fcntl.h>
# include <spawn.h>
# include <stdio.h>
2021-04-28 17:01:14 +03:00
# include <sys/stat.h>
2020-09-19 17:25:05 +03:00
# include <sys/types.h>
# include <sys/wait.h>
# include <unistd.h>
namespace HackStudio {
HackStudioWidget : : HackStudioWidget ( const String & path_to_project )
{
set_fill_with_background_color ( true ) ;
set_layout < GUI : : VerticalBoxLayout > ( ) ;
layout ( ) - > set_spacing ( 2 ) ;
open_project ( path_to_project ) ;
2021-04-13 17:18:20 +03:00
auto & toolbar_container = add < GUI : : ToolbarContainer > ( ) ;
2020-09-19 17:25:05 +03:00
auto & outer_splitter = add < GUI : : HorizontalSplitter > ( ) ;
2020-10-26 14:13:32 +03:00
auto & left_hand_splitter = outer_splitter . add < GUI : : VerticalSplitter > ( ) ;
2020-12-30 03:23:32 +03:00
left_hand_splitter . set_fixed_width ( 150 ) ;
2021-04-10 17:38:11 +03:00
create_project_tab ( left_hand_splitter ) ;
2020-09-19 17:25:05 +03:00
m_project_tree_view_context_menu = create_project_tree_view_context_menu ( ) ;
2020-10-26 14:13:32 +03:00
create_open_files_view ( left_hand_splitter ) ;
2020-09-19 17:25:05 +03:00
m_right_hand_splitter = outer_splitter . add < GUI : : VerticalSplitter > ( ) ;
m_right_hand_stack = m_right_hand_splitter - > add < GUI : : StackWidget > ( ) ;
2020-12-10 20:59:03 +03:00
// Put a placeholder widget front & center since we don't have a file open yet.
m_right_hand_stack - > add < GUI : : Widget > ( ) ;
2020-09-19 17:25:05 +03:00
m_diff_viewer = m_right_hand_stack - > add < DiffViewer > ( ) ;
m_editors_splitter = m_right_hand_stack - > add < GUI : : VerticalSplitter > ( ) ;
m_editors_splitter - > layout ( ) - > set_margins ( { 0 , 3 , 0 , 0 } ) ;
add_new_editor ( * m_editors_splitter ) ;
m_switch_to_next_editor = create_switch_to_next_editor_action ( ) ;
m_switch_to_previous_editor = create_switch_to_previous_editor_action ( ) ;
m_remove_current_editor_action = create_remove_current_editor_action ( ) ;
m_open_action = create_open_action ( ) ;
m_save_action = create_save_action ( ) ;
2021-02-13 13:22:48 +03:00
m_new_project_action = create_new_project_action ( ) ;
2020-09-19 17:25:05 +03:00
create_action_tab ( * m_right_hand_splitter ) ;
m_add_editor_action = create_add_editor_action ( ) ;
m_add_terminal_action = create_add_terminal_action ( ) ;
m_remove_current_terminal_action = create_remove_current_terminal_action ( ) ;
m_locator = add < Locator > ( ) ;
m_terminal_wrapper - > on_command_exit = [ this ] {
m_stop_action - > set_enabled ( false ) ;
} ;
m_build_action = create_build_action ( ) ;
m_run_action = create_run_action ( ) ;
m_stop_action = create_stop_action ( ) ;
m_debug_action = create_debug_action ( ) ;
initialize_debugger ( ) ;
create_toolbar ( toolbar_container ) ;
2021-05-09 20:09:30 +03:00
auto maybe_watcher = Core : : FileWatcher : : create ( ) ;
if ( maybe_watcher . is_error ( ) ) {
warnln ( " Couldn't create a file watcher, deleted files won't be noticed! Error: {} " , maybe_watcher . error ( ) ) ;
} else {
m_file_watcher = maybe_watcher . release_value ( ) ;
m_file_watcher - > on_change = [ this ] ( Core : : FileWatcherEvent const & event ) {
if ( event . type ! = Core : : FileWatcherEvent : : Type : : Deleted )
return ;
if ( event . event_path . starts_with ( project ( ) . root_path ( ) ) ) {
String relative_path = LexicalPath : : relative_path ( event . event_path , project ( ) . root_path ( ) ) ;
handle_external_file_deletion ( relative_path ) ;
} else {
handle_external_file_deletion ( event . event_path ) ;
}
} ;
}
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 ;
if ( StringView { " TerminalWrapper " } ! = widget - > class_name ( ) )
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
}
2020-12-10 20:59:03 +03:00
void HackStudioWidget : : open_project ( const String & root_path )
2020-09-19 17:25:05 +03:00
{
2020-12-10 20:59:03 +03:00
if ( chdir ( root_path . characters ( ) ) < 0 ) {
2020-09-19 17:25:05 +03:00
perror ( " chdir " ) ;
exit ( 1 ) ;
}
2020-12-10 20:59:03 +03:00
m_project = Project : : open_with_root_path ( root_path ) ;
2021-02-23 22:42:32 +03:00
VERIFY ( m_project ) ;
2020-09-19 17:25:05 +03:00
if ( m_project_tree_view ) {
m_project_tree_view - > set_model ( m_project - > model ( ) ) ;
m_project_tree_view - > update ( ) ;
}
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-06-12 07:07:18 +03:00
for ( auto & editor_wrapper : m_all_editor_wrappers )
editor_wrapper . set_project_root ( LexicalPath ( m_project - > root_path ( ) ) ) ;
2020-09-19 17:25:05 +03:00
}
2021-03-31 03:54:39 +03:00
Vector < String > HackStudioWidget : : selected_file_paths ( ) const
2020-09-19 17:25:05 +03:00
{
Vector < String > files ;
m_project_tree_view - > selection ( ) . for_each_index ( [ & ] ( const GUI : : ModelIndex & index ) {
2021-03-31 03:54:39 +03:00
String sub_path = index . data ( ) . as_string ( ) ;
GUI : : ModelIndex parent_or_invalid = index . parent ( ) ;
while ( parent_or_invalid . is_valid ( ) ) {
sub_path = String : : formatted ( " {}/{} " , parent_or_invalid . data ( ) . as_string ( ) , sub_path ) ;
parent_or_invalid = parent_or_invalid . parent ( ) ;
}
files . append ( sub_path ) ;
2020-09-19 17:25:05 +03:00
} ) ;
return files ;
}
2021-04-15 19:42:40 +03:00
bool HackStudioWidget : : open_file ( const String & full_filename )
2020-09-19 17:25:05 +03:00
{
2021-02-20 13:16:32 +03:00
String filename = full_filename ;
if ( full_filename . starts_with ( project ( ) . root_path ( ) ) ) {
filename = LexicalPath : : relative_path ( full_filename , project ( ) . root_path ( ) ) ;
}
dbgln ( " HackStudio is opening {} " , filename ) ;
2021-04-15 19:42:40 +03:00
if ( Core : : File : : is_directory ( filename ) | | ! Core : : File : : exists ( filename ) )
return false ;
2021-02-02 00:05:14 +03:00
2021-05-01 13:04:19 +03:00
if ( ! active_file ( ) . is_empty ( ) ) {
2020-11-03 15:27:05 +03:00
// Since the file is previously open, it should always be in m_open_files.
2021-05-01 13:04:19 +03:00
VERIFY ( m_open_files . find ( active_file ( ) ) ! = m_open_files . end ( ) ) ;
auto previous_open_project_file = m_open_files . get ( active_file ( ) ) . value ( ) ;
2020-11-03 15:27:05 +03:00
// Update the scrollbar values of the previous_open_project_file and save them to m_open_files.
previous_open_project_file - > vertical_scroll_value ( current_editor ( ) . vertical_scrollbar ( ) . value ( ) ) ;
previous_open_project_file - > horizontal_scroll_value ( current_editor ( ) . horizontal_scrollbar ( ) . value ( ) ) ;
2020-10-26 14:13:32 +03:00
}
2020-11-03 15:27:05 +03:00
RefPtr < ProjectFile > new_project_file = nullptr ;
if ( auto it = m_open_files . find ( filename ) ; it ! = m_open_files . end ( ) ) {
new_project_file = it - > value ;
} else {
new_project_file = m_project - > get_file ( filename ) ;
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
}
2020-10-26 14:13:32 +03:00
m_open_files_view - > model ( ) - > update ( ) ;
2020-09-19 17:25:05 +03:00
}
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-01-02 13:59:55 +03:00
current_editor ( ) . set_editing_engine ( make < GUI : : RegularEditingEngine > ( ) ) ;
2020-10-26 14:13:32 +03:00
2020-09-19 17:25:05 +03:00
if ( filename . ends_with ( " .frm " ) ) {
set_edit_mode ( EditMode : : Form ) ;
} else {
set_edit_mode ( EditMode : : Text ) ;
}
2021-05-01 13:04:19 +03:00
String relative_file_path = filename ;
if ( filename . starts_with ( m_project - > root_path ( ) ) )
relative_file_path = filename . substring ( m_project - > root_path ( ) . length ( ) + 1 ) ;
2020-12-10 20:59:03 +03:00
2021-01-05 01:59:22 +03:00
window ( ) - > set_title ( String : : formatted ( " {} - {} - Hack Studio " , relative_file_path , m_project - > name ( ) ) ) ;
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 ) ;
2020-09-19 17:25:05 +03:00
current_editor ( ) . set_focus ( true ) ;
2021-04-15 19:42:40 +03:00
return true ;
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 ;
}
GUI : : TextEditor & HackStudioWidget : : current_editor ( )
{
return current_editor_wrapper ( ) . editor ( ) ;
}
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 : : Form ) {
m_right_hand_stack - > set_active_widget ( m_form_inner_container ) ;
} else if ( mode = = EditMode : : Diff ) {
m_right_hand_stack - > set_active_widget ( m_diff_viewer ) ;
} else {
2021-02-23 22:42:32 +03:00
VERIFY_NOT_REACHED ( ) ;
2020-09-19 17:25:05 +03:00
}
m_right_hand_stack - > active_widget ( ) - > update ( ) ;
}
NonnullRefPtr < GUI : : Menu > HackStudioWidget : : create_project_tree_view_context_menu ( )
{
m_open_selected_action = create_open_selected_action ( ) ;
2021-06-12 00:33:39 +03:00
m_show_in_file_manager_action = create_show_in_file_manager_action ( ) ;
2021-01-31 21:06:41 +03:00
m_new_file_action = create_new_file_action ( ) ;
m_new_directory_action = create_new_directory_action ( ) ;
2020-09-19 17:25:05 +03:00
m_delete_action = create_delete_action ( ) ;
auto project_tree_view_context_menu = GUI : : Menu : : construct ( " Project Files " ) ;
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 ) ;
// TODO: Rename, cut, copy, duplicate with new name...
2020-09-19 17:25:05 +03:00
project_tree_view_context_menu - > add_separator ( ) ;
2021-01-31 21:06:41 +03:00
project_tree_view_context_menu - > add_action ( * m_new_file_action ) ;
project_tree_view_context_menu - > add_action ( * m_new_directory_action ) ;
2020-09-19 17:25:05 +03:00
project_tree_view_context_menu - > add_action ( * m_delete_action ) ;
return project_tree_view_context_menu ;
}
2021-01-31 21:06:41 +03:00
NonnullRefPtr < GUI : : Action > HackStudioWidget : : create_new_file_action ( )
2020-09-19 17:25:05 +03:00
{
2021-04-10 11:19:25 +03:00
return GUI : : Action : : create ( " New &File... " , { Mod_Ctrl , Key_N } , Gfx : : Bitmap : : load_from_file ( " /res/icons/16x16/new.png " ) , [ this ] ( const GUI : : Action & ) {
2020-09-19 17:25:05 +03:00
String filename ;
2021-02-20 14:03:28 +03:00
if ( GUI : : InputBox : : show ( window ( ) , filename , " Enter name of new file: " , " Add new file to project " ) ! = GUI : : InputBox : : ExecOK )
2020-09-19 17:25:05 +03:00
return ;
2021-03-31 03:54:39 +03:00
auto path_to_selected = selected_file_paths ( ) ;
String filepath ;
if ( ! path_to_selected . is_empty ( ) ) {
VERIFY ( Core : : File : : exists ( path_to_selected . first ( ) ) ) ;
LexicalPath selected ( path_to_selected . first ( ) ) ;
String dir_path ;
if ( Core : : File : : is_directory ( selected . string ( ) ) )
dir_path = selected . string ( ) ;
else
dir_path = selected . dirname ( ) ;
filepath = String : : formatted ( " {}/ " , dir_path ) ;
}
filepath = String : : formatted ( " {}{} " , filepath , filename ) ;
auto file = Core : : File : : construct ( filepath ) ;
2021-05-12 12:26:43 +03:00
if ( ! file - > open ( ( Core : : OpenMode ) ( Core : : OpenMode : : WriteOnly | Core : : OpenMode : : MustBeNew ) ) ) {
2021-03-31 03:54:39 +03:00
GUI : : MessageBox : : show ( window ( ) , String : : formatted ( " Failed to create '{}' " , filepath ) , " Error " , GUI : : MessageBox : : Type : : Error ) ;
2020-09-19 17:25:05 +03:00
return ;
}
2021-03-31 03:54:39 +03:00
open_file ( filepath ) ;
2020-09-19 17:25:05 +03:00
} ) ;
}
2021-01-31 21:06:41 +03:00
NonnullRefPtr < GUI : : Action > HackStudioWidget : : create_new_directory_action ( )
{
2021-05-21 19:51:06 +03:00
return GUI : : Action : : create ( " &New Directory... " , { Mod_Ctrl | Mod_Shift , Key_N } , Gfx : : Bitmap : : load_from_file ( " /res/icons/16x16/mkdir.png " ) , [ this ] ( const GUI : : Action & ) {
2021-01-31 21:06:41 +03:00
String directory_name ;
2021-02-20 14:03:28 +03:00
if ( GUI : : InputBox : : show ( window ( ) , directory_name , " Enter name of new directory: " , " Add new folder to project " ) ! = GUI : : InputBox : : ExecOK )
2021-01-31 21:06:41 +03:00
return ;
2021-03-31 03:54:39 +03:00
auto path_to_selected = selected_file_paths ( ) ;
if ( ! path_to_selected . is_empty ( ) ) {
LexicalPath selected ( path_to_selected . first ( ) ) ;
String dir_path ;
if ( Core : : File : : is_directory ( selected . string ( ) ) )
dir_path = selected . string ( ) ;
else
dir_path = selected . dirname ( ) ;
directory_name = String : : formatted ( " {}/{} " , dir_path , directory_name ) ;
}
2021-01-31 21:06:41 +03:00
auto formatted_dir_name = LexicalPath : : canonicalized_path ( String : : formatted ( " {}/{} " , m_project - > model ( ) . root_path ( ) , directory_name ) ) ;
int rc = mkdir ( formatted_dir_name . characters ( ) , 0755 ) ;
if ( rc < 0 ) {
GUI : : MessageBox : : show ( window ( ) , " Failed to create new directory " , " Error " , GUI : : MessageBox : : Type : : Error ) ;
return ;
}
} ) ;
}
2020-09-19 17:25:05 +03:00
NonnullRefPtr < GUI : : Action > HackStudioWidget : : create_open_selected_action ( )
{
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 ) ;
} ) ;
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 ( )
{
auto show_in_file_manager_action = GUI : : Action : : create ( " Show in File Manager " , [ this ] ( const GUI : : Action & ) {
auto files = selected_file_paths ( ) ;
for ( auto & file : files )
Desktop : : Launcher : : open ( URL : : create_with_file_protocol ( m_project - > root_path ( ) , file ) ) ;
} ) ;
show_in_file_manager_action - > set_enabled ( true ) ;
show_in_file_manager_action - > set_icon ( GUI : : Icon : : default_icon ( " app-file-manager " ) . bitmap_for_size ( 16 ) ) ;
return show_in_file_manager_action ;
}
2020-09-19 17:25:05 +03:00
NonnullRefPtr < GUI : : Action > HackStudioWidget : : create_delete_action ( )
{
auto delete_action = GUI : : CommonActions : : make_delete_action ( [ this ] ( const GUI : : Action & ) {
2021-03-31 03:54:39 +03:00
auto files = selected_file_paths ( ) ;
2020-09-19 17:25:05 +03:00
if ( files . is_empty ( ) )
return ;
String message ;
if ( files . size ( ) = = 1 ) {
2021-03-31 03:54:39 +03:00
LexicalPath file ( files [ 0 ] ) ;
message = String : : formatted ( " Really remove {} from disk? " , file . basename ( ) ) ;
2020-09-19 17:25:05 +03:00
} else {
2020-12-10 20:59:03 +03:00
message = String : : formatted ( " Really remove {} files from disk? " , files . size ( ) ) ;
2020-09-19 17:25:05 +03:00
}
auto result = GUI : : MessageBox : : show ( window ( ) ,
message ,
" Confirm deletion " ,
GUI : : MessageBox : : Type : : Warning ,
GUI : : MessageBox : : InputType : : OKCancel ) ;
if ( result = = GUI : : MessageBox : : ExecCancel )
return ;
for ( auto & file : files ) {
2021-03-02 08:18:59 +03:00
struct stat st ;
if ( lstat ( file . characters ( ) , & st ) < 0 ) {
2020-09-19 17:25:05 +03:00
GUI : : MessageBox : : show ( window ( ) ,
2021-03-02 08:18:59 +03:00
String : : formatted ( " lstat ({}) failed: {} " , file , strerror ( errno ) ) ,
2020-09-19 17:25:05 +03:00
" Removal failed " ,
GUI : : MessageBox : : Type : : Error ) ;
break ;
}
2021-03-02 08:18:59 +03:00
bool is_directory = S_ISDIR ( st . st_mode ) ;
auto result = Core : : File : : remove ( file , Core : : File : : RecursionMode : : Allowed , false ) ;
if ( result . is_error ( ) ) {
auto & error = result . error ( ) ;
if ( is_directory ) {
GUI : : MessageBox : : show ( window ( ) ,
String : : formatted ( " Removing directory {} from the project failed: {} " , error . file , error . error_code ) ,
" Removal failed " ,
GUI : : MessageBox : : Type : : Error ) ;
} else {
GUI : : MessageBox : : show ( window ( ) ,
String : : formatted ( " Removing file {} from the project failed: {} " , error . file , error . error_code ) ,
" Removal failed " ,
GUI : : MessageBox : : Type : : Error ) ;
}
break ;
}
2020-09-19 17:25:05 +03:00
}
} ) ;
delete_action - > set_enabled ( false ) ;
return delete_action ;
}
2021-02-13 13:22:48 +03:00
NonnullRefPtr < GUI : : Action > HackStudioWidget : : create_new_project_action ( )
{
2021-04-10 11:19:25 +03:00
return GUI : : Action : : create ( " &New Project... " , { Mod_Ctrl | Mod_Shift , Key_N } , Gfx : : Bitmap : : load_from_file ( " /res/icons/16x16/hackstudio-project.png " ) , [ this ] ( const GUI : : Action & ) {
2021-02-13 13:22:48 +03:00
auto dialog = NewProjectDialog : : construct ( window ( ) ) ;
dialog - > set_icon ( window ( ) - > icon ( ) ) ;
auto result = dialog - > exec ( ) ;
if ( result = = GUI : : Dialog : : ExecResult : : ExecOK & & dialog - > created_project_path ( ) . has_value ( ) )
open_project ( dialog - > created_project_path ( ) . value ( ) ) ;
} ) ;
}
2020-09-19 17:25:05 +03:00
void HackStudioWidget : : add_new_editor ( GUI : : Widget & parent )
{
2020-09-27 00:11:15 +03:00
auto wrapper = EditorWrapper : : construct ( ) ;
2020-09-19 17:25:05 +03:00
if ( m_action_tab_widget ) {
parent . insert_child_before ( wrapper , * m_action_tab_widget ) ;
} else {
parent . add_child ( wrapper ) ;
}
m_current_editor_wrapper = wrapper ;
m_all_editor_wrappers . append ( wrapper ) ;
wrapper - > editor ( ) . set_focus ( true ) ;
2021-06-12 07:07:18 +03:00
wrapper - > set_project_root ( LexicalPath ( m_project - > root_path ( ) ) ) ;
2020-09-19 17:25:05 +03:00
}
NonnullRefPtr < GUI : : Action > HackStudioWidget : : create_switch_to_next_editor_action ( )
{
2021-04-10 11:19:25 +03:00
return GUI : : Action : : create ( " Switch to &Next Editor " , { Mod_Ctrl , Key_E } , [ this ] ( auto & ) {
2020-09-19 17:25:05 +03:00
if ( m_all_editor_wrappers . size ( ) < = 1 )
return ;
2021-06-08 18:06:27 +03:00
Vector < EditorWrapper & > wrappers ;
2020-09-19 17:25:05 +03:00
m_editors_splitter - > for_each_child_of_type < EditorWrapper > ( [ this , & wrappers ] ( auto & child ) {
2021-06-08 18:06:27 +03:00
wrappers . append ( child ) ;
2020-09-19 17:25:05 +03:00
return IterationDecision : : Continue ;
} ) ;
for ( size_t i = 0 ; i < wrappers . size ( ) ; + + i ) {
2021-06-08 18:06:27 +03:00
if ( m_current_editor_wrapper . ptr ( ) = = & wrappers [ i ] ) {
2020-09-19 17:25:05 +03:00
if ( i = = wrappers . size ( ) - 1 )
2021-06-08 18:06:27 +03:00
wrappers [ 0 ] . editor ( ) . set_focus ( true ) ;
2020-09-19 17:25:05 +03:00
else
2021-06-08 18:06:27 +03:00
wrappers [ i + 1 ] . editor ( ) . set_focus ( true ) ;
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 & ) {
2020-09-19 17:25:05 +03:00
if ( m_all_editor_wrappers . size ( ) < = 1 )
return ;
2021-06-08 18:06:27 +03:00
Vector < EditorWrapper & > wrappers ;
2020-09-19 17:25:05 +03:00
m_editors_splitter - > for_each_child_of_type < EditorWrapper > ( [ this , & wrappers ] ( auto & child ) {
2021-06-08 18:06:27 +03:00
wrappers . append ( child ) ;
2020-09-19 17:25:05 +03:00
return IterationDecision : : Continue ;
} ) ;
for ( int i = wrappers . size ( ) - 1 ; i > = 0 ; - - i ) {
2021-06-08 18:06:27 +03:00
if ( m_current_editor_wrapper . ptr ( ) = = & wrappers [ i ] ) {
2020-09-19 17:25:05 +03:00
if ( i = = 0 )
2021-06-08 18:06:27 +03:00
wrappers . last ( ) . editor ( ) . set_focus ( true ) ;
2020-09-19 17:25:05 +03:00
else
2021-06-08 18:06:27 +03:00
wrappers [ i - 1 ] . editor ( ) . set_focus ( true ) ;
2020-09-19 17:25:05 +03:00
}
}
} ) ;
}
NonnullRefPtr < GUI : : Action > HackStudioWidget : : create_remove_current_editor_action ( )
{
2021-04-10 11:19:25 +03:00
return GUI : : Action : : create ( " &Remove Current Editor " , { Mod_Alt | Mod_Shift , Key_E } , [ this ] ( auto & ) {
2020-09-19 17:25:05 +03:00
if ( m_all_editor_wrappers . size ( ) < = 1 )
return ;
auto wrapper = m_current_editor_wrapper ;
m_switch_to_next_editor - > activate ( ) ;
m_editors_splitter - > remove_child ( * wrapper ) ;
m_all_editor_wrappers . remove_first_matching ( [ & wrapper ] ( auto & entry ) { return entry = = wrapper . ptr ( ) ; } ) ;
update_actions ( ) ;
} ) ;
}
NonnullRefPtr < GUI : : Action > HackStudioWidget : : create_open_action ( )
{
2021-04-10 11:19:25 +03:00
return GUI : : Action : : create ( " &Open Project... " , { Mod_Ctrl | Mod_Shift , Key_O } , Gfx : : Bitmap : : load_from_file ( " /res/icons/16x16/open.png " ) , [ this ] ( auto & ) {
2021-04-12 02:34:10 +03:00
auto open_path = GUI : : FilePicker : : get_open_filepath ( window ( ) , " Open project " , Core : : StandardPaths : : home_directory ( ) , 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 ( ) )
2020-09-19 17:25:05 +03:00
return ;
2021-05-01 13:29:30 +03:00
current_editor_wrapper ( ) . save ( ) ;
2020-09-19 17:25:05 +03:00
if ( m_git_widget - > initialized ( ) )
m_git_widget - > refresh ( ) ;
} ) ;
}
NonnullRefPtr < GUI : : Action > HackStudioWidget : : create_remove_current_terminal_action ( )
{
2021-05-21 19:51:06 +03:00
return GUI : : Action : : create ( " Remove &Current Terminal " , { Mod_Alt | Mod_Shift , Key_T } , [ 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 ( ) ;
} ) ;
}
NonnullRefPtr < GUI : : Action > HackStudioWidget : : create_add_editor_action ( )
{
2021-04-10 11:19:25 +03:00
return GUI : : Action : : create ( " Add New &Editor " , { Mod_Ctrl | Mod_Alt , Key_E } ,
2020-09-19 17:25:05 +03:00
Gfx : : Bitmap : : load_from_file ( " /res/icons/16x16/app-text-editor.png " ) ,
[ this ] ( auto & ) {
add_new_editor ( * m_editors_splitter ) ;
update_actions ( ) ;
} ) ;
}
NonnullRefPtr < GUI : : Action > HackStudioWidget : : create_add_terminal_action ( )
{
2021-04-10 11:19:25 +03:00
return GUI : : Action : : create ( " Add New &Terminal " , { Mod_Ctrl | Mod_Alt , Key_T } ,
2020-09-19 17:25:05 +03:00
Gfx : : Bitmap : : load_from_file ( " /res/icons/16x16/app-terminal.png " ) ,
[ this ] ( auto & ) {
2020-11-10 13:47:51 +03:00
auto & terminal_wrapper = m_action_tab_widget - > add_tab < TerminalWrapper > ( " Terminal " ) ;
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 )
{
2020-12-30 03:23:32 +03:00
if ( m_action_tab_widget - > min_height ( ) < 200 )
m_action_tab_widget - > set_fixed_height ( 200 ) ;
2020-09-19 17:25:05 +03:00
m_action_tab_widget - > set_active_widget ( & widget ) ;
}
NonnullRefPtr < GUI : : Action > HackStudioWidget : : create_debug_action ( )
{
2021-04-10 11:19:25 +03:00
return GUI : : Action : : create ( " &Debug " , Gfx : : Bitmap : : load_from_file ( " /res/icons/16x16/debug-run.png " ) , [ this ] ( auto & ) {
2021-02-20 01:46:54 +03:00
if ( ! Core : : File : : exists ( get_project_executable_path ( ) ) ) {
2020-10-08 14:41:36 +03:00
GUI : : MessageBox : : show ( window ( ) , String : : formatted ( " Could not find file: {}. (did you build the project?) " , get_project_executable_path ( ) ) , " Error " , GUI : : MessageBox : : Type : : Error ) ;
2020-09-19 17:25:05 +03:00
return ;
}
if ( Debugger : : the ( ) . session ( ) ) {
GUI : : MessageBox : : show ( window ( ) , " Debugger is already running " , " Error " , GUI : : MessageBox : : Type : : Error ) ;
return ;
}
2020-12-10 20:59:03 +03:00
2020-09-19 17:25:05 +03:00
Debugger : : the ( ) . set_executable_path ( get_project_executable_path ( ) ) ;
2021-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 ) ;
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 ( ) ,
2020-09-19 17:25:05 +03:00
[ this ] ( const PtraceRegisters & 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-01-06 23:00:53 +03:00
auto source_position = debug_session . get_source_position ( regs . eip ) ;
2020-09-19 17:25:05 +03:00
if ( ! source_position . has_value ( ) ) {
2020-10-08 14:41:36 +03:00
dbgln ( " Could not find source position for address: {:p} " , regs . eip ) ;
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
Core : : EventLoop : : main ( ) . post_event (
* window ( ) ,
make < Core : : DeferredInvocationEvent > (
[ this , source_position , & regs ] ( auto & ) {
m_current_editor_in_execution = get_editor_of_file ( source_position . value ( ) . file_path ) ;
2021-04-15 19:42:40 +03:00
if ( m_current_editor_in_execution )
m_current_editor_in_execution - > editor ( ) . set_execution_position ( source_position . value ( ) . line_number - 1 ) ;
2020-09-19 17:25:05 +03:00
m_debug_info_widget - > update_state ( * Debugger : : the ( ) . session ( ) , regs ) ;
m_debug_info_widget - > set_debug_actions_enabled ( true ) ;
m_disassembly_widget - > update_state ( * Debugger : : the ( ) . session ( ) , regs ) ;
HackStudioWidget : : reveal_action_tab ( * m_debug_info_widget ) ;
} ) ) ;
Core : : EventLoop : : wake ( ) ;
return Debugger : : HasControlPassedToUser : : Yes ;
} ,
[ this ] ( ) {
Core : : EventLoop : : main ( ) . post_event ( * window ( ) , make < Core : : DeferredInvocationEvent > ( [ this ] ( auto & ) {
m_debug_info_widget - > set_debug_actions_enabled ( false ) ;
2021-04-14 00:01:30 +03:00
if ( m_current_editor_in_execution )
2020-09-19 17:25:05 +03:00
m_current_editor_in_execution - > editor ( ) . clear_execution_position ( ) ;
} ) ) ;
Core : : EventLoop : : wake ( ) ;
} ,
[ this ] ( ) {
Core : : EventLoop : : main ( ) . post_event ( * window ( ) , make < Core : : DeferredInvocationEvent > ( [ this ] ( auto & ) {
2021-04-14 00:01:30 +03:00
m_debug_info_widget - > set_debug_actions_enabled ( false ) ;
if ( m_current_editor_in_execution )
m_current_editor_in_execution - > editor ( ) . clear_execution_position ( ) ;
2020-09-19 17:25:05 +03:00
m_debug_info_widget - > program_stopped ( ) ;
m_disassembly_widget - > program_stopped ( ) ;
2021-04-14 00:01:30 +03:00
m_stop_action - > set_enabled ( false ) ;
2021-04-14 00:04:46 +03:00
m_debugger_thread . clear ( ) ;
2020-09-19 17:25:05 +03:00
HackStudioWidget : : hide_action_tabs ( ) ;
GUI : : MessageBox : : show ( window ( ) , " Program Exited " , " Debugger " , GUI : : MessageBox : : Type : : Information ) ;
} ) ) ;
Core : : EventLoop : : wake ( ) ;
} ) ;
}
String HackStudioWidget : : get_full_path_of_serenity_source ( const String & file )
{
auto path_parts = LexicalPath ( file ) . parts ( ) ;
2021-02-23 22:42:32 +03:00
VERIFY ( path_parts [ 0 ] = = " .. " ) ;
2020-09-19 17:25:05 +03:00
path_parts . remove ( 0 ) ;
StringBuilder relative_path_builder ;
relative_path_builder . join ( " / " , path_parts ) ;
constexpr char SERENITY_LIBS_PREFIX [ ] = " /usr/src/serenity " ;
LexicalPath serenity_sources_base ( SERENITY_LIBS_PREFIX ) ;
2020-10-08 14:41:36 +03:00
return String : : formatted ( " {}/{} " , serenity_sources_base , relative_path_builder . to_string ( ) ) ;
2020-09-19 17:25:05 +03:00
}
2021-04-29 22:46:15 +03:00
RefPtr < EditorWrapper > HackStudioWidget : : get_editor_of_file ( const String & filename )
2020-09-19 17:25:05 +03:00
{
2021-04-29 22:46:15 +03:00
String file_path = filename ;
2020-09-19 17:25:05 +03:00
// TODO: We can probably do a more specific condition here, something like
// "if (file.starts_with("../Libraries/") || file.starts_with("../AK/"))"
2021-04-29 22:46:15 +03:00
if ( filename . starts_with ( " ../ " ) ) {
file_path = get_full_path_of_serenity_source ( filename ) ;
2020-09-19 17:25:05 +03:00
}
2021-04-15 19:42:40 +03:00
if ( ! open_file ( file_path ) )
return nullptr ;
2020-09-19 17:25:05 +03:00
return current_editor_wrapper ( ) ;
}
String HackStudioWidget : : get_project_executable_path ( ) const
{
2020-12-10 20:59:03 +03:00
// FIXME: Dumb heuristic ahead!
// e.g /my/project => /my/project/project
2020-09-19 17:25:05 +03:00
// TODO: Perhaps a Makefile rule for getting the value of $(PROGRAM) would be better?
2021-06-29 17:46:16 +03:00
return String : : formatted ( " {}/{} " , m_project - > root_path ( ) , LexicalPath : : basename ( m_project - > root_path ( ) ) ) ;
2020-09-19 17:25:05 +03:00
}
void HackStudioWidget : : build ( TerminalWrapper & wrapper )
{
2021-05-01 13:04:19 +03:00
if ( active_file ( ) . ends_with ( " .js " ) )
wrapper . run_command ( String : : formatted ( " js -A {} " , active_file ( ) ) ) ;
2020-09-19 17:25:05 +03:00
else
wrapper . run_command ( " make " ) ;
}
void HackStudioWidget : : run ( TerminalWrapper & wrapper )
{
2021-05-01 13:04:19 +03:00
if ( active_file ( ) . ends_with ( " .js " ) )
wrapper . run_command ( String : : formatted ( " js {} " , active_file ( ) ) ) ;
2020-09-19 17:25:05 +03:00
else
wrapper . run_command ( " make run " ) ;
}
void HackStudioWidget : : hide_action_tabs ( )
{
2020-12-30 03:23:32 +03:00
m_action_tab_widget - > set_fixed_height ( 24 ) ;
2020-09-19 17:25:05 +03:00
} ;
Project & HackStudioWidget : : project ( )
{
return * m_project ;
}
void HackStudioWidget : : set_current_editor_wrapper ( RefPtr < EditorWrapper > editor_wrapper )
{
m_current_editor_wrapper = editor_wrapper ;
}
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 ) ;
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 ( ) ) ;
m_delete_action - > set_enabled ( ! m_project_tree_view - > selection ( ) . is_empty ( ) ) ;
} ;
m_project_tree_view - > on_activation = [ this ] ( auto & index ) {
2021-02-23 01:00:57 +03:00
auto full_path_to_file = m_project - > model ( ) . full_path ( index ) ;
open_file ( full_path_to_file ) ;
2020-09-19 17:25:05 +03:00
} ;
}
2020-10-26 14:13:32 +03:00
void HackStudioWidget : : create_open_files_view ( GUI : : Widget & parent )
{
m_open_files_view = parent . add < GUI : : ListView > ( ) ;
auto open_files_model = GUI : : ItemListModel < String > : : create ( m_open_files_vector ) ;
m_open_files_view - > set_model ( open_files_model ) ;
2020-10-26 16:57:52 +03:00
m_open_files_view - > on_activation = [ this ] ( auto & index ) {
2020-10-26 14:13:32 +03:00
open_file ( index . data ( ) . to_string ( ) ) ;
} ;
}
2020-09-19 17:25:05 +03:00
void HackStudioWidget : : create_toolbar ( GUI : : Widget & parent )
{
2021-04-13 17:18:20 +03:00
auto & toolbar = parent . add < GUI : : Toolbar > ( ) ;
2021-01-31 21:06:41 +03:00
toolbar . add_action ( * m_new_file_action ) ;
toolbar . add_action ( * m_new_directory_action ) ;
2020-09-19 17:25:05 +03:00
toolbar . add_action ( * m_save_action ) ;
toolbar . add_action ( * m_delete_action ) ;
toolbar . add_separator ( ) ;
toolbar . add_action ( GUI : : CommonActions : : make_cut_action ( [ this ] ( auto & ) { current_editor ( ) . cut_action ( ) . activate ( ) ; } ) ) ;
toolbar . add_action ( GUI : : CommonActions : : make_copy_action ( [ this ] ( auto & ) { current_editor ( ) . copy_action ( ) . activate ( ) ; } ) ) ;
toolbar . add_action ( GUI : : CommonActions : : make_paste_action ( [ this ] ( auto & ) { current_editor ( ) . paste_action ( ) . activate ( ) ; } ) ) ;
toolbar . add_separator ( ) ;
toolbar . add_action ( GUI : : CommonActions : : make_undo_action ( [ this ] ( auto & ) { current_editor ( ) . undo_action ( ) . activate ( ) ; } ) ) ;
toolbar . add_action ( GUI : : CommonActions : : make_redo_action ( [ this ] ( auto & ) { current_editor ( ) . redo_action ( ) . activate ( ) ; } ) ) ;
toolbar . add_separator ( ) ;
toolbar . add_action ( * m_build_action ) ;
toolbar . add_separator ( ) ;
toolbar . add_action ( * m_run_action ) ;
toolbar . add_action ( * m_stop_action ) ;
toolbar . add_separator ( ) ;
toolbar . add_action ( * m_debug_action ) ;
}
NonnullRefPtr < GUI : : Action > HackStudioWidget : : create_build_action ( )
{
2021-04-10 11:19:25 +03:00
return GUI : : Action : : create ( " &Build " , { Mod_Ctrl , Key_B } , Gfx : : Bitmap : : load_from_file ( " /res/icons/16x16/build.png " ) , [ this ] ( auto & ) {
2021-05-01 13:42:07 +03:00
if ( warn_unsaved_changes ( " There are unsaved changes, do you want to save before building? " ) = = ContinueDecision : : No )
return ;
2020-09-19 17:25:05 +03:00
reveal_action_tab ( * m_terminal_wrapper ) ;
build ( * m_terminal_wrapper ) ;
m_stop_action - > set_enabled ( true ) ;
} ) ;
}
NonnullRefPtr < GUI : : Action > HackStudioWidget : : create_run_action ( )
{
2021-04-10 11:19:25 +03:00
return GUI : : Action : : create ( " &Run " , { Mod_Ctrl , Key_R } , Gfx : : Bitmap : : load_from_file ( " /res/icons/16x16/program-run.png " ) , [ this ] ( auto & ) {
2020-09-19 17:25:05 +03:00
reveal_action_tab ( * m_terminal_wrapper ) ;
run ( * m_terminal_wrapper ) ;
m_stop_action - > set_enabled ( true ) ;
} ) ;
}
void HackStudioWidget : : create_action_tab ( GUI : : Widget & parent )
{
m_action_tab_widget = parent . add < GUI : : TabWidget > ( ) ;
2020-12-30 03:23:32 +03:00
m_action_tab_widget - > set_fixed_height ( 24 ) ;
2020-09-19 17:25:05 +03:00
m_action_tab_widget - > on_change = [ this ] ( auto & ) {
on_action_tab_change ( ) ;
static bool first_time = true ;
if ( ! first_time )
2020-12-30 03:23:32 +03:00
m_action_tab_widget - > set_fixed_height ( 200 ) ;
2020-09-19 17:25:05 +03:00
first_time = false ;
} ;
m_find_in_files_widget = m_action_tab_widget - > add_tab < FindInFilesWidget > ( " Find in files " ) ;
2021-05-22 15:14:49 +03:00
m_todo_entries_widget = m_action_tab_widget - > add_tab < ToDoEntriesWidget > ( " TODO " ) ;
2020-09-19 17:25:05 +03:00
m_terminal_wrapper = m_action_tab_widget - > add_tab < TerminalWrapper > ( " Build " , false ) ;
m_debug_info_widget = m_action_tab_widget - > add_tab < DebugInfoWidget > ( " Debug " ) ;
m_disassembly_widget = m_action_tab_widget - > add_tab < DisassemblyWidget > ( " Disassembly " ) ;
2020-12-10 20:59:03 +03:00
m_git_widget = m_action_tab_widget - > add_tab < GitWidget > ( " Git " , LexicalPath ( m_project - > root_path ( ) ) ) ;
2020-09-19 17:25:05 +03:00
m_git_widget - > set_view_diff_callback ( [ this ] ( const auto & original_content , const auto & diff ) {
m_diff_viewer - > set_content ( original_content , diff ) ;
set_edit_mode ( EditMode : : Diff ) ;
} ) ;
2021-05-22 15:14:49 +03:00
ToDoEntries : : the ( ) . on_update = [ this ] ( ) {
m_todo_entries_widget - > refresh ( ) ;
} ;
2020-09-19 17:25:05 +03:00
}
2021-04-10 17:38:11 +03:00
void HackStudioWidget : : create_project_tab ( GUI : : Widget & parent )
{
m_project_tab = parent . add < GUI : : TabWidget > ( ) ;
m_project_tab - > set_tab_position ( GUI : : TabWidget : : TabPosition : : Bottom ) ;
2021-05-10 13:58:49 +03:00
auto & tree_view_container = m_project_tab - > add_tab < GUI : : Widget > ( " Files " ) ;
tree_view_container . set_layout < GUI : : VerticalBoxLayout > ( ) ;
tree_view_container . layout ( ) - > set_margins ( { 2 , 2 , 2 , 2 } ) ;
m_project_tree_view = tree_view_container . add < GUI : : TreeView > ( ) ;
2021-04-10 17:38:11 +03:00
configure_project_tree_view ( ) ;
2021-05-10 13:58:49 +03:00
auto & class_view_container = m_project_tab - > add_tab < GUI : : Widget > ( " Classes " ) ;
class_view_container . set_layout < GUI : : VerticalBoxLayout > ( ) ;
class_view_container . layout ( ) - > set_margins ( { 2 , 2 , 2 , 2 } ) ;
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 ( ) ;
} ;
}
2021-05-12 19:50:48 +03:00
void HackStudioWidget : : create_file_menubar ( GUI : : Menubar & menubar )
2020-09-19 17:25:05 +03:00
{
2021-04-10 11:19:25 +03:00
auto & file_menu = menubar . add_menu ( " &File " ) ;
file_menu . add_action ( * m_new_project_action ) ;
file_menu . add_action ( * m_open_action ) ;
file_menu . add_action ( * m_save_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 ( ) ;
} ) ) ;
}
2021-04-13 17:18:20 +03:00
void HackStudioWidget : : create_project_menubar ( GUI : : Menubar & menubar )
2020-09-19 17:25:05 +03:00
{
2021-04-10 11:19:25 +03:00
auto & project_menu = menubar . add_menu ( " &Project " ) ;
2021-01-31 21:06:41 +03:00
project_menu . add_action ( * m_new_file_action ) ;
project_menu . add_action ( * m_new_directory_action ) ;
2020-09-19 17:25:05 +03:00
}
2021-04-13 17:18:20 +03:00
void HackStudioWidget : : create_edit_menubar ( GUI : : Menubar & menubar )
2020-09-19 17:25:05 +03:00
{
2021-04-10 11:19:25 +03:00
auto & edit_menu = menubar . add_menu ( " &Edit " ) ;
2021-05-21 19:51:06 +03:00
edit_menu . add_action ( GUI : : Action : : create ( " &Find in Files... " , { Mod_Ctrl | Mod_Shift , Key_F } , Gfx : : Bitmap : : load_from_file ( " /res/icons/16x16/find.png " ) , [ this ] ( auto & ) {
2020-09-19 17:25:05 +03:00
reveal_action_tab ( * m_find_in_files_widget ) ;
m_find_in_files_widget - > focus_textbox_and_select_all ( ) ;
} ) ) ;
2020-10-26 14:20:03 +03:00
edit_menu . add_separator ( ) ;
2021-04-10 11:19:25 +03:00
auto vim_emulation_setting_action = GUI : : Action : : create_checkable ( " &Vim Emulation " , { Mod_Ctrl | Mod_Shift | Mod_Alt , Key_V } , [ this ] ( auto & action ) {
2021-01-02 13:59:55 +03:00
if ( action . is_checked ( ) )
current_editor ( ) . set_editing_engine ( make < GUI : : VimEditingEngine > ( ) ) ;
else
current_editor ( ) . set_editing_engine ( make < GUI : : RegularEditingEngine > ( ) ) ;
} ) ;
vim_emulation_setting_action - > set_checked ( false ) ;
edit_menu . add_action ( vim_emulation_setting_action ) ;
2020-09-19 17:25:05 +03:00
}
2021-04-13 17:18:20 +03:00
void HackStudioWidget : : create_build_menubar ( GUI : : Menubar & menubar )
2020-09-19 17:25:05 +03:00
{
2021-04-10 11:19:25 +03:00
auto & build_menu = menubar . add_menu ( " &Build " ) ;
2020-09-19 17:25:05 +03:00
build_menu . add_action ( * m_build_action ) ;
build_menu . add_separator ( ) ;
build_menu . add_action ( * m_run_action ) ;
build_menu . add_action ( * m_stop_action ) ;
build_menu . add_separator ( ) ;
build_menu . add_action ( * m_debug_action ) ;
}
2021-04-13 17:18:20 +03:00
void HackStudioWidget : : create_view_menubar ( GUI : : Menubar & menubar )
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-04-10 11:19:25 +03:00
auto & view_menu = menubar . add_menu ( " &View " ) ;
2020-09-19 17:25:05 +03:00
view_menu . add_action ( hide_action_tabs_action ) ;
view_menu . add_action ( open_locator_action ) ;
2021-02-26 15:19:19 +03:00
view_menu . add_separator ( ) ;
m_wrapping_mode_actions . set_exclusive ( true ) ;
2021-04-10 11:19:25 +03:00
auto & wrapping_mode_menu = view_menu . add_submenu ( " &Wrapping Mode " ) ;
m_no_wrapping_action = GUI : : Action : : create_checkable ( " &No Wrapping " , [ & ] ( auto & ) {
2021-02-26 15:19:19 +03:00
for ( auto & wrapper : m_all_editor_wrappers )
wrapper . editor ( ) . set_wrapping_mode ( GUI : : TextEditor : : WrappingMode : : NoWrap ) ;
} ) ;
2021-04-10 11:19:25 +03:00
m_wrap_anywhere_action = GUI : : Action : : create_checkable ( " Wrap &Anywhere " , [ & ] ( auto & ) {
2021-02-26 15:19:19 +03:00
for ( auto & wrapper : m_all_editor_wrappers )
wrapper . editor ( ) . set_wrapping_mode ( GUI : : TextEditor : : WrappingMode : : WrapAnywhere ) ;
} ) ;
2021-04-10 11:19:25 +03:00
m_wrap_at_words_action = GUI : : Action : : create_checkable ( " Wrap at &Words " , [ & ] ( auto & ) {
2021-02-26 15:19:19 +03:00
for ( auto & wrapper : m_all_editor_wrappers )
wrapper . editor ( ) . set_wrapping_mode ( GUI : : TextEditor : : WrappingMode : : WrapAtWords ) ;
} ) ;
m_wrapping_mode_actions . add_action ( * m_no_wrapping_action ) ;
m_wrapping_mode_actions . add_action ( * m_wrap_anywhere_action ) ;
m_wrapping_mode_actions . add_action ( * m_wrap_at_words_action ) ;
wrapping_mode_menu . add_action ( * m_no_wrapping_action ) ;
wrapping_mode_menu . add_action ( * m_wrap_anywhere_action ) ;
wrapping_mode_menu . add_action ( * m_wrap_at_words_action ) ;
m_no_wrapping_action - > set_checked ( true ) ;
2020-09-19 17:25:05 +03:00
view_menu . add_separator ( ) ;
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-04-13 17:18:20 +03:00
void HackStudioWidget : : create_help_menubar ( GUI : : Menubar & menubar )
2020-09-19 17:25:05 +03:00
{
2021-05-12 20:09:42 +03:00
auto & help_menu = menubar . add_menu ( " &Help " ) ;
2021-01-05 01:51:49 +03:00
help_menu . add_action ( GUI : : CommonActions : : make_about_action ( " Hack Studio " , GUI : : Icon : : default_icon ( " app-hack-studio " ) , window ( ) ) ) ;
2020-09-19 17:25:05 +03:00
}
NonnullRefPtr < GUI : : Action > HackStudioWidget : : create_stop_action ( )
{
2021-04-10 11:19:25 +03:00
auto action = GUI : : Action : : create ( " &Stop " , Gfx : : Bitmap : : load_from_file ( " /res/icons/16x16/program-stop.png " ) , [ this ] ( auto & ) {
2021-04-14 00:01:30 +03:00
if ( ! Debugger : : the ( ) . session ( ) ) {
m_terminal_wrapper - > kill_running_command ( ) ;
return ;
}
Debugger : : the ( ) . stop ( ) ;
2020-09-19 17:25:05 +03:00
} ) ;
action - > set_enabled ( false ) ;
return action ;
}
2021-04-13 17:18:20 +03:00
void HackStudioWidget : : initialize_menubar ( GUI : : Menubar & menubar )
2020-09-19 17:25:05 +03:00
{
2021-05-12 19:50:48 +03:00
create_file_menubar ( menubar ) ;
2020-09-19 17:25:05 +03:00
create_project_menubar ( menubar ) ;
create_edit_menubar ( menubar ) ;
create_build_menubar ( menubar ) ;
create_view_menubar ( menubar ) ;
create_help_menubar ( menubar ) ;
}
2021-04-28 17:01:14 +03:00
void HackStudioWidget : : handle_external_file_deletion ( const String & filepath )
{
m_open_files . remove ( filepath ) ;
m_open_files_vector . remove_all_matching (
[ & filepath ] ( const String & element ) { return element = = filepath ; } ) ;
for ( auto & editor_wrapper : m_all_editor_wrappers ) {
Editor & editor = editor_wrapper . editor ( ) ;
String editor_file_path = editor . code_document ( ) . file_path ( ) ;
String relative_editor_file_path = LexicalPath : : relative_path ( editor_file_path , project ( ) . root_path ( ) ) ;
if ( relative_editor_file_path = = filepath ) {
if ( m_open_files_vector . is_empty ( ) ) {
editor . set_document ( CodeDocument : : create ( ) ) ;
2021-05-01 13:04:19 +03:00
editor_wrapper . set_filename ( " " ) ;
2021-04-28 17:01:14 +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 ) ;
2021-05-01 13:04:19 +03:00
editor_wrapper . set_filename ( first_path ) ;
2021-04-28 17:01:14 +03:00
}
}
}
m_open_files_view - > model ( ) - > update ( ) ;
}
2020-09-26 11:23:49 +03:00
HackStudioWidget : : ~ HackStudioWidget ( )
{
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-05-01 13:42:07 +03:00
HackStudioWidget : : ContinueDecision HackStudioWidget : : warn_unsaved_changes ( const String & prompt )
{
if ( ! any_document_is_dirty ( ) )
return ContinueDecision : : Yes ;
auto result = GUI : : MessageBox : : show ( window ( ) , prompt , " Unsaved changes " , GUI : : MessageBox : : Type : : Warning , GUI : : MessageBox : : InputType : : YesNoCancel ) ;
if ( result = = GUI : : MessageBox : : ExecCancel )
return ContinueDecision : : No ;
if ( result = = GUI : : MessageBox : : ExecYes ) {
for ( auto & editor_wrapper : m_all_editor_wrappers ) {
if ( editor_wrapper . document_dirty ( ) ) {
editor_wrapper . save ( ) ;
}
}
}
return ContinueDecision : : Yes ;
}
bool HackStudioWidget : : any_document_is_dirty ( ) const
{
for ( auto & editor_wrapper : m_all_editor_wrappers ) {
if ( editor_wrapper . document_dirty ( ) ) {
return true ;
}
}
return false ;
}
2020-09-19 17:25:05 +03:00
}