2020-03-30 19:59:04 +03:00
/*
* Copyright ( c ) 2018 - 2020 , Andreas Kling < kling @ serenityos . org >
2021-01-10 15:53:04 +03:00
* Copyright ( c ) 2021 , the SerenityOS developers .
2020-03-30 19:59:04 +03:00
*
2021-04-22 11:24:48 +03:00
* SPDX - License - Identifier : BSD - 2 - Clause
2020-03-30 19:59:04 +03:00
*/
2020-03-31 14:34:57 +03:00
# include "Editor.h"
2021-06-01 22:18:08 +03:00
# include <AK/CharacterTypes.h>
2021-02-20 21:03:13 +03:00
# include <AK/Debug.h>
2020-08-17 19:38:52 +03:00
# include <AK/GenericLexer.h>
2020-05-26 13:34:39 +03:00
# include <AK/JsonObject.h>
2022-05-02 14:40:42 +03:00
# include <AK/RedBlackTree.h>
2020-08-31 20:34:32 +03:00
# include <AK/ScopeGuard.h>
2021-03-12 19:29:37 +03:00
# include <AK/ScopedValueRollback.h>
2020-04-01 12:24:10 +03:00
# include <AK/StringBuilder.h>
2020-05-18 12:17:34 +03:00
# include <AK/Utf32View.h>
# include <AK/Utf8View.h>
2020-08-17 17:43:52 +03:00
# include <LibCore/ConfigFile.h>
2020-05-26 13:34:39 +03:00
# include <LibCore/Event.h>
# include <LibCore/EventLoop.h>
2020-10-26 02:25:41 +03:00
# include <LibCore/File.h>
2023-01-10 14:21:17 +03:00
# include <LibCore/MemoryStream.h>
2020-05-26 13:34:39 +03:00
# include <LibCore/Notifier.h>
2021-03-12 19:29:37 +03:00
# include <errno.h>
2022-02-13 07:26:09 +03:00
# include <fcntl.h>
2020-08-20 18:39:48 +03:00
# include <signal.h>
2020-03-30 19:59:04 +03:00
# include <stdio.h>
# include <sys/ioctl.h>
2020-04-29 00:17:41 +03:00
# include <sys/select.h>
# include <sys/time.h>
2020-03-30 19:59:04 +03:00
# include <unistd.h>
2020-07-06 19:22:03 +03:00
namespace {
2020-08-06 21:58:47 +03:00
constexpr u32 ctrl ( char c ) { return c & 0x3f ; }
2020-07-06 19:22:03 +03:00
}
2020-03-31 14:34:06 +03:00
namespace Line {
2021-11-11 02:55:02 +03:00
Configuration Configuration : : from_config ( StringView libname )
2020-08-17 17:43:52 +03:00
{
Configuration configuration ;
2022-02-06 16:33:42 +03:00
auto config_file = Core : : ConfigFile : : open_for_lib ( libname ) . release_value_but_fixme_should_propagate_errors ( ) ;
2020-08-17 17:43:52 +03:00
2021-09-07 13:56:50 +03:00
// Read behavior options.
auto refresh = config_file - > read_entry ( " behavior " , " refresh " , " lazy " ) ;
auto operation = config_file - > read_entry ( " behavior " , " operation_mode " ) ;
auto bracketed_paste = config_file - > read_bool_entry ( " behavior " , " bracketed_paste " , true ) ;
auto default_text_editor = config_file - > read_entry ( " behavior " , " default_text_editor " ) ;
2020-08-17 17:43:52 +03:00
2021-05-24 15:00:44 +03:00
Configuration : : Flags flags { Configuration : : Flags : : None } ;
if ( bracketed_paste )
flags = static_cast < Flags > ( flags | Configuration : : Flags : : BracketedPaste ) ;
configuration . set ( flags ) ;
2022-07-11 20:32:29 +03:00
if ( refresh . equals_ignoring_case ( " lazy " sv ) )
2020-08-17 17:43:52 +03:00
configuration . set ( Configuration : : Lazy ) ;
2022-07-11 20:32:29 +03:00
else if ( refresh . equals_ignoring_case ( " eager " sv ) )
2020-08-17 17:43:52 +03:00
configuration . set ( Configuration : : Eager ) ;
2022-07-11 20:32:29 +03:00
if ( operation . equals_ignoring_case ( " full " sv ) )
2020-08-17 17:43:52 +03:00
configuration . set ( Configuration : : OperationMode : : Full ) ;
2022-07-11 20:32:29 +03:00
else if ( operation . equals_ignoring_case ( " noescapesequences " sv ) )
2020-08-17 17:43:52 +03:00
configuration . set ( Configuration : : OperationMode : : NoEscapeSequences ) ;
2022-07-11 20:32:29 +03:00
else if ( operation . equals_ignoring_case ( " noninteractive " sv ) )
2020-08-17 17:43:52 +03:00
configuration . set ( Configuration : : OperationMode : : NonInteractive ) ;
else
configuration . set ( Configuration : : OperationMode : : Unset ) ;
2021-04-17 20:11:38 +03:00
if ( ! default_text_editor . is_empty ( ) )
configuration . set ( DefaultTextEditor { move ( default_text_editor ) } ) ;
else
configuration . set ( DefaultTextEditor { " /bin/TextEditor " } ) ;
2020-08-17 19:38:52 +03:00
// Read keybinds.
for ( auto & binding_key : config_file - > keys ( " keybinds " ) ) {
GenericLexer key_lexer ( binding_key ) ;
auto has_ctrl = false ;
auto alt = false ;
2020-10-19 09:37:00 +03:00
auto escape = false ;
2020-10-19 09:09:36 +03:00
Vector < Key > keys ;
2020-08-17 19:38:52 +03:00
2020-10-19 09:09:36 +03:00
while ( ! key_lexer . is_eof ( ) ) {
2020-10-19 09:37:00 +03:00
unsigned key ;
if ( escape ) {
key = key_lexer . consume_escaped_character ( ) ;
escape = false ;
} else {
if ( key_lexer . next_is ( " alt+ " ) ) {
alt = key_lexer . consume_specific ( " alt+ " ) ;
continue ;
}
if ( key_lexer . next_is ( " ^[ " ) ) {
alt = key_lexer . consume_specific ( " ^[ " ) ;
continue ;
}
if ( key_lexer . next_is ( " ^ " ) ) {
has_ctrl = key_lexer . consume_specific ( " ^ " ) ;
continue ;
}
if ( key_lexer . next_is ( " ctrl+ " ) ) {
has_ctrl = key_lexer . consume_specific ( " ctrl+ " ) ;
continue ;
}
if ( key_lexer . next_is ( " \\ " ) ) {
escape = true ;
continue ;
}
// FIXME: Support utf?
key = key_lexer . consume ( ) ;
2020-08-17 19:38:52 +03:00
}
2020-10-19 09:09:36 +03:00
if ( has_ctrl )
key = ctrl ( key ) ;
2020-08-17 19:38:52 +03:00
2020-10-19 09:09:36 +03:00
keys . append ( Key { key , alt ? Key : : Alt : Key : : None } ) ;
alt = false ;
has_ctrl = false ;
}
2020-08-17 19:38:52 +03:00
2020-10-19 09:37:00 +03:00
GenericLexer value_lexer { config_file - > read_entry ( " keybinds " , binding_key ) } ;
StringBuilder value_builder ;
while ( ! value_lexer . is_eof ( ) )
value_builder . append ( value_lexer . consume_escaped_character ( ) ) ;
auto value = value_builder . string_view ( ) ;
2022-07-11 20:32:29 +03:00
if ( value . starts_with ( " internal: " sv ) ) {
2020-08-17 19:38:52 +03:00
configuration . set ( KeyBinding {
2020-10-19 09:09:36 +03:00
keys ,
2020-08-17 19:38:52 +03:00
KeyBinding : : Kind : : InternalFunction ,
2020-10-19 09:37:00 +03:00
value . substring_view ( 9 , value . length ( ) - 9 ) } ) ;
2020-08-17 19:38:52 +03:00
} else {
configuration . set ( KeyBinding {
2020-10-19 09:09:36 +03:00
keys ,
2020-08-17 19:38:52 +03:00
KeyBinding : : Kind : : Insertion ,
value } ) ;
}
}
2020-08-17 17:43:52 +03:00
return configuration ;
}
2020-08-17 19:08:10 +03:00
void Editor : : set_default_keybinds ( )
{
register_key_input_callback ( ctrl ( ' N ' ) , EDITOR_INTERNAL_FUNCTION ( search_forwards ) ) ;
register_key_input_callback ( ctrl ( ' P ' ) , EDITOR_INTERNAL_FUNCTION ( search_backwards ) ) ;
register_key_input_callback ( ctrl ( ' A ' ) , EDITOR_INTERNAL_FUNCTION ( go_home ) ) ;
register_key_input_callback ( ctrl ( ' B ' ) , EDITOR_INTERNAL_FUNCTION ( cursor_left_character ) ) ;
register_key_input_callback ( ctrl ( ' D ' ) , EDITOR_INTERNAL_FUNCTION ( erase_character_forwards ) ) ;
register_key_input_callback ( ctrl ( ' E ' ) , EDITOR_INTERNAL_FUNCTION ( go_end ) ) ;
register_key_input_callback ( ctrl ( ' F ' ) , EDITOR_INTERNAL_FUNCTION ( cursor_right_character ) ) ;
// ^H: ctrl('H') == '\b'
register_key_input_callback ( ctrl ( ' H ' ) , EDITOR_INTERNAL_FUNCTION ( erase_character_backwards ) ) ;
2021-03-13 12:29:34 +03:00
// DEL - Some terminals send this instead of ^H.
register_key_input_callback ( ( char ) 127 , EDITOR_INTERNAL_FUNCTION ( erase_character_backwards ) ) ;
2020-08-17 19:08:10 +03:00
register_key_input_callback ( ctrl ( ' K ' ) , EDITOR_INTERNAL_FUNCTION ( erase_to_end ) ) ;
register_key_input_callback ( ctrl ( ' L ' ) , EDITOR_INTERNAL_FUNCTION ( clear_screen ) ) ;
register_key_input_callback ( ctrl ( ' R ' ) , EDITOR_INTERNAL_FUNCTION ( enter_search ) ) ;
register_key_input_callback ( ctrl ( ' T ' ) , EDITOR_INTERNAL_FUNCTION ( transpose_characters ) ) ;
register_key_input_callback ( ' \n ' , EDITOR_INTERNAL_FUNCTION ( finish ) ) ;
2021-04-17 20:11:38 +03:00
// ^X^E: Edit in external editor
register_key_input_callback ( Vector < Key > { ctrl ( ' X ' ) , ctrl ( ' E ' ) } , EDITOR_INTERNAL_FUNCTION ( edit_in_external_editor ) ) ;
2020-08-17 19:08:10 +03:00
// ^[.: alt-.: insert last arg of previous command (similar to `!$`)
2020-10-19 09:09:36 +03:00
register_key_input_callback ( Key { ' . ' , Key : : Alt } , EDITOR_INTERNAL_FUNCTION ( insert_last_words ) ) ;
register_key_input_callback ( Key { ' b ' , Key : : Alt } , EDITOR_INTERNAL_FUNCTION ( cursor_left_word ) ) ;
register_key_input_callback ( Key { ' f ' , Key : : Alt } , EDITOR_INTERNAL_FUNCTION ( cursor_right_word ) ) ;
2020-08-17 19:08:10 +03:00
// ^[^H: alt-backspace: backward delete word
2020-10-19 09:09:36 +03:00
register_key_input_callback ( Key { ' \b ' , Key : : Alt } , EDITOR_INTERNAL_FUNCTION ( erase_alnum_word_backwards ) ) ;
register_key_input_callback ( Key { ' d ' , Key : : Alt } , EDITOR_INTERNAL_FUNCTION ( erase_alnum_word_forwards ) ) ;
register_key_input_callback ( Key { ' c ' , Key : : Alt } , EDITOR_INTERNAL_FUNCTION ( capitalize_word ) ) ;
register_key_input_callback ( Key { ' l ' , Key : : Alt } , EDITOR_INTERNAL_FUNCTION ( lowercase_word ) ) ;
register_key_input_callback ( Key { ' u ' , Key : : Alt } , EDITOR_INTERNAL_FUNCTION ( uppercase_word ) ) ;
register_key_input_callback ( Key { ' t ' , Key : : Alt } , EDITOR_INTERNAL_FUNCTION ( transpose_words ) ) ;
2021-05-24 15:20:16 +03:00
// Register these last to all the user to override the previous key bindings
// Normally ^W. `stty werase \^n` can change it to ^N (or something else).
register_key_input_callback ( m_termios . c_cc [ VWERASE ] , EDITOR_INTERNAL_FUNCTION ( erase_word_backwards ) ) ;
// Normally ^U. `stty kill \^n` can change it to ^N (or something else).
register_key_input_callback ( m_termios . c_cc [ VKILL ] , EDITOR_INTERNAL_FUNCTION ( kill_line ) ) ;
register_key_input_callback ( m_termios . c_cc [ VERASE ] , EDITOR_INTERNAL_FUNCTION ( erase_character_backwards ) ) ;
2020-08-17 19:08:10 +03:00
}
2020-04-30 06:49:47 +03:00
Editor : : Editor ( Configuration configuration )
2020-05-19 07:12:01 +03:00
: m_configuration ( move ( configuration ) )
2020-03-30 19:59:04 +03:00
{
2021-09-07 13:56:50 +03:00
m_always_refresh = m_configuration . refresh_behavior = = Configuration : : RefreshBehavior : : Eager ;
2021-09-06 01:59:52 +03:00
m_pending_chars = { } ;
2020-07-06 18:28:11 +03:00
get_terminal_size ( ) ;
2020-08-04 18:04:15 +03:00
m_suggestion_display = make < XtermSuggestionDisplay > ( m_num_lines , m_num_columns ) ;
2020-07-06 18:28:11 +03:00
}
Editor : : ~ Editor ( )
{
if ( m_initialized )
restore ( ) ;
}
2021-05-16 08:15:22 +03:00
void Editor : : ensure_free_lines_from_origin ( size_t count )
{
if ( count > m_num_lines ) {
2022-10-27 23:13:36 +03:00
// FIXME: Implement paging
2021-05-16 08:15:22 +03:00
}
if ( m_origin_row + count < = m_num_lines )
return ;
auto diff = m_origin_row + count - m_num_lines - 1 ;
out ( stderr , " \x1b [{}S " , diff ) ;
fflush ( stderr ) ;
m_origin_row - = diff ;
m_refresh_needed = false ;
m_chars_touched_in_the_middle = 0 ;
}
2020-07-06 18:28:11 +03:00
void Editor : : get_terminal_size ( )
{
2020-03-30 19:59:04 +03:00
struct winsize ws ;
2022-02-28 07:01:18 +03:00
ioctl ( STDERR_FILENO , TIOCGWINSZ , & ws ) ;
if ( ws . ws_col = = 0 | | ws . ws_row = = 0 ) {
// LLDB uses ttys which "work" and then gives us a zero sized
// terminal which is far from useful
if ( int fd = open ( " /dev/tty " , O_RDONLY ) ; fd ! = - 1 ) {
ioctl ( fd , TIOCGWINSZ , & ws ) ;
close ( fd ) ;
2022-02-13 07:26:09 +03:00
}
2020-04-11 11:59:55 +03:00
}
2022-02-28 07:01:18 +03:00
m_num_columns = ws . ws_col ;
m_num_lines = ws . ws_row ;
2020-03-30 19:59:04 +03:00
}
2022-12-04 21:02:33 +03:00
void Editor : : add_to_history ( DeprecatedString const & line )
2020-03-30 19:59:04 +03:00
{
2020-05-26 01:45:01 +03:00
if ( line . is_empty ( ) )
return ;
2022-12-04 21:02:33 +03:00
DeprecatedString histcontrol = getenv ( " HISTCONTROL " ) ;
2020-10-26 02:35:00 +03:00
auto ignoredups = histcontrol = = " ignoredups " | | histcontrol = = " ignoreboth " ;
auto ignorespace = histcontrol = = " ignorespace " | | histcontrol = = " ignoreboth " ;
2021-01-11 18:28:42 +03:00
if ( ignoredups & & ! m_history . is_empty ( ) & & line = = m_history . last ( ) . entry )
2020-10-26 02:35:00 +03:00
return ;
if ( ignorespace & & line . starts_with ( ' ' ) )
return ;
2020-03-30 19:59:04 +03:00
if ( ( m_history . size ( ) + 1 ) > m_history_capacity )
m_history . take_first ( ) ;
2021-01-11 18:28:42 +03:00
struct timeval tv ;
gettimeofday ( & tv , nullptr ) ;
m_history . append ( { line , tv . tv_sec } ) ;
2021-05-11 18:06:03 +03:00
m_history_dirty = true ;
2020-03-30 19:59:04 +03:00
}
2022-12-04 21:02:33 +03:00
bool Editor : : load_history ( DeprecatedString const & path )
2020-10-26 02:25:41 +03:00
{
auto history_file = Core : : File : : construct ( path ) ;
2021-05-12 12:26:43 +03:00
if ( ! history_file - > open ( Core : : OpenMode : : ReadOnly ) )
2020-10-26 02:25:41 +03:00
return false ;
2021-01-11 18:28:42 +03:00
auto data = history_file - > read_all ( ) ;
auto hist = StringView { data . data ( ) , data . size ( ) } ;
2022-07-11 20:32:29 +03:00
for ( auto & str : hist . split_view ( " \n \n " sv ) ) {
auto it = str . find ( " :: " sv ) . value_or ( 0 ) ;
2022-12-16 01:27:38 +03:00
auto time = str . substring_view ( 0 , it ) . to_int < time_t > ( ) . value_or ( 0 ) ;
2021-01-11 18:28:42 +03:00
auto string = str . substring_view ( it = = 0 ? it : it + 2 ) ;
m_history . append ( { string , time } ) ;
2020-10-26 02:25:41 +03:00
}
return true ;
}
2021-01-11 18:28:42 +03:00
template < typename It0 , typename It1 , typename OutputT , typename MapperT , typename LessThan >
2021-12-14 14:11:18 +03:00
static void merge ( It0 & & begin0 , It0 const & end0 , It1 & & begin1 , It1 const & end1 , OutputT & output , MapperT left_mapper , LessThan less_than )
2021-01-11 18:28:42 +03:00
{
for ( ; ; ) {
if ( begin0 = = end0 & & begin1 = = end1 )
return ;
if ( begin0 = = end0 ) {
auto & & right = * begin1 ;
if ( output . last ( ) . entry ! = right . entry )
output . append ( right ) ;
+ + begin1 ;
continue ;
}
auto & & left = left_mapper ( * begin0 ) ;
if ( left . entry . is_whitespace ( ) ) {
+ + begin0 ;
continue ;
}
if ( begin1 = = end1 ) {
if ( output . last ( ) . entry ! = left . entry )
output . append ( left ) ;
+ + begin0 ;
continue ;
}
auto & & right = * begin1 ;
if ( less_than ( left , right ) ) {
if ( output . last ( ) . entry ! = left . entry )
output . append ( left ) ;
+ + begin0 ;
} else {
if ( output . last ( ) . entry ! = right . entry )
output . append ( right ) ;
+ + begin1 ;
if ( right . entry = = left . entry )
+ + begin0 ;
}
}
}
2022-12-04 21:02:33 +03:00
bool Editor : : save_history ( DeprecatedString const & path )
2020-10-26 02:25:41 +03:00
{
2021-01-11 18:28:42 +03:00
Vector < HistoryEntry > final_history { { " " , 0 } } ;
{
2021-05-12 12:26:43 +03:00
auto file_or_error = Core : : File : : open ( path , Core : : OpenMode : : ReadWrite , 0600 ) ;
2021-01-11 18:28:42 +03:00
if ( file_or_error . is_error ( ) )
return false ;
auto file = file_or_error . release_value ( ) ;
merge (
file - > line_begin ( ) , file - > line_end ( ) , m_history . begin ( ) , m_history . end ( ) , final_history ,
[ ] ( StringView str ) {
2022-07-11 20:32:29 +03:00
auto it = str . find ( " :: " sv ) . value_or ( 0 ) ;
2022-12-16 01:27:38 +03:00
auto time = str . substring_view ( 0 , it ) . to_int < time_t > ( ) . value_or ( 0 ) ;
2021-01-11 18:28:42 +03:00
auto string = str . substring_view ( it = = 0 ? it : it + 2 ) ;
return HistoryEntry { string , time } ;
} ,
2021-12-14 14:11:18 +03:00
[ ] ( HistoryEntry const & left , HistoryEntry const & right ) { return left . timestamp < right . timestamp ; } ) ;
2021-01-11 18:28:42 +03:00
}
2021-05-12 12:26:43 +03:00
auto file_or_error = Core : : File : : open ( path , Core : : OpenMode : : WriteOnly , 0600 ) ;
2020-10-26 02:25:41 +03:00
if ( file_or_error . is_error ( ) )
return false ;
2021-01-11 18:28:42 +03:00
auto file = file_or_error . release_value ( ) ;
final_history . take_first ( ) ;
2021-12-14 14:11:18 +03:00
for ( auto const & entry : final_history )
2022-12-04 21:02:33 +03:00
file - > write ( DeprecatedString : : formatted ( " {}::{} \n \n " , entry . timestamp , entry . entry ) ) ;
2021-01-11 18:28:42 +03:00
2021-05-11 18:06:03 +03:00
m_history_dirty = false ;
2020-10-26 02:25:41 +03:00
return true ;
}
2020-03-31 14:34:06 +03:00
void Editor : : clear_line ( )
2020-03-30 19:59:04 +03:00
{
for ( size_t i = 0 ; i < m_cursor ; + + i )
2020-08-09 18:01:15 +03:00
fputc ( 0x8 , stderr ) ;
fputs ( " \033 [K " , stderr ) ;
fflush ( stderr ) ;
2021-02-21 03:33:43 +03:00
m_chars_touched_in_the_middle = buffer ( ) . size ( ) ;
2021-03-05 15:01:13 +03:00
m_buffer . clear ( ) ;
2020-03-30 19:59:04 +03:00
m_cursor = 0 ;
2020-04-20 15:50:31 +03:00
m_inline_search_cursor = m_cursor ;
2020-03-30 19:59:04 +03:00
}
2021-12-14 14:11:18 +03:00
void Editor : : insert ( Utf32View const & string )
2020-05-21 16:36:43 +03:00
{
for ( size_t i = 0 ; i < string . length ( ) ; + + i )
2020-08-05 23:31:20 +03:00
insert ( string . code_points ( ) [ i ] ) ;
2020-05-21 16:36:43 +03:00
}
2022-12-04 21:02:33 +03:00
void Editor : : insert ( DeprecatedString const & string )
2020-03-30 19:59:04 +03:00
{
2020-05-18 12:17:34 +03:00
for ( auto ch : Utf8View { string } )
2020-04-11 11:59:55 +03:00
insert ( ch ) ;
2020-03-30 19:59:04 +03:00
}
2021-11-11 02:55:02 +03:00
void Editor : : insert ( StringView string_view )
2020-08-06 21:01:56 +03:00
{
for ( auto ch : Utf8View { string_view } )
insert ( ch ) ;
}
2020-05-18 12:17:34 +03:00
void Editor : : insert ( const u32 cp )
2020-03-30 19:59:04 +03:00
{
2020-05-18 12:17:34 +03:00
StringBuilder builder ;
builder . append ( Utf32View ( & cp , 1 ) ) ;
auto str = builder . build ( ) ;
2021-11-10 16:33:44 +03:00
if ( m_pending_chars . try_append ( str . characters ( ) , str . length ( ) ) . is_error ( ) )
2021-09-06 01:58:46 +03:00
return ;
2020-05-18 12:17:34 +03:00
2020-05-18 00:55:58 +03:00
readjust_anchored_styles ( m_cursor , ModificationKind : : Insertion ) ;
2020-03-30 19:59:04 +03:00
if ( m_cursor = = m_buffer . size ( ) ) {
2020-05-18 12:17:34 +03:00
m_buffer . append ( cp ) ;
2020-03-30 19:59:04 +03:00
m_cursor = m_buffer . size ( ) ;
2020-04-20 15:50:31 +03:00
m_inline_search_cursor = m_cursor ;
2020-03-30 19:59:04 +03:00
return ;
}
2020-05-18 12:17:34 +03:00
m_buffer . insert ( m_cursor , cp ) ;
2021-02-20 21:03:13 +03:00
+ + m_chars_touched_in_the_middle ;
2020-03-30 19:59:04 +03:00
+ + m_cursor ;
2020-04-20 15:50:31 +03:00
m_inline_search_cursor = m_cursor ;
2020-03-30 19:59:04 +03:00
}
2021-12-14 14:11:18 +03:00
void Editor : : register_key_input_callback ( KeyBinding const & binding )
2020-03-30 19:59:04 +03:00
{
2020-08-17 19:08:10 +03:00
if ( binding . kind = = KeyBinding : : Kind : : InternalFunction ) {
auto internal_function = find_internal_function ( binding . binding ) ;
if ( ! internal_function ) {
2021-01-11 23:13:30 +03:00
dbgln ( " LibLine: Unknown internal function '{}' " , binding . binding ) ;
2020-08-17 19:08:10 +03:00
return ;
}
2020-10-19 09:09:36 +03:00
return register_key_input_callback ( binding . keys , move ( internal_function ) ) ;
2020-03-30 19:59:04 +03:00
}
2020-08-17 19:08:10 +03:00
2022-12-04 21:02:33 +03:00
return register_key_input_callback ( binding . keys , [ binding = DeprecatedString ( binding . binding ) ] ( auto & editor ) {
2020-08-17 19:08:10 +03:00
editor . insert ( binding ) ;
return false ;
} ) ;
}
2020-08-05 23:31:20 +03:00
static size_t code_point_length_in_utf8 ( u32 code_point )
2020-05-18 20:05:01 +03:00
{
2020-08-05 23:31:20 +03:00
if ( code_point < = 0x7f )
2020-05-18 20:05:01 +03:00
return 1 ;
2020-08-05 23:31:20 +03:00
if ( code_point < = 0x07ff )
2020-05-18 20:05:01 +03:00
return 2 ;
2020-08-05 23:31:20 +03:00
if ( code_point < = 0xffff )
2020-05-18 20:05:01 +03:00
return 3 ;
2020-08-05 23:31:20 +03:00
if ( code_point < = 0x10ffff )
2020-05-18 20:05:01 +03:00
return 4 ;
return 3 ;
}
2020-05-21 13:45:56 +03:00
// buffer [ 0 1 2 3 . . . A . . . B . . . M . . . N ]
// ^ ^ ^ ^
// | | | +- end of buffer
// | | +- scan offset = M
// | +- range end = M - B
// +- range start = M - A
2020-08-05 23:31:20 +03:00
// This method converts a byte range defined by [start_byte_offset, end_byte_offset] to a code_point range [M - A, M - B] as shown in the diagram above.
2020-05-21 13:45:56 +03:00
// If `reverse' is true, A and B are before M, if not, A and B are after M.
2020-08-05 23:31:20 +03:00
Editor : : CodepointRange Editor : : byte_offset_range_to_code_point_offset_range ( size_t start_byte_offset , size_t end_byte_offset , size_t scan_code_point_offset , bool reverse ) const
2020-05-21 13:45:56 +03:00
{
size_t byte_offset = 0 ;
2020-08-05 23:31:20 +03:00
size_t code_point_offset = scan_code_point_offset + ( reverse ? 1 : 0 ) ;
2020-05-21 13:45:56 +03:00
CodepointRange range ;
for ( ; ; ) {
if ( ! reverse ) {
2020-08-05 23:31:20 +03:00
if ( code_point_offset > = m_buffer . size ( ) )
2020-05-21 13:45:56 +03:00
break ;
} else {
2020-08-05 23:31:20 +03:00
if ( code_point_offset = = 0 )
2020-05-21 13:45:56 +03:00
break ;
}
if ( byte_offset > end_byte_offset )
break ;
if ( byte_offset < start_byte_offset )
+ + range . start ;
if ( byte_offset < end_byte_offset )
+ + range . end ;
2020-08-05 23:31:20 +03:00
byte_offset + = code_point_length_in_utf8 ( m_buffer [ reverse ? - - code_point_offset : code_point_offset + + ] ) ;
2020-05-21 13:45:56 +03:00
}
return range ;
}
2021-12-14 14:11:18 +03:00
void Editor : : stylize ( Span const & span , Style const & style )
2020-04-05 05:11:33 +03:00
{
2022-07-17 02:37:03 +03:00
if ( ! span . is_empty ( ) )
return ;
2020-05-18 00:55:58 +03:00
if ( style . is_empty ( ) )
return ;
2020-05-18 20:05:01 +03:00
auto start = span . beginning ( ) ;
auto end = span . end ( ) ;
if ( span . mode ( ) = = Span : : ByteOriented ) {
2020-08-05 23:31:20 +03:00
auto offsets = byte_offset_range_to_code_point_offset_range ( start , end , 0 ) ;
2020-05-18 20:05:01 +03:00
2020-05-21 13:45:56 +03:00
start = offsets . start ;
end = offsets . end ;
2020-05-18 20:05:01 +03:00
}
2022-05-02 14:40:42 +03:00
if ( auto maybe_mask = style . mask ( ) ; maybe_mask . has_value ( ) ) {
auto it = m_current_masks . find_smallest_not_below_iterator ( span . beginning ( ) ) ;
Optional < Style : : Mask > last_encountered_entry ;
if ( ! it . is_end ( ) ) {
// Delete all overlapping old masks.
while ( true ) {
auto next_it = m_current_masks . find_largest_not_above_iterator ( span . end ( ) ) ;
if ( next_it . is_end ( ) )
break ;
if ( it - > has_value ( ) )
last_encountered_entry = * it ;
m_current_masks . remove ( next_it . key ( ) ) ;
}
}
m_current_masks . insert ( span . beginning ( ) , move ( maybe_mask ) ) ;
m_current_masks . insert ( span . end ( ) , { } ) ;
if ( last_encountered_entry . has_value ( ) )
m_current_masks . insert ( span . end ( ) + 1 , move ( last_encountered_entry ) ) ;
style . unset_mask ( ) ;
}
2021-02-20 21:03:13 +03:00
auto & spans_starting = style . is_anchored ( ) ? m_current_spans . m_anchored_spans_starting : m_current_spans . m_spans_starting ;
auto & spans_ending = style . is_anchored ( ) ? m_current_spans . m_anchored_spans_ending : m_current_spans . m_spans_ending ;
2020-05-18 00:55:58 +03:00
2021-12-05 14:10:17 +03:00
auto & starting_map = spans_starting . ensure ( start ) ;
2020-05-18 20:05:01 +03:00
if ( ! starting_map . contains ( end ) )
2020-04-05 05:11:33 +03:00
m_refresh_needed = true ;
2020-05-18 20:05:01 +03:00
starting_map . set ( end , style ) ;
2020-04-05 05:11:33 +03:00
2021-12-05 14:10:17 +03:00
auto & ending_map = spans_ending . ensure ( end ) ;
2020-05-18 20:05:01 +03:00
if ( ! ending_map . contains ( start ) )
2020-04-05 05:11:33 +03:00
m_refresh_needed = true ;
2020-05-18 20:05:01 +03:00
ending_map . set ( start , style ) ;
2020-04-05 05:11:33 +03:00
}
2022-02-28 16:58:47 +03:00
void Editor : : transform_suggestion_offsets ( size_t & invariant_offset , size_t & static_offset , Span : : Mode offset_mode ) const
2020-05-21 13:45:56 +03:00
{
auto internal_static_offset = static_offset ;
auto internal_invariant_offset = invariant_offset ;
if ( offset_mode = = Span : : Mode : : ByteOriented ) {
// FIXME: We're assuming that invariant_offset points to the end of the available data
// this is not necessarily true, but is true in most cases.
2020-08-05 23:31:20 +03:00
auto offsets = byte_offset_range_to_code_point_offset_range ( internal_static_offset , internal_invariant_offset + internal_static_offset , m_cursor - 1 , true ) ;
2020-05-21 13:45:56 +03:00
internal_static_offset = offsets . start ;
internal_invariant_offset = offsets . end - offsets . start ;
}
2022-02-28 16:58:47 +03:00
invariant_offset = internal_invariant_offset ;
static_offset = internal_static_offset ;
2020-05-21 13:45:56 +03:00
}
2020-05-26 18:22:01 +03:00
void Editor : : initialize ( )
{
if ( m_initialized )
return ;
struct termios termios ;
tcgetattr ( 0 , & termios ) ;
m_default_termios = termios ; // grab a copy to restore
2021-04-17 20:30:03 +03:00
get_terminal_size ( ) ;
2020-05-26 18:22:01 +03:00
2020-08-05 08:28:47 +03:00
if ( m_configuration . operation_mode = = Configuration : : Unset ) {
2020-08-09 18:01:15 +03:00
auto istty = isatty ( STDIN_FILENO ) & & isatty ( STDERR_FILENO ) ;
2020-08-05 08:28:47 +03:00
if ( ! istty ) {
m_configuration . set ( Configuration : : NonInteractive ) ;
} else {
auto * term = getenv ( " TERM " ) ;
2022-07-11 22:53:29 +03:00
if ( term ! = NULL & & StringView { term , strlen ( term ) } . starts_with ( " xterm " sv ) )
2020-08-05 08:28:47 +03:00
m_configuration . set ( Configuration : : Full ) ;
else
m_configuration . set ( Configuration : : NoEscapeSequences ) ;
}
}
2020-05-26 18:22:01 +03:00
// Because we use our own line discipline which includes echoing,
// we disable ICANON and ECHO.
if ( m_configuration . operation_mode = = Configuration : : Full ) {
termios . c_lflag & = ~ ( ECHO | ICANON ) ;
tcsetattr ( 0 , TCSANOW , & termios ) ;
}
m_termios = termios ;
2020-08-18 13:54:26 +03:00
set_default_keybinds ( ) ;
for ( auto & keybind : m_configuration . keybindings )
register_key_input_callback ( keybind ) ;
2021-01-09 03:10:07 +03:00
if ( m_configuration . m_signal_mode = = Configuration : : WithSignalHandlers ) {
m_signal_handlers . append ( Core : : EventLoop : : register_signal ( SIGINT , [ this ] ( int ) {
2023-01-13 14:29:46 +03:00
interrupted ( ) . release_value_but_fixme_should_propagate_errors ( ) ;
2021-01-09 03:10:07 +03:00
} ) ) ;
2020-08-20 18:39:48 +03:00
2021-01-09 03:10:07 +03:00
m_signal_handlers . append ( Core : : EventLoop : : register_signal ( SIGWINCH , [ this ] ( int ) {
2023-01-13 14:29:46 +03:00
resized ( ) . release_value_but_fixme_should_propagate_errors ( ) ;
2021-01-09 03:10:07 +03:00
} ) ) ;
}
2020-08-20 18:39:48 +03:00
2020-05-26 18:22:01 +03:00
m_initialized = true ;
}
2021-05-24 15:20:16 +03:00
void Editor : : refetch_default_termios ( )
{
struct termios termios ;
tcgetattr ( 0 , & termios ) ;
m_default_termios = termios ;
if ( m_configuration . operation_mode = = Configuration : : Full )
termios . c_lflag & = ~ ( ECHO | ICANON ) ;
m_termios = termios ;
}
2023-01-13 14:29:46 +03:00
ErrorOr < void > Editor : : interrupted ( )
2020-08-20 18:34:55 +03:00
{
2021-01-09 03:10:07 +03:00
if ( m_is_searching )
return m_search_editor - > interrupted ( ) ;
2020-08-20 18:34:55 +03:00
if ( ! m_is_editing )
2023-01-13 14:29:46 +03:00
return { } ;
2020-08-20 18:34:55 +03:00
m_was_interrupted = true ;
handle_interrupt_event ( ) ;
2021-02-07 02:42:17 +03:00
if ( ! m_finish | | ! m_previous_interrupt_was_handled_as_interrupt )
2023-01-13 14:29:46 +03:00
return { } ;
2020-08-20 18:34:55 +03:00
m_finish = false ;
2021-07-19 21:42:28 +03:00
{
2023-01-13 14:29:46 +03:00
auto stderr_stream = TRY ( Core : : Stream : : File : : standard_error ( ) ) ;
TRY ( reposition_cursor ( * stderr_stream , true ) ) ;
if ( TRY ( m_suggestion_display - > cleanup ( ) ) )
TRY ( reposition_cursor ( * stderr_stream , true ) ) ;
TRY ( stderr_stream - > write_entire_buffer ( " \n " sv . bytes ( ) ) ) ;
2021-07-19 21:42:28 +03:00
}
2020-08-20 18:34:55 +03:00
m_buffer . clear ( ) ;
2021-02-21 03:33:43 +03:00
m_chars_touched_in_the_middle = buffer ( ) . size ( ) ;
2020-08-20 18:34:55 +03:00
m_is_editing = false ;
restore ( ) ;
2021-01-09 03:10:07 +03:00
m_notifier - > set_enabled ( false ) ;
2021-06-07 00:52:40 +03:00
m_notifier = nullptr ;
Core : : EventLoop : : current ( ) . quit ( Retry ) ;
2023-01-13 14:29:46 +03:00
return { } ;
2020-08-20 18:34:55 +03:00
}
2023-01-13 14:29:46 +03:00
ErrorOr < void > Editor : : resized ( )
2021-04-19 11:13:36 +03:00
{
m_was_resized = true ;
m_previous_num_columns = m_num_columns ;
get_terminal_size ( ) ;
2021-06-19 04:40:10 +03:00
if ( ! m_has_origin_reset_scheduled ) {
// Reset the origin, but make sure it doesn't blow up if we can't read it
if ( set_origin ( false ) ) {
2023-01-13 14:29:46 +03:00
TRY ( handle_resize_event ( false ) ) ;
2021-06-19 04:40:10 +03:00
} else {
2023-01-13 14:29:46 +03:00
deferred_invoke ( [ this ] { handle_resize_event ( true ) . release_value_but_fixme_should_propagate_errors ( ) ; } ) ;
2021-06-19 04:40:10 +03:00
m_has_origin_reset_scheduled = true ;
}
}
2023-01-13 14:29:46 +03:00
return { } ;
2021-06-19 04:40:10 +03:00
}
2023-01-13 14:29:46 +03:00
ErrorOr < void > Editor : : handle_resize_event ( bool reset_origin )
2021-06-19 04:40:10 +03:00
{
m_has_origin_reset_scheduled = false ;
if ( reset_origin & & ! set_origin ( false ) ) {
m_has_origin_reset_scheduled = true ;
2023-01-13 14:29:46 +03:00
deferred_invoke ( [ this ] { handle_resize_event ( true ) . release_value_but_fixme_should_propagate_errors ( ) ; } ) ;
return { } ;
2021-06-19 04:40:10 +03:00
}
set_origin ( m_origin_row , 1 ) ;
2023-01-13 14:29:46 +03:00
auto stderr_stream = TRY ( Core : : Stream : : File : : standard_error ( ) ) ;
2021-07-19 21:42:28 +03:00
2023-01-13 14:29:46 +03:00
TRY ( reposition_cursor ( * stderr_stream , true ) ) ;
TRY ( m_suggestion_display - > redisplay ( m_suggestion_manager , m_num_lines , m_num_columns ) ) ;
2021-12-14 13:37:22 +03:00
m_origin_row = m_suggestion_display - > origin_row ( ) ;
2023-01-13 14:29:46 +03:00
TRY ( reposition_cursor ( * stderr_stream ) ) ;
2021-04-19 11:13:36 +03:00
if ( m_is_searching )
2023-01-13 14:29:46 +03:00
TRY ( m_search_editor - > resized ( ) ) ;
return { } ;
2021-04-19 11:13:36 +03:00
}
2023-01-13 14:29:46 +03:00
ErrorOr < void > Editor : : really_quit_event_loop ( )
2020-08-20 18:34:55 +03:00
{
m_finish = false ;
2021-07-19 21:42:28 +03:00
{
2023-01-13 14:29:46 +03:00
auto stderr_stream = TRY ( Core : : Stream : : File : : standard_error ( ) ) ;
TRY ( reposition_cursor ( * stderr_stream , true ) ) ;
TRY ( stderr_stream - > write_entire_buffer ( " \n " sv . bytes ( ) ) ) ;
2021-07-19 21:42:28 +03:00
}
2020-08-20 18:34:55 +03:00
auto string = line ( ) ;
m_buffer . clear ( ) ;
2021-02-21 03:33:43 +03:00
m_chars_touched_in_the_middle = buffer ( ) . size ( ) ;
2020-08-20 18:34:55 +03:00
m_is_editing = false ;
2021-04-19 12:59:53 +03:00
if ( m_initialized )
restore ( ) ;
2020-08-20 18:34:55 +03:00
m_returned_line = string ;
2021-01-09 03:10:07 +03:00
m_notifier - > set_enabled ( false ) ;
2021-06-07 00:52:40 +03:00
m_notifier = nullptr ;
Core : : EventLoop : : current ( ) . quit ( Exit ) ;
2023-01-13 14:29:46 +03:00
return { } ;
2020-08-20 18:34:55 +03:00
}
2022-12-04 21:02:33 +03:00
auto Editor : : get_line ( DeprecatedString const & prompt ) - > Result < DeprecatedString , Editor : : Error >
2020-03-30 19:59:04 +03:00
{
2021-06-07 00:32:44 +03:00
initialize ( ) ;
m_is_editing = true ;
if ( m_configuration . operation_mode = = Configuration : : NoEscapeSequences | | m_configuration . operation_mode = = Configuration : : NonInteractive ) {
// Do not use escape sequences, instead, use LibC's getline.
size_t size = 0 ;
char * line = nullptr ;
// Show the prompt only on interactive mode (NoEscapeSequences in this case).
if ( m_configuration . operation_mode ! = Configuration : : NonInteractive )
fputs ( prompt . characters ( ) , stderr ) ;
auto line_length = getline ( & line , & size , stdin ) ;
// getline() returns -1 and sets errno=0 on EOF.
if ( line_length = = - 1 ) {
if ( line )
2020-08-05 08:28:47 +03:00
free ( line ) ;
2021-06-07 00:32:44 +03:00
if ( errno = = 0 )
return Error : : Eof ;
2020-08-05 08:28:47 +03:00
return Error : : ReadFailure ;
}
2021-06-07 00:32:44 +03:00
restore ( ) ;
if ( line ) {
2022-12-04 21:02:33 +03:00
DeprecatedString result { line , ( size_t ) line_length , Chomp } ;
2021-06-07 00:32:44 +03:00
free ( line ) ;
return result ;
}
2020-05-26 18:22:01 +03:00
2021-06-07 00:32:44 +03:00
return Error : : ReadFailure ;
}
auto old_cols = m_num_columns ;
auto old_lines = m_num_lines ;
get_terminal_size ( ) ;
2021-04-17 20:30:03 +03:00
2021-06-07 00:32:44 +03:00
if ( m_configuration . enable_bracketed_paste )
fprintf ( stderr , " \x1b [?2004h " ) ;
2021-05-24 15:00:44 +03:00
2021-06-07 00:32:44 +03:00
if ( m_num_columns ! = old_cols | | m_num_lines ! = old_lines )
m_refresh_needed = true ;
2020-03-30 19:59:04 +03:00
2021-06-07 00:32:44 +03:00
set_prompt ( prompt ) ;
reset ( ) ;
strip_styles ( true ) ;
2021-01-04 11:08:56 +03:00
2021-07-19 21:42:28 +03:00
{
2023-01-10 14:21:17 +03:00
auto stderr_stream = Core : : Stream : : File : : standard_error ( ) . release_value_but_fixme_should_propagate_errors ( ) ;
2021-07-19 21:42:28 +03:00
auto prompt_lines = max ( current_prompt_metrics ( ) . line_metrics . size ( ) , 1ul ) - 1 ;
for ( size_t i = 0 ; i < prompt_lines ; + + i )
2023-01-10 14:21:17 +03:00
stderr_stream - > write_entire_buffer ( " \n " sv . bytes ( ) ) . release_value_but_fixme_should_propagate_errors ( ) ;
2021-01-04 11:08:56 +03:00
2023-01-13 14:29:46 +03:00
VT : : move_relative ( - static_cast < int > ( prompt_lines ) , 0 , * stderr_stream ) . release_value_but_fixme_should_propagate_errors ( ) ;
2021-07-19 21:42:28 +03:00
}
2021-01-04 11:08:56 +03:00
2021-06-07 00:32:44 +03:00
set_origin ( ) ;
2020-05-26 13:34:39 +03:00
2021-06-07 00:32:44 +03:00
m_history_cursor = m_history . size ( ) ;
2020-05-26 13:34:39 +03:00
2023-01-13 14:29:46 +03:00
refresh_display ( ) . release_value_but_fixme_should_propagate_errors ( ) ;
2020-05-26 13:34:39 +03:00
2021-06-07 00:32:44 +03:00
Core : : EventLoop loop ;
2020-05-26 13:34:39 +03:00
2021-06-07 00:32:44 +03:00
m_notifier = Core : : Notifier : : construct ( STDIN_FILENO , Core : : Notifier : : Read ) ;
2023-01-13 14:29:46 +03:00
m_notifier - > on_ready_to_read = [ & ] { try_update_once ( ) . release_value_but_fixme_should_propagate_errors ( ) ; } ;
2021-06-07 00:32:44 +03:00
if ( ! m_incomplete_data . is_empty ( ) )
2023-01-13 14:29:46 +03:00
deferred_invoke ( [ & ] { try_update_once ( ) . release_value_but_fixme_should_propagate_errors ( ) ; } ) ;
2020-04-20 16:23:24 +03:00
2021-06-07 00:32:44 +03:00
if ( loop . exec ( ) = = Retry )
return get_line ( prompt ) ;
2020-04-20 16:23:24 +03:00
2022-12-04 21:02:33 +03:00
return m_input_error . has_value ( ) ? Result < DeprecatedString , Editor : : Error > { m_input_error . value ( ) } : Result < DeprecatedString , Editor : : Error > { m_returned_line } ;
2020-05-26 13:34:39 +03:00
}
2020-05-26 13:51:44 +03:00
void Editor : : save_to ( JsonObject & object )
{
Core : : Object : : save_to ( object ) ;
object . set ( " is_searching " , m_is_searching ) ;
object . set ( " is_editing " , m_is_editing ) ;
object . set ( " cursor_offset " , m_cursor ) ;
object . set ( " needs_refresh " , m_refresh_needed ) ;
object . set ( " unprocessed_characters " , m_incomplete_data . size ( ) ) ;
object . set ( " history_size " , m_history . size ( ) ) ;
object . set ( " current_prompt " , m_new_prompt ) ;
object . set ( " was_interrupted " , m_was_interrupted ) ;
JsonObject display_area ;
2020-06-07 23:18:40 +03:00
display_area . set ( " top_left_row " , m_origin_row ) ;
display_area . set ( " top_left_column " , m_origin_column ) ;
2020-05-26 13:51:44 +03:00
display_area . set ( " line_count " , num_lines ( ) ) ;
object . set ( " used_display_area " , move ( display_area ) ) ;
}
2020-04-19 22:04:58 +03:00
2023-01-13 14:29:46 +03:00
ErrorOr < void > Editor : : try_update_once ( )
2020-12-18 14:59:07 +03:00
{
if ( m_was_interrupted ) {
handle_interrupt_event ( ) ;
}
2023-01-13 14:29:46 +03:00
TRY ( handle_read_event ( ) ) ;
2020-12-18 14:59:07 +03:00
if ( m_always_refresh )
m_refresh_needed = true ;
2023-01-13 14:29:46 +03:00
TRY ( refresh_display ( ) ) ;
2020-12-18 14:59:07 +03:00
if ( m_finish )
2023-01-13 14:29:46 +03:00
TRY ( really_quit_event_loop ( ) ) ;
return { } ;
2020-12-18 14:59:07 +03:00
}
2020-06-01 14:56:31 +03:00
void Editor : : handle_interrupt_event ( )
{
m_was_interrupted = false ;
2021-02-07 02:42:17 +03:00
m_previous_interrupt_was_handled_as_interrupt = false ;
2020-06-01 14:56:31 +03:00
2020-10-19 09:09:36 +03:00
m_callback_machine . interrupted ( * this ) ;
if ( ! m_callback_machine . should_process_last_pressed_key ( ) )
return ;
2020-06-01 14:56:31 +03:00
2021-02-07 02:42:17 +03:00
m_previous_interrupt_was_handled_as_interrupt = true ;
2020-08-21 18:07:14 +03:00
fprintf ( stderr , " ^C " ) ;
fflush ( stderr ) ;
2020-06-01 14:56:31 +03:00
if ( on_interrupt_handled )
on_interrupt_handled ( ) ;
2020-08-20 18:34:55 +03:00
m_buffer . clear ( ) ;
2021-02-21 03:33:43 +03:00
m_chars_touched_in_the_middle = buffer ( ) . size ( ) ;
2020-08-20 18:34:55 +03:00
m_cursor = 0 ;
finish ( ) ;
2020-06-01 14:56:31 +03:00
}
2023-01-13 14:29:46 +03:00
ErrorOr < void > Editor : : handle_read_event ( )
2020-05-26 13:34:39 +03:00
{
2022-03-27 21:22:31 +03:00
if ( m_prohibit_input_processing ) {
m_have_unprocessed_read_event = true ;
2023-01-13 14:29:46 +03:00
return { } ;
2022-03-27 21:22:31 +03:00
}
auto prohibit_scope = prohibit_input ( ) ;
2020-05-26 13:34:39 +03:00
char keybuf [ 16 ] ;
ssize_t nread = 0 ;
2020-03-30 19:59:04 +03:00
2020-05-26 13:34:39 +03:00
if ( ! m_incomplete_data . size ( ) )
nread = read ( 0 , keybuf , sizeof ( keybuf ) ) ;
2020-05-13 12:52:47 +03:00
2020-05-26 13:34:39 +03:00
if ( nread < 0 ) {
if ( errno = = EINTR ) {
if ( ! m_was_interrupted ) {
if ( m_was_resized )
2023-01-13 14:29:46 +03:00
return { } ;
2020-05-13 12:52:47 +03:00
2020-05-26 13:34:39 +03:00
finish ( ) ;
2023-01-13 14:29:46 +03:00
return { } ;
2020-03-30 19:59:04 +03:00
}
2020-05-25 15:27:07 +03:00
2020-06-01 14:56:31 +03:00
handle_interrupt_event ( ) ;
2023-01-13 14:29:46 +03:00
return { } ;
2020-03-30 19:59:04 +03:00
}
2020-05-26 13:34:39 +03:00
ScopedValueRollback errno_restorer ( errno ) ;
perror ( " read failed " ) ;
2020-05-18 12:17:34 +03:00
2020-05-26 13:34:39 +03:00
m_input_error = Error : : ReadFailure ;
finish ( ) ;
2023-01-13 14:29:46 +03:00
return { } ;
2020-05-26 13:34:39 +03:00
}
2020-05-18 12:17:34 +03:00
2020-05-26 13:34:39 +03:00
m_incomplete_data . append ( keybuf , nread ) ;
2022-03-27 21:25:03 +03:00
auto available_bytes = m_incomplete_data . size ( ) ;
2020-05-18 12:17:34 +03:00
2022-03-27 21:25:03 +03:00
if ( available_bytes = = 0 ) {
2020-05-26 13:34:39 +03:00
m_input_error = Error : : Empty ;
finish ( ) ;
2023-01-13 14:29:46 +03:00
return { } ;
2020-05-26 13:34:39 +03:00
}
2020-05-18 12:17:34 +03:00
2020-05-26 13:34:39 +03:00
auto reverse_tab = false ;
2020-05-18 12:17:34 +03:00
2020-05-26 13:34:39 +03:00
// Discard starting bytes until they make sense as utf-8.
size_t valid_bytes = 0 ;
2022-03-27 21:25:03 +03:00
while ( available_bytes > 0 ) {
Utf8View { StringView { m_incomplete_data . data ( ) , available_bytes } } . validate ( valid_bytes ) ;
if ( valid_bytes ! = 0 )
2020-05-26 13:34:39 +03:00
break ;
m_incomplete_data . take_first ( ) ;
2022-03-27 21:25:03 +03:00
- - available_bytes ;
2020-05-26 13:34:39 +03:00
}
2020-05-18 13:17:19 +03:00
2020-05-26 13:34:39 +03:00
Utf8View input_view { StringView { m_incomplete_data . data ( ) , valid_bytes } } ;
2020-08-05 23:31:20 +03:00
size_t consumed_code_points = 0 ;
2020-05-18 13:17:19 +03:00
2021-06-06 21:54:07 +03:00
static Vector < u8 , 4 > csi_parameter_bytes ;
static Vector < u8 > csi_intermediate_bytes ;
2020-09-14 03:33:04 +03:00
Vector < unsigned , 4 > csi_parameters ;
u8 csi_final ;
2020-09-14 04:08:19 +03:00
enum CSIMod {
Shift = 1 ,
Alt = 2 ,
Ctrl = 4 ,
} ;
2020-09-14 03:33:04 +03:00
2020-08-05 23:31:20 +03:00
for ( auto code_point : input_view ) {
2020-05-26 13:34:39 +03:00
if ( m_finish )
break ;
2020-03-30 19:59:04 +03:00
2020-08-05 23:31:20 +03:00
+ + consumed_code_points ;
2020-05-26 13:34:39 +03:00
2020-08-05 23:31:20 +03:00
if ( code_point = = 0 )
2020-05-26 13:34:39 +03:00
continue ;
switch ( m_state ) {
2020-08-06 17:52:18 +03:00
case InputState : : GotEscape :
switch ( code_point ) {
case ' [ ' :
2020-09-14 03:33:04 +03:00
m_state = InputState : : CSIExpectParameter ;
2020-05-26 13:34:39 +03:00
continue ;
2020-08-17 19:08:10 +03:00
default : {
2020-10-19 09:09:36 +03:00
m_callback_machine . key_pressed ( * this , { code_point , Key : : Alt } ) ;
2021-01-10 15:53:04 +03:00
m_state = InputState : : Free ;
2023-01-13 14:29:46 +03:00
TRY ( cleanup_suggestions ( ) ) ;
2020-08-06 20:26:43 +03:00
continue ;
}
2020-05-26 13:34:39 +03:00
}
2020-09-14 03:33:04 +03:00
case InputState : : CSIExpectParameter :
if ( code_point > = 0x30 & & code_point < = 0x3f ) { // '0123456789:;<=>?'
csi_parameter_bytes . append ( code_point ) ;
continue ;
}
m_state = InputState : : CSIExpectIntermediate ;
[[fallthrough]] ;
case InputState : : CSIExpectIntermediate :
if ( code_point > = 0x20 & & code_point < = 0x2f ) { // ' !"#$%&\'()*+,-./'
csi_intermediate_bytes . append ( code_point ) ;
continue ;
}
m_state = InputState : : CSIExpectFinal ;
[[fallthrough]] ;
2020-09-14 04:08:19 +03:00
case InputState : : CSIExpectFinal : {
2021-05-24 15:00:44 +03:00
m_state = m_previous_free_state ;
auto is_in_paste = m_state = = InputState : : Paste ;
2022-12-04 21:02:33 +03:00
for ( auto & parameter : DeprecatedString : : copy ( csi_parameter_bytes ) . split ( ' ; ' ) ) {
2020-09-14 03:33:04 +03:00
if ( auto value = parameter . to_uint ( ) ; value . has_value ( ) )
csi_parameters . append ( value . value ( ) ) ;
else
csi_parameters . append ( 0 ) ;
}
2020-09-14 04:08:19 +03:00
unsigned param1 = 0 , param2 = 0 ;
if ( csi_parameters . size ( ) > = 1 )
param1 = csi_parameters [ 0 ] ;
if ( csi_parameters . size ( ) > = 2 )
param2 = csi_parameters [ 1 ] ;
unsigned modifiers = param2 ? param2 - 1 : 0 ;
2020-09-14 03:33:04 +03:00
2021-05-24 15:00:44 +03:00
if ( is_in_paste & & code_point ! = ' ~ ' & & param1 ! = 201 ) {
// The only valid escape to process in paste mode is the stop-paste sequence.
// so treat everything else as part of the pasted data.
insert ( ' \x1b ' ) ;
insert ( ' [ ' ) ;
insert ( StringView { csi_parameter_bytes . data ( ) , csi_parameter_bytes . size ( ) } ) ;
insert ( StringView { csi_intermediate_bytes . data ( ) , csi_intermediate_bytes . size ( ) } ) ;
insert ( code_point ) ;
continue ;
}
if ( ! ( code_point > = 0x40 & & code_point < = 0x7f ) ) {
dbgln ( " LibLine: Invalid CSI: {:02x} ({:c}) " , code_point , code_point ) ;
continue ;
}
csi_final = code_point ;
csi_parameters . clear ( ) ;
csi_parameter_bytes . clear ( ) ;
csi_intermediate_bytes . clear ( ) ;
2020-09-14 03:33:04 +03:00
if ( csi_final = = ' Z ' ) {
2020-09-06 00:57:30 +03:00
// 'reverse tab'
reverse_tab = true ;
break ;
}
2023-01-13 14:29:46 +03:00
TRY ( cleanup_suggestions ( ) ) ;
2020-09-14 04:08:19 +03:00
2020-09-14 03:33:04 +03:00
switch ( csi_final ) {
2020-08-06 17:52:18 +03:00
case ' A ' : // ^[[A: arrow up
2020-08-17 19:08:10 +03:00
search_backwards ( ) ;
2020-05-26 13:34:39 +03:00
continue ;
2020-08-06 17:52:18 +03:00
case ' B ' : // ^[[B: arrow down
2020-08-17 19:08:10 +03:00
search_forwards ( ) ;
2020-05-26 13:34:39 +03:00
continue ;
2020-08-06 17:52:18 +03:00
case ' D ' : // ^[[D: arrow left
2020-09-14 04:15:07 +03:00
if ( modifiers = = CSIMod : : Alt | | modifiers = = CSIMod : : Ctrl )
2020-08-17 19:08:10 +03:00
cursor_left_word ( ) ;
else
cursor_left_character ( ) ;
2020-05-26 13:34:39 +03:00
continue ;
2020-08-06 17:52:18 +03:00
case ' C ' : // ^[[C: arrow right
2020-09-14 04:15:07 +03:00
if ( modifiers = = CSIMod : : Alt | | modifiers = = CSIMod : : Ctrl )
2020-08-17 19:08:10 +03:00
cursor_right_word ( ) ;
else
cursor_right_character ( ) ;
2020-05-26 13:34:39 +03:00
continue ;
2020-08-06 17:52:18 +03:00
case ' H ' : // ^[[H: home
2020-08-17 19:08:10 +03:00
go_home ( ) ;
2020-03-30 19:59:04 +03:00
continue ;
2020-08-06 17:52:18 +03:00
case ' F ' : // ^[[F: end
2020-08-17 19:08:10 +03:00
go_end ( ) ;
2020-05-26 13:34:39 +03:00
continue ;
2022-11-16 13:40:11 +03:00
case 127 :
if ( modifiers = = CSIMod : : Ctrl )
erase_alnum_word_backwards ( ) ;
else
erase_character_backwards ( ) ;
continue ;
2020-09-14 03:33:04 +03:00
case ' ~ ' :
2020-09-14 04:08:19 +03:00
if ( param1 = = 3 ) { // ^[[3~: delete
2020-09-14 04:32:22 +03:00
if ( modifiers = = CSIMod : : Ctrl )
erase_alnum_word_forwards ( ) ;
else
erase_character_forwards ( ) ;
2020-09-14 03:33:04 +03:00
m_search_offset = 0 ;
continue ;
}
2021-05-24 15:00:44 +03:00
if ( m_configuration . enable_bracketed_paste ) {
// ^[[200~: start bracketed paste
// ^[[201~: end bracketed paste
if ( ! is_in_paste & & param1 = = 200 ) {
m_state = InputState : : Paste ;
continue ;
}
if ( is_in_paste & & param1 = = 201 ) {
m_state = InputState : : Free ;
2022-03-06 12:28:45 +03:00
if ( on_paste ) {
on_paste ( Utf32View { m_paste_buffer . data ( ) , m_paste_buffer . size ( ) } , * this ) ;
m_paste_buffer . clear_with_capacity ( ) ;
}
if ( ! m_paste_buffer . is_empty ( ) )
insert ( Utf32View { m_paste_buffer . data ( ) , m_paste_buffer . size ( ) } ) ;
2021-05-24 15:00:44 +03:00
continue ;
}
}
2020-09-14 03:33:04 +03:00
// ^[[5~: page up
// ^[[6~: page down
2021-01-10 12:02:20 +03:00
dbgln ( " LibLine: Unhandled '~': {} " , param1 ) ;
2020-05-26 13:34:39 +03:00
continue ;
default :
2021-01-10 12:02:20 +03:00
dbgln ( " LibLine: Unhandled final: {:02x} ({:c}) " , code_point , code_point ) ;
2020-05-26 13:34:39 +03:00
continue ;
2020-03-30 19:59:04 +03:00
}
2021-10-08 15:34:12 +03:00
VERIFY_NOT_REACHED ( ) ;
2020-09-14 04:08:19 +03:00
}
2021-01-10 15:53:04 +03:00
case InputState : : Verbatim :
m_state = InputState : : Free ;
// Verbatim mode will bypass all mechanisms and just insert the code point.
insert ( code_point ) ;
continue ;
2021-05-24 15:00:44 +03:00
case InputState : : Paste :
if ( code_point = = 27 ) {
m_previous_free_state = InputState : : Paste ;
m_state = InputState : : GotEscape ;
continue ;
}
2022-03-06 12:28:45 +03:00
if ( on_paste )
m_paste_buffer . append ( code_point ) ;
else
insert ( code_point ) ;
2021-05-24 15:00:44 +03:00
continue ;
2020-05-26 13:34:39 +03:00
case InputState : : Free :
2021-05-24 15:00:44 +03:00
m_previous_free_state = InputState : : Free ;
2020-08-05 23:31:20 +03:00
if ( code_point = = 27 ) {
2021-01-10 15:53:04 +03:00
m_callback_machine . key_pressed ( * this , code_point ) ;
// Note that this should also deal with explicitly registered keys
// that would otherwise be interpreted as escapes.
if ( m_callback_machine . should_process_last_pressed_key ( ) )
m_state = InputState : : GotEscape ;
continue ;
}
if ( code_point = = 22 ) { // ^v
m_callback_machine . key_pressed ( * this , code_point ) ;
if ( m_callback_machine . should_process_last_pressed_key ( ) )
m_state = InputState : : Verbatim ;
2020-05-26 13:34:39 +03:00
continue ;
}
break ;
}
2020-03-30 19:59:04 +03:00
2020-08-31 20:34:32 +03:00
// There are no sequences past this point, so short of 'tab', we will want to cleanup the suggestions.
2023-01-13 14:29:46 +03:00
ArmedScopeGuard suggestion_cleanup { [ this ] { cleanup_suggestions ( ) . release_value_but_fixme_should_propagate_errors ( ) ; } } ;
2020-08-31 20:34:32 +03:00
2020-08-17 19:08:10 +03:00
// Normally ^D. `stty eof \^n` can change it to ^N (or something else), but Serenity doesn't have `stty` yet.
2021-09-07 13:56:50 +03:00
// Process this here since the keybinds might override its behavior.
// This only applies when the buffer is empty. at any other time, the behavior should be configurable.
2020-09-07 06:07:54 +03:00
if ( code_point = = m_termios . c_cc [ VEOF ] & & m_buffer . size ( ) = = 0 ) {
2020-08-17 19:08:10 +03:00
finish_edit ( ) ;
continue ;
}
2020-10-19 09:09:36 +03:00
m_callback_machine . key_pressed ( * this , code_point ) ;
if ( ! m_callback_machine . should_process_last_pressed_key ( ) )
continue ;
2020-05-26 13:34:39 +03:00
m_search_offset = 0 ; // reset search offset on any key
2020-04-20 15:50:31 +03:00
2020-08-05 23:31:20 +03:00
if ( code_point = = ' \t ' | | reverse_tab ) {
2020-08-31 20:34:32 +03:00
suggestion_cleanup . disarm ( ) ;
2020-05-26 13:34:39 +03:00
if ( ! on_tab_complete )
continue ;
2020-03-30 19:59:04 +03:00
2020-05-26 13:34:39 +03:00
// Reverse tab can count as regular tab here.
m_times_tab_pressed + + ;
2020-04-11 17:32:15 +03:00
2020-05-26 13:34:39 +03:00
int token_start = m_cursor ;
// Ask for completions only on the first tab
// and scan for the largest common prefix to display,
// further tabs simply show the cached completions.
if ( m_times_tab_pressed = = 1 ) {
m_suggestion_manager . set_suggestions ( on_tab_complete ( * this ) ) ;
2022-03-06 17:30:28 +03:00
m_suggestion_manager . set_start_index ( 0 ) ;
2020-05-26 13:34:39 +03:00
m_prompt_lines_at_suggestion_initiation = num_lines ( ) ;
if ( m_suggestion_manager . count ( ) = = 0 ) {
// There are no suggestions, beep.
2020-08-09 18:01:15 +03:00
fputc ( ' \a ' , stderr ) ;
fflush ( stderr ) ;
2020-04-11 21:35:39 +03:00
}
2020-05-26 13:34:39 +03:00
}
2020-04-11 21:35:39 +03:00
2020-05-26 13:34:39 +03:00
// Adjust already incremented / decremented index when switching tab direction.
if ( reverse_tab & & m_tab_direction ! = TabDirection : : Backward ) {
m_suggestion_manager . previous ( ) ;
m_suggestion_manager . previous ( ) ;
m_tab_direction = TabDirection : : Backward ;
}
if ( ! reverse_tab & & m_tab_direction ! = TabDirection : : Forward ) {
m_suggestion_manager . next ( ) ;
m_suggestion_manager . next ( ) ;
m_tab_direction = TabDirection : : Forward ;
}
reverse_tab = false ;
2020-05-21 16:36:43 +03:00
2021-01-04 11:08:56 +03:00
SuggestionManager : : CompletionMode completion_mode ;
switch ( m_times_tab_pressed ) {
case 1 :
completion_mode = SuggestionManager : : CompletePrefix ;
break ;
case 2 :
completion_mode = SuggestionManager : : ShowSuggestions ;
break ;
default :
completion_mode = SuggestionManager : : CycleSuggestions ;
break ;
}
2020-05-21 16:36:43 +03:00
2022-04-15 00:18:56 +03:00
insert ( Utf32View { m_remembered_suggestion_static_data . data ( ) , m_remembered_suggestion_static_data . size ( ) } ) ;
m_remembered_suggestion_static_data . clear_with_capacity ( ) ;
2020-05-26 13:34:39 +03:00
auto completion_result = m_suggestion_manager . attempt_completion ( completion_mode , token_start ) ;
2020-03-30 19:59:04 +03:00
2022-04-15 00:18:56 +03:00
auto new_cursor = m_cursor ;
new_cursor + = completion_result . new_cursor_offset ;
2020-05-26 13:34:39 +03:00
for ( size_t i = completion_result . offset_region_to_remove . start ; i < completion_result . offset_region_to_remove . end ; + + i )
remove_at_index ( new_cursor ) ;
2020-03-30 19:59:04 +03:00
2022-04-15 00:18:56 +03:00
new_cursor - = completion_result . static_offset_from_cursor ;
for ( size_t i = 0 ; i < completion_result . static_offset_from_cursor ; + + i ) {
m_remembered_suggestion_static_data . append ( m_buffer [ new_cursor ] ) ;
remove_at_index ( new_cursor ) ;
}
2020-05-26 13:34:39 +03:00
m_cursor = new_cursor ;
m_inline_search_cursor = new_cursor ;
m_refresh_needed = true ;
2021-02-21 03:33:43 +03:00
m_chars_touched_in_the_middle + + ;
2020-03-30 19:59:04 +03:00
2020-05-26 13:34:39 +03:00
for ( auto & view : completion_result . insert )
insert ( view ) ;
2020-04-11 17:32:15 +03:00
2023-01-13 14:29:46 +03:00
auto stderr_stream = TRY ( Core : : Stream : : File : : standard_error ( ) ) ;
TRY ( reposition_cursor ( * stderr_stream ) ) ;
2021-02-21 03:33:43 +03:00
2020-05-26 13:34:39 +03:00
if ( completion_result . style_to_apply . has_value ( ) ) {
// Apply the style of the last suggestion.
readjust_anchored_styles ( m_suggestion_manager . current_suggestion ( ) . start_index , ModificationKind : : ForcedOverlapRemoval ) ;
stylize ( { m_suggestion_manager . current_suggestion ( ) . start_index , m_cursor , Span : : Mode : : CodepointOriented } , completion_result . style_to_apply . value ( ) ) ;
}
2020-04-11 17:32:15 +03:00
2020-05-26 13:34:39 +03:00
switch ( completion_result . new_completion_mode ) {
case SuggestionManager : : DontComplete :
m_times_tab_pressed = 0 ;
2022-04-15 00:18:56 +03:00
m_remembered_suggestion_static_data . clear_with_capacity ( ) ;
2020-05-26 13:34:39 +03:00
break ;
case SuggestionManager : : CompletePrefix :
break ;
default :
+ + m_times_tab_pressed ;
break ;
}
2020-04-11 17:32:15 +03:00
2021-12-14 13:33:57 +03:00
if ( m_times_tab_pressed > 1 & & m_suggestion_manager . count ( ) > 0 ) {
2023-01-13 14:29:46 +03:00
if ( TRY ( m_suggestion_display - > cleanup ( ) ) )
TRY ( reposition_cursor ( * stderr_stream ) ) ;
2020-05-11 10:25:42 +03:00
2021-12-14 13:33:57 +03:00
m_suggestion_display - > set_initial_prompt_lines ( m_prompt_lines_at_suggestion_initiation ) ;
2020-03-30 19:59:04 +03:00
2023-01-13 14:29:46 +03:00
TRY ( m_suggestion_display - > display ( m_suggestion_manager ) ) ;
2020-05-11 10:25:42 +03:00
2021-12-14 13:33:57 +03:00
m_origin_row = m_suggestion_display - > origin_row ( ) ;
2020-05-26 13:34:39 +03:00
}
2020-05-22 02:22:34 +03:00
2020-05-26 13:34:39 +03:00
if ( m_times_tab_pressed > 2 ) {
if ( m_tab_direction = = TabDirection : : Forward )
m_suggestion_manager . next ( ) ;
else
m_suggestion_manager . previous ( ) ;
2020-03-30 19:59:04 +03:00
}
2022-04-15 06:40:27 +03:00
if ( m_suggestion_manager . count ( ) < 2 & & ! completion_result . avoid_committing_to_single_suggestion ) {
2020-05-26 13:34:39 +03:00
// We have none, or just one suggestion,
// we should just commit that and continue
// after it, as if it were auto-completed.
2023-01-13 14:29:46 +03:00
TRY ( reposition_cursor ( * stderr_stream , true ) ) ;
TRY ( cleanup_suggestions ( ) ) ;
2022-04-15 00:18:56 +03:00
m_remembered_suggestion_static_data . clear_with_capacity ( ) ;
2020-04-11 11:59:55 +03:00
}
2020-05-26 13:34:39 +03:00
continue ;
}
2020-03-30 19:59:04 +03:00
2021-03-11 11:52:55 +03:00
// If we got here, manually cleanup the suggestions and then insert the new code point.
2022-04-15 00:18:56 +03:00
m_remembered_suggestion_static_data . clear_with_capacity ( ) ;
2021-03-11 11:52:55 +03:00
suggestion_cleanup . disarm ( ) ;
2023-01-13 14:29:46 +03:00
TRY ( cleanup_suggestions ( ) ) ;
2020-08-05 23:31:20 +03:00
insert ( code_point ) ;
2020-05-26 13:34:39 +03:00
}
2020-08-05 23:31:20 +03:00
if ( consumed_code_points = = m_incomplete_data . size ( ) ) {
2020-05-26 13:34:39 +03:00
m_incomplete_data . clear ( ) ;
} else {
2020-08-05 23:31:20 +03:00
for ( size_t i = 0 ; i < consumed_code_points ; + + i )
2020-05-26 13:34:39 +03:00
m_incomplete_data . take_first ( ) ;
2020-03-30 19:59:04 +03:00
}
2020-12-18 14:59:07 +03:00
if ( ! m_incomplete_data . is_empty ( ) & & ! m_finish )
2023-01-13 14:29:46 +03:00
deferred_invoke ( [ & ] { try_update_once ( ) . release_value_but_fixme_should_propagate_errors ( ) ; } ) ;
return { } ;
2020-03-30 19:59:04 +03:00
}
2023-01-13 14:29:46 +03:00
ErrorOr < void > Editor : : cleanup_suggestions ( )
2020-08-31 20:34:32 +03:00
{
2022-04-15 00:18:56 +03:00
if ( m_times_tab_pressed ! = 0 ) {
2020-08-31 20:34:32 +03:00
// Apply the style of the last suggestion.
readjust_anchored_styles ( m_suggestion_manager . current_suggestion ( ) . start_index , ModificationKind : : ForcedOverlapRemoval ) ;
stylize ( { m_suggestion_manager . current_suggestion ( ) . start_index , m_cursor , Span : : Mode : : CodepointOriented } , m_suggestion_manager . current_suggestion ( ) . style ) ;
// We probably have some suggestions drawn,
// let's clean them up.
2023-01-13 14:29:46 +03:00
if ( TRY ( m_suggestion_display - > cleanup ( ) ) ) {
auto stderr_stream = TRY ( Core : : Stream : : File : : standard_error ( ) ) ;
TRY ( reposition_cursor ( * stderr_stream ) ) ;
2020-08-31 20:34:32 +03:00
m_refresh_needed = true ;
}
m_suggestion_manager . reset ( ) ;
m_suggestion_display - > finish ( ) ;
}
m_times_tab_pressed = 0 ; // Safe to say if we get here, the user didn't press TAB
2023-01-13 14:29:46 +03:00
return { } ;
2020-08-31 20:34:32 +03:00
}
2021-11-11 02:55:02 +03:00
bool Editor : : search ( StringView phrase , bool allow_empty , bool from_beginning )
2020-04-20 15:50:31 +03:00
{
int last_matching_offset = - 1 ;
2020-08-23 16:14:07 +03:00
bool found = false ;
2020-04-20 15:50:31 +03:00
2020-05-23 01:49:48 +03:00
// Do not search for empty strings.
2020-04-20 15:50:31 +03:00
if ( allow_empty | | phrase . length ( ) > 0 ) {
size_t search_offset = m_search_offset ;
for ( size_t i = m_history_cursor ; i > 0 ; - - i ) {
2020-09-06 23:14:55 +03:00
auto & entry = m_history [ i - 1 ] ;
2021-01-11 18:28:42 +03:00
auto contains = from_beginning ? entry . entry . starts_with ( phrase ) : entry . entry . contains ( phrase ) ;
2020-04-20 15:50:31 +03:00
if ( contains ) {
last_matching_offset = i - 1 ;
2020-08-23 16:14:07 +03:00
if ( search_offset = = 0 ) {
found = true ;
2020-04-20 15:50:31 +03:00
break ;
2020-08-23 16:14:07 +03:00
}
2020-04-20 15:50:31 +03:00
- - search_offset ;
}
}
2020-08-23 16:14:07 +03:00
if ( ! found ) {
2020-08-09 18:01:15 +03:00
fputc ( ' \a ' , stderr ) ;
fflush ( stderr ) ;
2020-04-20 15:50:31 +03:00
}
}
2020-08-23 16:14:07 +03:00
if ( found ) {
2021-02-24 09:47:42 +03:00
// We plan to clear the buffer, so mark the entire thing touched.
m_chars_touched_in_the_middle = m_buffer . size ( ) ;
2020-08-23 16:14:07 +03:00
m_buffer . clear ( ) ;
m_cursor = 0 ;
2021-01-11 18:28:42 +03:00
insert ( m_history [ last_matching_offset ] . entry ) ;
2020-08-23 16:14:07 +03:00
// Always needed, as we have cleared the buffer above.
m_refresh_needed = true ;
2020-04-20 15:50:31 +03:00
}
2020-08-23 16:14:07 +03:00
return found ;
2020-04-20 15:50:31 +03:00
}
2020-04-11 12:36:46 +03:00
void Editor : : recalculate_origin ( )
{
2020-05-23 01:49:48 +03:00
// Changing the columns can affect our origin if
2020-04-11 12:36:46 +03:00
// the new size is smaller than our prompt, which would
// cause said prompt to take up more space, so we should
2020-05-23 01:49:48 +03:00
// compensate for that.
2020-06-26 15:13:55 +03:00
if ( m_cached_prompt_metrics . max_line_length > = m_num_columns ) {
auto added_lines = ( m_cached_prompt_metrics . max_line_length + 1 ) / m_num_columns - 1 ;
2020-06-07 23:18:40 +03:00
m_origin_row + = added_lines ;
2020-04-11 12:36:46 +03:00
}
2020-05-23 01:49:48 +03:00
// We also need to recalculate our cursor position,
2020-04-11 12:36:46 +03:00
// but that will be calculated and applied at the next
2020-05-23 01:49:48 +03:00
// refresh cycle.
2020-04-11 12:36:46 +03:00
}
2023-01-13 14:29:46 +03:00
ErrorOr < void > Editor : : cleanup ( )
2020-04-05 05:11:33 +03:00
{
2022-05-02 14:40:42 +03:00
auto current_buffer_metrics = actual_rendered_string_metrics ( buffer_view ( ) , m_current_masks ) ;
2020-06-29 18:38:02 +03:00
auto new_lines = current_prompt_metrics ( ) . lines_with_addition ( current_buffer_metrics , m_num_columns ) ;
2022-06-21 17:10:36 +03:00
if ( new_lines < m_shown_lines )
m_extra_forward_lines = max ( m_shown_lines - new_lines , m_extra_forward_lines ) ;
2020-06-29 18:38:02 +03:00
2023-01-13 14:29:46 +03:00
auto stderr_stream = TRY ( Core : : Stream : : File : : standard_error ( ) ) ;
TRY ( reposition_cursor ( * stderr_stream , true ) ) ;
2021-01-04 11:08:56 +03:00
auto current_line = num_lines ( ) - 1 ;
2023-01-13 14:29:46 +03:00
TRY ( VT : : clear_lines ( current_line , m_extra_forward_lines , * stderr_stream ) ) ;
2020-06-26 15:13:55 +03:00
m_extra_forward_lines = 0 ;
2023-01-13 14:29:46 +03:00
TRY ( reposition_cursor ( * stderr_stream ) ) ;
return { } ;
2020-04-19 22:04:58 +03:00
} ;
2020-04-10 08:23:22 +03:00
2023-01-13 14:29:46 +03:00
ErrorOr < void > Editor : : refresh_display ( )
2020-04-19 22:04:58 +03:00
{
2023-01-10 14:21:17 +03:00
Core : : Stream : : AllocatingMemoryStream output_stream ;
2021-07-19 21:42:28 +03:00
ScopeGuard flush_stream {
[ & ] {
2022-06-21 17:10:36 +03:00
m_shown_lines = current_prompt_metrics ( ) . lines_with_addition ( m_cached_buffer_metrics , m_num_columns ) ;
2023-01-10 14:21:17 +03:00
if ( output_stream . used_buffer_size ( ) = = 0 )
2021-07-19 21:42:28 +03:00
return ;
2023-01-10 14:21:17 +03:00
auto buffer = ByteBuffer : : create_uninitialized ( output_stream . used_buffer_size ( ) ) . release_value_but_fixme_should_propagate_errors ( ) ;
output_stream . read_entire_buffer ( buffer ) . release_value_but_fixme_should_propagate_errors ( ) ;
2021-07-19 21:42:28 +03:00
fwrite ( buffer . data ( ) , sizeof ( char ) , buffer . size ( ) , stderr ) ;
}
} ;
2020-04-10 08:23:22 +03:00
auto has_cleaned_up = false ;
2020-05-23 01:49:48 +03:00
// Someone changed the window size, figure it out
// and react to it, we might need to redraw.
2020-04-10 08:23:22 +03:00
if ( m_was_resized ) {
2020-07-06 18:28:11 +03:00
if ( m_previous_num_columns ! = m_num_columns ) {
2020-05-23 01:49:48 +03:00
// We need to cleanup and redo everything.
2020-04-10 08:23:22 +03:00
m_cached_prompt_valid = false ;
m_refresh_needed = true ;
2020-07-06 18:28:11 +03:00
swap ( m_previous_num_columns , m_num_columns ) ;
2020-04-11 12:36:46 +03:00
recalculate_origin ( ) ;
2023-01-13 14:29:46 +03:00
TRY ( cleanup ( ) ) ;
2020-07-06 18:28:11 +03:00
swap ( m_previous_num_columns , m_num_columns ) ;
2020-04-10 08:23:22 +03:00
has_cleaned_up = true ;
}
2020-06-07 23:01:33 +03:00
m_was_resized = false ;
2020-04-10 08:23:22 +03:00
}
2020-06-07 23:01:33 +03:00
// We might be at the last line, and have more than one line;
// Refreshing the display will cause the terminal to scroll,
2021-01-04 11:08:56 +03:00
// so note that fact and bring origin up, making sure to
// reserve the space for however many lines we move it up.
2020-06-07 23:01:33 +03:00
auto current_num_lines = num_lines ( ) ;
2021-01-04 11:08:56 +03:00
if ( m_origin_row + current_num_lines > m_num_lines ) {
if ( current_num_lines > m_num_lines ) {
for ( size_t i = 0 ; i < m_num_lines ; + + i )
2023-01-13 14:29:46 +03:00
TRY ( output_stream . write_entire_buffer ( " \n " sv . bytes ( ) ) ) ;
2020-06-07 23:01:33 +03:00
m_origin_row = 0 ;
2021-01-04 11:08:56 +03:00
} else {
auto old_origin_row = m_origin_row ;
2020-06-07 23:01:33 +03:00
m_origin_row = m_num_lines - current_num_lines + 1 ;
2021-01-04 11:08:56 +03:00
for ( size_t i = 0 ; i < old_origin_row - m_origin_row ; + + i )
2023-01-13 14:29:46 +03:00
TRY ( output_stream . write_entire_buffer ( " \n " sv . bytes ( ) ) ) ;
2021-01-04 11:08:56 +03:00
}
2020-06-07 23:01:33 +03:00
}
2020-12-18 14:46:53 +03:00
// Do not call hook on pure cursor movement.
if ( m_cached_prompt_valid & & ! m_refresh_needed & & m_pending_chars . size ( ) = = 0 ) {
// Probably just moving around.
2023-01-13 14:29:46 +03:00
TRY ( reposition_cursor ( output_stream ) ) ;
2022-05-02 14:40:42 +03:00
m_cached_buffer_metrics = actual_rendered_string_metrics ( buffer_view ( ) , m_current_masks ) ;
2021-02-20 21:03:13 +03:00
m_drawn_end_of_line_offset = m_buffer . size ( ) ;
2023-01-13 14:29:46 +03:00
return { } ;
2020-12-18 14:46:53 +03:00
}
2020-04-09 06:14:04 +03:00
2020-04-05 05:11:33 +03:00
if ( on_display_refresh )
on_display_refresh ( * this ) ;
2020-04-09 06:14:04 +03:00
if ( m_cached_prompt_valid ) {
if ( ! m_refresh_needed & & m_cursor = = m_buffer . size ( ) ) {
2020-05-23 01:49:48 +03:00
// Just write the characters out and continue,
// no need to refresh the entire line.
2023-01-13 14:29:46 +03:00
TRY ( output_stream . write_entire_buffer ( m_pending_chars ) ) ;
2020-04-09 06:14:04 +03:00
m_pending_chars . clear ( ) ;
m_drawn_cursor = m_cursor ;
2021-02-20 21:03:13 +03:00
m_drawn_end_of_line_offset = m_buffer . size ( ) ;
2022-05-02 14:40:42 +03:00
m_cached_buffer_metrics = actual_rendered_string_metrics ( buffer_view ( ) , m_current_masks ) ;
2021-02-20 21:03:13 +03:00
m_drawn_spans = m_current_spans ;
2023-01-13 14:29:46 +03:00
return { } ;
2020-04-09 06:14:04 +03:00
}
2020-04-05 05:11:33 +03:00
}
2023-01-13 14:29:46 +03:00
auto apply_styles = [ & , empty_styles = HashMap < u32 , Style > { } ] ( size_t i ) - > ErrorOr < void > {
2021-02-20 21:03:13 +03:00
auto ends = m_current_spans . m_spans_ending . get ( i ) . value_or ( empty_styles ) ;
auto starts = m_current_spans . m_spans_starting . get ( i ) . value_or ( empty_styles ) ;
2020-04-09 06:14:04 +03:00
2021-02-20 21:03:13 +03:00
auto anchored_ends = m_current_spans . m_anchored_spans_ending . get ( i ) . value_or ( empty_styles ) ;
auto anchored_starts = m_current_spans . m_anchored_spans_starting . get ( i ) . value_or ( empty_styles ) ;
2021-01-10 15:53:04 +03:00
2020-05-18 00:55:58 +03:00
if ( ends . size ( ) | | anchored_ends . size ( ) ) {
Style style ;
for ( auto & applicable_style : ends )
style . unify_with ( applicable_style . value ) ;
for ( auto & applicable_style : anchored_ends )
style . unify_with ( applicable_style . value ) ;
2020-05-23 01:49:48 +03:00
// Disable any style that should be turned off.
2023-01-13 14:29:46 +03:00
TRY ( VT : : apply_style ( style , output_stream , false ) ) ;
2020-05-18 00:55:58 +03:00
2020-05-23 01:49:48 +03:00
// Reapply styles for overlapping spans that include this one.
2020-05-18 00:55:58 +03:00
style = find_applicable_style ( i ) ;
2023-01-13 14:29:46 +03:00
TRY ( VT : : apply_style ( style , output_stream , true ) ) ;
2020-04-05 05:11:33 +03:00
}
2020-05-18 00:55:58 +03:00
if ( starts . size ( ) | | anchored_starts . size ( ) ) {
Style style ;
for ( auto & applicable_style : starts )
style . unify_with ( applicable_style . value ) ;
for ( auto & applicable_style : anchored_starts )
style . unify_with ( applicable_style . value ) ;
2020-05-23 01:49:48 +03:00
// Set new styles.
2023-01-13 14:29:46 +03:00
TRY ( VT : : apply_style ( style , output_stream , true ) ) ;
2020-04-05 05:11:33 +03:00
}
2023-01-13 14:29:46 +03:00
return { } ;
2021-02-20 21:03:13 +03:00
} ;
2021-01-10 15:53:04 +03:00
2021-07-19 21:42:28 +03:00
auto print_character_at = [ & ] ( size_t i ) {
2022-05-02 14:40:42 +03:00
Variant < u32 , Utf8View > c { Utf8View { } } ;
if ( auto it = m_current_masks . find_largest_not_above_iterator ( i ) ; ! it . is_end ( ) & & it - > has_value ( ) ) {
auto offset = i - it . key ( ) ;
if ( it - > value ( ) . mode = = Style : : Mask : : Mode : : ReplaceEntireSelection ) {
auto & mask = it - > value ( ) . replacement_view ;
auto replacement = mask . begin ( ) . peek ( offset ) ;
if ( ! replacement . has_value ( ) )
return ;
c = replacement . value ( ) ;
+ + it ;
u32 next_offset = it . is_end ( ) ? m_drawn_end_of_line_offset : it . key ( ) ;
if ( i + 1 = = next_offset )
c = mask . unicode_substring_view ( offset , mask . length ( ) - offset ) ;
} else {
c = it - > value ( ) . replacement_view ;
}
} else {
c = m_buffer [ i ] ;
}
2023-01-13 14:29:46 +03:00
auto print_single_character = [ & ] ( auto c ) - > ErrorOr < void > {
2022-05-02 14:40:42 +03:00
StringBuilder builder ;
bool should_print_masked = is_ascii_control ( c ) & & c ! = ' \n ' ;
bool should_print_caret = c < 64 & & should_print_masked ;
if ( should_print_caret )
builder . appendff ( " ^{:c} " , c + 64 ) ;
else if ( should_print_masked )
builder . appendff ( " \\ x{:0>2x} " , c ) ;
else
builder . append ( Utf32View { & c , 1 } ) ;
2021-01-10 15:53:04 +03:00
2022-05-02 14:40:42 +03:00
if ( should_print_masked )
2023-01-13 14:29:46 +03:00
TRY ( output_stream . write_entire_buffer ( " \033 [7m " sv . bytes ( ) ) ) ;
2021-01-10 15:53:04 +03:00
2023-01-13 14:29:46 +03:00
TRY ( output_stream . write_entire_buffer ( builder . string_view ( ) . bytes ( ) ) ) ;
2021-01-10 15:53:04 +03:00
2022-05-02 14:40:42 +03:00
if ( should_print_masked )
2023-01-13 14:29:46 +03:00
TRY ( output_stream . write_entire_buffer ( " \033 [27m " sv . bytes ( ) ) ) ;
return { } ;
2022-05-02 14:40:42 +03:00
} ;
c . visit (
2023-01-13 14:29:46 +03:00
[ & ] ( u32 c ) { print_single_character ( c ) . release_value_but_fixme_should_propagate_errors ( ) ; } ,
[ & ] ( auto & view ) { for ( auto c : view ) print_single_character ( c ) . release_value_but_fixme_should_propagate_errors ( ) ; } ) ;
2021-02-20 21:03:13 +03:00
} ;
// If there have been no changes to previous sections of the line (style or text)
// just append the new text with the appropriate styles.
2021-02-21 03:33:43 +03:00
if ( ! m_always_refresh & & m_cached_prompt_valid & & m_chars_touched_in_the_middle = = 0 & & m_drawn_spans . contains_up_to_offset ( m_current_spans , m_drawn_cursor ) ) {
2021-02-20 21:03:13 +03:00
auto initial_style = find_applicable_style ( m_drawn_end_of_line_offset ) ;
2023-01-13 14:29:46 +03:00
TRY ( VT : : apply_style ( initial_style , output_stream ) ) ;
2021-02-20 21:03:13 +03:00
for ( size_t i = m_drawn_end_of_line_offset ; i < m_buffer . size ( ) ; + + i ) {
2023-01-13 14:29:46 +03:00
TRY ( apply_styles ( i ) ) ;
2021-02-20 21:03:13 +03:00
print_character_at ( i ) ;
}
2023-01-13 14:29:46 +03:00
TRY ( VT : : apply_style ( Style : : reset_style ( ) , output_stream ) ) ;
2021-02-20 21:03:13 +03:00
m_pending_chars . clear ( ) ;
m_refresh_needed = false ;
2022-05-02 14:40:42 +03:00
m_cached_buffer_metrics = actual_rendered_string_metrics ( buffer_view ( ) , m_current_masks ) ;
2021-02-20 21:03:13 +03:00
m_chars_touched_in_the_middle = 0 ;
2021-02-24 09:08:46 +03:00
m_drawn_cursor = m_cursor ;
2021-02-20 21:03:13 +03:00
m_drawn_end_of_line_offset = m_buffer . size ( ) ;
// No need to reposition the cursor, the cursor is already where it needs to be.
2023-01-13 14:29:46 +03:00
return { } ;
2021-02-20 21:03:13 +03:00
}
if constexpr ( LINE_EDITOR_DEBUG ) {
if ( m_cached_prompt_valid & & m_chars_touched_in_the_middle = = 0 ) {
auto x = m_drawn_spans . contains_up_to_offset ( m_current_spans , m_drawn_cursor ) ;
dbgln ( " Contains: {} At offset: {} " , x , m_drawn_cursor ) ;
dbgln ( " Drawn Spans: " ) ;
for ( auto & sentry : m_drawn_spans . m_spans_starting ) {
for ( auto & entry : sentry . value ) {
2022-12-06 04:12:49 +03:00
dbgln ( " {}-{}: {} " , sentry . key , entry . key , entry . value . to_deprecated_string ( ) ) ;
2021-02-20 21:03:13 +03:00
}
}
dbgln ( " ========================================================================== " ) ;
dbgln ( " Current Spans: " ) ;
for ( auto & sentry : m_current_spans . m_spans_starting ) {
for ( auto & entry : sentry . value ) {
2022-12-06 04:12:49 +03:00
dbgln ( " {}-{}: {} " , sentry . key , entry . key , entry . value . to_deprecated_string ( ) ) ;
2021-02-20 21:03:13 +03:00
}
}
}
}
// Ouch, reflow entire line.
if ( ! has_cleaned_up ) {
2023-01-13 14:29:46 +03:00
TRY ( cleanup ( ) ) ;
2021-02-20 21:03:13 +03:00
}
2023-01-13 14:29:46 +03:00
TRY ( VT : : move_absolute ( m_origin_row , m_origin_column , output_stream ) ) ;
2021-02-20 21:03:13 +03:00
2023-01-13 14:29:46 +03:00
TRY ( output_stream . write_entire_buffer ( m_new_prompt . bytes ( ) ) ) ;
2021-02-20 21:03:13 +03:00
2023-01-13 14:29:46 +03:00
TRY ( VT : : clear_to_end_of_line ( output_stream ) ) ;
2021-02-20 21:03:13 +03:00
StringBuilder builder ;
for ( size_t i = 0 ; i < m_buffer . size ( ) ; + + i ) {
2023-01-13 14:29:46 +03:00
TRY ( apply_styles ( i ) ) ;
2021-02-20 21:03:13 +03:00
print_character_at ( i ) ;
2020-04-05 05:11:33 +03:00
}
2020-05-18 00:55:58 +03:00
2023-01-13 14:29:46 +03:00
TRY ( VT : : apply_style ( Style : : reset_style ( ) , output_stream ) ) ; // don't bleed to EOL
2020-05-18 00:55:58 +03:00
2020-04-05 05:11:33 +03:00
m_pending_chars . clear ( ) ;
m_refresh_needed = false ;
2022-05-02 14:40:42 +03:00
m_cached_buffer_metrics = actual_rendered_string_metrics ( buffer_view ( ) , m_current_masks ) ;
2021-02-20 21:03:13 +03:00
m_chars_touched_in_the_middle = 0 ;
m_drawn_spans = m_current_spans ;
m_drawn_end_of_line_offset = m_buffer . size ( ) ;
m_cached_prompt_valid = true ;
2020-04-09 06:14:04 +03:00
2023-01-13 14:29:46 +03:00
TRY ( reposition_cursor ( output_stream ) ) ;
return { } ;
2020-04-09 06:14:04 +03:00
}
2020-05-18 00:55:58 +03:00
void Editor : : strip_styles ( bool strip_anchored )
{
2021-02-20 21:03:13 +03:00
m_current_spans . m_spans_starting . clear ( ) ;
m_current_spans . m_spans_ending . clear ( ) ;
2022-05-02 14:40:42 +03:00
m_current_masks . clear ( ) ;
m_cached_buffer_metrics = actual_rendered_string_metrics ( buffer_view ( ) , { } ) ;
2020-05-18 00:55:58 +03:00
if ( strip_anchored ) {
2021-02-20 21:03:13 +03:00
m_current_spans . m_anchored_spans_starting . clear ( ) ;
m_current_spans . m_anchored_spans_ending . clear ( ) ;
2020-05-18 00:55:58 +03:00
}
m_refresh_needed = true ;
}
2023-01-13 14:29:46 +03:00
ErrorOr < void > Editor : : reposition_cursor ( Core : : Stream : : Stream & stream , bool to_end )
2020-04-09 06:14:04 +03:00
{
2020-06-29 18:38:02 +03:00
auto cursor = m_cursor ;
auto saved_cursor = m_cursor ;
if ( to_end )
cursor = m_buffer . size ( ) ;
m_cursor = cursor ;
m_drawn_cursor = cursor ;
2020-04-09 06:14:04 +03:00
auto line = cursor_line ( ) - 1 ;
auto column = offset_in_line ( ) ;
2021-05-16 08:15:22 +03:00
ensure_free_lines_from_origin ( line ) ;
2021-02-23 22:42:32 +03:00
VERIFY ( column + m_origin_column < = m_num_columns ) ;
2023-01-13 14:29:46 +03:00
TRY ( VT : : move_absolute ( line + m_origin_row , column + m_origin_column , stream ) ) ;
2020-06-29 18:38:02 +03:00
m_cursor = saved_cursor ;
2023-01-13 14:29:46 +03:00
return { } ;
2020-04-09 06:14:04 +03:00
}
2023-01-13 14:29:46 +03:00
ErrorOr < void > VT : : move_absolute ( u32 row , u32 col , Core : : Stream : : Stream & stream )
2020-04-09 06:14:04 +03:00
{
2023-01-13 14:29:46 +03:00
return stream . write_entire_buffer ( DeprecatedString : : formatted ( " \033 [{};{}H " , row , col ) . bytes ( ) ) ;
2020-04-05 05:11:33 +03:00
}
2023-01-13 14:29:46 +03:00
ErrorOr < void > VT : : move_relative ( int row , int col , Core : : Stream : : Stream & stream )
2020-04-05 05:11:33 +03:00
{
char x_op = ' A ' , y_op = ' D ' ;
2020-04-09 06:14:04 +03:00
2020-06-07 23:18:40 +03:00
if ( row > 0 )
2020-04-05 05:11:33 +03:00
x_op = ' B ' ;
else
2020-06-07 23:18:40 +03:00
row = - row ;
if ( col > 0 )
2020-04-05 05:11:33 +03:00
y_op = ' C ' ;
else
2020-06-07 23:18:40 +03:00
col = - col ;
2020-04-05 05:11:33 +03:00
2020-06-07 23:18:40 +03:00
if ( row > 0 )
2023-01-13 14:29:46 +03:00
TRY ( stream . write_entire_buffer ( DeprecatedString : : formatted ( " \033 [{}{} " , row , x_op ) . bytes ( ) ) ) ;
2020-06-07 23:18:40 +03:00
if ( col > 0 )
2023-01-13 14:29:46 +03:00
TRY ( stream . write_entire_buffer ( DeprecatedString : : formatted ( " \033 [{}{} " , col , y_op ) . bytes ( ) ) ) ;
return { } ;
2020-04-05 05:11:33 +03:00
}
Style Editor : : find_applicable_style ( size_t offset ) const
{
2020-05-23 01:49:48 +03:00
// Walk through our styles and merge all that fit in the offset.
2020-05-23 17:45:23 +03:00
auto style = Style : : reset_style ( ) ;
2020-05-18 00:55:58 +03:00
auto unify = [ & ] ( auto & entry ) {
if ( entry . key > = offset )
return ;
2020-04-05 05:11:33 +03:00
for ( auto & style_value : entry . value ) {
if ( style_value . key < = offset )
2020-05-18 00:55:58 +03:00
return ;
2020-05-23 17:45:23 +03:00
style . unify_with ( style_value . value , true ) ;
2020-04-05 05:11:33 +03:00
}
2020-05-18 00:55:58 +03:00
} ;
2021-02-20 21:03:13 +03:00
for ( auto & entry : m_current_spans . m_spans_starting ) {
2020-05-18 00:55:58 +03:00
unify ( entry ) ;
2020-04-05 05:11:33 +03:00
}
2020-05-18 00:55:58 +03:00
2021-02-20 21:03:13 +03:00
for ( auto & entry : m_current_spans . m_anchored_spans_starting ) {
2020-05-18 00:55:58 +03:00
unify ( entry ) ;
}
return style ;
2020-04-05 05:11:33 +03:00
}
2022-12-04 21:02:33 +03:00
DeprecatedString Style : : Background : : to_vt_escape ( ) const
2020-05-10 10:57:36 +03:00
{
2020-05-18 00:55:58 +03:00
if ( is_default ( ) )
return " " ;
2020-05-10 10:57:36 +03:00
if ( m_is_rgb ) {
2022-12-04 21:02:33 +03:00
return DeprecatedString : : formatted ( " \ e[48;2;{};{};{}m " , m_rgb_color [ 0 ] , m_rgb_color [ 1 ] , m_rgb_color [ 2 ] ) ;
2020-05-10 10:57:36 +03:00
} else {
2022-12-04 21:02:33 +03:00
return DeprecatedString : : formatted ( " \ e[{}m " , ( u8 ) m_xterm_color + 40 ) ;
2020-05-10 10:57:36 +03:00
}
}
2022-12-04 21:02:33 +03:00
DeprecatedString Style : : Foreground : : to_vt_escape ( ) const
2020-05-10 10:57:36 +03:00
{
2020-05-18 00:55:58 +03:00
if ( is_default ( ) )
return " " ;
2020-05-10 10:57:36 +03:00
if ( m_is_rgb ) {
2022-12-04 21:02:33 +03:00
return DeprecatedString : : formatted ( " \ e[38;2;{};{};{}m " , m_rgb_color [ 0 ] , m_rgb_color [ 1 ] , m_rgb_color [ 2 ] ) ;
2020-05-10 10:57:36 +03:00
} else {
2022-12-04 21:02:33 +03:00
return DeprecatedString : : formatted ( " \ e[{}m " , ( u8 ) m_xterm_color + 30 ) ;
2020-05-10 10:57:36 +03:00
}
}
2022-12-04 21:02:33 +03:00
DeprecatedString Style : : Hyperlink : : to_vt_escape ( bool starting ) const
2020-05-18 00:55:58 +03:00
{
if ( is_empty ( ) )
return " " ;
2022-12-04 21:02:33 +03:00
return DeprecatedString : : formatted ( " \ e]8;;{} \ e \\ " , starting ? m_link : DeprecatedString : : empty ( ) ) ;
2020-05-18 00:55:58 +03:00
}
2021-12-14 14:11:18 +03:00
void Style : : unify_with ( Style const & other , bool prefer_other )
2020-05-18 00:55:58 +03:00
{
2020-05-23 01:49:48 +03:00
// Unify colors.
2020-05-18 00:55:58 +03:00
if ( prefer_other | | m_background . is_default ( ) )
m_background = other . background ( ) ;
if ( prefer_other | | m_foreground . is_default ( ) )
m_foreground = other . foreground ( ) ;
2020-05-23 01:49:48 +03:00
// Unify graphic renditions.
2020-05-18 00:55:58 +03:00
if ( other . bold ( ) )
set ( Bold ) ;
if ( other . italic ( ) )
set ( Italic ) ;
if ( other . underline ( ) )
set ( Underline ) ;
2020-05-23 01:49:48 +03:00
// Unify links.
2020-05-18 00:55:58 +03:00
if ( prefer_other | | m_hyperlink . is_empty ( ) )
m_hyperlink = other . hyperlink ( ) ;
}
2022-12-06 04:12:49 +03:00
DeprecatedString Style : : to_deprecated_string ( ) const
2020-05-18 00:55:58 +03:00
{
StringBuilder builder ;
2022-07-11 20:32:29 +03:00
builder . append ( " Style { " sv ) ;
2020-05-18 00:55:58 +03:00
if ( ! m_foreground . is_default ( ) ) {
2022-07-11 20:32:29 +03:00
builder . append ( " Foreground( " sv ) ;
2020-05-18 00:55:58 +03:00
if ( m_foreground . m_is_rgb ) {
2022-07-11 20:32:29 +03:00
builder . join ( " , " sv , m_foreground . m_rgb_color ) ;
2020-05-18 00:55:58 +03:00
} else {
2021-05-07 21:47:40 +03:00
builder . appendff ( " (XtermColor) {} " , ( int ) m_foreground . m_xterm_color ) ;
2020-05-18 00:55:58 +03:00
}
2022-07-11 20:32:29 +03:00
builder . append ( " ), " sv ) ;
2020-05-18 00:55:58 +03:00
}
if ( ! m_background . is_default ( ) ) {
2022-07-11 20:32:29 +03:00
builder . append ( " Background( " sv ) ;
2020-05-18 00:55:58 +03:00
if ( m_background . m_is_rgb ) {
builder . join ( ' ' , m_background . m_rgb_color ) ;
} else {
2021-05-07 21:47:40 +03:00
builder . appendff ( " (XtermColor) {} " , ( int ) m_background . m_xterm_color ) ;
2020-05-18 00:55:58 +03:00
}
2022-07-11 20:32:29 +03:00
builder . append ( " ), " sv ) ;
2020-05-18 00:55:58 +03:00
}
if ( bold ( ) )
2022-07-11 20:32:29 +03:00
builder . append ( " Bold, " sv ) ;
2020-05-18 00:55:58 +03:00
if ( underline ( ) )
2022-07-11 20:32:29 +03:00
builder . append ( " Underline, " sv ) ;
2020-05-18 00:55:58 +03:00
if ( italic ( ) )
2022-07-11 20:32:29 +03:00
builder . append ( " Italic, " sv ) ;
2020-05-18 00:55:58 +03:00
if ( ! m_hyperlink . is_empty ( ) )
2021-05-07 21:47:40 +03:00
builder . appendff ( " Hyperlink( \" {} \" ), " , m_hyperlink . m_link ) ;
2020-05-18 00:55:58 +03:00
2022-05-02 14:40:42 +03:00
if ( ! m_mask . has_value ( ) ) {
builder . appendff ( " Mask( \" {} \" , {}), " ,
m_mask - > replacement ,
m_mask - > mode = = Mask : : Mode : : ReplaceEntireSelection
? " ReplaceEntireSelection "
: " ReplaceEachCodePointInSelection " ) ;
}
2022-07-11 23:10:18 +03:00
builder . append ( ' } ' ) ;
2020-05-18 00:55:58 +03:00
return builder . build ( ) ;
}
2023-01-13 14:29:46 +03:00
ErrorOr < void > VT : : apply_style ( Style const & style , Core : : Stream : : Stream & stream , bool is_starting )
2020-04-05 05:11:33 +03:00
{
2020-05-18 00:55:58 +03:00
if ( is_starting ) {
2023-01-13 14:29:46 +03:00
TRY ( stream . write_entire_buffer ( DeprecatedString : : formatted ( " \033 [{};{};{}m{}{}{} " ,
style . bold ( ) ? 1 : 22 ,
style . underline ( ) ? 4 : 24 ,
style . italic ( ) ? 3 : 23 ,
style . background ( ) . to_vt_escape ( ) ,
style . foreground ( ) . to_vt_escape ( ) ,
style . hyperlink ( ) . to_vt_escape ( true ) )
. bytes ( ) ) ) ;
2020-05-18 00:55:58 +03:00
} else {
2023-01-13 14:29:46 +03:00
TRY ( stream . write_entire_buffer ( style . hyperlink ( ) . to_vt_escape ( false ) . bytes ( ) ) ) ;
2020-05-18 00:55:58 +03:00
}
2023-01-13 14:29:46 +03:00
return { } ;
2020-04-05 05:11:33 +03:00
}
2023-01-13 14:29:46 +03:00
ErrorOr < void > VT : : clear_lines ( size_t count_above , size_t count_below , Core : : Stream : : Stream & stream )
2020-04-05 05:11:33 +03:00
{
2021-02-21 04:07:43 +03:00
if ( count_below + count_above = = 0 ) {
2023-01-13 14:29:46 +03:00
TRY ( stream . write_entire_buffer ( " \033 [2K " sv . bytes ( ) ) ) ;
2021-02-21 04:07:43 +03:00
} else {
// Go down count_below lines.
if ( count_below > 0 )
2023-01-13 14:29:46 +03:00
TRY ( stream . write_entire_buffer ( DeprecatedString : : formatted ( " \033 [{}B " , count_below ) . bytes ( ) ) ) ;
2021-02-21 04:07:43 +03:00
// Then clear lines going upwards.
2021-07-19 21:42:28 +03:00
for ( size_t i = count_below + count_above ; i > 0 ; - - i ) {
2023-01-13 14:29:46 +03:00
TRY ( stream . write_entire_buffer ( " \033 [2K " sv . bytes ( ) ) ) ;
2021-07-19 21:42:28 +03:00
if ( i ! = 1 )
2023-01-13 14:29:46 +03:00
TRY ( stream . write_entire_buffer ( " \033 [A " sv . bytes ( ) ) ) ;
2021-07-19 21:42:28 +03:00
}
2021-02-21 04:07:43 +03:00
}
2023-01-13 14:29:46 +03:00
return { } ;
2020-04-05 05:11:33 +03:00
}
2023-01-13 14:29:46 +03:00
ErrorOr < void > VT : : save_cursor ( Core : : Stream : : Stream & stream )
2020-03-30 19:59:04 +03:00
{
2023-01-13 14:29:46 +03:00
return stream . write_entire_buffer ( " \033 [s " sv . bytes ( ) ) ;
2020-03-30 19:59:04 +03:00
}
2023-01-13 14:29:46 +03:00
ErrorOr < void > VT : : restore_cursor ( Core : : Stream : : Stream & stream )
2020-03-30 19:59:04 +03:00
{
2023-01-13 14:29:46 +03:00
return stream . write_entire_buffer ( " \033 [u " sv . bytes ( ) ) ;
2020-03-30 19:59:04 +03:00
}
2023-01-13 14:29:46 +03:00
ErrorOr < void > VT : : clear_to_end_of_line ( Core : : Stream : : Stream & stream )
2020-03-30 19:59:04 +03:00
{
2023-01-13 14:29:46 +03:00
return stream . write_entire_buffer ( " \033 [K " sv . bytes ( ) ) ;
2020-03-30 19:59:04 +03:00
}
2020-04-09 06:14:04 +03:00
2022-05-02 14:40:42 +03:00
enum VTState {
Free = 1 ,
Escape = 3 ,
Bracket = 5 ,
BracketArgsSemi = 7 ,
Title = 9 ,
2022-11-08 13:47:49 +03:00
URL = 11 ,
2022-05-02 14:40:42 +03:00
} ;
2022-11-08 03:58:04 +03:00
static VTState actual_rendered_string_length_step ( StringMetrics & metrics , size_t index , StringMetrics : : LineMetrics & current_line , u32 c , u32 next_c , VTState state , Optional < Style : : Mask > const & mask , Optional < size_t > const & maximum_line_width = { } , Optional < size_t & > last_return = { } ) ;
2022-05-02 14:40:42 +03:00
enum class MaskedSelectionDecision {
Skip ,
Continue ,
} ;
static MaskedSelectionDecision resolve_masked_selection ( Optional < Style : : Mask > & mask , size_t & i , auto & mask_it , auto & view , auto & state , auto & metrics , auto & current_line )
{
if ( mask . has_value ( ) & & mask - > mode = = Style : : Mask : : Mode : : ReplaceEntireSelection ) {
+ + mask_it ;
auto actual_end_offset = mask_it . is_end ( ) ? view . length ( ) : mask_it . key ( ) ;
auto end_offset = min ( actual_end_offset , view . length ( ) ) ;
size_t j = 0 ;
for ( auto it = mask - > replacement_view . begin ( ) ; it ! = mask - > replacement_view . end ( ) ; + + it ) {
auto it_copy = it ;
+ + it_copy ;
auto next_c = it_copy = = mask - > replacement_view . end ( ) ? 0 : * it_copy ;
state = actual_rendered_string_length_step ( metrics , j , current_line , * it , next_c , state , { } ) ;
+ + j ;
if ( j < = actual_end_offset - i & & j + i > = view . length ( ) )
break ;
}
current_line . masked_chars . empend ( i , end_offset - i , j ) ;
i = end_offset ;
if ( mask_it . is_end ( ) )
mask = { } ;
else
mask = * mask_it ;
return MaskedSelectionDecision : : Skip ;
}
return MaskedSelectionDecision : : Continue ;
}
2022-11-08 03:58:04 +03:00
StringMetrics Editor : : actual_rendered_string_metrics ( StringView string , RedBlackTree < u32 , Optional < Style : : Mask > > const & masks , Optional < size_t > maximum_line_width )
2020-04-09 06:14:04 +03:00
{
2020-06-26 15:13:55 +03:00
StringMetrics metrics ;
2021-01-10 15:53:04 +03:00
StringMetrics : : LineMetrics current_line ;
2020-06-26 15:13:55 +03:00
VTState state { Free } ;
2020-05-18 12:17:34 +03:00
Utf8View view { string } ;
2022-11-08 03:58:04 +03:00
size_t last_return { } ;
2020-05-18 12:17:34 +03:00
auto it = view . begin ( ) ;
2022-05-02 14:40:42 +03:00
Optional < Style : : Mask > mask ;
size_t i = 0 ;
auto mask_it = masks . begin ( ) ;
2020-05-18 12:17:34 +03:00
2020-06-26 15:13:55 +03:00
for ( ; it ! = view . end ( ) ; + + it ) {
2022-05-02 14:40:42 +03:00
if ( ! mask_it . is_end ( ) & & mask_it . key ( ) < = i )
mask = * mask_it ;
2020-05-18 12:17:34 +03:00
auto c = * it ;
2020-06-26 15:13:55 +03:00
auto it_copy = it ;
+ + it_copy ;
2022-05-02 14:40:42 +03:00
if ( resolve_masked_selection ( mask , i , mask_it , view , state , metrics , current_line ) = = MaskedSelectionDecision : : Skip )
continue ;
2020-06-26 15:13:55 +03:00
auto next_c = it_copy = = view . end ( ) ? 0 : * it_copy ;
2022-11-08 03:58:04 +03:00
state = actual_rendered_string_length_step ( metrics , view . iterator_offset ( it ) , current_line , c , next_c , state , mask , maximum_line_width , last_return ) ;
2022-05-02 14:40:42 +03:00
if ( ! mask_it . is_end ( ) & & mask_it . key ( ) < = i ) {
auto mask_it_peek = mask_it ;
+ + mask_it_peek ;
if ( ! mask_it_peek . is_end ( ) & & mask_it_peek . key ( ) > i )
mask_it = mask_it_peek ;
}
+ + i ;
2020-06-26 15:13:55 +03:00
}
2021-01-10 15:53:04 +03:00
metrics . line_metrics . append ( current_line ) ;
2020-06-26 15:13:55 +03:00
2021-01-10 15:53:04 +03:00
for ( auto & line : metrics . line_metrics )
metrics . max_line_length = max ( line . total_length ( ) , metrics . max_line_length ) ;
2020-06-26 15:13:55 +03:00
return metrics ;
}
2022-05-02 14:40:42 +03:00
StringMetrics Editor : : actual_rendered_string_metrics ( Utf32View const & view , RedBlackTree < u32 , Optional < Style : : Mask > > const & masks )
2020-06-26 15:13:55 +03:00
{
StringMetrics metrics ;
2021-01-10 15:53:04 +03:00
StringMetrics : : LineMetrics current_line ;
2020-06-26 15:13:55 +03:00
VTState state { Free } ;
2022-05-02 14:40:42 +03:00
Optional < Style : : Mask > mask ;
auto mask_it = masks . begin ( ) ;
2020-06-26 15:13:55 +03:00
for ( size_t i = 0 ; i < view . length ( ) ; + + i ) {
2022-05-02 14:40:42 +03:00
auto c = view [ i ] ;
if ( ! mask_it . is_end ( ) & & mask_it . key ( ) < = i )
mask = * mask_it ;
if ( resolve_masked_selection ( mask , i , mask_it , view , state , metrics , current_line ) = = MaskedSelectionDecision : : Skip ) {
- - i ;
continue ;
}
2020-08-05 23:31:20 +03:00
auto next_c = i + 1 < view . length ( ) ? view . code_points ( ) [ i + 1 ] : 0 ;
2022-05-02 14:40:42 +03:00
state = actual_rendered_string_length_step ( metrics , i , current_line , c , next_c , state , mask ) ;
if ( ! mask_it . is_end ( ) & & mask_it . key ( ) < = i ) {
auto mask_it_peek = mask_it ;
+ + mask_it_peek ;
if ( ! mask_it_peek . is_end ( ) & & mask_it_peek . key ( ) > i )
mask_it = mask_it_peek ;
}
2020-06-26 15:13:55 +03:00
}
2021-01-10 15:53:04 +03:00
metrics . line_metrics . append ( current_line ) ;
2020-06-26 15:13:55 +03:00
2021-01-10 15:53:04 +03:00
for ( auto & line : metrics . line_metrics )
metrics . max_line_length = max ( line . total_length ( ) , metrics . max_line_length ) ;
2020-06-26 15:13:55 +03:00
return metrics ;
}
2022-11-08 03:58:04 +03:00
VTState actual_rendered_string_length_step ( StringMetrics & metrics , size_t index , StringMetrics : : LineMetrics & current_line , u32 c , u32 next_c , VTState state , Optional < Style : : Mask > const & mask , Optional < size_t > const & maximum_line_width , Optional < size_t & > last_return )
2020-06-26 15:13:55 +03:00
{
2022-11-08 03:58:04 +03:00
auto const save_line = [ & metrics , & current_line , & last_return , & index ] ( ) {
if ( last_return . has_value ( ) ) {
auto const last_index = index - 1 ;
current_line . bit_length = last_index - * last_return + 1 ;
last_return . value ( ) = last_index + 1 ;
}
metrics . line_metrics . append ( current_line ) ;
current_line . masked_chars = { } ;
current_line . length = 0 ;
current_line . visible_length = 0 ;
current_line . bit_length = { } ;
} ;
// FIXME: current_line.visible_length can go above maximum_line_width when using masks
2022-11-10 23:13:52 +03:00
if ( maximum_line_width . has_value ( ) & & current_line . visible_length > = maximum_line_width . value ( ) )
2022-11-08 03:58:04 +03:00
save_line ( ) ;
ScopeGuard bit_length_update { [ & last_return , & current_line , & index ] ( ) {
if ( last_return . has_value ( ) )
current_line . bit_length = index - * last_return + 1 ;
} } ;
2020-06-26 15:13:55 +03:00
switch ( state ) {
2022-05-02 14:40:42 +03:00
case Free : {
2020-06-26 15:13:55 +03:00
if ( c = = ' \x1b ' ) { // escape
return Escape ;
}
if ( c = = ' \r ' ) { // carriage return
2021-01-10 15:53:04 +03:00
current_line . masked_chars = { } ;
current_line . length = 0 ;
2022-11-08 03:58:04 +03:00
current_line . visible_length = 0 ;
2021-01-10 15:53:04 +03:00
if ( ! metrics . line_metrics . is_empty ( ) )
metrics . line_metrics . last ( ) = { { } , 0 } ;
2020-06-26 15:13:55 +03:00
return state ;
}
if ( c = = ' \n ' ) { // return
2022-11-08 03:58:04 +03:00
save_line ( ) ;
return state ;
}
if ( c = = ' \t ' ) {
// Tabs are a special case, because their width is variable.
+ + current_line . length ;
current_line . visible_length + = ( 8 - ( current_line . visible_length % 8 ) ) ;
2020-06-26 15:13:55 +03:00
return state ;
2020-04-09 06:14:04 +03:00
}
2022-05-02 14:40:42 +03:00
auto is_control = is_ascii_control ( c ) ;
if ( is_control ) {
if ( mask . has_value ( ) )
current_line . masked_chars . append ( { index , 1 , mask - > replacement_view . length ( ) } ) ;
else
current_line . masked_chars . append ( { index , 1 , c < 64 ? 2u : 4u } ) ; // if the character cannot be represented as ^c, represent it as \xbb.
}
2020-06-26 15:13:55 +03:00
// FIXME: This will not support anything sophisticated
2022-05-02 14:40:42 +03:00
if ( mask . has_value ( ) ) {
current_line . length + = mask - > replacement_view . length ( ) ;
2022-11-08 03:58:04 +03:00
current_line . visible_length + = mask - > replacement_view . length ( ) ;
2022-05-02 14:40:42 +03:00
metrics . total_length + = mask - > replacement_view . length ( ) ;
} else if ( is_control ) {
current_line . length + = current_line . masked_chars . last ( ) . masked_length ;
2022-11-08 03:58:04 +03:00
current_line . visible_length + = current_line . masked_chars . last ( ) . masked_length ;
2022-05-02 14:40:42 +03:00
metrics . total_length + = current_line . masked_chars . last ( ) . masked_length ;
} else {
+ + current_line . length ;
2022-11-08 03:58:04 +03:00
+ + current_line . visible_length ;
2022-05-02 14:40:42 +03:00
+ + metrics . total_length ;
}
2020-06-26 15:13:55 +03:00
return state ;
2022-05-02 14:40:42 +03:00
}
2020-06-26 15:13:55 +03:00
case Escape :
if ( c = = ' ] ' ) {
if ( next_c = = ' 0 ' )
state = Title ;
2022-11-08 13:47:49 +03:00
if ( next_c = = ' 8 ' )
state = URL ;
2020-06-26 15:13:55 +03:00
return state ;
}
if ( c = = ' [ ' ) {
return Bracket ;
}
// FIXME: This does not support non-VT (aside from set-title) escapes
return state ;
case Bracket :
2021-06-01 22:18:08 +03:00
if ( is_ascii_digit ( c ) ) {
2020-06-26 15:13:55 +03:00
return BracketArgsSemi ;
}
return state ;
case BracketArgsSemi :
if ( c = = ' ; ' ) {
return Bracket ;
}
2021-06-01 22:18:08 +03:00
if ( ! is_ascii_digit ( c ) )
2020-06-26 15:13:55 +03:00
state = Free ;
return state ;
case Title :
if ( c = = 7 )
state = Free ;
return state ;
2022-11-08 13:47:49 +03:00
case URL :
if ( c = = ' \\ ' )
state = Free ;
return state ;
2020-04-09 06:14:04 +03:00
}
2020-06-26 15:13:55 +03:00
return state ;
2020-04-09 06:14:04 +03:00
}
2021-06-19 04:40:10 +03:00
Result < Vector < size_t , 2 > , Editor : : Error > Editor : : vt_dsr ( )
2020-04-09 06:14:04 +03:00
{
2020-04-29 00:17:41 +03:00
char buf [ 16 ] ;
2020-05-23 01:49:48 +03:00
// Read whatever junk there is before talking to the terminal
// and insert them later when we're reading user input.
2020-04-29 00:17:41 +03:00
bool more_junk_to_read { false } ;
timeval timeout { 0 , 0 } ;
fd_set readfds ;
FD_ZERO ( & readfds ) ;
FD_SET ( 0 , & readfds ) ;
do {
more_junk_to_read = false ;
2020-12-21 02:09:48 +03:00
[[maybe_unused]] auto rc = select ( 1 , & readfds , nullptr , nullptr , & timeout ) ;
2020-04-29 00:17:41 +03:00
if ( FD_ISSET ( 0 , & readfds ) ) {
2020-04-29 01:03:19 +03:00
auto nread = read ( 0 , buf , 16 ) ;
2020-05-25 15:27:07 +03:00
if ( nread < 0 ) {
m_input_error = Error : : ReadFailure ;
2020-05-26 13:34:39 +03:00
finish ( ) ;
2020-05-25 15:27:07 +03:00
break ;
}
if ( nread = = 0 )
break ;
2020-05-18 12:17:34 +03:00
m_incomplete_data . append ( buf , nread ) ;
2020-04-29 00:17:41 +03:00
more_junk_to_read = true ;
}
} while ( more_junk_to_read ) ;
2020-05-25 15:27:07 +03:00
if ( m_input_error . has_value ( ) )
2021-06-19 04:40:10 +03:00
return m_input_error . value ( ) ;
2020-05-25 15:27:07 +03:00
2020-08-09 18:01:15 +03:00
fputs ( " \033 [6n " , stderr ) ;
fflush ( stderr ) ;
2020-04-09 06:14:04 +03:00
2021-03-16 22:18:34 +03:00
// Parse the DSR response
// it should be of the form .*\e[\d+;\d+R.*
// Anything not part of the response is just added to the incomplete data.
enum {
Free ,
SawEsc ,
SawBracket ,
InFirstCoordinate ,
SawSemicolon ,
InSecondCoordinate ,
SawR ,
} state { Free } ;
auto has_error = false ;
Vector < char , 4 > coordinate_buffer ;
size_t row { 1 } , col { 1 } ;
2020-04-09 06:14:04 +03:00
do {
2021-03-16 22:18:34 +03:00
char c ;
auto nread = read ( 0 , & c , 1 ) ;
2020-04-09 06:14:04 +03:00
if ( nread < 0 ) {
2021-01-10 01:19:55 +03:00
if ( errno = = 0 | | errno = = EINTR ) {
2020-04-10 08:23:22 +03:00
// ????
continue ;
}
2021-01-11 23:13:30 +03:00
dbgln ( " Error while reading DSR: {} " , strerror ( errno ) ) ;
2021-06-19 04:40:10 +03:00
return Error : : ReadFailure ;
2020-04-09 06:14:04 +03:00
}
if ( nread = = 0 ) {
2021-01-09 20:51:44 +03:00
dbgln ( " Terminal DSR issue; received no response " ) ;
2021-06-19 04:40:10 +03:00
return Error : : Empty ;
2020-04-09 06:14:04 +03:00
}
2021-03-16 22:18:34 +03:00
switch ( state ) {
case Free :
if ( c = = ' \x1b ' ) {
state = SawEsc ;
continue ;
}
m_incomplete_data . append ( c ) ;
continue ;
case SawEsc :
if ( c = = ' [ ' ) {
state = SawBracket ;
continue ;
}
m_incomplete_data . append ( c ) ;
continue ;
case SawBracket :
2021-06-01 22:18:08 +03:00
if ( is_ascii_digit ( c ) ) {
2021-03-16 22:18:34 +03:00
state = InFirstCoordinate ;
coordinate_buffer . append ( c ) ;
continue ;
}
m_incomplete_data . append ( c ) ;
continue ;
case InFirstCoordinate :
2021-06-01 22:18:08 +03:00
if ( is_ascii_digit ( c ) ) {
2021-03-16 22:18:34 +03:00
coordinate_buffer . append ( c ) ;
continue ;
}
if ( c = = ' ; ' ) {
auto maybe_row = StringView { coordinate_buffer . data ( ) , coordinate_buffer . size ( ) } . to_uint ( ) ;
if ( ! maybe_row . has_value ( ) )
has_error = true ;
row = maybe_row . value_or ( 1u ) ;
coordinate_buffer . clear_with_capacity ( ) ;
state = SawSemicolon ;
continue ;
}
m_incomplete_data . append ( c ) ;
continue ;
case SawSemicolon :
2021-06-01 22:18:08 +03:00
if ( is_ascii_digit ( c ) ) {
2021-03-16 22:18:34 +03:00
state = InSecondCoordinate ;
coordinate_buffer . append ( c ) ;
continue ;
}
m_incomplete_data . append ( c ) ;
continue ;
case InSecondCoordinate :
2021-06-01 22:18:08 +03:00
if ( is_ascii_digit ( c ) ) {
2021-03-16 22:18:34 +03:00
coordinate_buffer . append ( c ) ;
continue ;
}
if ( c = = ' R ' ) {
auto maybe_column = StringView { coordinate_buffer . data ( ) , coordinate_buffer . size ( ) } . to_uint ( ) ;
if ( ! maybe_column . has_value ( ) )
has_error = true ;
col = maybe_column . value_or ( 1u ) ;
coordinate_buffer . clear_with_capacity ( ) ;
state = SawR ;
continue ;
}
m_incomplete_data . append ( c ) ;
continue ;
case SawR :
m_incomplete_data . append ( c ) ;
continue ;
default :
VERIFY_NOT_REACHED ( ) ;
2020-04-09 06:14:04 +03:00
}
2021-03-16 22:18:34 +03:00
} while ( state ! = SawR ) ;
if ( has_error )
dbgln ( " Terminal DSR issue, couldn't parse DSR response " ) ;
2021-06-19 04:40:10 +03:00
return Vector < size_t , 2 > { row , col } ;
2020-04-09 06:14:04 +03:00
}
2020-05-18 12:17:34 +03:00
2022-12-04 21:02:33 +03:00
DeprecatedString Editor : : line ( size_t up_to_index ) const
2020-05-18 12:17:34 +03:00
{
StringBuilder builder ;
2020-05-19 07:12:01 +03:00
builder . append ( Utf32View { m_buffer . data ( ) , min ( m_buffer . size ( ) , up_to_index ) } ) ;
2020-05-18 12:17:34 +03:00
return builder . build ( ) ;
}
2020-05-18 00:55:58 +03:00
void Editor : : remove_at_index ( size_t index )
{
2020-05-23 01:49:48 +03:00
// See if we have any anchored styles, and reposition them if needed.
2020-05-18 00:55:58 +03:00
readjust_anchored_styles ( index , ModificationKind : : Removal ) ;
2020-06-26 15:13:55 +03:00
auto cp = m_buffer [ index ] ;
2020-05-18 00:55:58 +03:00
m_buffer . remove ( index ) ;
2020-06-26 15:13:55 +03:00
if ( cp = = ' \n ' )
+ + m_extra_forward_lines ;
2021-02-20 21:03:13 +03:00
+ + m_chars_touched_in_the_middle ;
2020-05-18 00:55:58 +03:00
}
void Editor : : readjust_anchored_styles ( size_t hint_index , ModificationKind modification )
{
struct Anchor {
Span old_span ;
Span new_span ;
Style style ;
} ;
Vector < Anchor > anchors_to_relocate ;
auto index_shift = modification = = ModificationKind : : Insertion ? 1 : - 1 ;
2020-05-21 03:44:34 +03:00
auto forced_removal = modification = = ModificationKind : : ForcedOverlapRemoval ;
2020-05-18 00:55:58 +03:00
2021-02-20 21:03:13 +03:00
for ( auto & start_entry : m_current_spans . m_anchored_spans_starting ) {
2020-05-18 00:55:58 +03:00
for ( auto & end_entry : start_entry . value ) {
2020-05-21 03:44:34 +03:00
if ( forced_removal ) {
2020-05-21 13:45:56 +03:00
if ( start_entry . key < = hint_index & & end_entry . key > hint_index ) {
2020-05-23 01:49:48 +03:00
// Remove any overlapping regions.
2020-05-21 03:44:34 +03:00
continue ;
}
}
2020-05-18 00:55:58 +03:00
if ( start_entry . key > = hint_index ) {
if ( start_entry . key = = hint_index & & end_entry . key = = hint_index + 1 & & modification = = ModificationKind : : Removal ) {
2020-05-23 01:49:48 +03:00
// Remove the anchor, as all its text was wiped.
2020-05-18 00:55:58 +03:00
continue ;
}
2020-05-23 01:49:48 +03:00
// Shift everything.
2020-05-18 00:55:58 +03:00
anchors_to_relocate . append ( { { start_entry . key , end_entry . key , Span : : Mode : : CodepointOriented } , { start_entry . key + index_shift , end_entry . key + index_shift , Span : : Mode : : CodepointOriented } , end_entry . value } ) ;
continue ;
}
if ( end_entry . key > hint_index ) {
2020-05-23 01:49:48 +03:00
// Shift just the end.
2020-05-18 00:55:58 +03:00
anchors_to_relocate . append ( { { start_entry . key , end_entry . key , Span : : Mode : : CodepointOriented } , { start_entry . key , end_entry . key + index_shift , Span : : Mode : : CodepointOriented } , end_entry . value } ) ;
continue ;
}
anchors_to_relocate . append ( { { start_entry . key , end_entry . key , Span : : Mode : : CodepointOriented } , { start_entry . key , end_entry . key , Span : : Mode : : CodepointOriented } , end_entry . value } ) ;
}
}
2021-02-20 21:03:13 +03:00
m_current_spans . m_anchored_spans_ending . clear ( ) ;
m_current_spans . m_anchored_spans_starting . clear ( ) ;
2020-05-23 01:49:48 +03:00
// Pass over the relocations and update the stale entries.
2020-05-18 00:55:58 +03:00
for ( auto & relocation : anchors_to_relocate ) {
stylize ( relocation . new_span , relocation . style ) ;
}
}
2020-06-26 15:13:55 +03:00
2021-12-14 14:11:18 +03:00
size_t StringMetrics : : lines_with_addition ( StringMetrics const & offset , size_t column_width ) const
2020-06-26 15:13:55 +03:00
{
size_t lines = 0 ;
2022-10-27 23:13:36 +03:00
if ( ! line_metrics . is_empty ( ) ) {
for ( size_t i = 0 ; i < line_metrics . size ( ) - 1 ; + + i )
lines + = ( line_metrics [ i ] . total_length ( ) + column_width ) / column_width ;
2020-06-26 15:13:55 +03:00
2022-10-27 23:13:36 +03:00
auto last = line_metrics . last ( ) . total_length ( ) ;
last + = offset . line_metrics . first ( ) . total_length ( ) ;
lines + = ( last + column_width ) / column_width ;
}
2020-06-26 15:13:55 +03:00
2021-01-10 15:53:04 +03:00
for ( size_t i = 1 ; i < offset . line_metrics . size ( ) ; + + i )
lines + = ( offset . line_metrics [ i ] . total_length ( ) + column_width ) / column_width ;
2020-06-26 15:13:55 +03:00
return lines ;
}
2021-01-04 11:08:56 +03:00
2021-12-14 14:11:18 +03:00
size_t StringMetrics : : offset_with_addition ( StringMetrics const & offset , size_t column_width ) const
2021-01-04 11:08:56 +03:00
{
2021-01-10 15:53:04 +03:00
if ( offset . line_metrics . size ( ) > 1 )
return offset . line_metrics . last ( ) . total_length ( ) % column_width ;
2021-01-04 11:08:56 +03:00
2022-10-27 23:13:36 +03:00
if ( ! line_metrics . is_empty ( ) ) {
auto last = line_metrics . last ( ) . total_length ( ) ;
last + = offset . line_metrics . first ( ) . total_length ( ) ;
return last % column_width ;
}
if ( offset . line_metrics . is_empty ( ) )
return 0 ;
return offset . line_metrics . first ( ) . total_length ( ) % column_width ;
2021-01-04 11:08:56 +03:00
}
2021-12-14 14:11:18 +03:00
bool Editor : : Spans : : contains_up_to_offset ( Spans const & other , size_t offset ) const
2021-02-20 21:03:13 +03:00
{
2022-04-01 20:58:27 +03:00
auto compare = [ & ] < typename K , typename V > ( HashMap < K , HashMap < K , V > > const & left , HashMap < K , HashMap < K , V > > const & right ) - > bool {
2021-02-20 21:03:13 +03:00
for ( auto & entry : right ) {
if ( entry . key > offset + 1 )
continue ;
2021-12-05 14:10:17 +03:00
auto left_map_it = left . find ( entry . key ) ;
if ( left_map_it = = left . end ( ) )
2021-02-20 21:03:13 +03:00
return false ;
2021-12-05 14:10:17 +03:00
for ( auto & left_entry : left_map_it - > value ) {
auto value_it = entry . value . find ( left_entry . key ) ;
if ( value_it = = entry . value . end ( ) ) {
2021-02-20 21:03:13 +03:00
// Might have the same thing with a longer span
bool found = false ;
for ( auto & possibly_longer_span_entry : entry . value ) {
if ( possibly_longer_span_entry . key > left_entry . key & & possibly_longer_span_entry . key > offset & & left_entry . value = = possibly_longer_span_entry . value ) {
found = true ;
break ;
}
}
if ( found )
continue ;
if constexpr ( LINE_EDITOR_DEBUG ) {
dbgln ( " Compare for {}-{} failed, no entry " , entry . key , left_entry . key ) ;
for ( auto & x : entry . value )
2022-12-06 04:12:49 +03:00
dbgln ( " Have: {}-{} = {} " , entry . key , x . key , x . value . to_deprecated_string ( ) ) ;
2021-02-20 21:03:13 +03:00
}
return false ;
2021-12-05 14:10:17 +03:00
} else if ( value_it - > value ! = left_entry . value ) {
2022-12-06 04:12:49 +03:00
dbgln_if ( LINE_EDITOR_DEBUG , " Compare for {}-{} failed, different values: {} != {} " , entry . key , left_entry . key , value_it - > value . to_deprecated_string ( ) , left_entry . value . to_deprecated_string ( ) ) ;
2021-02-20 21:03:13 +03:00
return false ;
}
}
}
return true ;
} ;
return compare ( m_spans_starting , other . m_spans_starting )
& & compare ( m_anchored_spans_starting , other . m_anchored_spans_starting ) ;
}
2020-03-31 14:34:06 +03:00
}