You've already forked inshellisense
mirror of
https://github.com/wavetermdev/inshellisense.git
synced 2026-04-22 15:25:33 -07:00
feat: restore argument descriptions & tweak keystroke management (#94)
Signed-off-by: Chapman Pendery <cpendery@vt.edu>
This commit is contained in:
@@ -88,6 +88,10 @@ export class ISTerm implements IPty {
|
||||
return true;
|
||||
}
|
||||
|
||||
noop() {
|
||||
this.#ptyEmitter.emit(ISTermOnDataEvent, "");
|
||||
}
|
||||
|
||||
resize(columns: number, rows: number) {
|
||||
this.cols = columns;
|
||||
this.rows = rows;
|
||||
|
||||
+30
-29
@@ -4,14 +4,14 @@
|
||||
import { Suggestion, SuggestionBlob } from "../runtime/model.js";
|
||||
import { getSuggestions } from "../runtime/runtime.js";
|
||||
import { ISTerm } from "../isterm/pty.js";
|
||||
import { renderBox, truncateText } from "./utils.js";
|
||||
import { renderBox, truncateText, truncateMultilineText } from "./utils.js";
|
||||
import ansi from "ansi-escapes";
|
||||
import chalk from "chalk";
|
||||
import { parseKeystroke } from "../utils/ansi.js";
|
||||
|
||||
const maxSuggestions = 5;
|
||||
const suggestionWidth = 40;
|
||||
const descriptionWidth = 30;
|
||||
const descriptionHeight = 6;
|
||||
const borderWidth = 2;
|
||||
const activeSuggestionBackgroundColor = "#7D56F4";
|
||||
export const MAX_LINES = borderWidth + maxSuggestions;
|
||||
@@ -47,23 +47,15 @@ export class SuggestionManager {
|
||||
this.#suggestBlob = suggestionBlob;
|
||||
}
|
||||
|
||||
// if I want a 30 box, this means that
|
||||
private _renderArgumentDescription(description: string | undefined, x: number) {
|
||||
if (!description) return "";
|
||||
return renderBox([truncateText(description, descriptionWidth - borderWidth)], descriptionWidth, x);
|
||||
}
|
||||
|
||||
// normalBorder = Border{
|
||||
// Top: "─",
|
||||
// Bottom: "─",
|
||||
// Left: "│",
|
||||
// Right: "│",
|
||||
// TopLeft: "┌",
|
||||
// TopRight: "┐",
|
||||
// BottomLeft: "└",
|
||||
// BottomRight: "┘",
|
||||
// MiddleLeft: "├",
|
||||
// MiddleRight: "┤",
|
||||
// Middle: "┼",
|
||||
// MiddleTop: "┬",
|
||||
// MiddleBottom: "┴",
|
||||
// }
|
||||
private _renderDescription(description: string | undefined, x: number) {
|
||||
if (!description) return "";
|
||||
return renderBox(truncateMultilineText(description, descriptionWidth - borderWidth, descriptionHeight), descriptionWidth, x);
|
||||
}
|
||||
|
||||
private _renderSuggestions(suggestions: Suggestion[], activeSuggestionIdx: number, x: number) {
|
||||
return renderBox(
|
||||
@@ -80,13 +72,12 @@ export class SuggestionManager {
|
||||
async render(): Promise<SuggestionsSequence> {
|
||||
await this._loadSuggestions();
|
||||
if (!this.#suggestBlob) return { data: "", columns: 0 };
|
||||
const { suggestions } = this.#suggestBlob;
|
||||
const { suggestions, argumentDescription } = this.#suggestBlob;
|
||||
|
||||
const page = Math.min(Math.floor(this.#activeSuggestionIdx / maxSuggestions) + 1, Math.floor(suggestions.length / maxSuggestions) + 1);
|
||||
const pagedSuggestions = suggestions.filter((_, idx) => idx < page * maxSuggestions && idx >= (page - 1) * maxSuggestions);
|
||||
const activePagedSuggestionIndex = this.#activeSuggestionIdx % maxSuggestions;
|
||||
// const activeDescription = pagedSuggestions.at(activePagedSuggestionIndex)?.description || "";
|
||||
const activeDescription = "";
|
||||
const activeDescription = pagedSuggestions.at(activePagedSuggestionIndex)?.description || argumentDescription || "";
|
||||
|
||||
const wrappedPadding = this.#term.getCursorState().cursorX % this.#term.cols;
|
||||
const maxPadding = activeDescription.length !== 0 ? this.#term.cols - suggestionWidth - descriptionWidth : this.#term.cols - suggestionWidth;
|
||||
@@ -99,17 +90,27 @@ export class SuggestionManager {
|
||||
}
|
||||
|
||||
if (pagedSuggestions.length == 0) {
|
||||
if (argumentDescription != null) {
|
||||
return {
|
||||
data:
|
||||
ansi.cursorHide +
|
||||
ansi.cursorUp(2) +
|
||||
ansi.cursorForward(clampedLeftPadding) +
|
||||
this._renderArgumentDescription(argumentDescription, clampedLeftPadding),
|
||||
columns: 3,
|
||||
};
|
||||
}
|
||||
return { data: "", columns: 0 };
|
||||
}
|
||||
|
||||
const columnsUsed = pagedSuggestions.length + borderWidth;
|
||||
const ui = swapDescription
|
||||
? this._renderDescription(activeDescription, clampedLeftPadding) +
|
||||
this._renderSuggestions(pagedSuggestions, activePagedSuggestionIndex, clampedLeftPadding + descriptionWidth)
|
||||
: this._renderSuggestions(pagedSuggestions, activePagedSuggestionIndex, clampedLeftPadding) +
|
||||
this._renderDescription(activeDescription, clampedLeftPadding + suggestionWidth);
|
||||
return {
|
||||
data:
|
||||
ansi.cursorHide +
|
||||
ansi.cursorUp(columnsUsed - 1) +
|
||||
ansi.cursorForward(clampedLeftPadding) +
|
||||
this._renderSuggestions(pagedSuggestions, activePagedSuggestionIndex, clampedLeftPadding) +
|
||||
ansi.cursorShow,
|
||||
data: ansi.cursorHide + ansi.cursorUp(columnsUsed - 1) + ansi.cursorForward(clampedLeftPadding) + ui + ansi.cursorShow,
|
||||
columns: columnsUsed,
|
||||
};
|
||||
}
|
||||
@@ -124,11 +125,11 @@ export class SuggestionManager {
|
||||
} else if (keyStroke == "tab") {
|
||||
const removals = "\u007F".repeat(this.#suggestBlob?.charactersToDrop ?? 0);
|
||||
const chars = this.#suggestBlob?.suggestions.at(this.#activeSuggestionIdx)?.name + " ";
|
||||
if (this.#suggestBlob == null || !chars.trim()) {
|
||||
if (this.#suggestBlob == null || !chars.trim() || this.#suggestBlob?.suggestions.length == 0) {
|
||||
return false;
|
||||
}
|
||||
this.#term.write(removals + chars);
|
||||
} else if (keyStroke == "ctrl-space") {
|
||||
} else if (keyStroke == "right-arrow") {
|
||||
this.#term.write("\t");
|
||||
return "fully-handled";
|
||||
}
|
||||
|
||||
+1
-1
@@ -91,7 +91,7 @@ export const render = async (shell: Shell) => {
|
||||
process.stdin.on("data", (d: Buffer) => {
|
||||
const suggestionResult = suggestionManager.update(d);
|
||||
if (previousSuggestionsColumns > 0 && suggestionResult == "handled") {
|
||||
term.write("\u001B[m");
|
||||
term.noop();
|
||||
} else if (!suggestionResult) {
|
||||
term.write(inputModifier(d));
|
||||
}
|
||||
|
||||
+17
-3
@@ -2,6 +2,7 @@
|
||||
// Licensed under the MIT License.
|
||||
|
||||
import ansi from "ansi-escapes";
|
||||
import wrapAnsi from "wrap-ansi";
|
||||
import chalk from "chalk";
|
||||
|
||||
/**
|
||||
@@ -13,12 +14,25 @@ import chalk from "chalk";
|
||||
export const renderBox = (rows: string[], width: number, x: number, borderColor?: string) => {
|
||||
const result = [];
|
||||
const setColor = (text: string) => (borderColor ? chalk.hex(borderColor).apply(text) : text);
|
||||
result.push(setColor("┌" + "─".repeat(width - 2) + "┐") + ansi.cursorTo(x));
|
||||
result.push(ansi.cursorTo(x) + setColor("┌" + "─".repeat(width - 2) + "┐") + ansi.cursorTo(x));
|
||||
rows.forEach((row) => {
|
||||
result.push(ansi.cursorDown() + setColor("│") + row + setColor("│") + ansi.cursorTo(x));
|
||||
});
|
||||
result.push(ansi.cursorDown() + setColor("└" + "─".repeat(width - 2) + "┘") + ansi.cursorTo(x));
|
||||
return result.join("");
|
||||
return result.join("") + ansi.cursorUp(rows.length + 1);
|
||||
};
|
||||
|
||||
export const truncateMultilineText = (description: string, width: number, maxHeight: number) => {
|
||||
const wrappedText = wrapAnsi(description, width, {
|
||||
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.map((line) => line.padEnd(width));
|
||||
};
|
||||
|
||||
/**
|
||||
@@ -27,5 +41,5 @@ export const renderBox = (rows: string[], width: number, x: number, borderColor?
|
||||
export const truncateText = (text: string, width: number) => {
|
||||
const textPoints = [...text];
|
||||
const slicedText = textPoints.slice(0, width - 1);
|
||||
return slicedText.length == textPoints.length ? text : slicedText.join("") + "…";
|
||||
return slicedText.length == textPoints.length ? text.padEnd(width) : (slicedText.join("") + "…").padEnd(width);
|
||||
};
|
||||
|
||||
+6
-5
@@ -4,6 +4,7 @@
|
||||
const CSI = "\u001B[";
|
||||
const OSC = "\u001B]";
|
||||
const BEL = "\u0007";
|
||||
const SS3 = "\u001BO";
|
||||
|
||||
export const IsTermOscPs = 6973;
|
||||
const IS_OSC = OSC + IsTermOscPs + ";";
|
||||
@@ -33,7 +34,7 @@ export const eraseLinesBelow = (count = 1) => {
|
||||
return [...Array(count).keys()].map(() => cursorNextLine + eraseLine).join("");
|
||||
};
|
||||
|
||||
export const parseKeystroke = (b: Buffer): "up" | "down" | "tab" | "ctrl-space" | undefined => {
|
||||
export const parseKeystroke = (b: Buffer): "up" | "down" | "tab" | "right-arrow" | undefined => {
|
||||
let s: string;
|
||||
if (b[0] > 127 && b[1] === undefined) {
|
||||
b[0] -= 128;
|
||||
@@ -42,13 +43,13 @@ export const parseKeystroke = (b: Buffer): "up" | "down" | "tab" | "ctrl-space"
|
||||
s = String(b);
|
||||
}
|
||||
|
||||
if (s == CSI + "A") {
|
||||
if (s == CSI + "A" || s == SS3 + "A") {
|
||||
return "up";
|
||||
} else if (s == CSI + "B") {
|
||||
} else if (s == CSI + "B" || s == SS3 + "B") {
|
||||
return "down";
|
||||
} else if (s == "\t") {
|
||||
return "tab";
|
||||
} else if (s == "\u0000") {
|
||||
return "ctrl-space";
|
||||
} else if (s == CSI + "D" || s == SS3 + "D" || s == CSI + "d" || s == SS3 + "d") {
|
||||
return "right-arrow";
|
||||
}
|
||||
};
|
||||
|
||||
Reference in New Issue
Block a user