feat(keymap-upgrader): Highlight changes
Updated the keymap upgrader to highlight which lines it changed as well as indicate when nothing needed to be upgraded. Also adjusted the line highlight colors to be more readable in both light and dark color schemes.
This commit is contained in:
parent
84e056793b
commit
37fcf190e6
6 changed files with 109 additions and 45 deletions
|
@ -7,7 +7,11 @@
|
||||||
import React from "react";
|
import React from "react";
|
||||||
import { useAsync } from "react-async";
|
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 CodeBlock from "@theme/CodeBlock";
|
||||||
|
|
||||||
import styles from "./styles.module.css";
|
import styles from "./styles.module.css";
|
||||||
|
@ -28,7 +32,15 @@ export default function KeymapUpgrader() {
|
||||||
|
|
||||||
function Editor() {
|
function Editor() {
|
||||||
const [keymap, setKeymap] = React.useState("");
|
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 (
|
return (
|
||||||
<div>
|
<div>
|
||||||
|
@ -40,7 +52,7 @@ function Editor() {
|
||||||
onChange={(e) => setKeymap(e.target.value)}
|
onChange={(e) => setKeymap(e.target.value)}
|
||||||
></textarea>
|
></textarea>
|
||||||
<div className={styles.result}>
|
<div className={styles.result}>
|
||||||
<CodeBlock language="dts" metastring={'title="Upgraded Keymap"'}>
|
<CodeBlock language="dts" metastring={`${highlights} title="${title}"`}>
|
||||||
{upgraded}
|
{upgraded}
|
||||||
</CodeBlock>
|
</CodeBlock>
|
||||||
</div>
|
</div>
|
||||||
|
|
|
@ -15,10 +15,15 @@
|
||||||
--ifm-color-primary-lighter: #0280e3;
|
--ifm-color-primary-lighter: #0280e3;
|
||||||
--ifm-color-primary-lightest: #0690fc;
|
--ifm-color-primary-lightest: #0690fc;
|
||||||
--ifm-code-font-size: 95%;
|
--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 {
|
.docusaurus-highlight-code-line {
|
||||||
background-color: rgb(72, 77, 91);
|
|
||||||
display: block;
|
display: block;
|
||||||
margin: 0 calc(-1 * var(--ifm-pre-padding));
|
margin: 0 calc(-1 * var(--ifm-pre-padding));
|
||||||
padding: 0 var(--ifm-pre-padding);
|
padding: 0 var(--ifm-pre-padding);
|
||||||
|
|
|
@ -1,5 +1,5 @@
|
||||||
import { createParser } from "./parser";
|
import { createParser } from "./parser";
|
||||||
import { applyEdits } from "./textedit";
|
import { applyEdits, Range } from "./textedit";
|
||||||
|
|
||||||
import { upgradeBehaviors } from "./behaviors";
|
import { upgradeBehaviors } from "./behaviors";
|
||||||
import { upgradeHeaders } from "./headers";
|
import { upgradeHeaders } from "./headers";
|
||||||
|
@ -23,3 +23,37 @@ export function upgradeKeymap(text: string) {
|
||||||
|
|
||||||
return applyEdits(text, edits);
|
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;
|
||||||
|
}
|
||||||
|
|
|
@ -101,7 +101,7 @@ export function upgradeKeycodes(tree: Tree) {
|
||||||
|
|
||||||
function keycodeReplaceHandler(node: SyntaxNode, replacement: string | null) {
|
function keycodeReplaceHandler(node: SyntaxNode, replacement: string | null) {
|
||||||
if (replacement) {
|
if (replacement) {
|
||||||
return [new TextEdit(node, replacement)];
|
return [TextEdit.fromNode(node, replacement)];
|
||||||
}
|
}
|
||||||
|
|
||||||
const nodes = findBehaviorNodes(node);
|
const nodes = findBehaviorNodes(node);
|
||||||
|
@ -110,7 +110,7 @@ function keycodeReplaceHandler(node: SyntaxNode, replacement: string | null) {
|
||||||
console.warn(
|
console.warn(
|
||||||
`Found deprecated code "${node.text}" but it is not a parameter to a behavior`
|
`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(" ");
|
const oldText = nodes.map((n) => n.text).join(" ");
|
||||||
|
|
|
@ -24,9 +24,9 @@ function removeLabels(tree: Tree) {
|
||||||
const node = findCapture("prop", captures);
|
const node = findCapture("prop", captures);
|
||||||
if (name?.text === "label" && node) {
|
if (name?.text === "label" && node) {
|
||||||
if (isLayerLabel(node)) {
|
if (isLayerLabel(node)) {
|
||||||
edits.push(new TextEdit(name, "display-name"));
|
edits.push(TextEdit.fromNode(name, "display-name"));
|
||||||
} else {
|
} else {
|
||||||
edits.push(new TextEdit(node, ""));
|
edits.push(TextEdit.fromNode(node, ""));
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -1,40 +1,23 @@
|
||||||
import type { SyntaxNode } from "web-tree-sitter";
|
import type { SyntaxNode } from "web-tree-sitter";
|
||||||
|
|
||||||
export class TextEdit {
|
export class Range {
|
||||||
startIndex: number;
|
constructor(public startIndex: number, public endIndex: number) {}
|
||||||
endIndex: number;
|
}
|
||||||
|
|
||||||
|
export class TextEdit extends Range {
|
||||||
newText: string;
|
newText: string;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Creates a text edit to replace a range with new text.
|
* Creates a text edit to replace a range with new text.
|
||||||
*/
|
*/
|
||||||
constructor(startIndex: number, endIndex: number, newText: string);
|
constructor(startIndex: number, endIndex: number, newText: string) {
|
||||||
/**
|
super(startIndex, endIndex);
|
||||||
* 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;
|
|
||||||
this.newText = newText;
|
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;
|
export type MatchFunc = (node: SyntaxNode, text: string) => boolean;
|
||||||
|
@ -67,7 +50,7 @@ export function getUpgradeEdits(
|
||||||
isMatch?: MatchFunc
|
isMatch?: MatchFunc
|
||||||
) {
|
) {
|
||||||
const defaultReplace: ReplaceFunc = (node, replacement) => [
|
const defaultReplace: ReplaceFunc = (node, replacement) => [
|
||||||
new TextEdit(node, replacement ?? ""),
|
TextEdit.fromNode(node, replacement ?? ""),
|
||||||
];
|
];
|
||||||
const defaultMatch: MatchFunc = (node, text) => node.text === text;
|
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);
|
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[]) {
|
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.
|
// 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 = edits.map((e) => (e.newText ? e : expandEditToLine(text, e)));
|
||||||
|
|
||||||
edits = sortEdits(edits);
|
edits = sortEdits(edits);
|
||||||
|
|
||||||
const chunks: string[] = [];
|
const chunks: TextChunk[] = [];
|
||||||
let currentIndex = 0;
|
let currentIndex = 0;
|
||||||
|
|
||||||
for (let edit of edits) {
|
for (let edit of edits) {
|
||||||
|
@ -107,14 +100,34 @@ export function applyEdits(text: string, edits: TextEdit[]) {
|
||||||
continue;
|
continue;
|
||||||
}
|
}
|
||||||
|
|
||||||
chunks.push(text.substring(currentIndex, edit.startIndex));
|
chunks.push({ text: text.substring(currentIndex, edit.startIndex) });
|
||||||
chunks.push(edit.newText);
|
chunks.push({ text: edit.newText, changed: true });
|
||||||
currentIndex = edit.endIndex;
|
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<EditResult>(
|
||||||
|
(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;
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
|
Loading…
Add table
Reference in a new issue