2020-01-18 09:38:21 +01:00
/*
* Copyright ( c ) 2018 - 2020 , Andreas Kling < kling @ serenityos . org >
2021-05-23 15:47:41 +02:00
* Copyright ( c ) 2021 , Daniel Bertalan < dani @ danielbertalan . dev >
2020-01-18 09:38:21 +01:00
*
2021-04-22 01:24:48 -07:00
* SPDX - License - Identifier : BSD - 2 - Clause
2020-01-18 09:38:21 +01:00
*/
2021-01-24 15:28:26 +01:00
# include <AK/Debug.h>
2021-06-19 03:53:03 +04:30
# include <AK/Queue.h>
2019-08-12 17:32:16 +02:00
# include <AK/StringBuilder.h>
2020-05-27 00:31:30 +03:00
# include <AK/StringView.h>
2021-06-19 18:17:18 +04:30
# include <AK/TemporaryChange.h>
2021-05-27 19:19:30 +02:00
# include <LibVT/Color.h>
2019-08-12 17:32:16 +02:00
# include <LibVT/Terminal.h>
2021-04-16 22:58:51 +03:00
# ifdef KERNEL
2023-07-08 14:18:32 +03:00
# include <Kernel / Devices / TTY / VirtualConsole.h>
2021-04-16 22:58:51 +03:00
# endif
2019-08-12 17:32:16 +02:00
namespace VT {
2021-04-16 22:58:51 +03:00
# ifndef KERNEL
2019-08-12 17:32:16 +02:00
Terminal : : Terminal ( TerminalClient & client )
2021-04-16 22:58:51 +03:00
# else
Terminal : : Terminal ( Kernel : : VirtualConsole & client )
# endif
2019-08-12 17:32:16 +02:00
: m_client ( client )
2021-05-08 20:37:43 +02:00
, m_parser ( * this )
2019-08-12 17:32:16 +02:00
{
}
2021-04-16 22:58:51 +03:00
# ifndef KERNEL
2019-08-12 17:32:16 +02:00
void Terminal : : clear ( )
{
2021-06-05 07:23:01 +02:00
dbgln_if ( TERMINAL_DEBUG , " Clear the entire screen " ) ;
2019-08-12 17:32:16 +02:00
for ( size_t i = 0 ; i < rows ( ) ; + + i )
2023-03-06 17:16:25 +01:00
active_buffer ( ) [ i ] - > clear ( ) ;
2019-08-12 17:32:16 +02:00
set_cursor ( 0 , 0 ) ;
}
2021-06-05 09:29:49 +02:00
void Terminal : : clear_history ( )
2020-07-05 23:34:02 +02:00
{
2021-06-05 09:29:49 +02:00
dbgln_if ( TERMINAL_DEBUG , " Clear history " ) ;
2021-06-05 16:46:33 +02:00
auto previous_history_size = m_history . size ( ) ;
2020-07-05 23:34:02 +02:00
m_history . clear ( ) ;
2020-09-09 19:06:57 -04:00
m_history_start = 0 ;
2021-06-05 16:46:33 +02:00
m_client . terminal_history_changed ( - previous_history_size ) ;
2020-07-05 23:34:02 +02:00
}
2021-04-16 22:58:51 +03:00
# endif
2020-07-05 23:34:02 +02:00
2021-07-09 12:08:36 +02:00
void Terminal : : alter_ansi_mode ( bool should_set , Parameters params )
{
for ( auto mode : params ) {
switch ( mode ) {
// FIXME: implement *something* for this
default :
dbgln ( " Terminal::alter_ansi_mode: Unimplemented mode {} (should_set={}) " , mode , should_set ) ;
break ;
}
}
}
void Terminal : : alter_private_mode ( bool should_set , Parameters params )
2021-05-15 14:58:24 +02:00
{
2021-07-09 12:08:36 +02:00
for ( auto mode : params ) {
switch ( mode ) {
2021-09-30 11:44:20 +02:00
case 1 :
// Cursor Keys Mode (DECCKM)
dbgln_if ( TERMINAL_DEBUG , " Setting cursor keys mode (should_set={}) " , should_set ) ;
m_cursor_keys_mode = should_set ? CursorKeysMode : : Application : CursorKeysMode : : Cursor ;
break ;
2021-07-09 12:08:36 +02:00
case 3 : {
// 80/132-column mode (DECCOLM)
unsigned new_columns = should_set ? 132 : 80 ;
dbgln_if ( TERMINAL_DEBUG , " Setting {}-column mode " , new_columns ) ;
set_size ( new_columns , rows ( ) ) ;
clear ( ) ;
break ;
}
case 12 :
if ( should_set ) {
// Start blinking cursor
2022-05-12 22:52:14 +02:00
m_client . set_cursor_blinking ( true ) ;
2021-07-09 12:08:36 +02:00
} else {
// Stop blinking cursor
2022-05-12 22:52:14 +02:00
m_client . set_cursor_blinking ( false ) ;
2021-05-15 14:58:24 +02:00
}
2021-07-09 12:08:36 +02:00
break ;
case 25 :
if ( should_set ) {
// Show cursor
2022-05-12 22:52:14 +02:00
m_cursor_shape = m_saved_cursor_shape ;
m_client . set_cursor_shape ( m_cursor_shape ) ;
2021-07-09 12:08:36 +02:00
} else {
// Hide cursor
2022-05-12 22:52:14 +02:00
m_saved_cursor_shape = m_cursor_shape ;
m_cursor_shape = VT : : CursorShape : : None ;
m_client . set_cursor_shape ( VT : : CursorShape : : None ) ;
2021-07-09 12:08:36 +02:00
}
break ;
case 1047 :
2021-05-23 15:47:41 +02:00
# ifndef KERNEL
2021-07-09 12:08:36 +02:00
if ( should_set ) {
dbgln_if ( TERMINAL_DEBUG , " Switching to Alternate Screen Buffer " ) ;
m_use_alternate_screen_buffer = true ;
clear ( ) ;
2021-07-09 17:36:23 +02:00
m_client . terminal_history_changed ( - m_history . size ( ) ) ;
2021-07-09 12:08:36 +02:00
} else {
dbgln_if ( TERMINAL_DEBUG , " Switching to Normal Screen Buffer " ) ;
m_use_alternate_screen_buffer = false ;
2021-07-09 17:36:23 +02:00
m_client . terminal_history_changed ( m_history . size ( ) ) ;
2021-07-09 12:08:36 +02:00
}
m_need_full_flush = true ;
2021-05-23 15:47:41 +02:00
# else
2021-07-09 12:08:36 +02:00
dbgln ( " Alternate Screen Buffer is not supported " ) ;
2021-05-23 15:47:41 +02:00
# endif
2021-07-09 12:08:36 +02:00
break ;
case 1048 :
if ( should_set )
SCOSC ( ) ;
else
SCORC ( ) ;
break ;
case 1049 :
2021-05-23 15:47:41 +02:00
# ifndef KERNEL
2021-07-09 12:08:36 +02:00
if ( should_set ) {
dbgln_if ( TERMINAL_DEBUG , " Switching to Alternate Screen Buffer and saving state " ) ;
m_normal_saved_state = m_current_state ;
m_use_alternate_screen_buffer = true ;
clear ( ) ;
2021-07-09 17:36:23 +02:00
m_client . terminal_history_changed ( - m_history . size ( ) ) ;
2021-07-09 12:08:36 +02:00
} else {
dbgln_if ( TERMINAL_DEBUG , " Switching to Normal Screen Buffer and restoring state " ) ;
m_current_state = m_normal_saved_state ;
m_use_alternate_screen_buffer = false ;
set_cursor ( cursor_row ( ) , cursor_column ( ) ) ;
2021-07-09 17:36:23 +02:00
m_client . terminal_history_changed ( m_history . size ( ) ) ;
2021-07-09 12:08:36 +02:00
}
m_need_full_flush = true ;
2021-05-23 15:47:41 +02:00
# else
2021-07-09 12:08:36 +02:00
dbgln ( " Alternate Screen Buffer is not supported " ) ;
2021-05-23 15:47:41 +02:00
# endif
2021-07-09 12:08:36 +02:00
break ;
case 2004 :
dbgln_if ( TERMINAL_DEBUG , " Setting bracketed mode enabled={} " , should_set ) ;
m_needs_bracketed_paste = should_set ;
break ;
default :
dbgln ( " Terminal::alter_private_mode: Unimplemented private mode {} (should_set={}) " , mode , should_set ) ;
break ;
2019-08-12 17:32:16 +02:00
}
}
}
2021-07-09 12:08:36 +02:00
void Terminal : : RM ( Parameters params )
{
alter_ansi_mode ( false , params ) ;
}
void Terminal : : DECRST ( Parameters params )
2020-01-26 14:21:54 +00:00
{
2021-07-09 12:08:36 +02:00
alter_private_mode ( false , params ) ;
2020-01-26 14:21:54 +00:00
}
2021-07-09 12:08:36 +02:00
void Terminal : : SM ( Parameters params )
2020-01-26 14:21:54 +00:00
{
2021-07-09 12:08:36 +02:00
alter_ansi_mode ( true , params ) ;
}
void Terminal : : DECSET ( Parameters params )
{
alter_private_mode ( true , params ) ;
2020-01-26 14:21:54 +00:00
}
2021-05-08 20:37:43 +02:00
void Terminal : : SGR ( Parameters params )
2019-08-12 17:32:16 +02:00
{
if ( params . is_empty ( ) ) {
2021-05-23 15:47:41 +02:00
m_current_state . attribute . reset ( ) ;
2019-08-12 17:32:16 +02:00
return ;
}
2021-05-27 19:19:30 +02:00
auto parse_color = [ & ] ( ) - > Optional < Color > {
2021-05-17 16:33:47 +02:00
if ( params . size ( ) < 2 ) {
dbgln ( " Color code has no type " ) ;
return { } ;
}
2021-05-27 19:19:30 +02:00
u32 rgb = 0 ;
2021-05-17 16:33:47 +02:00
switch ( params [ 1 ] ) {
2020-05-10 12:11:03 +04:30
case 5 : // 8-bit
2021-05-17 16:33:47 +02:00
if ( params . size ( ) < 3 ) {
dbgln ( " 8-bit color code has too few parameters " ) ;
return { } ;
}
2021-05-27 19:19:30 +02:00
if ( params [ 2 ] > 255 ) {
dbgln ( " 8-bit color code has out-of-bounds value " ) ;
return { } ;
}
return Color : : indexed ( params [ 2 ] ) ;
2020-05-10 12:11:03 +04:30
case 2 : // 24-bit
2021-05-17 16:33:47 +02:00
if ( params . size ( ) < 5 ) {
dbgln ( " 24-bit color code has too few parameters " ) ;
return { } ;
}
2020-05-10 12:11:03 +04:30
for ( size_t i = 0 ; i < 3 ; + + i ) {
2021-05-27 19:19:30 +02:00
rgb < < = 8 ;
rgb | = params [ i + 2 ] ;
2020-05-10 12:11:03 +04:30
}
2021-05-27 19:19:30 +02:00
return Color : : rgb ( rgb ) ;
2020-05-10 12:11:03 +04:30
default :
2021-05-17 16:33:47 +02:00
dbgln ( " Unknown color type {} " , params [ 1 ] ) ;
return { } ;
2020-05-10 12:11:03 +04:30
}
2021-05-17 16:33:47 +02:00
} ;
2020-05-10 12:11:03 +04:30
2021-05-17 16:33:47 +02:00
if ( params [ 0 ] = = 38 ) {
2021-05-23 15:47:41 +02:00
m_current_state . attribute . foreground_color = parse_color ( ) . value_or ( m_current_state . attribute . foreground_color ) ;
2021-05-17 16:33:47 +02:00
} else if ( params [ 0 ] = = 48 ) {
2021-05-23 15:47:41 +02:00
m_current_state . attribute . background_color = parse_color ( ) . value_or ( m_current_state . attribute . background_color ) ;
2021-05-17 16:33:47 +02:00
} else {
// A single escape sequence may set multiple parameters.
for ( auto param : params ) {
switch ( param ) {
case 0 :
// Reset
2021-05-23 15:47:41 +02:00
m_current_state . attribute . reset ( ) ;
2021-05-17 16:33:47 +02:00
break ;
case 1 :
2022-03-17 23:14:50 -07:00
m_current_state . attribute . flags | = Attribute : : Flags : : Bold ;
2021-05-17 16:33:47 +02:00
break ;
case 3 :
2022-03-17 23:14:50 -07:00
m_current_state . attribute . flags | = Attribute : : Flags : : Italic ;
2021-05-17 16:33:47 +02:00
break ;
case 4 :
2022-03-17 23:14:50 -07:00
m_current_state . attribute . flags | = Attribute : : Flags : : Underline ;
2021-05-17 16:33:47 +02:00
break ;
case 5 :
2022-03-17 23:14:50 -07:00
m_current_state . attribute . flags | = Attribute : : Flags : : Blink ;
2021-05-17 16:33:47 +02:00
break ;
case 7 :
2022-03-17 23:14:50 -07:00
m_current_state . attribute . flags | = Attribute : : Flags : : Negative ;
2021-05-17 16:33:47 +02:00
break ;
case 22 :
2022-03-17 23:14:50 -07:00
m_current_state . attribute . flags & = ~ Attribute : : Flags : : Bold ;
2021-05-17 16:33:47 +02:00
break ;
case 23 :
2022-03-17 23:14:50 -07:00
m_current_state . attribute . flags & = ~ Attribute : : Flags : : Italic ;
2021-05-17 16:33:47 +02:00
break ;
case 24 :
2022-03-17 23:14:50 -07:00
m_current_state . attribute . flags & = ~ Attribute : : Flags : : Underline ;
2021-05-17 16:33:47 +02:00
break ;
case 25 :
2022-03-17 23:14:50 -07:00
m_current_state . attribute . flags & = ~ Attribute : : Flags : : Blink ;
2021-05-17 16:33:47 +02:00
break ;
case 27 :
2022-03-17 23:14:50 -07:00
m_current_state . attribute . flags & = ~ Attribute : : Flags : : Negative ;
2021-05-17 16:33:47 +02:00
break ;
case 30 :
case 31 :
case 32 :
case 33 :
case 34 :
case 35 :
case 36 :
case 37 :
// Foreground color
2021-06-03 17:26:25 +02:00
m_current_state . attribute . foreground_color = Color : : named ( static_cast < Color : : ANSIColor > ( param - 30 ) ) ;
2021-05-17 16:33:47 +02:00
break ;
case 39 :
// reset foreground
2021-05-23 15:47:41 +02:00
m_current_state . attribute . foreground_color = Attribute : : default_foreground_color ;
2021-05-17 16:33:47 +02:00
break ;
case 40 :
case 41 :
case 42 :
case 43 :
case 44 :
case 45 :
case 46 :
case 47 :
// Background color
2021-06-03 17:26:25 +02:00
m_current_state . attribute . background_color = Color : : named ( static_cast < Color : : ANSIColor > ( param - 40 ) ) ;
2021-05-17 16:33:47 +02:00
break ;
case 49 :
// reset background
2021-05-23 15:47:41 +02:00
m_current_state . attribute . background_color = Attribute : : default_background_color ;
2021-05-17 16:33:47 +02:00
break ;
2021-06-03 17:26:25 +02:00
case 90 :
case 91 :
case 92 :
case 93 :
case 94 :
case 95 :
case 96 :
case 97 :
// Bright foreground color
m_current_state . attribute . foreground_color = Color : : named ( static_cast < Color : : ANSIColor > ( 8 + param - 90 ) ) ;
break ;
case 100 :
case 101 :
case 102 :
case 103 :
case 104 :
case 105 :
case 106 :
case 107 :
// Bright background color
m_current_state . attribute . background_color = Color : : named ( static_cast < Color : : ANSIColor > ( 8 + param - 100 ) ) ;
break ;
2021-05-17 16:33:47 +02:00
default :
dbgln ( " FIXME: SGR: p: {} " , param ) ;
2020-05-10 12:11:03 +04:30
}
2019-08-12 17:32:16 +02:00
}
}
}
2021-05-08 20:37:43 +02:00
void Terminal : : SCOSC ( )
2019-08-12 17:32:16 +02:00
{
2021-05-23 15:47:41 +02:00
dbgln_if ( TERMINAL_DEBUG , " Save cursor position " ) ;
m_saved_cursor_position = m_current_state . cursor ;
}
void Terminal : : SCORC ( )
{
dbgln_if ( TERMINAL_DEBUG , " Restore cursor position " ) ;
m_current_state . cursor = m_saved_cursor_position ;
set_cursor ( cursor_row ( ) , cursor_column ( ) ) ;
2019-08-12 17:32:16 +02:00
}
2021-05-23 15:47:41 +02:00
void Terminal : : DECSC ( )
2019-08-12 17:32:16 +02:00
{
2021-05-23 15:47:41 +02:00
dbgln_if ( TERMINAL_DEBUG , " Save cursor (and other state) " ) ;
if ( m_use_alternate_screen_buffer ) {
m_alternate_saved_state = m_current_state ;
} else {
m_normal_saved_state = m_current_state ;
}
}
void Terminal : : DECRC ( )
{
dbgln_if ( TERMINAL_DEBUG , " Restore cursor (and other state) " ) ;
if ( m_use_alternate_screen_buffer ) {
m_current_state = m_alternate_saved_state ;
} else {
m_current_state = m_normal_saved_state ;
}
set_cursor ( cursor_row ( ) , cursor_column ( ) ) ;
2019-08-12 17:32:16 +02:00
}
2021-05-08 20:37:43 +02:00
void Terminal : : XTERM_WM ( Parameters params )
2019-08-12 17:32:16 +02:00
{
if ( params . size ( ) < 1 )
return ;
2021-05-24 10:40:00 +02:00
switch ( params [ 0 ] ) {
case 22 : {
2022-07-14 05:53:13 +03:00
# ifndef KERNEL
2021-05-24 10:40:00 +02:00
if ( params . size ( ) > 1 & & params [ 1 ] = = 1 ) {
dbgln ( " FIXME: we don't support icon titles " ) ;
return ;
}
dbgln_if ( TERMINAL_DEBUG , " Title stack push: {} " , m_current_window_title ) ;
2022-02-15 22:46:49 +02:00
( void ) m_title_stack . try_append ( move ( m_current_window_title ) ) ;
# endif
2021-05-24 10:40:00 +02:00
break ;
}
case 23 : {
2022-07-14 05:53:13 +03:00
# ifndef KERNEL
2021-05-24 10:40:00 +02:00
if ( params . size ( ) > 1 & & params [ 1 ] = = 1 )
return ;
if ( m_title_stack . is_empty ( ) ) {
dbgln ( " Shenanigans: Tried to pop from empty title stack " ) ;
return ;
}
m_current_window_title = m_title_stack . take_last ( ) ;
dbgln_if ( TERMINAL_DEBUG , " Title stack pop: {} " , m_current_window_title ) ;
m_client . set_window_title ( m_current_window_title ) ;
2022-02-15 22:46:49 +02:00
# endif
2021-05-24 10:40:00 +02:00
break ;
}
default :
dbgln ( " FIXME: XTERM_WM: Ps: {} (param count: {}) " , params [ 0 ] , params . size ( ) ) ;
}
2019-08-12 17:32:16 +02:00
}
2021-05-08 20:37:43 +02:00
void Terminal : : DECSTBM ( Parameters params )
2019-08-12 17:32:16 +02:00
{
unsigned top = 1 ;
unsigned bottom = m_rows ;
2021-05-23 15:57:21 +02:00
if ( params . size ( ) > = 1 & & params [ 0 ] ! = 0 )
2019-08-12 17:32:16 +02:00
top = params [ 0 ] ;
2021-05-23 15:57:21 +02:00
if ( params . size ( ) > = 2 & & params [ 1 ] ! = 0 )
2019-08-12 17:32:16 +02:00
bottom = params [ 1 ] ;
if ( ( bottom - top ) < 2 | | bottom > m_rows ) {
2021-01-16 22:52:54 +01:00
dbgln ( " Error: DECSTBM: scrolling region invalid: {}-{} " , top , bottom ) ;
2019-08-12 17:32:16 +02:00
return ;
}
2021-07-08 20:11:55 +02:00
if ( top > = bottom ) {
return ;
}
2019-08-12 17:32:16 +02:00
m_scroll_region_top = top - 1 ;
m_scroll_region_bottom = bottom - 1 ;
set_cursor ( 0 , 0 ) ;
2021-05-23 15:57:21 +02:00
dbgln_if ( TERMINAL_DEBUG , " Set scrolling region: {}-{} " , m_scroll_region_top , m_scroll_region_bottom ) ;
2019-08-12 17:32:16 +02:00
}
2021-05-08 20:37:43 +02:00
void Terminal : : CUP ( Parameters params )
2019-08-12 17:32:16 +02:00
{
2020-01-26 13:42:00 +00:00
// CUP – Cursor Position
2019-08-12 17:32:16 +02:00
unsigned row = 1 ;
unsigned col = 1 ;
2021-05-23 15:57:21 +02:00
if ( params . size ( ) > = 1 & & params [ 0 ] ! = 0 )
2019-08-12 17:32:16 +02:00
row = params [ 0 ] ;
2021-05-23 15:57:21 +02:00
if ( params . size ( ) > = 2 & & params [ 1 ] ! = 0 )
2019-08-12 17:32:16 +02:00
col = params [ 1 ] ;
set_cursor ( row - 1 , col - 1 ) ;
}
2021-05-08 20:37:43 +02:00
void Terminal : : HVP ( Parameters params )
2020-01-25 19:49:42 +01:00
{
unsigned row = 1 ;
unsigned col = 1 ;
2021-05-23 15:47:41 +02:00
if ( params . size ( ) > = 1 & & params [ 0 ] ! = 0 )
2020-01-25 19:49:42 +01:00
row = params [ 0 ] ;
2021-05-23 15:47:41 +02:00
if ( params . size ( ) > = 2 & & params [ 1 ] ! = 0 )
2020-01-25 19:49:42 +01:00
col = params [ 1 ] ;
set_cursor ( row - 1 , col - 1 ) ;
}
2021-05-08 20:37:43 +02:00
void Terminal : : CUU ( Parameters params )
2019-08-12 17:32:16 +02:00
{
2021-05-23 15:47:41 +02:00
unsigned num = 1 ;
if ( params . size ( ) > = 1 & & params [ 0 ] ! = 0 )
2019-08-12 17:32:16 +02:00
num = params [ 0 ] ;
2021-05-23 15:47:41 +02:00
int new_row = cursor_row ( ) - num ;
2019-08-12 17:32:16 +02:00
if ( new_row < 0 )
new_row = 0 ;
2021-05-23 15:47:41 +02:00
set_cursor ( new_row , cursor_column ( ) ) ;
2019-08-12 17:32:16 +02:00
}
2021-05-08 20:37:43 +02:00
void Terminal : : CUD ( Parameters params )
2019-08-12 17:32:16 +02:00
{
2021-05-23 15:47:41 +02:00
unsigned num = 1 ;
if ( params . size ( ) > = 1 & & params [ 0 ] ! = 0 )
2019-08-12 17:32:16 +02:00
num = params [ 0 ] ;
2021-05-23 15:47:41 +02:00
unsigned new_row = cursor_row ( ) + num ;
2019-08-12 17:32:16 +02:00
if ( new_row > = m_rows )
new_row = m_rows - 1 ;
2021-05-23 15:47:41 +02:00
set_cursor ( new_row , cursor_column ( ) ) ;
2019-08-12 17:32:16 +02:00
}
2021-05-08 20:37:43 +02:00
void Terminal : : CUF ( Parameters params )
2019-08-12 17:32:16 +02:00
{
2021-05-23 15:47:41 +02:00
unsigned num = 1 ;
if ( params . size ( ) > = 1 & & params [ 0 ] ! = 0 )
2019-08-12 17:32:16 +02:00
num = params [ 0 ] ;
2021-05-23 15:47:41 +02:00
unsigned new_column = cursor_column ( ) + num ;
2019-08-12 17:32:16 +02:00
if ( new_column > = m_columns )
new_column = m_columns - 1 ;
2021-05-23 15:47:41 +02:00
set_cursor ( cursor_row ( ) , new_column ) ;
2019-08-12 17:32:16 +02:00
}
2021-05-08 20:37:43 +02:00
void Terminal : : CUB ( Parameters params )
2019-08-12 17:32:16 +02:00
{
2021-05-23 15:47:41 +02:00
unsigned num = 1 ;
if ( params . size ( ) > = 1 & & params [ 0 ] ! = 0 )
2019-08-12 17:32:16 +02:00
num = params [ 0 ] ;
2021-05-23 15:47:41 +02:00
int new_column = ( int ) cursor_column ( ) - num ;
2019-08-12 17:32:16 +02:00
if ( new_column < 0 )
new_column = 0 ;
2021-05-23 15:47:41 +02:00
set_cursor ( cursor_row ( ) , new_column ) ;
2019-08-12 17:32:16 +02:00
}
2021-05-29 10:48:02 +02:00
void Terminal : : CNL ( Parameters params )
{
unsigned num = 1 ;
if ( params . size ( ) > = 1 & & params [ 0 ] ! = 0 )
num = params [ 0 ] ;
unsigned new_row = cursor_row ( ) + num ;
if ( new_row > = m_columns )
new_row = m_columns - 1 ;
set_cursor ( new_row , 0 ) ;
}
void Terminal : : CPL ( Parameters params )
{
unsigned num = 1 ;
if ( params . size ( ) > = 1 & & params [ 0 ] ! = 0 )
num = params [ 0 ] ;
int new_row = ( int ) cursor_row ( ) - num ;
if ( new_row < 0 )
new_row = 0 ;
set_cursor ( new_row , 0 ) ;
}
2021-05-08 20:37:43 +02:00
void Terminal : : CHA ( Parameters params )
2019-08-12 17:32:16 +02:00
{
2021-05-23 15:47:41 +02:00
unsigned new_column = 1 ;
if ( params . size ( ) > = 1 & & params [ 0 ] ! = 0 )
2021-05-29 10:48:02 +02:00
new_column = params [ 0 ] ;
if ( new_column > m_columns )
new_column = m_columns ;
set_cursor ( cursor_row ( ) , new_column - 1 ) ;
2019-08-12 17:32:16 +02:00
}
2021-05-08 20:37:43 +02:00
void Terminal : : REP ( Parameters params )
2019-08-12 17:32:16 +02:00
{
2021-05-23 15:47:41 +02:00
unsigned count = 1 ;
if ( params . size ( ) > = 1 & & params [ 0 ] ! = 0 )
count = params [ 0 ] ;
2019-08-12 17:32:16 +02:00
2021-05-23 15:47:41 +02:00
for ( unsigned i = 0 ; i < count ; + + i )
put_character_at ( m_current_state . cursor . row , m_current_state . cursor . column + + , m_last_code_point ) ;
2019-08-12 17:32:16 +02:00
}
2021-05-08 20:37:43 +02:00
void Terminal : : VPA ( Parameters params )
2019-08-12 17:32:16 +02:00
{
2021-05-23 15:47:41 +02:00
unsigned new_row = 1 ;
if ( params . size ( ) > = 1 & & params [ 0 ] ! = 0 )
2021-05-29 10:48:02 +02:00
new_row = params [ 0 ] ;
if ( new_row > m_rows )
new_row = m_rows ;
set_cursor ( new_row - 1 , cursor_column ( ) ) ;
}
void Terminal : : VPR ( Parameters params )
{
unsigned num = 1 ;
if ( params . size ( ) > = 1 & & params [ 0 ] ! = 0 )
num = params [ 0 ] ;
int new_row = cursor_row ( ) + num ;
if ( new_row > = m_rows )
new_row = m_rows - 1 ;
2021-05-23 15:47:41 +02:00
set_cursor ( new_row , cursor_column ( ) ) ;
2019-08-12 17:32:16 +02:00
}
2021-05-29 10:48:02 +02:00
void Terminal : : HPA ( Parameters params )
{
unsigned new_column = 1 ;
if ( params . size ( ) > = 1 & & params [ 0 ] ! = 0 )
new_column = params [ 0 ] ;
if ( new_column > m_columns )
new_column = m_columns ;
set_cursor ( cursor_row ( ) , new_column - 1 ) ;
}
void Terminal : : HPR ( Parameters params )
{
unsigned num = 1 ;
if ( params . size ( ) > = 1 & & params [ 0 ] ! = 0 )
num = params [ 0 ] ;
unsigned new_column = cursor_column ( ) + num ;
if ( new_column > = m_columns )
new_column = m_columns - 1 ;
set_cursor ( cursor_row ( ) , new_column ) ;
}
2021-05-08 20:37:43 +02:00
void Terminal : : ECH ( Parameters params )
2019-08-12 17:32:16 +02:00
{
// Erase characters (without moving cursor)
2021-05-23 15:47:41 +02:00
unsigned num = 1 ;
if ( params . size ( ) > = 1 & & params [ 0 ] ! = 0 )
2019-08-12 17:32:16 +02:00
num = params [ 0 ] ;
2021-06-05 07:23:01 +02:00
// Clear num characters from the right of the cursor.
auto clear_end = min < unsigned > ( m_columns , cursor_column ( ) + num - 1 ) ;
dbgln_if ( TERMINAL_DEBUG , " Erase characters {}-{} on line {} " , cursor_column ( ) , clear_end , cursor_row ( ) ) ;
2021-06-05 11:12:00 +02:00
clear_in_line ( cursor_row ( ) , cursor_column ( ) , clear_end ) ;
2019-08-12 17:32:16 +02:00
}
2021-05-08 20:37:43 +02:00
void Terminal : : EL ( Parameters params )
2019-08-12 17:32:16 +02:00
{
2021-05-23 15:47:41 +02:00
unsigned mode = 0 ;
2019-08-12 17:32:16 +02:00
if ( params . size ( ) > = 1 )
mode = params [ 0 ] ;
switch ( mode ) {
case 0 :
2021-06-05 07:23:01 +02:00
dbgln_if ( TERMINAL_DEBUG , " Clear line {} from cursor column ({}) to the end " , cursor_row ( ) , cursor_column ( ) ) ;
2021-06-05 11:12:00 +02:00
clear_in_line ( cursor_row ( ) , cursor_column ( ) , m_columns - 1 ) ;
2019-08-12 17:32:16 +02:00
break ;
case 1 :
2021-06-05 07:23:01 +02:00
dbgln_if ( TERMINAL_DEBUG , " Clear line {} from the start to cursor column ({}) " , cursor_row ( ) , cursor_column ( ) ) ;
2021-06-05 11:12:00 +02:00
clear_in_line ( cursor_row ( ) , 0 , cursor_column ( ) ) ;
2019-08-12 17:32:16 +02:00
break ;
case 2 :
2021-06-05 07:23:01 +02:00
dbgln_if ( TERMINAL_DEBUG , " Clear line {} completely " , cursor_row ( ) ) ;
2021-06-05 11:12:00 +02:00
clear_in_line ( cursor_row ( ) , 0 , m_columns - 1 ) ;
2019-08-12 17:32:16 +02:00
break ;
default :
2021-05-08 20:37:43 +02:00
unimplemented_csi_sequence ( params , { } , ' K ' ) ;
2019-08-12 17:32:16 +02:00
break ;
}
}
2021-05-08 20:37:43 +02:00
void Terminal : : ED ( Parameters params )
2019-08-12 17:32:16 +02:00
{
2021-05-23 15:47:41 +02:00
unsigned mode = 0 ;
2019-08-12 17:32:16 +02:00
if ( params . size ( ) > = 1 )
mode = params [ 0 ] ;
switch ( mode ) {
case 0 :
2021-06-05 07:23:01 +02:00
dbgln_if ( TERMINAL_DEBUG , " Clear from cursor ({},{}) to end of screen " , cursor_row ( ) , cursor_column ( ) ) ;
2021-06-05 11:12:00 +02:00
clear_in_line ( cursor_row ( ) , cursor_column ( ) , m_columns - 1 ) ;
for ( int row = cursor_row ( ) + 1 ; row < m_rows ; + + row )
clear_in_line ( row , 0 , m_columns - 1 ) ;
2019-08-12 17:32:16 +02:00
break ;
case 1 :
2021-06-05 07:23:01 +02:00
dbgln_if ( TERMINAL_DEBUG , " Clear from beginning of screen to cursor ({},{}) " , cursor_row ( ) , cursor_column ( ) ) ;
2021-06-05 11:12:00 +02:00
clear_in_line ( cursor_row ( ) , 0 , cursor_column ( ) ) ;
for ( int row = cursor_row ( ) - 1 ; row > = 0 ; - - row )
clear_in_line ( row , 0 , m_columns - 1 ) ;
2019-08-12 17:32:16 +02:00
break ;
case 2 :
clear ( ) ;
break ;
case 3 :
2021-06-05 09:29:49 +02:00
clear_history ( ) ;
2019-08-12 17:32:16 +02:00
break ;
default :
2021-05-08 20:37:43 +02:00
unimplemented_csi_sequence ( params , { } , ' J ' ) ;
2019-08-12 17:32:16 +02:00
break ;
}
}
2021-05-08 20:37:43 +02:00
void Terminal : : SU ( Parameters params )
2019-08-12 17:32:16 +02:00
{
2021-05-23 15:57:21 +02:00
unsigned count = 1 ;
if ( params . size ( ) > = 1 & & params [ 0 ] ! = 0 )
2019-08-12 17:32:16 +02:00
count = params [ 0 ] ;
2021-06-02 15:36:31 +02:00
scroll_up ( count ) ;
2019-08-12 17:32:16 +02:00
}
2021-05-08 20:37:43 +02:00
void Terminal : : SD ( Parameters params )
2019-08-12 17:32:16 +02:00
{
2021-05-23 15:57:21 +02:00
unsigned count = 1 ;
if ( params . size ( ) > = 1 & & params [ 0 ] ! = 0 )
2019-08-12 17:32:16 +02:00
count = params [ 0 ] ;
2021-06-02 15:36:31 +02:00
scroll_down ( count ) ;
2019-08-12 17:32:16 +02:00
}
2021-05-24 09:36:41 +02:00
void Terminal : : DECSCUSR ( Parameters params )
{
unsigned style = 1 ;
if ( params . size ( ) > = 1 & & params [ 0 ] ! = 0 )
style = params [ 0 ] ;
switch ( style ) {
case 1 :
2022-05-12 22:52:14 +02:00
m_client . set_cursor_shape ( VT : : CursorShape : : Block ) ;
m_client . set_cursor_blinking ( true ) ;
2021-05-24 09:36:41 +02:00
break ;
case 2 :
2022-05-12 22:52:14 +02:00
m_client . set_cursor_shape ( VT : : CursorShape : : Block ) ;
m_client . set_cursor_blinking ( false ) ;
2021-05-24 09:36:41 +02:00
break ;
case 3 :
2022-05-12 22:52:14 +02:00
m_client . set_cursor_shape ( VT : : CursorShape : : Underline ) ;
m_client . set_cursor_blinking ( true ) ;
2021-05-24 09:36:41 +02:00
break ;
case 4 :
2022-05-12 22:52:14 +02:00
m_client . set_cursor_shape ( VT : : CursorShape : : Underline ) ;
m_client . set_cursor_blinking ( false ) ;
2021-05-24 09:36:41 +02:00
break ;
case 5 :
2022-05-12 22:52:14 +02:00
m_client . set_cursor_shape ( VT : : CursorShape : : Bar ) ;
m_client . set_cursor_blinking ( true ) ;
2021-05-24 09:36:41 +02:00
break ;
case 6 :
2022-05-12 22:52:14 +02:00
m_client . set_cursor_shape ( VT : : CursorShape : : Bar ) ;
m_client . set_cursor_blinking ( false ) ;
2021-05-24 09:36:41 +02:00
break ;
default :
dbgln ( " Unknown cursor style {} " , style ) ;
}
}
2021-05-08 20:37:43 +02:00
void Terminal : : IL ( Parameters params )
2019-08-12 17:32:16 +02:00
{
2021-06-02 15:36:31 +02:00
size_t count = 1 ;
2021-05-23 15:47:41 +02:00
if ( params . size ( ) > = 1 & & params [ 0 ] ! = 0 )
2019-08-12 17:32:16 +02:00
count = params [ 0 ] ;
2021-06-02 15:36:31 +02:00
if ( ! is_within_scroll_region ( cursor_row ( ) ) ) {
dbgln ( " Shenanigans! Tried to insert line outside the scroll region " ) ;
return ;
2019-08-12 17:32:16 +02:00
}
2021-06-02 15:36:31 +02:00
scroll_down ( cursor_row ( ) , m_scroll_region_bottom , count ) ;
2019-08-12 17:32:16 +02:00
}
2021-05-08 20:37:43 +02:00
void Terminal : : DA ( Parameters )
2020-01-25 19:12:08 +01:00
{
2022-07-11 17:32:29 +00:00
emit_string ( " \033 [?1;0c " sv ) ;
2020-01-25 19:12:08 +01:00
}
2021-05-08 20:37:43 +02:00
void Terminal : : DL ( Parameters params )
2019-08-12 17:32:16 +02:00
{
2021-06-02 15:36:31 +02:00
size_t count = 1 ;
2021-05-23 15:47:41 +02:00
if ( params . size ( ) > = 1 & & params [ 0 ] ! = 0 )
2019-08-12 17:32:16 +02:00
count = params [ 0 ] ;
2021-06-02 15:36:31 +02:00
if ( ! is_within_scroll_region ( cursor_row ( ) ) ) {
dbgln ( " Shenanigans! Tried to delete line outside the scroll region " ) ;
2019-08-12 17:32:16 +02:00
return ;
}
2021-06-02 15:36:31 +02:00
scroll_up ( cursor_row ( ) , m_scroll_region_bottom , count ) ;
2019-08-12 17:32:16 +02:00
}
2021-05-08 20:37:43 +02:00
void Terminal : : DCH ( Parameters params )
2019-08-12 17:32:16 +02:00
{
int num = 1 ;
2021-05-23 15:47:41 +02:00
if ( params . size ( ) > = 1 & & params [ 0 ] ! = 0 )
2019-08-12 17:32:16 +02:00
num = params [ 0 ] ;
2021-06-05 15:11:45 +02:00
num = min < int > ( num , columns ( ) - cursor_column ( ) ) ;
scroll_left ( cursor_row ( ) , cursor_column ( ) , num ) ;
2019-08-12 17:32:16 +02:00
}
2021-05-17 16:25:35 +02:00
void Terminal : : linefeed ( )
2019-08-12 17:32:16 +02:00
{
2021-05-23 15:47:41 +02:00
u16 new_row = cursor_row ( ) ;
2021-06-19 18:17:18 +04:30
# ifndef KERNEL
if ( ! m_controls_are_logically_generated )
2023-03-06 17:16:25 +01:00
active_buffer ( ) [ new_row ] - > set_terminated ( m_column_before_carriage_return . value_or ( cursor_column ( ) ) ) ;
2021-06-19 18:17:18 +04:30
# endif
2021-05-23 15:47:41 +02:00
if ( cursor_row ( ) = = m_scroll_region_bottom ) {
2019-08-12 17:32:16 +02:00
scroll_up ( ) ;
} else {
+ + new_row ;
2021-05-08 20:37:43 +02:00
} ;
2021-05-17 16:25:35 +02:00
// We shouldn't jump to the first column after receiving a line feed.
// The TTY will take care of generating the carriage return.
2021-05-23 15:47:41 +02:00
set_cursor ( new_row , cursor_column ( ) ) ;
2019-08-12 17:32:16 +02:00
}
2021-05-17 16:25:35 +02:00
2021-05-08 20:37:43 +02:00
void Terminal : : carriage_return ( )
{
2021-06-02 15:36:31 +02:00
dbgln_if ( TERMINAL_DEBUG , " Carriage return " ) ;
2021-06-19 18:17:18 +04:30
m_column_before_carriage_return = cursor_column ( ) ;
2021-05-23 15:47:41 +02:00
set_cursor ( cursor_row ( ) , 0 ) ;
2021-05-08 20:37:43 +02:00
}
2019-08-12 17:32:16 +02:00
2021-06-02 15:36:31 +02:00
void Terminal : : scroll_up ( size_t count )
2019-08-12 17:32:16 +02:00
{
2021-06-02 15:36:31 +02:00
scroll_up ( m_scroll_region_top , m_scroll_region_bottom , count ) ;
}
void Terminal : : scroll_down ( size_t count )
{
scroll_down ( m_scroll_region_top , m_scroll_region_bottom , count ) ;
}
# ifndef KERNEL
// Insert `count` blank lines at the bottom of the region. Text moves up, top lines get added to the scrollback.
void Terminal : : scroll_up ( u16 region_top , u16 region_bottom , size_t count )
{
VERIFY ( region_top < = region_bottom ) ;
VERIFY ( region_bottom < rows ( ) ) ;
// Only the specified region should be affected.
size_t region_size = region_bottom - region_top + 1 ;
count = min ( count , region_size ) ;
dbgln_if ( TERMINAL_DEBUG , " Scroll up {} lines in region {}-{} " , count , region_top , region_bottom ) ;
2019-08-12 17:32:16 +02:00
// NOTE: We have to invalidate the cursor first.
invalidate_cursor ( ) ;
2021-06-02 15:36:31 +02:00
2021-06-05 16:46:33 +02:00
int history_delta = - count ;
2021-06-05 08:55:33 +02:00
bool should_move_to_scrollback = ! m_use_alternate_screen_buffer & & max_history_size ( ) ! = 0 ;
if ( should_move_to_scrollback ) {
2021-06-05 16:46:33 +02:00
auto remaining_lines = max_history_size ( ) - history_size ( ) ;
history_delta = ( count > remaining_lines ) ? remaining_lines - count : 0 ;
2021-06-02 15:36:31 +02:00
for ( size_t i = 0 ; i < count ; + + i )
2023-03-06 17:16:25 +01:00
add_line_to_history ( move ( active_buffer ( ) . at ( region_top + i ) ) ) ;
2021-06-05 08:55:33 +02:00
}
2021-06-02 15:36:31 +02:00
2021-06-05 08:55:33 +02:00
// Move lines into their new place.
for ( u16 row = region_top ; row + count < = region_bottom ; + + row )
2023-03-06 17:16:25 +01:00
swap ( active_buffer ( ) . at ( row ) , active_buffer ( ) . at ( row + count ) ) ;
2021-06-05 08:55:33 +02:00
// Clear 'new' lines at the bottom.
if ( should_move_to_scrollback ) {
// Since we moved the previous lines into history, we can't just clear them.
for ( u16 row = region_bottom + 1 - count ; row < = region_bottom ; + + row )
2023-03-06 17:16:25 +01:00
active_buffer ( ) . at ( row ) = make < Line > ( columns ( ) ) ;
2021-06-05 08:55:33 +02:00
} else {
// The new lines haven't been moved and we don't want to leak memory.
for ( u16 row = region_bottom + 1 - count ; row < = region_bottom ; + + row )
2023-03-06 17:16:25 +01:00
active_buffer ( ) [ row ] - > clear ( ) ;
2019-08-19 19:07:52 +02:00
}
2021-06-05 08:55:33 +02:00
// Set dirty flag on swapped lines.
// The other lines have implicitly been set dirty by being cleared.
2022-11-25 22:51:42 +01:00
for ( u16 row = region_top ; row + count < = region_bottom ; + + row )
2023-03-06 17:16:25 +01:00
active_buffer ( ) [ row ] - > set_dirty ( true ) ;
2021-06-05 16:46:33 +02:00
m_client . terminal_history_changed ( history_delta ) ;
2019-08-12 17:32:16 +02:00
}
2021-06-02 15:36:31 +02:00
// Insert `count` blank lines at the top of the region. Text moves down. Does not affect the scrollback buffer.
void Terminal : : scroll_down ( u16 region_top , u16 region_bottom , size_t count )
2019-08-12 17:32:16 +02:00
{
2021-06-02 15:36:31 +02:00
VERIFY ( region_top < = region_bottom ) ;
VERIFY ( region_bottom < rows ( ) ) ;
// Only the specified region should be affected.
size_t region_size = region_bottom - region_top + 1 ;
count = min ( count , region_size ) ;
dbgln_if ( TERMINAL_DEBUG , " Scroll down {} lines in region {}-{} " , count , region_top , region_bottom ) ;
2019-08-12 17:32:16 +02:00
// NOTE: We have to invalidate the cursor first.
invalidate_cursor ( ) ;
2021-06-02 15:36:31 +02:00
2021-06-05 08:55:33 +02:00
// Move lines into their new place.
for ( int row = region_bottom ; row > = static_cast < int > ( region_top + count ) ; - - row )
2023-03-06 17:16:25 +01:00
swap ( active_buffer ( ) . at ( row ) , active_buffer ( ) . at ( row - count ) ) ;
2021-06-05 08:55:33 +02:00
// Clear the 'new' lines at the top.
for ( u16 row = region_top ; row < region_top + count ; + + row )
2023-03-06 17:16:25 +01:00
active_buffer ( ) [ row ] - > clear ( ) ;
2021-06-05 08:55:33 +02:00
// Set dirty flag on swapped lines.
// The other lines have implicitly been set dirty by being cleared.
for ( u16 row = region_top + count ; row < = region_bottom ; + + row )
2023-03-06 17:16:25 +01:00
active_buffer ( ) [ row ] - > set_dirty ( true ) ;
2019-08-12 17:32:16 +02:00
}
2021-06-05 15:11:45 +02:00
// Insert `count` blank cells at the end of the line. Text moves left.
void Terminal : : scroll_left ( u16 row , u16 column , size_t count )
{
VERIFY ( row < rows ( ) ) ;
VERIFY ( column < columns ( ) ) ;
count = min < size_t > ( count , columns ( ) - column ) ;
dbgln_if ( TERMINAL_DEBUG , " Scroll left {} columns from line {} column {} " , count , row , column ) ;
auto & line = active_buffer ( ) [ row ] ;
for ( size_t i = column ; i < columns ( ) - count ; + + i )
2023-03-06 17:16:25 +01:00
swap ( line - > cell_at ( i ) , line - > cell_at ( i + count ) ) ;
2021-06-05 15:11:45 +02:00
clear_in_line ( row , columns ( ) - count , columns ( ) - 1 ) ;
2023-03-06 17:16:25 +01:00
line - > set_dirty ( true ) ;
2021-06-05 15:11:45 +02:00
}
// Insert `count` blank cells after `row`. Text moves right.
void Terminal : : scroll_right ( u16 row , u16 column , size_t count )
{
VERIFY ( row < rows ( ) ) ;
VERIFY ( column < columns ( ) ) ;
count = min < size_t > ( count , columns ( ) - column ) ;
dbgln_if ( TERMINAL_DEBUG , " Scroll right {} columns from line {} column {} " , count , row , column ) ;
auto & line = active_buffer ( ) [ row ] ;
for ( int i = columns ( ) - 1 ; i > = static_cast < int > ( column + count ) ; - - i )
2023-03-06 17:16:25 +01:00
swap ( line - > cell_at ( i ) , line - > cell_at ( i - count ) ) ;
2021-06-05 15:11:45 +02:00
clear_in_line ( row , column , column + count - 1 ) ;
2023-03-06 17:16:25 +01:00
line - > set_dirty ( true ) ;
2021-06-05 15:11:45 +02:00
}
2021-04-16 22:58:51 +03:00
void Terminal : : put_character_at ( unsigned row , unsigned column , u32 code_point )
{
VERIFY ( row < rows ( ) ) ;
VERIFY ( column < columns ( ) ) ;
2021-05-23 15:47:41 +02:00
auto & line = active_buffer ( ) [ row ] ;
2023-03-06 17:16:25 +01:00
line - > set_code_point ( column , code_point ) ;
line - > attribute_at ( column ) = m_current_state . attribute ;
line - > attribute_at ( column ) . flags | = Attribute : : Flags : : Touched ;
line - > set_dirty ( true ) ;
2021-04-16 22:58:51 +03:00
m_last_code_point = code_point ;
}
2021-06-05 11:12:00 +02:00
void Terminal : : clear_in_line ( u16 row , u16 first_column , u16 last_column )
{
VERIFY ( row < rows ( ) ) ;
2023-03-06 17:16:25 +01:00
active_buffer ( ) [ row ] - > clear_range ( first_column , last_column , m_current_state . attribute ) ;
2021-06-05 11:12:00 +02:00
}
2021-04-16 22:58:51 +03:00
# endif
2021-05-23 15:47:41 +02:00
void Terminal : : set_cursor ( unsigned a_row , unsigned a_column , bool skip_debug )
2019-08-12 17:32:16 +02:00
{
unsigned row = min ( a_row , m_rows - 1u ) ;
unsigned column = min ( a_column , m_columns - 1u ) ;
2021-12-28 13:28:21 +01:00
m_stomp = false ;
2021-05-23 15:47:41 +02:00
if ( row = = cursor_row ( ) & & column = = cursor_column ( ) )
2019-08-12 17:32:16 +02:00
return ;
2021-02-23 20:42:32 +01:00
VERIFY ( row < rows ( ) ) ;
VERIFY ( column < columns ( ) ) ;
2019-08-12 17:32:16 +02:00
invalidate_cursor ( ) ;
2021-05-23 15:47:41 +02:00
m_current_state . cursor . row = row ;
m_current_state . cursor . column = column ;
2019-08-12 17:32:16 +02:00
invalidate_cursor ( ) ;
2021-05-23 15:47:41 +02:00
if ( ! skip_debug )
dbgln_if ( TERMINAL_DEBUG , " Set cursor position: {},{} " , cursor_row ( ) , cursor_column ( ) ) ;
2019-08-12 17:32:16 +02:00
}
2020-01-25 20:01:43 +01:00
void Terminal : : NEL ( )
{
2021-05-23 15:57:21 +02:00
if ( cursor_row ( ) = = m_scroll_region_bottom )
scroll_up ( ) ;
else
set_cursor ( cursor_row ( ) + 1 , 0 ) ;
2020-01-25 20:01:43 +01:00
}
2020-01-25 20:17:50 +01:00
void Terminal : : IND ( )
{
2021-05-23 15:57:21 +02:00
// Not equivalent to CUD: if we are at the bottom margin, we have to scroll up.
if ( cursor_row ( ) = = m_scroll_region_bottom )
scroll_up ( ) ;
else
set_cursor ( cursor_row ( ) + 1 , cursor_column ( ) ) ;
2020-01-25 20:17:50 +01:00
}
void Terminal : : RI ( )
{
2021-05-23 15:57:21 +02:00
// Not equivalent to CUU : if we at the top margin , we have to scroll down.
if ( cursor_row ( ) = = m_scroll_region_top )
scroll_down ( ) ;
else
set_cursor ( cursor_row ( ) - 1 , cursor_column ( ) ) ;
2020-01-25 20:17:50 +01:00
}
2021-06-05 15:35:14 +02:00
void Terminal : : DECFI ( )
{
if ( cursor_column ( ) = = columns ( ) - 1 )
scroll_left ( cursor_row ( ) , 0 , 1 ) ;
else
set_cursor ( cursor_row ( ) , cursor_column ( ) + 1 ) ;
}
void Terminal : : DECBI ( )
{
if ( cursor_column ( ) = = 0 )
scroll_right ( cursor_row ( ) , 0 , 1 ) ;
else
set_cursor ( cursor_row ( ) , cursor_column ( ) - 1 ) ;
}
2021-06-09 21:50:44 +02:00
void Terminal : : DECIC ( Parameters params )
{
unsigned num = 1 ;
if ( params . size ( ) > = 1 & & params [ 0 ] ! = 0 )
num = params [ 0 ] ;
num = min < unsigned > ( num , columns ( ) - cursor_column ( ) ) ;
for ( unsigned row = cursor_row ( ) ; row < = m_scroll_region_bottom ; + + row )
scroll_right ( row , cursor_column ( ) , num ) ;
}
void Terminal : : DECDC ( Parameters params )
{
unsigned num = 1 ;
if ( params . size ( ) > = 1 & & params [ 0 ] ! = 0 )
num = params [ 0 ] ;
num = min < unsigned > ( num , columns ( ) - cursor_column ( ) ) ;
for ( unsigned row = cursor_row ( ) ; row < = m_scroll_region_bottom ; + + row )
scroll_left ( row , cursor_column ( ) , num ) ;
}
2023-12-05 04:40:57 +07:00
void Terminal : : DECKPNM ( )
2021-09-30 11:43:17 +02:00
{
dbgln ( " FIXME: implement setting the keypad to numeric mode " ) ;
}
2023-12-05 04:40:57 +07:00
void Terminal : : DECKPAM ( )
2021-09-30 11:43:17 +02:00
{
dbgln ( " FIXME: implement setting the keypad to application mode " ) ;
}
2021-05-08 20:37:43 +02:00
void Terminal : : DSR ( Parameters params )
2020-04-09 07:43:12 +04:30
{
2020-05-09 10:38:57 +02:00
if ( params . size ( ) = = 1 & & params [ 0 ] = = 5 ) {
// Device status
2022-07-11 17:32:29 +00:00
emit_string ( " \033 [0n " sv ) ; // Terminal status OK!
2020-05-09 10:38:57 +02:00
} else if ( params . size ( ) = = 1 & & params [ 0 ] = = 6 ) {
// Cursor position query
2022-02-15 21:12:30 +02:00
StringBuilder builder ;
MUST ( builder . try_appendff ( " \ e[{};{}R " , cursor_row ( ) + 1 , cursor_column ( ) + 1 ) ) ; // StringBuilder's inline capacity of 256 is enough to guarantee no allocations
emit_string ( builder . string_view ( ) ) ;
2020-05-09 10:38:57 +02:00
} else {
2021-01-09 18:51:44 +01:00
dbgln ( " Unknown DSR " ) ;
2020-05-09 10:38:57 +02:00
}
2020-04-09 07:43:12 +04:30
}
2021-05-08 20:37:43 +02:00
void Terminal : : ICH ( Parameters params )
2021-01-08 01:33:17 -05:00
{
2021-05-23 15:47:41 +02:00
unsigned num = 1 ;
if ( params . size ( ) > = 1 & & params [ 0 ] ! = 0 )
2021-01-08 01:33:17 -05:00
num = params [ 0 ] ;
2021-06-05 15:11:45 +02:00
num = min < unsigned > ( num , columns ( ) - cursor_column ( ) ) ;
scroll_right ( cursor_row ( ) , cursor_column ( ) , num ) ;
2021-01-08 01:33:17 -05:00
}
2021-05-08 20:37:43 +02:00
void Terminal : : on_input ( u8 byte )
2019-08-12 17:32:16 +02:00
{
2021-05-08 20:37:43 +02:00
m_parser . on_input ( byte ) ;
}
2020-05-16 19:47:49 +02:00
2021-05-08 20:37:43 +02:00
void Terminal : : emit_code_point ( u32 code_point )
{
2021-09-30 13:58:12 +02:00
auto working_set = m_working_sets [ m_active_working_set_index ] ;
code_point = m_character_set_translator . translate_code_point ( working_set , code_point ) ;
2021-05-23 15:47:41 +02:00
auto new_column = cursor_column ( ) + 1 ;
2021-05-08 20:37:43 +02:00
if ( new_column < columns ( ) ) {
2021-05-23 15:47:41 +02:00
put_character_at ( cursor_row ( ) , cursor_column ( ) , code_point ) ;
set_cursor ( cursor_row ( ) , new_column , true ) ;
2020-05-16 19:47:49 +02:00
return ;
2019-08-12 17:32:16 +02:00
}
2021-05-08 20:37:43 +02:00
if ( m_stomp ) {
m_stomp = false ;
2021-06-19 18:17:18 +04:30
TemporaryChange change { m_controls_are_logically_generated , true } ;
2021-05-08 20:37:43 +02:00
carriage_return ( ) ;
2021-05-17 16:25:35 +02:00
linefeed ( ) ;
2021-05-23 15:47:41 +02:00
put_character_at ( cursor_row ( ) , cursor_column ( ) , code_point ) ;
set_cursor ( cursor_row ( ) , 1 ) ;
2021-05-08 20:37:43 +02:00
} else {
// Curious: We wait once on the right-hand side
m_stomp = true ;
2021-05-23 15:47:41 +02:00
put_character_at ( cursor_row ( ) , cursor_column ( ) , code_point ) ;
2021-05-08 20:37:43 +02:00
}
}
2019-08-12 17:32:16 +02:00
2021-05-08 20:37:43 +02:00
void Terminal : : execute_control_code ( u8 code )
{
2021-06-19 18:17:18 +04:30
ArmedScopeGuard clear_position_before_cr {
[ & ] {
m_column_before_carriage_return . clear ( ) ;
}
} ;
2021-05-08 20:37:43 +02:00
switch ( code ) {
case ' \a ' :
m_client . beep ( ) ;
2019-08-12 17:32:16 +02:00
return ;
2021-05-08 20:37:43 +02:00
case ' \b ' :
2021-05-23 15:47:41 +02:00
if ( cursor_column ( ) ) {
set_cursor ( cursor_row ( ) , cursor_column ( ) - 1 ) ;
2019-08-12 17:32:16 +02:00
return ;
}
return ;
case ' \t ' : {
2021-05-23 15:47:41 +02:00
for ( unsigned i = cursor_column ( ) + 1 ; i < columns ( ) ; + + i ) {
2019-08-12 17:32:16 +02:00
if ( m_horizontal_tabs [ i ] ) {
2021-05-23 15:47:41 +02:00
set_cursor ( cursor_row ( ) , i ) ;
2019-08-12 17:32:16 +02:00
return ;
}
}
return ;
}
case ' \n ' :
2021-05-17 16:25:35 +02:00
case ' \v ' :
case ' \f ' :
2021-06-19 18:17:18 +04:30
if ( m_column_before_carriage_return = = m_columns - 1 )
m_column_before_carriage_return = m_columns ;
2021-05-17 16:25:35 +02:00
linefeed ( ) ;
2019-08-12 17:32:16 +02:00
return ;
2021-05-08 20:37:43 +02:00
case ' \r ' :
carriage_return ( ) ;
2021-06-19 18:17:18 +04:30
clear_position_before_cr . disarm ( ) ;
2021-05-08 20:37:43 +02:00
return ;
default :
unimplemented_control_code ( code ) ;
2019-08-12 17:32:16 +02:00
}
2021-05-08 20:37:43 +02:00
}
2019-08-12 17:32:16 +02:00
2021-05-08 20:37:43 +02:00
void Terminal : : execute_escape_sequence ( Intermediates intermediates , bool ignore , u8 last_byte )
{
// FIXME: Handle it somehow?
if ( ignore )
dbgln ( " Escape sequence has its ignore flag set. " ) ;
if ( intermediates . size ( ) = = 0 ) {
switch ( last_byte ) {
case ' D ' :
IND ( ) ;
return ;
case ' E ' :
NEL ( ) ;
return ;
case ' M ' :
RI ( ) ;
return ;
2021-05-16 13:14:37 +00:00
case ' \\ ' :
// ST (string terminator) -- do nothing
2021-05-16 15:16:50 +01:00
return ;
2021-06-05 15:35:14 +02:00
case ' 6 ' :
DECBI ( ) ;
return ;
2021-05-23 15:47:41 +02:00
case ' 7 ' :
DECSC ( ) ;
return ;
case ' 8 ' :
DECRC ( ) ;
return ;
2021-06-05 15:35:14 +02:00
case ' 9 ' :
DECFI ( ) ;
return ;
2021-09-30 11:43:17 +02:00
case ' = ' :
2023-12-05 04:40:57 +07:00
DECKPAM ( ) ;
2021-09-30 11:43:17 +02:00
return ;
case ' > ' :
2023-12-05 04:40:57 +07:00
DECKPNM ( ) ;
2021-09-30 11:43:17 +02:00
return ;
2021-05-08 20:37:43 +02:00
}
2021-09-30 13:58:12 +02:00
unimplemented_escape_sequence ( intermediates , last_byte ) ;
return ;
}
char intermediate = intermediates [ 0 ] ;
switch ( intermediate ) {
case ' # ' :
2021-05-08 20:37:43 +02:00
switch ( last_byte ) {
case ' 8 ' :
// Confidence Test - Fill screen with E's
for ( size_t row = 0 ; row < m_rows ; + + row ) {
for ( size_t column = 0 ; column < m_columns ; + + column ) {
put_character_at ( row , column , ' E ' ) ;
}
}
return ;
}
2021-09-30 13:58:12 +02:00
break ;
case ' ( ' :
case ' ) ' :
case ' * ' :
case ' + ' :
// Determine G0..G3 index
size_t working_set_index = intermediate - ' ( ' ;
CharacterSet new_set ;
switch ( last_byte ) {
case ' B ' :
new_set = CharacterSet : : Iso_8859_1 ;
break ;
case ' 0 ' :
new_set = CharacterSet : : VT100 ;
break ;
case ' U ' :
new_set = CharacterSet : : Null ;
break ;
case ' K ' :
new_set = CharacterSet : : UserDefined ;
break ;
default :
unimplemented_escape_sequence ( intermediates , last_byte ) ;
return ;
}
dbgln_if ( TERMINAL_DEBUG , " Setting G{} working set to character set {} " , working_set_index , to_underlying ( new_set ) ) ;
VERIFY ( working_set_index < = 3 ) ;
m_working_sets [ working_set_index ] = new_set ;
return ;
2021-05-08 20:37:43 +02:00
}
2021-09-30 13:58:12 +02:00
2021-05-08 20:37:43 +02:00
unimplemented_escape_sequence ( intermediates , last_byte ) ;
2020-05-16 19:47:49 +02:00
}
2021-05-08 20:37:43 +02:00
void Terminal : : execute_csi_sequence ( Parameters parameters , Intermediates intermediates , bool ignore , u8 last_byte )
2020-05-16 19:47:49 +02:00
{
2021-05-08 20:37:43 +02:00
// FIXME: Handle it somehow?
if ( ignore )
dbgln ( " CSI sequence has its ignore flag set. " ) ;
2021-07-09 12:08:36 +02:00
if ( intermediates . is_empty ( ) ) {
switch ( last_byte ) {
case ' @ ' :
return ICH ( parameters ) ;
case ' A ' :
return CUU ( parameters ) ;
case ' B ' :
return CUD ( parameters ) ;
case ' C ' :
return CUF ( parameters ) ;
case ' D ' :
return CUB ( parameters ) ;
case ' E ' :
return CNL ( parameters ) ;
case ' F ' :
return CPL ( parameters ) ;
case ' G ' :
return CHA ( parameters ) ;
case ' H ' :
return CUP ( parameters ) ;
case ' J ' :
return ED ( parameters ) ;
case ' K ' :
return EL ( parameters ) ;
case ' L ' :
return IL ( parameters ) ;
case ' M ' :
return DL ( parameters ) ;
case ' P ' :
return DCH ( parameters ) ;
case ' S ' :
return SU ( parameters ) ;
case ' T ' :
return SD ( parameters ) ;
case ' X ' :
return ECH ( parameters ) ;
case ' ` ' :
return HPA ( parameters ) ;
case ' a ' :
return HPR ( parameters ) ;
case ' b ' :
return REP ( parameters ) ;
case ' c ' :
return DA ( parameters ) ;
case ' d ' :
return VPA ( parameters ) ;
case ' e ' :
return VPR ( parameters ) ;
case ' f ' :
return HVP ( parameters ) ;
case ' h ' :
return SM ( parameters ) ;
case ' l ' :
return RM ( parameters ) ;
case ' m ' :
return SGR ( parameters ) ;
case ' n ' :
return DSR ( parameters ) ;
case ' r ' :
return DECSTBM ( parameters ) ;
case ' s ' :
return SCOSC ( ) ;
case ' t ' :
return XTERM_WM ( parameters ) ;
case ' u ' :
return SCORC ( ) ;
}
} else if ( intermediates . size ( ) = = 1 & & intermediates [ 0 ] = = ' ? ' ) {
switch ( last_byte ) {
case ' h ' :
return DECSET ( parameters ) ;
case ' l ' :
return DECRST ( parameters ) ;
}
} else if ( intermediates . size ( ) = = 1 & & intermediates [ 0 ] = = ' \' ' ) {
switch ( last_byte ) {
case ' } ' :
return DECIC ( parameters ) ;
case ' ~ ' :
return DECDC ( parameters ) ;
}
} else if ( intermediates . size ( ) = = 1 & & intermediates [ 0 ] = = ' ' ) {
switch ( last_byte ) {
case ' q ' :
return DECSCUSR ( parameters ) ;
}
2020-05-16 19:47:49 +02:00
}
2021-07-09 12:08:36 +02:00
unimplemented_csi_sequence ( parameters , intermediates , last_byte ) ;
2021-05-08 20:37:43 +02:00
}
void Terminal : : execute_osc_sequence ( OscParameters parameters , u8 last_byte )
{
auto stringview_ify = [ & ] ( size_t param_idx ) {
2021-05-17 16:32:11 +02:00
return StringView ( parameters [ param_idx ] ) ;
2021-05-08 20:37:43 +02:00
} ;
2021-05-17 16:32:11 +02:00
if ( parameters . size ( ) = = 0 | | parameters [ 0 ] . is_empty ( ) ) {
unimplemented_osc_sequence ( parameters , last_byte ) ;
return ;
}
2023-12-23 15:59:14 +13:00
auto command_number = stringview_ify ( 0 ) . to_number < unsigned > ( ) ;
2021-05-17 16:32:11 +02:00
if ( ! command_number . has_value ( ) ) {
unimplemented_osc_sequence ( parameters , last_byte ) ;
return ;
}
switch ( command_number . value ( ) ) {
case 0 :
case 1 :
case 2 :
2021-05-24 10:40:00 +02:00
if ( parameters . size ( ) < 2 ) {
2021-05-17 16:32:11 +02:00
dbgln ( " Attempted to set window title without any parameters " ) ;
2021-05-24 10:40:00 +02:00
} else {
// FIXME: the split breaks titles containing semicolons.
// Should we expose the raw OSC string from the parser? Or join by semicolon?
2022-07-14 05:53:13 +03:00
# ifndef KERNEL
2023-12-16 17:49:34 +03:30
m_current_window_title = stringview_ify ( 1 ) . to_byte_string ( ) ;
2021-05-24 10:40:00 +02:00
m_client . set_window_title ( m_current_window_title ) ;
2022-02-15 22:46:49 +02:00
# endif
2021-05-24 10:40:00 +02:00
}
2021-05-17 16:32:11 +02:00
break ;
case 8 :
2021-04-16 22:58:51 +03:00
# ifndef KERNEL
2021-05-17 16:32:11 +02:00
if ( parameters . size ( ) < 3 ) {
dbgln ( " Attempted to set href but gave too few parameters " ) ;
} else if ( parameters [ 1 ] . is_empty ( ) & & parameters [ 2 ] . is_empty ( ) ) {
// Clear hyperlink
2023-10-16 21:31:15 +03:30
m_current_state . attribute . href = { } ;
m_current_state . attribute . href_id = { } ;
2021-05-08 20:37:43 +02:00
} else {
2021-05-23 15:47:41 +02:00
m_current_state . attribute . href = stringview_ify ( 2 ) ;
2021-05-17 16:32:11 +02:00
// FIXME: Respect the provided ID
2023-12-16 17:49:34 +03:30
m_current_state . attribute . href_id = ByteString : : number ( m_next_href_id + + ) ;
2021-05-08 20:37:43 +02:00
}
2021-05-17 16:32:11 +02:00
# endif
break ;
case 9 :
if ( parameters . size ( ) < 2 )
dbgln ( " Atttempted to set window progress but gave too few parameters " ) ;
else if ( parameters . size ( ) = = 2 )
2023-12-23 15:59:14 +13:00
m_client . set_window_progress ( stringview_ify ( 1 ) . to_number < int > ( ) . value_or ( - 1 ) , 0 ) ;
2021-05-17 16:32:11 +02:00
else
2023-12-23 15:59:14 +13:00
m_client . set_window_progress ( stringview_ify ( 1 ) . to_number < int > ( ) . value_or ( - 1 ) , stringview_ify ( 2 ) . to_number < int > ( ) . value_or ( 0 ) ) ;
2021-05-17 16:32:11 +02:00
break ;
default :
2021-05-08 20:37:43 +02:00
unimplemented_osc_sequence ( parameters , last_byte ) ;
2019-08-12 17:32:16 +02:00
}
}
2021-05-17 16:32:11 +02:00
void Terminal : : dcs_hook ( Parameters , Intermediates , bool , u8 )
2021-05-08 20:37:43 +02:00
{
dbgln ( " Received DCS parameters, but we don't support it yet " ) ;
}
void Terminal : : receive_dcs_char ( u8 byte )
{
dbgln_if ( TERMINAL_DEBUG , " DCS string character {:c} " , byte ) ;
}
void Terminal : : execute_dcs_sequence ( )
{
}
2021-11-11 00:55:02 +01:00
void Terminal : : inject_string ( StringView str )
2019-08-12 17:32:16 +02:00
{
2019-12-09 17:45:40 +01:00
for ( size_t i = 0 ; i < str . length ( ) ; + + i )
2020-05-16 19:24:24 +02:00
on_input ( str [ i ] ) ;
2019-08-12 17:32:16 +02:00
}
2021-11-11 00:55:02 +01:00
void Terminal : : emit_string ( StringView string )
2020-01-25 19:12:08 +01:00
{
2022-04-01 20:58:27 +03:00
m_client . emit ( ( u8 const * ) string . characters_without_null_termination ( ) , string . length ( ) ) ;
2020-01-25 19:12:08 +01:00
}
2020-06-13 13:56:39 +03:00
void Terminal : : handle_key_press ( KeyCode key , u32 code_point , u8 flags )
2020-05-27 00:31:30 +03:00
{
bool ctrl = flags & Mod_Ctrl ;
bool alt = flags & Mod_Alt ;
bool shift = flags & Mod_Shift ;
2020-09-13 21:08:19 -04:00
unsigned modifier_mask = int ( shift ) + ( int ( alt ) < < 1 ) + ( int ( ctrl ) < < 2 ) ;
auto emit_final_with_modifier = [ this , modifier_mask ] ( char final ) {
2021-09-30 11:44:20 +02:00
char escape_character = m_cursor_keys_mode = = CursorKeysMode : : Application ? ' O ' : ' [ ' ;
2022-02-15 21:12:30 +02:00
StringBuilder builder ;
2020-09-13 21:08:19 -04:00
if ( modifier_mask )
2022-02-15 21:12:30 +02:00
MUST ( builder . try_appendff ( " \ e{}1;{}{:c} " , escape_character , modifier_mask + 1 , final ) ) ; // StringBuilder's inline capacity of 256 is enough to guarantee no allocations
2020-09-13 21:08:19 -04:00
else
2022-02-15 21:12:30 +02:00
MUST ( builder . try_appendff ( " \ e{}{:c} " , escape_character , final ) ) ; // StringBuilder's inline capacity of 256 is enough to guarantee no allocations
emit_string ( builder . string_view ( ) ) ;
2020-09-13 21:08:19 -04:00
} ;
2020-09-13 21:31:59 -04:00
auto emit_tilde_with_modifier = [ this , modifier_mask ] ( unsigned num ) {
2022-02-15 21:12:30 +02:00
StringBuilder builder ;
2020-09-13 21:31:59 -04:00
if ( modifier_mask )
2022-02-15 21:12:30 +02:00
MUST ( builder . try_appendff ( " \ e[{};{}~ " , num , modifier_mask + 1 ) ) ; // StringBuilder's inline capacity of 256 is enough to guarantee no allocations
2020-09-13 21:31:59 -04:00
else
2022-02-15 21:12:30 +02:00
MUST ( builder . try_appendff ( " \ e[{}~ " , num ) ) ; // StringBuilder's inline capacity of 256 is enough to guarantee no allocations
emit_string ( builder . string_view ( ) ) ;
2020-09-13 21:31:59 -04:00
} ;
2020-05-27 00:31:30 +03:00
switch ( key ) {
case KeyCode : : Key_Up :
2020-09-13 21:08:19 -04:00
emit_final_with_modifier ( ' A ' ) ;
2020-05-27 00:31:30 +03:00
return ;
case KeyCode : : Key_Down :
2020-09-13 21:08:19 -04:00
emit_final_with_modifier ( ' B ' ) ;
2020-05-27 00:31:30 +03:00
return ;
case KeyCode : : Key_Right :
2020-09-13 21:08:19 -04:00
emit_final_with_modifier ( ' C ' ) ;
2020-05-27 00:31:30 +03:00
return ;
case KeyCode : : Key_Left :
2020-09-13 21:08:19 -04:00
emit_final_with_modifier ( ' D ' ) ;
2020-05-27 00:31:30 +03:00
return ;
case KeyCode : : Key_Insert :
2020-09-13 21:31:59 -04:00
emit_tilde_with_modifier ( 2 ) ;
2020-05-27 00:31:30 +03:00
return ;
case KeyCode : : Key_Delete :
2020-09-13 21:31:59 -04:00
emit_tilde_with_modifier ( 3 ) ;
2020-05-27 00:31:30 +03:00
return ;
case KeyCode : : Key_Home :
2020-09-13 21:08:19 -04:00
emit_final_with_modifier ( ' H ' ) ;
2020-05-27 00:31:30 +03:00
return ;
case KeyCode : : Key_End :
2020-09-13 21:08:19 -04:00
emit_final_with_modifier ( ' F ' ) ;
2020-05-27 00:31:30 +03:00
return ;
case KeyCode : : Key_PageUp :
2020-09-13 21:31:59 -04:00
emit_tilde_with_modifier ( 5 ) ;
2020-05-27 00:31:30 +03:00
return ;
case KeyCode : : Key_PageDown :
2020-09-13 21:31:59 -04:00
emit_tilde_with_modifier ( 6 ) ;
2020-05-27 00:31:30 +03:00
return ;
2022-11-16 04:40:11 -06:00
case KeyCode : : Key_Backspace :
if ( ctrl ) {
// This is an extension that allows Editor.cpp to delete whole words when
// Ctrl+Backspace is pressed. Ctrl cannot be transmitted without a CSI, and
// ANSI delete (127) is within the valid range for CSI codes in Editor.cpp.
// The code also has the same behavior as backspace when emitted with no CSI,
// though the backspace code (8) is preserved when Ctrl is not pressed.
emit_final_with_modifier ( 127 ) ;
return ;
}
break ;
2021-05-17 16:25:35 +02:00
case KeyCode : : Key_Return :
// The standard says that CR should be generated by the return key.
// The TTY will take care of translating it to CR LF for the terminal.
2022-07-11 17:32:29 +00:00
emit_string ( " \r " sv ) ;
2021-05-17 16:25:35 +02:00
return ;
2020-05-27 00:31:30 +03:00
default :
break ;
}
2020-06-13 13:56:39 +03:00
if ( ! code_point ) {
2020-05-29 22:13:51 +03:00
// Probably a modifier being pressed.
return ;
}
2020-05-27 00:31:30 +03:00
if ( shift & & key = = KeyCode : : Key_Tab ) {
2022-07-11 17:32:29 +00:00
emit_string ( " \033 [Z " sv ) ;
2020-05-27 00:31:30 +03:00
return ;
}
// Key event was not one of the above special cases,
// attempt to treat it as a character...
if ( ctrl ) {
2020-06-13 13:56:39 +03:00
if ( code_point > = ' a ' & & code_point < = ' z ' ) {
code_point = code_point - ' a ' + 1 ;
} else if ( code_point = = ' \\ ' ) {
code_point = 0x1c ;
2020-05-27 00:31:30 +03:00
}
}
// Alt modifier sends escape prefix.
if ( alt )
2022-07-11 17:32:29 +00:00
emit_string ( " \033 " sv ) ;
2020-05-27 00:31:30 +03:00
2020-06-13 13:56:39 +03:00
StringBuilder sb ;
2020-08-05 16:31:20 -04:00
sb . append_code_point ( code_point ) ;
2022-02-15 23:43:32 +02:00
emit_string ( sb . string_view ( ) ) ;
2020-05-27 00:31:30 +03:00
}
2021-05-08 20:37:43 +02:00
void Terminal : : unimplemented_control_code ( u8 code )
{
2021-07-03 17:08:37 +02:00
dbgln_if ( TERMINAL_DEBUG , " Unimplemented control code {:02x} " , code ) ;
2021-05-08 20:37:43 +02:00
}
void Terminal : : unimplemented_escape_sequence ( Intermediates intermediates , u8 last_byte )
2019-08-12 17:32:16 +02:00
{
StringBuilder builder ;
2021-05-08 20:37:43 +02:00
builder . appendff ( " Unimplemented escape sequence {:c} " , last_byte ) ;
if ( ! intermediates . is_empty ( ) ) {
2022-07-11 17:32:29 +00:00
builder . append ( " , intermediates: " sv ) ;
2021-05-08 20:37:43 +02:00
for ( size_t i = 0 ; i < intermediates . size ( ) ; + + i )
builder . append ( ( char ) intermediates [ i ] ) ;
2019-08-12 17:32:16 +02:00
}
2021-05-08 20:37:43 +02:00
dbgln ( " {} " , builder . string_view ( ) ) ;
}
void Terminal : : unimplemented_csi_sequence ( Parameters parameters , Intermediates intermediates , u8 last_byte )
{
StringBuilder builder ;
builder . appendff ( " Unimplemented CSI sequence: {:c} " , last_byte ) ;
if ( ! parameters . is_empty ( ) ) {
2022-07-11 17:32:29 +00:00
builder . append ( " , parameters: [ " sv ) ;
2021-05-08 20:37:43 +02:00
for ( size_t i = 0 ; i < parameters . size ( ) ; + + i )
builder . appendff ( " {}{} " , ( i = = 0 ) ? " " : " , " , parameters [ i ] ) ;
2022-07-11 20:10:18 +00:00
builder . append ( " ] " sv ) ;
2021-05-08 20:37:43 +02:00
}
if ( ! intermediates . is_empty ( ) ) {
2022-07-11 17:32:29 +00:00
builder . append ( " , intermediates: " sv ) ;
2021-05-08 20:37:43 +02:00
for ( size_t i = 0 ; i < intermediates . size ( ) ; + + i )
builder . append ( ( char ) intermediates [ i ] ) ;
2019-08-12 17:32:16 +02:00
}
2021-02-20 17:16:33 +01:00
dbgln ( " {} " , builder . string_view ( ) ) ;
2019-08-12 17:32:16 +02:00
}
2021-05-08 20:37:43 +02:00
void Terminal : : unimplemented_osc_sequence ( OscParameters parameters , u8 last_byte )
2019-08-12 17:32:16 +02:00
{
2021-05-08 20:37:43 +02:00
StringBuilder builder ;
builder . appendff ( " Unimplemented OSC sequence parameters: (bel_terminated={}) [ " , last_byte = = ' \a ' ) ;
bool first = true ;
for ( auto parameter : parameters ) {
if ( ! first )
2022-07-11 17:32:29 +00:00
builder . append ( " , " sv ) ;
builder . append ( ' [ ' ) ;
2021-05-08 20:37:43 +02:00
for ( auto character : parameter )
builder . append ( ( char ) character ) ;
2022-07-11 20:10:18 +00:00
builder . append ( ' ] ' ) ;
2021-05-08 20:37:43 +02:00
first = false ;
}
2022-07-11 17:32:29 +00:00
builder . append ( " ] " sv ) ;
2021-05-08 20:37:43 +02:00
dbgln ( " {} " , builder . string_view ( ) ) ;
2019-08-12 17:32:16 +02:00
}
2021-04-16 22:58:51 +03:00
# ifndef KERNEL
2019-08-12 17:32:16 +02:00
void Terminal : : set_size ( u16 columns , u16 rows )
{
2020-01-15 21:13:54 -06:00
if ( ! columns )
columns = 1 ;
if ( ! rows )
rows = 1 ;
2019-08-12 17:32:16 +02:00
if ( columns = = m_columns & & rows = = m_rows )
return ;
2021-06-19 03:53:03 +04:30
// If we're making the terminal larger (column-wise), start at the end and go up, taking cells from the line below.
// otherwise start at the beginning and go down, pushing cells into the line below.
auto resize_and_rewrap = [ & ] ( auto & buffer , auto & old_cursor ) {
auto cursor_on_line = [ & ] ( auto index ) {
return index = = old_cursor . row ? & old_cursor : nullptr ;
} ;
// Two passes, one from top to bottom, another from bottom to top
for ( size_t pass = 0 ; pass < 2 ; + + pass ) {
auto forwards = ( pass = = 0 ) ^ ( columns < m_columns ) ;
if ( forwards ) {
for ( size_t i = 1 ; i < = buffer . size ( ) ; + + i ) {
auto is_at_seam = i = = 1 ;
2023-03-06 17:16:25 +01:00
Line * next_line = is_at_seam ? nullptr : buffer [ buffer . size ( ) - i + 1 ] . ptr ( ) ;
Line * line = buffer [ buffer . size ( ) - i ] . ptr ( ) ;
2021-06-19 03:53:03 +04:30
auto next_cursor = cursor_on_line ( buffer . size ( ) - i + 1 ) ;
2023-03-06 17:16:25 +01:00
line - > rewrap ( columns , next_line , next_cursor ? : cursor_on_line ( buffer . size ( ) - i ) , ! ! next_cursor ) ;
2021-06-19 03:53:03 +04:30
}
} else {
for ( size_t i = 0 ; i < buffer . size ( ) ; + + i ) {
auto is_at_seam = i + 1 = = buffer . size ( ) ;
2023-03-06 17:16:25 +01:00
Line * next_line = is_at_seam ? nullptr : buffer [ i + 1 ] . ptr ( ) ;
2021-06-19 03:53:03 +04:30
auto next_cursor = cursor_on_line ( i + 1 ) ;
2023-03-06 17:16:25 +01:00
buffer [ i ] - > rewrap ( columns , next_line , next_cursor ? : cursor_on_line ( i ) , ! ! next_cursor ) ;
2021-06-19 03:53:03 +04:30
}
}
Queue < size_t > lines_to_reevaluate ;
for ( size_t i = 0 ; i < buffer . size ( ) ; + + i ) {
2023-03-06 17:16:25 +01:00
if ( buffer [ i ] - > length ( ) ! = columns )
2021-06-19 03:53:03 +04:30
lines_to_reevaluate . enqueue ( i ) ;
}
while ( ! lines_to_reevaluate . is_empty ( ) ) {
auto index = lines_to_reevaluate . dequeue ( ) ;
auto is_at_seam = index + 1 = = buffer . size ( ) ;
2023-03-06 17:16:25 +01:00
Line * const next_line = is_at_seam ? nullptr : buffer [ index + 1 ] . ptr ( ) ;
Line * const line = buffer [ index ] . ptr ( ) ;
2021-06-19 03:53:03 +04:30
auto next_cursor = cursor_on_line ( index + 1 ) ;
2023-03-06 17:16:25 +01:00
line - > rewrap ( columns , next_line , next_cursor ? : cursor_on_line ( index ) , ! ! next_cursor ) ;
if ( line - > length ( ) > columns ) {
2021-06-19 03:53:03 +04:30
auto current_cursor = cursor_on_line ( index ) ;
// Split the line into two (or more)
+ + index ;
buffer . insert ( index , make < Line > ( 0 ) ) ;
2023-03-06 17:16:25 +01:00
VERIFY ( buffer [ index ] - > length ( ) = = 0 ) ;
line - > rewrap ( columns , buffer [ index ] . ptr ( ) , current_cursor , false ) ;
2021-06-19 03:53:03 +04:30
// If we inserted a line and the old cursor was after that line, increment its row
if ( ! current_cursor & & old_cursor . row > = index )
+ + old_cursor . row ;
2023-03-06 17:16:25 +01:00
if ( buffer [ index ] - > length ( ) ! = columns )
2021-06-19 03:53:03 +04:30
lines_to_reevaluate . enqueue ( index ) ;
}
if ( next_line & & next_line - > length ( ) ! = columns )
lines_to_reevaluate . enqueue ( index + 1 ) ;
}
}
2021-06-24 20:07:33 +04:30
for ( auto & line : buffer )
2023-03-06 17:16:25 +01:00
line - > set_length ( columns ) ;
2021-06-24 20:07:33 +04:30
2021-06-19 03:53:03 +04:30
return old_cursor ;
} ;
2021-06-22 20:01:24 +04:30
auto old_history_size = m_history . size ( ) ;
m_history . extend ( move ( m_normal_screen_buffer ) ) ;
CursorPosition cursor_tracker { cursor_row ( ) + old_history_size , cursor_column ( ) } ;
resize_and_rewrap ( m_history , cursor_tracker ) ;
if ( auto extra_lines = m_history . size ( ) - rows ) {
while ( extra_lines > 0 ) {
if ( m_history . size ( ) < = cursor_tracker . row )
2021-06-19 03:53:03 +04:30
break ;
2023-03-06 17:16:25 +01:00
if ( m_history . last ( ) - > is_empty ( ) ) {
if ( m_history . size ( ) > = 2 & & m_history [ m_history . size ( ) - 2 ] - > termination_column ( ) . has_value ( ) )
2021-06-22 20:01:24 +04:30
break ;
- - extra_lines ;
2021-12-02 12:53:50 +00:00
( void ) m_history . take_last ( ) ;
2021-06-22 20:01:24 +04:30
continue ;
2021-06-19 03:53:03 +04:30
}
2021-06-22 20:01:24 +04:30
break ;
2021-06-19 03:53:03 +04:30
}
}
2021-06-22 20:01:24 +04:30
// FIXME: This can use a more performant way to move the last N entries
// from the history into the normal buffer
m_normal_screen_buffer . ensure_capacity ( rows ) ;
while ( m_normal_screen_buffer . size ( ) < rows ) {
if ( ! m_history . is_empty ( ) )
m_normal_screen_buffer . prepend ( m_history . take_last ( ) ) ;
else
m_normal_screen_buffer . unchecked_append ( make < Line > ( columns ) ) ;
}
cursor_tracker . row - = m_history . size ( ) ;
if ( m_history . size ( ) ! = old_history_size ) {
m_client . terminal_history_changed ( - old_history_size ) ;
m_client . terminal_history_changed ( m_history . size ( ) ) ;
}
2021-06-19 03:53:03 +04:30
CursorPosition dummy_cursor_tracker { } ;
resize_and_rewrap ( m_alternate_screen_buffer , dummy_cursor_tracker ) ;
if ( m_alternate_screen_buffer . size ( ) > rows )
m_alternate_screen_buffer . remove ( 0 , m_alternate_screen_buffer . size ( ) - rows ) ;
2019-08-12 17:32:16 +02:00
if ( rows > m_rows ) {
2021-05-23 15:47:41 +02:00
while ( m_normal_screen_buffer . size ( ) < rows )
m_normal_screen_buffer . append ( make < Line > ( columns ) ) ;
while ( m_alternate_screen_buffer . size ( ) < rows )
m_alternate_screen_buffer . append ( make < Line > ( columns ) ) ;
2019-08-12 17:32:16 +02:00
} else {
2021-05-23 15:47:41 +02:00
m_normal_screen_buffer . shrink ( rows ) ;
m_alternate_screen_buffer . shrink ( rows ) ;
2019-08-12 17:32:16 +02:00
}
m_columns = columns ;
m_rows = rows ;
m_scroll_region_top = 0 ;
m_scroll_region_bottom = rows - 1 ;
2021-05-23 15:47:41 +02:00
m_current_state . cursor . clamp ( m_rows - 1 , m_columns - 1 ) ;
m_normal_saved_state . cursor . clamp ( m_rows - 1 , m_columns - 1 ) ;
m_alternate_saved_state . cursor . clamp ( m_rows - 1 , m_columns - 1 ) ;
m_saved_cursor_position . clamp ( m_rows - 1 , m_columns - 1 ) ;
2019-08-12 17:32:16 +02:00
m_horizontal_tabs . resize ( columns ) ;
for ( unsigned i = 0 ; i < columns ; + + i )
m_horizontal_tabs [ i ] = ( i % 8 ) = = 0 ;
// Rightmost column is always last tab on line.
m_horizontal_tabs [ columns - 1 ] = 1 ;
2021-06-19 03:53:03 +04:30
set_cursor ( cursor_tracker . row , cursor_tracker . column ) ;
2019-08-12 17:32:16 +02:00
m_client . terminal_did_resize ( m_columns , m_rows ) ;
2021-05-23 15:57:21 +02:00
dbgln_if ( TERMINAL_DEBUG , " Set terminal size: {}x{} " , m_rows , m_columns ) ;
2019-08-12 17:32:16 +02:00
}
2021-04-16 22:58:51 +03:00
# endif
2019-08-12 17:32:16 +02:00
2021-04-16 22:58:51 +03:00
# ifndef KERNEL
2019-08-12 17:32:16 +02:00
void Terminal : : invalidate_cursor ( )
{
2021-06-19 03:53:03 +04:30
if ( cursor_row ( ) < active_buffer ( ) . size ( ) )
2023-03-06 17:16:25 +01:00
active_buffer ( ) [ cursor_row ( ) ] - > set_dirty ( true ) ;
2019-08-12 17:32:16 +02:00
}
2022-04-01 20:58:27 +03:00
Attribute Terminal : : attribute_at ( Position const & position ) const
2020-05-09 16:16:16 +02:00
{
if ( ! position . is_valid ( ) )
return { } ;
2020-05-10 16:59:02 +02:00
if ( position . row ( ) > = static_cast < int > ( line_count ( ) ) )
2020-05-09 16:16:16 +02:00
return { } ;
auto & line = this - > line ( position . row ( ) ) ;
2021-02-26 20:28:22 +01:00
if ( static_cast < size_t > ( position . column ( ) ) > = line . length ( ) )
2020-05-09 16:16:16 +02:00
return { } ;
2021-02-26 20:28:22 +01:00
return line . attribute_at ( position . column ( ) ) ;
2020-05-09 16:16:16 +02:00
}
2021-04-16 22:58:51 +03:00
# endif
2019-08-12 17:32:16 +02:00
}