refactor: Improve keymap upgrader

Moved the keymap upgrader to a top-level page like the power profiler
to make it more discoverable. It upgrades more things than key codes
now, so putting it in the codes category doesn't make much sense.

Converted the upgrader code to TypeScript and split it up into smaller
files to make it easier to add new upgrade functions.

Added upgrade functions to remove/replace "label" properties and rename
matrix-transform.h to matrix_transform.h.
This commit is contained in:
Joel Spadin 2024-01-21 17:15:58 -06:00
parent 1a3529a3e6
commit a0465391be
14 changed files with 528 additions and 337 deletions

View File

@ -55,6 +55,11 @@ module.exports = {
label: "Power Profiler", label: "Power Profiler",
position: "left", position: "left",
}, },
{
to: "keymap-upgrader",
label: "Keymap Upgrader",
position: "left",
},
{ {
href: "https://github.com/zmkfirmware/zmk", href: "https://github.com/zmkfirmware/zmk",
label: "GitHub", label: "GitHub",

View File

@ -53,7 +53,6 @@ module.exports = {
"codes/applications", "codes/applications",
"codes/input-assist", "codes/input-assist",
"codes/power", "codes/power",
"codes/keymap-upgrader",
], ],
Configuration: [ Configuration: [
"config/index", "config/index",

View File

@ -40,7 +40,9 @@ 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 metastring={'title="Upgraded Keymap"'}>{upgraded}</CodeBlock> <CodeBlock language="dts" metastring={'title="Upgraded Keymap"'}>
{upgraded}
</CodeBlock>
</div> </div>
</div> </div>
); );

View File

@ -1,85 +0,0 @@
/*
* Copyright (c) 2020 The ZMK Contributors
*
* SPDX-License-Identifier: CC-BY-NC-SA-4.0
*/
export const Codes = {
NUM_1: "N1",
NUM_2: "N2",
NUM_3: "N3",
NUM_4: "N4",
NUM_5: "N5",
NUM_6: "N6",
NUM_7: "N7",
NUM_8: "N8",
NUM_9: "N9",
NUM_0: "N0",
BKSP: "BSPC",
SPC: "SPACE",
EQL: "EQUAL",
TILD: "TILDE",
SCLN: "SEMI",
QUOT: "SQT",
GRAV: "GRAVE",
CMMA: "COMMA",
PRSC: "PSCRN",
SCLK: "SLCK",
PAUS: "PAUSE_BREAK",
PGUP: "PG_UP",
PGDN: "PG_DN",
RARW: "RIGHT",
LARW: "LEFT",
DARW: "DOWN",
UARW: "UP",
KDIV: "KP_DIVIDE",
KMLT: "KP_MULTIPLY",
KMIN: "KP_MINUS",
KPLS: "KP_PLUS",
UNDO: "K_UNDO",
CUT: "K_CUT",
COPY: "K_COPY",
PSTE: "K_PASTE",
VOLU: "K_VOL_UP",
VOLD: "K_VOL_DN",
CURU: "DLLR",
LPRN: "LPAR",
RPRN: "RPAR",
LCUR: "LBRC",
RCUR: "RBRC",
CRRT: "CARET",
PRCT: "PRCNT",
LABT: "LT",
RABT: "GT",
COLN: "COLON",
KSPC: null,
ATSN: "AT",
BANG: "EXCL",
LCTL: "LCTRL",
LSFT: "LSHIFT",
RCTL: "RCTRL",
RSFT: "RSHIFT",
M_NEXT: "C_NEXT",
M_PREV: "C_PREV",
M_STOP: "C_STOP",
M_EJCT: "C_EJECT",
M_PLAY: "C_PP",
M_MUTE: "C_MUTE",
M_VOLU: "C_VOL_UP",
M_VOLD: "C_VOL_DN",
GUI: "K_CMENU",
MOD_LCTL: "LCTRL",
MOD_LSFT: "LSHIFT",
MOD_LALT: "LALT",
MOD_LGUI: "LGUI",
MOD_RCTL: "RCTRL",
MOD_RSFT: "RSHIFT",
MOD_RALT: "RALT",
MOD_RGUI: "RGUI",
};
export const Behaviors = {
cp: "kp",
inc_dec_cp: "inc_dec_kp",
reset: "sys_reset",
};

View File

@ -1,245 +0,0 @@
import Parser from "web-tree-sitter";
import { Codes, Behaviors } from "./data/keymap-upgrade";
const TREE_SITTER_WASM_URL = new URL(
"/node_modules/web-tree-sitter/tree-sitter.wasm",
import.meta.url
);
let Devicetree;
export async function initParser() {
await Parser.init({
locateFile: (path, prefix) => {
// When locating tree-sitter.wasm, use a path that Webpack can map to the correct URL.
if (path == "tree-sitter.wasm") {
return TREE_SITTER_WASM_URL.href;
}
return prefix + path;
},
});
Devicetree = await Parser.Language.load("/tree-sitter-devicetree.wasm");
}
function createParser() {
if (!Devicetree) {
throw new Error("Parser not loaded. Call initParser() first.");
}
const parser = new Parser();
parser.setLanguage(Devicetree);
return parser;
}
export function upgradeKeymap(text) {
const parser = createParser();
const tree = parser.parse(text);
const edits = [...upgradeBehaviors(tree), ...upgradeKeycodes(tree)];
return applyEdits(text, edits);
}
class TextEdit {
/**
* Creates a text edit to replace a range or node with new text.
* Construct with one of:
*
* * `Edit(startIndex, endIndex, newText)`
* * `Edit(node, newText)`
*/
constructor(startIndex, endIndex, newText) {
if (typeof startIndex !== "number") {
const node = startIndex;
newText = endIndex;
startIndex = node.startIndex;
endIndex = node.endIndex;
}
/** @type number */
this.startIndex = startIndex;
/** @type number */
this.endIndex = endIndex;
/** @type string */
this.newText = newText;
}
}
/**
* Upgrades deprecated behavior references.
* @param {Parser.Tree} tree
*/
function upgradeBehaviors(tree) {
/** @type TextEdit[] */
let edits = [];
const query = Devicetree.query("(reference label: (identifier) @ref)");
const matches = query.matches(tree.rootNode);
for (const { captures } of matches) {
const node = findCapture("ref", captures);
if (node) {
edits.push(...getUpgradeEdits(node, Behaviors));
}
}
return edits;
}
/**
* Upgrades deprecated key code identifiers.
* @param {Parser.Tree} tree
*/
function upgradeKeycodes(tree) {
/** @type TextEdit[] */
let edits = [];
// No need to filter to the bindings array. The C preprocessor would have
// replaced identifiers anywhere, so upgrading all identifiers preserves the
// original behavior of the keymap (even if that behavior wasn't intended).
const query = Devicetree.query("(identifier) @name");
const matches = query.matches(tree.rootNode);
for (const { captures } of matches) {
const node = findCapture("name", captures);
if (node) {
edits.push(...getUpgradeEdits(node, Codes, keycodeReplaceHandler));
}
}
return edits;
}
/**
* @param {Parser.SyntaxNode} node
* @param {string | null} replacement
* @returns TextEdit[]
*/
function keycodeReplaceHandler(node, replacement) {
if (replacement) {
return [new TextEdit(node, replacement)];
}
const nodes = findBehaviorNodes(node);
if (nodes.length === 0) {
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 */`)];
}
const oldText = nodes.map((n) => n.text).join(" ");
const newText = `&none /* "${oldText}" no longer exists */`;
const startIndex = nodes[0].startIndex;
const endIndex = nodes[nodes.length - 1].endIndex;
return [new TextEdit(startIndex, endIndex, newText)];
}
/**
* Returns the node for the named capture.
* @param {string} name
* @param {any[]} captures
* @returns {Parser.SyntaxNode | null}
*/
function findCapture(name, captures) {
for (const c of captures) {
if (c.name === name) {
return c.node;
}
}
return null;
}
/**
* Given a parameter to a keymap behavior, returns a list of nodes beginning
* with the behavior and including all parameters.
* Returns an empty array if no behavior was found.
* @param {Parser.SyntaxNode} paramNode
*/
function findBehaviorNodes(paramNode) {
// Walk backwards from the given parameter to find the behavior reference.
let behavior = paramNode.previousNamedSibling;
while (behavior && behavior.type !== "reference") {
behavior = behavior.previousNamedSibling;
}
if (!behavior) {
return [];
}
// Walk forward from the behavior to collect all its parameters.
let nodes = [behavior];
let param = behavior.nextNamedSibling;
while (param && param.type !== "reference") {
nodes.push(param);
param = param.nextNamedSibling;
}
return nodes;
}
/**
* Gets a list of text edits to apply based on a node and a map of text
* replacements.
*
* If replaceHandler is given, it will be called if the node matches a
* deprecated value and it should return the text edits to apply.
*
* @param {Parser.SyntaxNode} node
* @param {Map<string, string | null>} replacementMap
* @param {(node: Parser.SyntaxNode, replacement: string | null) => TextEdit[]} replaceHandler
*/
function getUpgradeEdits(node, replacementMap, replaceHandler = undefined) {
for (const [deprecated, replacement] of Object.entries(replacementMap)) {
if (node.text === deprecated) {
if (replaceHandler) {
return replaceHandler(node, replacement);
} else {
return [new TextEdit(node, replacement)];
}
}
}
return [];
}
/**
* Sorts a list of text edits in ascending order by position.
* @param {TextEdit[]} edits
*/
function sortEdits(edits) {
return edits.sort((a, b) => a.startIndex - b.startIndex);
}
/**
* Returns a string with text replacements applied.
* @param {string} text
* @param {TextEdit[]} edits
*/
function applyEdits(text, edits) {
edits = sortEdits(edits);
/** @type string[] */
const chunks = [];
let currentIndex = 0;
for (const edit of edits) {
if (edit.startIndex < currentIndex) {
console.warn("discarding overlapping edit", edit);
continue;
}
chunks.push(text.substring(currentIndex, edit.startIndex));
chunks.push(edit.newText);
currentIndex = edit.endIndex;
}
chunks.push(text.substring(currentIndex));
return chunks.join("");
}

View File

@ -0,0 +1,27 @@
import type { Tree } from "web-tree-sitter";
import { Devicetree, findCapture } from "./parser";
import { TextEdit, getUpgradeEdits } from "./textedit";
// Map of { "deprecated": "replacement" } behavior names (not including "&" prefixes).
const BEHAVIORS = {
cp: "kp",
inc_dec_cp: "inc_dec_kp",
reset: "sys_reset",
};
export function upgradeBehaviors(tree: Tree) {
const edits: TextEdit[] = [];
const query = Devicetree.query("(reference label: (identifier) @ref)");
const matches = query.matches(tree.rootNode);
for (const { captures } of matches) {
const node = findCapture("ref", captures);
if (node) {
edits.push(...getUpgradeEdits(node, BEHAVIORS));
}
}
return edits;
}

View File

@ -0,0 +1,40 @@
import type { SyntaxNode, Tree } from "web-tree-sitter";
import { Devicetree, findCapture } from "./parser";
import { getUpgradeEdits, MatchFunc, ReplaceFunc, TextEdit } from "./textedit";
// Map of { "deprecated": "replacement" } header paths.
const HEADERS = {
"dt-bindings/zmk/matrix-transform.h": "dt-bindings/zmk/matrix_transform.h",
};
export function upgradeHeaders(tree: Tree) {
const edits: TextEdit[] = [];
const query = Devicetree.query(
"(preproc_include path: [(string_literal) (system_lib_string)] @path)"
);
const matches = query.matches(tree.rootNode);
for (const { captures } of matches) {
const node = findCapture("path", captures);
if (node) {
edits.push(
...getUpgradeEdits(node, HEADERS, headerReplaceHandler, isHeaderMatch)
);
}
}
return edits;
}
const isHeaderMatch: MatchFunc = (node, text) => {
return node.text === `"${text}"` || node.text === `<${text}>`;
};
const headerReplaceHandler: ReplaceFunc = (node, replacement) => {
if (!replacement) {
throw new Error("Header replacement does not support removing headers");
}
return [new TextEdit(node.startIndex + 1, node.endIndex - 1, replacement)];
};

View File

@ -0,0 +1,25 @@
import { createParser } from "./parser";
import { applyEdits } from "./textedit";
import { upgradeBehaviors } from "./behaviors";
import { upgradeHeaders } from "./headers";
import { upgradeKeycodes } from "./keycodes";
import { upgradeProperties } from "./properties";
export { initParser } from "./parser";
const upgradeFunctions = [
upgradeBehaviors,
upgradeHeaders,
upgradeKeycodes,
upgradeProperties,
];
export function upgradeKeymap(text: string) {
const parser = createParser();
const tree = parser.parse(text);
const edits = upgradeFunctions.map((f) => f(tree)).flat();
return applyEdits(text, edits);
}

View File

@ -0,0 +1,150 @@
import type { SyntaxNode, Tree } from "web-tree-sitter";
import { Devicetree, findCapture } from "./parser";
import { getUpgradeEdits, TextEdit } from "./textedit";
// Map of { "DEPRECATED": "REPLACEMENT" } key codes.
const CODES = {
NUM_1: "N1",
NUM_2: "N2",
NUM_3: "N3",
NUM_4: "N4",
NUM_5: "N5",
NUM_6: "N6",
NUM_7: "N7",
NUM_8: "N8",
NUM_9: "N9",
NUM_0: "N0",
BKSP: "BSPC",
SPC: "SPACE",
EQL: "EQUAL",
TILD: "TILDE",
SCLN: "SEMI",
QUOT: "SQT",
GRAV: "GRAVE",
CMMA: "COMMA",
PRSC: "PSCRN",
SCLK: "SLCK",
PAUS: "PAUSE_BREAK",
PGUP: "PG_UP",
PGDN: "PG_DN",
RARW: "RIGHT",
LARW: "LEFT",
DARW: "DOWN",
UARW: "UP",
KDIV: "KP_DIVIDE",
KMLT: "KP_MULTIPLY",
KMIN: "KP_MINUS",
KPLS: "KP_PLUS",
UNDO: "K_UNDO",
CUT: "K_CUT",
COPY: "K_COPY",
PSTE: "K_PASTE",
VOLU: "K_VOL_UP",
VOLD: "K_VOL_DN",
CURU: "DLLR",
LPRN: "LPAR",
RPRN: "RPAR",
LCUR: "LBRC",
RCUR: "RBRC",
CRRT: "CARET",
PRCT: "PRCNT",
LABT: "LT",
RABT: "GT",
COLN: "COLON",
KSPC: null,
ATSN: "AT",
BANG: "EXCL",
LCTL: "LCTRL",
LSFT: "LSHIFT",
RCTL: "RCTRL",
RSFT: "RSHIFT",
M_NEXT: "C_NEXT",
M_PREV: "C_PREV",
M_STOP: "C_STOP",
M_EJCT: "C_EJECT",
M_PLAY: "C_PP",
M_MUTE: "C_MUTE",
M_VOLU: "C_VOL_UP",
M_VOLD: "C_VOL_DN",
GUI: "K_CMENU",
MOD_LCTL: "LCTRL",
MOD_LSFT: "LSHIFT",
MOD_LALT: "LALT",
MOD_LGUI: "LGUI",
MOD_RCTL: "RCTRL",
MOD_RSFT: "RSHIFT",
MOD_RALT: "RALT",
MOD_RGUI: "RGUI",
};
/**
* Upgrades deprecated key code identifiers.
*/
export function upgradeKeycodes(tree: Tree) {
const edits: TextEdit[] = [];
// No need to filter to the bindings array. The C preprocessor would have
// replaced identifiers anywhere, so upgrading all identifiers preserves the
// original behavior of the keymap (even if that behavior wasn't intended).
const query = Devicetree.query("(identifier) @name");
const matches = query.matches(tree.rootNode);
for (const { captures } of matches) {
const node = findCapture("name", captures);
if (node) {
edits.push(...getUpgradeEdits(node, CODES, keycodeReplaceHandler));
}
}
return edits;
}
function keycodeReplaceHandler(node: SyntaxNode, replacement: string | null) {
if (replacement) {
return [new TextEdit(node, replacement)];
}
const nodes = findBehaviorNodes(node);
if (nodes.length === 0) {
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 */`)];
}
const oldText = nodes.map((n) => n.text).join(" ");
const newText = `&none /* "${oldText}" no longer exists */`;
const startIndex = nodes[0].startIndex;
const endIndex = nodes[nodes.length - 1].endIndex;
return [new TextEdit(startIndex, endIndex, newText)];
}
/**
* Given a parameter to a keymap behavior, returns a list of nodes beginning
* with the behavior and including all parameters.
* Returns an empty array if no behavior was found.
*/
function findBehaviorNodes(paramNode: SyntaxNode) {
// Walk backwards from the given parameter to find the behavior reference.
let behavior = paramNode.previousNamedSibling;
while (behavior && behavior.type !== "reference") {
behavior = behavior.previousNamedSibling;
}
if (!behavior) {
return [];
}
// Walk forward from the behavior to collect all its parameters.
let nodes = [behavior];
let param = behavior.nextNamedSibling;
while (param && param.type !== "reference") {
nodes.push(param);
param = param.nextNamedSibling;
}
return nodes;
}

