feat: make chat file references clickable and responsive (#587)
* docs: clarify named and quick Cloudflare tunnel usage * feat: make chat file paths openable from rendered responses * perf: speed up chat file-path links and open behavior * fix: open chat file references at mentioned lines * fix: prevent context panel flicker on blocked file opens
This commit is contained in:
@@ -16,6 +16,7 @@ import {
|
||||
DropdownMenuTrigger,
|
||||
} from '@/components/ui/dropdown-menu';
|
||||
import { Button } from '@/components/ui/button';
|
||||
import { toast } from '@/components/ui';
|
||||
|
||||
import { ScrollableOverlay } from '@/components/ui/ScrollableOverlay';
|
||||
import { getLanguageFromExtension, isImageFile } from '@/lib/toolHelpers';
|
||||
@@ -25,6 +26,7 @@ import type { DiffViewMode } from '@/components/chat/message/types';
|
||||
import { PierreDiffViewer } from './PierreDiffViewer';
|
||||
import { useDeviceInfo } from '@/lib/device';
|
||||
import { FileTypeIcon } from '@/components/icons/FileTypeIcon';
|
||||
import { getContextFileOpenFailureMessage, validateContextFileOpen } from '@/lib/contextFileOpenGuard';
|
||||
|
||||
// Minimum width for side-by-side diff view (px)
|
||||
const SIDE_BY_SIDE_MIN_WIDTH = 1100;
|
||||
@@ -908,7 +910,7 @@ export const DiffView: React.FC<DiffViewProps> = ({
|
||||
pinSelectedFileHeaderToTopOnNavigate = false,
|
||||
showOpenInEditorAction = false,
|
||||
}) => {
|
||||
const { git } = useRuntimeAPIs();
|
||||
const { git, files } = useRuntimeAPIs();
|
||||
const effectiveDirectory = useEffectiveDirectory();
|
||||
const { screenWidth, isMobile } = useDeviceInfo();
|
||||
|
||||
@@ -1407,16 +1409,23 @@ export const DiffView: React.FC<DiffViewProps> = ({
|
||||
? 1
|
||||
: getFirstChangedModifiedLine(diffForNavigation.original, diffForNavigation.modified));
|
||||
|
||||
const absolutePath = toAbsolutePath(effectiveDirectory, filePath);
|
||||
const openValidation = await validateContextFileOpen(files, absolutePath);
|
||||
if (!openValidation.ok) {
|
||||
toast.error(getContextFileOpenFailureMessage(openValidation.reason));
|
||||
return;
|
||||
}
|
||||
|
||||
openContextFileAtLine(
|
||||
effectiveDirectory,
|
||||
toAbsolutePath(effectiveDirectory, filePath),
|
||||
absolutePath,
|
||||
resolvedTargetLine,
|
||||
1,
|
||||
);
|
||||
} finally {
|
||||
setOpeningEditorFilePath((current) => (current === filePath ? null : current));
|
||||
}
|
||||
}, [effectiveDirectory, git, openContextFileAtLine, setDiff]);
|
||||
}, [effectiveDirectory, files, git, openContextFileAtLine, setDiff]);
|
||||
|
||||
const openSelectedFileInEditorAtChange = React.useCallback(async () => {
|
||||
if (!selectedFile) {
|
||||
|
||||
@@ -649,6 +649,8 @@ export const FilesView: React.FC<FilesViewProps> = ({ mode = 'full' }) => {
|
||||
const setMainTabGuard = useUIStore((state) => state.setMainTabGuard);
|
||||
const pendingFileNavigation = useUIStore((state) => state.pendingFileNavigation);
|
||||
const setPendingFileNavigation = useUIStore((state) => state.setPendingFileNavigation);
|
||||
const pendingFileFocusPath = useUIStore((state) => state.pendingFileFocusPath);
|
||||
const setPendingFileFocusPath = useUIStore((state) => state.setPendingFileFocusPath);
|
||||
|
||||
// Global mouseup to end drag selection
|
||||
React.useEffect(() => {
|
||||
@@ -1842,6 +1844,57 @@ export const FilesView: React.FC<FilesViewProps> = ({ mode = 'full' }) => {
|
||||
textViewMode,
|
||||
]);
|
||||
|
||||
React.useEffect(() => {
|
||||
if (!pendingFileFocusPath || !root) {
|
||||
return;
|
||||
}
|
||||
|
||||
const targetPath = normalizePath(pendingFileFocusPath);
|
||||
if (!targetPath) {
|
||||
setPendingFileFocusPath(null);
|
||||
return;
|
||||
}
|
||||
|
||||
if (selectedFile?.path !== targetPath) {
|
||||
if (selectedPath !== targetPath) {
|
||||
setSelectedPath(root, targetPath);
|
||||
}
|
||||
return;
|
||||
}
|
||||
|
||||
if (fileLoading || loadedFilePath !== targetPath || fileError || isSelectedImage) {
|
||||
return;
|
||||
}
|
||||
|
||||
if (canEdit && textViewMode !== 'edit') {
|
||||
setTextViewMode('edit');
|
||||
return;
|
||||
}
|
||||
|
||||
if (canEdit) {
|
||||
const view = editorViewRef.current;
|
||||
if (!view) {
|
||||
return;
|
||||
}
|
||||
view.focus();
|
||||
}
|
||||
|
||||
setPendingFileFocusPath(null);
|
||||
}, [
|
||||
canEdit,
|
||||
fileError,
|
||||
fileLoading,
|
||||
isSelectedImage,
|
||||
loadedFilePath,
|
||||
pendingFileFocusPath,
|
||||
root,
|
||||
selectedFile?.path,
|
||||
selectedPath,
|
||||
setPendingFileFocusPath,
|
||||
setSelectedPath,
|
||||
textViewMode,
|
||||
]);
|
||||
|
||||
const nudgeEditorSelectionAboveKeyboard = React.useCallback((view: EditorView | null) => {
|
||||
if (!isMobile || !view || !view.hasFocus || typeof window === 'undefined') {
|
||||
return;
|
||||
|
||||
Reference in New Issue
Block a user