LibVT+Terminal: Support hyperlinks in the terminal :^)

We now support basic hyperlinking in the terminal with <OSC>;8;;URL<ST>
Links are opened via LaunchServer on Ctrl+LeftMouse.
This commit is contained in:
Andreas Kling 2020-05-09 16:16:16 +02:00
parent 427863f275
commit f596b12a04
7 changed files with 62 additions and 6 deletions

View file

@ -3,6 +3,6 @@ OBJS = \
PROGRAM = Terminal
LIB_DEPS = GUI Gfx VT IPC Protocol Core
LIB_DEPS = GUI Gfx VT Desktop IPC Protocol Core
include ../../Makefile.common

View file

@ -190,7 +190,7 @@ int main(int argc, char** argv)
GUI::Application app(argc, argv);
if (pledge("stdio tty rpath accept cpath wpath shared_buffer proc exec", nullptr) < 0) {
if (pledge("stdio tty rpath accept cpath wpath shared_buffer proc exec unix", nullptr) < 0) {
perror("pledge");
return 1;
}
@ -313,6 +313,11 @@ int main(int argc, char** argv)
return 1;
}
if (unveil("/tmp/portal/launch", "rw") < 0) {
perror("unveil");
return 1;
}
if (unveil(config->file_name().characters(), "rwc")) {
perror("unveil");
return 1;

View file

@ -19,6 +19,6 @@ OBJS = \
PROGRAM = HackStudio
LIB_DEPS = GUI Web TextCodec VT Protocol Markdown Gfx IPC Thread Pthread Core JS Debug
LIB_DEPS = GUI Web TextCodec VT Desktop Protocol Markdown Gfx IPC Thread Pthread Core JS Debug
include ../../Makefile.common

View file

@ -61,7 +61,8 @@ void Terminal::Line::set_length(u16 new_length)
memset(new_characters, ' ', new_length);
if (characters && attributes) {
memcpy(new_characters, characters, min(m_length, new_length));
memcpy(new_attributes, attributes, min(m_length, new_length) * sizeof(Attribute));
for (size_t i = 0; i < min(m_length, new_length); ++i)
new_attributes[i] = attributes[i];
}
delete[] characters;
delete[] attributes;
@ -605,6 +606,11 @@ void Terminal::execute_xterm_command()
case 2:
m_client.set_window_title(params[1]);
break;
case 8:
m_current_attribute.href = params[2];
// FIXME: Respect the provided ID
m_current_attribute.href_id = String::format("%u", m_next_href_id++);
break;
default:
unimplemented_xterm_escape();
break;
@ -1076,4 +1082,16 @@ void Terminal::execute_hashtag(u8 hashtag)
}
}
Attribute Terminal::attribute_at(const Position& position) const
{
if (!position.is_valid())
return {};
if (position.row() >= static_cast<int>(m_lines.size()))
return {};
auto& line = this->line(position.row());
if (position.column() >= line.m_length)
return {};
return line.attributes[position.column()];
}
}

View file

@ -60,6 +60,9 @@ struct Attribute {
u8 foreground_color;
u8 background_color;
String href;
String href_id;
enum Flags : u8 {
NoAttributes = 0x00,
Bold = 0x01,
@ -137,6 +140,8 @@ public:
void inject_string(const StringView&);
Attribute attribute_at(const Position&) const;
private:
typedef Vector<unsigned, 4> ParamVector;
@ -204,6 +209,8 @@ private:
Attribute m_current_attribute;
u32 m_next_href_id { 0 };
void execute_escape_sequence(u8 final);
void execute_xterm_command();
void execute_hashtag(u8);

View file

@ -32,6 +32,7 @@
#include <AK/Utf8View.h>
#include <Kernel/KeyCode.h>
#include <LibCore/MimeData.h>
#include <LibDesktop/Launcher.h>
#include <LibGUI/Action.h>
#include <LibGUI/Application.h>
#include <LibGUI/Clipboard.h>
@ -371,7 +372,11 @@ void TerminalWidget::paint_event(GUI::PaintEvent& event)
if (!has_only_one_background_color || should_reverse_fill_for_cursor_or_selection) {
painter.clear_rect(cell_rect, lookup_color(should_reverse_fill_for_cursor_or_selection ? attribute.foreground_color : attribute.background_color).with_alpha(m_opacity));
}
if (attribute.flags & VT::Attribute::Underline)
bool should_paint_underline = attribute.flags & VT::Attribute::Underline
|| (!m_hovered_href.is_empty() && m_hovered_href_id == attribute.href_id);
if (should_paint_underline)
painter.draw_line(cell_rect.bottom_left(), cell_rect.bottom_right(), lookup_color(should_reverse_fill_for_cursor_or_selection ? attribute.background_color : attribute.foreground_color));
}
@ -586,6 +591,15 @@ void TerminalWidget::copy()
void TerminalWidget::mousedown_event(GUI::MouseEvent& event)
{
if (event.modifiers() == Mod_Ctrl && event.button() == GUI::MouseButton::Left) {
auto attribute = m_terminal.attribute_at(buffer_position_at(event.position()));
if (!attribute.href.is_empty()) {
dbg() << "Open URL: _" << attribute.href << "_";
Desktop::Launcher::open(attribute.href);
}
return;
}
if (event.button() == GUI::MouseButton::Left) {
if (m_triple_click_timer.is_valid() && m_triple_click_timer.elapsed() < 250) {
int start_column = 0;
@ -609,11 +623,20 @@ void TerminalWidget::mousedown_event(GUI::MouseEvent& event)
void TerminalWidget::mousemove_event(GUI::MouseEvent& event)
{
auto position = buffer_position_at(event.position());
auto attribute = m_terminal.attribute_at(position);
if (attribute.href_id != m_hovered_href_id) {
m_hovered_href_id = attribute.href_id;
m_hovered_href = attribute.href;
update();
}
if (!(event.buttons() & GUI::MouseButton::Left))
return;
auto old_selection_end = m_selection_end;
m_selection_end = buffer_position_at(event.position());
m_selection_end = position;
if (old_selection_end != m_selection_end)
update();
}

View file

@ -129,6 +129,9 @@ private:
VT::Position m_selection_start;
VT::Position m_selection_end;
String m_hovered_href;
String m_hovered_href_id;
bool m_should_beep { false };
bool m_belling { false };
bool m_alt_key_held { false };