feat: add arrow navigation support to input field

Signed-off-by: Chapman Pendery <cpendery@vt.edu>
This commit is contained in:
Chapman Pendery
2023-10-07 11:41:33 -07:00
parent 9b1ba7683e
commit e6d38239bc
4 changed files with 55 additions and 17 deletions
+1
View File
@@ -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",
+2 -1
View File
@@ -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"
}
}
}
+47
View File
@@ -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 (
<Text>
{prompt}
{cursoredText}
</Text>
);
}
+5 -16
View File
@@ -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<Suggestion>();
@@ -28,23 +29,11 @@ function UI() {
});
}, [command]);
useInput((input, key) => {
if (key.backspace) {
setCommand([...command].slice(0, -1).join(""));
} else {
setCommand(command + input);
}
});
return (
<Box flexDirection="column" ref={measureRef}>
<Box>
<Text>
<Text>
{Prompt}
{command}
</Text>
<Cursor />
<Input value={command} setValue={setCommand} prompt={Prompt} />
</Text>
</Box>
<Suggestions leftPadding={leftPadding} setActiveSuggestion={setActiveSuggestion} suggestions={suggestions} />