@trioforge/brigadier 0.0.0-stage → 0.1.0
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/LICENSE +22 -0
- package/README.md +73 -2
- package/out/arguments.d.ts +91 -0
- package/out/arguments.luau +475 -0
- package/out/command-dispatcher.d.ts +38 -0
- package/out/command-dispatcher.luau +394 -0
- package/out/command.d.ts +13 -0
- package/out/command.luau +8 -0
- package/out/context.d.ts +121 -0
- package/out/context.luau +576 -0
- package/out/exceptions.d.ts +118 -0
- package/out/exceptions.luau +415 -0
- package/out/index.d.ts +10 -0
- package/out/init.luau +44 -0
- package/out/java.d.ts +40 -0
- package/out/java.luau +424 -0
- package/out/literal-message.d.ts +9 -0
- package/out/literal-message.luau +29 -0
- package/out/string-reader.d.ts +41 -0
- package/out/string-reader.luau +271 -0
- package/out/suggestion.d.ts +60 -0
- package/out/suggestion.luau +340 -0
- package/out/tree.d.ts +126 -0
- package/out/tree.luau +579 -0
- package/package.json +40 -3
|
@@ -0,0 +1,271 @@
|
|
|
1
|
+
-- Compiled with roblox-ts v3.0.0
|
|
2
|
+
local TS = _G[script]
|
|
3
|
+
local CommandSyntaxException = TS.import(script, script.Parent, "exceptions").CommandSyntaxException
|
|
4
|
+
local _java = TS.import(script, script.Parent, "java")
|
|
5
|
+
local charAt = _java.charAt
|
|
6
|
+
local substring = _java.substring
|
|
7
|
+
local toFloat = _java.toFloat
|
|
8
|
+
local SYNTAX_ESCAPE = "\\"
|
|
9
|
+
local SYNTAX_DOUBLE_QUOTE = '"'
|
|
10
|
+
local SYNTAX_SINGLE_QUOTE = "'"
|
|
11
|
+
local INT_MIN = -2147483648
|
|
12
|
+
local INT_MAX = 2147483647
|
|
13
|
+
-- Character.isWhitespace for the one-byte characters it accepts.
|
|
14
|
+
local function isWhitespace(c)
|
|
15
|
+
local code = string.byte(c)
|
|
16
|
+
return code ~= nil and ((code >= 9 and code <= 13) or (code >= 28 and code <= 32))
|
|
17
|
+
end
|
|
18
|
+
-- Integer.parseInt and Long.parseLong: an optional sign, then decimal digits that fit the type.
|
|
19
|
+
local function parseWholeNumber(text, minDigits, maxDigits)
|
|
20
|
+
local sign, digits = string.match(text, "^([+-]?)(%d+)$")
|
|
21
|
+
if not (type(digits) == "string") then
|
|
22
|
+
return nil
|
|
23
|
+
end
|
|
24
|
+
local significant = (string.match(digits, "^0*(%d-)$"))
|
|
25
|
+
local limit = if sign == "-" then minDigits else maxDigits
|
|
26
|
+
if #significant > #limit or (#significant == #limit and significant > limit) then
|
|
27
|
+
return nil
|
|
28
|
+
end
|
|
29
|
+
-- Adding zero turns "-0" into 0, which is what an int holds.
|
|
30
|
+
return tonumber(text) + 0
|
|
31
|
+
end
|
|
32
|
+
local function parseInt(text)
|
|
33
|
+
local value = parseWholeNumber(text, "2147483648", "2147483647")
|
|
34
|
+
if value == nil or value < INT_MIN or value > INT_MAX then
|
|
35
|
+
return nil
|
|
36
|
+
end
|
|
37
|
+
return value
|
|
38
|
+
end
|
|
39
|
+
local function parseLong(text)
|
|
40
|
+
return parseWholeNumber(text, "9223372036854775808", "9223372036854775807")
|
|
41
|
+
end
|
|
42
|
+
-- Double.parseDouble for what isAllowedNumber lets through: an optional minus, then digits with at most one point.
|
|
43
|
+
local function parseDouble(text)
|
|
44
|
+
local body = string.match(text, "^%-?([%d%.]+)$")
|
|
45
|
+
if not (type(body) == "string") or (string.find(body, "%d")) == nil then
|
|
46
|
+
return nil
|
|
47
|
+
end
|
|
48
|
+
local _, points = string.gsub(body, "%.", "")
|
|
49
|
+
if points > 1 then
|
|
50
|
+
return nil
|
|
51
|
+
end
|
|
52
|
+
return tonumber(text)
|
|
53
|
+
end
|
|
54
|
+
-- Float.parseFloat rounds the decimal straight to a float. Rounding through a double first differs only for
|
|
55
|
+
-- decimals within half a double ulp of the midpoint between two floats.
|
|
56
|
+
local function parseFloat(text)
|
|
57
|
+
local value = parseDouble(text)
|
|
58
|
+
return if value == nil then nil else toFloat(value)
|
|
59
|
+
end
|
|
60
|
+
local StringReader
|
|
61
|
+
do
|
|
62
|
+
StringReader = setmetatable({}, {
|
|
63
|
+
__tostring = function()
|
|
64
|
+
return "StringReader"
|
|
65
|
+
end,
|
|
66
|
+
})
|
|
67
|
+
StringReader.__index = StringReader
|
|
68
|
+
function StringReader.new(...)
|
|
69
|
+
local self = setmetatable({}, StringReader)
|
|
70
|
+
return self:constructor(...) or self
|
|
71
|
+
end
|
|
72
|
+
function StringReader:constructor(other)
|
|
73
|
+
local _other = other
|
|
74
|
+
if type(_other) == "string" then
|
|
75
|
+
self.text = other
|
|
76
|
+
self.cursor = 0
|
|
77
|
+
else
|
|
78
|
+
self.text = other.text
|
|
79
|
+
self.cursor = other.cursor
|
|
80
|
+
end
|
|
81
|
+
end
|
|
82
|
+
function StringReader:isAllowedNumber(c)
|
|
83
|
+
return (c >= "0" and c <= "9") or c == "." or c == "-"
|
|
84
|
+
end
|
|
85
|
+
function StringReader:isQuotedStringStart(c)
|
|
86
|
+
return c == SYNTAX_DOUBLE_QUOTE or c == SYNTAX_SINGLE_QUOTE
|
|
87
|
+
end
|
|
88
|
+
function StringReader:isAllowedInUnquotedString(c)
|
|
89
|
+
return (c >= "0" and c <= "9") or (c >= "A" and c <= "Z") or (c >= "a" and c <= "z") or c == "_" or c == "-" or c == "." or c == "+"
|
|
90
|
+
end
|
|
91
|
+
function StringReader:getString()
|
|
92
|
+
return self.text
|
|
93
|
+
end
|
|
94
|
+
function StringReader:setCursor(cursor)
|
|
95
|
+
self.cursor = cursor
|
|
96
|
+
end
|
|
97
|
+
function StringReader:getRemainingLength()
|
|
98
|
+
return #self.text - self.cursor
|
|
99
|
+
end
|
|
100
|
+
function StringReader:getTotalLength()
|
|
101
|
+
return #self.text
|
|
102
|
+
end
|
|
103
|
+
function StringReader:getCursor()
|
|
104
|
+
return self.cursor
|
|
105
|
+
end
|
|
106
|
+
function StringReader:getRead()
|
|
107
|
+
return substring(self.text, 0, self.cursor)
|
|
108
|
+
end
|
|
109
|
+
function StringReader:getRemaining()
|
|
110
|
+
return substring(self.text, self.cursor)
|
|
111
|
+
end
|
|
112
|
+
function StringReader:canRead(length)
|
|
113
|
+
if length == nil then
|
|
114
|
+
length = 1
|
|
115
|
+
end
|
|
116
|
+
return self.cursor + length <= #self.text
|
|
117
|
+
end
|
|
118
|
+
function StringReader:peek(offset)
|
|
119
|
+
if offset == nil then
|
|
120
|
+
offset = 0
|
|
121
|
+
end
|
|
122
|
+
return charAt(self.text, self.cursor + offset)
|
|
123
|
+
end
|
|
124
|
+
function StringReader:read()
|
|
125
|
+
local c = charAt(self.text, self.cursor)
|
|
126
|
+
self.cursor += 1
|
|
127
|
+
return c
|
|
128
|
+
end
|
|
129
|
+
function StringReader:skip()
|
|
130
|
+
self.cursor += 1
|
|
131
|
+
end
|
|
132
|
+
function StringReader:skipWhitespace()
|
|
133
|
+
while self:canRead() and isWhitespace(self:peek()) do
|
|
134
|
+
self:skip()
|
|
135
|
+
end
|
|
136
|
+
end
|
|
137
|
+
function StringReader:readNumber()
|
|
138
|
+
local start = self.cursor
|
|
139
|
+
while self:canRead() and StringReader:isAllowedNumber(self:peek()) do
|
|
140
|
+
self:skip()
|
|
141
|
+
end
|
|
142
|
+
return substring(self.text, start, self.cursor)
|
|
143
|
+
end
|
|
144
|
+
function StringReader:readInt()
|
|
145
|
+
local start = self.cursor
|
|
146
|
+
local text = self:readNumber()
|
|
147
|
+
if text == "" then
|
|
148
|
+
error(CommandSyntaxException.BUILT_IN_EXCEPTIONS:readerExpectedInt():createWithContext(self))
|
|
149
|
+
end
|
|
150
|
+
local value = parseInt(text)
|
|
151
|
+
if value == nil then
|
|
152
|
+
self.cursor = start
|
|
153
|
+
error(CommandSyntaxException.BUILT_IN_EXCEPTIONS:readerInvalidInt():createWithContext(self, text))
|
|
154
|
+
end
|
|
155
|
+
return value
|
|
156
|
+
end
|
|
157
|
+
function StringReader:readLong()
|
|
158
|
+
local start = self.cursor
|
|
159
|
+
local text = self:readNumber()
|
|
160
|
+
if text == "" then
|
|
161
|
+
error(CommandSyntaxException.BUILT_IN_EXCEPTIONS:readerExpectedLong():createWithContext(self))
|
|
162
|
+
end
|
|
163
|
+
local value = parseLong(text)
|
|
164
|
+
if value == nil then
|
|
165
|
+
self.cursor = start
|
|
166
|
+
error(CommandSyntaxException.BUILT_IN_EXCEPTIONS:readerInvalidLong():createWithContext(self, text))
|
|
167
|
+
end
|
|
168
|
+
return value
|
|
169
|
+
end
|
|
170
|
+
function StringReader:readDouble()
|
|
171
|
+
local start = self.cursor
|
|
172
|
+
local text = self:readNumber()
|
|
173
|
+
if text == "" then
|
|
174
|
+
error(CommandSyntaxException.BUILT_IN_EXCEPTIONS:readerExpectedDouble():createWithContext(self))
|
|
175
|
+
end
|
|
176
|
+
local value = parseDouble(text)
|
|
177
|
+
if value == nil then
|
|
178
|
+
self.cursor = start
|
|
179
|
+
error(CommandSyntaxException.BUILT_IN_EXCEPTIONS:readerInvalidDouble():createWithContext(self, text))
|
|
180
|
+
end
|
|
181
|
+
return value
|
|
182
|
+
end
|
|
183
|
+
function StringReader:readFloat()
|
|
184
|
+
local start = self.cursor
|
|
185
|
+
local text = self:readNumber()
|
|
186
|
+
if text == "" then
|
|
187
|
+
error(CommandSyntaxException.BUILT_IN_EXCEPTIONS:readerExpectedFloat():createWithContext(self))
|
|
188
|
+
end
|
|
189
|
+
local value = parseFloat(text)
|
|
190
|
+
if value == nil then
|
|
191
|
+
self.cursor = start
|
|
192
|
+
error(CommandSyntaxException.BUILT_IN_EXCEPTIONS:readerInvalidFloat():createWithContext(self, text))
|
|
193
|
+
end
|
|
194
|
+
return value
|
|
195
|
+
end
|
|
196
|
+
function StringReader:readUnquotedString()
|
|
197
|
+
local start = self.cursor
|
|
198
|
+
while self:canRead() and StringReader:isAllowedInUnquotedString(self:peek()) do
|
|
199
|
+
self:skip()
|
|
200
|
+
end
|
|
201
|
+
return substring(self.text, start, self.cursor)
|
|
202
|
+
end
|
|
203
|
+
function StringReader:readQuotedString()
|
|
204
|
+
if not self:canRead() then
|
|
205
|
+
return ""
|
|
206
|
+
end
|
|
207
|
+
local nextChar = self:peek()
|
|
208
|
+
if not StringReader:isQuotedStringStart(nextChar) then
|
|
209
|
+
error(CommandSyntaxException.BUILT_IN_EXCEPTIONS:readerExpectedStartOfQuote():createWithContext(self))
|
|
210
|
+
end
|
|
211
|
+
self:skip()
|
|
212
|
+
return self:readStringUntil(nextChar)
|
|
213
|
+
end
|
|
214
|
+
function StringReader:readStringUntil(terminator)
|
|
215
|
+
local result = {}
|
|
216
|
+
local escaped = false
|
|
217
|
+
while self:canRead() do
|
|
218
|
+
local c = self:read()
|
|
219
|
+
if escaped then
|
|
220
|
+
if c == terminator or c == SYNTAX_ESCAPE then
|
|
221
|
+
table.insert(result, c)
|
|
222
|
+
escaped = false
|
|
223
|
+
else
|
|
224
|
+
self:setCursor(self:getCursor() - 1)
|
|
225
|
+
error(CommandSyntaxException.BUILT_IN_EXCEPTIONS:readerInvalidEscape():createWithContext(self, c))
|
|
226
|
+
end
|
|
227
|
+
elseif c == SYNTAX_ESCAPE then
|
|
228
|
+
escaped = true
|
|
229
|
+
elseif c == terminator then
|
|
230
|
+
return table.concat(result, "")
|
|
231
|
+
else
|
|
232
|
+
table.insert(result, c)
|
|
233
|
+
end
|
|
234
|
+
end
|
|
235
|
+
error(CommandSyntaxException.BUILT_IN_EXCEPTIONS:readerExpectedEndOfQuote():createWithContext(self))
|
|
236
|
+
end
|
|
237
|
+
function StringReader:readString()
|
|
238
|
+
if not self:canRead() then
|
|
239
|
+
return ""
|
|
240
|
+
end
|
|
241
|
+
local nextChar = self:peek()
|
|
242
|
+
if StringReader:isQuotedStringStart(nextChar) then
|
|
243
|
+
self:skip()
|
|
244
|
+
return self:readStringUntil(nextChar)
|
|
245
|
+
end
|
|
246
|
+
return self:readUnquotedString()
|
|
247
|
+
end
|
|
248
|
+
function StringReader:readBoolean()
|
|
249
|
+
local start = self.cursor
|
|
250
|
+
local value = self:readString()
|
|
251
|
+
if value == "" then
|
|
252
|
+
error(CommandSyntaxException.BUILT_IN_EXCEPTIONS:readerExpectedBool():createWithContext(self))
|
|
253
|
+
end
|
|
254
|
+
if value == "true" then
|
|
255
|
+
return true
|
|
256
|
+
elseif value == "false" then
|
|
257
|
+
return false
|
|
258
|
+
end
|
|
259
|
+
self.cursor = start
|
|
260
|
+
error(CommandSyntaxException.BUILT_IN_EXCEPTIONS:readerInvalidBool():createWithContext(self, value))
|
|
261
|
+
end
|
|
262
|
+
function StringReader:expect(c)
|
|
263
|
+
if not self:canRead() or self:peek() ~= c then
|
|
264
|
+
error(CommandSyntaxException.BUILT_IN_EXCEPTIONS:readerExpectedSymbol():createWithContext(self, c))
|
|
265
|
+
end
|
|
266
|
+
self:skip()
|
|
267
|
+
end
|
|
268
|
+
end
|
|
269
|
+
return {
|
|
270
|
+
StringReader = StringReader,
|
|
271
|
+
}
|
|
@@ -0,0 +1,60 @@
|
|
|
1
|
+
import { StringRange } from "./context";
|
|
2
|
+
import type { Message } from "./literal-message";
|
|
3
|
+
export declare class Suggestion {
|
|
4
|
+
protected readonly range: StringRange;
|
|
5
|
+
protected readonly text: string;
|
|
6
|
+
protected readonly tooltip?: Message | undefined;
|
|
7
|
+
constructor(range: StringRange, text: string, tooltip?: Message | undefined);
|
|
8
|
+
getRange(): StringRange;
|
|
9
|
+
getText(): string;
|
|
10
|
+
getTooltip(): Message | undefined;
|
|
11
|
+
apply(input: string): string;
|
|
12
|
+
equals(other: unknown): boolean;
|
|
13
|
+
toString(): string;
|
|
14
|
+
compareTo(other: Suggestion): number;
|
|
15
|
+
compareToIgnoreCase(other: Suggestion): number;
|
|
16
|
+
expand(command: string, range: StringRange): Suggestion;
|
|
17
|
+
}
|
|
18
|
+
export declare class IntegerSuggestion extends Suggestion {
|
|
19
|
+
private readonly value;
|
|
20
|
+
constructor(range: StringRange, value: number, tooltip?: Message);
|
|
21
|
+
getValue(): number;
|
|
22
|
+
equals(other: unknown): boolean;
|
|
23
|
+
toString(): string;
|
|
24
|
+
compareTo(other: Suggestion): number;
|
|
25
|
+
compareToIgnoreCase(other: Suggestion): number;
|
|
26
|
+
}
|
|
27
|
+
export declare class Suggestions {
|
|
28
|
+
private readonly range;
|
|
29
|
+
private readonly suggestions;
|
|
30
|
+
private static readonly EMPTY;
|
|
31
|
+
constructor(range: StringRange, suggestions: readonly Suggestion[]);
|
|
32
|
+
static empty(): Promise<Suggestions>;
|
|
33
|
+
static merge(command: string, input: readonly Suggestions[]): Suggestions;
|
|
34
|
+
static create(command: string, suggestions: readonly Suggestion[]): Suggestions;
|
|
35
|
+
getRange(): StringRange;
|
|
36
|
+
getList(): readonly Suggestion[];
|
|
37
|
+
isEmpty(): boolean;
|
|
38
|
+
equals(other: unknown): boolean;
|
|
39
|
+
toString(): string;
|
|
40
|
+
}
|
|
41
|
+
export declare class SuggestionsBuilder {
|
|
42
|
+
private readonly input;
|
|
43
|
+
private readonly inputLowerCase;
|
|
44
|
+
private readonly start;
|
|
45
|
+
private readonly remaining;
|
|
46
|
+
private readonly remainingLowerCase;
|
|
47
|
+
private readonly result;
|
|
48
|
+
constructor(input: string, start: number);
|
|
49
|
+
constructor(input: string, inputLowerCase: string, start: number);
|
|
50
|
+
getInput(): string;
|
|
51
|
+
getStart(): number;
|
|
52
|
+
getRemaining(): string;
|
|
53
|
+
getRemainingLowerCase(): string;
|
|
54
|
+
build(): Suggestions;
|
|
55
|
+
buildFuture(): Promise<Suggestions>;
|
|
56
|
+
suggest(textOrValue: string | number, tooltip?: Message): this;
|
|
57
|
+
add(other: SuggestionsBuilder): this;
|
|
58
|
+
createOffset(start: number): SuggestionsBuilder;
|
|
59
|
+
restart(): SuggestionsBuilder;
|
|
60
|
+
}
|
|
@@ -0,0 +1,340 @@
|
|
|
1
|
+
-- Compiled with roblox-ts v3.0.0
|
|
2
|
+
local TS = _G[script]
|
|
3
|
+
local StringRange = TS.import(script, script.Parent, "context").StringRange
|
|
4
|
+
local _java = TS.import(script, script.Parent, "java")
|
|
5
|
+
local compareStrings = _java.compareStrings
|
|
6
|
+
local formatInteger = _java.formatInteger
|
|
7
|
+
local objectsEquals = _java.objectsEquals
|
|
8
|
+
local stableSort = _java.stableSort
|
|
9
|
+
local substring = _java.substring
|
|
10
|
+
local toLowerCase = _java.toLowerCase
|
|
11
|
+
local function describeTooltip(tooltip)
|
|
12
|
+
return if tooltip == nil then "null" else tostring(tooltip)
|
|
13
|
+
end
|
|
14
|
+
local Suggestion
|
|
15
|
+
do
|
|
16
|
+
Suggestion = setmetatable({}, {
|
|
17
|
+
__tostring = function()
|
|
18
|
+
return "Suggestion"
|
|
19
|
+
end,
|
|
20
|
+
})
|
|
21
|
+
Suggestion.__index = Suggestion
|
|
22
|
+
function Suggestion.new(...)
|
|
23
|
+
local self = setmetatable({}, Suggestion)
|
|
24
|
+
return self:constructor(...) or self
|
|
25
|
+
end
|
|
26
|
+
function Suggestion:constructor(range, text, tooltip)
|
|
27
|
+
self.range = range
|
|
28
|
+
self.text = text
|
|
29
|
+
self.tooltip = tooltip
|
|
30
|
+
end
|
|
31
|
+
function Suggestion:getRange()
|
|
32
|
+
return self.range
|
|
33
|
+
end
|
|
34
|
+
function Suggestion:getText()
|
|
35
|
+
return self.text
|
|
36
|
+
end
|
|
37
|
+
function Suggestion:getTooltip()
|
|
38
|
+
return self.tooltip
|
|
39
|
+
end
|
|
40
|
+
function Suggestion:apply(input)
|
|
41
|
+
if self.range:getStart() == 0 and self.range:getEnd() == #input then
|
|
42
|
+
return self.text
|
|
43
|
+
end
|
|
44
|
+
local result = ""
|
|
45
|
+
if self.range:getStart() > 0 then
|
|
46
|
+
result ..= substring(input, 0, self.range:getStart())
|
|
47
|
+
end
|
|
48
|
+
result ..= self.text
|
|
49
|
+
if self.range:getEnd() < #input then
|
|
50
|
+
result ..= substring(input, self.range:getEnd())
|
|
51
|
+
end
|
|
52
|
+
return result
|
|
53
|
+
end
|
|
54
|
+
function Suggestion:equals(other)
|
|
55
|
+
if rawequal(self, other) then
|
|
56
|
+
return true
|
|
57
|
+
end
|
|
58
|
+
if not (TS.instanceof(other, Suggestion)) then
|
|
59
|
+
return false
|
|
60
|
+
end
|
|
61
|
+
return objectsEquals(self.range, other.range) and objectsEquals(self.text, other.text) and objectsEquals(self.tooltip, other.tooltip)
|
|
62
|
+
end
|
|
63
|
+
function Suggestion:toString()
|
|
64
|
+
return `Suggestion\{range={self.range}, text='{self.text}', tooltip='{describeTooltip(self.tooltip)}'\}`
|
|
65
|
+
end
|
|
66
|
+
function Suggestion:compareTo(other)
|
|
67
|
+
return compareStrings(self.text, other:getText())
|
|
68
|
+
end
|
|
69
|
+
function Suggestion:compareToIgnoreCase(other)
|
|
70
|
+
return compareStrings(string.lower(self.text), string.lower(other:getText()))
|
|
71
|
+
end
|
|
72
|
+
function Suggestion:expand(command, range)
|
|
73
|
+
if range:equals(self.range) then
|
|
74
|
+
return self
|
|
75
|
+
end
|
|
76
|
+
local result = ""
|
|
77
|
+
if range:getStart() < self.range:getStart() then
|
|
78
|
+
result ..= substring(command, range:getStart(), self.range:getStart())
|
|
79
|
+
end
|
|
80
|
+
result ..= self.text
|
|
81
|
+
if range:getEnd() > self.range:getEnd() then
|
|
82
|
+
result ..= substring(command, self.range:getEnd(), range:getEnd())
|
|
83
|
+
end
|
|
84
|
+
return Suggestion.new(range, result, self.tooltip)
|
|
85
|
+
end
|
|
86
|
+
function Suggestion:__tostring()
|
|
87
|
+
return self:toString()
|
|
88
|
+
end
|
|
89
|
+
end
|
|
90
|
+
local IntegerSuggestion
|
|
91
|
+
do
|
|
92
|
+
local super = Suggestion
|
|
93
|
+
IntegerSuggestion = setmetatable({}, {
|
|
94
|
+
__tostring = function()
|
|
95
|
+
return "IntegerSuggestion"
|
|
96
|
+
end,
|
|
97
|
+
__index = super,
|
|
98
|
+
})
|
|
99
|
+
IntegerSuggestion.__index = IntegerSuggestion
|
|
100
|
+
function IntegerSuggestion.new(...)
|
|
101
|
+
local self = setmetatable({}, IntegerSuggestion)
|
|
102
|
+
return self:constructor(...) or self
|
|
103
|
+
end
|
|
104
|
+
function IntegerSuggestion:constructor(range, value, tooltip)
|
|
105
|
+
super.constructor(self, range, formatInteger(value), tooltip)
|
|
106
|
+
self.value = value
|
|
107
|
+
end
|
|
108
|
+
function IntegerSuggestion:getValue()
|
|
109
|
+
return self.value
|
|
110
|
+
end
|
|
111
|
+
function IntegerSuggestion:equals(other)
|
|
112
|
+
if rawequal(self, other) then
|
|
113
|
+
return true
|
|
114
|
+
end
|
|
115
|
+
if not (TS.instanceof(other, IntegerSuggestion)) then
|
|
116
|
+
return false
|
|
117
|
+
end
|
|
118
|
+
return self.value == other.value and super.equals(self, other)
|
|
119
|
+
end
|
|
120
|
+
function IntegerSuggestion:toString()
|
|
121
|
+
return `IntegerSuggestion\{value={formatInteger(self.value)}, range={self.range}, text='{self.text}', tooltip='{describeTooltip(self.tooltip)}'\}`
|
|
122
|
+
end
|
|
123
|
+
function IntegerSuggestion:compareTo(other)
|
|
124
|
+
if TS.instanceof(other, IntegerSuggestion) then
|
|
125
|
+
return if self.value < other.value then -1 elseif self.value == other.value then 0 else 1
|
|
126
|
+
end
|
|
127
|
+
return super.compareTo(self, other)
|
|
128
|
+
end
|
|
129
|
+
function IntegerSuggestion:compareToIgnoreCase(other)
|
|
130
|
+
return self:compareTo(other)
|
|
131
|
+
end
|
|
132
|
+
function IntegerSuggestion:__tostring()
|
|
133
|
+
return self:toString()
|
|
134
|
+
end
|
|
135
|
+
end
|
|
136
|
+
-- A HashSet<Suggestion> keeps the first of each group of equal suggestions. Equal suggestions share their range,
|
|
137
|
+
-- text and class, so those key the buckets that `equals` searches.
|
|
138
|
+
local function addToSet(set, buckets, suggestion)
|
|
139
|
+
local clazz = getmetatable(suggestion)
|
|
140
|
+
local byKey = buckets[clazz]
|
|
141
|
+
if byKey == nil then
|
|
142
|
+
byKey = {}
|
|
143
|
+
local _buckets = buckets
|
|
144
|
+
local _byKey = byKey
|
|
145
|
+
_buckets[clazz] = _byKey
|
|
146
|
+
end
|
|
147
|
+
local range = suggestion:getRange()
|
|
148
|
+
local key = `{range:getStart()}:{range:getEnd()}:{suggestion:getText()}`
|
|
149
|
+
local bucket = byKey[key]
|
|
150
|
+
if bucket == nil then
|
|
151
|
+
bucket = {}
|
|
152
|
+
local _byKey = byKey
|
|
153
|
+
local _bucket = bucket
|
|
154
|
+
_byKey[key] = _bucket
|
|
155
|
+
end
|
|
156
|
+
for _, existing in bucket do
|
|
157
|
+
if existing:equals(suggestion) then
|
|
158
|
+
return nil
|
|
159
|
+
end
|
|
160
|
+
end
|
|
161
|
+
local _bucket = bucket
|
|
162
|
+
local _suggestion = suggestion
|
|
163
|
+
table.insert(_bucket, _suggestion)
|
|
164
|
+
local _set = set
|
|
165
|
+
local _suggestion_1 = suggestion
|
|
166
|
+
table.insert(_set, _suggestion_1)
|
|
167
|
+
end
|
|
168
|
+
local Suggestions
|
|
169
|
+
do
|
|
170
|
+
Suggestions = setmetatable({}, {
|
|
171
|
+
__tostring = function()
|
|
172
|
+
return "Suggestions"
|
|
173
|
+
end,
|
|
174
|
+
})
|
|
175
|
+
Suggestions.__index = Suggestions
|
|
176
|
+
function Suggestions.new(...)
|
|
177
|
+
local self = setmetatable({}, Suggestions)
|
|
178
|
+
return self:constructor(...) or self
|
|
179
|
+
end
|
|
180
|
+
function Suggestions:constructor(range, suggestions)
|
|
181
|
+
self.range = range
|
|
182
|
+
self.suggestions = suggestions
|
|
183
|
+
end
|
|
184
|
+
function Suggestions:empty()
|
|
185
|
+
return TS.Promise.resolve(Suggestions.EMPTY)
|
|
186
|
+
end
|
|
187
|
+
function Suggestions:merge(command, input)
|
|
188
|
+
if #input == 0 then
|
|
189
|
+
return Suggestions.EMPTY
|
|
190
|
+
elseif #input == 1 then
|
|
191
|
+
return input[1]
|
|
192
|
+
end
|
|
193
|
+
local texts = {}
|
|
194
|
+
local buckets = {}
|
|
195
|
+
for _, suggestions in input do
|
|
196
|
+
for _1, suggestion in suggestions:getList() do
|
|
197
|
+
addToSet(texts, buckets, suggestion)
|
|
198
|
+
end
|
|
199
|
+
end
|
|
200
|
+
return Suggestions:create(command, texts)
|
|
201
|
+
end
|
|
202
|
+
function Suggestions:create(command, suggestions)
|
|
203
|
+
if #suggestions == 0 then
|
|
204
|
+
return Suggestions.EMPTY
|
|
205
|
+
end
|
|
206
|
+
local start = math.huge
|
|
207
|
+
local finish = -math.huge
|
|
208
|
+
for _, suggestion in suggestions do
|
|
209
|
+
start = math.min(suggestion:getRange():getStart(), start)
|
|
210
|
+
finish = math.max(suggestion:getRange():getEnd(), finish)
|
|
211
|
+
end
|
|
212
|
+
local range = StringRange.new(start, finish)
|
|
213
|
+
local texts = {}
|
|
214
|
+
local buckets = {}
|
|
215
|
+
for _, suggestion in suggestions do
|
|
216
|
+
addToSet(texts, buckets, suggestion:expand(command, range))
|
|
217
|
+
end
|
|
218
|
+
stableSort(texts, function(a, b)
|
|
219
|
+
return a:compareToIgnoreCase(b)
|
|
220
|
+
end)
|
|
221
|
+
return Suggestions.new(range, texts)
|
|
222
|
+
end
|
|
223
|
+
function Suggestions:getRange()
|
|
224
|
+
return self.range
|
|
225
|
+
end
|
|
226
|
+
function Suggestions:getList()
|
|
227
|
+
return self.suggestions
|
|
228
|
+
end
|
|
229
|
+
function Suggestions:isEmpty()
|
|
230
|
+
return #self.suggestions == 0
|
|
231
|
+
end
|
|
232
|
+
function Suggestions:equals(other)
|
|
233
|
+
if rawequal(self, other) then
|
|
234
|
+
return true
|
|
235
|
+
end
|
|
236
|
+
if not (TS.instanceof(other, Suggestions)) then
|
|
237
|
+
return false
|
|
238
|
+
end
|
|
239
|
+
return objectsEquals(self.range, other.range) and objectsEquals(self.suggestions, other.suggestions)
|
|
240
|
+
end
|
|
241
|
+
function Suggestions:toString()
|
|
242
|
+
local _exp = self.suggestions
|
|
243
|
+
-- ▼ ReadonlyArray.map ▼
|
|
244
|
+
local _newValue = table.create(#_exp)
|
|
245
|
+
local _callback = function(suggestion)
|
|
246
|
+
return tostring(suggestion)
|
|
247
|
+
end
|
|
248
|
+
for _k, _v in _exp do
|
|
249
|
+
_newValue[_k] = _callback(_v, _k - 1, _exp)
|
|
250
|
+
end
|
|
251
|
+
-- ▲ ReadonlyArray.map ▲
|
|
252
|
+
local parts = _newValue
|
|
253
|
+
return `Suggestions\{range={self.range}, suggestions=[{table.concat(parts, ", ")}]\}`
|
|
254
|
+
end
|
|
255
|
+
function Suggestions:__tostring()
|
|
256
|
+
return self:toString()
|
|
257
|
+
end
|
|
258
|
+
Suggestions.EMPTY = Suggestions.new(StringRange:at(0), {})
|
|
259
|
+
end
|
|
260
|
+
local SuggestionsBuilder
|
|
261
|
+
do
|
|
262
|
+
SuggestionsBuilder = setmetatable({}, {
|
|
263
|
+
__tostring = function()
|
|
264
|
+
return "SuggestionsBuilder"
|
|
265
|
+
end,
|
|
266
|
+
})
|
|
267
|
+
SuggestionsBuilder.__index = SuggestionsBuilder
|
|
268
|
+
function SuggestionsBuilder.new(...)
|
|
269
|
+
local self = setmetatable({}, SuggestionsBuilder)
|
|
270
|
+
return self:constructor(...) or self
|
|
271
|
+
end
|
|
272
|
+
function SuggestionsBuilder:constructor(input, inputLowerCaseOrStart, start)
|
|
273
|
+
self.input = input
|
|
274
|
+
self.result = {}
|
|
275
|
+
local _inputLowerCaseOrStart = inputLowerCaseOrStart
|
|
276
|
+
if type(_inputLowerCaseOrStart) == "number" then
|
|
277
|
+
self.inputLowerCase = toLowerCase(input)
|
|
278
|
+
self.start = inputLowerCaseOrStart
|
|
279
|
+
else
|
|
280
|
+
self.inputLowerCase = inputLowerCaseOrStart
|
|
281
|
+
self.start = start
|
|
282
|
+
end
|
|
283
|
+
self.remaining = substring(input, self.start)
|
|
284
|
+
self.remainingLowerCase = substring(self.inputLowerCase, self.start)
|
|
285
|
+
end
|
|
286
|
+
function SuggestionsBuilder:getInput()
|
|
287
|
+
return self.input
|
|
288
|
+
end
|
|
289
|
+
function SuggestionsBuilder:getStart()
|
|
290
|
+
return self.start
|
|
291
|
+
end
|
|
292
|
+
function SuggestionsBuilder:getRemaining()
|
|
293
|
+
return self.remaining
|
|
294
|
+
end
|
|
295
|
+
function SuggestionsBuilder:getRemainingLowerCase()
|
|
296
|
+
return self.remainingLowerCase
|
|
297
|
+
end
|
|
298
|
+
function SuggestionsBuilder:build()
|
|
299
|
+
return Suggestions:create(self.input, self.result)
|
|
300
|
+
end
|
|
301
|
+
function SuggestionsBuilder:buildFuture()
|
|
302
|
+
return TS.Promise.resolve(self:build())
|
|
303
|
+
end
|
|
304
|
+
function SuggestionsBuilder:suggest(textOrValue, tooltip)
|
|
305
|
+
local range = StringRange:between(self.start, #self.input)
|
|
306
|
+
local _textOrValue = textOrValue
|
|
307
|
+
if type(_textOrValue) == "number" then
|
|
308
|
+
local _result = self.result
|
|
309
|
+
local _integerSuggestion = IntegerSuggestion.new(range, textOrValue, tooltip)
|
|
310
|
+
table.insert(_result, _integerSuggestion)
|
|
311
|
+
return self
|
|
312
|
+
end
|
|
313
|
+
if textOrValue == self.remaining then
|
|
314
|
+
return self
|
|
315
|
+
end
|
|
316
|
+
local _result = self.result
|
|
317
|
+
local _suggestion = Suggestion.new(range, textOrValue, tooltip)
|
|
318
|
+
table.insert(_result, _suggestion)
|
|
319
|
+
return self
|
|
320
|
+
end
|
|
321
|
+
function SuggestionsBuilder:add(other)
|
|
322
|
+
for _, suggestion in other.result do
|
|
323
|
+
local _exp = self.result
|
|
324
|
+
table.insert(_exp, suggestion)
|
|
325
|
+
end
|
|
326
|
+
return self
|
|
327
|
+
end
|
|
328
|
+
function SuggestionsBuilder:createOffset(start)
|
|
329
|
+
return SuggestionsBuilder.new(self.input, self.inputLowerCase, start)
|
|
330
|
+
end
|
|
331
|
+
function SuggestionsBuilder:restart()
|
|
332
|
+
return self:createOffset(self.start)
|
|
333
|
+
end
|
|
334
|
+
end
|
|
335
|
+
return {
|
|
336
|
+
Suggestion = Suggestion,
|
|
337
|
+
IntegerSuggestion = IntegerSuggestion,
|
|
338
|
+
Suggestions = Suggestions,
|
|
339
|
+
SuggestionsBuilder = SuggestionsBuilder,
|
|
340
|
+
}
|