Skip to content
Draft
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
177 changes: 99 additions & 78 deletions src/CommandTerminal.gd
Original file line number Diff line number Diff line change
@@ -1,92 +1,113 @@
@icon("res://addons/command-terminal/ast/CommandTerminal.svg")
@tool
## A control that displays a UI for entering and running commands
##
## A control that displays a UI for entering and running commands. It accesses the plugin's CommandServer singleton.
class_name CommandTerminal
extends Control
extends PanelContainer

@export_group("Font")
## The font to use in the terminal.
@export var font : Font = load("res://addons/command-terminal/ast/windows_command_prompt.ttf")
## The font size to use in the terminal.
@export var font_size : int = 12
#@onready var autocomplete_panel : CommandTerminalAutocompletePanel = command_terminal_guts.autocomplete_panel

@export_group("Theming")
## The [StyleBox]es to use for the terminal panel.
@export var terminal_panel_styling : StyleBox
## The [StyleBox]es to use for the autocomplete panel.
@export var autocomplete_panel_styling : StyleBox
@onready var terminal_line_edit : CommandTerminalLineEdit = $"%TERMINAL-LINE-EDIT"

var guts : Node
signal contents_altered(new_contents : String)
signal command_ran(command : String)
var last_ran_command : String = ""

func _ready():
_first_time_setup()
_handle_editor_functions()
func _ready() -> void:
terminal_line_edit.terminal = self
terminal_line_edit.terminal_rich_label.terminal = self

func _process(_delta : float) -> void:
_handle_editor_properties()
_handle_functions()
if not Engine.is_editor_hint(): return
_handle_editor_functions()
var _console_key : Resource = ProjectSettings.get_setting("plugins/command_terminal/console_key_shortcut").duplicate()
InputMap.add_action("ui_console")
InputMap.action_add_event("ui_console", _console_key)
CommandTerminalLogger.log(2, ["TERMINAL"], "Registered 'ui_console' to InputMap")

func _first_time_setup() -> void:
if guts != null: return
if self.has_node("__guts__"):
guts = self.get_node("__guts__")
return
terminal_line_edit.text_changed.connect(
func(t : String) -> void:
fetch_autocomplete_entries(t)
autocomplete_selected_index = 0
terminal_line_edit.pre_autocompleted_text = t
terminal_line_edit.terminal_rich_label.update_text(t)
contents_altered.emit(t)
)
terminal_line_edit.text_submitted.connect(
func(t : String) -> void:
terminal_line_edit.text = ""
#terminal_rich_label.text = ""
CommandTerminalLogger.log(2, ["TERMINAL"], "Terminal submitted with: '%s'." % [t])
last_ran_command = t
command_ran.emit(t)
terminal_line_edit.text_changed.emit("")
)
#terminal_line_edit.focus_entered.connect(autocomplete_panel.redraw_autocomplete_contents)
#terminal_line_edit.focus_exited.connect(autocomplete_panel.redraw_autocomplete_contents)

var guts_scene : PackedScene = load("res://addons/command-terminal/scn/command_terminal_guts.tscn")
guts = guts_scene.instantiate()
self.add_child(guts)
guts.set_owner(get_tree().get_edited_scene_root())
#terminal_line_edit.clear()
#terminal_rich_label.clear()

guts.set_anchors_and_offsets_preset.call_deferred(PRESET_FULL_RECT)
self.set_anchors_and_offsets_preset.call_deferred(PRESET_BOTTOM_WIDE)
var last_input : String = ""
var last_output : CommandLexer.LexTreeNode
func tokenizer_cache(new_text : String) -> CommandLexer.LexTreeNode:
if new_text == last_input:
CommandTerminalLogger.log(3, ["TERMINAL", "TOKENIZE"], "Tokenization cache hit")
CommandTerminalLogger.log(3, ["TERMINAL", "TOKENIZE"], "Returning: \n%s" % [CommandLexer._print_tree(last_output)])
return last_output
else:
CommandTerminalLogger.log(3, ["TERMINAL", "TOKENIZE"], "Tokenization required for: %s" % [new_text])
last_input = new_text
last_output = CommandLexer.tokenize_input(new_text)
return last_output

