| // Copyright (C) 2023 The Android Open Source Project |
| // |
| // Licensed under the Apache License, Version 2.0 (the "License"); |
| // you may not use this file except in compliance with the License. |
| // You may obtain a copy of the License at |
| // |
| // http://www.apache.org/licenses/LICENSE-2.0 |
| // |
| // Unless required by applicable law or agreed to in writing, software |
| // distributed under the License is distributed on an "AS IS" BASIS, |
| // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. |
| // See the License for the specific language governing permissions and |
| // limitations under the License. |
| |
| import {indentWithTab} from '@codemirror/commands'; |
| import {Transaction} from '@codemirror/state'; |
| import {oneDarkTheme} from '@codemirror/theme-one-dark'; |
| import {keymap} from '@codemirror/view'; |
| import {basicSetup, EditorView} from 'codemirror'; |
| import m from 'mithril'; |
| |
| export interface EditorAttrs { |
| // Initial state for the editor. |
| initialText?: string; |
| |
| // Callback for the Ctrl/Cmd + Enter key binding. |
| onExecute?: (text: string) => void; |
| |
| // Callback for every change to the text. |
| onUpdate?: (text: string) => void; |
| } |
| |
| export class Editor implements m.ClassComponent<EditorAttrs> { |
| private editorView?: EditorView; |
| |
| oncreate({dom, attrs}: m.CVnodeDOM<EditorAttrs>) { |
| const keymaps = [indentWithTab]; |
| const onExecute = attrs.onExecute; |
| const onUpdate = attrs.onUpdate; |
| |
| if (onExecute) { |
| keymaps.push({ |
| key: 'Mod-Enter', |
| run: (view: EditorView) => { |
| const state = view.state; |
| const selection = state.selection; |
| let text = state.doc.toString(); |
| if (!selection.main.empty) { |
| let selectedText = ''; |
| |
| for (const r of selection.ranges) { |
| selectedText += text.slice(r.from, r.to); |
| } |
| |
| text = selectedText; |
| } |
| onExecute(text); |
| return true; |
| }, |
| }); |
| } |
| |
| let dispatch; |
| if (onUpdate) { |
| dispatch = (tr: Transaction, view: EditorView) => { |
| view.update([tr]); |
| const text = view.state.doc.toString(); |
| onUpdate(text); |
| }; |
| } |
| |
| this.editorView = new EditorView({ |
| doc: attrs.initialText ?? '', |
| extensions: [ |
| keymap.of(keymaps), |
| oneDarkTheme, |
| basicSetup, |
| ], |
| parent: dom, |
| dispatch, |
| }); |
| } |
| |
| onremove(): void { |
| if (this.editorView) { |
| this.editorView.destroy(); |
| this.editorView = undefined; |
| } |
| } |
| |
| view({}: m.Vnode<EditorAttrs, this>): void|m.Children { |
| return m( |
| '.pf-editor', |
| ); |
| } |
| } |