mirror of
https://github.com/RGBCube/serenity
synced 2025-05-30 21:28:11 +00:00
LibVT+Everywhere: Introduce 'automarks' and 'clear previous command'
Automarks are similar to bookmarks placed by the terminal, allowing the user to selectively remove a single command and its output from the terminal scrollback. This commit implements a single way to add marks: automatically placing them when the shell becomes interactive. To make sure the shell behaves correctly after its expected prompt position changes, the terminal layer forces a resize event to be passed to the shell on such (possibly) partial clears; this also has the nice side effect of fixing the disappearing prompt on the preexisting "clear including history" action: Fixes #4192.
This commit is contained in:
parent
cde528fdd9
commit
54ab6fe5b9
12 changed files with 238 additions and 0 deletions
|
@ -377,6 +377,11 @@ void VirtualConsole::terminal_history_changed(int)
|
||||||
// Do nothing, I guess?
|
// Do nothing, I guess?
|
||||||
}
|
}
|
||||||
|
|
||||||
|
void VirtualConsole::terminal_did_perform_possibly_partial_clear()
|
||||||
|
{
|
||||||
|
// Do nothing, we're not going to hit this anyway.
|
||||||
|
}
|
||||||
|
|
||||||
void VirtualConsole::emit(u8 const* data, size_t size)
|
void VirtualConsole::emit(u8 const* data, size_t size)
|
||||||
{
|
{
|
||||||
for (size_t i = 0; i < size; i++)
|
for (size_t i = 0; i < size; i++)
|
||||||
|
|
|
@ -99,6 +99,7 @@ private:
|
||||||
virtual void set_window_progress(int, int) override;
|
virtual void set_window_progress(int, int) override;
|
||||||
virtual void terminal_did_resize(u16 columns, u16 rows) override;
|
virtual void terminal_did_resize(u16 columns, u16 rows) override;
|
||||||
virtual void terminal_history_changed(int) override;
|
virtual void terminal_history_changed(int) override;
|
||||||
|
virtual void terminal_did_perform_possibly_partial_clear() override;
|
||||||
virtual void emit(u8 const*, size_t) override;
|
virtual void emit(u8 const*, size_t) override;
|
||||||
virtual void set_cursor_shape(VT::CursorShape) override;
|
virtual void set_cursor_shape(VT::CursorShape) override;
|
||||||
virtual void set_cursor_blinking(bool) override;
|
virtual void set_cursor_blinking(bool) override;
|
||||||
|
|
|
@ -284,6 +284,8 @@ ErrorOr<int> serenity_main(Main::Arguments arguments)
|
||||||
window->set_obey_widget_min_size(false);
|
window->set_obey_widget_min_size(false);
|
||||||
|
|
||||||
auto terminal = window->set_main_widget<VT::TerminalWidget>(ptm_fd, true);
|
auto terminal = window->set_main_widget<VT::TerminalWidget>(ptm_fd, true);
|
||||||
|
terminal->set_startup_process_id(shell_pid);
|
||||||
|
|
||||||
terminal->on_command_exit = [&] {
|
terminal->on_command_exit = [&] {
|
||||||
app->quit(0);
|
app->quit(0);
|
||||||
};
|
};
|
||||||
|
@ -309,6 +311,13 @@ ErrorOr<int> serenity_main(Main::Arguments arguments)
|
||||||
terminal->set_bell_mode(VT::TerminalWidget::BellMode::Visible);
|
terminal->set_bell_mode(VT::TerminalWidget::BellMode::Visible);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
auto automark = Config::read_string("Terminal"sv, "Terminal"sv, "AutoMark"sv, "MarkInteractiveShellPrompt"sv);
|
||||||
|
if (automark == "MarkNothing") {
|
||||||
|
terminal->set_auto_mark_mode(VT::TerminalWidget::AutoMarkMode::MarkNothing);
|
||||||
|
} else {
|
||||||
|
terminal->set_auto_mark_mode(VT::TerminalWidget::AutoMarkMode::MarkInteractiveShellPrompt);
|
||||||
|
}
|
||||||
|
|
||||||
auto cursor_shape = VT::TerminalWidget::parse_cursor_shape(Config::read_string("Terminal"sv, "Cursor"sv, "Shape"sv, "Block"sv)).value_or(VT::CursorShape::Block);
|
auto cursor_shape = VT::TerminalWidget::parse_cursor_shape(Config::read_string("Terminal"sv, "Cursor"sv, "Shape"sv, "Block"sv)).value_or(VT::CursorShape::Block);
|
||||||
terminal->set_cursor_shape(cursor_shape);
|
terminal->set_cursor_shape(cursor_shape);
|
||||||
|
|
||||||
|
@ -399,6 +408,7 @@ ErrorOr<int> serenity_main(Main::Arguments arguments)
|
||||||
window->set_fullscreen(!window->is_fullscreen());
|
window->set_fullscreen(!window->is_fullscreen());
|
||||||
}));
|
}));
|
||||||
view_menu->add_action(terminal->clear_including_history_action());
|
view_menu->add_action(terminal->clear_including_history_action());
|
||||||
|
view_menu->add_action(terminal->clear_to_previous_mark_action());
|
||||||
|
|
||||||
auto adjust_font_size = [&](float adjustment, Gfx::Font::AllowInexactSizeMatch preference) {
|
auto adjust_font_size = [&](float adjustment, Gfx::Font::AllowInexactSizeMatch preference) {
|
||||||
auto& font = terminal->font();
|
auto& font = terminal->font();
|
||||||
|
|
|
@ -40,10 +40,15 @@ ErrorOr<void> MainWidget::setup()
|
||||||
auto& beep_bell_radio = *find_descendant_of_type_named<GUI::RadioButton>("beep_bell_radio");
|
auto& beep_bell_radio = *find_descendant_of_type_named<GUI::RadioButton>("beep_bell_radio");
|
||||||
auto& visual_bell_radio = *find_descendant_of_type_named<GUI::RadioButton>("visual_bell_radio");
|
auto& visual_bell_radio = *find_descendant_of_type_named<GUI::RadioButton>("visual_bell_radio");
|
||||||
auto& no_bell_radio = *find_descendant_of_type_named<GUI::RadioButton>("no_bell_radio");
|
auto& no_bell_radio = *find_descendant_of_type_named<GUI::RadioButton>("no_bell_radio");
|
||||||
|
auto& automark_off_radio = *find_descendant_of_type_named<GUI::RadioButton>("automark_of");
|
||||||
|
auto& automark_on_interactive_prompt_radio = *find_descendant_of_type_named<GUI::RadioButton>("automark_on_interactive_prompt");
|
||||||
|
|
||||||
m_bell_mode = parse_bell(Config::read_string("Terminal"sv, "Window"sv, "Bell"sv));
|
m_bell_mode = parse_bell(Config::read_string("Terminal"sv, "Window"sv, "Bell"sv));
|
||||||
m_original_bell_mode = m_bell_mode;
|
m_original_bell_mode = m_bell_mode;
|
||||||
|
|
||||||
|
m_automark_mode = parse_automark_mode(Config::read_string("Terminal"sv, "Terminal"sv, "AutoMark"sv));
|
||||||
|
m_original_automark_mode = m_automark_mode;
|
||||||
|
|
||||||
switch (m_bell_mode) {
|
switch (m_bell_mode) {
|
||||||
case VT::TerminalWidget::BellMode::Visible:
|
case VT::TerminalWidget::BellMode::Visible:
|
||||||
visual_bell_radio.set_checked(true, GUI::AllowCallback::No);
|
visual_bell_radio.set_checked(true, GUI::AllowCallback::No);
|
||||||
|
@ -72,6 +77,26 @@ ErrorOr<void> MainWidget::setup()
|
||||||
set_modified(true);
|
set_modified(true);
|
||||||
};
|
};
|
||||||
|
|
||||||
|
switch (m_automark_mode) {
|
||||||
|
case VT::TerminalWidget::AutoMarkMode::MarkNothing:
|
||||||
|
automark_off_radio.set_checked(true, GUI::AllowCallback::No);
|
||||||
|
break;
|
||||||
|
case VT::TerminalWidget::AutoMarkMode::MarkInteractiveShellPrompt:
|
||||||
|
automark_on_interactive_prompt_radio.set_checked(true, GUI::AllowCallback::No);
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
|
||||||
|
automark_off_radio.on_checked = [this](bool) {
|
||||||
|
m_automark_mode = VT::TerminalWidget::AutoMarkMode::MarkNothing;
|
||||||
|
Config::write_string("Terminal"sv, "Terminal"sv, "AutoMark"sv, stringify_automark_mode(m_automark_mode));
|
||||||
|
set_modified(true);
|
||||||
|
};
|
||||||
|
automark_on_interactive_prompt_radio.on_checked = [this](bool) {
|
||||||
|
m_automark_mode = VT::TerminalWidget::AutoMarkMode::MarkInteractiveShellPrompt;
|
||||||
|
Config::write_string("Terminal"sv, "Terminal"sv, "AutoMark"sv, stringify_automark_mode(m_automark_mode));
|
||||||
|
set_modified(true);
|
||||||
|
};
|
||||||
|
|
||||||
m_confirm_close = Config::read_bool("Terminal"sv, "Terminal"sv, "ConfirmClose"sv, true);
|
m_confirm_close = Config::read_bool("Terminal"sv, "Terminal"sv, "ConfirmClose"sv, true);
|
||||||
m_orignal_confirm_close = m_confirm_close;
|
m_orignal_confirm_close = m_confirm_close;
|
||||||
auto& confirm_close_checkbox = *find_descendant_of_type_named<GUI::CheckBox>("terminal_confirm_close");
|
auto& confirm_close_checkbox = *find_descendant_of_type_named<GUI::CheckBox>("terminal_confirm_close");
|
||||||
|
@ -106,6 +131,24 @@ ByteString MainWidget::stringify_bell(VT::TerminalWidget::BellMode bell_mode)
|
||||||
VERIFY_NOT_REACHED();
|
VERIFY_NOT_REACHED();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
VT::TerminalWidget::AutoMarkMode MainWidget::parse_automark_mode(StringView automark_mode)
|
||||||
|
{
|
||||||
|
if (automark_mode == "MarkNothing")
|
||||||
|
return VT::TerminalWidget::AutoMarkMode::MarkNothing;
|
||||||
|
if (automark_mode == "MarkInteractiveShellPrompt")
|
||||||
|
return VT::TerminalWidget::AutoMarkMode::MarkInteractiveShellPrompt;
|
||||||
|
VERIFY_NOT_REACHED();
|
||||||
|
}
|
||||||
|
|
||||||
|
ByteString MainWidget::stringify_automark_mode(VT::TerminalWidget::AutoMarkMode automark_mode)
|
||||||
|
{
|
||||||
|
if (automark_mode == VT::TerminalWidget::AutoMarkMode::MarkNothing)
|
||||||
|
return "MarkNothing";
|
||||||
|
if (automark_mode == VT::TerminalWidget::AutoMarkMode::MarkInteractiveShellPrompt)
|
||||||
|
return "MarkInteractiveShellPrompt";
|
||||||
|
VERIFY_NOT_REACHED();
|
||||||
|
}
|
||||||
|
|
||||||
void MainWidget::apply_settings()
|
void MainWidget::apply_settings()
|
||||||
{
|
{
|
||||||
m_original_bell_mode = m_bell_mode;
|
m_original_bell_mode = m_bell_mode;
|
||||||
|
|
|
@ -29,12 +29,16 @@ private:
|
||||||
void write_back_settings() const;
|
void write_back_settings() const;
|
||||||
|
|
||||||
static VT::TerminalWidget::BellMode parse_bell(StringView bell_string);
|
static VT::TerminalWidget::BellMode parse_bell(StringView bell_string);
|
||||||
|
static VT::TerminalWidget::AutoMarkMode parse_automark_mode(StringView automark_mode);
|
||||||
static ByteString stringify_bell(VT::TerminalWidget::BellMode bell_mode);
|
static ByteString stringify_bell(VT::TerminalWidget::BellMode bell_mode);
|
||||||
|
static ByteString stringify_automark_mode(VT::TerminalWidget::AutoMarkMode automark_mode);
|
||||||
|
|
||||||
VT::TerminalWidget::BellMode m_bell_mode { VT::TerminalWidget::BellMode::Disabled };
|
VT::TerminalWidget::BellMode m_bell_mode { VT::TerminalWidget::BellMode::Disabled };
|
||||||
|
VT::TerminalWidget::AutoMarkMode m_automark_mode { VT::TerminalWidget::AutoMarkMode::MarkInteractiveShellPrompt };
|
||||||
bool m_confirm_close { true };
|
bool m_confirm_close { true };
|
||||||
|
|
||||||
VT::TerminalWidget::BellMode m_original_bell_mode;
|
VT::TerminalWidget::BellMode m_original_bell_mode;
|
||||||
|
VT::TerminalWidget::AutoMarkMode m_original_automark_mode;
|
||||||
bool m_orignal_confirm_close { true };
|
bool m_orignal_confirm_close { true };
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
|
@ -48,4 +48,22 @@
|
||||||
text: "Confirm exit when process is active"
|
text: "Confirm exit when process is active"
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@GUI::GroupBox {
|
||||||
|
title: "Auto-mark behavior"
|
||||||
|
preferred_height: "fit"
|
||||||
|
layout: @GUI::VerticalBoxLayout {
|
||||||
|
margins: [8]
|
||||||
|
}
|
||||||
|
|
||||||
|
@GUI::RadioButton {
|
||||||
|
name: "automark_off"
|
||||||
|
text: "Do not auto-mark"
|
||||||
|
}
|
||||||
|
|
||||||
|
@GUI::RadioButton {
|
||||||
|
name: "automark_on_interactive_prompt"
|
||||||
|
text: "Auto-mark on interactive shell prompts"
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -42,6 +42,8 @@ ErrorOr<void> TerminalWrapper::run_command(ByteString const& command, Optional<B
|
||||||
m_pid = TRY(Core::System::fork());
|
m_pid = TRY(Core::System::fork());
|
||||||
|
|
||||||
if (m_pid > 0) {
|
if (m_pid > 0) {
|
||||||
|
m_terminal_widget->set_startup_process_id(m_pid);
|
||||||
|
|
||||||
if (wait_for_exit == WaitForExit::Yes) {
|
if (wait_for_exit == WaitForExit::Yes) {
|
||||||
GUI::Application::the()->event_loop().spin_until([this]() {
|
GUI::Application::the()->event_loop().spin_until([this]() {
|
||||||
return m_child_exited;
|
return m_child_exited;
|
||||||
|
|
|
@ -8,6 +8,7 @@
|
||||||
#pragma once
|
#pragma once
|
||||||
|
|
||||||
#include <AK/AnyOf.h>
|
#include <AK/AnyOf.h>
|
||||||
|
#include <AK/DistinctNumeric.h>
|
||||||
#include <AK/Noncopyable.h>
|
#include <AK/Noncopyable.h>
|
||||||
#include <AK/Vector.h>
|
#include <AK/Vector.h>
|
||||||
#include <LibVT/Attribute.h>
|
#include <LibVT/Attribute.h>
|
||||||
|
@ -16,6 +17,10 @@
|
||||||
|
|
||||||
namespace VT {
|
namespace VT {
|
||||||
|
|
||||||
|
AK_TYPEDEF_DISTINCT_ORDERED_ID(u32, Mark);
|
||||||
|
|
||||||
|
inline static constexpr Mark Unmarked = 0;
|
||||||
|
|
||||||
class Line {
|
class Line {
|
||||||
AK_MAKE_NONCOPYABLE(Line);
|
AK_MAKE_NONCOPYABLE(Line);
|
||||||
AK_MAKE_NONMOVABLE(Line);
|
AK_MAKE_NONMOVABLE(Line);
|
||||||
|
@ -40,6 +45,7 @@ public:
|
||||||
void clear(Attribute const& attribute = Attribute())
|
void clear(Attribute const& attribute = Attribute())
|
||||||
{
|
{
|
||||||
m_terminated_at.clear();
|
m_terminated_at.clear();
|
||||||
|
m_mark = Unmarked;
|
||||||
clear_range(0, m_cells.size() - 1, attribute);
|
clear_range(0, m_cells.size() - 1, attribute);
|
||||||
}
|
}
|
||||||
void clear_range(size_t first_column, size_t last_column, Attribute const& attribute = Attribute());
|
void clear_range(size_t first_column, size_t last_column, Attribute const& attribute = Attribute());
|
||||||
|
@ -76,6 +82,16 @@ public:
|
||||||
bool is_dirty() const { return m_dirty; }
|
bool is_dirty() const { return m_dirty; }
|
||||||
void set_dirty(bool b) { m_dirty = b; }
|
void set_dirty(bool b) { m_dirty = b; }
|
||||||
|
|
||||||
|
Optional<Mark> mark() const
|
||||||
|
{
|
||||||
|
return m_mark == Unmarked ? OptionalNone {} : Optional<Mark>(m_mark);
|
||||||
|
}
|
||||||
|
void set_marked(Mark mark)
|
||||||
|
{
|
||||||
|
set_dirty(m_mark != mark);
|
||||||
|
m_mark = mark;
|
||||||
|
}
|
||||||
|
|
||||||
Optional<u16> termination_column() const { return m_terminated_at; }
|
Optional<u16> termination_column() const { return m_terminated_at; }
|
||||||
void set_terminated(u16 column) { m_terminated_at = column; }
|
void set_terminated(u16 column) { m_terminated_at = column; }
|
||||||
|
|
||||||
|
@ -84,6 +100,7 @@ private:
|
||||||
void push_cells_into_next_line(size_t new_length, Line* next_line, bool cursor_is_on_next_line, CursorPosition* cursor);
|
void push_cells_into_next_line(size_t new_length, Line* next_line, bool cursor_is_on_next_line, CursorPosition* cursor);
|
||||||
|
|
||||||
Vector<Cell> m_cells;
|
Vector<Cell> m_cells;
|
||||||
|
Mark m_mark { Unmarked };
|
||||||
bool m_dirty { false };
|
bool m_dirty { false };
|
||||||
// Note: The alignment is 8, so this member lives in the padding (that already existed before it was introduced)
|
// Note: The alignment is 8, so this member lives in the padding (that already existed before it was introduced)
|
||||||
[[no_unique_address]] Optional<u16> m_terminated_at;
|
[[no_unique_address]] Optional<u16> m_terminated_at;
|
||||||
|
|
|
@ -35,6 +35,7 @@ void Terminal::clear()
|
||||||
for (size_t i = 0; i < rows(); ++i)
|
for (size_t i = 0; i < rows(); ++i)
|
||||||
active_buffer()[i]->clear();
|
active_buffer()[i]->clear();
|
||||||
set_cursor(0, 0);
|
set_cursor(0, 0);
|
||||||
|
m_client.terminal_did_perform_possibly_partial_clear();
|
||||||
}
|
}
|
||||||
|
|
||||||
void Terminal::clear_history()
|
void Terminal::clear_history()
|
||||||
|
@ -45,6 +46,68 @@ void Terminal::clear_history()
|
||||||
m_history_start = 0;
|
m_history_start = 0;
|
||||||
m_client.terminal_history_changed(-previous_history_size);
|
m_client.terminal_history_changed(-previous_history_size);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
void Terminal::clear_to_mark(Mark mark)
|
||||||
|
{
|
||||||
|
auto cursor_row = this->cursor_row();
|
||||||
|
ScopeGuard send_sigwinch = [&] {
|
||||||
|
set_cursor(cursor_row, 1);
|
||||||
|
mark_cursor();
|
||||||
|
m_client.terminal_did_perform_possibly_partial_clear();
|
||||||
|
};
|
||||||
|
m_valid_marks.remove(mark);
|
||||||
|
|
||||||
|
{
|
||||||
|
auto it = active_buffer().rbegin();
|
||||||
|
size_t row = m_rows - 1;
|
||||||
|
// Skip to the cursor line.
|
||||||
|
for (size_t i = this->cursor_row() + 1; i < active_buffer().size(); ++i, row--)
|
||||||
|
++it;
|
||||||
|
for (; it != active_buffer().rend(); ++it, row--) {
|
||||||
|
auto& line = *it;
|
||||||
|
auto line_mark = line->mark();
|
||||||
|
auto is_target_line = line_mark == mark;
|
||||||
|
if (line_mark.has_value())
|
||||||
|
m_valid_marks.remove(*line_mark);
|
||||||
|
line->clear();
|
||||||
|
if (is_target_line) {
|
||||||
|
cursor_row = row;
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// If the mark is not found, go through the history.
|
||||||
|
auto it = AK::find_if(
|
||||||
|
m_history.rbegin(),
|
||||||
|
m_history.rend(),
|
||||||
|
[mark](auto& line) {
|
||||||
|
return line->mark() == mark;
|
||||||
|
});
|
||||||
|
auto index = it == m_history.rend() ? 0 : m_history.size() - it.index();
|
||||||
|
m_client.terminal_history_changed(m_history.size() - index);
|
||||||
|
auto count = m_history.size() - index;
|
||||||
|
for (size_t i = 0; i < count; ++i) {
|
||||||
|
if (auto mark = m_history[index + i]->mark(); mark.has_value())
|
||||||
|
m_valid_marks.remove(*mark);
|
||||||
|
}
|
||||||
|
m_history.remove(index, count);
|
||||||
|
cursor_row = 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
void Terminal::mark_cursor()
|
||||||
|
{
|
||||||
|
static u32 next_mark_id { 0 };
|
||||||
|
|
||||||
|
auto& line = active_buffer()[cursor_row()];
|
||||||
|
if (line->mark().has_value()) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
auto mark = Mark(next_mark_id++);
|
||||||
|
line->set_marked(mark);
|
||||||
|
m_valid_marks.set(mark);
|
||||||
|
}
|
||||||
#endif
|
#endif
|
||||||
|
|
||||||
void Terminal::alter_ansi_mode(bool should_set, Parameters params)
|
void Terminal::alter_ansi_mode(bool should_set, Parameters params)
|
||||||
|
|
|
@ -17,6 +17,7 @@
|
||||||
|
|
||||||
#ifndef KERNEL
|
#ifndef KERNEL
|
||||||
# include <AK/ByteString.h>
|
# include <AK/ByteString.h>
|
||||||
|
# include <AK/HashTable.h>
|
||||||
# include <LibVT/Attribute.h>
|
# include <LibVT/Attribute.h>
|
||||||
# include <LibVT/Line.h>
|
# include <LibVT/Line.h>
|
||||||
#else
|
#else
|
||||||
|
@ -49,6 +50,7 @@ public:
|
||||||
virtual void set_window_progress(int value, int max) = 0;
|
virtual void set_window_progress(int value, int max) = 0;
|
||||||
virtual void terminal_did_resize(u16 columns, u16 rows) = 0;
|
virtual void terminal_did_resize(u16 columns, u16 rows) = 0;
|
||||||
virtual void terminal_history_changed(int delta) = 0;
|
virtual void terminal_history_changed(int delta) = 0;
|
||||||
|
virtual void terminal_did_perform_possibly_partial_clear() = 0;
|
||||||
virtual void emit(u8 const*, size_t) = 0;
|
virtual void emit(u8 const*, size_t) = 0;
|
||||||
virtual void set_cursor_shape(CursorShape) = 0;
|
virtual void set_cursor_shape(CursorShape) = 0;
|
||||||
virtual void set_cursor_blinking(bool) = 0;
|
virtual void set_cursor_blinking(bool) = 0;
|
||||||
|
@ -85,8 +87,12 @@ public:
|
||||||
}
|
}
|
||||||
|
|
||||||
#ifndef KERNEL
|
#ifndef KERNEL
|
||||||
|
void mark_cursor();
|
||||||
|
OrderedHashTable<Mark> const& marks() const { return m_valid_marks; }
|
||||||
|
|
||||||
void clear();
|
void clear();
|
||||||
void clear_history();
|
void clear_history();
|
||||||
|
void clear_to_mark(Mark);
|
||||||
#else
|
#else
|
||||||
virtual void clear() = 0;
|
virtual void clear() = 0;
|
||||||
virtual void clear_history() = 0;
|
virtual void clear_history() = 0;
|
||||||
|
@ -438,6 +444,7 @@ protected:
|
||||||
#ifndef KERNEL
|
#ifndef KERNEL
|
||||||
ByteString m_current_window_title;
|
ByteString m_current_window_title;
|
||||||
Vector<ByteString> m_title_stack;
|
Vector<ByteString> m_title_stack;
|
||||||
|
OrderedHashTable<Mark> m_valid_marks;
|
||||||
#endif
|
#endif
|
||||||
|
|
||||||
#ifndef KERNEL
|
#ifndef KERNEL
|
||||||
|
|
|
@ -69,8 +69,18 @@ void TerminalWidget::set_pty_master_fd(int fd)
|
||||||
set_pty_master_fd(-1);
|
set_pty_master_fd(-1);
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
for (ssize_t i = 0; i < nread; ++i)
|
for (ssize_t i = 0; i < nread; ++i)
|
||||||
m_terminal.on_input(buffer[i]);
|
m_terminal.on_input(buffer[i]);
|
||||||
|
|
||||||
|
auto owned_by_startup_process = m_startup_process_owns_pty;
|
||||||
|
auto pgrp = tcgetpgrp(m_ptm_fd);
|
||||||
|
m_startup_process_owns_pty = pgrp == m_startup_process_id;
|
||||||
|
if (m_startup_process_owns_pty != owned_by_startup_process) {
|
||||||
|
// pty owner state changed, handle it.
|
||||||
|
handle_pty_owner_change(pgrp);
|
||||||
|
}
|
||||||
|
|
||||||
flush_dirty_lines();
|
flush_dirty_lines();
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
@ -140,11 +150,16 @@ TerminalWidget::TerminalWidget(int ptm_fd, bool automatic_size_policy)
|
||||||
clear_including_history();
|
clear_including_history();
|
||||||
});
|
});
|
||||||
|
|
||||||
|
m_clear_to_previous_mark_action = GUI::Action::create("Clear &Previous Command", { Mod_Ctrl | Mod_Shift, Key_U }, [this](auto&) {
|
||||||
|
clear_to_previous_mark();
|
||||||
|
});
|
||||||
|
|
||||||
m_context_menu = GUI::Menu::construct();
|
m_context_menu = GUI::Menu::construct();
|
||||||
m_context_menu->add_action(copy_action());
|
m_context_menu->add_action(copy_action());
|
||||||
m_context_menu->add_action(paste_action());
|
m_context_menu->add_action(paste_action());
|
||||||
m_context_menu->add_separator();
|
m_context_menu->add_separator();
|
||||||
m_context_menu->add_action(clear_including_history_action());
|
m_context_menu->add_action(clear_including_history_action());
|
||||||
|
m_context_menu->add_action(clear_to_previous_mark_action());
|
||||||
|
|
||||||
update_copy_action();
|
update_copy_action();
|
||||||
update_paste_action();
|
update_paste_action();
|
||||||
|
@ -1035,6 +1050,22 @@ void TerminalWidget::terminal_history_changed(int delta)
|
||||||
m_selection.offset_row(delta);
|
m_selection.offset_row(delta);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
void TerminalWidget::terminal_did_perform_possibly_partial_clear()
|
||||||
|
{
|
||||||
|
// Just pretend the whole terminal was cleared.
|
||||||
|
// Force an update by resizing slightly and then back to the original size.
|
||||||
|
winsize ws;
|
||||||
|
for (ssize_t offset = 1; offset >= 0; --offset) {
|
||||||
|
ws.ws_col = m_terminal.columns() - offset;
|
||||||
|
ws.ws_row = m_terminal.rows() - offset;
|
||||||
|
if (m_ptm_fd != -1) {
|
||||||
|
if (ioctl(m_ptm_fd, TIOCSWINSZ, &ws) < 0) {
|
||||||
|
perror("ioctl(TIOCSWINSZ)");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
void TerminalWidget::terminal_did_resize(u16 columns, u16 rows)
|
void TerminalWidget::terminal_did_resize(u16 columns, u16 rows)
|
||||||
{
|
{
|
||||||
auto pixel_size = widget_size_for_font(font());
|
auto pixel_size = widget_size_for_font(font());
|
||||||
|
@ -1218,6 +1249,15 @@ void TerminalWidget::clear_including_history()
|
||||||
m_terminal.clear_including_history();
|
m_terminal.clear_including_history();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
void TerminalWidget::clear_to_previous_mark()
|
||||||
|
{
|
||||||
|
auto marks = m_terminal.marks().values();
|
||||||
|
size_t offset = m_startup_process_owns_pty ? 2 : 1; // If the shell is the active process, we have an extra mark.
|
||||||
|
if (marks.size() < offset)
|
||||||
|
return;
|
||||||
|
m_terminal.clear_to_mark(marks[marks.size() - offset]);
|
||||||
|
}
|
||||||
|
|
||||||
void TerminalWidget::scroll_to_bottom()
|
void TerminalWidget::scroll_to_bottom()
|
||||||
{
|
{
|
||||||
m_scrollbar->set_value(m_scrollbar->max());
|
m_scrollbar->set_value(m_scrollbar->max());
|
||||||
|
@ -1375,4 +1415,11 @@ ByteString TerminalWidget::stringify_cursor_shape(VT::CursorShape cursor_shape)
|
||||||
VERIFY_NOT_REACHED();
|
VERIFY_NOT_REACHED();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
void TerminalWidget::handle_pty_owner_change(pid_t new_owner)
|
||||||
|
{
|
||||||
|
if (m_auto_mark_mode == AutoMarkMode::MarkInteractiveShellPrompt && new_owner == m_startup_process_id) {
|
||||||
|
m_terminal.mark_cursor();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
|
@ -55,6 +55,14 @@ public:
|
||||||
BellMode bell_mode() { return m_bell_mode; }
|
BellMode bell_mode() { return m_bell_mode; }
|
||||||
void set_bell_mode(BellMode bm) { m_bell_mode = bm; }
|
void set_bell_mode(BellMode bm) { m_bell_mode = bm; }
|
||||||
|
|
||||||
|
enum class AutoMarkMode {
|
||||||
|
MarkNothing,
|
||||||
|
MarkInteractiveShellPrompt,
|
||||||
|
};
|
||||||
|
|
||||||
|
AutoMarkMode auto_mark_mode() { return m_auto_mark_mode; }
|
||||||
|
void set_auto_mark_mode(AutoMarkMode am) { m_auto_mark_mode = am; }
|
||||||
|
|
||||||
bool has_selection() const;
|
bool has_selection() const;
|
||||||
bool selection_contains(const VT::Position&) const;
|
bool selection_contains(const VT::Position&) const;
|
||||||
ByteString selected_text() const;
|
ByteString selected_text() const;
|
||||||
|
@ -77,10 +85,14 @@ public:
|
||||||
GUI::Action& copy_action() { return *m_copy_action; }
|
GUI::Action& copy_action() { return *m_copy_action; }
|
||||||
GUI::Action& paste_action() { return *m_paste_action; }
|
GUI::Action& paste_action() { return *m_paste_action; }
|
||||||
GUI::Action& clear_including_history_action() { return *m_clear_including_history_action; }
|
GUI::Action& clear_including_history_action() { return *m_clear_including_history_action; }
|
||||||
|
GUI::Action& clear_to_previous_mark_action() { return *m_clear_to_previous_mark_action; }
|
||||||
|
|
||||||
void copy();
|
void copy();
|
||||||
void paste();
|
void paste();
|
||||||
void clear_including_history();
|
void clear_including_history();
|
||||||
|
void clear_to_previous_mark();
|
||||||
|
|
||||||
|
void set_startup_process_id(pid_t pid) { m_startup_process_id = pid; }
|
||||||
|
|
||||||
const StringView color_scheme_name() const { return m_color_scheme_name; }
|
const StringView color_scheme_name() const { return m_color_scheme_name; }
|
||||||
|
|
||||||
|
@ -133,6 +145,7 @@ private:
|
||||||
virtual void set_window_progress(int value, int max) override;
|
virtual void set_window_progress(int value, int max) override;
|
||||||
virtual void terminal_did_resize(u16 columns, u16 rows) override;
|
virtual void terminal_did_resize(u16 columns, u16 rows) override;
|
||||||
virtual void terminal_history_changed(int delta) override;
|
virtual void terminal_history_changed(int delta) override;
|
||||||
|
virtual void terminal_did_perform_possibly_partial_clear() override;
|
||||||
virtual void emit(u8 const*, size_t) override;
|
virtual void emit(u8 const*, size_t) override;
|
||||||
|
|
||||||
// ^GUI::Clipboard::ClipboardClient
|
// ^GUI::Clipboard::ClipboardClient
|
||||||
|
@ -163,6 +176,8 @@ private:
|
||||||
|
|
||||||
void update_cached_font_metrics();
|
void update_cached_font_metrics();
|
||||||
|
|
||||||
|
void handle_pty_owner_change(pid_t new_owner);
|
||||||
|
|
||||||
VT::Terminal m_terminal;
|
VT::Terminal m_terminal;
|
||||||
|
|
||||||
VT::Range m_selection;
|
VT::Range m_selection;
|
||||||
|
@ -183,6 +198,8 @@ private:
|
||||||
|
|
||||||
ByteString m_color_scheme_name;
|
ByteString m_color_scheme_name;
|
||||||
|
|
||||||
|
AutoMarkMode m_auto_mark_mode { AutoMarkMode::MarkInteractiveShellPrompt };
|
||||||
|
|
||||||
BellMode m_bell_mode { BellMode::Visible };
|
BellMode m_bell_mode { BellMode::Visible };
|
||||||
bool m_alt_key_held { false };
|
bool m_alt_key_held { false };
|
||||||
bool m_rectangle_selection { false };
|
bool m_rectangle_selection { false };
|
||||||
|
@ -229,6 +246,7 @@ private:
|
||||||
RefPtr<GUI::Action> m_copy_action;
|
RefPtr<GUI::Action> m_copy_action;
|
||||||
RefPtr<GUI::Action> m_paste_action;
|
RefPtr<GUI::Action> m_paste_action;
|
||||||
RefPtr<GUI::Action> m_clear_including_history_action;
|
RefPtr<GUI::Action> m_clear_including_history_action;
|
||||||
|
RefPtr<GUI::Action> m_clear_to_previous_mark_action;
|
||||||
|
|
||||||
RefPtr<GUI::Menu> m_context_menu;
|
RefPtr<GUI::Menu> m_context_menu;
|
||||||
RefPtr<GUI::Menu> m_context_menu_for_hyperlink;
|
RefPtr<GUI::Menu> m_context_menu_for_hyperlink;
|
||||||
|
@ -237,6 +255,9 @@ private:
|
||||||
|
|
||||||
Gfx::IntPoint m_left_mousedown_position;
|
Gfx::IntPoint m_left_mousedown_position;
|
||||||
VT::Position m_left_mousedown_position_buffer;
|
VT::Position m_left_mousedown_position_buffer;
|
||||||
|
|
||||||
|
bool m_startup_process_owns_pty { false };
|
||||||
|
pid_t m_startup_process_id { -1 };
|
||||||
};
|
};
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
Loading…
Add table
Add a link
Reference in a new issue