From 47db860cd48b8b8234567274f843279ba833e18c Mon Sep 17 00:00:00 2001 From: Andreas Arvidsson Date: Sun, 6 Jul 2025 20:57:40 +0200 Subject: [PATCH 1/3] Add common constant with url and a few common utilities --- ...nationAndScopeTypeToInsertSnippetAction.md | 2 +- changelog/2023-09-addedInsertPythonAction.md | 2 +- changelog/2025-07-scopeDocumentation.md | 6 +++ cursorless-talon/docs/customization.md | 2 +- cursorless-talon/src/marks/lines_number.py | 2 +- .../recorded/customRegex/clearWhite.yml | 2 +- .../ordinalScopes/clearFirstPaint.yml | 2 +- .../ordinalScopes/clearFirstPaint2.yml | 2 +- .../recorded/ordinalScopes/clearLastPaint.yml | 2 +- .../ordinalScopes/clearLastPaint2.yml | 2 +- .../selectionTypes/clearCustomRegex.yml | 2 +- .../selectionTypes/clearEveryCustomRegex.yml | 2 +- packages/common/src/constants.ts | 3 ++ packages/common/src/errors.ts | 4 +- packages/common/src/index.ts | 4 +- packages/common/src/types/Position.ts | 19 ++++++++ packages/common/src/types/Range.ts | 19 ++++++++ .../command/PartialTargetDescriptor.types.ts | 2 + .../common/src/util/camelCaseToAllDown.ts | 17 ------- packages/common/src/util/capitalize.ts | 3 -- .../common/src/util/serializeScopeType.ts | 3 +- packages/common/src/util/stringUtils.ts | 44 +++++++++++++++++++ .../generateSpokenForm/generateSpokenForm.ts | 8 ++-- .../src/docs/user/release-notes/0.29.0.md | 2 +- packages/cursorless-org/package.json | 1 + .../src/components/BaseSocial.tsx | 4 +- .../src/components/constants.ts | 5 ++- packages/cursorless-org/tsconfig.json | 3 ++ .../runCustomSpokenFormScopeInfoTest.ts | 15 +++---- .../src/ScopeTreeProvider.ts | 9 ++-- .../VscodeScopeVisualizer.ts | 4 +- packages/node-common/src/getFixturePaths.ts | 16 +++++-- .../src/getScopeTestPathsRecursively.ts | 26 +++++++++++ 33 files changed, 175 insertions(+), 64 deletions(-) create mode 100644 changelog/2025-07-scopeDocumentation.md create mode 100644 packages/common/src/constants.ts delete mode 100644 packages/common/src/util/camelCaseToAllDown.ts delete mode 100644 packages/common/src/util/capitalize.ts create mode 100644 packages/common/src/util/stringUtils.ts diff --git a/changelog/2023-09-addedDestinationAndScopeTypeToInsertSnippetAction.md b/changelog/2023-09-addedDestinationAndScopeTypeToInsertSnippetAction.md index b04b6e9271..da967730ae 100644 --- a/changelog/2023-09-addedDestinationAndScopeTypeToInsertSnippetAction.md +++ b/changelog/2023-09-addedDestinationAndScopeTypeToInsertSnippetAction.md @@ -3,4 +3,4 @@ tags: [enhancement, talon] pullRequest: 1879 --- -- Added `destination: CursorlessDestination` and `scope_type: Optional[Union[str, list[str]]]` arguments to the public Talon api action `user.cursorless_insert_snippet`. See the [talon-side api docs](https://www.cursorless.org/docs/user/customization/#public-talon-actions) for more. +- Added `destination: CursorlessDestination` and `scope_type: Optional[Union[str, list[str]]]` arguments to the public Talon api action `user.cursorless_insert_snippet`. See the [talon-side api docs](https://www.cursorless.org/docs/user/customization#public-talon-actions) for more. diff --git a/changelog/2023-09-addedInsertPythonAction.md b/changelog/2023-09-addedInsertPythonAction.md index 417b0d0c08..584ba42766 100644 --- a/changelog/2023-09-addedInsertPythonAction.md +++ b/changelog/2023-09-addedInsertPythonAction.md @@ -4,4 +4,4 @@ pullRequest: 1875 mergeDate: 2023-09-10 --- -- Added `cursorless_insert` action to the public Talon api. This api enables you to define custom grammars for Cursorless text insertion. See the [talon-side api docs](https://www.cursorless.org/docs/user/customization/#public-talon-actions) for more +- Added `cursorless_insert` action to the public Talon api. This api enables you to define custom grammars for Cursorless text insertion. See the [talon-side api docs](https://www.cursorless.org/docs/user/customization#public-talon-actions) for more diff --git a/changelog/2025-07-scopeDocumentation.md b/changelog/2025-07-scopeDocumentation.md new file mode 100644 index 0000000000..ec0517b4f5 --- /dev/null +++ b/changelog/2025-07-scopeDocumentation.md @@ -0,0 +1,6 @@ +--- +tags: [documentation] +pullRequest: 3016 +--- + +- Visualize scope tests in docs. Visualizes scope fixtures on [cursorless.org/docs/user/languages](https://www.cursorless.org/docs/user/languages). diff --git a/cursorless-talon/docs/customization.md b/cursorless-talon/docs/customization.md index 7fed001ae2..9685472453 100644 --- a/cursorless-talon/docs/customization.md +++ b/cursorless-talon/docs/customization.md @@ -1 +1 @@ -Cursorless is now monorepo 🙌. This document now lives at https://www.cursorless.org/docs/user/customization/. +Cursorless is now monorepo 🙌. This document now lives at https://www.cursorless.org/docs/user/customization. diff --git a/cursorless-talon/src/marks/lines_number.py b/cursorless-talon/src/marks/lines_number.py index 63a550bd96..fb049df360 100644 --- a/cursorless-talon/src/marks/lines_number.py +++ b/cursorless-talon/src/marks/lines_number.py @@ -19,7 +19,7 @@ class CustomizableTerm: # NOTE: Please do not change these dicts. Use the CSVs for customization. -# See https://www.cursorless.org/docs/user/customization/ +# See https://www.cursorless.org/docs/user/customization directions = [ CustomizableTerm("lineNumberModulo100", "modulo100", lambda number: number - 1), CustomizableTerm("lineNumberRelativeUp", "relative", lambda number: -number), diff --git a/data/fixtures/recorded/customRegex/clearWhite.yml b/data/fixtures/recorded/customRegex/clearWhite.yml index 45e5a91cfd..a07b8af5e4 100644 --- a/data/fixtures/recorded/customRegex/clearWhite.yml +++ b/data/fixtures/recorded/customRegex/clearWhite.yml @@ -12,7 +12,7 @@ command: usePrePhraseSnapshot: true spokenFormError: >- custom regex with id \p{Zs}+; please see - https://www.cursorless.org/docs/user/customization/ for more information + https://www.cursorless.org/docs/user/customization for more information initialState: documentContents: "\" \"" selections: diff --git a/data/fixtures/recorded/ordinalScopes/clearFirstPaint.yml b/data/fixtures/recorded/ordinalScopes/clearFirstPaint.yml index d66eb7f055..a6ca311d41 100644 --- a/data/fixtures/recorded/ordinalScopes/clearFirstPaint.yml +++ b/data/fixtures/recorded/ordinalScopes/clearFirstPaint.yml @@ -14,7 +14,7 @@ command: usePrePhraseSnapshot: true spokenFormError: >- custom regex with id [^\s"'`]+; please see - https://www.cursorless.org/docs/user/customization/ for more information + https://www.cursorless.org/docs/user/customization for more information initialState: documentContents: aaa-bbb ccc-ddd eee-fff ggg-hhh selections: diff --git a/data/fixtures/recorded/ordinalScopes/clearFirstPaint2.yml b/data/fixtures/recorded/ordinalScopes/clearFirstPaint2.yml index f68c228242..26a8dde02c 100644 --- a/data/fixtures/recorded/ordinalScopes/clearFirstPaint2.yml +++ b/data/fixtures/recorded/ordinalScopes/clearFirstPaint2.yml @@ -14,7 +14,7 @@ command: usePrePhraseSnapshot: true spokenFormError: >- custom regex with id [^\s"'`]+; please see - https://www.cursorless.org/docs/user/customization/ for more information + https://www.cursorless.org/docs/user/customization for more information initialState: documentContents: aaa-bbb ccc-ddd eee-fff ggg-hhh selections: diff --git a/data/fixtures/recorded/ordinalScopes/clearLastPaint.yml b/data/fixtures/recorded/ordinalScopes/clearLastPaint.yml index d4341f7a14..1d92998b24 100644 --- a/data/fixtures/recorded/ordinalScopes/clearLastPaint.yml +++ b/data/fixtures/recorded/ordinalScopes/clearLastPaint.yml @@ -14,7 +14,7 @@ command: usePrePhraseSnapshot: true spokenFormError: >- custom regex with id [^\s"'`]+; please see - https://www.cursorless.org/docs/user/customization/ for more information + https://www.cursorless.org/docs/user/customization for more information initialState: documentContents: aaa-bbb ccc-ddd eee-fff ggg-hhh selections: diff --git a/data/fixtures/recorded/ordinalScopes/clearLastPaint2.yml b/data/fixtures/recorded/ordinalScopes/clearLastPaint2.yml index e9c57ec1e8..57748da226 100644 --- a/data/fixtures/recorded/ordinalScopes/clearLastPaint2.yml +++ b/data/fixtures/recorded/ordinalScopes/clearLastPaint2.yml @@ -14,7 +14,7 @@ command: usePrePhraseSnapshot: true spokenFormError: >- custom regex with id [^\s"'`]+; please see - https://www.cursorless.org/docs/user/customization/ for more information + https://www.cursorless.org/docs/user/customization for more information initialState: documentContents: aaa-bbb ccc-ddd eee-fff ggg-hhh selections: diff --git a/data/fixtures/recorded/selectionTypes/clearCustomRegex.yml b/data/fixtures/recorded/selectionTypes/clearCustomRegex.yml index 927846b2bb..efbc8c1f62 100644 --- a/data/fixtures/recorded/selectionTypes/clearCustomRegex.yml +++ b/data/fixtures/recorded/selectionTypes/clearCustomRegex.yml @@ -12,7 +12,7 @@ command: usePrePhraseSnapshot: true spokenFormError: >- custom regex with id [\w/_.]+; please see - https://www.cursorless.org/docs/user/customization/ for more information + https://www.cursorless.org/docs/user/customization for more information initialState: documentContents: aa.bb/cc_dd123( ) selections: diff --git a/data/fixtures/recorded/selectionTypes/clearEveryCustomRegex.yml b/data/fixtures/recorded/selectionTypes/clearEveryCustomRegex.yml index 0a7d26968b..fbec1e1fa4 100644 --- a/data/fixtures/recorded/selectionTypes/clearEveryCustomRegex.yml +++ b/data/fixtures/recorded/selectionTypes/clearEveryCustomRegex.yml @@ -12,7 +12,7 @@ command: usePrePhraseSnapshot: true spokenFormError: >- custom regex with id [\w/_.]+; please see - https://www.cursorless.org/docs/user/customization/ for more information + https://www.cursorless.org/docs/user/customization for more information initialState: documentContents: aa.bb/cc_dd123 aa.bb/cc_dd123( ) selections: diff --git a/packages/common/src/constants.ts b/packages/common/src/constants.ts new file mode 100644 index 0000000000..5b07f3745a --- /dev/null +++ b/packages/common/src/constants.ts @@ -0,0 +1,3 @@ +export const CURSORLESS_ORG_URL = "https://www.cursorless.org"; +export const DOCS_URL = `${CURSORLESS_ORG_URL}/docs`; +export const GITHUB_URL = "https://github.com/cursorless-dev/cursorless"; diff --git a/packages/common/src/errors.ts b/packages/common/src/errors.ts index 800eff6ba6..3236fc5ed6 100644 --- a/packages/common/src/errors.ts +++ b/packages/common/src/errors.ts @@ -1,7 +1,9 @@ +import { DOCS_URL } from "./constants"; + export class UnsupportedLanguageError extends Error { constructor(languageId: string) { super( - `Language '${languageId}' is not implemented yet; See https://www.cursorless.org/docs/contributing/adding-a-new-language/`, + `Language '${languageId}' is not implemented yet; See ${DOCS_URL}/contributing/adding-a-new-language`, ); this.name = "UnsupportedLanguageError"; } diff --git a/packages/common/src/index.ts b/packages/common/src/index.ts index d09a33453c..a3194abfba 100644 --- a/packages/common/src/index.ts +++ b/packages/common/src/index.ts @@ -1,3 +1,4 @@ +export * from "./constants"; export * from "./cursorlessCommandIds"; export * from "./cursorlessSideBarIds"; export * from "./Debouncer"; @@ -92,8 +93,6 @@ export * from "./types/Token"; export * from "./types/TreeSitter"; export * from "./types/tutorial.types"; export * from "./util"; -export * from "./util/camelCaseToAllDown"; -export * from "./util/capitalize"; export * from "./util/clientSupportsFallback"; export * from "./util/CompositeKeyDefaultMap"; export * from "./util/CompositeKeyMap"; @@ -111,6 +110,7 @@ export * from "./util/selectionsEqual"; export * from "./util/serializedMarksToTokenHats"; export * from "./util/serializeScopeType"; export * from "./util/splitKey"; +export * from "./util/stringUtils"; export * from "./util/toPlainObject"; export * from "./util/type"; export * from "./util/typeUtils"; diff --git a/packages/common/src/types/Position.ts b/packages/common/src/types/Position.ts index c4b25bc076..04f2d2c471 100644 --- a/packages/common/src/types/Position.ts +++ b/packages/common/src/types/Position.ts @@ -12,6 +12,25 @@ export class Position { */ public readonly character: number; + /** + * Create a position from a concise string representation. + * The string should be in the format `line:character`, where both line and character + * are zero-based. + * + * @param concise A concise string representation of a position. + * @return A position with the given line and character values. + */ + static fromConcise(concise: string): Position { + const parts = concise.split(":"); + if (parts.length !== 2) { + throw new Error( + `Invalid concise position format: "${concise}". Expected "line:character" format.`, + ); + } + const [line, character] = parts.map((s) => parseInt(s, 10)); + return new Position(line, character); + } + /** * @param line A zero-based line value. * @param character A zero-based character value. diff --git a/packages/common/src/types/Range.ts b/packages/common/src/types/Range.ts index 02aa3a6ea9..3939151ac9 100644 --- a/packages/common/src/types/Range.ts +++ b/packages/common/src/types/Range.ts @@ -11,6 +11,25 @@ export class Range { */ readonly end: Position; + /** + * Create a new range from a concise string representation. + * The string should be in the format `start-end`, where both start and end + * are in the format `line:character`, where both line and character are zero-based. + * + * @param concise A concise string representation of a range. + * @return A range with the given start and end positions. + */ + static fromConcise(concise: string): Range { + const parts = concise.split("-"); + if (parts.length !== 2) { + throw new Error( + `Invalid concise range format: "${concise}". Expected "start-end" format.`, + ); + } + const [start, end] = parts.map((s) => Position.fromConcise(s)); + return new Range(start, end); + } + /** * Create a new range from two positions. * The earlier of `p1` and `p2` will be used as the start position. diff --git a/packages/common/src/types/command/PartialTargetDescriptor.types.ts b/packages/common/src/types/command/PartialTargetDescriptor.types.ts index ef59a14e1c..1cbc2942ef 100644 --- a/packages/common/src/types/command/PartialTargetDescriptor.types.ts +++ b/packages/common/src/types/command/PartialTargetDescriptor.types.ts @@ -222,6 +222,8 @@ export interface SimpleScopeType { type: SimpleScopeTypeType; } +export type ScopeTypeType = SimpleScopeTypeType | ScopeType["type"]; + export interface CustomRegexScopeType { type: "customRegex"; regex: string; diff --git a/packages/common/src/util/camelCaseToAllDown.ts b/packages/common/src/util/camelCaseToAllDown.ts deleted file mode 100644 index 0417ff2e79..0000000000 --- a/packages/common/src/util/camelCaseToAllDown.ts +++ /dev/null @@ -1,17 +0,0 @@ -/** - * Converts a camelCase string to a string with spaces between each word, and - * all words in lowercase. - * - * Example: `camelCaseToAllDown("fooBarBaz")` returns `"foo bar baz"`. - * - * @param input A camelCase string - * @returns The same string, but with spaces between each word, and all words - * in lowercase - */ -export function camelCaseToAllDown(input: string): string { - return input - .replace(/([A-Z])/g, " $1") - .split(" ") - .map((word) => word.toLowerCase()) - .join(" "); -} diff --git a/packages/common/src/util/capitalize.ts b/packages/common/src/util/capitalize.ts deleted file mode 100644 index 7519095d07..0000000000 --- a/packages/common/src/util/capitalize.ts +++ /dev/null @@ -1,3 +0,0 @@ -export function capitalize(str: string) { - return str.charAt(0).toUpperCase() + str.slice(1); -} diff --git a/packages/common/src/util/serializeScopeType.ts b/packages/common/src/util/serializeScopeType.ts index a1b120fb0c..7fe5e16ad1 100644 --- a/packages/common/src/util/serializeScopeType.ts +++ b/packages/common/src/util/serializeScopeType.ts @@ -1,11 +1,12 @@ import type { ScopeType, + ScopeTypeType, SimpleScopeTypeType, } from "../types/command/PartialTargetDescriptor.types"; export function serializeScopeType( scopeType: SimpleScopeTypeType | ScopeType, -): string { +): ScopeTypeType { if (typeof scopeType === "string") { return scopeType; } diff --git a/packages/common/src/util/stringUtils.ts b/packages/common/src/util/stringUtils.ts new file mode 100644 index 0000000000..037e182bc1 --- /dev/null +++ b/packages/common/src/util/stringUtils.ts @@ -0,0 +1,44 @@ +/** + * Converts a camelCase string to a string with spaces between each word, and + * all words in lowercase. + * + * Example: `camelCaseToAllDown("fooBarBaz")` returns `"foo bar baz"`. + * + * @param input A camelCase string + * @returns The same string, but with spaces between each word, and all words + * in lowercase + */ +export function camelCaseToAllDown(input: string): string { + return input + .replace(/([A-Z])/g, " $1") + .split(" ") + .map((word) => word.toLowerCase()) + .join(" "); +} + +/** + * Capitalizes string + * + * @param input A string + * @returns The same string, but with the first character capitalized + */ +export function capitalize(str: string) { + return str.charAt(0).toUpperCase() + str.slice(1); +} + +/** + * Converts a string to a URL-friendly hash ID. + * + * This function takes an input string, converts it to lowercase, replaces spaces + * with hyphens, and removes any characters that are not lowercase letters, + * digits, or hyphens. The result is a string that can be used as a + * URL-friendly hash ID. + * + * @param text The input string to be converted + * @returns A URL-friendly hash ID + */ +export function uriEncodeHashId(text: string): string { + return camelCaseToAllDown(text) + .replaceAll(" ", "-") + .replace(/[^a-z0-9-]/g, ""); +} diff --git a/packages/cursorless-engine/src/generateSpokenForm/generateSpokenForm.ts b/packages/cursorless-engine/src/generateSpokenForm/generateSpokenForm.ts index 4af33a89ad..001b2480b9 100644 --- a/packages/cursorless-engine/src/generateSpokenForm/generateSpokenForm.ts +++ b/packages/cursorless-engine/src/generateSpokenForm/generateSpokenForm.ts @@ -9,7 +9,7 @@ import type { SpokenFormMapKeyTypes, SpokenFormType, } from "@cursorless/common"; -import { camelCaseToAllDown } from "@cursorless/common"; +import { camelCaseToAllDown, DOCS_URL } from "@cursorless/common"; import type { SpokenFormMap } from "../spokenForms/SpokenFormMap"; import { NoSpokenFormError } from "./NoSpokenFormError"; import type { SpokenFormComponent } from "./SpokenFormComponent"; @@ -310,11 +310,9 @@ function constructSpokenForms(component: SpokenFormComponent): string[] { helpInfo = "this is a private spoken form currently only for internal experimentation"; } else if (component.spokenForms.requiresTalonUpdate) { - helpInfo = - "please update talon to the latest version (see https://www.cursorless.org/docs/user/updating/)"; + helpInfo = `please update talon to the latest version (see ${DOCS_URL}/user/updating)`; } else { - helpInfo = - "please see https://www.cursorless.org/docs/user/customization/ for more information"; + helpInfo = `please see ${DOCS_URL}/user/customization for more information`; } throw new NoSpokenFormError( diff --git a/packages/cursorless-org-docs/src/docs/user/release-notes/0.29.0.md b/packages/cursorless-org-docs/src/docs/user/release-notes/0.29.0.md index c8e3424ee2..aafaf1adfb 100644 --- a/packages/cursorless-org-docs/src/docs/user/release-notes/0.29.0.md +++ b/packages/cursorless-org-docs/src/docs/user/release-notes/0.29.0.md @@ -50,7 +50,7 @@ ICYMI Cursorless went to Strange Loop. The talk was so wild that they had to clo ### Improved Talon-side API -We beefed up our Talon-side API to allow you to build your own grammars on top of Cursorless. See the [talon-side api docs](https://www.cursorless.org/docs/user/customization/#public-talon-actions) for more. +We beefed up our Talon-side API to allow you to build your own grammars on top of Cursorless. See the [talon-side api docs](https://www.cursorless.org/docs/user/customization#public-talon-actions) for more. We also have an undocumented, secret action that has a built-in parser for building arbitrarily powerful custom commands on the fly. You should also definitely not heckle @AndreasArvidsson and @phillco to tell you how to use this one either. diff --git a/packages/cursorless-org/package.json b/packages/cursorless-org/package.json index b7f61f4f4f..2100adb921 100644 --- a/packages/cursorless-org/package.json +++ b/packages/cursorless-org/package.json @@ -30,6 +30,7 @@ }, "dependencies": { "@cursorless/cheatsheet": "workspace:*", + "@cursorless/common": "workspace:*", "@mdx-js/loader": "3.1.0", "@mdx-js/react": "3.1.0", "@next/mdx": "15.3.3", diff --git a/packages/cursorless-org/src/components/BaseSocial.tsx b/packages/cursorless-org/src/components/BaseSocial.tsx index b349d5f572..757ed1f25f 100644 --- a/packages/cursorless-org/src/components/BaseSocial.tsx +++ b/packages/cursorless-org/src/components/BaseSocial.tsx @@ -1,5 +1,5 @@ +import { CURSORLESS_ORG_URL } from "@cursorless/common"; import { - BASE_URL, VIDEO_SHARE_THUMBNAIL_HEIGHT, VIDEO_SHARE_THUMBNAIL_URL, VIDEO_SHARE_THUMBNAIL_WIDTH, @@ -24,7 +24,7 @@ export default function BaseSocial({ thumbnailWidth = VIDEO_SHARE_THUMBNAIL_WIDTH, thumbnailHeight = VIDEO_SHARE_THUMBNAIL_HEIGHT, }: Props) { - const url = `${BASE_URL}/${relativeUrl}`; + const url = `${CURSORLESS_ORG_URL}/${relativeUrl}`; return ( <> diff --git a/packages/cursorless-org/src/components/constants.ts b/packages/cursorless-org/src/components/constants.ts index 41e6cec2fb..811b3a5c07 100644 --- a/packages/cursorless-org/src/components/constants.ts +++ b/packages/cursorless-org/src/components/constants.ts @@ -1,7 +1,8 @@ +import { CURSORLESS_ORG_URL } from "@cursorless/common"; + export const DESCRIPTION = "Voice coding at the speed of thought"; export const TITLE = `Cursorless: ${DESCRIPTION}`; -export const BASE_URL = "https://cursorless.org/"; -export const VIDEO_SHARE_THUMBNAIL_URL = `${BASE_URL}video-share-thumbnail.jpg`; +export const VIDEO_SHARE_THUMBNAIL_URL = `${CURSORLESS_ORG_URL}/video-share-thumbnail.jpg`; export const VIDEO_SHARE_THUMBNAIL_WIDTH = "1280"; export const VIDEO_SHARE_THUMBNAIL_HEIGHT = "720"; export const YOUTUBE_SLUG = "5mAzHGM2M0k"; diff --git a/packages/cursorless-org/tsconfig.json b/packages/cursorless-org/tsconfig.json index 2ce4454b4f..569aece321 100644 --- a/packages/cursorless-org/tsconfig.json +++ b/packages/cursorless-org/tsconfig.json @@ -23,6 +23,9 @@ "references": [ { "path": "../cheatsheet" + }, + { + "path": "../common" } ], "exclude": ["node_modules"] diff --git a/packages/cursorless-vscode-e2e/src/suite/scopeProvider/runCustomSpokenFormScopeInfoTest.ts b/packages/cursorless-vscode-e2e/src/suite/scopeProvider/runCustomSpokenFormScopeInfoTest.ts index 88c846da81..9bdd2b932f 100644 --- a/packages/cursorless-vscode-e2e/src/suite/scopeProvider/runCustomSpokenFormScopeInfoTest.ts +++ b/packages/cursorless-vscode-e2e/src/suite/scopeProvider/runCustomSpokenFormScopeInfoTest.ts @@ -1,9 +1,9 @@ -import { getCursorlessApi } from "@cursorless/vscode-common"; import type { ScopeTypeInfo } from "@cursorless/common"; -import { sleep } from "@cursorless/common"; +import { DOCS_URL, sleep } from "@cursorless/common"; +import { getCursorlessApi } from "@cursorless/vscode-common"; +import { stat, unlink, writeFile } from "fs/promises"; import * as sinon from "sinon"; import { assertCalledWithScopeInfo } from "./assertCalledWithScopeInfo"; -import { stat, unlink, writeFile } from "fs/promises"; /** * Tests that the scope provider correctly reports custom spoken forms @@ -152,8 +152,7 @@ const squareMissing: ScopeTypeInfo = { scopeType: { type: "surroundingPair", delimiter: "squareBrackets" }, spokenForm: { isPrivate: false, - reason: - "paired delimiter with id squareBrackets; please update talon to the latest version (see https://www.cursorless.org/docs/user/updating/)", + reason: `paired delimiter with id squareBrackets; please update talon to the latest version (see ${DOCS_URL}/user/updating)`, requiresTalonUpdate: true, type: "error", }, @@ -195,8 +194,7 @@ const lambdaCustom: ScopeTypeInfo = { scopeType: { type: "anonymousFunction" }, spokenForm: { isPrivate: false, - reason: - "simple scope type type with id anonymousFunction; please see https://www.cursorless.org/docs/user/customization/ for more information", + reason: `simple scope type type with id anonymousFunction; please see ${DOCS_URL}/user/customization for more information`, requiresTalonUpdate: false, type: "error", }, @@ -218,8 +216,7 @@ const statementMissing: ScopeTypeInfo = { scopeType: { type: "statement" }, spokenForm: { isPrivate: false, - reason: - "simple scope type type with id statement; please update talon to the latest version (see https://www.cursorless.org/docs/user/updating/)", + reason: `simple scope type type with id statement; please update talon to the latest version (see ${DOCS_URL}/user/updating)`, requiresTalonUpdate: true, type: "error", }, diff --git a/packages/cursorless-vscode/src/ScopeTreeProvider.ts b/packages/cursorless-vscode/src/ScopeTreeProvider.ts index 730f4453cc..bf0b2fb3fe 100644 --- a/packages/cursorless-vscode/src/ScopeTreeProvider.ts +++ b/packages/cursorless-vscode/src/ScopeTreeProvider.ts @@ -6,6 +6,7 @@ import type { } from "@cursorless/common"; import { CURSORLESS_SCOPE_TREE_VIEW_ID, + DOCS_URL, ScopeSupport, disposableFrom, } from "@cursorless/common"; @@ -144,9 +145,7 @@ export class ScopeTreeProvider implements TreeDataProvider { if (result === HOW_BUTTON_TEXT) { await this.vscodeApi.env.openExternal( - URI.parse( - "https://www.cursorless.org/docs/user/updating/#updating-the-talon-side", - ), + URI.parse(`${DOCS_URL}/user/updating/#updating-the-talon-side`), ); } else if (result === DONT_SHOW_AGAIN_BUTTON_TEXT) { await this.context.globalState.update( @@ -235,8 +234,8 @@ class ScopeSupportTreeItem extends TreeItem { } else { label = "-"; tooltip = scopeTypeInfo.spokenForm.requiresTalonUpdate - ? "Requires Talon update; see [update instructions](https://www.cursorless.org/docs/user/updating/#updating-the-talon-side)" - : "Spoken form disabled; see [customization docs](https://www.cursorless.org/docs/user/customization/#talon-side-settings)"; + ? `Requires Talon update; see [update instructions](${DOCS_URL}/user/updating/#updating-the-talon-side)` + : `Spoken form disabled; see [customization docs](${DOCS_URL}/user/customization/#talon-side-settings)`; } super( diff --git a/packages/cursorless-vscode/src/ide/vscode/VSCodeScopeVisualizer/VscodeScopeVisualizer.ts b/packages/cursorless-vscode/src/ide/vscode/VSCodeScopeVisualizer/VscodeScopeVisualizer.ts index 743b404870..efe96c50b9 100644 --- a/packages/cursorless-vscode/src/ide/vscode/VSCodeScopeVisualizer/VscodeScopeVisualizer.ts +++ b/packages/cursorless-vscode/src/ide/vscode/VSCodeScopeVisualizer/VscodeScopeVisualizer.ts @@ -5,7 +5,7 @@ import type { ScopeType, TextEditor, } from "@cursorless/common"; -import { ScopeSupport, showError } from "@cursorless/common"; +import { DOCS_URL, ScopeSupport, showError } from "@cursorless/common"; import type { ScopeRangeType, ScopeVisualizerColorConfig, @@ -67,7 +67,7 @@ export abstract class VscodeScopeVisualizer { void showError( this.ide.messages, "ScopeVisualizer.scopeTypeNotSupported", - `Scope type not supported for ${editor.document.languageId}, or only defined using legacy API which doesn't support visualization. See https://www.cursorless.org/docs/contributing/adding-a-new-language/ for more about how to upgrade your language.`, + `Scope type not supported for ${editor.document.languageId}, or only defined using legacy API which doesn't support visualization. See ${DOCS_URL}/contributing/adding-a-new-language for more about how to upgrade your language.`, ); } } diff --git a/packages/node-common/src/getFixturePaths.ts b/packages/node-common/src/getFixturePaths.ts index 88e4e1ca98..b88327b5fe 100644 --- a/packages/node-common/src/getFixturePaths.ts +++ b/packages/node-common/src/getFixturePaths.ts @@ -1,11 +1,19 @@ +import type { + PlaintextScopeSupportFacet, + ScopeSupportFacet, +} from "@cursorless/common"; +import { getCursorlessRepoRoot } from "@cursorless/node-common"; import * as path from "path"; import { walkFilesSync } from "./walkSync"; -import { getCursorlessRepoRoot } from "@cursorless/node-common"; export function getFixturesPath() { return path.join(getCursorlessRepoRoot(), "data", "fixtures"); } +export function getPackagePath(name: string) { + return path.join(getCursorlessRepoRoot(), "packages", name); +} + export function getFixturePath(fixturePath: string) { return path.join(getFixturesPath(), fixturePath); } @@ -34,7 +42,7 @@ export interface ScopeTestPath { path: string; name: string; languageId: string; - facet: string; + facet: ScopeSupportFacet | PlaintextScopeSupportFacet; } export function getScopeTestPaths(): ScopeTestPath[] { @@ -47,7 +55,9 @@ export function getScopeTestPaths(): ScopeTestPath[] { path: p, name: pathToName(relativeDir, p), languageId: path.dirname(path.relative(directory, p)).split(path.sep)[0], - facet: path.basename(p).match(/([a-zA-Z.]+)\d*\.scope/)![1], + facet: path.basename(p).match(/([a-zA-Z.]+)\d*\.scope/)![1] as + | ScopeSupportFacet + | PlaintextScopeSupportFacet, })); } diff --git a/packages/node-common/src/getScopeTestPathsRecursively.ts b/packages/node-common/src/getScopeTestPathsRecursively.ts index a3ed7de22c..645e437ce3 100644 --- a/packages/node-common/src/getScopeTestPathsRecursively.ts +++ b/packages/node-common/src/getScopeTestPathsRecursively.ts @@ -57,6 +57,32 @@ export function getScopeTestPathsRecursively(): ScopeTestPath[] { return result; } +export function getScopeTestLanguagesRecursively(): Record { + const configPaths = getScopeTestConfigPaths(); + const configs = readConfigFiles(configPaths); + const result: Record = {}; + + function add(languageId: string, importLanguageId: string): void { + if (result[languageId] == null) { + result[languageId] = [languageId]; + } + if (!result[languageId].includes(importLanguageId)) { + result[languageId].push(importLanguageId); + const config = configs[importLanguageId]; + config?.imports?.forEach((lang) => add(languageId, lang)); + } + } + + for (const [languageId, config] of Object.entries(configs)) { + if (config.skip) { + continue; + } + config.imports?.forEach((lang) => add(languageId, lang)); + } + + return result; +} + function addTestPathsForLanguageRecursively( languages: Record, configs: Record, From 979808012d185834583975617f889cb1e9636c8c Mon Sep 17 00:00:00 2001 From: Andreas Arvidsson Date: Sun, 6 Jul 2025 20:58:37 +0200 Subject: [PATCH 2/3] pnpm install --- pnpm-lock.yaml | 3 +++ 1 file changed, 3 insertions(+) diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml index a43ca6adcc..7f6cfbee41 100644 --- a/pnpm-lock.yaml +++ b/pnpm-lock.yaml @@ -460,6 +460,9 @@ importers: '@cursorless/cheatsheet': specifier: workspace:* version: link:../cheatsheet + '@cursorless/common': + specifier: workspace:* + version: link:../common '@mdx-js/loader': specifier: 3.1.0 version: 3.1.0(webpack@5.99.9(esbuild@0.25.5)) From aaf6c218166819f6437b385c5ea1fb822af8bc75 Mon Sep 17 00:00:00 2001 From: Andreas Arvidsson Date: Sun, 6 Jul 2025 21:00:40 +0200 Subject: [PATCH 3/3] Remove changelog --- changelog/2025-07-scopeDocumentation.md | 6 ------ 1 file changed, 6 deletions(-) delete mode 100644 changelog/2025-07-scopeDocumentation.md diff --git a/changelog/2025-07-scopeDocumentation.md b/changelog/2025-07-scopeDocumentation.md deleted file mode 100644 index ec0517b4f5..0000000000 --- a/changelog/2025-07-scopeDocumentation.md +++ /dev/null @@ -1,6 +0,0 @@ ---- -tags: [documentation] -pullRequest: 3016 ---- - -- Visualize scope tests in docs. Visualizes scope fixtures on [cursorless.org/docs/user/languages](https://www.cursorless.org/docs/user/languages).