@microsoft/inshellisense 0.0.1-rc.11 → 0.0.1-rc.12

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.
@@ -5,6 +5,8 @@ import { Shell, supportedShells as shells, setupZshDotfiles, setupBashPreExec }
5
5
  import { inferShell } from "../utils/shell.js";
6
6
  import { loadConfig } from "../utils/config.js";
7
7
  import log from "../utils/log.js";
8
+ import { loadAliases } from "../runtime/alias.js";
9
+ import { loadLocalSpecsSet } from "../runtime/runtime.js";
8
10
  export const supportedShells = shells.join(", ");
9
11
  export const action = (program) => async (options) => {
10
12
  const inISTerm = process.env.ISTERM === "1";
@@ -15,6 +17,7 @@ export const action = (program) => async (options) => {
15
17
  if (options.verbose)
16
18
  await log.enable();
17
19
  await loadConfig(program);
20
+ await loadLocalSpecsSet();
18
21
  const shell = options.shell ?? (await inferShell());
19
22
  if (shell == null) {
20
23
  program.error(`Unable to identify shell, use the -s/--shell option to provide your shell`, { exitCode: 1 });
@@ -28,5 +31,6 @@ export const action = (program) => async (options) => {
28
31
  else if (shell == Shell.Bash) {
29
32
  await setupBashPreExec();
30
33
  }
34
+ await loadAliases(shell);
31
35
  await render(shell, options.test ?? false, options.parentTermExit ?? false);
32
36
  };
@@ -32,6 +32,8 @@ export class CommandManager {
32
32
  this.#activeCommand = { promptStartMarker: this.#terminal.registerMarker(0), hasOutput: false, cursorTerminated: false };
33
33
  }
34
34
  handlePromptEnd() {
35
+ if (this.#activeCommand.promptEndMarker != null)
36
+ return;
35
37
  this.#activeCommand.promptEndMarker = this.#terminal.registerMarker(0);
36
38
  if (this.#activeCommand.promptEndMarker?.line === this.#terminal.buffer.active.cursorY) {
37
39
  this.#activeCommand.promptEndX = this.#terminal.buffer.active.cursorX;
@@ -218,7 +220,9 @@ export class CommandManager {
218
220
  let lineY = this.#activeCommand.promptEndMarker.line;
219
221
  let line = this.#terminal.buffer.active.getLine(this.#activeCommand.promptEndMarker.line);
220
222
  let command = "";
223
+ let wrappedCommand = "";
221
224
  let suggestions = "";
225
+ let isWrapped = false;
222
226
  for (;;) {
223
227
  for (let i = lineY == this.#activeCommand.promptEndMarker.line ? this.#activeCommand.promptText.length : 0; i < this.#terminal.cols; i++) {
224
228
  if (command.endsWith(" "))
@@ -230,6 +234,7 @@ export class CommandManager {
230
234
  const cleanedChars = chars == "" ? " " : chars;
231
235
  if (!this._isSuggestion(cell) && suggestions.length == 0) {
232
236
  command += cleanedChars;
237
+ wrappedCommand += cleanedChars;
233
238
  }
234
239
  else {
235
240
  suggestions += cleanedChars;
@@ -237,11 +242,16 @@ export class CommandManager {
237
242
  }
238
243
  lineY += 1;
239
244
  line = this.#terminal.buffer.active.getLine(lineY);
240
- if (!line?.isWrapped) {
245
+ const wrapped = line?.isWrapped || this.#terminal.buffer.active.cursorY + this.#terminal.buffer.active.baseY != lineY - 1;
246
+ isWrapped = isWrapped || wrapped;
247
+ if (!wrapped) {
241
248
  break;
242
249
  }
250
+ wrappedCommand = "";
243
251
  }
244
- const cursorAtEndOfInput = (this.#activeCommand.promptText.length + command.trim().length) % this.#terminal.cols <= this.#terminal.buffer.active.cursorX;
252
+ const cursorAtEndOfInput = isWrapped
253
+ ? wrappedCommand.trim().length % this.#terminal.cols <= this.#terminal.buffer.active.cursorX
254
+ : (this.#activeCommand.promptText.length + command.trimEnd().length) % this.#terminal.cols <= this.#terminal.buffer.active.cursorX;
245
255
  let hasOutput = false;
246
256
  let cell = undefined;
247
257
  for (let i = 0; i < this.#terminal.cols; i++) {
@@ -253,7 +263,10 @@ export class CommandManager {
253
263
  break;
254
264
  }
255
265
  }
256
- const commandPostfix = this.#activeCommand.promptText.length + command.trim().length < this.#terminal.buffer.active.cursorX ? " " : "";
266
+ const postfixActive = isWrapped
267
+ ? wrappedCommand.trim().length < this.#terminal.buffer.active.cursorX
268
+ : this.#activeCommand.promptText.length + command.trimEnd().length < this.#terminal.buffer.active.cursorX;
269
+ const commandPostfix = postfixActive ? " " : "";
257
270
  this.#activeCommand.persistentOutput = this.#activeCommand.hasOutput && hasOutput;
258
271
  this.#activeCommand.hasOutput = hasOutput;
259
272
  this.#activeCommand.suggestionsText = suggestions.trim();
@@ -0,0 +1,60 @@
1
+ // Copyright (c) Microsoft Corporation.
2
+ // Licensed under the MIT License.
3
+ import log from "../utils/log.js";
4
+ import { gitBashPath, Shell } from "../utils/shell.js";
5
+ import { parseCommand } from "./parser.js";
6
+ import { buildExecuteShellCommand } from "./utils.js";
7
+ import os from "node:os";
8
+ const loadedAliases = {};
9
+ const platform = os.platform();
10
+ const executeShellCommand = buildExecuteShellCommand(5000);
11
+ const loadBashAliases = async () => {
12
+ const shellTarget = platform == "win32" ? await gitBashPath() : Shell.Bash;
13
+ const { stdout, stderr, status } = await executeShellCommand({ command: shellTarget, args: ["-i", "-c", "alias"], cwd: process.cwd() });
14
+ if (status !== 0) {
15
+ log.debug({ msg: "failed to load bash aliases", stderr, status });
16
+ return;
17
+ }
18
+ return stdout
19
+ .trim()
20
+ .split("\n")
21
+ .forEach((line) => {
22
+ const [alias, ...commandSegments] = line.replace("alias ", "").replaceAll("'\\''", "'").split("=");
23
+ loadedAliases[alias] = parseCommand(commandSegments.join("=").slice(1, -1) + " ");
24
+ });
25
+ };
26
+ const loadZshAliases = async () => {
27
+ const { stdout, stderr, status } = await executeShellCommand({ command: Shell.Zsh, args: ["-i", "-c", "alias"], cwd: process.cwd() });
28
+ if (status !== 0) {
29
+ log.debug({ msg: "failed to load zsh aliases", stderr, status });
30
+ return;
31
+ }
32
+ return stdout
33
+ .trim()
34
+ .split("\n")
35
+ .forEach((line) => {
36
+ const [alias, ...commandSegments] = line.replaceAll("'\\''", "'").split("=");
37
+ loadedAliases[alias] = parseCommand(commandSegments.join("=").slice(1, -1) + " ");
38
+ });
39
+ };
40
+ export const loadAliases = async (shell) => {
41
+ switch (shell) {
42
+ case Shell.Bash:
43
+ await loadBashAliases();
44
+ break;
45
+ case Shell.Zsh:
46
+ await loadZshAliases();
47
+ break;
48
+ }
49
+ return [];
50
+ };
51
+ export const aliasExpand = (command) => {
52
+ if (!command.at(0)?.complete)
53
+ return command;
54
+ const alias = loadedAliases[command.at(0)?.token ?? ""];
55
+ if (alias) {
56
+ log.debug({ msg: "expanding alias", alias, command: command.slice(1) });
57
+ return [...alias, ...command.slice(1)];
58
+ }
59
+ return command;
60
+ };
@@ -8,25 +8,31 @@ import path from "node:path";
8
8
  import { parseCommand } from "./parser.js";
9
9
  import { getArgDrivenRecommendation, getSubcommandDrivenRecommendation } from "./suggestion.js";
10
10
  import { buildExecuteShellCommand, resolveCwd } from "./utils.js";
11
+ import { aliasExpand } from "./alias.js";
12
+ import { getConfig } from "../utils/config.js";
13
+ import log from "../utils/log.js";
11
14
  // eslint-disable-next-line @typescript-eslint/no-explicit-any -- recursive type, setting as any
12
15
  const specSet = {};
13
- speclist.forEach((s) => {
14
- let activeSet = specSet;
15
- const specRoutes = s.split("/");
16
- specRoutes.forEach((route, idx) => {
17
- if (typeof activeSet !== "object") {
18
- return;
19
- }
20
- if (idx === specRoutes.length - 1) {
21
- const prefix = versionedSpeclist.includes(s) ? "/index.js" : `.js`;
22
- activeSet[route] = `@withfig/autocomplete/build/${s}${prefix}`;
23
- }
24
- else {
25
- activeSet[route] = activeSet[route] || {};
26
- activeSet = activeSet[route];
27
- }
16
+ function loadSpecsSet(speclist, versionedSpeclist, specsPath) {
17
+ speclist.forEach((s) => {
18
+ let activeSet = specSet;
19
+ const specRoutes = s.split("/");
20
+ specRoutes.forEach((route, idx) => {
21
+ if (typeof activeSet !== "object") {
22
+ return;
23
+ }
24
+ if (idx === specRoutes.length - 1) {
25
+ const prefix = versionedSpeclist.includes(s) ? "/index.js" : `.js`;
26
+ activeSet[route] = `${specsPath}/${s}${prefix}`;
27
+ }
28
+ else {
29
+ activeSet[route] = activeSet[route] || {};
30
+ activeSet = activeSet[route];
31
+ }
32
+ });
28
33
  });
29
- });
34
+ }
35
+ loadSpecsSet(speclist, versionedSpeclist, `@withfig/autocomplete/build`);
30
36
  const loadedSpecs = {};
31
37
  const loadSpec = async (cmd) => {
32
38
  const rootToken = cmd.at(0);
@@ -50,12 +56,32 @@ const lazyLoadSpec = async (key) => {
50
56
  const lazyLoadSpecLocation = async (location) => {
51
57
  return; //TODO: implement spec location loading
52
58
  };
59
+ export const loadLocalSpecsSet = async () => {
60
+ const specsPath = getConfig()?.specs?.path;
61
+ if (!specsPath) {
62
+ return;
63
+ }
64
+ try {
65
+ await Promise.allSettled(specsPath.map((specPath) => import(path.join(specPath, "index.js"))
66
+ .then((res) => {
67
+ const { default: speclist, diffVersionedCompletions: versionedSpeclist } = res;
68
+ loadSpecsSet(speclist, versionedSpeclist, specPath);
69
+ })
70
+ .catch((e) => {
71
+ log.debug({ msg: "load local spec failed", e: e.message, specPath });
72
+ })));
73
+ }
74
+ catch (e) {
75
+ log.debug({ msg: "load local specs failed", e: e.message, specsPath });
76
+ }
77
+ };
53
78
  export const getSuggestions = async (cmd, cwd, shell) => {
54
- const activeCmd = parseCommand(cmd);
79
+ let activeCmd = parseCommand(cmd);
55
80
  const rootToken = activeCmd.at(0);
56
81
  if (activeCmd.length === 0 || !rootToken?.complete) {
57
82
  return;
58
83
  }
84
+ activeCmd = aliasExpand(activeCmd);
59
85
  const spec = await loadSpec(activeCmd);
60
86
  if (spec == null)
61
87
  return;
@@ -127,17 +153,29 @@ const genSubcommand = async (command, parentCommand) => {
127
153
  return parentCommand.subcommands[subcommandIdx];
128
154
  }
129
155
  else {
130
- parentCommand.subcommands[subcommandIdx] = { ...subcommand, ...partSpec, loadSpec: undefined };
156
+ parentCommand.subcommands[subcommandIdx] = {
157
+ ...subcommand,
158
+ ...partSpec,
159
+ loadSpec: undefined,
160
+ };
131
161
  return parentCommand.subcommands[subcommandIdx];
132
162
  }
133
163
  }
134
164
  case "string": {
135
165
  const spec = await lazyLoadSpec(subcommand.loadSpec);
136
- parentCommand.subcommands[subcommandIdx] = { ...subcommand, ...(getSubcommand(spec) ?? []), loadSpec: undefined };
166
+ parentCommand.subcommands[subcommandIdx] = {
167
+ ...subcommand,
168
+ ...(getSubcommand(spec) ?? []),
169
+ loadSpec: undefined,
170
+ };
137
171
  return parentCommand.subcommands[subcommandIdx];
138
172
  }
139
173
  case "object": {
140
- parentCommand.subcommands[subcommandIdx] = { ...subcommand, ...(subcommand.loadSpec ?? {}), loadSpec: undefined };
174
+ parentCommand.subcommands[subcommandIdx] = {
175
+ ...subcommand,
176
+ ...(subcommand.loadSpec ?? {}),
177
+ loadSpec: undefined,
178
+ };
141
179
  return parentCommand.subcommands[subcommandIdx];
142
180
  }
143
181
  case "undefined": {
@@ -164,7 +202,10 @@ const runOption = async (tokens, option, subcommand, cwd, persistentOptions, acc
164
202
  const args = option.args instanceof Array ? option.args : [option.args];
165
203
  return runArg(tokens.slice(1), args, subcommand, cwd, persistentOptions, acceptedTokens.concat(activeToken), true, false);
166
204
  }
167
- return runSubcommand(tokens.slice(1), subcommand, cwd, persistentOptions, acceptedTokens.concat({ ...activeToken, isPersistent }));
205
+ return runSubcommand(tokens.slice(1), subcommand, cwd, persistentOptions, acceptedTokens.concat({
206
+ ...activeToken,
207
+ isPersistent,
208
+ }));
168
209
  };
169
210
  const runArg = async (tokens, args, subcommand, cwd, persistentOptions, acceptedTokens, fromOption, fromVariadic) => {
170
211
  if (args.length === 0) {
@@ -30,6 +30,11 @@ const promptPatternsSchema = {
30
30
  required: ["regex", "postfix"],
31
31
  },
32
32
  };
33
+ const specPathsSchema = {
34
+ type: "array",
35
+ items: { type: "string" },
36
+ nullable: true,
37
+ };
33
38
  const configSchema = {
34
39
  type: "object",
35
40
  nullable: true,
@@ -55,6 +60,13 @@ const configSchema = {
55
60
  nu: promptPatternsSchema,
56
61
  },
57
62
  },
63
+ specs: {
64
+ type: "object",
65
+ nullable: true,
66
+ properties: {
67
+ path: specPathsSchema,
68
+ },
69
+ },
58
70
  },
59
71
  additionalProperties: false,
60
72
  };
@@ -98,6 +110,9 @@ export const loadConfig = async (program) => {
98
110
  pwsh: config.prompt?.pwsh,
99
111
  nu: config.prompt?.nu,
100
112
  },
113
+ specs: {
114
+ path: [`${os.homedir()}/.fig/autocomplete/build`, ...(config?.specs?.path ?? [])],
115
+ },
101
116
  };
102
117
  }
103
118
  };
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@microsoft/inshellisense",
3
- "version": "0.0.1-rc.11",
3
+ "version": "0.0.1-rc.12",
4
4
  "description": "IDE style command line auto complete",
5
5
  "type": "module",
6
6
  "engines": {