mirror of
https://github.com/lihop/godot-xterm.git
synced 2024-09-20 08:26:20 +02:00
e8c27f2796
Clearing the terminal removes all lines in the scrollback buffer except for the most recent. With sb_reset, the terminal will return the scrollback buffer to the bottom when the user starts typing if they have previously scrolled up.
228 lines
5.6 KiB
GDScript
228 lines
5.6 KiB
GDScript
# Copyright (c) 2021, Leroy Hopson (MIT License).
|
|
#
|
|
# This file contains snippets of code derived from Godot's TextEdit node.
|
|
# These snippets are copyright of their authors and released under the MIT license:
|
|
# - Copyright (c) 2007-2021 Juan Linietsky, Ariel Manzur (MIT License).
|
|
# - Copyright (c) 2014-2021 Godot Engine contributors (MIT License).
|
|
tool
|
|
extends Control
|
|
|
|
const DefaultTheme = preload("../../themes/default.tres")
|
|
|
|
signal data_sent(data)
|
|
signal key_pressed(data, event)
|
|
signal size_changed(new_size)
|
|
signal bell
|
|
|
|
enum UpdateMode {
|
|
DISABLED,
|
|
AUTO,
|
|
ALL,
|
|
ALL_NEXT_FRAME,
|
|
}
|
|
|
|
enum SelectionMode {
|
|
NONE,
|
|
POINTER,
|
|
}
|
|
|
|
export (UpdateMode) var update_mode = UpdateMode.AUTO setget set_update_mode
|
|
|
|
var cols = 2
|
|
var rows = 2
|
|
|
|
# If true, text in the terminal will be copied to the clipboard when selected.
|
|
export (bool) var copy_on_selection
|
|
|
|
var _viewport: Viewport = preload("./viewport.tscn").instance()
|
|
var _native_terminal: Control = _viewport.get_node("Terminal")
|
|
var _screen := TextureRect.new()
|
|
|
|
var _selecting := false
|
|
var _selecting_mode: int = SelectionMode.NONE
|
|
var _selection_timer := Timer.new()
|
|
|
|
var _dirty := false
|
|
|
|
var buffer := StreamPeerBuffer.new()
|
|
|
|
var times = 0
|
|
|
|
|
|
func set_update_mode(value):
|
|
update_mode = value
|
|
_native_terminal.update_mode = value
|
|
|
|
|
|
func get_rows() -> int:
|
|
return 0
|
|
|
|
|
|
func get_cols() -> int:
|
|
return 0
|
|
|
|
|
|
func write(data) -> void:
|
|
assert(data is String or data is PoolByteArray)
|
|
|
|
# FIXME: This will occasionally cause a "Resumed function after yield, but class instance is gone" error after freeing the Terminal instance.
|
|
# However, this yield is necessary to ensure the terminal state machines framebuffer is up to date when we make all the draw_* calls.
|
|
yield(VisualServer, "frame_pre_draw")
|
|
|
|
_native_terminal.write(data if data is String else data.get_string_from_utf8())
|
|
_native_terminal.update()
|
|
|
|
|
|
func clear() -> void:
|
|
var initial_size = _native_terminal.rect_size
|
|
_native_terminal.rect_size.y = _native_terminal.cell_size.y
|
|
_native_terminal.clear_sb()
|
|
_native_terminal.rect_size = initial_size
|
|
|
|
|
|
func copy_selection() -> String:
|
|
return _native_terminal.copy_selection()
|
|
|
|
|
|
func copy_all() -> String:
|
|
return _native_terminal.copy_all()
|
|
|
|
|
|
func _ready():
|
|
if theme:
|
|
_native_terminal.theme = theme
|
|
|
|
_native_terminal.update_mode = update_mode
|
|
_native_terminal.connect("data_sent", self, "_on_data_sent")
|
|
_native_terminal.connect("key_pressed", self, "_on_key_pressed")
|
|
_native_terminal.connect("size_changed", self, "_on_size_changed")
|
|
_native_terminal.connect("bell", self, "_on_bell")
|
|
|
|
_viewport.size = rect_size
|
|
_viewport.render_target_update_mode = Viewport.UPDATE_ALWAYS
|
|
|
|
_screen.set_anchors_preset(PRESET_WIDE)
|
|
_screen.texture = _viewport.get_texture()
|
|
|
|
_selection_timer.wait_time = 0.05
|
|
_selection_timer.connect("timeout", self, "_on_selection_held")
|
|
|
|
add_child(_viewport)
|
|
add_child(_screen)
|
|
add_child(_selection_timer)
|
|
|
|
_refresh()
|
|
|
|
|
|
func _refresh():
|
|
_screen.update()
|
|
if update_mode == UpdateMode.AUTO:
|
|
_native_terminal.update_mode = UpdateMode.ALL_NEXT_FRAME
|
|
|
|
|
|
func _gui_input(event):
|
|
_native_terminal._gui_input(event)
|
|
|
|
if event is InputEventKey:
|
|
_native_terminal.sb_reset() # Return to bottom of scrollback buffer if we scrolled up.
|
|
|
|
_handle_mouse_wheel(event)
|
|
_handle_selection(event)
|
|
|
|
|
|
func _handle_mouse_wheel(event: InputEventMouseButton):
|
|
if not event or not event.is_pressed():
|
|
return
|
|
|
|
if event.button_index == BUTTON_WHEEL_UP:
|
|
if event.alt:
|
|
# Scroll 5 times as fast as normal (like TextEdit).
|
|
_native_terminal.sb_up(15 * event.factor)
|
|
else:
|
|
# Scroll 3 lines.
|
|
_native_terminal.sb_up(3 * event.factor)
|
|
|
|
if event.button_index == BUTTON_WHEEL_DOWN:
|
|
if event.alt:
|
|
# Scroll 5 times as fast as normal (like TextEdit).
|
|
_native_terminal.sb_down(15 * event.factor)
|
|
else:
|
|
# Scroll 3 lines.
|
|
_native_terminal.sb_down(3 * event.factor)
|
|
|
|
|
|
func _handle_selection(event: InputEventMouse):
|
|
if event is InputEventMouseButton:
|
|
if not event or not event.is_pressed() or not event.button_index == BUTTON_LEFT:
|
|
return
|
|
|
|
if _selecting:
|
|
_selecting = false
|
|
_selecting_mode = SelectionMode.NONE
|
|
_native_terminal.reset_selection()
|
|
|
|
# Single-click select pointer.
|
|
_selecting = false
|
|
_selecting_mode = SelectionMode.POINTER
|
|
|
|
elif event is InputEventMouseMotion:
|
|
if (
|
|
event.button_mask & BUTTON_MASK_LEFT
|
|
and _selecting_mode != SelectionMode.NONE
|
|
and not _selecting
|
|
):
|
|
_selecting = true
|
|
_native_terminal.start_selection(_mouse_to_cell(event.position))
|
|
_selection_timer.start()
|
|
|
|
|
|
func _on_selection_held() -> void:
|
|
if not Input.is_mouse_button_pressed(BUTTON_LEFT) or _selecting_mode == SelectionMode.NONE:
|
|
if copy_on_selection:
|
|
var selection = _native_terminal.copy_selection()
|
|
OS.set_clipboard(selection)
|
|
_selection_timer.stop()
|
|
return
|
|
|
|
var position: Vector2 = _mouse_to_cell(get_local_mouse_position())
|
|
_native_terminal.select_to_pointer(position)
|
|
_selection_timer.start()
|
|
|
|
|
|
func _notification(what: int) -> void:
|
|
match what:
|
|
NOTIFICATION_RESIZED:
|
|
_viewport.size = rect_size
|
|
_refresh()
|
|
NOTIFICATION_THEME_CHANGED:
|
|
_native_terminal.theme = theme
|
|
_refresh()
|
|
|
|
|
|
func _on_data_sent(data: PoolByteArray):
|
|
emit_signal("data_sent", data)
|
|
|
|
|
|
func _on_key_pressed(data: String, event: InputEventKey):
|
|
emit_signal("key_pressed", data, event)
|
|
|
|
|
|
func _on_size_changed(new_size: Vector2):
|
|
cols = new_size.x
|
|
rows = new_size.y
|
|
emit_signal("size_changed", new_size)
|
|
|
|
|
|
func _on_bell():
|
|
emit_signal("bell")
|
|
|
|
|
|
func _mouse_to_cell(pos: Vector2) -> Vector2:
|
|
return Vector2(pos / _native_terminal.cell_size)
|
|
|
|
|
|
func _set_size_warning(value):
|
|
if value:
|
|
push_warning(
|
|
"Terminal cols and rows are read only and determined by the font and rect sizes."
|
|
)
|