@rbxts/centurion-ui 1.0.8 → 2.0.0-rc.1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +60 -15
- package/centurion.luau +10 -0
- package/package.json +13 -23
- package/src/analyse/cursor.luau +25 -0
- package/src/analyse/highlight.luau +91 -0
- package/src/analyse/init.luau +318 -0
- package/src/analyse/leventine.luau +298 -0
- package/src/analyse/match.luau +39 -0
- package/src/analyse/suggest.luau +41 -0
- package/src/app/header.luau +128 -0
- package/src/app/history.luau +258 -0
- package/src/app/init.luau +699 -0
- package/src/app/suggestions.luau +419 -0
- package/src/button.luau +13 -0
- package/src/components/background.luau +37 -0
- package/src/components/container.luau +33 -0
- package/src/components/corner.luau +11 -0
- package/src/components/divider.luau +28 -0
- package/src/components/flex.luau +108 -0
- package/src/components/gap.luau +25 -0
- package/src/components/image.luau +33 -0
- package/src/components/padding.luau +59 -0
- package/src/components/stroke.luau +20 -0
- package/src/components/text.luau +57 -0
- package/src/components/textbox.luau +186 -0
- package/src/fade.luau +23 -0
- package/src/init.luau +142 -0
- package/src/input.luau +16 -0
- package/src/px.luau +68 -0
- package/src/rich.luau +14 -0
- package/src/state.luau +33 -0
- package/src/theme.luau +70 -0
- package/src/ts/index.d.ts +60 -0
- package/src/ts/init.luau +5 -0
- package/src/types.luau +85 -0
- package/vide/init.luau +8 -0
- package/vide/src/action.luau +25 -0
- package/vide/src/apply.luau +162 -0
- package/vide/src/batch.luau +23 -0
- package/vide/src/branch.luau +40 -0
- package/vide/src/changed.luau +18 -0
- package/vide/src/cleanup.luau +43 -0
- package/vide/src/context.luau +74 -0
- package/vide/src/create.luau +107 -0
- package/vide/src/defaults.luau +113 -0
- package/vide/src/derive.luau +18 -0
- package/vide/src/effect.luau +12 -0
- package/vide/src/flags.luau +12 -0
- package/vide/src/graph.luau +321 -0
- package/vide/src/implicit_effect.luau +125 -0
- package/vide/src/indexes.luau +121 -0
- package/vide/src/init.luau +10 -0
- package/vide/src/lib.luau +120 -0
- package/vide/src/mount.luau +11 -0
- package/vide/src/read.luau +5 -0
- package/vide/src/root.luau +35 -0
- package/vide/src/show.luau +36 -0
- package/vide/src/source.luau +31 -0
- package/vide/src/spring.luau +328 -0
- package/vide/src/switch.luau +114 -0
- package/vide/src/timeout.luau +27 -0
- package/vide/src/untrack.luau +25 -0
- package/vide/src/values.luau +131 -0
- package/out/app/centurion-app.d.ts +0 -3
- package/out/app/centurion-app.luau +0 -86
- package/out/app/index.d.ts +0 -32
- package/out/app/init.luau +0 -105
- package/out/components/history/history-line.d.ts +0 -9
- package/out/components/history/history-line.luau +0 -88
- package/out/components/history/history-list.d.ts +0 -11
- package/out/components/history/history-list.luau +0 -67
- package/out/components/history/index.d.ts +0 -1
- package/out/components/history/init.luau +0 -7
- package/out/components/index.d.ts +0 -2
- package/out/components/init.luau +0 -10
- package/out/components/suggestions/badge.d.ts +0 -16
- package/out/components/suggestions/badge.luau +0 -40
- package/out/components/suggestions/index.d.ts +0 -1
- package/out/components/suggestions/init.luau +0 -7
- package/out/components/suggestions/main-suggestion.d.ts +0 -9
- package/out/components/suggestions/main-suggestion.luau +0 -232
- package/out/components/suggestions/suggestion-list.d.ts +0 -8
- package/out/components/suggestions/suggestion-list.luau +0 -96
- package/out/components/suggestions/suggestions.d.ts +0 -2
- package/out/components/suggestions/suggestions.luau +0 -64
- package/out/components/suggestions/types.d.ts +0 -6
- package/out/components/suggestions/types.luau +0 -2
- package/out/components/suggestions/util.d.ts +0 -1
- package/out/components/suggestions/util.luau +0 -22
- package/out/components/terminal/command.d.ts +0 -6
- package/out/components/terminal/command.luau +0 -74
- package/out/components/terminal/index.d.ts +0 -1
- package/out/components/terminal/init.luau +0 -7
- package/out/components/terminal/suggestion.d.ts +0 -26
- package/out/components/terminal/suggestion.luau +0 -205
- package/out/components/terminal/terminal-text-field/index.d.ts +0 -1
- package/out/components/terminal/terminal-text-field/init.luau +0 -7
- package/out/components/terminal/terminal-text-field/suggestion.d.ts +0 -5
- package/out/components/terminal/terminal-text-field/suggestion.luau +0 -124
- package/out/components/terminal/terminal-text-field/terminal-text-field.d.ts +0 -13
- package/out/components/terminal/terminal-text-field/terminal-text-field.luau +0 -278
- package/out/components/terminal/terminal.d.ts +0 -2
- package/out/components/terminal/terminal.luau +0 -304
- package/out/components/ui/frame.d.ts +0 -17
- package/out/components/ui/frame.luau +0 -32
- package/out/components/ui/group.d.ts +0 -15
- package/out/components/ui/group.luau +0 -29
- package/out/components/ui/layer.d.ts +0 -8
- package/out/components/ui/layer.luau +0 -26
- package/out/components/ui/outline.d.ts +0 -15
- package/out/components/ui/outline.luau +0 -116
- package/out/components/ui/padding.d.ts +0 -10
- package/out/components/ui/padding.luau +0 -19
- package/out/components/ui/scrolling-frame.d.ts +0 -14
- package/out/components/ui/scrolling-frame.luau +0 -44
- package/out/components/ui/text-field.d.ts +0 -12
- package/out/components/ui/text-field.luau +0 -60
- package/out/components/ui/text.d.ts +0 -17
- package/out/components/ui/text.luau +0 -61
- package/out/constants/options.d.ts +0 -2
- package/out/constants/options.luau +0 -27
- package/out/constants/text.d.ts +0 -3
- package/out/constants/text.luau +0 -9
- package/out/constants/util.d.ts +0 -1
- package/out/constants/util.luau +0 -7
- package/out/hooks/use-client.d.ts +0 -2
- package/out/hooks/use-client.luau +0 -17
- package/out/hooks/use-event.d.ts +0 -14
- package/out/hooks/use-event.luau +0 -58
- package/out/hooks/use-px.d.ts +0 -19
- package/out/hooks/use-px.luau +0 -78
- package/out/index.d.ts +0 -2
- package/out/init.luau +0 -10
- package/out/palette.d.ts +0 -47
- package/out/palette.luau +0 -42
- package/out/store.d.ts +0 -12
- package/out/store.luau +0 -26
- package/out/types.d.ts +0 -44
- package/out/types.luau +0 -2
- package/out/utils/string.d.ts +0 -5
- package/out/utils/string.luau +0 -25
package/README.md
CHANGED
|
@@ -1,29 +1,74 @@
|
|
|
1
1
|
<div align="center">
|
|
2
2
|
<a href="https://centurion.paradoxum.dev/" target="_blank">
|
|
3
|
-
<img src="https://raw.githubusercontent.com/paradoxuum/centurion/main/docs/
|
|
3
|
+
<img src="https://raw.githubusercontent.com/paradoxuum/centurion/main/docs/public/logo.svg" width="150" />
|
|
4
4
|
</a>
|
|
5
5
|
|
|
6
|
-
<h1>Centurion
|
|
6
|
+
<h1>Centurion</h1>
|
|
7
7
|
|
|
8
|
-
<p>
|
|
9
|
-
<strong>A flexible and extensible command framework for roblox-ts</strong>
|
|
10
|
-
</p>
|
|
11
|
-
|
|
12
|
-
This package contains Centurion's user interface. To use it, you must
|
|
13
|
-
also install [@rbxts/centurion](https://www.npmjs.com/package/@rbxts/centurion).
|
|
8
|
+
<p><strong>A flexible and extensible command framework for Roblox</strong></p>
|
|
14
9
|
|
|
15
10
|
[![CI status][ci-badge]][ci-url]
|
|
16
11
|
[![npm latest version][npm-latest-badge]][npm-latest-url]
|
|
17
|
-
[![
|
|
12
|
+
[![MIT License][license-badge]][license-url]
|
|
18
13
|
|
|
19
|
-
[npm-latest-badge]: https://img.shields.io/npm/v
|
|
14
|
+
[npm-latest-badge]: https://img.shields.io/npm/v/@rbxts/centurion.svg?style=for-the-badge&logo=npm
|
|
20
15
|
[npm-latest-url]: https://www.npmjs.com/package/@rbxts/centurion/v/latest
|
|
21
|
-
[
|
|
22
|
-
[npm-next-url]: https://www.npmjs.com/package/@rbxts/centurion/v/next
|
|
23
|
-
[ci-badge]: https://github.com/paradoxuum/centurion/actions/workflows/ci.yml/badge.svg?branch=main
|
|
16
|
+
[ci-badge]: https://img.shields.io/github/actions/workflow/status/paradoxuum/centurion/ci.yml?style=for-the-badge&branch=main&logo=github
|
|
24
17
|
[ci-url]: https://github.com/paradoxuum/centurion/actions/workflows/ci.yml
|
|
18
|
+
[license-badge]: https://img.shields.io/github/license/paradoxuum/centurion?style=for-the-badge
|
|
19
|
+
[license-url]: https://github.com/paradoxuum/centurion/blob/main/LICENSE.md
|
|
20
|
+
|
|
21
|
+
<img src="https://raw.githubusercontent.com/paradoxuum/centurion/main/docs/public/example.webp" alt="Example" style="border-radius: 4px;" />
|
|
25
22
|
|
|
26
|
-
|
|
23
|
+
[📖 **Documentation →**](https://centurion.paradoxum.dev/)
|
|
27
24
|
</div>
|
|
28
25
|
|
|
29
|
-
|
|
26
|
+
## ✨ Features
|
|
27
|
+
|
|
28
|
+
- 🌙 **Luau and roblox-ts.** Written in Luau with full TypeScript typings, and available on both Wally and npm.
|
|
29
|
+
|
|
30
|
+
- ✏️ **Simple to define.** Register commands with a plain table, or use decorators and classes in roblox-ts.
|
|
31
|
+
|
|
32
|
+
- 🧩 **Typed arguments.** Built-in types for players, teams, numbers, colors, durations, vectors and more, with optional, multi-value and rest arguments. Register your own types with custom suggestions.
|
|
33
|
+
|
|
34
|
+
- 🔐 **Guards and permissions.** Guard commands with reusable checks, and control access with prioritised, inheritable roles.
|
|
35
|
+
|
|
36
|
+
- 🔄 **Client-server sync.** Players only receive the commands they're allowed to run, and stay up to date as commands or roles change.
|
|
37
|
+
|
|
38
|
+
- 💻 **Optional terminal UI.** A clean console with suggestions, fuzzy matching, tab completion, syntax highlighting and history. Use it as-is, theme it, or build your own.
|
|
39
|
+
|
|
40
|
+
## 📦 Installation
|
|
41
|
+
|
|
42
|
+
```toml
|
|
43
|
+
# wally.toml
|
|
44
|
+
[dependencies]
|
|
45
|
+
centurion = "paradoxuum/centurion@2.0.0"
|
|
46
|
+
centurion-ui = "paradoxuum/centurion-ui@2.0.0"
|
|
47
|
+
```
|
|
48
|
+
|
|
49
|
+
```bash
|
|
50
|
+
# roblox-ts
|
|
51
|
+
npm install @rbxts/centurion @rbxts/centurion-ui
|
|
52
|
+
```
|
|
53
|
+
|
|
54
|
+
See the [documentation](https://centurion.paradoxum.dev/) to get started.
|
|
55
|
+
|
|
56
|
+
## 💖 Attributions
|
|
57
|
+
|
|
58
|
+
- [🍀 Charm](https://github.com/littensy/charm): Inspiration for command syncing
|
|
59
|
+
|
|
60
|
+
- [💻 Conch](https://github.com/alicesaidhi/conch): UI utility components and text box state handling
|
|
61
|
+
|
|
62
|
+
- [🌿 Vide](https://github.com/centau/vide): Reactive UI library powering the terminal UI
|
|
63
|
+
|
|
64
|
+
---
|
|
65
|
+
|
|
66
|
+
<p align="center">
|
|
67
|
+
This project is released under the <a href="https://github.com/paradoxuum/centurion/blob/main/LICENSE.md">MIT License</a>.
|
|
68
|
+
</p>
|
|
69
|
+
|
|
70
|
+
<div align="center">
|
|
71
|
+
|
|
72
|
+
[](https://github.com/paradoxuum/centurion/blob/main/LICENSE.md)
|
|
73
|
+
|
|
74
|
+
</div>
|
package/centurion.luau
ADDED
|
@@ -0,0 +1,10 @@
|
|
|
1
|
+
local centurion = require(script.Parent.Parent.centurion.src)
|
|
2
|
+
|
|
3
|
+
export type Argument = centurion.Argument
|
|
4
|
+
export type ArgumentRange = centurion.ArgumentRange
|
|
5
|
+
export type Command<T...> = centurion.Command<T...>
|
|
6
|
+
export type Response = centurion.Response
|
|
7
|
+
export type Result<T = unknown> = centurion.Result<T>
|
|
8
|
+
export type Token = centurion.Token
|
|
9
|
+
|
|
10
|
+
return centurion
|
package/package.json
CHANGED
|
@@ -1,14 +1,20 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@rbxts/centurion-ui",
|
|
3
3
|
"description": "Terminal UI for the Centurion command framework",
|
|
4
|
-
"version": "
|
|
4
|
+
"version": "2.0.0-rc.1",
|
|
5
5
|
"author": "paradoxuum",
|
|
6
6
|
"license": "MIT",
|
|
7
|
-
"main": "
|
|
8
|
-
"types": "
|
|
7
|
+
"main": "src/ts/init.luau",
|
|
8
|
+
"types": "src/ts/index.d.ts",
|
|
9
9
|
"files": [
|
|
10
|
-
"
|
|
11
|
-
"
|
|
10
|
+
"src",
|
|
11
|
+
"vide/src",
|
|
12
|
+
"vide/init.luau",
|
|
13
|
+
"*.luau",
|
|
14
|
+
"!src/stories",
|
|
15
|
+
"!wally.toml",
|
|
16
|
+
"!wally.lock",
|
|
17
|
+
"!default.project.json",
|
|
12
18
|
"!**/__tests__"
|
|
13
19
|
],
|
|
14
20
|
"keywords": [
|
|
@@ -26,24 +32,8 @@
|
|
|
26
32
|
"publishConfig": {
|
|
27
33
|
"access": "public"
|
|
28
34
|
},
|
|
29
|
-
"devDependencies": {
|
|
30
|
-
"@rbxts/compiler-types": "3.0.0-types.0",
|
|
31
|
-
"@rbxts/types": "^1.0.813",
|
|
32
|
-
"roblox-ts": "3.0.0",
|
|
33
|
-
"shx": "^0.3.4",
|
|
34
|
-
"typescript": "5.5.3",
|
|
35
|
-
"@rbxts/centurion": "^1.0.1"
|
|
36
|
-
},
|
|
37
35
|
"dependencies": {
|
|
38
|
-
"@rbxts/
|
|
39
|
-
"@rbxts/set-timeout": "^1.1.2",
|
|
40
|
-
"@rbxts/vide": "^0.5.3"
|
|
41
|
-
},
|
|
42
|
-
"peerDependencies": {
|
|
43
|
-
"@rbxts/centurion": "^1.0.1"
|
|
36
|
+
"@rbxts/centurion": "^2.0.0-rc.1"
|
|
44
37
|
},
|
|
45
|
-
"scripts": {
|
|
46
|
-
"build": "shx rm -rf out && rbxtsc --verbose",
|
|
47
|
-
"dev": "shx rm -rf out && rbxtsc -w"
|
|
48
|
-
}
|
|
38
|
+
"scripts": {}
|
|
49
39
|
}
|
|
@@ -0,0 +1,25 @@
|
|
|
1
|
+
--!strict
|
|
2
|
+
|
|
3
|
+
type Token = {
|
|
4
|
+
read span: vector,
|
|
5
|
+
}
|
|
6
|
+
|
|
7
|
+
-- finds the token the cursor is on
|
|
8
|
+
local function locate(tokens: { read [number]: Token }, cursor_pos: number): (number, boolean)
|
|
9
|
+
local before = 0
|
|
10
|
+
for i, token in tokens do
|
|
11
|
+
if cursor_pos >= token.span.x and cursor_pos <= token.span.y + 1 then
|
|
12
|
+
return i, false
|
|
13
|
+
end
|
|
14
|
+
|
|
15
|
+
if token.span.y + 1 < cursor_pos then
|
|
16
|
+
before = i
|
|
17
|
+
end
|
|
18
|
+
end
|
|
19
|
+
|
|
20
|
+
return before + 1, true
|
|
21
|
+
end
|
|
22
|
+
|
|
23
|
+
return {
|
|
24
|
+
locate = locate,
|
|
25
|
+
}
|
|
@@ -0,0 +1,91 @@
|
|
|
1
|
+
--!strict
|
|
2
|
+
local Players = game:GetService("Players")
|
|
3
|
+
|
|
4
|
+
local centurion = require("../../centurion")
|
|
5
|
+
local rich = require("../rich")
|
|
6
|
+
local types = require("../types")
|
|
7
|
+
|
|
8
|
+
type Kind = "command" | "argument" | "string" | "invalid"
|
|
9
|
+
|
|
10
|
+
local function is_valid(raw: string, arg: centurion.Argument): boolean
|
|
11
|
+
local ok, result = pcall(centurion.transform_arg, Players.LocalPlayer, arg, raw)
|
|
12
|
+
return ok and result.success
|
|
13
|
+
end
|
|
14
|
+
|
|
15
|
+
local function classify(text: string): { { span: vector, kind: Kind } }
|
|
16
|
+
local resolution = centurion.resolve_command(text)
|
|
17
|
+
local tokens = resolution.tokens
|
|
18
|
+
local cmds = centurion.registry.commands
|
|
19
|
+
|
|
20
|
+
local highlights = {}
|
|
21
|
+
local function add(token: centurion.Token, kind: Kind)
|
|
22
|
+
table.insert(highlights, { span = token.span, kind = kind })
|
|
23
|
+
end
|
|
24
|
+
|
|
25
|
+
local cmd_name = resolution.name
|
|
26
|
+
local cmd_index = resolution.name_end
|
|
27
|
+
|
|
28
|
+
if not cmd_name then
|
|
29
|
+
for _, token in tokens do
|
|
30
|
+
add(token, "invalid")
|
|
31
|
+
end
|
|
32
|
+
return highlights
|
|
33
|
+
end
|
|
34
|
+
|
|
35
|
+
for i = 1, cmd_index do
|
|
36
|
+
add(tokens[i], "command")
|
|
37
|
+
end
|
|
38
|
+
|
|
39
|
+
local cmd = cmds[cmd_name]
|
|
40
|
+
local args: { centurion.Argument } = if cmd.arguments then { cmd.arguments() } :: { centurion.Argument } else {}
|
|
41
|
+
|
|
42
|
+
local ranges, next_index = centurion.assign_args(args, #tokens - cmd_index)
|
|
43
|
+
for i, arg in args do
|
|
44
|
+
for j = ranges[i].first, ranges[i].last do
|
|
45
|
+
local token = tokens[cmd_index + j]
|
|
46
|
+
local raw = text:sub(token.span.x, token.span.y)
|
|
47
|
+
|
|
48
|
+
if not is_valid(raw, arg) then
|
|
49
|
+
add(token, "invalid")
|
|
50
|
+
elseif raw:match("^[\"']") then
|
|
51
|
+
add(token, "string")
|
|
52
|
+
else
|
|
53
|
+
add(token, "argument")
|
|
54
|
+
end
|
|
55
|
+
end
|
|
56
|
+
end
|
|
57
|
+
|
|
58
|
+
-- input past the last argument is rejected, including by commands that
|
|
59
|
+
-- take no arguments at all
|
|
60
|
+
for j = cmd_index + next_index, #tokens do
|
|
61
|
+
add(tokens[j], "invalid")
|
|
62
|
+
end
|
|
63
|
+
|
|
64
|
+
if resolution.unclosed_quote and #highlights > 0 then
|
|
65
|
+
highlights[#highlights].kind = "invalid"
|
|
66
|
+
end
|
|
67
|
+
|
|
68
|
+
return highlights
|
|
69
|
+
end
|
|
70
|
+
|
|
71
|
+
local function highlight(text: string, theme: types.Theme): string
|
|
72
|
+
local colors: { [Kind]: Color3 } = {
|
|
73
|
+
command = theme.highlight,
|
|
74
|
+
argument = theme.text,
|
|
75
|
+
string = theme.success,
|
|
76
|
+
invalid = theme.error,
|
|
77
|
+
}
|
|
78
|
+
|
|
79
|
+
local out = {}
|
|
80
|
+
local pos = 1
|
|
81
|
+
for _, item in classify(text) do
|
|
82
|
+
table.insert(out, rich.escape(text:sub(pos, item.span.x - 1)))
|
|
83
|
+
table.insert(out, rich.color(rich.escape(text:sub(item.span.x, item.span.y)), colors[item.kind]))
|
|
84
|
+
pos = item.span.y + 1
|
|
85
|
+
end
|
|
86
|
+
table.insert(out, rich.escape(text:sub(pos)))
|
|
87
|
+
|
|
88
|
+
return table.concat(out)
|
|
89
|
+
end
|
|
90
|
+
|
|
91
|
+
return highlight
|
|
@@ -0,0 +1,318 @@
|
|
|
1
|
+
--!strict
|
|
2
|
+
local Players = game:GetService("Players")
|
|
3
|
+
|
|
4
|
+
local centurion = require("../centurion")
|
|
5
|
+
local cursor = require("@self/cursor")
|
|
6
|
+
local match = require("@self/match")
|
|
7
|
+
local suggest = require("@self/suggest")
|
|
8
|
+
local types = require("./types")
|
|
9
|
+
|
|
10
|
+
local function analyse(text: string, cursor_pos: number, max_suggestions: number?): types.AnalysisResult
|
|
11
|
+
local resolution = centurion.resolve_command(text)
|
|
12
|
+
local tokens = resolution.tokens
|
|
13
|
+
|
|
14
|
+
if #tokens == 0 then
|
|
15
|
+
return {
|
|
16
|
+
result = {
|
|
17
|
+
success = true,
|
|
18
|
+
value = nil :: any,
|
|
19
|
+
},
|
|
20
|
+
suggestions = {},
|
|
21
|
+
}
|
|
22
|
+
end
|
|
23
|
+
|
|
24
|
+
local cmds = centurion.registry.commands
|
|
25
|
+
|
|
26
|
+
local cmd_name: string? = resolution.name
|
|
27
|
+
local cmd_index: number? = cmd_name and resolution.name_end or nil
|
|
28
|
+
|
|
29
|
+
local arg_tokens: { centurion.Token } = cmd_index and { select(2, unpack(tokens, cmd_index)) } or {}
|
|
30
|
+
|
|
31
|
+
local at_arg = false
|
|
32
|
+
if cmd_index and cursor_pos - 1 > tokens[cmd_index].span.y then
|
|
33
|
+
at_arg = true
|
|
34
|
+
end
|
|
35
|
+
|
|
36
|
+
local slot, inserting = cursor.locate(arg_tokens, cursor_pos)
|
|
37
|
+
|
|
38
|
+
local function process_arg(): types.AnalysisResult?
|
|
39
|
+
if not cmd_name then
|
|
40
|
+
return nil
|
|
41
|
+
end
|
|
42
|
+
|
|
43
|
+
if not cmd_index then
|
|
44
|
+
return nil
|
|
45
|
+
end
|
|
46
|
+
|
|
47
|
+
local cmd = cmds[cmd_name]
|
|
48
|
+
if not cmd then
|
|
49
|
+
return nil
|
|
50
|
+
end
|
|
51
|
+
|
|
52
|
+
local cmd_args: { centurion.Argument } = if cmd.arguments
|
|
53
|
+
then { cmd.arguments() } :: { centurion.Argument }
|
|
54
|
+
else {}
|
|
55
|
+
local arg_text = resolution.args
|
|
56
|
+
|
|
57
|
+
-- commands taking no arguments have nothing to suggest, but still
|
|
58
|
+
-- reject extra input
|
|
59
|
+
if #cmd_args == 0 then
|
|
60
|
+
if #arg_tokens == 0 then
|
|
61
|
+
return nil
|
|
62
|
+
end
|
|
63
|
+
|
|
64
|
+
return {
|
|
65
|
+
result = centurion.transform_args(Players.LocalPlayer, arg_text, cmd_args),
|
|
66
|
+
suggestions = {},
|
|
67
|
+
}
|
|
68
|
+
end
|
|
69
|
+
|
|
70
|
+
local transform_result = centurion.transform_args(Players.LocalPlayer, arg_text, cmd_args)
|
|
71
|
+
|
|
72
|
+
if not at_arg then
|
|
73
|
+
return {
|
|
74
|
+
result = transform_result,
|
|
75
|
+
suggestions = {},
|
|
76
|
+
}
|
|
77
|
+
end
|
|
78
|
+
|
|
79
|
+
-- assign tokens to arguments the same way execution does, counting the
|
|
80
|
+
-- token about to be typed if the cursor isn't on an existing one
|
|
81
|
+
local count = if inserting then #arg_tokens + 1 else #arg_tokens
|
|
82
|
+
local ranges = centurion.assign_args(cmd_args, count)
|
|
83
|
+
|
|
84
|
+
local arg_index: number? = nil
|
|
85
|
+
for i, range in ranges do
|
|
86
|
+
if slot >= range.first and slot <= range.last then
|
|
87
|
+
arg_index = i
|
|
88
|
+
break
|
|
89
|
+
end
|
|
90
|
+
end
|
|
91
|
+
|
|
92
|
+
-- past the last argument, which execution rejects
|
|
93
|
+
if not arg_index then
|
|
94
|
+
return {
|
|
95
|
+
result = transform_result,
|
|
96
|
+
suggestions = {},
|
|
97
|
+
}
|
|
98
|
+
end
|
|
99
|
+
|
|
100
|
+
local arg = cmd_args[arg_index]
|
|
101
|
+
local arg_type = centurion.registry.types[arg.type]
|
|
102
|
+
assert(arg_type, "Unknown argument type: " .. tostring(arg.type))
|
|
103
|
+
|
|
104
|
+
local input_token: centurion.Token
|
|
105
|
+
if inserting then
|
|
106
|
+
-- an empty span at the cursor, so a suggestion is inserted there
|
|
107
|
+
input_token = {
|
|
108
|
+
span = vector.create(cursor_pos, cursor_pos - 1, 0),
|
|
109
|
+
text = "",
|
|
110
|
+
quoted = false,
|
|
111
|
+
}
|
|
112
|
+
else
|
|
113
|
+
input_token = arg_tokens[slot]
|
|
114
|
+
end
|
|
115
|
+
|
|
116
|
+
if arg_type.kind == "list" then
|
|
117
|
+
local parsed_token = centurion.parse_text(text:sub(input_token.span.x, input_token.span.y), ",")
|
|
118
|
+
|
|
119
|
+
local at_list_token = false
|
|
120
|
+
for _, t in parsed_token.tokens do
|
|
121
|
+
if t.span.y + input_token.span.x - 1 >= cursor_pos - 1 then
|
|
122
|
+
at_list_token = true
|
|
123
|
+
input_token = {
|
|
124
|
+
span = vector.create(t.span.x + input_token.span.x - 1, t.span.y + input_token.span.x - 1, 0),
|
|
125
|
+
text = t.text,
|
|
126
|
+
quoted = t.quoted,
|
|
127
|
+
}
|
|
128
|
+
break
|
|
129
|
+
end
|
|
130
|
+
end
|
|
131
|
+
|
|
132
|
+
if not at_list_token and #parsed_token.tokens > 0 then
|
|
133
|
+
local last_token = parsed_token.tokens[#parsed_token.tokens]
|
|
134
|
+
input_token = {
|
|
135
|
+
span = vector.create(
|
|
136
|
+
last_token.span.y + input_token.span.x + 1,
|
|
137
|
+
last_token.span.y + input_token.span.x + 1,
|
|
138
|
+
0
|
|
139
|
+
),
|
|
140
|
+
text = "",
|
|
141
|
+
quoted = false,
|
|
142
|
+
}
|
|
143
|
+
end
|
|
144
|
+
end
|
|
145
|
+
|
|
146
|
+
local arg_suggestions: { string } = {}
|
|
147
|
+
if arg_type.suggestions then
|
|
148
|
+
arg_suggestions = table.clone(arg_type.suggestions(input_token.text, Players.LocalPlayer))
|
|
149
|
+
end
|
|
150
|
+
|
|
151
|
+
if arg.suggestions then
|
|
152
|
+
for _, suggestion in arg.suggestions do
|
|
153
|
+
table.insert(arg_suggestions, suggestion)
|
|
154
|
+
end
|
|
155
|
+
end
|
|
156
|
+
|
|
157
|
+
-- check for duplicates
|
|
158
|
+
local seen = {}
|
|
159
|
+
for i = #arg_suggestions, 1, -1 do
|
|
160
|
+
local suggestion = arg_suggestions[i]
|
|
161
|
+
if seen[suggestion] then
|
|
162
|
+
table.remove(arg_suggestions, i)
|
|
163
|
+
else
|
|
164
|
+
seen[suggestion] = true
|
|
165
|
+
end
|
|
166
|
+
end
|
|
167
|
+
|
|
168
|
+
local suggestions = {}
|
|
169
|
+
if #arg_suggestions > 0 then
|
|
170
|
+
local matches = suggest(input_token.text, arg_suggestions, max_suggestions)
|
|
171
|
+
|
|
172
|
+
for _, suggestion_text in matches do
|
|
173
|
+
local replacement = ""
|
|
174
|
+
local replacement_text = {}
|
|
175
|
+
for _, replacement_token in centurion.parse_text(suggestion_text, " ").tokens do
|
|
176
|
+
table.insert(replacement_text, replacement_token.text)
|
|
177
|
+
end
|
|
178
|
+
|
|
179
|
+
if #replacement_text == 1 then
|
|
180
|
+
replacement = replacement_text[1]
|
|
181
|
+
else
|
|
182
|
+
replacement = '"' .. table.concat(replacement_text, " ") .. '"'
|
|
183
|
+
end
|
|
184
|
+
|
|
185
|
+
local suggestion = {
|
|
186
|
+
kind = "argument",
|
|
187
|
+
name = suggestion_text,
|
|
188
|
+
matches = match(input_token.text, suggestion_text),
|
|
189
|
+
|
|
190
|
+
span = input_token.span,
|
|
191
|
+
replace = replacement,
|
|
192
|
+
} :: types.AnalysisSuggestion
|
|
193
|
+
table.insert(suggestions, suggestion)
|
|
194
|
+
end
|
|
195
|
+
end
|
|
196
|
+
|
|
197
|
+
return {
|
|
198
|
+
result = transform_result,
|
|
199
|
+
argument = {
|
|
200
|
+
index = arg_index,
|
|
201
|
+
type = arg.type,
|
|
202
|
+
kind = arg_type.kind,
|
|
203
|
+
},
|
|
204
|
+
suggestions = suggestions,
|
|
205
|
+
} :: types.AnalysisResult
|
|
206
|
+
end
|
|
207
|
+
|
|
208
|
+
local function process_command(): types.AnalysisResult
|
|
209
|
+
local to_match = {}
|
|
210
|
+
for k, _ in cmds do
|
|
211
|
+
if k == cmd_name then
|
|
212
|
+
continue
|
|
213
|
+
end
|
|
214
|
+
table.insert(to_match, k)
|
|
215
|
+
end
|
|
216
|
+
|
|
217
|
+
local token_text = {}
|
|
218
|
+
for _, token in tokens do
|
|
219
|
+
table.insert(token_text, token.text)
|
|
220
|
+
end
|
|
221
|
+
|
|
222
|
+
local query = table.concat(token_text, " ")
|
|
223
|
+
local matches = suggest(query, to_match, max_suggestions)
|
|
224
|
+
if cmd_name then
|
|
225
|
+
table.insert(matches, 1, cmd_name)
|
|
226
|
+
end
|
|
227
|
+
|
|
228
|
+
local result: centurion.Result<unknown>
|
|
229
|
+
if cmd_name then
|
|
230
|
+
result = {
|
|
231
|
+
success = true,
|
|
232
|
+
value = nil :: any,
|
|
233
|
+
}
|
|
234
|
+
else
|
|
235
|
+
result = {
|
|
236
|
+
success = false :: false,
|
|
237
|
+
error = "Unknown command",
|
|
238
|
+
}
|
|
239
|
+
end
|
|
240
|
+
|
|
241
|
+
local suggestions = {}
|
|
242
|
+
for _, cmd_match in matches do
|
|
243
|
+
local suggestion = {
|
|
244
|
+
kind = "command",
|
|
245
|
+
name = cmd_match,
|
|
246
|
+
description = cmds[cmd_match].description,
|
|
247
|
+
matches = match(query, cmd_match),
|
|
248
|
+
|
|
249
|
+
span = vector.create(1, tokens[#tokens].span.y, 0),
|
|
250
|
+
replace = cmd_match,
|
|
251
|
+
} :: types.AnalysisSuggestion
|
|
252
|
+
table.insert(suggestions, suggestion)
|
|
253
|
+
end
|
|
254
|
+
|
|
255
|
+
return {
|
|
256
|
+
result = result,
|
|
257
|
+
suggestions = suggestions,
|
|
258
|
+
} :: types.AnalysisResult
|
|
259
|
+
end
|
|
260
|
+
|
|
261
|
+
local analysis_result: centurion.Result<unknown>
|
|
262
|
+
|
|
263
|
+
local arg_result = process_arg()
|
|
264
|
+
local cmd_result = process_command()
|
|
265
|
+
if resolution.unclosed_quote then
|
|
266
|
+
analysis_result = {
|
|
267
|
+
success = false,
|
|
268
|
+
error = "Unclosed quote at position " .. tokens[#tokens].span.x,
|
|
269
|
+
}
|
|
270
|
+
else
|
|
271
|
+
if arg_result then
|
|
272
|
+
analysis_result = arg_result.result
|
|
273
|
+
else
|
|
274
|
+
analysis_result = cmd_result.result
|
|
275
|
+
end
|
|
276
|
+
end
|
|
277
|
+
|
|
278
|
+
local final_suggestions = cmd_result.suggestions
|
|
279
|
+
if at_arg and arg_result then
|
|
280
|
+
final_suggestions = arg_result.suggestions
|
|
281
|
+
|
|
282
|
+
-- a subcommand shares the position of its parent's first argument, so
|
|
283
|
+
-- both sets are offered there instead of the argument replacing them
|
|
284
|
+
if cmd_name and slot == 1 then
|
|
285
|
+
local prefix = cmd_name .. " "
|
|
286
|
+
local merged: { types.AnalysisSuggestion } = {}
|
|
287
|
+
|
|
288
|
+
for _, suggestion in cmd_result.suggestions do
|
|
289
|
+
if suggestion.name ~= cmd_name and suggestion.name:sub(1, #prefix) == prefix then
|
|
290
|
+
table.insert(merged, suggestion)
|
|
291
|
+
end
|
|
292
|
+
end
|
|
293
|
+
|
|
294
|
+
if #merged > 0 then
|
|
295
|
+
for _, suggestion in final_suggestions do
|
|
296
|
+
table.insert(merged, suggestion)
|
|
297
|
+
end
|
|
298
|
+
|
|
299
|
+
if max_suggestions then
|
|
300
|
+
for i = #merged, max_suggestions + 1, -1 do
|
|
301
|
+
merged[i] = nil
|
|
302
|
+
end
|
|
303
|
+
end
|
|
304
|
+
|
|
305
|
+
final_suggestions = merged
|
|
306
|
+
end
|
|
307
|
+
end
|
|
308
|
+
end
|
|
309
|
+
|
|
310
|
+
return {
|
|
311
|
+
result = analysis_result,
|
|
312
|
+
command = cmd_name,
|
|
313
|
+
argument = arg_result and arg_result.argument or nil,
|
|
314
|
+
suggestions = final_suggestions,
|
|
315
|
+
}
|
|
316
|
+
end
|
|
317
|
+
|
|
318
|
+
return analyse
|