refactor: scope mention autocomplete lists to active tab

Files/directories/recentFiles render only on the 'files' tab; agents
only on 'agents'. Memoize visible slices and drop loading spinner when
the files tab is inactive.
This commit is contained in:
Bohdan Triapitsyn
2026-04-24 12:30:11 +03:00
parent 6a70d51ea7
commit 8e610ca8b3
@@ -18,6 +18,8 @@ type AgentInfo = {
description?: string;
mode?: string | null;
};
const EMPTY_FILES: FileInfo[] = [];
const EMPTY_AGENTS: AgentInfo[] = [];
export interface FileMentionHandle {
handleKeyDown: (key: string) => void;
@@ -84,8 +86,6 @@ export const FileMentionAutocomplete = React.forwardRef<FileMentionHandle, FileM
const containerRef = React.useRef<HTMLDivElement | null>(null);
const ignoreTabClickRef = React.useRef(false);
const normalizedSearchQuery = (searchQuery ?? '').trim();
const visibleAgents = normalizedSearchQuery.length > 0 ? agents : agents.slice(0, 2);
const recentFiles = React.useMemo(() => {
if (!projectRoot || !projectTabs) {
return [] as FileInfo[];
@@ -123,6 +123,15 @@ export const FileMentionAutocomplete = React.forwardRef<FileMentionHandle, FileM
return mapped;
}, [normalizedSearchQuery, projectRoot, projectTabs]);
const visibleAgents = React.useMemo(
() => activeTab === 'agents'
? (normalizedSearchQuery.length > 0 ? agents : agents.slice(0, 2))
: EMPTY_AGENTS,
[activeTab, agents, normalizedSearchQuery.length],
);
const visibleDirectories = activeTab === 'files' ? directories : EMPTY_FILES;
const visibleRecentFiles = activeTab === 'files' ? recentFiles : EMPTY_FILES;
const visibleFiles = activeTab === 'files' ? files : EMPTY_FILES;
React.useEffect(() => {
const handlePointerDown = (event: MouseEvent | TouchEvent) => {
@@ -278,7 +287,7 @@ export const FileMentionAutocomplete = React.forwardRef<FileMentionHandle, FileM
setSelectedIndex(0);
setOverflowMap({});
setMarqueeDurations({});
}, [files, directories, recentFiles.length, visibleAgents.length]);
}, [visibleFiles, visibleDirectories, visibleRecentFiles.length, visibleAgents.length]);
React.useEffect(() => {
itemRefs.current[selectedIndex]?.scrollIntoView({
@@ -325,7 +334,7 @@ export const FileMentionAutocomplete = React.forwardRef<FileMentionHandle, FileM
}
window.removeEventListener('resize', updateOverflow);
};
}, [files, directories]);
}, [visibleFiles, visibleDirectories]);
React.useEffect(() => {
const labelNode = labelRefs.current[selectedIndex];
@@ -369,7 +378,7 @@ export const FileMentionAutocomplete = React.forwardRef<FileMentionHandle, FileM
return;
}
const total = visibleAgents.length + directories.length + recentFiles.length + files.length;
const total = visibleAgents.length + visibleDirectories.length + visibleRecentFiles.length + visibleFiles.length;
if (total === 0) {
return;
}
@@ -394,23 +403,23 @@ export const FileMentionAutocomplete = React.forwardRef<FileMentionHandle, FileM
return;
}
const dirIndex = safeIndex - visibleAgents.length;
if (dirIndex < directories.length) {
const dir = directories[dirIndex];
if (dirIndex < visibleDirectories.length) {
const dir = visibleDirectories[dirIndex];
if (dir) {
handleFileSelect(dir);
}
return;
}
const fileIndex = dirIndex - directories.length;
const selectedFile = fileIndex < recentFiles.length
? recentFiles[fileIndex]
: files[fileIndex - recentFiles.length];
const fileIndex = dirIndex - visibleDirectories.length;
const selectedFile = fileIndex < visibleRecentFiles.length
? visibleRecentFiles[fileIndex]
: visibleFiles[fileIndex - visibleRecentFiles.length];
if (selectedFile) {
handleFileSelect(selectedFile);
}
}
}
}), [files, directories, recentFiles, visibleAgents, selectedIndex, onClose, handleFileSelect, handleAgentPick]);
}), [visibleFiles, visibleDirectories, visibleRecentFiles, visibleAgents, selectedIndex, onClose, handleFileSelect, handleAgentPick]);
const getFileIcon = (file: FileInfo) => {
const ext = file.extension?.toLowerCase();
@@ -483,7 +492,7 @@ export const FileMentionAutocomplete = React.forwardRef<FileMentionHandle, FileM
</div>
) : null}
<ScrollableOverlay outerClassName="flex-1 min-h-0" className="px-0">
{loading ? (
{activeTab === 'files' && loading ? (
<div className="flex items-center justify-center py-4">
<RiRefreshLine className="h-4 w-4 animate-spin text-muted-foreground" />
</div>
@@ -516,10 +525,10 @@ export const FileMentionAutocomplete = React.forwardRef<FileMentionHandle, FileM
Type to search more agents
</div>
)}
{visibleAgents.length > 0 && (directories.length > 0 || recentFiles.length > 0 || files.length > 0) && (
{visibleAgents.length > 0 && (visibleDirectories.length > 0 || visibleRecentFiles.length > 0 || visibleFiles.length > 0) && (
<div className="my-1 border-t border-border/60" />
)}
{directories.map((dir, index) => {
{visibleDirectories.map((dir, index) => {
const rowIndex = visibleAgents.length + index;
const relativePath = dir.relativePath || dir.name;
const displayPath = truncatePathMiddle(relativePath, { maxLength: 60 });
@@ -543,11 +552,11 @@ export const FileMentionAutocomplete = React.forwardRef<FileMentionHandle, FileM
</div>
);
})}
{directories.length > 0 && (recentFiles.length > 0 || files.length > 0) && (
{visibleDirectories.length > 0 && (visibleRecentFiles.length > 0 || visibleFiles.length > 0) && (
<div className="my-1 border-t border-border/60" />
)}
{recentFiles.map((file, index) => {
const rowIndex = visibleAgents.length + directories.length + index;
{visibleRecentFiles.map((file, index) => {
const rowIndex = visibleAgents.length + visibleDirectories.length + index;
const relativePath = file.relativePath || file.name;
const displayPath = truncatePathMiddle(relativePath, { maxLength: 60 });
const isSelected = selectedIndex === rowIndex;
@@ -595,11 +604,11 @@ export const FileMentionAutocomplete = React.forwardRef<FileMentionHandle, FileM
</div>
);
})}
{recentFiles.length > 0 && files.length > 0 && (
{visibleRecentFiles.length > 0 && visibleFiles.length > 0 && (
<div className="my-1 border-t border-border/60" />
)}
{files.map((file, index) => {
const rowIndex = visibleAgents.length + directories.length + recentFiles.length + index;
{visibleFiles.map((file, index) => {
const rowIndex = visibleAgents.length + visibleDirectories.length + visibleRecentFiles.length + index;
const relativePath = file.relativePath || file.name;
const displayPath = truncatePathMiddle(relativePath, { maxLength: 60 });
const isSelected = selectedIndex === rowIndex;
@@ -652,7 +661,7 @@ export const FileMentionAutocomplete = React.forwardRef<FileMentionHandle, FileM
</React.Fragment>
);
})}
{files.length === 0 && directories.length === 0 && recentFiles.length === 0 && visibleAgents.length === 0 && (
{visibleFiles.length === 0 && visibleDirectories.length === 0 && visibleRecentFiles.length === 0 && visibleAgents.length === 0 && (
<div className="px-3 py-2 typography-ui-label text-muted-foreground">
No matches found
</div>