View File

@ -0,0 +1,56 @@
import Parser from "web-tree-sitter";
const TREE_SITTER_WASM_URL = new URL(
"/node_modules/web-tree-sitter/tree-sitter.wasm",
import.meta.url
);
export let Devicetree: Parser.Language;
export async function initParser() {
await Parser.init({
locateFile: (path: string, prefix: string) => {
// When locating tree-sitter.wasm, use a path that Webpack can map to the correct URL.
if (path == "tree-sitter.wasm") {
return TREE_SITTER_WASM_URL.href;
}
return prefix + path;
},
});
Devicetree = await Parser.Language.load("/tree-sitter-devicetree.wasm");
}
export function createParser() {
if (!Devicetree) {
throw new Error("Parser not loaded. Call initParser() first.");
}
const parser = new Parser();
parser.setLanguage(Devicetree);
return parser;
}
/**
* Returns the node for the named capture.
*/
export function findCapture(name: string, captures: Parser.QueryCapture[]) {
for (const c of captures) {
if (c.name === name) {
return c.node;
}
}
return null;
}
/**
* Returns whether the node for the named capture exists and has the given text.
*/
export function captureHasText(
name: string,
captures: Parser.QueryCapture[],
text: string
) {
const node = findCapture(name, captures);
return node?.text === text;
}

