@microsoft/inshellisense 0.0.1-rc.13 → 0.0.1-rc.14

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.
@@ -12,7 +12,7 @@ export const action = (program) => async (options) => {
12
12
  const inISTerm = process.env.ISTERM === "1";
13
13
  if (options.check || inISTerm) {
14
14
  process.stdout.write(renderConfirmation(inISTerm));
15
- return;
15
+ process.exit(0);
16
16
  }
17
17
  if (options.verbose)
18
18
  await log.enable();
@@ -32,5 +32,5 @@ export const action = (program) => async (options) => {
32
32
  await setupBashPreExec();
33
33
  }
34
34
  await loadAliases(shell);
35
- await render(shell, options.test ?? false);
35
+ await render(shell, options.test ?? false, options.login ?? false);
36
36
  };
@@ -0,0 +1,26 @@
1
+ // Copyright (c) Microsoft Corporation.
2
+ // Licensed under the MIT License.
3
+ import { Command } from "commander";
4
+ import { loadConfig } from "../../utils/config.js";
5
+ import { getSpecNames, loadLocalSpecsSet } from "../../runtime/runtime.js";
6
+ import { getAliasNames, loadAliases } from "../../runtime/alias.js";
7
+ import { aliasSupportedShells } from "../../utils/shell.js";
8
+ const supportedShells = aliasSupportedShells.join(", ");
9
+ const action = (program) => async (options) => {
10
+ await loadConfig(program);
11
+ await loadLocalSpecsSet();
12
+ const { shell } = options;
13
+ if (shell && !aliasSupportedShells.map((s) => s.valueOf()).includes(shell)) {
14
+ program.error(`Unsupported shell: '${shell}', supported shells: ${supportedShells}`, { exitCode: 1 });
15
+ }
16
+ if (shell) {
17
+ await loadAliases(shell);
18
+ }
19
+ process.stdout.write(JSON.stringify([...getAliasNames(), ...getSpecNames()]));
20
+ process.exit(0);
21
+ };
22
+ const cmd = new Command("list");
23
+ cmd.description(`list the names of all available specs`);
24
+ cmd.option("-s, --shell <shell>", `shell to use alias specs, supported shells: ${supportedShells}`);
25
+ cmd.action(action(cmd));
26
+ export default cmd;
@@ -0,0 +1,8 @@
1
+ // Copyright (c) Microsoft Corporation.
2
+ // Licensed under the MIT License.
3
+ import { Command } from "commander";
4
+ import list from "./list.js";
5
+ const cmd = new Command("specs");
6
+ cmd.description(`manage specs`);
7
+ cmd.addCommand(list);
8
+ export default cmd;
package/build/index.js CHANGED
@@ -6,6 +6,7 @@ import { Command, Option } from "commander";
6
6
  import complete from "./commands/complete.js";
7
7
  import uninstall from "./commands/uninstall.js";
8
8
  import init from "./commands/init.js";
9
+ import specs from "./commands/specs/root.js";
9
10
  import { action, supportedShells } from "./commands/root.js";
10
11
  import { getVersion } from "./utils/version.js";
11
12
  const program = new Command();
@@ -19,12 +20,15 @@ program
19
20
  .description("IDE style command line auto complete")
20
21
  .version(await getVersion(), "-v, --version", "output the current version")
21
22
  .action(action(program))
23
+ .option("-l, --login", `start shell as a login shell`)
22
24
  .option("-s, --shell <shell>", `shell to use for command execution, supported shells: ${supportedShells}`)
23
25
  .option("-c, --check", `check if shell is in an inshellisense session`)
24
26
  .addOption(hiddenOption("-T, --test", "used to make e2e tests reproducible across machines"))
25
27
  .option("-V, --verbose", `enable verbose logging`)
26
- .showHelpAfterError("(add --help for additional information)");
28
+ .showHelpAfterError("(add --help for additional information)")
29
+ .passThroughOptions();
27
30
  program.addCommand(complete);
28
31
  program.addCommand(uninstall);
