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:
@@ -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>
|
||||
|
||||
Reference in New Issue
Block a user