2020-01-18 09:38:21 +01:00
/*
2021-04-09 16:34:51 +02:00
* Copyright ( c ) 2018 - 2021 , Andreas Kling < kling @ serenityos . org >
2020-01-18 09:38:21 +01:00
* All rights reserved .
*
* Redistribution and use in source and binary forms , with or without
* modification , are permitted provided that the following conditions are met :
*
* 1. Redistributions of source code must retain the above copyright notice , this
* list of conditions and the following disclaimer .
*
* 2. Redistributions in binary form must reproduce the above copyright notice ,
* this list of conditions and the following disclaimer in the documentation
* and / or other materials provided with the distribution .
*
* THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS " AS IS "
* AND ANY EXPRESS OR IMPLIED WARRANTIES , INCLUDING , BUT NOT LIMITED TO , THE
* IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
* DISCLAIMED . IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE
* FOR ANY DIRECT , INDIRECT , INCIDENTAL , SPECIAL , EXEMPLARY , OR CONSEQUENTIAL
* DAMAGES ( INCLUDING , BUT NOT LIMITED TO , PROCUREMENT OF SUBSTITUTE GOODS OR
* SERVICES ; LOSS OF USE , DATA , OR PROFITS ; OR BUSINESS INTERRUPTION ) HOWEVER
* CAUSED AND ON ANY THEORY OF LIABILITY , WHETHER IN CONTRACT , STRICT LIABILITY ,
* OR TORT ( INCLUDING NEGLIGENCE OR OTHERWISE ) ARISING IN ANY WAY OUT OF THE USE
* OF THIS SOFTWARE , EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE .
*/
2019-07-11 13:52:33 -05:00
# include "TextEditorWidget.h"
2020-09-14 12:51:12 +02:00
# include <AK/JsonObject.h>
# include <AK/JsonValue.h>
2019-07-13 19:58:04 -05:00
# include <AK/Optional.h>
2019-07-11 13:52:33 -05:00
# include <AK/StringBuilder.h>
2019-12-19 20:20:20 +01:00
# include <AK/URL.h>
2020-12-23 01:12:27 +01:00
# include <Applications/TextEditor/TextEditorWindowGML.h>
2021-02-12 12:34:04 -05:00
# include <LibCore/ConfigFile.h>
2020-02-06 15:04:03 +01:00
# include <LibCore/File.h>
2020-02-14 13:18:34 +01:00
# include <LibCore/MimeData.h>
2021-02-07 14:40:36 +01:00
# include <LibCpp/SyntaxHighlighter.h>
2020-08-11 15:13:07 +02:00
# include <LibDesktop/Launcher.h>
2020-02-06 20:33:02 +01:00
# include <LibGUI/Action.h>
2020-03-11 03:02:37 +02:00
# include <LibGUI/ActionGroup.h>
2020-02-06 20:33:02 +01:00
# include <LibGUI/BoxLayout.h>
# include <LibGUI/Button.h>
2021-02-21 19:01:26 -05:00
# include <LibGUI/CheckBox.h>
2020-02-06 20:33:02 +01:00
# include <LibGUI/FilePicker.h>
2020-12-30 18:04:55 +01:00
# include <LibGUI/FontPicker.h>
2020-12-21 13:59:21 +01:00
# include <LibGUI/GMLSyntaxHighlighter.h>
2021-02-21 19:01:26 -05:00
# include <LibGUI/GroupBox.h>
2020-05-01 01:57:06 +03:00
# include <LibGUI/INISyntaxHighlighter.h>
2020-02-15 01:56:30 +01:00
# include <LibGUI/Menu.h>
2021-04-13 16:18:20 +02:00
# include <LibGUI/Menubar.h>
2020-02-06 20:33:02 +01:00
# include <LibGUI/MessageBox.h>
2021-01-02 11:59:55 +01:00
# include <LibGUI/RegularEditingEngine.h>
2020-04-28 21:42:45 +02:00
# include <LibGUI/Splitter.h>
2021-04-13 16:18:20 +02:00
# include <LibGUI/Statusbar.h>
2020-02-06 20:33:02 +01:00
# include <LibGUI/TextBox.h>
# include <LibGUI/TextEditor.h>
2021-04-13 16:18:20 +02:00
# include <LibGUI/Toolbar.h>
# include <LibGUI/ToolbarContainer.h>
2021-01-02 11:59:55 +01:00
# include <LibGUI/VimEditingEngine.h>
2020-02-16 09:17:49 +01:00
# include <LibGfx/Font.h>
2021-04-17 18:44:45 +02:00
# include <LibGfx/Painter.h>
2021-02-07 16:56:02 +01:00
# include <LibJS/SyntaxHighlighter.h>
2020-04-28 21:42:45 +02:00
# include <LibMarkdown/Document.h>
2020-10-08 21:12:10 +01:00
# include <LibWeb/OutOfProcessWebView.h>
2021-02-07 17:07:33 +01:00
# include <Shell/SyntaxHighlighter.h>
2019-07-11 13:52:33 -05:00
TextEditorWidget : : TextEditorWidget ( )
{
2020-12-23 01:12:27 +01:00
load_from_gml ( text_editor_window_gml ) ;
2019-07-11 13:52:33 -05:00
2021-02-12 12:34:04 -05:00
m_config = Core : : ConfigFile : : get_for_app ( " TextEditor " ) ;
2021-04-13 16:18:20 +02:00
m_toolbar = * find_descendant_of_type_named < GUI : : Toolbar > ( " toolbar " ) ;
m_toolbar_container = * find_descendant_of_type_named < GUI : : ToolbarContainer > ( " toolbar_container " ) ;
2020-04-28 21:42:45 +02:00
2021-01-01 00:57:48 -07:00
m_editor = * find_descendant_of_type_named < GUI : : TextEditor > ( " editor " ) ;
2019-07-11 13:52:33 -05:00
m_editor - > set_ruler_visible ( true ) ;
m_editor - > set_automatic_indentation_enabled ( true ) ;
2021-01-02 11:59:55 +01:00
m_editor - > set_editing_engine ( make < GUI : : RegularEditingEngine > ( ) ) ;
2019-08-21 21:30:20 +02:00
2019-08-27 20:18:19 +02:00
m_editor - > on_change = [ this ] {
2020-07-04 21:19:01 +02:00
update_preview ( ) ;
2020-06-26 22:47:29 +02:00
2020-04-28 22:10:01 +01:00
// Do not mark as dirty on the first change (When document is first opened.)
2019-12-04 22:57:54 -08:00
if ( m_document_opening ) {
m_document_opening = false ;
return ;
}
2019-08-27 20:18:19 +02:00
bool was_dirty = m_document_dirty ;
m_document_dirty = true ;
if ( ! was_dirty )
update_title ( ) ;
} ;
2021-01-01 00:57:48 -07:00
m_page_view = * find_descendant_of_type_named < Web : : OutOfProcessWebView > ( " webview " ) ;
2020-08-29 13:44:25 +02:00
m_page_view - > on_link_hover = [ this ] ( auto & url ) {
2020-08-11 15:32:35 +02:00
if ( url . is_valid ( ) )
m_statusbar - > set_text ( url . to_string ( ) ) ;
else
2021-03-14 13:58:57 +01:00
update_statusbar ( ) ;
2020-08-11 15:32:35 +02:00
} ;
2020-08-11 15:13:07 +02:00
m_page_view - > on_link_click = [ & ] ( auto & url , auto & , unsigned ) {
if ( ! Desktop : : Launcher : : open ( url ) ) {
GUI : : MessageBox : : show (
window ( ) ,
2020-10-06 19:25:44 +02:00
String : : formatted ( " The link to '{}' could not be opened. " , url ) ,
2020-08-11 15:13:07 +02:00
" Failed to open link " ,
GUI : : MessageBox : : Type : : Error ) ;
}
} ;
2020-04-28 21:42:45 +02:00
2021-02-21 19:01:26 -05:00
m_find_replace_widget = * find_descendant_of_type_named < GUI : : GroupBox > ( " find_replace_widget " ) ;
2021-01-01 00:57:48 -07:00
m_find_widget = * find_descendant_of_type_named < GUI : : Widget > ( " find_widget " ) ;
m_replace_widget = * find_descendant_of_type_named < GUI : : Widget > ( " replace_widget " ) ;
2020-01-11 20:52:47 +02:00
2021-02-21 19:01:26 -05:00
m_find_textbox = * find_descendant_of_type_named < GUI : : TextBox > ( " find_textbox " ) ;
m_find_textbox - > set_placeholder ( " Find " ) ;
2019-08-21 21:30:20 +02:00
2021-02-21 19:01:26 -05:00
m_replace_textbox = * find_descendant_of_type_named < GUI : : TextBox > ( " replace_textbox " ) ;
m_replace_textbox - > set_placeholder ( " Replace " ) ;
2020-04-23 20:29:38 +02:00
2021-02-21 19:01:26 -05:00
m_match_case_checkbox = * find_descendant_of_type_named < GUI : : CheckBox > ( " match_case_checkbox " ) ;
m_match_case_checkbox - > on_checked = [ this ] {
m_match_case = m_match_case_checkbox - > is_checked ( ) ;
} ;
m_match_case_checkbox - > set_checked ( true ) ;
2020-04-23 20:29:38 +02:00
2021-02-21 19:01:26 -05:00
m_regex_checkbox = * find_descendant_of_type_named < GUI : : CheckBox > ( " regex_checkbox " ) ;
m_regex_checkbox - > on_checked = [ this ] {
m_use_regex = m_regex_checkbox - > is_checked ( ) ;
} ;
m_regex_checkbox - > set_checked ( false ) ;
2020-01-11 20:52:47 +02:00
2021-02-21 19:01:26 -05:00
m_wrap_around_checkbox = * find_descendant_of_type_named < GUI : : CheckBox > ( " wrap_around_checkbox " ) ;
m_wrap_around_checkbox - > on_checked = [ this ] {
m_should_wrap = m_wrap_around_checkbox - > is_checked ( ) ;
} ;
m_wrap_around_checkbox - > set_checked ( true ) ;
2020-04-23 20:29:38 +02:00
2021-04-09 16:34:51 +02:00
m_find_next_action = GUI : : Action : : create ( " Find &Next " , { Mod_Ctrl , Key_G } , Gfx : : Bitmap : : load_from_file ( " /res/icons/16x16/find-next.png " ) , [ & ] ( auto & ) {
2019-08-24 12:09:35 -06:00
auto needle = m_find_textbox - > text ( ) ;
2021-02-21 19:01:26 -05:00
if ( needle . is_empty ( ) )
2019-10-15 02:05:45 -04:00
return ;
2021-02-21 19:01:26 -05:00
if ( m_use_regex )
2020-04-23 20:29:38 +02:00
m_editor - > document ( ) . update_regex_matches ( needle ) ;
2021-02-21 19:01:26 -05:00
auto found_range = m_editor - > document ( ) . find_next ( needle , m_editor - > normalized_selection ( ) . end ( ) , m_should_wrap ? GUI : : TextDocument : : SearchShouldWrap : : Yes : GUI : : TextDocument : : SearchShouldWrap : : No , m_use_regex , m_match_case ) ;
dbgln ( " find_next('{}') returned {} " , needle , found_range ) ;
2019-08-21 21:30:20 +02:00
if ( found_range . is_valid ( ) ) {
m_editor - > set_selection ( found_range ) ;
} else {
2020-07-15 20:45:11 -06:00
GUI : : MessageBox : : show ( window ( ) ,
2020-10-06 19:25:44 +02:00
String : : formatted ( " Not found: \" {} \" " , needle ) ,
2019-08-21 21:30:20 +02:00
" Not found " ,
2020-07-15 20:45:11 -06:00
GUI : : MessageBox : : Type : : Information ) ;
2019-08-21 21:30:20 +02:00
}
2019-08-25 21:35:58 +02:00
} ) ;
2021-04-09 16:34:51 +02:00
m_find_previous_action = GUI : : Action : : create ( " Find &Previous " , { Mod_Ctrl | Mod_Shift , Key_G } , Gfx : : Bitmap : : load_from_file ( " /res/icons/16x16/find-previous.png " ) , [ & ] ( auto & ) {
2020-01-11 20:52:47 +02:00
auto needle = m_find_textbox - > text ( ) ;
if ( needle . is_empty ( ) )
return ;
2021-02-21 19:01:26 -05:00
if ( m_use_regex )
m_editor - > document ( ) . update_regex_matches ( needle ) ;
2020-01-11 20:52:47 +02:00
auto selection_start = m_editor - > normalized_selection ( ) . start ( ) ;
if ( ! selection_start . is_valid ( ) )
2021-02-21 19:01:26 -05:00
selection_start = m_editor - > normalized_selection ( ) . end ( ) ;
2020-01-11 20:52:47 +02:00
2021-02-21 19:01:26 -05:00
auto found_range = m_editor - > document ( ) . find_previous ( needle , selection_start , m_should_wrap ? GUI : : TextDocument : : SearchShouldWrap : : Yes : GUI : : TextDocument : : SearchShouldWrap : : No , m_use_regex , m_match_case ) ;
dbgln ( " find_prev( \" {} \" ) returned {} " , needle , found_range ) ;
2020-01-11 20:52:47 +02:00
if ( found_range . is_valid ( ) ) {
m_editor - > set_selection ( found_range ) ;
} else {
2020-07-15 20:45:11 -06:00
GUI : : MessageBox : : show ( window ( ) ,
2020-10-06 19:25:44 +02:00
String : : formatted ( " Not found: \" {} \" " , needle ) ,
2020-01-11 20:52:47 +02:00
" Not found " ,
2020-07-15 20:45:11 -06:00
GUI : : MessageBox : : Type : : Information ) ;
2020-01-11 20:52:47 +02:00
}
} ) ;
2021-04-09 16:34:51 +02:00
m_replace_action = GUI : : Action : : create ( " &Replace " , { Mod_Ctrl , Key_F1 } , [ & ] ( auto & ) {
2020-01-11 20:52:47 +02:00
auto needle = m_find_textbox - > text ( ) ;
auto substitute = m_replace_textbox - > text ( ) ;
if ( needle . is_empty ( ) )
return ;
2021-02-21 19:01:26 -05:00
if ( m_use_regex )
2020-04-23 20:29:38 +02:00
m_editor - > document ( ) . update_regex_matches ( needle ) ;
2021-02-21 19:01:26 -05:00
auto found_range = m_editor - > document ( ) . find_next ( needle , m_editor - > normalized_selection ( ) . start ( ) , m_should_wrap ? GUI : : TextDocument : : SearchShouldWrap : : Yes : GUI : : TextDocument : : SearchShouldWrap : : No , m_use_regex , m_match_case ) ;
2020-01-11 20:52:47 +02:00
if ( found_range . is_valid ( ) ) {
m_editor - > set_selection ( found_range ) ;
m_editor - > insert_at_cursor_or_replace_selection ( substitute ) ;
} else {
2020-07-15 20:45:11 -06:00
GUI : : MessageBox : : show ( window ( ) ,
2020-10-06 19:25:44 +02:00
String : : formatted ( " Not found: \" {} \" " , needle ) ,
2020-01-11 20:52:47 +02:00
" Not found " ,
2020-07-15 20:45:11 -06:00
GUI : : MessageBox : : Type : : Information ) ;
2020-01-11 20:52:47 +02:00
}
} ) ;
2021-04-09 16:34:51 +02:00
m_replace_all_action = GUI : : Action : : create ( " Replace &All " , { Mod_Ctrl , Key_F2 } , [ & ] ( auto & ) {
2020-01-11 20:52:47 +02:00
auto needle = m_find_textbox - > text ( ) ;
auto substitute = m_replace_textbox - > text ( ) ;
if ( needle . is_empty ( ) )
return ;
2021-02-21 19:01:26 -05:00
if ( m_use_regex )
2020-04-23 20:29:38 +02:00
m_editor - > document ( ) . update_regex_matches ( needle ) ;
2020-01-11 20:52:47 +02:00
2021-02-21 19:01:26 -05:00
auto found_range = m_editor - > document ( ) . find_next ( needle , { } , GUI : : TextDocument : : SearchShouldWrap : : Yes , m_use_regex , m_match_case ) ;
2020-02-14 13:18:34 +01:00
while ( found_range . is_valid ( ) ) {
m_editor - > set_selection ( found_range ) ;
m_editor - > insert_at_cursor_or_replace_selection ( substitute ) ;
2021-02-21 19:01:26 -05:00
found_range = m_editor - > document ( ) . find_next ( needle , { } , GUI : : TextDocument : : SearchShouldWrap : : Yes , m_use_regex , m_match_case ) ;
2020-01-11 20:52:47 +02:00
}
} ) ;
2021-01-01 00:57:48 -07:00
m_find_previous_button = * find_descendant_of_type_named < GUI : : Button > ( " find_previous_button " ) ;
2019-08-25 21:35:58 +02:00
m_find_previous_button - > set_action ( * m_find_previous_action ) ;
2021-02-21 19:01:26 -05:00
m_find_previous_button - > set_icon ( Gfx : : Bitmap : : load_from_file ( " /res/icons/16x16/find-previous.png " ) ) ;
2019-08-25 21:35:58 +02:00
2021-01-01 00:57:48 -07:00
m_find_next_button = * find_descendant_of_type_named < GUI : : Button > ( " find_next_button " ) ;
2019-08-25 21:35:58 +02:00
m_find_next_button - > set_action ( * m_find_next_action ) ;
2021-02-21 19:01:26 -05:00
m_find_next_button - > set_icon ( Gfx : : Bitmap : : load_from_file ( " /res/icons/16x16/find-next.png " ) ) ;
2019-08-21 21:30:20 +02:00
2019-08-22 11:09:25 +02:00
m_find_textbox - > on_return_pressed = [ this ] {
2019-08-24 12:09:35 -06:00
m_find_next_button - > click ( ) ;
2019-08-22 11:09:25 +02:00
} ;
m_find_textbox - > on_escape_pressed = [ this ] {
2020-01-11 20:52:47 +02:00
m_find_replace_widget - > set_visible ( false ) ;
m_editor - > set_focus ( true ) ;
} ;
2021-02-21 19:01:26 -05:00
m_replace_button = * find_descendant_of_type_named < GUI : : Button > ( " replace_button " ) ;
m_replace_button - > set_action ( * m_replace_action ) ;
2020-01-11 20:52:47 +02:00
2021-01-01 00:57:48 -07:00
m_replace_all_button = * find_descendant_of_type_named < GUI : : Button > ( " replace_all_button " ) ;
2020-01-11 20:52:47 +02:00
m_replace_all_button - > set_action ( * m_replace_all_action ) ;
m_replace_textbox - > on_return_pressed = [ this ] {
2021-02-21 19:01:26 -05:00
m_replace_button - > click ( ) ;
2020-01-11 20:52:47 +02:00
} ;
m_replace_textbox - > on_escape_pressed = [ this ] {
m_find_replace_widget - > set_visible ( false ) ;
2019-08-22 11:09:25 +02:00
m_editor - > set_focus ( true ) ;
} ;
2021-04-09 16:34:51 +02:00
m_vim_emulation_setting_action = GUI : : Action : : create_checkable ( " &Vim Emulation " , { Mod_Ctrl | Mod_Shift | Mod_Alt , Key_V } , [ & ] ( auto & action ) {
2021-01-02 11:59:55 +01:00
if ( action . is_checked ( ) )
m_editor - > set_editing_engine ( make < GUI : : VimEditingEngine > ( ) ) ;
else
m_editor - > set_editing_engine ( make < GUI : : RegularEditingEngine > ( ) ) ;
} ) ;
m_vim_emulation_setting_action - > set_checked ( false ) ;
2021-04-09 16:34:51 +02:00
m_find_replace_action = GUI : : Action : : create ( " &Find/Replace... " , { Mod_Ctrl , Key_F } , Gfx : : Bitmap : : load_from_file ( " /res/icons/16x16/find.png " ) , [ this ] ( auto & ) {
2020-01-11 20:52:47 +02:00
m_find_replace_widget - > set_visible ( true ) ;
2019-08-22 11:09:25 +02:00
m_find_widget - > set_visible ( true ) ;
2020-01-11 20:52:47 +02:00
m_replace_widget - > set_visible ( true ) ;
2019-08-22 11:02:03 +02:00
m_find_textbox - > set_focus ( true ) ;
2020-01-11 21:08:35 +02:00
if ( m_editor - > has_selection ( ) ) {
auto selected_text = m_editor - > document ( ) . text_in_range ( m_editor - > normalized_selection ( ) ) ;
m_find_textbox - > set_text ( selected_text ) ;
}
2019-08-25 21:44:59 +02:00
m_find_textbox - > select_all ( ) ;
2019-08-22 11:02:03 +02:00
} ) ;
2020-01-11 20:52:47 +02:00
m_editor - > add_custom_context_menu_action ( * m_find_replace_action ) ;
2019-08-25 21:38:13 +02:00
m_editor - > add_custom_context_menu_action ( * m_find_next_action ) ;
m_editor - > add_custom_context_menu_action ( * m_find_previous_action ) ;
2021-04-13 16:18:20 +02:00
m_statusbar = * find_descendant_of_type_named < GUI : : Statusbar > ( " statusbar " ) ;
2019-07-11 13:52:33 -05:00
2021-04-17 18:44:45 +02:00
GUI : : Application : : the ( ) - > on_action_enter = [ this ] ( GUI : : Action & action ) {
auto text = action . long_text ( ) ;
if ( text . is_empty ( ) )
text = Gfx : : parse_ampersand_string ( action . text ( ) ) ;
m_statusbar - > set_override_text ( move ( text ) ) ;
} ;
GUI : : Application : : the ( ) - > on_action_leave = [ this ] ( GUI : : Action & ) {
m_statusbar - > set_override_text ( { } ) ;
} ;
2021-03-14 13:58:57 +01:00
m_editor - > on_cursor_change = [ this ] { update_statusbar ( ) ; } ;
m_editor - > on_selection_change = [ this ] { update_statusbar ( ) ; } ;
2019-07-11 13:52:33 -05:00
2021-04-09 16:34:51 +02:00
m_new_action = GUI : : Action : : create ( " &New " , { Mod_Ctrl , Key_N } , Gfx : : Bitmap : : load_from_file ( " /res/icons/16x16/new.png " ) , [ this ] ( const GUI : : Action & ) {
2019-09-05 23:05:28 -06:00
if ( m_document_dirty ) {
2020-12-26 00:47:49 +01:00
auto save_document_first_result = GUI : : MessageBox : : show ( window ( ) , " Save changes to current document first? " , " Warning " , GUI : : MessageBox : : Type : : Warning , GUI : : MessageBox : : InputType : : YesNoCancel ) ;
2020-02-17 00:06:11 -05:00
if ( save_document_first_result = = GUI : : Dialog : : ExecResult : : ExecYes )
m_save_action - > activate ( ) ;
if ( save_document_first_result = = GUI : : Dialog : : ExecResult : : ExecCancel )
2019-09-14 22:10:44 +02:00
return ;
2019-09-05 23:05:28 -06:00
}
2019-09-14 22:10:44 +02:00
2019-09-05 23:05:28 -06:00
m_document_dirty = false ;
m_editor - > set_text ( StringView ( ) ) ;
2020-05-26 14:52:44 +03:00
set_path ( LexicalPath ( ) ) ;
2019-09-05 23:05:28 -06:00
update_title ( ) ;
2019-07-11 13:52:33 -05:00
} ) ;
2020-02-02 15:07:41 +01:00
m_open_action = GUI : : CommonActions : : make_open_action ( [ this ] ( auto & ) {
2020-07-15 19:52:02 -06:00
Optional < String > open_path = GUI : : FilePicker : : get_open_filepath ( window ( ) ) ;
2019-07-11 13:52:33 -05:00
2019-07-28 23:45:50 -05:00
if ( ! open_path . has_value ( ) )
2019-07-13 19:58:04 -05:00
return ;
2019-12-21 23:20:43 +01:00
if ( m_document_dirty ) {
2020-12-26 00:47:49 +01:00
auto save_document_first_result = GUI : : MessageBox : : show ( window ( ) , " Save changes to current document first? " , " Warning " , GUI : : MessageBox : : Type : : Warning , GUI : : MessageBox : : InputType : : YesNoCancel ) ;
2020-02-17 00:06:11 -05:00
if ( save_document_first_result = = GUI : : Dialog : : ExecResult : : ExecYes )
2019-12-21 23:20:43 +01:00
m_save_action - > activate ( ) ;
2020-02-17 00:06:11 -05:00
if ( save_document_first_result = = GUI : : Dialog : : ExecResult : : ExecCancel )
return ;
2019-12-21 23:20:43 +01:00
}
2021-02-27 11:05:39 +01:00
open_file ( open_path . value ( ) ) ;
2019-07-11 13:52:33 -05:00
} ) ;
2020-11-01 21:32:27 +00:00
m_save_as_action = GUI : : CommonActions : : make_save_as_action ( [ & ] ( auto & ) {
2020-07-15 19:52:02 -06:00
Optional < String > save_path = GUI : : FilePicker : : get_save_filepath ( window ( ) , m_name . is_null ( ) ? " Untitled " : m_name , m_extension . is_null ( ) ? " txt " : m_extension ) ;
2019-07-28 23:45:50 -05:00
if ( ! save_path . has_value ( ) )
2019-07-13 19:58:04 -05:00
return ;
2019-07-28 23:45:50 -05:00
if ( ! m_editor - > write_to_file ( save_path . value ( ) ) ) {
2020-07-15 20:45:11 -06:00
GUI : : MessageBox : : show ( window ( ) , " Unable to save file. \n " , " Error " , GUI : : MessageBox : : Type : : Error ) ;
2019-07-13 19:58:04 -05:00
return ;
}
2019-08-27 20:18:19 +02:00
m_document_dirty = false ;
2020-05-26 14:52:44 +03:00
set_path ( LexicalPath ( save_path . value ( ) ) ) ;
2020-10-06 19:25:44 +02:00
dbgln ( " Wrote document to {} " , save_path . value ( ) ) ;
2019-07-24 06:32:30 +02:00
} ) ;
2020-11-01 21:32:27 +00:00
m_save_action = GUI : : CommonActions : : make_save_action ( [ & ] ( auto & ) {
2019-07-24 06:32:30 +02:00
if ( ! m_path . is_empty ( ) ) {
2019-08-27 20:18:19 +02:00
if ( ! m_editor - > write_to_file ( m_path ) ) {
2020-07-15 20:45:11 -06:00
GUI : : MessageBox : : show ( window ( ) , " Unable to save file. \n " , " Error " , GUI : : MessageBox : : Type : : Error ) ;
2019-08-27 20:18:19 +02:00
} else {
m_document_dirty = false ;
update_title ( ) ;
}
2019-07-24 06:32:30 +02:00
return ;
}
2019-07-25 23:48:39 -05:00
m_save_as_action - > activate ( ) ;
2019-07-11 13:52:33 -05:00
} ) ;
2021-02-26 07:16:40 -05:00
m_toolbar - > add_action ( * m_new_action ) ;
m_toolbar - > add_action ( * m_open_action ) ;
m_toolbar - > add_action ( * m_save_action ) ;
m_toolbar - > add_separator ( ) ;
m_toolbar - > add_action ( m_editor - > cut_action ( ) ) ;
m_toolbar - > add_action ( m_editor - > copy_action ( ) ) ;
m_toolbar - > add_action ( m_editor - > paste_action ( ) ) ;
m_toolbar - > add_action ( m_editor - > delete_action ( ) ) ;
m_toolbar - > add_separator ( ) ;
m_toolbar - > add_action ( m_editor - > undo_action ( ) ) ;
m_toolbar - > add_action ( m_editor - > redo_action ( ) ) ;
}
TextEditorWidget : : ~ TextEditorWidget ( )
{
}
2021-04-13 16:18:20 +02:00
void TextEditorWidget : : initialize_menubar ( GUI : : Menubar & menubar )
2021-02-26 07:16:40 -05:00
{
2021-04-09 16:34:51 +02:00
auto & app_menu = menubar . add_menu ( " &File " ) ;
2020-04-04 12:18:40 +02:00
app_menu . add_action ( * m_new_action ) ;
app_menu . add_action ( * m_open_action ) ;
app_menu . add_action ( * m_save_action ) ;
app_menu . add_action ( * m_save_as_action ) ;
app_menu . add_separator ( ) ;
app_menu . add_action ( GUI : : CommonActions : : make_quit_action ( [ this ] ( auto & ) {
2019-08-27 20:37:41 +02:00
if ( ! request_close ( ) )
return ;
2020-07-04 16:52:01 +02:00
GUI : : Application : : the ( ) - > quit ( ) ;
2019-07-11 13:52:33 -05:00
} ) ) ;
2020-04-04 12:18:40 +02:00
2021-04-09 16:34:51 +02:00
auto & edit_menu = menubar . add_menu ( " &Edit " ) ;
2020-04-04 12:18:40 +02:00
edit_menu . add_action ( m_editor - > undo_action ( ) ) ;
edit_menu . add_action ( m_editor - > redo_action ( ) ) ;
edit_menu . add_separator ( ) ;
edit_menu . add_action ( m_editor - > cut_action ( ) ) ;
edit_menu . add_action ( m_editor - > copy_action ( ) ) ;
edit_menu . add_action ( m_editor - > paste_action ( ) ) ;
edit_menu . add_action ( m_editor - > delete_action ( ) ) ;
edit_menu . add_separator ( ) ;
2021-01-02 11:59:55 +01:00
edit_menu . add_action ( * m_vim_emulation_setting_action ) ;
edit_menu . add_separator ( ) ;
2020-04-04 12:18:40 +02:00
edit_menu . add_action ( * m_find_replace_action ) ;
edit_menu . add_action ( * m_find_next_action ) ;
edit_menu . add_action ( * m_find_previous_action ) ;
2021-02-21 19:01:26 -05:00
edit_menu . add_action ( * m_replace_action ) ;
2020-04-04 12:18:40 +02:00
edit_menu . add_action ( * m_replace_all_action ) ;
2020-07-04 21:19:01 +02:00
m_no_preview_action = GUI : : Action : : create_checkable (
2021-04-09 16:34:51 +02:00
" &No Preview " , [ this ] ( auto & ) {
2020-07-04 21:19:01 +02:00
set_preview_mode ( PreviewMode : : None ) ;
} ) ;
2020-04-29 11:48:11 +02:00
m_markdown_preview_action = GUI : : Action : : create_checkable (
2021-04-09 16:34:51 +02:00
" &Markdown Preview " , [ this ] ( auto & ) {
2020-07-04 21:19:01 +02:00
set_preview_mode ( PreviewMode : : Markdown ) ;
2020-04-29 11:48:11 +02:00
} ,
this ) ;
2020-06-26 22:47:29 +02:00
m_html_preview_action = GUI : : Action : : create_checkable (
2021-04-09 16:34:51 +02:00
" &HTML Preview " , [ this ] ( auto & ) {
2020-07-04 21:19:01 +02:00
set_preview_mode ( PreviewMode : : HTML ) ;
2020-06-26 22:47:29 +02:00
} ,
this ) ;
2020-07-04 21:19:01 +02:00
m_preview_actions . add_action ( * m_no_preview_action ) ;
2020-06-26 22:47:29 +02:00
m_preview_actions . add_action ( * m_markdown_preview_action ) ;
m_preview_actions . add_action ( * m_html_preview_action ) ;
m_preview_actions . set_exclusive ( true ) ;
2021-04-09 16:34:51 +02:00
m_layout_toolbar_action = GUI : : Action : : create_checkable ( " &Toolbar " , [ & ] ( auto & action ) {
2021-02-26 07:16:40 -05:00
action . is_checked ( ) ? m_toolbar_container - > set_visible ( true ) : m_toolbar_container - > set_visible ( false ) ;
2021-02-12 12:34:04 -05:00
m_config - > write_bool_entry ( " Layout " , " ShowToolbar " , action . is_checked ( ) ) ;
m_config - > sync ( ) ;
} ) ;
auto show_toolbar = m_config - > read_bool_entry ( " Layout " , " ShowToolbar " , true ) ;
m_layout_toolbar_action - > set_checked ( show_toolbar ) ;
2021-02-26 07:16:40 -05:00
m_toolbar_container - > set_visible ( show_toolbar ) ;
2021-02-12 12:34:04 -05:00
2021-04-09 16:34:51 +02:00
m_layout_statusbar_action = GUI : : Action : : create_checkable ( " &Status Bar " , [ & ] ( auto & action ) {
2021-02-12 12:34:04 -05:00
action . is_checked ( ) ? m_statusbar - > set_visible ( true ) : m_statusbar - > set_visible ( false ) ;
2021-04-13 16:18:20 +02:00
m_config - > write_bool_entry ( " Layout " , " ShowStatusbar " , action . is_checked ( ) ) ;
2021-02-12 12:34:04 -05:00
m_config - > sync ( ) ;
} ) ;
2021-04-13 16:18:20 +02:00
auto show_statusbar = m_config - > read_bool_entry ( " Layout " , " ShowStatusbar " , true ) ;
2021-02-12 12:34:04 -05:00
m_layout_statusbar_action - > set_checked ( show_statusbar ) ;
m_statusbar - > set_visible ( show_statusbar ) ;
m_layout_ruler_action = GUI : : Action : : create_checkable ( " Ruler " , [ & ] ( auto & action ) {
action . is_checked ( ) ? m_editor - > set_ruler_visible ( true ) : m_editor - > set_ruler_visible ( false ) ;
m_config - > write_bool_entry ( " Layout " , " ShowRuler " , action . is_checked ( ) ) ;
m_config - > sync ( ) ;
} ) ;
auto show_ruler = m_config - > read_bool_entry ( " Layout " , " ShowRuler " , true ) ;
m_layout_ruler_action - > set_checked ( show_ruler ) ;
m_editor - > set_ruler_visible ( show_ruler ) ;
2021-04-09 16:34:51 +02:00
auto & view_menu = menubar . add_menu ( " &View " ) ;
auto & layout_menu = view_menu . add_submenu ( " &Layout " ) ;
2021-02-12 12:34:04 -05:00
layout_menu . add_action ( * m_layout_toolbar_action ) ;
layout_menu . add_action ( * m_layout_statusbar_action ) ;
layout_menu . add_action ( * m_layout_ruler_action ) ;
view_menu . add_separator ( ) ;
2021-04-09 16:34:51 +02:00
view_menu . add_action ( GUI : : Action : : create ( " Editor &Font... " , Gfx : : Bitmap : : load_from_file ( " /res/icons/16x16/app-font-editor.png " ) ,
2020-12-30 18:04:55 +01:00
[ & ] ( auto & ) {
2021-01-03 17:45:08 +01:00
auto picker = GUI : : FontPicker : : construct ( window ( ) , & m_editor - > font ( ) , false ) ;
2020-12-30 18:04:55 +01:00
if ( picker - > exec ( ) = = GUI : : Dialog : : ExecOK ) {
dbgln ( " setting font {} " , picker - > font ( ) - > qualified_name ( ) ) ;
m_editor - > set_font ( picker - > font ( ) ) ;
}
} ) ) ;
view_menu . add_separator ( ) ;
2021-01-09 22:47:48 +10:00
m_wrapping_mode_actions . set_exclusive ( true ) ;
2021-04-09 16:34:51 +02:00
auto & wrapping_mode_menu = view_menu . add_submenu ( " &Wrapping Mode " ) ;
m_no_wrapping_action = GUI : : Action : : create_checkable ( " &No Wrapping " , [ & ] ( auto & ) {
2021-01-09 22:47:48 +10:00
m_editor - > set_wrapping_mode ( GUI : : TextEditor : : WrappingMode : : NoWrap ) ;
} ) ;
2021-04-09 16:34:51 +02:00
m_wrap_anywhere_action = GUI : : Action : : create_checkable ( " Wrap &Anywhere " , [ & ] ( auto & ) {
2021-01-09 22:47:48 +10:00
m_editor - > set_wrapping_mode ( GUI : : TextEditor : : WrappingMode : : WrapAnywhere ) ;
} ) ;
2021-04-09 16:34:51 +02:00
m_wrap_at_words_action = GUI : : Action : : create_checkable ( " Wrap at &Words " , [ & ] ( auto & ) {
2021-01-09 22:47:48 +10:00
m_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 ) ;
2021-02-12 12:35:20 -05:00
m_no_wrapping_action - > set_checked ( true ) ;
2021-01-09 22:47:48 +10:00
2021-03-15 18:28:24 -03:00
view_menu . add_separator ( ) ;
m_soft_tab_width_actions . set_exclusive ( true ) ;
2021-04-09 16:34:51 +02:00
auto & soft_tab_width_menu = view_menu . add_submenu ( " &Tab Width " ) ;
2021-03-15 18:28:24 -03:00
m_soft_tab_1_width_action = GUI : : Action : : create_checkable ( " 1 " , [ & ] ( auto & ) {
m_editor - > set_soft_tab_width ( 1 ) ;
} ) ;
m_soft_tab_2_width_action = GUI : : Action : : create_checkable ( " 2 " , [ & ] ( auto & ) {
m_editor - > set_soft_tab_width ( 2 ) ;
} ) ;
m_soft_tab_4_width_action = GUI : : Action : : create_checkable ( " 4 " , [ & ] ( auto & ) {
m_editor - > set_soft_tab_width ( 4 ) ;
} ) ;
m_soft_tab_8_width_action = GUI : : Action : : create_checkable ( " 8 " , [ & ] ( auto & ) {
m_editor - > set_soft_tab_width ( 8 ) ;
} ) ;
m_soft_tab_16_width_action = GUI : : Action : : create_checkable ( " 16 " , [ & ] ( auto & ) {
m_editor - > set_soft_tab_width ( 16 ) ;
} ) ;
2021-03-17 13:52:54 -03:00
m_soft_tab_width_actions . add_action ( * m_soft_tab_1_width_action ) ;
m_soft_tab_width_actions . add_action ( * m_soft_tab_2_width_action ) ;
m_soft_tab_width_actions . add_action ( * m_soft_tab_4_width_action ) ;
m_soft_tab_width_actions . add_action ( * m_soft_tab_8_width_action ) ;
m_soft_tab_width_actions . add_action ( * m_soft_tab_16_width_action ) ;
2021-03-15 18:28:24 -03:00
soft_tab_width_menu . add_action ( * m_soft_tab_1_width_action ) ;
soft_tab_width_menu . add_action ( * m_soft_tab_2_width_action ) ;
soft_tab_width_menu . add_action ( * m_soft_tab_4_width_action ) ;
soft_tab_width_menu . add_action ( * m_soft_tab_8_width_action ) ;
soft_tab_width_menu . add_action ( * m_soft_tab_16_width_action ) ;
m_soft_tab_4_width_action - > set_checked ( true ) ;
2021-03-17 13:52:54 -03:00
view_menu . add_separator ( ) ;
2021-04-09 16:34:51 +02:00
m_visualize_trailing_whitespace_action = GUI : : Action : : create_checkable ( " Visualize &Trailing Whitespace " , [ & ] ( auto & ) {
2021-03-17 13:52:54 -03:00
m_editor - > set_visualize_trailing_whitespace ( m_visualize_trailing_whitespace_action - > is_checked ( ) ) ;
} ) ;
2021-04-09 16:34:51 +02:00
m_visualize_leading_whitespace_action = GUI : : Action : : create_checkable ( " Visualize &Leading Whitespace " , [ & ] ( auto & ) {
2021-03-17 13:52:54 -03:00
m_editor - > set_visualize_leading_whitespace ( m_visualize_leading_whitespace_action - > is_checked ( ) ) ;
} ) ;
m_visualize_trailing_whitespace_action - > set_checked ( true ) ;
view_menu . add_action ( * m_visualize_trailing_whitespace_action ) ;
view_menu . add_action ( * m_visualize_leading_whitespace_action ) ;
2020-04-29 11:48:11 +02:00
view_menu . add_separator ( ) ;
2020-07-04 21:19:01 +02:00
view_menu . add_action ( * m_no_preview_action ) ;
2020-04-29 11:48:11 +02:00
view_menu . add_action ( * m_markdown_preview_action ) ;
2020-06-26 22:47:29 +02:00
view_menu . add_action ( * m_html_preview_action ) ;
2021-02-12 12:35:20 -05:00
m_no_preview_action - > set_checked ( true ) ;
2020-04-29 11:48:11 +02:00
view_menu . add_separator ( ) ;
2020-03-11 03:02:37 +02:00
syntax_actions . set_exclusive ( true ) ;
2021-04-09 16:34:51 +02:00
auto & syntax_menu = view_menu . add_submenu ( " &Syntax " ) ;
m_plain_text_highlight = GUI : : Action : : create_checkable ( " &Plain Text " , [ & ] ( auto & ) {
2021-01-10 16:29:28 -07:00
m_editor - > set_syntax_highlighter ( { } ) ;
2020-03-11 03:02:37 +02:00
m_editor - > update ( ) ;
} ) ;
m_plain_text_highlight - > set_checked ( true ) ;
syntax_actions . add_action ( * m_plain_text_highlight ) ;
2020-04-04 12:18:40 +02:00
syntax_menu . add_action ( * m_plain_text_highlight ) ;
2020-03-11 03:02:37 +02:00
2021-04-09 16:34:51 +02:00
m_cpp_highlight = GUI : : Action : : create_checkable ( " &C++ " , [ & ] ( auto & ) {
2021-02-07 14:40:36 +01:00
m_editor - > set_syntax_highlighter ( make < Cpp : : SyntaxHighlighter > ( ) ) ;
2020-03-11 03:02:37 +02:00
m_editor - > update ( ) ;
} ) ;
syntax_actions . add_action ( * m_cpp_highlight ) ;
2020-04-04 12:18:40 +02:00
syntax_menu . add_action ( * m_cpp_highlight ) ;
2020-03-11 03:02:37 +02:00
2021-04-09 16:34:51 +02:00
m_js_highlight = GUI : : Action : : create_checkable ( " &JavaScript " , [ & ] ( auto & ) {
2021-02-07 16:56:02 +01:00
m_editor - > set_syntax_highlighter ( make < JS : : SyntaxHighlighter > ( ) ) ;
2020-03-13 00:53:22 +02:00
m_editor - > update ( ) ;
} ) ;
syntax_actions . add_action ( * m_js_highlight ) ;
2020-04-04 12:18:40 +02:00
syntax_menu . add_action ( * m_js_highlight ) ;
2020-03-13 00:53:22 +02:00
2021-04-09 16:34:51 +02:00
m_gml_highlight = GUI : : Action : : create_checkable ( " &GML " , [ & ] ( auto & ) {
2020-12-21 13:59:21 +01:00
m_editor - > set_syntax_highlighter ( make < GUI : : GMLSyntaxHighlighter > ( ) ) ;
m_editor - > update ( ) ;
} ) ;
syntax_actions . add_action ( * m_gml_highlight ) ;
syntax_menu . add_action ( * m_gml_highlight ) ;
2021-04-09 16:34:51 +02:00
m_ini_highlight = GUI : : Action : : create_checkable ( " &INI File " , [ & ] ( auto & ) {
2020-05-01 01:57:06 +03:00
m_editor - > set_syntax_highlighter ( make < GUI : : IniSyntaxHighlighter > ( ) ) ;
m_editor - > update ( ) ;
} ) ;
syntax_actions . add_action ( * m_ini_highlight ) ;
syntax_menu . add_action ( * m_ini_highlight ) ;
2021-04-09 16:34:51 +02:00
m_shell_highlight = GUI : : Action : : create_checkable ( " &Shell File " , [ & ] ( auto & ) {
2021-02-07 17:07:33 +01:00
m_editor - > set_syntax_highlighter ( make < Shell : : SyntaxHighlighter > ( ) ) ;
2020-09-28 14:28:44 +03:30
m_editor - > update ( ) ;
} ) ;
syntax_actions . add_action ( * m_shell_highlight ) ;
syntax_menu . add_action ( * m_shell_highlight ) ;
2021-04-09 16:34:51 +02:00
auto & help_menu = menubar . add_menu ( " &Help " ) ;
2021-01-16 15:06:20 +00:00
help_menu . add_action ( GUI : : CommonActions : : make_help_action ( [ ] ( auto & ) {
Desktop : : Launcher : : open ( URL : : create_with_file_protocol ( " /usr/share/man/man1/TextEditor.md " ) , " /bin/Help " ) ;
} ) ) ;
2021-01-04 23:51:49 +01:00
help_menu . add_action ( GUI : : CommonActions : : make_about_action ( " Text Editor " , GUI : : Icon : : default_icon ( " app-text-editor " ) , window ( ) ) ) ;
2019-07-11 13:52:33 -05:00
}
2020-05-26 14:52:44 +03:00
void TextEditorWidget : : set_path ( const LexicalPath & lexical_path )
2019-07-24 06:32:30 +02:00
{
2020-05-26 14:52:44 +03:00
m_path = lexical_path . string ( ) ;
m_name = lexical_path . title ( ) ;
m_extension = lexical_path . extension ( ) ;
2020-02-07 20:12:25 +01:00
2020-08-11 15:13:07 +02:00
if ( m_extension = = " c " | | m_extension = = " cc " | | m_extension = = " cxx " | | m_extension = = " cpp " | | m_extension = = " h " ) {
2020-03-11 03:02:37 +02:00
m_cpp_highlight - > activate ( ) ;
2020-05-09 16:48:28 +02:00
} else if ( m_extension = = " js " | | m_extension = = " json " ) {
2020-03-13 00:53:22 +02:00
m_js_highlight - > activate ( ) ;
2020-12-21 13:59:21 +01:00
} else if ( m_extension = = " gml " ) {
m_gml_highlight - > activate ( ) ;
2020-05-01 01:57:06 +03:00
} else if ( m_extension = = " ini " ) {
m_ini_highlight - > activate ( ) ;
2020-04-28 21:42:45 +02:00
} else {
2020-03-11 03:02:37 +02:00
m_plain_text_highlight - > activate ( ) ;
2020-04-28 21:42:45 +02:00
}
2020-07-06 18:48:49 +04:30
if ( m_auto_detect_preview_mode ) {
if ( m_extension = = " md " )
set_preview_mode ( PreviewMode : : Markdown ) ;
else if ( m_extension = = " html " )
set_preview_mode ( PreviewMode : : HTML ) ;
else
set_preview_mode ( PreviewMode : : None ) ;
}
2020-02-07 20:12:25 +01:00
2019-08-27 20:18:19 +02:00
update_title ( ) ;
}
void TextEditorWidget : : update_title ( )
{
2019-07-24 06:32:30 +02:00
StringBuilder builder ;
2020-12-30 03:44:38 +01:00
if ( m_path . is_empty ( ) )
builder . append ( " Untitled " ) ;
else
builder . append ( m_path ) ;
2019-08-27 20:18:19 +02:00
if ( m_document_dirty )
builder . append ( " (*) " ) ;
2020-03-13 23:21:35 +01:00
builder . append ( " - Text Editor " ) ;
2019-07-24 06:32:30 +02:00
window ( ) - > set_title ( builder . to_string ( ) ) ;
}
2021-03-18 22:07:59 +00:00
bool TextEditorWidget : : open_file ( const String & path )
2019-07-11 13:52:33 -05:00
{
2020-02-02 12:34:39 +01:00
auto file = Core : : File : : construct ( path ) ;
2020-06-04 22:15:10 +02:00
if ( ! file - > open ( Core : : IODevice : : ReadOnly ) & & file - > error ( ) ! = ENOENT ) {
2020-10-06 19:25:44 +02:00
GUI : : MessageBox : : show ( window ( ) , String : : formatted ( " Opening \" {} \" failed: {} " , path , strerror ( errno ) ) , " Error " , GUI : : MessageBox : : Type : : Error ) ;
2021-03-18 22:07:59 +00:00
return false ;
2019-07-11 13:52:33 -05:00
}
2021-03-30 00:37:30 +03:00
if ( file - > is_device ( ) ) {
GUI : : MessageBox : : show ( window ( ) , String : : formatted ( " Opening \" {} \" failed: Can't open device files " , path ) , " Error " , GUI : : MessageBox : : Type : : Error ) ;
return false ;
}
2019-09-21 20:50:06 +02:00
m_editor - > set_text ( file - > read_all ( ) ) ;
2019-12-04 22:57:54 -08:00
m_document_dirty = false ;
m_document_opening = true ;
2020-05-26 14:52:44 +03:00
set_path ( LexicalPath ( path ) ) ;
2020-01-23 21:29:59 +01:00
m_editor - > set_focus ( true ) ;
2021-03-18 22:07:59 +00:00
return true ;
2019-07-16 21:32:10 +02:00
}
2019-08-27 20:37:41 +02:00
bool TextEditorWidget : : request_close ( )
{
if ( ! m_document_dirty )
return true ;
2020-07-15 20:45:11 -06:00
auto result = GUI : : MessageBox : : show ( window ( ) , " The document has been modified. Would you like to save? " , " Unsaved changes " , GUI : : MessageBox : : Type : : Warning , GUI : : MessageBox : : InputType : : YesNoCancel ) ;
2020-02-17 00:06:11 -05:00
2020-03-10 15:23:28 +02:00
if ( result = = GUI : : MessageBox : : ExecYes ) {
2020-02-17 00:06:11 -05:00
m_save_action - > activate ( ) ;
2020-03-10 15:23:28 +02:00
return true ;
}
2020-02-17 00:06:11 -05:00
if ( result = = GUI : : MessageBox : : ExecNo )
return true ;
return false ;
2019-08-27 20:37:41 +02:00
}
2019-12-19 20:20:20 +01:00
2020-02-02 15:07:41 +01:00
void TextEditorWidget : : drop_event ( GUI : : DropEvent & event )
2019-12-19 20:20:20 +01:00
{
event . accept ( ) ;
window ( ) - > move_to_front ( ) ;
2020-02-14 13:18:34 +01:00
if ( event . mime_data ( ) . has_urls ( ) ) {
auto urls = event . mime_data ( ) . urls ( ) ;
2020-02-16 09:17:49 +01:00
if ( urls . is_empty ( ) )
2019-12-19 20:20:20 +01:00
return ;
2020-02-14 13:18:34 +01:00
if ( urls . size ( ) > 1 ) {
2020-07-15 20:45:11 -06:00
GUI : : MessageBox : : show ( window ( ) , " TextEditor can only open one file at a time! " , " One at a time please! " , GUI : : MessageBox : : Type : : Error ) ;
2019-12-19 20:20:20 +01:00
return ;
}
2021-02-27 11:05:39 +01:00
open_file ( urls . first ( ) . path ( ) ) ;
2019-12-19 20:20:20 +01:00
}
}
2020-04-28 21:42:45 +02:00
2020-07-04 21:19:01 +02:00
void TextEditorWidget : : set_preview_mode ( PreviewMode mode )
2020-06-26 22:47:29 +02:00
{
2020-07-04 21:19:01 +02:00
if ( m_preview_mode = = mode )
2020-06-26 22:47:29 +02:00
return ;
2020-07-04 21:19:01 +02:00
m_preview_mode = mode ;
if ( m_preview_mode = = PreviewMode : : HTML ) {
m_html_preview_action - > set_checked ( true ) ;
m_page_view - > set_visible ( true ) ;
2020-06-26 22:47:29 +02:00
update_html_preview ( ) ;
2020-07-04 21:19:01 +02:00
} else if ( m_preview_mode = = PreviewMode : : Markdown ) {
m_markdown_preview_action - > set_checked ( true ) ;
m_page_view - > set_visible ( true ) ;
update_markdown_preview ( ) ;
} else {
m_no_preview_action - > set_checked ( true ) ;
m_page_view - > set_visible ( false ) ;
}
2020-06-26 22:47:29 +02:00
}
2020-07-04 21:19:01 +02:00
void TextEditorWidget : : update_preview ( )
2020-04-28 21:42:45 +02:00
{
2020-07-04 21:19:01 +02:00
switch ( m_preview_mode ) {
case PreviewMode : : Markdown :
2020-04-28 21:42:45 +02:00
update_markdown_preview ( ) ;
2020-07-04 21:19:01 +02:00
break ;
case PreviewMode : : HTML :
update_html_preview ( ) ;
break ;
default :
break ;
}
2020-04-28 21:42:45 +02:00
}
void TextEditorWidget : : update_markdown_preview ( )
{
2020-05-11 13:55:31 -04:00
auto document = Markdown : : Document : : parse ( m_editor - > text ( ) ) ;
if ( document ) {
auto html = document - > render_to_html ( ) ;
2020-07-20 22:22:07 +10:00
auto current_scroll_pos = m_page_view - > visible_content_rect ( ) ;
2020-06-21 21:52:51 +02:00
m_page_view - > load_html ( html , URL : : create_with_file_protocol ( m_path ) ) ;
2020-07-20 22:22:07 +10:00
m_page_view - > scroll_into_view ( current_scroll_pos , true , true ) ;
2020-04-28 21:42:45 +02:00
}
}
2020-06-26 22:47:29 +02:00
void TextEditorWidget : : update_html_preview ( )
{
2020-07-20 22:22:07 +10:00
auto current_scroll_pos = m_page_view - > visible_content_rect ( ) ;
2020-06-26 22:47:29 +02:00
m_page_view - > load_html ( m_editor - > text ( ) , URL : : create_with_file_protocol ( m_path ) ) ;
2020-07-20 22:22:07 +10:00
m_page_view - > scroll_into_view ( current_scroll_pos , true , true ) ;
2020-06-26 22:47:29 +02:00
}
2020-08-29 13:44:25 +02:00
2021-03-14 13:58:57 +01:00
void TextEditorWidget : : update_statusbar ( )
2020-08-29 13:44:25 +02:00
{
StringBuilder builder ;
2020-10-06 19:25:44 +02:00
builder . appendff ( " Line: {}, Column: {} " , m_editor - > cursor ( ) . line ( ) + 1 , m_editor - > cursor ( ) . column ( ) ) ;
2021-03-14 13:58:57 +01:00
if ( m_editor - > has_selection ( ) ) {
int word_count = 0 ;
bool in_word = false ;
String selected_text = m_editor - > selected_text ( ) ;
for ( char c : selected_text ) {
if ( in_word & & isspace ( c ) ) {
in_word = false ;
word_count + + ;
continue ;
}
if ( ! in_word & & ! isspace ( c ) )
in_word = true ;
}
if ( in_word )
word_count + + ;
builder . appendff ( " Selected: {} {} ({} {}) " , selected_text . length ( ) , selected_text . length ( ) = = 1 ? " character " : " characters " , word_count , word_count ! = 1 ? " words " : " word " ) ;
}
2021-02-02 19:01:54 +01:00
m_statusbar - > set_text ( builder . to_string ( ) ) ;
2020-08-29 13:44:25 +02:00
}