@gajae-code/tui 0.3.2 → 0.4.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/CHANGELOG.md +6 -0
- package/dist/types/autocomplete.d.ts +1 -0
- package/package.json +3 -3
- package/src/autocomplete.ts +59 -8
package/CHANGELOG.md
CHANGED
|
@@ -2,6 +2,12 @@
|
|
|
2
2
|
|
|
3
3
|
## [Unreleased]
|
|
4
4
|
|
|
5
|
+
## [0.4.0] - 2026-06-06
|
|
6
|
+
|
|
7
|
+
### Changed
|
|
8
|
+
|
|
9
|
+
- Ranked slash command-name suggestions by stronger prefix/token matches before fuzzy fallback ordering, while keeping priority as an intra-tier tie-breaker.
|
|
10
|
+
|
|
5
11
|
## [0.3.1] - 2026-06-05
|
|
6
12
|
|
|
7
13
|
### Changed
|
package/package.json
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
{
|
|
2
2
|
"type": "module",
|
|
3
3
|
"name": "@gajae-code/tui",
|
|
4
|
-
"version": "0.
|
|
4
|
+
"version": "0.4.0",
|
|
5
5
|
"description": "Terminal User Interface library with differential rendering for efficient text-based applications",
|
|
6
6
|
"homepage": "https://gaebal-gajae.dev",
|
|
7
7
|
"author": "Yeachan-Heo",
|
|
@@ -38,8 +38,8 @@
|
|
|
38
38
|
"fmt": "biome format --write ."
|
|
39
39
|
},
|
|
40
40
|
"dependencies": {
|
|
41
|
-
"@gajae-code/natives": "0.
|
|
42
|
-
"@gajae-code/utils": "0.
|
|
41
|
+
"@gajae-code/natives": "0.4.0",
|
|
42
|
+
"@gajae-code/utils": "0.4.0",
|
|
43
43
|
"lru-cache": "11.3.6",
|
|
44
44
|
"marked": "^18.0.3"
|
|
45
45
|
},
|
package/src/autocomplete.ts
CHANGED
|
@@ -147,6 +147,48 @@ function fuzzyScore(query: string, target: string): number {
|
|
|
147
147
|
// Base score 40 for subsequence, minus penalty for gaps
|
|
148
148
|
return Math.max(1, 40 - gaps * 5);
|
|
149
149
|
}
|
|
150
|
+
export function getSlashCommandMatchRank(query: string, commandName: string): number {
|
|
151
|
+
const normalizedQuery = normalizeSlashCommandText(query);
|
|
152
|
+
if (normalizedQuery.length === 0) return 4;
|
|
153
|
+
|
|
154
|
+
const normalizedName = normalizeSlashCommandText(commandName);
|
|
155
|
+
if (commandName.toLowerCase().startsWith(query.toLowerCase()) || normalizedName.startsWith(normalizedQuery)) {
|
|
156
|
+
return 0;
|
|
157
|
+
}
|
|
158
|
+
|
|
159
|
+
const queryTokens = normalizedQuery.split(" ").filter(Boolean);
|
|
160
|
+
const nameTokens = normalizedName.split(" ").filter(Boolean);
|
|
161
|
+
if (queryTokens.length === 1 && nameTokens.includes(queryTokens[0]!)) {
|
|
162
|
+
return 1;
|
|
163
|
+
}
|
|
164
|
+
|
|
165
|
+
if (
|
|
166
|
+
queryTokens.length > 1 &&
|
|
167
|
+
queryTokens.every((token, index) => {
|
|
168
|
+
const nameToken = nameTokens[index];
|
|
169
|
+
return nameToken ? nameToken.startsWith(token) : false;
|
|
170
|
+
})
|
|
171
|
+
) {
|
|
172
|
+
return 2;
|
|
173
|
+
}
|
|
174
|
+
|
|
175
|
+
if (
|
|
176
|
+
queryTokens.length === 1 &&
|
|
177
|
+
nameTokens.some(token => token.startsWith(queryTokens[0]!) || token.includes(queryTokens[0]!))
|
|
178
|
+
) {
|
|
179
|
+
return 3;
|
|
180
|
+
}
|
|
181
|
+
|
|
182
|
+
return 4;
|
|
183
|
+
}
|
|
184
|
+
|
|
185
|
+
function normalizeSlashCommandText(value: string): string {
|
|
186
|
+
return value
|
|
187
|
+
.toLowerCase()
|
|
188
|
+
.replace(/[^a-z0-9]+/g, " ")
|
|
189
|
+
.trim()
|
|
190
|
+
.replace(/\s+/g, " ");
|
|
191
|
+
}
|
|
150
192
|
|
|
151
193
|
export interface AutocompleteItem {
|
|
152
194
|
value: string;
|
|
@@ -273,12 +315,13 @@ export class CombinedAutocompleteProvider implements AutocompleteProvider {
|
|
|
273
315
|
.filter(cmd => {
|
|
274
316
|
const name = "name" in cmd ? cmd.name : cmd.value;
|
|
275
317
|
if (!name) return false;
|
|
276
|
-
// Match name or description
|
|
318
|
+
// Match name, normalized slash-name aliases, or description.
|
|
277
319
|
if (fuzzyMatch(lowerPrefix, name.toLowerCase())) return true;
|
|
320
|
+
if (getSlashCommandMatchRank(lowerPrefix, name.toLowerCase()) < 4) return true;
|
|
278
321
|
const desc = cmd.description?.toLowerCase();
|
|
279
322
|
return desc ? fuzzyMatch(lowerPrefix, desc) : false;
|
|
280
323
|
})
|
|
281
|
-
.map(cmd => {
|
|
324
|
+
.map((cmd, index) => {
|
|
282
325
|
const name = "name" in cmd ? cmd.name : cmd.value;
|
|
283
326
|
const lowerName = name?.toLowerCase() ?? "";
|
|
284
327
|
const lowerDesc = cmd.description?.toLowerCase() ?? "";
|
|
@@ -294,11 +337,16 @@ export class CombinedAutocompleteProvider implements AutocompleteProvider {
|
|
|
294
337
|
label: "name" in cmd ? cmd.name : cmd.label,
|
|
295
338
|
score: Math.max(nameScore, descScore),
|
|
296
339
|
priority,
|
|
340
|
+
matchRank: getSlashCommandMatchRank(lowerPrefix, lowerName),
|
|
341
|
+
index,
|
|
297
342
|
...(fullDesc && { description: fullDesc }),
|
|
298
343
|
};
|
|
299
344
|
})
|
|
300
|
-
.sort(
|
|
301
|
-
|
|
345
|
+
.sort(
|
|
346
|
+
(a, b) =>
|
|
347
|
+
a.matchRank - b.matchRank || b.priority - a.priority || b.score - a.score || a.index - b.index,
|
|
348
|
+
)
|
|
349
|
+
.map(({ score: _score, priority: _priority, matchRank: _matchRank, index: _index, ...rest }) => rest);
|
|
302
350
|
|
|
303
351
|
if (matches.length === 0) return null;
|
|
304
352
|
|
|
@@ -815,10 +863,11 @@ export class CombinedAutocompleteProvider implements AutocompleteProvider {
|
|
|
815
863
|
const name = "name" in cmd ? cmd.name : cmd.value;
|
|
816
864
|
if (!name) return false;
|
|
817
865
|
if (fuzzyMatch(lowerPrefix, name.toLowerCase())) return true;
|
|
866
|
+
if (getSlashCommandMatchRank(lowerPrefix, name.toLowerCase()) < 4) return true;
|
|
818
867
|
const desc = cmd.description?.toLowerCase();
|
|
819
868
|
return desc ? fuzzyMatch(lowerPrefix, desc) : false;
|
|
820
869
|
})
|
|
821
|
-
.map(cmd => {
|
|
870
|
+
.map((cmd, index) => {
|
|
822
871
|
const name = "name" in cmd ? cmd.name : cmd.value;
|
|
823
872
|
const lowerName = name?.toLowerCase() ?? "";
|
|
824
873
|
const lowerDesc = cmd.description?.toLowerCase() ?? "";
|
|
@@ -833,11 +882,13 @@ export class CombinedAutocompleteProvider implements AutocompleteProvider {
|
|
|
833
882
|
label: "name" in cmd ? cmd.name : cmd.label,
|
|
834
883
|
score: Math.max(nameScore, descScore),
|
|
835
884
|
priority,
|
|
885
|
+
matchRank: getSlashCommandMatchRank(lowerPrefix, lowerName),
|
|
886
|
+
index,
|
|
836
887
|
...(fullDesc && { description: fullDesc }),
|
|
837
|
-
} as AutocompleteItem & { score: number; priority: number };
|
|
888
|
+
} as AutocompleteItem & { score: number; priority: number; matchRank: number; index: number };
|
|
838
889
|
})
|
|
839
|
-
.sort((a, b) => b.priority - a.priority || b.score - a.score)
|
|
840
|
-
.map(({ score: _score, priority: _priority, ...rest }) => rest);
|
|
890
|
+
.sort((a, b) => a.matchRank - b.matchRank || b.priority - a.priority || b.score - a.score || a.index - b.index)
|
|
891
|
+
.map(({ score: _score, priority: _priority, matchRank: _matchRank, index: _index, ...rest }) => rest);
|
|
841
892
|
|
|
842
893
|
if (matches.length === 0) return null;
|
|
843
894
|
return { items: matches, prefix: textBeforeCursor };
|