LeetCode Custom Mapping

Apply custom vim mappings to LeetCode's built-in Monaco Vim mode

Du musst eine Erweiterung wie Tampermonkey, Greasemonkey oder Violentmonkey installieren, um dieses Skript zu installieren.

Um dieses Skript zu installieren, müssen Sie eine Erweiterung wie Tampermonkey oder Violentmonkey installieren.

Um dieses Skript zu installieren, müssen Sie eine Erweiterung wie Tampermonkey oder Violentmonkey installieren.

Um dieses Skript zu installieren, müssen Sie eine Erweiterung wie Tampermonkey oder Userscripts installieren.

Um dieses Skript zu installieren, müssen Sie eine Erweiterung wie Tampermonkey installieren.

Sie müssten eine Skript Manager Erweiterung installieren damit sie dieses Skript installieren können

(Ich habe schon ein Skript Manager, Lass mich es installieren!)

Um dieses Design zu installieren, müssen Sie eine Erweiterung wie Stylus installieren.

Um dieses Design zu installieren, müssen Sie eine Erweiterung wie Stylus installieren.

Um dieses Design zu installieren, müssen Sie eine Erweiterung wie Stylus installieren.

Um diesen Stil zu installieren, müssen Sie eine Erweiterung für den Benutzerstil Verwaltung installieren.

Um diesen Stil zu installieren, müssen Sie eine Erweiterung für den Benutzerstil Verwaltung installieren.

Um diesen Stil zu installieren, müssen Sie eine Erweiterung für den Benutzerstil Verwaltung installieren.

(Ich habe bereits einen Benutzerstil Verwaltung, ich möchte ihn installieren!)

// ==UserScript==
// @name         LeetCode Custom Mapping
// @namespace    http://tampermonkey.net/
// @version      0.2
// @description  Apply custom vim mappings to LeetCode's built-in Monaco Vim mode
// @author       thraxis
// @match        https://leetcode.com/problems/*
// @grant        GM_getValue
// @grant        GM_setValue
// @grant        GM_registerMenuCommand
// @license      MIT
// ==/UserScript==
(function () {
  "use strict";

  const defaultMappings = [
    { lhs: "jk", rhs: "<Esc>", mode: "insert" },
    { lhs: "jj", rhs: "<Esc>", mode: "insert" },
    { lhs: "H", rhs: "^", mode: "normal" },
    { lhs: "L", rhs: "$", mode: "normal" },
    { lhs: "J", rhs: "5j", mode: "normal" },
    { lhs: "K", rhs: "5k", mode: "normal" },
  ];

  function normalizeEntry(entry) {
    if (entry && typeof entry === "object" && !Array.isArray(entry)) {
      const { lhs, rhs, mode } = entry;
      if (typeof lhs === "string" && typeof rhs === "string" && typeof mode === "string") {
        return { lhs, rhs, mode };
      }
      return null;
    }
    if (typeof entry === "string") {
      const parts = entry.trim().split(/\s+/);
      if (parts[0] === "map" && parts.length >= 4) {
        return { lhs: parts[1], rhs: parts[2], mode: parts[3] };
      }
      return null;
    }
    return null;
  }

  function getMappings() {
    const stored = GM_getValue("mappings", defaultMappings);
    if (!Array.isArray(stored)) {
      GM_setValue("mappings", defaultMappings);
      return defaultMappings;
    }
    return stored.map(normalizeEntry).filter(Boolean);
  }

  function applyMappings() {
    if (!unsafeWindow.monacoVim?.VimMode?.Vim) return false;
    const V = unsafeWindow.monacoVim.VimMode.Vim;
    getMappings().forEach(({ lhs, rhs, mode }) => {
      try {
        V.map(lhs, rhs, mode);
      } catch (e) {
        console.error("Map failed:", lhs, rhs, mode, e);
      }
    });
    return true;
  }

  function startPolling() {
    let tries = 0;
    const maxTries = 60;
    const interval = setInterval(() => {
      tries++;
      if (applyMappings() || tries >= maxTries) {
        clearInterval(interval);
      }
    }, 1000);
  }

  startPolling();

  let lastUrl = location.href;
  new MutationObserver(() => {
    if (location.href !== lastUrl) {
      lastUrl = location.href;
      startPolling();
    }
  }).observe(document.body, { childList: true, subtree: true });

  function mappingsToText(mappings) {
    return mappings.map((m) => `map ${m.lhs} ${m.rhs} ${m.mode}`).join("\n");
  }

  function textToMappings(text) {
    return text.split("\n").map(normalizeEntry).filter(Boolean);
  }

  GM_registerMenuCommand("Edit Mappings", () => {
    const current = mappingsToText(getMappings());
    const newText = prompt('Enter mappings, one per line (e.g. "map jk <Esc> insert"):', current);
    if (newText !== null) {
      GM_setValue("mappings", textToMappings(newText));
      alert("Saved. Reload the page for changes to take effect.");
    }
  });

  GM_registerMenuCommand("Reset Mappings to Defaults", () => {
    GM_setValue("mappings", defaultMappings);
    alert("Reset to defaults. Reload the page.");
  });
})();