Update CodeMirror to V6 This change depends on Ic9e9ec8fc40a3b1fc022e1f089691e56ef5dcefa to add the new dependacies on CodeMirror v6. Depends-On: Ic9e9ec8fc40a3b1fc022e1f089691e56ef5dcefa Change-Id: Ia837c3f2e3c943051a352c85ff3b7b48ab160c0f
diff --git a/web/BUILD b/web/BUILD index 9b4b35b..3c91428 100644 --- a/web/BUILD +++ b/web/BUILD
@@ -32,7 +32,6 @@ tsconfig = ":tsconfig", deps = [ "@plugins_npm//@gerritcodereview/typescript-api", - "@plugins_npm//@types/codemirror", "@plugins_npm//lit", ], ) @@ -55,9 +54,27 @@ tsconfig = ":tsconfig", deps = [ "@plugins_npm//@gerritcodereview/typescript-api", - "@plugins_npm//@types/codemirror", - "@plugins_npm//codemirror", "@plugins_npm//lit", + "@plugins_npm//@codemirror/autocomplete", + "@plugins_npm//@codemirror/commands", + "@plugins_npm//@codemirror/lang-css", + "@plugins_npm//@codemirror/lang-cpp", + "@plugins_npm//@codemirror/lang-html", + "@plugins_npm//@codemirror/lang-java", + "@plugins_npm//@codemirror/lang-javascript", + "@plugins_npm//@codemirror/lang-json", + "@plugins_npm//@codemirror/lang-markdown", + "@plugins_npm//@codemirror/lang-php", + "@plugins_npm//@codemirror/lang-python", + "@plugins_npm//@codemirror/lang-rust", + "@plugins_npm//@codemirror/lang-sql", + "@plugins_npm//@codemirror/lang-xml", + "@plugins_npm//@codemirror/language", + "@plugins_npm//@codemirror/language-data", + "@plugins_npm//@codemirror/lint", + "@plugins_npm//@codemirror/search", + "@plugins_npm//@codemirror/state", + "@plugins_npm//@codemirror/view", ], )
diff --git a/web/codemirror-imports.ts b/web/codemirror-imports.ts deleted file mode 100644 index 6db3037..0000000 --- a/web/codemirror-imports.ts +++ /dev/null
@@ -1,44 +0,0 @@ -/** - * @license - * Copyright 2022 Google LLC - * SPDX-License-Identifier: Apache-2.0 - */ -import 'codemirror'; - -import {EditorConfiguration} from 'codemirror'; - -declare global { - interface HTMLElementTagNameMap { - 'codemirror-element': CodeMirrorElement; - } -} - -/** - * The type `codemirror.EditorConfiguration` is just outdated, so we have to add - * some props manually here. - */ -export interface CodeMirrorConfig extends EditorConfiguration { - autoCloseBrackets?: boolean; - matchBrackets?: boolean; - showTabs?: boolean; - showTrailingSpace?: boolean; - lineSeparator?: string; - rulers?: false | ReadonlyArray<number | Ruler> | undefined; -} - -/** - * <codemirror-element> is defined in a separate javascript bundle, so let's - * define its interface here. - */ -interface CodeMirrorElement extends HTMLElement { - lineNum?: number; - params?: CodeMirrorConfig; -} - -interface Ruler { - column: number; - className?: string | undefined; - color?: string | undefined; - lineStyle?: string | undefined; - width?: string | undefined; -}
diff --git a/web/element/codemirror-css.ts b/web/element/codemirror-css.ts deleted file mode 100644 index 71f9cf3..0000000 --- a/web/element/codemirror-css.ts +++ /dev/null
@@ -1,516 +0,0 @@ -/** - * @license - * Copyright 2022 Google LLC - * SPDX-License-Identifier: Apache-2.0 - */ -import {css} from 'lit'; - -/** - * This is just a copy of lib/codemirror.css. - * We need to be able to expose this to lit. - */ -export const codemirrorStyles = css` - /* BASICS */ - - .CodeMirror { - /* Set height, width, borders, and global font properties here */ - font-family: monospace; - height: 300px; - color: black; - direction: ltr; - } - - /* PADDING */ - - .CodeMirror-lines { - padding: 4px 0; /* Vertical padding around content */ - } - /* @noflip */ - .CodeMirror pre.CodeMirror-line, - .CodeMirror pre.CodeMirror-line-like { - padding: 0 4px; /* Horizontal padding of content */ - } - - .CodeMirror-scrollbar-filler, - .CodeMirror-gutter-filler { - background-color: white; /* The little square between H and V scrollbars */ - } - - /* GUTTER */ - - /* @noflip */ - .CodeMirror-gutters { - border-right: 1px solid #ddd; - background-color: #f7f7f7; - white-space: nowrap; - } - .CodeMirror-linenumbers { - } - /* @noflip */ - .CodeMirror-linenumber { - padding: 0 3px 0 5px; - min-width: 20px; - text-align: right; - color: #999; - white-space: nowrap; - } - - .CodeMirror-guttermarker { - color: black; - } - .CodeMirror-guttermarker-subtle { - color: #999; - } - - /* CURSOR */ - - /* @noflip */ - .CodeMirror-cursor { - border-left: 1px solid black; - border-right: none; - width: 0; - } - /* Shown when moving in bi-directional text */ - /* @noflip */ - .CodeMirror div.CodeMirror-secondarycursor { - border-left: 1px solid silver; - } - .cm-fat-cursor .CodeMirror-cursor { - width: auto; - border: 0 !important; - background: #7e7; - } - .cm-fat-cursor div.CodeMirror-cursors { - z-index: 1; - } - .cm-fat-cursor .CodeMirror-line::selection, - .cm-fat-cursor .CodeMirror-line > span::selection, - .cm-fat-cursor .CodeMirror-line > span > span::selection { - background: transparent; - } - .cm-fat-cursor .CodeMirror-line::-moz-selection, - .cm-fat-cursor .CodeMirror-line > span::-moz-selection, - .cm-fat-cursor .CodeMirror-line > span > span::-moz-selection { - background: transparent; - } - .cm-fat-cursor { - caret-color: transparent; - } - @-moz-keyframes blink { - 0% { - } - 50% { - background-color: transparent; - } - 100% { - } - } - @-webkit-keyframes blink { - 0% { - } - 50% { - background-color: transparent; - } - 100% { - } - } - @keyframes blink { - 0% { - } - 50% { - background-color: transparent; - } - 100% { - } - } - - /* Can style cursor different in overwrite (non-insert) mode */ - .CodeMirror-overwrite .CodeMirror-cursor { - } - - .cm-tab { - display: inline-block; - text-decoration: inherit; - } - - .CodeMirror-rulers { - position: absolute; - left: 0; - right: 0; - top: -50px; - bottom: 0; - overflow: hidden; - } - /* @noflip */ - .CodeMirror-ruler { - border-left: 1px solid #ccc; - top: 0; - bottom: 0; - position: absolute; - } - - /* DEFAULT THEME */ - - .cm-s-default .cm-header { - color: blue; - } - .cm-s-default .cm-quote { - color: #090; - } - .cm-negative { - color: #d44; - } - .cm-positive { - color: #292; - } - .cm-header, - .cm-strong { - font-weight: bold; - } - .cm-em { - font-style: italic; - } - .cm-link { - text-decoration: underline; - } - .cm-strikethrough { - text-decoration: line-through; - } - - .cm-s-default .cm-keyword { - color: #708; - } - .cm-s-default .cm-atom { - color: #219; - } - .cm-s-default .cm-number { - color: #164; - } - .cm-s-default .cm-def { - color: #00f; - } - .cm-s-default .cm-variable, - .cm-s-default .cm-punctuation, - .cm-s-default .cm-property, - .cm-s-default .cm-operator { - } - .cm-s-default .cm-variable-2 { - color: #05a; - } - .cm-s-default .cm-variable-3, - .cm-s-default .cm-type { - color: #085; - } - .cm-s-default .cm-comment { - color: #a50; - } - .cm-s-default .cm-string { - color: #a11; - } - .cm-s-default .cm-string-2 { - color: #f50; - } - .cm-s-default .cm-meta { - color: #555; - } - .cm-s-default .cm-qualifier { - color: #555; - } - .cm-s-default .cm-builtin { - color: #30a; - } - .cm-s-default .cm-bracket { - color: #997; - } - .cm-s-default .cm-tag { - color: #170; - } - .cm-s-default .cm-attribute { - color: #00c; - } - .cm-s-default .cm-hr { - color: #999; - } - .cm-s-default .cm-link { - color: #00c; - } - - .cm-s-default .cm-error { - color: #f00; - } - .cm-invalidchar { - color: #f00; - } - - .CodeMirror-composing { - border-bottom: 2px solid; - } - - /* Default styles for common addons */ - - div.CodeMirror span.CodeMirror-matchingbracket { - color: #0b0; - } - div.CodeMirror span.CodeMirror-nonmatchingbracket { - color: #a22; - } - .CodeMirror-matchingtag { - background: rgba(255, 150, 0, 0.3); - } - .CodeMirror-activeline-background { - background: #e8f2ff; - } - - /* STOP */ - - /* The rest of this file contains styles related to the mechanics of - the editor. You probably shouldn't touch them. */ - - .CodeMirror { - position: relative; - overflow: hidden; - background: white; - } - - /* @noflip */ - .CodeMirror-scroll { - overflow: scroll !important; /* Things will break if this is overridden */ - /* 50px is the magic margin used to hide the element's real scrollbars */ - /* See overflow: hidden in .CodeMirror */ - margin-bottom: -50px; - margin-right: -50px; - padding-bottom: 50px; - height: 100%; - outline: none; /* Prevent dragging from highlighting the element */ - position: relative; - z-index: 0; - } - /* @noflip */ - .CodeMirror-sizer { - position: relative; - border-right: 50px solid transparent; - } - - /* The fake, visible scrollbars. Used to force redraw during scrolling - before actual scrolling happens, thus preventing shaking and - flickering artifacts. */ - .CodeMirror-vscrollbar, - .CodeMirror-hscrollbar, - .CodeMirror-scrollbar-filler, - .CodeMirror-gutter-filler { - position: absolute; - z-index: 6; - display: none; - outline: none; - } - /* @noflip */ - .CodeMirror-vscrollbar { - right: 0; - top: 0; - overflow-x: hidden; - overflow-y: scroll; - } - /* @noflip */ - .CodeMirror-hscrollbar { - bottom: 0; - left: 0; - overflow-y: hidden; - overflow-x: scroll; - } - /* @noflip */ - .CodeMirror-scrollbar-filler { - right: 0; - bottom: 0; - } - /* @noflip */ - .CodeMirror-gutter-filler { - left: 0; - bottom: 0; - } - - /* @noflip */ - .CodeMirror-gutters { - position: absolute; - left: 0; - top: 0; - min-height: 100%; - z-index: 3; - } - .CodeMirror-gutter { - white-space: normal; - height: 100%; - display: inline-block; - vertical-align: top; - margin-bottom: -50px; - } - .CodeMirror-gutter-wrapper { - position: absolute; - z-index: 4; - background: none !important; - border: none !important; - } - .CodeMirror-gutter-background { - position: absolute; - top: 0; - bottom: 0; - z-index: 4; - } - .CodeMirror-gutter-elt { - position: absolute; - cursor: default; - z-index: 4; - } - .CodeMirror-gutter-wrapper ::selection { - background-color: transparent; - } - .CodeMirror-gutter-wrapper ::-moz-selection { - background-color: transparent; - } - - .CodeMirror-lines { - cursor: text; - min-height: 1px; /* prevents collapsing before first draw */ - } - .CodeMirror pre.CodeMirror-line, - .CodeMirror pre.CodeMirror-line-like { - /* Reset some styles that the rest of the page might have set */ - -moz-border-radius: 0; - -webkit-border-radius: 0; - border-radius: 0; - border-width: 0; - background: transparent; - font-family: inherit; - font-size: inherit; - margin: 0; - white-space: pre; - word-wrap: normal; - line-height: inherit; - color: inherit; - z-index: 2; - position: relative; - overflow: visible; - -webkit-tap-highlight-color: transparent; - -webkit-font-variant-ligatures: contextual; - font-variant-ligatures: contextual; - } - .CodeMirror-wrap pre.CodeMirror-line, - .CodeMirror-wrap pre.CodeMirror-line-like { - word-wrap: break-word; - white-space: pre-wrap; - word-break: normal; - } - - /* @noflip */ - .CodeMirror-linebackground { - position: absolute; - left: 0; - right: 0; - top: 0; - bottom: 0; - z-index: 0; - } - - .CodeMirror-linewidget { - position: relative; - z-index: 2; - padding: 0.1px; /* Force widget margins to stay inside of the container */ - } - - .CodeMirror-widget { - } - - .CodeMirror-rtl pre { - direction: rtl; - } - - .CodeMirror-code { - outline: none; - } - - /* Force content-box sizing for the elements where we expect it */ - .CodeMirror-scroll, - .CodeMirror-sizer, - .CodeMirror-gutter, - .CodeMirror-gutters, - .CodeMirror-linenumber { - -moz-box-sizing: content-box; - box-sizing: content-box; - } - - .CodeMirror-measure { - position: absolute; - width: 100%; - height: 0; - overflow: hidden; - visibility: hidden; - } - - .CodeMirror-cursor { - position: absolute; - pointer-events: none; - } - .CodeMirror-measure pre { - position: static; - } - - div.CodeMirror-cursors { - visibility: hidden; - position: relative; - z-index: 3; - } - div.CodeMirror-dragcursors { - visibility: visible; - } - - .CodeMirror-focused div.CodeMirror-cursors { - visibility: visible; - } - - .CodeMirror-selected { - background: #d9d9d9; - } - .CodeMirror-focused .CodeMirror-selected { - background: #d7d4f0; - } - .CodeMirror-crosshair { - cursor: crosshair; - } - .CodeMirror-line::selection, - .CodeMirror-line > span::selection, - .CodeMirror-line > span > span::selection { - background: #d7d4f0; - } - .CodeMirror-line::-moz-selection, - .CodeMirror-line > span::-moz-selection, - .CodeMirror-line > span > span::-moz-selection { - background: #d7d4f0; - } - - .cm-searching { - background-color: #ffa; - background-color: rgba(255, 255, 0, 0.4); - } - - /* Used to force a border model for a node */ - /* @noflip */ - .cm-force-border { - padding-right: 0.1px; - } - - @media print { - /* Hide the cursor when printing */ - .CodeMirror div.CodeMirror-cursors { - visibility: hidden; - } - } - - /* See issue #2901 */ - .cm-tab-wrap-hack:after { - content: ''; - } - - /* Help users use markselection to safely style text background */ - span.CodeMirror-selectedtext { - background: none; - } -`;
diff --git a/web/element/codemirror-element.ts b/web/element/codemirror-element.ts index 3cdd668..d8afff6 100644 --- a/web/element/codemirror-element.ts +++ b/web/element/codemirror-element.ts
@@ -3,17 +3,12 @@ * Copyright 2022 Google LLC * SPDX-License-Identifier: Apache-2.0 */ -import './codemirror-imports'; - -import {EditorConfiguration} from 'codemirror'; -import {css, html, LitElement} from 'lit'; -import {customElement, property, query} from 'lit/decorators.js'; - -import {codemirrorStyles} from './codemirror-css'; -import {dialogStyles} from './dialog-css'; -import {foldgutterStyles} from './foldgutter-css'; -import {searchMatchStyles} from './matchesonscrollbar-css'; -import {simpleScrollStyles} from './simplescrollbars-css'; +import {LitElement, css, html} from 'lit'; +import {customElement, property, query} from 'lit/decorators'; +import {EditorView} from '@codemirror/view'; +import {EditorState} from '@codemirror/state'; +import {updateRulerWidth} from './ruler'; +import {extensions} from './extensions'; type ValueChangedEvent<T = string> = CustomEvent<{value: T}>; @@ -26,45 +21,59 @@ } } +/** + * This is a standard REST API object: + * https://gerrit-review.googlesource.com/Documentation/rest-api-accounts.html#edit-preferences-info + * + * TODO: Add this object to the plugin API. + */ +export interface EditPreferencesInfo { + tab_size?: number; + line_length?: number; + indent_unit?: number; + show_tabs?: boolean; + show_whitespace_errors?: boolean; + syntax_highlighting?: boolean; + match_brackets?: boolean; + line_wrapping?: boolean; + indent_with_tabs?: boolean; + auto_close_brackets?: boolean; +} + @customElement('codemirror-element') export class CodeMirrorElement extends LitElement { @property({type: Number}) lineNum?: number; - @property({type: Object}) params?: EditorConfiguration; + @property({type: String}) fileContent?: string; - @query('#wrapper') wrapper?: HTMLElement; + @property({type: String}) fileType?: string; + + @property({type: Object}) prefs?: EditPreferencesInfo; + + @query('#wrapper') + wrapper!: HTMLElement; private initialized = false; static override get styles() { return [ - codemirrorStyles, - dialogStyles, - foldgutterStyles, - searchMatchStyles, - simpleScrollStyles, css` - .CodeMirror { - font-family: var(--monospace-font-family); + .cm-editor { + font-family: 'Roboto Mono', 'SF Mono', 'Lucida Console', Monaco, + monospace; + /* CodeMirror has a default z-index of 4. Set to 0 to avoid collisions with fixed header. */ + z-index: 0; + background: white; } - .CodeMirror-linenumbers { - background-color: var(--background-color-tertiary); - } - .CodeMirror-linenumber { - color: var(--deemphasized-text-color); + .cm-lineNumbers { + background-color: #f1f3f4; } .CodeMirror-ruler { - border-left: 1px solid var(--border-color); + border-left: 1px solid #ddd; } - .cm-trailingspace { - background-color: var(--error-background); - border: 1px solid var(--error-foreground); - border-radius: 2px; - } - .cm-tab:before { - color: var(--deemphasized-text-color); - content: '\\2192'; - position: absolute; + .cm-editor .cm-content { + font-family: 'Roboto Mono', 'SF Mono', 'Lucida Console', Monaco, + monospace; } `, ]; @@ -79,45 +88,66 @@ } private initialize() { - if (!this.params || !this.isConnected || !this.wrapper) return; + if (!this.isConnected || !this.wrapper) return; if (this.initialized) return; this.initialized = true; - // eslint-disable-next-line new-cap - const cm = CodeMirror(this.wrapper, this.params); - setTimeout(() => { - const offsetTop = this.getBoundingClientRect().top; - const clientHeight = window.innerHeight ?? document.body.clientHeight; - // We are setting a fixed height, because for large files we want to - // benefit from CodeMirror's virtual scrolling. - // 80px is roughly the size of the bottom margins plus the footer height. - // This ensures the height of the textarea doesn't push out of screen. - const height = clientHeight - offsetTop - 80; - cm.refresh(); - cm.focus(); - cm.setSize(null, height < 600 ? 600 : height); - if (this.lineNum) { - // We have to take away one from the line number, - // because CodeMirror's line count is zero-based. - cm.setCursor(this.lineNum - 1); - } - }, 1); - cm.on('change', e => { - this.dispatchEvent( - new CustomEvent('content-change', { - detail: {value: e.getValue()}, - bubbles: true, - composed: true, - }) - ); + const offsetTop = this.getBoundingClientRect().top; + const clientHeight = window.innerHeight ?? document.body.clientHeight; + // We are setting a fixed height, because for large files we want to + // benefit from CodeMirror's virtual scrolling. + // 80px is roughly the size of the bottom margins plus the footer height. + // This ensures the height of the textarea doesn't push out of screen. + const height = clientHeight - offsetTop - 80; + + const editor = new EditorView({ + state: EditorState.create({ + doc: this.fileContent ?? '', + extensions: [ + ...extensions( + height, + this.prefs, + this.fileType, + this.fileContent ?? '' + ), + EditorView.updateListener.of(update => { + if (this.prefs?.line_length) { + // This is required to be in the setTimeout() to ensure the + // line is set as correctly as possible. + updateRulerWidth(this.prefs.line_length, update.view.defaultCharacterWidth, true); + } + + if (update.docChanged) { + this.dispatchEvent( + new CustomEvent('content-change', { + detail: {value: update.state.doc.toString()}, + bubbles: true, + composed: true, + }) + ); + } + }), + EditorView.domEventHandlers({ + keydown(e: KeyboardEvent) { + // Exempt the ctrl/command+s key from preventing events from propagating + // through the app. This is because we use it to save changes. + if (!e.metaKey && !e.ctrlKey) { + e.stopPropagation(); + } + }, + }) + ], + }), + parent: this.wrapper as Element, }); - cm.getInputField().addEventListener('keydown', e => { - // Exempt the ctrl/command+s key from preventing events from propagating - // through the app. This is because we use it to save changes. - if (!e.metaKey && !e.ctrlKey) { - e.stopPropagation(); - } - }); + + editor.focus(); + + if (this.lineNum) { + // We have to take away one from the line number, + // ... because CodeMirror's line count is zero-based. + editor.dispatch({selection: {anchor: this.lineNum - 1}}); + } } }
diff --git a/web/element/codemirror-imports.ts b/web/element/codemirror-imports.ts deleted file mode 100644 index a79193e..0000000 --- a/web/element/codemirror-imports.ts +++ /dev/null
@@ -1,71 +0,0 @@ -/** - * @license - * Copyright 2022 Google LLC - * SPDX-License-Identifier: Apache-2.0 - */ -import 'codemirror'; -import 'codemirror/addon/dialog/dialog'; -import 'codemirror/addon/display/rulers'; -import 'codemirror/addon/edit/closebrackets'; -import 'codemirror/addon/edit/closetag'; -import 'codemirror/addon/edit/matchbrackets'; -import 'codemirror/addon/edit/matchtags'; -import 'codemirror/addon/edit/trailingspace'; -import 'codemirror/addon/scroll/annotatescrollbar'; -import 'codemirror/addon/scroll/simplescrollbars'; -import 'codemirror/addon/search/matchesonscrollbar'; -import 'codemirror/addon/search/jump-to-line'; -import 'codemirror/addon/search/matchesonscrollbar'; -import 'codemirror/addon/search/searchcursor'; -import 'codemirror/addon/search/search'; -import 'codemirror/addon/mode/simple'; -import 'codemirror/addon/mode/multiplex'; -import 'codemirror/mode/meta'; -import 'codemirror/mode/clike/clike'; -import 'codemirror/mode/clojure/clojure'; -import 'codemirror/mode/coffeescript/coffeescript'; -import 'codemirror/mode/commonlisp/commonlisp'; -import 'codemirror/mode/css/css'; -import 'codemirror/mode/d/d'; -import 'codemirror/mode/dart/dart'; -import 'codemirror/mode/diff/diff'; -import 'codemirror/mode/django/django'; -import 'codemirror/mode/dockerfile/dockerfile'; -import 'codemirror/mode/erlang/erlang'; -import 'codemirror/mode/go/go'; -import 'codemirror/mode/groovy/groovy'; -import 'codemirror/mode/haml/haml'; -import 'codemirror/mode/handlebars/handlebars'; -import 'codemirror/mode/haskell/haskell'; -import 'codemirror/mode/htmlembedded/htmlembedded'; -import 'codemirror/mode/htmlmixed/htmlmixed'; -import 'codemirror/mode/javascript/javascript'; -import 'codemirror/mode/jinja2/jinja2'; -import 'codemirror/mode/jsx/jsx'; -import 'codemirror/mode/julia/julia'; -import 'codemirror/mode/lua/lua'; -import 'codemirror/mode/markdown/markdown'; -import 'codemirror/mode/mllike/mllike'; -import 'codemirror/mode/nginx/nginx'; -import 'codemirror/mode/perl/perl'; -import 'codemirror/mode/php/php'; -import 'codemirror/mode/powershell/powershell'; -import 'codemirror/mode/properties/properties'; -import 'codemirror/mode/protobuf/protobuf'; -import 'codemirror/mode/puppet/puppet'; -import 'codemirror/mode/python/python'; -import 'codemirror/mode/rpm/rpm'; -import 'codemirror/mode/ruby/ruby'; -import 'codemirror/mode/sass/sass'; -import 'codemirror/mode/scheme/scheme'; -import 'codemirror/mode/shell/shell'; -import 'codemirror/mode/soy/soy'; -import 'codemirror/mode/sparql/sparql'; -import 'codemirror/mode/sql/sql'; -import 'codemirror/mode/swift/swift'; -import 'codemirror/mode/tcl/tcl'; -import 'codemirror/mode/velocity/velocity'; -import 'codemirror/mode/verilog/verilog'; -import 'codemirror/mode/vb/vb'; -import 'codemirror/mode/xml/xml'; -import 'codemirror/mode/yaml/yaml';
diff --git a/web/element/dialog-css.ts b/web/element/dialog-css.ts deleted file mode 100644 index 917d0eb..0000000 --- a/web/element/dialog-css.ts +++ /dev/null
@@ -1,46 +0,0 @@ -/** - * @license - * Copyright 2022 Google LLC - * SPDX-License-Identifier: Apache-2.0 - */ -import {css} from 'lit'; - -/** - * This is just a copy of addon/dialog/dialog.css. - * We need to be able to expose this to lit. - */ -export const dialogStyles = css` - .CodeMirror-dialog { - position: absolute; - left: 0; - right: 0; - background: inherit; - z-index: 15; - padding: 0.1em 0.8em; - overflow: hidden; - color: inherit; - } - - .CodeMirror-dialog-top { - border-bottom: 1px solid #eee; - top: 0; - } - - .CodeMirror-dialog-bottom { - border-top: 1px solid #eee; - bottom: 0; - } - - .CodeMirror-dialog input { - border: none; - outline: none; - background: transparent; - width: 20em; - color: inherit; - font-family: monospace; - } - - .CodeMirror-dialog button { - font-size: 70%; - } -`;
diff --git a/web/element/extensions.ts b/web/element/extensions.ts new file mode 100644 index 0000000..41dcca2 --- /dev/null +++ b/web/element/extensions.ts
@@ -0,0 +1,166 @@ +/** + * @license + * Copyright 2023 Google LLC + * SPDX-License-Identifier: Apache-2.0 + */ +import { + EditorView, + keymap, + highlightSpecialChars, + drawSelection, + lineNumbers, + highlightActiveLineGutter, + highlightWhitespace, + highlightTrailingWhitespace, +} from '@codemirror/view'; +import {EditorState, Extension} from '@codemirror/state'; +import { + defaultHighlightStyle, + indentOnInput, + foldGutter, + foldKeymap, + bracketMatching, + syntaxHighlighting, + indentUnit, +} from '@codemirror/language'; +import { + defaultKeymap, + history, + historyKeymap, + indentWithTab, + insertTab +} from '@codemirror/commands'; +import {searchKeymap, highlightSelectionMatches} from '@codemirror/search'; +import {closeBrackets, closeBracketsKeymap} from '@codemirror/autocomplete'; +import {rulerPlugin} from './ruler'; +import {language} from './language'; +import {EditPreferencesInfo} from './codemirror-element'; + +const trailingspace = () => + EditorView.theme({ + '.cm-trailingspace': { + 'background-color': '#fce8e6', + "border": '1px solid #c5221f', + 'border-radius': '2px', + }, + }); + +const tabsOrSpaces = () => + EditorView.theme({ + '.cm-tab:before': { + color: '#5f6368', + content: "'\\2192'", + position: 'absolute', + }, + + // Class is created and used by highlightWhitespace() + '.cm-highlightTab': { + 'background-image': 'none', + 'background-size': 'none', + 'background-position': 'none', + 'background-repeat': 'none', + "display": 'inline-block', + 'text-decoration': 'inherit', + }, + '.cm-highlightTab:before': { + color: '#5f6368', + content: "'\\2192'", + position: 'absolute', + }, + '.cm-highlightSpace:before': { + content: "''", + }, + }); + +const fixedHeightEditor = (height: number) => + EditorView.theme({ + '&': {height: `${height}px`}, + '.cm-scroller': {overflow: 'auto'}, + }); + +export const extensions = ( + height: number, + prefs?: EditPreferencesInfo, + fileType?: string, + fileContent?: string +) => { + // This uses the preference to detect whether + // to use 'tabs' when you use the tab button + // or to use 'spaces' when using the tab button. + const tab = prefs?.indent_with_tabs ? + { + key: 'Tab', + preventDefault: true, + run: insertTab, + } : indentWithTab; + + const codeExtensions: Array<Extension> = [ + lineNumbers(), + highlightActiveLineGutter(), + highlightSpecialChars(), + history(), + foldGutter(), + drawSelection(), + EditorState.allowMultipleSelections.of(true), + indentOnInput(), + highlightSelectionMatches(), + keymap.of([ + ...closeBracketsKeymap, + ...defaultKeymap, + ...searchKeymap, + ...historyKeymap, + ...foldKeymap, + tab + ]), + trailingspace(), + tabsOrSpaces(), + fixedHeightEditor(height), + ]; + + if (!prefs) return codeExtensions; + + if (prefs.line_length && prefs.line_length > 0) { + codeExtensions.push(rulerPlugin); + } + + if (prefs.auto_close_brackets) { + codeExtensions.push(closeBrackets()); + } + + if (prefs.indent_unit && prefs.indent_unit >= 0) { + codeExtensions.push(indentUnit.of(' '.repeat(prefs.indent_unit))); + } + + if (prefs.line_wrapping) { + codeExtensions.push(EditorView.lineWrapping); + } + + if (prefs.match_brackets) { + codeExtensions.push(bracketMatching()); + } + + if (prefs.syntax_highlighting && language(fileType)) { + codeExtensions.push( + language(fileType) as Extension, + syntaxHighlighting(defaultHighlightStyle, {fallback: true}) + ); + } + + if (prefs.show_tabs) { + codeExtensions.push(highlightWhitespace()); + } + + if (prefs.show_whitespace_errors) { + codeExtensions.push(highlightTrailingWhitespace()); + } + + if (prefs.tab_size && prefs.tab_size >= 0) { + codeExtensions.push(EditorState.tabSize.of(prefs.tab_size)); + } + + if (fileContent?.includes('\r\n')) { + codeExtensions.push(EditorState.lineSeparator.of('\r\n')); + } + + return codeExtensions; +};
diff --git a/web/element/foldgutter-css.ts b/web/element/foldgutter-css.ts deleted file mode 100644 index d9c59c8..0000000 --- a/web/element/foldgutter-css.ts +++ /dev/null
@@ -1,34 +0,0 @@ -/** - * @license - * Copyright 2022 Google LLC - * SPDX-License-Identifier: Apache-2.0 - */ -import {css} from 'lit'; - -/** - * This is just a copy of addon/fold/foldgutter.css. - * We need to be able to expose this to lit. - */ -export const foldgutterStyles = css` - .CodeMirror-foldmarker { - color: blue; - text-shadow: #b9f 1px 1px 2px, #b9f -1px -1px 2px, #b9f 1px -1px 2px, - #b9f -1px 1px 2px; - font-family: arial; - line-height: 0.3; - cursor: pointer; - } - .CodeMirror-foldgutter { - width: 0.7em; - } - .CodeMirror-foldgutter-open, - .CodeMirror-foldgutter-folded { - cursor: pointer; - } - .CodeMirror-foldgutter-open:after { - content: '\\25BE'; - } - .CodeMirror-foldgutter-folded:after { - content: '\\25B8'; - } -`;
diff --git a/web/element/language.ts b/web/element/language.ts new file mode 100644 index 0000000..50c1f03 --- /dev/null +++ b/web/element/language.ts
@@ -0,0 +1,378 @@ +/** + * @license + * Copyright 2023 Google LLC + * SPDX-License-Identifier: Apache-2.0 + */ + +import {StreamLanguage} from '@codemirror/language'; + +// Languages +import {asciiArmor} from '@codemirror/legacy-modes/mode/asciiarmor'; +import {apl} from '@codemirror/legacy-modes/mode/apl'; +import {asn1} from '@codemirror/legacy-modes/mode/asn1'; +import {asterisk} from '@codemirror/legacy-modes/mode/asterisk'; +import {brainfuck} from '@codemirror/legacy-modes/mode/brainfuck'; +import {c} from '@codemirror/legacy-modes/mode/clike'; +import {csharp} from '@codemirror/legacy-modes/mode/clike'; +import {clojure} from '@codemirror/legacy-modes/mode/clojure'; +import {cmake} from '@codemirror/legacy-modes/mode/cmake'; +import {cobol} from '@codemirror/legacy-modes/mode/cobol'; +import {coffeeScript} from '@codemirror/legacy-modes/mode/coffeescript'; +import {commonLisp} from '@codemirror/legacy-modes/mode/commonlisp'; +import {crystal} from '@codemirror/legacy-modes/mode/crystal'; +import {less, sCSS, gss} from '@codemirror/legacy-modes/mode/css'; +import {cypher} from '@codemirror/legacy-modes/mode/cypher'; +import {d} from '@codemirror/legacy-modes/mode/d'; +import {dart} from '@codemirror/legacy-modes/mode/clike'; +import {diff} from '@codemirror/legacy-modes/mode/diff'; +import {dockerFile} from '@codemirror/legacy-modes/mode/dockerfile'; +import {dtd} from '@codemirror/legacy-modes/mode/dtd'; +import {dylan} from '@codemirror/legacy-modes/mode/dylan'; +import {ebnf} from '@codemirror/legacy-modes/mode/ebnf'; +import {ecl} from '@codemirror/legacy-modes/mode/ecl'; +import {eiffel} from '@codemirror/legacy-modes/mode/eiffel'; +import {elm} from '@codemirror/legacy-modes/mode/elm'; +import {erlang} from '@codemirror/legacy-modes/mode/erlang'; +import {factor} from '@codemirror/legacy-modes/mode/factor'; +import {fcl} from '@codemirror/legacy-modes/mode/fcl'; +import {forth} from '@codemirror/legacy-modes/mode/forth'; +import {fortran} from '@codemirror/legacy-modes/mode/fortran'; +import {gas} from '@codemirror/legacy-modes/mode/gas'; +import {go} from '@codemirror/legacy-modes/mode/go'; +import {gherkin} from '@codemirror/legacy-modes/mode/gherkin'; +import {groovy} from '@codemirror/legacy-modes/mode/groovy'; +import {fSharp, oCaml} from '@codemirror/legacy-modes/mode/mllike'; +import {haskell} from '@codemirror/legacy-modes/mode/haskell'; +import {haxe, hxml} from '@codemirror/legacy-modes/mode/haxe'; +import {http} from '@codemirror/legacy-modes/mode/http'; +import {idl} from '@codemirror/legacy-modes/mode/idl'; +import {jinja2} from '@codemirror/legacy-modes/mode/jinja2'; +import {jsonld} from '@codemirror/legacy-modes/mode/javascript'; +import {julia} from '@codemirror/legacy-modes/mode/julia'; +import {kotlin} from '@codemirror/legacy-modes/mode/clike'; +import {liveScript} from '@codemirror/legacy-modes/mode/livescript'; +import {lua} from '@codemirror/legacy-modes/mode/lua'; +import {mathematica} from '@codemirror/legacy-modes/mode/mathematica'; +import {mbox} from '@codemirror/legacy-modes/mode/mbox'; +import {mirc} from '@codemirror/legacy-modes/mode/mirc'; +import {modelica} from '@codemirror/legacy-modes/mode/modelica'; +import {mscgen, msgenny} from '@codemirror/legacy-modes/mode/mscgen'; +import {mumps} from '@codemirror/legacy-modes/mode/mumps'; +import {nginx} from '@codemirror/legacy-modes/mode/nginx'; +import {nsis} from '@codemirror/legacy-modes/mode/nsis'; +import {ntriples} from '@codemirror/legacy-modes/mode/ntriples'; +import {objectiveC} from '@codemirror/legacy-modes/mode/clike'; +import {oz} from '@codemirror/legacy-modes/mode/oz'; +import {pascal} from '@codemirror/legacy-modes/mode/pascal'; +import {perl} from '@codemirror/legacy-modes/mode/perl'; +import {pig} from '@codemirror/legacy-modes/mode/pig'; +import {powerShell} from '@codemirror/legacy-modes/mode/powershell'; +import {properties} from '@codemirror/legacy-modes/mode/properties'; +import {protobuf} from '@codemirror/legacy-modes/mode/protobuf'; +import {puppet} from '@codemirror/legacy-modes/mode/puppet'; +import {q} from '@codemirror/legacy-modes/mode/q'; +import {rpmChanges, rpmSpec} from '@codemirror/legacy-modes/mode/rpm'; +import {ruby} from '@codemirror/legacy-modes/mode/ruby'; +import {sas} from '@codemirror/legacy-modes/mode/sas'; +import {sass} from '@codemirror/legacy-modes/mode/sass'; +import {scala} from '@codemirror/legacy-modes/mode/clike'; +import {scheme} from '@codemirror/legacy-modes/mode/scheme'; +import {shader} from '@codemirror/legacy-modes/mode/clike'; +import {shell} from '@codemirror/legacy-modes/mode/shell'; +import {sieve} from '@codemirror/legacy-modes/mode/sieve'; +import {sparql} from '@codemirror/legacy-modes/mode/sparql'; +import {spreadsheet} from '@codemirror/legacy-modes/mode/spreadsheet'; +import {solr} from '@codemirror/legacy-modes/mode/solr'; +import {pgSQL, plSQL, cassandra} from '@codemirror/legacy-modes/mode/sql'; +import {squirrel} from '@codemirror/legacy-modes/mode/clike'; +import {stex} from '@codemirror/legacy-modes/mode/stex'; +import {swift} from '@codemirror/legacy-modes/mode/swift'; +import {tcl} from '@codemirror/legacy-modes/mode/tcl'; +import {textile} from '@codemirror/legacy-modes/mode/textile'; +import {tiddlyWiki} from '@codemirror/legacy-modes/mode/tiddlywiki'; +import {tiki} from '@codemirror/legacy-modes/mode/tiki'; +import {toml} from '@codemirror/legacy-modes/mode/toml'; +import {troff} from '@codemirror/legacy-modes/mode/troff'; +import {ttcn} from '@codemirror/legacy-modes/mode/ttcn'; +import {ttcnCfg} from '@codemirror/legacy-modes/mode/ttcn-cfg'; +import {turtle} from '@codemirror/legacy-modes/mode/turtle'; +import {vb} from '@codemirror/legacy-modes/mode/vb'; +import {vbScript} from '@codemirror/legacy-modes/mode/vbscript'; +import {velocity} from '@codemirror/legacy-modes/mode/velocity'; +import {verilog} from '@codemirror/legacy-modes/mode/verilog'; +import {vhdl} from '@codemirror/legacy-modes/mode/vhdl'; +import {webIDL} from '@codemirror/legacy-modes/mode/webidl'; +import {xQuery} from '@codemirror/legacy-modes/mode/xquery'; +import {yacas} from '@codemirror/legacy-modes/mode/yacas'; +import {yaml} from '@codemirror/legacy-modes/mode/yaml'; +import {z80} from '@codemirror/legacy-modes/mode/z80'; + +import {cpp} from '@codemirror/lang-cpp'; +import {css as _css} from '@codemirror/lang-css'; +import {java} from '@codemirror/lang-java'; +import {html as _html} from '@codemirror/lang-html'; +import {javascript} from '@codemirror/lang-javascript'; +import {json} from '@codemirror/lang-json'; +import {markdown} from '@codemirror/lang-markdown'; +import {php} from '@codemirror/lang-php'; +import {python} from '@codemirror/lang-python'; +import {rust} from '@codemirror/lang-rust'; +import {sql} from '@codemirror/lang-sql'; +import {xml} from '@codemirror/lang-xml'; + +export const language = (fileType?: string) => { + switch (fileType) { + case 'text/apl': + return StreamLanguage.define(apl); + case 'text/x-ttcn-asn': + return StreamLanguage.define(asn1({})); + case 'text/x-asterisk': + return StreamLanguage.define(asterisk); + case 'text/x-brainfuck': + return StreamLanguage.define(brainfuck); + case 'text/x-ebnf': + return StreamLanguage.define(ebnf); + case 'text/x-python': + return python(); + case 'text/x-csrc': + return StreamLanguage.define(c); + case 'text/x-csharp': + return StreamLanguage.define(csharp); + case 'text/x-c++src': + return cpp(); + case 'application/dart': + return StreamLanguage.define(dart); + case 'text/x-kotlin': + return StreamLanguage.define(kotlin); + case 'text/x-objectivec': + return StreamLanguage.define(objectiveC); + case 'x-shader/x-fragment': + case 'x-shader/x-vertex': + return StreamLanguage.define(shader); + case 'text/x-ttcn-cfg': + return StreamLanguage.define(ttcnCfg); + case 'text/x-common-lisp': + return StreamLanguage.define(commonLisp); + case 'text/x-clojure': + case 'text/x-clojurescript': + return StreamLanguage.define(clojure); + case 'text/x-cmake': + return StreamLanguage.define(cmake); + case 'application/json': + return json(); + case 'text/x-cobol': + return StreamLanguage.define(cobol); + case 'text/x-coffeescript': + return StreamLanguage.define(coffeeScript); + case 'text/x-ini': + case 'text/x-properties': + return StreamLanguage.define(properties); + case 'text/x-crystal': + return StreamLanguage.define(crystal); + case 'application/xml': + return xml; + case 'text/css': + return _css(); + case 'text/x-less': + return StreamLanguage.define(less); + case 'text/x-scss': + return StreamLanguage.define(sCSS); + case 'text/x-gss': + return StreamLanguage.define(gss); + case 'text/x-cassandra': + return StreamLanguage.define(cassandra); + case 'text/x-pgsql': + return StreamLanguage.define(pgSQL); + case 'text/x-plsql': + return StreamLanguage.define(plSQL); + case 'application/x-cypher-query': + return StreamLanguage.define(cypher); + case 'text/x-d': + return StreamLanguage.define(d); + case 'text/x-diff': + return StreamLanguage.define(diff); + case 'application/xml-dtd': + return StreamLanguage.define(dtd); + case 'text/x-dylan': + return StreamLanguage.define(dylan); + case 'text/x-dockerfile': + return StreamLanguage.define(dockerFile); + case 'text/x-eiffel': + return StreamLanguage.define(eiffel); + case 'text/x-ecl': + return StreamLanguage.define(ecl); + case 'text/x-elm': + return StreamLanguage.define(elm); + case 'application/x-ejs': + case 'text/html': + case 'application/x-jsp': + return _html(); + case 'application/x-erb': + case 'text/x-ruby': + return StreamLanguage.define(ruby); + case 'text/x-erlang': + return StreamLanguage.define(erlang); + case 'text/jsx': + return javascript({jsx: true}); + case 'text/x-spreadsheet': + return StreamLanguage.define(spreadsheet); + case 'text/x-fortran': + return StreamLanguage.define(fortran); + case 'text/x-factor': + return StreamLanguage.define(factor); + case 'text/x-feature': + case 'text/x-gherkin': + return StreamLanguage.define(gherkin); + case 'text/x-fcl': + return StreamLanguage.define(fcl); + case 'text/x-forth': + return StreamLanguage.define(forth); + case 'text/x-fsharp': + return StreamLanguage.define(fSharp); + case 'text/x-go': + return StreamLanguage.define(go); + case 'text/x-groovy': + return StreamLanguage.define(groovy); + case 'text/x-haskell': + case 'text/x-literate-haskell': + return StreamLanguage.define(haskell); + case 'message/http': + return StreamLanguage.define(http); + case 'text/x-haxe': + return StreamLanguage.define(haxe); + case 'text/x-hxml': + return StreamLanguage.define(hxml); + case 'text/x-jinja2': + return StreamLanguage.define(jinja2); + case 'text/x-java': + return java(); + case 'text/x-julia': + return StreamLanguage.define(julia); + case 'application/ld+json': + return StreamLanguage.define(jsonld); + case 'text/x-livescript': + return StreamLanguage.define(liveScript); + case 'text/x-lua': + return StreamLanguage.define(lua); + case 'text/x-markdown': + return markdown(); + case 'application/mbox': + return StreamLanguage.define(mbox); + case 'text/mirc': + return StreamLanguage.define(mirc); + case 'text/x-ocaml': + return StreamLanguage.define(oCaml); + case 'text/x-modelica': + return StreamLanguage.define(modelica); + case 'text/x-mumps': + return StreamLanguage.define(mumps); + case 'text/x-mscgen': + return StreamLanguage.define(mscgen); + case 'text/x-msgenny': + return StreamLanguage.define(msgenny); + case 'text/x-mathematica': + return StreamLanguage.define(mathematica); + case 'text/x-nginx-conf': + return StreamLanguage.define(nginx); + case 'text/x-nsis': + return StreamLanguage.define(nsis); + case 'text/n-triples': + return StreamLanguage.define(ntriples); + case 'text/x-squirrel': + return StreamLanguage.define(squirrel); + case 'text/x-oz': + return StreamLanguage.define(oz); + case 'text/x-pascal': + return StreamLanguage.define(pascal); + case 'application/pgp': + return StreamLanguage.define(asciiArmor); + case 'text/x-pig': + return StreamLanguage.define(pig); + case 'text/x-perl': + return StreamLanguage.define(perl); + case 'text/x-puppet': + return StreamLanguage.define(puppet); + case 'text/x-idl': + return StreamLanguage.define(idl); + case 'text/x-protobuf': + return StreamLanguage.define(protobuf); + case 'application/x-powershell': + return StreamLanguage.define(powerShell); + case 'text/x-q': + return StreamLanguage.define(q); + case 'text/x-rpm-spec': + return StreamLanguage.define(rpmSpec); + case 'text/x-rpm-changes': + return StreamLanguage.define(rpmChanges); + case 'application/sparql-query': + return StreamLanguage.define(sparql); + case 'text/x-rustsrc': + return rust(); + case 'text/x-gas': + return StreamLanguage.define(gas); + case 'text/x-sas': + return StreamLanguage.define(sas); + case 'text/x-sass': + return StreamLanguage.define(sass); + case 'text/x-scala': + return StreamLanguage.define(scala); + case 'text/x-scheme': + return StreamLanguage.define(scheme); + case 'application/sieve': + return StreamLanguage.define(sieve); + case 'text/x-solr': + return StreamLanguage.define(solr); + case 'text/x-stex': + case 'text/x-latex': + return StreamLanguage.define(stex); + case 'text/x-systemverilog': + return StreamLanguage.define(verilog); + case 'text/x-swift': + return StreamLanguage.define(swift); + case 'text/x-tcl': + return StreamLanguage.define(tcl); + case 'text/x-textile': + return StreamLanguage.define(textile); + case 'text/x-tiddlywiki': + return StreamLanguage.define(tiddlyWiki); + case 'text/tiki': + return StreamLanguage.define(tiki); + case 'text/x-toml': + return StreamLanguage.define(toml); + case 'text/x-toml': + return StreamLanguage.define(toml); + case 'application/typescript': + return javascript({typescript: true}); + case 'text/x-ttcn': + return StreamLanguage.define(ttcn); + case 'text/turtle': + return StreamLanguage.define(turtle); + case 'text/x-vb': + return StreamLanguage.define(vb); + case 'text/vbscript': + return StreamLanguage.define(vbScript); + case 'text/x-vhdl': + return StreamLanguage.define(vhdl); + case 'text/velocity': + return StreamLanguage.define(velocity); + case 'text/x-webidl': + return StreamLanguage.define(webIDL); + case 'application/xquery': + return StreamLanguage.define(xQuery); + case 'text/x-yaml': + return StreamLanguage.define(yaml); + case 'text/x-yacas': + return StreamLanguage.define(yacas); + case 'text/x-z80': + return StreamLanguage.define(z80); + case 'text/troff': + return StreamLanguage.define(troff); + case 'text/x-sh': + return StreamLanguage.define(shell); + case 'text/x-php': + return php(); + case 'text/x-sql': + return sql(); + } + return []; +};
diff --git a/web/element/matchesonscrollbar-css.ts b/web/element/matchesonscrollbar-css.ts deleted file mode 100644 index 32a274c..0000000 --- a/web/element/matchesonscrollbar-css.ts +++ /dev/null
@@ -1,21 +0,0 @@ -/** - * @license - * Copyright 2022 Google LLC - * SPDX-License-Identifier: Apache-2.0 - */ -import {css} from 'lit'; - -/** - * This is just a copy of addon/search/matchesonscrollbar.css. - * We need to be able to expose this to lit. - */ -export const searchMatchStyles = css` - .CodeMirror-search-match { - background: gold; - border-top: 1px solid orange; - border-bottom: 1px solid orange; - -moz-box-sizing: border-box; - box-sizing: border-box; - opacity: 0.5; - } -`;
diff --git a/web/element/ruler.ts b/web/element/ruler.ts new file mode 100644 index 0000000..5f37d72 --- /dev/null +++ b/web/element/ruler.ts
@@ -0,0 +1,60 @@ +/** + * @license + * Copyright 2023 Google LLC + * SPDX-License-Identifier: Apache-2.0 + */ +import {EditorView, ViewPlugin} from '@codemirror/view'; + +// Kindly provided by https://discuss.codemirror.net/t/how-to-implement-ruler/4616/4 +function generateRulerPlugin() { + let width = 0; + let rulerElement: HTMLDivElement; + + class RulerPlugin { + containerElement: HTMLDivElement; + + constructor(view: EditorView) { + this.containerElement = view.dom.appendChild( + document.createElement('div') + ); + this.containerElement.style.cssText = ` + position: absolute; + left: 0; + top: 0; + width: 100%; + height: 100%; + pointer-events: none; + overflow: hidden; + `; + rulerElement = this.containerElement.appendChild( + document.createElement('div') + ); + rulerElement.style.cssText = ` + position: absolute; + border-right: 1px solid #dadce0; + height: 100%; + opacity: 0.7; + `; + // TODO: This should be equal to the amount of padding on a line. + // This value should be extracted from CodeMirror rather than hardcoded. + rulerElement.style.width = `4px`; + } + + destroy() { + rulerElement.remove(); + } + } + + function updateRulerWidth(newWidth: number, defaultWidth: number, force = false) { + if ((newWidth !== width || force) && rulerElement) { + width = newWidth; + rulerElement.style.left = `${width * defaultWidth}px`; + } + } + + return {rulerPlugin: ViewPlugin.fromClass(RulerPlugin), updateRulerWidth}; +} + +const {rulerPlugin, updateRulerWidth} = generateRulerPlugin(); + +export {rulerPlugin, updateRulerWidth};
diff --git a/web/element/simplescrollbars-css.ts b/web/element/simplescrollbars-css.ts deleted file mode 100644 index 2203cd6..0000000 --- a/web/element/simplescrollbars-css.ts +++ /dev/null
@@ -1,87 +0,0 @@ -/** - * @license - * Copyright 2022 Google LLC - * SPDX-License-Identifier: Apache-2.0 - */ -import {css} from 'lit'; - -/** - * This is just a copy of addon/scroll/simplescrollbars.css. - * We need to be able to expose this to lit. - */ -export const simpleScrollStyles = css` - .CodeMirror-simplescroll-horizontal div, - .CodeMirror-simplescroll-vertical div { - position: absolute; - background: #ccc; - -moz-box-sizing: border-box; - box-sizing: border-box; - border: 1px solid #bbb; - border-radius: 2px; - } - - .CodeMirror-simplescroll-horizontal, - .CodeMirror-simplescroll-vertical { - position: absolute; - z-index: 6; - background: #eee; - } - - .CodeMirror-simplescroll-horizontal { - bottom: 0; - left: 0; - height: 8px; - } - .CodeMirror-simplescroll-horizontal div { - bottom: 0; - height: 100%; - } - - .CodeMirror-simplescroll-vertical { - right: 0; - top: 0; - width: 8px; - } - .CodeMirror-simplescroll-vertical div { - right: 0; - width: 100%; - } - - .CodeMirror-overlayscroll .CodeMirror-scrollbar-filler, - .CodeMirror-overlayscroll .CodeMirror-gutter-filler { - display: none; - } - - .CodeMirror-overlayscroll-horizontal div, - .CodeMirror-overlayscroll-vertical div { - position: absolute; - background: #bcd; - border-radius: 3px; - } - - .CodeMirror-overlayscroll-horizontal, - .CodeMirror-overlayscroll-vertical { - position: absolute; - z-index: 6; - } - - .CodeMirror-overlayscroll-horizontal { - bottom: 0; - left: 0; - height: 6px; - } - .CodeMirror-overlayscroll-horizontal div { - bottom: 0; - height: 100%; - } - - .CodeMirror-overlayscroll-vertical { - right: 0; - top: 0; - width: 6px; - } - .CodeMirror-overlayscroll-vertical div { - right: 0; - width: 100%; - } -`;
diff --git a/web/gr-editor.ts b/web/gr-editor.ts index 04a0425..6d04661 100644 --- a/web/gr-editor.ts +++ b/web/gr-editor.ts
@@ -3,13 +3,12 @@ * Copyright 2022 Google LLC * SPDX-License-Identifier: Apache-2.0 */ -import './codemirror-imports'; + import '@gerritcodereview/typescript-api/gerrit'; import {PluginApi} from '@gerritcodereview/typescript-api/plugin'; import {html, LitElement} from 'lit'; import {customElement, property, query} from 'lit/decorators.js'; -import {CodeMirrorConfig} from './codemirror-imports'; import {setScriptSrc} from './safe-script'; declare global { @@ -19,25 +18,6 @@ } /** - * This is a standard REST API object: - * https://gerrit-review.googlesource.com/Documentation/rest-api-accounts.html#edit-preferences-info - * - * TODO: Add this object to the plugin API. - */ -interface EditPreferencesInfo { - tab_size?: number; - line_length?: number; - indent_unit?: number; - show_tabs?: boolean; - show_whitespace_errors?: boolean; - syntax_highlighting?: boolean; - match_brackets?: boolean; - line_wrapping?: boolean; - indent_with_tabs?: boolean; - auto_close_brackets?: boolean; -} - -/** * This component just loads the CodeMirror js bundle lazily and converts the * Gerrit preferences into CodeMirror params. */ @@ -49,7 +29,7 @@ @property({type: Number}) lineNum?: number; - @property({type: Object}) prefs?: EditPreferencesInfo; + @property({type: Object}) prefs?: unknown; @property({type: Object}) plugin?: PluginApi; @@ -62,7 +42,9 @@ <codemirror-element id="codemirror" .lineNum=${this.lineNum} - .params=${this.codeMirrorParams()} + .prefs=${this.prefs} + .fileContent=${this.fileContent} + .fileType=${this.fileType} > </codemirror-element> `; @@ -85,32 +67,4 @@ setScriptSrc(script, this.plugin?.url() ?? ''); document.head.appendChild(script); } - - private codeMirrorParams(): CodeMirrorConfig { - const params: CodeMirrorConfig = { - value: this.fileContent ?? '', - }; - - if (this.prefs) { - params.autoCloseBrackets = this.prefs.auto_close_brackets; - params.cursorHeight = 0.85; - params.indentUnit = this.prefs.indent_unit; - params.indentWithTabs = this.prefs.indent_with_tabs; - params.lineNumbers = true; - params.lineWrapping = this.prefs.line_wrapping; - params.matchBrackets = this.prefs.match_brackets; - params.mode = this.prefs.syntax_highlighting ? this.fileType ?? '' : ''; - params.showTabs = this.prefs.show_tabs; - params.showTrailingSpace = this.prefs.show_whitespace_errors; - params.tabSize = this.prefs.tab_size; - if (this.prefs.line_length) { - params.rulers = [{column: this.prefs.line_length}]; - } - if (this.fileContent?.includes('\r\n')) { - params.lineSeparator = '\r\n'; - } - } - - return params; - } }