Merge changes Ia6823f9d,I549e570f,Ibcac9d5e,I75c48a1c into stable-3.12

* changes:
  Resize CodeMirror dynamically
  Remove outline on textbox
  Display cursor position in the bottom
  Fix going to line
diff --git a/web/element/codemirror-element.ts b/web/element/codemirror-element.ts
index d608598..27c6ef2 100644
--- a/web/element/codemirror-element.ts
+++ b/web/element/codemirror-element.ts
@@ -57,8 +57,13 @@
   @query('#wrapper')
   wrapper!: HTMLElement;
 
+  @query('#result')
+  result!: HTMLElement;
+
   private initialized = false;
 
+  private onResize: (() => void) | null = null;
+
   static override get styles() {
     return [
       css`
@@ -75,12 +80,36 @@
           font-family: 'Roboto Mono', 'SF Mono', 'Lucida Console', Monaco,
             monospace;
         }
+        #statusLine {
+          position: absolute;
+          bottom: 0;
+          left: 0;
+          width: 100%;
+          background-color: #f7f7f7;
+          border-top: 1px solid #ddd;
+          border-right: 1px solid #ddd;
+        }
+        #statusLine div {
+          height: inherit;
+        }
+        .cursorPosition {
+          display: inline-block;
+          margin: 0 5px 0 35px;
+          white-space: nowrap;
+        }
       `,
     ];
   }
 
   override render() {
-    return html`<div id="wrapper"></div>`;
+    return html`
+      <div id="wrapper"></div>
+      <div class="statusLine">
+        <div class="cursorPosition">
+          <span id="result"></span>
+        </div>
+      </div>
+    `;
   }
 
   override updated() {
@@ -93,20 +122,12 @@
     if (this.initialized) return;
     this.initialized = 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.calculateHeight(),
             this.prefs,
             this.fileType,
             this.fileContent ?? '',
@@ -154,6 +175,11 @@
               }
             },
           }),
+          EditorView.updateListener.of(update => {
+            if (update.selectionSet) {
+              this.updateCursorPosition(update.view);
+            }
+          }),
         ],
       }),
       parent: this.wrapper as Element,
@@ -162,9 +188,65 @@
     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}});
+      this.setCursorToLine(editor, this.lineNum);
+    }
+
+    // Makes sure to show line number and column number on initial
+    // load.
+    this.updateCursorPosition(editor);
+
+    this.onResize = () => this.updateEditorHeight(editor);
+    window.addEventListener('resize', this.onResize);
+  }
+
+  override disconnectedCallback() {
+    super.disconnectedCallback();
+    if (this.onResize) {
+      window.removeEventListener('resize', this.onResize);
+      this.onResize = null;
+    }
+  }
+
+  setCursorToLine(view: EditorView, lineNum: number) {
+    const totalLines = view.state.doc.lines;
+    // If you try going to a line that does not exist
+    // codemirror will error out. Instead lets just log.
+    // Line 1 will be selected automatically.
+    if (lineNum < 1 || lineNum > totalLines) {
+      console.warn(`Line number ${lineNum} is out of bounds (valid range: 1 - ${totalLines}).`);
+      return;
+    }
+
+    const line = view.state.doc.line(lineNum);
+    view.dispatch({
+      selection: { anchor: line.from },
+      scrollIntoView: true
+    });
+    view.focus();
+  }
+
+  private updateCursorPosition(view: EditorView) {
+    const cursor = view.state.selection.main.head;
+    const line = view.state.doc.lineAt(cursor);
+    if (this.result) {
+      this.result.textContent = `Line: ${line.number}, Column: ${cursor - line.from + 1}`;
+    }
+  }
+
+  private calculateHeight() {
+    const offsetTop = this.getBoundingClientRect().top;
+    const clientHeight = window.innerHeight ?? document.body.clientHeight;
+    // We take offsetTop twice to ensure the height of the texarea doesn't push
+    // out of screen. We no longer do a hardcore value which was 80 before.
+    // offsetTop seems to be what we've been looking for to do it dynamically.
+    return Math.max(0, clientHeight - offsetTop - offsetTop);
+  }
+
+  private updateEditorHeight(editor: EditorView) {
+    const height = this.calculateHeight();
+    const editorElement = editor.dom;
+    if (editorElement) {
+      editorElement.style.height = `${height}px`;
     }
   }
 }
diff --git a/web/element/extensions.ts b/web/element/extensions.ts
index 64b998c..6c2be75 100644
--- a/web/element/extensions.ts
+++ b/web/element/extensions.ts
@@ -47,6 +47,9 @@
         color: 'var(--deemphasized-text-color)',
         'background-color': 'var(--background-color-secondary)',
       },
+      '&.cm-editor.cm-focused': {
+        outline: 'none'
+      },
     },
     {dark}
   );