-- Compiled with roblox-ts v3.0.0 local TS = _G[script] local RegistryPath = TS.import(script, TS.getModule(script, "@rbxts", "centurion").out).RegistryPath local ArrayUtil = TS.import(script, TS.getModule(script, "@rbxts", "centurion").out.shared.util.data).ArrayUtil local splitString = TS.import(script, TS.getModule(script, "@rbxts", "centurion").out.shared.util.string).splitString local _vide = TS.import(script, TS.getModule(script, "@rbxts", "vide").src) local Vide = _vide local derive = _vide.derive local source = _vide.source local spring = _vide.spring local HISTORY_TEXT_SIZE = TS.import(script, script.Parent.Parent.Parent, "constants", "text").HISTORY_TEXT_SIZE local useClient = TS.import(script, script.Parent.Parent.Parent, "hooks", "use-client").useClient local useEvent = TS.import(script, script.Parent.Parent.Parent, "hooks", "use-event").useEvent local px = TS.import(script, script.Parent.Parent.Parent, "hooks", "use-px").px local _store = TS.import(script, script.Parent.Parent.Parent, "store") local commandArgIndex = _store.commandArgIndex local currentCommandPath = _store.currentCommandPath local currentSuggestion = _store.currentSuggestion local currentTextPart = _store.currentTextPart local mouseOverInterface = _store.mouseOverInterface local options = _store.options local terminalArgIndex = _store.terminalArgIndex local terminalText = _store.terminalText local terminalTextValid = _store.terminalTextValid local _string = TS.import(script, script.Parent.Parent.Parent, "utils", "string") local isQuoteEnded = _string.isQuoteEnded local isQuoteStarted = _string.isQuoteStarted local HistoryList = TS.import(script, script.Parent.Parent, "history").HistoryList local Frame = TS.import(script, script.Parent.Parent, "ui", "frame").Frame local Padding = TS.import(script, script.Parent.Parent, "ui", "padding").Padding local _command = TS.import(script, script.Parent, "command") local getMissingArgs = _command.getMissingArgs local getValidPath = _command.getValidPath local _suggestion = TS.import(script, script.Parent, "suggestion") local getArgumentSuggestion = _suggestion.getArgumentSuggestion local getCommandSuggestion = _suggestion.getCommandSuggestion local TerminalTextField = TS.import(script, script.Parent, "terminal-text-field").TerminalTextField local MAX_HEIGHT = HISTORY_TEXT_SIZE * 10 local TEXT_FIELD_HEIGHT = 40 local TRAILING_SPACE_PATTERN = "(%s+)$" local function Terminal() local client = useClient() local missingArgs = source({}) local history = source(client.dispatcher:getHistory()) local historyHeight = source(0) useEvent(client.dispatcher.historyUpdated, function(entries) local _array = {} local _length = #_array table.move(entries, 1, #entries, _length + 1, _array) return history(_array) end) local maxHeight = derive(function() return px(MAX_HEIGHT) end) local scrollingEnabled = derive(function() return historyHeight() > maxHeight() end) local terminalTextParts = derive(function() return splitString(terminalText(), " ", true) end) local atNextPart = derive(function() local parts = terminalTextParts() local textPart = parts[#parts] local quoted = false if textPart ~= nil then quoted = isQuoteStarted(textPart) and not isQuoteEnded(textPart) end return string.sub(terminalText(), -1) == " " and not quoted end) local terminalHeight = derive(function() local padding = px.ceil(TEXT_FIELD_HEIGHT + 16) if #history() == 0 then return padding end local clampedHeight = if scrollingEnabled() then maxHeight() else historyHeight() return math.ceil(padding + clampedHeight + px(8)) end) return Vide.jsx(Frame, { size = spring(function() return UDim2.new(1, 0, 0, terminalHeight()) end, 0.2), backgroundColor = function() return options().palette.background end, backgroundTransparency = function() local _condition = options().backgroundTransparency if _condition == nil then _condition = 0 end return _condition end, cornerRadius = function() return UDim.new(0, px(8)) end, native = { MouseEnter = function() return mouseOverInterface(true) end, MouseLeave = function() return mouseOverInterface(false) end, }, }, Vide.jsx(Padding, { all = function() return UDim.new(0, px(8)) end, }), Vide.jsx(HistoryList, { size = function() return UDim2.new(1, 0, 1, -px(TEXT_FIELD_HEIGHT + 8)) end, entries = history, scrollingEnabled = scrollingEnabled, onContentSizeChanged = function(size) historyHeight(size.Y) end, }), Vide.jsx(TerminalTextField, { anchor = Vector2.new(0, 1), size = function() return UDim2.new(1, 0, 0, px(TEXT_FIELD_HEIGHT)) end, position = UDim2.fromScale(0, 1), backgroundTransparency = function() local _condition = options().backgroundTransparency if _condition == nil then _condition = 0 end return _condition end, textParts = terminalTextParts, atNextPart = atNextPart, onTextChange = function(text) terminalText(text) terminalTextValid(false) missingArgs({}) local parts = terminalTextParts() if #parts == 0 then currentCommandPath(nil) currentSuggestion(nil) terminalArgIndex(nil) return nil end -- If the text ends in a space, we want to count that as having traversed -- to the next "part" of the text. local endsInSpace = string.sub(text, -1) == " " local path = getValidPath(client.registry, parts) local command = if path ~= nil then client.registry:getCommand(path) else nil if command ~= nil then -- Check for missing arguments local missing = getMissingArgs(command, parts) local _result = command.options.arguments if _result ~= nil then _result = #_result == 0 end local _condition = _result if _condition == nil then _condition = true end local noArgs = _condition currentCommandPath(path) missingArgs(missing) terminalTextValid(noArgs or #missing == 0 or (endsInSpace and #missing == 1)) else currentCommandPath(nil) terminalTextValid(false) end local textPart = parts[#parts] local atNext = atNextPart() if atNext then textPart = nil elseif textPart ~= nil then local _condition = (string.match(text, TRAILING_SPACE_PATTERN)) if _condition == nil then _condition = "" end local spaces = _condition textPart ..= tostring(spaces) end local argIndex = if path ~= nil then #parts - path:size() - (if atNext then 0 else 1) else -1 terminalArgIndex(argIndex) if command == nil or argIndex == -1 then -- Get command suggestions local parentPath = if not (#parts == 0) then RegistryPath.new(ArrayUtil.slice(parts, 0, #parts - (if atNext then 0 else 1))) else nil currentTextPart(textPart) local _exp = client.registry local _result = parentPath if _result ~= nil then _result = _result:isEmpty() end currentSuggestion(getCommandSuggestion(_exp, if not _result then parentPath else nil, textPart)) return nil end -- Handle arguments local args = command.options.arguments if args == nil then currentTextPart(nil) currentSuggestion(nil) commandArgIndex(nil) return nil end local index = -1 local currentArg local endIndex = -1 for i = 0, argIndex do if endIndex == nil or i <= endIndex then continue end index += 1 if index >= #args then currentArg = nil break end currentArg = args[index + 1] local _condition = currentArg.numArgs if _condition == nil then _condition = 1 end local numArgs = _condition endIndex = if numArgs ~= "rest" then i + (numArgs - 1) else nil end local _registry = client.registry local _result = currentArg if _result ~= nil then _result = _result.type end local _condition = _result if _condition == nil then _condition = "" end local argType = _registry:getType(_condition) if currentArg == nil or argType == nil then currentSuggestion(nil) currentTextPart(nil) terminalArgIndex(nil) commandArgIndex(nil) return nil end commandArgIndex(index) local argTextPart local suggestion if argType.kind == "single" then argTextPart = textPart suggestion = getArgumentSuggestion({ kind = "single", options = currentArg, type = argType, input = argTextPart, }, argTextPart) else local _condition_1 = textPart if _condition_1 == nil then _condition_1 = "" end local textParts = splitString(_condition_1, ",", true) local lastText = if not (#textParts == 0) then textParts[#textParts] else nil local _result_1 = textPart if _result_1 ~= nil then _result_1 = string.sub(_result_1, -1) end argTextPart = if _result_1 ~= "," then lastText else nil suggestion = getArgumentSuggestion({ kind = "list", options = currentArg, type = argType, input = if #textParts > 1 then textParts else nil, }, argTextPart) end currentTextPart(argTextPart) currentSuggestion(suggestion) local _result_1 = suggestion if _result_1 ~= nil then _result_1 = _result_1.error end if _result_1 ~= nil then terminalTextValid(false) end end, onSubmit = function() local commandPath = currentCommandPath() local command = if commandPath ~= nil then client.registry:getCommand(commandPath) else nil if commandPath == nil or command == nil then client.dispatcher:addHistoryEntry({ success = false, text = "Command not found.", sentAt = os.time(), }) return nil end local missing = missingArgs() if not (#missing == 0) then client.dispatcher:addHistoryEntry({ success = false, text = `Missing arguments: {table.concat(missing, ", ")}`, sentAt = os.time(), }) return nil end local args = ArrayUtil.slice(splitString(terminalText(), " "), commandPath:size()) client.dispatcher:run(commandPath, args) end, })) end return { Terminal = Terminal, }