How I solved the 'Home' and 'End' problem

I was frustrated by the fact that the Home and End keyboard buttons would take me to the beginning and end of the current paragraph rather than the current line. After several attempts, I was able to make the following script do what I wanted. I used ScriptCat in chrome.

Perhaps this will help someone else. Or perhaps I will learn there was an easier way to do this. Cheers.

// ==UserScript==
// @name         SilverBullet: Visual-line Home/End
// @namespace    local.silverbullet.visualline
// @version      1.6
// @description  Make Home/End (and Shift variants) move to the start/end of the visual (wrapped) line instead of the logical paragraph in SilverBullet's CodeMirror editor.
// @match        https://YOUR-SILVERBULLET-HOST/*
// @run-at       document-idle
// @grant        none
// ==/UserScript==

// ----------------------------------------------------------------------------
// WHAT THIS DOES
//   SilverBullet binds Home/End to logical-line (paragraph) boundaries. This
//   rebinds them to VISUAL-line boundaries (respecting soft wraps) by driving
//   CodeMirror's own wrap-aware command (view.moveToLineBoundary) and
//   dispatching the resulting selection as a normal CM transaction.
//
// HOW IT REACHES THE EDITOR (the fragile bit)
//   This SilverBullet build does NOT expose CodeMirror's standard `cmView`
//   property. Instead the EditorView is found at:
//       document.querySelector(".cm-content").cmTile.view
//   getView() reads `cmTile.view` from the .cm-content that contains the caret.
//   `cmTile` is a SilverBullet internal, so a future SB update could rename it.
//   If that happens the script fails SAFE (Home/End just revert to SB's default
//   paragraph behaviour) — it won't break the editor.
//
// RECOVERY: if Home/End stop working after a SilverBullet upgrade, click into
//   the editor, open DevTools console, and run this to find the new handle:
//
//     (() => {
//       const isView = v => v && v.state &&
//         typeof v.moveToLineBoundary === "function" &&
//         typeof v.dispatch === "function";
//       const seen = new WeakSet(), out = [];
//       const stack = [[window.client, "client"]];
//       const cont = document.querySelector(".cm-content");
//       if (cont && cont.cmTile) stack.push([cont.cmTile, "cmContent.cmTile"]);
//       while (stack.length) {
//         const [obj, path] = stack.pop();
//         if (!obj || typeof obj !== "object" || seen.has(obj) ||
//             path.split(".").length > 6) continue;
//         seen.add(obj);
//         let keys; try { keys = Object.keys(obj); } catch (e) { continue; }
//         for (const k of keys) {
//           let v; try { v = obj[k]; } catch (e) { continue; }
//           if (isView(v)) out.push(path + "." + k);
//           else if (v && typeof v === "object") stack.push([v, path + "." + k]);
//         }
//       }
//       return out.length ? "EditorView at: " + out.join("  |  ")
//                         : "no EditorView found under client/cmTile";
//     })()
//
//   Then update getView() below to read the view from the reported location.
// ----------------------------------------------------------------------------

(function () {
  "use strict";

  function isView(v) {
    return v && v.state && v.dom &&
      typeof v.dispatch === "function" &&
      typeof v.moveToLineBoundary === "function";
  }

  // The EditorView lives on the caret's .cm-content element as `cmTile.view`.
  function getView(node) {
    if (node && node.nodeType === 3) node = node.parentElement;
    let el = node && node.closest ? node.closest(".cm-content") : null;
    while (el) {
      const v = el.cmTile && el.cmTile.view;
      if (isView(v)) return v;
      el = el.parentElement ? el.parentElement.closest(".cm-content") : null;
    }
    return null;
  }

  document.addEventListener(
    "keydown",
    function (e) {
      if (e.key !== "End" && e.key !== "Home") return;
      if (e.ctrlKey || e.altKey || e.metaKey) return; // leave Ctrl/Cmd/Alt combos

      const sel = window.getSelection();
      const focus = sel && sel.rangeCount ? sel.focusNode : null;
      const view = getView(focus);
      if (!view) return; // not in the editor (or handle moved) -> default

      e.preventDefault();
      e.stopImmediatePropagation();

      const forward = e.key === "End";
      const extend = e.shiftKey;
      const cmSel = view.state.selection;
      const ES = cmSel.constructor; // EditorSelection

      // Keep the range moveToLineBoundary returns intact (it carries the
      // wrap-side association); rebuilding a bare cursor from .head loses it
      // and lands End at the start of the next row.
      const ranges = cmSel.ranges.map(function (range) {
        const b = view.moveToLineBoundary(range, forward, true);
        return extend ? ES.range(range.anchor, b.head, b.goalColumn, b.bidiLevel) : b;
      });

      view.dispatch({
        selection: ES.create(ranges, cmSel.mainIndex),
        scrollIntoView: true,
        userEvent: "select",
      });
    },
    true // capture phase: fire before CodeMirror's own keydown handler
  );
})();