code editor panel keyed (528 -> 516)
The 'editor buffer changed while unfocused' Error stays English — it is the trap marker for docs/editor-buffer-corruption.md, read from a log rather than a screen.
This commit is contained in:
@@ -13,6 +13,7 @@ import { useFilesRefresh } from '../../channels';
|
|||||||
import { useWorkspace } from '../../components/Workspace';
|
import { useWorkspace } from '../../components/Workspace';
|
||||||
import { useEditorTabs } from './use-editor-tabs';
|
import { useEditorTabs } from './use-editor-tabs';
|
||||||
import { configureMonacoTypescript } from './monaco-setup';
|
import { configureMonacoTypescript } from './monaco-setup';
|
||||||
|
import { useTranslation } from '../../locales';
|
||||||
|
|
||||||
type CodeEditorViewProps = {
|
type CodeEditorViewProps = {
|
||||||
className?: string;
|
className?: string;
|
||||||
@@ -103,6 +104,7 @@ export const CodeEditorView = ({
|
|||||||
urlState,
|
urlState,
|
||||||
panelId,
|
panelId,
|
||||||
}: CodeEditorViewProps) => {
|
}: CodeEditorViewProps) => {
|
||||||
|
const { t } = useTranslation();
|
||||||
const { workspace } = useWorkspace();
|
const { workspace } = useWorkspace();
|
||||||
const { restored, remember } = useEditorTabs(panelId, workspace);
|
const { restored, remember } = useEditorTabs(panelId, workspace);
|
||||||
const {
|
const {
|
||||||
@@ -138,7 +140,7 @@ export const CodeEditorView = ({
|
|||||||
const res = await readFile(path);
|
const res = await readFile(path);
|
||||||
openFile(path, name, res.content);
|
openFile(path, name, res.content);
|
||||||
} catch {
|
} catch {
|
||||||
toast.error('Failed to read file');
|
toast.error(t('codeEditor.readFailed'));
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
@@ -159,7 +161,7 @@ export const CodeEditorView = ({
|
|||||||
.then((res) => alive && openFile(path, path.split('/').pop() ?? path, res.content))
|
.then((res) => alive && openFile(path, path.split('/').pop() ?? path, res.content))
|
||||||
.catch(() => {
|
.catch(() => {
|
||||||
unreadable.current.add(path);
|
unreadable.current.add(path);
|
||||||
if (alive) toast.error('Failed to read file');
|
if (alive) toast.error(t('codeEditor.readFailed'));
|
||||||
});
|
});
|
||||||
return () => {
|
return () => {
|
||||||
alive = false;
|
alive = false;
|
||||||
@@ -273,9 +275,9 @@ export const CodeEditorView = ({
|
|||||||
try {
|
try {
|
||||||
await writeFile(activeFile.path, activeFile.content);
|
await writeFile(activeFile.path, activeFile.content);
|
||||||
markSaved(activeFile.path, activeFile.content);
|
markSaved(activeFile.path, activeFile.content);
|
||||||
toast.success('File saved');
|
toast.success(t('codeEditor.saved'));
|
||||||
} catch {
|
} catch {
|
||||||
toast.error('Failed to save file');
|
toast.error(t('codeEditor.saveFailed'));
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
@@ -322,10 +324,10 @@ export const CodeEditorView = ({
|
|||||||
className="px-3 py-2 text-xs font-semibold uppercase tracking-wider flex items-center justify-between gap-2"
|
className="px-3 py-2 text-xs font-semibold uppercase tracking-wider flex items-center justify-between gap-2"
|
||||||
style={{ color: '#888', borderBottom: `1px solid ${borderColor}` }}
|
style={{ color: '#888', borderBottom: `1px solid ${borderColor}` }}
|
||||||
>
|
>
|
||||||
<span>Explorer</span>
|
<span>{t('codeEditor.explorer')}</span>
|
||||||
<button
|
<button
|
||||||
onClick={() => setManualRefresh((n) => n + 1)}
|
onClick={() => setManualRefresh((n) => n + 1)}
|
||||||
title="Refresh the tree and reload unmodified open files"
|
title={t('codeEditor.refresh')}
|
||||||
className="p-0.5 rounded hover:bg-white/10 transition-colors cursor-pointer"
|
className="p-0.5 rounded hover:bg-white/10 transition-colors cursor-pointer"
|
||||||
>
|
>
|
||||||
<RefreshCw className="h-3.5 w-3.5" />
|
<RefreshCw className="h-3.5 w-3.5" />
|
||||||
@@ -383,7 +385,7 @@ export const CodeEditorView = ({
|
|||||||
/>
|
/>
|
||||||
) : (
|
) : (
|
||||||
<div className="flex-1 flex items-center justify-center text-sm" style={{ color: '#888' }}>
|
<div className="flex-1 flex items-center justify-center text-sm" style={{ color: '#888' }}>
|
||||||
Open a file from the explorer to start editing
|
{t('codeEditor.openAFile')}
|
||||||
</div>
|
</div>
|
||||||
)}
|
)}
|
||||||
</div>
|
</div>
|
||||||
|
|||||||
@@ -1,12 +1,14 @@
|
|||||||
import { Code } from 'lucide-react';
|
import { Code } from 'lucide-react';
|
||||||
import { useWorkspace } from '../../components/Workspace';
|
import { useWorkspace } from '../../components/Workspace';
|
||||||
|
import { useTranslation } from '../../locales';
|
||||||
|
|
||||||
export const CodeEditorHeader = () => {
|
export const CodeEditorHeader = () => {
|
||||||
|
const { t } = useTranslation();
|
||||||
const { cwd } = useWorkspace();
|
const { cwd } = useWorkspace();
|
||||||
return (
|
return (
|
||||||
<>
|
<>
|
||||||
<Code className="h-3.5 w-3.5 shrink-0" />
|
<Code className="h-3.5 w-3.5 shrink-0" />
|
||||||
<span className="text-xs font-medium shrink-0">Code Editor</span>
|
<span className="text-xs font-medium shrink-0">{t('codeEditor.title')}</span>
|
||||||
<span className="text-[10px] font-mono truncate opacity-60">{cwd}</span>
|
<span className="text-[10px] font-mono truncate opacity-60">{cwd}</span>
|
||||||
</>
|
</>
|
||||||
);
|
);
|
||||||
|
|||||||
@@ -2,6 +2,7 @@ import { Link } from 'react-router';
|
|||||||
import { X, FileX } from 'lucide-react';
|
import { X, FileX } from 'lucide-react';
|
||||||
import { getIcon } from 'material-file-icons';
|
import { getIcon } from 'material-file-icons';
|
||||||
import type { OpenFile } from './useEditorState';
|
import type { OpenFile } from './useEditorState';
|
||||||
|
import { useTranslation } from '../../locales';
|
||||||
|
|
||||||
type EditorTabsProps = {
|
type EditorTabsProps = {
|
||||||
files: OpenFile[];
|
files: OpenFile[];
|
||||||
@@ -19,6 +20,7 @@ const FileIcon = ({ name }: { name: string }) => {
|
|||||||
};
|
};
|
||||||
|
|
||||||
export const EditorTabs = ({ files, activePath, onSelect, onClose, theme, searchForFile }: EditorTabsProps) => {
|
export const EditorTabs = ({ files, activePath, onSelect, onClose, theme, searchForFile }: EditorTabsProps) => {
|
||||||
|
const { t } = useTranslation();
|
||||||
if (files.length === 0) return null;
|
if (files.length === 0) return null;
|
||||||
|
|
||||||
const isDark = theme !== 'vs';
|
const isDark = theme !== 'vs';
|
||||||
@@ -75,7 +77,7 @@ export const EditorTabs = ({ files, activePath, onSelect, onClose, theme, search
|
|||||||
)}
|
)}
|
||||||
<button
|
<button
|
||||||
onClick={() => onClose(file.path)}
|
onClick={() => onClose(file.path)}
|
||||||
aria-label={`Close ${file.name}`}
|
aria-label={t('codeEditor.closeFile', { name: file.name })}
|
||||||
className="p-0.5 rounded cursor-pointer transition-colors hover:bg-white/10"
|
className="p-0.5 rounded cursor-pointer transition-colors hover:bg-white/10"
|
||||||
style={{ color: inactiveColor }}
|
style={{ color: inactiveColor }}
|
||||||
>
|
>
|
||||||
|
|||||||
@@ -3,6 +3,7 @@ import { Link } from 'react-router';
|
|||||||
import { ChevronRight, ChevronDown, Folder } from 'lucide-react';
|
import { ChevronRight, ChevronDown, Folder } from 'lucide-react';
|
||||||
import { getIcon } from 'material-file-icons';
|
import { getIcon } from 'material-file-icons';
|
||||||
import { useFilesAPI, type DirEntry } from '../../hooks/useFilesAPI';
|
import { useFilesAPI, type DirEntry } from '../../hooks/useFilesAPI';
|
||||||
|
import { useTranslation } from '../../locales';
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* `searchForFile` turns file rows into real links when the editor owns the address bar. Folder rows stay
|
* `searchForFile` turns file rows into real links when the editor owns the address bar. Folder rows stay
|
||||||
@@ -148,6 +149,7 @@ const TreeNode = ({ entry, parentPath, root, onOpenFile, searchForFile, depth, r
|
|||||||
};
|
};
|
||||||
|
|
||||||
export const FileTree = ({ root, basePath, onOpenFile, searchForFile, refreshToken }: FileTreeProps) => {
|
export const FileTree = ({ root, basePath, onOpenFile, searchForFile, refreshToken }: FileTreeProps) => {
|
||||||
|
const { t } = useTranslation();
|
||||||
const { listDir } = useFilesAPI(root);
|
const { listDir } = useFilesAPI(root);
|
||||||
const [entries, setEntries] = useState<DirEntry[] | null>(null);
|
const [entries, setEntries] = useState<DirEntry[] | null>(null);
|
||||||
const [loading, setLoading] = useState(false);
|
const [loading, setLoading] = useState(false);
|
||||||
@@ -187,11 +189,11 @@ export const FileTree = ({ root, basePath, onOpenFile, searchForFile, refreshTok
|
|||||||
}
|
}
|
||||||
|
|
||||||
if (loading && entries === null) {
|
if (loading && entries === null) {
|
||||||
return <div className="p-2 text-sm text-[#888]">Loading...</div>;
|
return <div className="p-2 text-sm text-[#888]">{t('codeEditor.loading')}</div>;
|
||||||
}
|
}
|
||||||
|
|
||||||
if (!entries || entries.length === 0) {
|
if (!entries || entries.length === 0) {
|
||||||
return <div className="p-2 text-sm text-[#888]">No files</div>;
|
return <div className="p-2 text-sm text-[#888]">{t('codeEditor.noFiles')}</div>;
|
||||||
}
|
}
|
||||||
|
|
||||||
return (
|
return (
|
||||||
|
|||||||
Reference in New Issue
Block a user