2026-03-04 11:06:40 -08:00
|
|
|
"use client";
|
|
|
|
|
|
|
|
|
|
import { useCallback, useEffect, useRef, useState } from "react";
|
|
|
|
|
import Editor, { type OnMount } from "@monaco-editor/react";
|
|
|
|
|
import type { editor } from "monaco-editor";
|
|
|
|
|
import { DiffCard } from "../diff-viewer";
|
|
|
|
|
|
|
|
|
|
const EXT_TO_MONACO_LANG: Record<string, string> = {
|
|
|
|
|
ts: "typescript",
|
|
|
|
|
tsx: "typescript",
|
|
|
|
|
js: "javascript",
|
|
|
|
|
jsx: "javascript",
|
|
|
|
|
mjs: "javascript",
|
|
|
|
|
cjs: "javascript",
|
|
|
|
|
py: "python",
|
|
|
|
|
rb: "ruby",
|
|
|
|
|
go: "go",
|
|
|
|
|
rs: "rust",
|
|
|
|
|
java: "java",
|
|
|
|
|
kt: "kotlin",
|
|
|
|
|
swift: "swift",
|
|
|
|
|
c: "c",
|
|
|
|
|
cpp: "cpp",
|
|
|
|
|
h: "c",
|
|
|
|
|
hpp: "cpp",
|
|
|
|
|
cs: "csharp",
|
|
|
|
|
css: "css",
|
|
|
|
|
scss: "scss",
|
|
|
|
|
less: "less",
|
|
|
|
|
html: "html",
|
|
|
|
|
htm: "html",
|
|
|
|
|
xml: "xml",
|
|
|
|
|
svg: "xml",
|
|
|
|
|
json: "json",
|
|
|
|
|
jsonc: "json",
|
|
|
|
|
yaml: "yaml",
|
|
|
|
|
yml: "yaml",
|
|
|
|
|
toml: "plaintext",
|
|
|
|
|
md: "markdown",
|
|
|
|
|
mdx: "markdown",
|
|
|
|
|
sh: "shell",
|
|
|
|
|
bash: "shell",
|
|
|
|
|
zsh: "shell",
|
|
|
|
|
fish: "shell",
|
|
|
|
|
ps1: "powershell",
|
|
|
|
|
sql: "sql",
|
|
|
|
|
graphql: "graphql",
|
|
|
|
|
gql: "graphql",
|
|
|
|
|
dockerfile: "dockerfile",
|
|
|
|
|
docker: "dockerfile",
|
|
|
|
|
makefile: "plaintext",
|
|
|
|
|
cmake: "plaintext",
|
|
|
|
|
r: "r",
|
|
|
|
|
lua: "lua",
|
|
|
|
|
php: "php",
|
|
|
|
|
vue: "html",
|
|
|
|
|
svelte: "html",
|
|
|
|
|
diff: "plaintext",
|
|
|
|
|
patch: "plaintext",
|
|
|
|
|
ini: "ini",
|
|
|
|
|
env: "ini",
|
|
|
|
|
tf: "plaintext",
|
|
|
|
|
proto: "protobuf",
|
|
|
|
|
zig: "plaintext",
|
|
|
|
|
elixir: "plaintext",
|
|
|
|
|
ex: "plaintext",
|
|
|
|
|
erl: "plaintext",
|
|
|
|
|
hs: "plaintext",
|
|
|
|
|
scala: "scala",
|
|
|
|
|
clj: "clojure",
|
|
|
|
|
dart: "dart",
|
|
|
|
|
};
|
|
|
|
|
|
|
|
|
|
export function extFromFilename(filename: string): string {
|
|
|
|
|
const lower = filename.toLowerCase();
|
|
|
|
|
if (lower === "dockerfile" || lower.startsWith("dockerfile.")) {return "dockerfile";}
|
|
|
|
|
if (lower === "makefile" || lower === "gnumakefile") {return "makefile";}
|
|
|
|
|
if (lower === "cmakelists.txt") {return "cmake";}
|
|
|
|
|
return lower.split(".").pop() ?? "";
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
export function monacoLangFromFilename(filename: string): string {
|
|
|
|
|
const ext = extFromFilename(filename);
|
|
|
|
|
return EXT_TO_MONACO_LANG[ext] ?? "plaintext";
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
export function displayLang(filename: string): string {
|
|
|
|
|
const lang = monacoLangFromFilename(filename);
|
|
|
|
|
if (lang === "plaintext") {
|
|
|
|
|
const ext = extFromFilename(filename);
|
|
|
|
|
return ext || "TEXT";
|
|
|
|
|
}
|
|
|
|
|
return lang;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
let themesRegistered = false;
|
|
|
|
|
|
|
|
|
|
function registerThemes(monaco: typeof import("monaco-editor")) {
|
|
|
|
|
if (themesRegistered) {return;}
|
|
|
|
|
themesRegistered = true;
|
|
|
|
|
|
2026-03-04 13:23:34 -08:00
|
|
|
monaco.editor.defineTheme("denchclaw-light", {
|
2026-03-04 11:06:40 -08:00
|
|
|
base: "vs",
|
|
|
|
|
inherit: true,
|
|
|
|
|
rules: [],
|
|
|
|
|
colors: {
|
|
|
|
|
"editor.background": "#ffffff",
|
|
|
|
|
"editor.foreground": "#1c1c1a",
|
|
|
|
|
"editor.lineHighlightBackground": "#f5f4f1",
|
|
|
|
|
"editor.selectionBackground": "#2563eb20",
|
|
|
|
|
"editor.inactiveSelectionBackground": "#2563eb10",
|
|
|
|
|
"editorLineNumber.foreground": "#8a8a82",
|
|
|
|
|
"editorLineNumber.activeForeground": "#44443e",
|
|
|
|
|
"editorIndentGuide.background": "#00000010",
|
|
|
|
|
"editorIndentGuide.activeBackground": "#00000020",
|
|
|
|
|
"editor.selectionHighlightBackground": "#2563eb12",
|
|
|
|
|
"editorCursor.foreground": "#2563eb",
|
|
|
|
|
"editorWhitespace.foreground": "#00000010",
|
|
|
|
|
"editorBracketMatch.background": "#2563eb15",
|
|
|
|
|
"editorBracketMatch.border": "#2563eb40",
|
|
|
|
|
"editorGutter.background": "#ffffff",
|
|
|
|
|
"editorWidget.background": "#ffffff",
|
|
|
|
|
"editorWidget.border": "#00000014",
|
|
|
|
|
"editorSuggestWidget.background": "#ffffff",
|
|
|
|
|
"editorSuggestWidget.border": "#00000014",
|
|
|
|
|
"editorSuggestWidget.selectedBackground": "#f5f4f1",
|
|
|
|
|
"editorHoverWidget.background": "#ffffff",
|
|
|
|
|
"editorHoverWidget.border": "#00000014",
|
|
|
|
|
"input.background": "#f5f5f4",
|
|
|
|
|
"input.border": "#00000014",
|
|
|
|
|
"input.foreground": "#1c1c1a",
|
|
|
|
|
"minimap.background": "#ffffff",
|
|
|
|
|
"scrollbarSlider.background": "#00000012",
|
|
|
|
|
"scrollbarSlider.hoverBackground": "#00000020",
|
|
|
|
|
"scrollbarSlider.activeBackground": "#0000002a",
|
|
|
|
|
},
|
|
|
|
|
});
|
|
|
|
|
|
2026-03-04 13:23:34 -08:00
|
|
|
monaco.editor.defineTheme("denchclaw-dark", {
|
2026-03-04 11:06:40 -08:00
|
|
|
base: "vs-dark",
|
|
|
|
|
inherit: true,
|
|
|
|
|
rules: [],
|
|
|
|
|
colors: {
|
|
|
|
|
"editor.background": "#0c0c0b",
|
|
|
|
|
"editor.foreground": "#ececea",
|
|
|
|
|
"editor.lineHighlightBackground": "#1e1e1c",
|
|
|
|
|
"editor.selectionBackground": "#3b82f630",
|
|
|
|
|
"editor.inactiveSelectionBackground": "#3b82f618",
|
|
|
|
|
"editorLineNumber.foreground": "#78776f",
|
|
|
|
|
"editorLineNumber.activeForeground": "#b8b8b0",
|
|
|
|
|
"editorIndentGuide.background": "#ffffff08",
|
|
|
|
|
"editorIndentGuide.activeBackground": "#ffffff14",
|
|
|
|
|
"editor.selectionHighlightBackground": "#3b82f618",
|
|
|
|
|
"editorCursor.foreground": "#3b82f6",
|
|
|
|
|
"editorWhitespace.foreground": "#ffffff08",
|
|
|
|
|
"editorBracketMatch.background": "#3b82f620",
|
|
|
|
|
"editorBracketMatch.border": "#3b82f650",
|
|
|
|
|
"editorGutter.background": "#0c0c0b",
|
|
|
|
|
"editorWidget.background": "#161615",
|
|
|
|
|
"editorWidget.border": "#ffffff14",
|
|
|
|
|
"editorSuggestWidget.background": "#161615",
|
|
|
|
|
"editorSuggestWidget.border": "#ffffff14",
|
|
|
|
|
"editorSuggestWidget.selectedBackground": "#1e1e1c",
|
|
|
|
|
"editorHoverWidget.background": "#161615",
|
|
|
|
|
"editorHoverWidget.border": "#ffffff14",
|
|
|
|
|
"input.background": "#1e1e1c",
|
|
|
|
|
"input.border": "#ffffff14",
|
|
|
|
|
"input.foreground": "#ececea",
|
|
|
|
|
"minimap.background": "#0c0c0b",
|
|
|
|
|
"scrollbarSlider.background": "#ffffff12",
|
|
|
|
|
"scrollbarSlider.hoverBackground": "#ffffff20",
|
|
|
|
|
"scrollbarSlider.activeBackground": "#ffffff2a",
|
|
|
|
|
},
|
|
|
|
|
});
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
function isDarkMode(): boolean {
|
|
|
|
|
if (typeof document === "undefined") {return false;}
|
|
|
|
|
return document.documentElement.classList.contains("dark");
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
type CodeEditorProps = {
|
|
|
|
|
content: string;
|
|
|
|
|
filename: string;
|
|
|
|
|
filePath?: string;
|
|
|
|
|
className?: string;
|
|
|
|
|
};
|
|
|
|
|
|
|
|
|
|
export function MonacoCodeEditor({ content, filename, filePath, className }: CodeEditorProps) {
|
|
|
|
|
const ext = extFromFilename(filename);
|
|
|
|
|
|
|
|
|
|
if (ext === "diff" || ext === "patch") {
|
|
|
|
|
return (
|
|
|
|
|
<div className="max-w-4xl mx-auto px-6 py-8">
|
|
|
|
|
<DiffCard diff={content} />
|
|
|
|
|
</div>
|
|
|
|
|
);
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
return <EditorInner content={content} filename={filename} filePath={filePath} className={className} />;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
type SaveState = "clean" | "dirty" | "saving" | "saved" | "error";
|
|
|
|
|
|
|
|
|
|
function EditorInner({ content, filename, filePath, className }: CodeEditorProps) {
|
|
|
|
|
const editorRef = useRef<editor.IStandaloneCodeEditor | null>(null);
|
|
|
|
|
const monacoRef = useRef<typeof import("monaco-editor") | null>(null);
|
2026-03-04 13:23:34 -08:00
|
|
|
const [theme, setTheme] = useState<string>(isDarkMode() ? "denchclaw-dark" : "denchclaw-light");
|
2026-03-04 11:06:40 -08:00
|
|
|
const [saveState, setSaveState] = useState<SaveState>("clean");
|
|
|
|
|
const [cursorPos, setCursorPos] = useState({ line: 1, col: 1 });
|
|
|
|
|
const saveTimeoutRef = useRef<ReturnType<typeof setTimeout> | null>(null);
|
|
|
|
|
const currentContentRef = useRef(content);
|
|
|
|
|
|
|
|
|
|
const language = monacoLangFromFilename(filename);
|
|
|
|
|
const canSave = !!filePath;
|
|
|
|
|
|
|
|
|
|
useEffect(() => {
|
|
|
|
|
currentContentRef.current = content;
|
|
|
|
|
}, [content]);
|
|
|
|
|
|
|
|
|
|
// Watch for theme changes via MutationObserver on <html> class
|
|
|
|
|
useEffect(() => {
|
|
|
|
|
const html = document.documentElement;
|
2026-03-04 13:23:34 -08:00
|
|
|
const update = () => setTheme(isDarkMode() ? "denchclaw-dark" : "denchclaw-light");
|
2026-03-04 11:06:40 -08:00
|
|
|
const observer = new MutationObserver(update);
|
|
|
|
|
observer.observe(html, { attributes: true, attributeFilter: ["class"] });
|
|
|
|
|
return () => observer.disconnect();
|
|
|
|
|
}, []);
|
|
|
|
|
|
|
|
|
|
const saveFile = useCallback(async () => {
|
|
|
|
|
if (!filePath || !editorRef.current) {return;}
|
|
|
|
|
const value = editorRef.current.getValue();
|
|
|
|
|
setSaveState("saving");
|
|
|
|
|
try {
|
|
|
|
|
const res = await fetch("/api/workspace/file", {
|
|
|
|
|
method: "POST",
|
|
|
|
|
headers: { "Content-Type": "application/json" },
|
|
|
|
|
body: JSON.stringify({ path: filePath, content: value }),
|
|
|
|
|
});
|
|
|
|
|
if (!res.ok) {throw new Error("Save failed");}
|
|
|
|
|
currentContentRef.current = value;
|
|
|
|
|
setSaveState("saved");
|
|
|
|
|
if (saveTimeoutRef.current) {clearTimeout(saveTimeoutRef.current);}
|
|
|
|
|
saveTimeoutRef.current = setTimeout(() => setSaveState("clean"), 2000);
|
|
|
|
|
} catch {
|
|
|
|
|
setSaveState("error");
|
|
|
|
|
if (saveTimeoutRef.current) {clearTimeout(saveTimeoutRef.current);}
|
|
|
|
|
saveTimeoutRef.current = setTimeout(() => setSaveState("dirty"), 3000);
|
|
|
|
|
}
|
|
|
|
|
}, [filePath]);
|
|
|
|
|
|
|
|
|
|
const handleMount: OnMount = useCallback((ed, monaco) => {
|
|
|
|
|
editorRef.current = ed;
|
|
|
|
|
monacoRef.current = monaco;
|
|
|
|
|
registerThemes(monaco);
|
2026-03-04 13:23:34 -08:00
|
|
|
monaco.editor.setTheme(isDarkMode() ? "denchclaw-dark" : "denchclaw-light");
|
2026-03-04 11:06:40 -08:00
|
|
|
|
|
|
|
|
// Cmd+S / Ctrl+S save
|
|
|
|
|
ed.addCommand(monaco.KeyMod.CtrlCmd | monaco.KeyCode.KeyS, () => {
|
|
|
|
|
void saveFile();
|
|
|
|
|
});
|
|
|
|
|
|
|
|
|
|
// Track cursor position
|
|
|
|
|
ed.onDidChangeCursorPosition((e) => {
|
|
|
|
|
setCursorPos({ line: e.position.lineNumber, col: e.position.column });
|
|
|
|
|
});
|
|
|
|
|
|
|
|
|
|
// Track dirty state
|
|
|
|
|
ed.onDidChangeModelContent(() => {
|
|
|
|
|
const current = ed.getValue();
|
|
|
|
|
if (current !== currentContentRef.current) {
|
|
|
|
|
setSaveState("dirty");
|
|
|
|
|
} else {
|
|
|
|
|
setSaveState("clean");
|
|
|
|
|
}
|
|
|
|
|
});
|
|
|
|
|
|
|
|
|
|
ed.focus();
|
|
|
|
|
}, [saveFile]);
|
|
|
|
|
|
|
|
|
|
// Apply theme when it changes
|
|
|
|
|
useEffect(() => {
|
|
|
|
|
if (monacoRef.current) {
|
|
|
|
|
monacoRef.current.editor.setTheme(theme);
|
|
|
|
|
}
|
|
|
|
|
}, [theme]);
|
|
|
|
|
|
|
|
|
|
const lang = displayLang(filename);
|
|
|
|
|
const lineCount = content.split("\n").length;
|
|
|
|
|
|
|
|
|
|
return (
|
|
|
|
|
<div className={`flex flex-col h-full ${className ?? ""}`}>
|
|
|
|
|
{/* Header bar */}
|
|
|
|
|
<div
|
|
|
|
|
className="flex items-center gap-2 px-4 py-2 flex-shrink-0"
|
|
|
|
|
style={{
|
|
|
|
|
background: "var(--color-surface)",
|
|
|
|
|
borderBottom: "1px solid var(--color-border)",
|
|
|
|
|
}}
|
|
|
|
|
>
|
|
|
|
|
<svg
|
|
|
|
|
width="15"
|
|
|
|
|
height="15"
|
|
|
|
|
viewBox="0 0 24 24"
|
|
|
|
|
fill="none"
|
|
|
|
|
stroke="currentColor"
|
|
|
|
|
strokeWidth="2"
|
|
|
|
|
strokeLinecap="round"
|
|
|
|
|
strokeLinejoin="round"
|
|
|
|
|
style={{ color: "var(--color-text-muted)", flexShrink: 0 }}
|
|
|
|
|
>
|
|
|
|
|
<polyline points="16 18 22 12 16 6" />
|
|
|
|
|
<polyline points="8 6 2 12 8 18" />
|
|
|
|
|
</svg>
|
|
|
|
|
<span
|
|
|
|
|
className="text-sm font-medium truncate"
|
|
|
|
|
style={{ color: "var(--color-text)" }}
|
|
|
|
|
>
|
|
|
|
|
{filename}
|
|
|
|
|
</span>
|
|
|
|
|
|
|
|
|
|
{/* Dirty indicator */}
|
|
|
|
|
{saveState === "dirty" && (
|
|
|
|
|
<span
|
|
|
|
|
className="w-2 h-2 rounded-full flex-shrink-0"
|
|
|
|
|
style={{ background: "var(--color-text-muted)" }}
|
|
|
|
|
title="Unsaved changes"
|
|
|
|
|
/>
|
|
|
|
|
)}
|
|
|
|
|
|
|
|
|
|
<div className="flex-1" />
|
|
|
|
|
|
|
|
|
|
{/* Language badge */}
|
|
|
|
|
<span
|
|
|
|
|
className="text-[11px] px-1.5 py-0.5 rounded font-medium"
|
|
|
|
|
style={{
|
|
|
|
|
background: "var(--color-surface-hover)",
|
|
|
|
|
color: "var(--color-text-muted)",
|
|
|
|
|
}}
|
|
|
|
|
>
|
|
|
|
|
{lang.toUpperCase()}
|
|
|
|
|
</span>
|
|
|
|
|
|
|
|
|
|
{/* Line count */}
|
|
|
|
|
<span
|
|
|
|
|
className="text-[11px] tabular-nums"
|
|
|
|
|
style={{ color: "var(--color-text-muted)" }}
|
|
|
|
|
>
|
|
|
|
|
{lineCount} lines
|
|
|
|
|
</span>
|
|
|
|
|
|
|
|
|
|
{/* Cursor position */}
|
|
|
|
|
<span
|
|
|
|
|
className="text-[11px] tabular-nums"
|
|
|
|
|
style={{ color: "var(--color-text-muted)" }}
|
|
|
|
|
>
|
|
|
|
|
Ln {cursorPos.line}, Col {cursorPos.col}
|
|
|
|
|
</span>
|
|
|
|
|
|
|
|
|
|
{/* Save status / button */}
|
|
|
|
|
{canSave && (
|
|
|
|
|
<>
|
|
|
|
|
{saveState === "saving" && (
|
|
|
|
|
<span className="text-[11px]" style={{ color: "var(--color-text-muted)" }}>
|
|
|
|
|
Saving...
|
|
|
|
|
</span>
|
|
|
|
|
)}
|
|
|
|
|
{saveState === "saved" && (
|
|
|
|
|
<span className="text-[11px]" style={{ color: "var(--color-success)" }}>
|
|
|
|
|
Saved
|
|
|
|
|
</span>
|
|
|
|
|
)}
|
|
|
|
|
{saveState === "error" && (
|
|
|
|
|
<span className="text-[11px]" style={{ color: "var(--color-error)" }}>
|
|
|
|
|
Save failed
|
|
|
|
|
</span>
|
|
|
|
|
)}
|
|
|
|
|
<button
|
|
|
|
|
type="button"
|
|
|
|
|
onClick={() => void saveFile()}
|
|
|
|
|
disabled={saveState !== "dirty"}
|
|
|
|
|
className="text-[11px] px-2 py-0.5 rounded transition-colors"
|
|
|
|
|
style={{
|
|
|
|
|
background: saveState === "dirty" ? "var(--color-accent)" : "var(--color-surface-hover)",
|
|
|
|
|
color: saveState === "dirty" ? "#fff" : "var(--color-text-muted)",
|
|
|
|
|
cursor: saveState === "dirty" ? "pointer" : "default",
|
|
|
|
|
opacity: saveState === "dirty" ? 1 : 0.5,
|
|
|
|
|
}}
|
|
|
|
|
>
|
|
|
|
|
{saveState === "dirty" ? "Save" : "Save"}
|
|
|
|
|
</button>
|
|
|
|
|
</>
|
|
|
|
|
)}
|
|
|
|
|
</div>
|
|
|
|
|
|
|
|
|
|
{/* Monaco Editor */}
|
|
|
|
|
<div className="flex-1 min-h-0">
|
|
|
|
|
<Editor
|
|
|
|
|
defaultValue={content}
|
|
|
|
|
language={language}
|
|
|
|
|
theme={theme}
|
|
|
|
|
onMount={handleMount}
|
|
|
|
|
loading={
|
|
|
|
|
<div
|
|
|
|
|
className="flex items-center justify-center h-full"
|
|
|
|
|
style={{ background: "var(--color-bg)" }}
|
|
|
|
|
>
|
|
|
|
|
<span className="text-sm" style={{ color: "var(--color-text-muted)" }}>
|
|
|
|
|
Loading editor...
|
|
|
|
|
</span>
|
|
|
|
|
</div>
|
|
|
|
|
}
|
|
|
|
|
options={{
|
|
|
|
|
readOnly: !canSave,
|
|
|
|
|
fontSize: 13,
|
|
|
|
|
lineHeight: 20,
|
|
|
|
|
fontFamily: "'SF Mono', 'Fira Code', 'Cascadia Code', 'JetBrains Mono', Menlo, Monaco, 'Courier New', monospace",
|
|
|
|
|
fontLigatures: true,
|
|
|
|
|
minimap: { enabled: true, scale: 1, showSlider: "mouseover" },
|
|
|
|
|
scrollBeyondLastLine: true,
|
|
|
|
|
smoothScrolling: true,
|
|
|
|
|
cursorBlinking: "smooth",
|
|
|
|
|
cursorSmoothCaretAnimation: "on",
|
|
|
|
|
renderLineHighlight: "all",
|
|
|
|
|
renderWhitespace: "selection",
|
|
|
|
|
bracketPairColorization: { enabled: true },
|
|
|
|
|
guides: {
|
|
|
|
|
bracketPairs: true,
|
|
|
|
|
indentation: true,
|
|
|
|
|
highlightActiveIndentation: true,
|
|
|
|
|
},
|
|
|
|
|
stickyScroll: { enabled: true },
|
|
|
|
|
folding: true,
|
|
|
|
|
foldingHighlight: true,
|
|
|
|
|
showFoldingControls: "mouseover",
|
|
|
|
|
links: true,
|
|
|
|
|
wordWrap: "off",
|
|
|
|
|
padding: { top: 8, bottom: 8 },
|
|
|
|
|
scrollbar: {
|
|
|
|
|
verticalScrollbarSize: 10,
|
|
|
|
|
horizontalScrollbarSize: 10,
|
|
|
|
|
verticalSliderSize: 10,
|
|
|
|
|
horizontalSliderSize: 10,
|
|
|
|
|
},
|
|
|
|
|
overviewRulerBorder: false,
|
|
|
|
|
hideCursorInOverviewRuler: true,
|
|
|
|
|
automaticLayout: true,
|
|
|
|
|
tabSize: 2,
|
|
|
|
|
insertSpaces: false,
|
|
|
|
|
detectIndentation: true,
|
|
|
|
|
formatOnPaste: false,
|
|
|
|
|
formatOnType: false,
|
|
|
|
|
suggestOnTriggerCharacters: true,
|
|
|
|
|
quickSuggestions: true,
|
|
|
|
|
contextmenu: true,
|
|
|
|
|
mouseWheelZoom: true,
|
|
|
|
|
find: {
|
|
|
|
|
addExtraSpaceOnTop: false,
|
|
|
|
|
autoFindInSelection: "multiline",
|
|
|
|
|
seedSearchStringFromSelection: "selection",
|
|
|
|
|
},
|
|
|
|
|
}}
|
|
|
|
|
/>
|
|
|
|
|
</div>
|
|
|
|
|
</div>
|
|
|
|
|
);
|
|
|
|
|
}
|