View File

@ -0,0 +1,65 @@
import type { SyntaxNode, Tree } from "web-tree-sitter";
import { captureHasText, Devicetree, findCapture } from "./parser";
import { TextEdit } from "./textedit";
/**
* Upgrades deprecated properties.
*/
export function upgradeProperties(tree: Tree) {
return removeLabels(tree);
}
/**
* Renames "label" properties in keymap layers to "display-name". Removes all
* other "label" properties.
*/
function removeLabels(tree: Tree) {
const edits: TextEdit[] = [];
const query = Devicetree.query("(property name: (identifier) @name) @prop");
const matches = query.matches(tree.rootNode);
for (const { captures } of matches) {
const name = findCapture("name", captures);
const node = findCapture("prop", captures);
if (name?.text === "label" && node) {
if (isLayerLabel(node)) {
edits.push(new TextEdit(name, "display-name"));
} else {
edits.push(new TextEdit(node, ""));
}
}
}
return edits;
}
/**
* Given a "label" property node, returns whether it is a label for a keymap layer.
*/
function isLayerLabel(node: SyntaxNode) {
const maybeKeymap = node.parent?.parent;
if (!maybeKeymap) {
return false;
}
const query = Devicetree.query(
`(property
name: (identifier) @name
value: (string_literal) @value
) @prop`
);
const matches = query.matches(maybeKeymap);
for (const { captures } of matches) {
if (
findCapture("prop", captures)?.parent?.equals(maybeKeymap) &&
captureHasText("name", captures, "compatible") &&
captureHasText("value", captures, '"zmk,keymap"')
) {
return true;
}
}
return false;
}

