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}
-
-
+