From ec0798af907fc8a8ece70c954671bf34d03954f6 Mon Sep 17 00:00:00 2001 From: Chapman Pendery <35637443+cpendery@users.noreply.github.com> Date: Thu, 15 Feb 2024 12:37:59 -0500 Subject: [PATCH] fix: batched keypress events failed to be recognized. (#167) * fix: use 'keypress' event instead of 'data' and use readline's built in parsing to support batched key actions Signed-off-by: Chapman Pendery * fix: drop coming pr code Signed-off-by: Chapman Pendery --------- Signed-off-by: Chapman Pendery --- src/ui/input.ts | 10 ---------- src/ui/suggestionManager.ts | 27 ++++++++++++++++++--------- src/ui/ui-root.ts | 19 +++++++++++-------- src/utils/ansi.ts | 23 +---------------------- 4 files changed, 30 insertions(+), 49 deletions(-) delete mode 100644 src/ui/input.ts diff --git a/src/ui/input.ts b/src/ui/input.ts deleted file mode 100644 index f6b8ada..0000000 --- a/src/ui/input.ts +++ /dev/null @@ -1,10 +0,0 @@ -// Copyright (c) Microsoft Corporation. -// Licensed under the MIT License. - -export const inputModifier = (input: Buffer): string => { - switch (input.toString()) { - case "\b": - return "\u007F"; // DEL - } - return input.toString(); -}; diff --git a/src/ui/suggestionManager.ts b/src/ui/suggestionManager.ts index ec07594..e84a949 100644 --- a/src/ui/suggestionManager.ts +++ b/src/ui/suggestionManager.ts @@ -7,8 +7,8 @@ import { ISTerm } from "../isterm/pty.js"; import { renderBox, truncateText, truncateMultilineText } from "./utils.js"; import ansi from "ansi-escapes"; import chalk from "chalk"; -import { parseKeystroke } from "../utils/ansi.js"; import { Shell } from "../utils/shell.js"; +import log from "../utils/log.js"; const maxSuggestions = 5; const suggestionWidth = 40; @@ -22,6 +22,13 @@ type SuggestionsSequence = { rows: number; }; +export type KeyPressEvent = [string | null | undefined, KeyPress]; + +type KeyPress = { + sequence: string; + name: string; +}; + export class SuggestionManager { #term: ISTerm; #command: string; @@ -140,16 +147,15 @@ export class SuggestionManager { }; } - update(input: Buffer): "handled" | "fully-handled" | false { - const keyStroke = parseKeystroke(input); - if (keyStroke == null) return false; - if (keyStroke == "esc") { + update(keyPress: KeyPress): boolean { + const { name } = keyPress; + if (name == "escape") { this.#suggestBlob = undefined; - } else if (keyStroke == "up") { + } else if (name == "up") { this.#activeSuggestionIdx = Math.max(0, this.#activeSuggestionIdx - 1); - } else if (keyStroke == "down") { + } else if (name == "down") { this.#activeSuggestionIdx = Math.min(this.#activeSuggestionIdx + 1, (this.#suggestBlob?.suggestions.length ?? 1) - 1); - } else if (keyStroke == "tab") { + } else if (name == "tab") { const removals = "\u007F".repeat(this.#suggestBlob?.charactersToDrop ?? 0); const suggestion = this.#suggestBlob?.suggestions.at(this.#activeSuggestionIdx); const chars = suggestion?.insertValue ?? suggestion?.name + " "; @@ -157,7 +163,10 @@ export class SuggestionManager { return false; } this.#term.write(removals + chars); + } else { + return false; } - return "handled"; + log.debug({ msg: "handled keypress", ...keyPress }); + return true; } } diff --git a/src/ui/ui-root.ts b/src/ui/ui-root.ts index 787b8f8..20d8854 100644 --- a/src/ui/ui-root.ts +++ b/src/ui/ui-root.ts @@ -1,15 +1,15 @@ // Copyright (c) Microsoft Corporation. // Licensed under the MIT License. +import readline from "node:readline"; import ansi from "ansi-escapes"; import chalk from "chalk"; -import { inputModifier } from "./input.js"; import log from "../utils/log.js"; import { Shell } from "../utils/shell.js"; import isterm from "../isterm/index.js"; import { eraseLinesBelow } from "../utils/ansi.js"; -import { SuggestionManager, MAX_LINES } from "./suggestionManager.js"; +import { SuggestionManager, MAX_LINES, KeyPressEvent } from "./suggestionManager.js"; export const renderConfirmation = (live: boolean): string => { const statusMessage = live ? chalk.green("live") : chalk.red("not found"); @@ -21,7 +21,8 @@ export const render = async (shell: Shell) => { const suggestionManager = new SuggestionManager(term, shell); let hasActiveSuggestions = false; let previousSuggestionsRows = 0; - process.stdin.setRawMode(true); + if (process.stdin.isTTY) process.stdin.setRawMode(true); + readline.emitKeypressEvents(process.stdin); const writeOutput = (data: string) => { log.debug({ msg: "writing data", data }); @@ -122,12 +123,14 @@ export const render = async (shell: Shell) => { previousSuggestionsRows = suggestion.rows; }); }); - process.stdin.on("data", (d: Buffer) => { - const suggestionResult = suggestionManager.update(d); - if (previousSuggestionsRows > 0 && suggestionResult == "handled") { + + process.stdin.on("keypress", (...keyPress: KeyPressEvent) => { + const press = keyPress[1]; + const inputHandled = suggestionManager.update(press); + if (previousSuggestionsRows > 0 && inputHandled) { term.noop(); - } else if (suggestionResult != "fully-handled") { - term.write(inputModifier(d)); + } else if (!inputHandled) { + term.write(press.sequence); } }); diff --git a/src/utils/ansi.ts b/src/utils/ansi.ts index 6169606..c869913 100644 --- a/src/utils/ansi.ts +++ b/src/utils/ansi.ts @@ -2,10 +2,9 @@ // Licensed under the MIT License. const ESC = "\u001B"; -const CSI = "\u001B["; +const CSI = ESC + "["; const OSC = "\u001B]"; const BEL = "\u0007"; -const SS3 = "\u001BO"; export const IsTermOscPs = 6973; const IS_OSC = OSC + IsTermOscPs + ";"; @@ -35,23 +34,3 @@ export const scrollDown = (count = 1) => CSI + count + "T"; export const eraseLinesBelow = (count = 1) => { return [...Array(count).keys()].map(() => cursorNextLine + eraseLine).join(""); }; - -export const parseKeystroke = (b: Buffer): "up" | "down" | "tab" | "esc" | undefined => { - let s: string; - if (b[0] > 127 && b[1] === undefined) { - b[0] -= 128; - s = "\u001B" + String(b); - } else { - s = String(b); - } - - if (s == ESC) { - return "esc"; - } else if (s == CSI + "A" || s == SS3 + "A") { - return "up"; - } else if (s == CSI + "B" || s == SS3 + "B") { - return "down"; - } else if (s == "\t") { - return "tab"; - } -};