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;
- }
}