From 2ada4a06167b3a00a4c4c69e1b0c78b00ef1db5f Mon Sep 17 00:00:00 2001 From: Kristóf Marussy Date: Sun, 10 Oct 2021 01:11:33 +0200 Subject: feat(web): add CodeMirror 6 editor --- language-web/package.json | 17 +- language-web/src/main/css/index.scss | 213 ---------------- language-web/src/main/js/RootStore.tsx | 4 +- language-web/src/main/js/editor/EditorArea.tsx | 142 ++++++++--- language-web/src/main/js/editor/EditorButtons.tsx | 35 ++- language-web/src/main/js/editor/EditorParent.ts | 61 +++++ language-web/src/main/js/editor/EditorStore.ts | 294 ++++++++++++---------- language-web/src/main/js/editor/editor.ts | 18 -- language-web/src/main/js/index.tsx | 3 +- language-web/src/main/js/theme/ThemeStore.ts | 4 + language-web/yarn.lock | 220 +++++++++++++++- 11 files changed, 588 insertions(+), 423 deletions(-) create mode 100644 language-web/src/main/js/editor/EditorParent.ts delete mode 100644 language-web/src/main/js/editor/editor.ts diff --git a/language-web/package.json b/language-web/package.json index 7a931d95..d55968ee 100644 --- a/language-web/package.json +++ b/language-web/package.json @@ -62,20 +62,33 @@ "dependencies": { "ansi-styles": "^6.1.0", "@babel/runtime": "^7.15.4", + "@codemirror/autocomplete": "^0.19.3", + "@codemirror/closebrackets": "^0.19.0", + "@codemirror/commands": "^0.19.5", + "@codemirror/comment": "^0.19.0", + "@codemirror/fold": "^0.19.0", + "@codemirror/gutter": "^0.19.2", + "@codemirror/highlight": "^0.19.6", + "@codemirror/history": "^0.19.0", + "@codemirror/language": "^0.19.3", + "@codemirror/lint": "^0.19.2", + "@codemirror/matchbrackets": "^0.19.3", + "@codemirror/rectangular-selection": "^0.19.1", + "@codemirror/search": "^0.19.2", + "@codemirror/state": "^0.19.0", + "@codemirror/view": "^0.19.9", "@emotion/react": "^11.4.1", "@emotion/styled": "^11.3.0", "@fontsource/jetbrains-mono": "^4.5.0", "@fontsource/roboto": "^4.5.0", "@mui/material": "5.0.2", "@mui/icons-material": "5.0.1", - "codemirror": "^5.63.1", "jquery": "^3.6.0", "loglevel": "^1.7.1", "loglevel-plugin-prefix": "^0.8.4", "mobx": "^6.3.3", "mobx-react-lite": "^3.2.1", "react": "^17.0.2", - "react-codemirror2": "npm:react17-codemirror2@^7.2.3", "react-dom": "^17.0.2" } } diff --git a/language-web/src/main/css/index.scss b/language-web/src/main/css/index.scss index 54f3a654..ad876aaf 100644 --- a/language-web/src/main/css/index.scss +++ b/language-web/src/main/css/index.scss @@ -1,13 +1,6 @@ -@use 'sass:map'; @use '@fontsource/roboto/scss/mixins' as Roboto; @use '@fontsource/jetbrains-mono/scss/mixins' as JetbrainsMono; -@import 'codemirror/lib/codemirror'; -@import 'codemirror/addon/hint/show-hint'; -@import 'codemirror/theme/material-darker'; - -@import './themes'; - $fontWeights: 300, 400, 500, 700; @each $weight in $fontWeights { @include Roboto.fontFace($fontName: 'Roboto', $weight: $weight); @@ -21,209 +14,3 @@ $monoFontWeights: 400, 700; } @include JetbrainsMono.fontFaceVariable($fontName: 'JetBrains MonoVariable'); @include JetbrainsMono.fontFaceVariable($fontName: 'JetBrains MonoVariable', $style: italic); - -body { - font-family: 'Roboto', sans-serif; -} - -.CodeMirror { - height: 100%; -} - -.problem-fallback-editor { - display: block; - height: 100%; - width: 100%; - resize: none; - border: none; - outline: none; - padding: 4px 4px 4px 16px; - white-space: pre; - overflow-wrap: normal; - overflow: auto; -} - -.CodeMirror, .CodeMirror-hints, .problem-fallback-editor { - font-size: 16px; - font-family: 'JetBrains MonoVariable', 'JetBrains Mono', monospace; - font-feature-settings: 'liga', 'calt'; - font-weight: 400; - text-rendering: optimizeLegibility; - line-height: 1.35; - letter-spacing: 0; -} - -@each $themeName, $theme in $themes { - .cm-s-problem-#{$themeName} { - &.CodeMirror { - background: map.get($theme, 'background'); - color: map.get($theme, 'foreground'); - } - - &.problem-fallback-editor { - background: map.get($theme, 'background'); - color: map.get($theme, 'foreground'); - caret-color: map.get($theme, 'cursor'); - - &::selection { - background: map.get($theme, 'selection'); - } - } - - .CodeMirror-gutters { - background: map.get($theme, 'background'); - border: none; - } - - .CodeMirror-cursor { - border-left: 1px solid map.get($theme, 'cursor'); - } - - div.CodeMirror-selected, - &.CodeMirror-focused div.CodeMirror-selected, - .CodeMirror-line::selection, - .CodeMirror-line > span::selection, - .CodeMirror-line > span > span::selection { - background: map.get($theme, 'selection'); - } - - .CodeMirror-guttermarker, - .CodeMirror-guttermarker-subtle, - .CodeMirror-linenumber { - color: map.get($theme, 'lineNumber'); - } - - .CodeMirror-activeline-background { - background: map.get($theme, 'currentLine'); - } - - .CodeMirror-activeline-gutter { - background: map.get($theme, 'currentLine'); - - .CodeMirror-guttermarker, - .CodeMirror-guttermarker-subtle, - .CodeMirror-linenumber { - color: map.get($theme, 'foreground'); - } - } - - .cm-keyword { - color: map.get($theme, 'keyword'); - } - - .cm-number { - color: map.get($theme, 'number'); - } - - .cm-lparen, .cm-rparen { - color: map.get($theme, 'delimiter'); - } - - .cm-comment { - color: map.get($theme, 'comment'); - font-style: italic; - } - - .problem-predicate, .problem-class, .problem-reference, .problem-enum { - color: map.get($theme, 'predicate'); - } - - .problem-unique-node { - color: map.get($theme, 'uniqueNode'); - } - - .problem-variable { - color: map.get($theme, 'variable'); - } - } -} - -.CodeMirror-hints { - background: #333; - border: 0; - border-radius: 4px; - box-shadow: 0 2px 4px -1px rgba(0, 0, 0, 0.2), - 0 5px 8px 0 rgba(0, 0, 0, 0.14), - 0 1px 8px 0 rgba(0, 0, 0, 0.12); - padding: 0; -} - -.CodeMirror-hint { - color: #fff; - border-radius: 0; -} - -li.CodeMirror-hint-active { - background: rgba(128, 203, 196, 0.2); -} - -.annotations-gutter { - width: 12px; -} - -.xtext-annotation_error { - width: 12px; - height: 1em; - background-image: url('images/error_an.gif'); - background-repeat: no-repeat; - background-position: bottom; -} - -.xtext-annotation_warning { - width: 12px; - height: 1em; - background-image: url('images/warning_an.gif'); - background-repeat: no-repeat; - background-position: bottom; -} - -.xtext-annotation_info { - width: 12px; - height: 1em; - background-image: url('images/info_an.gif'); - background-repeat: no-repeat; - background-position: bottom; -} - -.xtext-marker_error { - z-index: 30; - background-image: url("data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAAAQAAAADCAYAAAC09K7GAAAABmJLR0QA/wD/AP+gvaeTAAAAHElEQVQI12NggIL/DAz/GdA5/xkY/qPKMDAwAADLZwf5rvm+LQAAAABJRU5ErkJggg=="); - background-repeat: repeat-x; - background-position: left bottom; -} - -.xtext-marker_warning { - z-index: 20; - background-image: url("data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAAAQAAAADCAYAAAC09K7GAAAABmJLR0QA/wD/AP+gvaeTAAAAMklEQVQI12NkgIIvJ3QXMjAwdDN+OaEbysDA4MPAwNDNwMCwiOHLCd1zX07o6kBVGQEAKBANtobskNMAAAAASUVORK5CYII="); - background-repeat: repeat-x; - background-position: left bottom; -} - -.xtext-marker_info { - z-index: 10; - background-image: url("data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAAAQAAAADCAYAAAC09K7GAAAABmJLR0QA/wD/AP+gvaeTAAAANklEQVQI12NkgIIVRx8tZGBg6GZccfRRKAMDgw8DA0M3AwPDIiYGBoZKBgaG7ghruSsMDAwpABH5CoqwzCoTAAAAAElFTkSuQmCC"); - background-repeat: repeat-x; - background-position: left bottom; -} - -.xtext-marker_read { - background: rgba(128, 203, 196, 0.2); - display: inline-block; -} - - -.xtext-marker_write { - background: rgba(255, 229, 100, 0.2); - display: inline-block; -} - -.problem-abstract { - font-style: italic; -} - -.problem-containment { - font-weight: 700; -} -.problem-new-node { - font-style: italic; -} diff --git a/language-web/src/main/js/RootStore.tsx b/language-web/src/main/js/RootStore.tsx index 88b8a445..96e1b26a 100644 --- a/language-web/src/main/js/RootStore.tsx +++ b/language-web/src/main/js/RootStore.tsx @@ -8,9 +8,9 @@ export class RootStore { themeStore; - constructor() { + constructor(initialValue: string) { this.themeStore = new ThemeStore(); - this.editorStore = new EditorStore(this.themeStore); + this.editorStore = new EditorStore(initialValue, this.themeStore); } } diff --git a/language-web/src/main/js/editor/EditorArea.tsx b/language-web/src/main/js/editor/EditorArea.tsx index 531a57c9..58d65184 100644 --- a/language-web/src/main/js/editor/EditorArea.tsx +++ b/language-web/src/main/js/editor/EditorArea.tsx @@ -1,41 +1,123 @@ +import { Command, EditorView } from '@codemirror/view'; +import { closeSearchPanel, openSearchPanel } from '@codemirror/search'; +import { closeLintPanel, openLintPanel } from '@codemirror/lint'; import { observer } from 'mobx-react-lite'; -import React, { useRef } from 'react'; +import React, { useEffect, useRef, useState } from 'react'; +import { EditorParent } from './EditorParent'; +import { getLogger } from '../logging'; import { useRootStore } from '../RootStore'; +const log = getLogger('EditorArea'); + +function usePanel( + label: string, + stateToSet: boolean, + editorView: EditorView | null, + openCommand: Command, + closeCommand: Command, +) { + const [cachedViewState, setCachedViewState] = useState(false); + useEffect(() => { + if (editorView === null || cachedViewState === stateToSet) { + return; + } + const success = stateToSet ? openCommand(editorView) : closeCommand(editorView); + if (!success) { + log.error( + 'Failed to synchronize', + label, + 'panel state - store state:', + cachedViewState, + 'view state:', + stateToSet, + ); + } + setCachedViewState(stateToSet); + }, [ + stateToSet, + editorView, + cachedViewState, + label, + openCommand, + closeCommand, + ]); + return setCachedViewState; +} + export const EditorArea = observer(() => { const { editorStore } = useRootStore(); - const { CodeMirror } = editorStore.chunk || {}; - const fallbackTextarea = useRef(null); - - if (!CodeMirror) { - return ( -