waveterm/frontend/app/view/codeeditor/codeeditor.tsx
Mike Sawka ff9923f486
Session Durability Checkpoint (#2821)
Working on bug fixes and UX. Streams restarting, fixed lots of bugs,
timing issues, concurrency bugs. Get status shipped to the FE to drive
"shield" state display. Deal with stale streams.

Also big UX changes to the block headers. Specialize the terminal
headers to prioritize the connection (sense of place), remove old
terminal icon and word "Terminal" from the header. Also drop "Web" and
"Preview" labels on web/preview blocks.

Added `wsh focusblock` command.
2026-02-03 11:49:52 -08:00

110 lines
3.6 KiB
TypeScript

// Copyright 2025, Command Line Inc.
// SPDX-License-Identifier: Apache-2.0
import { MonacoCodeEditor } from "@/app/monaco/monaco-react";
import { useOverrideConfigAtom } from "@/app/store/global";
import { boundNumber } from "@/util/util";
import type * as MonacoTypes from "monaco-editor";
import * as MonacoModule from "monaco-editor";
import React, { useMemo, useRef } from "react";
function defaultEditorOptions(): MonacoTypes.editor.IEditorOptions {
const opts: MonacoTypes.editor.IEditorOptions = {
scrollBeyondLastLine: false,
fontSize: 12,
fontFamily: "Hack",
smoothScrolling: true,
scrollbar: {
useShadows: false,
verticalScrollbarSize: 5,
horizontalScrollbarSize: 5,
},
minimap: {
enabled: true,
},
stickyScroll: {
enabled: false,
},
};
return opts;
}
interface CodeEditorProps {
blockId: string;
text: string;
readonly: boolean;
language?: string;
fileName?: string;
onChange?: (text: string) => void;
onMount?: (monacoPtr: MonacoTypes.editor.IStandaloneCodeEditor, monaco: typeof MonacoModule) => () => void;
}
export function CodeEditor({ blockId, text, language, fileName, readonly, onChange, onMount }: CodeEditorProps) {
const divRef = useRef<HTMLDivElement>(null);
const unmountRef = useRef<() => void>(null);
const minimapEnabled = useOverrideConfigAtom(blockId, "editor:minimapenabled") ?? false;
const stickyScrollEnabled = useOverrideConfigAtom(blockId, "editor:stickyscrollenabled") ?? false;
const wordWrap = useOverrideConfigAtom(blockId, "editor:wordwrap") ?? false;
const fontSize = boundNumber(useOverrideConfigAtom(blockId, "editor:fontsize"), 6, 64);
const uuidRef = useRef(crypto.randomUUID()).current;
let editorPath: string;
if (fileName) {
const separator = fileName.startsWith("/") ? "" : "/";
editorPath = blockId + separator + fileName;
} else {
editorPath = uuidRef;
}
React.useEffect(() => {
return () => {
// unmount function
if (unmountRef.current) {
unmountRef.current();
}
};
}, []);
function handleEditorChange(text: string) {
if (onChange) {
onChange(text);
}
}
function handleEditorOnMount(
editor: MonacoTypes.editor.IStandaloneCodeEditor,
monaco: typeof MonacoModule
): () => void {
if (onMount) {
const cleanup = onMount(editor, monaco);
unmountRef.current = cleanup;
return cleanup;
}
return undefined;
}
const editorOpts = useMemo(() => {
const opts = defaultEditorOptions();
opts.minimap.enabled = minimapEnabled;
opts.stickyScroll.enabled = stickyScrollEnabled;
opts.wordWrap = wordWrap ? "on" : "off";
opts.fontSize = fontSize;
opts.copyWithSyntaxHighlighting = false;
return opts;
}, [minimapEnabled, stickyScrollEnabled, wordWrap, fontSize, readonly]);
return (
<div className="flex flex-col w-full h-full overflow-hidden items-center justify-center">
<div className="flex flex-col h-full w-full" ref={divRef}>
<MonacoCodeEditor
readonly={readonly}
text={text}
options={editorOpts}
onChange={handleEditorChange}
onMount={handleEditorOnMount}
path={editorPath}
language={language}
/>
</div>
</div>
);
}