mirror of
https://github.com/wavetermdev/inshellisense.git
synced 2026-08-05 13:43:30 -07:00
fix: improve git bash interfence & cleanup inference code (#91)
* fix: improve git bash interfence & cleanup inference code Signed-off-by: Chapman Pendery <cpendery@vt.edu> * fix: async issue Signed-off-by: Chapman Pendery <cpendery@vt.edu> * style: fix missing node Signed-off-by: Chapman Pendery <cpendery@vt.edu> * chore: delete dead code Signed-off-by: Chapman Pendery <cpendery@vt.edu> * chore: delete more dead code Signed-off-by: Chapman Pendery <cpendery@vt.edu> --------- Signed-off-by: Chapman Pendery <cpendery@vt.edu>
This commit is contained in:
Generated
+71
-20
@@ -16,6 +16,7 @@
|
||||
"ink": "^4.4.1",
|
||||
"node-pty": "^1.0.0",
|
||||
"react": "^18.2.0",
|
||||
"which": "^4.0.0",
|
||||
"wrap-ansi": "^8.1.0",
|
||||
"xterm-headless": "^5.3.0"
|
||||
},
|
||||
@@ -27,6 +28,7 @@
|
||||
"@tsconfig/node18": "^18.2.2",
|
||||
"@types/jest": "^29.5.5",
|
||||
"@types/react": "^18.2.24",
|
||||
"@types/which": "^3.0.3",
|
||||
"@typescript-eslint/eslint-plugin": "^6.7.4",
|
||||
"@typescript-eslint/parser": "^6.7.4",
|
||||
"@withfig/autocomplete-types": "^1.28.0",
|
||||
@@ -1806,6 +1808,12 @@
|
||||
"integrity": "sha512-Hl219/BT5fLAaz6NDkSuhzasy49dwQS/DSdu4MdggFB8zcXv7vflBI3xp7FEmkmdDkBUI2bPUNeMttp2knYdxw==",
|
||||
"dev": true
|
||||
},
|
||||
"node_modules/@types/which": {
|
||||
"version": "3.0.3",
|
||||
"resolved": "https://registry.npmjs.org/@types/which/-/which-3.0.3.tgz",
|
||||
"integrity": "sha512-2C1+XoY0huExTbs8MQv1DuS5FS86+SEjdM9F/+GS61gg5Hqbtj8ZiDSx8MfWcyei907fIPbfPGCOrNUTnVHY1g==",
|
||||
"dev": true
|
||||
},
|
||||
"node_modules/@types/yargs": {
|
||||
"version": "17.0.26",
|
||||
"resolved": "https://registry.npmjs.org/@types/yargs/-/yargs-17.0.26.tgz",
|
||||
@@ -2959,6 +2967,27 @@
|
||||
"node": ">= 8"
|
||||
}
|
||||
},
|
||||
"node_modules/cross-spawn/node_modules/isexe": {
|
||||
"version": "2.0.0",
|
||||
"resolved": "https://registry.npmjs.org/isexe/-/isexe-2.0.0.tgz",
|
||||
"integrity": "sha512-RHxMLp9lnKHGHRng9QFhRCMbYAcVpn69smSGcq3f36xjgVVWThj4qqLbTLlq7Ssj8B+fIQ1EuCEGI2lKsyQeIw==",
|
||||
"dev": true
|
||||
},
|
||||
"node_modules/cross-spawn/node_modules/which": {
|
||||
"version": "2.0.2",
|
||||
"resolved": "https://registry.npmjs.org/which/-/which-2.0.2.tgz",
|
||||
"integrity": "sha512-BLI3Tl1TW3Pvl70l3yq3Y64i+awpwXqsGBYWkkqMtnbXgrMD+yj7rhW0kuEDxzJaYXGjEW5ogapKNMEKNMjibA==",
|
||||
"dev": true,
|
||||
"dependencies": {
|
||||
"isexe": "^2.0.0"
|
||||
},
|
||||
"bin": {
|
||||
"node-which": "bin/node-which"
|
||||
},
|
||||
"engines": {
|
||||
"node": ">= 8"
|
||||
}
|
||||
},
|
||||
"node_modules/csstype": {
|
||||
"version": "3.1.2",
|
||||
"resolved": "https://registry.npmjs.org/csstype/-/csstype-3.1.2.tgz",
|
||||
@@ -4764,10 +4793,12 @@
|
||||
"dev": true
|
||||
},
|
||||
"node_modules/isexe": {
|
||||
"version": "2.0.0",
|
||||
"resolved": "https://registry.npmjs.org/isexe/-/isexe-2.0.0.tgz",
|
||||
"integrity": "sha512-RHxMLp9lnKHGHRng9QFhRCMbYAcVpn69smSGcq3f36xjgVVWThj4qqLbTLlq7Ssj8B+fIQ1EuCEGI2lKsyQeIw==",
|
||||
"dev": true
|
||||
"version": "3.1.1",
|
||||
"resolved": "https://registry.npmjs.org/isexe/-/isexe-3.1.1.tgz",
|
||||
"integrity": "sha512-LpB/54B+/2J5hqQ7imZHfdU31OlgQqx7ZicVlkm9kzg9/w8GKLEcFfJl/t7DCEDueOyBAD6zCCwTO6Fzs0NoEQ==",
|
||||
"engines": {
|
||||
"node": ">=16"
|
||||
}
|
||||
},
|
||||
"node_modules/istanbul-lib-coverage": {
|
||||
"version": "3.2.0",
|
||||
@@ -7761,18 +7792,17 @@
|
||||
}
|
||||
},
|
||||
"node_modules/which": {
|
||||
"version": "2.0.2",
|
||||
"resolved": "https://registry.npmjs.org/which/-/which-2.0.2.tgz",
|
||||
"integrity": "sha512-BLI3Tl1TW3Pvl70l3yq3Y64i+awpwXqsGBYWkkqMtnbXgrMD+yj7rhW0kuEDxzJaYXGjEW5ogapKNMEKNMjibA==",
|
||||
"dev": true,
|
||||
"version": "4.0.0",
|
||||
"resolved": "https://registry.npmjs.org/which/-/which-4.0.0.tgz",
|
||||
"integrity": "sha512-GlaYyEb07DPxYCKhKzplCWBJtvxZcZMrL+4UkrTSJHHPyZU4mYYTv3qaOe77H7EODLSSopAUFAc6W8U4yqvscg==",
|
||||
"dependencies": {
|
||||
"isexe": "^2.0.0"
|
||||
"isexe": "^3.1.1"
|
||||
},
|
||||
"bin": {
|
||||
"node-which": "bin/node-which"
|
||||
"node-which": "bin/which.js"
|
||||
},
|
||||
"engines": {
|
||||
"node": ">= 8"
|
||||
"node": "^16.13.0 || >=18.0.0"
|
||||
}
|
||||
},
|
||||
"node_modules/which-boxed-primitive": {
|
||||
@@ -9421,6 +9451,12 @@
|
||||
"integrity": "sha512-Hl219/BT5fLAaz6NDkSuhzasy49dwQS/DSdu4MdggFB8zcXv7vflBI3xp7FEmkmdDkBUI2bPUNeMttp2knYdxw==",
|
||||
"dev": true
|
||||
},
|
||||
"@types/which": {
|
||||
"version": "3.0.3",
|
||||
"resolved": "https://registry.npmjs.org/@types/which/-/which-3.0.3.tgz",
|
||||
"integrity": "sha512-2C1+XoY0huExTbs8MQv1DuS5FS86+SEjdM9F/+GS61gg5Hqbtj8ZiDSx8MfWcyei907fIPbfPGCOrNUTnVHY1g==",
|
||||
"dev": true
|
||||
},
|
||||
"@types/yargs": {
|
||||
"version": "17.0.26",
|
||||
"resolved": "https://registry.npmjs.org/@types/yargs/-/yargs-17.0.26.tgz",
|
||||
@@ -10211,6 +10247,23 @@
|
||||
"path-key": "^3.1.0",
|
||||
"shebang-command": "^2.0.0",
|
||||
"which": "^2.0.1"
|
||||
},
|
||||
"dependencies": {
|
||||
"isexe": {
|
||||
"version": "2.0.0",
|
||||
"resolved": "https://registry.npmjs.org/isexe/-/isexe-2.0.0.tgz",
|
||||
"integrity": "sha512-RHxMLp9lnKHGHRng9QFhRCMbYAcVpn69smSGcq3f36xjgVVWThj4qqLbTLlq7Ssj8B+fIQ1EuCEGI2lKsyQeIw==",
|
||||
"dev": true
|
||||
},
|
||||
"which": {
|
||||
"version": "2.0.2",
|
||||
"resolved": "https://registry.npmjs.org/which/-/which-2.0.2.tgz",
|
||||
"integrity": "sha512-BLI3Tl1TW3Pvl70l3yq3Y64i+awpwXqsGBYWkkqMtnbXgrMD+yj7rhW0kuEDxzJaYXGjEW5ogapKNMEKNMjibA==",
|
||||
"dev": true,
|
||||
"requires": {
|
||||
"isexe": "^2.0.0"
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
"csstype": {
|
||||
@@ -11505,10 +11558,9 @@
|
||||
"dev": true
|
||||
},
|
||||
"isexe": {
|
||||
"version": "2.0.0",
|
||||
"resolved": "https://registry.npmjs.org/isexe/-/isexe-2.0.0.tgz",
|
||||
"integrity": "sha512-RHxMLp9lnKHGHRng9QFhRCMbYAcVpn69smSGcq3f36xjgVVWThj4qqLbTLlq7Ssj8B+fIQ1EuCEGI2lKsyQeIw==",
|
||||
"dev": true
|
||||
"version": "3.1.1",
|
||||
"resolved": "https://registry.npmjs.org/isexe/-/isexe-3.1.1.tgz",
|
||||
"integrity": "sha512-LpB/54B+/2J5hqQ7imZHfdU31OlgQqx7ZicVlkm9kzg9/w8GKLEcFfJl/t7DCEDueOyBAD6zCCwTO6Fzs0NoEQ=="
|
||||
},
|
||||
"istanbul-lib-coverage": {
|
||||
"version": "3.2.0",
|
||||
@@ -13677,12 +13729,11 @@
|
||||
}
|
||||
},
|
||||
"which": {
|
||||
"version": "2.0.2",
|
||||
"resolved": "https://registry.npmjs.org/which/-/which-2.0.2.tgz",
|
||||
"integrity": "sha512-BLI3Tl1TW3Pvl70l3yq3Y64i+awpwXqsGBYWkkqMtnbXgrMD+yj7rhW0kuEDxzJaYXGjEW5ogapKNMEKNMjibA==",
|
||||
"dev": true,
|
||||
"version": "4.0.0",
|
||||
"resolved": "https://registry.npmjs.org/which/-/which-4.0.0.tgz",
|
||||
"integrity": "sha512-GlaYyEb07DPxYCKhKzplCWBJtvxZcZMrL+4UkrTSJHHPyZU4mYYTv3qaOe77H7EODLSSopAUFAc6W8U4yqvscg==",
|
||||
"requires": {
|
||||
"isexe": "^2.0.0"
|
||||
"isexe": "^3.1.1"
|
||||
}
|
||||
},
|
||||
"which-boxed-primitive": {
|
||||
|
||||
@@ -40,6 +40,7 @@
|
||||
"ink": "^4.4.1",
|
||||
"node-pty": "^1.0.0",
|
||||
"react": "^18.2.0",
|
||||
"which": "^4.0.0",
|
||||
"wrap-ansi": "^8.1.0",
|
||||
"xterm-headless": "^5.3.0"
|
||||
},
|
||||
@@ -47,6 +48,7 @@
|
||||
"@tsconfig/node18": "^18.2.2",
|
||||
"@types/jest": "^29.5.5",
|
||||
"@types/react": "^18.2.24",
|
||||
"@types/which": "^3.0.3",
|
||||
"@typescript-eslint/eslint-plugin": "^6.7.4",
|
||||
"@typescript-eslint/parser": "^6.7.4",
|
||||
"@withfig/autocomplete-types": "^1.28.0",
|
||||
|
||||
@@ -1,7 +1,6 @@
|
||||
// Copyright (c) Microsoft Corporation.
|
||||
// Licensed under the MIT License.
|
||||
|
||||
import { initRender } from "../ui/ui-init.js";
|
||||
import { render } from "../ui/ui-root.js";
|
||||
import { Shell, supportedShells as shells } from "../utils/bindings.js";
|
||||
import { inferShell } from "../utils/shell.js";
|
||||
@@ -10,7 +9,10 @@ import { Command } from "commander";
|
||||
export const supportedShells = shells.join(", ");
|
||||
|
||||
export const action = (program: Command) => async () => {
|
||||
const shell = ((await inferShell()) ?? (await initRender()) ?? "") as unknown as Shell;
|
||||
const shell = (await inferShell()) as unknown as Shell | undefined;
|
||||
if (shell == null) {
|
||||
program.error(`Unable to identify shell, use the -s/--shell option to provide your shell`, { exitCode: 1 });
|
||||
}
|
||||
if (!shells.map((s) => s.valueOf()).includes(shell)) {
|
||||
program.error(`Unsupported shell: '${shell}', supported shells: ${supportedShells}`, { exitCode: 1 });
|
||||
}
|
||||
|
||||
+12
-6
@@ -11,6 +11,7 @@ import { IsTermOscPs, IstermOscPt, IstermPromptStart, IstermPromptEnd } from "..
|
||||
import xterm from "xterm-headless";
|
||||
import { CommandManager, CommandState } from "./commandManager.js";
|
||||
import log from "../utils/log.js";
|
||||
import { gitBashPath } from "../utils/shell.js";
|
||||
|
||||
const ISTermOnDataEvent = "data";
|
||||
|
||||
@@ -36,8 +37,8 @@ export class ISTerm implements IPty {
|
||||
readonly #term: xterm.Terminal;
|
||||
readonly #commandManager: CommandManager;
|
||||
|
||||
constructor({ shell, cols, rows, env }: ISTermOptions) {
|
||||
this.#pty = pty.spawn(convertToPtyTarget(shell), [], {
|
||||
constructor({ shell, cols, rows, env, shellTarget }: ISTermOptions & { shellTarget: string }) {
|
||||
this.#pty = pty.spawn(shellTarget, [], {
|
||||
name: "xterm-256color",
|
||||
cols,
|
||||
rows,
|
||||
@@ -130,12 +131,17 @@ export class ISTerm implements IPty {
|
||||
}
|
||||
}
|
||||
|
||||
export const spawn = (options: ISTermOptions): ISTerm => {
|
||||
return new ISTerm(options);
|
||||
export const spawn = async (options: ISTermOptions): Promise<ISTerm> => {
|
||||
const shellTarget = await convertToPtyTarget(options.shell);
|
||||
return new ISTerm({ ...options, shellTarget });
|
||||
};
|
||||
|
||||
const convertToPtyTarget = (shell: Shell): string => {
|
||||
return os.platform() == "win32" ? `${shell}.exe` : shell;
|
||||
const convertToPtyTarget = async (shell: Shell): Promise<string> => {
|
||||
const platform = os.platform();
|
||||
if (shell == Shell.Bash && platform == "win32") {
|
||||
return await gitBashPath();
|
||||
}
|
||||
return platform == "win32" ? `${shell}.exe` : shell;
|
||||
};
|
||||
|
||||
const convertToPtyEnv = (shell: Shell) => {
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
// Copyright (c) Microsoft Corporation.
|
||||
// Licensed under the MIT License.
|
||||
|
||||
import fsAsync from "fs/promises";
|
||||
import fsAsync from "node:fs/promises";
|
||||
import process from "node:process";
|
||||
|
||||
const filepathsTemplate = async (): Promise<Fig.Suggestion[]> => {
|
||||
|
||||
@@ -13,7 +13,7 @@ const bashEnv = { PS1: `${IstermPromptStart}\\u$ ${IstermPromptEnd}` };
|
||||
const zshEnv = { PROMPT: `%{${IstermPromptStart}%}%/ %# %{${IstermPromptEnd}%}` };
|
||||
|
||||
const runTerm = async (shell: Shell, input: string[], env?: { [key: string]: string | undefined }) => {
|
||||
const ptyProcess = isterm.spawn({ shell, rows: process.stdout.rows, cols: process.stdout.columns, env });
|
||||
const ptyProcess = await isterm.spawn({ shell, rows: process.stdout.rows, cols: process.stdout.columns, env });
|
||||
await new Promise((r) => setTimeout(r, 1_000));
|
||||
for (const data of input) {
|
||||
ptyProcess.write(data);
|
||||
|
||||
@@ -1,75 +0,0 @@
|
||||
// Copyright (c) Microsoft Corporation.
|
||||
// Licensed under the MIT License.
|
||||
|
||||
import React, { useState, useEffect } from "react";
|
||||
import { useInput, Text } from "ink";
|
||||
import chalk from "chalk";
|
||||
import { Suggestion } from "../runtime/model.js";
|
||||
|
||||
const BlinkSpeed = 530;
|
||||
const CursorColor = "#FFFFFF";
|
||||
|
||||
export default function Input({
|
||||
value,
|
||||
setValue,
|
||||
prompt,
|
||||
activeSuggestion,
|
||||
tabCompletionDropSize,
|
||||
}: {
|
||||
value: string;
|
||||
setValue: (_: string) => void;
|
||||
prompt: string;
|
||||
activeSuggestion: Suggestion | undefined;
|
||||
tabCompletionDropSize: number;
|
||||
}) {
|
||||
const [cursorLocation, setCursorLocation] = useState(value.length);
|
||||
const [cursorBlink, setCursorBlink] = useState(true);
|
||||
|
||||
useEffect(() => {
|
||||
setTimeout(() => {
|
||||
setCursorBlink(!cursorBlink);
|
||||
}, BlinkSpeed);
|
||||
}, [cursorBlink]);
|
||||
|
||||
// TODO: arrow key navigation shortcuts (emacs & vim modes)
|
||||
useInput((input, key) => {
|
||||
// TODO: handle delete better on unix systems: https://github.com/vadimdemedes/ink/issues/634
|
||||
const windows = process.platform === "win32";
|
||||
const backspaceKey = windows ? key.backspace : key.backspace || key.delete;
|
||||
const deleteKey = windows ? key.delete : false;
|
||||
|
||||
if (backspaceKey) {
|
||||
setValue([...value].slice(0, Math.max(cursorLocation - 1, 0)).join("") + [...value].slice(cursorLocation).join(""));
|
||||
setCursorLocation(Math.max(cursorLocation - 1, 0));
|
||||
} else if (deleteKey) {
|
||||
setValue([...value].slice(0, cursorLocation).join("") + [...value].slice(Math.min(value.length, cursorLocation + 1)).join(""));
|
||||
} else if (key.leftArrow) {
|
||||
setCursorLocation(Math.max(cursorLocation - 1, 0));
|
||||
} else if (key.rightArrow) {
|
||||
setCursorLocation(Math.min(cursorLocation + 1, value.length));
|
||||
} else if (key.tab) {
|
||||
if (activeSuggestion) {
|
||||
// TOOD: support insertValue
|
||||
const newValue = [...value].slice(0, cursorLocation - tabCompletionDropSize).join("") + activeSuggestion.name + " ";
|
||||
setValue(newValue);
|
||||
setCursorLocation(newValue.length);
|
||||
}
|
||||
} else if (input) {
|
||||
setValue([...value].slice(0, cursorLocation).join("") + input + [...value].slice(cursorLocation).join(""));
|
||||
setCursorLocation(cursorLocation + input.length);
|
||||
}
|
||||
});
|
||||
|
||||
const cursoredCommand = value + " ";
|
||||
const cursoredText =
|
||||
[...cursoredCommand].slice(0, cursorLocation).join("") +
|
||||
(cursorBlink ? chalk.bgHex(CursorColor).inverse([...cursoredCommand].at(cursorLocation)) : [...cursoredCommand].at(cursorLocation)) +
|
||||
[...cursoredCommand].slice(cursorLocation + 1).join("");
|
||||
|
||||
return (
|
||||
<Text>
|
||||
{prompt}
|
||||
{cursoredText}
|
||||
</Text>
|
||||
);
|
||||
}
|
||||
@@ -1,132 +0,0 @@
|
||||
// Copyright (c) Microsoft Corporation.
|
||||
// Licensed under the MIT License.
|
||||
|
||||
import React, { useState, useCallback, useEffect } from "react";
|
||||
import { Text, useInput, Box, measureElement, DOMElement } from "ink";
|
||||
import wrapAnsi from "wrap-ansi";
|
||||
import { Suggestion } from "../runtime/model.js";
|
||||
|
||||
const MaxSuggestions = 5;
|
||||
const SuggestionWidth = 40;
|
||||
const DescriptionWidth = 30;
|
||||
const DescriptionMaxHeight = 6;
|
||||
const BorderWidth = 2;
|
||||
const ActiveSuggestionBackgroundColor = "#7D56F4";
|
||||
|
||||
function Description({ description }: { description: string }) {
|
||||
wrapAnsi(description, DescriptionWidth, { hard: true });
|
||||
if (description.length !== 0) {
|
||||
return (
|
||||
<Box flexDirection="column">
|
||||
<Box borderStyle="single" width={DescriptionWidth}>
|
||||
<Text>{truncateDescription(description, DescriptionMaxHeight)}</Text>
|
||||
</Box>
|
||||
</Box>
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
function truncateDescription(description: string, maxHeight: number) {
|
||||
const wrappedText = wrapAnsi(description, DescriptionWidth - BorderWidth, {
|
||||
trim: false,
|
||||
hard: true,
|
||||
});
|
||||
const lines = wrappedText.split("\n");
|
||||
const truncatedLines = lines.slice(0, maxHeight);
|
||||
if (lines.length > maxHeight) {
|
||||
truncatedLines[maxHeight - 1] = [...truncatedLines[maxHeight - 1]].slice(0, -1).join("") + "…";
|
||||
}
|
||||
return truncatedLines.join("\n");
|
||||
}
|
||||
|
||||
function SuggestionList({ suggestions, activeSuggestionIdx }: { suggestions: Suggestion[]; activeSuggestionIdx: number }) {
|
||||
return (
|
||||
<Box flexDirection="column">
|
||||
<Box borderStyle="single" width={SuggestionWidth} flexDirection="column">
|
||||
{suggestions
|
||||
.map((suggestion, idx) => {
|
||||
const bgColor = idx === activeSuggestionIdx ? ActiveSuggestionBackgroundColor : undefined;
|
||||
|
||||
return (
|
||||
<Box key={idx}>
|
||||
<Text backgroundColor={bgColor} wrap="truncate-end">
|
||||
{`${suggestion.icon} ${suggestion.name}`.padEnd(SuggestionWidth - BorderWidth, " ")}
|
||||
</Text>
|
||||
</Box>
|
||||
);
|
||||
})
|
||||
.filter((node) => node !== undefined)}
|
||||
</Box>
|
||||
</Box>
|
||||
);
|
||||
}
|
||||
|
||||
export default function Suggestions({
|
||||
leftPadding,
|
||||
setActiveSuggestion,
|
||||
suggestions,
|
||||
}: {
|
||||
leftPadding: number;
|
||||
setActiveSuggestion: (_: Suggestion) => void;
|
||||
suggestions: Suggestion[];
|
||||
}) {
|
||||
const [activeSuggestionIndex, setActiveSuggestionIndex] = useState(0);
|
||||
const [windowWidth, setWindowWidth] = useState(500);
|
||||
|
||||
const page = Math.floor(activeSuggestionIndex / MaxSuggestions) + 1;
|
||||
const pagedSuggestions = suggestions.filter((_, idx) => idx < page * MaxSuggestions && idx >= (page - 1) * MaxSuggestions);
|
||||
const activePagedSuggestionIndex = activeSuggestionIndex % MaxSuggestions;
|
||||
const activeDescription = pagedSuggestions.at(activePagedSuggestionIndex)?.description || "";
|
||||
|
||||
const wrappedPadding = leftPadding % windowWidth;
|
||||
const maxPadding = activeDescription.length !== 0 ? windowWidth - SuggestionWidth - DescriptionWidth : windowWidth - SuggestionWidth;
|
||||
const swapDescription = wrappedPadding > maxPadding;
|
||||
const swappedPadding = swapDescription ? Math.max(wrappedPadding - DescriptionWidth, 0) : wrappedPadding;
|
||||
const clampedLeftPadding = Math.min(Math.min(wrappedPadding, swappedPadding), maxPadding);
|
||||
|
||||
useEffect(() => {
|
||||
setActiveSuggestion(suggestions[activeSuggestionIndex]);
|
||||
}, [activeSuggestionIndex, suggestions]);
|
||||
|
||||
useEffect(() => {
|
||||
if (suggestions.length <= activeSuggestionIndex) {
|
||||
setActiveSuggestionIndex(Math.max(suggestions.length - 1, 0));
|
||||
}
|
||||
}, [suggestions]);
|
||||
|
||||
useInput((_, key) => {
|
||||
if (key.upArrow) {
|
||||
setActiveSuggestionIndex(Math.max(0, activeSuggestionIndex - 1));
|
||||
}
|
||||
if (key.downArrow) {
|
||||
setActiveSuggestionIndex(Math.min(activeSuggestionIndex + 1, suggestions.length - 1));
|
||||
}
|
||||
});
|
||||
|
||||
const measureRef = useCallback((node: DOMElement) => {
|
||||
if (node !== null) {
|
||||
const { width } = measureElement(node);
|
||||
setWindowWidth(width);
|
||||
}
|
||||
}, []);
|
||||
|
||||
if (suggestions.length === 0) return <></>;
|
||||
|
||||
return (
|
||||
<Box ref={measureRef}>
|
||||
<Box paddingLeft={clampedLeftPadding}>
|
||||
{swapDescription ? (
|
||||
<>
|
||||
<Description description={activeDescription} />
|
||||
<SuggestionList suggestions={pagedSuggestions} activeSuggestionIdx={activePagedSuggestionIndex} />
|
||||
</>
|
||||
) : (
|
||||
<>
|
||||
<SuggestionList suggestions={pagedSuggestions} activeSuggestionIdx={activePagedSuggestionIndex} />
|
||||
<Description description={activeDescription} />
|
||||
</>
|
||||
)}
|
||||
</Box>
|
||||
</Box>
|
||||
);
|
||||
}
|
||||
@@ -1,64 +0,0 @@
|
||||
// Copyright (c) Microsoft Corporation.
|
||||
// Licensed under the MIT License.
|
||||
|
||||
import React, { useState } from "react";
|
||||
import { Box, Text, render, useApp, useInput } from "ink";
|
||||
|
||||
import { supportedShells } from "../utils/bindings.js";
|
||||
|
||||
let uiResult = undefined;
|
||||
|
||||
function UI() {
|
||||
const { exit } = useApp();
|
||||
const [selectionIdx, setSelectionIdx] = useState(0);
|
||||
const [exited, setExited] = useState(false);
|
||||
|
||||
useInput(async (_, key) => {
|
||||
if (key.upArrow) {
|
||||
setSelectionIdx(Math.max(0, selectionIdx - 1));
|
||||
} else if (key.downArrow) {
|
||||
setSelectionIdx(Math.min(supportedShells.length - 1, selectionIdx + 1));
|
||||
} else if (key.return) {
|
||||
uiResult = supportedShells[selectionIdx];
|
||||
setExited(true);
|
||||
setTimeout(exit, 0);
|
||||
}
|
||||
});
|
||||
|
||||
return (
|
||||
<>
|
||||
{exited ? null : (
|
||||
<Box flexDirection="column">
|
||||
<Box>
|
||||
<Text bold>Select your desired shell</Text>
|
||||
</Box>
|
||||
<Box flexDirection="column">
|
||||
{supportedShells.map((shell, idx) => {
|
||||
if (idx == selectionIdx) {
|
||||
return (
|
||||
<Text color="cyan" underline key={idx}>
|
||||
{">"} {shell}
|
||||
</Text>
|
||||
);
|
||||
}
|
||||
return (
|
||||
<Text key={idx}>
|
||||
{" "}
|
||||
{shell}
|
||||
</Text>
|
||||
);
|
||||
})}
|
||||
</Box>
|
||||
</Box>
|
||||
)}
|
||||
</>
|
||||
);
|
||||
}
|
||||
|
||||
export const initRender = async (): Promise<string | undefined> => {
|
||||
uiResult = undefined;
|
||||
const { waitUntilExit } = render(<UI />);
|
||||
await waitUntilExit();
|
||||
|
||||
return uiResult;
|
||||
};
|
||||
+1
-1
@@ -10,7 +10,7 @@ import ansi from "ansi-escapes";
|
||||
import { SuggestionManager, MAX_LINES } from "./suggestionManager.js";
|
||||
|
||||
export const render = async (shell: Shell) => {
|
||||
const term = isterm.spawn({ shell, rows: process.stdout.rows, cols: process.stdout.columns });
|
||||
const term = await isterm.spawn({ shell, rows: process.stdout.rows, cols: process.stdout.columns });
|
||||
const suggestionManager = new SuggestionManager(term);
|
||||
let hasActiveSuggestions = false;
|
||||
let previousSuggestionsColumns = 0;
|
||||
|
||||
@@ -3,10 +3,63 @@
|
||||
|
||||
import process from "node:process";
|
||||
import find from "find-process";
|
||||
import path from "node:path";
|
||||
import { supportedShells } from "./bindings.js";
|
||||
import which from "which";
|
||||
import fs from "node:fs";
|
||||
|
||||
export const inferShell = async () => {
|
||||
try {
|
||||
return path.parse(process.env.SHELL ?? "").name;
|
||||
} catch {
|
||||
/* empty */
|
||||
}
|
||||
const processResult = (await find("pid", process.ppid)).at(0);
|
||||
const name = processResult?.name;
|
||||
return name != null ? supportedShells.find((shell) => name.includes(shell)) : undefined;
|
||||
};
|
||||
|
||||
export const gitBashPath = async (): Promise<string> => {
|
||||
const gitBashPaths = await getGitBashPaths();
|
||||
for (const gitBashPath of gitBashPaths) {
|
||||
if (fs.existsSync(gitBashPath)) {
|
||||
return gitBashPath;
|
||||
}
|
||||
}
|
||||
throw new Error("unable to find a git bash executable installed");
|
||||
};
|
||||
|
||||
const getGitBashPaths = async (): Promise<string[]> => {
|
||||
const gitDirs: Set<string> = new Set();
|
||||
|
||||
const gitExePath = await which("git.exe", { nothrow: true });
|
||||
if (gitExePath) {
|
||||
const gitExeDir = path.dirname(gitExePath);
|
||||
gitDirs.add(path.resolve(gitExeDir, "../.."));
|
||||
}
|
||||
|
||||
const addValid = <T>(set: Set<T>, value: T | undefined) => {
|
||||
if (value) set.add(value);
|
||||
};
|
||||
|
||||
// Add common git install locations
|
||||
addValid(gitDirs, process.env["ProgramW6432"]);
|
||||
addValid(gitDirs, process.env["ProgramFiles"]);
|
||||
addValid(gitDirs, process.env["ProgramFiles(X86)"]);
|
||||
addValid(gitDirs, `${process.env["LocalAppData"]}\\Program`);
|
||||
|
||||
const gitBashPaths: string[] = [];
|
||||
for (const gitDir of gitDirs) {
|
||||
gitBashPaths.push(
|
||||
`${gitDir}\\Git\\bin\\bash.exe`,
|
||||
`${gitDir}\\Git\\usr\\bin\\bash.exe`,
|
||||
`${gitDir}\\usr\\bin\\bash.exe`, // using Git for Windows SDK
|
||||
);
|
||||
}
|
||||
|
||||
// Add special installs that don't follow the standard directory structure
|
||||
gitBashPaths.push(`${process.env["UserProfile"]}\\scoop\\apps\\git\\current\\bin\\bash.exe`);
|
||||
gitBashPaths.push(`${process.env["UserProfile"]}\\scoop\\apps\\git-with-openssh\\current\\bin\\bash.exe`);
|
||||
|
||||
return gitBashPaths;
|
||||
};
|
||||
|
||||
Reference in New Issue
Block a user