From e6d38239bc41ff447b7266f447cfd6615dfe9f6e Mon Sep 17 00:00:00 2001 From: Chapman Pendery Date: Sat, 7 Oct 2023 11:41:33 -0700 Subject: [PATCH] feat: add arrow navigation support to input field Signed-off-by: Chapman Pendery --- package-lock.json | 1 + package.json | 3 ++- src/ui/input.tsx | 47 +++++++++++++++++++++++++++++++++++++++++++++++ src/ui/ui.tsx | 21 +++++---------------- 4 files changed, 55 insertions(+), 17 deletions(-) create mode 100644 src/ui/input.tsx diff --git a/package-lock.json b/package-lock.json index 38842bc..c319c61 100644 --- a/package-lock.json +++ b/package-lock.json @@ -10,6 +10,7 @@ "license": "MIT", "dependencies": { "@withfig/autocomplete": "^2.633.0", + "chalk": "^5.3.0", "commander": "^11.0.0", "ink": "^4.4.1", "react": "^18.2.0", diff --git a/package.json b/package.json index c973c09..ab9328c 100644 --- a/package.json +++ b/package.json @@ -23,6 +23,7 @@ "homepage": "https://github.com/microsoft/clac#readme", "dependencies": { "@withfig/autocomplete": "^2.633.0", + "chalk": "^5.3.0", "commander": "^11.0.0", "ink": "^4.4.1", "react": "^18.2.0", @@ -38,4 +39,4 @@ "ts-jest": "^29.1.1", "typescript": "^5.2.2" } -} \ No newline at end of file +} diff --git a/src/ui/input.tsx b/src/ui/input.tsx new file mode 100644 index 0000000..1fe4d8b --- /dev/null +++ b/src/ui/input.tsx @@ -0,0 +1,47 @@ +import React, { useState, useEffect } from "react"; +import { useInput, Text } from "ink"; +import chalk from "chalk"; + +const BlinkSpeed = 530; +const CursorColor = "#FFFFFF"; + +export default function Input({ value, setValue, prompt }: { value: string; setValue: (_: string) => void; prompt: string }) { + 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) => { + if (key.backspace) { + setValue([...value].slice(0, Math.max(cursorLocation - 1, 0)).join("") + [...value].slice(cursorLocation).join("")); + setCursorLocation(Math.max(cursorLocation - 1, 0)); + } else if (key.delete) { + 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 (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 ( + + {prompt} + {cursoredText} + + ); +} diff --git a/src/ui/ui.tsx b/src/ui/ui.tsx index 2257402..fe7fe11 100644 --- a/src/ui/ui.tsx +++ b/src/ui/ui.tsx @@ -1,13 +1,14 @@ import React, { useCallback, useEffect, useState } from "react"; -import { Text, Box, useInput, render as inkRender, measureElement, DOMElement } from "ink"; +import { Text, Box, render as inkRender, measureElement, DOMElement, useApp } from "ink"; import wrapAnsi from "wrap-ansi"; + import { getSuggestions } from "../runtime/runtime.js"; import { Suggestion } from "../runtime/model.js"; -import Cursor from "./cursor.js"; import Suggestions from "./suggestions.js"; - +import Input from "./input.js"; const Prompt = "> "; +// TODO: support tab completion function UI() { const [command, setCommand] = useState(""); const [activeSuggestion, setActiveSuggestion] = useState(); @@ -28,23 +29,11 @@ function UI() { }); }, [command]); - useInput((input, key) => { - if (key.backspace) { - setCommand([...command].slice(0, -1).join("")); - } else { - setCommand(command + input); - } - }); - return ( - - {Prompt} - {command} - - +