feat(tui): added file-path completion support to autocomplete provider
- Added `getForceFileSuggestions` and `shouldTriggerFileCompletion` to `AutocompleteProvider` interface. - Updated `Editor` to invoke file completion logic dynamically when providers support it. - Corrected log viewer scroll offset calculation to account for viewport height. - Added comprehensive unit test for log row selection and expansion.
This commit is contained in:
@@ -641,7 +641,7 @@ export class DebugLogViewerComponent implements Component {
|
||||
const overBody = event.row >= this.#bodyRowStart && event.row < this.#bodyRowStart + this.#bodyRowCount;
|
||||
if (event.wheel !== null && overBody) {
|
||||
this.#statusMessage = undefined;
|
||||
const maxOffset = Math.max(0, this.#model.rows.length - 1);
|
||||
const maxOffset = Math.max(0, this.#model.rows.length - this.#bodyHeight());
|
||||
this.#scrollRowOffset = Math.max(0, Math.min(maxOffset, this.#scrollRowOffset + event.wheel * 3));
|
||||
this.#onUpdate?.();
|
||||
return true;
|
||||
|
||||
@@ -1,10 +1,18 @@
|
||||
import { describe, expect, it } from "bun:test";
|
||||
import { beforeAll, describe, expect, it } from "bun:test";
|
||||
import {
|
||||
buildLogCopyPayload,
|
||||
DebugLogViewerComponent,
|
||||
DebugLogViewerModel,
|
||||
LOAD_OLDER_LABEL,
|
||||
SESSION_BOUNDARY_WARNING,
|
||||
} from "@oh-my-pi/pi-coding-agent/debug/log-viewer";
|
||||
import { getThemeByName, setThemeInstance } from "@oh-my-pi/pi-coding-agent/modes/theme/theme";
|
||||
|
||||
beforeAll(async () => {
|
||||
const theme = await getThemeByName("dark");
|
||||
if (!theme) throw new Error("Expected dark theme");
|
||||
setThemeInstance(theme);
|
||||
});
|
||||
|
||||
describe("DebugLogViewerModel", () => {
|
||||
const describeRow = (row: { kind: string; logIndex?: number }): string => {
|
||||
@@ -190,6 +198,28 @@ describe("DebugLogViewerModel", () => {
|
||||
expect(model.isExpanded(0)).toBe(false);
|
||||
expect(model.isExpanded(1)).toBe(false);
|
||||
});
|
||||
|
||||
it("selects and expands a clicked visible log row", () => {
|
||||
let updates = 0;
|
||||
const viewer = new DebugLogViewerComponent({
|
||||
logs: ["alpha", "beta", "gamma"].join("\n"),
|
||||
terminalRows: 12,
|
||||
onExit: () => {},
|
||||
onUpdate: () => {
|
||||
updates++;
|
||||
},
|
||||
});
|
||||
|
||||
viewer.render(80);
|
||||
viewer.handleInput("\x1b[<0;2;6M");
|
||||
|
||||
const rendered = viewer
|
||||
.render(80)
|
||||
.map(line => Bun.stripANSI(line))
|
||||
.join("\n");
|
||||
expect(rendered).toContain("▾ beta");
|
||||
expect(updates).toBe(1);
|
||||
});
|
||||
});
|
||||
|
||||
describe("buildLogCopyPayload", () => {
|
||||
|
||||
@@ -230,6 +230,20 @@ export interface AutocompleteProvider {
|
||||
* buffer untouched.
|
||||
*/
|
||||
trySyncInlineReplace?(textBeforeCursor: string): { replaceLen: number; insert: string } | null;
|
||||
|
||||
/**
|
||||
* Force file-path completion (called on Tab). Returns matched items plus the
|
||||
* full prefix, or null when no path token sits before the cursor. Present on
|
||||
* file-aware providers; absent on slash-only ones.
|
||||
*/
|
||||
getForceFileSuggestions?(
|
||||
lines: string[],
|
||||
cursorLine: number,
|
||||
cursorCol: number,
|
||||
): Promise<{ items: AutocompleteItem[]; prefix: string } | null>;
|
||||
|
||||
/** Whether a Tab press should attempt file completion at the cursor. */
|
||||
shouldTriggerFileCompletion?(lines: string[], cursorLine: number, cursorCol: number): boolean;
|
||||
}
|
||||
|
||||
type CommandEntry = SlashCommand | AutocompleteItem;
|
||||
|
||||
@@ -1,7 +1,6 @@
|
||||
import { getProjectDir, logger } from "@oh-my-pi/pi-utils";
|
||||
import {
|
||||
type AutocompleteProvider,
|
||||
type CombinedAutocompleteProvider,
|
||||
findLeadingSlashCommandStart,
|
||||
findTrailingSlashCommandStart,
|
||||
} from "../autocomplete";
|
||||
@@ -2859,10 +2858,13 @@ export class Editor implements Component, Focusable {
|
||||
if (!this.#autocompleteProvider) return;
|
||||
// Check if we should trigger file completion on Tab
|
||||
if (explicitTab) {
|
||||
const provider = this.#autocompleteProvider as CombinedAutocompleteProvider;
|
||||
const shouldTrigger =
|
||||
!provider.shouldTriggerFileCompletion ||
|
||||
provider.shouldTriggerFileCompletion(this.#state.lines, this.#state.cursorLine, this.#state.cursorCol);
|
||||
!this.#autocompleteProvider.shouldTriggerFileCompletion ||
|
||||
this.#autocompleteProvider.shouldTriggerFileCompletion(
|
||||
this.#state.lines,
|
||||
this.#state.cursorLine,
|
||||
this.#state.cursorCol,
|
||||
);
|
||||
if (!shouldTrigger) {
|
||||
return;
|
||||
}
|
||||
@@ -2924,17 +2926,16 @@ https://github.com/EsotericSoftware/spine-runtimes/actions/runs/19536643416/job/
|
||||
async #forceFileAutocomplete(explicitTab: boolean = false): Promise<void> {
|
||||
if (!this.#autocompleteProvider) return;
|
||||
|
||||
// Check if provider supports force file suggestions via runtime check
|
||||
const provider = this.#autocompleteProvider as {
|
||||
getForceFileSuggestions?: CombinedAutocompleteProvider["getForceFileSuggestions"];
|
||||
};
|
||||
if (typeof provider.getForceFileSuggestions !== "function") {
|
||||
// File-aware providers expose getForceFileSuggestions; slash-only ones fall back to regular completion.
|
||||
const getForceFileSuggestions = this.#autocompleteProvider.getForceFileSuggestions;
|
||||
if (typeof getForceFileSuggestions !== "function") {
|
||||
await this.#tryTriggerAutocomplete(true);
|
||||
return;
|
||||
}
|
||||
|
||||
const requestId = ++this.#autocompleteRequestId;
|
||||
const suggestions = await provider.getForceFileSuggestions(
|
||||
const suggestions = await getForceFileSuggestions.call(
|
||||
this.#autocompleteProvider,
|
||||
this.#state.lines,
|
||||
this.#state.cursorLine,
|
||||
this.#state.cursorCol,
|
||||
|
||||
Reference in New Issue
Block a user