mirror of
https://github.com/wavetermdev/inshellisense.git
synced 2026-08-05 13:43:30 -07:00
feat: render suggestions in the UI
Signed-off-by: Chapman Pendery <cpendery@vt.edu>
This commit is contained in:
@@ -1,4 +1,4 @@
|
||||
import { render } from "@/ui/ui.js";
|
||||
import { render } from "../ui/ui.js";
|
||||
|
||||
export const action = () => {
|
||||
render();
|
||||
|
||||
@@ -3,3 +3,8 @@ export type Suggestion = {
|
||||
description?: string;
|
||||
icon: string;
|
||||
};
|
||||
|
||||
export type SuggestionBlob = {
|
||||
suggestions: Suggestion[];
|
||||
argumentDescription?: string;
|
||||
};
|
||||
|
||||
@@ -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,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
@@ -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
@@ -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
@@ -5,9 +5,6 @@
|
||||
"compilerOptions": {
|
||||
"jsx": "react",
|
||||
"outDir": "./build",
|
||||
"types": ["@withfig/autocomplete-types", "jest"],
|
||||
"paths": {
|
||||
"@/*": ["./src/*"]
|
||||
}
|
||||
"types": ["@withfig/autocomplete-types", "jest"]
|
||||
}
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user