Skip to content

Commit

Permalink
WIP
Browse files Browse the repository at this point in the history
  • Loading branch information
lixk28 committed Jun 11, 2023
1 parent 8742020 commit 852e526
Show file tree
Hide file tree
Showing 8 changed files with 444 additions and 243 deletions.
3 changes: 3 additions & 0 deletions Userland/Applications/Terminal/CMakeLists.txt
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,9 @@ serenity_component(

set(SOURCES
main.cpp
TerminalChangeListener.cpp
TerminalTabWidget.cpp
TerminalUtilities.cpp
)

serenity_app(Terminal ICON app-terminal)
Expand Down
64 changes: 64 additions & 0 deletions Userland/Applications/Terminal/TerminalChangeListener.cpp
Original file line number Diff line number Diff line change
@@ -0,0 +1,64 @@
/*
* Copyright (c) 2023, the SerenityOS developers.
*
* SPDX-License-Identifier: BSD-2-Clause
*/

#include "TerminalChangeListener.h"
#include <LibGUI/Window.h>

void TerminalChangeListener::config_bool_did_change(DeprecatedString const& domain, DeprecatedString const& group, DeprecatedString const& key, bool value)
{
VERIFY(domain == "Terminal");

if (group == "Terminal") {
if (key == "ShowScrollBar")
m_parent_terminal.set_show_scrollbar(value);
else if (key == "ConfirmClose" && on_confirm_close_changed)
on_confirm_close_changed(value);
} else if (group == "Cursor" && key == "Blinking") {
m_parent_terminal.set_cursor_blinking(value);
}
}

void TerminalChangeListener::config_string_did_change(DeprecatedString const& domain, DeprecatedString const& group, DeprecatedString const& key, DeprecatedString const& value)
{
VERIFY(domain == "Terminal");

if (group == "Window" && key == "Bell") {
auto bell_mode = VT::TerminalWidget::BellMode::Visible;
if (value == "AudibleBeep")
bell_mode = VT::TerminalWidget::BellMode::AudibleBeep;
if (value == "Visible")
bell_mode = VT::TerminalWidget::BellMode::Visible;
if (value == "Disabled")
bell_mode = VT::TerminalWidget::BellMode::Disabled;
m_parent_terminal.set_bell_mode(bell_mode);
} else if (group == "Text" && key == "Font") {
auto font = Gfx::FontDatabase::the().get_by_name(value);
if (font.is_null())
font = Gfx::FontDatabase::default_fixed_width_font();
m_parent_terminal.set_font_and_resize_to_fit(*font);
m_parent_terminal.apply_size_increments_to_window(*m_parent_terminal.window());
m_parent_terminal.window()->resize(m_parent_terminal.size());
} else if (group == "Cursor" && key == "Shape") {
auto cursor_shape = VT::TerminalWidget::parse_cursor_shape(value).value_or(VT::CursorShape::Block);
m_parent_terminal.set_cursor_shape(cursor_shape);
}
}

void TerminalChangeListener::config_i32_did_change(DeprecatedString const& domain, DeprecatedString const& group, DeprecatedString const& key, i32 value)
{
VERIFY(domain == "Terminal");

if (group == "Terminal" && key == "MaxHistorySize") {
m_parent_terminal.set_max_history_size(value);
} else if (group == "Window" && key == "Opacity") {
m_parent_terminal.set_opacity(value);
}
}

void TerminalChangeListener::set_parent_terminal(VT::TerminalWidget* terminal_widget)
{
m_parent_terminal = *terminal_widget;
}
31 changes: 31 additions & 0 deletions Userland/Applications/Terminal/TerminalChangeListener.h
Original file line number Diff line number Diff line change
@@ -0,0 +1,31 @@
/*
* Copyright (c) 2023, the SerenityOS developers.
*
* SPDX-License-Identifier: BSD-2-Clause
*/

#pragma once

#include <AK/WeakPtr.h>
#include <LibGfx/Font/FontDatabase.h>
#include <LibConfig/Listener.h>
#include <LibVT/TerminalWidget.h>

class TerminalChangeListener final : public Config::Listener {
public:
TerminalChangeListener(VT::TerminalWidget& parent_terminal)
: m_parent_terminal(&parent_terminal)
{
}

virtual void config_bool_did_change(DeprecatedString const& domain, DeprecatedString const& group, DeprecatedString const& key, bool value) override;
virtual void config_string_did_change(DeprecatedString const& domain, DeprecatedString const& group, DeprecatedString const& key, DeprecatedString const& value) override;
virtual void config_i32_did_change(DeprecatedString const& domain, DeprecatedString const& group, DeprecatedString const& key, i32 value) override;

Function<void(bool)> on_confirm_close_changed;

void set_parent_terminal(VT::TerminalWidget& terminal) { m_parent_terminal = &terminal; }

private:
VT::TerminalWidget* m_parent_terminal;
};
190 changes: 190 additions & 0 deletions Userland/Applications/Terminal/TerminalTabWidget.cpp
Original file line number Diff line number Diff line change
@@ -0,0 +1,190 @@
/*
* Copyright (c) 2022, the SerenityOS developers.
*
* SPDX-License-Identifier: BSD-2-Clause
*/

#include "TerminalChangeListener.h"
#include "TerminalTabWidget.h"
#include "TerminalUtilities.h"
#include <LibCore/System.h>
#include <LibConfig/Client.h>
#include <LibGfx/Palette.h>
#include <LibGUI/Button.h>
#include <LibGUI/CheckBox.h>
#include <LibGUI/Menu.h>
#include <LibGUI/Process.h>
#include <LibGUI/TextBox.h>
#include <LibGUI/Window.h>
#include <pty.h>

TerminalTabWidget::TerminalTabWidget()
{
m_find_window = MUST(create_and_setup_find_window());

m_terminal_change_listener.on_confirm_close_changed = [&](bool confirm_close) {
if (confirm_close) {
modified_state_check_timer->start();
} else {
modified_state_check_timer->stop();
window()->set_modified(false);
}
should_confirm_close = confirm_close;
};

// TODO: This should be moved into main.cpp
on_tab_count_change = [this](size_t tab_count) {
set_bar_visible(tab_count > 1);
};
}

void TerminalTabWidget::keydown_event(GUI::KeyEvent& event)
{
if (event.ctrl() && event.shift() && event.key() == Key_T) {
auto new_tab = MUST(create_and_setup_new_tab());
MUST(add_tab(new_tab, MUST("A New Tab"_string)));
set_active_widget(new_tab);
}

return GUI::TabWidget::keydown_event(event);
}

ErrorOr<NonnullRefPtr<VT::TerminalWidget>> TerminalTabWidget::create_and_setup_new_tab()
{
int ptm_fd;
pid_t shell_pid = forkpty(&ptm_fd, nullptr, nullptr, nullptr);
if (shell_pid == 0) {
MUST(run_command(Config::read_string("Terminal"sv, "Startup"sv, "Command"sv, ""sv), false));
VERIFY_NOT_REACHED();
}

auto ptsname = TRY(Core::System::ptsname(ptm_fd));
utmp_update(ptsname, shell_pid, true);

auto terminal = TRY(VT::TerminalWidget::try_create(ptm_fd, true));

terminal->on_command_exit = [&] {
if (tab_count() > 1) {
remove_tab(terminal);
activate_last_tab();
} else if (tab_count() == 1) {
// Is this OK?
window()->close();
} else {
VERIFY_NOT_REACHED();
}
};
terminal->on_title_change = [&](auto title) {
window()->set_title(title);
};
terminal->on_terminal_size_change = [&](auto size) {
window()->resize(size);
};
terminal->apply_size_increments_to_window(*window());

Config::monitor_domain("Terminal");
auto should_confirm_close = Config::read_bool("Terminal"sv, "Terminal"sv, "ConfirmClose"sv, true);

auto bell = Config::read_string("Terminal"sv, "Window"sv, "Bell"sv, "Visible"sv);
if (bell == "AudibleBeep") {
terminal->set_bell_mode(VT::TerminalWidget::BellMode::AudibleBeep);
} else if (bell == "Disabled") {
terminal->set_bell_mode(VT::TerminalWidget::BellMode::Disabled);
} else {
terminal->set_bell_mode(VT::TerminalWidget::BellMode::Visible);
}

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);

auto cursor_blinking = Config::read_bool("Terminal"sv, "Cursor"sv, "Blinking"sv, true);
terminal->set_cursor_blinking(cursor_blinking);

auto new_opacity = Config::read_i32("Terminal"sv, "Window"sv, "Opacity"sv, 255);
terminal->set_opacity(new_opacity);
window()->set_has_alpha_channel(new_opacity < 255);

auto new_scrollback_size = Config::read_i32("Terminal"sv, "Terminal"sv, "MaxHistorySize"sv, terminal->max_history_size());
terminal->set_max_history_size(new_scrollback_size);

auto show_scroll_bar = Config::read_bool("Terminal"sv, "Terminal"sv, "ShowScrollBar"sv, true);
terminal->set_show_scrollbar(show_scroll_bar);

auto open_settings_action = TRY(GUI::Action::try_create("Terminal &Settings", TRY(Gfx::Bitmap::load_from_file("/res/icons/16x16/settings.png"sv)), [this](auto&) {
GUI::Process::spawn_or_show_error(window(), "/bin/TerminalSettings"sv);
}));
TRY(terminal->context_menu().try_add_separator());
TRY(terminal->context_menu().try_add_action(open_settings_action));

return terminal;
}

