2019-10-25 21:09:16 +02:00
# include "CppLexer.h"
2019-10-23 21:13:08 +02:00
# include "FindInFilesWidget.h"
2019-10-21 18:46:55 +02:00
# include "Project.h"
2019-10-21 20:17:32 +02:00
# include "TerminalWrapper.h"
2019-10-21 18:46:55 +02:00
# include <LibCore/CFile.h>
2019-10-21 22:13:20 +02:00
# include <LibGUI/GAboutDialog.h>
2019-10-21 19:03:09 +02:00
# include <LibGUI/GAction.h>
2019-10-21 18:46:55 +02:00
# include <LibGUI/GApplication.h>
# include <LibGUI/GBoxLayout.h>
2019-10-23 21:13:08 +02:00
# include <LibGUI/GButton.h>
2019-10-26 21:43:08 +02:00
# include <LibGUI/GFilePicker.h>
2019-10-21 19:03:09 +02:00
# include <LibGUI/GInputBox.h>
2019-10-21 18:46:55 +02:00
# include <LibGUI/GListView.h>
2019-10-21 22:13:20 +02:00
# include <LibGUI/GMenu.h>
# include <LibGUI/GMenuBar.h>
2019-10-21 18:46:55 +02:00
# include <LibGUI/GMessageBox.h>
# include <LibGUI/GSplitter.h>
# include <LibGUI/GStatusBar.h>
2019-10-23 19:52:34 +02:00
# include <LibGUI/GTabWidget.h>
2019-10-23 20:54:41 +02:00
# include <LibGUI/GTextBox.h>
2019-10-21 18:46:55 +02:00
# include <LibGUI/GTextEditor.h>
# include <LibGUI/GToolBar.h>
# include <LibGUI/GWidget.h>
# include <LibGUI/GWindow.h>
# include <stdio.h>
# include <unistd.h>
2019-10-22 21:38:58 +02:00
String g_currently_open_file ;
2019-10-23 20:54:41 +02:00
OwnPtr < Project > g_project ;
RefPtr < GWindow > g_window ;
RefPtr < GListView > g_project_list_view ;
RefPtr < GTextEditor > g_text_editor ;
2019-10-22 21:38:58 +02:00
2019-10-25 10:25:42 +02:00
GTextEditor & main_editor ( )
{
ASSERT ( g_text_editor ) ;
return * g_text_editor ;
}
2019-10-22 22:15:43 +02:00
static void build ( TerminalWrapper & ) ;
2019-10-22 22:18:46 +02:00
static void run ( TerminalWrapper & ) ;
2019-10-23 21:13:08 +02:00
void open_file ( const String & ) ;
2019-10-22 22:15:43 +02:00
2019-10-21 18:46:55 +02:00
int main ( int argc , char * * argv )
{
GApplication app ( argc , argv ) ;
2019-10-23 20:54:41 +02:00
g_window = GWindow : : construct ( ) ;
g_window - > set_rect ( 100 , 100 , 800 , 600 ) ;
g_window - > set_title ( " HackStudio " ) ;
2019-10-21 18:46:55 +02:00
auto widget = GWidget : : construct ( ) ;
2019-10-23 20:54:41 +02:00
g_window - > set_main_widget ( widget ) ;
2019-10-21 18:46:55 +02:00
widget - > set_fill_with_background_color ( true ) ;
widget - > set_layout ( make < GBoxLayout > ( Orientation : : Vertical ) ) ;
widget - > layout ( ) - > set_spacing ( 0 ) ;
2019-10-22 21:38:58 +02:00
if ( chdir ( " /home/anon/little " ) < 0 ) {
2019-10-21 18:46:55 +02:00
perror ( " chdir " ) ;
return 1 ;
}
2019-10-23 20:54:41 +02:00
g_project = Project : : load_from_file ( " little.files " ) ;
ASSERT ( g_project ) ;
2019-10-21 18:46:55 +02:00
auto toolbar = GToolBar : : construct ( widget ) ;
2019-10-21 20:17:32 +02:00
auto outer_splitter = GSplitter : : construct ( Orientation : : Horizontal , widget ) ;
2019-10-23 20:54:41 +02:00
g_project_list_view = GListView : : construct ( outer_splitter ) ;
g_project_list_view - > set_model ( g_project - > model ( ) ) ;
g_project_list_view - > set_size_policy ( SizePolicy : : Fixed , SizePolicy : : Fill ) ;
g_project_list_view - > set_preferred_size ( 200 , 0 ) ;
2019-10-21 18:46:55 +02:00
2019-10-21 20:17:32 +02:00
auto inner_splitter = GSplitter : : construct ( Orientation : : Vertical , outer_splitter ) ;
2019-10-23 20:54:41 +02:00
g_text_editor = GTextEditor : : construct ( GTextEditor : : MultiLine , inner_splitter ) ;
g_text_editor - > set_ruler_visible ( true ) ;
2019-10-26 11:33:39 +02:00
g_text_editor - > set_line_wrapping_enabled ( true ) ;
g_text_editor - > set_automatic_indentation_enabled ( true ) ;
2019-10-23 20:54:41 +02:00
2019-10-26 21:43:08 +02:00
auto new_action = GAction : : create ( " Add new file to project... " , { Mod_Ctrl , Key_N } , GraphicsBitmap : : load_from_file ( " /res/icons/16x16/new.png " ) , [ & ] ( const GAction & ) {
2019-10-26 21:27:57 +02:00
auto input_box = GInputBox : : construct ( " Enter name of new file: " , " Add new file to project " , g_window ) ;
if ( input_box - > exec ( ) = = GInputBox : : ExecCancel )
return ;
auto filename = input_box - > text_value ( ) ;
auto file = CFile : : construct ( filename ) ;
if ( ! file - > open ( ( CIODevice : : OpenMode ) ( CIODevice : : WriteOnly | CIODevice : : MustBeNew ) ) ) {
GMessageBox : : show ( String : : format ( " Failed to create '%s' " , filename . characters ( ) ) , " Error " , GMessageBox : : Type : : Error , GMessageBox : : InputType : : OK , g_window ) ;
return ;
}
if ( ! g_project - > add_file ( filename ) ) {
GMessageBox : : show ( String : : format ( " Failed to add '%s' to project " , filename . characters ( ) ) , " Error " , GMessageBox : : Type : : Error , GMessageBox : : InputType : : OK , g_window ) ;
// FIXME: Should we unlink the file here maybe?
return ;
}
open_file ( filename ) ;
2019-10-24 22:25:26 +02:00
} ) ;
2019-10-26 21:43:08 +02:00
auto add_existing_file_action = GAction : : create ( " Add existing file to project... " , GraphicsBitmap : : load_from_file ( " /res/icons/16x16/open.png " ) , [ & ] ( auto & ) {
auto result = GFilePicker : : get_open_filepath ( " Add existing file to project " ) ;
if ( ! result . has_value ( ) )
return ;
auto & filename = result . value ( ) ;
if ( ! g_project - > add_file ( filename ) ) {
GMessageBox : : show ( String : : format ( " Failed to add '%s' to project " , filename . characters ( ) ) , " Error " , GMessageBox : : Type : : Error , GMessageBox : : InputType : : OK , g_window ) ;
return ;
}
open_file ( filename ) ;
2019-10-24 22:25:26 +02:00
} ) ;
auto save_action = GAction : : create ( " Save " , { Mod_Ctrl , Key_S } , GraphicsBitmap : : load_from_file ( " /res/icons/16x16/save.png " ) , [ & ] ( auto & ) {
if ( g_currently_open_file . is_empty ( ) )
return ;
g_text_editor - > write_to_file ( g_currently_open_file ) ;
} ) ;
toolbar - > add_action ( new_action ) ;
2019-10-26 21:43:08 +02:00
toolbar - > add_action ( add_existing_file_action ) ;
2019-10-24 22:25:26 +02:00
toolbar - > add_action ( save_action ) ;
toolbar - > add_separator ( ) ;
toolbar - > add_action ( g_text_editor - > cut_action ( ) ) ;
toolbar - > add_action ( g_text_editor - > copy_action ( ) ) ;
toolbar - > add_action ( g_text_editor - > paste_action ( ) ) ;
toolbar - > add_separator ( ) ;
toolbar - > add_action ( g_text_editor - > undo_action ( ) ) ;
toolbar - > add_action ( g_text_editor - > redo_action ( ) ) ;
toolbar - > add_separator ( ) ;
2019-10-23 20:54:41 +02:00
g_project_list_view - > on_activation = [ & ] ( auto & index ) {
auto filename = g_project_list_view - > model ( ) - > data ( index ) . to_string ( ) ;
open_file ( filename ) ;
2019-10-21 18:46:55 +02:00
} ;
2019-10-23 19:52:34 +02:00
auto tab_widget = GTabWidget : : construct ( inner_splitter ) ;
2019-10-26 12:30:49 +02:00
tab_widget - > set_size_policy ( SizePolicy : : Fill , SizePolicy : : Fixed ) ;
tab_widget - > set_preferred_size ( 0 , 24 ) ;
auto reveal_action_tab = [ & ] ( auto & widget ) {
dbg ( ) < < " tab_widget preferred height: " < < tab_widget - > preferred_size ( ) . height ( ) ;
if ( tab_widget - > preferred_size ( ) . height ( ) < 200 )
tab_widget - > set_preferred_size ( 0 , 200 ) ;
tab_widget - > set_active_widget ( widget ) ;
} ;
auto hide_action_tabs = [ & ] {
tab_widget - > set_preferred_size ( 0 , 24 ) ;
} ;
auto hide_action_tabs_action = GAction : : create ( " Hide action tabs " , { Mod_Ctrl | Mod_Shift , Key_X } , [ & ] ( auto & ) {
hide_action_tabs ( ) ;
} ) ;
2019-10-23 21:13:08 +02:00
auto find_in_files_widget = FindInFilesWidget : : construct ( nullptr ) ;
2019-10-23 21:02:02 +02:00
tab_widget - > add_widget ( " Find in files " , find_in_files_widget ) ;
2019-10-23 20:54:41 +02:00
2019-10-23 19:52:34 +02:00
auto terminal_wrapper = TerminalWrapper : : construct ( nullptr ) ;
tab_widget - > add_widget ( " Console " , terminal_wrapper ) ;
2019-10-21 20:17:32 +02:00
2019-10-21 18:46:55 +02:00
auto statusbar = GStatusBar : : construct ( widget ) ;
2019-10-23 20:54:41 +02:00
g_text_editor - > on_cursor_change = [ & ] {
2019-10-25 10:25:42 +02:00
statusbar - > set_text ( String : : format ( " Line: %d, Column: %d " , g_text_editor - > cursor ( ) . line ( ) + 1 , g_text_editor - > cursor ( ) . column ( ) ) ) ;
2019-10-21 18:50:24 +02:00
} ;
2019-10-23 20:54:41 +02:00
g_text_editor - > add_custom_context_menu_action ( GAction : : create (
2019-10-21 22:13:20 +02:00
" Go to line... " , { Mod_Ctrl , Key_L } , GraphicsBitmap : : load_from_file ( " /res/icons/16x16/go-forward.png " ) , [ & ] ( auto & ) {
2019-10-23 20:54:41 +02:00
auto input_box = GInputBox : : construct ( " Line: " , " Go to line " , g_window ) ;
2019-10-21 22:13:20 +02:00
auto result = input_box - > exec ( ) ;
if ( result = = GInputBox : : ExecOK ) {
bool ok ;
auto line_number = input_box - > text_value ( ) . to_uint ( ok ) ;
if ( ok ) {
2019-10-23 20:54:41 +02:00
g_text_editor - > set_cursor ( line_number - 1 , 0 ) ;
2019-10-21 22:13:20 +02:00
}
2019-10-21 19:03:09 +02:00
}
2019-10-21 22:13:20 +02:00
} ,
2019-10-23 20:54:41 +02:00
g_text_editor ) ) ;
2019-10-21 22:13:20 +02:00
auto menubar = make < GMenuBar > ( ) ;
auto app_menu = make < GMenu > ( " HackStudio " ) ;
2019-10-24 22:25:26 +02:00
app_menu - > add_action ( save_action ) ;
2019-10-21 22:13:20 +02:00
app_menu - > add_action ( GCommonActions : : make_quit_action ( [ & ] ( auto & ) {
app . quit ( ) ;
} ) ) ;
menubar - > add_menu ( move ( app_menu ) ) ;
2019-10-26 21:43:08 +02:00
auto project_menu = make < GMenu > ( " Project " ) ;
project_menu - > add_action ( new_action ) ;
project_menu - > add_action ( add_existing_file_action ) ;
menubar - > add_menu ( move ( project_menu ) ) ;
2019-10-23 21:02:02 +02:00
auto edit_menu = make < GMenu > ( " Edit " ) ;
edit_menu - > add_action ( GAction : : create ( " Find in files... " , { Mod_Ctrl | Mod_Shift , Key_F } , [ & ] ( auto & ) {
2019-10-26 12:30:49 +02:00
reveal_action_tab ( find_in_files_widget ) ;
2019-10-23 21:13:08 +02:00
find_in_files_widget - > focus_textbox_and_select_all ( ) ;
2019-10-23 21:02:02 +02:00
} ) ) ;
menubar - > add_menu ( move ( edit_menu ) ) ;
2019-10-24 22:25:26 +02:00
auto build_action = GAction : : create ( " Build " , { Mod_Ctrl , Key_B } , GraphicsBitmap : : load_from_file ( " /res/icons/16x16/build.png " ) , [ & ] ( auto & ) {
2019-10-26 12:30:49 +02:00
reveal_action_tab ( terminal_wrapper ) ;
2019-10-22 22:15:43 +02:00
build ( terminal_wrapper ) ;
2019-10-24 22:25:26 +02:00
} ) ;
toolbar - > add_action ( build_action ) ;
auto run_action = GAction : : create ( " Run " , { Mod_Ctrl , Key_R } , GraphicsBitmap : : load_from_file ( " /res/icons/16x16/run.png " ) , [ & ] ( auto & ) {
2019-10-26 12:30:49 +02:00
reveal_action_tab ( terminal_wrapper ) ;
2019-10-22 22:18:46 +02:00
run ( terminal_wrapper ) ;
2019-10-24 22:25:26 +02:00
} ) ;
toolbar - > add_action ( run_action ) ;
auto build_menu = make < GMenu > ( " Build " ) ;
build_menu - > add_action ( build_action ) ;
build_menu - > add_action ( run_action ) ;
2019-10-22 22:15:43 +02:00
menubar - > add_menu ( move ( build_menu ) ) ;
2019-10-26 12:30:49 +02:00
auto view_menu = make < GMenu > ( " View " ) ;
view_menu - > add_action ( hide_action_tabs_action ) ;
menubar - > add_menu ( move ( view_menu ) ) ;
2019-10-21 22:13:20 +02:00
auto small_icon = GraphicsBitmap : : load_from_file ( " /res/icons/16x16/app-hack-studio.png " ) ;
auto help_menu = make < GMenu > ( " Help " ) ;
help_menu - > add_action ( GAction : : create ( " About " , [ & ] ( auto & ) {
2019-10-23 20:54:41 +02:00
GAboutDialog : : show ( " HackStudio " , small_icon , g_window ) ;
2019-10-21 22:13:20 +02:00
} ) ) ;
menubar - > add_menu ( move ( help_menu ) ) ;
app . set_menubar ( move ( menubar ) ) ;
2019-10-23 20:54:41 +02:00
g_window - > set_icon ( small_icon ) ;
2019-10-21 19:03:09 +02:00
2019-10-23 20:54:41 +02:00
g_window - > show ( ) ;
2019-10-26 10:32:12 +02:00
open_file ( " main.cpp " ) ;
2019-10-21 18:46:55 +02:00
return app . exec ( ) ;
}
2019-10-22 22:15:43 +02:00
void build ( TerminalWrapper & wrapper )
{
wrapper . run_command ( " make " ) ;
}
2019-10-22 22:18:46 +02:00
void run ( TerminalWrapper & wrapper )
{
wrapper . run_command ( " make run " ) ;
}
2019-10-23 20:54:41 +02:00
2019-10-26 00:18:35 +02:00
struct TextStyle {
Color color ;
const Font * font { nullptr } ;
} ;
static TextStyle style_for_token_type ( CppToken : : Type type )
2019-10-25 21:09:16 +02:00
{
switch ( type ) {
case CppToken : : Type : : Keyword :
2019-10-26 00:18:35 +02:00
return { Color : : Black , & Font : : default_bold_fixed_width_font ( ) } ;
2019-10-26 10:32:12 +02:00
case CppToken : : Type : : KnownType :
return { Color : : from_rgb ( 0x929200 ) , & Font : : default_bold_fixed_width_font ( ) } ;
2019-10-25 21:09:16 +02:00
case CppToken : : Type : : Identifier :
2019-10-26 21:47:51 +02:00
return { Color : : from_rgb ( 0x000092 ) } ;
2019-10-25 21:09:16 +02:00
case CppToken : : Type : : DoubleQuotedString :
case CppToken : : Type : : SingleQuotedString :
case CppToken : : Type : : Number :
2019-10-26 10:32:12 +02:00
return { Color : : from_rgb ( 0x920000 ) } ;
2019-10-25 21:09:16 +02:00
case CppToken : : Type : : PreprocessorStatement :
2019-10-26 10:32:12 +02:00
return { Color : : from_rgb ( 0x009292 ) } ;
2019-10-25 21:09:16 +02:00
case CppToken : : Type : : Comment :
2019-10-26 10:32:12 +02:00
return { Color : : from_rgb ( 0x009200 ) } ;
2019-10-25 21:09:16 +02:00
default :
2019-10-26 00:18:35 +02:00
return { Color : : Black } ;
2019-10-25 21:09:16 +02:00
}
}
2019-10-26 10:32:12 +02:00
static void rehighlight ( )
{
auto text = g_text_editor - > text ( ) ;
CppLexer lexer ( text ) ;
auto tokens = lexer . lex ( ) ;
Vector < GTextEditor : : Span > spans ;
for ( auto & token : tokens ) {
# ifdef DEBUG_SYNTAX_HIGHLIGHTING
dbg ( ) < < token . to_string ( ) < < " @ " < < token . m_start . line < < " : " < < token . m_start . column < < " - " < < token . m_end . line < < " : " < < token . m_end . column ;
# endif
GTextEditor : : Span span ;
2019-10-26 15:32:12 +02:00
span . range . set_start ( { token . m_start . line , token . m_start . column } ) ;
span . range . set_end ( { token . m_end . line , token . m_end . column } ) ;
2019-10-26 10:32:12 +02:00
auto style = style_for_token_type ( token . m_type ) ;
span . color = style . color ;
span . font = style . font ;
spans . append ( span ) ;
}
g_text_editor - > set_spans ( spans ) ;
g_text_editor - > update ( ) ;
}
2019-10-23 20:54:41 +02:00
void open_file ( const String & filename )
{
auto file = CFile : : construct ( filename ) ;
if ( ! file - > open ( CFile : : ReadOnly ) ) {
GMessageBox : : show ( " Could not open! " , " Error " , GMessageBox : : Type : : Error , GMessageBox : : InputType : : OK , g_window ) ;
return ;
}
2019-10-25 21:09:16 +02:00
auto contents = file - > read_all ( ) ;
g_text_editor - > set_text ( contents ) ;
if ( filename . ends_with ( " .cpp " ) ) {
2019-10-26 10:32:12 +02:00
g_text_editor - > on_change = [ ] { rehighlight ( ) ; } ;
rehighlight ( ) ;
} else {
g_text_editor - > on_change = nullptr ;
2019-10-25 21:09:16 +02:00
}
2019-10-26 10:32:12 +02:00
g_currently_open_file = filename ;
g_window - > set_title ( String : : format ( " %s - HackStudio " , g_currently_open_file . characters ( ) ) ) ;
g_project_list_view - > update ( ) ;
2019-10-26 21:45:29 +02:00
g_text_editor - > set_focus ( true ) ;
2019-10-23 20:54:41 +02:00
}