feat(term): implement clear() method

Clears all but the bottommost row of the terminal (including scrollback
buffer) and moves the bottommost row to the top.
This commit is contained in:
Leroy Hopson 2024-03-03 22:25:14 +13:00
parent fc03595e29
commit d00a31fb45
No known key found for this signature in database
GPG key ID: D2747312A6DB51AA
4 changed files with 82 additions and 12 deletions

View file

@ -75,8 +75,8 @@ void Terminal::_bind_methods()
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);
ClassDB::add_property("Terminal", PropertyInfo(Variant::BOOL, "copy_on_selection"), "set_copy_on_selection", "get_copy_on_selection"); ClassDB::add_property("Terminal", PropertyInfo(Variant::BOOL, "copy_on_selection"), "set_copy_on_selection", "get_copy_on_selection");
// Methods. // Other methods.
ClassDB::bind_method(D_METHOD("clear"), &Terminal::clear);
ClassDB::bind_method(D_METHOD("write", "data"), &Terminal::write); ClassDB::bind_method(D_METHOD("write", "data"), &Terminal::write);
ClassDB::bind_method(D_METHOD("get_cursor_pos"), &Terminal::get_cursor_pos); ClassDB::bind_method(D_METHOD("get_cursor_pos"), &Terminal::get_cursor_pos);
ClassDB::bind_method(D_METHOD("get_cell_size"), &Terminal::get_cell_size); ClassDB::bind_method(D_METHOD("get_cell_size"), &Terminal::get_cell_size);
@ -642,6 +642,19 @@ double Terminal::get_blink_off_time() const
return blink_off_time; return blink_off_time;
} }
void Terminal::clear() {
// Resize the terminal to a single row, forcing content above in to the scrollback buffer.
tsm_screen_resize(screen, cols, 1);
// Clear the scrollback buffer (hence clearing the content that was above).
tsm_screen_clear_sb(screen);
// Resize the screen to its original size.
tsm_screen_resize(screen, cols, rows);
refresh();
}
String Terminal::_copy_screen(ScreenCopyFunction func) { String Terminal::_copy_screen(ScreenCopyFunction func) {
char *out; char *out;
PackedByteArray data; PackedByteArray data;

View file

@ -72,6 +72,8 @@ 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;
void clear();
String copy_all(); 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);

View file

@ -12,7 +12,7 @@ func get_described_class():
func pick_cell_color(cell := Vector2i(0, 0)) -> Color: func pick_cell_color(cell := Vector2i(0, 0)) -> Color:
var cell_size = subject.get_cell_size() var cell_size = subject.get_cell_size()
var pixelv = Vector2(cell) * cell_size + (cell_size / 2) var pixelv = Vector2(cell) * cell_size + (cell_size / 2)
return get_viewport().get_texture().get_image().get_pixelv(cell_size / 2) return get_viewport().get_texture().get_image().get_pixelv(pixelv)
func before_each(): func before_each():
@ -27,9 +27,20 @@ func before_each():
class TestRendering: class TestRendering:
extends RenderingTest extends RenderingTest
# Fill the terminal with colored blocks.
func fill_color(color = Color.BLACK, rows: int = subject.get_rows()):
subject.write("\u001b[38;2;%d;%d;%dm" % [color.r8, color.g8, color.b8])
subject.write("".repeat(subject.get_cols() * rows))
func test_render():
fill_color(Color.BLUE)
await wait_for_signal(subject.draw, 3)
await wait_frames(15)
var cell_color = pick_cell_color()
assert_eq(cell_color, Color.BLUE)
func test_update(): func test_update():
subject.write("\u001b[38;2;255;0;0m") fill_color(Color.RED)
subject.write("".repeat(subject.get_cols() * subject.get_rows()))
await get_tree().physics_frame await get_tree().physics_frame
subject.queue_redraw() subject.queue_redraw()
await wait_for_signal(subject.draw, 3) await wait_for_signal(subject.draw, 3)
@ -37,6 +48,30 @@ class TestRendering:
var cell_color = pick_cell_color(Vector2i(0, 0)) var cell_color = pick_cell_color(Vector2i(0, 0))
assert_eq(cell_color, Color.RED) assert_eq(cell_color, Color.RED)
func test_clear_clears_all_but_the_first_row():
await wait_frames(15)
var cell = Vector2i(0, 1) # Pick a cell not on the first row.
var original_color = pick_cell_color(cell)
call_deferred("fill_color", Color.CYAN)
await wait_for_signal(subject.draw, 3)
await wait_frames(15)
subject.clear()
await wait_for_signal(subject.draw, 3)
await wait_frames(15)
var cell_color = pick_cell_color(cell)
assert_eq(cell_color, original_color)
func test_clear_keeps_the_last_row():
fill_color(Color.GREEN)
fill_color(Color.ORANGE, 1)
await wait_for_signal(subject.draw, 3)
await wait_frames(15)
subject.clear()
await wait_for_signal(subject.draw, 3)
await wait_frames(15)
var cell_color = pick_cell_color(Vector2i(0, 0))
assert_eq(cell_color, Color.ORANGE)
class TestKeyPressed: class TestKeyPressed:
extends RenderingTest extends RenderingTest

View file

@ -13,6 +13,13 @@ func before_each():
subject.size = Vector2(400, 200) subject.size = Vector2(400, 200)
# Helper function to fill the screen with the given character.
func fill_screen(char: String = "A") -> String:
var result = char.repeat(subject.get_cols() * subject.get_rows())
subject.write(result)
return result
class TestInterface: class TestInterface:
extends TerminalTest extends TerminalTest
@ -44,8 +51,7 @@ class TestInterface:
# Methods. # Methods.
# TODO: Implement clear() method. func test_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)
func test_has_method_copy_all(): func test_has_method_copy_all():
@ -196,11 +202,6 @@ class TestWrite:
class TestCopy: class TestCopy:
extends TerminalTest 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(): func test_copy_all_copies_the_entire_screen():
var text = fill_screen() var text = fill_screen()
# The text will be wrapped over multiple lines and copy_all() preserves # The text will be wrapped over multiple lines and copy_all() preserves
@ -220,3 +221,22 @@ class TestCopy:
var text = "アイウエオカキクケコサシスセソ" var text = "アイウエオカキクケコサシスセソ"
subject.write(text) subject.write(text)
assert_string_contains(subject.copy_all(), text) assert_string_contains(subject.copy_all(), text)
class TestClear:
extends TerminalTest
func test_clear_an_empty_screen_changes_nothing():
var empty_screen = subject.copy_all()
subject.clear()
var screen_after = subject.copy_all()
assert_eq(screen_after, empty_screen)
func test_clear_when_screen_is_full_clears_all_but_the_bottommost_row():
fill_screen()
var final_line = "THIS SHOULDN'T BE CLEARED"
subject.write(final_line)
subject.clear()
var screen_after = subject.copy_all()
var expected = final_line + "\n".repeat(subject.get_rows())
assert_eq(screen_after, expected)