Some checks failed
build-packages / resolve bundled mosh-client (push) Has been cancelled
build-packages / resolve bundled et-client (push) Has been cancelled
build-packages / build-macos (push) Has been cancelled
build-packages / build-windows (push) Has been cancelled
build-packages / build-linux-x64 (push) Has been cancelled
build-packages / build-linux-arm64 (push) Has been cancelled
build-packages / release (push) Has been cancelled
build-packages / update Nix release metadata (push) Has been cancelled
build-packages / bump homebrew tap (push) Has been cancelled
test / lint-and-test (push) Has been cancelled
AI automation / Route event (push) Has been cancelled
AI automation / Hand reopened issue to maintainers (push) Has been cancelled
AI automation / Clean source issue state (push) Has been cancelled
AI automation / Reconcile handoffs (push) Has been cancelled
AI automation / Classify issue (push) Has been cancelled
AI automation / Claude Code smoke (push) Has been cancelled
AI automation / Review issue follow-up (push) Has been cancelled
AI automation / Publish issue follow-up (push) Has been cancelled
AI automation / Implement with Claude Code (push) Has been cancelled
AI automation / Publish implement PR (push) Has been cancelled
AI automation / Continue queued issue comments (push) Has been cancelled
AI automation / Codex review loop (push) Has been cancelled
AI automation / Publish Codex fix (push) Has been cancelled
AI automation / Clear Codex dispatch marker (push) Has been cancelled
AI automation / Own PR re-request Codex (push) Has been cancelled
AI automation / External PR re-request Codex (push) Has been cancelled
AI automation / Poll Codex reaction / retry (push) Has been cancelled
build-et-binaries / build-linux-x64 (push) Has been cancelled
build-et-binaries / build-linux-arm64 (push) Has been cancelled
build-et-binaries / build-macos-universal (push) Has been cancelled
build-et-binaries / build-windows-x64 (push) Has been cancelled
build-et-binaries / release (push) Has been cancelled
66 lines
2.5 KiB
TypeScript
66 lines
2.5 KiB
TypeScript
/**
|
|
* Snippet completion source. Surfaces custom snippets in terminal autocomplete
|
|
* when the user is typing the command name. Matches against the snippet label
|
|
* and the first line of its command (case-insensitive; prefix matches rank
|
|
* above substring matches). Chinese labels also match via pinyin / initials
|
|
* through the shared search matcher (#2813). Each suggestion carries the full
|
|
* Snippet so the accept path can run it through the canonical executeSnippetCommand.
|
|
*/
|
|
import type { Snippet } from "../../../domain/models";
|
|
import { snippetAppliesToHost } from "../../../domain/snippetTargets";
|
|
import { matchesSearchQuery } from "../../../lib/searchMatcher";
|
|
import type { CompletionSuggestion } from "./completionEngine";
|
|
|
|
const SNIPPET_BASE_SCORE = 2000; // Above history (1000+freq) per "snippet > history".
|
|
const SNIPPET_PREFIX_BONUS = 100;
|
|
|
|
function snippetAvailableForAutocomplete(
|
|
snippet: Snippet,
|
|
host: { hostId?: string; hostGroup?: string },
|
|
): boolean {
|
|
if (snippet.targetsAllHosts) return true;
|
|
const hasScopedTargets = Boolean(
|
|
snippet.targets?.length || snippet.targetGroups !== undefined,
|
|
);
|
|
if (!hasScopedTargets) return true;
|
|
if (!host.hostId) return false;
|
|
return snippetAppliesToHost(snippet, { id: host.hostId, group: host.hostGroup });
|
|
}
|
|
|
|
export function getSnippetSuggestions(
|
|
input: string,
|
|
snippets: Snippet[],
|
|
options: { hostId?: string; hostGroup?: string } = {},
|
|
): CompletionSuggestion[] {
|
|
const needle = input.trim().toLowerCase();
|
|
if (!needle || !Array.isArray(snippets)) return [];
|
|
|
|
const out: CompletionSuggestion[] = [];
|
|
for (const snippet of snippets) {
|
|
if (!snippetAvailableForAutocomplete(snippet, options)) continue;
|
|
const label = (snippet.label || "").toLowerCase();
|
|
const firstLine = (snippet.command || "").split("\n")[0].trim().toLowerCase();
|
|
|
|
const labelPrefix = label.startsWith(needle);
|
|
// Literal prefix/substring first (cheap); fall back to shared smart matcher
|
|
// so Chinese titles surface for pinyin / initials the same way host search does.
|
|
const matches = labelPrefix
|
|
|| label.includes(needle)
|
|
|| firstLine.startsWith(needle)
|
|
|| matchesSearchQuery(needle, snippet.label, firstLine);
|
|
if (!matches) continue;
|
|
|
|
out.push({
|
|
text: snippet.label,
|
|
displayText: snippet.label,
|
|
description: snippet.command,
|
|
source: "snippet",
|
|
score: SNIPPET_BASE_SCORE + (labelPrefix ? SNIPPET_PREFIX_BONUS : 0),
|
|
snippet,
|
|
});
|
|
}
|
|
|
|
out.sort((a, b) => b.score - a.score);
|
|
return out;
|
|
}
|