@microsoft/inshellisense 0.0.1-rc.8 → 0.0.1-rc.9
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 +42 -3
- package/build/commands/complete.js +1 -0
- package/build/isterm/commandManager.js +53 -13
- package/build/isterm/pty.js +15 -2
- package/build/runtime/generator.js +7 -5
- package/build/runtime/suggestion.js +13 -7
- package/build/runtime/template.js +2 -2
- package/build/ui/suggestionManager.js +8 -6
- package/build/ui/ui-uninstall.js +3 -3
- package/build/ui/utils.js +3 -1
- package/build/utils/config.js +76 -37
- package/build/utils/shell.js +5 -0
- package/package.json +7 -2
- package/shell/shellIntegration.xsh +29 -0
package/README.md
CHANGED
|
@@ -8,8 +8,7 @@
|
|
|
8
8
|
|
|
9
9
|
### Requirements
|
|
10
10
|
|
|
11
|
-
-
|
|
12
|
-
- node-gyp dependencies installed for your platform (see [node-gyp](https://github.com/nodejs/node-gyp) for more details)
|
|
11
|
+
- Node.js 20.X, 18.X, 16.X (16.6.0 >=)
|
|
13
12
|
|
|
14
13
|
### Installation
|
|
15
14
|
|
|
@@ -31,7 +30,7 @@ After completing the installation, you can run `is` to start the autocomplete se
|
|
|
31
30
|
|
|
32
31
|
#### Keybindings
|
|
33
32
|
|
|
34
|
-
All other keys are passed through to the shell. The keybindings below are only captured when the inshellisense suggestions are visible, otherwise they are passed through to the shell as well.
|
|
33
|
+
All other keys are passed through to the shell. The keybindings below are only captured when the inshellisense suggestions are visible, otherwise they are passed through to the shell as well. These can be customized in the [config](#configuration).
|
|
35
34
|
|
|
36
35
|
| Action | Keybinding |
|
|
37
36
|
| ------------------------- | -------------- |
|
|
@@ -50,6 +49,46 @@ inshellisense supports the following shells:
|
|
|
50
49
|
- [pwsh](https://github.com/PowerShell/PowerShell)
|
|
51
50
|
- [powershell](https://learn.microsoft.com/en-us/powershell/scripting/windows-powershell/starting-windows-powershell) (Windows Powershell)
|
|
52
51
|
- [cmd](https://learn.microsoft.com/en-us/windows-server/administration/windows-commands/cmd) _(experimental)_
|
|
52
|
+
- [xonsh](https://xon.sh/)
|
|
53
|
+
|
|
54
|
+
## Configuration
|
|
55
|
+
|
|
56
|
+
All configuration is done through a [toml](https://toml.io/) file located at `~/.inshellisenserc`. The [JSON schema](https://json-schema.org/) for the configuration file can be found [here](https://github.com/microsoft/inshellisense/blob/main/src/utils/config.ts).
|
|
57
|
+
|
|
58
|
+
### Keybindings
|
|
59
|
+
|
|
60
|
+
You can customize the keybindings for inshellisense by adding a `bindings` section to your config file. The following is the default configuration for the [keybindings](#keybindings):
|
|
61
|
+
|
|
62
|
+
```toml
|
|
63
|
+
[bindings.acceptSuggestion]
|
|
64
|
+
key = "tab"
|
|
65
|
+
# shift and tab are optional and default to false
|
|
66
|
+
shift = false
|
|
67
|
+
ctrl = false
|
|
68
|
+
|
|
69
|
+
[bindings.nextSuggestion]
|
|
70
|
+
key = "down"
|
|
71
|
+
|
|
72
|
+
[bindings.previousSuggestion]
|
|
73
|
+
key = "up"
|
|
74
|
+
|
|
75
|
+
[bindings.dismissSuggestions]
|
|
76
|
+
key = "escape"
|
|
77
|
+
```
|
|
78
|
+
|
|
79
|
+
Key names are matched against the Node.js [keypress](https://nodejs.org/api/readline.html#readlineemitkeypresseventsstream-interface) events.
|
|
80
|
+
|
|
81
|
+
### Custom Prompts (Windows)
|
|
82
|
+
|
|
83
|
+
If you are using a custom prompt in your shell (anything that is not the default PS1), you will need to set up a custom prompt in the inshellisense config file. This is because Windows strips details from your prompt which are required for inshellisense to work. To do this, update your config file in your home directory and add the following configuration:
|
|
84
|
+
|
|
85
|
+
```toml
|
|
86
|
+
[[prompt.bash]]
|
|
87
|
+
regex = "(?<prompt>^>\\s*)" # the prompt match group will be used to detect the prompt
|
|
88
|
+
postfix = ">" # the postfix is the last expected character in your prompt
|
|
89
|
+
```
|
|
90
|
+
|
|
91
|
+
This example adds custom prompt detection for bash where the prompt is expected to be only `> `. You can add similar configurations for other shells as well as well as multiple configurations for each shell.
|
|
53
92
|
|
|
54
93
|
## Contributing
|
|
55
94
|
|
|
@@ -7,6 +7,7 @@ import { Shell } from "../utils/shell.js";
|
|
|
7
7
|
const action = async (input) => {
|
|
8
8
|
const suggestions = await getSuggestions(input, process.cwd(), os.platform() === "win32" ? Shell.Cmd : Shell.Bash);
|
|
9
9
|
process.stdout.write(JSON.stringify(suggestions));
|
|
10
|
+
process.exit(0);
|
|
10
11
|
};
|
|
11
12
|
const cmd = new Command("complete");
|
|
12
13
|
cmd.description(`generates a completion for the provided input`);
|
|
@@ -1,5 +1,6 @@
|
|
|
1
1
|
// Copyright (c) Microsoft Corporation.
|
|
2
2
|
// Licensed under the MIT License.
|
|
3
|
+
import convert from "color-convert";
|
|
3
4
|
import os from "node:os";
|
|
4
5
|
import { Shell } from "../utils/shell.js";
|
|
5
6
|
import log from "../utils/log.js";
|
|
@@ -57,11 +58,13 @@ export class CommandManager {
|
|
|
57
58
|
// User defined prompt
|
|
58
59
|
const inshellisenseConfig = getConfig();
|
|
59
60
|
if (this.#shell == Shell.Bash) {
|
|
60
|
-
if (inshellisenseConfig
|
|
61
|
-
const
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
|
|
61
|
+
if (inshellisenseConfig?.prompt?.bash != null) {
|
|
62
|
+
for (const { regex, postfix } of inshellisenseConfig.prompt.bash) {
|
|
63
|
+
const customPrompt = lineText.match(new RegExp(regex))?.groups?.prompt;
|
|
64
|
+
const adjustedPrompt = this._adjustPrompt(customPrompt, lineText, postfix);
|
|
65
|
+
if (adjustedPrompt) {
|
|
66
|
+
return adjustedPrompt;
|
|
67
|
+
}
|
|
65
68
|
}
|
|
66
69
|
}
|
|
67
70
|
const bashPrompt = lineText.match(/^(?<prompt>.*\$\s?)/)?.groups?.prompt;
|
|
@@ -72,21 +75,50 @@ export class CommandManager {
|
|
|
72
75
|
}
|
|
73
76
|
}
|
|
74
77
|
}
|
|
75
|
-
if (this.#shell == Shell.
|
|
76
|
-
if (inshellisenseConfig
|
|
77
|
-
const
|
|
78
|
-
|
|
78
|
+
if (this.#shell == Shell.Xonsh) {
|
|
79
|
+
if (inshellisenseConfig?.prompt?.xonsh != null) {
|
|
80
|
+
for (const { regex, postfix } of inshellisenseConfig.prompt.xonsh) {
|
|
81
|
+
const customPrompt = lineText.match(new RegExp(regex))?.groups?.prompt;
|
|
82
|
+
const adjustedPrompt = this._adjustPrompt(customPrompt, lineText, postfix);
|
|
83
|
+
if (adjustedPrompt) {
|
|
84
|
+
return adjustedPrompt;
|
|
85
|
+
}
|
|
86
|
+
}
|
|
87
|
+
}
|
|
88
|
+
let xonshPrompt = lineText.match(/(?<prompt>.*@\s?)/)?.groups?.prompt;
|
|
89
|
+
if (xonshPrompt) {
|
|
90
|
+
const adjustedPrompt = this._adjustPrompt(xonshPrompt, lineText, "@");
|
|
79
91
|
if (adjustedPrompt) {
|
|
80
92
|
return adjustedPrompt;
|
|
81
93
|
}
|
|
82
94
|
}
|
|
83
|
-
|
|
84
|
-
|
|
85
|
-
const adjustedPrompt = this._adjustPrompt(
|
|
95
|
+
xonshPrompt = lineText.match(/(?<prompt>.*>\s?)/)?.groups?.prompt;
|
|
96
|
+
if (xonshPrompt) {
|
|
97
|
+
const adjustedPrompt = this._adjustPrompt(xonshPrompt, lineText, ">");
|
|
86
98
|
if (adjustedPrompt) {
|
|
87
99
|
return adjustedPrompt;
|
|
88
100
|
}
|
|
89
101
|
}
|
|
102
|
+
}
|
|
103
|
+
if (this.#shell == Shell.Powershell || this.#shell == Shell.Pwsh) {
|
|
104
|
+
if (inshellisenseConfig?.prompt?.powershell != null) {
|
|
105
|
+
for (const { regex, postfix } of inshellisenseConfig.prompt.powershell) {
|
|
106
|
+
const customPrompt = lineText.match(new RegExp(regex))?.groups?.prompt;
|
|
107
|
+
const adjustedPrompt = this._adjustPrompt(customPrompt, lineText, postfix);
|
|
108
|
+
if (adjustedPrompt) {
|
|
109
|
+
return adjustedPrompt;
|
|
110
|
+
}
|
|
111
|
+
}
|
|
112
|
+
}
|
|
113
|
+
if (inshellisenseConfig?.prompt?.pwsh != null) {
|
|
114
|
+
for (const { regex, postfix } of inshellisenseConfig.prompt.pwsh) {
|
|
115
|
+
const customPrompt = lineText.match(new RegExp(regex))?.groups?.prompt;
|
|
116
|
+
const adjustedPrompt = this._adjustPrompt(customPrompt, lineText, postfix);
|
|
117
|
+
if (adjustedPrompt) {
|
|
118
|
+
return adjustedPrompt;
|
|
119
|
+
}
|
|
120
|
+
}
|
|
121
|
+
}
|
|
90
122
|
const pwshPrompt = lineText.match(/(?<prompt>(\(.+\)\s)?(?:PS.+>\s?))/)?.groups?.prompt;
|
|
91
123
|
if (pwshPrompt) {
|
|
92
124
|
const adjustedPrompt = this._adjustPrompt(pwshPrompt, lineText, ">");
|
|
@@ -117,8 +149,16 @@ export class CommandManager {
|
|
|
117
149
|
}
|
|
118
150
|
return prompt;
|
|
119
151
|
}
|
|
152
|
+
_getFgPaletteColor(cell) {
|
|
153
|
+
if (cell?.isFgDefault())
|
|
154
|
+
return 0;
|
|
155
|
+
if (cell?.isFgPalette())
|
|
156
|
+
return cell.getFgColor();
|
|
157
|
+
if (cell?.isFgRGB())
|
|
158
|
+
return convert.hex.ansi256(cell.getFgColor().toString(16));
|
|
159
|
+
}
|
|
120
160
|
_isSuggestion(cell) {
|
|
121
|
-
const color = cell
|
|
161
|
+
const color = this._getFgPaletteColor(cell);
|
|
122
162
|
const dim = (cell?.isDim() ?? 0) > 0;
|
|
123
163
|
const italic = (cell?.isItalic() ?? 0) > 0;
|
|
124
164
|
const dullColor = color == 8 || color == 7 || (color ?? 0) > 235 || (color == 15 && dim);
|
package/build/isterm/pty.js
CHANGED
|
@@ -5,8 +5,9 @@ import process from "node:process";
|
|
|
5
5
|
import os from "node:os";
|
|
6
6
|
import path from "node:path";
|
|
7
7
|
import url from "node:url";
|
|
8
|
+
import fs from "node:fs";
|
|
8
9
|
import pty from "@homebridge/node-pty-prebuilt-multiarch";
|
|
9
|
-
import { Shell, userZdotdir, zdotdir } from "../utils/shell.js";
|
|
10
|
+
import { Shell, getPythonPath, userZdotdir, zdotdir } from "../utils/shell.js";
|
|
10
11
|
import { IsTermOscPs, IstermOscPt, IstermPromptStart, IstermPromptEnd } from "../utils/ansi.js";
|
|
11
12
|
import xterm from "xterm-headless";
|
|
12
13
|
import { CommandManager } from "./commandManager.js";
|
|
@@ -218,7 +219,7 @@ export const spawn = async (options) => {
|
|
|
218
219
|
};
|
|
219
220
|
const convertToPtyTarget = async (shell) => {
|
|
220
221
|
const platform = os.platform();
|
|
221
|
-
|
|
222
|
+
let shellTarget = shell == Shell.Bash && platform == "win32" ? await gitBashPath() : platform == "win32" ? `${shell}.exe` : shell;
|
|
222
223
|
const shellFolderPath = path.join(path.dirname(url.fileURLToPath(import.meta.url)), "..", "..", "shell");
|
|
223
224
|
let shellArgs = [];
|
|
224
225
|
switch (shell) {
|
|
@@ -232,6 +233,18 @@ const convertToPtyTarget = async (shell) => {
|
|
|
232
233
|
case Shell.Fish:
|
|
233
234
|
shellArgs = ["--init-command", `. ${path.join(shellFolderPath, "shellIntegration.fish").replace(/(\s+)/g, "\\$1")}`];
|
|
234
235
|
break;
|
|
236
|
+
case Shell.Xonsh: {
|
|
237
|
+
const sharedConfig = os.platform() == "win32" ? path.join("C:\\ProgramData", "xonsh", "xonshrc") : path.join("etc", "xonsh", "xonshrc");
|
|
238
|
+
const userConfigs = [
|
|
239
|
+
path.join(os.homedir(), ".xonshrc"),
|
|
240
|
+
path.join(os.homedir(), ".config", "xonsh", "rc.xsh"),
|
|
241
|
+
path.join(os.homedir(), ".config", "xonsh", "rc.d"),
|
|
242
|
+
];
|
|
243
|
+
const configs = [sharedConfig, ...userConfigs].filter((config) => fs.existsSync(config));
|
|
244
|
+
shellArgs = ["-m", "xonsh", "--rc", ...configs, path.join(shellFolderPath, "shellIntegration.xsh")];
|
|
245
|
+
shellTarget = await getPythonPath();
|
|
246
|
+
break;
|
|
247
|
+
}
|
|
235
248
|
}
|
|
236
249
|
return { shellTarget, shellArgs };
|
|
237
250
|
};
|
|
@@ -23,13 +23,14 @@ export const runGenerator = async (generator, tokens, cwd) => {
|
|
|
23
23
|
if (script) {
|
|
24
24
|
const shellInput = typeof script === "function" ? script(tokens) : script;
|
|
25
25
|
const scriptOutput = Array.isArray(shellInput)
|
|
26
|
-
? await executeShellCommand({ command: shellInput.at(0) ?? "", args: shellInput.slice(1) })
|
|
27
|
-
: await executeShellCommand(shellInput);
|
|
26
|
+
? await executeShellCommand({ command: shellInput.at(0) ?? "", args: shellInput.slice(1), cwd })
|
|
27
|
+
: await executeShellCommand({ ...shellInput, cwd });
|
|
28
|
+
const scriptStdout = scriptOutput.stdout.trim();
|
|
28
29
|
if (postProcess) {
|
|
29
|
-
suggestions.push(...postProcess(
|
|
30
|
+
suggestions.push(...postProcess(scriptStdout, tokens));
|
|
30
31
|
}
|
|
31
32
|
else if (splitOn) {
|
|
32
|
-
suggestions.push(...
|
|
33
|
+
suggestions.push(...scriptStdout.split(splitOn).map((s) => ({ name: s })));
|
|
33
34
|
}
|
|
34
35
|
}
|
|
35
36
|
if (custom) {
|
|
@@ -47,7 +48,8 @@ export const runGenerator = async (generator, tokens, cwd) => {
|
|
|
47
48
|
return suggestions;
|
|
48
49
|
}
|
|
49
50
|
catch (e) {
|
|
50
|
-
|
|
51
|
+
const err = typeof e === "string" ? e : e instanceof Error ? e.message : e;
|
|
52
|
+
log.debug({ msg: "generator failed", err, script, splitOn, template });
|
|
51
53
|
}
|
|
52
54
|
return suggestions;
|
|
53
55
|
};
|
|
@@ -15,7 +15,11 @@ var SuggestionIcons;
|
|
|
15
15
|
SuggestionIcons["Special"] = "\u2B50";
|
|
16
16
|
SuggestionIcons["Default"] = "\uD83D\uDCC0";
|
|
17
17
|
})(SuggestionIcons || (SuggestionIcons = {}));
|
|
18
|
-
const getIcon = (suggestionType) => {
|
|
18
|
+
const getIcon = (icon, suggestionType) => {
|
|
19
|
+
// TODO: enable fig icons once spacing is better
|
|
20
|
+
// if (icon && /[^\u0000-\u00ff]/.test(icon)) {
|
|
21
|
+
// return icon;
|
|
22
|
+
// }
|
|
19
23
|
switch (suggestionType) {
|
|
20
24
|
case "arg":
|
|
21
25
|
return SuggestionIcons.Argument;
|
|
@@ -45,7 +49,7 @@ const toSuggestion = (suggestion, name, type) => {
|
|
|
45
49
|
return {
|
|
46
50
|
name: name ?? getLong(suggestion.name),
|
|
47
51
|
description: suggestion.description,
|
|
48
|
-
icon: getIcon(type ?? suggestion.type),
|
|
52
|
+
icon: getIcon(suggestion.icon, type ?? suggestion.type),
|
|
49
53
|
allNames: suggestion.name instanceof Array ? suggestion.name : [suggestion.name],
|
|
50
54
|
priority: suggestion.priority ?? 50,
|
|
51
55
|
insertValue: suggestion.insertValue,
|
|
@@ -66,7 +70,7 @@ function filter(suggestions, filterStrategy, partialCmd, suggestionType) {
|
|
|
66
70
|
? {
|
|
67
71
|
name: matchedName,
|
|
68
72
|
description: s.description,
|
|
69
|
-
icon: getIcon(s.type ?? suggestionType),
|
|
73
|
+
icon: getIcon(s.icon, s.type ?? suggestionType),
|
|
70
74
|
allNames: s.name,
|
|
71
75
|
priority: s.priority ?? 50,
|
|
72
76
|
insertValue: s.insertValue,
|
|
@@ -77,7 +81,7 @@ function filter(suggestions, filterStrategy, partialCmd, suggestionType) {
|
|
|
77
81
|
? {
|
|
78
82
|
name: s.name,
|
|
79
83
|
description: s.description,
|
|
80
|
-
icon: getIcon(s.type ?? suggestionType),
|
|
84
|
+
icon: getIcon(s.icon, s.type ?? suggestionType),
|
|
81
85
|
allNames: [s.name],
|
|
82
86
|
priority: s.priority ?? 50,
|
|
83
87
|
insertValue: s.insertValue,
|
|
@@ -96,7 +100,7 @@ function filter(suggestions, filterStrategy, partialCmd, suggestionType) {
|
|
|
96
100
|
? {
|
|
97
101
|
name: matchedName,
|
|
98
102
|
description: s.description,
|
|
99
|
-
icon: getIcon(s.type ?? suggestionType),
|
|
103
|
+
icon: getIcon(s.icon, s.type ?? suggestionType),
|
|
100
104
|
allNames: s.name,
|
|
101
105
|
insertValue: s.insertValue,
|
|
102
106
|
priority: s.priority ?? 50,
|
|
@@ -107,7 +111,7 @@ function filter(suggestions, filterStrategy, partialCmd, suggestionType) {
|
|
|
107
111
|
? {
|
|
108
112
|
name: s.name,
|
|
109
113
|
description: s.description,
|
|
110
|
-
icon: getIcon(s.type ?? suggestionType),
|
|
114
|
+
icon: getIcon(s.icon, s.type ?? suggestionType),
|
|
111
115
|
allNames: [s.name],
|
|
112
116
|
insertValue: s.insertValue,
|
|
113
117
|
priority: s.priority ?? 50,
|
|
@@ -120,8 +124,10 @@ function filter(suggestions, filterStrategy, partialCmd, suggestionType) {
|
|
|
120
124
|
const generatorSuggestions = async (generator, acceptedTokens, filterStrategy, partialCmd, cwd) => {
|
|
121
125
|
const generators = generator instanceof Array ? generator : generator ? [generator] : [];
|
|
122
126
|
const tokens = acceptedTokens.map((t) => t.token);
|
|
127
|
+
if (partialCmd)
|
|
128
|
+
tokens.push(partialCmd);
|
|
123
129
|
const suggestions = (await Promise.all(generators.map((gen) => runGenerator(gen, tokens, cwd)))).flat();
|
|
124
|
-
return filter(suggestions, filterStrategy, partialCmd, undefined);
|
|
130
|
+
return filter(suggestions.map((suggestion) => ({ ...suggestion, priority: suggestion.priority ?? 60 })), filterStrategy, partialCmd, undefined);
|
|
125
131
|
};
|
|
126
132
|
const templateSuggestions = async (templates, filterStrategy, partialCmd, cwd) => {
|
|
127
133
|
return filter(await runTemplates(templates ?? [], cwd), filterStrategy, partialCmd, undefined);
|
|
@@ -4,11 +4,11 @@ import fsAsync from "node:fs/promises";
|
|
|
4
4
|
import log from "../utils/log.js";
|
|
5
5
|
const filepathsTemplate = async (cwd) => {
|
|
6
6
|
const files = await fsAsync.readdir(cwd, { withFileTypes: true });
|
|
7
|
-
return files.filter((f) => f.isFile() || f.isDirectory()).map((f) => ({ name: f.name, priority:
|
|
7
|
+
return files.filter((f) => f.isFile() || f.isDirectory()).map((f) => ({ name: f.name, priority: 55, context: { templateType: "filepaths" } }));
|
|
8
8
|
};
|
|
9
9
|
const foldersTemplate = async (cwd) => {
|
|
10
10
|
const files = await fsAsync.readdir(cwd, { withFileTypes: true });
|
|
11
|
-
return files.filter((f) => f.isDirectory()).map((f) => ({ name: f.name, priority:
|
|
11
|
+
return files.filter((f) => f.isDirectory()).map((f) => ({ name: f.name, priority: 55, context: { templateType: "folders" } }));
|
|
12
12
|
};
|
|
13
13
|
// TODO: implement history template
|
|
14
14
|
const historyTemplate = () => {
|
|
@@ -5,6 +5,7 @@ import { renderBox, truncateText, truncateMultilineText } from "./utils.js";
|
|
|
5
5
|
import ansi from "ansi-escapes";
|
|
6
6
|
import chalk from "chalk";
|
|
7
7
|
import log from "../utils/log.js";
|
|
8
|
+
import { getConfig } from "../utils/config.js";
|
|
8
9
|
const maxSuggestions = 5;
|
|
9
10
|
const suggestionWidth = 40;
|
|
10
11
|
const descriptionWidth = 30;
|
|
@@ -57,7 +58,7 @@ export class SuggestionManager {
|
|
|
57
58
|
}
|
|
58
59
|
_renderSuggestions(suggestions, activeSuggestionIdx, x) {
|
|
59
60
|
return renderBox(suggestions.map((suggestion, idx) => {
|
|
60
|
-
const suggestionText = `${suggestion.icon} ${suggestion.name}
|
|
61
|
+
const suggestionText = `${suggestion.icon} ${suggestion.name}`;
|
|
61
62
|
const truncatedSuggestion = truncateText(suggestionText, suggestionWidth - 2);
|
|
62
63
|
return idx == activeSuggestionIdx ? chalk.bgHex(activeSuggestionBackgroundColor)(truncatedSuggestion) : truncatedSuggestion;
|
|
63
64
|
}), suggestionWidth, x);
|
|
@@ -120,20 +121,21 @@ export class SuggestionManager {
|
|
|
120
121
|
};
|
|
121
122
|
}
|
|
122
123
|
update(keyPress) {
|
|
123
|
-
const { name } = keyPress;
|
|
124
|
+
const { name, shift, ctrl } = keyPress;
|
|
124
125
|
if (!this.#suggestBlob) {
|
|
125
126
|
return false;
|
|
126
127
|
}
|
|
127
|
-
|
|
128
|
+
const { dismissSuggestions: { key: dismissKey, shift: dismissShift, control: dismissCtrl }, acceptSuggestion: { key: acceptKey, shift: acceptShift, control: acceptCtrl }, nextSuggestion: { key: nextKey, shift: nextShift, control: nextCtrl }, previousSuggestion: { key: prevKey, shift: prevShift, control: prevCtrl }, } = getConfig().bindings;
|
|
129
|
+
if (name == dismissKey && shift == !!dismissShift && ctrl == !!dismissCtrl) {
|
|
128
130
|
this.#suggestBlob = undefined;
|
|
129
131
|
}
|
|
130
|
-
else if (name ==
|
|
132
|
+
else if (name == prevKey && shift == !!prevShift && ctrl == !!prevCtrl) {
|
|
131
133
|
this.#activeSuggestionIdx = Math.max(0, this.#activeSuggestionIdx - 1);
|
|
132
134
|
}
|
|
133
|
-
else if (name ==
|
|
135
|
+
else if (name == nextKey && shift == !!nextShift && ctrl == !!nextCtrl) {
|
|
134
136
|
this.#activeSuggestionIdx = Math.min(this.#activeSuggestionIdx + 1, (this.#suggestBlob?.suggestions.length ?? 1) - 1);
|
|
135
137
|
}
|
|
136
|
-
else if (name ==
|
|
138
|
+
else if (name == acceptKey && shift == !!acceptShift && ctrl == !!acceptCtrl) {
|
|
137
139
|
const removals = "\u007F".repeat(this.#suggestBlob?.charactersToDrop ?? 0);
|
|
138
140
|
const suggestion = this.#suggestBlob?.suggestions.at(this.#activeSuggestionIdx);
|
|
139
141
|
const chars = suggestion?.insertValue ?? suggestion?.name + " ";
|
package/build/ui/ui-uninstall.js
CHANGED
|
@@ -1,9 +1,9 @@
|
|
|
1
1
|
// Copyright (c) Microsoft Corporation.
|
|
2
2
|
// Licensed under the MIT License.
|
|
3
3
|
import chalk from "chalk";
|
|
4
|
-
import {
|
|
4
|
+
import { deleteCacheFolder } from "../utils/config.js";
|
|
5
5
|
export const render = async () => {
|
|
6
|
-
|
|
7
|
-
process.stdout.write(chalk.green("✓") + " successfully deleted the .inshellisense
|
|
6
|
+
deleteCacheFolder();
|
|
7
|
+
process.stdout.write(chalk.green("✓") + " successfully deleted the .inshellisense cache folder \n");
|
|
8
8
|
process.stdout.write(chalk.magenta("•") + " to complete the uninstall, run the the command: " + chalk.underline(chalk.cyan("npm uninstall -g @microsoft/inshellisense")) + "\n");
|
|
9
9
|
};
|
package/build/ui/utils.js
CHANGED
|
@@ -3,6 +3,7 @@
|
|
|
3
3
|
import ansi from "ansi-escapes";
|
|
4
4
|
import wrapAnsi from "wrap-ansi";
|
|
5
5
|
import chalk from "chalk";
|
|
6
|
+
import wcwdith from "wcwidth";
|
|
6
7
|
/**
|
|
7
8
|
* Renders a box around the given rows
|
|
8
9
|
* @param rows the text content to be included in the box, must be <= width - 2
|
|
@@ -36,6 +37,7 @@ export const truncateMultilineText = (description, width, maxHeight) => {
|
|
|
36
37
|
*/
|
|
37
38
|
export const truncateText = (text, width) => {
|
|
38
39
|
const textPoints = [...text];
|
|
39
|
-
const
|
|
40
|
+
const wcOffset = Math.max(wcwdith(text) - textPoints.length, 0);
|
|
41
|
+
const slicedText = textPoints.slice(0, width - 1 - wcOffset);
|
|
40
42
|
return slicedText.length == textPoints.length ? text.padEnd(width) : (slicedText.join("") + "…").padEnd(width);
|
|
41
43
|
};
|
package/build/utils/config.js
CHANGED
|
@@ -4,64 +4,103 @@ import os from "node:os";
|
|
|
4
4
|
import path from "node:path";
|
|
5
5
|
import fs from "node:fs";
|
|
6
6
|
import fsAsync from "node:fs/promises";
|
|
7
|
+
import toml from "toml";
|
|
7
8
|
import _Ajv from "ajv";
|
|
8
9
|
const Ajv = _Ajv;
|
|
9
10
|
const ajv = new Ajv();
|
|
11
|
+
const bindingSchema = {
|
|
12
|
+
type: "object",
|
|
13
|
+
nullable: true,
|
|
14
|
+
properties: {
|
|
15
|
+
shift: { type: "boolean", nullable: true },
|
|
16
|
+
control: { type: "boolean", nullable: true },
|
|
17
|
+
key: { type: "string" },
|
|
18
|
+
},
|
|
19
|
+
required: ["key"],
|
|
20
|
+
};
|
|
21
|
+
const promptPatternsSchema = {
|
|
22
|
+
type: "array",
|
|
23
|
+
nullable: true,
|
|
24
|
+
items: {
|
|
25
|
+
type: "object",
|
|
26
|
+
properties: {
|
|
27
|
+
regex: { type: "string" },
|
|
28
|
+
postfix: { type: "string" },
|
|
29
|
+
},
|
|
30
|
+
required: ["regex", "postfix"],
|
|
31
|
+
},
|
|
32
|
+
};
|
|
10
33
|
const configSchema = {
|
|
11
34
|
type: "object",
|
|
35
|
+
nullable: true,
|
|
12
36
|
properties: {
|
|
13
|
-
|
|
37
|
+
bindings: {
|
|
14
38
|
type: "object",
|
|
39
|
+
nullable: true,
|
|
15
40
|
properties: {
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
regex: { type: "string" },
|
|
21
|
-
postfix: { type: "string" },
|
|
22
|
-
},
|
|
23
|
-
required: ["regex", "postfix"],
|
|
24
|
-
},
|
|
25
|
-
pwsh: {
|
|
26
|
-
type: "object",
|
|
27
|
-
nullable: true,
|
|
28
|
-
properties: {
|
|
29
|
-
regex: { type: "string" },
|
|
30
|
-
postfix: { type: "string" },
|
|
31
|
-
},
|
|
32
|
-
required: ["regex", "postfix"],
|
|
33
|
-
},
|
|
34
|
-
powershell: {
|
|
35
|
-
type: "object",
|
|
36
|
-
nullable: true,
|
|
37
|
-
properties: {
|
|
38
|
-
regex: { type: "string" },
|
|
39
|
-
postfix: { type: "string" },
|
|
40
|
-
},
|
|
41
|
-
required: ["regex", "postfix"],
|
|
42
|
-
},
|
|
41
|
+
nextSuggestion: bindingSchema,
|
|
42
|
+
previousSuggestion: bindingSchema,
|
|
43
|
+
dismissSuggestions: bindingSchema,
|
|
44
|
+
acceptSuggestion: bindingSchema,
|
|
43
45
|
},
|
|
46
|
+
},
|
|
47
|
+
prompt: {
|
|
48
|
+
type: "object",
|
|
44
49
|
nullable: true,
|
|
50
|
+
properties: {
|
|
51
|
+
bash: promptPatternsSchema,
|
|
52
|
+
pwsh: promptPatternsSchema,
|
|
53
|
+
powershell: promptPatternsSchema,
|
|
54
|
+
xonsh: promptPatternsSchema,
|
|
55
|
+
},
|
|
45
56
|
},
|
|
46
57
|
},
|
|
47
58
|
additionalProperties: false,
|
|
48
59
|
};
|
|
49
|
-
const
|
|
50
|
-
const cachePath = path.join(os.homedir(),
|
|
51
|
-
|
|
60
|
+
const configFile = ".inshellisenserc";
|
|
61
|
+
const cachePath = path.join(os.homedir(), ".inshellisense");
|
|
62
|
+
const configPath = path.join(os.homedir(), configFile);
|
|
63
|
+
let globalConfig = {
|
|
64
|
+
bindings: {
|
|
65
|
+
nextSuggestion: { key: "down" },
|
|
66
|
+
previousSuggestion: { key: "up" },
|
|
67
|
+
acceptSuggestion: { key: "tab" },
|
|
68
|
+
dismissSuggestions: { key: "escape" },
|
|
69
|
+
},
|
|
70
|
+
};
|
|
52
71
|
export const getConfig = () => globalConfig;
|
|
53
72
|
export const loadConfig = async (program) => {
|
|
54
|
-
if (fs.existsSync(
|
|
55
|
-
|
|
73
|
+
if (fs.existsSync(configPath)) {
|
|
74
|
+
let config;
|
|
75
|
+
try {
|
|
76
|
+
config = toml.parse((await fsAsync.readFile(configPath)).toString());
|
|
77
|
+
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
|
78
|
+
}
|
|
79
|
+
catch (e) {
|
|
80
|
+
program.error(`${configFile} is invalid toml. Parsing error on line ${e.line}, column ${e.column}: ${e.message}`);
|
|
81
|
+
}
|
|
56
82
|
const isValid = ajv.validate(configSchema, config);
|
|
57
83
|
if (!isValid) {
|
|
58
|
-
program.error(
|
|
84
|
+
program.error(`${configFile} is invalid: ${ajv.errorsText()}`);
|
|
59
85
|
}
|
|
60
|
-
globalConfig =
|
|
86
|
+
globalConfig = {
|
|
87
|
+
bindings: {
|
|
88
|
+
nextSuggestion: config?.bindings?.nextSuggestion ?? globalConfig.bindings.nextSuggestion,
|
|
89
|
+
previousSuggestion: config?.bindings?.previousSuggestion ?? globalConfig.bindings.previousSuggestion,
|
|
90
|
+
acceptSuggestion: config?.bindings?.acceptSuggestion ?? globalConfig.bindings.acceptSuggestion,
|
|
91
|
+
dismissSuggestions: config?.bindings?.dismissSuggestions ?? globalConfig.bindings.dismissSuggestions,
|
|
92
|
+
},
|
|
93
|
+
prompt: {
|
|
94
|
+
bash: config.prompt?.bash,
|
|
95
|
+
powershell: config.prompt?.powershell,
|
|
96
|
+
xonsh: config.prompt?.xonsh,
|
|
97
|
+
pwsh: config.prompt?.pwsh,
|
|
98
|
+
},
|
|
99
|
+
};
|
|
61
100
|
}
|
|
62
101
|
};
|
|
63
|
-
export const
|
|
64
|
-
const cliConfigPath = path.join(os.homedir(),
|
|
102
|
+
export const deleteCacheFolder = async () => {
|
|
103
|
+
const cliConfigPath = path.join(os.homedir(), cachePath);
|
|
65
104
|
if (fs.existsSync(cliConfigPath)) {
|
|
66
105
|
fs.rmSync(cliConfigPath, { recursive: true });
|
|
67
106
|
}
|
package/build/utils/shell.js
CHANGED
|
@@ -16,6 +16,7 @@ export var Shell;
|
|
|
16
16
|
Shell["Zsh"] = "zsh";
|
|
17
17
|
Shell["Fish"] = "fish";
|
|
18
18
|
Shell["Cmd"] = "cmd";
|
|
19
|
+
Shell["Xonsh"] = "xonsh";
|
|
19
20
|
})(Shell || (Shell = {}));
|
|
20
21
|
export const supportedShells = [
|
|
21
22
|
Shell.Bash,
|
|
@@ -24,6 +25,7 @@ export const supportedShells = [
|
|
|
24
25
|
Shell.Zsh,
|
|
25
26
|
Shell.Fish,
|
|
26
27
|
process.platform == "win32" ? Shell.Cmd : null,
|
|
28
|
+
Shell.Xonsh,
|
|
27
29
|
].filter((shell) => shell != null);
|
|
28
30
|
export const userZdotdir = process.env?.ZDOTDIR ?? os.homedir() ?? `~`;
|
|
29
31
|
export const zdotdir = path.join(os.tmpdir(), `is-zsh`);
|
|
@@ -66,6 +68,9 @@ export const gitBashPath = async () => {
|
|
|
66
68
|
}
|
|
67
69
|
throw new Error("unable to find a git bash executable installed");
|
|
68
70
|
};
|
|
71
|
+
export const getPythonPath = async () => {
|
|
72
|
+
return await which("python", { nothrow: true });
|
|
73
|
+
};
|
|
69
74
|
const getGitBashPaths = async () => {
|
|
70
75
|
const gitDirs = new Set();
|
|
71
76
|
const gitExePath = await which("git.exe", { nothrow: true });
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@microsoft/inshellisense",
|
|
3
|
-
"version": "0.0.1-rc.
|
|
3
|
+
"version": "0.0.1-rc.9",
|
|
4
4
|
"description": "IDE style command line auto complete",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"engines": {
|
|
@@ -40,22 +40,27 @@
|
|
|
40
40
|
"homepage": "https://github.com/microsoft/inshellisense#readme",
|
|
41
41
|
"dependencies": {
|
|
42
42
|
"@homebridge/node-pty-prebuilt-multiarch": "^0.11.12",
|
|
43
|
-
"@microsoft/tui-test": "^0.0.1-rc.3",
|
|
44
43
|
"@withfig/autocomplete": "2.651.0",
|
|
45
44
|
"ajv": "^8.12.0",
|
|
46
45
|
"ansi-escapes": "^6.2.0",
|
|
47
46
|
"ansi-styles": "^6.2.1",
|
|
48
47
|
"chalk": "^5.3.0",
|
|
48
|
+
"color-convert": "^2.0.1",
|
|
49
49
|
"commander": "^11.0.0",
|
|
50
50
|
"find-process": "^1.4.7",
|
|
51
|
+
"toml": "^3.0.0",
|
|
52
|
+
"wcwidth": "^1.0.1",
|
|
51
53
|
"which": "^4.0.0",
|
|
52
54
|
"wrap-ansi": "^8.1.0",
|
|
53
55
|
"xterm-headless": "^5.3.0"
|
|
54
56
|
},
|
|
55
57
|
"devDependencies": {
|
|
58
|
+
"@microsoft/tui-test": "^0.0.1-rc.3",
|
|
56
59
|
"@tsconfig/node18": "^18.2.2",
|
|
60
|
+
"@types/color-convert": "^2.0.3",
|
|
57
61
|
"@types/jest": "^29.5.5",
|
|
58
62
|
"@types/react": "^18.2.24",
|
|
63
|
+
"@types/wcwidth": "^1.0.2",
|
|
59
64
|
"@types/which": "^3.0.3",
|
|
60
65
|
"@typescript-eslint/eslint-plugin": "^6.7.4",
|
|
61
66
|
"@typescript-eslint/parser": "^6.7.4",
|
|
@@ -0,0 +1,29 @@
|
|
|
1
|
+
import os
|
|
2
|
+
|
|
3
|
+
def __is_prompt_start() -> str:
|
|
4
|
+
return "\001" + "\x1b]6973;PS\x07"
|
|
5
|
+
|
|
6
|
+
|
|
7
|
+
def __is_prompt_end() -> str:
|
|
8
|
+
return "\001" + "\x1b]6973;PE\x07" + "\002"
|
|
9
|
+
|
|
10
|
+
|
|
11
|
+
def __is_escape_value(value: str) -> str:
|
|
12
|
+
byte_list = [bytes([byte]).decode("utf-8") for byte in list(value.encode("utf-8"))]
|
|
13
|
+
return "".join(
|
|
14
|
+
[
|
|
15
|
+
"\\x3b" if byte == ";" else "\\\\" if byte == "\\" else byte
|
|
16
|
+
for byte in byte_list
|
|
17
|
+
]
|
|
18
|
+
)
|
|
19
|
+
|
|
20
|
+
def __is_update_cwd() -> str:
|
|
21
|
+
return f"\x1b]6973;CWD;{__is_escape_value(os.getcwd())}\x07" + "\002"
|
|
22
|
+
|
|
23
|
+
$PROMPT_FIELDS['__is_prompt_start'] = __is_prompt_start
|
|
24
|
+
$PROMPT_FIELDS['__is_prompt_end'] = __is_prompt_end
|
|
25
|
+
$PROMPT_FIELDS['__is_update_cwd'] = __is_update_cwd
|
|
26
|
+
if $ISTERM_TESTING:
|
|
27
|
+
$PROMPT = "> "
|
|
28
|
+
|
|
29
|
+
$PROMPT = "{__is_prompt_start}{__is_update_cwd}" + $PROMPT + "{__is_prompt_end}"
|