2020-01-18 05:38:21 -03:00
/*
2023-02-20 15:03:44 -03:00
* Copyright ( c ) 2018 - 2023 , Andreas Kling < kling @ serenityos . org >
2022-09-12 11:06:24 -03:00
* Copyright ( c ) 2021 , networkException < networkexception @ serenityos . org >
2022-03-29 10:33:46 -03:00
* Copyright ( c ) 2022 , the SerenityOS developers .
2023-02-19 13:14:16 -03:00
* Copyright ( c ) 2023 , Sam Atkins < atkinssj @ serenityos . org >
2020-01-18 05:38:21 -03:00
*
2021-04-22 05:24:48 -03:00
* SPDX - License - Identifier : BSD - 2 - Clause
2020-01-18 05:38:21 -03:00
*/
2021-06-01 16:18:08 -03:00
# include <AK/CharacterTypes.h>
2021-01-24 11:28:26 -03:00
# include <AK/Debug.h>
2020-12-30 07:25:06 -03:00
# include <AK/ScopeGuard.h>
2019-06-07 06:46:02 -03:00
# include <AK/StringBuilder.h>
2020-10-15 15:46:52 -03:00
# include <AK/TemporaryChange.h>
2023-02-08 17:08:01 -03:00
# include <LibCore/DeprecatedFile.h>
2020-04-20 16:31:49 -03:00
# include <LibCore/Timer.h>
2020-02-06 16:33:02 -03:00
# include <LibGUI/Action.h>
2020-12-30 07:25:06 -03:00
# include <LibGUI/AutocompleteProvider.h>
2020-02-06 16:33:02 -03:00
# include <LibGUI/Clipboard.h>
2021-01-02 07:59:55 -03:00
# include <LibGUI/EditingEngine.h>
2022-09-08 19:06:08 -03:00
# include <LibGUI/EmojiInputDialog.h>
2022-11-28 20:09:46 -03:00
# include <LibGUI/IncrementalSearchBanner.h>
2020-02-06 16:33:02 -03:00
# include <LibGUI/InputBox.h>
# include <LibGUI/Menu.h>
# include <LibGUI/Painter.h>
2021-01-02 07:59:55 -03:00
# include <LibGUI/RegularEditingEngine.h>
2021-04-13 11:18:20 -03:00
# include <LibGUI/Scrollbar.h>
2020-02-06 16:33:02 -03:00
# include <LibGUI/TextEditor.h>
# include <LibGUI/Window.h>
2020-02-14 19:02:47 -03:00
# include <LibGfx/Bitmap.h>
2022-04-09 04:28:38 -03:00
# include <LibGfx/Font/Font.h>
# include <LibGfx/Font/FontDatabase.h>
2020-02-07 16:07:15 -03:00
# include <LibGfx/Palette.h>
2022-08-18 10:38:06 -03:00
# include <LibGfx/StandardCursor.h>
2021-02-07 11:15:10 -03:00
# include <LibSyntax/Highlighter.h>
2023-02-20 14:31:48 -03:00
# include <LibUnicode/Segmentation.h>
2019-03-07 13:06:11 -03:00
# include <fcntl.h>
# include <stdio.h>
2019-06-07 06:46:02 -03:00
# include <unistd.h>
2019-03-06 20:31:06 -03:00
2021-01-02 20:30:13 -03:00
REGISTER_WIDGET ( GUI , TextEditor )
2020-02-02 11:07:41 -03:00
namespace GUI {
2023-02-23 12:31:04 -03:00
static constexpr StringView folded_region_summary_text = " ... " sv ;
2020-02-23 08:07:13 -03:00
TextEditor : : TextEditor ( Type type )
: m_type ( type )
2019-03-06 20:31:06 -03:00
{
2023-03-10 14:02:14 -03:00
REGISTER_DEPRECATED_STRING_PROPERTY ( " text " , text , set_text ) ;
REGISTER_DEPRECATED_STRING_PROPERTY ( " placeholder " , placeholder , set_placeholder ) ;
2022-02-02 21:18:23 -03:00
REGISTER_BOOL_PROPERTY ( " gutter " , is_gutter_visible , set_gutter_visible ) ;
REGISTER_BOOL_PROPERTY ( " ruler " , is_ruler_visible , set_ruler_visible ) ;
2020-12-28 09:01:41 -03:00
REGISTER_ENUM_PROPERTY ( " mode " , mode , set_mode , Mode ,
{ Editable , " Editable " } ,
{ ReadOnly , " ReadOnly " } ,
{ DisplayOnly , " DisplayOnly " } ) ;
2020-09-25 15:43:35 -03:00
2020-10-30 06:58:27 -03:00
set_focus_policy ( GUI : : FocusPolicy : : StrongFocus ) ;
2022-09-09 09:47:24 -03:00
set_or_clear_emoji_input_callback ( ) ;
2020-09-11 09:25:48 -03:00
set_override_cursor ( Gfx : : StandardCursor : : IBeam ) ;
2019-12-24 18:01:32 -03:00
set_background_role ( ColorRole : : Base ) ;
set_foreground_role ( ColorRole : : BaseText ) ;
2020-02-02 11:07:41 -03:00
set_document ( TextDocument : : create ( ) ) ;
2020-09-01 18:57:30 -03:00
if ( is_single_line ( ) )
set_visualize_trailing_whitespace ( false ) ;
2019-03-16 12:54:51 -03:00
set_scrollbars_enabled ( is_multi_line ( ) ) ;
2022-02-26 17:22:30 -03:00
if ( is_multi_line ( ) ) {
2020-12-29 14:25:13 -03:00
set_font ( Gfx : : FontDatabase : : default_fixed_width_font ( ) ) ;
2022-02-26 17:22:30 -03:00
set_wrapping_mode ( WrappingMode : : WrapAtWords ) ;
2022-11-28 20:09:46 -03:00
m_search_banner = GUI : : IncrementalSearchBanner : : construct ( * this ) ;
set_banner_widget ( m_search_banner ) ;
2022-02-26 17:22:30 -03:00
}
2019-06-07 05:42:43 -03:00
vertical_scrollbar ( ) . set_step ( line_height ( ) ) ;
2019-03-18 21:41:00 -03:00
m_cursor = { 0 , 0 } ;
2019-04-18 07:25:00 -03:00
create_actions ( ) ;
2021-01-02 07:59:55 -03:00
set_editing_engine ( make < RegularEditingEngine > ( ) ) ;
2019-03-06 20:31:06 -03:00
}
2020-02-02 11:07:41 -03:00
TextEditor : : ~ TextEditor ( )
2019-03-06 20:31:06 -03:00
{
2019-10-27 14:00:07 -03:00
if ( m_document )
m_document - > unregister_client ( * this ) ;
2019-03-06 20:31:06 -03:00
}
2020-02-02 11:07:41 -03:00
void TextEditor : : create_actions ( )
2019-04-18 07:25:00 -03:00
{
2020-02-02 11:07:41 -03:00
m_undo_action = CommonActions : : make_undo_action ( [ & ] ( auto & ) { undo ( ) ; } , this ) ;
m_redo_action = CommonActions : : make_redo_action ( [ & ] ( auto & ) { redo ( ) ; } , this ) ;
2019-11-09 04:50:39 -03:00
m_undo_action - > set_enabled ( false ) ;
m_redo_action - > set_enabled ( false ) ;
2020-02-02 11:07:41 -03:00
m_cut_action = CommonActions : : make_cut_action ( [ & ] ( auto & ) { cut ( ) ; } , this ) ;
m_copy_action = CommonActions : : make_copy_action ( [ & ] ( auto & ) { copy ( ) ; } , this ) ;
2021-06-15 12:01:14 -03:00
m_cut_action - > set_enabled ( false ) ;
m_copy_action - > set_enabled ( false ) ;
2020-02-02 11:07:41 -03:00
m_paste_action = CommonActions : : make_paste_action ( [ & ] ( auto & ) { paste ( ) ; } , this ) ;
2022-07-11 14:32:29 -03:00
m_paste_action - > set_enabled ( is_editable ( ) & & Clipboard : : the ( ) . fetch_mime_type ( ) . starts_with ( " text/ " sv ) ) ;
2020-02-10 15:39:30 -03:00
if ( is_multi_line ( ) ) {
m_go_to_line_action = Action : : create (
2023-01-20 16:06:05 -03:00
" Go to line... " , { Mod_Ctrl , Key_L } , Gfx : : Bitmap : : load_from_file ( " /res/icons/16x16/go-to.png " sv ) . release_value_but_fixme_should_propagate_errors ( ) , [ this ] ( auto & ) {
2022-12-04 15:02:33 -03:00
DeprecatedString value ;
2022-07-11 14:32:29 -03:00
if ( InputBox : : show ( window ( ) , value , " Line: " sv , " Go to line " sv ) = = InputBox : : ExecResult : : OK ) {
2020-11-10 05:53:50 -03:00
auto line_target = value . to_uint ( ) ;
if ( line_target . has_value ( ) ) {
set_cursor_and_focus_line ( line_target . value ( ) - 1 , 0 ) ;
}
2020-02-10 15:39:30 -03:00
}
} ,
this ) ;
}
2020-07-03 15:54:38 -03:00
m_select_all_action = CommonActions : : make_select_all_action ( [ this ] ( auto & ) { select_all ( ) ; } , this ) ;
2022-09-08 19:06:08 -03:00
m_insert_emoji_action = CommonActions : : make_insert_emoji_action ( [ & ] ( auto & ) { insert_emoji ( ) ; } , this ) ;
2019-04-18 07:25:00 -03:00
}
2021-11-10 20:55:02 -03:00
void TextEditor : : set_text ( StringView text , AllowCallback allow_callback )
2019-03-06 20:31:06 -03:00
{
2019-04-09 11:20:36 -03:00
m_selection . clear ( ) ;
2019-10-27 12:10:07 -03:00
2021-09-21 18:02:48 -03:00
document ( ) . set_text ( text , allow_callback ) ;
2019-10-27 12:10:07 -03:00
2019-03-16 12:54:51 -03:00
update_content_size ( ) ;
2019-08-25 03:43:01 -03:00
recompute_all_visual_lines ( ) ;
2019-03-20 14:12:56 -03:00
if ( is_single_line ( ) )
2019-10-27 12:10:07 -03:00
set_cursor ( 0 , line ( 0 ) . length ( ) ) ;
2019-03-20 14:12:56 -03:00
else
set_cursor ( 0 , 0 ) ;
2019-04-11 21:52:34 -03:00
did_update_selection ( ) ;
2019-03-06 20:31:06 -03:00
update ( ) ;
}
2020-02-02 11:07:41 -03:00
void TextEditor : : update_content_size ( )
2019-03-07 09:54:02 -03:00
{
2019-03-16 12:54:51 -03:00
int content_width = 0 ;
2019-08-29 01:26:24 -03:00
int content_height = 0 ;
2019-10-27 14:00:07 -03:00
for ( auto & line : m_line_visual_data ) {
2023-03-06 13:16:25 -03:00
content_width = max ( line - > visual_rect . width ( ) , content_width ) ;
content_height + = line - > visual_rect . height ( ) ;
2019-08-29 01:26:24 -03:00
}
2019-03-16 19:16:37 -03:00
content_width + = m_horizontal_content_padding * 2 ;
2019-04-24 18:42:49 -03:00
if ( is_right_text_alignment ( m_text_alignment ) )
content_width = max ( frame_inner_rect ( ) . width ( ) , content_width ) ;
2019-08-29 01:26:24 -03:00
2019-03-16 12:54:51 -03:00
set_content_size ( { content_width , content_height } ) ;
2023-02-19 13:17:18 -03:00
set_size_occupied_by_fixed_elements ( { fixed_elements_width ( ) , 0 } ) ;
2019-03-06 20:31:06 -03:00
}
2022-12-06 17:27:44 -03:00
TextPosition TextEditor : : text_position_at_content_position ( Gfx : : IntPoint content_position ) const
2019-03-06 20:31:06 -03:00
{
2021-01-02 20:11:56 -03:00
auto position = content_position ;
2020-06-29 15:34:42 -03:00
if ( is_single_line ( ) & & icon ( ) )
2021-04-12 15:47:09 -03:00
position . translate_by ( - ( icon_size ( ) + icon_padding ( ) ) , 0 ) ;
2020-06-29 15:34:42 -03:00
2019-12-09 13:45:40 -03:00
size_t line_index = 0 ;
2019-08-25 03:43:01 -03:00
2021-02-15 12:59:13 -03:00
if ( position . y ( ) > = 0 ) {
2023-02-23 12:31:04 -03:00
size_t last_visible_line_index = 0 ;
// FIXME: Oh boy is this a slow way of calculating this!
// NOTE: Offset by 1 in calculations is because we can't do `i >= 0` with an unsigned type.
for ( size_t i = line_count ( ) ; i > 0 ; - - i ) {
if ( document ( ) . line_is_visible ( i - 1 ) ) {
last_visible_line_index = i - 1 ;
break ;
}
}
2023-02-09 11:21:52 -03:00
for ( size_t i = 0 ; i < line_count ( ) ; + + i ) {
2023-02-23 12:31:04 -03:00
if ( ! document ( ) . line_is_visible ( i ) )
continue ;
2023-03-06 13:16:25 -03:00
auto & rect = m_line_visual_data [ i ] - > visual_rect ;
2023-02-09 11:21:52 -03:00
if ( position . y ( ) > = rect . top ( ) & & position . y ( ) < = rect . bottom ( ) ) {
line_index = i ;
break ;
2019-10-27 14:00:07 -03:00
}
2023-02-09 11:21:52 -03:00
if ( position . y ( ) > rect . bottom ( ) )
2023-02-23 12:31:04 -03:00
line_index = last_visible_line_index ;
2019-08-25 03:43:01 -03:00
}
2023-02-23 12:31:04 -03:00
line_index = max ( ( size_t ) 0 , min ( line_index , last_visible_line_index ) ) ;
2019-08-25 03:43:01 -03:00
}
2020-05-18 11:38:28 -03:00
size_t column_index = 0 ;
2019-04-24 17:24:16 -03:00
switch ( m_text_alignment ) {
2020-02-06 07:56:38 -03:00
case Gfx : : TextAlignment : : CenterLeft :
2021-07-09 08:34:20 -03:00
for_each_visual_line ( line_index , [ & ] ( Gfx : : IntRect const & rect , auto & view , size_t start_of_line , [[maybe_unused]] bool is_last_visual_line ) {
2020-11-13 02:29:55 -03:00
if ( is_multi_line ( ) & & ! rect . contains_vertically ( position . y ( ) ) & & ! is_last_visual_line )
2020-05-18 12:55:21 -03:00
return IterationDecision : : Continue ;
2023-02-22 14:41:58 -03:00
2020-05-18 12:55:21 -03:00
column_index = start_of_line ;
2023-02-22 14:41:58 -03:00
int glyph_x = 0 ;
2020-05-18 12:55:21 -03:00
if ( position . x ( ) < = 0 ) {
// We're outside the text on the left side, put cursor at column 0 on this visual line.
2023-02-22 14:41:58 -03:00
return IterationDecision : : Break ;
2020-05-18 11:38:28 -03:00
}
2023-02-22 14:41:58 -03:00
for ( auto it = view . begin ( ) ; it ! = view . end ( ) ; + + it , + + column_index ) {
int advance = font ( ) . glyph_or_emoji_width ( it ) + font ( ) . glyph_spacing ( ) ;
if ( ( glyph_x + ( advance / 2 ) ) > = position . x ( ) )
break ;
glyph_x + = advance ;
}
2020-05-18 12:55:21 -03:00
return IterationDecision : : Break ;
2020-05-18 11:38:28 -03:00
} ) ;
2019-04-24 17:24:16 -03:00
break ;
2020-02-06 07:56:38 -03:00
case Gfx : : TextAlignment : : CenterRight :
2019-08-25 03:43:01 -03:00
// FIXME: Support right-aligned line wrapping, I guess.
2021-02-23 16:42:32 -03:00
VERIFY ( ! is_wrapping_enabled ( ) ) ;
2020-05-18 11:38:28 -03:00
column_index = ( position . x ( ) - content_x_for_position ( { line_index , 0 } ) + fixed_glyph_width ( ) / 2 ) / fixed_glyph_width ( ) ;
2019-04-24 17:24:16 -03:00
break ;
default :
2021-02-23 16:42:32 -03:00
VERIFY_NOT_REACHED ( ) ;
2019-04-24 17:24:16 -03:00
}
2019-12-09 13:45:40 -03:00
column_index = max ( ( size_t ) 0 , min ( column_index , line ( line_index ) . length ( ) ) ) ;
2019-03-07 11:03:38 -03:00
return { line_index , column_index } ;
}
2022-12-06 17:27:44 -03:00
TextPosition TextEditor : : text_position_at ( Gfx : : IntPoint widget_position ) const
2021-01-02 20:11:56 -03:00
{
auto content_position = widget_position ;
2021-04-12 15:47:09 -03:00
content_position . translate_by ( horizontal_scrollbar ( ) . value ( ) , vertical_scrollbar ( ) . value ( ) ) ;
2023-02-19 13:17:18 -03:00
content_position . translate_by ( - ( m_horizontal_content_padding + fixed_elements_width ( ) ) , 0 ) ;
2021-04-12 15:47:09 -03:00
content_position . translate_by ( - frame_thickness ( ) , - frame_thickness ( ) ) ;
2022-11-28 20:09:46 -03:00
content_position . translate_by ( 0 , - height_occupied_by_banner_widget ( ) ) ;
2021-01-02 20:11:56 -03:00
return text_position_at_content_position ( content_position ) ;
}
2020-02-02 11:07:41 -03:00
void TextEditor : : doubleclick_event ( MouseEvent & event )
2019-04-25 17:29:25 -03:00
{
2021-10-27 08:20:27 -03:00
if ( event . button ( ) ! = MouseButton : : Primary )
2019-04-25 17:29:25 -03:00
return ;
2020-07-14 18:02:46 -03:00
if ( is_displayonly ( ) )
return ;
2021-04-21 18:48:26 -03:00
if ( ! current_line ( ) . can_select ( ) )
return ;
2021-06-29 06:22:57 -03:00
rehighlight_if_needed ( ) ;
2019-11-08 15:49:08 -03:00
2019-05-15 19:40:55 -03:00
m_triple_click_timer . start ( ) ;
2019-04-25 17:29:25 -03:00
m_in_drag_select = false ;
2021-06-25 06:57:34 -03:00
auto position = text_position_at ( event . position ( ) ) ;
2019-04-25 17:29:25 -03:00
2022-05-07 11:03:33 -03:00
if ( m_substitution_code_point . has_value ( ) ) {
2021-06-25 12:48:51 -03:00
// NOTE: If we substitute the code points, we don't want double clicking to only select a single word, since
// whitespace isn't visible anymore.
m_selection = document ( ) . range_for_entire_line ( position . line ( ) ) ;
} else if ( document ( ) . has_spans ( ) ) {
2019-10-27 12:10:07 -03:00
for ( auto & span : document ( ) . spans ( ) ) {
2021-06-25 06:57:34 -03:00
if ( span . range . contains ( position ) ) {
m_selection = span . range ;
break ;
}
2019-10-27 07:10:32 -03:00
}
2021-06-25 06:57:34 -03:00
} else {
m_selection . set_start ( document ( ) . first_word_break_before ( position , false ) ) ;
m_selection . set_end ( document ( ) . first_word_break_after ( position ) ) ;
2019-04-25 17:29:25 -03:00
}
2021-06-25 06:57:34 -03:00
set_cursor ( m_selection . end ( ) ) ;
2019-04-25 17:29:25 -03:00
update ( ) ;
did_update_selection ( ) ;
}
2020-02-02 11:07:41 -03:00
void TextEditor : : mousedown_event ( MouseEvent & event )
2019-03-07 11:03:38 -03:00
{
2023-01-02 02:38:53 -03:00
using namespace AK : : TimeLiterals ;
2021-10-27 08:20:27 -03:00
if ( event . button ( ) ! = MouseButton : : Primary ) {
2019-05-15 19:15:58 -03:00
return ;
}
2019-03-08 13:53:02 -03:00
2023-02-23 12:31:04 -03:00
auto text_position = text_position_at ( event . position ( ) ) ;
if ( event . modifiers ( ) = = 0 & & folding_indicator_rect ( text_position . line ( ) ) . contains ( event . position ( ) ) ) {
if ( auto folding_region = document ( ) . folding_region_starting_on_line ( text_position . line ( ) ) ; folding_region . has_value ( ) ) {
folding_region - > is_folded = ! folding_region - > is_folded ;
dbgln_if ( TEXTEDITOR_DEBUG , " TextEditor: {} region {}. " , folding_region - > is_folded ? " Folding " sv : " Unfolding " sv , folding_region - > range ) ;
if ( folding_region - > is_folded & & folding_region - > range . contains ( cursor ( ) ) ) {
// Cursor is now within a hidden range, so move it outside.
set_cursor ( folding_region - > range . start ( ) ) ;
}
recompute_all_visual_lines ( ) ;
update ( ) ;
return ;
}
}
2020-07-14 18:02:46 -03:00
if ( on_mousedown )
on_mousedown ( ) ;
if ( is_displayonly ( ) )
return ;
2023-01-02 02:38:53 -03:00
if ( m_triple_click_timer . is_valid ( ) & & m_triple_click_timer . elapsed_time ( ) < 250 _ms ) {
2020-02-02 08:34:39 -03:00
m_triple_click_timer = Core : : ElapsedTimer ( ) ;
2021-08-15 02:48:53 -03:00
select_current_line ( ) ;
2019-05-15 19:40:55 -03:00
return ;
}
2019-05-15 19:15:58 -03:00
if ( event . modifiers ( ) & Mod_Shift ) {
if ( ! has_selection ( ) )
2019-06-07 06:46:02 -03:00
m_selection . set ( m_cursor , { } ) ;
2019-05-15 19:15:58 -03:00
} else {
m_selection . clear ( ) ;
}
2019-03-08 13:53:02 -03:00
2019-05-15 19:15:58 -03:00
m_in_drag_select = true ;
2019-03-08 13:53:02 -03:00
2023-02-20 14:31:48 -03:00
set_cursor_to_text_position ( event . position ( ) ) ;
2019-03-08 14:28:24 -03:00
2019-05-15 19:15:58 -03:00
if ( ! ( event . modifiers ( ) & Mod_Shift ) ) {
if ( ! has_selection ( ) )
2019-06-07 06:46:02 -03:00
m_selection . set ( m_cursor , { } ) ;
2019-03-08 13:53:02 -03:00
}
2019-05-15 19:15:58 -03:00
if ( m_selection . start ( ) . is_valid ( ) & & m_selection . start ( ) ! = m_cursor )
m_selection . set_end ( m_cursor ) ;
// FIXME: Only update the relevant rects.
update ( ) ;
did_update_selection ( ) ;
2019-03-08 13:53:02 -03:00
}
2020-02-02 11:07:41 -03:00
void TextEditor : : mouseup_event ( MouseEvent & event )
2019-03-08 13:53:02 -03:00
{
2021-10-27 08:20:27 -03:00
if ( event . button ( ) = = MouseButton : : Primary ) {
2019-03-08 13:53:02 -03:00
if ( m_in_drag_select ) {
m_in_drag_select = false ;
}
return ;
}
}
2020-02-02 11:07:41 -03:00
void TextEditor : : mousemove_event ( MouseEvent & event )
2019-03-08 13:53:02 -03:00
{
2020-04-20 16:31:49 -03:00
m_last_mousemove_position = event . position ( ) ;
2022-12-25 17:51:04 -03:00
if ( m_in_drag_select ) {
auto constrained = event . position ( ) . constrained ( widget_inner_rect ( ) ) ;
2023-02-20 14:31:48 -03:00
set_cursor_to_text_position ( constrained ) ;
2019-03-08 14:28:24 -03:00
m_selection . set_end ( m_cursor ) ;
2019-04-11 21:52:34 -03:00
did_update_selection ( ) ;
2019-03-07 20:49:45 -03:00
update ( ) ;
2019-03-08 13:53:02 -03:00
return ;
2019-03-07 20:49:45 -03:00
}
2022-08-18 10:38:06 -03:00
2023-02-11 16:35:21 -03:00
if ( m_ruler_visible & & ruler_rect_in_inner_coordinates ( ) . contains ( event . position ( ) ) ) {
set_override_cursor ( Gfx : : StandardCursor : : None ) ;
2023-02-23 12:31:04 -03:00
} else if ( m_ruler_visible & & folding_indicator_rect_in_inner_coordinates ( ) . contains ( event . position ( ) ) ) {
auto text_position = text_position_at ( event . position ( ) ) ;
if ( document ( ) . folding_region_starting_on_line ( text_position . line ( ) ) . has_value ( )
& & folding_indicator_rect ( text_position . line ( ) ) . contains ( event . position ( ) ) ) {
set_override_cursor ( Gfx : : StandardCursor : : Hand ) ;
} else {
set_override_cursor ( Gfx : : StandardCursor : : None ) ;
}
2023-02-11 16:35:21 -03:00
} else if ( m_gutter_visible & & gutter_rect_in_inner_coordinates ( ) . contains ( event . position ( ) ) ) {
2022-08-18 10:38:06 -03:00
set_override_cursor ( Gfx : : StandardCursor : : None ) ;
} else {
set_editing_cursor ( ) ;
}
2019-03-06 20:31:06 -03:00
}
2021-08-15 02:48:53 -03:00
void TextEditor : : select_current_line ( )
{
m_selection = document ( ) . range_for_entire_line ( m_cursor . line ( ) ) ;
set_cursor ( m_selection . end ( ) ) ;
update ( ) ;
did_update_selection ( ) ;
}
2022-12-25 17:51:04 -03:00
void TextEditor : : automatic_scrolling_timer_did_fire ( )
2020-04-20 16:31:49 -03:00
{
if ( ! m_in_drag_select ) {
2022-12-25 17:51:04 -03:00
set_automatic_scrolling_timer_active ( false ) ;
2020-04-20 16:31:49 -03:00
return ;
}
2023-02-20 14:31:48 -03:00
set_cursor_to_text_position ( m_last_mousemove_position ) ;
2020-04-20 16:31:49 -03:00
m_selection . set_end ( m_cursor ) ;
did_update_selection ( ) ;
update ( ) ;
}
2023-02-23 12:31:04 -03:00
int TextEditor : : folding_indicator_width ( ) const
{
return document ( ) . has_folding_regions ( ) ? line_height ( ) : 0 ;
}
2020-02-02 11:07:41 -03:00
int TextEditor : : ruler_width ( ) const
2019-03-07 16:05:05 -03:00
{
2019-03-15 13:54:05 -03:00
if ( ! m_ruler_visible )
return 0 ;
2020-08-15 11:27:54 -03:00
int line_count_digits = static_cast < int > ( log10 ( line_count ( ) ) ) + 1 ;
2022-02-24 10:48:17 -03:00
auto padding = 5 + ( font ( ) . is_fixed_width ( ) ? 1 : ( line_count_digits - ( line_count ( ) < 10 ? - 1 : 0 ) ) ) ;
auto widest_numeral = font ( ) . bold_variant ( ) . glyph_width ( ' 4 ' ) ;
return line_count ( ) < 10 ? ( line_count_digits + 1 ) * widest_numeral + padding : line_count_digits * widest_numeral + padding ;
2019-03-07 16:05:05 -03:00
}
2021-06-11 22:50:23 -03:00
int TextEditor : : gutter_width ( ) const
{
if ( ! m_gutter_visible )
return 0 ;
return line_height ( ) ; // square gutter
}
2023-02-19 13:14:16 -03:00
Gfx : : IntRect TextEditor : : gutter_content_rect ( size_t line_index ) const
2019-03-07 16:05:05 -03:00
{
2023-02-19 13:14:16 -03:00
if ( ! m_gutter_visible )
2019-06-07 06:46:02 -03:00
return { } ;
2023-02-23 12:31:04 -03:00
2019-03-07 16:05:05 -03:00
return {
2023-02-19 13:14:16 -03:00
0 ,
line_content_rect ( line_index ) . y ( ) - vertical_scrollbar ( ) . value ( ) ,
gutter_width ( ) ,
2019-08-25 02:14:02 -03:00
line_content_rect ( line_index ) . height ( )
} ;
}
2023-02-19 13:14:16 -03:00
Gfx : : IntRect TextEditor : : ruler_content_rect ( size_t line_index ) const
2021-06-11 22:50:23 -03:00
{
2023-02-19 13:14:16 -03:00
if ( ! m_ruler_visible )
2021-06-11 22:50:23 -03:00
return { } ;
2023-02-23 12:31:04 -03:00
2021-06-11 22:50:23 -03:00
return {
gutter_width ( ) ,
2023-02-19 13:14:16 -03:00
line_content_rect ( line_index ) . y ( ) - vertical_scrollbar ( ) . value ( ) ,
ruler_width ( ) ,
2021-06-11 22:50:23 -03:00
line_content_rect ( line_index ) . height ( )
} ;
}
2023-02-23 12:31:04 -03:00
Gfx : : IntRect TextEditor : : folding_indicator_rect ( size_t line_index ) const
{
if ( ! m_ruler_visible | | ! document ( ) . has_folding_regions ( ) )
return { } ;
return {
gutter_width ( ) + ruler_width ( ) ,
line_content_rect ( line_index ) . y ( ) - vertical_scrollbar ( ) . value ( ) ,
folding_indicator_width ( ) ,
line_content_rect ( line_index ) . height ( )
} ;
}
2023-02-19 13:14:16 -03:00
Gfx : : IntRect TextEditor : : gutter_rect_in_inner_coordinates ( ) const
2019-08-25 02:14:02 -03:00
{
2023-02-19 13:14:16 -03:00
return { 0 , 0 , gutter_width ( ) , widget_inner_rect ( ) . height ( ) } ;
2021-06-11 22:50:23 -03:00
}
2023-02-19 13:14:16 -03:00
Gfx : : IntRect TextEditor : : ruler_rect_in_inner_coordinates ( ) const
2021-06-11 22:50:23 -03:00
{
2023-02-19 13:14:16 -03:00
return { gutter_width ( ) , 0 , ruler_width ( ) , widget_inner_rect ( ) . height ( ) } ;
2019-08-25 02:14:02 -03:00
}
2023-02-23 12:31:04 -03:00
Gfx : : IntRect TextEditor : : folding_indicator_rect_in_inner_coordinates ( ) const
{
return { gutter_width ( ) + ruler_width ( ) , 0 , folding_indicator_width ( ) , widget_inner_rect ( ) . height ( ) } ;
}
2020-06-10 05:57:59 -03:00
Gfx : : IntRect TextEditor : : visible_text_rect_in_inner_coordinates ( ) const
2019-08-25 02:14:02 -03:00
{
return {
2023-02-19 13:17:18 -03:00
m_horizontal_content_padding + fixed_elements_width ( ) ,
2019-08-25 02:14:02 -03:00
0 ,
2023-02-19 13:17:18 -03:00
frame_inner_rect ( ) . width ( ) - ( m_horizontal_content_padding * 2 ) - width_occupied_by_vertical_scrollbar ( ) - fixed_elements_width ( ) ,
2019-09-01 15:04:25 -03:00
frame_inner_rect ( ) . height ( ) - height_occupied_by_horizontal_scrollbar ( )
2019-03-07 16:05:05 -03:00
} ;
}
2020-02-02 11:07:41 -03:00
void TextEditor : : paint_event ( PaintEvent & event )
2019-03-06 20:31:06 -03:00
{
2020-04-28 08:15:53 -03:00
Color widget_background_color = palette ( ) . color ( is_enabled ( ) ? background_role ( ) : Gfx : : ColorRole : : Window ) ;
2021-06-29 06:22:57 -03:00
rehighlight_if_needed ( ) ;
2019-11-08 15:49:08 -03:00
2020-02-02 11:07:41 -03:00
Frame : : paint_event ( event ) ;
2019-03-28 12:14:26 -03:00
2020-02-02 11:07:41 -03:00
Painter painter ( * this ) ;
2019-03-29 11:01:54 -03:00
painter . add_clip_rect ( widget_inner_rect ( ) ) ;
painter . add_clip_rect ( event . rect ( ) ) ;
2019-12-24 16:57:54 -03:00
painter . fill_rect ( event . rect ( ) , widget_background_color ) ;
2019-03-07 16:05:05 -03:00
2023-02-21 12:10:37 -03:00
Gfx : : TextAttributes unspanned_text_attributes ;
if ( is_displayonly ( ) & & is_focused ( ) ) {
unspanned_text_attributes . color = palette ( ) . color ( is_enabled ( ) ? Gfx : : ColorRole : : SelectionText : Gfx : : ColorRole : : DisabledText ) ;
} else {
unspanned_text_attributes . color = palette ( ) . color ( is_enabled ( ) ? foreground_role ( ) : Gfx : : ColorRole : : DisabledText ) ;
}
2023-02-23 12:31:04 -03:00
auto & folded_region_summary_font = font ( ) . bold_variant ( ) ;
Gfx : : TextAttributes folded_region_summary_attributes { palette ( ) . color ( Gfx : : ColorRole : : SyntaxComment ) } ;
2021-06-25 12:48:51 -03:00
// NOTE: This lambda and TextEditor::text_width_for_font() are used to substitute all glyphs with m_substitution_code_point if necessary.
// Painter::draw_text() and Gfx::Font::width() should not be called directly, but using this lambda and TextEditor::text_width_for_font().
2023-03-03 16:40:56 -03:00
auto draw_text = [ & ] ( auto const & rect , auto const & raw_text , Gfx : : Font const & font , Gfx : : TextAlignment alignment , Gfx : : TextAttributes attributes , bool substitute = true ) {
2022-05-07 11:03:33 -03:00
if ( m_substitution_code_point . has_value ( ) & & substitute ) {
2022-01-20 15:59:44 -03:00
painter . draw_text ( rect , substitution_code_point_view ( raw_text . length ( ) ) , font , alignment , attributes . color ) ;
2021-06-25 12:48:51 -03:00
} else {
2022-01-20 15:59:44 -03:00
painter . draw_text ( rect , raw_text , font , alignment , attributes . color ) ;
}
2023-03-15 09:35:00 -03:00
if ( attributes . underline_style . has_value ( ) ) {
2023-03-03 16:40:56 -03:00
auto bottom_left = [ & ] ( ) {
auto point = rect . bottom_left ( ) . translated ( 0 , 1 ) ;
if constexpr ( IsSame < RemoveCVReference < decltype ( rect ) > , Gfx : : IntRect > )
return point ;
else
return point . template to_type < int > ( ) ;
} ;
auto bottom_right = [ & ] ( ) {
auto point = rect . bottom_right ( ) . translated ( 0 , 1 ) ;
if constexpr ( IsSame < RemoveCVReference < decltype ( rect ) > , Gfx : : IntRect > )
return point ;
else
return point . template to_type < int > ( ) ;
} ;
2022-01-20 15:59:44 -03:00
if ( attributes . underline_style = = Gfx : : TextAttributes : : UnderlineStyle : : Solid )
2023-03-03 16:40:56 -03:00
painter . draw_line ( bottom_left ( ) , bottom_right ( ) , attributes . underline_color . value_or ( attributes . color ) ) ;
2022-01-20 15:59:44 -03:00
if ( attributes . underline_style = = Gfx : : TextAttributes : : UnderlineStyle : : Wavy )
2023-03-03 16:40:56 -03:00
painter . draw_triangle_wave ( bottom_left ( ) , bottom_right ( ) , attributes . underline_color . value_or ( attributes . color ) , 2 ) ;
2021-06-25 12:48:51 -03:00
}
} ;
2021-03-15 21:11:43 -03:00
if ( is_displayonly ( ) & & is_focused ( ) ) {
2020-07-14 18:02:46 -03:00
Gfx : : IntRect display_rect {
widget_inner_rect ( ) . x ( ) + 1 ,
widget_inner_rect ( ) . y ( ) + 1 ,
2020-12-12 19:17:41 -03:00
widget_inner_rect ( ) . width ( ) - 2 ,
2020-07-14 18:02:46 -03:00
widget_inner_rect ( ) . height ( ) - 2
} ;
2022-02-02 21:32:58 -03:00
painter . fill_rect ( display_rect , palette ( ) . selection ( ) ) ;
2020-07-14 18:02:46 -03:00
}
2019-04-24 20:09:44 -03:00
painter . translate ( frame_thickness ( ) , frame_thickness ( ) ) ;
2022-11-28 20:09:46 -03:00
painter . translate ( 0 , height_occupied_by_banner_widget ( ) ) ;
2019-04-24 20:09:44 -03:00
2022-02-07 08:25:25 -03:00
if ( ! is_multi_line ( ) & & m_icon ) {
Gfx : : IntRect icon_rect { icon_padding ( ) , 1 , icon_size ( ) , icon_size ( ) } ;
painter . draw_scaled_bitmap ( icon_rect , * m_icon , m_icon - > rect ( ) ) ;
}
2021-06-11 22:50:23 -03:00
if ( m_gutter_visible ) {
auto gutter_rect = gutter_rect_in_inner_coordinates ( ) ;
painter . fill_rect ( gutter_rect , palette ( ) . gutter ( ) ) ;
if ( ! m_ruler_visible )
painter . draw_line ( gutter_rect . top_right ( ) , gutter_rect . bottom_right ( ) , palette ( ) . gutter_border ( ) ) ;
}
2019-03-15 13:54:05 -03:00
if ( m_ruler_visible ) {
2023-02-23 12:31:04 -03:00
auto ruler_rect = ruler_rect_in_inner_coordinates ( ) . inflated ( 0 , folding_indicator_width ( ) , 0 , 0 ) ;
2020-02-19 07:11:37 -03:00
painter . fill_rect ( ruler_rect , palette ( ) . ruler ( ) ) ;
painter . draw_line ( ruler_rect . top_right ( ) , ruler_rect . bottom_right ( ) , palette ( ) . ruler_border ( ) ) ;
2023-02-23 12:31:04 -03:00
// Paint +/- buttons for folding regions
for ( auto const & folding_region : document ( ) . folding_regions ( ) ) {
auto start_line = folding_region . range . start ( ) . line ( ) ;
if ( ! document ( ) . line_is_visible ( start_line ) )
continue ;
auto fold_indicator_rect = folding_indicator_rect ( start_line ) . shrunken ( 4 , 4 ) ;
fold_indicator_rect . set_height ( fold_indicator_rect . width ( ) ) ;
painter . draw_rect ( fold_indicator_rect , palette ( ) . ruler_inactive_text ( ) ) ;
auto fold_symbol = folding_region . is_folded ? " + " sv : " - " sv ;
painter . draw_text ( fold_indicator_rect , fold_symbol , font ( ) , Gfx : : TextAlignment : : Center , palette ( ) . ruler_inactive_text ( ) ) ;
}
2019-03-15 13:54:05 -03:00
}
2019-03-07 16:05:05 -03:00
2019-12-09 13:45:40 -03:00
size_t first_visible_line = text_position_at ( event . rect ( ) . top_left ( ) ) . line ( ) ;
size_t last_visible_line = text_position_at ( event . rect ( ) . bottom_right ( ) ) . line ( ) ;
2019-03-07 11:03:38 -03:00
2019-03-08 14:28:24 -03:00
auto selection = normalized_selection ( ) ;
bool has_selection = selection . is_valid ( ) ;
2019-03-07 20:49:45 -03:00
2019-03-15 13:54:05 -03:00
if ( m_ruler_visible ) {
2019-12-09 13:45:40 -03:00
for ( size_t i = first_visible_line ; i < = last_visible_line ; + + i ) {
2023-02-23 12:31:04 -03:00
if ( ! document ( ) . line_is_visible ( i ) )
continue ;
2019-03-15 13:54:05 -03:00
bool is_current_line = i = = m_cursor . line ( ) ;
auto ruler_line_rect = ruler_content_rect ( i ) ;
2022-04-24 14:03:17 -03:00
// NOTE: Shrink the rectangle to be only on the first visual line.
auto const line_height = font ( ) . preferred_line_height ( ) ;
if ( ruler_line_rect . height ( ) > line_height )
ruler_line_rect . set_height ( line_height ) ;
2021-06-25 12:48:51 -03:00
// NOTE: Use Painter::draw_text() directly here, as we want to always draw the line numbers in clear text.
2022-12-08 06:07:50 -03:00
size_t const line_number = is_relative_line_number ( ) & & ! is_current_line ? max ( i , m_cursor . line ( ) ) - min ( i , m_cursor . line ( ) ) : i + 1 ;
2019-03-15 13:54:05 -03:00
painter . draw_text (
2022-02-24 10:52:31 -03:00
ruler_line_rect . shrunken ( 2 , 0 ) ,
2022-12-08 06:07:50 -03:00
DeprecatedString : : number ( line_number ) ,
2020-12-30 21:49:05 -03:00
is_current_line ? font ( ) . bold_variant ( ) : font ( ) ,
2022-02-24 10:52:31 -03:00
Gfx : : TextAlignment : : CenterRight ,
2020-02-19 07:11:37 -03:00
is_current_line ? palette ( ) . ruler_active_text ( ) : palette ( ) . ruler_inactive_text ( ) ) ;
2019-03-15 13:54:05 -03:00
}
2019-03-07 16:05:05 -03:00
}
2023-02-19 13:14:16 -03:00
auto horizontal_scrollbar_value = horizontal_scrollbar ( ) . value ( ) ;
painter . translate ( - horizontal_scrollbar_value , - vertical_scrollbar ( ) . value ( ) ) ;
if ( m_icon & & horizontal_scrollbar_value > 0 )
painter . translate ( min ( icon_size ( ) + icon_padding ( ) , horizontal_scrollbar_value ) , 0 ) ;
2023-02-19 13:17:18 -03:00
auto gutter_ruler_width = fixed_elements_width ( ) ;
2023-02-19 13:14:16 -03:00
painter . translate ( gutter_ruler_width , 0 ) ;
2022-02-02 21:20:09 -03:00
Gfx : : IntRect text_clip_rect { 0 , 0 , widget_inner_rect ( ) . width ( ) - gutter_ruler_width , widget_inner_rect ( ) . height ( ) } ;
2021-04-12 15:47:09 -03:00
text_clip_rect . translate_by ( horizontal_scrollbar ( ) . value ( ) , vertical_scrollbar ( ) . value ( ) ) ;
2019-08-25 02:14:02 -03:00
painter . add_clip_rect ( text_clip_rect ) ;
2019-03-07 16:05:05 -03:00
2021-02-27 13:32:55 -03:00
size_t span_index = 0 ;
if ( document ( ) . has_spans ( ) ) {
for ( ; ; ) {
if ( span_index > = document ( ) . spans ( ) . size ( ) | | document ( ) . spans ( ) [ span_index ] . range . end ( ) . line ( ) > = first_visible_line ) {
break ;
}
+ + span_index ;
}
}
2019-12-09 13:45:40 -03:00
for ( size_t line_index = first_visible_line ; line_index < = last_visible_line ; + + line_index ) {
auto & line = this - > line ( line_index ) ;
2019-08-25 05:23:11 -03:00
bool physical_line_has_selection = has_selection & & line_index > = selection . start ( ) . line ( ) & & line_index < = selection . end ( ) . line ( ) ;
2019-12-09 13:45:40 -03:00
size_t first_visual_line_with_selection = 0 ;
size_t last_visual_line_with_selection = 0 ;
2019-08-25 05:23:11 -03:00
if ( physical_line_has_selection ) {
2019-08-25 09:04:46 -03:00
if ( selection . start ( ) . line ( ) < line_index )
2019-08-25 05:23:11 -03:00
first_visual_line_with_selection = 0 ;
2019-08-25 09:04:46 -03:00
else
2019-10-27 14:00:07 -03:00
first_visual_line_with_selection = visual_line_containing ( line_index , selection . start ( ) . column ( ) ) ;
2019-08-25 09:04:46 -03:00
if ( selection . end ( ) . line ( ) > line_index )
2023-03-06 13:16:25 -03:00
last_visual_line_with_selection = m_line_visual_data [ line_index ] - > visual_lines . size ( ) ;
2019-08-25 09:04:46 -03:00
else
2019-10-27 14:00:07 -03:00
last_visual_line_with_selection = visual_line_containing ( line_index , selection . end ( ) . column ( ) ) ;
2019-08-25 05:23:11 -03:00
}
2019-12-09 13:45:40 -03:00
size_t selection_start_column_within_line = selection . start ( ) . line ( ) = = line_index ? selection . start ( ) . column ( ) : 0 ;
size_t selection_end_column_within_line = selection . end ( ) . line ( ) = = line_index ? selection . end ( ) . column ( ) : line . length ( ) ;
2019-08-25 05:23:11 -03:00
2019-12-09 13:45:40 -03:00
size_t visual_line_index = 0 ;
2022-02-10 14:22:44 -03:00
size_t last_start_of_visual_line = 0 ;
Optional < size_t > multiline_trailing_space_offset { } ;
2021-07-09 08:34:20 -03:00
for_each_visual_line ( line_index , [ & ] ( Gfx : : IntRect const & visual_line_rect , auto & visual_line_text , size_t start_of_visual_line , [[maybe_unused]] bool is_last_visual_line ) {
2022-02-10 14:22:44 -03:00
ScopeGuard update_last_start_of_visual_line { [ & ] ( ) { last_start_of_visual_line = start_of_visual_line ; } } ;
2022-02-02 21:28:58 -03:00
if ( is_focused ( ) & & is_multi_line ( ) & & line_index = = m_cursor . line ( ) & & is_cursor_line_highlighted ( ) ) {
Gfx : : IntRect visible_content_line_rect {
visible_content_rect ( ) . x ( ) ,
visual_line_rect . y ( ) ,
widget_inner_rect ( ) . width ( ) - gutter_ruler_width ,
line_height ( )
} ;
painter . fill_rect ( visible_content_line_rect , widget_background_color . darkened ( 0.9f ) ) ;
}
2021-05-01 16:10:08 -03:00
if constexpr ( TEXTEDITOR_DEBUG )
painter . draw_rect ( visual_line_rect , Color : : Cyan ) ;
2020-09-20 16:20:24 -03:00
2021-07-06 11:44:44 -03:00
if ( ! placeholder ( ) . is_empty ( ) & & document ( ) . is_empty ( ) & & line_index = = 0 ) {
2020-09-20 16:20:24 -03:00
auto line_rect = visual_line_rect ;
2021-06-25 12:48:51 -03:00
line_rect . set_width ( text_width_for_font ( placeholder ( ) , font ( ) ) ) ;
2022-01-20 15:59:44 -03:00
draw_text ( line_rect , placeholder ( ) , font ( ) , m_text_alignment , { palette ( ) . color ( Gfx : : ColorRole : : PlaceholderText ) } , false ) ;
2020-09-20 16:20:24 -03:00
} else if ( ! document ( ) . has_spans ( ) ) {
2019-10-25 16:05:06 -03:00
// Fast-path for plain text
2023-02-21 12:10:37 -03:00
draw_text ( visual_line_rect , visual_line_text , font ( ) , m_text_alignment , unspanned_text_attributes ) ;
2019-10-25 16:05:06 -03:00
} else {
2021-02-27 13:32:55 -03:00
size_t next_column = 0 ;
2023-03-03 16:40:56 -03:00
Gfx : : FloatRect span_rect = { visual_line_rect . location ( ) , { 0 , line_height ( ) } } ;
2021-02-27 13:32:55 -03:00
2023-02-21 12:10:37 -03:00
auto draw_text_helper = [ & ] ( size_t start , size_t end , Gfx : : Font const & font , Gfx : : TextAttributes text_attributes ) {
2021-06-03 18:09:35 -03:00
size_t length = end - start ;
if ( length = = 0 )
return ;
2021-02-27 13:32:55 -03:00
auto text = visual_line_text . substring_view ( start , length ) ;
2023-02-21 12:10:37 -03:00
span_rect . set_width ( font . width ( text ) + font . glyph_spacing ( ) ) ;
2022-01-20 15:59:44 -03:00
if ( text_attributes . background_color . has_value ( ) ) {
2023-03-03 16:40:56 -03:00
painter . fill_rect ( span_rect . to_type < int > ( ) , text_attributes . background_color . value ( ) ) ;
2021-02-27 13:32:55 -03:00
}
2023-02-21 12:10:37 -03:00
draw_text ( span_rect , text , font , m_text_alignment , text_attributes ) ;
2022-01-23 19:01:23 -03:00
span_rect . translate_by ( span_rect . width ( ) , 0 ) ;
2021-02-27 13:32:55 -03:00
} ;
2023-02-23 12:31:04 -03:00
bool started_new_folded_region = false ;
2023-02-20 13:09:35 -03:00
while ( span_index < document ( ) . spans ( ) . size ( ) ) {
2021-02-27 13:32:55 -03:00
auto & span = document ( ) . spans ( ) [ span_index ] ;
2023-02-23 12:31:04 -03:00
// Skip spans that have ended before this point.
// That is, for spans that are for lines inside a folded region.
if ( ( span . range . end ( ) . line ( ) < line_index )
| | ( span . range . end ( ) . line ( ) = = line_index & & span . range . end ( ) . column ( ) < = start_of_visual_line ) ) {
2023-02-21 09:41:15 -03:00
+ + span_index ;
continue ;
}
2021-02-27 17:06:46 -03:00
if ( span . range . start ( ) . line ( ) > line_index
| | ( span . range . start ( ) . line ( ) = = line_index & & span . range . start ( ) . column ( ) > = start_of_visual_line + visual_line_text . length ( ) ) ) {
2021-02-27 13:32:55 -03:00
// no more spans in this line, moving on
break ;
2020-03-12 11:36:25 -03:00
}
2021-02-27 13:32:55 -03:00
size_t span_start ;
2021-02-27 17:06:46 -03:00
if ( span . range . start ( ) . line ( ) < line_index | | span . range . start ( ) . column ( ) < start_of_visual_line ) {
2021-02-27 13:32:55 -03:00
span_start = 0 ;
} else {
2021-02-27 17:06:46 -03:00
span_start = span . range . start ( ) . column ( ) - start_of_visual_line ;
2021-02-27 13:32:55 -03:00
}
size_t span_end ;
2023-02-09 08:46:49 -03:00
bool span_consumed ;
2021-06-03 18:09:35 -03:00
if ( span . range . end ( ) . line ( ) > line_index | | span . range . end ( ) . column ( ) > start_of_visual_line + visual_line_text . length ( ) ) {
span_end = visual_line_text . length ( ) ;
2023-02-09 08:46:49 -03:00
span_consumed = false ;
2021-02-27 13:32:55 -03:00
} else {
2021-02-27 17:06:46 -03:00
span_end = span . range . end ( ) . column ( ) - start_of_visual_line ;
2023-02-09 08:46:49 -03:00
span_consumed = true ;
2021-02-27 13:32:55 -03:00
}
2021-06-03 18:09:35 -03:00
2021-02-27 13:32:55 -03:00
if ( span_start ! = next_column ) {
// draw unspanned text between spans
2023-02-21 12:10:37 -03:00
draw_text_helper ( next_column , span_start , font ( ) , unspanned_text_attributes ) ;
2021-02-27 13:32:55 -03:00
}
2023-02-21 12:10:37 -03:00
auto & span_font = span . attributes . bold ? font ( ) . bold_variant ( ) : font ( ) ;
draw_text_helper ( span_start , span_end , span_font , span . attributes ) ;
2021-06-03 18:09:35 -03:00
next_column = span_end ;
2023-02-09 08:46:49 -03:00
if ( ! span_consumed ) {
2021-02-27 13:32:55 -03:00
// continue with same span on next line
break ;
} else {
+ + span_index ;
}
}
// draw unspanned text after last span
2023-02-23 12:31:04 -03:00
if ( ! started_new_folded_region & & next_column < visual_line_text . length ( ) ) {
2023-02-21 12:10:37 -03:00
draw_text_helper ( next_column , visual_line_text . length ( ) , font ( ) , unspanned_text_attributes ) ;
2019-10-25 16:05:06 -03:00
}
2023-02-23 12:31:04 -03:00
// Paint "..." at the end of the line if it starts a folded region.
// FIXME: This doesn't wrap.
if ( is_last_visual_line ) {
if ( auto folded_region = document ( ) . folding_region_starting_on_line ( line_index ) ; folded_region . has_value ( ) & & folded_region - > is_folded ) {
span_rect . set_width ( folded_region_summary_font . width ( folded_region_summary_text ) ) ;
draw_text ( span_rect , folded_region_summary_text , folded_region_summary_font , m_text_alignment , folded_region_summary_attributes ) ;
}
}
2019-10-25 16:05:06 -03:00
}
2020-05-18 11:38:28 -03:00
2020-09-01 14:10:55 -03:00
if ( m_visualize_trailing_whitespace & & line . ends_in_whitespace ( ) ) {
size_t physical_column ;
auto last_non_whitespace_column = line . last_non_whitespace_column ( ) ;
if ( last_non_whitespace_column . has_value ( ) )
physical_column = last_non_whitespace_column . value ( ) + 1 ;
else
physical_column = 0 ;
size_t end_of_visual_line = ( start_of_visual_line + visual_line_text . length ( ) ) ;
if ( physical_column < end_of_visual_line ) {
2022-02-10 14:22:44 -03:00
physical_column - = multiline_trailing_space_offset . value_or ( 0 ) ;
2020-09-01 14:10:55 -03:00
size_t visual_column = physical_column > start_of_visual_line ? ( physical_column - start_of_visual_line ) : 0 ;
2022-02-10 14:22:44 -03:00
2020-09-01 14:10:55 -03:00
Gfx : : IntRect whitespace_rect {
2022-02-10 13:40:16 -03:00
content_x_for_position ( { line_index , physical_column } ) ,
2020-09-01 14:10:55 -03:00
visual_line_rect . y ( ) ,
2021-06-25 12:48:51 -03:00
text_width_for_font ( visual_line_text . substring_view ( visual_column , visual_line_text . length ( ) - visual_column ) , font ( ) ) ,
2020-09-01 14:10:55 -03:00
visual_line_rect . height ( )
} ;
painter . fill_rect_with_dither_pattern ( whitespace_rect , Color ( ) , Color ( 255 , 192 , 192 ) ) ;
2022-02-10 14:22:44 -03:00
if ( ! multiline_trailing_space_offset . has_value ( ) )
multiline_trailing_space_offset = physical_column - last_start_of_visual_line ;
2020-09-01 14:10:55 -03:00
}
}
2021-03-17 13:52:42 -03:00
if ( m_visualize_leading_whitespace & & line . leading_spaces ( ) > 0 ) {
size_t physical_column = line . leading_spaces ( ) ;
2021-12-27 08:31:05 -03:00
if ( start_of_visual_line < physical_column ) {
size_t end_of_leading_whitespace = min ( physical_column - start_of_visual_line , visual_line_text . length ( ) ) ;
2021-03-17 13:52:42 -03:00
Gfx : : IntRect whitespace_rect {
content_x_for_position ( { line_index , start_of_visual_line } ) ,
visual_line_rect . y ( ) ,
2021-06-25 12:48:51 -03:00
text_width_for_font ( visual_line_text . substring_view ( 0 , end_of_leading_whitespace ) , font ( ) ) ,
2021-03-17 13:52:42 -03:00
visual_line_rect . height ( )
} ;
painter . fill_rect_with_dither_pattern ( whitespace_rect , Color ( ) , Color ( 192 , 255 , 192 ) ) ;
}
}
2021-10-21 14:38:59 -03:00
if ( physical_line_has_selection & & window ( ) - > focused_widget ( ) = = this ) {
2022-04-24 13:26:34 -03:00
size_t const start_of_selection_within_visual_line = ( size_t ) max ( 0 , ( int ) selection_start_column_within_line - ( int ) start_of_visual_line ) ;
size_t const end_of_selection_within_visual_line = min ( selection_end_column_within_line - start_of_visual_line , visual_line_text . length ( ) ) ;
2019-08-25 05:23:11 -03:00
2020-04-24 15:01:57 -03:00
bool current_visual_line_has_selection = start_of_selection_within_visual_line ! = end_of_selection_within_visual_line
& & ( ( line_index ! = selection . start ( ) . line ( ) & & line_index ! = selection . end ( ) . line ( ) )
| | ( visual_line_index > = first_visual_line_with_selection & & visual_line_index < = last_visual_line_with_selection ) ) ;
2019-08-25 05:23:11 -03:00
if ( current_visual_line_has_selection ) {
bool selection_begins_on_current_visual_line = visual_line_index = = first_visual_line_with_selection ;
bool selection_ends_on_current_visual_line = visual_line_index = = last_visual_line_with_selection ;
int selection_left = selection_begins_on_current_visual_line
2019-12-17 17:15:10 -03:00
? content_x_for_position ( { line_index , ( size_t ) selection_start_column_within_line } )
2019-08-25 05:23:11 -03:00
: m_horizontal_content_padding ;
int selection_right = selection_ends_on_current_visual_line
2019-12-17 17:15:10 -03:00
? content_x_for_position ( { line_index , ( size_t ) selection_end_column_within_line } )
2019-09-01 12:30:23 -03:00
: visual_line_rect . right ( ) + 1 ;
2019-08-25 05:23:11 -03:00
2020-06-10 05:57:59 -03:00
Gfx : : IntRect selection_rect {
2019-08-25 05:23:11 -03:00
selection_left ,
visual_line_rect . y ( ) ,
selection_right - selection_left ,
visual_line_rect . height ( )
} ;
2022-11-17 11:29:54 -03:00
Color background_color = is_focused ( ) ? palette ( ) . selection ( ) : palette ( ) . inactive_selection ( ) ;
Color text_color = is_focused ( ) ? palette ( ) . selection_text ( ) : palette ( ) . inactive_selection_text ( ) ;
2020-02-15 12:26:52 -03:00
painter . fill_rect ( selection_rect , background_color ) ;
2019-08-25 05:23:11 -03:00
2020-08-05 17:31:20 -03:00
if ( visual_line_text . code_points ( ) ) {
2020-05-29 15:10:36 -03:00
Utf32View visual_selected_text {
2020-08-05 17:31:20 -03:00
visual_line_text . code_points ( ) + start_of_selection_within_visual_line ,
2020-05-29 15:10:36 -03:00
end_of_selection_within_visual_line - start_of_selection_within_visual_line
} ;
2019-08-25 05:23:11 -03:00
2022-01-20 15:59:44 -03:00
draw_text ( selection_rect , visual_selected_text , font ( ) , Gfx : : TextAlignment : : CenterLeft , { text_color } ) ;
2020-05-29 15:10:36 -03:00
}
2019-08-25 05:23:11 -03:00
}
2019-08-25 03:43:01 -03:00
}
2020-09-01 14:10:55 -03:00
2019-08-25 05:23:11 -03:00
+ + visual_line_index ;
2019-08-25 03:43:01 -03:00
return IterationDecision : : Continue ;
} ) ;
2019-03-06 20:31:06 -03:00
}
2022-11-17 11:29:54 -03:00
if ( is_enabled ( ) & & is_focused ( ) & & ! focus_preempted ( ) & & m_cursor_state & & ! is_displayonly ( ) )
2020-02-20 05:01:48 -03:00
painter . fill_rect ( cursor_content_rect ( ) , palette ( ) . text_cursor ( ) ) ;
2019-03-06 20:31:06 -03:00
}
2022-02-22 16:25:30 -03:00
Optional < UISize > TextEditor : : calculated_min_size ( ) const
{
2022-09-22 20:00:01 -03:00
if ( is_multi_line ( ) )
return AbstractScrollableWidget : : calculated_min_size ( ) ;
auto m = content_margins ( ) ;
return UISize { m . left ( ) + m . right ( ) , m . top ( ) + m . bottom ( ) } ;
2022-02-22 16:25:30 -03:00
}
2020-02-02 11:07:41 -03:00
void TextEditor : : select_all ( )
2019-06-22 05:38:38 -03:00
{
2020-02-02 11:07:41 -03:00
TextPosition start_of_document { 0 , 0 } ;
TextPosition end_of_document { line_count ( ) - 1 , line ( line_count ( ) - 1 ) . length ( ) } ;
2020-05-27 13:41:54 -03:00
m_selection . set ( end_of_document , start_of_document ) ;
2019-06-22 05:38:38 -03:00
did_update_selection ( ) ;
2020-05-27 13:41:54 -03:00
set_cursor ( start_of_document ) ;
2019-06-22 05:38:38 -03:00
update ( ) ;
}
2022-09-08 19:06:08 -03:00
void TextEditor : : insert_emoji ( )
{
2022-09-09 09:47:24 -03:00
if ( ! on_emoji_input | | window ( ) - > blocks_emoji_input ( ) )
2022-09-09 08:21:31 -03:00
return ;
2022-09-08 19:06:08 -03:00
auto emoji_input_dialog = EmojiInputDialog : : construct ( window ( ) ) ;
if ( emoji_input_dialog - > exec ( ) ! = EmojiInputDialog : : ExecResult : : OK )
return ;
auto emoji_code_point = emoji_input_dialog - > selected_emoji_text ( ) ;
insert_at_cursor_or_replace_selection ( emoji_code_point ) ;
}
2022-09-09 09:47:24 -03:00
void TextEditor : : set_or_clear_emoji_input_callback ( )
{
switch ( m_mode ) {
case Editable :
on_emoji_input = [ this ] ( auto emoji ) {
insert_at_cursor_or_replace_selection ( emoji ) ;
} ;
break ;
case DisplayOnly :
case ReadOnly :
on_emoji_input = { } ;
break ;
default :
VERIFY_NOT_REACHED ( ) ;
}
}
2020-02-02 11:07:41 -03:00
void TextEditor : : keydown_event ( KeyEvent & event )
2019-03-06 20:31:06 -03:00
{
2022-07-31 11:51:50 -03:00
if ( ! is_editable ( ) & & event . key ( ) = = KeyCode : : Key_Tab )
return AbstractScrollableWidget : : keydown_event ( event ) ;
2020-12-30 07:25:06 -03:00
if ( m_autocomplete_box & & m_autocomplete_box - > is_visible ( ) & & ( event . key ( ) = = KeyCode : : Key_Return | | event . key ( ) = = KeyCode : : Key_Tab ) ) {
2021-10-28 22:31:54 -03:00
TemporaryChange change { m_should_keep_autocomplete_box , true } ;
2022-05-14 11:09:24 -03:00
if ( m_autocomplete_box - > apply_suggestion ( ) = = CodeComprehension : : AutocompleteResultEntry : : HideAutocompleteAfterApplying : : Yes )
2021-10-28 22:31:54 -03:00
hide_autocomplete ( ) ;
else
try_update_autocomplete ( ) ;
2020-12-30 07:25:06 -03:00
return ;
}
if ( m_autocomplete_box & & m_autocomplete_box - > is_visible ( ) & & event . key ( ) = = KeyCode : : Key_Escape ) {
2021-10-28 22:23:58 -03:00
hide_autocomplete ( ) ;
2020-12-30 07:25:06 -03:00
return ;
}
if ( m_autocomplete_box & & m_autocomplete_box - > is_visible ( ) & & event . key ( ) = = KeyCode : : Key_Up ) {
m_autocomplete_box - > previous_suggestion ( ) ;
return ;
}
if ( m_autocomplete_box & & m_autocomplete_box - > is_visible ( ) & & event . key ( ) = = KeyCode : : Key_Down ) {
m_autocomplete_box - > next_suggestion ( ) ;
return ;
}
2021-01-02 07:59:55 -03:00
if ( is_single_line ( ) ) {
if ( event . key ( ) = = KeyCode : : Key_Tab )
2021-05-03 15:31:58 -03:00
return AbstractScrollableWidget : : keydown_event ( event ) ;
2019-06-23 02:53:58 -03:00
2021-06-02 08:21:17 -03:00
if ( event . modifiers ( ) = = KeyModifier : : Mod_Shift & & event . key ( ) = = KeyCode : : Key_Return ) {
if ( on_shift_return_pressed )
on_shift_return_pressed ( ) ;
return ;
}
2022-03-19 08:09:44 -03:00
if ( event . modifiers ( ) = = KeyModifier : : Mod_Ctrl & & event . key ( ) = = KeyCode : : Key_Return ) {
if ( on_ctrl_return_pressed )
on_ctrl_return_pressed ( ) ;
return ;
}
2021-06-02 08:21:17 -03:00
2021-01-02 07:59:55 -03:00
if ( event . key ( ) = = KeyCode : : Key_Return ) {
if ( on_return_pressed )
on_return_pressed ( ) ;
return ;
2020-12-30 07:25:06 -03:00
}
2021-01-02 07:59:55 -03:00
if ( event . key ( ) = = KeyCode : : Key_Up ) {
if ( on_up_pressed )
on_up_pressed ( ) ;
return ;
2019-03-07 13:11:17 -03:00
}
2021-01-02 07:59:55 -03:00
if ( event . key ( ) = = KeyCode : : Key_Down ) {
if ( on_down_pressed )
on_down_pressed ( ) ;
return ;
2019-03-07 13:11:17 -03:00
}
2021-01-02 07:59:55 -03:00
if ( event . key ( ) = = KeyCode : : Key_PageUp ) {
if ( on_pageup_pressed )
on_pageup_pressed ( ) ;
return ;
2020-10-08 15:00:40 -03:00
}
2021-01-02 07:59:55 -03:00
if ( event . key ( ) = = KeyCode : : Key_PageDown ) {
if ( on_pagedown_pressed )
on_pagedown_pressed ( ) ;
2019-11-15 16:17:49 -03:00
return ;
}
2021-01-02 07:59:55 -03:00
2021-04-07 02:04:31 -03:00
} else if ( ! is_multi_line ( ) ) {
VERIFY_NOT_REACHED ( ) ;
}
2021-01-02 07:59:55 -03:00
2021-04-07 02:04:31 -03:00
ArmedScopeGuard update_autocomplete { [ & ] {
2021-10-28 04:04:33 -03:00
try_update_autocomplete ( ) ;
2021-04-07 02:04:31 -03:00
} } ;
2022-12-02 05:14:57 -03:00
if ( is_multi_line ( ) & & ! event . alt ( ) & & event . ctrl ( ) & & event . key ( ) = = KeyCode : : Key_Return ) {
if ( ! is_editable ( ) )
return ;
size_t indent_length = current_line ( ) . leading_spaces ( ) ;
DeprecatedString indent = current_line ( ) . to_utf8 ( ) . substring ( 0 , indent_length ) ;
auto insert_pos = event . shift ( ) ? InsertLineCommand : : InsertPosition : : Above : InsertLineCommand : : InsertPosition : : Below ;
execute < InsertLineCommand > ( m_cursor , move ( indent ) , insert_pos ) ;
return ;
}
2021-04-07 02:04:31 -03:00
if ( is_multi_line ( ) & & ! event . shift ( ) & & ! event . alt ( ) & & event . ctrl ( ) & & event . key ( ) = = KeyCode : : Key_Space ) {
if ( m_autocomplete_provider ) {
2021-10-28 01:13:49 -03:00
try_show_autocomplete ( UserRequestedAutocomplete : : Yes ) ;
2021-04-07 02:04:31 -03:00
update_autocomplete . disarm ( ) ;
return ;
2020-10-08 15:00:40 -03:00
}
2019-03-07 09:21:51 -03:00
}
2021-01-02 07:59:55 -03:00
2022-11-28 20:09:46 -03:00
if ( is_multi_line ( ) & & ! event . shift ( ) & & ! event . alt ( ) & & event . ctrl ( ) & & event . key ( ) = = KeyCode : : Key_F ) {
m_search_banner - > show ( ) ;
return ;
}
2021-01-02 07:59:55 -03:00
if ( m_editing_engine - > on_key ( event ) )
2019-03-07 09:21:51 -03:00
return ;
2021-01-02 07:59:55 -03:00
if ( event . key ( ) = = KeyCode : : Key_Escape ) {
if ( on_escape_pressed )
on_escape_pressed ( ) ;
2019-03-07 09:21:51 -03:00
return ;
}
2021-01-02 07:59:55 -03:00
if ( event . modifiers ( ) = = Mod_Shift & & event . key ( ) = = KeyCode : : Key_Delete ) {
if ( m_autocomplete_box )
2021-10-28 22:23:58 -03:00
hide_autocomplete ( ) ;
2019-03-07 09:21:51 -03:00
return ;
}
2021-01-02 07:59:55 -03:00
2022-06-18 16:02:51 -03:00
if ( event . key ( ) = = KeyCode : : Key_Tab ) {
if ( has_selection ( ) ) {
2022-06-18 16:03:49 -03:00
if ( event . modifiers ( ) = = Mod_Shift ) {
unindent_selection ( ) ;
return ;
}
2022-06-18 16:02:51 -03:00
if ( is_indenting_selection ( ) ) {
indent_selection ( ) ;
return ;
}
2022-10-28 09:42:32 -03:00
} else {
if ( event . modifiers ( ) = = Mod_Shift ) {
unindent_line ( ) ;
return ;
}
2022-06-18 16:02:51 -03:00
}
}
2021-01-02 07:59:55 -03:00
if ( event . key ( ) = = KeyCode : : Key_Delete ) {
2021-08-20 14:56:43 -03:00
if ( ! is_editable ( ) )
return ;
2021-01-02 07:59:55 -03:00
if ( m_autocomplete_box )
2021-10-28 22:23:58 -03:00
hide_autocomplete ( ) ;
2021-08-20 14:56:43 -03:00
if ( has_selection ( ) ) {
delete_selection ( ) ;
did_update_selection ( ) ;
return ;
}
if ( m_cursor . column ( ) < current_line ( ) . length ( ) ) {
// Delete within line
2021-08-20 15:08:34 -03:00
int erase_count = 1 ;
if ( event . modifiers ( ) = = Mod_Ctrl ) {
auto word_break_pos = document ( ) . first_word_break_after ( m_cursor ) ;
erase_count = word_break_pos . column ( ) - m_cursor . column ( ) ;
2023-02-15 10:49:01 -03:00
} else {
auto grapheme_break_position = document ( ) . get_next_grapheme_cluster_boundary ( m_cursor ) ;
erase_count = grapheme_break_position - m_cursor . column ( ) ;
2021-08-20 15:08:34 -03:00
}
TextRange erased_range ( m_cursor , { m_cursor . line ( ) , m_cursor . column ( ) + erase_count } ) ;
2021-08-20 14:56:43 -03:00
execute < RemoveTextCommand > ( document ( ) . text_in_range ( erased_range ) , erased_range ) ;
return ;
}
if ( m_cursor . column ( ) = = current_line ( ) . length ( ) & & m_cursor . line ( ) ! = line_count ( ) - 1 ) {
// Delete at end of line; merge with next line
2021-08-20 15:08:34 -03:00
size_t erase_count = 0 ;
if ( event . modifiers ( ) = = Mod_Ctrl ) {
erase_count = document ( ) . first_word_break_after ( { m_cursor . line ( ) + 1 , 0 } ) . column ( ) ;
}
TextRange erased_range ( m_cursor , { m_cursor . line ( ) + 1 , erase_count } ) ;
2021-08-20 14:56:43 -03:00
execute < RemoveTextCommand > ( document ( ) . text_in_range ( erased_range ) , erased_range ) ;
return ;
}
2019-11-15 16:45:27 -03:00
return ;
}
2021-01-02 07:59:55 -03:00
2019-03-08 14:50:14 -03:00
if ( event . key ( ) = = KeyCode : : Key_Backspace ) {
2020-07-15 18:04:49 -03:00
if ( ! is_editable ( ) )
2019-05-08 00:00:28 -03:00
return ;
2021-01-01 10:59:11 -03:00
if ( m_autocomplete_box )
2021-10-28 22:23:58 -03:00
hide_autocomplete ( ) ;
2019-03-08 10:10:34 -03:00
if ( has_selection ( ) ) {
delete_selection ( ) ;
2019-04-11 21:52:34 -03:00
did_update_selection ( ) ;
2019-03-08 10:10:34 -03:00
return ;
}
2019-03-07 12:04:21 -03:00
if ( m_cursor . column ( ) > 0 ) {
2019-10-27 06:42:48 -03:00
int erase_count = 1 ;
2020-07-22 02:28:59 -03:00
if ( event . modifiers ( ) = = Mod_Ctrl ) {
auto word_break_pos = document ( ) . first_word_break_before ( m_cursor , true ) ;
erase_count = m_cursor . column ( ) - word_break_pos . column ( ) ;
} else if ( current_line ( ) . first_non_whitespace_column ( ) > = m_cursor . column ( ) ) {
2019-10-27 06:42:48 -03:00
int new_column ;
if ( m_cursor . column ( ) % m_soft_tab_width = = 0 )
new_column = m_cursor . column ( ) - m_soft_tab_width ;
else
new_column = ( m_cursor . column ( ) / m_soft_tab_width ) * m_soft_tab_width ;
erase_count = m_cursor . column ( ) - new_column ;
2023-02-15 10:49:01 -03:00
} else {
auto grapheme_break_position = document ( ) . get_previous_grapheme_cluster_boundary ( m_cursor ) ;
erase_count = m_cursor . column ( ) - grapheme_break_position ;
2019-10-27 06:42:48 -03:00
}
2019-03-07 12:15:25 -03:00
// Backspace within line
2020-02-02 11:07:41 -03:00
TextRange erased_range ( { m_cursor . line ( ) , m_cursor . column ( ) - erase_count } , m_cursor ) ;
2019-11-30 12:50:24 -03:00
auto erased_text = document ( ) . text_in_range ( erased_range ) ;
execute < RemoveTextCommand > ( erased_text , erased_range ) ;
2019-03-07 13:18:22 -03:00
return ;
2019-03-07 12:04:21 -03:00
}
2019-03-07 12:15:25 -03:00
if ( m_cursor . column ( ) = = 0 & & m_cursor . line ( ) ! = 0 ) {
2019-03-07 12:33:07 -03:00
// Backspace at column 0; merge with previous line
2019-12-09 13:45:40 -03:00
size_t previous_length = line ( m_cursor . line ( ) - 1 ) . length ( ) ;
2020-02-02 11:07:41 -03:00
TextRange erased_range ( { m_cursor . line ( ) - 1 , previous_length } , m_cursor ) ;
2019-11-30 12:50:24 -03:00
execute < RemoveTextCommand > ( " \n " , erased_range ) ;
2019-03-07 13:18:22 -03:00
return ;
2019-03-07 12:15:25 -03:00
}
2019-03-07 12:04:21 -03:00
return ;
}
2022-05-13 14:06:26 -03:00
// AltGr is emulated as Ctrl+Alt; if Ctrl is set check if it's not for AltGr
if ( ( ! event . ctrl ( ) | | event . altgr ( ) ) & & ! event . alt ( ) & & event . code_point ( ) ! = 0 ) {
2021-10-28 01:39:55 -03:00
TemporaryChange change { m_should_keep_autocomplete_box , true } ;
2021-01-02 07:59:55 -03:00
add_code_point ( event . code_point ( ) ) ;
2020-02-24 06:20:25 -03:00
return ;
}
2022-11-03 01:32:12 -03:00
if ( event . ctrl ( ) & & event . key ( ) = = KeyCode : : Key_Slash ) {
if ( m_highlighter ! = nullptr ) {
auto prefix = m_highlighter - > comment_prefix ( ) . value_or ( " " sv ) ;
auto suffix = m_highlighter - > comment_suffix ( ) . value_or ( " " sv ) ;
2022-12-01 08:47:59 -03:00
auto range = has_selection ( ) ? selection ( ) . normalized ( ) : TextRange { { m_cursor . line ( ) , m_cursor . column ( ) } , { m_cursor . line ( ) , m_cursor . column ( ) } } ;
2022-11-03 01:32:12 -03:00
auto is_already_commented = true ;
for ( size_t i = range . start ( ) . line ( ) ; i < = range . end ( ) . line ( ) ; i + + ) {
auto text = m_document - > line ( i ) . to_utf8 ( ) . trim_whitespace ( ) ;
if ( ! ( text . starts_with ( prefix ) & & text . ends_with ( suffix ) ) ) {
is_already_commented = false ;
break ;
}
}
if ( is_already_commented )
execute < UncommentSelection > ( prefix , suffix , range ) ;
else
execute < CommentSelection > ( prefix , suffix , range ) ;
return ;
}
}
2020-02-24 06:20:25 -03:00
event . ignore ( ) ;
2019-03-06 20:31:06 -03:00
}
2022-06-18 16:02:51 -03:00
bool TextEditor : : is_indenting_selection ( )
{
auto const selection_start = m_selection . start ( ) > m_selection . end ( ) ? m_selection . end ( ) : m_selection . start ( ) ;
auto const selection_end = m_selection . end ( ) > m_selection . start ( ) ? m_selection . end ( ) : m_selection . start ( ) ;
auto const whole_line_selected = selection_end . column ( ) - selection_start . column ( ) > = current_line ( ) . length ( ) - current_line ( ) . first_non_whitespace_column ( ) ;
auto const on_same_line = selection_start . line ( ) = = selection_end . line ( ) ;
if ( has_selection ( ) & & ( whole_line_selected | | ! on_same_line ) ) {
return true ;
}
return false ;
}
void TextEditor : : indent_selection ( )
{
auto const selection_start = m_selection . start ( ) > m_selection . end ( ) ? m_selection . end ( ) : m_selection . start ( ) ;
auto const selection_end = m_selection . end ( ) > m_selection . start ( ) ? m_selection . end ( ) : m_selection . start ( ) ;
if ( is_indenting_selection ( ) ) {
execute < IndentSelection > ( m_soft_tab_width , TextRange ( selection_start , selection_end ) ) ;
m_selection . set_start ( { selection_start . line ( ) , selection_start . column ( ) + m_soft_tab_width } ) ;
m_selection . set_end ( { selection_end . line ( ) , selection_end . column ( ) + m_soft_tab_width } ) ;
set_cursor ( { m_cursor . line ( ) , m_cursor . column ( ) + m_soft_tab_width } ) ;
}
}
2022-06-18 16:03:49 -03:00
void TextEditor : : unindent_selection ( )
{
auto const selection_start = m_selection . start ( ) > m_selection . end ( ) ? m_selection . end ( ) : m_selection . start ( ) ;
auto const selection_end = m_selection . end ( ) > m_selection . start ( ) ? m_selection . end ( ) : m_selection . start ( ) ;
if ( current_line ( ) . first_non_whitespace_column ( ) ! = 0 ) {
if ( current_line ( ) . first_non_whitespace_column ( ) > m_soft_tab_width & & selection_start . column ( ) ! = 0 ) {
m_selection . set_start ( { selection_start . line ( ) , selection_start . column ( ) - m_soft_tab_width } ) ;
m_selection . set_end ( { selection_end . line ( ) , selection_end . column ( ) - m_soft_tab_width } ) ;
} else if ( selection_start . column ( ) ! = 0 ) {
m_selection . set_start ( { selection_start . line ( ) , selection_start . column ( ) - current_line ( ) . leading_spaces ( ) } ) ;
m_selection . set_end ( { selection_end . line ( ) , selection_end . column ( ) - current_line ( ) . leading_spaces ( ) } ) ;
}
execute < UnindentSelection > ( m_soft_tab_width , TextRange ( selection_start , selection_end ) ) ;
}
}
2022-10-28 09:42:32 -03:00
void TextEditor : : unindent_line ( )
{
if ( current_line ( ) . first_non_whitespace_column ( ) ! = 0 ) {
auto const unindent_size = current_line ( ) . leading_spaces ( ) < m_soft_tab_width ? current_line ( ) . leading_spaces ( ) : m_soft_tab_width ;
auto const temp_column = m_cursor . column ( ) ;
execute < UnindentSelection > ( unindent_size , TextRange ( { m_cursor . line ( ) , 0 } , { m_cursor . line ( ) , line ( m_cursor . line ( ) ) . length ( ) } ) ) ;
set_cursor ( { m_cursor . line ( ) , temp_column < = unindent_size ? 0 : temp_column - unindent_size } ) ;
}
}
2021-07-14 00:12:07 -03:00
void TextEditor : : delete_previous_word ( )
{
TextRange to_erase ( document ( ) . first_word_before ( m_cursor , true ) , m_cursor ) ;
execute < RemoveTextCommand > ( document ( ) . text_in_range ( to_erase ) , to_erase ) ;
}
2020-02-02 11:07:41 -03:00
void TextEditor : : delete_current_line ( )
2019-03-25 09:13:46 -03:00
{
if ( has_selection ( ) )
return delete_selection ( ) ;
2020-02-02 11:07:41 -03:00
TextPosition start ;
TextPosition end ;
2019-12-09 13:45:40 -03:00
if ( m_cursor . line ( ) = = 0 & & line_count ( ) = = 1 ) {
2019-11-30 13:19:35 -03:00
start = { 0 , 0 } ;
end = { 0 , line ( 0 ) . length ( ) } ;
2019-12-09 13:45:40 -03:00
} else if ( m_cursor . line ( ) = = line_count ( ) - 1 ) {
2021-01-25 10:19:07 -03:00
start = { m_cursor . line ( ) - 1 , line ( m_cursor . line ( ) - 1 ) . length ( ) } ;
2019-11-30 13:19:35 -03:00
end = { m_cursor . line ( ) , line ( m_cursor . line ( ) ) . length ( ) } ;
} else {
start = { m_cursor . line ( ) , 0 } ;
end = { m_cursor . line ( ) + 1 , 0 } ;
}
2019-03-25 09:13:46 -03:00
2020-02-02 11:07:41 -03:00
TextRange erased_range ( start , end ) ;
2019-11-30 13:19:35 -03:00
execute < RemoveTextCommand > ( document ( ) . text_in_range ( erased_range ) , erased_range ) ;
2019-03-25 09:13:46 -03:00
}
2021-07-15 17:50:03 -03:00
void TextEditor : : delete_previous_char ( )
{
if ( ! is_editable ( ) )
return ;
if ( has_selection ( ) )
return delete_selection ( ) ;
TextRange to_erase ( { m_cursor . line ( ) , m_cursor . column ( ) - 1 } , m_cursor ) ;
if ( m_cursor . column ( ) = = 0 & & m_cursor . line ( ) ! = 0 ) {
size_t prev_line_len = line ( m_cursor . line ( ) - 1 ) . length ( ) ;
to_erase . set_start ( { m_cursor . line ( ) - 1 , prev_line_len } ) ;
}
execute < RemoveTextCommand > ( document ( ) . text_in_range ( to_erase ) , to_erase ) ;
}
2021-07-15 18:35:45 -03:00
void TextEditor : : delete_from_line_start_to_cursor ( )
{
TextPosition start ( m_cursor . line ( ) , current_line ( ) . first_non_whitespace_column ( ) ) ;
TextRange to_erase ( start , m_cursor ) ;
execute < RemoveTextCommand > ( document ( ) . text_in_range ( to_erase ) , to_erase ) ;
}
2020-02-02 11:07:41 -03:00
void TextEditor : : do_delete ( )
2019-03-20 19:11:00 -03:00
{
2020-07-15 18:04:49 -03:00
if ( ! is_editable ( ) )
2019-05-08 00:00:28 -03:00
return ;
2019-03-25 09:13:46 -03:00
if ( has_selection ( ) )
return delete_selection ( ) ;
2019-03-20 19:11:00 -03:00
if ( m_cursor . column ( ) < current_line ( ) . length ( ) ) {
// Delete within line
2020-02-02 11:07:41 -03:00
TextRange erased_range ( m_cursor , { m_cursor . line ( ) , m_cursor . column ( ) + 1 } ) ;
2019-11-30 12:58:07 -03:00
execute < RemoveTextCommand > ( document ( ) . text_in_range ( erased_range ) , erased_range ) ;
2019-03-20 19:11:00 -03:00
return ;
}
if ( m_cursor . column ( ) = = current_line ( ) . length ( ) & & m_cursor . line ( ) ! = line_count ( ) - 1 ) {
// Delete at end of line; merge with next line
2020-02-02 11:07:41 -03:00
TextRange erased_range ( m_cursor , { m_cursor . line ( ) + 1 , 0 } ) ;
2019-11-30 12:58:07 -03:00
execute < RemoveTextCommand > ( document ( ) . text_in_range ( erased_range ) , erased_range ) ;
2019-03-20 19:11:00 -03:00
return ;
}
}
2021-01-02 07:59:55 -03:00
void TextEditor : : add_code_point ( u32 code_point )
{
if ( ! is_editable ( ) )
return ;
StringBuilder sb ;
sb . append_code_point ( code_point ) ;
if ( should_autocomplete_automatically ( ) ) {
if ( sb . string_view ( ) . is_whitespace ( ) )
m_autocomplete_timer - > stop ( ) ;
else
m_autocomplete_timer - > start ( ) ;
}
2022-12-05 22:12:49 -03:00
insert_at_cursor_or_replace_selection ( sb . to_deprecated_string ( ) ) ;
2021-01-02 07:59:55 -03:00
} ;
void TextEditor : : reset_cursor_blink ( )
{
m_cursor_state = true ;
update_cursor ( ) ;
stop_timer ( ) ;
start_timer ( 500 ) ;
}
2021-04-26 17:29:05 -03:00
void TextEditor : : update_selection ( bool is_selecting )
2021-01-02 07:59:55 -03:00
{
2021-10-11 17:51:40 -03:00
if ( is_selecting & & ! selection ( ) . is_valid ( ) ) {
selection ( ) . set ( cursor ( ) , { } ) ;
2021-01-02 07:59:55 -03:00
did_update_selection ( ) ;
update ( ) ;
return ;
}
2021-10-11 17:51:40 -03:00
if ( ! is_selecting & & selection ( ) . is_valid ( ) ) {
selection ( ) . clear ( ) ;
2021-01-02 07:59:55 -03:00
did_update_selection ( ) ;
update ( ) ;
return ;
}
2021-10-11 17:51:40 -03:00
if ( is_selecting & & selection ( ) . start ( ) . is_valid ( ) ) {
selection ( ) . set_end ( cursor ( ) ) ;
2021-01-02 07:59:55 -03:00
did_update_selection ( ) ;
update ( ) ;
return ;
}
}
2021-07-09 08:34:20 -03:00
int TextEditor : : content_x_for_position ( TextPosition const & position ) const
2019-04-24 17:24:16 -03:00
{
2019-12-09 13:45:40 -03:00
auto & line = this - > line ( position . line ( ) ) ;
2020-05-18 11:38:28 -03:00
int x_offset = 0 ;
2019-04-24 17:24:16 -03:00
switch ( m_text_alignment ) {
2020-02-06 07:56:38 -03:00
case Gfx : : TextAlignment : : CenterLeft :
2021-07-09 08:34:20 -03:00
for_each_visual_line ( position . line ( ) , [ & ] ( Gfx : : IntRect const & , auto & visual_line_view , size_t start_of_visual_line , bool is_last_visual_line ) {
2020-05-18 11:38:28 -03:00
size_t offset_in_visual_line = position . column ( ) - start_of_visual_line ;
2021-02-22 22:34:26 -03:00
auto before_line_end = is_last_visual_line ? ( offset_in_visual_line < = visual_line_view . length ( ) ) : ( offset_in_visual_line < visual_line_view . length ( ) ) ;
if ( position . column ( ) > = start_of_visual_line & & before_line_end ) {
2020-05-18 11:38:28 -03:00
if ( offset_in_visual_line = = 0 ) {
x_offset = 0 ;
} else {
2021-06-25 12:48:51 -03:00
x_offset = text_width_for_font ( visual_line_view . substring_view ( 0 , offset_in_visual_line ) , font ( ) ) ;
2020-05-18 11:38:28 -03:00
x_offset + = font ( ) . glyph_spacing ( ) ;
}
2019-08-25 04:06:54 -03:00
return IterationDecision : : Break ;
}
return IterationDecision : : Continue ;
} ) ;
2020-06-29 15:34:42 -03:00
return m_horizontal_content_padding + ( ( is_single_line ( ) & & icon ( ) ) ? ( icon_size ( ) + icon_padding ( ) ) : 0 ) + x_offset ;
2020-02-06 07:56:38 -03:00
case Gfx : : TextAlignment : : CenterRight :
2019-08-25 04:06:54 -03:00
// FIXME
2021-02-23 16:42:32 -03:00
VERIFY ( ! is_wrapping_enabled ( ) ) ;
2020-05-18 11:38:28 -03:00
return content_width ( ) - m_horizontal_content_padding - ( line . length ( ) * fixed_glyph_width ( ) ) + ( position . column ( ) * fixed_glyph_width ( ) ) ;
2019-04-24 17:24:16 -03:00
default :
2021-02-23 16:42:32 -03:00
VERIFY_NOT_REACHED ( ) ;
2019-04-24 17:24:16 -03:00
}
}
2021-06-25 12:48:51 -03:00
int TextEditor : : text_width_for_font ( auto const & text , Gfx : : Font const & font ) const
{
2022-05-07 11:03:33 -03:00
if ( m_substitution_code_point . has_value ( ) )
2021-06-25 12:48:51 -03:00
return font . width ( substitution_code_point_view ( text . length ( ) ) ) ;
else
return font . width ( text ) ;
}
Utf32View TextEditor : : substitution_code_point_view ( size_t length ) const
{
2022-05-07 11:03:33 -03:00
VERIFY ( m_substitution_code_point . has_value ( ) ) ;
2021-06-25 12:48:51 -03:00
if ( ! m_substitution_string_data )
m_substitution_string_data = make < Vector < u32 > > ( ) ;
if ( ! m_substitution_string_data - > is_empty ( ) )
VERIFY ( m_substitution_string_data - > first ( ) = = m_substitution_code_point ) ;
while ( m_substitution_string_data - > size ( ) < length )
2022-05-07 11:03:33 -03:00
m_substitution_string_data - > append ( m_substitution_code_point . value ( ) ) ;
2021-06-25 12:48:51 -03:00
return Utf32View { m_substitution_string_data - > data ( ) , length } ;
}
2021-07-09 08:34:20 -03:00
Gfx : : IntRect TextEditor : : content_rect_for_position ( TextPosition const & position ) const
2019-03-06 20:31:06 -03:00
{
2019-08-21 16:23:17 -03:00
if ( ! position . is_valid ( ) )
2019-06-07 06:46:02 -03:00
return { } ;
2021-02-23 16:42:32 -03:00
VERIFY ( ! lines ( ) . is_empty ( ) ) ;
VERIFY ( position . column ( ) < = ( current_line ( ) . length ( ) + 1 ) ) ;
2019-04-24 17:24:16 -03:00
2019-08-21 16:23:17 -03:00
int x = content_x_for_position ( position ) ;
2019-04-24 17:24:16 -03:00
2019-03-28 11:30:29 -03:00
if ( is_single_line ( ) ) {
2021-01-01 20:33:14 -03:00
Gfx : : IntRect rect { x , 0 , 1 , line_height ( ) } ;
2019-08-21 16:23:17 -03:00
rect . center_vertically_within ( { { } , frame_inner_rect ( ) . size ( ) } ) ;
return rect ;
2019-03-28 11:30:29 -03:00
}
2019-08-25 04:06:54 -03:00
2020-06-10 05:57:59 -03:00
Gfx : : IntRect rect ;
2021-07-09 08:34:20 -03:00
for_each_visual_line ( position . line ( ) , [ & ] ( Gfx : : IntRect const & visual_line_rect , auto & view , size_t start_of_visual_line , bool is_last_visual_line ) {
2021-02-22 22:34:26 -03:00
auto before_line_end = is_last_visual_line ? ( ( position . column ( ) - start_of_visual_line ) < = view . length ( ) ) : ( ( position . column ( ) - start_of_visual_line ) < view . length ( ) ) ;
if ( position . column ( ) > = start_of_visual_line & & before_line_end ) {
2019-08-25 04:06:54 -03:00
// NOTE: We have to subtract the horizontal padding here since it's part of the visual line rect
// *and* included in what we get from content_x_for_position().
rect = {
visual_line_rect . x ( ) + x - ( m_horizontal_content_padding ) ,
visual_line_rect . y ( ) ,
2021-01-02 07:59:55 -03:00
m_editing_engine - > cursor_width ( ) = = CursorWidth : : WIDE ? 7 : 1 ,
2019-08-25 04:06:54 -03:00
line_height ( )
} ;
return IterationDecision : : Break ;
}
return IterationDecision : : Continue ;
} ) ;
return rect ;
2019-08-21 16:23:17 -03:00
}
2020-06-10 05:57:59 -03:00
Gfx : : IntRect TextEditor : : cursor_content_rect ( ) const
2019-08-21 16:23:17 -03:00
{
return content_rect_for_position ( m_cursor ) ;
2019-03-06 21:05:35 -03:00
}
2020-06-10 05:57:59 -03:00
Gfx : : IntRect TextEditor : : line_widget_rect ( size_t line_index ) const
2019-03-06 22:24:46 -03:00
{
auto rect = line_content_rect ( line_index ) ;
2019-04-24 18:15:15 -03:00
rect . set_x ( frame_thickness ( ) ) ;
rect . set_width ( frame_inner_rect ( ) . width ( ) ) ;
2021-04-12 15:47:09 -03:00
rect . translate_by ( 0 , - ( vertical_scrollbar ( ) . value ( ) ) ) ;
rect . translate_by ( 0 , frame_thickness ( ) ) ;
2022-11-28 20:09:46 -03:00
rect . translate_by ( 0 , height_occupied_by_banner_widget ( ) ) ;
2019-04-24 20:09:44 -03:00
rect . intersect ( frame_inner_rect ( ) ) ;
2019-03-07 09:13:25 -03:00
return rect ;
2019-03-06 22:24:46 -03:00
}
2021-07-09 08:34:20 -03:00
void TextEditor : : scroll_position_into_view ( TextPosition const & position )
2019-03-06 20:31:06 -03:00
{
2019-08-21 16:23:17 -03:00
auto rect = content_rect_for_position ( position ) ;
if ( position . column ( ) = = 0 )
rect . set_x ( content_x_for_position ( { position . line ( ) , 0 } ) - 2 ) ;
2019-12-09 13:45:40 -03:00
else if ( position . column ( ) = = line ( position . line ( ) ) . length ( ) )
rect . set_x ( content_x_for_position ( { position . line ( ) , line ( position . line ( ) ) . length ( ) } ) + 2 ) ;
2019-04-24 20:33:59 -03:00
scroll_into_view ( rect , true , true ) ;
2019-03-06 20:31:06 -03:00
}
2020-02-02 11:07:41 -03:00
void TextEditor : : scroll_cursor_into_view ( )
2019-08-21 16:23:17 -03:00
{
2020-06-07 16:03:29 -03:00
if ( ! m_reflow_deferred )
scroll_position_into_view ( m_cursor ) ;
2019-08-21 16:23:17 -03:00
}
2020-06-10 05:57:59 -03:00
Gfx : : IntRect TextEditor : : line_content_rect ( size_t line_index ) const
2019-03-06 20:31:06 -03:00
{
2019-12-09 13:45:40 -03:00
auto & line = this - > line ( line_index ) ;
2019-03-28 11:30:29 -03:00
if ( is_single_line ( ) ) {
2023-03-03 15:32:19 -03:00
Gfx : : IntRect line_rect = { content_x_for_position ( { line_index , 0 } ) , 0 , text_width_for_font ( line . view ( ) , font ( ) ) , font ( ) . pixel_size_rounded_up ( ) + 4 } ;
2019-06-07 06:46:02 -03:00
line_rect . center_vertically_within ( { { } , frame_inner_rect ( ) . size ( ) } ) ;
2019-03-28 11:30:29 -03:00
return line_rect ;
}
2023-03-06 13:16:25 -03:00
return m_line_visual_data [ line_index ] - > visual_rect ;
2019-03-06 20:31:06 -03:00
}
2020-11-10 05:53:50 -03:00
void TextEditor : : set_cursor_and_focus_line ( size_t line , size_t column )
{
u_int index_max = line_count ( ) - 1 ;
set_cursor ( line , column ) ;
if ( line > 1 & & line < index_max ) {
int headroom = frame_inner_rect ( ) . height ( ) / 3 ;
do {
2023-03-06 13:16:25 -03:00
auto const & line_data = m_line_visual_data [ line ] ;
headroom - = line_data - > visual_rect . height ( ) ;
2020-11-10 05:53:50 -03:00
line - - ;
} while ( line > 0 & & headroom > 0 ) ;
Gfx : : IntRect rect = { 0 , line_content_rect ( line ) . y ( ) ,
1 , frame_inner_rect ( ) . height ( ) } ;
scroll_into_view ( rect , false , true ) ;
}
}
2020-02-02 11:07:41 -03:00
void TextEditor : : update_cursor ( )
2019-03-06 20:31:06 -03:00
{
2019-03-07 09:23:17 -03:00
update ( line_widget_rect ( m_cursor . line ( ) ) ) ;
2019-03-06 20:31:06 -03:00
}
2020-02-02 11:07:41 -03:00
void TextEditor : : set_cursor ( size_t line , size_t column )
2019-03-06 20:46:29 -03:00
{
2019-03-07 11:03:38 -03:00
set_cursor ( { line , column } ) ;
}
2021-07-09 08:34:20 -03:00
void TextEditor : : set_cursor ( TextPosition const & a_position )
2019-03-07 11:03:38 -03:00
{
2021-02-23 16:42:32 -03:00
VERIFY ( ! lines ( ) . is_empty ( ) ) ;
2019-10-21 13:58:27 -03:00
2020-02-02 11:07:41 -03:00
TextPosition position = a_position ;
2019-10-21 13:58:27 -03:00
2019-12-09 13:45:40 -03:00
if ( position . line ( ) > = line_count ( ) )
position . set_line ( line_count ( ) - 1 ) ;
2019-10-21 13:58:27 -03:00
2023-03-06 13:16:25 -03:00
if ( position . column ( ) > lines ( ) [ position . line ( ) ] - > length ( ) )
position . set_column ( lines ( ) [ position . line ( ) ] - > length ( ) ) ;
2019-10-21 13:58:27 -03:00
2020-06-12 19:31:46 -03:00
if ( m_cursor ! = position & & is_visual_data_up_to_date ( ) ) {
2019-05-06 17:04:53 -03:00
// NOTE: If the old cursor is no longer valid, repaint everything just in case.
2019-12-09 13:45:40 -03:00
auto old_cursor_line_rect = m_cursor . line ( ) < line_count ( )
2019-05-06 17:04:53 -03:00
? line_widget_rect ( m_cursor . line ( ) )
: rect ( ) ;
2019-03-08 13:53:02 -03:00
m_cursor = position ;
m_cursor_state = true ;
scroll_cursor_into_view ( ) ;
update ( old_cursor_line_rect ) ;
update_cursor ( ) ;
2020-06-12 19:31:46 -03:00
} else if ( m_cursor ! = position ) {
m_cursor = position ;
m_cursor_state = true ;
2019-03-08 13:53:02 -03:00
}
2019-11-18 15:10:06 -03:00
cursor_did_change ( ) ;
2019-03-06 20:46:29 -03:00
if ( on_cursor_change )
2019-04-09 22:08:29 -03:00
on_cursor_change ( ) ;
2020-02-07 16:07:15 -03:00
if ( m_highlighter )
m_highlighter - > cursor_did_change ( ) ;
2022-12-08 06:07:50 -03:00
if ( m_relative_line_number )
update ( ) ;
2019-03-06 20:46:29 -03:00
}
2023-02-20 14:31:48 -03:00
void TextEditor : : set_cursor_to_text_position ( Gfx : : IntPoint position )
{
auto visual_position = text_position_at ( position ) ;
size_t physical_column = 0 ;
auto const & line = document ( ) . line ( visual_position . line ( ) ) ;
size_t boundary_index = 0 ;
Unicode : : for_each_grapheme_segmentation_boundary ( line . view ( ) , [ & ] ( auto boundary ) {
physical_column = boundary ;
if ( boundary_index + + > = visual_position . column ( ) )
return IterationDecision : : Break ;
return IterationDecision : : Continue ;
} ) ;
set_cursor ( { visual_position . line ( ) , physical_column } ) ;
}
2023-02-27 10:16:48 -03:00
void TextEditor : : set_cursor_to_end_of_visual_line ( )
{
for_each_visual_line ( m_cursor . line ( ) , [ & ] ( auto const & , auto & view , size_t start_of_visual_line , auto ) {
if ( m_cursor . column ( ) < start_of_visual_line )
return IterationDecision : : Continue ;
if ( ( m_cursor . column ( ) - start_of_visual_line ) > = view . length ( ) )
return IterationDecision : : Continue ;
set_cursor ( m_cursor . line ( ) , start_of_visual_line + view . length ( ) ) ;
return IterationDecision : : Break ;
} ) ;
}
2020-08-14 14:58:38 -03:00
void TextEditor : : focusin_event ( FocusEvent & event )
2019-03-06 21:05:35 -03:00
{
2020-08-14 14:58:38 -03:00
if ( event . source ( ) = = FocusSource : : Keyboard )
select_all ( ) ;
2020-05-21 12:37:28 -03:00
m_cursor_state = true ;
2019-03-06 21:05:35 -03:00
update_cursor ( ) ;
2021-01-02 07:59:55 -03:00
stop_timer ( ) ;
2019-03-06 21:05:35 -03:00
start_timer ( 500 ) ;
2020-07-04 15:07:55 -03:00
if ( on_focusin )
on_focusin ( ) ;
2019-03-06 21:05:35 -03:00
}
2020-08-14 14:56:40 -03:00
void TextEditor : : focusout_event ( FocusEvent & )
2019-03-06 21:05:35 -03:00
{
2021-03-10 13:57:59 -03:00
if ( is_displayonly ( ) & & has_selection ( ) )
m_selection . clear ( ) ;
2019-03-06 21:05:35 -03:00
stop_timer ( ) ;
2020-07-04 15:07:55 -03:00
if ( on_focusout )
on_focusout ( ) ;
2019-03-06 21:05:35 -03:00
}
2020-02-02 11:07:41 -03:00
void TextEditor : : timer_event ( Core : : TimerEvent & )
2019-03-06 21:05:35 -03:00
{
m_cursor_state = ! m_cursor_state ;
2019-11-05 19:37:47 -03:00
if ( is_focused ( ) )
2019-03-06 21:05:35 -03:00
update_cursor ( ) ;
}
2023-01-15 02:30:51 -03:00
ErrorOr < void > TextEditor : : write_to_file ( StringView path )
2019-03-07 13:06:11 -03:00
{
2023-02-08 23:02:46 -03:00
auto file = TRY ( Core : : File : : open ( path , Core : : File : : OpenMode : : Write | Core : : File : : OpenMode : : Truncate ) ) ;
2023-01-15 02:30:51 -03:00
TRY ( write_to_file ( * file ) ) ;
return { } ;
2021-06-30 10:13:06 -03:00
}
2023-02-08 23:02:46 -03:00
ErrorOr < void > TextEditor : : write_to_file ( Core : : File & file )
2023-01-07 13:52:46 -03:00
{
off_t file_size = 0 ;
if ( line_count ( ) = = 1 & & line ( 0 ) . is_empty ( ) ) {
// Truncate to zero.
} else {
// Compute the final file size and ftruncate() to make writing fast.
// FIXME: Remove this once the kernel is smart enough to do this instead.
for ( size_t i = 0 ; i < line_count ( ) ; + + i )
file_size + = line ( i ) . length ( ) ;
file_size + = line_count ( ) ;
}
TRY ( file . truncate ( file_size ) ) ;
if ( file_size = = 0 ) {
// A size 0 file doesn't need a data copy.
} else {
for ( size_t i = 0 ; i < line_count ( ) ; + + i ) {
2023-03-01 13:24:50 -03:00
TRY ( file . write_until_depleted ( line ( i ) . to_utf8 ( ) . bytes ( ) ) ) ;
TRY ( file . write_until_depleted ( " \n " sv . bytes ( ) ) ) ;
2023-01-07 13:52:46 -03:00
}
}
document ( ) . set_unmodified ( ) ;
return { } ;
}
2022-12-04 15:02:33 -03:00
DeprecatedString TextEditor : : text ( ) const
2019-03-15 13:37:13 -03:00
{
2020-09-28 10:37:37 -03:00
return document ( ) . text ( ) ;
2019-03-15 13:37:13 -03:00
}
2020-02-02 11:07:41 -03:00
void TextEditor : : clear ( )
2019-03-15 13:37:13 -03:00
{
2019-10-27 15:36:59 -03:00
document ( ) . remove_all_lines ( ) ;
2020-02-02 11:07:41 -03:00
document ( ) . append_line ( make < TextDocumentLine > ( document ( ) ) ) ;
2019-03-15 13:37:13 -03:00
m_selection . clear ( ) ;
2019-04-11 21:52:34 -03:00
did_update_selection ( ) ;
2019-03-15 13:37:13 -03:00
set_cursor ( 0 , 0 ) ;
update ( ) ;
}
2022-12-04 15:02:33 -03:00
DeprecatedString TextEditor : : selected_text ( ) const
2019-03-07 21:59:59 -03:00
{
if ( ! has_selection ( ) )
2019-06-07 06:46:02 -03:00
return { } ;
2019-03-07 21:59:59 -03:00
2019-10-29 17:36:47 -03:00
return document ( ) . text_in_range ( m_selection ) ;
2019-03-07 21:59:59 -03:00
}
2019-03-08 10:08:15 -03:00
2021-05-25 21:58:55 -03:00
size_t TextEditor : : number_of_selected_words ( ) const
{
if ( ! has_selection ( ) )
return 0 ;
size_t word_count = 0 ;
bool in_word = false ;
auto selected_text = this - > selected_text ( ) ;
for ( char c : selected_text ) {
2021-06-01 16:18:08 -03:00
if ( in_word & & is_ascii_space ( c ) ) {
2021-05-25 21:58:55 -03:00
in_word = false ;
word_count + + ;
continue ;
}
2021-06-01 16:18:08 -03:00
if ( ! in_word & & ! is_ascii_space ( c ) )
2021-05-25 21:58:55 -03:00
in_word = true ;
}
if ( in_word )
word_count + + ;
return word_count ;
}
2021-09-18 17:31:47 -03:00
size_t TextEditor : : number_of_words ( ) const
{
if ( document ( ) . is_empty ( ) )
return 0 ;
size_t word_count = 0 ;
bool in_word = false ;
auto text = this - > text ( ) ;
for ( char c : text ) {
if ( in_word & & is_ascii_space ( c ) ) {
in_word = false ;
word_count + + ;
continue ;
}
if ( ! in_word & & ! is_ascii_space ( c ) )
in_word = true ;
}
if ( in_word )
word_count + + ;
return word_count ;
}
2020-02-02 11:07:41 -03:00
void TextEditor : : delete_selection ( )
2019-03-08 10:08:15 -03:00
{
2019-03-08 14:28:24 -03:00
auto selection = normalized_selection ( ) ;
2021-05-02 13:33:31 -03:00
auto selected = selected_text ( ) ;
2019-03-08 14:28:24 -03:00
m_selection . clear ( ) ;
2021-05-02 13:33:31 -03:00
execute < RemoveTextCommand > ( selected , selection ) ;
2019-04-11 21:52:34 -03:00
did_update_selection ( ) ;
2019-04-24 20:28:17 -03:00
did_change ( ) ;
2019-03-08 14:28:24 -03:00
set_cursor ( selection . start ( ) ) ;
2019-03-08 10:08:15 -03:00
update ( ) ;
}
2021-01-27 08:57:39 -03:00
void TextEditor : : delete_text_range ( TextRange range )
{
auto normalized_range = range . normalized ( ) ;
execute < RemoveTextCommand > ( document ( ) . text_in_range ( normalized_range ) , normalized_range ) ;
did_change ( ) ;
set_cursor ( normalized_range . start ( ) ) ;
update ( ) ;
}
2021-11-10 20:55:02 -03:00
void TextEditor : : insert_at_cursor_or_replace_selection ( StringView text )
2019-03-08 10:08:15 -03:00
{
2020-05-27 14:18:40 -03:00
ReflowDeferrer defer ( * this ) ;
2021-02-23 16:42:32 -03:00
VERIFY ( is_editable ( ) ) ;
2022-07-10 07:02:19 -03:00
if ( has_selection ( ) )
2019-03-08 10:08:15 -03:00
delete_selection ( ) ;
2021-05-15 13:38:53 -03:00
// Check if adding a newline leaves the previous line as just whitespace.
auto const clear_length = m_cursor . column ( ) ;
auto const should_clear_last_line = text = = " \n "
& & clear_length > 0
& & current_line ( ) . leading_spaces ( ) = = clear_length ;
2022-07-10 07:02:19 -03:00
execute < InsertTextCommand > ( text , m_cursor ) ;
2021-05-15 13:38:53 -03:00
if ( should_clear_last_line ) { // If it does leave just whitespace, clear it.
auto const original_cursor_position = cursor ( ) ;
TextPosition start ( original_cursor_position . line ( ) - 1 , 0 ) ;
TextPosition end ( original_cursor_position . line ( ) - 1 , clear_length ) ;
TextRange erased_range ( start , end ) ;
execute < RemoveTextCommand > ( document ( ) . text_in_range ( erased_range ) , erased_range ) ;
set_cursor ( original_cursor_position ) ;
}
2019-03-08 10:08:15 -03:00
}
2022-04-13 11:14:59 -03:00
void TextEditor : : replace_all_text_without_resetting_undo_stack ( StringView text )
2022-04-12 17:46:26 -03:00
{
auto start = GUI : : TextPosition ( 0 , 0 ) ;
auto last_line_index = line_count ( ) - 1 ;
auto end = GUI : : TextPosition ( last_line_index , line ( last_line_index ) . length ( ) ) ;
auto range = GUI : : TextRange ( start , end ) ;
auto normalized_range = range . normalized ( ) ;
2022-04-13 11:14:59 -03:00
execute < ReplaceAllTextCommand > ( text , range , " GML Playground Format Text " ) ;
2022-04-12 17:46:26 -03:00
did_change ( ) ;
set_cursor ( normalized_range . start ( ) ) ;
update ( ) ;
}
2020-02-02 11:07:41 -03:00
void TextEditor : : cut ( )
2019-03-08 10:08:15 -03:00
{
2020-07-15 18:04:49 -03:00
if ( ! is_editable ( ) )
2019-05-08 00:00:28 -03:00
return ;
2019-03-08 10:08:15 -03:00
auto selected_text = this - > selected_text ( ) ;
2021-06-03 06:05:58 -03:00
dbgln_if ( TEXTEDITOR_DEBUG , " Cut: \" {} \" " , selected_text ) ;
2020-09-05 11:16:01 -03:00
Clipboard : : the ( ) . set_plain_text ( selected_text ) ;
2019-03-08 10:08:15 -03:00
delete_selection ( ) ;
}
2020-02-02 11:07:41 -03:00
void TextEditor : : copy ( )
2019-03-08 10:08:15 -03:00
{
auto selected_text = this - > selected_text ( ) ;
2021-06-03 06:05:58 -03:00
dbgln_if ( TEXTEDITOR_DEBUG , " Copy: \" {} \" \n " , selected_text ) ;
2020-09-05 11:16:01 -03:00
Clipboard : : the ( ) . set_plain_text ( selected_text ) ;
2019-03-08 10:08:15 -03:00
}
2020-02-02 11:07:41 -03:00
void TextEditor : : paste ( )
2019-03-08 10:08:15 -03:00
{
2020-07-15 18:04:49 -03:00
if ( ! is_editable ( ) )
2019-05-08 00:00:28 -03:00
return ;
2020-05-27 14:18:40 -03:00
2021-11-20 11:22:01 -03:00
auto [ data , mime_type , _ ] = GUI : : Clipboard : : the ( ) . fetch_data_and_type ( ) ;
2022-07-11 14:32:29 -03:00
if ( ! mime_type . starts_with ( " text/ " sv ) )
2021-07-25 06:59:30 -03:00
return ;
2021-11-20 10:59:58 -03:00
if ( data . is_empty ( ) )
2021-05-22 17:29:32 -03:00
return ;
2022-12-04 15:02:33 -03:00
dbgln_if ( TEXTEDITOR_DEBUG , " Paste: \" {} \" " , DeprecatedString : : copy ( data ) ) ;
2020-01-23 17:07:37 -03:00
TemporaryChange change ( m_automatic_indentation_enabled , false ) ;
2021-11-20 10:59:58 -03:00
insert_at_cursor_or_replace_selection ( data ) ;
2019-03-08 10:08:15 -03:00
}
2019-03-31 18:52:02 -03:00
2020-05-27 14:18:40 -03:00
void TextEditor : : defer_reflow ( )
{
+ + m_reflow_deferred ;
}
void TextEditor : : undefer_reflow ( )
{
2021-02-23 16:42:32 -03:00
VERIFY ( m_reflow_deferred ) ;
2020-05-27 14:18:40 -03:00
if ( ! - - m_reflow_deferred ) {
2020-05-29 16:43:06 -03:00
if ( m_reflow_requested ) {
2020-05-27 14:18:40 -03:00
recompute_all_visual_lines ( ) ;
2020-05-29 16:43:06 -03:00
scroll_cursor_into_view ( ) ;
}
2020-05-27 14:18:40 -03:00
}
}
2021-10-28 01:13:49 -03:00
void TextEditor : : try_show_autocomplete ( UserRequestedAutocomplete user_requested_autocomplete )
2021-10-28 04:04:33 -03:00
{
force_update_autocomplete ( [ & , user_requested_autocomplete = move ( user_requested_autocomplete ) ] {
if ( user_requested_autocomplete = = Yes | | m_autocomplete_box - > has_suggestions ( ) ) {
auto position = content_rect_for_position ( cursor ( ) ) . translated ( 0 , - visible_content_rect ( ) . y ( ) ) . bottom_right ( ) . translated ( screen_relative_rect ( ) . top_left ( ) . translated ( ruler_width ( ) , 0 ) . translated ( 10 , 5 ) ) ;
m_autocomplete_box - > show ( position ) ;
}
} ) ;
}
void TextEditor : : try_update_autocomplete ( Function < void ( ) > callback )
{
if ( m_autocomplete_box & & m_autocomplete_box - > is_visible ( ) )
force_update_autocomplete ( move ( callback ) ) ;
}
void TextEditor : : force_update_autocomplete ( Function < void ( ) > callback )
2021-01-01 10:59:11 -03:00
{
if ( m_autocomplete_provider ) {
2021-10-28 04:04:33 -03:00
m_autocomplete_provider - > provide_completions ( [ & , callback = move ( callback ) ] ( auto completions ) {
2021-01-01 10:59:11 -03:00
m_autocomplete_box - > update_suggestions ( move ( completions ) ) ;
2021-10-28 04:04:33 -03:00
if ( callback )
callback ( ) ;
2021-01-01 10:59:11 -03:00
} ) ;
}
}
2021-10-28 01:39:55 -03:00
void TextEditor : : hide_autocomplete_if_needed ( )
{
2021-10-28 22:23:58 -03:00
if ( ! m_should_keep_autocomplete_box )
hide_autocomplete ( ) ;
}
void TextEditor : : hide_autocomplete ( )
{
if ( m_autocomplete_box ) {
2021-10-28 01:39:55 -03:00
m_autocomplete_box - > close ( ) ;
if ( m_autocomplete_timer )
m_autocomplete_timer - > stop ( ) ;
}
}
2020-02-02 11:07:41 -03:00
void TextEditor : : enter_event ( Core : : Event & )
2019-03-31 18:52:02 -03:00
{
2022-12-25 17:51:04 -03:00
set_automatic_scrolling_timer_active ( false ) ;
2019-03-31 18:52:02 -03:00
}
2020-02-02 11:07:41 -03:00
void TextEditor : : leave_event ( Core : : Event & )
2019-03-31 18:52:02 -03:00
{
2020-04-20 16:31:49 -03:00
if ( m_in_drag_select )
2022-12-25 17:51:04 -03:00
set_automatic_scrolling_timer_active ( true ) ;
2019-03-31 18:52:02 -03:00
}
2019-04-09 11:20:36 -03:00
2021-09-21 18:02:48 -03:00
void TextEditor : : did_change ( AllowCallback allow_callback )
2019-04-09 11:20:36 -03:00
{
update_content_size ( ) ;
2019-08-25 03:43:01 -03:00
recompute_all_visual_lines ( ) ;
2021-10-28 01:39:55 -03:00
hide_autocomplete_if_needed ( ) ;
2021-06-29 06:22:57 -03:00
m_needs_rehighlight = true ;
2022-04-29 12:48:55 -03:00
if ( on_change & & allow_callback = = AllowCallback : : Yes )
on_change ( ) ;
2019-04-09 11:20:36 -03:00
}
2020-07-14 18:02:46 -03:00
void TextEditor : : set_mode ( const Mode mode )
{
if ( m_mode = = mode )
return ;
m_mode = mode ;
switch ( mode ) {
case Editable :
2021-08-02 06:10:56 -03:00
m_cut_action - > set_enabled ( has_selection ( ) & & ! text_is_secret ( ) ) ;
2020-07-14 18:02:46 -03:00
m_paste_action - > set_enabled ( true ) ;
2022-09-08 19:06:08 -03:00
m_insert_emoji_action - > set_enabled ( true ) ;
2020-07-14 18:02:46 -03:00
break ;
case DisplayOnly :
2020-07-15 18:04:49 -03:00
case ReadOnly :
2021-08-02 08:19:42 -03:00
m_cut_action - > set_enabled ( false ) ;
2020-07-14 18:02:46 -03:00
m_paste_action - > set_enabled ( false ) ;
2022-09-08 19:06:08 -03:00
m_insert_emoji_action - > set_enabled ( false ) ;
2020-07-14 18:02:46 -03:00
break ;
default :
2021-02-23 16:42:32 -03:00
VERIFY_NOT_REACHED ( ) ;
2020-07-14 18:02:46 -03:00
}
2020-09-11 09:25:48 -03:00
2022-09-09 09:47:24 -03:00
set_or_clear_emoji_input_callback ( ) ;
2022-08-18 10:38:06 -03:00
set_editing_cursor ( ) ;
}
void TextEditor : : set_editing_cursor ( )
{
2020-09-11 09:25:48 -03:00
if ( ! is_displayonly ( ) )
set_override_cursor ( Gfx : : StandardCursor : : IBeam ) ;
else
set_override_cursor ( Gfx : : StandardCursor : : None ) ;
2020-07-14 18:02:46 -03:00
}
2019-04-11 21:52:34 -03:00
2020-02-02 11:07:41 -03:00
void TextEditor : : did_update_selection ( )
2019-04-11 21:52:34 -03:00
{
2021-08-02 06:10:56 -03:00
m_cut_action - > set_enabled ( is_editable ( ) & & has_selection ( ) & & ! text_is_secret ( ) ) ;
m_copy_action - > set_enabled ( has_selection ( ) & & ! text_is_secret ( ) ) ;
2019-04-11 21:52:34 -03:00
if ( on_selection_change )
on_selection_change ( ) ;
2021-01-09 09:47:48 -03:00
if ( is_wrapping_enabled ( ) ) {
2019-08-25 05:23:11 -03:00
// FIXME: Try to repaint less.
update ( ) ;
}
2019-04-11 21:52:34 -03:00
}
2019-04-18 07:25:00 -03:00
2020-02-02 11:07:41 -03:00
void TextEditor : : context_menu_event ( ContextMenuEvent & event )
2019-04-18 07:25:00 -03:00
{
2020-07-14 18:02:46 -03:00
if ( is_displayonly ( ) )
return ;
2022-09-09 09:47:24 -03:00
m_insert_emoji_action - > set_enabled ( on_emoji_input & & ! window ( ) - > blocks_emoji_input ( ) ) ;
2022-09-09 08:21:31 -03:00
2019-04-18 07:25:00 -03:00
if ( ! m_context_menu ) {
2020-02-02 11:07:41 -03:00
m_context_menu = Menu : : construct ( ) ;
2019-04-18 07:25:00 -03:00
m_context_menu - > add_action ( undo_action ( ) ) ;
m_context_menu - > add_action ( redo_action ( ) ) ;
m_context_menu - > add_separator ( ) ;
m_context_menu - > add_action ( cut_action ( ) ) ;
m_context_menu - > add_action ( copy_action ( ) ) ;
m_context_menu - > add_action ( paste_action ( ) ) ;
2020-04-20 16:10:03 -03:00
m_context_menu - > add_separator ( ) ;
m_context_menu - > add_action ( select_all_action ( ) ) ;
2022-09-08 19:06:08 -03:00
m_context_menu - > add_action ( insert_emoji_action ( ) ) ;
2020-01-23 17:21:55 -03:00
if ( is_multi_line ( ) ) {
m_context_menu - > add_separator ( ) ;
2020-01-23 17:27:27 -03:00
m_context_menu - > add_action ( go_to_line_action ( ) ) ;
2020-01-23 17:21:55 -03:00
}
2019-08-25 16:33:08 -03:00
if ( ! m_custom_context_menu_actions . is_empty ( ) ) {
m_context_menu - > add_separator ( ) ;
for ( auto & action : m_custom_context_menu_actions ) {
m_context_menu - > add_action ( action ) ;
}
}
2019-04-18 07:25:00 -03:00
}
m_context_menu - > popup ( event . screen_position ( ) ) ;
}
2019-04-24 17:24:16 -03:00
2020-02-06 07:56:38 -03:00
void TextEditor : : set_text_alignment ( Gfx : : TextAlignment alignment )
2019-04-24 17:24:16 -03:00
{
if ( m_text_alignment = = alignment )
return ;
m_text_alignment = alignment ;
update ( ) ;
}
2019-04-24 18:42:49 -03:00
2020-02-02 11:07:41 -03:00
void TextEditor : : resize_event ( ResizeEvent & event )
2019-04-24 18:42:49 -03:00
{
2021-05-03 15:31:58 -03:00
AbstractScrollableWidget : : resize_event ( event ) ;
2019-04-24 18:42:49 -03:00
update_content_size ( ) ;
2019-08-25 03:43:01 -03:00
recompute_all_visual_lines ( ) ;
2019-04-24 18:42:49 -03:00
}
2019-08-21 16:23:17 -03:00
2020-03-16 08:36:21 -03:00
void TextEditor : : theme_change_event ( ThemeChangeEvent & event )
{
2021-05-03 15:31:58 -03:00
AbstractScrollableWidget : : theme_change_event ( event ) ;
2021-06-29 06:22:57 -03:00
m_needs_rehighlight = true ;
2020-03-16 08:36:21 -03:00
}
2021-07-09 08:34:20 -03:00
void TextEditor : : set_selection ( TextRange const & selection )
2019-08-21 16:23:17 -03:00
{
if ( m_selection = = selection )
return ;
m_selection = selection ;
set_cursor ( m_selection . end ( ) ) ;
scroll_position_into_view ( normalized_selection ( ) . start ( ) ) ;
update ( ) ;
}
2020-02-02 11:07:41 -03:00
void TextEditor : : clear_selection ( )
2019-12-01 23:48:32 -03:00
{
if ( ! has_selection ( ) )
return ;
m_selection . clear ( ) ;
update ( ) ;
}
2020-02-02 11:07:41 -03:00
void TextEditor : : recompute_all_visual_lines ( )
2019-08-25 03:43:01 -03:00
{
2020-05-27 14:18:40 -03:00
if ( m_reflow_deferred ) {
m_reflow_requested = true ;
return ;
}
2020-06-12 19:31:46 -03:00
m_reflow_requested = false ;
2019-08-25 03:43:01 -03:00
int y_offset = 0 ;
2023-02-23 12:31:04 -03:00
auto folded_regions = document ( ) . currently_folded_regions ( ) ;
auto folded_region_iterator = folded_regions . begin ( ) ;
2019-12-09 13:45:40 -03:00
for ( size_t line_index = 0 ; line_index < line_count ( ) ; + + line_index ) {
2023-02-23 12:31:04 -03:00
recompute_visual_lines ( line_index , folded_region_iterator ) ;
2023-03-06 13:16:25 -03:00
m_line_visual_data [ line_index ] - > visual_rect . set_y ( y_offset ) ;
y_offset + = m_line_visual_data [ line_index ] - > visual_rect . height ( ) ;
2019-08-25 03:43:01 -03:00
}
2019-08-29 01:26:24 -03:00
update_content_size ( ) ;
2019-08-25 03:43:01 -03:00
}
2020-02-02 11:07:41 -03:00
void TextEditor : : ensure_cursor_is_valid ( )
2019-11-05 19:37:47 -03:00
{
2019-11-30 12:50:24 -03:00
auto new_cursor = m_cursor ;
2019-12-09 13:45:40 -03:00
if ( new_cursor . line ( ) > = line_count ( ) )
new_cursor . set_line ( line_count ( ) - 1 ) ;
if ( new_cursor . column ( ) > line ( new_cursor . line ( ) ) . length ( ) )
new_cursor . set_column ( line ( new_cursor . line ( ) ) . length ( ) ) ;
2019-11-30 12:50:24 -03:00
if ( m_cursor ! = new_cursor )
set_cursor ( new_cursor ) ;
2019-11-05 19:37:47 -03:00
}
2020-02-02 11:07:41 -03:00
size_t TextEditor : : visual_line_containing ( size_t line_index , size_t column ) const
2019-10-27 12:10:07 -03:00
{
2019-12-09 13:45:40 -03:00
size_t visual_line_index = 0 ;
2021-07-09 08:34:20 -03:00
for_each_visual_line ( line_index , [ & ] ( Gfx : : IntRect const & , auto & view , size_t start_of_visual_line , [[maybe_unused]] bool is_last_visual_line ) {
2019-10-27 12:10:07 -03:00
if ( column > = start_of_visual_line & & ( ( column - start_of_visual_line ) < view . length ( ) ) )
return IterationDecision : : Break ;
+ + visual_line_index ;
return IterationDecision : : Continue ;
} ) ;
return visual_line_index ;
}
2023-02-23 12:31:04 -03:00
void TextEditor : : recompute_visual_lines ( size_t line_index , Vector < TextDocumentFoldingRegion const & > : : Iterator & folded_region_iterator )
2019-08-25 03:43:01 -03:00
{
2023-02-22 09:49:16 -03:00
auto const & line = document ( ) . line ( line_index ) ;
size_t line_width_so_far = 0 ;
2019-08-25 03:43:01 -03:00
2023-02-22 09:49:16 -03:00
auto & visual_data = m_line_visual_data [ line_index ] ;
2023-03-06 13:16:25 -03:00
visual_data - > visual_lines . clear_with_capacity ( ) ;
2019-08-25 03:43:01 -03:00
2023-02-22 09:49:16 -03:00
auto available_width = visible_text_rect_in_inner_coordinates ( ) . width ( ) ;
auto glyph_spacing = font ( ) . glyph_spacing ( ) ;
2019-10-27 14:00:07 -03:00
2023-02-23 12:31:04 -03:00
while ( ! folded_region_iterator . is_end ( ) & & folded_region_iterator - > range . end ( ) < TextPosition { line_index , 0 } )
+ + folded_region_iterator ;
bool line_is_visible = true ;
if ( ! folded_region_iterator . is_end ( ) ) {
if ( folded_region_iterator - > range . start ( ) . line ( ) < line_index ) {
if ( folded_region_iterator - > range . end ( ) . line ( ) > line_index ) {
line_is_visible = false ;
} else if ( folded_region_iterator - > range . end ( ) . line ( ) = = line_index ) {
+ + folded_region_iterator ;
}
}
}
2023-02-22 09:49:16 -03:00
auto wrap_visual_lines_anywhere = [ & ] ( ) {
2023-02-22 13:03:04 -03:00
size_t start_of_visual_line = 0 ;
2023-02-22 09:49:16 -03:00
for ( auto it = line . view ( ) . begin ( ) ; it ! = line . view ( ) . end ( ) ; + + it ) {
auto it_before_computing_glyph_width = it ;
auto glyph_width = font ( ) . glyph_or_emoji_width ( it ) ;
if ( line_width_so_far + glyph_width + glyph_spacing > available_width ) {
2023-02-22 13:03:04 -03:00
auto start_of_next_visual_line = line . view ( ) . iterator_offset ( it_before_computing_glyph_width ) ;
2023-03-06 13:16:25 -03:00
visual_data - > visual_lines . append ( line . view ( ) . substring_view ( start_of_visual_line , start_of_next_visual_line - start_of_visual_line ) ) ;
2023-02-22 09:49:16 -03:00
line_width_so_far = 0 ;
2023-02-22 13:03:04 -03:00
start_of_visual_line = start_of_next_visual_line ;
2019-08-25 03:43:01 -03:00
}
2023-02-22 09:49:16 -03:00
2020-05-18 11:38:28 -03:00
line_width_so_far + = glyph_width + glyph_spacing ;
2019-08-25 03:43:01 -03:00
}
2023-02-22 13:03:04 -03:00
2023-03-06 13:16:25 -03:00
visual_data - > visual_lines . append ( line . view ( ) . substring_view ( start_of_visual_line , line . view ( ) . length ( ) - start_of_visual_line ) ) ;
2023-02-22 09:49:16 -03:00
} ;
auto wrap_visual_lines_at_words = [ & ] ( ) {
size_t last_boundary = 0 ;
2023-02-22 13:03:04 -03:00
size_t start_of_visual_line = 0 ;
2023-02-22 09:49:16 -03:00
Unicode : : for_each_word_segmentation_boundary ( line . view ( ) , [ & ] ( auto boundary ) {
if ( boundary = = 0 )
return IterationDecision : : Continue ;
auto word = line . view ( ) . substring_view ( last_boundary , boundary - last_boundary ) ;
auto word_width = font ( ) . width ( word ) ;
if ( line_width_so_far + word_width + glyph_spacing > available_width ) {
2023-03-06 13:16:25 -03:00
visual_data - > visual_lines . append ( line . view ( ) . substring_view ( start_of_visual_line , last_boundary - start_of_visual_line ) ) ;
2023-02-22 09:49:16 -03:00
line_width_so_far = 0 ;
2023-02-22 13:03:04 -03:00
start_of_visual_line = last_boundary ;
2023-02-22 09:49:16 -03:00
}
line_width_so_far + = word_width + glyph_spacing ;
last_boundary = boundary ;
return IterationDecision : : Continue ;
} ) ;
2023-02-22 13:03:04 -03:00
2023-03-06 13:16:25 -03:00
visual_data - > visual_lines . append ( line . view ( ) . substring_view ( start_of_visual_line , line . view ( ) . length ( ) - start_of_visual_line ) ) ;
2023-02-22 09:49:16 -03:00
} ;
2023-02-23 12:31:04 -03:00
if ( line_is_visible ) {
switch ( wrapping_mode ( ) ) {
case WrappingMode : : NoWrap :
2023-03-06 13:16:25 -03:00
visual_data - > visual_lines . append ( line . view ( ) ) ;
2023-02-23 12:31:04 -03:00
break ;
case WrappingMode : : WrapAnywhere :
wrap_visual_lines_anywhere ( ) ;
break ;
case WrappingMode : : WrapAtWords :
wrap_visual_lines_at_words ( ) ;
break ;
}
2019-08-25 03:43:01 -03:00
}
2021-01-09 09:47:48 -03:00
if ( is_wrapping_enabled ( ) )
2023-03-06 13:16:25 -03:00
visual_data - > visual_rect = { m_horizontal_content_padding , 0 , available_width , static_cast < int > ( visual_data - > visual_lines . size ( ) ) * line_height ( ) } ;
2019-08-25 03:43:01 -03:00
else
2023-03-06 13:16:25 -03:00
visual_data - > visual_rect = { m_horizontal_content_padding , 0 , text_width_for_font ( line . view ( ) , font ( ) ) , line_height ( ) } ;
2019-08-25 03:43:01 -03:00
}
template < typename Callback >
2020-02-02 11:07:41 -03:00
void TextEditor : : for_each_visual_line ( size_t line_index , Callback callback ) const
2019-08-25 03:43:01 -03:00
{
2019-10-27 14:00:07 -03:00
auto editor_visible_text_rect = visible_text_rect_in_inner_coordinates ( ) ;
2019-12-09 13:45:40 -03:00
size_t visual_line_index = 0 ;
2019-10-27 14:00:07 -03:00
auto & line = document ( ) . line ( line_index ) ;
auto & visual_data = m_line_visual_data [ line_index ] ;
2023-03-06 13:16:25 -03:00
for ( auto visual_line_view : visual_data - > visual_lines ) {
2020-06-10 05:57:59 -03:00
Gfx : : IntRect visual_line_rect {
2023-03-06 13:16:25 -03:00
visual_data - > visual_rect . x ( ) ,
visual_data - > visual_rect . y ( ) + ( ( int ) visual_line_index * line_height ( ) ) ,
2021-06-25 12:48:51 -03:00
text_width_for_font ( visual_line_view , font ( ) ) + font ( ) . glyph_spacing ( ) ,
2019-10-27 14:00:07 -03:00
line_height ( )
2019-08-25 03:43:01 -03:00
} ;
2019-10-27 14:00:07 -03:00
if ( is_right_text_alignment ( text_alignment ( ) ) )
2019-09-16 15:57:32 -03:00
visual_line_rect . set_right_without_resize ( editor_visible_text_rect . right ( ) ) ;
2020-06-29 15:34:42 -03:00
if ( is_single_line ( ) ) {
2019-09-16 15:57:32 -03:00
visual_line_rect . center_vertically_within ( editor_visible_text_rect ) ;
2020-06-29 15:34:42 -03:00
if ( m_icon )
2021-04-12 15:47:09 -03:00
visual_line_rect . translate_by ( icon_size ( ) + icon_padding ( ) , 0 ) ;
2020-06-29 15:34:42 -03:00
}
2023-02-22 13:03:04 -03:00
size_t start_of_line = visual_line_view . code_points ( ) - line . code_points ( ) ;
2023-03-06 13:16:25 -03:00
if ( callback ( visual_line_rect , visual_line_view , start_of_line , visual_line_index = = visual_data - > visual_lines . size ( ) - 1 ) = = IterationDecision : : Break )
2019-08-25 03:43:01 -03:00
break ;
2019-10-27 14:00:07 -03:00
+ + visual_line_index ;
2019-08-25 03:43:01 -03:00
}
}
2019-08-25 07:23:14 -03:00
2021-01-09 09:47:48 -03:00
void TextEditor : : set_wrapping_mode ( WrappingMode mode )
2019-08-25 07:23:14 -03:00
{
2021-01-09 09:47:48 -03:00
if ( m_wrapping_mode = = mode )
2019-08-25 07:23:14 -03:00
return ;
2021-01-09 09:47:48 -03:00
m_wrapping_mode = mode ;
horizontal_scrollbar ( ) . set_visible ( m_wrapping_mode = = WrappingMode : : NoWrap ) ;
2019-08-25 07:23:14 -03:00
update_content_size ( ) ;
recompute_all_visual_lines ( ) ;
update ( ) ;
}
2019-08-25 09:04:46 -03:00
2020-02-02 11:07:41 -03:00
void TextEditor : : add_custom_context_menu_action ( Action & action )
2019-08-25 16:33:08 -03:00
{
m_custom_context_menu_actions . append ( action ) ;
}
2019-09-01 07:26:35 -03:00
2020-02-02 11:07:41 -03:00
void TextEditor : : did_change_font ( )
2019-09-01 07:26:35 -03:00
{
vertical_scrollbar ( ) . set_step ( line_height ( ) ) ;
2019-12-29 19:03:41 -03:00
recompute_all_visual_lines ( ) ;
update ( ) ;
2021-05-03 15:31:58 -03:00
AbstractScrollableWidget : : did_change_font ( ) ;
2019-09-01 07:26:35 -03:00
}
2019-10-27 14:00:07 -03:00
2020-02-02 11:07:41 -03:00
void TextEditor : : document_did_append_line ( )
2019-10-27 14:00:07 -03:00
{
m_line_visual_data . append ( make < LineVisualData > ( ) ) ;
2019-10-27 15:36:59 -03:00
recompute_all_visual_lines ( ) ;
update ( ) ;
2019-10-27 14:00:07 -03:00
}
2020-02-02 11:07:41 -03:00
void TextEditor : : document_did_remove_line ( size_t line_index )
2019-10-27 14:00:07 -03:00
{
m_line_visual_data . remove ( line_index ) ;
2019-10-27 15:36:59 -03:00
recompute_all_visual_lines ( ) ;
update ( ) ;
2019-10-27 14:00:07 -03:00
}
2020-02-02 11:07:41 -03:00
void TextEditor : : document_did_remove_all_lines ( )
2019-10-27 14:00:07 -03:00
{
m_line_visual_data . clear ( ) ;
2019-10-27 15:36:59 -03:00
recompute_all_visual_lines ( ) ;
update ( ) ;
2019-10-27 14:00:07 -03:00
}
2020-02-02 11:07:41 -03:00
void TextEditor : : document_did_insert_line ( size_t line_index )
2019-10-27 14:00:07 -03:00
{
m_line_visual_data . insert ( line_index , make < LineVisualData > ( ) ) ;
2019-10-27 15:36:59 -03:00
recompute_all_visual_lines ( ) ;
update ( ) ;
}
2021-09-21 18:02:48 -03:00
void TextEditor : : document_did_change ( AllowCallback allow_callback )
2019-10-27 15:36:59 -03:00
{
2021-09-21 18:02:48 -03:00
did_change ( allow_callback ) ;
2019-10-27 15:36:59 -03:00
update ( ) ;
2019-10-27 14:00:07 -03:00
}
2021-05-08 08:16:37 -03:00
void TextEditor : : document_did_update_undo_stack ( )
{
2021-05-08 16:44:22 -03:00
auto make_action_text = [ ] ( auto prefix , auto suffix ) {
StringBuilder builder ;
builder . append ( prefix ) ;
if ( suffix . has_value ( ) ) {
builder . append ( ' ' ) ;
builder . append ( suffix . value ( ) ) ;
}
2022-12-05 22:12:49 -03:00
return builder . to_deprecated_string ( ) ;
2021-05-08 16:44:22 -03:00
} ;
2021-08-02 06:10:56 -03:00
m_undo_action - > set_enabled ( can_undo ( ) & & ! text_is_secret ( ) ) ;
m_redo_action - > set_enabled ( can_redo ( ) & & ! text_is_secret ( ) ) ;
2021-05-08 08:40:33 -03:00
2022-07-11 14:32:29 -03:00
m_undo_action - > set_text ( make_action_text ( " &Undo " sv , document ( ) . undo_stack ( ) . undo_action_text ( ) ) ) ;
m_redo_action - > set_text ( make_action_text ( " &Redo " sv , document ( ) . undo_stack ( ) . redo_action_text ( ) ) ) ;
2021-05-08 16:44:22 -03:00
2021-05-08 08:40:33 -03:00
// FIXME: This is currently firing more often than it should.
// Ideally we'd only send this out when the undo stack modified state actually changes.
if ( on_modified_change )
on_modified_change ( document ( ) . is_modified ( ) ) ;
2021-05-08 08:16:37 -03:00
}
2021-09-21 18:02:48 -03:00
void TextEditor : : document_did_set_text ( AllowCallback allow_callback )
2019-11-23 13:41:14 -03:00
{
m_line_visual_data . clear ( ) ;
2019-12-09 13:45:40 -03:00
for ( size_t i = 0 ; i < m_document - > line_count ( ) ; + + i )
2019-11-23 13:41:14 -03:00
m_line_visual_data . append ( make < LineVisualData > ( ) ) ;
2021-09-21 18:02:48 -03:00
document_did_change ( allow_callback ) ;
2019-11-23 13:41:14 -03:00
}
2021-07-09 08:34:20 -03:00
void TextEditor : : document_did_set_cursor ( TextPosition const & position )
2019-11-30 09:05:17 -03:00
{
set_cursor ( position ) ;
}
2021-10-28 01:39:55 -03:00
void TextEditor : : cursor_did_change ( )
{
hide_autocomplete_if_needed ( ) ;
}
2022-12-04 15:02:33 -03:00
void TextEditor : : clipboard_content_did_change ( DeprecatedString const & mime_type )
2021-07-26 16:19:56 -03:00
{
2022-07-11 14:32:29 -03:00
m_paste_action - > set_enabled ( is_editable ( ) & & mime_type . starts_with ( " text/ " sv ) ) ;
2021-07-26 16:19:56 -03:00
}
2020-02-02 11:07:41 -03:00
void TextEditor : : set_document ( TextDocument & document )
2019-10-27 15:36:59 -03:00
{
if ( m_document . ptr ( ) = = & document )
return ;
if ( m_document )
m_document - > unregister_client ( * this ) ;
m_document = document ;
m_line_visual_data . clear ( ) ;
2019-12-09 13:45:40 -03:00
for ( size_t i = 0 ; i < m_document - > line_count ( ) ; + + i ) {
2019-10-27 15:36:59 -03:00
m_line_visual_data . append ( make < LineVisualData > ( ) ) ;
}
2020-08-05 12:19:37 -03:00
set_cursor ( 0 , 0 ) ;
2019-12-01 23:49:13 -03:00
if ( has_selection ( ) )
m_selection . clear ( ) ;
2019-10-27 15:36:59 -03:00
recompute_all_visual_lines ( ) ;
update ( ) ;
m_document - > register_client ( * this ) ;
}
2019-11-02 16:22:49 -03:00
2021-06-29 06:22:57 -03:00
void TextEditor : : rehighlight_if_needed ( )
2019-11-08 15:49:08 -03:00
{
2021-06-29 06:22:57 -03:00
if ( ! m_needs_rehighlight )
2019-11-08 15:49:08 -03:00
return ;
2022-02-07 03:08:09 -03:00
force_rehighlight ( ) ;
}
void TextEditor : : force_rehighlight ( )
{
2020-02-07 16:07:15 -03:00
if ( m_highlighter )
2020-03-15 20:05:06 -03:00
m_highlighter - > rehighlight ( palette ( ) ) ;
2021-06-29 06:22:57 -03:00
m_needs_rehighlight = false ;
2019-11-08 15:49:08 -03:00
}
2020-02-02 11:07:41 -03:00
2021-07-09 08:34:20 -03:00
Syntax : : Highlighter const * TextEditor : : syntax_highlighter ( ) const
2020-03-12 11:36:25 -03:00
{
2020-03-12 12:23:54 -03:00
return m_highlighter . ptr ( ) ;
2020-03-12 11:36:25 -03:00
}
2022-02-07 03:08:09 -03:00
Syntax : : Highlighter * TextEditor : : syntax_highlighter ( )
{
return m_highlighter . ptr ( ) ;
}
2021-02-07 11:15:10 -03:00
void TextEditor : : set_syntax_highlighter ( OwnPtr < Syntax : : Highlighter > highlighter )
2020-02-07 16:07:15 -03:00
{
if ( m_highlighter )
m_highlighter - > detach ( ) ;
m_highlighter = move ( highlighter ) ;
if ( m_highlighter ) {
m_highlighter - > attach ( * this ) ;
2021-06-29 06:22:57 -03:00
m_needs_rehighlight = true ;
2020-03-10 22:01:52 -03:00
} else
2022-03-29 10:31:26 -03:00
document ( ) . set_spans ( Syntax : : HighlighterClient : : span_collection_index , { } ) ;
2022-02-22 17:00:56 -03:00
if ( on_highlighter_change )
on_highlighter_change ( ) ;
2020-02-07 16:07:15 -03:00
}
2021-07-09 08:34:20 -03:00
AutocompleteProvider const * TextEditor : : autocomplete_provider ( ) const
2020-12-30 07:25:06 -03:00
{
return m_autocomplete_provider . ptr ( ) ;
}
void TextEditor : : set_autocomplete_provider ( OwnPtr < AutocompleteProvider > & & provider )
{
if ( m_autocomplete_provider )
m_autocomplete_provider - > detach ( ) ;
m_autocomplete_provider = move ( provider ) ;
if ( m_autocomplete_provider ) {
m_autocomplete_provider - > attach ( * this ) ;
if ( ! m_autocomplete_box )
m_autocomplete_box = make < AutocompleteBox > ( * this ) ;
}
if ( m_autocomplete_box )
2021-10-28 22:23:58 -03:00
hide_autocomplete ( ) ;
2020-12-30 07:25:06 -03:00
}
2021-07-09 08:34:20 -03:00
EditingEngine const * TextEditor : : editing_engine ( ) const
2021-01-02 07:59:55 -03:00
{
return m_editing_engine . ptr ( ) ;
}
void TextEditor : : set_editing_engine ( OwnPtr < EditingEngine > editing_engine )
{
if ( m_editing_engine )
m_editing_engine - > detach ( ) ;
m_editing_engine = move ( editing_engine ) ;
2021-02-23 16:42:32 -03:00
VERIFY ( m_editing_engine ) ;
2021-01-02 07:59:55 -03:00
m_editing_engine - > attach ( * this ) ;
m_cursor_state = true ;
update_cursor ( ) ;
stop_timer ( ) ;
start_timer ( 500 ) ;
}
2020-02-14 20:24:14 -03:00
int TextEditor : : line_height ( ) const
{
2023-01-05 13:13:55 -03:00
return static_cast < int > ( ceilf ( font ( ) . preferred_line_height ( ) ) ) ;
2020-02-14 20:24:14 -03:00
}
2020-05-18 11:38:28 -03:00
int TextEditor : : fixed_glyph_width ( ) const
2020-02-14 20:24:14 -03:00
{
2021-02-23 16:42:32 -03:00
VERIFY ( font ( ) . is_fixed_width ( ) ) ;
2020-05-18 11:38:28 -03:00
return font ( ) . glyph_width ( ' ' ) ;
2020-02-14 20:24:14 -03:00
}
2021-07-09 08:34:20 -03:00
void TextEditor : : set_icon ( Gfx : : Bitmap const * icon )
2020-06-29 15:34:42 -03:00
{
if ( m_icon = = icon )
return ;
m_icon = icon ;
update ( ) ;
}
2020-09-01 14:10:55 -03:00
void TextEditor : : set_visualize_trailing_whitespace ( bool enabled )
{
if ( m_visualize_trailing_whitespace = = enabled )
return ;
m_visualize_trailing_whitespace = enabled ;
update ( ) ;
}
2021-03-17 13:52:42 -03:00
void TextEditor : : set_visualize_leading_whitespace ( bool enabled )
{
if ( m_visualize_leading_whitespace = = enabled )
return ;
m_visualize_leading_whitespace = enabled ;
update ( ) ;
}
2021-01-01 10:59:11 -03:00
void TextEditor : : set_should_autocomplete_automatically ( bool value )
{
if ( value = = should_autocomplete_automatically ( ) )
return ;
if ( value ) {
2021-02-23 16:42:32 -03:00
VERIFY ( m_autocomplete_provider ) ;
2021-10-28 22:41:56 -03:00
m_autocomplete_timer = Core : : Timer : : create_single_shot ( m_automatic_autocomplete_delay_ms , [ this ] {
if ( m_autocomplete_box & & ! m_autocomplete_box - > is_visible ( ) )
try_show_autocomplete ( UserRequestedAutocomplete : : No ) ;
2023-01-11 13:31:10 -03:00
} ) . release_value_but_fixme_should_propagate_errors ( ) ;
2021-01-01 10:59:11 -03:00
return ;
}
remove_child ( * m_autocomplete_timer ) ;
m_autocomplete_timer = nullptr ;
}
2021-06-25 12:48:51 -03:00
2022-05-07 11:03:33 -03:00
void TextEditor : : set_substitution_code_point ( Optional < u32 > code_point )
2021-06-25 12:48:51 -03:00
{
2022-05-07 11:03:33 -03:00
if ( code_point . has_value ( ) )
VERIFY ( is_unicode ( code_point . value ( ) ) ) ;
2021-06-25 12:48:51 -03:00
m_substitution_string_data . clear ( ) ;
2022-05-07 11:03:33 -03:00
m_substitution_code_point = move ( code_point ) ;
2021-06-25 12:48:51 -03:00
}
2021-01-02 07:59:55 -03:00
int TextEditor : : number_of_visible_lines ( ) const
{
return visible_content_rect ( ) . height ( ) / line_height ( ) ;
}
2021-01-01 10:59:11 -03:00
2022-12-08 06:07:50 -03:00
void TextEditor : : set_relative_line_number ( bool relative )
{
if ( m_relative_line_number = = relative )
return ;
m_relative_line_number = relative ;
recompute_all_visual_lines ( ) ;
update ( ) ;
}
2021-04-09 19:09:44 -03:00
void TextEditor : : set_ruler_visible ( bool visible )
{
if ( m_ruler_visible = = visible )
return ;
m_ruler_visible = visible ;
recompute_all_visual_lines ( ) ;
update ( ) ;
}
2021-06-11 22:50:23 -03:00
void TextEditor : : set_gutter_visible ( bool visible )
{
if ( m_gutter_visible = = visible )
return ;
m_gutter_visible = visible ;
recompute_all_visual_lines ( ) ;
update ( ) ;
}
2021-09-11 12:08:01 -03:00
void TextEditor : : set_cursor_line_highlighting ( bool highlighted )
{
if ( m_cursor_line_highlighting = = highlighted )
return ;
m_cursor_line_highlighting = highlighted ;
update ( ) ;
}
2021-05-08 18:38:01 -03:00
void TextEditor : : undo ( )
{
clear_selection ( ) ;
document ( ) . undo ( ) ;
}
void TextEditor : : redo ( )
{
clear_selection ( ) ;
document ( ) . redo ( ) ;
}
2021-08-02 06:10:56 -03:00
void TextEditor : : set_text_is_secret ( bool text_is_secret )
{
m_text_is_secret = text_is_secret ;
document_did_update_undo_stack ( ) ;
did_update_selection ( ) ;
}
2022-03-29 10:33:46 -03:00
TextRange TextEditor : : find_text ( StringView needle , SearchDirection direction , GUI : : TextDocument : : SearchShouldWrap should_wrap , bool use_regex , bool match_case )
{
GUI : : TextRange range { } ;
if ( direction = = SearchDirection : : Forward ) {
range = document ( ) . find_next ( needle ,
m_search_result_index . has_value ( ) ? m_search_results [ * m_search_result_index ] . end ( ) : GUI : : TextPosition { } ,
should_wrap , use_regex , match_case ) ;
} else {
range = document ( ) . find_previous ( needle ,
m_search_result_index . has_value ( ) ? m_search_results [ * m_search_result_index ] . start ( ) : GUI : : TextPosition { } ,
should_wrap , use_regex , match_case ) ;
}
if ( ! range . is_valid ( ) ) {
reset_search_results ( ) ;
return { } ;
}
auto all_results = document ( ) . find_all ( needle , use_regex , match_case ) ;
on_search_results ( range , all_results ) ;
return range ;
}
void TextEditor : : reset_search_results ( )
{
m_search_result_index . clear ( ) ;
m_search_results . clear ( ) ;
document ( ) . set_spans ( search_results_span_collection_index , { } ) ;
update ( ) ;
}
void TextEditor : : on_search_results ( GUI : : TextRange current , Vector < GUI : : TextRange > all_results )
{
m_search_result_index . clear ( ) ;
m_search_results . clear ( ) ;
set_cursor ( current . start ( ) ) ;
if ( auto it = all_results . find ( current ) ; it - > is_valid ( ) )
m_search_result_index = it . index ( ) ;
m_search_results = move ( all_results ) ;
Vector < GUI : : TextDocumentSpan > spans ;
for ( size_t i = 0 ; i < m_search_results . size ( ) ; + + i ) {
auto & result = m_search_results [ i ] ;
GUI : : TextDocumentSpan span ;
span . range = result ;
span . attributes . background_color = palette ( ) . hover_highlight ( ) ;
span . attributes . color = Color : : from_argb ( 0xff000000 ) ; // So text without spans from a highlighter will have color
if ( i = = m_search_result_index ) {
span . attributes . bold = true ;
2023-03-15 09:35:00 -03:00
span . attributes . underline_style = Gfx : : TextAttributes : : UnderlineStyle : : Solid ;
2022-03-29 10:33:46 -03:00
}
spans . append ( move ( span ) ) ;
}
document ( ) . set_spans ( search_results_span_collection_index , move ( spans ) ) ;
update ( ) ;
}
2023-02-23 12:33:48 -03:00
void TextEditor : : highlighter_did_set_folding_regions ( Vector < GUI : : TextDocumentFoldingRegion > folding_regions )
{
document ( ) . set_folding_regions ( move ( folding_regions ) ) ;
recompute_all_visual_lines ( ) ;
}
2020-02-02 11:07:41 -03:00
}