29
32
  program.addCommand(init);
33
+ program.addCommand(specs);
30
34
  program.parse();
@@ -188,6 +188,9 @@ export class CommandManager {
188
188
  cursorTerminated: this.#activeCommand.cursorTerminated,
189
189
  };
190
190
  }
191
+ clearActiveCommand() {
192
+ this.#activeCommand = {};
193
+ }
191
194
  termSync() {
192
195
  if (this.#activeCommand.promptEndMarker == null || this.#activeCommand.promptStartMarker == null) {
193
196
  return;
@@ -197,6 +200,7 @@ export class CommandManager {
197
200
  if (globalCursorPosition < this.#activeCommand.promptStartMarker.line) {
198
201
  this.handleClear();
199
202
  this.#activeCommand.promptEndMarker = this.#terminal.registerMarker(0);
203
+ return;
200
204
  }
201
205
  if (this.#activeCommand.promptEndMarker == null)
202
206
  return;
@@ -31,13 +31,13 @@ export class ISTerm {
31
31
  #term;
32
32
  #commandManager;
33
33
  #shell;
34
- constructor({ shell, cols, rows, env, shellTarget, shellArgs, underTest }) {
34
+ constructor({ shell, cols, rows, env, shellTarget, shellArgs, underTest, login }) {
35
35
  this.#pty = pty.spawn(shellTarget, shellArgs ?? [], {
36
36
  name: "xterm-256color",
37
37
  cols,
38
38
  rows,
39
39
  cwd: process.cwd(),
40
- env: { ...convertToPtyEnv(shell, underTest), ...env },
40
+ env: { ...convertToPtyEnv(shell, underTest, login), ...env },
41
41
  });
42
42
  this.pid = this.#pty.pid;
43
43
  this.cols = this.#pty.cols;
@@ -144,24 +144,37 @@ export class ISTerm {
144
144
  };
145
145
  }
146
146
  _sameAccent(baseCell, targetCell) {
147
- return baseCell?.isBold() == targetCell?.isBold() && baseCell?.isItalic() == targetCell?.isItalic() && baseCell?.isUnderline() == targetCell?.isUnderline();
147
+ return (baseCell?.isBold() == targetCell?.isBold() &&
148
+ baseCell?.isItalic() == targetCell?.isItalic() &&
149
+ baseCell?.isUnderline() == targetCell?.isUnderline() &&
150
+ baseCell?.extended.underlineStyle == targetCell?.extended.underlineStyle &&
151
+ baseCell?.hasExtendedAttrs() == targetCell?.hasExtendedAttrs() &&
152
+ baseCell?.isInverse() == targetCell?.isInverse() &&
153
+ baseCell?.isBlink() == targetCell?.isBlink() &&
154
+ baseCell?.isInvisible() == targetCell?.isInvisible() &&
155
+ baseCell?.isDim() == targetCell?.isDim() &&
156
+ baseCell?.isStrikethrough() == targetCell?.isStrikethrough());
148
157
  }
149
158
  _getAnsiAccents(cell) {
150
159
  if (cell == null)
151
160
  return "";
152
- let boldAnsi = "";
153
- if (cell.isBold()) {
154
- boldAnsi = "\x1b[1m";
155
- }
156
- let italicAnsi = "";
157
- if (cell.isItalic()) {
158
- italicAnsi = "\x1b[3m";
159
- }
160
161
  let underlineAnsi = "";
161
162
  if (cell.isUnderline()) {
162
- underlineAnsi = "\x1b[4m";
163
+ if (cell.hasExtendedAttrs() && cell.extended.underlineStyle) {
164
+ underlineAnsi = `\x1b[4:${cell.extended.underlineStyle}m`;
165
+ }
166
+ else {
167
+ underlineAnsi = "\x1b[4m";
168
+ }
163
169
  }
164
- return boldAnsi + italicAnsi + underlineAnsi;
170
+ const boldAnsi = cell.isBold() ? "\x1b[1m" : "";
171
+ const dimAnsi = cell.isDim() ? "\x1b[2m" : "";
172
+ const italicAnsi = cell.isItalic() ? "\x1b[3m" : "";
173
+ const blinkAnsi = cell.isBlink() ? "\x1b[5m" : "";
174
+ const inverseAnsi = cell.isInverse() ? "\x1b[7m" : "";
175
+ const invisibleAnsi = cell.isInvisible() ? "\x1b[8m" : "";
176
+ const strikethroughAnsi = cell.isStrikethrough() ? "\x1b[9m" : "";
177
+ return boldAnsi + italicAnsi + underlineAnsi + inverseAnsi + dimAnsi + blinkAnsi + invisibleAnsi + strikethroughAnsi;
165
178
  }
166
179
  _sameColor(baseCell, targetCell) {
167
180
  return (baseCell?.getBgColorMode() == targetCell?.getBgColorMode() &&
@@ -196,6 +209,9 @@ export class ISTerm {
196
209
  }
197
210
  return bgAnsi + fgAnsi;
198
211
  }
212
+ clearCommand() {
213
+ this.#commandManager.clearActiveCommand();
214
+ }
199
215
  getCells(height, direction) {
200
216
  const currentCursorPosition = this.#term.buffer.active.cursorY + this.#term.buffer.active.baseY;
201
217
  const writeLine = (y) => {
@@ -207,10 +223,15 @@ export class ISTerm {
207
223
  for (let x = 0; x < line.length; x++) {
208
224
  const cell = line.getCell(x);
209
225
  const chars = cell?.getChars() ?? "";
210
- if (!this._sameColor(prevCell, cell)) {
226
+ const sameColor = this._sameColor(prevCell, cell);
227
+ const sameAccents = this._sameAccent(prevCell, cell);
228
+ if (!sameColor || !sameAccents) {
229
+ ansiLine.push("\x1b[0m");
230
+ }
231
+ if (!sameColor) {
211
232
  ansiLine.push(this._getAnsiColors(cell));
212
233
  }
213
- if (!this._sameAccent(prevCell, cell)) {
234
+ if (!sameAccents) {
214
235
  ansiLine.push(this._getAnsiAccents(cell));
215
236
  }
216
237
  ansiLine.push(chars == "" ? " " : chars);
@@ -237,10 +258,10 @@ export class ISTerm {
237
258
  }
238
259
  }
239
260
  export const spawn = async (options) => {
240
- const { shellTarget, shellArgs } = await convertToPtyTarget(options.shell, options.underTest);
261
+ const { shellTarget, shellArgs } = await convertToPtyTarget(options.shell, options.underTest, options.login);
241
262
  return new ISTerm({ ...options, shellTarget, shellArgs });
242
263
  };
243
- const convertToPtyTarget = async (shell, underTest) => {
264
+ const convertToPtyTarget = async (shell, underTest, login) => {
244
265
  const platform = os.platform();
245
266
  const shellTarget = shell == Shell.Bash && platform == "win32" ? await gitBashPath() : platform == "win32" ? `${shell}.exe` : shell;
246
267
  const shellFolderPath = path.join(path.dirname(url.fileURLToPath(import.meta.url)), "..", "..", "shell");
@@ -273,15 +294,31 @@ const convertToPtyTarget = async (shell, underTest) => {
273
294
  shellArgs.push("-n");
274
295
  break;
275
296
  }
297
+ if (login) {
298
+ switch (shell) {
299
+ case Shell.Powershell:
300
+ case Shell.Pwsh:
301
+ shellArgs.unshift("-login");
302
+ break;
303
+ case Shell.Zsh:
304
+ case Shell.Fish:
305
+ case Shell.Xonsh:
306
+ case Shell.Nushell:
307
+ shellArgs.unshift("--login");
308
+ break;
309
+ }
310
+ }
276
311
  return { shellTarget, shellArgs };
277
312
  };
278
- const convertToPtyEnv = (shell, underTest) => {
313
+ const convertToPtyEnv = (shell, underTest, login) => {
279
314
  const env = {
280
315
  ...process.env,
281
316
  ISTERM: "1",
282
317
  };
283
318
  if (underTest)
284
319
  env.ISTERM_TESTING = "1";
320
+ if (login)
321
+ env.ISTERM_LOGIN = "1";
285
322
  switch (shell) {
286
323
  case Shell.Cmd: {
287
324
  if (underTest) {
@@ -48,6 +48,7 @@ export const loadAliases = async (shell) => {
48
48
  }
49
49
  return [];
50
50
  };
51
+ export const getAliasNames = () => Object.keys(loadedAliases);
51
52
  export const aliasExpand = (command) => {
52
53
  if (!command.at(0)?.complete)
53
54
  return command;
@@ -103,6 +103,9 @@ export const getSuggestions = async (cmd, cwd, shell) => {
103
103
  }
104
104
  return { ...result, charactersToDrop };
105
105
  };
106
+ export const getSpecNames = () => {
107
+ return Object.keys(specSet).filter((spec) => !spec.startsWith("@") && spec != "-");
108
+ };
106
109
  const getPersistentOptions = (persistentOptions, options) => {
107
110
  const persistentOptionNames = new Set(persistentOptions.map((o) => (typeof o.name === "string" ? [o.name] : o.name)).flat());
108
111
  return persistentOptions.concat((options ?? []).filter((o) => (typeof o.name == "string" ? !persistentOptionNames.has(o.name) : o.name.some((n) => !persistentOptionNames.has(n))) && o.isPersistent === true));
@@ -122,6 +122,9 @@ export class SuggestionManager {
122
122
  }
123
123
  update(keyPress) {
124
124
  const { name, shift, ctrl } = keyPress;
125
+ if (name == "return") {
126
+ this.#term.clearCommand(); // clear the current command on enter
127
+ }
125
128
  if (!this.#suggestBlob) {
126
129
  return false;
127
130
  }
@@ -12,8 +12,8 @@ export const renderConfirmation = (live) => {
12
12
  const statusMessage = live ? chalk.green("live") : chalk.red("not found");
13
13
  return `inshellisense session [${statusMessage}]\n`;
14
14
  };
15
- export const render = async (shell, underTest) => {
16
- const term = await isterm.spawn({ shell, rows: process.stdout.rows, cols: process.stdout.columns, underTest });
15
+ export const render = async (shell, underTest, login) => {
16
+ const term = await isterm.spawn({ shell, rows: process.stdout.rows, cols: process.stdout.columns, underTest, login });
17
17
  const suggestionManager = new SuggestionManager(term, shell);
18
18
  let hasActiveSuggestions = false;
19
19
  let previousSuggestionsRows = 0;
@@ -30,6 +30,7 @@ export const supportedShells = [
30
30
  Shell.Nushell,
31
31
  ].filter((shell) => shell != null);
32
32
  export const initSupportedShells = supportedShells.filter((shell) => shell != Shell.Cmd);
33
+ export const aliasSupportedShells = [Shell.Bash, Shell.Zsh];
33
34
  export const userZdotdir = process.env?.ZDOTDIR ?? os.homedir() ?? `~`;
34
35
  export const zdotdir = path.join(os.tmpdir(), `is-zsh`);
35
36
  const configFolder = ".inshellisense";
@@ -103,9 +104,22 @@ export const getShellPromptRewrites = (shell) => shell == Shell.Nushell;
103
104
  export const getShellConfig = (shell) => {
104
105
  switch (shell) {
105
106
  case Shell.Zsh:
107
+ return `if [[ -z "\${ISTERM}" && $- = *i* && $- != *c* ]]; then
108
+ if [[ -o login ]]; then
109
+ is -s zsh --login ; exit
110
+ else
111
+ is -s zsh ; exit
112
+ fi
113
+ fi`;
106
114
  case Shell.Bash:
107
115
  return `if [[ -z "\${ISTERM}" && $- = *i* && $- != *c* ]]; then
108
- is -s ${shell} ; exit
116
+ shopt -q login_shell
117
+ login_shell=$?
118
+ if [ $login_shell -eq 0 ]; then
119
+ is -s bash --login ; exit
120
+ else
121
+ is -s bash ; exit
122
+ fi
109
123
  fi`;
110
124
  case Shell.Powershell:
111
125
  case Shell.Pwsh:
@@ -118,14 +132,21 @@ if ([string]::IsNullOrEmpty($env:ISTERM) -and [Environment]::UserInteractive -an
118
132
  }`;
119
133
  case Shell.Fish:
120
134
  return `if test -z "$ISTERM" && status --is-interactive
121
- is -s fish ; kill %self
135
+ if status --is-login
136
+ is -s fish --login ; kill %self
137
+ else
138
+ is -s fish ; kill %self
139
+ end
122
140
  end`;
123
141
  case Shell.Xonsh:
124
142
  return `if 'ISTERM' not in \${...} and $XONSH_INTERACTIVE:
125
- is -s xonsh ; exit`;
143
+ if $XONSH_LOGIN:
144
+ is -s xonsh --login ; exit
145
+ else:
146
+ is -s xonsh ; exit`;
126
147
  case Shell.Nushell:
127
148
  return `if "ISTERM" not-in $env and $nu.is-interactive {
128
- is -s nu ; exit
149
+ if $nu.is-login { is -s nu --login ; exit } else { is -s nu ; exit }
129
150
  }`;
130
151
  }
131
152
  return "";
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@microsoft/inshellisense",
3
- "version": "0.0.1-rc.13",
3
+ "version": "0.0.1-rc.14",
4
4
  "description": "IDE style command line auto complete",
5
5
  "type": "module",
6
6
  "engines": {
@@ -41,6 +41,7 @@
41
41
  "dependencies": {
42
42
  "@homebridge/node-pty-prebuilt-multiarch": "^0.11.12",
43
43
  "@withfig/autocomplete": "2.651.0",
44
+ "@xterm/headless": "^5.5.0",
44
45
  "ajv": "^8.12.0",
45
46
  "ansi-escapes": "^6.2.0",
46
47
  "ansi-styles": "^6.2.1",
@@ -51,8 +52,7 @@
51
52
  "toml": "^3.0.0",
52
53
  "wcwidth": "^1.0.1",
53
54
  "which": "^4.0.0",
54
- "wrap-ansi": "^8.1.0",
55
- "@xterm/headless": "^5.3.0"
55
+ "wrap-ansi": "^8.1.0"
56
56
  },
57
57
  "devDependencies": {
58
58
  "@microsoft/tui-test": "^0.0.1-rc.3",
@@ -65,6 +65,7 @@
65
65
  "@typescript-eslint/eslint-plugin": "^6.7.4",
66
66
  "@typescript-eslint/parser": "^6.7.4",
67
67
  "@withfig/autocomplete-types": "^1.28.0",
68
+ "@xterm/xterm": "^5.5.0",
68
69
  "eslint": "^8.51.0",
69
70
  "eslint-config-prettier": "^9.0.0",
70
71
  "eslint-plugin-header": "^3.1.1",
@@ -1,5 +1,19 @@
1
- if [ -r ~/.bashrc ]; then
2
- . ~/.bashrc
1
+ if [ -z "$ISTERM_LOGIN" ]; then
2
+ if [ -r ~/.bashrc ]; then
3
+ . ~/.bashrc
4
+ fi
5
+ else
6
+ if [ -r /etc/profile ]; then
7
+ . /etc/profile
8
+ fi
9
+ # execute the first that exists
10
+ if [ -r ~/.bash_profile ]; then
11
+ . ~/.bash_profile
12
+ elif [ -r ~/.bash_login ]; then
13
+ . ~/.bash_login
14
+ elif [ -r ~/.profile ]; then
15
+ . ~/.profile
16
+ fi
3
17
  fi
4
18
 
5
19
  if [ -r ~/.inshellisense/bash-preexec.sh ]; then