View File

@ -0,0 +1,153 @@
import type { SyntaxNode } from "web-tree-sitter";
export class TextEdit {
startIndex: number;
endIndex: number;
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;
this.newText = newText;
}
}
export type MatchFunc = (node: SyntaxNode, text: string) => boolean;
export type ReplaceFunc = (
node: SyntaxNode,
replacement: string | null
) => TextEdit[];
/**
* Gets a list of text edits to apply based on a node and a map of text
* replacements.
*
* If replaceHandler is given, it will be called if the node matches a
* deprecated value and it should return the text edits to apply.
* Otherwise, the full node is replaced by the new text.
*
* If isMatch is given, it will be called to check if a node matches a
* deprecated value. Otherwise, the node's text is matched against the
* deprecated text.
*
* @param {SyntaxNode} node
* @param {Record<string, string | null>} replacementMap
* @param {ReplaceFunc} [replaceHandler]
* @param {MatchFunc} [isMatch]
*/
export function getUpgradeEdits(
node: SyntaxNode,
replacementMap: Record<string, string | null>,
replaceHandler?: ReplaceFunc,
isMatch?: MatchFunc
) {
const defaultReplace: ReplaceFunc = (node, replacement) => [
new TextEdit(node, replacement ?? ""),
];
const defaultMatch: MatchFunc = (node, text) => node.text === text;
replaceHandler = replaceHandler ?? defaultReplace;
isMatch = isMatch ?? defaultMatch;
for (const [deprecated, replacement] of Object.entries(replacementMap)) {
if (isMatch(node, deprecated)) {
return replaceHandler(node, replacement);
}
}
return [];
}
/**
* Sorts a list of text edits in ascending order by position.
*/
function sortEdits(edits: TextEdit[]) {
return edits.sort((a, b) => a.startIndex - b.startIndex);
}
/**
* Returns a string with text replacements applied.
*/
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[] = [];
let currentIndex = 0;
for (let edit of edits) {
if (edit.startIndex < currentIndex) {
console.warn("discarding overlapping edit", edit);
continue;
}
chunks.push(text.substring(currentIndex, edit.startIndex));
chunks.push(edit.newText);
currentIndex = edit.endIndex;
}
chunks.push(text.substring(currentIndex));
return chunks.join("");
}
/**
* If the given edit is surrounded by only whitespace on a line, expands it to
* replace the entire line, else returns it unmodified.
*/
function expandEditToLine(text: string, edit: TextEdit) {
// Expand the selection to adjacent whitespace
let newStart = edit.startIndex;
let newEnd = edit.endIndex;
while (newStart > 0 && text[newStart - 1].match(/[ \t]/)) {
newStart--;
}
while (newEnd < text.length && text[newEnd].match(/[ \t]/)) {
newEnd++;
}
// Check that we selected the entire line
if (
(newEnd !== text.length && text[newEnd] !== "\n") ||
(newStart > 0 && text[newStart - 1] !== "\n")
) {
return edit;
}
// Select one of the line breaks to remove.
if (newEnd !== text.length) {
newEnd++;
} else if (newStart !== 0) {
newStart--;
}
return new TextEdit(newStart, newEnd, edit.newText);
}

