Line data Source code
1 : import * as React from 'react';
2 : import * as themes from './themes/index.js';
3 :
4 : import { EditorState, Extension } from '@codemirror/state';
5 : import { defaultKeymap, history, historyKeymap } from "@codemirror/commands"
6 : import { search, searchKeymap } from '@codemirror/search';
7 : import { EditorView, drawSelection, keymap, lineNumbers } from '@codemirror/view';
8 : import { DashQLExtensions, DashQLReadonlyExtensions } from './dashql_extension.js';
9 :
10 : import { useLogger } from '../../platform/logger/logger_provider.js';
11 :
12 : import './codemirror.css';
13 :
14 : export interface CodeMirrorProps {
15 : /// Root of the DOM where the editor is mounted
16 : root?: ShadowRoot | Document;
17 : /// Override styles for the container div (e.g. height: 'auto' for auto-expand)
18 : style?: React.CSSProperties;
19 : /// Additional extensions appended after the default set
20 : extraExtensions?: Extension[];
21 : /// Full editor-specific extension set to use instead of defaults. Shared search is still appended.
22 : extensions?: Extension[];
23 : /// Document used when constructing the EditorState. Later changes are owned by the caller.
24 : initialDoc?: string;
25 : }
26 :
27 1 : export const CodeMirrorSearchExtensions: Extension[] = [
28 1 : search({ top: true }),
29 1 : keymap.of(searchKeymap),
30 1 : ];
31 :
32 1 : export function createCodeMirrorExtensions(): Extension[] {
33 : // See: https://github.com/codemirror/basic-setup/blob/main/src/codemirror.ts
34 : // We might want to add other plugins later.
35 1 : const keymapExtension = keymap.of([
36 1 : ...defaultKeymap,
37 1 : ...historyKeymap
38 1 : ]);
39 : const extensions: Extension[] = [
40 1 : themes.xcode,
41 1 : lineNumbers(),
42 1 : drawSelection(),
43 1 : history(),
44 1 : ...DashQLExtensions,
45 1 : ...CodeMirrorSearchExtensions,
46 1 : keymapExtension
47 : ];
48 1 : return extensions;
49 1 : }
50 :
51 1 : export function createReadonlyCodeMirrorExtensions(): Extension[] {
52 1 : return [
53 1 : themes.xcode,
54 1 : lineNumbers(),
55 1 : drawSelection(),
56 1 : EditorState.readOnly.of(true),
57 1 : ...DashQLReadonlyExtensions,
58 1 : ...CodeMirrorSearchExtensions,
59 1 : ];
60 1 : }
61 :
62 1 : export const CodeMirror = React.forwardRef<EditorView, CodeMirrorProps>((props: CodeMirrorProps, ref) => {
63 1 : const logger = useLogger();
64 :
65 1 : const [node, setNode] = React.useState<HTMLDivElement | null>(null);
66 :
67 1 : React.useEffect(() => {
68 : // Not mounted yet?
69 : // Then do nothing.
70 1 : if (node == null) {
71 1 : return () => { };
72 : }
73 1 : logger.debug("Creating a new codemirror view", {}, "codemirror");
74 :
75 : // The DOM node has changed, create a new view
76 1 : const extensions = props.extensions == null
77 1 : ? [...createCodeMirrorExtensions(), ...(props.extraExtensions ?? [])]
78 1 : : [...props.extensions, ...CodeMirrorSearchExtensions];
79 1 : const view = new EditorView({
80 1 : state: EditorState.create({ doc: props.initialDoc, extensions }),
81 1 : parent: node,
82 1 : root: props.root,
83 1 : });
84 :
85 : // Forward the ref
86 1 : if (typeof ref === 'function') {
87 1 : ref(view);
88 1 : } else if (ref) {
89 1 : ref.current = view;
90 : }
91 :
92 : // Destroy the view when unmounting
93 1 : return () => {
94 1 : view.destroy();
95 1 : };
96 1 : }, [node, ref]);
97 :
98 :
99 1 : return <div style={{ width: '100%', height: '100%', ...props.style }} ref={setNode}></div>;
100 1 : });
|