-
Notifications
You must be signed in to change notification settings - Fork 220
Expand file tree
/
Copy pathui-root.ts
More file actions
137 lines (115 loc) · 6.05 KB
/
ui-root.ts
File metadata and controls
137 lines (115 loc) · 6.05 KB
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
// Copyright (c) Microsoft Corporation.
// Licensed under the MIT License.
import readline from "node:readline";
import ansi from "ansi-escapes";
import chalk from "chalk";
import { Command } from "commander";
import log from "../utils/log.js";
import { getBackspaceSequence, Shell } from "../utils/shell.js";
import { enableWin32InputMode, resetToInitialState } from "../utils/ansi.js";
import { getMaxLines, type KeyPressEvent, type SuggestionManager } from "./suggestionManager.js";
import type { ISTerm } from "../isterm/pty.js";
export const renderConfirmation = (live: boolean): string => {
const statusMessage = live ? chalk.green("live") : chalk.red("not found");
return `inshellisense session [${statusMessage}]\n`;
};
export const renderMissingResources = (): string => {
return chalk.red(`inshellisense resources out of date, run "is reinit" to refresh\n`);
};
const writeOutput = (data: string) => {
log.debug({ msg: "writing data", data });
process.stdout.write(data);
};
const _suggestionLayout = (term: ISTerm): { direction: "above" | "below"; lines: number } => {
const maxLines = getMaxLines();
const { remainingLines, cursorY } = term.getCursorState();
const direction = remainingLines >= maxLines ? "below" : cursorY >= maxLines ? "above" : remainingLines >= cursorY ? "below" : "above";
const lines = direction === "above" ? Math.min(maxLines, cursorY) : Math.min(maxLines, remainingLines);
return { direction, lines };
};
const _render = (term: ISTerm, suggestionManager: SuggestionManager, data: string, handlingBackspace: boolean, handlingSuggestion: boolean): boolean => {
const { direction, lines } = _suggestionLayout(term);
const { hidden: cursorHidden, shift: cursorShift } = term.getCursorState();
const suggestion = suggestionManager.render(direction);
const hasSuggestion = suggestion.length != 0;
// there is no rendered suggestion and this will not render a suggestion
if (!handlingSuggestion && !hasSuggestion) {
writeOutput(data);
return false;
}
const commandState = term.getCommandState();
const cursorTerminated = handlingBackspace ? true : commandState.cursorTerminated ?? false;
const showSuggestions = hasSuggestion && cursorTerminated && !commandState.hasOutput && !cursorShift && !!commandState.commandText;
const patch = term.getPatch(lines, showSuggestions ? suggestion : [], direction);
const ansiCursorShow = cursorHidden ? "" : ansi.cursorShow;
if (direction == "above") {
writeOutput(data + ansi.cursorHide + ansi.cursorSavePosition + ansi.cursorPrevLine.repeat(lines) + patch + ansi.cursorRestorePosition + ansiCursorShow);
} else {
writeOutput(ansi.cursorHide + ansi.cursorSavePosition + ansi.cursorNextLine + patch + ansi.cursorRestorePosition + ansiCursorShow + data);
}
return showSuggestions;
};
const _clear = (term: ISTerm): void => {
const { direction } = _suggestionLayout(term);
const clearDirection = direction == "above" ? "below" : "above"; // invert direction to clear what was previously rendered
const { hidden: cursorHidden, cursorY, remainingLines } = term.getCursorState();
const lines = clearDirection === "above" ? Math.min(getMaxLines(), cursorY) : Math.min(getMaxLines(), remainingLines);
const patch = term.getPatch(lines, [], clearDirection);
const ansiCursorShow = cursorHidden ? "" : ansi.cursorShow;
if (clearDirection == "above") {
writeOutput(ansi.cursorHide + ansi.cursorSavePosition + ansi.cursorPrevLine.repeat(lines) + patch + ansi.cursorRestorePosition + ansiCursorShow);
} else {
writeOutput(ansi.cursorHide + ansi.cursorSavePosition + ansi.cursorNextLine + patch + ansi.cursorRestorePosition + ansiCursorShow);
}
};
export const render = async (program: Command, shell: Shell, underTest: boolean, login: boolean) => {
const [isterm, { SuggestionManager }] = await Promise.all([import("../isterm/index.js"), import("./suggestionManager.js")]);
const term = await isterm.default.spawn(program, { shell, rows: process.stdout.rows, cols: process.stdout.columns, underTest, login });
const suggestionManager = new SuggestionManager(term, shell);
let hasSuggestion = false;
let direction = _suggestionLayout(term).direction;
let handlingBackspace = false; // backspace normally consistent of two data points (move back & delete), so on the first data point, we won't enforce the cursor terminated rule. this will help reduce flicker
const stdinStartedInRawMode = process.stdin.isRaw;
if (process.stdin.isTTY) process.stdin.setRawMode(true);
readline.emitKeypressEvents(process.stdin);
const writeOutput = (data: string) => {
log.debug({ msg: "writing data", data });
process.stdout.write(data);
};
writeOutput(ansi.clearTerminal);
term.onData(async (data) => {
data = data.replace(enableWin32InputMode, ""); // remove win32-input-mode enable sequence if it comes through data
const handlingDirectionChange = direction != _suggestionLayout(term).direction;
// clear the previous suggestion if the direction has changed to avoid leftover suggestions
if (handlingDirectionChange) {
_clear(term);
}
hasSuggestion = _render(term, suggestionManager, data, handlingBackspace, hasSuggestion);
await suggestionManager.exec();
hasSuggestion = _render(term, suggestionManager, "", handlingBackspace, hasSuggestion);
handlingBackspace = false;
direction = _suggestionLayout(term).direction;
});
process.stdin.on("keypress", (...keyPress: KeyPressEvent) => {
const press = keyPress[1];
const inputHandled = suggestionManager.update(press);
if (hasSuggestion && inputHandled) {
term.noop();
} else if (!inputHandled) {
if (press.name == "backspace") {
handlingBackspace = true;
term.write(getBackspaceSequence(keyPress, shell));
} else {
term.write(press.sequence);
}
}
});
term.onExit(({ exitCode }) => {
if (!stdinStartedInRawMode) process.stdin.setRawMode(false);
process.stdout.write(resetToInitialState);
process.exit(exitCode);
});
process.stdout.on("resize", () => {
term.resize(process.stdout.columns, process.stdout.rows);
});
};