ErrorOr<NonnullRefPtr<GUI::Window>> TerminalTabWidget::create_and_setup_find_window()
{
auto window = TRY(GUI::Window::try_create(*this));
window->set_window_mode(GUI::WindowMode::RenderAbove);
window->set_title("Find in Terminal");
window->set_resizable(false);
window->resize(300, 90);

auto main_widget = TRY(window->set_main_widget<GUI::Widget>());
main_widget->set_fill_with_background_color(true);
main_widget->set_background_role(ColorRole::Button);
TRY(main_widget->try_set_layout<GUI::VerticalBoxLayout>(4));

auto find = TRY(main_widget->try_add<GUI::Widget>());
TRY(find->try_set_layout<GUI::HorizontalBoxLayout>(4));
find->set_fixed_height(30);

auto find_textbox = TRY(find->try_add<GUI::TextBox>());
find_textbox->set_fixed_width(230);
find_textbox->set_focus(true);
if (current_tab().has_selection())
find_textbox->set_text(current_tab().selected_text().replace("\n"sv, " "sv, ReplaceMode::All));
auto find_backwards = TRY(find->try_add<GUI::Button>());
find_backwards->set_fixed_width(25);
find_backwards->set_icon(TRY(Gfx::Bitmap::load_from_file("/res/icons/16x16/upward-triangle.png"sv)));
auto find_forwards = TRY(find->try_add<GUI::Button>());
find_forwards->set_fixed_width(25);
find_forwards->set_icon(TRY(Gfx::Bitmap::load_from_file("/res/icons/16x16/downward-triangle.png"sv)));

find_textbox->on_return_pressed = [find_backwards] {
find_backwards->click();
};

find_textbox->on_shift_return_pressed = [find_forwards] {
find_forwards->click();
};

auto match_case = TRY(main_widget->try_add<GUI::CheckBox>(TRY("Case sensitive"_string)));
auto wrap_around = TRY(main_widget->try_add<GUI::CheckBox>(TRY("Wrap around"_string)));

find_backwards->on_click = [this, find_textbox, match_case, wrap_around](auto) {
auto needle = find_textbox->text();
if (needle.is_empty()) {
return;
}

auto found_range = current_tab().find_previous(needle, current_tab().normalized_selection().start(), match_case->is_checked(), wrap_around->is_checked());

if (found_range.is_valid()) {
current_tab().scroll_to_row(found_range.start().row());
current_tab().set_selection(found_range);
}
};
find_forwards->on_click = [this, find_textbox, match_case, wrap_around](auto) {
auto needle = find_textbox->text();
if (needle.is_empty()) {
return;
}

auto found_range = current_tab().find_next(needle, current_tab().normalized_selection().end(), match_case->is_checked(), wrap_around->is_checked());

if (found_range.is_valid()) {
current_tab().scroll_to_row(found_range.start().row());
current_tab().set_selection(found_range);
}
};

return window;
}
39 changes: 39 additions & 0 deletions Userland/Applications/Terminal/TerminalTabWidget.h
Original file line number Diff line number Diff line change
@@ -0,0 +1,39 @@
/*
* Copyright (c) 2023, the SerenityOS developers.
*
* SPDX-License-Identifier: BSD-2-Clause
*/

