Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
51 changes: 33 additions & 18 deletions src/runtime/suggestion.ts
Original file line number Diff line number Diff line change
Expand Up @@ -126,6 +126,37 @@ const getSuggestionPriority = (suggestion: Fig.Suggestion): number => {
return suggestion.priority ?? 50;
};

// How well a name matches the text typed so far: 3 = exact, 2 = prefix, 1 = substring, 0 = fuzzy only.
const getMatchRelevance = (name: string, partialCmd: string): number => {
const haystack = name.toLowerCase();
const needle = partialCmd.toLowerCase();
if (haystack === needle) return 3;
const matchIndex = haystack.indexOf(needle);
if (matchIndex === 0) return 2;
if (matchIndex > 0) return 1;
return 0;
};

// Sorts by priority, then ranks file and folder suggestions within a priority band by match quality,
// shorter names and alphabetically. Paths only swap places with other paths, so subcommands, options
// and spec-authored suggestions keep their source order.
const sortSuggestions = (suggestions: Suggestion[], partialCmd: string | undefined): Suggestion[] => {
const sorted = suggestions.sort((a, b) => b.priority - a.priority);
if (!partialCmd) return sorted;
const pathIndexes = sorted.flatMap((s, i) => (getPathy(s.type) ? [i] : []));
const rankedPaths = pathIndexes
.map((i) => sorted[i])
.sort(
(a, b) =>
b.priority - a.priority ||
getMatchRelevance(b.name, partialCmd) - getMatchRelevance(a.name, partialCmd) ||
a.name.length - b.name.length ||
a.name.localeCompare(b.name),
);
pathIndexes.forEach((sortedIndex, rank) => (sorted[sortedIndex] = rankedPaths[rank]));
return sorted;
};

function filter<T extends Fig.BaseSuggestion & { name?: Fig.SingleOrArray<string>; type?: Fig.SuggestionType | undefined }>(
suggestions: T[],
filterStrategy: FilterStrategy | undefined,
Expand Down Expand Up @@ -367,15 +398,7 @@ export const getSubcommandDrivenRecommendation = async (
removeEmptySuggestion(
removeHiddenSuggestions(
removeHomeDirectorySuggestion(
removeAcceptedSuggestions(
adjustPathSuggestions(
suggestions.sort((a, b) => b.priority - a.priority),
partialToken,
lastToken,
shell,
),
acceptedTokens,
),
removeAcceptedSuggestions(adjustPathSuggestions(sortSuggestions(suggestions, partialCmd), partialToken, lastToken, shell), acceptedTokens),
allTokens,
),
partialToken,
Expand Down Expand Up @@ -422,15 +445,7 @@ export const getArgDrivenRecommendation = async (
removeEmptySuggestion(
removeHiddenSuggestions(
removeHomeDirectorySuggestion(
removeAcceptedSuggestions(
adjustPathSuggestions(
suggestions.sort((a, b) => b.priority - a.priority),
partialToken,
lastToken,
shell,
),
acceptedTokens,
),
removeAcceptedSuggestions(adjustPathSuggestions(sortSuggestions(suggestions, partialCmd), partialToken, lastToken, shell), acceptedTokens),
allTokens,
),
partialToken,
Expand Down
91 changes: 88 additions & 3 deletions src/tests/runtime/__snapshots__/runtime.test.ts.snap
Original file line number Diff line number Diff line change
Expand Up @@ -701,6 +701,91 @@ exports[`parseCommand pathNestedSuggestion 1`] = `
}
`;

exports[`parseCommand pathPrefixMatchesRankedFirst 1`] = `
{
"activeToken": {
"complete": false,
"isOption": false,
"token": "s",
"tokenLength": 1,
},
"suggestions": [
{
"allNames": [
"src/",
],
"description": undefined,
"hidden": undefined,
"icon": "📁",
"insertValue": "src/",
"name": "src",
"priority": 60,
"type": "folder",
},
{
"allNames": [
"shell/",
],
"description": undefined,
"hidden": undefined,
"icon": "📁",
"insertValue": "shell/",
"name": "shell",
"priority": 60,
"type": "folder",
},
{
"allNames": [
"scripts/",
],
"description": undefined,
"hidden": undefined,
"icon": "📁",
"insertValue": "scripts/",
"name": "scripts",
"priority": 60,
"type": "folder",
},
{
"allNames": [
"docs/",
],
"description": undefined,
"hidden": undefined,
"icon": "📁",
"insertValue": "docs/",
"name": "docs",
"priority": 60,
"type": "folder",
},
{
"allNames": [
".husky/",
],
"description": undefined,
"hidden": undefined,
"icon": "📁",
"insertValue": ".husky/",
"name": ".husky",
"priority": 60,
"type": "folder",
},
{
"allNames": [
"node_modules/",
],
"description": undefined,
"hidden": undefined,
"icon": "📁",
"insertValue": "node_modules/",
"name": "node_modules",
"priority": 60,
"type": "folder",
},
],
}
`;

exports[`parseCommand pathSuggestion 1`] = `
{
"activeToken": {
Expand Down Expand Up @@ -739,13 +824,13 @@ exports[`parseCommand pathWithFileFilteredSuggestion 1`] = `
"suggestions": [
{
"allNames": [
"shellIntegration.bash",
"shellIntegration.nu",
],
"description": undefined,
"hidden": undefined,
"icon": "📄",
"insertValue": "shell/shellIntegration.bash",
"name": "shellIntegration.bash",
"insertValue": "shell/shellIntegration.nu",
"name": "shellIntegration.nu",
"priority": 55,
"type": "file",
},
Expand Down
1 change: 1 addition & 0 deletions src/tests/runtime/runtime.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -32,6 +32,7 @@ const testData = [
{ name: "pathNestedSuggestion", command: "source .github/work" },
{ name: "pathWithFileSuggestion", command: "source shell/", maxSuggestions: 1 },
{ name: "pathWithFileFilteredSuggestion", command: "source shell/shellIntegration.", maxSuggestions: 1 },
{ name: "pathPrefixMatchesRankedFirst", command: "cd s" },
];

beforeAll(async () => await unpackResources());
Expand Down
Loading