View File

@ -7,14 +7,13 @@ hide_table_of_contents: true
# Keymap Upgrader # Keymap Upgrader
Many codes have been renamed to be more consistent with each other. Some behaviors, key codes, and other features have been renamed to be more consistent with each other. This tool will upgrade most deprecated features to their replacements.
Paste the contents of a `.keymap` file below to upgrade all deprecated codes to their replacements.
Hover your mouse over the upgraded keymap and click the `Copy` button to copy it to your clipboard. Paste the contents of a `.keymap` file below. Then, hover your mouse over the upgraded keymap and click the `Copy` button in the upper-right corner to copy it to your clipboard.
You will likely need to realign columns in the upgraded keymap. The upgrader also does not handle You will likely need to realign columns in the upgraded keymap. The upgrader also does not handle
codes inside a `#define`, so you will need to update those manually using codes inside a `#define`, so you will need to update those manually using
[this list of deprecated codes and replacements](https://github.com/zmkfirmware/zmk/blob/main/docs/src/data/keymap-upgrade.js). [this list of deprecated codes and replacements](https://github.com/zmkfirmware/zmk/blob/main/docs/src/keymap-upgrade/keycodes.ts).
import KeymapUpgrader from "@site/src/components/KeymapUpgrader/index"; import KeymapUpgrader from "@site/src/components/KeymapUpgrader/index";

View File

@ -1,5 +1,5 @@
{ {
"extends": "@docusaurus/tsconfig/tsconfig.json", "extends": "@docusaurus/tsconfig",
"include": ["src/"], "include": ["src/"],
"compilerOptions": { "compilerOptions": {
"types": ["node", "@docusaurus/theme-classic"], "types": ["node", "@docusaurus/theme-classic"],