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`.
This commit is contained in:
Leroy Hopson 2020-05-19 18:45:18 +07:00
parent 0769592a1b
commit 0d4e10f5ab
30 changed files with 2640 additions and 1157 deletions

View file

@ -15,6 +15,12 @@ var bg = 0
var extended = ExtendedAttrs.new()
static func to_color_rgb(value: int) -> Color:
# Create color from RGB format.
return Color("%02x%02x%02x" % [value >> Attributes.RED_SHIFT & 255,
value >> Attributes.GREEN_SHIFT & 255, value & 255])
# flags
func is_inverse() -> int:
return fg & FgFlags.INVERSE
@ -27,7 +33,7 @@ func is_blink() -> int:
func is_invisible() -> int:
return fg & FgFlags.INVISIBLE
func is_italic() -> int:
return fg & BgFlags.ITALIC
return bg & BgFlags.ITALIC
func is_dim() -> int:
return fg & BgFlags.DIM
@ -60,13 +66,30 @@ func get_fg_color() -> int:
Attributes.CM_RGB:
return fg & Attributes.RGB_MASK
_:
return -1
return -1 # CM_DEFAULT defaults to -1
func get_bg_color() -> int:
match bg & Attributes.CM_MASK:
Attributes.CM_P16, Attributes.CM_P256:
return bg & Attributes.PCOLOR_MASK
Attributes.CM_RGB:
return bg & Attributes.RGB_MASK
_:
return -1 # CM_DEFAULT defaults to -1
func has_extended_attrs() -> int:
return bg & BgFlags.HAS_EXTENDED
func update_extended() -> void:
if extended.is_empty():
bg &= ~BgFlags.HAS_EXTENDED
else:
bg |= BgFlags.HAS_EXTENDED
func get_underline_color() -> int:
if bg & BgFlags.HAS_EXTENDED and ~extended.underline_color:
match extended.underline_color & Attributes.CM_MASK:
@ -117,6 +140,9 @@ func get_underline_style():
class ExtendedAttrs:
extends Reference
# Extended attributes for a cell.
# Holds information about different underline styles and color.
var underline_style = UnderlineStyle.NONE
@ -125,3 +151,18 @@ class ExtendedAttrs:
func _init():
underline_style
func duplicate():
# Workaround as per: https://github.com/godotengine/godot/issues/19345#issuecomment-471218401
var AttributeData = load("res://addons/godot_xterm/buffer/attribute_data.gd")
var clone = AttributeData.ExtendedAttrs.new()
clone.underline_style = underline_style
clone.underline_color = underline_color
return clone
# Convenient method to indicate whether the object holds no additional information,
# that needs to be persistant in the buffer.
func is_empty():
return underline_style == UnderlineStyle.NONE

View file

@ -10,7 +10,7 @@ const Charsets = preload("res://addons/godot_xterm/data/charsets.gd")
const Constants = preload("res://addons/godot_xterm/buffer/constants.gd")
const CircularList = preload("res://addons/godot_xterm/circular_list.gd")
const AttributeData = preload("res://addons/godot_xterm/buffer/attribute_data.gd")
const BufferReflow = preload("res://addons/godot_xterm/buffer/buffer_reflow.gd")
const MAX_BUFFER_SIZE = 4294967295 # 2^32 - 1
@ -51,6 +51,286 @@ func _init(has_scrollback: bool, options_service, buffer_service):
setup_tab_stops()
# Resizes the buffer, adjusting its data accordingly.
# @param new_cols The new number of columns.
# @param new_rows The new number of rows.
func resize(new_cols: int, new_rows: int) -> void:
# store reference to null cell with default attrs
var null_cell = get_null_cell(AttributeData.new())
# Increase max length if needed before adjustments to allow space to fill
# as required.
var new_max_length = _get_correct_buffer_length(new_rows)
if new_max_length > lines.max_length:
lines.max_length = new_max_length
# The following adjustments should only happen if the buffer has been
# initialized/filled.
if lines.length > 0:
# Deal with columns increasing (reducing needs to happen after reflow)
if _cols < new_cols:
for i in range(lines.length):
lines.get_line(i).resize(new_cols, null_cell)
# Resize rows in both directions as needed
var add_to_y = 0
if _rows < new_rows:
for y in range(_rows, new_rows):
if lines.length < new_rows + ybase:
if _options_service.options.windows_mode:
# Just add the new missing rows on Windows as conpty reprints the screen with it's
# view of the world. Once a line enters scrollback for conpty it remains there
lines.push(BufferLine.new(new_cols, null_cell))
else:
if ybase > 0 and lines.length <= ybase + y + add_to_y + 1:
# There is room above the buffer and there are no empty elements below the line,
# scroll up
ybase -= 1
add_to_y += 1
if ydisp > 0:
# Viewport is at the top of the buffer, must increase downwards
ydisp -= 1
else:
# Add a blank line if tere is no buffer left at the top to srcoll to, or if there
# are blank lines after the cursor
lines.push(BufferLine.new(new_cols, null_cell))
else: # _rows >= new_rows
for _y in range(_rows, new_rows, -1):
if lines.length > new_rows + ybase:
if lines.length > ybase + y + 1:
# The line is blank line below the cursor, remove it
lines.pop()
else:
# The line is the cursor, scroll down
ybase += 1
ydisp += 1
# Reduce max length if needed after adjustments, this is done after as it
# would otherwise cut data from the bottom of the buffer.
if new_max_length < lines.max_length:
# Trim from the top of th ebuffer and adjust ybase and ydisp.
var amount_to_trim = lines.length - new_max_length
if amount_to_trim > 0:
lines.trim_start(amount_to_trim)
ybase = max(ybase - amount_to_trim, 0)
ydisp = max(ydisp - amount_to_trim, 0)
saved_y = max(saved_y - amount_to_trim, 0)
lines.max_length = new_max_length
# Make sure that the cursor stays on screen
x = min(x, new_cols - 1)
y = min(y, new_rows - 1)
if add_to_y:
y += add_to_y
saved_x = min(saved_x, new_cols -1)
scroll_top = 0
scroll_bottom = new_rows - 1
if _is_reflow_enabled():
_reflow(new_cols, new_rows)
# Trim the end of the line off if cols shrunk
if _cols > new_cols:
for i in range(lines.length):
lines.get_line(i).resize(new_cols, null_cell)
_cols = new_cols
_rows = new_rows
func _is_reflow_enabled() -> bool:
return _has_scrollback and not _options_service.options.windows_mode
func _reflow(new_cols: int, new_rows: int) -> void:
if _cols == new_cols:
return
# Iterate through rows, ignore the last one as it cannot be wrapped
if new_cols > _cols:
_reflow_larger(new_cols, new_rows)
else:
_reflow_smaller(new_cols, new_rows)
func _reflow_larger(new_cols: int, new_rows: int) -> void:
var to_remove: PoolIntArray = BufferReflow.reflow_larger_get_lines_to_remove(lines,
_cols, new_cols, ybase + y, get_null_cell(AttributeData.new()))
if not to_remove.empty():
var new_layout_result = BufferReflow.reflow_larger_create_new_layout(lines, to_remove)
BufferReflow.reflow_larger_apply_new_layout(lines, new_layout_result.layout)
_reflow_larger_adjust_viewport(new_cols, new_rows, new_layout_result.count_removed)
func _reflow_larger_adjust_viewport(new_cols: int, new_rows: int, count_removed: int) -> void:
var null_cell = get_null_cell(AttributeData.new())
# Adjust viewport based on number of items removed
var viewport_adjustments = count_removed
while viewport_adjustments > 0:
viewport_adjustments -= 1
if ybase == 0:
if y > 0:
y -= 1
if lines.length < new_rows:
# Add an extra row at the bottom of the viewport
lines.push(BufferLine.new(new_cols, null_cell))
else:
if ydisp == ybase:
ydisp -= 1
ybase -= 1
saved_y = max(saved_y - count_removed, 0)
func _reflow_smaller(new_cols: int, new_rows: int) -> void:
var null_cell = get_null_cell(AttributeData.new())
# Gather all BufferLines that need to be inserted into the Buffer here so that they can be
# batched up and only commited once
var to_insert = []
var count_to_insert = 0
# Go backwards as many lines may be trimmed and this will avoid considering them
var i = lines.length - 1
while i >= 0:
# Check wether this line is a problem
var next_line = lines.get_line(i)
if not next_line or not next_line.is_wrapped and next_line.get_trimmed_length() <= new_cols:
i -= 1
continue
# Gather wrapped lines and adjust y to be the starting line
var wrapped_lines = [next_line]
while next_line.is_wrapped and i > 0:
i -= 1
next_line = lines.get_line(i)
wrapped_lines.push_front(next_line)
# If these lines contain the cursor don't touch them, the program will handle fixing up
# wrapped lines with the cursor
var absolute_y = ybase + y
if absolute_y >= i and absolute_y < i + wrapped_lines.size():
i -= 1
continue
var last_line_length = wrapped_lines[wrapped_lines.size() - 1].get_trimmed_length()
var dest_line_lengths = BufferReflow.reflow_smaller_get_new_line_lengths(wrapped_lines, _cols, new_cols)
var lines_to_add = dest_line_lengths.size() - wrapped_lines.size()
var trimmed_lines: int
if ybase == 0 and y != lines.length - 1:
# If the top section of the buffer is not yet filled
trimmed_lines = max(0, y - lines.max_length + lines_to_add)
else:
trimmed_lines = max(0, lines.length - lines.max_length + lines_to_add)
# Add the new lines
var new_lines = []
for j in range(lines_to_add):
var new_line = get_blank_line(AttributeData.new(), true)
new_lines.append(new_line)
if not new_lines.empty():
to_insert.append({"start": i + wrapped_lines.size() + count_to_insert,
"new_lines": new_lines})
count_to_insert += new_lines.size()
wrapped_lines += new_lines
# Copy buffer data to new locations, this needs to happen backwards to do in-place
var dest_line_index = dest_line_lengths.size() - 1 # floor(cells_needed / new_cols)
var dest_col = dest_line_lengths[dest_line_index] # cells_needed % new_cols
if dest_col == 0:
dest_line_index -= 1
dest_col = dest_line_lengths[dest_line_index]
var src_line_index = wrapped_lines.size() - lines_to_add - 1
var src_col = last_line_length
while src_line_index >= 0:
var cells_to_copy = min(src_col, dest_col)
wrapped_lines[dest_line_index].copy_cells_from(wrapped_lines[src_line_index],
src_col - cells_to_copy, dest_col - cells_to_copy, cells_to_copy, true)
dest_col -= cells_to_copy
if dest_col == 0:
dest_line_index -= 1
dest_col = dest_line_lengths[dest_line_index]
src_col -= cells_to_copy
if src_col == 0:
src_line_index -= 1
var wrapped_lines_index = max(src_line_index, 0)
src_col = BufferReflow.get_wrapped_line_trimmed_length(wrapped_lines, wrapped_lines_index, _cols)
# Null out the end of the line ends if a wide character wrapped to the following line
for j in range(wrapped_lines.size()):
if dest_line_lengths[j] < new_cols:
wrapped_lines[j].set_cell(dest_line_lengths[j], null_cell)
# Adjust viewport as needed
var viewport_adjustments = lines_to_add - trimmed_lines
while viewport_adjustments > 0:
if ybase == 0:
if y < new_rows - 1:
y += 1
lines.pop()
else:
ybase += 1
ydisp += 1
else:
# Ensure ybase does not exceed its maximum value
if ybase < min(lines.max_length, (lines.length + count_to_insert) - new_rows):
if ybase == ydisp:
ydisp += 1
ybase += 1
viewport_adjustments -= 1
saved_y = min(saved_y + lines_to_add, ybase + new_rows - 1)
i -= 1
# Rearrange lines in the buffer if there are any insertions, this is done at the end rather
# than earlier so that it's a single O(n) pass through the buffer, instead of O(n^2) from many
# costly calls to CircularList.splice.
if not to_insert.empty():
# Record buffer insert events and then play them backwards so that the indexes are
# correct
var insert_events = []
# Record original lines so they don't get overridden when we rearrange the list
var original_lines = []
for i in range(lines.length):
original_lines.append(lines.get_line(i))
var original_lines_length = lines.length
var original_line_index = original_lines_length - 1
var next_to_insert_index = 0
var next_to_insert = to_insert[next_to_insert_index]
lines.length = min(lines.max_length, lines.length + count_to_insert)
var count_inserted_so_far = 0
var j = min(lines.max_length - 1, original_lines_length + count_to_insert - 1)
while j >= 0:
if next_to_insert and next_to_insert.start > original_line_index + count_inserted_so_far:
# Insert extra lines here, adjusting i as needed
for next_i in range(next_to_insert.new_lines.size() - 1, -1, -1):
lines.set_line(j, next_to_insert.new_lines[next_i])
j -= 1
j += 1
# Create insert events for later
insert_events.append({"index": original_line_index + 1,
"amount": next_to_insert.new_lines.size()})
count_inserted_so_far += next_to_insert.new_lines.size()
next_to_insert_index += 1
next_to_insert = to_insert[next_to_insert_index] if to_insert.size() > next_to_insert_index else null
else:
lines.set_line(j, original_lines[original_line_index])
original_line_index -= 1
j -= 1
# Update markers
var insert_count_emitted = 0
for i in range(insert_events.size() - 1, -1, -1):
insert_events[i].index += insert_count_emitted
lines.emit_signal("inserted", insert_events[i])
insert_count_emitted += insert_events[i].amount
var amount_to_trim = max(0, original_lines_length + count_to_insert - lines.max_length)
if amount_to_trim > 0:
lines.emit_signal("trimmed", amount_to_trim)
func get_null_cell(attr = null):
if attr:
_null_cell.fg = attr.fg
@ -111,21 +391,23 @@ func get_wrapped_range_for_line(y: int) -> Dictionary:
return {"first": first, "last": last}
# Setup the tab stops.
# @param i The index to start setting up tab stops from.
func setup_tab_stops(i = null) -> void:
if i == null:
return
if not tabs.get(i):
i = prev_stop(i)
if i != null:
if not tabs.get(i):
i = prev_stop(i)
else:
tabs = {}
i = 0
while i < _cols:
tabs[i] = true
i += _options_service.options.tab_stop_width
i += max(_options_service.options.tab_stop_width, 1)
# Move the cursor to the previous tab stop from the given position (default is current).
# @param x The position to move the cursor to the previous tab stop.
func prev_stop(x: int) -> int:
if x == null:
x = self.x
@ -135,7 +417,14 @@ func prev_stop(x: int) -> int:
return _cols - 1 if x > _cols else 0 if x < 0 else x
# Move the cursor one tab stop forward from the given position (default is current).
# @param x The position to move the cursor one tab stop forward.
func next_stop(x = null) -> int:
if x == null:
x = self.x
x += 1
while not tabs.get(x) and x < _cols:
x += 1
return _cols - 1 if x >= _cols else 0 if x < 0 else x

View file

@ -41,7 +41,10 @@ func get_cell(index: int):
func get_width(index: int) -> int:
return _data[index * CELL_SIZE + Cell.CONTENT] >> Content.WIDTH_SHIFT
if (index * CELL_SIZE + Cell.CONTENT) < _data.size():
return _data[index * CELL_SIZE + Cell.CONTENT] >> Content.WIDTH_SHIFT
else:
return 0
func has_content(index: int) -> int:
@ -221,13 +224,43 @@ func fill(fill_cell_data) -> void:
set_cell(i, fill_cell_data)
# alter to a full copy of line
func copy_from(line) -> void:
_data = line._data.duplicate()
length = line.length
_combined = {}
for k in line._combined.keys():
_combined[k] = line._combined[k]
is_wrapped = line.is_wrapped
func get_trimmed_length() -> int:
for i in range(length - 1, 0, -1):
for i in range(length - 1, -1, -1):
if _data[i * CELL_SIZE + Cell.CONTENT] & Content.HAS_CONTENT_MASK:
return i + (_data[i * CELL_SIZE + Cell.CONTENT] >> Content.WIDTH_SHIFT)
return 0
func copy_cells_from(src, src_col: int, dest_col: int, length: int, apply_in_reverse: bool) -> void:
var src_data = src._data
if apply_in_reverse:
for cell in range(length - 1, -1, -1):
for i in range(CELL_SIZE):
_data[(dest_col + cell) * CELL_SIZE + i] = src_data[(src_col + cell) * CELL_SIZE + i]
else:
for cell in range(length):
for i in range(CELL_SIZE):
_data[(dest_col + cell) * CELL_SIZE + i] = src_data[(src_col + cell) * CELL_SIZE + i]
# Move any combined data over as needed, FIXME: repeat for extended attrs
var src_combined_keys = src._combined.keys()
for i in range(src_combined_keys.size()):
var key = int(src_combined_keys[i])
if key >= src_col:
_combined[key + src_col + dest_col] = src._combined[key]
func translate_to_string(trim_right: bool = false, start_col: int = 0, end_col: int = -1) -> String:
if end_col == -1:
end_col = length
@ -245,3 +278,14 @@ func translate_to_string(trim_right: bool = false, start_col: int = 0, end_col:
result += Constants.WHITESPACE_CELL_CHAR
start_col += max(content >> Content.WIDTH_SHIFT, 1) # always advance by 1
return result
func duplicate():
# Workaround as per: https://github.com/godotengine/godot/issues/19345#issuecomment-471218401
var duplicant = load("res://addons/godot_xterm/buffer/buffer_line.gd").new(length)
duplicant._data = _data.duplicate(true)
duplicant._combined = _combined.duplicate(true)
duplicant._extended_attrs = _extended_attrs.duplicate(true)
duplicant.length = length
duplicant.is_wrapped = is_wrapped
return duplicant

View file

@ -0,0 +1,198 @@
# Copyright (c) 2019 The xterm.js authors. All rights reserved.
# Ported to GDScript by the GodotXterm authors.
# License MIT
extends Object
# Evaluates and returns indexes to be removed after a reflow larger occurs. Lines will be removed
# when a wrapped line unwraps.
# @param lines The buffer lines.
# @param newCols The columns after resize.
static func reflow_larger_get_lines_to_remove(lines, old_cols: int, new_cols: int,
buffer_absolute_y: int, null_cell) -> PoolIntArray:
# Gather all BufferLines that need to be removed from the Buffer here so that they can be
# batched up and only committed once
var to_remove = PoolIntArray([])
var y = 0
while y < lines.length - 1:
# Check if this row is wrapped
var i = y
i += 1
var next_line = lines.get_line(i)
if not next_line.is_wrapped:
y += 1
continue
# Check how many lines it's wrapped for
var wrapped_lines = [lines.get_line(y)]
while i < lines.length and next_line.is_wrapped:
wrapped_lines.append(next_line)
i += 1
next_line = lines.get_line(i)
# If these lines contain the cursor don't touch them, the program will handle fixing up wrapped
# lines with the cursor
if buffer_absolute_y >= y and buffer_absolute_y < i:
y += wrapped_lines.size() - 1
y += 1
continue
# Copy buffer data to new locations
var dest_line_index = 0
var dest_col = get_wrapped_line_trimmed_length(wrapped_lines, dest_line_index, old_cols)
var src_line_index = 1
var src_col = 0
while src_line_index < wrapped_lines.size():
var src_trimmed_line_length = get_wrapped_line_trimmed_length(wrapped_lines, src_line_index, old_cols)
var src_remaining_cells = src_trimmed_line_length - src_col
var dest_remaining_cells = new_cols - dest_col
var cells_to_copy = min(src_remaining_cells, dest_remaining_cells)
wrapped_lines[dest_line_index].copy_cells_from(wrapped_lines[src_line_index], src_col, dest_col, cells_to_copy, false)
dest_col += cells_to_copy
if dest_col == new_cols:
dest_line_index += 1
dest_col = 0
src_col += cells_to_copy
if src_col == src_trimmed_line_length:
src_line_index += 1
src_col = 0
# Make sure the last cell isn't wide, if it is copy it to the current dest
if dest_col == 0 and dest_line_index != 0:
if wrapped_lines[dest_line_index - 1].get_width(new_cols - 1) == 2:
wrapped_lines[dest_line_index].copy_cells_from(wrapped_lines[dest_line_index - 1], new_cols - 1, dest_col, 1, false)
dest_col += 1
# Null out the end of the last row
wrapped_lines[dest_line_index - 1].set_cell(new_cols - 1, null_cell)
# Clear out remaining cells or fragments could remain;
var replaced = wrapped_lines[dest_line_index].translate_to_string()
wrapped_lines[dest_line_index].replace_cells(dest_col, new_cols, null_cell)
# Work backwards and remove any rows at the end that only contain null cells
var count_to_remove = 0
for i in range(wrapped_lines.size() - 1, 0, -1):
if i > dest_line_index or wrapped_lines[i].get_trimmed_length() == 0:
count_to_remove += 1
else:
break
if count_to_remove > 0:
to_remove.append(y + wrapped_lines.size() - count_to_remove) # index
to_remove.append(count_to_remove)
y += wrapped_lines.size() - 1
y += 1
return to_remove
# Creates and return the new layout for lines given an array of indexes to be removed.
# @param lines The buffer lines.
# @param to_remove The indexes to remove.
static func reflow_larger_create_new_layout(lines, to_remove: PoolIntArray):
var layout = PoolIntArray([])
# First iterate through the list and get the actual indexes to use for rows
var next_to_remove_index = 0
var next_to_remove_start = to_remove[next_to_remove_index]
var count_removed_so_far = 0
var i = 0
while i < lines.length:
if next_to_remove_start == i:
next_to_remove_index += 1
var count_to_remove = to_remove[next_to_remove_index]
# Tell markers that there was a deletion
lines.emit_signal("deleted", i - count_removed_so_far, count_to_remove)
i += count_to_remove - 1
count_removed_so_far += count_to_remove
next_to_remove_index += 1
next_to_remove_start = to_remove[next_to_remove_index] if next_to_remove_index < to_remove.size() else null
else:
layout.append(i)
i += 1
return { "layout": layout, "count_removed": count_removed_so_far }
# Applies a new layout to the buffer. This essentially does the same as many splice calls but it's
# done all at once in a single iteration through the list since splice is very expensive.
# @param lines The buffer lines.
# @param new_layout The new layout to apply.
static func reflow_larger_apply_new_layout(lines, new_layout: PoolIntArray) -> void:
# Record original lines so they don't get overridden when we rearrange the list
var new_layout_lines = []
for i in range(new_layout.size()):
new_layout_lines.append(lines.get_line(new_layout[i]))
# Rearrange the list
for i in range(new_layout_lines.size()):
lines.set_line(i, new_layout_lines[i])
lines.length = new_layout.size()
# Gets the new line lengths for a given wrapped line. The purpose of this function it to pre-
# compute the wrapping points since wide characters may need to be wrapped onto the following line.
# This function will return an array of numbers of where each line wraps to, the resulting array
# will only contain the values `newCols` (when the line does not end with a wide character) and
# `new_cols - 1` (when the line does end with a wide character), except for the last value which
# will contain the remaining items to fill the line.
#
# Calling this with a `new_cols` value of `1` will lock up.
#
# @param wrapped_lines The wrapped lines to evaluate.
# @param old_cols The columns before resize.
# @param new_cols The columns after resize.
static func reflow_smaller_get_new_line_lengths(wrapped_lines: Array, old_cols: int, new_cols: int) -> PoolIntArray:
var new_line_lengths = PoolIntArray([])
var cells_needed: int
for i in range(wrapped_lines.size()):
cells_needed += get_wrapped_line_trimmed_length(wrapped_lines, i, old_cols)
# Use src_col and scr_line to find the new wrapping point, use that to get the cells_available and
# lines_needed
var src_col = 0
var src_line = 0
var cells_available = 0
while cells_available < cells_needed:
if cells_needed - cells_available < new_cols:
# Add the final line and exit the loop
new_line_lengths.append(cells_needed - cells_available)
break
src_col += new_cols
var old_trimmed_length = get_wrapped_line_trimmed_length(wrapped_lines, src_line, old_cols)
if src_col > old_trimmed_length:
src_col -= old_trimmed_length
src_line += 1
var ends_with_wide = wrapped_lines[src_line].get_width(src_col - 1) == 2
if ends_with_wide:
src_col -= 1
var line_length = new_cols - 1 if ends_with_wide else new_cols
new_line_lengths.append(line_length)
cells_available += line_length
return new_line_lengths
static func get_wrapped_line_trimmed_length(lines: Array, i: int, cols: int) -> int:
# If this is the last row in the wrapped line, get the actual trimmed length
if i == lines.size() - 1:
return lines[i].get_trimmed_length()
# Detect whether the following line starts with a wide character and the end of the current line
# is null, if so then we can be pretty sure the null character should be excluded from the line
# length
var ends_in_null = not (lines[i].has_content(cols - 1)) and lines[i].get_width(cols - 1) == 1
var following_line_starts_with_wide = lines[i + 1].get_width(0) == 2
if ends_in_null and following_line_starts_with_wide:
return cols - 1
return cols

View file

@ -21,8 +21,7 @@ func _init(options_service, buffer_service):
alt = Buffer.new(false, options_service, buffer_service)
active = normal
# TODO
#setup_tab_stops()
setup_tab_stops()
# Sets the normal Bufer of the BufferSet as its currently active Buffer.
@ -53,3 +52,18 @@ func activate_alt_buffer(fill_attr = null) -> void:
alt.y = normal.y
active = alt
emit_signal("buffer_activated", alt, normal)
# Resizes both normal and alt buffers, adjusting their data accordingly.
# @param new_cols The new number of columns.
# @param new_rows The new number of rows.
func resize(new_cols: int, new_rows: int) -> void:
normal.resize(new_cols, new_rows)
alt.resize(new_cols, new_rows)
# Setup the tab stops.
# @param i The index to start setting up tab stops from.
func setup_tab_stops(i = null) -> void:
normal.setup_tab_stops(i)
alt.setup_tab_stops(i)

View file

@ -91,3 +91,16 @@ enum UnderlineStyle {
DOTTED
DASHED
}
enum CursorStyle {
BLOCK
UNDERLINE
BAR
}
enum BellStyle {
NONE
VISUAL
SOUND
BOTH
}