diff --git a/docs/src/components/KeymapUpgrader/index.jsx b/docs/src/components/KeymapUpgrader/index.jsx
index 90429d83..fdd4db4b 100644
--- a/docs/src/components/KeymapUpgrader/index.jsx
+++ b/docs/src/components/KeymapUpgrader/index.jsx
@@ -7,7 +7,11 @@
import React from "react";
import { useAsync } from "react-async";
-import { initParser, upgradeKeymap } from "@site/src/keymap-upgrade";
+import {
+ initParser,
+ upgradeKeymap,
+ rangesToLineNumbers,
+} from "@site/src/keymap-upgrade";
import CodeBlock from "@theme/CodeBlock";
import styles from "./styles.module.css";
@@ -28,7 +32,15 @@ export default function KeymapUpgrader() {
function Editor() {
const [keymap, setKeymap] = React.useState("");
- const upgraded = upgradeKeymap(keymap);
+
+ const { text: upgraded, changedRanges } = upgradeKeymap(keymap);
+ const highlights = rangesToLineNumbers(upgraded, changedRanges);
+
+ let title = "Upgraded Keymap";
+
+ if (keymap && upgraded === keymap) {
+ title += " (No Changes)";
+ }
return (
@@ -40,7 +52,7 @@ function Editor() {
onChange={(e) => setKeymap(e.target.value)}
>
-
+
{upgraded}
diff --git a/docs/src/css/custom.css b/docs/src/css/custom.css
index d9cddb85..c2df00e2 100644
--- a/docs/src/css/custom.css
+++ b/docs/src/css/custom.css
@@ -15,10 +15,15 @@
--ifm-color-primary-lighter: #0280e3;
--ifm-color-primary-lightest: #0690fc;
--ifm-code-font-size: 95%;
+
+ --docusaurus-highlighted-code-line-bg: rgb(0 0 0 / 8%);
+}
+
+[data-theme="dark"] {
+ --docusaurus-highlighted-code-line-bg: rgb(255 255 255 / 8%);
}
.docusaurus-highlight-code-line {
- background-color: rgb(72, 77, 91);
display: block;
margin: 0 calc(-1 * var(--ifm-pre-padding));
padding: 0 var(--ifm-pre-padding);
diff --git a/docs/src/keymap-upgrade/index.ts b/docs/src/keymap-upgrade/index.ts
index 4d091e23..3df9bb9d 100644
--- a/docs/src/keymap-upgrade/index.ts
+++ b/docs/src/keymap-upgrade/index.ts
@@ -1,5 +1,5 @@
import { createParser } from "./parser";
-import { applyEdits } from "./textedit";
+import { applyEdits, Range } from "./textedit";
import { upgradeBehaviors } from "./behaviors";
import { upgradeHeaders } from "./headers";
@@ -23,3 +23,37 @@ export function upgradeKeymap(text: string) {
return applyEdits(text, edits);
}
+
+export function rangesToLineNumbers(
+ text: string,
+ changedRanges: Range[]
+): string {
+ const lineBreaks = getLineBreakPositions(text);
+
+ const changedLines = changedRanges.map((range) => {
+ const startLine = positionToLineNumber(range.startIndex, lineBreaks);
+ const endLine = positionToLineNumber(range.endIndex, lineBreaks);
+
+ return startLine === endLine ? `${startLine}` : `${startLine}-${endLine}`;
+ });
+
+ return `{${changedLines.join(",")}}`;
+}
+
+function getLineBreakPositions(text: string) {
+ const positions: number[] = [];
+ let index = 0;
+
+ while ((index = text.indexOf("\n", index)) >= 0) {
+ positions.push(index);
+ index++;
+ }
+
+ return positions;
+}
+
+function positionToLineNumber(position: number, lineBreaks: number[]) {
+ const line = lineBreaks.findIndex((lineBreak) => position <= lineBreak);
+
+ return line < 0 ? 0 : line + 1;
+}
diff --git a/docs/src/keymap-upgrade/keycodes.ts b/docs/src/keymap-upgrade/keycodes.ts
index 9a9ede66..5069556c 100644
--- a/docs/src/keymap-upgrade/keycodes.ts
+++ b/docs/src/keymap-upgrade/keycodes.ts
@@ -101,7 +101,7 @@ export function upgradeKeycodes(tree: Tree) {
function keycodeReplaceHandler(node: SyntaxNode, replacement: string | null) {
if (replacement) {
- return [new TextEdit(node, replacement)];
+ return [TextEdit.fromNode(node, replacement)];
}
const nodes = findBehaviorNodes(node);
@@ -110,7 +110,7 @@ function keycodeReplaceHandler(node: SyntaxNode, replacement: string | null) {
console.warn(
`Found deprecated code "${node.text}" but it is not a parameter to a behavior`
);
- return [new TextEdit(node, `/* "${node.text}" no longer exists */`)];
+ return [TextEdit.fromNode(node, `/* "${node.text}" no longer exists */`)];
}
const oldText = nodes.map((n) => n.text).join(" ");
diff --git a/docs/src/keymap-upgrade/properties.ts b/docs/src/keymap-upgrade/properties.ts
index 7edc555a..1cd3210f 100644
--- a/docs/src/keymap-upgrade/properties.ts
+++ b/docs/src/keymap-upgrade/properties.ts
@@ -24,9 +24,9 @@ function removeLabels(tree: Tree) {
const node = findCapture("prop", captures);
if (name?.text === "label" && node) {
if (isLayerLabel(node)) {
- edits.push(new TextEdit(name, "display-name"));
+ edits.push(TextEdit.fromNode(name, "display-name"));
} else {
- edits.push(new TextEdit(node, ""));
+ edits.push(TextEdit.fromNode(node, ""));
}
}
}
diff --git a/docs/src/keymap-upgrade/textedit.ts b/docs/src/keymap-upgrade/textedit.ts
index a791c1a6..263b2ab8 100644
--- a/docs/src/keymap-upgrade/textedit.ts
+++ b/docs/src/keymap-upgrade/textedit.ts
@@ -1,40 +1,23 @@
import type { SyntaxNode } from "web-tree-sitter";
-export class TextEdit {
- startIndex: number;
- endIndex: number;
+export class Range {
+ constructor(public startIndex: number, public endIndex: number) {}
+}
+
+export class TextEdit extends Range {
newText: string;
/**
* Creates a text edit to replace a range with new text.
*/
- constructor(startIndex: number, endIndex: number, newText: string);
- /**
- * Creates a text edit to replace a node with new text.
- */
- constructor(node: SyntaxNode, newText: string);
- constructor(
- startIndex: number | SyntaxNode,
- endIndex: number | string,
- newText?: string
- ) {
- if (typeof startIndex !== "number") {
- if (typeof endIndex === "string") {
- const node = startIndex;
- newText = endIndex;
- startIndex = node.startIndex;
- endIndex = node.endIndex;
- } else {
- throw new TypeError();
- }
- } else if (typeof endIndex !== "number" || typeof newText !== "string") {
- throw new TypeError();
- }
-
- this.startIndex = startIndex;
- this.endIndex = endIndex;
+ constructor(startIndex: number, endIndex: number, newText: string) {
+ super(startIndex, endIndex);
this.newText = newText;
}
+
+ static fromNode(node: SyntaxNode | Range, newText: string) {
+ return new TextEdit(node.startIndex, node.endIndex, newText);
+ }
}
export type MatchFunc = (node: SyntaxNode, text: string) => boolean;
@@ -67,7 +50,7 @@ export function getUpgradeEdits(
isMatch?: MatchFunc
) {
const defaultReplace: ReplaceFunc = (node, replacement) => [
- new TextEdit(node, replacement ?? ""),
+ TextEdit.fromNode(node, replacement ?? ""),
];
const defaultMatch: MatchFunc = (node, text) => node.text === text;
@@ -89,16 +72,26 @@ function sortEdits(edits: TextEdit[]) {
return edits.sort((a, b) => a.startIndex - b.startIndex);
}
+export interface EditResult {
+ text: string;
+ changedRanges: Range[];
+}
+
+interface TextChunk {
+ text: string;
+ changed?: boolean;
+}
+
/**
- * Returns a string with text replacements applied.
+ * Returns a string with text replacements applied and a list of ranges within
+ * that string that were modified.
*/
export function applyEdits(text: string, edits: TextEdit[]) {
// If we are removing text and it's the only thing on a line, remove the whole line.
edits = edits.map((e) => (e.newText ? e : expandEditToLine(text, e)));
-
edits = sortEdits(edits);
- const chunks: string[] = [];
+ const chunks: TextChunk[] = [];
let currentIndex = 0;
for (let edit of edits) {
@@ -107,14 +100,34 @@ export function applyEdits(text: string, edits: TextEdit[]) {
continue;
}
- chunks.push(text.substring(currentIndex, edit.startIndex));
- chunks.push(edit.newText);
+ chunks.push({ text: text.substring(currentIndex, edit.startIndex) });
+ chunks.push({ text: edit.newText, changed: true });
currentIndex = edit.endIndex;
}
- chunks.push(text.substring(currentIndex));
+ chunks.push({ text: text.substring(currentIndex) });
- return chunks.join("");
+ // Join all of the text chunks while recording the ranges of any chunks that were changed.
+ return chunks.reduce
(
+ (prev, current) => {
+ return {
+ text: prev.text + current.text,
+ changedRanges: reduceChangedRanges(prev, current),
+ };
+ },
+ { text: "", changedRanges: [] }
+ );
+}
+
+function reduceChangedRanges(prev: EditResult, current: TextChunk): Range[] {
+ if (current.changed) {
+ return [
+ ...prev.changedRanges,
+ new Range(prev.text.length, prev.text.length + current.text.length),
+ ];
+ }
+
+ return prev.changedRanges;
}
/**