@microsoft/inshellisense 0.0.1-rc.3 → 0.0.1-rc.31

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.
@@ -1,224 +0,0 @@
1
- // Copyright (c) Microsoft Corporation.
2
- // Licensed under the MIT License.
3
- import speclist, { diffVersionedCompletions as versionedSpeclist,
4
- // eslint-disable-next-line @typescript-eslint/ban-ts-comment
5
- // @ts-ignore
6
- } from "@withfig/autocomplete/build/index.js";
7
- import { parseCommand } from "./parser.js";
8
- import { getArgDrivenRecommendation, getSubcommandDrivenRecommendation } from "./suggestion.js";
9
- import { buildExecuteShellCommand } from "./utils.js";
10
- // eslint-disable-next-line @typescript-eslint/no-explicit-any -- recursive type, setting as any
11
- const specSet = {};
12
- speclist.forEach((s) => {
13
- let activeSet = specSet;
14
- const specRoutes = s.split("/");
15
- specRoutes.forEach((route, idx) => {
16
- if (idx === specRoutes.length - 1) {
17
- const prefix = versionedSpeclist.includes(s) ? "/index.js" : `.js`;
18
- activeSet[route] = `@withfig/autocomplete/build/${s}${prefix}`;
19
- }
20
- else {
21
- activeSet[route] = activeSet[route] || {};
22
- activeSet = activeSet[route];
23
- }
24
- });
25
- });
26
- const loadedSpecs = {};
27
- const loadSpec = async (cmd) => {
28
- const rootToken = cmd.at(0);
29
- if (!rootToken?.complete) {
30
- return;
31
- }
32
- if (loadedSpecs[rootToken.token]) {
33
- return loadedSpecs[rootToken.token];
34
- }
35
- if (specSet[rootToken.token]) {
36
- const spec = (await import(specSet[rootToken.token])).default;
37
- loadedSpecs[rootToken.token] = spec;
38
- return spec;
39
- }
40
- };
41
- // this load spec function should only be used for `loadSpec` on the fly as it is cacheless
42
- const lazyLoadSpec = async (key) => {
43
- return (await import(`@withfig/autocomplete/build/${key}.js`)).default;
44
- };
45
- // eslint-disable-next-line @typescript-eslint/no-unused-vars -- will be implemented in below TODO
46
- const lazyLoadSpecLocation = async (location) => {
47
- return; //TODO: implement spec location loading
48
- };
49
- export const getSuggestions = async (cmd) => {
50
- const activeCmd = parseCommand(cmd);
51
- const rootToken = activeCmd.at(0);
52
- if (activeCmd.length === 0 || !rootToken?.complete) {
53
- return;
54
- }
55
- const spec = await loadSpec(activeCmd);
56
- if (spec == null)
57
- return;
58
- const subcommand = getSubcommand(spec);
59
- if (subcommand == null)
60
- return;
61
- const result = await runSubcommand(activeCmd.slice(1), subcommand);
62
- if (result == null)
63
- return;
64
- const lastCommand = activeCmd.at(-1);
65
- const charactersToDrop = lastCommand?.complete ? 0 : lastCommand?.token.length ?? 0;
66
- return { ...result, charactersToDrop };
67
- };
68
- const getPersistentOptions = (persistentOptions, options) => {
69
- const persistentOptionNames = new Set(persistentOptions.map((o) => (typeof o.name === "string" ? [o.name] : o.name)).flat());
70
- return persistentOptions.concat((options ?? []).filter((o) => (typeof o.name == "string" ? !persistentOptionNames.has(o.name) : o.name.some((n) => !persistentOptionNames.has(n))) && o.isPersistent === true));
71
- };
72
- // TODO: handle subcommands that are versioned
73
- const getSubcommand = (spec) => {
74
- if (spec == null)
75
- return;
76
- if (typeof spec === "function") {
77
- const potentialSubcommand = spec();
78
- if (Object.prototype.hasOwnProperty.call(potentialSubcommand, "name")) {
79
- return potentialSubcommand;
80
- }
81
- return;
82
- }
83
- return spec;
84
- };
85
- const executeShellCommand = buildExecuteShellCommand(5000);
86
- const genSubcommand = async (command, parentCommand) => {
87
- const subcommandIdx = parentCommand.subcommands?.findIndex((s) => s.name === command);
88
- if (subcommandIdx == null)
89
- return;
90
- const subcommand = parentCommand.subcommands?.at(subcommandIdx);
91
- if (subcommand == null)
92
- return;
93
- // this pulls in the spec from the load spec and overwrites the subcommand in the parent with the loaded spec.
94
- // then it returns the subcommand and clears the loadSpec field so that it doesn't get called again
95
- switch (typeof subcommand.loadSpec) {
96
- case "function": {
97
- const partSpec = await subcommand.loadSpec(command, executeShellCommand);
98
- if (partSpec instanceof Array) {
99
- const locationSpecs = (await Promise.all(partSpec.map((s) => lazyLoadSpecLocation(s)))).filter((s) => s != null);
100
- const subcommands = locationSpecs.map((s) => getSubcommand(s)).filter((s) => s != null);
101
- parentCommand.subcommands[subcommandIdx] = {
102
- ...subcommand,
103
- ...(subcommands.find((s) => s?.name == command) ?? []),
104
- loadSpec: undefined,
105
- };
106
- return parentCommand.subcommands[subcommandIdx];
107
- }
108
- else if (Object.prototype.hasOwnProperty.call(partSpec, "type")) {
109
- const locationSingleSpec = await lazyLoadSpecLocation(partSpec);
110
- parentCommand.subcommands[subcommandIdx] = {
111
- ...subcommand,
112
- ...(getSubcommand(locationSingleSpec) ?? []),
113
- loadSpec: undefined,
114
- };
115
- return parentCommand.subcommands[subcommandIdx];
116
- }
117
- else {
118
- parentCommand.subcommands[subcommandIdx] = { ...subcommand, ...partSpec, loadSpec: undefined };
119
- return parentCommand.subcommands[subcommandIdx];
120
- }
121
- }
122
- case "string": {
123
- const spec = await lazyLoadSpec(subcommand.loadSpec);
124
- parentCommand.subcommands[subcommandIdx] = { ...subcommand, ...(getSubcommand(spec) ?? []), loadSpec: undefined };
125
- return parentCommand.subcommands[subcommandIdx];
126
- }
127
- case "object": {
128
- parentCommand.subcommands[subcommandIdx] = { ...subcommand, ...(subcommand.loadSpec ?? {}), loadSpec: undefined };
129
- return parentCommand.subcommands[subcommandIdx];
130
- }
131
- case "undefined": {
132
- return subcommand;
133
- }
134
- }
135
- };
136
- const getOption = (activeToken, options) => {
137
- return options.find((o) => (typeof o.name === "string" ? o.name === activeToken.token : o.name.includes(activeToken.token)));
138
- };
139
- const getPersistentTokens = (tokens) => {
140
- return tokens.filter((t) => t.isPersistent === true);
141
- };
142
- const getArgs = (args) => {
143
- return args instanceof Array ? args : args != null ? [args] : [];
144
- };
145
- const runOption = async (tokens, option, subcommand, persistentOptions, acceptedTokens) => {
146
- if (tokens.length === 0) {
147
- throw new Error("invalid state reached, option expected but no tokens found");
148
- }
149
- const activeToken = tokens[0];
150
- const isPersistent = persistentOptions.some((o) => (typeof o.name === "string" ? o.name === activeToken.token : o.name.includes(activeToken.token)));
151
- if ((option.args instanceof Array && option.args.length > 0) || option.args != null) {
152
- const args = option.args instanceof Array ? option.args : [option.args];
153
- return runArg(tokens.slice(1), args, subcommand, persistentOptions, acceptedTokens.concat(activeToken), true, false);
154
- }
155
- return runSubcommand(tokens.slice(1), subcommand, persistentOptions, acceptedTokens.concat({ ...activeToken, isPersistent }));
156
- };
157
- const runArg = async (tokens, args, subcommand, persistentOptions, acceptedTokens, fromOption, fromVariadic) => {
158
- if (args.length === 0) {
159
- return runSubcommand(tokens, subcommand, persistentOptions, acceptedTokens, true, !fromOption);
160
- }
161
- else if (tokens.length === 0) {
162
- return await getArgDrivenRecommendation(args, subcommand, persistentOptions, undefined, acceptedTokens, fromVariadic);
163
- }
164
- else if (!tokens.at(0)?.complete) {
165
- return await getArgDrivenRecommendation(args, subcommand, persistentOptions, tokens[0].token, acceptedTokens, fromVariadic);
166
- }
167
- const activeToken = tokens[0];
168
- if (args.every((a) => a.isOptional)) {
169
- if (activeToken.isOption) {
170
- const option = getOption(activeToken, persistentOptions.concat(subcommand.options ?? []));
171
- if (option != null) {
172
- return runOption(tokens, option, subcommand, persistentOptions, acceptedTokens);
173
- }
174
- return;
175
- }
176
- const nextSubcommand = await genSubcommand(activeToken.token, subcommand);
177
- if (nextSubcommand != null) {
178
- return runSubcommand(tokens.slice(1), nextSubcommand, persistentOptions, getPersistentTokens(acceptedTokens.concat(activeToken)));
179
- }
180
- }
181
- const activeArg = args[0];
182
- if (activeArg.isVariadic) {
183
- return runArg(tokens.slice(1), args, subcommand, persistentOptions, acceptedTokens.concat(activeToken), fromOption, true);
184
- }
185
- else if (activeArg.isCommand) {
186
- if (tokens.length <= 0) {
187
- return;
188
- }
189
- const spec = await loadSpec(tokens);
190
- if (spec == null)
191
- return;
192
- const subcommand = getSubcommand(spec);
193
- if (subcommand == null)
194
- return;
195
- return runSubcommand(tokens.slice(1), subcommand);
196
- }
197
- return runArg(tokens.slice(1), args.slice(1), subcommand, persistentOptions, acceptedTokens.concat(activeToken), fromOption, false);
198
- };
199
- const runSubcommand = async (tokens, subcommand, persistentOptions = [], acceptedTokens = [], argsDepleted = false, argsUsed = false) => {
200
- if (tokens.length === 0) {
201
- return getSubcommandDrivenRecommendation(subcommand, persistentOptions, undefined, argsDepleted, argsUsed, acceptedTokens);
202
- }
203
- else if (!tokens.at(0)?.complete) {
204
- return getSubcommandDrivenRecommendation(subcommand, persistentOptions, tokens[0].token, argsDepleted, argsUsed, acceptedTokens);
205
- }
206
- const activeToken = tokens[0];
207
- const activeArgsLength = subcommand.args instanceof Array ? subcommand.args.length : 1;
208
- const allOptions = [...persistentOptions, ...(subcommand.options ?? [])];
209
- if (activeToken.isOption) {
210
- const option = getOption(activeToken, allOptions);
211
- if (option != null) {
212
- return runOption(tokens, option, subcommand, persistentOptions, acceptedTokens);
213
- }
214
- return;
215
- }
216
- const nextSubcommand = await genSubcommand(activeToken.token, subcommand);
217
- if (nextSubcommand != null) {
218
- return runSubcommand(tokens.slice(1), nextSubcommand, getPersistentOptions(persistentOptions, subcommand.options), getPersistentTokens(acceptedTokens.concat(activeToken)));
219
- }
220
- if (activeArgsLength <= 0) {
221
- return; // not subcommand or option & no args exist
222
- }
223
- return runArg(tokens, getArgs(subcommand.args), subcommand, allOptions, acceptedTokens, false, false);
224
- };
@@ -1,184 +0,0 @@
1
- // Copyright (c) Microsoft Corporation.
2
- // Licensed under the MIT License.
3
- import { runGenerator } from "./generator.js";
4
- import { runTemplates } from "./template.js";
5
- var SuggestionIcons;
6
- (function (SuggestionIcons) {
7
- SuggestionIcons["File"] = "\uD83D\uDCC4";
8
- SuggestionIcons["Folder"] = "\uD83D\uDCC1";
9
- SuggestionIcons["Subcommand"] = "\uD83D\uDCE6";
10
- SuggestionIcons["Option"] = "\uD83D\uDD17";
11
- SuggestionIcons["Argument"] = "\uD83D\uDCB2";
12
- SuggestionIcons["Mixin"] = "\uD83C\uDFDD\uFE0F";
13
- SuggestionIcons["Shortcut"] = "\uD83D\uDD25";
14
- SuggestionIcons["Special"] = "\u2B50";
15
- SuggestionIcons["Default"] = "\uD83D\uDCC0";
16
- })(SuggestionIcons || (SuggestionIcons = {}));
17
- const getIcon = (suggestionType) => {
18
- switch (suggestionType) {
19
- case "arg":
20
- return SuggestionIcons.Argument;
21
- case "file":
22
- return SuggestionIcons.File;
23
- case "folder":
24
- return SuggestionIcons.Folder;
25
- case "option":
26
- return SuggestionIcons.Option;
27
- case "subcommand":
28
- return SuggestionIcons.Subcommand;
29
- case "mixin":
30
- return SuggestionIcons.Mixin;
31
- case "shortcut":
32
- return SuggestionIcons.Shortcut;
33
- case "special":
34
- return SuggestionIcons.Special;
35
- }
36
- return SuggestionIcons.Default;
37
- };
38
- const getLong = (suggestion) => {
39
- return suggestion instanceof Array ? suggestion.reduce((p, c) => (p.length > c.length ? p : c)) : suggestion;
40
- };
41
- const toSuggestion = (suggestion, name, type) => {
42
- if (suggestion.name == null)
43
- return;
44
- return {
45
- name: name ?? getLong(suggestion.name),
46
- description: suggestion.description,
47
- icon: getIcon(type ?? suggestion.type),
48
- allNames: suggestion.name instanceof Array ? suggestion.name : [suggestion.name],
49
- priority: suggestion.priority ?? 50,
50
- insertValue: suggestion.insertValue,
51
- };
52
- };
53
- function filter(suggestions, filterStrategy, partialCmd, suggestionType) {
54
- if (!partialCmd)
55
- return suggestions.map((s) => toSuggestion(s, undefined, suggestionType)).filter((s) => s != null);
56
- switch (filterStrategy) {
57
- case "fuzzy":
58
- return suggestions
59
- .map((s) => {
60
- if (s.name == null)
61
- return;
62
- if (s.name instanceof Array) {
63
- const matchedName = s.name.find((n) => n.toLowerCase().includes(partialCmd.toLowerCase()));
64
- return matchedName != null
65
- ? {
66
- name: matchedName,
67
- description: s.description,
68
- icon: getIcon(s.type ?? suggestionType),
69
- allNames: s.name,
70
- priority: s.priority ?? 50,
71
- insertValue: s.insertValue,
72
- }
73
- : undefined;
74
- }
75
- return s.name.toLowerCase().includes(partialCmd.toLowerCase())
76
- ? {
77
- name: s.name,
78
- description: s.description,
79
- icon: getIcon(s.type ?? suggestionType),
80
- allNames: [s.name],
81
- priority: s.priority ?? 50,
82
- insertValue: s.insertValue,
83
- }
84
- : undefined;
85
- })
86
- .filter((s) => s != null);
87
- default:
88
- return suggestions
89
- .map((s) => {
90
- if (s.name == null)
91
- return;
92
- if (s.name instanceof Array) {
93
- const matchedName = s.name.find((n) => n.toLowerCase().startsWith(partialCmd.toLowerCase()));
94
- return matchedName != null
95
- ? {
96
- name: matchedName,
97
- description: s.description,
98
- icon: getIcon(s.type ?? suggestionType),
99
- allNames: s.name,
100
- insertValue: s.insertValue,
101
- priority: s.priority ?? 50,
102
- }
103
- : undefined;
104
- }
105
- return s.name.toLowerCase().startsWith(partialCmd.toLowerCase())
106
- ? {
107
- name: s.name,
108
- description: s.description,
109
- icon: getIcon(s.type ?? suggestionType),
110
- allNames: [s.name],
111
- insertValue: s.insertValue,
112
- priority: s.priority ?? 50,
113
- }
114
- : undefined;
115
- })
116
- .filter((s) => s != null);
117
- }
118
- }
119
- const generatorSuggestions = async (generator, acceptedTokens, filterStrategy, partialCmd) => {
120
- const generators = generator instanceof Array ? generator : generator ? [generator] : [];
121
- const tokens = acceptedTokens.map((t) => t.token);
122
- const suggestions = (await Promise.all(generators.map((gen) => runGenerator(gen, tokens)))).flat();
123
- return filter(suggestions, filterStrategy, partialCmd, undefined);
124
- };
125
- const templateSuggestions = async (templates, filterStrategy, partialCmd) => {
126
- return filter(await runTemplates(templates ?? []), filterStrategy, partialCmd, undefined);
127
- };
128
- const suggestionSuggestions = (suggestions, filterStrategy, partialCmd) => {
129
- const cleanedSuggestions = suggestions?.map((s) => (typeof s === "string" ? { name: s } : s)) ?? [];
130
- return filter(cleanedSuggestions ?? [], filterStrategy, partialCmd, undefined);
131
- };
132
- const subcommandSuggestions = (subcommands, filterStrategy, partialCmd) => {
133
- return filter(subcommands ?? [], filterStrategy, partialCmd, "subcommand");
134
- };
135
- const optionSuggestions = (options, acceptedTokens, filterStrategy, partialCmd) => {
136
- const usedOptions = new Set(acceptedTokens.filter((t) => t.isOption).map((t) => t.token));
137
- const validOptions = options?.filter((o) => o.exclusiveOn?.every((exclusiveOption) => !usedOptions.has(exclusiveOption)) ?? true);
138
- return filter(validOptions ?? [], filterStrategy, partialCmd, "option");
139
- };
140
- const removeDuplicateSuggestions = (suggestions, acceptedTokens) => {
141
- const seen = new Set(acceptedTokens.map((t) => t.token));
142
- return suggestions.filter((s) => s.allNames.every((n) => !seen.has(n)));
143
- };
144
- const removeEmptySuggestion = (suggestions) => {
145
- return suggestions.filter((s) => s.name.length > 0);
146
- };
147
- export const getSubcommandDrivenRecommendation = async (subcommand, persistentOptions, partialCmd, argsDepleted, argsFromSubcommand, acceptedTokens) => {
148
- if (argsDepleted && argsFromSubcommand) {
149
- return;
150
- }
151
- const suggestions = [];
152
- const argLength = subcommand.args instanceof Array ? subcommand.args.length : subcommand.args ? 1 : 0;
153
- const allOptions = persistentOptions.concat(subcommand.options ?? []);
154
- if (!argsFromSubcommand) {
155
- suggestions.push(...subcommandSuggestions(subcommand.subcommands, subcommand.filterStrategy, partialCmd));
156
- suggestions.push(...optionSuggestions(allOptions, acceptedTokens, subcommand.filterStrategy, partialCmd));
157
- }
158
- if (argLength != 0) {
159
- const activeArg = subcommand.args instanceof Array ? subcommand.args[0] : subcommand.args;
160
- suggestions.push(...(await generatorSuggestions(activeArg?.generators, acceptedTokens, activeArg?.filterStrategy, partialCmd)));
161
- suggestions.push(...suggestionSuggestions(activeArg?.suggestions, activeArg?.filterStrategy, partialCmd));
162
- suggestions.push(...(await templateSuggestions(activeArg?.template, activeArg?.filterStrategy, partialCmd)));
163
- }
164
- return {
165
- suggestions: removeEmptySuggestion(removeDuplicateSuggestions(suggestions.sort((a, b) => b.priority - a.priority), acceptedTokens)),
166
- };
167
- };
168
- export const getArgDrivenRecommendation = async (args, subcommand, persistentOptions, partialCmd, acceptedTokens, variadicArgBound) => {
169
- const activeArg = args[0];
170
- const allOptions = persistentOptions.concat(subcommand.options ?? []);
171
- const suggestions = [
172
- ...(await generatorSuggestions(args[0].generators, acceptedTokens, activeArg?.filterStrategy, partialCmd)),
173
- ...suggestionSuggestions(args[0].suggestions, activeArg?.filterStrategy, partialCmd),
174
- ...(await templateSuggestions(args[0].template, activeArg?.filterStrategy, partialCmd)),
175
- ];
176
- if ((activeArg.isOptional && !activeArg.isVariadic) || (activeArg.isVariadic && activeArg.isOptional && !variadicArgBound)) {
177
- suggestions.push(...subcommandSuggestions(subcommand.subcommands, activeArg?.filterStrategy, partialCmd));
178
- suggestions.push(...optionSuggestions(allOptions, acceptedTokens, activeArg?.filterStrategy, partialCmd));
179
- }
180
- return {
181
- suggestions: removeEmptySuggestion(removeDuplicateSuggestions(suggestions.sort((a, b) => b.priority - a.priority), acceptedTokens)),
182
- argumentDescription: activeArg.description ?? activeArg.name,
183
- };
184
- };
@@ -1,35 +0,0 @@
1
- // Copyright (c) Microsoft Corporation.
2
- // Licensed under the MIT License.
3
- import fsAsync from "fs/promises";
4
- import process from "node:process";
5
- const filepathsTemplate = async () => {
6
- const files = await fsAsync.readdir(process.cwd(), { withFileTypes: true });
7
- return files.filter((f) => f.isFile() || f.isDirectory()).map((f) => ({ name: f.name, priority: 90 }));
8
- };
9
- const foldersTemplate = async () => {
10
- const files = await fsAsync.readdir(process.cwd(), { withFileTypes: true });
11
- return files.filter((f) => f.isDirectory()).map((f) => ({ name: f.name, priority: 90 }));
12
- };
13
- // TODO: implement history template
14
- const historyTemplate = () => {
15
- return [];
16
- };
17
- // TODO: implement help template
18
- const helpTemplate = () => {
19
- return [];
20
- };
21
- export const runTemplates = async (template) => {
22
- const templates = template instanceof Array ? template : [template];
23
- return (await Promise.all(templates.map(async (t) => {
24
- switch (t) {
25
- case "filepaths":
26
- return await filepathsTemplate();
27
- case "folders":
28
- return await foldersTemplate();
29
- case "history":
30
- return historyTemplate();
31
- case "help":
32
- return helpTemplate();
33
- }
34
- }))).flat();
35
- };
@@ -1,22 +0,0 @@
1
- // Copyright (c) Microsoft Corporation.
2
- // Licensed under the MIT License.
3
- import { exec, spawn } from "node:child_process";
4
- export const buildExecuteShellCommand = (timeout) =>
5
- // eslint-disable-next-line @typescript-eslint/no-unused-vars -- TODO: use cwd in the future
6
- async (command, cwd) => {
7
- return new Promise((resolve) => {
8
- exec(command, { timeout }, (_, stdout, stderr) => {
9
- resolve(stdout || stderr);
10
- });
11
- });
12
- };
13
- export const executeShellCommandTTY = async (shell, command) => {
14
- const child = spawn(shell, ["-c", command.trim()], { stdio: "inherit" });
15
- return new Promise((resolve) => {
16
- child.on("close", (code) => {
17
- resolve({
18
- code,
19
- });
20
- });
21
- });
22
- };
package/build/ui/input.js DELETED
@@ -1,55 +0,0 @@
1
- // Copyright (c) Microsoft Corporation.
2
- // Licensed under the MIT License.
3
- import React, { useState, useEffect } from "react";
4
- import { useInput, Text } from "ink";
5
- import chalk from "chalk";
6
- const BlinkSpeed = 530;
7
- const CursorColor = "#FFFFFF";
8
- export default function Input({ value, setValue, prompt, activeSuggestion, tabCompletionDropSize, }) {
9
- const [cursorLocation, setCursorLocation] = useState(value.length);
10
- const [cursorBlink, setCursorBlink] = useState(true);
11
- useEffect(() => {
12
- setTimeout(() => {
13
- setCursorBlink(!cursorBlink);
14
- }, BlinkSpeed);
15
- }, [cursorBlink]);
16
- // TODO: arrow key navigation shortcuts (emacs & vim modes)
17
- useInput((input, key) => {
18
- // TODO: handle delete better on unix systems: https://github.com/vadimdemedes/ink/issues/634
19
- const windows = process.platform === "win32";
20
- const backspaceKey = windows ? key.backspace : key.backspace || key.delete;
21
- const deleteKey = windows ? key.delete : false;
22
- if (backspaceKey) {
23
- setValue([...value].slice(0, Math.max(cursorLocation - 1, 0)).join("") + [...value].slice(cursorLocation).join(""));
24
- setCursorLocation(Math.max(cursorLocation - 1, 0));
25
- }
26
- else if (deleteKey) {
27
- setValue([...value].slice(0, cursorLocation).join("") + [...value].slice(Math.min(value.length, cursorLocation + 1)).join(""));
28
- }
29
- else if (key.leftArrow) {
30
- setCursorLocation(Math.max(cursorLocation - 1, 0));
31
- }
32
- else if (key.rightArrow) {
33
- setCursorLocation(Math.min(cursorLocation + 1, value.length));
34
- }
35
- else if (key.tab) {
36
- if (activeSuggestion) {
37
- // TOOD: support insertValue
38
- const newValue = [...value].slice(0, cursorLocation - tabCompletionDropSize).join("") + activeSuggestion.name + " ";
39
- setValue(newValue);
40
- setCursorLocation(newValue.length);
41
- }
42
- }
43
- else if (input) {
44
- setValue([...value].slice(0, cursorLocation).join("") + input + [...value].slice(cursorLocation).join(""));
45
- setCursorLocation(cursorLocation + input.length);
46
- }
47
- });
48
- const cursoredCommand = value + " ";
49
- const cursoredText = [...cursoredCommand].slice(0, cursorLocation).join("") +
50
- (cursorBlink ? chalk.bgHex(CursorColor).inverse([...cursoredCommand].at(cursorLocation)) : [...cursoredCommand].at(cursorLocation)) +
51
- [...cursoredCommand].slice(cursorLocation + 1).join("");
52
- return (React.createElement(Text, null,
53
- prompt,
54
- cursoredText));
55
- }
@@ -1,84 +0,0 @@
1
- // Copyright (c) Microsoft Corporation.
2
- // Licensed under the MIT License.
3
- import React, { useState, useCallback, useEffect } from "react";
4
- import { Text, useInput, Box, measureElement } from "ink";
5
- import wrapAnsi from "wrap-ansi";
6
- const MaxSuggestions = 5;
7
- const SuggestionWidth = 40;
8
- const DescriptionWidth = 30;
9
- const DescriptionMaxHeight = 6;
10
- const BorderWidth = 2;
11
- const ActiveSuggestionBackgroundColor = "#7D56F4";
12
- function Description({ description }) {
13
- wrapAnsi(description, DescriptionWidth, { hard: true });
14
- if (description.length !== 0) {
15
- return (React.createElement(Box, { flexDirection: "column" },
16
- React.createElement(Box, { borderStyle: "single", width: DescriptionWidth },
17
- React.createElement(Text, null, truncateDescription(description, DescriptionMaxHeight)))));
18
- }
19
- }
20
- function truncateDescription(description, maxHeight) {
21
- const wrappedText = wrapAnsi(description, DescriptionWidth - BorderWidth, {
22
- trim: false,
23
- hard: true,
24
- });
25
- const lines = wrappedText.split("\n");
26
- const truncatedLines = lines.slice(0, maxHeight);
27
- if (lines.length > maxHeight) {
28
- truncatedLines[maxHeight - 1] = [...truncatedLines[maxHeight - 1]].slice(0, -1).join("") + "…";
29
- }
30
- return truncatedLines.join("\n");
31
- }
32
- function SuggestionList({ suggestions, activeSuggestionIdx }) {
33
- return (React.createElement(Box, { flexDirection: "column" },
34
- React.createElement(Box, { borderStyle: "single", width: SuggestionWidth, flexDirection: "column" }, suggestions
35
- .map((suggestion, idx) => {
36
- const bgColor = idx === activeSuggestionIdx ? ActiveSuggestionBackgroundColor : undefined;
37
- return (React.createElement(Box, { key: idx },
38
- React.createElement(Text, { backgroundColor: bgColor, wrap: "truncate-end" }, `${suggestion.icon} ${suggestion.name}`.padEnd(SuggestionWidth - BorderWidth, " "))));
39
- })
40
- .filter((node) => node !== undefined))));
41
- }
42
- export default function Suggestions({ leftPadding, setActiveSuggestion, suggestions, }) {
43
- const [activeSuggestionIndex, setActiveSuggestionIndex] = useState(0);
44
- const [windowWidth, setWindowWidth] = useState(500);
45
- const page = Math.floor(activeSuggestionIndex / MaxSuggestions) + 1;
46
- const pagedSuggestions = suggestions.filter((_, idx) => idx < page * MaxSuggestions && idx >= (page - 1) * MaxSuggestions);
47
- const activePagedSuggestionIndex = activeSuggestionIndex % MaxSuggestions;
48
- const activeDescription = pagedSuggestions.at(activePagedSuggestionIndex)?.description || "";
49
- const wrappedPadding = leftPadding % windowWidth;
50
- const maxPadding = activeDescription.length !== 0 ? windowWidth - SuggestionWidth - DescriptionWidth : windowWidth - SuggestionWidth;
51
- const swapDescription = wrappedPadding > maxPadding;
52
- const swappedPadding = swapDescription ? Math.max(wrappedPadding - DescriptionWidth, 0) : wrappedPadding;
53
- const clampedLeftPadding = Math.min(Math.min(wrappedPadding, swappedPadding), maxPadding);
54
- useEffect(() => {
55
- setActiveSuggestion(suggestions[activeSuggestionIndex]);
56
- }, [activeSuggestionIndex, suggestions]);
57
- useEffect(() => {
58
- if (suggestions.length <= activeSuggestionIndex) {
59
- setActiveSuggestionIndex(Math.max(suggestions.length - 1, 0));
60
- }
61
- }, [suggestions]);
62
- useInput((_, key) => {
63
- if (key.upArrow) {
64
- setActiveSuggestionIndex(Math.max(0, activeSuggestionIndex - 1));
65
- }
66
- if (key.downArrow) {
67
- setActiveSuggestionIndex(Math.min(activeSuggestionIndex + 1, suggestions.length - 1));
68
- }
69
- });
70
- const measureRef = useCallback((node) => {
71
- if (node !== null) {
72
- const { width } = measureElement(node);
73
- setWindowWidth(width);
74
- }
75
- }, []);
76
- if (suggestions.length === 0)
77
- return React.createElement(React.Fragment, null);
78
- return (React.createElement(Box, { ref: measureRef },
79
- React.createElement(Box, { paddingLeft: clampedLeftPadding }, swapDescription ? (React.createElement(React.Fragment, null,
80
- React.createElement(Description, { description: activeDescription }),
81
- React.createElement(SuggestionList, { suggestions: pagedSuggestions, activeSuggestionIdx: activePagedSuggestionIndex }))) : (React.createElement(React.Fragment, null,
82
- React.createElement(SuggestionList, { suggestions: pagedSuggestions, activeSuggestionIdx: activePagedSuggestionIndex }),
83
- React.createElement(Description, { description: activeDescription }))))));
84
- }
@@ -1,69 +0,0 @@
1
- // Copyright (c) Microsoft Corporation.
2
- // Licensed under the MIT License.
3
- import React, { useEffect, useState } from "react";
4
- import { Box, Text, render as inkRender, useInput, useApp } from "ink";
5
- import chalk from "chalk";
6
- import { availableBindings, bind, supportedShells } from "../utils/bindings.js";
7
- let uiResult = "";
8
- function UI() {
9
- const { exit } = useApp();
10
- const [selectionIdx, setSelectionIdx] = useState(0);
11
- const [loaded, setLoaded] = useState(false);
12
- const [availableShells, setAvailableShells] = useState([]);
13
- useEffect(() => {
14
- availableBindings().then((bindings) => {
15
- if (bindings.length == 0) {
16
- exit();
17
- }
18
- setAvailableShells(bindings);
19
- setLoaded(true);
20
- });
21
- }, []);
22
- useInput(async (_, key) => {
23
- if (key.upArrow) {
24
- setSelectionIdx(Math.max(0, selectionIdx - 1));
25
- }
26
- else if (key.downArrow) {
27
- setSelectionIdx(Math.min(availableShells.length - 1, selectionIdx + 1));
28
- }
29
- else if (key.return) {
30
- await bind(availableShells[selectionIdx]);
31
- uiResult = availableShells[selectionIdx];
32
- exit();
33
- }
34
- });
35
- return (React.createElement(Box, { flexDirection: "column" },
36
- React.createElement(Box, null,
37
- React.createElement(Text, { bold: true }, "Select your desired shell for keybinding creation")),
38
- React.createElement(Box, { flexDirection: "column" },
39
- availableShells.map((shell, idx) => {
40
- if (idx == selectionIdx) {
41
- return (React.createElement(Text, { color: "cyan", underline: true, key: idx },
42
- ">",
43
- " ",
44
- shell));
45
- }
46
- return (React.createElement(Text, { key: idx },
47
- " ",
48
- shell));
49
- }),
50
- loaded
51
- ? supportedShells
52
- .filter((s) => !availableShells.includes(s))
53
- .map((shell, idx) => (React.createElement(Text, { color: "gray", key: idx },
54
- " ",
55
- shell,
56
- " (already bound)")))
57
- : null,
58
- !loaded ? React.createElement(Text, null, "Loading...") : null)));
59
- }
60
- export const render = async () => {
61
- const { waitUntilExit } = inkRender(React.createElement(UI, null));
62
- await waitUntilExit();
63
- if (uiResult.length !== 0) {
64
- process.stdout.write("\n" + chalk.green("✓") + " successfully created new bindings \n");
65
- }
66
- else {
67
- process.stdout.write("\n");
68
- }
69
- };