Separate modules by subdirectory

This commit is contained in:
Keith Winstein
2012-02-06 18:26:45 -05:00
parent 7e56af8fcd
commit 38c9e99882
58 changed files with 79 additions and 16 deletions
+7
View File
@@ -0,0 +1,7 @@
AM_CPPFLAGS = -I$(srcdir)/../util
AM_CXXFLAGS = --std=c++0x -pedantic -Werror -Wall -Wextra -Weffc++ -fno-default-inline -pipe
noinst_LIBRARIES = libmoshterminal.a
libmoshterminal_a_SOURCES = parseraction.cc parseraction.h parser.cc parser.h parserstate.cc parserstatefamily.h parserstate.h parsertransition.h terminal.cc terminaldispatcher.cc terminaldispatcher.h terminaldisplay.cc terminaldisplay.h terminalframebuffer.cc terminalframebuffer.h terminalfunctions.cc terminal.h terminaluserinput.cc terminaluserinput.h
+127
View File
@@ -0,0 +1,127 @@
#include <assert.h>
#include <typeinfo>
#include <langinfo.h>
#include "parser.h"
static void append_or_delete( Parser::Action *act,
std::list<Parser::Action *>&vec )
{
assert( act );
if ( typeid( *act ) != typeid( Parser::Ignore ) ) {
vec.push_back( act );
} else {
delete act;
}
}
std::list<Parser::Action *> Parser::Parser::input( wchar_t ch )
{
std::list<Action *> ret;
Transition tx = state->input( ch );
if ( tx.next_state != NULL ) {
append_or_delete( state->exit(), ret );
}
append_or_delete( tx.action, ret );
if ( tx.next_state != NULL ) {
append_or_delete( tx.next_state->enter(), ret );
state = tx.next_state;
}
return ret;
}
Parser::UTF8Parser::UTF8Parser()
: parser(), buf_len( 0 )
{
assert( BUF_SIZE >= MB_CUR_MAX );
}
std::list<Parser::Action *> Parser::UTF8Parser::input( char c )
{
assert( buf_len < BUF_SIZE );
buf[ buf_len++ ] = c;
/* This function will only work in a UTF-8 locale. */
/* This is asserted in the constructor. */
wchar_t pwc;
mbstate_t ps;
memset( &ps, 0, sizeof( ps ) );
size_t total_bytes_parsed = 0;
size_t orig_buf_len = buf_len;
std::list<Action *> ret;
/* this routine is somewhat complicated in order to comply with
Unicode 6.0, section 3.9, "Best Practices for using U+FFFD" */
while ( total_bytes_parsed != orig_buf_len ) {
assert( total_bytes_parsed < orig_buf_len );
assert( buf_len > 0 );
size_t bytes_parsed = mbrtowc( &pwc, buf, buf_len, &ps );
/* this returns 0 when n = 0! */
/* This function annoying returns a size_t so we have to check
the negative values first before the "> 0" branch */
if ( bytes_parsed == 0 ) {
/* character was NUL, accept and clear buffer */
assert( buf_len == 1 );
buf_len = 0;
pwc = L'\0';
bytes_parsed = 1;
} else if ( bytes_parsed == (size_t) -1 ) {
/* invalid sequence, use replacement character and try again with last char */
assert( errno == EILSEQ );
if ( buf_len > 1 ) {
buf[ 0 ] = buf[ buf_len - 1 ];
bytes_parsed = buf_len - 1;
buf_len = 1;
} else {
buf_len = 0;
bytes_parsed = 1;
}
pwc = (wchar_t) 0xFFFD;
} else if ( bytes_parsed == (size_t) -2 ) {
/* can't parse incomplete multibyte character */
total_bytes_parsed += buf_len;
continue;
} else if ( bytes_parsed > 0 ) {
/* parsed into pwc, accept */
assert( bytes_parsed <= buf_len );
memcpy( buf, buf + bytes_parsed, buf_len - bytes_parsed );
buf_len = buf_len - bytes_parsed;
} else {
throw std::string( "Unknown return value from mbrtowc" );
}
if ( (pwc < 0) || (pwc > 0x10FFFF) ) { /* outside Unicode range */
pwc = (wchar_t) 0xFFFD;
}
std::list<Action *> vec = parser.input( pwc );
ret.insert( ret.end(), vec.begin(), vec.end() );
total_bytes_parsed += bytes_parsed;
}
return ret;
}
Parser::Parser::Parser( const Parser &other )
: state( other.state )
{}
Parser::Parser & Parser::Parser::operator=( const Parser &other )
{
state = other.state;
return *this;
}
+67
View File
@@ -0,0 +1,67 @@
#ifndef PARSER_HPP
#define PARSER_HPP
/* Based on Paul Williams's parser,
http://www.vt100.net/emu/dec_ansi_parser */
#include <wchar.h>
#include <list>
#include <string.h>
#include "parsertransition.h"
#include "parseraction.h"
#include "parserstate.h"
#include "parserstatefamily.h"
#ifndef __STDC_ISO_10646__
#error "Must have __STDC_ISO_10646__"
#endif
namespace Parser {
static const StateFamily family;
class Parser {
private:
State const *state;
public:
Parser() : state( &family.s_Ground ) {}
Parser( const Parser &other );
Parser & operator=( const Parser & );
~Parser() {}
std::list<Action *> input( wchar_t ch );
bool operator==( const Parser &x ) const
{
return state == x.state;
}
bool is_grounded( void ) const { return state == &family.s_Ground; }
};
static const size_t BUF_SIZE = 8;
class UTF8Parser {
private:
Parser parser;
char buf[ BUF_SIZE ];
size_t buf_len;
public:
UTF8Parser();
std::list<Action *> input( char c );
bool operator==( const UTF8Parser &x ) const
{
return parser == x.parser;
}
bool is_grounded( void ) const { return parser.is_grounded(); }
};
}
#endif
+86
View File
@@ -0,0 +1,86 @@
#include <stdio.h>
#include <wctype.h>
#include "parseraction.h"
#include "terminal.h"
using namespace Parser;
std::string Action::str( void )
{
char thechar[ 10 ] = { 0 };
if ( char_present ) {
snprintf( thechar, 10, iswprint( ch ) ? "(%lc)" : "(0x%x)", ch );
}
return name() + std::string( thechar );
}
void Print::act_on_terminal( Terminal::Emulator *emu ) const
{
emu->print( this );
}
void Execute::act_on_terminal( Terminal::Emulator *emu ) const
{
emu->execute( this );
}
void Clear::act_on_terminal( Terminal::Emulator *emu ) const
{
emu->dispatch.clear( this );
}
void Param::act_on_terminal( Terminal::Emulator *emu ) const
{
emu->dispatch.newparamchar( this );
}
void Collect::act_on_terminal( Terminal::Emulator *emu ) const
{
emu->dispatch.collect( this );
}
void CSI_Dispatch::act_on_terminal( Terminal::Emulator *emu ) const
{
emu->CSI_dispatch( this );
}
void Esc_Dispatch::act_on_terminal( Terminal::Emulator *emu ) const
{
emu->Esc_dispatch( this );
}
void OSC_Put::act_on_terminal( Terminal::Emulator *emu ) const
{
emu->dispatch.OSC_put( this );
}
void OSC_Start::act_on_terminal( Terminal::Emulator *emu ) const
{
emu->dispatch.OSC_start( this );
}
void OSC_End::act_on_terminal( Terminal::Emulator *emu ) const
{
emu->OSC_end( this );
}
void UserByte::act_on_terminal( Terminal::Emulator *emu ) const
{
emu->dispatch.terminal_to_host.append( emu->user.input( this,
emu->fb.ds.application_mode_cursor_keys ) );
}
void Resize::act_on_terminal( Terminal::Emulator *emu ) const
{
emu->resize( width, height );
handled = true;
}
bool Action::operator==( const Action &other ) const
{
return ( char_present == other.char_present )
&& ( ch == other.ch )
&& ( handled == other.handled );
}
+129
View File
@@ -0,0 +1,129 @@
#ifndef PARSERACTION_HPP
#define PARSERACTION_HPP
#include <string>
namespace Terminal {
class Emulator;
}
namespace Parser {
class Action
{
public:
bool char_present;
wchar_t ch;
mutable bool handled;
std::string str( void );
virtual std::string name( void ) = 0;
virtual void act_on_terminal( Terminal::Emulator * ) const {};
Action() : char_present( false ), ch( -1 ), handled( false ) {};
virtual ~Action() {};
virtual bool operator==( const Action &other ) const;
};
class Ignore : public Action {
public: std::string name( void ) { return std::string( "Ignore" ); }
};
class Print : public Action {
public:
std::string name( void ) { return std::string( "Print" ); }
void act_on_terminal( Terminal::Emulator *emu ) const;
};
class Execute : public Action {
public:
std::string name( void ) { return std::string( "Execute" ); }
void act_on_terminal( Terminal::Emulator *emu ) const;
};
class Clear : public Action {
public:
std::string name( void ) { return std::string( "Clear" ); }
void act_on_terminal( Terminal::Emulator *emu ) const;
};
class Collect : public Action {
public:
std::string name( void ) { return std::string( "Collect" ); }
void act_on_terminal( Terminal::Emulator *emu ) const;
};
class Param : public Action {
public:
std::string name( void ) { return std::string( "Param" ); }
void act_on_terminal( Terminal::Emulator *emu ) const;
};
class Esc_Dispatch : public Action {
public:
std::string name( void ) { return std::string( "Esc_Dispatch" ); }
void act_on_terminal( Terminal::Emulator *emu ) const;
};
class CSI_Dispatch : public Action {
public:
std::string name( void ) { return std::string( "CSI_Dispatch" ); }
void act_on_terminal( Terminal::Emulator *emu ) const;
};
class Hook : public Action {
public: std::string name( void ) { return std::string( "Hook" ); }
};
class Put : public Action {
public: std::string name( void ) { return std::string( "Put" ); }
};
class Unhook : public Action {
public: std::string name( void ) { return std::string( "Unhook" ); }
};
class OSC_Start : public Action {
public:
std::string name( void ) { return std::string( "OSC_Start" ); }
void act_on_terminal( Terminal::Emulator *emu ) const;
};
class OSC_Put : public Action {
public:
std::string name( void ) { return std::string( "OSC_Put" ); }
void act_on_terminal( Terminal::Emulator *emu ) const;
};
class OSC_End : public Action {
public:
std::string name( void ) { return std::string( "OSC_End" ); }
void act_on_terminal( Terminal::Emulator *emu ) const;
};
class UserByte : public Action {
/* user keystroke -- not part of the host-source state machine*/
public:
char c; /* The user-source byte. We don't try to interpret the charset */
std::string name( void ) { return std::string( "UserByte" ); }
void act_on_terminal( Terminal::Emulator *emu ) const;
UserByte( int s_c ) : c( s_c ) {}
bool operator==( const UserByte &other ) const
{
return c == other.c;
}
};
class Resize : public Action {
/* resize event -- not part of the host-source state machine*/
public:
size_t width, height;
std::string name( void ) { return std::string( "Resize" ); }
void act_on_terminal( Terminal::Emulator *emu ) const;
Resize( size_t s_width, size_t s_height )
: width( s_width ),
height( s_height )
{}
bool operator==( const Resize &other ) const
{
return ( width == other.width ) && ( height == other.height );
}
};
}
#endif
+356
View File
@@ -0,0 +1,356 @@
#include "parserstate.h"
#include "parserstatefamily.h"
using namespace Parser;
Transition State::anywhere_rule( wchar_t ch ) const
{
if ( (ch == 0x18) || (ch == 0x1A)
|| ((0x80 <= ch) && (ch <= 0x8F))
|| ((0x91 <= ch) && (ch <= 0x97))
|| (ch == 0x99) || (ch == 0x9A) ) {
return Transition( new Execute, &family->s_Ground );
} else if ( ch == 0x9C ) {
return Transition( &family->s_Ground );
} else if ( ch == 0x1B ) {
return Transition( &family->s_Escape );
} else if ( (ch == 0x98) || (ch == 0x9E) || (ch == 0x9F) ) {
return Transition( &family->s_SOS_PM_APC_String );
} else if ( ch == 0x90 ) {
return Transition( &family->s_DCS_Entry );
} else if ( ch == 0x9D ) {
return Transition( &family->s_OSC_String );
} else if ( ch == 0x9B ) {
return Transition( &family->s_CSI_Entry );
}
return Transition( NULL, NULL ); /* don't allocate an Ignore action */
}
Transition State::input( wchar_t ch ) const
{
Transition ret = anywhere_rule( ch );
if ( !ret.next_state ) {
if ( ch >= 0xA0 ) {
ret = this->input_state_rule( 0x41 );
} else {
ret = this->input_state_rule( ch );
}
}
ret.action->char_present = true;
ret.action->ch = ch;
return ret;
}
static bool C0_prime( wchar_t ch )
{
return ( (ch <= 0x17)
|| (ch == 0x19)
|| ( (0x1C <= ch) && (ch <= 0x1F) ) );
}
static bool GLGR ( wchar_t ch )
{
return ( ( (0x20 <= ch) && (ch <= 0x7F) ) /* GL area */
|| ( (0xA0 <= ch) && (ch <= 0xFF) ) ); /* GR area */
}
Transition Ground::input_state_rule( wchar_t ch ) const
{
if ( C0_prime( ch ) ) {
return Transition( new Execute );
}
if ( GLGR( ch ) ) {
return Transition( new Print );
}
return Transition();
}
Action *Escape::enter( void ) const
{
return new Clear;
}
Transition Escape::input_state_rule( wchar_t ch ) const
{
if ( C0_prime( ch ) ) {
return Transition( new Execute );
}
if ( (0x20 <= ch) && (ch <= 0x2F) ) {
return Transition( new Collect, &family->s_Escape_Intermediate );
}
if ( ( (0x30 <= ch) && (ch <= 0x4F) )
|| ( (0x51 <= ch) && (ch <= 0x57) )
|| ( ch == 0x59 )
|| ( ch == 0x5A )
|| ( ch == 0x5C )
|| ( (0x60 <= ch) && (ch <= 0x7E) ) ) {
return Transition( new Esc_Dispatch, &family->s_Ground );
}
if ( ch == 0x5B ) {
return Transition( &family->s_CSI_Entry );
}
if ( ch == 0x5D ) {
return Transition( &family->s_OSC_String );
}
if ( ch == 0x50 ) {
return Transition( &family->s_DCS_Entry );
}
if ( (ch == 0x58) || (ch == 0x5E) || (ch == 0x5F) ) {
return Transition( &family->s_SOS_PM_APC_String );
}
return Transition();
}
Transition Escape_Intermediate::input_state_rule( wchar_t ch ) const
{
if ( C0_prime( ch ) ) {
return Transition( new Execute );
}
if ( (0x20 <= ch) && (ch <= 0x2F) ) {
return Transition( new Collect );
}
if ( (0x30 <= ch) && (ch <= 0x7E) ) {
return Transition( new Esc_Dispatch, &family->s_Ground );
}
return Transition();
}
Action *CSI_Entry::enter( void ) const
{
return new Clear;
}
Transition CSI_Entry::input_state_rule( wchar_t ch ) const
{
if ( C0_prime( ch ) ) {
return Transition( new Execute );
}
if ( (0x40 <= ch) && (ch <= 0x7E) ) {
return Transition( new CSI_Dispatch, &family->s_Ground );
}
if ( ( (0x30 <= ch) && (ch <= 0x39) )
|| ( ch == 0x3B ) ) {
return Transition( new Param, &family->s_CSI_Param );
}
if ( (0x3C <= ch) && (ch <= 0x3F) ) {
return Transition( new Collect, &family->s_CSI_Param );
}
if ( ch == 0x3A ) {
return Transition( &family->s_CSI_Ignore );
}
if ( (0x20 <= ch) && (ch <= 0x2F) ) {
return Transition( new Collect, &family->s_CSI_Intermediate );
}
return Transition();
}
Transition CSI_Param::input_state_rule( wchar_t ch ) const
{
if ( C0_prime( ch ) ) {
return Transition( new Execute );
}
if ( ( (0x30 <= ch) && (ch <= 0x39) ) || ( ch == 0x3B ) ) {
return Transition( new Param );
}
if ( ( ch == 0x3A ) || ( (0x3C <= ch) && (ch <= 0x3F) ) ) {
return Transition( &family->s_CSI_Ignore );
}
if ( (0x20 <= ch) && (ch <= 0x2F) ) {
return Transition( new Collect, &family->s_CSI_Intermediate );
}
if ( (0x40 <= ch) && (ch <= 0x7E) ) {
return Transition( new CSI_Dispatch, &family->s_Ground );
}
return Transition();
}
Transition CSI_Intermediate::input_state_rule( wchar_t ch ) const
{
if ( C0_prime( ch ) ) {
return Transition( new Execute );
}
if ( (0x20 <= ch) && (ch <= 0x2F) ) {
return Transition( new Collect );
}
if ( (0x40 <= ch) && (ch <= 0x7E) ) {
return Transition( new CSI_Dispatch, &family->s_Ground );
}
if ( (0x30 <= ch) && (ch <= 0x3F) ) {
return Transition( &family->s_CSI_Ignore );
}
return Transition();
}
Transition CSI_Ignore::input_state_rule( wchar_t ch ) const
{
if ( C0_prime( ch ) ) {
return Transition( new Execute );
}
if ( (0x40 <= ch) && (ch <= 0x7E) ) {
return Transition( &family->s_Ground );
}
return Transition();
}
Action *DCS_Entry::enter( void ) const
{
return new Clear;
}
Transition DCS_Entry::input_state_rule( wchar_t ch ) const
{
if ( (0x20 <= ch) && (ch <= 0x2F) ) {
return Transition( new Collect, &family->s_DCS_Intermediate );
}
if ( ch == 0x3A ) {
return Transition( &family->s_DCS_Ignore );
}
if ( ( (0x30 <= ch) && (ch <= 0x39) ) || ( ch == 0x3B ) ) {
return Transition( new Param, &family->s_DCS_Param );
}
if ( (0x3C <= ch) && (ch <= 0x3F) ) {
return Transition( new Collect, &family->s_DCS_Param );
}
if ( (0x40 <= ch) && (ch <= 0x7E) ) {
return Transition( &family->s_DCS_Passthrough );
}
return Transition();
}
Transition DCS_Param::input_state_rule( wchar_t ch ) const
{
if ( ( (0x30 <= ch) && (ch <= 0x39) ) || ( ch == 0x3B ) ) {
return Transition( new Param );
}
if ( ( ch == 0x3A ) || ( (0x3C <= ch) && (ch <= 0x3F) ) ) {
return Transition( &family->s_DCS_Ignore );
}
if ( (0x20 <= ch) && (ch <= 0x2F) ) {
return Transition( new Collect, &family->s_DCS_Intermediate );
}
if ( (0x40 <= ch) && (ch <= 0x7E) ) {
return Transition( &family->s_DCS_Passthrough );
}
return Transition();
}
Transition DCS_Intermediate::input_state_rule( wchar_t ch ) const
{
if ( (0x20 <= ch) && (ch <= 0x2F) ) {
return Transition( new Collect );
}
if ( (0x40 <= ch) && (ch <= 0x7E) ) {
return Transition( &family->s_DCS_Passthrough );
}
if ( (0x30 <= ch) && (ch <= 0x3F) ) {
return Transition( &family->s_DCS_Ignore );
}
return Transition();
}
Action *DCS_Passthrough::enter( void ) const
{
return new Hook;
}
Action *DCS_Passthrough::exit( void ) const
{
return new Unhook;
}
Transition DCS_Passthrough::input_state_rule( wchar_t ch ) const
{
if ( C0_prime( ch ) || ( (0x20 <= ch) && (ch <= 0x7E) ) ) {
return Transition( new Put );
}
if ( ch == 0x9C ) {
return Transition( &family->s_Ground );
}
return Transition();
}
Transition DCS_Ignore::input_state_rule( wchar_t ch ) const
{
if ( ch == 0x9C ) {
return Transition( &family->s_Ground );
}
return Transition();
}
Action *OSC_String::enter( void ) const
{
return new OSC_Start;
}
Action *OSC_String::exit( void ) const
{
return new OSC_End;
}
Transition OSC_String::input_state_rule( wchar_t ch ) const
{
if ( (0x20 <= ch) && (ch <= 0x7F) ) {
return Transition( new OSC_Put );
}
if ( (ch == 0x9C) || (ch == 0x07) ) { /* 0x07 is xterm non-ANSI variant */
return Transition( &family->s_Ground );
}
return Transition();
}
Transition SOS_PM_APC_String::input_state_rule( wchar_t ch ) const
{
if ( ch == 0x9C ) {
return Transition( &family->s_Ground );
}
return Transition();
}
+87
View File
@@ -0,0 +1,87 @@
#ifndef PARSERSTATE_HPP
#define PARSERSTATE_HPP
#include "parsertransition.h"
namespace Parser {
class StateFamily;
class State
{
protected:
virtual Transition input_state_rule( wchar_t ch ) const = 0;
StateFamily *family;
private:
Transition anywhere_rule( wchar_t ch ) const;
public:
void setfamily( StateFamily *s_family ) { family = s_family; }
Transition input( wchar_t ch ) const;
virtual Action *enter( void ) const { return new Ignore; }
virtual Action *exit( void ) const { return new Ignore; }
State() : family( NULL ) {};
virtual ~State() {};
State( const State & );
State & operator=( const State & );
};
class Ground : public State {
Transition input_state_rule( wchar_t ch ) const;
};
class Escape : public State {
Action *enter( void ) const;
Transition input_state_rule( wchar_t ch ) const;
};
class Escape_Intermediate : public State {
Transition input_state_rule( wchar_t ch ) const;
};
class CSI_Entry : public State {
Action *enter( void ) const;
Transition input_state_rule( wchar_t ch ) const;
};
class CSI_Param : public State {
Transition input_state_rule( wchar_t ch ) const;
};
class CSI_Intermediate : public State {
Transition input_state_rule( wchar_t ch ) const;
};
class CSI_Ignore : public State {
Transition input_state_rule( wchar_t ch ) const;
};
class DCS_Entry : public State {
Action *enter( void ) const;
Transition input_state_rule( wchar_t ch ) const;
};
class DCS_Param : public State {
Transition input_state_rule( wchar_t ch ) const;
};
class DCS_Intermediate : public State {
Transition input_state_rule( wchar_t ch ) const;
};
class DCS_Passthrough : public State {
Action *enter( void ) const;
Transition input_state_rule( wchar_t ch ) const;
Action *exit( void ) const;
};
class DCS_Ignore : public State {
Transition input_state_rule( wchar_t ch ) const;
};
class OSC_String : public State {
Action *enter( void ) const;
Transition input_state_rule( wchar_t ch ) const;
Action *exit( void ) const;
};
class SOS_PM_APC_String : public State {
Transition input_state_rule( wchar_t ch ) const;
};
}
#endif
+54
View File
@@ -0,0 +1,54 @@
#ifndef PARSERSTATEFAMILY_HPP
#define PARSERSTATEFAMILY_HPP
#include "parserstate.h"
namespace Parser {
class StateFamily
{
public:
Ground s_Ground;
Escape s_Escape;
Escape_Intermediate s_Escape_Intermediate;
CSI_Entry s_CSI_Entry;
CSI_Param s_CSI_Param;
CSI_Intermediate s_CSI_Intermediate;
CSI_Ignore s_CSI_Ignore;
DCS_Entry s_DCS_Entry;
DCS_Param s_DCS_Param;
DCS_Intermediate s_DCS_Intermediate;
DCS_Passthrough s_DCS_Passthrough;
DCS_Ignore s_DCS_Ignore;
OSC_String s_OSC_String;
SOS_PM_APC_String s_SOS_PM_APC_String;
StateFamily()
: s_Ground(), s_Escape(), s_Escape_Intermediate(),
s_CSI_Entry(), s_CSI_Param(), s_CSI_Intermediate(), s_CSI_Ignore(),
s_DCS_Entry(), s_DCS_Param(), s_DCS_Intermediate(),
s_DCS_Passthrough(), s_DCS_Ignore(),
s_OSC_String(), s_SOS_PM_APC_String()
{
s_Ground.setfamily( this );
s_Escape.setfamily( this );
s_Escape_Intermediate.setfamily( this );
s_CSI_Entry.setfamily( this );
s_CSI_Param.setfamily( this );
s_CSI_Intermediate.setfamily( this );
s_CSI_Ignore.setfamily( this );
s_DCS_Entry.setfamily( this );
s_DCS_Param.setfamily( this );
s_DCS_Intermediate.setfamily( this );
s_DCS_Passthrough.setfamily( this );
s_DCS_Ignore.setfamily( this );
s_OSC_String.setfamily( this );
s_SOS_PM_APC_String.setfamily( this );
}
};
}
#endif
+39
View File
@@ -0,0 +1,39 @@
#ifndef PARSERTRANSITION_HPP
#define PARSERTRANSITION_HPP
#include <stdlib.h>
#include "parseraction.h"
namespace Parser {
class State;
class Transition
{
public:
Action *action;
State *next_state;
Transition( const Transition &x )
: action( x.action ),
next_state( x.next_state ) {}
Transition & operator=( const Transition &t )
{
action = t.action;
next_state = t.next_state;
return *this;
}
virtual ~Transition() {}
Transition( Action *s_action=new Ignore, State *s_next_state=NULL )
: action( s_action ), next_state( s_next_state )
{}
Transition( State *s_next_state )
: action( new Ignore ), next_state( s_next_state )
{}
};
}
#endif
+155
View File
@@ -0,0 +1,155 @@
#include <assert.h>
#include <string.h>
#include <stdlib.h>
#include <unistd.h>
#include <typeinfo>
#include "terminal.h"
#include "swrite.h"
using namespace Terminal;
Emulator::Emulator( size_t s_width, size_t s_height )
: fb( s_width, s_height ), dispatch(), user()
{}
std::string Emulator::read_octets_to_host( void )
{
std::string ret = dispatch.terminal_to_host;
dispatch.terminal_to_host.clear();
return ret;
}
void Emulator::execute( const Parser::Execute *act )
{
dispatch.dispatch( CONTROL, act, &fb );
}
void Emulator::print( const Parser::Print *act )
{
assert( act->char_present );
int chwidth = act->ch == L'\0' ? -1 : wcwidth( act->ch );
Cell *this_cell = fb.get_mutable_cell();
Cell *combining_cell = fb.get_combining_cell(); /* can be null if we were resized */
switch ( chwidth ) {
case 1: /* normal character */
case 2: /* wide character */
if ( fb.ds.auto_wrap_mode && fb.ds.next_print_will_wrap ) {
fb.get_mutable_row( -1 )->wrap = true;
fb.ds.move_col( 0 );
fb.move_rows_autoscroll( 1 );
}
/* wrap 2-cell chars if no room, even without will-wrap flag */
if ( fb.ds.auto_wrap_mode
&& (chwidth == 2)
&& (fb.ds.get_cursor_col() == fb.ds.get_width() - 1) ) {
fb.reset_cell( this_cell );
fb.get_mutable_row( -1 )->wrap = false;
/* There doesn't seem to be a consistent way to get the
downstream terminal emulator to set the wrap-around
copy-and-paste flag on a row that ends with an empty cell
because a wide char was wrapped to the next line. */
fb.ds.move_col( 0 );
fb.move_rows_autoscroll( 1 );
}
if ( fb.ds.insert_mode ) {
for ( int i = 0; i < chwidth; i++ ) {
fb.insert_cell( fb.ds.get_cursor_row(), fb.ds.get_cursor_col() );
}
}
this_cell = fb.get_mutable_cell();
fb.reset_cell( this_cell );
this_cell->contents.push_back( act->ch );
this_cell->width = chwidth;
fb.apply_renditions_to_current_cell();
if ( chwidth == 2 ) { /* erase overlapped cell */
if ( fb.ds.get_cursor_col() + 1 < fb.ds.get_width() ) {
fb.reset_cell( fb.get_mutable_cell( fb.ds.get_cursor_row(), fb.ds.get_cursor_col() + 1 ) );
}
}
fb.ds.move_col( chwidth, true, true );
act->handled = true;
break;
case 0: /* combining character */
if ( combining_cell == NULL ) { /* character is now offscreen */
act->handled = true;
break;
}
if ( combining_cell->contents.size() == 0 ) {
/* cell starts with combining character */
assert( this_cell == combining_cell );
assert( combining_cell->width == 1 );
combining_cell->fallback = true;
fb.ds.move_col( 1, true, true );
}
if ( combining_cell->contents.size() < 16 ) {
/* seems like a reasonable limit on combining characters */
combining_cell->contents.push_back( act->ch );
}
act->handled = true;
break;
case -1: /* unprintable character */
break;
default:
assert( false );
}
}
void Emulator::CSI_dispatch( const Parser::CSI_Dispatch *act )
{
dispatch.dispatch( CSI, act, &fb );
}
void Emulator::OSC_end( const Parser::OSC_End *act )
{
dispatch.OSC_dispatch( act, &fb );
}
void Emulator::Esc_dispatch( const Parser::Esc_Dispatch *act )
{
/* handle 7-bit ESC-encoding of C1 control characters */
if ( (dispatch.get_dispatch_chars().size() == 0)
&& (0x40 <= act->ch)
&& (act->ch <= 0x5F) ) {
Parser::Esc_Dispatch act2 = *act;
act2.ch += 0x40;
dispatch.dispatch( CONTROL, &act2, &fb );
} else {
dispatch.dispatch( ESCAPE, act, &fb );
}
}
std::string Emulator::open( void )
{
char appmode[ 6 ] = { 0x1b, '[', '?', '1', 'h', 0 };
return std::string( appmode );
}
std::string Emulator::close( void )
{
return std::string( "\033[?1l\033[!p" );
}
void Emulator::resize( size_t s_width, size_t s_height )
{
fb.resize( s_width, s_height );
}
bool Emulator::operator==( Emulator const &x ) const
{
/* dispatcher and user are irrelevant for us */
return ( fb == x.fb );
}
+58
View File
@@ -0,0 +1,58 @@
#ifndef TERMINAL_CPP
#define TERMINAL_CPP
#include <wchar.h>
#include <stdio.h>
#include <vector>
#include <deque>
#include "parseraction.h"
#include "terminalframebuffer.h"
#include "terminaldispatcher.h"
#include "terminaluserinput.h"
#include "terminaldisplay.h"
namespace Terminal {
class Emulator {
friend void Parser::Print::act_on_terminal( Emulator * ) const;
friend void Parser::Execute::act_on_terminal( Emulator * ) const;
friend void Parser::Clear::act_on_terminal( Emulator * ) const;
friend void Parser::Param::act_on_terminal( Emulator * ) const;
friend void Parser::Collect::act_on_terminal( Emulator * ) const;
friend void Parser::CSI_Dispatch::act_on_terminal( Emulator * ) const;
friend void Parser::Esc_Dispatch::act_on_terminal( Emulator * ) const;
friend void Parser::OSC_Start::act_on_terminal( Emulator * ) const;
friend void Parser::OSC_Put::act_on_terminal( Emulator * ) const;
friend void Parser::OSC_End::act_on_terminal( Emulator * ) const;
friend void Parser::UserByte::act_on_terminal( Emulator * ) const;
friend void Parser::Resize::act_on_terminal( Emulator * ) const;
private:
Framebuffer fb;
Dispatcher dispatch;
UserInput user;
/* action methods */
void print( const Parser::Print *act );
void execute( const Parser::Execute *act );
void CSI_dispatch( const Parser::CSI_Dispatch *act );
void Esc_dispatch( const Parser::Esc_Dispatch *act );
void OSC_end( const Parser::OSC_End *act );
void resize( size_t s_width, size_t s_height );
public:
Emulator( size_t s_width, size_t s_height );
std::string read_octets_to_host( void );
static std::string open( void ); /* put user cursor keys in application mode */
static std::string close( void ); /* restore user cursor keys */
const Framebuffer & get_fb( void ) const { return fb; }
bool operator==( Emulator const &x ) const;
};
}
#endif
+211
View File
@@ -0,0 +1,211 @@
#include <stdio.h>
#include <assert.h>
#include <string.h>
#include "terminaldispatcher.h"
#include "parseraction.h"
#include "terminalframebuffer.h"
using namespace Terminal;
Dispatcher::Dispatcher()
: params(), parsed_params(), parsed( false ), dispatch_chars(),
OSC_string(), terminal_to_host()
{}
void Dispatcher::newparamchar( const Parser::Param *act )
{
assert( act->char_present );
assert( (act->ch == ';') || ( (act->ch >= '0') && (act->ch <= '9') ) );
if ( params.length() < 100 ) {
/* enough for 16 five-char params plus 15 semicolons */
params.push_back( act->ch );
act->handled = true;
}
parsed = false;
}
void Dispatcher::collect( const Parser::Collect *act )
{
assert( act->char_present );
if ( ( dispatch_chars.length() < 8 ) /* never should need more than 2 */
&& ( act->ch <= 255 ) ) { /* ignore non-8-bit */
dispatch_chars.push_back( act->ch );
act->handled = true;
}
}
void Dispatcher::clear( const Parser::Clear *act )
{
params.clear();
dispatch_chars.clear();
parsed = false;
act->handled = true;
}
void Dispatcher::parse_params( void )
{
if ( parsed ) {
return;
}
parsed_params.clear();
const char *str = params.c_str();
const char *segment_begin = str;
while ( 1 ) {
const char *segment_end = strchr( segment_begin, ';' );
if ( segment_end == NULL ) {
break;
}
errno = 0;
char *endptr;
int val = strtol( segment_begin, &endptr, 10 );
if ( endptr == segment_begin ) {
val = -1;
}
if ( errno == 0 ) {
parsed_params.push_back( val );
}
segment_begin = segment_end + 1;
}
/* get last param */
errno = 0;
char *endptr;
int val = strtol( segment_begin, &endptr, 10 );
if ( endptr == segment_begin ) {
val = -1;
}
if ( errno == 0 ) {
parsed_params.push_back( val );
}
parsed = true;
}
int Dispatcher::getparam( size_t N, int defaultval )
{
int ret = defaultval;
if ( !parsed ) {
parse_params();
}
if ( parsed_params.size() > N ) {
ret = parsed_params[ N ];
}
if ( ret < 1 ) ret = defaultval;
return ret;
}
int Dispatcher::param_count( void )
{
if ( !parsed ) {
parse_params();
}
return parsed_params.size();
}
std::string Dispatcher::str( void )
{
char assum[ 64 ];
snprintf( assum, 64, "[dispatch=\"%s\" params=\"%s\"]",
dispatch_chars.c_str(), params.c_str() );
return std::string( assum );
}
/* construct on first use to avoid static initialization order crash */
DispatchRegistry & Terminal::get_global_dispatch_registry( void )
{
static DispatchRegistry global_dispatch_registry;
return global_dispatch_registry;
}
static void register_function( Function_Type type,
std::string dispatch_chars,
Function f )
{
switch ( type ) {
case ESCAPE:
get_global_dispatch_registry().escape.insert( dispatch_map_t::value_type( dispatch_chars, f ) );
break;
case CSI:
get_global_dispatch_registry().CSI.insert( dispatch_map_t::value_type( dispatch_chars, f ) );
break;
case CONTROL:
get_global_dispatch_registry().control.insert( dispatch_map_t::value_type( dispatch_chars, f ) );
break;
}
}
Function::Function( Function_Type type, std::string dispatch_chars,
void (*s_function)( Framebuffer *, Dispatcher * ),
bool s_clears_wrap_state )
: function( s_function ), clears_wrap_state( s_clears_wrap_state )
{
register_function( type, dispatch_chars, *this );
}
void Dispatcher::dispatch( Function_Type type, const Parser::Action *act, Framebuffer *fb )
{
/* add final char to dispatch key */
if ( (type == ESCAPE) || (type == CSI) ) {
assert( act->char_present );
Parser::Collect act2;
act2.char_present = true;
act2.ch = act->ch;
collect( &act2 );
}
dispatch_map_t *map = NULL;
switch ( type ) {
case ESCAPE: map = &get_global_dispatch_registry().escape; break;
case CSI: map = &get_global_dispatch_registry().CSI; break;
case CONTROL: map = &get_global_dispatch_registry().control; break;
}
std::string key = dispatch_chars;
if ( type == CONTROL ) {
assert( act->ch <= 255 );
char ctrlstr[ 2 ] = { (char)act->ch, 0 };
key = std::string( ctrlstr, 1 );
}
dispatch_map_t::const_iterator i = map->find( key );
if ( i == map->end() ) {
/* unknown function */
fb->ds.next_print_will_wrap = false;
return;
} else {
act->handled = true;
if ( i->second.clears_wrap_state ) {
fb->ds.next_print_will_wrap = false;
}
return i->second.function( fb, this );
}
}
void Dispatcher::OSC_put( const Parser::OSC_Put *act )
{
assert( act->char_present );
if ( OSC_string.size() < 256 ) { /* should be a long enough window title */
OSC_string.push_back( act->ch );
act->handled = true;
}
}
void Dispatcher::OSC_start( const Parser::OSC_Start *act )
{
OSC_string.clear();
act->handled = true;
}
bool Dispatcher::operator==( const Dispatcher &x ) const
{
return ( params == x.params ) && ( parsed_params == x.parsed_params ) && ( parsed == x.parsed )
&& ( dispatch_chars == x.dispatch_chars ) && ( OSC_string == x.OSC_string ) && ( terminal_to_host == x.terminal_to_host );
}
+86
View File
@@ -0,0 +1,86 @@
#ifndef TERMINALDISPATCHER_HPP
#define TERMINALDISPATCHER_HPP
#include <vector>
#include <string>
#include <map>
namespace Parser {
class Action;
class Param;
class Collect;
class Clear;
class Esc_Dispatch;
class CSI_Dispatch;
class Execute;
class OSC_Start;
class OSC_Put;
class OSC_End;
}
namespace Terminal {
class Framebuffer;
class Dispatcher;
enum Function_Type { ESCAPE, CSI, CONTROL };
class Function {
public:
Function() : function( NULL ), clears_wrap_state( true ) {}
Function( Function_Type type, std::string dispatch_chars,
void (*s_function)( Framebuffer *, Dispatcher * ),
bool s_clears_wrap_state = true );
void (*function)( Framebuffer *, Dispatcher * );
bool clears_wrap_state;
};
typedef std::map<std::string, Function> dispatch_map_t;
class DispatchRegistry {
public:
dispatch_map_t escape;
dispatch_map_t CSI;
dispatch_map_t control;
DispatchRegistry() : escape(), CSI(), control() {}
};
DispatchRegistry & get_global_dispatch_registry( void );
class Dispatcher {
private:
std::string params;
std::vector<int> parsed_params;
bool parsed;
std::string dispatch_chars;
std::vector<wchar_t> OSC_string; /* only used to set the window title */
void parse_params( void );
public:
std::string terminal_to_host; /* this is the reply string */
Dispatcher();
int getparam( size_t N, int defaultval );
int param_count( void );
void newparamchar( const Parser::Param *act );
void collect( const Parser::Collect *act );
void clear( const Parser::Clear *act );
std::string str( void );
void dispatch( Function_Type type, const Parser::Action *act, Framebuffer *fb );
std::string get_dispatch_chars( void ) { return dispatch_chars; }
std::vector<wchar_t> get_OSC_string( void ) { return OSC_string; }
void OSC_put( const Parser::OSC_Put *act );
void OSC_start( const Parser::OSC_Start *act );
void OSC_dispatch( const Parser::OSC_End *act, Framebuffer *fb );
bool operator==( const Dispatcher &x ) const;
};
}
#endif
+259
View File
@@ -0,0 +1,259 @@
#include <assert.h>
#include "terminaldisplay.h"
using namespace Terminal;
/* Print a new "frame" to the terminal, using ANSI/ECMA-48 escape codes. */
std::string Display::new_frame( bool initialized, const Framebuffer &last, const Framebuffer &f )
{
FrameState frame( last );
char tmp[ 64 ];
/* has bell been rung? */
if ( f.get_bell_count() != frame.last_frame.get_bell_count() ) {
frame.append( "\x07" );
}
/* has window title changed? */
if ( (!initialized)
|| (f.get_window_title() != frame.last_frame.get_window_title()) ) {
/* set window title */
frame.append( "\033]0;" );
const std::deque<wchar_t> &window_title( f.get_window_title() );
for ( auto i = window_title.begin();
i != window_title.end();
i++ ) {
snprintf( tmp, 64, "%lc", *i );
frame.append( tmp );
}
frame.append( "\033\\" );
}
/* has reverse video state changed? */
if ( (!initialized)
|| (f.ds.reverse_video != frame.last_frame.ds.reverse_video) ) {
/* set reverse video */
snprintf( tmp, 64, "\033[?5%c", (f.ds.reverse_video ? 'h' : 'l') );
frame.append( tmp );
}
/* has size changed? */
if ( (!initialized)
|| (f.ds.get_width() != frame.last_frame.ds.get_width())
|| (f.ds.get_height() != frame.last_frame.ds.get_height()) ) {
/* clear screen */
frame.append( "\033[0m\033[H\033[2J" );
initialized = false;
frame.cursor_x = frame.cursor_y = 0;
frame.current_rendition_string = "\033[0m";
} else {
frame.cursor_x = frame.last_frame.ds.get_cursor_col();
frame.cursor_y = frame.last_frame.ds.get_cursor_row();
frame.current_rendition_string = frame.last_frame.ds.get_renditions().sgr();
}
/* shortcut -- has display moved up by a certain number of lines? */
frame.y = 0;
if ( initialized ) {
int lines_scrolled = 0;
int scroll_height = 0;
for ( int row = 0; row < f.ds.get_height(); row++ ) {
if ( *(f.get_row( 0 )) == *(frame.last_frame.get_row( row )) ) {
/* found a scroll */
lines_scrolled = row;
scroll_height = 1;
/* how big is the region that was scrolled? */
for ( int region_height = 1;
lines_scrolled + region_height < f.ds.get_height();
region_height++ ) {
if ( *(f.get_row( region_height ))
== *(frame.last_frame.get_row( lines_scrolled + region_height )) ) {
scroll_height = region_height + 1;
} else {
break;
}
}
break;
}
}
if ( scroll_height ) {
frame.y = scroll_height;
if ( lines_scrolled ) {
if ( frame.cursor_y != f.ds.get_height() - 1 ) {
frame.append_silent_move( f.ds.get_height() - 1, 0 );
}
if ( frame.current_rendition_string != "\033[0m" ) {
frame.append( "\033[0m" );
frame.current_rendition_string = "\033[0m";
}
for ( int i = 0; i < lines_scrolled; i++ ) {
frame.append( "\n" );
}
for ( int i = 0; i < f.ds.get_height(); i++ ) {
if ( i + lines_scrolled < f.ds.get_height() ) {
*(frame.last_frame.get_mutable_row( i )) = *(frame.last_frame.get_row( i + lines_scrolled ));
} else {
frame.last_frame.get_mutable_row( i )->reset( 0 );
}
}
}
}
}
/* iterate for every cell */
for ( ; frame.y < f.ds.get_height(); frame.y++ ) {
int last_x = 0;
for ( frame.x = 0;
frame.x < f.ds.get_width(); /* let put_cell() handle advance */ ) {
last_x = frame.x;
put_cell( initialized, frame, f );
/* To hint that a word-select should group the end of one line
with the beginning of the next, we let the real cursor
actually wrap around in cases where it wrapped around for us. */
if ( (frame.cursor_x >= f.ds.get_width())
&& (frame.y < f.ds.get_height() - 1)
&& f.get_row( frame.y )->wrap
&& (!initialized || !frame.last_frame.get_row( frame.y )->wrap) ) {
/* next write will wrap */
frame.cursor_x = 0;
frame.cursor_y++;
}
}
/* Turn off wrap */
if ( (frame.y < f.ds.get_height() - 1)
&& (!f.get_row( frame.y )->wrap)
&& (!initialized || frame.last_frame.get_row( frame.y )->wrap) ) {
frame.x = last_x;
if ( initialized ) {
frame.last_frame.reset_cell( frame.last_frame.get_mutable_cell( frame.y, frame.x ) );
}
snprintf( tmp, 64, "\033[%d;%dH\033[K", frame.y + 1, frame.x + 1 );
frame.append( tmp );
frame.cursor_x = frame.x;
put_cell( initialized, frame, f );
}
}
/* has cursor location changed? */
if ( (!initialized)
|| (f.ds.get_cursor_row() != frame.cursor_y)
|| (f.ds.get_cursor_col() != frame.cursor_x) ) {
snprintf( tmp, 64, "\033[%d;%dH", f.ds.get_cursor_row() + 1,
f.ds.get_cursor_col() + 1 );
frame.append( tmp );
frame.cursor_x = f.ds.get_cursor_col();
frame.cursor_y = f.ds.get_cursor_row();
}
/* has cursor visibility changed? */
if ( (!initialized)
|| (f.ds.cursor_visible != frame.last_frame.ds.cursor_visible) ) {
if ( f.ds.cursor_visible ) {
frame.append( "\033[?25h" );
} else {
frame.append( "\033[?25l" );
}
}
/* have renditions changed? */
if ( (!initialized)
|| (f.ds.get_renditions().sgr() != frame.current_rendition_string) ) {
frame.appendstring( f.ds.get_renditions().sgr() );
frame.current_rendition_string = f.ds.get_renditions().sgr();
}
return frame.str;
}
void Display::put_cell( bool initialized, FrameState &frame, const Framebuffer &f )
{
char tmp[ 64 ];
const Cell *cell = f.get_cell( frame.y, frame.x );
if ( initialized
&& ( *cell == *(frame.last_frame.get_cell( frame.y, frame.x )) ) ) {
frame.x += cell->width;
return;
}
if ( (frame.x != frame.cursor_x) || (frame.y != frame.cursor_y) ) {
frame.append_silent_move( frame.y, frame.x );
}
std::string rendition_str = cell->renditions.sgr();
if ( frame.current_rendition_string != rendition_str ) {
/* print renditions */
frame.appendstring( rendition_str );
frame.current_rendition_string = rendition_str;
}
if ( cell->contents.empty() ) {
/* see how far we can stretch a clear */
int clear_count = 0;
for ( int col = frame.x; col < f.ds.get_width(); col++ ) {
const Cell *other_cell = f.get_cell( frame.y, col );
if ( (cell->renditions == other_cell->renditions)
&& (other_cell->contents.empty()) ) {
clear_count++;
} else {
break;
}
}
snprintf( tmp, 64, "\033[%dX", clear_count );
frame.append( tmp );
frame.x += clear_count;
return;
}
/* cells that begin with combining character get combiner attached to no-break space */
if ( cell->fallback ) {
snprintf( tmp, 64, "%lc", 0xA0 );
frame.append( tmp );
}
for ( std::vector<wchar_t>::const_iterator i = cell->contents.begin();
i != cell->contents.end();
i++ ) {
snprintf( tmp, 64, "%lc", *i );
frame.append( tmp );
}
frame.x += cell->width;
frame.cursor_x += cell->width;
}
void FrameState::append_silent_move( int y, int x )
{
char tmp[ 64 ];
/* turn off cursor if necessary before moving cursor */
if ( last_frame.ds.cursor_visible ) {
append( "\033[?25l" );
last_frame.ds.cursor_visible = false;
}
snprintf( tmp, 64, "\033[%d;%dH", y + 1, x + 1 );
append( tmp );
cursor_x = x;
cursor_y = y;
}
+41
View File
@@ -0,0 +1,41 @@
#ifndef TERMINALDISPLAY_HPP
#define TERMINALDISPLAY_HPP
#include "terminalframebuffer.h"
namespace Terminal {
/* variables used within a new_frame */
class FrameState {
public:
int x, y;
std::string str;
int cursor_x, cursor_y;
std::string current_rendition_string;
Framebuffer last_frame;
FrameState( const Framebuffer &s_last )
: x(0), y(0),
str(), cursor_x(0), cursor_y(0), current_rendition_string(),
last_frame( s_last )
{
str.reserve( 1024 );
}
void append( const char * s ) { str.append( s ); }
void appendstring( const std::string s ) { str.append( s ); }
void append_silent_move( int y, int x );
};
class Display {
private:
static void put_cell( bool initialized, FrameState &frame, const Framebuffer &f );
public:
static std::string new_frame( bool initialized, const Framebuffer &last, const Framebuffer &f );
};
}
#endif
+425
View File
@@ -0,0 +1,425 @@
#include <assert.h>
#include "terminalframebuffer.h"
using namespace Terminal;
void Cell::reset( int background_color )
{
contents.clear();
fallback = false;
width = 1;
renditions = Renditions( background_color );
}
DrawState::DrawState( int s_width, int s_height )
: width( s_width ), height( s_height ),
cursor_col( 0 ), cursor_row( 0 ),
combining_char_col( 0 ), combining_char_row( 0 ), tabs( s_width ),
scrolling_region_top_row( 0 ), scrolling_region_bottom_row( height - 1 ),
renditions( 0 ), save(),
next_print_will_wrap( false ), origin_mode( false ), auto_wrap_mode( true ),
insert_mode( false ), cursor_visible( true ), reverse_video( false ),
application_mode_cursor_keys( false )
{
for ( int i = 0; i < width; i++ ) {
tabs[ i ] = ( (i % 8) == 0 );
}
}
Framebuffer::Framebuffer( int s_width, int s_height )
: rows( s_height, Row( s_width, 0 ) ), window_title(), bell_count( 0 ), ds( s_width, s_height )
{
assert( s_height > 0 );
assert( s_width > 0 );
}
void Framebuffer::scroll( int N )
{
if ( N >= 0 ) {
for ( int i = 0; i < N; i++ ) {
delete_line( ds.get_scrolling_region_top_row() );
ds.move_row( -1, true );
}
} else {
N = -N;
for ( int i = 0; i < N; i++ ) {
rows.insert( rows.begin() + ds.get_scrolling_region_top_row(), newrow() );
rows.erase( rows.begin() + ds.get_scrolling_region_bottom_row() + 1 );
ds.move_row( 1, true );
}
}
}
void DrawState::new_grapheme( void )
{
combining_char_col = cursor_col;
combining_char_row = cursor_row;
}
void DrawState::snap_cursor_to_border( void )
{
if ( cursor_row < limit_top() ) cursor_row = limit_top();
if ( cursor_row > limit_bottom() ) cursor_row = limit_bottom();
if ( cursor_col < 0 ) cursor_col = 0;
if ( cursor_col >= width ) cursor_col = width - 1;
}
void DrawState::move_row( int N, bool relative )
{
if ( relative ) {
cursor_row += N;
} else {
cursor_row = N + limit_top();
}
snap_cursor_to_border();
new_grapheme();
next_print_will_wrap = false;
}
void DrawState::move_col( int N, bool relative, bool implicit )
{
if ( implicit ) {
new_grapheme();
}
if ( relative ) {
cursor_col += N;
} else {
cursor_col = N;
}
if ( implicit && (cursor_col >= width) ) {
next_print_will_wrap = true;
}
snap_cursor_to_border();
if ( !implicit ) {
new_grapheme();
next_print_will_wrap = false;
}
}
void Framebuffer::move_rows_autoscroll( int rows )
{
/* don't scroll if outside the scrolling region */
if ( (ds.get_cursor_row() < ds.get_scrolling_region_top_row())
|| (ds.get_cursor_row() > ds.get_scrolling_region_bottom_row()) ) {
ds.move_row( rows, true );
return;
}
if ( ds.get_cursor_row() + rows > ds.get_scrolling_region_bottom_row() ) {
scroll( ds.get_cursor_row() + rows - ds.get_scrolling_region_bottom_row() );
} else if ( ds.get_cursor_row() + rows < ds.get_scrolling_region_top_row() ) {
scroll( ds.get_cursor_row() + rows - ds.get_scrolling_region_top_row() );
}
ds.move_row( rows, true );
}
Cell *Framebuffer::get_combining_cell( void )
{
if ( (ds.get_combining_char_col() < 0)
|| (ds.get_combining_char_row() < 0)
|| (ds.get_combining_char_col() >= ds.get_width())
|| (ds.get_combining_char_row() >= ds.get_height()) ) {
return NULL;
} /* can happen if a resize came in between */
return &rows[ ds.get_combining_char_row() ].cells[ ds.get_combining_char_col() ];
}
void DrawState::set_tab( void )
{
tabs[ cursor_col ] = true;
}
void DrawState::clear_tab( int col )
{
tabs[ col ] = false;
}
int DrawState::get_next_tab( void )
{
for ( int i = cursor_col + 1; i < width; i++ ) {
if ( tabs[ i ] ) {
return i;
}
}
return -1;
}
void DrawState::set_scrolling_region( int top, int bottom )
{
if ( height < 1 ) {
return;
}
scrolling_region_top_row = top;
scrolling_region_bottom_row = bottom;
if ( scrolling_region_top_row < 0 ) scrolling_region_top_row = 0;
if ( scrolling_region_bottom_row >= height ) scrolling_region_bottom_row = height - 1;
if ( scrolling_region_bottom_row < scrolling_region_top_row )
scrolling_region_bottom_row = scrolling_region_top_row;
/* real rule requires TWO-line scrolling region */
if ( origin_mode ) {
snap_cursor_to_border();
new_grapheme();
}
}
int DrawState::limit_top( void )
{
return origin_mode ? scrolling_region_top_row : 0;
}
int DrawState::limit_bottom( void )
{
return origin_mode ? scrolling_region_bottom_row : height - 1;
}
std::vector<int> DrawState::get_tabs( void )
{
std::vector<int> ret;
for ( int i = 0; i < width; i++ ) {
if ( tabs[ i ] ) {
ret.push_back( i );
}
}
return ret;
}
void Framebuffer::apply_renditions_to_current_cell( void )
{
get_mutable_cell()->renditions = ds.get_renditions();
}
SavedCursor::SavedCursor()
: cursor_col( 0 ), cursor_row( 0 ),
renditions( 0 ),
auto_wrap_mode( true ),
origin_mode( false )
{}
void DrawState::save_cursor( void )
{
save.cursor_col = cursor_col;
save.cursor_row = cursor_row;
save.renditions = renditions;
save.auto_wrap_mode = auto_wrap_mode;
save.origin_mode = origin_mode;
}
void DrawState::restore_cursor( void )
{
cursor_col = save.cursor_col;
cursor_row = save.cursor_row;
renditions = save.renditions;
auto_wrap_mode = save.auto_wrap_mode;
origin_mode = save.origin_mode;
snap_cursor_to_border(); /* we could have resized in between */
new_grapheme();
}
void Framebuffer::insert_line( int before_row )
{
if ( (before_row < ds.get_scrolling_region_top_row())
|| (before_row > ds.get_scrolling_region_bottom_row() + 1) ) {
return;
}
rows.insert( rows.begin() + before_row, newrow() );
rows.erase( rows.begin() + ds.get_scrolling_region_bottom_row() + 1 );
}
void Framebuffer::delete_line( int row )
{
if ( (row < ds.get_scrolling_region_top_row())
|| (row > ds.get_scrolling_region_bottom_row()) ) {
return;
}
int insertbefore = ds.get_scrolling_region_bottom_row() + 1;
if ( insertbefore == ds.get_height() ) {
rows.push_back( newrow() );
} else {
rows.insert( rows.begin() + insertbefore, newrow() );
}
rows.erase( rows.begin() + row );
}
void Row::insert_cell( int col, int background_color )
{
cells.insert( cells.begin() + col, Cell( background_color ) );
cells.pop_back();
}
void Row::delete_cell( int col, int background_color )
{
cells.push_back( Cell( background_color ) );
cells.erase( cells.begin() + col );
}
void Framebuffer::insert_cell( int row, int col )
{
rows[ row ].insert_cell( col, ds.get_background_rendition() );
}
void Framebuffer::delete_cell( int row, int col )
{
rows[ row ].delete_cell( col, ds.get_background_rendition() );
}
void Framebuffer::reset( void )
{
int width = ds.get_width(), height = ds.get_height();
ds = DrawState( width, height );
rows = std::deque<Row>( height, newrow() );
window_title.clear();
/* do not reset bell_count */
}
void Framebuffer::soft_reset( void )
{
ds.insert_mode = false;
ds.origin_mode = false;
ds.cursor_visible = true; /* per xterm and gnome-terminal */
ds.application_mode_cursor_keys = false;
ds.set_scrolling_region( 0, ds.get_height() - 1 );
ds.add_rendition( 0 );
ds.clear_saved_cursor();
}
void Framebuffer::resize( int s_width, int s_height )
{
assert( s_width > 0 );
assert( s_height > 0 );
rows.resize( s_height, newrow() );
for ( std::deque<Row>::iterator i = rows.begin();
i != rows.end();
i++ ) {
(*i).cells.resize( s_width, Cell( ds.get_background_rendition() ) );
}
ds.resize( s_width, s_height );
}
void DrawState::resize( int s_width, int s_height )
{
if ( (width != s_width)
|| (height != s_height) ) {
/* reset entire scrolling region on any resize */
/* xterm and rxvt-unicode do this. gnome-terminal only
resets scrolling region if it has to become smaller in resize */
scrolling_region_top_row = 0;
scrolling_region_bottom_row = s_height - 1;
}
width = s_width;
height = s_height;
snap_cursor_to_border();
tabs.resize( width );
/* saved cursor will be snapped to border on restore */
/* invalidate combining char cell if necessary */
if ( (combining_char_col >= width)
|| (combining_char_row >= height) ) {
combining_char_col = combining_char_row = -1;
}
}
Renditions::Renditions( int s_background )
: bold( false ), underlined( false ), blink( false ),
inverse( false ), invisible( false ), foreground_color( 0 ),
background_color( s_background )
{}
void Renditions::set_rendition( int num )
{
if ( num == 0 ) {
bold = underlined = blink = inverse = invisible = false;
foreground_color = background_color = 0;
return;
}
if ( (30 <= num) && (num <= 39) ) { /* foreground color */
foreground_color = num;
return;
} else if ( (40 <= num) && (num <= 49) ) { /* background color */
background_color = num;
return;
}
switch ( num ) {
case 1: case 22: bold = (num == 1); break;
case 4: case 24: underlined = (num == 4); break;
case 5: case 25: blink = (num == 5); break;
case 7: case 27: inverse = (num == 7); break;
case 8: case 28: invisible = (num == 8); break;
}
}
std::string Renditions::sgr( void ) const
{
std::string ret;
ret.append( "\033[0" );
if ( bold ) ret.append( ";1" );
if ( underlined ) ret.append( ";4" );
if ( blink ) ret.append( ";5" );
if ( inverse ) ret.append( ";7" );
if ( invisible ) ret.append( ";8" );
if ( foreground_color ) {
char col[ 8 ];
snprintf( col, 8, ";%d", foreground_color );
ret.append( col );
}
if ( background_color ) {
char col[ 8 ];
snprintf( col, 8, ";%d", background_color );
ret.append( col );
}
ret.append( "m" );
return ret;
}
void Row::reset( int background_color )
{
for ( std::vector<Cell>::iterator i = cells.begin();
i != cells.end();
i++ ) {
i->reset( background_color );
}
}
void Framebuffer::prefix_window_title( const std::deque<wchar_t> &s )
{
for ( auto i = s.rbegin(); i != s.rend(); i++ ) {
window_title.push_front( *i );
}
}
wchar_t Cell::debug_contents( void ) const
{
if ( contents.empty() ) {
return '_';
} else {
return contents.front();
}
}
+276
View File
@@ -0,0 +1,276 @@
#ifndef TERMINALFB_HPP
#define TERMINALFB_HPP
#include <vector>
#include <deque>
#include <string>
#include <list>
#include <assert.h>
/* Terminal framebuffer */
namespace Terminal {
class Renditions {
public:
bool bold, underlined, blink, inverse, invisible;
int foreground_color;
int background_color;
Renditions( int s_background );
void set_rendition( int num );
std::string sgr( void ) const;
bool operator==( const Renditions &x ) const
{
return (bold == x.bold) && (underlined == x.underlined)
&& (blink == x.blink) && (inverse == x.inverse)
&& (invisible == x.invisible) && (foreground_color == x.foreground_color)
&& (background_color == x.background_color);
}
};
class Cell {
public:
std::vector<wchar_t> contents;
char fallback; /* first character is combining character */
int width;
Renditions renditions;
Cell( int background_color )
: contents(),
fallback( false ),
width( 1 ),
renditions( background_color )
{}
Cell() /* default constructor required by C++11 STL */
: contents(),
fallback( false ),
width( 1 ),
renditions( 0 )
{
assert( false );
}
void reset( int background_color );
bool operator==( const Cell &x ) const
{
return ( (contents == x.contents)
&& (fallback == x.fallback)
&& (width == x.width)
&& (renditions == x.renditions) );
}
wchar_t debug_contents( void ) const;
bool is_blank( void ) const
{
return ( contents.empty()
|| ( (contents.size() == 1) && ( (contents.front() == 0x20)
|| (contents.front() == 0xA0) ) ) );
}
};
class Row {
public:
std::vector<Cell> cells;
bool wrap;
Row( size_t s_width, int background_color )
: cells( s_width, Cell( background_color ) ), wrap( false )
{}
Row() /* default constructor required by C++11 STL */
: cells( 1, Cell() ), wrap( false )
{
assert( false );
}
void insert_cell( int col, int background_color );
void delete_cell( int col, int background_color );
void reset( int background_color );
bool operator==( const Row &x ) const
{
return ( (cells == x.cells) && (wrap == x.wrap) );
}
};
class SavedCursor {
public:
int cursor_col, cursor_row;
Renditions renditions;
/* not implemented: character set shift state */
bool auto_wrap_mode;
bool origin_mode;
/* not implemented: state of selective erase */
SavedCursor();
};
class DrawState {
private:
int width, height;
void new_grapheme( void );
void snap_cursor_to_border( void );
int cursor_col, cursor_row;
int combining_char_col, combining_char_row;
std::vector<bool> tabs;
int scrolling_region_top_row, scrolling_region_bottom_row;
Renditions renditions;
SavedCursor save;
public:
bool next_print_will_wrap;
bool origin_mode;
bool auto_wrap_mode;
bool insert_mode;
bool cursor_visible;
bool reverse_video;
bool application_mode_cursor_keys;
/* bold, etc. */
void move_row( int N, bool relative = false );
void move_col( int N, bool relative = false, bool implicit = false );
int get_cursor_col( void ) const { return cursor_col; }
int get_cursor_row( void ) const { return cursor_row; }
int get_combining_char_col( void ) const { return combining_char_col; }
int get_combining_char_row( void ) const { return combining_char_row; }
int get_width( void ) const { return width; }
int get_height( void ) const { return height; }
void set_tab( void );
void clear_tab( int col );
int get_next_tab( void );
std::vector<int> get_tabs( void );
void set_scrolling_region( int top, int bottom );
int get_scrolling_region_top_row( void ) const { return scrolling_region_top_row; }
int get_scrolling_region_bottom_row( void ) const { return scrolling_region_bottom_row; }
int limit_top( void );
int limit_bottom( void );
void add_rendition( int x ) { renditions.set_rendition( x ); }
Renditions get_renditions( void ) const { return renditions; }
int get_background_rendition( void ) { return renditions.background_color; }
void save_cursor( void );
void restore_cursor( void );
void clear_saved_cursor( void ) { save = SavedCursor(); }
void resize( int s_width, int s_height );
DrawState( int s_width, int s_height );
bool operator==( const DrawState &x ) const
{
/* only compare fields that affect display */
return ( width == x.width ) && ( height == x.height ) && ( cursor_col == x.cursor_col )
&& ( cursor_row == x.cursor_row ) && ( cursor_visible == x.cursor_visible ) &&
( reverse_video == x.reverse_video ) && ( renditions == x.renditions );
}
};
class Framebuffer {
private:
std::deque<Row> rows;
std::deque<wchar_t> window_title;
unsigned int bell_count;
Row newrow( void ) { return Row( ds.get_width(), ds.get_background_rendition() ); }
public:
Framebuffer( int s_width, int s_height );
DrawState ds;
void scroll( int N );
void move_rows_autoscroll( int rows );
const Row *get_row( int row ) const
{
if ( row == -1 ) row = ds.get_cursor_row();
return &rows[ row ];
}
inline const Cell *get_cell( void ) const
{
return &rows[ ds.get_cursor_row() ].cells[ ds.get_cursor_col() ];
}
inline const Cell *get_cell( int row, int col ) const
{
if ( row == -1 ) row = ds.get_cursor_row();
if ( col == -1 ) col = ds.get_cursor_col();
return &rows[ row ].cells[ col ];
}
Row *get_mutable_row( int row )
{
if ( row == -1 ) row = ds.get_cursor_row();
return &rows[ row ];
}
inline Cell *get_mutable_cell( void )
{
return &rows[ ds.get_cursor_row() ].cells[ ds.get_cursor_col() ];
}
inline Cell *get_mutable_cell( int row, int col )
{
if ( row == -1 ) row = ds.get_cursor_row();
if ( col == -1 ) col = ds.get_cursor_col();
return &rows[ row ].cells[ col ];
}
Cell *get_combining_cell( void );
void apply_renditions_to_current_cell( void );
void insert_line( int before_row );
void delete_line( int row );
void insert_cell( int row, int col );
void delete_cell( int row, int col );
void reset( void );
void soft_reset( void );
void set_window_title( const std::deque<wchar_t> &s ) { window_title = s; }
const std::deque<wchar_t> & get_window_title( void ) const { return window_title; }
void prefix_window_title( const std::deque<wchar_t> &s );
void resize( int s_width, int s_height );
void reset_cell( Cell *c ) { c->reset( ds.get_background_rendition() ); }
void reset_row( Row *r ) { r->reset( ds.get_background_rendition() ); }
void ring_bell( void ) { bell_count++; }
unsigned int get_bell_count( void ) const { return bell_count; }
bool operator==( const Framebuffer &x ) const
{
return ( rows == x.rows ) && ( window_title == x.window_title ) && ( bell_count == x.bell_count ) && ( ds == x.ds );
}
};
}
#endif
+493
View File
@@ -0,0 +1,493 @@
#include <unistd.h>
#include <string>
#include "terminaldispatcher.h"
#include "terminalframebuffer.h"
#include "parseraction.h"
using namespace Terminal;
/* Terminal functions -- routines activated by CSI, escape or a control char */
static void clearline( Framebuffer *fb, int row, int start, int end )
{
for ( int col = start; col <= end; col++ ) {
fb->reset_cell( fb->get_mutable_cell( row, col ) );
}
}
/* erase in line */
void CSI_EL( Framebuffer *fb, Dispatcher *dispatch )
{
switch ( dispatch->getparam( 0, 0 ) ) {
case 0: /* default: active position to end of line, inclusive */
clearline( fb, -1, fb->ds.get_cursor_col(), fb->ds.get_width() - 1 );
break;
case 1: /* start of screen to active position, inclusive */
clearline( fb, -1, 0, fb->ds.get_cursor_col() );
break;
case 2: /* all of line */
fb->reset_row( fb->get_mutable_row( -1 ) );
break;
}
}
static Function func_CSI_EL( CSI, "K", CSI_EL );
/* erase in display */
void CSI_ED( Framebuffer *fb, Dispatcher *dispatch ) {
switch ( dispatch->getparam( 0, 0 ) ) {
case 0: /* active position to end of screen, inclusive */
clearline( fb, -1, fb->ds.get_cursor_col(), fb->ds.get_width() - 1 );
for ( int y = fb->ds.get_cursor_row() + 1; y < fb->ds.get_height(); y++ ) {
fb->reset_row( fb->get_mutable_row( y ) );
}
break;
case 1: /* start of screen to active position, inclusive */
for ( int y = 0; y < fb->ds.get_cursor_row(); y++ ) {
fb->reset_row( fb->get_mutable_row( y ) );
}
clearline( fb, -1, 0, fb->ds.get_cursor_col() );
break;
case 2: /* entire screen */
for ( int y = 0; y < fb->ds.get_height(); y++ ) {
fb->reset_row( fb->get_mutable_row( y ) );
}
break;
}
}
static Function func_CSI_ED( CSI, "J", CSI_ED );
/* cursor movement -- relative and absolute */
void CSI_cursormove( Framebuffer *fb, Dispatcher *dispatch )
{
int num = dispatch->getparam( 0, 1 );
switch ( dispatch->get_dispatch_chars()[ 0 ] ) {
case 'A':
fb->ds.move_row( -num, true );
break;
case 'B':
fb->ds.move_row( num, true );
break;
case 'C':
fb->ds.move_col( num, true );
break;
case 'D':
fb->ds.move_col( -num, true );
break;
case 'H':
case 'f':
int x = dispatch->getparam( 0, 1 );
int y = dispatch->getparam( 1, 1 );
fb->ds.move_row( x - 1 );
fb->ds.move_col( y - 1 );
}
}
static Function func_CSI_cursormove_A( CSI, "A", CSI_cursormove );
static Function func_CSI_cursormove_B( CSI, "B", CSI_cursormove );
static Function func_CSI_cursormove_C( CSI, "C", CSI_cursormove );
static Function func_CSI_cursormove_D( CSI, "D", CSI_cursormove );
static Function func_CSI_cursormove_H( CSI, "H", CSI_cursormove );
static Function func_CSI_cursormove_f( CSI, "f", CSI_cursormove );
/* device attributes */
void CSI_DA( Framebuffer *fb __attribute((unused)), Dispatcher *dispatch )
{
dispatch->terminal_to_host.append( "\033[?62c" ); /* plain vt220 */
}
static Function func_CSI_DA( CSI, "c", CSI_DA );
/* secondary device attributes */
void CSI_SDA( Framebuffer *fb __attribute((unused)), Dispatcher *dispatch )
{
dispatch->terminal_to_host.append( "\033[>1;10;0c" ); /* plain vt220 */
}
static Function func_CSI_SDA( CSI, ">c", CSI_SDA );
/* screen alignment diagnostic */
void Esc_DECALN( Framebuffer *fb, Dispatcher *dispatch __attribute((unused)) )
{
for ( int y = 0; y < fb->ds.get_height(); y++ ) {
for ( int x = 0; x < fb->ds.get_width(); x++ ) {
fb->reset_cell( fb->get_mutable_cell( y, x ) );
fb->get_mutable_cell( y, x )->contents.push_back( L'E' );
}
}
}
static Function func_Esc_DECALN( ESCAPE, "#8", Esc_DECALN );
/* line feed */
void Ctrl_LF( Framebuffer *fb, Dispatcher *dispatch __attribute((unused)) )
{
fb->move_rows_autoscroll( 1 );
}
/* same procedure for index, vertical tab, and form feed control codes */
static Function func_Ctrl_LF( CONTROL, "\x0a", Ctrl_LF );
static Function func_Ctrl_IND( CONTROL, "\x84", Ctrl_LF );
static Function func_Ctrl_VT( CONTROL, "\x0b", Ctrl_LF );
static Function func_Ctrl_FF( CONTROL, "\x0c", Ctrl_LF );
/* carriage return */
void Ctrl_CR( Framebuffer *fb, Dispatcher *dispatch __attribute((unused)) )
{
fb->ds.move_col( 0 );
}
static Function func_Ctrl_CR( CONTROL, "\x0d", Ctrl_CR );
/* backspace */
void Ctrl_BS( Framebuffer *fb, Dispatcher *dispatch __attribute((unused)) )
{
fb->ds.move_col( -1, true );
}
static Function func_Ctrl_BS( CONTROL, "\x08", Ctrl_BS );
/* reverse index -- like a backwards line feed */
void Ctrl_RI( Framebuffer *fb, Dispatcher *dispatch __attribute((unused)) )
{
fb->move_rows_autoscroll( -1 );
}
static Function func_Ctrl_RI( CONTROL, "\x8D", Ctrl_RI );
/* newline */
void Ctrl_NEL( Framebuffer *fb, Dispatcher *dispatch __attribute((unused)) )
{
fb->ds.move_col( 0 );
fb->move_rows_autoscroll( 1 );
}
static Function func_Ctrl_NEL( CONTROL, "\x85", Ctrl_NEL );
/* horizontal tab */
void Ctrl_HT( Framebuffer *fb, Dispatcher *dispatch __attribute((unused)) )
{
int col = fb->ds.get_next_tab();
if ( col == -1 ) { /* no tabs, go to end of line */
fb->ds.move_col( fb->ds.get_width() - 1 );
} else {
fb->ds.move_col( col );
}
}
static Function func_Ctrl_HT( CONTROL, "\x09", Ctrl_HT );
/* horizontal tab set */
void Ctrl_HTS( Framebuffer *fb, Dispatcher *dispatch __attribute((unused)) )
{
fb->ds.set_tab();
}
static Function func_Ctrl_HTS( CONTROL, "\x88", Ctrl_HTS );
/* tabulation clear */
void CSI_TBC( Framebuffer *fb, Dispatcher *dispatch )
{
int param = dispatch->getparam( 0, 0 );
switch ( param ) {
case 0: /* clear this tab stop */
fb->ds.clear_tab( fb->ds.get_cursor_col() );
break;
case 3: /* clear all tab stops */
for ( int x = 0; x < fb->ds.get_width(); x++ ) {
fb->ds.clear_tab( x );
}
break;
}
}
static Function func_CSI_TBC( CSI, "g", CSI_TBC );
static bool *get_DEC_mode( int param, Framebuffer *fb ) {
switch ( param ) {
case 1: /* cursor key mode */
return &(fb->ds.application_mode_cursor_keys);
case 3: /* 80/132. Ignore but clear screen. */
/* clear screen */
fb->ds.move_row( 0 );
fb->ds.move_col( 0 );
for ( int y = 0; y < fb->ds.get_height(); y++ ) {
fb->reset_row( fb->get_mutable_row( y ) );
}
return NULL;
case 5: /* reverse video */
return &(fb->ds.reverse_video);
case 6: /* origin */
fb->ds.move_row( 0 );
fb->ds.move_col( 0 );
return &(fb->ds.origin_mode);
case 7: /* auto wrap */
return &(fb->ds.auto_wrap_mode);
case 25:
return &(fb->ds.cursor_visible);
}
return NULL;
}
/* set private mode */
void CSI_DECSM( Framebuffer *fb, Dispatcher *dispatch )
{
for ( int i = 0; i < dispatch->param_count(); i++ ) {
bool *mode = get_DEC_mode( dispatch->getparam( i, 0 ), fb );
if ( mode ) {
*mode = true;
}
}
}
/* clear private mode */
void CSI_DECRM( Framebuffer *fb, Dispatcher *dispatch )
{
for ( int i = 0; i < dispatch->param_count(); i++ ) {
bool *mode = get_DEC_mode( dispatch->getparam( i, 0 ), fb );
if ( mode ) {
*mode = false;
}
}
}
static Function func_CSI_DECSM( CSI, "?h", CSI_DECSM );
static Function func_CSI_DECRM( CSI, "?l", CSI_DECRM );
static bool *get_ANSI_mode( int param, Framebuffer *fb ) {
switch ( param ) {
case 4: /* insert/replace mode */
return &(fb->ds.insert_mode);
}
return NULL;
}
/* set mode */
void CSI_SM( Framebuffer *fb, Dispatcher *dispatch )
{
for ( int i = 0; i < dispatch->param_count(); i++ ) {
bool *mode = get_ANSI_mode( dispatch->getparam( i, 0 ), fb );
if ( mode ) {
*mode = true;
}
}
}
/* clear mode */
void CSI_RM( Framebuffer *fb, Dispatcher *dispatch )
{
for ( int i = 0; i < dispatch->param_count(); i++ ) {
bool *mode = get_ANSI_mode( dispatch->getparam( i, 0 ), fb );
if ( mode ) {
*mode = false;
}
}
}
static Function func_CSI_SM( CSI, "h", CSI_SM );
static Function func_CSI_RM( CSI, "l", CSI_RM );
/* set top and bottom margins */
void CSI_DECSTBM( Framebuffer *fb, Dispatcher *dispatch )
{
int top = dispatch->getparam( 0, 1 );
int bottom = dispatch->getparam( 1, fb->ds.get_height() );
fb->ds.set_scrolling_region( top - 1, bottom - 1 );
fb->ds.move_row( 0 );
fb->ds.move_col( 0 );
}
static Function func_CSI_DECSTMB( CSI, "r", CSI_DECSTBM );
/* terminal bell */
void Ctrl_BEL( Framebuffer *fb, Dispatcher *dispatch __attribute((unused)) ) {
fb->ring_bell();
}
static Function func_Ctrl_BEL( CONTROL, "\x07", Ctrl_BEL );
/* select graphics rendition -- e.g., bold, blinking, etc. */
void CSI_SGR( Framebuffer *fb, Dispatcher *dispatch )
{
for ( int i = 0; i < dispatch->param_count(); i++ ) {
int rendition = dispatch->getparam( i, 0 );
fb->ds.add_rendition( rendition );
}
}
static Function func_CSI_SGR( CSI, "m", CSI_SGR, false ); /* changing renditions doesn't clear wrap flag */
/* save and restore cursor */
void Esc_DECSC( Framebuffer *fb, Dispatcher *dispatch __attribute((unused)) )
{
fb->ds.save_cursor();
}
void Esc_DECRC( Framebuffer *fb, Dispatcher *dispatch __attribute((unused)) )
{
fb->ds.restore_cursor();
}
static Function func_Esc_DECSC( ESCAPE, "7", Esc_DECSC );
static Function func_Esc_DECRC( ESCAPE, "8", Esc_DECRC );
/* device status report -- e.g., cursor position (used by resize) */
void CSI_DSR( Framebuffer *fb, Dispatcher *dispatch )
{
int param = dispatch->getparam( 0, 0 );
switch ( param ) {
case 5: /* device status report requested */
dispatch->terminal_to_host.append( "\033[0n" );
break;
case 6: /* report of active position requested */
char cpr[ 32 ];
snprintf( cpr, 32, "\033[%d;%dR",
fb->ds.get_cursor_row() + 1,
fb->ds.get_cursor_col() + 1 );
dispatch->terminal_to_host.append( cpr );
break;
}
}
static Function func_CSI_DSR( CSI, "n", CSI_DSR );
/* insert line */
void CSI_IL( Framebuffer *fb, Dispatcher *dispatch )
{
int lines = dispatch->getparam( 0, 1 );
for ( int i = 0; i < lines; i++ ) {
fb->insert_line( fb->ds.get_cursor_row() );
}
/* vt220 manual and Ecma-48 say to move to first column */
/* but xterm and gnome-terminal don't */
fb->ds.move_col( 0 );
}
static Function func_CSI_IL( CSI, "L", CSI_IL );
/* delete line */
void CSI_DL( Framebuffer *fb, Dispatcher *dispatch )
{
int lines = dispatch->getparam( 0, 1 );
for ( int i = 0; i < lines; i++ ) {
fb->delete_line( fb->ds.get_cursor_row() );
}
/* same story -- xterm and gnome-terminal don't
move to first column */
fb->ds.move_col( 0 );
}
static Function func_CSI_DL( CSI, "M", CSI_DL );
/* insert characters */
void CSI_ICH( Framebuffer *fb, Dispatcher *dispatch )
{
int cells = dispatch->getparam( 0, 1 );
for ( int i = 0; i < cells; i++ ) {
fb->insert_cell( fb->ds.get_cursor_row(), fb->ds.get_cursor_col() );
}
}
static Function func_CSI_ICH( CSI, "@", CSI_ICH );
/* delete character */
void CSI_DCH( Framebuffer *fb, Dispatcher *dispatch )
{
int cells = dispatch->getparam( 0, 1 );
for ( int i = 0; i < cells; i++ ) {
fb->delete_cell( fb->ds.get_cursor_row(), fb->ds.get_cursor_col() );
}
}
static Function func_CSI_DCH( CSI, "P", CSI_DCH );
/* line position absolute */
void CSI_VPA( Framebuffer *fb, Dispatcher *dispatch )
{
int row = dispatch->getparam( 0, 1 );
fb->ds.move_row( row - 1 );
}
static Function func_CSI_VPA( CSI, "d", CSI_VPA );
/* character position absolute */
void CSI_HPA( Framebuffer *fb, Dispatcher *dispatch )
{
int col = dispatch->getparam( 0, 1 );
fb->ds.move_col( col - 1 );
}
static Function func_CSI_CHA( CSI, "G", CSI_HPA ); /* ECMA-48 name: CHA */
static Function func_CSI_HPA( CSI, "\x60", CSI_HPA ); /* ECMA-48 name: HPA */
/* erase character */
void CSI_ECH( Framebuffer *fb, Dispatcher *dispatch )
{
int num = dispatch->getparam( 0, 1 );
int limit = fb->ds.get_cursor_col() + num - 1;
if ( limit >= fb->ds.get_width() ) {
limit = fb->ds.get_width() - 1;
}
clearline( fb, -1, fb->ds.get_cursor_col(), limit );
}
static Function func_CSI_ECH( CSI, "X", CSI_ECH );
/* reset to initial state */
void Esc_RIS( Framebuffer *fb, Dispatcher *dispatch __attribute((unused)) )
{
fb->reset();
}
static Function func_Esc_RIS( ESCAPE, "c", Esc_RIS );
/* soft reset */
void CSI_DECSTR( Framebuffer *fb, Dispatcher *dispatch __attribute((unused)) )
{
fb->soft_reset();
}
static Function func_CSI_DECSTR( CSI, "!p", CSI_DECSTR );
/* xterm uses an Operating System Command to set the window title */
void Dispatcher::OSC_dispatch( const Parser::OSC_End *act, Framebuffer *fb )
{
if ( OSC_string.size() >= 2 ) {
if ( (OSC_string[ 0 ] == L'0')
&& (OSC_string[ 1 ] == L';') ) {
std::deque<wchar_t> newtitle( OSC_string.begin(), OSC_string.end() );
newtitle.erase( newtitle.begin() );
newtitle.erase( newtitle.begin() );
fb->set_window_title( newtitle );
act->handled = true;
}
}
}
/* scroll down or terminfo indn */
void CSI_SD( Framebuffer *fb, Dispatcher *dispatch )
{
fb->scroll( dispatch->getparam( 0, 1 ) );
}
static Function func_CSI_SD( CSI, "S", CSI_SD );
/* scroll up or terminfo rin */
void CSI_SU( Framebuffer *fb, Dispatcher *dispatch )
{
fb->scroll( -dispatch->getparam( 0, 1 ) );
}
static Function func_CSI_SU( CSI, "T", CSI_SU );
+30
View File
@@ -0,0 +1,30 @@
#include "terminaluserinput.h"
using namespace Terminal;
std::string UserInput::input( const Parser::UserByte *act,
bool application_mode_cursor_keys )
{
char translated_str[ 2 ] = { act->c, 0 };
/* The user will always be in application mode. If stm is not in
application mode, convert user's cursor control function to an
ANSI cursor control sequence */
/* We don't need lookahead to do this for 7-bit. */
if ( (!application_mode_cursor_keys)
&& (last_byte == 0x1b) /* ESC */
&& (act->c == 'O') ) { /* ESC O = 7-bit SS3 = application mode */
translated_str[ 0 ] = '[';
}
/* This doesn't handle the 8-bit SS3 C1 control, which would be
two octets in UTF-8. Fortunately nobody seems to send this. */
last_byte = act->c;
act->handled = true;
return std::string( translated_str, 1 );
}
+24
View File
@@ -0,0 +1,24 @@
#ifndef TERMINALUSERINPUT_HPP
#define TERMINALUSERINPUT_HPP
#include <string>
#include "parseraction.h"
namespace Terminal {
class UserInput {
private:
wchar_t last_byte;
public:
UserInput()
: last_byte( -1 )
{}
std::string input( const Parser::UserByte *act,
bool application_mode_cursor_keys );
bool operator==( const UserInput &x ) const { return last_byte == x.last_byte; }
};
}
#endif