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