From 16540124fd180e8582e866120d505990e7cb0441 Mon Sep 17 00:00:00 2001 From: Andreas Arvidsson Date: Mon, 31 Aug 2026 23:22:05 +0200 Subject: [PATCH] Generate spoken_forms.json with Meta Updater --- cursorless-talon/src/spoken_forms.json | 124 ++++++---- packages/tool-meta-updater/src/metaUpdater.ts | 2 + .../src/updateSpokenForms.ts | 234 ++++++++++++++++++ 3 files changed, 318 insertions(+), 42 deletions(-) create mode 100644 packages/tool-meta-updater/src/updateSpokenForms.ts diff --git a/cursorless-talon/src/spoken_forms.json b/cursorless-talon/src/spoken_forms.json index ba30376933..8ef1b70039 100644 --- a/cursorless-talon/src/spoken_forms.json +++ b/cursorless-talon/src/spoken_forms.json @@ -2,18 +2,18 @@ "NOTE FOR USERS": "Please don't edit this json file; see https://www.cursorless.org/docs/user/customization", "actions.csv": { "simple_action": { + "append": "addSelection", "append post": "addSelectionAfter", "append pre": "addSelectionBefore", - "append": "addSelection", "bottom": "scrollToBottom", - "break point": "toggleLineBreakpoint", "break": "breakLine", + "break point": "toggleLineBreakpoint", "carve": "cutToClipboard", "center": "scrollToCenter", "change": "clearAndSetSelection", "chuck": "remove", - "clone up": "insertCopyBefore", "clone": "insertCopyAfter", + "clone up": "insertCopyBefore", "comment": "toggleLineComment", "copy": "copyToClipboard", "crown": "scrollToTop", @@ -26,12 +26,12 @@ "flash": "flashTargets", "float": "insertEmptyLineAfter", "fold": "foldRegion", - "follow split": "followLinkAside", "follow": "followLink", + "follow split": "followLinkAside", "git accept": "gitAccept", + "git revert": "gitRevert", "git stage": "gitStage", "git unstage": "gitUnstage", - "git revert": "gitRevert", "give": "deselect", "highlight": "highlight", "hover": "showHover", @@ -47,8 +47,8 @@ "reference": "showReferences", "rename": "rename", "reverse": "reverseTargets", - "scout all": "findInWorkspace", "scout": "findInDocument", + "scout all": "findInWorkspace", "shuffle": "randomizeTargets", "snip make": "generateSnippet", "sort": "sortTargets", @@ -59,16 +59,29 @@ "callback_action": { "phones": "nextHomophone" }, - "paste_action": { "paste": "pasteFromClipboard" }, + "paste_action": { + "paste": "pasteFromClipboard" + }, "bring_move_action": { "bring": "replaceWithTarget", "move": "moveToTarget" }, - "swap_action": { "swap": "swapTargets" }, - "wrap_action": { "wrap": "wrapWithPairedDelimiter", "repack": "rewrap" }, - "insert_snippet_action": { "snip": "insertSnippet" }, - "reformat_action": { "format": "applyFormatter" }, - "call_action": { "call": "callAsFunction" } + "swap_action": { + "swap": "swapTargets" + }, + "wrap_action": { + "wrap": "wrapWithPairedDelimiter", + "repack": "rewrap" + }, + "insert_snippet_action": { + "snip": "insertSnippet" + }, + "reformat_action": { + "format": "applyFormatter" + }, + "call_action": { + "call": "callAsFunction" + } }, "target_connectives.csv": { "range_connective": { @@ -77,9 +90,15 @@ "-": "rangeExcludingStart", "until": "rangeExcludingEnd" }, - "list_connective": { "and": "listConnective" }, - "swap_connective": { "with": "swapConnective" }, - "insertion_mode_to": { "to": "sourceDestinationConnective" } + "list_connective": { + "and": "listConnective" + }, + "swap_connective": { + "with": "swapConnective" + }, + "insertion_mode_to": { + "to": "sourceDestinationConnective" + } }, "modifiers.csv": { "simple_modifier": { @@ -92,8 +111,9 @@ "its": "inferPreviousMark", "visible": "visible" }, - "every_scope_modifier": { "every": "every" }, - "ancestor_scope_modifier": { "grand": "ancestor" }, + "every_scope_modifier": { + "every": "every" + }, "interior_modifier": { "inside": "interiorOnly" }, @@ -104,9 +124,19 @@ "range_type": { "slice": "verticalRange" }, - "first_modifier": { "first": "first" }, - "last_modifier": { "last": "last" }, - "previous_next_modifier": { "previous": "previous", "next": "next" }, + "ancestor_scope_modifier": { + "grand": "ancestor" + }, + "first_modifier": { + "first": "first" + }, + "last_modifier": { + "last": "last" + }, + "previous_next_modifier": { + "previous": "previous", + "next": "next" + }, "forward_backward_modifier": { "forward": "forward", "backward": "backward" @@ -126,23 +156,28 @@ "scope_type": { "arg": "argumentOrParameter", "arg list": "argumentList", + "lambda": "anonymousFunction", "attribute": "attribute", - "call": "functionCall", - "callee": "functionCallee", - "class name": "className", + "branch": "branch", "class": "class", + "class name": "className", + "item": "collectionItem", + "key": "collectionKey", "comment": "comment", + "call": "functionCall", + "callee": "functionCallee", "funk name": "functionName", - "funk": "namedFunction", "if state": "ifStatement", "instance": "instance", - "item": "collectionItem", - "key": "collectionKey", - "lambda": "anonymousFunction", "list": "list", "map": "map", "name": "name", + "funk": "namedFunction", "regex": "regularExpression", + "state": "statement", + "type": "type", + "value": "value", + "condition": "condition", "section": "section", "-one section": "sectionLevelOne", "-two section": "sectionLevelTwo", @@ -151,16 +186,11 @@ "-five section": "sectionLevelFive", "-six section": "sectionLevelSix", "selector": "selector", - "state": "statement", - "branch": "branch", - "type": "type", - "value": "value", - "condition": "condition", "unit": "unit", - "element": "xmlElement", "tags": "xmlBothTags", - "start tag": "xmlStartTag", + "element": "xmlElement", "end tag": "xmlEndTag", + "start tag": "xmlStartTag", "part": "part", "chapter": "chapter", "subsection": "subSection", @@ -168,7 +198,6 @@ "paragraph": "namedParagraph", "subparagraph": "subParagraph", "environment": "environment", - "command": "command", "char": "character", "sub": "word", "token": "token", @@ -177,12 +206,13 @@ "full line": "fullLine", "sentence": "sentence", "block": "paragraph", + "short block": "boundedParagraph", "file": "document", "paint": "nonWhitespaceSequence", "short paint": "boundedNonWhitespaceSequence", - "short block": "boundedParagraph", "link": "url", - "cell": "notebookCell" + "cell": "notebookCell", + "command": "command" }, "surrounding_pair_scope_type": { "string": "string" @@ -192,8 +222,12 @@ } }, "paired_delimiters.csv": { - "selectable_only_paired_delimiter": { "pair": "any" }, - "wrapper_only_paired_delimiter": { "void": "whitespace" }, + "selectable_only_paired_delimiter": { + "pair": "any" + }, + "wrapper_only_paired_delimiter": { + "void": "whitespace" + }, "wrapper_selectable_paired_delimiter": { "curly": "curlyBrackets", "diamond": "angleBrackets", @@ -215,7 +249,9 @@ "source": "previousSource", "nothing": "nothing" }, - "unknown_symbol": { "special": "unknownSymbol" }, + "unknown_symbol": { + "special": "unknownSymbol" + }, "line_direction": { "row": "lineNumberModulo100", "up": "lineNumberRelativeUp", @@ -223,8 +259,12 @@ } }, "scope_visualizer.csv": { - "show_scope_visualizer": { "visualize": "showScopeVisualizer" }, - "hide_scope_visualizer": { "visualize nothing": "hideScopeVisualizer" }, + "show_scope_visualizer": { + "visualize": "showScopeVisualizer" + }, + "hide_scope_visualizer": { + "visualize nothing": "hideScopeVisualizer" + }, "visualization_type": { "removal": "removal", "iteration": "iteration" diff --git a/packages/tool-meta-updater/src/metaUpdater.ts b/packages/tool-meta-updater/src/metaUpdater.ts index e6e7eb535c..58b3eb37dd 100644 --- a/packages/tool-meta-updater/src/metaUpdater.ts +++ b/packages/tool-meta-updater/src/metaUpdater.ts @@ -25,6 +25,7 @@ import { updatePairedDelimitersMd } from "./updatePairedDelimitersMd"; import { updateReferenceMdx } from "./updateReferenceMdx"; import { updateReferenceReadmeMd } from "./updateReferenceReadmeMd"; import { updateScopeSupportFacetInfos } from "./updateScopeSupportFacetInfos"; +import { updateSpokenForms } from "./updateSpokenForms"; import { updateTSConfig } from "./updateTSConfig"; import { updateTSConfigBase } from "./updateTSConfigBase"; import { cleanId } from "./util/cleanId"; @@ -55,6 +56,7 @@ export const updater = async (workspaceDir: string) => { "tsconfig.base.json": updateTSConfigBase.bind(null, context), "resources/fixtures/scope-support-facet-infos.md": updateScopeSupportFacetInfos, + "cursorless-talon/src/spoken_forms.json": updateSpokenForms, ...Object.fromEntries( languageIds.map((languageId) => [ `${userDir}/languages/${languageId}.mdx`, diff --git a/packages/tool-meta-updater/src/updateSpokenForms.ts b/packages/tool-meta-updater/src/updateSpokenForms.ts new file mode 100644 index 0000000000..66c2d4b32f --- /dev/null +++ b/packages/tool-meta-updater/src/updateSpokenForms.ts @@ -0,0 +1,234 @@ +import type { FormatPluginFnOptions } from "@pnpm/meta-updater"; +import type { DefaultSpokenFormMapEntry } from "@cursorless/lib-common"; +import { + defaultSpokenFormMapCore, + simpleActionNames, +} from "@cursorless/lib-common"; + +type SpokenFormEntries = Record; +type SpokenFormLists = Record; +type DefaultSpokenForms = Readonly< + Record +>; + +const NOTE = + "Please don't edit this json file; see https://www.cursorless.org/docs/user/customization"; + +const actionListIds = { + callback_action: ["nextHomophone"], + paste_action: ["pasteFromClipboard"], + bring_move_action: ["replaceWithTarget", "moveToTarget"], + swap_action: ["swapTargets"], + wrap_action: ["wrapWithPairedDelimiter", "rewrapWithPairedDelimiter"], + insert_snippet_action: ["insertSnippet"], + reformat_action: ["applyFormatter"], + call_action: ["callAsFunction"], +} as const; + +const nonSimpleActionIds = new Set(Object.values(actionListIds).flat()); + +const modifierListIds = { + simple_modifier: [ + "excludeInterior", + "toRawSelection", + "leading", + "trailing", + "keepContentFilter", + "keepEmptyFilter", + "inferPreviousMark", + "visible", + ], + every_scope_modifier: ["everyScope"], + interior_modifier: ["interiorOnly"], + head_tail_modifier: ["extendThroughStartOf", "extendThroughEndOf"], +} as const; + +const modifierExtraListIds = { + ancestor_scope_modifier: ["ancestor"], + first_modifier: ["first"], + last_modifier: ["last"], + previous_next_modifier: ["previous", "next"], + forward_backward_modifier: ["forward", "backward"], +} as const; + +const pairedDelimiterListIds = { + selectable_only_paired_delimiter: ["any"], + wrapper_only_paired_delimiter: ["whitespace"], + wrapper_selectable_paired_delimiter: [ + "curlyBrackets", + "angleBrackets", + "escapedDoubleQuotes", + "escapedSingleQuotes", + "escapedParentheses", + "escapedSquareBrackets", + "doubleQuotes", + "parentheses", + "backtickQuotes", + "squareBrackets", + "singleQuotes", + ], +} as const; + +export function updateSpokenForms( + _actual: object | null, + options: FormatPluginFnOptions, +): object | null { + if (options.manifest.name !== "cursorless") { + return null; + } + + const map = defaultSpokenFormMapCore; + const simpleActionIds = simpleActionNames.filter( + (id) => + id !== "experimental.setInstanceReference" && !nonSimpleActionIds.has(id), + ); + + return { + "NOTE FOR USERS": NOTE, + "actions.csv": { + simple_action: entries( + map.action, + [...simpleActionIds, "generateSnippet", "highlight"], + { sort: true }, + ), + ...lists(map.action, actionListIds, { + rewrapWithPairedDelimiter: "rewrap", + }), + }, + "target_connectives.csv": { + range_connective: entries(map.connective, [ + "rangeExclusive", + "rangeInclusive", + "rangeExcludingStart", + "rangeExcludingEnd", + ]), + list_connective: entries(map.connective, ["listConnective"]), + swap_connective: entries(map.connective, ["swapConnective"]), + insertion_mode_to: entries(map.insertionMode, ["to"], { + idRewrites: { to: "sourceDestinationConnective" }, + }), + }, + "modifiers.csv": { + ...lists(map.simpleModifier, modifierListIds, { + everyScope: "every", + }), + range_type: entries(map.connective, ["verticalRange"]), + ...lists(map.modifierExtra, modifierExtraListIds), + }, + "positions.csv": { + position_modifier: entries(map.simpleModifier, ["startOf", "endOf"], { + idRewrites: { + startOf: "start", + endOf: "end", + }, + }), + insertion_mode_before_after: entries(map.insertionMode, [ + "before", + "after", + ]), + }, + "modifier_scope_types.csv": { + scope_type: entries( + map.simpleScopeTypeType, + Object.keys(map.simpleScopeTypeType).filter((id) => id !== "string"), + ), + surrounding_pair_scope_type: entries(map.pairedDelimiter, ["string"]), + glyph_scope_type: entries(map.complexScopeTypeType, ["glyph"]), + }, + "paired_delimiters.csv": lists(map.pairedDelimiter, pairedDelimiterListIds), + "special_marks.csv": { + simple_mark: entries(map.specialMark, [ + "currentSelection", + "previousTarget", + "previousSource", + "nothing", + ]), + unknown_symbol: entries(map.specialMark, ["unknownSymbol"]), + line_direction: entries(map.specialMark, [ + "lineNumberModulo100", + "lineNumberRelativeUp", + "lineNumberRelativeDown", + ]), + }, + "scope_visualizer.csv": { + show_scope_visualizer: entries(map.scopeVisualizer, [ + "showScopeVisualizer", + ]), + hide_scope_visualizer: entries(map.scopeVisualizer, [ + "hideScopeVisualizer", + ]), + visualization_type: entries(map.scopeVisualizer, [ + "removal", + "iteration", + ]), + }, + "experimental/experimental_actions.csv": { + experimental_action: entries(map.action, [ + "experimental.setInstanceReference", + ]), + }, + "experimental/actions_custom.csv": {}, + "experimental/regex_scope_types.csv": {}, + "hat_styles.csv": { + hat_color: entries(map.hatColor, Object.keys(map.hatColor), { + dontDisableSpokenForms: true, + }), + hat_shape: entries(map.hatShape, Object.keys(map.hatShape), { + dontDisableSpokenForms: true, + }), + }, + }; +} + +function lists( + map: DefaultSpokenForms, + listIds: Readonly>, + idRewrites: Readonly> = {}, +): SpokenFormLists { + return Object.fromEntries( + Object.entries(listIds).map(([listName, ids]) => [ + listName, + entries(map, ids, { idRewrites }), + ]), + ); +} + +function entries( + map: DefaultSpokenForms, + ids: readonly string[], + options: { + dontDisableSpokenForms?: boolean; + sort?: boolean; + idRewrites?: Readonly>; + } = {}, +): SpokenFormEntries { + const entries = ids.flatMap((id) => { + const value = map[id]; + + if (value == null) { + throw new Error(`No default spoken form found for '${id}'`); + } + + if (typeof value !== "string" && value.visibility === "private") { + return []; + } + + const spokenForms = + typeof value === "string" ? [value] : value.defaultSpokenForms; + const prefix = + typeof value === "string" || + value.visibility == null || + options.dontDisableSpokenForms + ? "" + : "-"; + const spokenForm = `${prefix}${spokenForms.join("|")}`; + const resolvedId = options.idRewrites?.[id] ?? id; + return [[spokenForm, resolvedId]]; + }); + + if (options.sort) { + entries.sort((a, b) => a[0].localeCompare(b[0])); + } + + return Object.fromEntries(entries); +}