func _handle_functions() -> void:
var terminal_line_edit = guts.find_child("TERMINAL-LINE-EDIT", true, false)
var terminal_rich_label = guts.find_child("TERMINAL-RICH-LABEL", true, false)
terminal_line_edit.set_anchors_and_offsets_preset(PRESET_HCENTER_WIDE)
terminal_rich_label.set_anchors_and_offsets_preset(PRESET_HCENTER_WIDE)
var autocomplete_entries : Array[String] = []
var autocomplete_entry_owners : Array[Argument] = []
func fetch_autocomplete_entries(new_text : String) -> void:
CommandTerminalLogger.log(3, ["TERMINAL", "AUTOCOMPLETE"], "Fetching autocomplete entries for text: %s" % [new_text])
var lextreeroot : CommandLexer.LexTreeNode = self.tokenizer_cache(new_text)
autocomplete_entries.clear()
autocomplete_entry_owners.clear()
_fetch_autocomplete_entries(lextreeroot)

func _handle_editor_functions() -> void:
if self.size.y < 15:
self.position.y -= 15 - self.size.y
self.size.y = 15
var autocomplete_panel = guts.find_child("AUTOCOMPLETE-PANEL", true, false)
var terminal_panel = guts.find_child("TERMINAL-PANEL", true, false)
var terminal_panel_rect = terminal_panel.get_rect()
autocomplete_panel.size = Vector2(0, terminal_panel_rect.size.y)
autocomplete_panel.position = -Vector2(0, terminal_panel_rect.size.y)
func _fetch_autocomplete_entries(_token_tree_node : CommandLexer.LexTreeNode) -> void:
if _token_tree_node == null: return
if _token_tree_node.token is CommandLexer.CommandToken:
for entry : String in _token_tree_node.token.provided_autocomplete_entries:
autocomplete_entries.append(entry)
autocomplete_entry_owners.append(_token_tree_node.token.argument)
var children : Array[CommandLexer.LexTreeNode] = _token_tree_node.children.duplicate()
children.sort_custom(CommandServer._sort_pnaltn)
for child : CommandLexer.LexTreeNode in children:
_fetch_autocomplete_entries(child)

func _handle_editor_properties() -> void:
if autocomplete_panel_styling:
guts.get_node("%AUTOCOMPLETE-PANEL").add_theme_stylebox_override("panel", autocomplete_panel_styling)
else:
guts.get_node("%AUTOCOMPLETE-PANEL").remove_theme_stylebox_override("panel")
var autocomplete_selected_index : int = 0
func advance_autocomplete_index() -> void: _change_autocomplete_index(true)
func reverse_autocomplete_index() -> void: _change_autocomplete_index(false)
func _change_autocomplete_index(forward : bool) -> void:
if autocomplete_entries.is_empty(): return
if not terminal_line_edit.text == terminal_line_edit.pre_autocompleted_text:
autocomplete_selected_index -= 1 if forward else -1
autocomplete_selected_index = wrapi(autocomplete_selected_index, 0, len(autocomplete_entries))
var selected_owner : Argument = autocomplete_entry_owners[autocomplete_selected_index]
var autocomp_text : String = autocomplete_entries[autocomplete_selected_index]
CommandTerminalLogger.log(3, ["AUTOCOMPLETE"], "Selected entry: %s" % [autocomp_text])
if selected_owner is PeculiarArgument:
autocomp_text = selected_owner.get_autocomplete_content()
CommandTerminalLogger.log(3, ["AUTOCOMPLETE"], "Autocompleting: %s" % [autocomp_text])
terminal_line_edit.autocomplete_text(autocomp_text)

if terminal_panel_styling:
guts.get_node("%TERMINAL-PANEL").add_theme_stylebox_override("panel", terminal_panel_styling)
guts.get_node("%TERMINAL-PANEL").add_theme_stylebox_override("panel", terminal_panel_styling)
else:
guts.get_node("%TERMINAL-PANEL").remove_theme_stylebox_override("panel")
guts.get_node("%TERMINAL-PANEL").remove_theme_stylebox_override("panel")

