extends Node class_name TerminalControls @onready var historyContainer = $MarginContainer/ScrollContainer/VBoxContainer @onready var caret = $MarginContainer/ScrollContainer/VBoxContainer/Label/Caret @onready var blink_timer = $MarginContainer/ScrollContainer/VBoxContainer/Label/CaretTimer @export var terminalLine: Label var command: String var directory: String = "~/" var fileSystem: Dictionary = { "documents": { "special": { "notes.txt": "test", "secrets": { "password.txt": 123 } } }, "bin": { "sh": "binary data", } } func _ready() -> void: terminalLine.text = "user@work "+ directory + " " UpdateCaretPos() blink_timer.start() # --- Caret Blinking Logic --- func _on_caret_timer_timeout() -> void: caret.visible = !caret.visible func reset_blink() -> void: caret.visible = true blink_timer.start() # Resets the countdown # --- Input Handling --- func InputChar(input) -> void: if input == null: return command = input terminalLine.text += command UpdateCaretPos() reset_blink() func InputDelChar() -> void: if terminalLine.text.length() > ("user@work " + directory).length() + 1: terminalLine.text = terminalLine.text.left(-1) UpdateCaretPos() reset_blink() func EnterCommand() -> void: var fullText = terminalLine.text var userInput = fullText.trim_prefix("user@work " + directory).strip_edges() if historyContainer.get_child_count() <22: CreateHistoryEntry(fullText) var parts = userInput.split(" ") match parts[0]: "ls": var currentDirData if parts.size() > 1: currentDirData = GetDirAtPath(directory + parts[1]) else: currentDirData = GetDirAtPath(directory) if currentDirData is Dictionary: var list = "" for key in currentDirData.keys(): list += key + " " CreateHistoryEntry(list) else: CreateHistoryEntry("error: Directory not found") "cd": if parts.size() > 1: var targetPath = parts[1] var newDir = ResolvePath(directory, targetPath) if GetDirAtPath(newDir) != null: directory = newDir else: CreateHistoryEntry("cd: no such directory: " + targetPath) else: directory = "~/" "cat": if parts.size() > 1: RetrieveData(parts[1]) else: CreateHistoryEntry("usage: cat [filename]") "clear": for child in historyContainer.get_children(): if child != terminalLine: child.queue_free() "": pass _: CreateHistoryEntry("Command not found: " + userInput) terminalLine.text = "user@work "+ directory + " " # Handle UI scrolling correctly var scrollBox = historyContainer.get_parent() as ScrollContainer historyContainer.force_update_transform() await get_tree().process_frame scrollBox.scroll_vertical = int(scrollBox.get_v_scroll_bar().max_value) UpdateCaretPos() # --- History and FileSystem Helpers --- func CreateHistoryEntry(content: String) -> void: var label = Label.new() label.text = content label.label_settings = LabelSettings.new() label.label_settings.font_size = 42 label.autowrap_mode = TextServer.AUTOWRAP_WORD_SMART label.custom_minimum_size = Vector2(1400, 0) if terminalLine.text.length() > 200: for child in historyContainer.get_children(): if child != terminalLine: child.queue_free() historyContainer.add_child(label) historyContainer.move_child(label, historyContainer.get_child_count() - 2) # Limit history to 15 entries + 1 active line if historyContainer.get_child_count() > 22: historyContainer.get_child(0).queue_free() func ResolvePath(current: String, target: String) -> String: if target.begins_with("~/"): return target if target.ends_with("/") else target + "/" if target == "..": if current == "~/": return "~/" var parts = current.trim_suffix("/").rsplit("/", true, 1) return parts[0] + "/" var joined = current + target return joined if joined.ends_with("/") else joined + "/" func GetDirAtPath(path: String): if path == "~/": return fileSystem var cleanPath = path.trim_prefix("~/").trim_suffix("/") var folders = cleanPath.split("/") var current = fileSystem for folder in folders: if current is Dictionary and current.has(folder) and current[folder] is Dictionary: current = current[folder] else: return null return current if current is Dictionary else null func RetrieveData(filename: String): var currentData = GetDirAtPath(directory) if currentData == null: CreateHistoryEntry("Error: Current directory invalid.") return if currentData.has(filename): var content = currentData[filename] if content is Dictionary: CreateHistoryEntry("cat: " + filename + ": is a directory") else: CreateHistoryEntry(str(content)) else: CreateHistoryEntry("cat: " + filename + ": no such file") func UpdateCaretPos(): var last_char_index = terminalLine.text.length() - 1 var char_rect = terminalLine.get_character_bounds(max(0, last_char_index)) caret.position.x = char_rect.end.x + 1 caret.position.y = char_rect.position.y