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: var charPos = terminalLine.get_character_bounds(max(0, terminalLine.text.length() - 1)) if input == null: return if charPos.end.x > 1600: pass else: 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 + " " 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 == "~/" or path == "": return fileSystem var cleanPath = path.trim_prefix("~/").trim_suffix("/") var folders = cleanPath.split("/") var current = fileSystem for folder in folders: if folder == "": continue if current is Dictionary and current.has(folder): if current[folder] is Dictionary: current = current[folder] else: # We hit a file, not a directory return null else: return null return current func RetrieveData(inputPath: String): var targetFileName: String var targetDirData: Dictionary if "/" in inputPath: var pathParts = inputPath.rsplit("/", true, 1) var dirPath = pathParts[0] targetFileName = pathParts[1] var resolvedDirPath = ResolvePath(directory, dirPath) var result = GetDirAtPath(resolvedDirPath) if result is Dictionary: targetDirData = result else: CreateHistoryEntry("cat: " + dirPath + ": No such directory") return else: targetFileName = inputPath targetDirData = GetDirAtPath(directory) if targetDirData.has(targetFileName): var content = targetDirData[targetFileName] if content is Dictionary: CreateHistoryEntry("cat: " + targetFileName + ": Is a directory") else: CreateHistoryEntry(str(content)) else: CreateHistoryEntry("cat: " + targetFileName + ": No such file or directory") 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 print(char_rect.end.x)