Skip to content

Commit

Permalink
feat: restore argument descriptions & tweak keystroke management (#94)
Browse files Browse the repository at this point in the history
Signed-off-by: Chapman Pendery <cpendery@vt.edu>
  • Loading branch information
cpendery authored Nov 28, 2023
1 parent ba58fc5 commit 4e5d7de
Show file tree
Hide file tree
Showing 5 changed files with 58 additions and 38 deletions.
4 changes: 4 additions & 0 deletions src/isterm/pty.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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;
Expand Down
59 changes: 30 additions & 29 deletions src/ui/suggestionManager.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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;
Expand Down Expand Up @@ -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(
Expand All @@ -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;
Expand All @@ -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,
};
}
Expand All @@ -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";
}
Expand Down
2 changes: 1 addition & 1 deletion src/ui/ui-root.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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));
}
Expand Down
20 changes: 17 additions & 3 deletions src/ui/utils.ts
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,7 @@
// Licensed under the MIT License.

import ansi from "ansi-escapes";
import wrapAnsi from "wrap-ansi";
import chalk from "chalk";

/**
Expand All @@ -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));
};

/**
Expand All @@ -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);
};
11 changes: 6 additions & 5 deletions src/utils/ansi.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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 + ";";
Expand Down Expand Up @@ -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;
Expand All @@ -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";
}
};

0 comments on commit 4e5d7de

Please sign in to comment.