Skip to content
Open
Show file tree
Hide file tree
Changes from 1 commit
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions src/terminal/terminal.cc
Original file line number Diff line number Diff line change
Expand Up @@ -106,6 +106,7 @@ void Emulator::print( const Parser::Print* act )
this_cell->append( ch );
this_cell->set_wide( chwidth == 2 ); /* chwidth had better be 1 or 2 here */
fb.apply_renditions_to_cell( this_cell );
fb.apply_hyperlink_to_cell( this_cell );

if ( chwidth == 2 && fb.ds.get_cursor_col() + 1 < fb.ds.get_width() ) { /* erase overlapped cell */
fb.reset_cell( fb.get_mutable_cell( fb.ds.get_cursor_row(), fb.ds.get_cursor_col() + 1 ) );
Expand Down
2 changes: 1 addition & 1 deletion src/terminal/terminaldispatcher.h
Original file line number Diff line number Diff line change
Expand Up @@ -95,7 +95,7 @@ class Dispatcher
bool parsed;

std::string dispatch_chars;
std::vector<wchar_t> OSC_string; /* only used to set the window title */
std::vector<wchar_t> OSC_string;

void parse_params( void );

Expand Down
29 changes: 26 additions & 3 deletions src/terminal/terminaldisplay.cc
Original file line number Diff line number Diff line change
Expand Up @@ -130,10 +130,12 @@ std::string Display::new_frame( bool initialized, const Framebuffer& last, const
initialized = false;
frame.cursor_x = frame.cursor_y = 0;
frame.current_rendition = initial_rendition();
frame.current_hyperlink = Hyperlink();
} else {
frame.cursor_x = frame.last_frame.ds.get_cursor_col();
frame.cursor_y = frame.last_frame.ds.get_cursor_row();
frame.current_rendition = frame.last_frame.ds.get_renditions();
frame.current_hyperlink = frame.last_frame.ds.get_hyperlink();
}

/* is cursor visibility initialized? */
Expand Down Expand Up @@ -203,6 +205,7 @@ std::string Display::new_frame( bool initialized, const Framebuffer& last, const
blank_row = std::make_shared<Row>( w, c );
}
frame.update_rendition( initial_rendition(), true );
frame.update_hyperlink( Hyperlink(), true );

int top_margin = 0;
int bottom_margin = top_margin + lines_scrolled + scroll_height - 1;
Expand Down Expand Up @@ -269,6 +272,8 @@ std::string Display::new_frame( bool initialized, const Framebuffer& last, const

/* have renditions changed? */
frame.update_rendition( f.ds.get_renditions(), !initialized );
/* has hyperlink changed? */
frame.update_hyperlink( f.ds.get_hyperlink(), !initialized );

/* has bracketed paste mode changed? */
if ( ( !initialized ) || ( f.ds.bracketed_paste != frame.last_frame.ds.bracketed_paste ) ) {
Expand Down Expand Up @@ -334,6 +339,7 @@ bool Display::put_row( bool initialized,
if ( wrap ) {
const Cell& cell = cells.at( 0 );
frame.update_rendition( cell.get_renditions() );
frame.update_hyperlink( cell.get_hyperlink() );
frame.append_cell( cell );
frame_x += cell.get_width();
frame.cursor_x += cell.get_width();
Expand All @@ -349,6 +355,7 @@ bool Display::put_row( bool initialized,
int clear_count = 0;
bool wrote_last_cell = false;
Renditions blank_renditions = initial_rendition();
Hyperlink blank_hyperlink;

/* iterate for every cell */
while ( frame_x < row_width ) {
Expand All @@ -365,8 +372,9 @@ bool Display::put_row( bool initialized,
if ( cell.empty() ) {
if ( !clear_count ) {
blank_renditions = cell.get_renditions();
blank_hyperlink = cell.get_hyperlink();
}
if ( cell.get_renditions() == blank_renditions ) {
if ( cell.get_renditions() == blank_renditions && cell.get_hyperlink() == blank_hyperlink ) {
/* Remember run of blank cells */
clear_count++;
frame_x++;
Expand All @@ -379,7 +387,9 @@ bool Display::put_row( bool initialized,
/* Move to the right position. */
frame.append_silent_move( frame_y, frame_x - clear_count );
frame.update_rendition( blank_renditions );
bool can_use_erase = has_bce || ( frame.current_rendition == initial_rendition() );
frame.update_hyperlink( blank_hyperlink );
bool can_use_erase
= has_bce || ( frame.current_rendition == initial_rendition() && frame.current_hyperlink.empty() );
if ( can_use_erase && has_ech && clear_count > 4 ) {
snprintf( tmp, 64, "\033[%dX", clear_count );
frame.append( tmp );
Expand All @@ -392,6 +402,7 @@ bool Display::put_row( bool initialized,
// we restart counting and continue here
if ( cell.empty() ) {
blank_renditions = cell.get_renditions();
blank_hyperlink = cell.get_hyperlink();
clear_count = 1;
frame_x++;
continue;
Expand All @@ -413,6 +424,7 @@ bool Display::put_row( bool initialized,
}
frame.append_silent_move( frame_y, frame_x );
frame.update_rendition( cell.get_renditions() );
frame.update_hyperlink( cell.get_hyperlink() );
frame.append_cell( cell );
frame_x += cell_width;
frame.cursor_x += cell_width;
Expand All @@ -428,8 +440,10 @@ bool Display::put_row( bool initialized,
/* Move to the right position. */
frame.append_silent_move( frame_y, frame_x - clear_count );
frame.update_rendition( blank_renditions );
frame.update_hyperlink( blank_hyperlink );

bool can_use_erase = has_bce || ( frame.current_rendition == initial_rendition() );
bool can_use_erase
= has_bce || ( frame.current_rendition == initial_rendition() && frame.current_hyperlink.empty() );
if ( can_use_erase && !wrap_this ) {
frame.append( "\033[K" );
} else {
Expand Down Expand Up @@ -514,3 +528,12 @@ void FrameState::update_rendition( const Renditions& r, bool force )
current_rendition = r;
}
}

void FrameState::update_hyperlink( const Hyperlink& h, bool force )
{
if ( force || current_hyperlink != h ) {
/* print hyperlink */
append_string( h.osc8() );
current_hyperlink = h;
}
}
2 changes: 2 additions & 0 deletions src/terminal/terminaldisplay.h
Original file line number Diff line number Diff line change
Expand Up @@ -44,6 +44,7 @@ class FrameState

int cursor_x, cursor_y;
Renditions current_rendition;
Hyperlink current_hyperlink;
bool cursor_visible;

const Framebuffer& last_frame;
Expand All @@ -60,6 +61,7 @@ class FrameState
void append_silent_move( int y, int x );
void append_move( int y, int x );
void update_rendition( const Renditions& r, bool force = false );
void update_hyperlink( const Hyperlink& h, bool force = false );
};

class Display
Expand Down
41 changes: 41 additions & 0 deletions src/terminal/terminalframebuffer.cc
Original file line number Diff line number Diff line change
Expand Up @@ -268,6 +268,14 @@ void Framebuffer::apply_renditions_to_cell( Cell* cell )
cell->set_renditions( ds.get_renditions() );
}

void Framebuffer::apply_hyperlink_to_cell( Cell* cell )
{
if ( !cell ) {
cell = get_mutable_cell();
}
cell->set_hyperlink( ds.get_hyperlink() );
}

SavedCursor::SavedCursor()
: cursor_col( 0 ), cursor_row( 0 ), renditions( 0 ), auto_wrap_mode( true ), origin_mode( false )
{}
Expand Down Expand Up @@ -390,6 +398,7 @@ void Framebuffer::soft_reset( void )
ds.application_mode_cursor_keys = false;
ds.set_scrolling_region( 0, ds.get_height() - 1 );
ds.add_rendition( 0 );
ds.set_hyperlink( Hyperlink() );
ds.clear_saved_cursor();
}

Expand Down Expand Up @@ -594,6 +603,38 @@ std::string Renditions::sgr( void ) const
return ret;
}

// Empty static string to return from accessors.
/* static */ const std::string* Hyperlink::empty_string = new std::string;

bool Hyperlink::operator==( const Hyperlink& x ) const
{
if ( rep == nullptr && x.rep == nullptr ) {
return true;
}
if ( rep == nullptr || x.rep == nullptr ) {
return false;
}

return rep->url == x.rep->url && rep->id == x.rep->id;
}

std::string Hyperlink::osc8() const
{
std::string ret;

ret.append( "\033]8;" );
const std::string& id = get_id();
if ( !id.empty() ) {
ret.append( "id=" );
ret.append( id );
}
ret.append( ";" );
ret.append( get_url() );

ret.append( "\033\\" );
return ret;
}

void Row::reset( color_type background_color )
{
gen = get_gen();
Expand Down
41 changes: 39 additions & 2 deletions src/terminal/terminalframebuffer.h
Original file line number Diff line number Diff line change
Expand Up @@ -98,12 +98,42 @@ class Renditions
void clear_attributes() { attributes = 0; }
};

class Hyperlink
{
private:
struct Rep
{
std::string id;
std::string url;
};
std::shared_ptr<Rep> rep;
static const std::string* empty_string;

public:
Hyperlink() = default;
Hyperlink( std::string id, std::string url )
: rep( url.empty() ? nullptr : std::make_shared<Rep>( Rep { std::move( id ), std::move( url ) } ) )
{}

std::string osc8() const;

const std::string& get_id() const { return rep == nullptr ? *empty_string : rep->id; }
const std::string& get_url() const { return rep == nullptr ? *empty_string : rep->url; }

bool empty() const { return rep == nullptr; }

bool operator==( const Hyperlink& x ) const;

bool operator!=( const Hyperlink& x ) const { return !operator==( x ); }
};

class Cell
{
private:
typedef std::string content_type; /* can be std::string, std::vector<uint8_t>, or __gnu_cxx::__vstring */
content_type contents;
Renditions renditions;
Hyperlink hyperlink;
unsigned int wide : 1; /* 0 = narrow, 1 = wide */
unsigned int fallback : 1; /* first character is combining character */
unsigned int wrap : 1;
Expand All @@ -119,7 +149,7 @@ class Cell
bool operator==( const Cell& x ) const
{
return ( ( contents == x.contents ) && ( fallback == x.fallback ) && ( wide == x.wide )
&& ( renditions == x.renditions ) && ( wrap == x.wrap ) );
&& ( renditions == x.renditions ) && ( hyperlink == x.hyperlink ) && ( wrap == x.wrap ) );
}

bool operator!=( const Cell& x ) const { return !operator==( x ); }
Expand Down Expand Up @@ -198,6 +228,8 @@ class Cell
}

/* Other accessors */
const Hyperlink& get_hyperlink() const { return hyperlink; }
const void set_hyperlink( Hyperlink l ) { hyperlink = std::move( l ); }
const Renditions& get_renditions( void ) const { return renditions; }
Renditions& get_renditions( void ) { return renditions; }
void set_renditions( const Renditions& r ) { renditions = r; }
Expand Down Expand Up @@ -271,6 +303,7 @@ class DrawState
int scrolling_region_top_row, scrolling_region_bottom_row;

Renditions renditions;
Hyperlink hyperlink;

SavedCursor save;

Expand Down Expand Up @@ -332,6 +365,9 @@ class DrawState
int limit_top( void ) const;
int limit_bottom( void ) const;

const Hyperlink& get_hyperlink() const { return hyperlink; }
void set_hyperlink( Hyperlink x ) { hyperlink = std::move( x ); }

void set_foreground_color( int x ) { renditions.set_foreground_color( x ); }
void set_background_color( int x ) { renditions.set_background_color( x ); }
void add_rendition( color_type x ) { renditions.set_rendition( x ); }
Expand All @@ -355,7 +391,7 @@ class DrawState
&& ( reverse_video == x.reverse_video ) && ( renditions == x.renditions )
&& ( bracketed_paste == x.bracketed_paste ) && ( mouse_reporting_mode == x.mouse_reporting_mode )
&& ( mouse_focus_event == x.mouse_focus_event ) && ( mouse_alternate_scroll == x.mouse_alternate_scroll )
&& ( mouse_encoding_mode == x.mouse_encoding_mode );
&& ( mouse_encoding_mode == x.mouse_encoding_mode ) && hyperlink == x.hyperlink;
}
};

Expand Down Expand Up @@ -447,6 +483,7 @@ class Framebuffer
Cell* get_combining_cell( void );

void apply_renditions_to_cell( Cell* cell );
void apply_hyperlink_to_cell( Cell* cell );

void insert_line( int before_row, int count );
void delete_line( int row, int count );
Expand Down
49 changes: 49 additions & 0 deletions src/terminal/terminalfunctions.cc
Original file line number Diff line number Diff line change
Expand Up @@ -588,6 +588,45 @@ static void CSI_DECSTR( Framebuffer* fb, Dispatcher* dispatch __attribute( ( unu

static Function func_CSI_DECSTR( CSI, "!p", CSI_DECSTR );

static bool Parse_OSC_8( const std::vector<wchar_t>& osc8_vector, std::string& osc8_str )
{
osc8_str.reserve( osc8_vector.size() );
for ( wchar_t wide_char : osc8_vector ) {
// Valid char range is 32-126
if ( wide_char < 32 || wide_char > 126 ) {
return false;
}
osc8_str.append( 1, static_cast<char>( wide_char ) );
}
return true;
}

static void OSC_8( const std::string& OSC_string, Framebuffer* fb )
{
// OSC of the form "\033]8;params;url\007"
assert( OSC_string[0] == '8' );
if ( OSC_string.size() <= 2 || OSC_string[1] != ';' ) {
// Bail early if the string is malformed.
return;
}

size_t second_semicolon = OSC_string.find_first_of( ';', 2 );
if ( second_semicolon == std::string::npos ) {
// Missing the second semicolon, malformed.
return;
}

std::string id;
std::string params = OSC_string.substr( 2, second_semicolon - 2 );
size_t id_pos = params.find( "id=" );
if ( id_pos != std::string::npos ) {
id = params.substr( id_pos + 3, params.find( ":", id_pos + 3 ) );
}

std::string url = OSC_string.substr( second_semicolon + 1 );
fb->ds.set_hyperlink( Hyperlink( id, url ) );
}

/* xterm uses an Operating System Command to set the window title */
void Dispatcher::OSC_dispatch( const Parser::OSC_End* act __attribute( ( unused ) ), Framebuffer* fb )
{
Expand All @@ -612,6 +651,16 @@ void Dispatcher::OSC_dispatch( const Parser::OSC_End* act __attribute( ( unused
cmd_num = OSC_string[0] - L'0';
offset = 2;
}
if ( cmd_num == 8 ) {
// Handle OSC8 hyperlinks separately
std::string osc_8_str;
if ( !Parse_OSC_8( OSC_string, osc_8_str ) ) {
//
return;
}
OSC_8( osc_8_str, fb );
return;
}
bool set_icon = cmd_num == 0 || cmd_num == 1;
bool set_title = cmd_num == 0 || cmd_num == 2;
if ( set_icon || set_title ) {
Expand Down