From dd118d72f3b79abe3f5563ef07649bc6db48458e Mon Sep 17 00:00:00 2001 From: Leroy Hopson Date: Fri, 26 Apr 2024 13:58:49 +1200 Subject: [PATCH] feat(term): add select method Adds select() method to Terminal. Method behaves the same way as TextEdit's select method. --- addons/godot_xterm/native/src/terminal.cpp | 24 ++++++++ addons/godot_xterm/native/src/terminal.h | 2 + docs/CHANGELOG.md | 4 ++ docs/api/terminal.md | 23 +++++--- test/test_terminal.gd | 67 ++++++++++++++++++++++ 5 files changed, 112 insertions(+), 8 deletions(-) diff --git a/addons/godot_xterm/native/src/terminal.cpp b/addons/godot_xterm/native/src/terminal.cpp index 6d60145..f657ef8 100644 --- a/addons/godot_xterm/native/src/terminal.cpp +++ b/addons/godot_xterm/native/src/terminal.cpp @@ -3,6 +3,7 @@ #include "terminal.h" +#include #include #include #include @@ -70,6 +71,9 @@ 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. + ClassDB::bind_method(D_METHOD("select", "from_line", "from_column", "to_line", "to_column"), &Terminal::select); + // Copying. ClassDB::bind_method(D_METHOD("copy_all"), &Terminal::copy_all); ClassDB::bind_method(D_METHOD("copy_selection"), &Terminal::copy_selection); @@ -657,6 +661,26 @@ String Terminal::_copy_screen(ScreenCopyFunction func) { return data.get_string_from_utf8(); } +void Terminal::select(const int p_from_line, const int p_from_column, const int p_to_line, const int p_to_column) { + int from_line = std::clamp((int)p_from_line, 0, (int)rows); + int from_column = std::clamp((int)p_from_column, 0, (int)cols); + int to_line = std::clamp((int)p_to_line, 0, (int)rows); + int to_column = std::clamp((int)p_to_column, 0, (int)cols); + + if (from_line > to_line) { + std::swap(to_line, from_line); + std::swap(to_column, from_column); + } else if ((from_line == to_line) && (from_column > to_column)) { + std::swap(to_column, from_column); + } + + to_column -= 1; + + tsm_screen_selection_reset(screen); + tsm_screen_selection_start(screen, from_column, from_line); + tsm_screen_selection_target(screen, to_column, to_line); +} + String Terminal::copy_all() { return _copy_screen(&tsm_screen_copy_all); } diff --git a/addons/godot_xterm/native/src/terminal.h b/addons/godot_xterm/native/src/terminal.h index ae8c06a..949c4aa 100644 --- a/addons/godot_xterm/native/src/terminal.h +++ b/addons/godot_xterm/native/src/terminal.h @@ -74,6 +74,8 @@ namespace godot void clear(); + void select(const int p_from_line, const int p_from_column, const int p_to_line, const int p_to_column); + String copy_all(); String copy_selection(); void set_copy_on_selection(const bool p_enable); diff --git a/docs/CHANGELOG.md b/docs/CHANGELOG.md index cf488a7..85e2479 100644 --- a/docs/CHANGELOG.md +++ b/docs/CHANGELOG.md @@ -7,6 +7,10 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0 ## [Unreleased](https://github.com/lihop/godot-xterm/compare/v2.2.0...HEAD) +### Added + +- Added select() method to Terminal. + ### Changed - Custom export templates are no longer required when exporting to HTML5 from Godot v3.5.x. diff --git a/docs/api/terminal.md b/docs/api/terminal.md index 6015a66..a3dfd04 100644 --- a/docs/api/terminal.md +++ b/docs/api/terminal.md @@ -42,14 +42,15 @@ For example if the string `"\u001b[38;2;0;255;0;mA"` was written to the terminal ## Methods -| Returns | Signature | -| ---------- | ------------------------------------------------------------------- | -| void | [clear](#mthd-clear) **( )** | -| {{String}} | [copy_all](#mthd-copy_all) **( )** | -| {{String}} | [copy_selection](#mthd-copy_selection) **( )** | -| {{int}} | [get_cols](#mthd-get_cols) **( )** | -| {{int}} | [get_rows](#mthd-get_rows) **( )** | -| void | [write](#mthd-write) **(** {{String}}\|{{PoolByteArray}} data **)** | +| Returns | Signature | +| ---------- | ------------------------------------------------------------------------------------------------------------ | +| void | [clear](#mthd-clear) **( )** | +| {{String}} | [copy_all](#mthd-copy_all) **( )** | +| {{String}} | [copy_selection](#mthd-copy_selection) **( )** | +| {{int}} | [get_cols](#mthd-get_cols) **( )** | +| {{int}} | [get_rows](#mthd-get_rows) **( )** | +| void | [select](#mthd-select) **(** {{int}} from_line, {{int}} from_column, {{int}} to_line {{int}} to_column **)** | +| void | [write](#mthd-write) **(** {{String}}\|{{PoolByteArray}} data **)** | ## Signals @@ -189,6 +190,12 @@ Returns the height of the terminal in characters. When using a monospace font, this is the number of visible characters that can fit from the top of the terminal to the bottom in a single column. It will automatically update according to the terminal's rect_size and theme's font size. +
+ +void **select** **(** {{int}} from_line, {{int}} from_column, {{int}} to_line, {{int}} to_column **)** + +Perform selection, from line/column to line/column. +
void **write** **(** {{String}}\|{{PoolByteArray}} data **)** diff --git a/test/test_terminal.gd b/test/test_terminal.gd index b47b188..ea3e0a8 100644 --- a/test/test_terminal.gd +++ b/test/test_terminal.gd @@ -243,3 +243,70 @@ class TestClear: var screen_after = subject.copy_all() var expected = final_line + "\n".repeat(subject.get_rows()) assert_eq(screen_after, expected) + + +class TestSelect: + extends TerminalTest + + # Use the behavior of TextEdit's select() method as a reference. + var text_edit: TextEdit + + func assert_select_eq(argv, expected): + text_edit.callv("select", argv) + subject.callv("select", argv) + assert_eq( + expected, + text_edit.get_selected_text(), + "expected does not match reference implementation" + ) + assert_eq(subject.copy_selection(), expected) + + func before_each(): + super.before_each() + text_edit = TextEdit.new() + text_edit.text = "0123456789\nABCDEFGHIJ\n)!@#$%^&*(\n\n\n\n\n\n\n" + add_child_autofree(text_edit) + subject.write("0123456789\r\nABCDEFGHIJ\r\n)!@#$%^&*(") + + func test_select_nothing(): + assert_select_eq([0, 0, 0, 0], "") + + func test_select_first_character(): + assert_select_eq([0, 0, 0, 1], "0") + + func test_select_last_character(): + assert_select_eq([2, 9, 2, 10], "(") + + func test_select_reverse_column(): + assert_select_eq([0, 6, 0, 1], "12345") + + func test_select_preceeds_column_bounds(): + assert_select_eq([0, -2, 0, -1], "") + assert_select_eq([0, -2, 0, 0], "") + assert_select_eq([0, -2, 0, 1], "0") + + func test_select_exceeds_column_bounds(): + assert_select_eq([0, 5, 0, 999], "56789") + + func test_select_first_row(): + assert_select_eq([0, 0, 0, 10], "0123456789") + + func test_select_second_row(): + assert_select_eq([1, 0, 1, 10], "ABCDEFGHIJ") + + func test_select_multiple_rows(): + assert_select_eq([0, 0, 1, 10], "0123456789\nABCDEFGHIJ") + + func test_select_rows_reverse(): + assert_select_eq([1, 5, 0, 0], "0123456789\nABCDE") + + func test_select_preceeds_row_bounds(): + assert_select_eq([-2, 0, -1, 10], "0123456789") + assert_select_eq([-2, 0, 0, 10], "0123456789") + assert_select_eq([-2, 0, 1, 10], "0123456789\nABCDEFGHIJ") + + func test_select_exceeds_row_bounds(): + assert_select_eq([1, 5, 999, 999], "FGHIJ\n)!@#$%^&*(\n\n\n\n\n\n\n") + + func test_wide_bounds(): + assert_select_eq([-999, -999, 999, 999], "0123456789\nABCDEFGHIJ\n)!@#$%^&*(\n\n\n\n\n\n\n")