From 71df1e71bdc73c7e2162eaff951edb15a73cdabf Mon Sep 17 00:00:00 2001 From: Leroy Hopson Date: Sun, 3 Mar 2024 20:27:59 +1300 Subject: [PATCH] feat(term): implement copy_all() Implements the copy_all() method which copies all text in the screen including text in the scrollback buffer. Includes a fix to an upstream bug in libtsm that resulted in double the number of '\n' characters being copied for each row. --- .gitmodules | 2 +- addons/godot_xterm/native/src/terminal.cpp | 27 +++++++++++------ addons/godot_xterm/native/src/terminal.h | 7 ++++- addons/godot_xterm/native/thirdparty/libtsm | 2 +- test/test_terminal.gd | 32 +++++++++++++++++++-- 5 files changed, 56 insertions(+), 14 deletions(-) diff --git a/.gitmodules b/.gitmodules index f539184..5a977fa 100644 --- a/.gitmodules +++ b/.gitmodules @@ -6,7 +6,7 @@ url = https://github.com/libuv/libuv [submodule "addons/godot_xterm/native/thirdparty/libtsm"] path = addons/godot_xterm/native/thirdparty/libtsm - url = https://github.com/Aetf/libtsm + url = https://github.com/lihop/libtsm [submodule "addons/godot_xterm/native/thirdparty/node-pty"] path = addons/godot_xterm/native/thirdparty/node-pty url = https://github.com/microsoft/node-pty diff --git a/addons/godot_xterm/native/src/terminal.cpp b/addons/godot_xterm/native/src/terminal.cpp index e5a0946..2fd6baa 100644 --- a/addons/godot_xterm/native/src/terminal.cpp +++ b/addons/godot_xterm/native/src/terminal.cpp @@ -68,7 +68,8 @@ void Terminal::_bind_methods() ClassDB::bind_method(D_METHOD("set_blink_off_time", "time"), &Terminal::set_blink_off_time); ClassDB::add_property("Terminal", PropertyInfo(Variant::FLOAT, "blink_off_time"), "set_blink_off_time", "get_blink_off_time"); - // Selection copying. + // Copying. + ClassDB::bind_method(D_METHOD("copy_all"), &Terminal::copy_all); ClassDB::bind_method(D_METHOD("copy_selection"), &Terminal::copy_selection); ClassDB::bind_method(D_METHOD("set_copy_on_selection", "enabled"), &Terminal::set_copy_on_selection); ClassDB::bind_method(D_METHOD("get_copy_on_selection"), &Terminal::get_copy_on_selection); @@ -641,15 +642,23 @@ double Terminal::get_blink_off_time() const return blink_off_time; } +String Terminal::_copy_screen(ScreenCopyFunction func) { + char *out; + PackedByteArray data; + + data.resize(func(screen, &out)); + memcpy(data.ptrw(), out, data.size()); + std::free(out); + + return data.get_string_from_utf8(); +} + +String Terminal::copy_all() { + return _copy_screen(&tsm_screen_copy_all); +} + String Terminal::copy_selection() { - char *out; - PackedByteArray data; - - data.resize(tsm_screen_selection_copy(screen, &out)); - memcpy(data.ptrw(), out, data.size()); - std::free(out); - - return data.get_string_from_utf8(); + return _copy_screen(&tsm_screen_selection_copy); } void Terminal::set_copy_on_selection(const bool p_enabled) { diff --git a/addons/godot_xterm/native/src/terminal.h b/addons/godot_xterm/native/src/terminal.h index 0f46f61..a87ae0c 100644 --- a/addons/godot_xterm/native/src/terminal.h +++ b/addons/godot_xterm/native/src/terminal.h @@ -3,7 +3,8 @@ #pragma once -#include +#include +#include #include #include @@ -71,6 +72,7 @@ namespace godot void set_blink_off_time(const double p_blink_off_time); double get_blink_off_time() const; + String copy_all(); String copy_selection(); void set_copy_on_selection(const bool p_enable); bool get_copy_on_selection() const; @@ -170,6 +172,9 @@ namespace godot Timer *selection_timer; void _handle_selection(Ref event); void _on_selection_held(); + + typedef std::function ScreenCopyFunction; + String _copy_screen(ScreenCopyFunction func); }; } // namespace godot diff --git a/addons/godot_xterm/native/thirdparty/libtsm b/addons/godot_xterm/native/thirdparty/libtsm index 2131b47..fa50219 160000 --- a/addons/godot_xterm/native/thirdparty/libtsm +++ b/addons/godot_xterm/native/thirdparty/libtsm @@ -1 +1 @@ -Subproject commit 2131b47acdee1088a78ca922ca96361d6182a03f +Subproject commit fa5021916aa8f4e292ae6dbbf9fc874ae517c3a3 diff --git a/test/test_terminal.gd b/test/test_terminal.gd index 0d3a3ff..ded9651 100644 --- a/test/test_terminal.gd +++ b/test/test_terminal.gd @@ -48,8 +48,7 @@ class TestInterface: func xtest_has_method_clear(): assert_has_method_with_return_type("clear", TYPE_NIL) - # TODO: Implement copy_all() method. - func xtest_has_method_copy_all(): + func test_has_method_copy_all(): assert_has_method_with_return_type("copy_all", TYPE_STRING) func test_has_method_copy_selection(): @@ -192,3 +191,32 @@ class TestWrite: func test_data_sent_not_emitted_when_empty_string_written(): subject.write("") assert_signal_emit_count(subject, "data_sent", 0) + + +class TestCopy: + extends TerminalTest + + func fill_screen(char: String = "A") -> String: + var result = char.repeat(subject.get_cols() * subject.get_rows()) + subject.write(result) + return result + + func test_copy_all_copies_the_entire_screen(): + var text = fill_screen() + # The text will be wrapped over multiple lines and copy_all() preserves + # these line wraps, therefore we need to strip them. + assert_eq(subject.copy_all().replace("\n", ""), text) + + func test_copy_all_empty_screen(): + assert_eq(subject.copy_all(), "\n".repeat(subject.get_rows())) + + func test_copy_all_copies_the_scrollback_buffer(): + var text = fill_screen() + text += fill_screen("B") + text += fill_screen("C") + assert_eq(subject.copy_all().replace("\n", ""), text) + + func test_copy_all_copies_unicode_text(): + var text = "アイウエオカキクケコサシスセソ" + subject.write(text) + assert_string_contains(subject.copy_all(), text)