feat: render suggestions in the UI

Signed-off-by: Chapman Pendery <cpendery@vt.edu>
This commit is contained in:
Chapman Pendery
2023-10-06 21:02:42 -07:00
parent 65418a89d5
commit 703857091f
7 changed files with 49 additions and 89 deletions
+1 -1
View File
@@ -1,4 +1,4 @@
import { render } from "@/ui/ui.js";
import { render } from "../ui/ui.js";
export const action = () => {
render();
+5
View File
@@ -3,3 +3,8 @@ export type Suggestion = {
description?: string;
icon: string;
};
export type SuggestionBlob = {
suggestions: Suggestion[];
argumentDescription?: string;
};
+2 -1
View File
@@ -3,7 +3,8 @@ import speclist, {
// @ts-ignore
} from "@withfig/autocomplete/build/index.js";
import { parseCommand, CommandToken } from "./parser.js";
import { SuggestionBlob, getArgDrivenRecommendation, getSubcommandDrivenRecommendation } from "./suggestion.js";
import { getArgDrivenRecommendation, getSubcommandDrivenRecommendation } from "./suggestion.js";
import { SuggestionBlob } from "./model.js";
const specSet: any = {};
(speclist as string[]).forEach((s) => {
+1 -6
View File
@@ -1,7 +1,7 @@
import { CommandToken } from "./parser.js";
import { runGenerator } from "./generator.js";
import { runTemplates } from "./template.js";
import { Suggestion } from "./model.js";
import { Suggestion, SuggestionBlob } from "./model.js";
// TODO: support other suggestion attributes
enum SuggestionIcons {
@@ -89,11 +89,6 @@ function filter<T extends Fig.BaseSuggestion & { name?: Fig.SingleOrArray<string
}
}
export type SuggestionBlob = {
suggestions: Suggestion[];
argumentDescription?: string;
};
type FilterStrategy = "fuzzy" | "prefix" | "default";
const generatorSuggestions = async (
+27 -55
View File
@@ -1,5 +1,6 @@
import React, { useState, useCallback } from "react";
import { Text, useInput, Box, measureElement, DOMElement } from "ink";
import { Suggestion } from "../runtime/model.js";
const MaxSuggestions = 5;
const SuggestionWidth = 40;
@@ -19,34 +20,25 @@ function Description({ description }: { description: string }) {
}
}
function SuggestionList({
suggestions,
activeSuggestionIdx,
}: {
suggestions: Fig.Suggestion[];
activeSuggestionIdx: number;
}) {
function SuggestionList({ suggestions, activeSuggestionIdx }: { suggestions: Suggestion[]; activeSuggestionIdx: number }) {
return (
<Box borderStyle="single" width={SuggestionWidth} flexDirection="column">
{suggestions.map((suggestion, idx) => {
const bgColor =
idx === activeSuggestionIdx
? ActiveSuggestionBackgroundColor
: undefined;
<Box flexDirection="column">
<Box borderStyle="single" width={SuggestionWidth} flexDirection="column">
{suggestions.map((suggestion, idx) => {
const bgColor = idx === activeSuggestionIdx ? ActiveSuggestionBackgroundColor : undefined;
const rawName = suggestion.displayName ?? (suggestion.name || "");
const name =
typeof rawName === "string" ? rawName : rawName.at(0) || "";
if (name.length === 0) return <></>;
const name = suggestion.name;
if (name.length === 0) return <></>;
return (
<Box key={idx}>
<Text backgroundColor={bgColor} wrap="truncate-end">
{name.padEnd(SuggestionWidth - BorderWidth, " ")}
</Text>
</Box>
);
})}
return (
<Box key={idx}>
<Text backgroundColor={bgColor} wrap="truncate-end">
{name.padEnd(SuggestionWidth - BorderWidth, " ")}
</Text>
</Box>
);
})}
</Box>
</Box>
);
}
@@ -57,39 +49,27 @@ export default function Suggestions({
suggestions,
}: {
leftPadding: number;
setActiveSuggestion: (_: Fig.Suggestion) => void;
suggestions: Fig.Suggestion[];
setActiveSuggestion: (_: Suggestion) => void;
suggestions: Suggestion[];
}) {
const [activeSuggestionIndex, setActiveSuggestionIndex] = useState(0);
const [windowWidth, setWindowWidth] = useState(500);
const page = Math.floor(activeSuggestionIndex / MaxSuggestions) + 1;
const pagedSuggestions = suggestions.filter(
(_, idx) =>
idx < page * MaxSuggestions && idx >= (page - 1) * MaxSuggestions
);
const pagedSuggestions = suggestions.filter((_, idx) => idx < page * MaxSuggestions && idx >= (page - 1) * MaxSuggestions);
const activePagedSuggestionIndex = activeSuggestionIndex % MaxSuggestions;
const activeDescription =
pagedSuggestions.at(activePagedSuggestionIndex)?.description || "";
const activeDescription = pagedSuggestions.at(activePagedSuggestionIndex)?.description || "";
// TODO: tweak this logic to be more accurate as it gives bad offsets on wrap
const wrappedPadding = leftPadding % windowWidth;
const maxPadding =
activeDescription.length !== 0
? windowWidth - SuggestionWidth - DescriptionWidth
: windowWidth - SuggestionWidth;
const maxPadding = activeDescription.length !== 0 ? windowWidth - SuggestionWidth - DescriptionWidth : windowWidth - SuggestionWidth;
const swapDescription = wrappedPadding > maxPadding;
const swappedPadding = swapDescription
? Math.max(wrappedPadding - DescriptionWidth, 0)
: wrappedPadding;
const swappedPadding = swapDescription ? Math.max(wrappedPadding - DescriptionWidth, 0) : wrappedPadding;
const clampedLeftPadding = Math.min(
Math.min(wrappedPadding, swappedPadding),
maxPadding
);
const clampedLeftPadding = Math.min(Math.min(wrappedPadding, swappedPadding), maxPadding);
useInput((_, key) => {
if (key.upArrow) {
@@ -97,9 +77,7 @@ export default function Suggestions({
setActiveSuggestion(suggestions[activeSuggestionIndex]);
}
if (key.downArrow) {
setActiveSuggestionIndex(
Math.min(activeSuggestionIndex + 1, suggestions.length - 1)
);
setActiveSuggestionIndex(Math.min(activeSuggestionIndex + 1, suggestions.length - 1));
setActiveSuggestion(suggestions[activeSuggestionIndex]);
}
});
@@ -119,17 +97,11 @@ export default function Suggestions({
{swapDescription ? (
<>
<Description description={activeDescription} />
<SuggestionList
suggestions={pagedSuggestions}
activeSuggestionIdx={activePagedSuggestionIndex}
/>
<SuggestionList suggestions={pagedSuggestions} activeSuggestionIdx={activePagedSuggestionIndex} />
</>
) : (
<>
<SuggestionList
suggestions={pagedSuggestions}
activeSuggestionIdx={activePagedSuggestionIndex}
/>
<SuggestionList suggestions={pagedSuggestions} activeSuggestionIdx={activePagedSuggestionIndex} />
<Description description={activeDescription} />
</>
)}
+12 -22
View File
@@ -1,23 +1,19 @@
import React, { useCallback, useEffect, useState } from "react";
import {
Text,
Box,
useInput,
render as inkRender,
measureElement,
DOMElement,
} from "ink";
import { Text, Box, useInput, render as inkRender, measureElement, DOMElement } from "ink";
import wrapAnsi from "wrap-ansi";
import { getSuggestions } from "@/runtime/runtime.js";
import { getSuggestions } from "../runtime/runtime.js";
import { Suggestion } from "../runtime/model.js";
import Cursor from "./cursor.js";
import Suggestions from "./suggestions.js";
// TODO: fix issue where we are not rendering some rows of the suggestion UI
const Prompt = "> ";
function UI() {
const [command, setCommand] = useState("");
const [activeSuggestion, setActiveSuggestion] = useState<Fig.Suggestion>();
const [suggestions, setSuggestions] = useState<Fig.Suggestion[]>([]);
const [activeSuggestion, setActiveSuggestion] = useState<Suggestion>();
const [suggestions, setSuggestions] = useState<Suggestion[]>([]);
const [windowWidth, setWindowWidth] = useState(500);
const leftPadding = getLeftPadding(windowWidth, command);
@@ -29,7 +25,9 @@ function UI() {
}, []);
useEffect(() => {
setSuggestions(getSuggestions(command));
getSuggestions(command).then((suggestions) => {
setSuggestions(suggestions?.suggestions ?? []);
});
}, [command]);
useInput((input, key) => {
@@ -51,11 +49,7 @@ function UI() {
<Cursor />
</Text>
</Box>
<Suggestions
leftPadding={leftPadding}
setActiveSuggestion={setActiveSuggestion}
suggestions={suggestions}
/>
<Suggestions leftPadding={leftPadding} setActiveSuggestion={setActiveSuggestion} suggestions={suggestions} />
</Box>
);
}
@@ -71,9 +65,5 @@ function getLeftPadding(windowWidth: number, command: string) {
hard: true,
});
const lines = wrappedText.split("\n");
return (
(lines.length - 1) * windowWidth +
lines[lines.length - 1].length +
Prompt.length
);
return (lines.length - 1) * windowWidth + lines[lines.length - 1].length + Prompt.length;
}
+1 -4
View File
@@ -5,9 +5,6 @@
"compilerOptions": {
"jsx": "react",
"outDir": "./build",
"types": ["@withfig/autocomplete-types", "jest"],
"paths": {
"@/*": ["./src/*"]
}
"types": ["@withfig/autocomplete-types", "jest"]
}
}