2020-01-18 11:38:21 +03:00
/*
* Copyright ( c ) 2018 - 2020 , Andreas Kling < kling @ serenityos . org >
*
2021-04-22 11:24:48 +03:00
* SPDX - License - Identifier : BSD - 2 - Clause
2020-01-18 11:38:21 +03:00
*/
2021-01-24 17:28:26 +03:00
# include <AK/Debug.h>
2020-12-30 13:25:06 +03:00
# include <AK/ScopeGuard.h>
2019-06-07 12:46:02 +03:00
# include <AK/StringBuilder.h>
2020-10-15 21:46:52 +03:00
# include <AK/TemporaryChange.h>
2020-04-20 22:31:49 +03:00
# include <LibCore/Timer.h>
2020-02-06 22:33:02 +03:00
# include <LibGUI/Action.h>
2020-12-30 13:25:06 +03:00
# include <LibGUI/AutocompleteProvider.h>
2020-02-06 22:33:02 +03:00
# include <LibGUI/Clipboard.h>
2021-01-02 13:59:55 +03:00
# include <LibGUI/EditingEngine.h>
2020-02-06 22:33:02 +03:00
# include <LibGUI/InputBox.h>
# include <LibGUI/Menu.h>
# include <LibGUI/Painter.h>
2021-01-02 13:59:55 +03:00
# include <LibGUI/RegularEditingEngine.h>
2021-04-13 17:18:20 +03:00
# include <LibGUI/Scrollbar.h>
2020-02-06 22:33:02 +03:00
# include <LibGUI/TextEditor.h>
# include <LibGUI/Window.h>
2020-02-15 01:02:47 +03:00
# include <LibGfx/Bitmap.h>
2020-02-15 02:24:14 +03:00
# include <LibGfx/Font.h>
2020-12-28 17:51:43 +03:00
# include <LibGfx/FontDatabase.h>
2020-02-07 22:07:15 +03:00
# include <LibGfx/Palette.h>
2021-02-07 17:15:10 +03:00
# include <LibSyntax/Highlighter.h>
2019-06-07 12:46:02 +03:00
# include <ctype.h>
2019-03-07 19:06:11 +03:00
# include <fcntl.h>
2020-08-15 17:27:54 +03:00
# include <math.h>
2019-03-07 19:06:11 +03:00
# include <stdio.h>
2019-06-07 12:46:02 +03:00
# include <unistd.h>
2019-03-07 02:31:06 +03:00
2021-01-03 02:30:13 +03:00
REGISTER_WIDGET ( GUI , TextEditor )
2020-02-02 17:07:41 +03:00
namespace GUI {
2020-02-23 14:07:13 +03:00
TextEditor : : TextEditor ( Type type )
: m_type ( type )
2019-03-07 02:31:06 +03:00
{
2020-09-25 21:43:35 +03:00
REGISTER_STRING_PROPERTY ( " text " , text , set_text ) ;
2021-03-08 19:38:43 +03:00
REGISTER_STRING_PROPERTY ( " placeholder " , placeholder , set_placeholder ) ;
2020-12-28 15:01:41 +03:00
REGISTER_ENUM_PROPERTY ( " mode " , mode , set_mode , Mode ,
{ Editable , " Editable " } ,
{ ReadOnly , " ReadOnly " } ,
{ DisplayOnly , " DisplayOnly " } ) ;
2020-09-25 21:43:35 +03:00
2020-10-30 12:58:27 +03:00
set_focus_policy ( GUI : : FocusPolicy : : StrongFocus ) ;
2020-05-17 22:51:58 +03:00
set_accepts_emoji_input ( true ) ;
2020-09-11 15:25:48 +03:00
set_override_cursor ( Gfx : : StandardCursor : : IBeam ) ;
2019-12-25 00:01:32 +03:00
set_background_role ( ColorRole : : Base ) ;
set_foreground_role ( ColorRole : : BaseText ) ;
2020-02-02 17:07:41 +03:00
set_document ( TextDocument : : create ( ) ) ;
2020-09-02 00:57:30 +03:00
if ( is_single_line ( ) )
set_visualize_trailing_whitespace ( false ) ;
2019-03-16 18:54:51 +03:00
set_scrollbars_enabled ( is_multi_line ( ) ) ;
2020-05-18 17:40:40 +03:00
if ( is_multi_line ( ) )
2020-12-29 20:25:13 +03:00
set_font ( Gfx : : FontDatabase : : default_fixed_width_font ( ) ) ;
2019-06-07 11:42:43 +03:00
vertical_scrollbar ( ) . set_step ( line_height ( ) ) ;
2019-03-19 03:41:00 +03:00
m_cursor = { 0 , 0 } ;
2020-04-20 22:31:49 +03:00
m_automatic_selection_scroll_timer = add < Core : : Timer > ( 100 , [ this ] {
automatic_selection_scroll_timer_fired ( ) ;
} ) ;
m_automatic_selection_scroll_timer - > stop ( ) ;
2019-04-18 13:25:00 +03:00
create_actions ( ) ;
2021-01-02 13:59:55 +03:00
set_editing_engine ( make < RegularEditingEngine > ( ) ) ;
2019-03-07 02:31:06 +03:00
}
2020-02-02 17:07:41 +03:00
TextEditor : : ~ TextEditor ( )
2019-03-07 02:31:06 +03:00
{
2019-10-27 20:00:07 +03:00
if ( m_document )
m_document - > unregister_client ( * this ) ;
2019-03-07 02:31:06 +03:00
}
2020-02-02 17:07:41 +03:00
void TextEditor : : create_actions ( )
2019-04-18 13:25:00 +03:00
{
2020-02-02 17: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 10:50:39 +03:00
m_undo_action - > set_enabled ( false ) ;
m_redo_action - > set_enabled ( false ) ;
2020-02-02 17:07:41 +03:00
m_cut_action = CommonActions : : make_cut_action ( [ & ] ( auto & ) { cut ( ) ; } , this ) ;
m_copy_action = CommonActions : : make_copy_action ( [ & ] ( auto & ) { copy ( ) ; } , this ) ;
m_paste_action = CommonActions : : make_paste_action ( [ & ] ( auto & ) { paste ( ) ; } , this ) ;
m_delete_action = CommonActions : : make_delete_action ( [ & ] ( auto & ) { do_delete ( ) ; } , this ) ;
2020-02-10 21:39:30 +03:00
if ( is_multi_line ( ) ) {
m_go_to_line_action = Action : : create (
" Go to line... " , { Mod_Ctrl , Key_L } , Gfx : : Bitmap : : load_from_file ( " /res/icons/16x16/go-forward.png " ) , [ this ] ( auto & ) {
2020-07-16 16:54:42 +03:00
String value ;
2021-02-20 14:03:28 +03:00
if ( InputBox : : show ( window ( ) , value , " Line: " , " Go to line " ) = = InputBox : : ExecOK ) {
2020-11-10 11: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 21:39:30 +03:00
}
} ,
this ) ;
}
2020-07-03 21:54:38 +03:00
m_select_all_action = CommonActions : : make_select_all_action ( [ this ] ( auto & ) { select_all ( ) ; } , this ) ;
2019-04-18 13:25:00 +03:00
}
2020-02-02 17:07:41 +03:00
void TextEditor : : set_text ( const StringView & text )
2019-03-07 02:31:06 +03:00
{
2019-04-09 17:20:36 +03:00
m_selection . clear ( ) ;
2019-10-27 18:10:07 +03:00
document ( ) . set_text ( text ) ;
2019-03-16 18:54:51 +03:00
update_content_size ( ) ;
2019-08-25 09:43:01 +03:00
recompute_all_visual_lines ( ) ;
2019-03-20 20:12:56 +03:00
if ( is_single_line ( ) )
2019-10-27 18:10:07 +03:00
set_cursor ( 0 , line ( 0 ) . length ( ) ) ;
2019-03-20 20:12:56 +03:00
else
set_cursor ( 0 , 0 ) ;
2019-04-12 03:52:34 +03:00
did_update_selection ( ) ;
2019-03-07 02:31:06 +03:00
update ( ) ;
}
2020-02-02 17:07:41 +03:00
void TextEditor : : update_content_size ( )
2019-03-07 15:54:02 +03:00
{
2019-03-16 18:54:51 +03:00
int content_width = 0 ;
2019-08-29 07:26:24 +03:00
int content_height = 0 ;
2019-10-27 20:00:07 +03:00
for ( auto & line : m_line_visual_data ) {
content_width = max ( line . visual_rect . width ( ) , content_width ) ;
content_height + = line . visual_rect . height ( ) ;
2019-08-29 07:26:24 +03:00
}
2019-03-17 01:16:37 +03:00
content_width + = m_horizontal_content_padding * 2 ;
2019-04-25 00:42:49 +03:00
if ( is_right_text_alignment ( m_text_alignment ) )
content_width = max ( frame_inner_rect ( ) . width ( ) , content_width ) ;
2019-08-29 07:26:24 +03:00
2019-03-16 18:54:51 +03:00
set_content_size ( { content_width , content_height } ) ;
set_size_occupied_by_fixed_elements ( { ruler_width ( ) , 0 } ) ;
2019-03-07 02:31:06 +03:00
}
2021-01-03 02:11:56 +03:00
TextPosition TextEditor : : text_position_at_content_position ( const Gfx : : IntPoint & content_position ) const
2019-03-07 02:31:06 +03:00
{
2021-01-03 02:11:56 +03:00
auto position = content_position ;
2020-06-29 21:34:42 +03:00
if ( is_single_line ( ) & & icon ( ) )
2021-04-12 21:47:09 +03:00
position . translate_by ( - ( icon_size ( ) + icon_padding ( ) ) , 0 ) ;
2020-06-29 21:34:42 +03:00
2019-12-09 19:45:40 +03:00
size_t line_index = 0 ;
2019-08-25 09:43:01 +03:00
2021-02-15 18:59:13 +03:00
if ( position . y ( ) > = 0 ) {
if ( is_wrapping_enabled ( ) ) {
for ( size_t i = 0 ; i < line_count ( ) ; + + i ) {
auto & rect = m_line_visual_data [ i ] . visual_rect ;
if ( position . y ( ) > = rect . top ( ) & & position . y ( ) < = rect . bottom ( ) ) {
line_index = i ;
break ;
}
if ( position . y ( ) > rect . bottom ( ) )
line_index = line_count ( ) - 1 ;
2019-10-27 20:00:07 +03:00
}
2021-02-15 18:59:13 +03:00
} else {
line_index = ( size_t ) ( position . y ( ) / line_height ( ) ) ;
2019-08-25 09:43:01 +03:00
}
2021-02-15 18:59:13 +03:00
line_index = max ( ( size_t ) 0 , min ( line_index , line_count ( ) - 1 ) ) ;
2019-08-25 09:43:01 +03:00
}
2020-05-18 17:38:28 +03:00
size_t column_index = 0 ;
2019-04-24 23:24:16 +03:00
switch ( m_text_alignment ) {
2020-02-06 13:56:38 +03:00
case Gfx : : TextAlignment : : CenterLeft :
2020-11-13 08:29:55 +03:00
for_each_visual_line ( line_index , [ & ] ( const Gfx : : IntRect & rect , auto & view , size_t start_of_line , [[maybe_unused]] bool is_last_visual_line ) {
if ( is_multi_line ( ) & & ! rect . contains_vertically ( position . y ( ) ) & & ! is_last_visual_line )
2020-05-18 18:55:21 +03:00
return IterationDecision : : Continue ;
column_index = start_of_line ;
if ( position . x ( ) < = 0 ) {
// We're outside the text on the left side, put cursor at column 0 on this visual line.
} else {
int glyph_x = 0 ;
size_t i = 0 ;
for ( ; i < view . length ( ) ; + + i ) {
2020-08-05 23:31:20 +03:00
int advance = font ( ) . glyph_width ( view . code_points ( ) [ i ] ) + font ( ) . glyph_spacing ( ) ;
2020-05-18 18:55:21 +03:00
if ( ( glyph_x + ( advance / 2 ) ) > = position . x ( ) )
break ;
glyph_x + = advance ;
2019-08-25 09:43:01 +03:00
}
2020-05-18 18:55:21 +03:00
column_index + = i ;
2020-05-18 17:38:28 +03:00
}
2020-05-18 18:55:21 +03:00
return IterationDecision : : Break ;
2020-05-18 17:38:28 +03:00
} ) ;
2019-04-24 23:24:16 +03:00
break ;
2020-02-06 13:56:38 +03:00
case Gfx : : TextAlignment : : CenterRight :
2019-08-25 09:43:01 +03:00
// FIXME: Support right-aligned line wrapping, I guess.
2021-02-23 22:42:32 +03:00
VERIFY ( ! is_wrapping_enabled ( ) ) ;
2020-05-18 17: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 23:24:16 +03:00
break ;
default :
2021-02-23 22:42:32 +03:00
VERIFY_NOT_REACHED ( ) ;
2019-04-24 23:24:16 +03:00
}
2019-12-09 19:45:40 +03:00
column_index = max ( ( size_t ) 0 , min ( column_index , line ( line_index ) . length ( ) ) ) ;
2019-03-07 17:03:38 +03:00
return { line_index , column_index } ;
}
2021-01-03 02:11:56 +03:00
TextPosition TextEditor : : text_position_at ( const Gfx : : IntPoint & widget_position ) const
{
auto content_position = widget_position ;
2021-04-12 21:47:09 +03:00
content_position . translate_by ( horizontal_scrollbar ( ) . value ( ) , vertical_scrollbar ( ) . value ( ) ) ;
content_position . translate_by ( - ( m_horizontal_content_padding + ruler_width ( ) ) , 0 ) ;
content_position . translate_by ( - frame_thickness ( ) , - frame_thickness ( ) ) ;
2021-01-03 02:11:56 +03:00
return text_position_at_content_position ( content_position ) ;
}
2020-02-02 17:07:41 +03:00
void TextEditor : : doubleclick_event ( MouseEvent & event )
2019-04-25 23:29:25 +03:00
{
2020-02-02 17:07:41 +03:00
if ( event . button ( ) ! = MouseButton : : Left )
2019-04-25 23:29:25 +03:00
return ;
2020-07-15 00:02:46 +03:00
if ( is_displayonly ( ) )
return ;
2021-04-22 00:48:26 +03:00
if ( ! current_line ( ) . can_select ( ) )
return ;
2019-11-08 21:49:08 +03:00
// NOTE: This ensures that spans are updated before we look at them.
flush_pending_change_notification_if_needed ( ) ;
2019-05-16 01:40:55 +03:00
m_triple_click_timer . start ( ) ;
2019-04-25 23:29:25 +03:00
m_in_drag_select = false ;
auto start = text_position_at ( event . position ( ) ) ;
auto end = start ;
2019-10-27 18:10:07 +03:00
if ( ! document ( ) . has_spans ( ) ) {
2020-05-24 20:32:08 +03:00
start = document ( ) . first_word_break_before ( start , false ) ;
2020-05-24 01:41:34 +03:00
end = document ( ) . first_word_break_after ( end ) ;
2019-10-27 13:10:32 +03:00
} else {
2019-10-27 18:10:07 +03:00
for ( auto & span : document ( ) . spans ( ) ) {
2019-10-27 13:10:32 +03:00
if ( ! span . range . contains ( start ) )
continue ;
start = span . range . start ( ) ;
end = span . range . end ( ) ;
end . set_column ( end . column ( ) + 1 ) ;
2019-04-25 23:29:25 +03:00
break ;
2019-10-27 13:10:32 +03:00
}
2019-04-25 23:29:25 +03:00
}
m_selection . set ( start , end ) ;
set_cursor ( end ) ;
update ( ) ;
did_update_selection ( ) ;
}
2020-02-02 17:07:41 +03:00
void TextEditor : : mousedown_event ( MouseEvent & event )
2019-03-07 17:03:38 +03:00
{
2020-02-02 17:07:41 +03:00
if ( event . button ( ) ! = MouseButton : : Left ) {
2019-05-16 01:15:58 +03:00
return ;
}
2019-03-08 19:53:02 +03:00
2020-07-15 00:02:46 +03:00
if ( on_mousedown )
on_mousedown ( ) ;
if ( is_displayonly ( ) )
return ;
2019-05-16 01:40:55 +03:00
if ( m_triple_click_timer . is_valid ( ) & & m_triple_click_timer . elapsed ( ) < 250 ) {
2020-02-02 14:34:39 +03:00
m_triple_click_timer = Core : : ElapsedTimer ( ) ;
2019-05-16 01:40:55 +03:00
2020-02-02 17:07:41 +03:00
TextPosition start ;
TextPosition end ;
2019-05-16 01:40:55 +03:00
if ( is_multi_line ( ) ) {
// select *current* line
2020-02-02 17:07:41 +03:00
start = TextPosition ( m_cursor . line ( ) , 0 ) ;
end = TextPosition ( m_cursor . line ( ) , line ( m_cursor . line ( ) ) . length ( ) ) ;
2019-05-16 01:40:55 +03:00
} else {
// select *whole* line
2020-02-02 17:07:41 +03:00
start = TextPosition ( 0 , 0 ) ;
end = TextPosition ( line_count ( ) - 1 , line ( line_count ( ) - 1 ) . length ( ) ) ;
2019-05-16 01:40:55 +03:00
}
m_selection . set ( start , end ) ;
set_cursor ( end ) ;
2021-01-11 13:42:06 +03:00
update ( ) ;
did_update_selection ( ) ;
2019-05-16 01:40:55 +03:00
return ;
}
2019-05-16 01:15:58 +03:00
if ( event . modifiers ( ) & Mod_Shift ) {
if ( ! has_selection ( ) )
2019-06-07 12:46:02 +03:00
m_selection . set ( m_cursor , { } ) ;
2019-05-16 01:15:58 +03:00
} else {
m_selection . clear ( ) ;
}
2019-03-08 19:53:02 +03:00
2019-05-16 01:15:58 +03:00
m_in_drag_select = true ;
2020-04-20 22:31:49 +03:00
m_automatic_selection_scroll_timer - > start ( ) ;
2019-03-08 19:53:02 +03:00
2019-05-16 01:15:58 +03:00
set_cursor ( text_position_at ( event . position ( ) ) ) ;
2019-03-08 20:28:24 +03:00
2019-05-16 01:15:58 +03:00
if ( ! ( event . modifiers ( ) & Mod_Shift ) ) {
if ( ! has_selection ( ) )
2019-06-07 12:46:02 +03:00
m_selection . set ( m_cursor , { } ) ;
2019-03-08 19:53:02 +03:00
}
2019-05-16 01: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 19:53:02 +03:00
}
2020-02-02 17:07:41 +03:00
void TextEditor : : mouseup_event ( MouseEvent & event )
2019-03-08 19:53:02 +03:00
{
2020-02-02 17:07:41 +03:00
if ( event . button ( ) = = MouseButton : : Left ) {
2019-03-08 19:53:02 +03:00
if ( m_in_drag_select ) {
m_in_drag_select = false ;
}
return ;
}
}
2020-02-02 17:07:41 +03:00
void TextEditor : : mousemove_event ( MouseEvent & event )
2019-03-08 19:53:02 +03:00
{
2020-04-20 22:31:49 +03:00
m_last_mousemove_position = event . position ( ) ;
2020-04-24 20:14:22 +03:00
if ( m_in_drag_select & & ( rect ( ) . contains ( event . position ( ) ) | | ! m_automatic_selection_scroll_timer - > is_active ( ) ) ) {
2019-03-08 19:53:02 +03:00
set_cursor ( text_position_at ( event . position ( ) ) ) ;
2019-03-08 20:28:24 +03:00
m_selection . set_end ( m_cursor ) ;
2019-04-12 03:52:34 +03:00
did_update_selection ( ) ;
2019-03-08 02:49:45 +03:00
update ( ) ;
2019-03-08 19:53:02 +03:00
return ;
2019-03-08 02:49:45 +03:00
}
2019-03-07 02:31:06 +03:00
}
2020-04-20 22:31:49 +03:00
void TextEditor : : automatic_selection_scroll_timer_fired ( )
{
if ( ! m_in_drag_select ) {
m_automatic_selection_scroll_timer - > stop ( ) ;
return ;
}
set_cursor ( text_position_at ( m_last_mousemove_position ) ) ;
m_selection . set_end ( m_cursor ) ;
did_update_selection ( ) ;
update ( ) ;
}
2020-02-02 17:07:41 +03:00
int TextEditor : : ruler_width ( ) const
2019-03-07 22:05:05 +03:00
{
2019-03-15 19:54:05 +03:00
if ( ! m_ruler_visible )
return 0 ;
2020-08-15 17:27:54 +03:00
int line_count_digits = static_cast < int > ( log10 ( line_count ( ) ) ) + 1 ;
2020-08-21 10:58:05 +03:00
constexpr size_t padding = 20 ;
return line_count ( ) < 10 ? ( line_count_digits + 1 ) * font ( ) . glyph_width ( ' x ' ) + padding : line_count_digits * font ( ) . glyph_width ( ' x ' ) + padding ;
2019-03-07 22:05:05 +03:00
}
2020-06-10 11:57:59 +03:00
Gfx : : IntRect TextEditor : : ruler_content_rect ( size_t line_index ) const
2019-03-07 22:05:05 +03:00
{
2019-03-15 19:54:05 +03:00
if ( ! m_ruler_visible )
2019-06-07 12:46:02 +03:00
return { } ;
2019-03-07 22:05:05 +03:00
return {
2019-03-17 01:16:37 +03:00
0 - ruler_width ( ) + horizontal_scrollbar ( ) . value ( ) ,
2019-08-25 08:14:02 +03:00
line_content_rect ( line_index ) . y ( ) ,
2019-03-07 22:05:05 +03:00
ruler_width ( ) ,
2019-08-25 08:14:02 +03:00
line_content_rect ( line_index ) . height ( )
} ;
}
2020-06-10 11:57:59 +03:00
Gfx : : IntRect TextEditor : : ruler_rect_in_inner_coordinates ( ) const
2019-08-25 08:14:02 +03:00
{
return { 0 , 0 , ruler_width ( ) , height ( ) - height_occupied_by_horizontal_scrollbar ( ) } ;
}
2020-06-10 11:57:59 +03:00
Gfx : : IntRect TextEditor : : visible_text_rect_in_inner_coordinates ( ) const
2019-08-25 08:14:02 +03:00
{
return {
2019-09-01 21:04:25 +03:00
m_horizontal_content_padding + ( m_ruler_visible ? ( ruler_rect_in_inner_coordinates ( ) . right ( ) + 1 ) : 0 ) ,
2019-08-25 08:14:02 +03:00
0 ,
2019-09-01 21:04:25 +03:00
frame_inner_rect ( ) . width ( ) - ( m_horizontal_content_padding * 2 ) - width_occupied_by_vertical_scrollbar ( ) - ruler_width ( ) ,
frame_inner_rect ( ) . height ( ) - height_occupied_by_horizontal_scrollbar ( )
2019-03-07 22:05:05 +03:00
} ;
}
2020-02-02 17:07:41 +03:00
void TextEditor : : paint_event ( PaintEvent & event )
2019-03-07 02:31:06 +03:00
{
2020-04-28 14:15:53 +03:00
Color widget_background_color = palette ( ) . color ( is_enabled ( ) ? background_role ( ) : Gfx : : ColorRole : : Window ) ;
2019-11-08 21:49:08 +03:00
// NOTE: This ensures that spans are updated before we look at them.
flush_pending_change_notification_if_needed ( ) ;
2020-02-02 17:07:41 +03:00
Frame : : paint_event ( event ) ;
2019-03-28 18:14:26 +03:00
2020-02-02 17:07:41 +03:00
Painter painter ( * this ) ;
2019-03-29 17:01:54 +03:00
painter . add_clip_rect ( widget_inner_rect ( ) ) ;
painter . add_clip_rect ( event . rect ( ) ) ;
2019-12-24 22:57:54 +03:00
painter . fill_rect ( event . rect ( ) , widget_background_color ) ;
2019-03-07 22:05:05 +03:00
2021-03-16 03:11:43 +03:00
if ( is_displayonly ( ) & & is_focused ( ) ) {
2020-07-15 00:02:46 +03:00
widget_background_color = palette ( ) . selection ( ) ;
Gfx : : IntRect display_rect {
widget_inner_rect ( ) . x ( ) + 1 ,
widget_inner_rect ( ) . y ( ) + 1 ,
2020-12-13 01:17:41 +03:00
widget_inner_rect ( ) . width ( ) - 2 ,
2020-07-15 00:02:46 +03:00
widget_inner_rect ( ) . height ( ) - 2
} ;
painter . add_clip_rect ( display_rect ) ;
painter . add_clip_rect ( event . rect ( ) ) ;
painter . fill_rect ( event . rect ( ) , widget_background_color ) ;
}
2019-04-25 02:09:44 +03:00
painter . translate ( frame_thickness ( ) , frame_thickness ( ) ) ;
2019-08-25 08:14:02 +03:00
auto ruler_rect = ruler_rect_in_inner_coordinates ( ) ;
2019-03-15 19:54:05 +03:00
if ( m_ruler_visible ) {
2020-02-19 13: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 ( ) ) ;
2019-03-15 19:54:05 +03:00
}
2019-03-07 22:05:05 +03:00
2019-03-16 18:54:51 +03:00
painter . translate ( - horizontal_scrollbar ( ) . value ( ) , - vertical_scrollbar ( ) . value ( ) ) ;
2019-03-19 04:45:23 +03:00
if ( m_ruler_visible )
painter . translate ( ruler_width ( ) , 0 ) ;
2019-03-07 02:31:06 +03:00
2019-12-09 19: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 17:03:38 +03:00
2019-03-08 20:28:24 +03:00
auto selection = normalized_selection ( ) ;
bool has_selection = selection . is_valid ( ) ;
2019-03-08 02:49:45 +03:00
2019-03-15 19:54:05 +03:00
if ( m_ruler_visible ) {
2019-12-09 19:45:40 +03:00
for ( size_t i = first_visible_line ; i < = last_visible_line ; + + i ) {
2019-03-15 19:54:05 +03:00
bool is_current_line = i = = m_cursor . line ( ) ;
auto ruler_line_rect = ruler_content_rect ( i ) ;
painter . draw_text (
2019-09-06 20:24:16 +03:00
ruler_line_rect . shrunken ( 2 , 0 ) . translated ( 0 , m_line_spacing / 2 ) ,
2019-07-27 22:20:38 +03:00
String : : number ( i + 1 ) ,
2020-12-31 03:49:05 +03:00
is_current_line ? font ( ) . bold_variant ( ) : font ( ) ,
2020-02-06 13:56:38 +03:00
Gfx : : TextAlignment : : TopRight ,
2020-02-19 13:11:37 +03:00
is_current_line ? palette ( ) . ruler_active_text ( ) : palette ( ) . ruler_inactive_text ( ) ) ;
2019-03-15 19:54:05 +03:00
}
2019-03-07 22:05:05 +03:00
}
2020-06-10 11:57:59 +03:00
Gfx : : IntRect text_clip_rect {
2019-08-25 08:14:02 +03:00
( m_ruler_visible ? ( ruler_rect_in_inner_coordinates ( ) . right ( ) + frame_thickness ( ) + 1 ) : frame_thickness ( ) ) ,
frame_thickness ( ) ,
width ( ) - width_occupied_by_vertical_scrollbar ( ) - ruler_width ( ) ,
height ( ) - height_occupied_by_horizontal_scrollbar ( )
} ;
2020-06-06 20:35:07 +03:00
if ( m_ruler_visible )
2021-04-12 21:47:09 +03:00
text_clip_rect . translate_by ( - ruler_width ( ) , 0 ) ;
text_clip_rect . translate_by ( horizontal_scrollbar ( ) . value ( ) , vertical_scrollbar ( ) . value ( ) ) ;
2019-08-25 08:14:02 +03:00
painter . add_clip_rect ( text_clip_rect ) ;
2019-03-07 22:05:05 +03:00
2021-02-27 19: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 19: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 11:23:11 +03:00
bool physical_line_has_selection = has_selection & & line_index > = selection . start ( ) . line ( ) & & line_index < = selection . end ( ) . line ( ) ;
2019-12-09 19:45:40 +03:00
size_t first_visual_line_with_selection = 0 ;
size_t last_visual_line_with_selection = 0 ;
2019-08-25 11:23:11 +03:00
if ( physical_line_has_selection ) {
2019-08-25 15:04:46 +03:00
if ( selection . start ( ) . line ( ) < line_index )
2019-08-25 11:23:11 +03:00
first_visual_line_with_selection = 0 ;
2019-08-25 15:04:46 +03:00
else
2019-10-27 20:00:07 +03:00
first_visual_line_with_selection = visual_line_containing ( line_index , selection . start ( ) . column ( ) ) ;
2019-08-25 15:04:46 +03:00
if ( selection . end ( ) . line ( ) > line_index )
2019-10-27 20:00:07 +03:00
last_visual_line_with_selection = m_line_visual_data [ line_index ] . visual_line_breaks . size ( ) ;
2019-08-25 15:04:46 +03:00
else
2019-10-27 20:00:07 +03:00
last_visual_line_with_selection = visual_line_containing ( line_index , selection . end ( ) . column ( ) ) ;
2019-08-25 11:23:11 +03:00
}
2019-12-09 19: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 11:23:11 +03:00
2019-12-09 19:45:40 +03:00
size_t visual_line_index = 0 ;
2020-11-13 08:29:55 +03:00
for_each_visual_line ( line_index , [ & ] ( const Gfx : : IntRect & visual_line_rect , auto & visual_line_text , size_t start_of_visual_line , [[maybe_unused]] bool is_last_visual_line ) {
2019-08-25 11:23:11 +03:00
if ( is_multi_line ( ) & & line_index = = m_cursor . line ( ) )
2019-12-24 22:57:54 +03:00
painter . fill_rect ( visual_line_rect , widget_background_color . darkened ( 0.9f ) ) ;
2021-05-01 22:10:08 +03:00
if constexpr ( TEXTEDITOR_DEBUG )
painter . draw_rect ( visual_line_rect , Color : : Cyan ) ;
2020-09-20 22:20:24 +03:00
if ( ! placeholder ( ) . is_empty ( ) & & document ( ) . is_empty ( ) & & ! is_focused ( ) & & line_index = = 0 ) {
auto line_rect = visual_line_rect ;
line_rect . set_width ( font ( ) . width ( placeholder ( ) ) ) ;
2020-09-20 23:22:54 +03:00
painter . draw_text ( line_rect , placeholder ( ) , m_text_alignment , palette ( ) . color ( Gfx : : ColorRole : : PlaceholderText ) ) ;
2020-09-20 22:20:24 +03:00
} else if ( ! document ( ) . has_spans ( ) ) {
2019-10-25 22:05:06 +03:00
// Fast-path for plain text
2020-03-04 23:19:35 +03:00
auto color = palette ( ) . color ( is_enabled ( ) ? foreground_role ( ) : Gfx : : ColorRole : : DisabledText ) ;
2021-03-16 03:11:43 +03:00
if ( is_displayonly ( ) & & is_focused ( ) )
2020-07-15 00:02:46 +03:00
color = palette ( ) . color ( is_enabled ( ) ? Gfx : : ColorRole : : SelectionText : Gfx : : ColorRole : : DisabledText ) ;
2020-03-04 23:19:35 +03:00
painter . draw_text ( visual_line_rect , visual_line_text , m_text_alignment , color ) ;
2019-10-25 22:05:06 +03:00
} else {
2021-02-27 19:32:55 +03:00
auto unspanned_color = palette ( ) . color ( is_enabled ( ) ? foreground_role ( ) : Gfx : : ColorRole : : DisabledText ) ;
2021-03-16 03:11:43 +03:00
if ( is_displayonly ( ) & & is_focused ( ) )
2021-02-27 19:32:55 +03:00
unspanned_color = palette ( ) . color ( is_enabled ( ) ? Gfx : : ColorRole : : SelectionText : Gfx : : ColorRole : : DisabledText ) ;
RefPtr < Gfx : : Font > unspanned_font = this - > font ( ) ;
size_t next_column = 0 ;
Gfx : : IntRect span_rect = { visual_line_rect . location ( ) , { 0 , line_height ( ) } } ;
auto draw_text_helper = [ & ] ( size_t start , size_t end , RefPtr < Gfx : : Font > & font , Color & color , Optional < Color > background_color = { } , bool underline = false ) {
size_t length = end - start + 1 ;
auto text = visual_line_text . substring_view ( start , length ) ;
2021-02-27 23:06:46 +03:00
span_rect . set_width ( font - > width ( text ) ) ;
2021-02-27 19:32:55 +03:00
if ( background_color . has_value ( ) ) {
painter . fill_rect ( span_rect , background_color . value ( ) ) ;
2019-10-25 22:05:06 +03:00
}
2021-02-27 19:32:55 +03:00
painter . draw_text ( span_rect , text , * font , m_text_alignment , color ) ;
2020-03-12 17:36:25 +03:00
if ( underline ) {
2021-02-27 19:32:55 +03:00
painter . draw_line ( span_rect . bottom_left ( ) . translated ( 0 , 1 ) , span_rect . bottom_right ( ) . translated ( 0 , 1 ) , color ) ;
}
2021-04-12 21:47:09 +03:00
span_rect . translate_by ( span_rect . width ( ) , 0 ) ;
2021-02-27 19:32:55 +03:00
} ;
for ( ; ; ) {
if ( span_index > = document ( ) . spans ( ) . size ( ) ) {
break ;
}
auto & span = document ( ) . spans ( ) [ span_index ] ;
2021-03-02 09:06:52 +03:00
if ( ! span . range . is_valid ( ) ) {
+ + span_index ;
continue ;
}
2021-02-27 19:32:55 +03:00
if ( span . range . end ( ) . line ( ) < line_index ) {
2021-02-27 23:06:46 +03:00
dbgln ( " spans not sorted (span end {}:{} is before current line {}) => ignoring " , span . range . end ( ) . line ( ) , span . range . end ( ) . column ( ) , line_index ) ;
2021-02-27 19:32:55 +03:00
+ + span_index ;
continue ;
}
2021-02-27 23: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 19:32:55 +03:00
// no more spans in this line, moving on
break ;
2020-03-12 17:36:25 +03:00
}
2021-02-27 23:06:46 +03:00
if ( span . range . start ( ) . line ( ) = = span . range . end ( ) . line ( ) & & span . range . end ( ) . column ( ) < span . range . start ( ) . column ( ) ) {
if ( span . range . end ( ) . column ( ) = = span . range . start ( ) . column ( ) - 1 ) {
// span length is zero, just ignore
} else {
dbgln ( " span form {}:{} to {}:{} has negative length => ignoring " , span . range . start ( ) . line ( ) , span . range . start ( ) . column ( ) , span . range . end ( ) . line ( ) , span . range . end ( ) . column ( ) ) ;
}
+ + span_index ;
continue ;
}
if ( span . range . end ( ) . line ( ) = = line_index & & span . range . end ( ) . column ( ) < start_of_visual_line + next_column ) {
dbgln ( " spans not sorted (span end {}:{} is before current position {}:{}) => ignoring " ,
span . range . end ( ) . line ( ) , span . range . end ( ) . column ( ) , line_index , start_of_visual_line + next_column ) ;
2021-02-27 19:32:55 +03:00
+ + span_index ;
continue ;
}
size_t span_start ;
2021-02-27 23:06:46 +03:00
if ( span . range . start ( ) . line ( ) < line_index | | span . range . start ( ) . column ( ) < start_of_visual_line ) {
2021-02-27 19:32:55 +03:00
span_start = 0 ;
} else {
2021-02-27 23:06:46 +03:00
span_start = span . range . start ( ) . column ( ) - start_of_visual_line ;
2021-02-27 19:32:55 +03:00
}
2021-03-02 09:06:52 +03:00
if ( span_start < next_column ) {
dbgln ( " span started before the current position, maybe two spans overlap? (span start {} is before current position {}) => ignoring " , span_start , next_column ) ;
+ + span_index ;
continue ;
}
2021-02-27 19:32:55 +03:00
size_t span_end ;
2021-02-27 23:06:46 +03:00
bool span_consumned ;
if ( span . range . end ( ) . line ( ) > line_index | | span . range . end ( ) . column ( ) > = start_of_visual_line + visual_line_text . length ( ) ) {
2021-02-27 19:32:55 +03:00
if ( visual_line_text . length ( ) = = 0 ) {
// subtracting 1 would wrap around
2021-04-18 11:30:03 +03:00
// since there is nothing to draw here just move on
2021-02-27 19:32:55 +03:00
break ;
}
span_end = visual_line_text . length ( ) - 1 ;
2021-02-27 23:06:46 +03:00
span_consumned = false ;
2021-02-27 19:32:55 +03:00
} else {
2021-02-27 23:06:46 +03:00
span_end = span . range . end ( ) . column ( ) - start_of_visual_line ;
span_consumned = true ;
2021-02-27 19:32:55 +03:00
}
if ( span_start ! = next_column ) {
// draw unspanned text between spans
draw_text_helper ( next_column , span_start - 1 , unspanned_font , unspanned_color ) ;
}
auto font = unspanned_font ;
if ( span . attributes . bold ) {
if ( auto bold_font = Gfx : : FontDatabase : : the ( ) . get ( font - > family ( ) , font - > presentation_size ( ) , 700 ) )
font = bold_font ;
}
draw_text_helper ( span_start , span_end , font , span . attributes . color , span . attributes . background_color , span . attributes . underline ) ;
next_column = span_end + 1 ;
2021-02-27 23:06:46 +03:00
if ( ! span_consumned ) {
2021-02-27 19:32:55 +03:00
// continue with same span on next line
break ;
} else {
+ + span_index ;
}
}
// draw unspanned text after last span
if ( next_column < visual_line_text . length ( ) ) {
draw_text_helper ( next_column , visual_line_text . length ( ) - 1 , unspanned_font , unspanned_color ) ;
2019-10-25 22:05:06 +03:00
}
2021-02-27 23:06:46 +03:00
// consume all spans that should end this line
// this is necessary since the spans can include the new line character
while ( is_last_visual_line & & span_index < document ( ) . spans ( ) . size ( ) ) {
auto & span = document ( ) . spans ( ) [ span_index ] ;
if ( span . range . end ( ) . line ( ) = = line_index ) {
+ + span_index ;
} else {
break ;
}
}
2019-10-25 22:05:06 +03:00
}
2020-05-18 17:38:28 +03:00
2020-09-01 20: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 ) {
size_t visual_column = physical_column > start_of_visual_line ? ( physical_column - start_of_visual_line ) : 0 ;
Gfx : : IntRect whitespace_rect {
content_x_for_position ( { line_index , visual_column } ) ,
visual_line_rect . y ( ) ,
font ( ) . width ( visual_line_text . substring_view ( visual_column , visual_line_text . length ( ) - visual_column ) ) ,
visual_line_rect . height ( )
} ;
painter . fill_rect_with_dither_pattern ( whitespace_rect , Color ( ) , Color ( 255 , 192 , 192 ) ) ;
}
}
2021-03-17 19:52:42 +03:00
if ( m_visualize_leading_whitespace & & line . leading_spaces ( ) > 0 ) {
size_t physical_column = line . leading_spaces ( ) ;
size_t end_of_leading_whitespace = ( start_of_visual_line + physical_column ) ;
size_t end_of_visual_line = ( start_of_visual_line + visual_line_text . length ( ) ) ;
if ( end_of_leading_whitespace < end_of_visual_line ) {
Gfx : : IntRect whitespace_rect {
content_x_for_position ( { line_index , start_of_visual_line } ) ,
visual_line_rect . y ( ) ,
font ( ) . width ( visual_line_text . substring_view ( 0 , end_of_leading_whitespace ) ) ,
visual_line_rect . height ( )
} ;
painter . fill_rect_with_dither_pattern ( whitespace_rect , Color ( ) , Color ( 192 , 255 , 192 ) ) ;
}
}
2019-08-25 11:23:11 +03:00
if ( physical_line_has_selection ) {
2020-04-24 21:01:57 +03:00
size_t start_of_selection_within_visual_line = ( size_t ) max ( 0 , ( int ) selection_start_column_within_line - ( int ) start_of_visual_line ) ;
size_t end_of_selection_within_visual_line = selection_end_column_within_line - start_of_visual_line ;
2019-08-25 11:23:11 +03:00
2020-04-24 21: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 11: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 23:15:10 +03:00
? content_x_for_position ( { line_index , ( size_t ) selection_start_column_within_line } )
2019-08-25 11:23:11 +03:00
: m_horizontal_content_padding ;
int selection_right = selection_ends_on_current_visual_line
2019-12-17 23:15:10 +03:00
? content_x_for_position ( { line_index , ( size_t ) selection_end_column_within_line } )
2019-09-01 18:30:23 +03:00
: visual_line_rect . right ( ) + 1 ;
2019-08-25 11:23:11 +03:00
2020-06-10 11:57:59 +03:00
Gfx : : IntRect selection_rect {
2019-08-25 11:23:11 +03:00
selection_left ,
visual_line_rect . y ( ) ,
selection_right - selection_left ,
visual_line_rect . height ( )
} ;
2020-02-15 18:26:52 +03:00
Color background_color = is_focused ( ) ? palette ( ) . selection ( ) : palette ( ) . inactive_selection ( ) ;
Color text_color = is_focused ( ) ? palette ( ) . selection_text ( ) : palette ( ) . inactive_selection_text ( ) ;
painter . fill_rect ( selection_rect , background_color ) ;
2019-08-25 11:23:11 +03:00
2020-08-05 23:31:20 +03:00
if ( visual_line_text . code_points ( ) ) {
2020-05-29 21:10:36 +03:00
Utf32View visual_selected_text {
2020-08-05 23:31:20 +03:00
visual_line_text . code_points ( ) + start_of_selection_within_visual_line ,
2020-05-29 21:10:36 +03:00
end_of_selection_within_visual_line - start_of_selection_within_visual_line
} ;
2019-08-25 11:23:11 +03:00
2020-05-29 21:10:36 +03:00
painter . draw_text ( selection_rect , visual_selected_text , Gfx : : TextAlignment : : CenterLeft , text_color ) ;
}
2019-08-25 11:23:11 +03:00
}
2019-08-25 09:43:01 +03:00
}
2020-09-01 20:10:55 +03:00
2019-08-25 11:23:11 +03:00
+ + visual_line_index ;
2019-08-25 09:43:01 +03:00
return IterationDecision : : Continue ;
} ) ;
2019-03-07 02:31:06 +03:00
}
2020-06-29 21:34:42 +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 ( ) ) ;
}
2020-07-15 00:02:46 +03:00
if ( is_focused ( ) & & m_cursor_state & & ! is_displayonly ( ) )
2020-02-20 11:01:48 +03:00
painter . fill_rect ( cursor_content_rect ( ) , palette ( ) . text_cursor ( ) ) ;
2019-03-07 02:31:06 +03:00
}
2020-02-02 17:07:41 +03:00
void TextEditor : : select_all ( )
2019-06-22 11:38:38 +03:00
{
2020-02-02 17: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 19:41:54 +03:00
m_selection . set ( end_of_document , start_of_document ) ;
2019-06-22 11:38:38 +03:00
did_update_selection ( ) ;
2020-05-27 19:41:54 +03:00
set_cursor ( start_of_document ) ;
2019-06-22 11:38:38 +03:00
update ( ) ;
}
2020-02-02 17:07:41 +03:00
void TextEditor : : keydown_event ( KeyEvent & event )
2019-03-07 02:31:06 +03:00
{
2021-01-01 16:59:11 +03:00
TemporaryChange change { m_should_keep_autocomplete_box , true } ;
2020-12-30 13:25:06 +03:00
if ( m_autocomplete_box & & m_autocomplete_box - > is_visible ( ) & & ( event . key ( ) = = KeyCode : : Key_Return | | event . key ( ) = = KeyCode : : Key_Tab ) ) {
m_autocomplete_box - > apply_suggestion ( ) ;
m_autocomplete_box - > close ( ) ;
return ;
}
if ( m_autocomplete_box & & m_autocomplete_box - > is_visible ( ) & & event . key ( ) = = KeyCode : : Key_Escape ) {
m_autocomplete_box - > close ( ) ;
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 13:59:55 +03:00
if ( is_single_line ( ) ) {
if ( event . key ( ) = = KeyCode : : Key_Tab )
2021-05-03 21:31:58 +03:00
return AbstractScrollableWidget : : keydown_event ( event ) ;
2019-06-23 08:53:58 +03:00
2021-01-02 13:59:55 +03:00
if ( event . key ( ) = = KeyCode : : Key_Return ) {
if ( on_return_pressed )
on_return_pressed ( ) ;
return ;
2020-12-30 13:25:06 +03:00
}
2021-01-02 13:59:55 +03:00
if ( event . key ( ) = = KeyCode : : Key_Up ) {
if ( on_up_pressed )
on_up_pressed ( ) ;
return ;
2019-03-07 19:11:17 +03:00
}
2021-01-02 13:59:55 +03:00
if ( event . key ( ) = = KeyCode : : Key_Down ) {
if ( on_down_pressed )
on_down_pressed ( ) ;
return ;
2019-03-07 19:11:17 +03:00
}
2021-01-02 13:59:55 +03:00
if ( event . key ( ) = = KeyCode : : Key_PageUp ) {
if ( on_pageup_pressed )
on_pageup_pressed ( ) ;
return ;
2020-10-08 21:00:40 +03:00
}
2021-01-02 13:59:55 +03:00
if ( event . key ( ) = = KeyCode : : Key_PageDown ) {
if ( on_pagedown_pressed )
on_pagedown_pressed ( ) ;
2019-11-15 22:17:49 +03:00
return ;
}
2021-01-02 13:59:55 +03:00
2021-04-07 08:04:31 +03:00
} else if ( ! is_multi_line ( ) ) {
VERIFY_NOT_REACHED ( ) ;
}
2021-01-02 13:59:55 +03:00
2021-04-07 08:04:31 +03:00
ArmedScopeGuard update_autocomplete { [ & ] {
if ( m_autocomplete_box & & m_autocomplete_box - > is_visible ( ) ) {
m_autocomplete_provider - > provide_completions ( [ & ] ( auto completions ) {
m_autocomplete_box - > update_suggestions ( move ( completions ) ) ;
} ) ;
}
} } ;
if ( is_multi_line ( ) & & ! event . shift ( ) & & ! event . alt ( ) & & event . ctrl ( ) & & event . key ( ) = = KeyCode : : Key_Space ) {
if ( m_autocomplete_provider ) {
try_show_autocomplete ( ) ;
update_autocomplete . disarm ( ) ;
return ;
2020-10-08 21:00:40 +03:00
}
2019-03-07 15:21:51 +03:00
}
2021-01-02 13:59:55 +03:00
if ( m_editing_engine - > on_key ( event ) )
2019-03-07 15:21:51 +03:00
return ;
2021-01-02 13:59:55 +03:00
if ( event . key ( ) = = KeyCode : : Key_Escape ) {
if ( on_escape_pressed )
on_escape_pressed ( ) ;
2019-03-07 15:21:51 +03:00
return ;
}
2021-01-02 13:59:55 +03:00
if ( event . modifiers ( ) = = Mod_Shift & & event . key ( ) = = KeyCode : : Key_Delete ) {
if ( m_autocomplete_box )
m_autocomplete_box - > close ( ) ;
2019-03-07 15:21:51 +03:00
return ;
}
2021-01-02 13:59:55 +03:00
if ( event . key ( ) = = KeyCode : : Key_Delete ) {
if ( m_autocomplete_box )
m_autocomplete_box - > close ( ) ;
2019-11-15 22:45:27 +03:00
return ;
}
2021-01-02 13:59:55 +03:00
2019-03-08 20:50:14 +03:00
if ( event . key ( ) = = KeyCode : : Key_Backspace ) {
2020-07-16 00:04:49 +03:00
if ( ! is_editable ( ) )
2019-05-08 06:00:28 +03:00
return ;
2021-01-01 16:59:11 +03:00
if ( m_autocomplete_box )
m_autocomplete_box - > close ( ) ;
2019-03-08 16:10:34 +03:00
if ( has_selection ( ) ) {
delete_selection ( ) ;
2019-04-12 03:52:34 +03:00
did_update_selection ( ) ;
2019-03-08 16:10:34 +03:00
return ;
}
2019-03-07 18:04:21 +03:00
if ( m_cursor . column ( ) > 0 ) {
2019-10-27 12:42:48 +03:00
int erase_count = 1 ;
2020-07-22 08: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 12: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 ;
}
2019-03-07 18:15:25 +03:00
// Backspace within line
2020-02-02 17:07:41 +03:00
TextRange erased_range ( { m_cursor . line ( ) , m_cursor . column ( ) - erase_count } , m_cursor ) ;
2019-11-30 18:50:24 +03:00
auto erased_text = document ( ) . text_in_range ( erased_range ) ;
execute < RemoveTextCommand > ( erased_text , erased_range ) ;
2019-03-07 19:18:22 +03:00
return ;
2019-03-07 18:04:21 +03:00
}
2019-03-07 18:15:25 +03:00
if ( m_cursor . column ( ) = = 0 & & m_cursor . line ( ) ! = 0 ) {
2019-03-07 18:33:07 +03:00
// Backspace at column 0; merge with previous line
2019-12-09 19:45:40 +03:00
size_t previous_length = line ( m_cursor . line ( ) - 1 ) . length ( ) ;
2020-02-02 17:07:41 +03:00
TextRange erased_range ( { m_cursor . line ( ) - 1 , previous_length } , m_cursor ) ;
2019-11-30 18:50:24 +03:00
execute < RemoveTextCommand > ( " \n " , erased_range ) ;
2019-03-07 19:18:22 +03:00
return ;
2019-03-07 18:15:25 +03:00
}
2019-03-07 18:04:21 +03:00
return ;
}
2021-01-02 13:59:55 +03:00
if ( ! event . ctrl ( ) & & ! event . alt ( ) & & event . code_point ( ) ! = 0 ) {
add_code_point ( event . code_point ( ) ) ;
2020-02-24 12:20:25 +03:00
return ;
}
event . ignore ( ) ;
2019-03-07 02:31:06 +03:00
}
2020-02-02 17:07:41 +03:00
void TextEditor : : delete_current_line ( )
2019-03-25 15:13:46 +03:00
{
if ( has_selection ( ) )
return delete_selection ( ) ;
2020-02-02 17:07:41 +03:00
TextPosition start ;
TextPosition end ;
2019-12-09 19:45:40 +03:00
if ( m_cursor . line ( ) = = 0 & & line_count ( ) = = 1 ) {
2019-11-30 19:19:35 +03:00
start = { 0 , 0 } ;
end = { 0 , line ( 0 ) . length ( ) } ;
2019-12-09 19:45:40 +03:00
} else if ( m_cursor . line ( ) = = line_count ( ) - 1 ) {
2021-01-25 16:19:07 +03:00
start = { m_cursor . line ( ) - 1 , line ( m_cursor . line ( ) - 1 ) . length ( ) } ;
2019-11-30 19: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 15:13:46 +03:00
2020-02-02 17:07:41 +03:00
TextRange erased_range ( start , end ) ;
2019-11-30 19:19:35 +03:00
execute < RemoveTextCommand > ( document ( ) . text_in_range ( erased_range ) , erased_range ) ;
2019-03-25 15:13:46 +03:00
}
2020-02-02 17:07:41 +03:00
void TextEditor : : do_delete ( )
2019-03-21 01:11:00 +03:00
{
2020-07-16 00:04:49 +03:00
if ( ! is_editable ( ) )
2019-05-08 06:00:28 +03:00
return ;
2019-03-25 15:13:46 +03:00
if ( has_selection ( ) )
return delete_selection ( ) ;
2019-03-21 01:11:00 +03:00
if ( m_cursor . column ( ) < current_line ( ) . length ( ) ) {
// Delete within line
2020-02-02 17:07:41 +03:00
TextRange erased_range ( m_cursor , { m_cursor . line ( ) , m_cursor . column ( ) + 1 } ) ;
2019-11-30 18:58:07 +03:00
execute < RemoveTextCommand > ( document ( ) . text_in_range ( erased_range ) , erased_range ) ;
2019-03-21 01: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 17:07:41 +03:00
TextRange erased_range ( m_cursor , { m_cursor . line ( ) + 1 , 0 } ) ;
2019-11-30 18:58:07 +03:00
execute < RemoveTextCommand > ( document ( ) . text_in_range ( erased_range ) , erased_range ) ;
2019-03-21 01:11:00 +03:00
return ;
}
}
2021-01-02 13: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 ( ) ;
}
insert_at_cursor_or_replace_selection ( sb . to_string ( ) ) ;
} ;
void TextEditor : : reset_cursor_blink ( )
{
m_cursor_state = true ;
update_cursor ( ) ;
stop_timer ( ) ;
start_timer ( 500 ) ;
}
2021-04-26 23:29:05 +03:00
void TextEditor : : update_selection ( bool is_selecting )
2021-01-02 13:59:55 +03:00
{
if ( is_selecting & & ! selection ( ) - > is_valid ( ) ) {
selection ( ) - > set ( cursor ( ) , { } ) ;
did_update_selection ( ) ;
update ( ) ;
return ;
}
if ( ! is_selecting & & selection ( ) - > is_valid ( ) ) {
selection ( ) - > clear ( ) ;
did_update_selection ( ) ;
update ( ) ;
return ;
}
if ( is_selecting & & selection ( ) - > start ( ) . is_valid ( ) ) {
selection ( ) - > set_end ( cursor ( ) ) ;
did_update_selection ( ) ;
update ( ) ;
return ;
}
}
2020-02-02 17:07:41 +03:00
int TextEditor : : content_x_for_position ( const TextPosition & position ) const
2019-04-24 23:24:16 +03:00
{
2019-12-09 19:45:40 +03:00
auto & line = this - > line ( position . line ( ) ) ;
2020-05-18 17:38:28 +03:00
int x_offset = 0 ;
2019-04-24 23:24:16 +03:00
switch ( m_text_alignment ) {
2020-02-06 13:56:38 +03:00
case Gfx : : TextAlignment : : CenterLeft :
2021-02-23 04:34:26 +03:00
for_each_visual_line ( position . line ( ) , [ & ] ( const Gfx : : IntRect & , auto & visual_line_view , size_t start_of_visual_line , bool is_last_visual_line ) {
2020-05-18 17:38:28 +03:00
size_t offset_in_visual_line = position . column ( ) - start_of_visual_line ;
2021-02-23 04: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 17:38:28 +03:00
if ( offset_in_visual_line = = 0 ) {
x_offset = 0 ;
} else {
x_offset = font ( ) . width ( visual_line_view . substring_view ( 0 , offset_in_visual_line ) ) ;
x_offset + = font ( ) . glyph_spacing ( ) ;
}
2019-08-25 10:06:54 +03:00
return IterationDecision : : Break ;
}
return IterationDecision : : Continue ;
} ) ;
2020-06-29 21:34:42 +03:00
return m_horizontal_content_padding + ( ( is_single_line ( ) & & icon ( ) ) ? ( icon_size ( ) + icon_padding ( ) ) : 0 ) + x_offset ;
2020-02-06 13:56:38 +03:00
case Gfx : : TextAlignment : : CenterRight :
2019-08-25 10:06:54 +03:00
// FIXME
2021-02-23 22:42:32 +03:00
VERIFY ( ! is_wrapping_enabled ( ) ) ;
2020-05-18 17:38:28 +03:00
return content_width ( ) - m_horizontal_content_padding - ( line . length ( ) * fixed_glyph_width ( ) ) + ( position . column ( ) * fixed_glyph_width ( ) ) ;
2019-04-24 23:24:16 +03:00
default :
2021-02-23 22:42:32 +03:00
VERIFY_NOT_REACHED ( ) ;
2019-04-24 23:24:16 +03:00
}
}
2020-06-10 11:57:59 +03:00
Gfx : : IntRect TextEditor : : content_rect_for_position ( const TextPosition & position ) const
2019-03-07 02:31:06 +03:00
{
2019-08-21 22:23:17 +03:00
if ( ! position . is_valid ( ) )
2019-06-07 12:46:02 +03:00
return { } ;
2021-02-23 22:42:32 +03:00
VERIFY ( ! lines ( ) . is_empty ( ) ) ;
VERIFY ( position . column ( ) < = ( current_line ( ) . length ( ) + 1 ) ) ;
2019-04-24 23:24:16 +03:00
2019-08-21 22:23:17 +03:00
int x = content_x_for_position ( position ) ;
2019-04-24 23:24:16 +03:00
2019-03-28 17:30:29 +03:00
if ( is_single_line ( ) ) {
2021-01-02 02:33:14 +03:00
Gfx : : IntRect rect { x , 0 , 1 , line_height ( ) } ;
2019-08-21 22:23:17 +03:00
rect . center_vertically_within ( { { } , frame_inner_rect ( ) . size ( ) } ) ;
return rect ;
2019-03-28 17:30:29 +03:00
}
2019-08-25 10:06:54 +03:00
2020-06-10 11:57:59 +03:00
Gfx : : IntRect rect ;
2021-02-23 04:34:26 +03:00
for_each_visual_line ( position . line ( ) , [ & ] ( const Gfx : : IntRect & visual_line_rect , auto & view , size_t start_of_visual_line , bool is_last_visual_line ) {
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 10: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 13:59:55 +03:00
m_editing_engine - > cursor_width ( ) = = CursorWidth : : WIDE ? 7 : 1 ,
2019-08-25 10:06:54 +03:00
line_height ( )
} ;
return IterationDecision : : Break ;
}
return IterationDecision : : Continue ;
} ) ;
return rect ;
2019-08-21 22:23:17 +03:00
}
2020-06-10 11:57:59 +03:00
Gfx : : IntRect TextEditor : : cursor_content_rect ( ) const
2019-08-21 22:23:17 +03:00
{
return content_rect_for_position ( m_cursor ) ;
2019-03-07 03:05:35 +03:00
}
2020-06-10 11:57:59 +03:00
Gfx : : IntRect TextEditor : : line_widget_rect ( size_t line_index ) const
2019-03-07 04:24:46 +03:00
{
auto rect = line_content_rect ( line_index ) ;
2019-04-25 00:15:15 +03:00
rect . set_x ( frame_thickness ( ) ) ;
rect . set_width ( frame_inner_rect ( ) . width ( ) ) ;
2021-04-12 21:47:09 +03:00
rect . translate_by ( 0 , - ( vertical_scrollbar ( ) . value ( ) ) ) ;
rect . translate_by ( 0 , frame_thickness ( ) ) ;
2019-04-25 02:09:44 +03:00
rect . intersect ( frame_inner_rect ( ) ) ;
2019-03-07 15:13:25 +03:00
return rect ;
2019-03-07 04:24:46 +03:00
}
2020-02-02 17:07:41 +03:00
void TextEditor : : scroll_position_into_view ( const TextPosition & position )
2019-03-07 02:31:06 +03:00
{
2019-08-21 22: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 19: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-25 02:33:59 +03:00
scroll_into_view ( rect , true , true ) ;
2019-03-07 02:31:06 +03:00
}
2020-02-02 17:07:41 +03:00
void TextEditor : : scroll_cursor_into_view ( )
2019-08-21 22:23:17 +03:00
{
2020-06-07 22:03:29 +03:00
if ( ! m_reflow_deferred )
scroll_position_into_view ( m_cursor ) ;
2019-08-21 22:23:17 +03:00
}
2020-06-10 11:57:59 +03:00
Gfx : : IntRect TextEditor : : line_content_rect ( size_t line_index ) const
2019-03-07 02:31:06 +03:00
{
2019-12-09 19:45:40 +03:00
auto & line = this - > line ( line_index ) ;
2019-03-28 17:30:29 +03:00
if ( is_single_line ( ) ) {
2020-06-10 11:57:59 +03:00
Gfx : : IntRect line_rect = { content_x_for_position ( { line_index , 0 } ) , 0 , font ( ) . width ( line . view ( ) ) , font ( ) . glyph_height ( ) + 4 } ;
2019-06-07 12:46:02 +03:00
line_rect . center_vertically_within ( { { } , frame_inner_rect ( ) . size ( ) } ) ;
2019-03-28 17:30:29 +03:00
return line_rect ;
}
2021-01-09 15:47:48 +03:00
if ( is_wrapping_enabled ( ) )
2019-10-27 20:00:07 +03:00
return m_line_visual_data [ line_index ] . visual_rect ;
2019-03-07 02:31:06 +03:00
return {
2019-04-24 23:46:27 +03:00
content_x_for_position ( { line_index , 0 } ) ,
2019-12-09 19:45:40 +03:00
( int ) line_index * line_height ( ) ,
2020-05-18 17:38:28 +03:00
font ( ) . width ( line . view ( ) ) ,
2019-03-07 03:05:35 +03:00
line_height ( )
2019-03-07 02:31:06 +03:00
} ;
}
2020-11-10 11: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 {
auto line_data = m_line_visual_data [ line ] ;
headroom - = line_data . visual_rect . height ( ) ;
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 17:07:41 +03:00
void TextEditor : : update_cursor ( )
2019-03-07 02:31:06 +03:00
{
2019-03-07 15:23:17 +03:00
update ( line_widget_rect ( m_cursor . line ( ) ) ) ;
2019-03-07 02:31:06 +03:00
}
2020-02-02 17:07:41 +03:00
void TextEditor : : set_cursor ( size_t line , size_t column )
2019-03-07 02:46:29 +03:00
{
2019-03-07 17:03:38 +03:00
set_cursor ( { line , column } ) ;
}
2020-02-02 17:07:41 +03:00
void TextEditor : : set_cursor ( const TextPosition & a_position )
2019-03-07 17:03:38 +03:00
{
2021-02-23 22:42:32 +03:00
VERIFY ( ! lines ( ) . is_empty ( ) ) ;
2019-10-21 19:58:27 +03:00
2020-02-02 17:07:41 +03:00
TextPosition position = a_position ;
2019-10-21 19:58:27 +03:00
2019-12-09 19:45:40 +03:00
if ( position . line ( ) > = line_count ( ) )
position . set_line ( line_count ( ) - 1 ) ;
2019-10-21 19:58:27 +03:00
2019-10-27 18:10:07 +03:00
if ( position . column ( ) > lines ( ) [ position . line ( ) ] . length ( ) )
position . set_column ( lines ( ) [ position . line ( ) ] . length ( ) ) ;
2019-10-21 19:58:27 +03:00
2020-06-13 01:31:46 +03:00
if ( m_cursor ! = position & & is_visual_data_up_to_date ( ) ) {
2019-05-06 23:04:53 +03:00
// NOTE: If the old cursor is no longer valid, repaint everything just in case.
2019-12-09 19:45:40 +03:00
auto old_cursor_line_rect = m_cursor . line ( ) < line_count ( )
2019-05-06 23:04:53 +03:00
? line_widget_rect ( m_cursor . line ( ) )
: rect ( ) ;
2019-03-08 19:53:02 +03:00
m_cursor = position ;
m_cursor_state = true ;
scroll_cursor_into_view ( ) ;
update ( old_cursor_line_rect ) ;
update_cursor ( ) ;
2020-06-13 01:31:46 +03:00
} else if ( m_cursor ! = position ) {
m_cursor = position ;
m_cursor_state = true ;
2019-03-08 19:53:02 +03:00
}
2019-11-18 21:10:06 +03:00
cursor_did_change ( ) ;
2019-03-07 02:46:29 +03:00
if ( on_cursor_change )
2019-04-10 04:08:29 +03:00
on_cursor_change ( ) ;
2020-02-07 22:07:15 +03:00
if ( m_highlighter )
m_highlighter - > cursor_did_change ( ) ;
2019-03-07 02:46:29 +03:00
}
2020-08-14 20:58:38 +03:00
void TextEditor : : focusin_event ( FocusEvent & event )
2019-03-07 03:05:35 +03:00
{
2020-08-14 20:58:38 +03:00
if ( event . source ( ) = = FocusSource : : Keyboard )
select_all ( ) ;
2020-05-21 18:37:28 +03:00
m_cursor_state = true ;
2019-03-07 03:05:35 +03:00
update_cursor ( ) ;
2021-01-02 13:59:55 +03:00
stop_timer ( ) ;
2019-03-07 03:05:35 +03:00
start_timer ( 500 ) ;
2020-07-04 21:07:55 +03:00
if ( on_focusin )
on_focusin ( ) ;
2019-03-07 03:05:35 +03:00
}
2020-08-14 20:56:40 +03:00
void TextEditor : : focusout_event ( FocusEvent & )
2019-03-07 03:05:35 +03:00
{
2021-03-10 19:57:59 +03:00
if ( is_displayonly ( ) & & has_selection ( ) )
m_selection . clear ( ) ;
2019-03-07 03:05:35 +03:00
stop_timer ( ) ;
2020-07-04 21:07:55 +03:00
if ( on_focusout )
on_focusout ( ) ;
2019-03-07 03:05:35 +03:00
}
2020-02-02 17:07:41 +03:00
void TextEditor : : timer_event ( Core : : TimerEvent & )
2019-03-07 03:05:35 +03:00
{
m_cursor_state = ! m_cursor_state ;
2019-11-06 01:37:47 +03:00
if ( is_focused ( ) )
2019-03-07 03:05:35 +03:00
update_cursor ( ) ;
}
2021-01-12 21:21:59 +03:00
bool TextEditor : : write_to_file ( const String & path )
2019-03-07 19:06:11 +03:00
{
2021-01-12 21:21:59 +03:00
int fd = open ( path . characters ( ) , O_WRONLY | O_CREAT | O_TRUNC , 0666 ) ;
2019-03-07 19:06:11 +03:00
if ( fd < 0 ) {
perror ( " open " ) ;
return false ;
}
2019-08-28 20:32:45 +03:00
2021-02-15 19:48:38 +03:00
ScopeGuard fd_guard = [ fd ] { close ( fd ) ; } ;
2019-08-28 20:32:45 +03:00
off_t file_size = 0 ;
2021-02-15 20:02:33 +03:00
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 ( ) ;
}
2019-08-28 20:32:45 +03:00
2021-02-15 20:02:33 +03:00
if ( ftruncate ( fd , file_size ) < 0 ) {
2019-08-28 20:32:45 +03:00
perror ( " ftruncate " ) ;
return false ;
}
2021-02-15 20:02:33 +03:00
if ( file_size = = 0 )
return true ;
2019-12-09 19:45:40 +03:00
for ( size_t i = 0 ; i < line_count ( ) ; + + i ) {
auto & line = this - > line ( i ) ;
2019-03-07 19:06:11 +03:00
if ( line . length ( ) ) {
2020-05-17 21:33:06 +03:00
auto line_as_utf8 = line . to_utf8 ( ) ;
ssize_t nwritten = write ( fd , line_as_utf8 . characters ( ) , line_as_utf8 . length ( ) ) ;
2019-03-07 19:06:11 +03:00
if ( nwritten < 0 ) {
perror ( " write " ) ;
return false ;
}
}
2021-02-15 20:02:33 +03:00
char ch = ' \n ' ;
ssize_t nwritten = write ( fd , & ch , 1 ) ;
if ( nwritten ! = 1 ) {
perror ( " write " ) ;
return false ;
2019-03-07 19:06:11 +03:00
}
}
return true ;
}
2019-03-08 03:59:59 +03:00
2020-02-02 17:07:41 +03:00
String TextEditor : : text ( ) const
2019-03-15 19:37:13 +03:00
{
2020-09-28 16:37:37 +03:00
return document ( ) . text ( ) ;
2019-03-15 19:37:13 +03:00
}
2020-02-02 17:07:41 +03:00
void TextEditor : : clear ( )
2019-03-15 19:37:13 +03:00
{
2019-10-27 21:36:59 +03:00
document ( ) . remove_all_lines ( ) ;
2020-02-02 17:07:41 +03:00
document ( ) . append_line ( make < TextDocumentLine > ( document ( ) ) ) ;
2019-03-15 19:37:13 +03:00
m_selection . clear ( ) ;
2019-04-12 03:52:34 +03:00
did_update_selection ( ) ;
2019-03-15 19:37:13 +03:00
set_cursor ( 0 , 0 ) ;
update ( ) ;
}
2020-02-02 17:07:41 +03:00
String TextEditor : : selected_text ( ) const
2019-03-08 03:59:59 +03:00
{
if ( ! has_selection ( ) )
2019-06-07 12:46:02 +03:00
return { } ;
2019-03-08 03:59:59 +03:00
2019-10-29 23:36:47 +03:00
return document ( ) . text_in_range ( m_selection ) ;
2019-03-08 03:59:59 +03:00
}
2019-03-08 16:08:15 +03:00
2020-02-02 17:07:41 +03:00
void TextEditor : : delete_selection ( )
2019-03-08 16:08:15 +03:00
{
2019-03-08 20:28:24 +03:00
auto selection = normalized_selection ( ) ;
2021-05-02 19:33:31 +03:00
auto selected = selected_text ( ) ;
2019-03-08 20:28:24 +03:00
m_selection . clear ( ) ;
2021-05-02 19:33:31 +03:00
execute < RemoveTextCommand > ( selected , selection ) ;
2019-04-12 03:52:34 +03:00
did_update_selection ( ) ;
2019-04-25 02:28:17 +03:00
did_change ( ) ;
2019-03-08 20:28:24 +03:00
set_cursor ( selection . start ( ) ) ;
2019-03-08 16:08:15 +03:00
update ( ) ;
}
2021-01-27 14: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 ( ) ;
}
2020-02-02 17:07:41 +03:00
void TextEditor : : insert_at_cursor_or_replace_selection ( const StringView & text )
2019-03-08 16:08:15 +03:00
{
2020-05-27 20:18:40 +03:00
ReflowDeferrer defer ( * this ) ;
2021-02-23 22:42:32 +03:00
VERIFY ( is_editable ( ) ) ;
2019-03-08 16:08:15 +03:00
if ( has_selection ( ) )
delete_selection ( ) ;
2019-11-30 18:50:24 +03:00
execute < InsertTextCommand > ( text , m_cursor ) ;
2019-03-08 16:08:15 +03:00
}
2020-02-02 17:07:41 +03:00
void TextEditor : : cut ( )
2019-03-08 16:08:15 +03:00
{
2020-07-16 00:04:49 +03:00
if ( ! is_editable ( ) )
2019-05-08 06:00:28 +03:00
return ;
2019-03-08 16:08:15 +03:00
auto selected_text = this - > selected_text ( ) ;
printf ( " Cut: \" %s \" \n " , selected_text . characters ( ) ) ;
2020-09-05 17:16:01 +03:00
Clipboard : : the ( ) . set_plain_text ( selected_text ) ;
2019-03-08 16:08:15 +03:00
delete_selection ( ) ;
}
2020-02-02 17:07:41 +03:00
void TextEditor : : copy ( )
2019-03-08 16:08:15 +03:00
{
auto selected_text = this - > selected_text ( ) ;
printf ( " Copy: \" %s \" \n " , selected_text . characters ( ) ) ;
2020-09-05 17:16:01 +03:00
Clipboard : : the ( ) . set_plain_text ( selected_text ) ;
2019-03-08 16:08:15 +03:00
}
2020-02-02 17:07:41 +03:00
void TextEditor : : paste ( )
2019-03-08 16:08:15 +03:00
{
2020-07-16 00:04:49 +03:00
if ( ! is_editable ( ) )
2019-05-08 06:00:28 +03:00
return ;
2020-05-27 20:18:40 +03:00
2020-02-02 17:07:41 +03:00
auto paste_text = Clipboard : : the ( ) . data ( ) ;
2020-09-05 17:16:01 +03:00
printf ( " Paste: \" %s \" \n " , String : : copy ( paste_text ) . characters ( ) ) ;
2020-01-23 23:07:37 +03:00
TemporaryChange change ( m_automatic_indentation_enabled , false ) ;
2019-03-08 16:08:15 +03:00
insert_at_cursor_or_replace_selection ( paste_text ) ;
}
2019-04-01 00:52:02 +03:00
2020-05-27 20:18:40 +03:00
void TextEditor : : defer_reflow ( )
{
+ + m_reflow_deferred ;
}
void TextEditor : : undefer_reflow ( )
{
2021-02-23 22:42:32 +03:00
VERIFY ( m_reflow_deferred ) ;
2020-05-27 20:18:40 +03:00
if ( ! - - m_reflow_deferred ) {
2020-05-29 22:43:06 +03:00
if ( m_reflow_requested ) {
2020-05-27 20:18:40 +03:00
recompute_all_visual_lines ( ) ;
2020-05-29 22:43:06 +03:00
scroll_cursor_into_view ( ) ;
}
2020-05-27 20:18:40 +03:00
}
}
2021-01-01 16:59:11 +03:00
void TextEditor : : try_show_autocomplete ( )
{
if ( m_autocomplete_provider ) {
m_autocomplete_provider - > provide_completions ( [ & ] ( auto completions ) {
auto has_completions = ! completions . is_empty ( ) ;
m_autocomplete_box - > update_suggestions ( move ( completions ) ) ;
2021-03-27 18:04:51 +03:00
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 ) ) ;
2021-01-01 16:59:11 +03:00
if ( has_completions )
m_autocomplete_box - > show ( position ) ;
} ) ;
}
}
2020-02-02 17:07:41 +03:00
void TextEditor : : enter_event ( Core : : Event & )
2019-04-01 00:52:02 +03:00
{
2020-04-20 22:31:49 +03:00
m_automatic_selection_scroll_timer - > stop ( ) ;
2019-04-01 00:52:02 +03:00
}
2020-02-02 17:07:41 +03:00
void TextEditor : : leave_event ( Core : : Event & )
2019-04-01 00:52:02 +03:00
{
2020-04-20 22:31:49 +03:00
if ( m_in_drag_select )
m_automatic_selection_scroll_timer - > start ( ) ;
2021-01-01 16:59:11 +03:00
if ( m_autocomplete_timer )
m_autocomplete_timer - > stop ( ) ;
if ( m_autocomplete_box )
m_autocomplete_box - > close ( ) ;
2019-04-01 00:52:02 +03:00
}
2019-04-09 17:20:36 +03:00
2020-02-02 17:07:41 +03:00
void TextEditor : : did_change ( )
2019-04-09 17:20:36 +03:00
{
update_content_size ( ) ;
2019-08-25 09:43:01 +03:00
recompute_all_visual_lines ( ) ;
2021-01-01 16:59:11 +03:00
if ( m_autocomplete_box & & ! m_should_keep_autocomplete_box ) {
m_autocomplete_box - > close ( ) ;
2021-03-03 00:08:56 +03:00
if ( m_autocomplete_timer )
m_autocomplete_timer - > stop ( ) ;
2021-01-01 16:59:11 +03:00
}
2019-11-08 21:49:08 +03:00
if ( ! m_has_pending_change_notification ) {
m_has_pending_change_notification = true ;
2019-06-07 12:46:02 +03:00
deferred_invoke ( [ this ] ( auto & ) {
2019-11-08 21:49:08 +03:00
if ( ! m_has_pending_change_notification )
return ;
2019-04-09 17:20:36 +03:00
if ( on_change )
on_change ( ) ;
2020-02-07 22:07:15 +03:00
if ( m_highlighter )
2020-03-16 02:05:06 +03:00
m_highlighter - > rehighlight ( palette ( ) ) ;
2019-11-08 21:49:08 +03:00
m_has_pending_change_notification = false ;
2019-04-09 17:20:36 +03:00
} ) ;
}
}
2020-07-15 00:02:46 +03:00
void TextEditor : : set_mode ( const Mode mode )
{
if ( m_mode = = mode )
return ;
m_mode = mode ;
switch ( mode ) {
case Editable :
m_cut_action - > set_enabled ( true & & has_selection ( ) ) ;
m_delete_action - > set_enabled ( true ) ;
m_paste_action - > set_enabled ( true ) ;
set_accepts_emoji_input ( true ) ;
break ;
case DisplayOnly :
2020-07-16 00:04:49 +03:00
case ReadOnly :
2020-07-15 00:02:46 +03:00
m_cut_action - > set_enabled ( false & & has_selection ( ) ) ;
m_delete_action - > set_enabled ( false ) ;
m_paste_action - > set_enabled ( false ) ;
set_accepts_emoji_input ( false ) ;
break ;
default :
2021-02-23 22:42:32 +03:00
VERIFY_NOT_REACHED ( ) ;
2020-07-15 00:02:46 +03:00
}
2020-09-11 15:25:48 +03:00
if ( ! is_displayonly ( ) )
set_override_cursor ( Gfx : : StandardCursor : : IBeam ) ;
else
set_override_cursor ( Gfx : : StandardCursor : : None ) ;
2020-07-15 00:02:46 +03:00
}
2019-04-12 03:52:34 +03:00
2020-02-02 17:07:41 +03:00
void TextEditor : : did_update_selection ( )
2019-04-12 03:52:34 +03:00
{
2020-07-16 00:04:49 +03:00
m_cut_action - > set_enabled ( is_editable ( ) & & has_selection ( ) ) ;
2019-04-18 13:25:00 +03:00
m_copy_action - > set_enabled ( has_selection ( ) ) ;
2019-04-12 03:52:34 +03:00
if ( on_selection_change )
on_selection_change ( ) ;
2021-01-09 15:47:48 +03:00
if ( is_wrapping_enabled ( ) ) {
2019-08-25 11:23:11 +03:00
// FIXME: Try to repaint less.
update ( ) ;
}
2019-04-12 03:52:34 +03:00
}
2019-04-18 13:25:00 +03:00
2020-02-02 17:07:41 +03:00
void TextEditor : : context_menu_event ( ContextMenuEvent & event )
2019-04-18 13:25:00 +03:00
{
2020-07-15 00:02:46 +03:00
if ( is_displayonly ( ) )
return ;
2019-04-18 13:25:00 +03:00
if ( ! m_context_menu ) {
2020-02-02 17:07:41 +03:00
m_context_menu = Menu : : construct ( ) ;
2019-04-18 13: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 ( ) ) ;
m_context_menu - > add_action ( delete_action ( ) ) ;
2020-04-20 22:10:03 +03:00
m_context_menu - > add_separator ( ) ;
m_context_menu - > add_action ( select_all_action ( ) ) ;
2020-01-23 23:21:55 +03:00
if ( is_multi_line ( ) ) {
m_context_menu - > add_separator ( ) ;
2020-01-23 23:27:27 +03:00
m_context_menu - > add_action ( go_to_line_action ( ) ) ;
2020-01-23 23:21:55 +03:00
}
2019-08-25 22: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 13:25:00 +03:00
}
m_context_menu - > popup ( event . screen_position ( ) ) ;
}
2019-04-24 23:24:16 +03:00
2020-02-06 13:56:38 +03:00
void TextEditor : : set_text_alignment ( Gfx : : TextAlignment alignment )
2019-04-24 23:24:16 +03:00
{
if ( m_text_alignment = = alignment )
return ;
m_text_alignment = alignment ;
update ( ) ;
}
2019-04-25 00:42:49 +03:00
2020-02-02 17:07:41 +03:00
void TextEditor : : resize_event ( ResizeEvent & event )
2019-04-25 00:42:49 +03:00
{
2021-05-03 21:31:58 +03:00
AbstractScrollableWidget : : resize_event ( event ) ;
2019-04-25 00:42:49 +03:00
update_content_size ( ) ;
2019-08-25 09:43:01 +03:00
recompute_all_visual_lines ( ) ;
2019-04-25 00:42:49 +03:00
}
2019-08-21 22:23:17 +03:00
2020-03-16 14:36:21 +03:00
void TextEditor : : theme_change_event ( ThemeChangeEvent & event )
{
2021-05-03 21:31:58 +03:00
AbstractScrollableWidget : : theme_change_event ( event ) ;
2020-03-16 14:36:21 +03:00
if ( m_highlighter )
m_highlighter - > rehighlight ( palette ( ) ) ;
}
2020-02-02 17:07:41 +03:00
void TextEditor : : set_selection ( const TextRange & selection )
2019-08-21 22: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 17:07:41 +03:00
void TextEditor : : clear_selection ( )
2019-12-02 05:48:32 +03:00
{
if ( ! has_selection ( ) )
return ;
m_selection . clear ( ) ;
update ( ) ;
}
2020-02-02 17:07:41 +03:00
void TextEditor : : recompute_all_visual_lines ( )
2019-08-25 09:43:01 +03:00
{
2020-05-27 20:18:40 +03:00
if ( m_reflow_deferred ) {
m_reflow_requested = true ;
return ;
}
2020-06-13 01:31:46 +03:00
m_reflow_requested = false ;
2019-08-25 09:43:01 +03:00
int y_offset = 0 ;
2019-12-09 19:45:40 +03:00
for ( size_t line_index = 0 ; line_index < line_count ( ) ; + + line_index ) {
2019-10-27 20:00:07 +03:00
recompute_visual_lines ( line_index ) ;
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 09:43:01 +03:00
}
2019-08-29 07:26:24 +03:00
update_content_size ( ) ;
2019-08-25 09:43:01 +03:00
}
2020-02-02 17:07:41 +03:00
void TextEditor : : ensure_cursor_is_valid ( )
2019-11-06 01:37:47 +03:00
{
2019-11-30 18:50:24 +03:00
auto new_cursor = m_cursor ;
2019-12-09 19: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 18:50:24 +03:00
if ( m_cursor ! = new_cursor )
set_cursor ( new_cursor ) ;
2019-11-06 01:37:47 +03:00
}
2020-02-02 17:07:41 +03:00
size_t TextEditor : : visual_line_containing ( size_t line_index , size_t column ) const
2019-10-27 18:10:07 +03:00
{
2019-12-09 19:45:40 +03:00
size_t visual_line_index = 0 ;
2020-11-13 08:29:55 +03:00
for_each_visual_line ( line_index , [ & ] ( const Gfx : : IntRect & , auto & view , size_t start_of_visual_line , [[maybe_unused]] bool is_last_visual_line ) {
2019-10-27 18: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 ;
}
2020-02-02 17:07:41 +03:00
void TextEditor : : recompute_visual_lines ( size_t line_index )
2019-08-25 09:43:01 +03:00
{
2019-10-27 20:00:07 +03:00
auto & line = document ( ) . line ( line_index ) ;
auto & visual_data = m_line_visual_data [ line_index ] ;
2019-08-25 09:43:01 +03:00
2019-10-27 20:00:07 +03:00
visual_data . visual_line_breaks . clear_with_capacity ( ) ;
2019-08-25 09:43:01 +03:00
2019-10-27 20:00:07 +03:00
int available_width = visible_text_rect_in_inner_coordinates ( ) . width ( ) ;
2021-01-09 15:47:48 +03:00
if ( is_wrapping_enabled ( ) ) {
2019-08-25 09:43:01 +03:00
int line_width_so_far = 0 ;
2021-01-09 15:47:48 +03:00
size_t last_whitespace_index = 0 ;
size_t line_width_since_last_whitespace = 0 ;
2020-05-18 17:38:28 +03:00
auto glyph_spacing = font ( ) . glyph_spacing ( ) ;
2019-12-09 19:45:40 +03:00
for ( size_t i = 0 ; i < line . length ( ) ; + + i ) {
2020-08-05 23:31:20 +03:00
auto code_point = line . code_points ( ) [ i ] ;
2021-01-09 15:47:48 +03:00
if ( isspace ( code_point ) ) {
last_whitespace_index = i ;
line_width_since_last_whitespace = 0 ;
}
2020-08-05 23:31:20 +03:00
auto glyph_width = font ( ) . glyph_or_emoji_width ( code_point ) ;
2021-01-09 15:47:48 +03:00
line_width_since_last_whitespace + = glyph_width + glyph_spacing ;
2020-05-18 17:38:28 +03:00
if ( ( line_width_so_far + glyph_width + glyph_spacing ) > available_width ) {
2021-01-09 15:47:48 +03:00
if ( m_wrapping_mode = = WrappingMode : : WrapAtWords & & last_whitespace_index ! = 0 ) {
// Plus 1 to get the first letter of the word.
visual_data . visual_line_breaks . append ( last_whitespace_index + 1 ) ;
line_width_so_far = line_width_since_last_whitespace ;
last_whitespace_index = 0 ;
line_width_since_last_whitespace = 0 ;
} else {
visual_data . visual_line_breaks . append ( i ) ;
line_width_so_far = glyph_width + glyph_spacing ;
}
2019-08-25 09:43:01 +03:00
continue ;
}
2020-05-18 17:38:28 +03:00
line_width_so_far + = glyph_width + glyph_spacing ;
2019-08-25 09:43:01 +03:00
}
}
2019-10-27 20:00:07 +03:00
visual_data . visual_line_breaks . append ( line . length ( ) ) ;
2019-08-25 09:43:01 +03:00
2021-01-09 15:47:48 +03:00
if ( is_wrapping_enabled ( ) )
2020-02-25 16:49:47 +03:00
visual_data . visual_rect = { m_horizontal_content_padding , 0 , available_width , static_cast < int > ( visual_data . visual_line_breaks . size ( ) ) * line_height ( ) } ;
2019-08-25 09:43:01 +03:00
else
2019-10-27 20:00:07 +03:00
visual_data . visual_rect = { m_horizontal_content_padding , 0 , font ( ) . width ( line . view ( ) ) , line_height ( ) } ;
2019-08-25 09:43:01 +03:00
}
template < typename Callback >
2020-02-02 17:07:41 +03:00
void TextEditor : : for_each_visual_line ( size_t line_index , Callback callback ) const
2019-08-25 09:43:01 +03:00
{
2019-10-27 20:00:07 +03:00
auto editor_visible_text_rect = visible_text_rect_in_inner_coordinates ( ) ;
2019-12-09 19:45:40 +03:00
size_t start_of_line = 0 ;
size_t visual_line_index = 0 ;
2019-10-27 20:00:07 +03:00
auto & line = document ( ) . line ( line_index ) ;
auto & visual_data = m_line_visual_data [ line_index ] ;
for ( auto visual_line_break : visual_data . visual_line_breaks ) {
2020-08-05 23:31:20 +03:00
auto visual_line_view = Utf32View ( line . code_points ( ) + start_of_line , visual_line_break - start_of_line ) ;
2020-06-10 11:57:59 +03:00
Gfx : : IntRect visual_line_rect {
2019-10-27 20:00:07 +03:00
visual_data . visual_rect . x ( ) ,
2019-12-09 19:45:40 +03:00
visual_data . visual_rect . y ( ) + ( ( int ) visual_line_index * line_height ( ) ) ,
2021-01-02 02:39:52 +03:00
font ( ) . width ( visual_line_view ) + font ( ) . glyph_spacing ( ) ,
2019-10-27 20:00:07 +03:00
line_height ( )
2019-08-25 09:43:01 +03:00
} ;
2019-10-27 20:00:07 +03:00
if ( is_right_text_alignment ( text_alignment ( ) ) )
2019-09-16 21:57:32 +03:00
visual_line_rect . set_right_without_resize ( editor_visible_text_rect . right ( ) ) ;
2020-06-29 21:34:42 +03:00
if ( is_single_line ( ) ) {
2019-09-16 21:57:32 +03:00
visual_line_rect . center_vertically_within ( editor_visible_text_rect ) ;
2020-06-29 21:34:42 +03:00
if ( m_icon )
2021-04-12 21:47:09 +03:00
visual_line_rect . translate_by ( icon_size ( ) + icon_padding ( ) , 0 ) ;
2020-06-29 21:34:42 +03:00
}
2020-11-13 08:29:55 +03:00
if ( callback ( visual_line_rect , visual_line_view , start_of_line , visual_line_index = = visual_data . visual_line_breaks . size ( ) - 1 ) = = IterationDecision : : Break )
2019-08-25 09:43:01 +03:00
break ;
start_of_line = visual_line_break ;
2019-10-27 20:00:07 +03:00
+ + visual_line_index ;
2019-08-25 09:43:01 +03:00
}
}
2019-08-25 13:23:14 +03:00
2021-01-09 15:47:48 +03:00
void TextEditor : : set_wrapping_mode ( WrappingMode mode )
2019-08-25 13:23:14 +03:00
{
2021-01-09 15:47:48 +03:00
if ( m_wrapping_mode = = mode )
2019-08-25 13:23:14 +03:00
return ;
2021-01-09 15:47:48 +03:00
m_wrapping_mode = mode ;
horizontal_scrollbar ( ) . set_visible ( m_wrapping_mode = = WrappingMode : : NoWrap ) ;
2019-08-25 13:23:14 +03:00
update_content_size ( ) ;
recompute_all_visual_lines ( ) ;
update ( ) ;
}
2019-08-25 15:04:46 +03:00
2020-02-02 17:07:41 +03:00
void TextEditor : : add_custom_context_menu_action ( Action & action )
2019-08-25 22:33:08 +03:00
{
m_custom_context_menu_actions . append ( action ) ;
}
2019-09-01 13:26:35 +03:00
2020-02-02 17:07:41 +03:00
void TextEditor : : did_change_font ( )
2019-09-01 13:26:35 +03:00
{
vertical_scrollbar ( ) . set_step ( line_height ( ) ) ;
2019-12-30 01:03:41 +03:00
recompute_all_visual_lines ( ) ;
update ( ) ;
2021-05-03 21:31:58 +03:00
AbstractScrollableWidget : : did_change_font ( ) ;
2019-09-01 13:26:35 +03:00
}
2019-10-27 20:00:07 +03:00
2020-02-02 17:07:41 +03:00
void TextEditor : : document_did_append_line ( )
2019-10-27 20:00:07 +03:00
{
m_line_visual_data . append ( make < LineVisualData > ( ) ) ;
2019-10-27 21:36:59 +03:00
recompute_all_visual_lines ( ) ;
update ( ) ;
2019-10-27 20:00:07 +03:00
}
2020-02-02 17:07:41 +03:00
void TextEditor : : document_did_remove_line ( size_t line_index )
2019-10-27 20:00:07 +03:00
{
m_line_visual_data . remove ( line_index ) ;
2019-10-27 21:36:59 +03:00
recompute_all_visual_lines ( ) ;
update ( ) ;
2019-10-27 20:00:07 +03:00
}
2020-02-02 17:07:41 +03:00
void TextEditor : : document_did_remove_all_lines ( )
2019-10-27 20:00:07 +03:00
{
m_line_visual_data . clear ( ) ;
2019-10-27 21:36:59 +03:00
recompute_all_visual_lines ( ) ;
update ( ) ;
2019-10-27 20:00:07 +03:00
}
2020-02-02 17:07:41 +03:00
void TextEditor : : document_did_insert_line ( size_t line_index )
2019-10-27 20:00:07 +03:00
{
m_line_visual_data . insert ( line_index , make < LineVisualData > ( ) ) ;
2019-10-27 21:36:59 +03:00
recompute_all_visual_lines ( ) ;
update ( ) ;
}
2020-02-02 17:07:41 +03:00
void TextEditor : : document_did_change ( )
2019-10-27 21:36:59 +03:00
{
2019-11-30 20:54:53 +03:00
did_change ( ) ;
2019-10-27 21:36:59 +03:00
update ( ) ;
2019-10-27 20:00:07 +03:00
}
2021-05-08 14:16:37 +03:00
void TextEditor : : document_did_update_undo_stack ( )
{
m_undo_action - > set_enabled ( can_undo ( ) ) ;
m_redo_action - > set_enabled ( can_redo ( ) ) ;
2021-05-08 14: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 14:16:37 +03:00
}
2020-02-02 17:07:41 +03:00
void TextEditor : : document_did_set_text ( )
2019-11-23 19:41:14 +03:00
{
m_line_visual_data . clear ( ) ;
2019-12-09 19:45:40 +03:00
for ( size_t i = 0 ; i < m_document - > line_count ( ) ; + + i )
2019-11-23 19:41:14 +03:00
m_line_visual_data . append ( make < LineVisualData > ( ) ) ;
document_did_change ( ) ;
}
2020-02-02 17:07:41 +03:00
void TextEditor : : document_did_set_cursor ( const TextPosition & position )
2019-11-30 15:05:17 +03:00
{
set_cursor ( position ) ;
}
2020-02-02 17:07:41 +03:00
void TextEditor : : set_document ( TextDocument & document )
2019-10-27 21: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 19:45:40 +03:00
for ( size_t i = 0 ; i < m_document - > line_count ( ) ; + + i ) {
2019-10-27 21:36:59 +03:00
m_line_visual_data . append ( make < LineVisualData > ( ) ) ;
}
2020-08-05 18:19:37 +03:00
set_cursor ( 0 , 0 ) ;
2019-12-02 05:49:13 +03:00
if ( has_selection ( ) )
m_selection . clear ( ) ;
2019-10-27 21:36:59 +03:00
recompute_all_visual_lines ( ) ;
update ( ) ;
m_document - > register_client ( * this ) ;
}
2019-11-02 22:22:49 +03:00
2020-02-02 17:07:41 +03:00
void TextEditor : : flush_pending_change_notification_if_needed ( )
2019-11-08 21:49:08 +03:00
{
if ( ! m_has_pending_change_notification )
return ;
if ( on_change )
on_change ( ) ;
2020-02-07 22:07:15 +03:00
if ( m_highlighter )
2020-03-16 02:05:06 +03:00
m_highlighter - > rehighlight ( palette ( ) ) ;
2019-11-08 21:49:08 +03:00
m_has_pending_change_notification = false ;
}
2020-02-02 17:07:41 +03:00
2021-02-07 17:15:10 +03:00
const Syntax : : Highlighter * TextEditor : : syntax_highlighter ( ) const
2020-03-12 17:36:25 +03:00
{
2020-03-12 18:23:54 +03:00
return m_highlighter . ptr ( ) ;
2020-03-12 17:36:25 +03:00
}
2021-02-07 17:15:10 +03:00
void TextEditor : : set_syntax_highlighter ( OwnPtr < Syntax : : Highlighter > highlighter )
2020-02-07 22:07:15 +03:00
{
if ( m_highlighter )
m_highlighter - > detach ( ) ;
m_highlighter = move ( highlighter ) ;
if ( m_highlighter ) {
m_highlighter - > attach ( * this ) ;
2020-03-16 02:05:06 +03:00
m_highlighter - > rehighlight ( palette ( ) ) ;
2020-03-11 04:01:52 +03:00
} else
document ( ) . set_spans ( { } ) ;
2020-02-07 22:07:15 +03:00
}
2020-12-30 13:25:06 +03:00
const AutocompleteProvider * TextEditor : : autocomplete_provider ( ) const
{
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 )
m_autocomplete_box - > close ( ) ;
}
2021-01-02 13:59:55 +03:00
const EditingEngine * TextEditor : : editing_engine ( ) const
{
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 22:42:32 +03:00
VERIFY ( m_editing_engine ) ;
2021-01-02 13:59:55 +03:00
m_editing_engine - > attach ( * this ) ;
m_cursor_state = true ;
update_cursor ( ) ;
stop_timer ( ) ;
start_timer ( 500 ) ;
}
2020-02-15 02:24:14 +03:00
int TextEditor : : line_height ( ) const
{
return font ( ) . glyph_height ( ) + m_line_spacing ;
}
2020-05-18 17:38:28 +03:00
int TextEditor : : fixed_glyph_width ( ) const
2020-02-15 02:24:14 +03:00
{
2021-02-23 22:42:32 +03:00
VERIFY ( font ( ) . is_fixed_width ( ) ) ;
2020-05-18 17:38:28 +03:00
return font ( ) . glyph_width ( ' ' ) ;
2020-02-15 02:24:14 +03:00
}
2020-06-29 21:34:42 +03:00
void TextEditor : : set_icon ( const Gfx : : Bitmap * icon )
{
if ( m_icon = = icon )
return ;
m_icon = icon ;
update ( ) ;
}
2020-09-01 20: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 19: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 16:59:11 +03:00
void TextEditor : : set_should_autocomplete_automatically ( bool value )
{
if ( value = = should_autocomplete_automatically ( ) )
return ;
if ( value ) {
2021-02-23 22:42:32 +03:00
VERIFY ( m_autocomplete_provider ) ;
2021-01-01 16:59:11 +03:00
m_autocomplete_timer = Core : : Timer : : create_single_shot ( m_automatic_autocomplete_delay_ms , [ this ] { try_show_autocomplete ( ) ; } ) ;
return ;
}
remove_child ( * m_autocomplete_timer ) ;
m_autocomplete_timer = nullptr ;
}
2021-01-02 13:59:55 +03:00
int TextEditor : : number_of_visible_lines ( ) const
{
return visible_content_rect ( ) . height ( ) / line_height ( ) ;
}
2021-01-01 16:59:11 +03:00
2021-04-10 01: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 ( ) ;
}
2020-02-02 17:07:41 +03:00
}