#pragma once

#include "TerminalChangeListener.h"
#include <LibConfig/Listener.h>
#include <LibGUI/TabWidget.h>
#include <LibGUI/Window.h>
#include <LibVT/TerminalWidget.h>

class TerminalTabWidget final : public GUI::TabWidget {
C_OBJECT(TerminalTabWidget);

public:
ErrorOr<void> add_tab(NonnullRefPtr<VT::TerminalWidget> const& tab, String title);
// ErrorOr<void> remove_current_tab();

// ErrorOr<NonnullRefPtr<VT::TerminalWidget>> create_and_setup_new_tab_at();
// Note: These casts are OK since we only add VT::TerminalWidget
VT::TerminalWidget& current_tab() { return *verify_cast<VT::TerminalWidget>(active_widget()); }
// NonnullRefPtr<VT::TerminalWidget> last_active_tab() const;

GUI::Window& find_window() const { return *m_find_window; };

private:
TerminalTabWidget();

virtual void keydown_event(GUI::KeyEvent& event) override;

ErrorOr<NonnullRefPtr<VT::TerminalWidget>> create_and_setup_new_tab();
ErrorOr<NonnullRefPtr<GUI::Window>> create_and_setup_find_window();

RefPtr<GUI::Window> m_find_window;
TerminalChangeListener m_terminal_change_listener;
};
Loading

0 comments on commit 852e526

Please sign in to comment.