Align web at-space completion with TUI

This commit is contained in:
Federico Jaramillo Martinez
2026-05-07 23:27:09 +02:00
parent be624dbdc9
commit 34994705e4
5 changed files with 44 additions and 9 deletions
+1 -1
View File
@@ -103,7 +103,7 @@ export const api = {
messages: (sessionId: string, options?: { limit?: number; before?: number }) => request(messageUrl(sessionId, options), parseMessagePage),
status: (sessionId: string) => request(`/api/sessions/${sessionId}/status`, parseSessionStatus),
commands: (sessionId: string) => request(`/api/sessions/${sessionId}/commands`, arrayOf(parseSlashCommand)),
files: (cwd: string, query: string, kind?: FileSuggestion["kind"]) => request(`/api/files?cwd=${encodeURIComponent(cwd)}&q=${encodeURIComponent(query)}${kind !== undefined ? `&kind=${encodeURIComponent(kind)}` : ""}`, arrayOf(parseFileSuggestion)),
files: (cwd: string, query: string, kind?: FileSuggestion["kind"], mode?: "file" | "path") => request(`/api/files?cwd=${encodeURIComponent(cwd)}&q=${encodeURIComponent(query)}${kind !== undefined ? `&kind=${encodeURIComponent(kind)}` : ""}${mode !== undefined ? `&mode=${encodeURIComponent(mode)}` : ""}`, arrayOf(parseFileSuggestion)),
prompt: (sessionId: string, text: string, streamingBehavior?: "steer" | "followUp") => request(`/api/sessions/${sessionId}/prompt`, parseAccepted, { method: "POST", body: JSON.stringify(streamingBehavior === undefined ? { text } : { text, streamingBehavior }) }),
shell: (sessionId: string, text: string) => request(`/api/sessions/${sessionId}/shell`, parseAccepted, { method: "POST", body: JSON.stringify({ text }) }),
runCommand: (sessionId: string, text: string) => request(`/api/sessions/${sessionId}/commands/run`, parseCommandResult, { method: "POST", body: JSON.stringify({ text }) }),
+7 -5
View File
@@ -103,21 +103,22 @@ export class PromptEditor extends LitElement {
...(command.description === undefined ? {} : { description: command.description }),
}));
} else if (trigger.kind === "file" && this.cwd !== undefined && this.cwd !== "") {
const files = await api.files(this.cwd, trigger.query, trigger.fileKind).catch(emptyFileSuggestions);
const files = await api.files(this.cwd, trigger.query, trigger.fileKind, trigger.fileMode).catch(emptyFileSuggestions);
if (version !== this.requestVersion) return;
this.completions = files
.slice(0, 12)
.map((file) => ({ kind: "file", replaceFrom: trigger.from, replaceTo: trigger.to, insertText: `@${file.path}`, detail: file.kind }));
.map((file) => ({ kind: "file", replaceFrom: trigger.from, replaceTo: trigger.to, insertText: `${trigger.fileMode === "path" ? "" : "@"}${file.path}`, detail: file.kind }));
}
}
private currentTrigger(): { kind: "command" | "file"; query: string; from: number; to: number; fileKind?: FileSuggestion["kind"] } | undefined {
private currentTrigger(): { kind: "command" | "file"; query: string; from: number; to: number; fileKind?: FileSuggestion["kind"]; fileMode?: "file" | "path" } | undefined {
const cursor = this.textarea?.selectionStart ?? this.draft.length;
const beforeCursor = this.draft.slice(0, cursor);
if (beforeCursor.endsWith("@ ")) return { kind: "file", query: "", from: beforeCursor.length - 2, to: cursor, fileKind: "untracked" };
const tokenStart = Math.max(beforeCursor.lastIndexOf(" "), beforeCursor.lastIndexOf("\n")) + 1;
const token = beforeCursor.slice(tokenStart);
const beforeToken = beforeCursor.slice(0, tokenStart);
if (beforeToken.endsWith("@ ")) return { kind: "file", query: token, from: tokenStart, to: cursor, fileMode: "path" };
if (token.startsWith("/") && tokenStart === 0) return { kind: "command", query: token.slice(1), from: tokenStart, to: cursor };
if (token.startsWith("@")) return { kind: "file", query: token.slice(1), from: tokenStart, to: cursor };
return undefined;
@@ -154,7 +155,8 @@ export class PromptEditor extends LitElement {
}
private pick(item: CompletionItem) {
this.draft = `${this.draft.slice(0, item.replaceFrom)}${item.insertText} ${this.draft.slice(item.replaceTo)}`;
const suffix = item.kind === "file" && item.insertText.endsWith("/") ? "" : " ";
this.draft = `${this.draft.slice(0, item.replaceFrom)}${item.insertText}${suffix}${this.draft.slice(item.replaceTo)}`;
if (this.sessionId !== undefined && this.sessionId !== "") saveDraft(this.sessionId, this.draft);
this.completions = [];
}
+8 -1
View File
@@ -8,7 +8,7 @@ export function inputModeForDraft(draft: string): InputMode {
const trimmed = draft.trimStart();
if (trimmed.startsWith("!")) return { kind: "shell", excludeFromContext: trimmed.startsWith("!!") };
if (currentToken(draft).startsWith("/")) return { kind: "command" };
if (draft.endsWith("@ ") || currentToken(draft).startsWith("@")) return { kind: "file" };
if (isFileCompletionContext(draft)) return { kind: "file" };
return { kind: "normal" };
}
@@ -20,3 +20,10 @@ function currentToken(draft: string): string {
const tokenStart = Math.max(draft.lastIndexOf(" "), draft.lastIndexOf("\n")) + 1;
return draft.slice(tokenStart);
}
function isFileCompletionContext(draft: string): boolean {
const token = currentToken(draft);
if (token.startsWith("@")) return true;
const tokenStart = draft.length - token.length;
return draft.slice(0, tokenStart).endsWith("@ ");
}