godot-xterm/test/unit/buffer/test_buffer_line.gd
Leroy Hopson 0d4e10f5ab Add more features, bug fixes and bugs ;-)
Most notably:
- Reflow is now working. Terminal size will fill the window and
cols/rows will be resized/calculated based on window and font size.
- Added support for different fonts (i.e. bold, italic, bolditalic).
- Enabled blinking characters.
- Adde more tests and caught a few subtle bugs.
- Removed renderer code (which was part of xterm.js) and just
doing naive rendering in terminal.gd, but it seems to perform
a lot faster.

Still not working completely:
- vim (some weirdness going on).
- vttest (more weirdness).

Todo:
- Fix the above.
- Draw the cursor!
- Improve performance. Performance is still not great. The terminal
becomes unusable when running `yes` or `cmatrix -r`.
2020-05-19 18:55:43 +07:00

436 lines
15 KiB
GDScript
Raw Permalink Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

# Copyright (c) 2018 The xterm.js authors. All rights reserved.
# Ported to GDScript by the GodotXterm authors.
# License MIT
extends "res://addons/gut/test.gd"
const BufferLine = preload("res://addons/godot_xterm/buffer/buffer_line.gd")
const AttributeData = preload("res://addons/godot_xterm/buffer/attribute_data.gd")
const CellData = preload("res://addons/godot_xterm/buffer/cell_data.gd")
const Decoder = preload("res://addons/godot_xterm/input/text_decoder.gd")
const Constants = preload("res://addons/godot_xterm/buffer/constants.gd")
const BgFlags = Constants.BgFlags
const Attributes = Constants.Attributes
const UnderlineStyle = Constants.UnderlineStyle
const FgFlags = Constants.FgFlags
const Content = Constants.Content
class BufferLineTest:
extends BufferLine
func _init(cols: int, fill_cell_data = null, is_wrapped: bool = false).(cols, fill_cell_data, is_wrapped):
pass
func to_array():
var result = []
for i in range(length):
result.append(load_cell(i, CellData.new()).get_as_char_data())
return result
class TestAttributeData:
extends "res://addons/gut/test.gd"
var attrs
func before_each():
attrs = AttributeData.new()
func test_has_extended_attrs():
assert_eq(attrs.has_extended_attrs() as bool, false)
attrs.bg |= BgFlags.HAS_EXTENDED
assert_eq(attrs.has_extended_attrs() as bool, true)
func test_get_underline_color_P256():
# set a P256 color
attrs.extended.underline_color = Attributes.CM_P256 | 45
# should use FG color if BgFlags.HAS_EXTENDED is not set
assert_eq(attrs.get_underline_color(), -1)
# should use underline_color is BgFlags.HAS_EXTENDED is set and underline_color holds a value
attrs.bg |= BgFlags.HAS_EXTENDED
assert_eq(attrs.get_underline_color(), 45)
# should use FG color if underline_color holds no value
attrs.extended.underline_color = -1
attrs.fg |= Attributes.CM_P256 | 123
assert_eq(attrs.get_underline_color(), 123)
func test_get_underline_color_RGB():
# set a P256 color
attrs.extended.underline_color = Attributes.CM_RGB | (1 << 16) | (2 << 8) | 3
# should use FG color if BgFlags.HAS_EXTENDED is not set
assert_eq(attrs.get_underline_color(), -1)
# should use underline_color if BgFlags.HAS_EXTENDED is set and underline_color holds a value
attrs.bg |= BgFlags.HAS_EXTENDED
assert_eq(attrs.get_underline_color(), (1 << 16) | (2 << 8) | 3)
# should use FG color if underline_color holds no value
attrs.extended.underline_color = -1
attrs.fg |= Attributes.CM_P256 | 123
assert_eq(attrs.get_underline_color(), 123)
func test_underline_attrs():
# should always return color mode of fg
for mode in [Attributes.CM_DEFAULT, Attributes.CM_P16, Attributes.CM_P256, Attributes.CM_RGB]:
attrs.extended.underline_color = mode
assert_eq(attrs.get_underline_color_mode(), attrs.get_fg_color_mode())
assert_eq(attrs.is_underline_color_default(), true)
attrs.fg = Attributes.CM_RGB
for mode in [Attributes.CM_DEFAULT, Attributes.CM_P16, Attributes.CM_P256, Attributes.CM_RGB]:
attrs.extended.underline_color = mode
assert_eq(attrs.get_underline_color_mode(), attrs.get_fg_color_mode())
assert_eq(attrs.is_underline_color_default(), false)
assert_eq(attrs.is_underline_color_rgb(), true)
# should return own mode
attrs.bg |= BgFlags.HAS_EXTENDED
attrs.extended.underline_color = Attributes.CM_DEFAULT
assert_eq(attrs.get_underline_color_mode(), Attributes.CM_DEFAULT)
attrs.extended.underline_color = Attributes.CM_P16
assert_eq(attrs.get_underline_color_mode(), Attributes.CM_P16)
assert_eq(attrs.is_underline_color_palette(), true)
attrs.extended.underline_color = Attributes.CM_P256
assert_eq(attrs.get_underline_color_mode(), Attributes.CM_P256)
assert_eq(attrs.is_underline_color_palette(), true)
attrs.extended.underline_color = Attributes.CM_RGB
assert_eq(attrs.get_underline_color_mode(), Attributes.CM_RGB)
assert_eq(attrs.is_underline_color_rgb(), true)
func test_get_underline_style():
# defaults to no underline style
assert_eq(attrs.get_underline_style(), UnderlineStyle.NONE)
# should return NONE if UNDERLINE is not set
attrs.extended.underline_style = UnderlineStyle.CURLY
assert_eq(attrs.get_underline_style(), UnderlineStyle.NONE)
# should return SINGLE style if UNDERLINE is set and HAS_EXTENDED is false
attrs.fg |= FgFlags.UNDERLINE
assert_eq(attrs.get_underline_style(), UnderlineStyle.SINGLE)
# shoud return correct style if both is set
attrs.bg |= BgFlags.HAS_EXTENDED
assert_eq(attrs.get_underline_style(), UnderlineStyle.CURLY)
# should return NONE if UNDERLINE is not set, but HAS_EXTENDED is true
attrs.fg &= ~FgFlags.UNDERLINE
assert_eq(attrs.get_underline_style(), UnderlineStyle.NONE)
class TestCellData:
extends "res://addons/gut/test.gd"
var cell
var decoder = Decoder.Utf8ToUtf32.new()
func before_each():
cell = CellData.new()
func test_char_data_cell_data_equality():
# ASCII
cell.set_from_char_data([123, 'a', 1, 'a'.ord_at(0)])
assert_eq(cell.get_as_char_data(), [123, 'a', 1, 'a'.ord_at(0)])
assert_eq(cell.is_combined(), 0)
# combining
cell.set_from_char_data([123, 'e\u0301', 1, '\u0301'.ord_at(0)])
assert_eq(cell.get_as_char_data(), [123, 'e\u0301', 1, '\u0301'.ord_at(0)])
assert_eq(cell.is_combined(), Content.IS_COMBINED_MASK)
# surrogate
cell.set_from_char_data([123, '𝄞', 1, 0x1D11E])
assert_eq(cell.get_as_char_data(), [123, '𝄞', 1, 0x1D11E])
assert_eq(cell.is_combined(), 0)
# surrogate + combining
cell.set_from_char_data([123, '𓂀\u0301', 1, '𓂀\u0301'.ord_at(1)])
assert_eq(cell.get_as_char_data(), [123, '𓂀\u0301', 1, '𓂀\u0301'.ord_at(1)])
assert_eq(cell.is_combined(), Content.IS_COMBINED_MASK)
# wide char
cell.set_from_char_data([123, '', 2, ''.ord_at(0)])
assert_eq(cell.get_as_char_data(), [123, '', 2, ''.ord_at(0)])
assert_eq(cell.is_combined(), 0)
class TestBufferLine:
extends "res://addons/gut/test.gd"
func test_ctor():
var line = BufferLineTest.new(0)
assert_eq(line.length, 0)
assert_eq(line.is_wrapped, false)
line = BufferLineTest.new(10)
assert_eq(line.length, 10)
assert_eq(line.load_cell(0, CellData.new()).get_as_char_data(),
[0, Constants.NULL_CELL_CHAR, Constants.NULL_CELL_WIDTH,
Constants.NULL_CELL_CODE])
assert_eq(line.is_wrapped, false)
line = BufferLineTest.new(10, null, true)
assert_eq(line.length, 10)
assert_eq(line.load_cell(0, CellData.new()).get_as_char_data(),
[0, Constants.NULL_CELL_CHAR, Constants.NULL_CELL_WIDTH,
Constants.NULL_CELL_CODE])
assert_eq(line.is_wrapped, true)
var char_data = [123, 'a', 456, 'a'.ord_at(0)]
line = BufferLineTest.new(10, CellData.from_char_data(char_data), true)
assert_eq(line.length, 10)
assert_eq(line.load_cell(0, CellData.new()).get_as_char_data(), char_data)
assert_eq(line.is_wrapped, true)
func test_insert_cells() -> void:
var line = BufferLineTest.new(3)
line.set_cell(0, CellData.from_char_data([1, 'a', 0, 'a'.ord_at(0)]))
line.set_cell(1, CellData.from_char_data([2, 'b', 0, 'b'.ord_at(0)]))
line.set_cell(2, CellData.from_char_data([3, 'c', 0, 'c'.ord_at(0)]))
line.insert_cells(1, 3, CellData.from_char_data([4, 'd', 0, 'd'.ord_at(0)]))
assert_eq(line.to_array(), [
[1, 'a', 0, 'a'.ord_at(0)],
[4, 'd', 0, 'd'.ord_at(0)],
[4, 'd', 0, 'd'.ord_at(0)]
])
func test_delete_cells() -> void:
var line = BufferLineTest.new(5)
line.set_cell(0, CellData.from_char_data([1, 'a', 0, 'a'.ord_at(0)]))
line.set_cell(1, CellData.from_char_data([2, 'b', 0, 'b'.ord_at(0)]))
line.set_cell(2, CellData.from_char_data([3, 'c', 0, 'c'.ord_at(0)]))
line.set_cell(3, CellData.from_char_data([4, 'd', 0, 'd'.ord_at(0)]))
line.set_cell(4, CellData.from_char_data([5, 'e', 0, 'e'.ord_at(0)]))
line.delete_cells(1, 2, CellData.from_char_data([6, 'f', 0, 'f'.ord_at(0)]))
assert_eq(line.to_array(), [
[1, 'a', 0, 'a'.ord_at(0)],
[4, 'd', 0, 'd'.ord_at(0)],
[5, 'e', 0, 'e'.ord_at(0)],
[6, 'f', 0, 'f'.ord_at(0)],
[6, 'f', 0, 'f'.ord_at(0)]
])
func test_replace_cells():
var line = BufferLineTest.new(5)
line.set_cell(0, CellData.from_char_data([1, 'a', 0, 'a'.ord_at(0)]))
line.set_cell(1, CellData.from_char_data([2, 'b', 0, 'b'.ord_at(0)]))
line.set_cell(2, CellData.from_char_data([3, 'c', 0, 'c'.ord_at(0)]))
line.set_cell(3, CellData.from_char_data([4, 'd', 0, 'd'.ord_at(0)]))
line.set_cell(4, CellData.from_char_data([5, 'e', 0, 'e'.ord_at(0)]))
line.replace_cells(2, 4, CellData.from_char_data([6, 'f', 0, 'f'.ord_at(0)]))
assert_eq(line.to_array(), [
[1, 'a', 0, 'a'.ord_at(0)],
[2, 'b', 0, 'b'.ord_at(0)],
[6, 'f', 0, 'f'.ord_at(0)],
[6, 'f', 0, 'f'.ord_at(0)],
[5, 'e', 0, 'e'.ord_at(0)],
])
func test_copy_from():
var line = BufferLineTest.new(5)
line.set_cell(0, CellData.from_char_data([1, 'a', 0, 'a'.ord_at(0)]))
line.set_cell(0, CellData.from_char_data([2, 'b', 0, 'b'.ord_at(0)]))
line.set_cell(0, CellData.from_char_data([3, 'c', 0, 'c'.ord_at(0)]))
line.set_cell(0, CellData.from_char_data([4, 'd', 0, 'd'.ord_at(0)]))
line.set_cell(0, CellData.from_char_data([5, 'e', 0, 'e'.ord_at(0)]))
var line2 = BufferLineTest.new(5, CellData.from_char_data([1, 'a', 0, 'a'.ord_at(0)]), true)
line2.copy_from(line)
assert_eq(line2.to_array(), line.to_array())
assert_eq(line2.length, line.length)
assert_eq(line2.is_wrapped, line.is_wrapped)
class TestResize:
extends "res://addons/gut/test.gd"
var CHAR_DATA = [1, 'a', 0, 'a'.ord_at(0)]
var line
func repeat(el, times: int) -> Array:
var result = []
result.resize(times)
for i in range(times):
result[i] = el
return result
func test_enlarge():
line = BufferLineTest.new(5, CellData.from_char_data(CHAR_DATA), false)
line.resize(10, CellData.from_char_data(CHAR_DATA))
assert_eq(line.to_array(), repeat(CHAR_DATA, 10))
func test_shrink():
line = BufferLineTest.new(10, CellData.from_char_data(CHAR_DATA), false)
line.resize(5, CellData.from_char_data(CHAR_DATA))
assert_eq(line.to_array(), repeat(CHAR_DATA, 5))
func test_shrink_to_0_length():
line = BufferLineTest.new(10, CellData.from_char_data(CHAR_DATA), false)
line.resize(0, CellData.from_char_data(CHAR_DATA))
assert_eq(line.to_array(), repeat(CHAR_DATA, 0))
func shrink_then_enlarge():
line = BufferLineTest.new(10, CellData.from_char_data(CHAR_DATA), false);
line.set_cell(2, CellData.from_char_data([0, '😁', 1, '😁'.ord_at(0)]))
line.set_cell(9, CellData.from_char_data([0, '😁', 1, '😁'.ord_at(0)]))
assert_eq(line.translate_to_string(), 'aa😁aaaaaa😁')
line.resize(5, CellData.from_char_data(CHAR_DATA))
assert_eq(line.translate_to_string(), 'aa😁aa')
line.resize(10, CellData.from_char_data(CHAR_DATA))
assert_eq(line.translate_to_string(), 'aa😁aaaaaaa')
class TestTrimLength:
extends "res://addons/gut/test.gd"
var line
func before_each():
line = BufferLineTest.new(3, CellData.from_char_data([Constants.DEFAULT_ATTR,
Constants.NULL_CELL_CHAR, Constants.NULL_CELL_WIDTH, Constants.NULL_CELL_CODE]))
func test_empty_line():
assert_eq(line.get_trimmed_length(), 0)
func test_ascii():
line.set_cell(0, CellData.from_char_data([1, "a", 1, "a".ord_at(0)]))
line.set_cell(2, CellData.from_char_data([1, "a", 1, "a".ord_at(0)]))
assert_eq(line.get_trimmed_length(), 3)
func test_unicode():
line.set_cell(0, CellData.from_char_data([1, "\u1f914", 1, "\u1f914".ord_at(0)]))
line.set_cell(2, CellData.from_char_data([1, "\u1f914", 1, "\u1f914".ord_at(0)]))
assert_eq(line.get_trimmed_length(), 3)
func test_one_cell():
line.set_cell(0, CellData.from_char_data([1, "a", 1, "a".ord_at(0)]))
assert_eq(line.get_trimmed_length(), 1)
class TestAddCharToCell:
extends "res://addons/gut/test.gd"
var line
var cell
func before_each():
line = BufferLineTest.new(3, CellData.from_char_data([Constants.DEFAULT_ATTR,
Constants.NULL_CELL_CHAR, Constants.NULL_CELL_WIDTH, Constants.NULL_CELL_CODE]))
cell = line.load_cell(0, CellData.new())
func test_sets_width_to_1_for_empty_cell():
line.add_codepoint_to_cell(0, "\u0301".ord_at(0))
cell = line.load_cell(0, CellData.new())
# chars contains single combining char
# width is set to 1
assert_eq(cell.get_as_char_data(), [Constants.DEFAULT_ATTR, '\u0301', 1, 0x0301])
# do not account a single combining char as combined
assert_eq(cell.is_combined(), 0)
func test_add_char_to_combining_string_in_cell():
cell.set_from_char_data([123, "e\u0301", 1, "e\u0301".ord_at(1)])
line.set_cell(0, cell)
line.add_codepoint_to_cell(0, "\u0301".ord_at(0))
line.load_cell(0, cell)
# char contains 3 chars
# width is set to 1
assert_eq(cell.get_as_char_data(), [123, "e\u0301\u0301", 1, 0x0301])
# do not account a single combining char as combined
assert_eq(cell.is_combined(), Content.IS_COMBINED_MASK)
func test_create_combining_string_on_taken_cell():
cell.set_from_char_data([123, "e", 1, "e".ord_at(1)])
line.set_cell(0, cell)
line.add_codepoint_to_cell(0, "\u0301".ord_at(0))
line.load_cell(0, cell)
# chars contains 2 chars
# width is set to 1
assert_eq(cell.get_as_char_data(), [123, "e\u0301", 1, 0x0301])
# do not account a single combining char as combined
assert_eq(cell.is_combined(), Content.IS_COMBINED_MASK)
class TestTranslateToString:
extends "res://addons/gut/test.gd"
var line
func before_each():
line = BufferLineTest.new(10, CellData.from_char_data([Constants.DEFAULT_ATTR,
Constants.NULL_CELL_CHAR, Constants.NULL_CELL_WIDTH, Constants.NULL_CELL_CODE]), false)
func test_empty_line():
assert_eq(line.translate_to_string(false), ' ')
assert_eq(line.translate_to_string(true), '')
func test_ASCII():
line.set_cell(0, CellData.from_char_data([1, 'a', 1, 'a'.ord_at(0)]))
line.set_cell(2, CellData.from_char_data([1, 'a', 1, 'a'.ord_at(0)]))
line.set_cell(4, CellData.from_char_data([1, 'a', 1, 'a'.ord_at(0)]))
line.set_cell(5, CellData.from_char_data([1, 'a', 1, 'a'.ord_at(0)]))
assert_eq(line.translate_to_string(false), 'a a aa ')
assert_eq(line.translate_to_string(true), 'a a aa')
assert_eq(line.translate_to_string(false, 0, 5), 'a a a')
assert_eq(line.translate_to_string(false, 0, 4), 'a a ')
assert_eq(line.translate_to_string(false, 0, 3), 'a a')
assert_eq(line.translate_to_string(true, 0, 5), 'a a a')
assert_eq(line.translate_to_string(true, 0, 4), 'a a ')
assert_eq(line.translate_to_string(true, 0, 3), 'a a')
func test_space_at_end():
line.set_cell(0, CellData.from_char_data([1, 'a', 1, 'a'.ord_at(0)]))
line.set_cell(2, CellData.from_char_data([1, 'a', 1, 'a'.ord_at(0)]))
line.set_cell(4, CellData.from_char_data([1, 'a', 1, 'a'.ord_at(0)]))
line.set_cell(5, CellData.from_char_data([1, 'a', 1, 'a'.ord_at(0)]))
line.set_cell(6, CellData.from_char_data([1, ' ', 1, ' '.ord_at(0)]))
assert_eq(line.translate_to_string(false), 'a a aa ')
assert_eq(line.translate_to_string(true), 'a a aa ')
func test_always_returns_some_sane_value():
# sanity check - broken line with invalid out of bound null width cells
# this can atm happen with deleting/inserting chars in inputhandler by "breaking"
# fullwidth pairs --> needs to be fixed after settling BufferLine impl
assert_eq(line.translate_to_string(false), ' ')
assert_eq(line.translate_to_string(true), '')
func test_works_with_end_col_0():
line.set_cell(0, CellData.from_char_data([1, 'a', 1, 'a'.ord_at(0)]))
assert_eq(line.translate_to_string(true, 0, 0), '')