mirror of
https://github.com/google-gemini/gemini-cli.git
synced 2026-03-14 16:10:59 -07:00
feat(cli): add filepath autosuggestion after slash commands (#14738)
Co-authored-by: Tommaso Sciortino <sciortino@gmail.com>
This commit is contained in:
@@ -585,4 +585,117 @@ describe('useCommandCompletion', () => {
|
||||
);
|
||||
});
|
||||
});
|
||||
|
||||
describe('@ completion after slash commands (issue #14420)', () => {
|
||||
it('should show file suggestions when typing @path after a slash command', async () => {
|
||||
setupMocks({
|
||||
atSuggestions: [{ label: 'src/file.txt', value: 'src/file.txt' }],
|
||||
});
|
||||
|
||||
const text = '/mycommand @src/fi';
|
||||
const cursorOffset = text.length;
|
||||
|
||||
renderCommandCompletionHook(text, cursorOffset);
|
||||
|
||||
await waitFor(() => {
|
||||
expect(useAtCompletion).toHaveBeenLastCalledWith(
|
||||
expect.objectContaining({
|
||||
enabled: true,
|
||||
pattern: 'src/fi',
|
||||
}),
|
||||
);
|
||||
});
|
||||
});
|
||||
|
||||
it('should show slash suggestions when cursor is on command part (no @)', async () => {
|
||||
setupMocks({
|
||||
slashSuggestions: [{ label: 'mycommand', value: 'mycommand' }],
|
||||
});
|
||||
|
||||
const text = '/mycom';
|
||||
const cursorOffset = text.length;
|
||||
|
||||
const { result } = renderCommandCompletionHook(text, cursorOffset);
|
||||
|
||||
await waitFor(() => {
|
||||
expect(result.current.suggestions).toHaveLength(1);
|
||||
expect(result.current.suggestions[0]?.label).toBe('mycommand');
|
||||
});
|
||||
});
|
||||
|
||||
it('should switch to @ completion when typing @ after slash command', async () => {
|
||||
setupMocks({
|
||||
atSuggestions: [{ label: 'file.txt', value: 'file.txt' }],
|
||||
});
|
||||
|
||||
const text = '/command @';
|
||||
const cursorOffset = text.length;
|
||||
|
||||
renderCommandCompletionHook(text, cursorOffset);
|
||||
|
||||
await waitFor(() => {
|
||||
expect(useAtCompletion).toHaveBeenLastCalledWith(
|
||||
expect.objectContaining({
|
||||
enabled: true,
|
||||
pattern: '',
|
||||
}),
|
||||
);
|
||||
});
|
||||
});
|
||||
|
||||
it('should handle multiple @ references in a slash command', async () => {
|
||||
setupMocks({
|
||||
atSuggestions: [{ label: 'src/bar.ts', value: 'src/bar.ts' }],
|
||||
});
|
||||
|
||||
const text = '/diff @src/foo.ts @src/ba';
|
||||
const cursorOffset = text.length;
|
||||
|
||||
renderCommandCompletionHook(text, cursorOffset);
|
||||
|
||||
await waitFor(() => {
|
||||
expect(useAtCompletion).toHaveBeenLastCalledWith(
|
||||
expect.objectContaining({
|
||||
enabled: true,
|
||||
pattern: 'src/ba',
|
||||
}),
|
||||
);
|
||||
});
|
||||
});
|
||||
|
||||
it('should complete file path and add trailing space', async () => {
|
||||
setupMocks({
|
||||
atSuggestions: [{ label: 'src/file.txt', value: 'src/file.txt' }],
|
||||
});
|
||||
|
||||
const { result } = renderCommandCompletionHook('/cmd @src/fi');
|
||||
|
||||
await waitFor(() => {
|
||||
expect(result.current.suggestions.length).toBe(1);
|
||||
});
|
||||
|
||||
act(() => {
|
||||
result.current.handleAutocomplete(0);
|
||||
});
|
||||
|
||||
expect(result.current.textBuffer.text).toBe('/cmd @src/file.txt ');
|
||||
});
|
||||
|
||||
it('should stay in slash mode when slash command has trailing space but no @', async () => {
|
||||
setupMocks({
|
||||
slashSuggestions: [{ label: 'help', value: 'help' }],
|
||||
});
|
||||
|
||||
const text = '/help ';
|
||||
renderCommandCompletionHook(text);
|
||||
|
||||
await waitFor(() => {
|
||||
expect(useSlashCompletion).toHaveBeenLastCalledWith(
|
||||
expect.objectContaining({
|
||||
enabled: true,
|
||||
}),
|
||||
);
|
||||
});
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
@@ -92,16 +92,11 @@ export function useCommandCompletion(
|
||||
const { completionMode, query, completionStart, completionEnd } =
|
||||
useMemo(() => {
|
||||
const currentLine = buffer.lines[cursorRow] || '';
|
||||
if (cursorRow === 0 && isSlashCommand(currentLine.trim())) {
|
||||
return {
|
||||
completionMode: CompletionMode.SLASH,
|
||||
query: currentLine,
|
||||
completionStart: 0,
|
||||
completionEnd: currentLine.length,
|
||||
};
|
||||
}
|
||||
|
||||
const codePoints = toCodePoints(currentLine);
|
||||
|
||||
// FIRST: Check for @ completion (scan backwards from cursor)
|
||||
// This must happen before slash command check so that `/cmd @file`
|
||||
// triggers file completion, not just slash command completion.
|
||||
for (let i = cursorCol - 1; i >= 0; i--) {
|
||||
const char = codePoints[i];
|
||||
|
||||
@@ -139,6 +134,16 @@ export function useCommandCompletion(
|
||||
}
|
||||
}
|
||||
|
||||
// THEN: Check for slash command (only if no @ completion is active)
|
||||
if (cursorRow === 0 && isSlashCommand(currentLine.trim())) {
|
||||
return {
|
||||
completionMode: CompletionMode.SLASH,
|
||||
query: currentLine,
|
||||
completionStart: 0,
|
||||
completionEnd: currentLine.length,
|
||||
};
|
||||
}
|
||||
|
||||
// Check for prompt completion - only if enabled
|
||||
const trimmedText = buffer.text.trim();
|
||||
const isPromptCompletionEnabled =
|
||||
|
||||
Reference in New Issue
Block a user