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

@ -33,9 +33,9 @@ const Const = preload("res://addons/godot_xterm/Constants.gd")
const Constants = preload("res://addons/godot_xterm/parser/constants.gd")
const Parser = preload("res://addons/godot_xterm/parser/escape_sequence_parser.gd")
const Decoder = preload("res://addons/godot_xterm/input/text_decoder.gd")
const Renderer = preload("res://addons/godot_xterm/renderer/renderer.gd")
const ColorManager = preload("res://addons/godot_xterm/color_manager.gd")
const CellData = preload("res://addons/godot_xterm/buffer/cell_data.gd")
const AttributeData = preload("res://addons/godot_xterm/buffer/attribute_data.gd")
const SourceCodeProRegular = preload("res://addons/godot_xterm/fonts/source_code_pro/source_code_pro_regular.tres")
const SourceCodeProBold = preload("res://addons/godot_xterm/fonts/source_code_pro/source_code_pro_bold.tres")
@ -52,15 +52,21 @@ const LEFT_BRACKET = 91
const ENTER = 10
const BACKSPACE_ALT = 127
const BLINK_INTERVAL = 0.6 # 600ms. The time between blinks.
# TODO: Move me somewhere else.
enum BellStyle {
NONE
}
signal output(data)
signal scrolled(ydisp)
export var cols = 80
export var rows = 24
# If set, terminals rows and cols will be automatically calculated based on the
# control's rect size and font_size.
export var auto_resize = false
export var cursor_blink = false
export var cursor_style = 'block'
export var cursor_width = 1
@ -76,8 +82,6 @@ export var font_family: Dictionary = {
"bold_italic": SourceCodeProBoldItalic,
}
export var font_size: int = 15
export var font_weight = 'normal' # Enum?
export var font_weight_bold = 'bold' # Enum?
export var line_height = 1.0
export var link_tooltip_hover_duration = 500 # Not relevant?
export var letter_spacing = 0
@ -93,7 +97,22 @@ export var allow_proposed_api = true
export var allow_transparency = false
export var tab_stop_width = 8
export var colors: Dictionary = {
'black': Color(0, 0, 0)
"black": Color("#2e3436"),
"red": Color("#cc0000"),
"green": Color("#4e9a06"),
"yellow": Color("#c4a000"),
"blue": Color("#3465a4"),
"magenta": Color("#75507b"),
"cyan": Color("#06989a"),
"white": Color("#d3d7cf"),
"bright_black": Color("#555753"),
"bright_red": Color("#ef2929"),
"bright_green": Color("#8ae234"),
"bright_yellow": Color("#fce94f"),
"bright_blue": Color("#729fcf"),
"bright_magenta": Color("#ad7fa8"),
"bright_cyan": Color("#34e2e2"),
"bright_white": Color("#eeeeec"),
}
export var right_click_selects_word = 'isMac' # TODO
export var renderer_type = 'canvas' # Relevant?
@ -122,19 +141,13 @@ var _scaled_cell_height
var _scaled_char_top
var _scaled_char_left
var _work_cell = CellData.new()
var _blink_on = false
var _time_since_last_blink = 0
func _ready():
var options = OptionsService.TerminalOptions.new()
options.cols = cols
options.rows = rows
options.font_family = font_family
options.line_height = line_height
options.screen_reader_mode = screen_reader_mode
options.window_options = window_options
options.convert_eol = convert_eol
options.copy_from(self)
options_service = OptionsService.new(options)
options_service.connect("option_changed", self, "_update_options")
_buffer_service = BufferService.new(options_service)
_core_service = CoreService.new()
@ -151,9 +164,10 @@ func _ready():
_color_manager = ColorManager.new()
_color_manager.set_theme(colors)
_render_service = Renderer.new(_color_manager.colors, self, _buffer_service, options_service)
connect("resized", self, "_update_dimensions")
if auto_resize:
connect("resized", self, "_update_dimensions")
_update_dimensions()
@ -231,21 +245,146 @@ func _update_dimensions():
# Calculate the x coordinate with a cell that text should draw from in
# order to draw in the center of a cell.
_scaled_char_left = floor(options_service.options.letter_spacing / 2)
if auto_resize:
# Calculate cols and rows based on cell size
var rect: Rect2 = get_rect()
var cols = max(2, floor(rect.size.x / _scaled_cell_width))
var rows = max(1, floor(rect.size.y / _scaled_cell_height))
self.cols = cols
self.rows = rows
options_service.set_option("rows", rows)
options_service.set_option("cols", cols)
# Scroll the terminal down 1 row, creating a blank line.
# @param is_wrapped Whether the new line is wrapped from the previous line.
func scroll(erase_attr, is_wrapped: bool = false) -> void:
var buffer = _buffer_service.buffer
var new_line = buffer.get_blank_line(erase_attr, is_wrapped)
var top_row = buffer.ybase + buffer.scroll_top
var bottom_row = buffer.ybase + buffer.scroll_bottom
if buffer.scroll_top == 0:
# Determine whether the buffer is going to be trimmed after insertion.
var will_buffer_be_trimmed = buffer.lines.is_full
# Insert the line using the fastest method
if bottom_row == buffer.lines.length - 1:
if will_buffer_be_trimmed:
buffer.lines.recycle().copy_from(new_line.duplicate())
else:
buffer.lines.push(new_line.duplicate())
else:
buffer.lines.splice(bottom_row + 1, 0, [new_line.duplicate()])
# Only adjust ybase and ydisp when the buffer is not trimmed
if not will_buffer_be_trimmed:
buffer.ybase += 1
# Only scroll the ydisp with ybase if the user has not scrolled up
if not _buffer_service.is_user_scrolling:
buffer.ydisp += 1
else:
# When the buffer is full and the user has scrolled up, keep the text
# stable unless ydisp is right at the top
if _buffer_service.is_user_scrolling:
buffer.ydisp = max(buffer.ydisp - 1, 0)
else:
# scroll_top is non-zero which means no line will be going to the
# scrollback, instead we can just shift them in-place.
var scroll_region_height = bottom_row - top_row + 1 # as it's zero based
buffer.lines.shift_elements(top_row + 1, scroll_region_height - 1, -1)
buffer.lines.set_line(bottom_row, new_line.duplicate())
# Move the viewport to the bottom of the buffer unless the user is scrolling.
if not _buffer_service.is_user_scrolling:
buffer.ydisp = buffer.ybase
# Flag rows that need updating
# TODO
emit_signal("scrolled", buffer.ydisp)
func _process(delta):
_time_since_last_blink += delta
if _time_since_last_blink > BLINK_INTERVAL:
_blink_on = not _blink_on
_time_since_last_blink = 0
update()
func _draw():
# Draw the background and foreground
if _buffer_service == null:
return
var buffer = _buffer_service.buffer
for y in range(buffer.ybase, rows):
var line = buffer.lines.get_el(y)
var rows = _buffer_service.rows
for y in range(0, rows):
var row = y + buffer.ydisp
var line = buffer.lines.get_line(row)
for x in line.length:
line.load_cell(x, _work_cell)
# Background
# Get the background color
# TODO: handle inverse
var bg_color
if _work_cell.is_bg_rgb():
bg_color = AttributeData.to_color_rgb(_work_cell.get_bg_color())
elif _work_cell.is_bg_palette():
bg_color = _color_manager.colors.ansi[_work_cell.get_bg_color()]
else:
bg_color = _color_manager.colors.background
draw_rect(Rect2(x * _scaled_cell_width, y * _scaled_cell_height,
(cols - x) * _scaled_cell_width, 1 * _scaled_cell_height), Color())
var color = _color_manager.colors.ansi[_work_cell.get_fg_color()] if _work_cell.get_fg_color() >= 0 else Color(1, 1, 1)
draw_char(options_service.options.font_family.regular,
(cols - x) * _scaled_cell_width, 1 * _scaled_cell_height),
bg_color)
# Foreground
# Don't draw if cell is invisible
if _work_cell.is_invisible():
continue
# Don't draw if cell is blink and blink is off
if _work_cell.is_blink() and not _blink_on:
continue
# Get the foreground color
# TODO: handle inverse min contrast and draw bold in bright colors
# dim and maybe more!
var fg_color
if _work_cell.is_fg_default():
fg_color = _color_manager.colors.foreground
if _work_cell.is_fg_rgb():
fg_color = AttributeData.to_color_rgb(_work_cell.get_fg_color())
else:
fg_color = _color_manager.colors.ansi[_work_cell.get_fg_color()]
# Get font
var font: DynamicFont = options_service.options.font_family.regular
var is_bold = _work_cell.is_bold()
var is_italic = _work_cell.is_italic()
if is_bold and is_italic:
font = options_service.options.font_family.bold_italic
elif is_bold:
font = options_service.options.font_family.bold
elif is_italic:
font = options_service.options.font_family.italic
# TODO: set this once initially
font.size = options_service.options.font_size
draw_char(font,
Vector2(x * _scaled_cell_width + _scaled_char_left,
y * _scaled_cell_height + _scaled_char_top + _scaled_char_height / 2),
_work_cell.get_chars() if _work_cell.get_chars() else ' ', "", color)
_work_cell.get_chars() if _work_cell.get_chars() else ' ', "", fg_color)
# Draw the cursor
# Draw selection