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.
This commit is contained in:
Leroy Hopson 2024-03-03 20:27:59 +13:00
parent 8255d8b3ce
commit 71df1e71bd
No known key found for this signature in database
GPG key ID: D2747312A6DB51AA
5 changed files with 56 additions and 14 deletions

2
.gitmodules vendored
View file

@ -6,7 +6,7 @@
url = https://github.com/libuv/libuv url = https://github.com/libuv/libuv
[submodule "addons/godot_xterm/native/thirdparty/libtsm"] [submodule "addons/godot_xterm/native/thirdparty/libtsm"]
path = 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"] [submodule "addons/godot_xterm/native/thirdparty/node-pty"]
path = addons/godot_xterm/native/thirdparty/node-pty path = addons/godot_xterm/native/thirdparty/node-pty
url = https://github.com/microsoft/node-pty url = https://github.com/microsoft/node-pty

View file

@ -68,7 +68,8 @@ void Terminal::_bind_methods()
ClassDB::bind_method(D_METHOD("set_blink_off_time", "time"), &Terminal::set_blink_off_time); 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"); 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("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("set_copy_on_selection", "enabled"), &Terminal::set_copy_on_selection);
ClassDB::bind_method(D_METHOD("get_copy_on_selection"), &Terminal::get_copy_on_selection); ClassDB::bind_method(D_METHOD("get_copy_on_selection"), &Terminal::get_copy_on_selection);
@ -641,17 +642,25 @@ double Terminal::get_blink_off_time() const
return blink_off_time; return blink_off_time;
} }
String Terminal::copy_selection() { String Terminal::_copy_screen(ScreenCopyFunction func) {
char *out; char *out;
PackedByteArray data; PackedByteArray data;
data.resize(tsm_screen_selection_copy(screen, &out)); data.resize(func(screen, &out));
memcpy(data.ptrw(), out, data.size()); memcpy(data.ptrw(), out, data.size());
std::free(out); std::free(out);
return data.get_string_from_utf8(); return data.get_string_from_utf8();
} }
String Terminal::copy_all() {
return _copy_screen(&tsm_screen_copy_all);
}
String Terminal::copy_selection() {
return _copy_screen(&tsm_screen_selection_copy);
}
void Terminal::set_copy_on_selection(const bool p_enabled) { void Terminal::set_copy_on_selection(const bool p_enabled) {
copy_on_selection = p_enabled; copy_on_selection = p_enabled;
} }

View file

@ -3,7 +3,8 @@
#pragma once #pragma once
#include<map> #include <functional>
#include <map>
#include <godot_cpp/classes/control.hpp> #include <godot_cpp/classes/control.hpp>
#include <godot_cpp/classes/image_texture.hpp> #include <godot_cpp/classes/image_texture.hpp>
@ -71,6 +72,7 @@ namespace godot
void set_blink_off_time(const double p_blink_off_time); void set_blink_off_time(const double p_blink_off_time);
double get_blink_off_time() const; double get_blink_off_time() const;
String copy_all();
String copy_selection(); String copy_selection();
void set_copy_on_selection(const bool p_enable); void set_copy_on_selection(const bool p_enable);
bool get_copy_on_selection() const; bool get_copy_on_selection() const;
@ -170,6 +172,9 @@ namespace godot
Timer *selection_timer; Timer *selection_timer;
void _handle_selection(Ref<InputEventMouse> event); void _handle_selection(Ref<InputEventMouse> event);
void _on_selection_held(); void _on_selection_held();
typedef std::function<int(struct tsm_screen*, char**)> ScreenCopyFunction;
String _copy_screen(ScreenCopyFunction func);
}; };
} // namespace godot } // namespace godot

@ -1 +1 @@
Subproject commit 2131b47acdee1088a78ca922ca96361d6182a03f Subproject commit fa5021916aa8f4e292ae6dbbf9fc874ae517c3a3

View file

@ -48,8 +48,7 @@ class TestInterface:
func xtest_has_method_clear(): func xtest_has_method_clear():
assert_has_method_with_return_type("clear", TYPE_NIL) assert_has_method_with_return_type("clear", TYPE_NIL)
# TODO: Implement copy_all() method. func test_has_method_copy_all():
func xtest_has_method_copy_all():
assert_has_method_with_return_type("copy_all", TYPE_STRING) assert_has_method_with_return_type("copy_all", TYPE_STRING)
func test_has_method_copy_selection(): func test_has_method_copy_selection():
@ -192,3 +191,32 @@ class TestWrite:
func test_data_sent_not_emitted_when_empty_string_written(): func test_data_sent_not_emitted_when_empty_string_written():
subject.write("") subject.write("")
assert_signal_emit_count(subject, "data_sent", 0) 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)