From 3b7ccef0694bbda1166b1468e48ab461ba587ce4 Mon Sep 17 00:00:00 2001 From: Pokey Rule <755842+pokey@users.noreply.github.com> Date: Thu, 6 Apr 2023 15:09:09 +0100 Subject: [PATCH 01/25] Introduce tree-sitter queries for syntactic scopes --- .../command/PartialTargetDescriptor.types.ts | 115 +++--- .../src/core/commandRunner/CommandRunner.ts | 1 + .../cursorless-engine/src/languages/ruby.ts | 10 +- .../scopeHandlers/ScopeHandlerFactoryImpl.ts | 9 +- .../TreeSitterScopeHandler.ts | 136 +++++++ .../TreeSitterScopeHandler/index.ts | 2 + .../queryBasedSpecification.ts | 42 +++ .../queryNodeMatchers.ts | 336 ++++++++++++++++++ .../modifiers/scopeHandlers/index.ts | 2 + .../src/typings/TreeSitter.ts | 7 +- .../cursorless-engine/src/typings/Types.ts | 3 +- .../src/util/selectionUtils.ts | 2 +- .../languages/typescript/changeCall.yml | 23 ++ .../recorded/ordinalScopes/clearFirstFunk.yml | 33 ++ .../recorded/ordinalScopes/clearLastFunk.yml | 33 ++ .../queryBasedMatchers/changeList.yml | 27 ++ .../queryBasedMatchers/changeList10.yml | 23 ++ .../queryBasedMatchers/changeList11.yml | 23 ++ .../queryBasedMatchers/changeList12.yml | 23 ++ .../queryBasedMatchers/changeList13.yml | 26 ++ .../queryBasedMatchers/changeList14.yml | 26 ++ .../queryBasedMatchers/changeList2.yml | 23 ++ .../queryBasedMatchers/changeList3.yml | 23 ++ .../queryBasedMatchers/changeList4.yml | 23 ++ .../queryBasedMatchers/changeList5.yml | 23 ++ .../queryBasedMatchers/changeList6.yml | 23 ++ .../queryBasedMatchers/changeList7.yml | 23 ++ .../queryBasedMatchers/changeList8.yml | 23 ++ .../queryBasedMatchers/changeList9.yml | 23 ++ .../src/suite/queryBasedSpecification.test.ts | 3 + .../src/suite/queryNodeMatchers.test.ts | 161 +++++++++ packages/cursorless-vscode/src/extension.ts | 4 + packages/vscode-common/src/getExtensionApi.ts | 3 +- queries/html/scopeTypes.scm | 15 + queries/ruby/scopeTypes.scm | 15 + queries/typescript/scopeTypes.scm | 12 + typings/treeSitter.d.ts | 3 +- 37 files changed, 1228 insertions(+), 74 deletions(-) create mode 100644 packages/cursorless-engine/src/processTargets/modifiers/scopeHandlers/TreeSitterScopeHandler/TreeSitterScopeHandler.ts create mode 100644 packages/cursorless-engine/src/processTargets/modifiers/scopeHandlers/TreeSitterScopeHandler/index.ts create mode 100644 packages/cursorless-engine/src/processTargets/modifiers/scopeHandlers/TreeSitterScopeHandler/queryBasedSpecification.ts create mode 100644 packages/cursorless-engine/src/processTargets/modifiers/scopeHandlers/TreeSitterScopeHandler/queryNodeMatchers.ts create mode 100644 packages/cursorless-vscode-e2e/src/suite/fixtures/recorded/languages/typescript/changeCall.yml create mode 100644 packages/cursorless-vscode-e2e/src/suite/fixtures/recorded/ordinalScopes/clearFirstFunk.yml create mode 100644 packages/cursorless-vscode-e2e/src/suite/fixtures/recorded/ordinalScopes/clearLastFunk.yml create mode 100644 packages/cursorless-vscode-e2e/src/suite/fixtures/recorded/queryBasedMatchers/changeList.yml create mode 100644 packages/cursorless-vscode-e2e/src/suite/fixtures/recorded/queryBasedMatchers/changeList10.yml create mode 100644 packages/cursorless-vscode-e2e/src/suite/fixtures/recorded/queryBasedMatchers/changeList11.yml create mode 100644 packages/cursorless-vscode-e2e/src/suite/fixtures/recorded/queryBasedMatchers/changeList12.yml create mode 100644 packages/cursorless-vscode-e2e/src/suite/fixtures/recorded/queryBasedMatchers/changeList13.yml create mode 100644 packages/cursorless-vscode-e2e/src/suite/fixtures/recorded/queryBasedMatchers/changeList14.yml create mode 100644 packages/cursorless-vscode-e2e/src/suite/fixtures/recorded/queryBasedMatchers/changeList2.yml create mode 100644 packages/cursorless-vscode-e2e/src/suite/fixtures/recorded/queryBasedMatchers/changeList3.yml create mode 100644 packages/cursorless-vscode-e2e/src/suite/fixtures/recorded/queryBasedMatchers/changeList4.yml create mode 100644 packages/cursorless-vscode-e2e/src/suite/fixtures/recorded/queryBasedMatchers/changeList5.yml create mode 100644 packages/cursorless-vscode-e2e/src/suite/fixtures/recorded/queryBasedMatchers/changeList6.yml create mode 100644 packages/cursorless-vscode-e2e/src/suite/fixtures/recorded/queryBasedMatchers/changeList7.yml create mode 100644 packages/cursorless-vscode-e2e/src/suite/fixtures/recorded/queryBasedMatchers/changeList8.yml create mode 100644 packages/cursorless-vscode-e2e/src/suite/fixtures/recorded/queryBasedMatchers/changeList9.yml create mode 100644 packages/cursorless-vscode-e2e/src/suite/queryBasedSpecification.test.ts create mode 100644 packages/cursorless-vscode-e2e/src/suite/queryNodeMatchers.test.ts create mode 100644 queries/html/scopeTypes.scm create mode 100644 queries/ruby/scopeTypes.scm create mode 100644 queries/typescript/scopeTypes.scm diff --git a/packages/common/src/types/command/PartialTargetDescriptor.types.ts b/packages/common/src/types/command/PartialTargetDescriptor.types.ts index cfe2ad514f..cc2f9ce346 100644 --- a/packages/common/src/types/command/PartialTargetDescriptor.types.ts +++ b/packages/common/src/types/command/PartialTargetDescriptor.types.ts @@ -72,64 +72,67 @@ export type SurroundingPairName = | SimpleSurroundingPairName | ComplexSurroundingPairName; -export type SimpleScopeTypeType = - | "argumentOrParameter" - | "anonymousFunction" - | "attribute" - | "branch" - | "class" - | "className" - | "collectionItem" - | "collectionKey" - | "comment" - | "functionCall" - | "functionCallee" - | "functionName" - | "ifStatement" - | "list" - | "map" - | "name" - | "namedFunction" - | "regularExpression" - | "statement" - | "string" - | "type" - | "value" - | "condition" - | "section" - | "sectionLevelOne" - | "sectionLevelTwo" - | "sectionLevelThree" - | "sectionLevelFour" - | "sectionLevelFive" - | "sectionLevelSix" - | "selector" - | "switchStatementSubject" - | "unit" - | "xmlBothTags" - | "xmlElement" - | "xmlEndTag" - | "xmlStartTag" +export const simpleScopeTypeTypes = [ + "argumentOrParameter", + "anonymousFunction", + "attribute", + "branch", + "class", + "className", + "collectionItem", + "collectionKey", + "comment", + "functionCall", + "functionCallee", + "functionName", + "ifStatement", + "list", + "map", + "name", + "namedFunction", + "regularExpression", + "statement", + "string", + "type", + "value", + "condition", + "section", + "sectionLevelOne", + "sectionLevelTwo", + "sectionLevelThree", + "sectionLevelFour", + "sectionLevelFive", + "sectionLevelSix", + "selector", + "switchStatementSubject", + "unit", + "xmlBothTags", + "xmlElement", + "xmlEndTag", + "xmlStartTag", // Latex scope types - | "part" - | "chapter" - | "subSection" - | "subSubSection" - | "namedParagraph" - | "subParagraph" - | "environment" + "part", + "chapter", + "subSection", + "subSubSection", + "namedParagraph", + "subParagraph", + "environment", // Text based scopes - | "token" - | "line" - | "notebookCell" - | "paragraph" - | "document" - | "character" - | "word" - | "identifier" - | "nonWhitespaceSequence" - | "boundedNonWhitespaceSequence" - | "url"; + "token", + "line", + "notebookCell", + "paragraph", + "document", + "character", + "word", + "identifier", + "nonWhitespaceSequence", + "boundedNonWhitespaceSequence", + "url", +] as const; + +export type SimpleScopeTypeType = typeof simpleScopeTypeTypes[number]; export interface SimpleScopeType { type: SimpleScopeTypeType; diff --git a/packages/cursorless-engine/src/core/commandRunner/CommandRunner.ts b/packages/cursorless-engine/src/core/commandRunner/CommandRunner.ts index c0ce14c4bd..8962ce0952 100644 --- a/packages/cursorless-engine/src/core/commandRunner/CommandRunner.ts +++ b/packages/cursorless-engine/src/core/commandRunner/CommandRunner.ts @@ -123,6 +123,7 @@ export class CommandRunner { thatMark: this.thatMark.exists() ? this.thatMark.get() : [], sourceMark: this.sourceMark.exists() ? this.sourceMark.get() : [], getNodeAtLocation: this.treeSitter.getNodeAtLocation, + getTree: this.treeSitter.getTree, }; if (this.testCaseRecorder.isActive()) { diff --git a/packages/cursorless-engine/src/languages/ruby.ts b/packages/cursorless-engine/src/languages/ruby.ts index 56a73715fa..2b3c89ce0e 100644 --- a/packages/cursorless-engine/src/languages/ruby.ts +++ b/packages/cursorless-engine/src/languages/ruby.ts @@ -151,8 +151,6 @@ function blockFinder(node: SyntaxNode) { const nodeMatchers: Partial< Record > = { - map: mapTypes, - list: listTypes, statement: cascadingMatcher( patternMatcher(...STATEMENT_TYPES), ancestorChainNodeMatcher( @@ -164,16 +162,11 @@ const nodeMatchers: Partial< ), ), string: "string", - ifStatement: "if", - functionCall: "call", - comment: "comment", - namedFunction: ["method", "singleton_method"], functionName: ["method[name]", "singleton_method[name]"], anonymousFunction: cascadingMatcher( patternMatcher("lambda", "do_block"), matcher(blockFinder), ), - regularExpression: "regex", condition: conditionMatcher("*[condition]"), argumentOrParameter: argumentMatcher( "lambda_parameters", @@ -181,9 +174,8 @@ const nodeMatchers: Partial< "block_parameters", "argument_list", ), - class: "class", - className: "class[name]", collectionKey: trailingMatcher(["pair[key]"], [":"]), + className: "class[name]", name: [ "assignment[left]", "operator_assignment[left]", diff --git a/packages/cursorless-engine/src/processTargets/modifiers/scopeHandlers/ScopeHandlerFactoryImpl.ts b/packages/cursorless-engine/src/processTargets/modifiers/scopeHandlers/ScopeHandlerFactoryImpl.ts index 6e2a069af1..435ec0c128 100644 --- a/packages/cursorless-engine/src/processTargets/modifiers/scopeHandlers/ScopeHandlerFactoryImpl.ts +++ b/packages/cursorless-engine/src/processTargets/modifiers/scopeHandlers/ScopeHandlerFactoryImpl.ts @@ -1,14 +1,15 @@ +import type { ScopeType } from "@cursorless/common"; import { CharacterScopeHandler, DocumentScopeHandler, IdentifierScopeHandler, LineScopeHandler, - TokenScopeHandler, - WordScopeHandler, OneOfScopeHandler, ParagraphScopeHandler, + TokenScopeHandler, + WordScopeHandler, } from "."; -import type { ScopeType } from "@cursorless/common"; +import { maybeGetTreeSitterScopeHandler } from "./TreeSitterScopeHandler"; import type { ScopeHandler } from "./scopeHandler.types"; import { ScopeHandlerFactory } from "./ScopeHandlerFactory"; @@ -53,7 +54,7 @@ export class ScopeHandlerFactoryImpl implements ScopeHandlerFactory { case "paragraph": return new ParagraphScopeHandler(scopeType, languageId); default: - return undefined; + return maybeGetTreeSitterScopeHandler(scopeType, languageId); } } } diff --git a/packages/cursorless-engine/src/processTargets/modifiers/scopeHandlers/TreeSitterScopeHandler/TreeSitterScopeHandler.ts b/packages/cursorless-engine/src/processTargets/modifiers/scopeHandlers/TreeSitterScopeHandler/TreeSitterScopeHandler.ts new file mode 100644 index 0000000000..0b58df6375 --- /dev/null +++ b/packages/cursorless-engine/src/processTargets/modifiers/scopeHandlers/TreeSitterScopeHandler/TreeSitterScopeHandler.ts @@ -0,0 +1,136 @@ +import { + Direction, + NoContainingScopeError, + Position, + ScopeType, + Selection, + SimpleScopeType, + TextEditor, +} from "@cursorless/common"; +import { + NodeMatcher, + ProcessedTargetsContext, +} from "../../../../typings/Types"; +import { Target } from "../../../../typings/target.types"; + +import { selectionWithEditorFromRange } from "../../../../util/selectionUtils"; +import ScopeTypeTarget from "../../../targets/ScopeTypeTarget"; +import { TargetScope } from "../scope.types"; +import { ScopeHandler } from "../scopeHandler.types"; +import { getQueryNodeMatcher } from "./queryNodeMatchers"; + +/** + * Handles scopes that are implemented using tree-sitter. + */ +export default class TreeSitterScopeHandler implements ScopeHandler { + constructor(scopeType: ScopeType, private languageId: string) {} + + scopeType: SimpleScopeType; + iterationScopeType: ScopeType; + + generateScopesRelativeToPosition( + editor: TextEditor, + position: Position, + direction: Direction, + hints?: ScopeIteratorHints | undefined, + ): Iterable { + throw new Error("Method not implemented."); + } + + run(context: ProcessedTargetsContext, target: Target): ScopeTypeTarget[] { + // TODO: Delete this function. It should probably be refactored into some + // common functions that are called by both `getScopeContainingPosition` + // and `getEveryScope` + const languageId = target.editor.document.languageId; + const { type: scopeTypeType } = this.scopeType; + const queryNodeMatcher = getQueryNodeMatcher(languageId, scopeTypeType)!; + const scopeNodes = this.runQueryBasedNodeMatcher( + queryNodeMatcher, + context, + target, + ); + + if (scopeNodes == null) { + throw new NoContainingScopeError(scopeTypeType); + } + + return scopeNodes.map((scope) => { + const { + containingListDelimiter, + leadingDelimiterRange, + trailingDelimiterRange, + removalRange, + interiorRange, + } = scope.context; + + if ( + removalRange != null && + (leadingDelimiterRange != null || trailingDelimiterRange != null) + ) { + throw Error( + "Removal range is mutually exclusive with leading or trailing delimiter range", + ); + } + + const { editor, selection: contentSelection } = scope.selection; + + return new ScopeTypeTarget({ + scopeTypeType, + editor, + isReversed: target.isReversed, + contentRange: contentSelection, + removalRange: removalRange, + delimiter: containingListDelimiter, + interiorRange, + leadingDelimiterRange, + trailingDelimiterRange, + }); + }); + } + + private runQueryBasedNodeMatcher( + queryNodeMatcher: NodeMatcher, + context: ProcessedTargetsContext, + target: Target, + ) { + const selectionWithEditor = getSelectionWithEditor(target); + + const matchResult = queryNodeMatcher( + selectionWithEditor, + context.getTree(selectionWithEditor.editor.document), + this.modifier.type === "everyScope", + ); + + return matchResult + ? matchResult.map((match) => ({ + selection: selectionWithEditorFromRange( + selectionWithEditor, + match.selection.selection, + ), + context: match.selection.context, + })) + : null; + } +} + +function getSelectionWithEditor(target: Target) { + return { + editor: target.editor, + selection: new Selection( + target.contentRange.start, + target.contentRange.end, + ), + }; +} + +export function maybeGetTreeSitterScopeHandler( + scopeType: ScopeType, + languageId: string, +): TreeSitterScopeHandler | undefined { + const queryNodeMatcher = getQueryNodeMatcher(languageId, scopeType.type); + if (queryNodeMatcher != null) { + return new TreeSitterScopeHandler(queryNodeMatcher); + } + + return undefined; +} diff --git a/packages/cursorless-engine/src/processTargets/modifiers/scopeHandlers/TreeSitterScopeHandler/index.ts b/packages/cursorless-engine/src/processTargets/modifiers/scopeHandlers/TreeSitterScopeHandler/index.ts new file mode 100644 index 0000000000..a4859d6b05 --- /dev/null +++ b/packages/cursorless-engine/src/processTargets/modifiers/scopeHandlers/TreeSitterScopeHandler/index.ts @@ -0,0 +1,2 @@ +export * from "./TreeSitterScopeHandler"; +export { default } from "./TreeSitterScopeHandler"; diff --git a/packages/cursorless-engine/src/processTargets/modifiers/scopeHandlers/TreeSitterScopeHandler/queryBasedSpecification.ts b/packages/cursorless-engine/src/processTargets/modifiers/scopeHandlers/TreeSitterScopeHandler/queryBasedSpecification.ts new file mode 100644 index 0000000000..a7c3a202f5 --- /dev/null +++ b/packages/cursorless-engine/src/processTargets/modifiers/scopeHandlers/TreeSitterScopeHandler/queryBasedSpecification.ts @@ -0,0 +1,42 @@ +import { SimpleScopeTypeType, simpleScopeTypeTypes } from "@cursorless/common"; +import * as fs from "fs"; +import * as path from "path"; +import { NodeMatcherAlternative } from "../../../../typings/Types"; +import { defaultMatcher } from "./queryNodeMatchers"; + +function getMatchers( + queries: string, +): Partial> { + const matchers: Partial> = + {}; + for (const scopeTypeType of simpleScopeTypeTypes) { + generateMatcher(queries, scopeTypeType, matchers); + } + return matchers; +} + +function generateMatcher( + queries: string, + scopeTypeType: SimpleScopeTypeType, + matchers: Partial>, +) { + if (queries.match(`@${scopeTypeType}[^a-zA-Z]`)) { + const isIterationScopePresent = !!queries.match( + `@${scopeTypeType}.iterationScope[^a-zA-Z]`, + ); + matchers[scopeTypeType as SimpleScopeTypeType] = defaultMatcher( + scopeTypeType, + isIterationScopePresent, + queries, + ); + } +} + +export default function queryBasedSpecification(languageName: string) { + const queryPath = fs.readFileSync( + path.join(__dirname, `../queries/${languageName}/scopeTypes.scm`), + "utf-8", + ); + + return getMatchers(queryPath); +} diff --git a/packages/cursorless-engine/src/processTargets/modifiers/scopeHandlers/TreeSitterScopeHandler/queryNodeMatchers.ts b/packages/cursorless-engine/src/processTargets/modifiers/scopeHandlers/TreeSitterScopeHandler/queryNodeMatchers.ts new file mode 100644 index 0000000000..bac160b351 --- /dev/null +++ b/packages/cursorless-engine/src/processTargets/modifiers/scopeHandlers/TreeSitterScopeHandler/queryNodeMatchers.ts @@ -0,0 +1,336 @@ +import { + Position, + Range, + Selection, + SimpleScopeTypeType, +} from "@cursorless/common"; +import { intersection } from "lodash"; +import { Point, Query, QueryCapture, SyntaxNode, Tree } from "web-tree-sitter"; +import { SupportedLanguageId } from "../../../../languages/constants"; +import { + NodeMatcher, + NodeMatcherValue, + SelectionExtractor, + SelectionWithEditor, +} from "../../../../typings/Types"; +import { + makeRangeFromPositions, + simpleSelectionExtractor, +} from "../../../../util/nodeSelectors"; +import queryBasedSpecification from "./queryBasedSpecification"; + +let query: Query; + +/** + * + * @param scopeType The scopeType the matcher is responsible for matching. + * @param isIterationScopePresent Indicates whether iteration scope will be defined via the scm file or derived + * by looking at the parent. + * @param scopeQuery The query with node matchers for a specific language. + * @param selector Unused, discard once legacy matching is removed. + * @returns A {@link NodeMatcher} object that can be used to for a specific scopeType. + */ +export function defaultMatcher( + scopeType: string, + isIterationScopePresent: boolean, + scopeQuery: string, + selector: SelectionExtractor = simpleSelectionExtractor, +): NodeMatcher { + return ( + selection: SelectionWithEditor, + treeSitterHook: Tree | SyntaxNode, + siblings: boolean = false, + ): NodeMatcherValue[] | null => { + const tree = treeSitterHook as Tree; + const query = getQuery(tree, scopeQuery); + const rawCaptures = getCapture(selection, tree.rootNode, query, scopeType); + + if (!rawCaptures || rawCaptures.length === 0) { + return null; + } + const selectedCaptures = selectCaptureByRange(rawCaptures, selection); + + if (!selectedCaptures) { + return null; + } + + if (siblings) { + return generateCapturesIfSiblingsPresent( + selectedCaptures, + isIterationScopePresent, + query, + scopeType, + selector, + selection, + ); + } + + const leadingNode = selectedCaptures[0].node; + // TODO: Could we target ES2022 and use .at(-1)? + const trailingNode = selectedCaptures[selectedCaptures.length - 1].node; + return [ + { + // TODO: What should we do here if we are matching multiple nodes for the selection? Which node do we reference? + node: selectedCaptures[0].node, + selection: { + selection: new Selection( + new Position( + leadingNode.startPosition.row, + leadingNode.startPosition.column, + ), + new Position( + trailingNode.endPosition.row, + trailingNode.endPosition.column, + ), + ), + context: {}, + }, + }, + ]; + }; +} + +function generateCapturesIfSiblingsPresent( + selectedCaptures: QueryCapture[], + isIterationScopePresent: boolean, + query: Query, + scopeType: string, + selector: SelectionExtractor, + selection: SelectionWithEditor, +) { + if (selectedCaptures.length > 1) { + throw new Error( + "Cannot match siblings on captures which return a range. Try selecitng a single node.", + ); + } else if (isIterationScopePresent) { + throw new Error("searchScope based queries are not implemented."); + } + + const siblingCaptures = findBySiblingsByParent( + selectedCaptures[0].node, + query, + scopeType, + ); + + return siblingCaptures!.map((c) => { + return { + node: c.node, + selection: selector(selection.editor, c.node), + }; + }); +} + +/** + * Accesses the memoized compiled query. + * @param node Used only to return the tree's language. + * @param scopeQuery The scm query file for a given language. + * @returns Query object + */ +function getQuery(tree: Tree, scopeQuery: string): Query { + if (!query) { + query = tree.getLanguage().query(scopeQuery); + } + return query; +} + +/** + * Prioritize current position of the cursor, one space to the right and then finally one space to the left. + * @param selection Used to derive start and end points for a selection. + * @param root Query is run against the root node. + * @param query Compiled query to run against the parsed tree. + * @param scopeType The scope type that the matcher is responsible for matching. + * @returns Captures that match the scope type, possibly an empty list if there are no matches. + */ +function getCapture( + selection: SelectionWithEditor, + root: SyntaxNode, + query: Query, + scopeType: string, +) { + const startPoint = generatePointFromSelection(selection, "start"); + const endPoint = generatePointFromSelection(selection, "end"); + + const positions = [ + { startPoint, endPoint }, + { + startPoint, + endPoint: { row: endPoint.row, column: endPoint.column + 1 }, + }, + { + startPoint: { row: startPoint.row, column: startPoint.column - 1 }, + endPoint, + }, + ]; + + for (const { startPoint, endPoint } of positions) { + const matches = query + .matches(root, startPoint, endPoint) + .filter(({ captures }) => + captures.some((capture) => capture.name === scopeType), + ); + + if (matches && matches.length > 0) { + return matches; + } + } +} + +/** + * This method takes captures for a given range and scopeType, alongside a selection and then returns + * the relevant captures. If the selection is a single point, only return one capture. If the selection is a + * range, return a leading and trailing capture. If there is no leading capture, bail early. + * + * @param captures The matching nodes for a given scopeType and range. + * @param selection Used to derive start and end points which are matched against a capture. + * @returns The capture for a single point or the captures for a start and end point. + */ +function selectCaptureByRange( + captures: QueryCapture[], + selection: SelectionWithEditor, +): QueryCapture[] | null { + const isSinglePoint = selection.selection.isEmpty; + + const leadingCapture = matchCapturesOnPosition( + captures, + selection.selection.start, + ); + + if (!leadingCapture) { + return null; + } + + if (isSinglePoint) { + return [leadingCapture]; + } else { + const trailingCapture = matchCapturesOnPosition( + captures, + selection.selection.end, + ); + if (!trailingCapture) { + return null; + } + return [leadingCapture, trailingCapture]; + } +} + +/** + * + * @param captures The matching nodes for a given scopeType and range. + * @param position The position to match against. + * @returns The capture furthest down the tree which contains the position. + */ +function matchCapturesOnPosition(captures: QueryCapture[], position: Position) { + let capture; + for (const c of captures) { + const captureRange: Range = makeRangeFromPositions( + c.node.startPosition, + c.node.endPosition, + ); + if (captureRange.contains(position)) { + if (!capture) { + capture = c; + } else { + const leadingCaptureRange = makeRangeFromPositions( + capture.node.startPosition, + capture.node.endPosition, + ); + if (leadingCaptureRange.contains(captureRange)) { + capture = c; + } + } + } + } + return capture; +} + +function generatePointFromSelection( + selection: SelectionWithEditor, + pointType: "start" | "end", +): Point { + return { + row: selection.selection[pointType].line, + column: selection.selection[pointType].character, + }; +} + +/** + * Ported from legacy parent matching. This code is responsible for finding siblings of nodes which do not have + * a `searchScope` defined in the language's .scm query file. + * @param node The matching node from the query and range matching. We will look at this node's parent to find siblings. + * @param query The compiled query which will be run against the parent node. + * @param scopeType The scope type that the matcher is responsible for matching. + * @returns Sibling matches of the node or only the node itself. + */ +function findBySiblingsByParent( + node: SyntaxNode, + query: Query, + scopeType: string, +) { + let parent: SyntaxNode | null = node.parent; + const ids = parent?.namedChildren.map((c) => c.id); + while (parent != null) { + const matches = query + .captures(parent) + .filter( + (capture) => + capture.name === scopeType && ids?.includes(capture.node.id), + ); + if (matches.length > 0) { + return matches; + } + parent = parent.parent; + } + return []; +} + +export function getQueryNodeMatcher( + languageId: string, + scopeTypeType: SimpleScopeTypeType, +): NodeMatcher | undefined { + const matchers = queryBasedMatchers[languageId as SupportedLanguageId]; + + if (matchers == null) { + // Note: When all nodes are matched using this method, return notSupported. + return undefined; + } + + return matchers[scopeTypeType]; +} + +const queryBasedMatchers: Partial< + Record> +> = { + ruby: queryBasedSpecification("ruby"), +}; + +for (const languageId in queryBasedMatchers) { + const queryBasedMatcher = + queryBasedMatchers[languageId as SupportedLanguageId]; + if (queryBasedMatcher) { + ensureUniqueMatchers( + languageMatchers[languageId as SupportedLanguageId], + queryBasedMatcher, + languageId, + ); + } +} + +function ensureUniqueMatchers( + regexMatcher: Record, + queryBasedMatchers: + | Partial> + | undefined, + languageName: string, +) { + const duplicates = intersection( + Object.keys(regexMatcher), + Object.keys(queryBasedMatchers), + ); + if (duplicates.length > 0) { + throw new Error( + `ScopeTypes: [${duplicates.join( + ", ", + )}] for ${languageName} defined via both Regex and Query code paths. Please remove duplicates`, + ); + } +} diff --git a/packages/cursorless-engine/src/processTargets/modifiers/scopeHandlers/index.ts b/packages/cursorless-engine/src/processTargets/modifiers/scopeHandlers/index.ts index b3777c6318..b33decb783 100644 --- a/packages/cursorless-engine/src/processTargets/modifiers/scopeHandlers/index.ts +++ b/packages/cursorless-engine/src/processTargets/modifiers/scopeHandlers/index.ts @@ -12,6 +12,8 @@ export * from "./TokenScopeHandler"; export { default as TokenScopeHandler } from "./TokenScopeHandler"; export * from "./DocumentScopeHandler"; export { default as DocumentScopeHandler } from "./DocumentScopeHandler"; +export * from "./TreeSitterScopeHandler"; +export { default as TreeSitterScopeHandler } from "./TreeSitterScopeHandler"; export * from "./OneOfScopeHandler"; export { default as OneOfScopeHandler } from "./OneOfScopeHandler"; export * from "./ParagraphScopeHandler"; diff --git a/packages/cursorless-engine/src/typings/TreeSitter.ts b/packages/cursorless-engine/src/typings/TreeSitter.ts index adcd6ac6ac..afdaac357f 100644 --- a/packages/cursorless-engine/src/typings/TreeSitter.ts +++ b/packages/cursorless-engine/src/typings/TreeSitter.ts @@ -1,5 +1,5 @@ import { Range, TextDocument } from "@cursorless/common"; -import { SyntaxNode } from "web-tree-sitter"; +import { SyntaxNode, Tree } from "web-tree-sitter"; export interface TreeSitter { /** @@ -9,4 +9,9 @@ export interface TreeSitter { document: TextDocument, range: Range, ) => SyntaxNode; + + /** + * Function to access the tree sitter tree. + */ + readonly getTree: (document: TextDocument) => Tree; } diff --git a/packages/cursorless-engine/src/typings/Types.ts b/packages/cursorless-engine/src/typings/Types.ts index c3d1b7df3b..535f6045d4 100644 --- a/packages/cursorless-engine/src/typings/Types.ts +++ b/packages/cursorless-engine/src/typings/Types.ts @@ -5,7 +5,7 @@ import type { TextDocument, TextEditor, } from "@cursorless/common"; -import type { SyntaxNode } from "web-tree-sitter"; +import type { SyntaxNode, Tree } from "web-tree-sitter"; import { ModifierStage } from "../processTargets/PipelineStages.types"; import { Target } from "./target.types"; @@ -26,6 +26,7 @@ export interface ProcessedTargetsContext { thatMark: Target[]; sourceMark: Target[]; getNodeAtLocation: (document: TextDocument, range: Range) => SyntaxNode; + getTree: (document: TextDocument) => Tree; } export interface SelectionWithEditor { diff --git a/packages/cursorless-engine/src/util/selectionUtils.ts b/packages/cursorless-engine/src/util/selectionUtils.ts index e664f67c6b..4c33b1491d 100644 --- a/packages/cursorless-engine/src/util/selectionUtils.ts +++ b/packages/cursorless-engine/src/util/selectionUtils.ts @@ -8,7 +8,7 @@ export function selectionWithEditorFromRange( return selectionWithEditorFromPositions(selection, range.start, range.end); } -function selectionWithEditorFromPositions( +export function selectionWithEditorFromPositions( selection: SelectionWithEditor, start: Position, end: Position, diff --git a/packages/cursorless-vscode-e2e/src/suite/fixtures/recorded/languages/typescript/changeCall.yml b/packages/cursorless-vscode-e2e/src/suite/fixtures/recorded/languages/typescript/changeCall.yml new file mode 100644 index 0000000000..79b50ce33b --- /dev/null +++ b/packages/cursorless-vscode-e2e/src/suite/fixtures/recorded/languages/typescript/changeCall.yml @@ -0,0 +1,23 @@ +languageId: ruby +command: + version: 1 + spokenForm: change call + action: clearAndSetSelection + targets: + - type: primitive + modifier: {type: containingScope, scopeType: functionCall, includeSiblings: false} +initialState: + documentContents: hello()world() + selections: + - anchor: {line: 0, character: 7} + active: {line: 0, character: 7} + marks: {} +finalState: + documentContents: hello() + selections: + - anchor: {line: 0, character: 7} + active: {line: 0, character: 7} + thatMark: + - anchor: {line: 0, character: 7} + active: {line: 0, character: 7} +fullTargets: [{type: primitive, mark: {type: cursor}, selectionType: token, position: contents, insideOutsideType: inside, modifier: {type: containingScope, scopeType: functionCall, includeSiblings: false}, isImplicit: false}] diff --git a/packages/cursorless-vscode-e2e/src/suite/fixtures/recorded/ordinalScopes/clearFirstFunk.yml b/packages/cursorless-vscode-e2e/src/suite/fixtures/recorded/ordinalScopes/clearFirstFunk.yml new file mode 100644 index 0000000000..3b4fc0c475 --- /dev/null +++ b/packages/cursorless-vscode-e2e/src/suite/fixtures/recorded/ordinalScopes/clearFirstFunk.yml @@ -0,0 +1,33 @@ +languageId: typescript +command: + spokenForm: clear first funk + version: 3 + targets: + - type: primitive + modifiers: + - type: ordinalScope + scopeType: {type: namedFunction} + start: 0 + length: 1 + usePrePhraseSnapshot: true + action: {name: clearAndSetSelection} +initialState: + documentContents: |- + function aaa() {} + function bbb() {} + function ccc() {} + function ddd() {} + selections: + - anchor: {line: 1, character: 10} + active: {line: 2, character: 10} + marks: {} +finalState: + documentContents: |- + function aaa() {} + + function ccc() {} + function ddd() {} + selections: + - anchor: {line: 1, character: 0} + active: {line: 1, character: 0} +fullTargets: [{type: primitive, mark: {type: cursor}, modifiers: [{type: ordinalScope, scopeType: {type: namedFunction}, start: 0, length: 1}]}] diff --git a/packages/cursorless-vscode-e2e/src/suite/fixtures/recorded/ordinalScopes/clearLastFunk.yml b/packages/cursorless-vscode-e2e/src/suite/fixtures/recorded/ordinalScopes/clearLastFunk.yml new file mode 100644 index 0000000000..aeec6da635 --- /dev/null +++ b/packages/cursorless-vscode-e2e/src/suite/fixtures/recorded/ordinalScopes/clearLastFunk.yml @@ -0,0 +1,33 @@ +languageId: typescript +command: + spokenForm: clear last funk + version: 3 + targets: + - type: primitive + modifiers: + - type: ordinalScope + scopeType: {type: namedFunction} + start: -1 + length: 1 + usePrePhraseSnapshot: true + action: {name: clearAndSetSelection} +initialState: + documentContents: |- + function aaa() {} + function bbb() {} + function ccc() {} + function ddd() {} + selections: + - anchor: {line: 1, character: 10} + active: {line: 2, character: 10} + marks: {} +finalState: + documentContents: |- + function aaa() {} + function bbb() {} + + function ddd() {} + selections: + - anchor: {line: 2, character: 0} + active: {line: 2, character: 0} +fullTargets: [{type: primitive, mark: {type: cursor}, modifiers: [{type: ordinalScope, scopeType: {type: namedFunction}, start: -1, length: 1}]}] diff --git a/packages/cursorless-vscode-e2e/src/suite/fixtures/recorded/queryBasedMatchers/changeList.yml b/packages/cursorless-vscode-e2e/src/suite/fixtures/recorded/queryBasedMatchers/changeList.yml new file mode 100644 index 0000000000..3dfcc5774e --- /dev/null +++ b/packages/cursorless-vscode-e2e/src/suite/fixtures/recorded/queryBasedMatchers/changeList.yml @@ -0,0 +1,27 @@ +languageId: ruby +command: + version: 1 + spokenForm: change list + action: clearAndSetSelection + targets: + - type: primitive + modifier: {type: containingScope, scopeType: list, includeSiblings: false} +initialState: + documentContents: | + ["hello"] + #comment + ["world"] + selections: + - anchor: {line: 0, character: 5} + active: {line: 2, character: 6} + marks: {} +finalState: + documentContents: |+ + + selections: + - anchor: {line: 0, character: 0} + active: {line: 0, character: 0} + thatMark: + - anchor: {line: 0, character: 0} + active: {line: 0, character: 0} +fullTargets: [{type: primitive, mark: {type: cursor}, selectionType: token, position: contents, insideOutsideType: inside, modifier: {type: containingScope, scopeType: list, includeSiblings: false}, isImplicit: false}] diff --git a/packages/cursorless-vscode-e2e/src/suite/fixtures/recorded/queryBasedMatchers/changeList10.yml b/packages/cursorless-vscode-e2e/src/suite/fixtures/recorded/queryBasedMatchers/changeList10.yml new file mode 100644 index 0000000000..e79385bce6 --- /dev/null +++ b/packages/cursorless-vscode-e2e/src/suite/fixtures/recorded/queryBasedMatchers/changeList10.yml @@ -0,0 +1,23 @@ +languageId: ruby +command: + version: 1 + spokenForm: change list + action: clearAndSetSelection + targets: + - type: primitive + modifier: {type: containingScope, scopeType: list, includeSiblings: false} +initialState: + documentContents: "[1, 2, 3, [4, 5, 6, [7, 8, 9]]]" + selections: + - anchor: {line: 0, character: 18} + active: {line: 0, character: 18} + marks: {} +finalState: + documentContents: "[1, 2, 3, ]" + selections: + - anchor: {line: 0, character: 10} + active: {line: 0, character: 10} + thatMark: + - anchor: {line: 0, character: 10} + active: {line: 0, character: 10} +fullTargets: [{type: primitive, mark: {type: cursor}, selectionType: token, position: contents, insideOutsideType: inside, modifier: {type: containingScope, scopeType: list, includeSiblings: false}, isImplicit: false}] diff --git a/packages/cursorless-vscode-e2e/src/suite/fixtures/recorded/queryBasedMatchers/changeList11.yml b/packages/cursorless-vscode-e2e/src/suite/fixtures/recorded/queryBasedMatchers/changeList11.yml new file mode 100644 index 0000000000..79d54ce701 --- /dev/null +++ b/packages/cursorless-vscode-e2e/src/suite/fixtures/recorded/queryBasedMatchers/changeList11.yml @@ -0,0 +1,23 @@ +languageId: ruby +command: + version: 1 + spokenForm: change list + action: clearAndSetSelection + targets: + - type: primitive + modifier: {type: containingScope, scopeType: list, includeSiblings: false} +initialState: + documentContents: "[1, 2, 3, [4, 5, 6, [7, 8, 9]]]" + selections: + - anchor: {line: 0, character: 5} + active: {line: 0, character: 5} + marks: {} +finalState: + documentContents: "" + selections: + - anchor: {line: 0, character: 0} + active: {line: 0, character: 0} + thatMark: + - anchor: {line: 0, character: 0} + active: {line: 0, character: 0} +fullTargets: [{type: primitive, mark: {type: cursor}, selectionType: token, position: contents, insideOutsideType: inside, modifier: {type: containingScope, scopeType: list, includeSiblings: false}, isImplicit: false}] diff --git a/packages/cursorless-vscode-e2e/src/suite/fixtures/recorded/queryBasedMatchers/changeList12.yml b/packages/cursorless-vscode-e2e/src/suite/fixtures/recorded/queryBasedMatchers/changeList12.yml new file mode 100644 index 0000000000..dadfc3bdaa --- /dev/null +++ b/packages/cursorless-vscode-e2e/src/suite/fixtures/recorded/queryBasedMatchers/changeList12.yml @@ -0,0 +1,23 @@ +languageId: ruby +command: + version: 1 + spokenForm: change list + action: clearAndSetSelection + targets: + - type: primitive + modifier: {type: containingScope, scopeType: list, includeSiblings: false} +initialState: + documentContents: "[1, 2, 3, [4, 5, 6, [7, 8, 9]]]" + selections: + - anchor: {line: 0, character: 0} + active: {line: 0, character: 0} + marks: {} +finalState: + documentContents: "" + selections: + - anchor: {line: 0, character: 0} + active: {line: 0, character: 0} + thatMark: + - anchor: {line: 0, character: 0} + active: {line: 0, character: 0} +fullTargets: [{type: primitive, mark: {type: cursor}, selectionType: token, position: contents, insideOutsideType: inside, modifier: {type: containingScope, scopeType: list, includeSiblings: false}, isImplicit: false}] diff --git a/packages/cursorless-vscode-e2e/src/suite/fixtures/recorded/queryBasedMatchers/changeList13.yml b/packages/cursorless-vscode-e2e/src/suite/fixtures/recorded/queryBasedMatchers/changeList13.yml new file mode 100644 index 0000000000..a104d83cab --- /dev/null +++ b/packages/cursorless-vscode-e2e/src/suite/fixtures/recorded/queryBasedMatchers/changeList13.yml @@ -0,0 +1,26 @@ +languageId: ruby +command: + version: 1 + spokenForm: change list + action: clearAndSetSelection + targets: + - type: primitive + modifier: {type: containingScope, scopeType: list, includeSiblings: false} +initialState: + documentContents: |- + [1, 2, 3, [4, 5, 6, [7, 8, 9]]] + + [["a"], ["b", "c", ["d", "e"]], "f"] + selections: + - anchor: {line: 0, character: 24} + active: {line: 2, character: 4} + marks: {} +finalState: + documentContents: "[1, 2, 3, [4, 5, 6, , [\"b\", \"c\", [\"d\", \"e\"]], \"f\"]" + selections: + - anchor: {line: 0, character: 20} + active: {line: 0, character: 20} + thatMark: + - anchor: {line: 0, character: 20} + active: {line: 0, character: 20} +fullTargets: [{type: primitive, mark: {type: cursor}, selectionType: token, position: contents, insideOutsideType: inside, modifier: {type: containingScope, scopeType: list, includeSiblings: false}, isImplicit: false}] diff --git a/packages/cursorless-vscode-e2e/src/suite/fixtures/recorded/queryBasedMatchers/changeList14.yml b/packages/cursorless-vscode-e2e/src/suite/fixtures/recorded/queryBasedMatchers/changeList14.yml new file mode 100644 index 0000000000..14f81cf932 --- /dev/null +++ b/packages/cursorless-vscode-e2e/src/suite/fixtures/recorded/queryBasedMatchers/changeList14.yml @@ -0,0 +1,26 @@ +languageId: ruby +command: + version: 1 + spokenForm: change list + action: clearAndSetSelection + targets: + - type: primitive + modifier: {type: containingScope, scopeType: list, includeSiblings: false} +initialState: + documentContents: |- + [1, 2, 3, [4, 5, 6, [7, 8, 9]]] + + [["a"], ["b", "c", ["d", "e"]], "f"] + selections: + - anchor: {line: 0, character: 14} + active: {line: 2, character: 14} + marks: {} +finalState: + documentContents: "[1, 2, 3, , \"f\"]" + selections: + - anchor: {line: 0, character: 10} + active: {line: 0, character: 10} + thatMark: + - anchor: {line: 0, character: 10} + active: {line: 0, character: 10} +fullTargets: [{type: primitive, mark: {type: cursor}, selectionType: token, position: contents, insideOutsideType: inside, modifier: {type: containingScope, scopeType: list, includeSiblings: false}, isImplicit: false}] diff --git a/packages/cursorless-vscode-e2e/src/suite/fixtures/recorded/queryBasedMatchers/changeList2.yml b/packages/cursorless-vscode-e2e/src/suite/fixtures/recorded/queryBasedMatchers/changeList2.yml new file mode 100644 index 0000000000..e6a1ef1c4f --- /dev/null +++ b/packages/cursorless-vscode-e2e/src/suite/fixtures/recorded/queryBasedMatchers/changeList2.yml @@ -0,0 +1,23 @@ +languageId: ruby +command: + version: 1 + spokenForm: change list + action: clearAndSetSelection + targets: + - type: primitive + modifier: {type: containingScope, scopeType: list, includeSiblings: false} +initialState: + documentContents: "[\"hello\"]" + selections: + - anchor: {line: 0, character: 0} + active: {line: 0, character: 0} + marks: {} +finalState: + documentContents: "" + selections: + - anchor: {line: 0, character: 0} + active: {line: 0, character: 0} + thatMark: + - anchor: {line: 0, character: 0} + active: {line: 0, character: 0} +fullTargets: [{type: primitive, mark: {type: cursor}, selectionType: token, position: contents, insideOutsideType: inside, modifier: {type: containingScope, scopeType: list, includeSiblings: false}, isImplicit: false}] diff --git a/packages/cursorless-vscode-e2e/src/suite/fixtures/recorded/queryBasedMatchers/changeList3.yml b/packages/cursorless-vscode-e2e/src/suite/fixtures/recorded/queryBasedMatchers/changeList3.yml new file mode 100644 index 0000000000..898f5a2ab3 --- /dev/null +++ b/packages/cursorless-vscode-e2e/src/suite/fixtures/recorded/queryBasedMatchers/changeList3.yml @@ -0,0 +1,23 @@ +languageId: ruby +command: + version: 1 + spokenForm: change list + action: clearAndSetSelection + targets: + - type: primitive + modifier: {type: containingScope, scopeType: list, includeSiblings: false} +initialState: + documentContents: "[\"hello\"]" + selections: + - anchor: {line: 0, character: 1} + active: {line: 0, character: 1} + marks: {} +finalState: + documentContents: "" + selections: + - anchor: {line: 0, character: 0} + active: {line: 0, character: 0} + thatMark: + - anchor: {line: 0, character: 0} + active: {line: 0, character: 0} +fullTargets: [{type: primitive, mark: {type: cursor}, selectionType: token, position: contents, insideOutsideType: inside, modifier: {type: containingScope, scopeType: list, includeSiblings: false}, isImplicit: false}] diff --git a/packages/cursorless-vscode-e2e/src/suite/fixtures/recorded/queryBasedMatchers/changeList4.yml b/packages/cursorless-vscode-e2e/src/suite/fixtures/recorded/queryBasedMatchers/changeList4.yml new file mode 100644 index 0000000000..d4dddd3e44 --- /dev/null +++ b/packages/cursorless-vscode-e2e/src/suite/fixtures/recorded/queryBasedMatchers/changeList4.yml @@ -0,0 +1,23 @@ +languageId: ruby +command: + version: 1 + spokenForm: change list + action: clearAndSetSelection + targets: + - type: primitive + modifier: {type: containingScope, scopeType: list, includeSiblings: false} +initialState: + documentContents: "[\"hello\"]" + selections: + - anchor: {line: 0, character: 8} + active: {line: 0, character: 8} + marks: {} +finalState: + documentContents: "" + selections: + - anchor: {line: 0, character: 0} + active: {line: 0, character: 0} + thatMark: + - anchor: {line: 0, character: 0} + active: {line: 0, character: 0} +fullTargets: [{type: primitive, mark: {type: cursor}, selectionType: token, position: contents, insideOutsideType: inside, modifier: {type: containingScope, scopeType: list, includeSiblings: false}, isImplicit: false}] diff --git a/packages/cursorless-vscode-e2e/src/suite/fixtures/recorded/queryBasedMatchers/changeList5.yml b/packages/cursorless-vscode-e2e/src/suite/fixtures/recorded/queryBasedMatchers/changeList5.yml new file mode 100644 index 0000000000..ea2d628908 --- /dev/null +++ b/packages/cursorless-vscode-e2e/src/suite/fixtures/recorded/queryBasedMatchers/changeList5.yml @@ -0,0 +1,23 @@ +languageId: ruby +command: + version: 1 + spokenForm: change list + action: clearAndSetSelection + targets: + - type: primitive + modifier: {type: containingScope, scopeType: list, includeSiblings: false} +initialState: + documentContents: "[\"hello\"]" + selections: + - anchor: {line: 0, character: 9} + active: {line: 0, character: 9} + marks: {} +finalState: + documentContents: "" + selections: + - anchor: {line: 0, character: 0} + active: {line: 0, character: 0} + thatMark: + - anchor: {line: 0, character: 0} + active: {line: 0, character: 0} +fullTargets: [{type: primitive, mark: {type: cursor}, selectionType: token, position: contents, insideOutsideType: inside, modifier: {type: containingScope, scopeType: list, includeSiblings: false}, isImplicit: false}] diff --git a/packages/cursorless-vscode-e2e/src/suite/fixtures/recorded/queryBasedMatchers/changeList6.yml b/packages/cursorless-vscode-e2e/src/suite/fixtures/recorded/queryBasedMatchers/changeList6.yml new file mode 100644 index 0000000000..2fb109eed9 --- /dev/null +++ b/packages/cursorless-vscode-e2e/src/suite/fixtures/recorded/queryBasedMatchers/changeList6.yml @@ -0,0 +1,23 @@ +languageId: ruby +command: + version: 1 + spokenForm: change list + action: clearAndSetSelection + targets: + - type: primitive + modifier: {type: containingScope, scopeType: list, includeSiblings: false} +initialState: + documentContents: "[\"hello\"] " + selections: + - anchor: {line: 0, character: 4} + active: {line: 0, character: 7} + marks: {} +finalState: + documentContents: " " + selections: + - anchor: {line: 0, character: 0} + active: {line: 0, character: 0} + thatMark: + - anchor: {line: 0, character: 0} + active: {line: 0, character: 0} +fullTargets: [{type: primitive, mark: {type: cursor}, selectionType: token, position: contents, insideOutsideType: inside, modifier: {type: containingScope, scopeType: list, includeSiblings: false}, isImplicit: false}] diff --git a/packages/cursorless-vscode-e2e/src/suite/fixtures/recorded/queryBasedMatchers/changeList7.yml b/packages/cursorless-vscode-e2e/src/suite/fixtures/recorded/queryBasedMatchers/changeList7.yml new file mode 100644 index 0000000000..7563500936 --- /dev/null +++ b/packages/cursorless-vscode-e2e/src/suite/fixtures/recorded/queryBasedMatchers/changeList7.yml @@ -0,0 +1,23 @@ +languageId: ruby +command: + version: 1 + spokenForm: change list + action: clearAndSetSelection + targets: + - type: primitive + modifier: {type: containingScope, scopeType: list, includeSiblings: false} +initialState: + documentContents: "[\"hello\"] " + selections: + - anchor: {line: 0, character: 6} + active: {line: 0, character: 6} + marks: {} +finalState: + documentContents: " " + selections: + - anchor: {line: 0, character: 0} + active: {line: 0, character: 0} + thatMark: + - anchor: {line: 0, character: 0} + active: {line: 0, character: 0} +fullTargets: [{type: primitive, mark: {type: cursor}, selectionType: token, position: contents, insideOutsideType: inside, modifier: {type: containingScope, scopeType: list, includeSiblings: false}, isImplicit: false}] diff --git a/packages/cursorless-vscode-e2e/src/suite/fixtures/recorded/queryBasedMatchers/changeList8.yml b/packages/cursorless-vscode-e2e/src/suite/fixtures/recorded/queryBasedMatchers/changeList8.yml new file mode 100644 index 0000000000..038466d9ca --- /dev/null +++ b/packages/cursorless-vscode-e2e/src/suite/fixtures/recorded/queryBasedMatchers/changeList8.yml @@ -0,0 +1,23 @@ +languageId: ruby +command: + version: 1 + spokenForm: change list + action: clearAndSetSelection + targets: + - type: primitive + modifier: {type: containingScope, scopeType: list, includeSiblings: false} +initialState: + documentContents: "[1,2,3, [4,5,6, [7, 8, 9]]]" + selections: + - anchor: {line: 0, character: 19} + active: {line: 0, character: 19} + marks: {} +finalState: + documentContents: "[1,2,3, [4,5,6, ]]" + selections: + - anchor: {line: 0, character: 16} + active: {line: 0, character: 16} + thatMark: + - anchor: {line: 0, character: 16} + active: {line: 0, character: 16} +fullTargets: [{type: primitive, mark: {type: cursor}, selectionType: token, position: contents, insideOutsideType: inside, modifier: {type: containingScope, scopeType: list, includeSiblings: false}, isImplicit: false}] diff --git a/packages/cursorless-vscode-e2e/src/suite/fixtures/recorded/queryBasedMatchers/changeList9.yml b/packages/cursorless-vscode-e2e/src/suite/fixtures/recorded/queryBasedMatchers/changeList9.yml new file mode 100644 index 0000000000..edf9e5cbd0 --- /dev/null +++ b/packages/cursorless-vscode-e2e/src/suite/fixtures/recorded/queryBasedMatchers/changeList9.yml @@ -0,0 +1,23 @@ +languageId: ruby +command: + version: 1 + spokenForm: change list + action: clearAndSetSelection + targets: + - type: primitive + modifier: {type: containingScope, scopeType: list, includeSiblings: false} +initialState: + documentContents: "[1, 2, 3, [4, 5, 6, [7, 8, 9]]]" + selections: + - anchor: {line: 0, character: 24} + active: {line: 0, character: 24} + marks: {} +finalState: + documentContents: "[1, 2, 3, [4, 5, 6, ]]" + selections: + - anchor: {line: 0, character: 20} + active: {line: 0, character: 20} + thatMark: + - anchor: {line: 0, character: 20} + active: {line: 0, character: 20} +fullTargets: [{type: primitive, mark: {type: cursor}, selectionType: token, position: contents, insideOutsideType: inside, modifier: {type: containingScope, scopeType: list, includeSiblings: false}, isImplicit: false}] diff --git a/packages/cursorless-vscode-e2e/src/suite/queryBasedSpecification.test.ts b/packages/cursorless-vscode-e2e/src/suite/queryBasedSpecification.test.ts new file mode 100644 index 0000000000..c9aa184936 --- /dev/null +++ b/packages/cursorless-vscode-e2e/src/suite/queryBasedSpecification.test.ts @@ -0,0 +1,3 @@ +suite("queryBasedSpecification", async function () { + test("generates the correct matchers", async () => {}); +}); diff --git a/packages/cursorless-vscode-e2e/src/suite/queryNodeMatchers.test.ts b/packages/cursorless-vscode-e2e/src/suite/queryNodeMatchers.test.ts new file mode 100644 index 0000000000..6dac268855 --- /dev/null +++ b/packages/cursorless-vscode-e2e/src/suite/queryNodeMatchers.test.ts @@ -0,0 +1,161 @@ +import { getParseTreeApi, openNewEditor } from "@cursorless/vscode-common"; +import * as assert from "assert"; +import { Position } from "vscode"; +import { Tree } from "web-tree-sitter"; + +suite("queryNodeMatcher", async () => { + suite("happy path", async () => { + const matcher = defaultMatcher("comment", false, `(comment) @comment`); + const nodeType = "comment"; + + test("match single", async () => { + const code = "# hello world"; + + const { selection, tree } = await getNodeAndEditor(code); + assertMatch(matcher, tree, nodeType, selection, 1); + }); + + test("match multiple by parent", async () => { + const code = `# hello world +variable_def = "yep" +# hello world +# hello world`; + const { selection, tree } = await getNodeAndEditor(code); + assertMatch(matcher, tree, nodeType, selection, 3, { + includeSiblings: true, + }); + }); + }); + + suite("expands range", async () => { + const matcher = defaultMatcher("comment", false, `(comment) @comment`); + const nodeType = "comment"; + const cursorPositions = { + start: new Position(0, 0), + end: new Position(1, 2), + }; + + test("matches with selection across two node the same type", async () => { + const code = `# hello +# world`; + const { selection, tree } = await getNodeAndEditor(code, cursorPositions); + const matches = assertMatch(matcher, tree, nodeType, selection, 1); + assert.equal(matches![0].selection.selection.isSingleLine, false); + }); + + test("no match with selection across two node the different type", async () => { + const code = `# hello +hello_world()`; + const { selection, tree } = await getNodeAndEditor(code, cursorPositions); + assertMatch(matcher, tree, nodeType, selection, 0); + }); + }); + + suite("non-matches due to positions", async () => { + const matcher = defaultMatcher( + "functionCall", + false, + `(call) @functionCall`, + ); + + // Use a node type that does not take up the entire line following the it. + const code = " hello_world() "; + const nonMatchPositions = [ + { + start: new Position(0, 0), + end: new Position(0, 0), + message: "Empty selection before the scope", + }, + { + start: new Position(0, code.length - 1), + end: new Position(0, code.length - 1), + message: "Empty selection after the scope", + }, + { + start: new Position(0, 0), + end: new Position(0, 1), + message: "Non-empty selection before the scope", + }, + { + start: new Position(0, 0), + end: new Position(0, 1), + message: "Non-empty selection abutting scope", + }, + { + start: new Position(0, code.length - 2), + end: new Position(0, code.length - 1), + message: "Non-empty selection after the scope", + }, + { + start: new Position(0, 0), + end: new Position(0, 5), + message: "Non-empty selection starting before and ending within scope", + }, + { + start: new Position(0, 5), + end: new Position(0, code.length - 1), + message: + "Non-empty selection starting within scope and ending outside of scope", + }, + ]; + for (const position of nonMatchPositions) { + test(`empty selection in whitespace before scope does not match: ${position.message}`, async () => { + const nodeType = "comment"; + const { selection, tree } = await getNodeAndEditor(code, position); + assertMatch(matcher, tree, nodeType, selection, 0); + }); + } + }); + + test("match multiple by searchScope throws error", async () => { + const code = "# hello world"; + const matcher = defaultMatcher("comment", true, `(comment) @comment`); + const { selection, tree } = await getNodeAndEditor(code); + assert.throws(() => { + matcher(selection, tree, true); + }); + }); +}); + +const getNodeAndEditor = async ( + code: string, + cursorPositions: { start: Position; end: Position } = { + start: new Position(0, 0), + end: new Position(0, 0), + }, +): Promise<{ selection: SelectionWithEditor; tree: Tree }> => { + const editor = await openNewEditor(code, "ruby"); + const { getTree } = await getParseTreeApi(); + const tree = getTree(editor.document); + + const selectionWithEditor = selectionWithEditorFromPositions( + { selection: editor.selection, editor }, + cursorPositions.start, + cursorPositions.end, + ); + + return { + selection: selectionWithEditor, + tree: tree, + }; +}; + +const assertMatch = ( + matcher: NodeMatcher, + tree: Tree, + nodeType: string, + selectionWithEditor: SelectionWithEditor, + expectedMatches: number, + options: { includeSiblings?: boolean } = { includeSiblings: false }, +) => { + const matches = matcher(selectionWithEditor, tree, options?.includeSiblings); + if (expectedMatches === 0) { + assert.equal(matches, null); + } else { + assert.equal(expectedMatches, matches!.length); + for (const match of matches!) { + assert.equal(nodeType, match.node.type); + } + } + return matches; +}; diff --git a/packages/cursorless-vscode/src/extension.ts b/packages/cursorless-vscode/src/extension.ts index e6820499c7..b13e6cf546 100644 --- a/packages/cursorless-vscode/src/extension.ts +++ b/packages/cursorless-vscode/src/extension.ts @@ -126,6 +126,10 @@ function createTreeSitter(parseTreeApi: ParseTreeApi): TreeSitter { new vscode.Location(document.uri, toVscodeRange(range)), ); }, + + getTree(document: TextDocument) { + return parseTreeApi.getTreeForUri(document.uri); + }, }; } diff --git a/packages/vscode-common/src/getExtensionApi.ts b/packages/vscode-common/src/getExtensionApi.ts index c455b87f6d..f7747bd26a 100644 --- a/packages/vscode-common/src/getExtensionApi.ts +++ b/packages/vscode-common/src/getExtensionApi.ts @@ -12,7 +12,7 @@ import type { TextEditor, } from "@cursorless/common"; import * as vscode from "vscode"; -import type { SyntaxNode } from "web-tree-sitter"; +import type { SyntaxNode, Tree } from "web-tree-sitter"; export interface TestHelpers { ide: NormalizedIDE; @@ -58,6 +58,7 @@ export interface CursorlessApi { export interface ParseTreeApi { getNodeAtLocation(location: vscode.Location): SyntaxNode; + getTreeForUri(uri: vscode.Uri): Tree; loadLanguage: (languageId: string) => Promise; } diff --git a/queries/html/scopeTypes.scm b/queries/html/scopeTypes.scm new file mode 100644 index 0000000000..0f699ad1bb --- /dev/null +++ b/queries/html/scopeTypes.scm @@ -0,0 +1,15 @@ +(element + (start_tag) @.interior.start @.boundary + (end_tag) @.interior.end @.boundary + ; What happens with these two boundary elements? + (#make-range! @.interior.start @.interior.end ".interior" true true) + ; Note that the final `true true` means to exclude start and end node of range +) @element + +; Alternately: +(element + (start_tag) @.interior.startExclusive @.boundary + (end_tag) @.interior.endExclusive @.boundary +) @element + +(element (self_closing_tag)) @element diff --git a/queries/ruby/scopeTypes.scm b/queries/ruby/scopeTypes.scm new file mode 100644 index 0000000000..19773d755b --- /dev/null +++ b/queries/ruby/scopeTypes.scm @@ -0,0 +1,15 @@ +(comment) @comment +(if) @ifStatement +(call) @functionCall +[(method) (singleton_method)] @namedFunction +(hash) @map + +[ + (array) + (string_array) + (symbol_array) +] @list + +(regex) @regularExpression + +(class) @class diff --git a/queries/typescript/scopeTypes.scm b/queries/typescript/scopeTypes.scm new file mode 100644 index 0000000000..7359b31a76 --- /dev/null +++ b/queries/typescript/scopeTypes.scm @@ -0,0 +1,12 @@ +(if_statement consequence: (_) ) +(if_statement + consequence: [ + (statement_block + (_)* @.interior + ; What happens with this range? + ) + (_) @.interior + ; This is for `if (foo) bar();` + ; Does this one only match if above doesn't? + ] +) @ifStatement diff --git a/typings/treeSitter.d.ts b/typings/treeSitter.d.ts index be59fb1d65..621ea7c11e 100644 --- a/typings/treeSitter.d.ts +++ b/typings/treeSitter.d.ts @@ -159,7 +159,7 @@ declare module "web-tree-sitter" { query(source: string): Query; } - interface QueryCapture { + export interface QueryCapture { name: string; node: SyntaxNode; } @@ -189,6 +189,7 @@ declare module "web-tree-sitter" { endPosition?: Point, ): QueryCapture[]; predicatesForPattern(patternIndex: number): PredicateResult[]; + predicates: PredicateResult; } } From f8dd20df239c7e5a64fafd8eb078bfa3c5bc40bc Mon Sep 17 00:00:00 2001 From: Pokey Rule <755842+pokey@users.noreply.github.com> Date: Fri, 14 Apr 2023 11:34:21 +0100 Subject: [PATCH 02/25] Initial working version --- .../cursorless-engine/src/cursorlessEngine.ts | 4 +- .../src/languages/LanguageDefinitionImpl.ts | 38 ++ .../src/languages/LanguageDefinitions.ts | 12 + .../src/languages/LanguageDefinitionsImpl.ts | 29 ++ .../scopeHandlers/ScopeHandlerFactoryImpl.ts | 10 +- .../scopeHandlers/TreeSitterScopeHandler.ts | 133 +++++++ .../TreeSitterScopeHandler.ts | 136 ------- .../TreeSitterScopeHandler/index.ts | 2 - .../queryBasedSpecification.ts | 42 --- .../queryNodeMatchers.ts | 336 ------------------ .../modifiers/scopeHandlers/index.ts | 2 +- .../src/typings/TreeSitter.ts | 17 +- .../src/suite/queryNodeMatchers.test.ts | 161 --------- packages/cursorless-vscode/src/extension.ts | 2 + .../src/scripts/populateDist/assets.ts | 4 + packages/vscode-common/src/getExtensionApi.ts | 3 +- typings/treeSitter.d.ts | 2 +- 17 files changed, 242 insertions(+), 691 deletions(-) create mode 100644 packages/cursorless-engine/src/languages/LanguageDefinitionImpl.ts create mode 100644 packages/cursorless-engine/src/languages/LanguageDefinitions.ts create mode 100644 packages/cursorless-engine/src/languages/LanguageDefinitionsImpl.ts create mode 100644 packages/cursorless-engine/src/processTargets/modifiers/scopeHandlers/TreeSitterScopeHandler.ts delete mode 100644 packages/cursorless-engine/src/processTargets/modifiers/scopeHandlers/TreeSitterScopeHandler/TreeSitterScopeHandler.ts delete mode 100644 packages/cursorless-engine/src/processTargets/modifiers/scopeHandlers/TreeSitterScopeHandler/index.ts delete mode 100644 packages/cursorless-engine/src/processTargets/modifiers/scopeHandlers/TreeSitterScopeHandler/queryBasedSpecification.ts delete mode 100644 packages/cursorless-engine/src/processTargets/modifiers/scopeHandlers/TreeSitterScopeHandler/queryNodeMatchers.ts delete mode 100644 packages/cursorless-vscode-e2e/src/suite/queryNodeMatchers.test.ts diff --git a/packages/cursorless-engine/src/cursorlessEngine.ts b/packages/cursorless-engine/src/cursorlessEngine.ts index 656a5951e6..064405b29c 100644 --- a/packages/cursorless-engine/src/cursorlessEngine.ts +++ b/packages/cursorless-engine/src/cursorlessEngine.ts @@ -9,6 +9,7 @@ import { MarkStageFactoryImpl } from "./processTargets/MarkStageFactoryImpl"; import { ModifierStageFactoryImpl } from "./processTargets/ModifierStageFactoryImpl"; import { ScopeHandlerFactoryImpl } from "./processTargets/modifiers/scopeHandlers"; import { injectIde } from "./singletons/ide.singleton"; +import { LanguageDefinitionsImpl } from "./languages/LanguageDefinitionsImpl"; export function createCursorlessEngine( treeSitter: TreeSitter, @@ -36,7 +37,8 @@ export function createCursorlessEngine( const testCaseRecorder = new TestCaseRecorder(hatTokenMap); - const scopeHandlerFactory = new ScopeHandlerFactoryImpl(); + const languageDefinitions = new LanguageDefinitionsImpl(treeSitter); + const scopeHandlerFactory = new ScopeHandlerFactoryImpl(languageDefinitions); const markStageFactory = new MarkStageFactoryImpl(); const modifierStageFactory = new ModifierStageFactoryImpl( scopeHandlerFactory, diff --git a/packages/cursorless-engine/src/languages/LanguageDefinitionImpl.ts b/packages/cursorless-engine/src/languages/LanguageDefinitionImpl.ts new file mode 100644 index 0000000000..7aa4f2ce0b --- /dev/null +++ b/packages/cursorless-engine/src/languages/LanguageDefinitionImpl.ts @@ -0,0 +1,38 @@ +import { ScopeType, SimpleScopeType } from "@cursorless/common"; +import { Query } from "web-tree-sitter"; +import { LanguageDefinition } from "./LanguageDefinitions"; +import { LanguageId } from "./constants"; +import { ide } from "../singletons/ide.singleton"; +import { join } from "path"; +import { TreeSitterScopeHandler } from "../processTargets/modifiers/scopeHandlers"; +import { TreeSitter } from "../typings/TreeSitter"; +import { readFileSync } from "fs"; + +export class LanguageDefinitionImpl implements LanguageDefinition { + private query!: Query; + + constructor(private treeSitter: TreeSitter, private languageId: LanguageId) {} + + init() { + const rawLanguageQueryString = readFileSync( + join(ide().assetsRoot, "queries", this.languageId, "scopeTypes.scm"), + "utf8", + ); + + this.query = this.treeSitter + .getLanguage(this.languageId)! + .query(rawLanguageQueryString); + } + + maybeGetLanguageScopeHandler(scopeType: ScopeType) { + if (!this.query.captureNames.includes(scopeType.type)) { + return undefined; + } + + return new TreeSitterScopeHandler( + this.treeSitter, + this.query, + scopeType as SimpleScopeType, + ); + } +} diff --git a/packages/cursorless-engine/src/languages/LanguageDefinitions.ts b/packages/cursorless-engine/src/languages/LanguageDefinitions.ts new file mode 100644 index 0000000000..3910332da7 --- /dev/null +++ b/packages/cursorless-engine/src/languages/LanguageDefinitions.ts @@ -0,0 +1,12 @@ +import { ScopeType } from "@cursorless/common"; +import { ScopeHandler } from "../processTargets/modifiers/scopeHandlers/scopeHandler.types"; + +export interface LanguageDefinitions { + get(languageId: string): LanguageDefinition | undefined; +} + +export interface LanguageDefinition { + maybeGetLanguageScopeHandler: ( + scopeType: ScopeType, + ) => ScopeHandler | undefined; +} diff --git a/packages/cursorless-engine/src/languages/LanguageDefinitionsImpl.ts b/packages/cursorless-engine/src/languages/LanguageDefinitionsImpl.ts new file mode 100644 index 0000000000..34e76b1b8d --- /dev/null +++ b/packages/cursorless-engine/src/languages/LanguageDefinitionsImpl.ts @@ -0,0 +1,29 @@ +import { TreeSitter } from ".."; +import { LanguageDefinition, LanguageDefinitions } from "./LanguageDefinitions"; +import { LanguageId } from "./constants"; +import { LanguageDefinitionImpl } from "./LanguageDefinitionImpl"; + +export class LanguageDefinitionsImpl implements LanguageDefinitions { + private languageDefinitions: Map = + new Map(); + + constructor(private treeSitter: TreeSitter) {} + + get(languageId: LanguageId): LanguageDefinition | undefined { + if (!languages.includes(languageId)) { + return undefined; + } + + let definition = this.languageDefinitions.get(languageId); + + if (definition == null) { + definition = new LanguageDefinitionImpl(this.treeSitter, languageId); + definition.init(); + this.languageDefinitions.set(languageId, definition); + } + + return definition; + } +} + +const languages: LanguageId[] = ["ruby"]; diff --git a/packages/cursorless-engine/src/processTargets/modifiers/scopeHandlers/ScopeHandlerFactoryImpl.ts b/packages/cursorless-engine/src/processTargets/modifiers/scopeHandlers/ScopeHandlerFactoryImpl.ts index 435ec0c128..cb4e5dfc91 100644 --- a/packages/cursorless-engine/src/processTargets/modifiers/scopeHandlers/ScopeHandlerFactoryImpl.ts +++ b/packages/cursorless-engine/src/processTargets/modifiers/scopeHandlers/ScopeHandlerFactoryImpl.ts @@ -9,9 +9,9 @@ import { TokenScopeHandler, WordScopeHandler, } from "."; -import { maybeGetTreeSitterScopeHandler } from "./TreeSitterScopeHandler"; -import type { ScopeHandler } from "./scopeHandler.types"; +import { LanguageDefinitions } from "../../../languages/LanguageDefinitions"; import { ScopeHandlerFactory } from "./ScopeHandlerFactory"; +import type { ScopeHandler } from "./scopeHandler.types"; /** * Returns a scope handler for the given scope type and language id, or @@ -31,7 +31,7 @@ import { ScopeHandlerFactory } from "./ScopeHandlerFactory"; * legacy pathways */ export class ScopeHandlerFactoryImpl implements ScopeHandlerFactory { - constructor() { + constructor(private languageDefinitions: LanguageDefinitions) { this.create = this.create.bind(this); } @@ -54,7 +54,9 @@ export class ScopeHandlerFactoryImpl implements ScopeHandlerFactory { case "paragraph": return new ParagraphScopeHandler(scopeType, languageId); default: - return maybeGetTreeSitterScopeHandler(scopeType, languageId); + return this.languageDefinitions + .get(languageId) + ?.maybeGetLanguageScopeHandler(scopeType); } } } diff --git a/packages/cursorless-engine/src/processTargets/modifiers/scopeHandlers/TreeSitterScopeHandler.ts b/packages/cursorless-engine/src/processTargets/modifiers/scopeHandlers/TreeSitterScopeHandler.ts new file mode 100644 index 0000000000..74a4b7417b --- /dev/null +++ b/packages/cursorless-engine/src/processTargets/modifiers/scopeHandlers/TreeSitterScopeHandler.ts @@ -0,0 +1,133 @@ +import { + Direction, + Position, + ScopeType, + SimpleScopeType, + TextDocument, + TextEditor, +} from "@cursorless/common"; + +import { TreeSitter } from "../../.."; +import ScopeTypeTarget from "../../targets/ScopeTypeTarget"; +import BaseScopeHandler from "./BaseScopeHandler"; +import { TargetScope } from "./scope.types"; +import { Point, Query, QueryMatch } from "web-tree-sitter"; +import { getNodeRange } from "../../../util/nodeSelectors"; +import { ScopeIteratorRequirements } from "./scopeHandler.types"; + +/** + * Handles scopes that are implemented using tree-sitter. + */ +export class TreeSitterScopeHandler extends BaseScopeHandler { + protected isHierarchical: boolean = true; + + constructor( + private treeSitter: TreeSitter, + private query: Query, + public scopeType: SimpleScopeType, + ) { + super(); + } + + public get iterationScopeType(): ScopeType { + throw Error("Not implemented"); + } + + *generateScopeCandidates( + editor: TextEditor, + position: Position, + direction: Direction, + hints: ScopeIteratorRequirements, + ): Iterable { + const { document } = editor; + + const { start, end } = this.getQueryRange( + document, + position, + direction, + hints, + ); + + const matches = this.query + .matches( + this.treeSitter.getTree(document).rootNode, + positionToPoint(start), + positionToPoint(end), + ) + .filter(({ captures }) => + captures.some((capture) => capture.name === this.scopeType.type), + ); + + // FIXME: Sort? + + for (const match of matches) { + yield this.matchToScope(editor, match); + } + } + + private getQueryRange( + document: TextDocument, + position: Position, + direction: Direction, + { containment, distalPosition }: ScopeIteratorRequirements, + ) { + const offset = document.offsetAt(position); + const distalOffset = + distalPosition == null ? null : document.offsetAt(distalPosition); + + if (containment === "required") { + return { + start: document.positionAt(offset - 1), + end: document.positionAt(offset + 1), + }; + } + + const proximalShift = containment === "disallowed" ? 1 : -1; + + // FIXME: Don't go all the way to end of document when there is no distalPosition? + // Seems wasteful to query all the way to end of document for something like "next funk" + // Might be better to start smaller and exponentially grow + return direction === "forward" + ? { + start: document.positionAt(offset + proximalShift), + end: + distalOffset == null + ? document.range.end + : document.positionAt(distalOffset + 1), + } + : { + start: + distalOffset == null + ? document.range.start + : document.positionAt(distalOffset - 1), + end: document.positionAt(offset - proximalShift), + }; + } + + private matchToScope(editor: TextEditor, match: QueryMatch): TargetScope { + const contentRange = getNodeRange( + match.captures.find((capture) => capture.name === this.scopeType.type)! + .node, + ); + + return { + editor, + // FIXME: Actually get domain + domain: contentRange, + getTarget: (isReversed) => + new ScopeTypeTarget({ + scopeTypeType: this.scopeType.type, + editor, + isReversed, + contentRange, + // FIXME: Actually get removalRange + removalRange: contentRange, + // FIXME: Other fields here + }), + }; + } +} + +function positionToPoint(start: Position): Point | undefined { + return { row: start.line, column: start.character }; +} diff --git a/packages/cursorless-engine/src/processTargets/modifiers/scopeHandlers/TreeSitterScopeHandler/TreeSitterScopeHandler.ts b/packages/cursorless-engine/src/processTargets/modifiers/scopeHandlers/TreeSitterScopeHandler/TreeSitterScopeHandler.ts deleted file mode 100644 index 0b58df6375..0000000000 --- a/packages/cursorless-engine/src/processTargets/modifiers/scopeHandlers/TreeSitterScopeHandler/TreeSitterScopeHandler.ts +++ /dev/null @@ -1,136 +0,0 @@ -import { - Direction, - NoContainingScopeError, - Position, - ScopeType, - Selection, - SimpleScopeType, - TextEditor, -} from "@cursorless/common"; -import { - NodeMatcher, - ProcessedTargetsContext, -} from "../../../../typings/Types"; -import { Target } from "../../../../typings/target.types"; - -import { selectionWithEditorFromRange } from "../../../../util/selectionUtils"; -import ScopeTypeTarget from "../../../targets/ScopeTypeTarget"; -import { TargetScope } from "../scope.types"; -import { ScopeHandler } from "../scopeHandler.types"; -import { getQueryNodeMatcher } from "./queryNodeMatchers"; - -/** - * Handles scopes that are implemented using tree-sitter. - */ -export default class TreeSitterScopeHandler implements ScopeHandler { - constructor(scopeType: ScopeType, private languageId: string) {} - - scopeType: SimpleScopeType; - iterationScopeType: ScopeType; - - generateScopesRelativeToPosition( - editor: TextEditor, - position: Position, - direction: Direction, - hints?: ScopeIteratorHints | undefined, - ): Iterable { - throw new Error("Method not implemented."); - } - - run(context: ProcessedTargetsContext, target: Target): ScopeTypeTarget[] { - // TODO: Delete this function. It should probably be refactored into some - // common functions that are called by both `getScopeContainingPosition` - // and `getEveryScope` - const languageId = target.editor.document.languageId; - const { type: scopeTypeType } = this.scopeType; - const queryNodeMatcher = getQueryNodeMatcher(languageId, scopeTypeType)!; - const scopeNodes = this.runQueryBasedNodeMatcher( - queryNodeMatcher, - context, - target, - ); - - if (scopeNodes == null) { - throw new NoContainingScopeError(scopeTypeType); - } - - return scopeNodes.map((scope) => { - const { - containingListDelimiter, - leadingDelimiterRange, - trailingDelimiterRange, - removalRange, - interiorRange, - } = scope.context; - - if ( - removalRange != null && - (leadingDelimiterRange != null || trailingDelimiterRange != null) - ) { - throw Error( - "Removal range is mutually exclusive with leading or trailing delimiter range", - ); - } - - const { editor, selection: contentSelection } = scope.selection; - - return new ScopeTypeTarget({ - scopeTypeType, - editor, - isReversed: target.isReversed, - contentRange: contentSelection, - removalRange: removalRange, - delimiter: containingListDelimiter, - interiorRange, - leadingDelimiterRange, - trailingDelimiterRange, - }); - }); - } - - private runQueryBasedNodeMatcher( - queryNodeMatcher: NodeMatcher, - context: ProcessedTargetsContext, - target: Target, - ) { - const selectionWithEditor = getSelectionWithEditor(target); - - const matchResult = queryNodeMatcher( - selectionWithEditor, - context.getTree(selectionWithEditor.editor.document), - this.modifier.type === "everyScope", - ); - - return matchResult - ? matchResult.map((match) => ({ - selection: selectionWithEditorFromRange( - selectionWithEditor, - match.selection.selection, - ), - context: match.selection.context, - })) - : null; - } -} - -function getSelectionWithEditor(target: Target) { - return { - editor: target.editor, - selection: new Selection( - target.contentRange.start, - target.contentRange.end, - ), - }; -} - -export function maybeGetTreeSitterScopeHandler( - scopeType: ScopeType, - languageId: string, -): TreeSitterScopeHandler | undefined { - const queryNodeMatcher = getQueryNodeMatcher(languageId, scopeType.type); - if (queryNodeMatcher != null) { - return new TreeSitterScopeHandler(queryNodeMatcher); - } - - return undefined; -} diff --git a/packages/cursorless-engine/src/processTargets/modifiers/scopeHandlers/TreeSitterScopeHandler/index.ts b/packages/cursorless-engine/src/processTargets/modifiers/scopeHandlers/TreeSitterScopeHandler/index.ts deleted file mode 100644 index a4859d6b05..0000000000 --- a/packages/cursorless-engine/src/processTargets/modifiers/scopeHandlers/TreeSitterScopeHandler/index.ts +++ /dev/null @@ -1,2 +0,0 @@ -export * from "./TreeSitterScopeHandler"; -export { default } from "./TreeSitterScopeHandler"; diff --git a/packages/cursorless-engine/src/processTargets/modifiers/scopeHandlers/TreeSitterScopeHandler/queryBasedSpecification.ts b/packages/cursorless-engine/src/processTargets/modifiers/scopeHandlers/TreeSitterScopeHandler/queryBasedSpecification.ts deleted file mode 100644 index a7c3a202f5..0000000000 --- a/packages/cursorless-engine/src/processTargets/modifiers/scopeHandlers/TreeSitterScopeHandler/queryBasedSpecification.ts +++ /dev/null @@ -1,42 +0,0 @@ -import { SimpleScopeTypeType, simpleScopeTypeTypes } from "@cursorless/common"; -import * as fs from "fs"; -import * as path from "path"; -import { NodeMatcherAlternative } from "../../../../typings/Types"; -import { defaultMatcher } from "./queryNodeMatchers"; - -function getMatchers( - queries: string, -): Partial> { - const matchers: Partial> = - {}; - for (const scopeTypeType of simpleScopeTypeTypes) { - generateMatcher(queries, scopeTypeType, matchers); - } - return matchers; -} - -function generateMatcher( - queries: string, - scopeTypeType: SimpleScopeTypeType, - matchers: Partial>, -) { - if (queries.match(`@${scopeTypeType}[^a-zA-Z]`)) { - const isIterationScopePresent = !!queries.match( - `@${scopeTypeType}.iterationScope[^a-zA-Z]`, - ); - matchers[scopeTypeType as SimpleScopeTypeType] = defaultMatcher( - scopeTypeType, - isIterationScopePresent, - queries, - ); - } -} - -export default function queryBasedSpecification(languageName: string) { - const queryPath = fs.readFileSync( - path.join(__dirname, `../queries/${languageName}/scopeTypes.scm`), - "utf-8", - ); - - return getMatchers(queryPath); -} diff --git a/packages/cursorless-engine/src/processTargets/modifiers/scopeHandlers/TreeSitterScopeHandler/queryNodeMatchers.ts b/packages/cursorless-engine/src/processTargets/modifiers/scopeHandlers/TreeSitterScopeHandler/queryNodeMatchers.ts deleted file mode 100644 index bac160b351..0000000000 --- a/packages/cursorless-engine/src/processTargets/modifiers/scopeHandlers/TreeSitterScopeHandler/queryNodeMatchers.ts +++ /dev/null @@ -1,336 +0,0 @@ -import { - Position, - Range, - Selection, - SimpleScopeTypeType, -} from "@cursorless/common"; -import { intersection } from "lodash"; -import { Point, Query, QueryCapture, SyntaxNode, Tree } from "web-tree-sitter"; -import { SupportedLanguageId } from "../../../../languages/constants"; -import { - NodeMatcher, - NodeMatcherValue, - SelectionExtractor, - SelectionWithEditor, -} from "../../../../typings/Types"; -import { - makeRangeFromPositions, - simpleSelectionExtractor, -} from "../../../../util/nodeSelectors"; -import queryBasedSpecification from "./queryBasedSpecification"; - -let query: Query; - -/** - * - * @param scopeType The scopeType the matcher is responsible for matching. - * @param isIterationScopePresent Indicates whether iteration scope will be defined via the scm file or derived - * by looking at the parent. - * @param scopeQuery The query with node matchers for a specific language. - * @param selector Unused, discard once legacy matching is removed. - * @returns A {@link NodeMatcher} object that can be used to for a specific scopeType. - */ -export function defaultMatcher( - scopeType: string, - isIterationScopePresent: boolean, - scopeQuery: string, - selector: SelectionExtractor = simpleSelectionExtractor, -): NodeMatcher { - return ( - selection: SelectionWithEditor, - treeSitterHook: Tree | SyntaxNode, - siblings: boolean = false, - ): NodeMatcherValue[] | null => { - const tree = treeSitterHook as Tree; - const query = getQuery(tree, scopeQuery); - const rawCaptures = getCapture(selection, tree.rootNode, query, scopeType); - - if (!rawCaptures || rawCaptures.length === 0) { - return null; - } - const selectedCaptures = selectCaptureByRange(rawCaptures, selection); - - if (!selectedCaptures) { - return null; - } - - if (siblings) { - return generateCapturesIfSiblingsPresent( - selectedCaptures, - isIterationScopePresent, - query, - scopeType, - selector, - selection, - ); - } - - const leadingNode = selectedCaptures[0].node; - // TODO: Could we target ES2022 and use .at(-1)? - const trailingNode = selectedCaptures[selectedCaptures.length - 1].node; - return [ - { - // TODO: What should we do here if we are matching multiple nodes for the selection? Which node do we reference? - node: selectedCaptures[0].node, - selection: { - selection: new Selection( - new Position( - leadingNode.startPosition.row, - leadingNode.startPosition.column, - ), - new Position( - trailingNode.endPosition.row, - trailingNode.endPosition.column, - ), - ), - context: {}, - }, - }, - ]; - }; -} - -function generateCapturesIfSiblingsPresent( - selectedCaptures: QueryCapture[], - isIterationScopePresent: boolean, - query: Query, - scopeType: string, - selector: SelectionExtractor, - selection: SelectionWithEditor, -) { - if (selectedCaptures.length > 1) { - throw new Error( - "Cannot match siblings on captures which return a range. Try selecitng a single node.", - ); - } else if (isIterationScopePresent) { - throw new Error("searchScope based queries are not implemented."); - } - - const siblingCaptures = findBySiblingsByParent( - selectedCaptures[0].node, - query, - scopeType, - ); - - return siblingCaptures!.map((c) => { - return { - node: c.node, - selection: selector(selection.editor, c.node), - }; - }); -} - -/** - * Accesses the memoized compiled query. - * @param node Used only to return the tree's language. - * @param scopeQuery The scm query file for a given language. - * @returns Query object - */ -function getQuery(tree: Tree, scopeQuery: string): Query { - if (!query) { - query = tree.getLanguage().query(scopeQuery); - } - return query; -} - -/** - * Prioritize current position of the cursor, one space to the right and then finally one space to the left. - * @param selection Used to derive start and end points for a selection. - * @param root Query is run against the root node. - * @param query Compiled query to run against the parsed tree. - * @param scopeType The scope type that the matcher is responsible for matching. - * @returns Captures that match the scope type, possibly an empty list if there are no matches. - */ -function getCapture( - selection: SelectionWithEditor, - root: SyntaxNode, - query: Query, - scopeType: string, -) { - const startPoint = generatePointFromSelection(selection, "start"); - const endPoint = generatePointFromSelection(selection, "end"); - - const positions = [ - { startPoint, endPoint }, - { - startPoint, - endPoint: { row: endPoint.row, column: endPoint.column + 1 }, - }, - { - startPoint: { row: startPoint.row, column: startPoint.column - 1 }, - endPoint, - }, - ]; - - for (const { startPoint, endPoint } of positions) { - const matches = query - .matches(root, startPoint, endPoint) - .filter(({ captures }) => - captures.some((capture) => capture.name === scopeType), - ); - - if (matches && matches.length > 0) { - return matches; - } - } -} - -/** - * This method takes captures for a given range and scopeType, alongside a selection and then returns - * the relevant captures. If the selection is a single point, only return one capture. If the selection is a - * range, return a leading and trailing capture. If there is no leading capture, bail early. - * - * @param captures The matching nodes for a given scopeType and range. - * @param selection Used to derive start and end points which are matched against a capture. - * @returns The capture for a single point or the captures for a start and end point. - */ -function selectCaptureByRange( - captures: QueryCapture[], - selection: SelectionWithEditor, -): QueryCapture[] | null { - const isSinglePoint = selection.selection.isEmpty; - - const leadingCapture = matchCapturesOnPosition( - captures, - selection.selection.start, - ); - - if (!leadingCapture) { - return null; - } - - if (isSinglePoint) { - return [leadingCapture]; - } else { - const trailingCapture = matchCapturesOnPosition( - captures, - selection.selection.end, - ); - if (!trailingCapture) { - return null; - } - return [leadingCapture, trailingCapture]; - } -} - -/** - * - * @param captures The matching nodes for a given scopeType and range. - * @param position The position to match against. - * @returns The capture furthest down the tree which contains the position. - */ -function matchCapturesOnPosition(captures: QueryCapture[], position: Position) { - let capture; - for (const c of captures) { - const captureRange: Range = makeRangeFromPositions( - c.node.startPosition, - c.node.endPosition, - ); - if (captureRange.contains(position)) { - if (!capture) { - capture = c; - } else { - const leadingCaptureRange = makeRangeFromPositions( - capture.node.startPosition, - capture.node.endPosition, - ); - if (leadingCaptureRange.contains(captureRange)) { - capture = c; - } - } - } - } - return capture; -} - -function generatePointFromSelection( - selection: SelectionWithEditor, - pointType: "start" | "end", -): Point { - return { - row: selection.selection[pointType].line, - column: selection.selection[pointType].character, - }; -} - -/** - * Ported from legacy parent matching. This code is responsible for finding siblings of nodes which do not have - * a `searchScope` defined in the language's .scm query file. - * @param node The matching node from the query and range matching. We will look at this node's parent to find siblings. - * @param query The compiled query which will be run against the parent node. - * @param scopeType The scope type that the matcher is responsible for matching. - * @returns Sibling matches of the node or only the node itself. - */ -function findBySiblingsByParent( - node: SyntaxNode, - query: Query, - scopeType: string, -) { - let parent: SyntaxNode | null = node.parent; - const ids = parent?.namedChildren.map((c) => c.id); - while (parent != null) { - const matches = query - .captures(parent) - .filter( - (capture) => - capture.name === scopeType && ids?.includes(capture.node.id), - ); - if (matches.length > 0) { - return matches; - } - parent = parent.parent; - } - return []; -} - -export function getQueryNodeMatcher( - languageId: string, - scopeTypeType: SimpleScopeTypeType, -): NodeMatcher | undefined { - const matchers = queryBasedMatchers[languageId as SupportedLanguageId]; - - if (matchers == null) { - // Note: When all nodes are matched using this method, return notSupported. - return undefined; - } - - return matchers[scopeTypeType]; -} - -const queryBasedMatchers: Partial< - Record> -> = { - ruby: queryBasedSpecification("ruby"), -}; - -for (const languageId in queryBasedMatchers) { - const queryBasedMatcher = - queryBasedMatchers[languageId as SupportedLanguageId]; - if (queryBasedMatcher) { - ensureUniqueMatchers( - languageMatchers[languageId as SupportedLanguageId], - queryBasedMatcher, - languageId, - ); - } -} - -function ensureUniqueMatchers( - regexMatcher: Record, - queryBasedMatchers: - | Partial> - | undefined, - languageName: string, -) { - const duplicates = intersection( - Object.keys(regexMatcher), - Object.keys(queryBasedMatchers), - ); - if (duplicates.length > 0) { - throw new Error( - `ScopeTypes: [${duplicates.join( - ", ", - )}] for ${languageName} defined via both Regex and Query code paths. Please remove duplicates`, - ); - } -} diff --git a/packages/cursorless-engine/src/processTargets/modifiers/scopeHandlers/index.ts b/packages/cursorless-engine/src/processTargets/modifiers/scopeHandlers/index.ts index b33decb783..eadf0fb7ea 100644 --- a/packages/cursorless-engine/src/processTargets/modifiers/scopeHandlers/index.ts +++ b/packages/cursorless-engine/src/processTargets/modifiers/scopeHandlers/index.ts @@ -13,7 +13,7 @@ export { default as TokenScopeHandler } from "./TokenScopeHandler"; export * from "./DocumentScopeHandler"; export { default as DocumentScopeHandler } from "./DocumentScopeHandler"; export * from "./TreeSitterScopeHandler"; -export { default as TreeSitterScopeHandler } from "./TreeSitterScopeHandler"; +export { TreeSitterScopeHandler } from "./TreeSitterScopeHandler"; export * from "./OneOfScopeHandler"; export { default as OneOfScopeHandler } from "./OneOfScopeHandler"; export * from "./ParagraphScopeHandler"; diff --git a/packages/cursorless-engine/src/typings/TreeSitter.ts b/packages/cursorless-engine/src/typings/TreeSitter.ts index afdaac357f..3169d1a1a3 100644 --- a/packages/cursorless-engine/src/typings/TreeSitter.ts +++ b/packages/cursorless-engine/src/typings/TreeSitter.ts @@ -1,17 +1,22 @@ import { Range, TextDocument } from "@cursorless/common"; -import { SyntaxNode, Tree } from "web-tree-sitter"; +import { Language, SyntaxNode, Tree } from "web-tree-sitter"; export interface TreeSitter { /** * Function to access nodes in the tree sitter. */ - readonly getNodeAtLocation: ( - document: TextDocument, - range: Range, - ) => SyntaxNode; + getNodeAtLocation(document: TextDocument, range: Range): SyntaxNode; /** * Function to access the tree sitter tree. */ - readonly getTree: (document: TextDocument) => Tree; + getTree(document: TextDocument): Tree; + + /** + * Gets a language if it is loaded + * + * @param languageId The language id of the language to load + * @returns The language if it is already loaded + */ + getLanguage(languageId: string): Language | undefined; } diff --git a/packages/cursorless-vscode-e2e/src/suite/queryNodeMatchers.test.ts b/packages/cursorless-vscode-e2e/src/suite/queryNodeMatchers.test.ts deleted file mode 100644 index 6dac268855..0000000000 --- a/packages/cursorless-vscode-e2e/src/suite/queryNodeMatchers.test.ts +++ /dev/null @@ -1,161 +0,0 @@ -import { getParseTreeApi, openNewEditor } from "@cursorless/vscode-common"; -import * as assert from "assert"; -import { Position } from "vscode"; -import { Tree } from "web-tree-sitter"; - -suite("queryNodeMatcher", async () => { - suite("happy path", async () => { - const matcher = defaultMatcher("comment", false, `(comment) @comment`); - const nodeType = "comment"; - - test("match single", async () => { - const code = "# hello world"; - - const { selection, tree } = await getNodeAndEditor(code); - assertMatch(matcher, tree, nodeType, selection, 1); - }); - - test("match multiple by parent", async () => { - const code = `# hello world -variable_def = "yep" -# hello world -# hello world`; - const { selection, tree } = await getNodeAndEditor(code); - assertMatch(matcher, tree, nodeType, selection, 3, { - includeSiblings: true, - }); - }); - }); - - suite("expands range", async () => { - const matcher = defaultMatcher("comment", false, `(comment) @comment`); - const nodeType = "comment"; - const cursorPositions = { - start: new Position(0, 0), - end: new Position(1, 2), - }; - - test("matches with selection across two node the same type", async () => { - const code = `# hello -# world`; - const { selection, tree } = await getNodeAndEditor(code, cursorPositions); - const matches = assertMatch(matcher, tree, nodeType, selection, 1); - assert.equal(matches![0].selection.selection.isSingleLine, false); - }); - - test("no match with selection across two node the different type", async () => { - const code = `# hello -hello_world()`; - const { selection, tree } = await getNodeAndEditor(code, cursorPositions); - assertMatch(matcher, tree, nodeType, selection, 0); - }); - }); - - suite("non-matches due to positions", async () => { - const matcher = defaultMatcher( - "functionCall", - false, - `(call) @functionCall`, - ); - - // Use a node type that does not take up the entire line following the it. - const code = " hello_world() "; - const nonMatchPositions = [ - { - start: new Position(0, 0), - end: new Position(0, 0), - message: "Empty selection before the scope", - }, - { - start: new Position(0, code.length - 1), - end: new Position(0, code.length - 1), - message: "Empty selection after the scope", - }, - { - start: new Position(0, 0), - end: new Position(0, 1), - message: "Non-empty selection before the scope", - }, - { - start: new Position(0, 0), - end: new Position(0, 1), - message: "Non-empty selection abutting scope", - }, - { - start: new Position(0, code.length - 2), - end: new Position(0, code.length - 1), - message: "Non-empty selection after the scope", - }, - { - start: new Position(0, 0), - end: new Position(0, 5), - message: "Non-empty selection starting before and ending within scope", - }, - { - start: new Position(0, 5), - end: new Position(0, code.length - 1), - message: - "Non-empty selection starting within scope and ending outside of scope", - }, - ]; - for (const position of nonMatchPositions) { - test(`empty selection in whitespace before scope does not match: ${position.message}`, async () => { - const nodeType = "comment"; - const { selection, tree } = await getNodeAndEditor(code, position); - assertMatch(matcher, tree, nodeType, selection, 0); - }); - } - }); - - test("match multiple by searchScope throws error", async () => { - const code = "# hello world"; - const matcher = defaultMatcher("comment", true, `(comment) @comment`); - const { selection, tree } = await getNodeAndEditor(code); - assert.throws(() => { - matcher(selection, tree, true); - }); - }); -}); - -const getNodeAndEditor = async ( - code: string, - cursorPositions: { start: Position; end: Position } = { - start: new Position(0, 0), - end: new Position(0, 0), - }, -): Promise<{ selection: SelectionWithEditor; tree: Tree }> => { - const editor = await openNewEditor(code, "ruby"); - const { getTree } = await getParseTreeApi(); - const tree = getTree(editor.document); - - const selectionWithEditor = selectionWithEditorFromPositions( - { selection: editor.selection, editor }, - cursorPositions.start, - cursorPositions.end, - ); - - return { - selection: selectionWithEditor, - tree: tree, - }; -}; - -const assertMatch = ( - matcher: NodeMatcher, - tree: Tree, - nodeType: string, - selectionWithEditor: SelectionWithEditor, - expectedMatches: number, - options: { includeSiblings?: boolean } = { includeSiblings: false }, -) => { - const matches = matcher(selectionWithEditor, tree, options?.includeSiblings); - if (expectedMatches === 0) { - assert.equal(matches, null); - } else { - assert.equal(expectedMatches, matches!.length); - for (const match of matches!) { - assert.equal(nodeType, match.node.type); - } - } - return matches; -}; diff --git a/packages/cursorless-vscode/src/extension.ts b/packages/cursorless-vscode/src/extension.ts index b13e6cf546..352bb7a88e 100644 --- a/packages/cursorless-vscode/src/extension.ts +++ b/packages/cursorless-vscode/src/extension.ts @@ -130,6 +130,8 @@ function createTreeSitter(parseTreeApi: ParseTreeApi): TreeSitter { getTree(document: TextDocument) { return parseTreeApi.getTreeForUri(document.uri); }, + + getLanguage: parseTreeApi.getLanguage, }; } diff --git a/packages/cursorless-vscode/src/scripts/populateDist/assets.ts b/packages/cursorless-vscode/src/scripts/populateDist/assets.ts index a2b6e16312..b628120496 100644 --- a/packages/cursorless-vscode/src/scripts/populateDist/assets.ts +++ b/packages/cursorless-vscode/src/scripts/populateDist/assets.ts @@ -30,6 +30,10 @@ export const assets: Asset[] = [ source: "../../third-party-licenses.csv", destination: "third-party-licenses.csv", }, + { + source: "../../queries", + destination: "queries", + }, { generateContent: generateBuildInfo, destination: "build-info.json", diff --git a/packages/vscode-common/src/getExtensionApi.ts b/packages/vscode-common/src/getExtensionApi.ts index f7747bd26a..9c5bce4ff1 100644 --- a/packages/vscode-common/src/getExtensionApi.ts +++ b/packages/vscode-common/src/getExtensionApi.ts @@ -12,7 +12,7 @@ import type { TextEditor, } from "@cursorless/common"; import * as vscode from "vscode"; -import type { SyntaxNode, Tree } from "web-tree-sitter"; +import type { Language, SyntaxNode, Tree } from "web-tree-sitter"; export interface TestHelpers { ide: NormalizedIDE; @@ -60,6 +60,7 @@ export interface ParseTreeApi { getNodeAtLocation(location: vscode.Location): SyntaxNode; getTreeForUri(uri: vscode.Uri): Tree; loadLanguage: (languageId: string) => Promise; + getLanguage(languageId: string): Language | undefined; } export async function getExtensionApi(extensionId: string) { diff --git a/typings/treeSitter.d.ts b/typings/treeSitter.d.ts index 621ea7c11e..789f8d37d1 100644 --- a/typings/treeSitter.d.ts +++ b/typings/treeSitter.d.ts @@ -140,7 +140,7 @@ declare module "web-tree-sitter" { walk(): TreeCursor; getChangedRanges(other: Tree): Range[]; getEditedRange(other: Tree): Range; - getLanguage(): any; + getLanguage(): Language; } class Language { From 5cbe3dacba75f94ccfaff01e2f6edf3c534a1cb1 Mon Sep 17 00:00:00 2001 From: Pokey Rule <755842+pokey@users.noreply.github.com> Date: Fri, 14 Apr 2023 12:31:13 +0100 Subject: [PATCH 03/25] Cleanup --- .../command/PartialTargetDescriptor.types.ts | 2 +- .../scopeHandlers/TreeSitterScopeHandler.ts | 17 +++++++---------- .../recorded/queryBasedMatchers/changeList.yml | 13 ++++++------- .../queryBasedMatchers/changeList10.yml | 13 ++++++------- .../queryBasedMatchers/changeList11.yml | 13 ++++++------- .../queryBasedMatchers/changeList12.yml | 13 ++++++------- .../queryBasedMatchers/changeList13.yml | 13 ++++++------- .../queryBasedMatchers/changeList14.yml | 13 ++++++------- .../recorded/queryBasedMatchers/changeList2.yml | 13 ++++++------- .../recorded/queryBasedMatchers/changeList3.yml | 13 ++++++------- .../recorded/queryBasedMatchers/changeList4.yml | 13 ++++++------- .../recorded/queryBasedMatchers/changeList5.yml | 13 ++++++------- .../recorded/queryBasedMatchers/changeList6.yml | 13 ++++++------- .../recorded/queryBasedMatchers/changeList7.yml | 13 ++++++------- .../recorded/queryBasedMatchers/changeList8.yml | 13 ++++++------- .../recorded/queryBasedMatchers/changeList9.yml | 13 ++++++------- 16 files changed, 92 insertions(+), 109 deletions(-) diff --git a/packages/common/src/types/command/PartialTargetDescriptor.types.ts b/packages/common/src/types/command/PartialTargetDescriptor.types.ts index cc2f9ce346..c0f22b3046 100644 --- a/packages/common/src/types/command/PartialTargetDescriptor.types.ts +++ b/packages/common/src/types/command/PartialTargetDescriptor.types.ts @@ -132,7 +132,7 @@ export const simpleScopeTypeTypes = [ "url", ] as const; -export type SimpleScopeTypeType = typeof simpleScopeTypeTypes[number]; +export type SimpleScopeTypeType = (typeof simpleScopeTypeTypes)[number]; export interface SimpleScopeType { type: SimpleScopeTypeType; diff --git a/packages/cursorless-engine/src/processTargets/modifiers/scopeHandlers/TreeSitterScopeHandler.ts b/packages/cursorless-engine/src/processTargets/modifiers/scopeHandlers/TreeSitterScopeHandler.ts index 74a4b7417b..2db6a6ba10 100644 --- a/packages/cursorless-engine/src/processTargets/modifiers/scopeHandlers/TreeSitterScopeHandler.ts +++ b/packages/cursorless-engine/src/processTargets/modifiers/scopeHandlers/TreeSitterScopeHandler.ts @@ -7,12 +7,13 @@ import { TextEditor, } from "@cursorless/common"; +import { Point, Query, QueryMatch } from "web-tree-sitter"; import { TreeSitter } from "../../.."; +import { getNodeRange } from "../../../util/nodeSelectors"; import ScopeTypeTarget from "../../targets/ScopeTypeTarget"; import BaseScopeHandler from "./BaseScopeHandler"; +import { compareTargetScopes } from "./compareTargetScopes"; import { TargetScope } from "./scope.types"; -import { Point, Query, QueryMatch } from "web-tree-sitter"; -import { getNodeRange } from "../../../util/nodeSelectors"; import { ScopeIteratorRequirements } from "./scopeHandler.types"; /** @@ -48,7 +49,7 @@ export class TreeSitterScopeHandler extends BaseScopeHandler { hints, ); - const matches = this.query + yield* this.query .matches( this.treeSitter.getTree(document).rootNode, positionToPoint(start), @@ -56,13 +57,9 @@ export class TreeSitterScopeHandler extends BaseScopeHandler { ) .filter(({ captures }) => captures.some((capture) => capture.name === this.scopeType.type), - ); - - // FIXME: Sort? - - for (const match of matches) { - yield this.matchToScope(editor, match); - } + ) + .map((match) => this.matchToScope(editor, match)) + .sort((a, b) => compareTargetScopes(direction, position, a, b)); } private getQueryRange( diff --git a/packages/cursorless-vscode-e2e/src/suite/fixtures/recorded/queryBasedMatchers/changeList.yml b/packages/cursorless-vscode-e2e/src/suite/fixtures/recorded/queryBasedMatchers/changeList.yml index 3dfcc5774e..d2db8a800b 100644 --- a/packages/cursorless-vscode-e2e/src/suite/fixtures/recorded/queryBasedMatchers/changeList.yml +++ b/packages/cursorless-vscode-e2e/src/suite/fixtures/recorded/queryBasedMatchers/changeList.yml @@ -1,11 +1,14 @@ languageId: ruby command: - version: 1 + version: 5 spokenForm: change list - action: clearAndSetSelection + action: {name: clearAndSetSelection} targets: - type: primitive - modifier: {type: containingScope, scopeType: list, includeSiblings: false} + modifiers: + - type: containingScope + scopeType: {type: list} + usePrePhraseSnapshot: false initialState: documentContents: | ["hello"] @@ -21,7 +24,3 @@ finalState: selections: - anchor: {line: 0, character: 0} active: {line: 0, character: 0} - thatMark: - - anchor: {line: 0, character: 0} - active: {line: 0, character: 0} -fullTargets: [{type: primitive, mark: {type: cursor}, selectionType: token, position: contents, insideOutsideType: inside, modifier: {type: containingScope, scopeType: list, includeSiblings: false}, isImplicit: false}] diff --git a/packages/cursorless-vscode-e2e/src/suite/fixtures/recorded/queryBasedMatchers/changeList10.yml b/packages/cursorless-vscode-e2e/src/suite/fixtures/recorded/queryBasedMatchers/changeList10.yml index e79385bce6..386380717f 100644 --- a/packages/cursorless-vscode-e2e/src/suite/fixtures/recorded/queryBasedMatchers/changeList10.yml +++ b/packages/cursorless-vscode-e2e/src/suite/fixtures/recorded/queryBasedMatchers/changeList10.yml @@ -1,11 +1,14 @@ languageId: ruby command: - version: 1 + version: 5 spokenForm: change list - action: clearAndSetSelection + action: {name: clearAndSetSelection} targets: - type: primitive - modifier: {type: containingScope, scopeType: list, includeSiblings: false} + modifiers: + - type: containingScope + scopeType: {type: list} + usePrePhraseSnapshot: false initialState: documentContents: "[1, 2, 3, [4, 5, 6, [7, 8, 9]]]" selections: @@ -17,7 +20,3 @@ finalState: selections: - anchor: {line: 0, character: 10} active: {line: 0, character: 10} - thatMark: - - anchor: {line: 0, character: 10} - active: {line: 0, character: 10} -fullTargets: [{type: primitive, mark: {type: cursor}, selectionType: token, position: contents, insideOutsideType: inside, modifier: {type: containingScope, scopeType: list, includeSiblings: false}, isImplicit: false}] diff --git a/packages/cursorless-vscode-e2e/src/suite/fixtures/recorded/queryBasedMatchers/changeList11.yml b/packages/cursorless-vscode-e2e/src/suite/fixtures/recorded/queryBasedMatchers/changeList11.yml index 79d54ce701..309f715d45 100644 --- a/packages/cursorless-vscode-e2e/src/suite/fixtures/recorded/queryBasedMatchers/changeList11.yml +++ b/packages/cursorless-vscode-e2e/src/suite/fixtures/recorded/queryBasedMatchers/changeList11.yml @@ -1,11 +1,14 @@ languageId: ruby command: - version: 1 + version: 5 spokenForm: change list - action: clearAndSetSelection + action: {name: clearAndSetSelection} targets: - type: primitive - modifier: {type: containingScope, scopeType: list, includeSiblings: false} + modifiers: + - type: containingScope + scopeType: {type: list} + usePrePhraseSnapshot: false initialState: documentContents: "[1, 2, 3, [4, 5, 6, [7, 8, 9]]]" selections: @@ -17,7 +20,3 @@ finalState: selections: - anchor: {line: 0, character: 0} active: {line: 0, character: 0} - thatMark: - - anchor: {line: 0, character: 0} - active: {line: 0, character: 0} -fullTargets: [{type: primitive, mark: {type: cursor}, selectionType: token, position: contents, insideOutsideType: inside, modifier: {type: containingScope, scopeType: list, includeSiblings: false}, isImplicit: false}] diff --git a/packages/cursorless-vscode-e2e/src/suite/fixtures/recorded/queryBasedMatchers/changeList12.yml b/packages/cursorless-vscode-e2e/src/suite/fixtures/recorded/queryBasedMatchers/changeList12.yml index dadfc3bdaa..e159146172 100644 --- a/packages/cursorless-vscode-e2e/src/suite/fixtures/recorded/queryBasedMatchers/changeList12.yml +++ b/packages/cursorless-vscode-e2e/src/suite/fixtures/recorded/queryBasedMatchers/changeList12.yml @@ -1,11 +1,14 @@ languageId: ruby command: - version: 1 + version: 5 spokenForm: change list - action: clearAndSetSelection + action: {name: clearAndSetSelection} targets: - type: primitive - modifier: {type: containingScope, scopeType: list, includeSiblings: false} + modifiers: + - type: containingScope + scopeType: {type: list} + usePrePhraseSnapshot: false initialState: documentContents: "[1, 2, 3, [4, 5, 6, [7, 8, 9]]]" selections: @@ -17,7 +20,3 @@ finalState: selections: - anchor: {line: 0, character: 0} active: {line: 0, character: 0} - thatMark: - - anchor: {line: 0, character: 0} - active: {line: 0, character: 0} -fullTargets: [{type: primitive, mark: {type: cursor}, selectionType: token, position: contents, insideOutsideType: inside, modifier: {type: containingScope, scopeType: list, includeSiblings: false}, isImplicit: false}] diff --git a/packages/cursorless-vscode-e2e/src/suite/fixtures/recorded/queryBasedMatchers/changeList13.yml b/packages/cursorless-vscode-e2e/src/suite/fixtures/recorded/queryBasedMatchers/changeList13.yml index a104d83cab..71fbb522d9 100644 --- a/packages/cursorless-vscode-e2e/src/suite/fixtures/recorded/queryBasedMatchers/changeList13.yml +++ b/packages/cursorless-vscode-e2e/src/suite/fixtures/recorded/queryBasedMatchers/changeList13.yml @@ -1,11 +1,14 @@ languageId: ruby command: - version: 1 + version: 5 spokenForm: change list - action: clearAndSetSelection + action: {name: clearAndSetSelection} targets: - type: primitive - modifier: {type: containingScope, scopeType: list, includeSiblings: false} + modifiers: + - type: containingScope + scopeType: {type: list} + usePrePhraseSnapshot: false initialState: documentContents: |- [1, 2, 3, [4, 5, 6, [7, 8, 9]]] @@ -20,7 +23,3 @@ finalState: selections: - anchor: {line: 0, character: 20} active: {line: 0, character: 20} - thatMark: - - anchor: {line: 0, character: 20} - active: {line: 0, character: 20} -fullTargets: [{type: primitive, mark: {type: cursor}, selectionType: token, position: contents, insideOutsideType: inside, modifier: {type: containingScope, scopeType: list, includeSiblings: false}, isImplicit: false}] diff --git a/packages/cursorless-vscode-e2e/src/suite/fixtures/recorded/queryBasedMatchers/changeList14.yml b/packages/cursorless-vscode-e2e/src/suite/fixtures/recorded/queryBasedMatchers/changeList14.yml index 14f81cf932..816d43d339 100644 --- a/packages/cursorless-vscode-e2e/src/suite/fixtures/recorded/queryBasedMatchers/changeList14.yml +++ b/packages/cursorless-vscode-e2e/src/suite/fixtures/recorded/queryBasedMatchers/changeList14.yml @@ -1,11 +1,14 @@ languageId: ruby command: - version: 1 + version: 5 spokenForm: change list - action: clearAndSetSelection + action: {name: clearAndSetSelection} targets: - type: primitive - modifier: {type: containingScope, scopeType: list, includeSiblings: false} + modifiers: + - type: containingScope + scopeType: {type: list} + usePrePhraseSnapshot: false initialState: documentContents: |- [1, 2, 3, [4, 5, 6, [7, 8, 9]]] @@ -20,7 +23,3 @@ finalState: selections: - anchor: {line: 0, character: 10} active: {line: 0, character: 10} - thatMark: - - anchor: {line: 0, character: 10} - active: {line: 0, character: 10} -fullTargets: [{type: primitive, mark: {type: cursor}, selectionType: token, position: contents, insideOutsideType: inside, modifier: {type: containingScope, scopeType: list, includeSiblings: false}, isImplicit: false}] diff --git a/packages/cursorless-vscode-e2e/src/suite/fixtures/recorded/queryBasedMatchers/changeList2.yml b/packages/cursorless-vscode-e2e/src/suite/fixtures/recorded/queryBasedMatchers/changeList2.yml index e6a1ef1c4f..2519e7abb7 100644 --- a/packages/cursorless-vscode-e2e/src/suite/fixtures/recorded/queryBasedMatchers/changeList2.yml +++ b/packages/cursorless-vscode-e2e/src/suite/fixtures/recorded/queryBasedMatchers/changeList2.yml @@ -1,11 +1,14 @@ languageId: ruby command: - version: 1 + version: 5 spokenForm: change list - action: clearAndSetSelection + action: {name: clearAndSetSelection} targets: - type: primitive - modifier: {type: containingScope, scopeType: list, includeSiblings: false} + modifiers: + - type: containingScope + scopeType: {type: list} + usePrePhraseSnapshot: false initialState: documentContents: "[\"hello\"]" selections: @@ -17,7 +20,3 @@ finalState: selections: - anchor: {line: 0, character: 0} active: {line: 0, character: 0} - thatMark: - - anchor: {line: 0, character: 0} - active: {line: 0, character: 0} -fullTargets: [{type: primitive, mark: {type: cursor}, selectionType: token, position: contents, insideOutsideType: inside, modifier: {type: containingScope, scopeType: list, includeSiblings: false}, isImplicit: false}] diff --git a/packages/cursorless-vscode-e2e/src/suite/fixtures/recorded/queryBasedMatchers/changeList3.yml b/packages/cursorless-vscode-e2e/src/suite/fixtures/recorded/queryBasedMatchers/changeList3.yml index 898f5a2ab3..a5364b262e 100644 --- a/packages/cursorless-vscode-e2e/src/suite/fixtures/recorded/queryBasedMatchers/changeList3.yml +++ b/packages/cursorless-vscode-e2e/src/suite/fixtures/recorded/queryBasedMatchers/changeList3.yml @@ -1,11 +1,14 @@ languageId: ruby command: - version: 1 + version: 5 spokenForm: change list - action: clearAndSetSelection + action: {name: clearAndSetSelection} targets: - type: primitive - modifier: {type: containingScope, scopeType: list, includeSiblings: false} + modifiers: + - type: containingScope + scopeType: {type: list} + usePrePhraseSnapshot: false initialState: documentContents: "[\"hello\"]" selections: @@ -17,7 +20,3 @@ finalState: selections: - anchor: {line: 0, character: 0} active: {line: 0, character: 0} - thatMark: - - anchor: {line: 0, character: 0} - active: {line: 0, character: 0} -fullTargets: [{type: primitive, mark: {type: cursor}, selectionType: token, position: contents, insideOutsideType: inside, modifier: {type: containingScope, scopeType: list, includeSiblings: false}, isImplicit: false}] diff --git a/packages/cursorless-vscode-e2e/src/suite/fixtures/recorded/queryBasedMatchers/changeList4.yml b/packages/cursorless-vscode-e2e/src/suite/fixtures/recorded/queryBasedMatchers/changeList4.yml index d4dddd3e44..9675960b69 100644 --- a/packages/cursorless-vscode-e2e/src/suite/fixtures/recorded/queryBasedMatchers/changeList4.yml +++ b/packages/cursorless-vscode-e2e/src/suite/fixtures/recorded/queryBasedMatchers/changeList4.yml @@ -1,11 +1,14 @@ languageId: ruby command: - version: 1 + version: 5 spokenForm: change list - action: clearAndSetSelection + action: {name: clearAndSetSelection} targets: - type: primitive - modifier: {type: containingScope, scopeType: list, includeSiblings: false} + modifiers: + - type: containingScope + scopeType: {type: list} + usePrePhraseSnapshot: false initialState: documentContents: "[\"hello\"]" selections: @@ -17,7 +20,3 @@ finalState: selections: - anchor: {line: 0, character: 0} active: {line: 0, character: 0} - thatMark: - - anchor: {line: 0, character: 0} - active: {line: 0, character: 0} -fullTargets: [{type: primitive, mark: {type: cursor}, selectionType: token, position: contents, insideOutsideType: inside, modifier: {type: containingScope, scopeType: list, includeSiblings: false}, isImplicit: false}] diff --git a/packages/cursorless-vscode-e2e/src/suite/fixtures/recorded/queryBasedMatchers/changeList5.yml b/packages/cursorless-vscode-e2e/src/suite/fixtures/recorded/queryBasedMatchers/changeList5.yml index ea2d628908..9430689c87 100644 --- a/packages/cursorless-vscode-e2e/src/suite/fixtures/recorded/queryBasedMatchers/changeList5.yml +++ b/packages/cursorless-vscode-e2e/src/suite/fixtures/recorded/queryBasedMatchers/changeList5.yml @@ -1,11 +1,14 @@ languageId: ruby command: - version: 1 + version: 5 spokenForm: change list - action: clearAndSetSelection + action: {name: clearAndSetSelection} targets: - type: primitive - modifier: {type: containingScope, scopeType: list, includeSiblings: false} + modifiers: + - type: containingScope + scopeType: {type: list} + usePrePhraseSnapshot: false initialState: documentContents: "[\"hello\"]" selections: @@ -17,7 +20,3 @@ finalState: selections: - anchor: {line: 0, character: 0} active: {line: 0, character: 0} - thatMark: - - anchor: {line: 0, character: 0} - active: {line: 0, character: 0} -fullTargets: [{type: primitive, mark: {type: cursor}, selectionType: token, position: contents, insideOutsideType: inside, modifier: {type: containingScope, scopeType: list, includeSiblings: false}, isImplicit: false}] diff --git a/packages/cursorless-vscode-e2e/src/suite/fixtures/recorded/queryBasedMatchers/changeList6.yml b/packages/cursorless-vscode-e2e/src/suite/fixtures/recorded/queryBasedMatchers/changeList6.yml index 2fb109eed9..b520ada47b 100644 --- a/packages/cursorless-vscode-e2e/src/suite/fixtures/recorded/queryBasedMatchers/changeList6.yml +++ b/packages/cursorless-vscode-e2e/src/suite/fixtures/recorded/queryBasedMatchers/changeList6.yml @@ -1,11 +1,14 @@ languageId: ruby command: - version: 1 + version: 5 spokenForm: change list - action: clearAndSetSelection + action: {name: clearAndSetSelection} targets: - type: primitive - modifier: {type: containingScope, scopeType: list, includeSiblings: false} + modifiers: + - type: containingScope + scopeType: {type: list} + usePrePhraseSnapshot: false initialState: documentContents: "[\"hello\"] " selections: @@ -17,7 +20,3 @@ finalState: selections: - anchor: {line: 0, character: 0} active: {line: 0, character: 0} - thatMark: - - anchor: {line: 0, character: 0} - active: {line: 0, character: 0} -fullTargets: [{type: primitive, mark: {type: cursor}, selectionType: token, position: contents, insideOutsideType: inside, modifier: {type: containingScope, scopeType: list, includeSiblings: false}, isImplicit: false}] diff --git a/packages/cursorless-vscode-e2e/src/suite/fixtures/recorded/queryBasedMatchers/changeList7.yml b/packages/cursorless-vscode-e2e/src/suite/fixtures/recorded/queryBasedMatchers/changeList7.yml index 7563500936..e4c4d1e277 100644 --- a/packages/cursorless-vscode-e2e/src/suite/fixtures/recorded/queryBasedMatchers/changeList7.yml +++ b/packages/cursorless-vscode-e2e/src/suite/fixtures/recorded/queryBasedMatchers/changeList7.yml @@ -1,11 +1,14 @@ languageId: ruby command: - version: 1 + version: 5 spokenForm: change list - action: clearAndSetSelection + action: {name: clearAndSetSelection} targets: - type: primitive - modifier: {type: containingScope, scopeType: list, includeSiblings: false} + modifiers: + - type: containingScope + scopeType: {type: list} + usePrePhraseSnapshot: false initialState: documentContents: "[\"hello\"] " selections: @@ -17,7 +20,3 @@ finalState: selections: - anchor: {line: 0, character: 0} active: {line: 0, character: 0} - thatMark: - - anchor: {line: 0, character: 0} - active: {line: 0, character: 0} -fullTargets: [{type: primitive, mark: {type: cursor}, selectionType: token, position: contents, insideOutsideType: inside, modifier: {type: containingScope, scopeType: list, includeSiblings: false}, isImplicit: false}] diff --git a/packages/cursorless-vscode-e2e/src/suite/fixtures/recorded/queryBasedMatchers/changeList8.yml b/packages/cursorless-vscode-e2e/src/suite/fixtures/recorded/queryBasedMatchers/changeList8.yml index 038466d9ca..0379f5d138 100644 --- a/packages/cursorless-vscode-e2e/src/suite/fixtures/recorded/queryBasedMatchers/changeList8.yml +++ b/packages/cursorless-vscode-e2e/src/suite/fixtures/recorded/queryBasedMatchers/changeList8.yml @@ -1,11 +1,14 @@ languageId: ruby command: - version: 1 + version: 5 spokenForm: change list - action: clearAndSetSelection + action: {name: clearAndSetSelection} targets: - type: primitive - modifier: {type: containingScope, scopeType: list, includeSiblings: false} + modifiers: + - type: containingScope + scopeType: {type: list} + usePrePhraseSnapshot: false initialState: documentContents: "[1,2,3, [4,5,6, [7, 8, 9]]]" selections: @@ -17,7 +20,3 @@ finalState: selections: - anchor: {line: 0, character: 16} active: {line: 0, character: 16} - thatMark: - - anchor: {line: 0, character: 16} - active: {line: 0, character: 16} -fullTargets: [{type: primitive, mark: {type: cursor}, selectionType: token, position: contents, insideOutsideType: inside, modifier: {type: containingScope, scopeType: list, includeSiblings: false}, isImplicit: false}] diff --git a/packages/cursorless-vscode-e2e/src/suite/fixtures/recorded/queryBasedMatchers/changeList9.yml b/packages/cursorless-vscode-e2e/src/suite/fixtures/recorded/queryBasedMatchers/changeList9.yml index edf9e5cbd0..146b3b120c 100644 --- a/packages/cursorless-vscode-e2e/src/suite/fixtures/recorded/queryBasedMatchers/changeList9.yml +++ b/packages/cursorless-vscode-e2e/src/suite/fixtures/recorded/queryBasedMatchers/changeList9.yml @@ -1,11 +1,14 @@ languageId: ruby command: - version: 1 + version: 5 spokenForm: change list - action: clearAndSetSelection + action: {name: clearAndSetSelection} targets: - type: primitive - modifier: {type: containingScope, scopeType: list, includeSiblings: false} + modifiers: + - type: containingScope + scopeType: {type: list} + usePrePhraseSnapshot: false initialState: documentContents: "[1, 2, 3, [4, 5, 6, [7, 8, 9]]]" selections: @@ -17,7 +20,3 @@ finalState: selections: - anchor: {line: 0, character: 20} active: {line: 0, character: 20} - thatMark: - - anchor: {line: 0, character: 20} - active: {line: 0, character: 20} -fullTargets: [{type: primitive, mark: {type: cursor}, selectionType: token, position: contents, insideOutsideType: inside, modifier: {type: containingScope, scopeType: list, includeSiblings: false}, isImplicit: false}] From df2736f23c55d69fb29a4c3f4153d3247e422e63 Mon Sep 17 00:00:00 2001 From: Pokey Rule <755842+pokey@users.noreply.github.com> Date: Fri, 14 Apr 2023 12:48:29 +0100 Subject: [PATCH 04/25] remove another test file for now --- .../src/suite/queryBasedSpecification.test.ts | 3 --- 1 file changed, 3 deletions(-) delete mode 100644 packages/cursorless-vscode-e2e/src/suite/queryBasedSpecification.test.ts diff --git a/packages/cursorless-vscode-e2e/src/suite/queryBasedSpecification.test.ts b/packages/cursorless-vscode-e2e/src/suite/queryBasedSpecification.test.ts deleted file mode 100644 index c9aa184936..0000000000 --- a/packages/cursorless-vscode-e2e/src/suite/queryBasedSpecification.test.ts +++ /dev/null @@ -1,3 +0,0 @@ -suite("queryBasedSpecification", async function () { - test("generates the correct matchers", async () => {}); -}); From 04ab57f086528c9c62b21124ee5f89c8ddba8e02 Mon Sep 17 00:00:00 2001 From: Pokey Rule <755842+pokey@users.noreply.github.com> Date: Fri, 14 Apr 2023 12:56:24 +0100 Subject: [PATCH 05/25] revert target descriptor change --- .../command/PartialTargetDescriptor.types.ts | 115 +++++++++--------- 1 file changed, 56 insertions(+), 59 deletions(-) diff --git a/packages/common/src/types/command/PartialTargetDescriptor.types.ts b/packages/common/src/types/command/PartialTargetDescriptor.types.ts index c0f22b3046..cfe2ad514f 100644 --- a/packages/common/src/types/command/PartialTargetDescriptor.types.ts +++ b/packages/common/src/types/command/PartialTargetDescriptor.types.ts @@ -72,67 +72,64 @@ export type SurroundingPairName = | SimpleSurroundingPairName | ComplexSurroundingPairName; -export const simpleScopeTypeTypes = [ - "argumentOrParameter", - "anonymousFunction", - "attribute", - "branch", - "class", - "className", - "collectionItem", - "collectionKey", - "comment", - "functionCall", - "functionCallee", - "functionName", - "ifStatement", - "list", - "map", - "name", - "namedFunction", - "regularExpression", - "statement", - "string", - "type", - "value", - "condition", - "section", - "sectionLevelOne", - "sectionLevelTwo", - "sectionLevelThree", - "sectionLevelFour", - "sectionLevelFive", - "sectionLevelSix", - "selector", - "switchStatementSubject", - "unit", - "xmlBothTags", - "xmlElement", - "xmlEndTag", - "xmlStartTag", +export type SimpleScopeTypeType = + | "argumentOrParameter" + | "anonymousFunction" + | "attribute" + | "branch" + | "class" + | "className" + | "collectionItem" + | "collectionKey" + | "comment" + | "functionCall" + | "functionCallee" + | "functionName" + | "ifStatement" + | "list" + | "map" + | "name" + | "namedFunction" + | "regularExpression" + | "statement" + | "string" + | "type" + | "value" + | "condition" + | "section" + | "sectionLevelOne" + | "sectionLevelTwo" + | "sectionLevelThree" + | "sectionLevelFour" + | "sectionLevelFive" + | "sectionLevelSix" + | "selector" + | "switchStatementSubject" + | "unit" + | "xmlBothTags" + | "xmlElement" + | "xmlEndTag" + | "xmlStartTag" // Latex scope types - "part", - "chapter", - "subSection", - "subSubSection", - "namedParagraph", - "subParagraph", - "environment", + | "part" + | "chapter" + | "subSection" + | "subSubSection" + | "namedParagraph" + | "subParagraph" + | "environment" // Text based scopes - "token", - "line", - "notebookCell", - "paragraph", - "document", - "character", - "word", - "identifier", - "nonWhitespaceSequence", - "boundedNonWhitespaceSequence", - "url", -] as const; - -export type SimpleScopeTypeType = (typeof simpleScopeTypeTypes)[number]; + | "token" + | "line" + | "notebookCell" + | "paragraph" + | "document" + | "character" + | "word" + | "identifier" + | "nonWhitespaceSequence" + | "boundedNonWhitespaceSequence" + | "url"; export interface SimpleScopeType { type: SimpleScopeTypeType; From 2c0ac1b417935cd54aaeaf01ca1e176dc8d17b01 Mon Sep 17 00:00:00 2001 From: Pokey Rule <755842+pokey@users.noreply.github.com> Date: Fri, 14 Apr 2023 12:57:25 +0100 Subject: [PATCH 06/25] fixes --- .../cursorless-engine/src/core/commandRunner/CommandRunner.ts | 1 - packages/cursorless-engine/src/typings/Types.ts | 3 +-- 2 files changed, 1 insertion(+), 3 deletions(-) diff --git a/packages/cursorless-engine/src/core/commandRunner/CommandRunner.ts b/packages/cursorless-engine/src/core/commandRunner/CommandRunner.ts index 8962ce0952..c0ce14c4bd 100644 --- a/packages/cursorless-engine/src/core/commandRunner/CommandRunner.ts +++ b/packages/cursorless-engine/src/core/commandRunner/CommandRunner.ts @@ -123,7 +123,6 @@ export class CommandRunner { thatMark: this.thatMark.exists() ? this.thatMark.get() : [], sourceMark: this.sourceMark.exists() ? this.sourceMark.get() : [], getNodeAtLocation: this.treeSitter.getNodeAtLocation, - getTree: this.treeSitter.getTree, }; if (this.testCaseRecorder.isActive()) { diff --git a/packages/cursorless-engine/src/typings/Types.ts b/packages/cursorless-engine/src/typings/Types.ts index 535f6045d4..c3d1b7df3b 100644 --- a/packages/cursorless-engine/src/typings/Types.ts +++ b/packages/cursorless-engine/src/typings/Types.ts @@ -5,7 +5,7 @@ import type { TextDocument, TextEditor, } from "@cursorless/common"; -import type { SyntaxNode, Tree } from "web-tree-sitter"; +import type { SyntaxNode } from "web-tree-sitter"; import { ModifierStage } from "../processTargets/PipelineStages.types"; import { Target } from "./target.types"; @@ -26,7 +26,6 @@ export interface ProcessedTargetsContext { thatMark: Target[]; sourceMark: Target[]; getNodeAtLocation: (document: TextDocument, range: Range) => SyntaxNode; - getTree: (document: TextDocument) => Tree; } export interface SelectionWithEditor { From d9687049b292076cc930d1aa3cb57bc6af935f1e Mon Sep 17 00:00:00 2001 From: Pokey Rule <755842+pokey@users.noreply.github.com> Date: Fri, 14 Apr 2023 15:27:33 +0100 Subject: [PATCH 07/25] more cleanup --- .../src/processTargets/modifiers/scopeHandlers/index.ts | 1 - packages/cursorless-engine/src/util/selectionUtils.ts | 2 +- 2 files changed, 1 insertion(+), 2 deletions(-) diff --git a/packages/cursorless-engine/src/processTargets/modifiers/scopeHandlers/index.ts b/packages/cursorless-engine/src/processTargets/modifiers/scopeHandlers/index.ts index eadf0fb7ea..9c13ffcec5 100644 --- a/packages/cursorless-engine/src/processTargets/modifiers/scopeHandlers/index.ts +++ b/packages/cursorless-engine/src/processTargets/modifiers/scopeHandlers/index.ts @@ -13,7 +13,6 @@ export { default as TokenScopeHandler } from "./TokenScopeHandler"; export * from "./DocumentScopeHandler"; export { default as DocumentScopeHandler } from "./DocumentScopeHandler"; export * from "./TreeSitterScopeHandler"; -export { TreeSitterScopeHandler } from "./TreeSitterScopeHandler"; export * from "./OneOfScopeHandler"; export { default as OneOfScopeHandler } from "./OneOfScopeHandler"; export * from "./ParagraphScopeHandler"; diff --git a/packages/cursorless-engine/src/util/selectionUtils.ts b/packages/cursorless-engine/src/util/selectionUtils.ts index 4c33b1491d..e664f67c6b 100644 --- a/packages/cursorless-engine/src/util/selectionUtils.ts +++ b/packages/cursorless-engine/src/util/selectionUtils.ts @@ -8,7 +8,7 @@ export function selectionWithEditorFromRange( return selectionWithEditorFromPositions(selection, range.start, range.end); } -export function selectionWithEditorFromPositions( +function selectionWithEditorFromPositions( selection: SelectionWithEditor, start: Position, end: Position, From 17f386771f7b7a47318c65461fd6c9c5f0f7aab5 Mon Sep 17 00:00:00 2001 From: Pokey Rule <755842+pokey@users.noreply.github.com> Date: Fri, 14 Apr 2023 15:37:44 +0100 Subject: [PATCH 08/25] Fix tests --- .../languages/typescript/changeCall.yml | 23 ----------------- .../recorded/ordinalScopes/clearFirstFunk.yml | 25 +++++++++---------- .../recorded/ordinalScopes/clearLastFunk.yml | 25 +++++++++---------- .../recorded/queryBasedMatchers/clearCall.yml | 22 ++++++++++++++++ .../queryBasedMatchers/clearCall2.yml | 22 ++++++++++++++++ 5 files changed, 68 insertions(+), 49 deletions(-) delete mode 100644 packages/cursorless-vscode-e2e/src/suite/fixtures/recorded/languages/typescript/changeCall.yml create mode 100644 packages/cursorless-vscode-e2e/src/suite/fixtures/recorded/queryBasedMatchers/clearCall.yml create mode 100644 packages/cursorless-vscode-e2e/src/suite/fixtures/recorded/queryBasedMatchers/clearCall2.yml diff --git a/packages/cursorless-vscode-e2e/src/suite/fixtures/recorded/languages/typescript/changeCall.yml b/packages/cursorless-vscode-e2e/src/suite/fixtures/recorded/languages/typescript/changeCall.yml deleted file mode 100644 index 79b50ce33b..0000000000 --- a/packages/cursorless-vscode-e2e/src/suite/fixtures/recorded/languages/typescript/changeCall.yml +++ /dev/null @@ -1,23 +0,0 @@ -languageId: ruby -command: - version: 1 - spokenForm: change call - action: clearAndSetSelection - targets: - - type: primitive - modifier: {type: containingScope, scopeType: functionCall, includeSiblings: false} -initialState: - documentContents: hello()world() - selections: - - anchor: {line: 0, character: 7} - active: {line: 0, character: 7} - marks: {} -finalState: - documentContents: hello() - selections: - - anchor: {line: 0, character: 7} - active: {line: 0, character: 7} - thatMark: - - anchor: {line: 0, character: 7} - active: {line: 0, character: 7} -fullTargets: [{type: primitive, mark: {type: cursor}, selectionType: token, position: contents, insideOutsideType: inside, modifier: {type: containingScope, scopeType: functionCall, includeSiblings: false}, isImplicit: false}] diff --git a/packages/cursorless-vscode-e2e/src/suite/fixtures/recorded/ordinalScopes/clearFirstFunk.yml b/packages/cursorless-vscode-e2e/src/suite/fixtures/recorded/ordinalScopes/clearFirstFunk.yml index 3b4fc0c475..90d7a8c701 100644 --- a/packages/cursorless-vscode-e2e/src/suite/fixtures/recorded/ordinalScopes/clearFirstFunk.yml +++ b/packages/cursorless-vscode-e2e/src/suite/fixtures/recorded/ordinalScopes/clearFirstFunk.yml @@ -1,7 +1,8 @@ -languageId: typescript +languageId: ruby command: + version: 5 spokenForm: clear first funk - version: 3 + action: {name: clearAndSetSelection} targets: - type: primitive modifiers: @@ -10,24 +11,22 @@ command: start: 0 length: 1 usePrePhraseSnapshot: true - action: {name: clearAndSetSelection} initialState: documentContents: |- - function aaa() {} - function bbb() {} - function ccc() {} - function ddd() {} + def aaa; end + def bbb; end + def ccc; end + def ddd; end selections: - - anchor: {line: 1, character: 10} - active: {line: 2, character: 10} + - anchor: {line: 1, character: 7} + active: {line: 2, character: 7} marks: {} finalState: documentContents: |- - function aaa() {} + def aaa; end - function ccc() {} - function ddd() {} + def ccc; end + def ddd; end selections: - anchor: {line: 1, character: 0} active: {line: 1, character: 0} -fullTargets: [{type: primitive, mark: {type: cursor}, modifiers: [{type: ordinalScope, scopeType: {type: namedFunction}, start: 0, length: 1}]}] diff --git a/packages/cursorless-vscode-e2e/src/suite/fixtures/recorded/ordinalScopes/clearLastFunk.yml b/packages/cursorless-vscode-e2e/src/suite/fixtures/recorded/ordinalScopes/clearLastFunk.yml index aeec6da635..1dc14df647 100644 --- a/packages/cursorless-vscode-e2e/src/suite/fixtures/recorded/ordinalScopes/clearLastFunk.yml +++ b/packages/cursorless-vscode-e2e/src/suite/fixtures/recorded/ordinalScopes/clearLastFunk.yml @@ -1,7 +1,8 @@ -languageId: typescript +languageId: ruby command: + version: 5 spokenForm: clear last funk - version: 3 + action: {name: clearAndSetSelection} targets: - type: primitive modifiers: @@ -10,24 +11,22 @@ command: start: -1 length: 1 usePrePhraseSnapshot: true - action: {name: clearAndSetSelection} initialState: documentContents: |- - function aaa() {} - function bbb() {} - function ccc() {} - function ddd() {} + def aaa; end + def bbb; end + def ccc; end + def ddd; end selections: - - anchor: {line: 1, character: 10} - active: {line: 2, character: 10} + - anchor: {line: 1, character: 7} + active: {line: 2, character: 7} marks: {} finalState: documentContents: |- - function aaa() {} - function bbb() {} + def aaa; end + def bbb; end - function ddd() {} + def ddd; end selections: - anchor: {line: 2, character: 0} active: {line: 2, character: 0} -fullTargets: [{type: primitive, mark: {type: cursor}, modifiers: [{type: ordinalScope, scopeType: {type: namedFunction}, start: -1, length: 1}]}] diff --git a/packages/cursorless-vscode-e2e/src/suite/fixtures/recorded/queryBasedMatchers/clearCall.yml b/packages/cursorless-vscode-e2e/src/suite/fixtures/recorded/queryBasedMatchers/clearCall.yml new file mode 100644 index 0000000000..8e556a9293 --- /dev/null +++ b/packages/cursorless-vscode-e2e/src/suite/fixtures/recorded/queryBasedMatchers/clearCall.yml @@ -0,0 +1,22 @@ +languageId: ruby +command: + version: 5 + spokenForm: clear call + action: {name: clearAndSetSelection} + targets: + - type: primitive + modifiers: + - type: containingScope + scopeType: {type: functionCall} + usePrePhraseSnapshot: true +initialState: + documentContents: aaa()bbb() + selections: + - anchor: {line: 0, character: 5} + active: {line: 0, character: 5} + marks: {} +finalState: + documentContents: aaa() + selections: + - anchor: {line: 0, character: 5} + active: {line: 0, character: 5} diff --git a/packages/cursorless-vscode-e2e/src/suite/fixtures/recorded/queryBasedMatchers/clearCall2.yml b/packages/cursorless-vscode-e2e/src/suite/fixtures/recorded/queryBasedMatchers/clearCall2.yml new file mode 100644 index 0000000000..ace7b4d151 --- /dev/null +++ b/packages/cursorless-vscode-e2e/src/suite/fixtures/recorded/queryBasedMatchers/clearCall2.yml @@ -0,0 +1,22 @@ +languageId: ruby +command: + version: 5 + spokenForm: clear call + action: {name: clearAndSetSelection} + targets: + - type: primitive + modifiers: + - type: containingScope + scopeType: {type: functionCall} + usePrePhraseSnapshot: true +initialState: + documentContents: aaa()bbb() + selections: + - anchor: {line: 0, character: 4} + active: {line: 0, character: 5} + marks: {} +finalState: + documentContents: bbb() + selections: + - anchor: {line: 0, character: 0} + active: {line: 0, character: 0} From 4e3453156d41a256925bf95808d6eec6d2ee2505 Mon Sep 17 00:00:00 2001 From: Pokey Rule <755842+pokey@users.noreply.github.com> Date: Fri, 14 Apr 2023 15:42:29 +0100 Subject: [PATCH 09/25] Add test --- .../queryBasedMatchers/clearNextCall.yml | 25 +++++++++++++++++++ 1 file changed, 25 insertions(+) create mode 100644 packages/cursorless-vscode-e2e/src/suite/fixtures/recorded/queryBasedMatchers/clearNextCall.yml diff --git a/packages/cursorless-vscode-e2e/src/suite/fixtures/recorded/queryBasedMatchers/clearNextCall.yml b/packages/cursorless-vscode-e2e/src/suite/fixtures/recorded/queryBasedMatchers/clearNextCall.yml new file mode 100644 index 0000000000..e1c75deb61 --- /dev/null +++ b/packages/cursorless-vscode-e2e/src/suite/fixtures/recorded/queryBasedMatchers/clearNextCall.yml @@ -0,0 +1,25 @@ +languageId: ruby +command: + version: 5 + spokenForm: clear next call + action: {name: clearAndSetSelection} + targets: + - type: primitive + modifiers: + - type: relativeScope + scopeType: {type: functionCall} + offset: 1 + length: 1 + direction: forward + usePrePhraseSnapshot: true +initialState: + documentContents: " aaa()" + selections: + - anchor: {line: 0, character: 0} + active: {line: 0, character: 0} + marks: {} +finalState: + documentContents: " " + selections: + - anchor: {line: 0, character: 1} + active: {line: 0, character: 1} From 2c25fa950bcfef4981560ffda946ec56ee0b0b96 Mon Sep 17 00:00:00 2001 From: Pokey Rule <755842+pokey@users.noreply.github.com> Date: Fri, 14 Apr 2023 16:00:40 +0100 Subject: [PATCH 10/25] More tests --- .../queryBasedMatchers/clearCall5.yml | 22 ++++++++++++++++ .../queryBasedMatchers/clearCall6.yml | 22 ++++++++++++++++ .../queryBasedMatchers/clearCall7.yml | 25 +++++++++++++++++++ .../queryBasedMatchers/clearCall8.yml | 25 +++++++++++++++++++ .../queryBasedMatchers/clearCall9.yml | 25 +++++++++++++++++++ .../queryBasedMatchers/clearNextCall2.yml | 25 +++++++++++++++++++ .../queryBasedMatchers/clearNextCall3.yml | 25 +++++++++++++++++++ .../queryBasedMatchers/clearNextCall4.yml | 25 +++++++++++++++++++ 8 files changed, 194 insertions(+) create mode 100644 packages/cursorless-vscode-e2e/src/suite/fixtures/recorded/queryBasedMatchers/clearCall5.yml create mode 100644 packages/cursorless-vscode-e2e/src/suite/fixtures/recorded/queryBasedMatchers/clearCall6.yml create mode 100644 packages/cursorless-vscode-e2e/src/suite/fixtures/recorded/queryBasedMatchers/clearCall7.yml create mode 100644 packages/cursorless-vscode-e2e/src/suite/fixtures/recorded/queryBasedMatchers/clearCall8.yml create mode 100644 packages/cursorless-vscode-e2e/src/suite/fixtures/recorded/queryBasedMatchers/clearCall9.yml create mode 100644 packages/cursorless-vscode-e2e/src/suite/fixtures/recorded/queryBasedMatchers/clearNextCall2.yml create mode 100644 packages/cursorless-vscode-e2e/src/suite/fixtures/recorded/queryBasedMatchers/clearNextCall3.yml create mode 100644 packages/cursorless-vscode-e2e/src/suite/fixtures/recorded/queryBasedMatchers/clearNextCall4.yml diff --git a/packages/cursorless-vscode-e2e/src/suite/fixtures/recorded/queryBasedMatchers/clearCall5.yml b/packages/cursorless-vscode-e2e/src/suite/fixtures/recorded/queryBasedMatchers/clearCall5.yml new file mode 100644 index 0000000000..1e90870a4e --- /dev/null +++ b/packages/cursorless-vscode-e2e/src/suite/fixtures/recorded/queryBasedMatchers/clearCall5.yml @@ -0,0 +1,22 @@ +languageId: ruby +command: + version: 5 + spokenForm: clear call + action: {name: clearAndSetSelection} + targets: + - type: primitive + modifiers: + - type: containingScope + scopeType: {type: functionCall} + usePrePhraseSnapshot: true +initialState: + documentContents: aaa(bbb()) + selections: + - anchor: {line: 0, character: 4} + active: {line: 0, character: 4} + marks: {} +finalState: + documentContents: aaa() + selections: + - anchor: {line: 0, character: 4} + active: {line: 0, character: 4} diff --git a/packages/cursorless-vscode-e2e/src/suite/fixtures/recorded/queryBasedMatchers/clearCall6.yml b/packages/cursorless-vscode-e2e/src/suite/fixtures/recorded/queryBasedMatchers/clearCall6.yml new file mode 100644 index 0000000000..aa88a85e3c --- /dev/null +++ b/packages/cursorless-vscode-e2e/src/suite/fixtures/recorded/queryBasedMatchers/clearCall6.yml @@ -0,0 +1,22 @@ +languageId: ruby +command: + version: 5 + spokenForm: clear call + action: {name: clearAndSetSelection} + targets: + - type: primitive + modifiers: + - type: containingScope + scopeType: {type: functionCall} + usePrePhraseSnapshot: true +initialState: + documentContents: " aaa(bbb()) + ccc()" + selections: + - anchor: {line: 0, character: 14} + active: {line: 0, character: 14} + marks: {} +finalState: + documentContents: " aaa(bbb()) + " + selections: + - anchor: {line: 0, character: 14} + active: {line: 0, character: 14} diff --git a/packages/cursorless-vscode-e2e/src/suite/fixtures/recorded/queryBasedMatchers/clearCall7.yml b/packages/cursorless-vscode-e2e/src/suite/fixtures/recorded/queryBasedMatchers/clearCall7.yml new file mode 100644 index 0000000000..d14a10fd8a --- /dev/null +++ b/packages/cursorless-vscode-e2e/src/suite/fixtures/recorded/queryBasedMatchers/clearCall7.yml @@ -0,0 +1,25 @@ +languageId: ruby +command: + version: 5 + spokenForm: clear call + action: {name: clearAndSetSelection} + targets: + - type: primitive + modifiers: + - type: relativeScope + scopeType: {type: functionCall} + offset: 1 + length: 1 + direction: forward + usePrePhraseSnapshot: false +initialState: + documentContents: " aaa(bbb(), ccc()) + ddd()" + selections: + - anchor: {line: 0, character: 0} + active: {line: 0, character: 0} + marks: {} +finalState: + documentContents: " + ddd()" + selections: + - anchor: {line: 0, character: 1} + active: {line: 0, character: 1} diff --git a/packages/cursorless-vscode-e2e/src/suite/fixtures/recorded/queryBasedMatchers/clearCall8.yml b/packages/cursorless-vscode-e2e/src/suite/fixtures/recorded/queryBasedMatchers/clearCall8.yml new file mode 100644 index 0000000000..8f2ba9db1e --- /dev/null +++ b/packages/cursorless-vscode-e2e/src/suite/fixtures/recorded/queryBasedMatchers/clearCall8.yml @@ -0,0 +1,25 @@ +languageId: ruby +command: + version: 5 + spokenForm: clear call + action: {name: clearAndSetSelection} + targets: + - type: primitive + modifiers: + - type: relativeScope + scopeType: {type: functionCall} + offset: 1 + length: 1 + direction: forward + usePrePhraseSnapshot: false +initialState: + documentContents: aaa(bbb(), ccc()) + ddd() + selections: + - anchor: {line: 0, character: 17} + active: {line: 0, character: 17} + marks: {} +finalState: + documentContents: "aaa(bbb(), ccc()) + " + selections: + - anchor: {line: 0, character: 20} + active: {line: 0, character: 20} diff --git a/packages/cursorless-vscode-e2e/src/suite/fixtures/recorded/queryBasedMatchers/clearCall9.yml b/packages/cursorless-vscode-e2e/src/suite/fixtures/recorded/queryBasedMatchers/clearCall9.yml new file mode 100644 index 0000000000..6a90efcca4 --- /dev/null +++ b/packages/cursorless-vscode-e2e/src/suite/fixtures/recorded/queryBasedMatchers/clearCall9.yml @@ -0,0 +1,25 @@ +languageId: ruby +command: + version: 5 + spokenForm: clear call + action: {name: clearAndSetSelection} + targets: + - type: primitive + modifiers: + - type: relativeScope + scopeType: {type: functionCall} + offset: 1 + length: 1 + direction: forward + usePrePhraseSnapshot: false +initialState: + documentContents: aaa(bbb(), ccc()) + ddd() + selections: + - anchor: {line: 0, character: 16} + active: {line: 0, character: 16} + marks: {} +finalState: + documentContents: "aaa(bbb(), ccc()) + " + selections: + - anchor: {line: 0, character: 20} + active: {line: 0, character: 20} diff --git a/packages/cursorless-vscode-e2e/src/suite/fixtures/recorded/queryBasedMatchers/clearNextCall2.yml b/packages/cursorless-vscode-e2e/src/suite/fixtures/recorded/queryBasedMatchers/clearNextCall2.yml new file mode 100644 index 0000000000..2671f2e008 --- /dev/null +++ b/packages/cursorless-vscode-e2e/src/suite/fixtures/recorded/queryBasedMatchers/clearNextCall2.yml @@ -0,0 +1,25 @@ +languageId: ruby +command: + version: 5 + spokenForm: clear next call + action: {name: clearAndSetSelection} + targets: + - type: primitive + modifiers: + - type: relativeScope + scopeType: {type: functionCall} + offset: 1 + length: 1 + direction: forward + usePrePhraseSnapshot: true +initialState: + documentContents: aaa(bbb(), ccc()) + ddd() + selections: + - anchor: {line: 0, character: 0} + active: {line: 0, character: 0} + marks: {} +finalState: + documentContents: aaa(, ccc()) + ddd() + selections: + - anchor: {line: 0, character: 4} + active: {line: 0, character: 4} diff --git a/packages/cursorless-vscode-e2e/src/suite/fixtures/recorded/queryBasedMatchers/clearNextCall3.yml b/packages/cursorless-vscode-e2e/src/suite/fixtures/recorded/queryBasedMatchers/clearNextCall3.yml new file mode 100644 index 0000000000..73889ab7d3 --- /dev/null +++ b/packages/cursorless-vscode-e2e/src/suite/fixtures/recorded/queryBasedMatchers/clearNextCall3.yml @@ -0,0 +1,25 @@ +languageId: ruby +command: + version: 5 + spokenForm: clear next call + action: {name: clearAndSetSelection} + targets: + - type: primitive + modifiers: + - type: relativeScope + scopeType: {type: functionCall} + offset: 1 + length: 1 + direction: forward + usePrePhraseSnapshot: false +initialState: + documentContents: aaa(bbb(), ccc()) + ddd() + selections: + - anchor: {line: 0, character: 4} + active: {line: 0, character: 4} + marks: {} +finalState: + documentContents: aaa(bbb(), ) + ddd() + selections: + - anchor: {line: 0, character: 11} + active: {line: 0, character: 11} diff --git a/packages/cursorless-vscode-e2e/src/suite/fixtures/recorded/queryBasedMatchers/clearNextCall4.yml b/packages/cursorless-vscode-e2e/src/suite/fixtures/recorded/queryBasedMatchers/clearNextCall4.yml new file mode 100644 index 0000000000..971a163cdc --- /dev/null +++ b/packages/cursorless-vscode-e2e/src/suite/fixtures/recorded/queryBasedMatchers/clearNextCall4.yml @@ -0,0 +1,25 @@ +languageId: ruby +command: + version: 5 + spokenForm: clear next call + action: {name: clearAndSetSelection} + targets: + - type: primitive + modifiers: + - type: relativeScope + scopeType: {type: functionCall} + offset: 1 + length: 1 + direction: forward + usePrePhraseSnapshot: false +initialState: + documentContents: aaa(bbb(), ccc()) + ddd() + selections: + - anchor: {line: 0, character: 1} + active: {line: 0, character: 1} + marks: {} +finalState: + documentContents: aaa(, ccc()) + ddd() + selections: + - anchor: {line: 0, character: 4} + active: {line: 0, character: 4} From 04e3e7f186939074902d40479c29a878ad3bfb65 Mon Sep 17 00:00:00 2001 From: Pokey Rule <755842+pokey@users.noreply.github.com> Date: Fri, 14 Apr 2023 16:08:47 +0100 Subject: [PATCH 11/25] docstring --- .../src/languages/LanguageDefinitionsImpl.ts | 8 ++++++++ 1 file changed, 8 insertions(+) diff --git a/packages/cursorless-engine/src/languages/LanguageDefinitionsImpl.ts b/packages/cursorless-engine/src/languages/LanguageDefinitionsImpl.ts index 34e76b1b8d..72c41156b4 100644 --- a/packages/cursorless-engine/src/languages/LanguageDefinitionsImpl.ts +++ b/packages/cursorless-engine/src/languages/LanguageDefinitionsImpl.ts @@ -26,4 +26,12 @@ export class LanguageDefinitionsImpl implements LanguageDefinitions { } } +/** + * A list of languages which have query definitions. Note that it's possible + * for a language to have some of its scope types defined via queries and the + * rest via legacy `nodeMatcher` definitions. The + * {@link LanguageDefinitionImpl} will return `undefined` for any scope types + * which are not defined via queries, which will cause the modifier stage to + * fall back to the legacy `nodeMatcher` definitions. + */ const languages: LanguageId[] = ["ruby"]; From 0fb7cfbc2b88ad2dd68e685908dc65a0b00d8d28 Mon Sep 17 00:00:00 2001 From: Pokey Rule <755842+pokey@users.noreply.github.com> Date: Fri, 14 Apr 2023 16:14:15 +0100 Subject: [PATCH 12/25] Add missing test --- .../languages/ruby/clearClassName.yml | 22 +++++++++++++++++++ 1 file changed, 22 insertions(+) create mode 100644 packages/cursorless-vscode-e2e/src/suite/fixtures/recorded/languages/ruby/clearClassName.yml diff --git a/packages/cursorless-vscode-e2e/src/suite/fixtures/recorded/languages/ruby/clearClassName.yml b/packages/cursorless-vscode-e2e/src/suite/fixtures/recorded/languages/ruby/clearClassName.yml new file mode 100644 index 0000000000..d3c634e596 --- /dev/null +++ b/packages/cursorless-vscode-e2e/src/suite/fixtures/recorded/languages/ruby/clearClassName.yml @@ -0,0 +1,22 @@ +languageId: ruby +command: + version: 5 + spokenForm: clear class name + action: {name: clearAndSetSelection} + targets: + - type: primitive + modifiers: + - type: containingScope + scopeType: {type: className} + usePrePhraseSnapshot: true +initialState: + documentContents: class Aaa; end + selections: + - anchor: {line: 0, character: 0} + active: {line: 0, character: 0} + marks: {} +finalState: + documentContents: class ; end + selections: + - anchor: {line: 0, character: 6} + active: {line: 0, character: 6} From 70031d2de40ec361952416e61c1f723f973e66c5 Mon Sep 17 00:00:00 2001 From: Pokey Rule <755842+pokey@users.noreply.github.com> Date: Fri, 14 Apr 2023 16:16:44 +0100 Subject: [PATCH 13/25] remove test --- .../queryBasedMatchers/clearCall6.yml | 22 ------------------- 1 file changed, 22 deletions(-) delete mode 100644 packages/cursorless-vscode-e2e/src/suite/fixtures/recorded/queryBasedMatchers/clearCall6.yml diff --git a/packages/cursorless-vscode-e2e/src/suite/fixtures/recorded/queryBasedMatchers/clearCall6.yml b/packages/cursorless-vscode-e2e/src/suite/fixtures/recorded/queryBasedMatchers/clearCall6.yml deleted file mode 100644 index aa88a85e3c..0000000000 --- a/packages/cursorless-vscode-e2e/src/suite/fixtures/recorded/queryBasedMatchers/clearCall6.yml +++ /dev/null @@ -1,22 +0,0 @@ -languageId: ruby -command: - version: 5 - spokenForm: clear call - action: {name: clearAndSetSelection} - targets: - - type: primitive - modifiers: - - type: containingScope - scopeType: {type: functionCall} - usePrePhraseSnapshot: true -initialState: - documentContents: " aaa(bbb()) + ccc()" - selections: - - anchor: {line: 0, character: 14} - active: {line: 0, character: 14} - marks: {} -finalState: - documentContents: " aaa(bbb()) + " - selections: - - anchor: {line: 0, character: 14} - active: {line: 0, character: 14} From 26669cd92fcaf14b84c88ba5a234dee1bb97c78f Mon Sep 17 00:00:00 2001 From: Pokey Rule <755842+pokey@users.noreply.github.com> Date: Fri, 14 Apr 2023 16:19:17 +0100 Subject: [PATCH 14/25] renames --- .../queryBasedMatchers/{clearCall7.yml => clearNextCall5.yml} | 2 +- .../queryBasedMatchers/{clearCall8.yml => clearNextCall6.yml} | 2 +- .../queryBasedMatchers/{clearCall9.yml => clearNextCall7.yml} | 2 +- 3 files changed, 3 insertions(+), 3 deletions(-) rename packages/cursorless-vscode-e2e/src/suite/fixtures/recorded/queryBasedMatchers/{clearCall7.yml => clearNextCall5.yml} (95%) rename packages/cursorless-vscode-e2e/src/suite/fixtures/recorded/queryBasedMatchers/{clearCall8.yml => clearNextCall6.yml} (95%) rename packages/cursorless-vscode-e2e/src/suite/fixtures/recorded/queryBasedMatchers/{clearCall9.yml => clearNextCall7.yml} (95%) diff --git a/packages/cursorless-vscode-e2e/src/suite/fixtures/recorded/queryBasedMatchers/clearCall7.yml b/packages/cursorless-vscode-e2e/src/suite/fixtures/recorded/queryBasedMatchers/clearNextCall5.yml similarity index 95% rename from packages/cursorless-vscode-e2e/src/suite/fixtures/recorded/queryBasedMatchers/clearCall7.yml rename to packages/cursorless-vscode-e2e/src/suite/fixtures/recorded/queryBasedMatchers/clearNextCall5.yml index d14a10fd8a..2543027407 100644 --- a/packages/cursorless-vscode-e2e/src/suite/fixtures/recorded/queryBasedMatchers/clearCall7.yml +++ b/packages/cursorless-vscode-e2e/src/suite/fixtures/recorded/queryBasedMatchers/clearNextCall5.yml @@ -1,7 +1,7 @@ languageId: ruby command: version: 5 - spokenForm: clear call + spokenForm: clear next call action: {name: clearAndSetSelection} targets: - type: primitive diff --git a/packages/cursorless-vscode-e2e/src/suite/fixtures/recorded/queryBasedMatchers/clearCall8.yml b/packages/cursorless-vscode-e2e/src/suite/fixtures/recorded/queryBasedMatchers/clearNextCall6.yml similarity index 95% rename from packages/cursorless-vscode-e2e/src/suite/fixtures/recorded/queryBasedMatchers/clearCall8.yml rename to packages/cursorless-vscode-e2e/src/suite/fixtures/recorded/queryBasedMatchers/clearNextCall6.yml index 8f2ba9db1e..c37f7d44ea 100644 --- a/packages/cursorless-vscode-e2e/src/suite/fixtures/recorded/queryBasedMatchers/clearCall8.yml +++ b/packages/cursorless-vscode-e2e/src/suite/fixtures/recorded/queryBasedMatchers/clearNextCall6.yml @@ -1,7 +1,7 @@ languageId: ruby command: version: 5 - spokenForm: clear call + spokenForm: clear next call action: {name: clearAndSetSelection} targets: - type: primitive diff --git a/packages/cursorless-vscode-e2e/src/suite/fixtures/recorded/queryBasedMatchers/clearCall9.yml b/packages/cursorless-vscode-e2e/src/suite/fixtures/recorded/queryBasedMatchers/clearNextCall7.yml similarity index 95% rename from packages/cursorless-vscode-e2e/src/suite/fixtures/recorded/queryBasedMatchers/clearCall9.yml rename to packages/cursorless-vscode-e2e/src/suite/fixtures/recorded/queryBasedMatchers/clearNextCall7.yml index 6a90efcca4..4b8f4856c5 100644 --- a/packages/cursorless-vscode-e2e/src/suite/fixtures/recorded/queryBasedMatchers/clearCall9.yml +++ b/packages/cursorless-vscode-e2e/src/suite/fixtures/recorded/queryBasedMatchers/clearNextCall7.yml @@ -1,7 +1,7 @@ languageId: ruby command: version: 5 - spokenForm: clear call + spokenForm: clear next call action: {name: clearAndSetSelection} targets: - type: primitive From 8ef59350a08ee0c63d2e614097b28a68ec114800 Mon Sep 17 00:00:00 2001 From: Pokey Rule <755842+pokey@users.noreply.github.com> Date: Fri, 14 Apr 2023 16:24:13 +0100 Subject: [PATCH 15/25] more tests --- .../queryBasedMatchers/clearCall3.yml | 22 +++++++++++++++++++ .../queryBasedMatchers/clearCall4.yml | 22 +++++++++++++++++++ .../queryBasedMatchers/clearCall6.yml | 22 +++++++++++++++++++ 3 files changed, 66 insertions(+) create mode 100644 packages/cursorless-vscode-e2e/src/suite/fixtures/recorded/queryBasedMatchers/clearCall3.yml create mode 100644 packages/cursorless-vscode-e2e/src/suite/fixtures/recorded/queryBasedMatchers/clearCall4.yml create mode 100644 packages/cursorless-vscode-e2e/src/suite/fixtures/recorded/queryBasedMatchers/clearCall6.yml diff --git a/packages/cursorless-vscode-e2e/src/suite/fixtures/recorded/queryBasedMatchers/clearCall3.yml b/packages/cursorless-vscode-e2e/src/suite/fixtures/recorded/queryBasedMatchers/clearCall3.yml new file mode 100644 index 0000000000..f067446a38 --- /dev/null +++ b/packages/cursorless-vscode-e2e/src/suite/fixtures/recorded/queryBasedMatchers/clearCall3.yml @@ -0,0 +1,22 @@ +languageId: ruby +command: + version: 5 + spokenForm: clear call + action: {name: clearAndSetSelection} + targets: + - type: primitive + modifiers: + - type: containingScope + scopeType: {type: functionCall} + usePrePhraseSnapshot: true +initialState: + documentContents: aaa(bbb()) + selections: + - anchor: {line: 0, character: 8} + active: {line: 0, character: 8} + marks: {} +finalState: + documentContents: aaa() + selections: + - anchor: {line: 0, character: 4} + active: {line: 0, character: 4} diff --git a/packages/cursorless-vscode-e2e/src/suite/fixtures/recorded/queryBasedMatchers/clearCall4.yml b/packages/cursorless-vscode-e2e/src/suite/fixtures/recorded/queryBasedMatchers/clearCall4.yml new file mode 100644 index 0000000000..2c9687f8e7 --- /dev/null +++ b/packages/cursorless-vscode-e2e/src/suite/fixtures/recorded/queryBasedMatchers/clearCall4.yml @@ -0,0 +1,22 @@ +languageId: ruby +command: + version: 5 + spokenForm: clear call + action: {name: clearAndSetSelection} + targets: + - type: primitive + modifiers: + - type: containingScope + scopeType: {type: functionCall} + usePrePhraseSnapshot: true +initialState: + documentContents: aaa(bbb()) + selections: + - anchor: {line: 0, character: 3} + active: {line: 0, character: 3} + marks: {} +finalState: + documentContents: "" + selections: + - anchor: {line: 0, character: 0} + active: {line: 0, character: 0} diff --git a/packages/cursorless-vscode-e2e/src/suite/fixtures/recorded/queryBasedMatchers/clearCall6.yml b/packages/cursorless-vscode-e2e/src/suite/fixtures/recorded/queryBasedMatchers/clearCall6.yml new file mode 100644 index 0000000000..4ae038c1e7 --- /dev/null +++ b/packages/cursorless-vscode-e2e/src/suite/fixtures/recorded/queryBasedMatchers/clearCall6.yml @@ -0,0 +1,22 @@ +languageId: ruby +command: + version: 5 + spokenForm: clear call + action: {name: clearAndSetSelection} + targets: + - type: primitive + modifiers: + - type: containingScope + scopeType: {type: functionCall} + usePrePhraseSnapshot: true +initialState: + documentContents: aaa(bbb()) + selections: + - anchor: {line: 0, character: 9} + active: {line: 0, character: 9} + marks: {} +finalState: + documentContents: "" + selections: + - anchor: {line: 0, character: 0} + active: {line: 0, character: 0} From b68bd0411e92aa295cc60e6aea27baa2d172ccc3 Mon Sep 17 00:00:00 2001 From: Pokey Rule <755842+pokey@users.noreply.github.com> Date: Fri, 14 Apr 2023 16:28:46 +0100 Subject: [PATCH 16/25] more tests --- .../queryBasedMatchers/clearNextCall8.yml | 25 +++++++++++++++++++ .../clearSecondNextCall.yml | 25 +++++++++++++++++++ 2 files changed, 50 insertions(+) create mode 100644 packages/cursorless-vscode-e2e/src/suite/fixtures/recorded/queryBasedMatchers/clearNextCall8.yml create mode 100644 packages/cursorless-vscode-e2e/src/suite/fixtures/recorded/queryBasedMatchers/clearSecondNextCall.yml diff --git a/packages/cursorless-vscode-e2e/src/suite/fixtures/recorded/queryBasedMatchers/clearNextCall8.yml b/packages/cursorless-vscode-e2e/src/suite/fixtures/recorded/queryBasedMatchers/clearNextCall8.yml new file mode 100644 index 0000000000..e172f94469 --- /dev/null +++ b/packages/cursorless-vscode-e2e/src/suite/fixtures/recorded/queryBasedMatchers/clearNextCall8.yml @@ -0,0 +1,25 @@ +languageId: ruby +command: + version: 5 + spokenForm: clear next call + action: {name: clearAndSetSelection} + targets: + - type: primitive + modifiers: + - type: relativeScope + scopeType: {type: functionCall} + offset: 1 + length: 1 + direction: forward + usePrePhraseSnapshot: true +initialState: + documentContents: aaa(bbb(), ccc()) + ddd() + selections: + - anchor: {line: 0, character: 11} + active: {line: 0, character: 11} + marks: {} +finalState: + documentContents: "aaa(bbb(), ccc()) + " + selections: + - anchor: {line: 0, character: 20} + active: {line: 0, character: 20} diff --git a/packages/cursorless-vscode-e2e/src/suite/fixtures/recorded/queryBasedMatchers/clearSecondNextCall.yml b/packages/cursorless-vscode-e2e/src/suite/fixtures/recorded/queryBasedMatchers/clearSecondNextCall.yml new file mode 100644 index 0000000000..3298894c7e --- /dev/null +++ b/packages/cursorless-vscode-e2e/src/suite/fixtures/recorded/queryBasedMatchers/clearSecondNextCall.yml @@ -0,0 +1,25 @@ +languageId: ruby +command: + version: 5 + spokenForm: clear second next call + action: {name: clearAndSetSelection} + targets: + - type: primitive + modifiers: + - type: relativeScope + scopeType: {type: functionCall} + offset: 2 + length: 1 + direction: forward + usePrePhraseSnapshot: true +initialState: + documentContents: aaa(bbb(), ccc()) + ddd() + selections: + - anchor: {line: 0, character: 10} + active: {line: 0, character: 10} + marks: {} +finalState: + documentContents: "aaa(bbb(), ccc()) + " + selections: + - anchor: {line: 0, character: 20} + active: {line: 0, character: 20} From 2632601feaa193d30a054f49a522e5a8914bbe0d Mon Sep 17 00:00:00 2001 From: Pokey Rule <755842+pokey@users.noreply.github.com> Date: Fri, 14 Apr 2023 16:32:15 +0100 Subject: [PATCH 17/25] test --- .../queryBasedMatchers/clearPreviousCall.yml | 25 +++++++++++++++++++ 1 file changed, 25 insertions(+) create mode 100644 packages/cursorless-vscode-e2e/src/suite/fixtures/recorded/queryBasedMatchers/clearPreviousCall.yml diff --git a/packages/cursorless-vscode-e2e/src/suite/fixtures/recorded/queryBasedMatchers/clearPreviousCall.yml b/packages/cursorless-vscode-e2e/src/suite/fixtures/recorded/queryBasedMatchers/clearPreviousCall.yml new file mode 100644 index 0000000000..7952acbdea --- /dev/null +++ b/packages/cursorless-vscode-e2e/src/suite/fixtures/recorded/queryBasedMatchers/clearPreviousCall.yml @@ -0,0 +1,25 @@ +languageId: ruby +command: + version: 5 + spokenForm: clear previous call + action: {name: clearAndSetSelection} + targets: + - type: primitive + modifiers: + - type: relativeScope + scopeType: {type: functionCall} + offset: 1 + length: 1 + direction: backward + usePrePhraseSnapshot: true +initialState: + documentContents: "aaa() " + selections: + - anchor: {line: 0, character: 6} + active: {line: 0, character: 6} + marks: {} +finalState: + documentContents: " " + selections: + - anchor: {line: 0, character: 0} + active: {line: 0, character: 0} From c697971155049ffe8519c99da58090ce8f773a85 Mon Sep 17 00:00:00 2001 From: Pokey Rule <755842+pokey@users.noreply.github.com> Date: Fri, 14 Apr 2023 16:37:51 +0100 Subject: [PATCH 18/25] docs --- .../scopeHandlers/TreeSitterScopeHandler.ts | 15 +++++++++++++++ 1 file changed, 15 insertions(+) diff --git a/packages/cursorless-engine/src/processTargets/modifiers/scopeHandlers/TreeSitterScopeHandler.ts b/packages/cursorless-engine/src/processTargets/modifiers/scopeHandlers/TreeSitterScopeHandler.ts index 2db6a6ba10..823d228d20 100644 --- a/packages/cursorless-engine/src/processTargets/modifiers/scopeHandlers/TreeSitterScopeHandler.ts +++ b/packages/cursorless-engine/src/processTargets/modifiers/scopeHandlers/TreeSitterScopeHandler.ts @@ -62,6 +62,15 @@ export class TreeSitterScopeHandler extends BaseScopeHandler { .sort((a, b) => compareTargetScopes(direction, position, a, b)); } + /** + * Constructs a range to pass to {@link Query.matches} to find scopes. Note + * that {@link Query.matches} will only return scopes that have non-empty + * intersection with this range. Also note that the base + * {@link BaseScopeHandler.generateScopes} will filter out any extra scopes + * that we yield, so we don't need to be totally precise. + * + * @returns Range to pass to {@link Query.matches} + */ private getQueryRange( document: TextDocument, position: Position, @@ -73,12 +82,18 @@ export class TreeSitterScopeHandler extends BaseScopeHandler { distalPosition == null ? null : document.offsetAt(distalPosition); if (containment === "required") { + // If containment is required, we smear the position left and right by one + // character so that we have a non-empty intersection with any scope that + // touches position return { start: document.positionAt(offset - 1), end: document.positionAt(offset + 1), }; } + // If containment is disallowed, we can shift the position forward by a character to avoid + // matching scopes that touch position. Otherwise, we shift the position backward by a + // character to ensure we get scopes that touch position. const proximalShift = containment === "disallowed" ? 1 : -1; // FIXME: Don't go all the way to end of document when there is no distalPosition? From 0542e8f3bc73435d5ee9bbf839541c4bf8ccd9f7 Mon Sep 17 00:00:00 2001 From: Pokey Rule <755842+pokey@users.noreply.github.com> Date: Fri, 14 Apr 2023 17:28:38 +0100 Subject: [PATCH 19/25] more tests --- .../queryBasedMatchers/clearEveryCallLine.yml | 26 +++++++++++++++++++ 1 file changed, 26 insertions(+) create mode 100644 packages/cursorless-vscode-e2e/src/suite/fixtures/recorded/queryBasedMatchers/clearEveryCallLine.yml diff --git a/packages/cursorless-vscode-e2e/src/suite/fixtures/recorded/queryBasedMatchers/clearEveryCallLine.yml b/packages/cursorless-vscode-e2e/src/suite/fixtures/recorded/queryBasedMatchers/clearEveryCallLine.yml new file mode 100644 index 0000000000..9709ecd757 --- /dev/null +++ b/packages/cursorless-vscode-e2e/src/suite/fixtures/recorded/queryBasedMatchers/clearEveryCallLine.yml @@ -0,0 +1,26 @@ +languageId: ruby +command: + version: 5 + spokenForm: clear every call line + action: {name: clearAndSetSelection} + targets: + - type: primitive + modifiers: + - type: everyScope + scopeType: {type: functionCall} + - type: containingScope + scopeType: {type: line} + usePrePhraseSnapshot: true +initialState: + documentContents: aaa() + bbb() + selections: + - anchor: {line: 0, character: 6} + active: {line: 0, character: 6} + marks: {} +finalState: + documentContents: " + " + selections: + - anchor: {line: 0, character: 0} + active: {line: 0, character: 0} + - anchor: {line: 0, character: 3} + active: {line: 0, character: 3} From e0523e274c49e75bd7aef4a5af6834e5798d8eee Mon Sep 17 00:00:00 2001 From: Pokey Rule <755842+pokey@users.noreply.github.com> Date: Fri, 14 Apr 2023 23:42:24 +0100 Subject: [PATCH 20/25] Remove experimental query files --- queries/html/scopeTypes.scm | 15 --------------- queries/typescript/scopeTypes.scm | 12 ------------ 2 files changed, 27 deletions(-) delete mode 100644 queries/html/scopeTypes.scm delete mode 100644 queries/typescript/scopeTypes.scm diff --git a/queries/html/scopeTypes.scm b/queries/html/scopeTypes.scm deleted file mode 100644 index 0f699ad1bb..0000000000 --- a/queries/html/scopeTypes.scm +++ /dev/null @@ -1,15 +0,0 @@ -(element - (start_tag) @.interior.start @.boundary - (end_tag) @.interior.end @.boundary - ; What happens with these two boundary elements? - (#make-range! @.interior.start @.interior.end ".interior" true true) - ; Note that the final `true true` means to exclude start and end node of range -) @element - -; Alternately: -(element - (start_tag) @.interior.startExclusive @.boundary - (end_tag) @.interior.endExclusive @.boundary -) @element - -(element (self_closing_tag)) @element diff --git a/queries/typescript/scopeTypes.scm b/queries/typescript/scopeTypes.scm deleted file mode 100644 index 7359b31a76..0000000000 --- a/queries/typescript/scopeTypes.scm +++ /dev/null @@ -1,12 +0,0 @@ -(if_statement consequence: (_) ) -(if_statement - consequence: [ - (statement_block - (_)* @.interior - ; What happens with this range? - ) - (_) @.interior - ; This is for `if (foo) bar();` - ; Does this one only match if above doesn't? - ] -) @ifStatement From ecdb2ce6e394e8bbc0a0eb42e67fc6d063dbef1a Mon Sep 17 00:00:00 2001 From: Pokey Rule <755842+pokey@users.noreply.github.com> Date: Sat, 15 Apr 2023 20:40:25 +0100 Subject: [PATCH 21/25] Test cleanup --- .../clearCall.yml} | 14 ++++---- .../recorded/containingScope/clearCall2.yml | 22 +++++++++++++ .../recorded/ordinalScopes/clearFirstFunk.yml | 32 ------------------- .../recorded/ordinalScopes/clearLastFunk.yml | 32 ------------------- .../queryBasedMatchers/changeList.yml | 26 --------------- .../queryBasedMatchers/changeList10.yml | 22 ------------- .../queryBasedMatchers/changeList13.yml | 25 --------------- .../queryBasedMatchers/changeList14.yml | 25 --------------- .../queryBasedMatchers/changeList2.yml | 22 ------------- .../queryBasedMatchers/changeList8.yml | 22 ------------- .../queryBasedMatchers/changeList9.yml | 22 ------------- .../{changeList3.yml => clearCall10.yml} | 16 ++++------ .../queryBasedMatchers/clearCall11.yml | 18 +++++++++++ .../{changeList6.yml => clearCall12.yml} | 16 ++++------ .../{changeList11.yml => clearCall14.yml} | 8 ++--- .../{changeList4.yml => clearCall15.yml} | 12 +++---- .../{changeList5.yml => clearCall7.yml} | 16 ++++------ .../queryBasedMatchers/clearCall8.yml | 18 +++++++++++ .../queryBasedMatchers/clearCall9.yml | 18 +++++++++++ .../queryBasedMatchers/clearCallBat.yml | 28 ++++++++++++++++ .../queryBasedMatchers/clearCallBat2.yml | 28 ++++++++++++++++ .../queryBasedMatchers/clearEveryCall.yml | 24 ++++++++++++++ .../queryBasedMatchers/clearFirstCall.yml | 24 ++++++++++++++ .../queryBasedMatchers/clearLastCall.yml | 24 ++++++++++++++ .../clearNextCall.yml | 0 .../clearNextCall2.yml | 0 .../clearNextCall3.yml | 0 .../clearNextCall4.yml | 0 .../clearNextCall5.yml | 0 .../clearNextCall6.yml | 0 .../clearNextCall7.yml | 0 .../clearNextCall8.yml | 0 .../clearSecondNextCall.yml | 0 .../recorded/relativeScopes/clearTwoCalls.yml | 25 +++++++++++++++ .../clearTwoCalls2.yml} | 13 +++++--- .../relativeScopes/clearTwoCalls3.yml | 25 +++++++++++++++ .../relativeScopes/clearTwoCalls4.yml | 25 +++++++++++++++ 37 files changed, 323 insertions(+), 279 deletions(-) rename packages/cursorless-vscode-e2e/src/suite/fixtures/recorded/{queryBasedMatchers/changeList7.yml => containingScope/clearCall.yml} (63%) create mode 100644 packages/cursorless-vscode-e2e/src/suite/fixtures/recorded/containingScope/clearCall2.yml delete mode 100644 packages/cursorless-vscode-e2e/src/suite/fixtures/recorded/ordinalScopes/clearFirstFunk.yml delete mode 100644 packages/cursorless-vscode-e2e/src/suite/fixtures/recorded/ordinalScopes/clearLastFunk.yml delete mode 100644 packages/cursorless-vscode-e2e/src/suite/fixtures/recorded/queryBasedMatchers/changeList.yml delete mode 100644 packages/cursorless-vscode-e2e/src/suite/fixtures/recorded/queryBasedMatchers/changeList10.yml delete mode 100644 packages/cursorless-vscode-e2e/src/suite/fixtures/recorded/queryBasedMatchers/changeList13.yml delete mode 100644 packages/cursorless-vscode-e2e/src/suite/fixtures/recorded/queryBasedMatchers/changeList14.yml delete mode 100644 packages/cursorless-vscode-e2e/src/suite/fixtures/recorded/queryBasedMatchers/changeList2.yml delete mode 100644 packages/cursorless-vscode-e2e/src/suite/fixtures/recorded/queryBasedMatchers/changeList8.yml delete mode 100644 packages/cursorless-vscode-e2e/src/suite/fixtures/recorded/queryBasedMatchers/changeList9.yml rename packages/cursorless-vscode-e2e/src/suite/fixtures/recorded/queryBasedMatchers/{changeList3.yml => clearCall10.yml} (52%) create mode 100644 packages/cursorless-vscode-e2e/src/suite/fixtures/recorded/queryBasedMatchers/clearCall11.yml rename packages/cursorless-vscode-e2e/src/suite/fixtures/recorded/queryBasedMatchers/{changeList6.yml => clearCall12.yml} (52%) rename packages/cursorless-vscode-e2e/src/suite/fixtures/recorded/queryBasedMatchers/{changeList11.yml => clearCall14.yml} (73%) rename packages/cursorless-vscode-e2e/src/suite/fixtures/recorded/queryBasedMatchers/{changeList4.yml => clearCall15.yml} (61%) rename packages/cursorless-vscode-e2e/src/suite/fixtures/recorded/queryBasedMatchers/{changeList5.yml => clearCall7.yml} (52%) create mode 100644 packages/cursorless-vscode-e2e/src/suite/fixtures/recorded/queryBasedMatchers/clearCall8.yml create mode 100644 packages/cursorless-vscode-e2e/src/suite/fixtures/recorded/queryBasedMatchers/clearCall9.yml create mode 100644 packages/cursorless-vscode-e2e/src/suite/fixtures/recorded/queryBasedMatchers/clearCallBat.yml create mode 100644 packages/cursorless-vscode-e2e/src/suite/fixtures/recorded/queryBasedMatchers/clearCallBat2.yml create mode 100644 packages/cursorless-vscode-e2e/src/suite/fixtures/recorded/queryBasedMatchers/clearEveryCall.yml create mode 100644 packages/cursorless-vscode-e2e/src/suite/fixtures/recorded/queryBasedMatchers/clearFirstCall.yml create mode 100644 packages/cursorless-vscode-e2e/src/suite/fixtures/recorded/queryBasedMatchers/clearLastCall.yml rename packages/cursorless-vscode-e2e/src/suite/fixtures/recorded/{queryBasedMatchers => relativeScopes}/clearNextCall.yml (100%) rename packages/cursorless-vscode-e2e/src/suite/fixtures/recorded/{queryBasedMatchers => relativeScopes}/clearNextCall2.yml (100%) rename packages/cursorless-vscode-e2e/src/suite/fixtures/recorded/{queryBasedMatchers => relativeScopes}/clearNextCall3.yml (100%) rename packages/cursorless-vscode-e2e/src/suite/fixtures/recorded/{queryBasedMatchers => relativeScopes}/clearNextCall4.yml (100%) rename packages/cursorless-vscode-e2e/src/suite/fixtures/recorded/{queryBasedMatchers => relativeScopes}/clearNextCall5.yml (100%) rename packages/cursorless-vscode-e2e/src/suite/fixtures/recorded/{queryBasedMatchers => relativeScopes}/clearNextCall6.yml (100%) rename packages/cursorless-vscode-e2e/src/suite/fixtures/recorded/{queryBasedMatchers => relativeScopes}/clearNextCall7.yml (100%) rename packages/cursorless-vscode-e2e/src/suite/fixtures/recorded/{queryBasedMatchers => relativeScopes}/clearNextCall8.yml (100%) rename packages/cursorless-vscode-e2e/src/suite/fixtures/recorded/{queryBasedMatchers => relativeScopes}/clearSecondNextCall.yml (100%) create mode 100644 packages/cursorless-vscode-e2e/src/suite/fixtures/recorded/relativeScopes/clearTwoCalls.yml rename packages/cursorless-vscode-e2e/src/suite/fixtures/recorded/{queryBasedMatchers/changeList12.yml => relativeScopes/clearTwoCalls2.yml} (60%) create mode 100644 packages/cursorless-vscode-e2e/src/suite/fixtures/recorded/relativeScopes/clearTwoCalls3.yml create mode 100644 packages/cursorless-vscode-e2e/src/suite/fixtures/recorded/relativeScopes/clearTwoCalls4.yml diff --git a/packages/cursorless-vscode-e2e/src/suite/fixtures/recorded/queryBasedMatchers/changeList7.yml b/packages/cursorless-vscode-e2e/src/suite/fixtures/recorded/containingScope/clearCall.yml similarity index 63% rename from packages/cursorless-vscode-e2e/src/suite/fixtures/recorded/queryBasedMatchers/changeList7.yml rename to packages/cursorless-vscode-e2e/src/suite/fixtures/recorded/containingScope/clearCall.yml index e4c4d1e277..7901327715 100644 --- a/packages/cursorless-vscode-e2e/src/suite/fixtures/recorded/queryBasedMatchers/changeList7.yml +++ b/packages/cursorless-vscode-e2e/src/suite/fixtures/recorded/containingScope/clearCall.yml @@ -1,22 +1,24 @@ languageId: ruby command: version: 5 - spokenForm: change list + spokenForm: clear call action: {name: clearAndSetSelection} targets: - type: primitive modifiers: - type: containingScope - scopeType: {type: list} - usePrePhraseSnapshot: false + scopeType: {type: functionCall} + usePrePhraseSnapshot: true initialState: - documentContents: "[\"hello\"] " + documentContents: | + aaa(bbb()) selections: - - anchor: {line: 0, character: 6} + - anchor: {line: 0, character: 2} active: {line: 0, character: 6} marks: {} finalState: - documentContents: " " + documentContents: |+ + selections: - anchor: {line: 0, character: 0} active: {line: 0, character: 0} diff --git a/packages/cursorless-vscode-e2e/src/suite/fixtures/recorded/containingScope/clearCall2.yml b/packages/cursorless-vscode-e2e/src/suite/fixtures/recorded/containingScope/clearCall2.yml new file mode 100644 index 0000000000..f6c823796f --- /dev/null +++ b/packages/cursorless-vscode-e2e/src/suite/fixtures/recorded/containingScope/clearCall2.yml @@ -0,0 +1,22 @@ +languageId: ruby +command: + version: 5 + spokenForm: clear call + action: {name: clearAndSetSelection} + targets: + - type: primitive + modifiers: + - type: containingScope + scopeType: {type: functionCall} + usePrePhraseSnapshot: true +initialState: + documentContents: aaa(bbb(), ccc()) + selections: + - anchor: {line: 0, character: 6} + active: {line: 0, character: 13} + marks: {} +finalState: + documentContents: aaa() + selections: + - anchor: {line: 0, character: 4} + active: {line: 0, character: 4} diff --git a/packages/cursorless-vscode-e2e/src/suite/fixtures/recorded/ordinalScopes/clearFirstFunk.yml b/packages/cursorless-vscode-e2e/src/suite/fixtures/recorded/ordinalScopes/clearFirstFunk.yml deleted file mode 100644 index 90d7a8c701..0000000000 --- a/packages/cursorless-vscode-e2e/src/suite/fixtures/recorded/ordinalScopes/clearFirstFunk.yml +++ /dev/null @@ -1,32 +0,0 @@ -languageId: ruby -command: - version: 5 - spokenForm: clear first funk - action: {name: clearAndSetSelection} - targets: - - type: primitive - modifiers: - - type: ordinalScope - scopeType: {type: namedFunction} - start: 0 - length: 1 - usePrePhraseSnapshot: true -initialState: - documentContents: |- - def aaa; end - def bbb; end - def ccc; end - def ddd; end - selections: - - anchor: {line: 1, character: 7} - active: {line: 2, character: 7} - marks: {} -finalState: - documentContents: |- - def aaa; end - - def ccc; end - def ddd; end - selections: - - anchor: {line: 1, character: 0} - active: {line: 1, character: 0} diff --git a/packages/cursorless-vscode-e2e/src/suite/fixtures/recorded/ordinalScopes/clearLastFunk.yml b/packages/cursorless-vscode-e2e/src/suite/fixtures/recorded/ordinalScopes/clearLastFunk.yml deleted file mode 100644 index 1dc14df647..0000000000 --- a/packages/cursorless-vscode-e2e/src/suite/fixtures/recorded/ordinalScopes/clearLastFunk.yml +++ /dev/null @@ -1,32 +0,0 @@ -languageId: ruby -command: - version: 5 - spokenForm: clear last funk - action: {name: clearAndSetSelection} - targets: - - type: primitive - modifiers: - - type: ordinalScope - scopeType: {type: namedFunction} - start: -1 - length: 1 - usePrePhraseSnapshot: true -initialState: - documentContents: |- - def aaa; end - def bbb; end - def ccc; end - def ddd; end - selections: - - anchor: {line: 1, character: 7} - active: {line: 2, character: 7} - marks: {} -finalState: - documentContents: |- - def aaa; end - def bbb; end - - def ddd; end - selections: - - anchor: {line: 2, character: 0} - active: {line: 2, character: 0} diff --git a/packages/cursorless-vscode-e2e/src/suite/fixtures/recorded/queryBasedMatchers/changeList.yml b/packages/cursorless-vscode-e2e/src/suite/fixtures/recorded/queryBasedMatchers/changeList.yml deleted file mode 100644 index d2db8a800b..0000000000 --- a/packages/cursorless-vscode-e2e/src/suite/fixtures/recorded/queryBasedMatchers/changeList.yml +++ /dev/null @@ -1,26 +0,0 @@ -languageId: ruby -command: - version: 5 - spokenForm: change list - action: {name: clearAndSetSelection} - targets: - - type: primitive - modifiers: - - type: containingScope - scopeType: {type: list} - usePrePhraseSnapshot: false -initialState: - documentContents: | - ["hello"] - #comment - ["world"] - selections: - - anchor: {line: 0, character: 5} - active: {line: 2, character: 6} - marks: {} -finalState: - documentContents: |+ - - selections: - - anchor: {line: 0, character: 0} - active: {line: 0, character: 0} diff --git a/packages/cursorless-vscode-e2e/src/suite/fixtures/recorded/queryBasedMatchers/changeList10.yml b/packages/cursorless-vscode-e2e/src/suite/fixtures/recorded/queryBasedMatchers/changeList10.yml deleted file mode 100644 index 386380717f..0000000000 --- a/packages/cursorless-vscode-e2e/src/suite/fixtures/recorded/queryBasedMatchers/changeList10.yml +++ /dev/null @@ -1,22 +0,0 @@ -languageId: ruby -command: - version: 5 - spokenForm: change list - action: {name: clearAndSetSelection} - targets: - - type: primitive - modifiers: - - type: containingScope - scopeType: {type: list} - usePrePhraseSnapshot: false -initialState: - documentContents: "[1, 2, 3, [4, 5, 6, [7, 8, 9]]]" - selections: - - anchor: {line: 0, character: 18} - active: {line: 0, character: 18} - marks: {} -finalState: - documentContents: "[1, 2, 3, ]" - selections: - - anchor: {line: 0, character: 10} - active: {line: 0, character: 10} diff --git a/packages/cursorless-vscode-e2e/src/suite/fixtures/recorded/queryBasedMatchers/changeList13.yml b/packages/cursorless-vscode-e2e/src/suite/fixtures/recorded/queryBasedMatchers/changeList13.yml deleted file mode 100644 index 71fbb522d9..0000000000 --- a/packages/cursorless-vscode-e2e/src/suite/fixtures/recorded/queryBasedMatchers/changeList13.yml +++ /dev/null @@ -1,25 +0,0 @@ -languageId: ruby -command: - version: 5 - spokenForm: change list - action: {name: clearAndSetSelection} - targets: - - type: primitive - modifiers: - - type: containingScope - scopeType: {type: list} - usePrePhraseSnapshot: false -initialState: - documentContents: |- - [1, 2, 3, [4, 5, 6, [7, 8, 9]]] - - [["a"], ["b", "c", ["d", "e"]], "f"] - selections: - - anchor: {line: 0, character: 24} - active: {line: 2, character: 4} - marks: {} -finalState: - documentContents: "[1, 2, 3, [4, 5, 6, , [\"b\", \"c\", [\"d\", \"e\"]], \"f\"]" - selections: - - anchor: {line: 0, character: 20} - active: {line: 0, character: 20} diff --git a/packages/cursorless-vscode-e2e/src/suite/fixtures/recorded/queryBasedMatchers/changeList14.yml b/packages/cursorless-vscode-e2e/src/suite/fixtures/recorded/queryBasedMatchers/changeList14.yml deleted file mode 100644 index 816d43d339..0000000000 --- a/packages/cursorless-vscode-e2e/src/suite/fixtures/recorded/queryBasedMatchers/changeList14.yml +++ /dev/null @@ -1,25 +0,0 @@ -languageId: ruby -command: - version: 5 - spokenForm: change list - action: {name: clearAndSetSelection} - targets: - - type: primitive - modifiers: - - type: containingScope - scopeType: {type: list} - usePrePhraseSnapshot: false -initialState: - documentContents: |- - [1, 2, 3, [4, 5, 6, [7, 8, 9]]] - - [["a"], ["b", "c", ["d", "e"]], "f"] - selections: - - anchor: {line: 0, character: 14} - active: {line: 2, character: 14} - marks: {} -finalState: - documentContents: "[1, 2, 3, , \"f\"]" - selections: - - anchor: {line: 0, character: 10} - active: {line: 0, character: 10} diff --git a/packages/cursorless-vscode-e2e/src/suite/fixtures/recorded/queryBasedMatchers/changeList2.yml b/packages/cursorless-vscode-e2e/src/suite/fixtures/recorded/queryBasedMatchers/changeList2.yml deleted file mode 100644 index 2519e7abb7..0000000000 --- a/packages/cursorless-vscode-e2e/src/suite/fixtures/recorded/queryBasedMatchers/changeList2.yml +++ /dev/null @@ -1,22 +0,0 @@ -languageId: ruby -command: - version: 5 - spokenForm: change list - action: {name: clearAndSetSelection} - targets: - - type: primitive - modifiers: - - type: containingScope - scopeType: {type: list} - usePrePhraseSnapshot: false -initialState: - documentContents: "[\"hello\"]" - selections: - - anchor: {line: 0, character: 0} - active: {line: 0, character: 0} - marks: {} -finalState: - documentContents: "" - selections: - - anchor: {line: 0, character: 0} - active: {line: 0, character: 0} diff --git a/packages/cursorless-vscode-e2e/src/suite/fixtures/recorded/queryBasedMatchers/changeList8.yml b/packages/cursorless-vscode-e2e/src/suite/fixtures/recorded/queryBasedMatchers/changeList8.yml deleted file mode 100644 index 0379f5d138..0000000000 --- a/packages/cursorless-vscode-e2e/src/suite/fixtures/recorded/queryBasedMatchers/changeList8.yml +++ /dev/null @@ -1,22 +0,0 @@ -languageId: ruby -command: - version: 5 - spokenForm: change list - action: {name: clearAndSetSelection} - targets: - - type: primitive - modifiers: - - type: containingScope - scopeType: {type: list} - usePrePhraseSnapshot: false -initialState: - documentContents: "[1,2,3, [4,5,6, [7, 8, 9]]]" - selections: - - anchor: {line: 0, character: 19} - active: {line: 0, character: 19} - marks: {} -finalState: - documentContents: "[1,2,3, [4,5,6, ]]" - selections: - - anchor: {line: 0, character: 16} - active: {line: 0, character: 16} diff --git a/packages/cursorless-vscode-e2e/src/suite/fixtures/recorded/queryBasedMatchers/changeList9.yml b/packages/cursorless-vscode-e2e/src/suite/fixtures/recorded/queryBasedMatchers/changeList9.yml deleted file mode 100644 index 146b3b120c..0000000000 --- a/packages/cursorless-vscode-e2e/src/suite/fixtures/recorded/queryBasedMatchers/changeList9.yml +++ /dev/null @@ -1,22 +0,0 @@ -languageId: ruby -command: - version: 5 - spokenForm: change list - action: {name: clearAndSetSelection} - targets: - - type: primitive - modifiers: - - type: containingScope - scopeType: {type: list} - usePrePhraseSnapshot: false -initialState: - documentContents: "[1, 2, 3, [4, 5, 6, [7, 8, 9]]]" - selections: - - anchor: {line: 0, character: 24} - active: {line: 0, character: 24} - marks: {} -finalState: - documentContents: "[1, 2, 3, [4, 5, 6, ]]" - selections: - - anchor: {line: 0, character: 20} - active: {line: 0, character: 20} diff --git a/packages/cursorless-vscode-e2e/src/suite/fixtures/recorded/queryBasedMatchers/changeList3.yml b/packages/cursorless-vscode-e2e/src/suite/fixtures/recorded/queryBasedMatchers/clearCall10.yml similarity index 52% rename from packages/cursorless-vscode-e2e/src/suite/fixtures/recorded/queryBasedMatchers/changeList3.yml rename to packages/cursorless-vscode-e2e/src/suite/fixtures/recorded/queryBasedMatchers/clearCall10.yml index a5364b262e..2f07ed3e5d 100644 --- a/packages/cursorless-vscode-e2e/src/suite/fixtures/recorded/queryBasedMatchers/changeList3.yml +++ b/packages/cursorless-vscode-e2e/src/suite/fixtures/recorded/queryBasedMatchers/clearCall10.yml @@ -1,22 +1,18 @@ languageId: ruby command: version: 5 - spokenForm: change list + spokenForm: clear call action: {name: clearAndSetSelection} targets: - type: primitive modifiers: - type: containingScope - scopeType: {type: list} - usePrePhraseSnapshot: false + scopeType: {type: functionCall} + usePrePhraseSnapshot: true initialState: - documentContents: "[\"hello\"]" + documentContents: " aaa()" selections: - - anchor: {line: 0, character: 1} + - anchor: {line: 0, character: 0} active: {line: 0, character: 1} marks: {} -finalState: - documentContents: "" - selections: - - anchor: {line: 0, character: 0} - active: {line: 0, character: 0} +thrownError: {name: NoContainingScopeError} diff --git a/packages/cursorless-vscode-e2e/src/suite/fixtures/recorded/queryBasedMatchers/clearCall11.yml b/packages/cursorless-vscode-e2e/src/suite/fixtures/recorded/queryBasedMatchers/clearCall11.yml new file mode 100644 index 0000000000..883664d699 --- /dev/null +++ b/packages/cursorless-vscode-e2e/src/suite/fixtures/recorded/queryBasedMatchers/clearCall11.yml @@ -0,0 +1,18 @@ +languageId: ruby +command: + version: 5 + spokenForm: clear call + action: {name: clearAndSetSelection} + targets: + - type: primitive + modifiers: + - type: containingScope + scopeType: {type: functionCall} + usePrePhraseSnapshot: true +initialState: + documentContents: " aaa()" + selections: + - anchor: {line: 0, character: 0} + active: {line: 0, character: 2} + marks: {} +thrownError: {name: NoContainingScopeError} diff --git a/packages/cursorless-vscode-e2e/src/suite/fixtures/recorded/queryBasedMatchers/changeList6.yml b/packages/cursorless-vscode-e2e/src/suite/fixtures/recorded/queryBasedMatchers/clearCall12.yml similarity index 52% rename from packages/cursorless-vscode-e2e/src/suite/fixtures/recorded/queryBasedMatchers/changeList6.yml rename to packages/cursorless-vscode-e2e/src/suite/fixtures/recorded/queryBasedMatchers/clearCall12.yml index b520ada47b..a87d14971b 100644 --- a/packages/cursorless-vscode-e2e/src/suite/fixtures/recorded/queryBasedMatchers/changeList6.yml +++ b/packages/cursorless-vscode-e2e/src/suite/fixtures/recorded/queryBasedMatchers/clearCall12.yml @@ -1,22 +1,18 @@ languageId: ruby command: version: 5 - spokenForm: change list + spokenForm: clear call action: {name: clearAndSetSelection} targets: - type: primitive modifiers: - type: containingScope - scopeType: {type: list} - usePrePhraseSnapshot: false + scopeType: {type: functionCall} + usePrePhraseSnapshot: true initialState: - documentContents: "[\"hello\"] " + documentContents: " aaa() " selections: - - anchor: {line: 0, character: 4} + - anchor: {line: 0, character: 0} active: {line: 0, character: 7} marks: {} -finalState: - documentContents: " " - selections: - - anchor: {line: 0, character: 0} - active: {line: 0, character: 0} +thrownError: {name: NoContainingScopeError} diff --git a/packages/cursorless-vscode-e2e/src/suite/fixtures/recorded/queryBasedMatchers/changeList11.yml b/packages/cursorless-vscode-e2e/src/suite/fixtures/recorded/queryBasedMatchers/clearCall14.yml similarity index 73% rename from packages/cursorless-vscode-e2e/src/suite/fixtures/recorded/queryBasedMatchers/changeList11.yml rename to packages/cursorless-vscode-e2e/src/suite/fixtures/recorded/queryBasedMatchers/clearCall14.yml index 309f715d45..77fdd11330 100644 --- a/packages/cursorless-vscode-e2e/src/suite/fixtures/recorded/queryBasedMatchers/changeList11.yml +++ b/packages/cursorless-vscode-e2e/src/suite/fixtures/recorded/queryBasedMatchers/clearCall14.yml @@ -1,16 +1,16 @@ languageId: ruby command: version: 5 - spokenForm: change list + spokenForm: clear call action: {name: clearAndSetSelection} targets: - type: primitive modifiers: - type: containingScope - scopeType: {type: list} - usePrePhraseSnapshot: false + scopeType: {type: functionCall} + usePrePhraseSnapshot: true initialState: - documentContents: "[1, 2, 3, [4, 5, 6, [7, 8, 9]]]" + documentContents: aaa() selections: - anchor: {line: 0, character: 5} active: {line: 0, character: 5} diff --git a/packages/cursorless-vscode-e2e/src/suite/fixtures/recorded/queryBasedMatchers/changeList4.yml b/packages/cursorless-vscode-e2e/src/suite/fixtures/recorded/queryBasedMatchers/clearCall15.yml similarity index 61% rename from packages/cursorless-vscode-e2e/src/suite/fixtures/recorded/queryBasedMatchers/changeList4.yml rename to packages/cursorless-vscode-e2e/src/suite/fixtures/recorded/queryBasedMatchers/clearCall15.yml index 9675960b69..a6719e38fa 100644 --- a/packages/cursorless-vscode-e2e/src/suite/fixtures/recorded/queryBasedMatchers/changeList4.yml +++ b/packages/cursorless-vscode-e2e/src/suite/fixtures/recorded/queryBasedMatchers/clearCall15.yml @@ -1,19 +1,19 @@ languageId: ruby command: version: 5 - spokenForm: change list + spokenForm: clear call action: {name: clearAndSetSelection} targets: - type: primitive modifiers: - type: containingScope - scopeType: {type: list} - usePrePhraseSnapshot: false + scopeType: {type: functionCall} + usePrePhraseSnapshot: true initialState: - documentContents: "[\"hello\"]" + documentContents: aaa() + bbb() selections: - - anchor: {line: 0, character: 8} - active: {line: 0, character: 8} + - anchor: {line: 0, character: 2} + active: {line: 0, character: 10} marks: {} finalState: documentContents: "" diff --git a/packages/cursorless-vscode-e2e/src/suite/fixtures/recorded/queryBasedMatchers/changeList5.yml b/packages/cursorless-vscode-e2e/src/suite/fixtures/recorded/queryBasedMatchers/clearCall7.yml similarity index 52% rename from packages/cursorless-vscode-e2e/src/suite/fixtures/recorded/queryBasedMatchers/changeList5.yml rename to packages/cursorless-vscode-e2e/src/suite/fixtures/recorded/queryBasedMatchers/clearCall7.yml index 9430689c87..d713ed63fc 100644 --- a/packages/cursorless-vscode-e2e/src/suite/fixtures/recorded/queryBasedMatchers/changeList5.yml +++ b/packages/cursorless-vscode-e2e/src/suite/fixtures/recorded/queryBasedMatchers/clearCall7.yml @@ -1,22 +1,18 @@ languageId: ruby command: version: 5 - spokenForm: change list + spokenForm: clear call action: {name: clearAndSetSelection} targets: - type: primitive modifiers: - type: containingScope - scopeType: {type: list} - usePrePhraseSnapshot: false + scopeType: {type: functionCall} + usePrePhraseSnapshot: true initialState: - documentContents: "[\"hello\"]" - selections: - - anchor: {line: 0, character: 9} - active: {line: 0, character: 9} - marks: {} -finalState: - documentContents: "" + documentContents: " aaa()" selections: - anchor: {line: 0, character: 0} active: {line: 0, character: 0} + marks: {} +thrownError: {name: NoContainingScopeError} diff --git a/packages/cursorless-vscode-e2e/src/suite/fixtures/recorded/queryBasedMatchers/clearCall8.yml b/packages/cursorless-vscode-e2e/src/suite/fixtures/recorded/queryBasedMatchers/clearCall8.yml new file mode 100644 index 0000000000..de92a335de --- /dev/null +++ b/packages/cursorless-vscode-e2e/src/suite/fixtures/recorded/queryBasedMatchers/clearCall8.yml @@ -0,0 +1,18 @@ +languageId: ruby +command: + version: 5 + spokenForm: clear call + action: {name: clearAndSetSelection} + targets: + - type: primitive + modifiers: + - type: containingScope + scopeType: {type: functionCall} + usePrePhraseSnapshot: true +initialState: + documentContents: "aaa() " + selections: + - anchor: {line: 0, character: 6} + active: {line: 0, character: 6} + marks: {} +thrownError: {name: NoContainingScopeError} diff --git a/packages/cursorless-vscode-e2e/src/suite/fixtures/recorded/queryBasedMatchers/clearCall9.yml b/packages/cursorless-vscode-e2e/src/suite/fixtures/recorded/queryBasedMatchers/clearCall9.yml new file mode 100644 index 0000000000..c5949421e2 --- /dev/null +++ b/packages/cursorless-vscode-e2e/src/suite/fixtures/recorded/queryBasedMatchers/clearCall9.yml @@ -0,0 +1,18 @@ +languageId: ruby +command: + version: 5 + spokenForm: clear call + action: {name: clearAndSetSelection} + targets: + - type: primitive + modifiers: + - type: containingScope + scopeType: {type: functionCall} + usePrePhraseSnapshot: true +initialState: + documentContents: " aaa()" + selections: + - anchor: {line: 0, character: 0} + active: {line: 0, character: 1} + marks: {} +thrownError: {name: NoContainingScopeError} diff --git a/packages/cursorless-vscode-e2e/src/suite/fixtures/recorded/queryBasedMatchers/clearCallBat.yml b/packages/cursorless-vscode-e2e/src/suite/fixtures/recorded/queryBasedMatchers/clearCallBat.yml new file mode 100644 index 0000000000..6ca8bf2c2f --- /dev/null +++ b/packages/cursorless-vscode-e2e/src/suite/fixtures/recorded/queryBasedMatchers/clearCallBat.yml @@ -0,0 +1,28 @@ +languageId: ruby +command: + version: 5 + spokenForm: clear call bat + action: {name: clearAndSetSelection} + targets: + - type: primitive + modifiers: + - type: containingScope + scopeType: {type: functionCall} + mark: {type: decoratedSymbol, symbolColor: default, character: b} + usePrePhraseSnapshot: true +initialState: + documentContents: | + aaa(bbb()) + selections: + - anchor: {line: 1, character: 0} + active: {line: 1, character: 0} + marks: + default.b: + start: {line: 0, character: 4} + end: {line: 0, character: 7} +finalState: + documentContents: | + aaa() + selections: + - anchor: {line: 0, character: 4} + active: {line: 0, character: 4} diff --git a/packages/cursorless-vscode-e2e/src/suite/fixtures/recorded/queryBasedMatchers/clearCallBat2.yml b/packages/cursorless-vscode-e2e/src/suite/fixtures/recorded/queryBasedMatchers/clearCallBat2.yml new file mode 100644 index 0000000000..8ecf17eae8 --- /dev/null +++ b/packages/cursorless-vscode-e2e/src/suite/fixtures/recorded/queryBasedMatchers/clearCallBat2.yml @@ -0,0 +1,28 @@ +languageId: ruby +command: + version: 5 + spokenForm: clear call bat + action: {name: clearAndSetSelection} + targets: + - type: primitive + modifiers: + - type: containingScope + scopeType: {type: functionCall} + mark: {type: decoratedSymbol, symbolColor: default, character: b} + usePrePhraseSnapshot: true +initialState: + documentContents: | + aaa(bbb) + selections: + - anchor: {line: 1, character: 0} + active: {line: 1, character: 0} + marks: + default.b: + start: {line: 0, character: 4} + end: {line: 0, character: 7} +finalState: + documentContents: |+ + + selections: + - anchor: {line: 0, character: 0} + active: {line: 0, character: 0} diff --git a/packages/cursorless-vscode-e2e/src/suite/fixtures/recorded/queryBasedMatchers/clearEveryCall.yml b/packages/cursorless-vscode-e2e/src/suite/fixtures/recorded/queryBasedMatchers/clearEveryCall.yml new file mode 100644 index 0000000000..ef96273681 --- /dev/null +++ b/packages/cursorless-vscode-e2e/src/suite/fixtures/recorded/queryBasedMatchers/clearEveryCall.yml @@ -0,0 +1,24 @@ +languageId: ruby +command: + version: 5 + spokenForm: clear every call + action: {name: clearAndSetSelection} + targets: + - type: primitive + modifiers: + - type: everyScope + scopeType: {type: functionCall} + usePrePhraseSnapshot: true +initialState: + documentContents: aaa() + bbb() + ccc() + ddd() + selections: + - anchor: {line: 0, character: 10} + active: {line: 0, character: 18} + marks: {} +finalState: + documentContents: aaa() + + + ddd() + selections: + - anchor: {line: 0, character: 8} + active: {line: 0, character: 8} + - anchor: {line: 0, character: 11} + active: {line: 0, character: 11} diff --git a/packages/cursorless-vscode-e2e/src/suite/fixtures/recorded/queryBasedMatchers/clearFirstCall.yml b/packages/cursorless-vscode-e2e/src/suite/fixtures/recorded/queryBasedMatchers/clearFirstCall.yml new file mode 100644 index 0000000000..066bfe1cf5 --- /dev/null +++ b/packages/cursorless-vscode-e2e/src/suite/fixtures/recorded/queryBasedMatchers/clearFirstCall.yml @@ -0,0 +1,24 @@ +languageId: ruby +command: + version: 5 + spokenForm: clear first call + action: {name: clearAndSetSelection} + targets: + - type: primitive + modifiers: + - type: ordinalScope + scopeType: {type: functionCall} + start: 0 + length: 1 + usePrePhraseSnapshot: true +initialState: + documentContents: aaa() + bbb() + ccc() + ddd() + selections: + - anchor: {line: 0, character: 10} + active: {line: 0, character: 18} + marks: {} +finalState: + documentContents: aaa() + + ccc() + ddd() + selections: + - anchor: {line: 0, character: 8} + active: {line: 0, character: 8} diff --git a/packages/cursorless-vscode-e2e/src/suite/fixtures/recorded/queryBasedMatchers/clearLastCall.yml b/packages/cursorless-vscode-e2e/src/suite/fixtures/recorded/queryBasedMatchers/clearLastCall.yml new file mode 100644 index 0000000000..bc4c885e6e --- /dev/null +++ b/packages/cursorless-vscode-e2e/src/suite/fixtures/recorded/queryBasedMatchers/clearLastCall.yml @@ -0,0 +1,24 @@ +languageId: ruby +command: + version: 5 + spokenForm: clear last call + action: {name: clearAndSetSelection} + targets: + - type: primitive + modifiers: + - type: ordinalScope + scopeType: {type: functionCall} + start: -1 + length: 1 + usePrePhraseSnapshot: true +initialState: + documentContents: aaa() + bbb() + ccc() + ddd() + selections: + - anchor: {line: 0, character: 10} + active: {line: 0, character: 18} + marks: {} +finalState: + documentContents: aaa() + bbb() + + ddd() + selections: + - anchor: {line: 0, character: 16} + active: {line: 0, character: 16} diff --git a/packages/cursorless-vscode-e2e/src/suite/fixtures/recorded/queryBasedMatchers/clearNextCall.yml b/packages/cursorless-vscode-e2e/src/suite/fixtures/recorded/relativeScopes/clearNextCall.yml similarity index 100% rename from packages/cursorless-vscode-e2e/src/suite/fixtures/recorded/queryBasedMatchers/clearNextCall.yml rename to packages/cursorless-vscode-e2e/src/suite/fixtures/recorded/relativeScopes/clearNextCall.yml diff --git a/packages/cursorless-vscode-e2e/src/suite/fixtures/recorded/queryBasedMatchers/clearNextCall2.yml b/packages/cursorless-vscode-e2e/src/suite/fixtures/recorded/relativeScopes/clearNextCall2.yml similarity index 100% rename from packages/cursorless-vscode-e2e/src/suite/fixtures/recorded/queryBasedMatchers/clearNextCall2.yml rename to packages/cursorless-vscode-e2e/src/suite/fixtures/recorded/relativeScopes/clearNextCall2.yml diff --git a/packages/cursorless-vscode-e2e/src/suite/fixtures/recorded/queryBasedMatchers/clearNextCall3.yml b/packages/cursorless-vscode-e2e/src/suite/fixtures/recorded/relativeScopes/clearNextCall3.yml similarity index 100% rename from packages/cursorless-vscode-e2e/src/suite/fixtures/recorded/queryBasedMatchers/clearNextCall3.yml rename to packages/cursorless-vscode-e2e/src/suite/fixtures/recorded/relativeScopes/clearNextCall3.yml diff --git a/packages/cursorless-vscode-e2e/src/suite/fixtures/recorded/queryBasedMatchers/clearNextCall4.yml b/packages/cursorless-vscode-e2e/src/suite/fixtures/recorded/relativeScopes/clearNextCall4.yml similarity index 100% rename from packages/cursorless-vscode-e2e/src/suite/fixtures/recorded/queryBasedMatchers/clearNextCall4.yml rename to packages/cursorless-vscode-e2e/src/suite/fixtures/recorded/relativeScopes/clearNextCall4.yml diff --git a/packages/cursorless-vscode-e2e/src/suite/fixtures/recorded/queryBasedMatchers/clearNextCall5.yml b/packages/cursorless-vscode-e2e/src/suite/fixtures/recorded/relativeScopes/clearNextCall5.yml similarity index 100% rename from packages/cursorless-vscode-e2e/src/suite/fixtures/recorded/queryBasedMatchers/clearNextCall5.yml rename to packages/cursorless-vscode-e2e/src/suite/fixtures/recorded/relativeScopes/clearNextCall5.yml diff --git a/packages/cursorless-vscode-e2e/src/suite/fixtures/recorded/queryBasedMatchers/clearNextCall6.yml b/packages/cursorless-vscode-e2e/src/suite/fixtures/recorded/relativeScopes/clearNextCall6.yml similarity index 100% rename from packages/cursorless-vscode-e2e/src/suite/fixtures/recorded/queryBasedMatchers/clearNextCall6.yml rename to packages/cursorless-vscode-e2e/src/suite/fixtures/recorded/relativeScopes/clearNextCall6.yml diff --git a/packages/cursorless-vscode-e2e/src/suite/fixtures/recorded/queryBasedMatchers/clearNextCall7.yml b/packages/cursorless-vscode-e2e/src/suite/fixtures/recorded/relativeScopes/clearNextCall7.yml similarity index 100% rename from packages/cursorless-vscode-e2e/src/suite/fixtures/recorded/queryBasedMatchers/clearNextCall7.yml rename to packages/cursorless-vscode-e2e/src/suite/fixtures/recorded/relativeScopes/clearNextCall7.yml diff --git a/packages/cursorless-vscode-e2e/src/suite/fixtures/recorded/queryBasedMatchers/clearNextCall8.yml b/packages/cursorless-vscode-e2e/src/suite/fixtures/recorded/relativeScopes/clearNextCall8.yml similarity index 100% rename from packages/cursorless-vscode-e2e/src/suite/fixtures/recorded/queryBasedMatchers/clearNextCall8.yml rename to packages/cursorless-vscode-e2e/src/suite/fixtures/recorded/relativeScopes/clearNextCall8.yml diff --git a/packages/cursorless-vscode-e2e/src/suite/fixtures/recorded/queryBasedMatchers/clearSecondNextCall.yml b/packages/cursorless-vscode-e2e/src/suite/fixtures/recorded/relativeScopes/clearSecondNextCall.yml similarity index 100% rename from packages/cursorless-vscode-e2e/src/suite/fixtures/recorded/queryBasedMatchers/clearSecondNextCall.yml rename to packages/cursorless-vscode-e2e/src/suite/fixtures/recorded/relativeScopes/clearSecondNextCall.yml diff --git a/packages/cursorless-vscode-e2e/src/suite/fixtures/recorded/relativeScopes/clearTwoCalls.yml b/packages/cursorless-vscode-e2e/src/suite/fixtures/recorded/relativeScopes/clearTwoCalls.yml new file mode 100644 index 0000000000..6440cce43e --- /dev/null +++ b/packages/cursorless-vscode-e2e/src/suite/fixtures/recorded/relativeScopes/clearTwoCalls.yml @@ -0,0 +1,25 @@ +languageId: ruby +command: + version: 5 + spokenForm: clear two calls + action: {name: clearAndSetSelection} + targets: + - type: primitive + modifiers: + - type: relativeScope + scopeType: {type: functionCall} + offset: 0 + length: 2 + direction: forward + usePrePhraseSnapshot: true +initialState: + documentContents: aaa(bbb(), ccc()) + selections: + - anchor: {line: 0, character: 4} + active: {line: 0, character: 4} + marks: {} +finalState: + documentContents: aaa() + selections: + - anchor: {line: 0, character: 4} + active: {line: 0, character: 4} diff --git a/packages/cursorless-vscode-e2e/src/suite/fixtures/recorded/queryBasedMatchers/changeList12.yml b/packages/cursorless-vscode-e2e/src/suite/fixtures/recorded/relativeScopes/clearTwoCalls2.yml similarity index 60% rename from packages/cursorless-vscode-e2e/src/suite/fixtures/recorded/queryBasedMatchers/changeList12.yml rename to packages/cursorless-vscode-e2e/src/suite/fixtures/recorded/relativeScopes/clearTwoCalls2.yml index e159146172..04920bdda8 100644 --- a/packages/cursorless-vscode-e2e/src/suite/fixtures/recorded/queryBasedMatchers/changeList12.yml +++ b/packages/cursorless-vscode-e2e/src/suite/fixtures/recorded/relativeScopes/clearTwoCalls2.yml @@ -1,16 +1,19 @@ languageId: ruby command: version: 5 - spokenForm: change list + spokenForm: clear two calls action: {name: clearAndSetSelection} targets: - type: primitive modifiers: - - type: containingScope - scopeType: {type: list} - usePrePhraseSnapshot: false + - type: relativeScope + scopeType: {type: functionCall} + offset: 0 + length: 2 + direction: forward + usePrePhraseSnapshot: true initialState: - documentContents: "[1, 2, 3, [4, 5, 6, [7, 8, 9]]]" + documentContents: aaa(bbb()) + ccc() selections: - anchor: {line: 0, character: 0} active: {line: 0, character: 0} diff --git a/packages/cursorless-vscode-e2e/src/suite/fixtures/recorded/relativeScopes/clearTwoCalls3.yml b/packages/cursorless-vscode-e2e/src/suite/fixtures/recorded/relativeScopes/clearTwoCalls3.yml new file mode 100644 index 0000000000..18d3d68473 --- /dev/null +++ b/packages/cursorless-vscode-e2e/src/suite/fixtures/recorded/relativeScopes/clearTwoCalls3.yml @@ -0,0 +1,25 @@ +languageId: ruby +command: + version: 5 + spokenForm: clear two calls + action: {name: clearAndSetSelection} + targets: + - type: primitive + modifiers: + - type: relativeScope + scopeType: {type: functionCall} + offset: 0 + length: 2 + direction: forward + usePrePhraseSnapshot: true +initialState: + documentContents: aaa(bbb()) + ccc() + selections: + - anchor: {line: 0, character: 3} + active: {line: 0, character: 3} + marks: {} +finalState: + documentContents: "" + selections: + - anchor: {line: 0, character: 0} + active: {line: 0, character: 0} diff --git a/packages/cursorless-vscode-e2e/src/suite/fixtures/recorded/relativeScopes/clearTwoCalls4.yml b/packages/cursorless-vscode-e2e/src/suite/fixtures/recorded/relativeScopes/clearTwoCalls4.yml new file mode 100644 index 0000000000..9bef7e3fd5 --- /dev/null +++ b/packages/cursorless-vscode-e2e/src/suite/fixtures/recorded/relativeScopes/clearTwoCalls4.yml @@ -0,0 +1,25 @@ +languageId: ruby +command: + version: 5 + spokenForm: clear two calls + action: {name: clearAndSetSelection} + targets: + - type: primitive + modifiers: + - type: relativeScope + scopeType: {type: functionCall} + offset: 0 + length: 2 + direction: forward + usePrePhraseSnapshot: true +initialState: + documentContents: aaa(bbb()) + ccc() + selections: + - anchor: {line: 0, character: 4} + active: {line: 0, character: 4} + marks: {} +finalState: + documentContents: aaa( + selections: + - anchor: {line: 0, character: 4} + active: {line: 0, character: 4} From 6c8058f2560e4b35e5df4af8b91b3ec48ea4b835 Mon Sep 17 00:00:00 2001 From: Pokey Rule <755842+pokey@users.noreply.github.com> Date: Sat, 15 Apr 2023 22:35:56 +0100 Subject: [PATCH 22/25] `ruby/scopeTypes.scm` => `ruby.scm` --- .../cursorless-engine/src/languages/LanguageDefinitionImpl.ts | 2 +- queries/{ruby/scopeTypes.scm => ruby.scm} | 0 2 files changed, 1 insertion(+), 1 deletion(-) rename queries/{ruby/scopeTypes.scm => ruby.scm} (100%) diff --git a/packages/cursorless-engine/src/languages/LanguageDefinitionImpl.ts b/packages/cursorless-engine/src/languages/LanguageDefinitionImpl.ts index 7aa4f2ce0b..35e86245cd 100644 --- a/packages/cursorless-engine/src/languages/LanguageDefinitionImpl.ts +++ b/packages/cursorless-engine/src/languages/LanguageDefinitionImpl.ts @@ -15,7 +15,7 @@ export class LanguageDefinitionImpl implements LanguageDefinition { init() { const rawLanguageQueryString = readFileSync( - join(ide().assetsRoot, "queries", this.languageId, "scopeTypes.scm"), + join(ide().assetsRoot, "queries", `${this.languageId}.scm`), "utf8", ); diff --git a/queries/ruby/scopeTypes.scm b/queries/ruby.scm similarity index 100% rename from queries/ruby/scopeTypes.scm rename to queries/ruby.scm From 631aa40964e12047d110c84e22b5f3623635e573 Mon Sep 17 00:00:00 2001 From: Pokey Rule <755842+pokey@users.noreply.github.com> Date: Sun, 16 Apr 2023 12:42:12 +0100 Subject: [PATCH 23/25] Remove test case --- .../queryBasedMatchers/clearCall6.yml | 22 ------------------- 1 file changed, 22 deletions(-) delete mode 100644 packages/cursorless-vscode-e2e/src/suite/fixtures/recorded/queryBasedMatchers/clearCall6.yml diff --git a/packages/cursorless-vscode-e2e/src/suite/fixtures/recorded/queryBasedMatchers/clearCall6.yml b/packages/cursorless-vscode-e2e/src/suite/fixtures/recorded/queryBasedMatchers/clearCall6.yml deleted file mode 100644 index 4ae038c1e7..0000000000 --- a/packages/cursorless-vscode-e2e/src/suite/fixtures/recorded/queryBasedMatchers/clearCall6.yml +++ /dev/null @@ -1,22 +0,0 @@ -languageId: ruby -command: - version: 5 - spokenForm: clear call - action: {name: clearAndSetSelection} - targets: - - type: primitive - modifiers: - - type: containingScope - scopeType: {type: functionCall} - usePrePhraseSnapshot: true -initialState: - documentContents: aaa(bbb()) - selections: - - anchor: {line: 0, character: 9} - active: {line: 0, character: 9} - marks: {} -finalState: - documentContents: "" - selections: - - anchor: {line: 0, character: 0} - active: {line: 0, character: 0} From 38d35f794960386fef7613a6054236351869699e Mon Sep 17 00:00:00 2001 From: Pokey Rule <755842+pokey@users.noreply.github.com> Date: Sun, 16 Apr 2023 20:13:20 +0100 Subject: [PATCH 24/25] More cleanup --- .../scopeHandlers/TreeSitterScopeHandler.ts | 116 +++++++++--------- 1 file changed, 56 insertions(+), 60 deletions(-) diff --git a/packages/cursorless-engine/src/processTargets/modifiers/scopeHandlers/TreeSitterScopeHandler.ts b/packages/cursorless-engine/src/processTargets/modifiers/scopeHandlers/TreeSitterScopeHandler.ts index 823d228d20..44db8e526d 100644 --- a/packages/cursorless-engine/src/processTargets/modifiers/scopeHandlers/TreeSitterScopeHandler.ts +++ b/packages/cursorless-engine/src/processTargets/modifiers/scopeHandlers/TreeSitterScopeHandler.ts @@ -42,12 +42,8 @@ export class TreeSitterScopeHandler extends BaseScopeHandler { ): Iterable { const { document } = editor; - const { start, end } = this.getQueryRange( - document, - position, - direction, - hints, - ); + /** Narrow the range within which tree-sitter searches, for performance */ + const { start, end } = getQueryRange(document, position, direction, hints); yield* this.query .matches( @@ -62,60 +58,6 @@ export class TreeSitterScopeHandler extends BaseScopeHandler { .sort((a, b) => compareTargetScopes(direction, position, a, b)); } - /** - * Constructs a range to pass to {@link Query.matches} to find scopes. Note - * that {@link Query.matches} will only return scopes that have non-empty - * intersection with this range. Also note that the base - * {@link BaseScopeHandler.generateScopes} will filter out any extra scopes - * that we yield, so we don't need to be totally precise. - * - * @returns Range to pass to {@link Query.matches} - */ - private getQueryRange( - document: TextDocument, - position: Position, - direction: Direction, - { containment, distalPosition }: ScopeIteratorRequirements, - ) { - const offset = document.offsetAt(position); - const distalOffset = - distalPosition == null ? null : document.offsetAt(distalPosition); - - if (containment === "required") { - // If containment is required, we smear the position left and right by one - // character so that we have a non-empty intersection with any scope that - // touches position - return { - start: document.positionAt(offset - 1), - end: document.positionAt(offset + 1), - }; - } - - // If containment is disallowed, we can shift the position forward by a character to avoid - // matching scopes that touch position. Otherwise, we shift the position backward by a - // character to ensure we get scopes that touch position. - const proximalShift = containment === "disallowed" ? 1 : -1; - - // FIXME: Don't go all the way to end of document when there is no distalPosition? - // Seems wasteful to query all the way to end of document for something like "next funk" - // Might be better to start smaller and exponentially grow - return direction === "forward" - ? { - start: document.positionAt(offset + proximalShift), - end: - distalOffset == null - ? document.range.end - : document.positionAt(distalOffset + 1), - } - : { - start: - distalOffset == null - ? document.range.start - : document.positionAt(distalOffset - 1), - end: document.positionAt(offset - proximalShift), - }; - } - private matchToScope(editor: TextEditor, match: QueryMatch): TargetScope { const contentRange = getNodeRange( match.captures.find((capture) => capture.name === this.scopeType.type)! @@ -140,6 +82,60 @@ export class TreeSitterScopeHandler extends BaseScopeHandler { } } +/** + * Constructs a range to pass to {@link Query.matches} to find scopes. Note + * that {@link Query.matches} will only return scopes that have non-empty + * intersection with this range. Also note that the base + * {@link BaseScopeHandler.generateScopes} will filter out any extra scopes + * that we yield, so we don't need to be totally precise. + * + * @returns Range to pass to {@link Query.matches} + */ +function getQueryRange( + document: TextDocument, + position: Position, + direction: Direction, + { containment, distalPosition }: ScopeIteratorRequirements, +) { + const offset = document.offsetAt(position); + const distalOffset = + distalPosition == null ? null : document.offsetAt(distalPosition); + + if (containment === "required") { + // If containment is required, we smear the position left and right by one + // character so that we have a non-empty intersection with any scope that + // touches position + return { + start: document.positionAt(offset - 1), + end: document.positionAt(offset + 1), + }; + } + + // If containment is disallowed, we can shift the position forward by a character to avoid + // matching scopes that touch position. Otherwise, we shift the position backward by a + // character to ensure we get scopes that touch position. + const proximalShift = containment === "disallowed" ? 1 : -1; + + // FIXME: Don't go all the way to end of document when there is no distalPosition? + // Seems wasteful to query all the way to end of document for something like "next funk" + // Might be better to start smaller and exponentially grow + return direction === "forward" + ? { + start: document.positionAt(offset + proximalShift), + end: + distalOffset == null + ? document.range.end + : document.positionAt(distalOffset + 1), + } + : { + start: + distalOffset == null + ? document.range.start + : document.positionAt(distalOffset - 1), + end: document.positionAt(offset - proximalShift), + }; +} + function positionToPoint(start: Position): Point | undefined { return { row: start.line, column: start.character }; } From ecaf27f6cbf354ce1437cf9523e2c278b272c422 Mon Sep 17 00:00:00 2001 From: Pokey Rule <755842+pokey@users.noreply.github.com> Date: Mon, 17 Apr 2023 16:55:53 +0100 Subject: [PATCH 25/25] PR feedback --- .../cursorless-engine/src/cursorlessEngine.ts | 4 +- .../src/languages/LanguageDefinition.ts | 69 +++++++++++++++++++ .../src/languages/LanguageDefinitionImpl.ts | 38 ---------- .../src/languages/LanguageDefinitions.ts | 60 +++++++++++++--- .../src/languages/LanguageDefinitionsImpl.ts | 37 ---------- .../cursorless-engine/src/languages/index.ts | 2 +- .../scopeHandlers/ScopeHandlerFactoryImpl.ts | 2 +- 7 files changed, 124 insertions(+), 88 deletions(-) create mode 100644 packages/cursorless-engine/src/languages/LanguageDefinition.ts delete mode 100644 packages/cursorless-engine/src/languages/LanguageDefinitionImpl.ts delete mode 100644 packages/cursorless-engine/src/languages/LanguageDefinitionsImpl.ts diff --git a/packages/cursorless-engine/src/cursorlessEngine.ts b/packages/cursorless-engine/src/cursorlessEngine.ts index 064405b29c..0311317457 100644 --- a/packages/cursorless-engine/src/cursorlessEngine.ts +++ b/packages/cursorless-engine/src/cursorlessEngine.ts @@ -9,7 +9,7 @@ import { MarkStageFactoryImpl } from "./processTargets/MarkStageFactoryImpl"; import { ModifierStageFactoryImpl } from "./processTargets/ModifierStageFactoryImpl"; import { ScopeHandlerFactoryImpl } from "./processTargets/modifiers/scopeHandlers"; import { injectIde } from "./singletons/ide.singleton"; -import { LanguageDefinitionsImpl } from "./languages/LanguageDefinitionsImpl"; +import { LanguageDefinitions } from "./languages/LanguageDefinitions"; export function createCursorlessEngine( treeSitter: TreeSitter, @@ -37,7 +37,7 @@ export function createCursorlessEngine( const testCaseRecorder = new TestCaseRecorder(hatTokenMap); - const languageDefinitions = new LanguageDefinitionsImpl(treeSitter); + const languageDefinitions = new LanguageDefinitions(treeSitter); const scopeHandlerFactory = new ScopeHandlerFactoryImpl(languageDefinitions); const markStageFactory = new MarkStageFactoryImpl(); const modifierStageFactory = new ModifierStageFactoryImpl( diff --git a/packages/cursorless-engine/src/languages/LanguageDefinition.ts b/packages/cursorless-engine/src/languages/LanguageDefinition.ts new file mode 100644 index 0000000000..974d3e1710 --- /dev/null +++ b/packages/cursorless-engine/src/languages/LanguageDefinition.ts @@ -0,0 +1,69 @@ +import { ScopeType, SimpleScopeType } from "@cursorless/common"; +import { Query } from "web-tree-sitter"; +import { ide } from "../singletons/ide.singleton"; +import { join } from "path"; +import { TreeSitterScopeHandler } from "../processTargets/modifiers/scopeHandlers"; +import { TreeSitter } from "../typings/TreeSitter"; +import { existsSync, readFileSync } from "fs"; +import { LanguageId } from "./constants"; + +/** + * Represents a language definition for a single language, including the + * tree-sitter query used to extract scopes for the given language + */ +export class LanguageDefinition { + private constructor( + private treeSitter: TreeSitter, + /** + * The tree-sitter query used to extract scopes for the given language. + * Note that this query contains patterns for all scope types that the + * language supports using new-style tree-sitter queries + */ + private query: Query, + ) {} + + /** + * Construct a language definition for the given language id, if the language + * has a new-style query definition, or return undefined if the language doesn't + * + * @param treeSitter The tree-sitter instance to use for parsing + * @param languageId The language id for which to create a language definition + * @returns A language definition for the given language id, or undefined if the given language + * id doesn't have a new-style query definition + */ + static create( + treeSitter: TreeSitter, + languageId: LanguageId, + ): LanguageDefinition | undefined { + const queryPath = join(ide().assetsRoot, "queries", `${languageId}.scm`); + + if (!existsSync(queryPath)) { + return undefined; + } + + const rawLanguageQueryString = readFileSync(queryPath, "utf8"); + + return new LanguageDefinition( + treeSitter, + treeSitter.getLanguage(languageId)!.query(rawLanguageQueryString), + ); + } + + /** + * @param scopeType The scope type for which to get a scope handler + * @returns A scope handler for the given scope type and language id, or + * undefined if the given scope type / language id combination is still using + * legacy pathways + */ + getScopeHandler(scopeType: ScopeType) { + if (!this.query.captureNames.includes(scopeType.type)) { + return undefined; + } + + return new TreeSitterScopeHandler( + this.treeSitter, + this.query, + scopeType as SimpleScopeType, + ); + } +} diff --git a/packages/cursorless-engine/src/languages/LanguageDefinitionImpl.ts b/packages/cursorless-engine/src/languages/LanguageDefinitionImpl.ts deleted file mode 100644 index 35e86245cd..0000000000 --- a/packages/cursorless-engine/src/languages/LanguageDefinitionImpl.ts +++ /dev/null @@ -1,38 +0,0 @@ -import { ScopeType, SimpleScopeType } from "@cursorless/common"; -import { Query } from "web-tree-sitter"; -import { LanguageDefinition } from "./LanguageDefinitions"; -import { LanguageId } from "./constants"; -import { ide } from "../singletons/ide.singleton"; -import { join } from "path"; -import { TreeSitterScopeHandler } from "../processTargets/modifiers/scopeHandlers"; -import { TreeSitter } from "../typings/TreeSitter"; -import { readFileSync } from "fs"; - -export class LanguageDefinitionImpl implements LanguageDefinition { - private query!: Query; - - constructor(private treeSitter: TreeSitter, private languageId: LanguageId) {} - - init() { - const rawLanguageQueryString = readFileSync( - join(ide().assetsRoot, "queries", `${this.languageId}.scm`), - "utf8", - ); - - this.query = this.treeSitter - .getLanguage(this.languageId)! - .query(rawLanguageQueryString); - } - - maybeGetLanguageScopeHandler(scopeType: ScopeType) { - if (!this.query.captureNames.includes(scopeType.type)) { - return undefined; - } - - return new TreeSitterScopeHandler( - this.treeSitter, - this.query, - scopeType as SimpleScopeType, - ); - } -} diff --git a/packages/cursorless-engine/src/languages/LanguageDefinitions.ts b/packages/cursorless-engine/src/languages/LanguageDefinitions.ts index 3910332da7..6a3bc22735 100644 --- a/packages/cursorless-engine/src/languages/LanguageDefinitions.ts +++ b/packages/cursorless-engine/src/languages/LanguageDefinitions.ts @@ -1,12 +1,54 @@ -import { ScopeType } from "@cursorless/common"; -import { ScopeHandler } from "../processTargets/modifiers/scopeHandlers/scopeHandler.types"; +import { TreeSitter } from ".."; +import { LanguageDefinition } from "./LanguageDefinition"; +import { LanguageId } from "./constants"; -export interface LanguageDefinitions { - get(languageId: string): LanguageDefinition | undefined; -} +/** + * Sentinel value to indicate that a language doesn't have + * a new-style query definition file + */ +const LANGUAGE_UNDEFINED = Symbol("LANGUAGE_UNDEFINED"); + +/** + * Keeps a map from language ids to {@link LanguageDefinition} instances, + * constructing them as necessary + */ +export class LanguageDefinitions { + /** + * Maps from language id to {@link LanguageDefinition} or + * {@link LANGUAGE_UNDEFINED} if language doesn't have new-style definitions. + * We use a sentinel value instead of undefined so that we can distinguish + * between a situation where we haven't yet checked whether a language has a + * new-style query definition and a situation where we've checked and found + * that it doesn't. The former case is represented by `undefined` (due to the + * semantics of {@link Map.get}), while the latter is represented by the + * sentinel value. + */ + private languageDefinitions: Map< + string, + LanguageDefinition | typeof LANGUAGE_UNDEFINED + > = new Map(); + + constructor(private treeSitter: TreeSitter) {} + + /** + * Get a language definition for the given language id, if the language + * has a new-style query definition, or return undefined if the language doesn't + * + * @param languageId The language id for which to get a language definition + * @returns A language definition for the given language id, or undefined if + * the given language id doesn't have a new-style query definition + */ + get(languageId: string): LanguageDefinition | undefined { + let definition = this.languageDefinitions.get(languageId); + + if (definition == null) { + definition = + LanguageDefinition.create(this.treeSitter, languageId as LanguageId) ?? + LANGUAGE_UNDEFINED; + + this.languageDefinitions.set(languageId, definition); + } -export interface LanguageDefinition { - maybeGetLanguageScopeHandler: ( - scopeType: ScopeType, - ) => ScopeHandler | undefined; + return definition === LANGUAGE_UNDEFINED ? undefined : definition; + } } diff --git a/packages/cursorless-engine/src/languages/LanguageDefinitionsImpl.ts b/packages/cursorless-engine/src/languages/LanguageDefinitionsImpl.ts deleted file mode 100644 index 72c41156b4..0000000000 --- a/packages/cursorless-engine/src/languages/LanguageDefinitionsImpl.ts +++ /dev/null @@ -1,37 +0,0 @@ -import { TreeSitter } from ".."; -import { LanguageDefinition, LanguageDefinitions } from "./LanguageDefinitions"; -import { LanguageId } from "./constants"; -import { LanguageDefinitionImpl } from "./LanguageDefinitionImpl"; - -export class LanguageDefinitionsImpl implements LanguageDefinitions { - private languageDefinitions: Map = - new Map(); - - constructor(private treeSitter: TreeSitter) {} - - get(languageId: LanguageId): LanguageDefinition | undefined { - if (!languages.includes(languageId)) { - return undefined; - } - - let definition = this.languageDefinitions.get(languageId); - - if (definition == null) { - definition = new LanguageDefinitionImpl(this.treeSitter, languageId); - definition.init(); - this.languageDefinitions.set(languageId, definition); - } - - return definition; - } -} - -/** - * A list of languages which have query definitions. Note that it's possible - * for a language to have some of its scope types defined via queries and the - * rest via legacy `nodeMatcher` definitions. The - * {@link LanguageDefinitionImpl} will return `undefined` for any scope types - * which are not defined via queries, which will cause the modifier stage to - * fall back to the legacy `nodeMatcher` definitions. - */ -const languages: LanguageId[] = ["ruby"]; diff --git a/packages/cursorless-engine/src/languages/index.ts b/packages/cursorless-engine/src/languages/index.ts index eb3b766e4c..f5f79c357c 100644 --- a/packages/cursorless-engine/src/languages/index.ts +++ b/packages/cursorless-engine/src/languages/index.ts @@ -3,5 +3,5 @@ import { SupportedLanguageId, supportedLanguageIds } from "./constants"; export function isLanguageSupported( languageId: string, ): languageId is SupportedLanguageId { - return languageId in supportedLanguageIds; + return supportedLanguageIds.includes(languageId as SupportedLanguageId); } diff --git a/packages/cursorless-engine/src/processTargets/modifiers/scopeHandlers/ScopeHandlerFactoryImpl.ts b/packages/cursorless-engine/src/processTargets/modifiers/scopeHandlers/ScopeHandlerFactoryImpl.ts index cb4e5dfc91..e11acfbb63 100644 --- a/packages/cursorless-engine/src/processTargets/modifiers/scopeHandlers/ScopeHandlerFactoryImpl.ts +++ b/packages/cursorless-engine/src/processTargets/modifiers/scopeHandlers/ScopeHandlerFactoryImpl.ts @@ -56,7 +56,7 @@ export class ScopeHandlerFactoryImpl implements ScopeHandlerFactory { default: return this.languageDefinitions .get(languageId) - ?.maybeGetLanguageScopeHandler(scopeType); + ?.getScopeHandler(scopeType); } } }