if font_size:
guts.get_node("%AUTOCOMPLETE-RICH-LABEL").add_theme_font_size_override("normal_font_size", font_size)
guts.get_node("%TERMINAL-RICH-LABEL").add_theme_font_size_override("normal_font_size", font_size)
guts.get_node("%TERMINAL-LINE-EDIT").add_theme_font_size_override("font_size", font_size)
else:
guts.get_node("%AUTOCOMPLETE-RICH-LABEL").remove_theme_font_size_override("normal_font_size")
guts.get_node("%TERMINAL-RICH-LABEL").remove_theme_font_size_override("normal_font_size")
guts.get_node("%TERMINAL-LINE-EDIT").remove_theme_font_size_override("font_size")

if font:
guts.get_node("%AUTOCOMPLETE-RICH-LABEL").add_theme_font_override("normal_font", font)
guts.get_node("%TERMINAL-RICH-LABEL").add_theme_font_override("normal_font", font)
guts.get_node("%TERMINAL-LINE-EDIT").add_theme_font_override("font", font)
else:
guts.get_node("%AUTOCOMPLETE-RICH-LABEL").remove_theme_font_override("normal_font")
guts.get_node("%TERMINAL-RICH-LABEL").remove_theme_font_override("normal_font")
guts.get_node("%TERMINAL-LINE-EDIT").remove_theme_font_override("font")
func _get_all_complete_args(text : String) -> Array[String]:
CommandTerminalLogger.log(3, ["TERMINAL"], "Complete args requested for: %s" % [text])
var working_token_node : CommandLexer.LexTreeNode = self.tokenizer_cache(text)
var args : Array[String] = []
while working_token_node.children.size() > 0:
working_token_node = working_token_node.children[0]
if not working_token_node.token is CommandLexer.CommandToken: continue
if working_token_node.token.content.is_empty(): continue
if not working_token_node.token.provided_autocomplete_entries.is_empty(): break
args.push_back(working_token_node.token.content)
CommandTerminalLogger.log(3, ["TERMINAL"], "Returning: %s" % [args])
return args

func _process(_delta : float) -> void:
if Input.is_action_just_pressed("ui_console"):
var window_owner : Window = get_tree().get_root()
var do_grab_focus_crosswindow : bool = ProjectSettings.get_setting(CommandTerminalPluginData.PLUGIN_PATH + "shortcut_works_cross-window")
if do_grab_focus_crosswindow:
window_owner.grab_focus()
terminal_line_edit.grab_focus()
19 changes: 1 addition & 18 deletions src/CommandTerminalAutocompletePanel.gd
Original file line number Diff line number Diff line change
Expand Up @@ -14,27 +14,10 @@ func refresh_autocomplete_contents(new_text : String) -> void:
CommandTerminalLogger.log(3, ["AUTOCOMPLETE"], "Getting autocomplete options for '%s'..." % [new_text])
fetch_autocomplete_entries(new_text)
CommandTerminalLogger.log(3, ["AUTOCOMPLETE"], "Fetched: %s" % [autocomplete_entries])
autocomplete_selected_index = -1
autocomplete_selected_index = 0
redraw_autocomplete_contents(new_text)

var autocomplete_entries : Array[String] = []
var autocomplete_entry_owners : Array[Argument] = []
func fetch_autocomplete_entries(new_text : String) -> void:
var lextreeroot : CommandLexer.LexTreeNode = command_terminal_guts.tokenizer_cache(new_text)
autocomplete_entries.clear()
autocomplete_entry_owners.clear()
_fetch_autocomplete_entries(lextreeroot)

func _fetch_autocomplete_entries(_token_tree_node : CommandLexer.LexTreeNode) -> void:
if _token_tree_node == null: return
if _token_tree_node.token is CommandLexer.CommandToken:
for entry : String in _token_tree_node.token.provided_autocomplete_entries:
autocomplete_entries.append(entry)
autocomplete_entry_owners.append(_token_tree_node.token.argument)
var children : Array[CommandLexer.LexTreeNode] = _token_tree_node.children.duplicate()
children.sort_custom(CommandServer._sort_pnaltn)
for child : CommandLexer.LexTreeNode in children:
_fetch_autocomplete_entries(child)

var autocomplete_selected_index : int = 0
func advance_autocomplete_index() -> void: _change_autocomplete_index(true)
Expand Down