107 lines
3.6 KiB
GDScript
107 lines
3.6 KiB
GDScript
@tool
|
|
extends VBoxContainer
|
|
|
|
signal command_submitted(command: String)
|
|
signal focus_requested
|
|
|
|
@export var session_id := "rov"
|
|
@export var tab_label := "ROV"
|
|
@export var shortcut: Key = KEY_F5
|
|
@export var prompt := "rov $"
|
|
@export var read_only := false
|
|
@export var introduction := "ROV shell disconnected."
|
|
@export var max_scrollback_characters := 128000
|
|
var connected := false
|
|
var _buffer := ""
|
|
var _history: Array[String] = []
|
|
var _history_index := 0
|
|
var _draft := ""
|
|
|
|
@onready var output: RichTextLabel = $Output
|
|
@onready var command_input: LineEdit = $Input/Command
|
|
@onready var filter_input: LineEdit = $Filter/Text
|
|
|
|
func _ready() -> void:
|
|
$Input.visible = not read_only
|
|
$Filter.visible = read_only
|
|
$Input/Prompt.text = prompt
|
|
_buffer = introduction + "\n"
|
|
output.text = _buffer
|
|
if Engine.is_editor_hint():
|
|
return
|
|
command_input.text_submitted.connect(submit_command)
|
|
command_input.gui_input.connect(_history_input)
|
|
filter_input.text_changed.connect(func(_text: String) -> void: _render_output())
|
|
$Status/Clear.pressed.connect(clear_output)
|
|
$Status/Follow.toggled.connect(func(enabled: bool) -> void:
|
|
output.scroll_following = enabled
|
|
if enabled: output.scroll_to_line(maxi(output.get_line_count() - 1, 0))
|
|
)
|
|
for control in [output, command_input, filter_input, $Status/Clear, $Status/Follow]:
|
|
control.gui_input.connect(func(event: InputEvent) -> void:
|
|
if event is InputEventMouseButton and event.pressed:
|
|
focus_requested.emit()
|
|
)
|
|
|
|
func preferred_input() -> Control:
|
|
return filter_input if read_only else command_input
|
|
|
|
func set_connection(value: bool, message: String = "") -> void:
|
|
connected = value
|
|
$Status/Connection.text = message if not message.is_empty() else ("Connected" if value else "Not connected")
|
|
|
|
func submit_command(command: String) -> bool:
|
|
if read_only or command.strip_edges().is_empty():
|
|
return false
|
|
if _history.is_empty() or _history.back() != command:
|
|
_history.append(command)
|
|
if _history.size() > 200: _history.pop_front()
|
|
_history_index = _history.size()
|
|
_draft = ""
|
|
append_output(prompt + " " + command + "\n")
|
|
command_input.clear()
|
|
command_submitted.emit(command)
|
|
return true
|
|
|
|
func append_output(text: String) -> void:
|
|
_buffer += text
|
|
var truncated := _buffer.length() > max_scrollback_characters
|
|
if truncated:
|
|
_buffer = _buffer.right(max_scrollback_characters)
|
|
var newline := _buffer.find("\n")
|
|
if newline >= 0 and newline < _buffer.length() - 1:
|
|
_buffer = _buffer.substr(newline + 1)
|
|
if truncated or (read_only and not filter_input.text.is_empty()):
|
|
_render_output()
|
|
else:
|
|
output.add_text(text)
|
|
|
|
func clear_output() -> void:
|
|
_buffer = ""
|
|
output.clear()
|
|
|
|
func _render_output() -> void:
|
|
var scroll := output.get_v_scroll_bar().value
|
|
if read_only and not filter_input.text.is_empty():
|
|
var matches := PackedStringArray()
|
|
for line in _buffer.split("\n"):
|
|
if line.to_lower().contains(filter_input.text.to_lower()):
|
|
matches.append(line)
|
|
output.text = "\n".join(matches)
|
|
else:
|
|
output.text = _buffer
|
|
if not $Status/Follow.button_pressed:
|
|
output.get_v_scroll_bar().set_deferred("value", scroll)
|
|
|
|
func _history_input(event: InputEvent) -> void:
|
|
if not event is InputEventKey or not event.pressed or event.ctrl_pressed or event.alt_pressed or event.meta_pressed:
|
|
return
|
|
if event.keycode not in [KEY_UP, KEY_DOWN] or _history.is_empty():
|
|
return
|
|
if _history_index == _history.size():
|
|
_draft = command_input.text
|
|
_history_index = clampi(_history_index + (-1 if event.keycode == KEY_UP else 1), 0, _history.size())
|
|
command_input.text = _draft if _history_index == _history.size() else _history[_history_index]
|
|
command_input.caret_column = command_input.text.length()
|
|
command_input.accept_event()
|