diff --git a/packages/cursorless-engine/src/cursorlessEngine.ts b/packages/cursorless-engine/src/cursorlessEngine.ts index 656a5951e6..0311317457 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 { LanguageDefinitions } from "./languages/LanguageDefinitions"; export function createCursorlessEngine( treeSitter: TreeSitter, @@ -36,7 +37,8 @@ export function createCursorlessEngine( const testCaseRecorder = new TestCaseRecorder(hatTokenMap); - const scopeHandlerFactory = new ScopeHandlerFactoryImpl(); + const languageDefinitions = new LanguageDefinitions(treeSitter); + const scopeHandlerFactory = new ScopeHandlerFactoryImpl(languageDefinitions); const markStageFactory = new MarkStageFactoryImpl(); const modifierStageFactory = new ModifierStageFactoryImpl( scopeHandlerFactory, 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/LanguageDefinitions.ts b/packages/cursorless-engine/src/languages/LanguageDefinitions.ts new file mode 100644 index 0000000000..6a3bc22735 --- /dev/null +++ b/packages/cursorless-engine/src/languages/LanguageDefinitions.ts @@ -0,0 +1,54 @@ +import { TreeSitter } from ".."; +import { LanguageDefinition } from "./LanguageDefinition"; +import { LanguageId } from "./constants"; + +/** + * 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); + } + + return definition === LANGUAGE_UNDEFINED ? undefined : definition; + } +} 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/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..e11acfbb63 100644 --- a/packages/cursorless-engine/src/processTargets/modifiers/scopeHandlers/ScopeHandlerFactoryImpl.ts +++ b/packages/cursorless-engine/src/processTargets/modifiers/scopeHandlers/ScopeHandlerFactoryImpl.ts @@ -1,16 +1,17 @@ +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 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 @@ -30,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); } @@ -53,7 +54,9 @@ export class ScopeHandlerFactoryImpl implements ScopeHandlerFactory { case "paragraph": return new ParagraphScopeHandler(scopeType, languageId); default: - return undefined; + return this.languageDefinitions + .get(languageId) + ?.getScopeHandler(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..44db8e526d --- /dev/null +++ b/packages/cursorless-engine/src/processTargets/modifiers/scopeHandlers/TreeSitterScopeHandler.ts @@ -0,0 +1,141 @@ +import { + Direction, + Position, + ScopeType, + SimpleScopeType, + TextDocument, + 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 { 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; + + /** Narrow the range within which tree-sitter searches, for performance */ + const { start, end } = getQueryRange(document, position, direction, hints); + + yield* this.query + .matches( + this.treeSitter.getTree(document).rootNode, + positionToPoint(start), + positionToPoint(end), + ) + .filter(({ captures }) => + captures.some((capture) => capture.name === this.scopeType.type), + ) + .map((match) => this.matchToScope(editor, match)) + .sort((a, b) => compareTargetScopes(direction, position, a, b)); + } + + 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 + }), + }; + } +} + +/** + * 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 }; +} diff --git a/packages/cursorless-engine/src/processTargets/modifiers/scopeHandlers/index.ts b/packages/cursorless-engine/src/processTargets/modifiers/scopeHandlers/index.ts index b3777c6318..9c13ffcec5 100644 --- a/packages/cursorless-engine/src/processTargets/modifiers/scopeHandlers/index.ts +++ b/packages/cursorless-engine/src/processTargets/modifiers/scopeHandlers/index.ts @@ -12,6 +12,7 @@ export * from "./TokenScopeHandler"; export { default as TokenScopeHandler } from "./TokenScopeHandler"; export * from "./DocumentScopeHandler"; export { default as DocumentScopeHandler } from "./DocumentScopeHandler"; +export * 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..3169d1a1a3 100644 --- a/packages/cursorless-engine/src/typings/TreeSitter.ts +++ b/packages/cursorless-engine/src/typings/TreeSitter.ts @@ -1,12 +1,22 @@ import { Range, TextDocument } from "@cursorless/common"; -import { SyntaxNode } 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. + */ + 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/fixtures/recorded/containingScope/clearCall.yml b/packages/cursorless-vscode-e2e/src/suite/fixtures/recorded/containingScope/clearCall.yml new file mode 100644 index 0000000000..7901327715 --- /dev/null +++ b/packages/cursorless-vscode-e2e/src/suite/fixtures/recorded/containingScope/clearCall.yml @@ -0,0 +1,24 @@ +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: 2} + active: {line: 0, 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/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/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} 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/clearCall10.yml b/packages/cursorless-vscode-e2e/src/suite/fixtures/recorded/queryBasedMatchers/clearCall10.yml new file mode 100644 index 0000000000..2f07ed3e5d --- /dev/null +++ b/packages/cursorless-vscode-e2e/src/suite/fixtures/recorded/queryBasedMatchers/clearCall10.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/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/clearCall12.yml b/packages/cursorless-vscode-e2e/src/suite/fixtures/recorded/queryBasedMatchers/clearCall12.yml new file mode 100644 index 0000000000..a87d14971b --- /dev/null +++ b/packages/cursorless-vscode-e2e/src/suite/fixtures/recorded/queryBasedMatchers/clearCall12.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: 7} + marks: {} +thrownError: {name: NoContainingScopeError} diff --git a/packages/cursorless-vscode-e2e/src/suite/fixtures/recorded/queryBasedMatchers/clearCall14.yml b/packages/cursorless-vscode-e2e/src/suite/fixtures/recorded/queryBasedMatchers/clearCall14.yml new file mode 100644 index 0000000000..77fdd11330 --- /dev/null +++ b/packages/cursorless-vscode-e2e/src/suite/fixtures/recorded/queryBasedMatchers/clearCall14.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() + 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} diff --git a/packages/cursorless-vscode-e2e/src/suite/fixtures/recorded/queryBasedMatchers/clearCall15.yml b/packages/cursorless-vscode-e2e/src/suite/fixtures/recorded/queryBasedMatchers/clearCall15.yml new file mode 100644 index 0000000000..a6719e38fa --- /dev/null +++ b/packages/cursorless-vscode-e2e/src/suite/fixtures/recorded/queryBasedMatchers/clearCall15.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: 2} + active: {line: 0, character: 10} + 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/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} 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/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/clearCall7.yml b/packages/cursorless-vscode-e2e/src/suite/fixtures/recorded/queryBasedMatchers/clearCall7.yml new file mode 100644 index 0000000000..d713ed63fc --- /dev/null +++ b/packages/cursorless-vscode-e2e/src/suite/fixtures/recorded/queryBasedMatchers/clearCall7.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: 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/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} 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/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} diff --git a/packages/cursorless-vscode-e2e/src/suite/fixtures/recorded/relativeScopes/clearNextCall.yml b/packages/cursorless-vscode-e2e/src/suite/fixtures/recorded/relativeScopes/clearNextCall.yml new file mode 100644 index 0000000000..e1c75deb61 --- /dev/null +++ b/packages/cursorless-vscode-e2e/src/suite/fixtures/recorded/relativeScopes/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} diff --git a/packages/cursorless-vscode-e2e/src/suite/fixtures/recorded/relativeScopes/clearNextCall2.yml b/packages/cursorless-vscode-e2e/src/suite/fixtures/recorded/relativeScopes/clearNextCall2.yml new file mode 100644 index 0000000000..2671f2e008 --- /dev/null +++ b/packages/cursorless-vscode-e2e/src/suite/fixtures/recorded/relativeScopes/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/relativeScopes/clearNextCall3.yml b/packages/cursorless-vscode-e2e/src/suite/fixtures/recorded/relativeScopes/clearNextCall3.yml new file mode 100644 index 0000000000..73889ab7d3 --- /dev/null +++ b/packages/cursorless-vscode-e2e/src/suite/fixtures/recorded/relativeScopes/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/relativeScopes/clearNextCall4.yml b/packages/cursorless-vscode-e2e/src/suite/fixtures/recorded/relativeScopes/clearNextCall4.yml new file mode 100644 index 0000000000..971a163cdc --- /dev/null +++ b/packages/cursorless-vscode-e2e/src/suite/fixtures/recorded/relativeScopes/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} diff --git a/packages/cursorless-vscode-e2e/src/suite/fixtures/recorded/relativeScopes/clearNextCall5.yml b/packages/cursorless-vscode-e2e/src/suite/fixtures/recorded/relativeScopes/clearNextCall5.yml new file mode 100644 index 0000000000..2543027407 --- /dev/null +++ b/packages/cursorless-vscode-e2e/src/suite/fixtures/recorded/relativeScopes/clearNextCall5.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: 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/relativeScopes/clearNextCall6.yml b/packages/cursorless-vscode-e2e/src/suite/fixtures/recorded/relativeScopes/clearNextCall6.yml new file mode 100644 index 0000000000..c37f7d44ea --- /dev/null +++ b/packages/cursorless-vscode-e2e/src/suite/fixtures/recorded/relativeScopes/clearNextCall6.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: 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/relativeScopes/clearNextCall7.yml b/packages/cursorless-vscode-e2e/src/suite/fixtures/recorded/relativeScopes/clearNextCall7.yml new file mode 100644 index 0000000000..4b8f4856c5 --- /dev/null +++ b/packages/cursorless-vscode-e2e/src/suite/fixtures/recorded/relativeScopes/clearNextCall7.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: 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/relativeScopes/clearNextCall8.yml b/packages/cursorless-vscode-e2e/src/suite/fixtures/recorded/relativeScopes/clearNextCall8.yml new file mode 100644 index 0000000000..e172f94469 --- /dev/null +++ b/packages/cursorless-vscode-e2e/src/suite/fixtures/recorded/relativeScopes/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/relativeScopes/clearSecondNextCall.yml b/packages/cursorless-vscode-e2e/src/suite/fixtures/recorded/relativeScopes/clearSecondNextCall.yml new file mode 100644 index 0000000000..3298894c7e --- /dev/null +++ b/packages/cursorless-vscode-e2e/src/suite/fixtures/recorded/relativeScopes/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} 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/relativeScopes/clearTwoCalls2.yml b/packages/cursorless-vscode-e2e/src/suite/fixtures/recorded/relativeScopes/clearTwoCalls2.yml new file mode 100644 index 0000000000..04920bdda8 --- /dev/null +++ b/packages/cursorless-vscode-e2e/src/suite/fixtures/recorded/relativeScopes/clearTwoCalls2.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: 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/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} diff --git a/packages/cursorless-vscode/src/extension.ts b/packages/cursorless-vscode/src/extension.ts index e6820499c7..352bb7a88e 100644 --- a/packages/cursorless-vscode/src/extension.ts +++ b/packages/cursorless-vscode/src/extension.ts @@ -126,6 +126,12 @@ function createTreeSitter(parseTreeApi: ParseTreeApi): TreeSitter { new vscode.Location(document.uri, toVscodeRange(range)), ); }, + + 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 c455b87f6d..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 } from "web-tree-sitter"; +import type { Language, SyntaxNode, Tree } from "web-tree-sitter"; export interface TestHelpers { ide: NormalizedIDE; @@ -58,7 +58,9 @@ export interface CursorlessApi { 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/queries/ruby.scm b/queries/ruby.scm new file mode 100644 index 0000000000..19773d755b --- /dev/null +++ b/queries/ruby.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/typings/treeSitter.d.ts b/typings/treeSitter.d.ts index be59fb1d65..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 { @@ -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; } }