diff --git a/packages/common/src/scopeSupportFacets/scopeSupportFacets.types.ts b/packages/common/src/scopeSupportFacets/scopeSupportFacets.types.ts index c189dc2ca3..eaed8cfa75 100644 --- a/packages/common/src/scopeSupportFacets/scopeSupportFacets.types.ts +++ b/packages/common/src/scopeSupportFacets/scopeSupportFacets.types.ts @@ -233,7 +233,6 @@ export interface ScopeSupportFacetInfo { export enum ScopeSupportFacetLevel { supported, - supportedLegacy, unsupported, notApplicable, } diff --git a/packages/common/src/types/ScopeProvider.ts b/packages/common/src/types/ScopeProvider.ts index 56bff4cdc3..2e987b84d4 100644 --- a/packages/common/src/types/ScopeProvider.ts +++ b/packages/common/src/types/ScopeProvider.ts @@ -218,6 +218,5 @@ export interface IterationScopeRanges { export enum ScopeSupport { supportedAndPresentInEditor, supportedButNotPresentInEditor, - supportedLegacy, unsupported, } diff --git a/packages/cursorless-engine/src/api/CursorlessEngineApi.ts b/packages/cursorless-engine/src/api/CursorlessEngineApi.ts index 87d9ae65ac..928d1e8202 100644 --- a/packages/cursorless-engine/src/api/CursorlessEngineApi.ts +++ b/packages/cursorless-engine/src/api/CursorlessEngineApi.ts @@ -21,7 +21,6 @@ export interface CursorlessEngine { storedTargets: StoredTargetMap; hatTokenMap: HatTokenMap; injectIde: (ide: IDE | undefined) => void; - runIntegrationTests: () => Promise; addCommandRunnerDecorator: ( commandRunnerDecorator: CommandRunnerDecorator, ) => void; diff --git a/packages/cursorless-engine/src/cursorlessEngine.ts b/packages/cursorless-engine/src/cursorlessEngine.ts index fb9d7dd867..2ef8aca2f9 100644 --- a/packages/cursorless-engine/src/cursorlessEngine.ts +++ b/packages/cursorless-engine/src/cursorlessEngine.ts @@ -35,7 +35,6 @@ import { import { ModifierStageFactoryImpl } from "./processTargets/ModifierStageFactoryImpl"; import { ScopeHandlerFactoryImpl } from "./processTargets/modifiers/scopeHandlers"; import { runCommand } from "./runCommand"; -import { runIntegrationTests } from "./runIntegrationTests"; import { ScopeInfoProvider } from "./scopeProviders/ScopeInfoProvider"; import { ScopeRangeProvider } from "./scopeProviders/ScopeRangeProvider"; import { ScopeRangeWatcher } from "./scopeProviders/ScopeRangeWatcher"; @@ -142,8 +141,6 @@ export async function createCursorlessEngine({ storedTargets, hatTokenMap, injectIde, - runIntegrationTests: () => - runIntegrationTests(treeSitter, languageDefinitions), addCommandRunnerDecorator: (decorator: CommandRunnerDecorator) => { commandRunnerDecorators.push(decorator); }, diff --git a/packages/cursorless-engine/src/languages/LegacyLanguageId.ts b/packages/cursorless-engine/src/languages/LegacyLanguageId.ts deleted file mode 100644 index 18188217c5..0000000000 --- a/packages/cursorless-engine/src/languages/LegacyLanguageId.ts +++ /dev/null @@ -1,7 +0,0 @@ -/** - * The language IDs that we have full tree-sitter support for using our legacy - * modifiers. - */ -export const legacyLanguageIds = ["dummy"] as const; - -export type LegacyLanguageId = (typeof legacyLanguageIds)[number]; diff --git a/packages/cursorless-engine/src/languages/TreeSitterQuery/TreeSitterQuery.ts b/packages/cursorless-engine/src/languages/TreeSitterQuery/TreeSitterQuery.ts index 60d3763a02..579d5eac54 100644 --- a/packages/cursorless-engine/src/languages/TreeSitterQuery/TreeSitterQuery.ts +++ b/packages/cursorless-engine/src/languages/TreeSitterQuery/TreeSitterQuery.ts @@ -2,7 +2,7 @@ import type { Position, TextDocument } from "@cursorless/common"; import { type TreeSitter } from "@cursorless/common"; import type * as treeSitter from "web-tree-sitter"; import { ide } from "../../singletons/ide.singleton"; -import { getNodeRange } from "../../util/nodeSelectors"; +import { getNodeRange } from "./getNodeRange"; import type { MutableQueryCapture, MutableQueryMatch, diff --git a/packages/cursorless-engine/src/languages/TreeSitterQuery/getNodeRange.ts b/packages/cursorless-engine/src/languages/TreeSitterQuery/getNodeRange.ts new file mode 100644 index 0000000000..6aa87b6a6e --- /dev/null +++ b/packages/cursorless-engine/src/languages/TreeSitterQuery/getNodeRange.ts @@ -0,0 +1,11 @@ +import { Range } from "@cursorless/common"; +import type { Node } from "web-tree-sitter"; + +export function getNodeRange(node: Node) { + return new Range( + node.startPosition.row, + node.startPosition.column, + node.endPosition.row, + node.endPosition.column, + ); +} diff --git a/packages/cursorless-engine/src/languages/TreeSitterQuery/makeRangeFromPositions.ts b/packages/cursorless-engine/src/languages/TreeSitterQuery/makeRangeFromPositions.ts new file mode 100644 index 0000000000..461cdd0e62 --- /dev/null +++ b/packages/cursorless-engine/src/languages/TreeSitterQuery/makeRangeFromPositions.ts @@ -0,0 +1,14 @@ +import { Range } from "@cursorless/common"; +import type { Point } from "web-tree-sitter"; + +export function makeRangeFromPositions( + startPosition: Point, + endPosition: Point, +) { + return new Range( + startPosition.row, + startPosition.column, + endPosition.row, + endPosition.column, + ); +} diff --git a/packages/cursorless-engine/src/languages/TreeSitterQuery/queryPredicateOperators.ts b/packages/cursorless-engine/src/languages/TreeSitterQuery/queryPredicateOperators.ts index 2565679bb9..eeac765dae 100644 --- a/packages/cursorless-engine/src/languages/TreeSitterQuery/queryPredicateOperators.ts +++ b/packages/cursorless-engine/src/languages/TreeSitterQuery/queryPredicateOperators.ts @@ -1,7 +1,7 @@ import type { Position } from "@cursorless/common"; import { Range, adjustPosition } from "@cursorless/common"; import { z } from "zod"; -import { makeRangeFromPositions } from "../../util/nodeSelectors"; +import { makeRangeFromPositions } from "./makeRangeFromPositions"; import type { MutableQueryCapture } from "./QueryCapture"; import { QueryPredicateOperator } from "./QueryPredicateOperator"; import { isEven } from "./isEven"; diff --git a/packages/cursorless-engine/src/languages/elseIfExtractor.ts b/packages/cursorless-engine/src/languages/elseIfExtractor.ts deleted file mode 100644 index cd16e383d9..0000000000 --- a/packages/cursorless-engine/src/languages/elseIfExtractor.ts +++ /dev/null @@ -1,95 +0,0 @@ -import type { TextEditor } from "@cursorless/common"; -import { Selection } from "@cursorless/common"; -import type { Node } from "web-tree-sitter"; -import type { - SelectionExtractor, - SelectionWithContext, -} from "../typings/Types"; -import { - childRangeSelector, - positionFromPoint, - simpleSelectionExtractor, -} from "../util/nodeSelectors"; - -/** - * Returns an extractor that can be used to extract `else if` branches in languages - * with C-like structure, where the `if` portion of an `else if` structure is - * structurally just an arbitrary statement that happens to be an `if` - * statement. - * @returns An extractor that will exctract `else if` branches - */ -export function elseIfExtractor(): SelectionExtractor { - /** - * This extractor pulls out `if (foo) {}` from `if (foo) {} else {}`, ie it - * excludes any child `else` statement if it exists. It will be used as the - * content range, but the removal range will want to include a leading or - * trailing `else` keyword if one exists. - */ - const contentRangeExtractor = childRangeSelector(["else_clause"], [], { - includeUnnamedChildren: true, - }); - - return function (editor: TextEditor, node: Node): SelectionWithContext { - const contentRange = contentRangeExtractor(editor, node); - - const parent = node.parent; - if (parent?.type !== "else_clause") { - // We have no leading `else` clause; ie we are a standalone `if` - // statement. We may still have our own `else` child, but we are not - // ourselves a branch of a bigger `if` statement. - const alternative = node.childForFieldName("alternative"); - - if (alternative == null) { - // If we have no nested else clause, and are not part of an else clause - // ourself, then we don't need to remove any leading / trailing `else` - // keyword - return contentRange; - } - - // Otherwise, we have no leading `else`, but we do have our own nested - // `else` clause, so we want to remove its `else` keyword - const { selection } = contentRange; - return { - selection, - context: { - removalRange: new Selection( - selection.start, - positionFromPoint(alternative.namedChild(0)!.startPosition), - ), - }, - }; - } - - // If we get here, we are part of a bigger `if` statement; extend our - // content range past our leading `else` keyword. - const { selection } = contentRange; - return { - selection: new Selection( - positionFromPoint(parent.child(0)!.startPosition), - selection.end, - ), - context: {}, - }; - }; -} - -/** - * Returns an extractor that can be used to extract `else` branches in languages - * with C-like structure, where the `if` portion of an `else if` structure is - * structurally just an arbitrary statement that happens to be an `if` - * statement. - * @param ifNodeType The node type for `if` statements - * @returns An extractor that will exctract `else` branches - */ -export function elseExtractor(ifNodeType: string): SelectionExtractor { - const nestedElseIfExtractor = elseIfExtractor(); - - return function (editor: TextEditor, node: Node): SelectionWithContext { - // If we are an `else if` statement, then we just run `elseIfExtractor` on - // our nested `if` node. Otherwise we are a simple `else` branch and don't - // need to do anything fancy. - return node.namedChild(0)!.type === ifNodeType - ? nestedElseIfExtractor(editor, node.namedChild(0)!) - : simpleSelectionExtractor(editor, node); - }; -} diff --git a/packages/cursorless-engine/src/languages/getNodeMatcher.ts b/packages/cursorless-engine/src/languages/getNodeMatcher.ts deleted file mode 100644 index 897d2c386b..0000000000 --- a/packages/cursorless-engine/src/languages/getNodeMatcher.ts +++ /dev/null @@ -1,83 +0,0 @@ -import type { SimpleScopeTypeType } from "@cursorless/common"; -import { UnsupportedLanguageError } from "@cursorless/common"; -import type { Node } from "web-tree-sitter"; -import type { - NodeMatcher, - NodeMatcherValue, - SelectionWithEditor, -} from "../typings/Types"; -import { notSupported } from "../util/nodeMatchers"; -import { selectionWithEditorFromRange } from "../util/selectionUtils"; -import type { LegacyLanguageId } from "./LegacyLanguageId"; - -export function getNodeMatcher( - languageId: string, - scopeTypeType: SimpleScopeTypeType, - includeSiblings: boolean, -): NodeMatcher { - const matchers = languageMatchers[languageId as LegacyLanguageId]; - - if (matchers == null) { - throw new UnsupportedLanguageError(languageId); - } - - const matcher = matchers[scopeTypeType]; - - if (matcher == null) { - return notSupported(scopeTypeType); - } - - if (includeSiblings) { - return matcherIncludeSiblings(matcher); - } - - return matcher; -} - -export const languageMatchers: Record< - LegacyLanguageId, - Partial> -> = { - dummy: {}, -}; - -function matcherIncludeSiblings(matcher: NodeMatcher): NodeMatcher { - return ( - selection: SelectionWithEditor, - node: Node, - ): NodeMatcherValue[] | null => { - let matches = matcher(selection, node); - if (matches == null) { - return null; - } - matches = matches.flatMap((match) => - iterateNearestIterableAncestor( - match.node, - selectionWithEditorFromRange(selection, match.selection.selection), - matcher, - ), - ); - if (matches.length > 0) { - return matches; - } - return null; - }; -} - -function iterateNearestIterableAncestor( - node: Node, - selection: SelectionWithEditor, - nodeMatcher: NodeMatcher, -) { - let parent: Node | null = node.parent; - while (parent != null) { - const matches = parent.namedChildren - .flatMap((sibling) => nodeMatcher(selection, sibling)) - .filter((match) => match != null) as NodeMatcherValue[]; - if (matches.length > 0) { - return matches; - } - parent = parent.parent; - } - return []; -} diff --git a/packages/cursorless-engine/src/processTargets/ModifierStageFactory.ts b/packages/cursorless-engine/src/processTargets/ModifierStageFactory.ts index d02f0e8390..07184af532 100644 --- a/packages/cursorless-engine/src/processTargets/ModifierStageFactory.ts +++ b/packages/cursorless-engine/src/processTargets/ModifierStageFactory.ts @@ -1,13 +1,6 @@ -import type { - ContainingScopeModifier, - EveryScopeModifier, - Modifier, -} from "@cursorless/common"; +import type { Modifier } from "@cursorless/common"; import type { ModifierStage } from "./PipelineStages.types"; export interface ModifierStageFactory { create(modifier: Modifier): ModifierStage; - getLegacyScopeStage( - modifier: ContainingScopeModifier | EveryScopeModifier, - ): ModifierStage; } diff --git a/packages/cursorless-engine/src/processTargets/ModifierStageFactoryImpl.ts b/packages/cursorless-engine/src/processTargets/ModifierStageFactoryImpl.ts index 15a1f14c47..92f011e5a8 100644 --- a/packages/cursorless-engine/src/processTargets/ModifierStageFactoryImpl.ts +++ b/packages/cursorless-engine/src/processTargets/ModifierStageFactoryImpl.ts @@ -1,8 +1,4 @@ -import type { - ContainingScopeModifier, - EveryScopeModifier, - Modifier, -} from "@cursorless/common"; +import type { Modifier } from "@cursorless/common"; import type { StoredTargetMap } from "../core/StoredTargets"; import type { LanguageDefinitions } from "../languages/LanguageDefinitions"; import type { ModifierStageFactory } from "./ModifierStageFactory"; @@ -30,11 +26,6 @@ import { RawSelectionStage } from "./modifiers/RawSelectionStage"; import { RelativeScopeStage } from "./modifiers/RelativeScopeStage"; import { VisibleStage } from "./modifiers/VisibleStage"; import type { ScopeHandlerFactory } from "./modifiers/scopeHandlers/ScopeHandlerFactory"; -import type { - SimpleContainingScopeModifier, - SimpleEveryScopeModifier, -} from "./modifiers/scopeTypeStages/LegacyContainingSyntaxScopeStage"; -import { LegacyContainingSyntaxScopeStage } from "./modifiers/scopeTypeStages/LegacyContainingSyntaxScopeStage"; export class ModifierStageFactoryImpl implements ModifierStageFactory { constructor( @@ -116,30 +107,4 @@ export class ModifierStageFactoryImpl implements ModifierStageFactory { } } } - - /** - * Any scope type that has not been fully migrated to the new - * {@link ScopeHandler} setup should have a branch in this `switch` statement. - * Once the scope type is fully migrated, remove the branch and the legacy - * modifier stage. - * - * Note that it is possible for a scope type to be partially migrated. For - * example, we could support modern scope handlers for a certain scope type in - * Ruby, but not yet in Python. - * - * @param modifier The modifier for which to get the modifier stage - * @returns A scope stage implementing the modifier for the given scope type - */ - getLegacyScopeStage( - modifier: ContainingScopeModifier | EveryScopeModifier, - ): ModifierStage { - switch (modifier.scopeType.type) { - default: - // Default to containing syntax scope using tree sitter - return new LegacyContainingSyntaxScopeStage( - this.languageDefinitions, - modifier as SimpleContainingScopeModifier | SimpleEveryScopeModifier, - ); - } - } } diff --git a/packages/cursorless-engine/src/processTargets/modifiers/ContainingScopeStage.ts b/packages/cursorless-engine/src/processTargets/modifiers/ContainingScopeStage.ts index 35c019b9bb..fdbe17ab1b 100644 --- a/packages/cursorless-engine/src/processTargets/modifiers/ContainingScopeStage.ts +++ b/packages/cursorless-engine/src/processTargets/modifiers/ContainingScopeStage.ts @@ -2,10 +2,7 @@ import type { ContainingScopeModifier } from "@cursorless/common"; import { NoContainingScopeError } from "@cursorless/common"; import type { Target } from "../../typings/target.types"; import type { ModifierStageFactory } from "../ModifierStageFactory"; -import type { - ModifierStage, - ModifierStateOptions, -} from "../PipelineStages.types"; +import type { ModifierStage } from "../PipelineStages.types"; import { getContainingScopeTarget } from "./getContainingScopeTarget"; import type { ScopeHandlerFactory } from "./scopeHandlers/ScopeHandlerFactory"; @@ -34,7 +31,7 @@ export class ContainingScopeStage implements ModifierStage { private modifier: ContainingScopeModifier, ) {} - run(target: Target, options: ModifierStateOptions): Target[] { + run(target: Target): Target[] { const { scopeType, ancestorIndex = 0 } = this.modifier; const scopeHandler = this.scopeHandlerFactory.maybeCreate( @@ -43,9 +40,7 @@ export class ContainingScopeStage implements ModifierStage { ); if (scopeHandler == null) { - return this.modifierStageFactory - .getLegacyScopeStage(this.modifier) - .run(target, options); + throw new NoContainingScopeError(scopeType.type); } const containingScopes = getContainingScopeTarget( diff --git a/packages/cursorless-engine/src/processTargets/modifiers/EveryScopeStage.ts b/packages/cursorless-engine/src/processTargets/modifiers/EveryScopeStage.ts index 582ad2920e..f9b3f43c43 100644 --- a/packages/cursorless-engine/src/processTargets/modifiers/EveryScopeStage.ts +++ b/packages/cursorless-engine/src/processTargets/modifiers/EveryScopeStage.ts @@ -48,9 +48,7 @@ export class EveryScopeStage implements ModifierStage { ); if (scopeHandler == null) { - return this.modifierStageFactory - .getLegacyScopeStage(this.modifier) - .run(target, options); + throw new NoContainingScopeError(scopeType.type); } let scopes: TargetScope[] | undefined; diff --git a/packages/cursorless-engine/src/processTargets/modifiers/PreferredScopeStage.ts b/packages/cursorless-engine/src/processTargets/modifiers/PreferredScopeStage.ts index f2f0f7d412..03e9fb7ca3 100644 --- a/packages/cursorless-engine/src/processTargets/modifiers/PreferredScopeStage.ts +++ b/packages/cursorless-engine/src/processTargets/modifiers/PreferredScopeStage.ts @@ -5,10 +5,7 @@ import { } from "@cursorless/common"; import type { Target } from "../../typings/target.types"; import type { ModifierStageFactory } from "../ModifierStageFactory"; -import type { - ModifierStage, - ModifierStateOptions, -} from "../PipelineStages.types"; +import type { ModifierStage } from "../PipelineStages.types"; import { ContainingScopeStage } from "./ContainingScopeStage"; import type { TargetScope } from "./scopeHandlers/scope.types"; import type { ScopeHandler } from "./scopeHandlers/scopeHandler.types"; @@ -26,7 +23,7 @@ export class PreferredScopeStage implements ModifierStage { private modifier: PreferredScopeModifier, ) {} - run(target: Target, options: ModifierStateOptions): Target[] { + run(target: Target): Target[] { const { scopeType } = this.modifier; const containingScopeStage = new ContainingScopeStage( @@ -36,7 +33,7 @@ export class PreferredScopeStage implements ModifierStage { ); try { - return containingScopeStage.run(target, options); + return containingScopeStage.run(target); } catch (ex) { // NoContainingScopeError is thrown if no containing scope was found, which is fine. if (!(ex instanceof NoContainingScopeError)) { diff --git a/packages/cursorless-engine/src/processTargets/modifiers/scopeTypeStages/LegacyContainingSyntaxScopeStage.ts b/packages/cursorless-engine/src/processTargets/modifiers/scopeTypeStages/LegacyContainingSyntaxScopeStage.ts deleted file mode 100644 index 39461acc41..0000000000 --- a/packages/cursorless-engine/src/processTargets/modifiers/scopeTypeStages/LegacyContainingSyntaxScopeStage.ts +++ /dev/null @@ -1,118 +0,0 @@ -import type { - ContainingScopeModifier, - EveryScopeModifier, - SimpleScopeType, -} from "@cursorless/common"; -import { NoContainingScopeError, Selection } from "@cursorless/common"; -import type { Node } from "web-tree-sitter"; -import type { LanguageDefinitions } from "../../../languages/LanguageDefinitions"; -import { getNodeMatcher } from "../../../languages/getNodeMatcher"; -import type { - NodeMatcher, - SelectionWithEditor, - SelectionWithEditorWithContext, -} from "../../../typings/Types"; -import type { Target } from "../../../typings/target.types"; -import { selectionWithEditorFromRange } from "../../../util/selectionUtils"; -import type { ModifierStage } from "../../PipelineStages.types"; -import { ScopeTypeTarget } from "../../targets"; - -export interface SimpleContainingScopeModifier extends ContainingScopeModifier { - scopeType: SimpleScopeType; -} - -export interface SimpleEveryScopeModifier extends EveryScopeModifier { - scopeType: SimpleScopeType; -} - -export class LegacyContainingSyntaxScopeStage implements ModifierStage { - constructor( - private languageDefinitions: LanguageDefinitions, - private modifier: SimpleContainingScopeModifier | SimpleEveryScopeModifier, - ) {} - - run(target: Target): ScopeTypeTarget[] { - const nodeMatcher = getNodeMatcher( - target.editor.document.languageId, - this.modifier.scopeType.type, - this.modifier.type === "everyScope", - ); - - const node = this.languageDefinitions.getNodeAtLocation( - target.editor.document, - target.contentRange, - ); - - if (node == null) { - throw new NoContainingScopeError(this.modifier.scopeType.type); - } - - const scopeNodes = findNearestContainingAncestorNode(node, nodeMatcher, { - editor: target.editor, - selection: new Selection( - target.contentRange.start, - target.contentRange.end, - ), - }); - - if (scopeNodes == null) { - throw new NoContainingScopeError(this.modifier.scopeType.type); - } - - return scopeNodes.map((scope) => { - const { - containingListDelimiter, - leadingDelimiterRange, - trailingDelimiterRange, - removalRange, - } = 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: this.modifier.scopeType.type, - editor, - isReversed: target.isReversed, - contentRange: contentSelection, - removalRange: removalRange, - insertionDelimiter: containingListDelimiter, - leadingDelimiterRange, - trailingDelimiterRange, - }); - }); - } -} - -function findNearestContainingAncestorNode( - startNode: Node, - nodeMatcher: NodeMatcher, - selection: SelectionWithEditor, -): SelectionWithEditorWithContext[] | null { - let node: Node | null = startNode; - while (node != null) { - const matches = nodeMatcher(selection, node); - if (matches != null) { - return matches - .map((match) => match.selection) - .map((matchedSelection) => ({ - selection: selectionWithEditorFromRange( - selection, - matchedSelection.selection, - ), - context: matchedSelection.context, - })); - } - node = node.parent; - } - - return null; -} diff --git a/packages/cursorless-engine/src/runIntegrationTests.ts b/packages/cursorless-engine/src/runIntegrationTests.ts deleted file mode 100644 index 89294a92da..0000000000 --- a/packages/cursorless-engine/src/runIntegrationTests.ts +++ /dev/null @@ -1,46 +0,0 @@ -import { unsafeKeys, type TreeSitter } from "@cursorless/common"; -import type { LanguageDefinitions } from "./languages/LanguageDefinitions"; -import { legacyLanguageIds } from "./languages/LegacyLanguageId"; -import { languageMatchers } from "./languages/getNodeMatcher"; - -/** - * Run tests that require multiple components to be instantiated, as well as a - * full context, eg including tree sitter, but that are too closely tied to the - * engine to be defined in cursorless-vscode-e2e - * - * @param treeSitter The tree sitter instance - * @param languageDefinitions The language definitions instance - */ -export async function runIntegrationTests( - treeSitter: TreeSitter, - languageDefinitions: LanguageDefinitions, -) { - await assertNoScopesBothLegacyAndNew(treeSitter, languageDefinitions); -} - -async function assertNoScopesBothLegacyAndNew( - treeSitter: TreeSitter, - languageDefinitions: LanguageDefinitions, -) { - const errors: string[] = []; - for (const languageId of legacyLanguageIds) { - await treeSitter.loadLanguage(languageId); - await languageDefinitions.loadLanguage(languageId); - - unsafeKeys(languageMatchers[languageId] ?? {}).map((scopeTypeType) => { - if ( - languageDefinitions.get(languageId)?.getScopeHandler({ - type: scopeTypeType, - }) != null - ) { - errors.push( - `Scope '${scopeTypeType}' defined as both legacy and new for language ${languageId}`, - ); - } - }); - } - - if (errors.length > 0) { - throw Error(errors.join("\n")); - } -} diff --git a/packages/cursorless-engine/src/scopeProviders/ScopeSupportChecker.ts b/packages/cursorless-engine/src/scopeProviders/ScopeSupportChecker.ts index 73918bd104..3ae308c04d 100644 --- a/packages/cursorless-engine/src/scopeProviders/ScopeSupportChecker.ts +++ b/packages/cursorless-engine/src/scopeProviders/ScopeSupportChecker.ts @@ -1,11 +1,5 @@ -import type { - ScopeType, - SimpleScopeTypeType, - TextEditor, -} from "@cursorless/common"; +import type { ScopeType, TextEditor } from "@cursorless/common"; import { Position, ScopeSupport, isEmptyIterable } from "@cursorless/common"; -import type { LegacyLanguageId } from "../languages/LegacyLanguageId"; -import { languageMatchers } from "../languages/getNodeMatcher"; import type { ScopeHandlerFactory } from "../processTargets/modifiers/scopeHandlers/ScopeHandlerFactory"; import type { ScopeHandler } from "../processTargets/modifiers/scopeHandlers/scopeHandler.types"; @@ -35,7 +29,7 @@ export class ScopeSupportChecker { ); if (scopeHandler == null) { - return getLegacyScopeSupport(languageId, scopeType); + return ScopeSupport.unsupported; } return editorContainsScope(editor, scopeHandler) @@ -62,7 +56,7 @@ export class ScopeSupportChecker { ); if (scopeHandler == null) { - return getLegacyScopeSupport(languageId, scopeType); + return ScopeSupport.unsupported; } const iterationScopeHandler = this.scopeHandlerFactory.maybeCreate( @@ -88,24 +82,3 @@ function editorContainsScope( scopeHandler.generateScopes(editor, new Position(0, 0), "forward"), ); } - -function getLegacyScopeSupport( - languageId: string, - scopeType: ScopeType, -): ScopeSupport { - switch (scopeType.type) { - case "notebookCell": - // FIXME: What to do here - return ScopeSupport.unsupported; - default: - if ( - languageMatchers[languageId as LegacyLanguageId]?.[ - scopeType.type as SimpleScopeTypeType - ] != null - ) { - return ScopeSupport.supportedLegacy; - } - - return ScopeSupport.unsupported; - } -} diff --git a/packages/cursorless-engine/src/util/nodeFinders.ts b/packages/cursorless-engine/src/util/nodeFinders.ts deleted file mode 100644 index 987eb5a908..0000000000 --- a/packages/cursorless-engine/src/util/nodeFinders.ts +++ /dev/null @@ -1,359 +0,0 @@ -import type { Selection } from "@cursorless/common"; -import { Position } from "@cursorless/common"; -import type { Point, Node } from "web-tree-sitter"; -import type { NodeFinder } from "../typings/Types"; - -export const nodeFinder = ( - isTargetNode: (node: Node) => boolean, -): NodeFinder => { - return (node: Node) => { - return isTargetNode(node) ? node : null; - }; -}; - -/** - * Returns a new node finder which applies `nodeFinder` to the given node and - * then to each of its previous siblings in turn, returning the first one that - * is non null. - * @param nodeFinder The node finder to use - * @returns A node finder - */ -export function leadingSiblingNodeFinder(nodeFinder: NodeFinder) { - return (node: Node) => { - let currentNode: Node | null = node; - - while (currentNode != null) { - const returnNode = nodeFinder(currentNode); - - if (returnNode != null) { - return returnNode; - } - - currentNode = currentNode.previousSibling; - } - - return null; - }; -} - -/** - * Given a list of node finders returns a new node finder which applies them in - * sequence returning null if any of the sequence returns null otherwise - * returning the output of the final node finder - * @param nodeFinders A list of node finders to apply in sequence - * @returns A node finder which is a chain of the input node finders - */ -export function chainedNodeFinder(...nodeFinders: NodeFinder[]) { - return (node: Node) => { - let currentNode: Node | null = node; - for (const nodeFinder of nodeFinders) { - currentNode = nodeFinder(currentNode); - if (currentNode == null) { - return null; - } - } - - return currentNode; - }; -} - -/** - * Given a sequence of node finders, returns a new node finder which applies - * them in reverse, walking up the ancestor chain from `node`. - * Returns `null` if any finder in the chain returns null. For example: - * - * ancestorChainNodeFinder(0, patternFinder("foo", "bar"), patternFinder("bongo")) - * - * is equivalent to: - * - * patternFinder("foo.bongo", "bar.bongo") - * - * @param nodeToReturn The index of the node from the sequence to return. For - * example, `0` returns the top ancestor in the chain - * @param nodeFinders A list of node finders to apply in sequence - * @returns A node finder which is a chain of the input node finders - */ -export function ancestorChainNodeFinder( - nodeToReturn: number, - ...nodeFinders: NodeFinder[] -) { - return (node: Node) => { - let currentNode: Node | null = node; - const nodeList: Node[] = []; - const nodeFindersReversed = [...nodeFinders].reverse(); - - for (const nodeFinder of nodeFindersReversed) { - if (currentNode == null) { - return null; - } - - currentNode = nodeFinder(currentNode); - - if (currentNode == null) { - return null; - } - - nodeList.push(currentNode); - - currentNode = currentNode.parent ?? null; - } - - return nodeList.reverse()[nodeToReturn]; - }; -} - -export const typedNodeFinder = (...typeNames: string[]): NodeFinder => { - return nodeFinder((node) => typeNames.includes(node.type)); -}; - -const toPosition = (point: Point) => new Position(point.row, point.column); - -export const argumentNodeFinder = (...parentTypes: string[]): NodeFinder => { - const left = ["(", "{", "[", "<"]; - const right = [")", "}", "]", ">"]; - const delimiters = left.concat(right); - const isType = (node: Node | null, typeNames: string[]) => - node != null && typeNames.includes(node.type); - const isOk = (node: Node | null) => node != null && !isType(node, delimiters); - return (node: Node, selection?: Selection) => { - let resultNode: Node | null; - const { start, end } = selection!; - // Is already child - if (isType(node.parent, parentTypes)) { - if (isType(node, left)) { - resultNode = node.nextNamedSibling; - } else if (isType(node, right)) { - resultNode = node.previousNamedSibling; - } else if (node.type === ",") { - resultNode = end.isBeforeOrEqual(toPosition(node.startPosition)) - ? node.previousNamedSibling - : node.nextNamedSibling; - } else { - resultNode = node; - } - return isOk(resultNode) ? resultNode : null; - // Is parent - } else if (isType(node, parentTypes)) { - const children = [...node.children]; - const childRight = - children.find(({ startPosition }) => - toPosition(startPosition).isAfterOrEqual(end), - ) ?? null; - if (isOk(childRight)) { - return childRight; - } - children.reverse(); - const childLeft = - children.find(({ endPosition }) => - toPosition(endPosition).isBeforeOrEqual(start), - ) ?? null; - if (isOk(childLeft)) { - return childLeft; - } - } - return null; - }; -}; - -/** - * Creates a matcher that can match potentially wrapped nodes. For example - * typescript export statements or python decorators - * @param isWrapperNode Returns node if the given node has the right type to be - * a wrapper node - * @param isTargetNode Returns node if the given node has the right type to be - * the target - * @param getWrappedNodes Given a wrapper node returns a list of possible - * target nodes - * @returns A matcher that will return the given target node or the wrapper - * node, if it is wrapping a target node - */ -export function findPossiblyWrappedNode( - isWrapperNode: NodeFinder, - isTargetNode: NodeFinder, - getWrappedNodes: (node: Node) => (Node | null)[], -): NodeFinder { - return (node: Node) => { - if (node.parent != null && isWrapperNode(node.parent)) { - // We don't want to return the target node if it is wrapped. We return - // null, knowing that the ancestor walk will call us again with the - // wrapper node - return null; - } - - if (isWrapperNode(node)) { - const isWrappingTargetNode = getWrappedNodes(node).some( - (node) => node != null && isTargetNode(node), - ); - - if (isWrappingTargetNode) { - return node; - } - } - - return isTargetNode(node) ? node : null; - }; -} - -export function patternFinder(...patterns: string[]): NodeFinder { - const parsedPatterns = parsePatternStrings(patterns); - return (node: Node) => { - for (const pattern of parsedPatterns) { - const match = tryPatternMatch(node, pattern); - if (match != null) { - return match; - } - } - return null; - }; -} - -function parsePatternStrings(patternStrings: string[]) { - return patternStrings.map((patternString) => - patternString.split(".").map((pattern) => new Pattern(pattern)), - ); -} - -function tryPatternMatch(node: Node, patterns: Pattern[]): Node | null { - let result = searchNodeAscending(node, patterns); - - if (!result && patterns.length > 1) { - result = searchNodeDescending(node, patterns); - } - - let resultNode: Node | null = null; - let resultPattern; - - if (result != null) { - [resultNode, resultPattern] = result; - } - - // Use field name child if field name is given - if ( - resultNode != null && - resultPattern != null && - resultPattern.fields != null - ) { - resultPattern.fields.forEach((field) => { - resultNode = - (field.isIndex - ? resultNode?.namedChild(field.value) - : resultNode?.childForFieldName(field.value)) ?? null; - }); - } - - return resultNode; -} - -type NodePattern = [Node, Pattern] | null; - -function searchNodeAscending(node: Node, patterns: Pattern[]): NodePattern { - let result: NodePattern = null; - let currentNode: Node | null = node; - - for (let i = patterns.length - 1; i > -1; --i) { - const pattern = patterns[i]; - - if (currentNode == null || !pattern.typeEquals(currentNode)) { - if (pattern.isOptional) { - continue; - } - return null; - } - - // Return top node if not important found - if (!result || !result[1].isImportant) { - result = [currentNode, pattern]; - } - - currentNode = currentNode.parent; - } - - return result; -} - -function searchNodeDescending(node: Node, patterns: Pattern[]): NodePattern { - let result: NodePattern = null; - let currentNode: Node | null = node; - - for (let i = 0; i < patterns.length; ++i) { - const pattern = patterns[i]; - - if (currentNode == null || !pattern.typeEquals(currentNode)) { - if (pattern.isOptional) { - continue; - } - return null; - } - - // Return top node if not important found - if (!result || pattern.isImportant) { - result = [currentNode, pattern]; - } - - if (i + 1 < patterns.length) { - const children: Node[] = currentNode.namedChildren.filter((node) => - patterns[i + 1].typeEquals(node), - ); - currentNode = children.length === 1 ? children[0] : null; - } - } - - return result; -} - -interface PatternFieldIndex { - isIndex: true; - value: number; -} - -interface PatternFieldName { - isIndex: false; - value: string; -} - -type PatternField = PatternFieldName | PatternFieldIndex; - -class Pattern { - type: string; - fields: PatternField[]; - isImportant: boolean; - isOptional: boolean; - anyType: boolean = false; - notType: boolean = false; - - constructor(pattern: string) { - this.type = pattern.match(/^[\w*~]+/)![0]; - if (this.type === "*") { - this.anyType = true; - } else if (this.type.startsWith("~")) { - this.type = this.type.slice(1); - this.notType = true; - } - this.isImportant = pattern.indexOf("!") > -1; - this.isOptional = pattern.indexOf("?") > -1; - this.fields = [...pattern.matchAll(/(?<=\[).+?(?=\])/g)] - .map((m) => m[0]) - .map((field) => { - if (/\d+/.test(field)) { - return { - isIndex: true, - value: Number(field), - }; - } - return { - isIndex: false, - value: field, - }; - }); - } - - typeEquals(node: Node) { - if (this.anyType) { - return true; - } - if (this.notType) { - return this.type !== node.type; - } - return this.type === node.type; - } -} diff --git a/packages/cursorless-engine/src/util/nodeMatchers.ts b/packages/cursorless-engine/src/util/nodeMatchers.ts deleted file mode 100644 index b0bf026d42..0000000000 --- a/packages/cursorless-engine/src/util/nodeMatchers.ts +++ /dev/null @@ -1,195 +0,0 @@ -import type { SimpleScopeTypeType } from "@cursorless/common"; -import { unsafeKeys } from "@cursorless/common"; -import type { Node } from "web-tree-sitter"; -import type { - NodeFinder, - NodeMatcher, - NodeMatcherAlternative, - SelectionExtractor, - SelectionWithEditor, -} from "../typings/Types"; -import { - ancestorChainNodeFinder, - argumentNodeFinder, - chainedNodeFinder, - patternFinder, - typedNodeFinder, -} from "./nodeFinders"; -import { - argumentSelectionExtractor, - selectWithLeadingDelimiter, - selectWithTrailingDelimiter, - simpleSelectionExtractor, - unwrapSelectionExtractor, -} from "./nodeSelectors"; - -export function matcher( - finder: NodeFinder, - selector: SelectionExtractor = simpleSelectionExtractor, -): NodeMatcher { - return function (selection: SelectionWithEditor, node: Node) { - const targetNode = finder(node, selection.selection); - return targetNode != null - ? [ - { - node: targetNode, - selection: selector(selection.editor, targetNode), - }, - ] - : null; - }; -} - -/** - * Given a list of node finders returns a matcher which applies them in - * sequence returning null if any of the sequence returns null otherwise - * returning the output of the final node finder - * @param nodeFinders A list of node finders to apply in sequence - * @param selector The selector to apply to the final node - * @returns A matcher which is a chain of the input node finders - */ -export function chainedMatcher( - finders: NodeFinder[], - selector: SelectionExtractor = simpleSelectionExtractor, -): NodeMatcher { - const nodeFinder = chainedNodeFinder(...finders); - - return function (selection: SelectionWithEditor, initialNode: Node) { - const returnNode = nodeFinder(initialNode); - - if (returnNode == null) { - return null; - } - - return [ - { - node: returnNode, - selection: selector(selection.editor, returnNode), - }, - ]; - }; -} - -/** - * Given a sequence of node finders, returns a new node matcher which applies - * them in reverse, walking up the ancestor chain from `node`. - * Returns `null` if any finder in the chain returns null. For example: - * - * ancestorChainNodeMatcher([patternFinder("foo", "bar"), patternFinder("bongo")], 0) - * - * is equivalent to: - * - * patternFinder("foo.bongo", "bar.bongo") - * - * @param nodeFinders A list of node finders to apply in sequence - * @param nodeToReturn The index of the node from the sequence to return. For - * example, `0` returns the top ancestor in the chain - * @param selector The selector to apply to the final node - * @returns A node finder which is a chain of the input node finders - */ -export function ancestorChainNodeMatcher( - nodeFinders: NodeFinder[], - nodeToReturn: number = 0, - selector: SelectionExtractor = simpleSelectionExtractor, -) { - return matcher( - ancestorChainNodeFinder(nodeToReturn, ...nodeFinders), - selector, - ); -} - -export function typeMatcher(...typeNames: string[]) { - return matcher(typedNodeFinder(...typeNames)); -} - -export function patternMatcher(...patterns: string[]): NodeMatcher { - return matcher(patternFinder(...patterns)); -} - -export function argumentMatcher(...parentTypes: string[]): NodeMatcher { - return matcher( - argumentNodeFinder(...parentTypes), - argumentSelectionExtractor(), - ); -} - -export function conditionMatcher(...patterns: string[]): NodeMatcher { - return matcher(patternFinder(...patterns), unwrapSelectionExtractor); -} - -/** - * Given `patterns`, creates a node matcher that will add leading delimiter to - * removal range. - * @param patterns Patterns for pattern finder - * @returns A node matcher - */ -export function leadingMatcher( - patterns: string[], - delimiters: string[] = [], -): NodeMatcher { - return matcher( - patternFinder(...patterns), - selectWithLeadingDelimiter(...delimiters), - ); -} - -/** - * Given `patterns`, creates a node matcher that will add trailing delimiter to - * removal range. - * @param patterns Patterns for pattern finder - * @returns A node matcher - */ -export function trailingMatcher( - patterns: string[], - delimiters: string[] = [], -): NodeMatcher { - return matcher( - patternFinder(...patterns), - selectWithTrailingDelimiter(...delimiters), - ); -} - -/** - * Create a new matcher that will try the given matchers in sequence until one - * returns non-null - * @param matchers A list of matchers to try in sequence until one doesn't - * return null - * @returns A NodeMatcher that tries the given matchers in sequence - */ -export function cascadingMatcher(...matchers: NodeMatcher[]): NodeMatcher { - return (selection: SelectionWithEditor, node: Node) => { - for (const matcher of matchers) { - const match = matcher(selection, node); - if (match != null) { - return match; - } - } - - return null; - }; -} - -export function notSupported(scopeTypeType: SimpleScopeTypeType): NodeMatcher { - return (_selection: SelectionWithEditor, _node: Node) => { - throw new Error(`Node type '${scopeTypeType}' not supported`); - }; -} - -export function createPatternMatchers( - nodeMatchers: Partial>, -): Partial> { - return Object.freeze( - Object.fromEntries( - unsafeKeys(nodeMatchers).map((scopeType: SimpleScopeTypeType) => { - const matcher = nodeMatchers[scopeType]; - if (Array.isArray(matcher)) { - return [scopeType, patternMatcher(...matcher)]; - } else if (typeof matcher === "string") { - return [scopeType, patternMatcher(matcher)]; - } else { - return [scopeType, matcher]; - } - }), - ), - ); -} diff --git a/packages/cursorless-engine/src/util/nodeSelectors.ts b/packages/cursorless-engine/src/util/nodeSelectors.ts deleted file mode 100644 index 4b309bc3e3..0000000000 --- a/packages/cursorless-engine/src/util/nodeSelectors.ts +++ /dev/null @@ -1,448 +0,0 @@ -import type { TextEditor } from "@cursorless/common"; -import { Position, Range, Selection } from "@cursorless/common"; -import { identity, maxBy } from "lodash-es"; -import type { Point, Node } from "web-tree-sitter"; -import type { - NodeFinder, - SelectionExtractor, - SelectionWithContext, -} from "../typings/Types"; - -export function makeRangeFromPositions( - startPosition: Point, - endPosition: Point, -) { - return new Range( - startPosition.row, - startPosition.column, - endPosition.row, - endPosition.column, - ); -} - -export function positionFromPoint(point: Point): Position { - return new Position(point.row, point.column); -} - -export function getNodeRange(node: Node) { - return new Range( - node.startPosition.row, - node.startPosition.column, - node.endPosition.row, - node.endPosition.column, - ); -} - -export function makeNodePairSelection(anchor: Node, active: Node) { - return new Selection( - anchor.startPosition.row, - anchor.startPosition.column, - active.endPosition.row, - active.endPosition.column, - ); -} - -/** - * Returns node range excluding the first and last child - * @param node The note for which to get the internal range - * @returns The internal range of the given node - */ -export function getNodeInternalRange(node: Node) { - const children = node.children; - - return makeRangeFromPositions( - children[0].endPosition, - children[children.length - 1].startPosition, - ); -} - -export function simpleSelectionExtractor( - editor: TextEditor, - node: Node, -): SelectionWithContext { - return { - selection: new Selection( - new Position(node.startPosition.row, node.startPosition.column), - new Position(node.endPosition.row, node.endPosition.column), - ), - context: {}, - }; -} - -/** - * Given a node and a node finder extends the selection from the given node - * until just before the next matching sibling node or the final node if no - * sibling matches - * @param editor The text editor containing the given node - * @param node The node from which to extend - * @param nodeFinder The finder to use to determine whether a given node matches - * @returns A range from the start node until just before the next matching - * sibling or the final sibling if non matches - */ -export function extendUntilNextMatchingSiblingOrLast( - editor: TextEditor, - node: Node, - nodeFinder: NodeFinder, -) { - const endNode = getNextMatchingSiblingNodeOrLast(node, nodeFinder); - return pairSelectionExtractor(editor, node, endNode); -} - -function getNextMatchingSiblingNodeOrLast( - node: Node, - nodeFinder: NodeFinder, -): Node { - let currentNode: Node = node; - let nextNode: Node | null = node.nextSibling; - - while (nextNode != null && nodeFinder(nextNode) == null) { - currentNode = nextNode; - nextNode = nextNode.nextSibling; - } - return currentNode; -} - -/** - * Creates an extractor that will extend past the next node if it has one of the - * types defined in {@link delimiters} - * @param delimiters Allowable next node type - * @returns An extractor - */ -export function extendForwardPastOptional(...delimiters: string[]) { - return function (editor: TextEditor, node: Node): SelectionWithContext { - const nextNode: Node | null = node.nextSibling; - - if (nextNode != null && delimiters.includes(nextNode.type)) { - return pairSelectionExtractor(editor, node, nextNode); - } - - return simpleSelectionExtractor(editor, node); - }; -} - -/** - * Extracts a selection from the first node to the second node. - * Both nodes are included in the selected nodes - */ -export function pairSelectionExtractor( - editor: TextEditor, - node1: Node, - node2: Node, -): SelectionWithContext { - const isForward = node1.startIndex < node2.startIndex; - const start = isForward ? node1 : node2; - const end = isForward ? node2 : node1; - return { - selection: new Selection( - new Position(start.startPosition.row, start.startPosition.column), - new Position(end.endPosition.row, end.endPosition.column), - ), - context: {}, - }; -} - -export function argumentSelectionExtractor(): SelectionExtractor { - return delimitedSelector( - (node) => - node.type === "," || - node.type === "(" || - node.type === ")" || - node.type === "[" || - node.type === "]" || - node.type === ">" || - node.type === "<" || - node.type === "}" || - node.type === "{", - ", ", - ); -} - -export function unwrapSelectionExtractor( - editor: TextEditor, - node: Node, -): SelectionWithContext { - let startIndex = node.startIndex; - let endIndex = node.endIndex; - if (node.text.startsWith("(") && node.text.endsWith(")")) { - startIndex += 1; - endIndex -= 1; - } else if (node.text.endsWith(";")) { - endIndex -= 1; - } - return { - selection: new Selection( - editor.document.positionAt(startIndex), - editor.document.positionAt(endIndex), - ), - context: {}, - }; -} - -export function selectWithLeadingDelimiter(...delimiters: string[]) { - return function (editor: TextEditor, node: Node): SelectionWithContext { - const firstSibling = node.previousSibling; - const secondSibling = firstSibling?.previousSibling; - let leadingDelimiterRange; - if (firstSibling) { - if (delimiters.includes(firstSibling.type)) { - // Second sibling exists. Terminate before it. - if (secondSibling) { - leadingDelimiterRange = makeRangeFromPositions( - secondSibling.endPosition, - node.startPosition, - ); - } - // First delimiter sibling exists. Terminate after it. - else { - leadingDelimiterRange = makeRangeFromPositions( - firstSibling.startPosition, - node.startPosition, - ); - } - } - // First sibling exists but is not the delimiter we are looking for. Terminate before it. - else { - leadingDelimiterRange = makeRangeFromPositions( - firstSibling.endPosition, - node.startPosition, - ); - } - } - return { - ...simpleSelectionExtractor(editor, node), - context: { - leadingDelimiterRange, - }, - }; - }; -} - -interface ChildRangeSelectorOptions { - /** - * If `true`, include unnamed children. Otherwise, only named children will - * be used to construct the child range. Defaults to `false` - */ - includeUnnamedChildren?: boolean; -} - -/** - * Creates an extractor that returns a contiguous range between children of a node. - * When no arguments are passed, the function will return a range from the first to the last child node. Pass in either inclusions - * If an inclusion or exclusion list is passed, we return the first range of children such that every child in the range matches the inclusion / exclusion criteria. - * @param typesToExclude Ensure these child types are excluded in the contiguous range returned. - * @param typesToInclude Ensure these child types are included in the contiguous range returned. - * @returns A selection extractor - */ -export function childRangeSelector( - typesToExclude: string[] = [], - typesToInclude: string[] = [], - { includeUnnamedChildren = false }: ChildRangeSelectorOptions = {}, -) { - return function (editor: TextEditor, node: Node): SelectionWithContext { - if (typesToExclude.length > 0 && typesToInclude.length > 0) { - throw new Error("Cannot have both exclusions and inclusions."); - } - let nodes = includeUnnamedChildren ? node.children : node.namedChildren; - const exclusionSet = new Set(typesToExclude); - const inclusionSet = new Set(typesToInclude); - nodes = nodes.filter((child) => { - if (exclusionSet.size > 0) { - return !exclusionSet.has(child.type); - } - - if (inclusionSet.size > 0) { - return inclusionSet.has(child.type); - } - - return true; - }); - - return pairSelectionExtractor(editor, nodes[0], nodes[nodes.length - 1]); - }; -} - -export function selectWithTrailingDelimiter(...delimiters: string[]) { - return function (editor: TextEditor, node: Node): SelectionWithContext { - const firstSibling = node.nextSibling; - const secondSibling = firstSibling?.nextSibling; - let trailingDelimiterRange; - if (firstSibling) { - if (delimiters.includes(firstSibling.type)) { - if (secondSibling) { - trailingDelimiterRange = makeRangeFromPositions( - node.endPosition, - secondSibling.startPosition, - ); - } else { - trailingDelimiterRange = makeRangeFromPositions( - node.endPosition, - firstSibling.endPosition, - ); - } - } else { - trailingDelimiterRange = makeRangeFromPositions( - node.endPosition, - firstSibling.startPosition, - ); - } - } - return { - ...simpleSelectionExtractor(editor, node), - context: { - trailingDelimiterRange, - }, - }; - }; -} - -function getNextNonDelimiterNode( - startNode: Node, - isDelimiterNode: (node: Node) => boolean, -): Node | null { - let node = startNode.nextSibling; - - while (node != null) { - if (!isDelimiterNode(node)) { - return node; - } - - node = node.nextSibling; - } - - return node; -} - -function getPreviousNonDelimiterNode( - startNode: Node, - isDelimiterNode: (node: Node) => boolean, -): Node | null { - let node = startNode.previousSibling; - - while (node != null) { - if (!isDelimiterNode(node)) { - return node; - } - - node = node.previousSibling; - } - - return node; -} - -export function delimitersSelector(...delimiters: string[]) { - return delimitedSelector((node) => delimiters.includes(node.type), ", "); -} - -/** - * Creates a selector which can be used to automatically clean up after elements - * in a list by removing leading or trailing delimiters - * @param isDelimiterNode A function used to determine whether a given node is a - * delimiter node - * @param defaultDelimiter The default list separator to use if we can't - * determine it by looking before or after the given node - * @param getStartNode A function to be applied to the node to determine which - * node is the start node if we really want to expand to a sequence of nodes - * @param getEndNode A function to be applied to the node to determine which - * node is the end node if we really want to expand to a sequence of nodes - * @returns A selection extractor - */ -export function delimitedSelector( - isDelimiterNode: (node: Node) => boolean, - defaultDelimiter: string, - getStartNode: (node: Node) => Node = identity, - getEndNode: (node: Node) => Node = identity, -): SelectionExtractor { - return (editor: TextEditor, node: Node) => { - let leadingDelimiterRange: Range | undefined; - let trailingDelimiterRange: Range | undefined; - const startNode = getStartNode(node); - const endNode = getEndNode(node); - - const previousNonDelimiterNode = getPreviousNonDelimiterNode( - startNode, - isDelimiterNode, - ); - const nextNonDelimiterNode = getNextNonDelimiterNode( - endNode, - isDelimiterNode, - ); - - if (previousNonDelimiterNode != null) { - leadingDelimiterRange = makeRangeFromPositions( - previousNonDelimiterNode.endPosition, - startNode.startPosition, - ); - } - - if (nextNonDelimiterNode != null) { - trailingDelimiterRange = makeRangeFromPositions( - endNode.endPosition, - nextNonDelimiterNode.startPosition, - ); - } - - const containingListDelimiter = getInsertionDelimiter( - editor, - leadingDelimiterRange, - trailingDelimiterRange, - defaultDelimiter, - ); - - return { - selection: new Selection( - new Position( - startNode.startPosition.row, - startNode.startPosition.column, - ), - new Position(endNode.endPosition.row, endNode.endPosition.column), - ), - context: { - containingListDelimiter, - leadingDelimiterRange, - trailingDelimiterRange, - }, - }; - }; -} - -export function xmlElementExtractor( - editor: TextEditor, - node: Node, -): SelectionWithContext { - const selection = simpleSelectionExtractor(editor, node); - - // Interior range for an element is found by excluding the start and end nodes. - // Element nodes with too few children are self closing and therefore have no interior. - if (node.namedChildCount > 1) { - const { firstNamedChild, lastNamedChild } = node; - if (firstNamedChild != null && lastNamedChild != null) { - selection.context.interiorRange = new Range( - firstNamedChild.endPosition.row, - firstNamedChild.endPosition.column, - lastNamedChild.startPosition.row, - lastNamedChild.startPosition.column, - ); - } - } - - return selection; -} - -export function getInsertionDelimiter( - editor: TextEditor, - leadingDelimiterRange: Range | undefined, - trailingDelimiterRange: Range | undefined, - defaultDelimiterInsertion: string, -) { - const { document } = editor; - const delimiters = [ - trailingDelimiterRange != null - ? document.getText(trailingDelimiterRange) - : defaultDelimiterInsertion, - leadingDelimiterRange != null - ? document.getText(leadingDelimiterRange) - : defaultDelimiterInsertion, - ]; - - return maxBy(delimiters, "length"); -} diff --git a/packages/cursorless-neovim/src/constructTestHelpers.ts b/packages/cursorless-neovim/src/constructTestHelpers.ts index 1d0c941191..044beebbe7 100644 --- a/packages/cursorless-neovim/src/constructTestHelpers.ts +++ b/packages/cursorless-neovim/src/constructTestHelpers.ts @@ -25,7 +25,6 @@ export function constructTestHelpers( normalizedIde: NormalizedIDE, scopeProvider: ScopeProvider, injectIde: (ide: IDE) => void, - runIntegrationTests: () => Promise, ): NeovimTestHelpers | undefined { return { commandServerApi: commandServerApi!, @@ -66,6 +65,5 @@ export function constructTestHelpers( ); }, hatTokenMap, - runIntegrationTests, }; } diff --git a/packages/cursorless-neovim/src/extension.ts b/packages/cursorless-neovim/src/extension.ts index 9516745b60..9d557b68e6 100644 --- a/packages/cursorless-neovim/src/extension.ts +++ b/packages/cursorless-neovim/src/extension.ts @@ -40,14 +40,8 @@ export async function activate(plugin: NvimPlugin) { ? fakeCommandServerApi : neovimCommandServerApi; - const { - commandApi, - storedTargets, - hatTokenMap, - scopeProvider, - injectIde, - runIntegrationTests, - } = await createCursorlessEngine({ ide: normalizedIde, commandServerApi }); + const { commandApi, storedTargets, hatTokenMap, scopeProvider, injectIde } = + await createCursorlessEngine({ ide: normalizedIde, commandServerApi }); await registerCommands(client, neovimIDE, commandApi, commandServerApi); @@ -62,7 +56,6 @@ export async function activate(plugin: NvimPlugin) { normalizedIde as NormalizedIDE, scopeProvider, injectIde, - runIntegrationTests, ) : undefined, }; diff --git a/packages/cursorless-org-docs/src/docs/contributing/CONTRIBUTING.md b/packages/cursorless-org-docs/src/docs/contributing/CONTRIBUTING.md index 2ec381fd3a..1144190f5f 100644 --- a/packages/cursorless-org-docs/src/docs/contributing/CONTRIBUTING.md +++ b/packages/cursorless-org-docs/src/docs/contributing/CONTRIBUTING.md @@ -95,10 +95,6 @@ It is also possible to write manual tests. When doing so, we have a convention t See [docs](./adding-a-new-language.md). -### Adding syntactic scope types to an existing language - -See [parse-tree-patterns.md](./parse-tree-patterns.md). - ### Testing Cursorless with a local version of the VSCode parse tree extension First bundle the parse tree extension into a `.vsix`, using something like the following: diff --git a/packages/cursorless-org-docs/src/docs/contributing/parse-tree-patterns.md b/packages/cursorless-org-docs/src/docs/contributing/parse-tree-patterns.md deleted file mode 100644 index 6d7371b860..0000000000 --- a/packages/cursorless-org-docs/src/docs/contributing/parse-tree-patterns.md +++ /dev/null @@ -1,26 +0,0 @@ -# Parse tree pattern matcher [DEPRECATED] - -We have a small domain-specific language that we use to define patterns to look for in tree-sitter parse trees. This DSL enables us to rapidly define new syntactic scope types and support new programming languages. - -## Format - -`GRAND_PARENT_TYPE.*.CHILD_TYPE[FIELD]!?` - -| Syntax | Description | -| ------------------- | --------------------------------------------------------------------------------------- | -| TYPE | Match node type
`node.type` | -| Dot operator(`.`) | Type hierarchy between parent and child | -| Wildcard op(`*`) | Match any type | -| Negation op(`~`) | Match any type other than what is specified after `~` | -| Important op(`!`) | Use this node as result instead of parent.
By default the leftmost/top node is used | -| Optional op(`?`) | Node is optional. Will match if available. | -| Field op(`[field]`) | Return child node at given field.
`node.childForFieldName(field)` | -| Index op(`[n]`) | Return nth named child node. | - -## Multiple patterns - -When using multiple patterns evaluation will be performed top to bottom and the first pattern too match will be used. - -```js -["export_statement?.class_declaration", "export_statement.class"]; -``` diff --git a/packages/cursorless-org-docs/src/docs/user/languages/components/ScopeSupport.tsx b/packages/cursorless-org-docs/src/docs/user/languages/components/ScopeSupport.tsx index bafebf11f1..18cd0bba24 100644 --- a/packages/cursorless-org-docs/src/docs/user/languages/components/ScopeSupport.tsx +++ b/packages/cursorless-org-docs/src/docs/user/languages/components/ScopeSupport.tsx @@ -17,9 +17,6 @@ export function ScopeSupport({ languageId }: Props): React.JSX.Element { const supportedFacets = facetsSorted.filter( (facet) => scopeSupport[facet] === ScopeSupportFacetLevel.supported, ); - const supportedLegacyFacets = facetsSorted.filter( - (facet) => scopeSupport[facet] === ScopeSupportFacetLevel.supportedLegacy, - ); const unsupportedFacets = facetsSorted.filter( (facet) => scopeSupport[facet] === ScopeSupportFacetLevel.unsupported, ); @@ -38,12 +35,6 @@ export function ScopeSupport({ languageId }: Props): React.JSX.Element { open /> - - { - const cursorlessApi = await getCursorlessApi(); - const { runIntegrationTests } = cursorlessApi.testHelpers!; - await runIntegrationTests(); - }); -}); diff --git a/packages/cursorless-vscode/src/ScopeTreeProvider.ts b/packages/cursorless-vscode/src/ScopeTreeProvider.ts index 75f3e20c8a..730f4453cc 100644 --- a/packages/cursorless-vscode/src/ScopeTreeProvider.ts +++ b/packages/cursorless-vscode/src/ScopeTreeProvider.ts @@ -112,7 +112,7 @@ export class ScopeTreeProvider implements TreeDataProvider { getChildren(element?: MyTreeItem): MyTreeItem[] { if (element == null) { void this.possiblyShowUpdateTalonMessage(); - return getSupportCategories(this.hasLegacyScopes()); + return getSupportCategories(); } if (element instanceof SupportCategoryTreeItem) { @@ -156,12 +156,6 @@ export class ScopeTreeProvider implements TreeDataProvider { } } - private hasLegacyScopes(): boolean { - return this.supportLevels.some( - (supportLevel) => supportLevel.support === ScopeSupport.supportedLegacy, - ); - } - private getScopeTypesWithSupport( scopeSupport: ScopeSupport, ): ScopeSupportTreeItem[] { @@ -210,15 +204,10 @@ export class ScopeTreeProvider implements TreeDataProvider { } } -function getSupportCategories( - includeLegacy: boolean, -): SupportCategoryTreeItem[] { +function getSupportCategories(): SupportCategoryTreeItem[] { return [ new SupportCategoryTreeItem(ScopeSupport.supportedAndPresentInEditor), new SupportCategoryTreeItem(ScopeSupport.supportedButNotPresentInEditor), - ...(includeLegacy - ? [new SupportCategoryTreeItem(ScopeSupport.supportedLegacy)] - : []), new SupportCategoryTreeItem(ScopeSupport.unsupported), ]; } @@ -314,11 +303,6 @@ class SupportCategoryTreeItem extends TreeItem { description = "but not present in active editor"; collapsibleState = TreeItemCollapsibleState.Expanded; break; - case ScopeSupport.supportedLegacy: - label = "Legacy"; - description = "may or may not be present in active editor"; - collapsibleState = TreeItemCollapsibleState.Expanded; - break; case ScopeSupport.unsupported: label = "Unsupported"; description = "unsupported in language of active editor"; diff --git a/packages/cursorless-vscode/src/constructTestHelpers.ts b/packages/cursorless-vscode/src/constructTestHelpers.ts index 76d51b48e6..d224027ff4 100644 --- a/packages/cursorless-vscode/src/constructTestHelpers.ts +++ b/packages/cursorless-vscode/src/constructTestHelpers.ts @@ -32,7 +32,6 @@ export function constructTestHelpers( fileSystem: VscodeFileSystem, scopeProvider: ScopeProvider, injectIde: (ide: IDE) => void, - runIntegrationTests: () => Promise, vscodeTutorial: VscodeTutorial, ): VscodeTestHelpers | undefined { return { @@ -81,7 +80,6 @@ export function constructTestHelpers( ); }, hatTokenMap, - runIntegrationTests, vscodeApi, getTutorialWebviewEventLog() { return vscodeTutorial.getEventLog(); diff --git a/packages/cursorless-vscode/src/extension.ts b/packages/cursorless-vscode/src/extension.ts index 9293574f1b..b00e14ed27 100644 --- a/packages/cursorless-vscode/src/extension.ts +++ b/packages/cursorless-vscode/src/extension.ts @@ -124,7 +124,6 @@ export async function activate( hatTokenMap, scopeProvider, injectIde, - runIntegrationTests, addCommandRunnerDecorator, customSpokenFormGenerator, } = await createCursorlessEngine(engineProps); @@ -213,7 +212,6 @@ export async function activate( fileSystem, scopeProvider, injectIde, - runIntegrationTests, vscodeTutorial, ) : undefined, diff --git a/packages/cursorless-vscode/src/ide/vscode/VSCodeScopeVisualizer/VscodeScopeVisualizer.ts b/packages/cursorless-vscode/src/ide/vscode/VSCodeScopeVisualizer/VscodeScopeVisualizer.ts index 0ab33c9b0f..743b404870 100644 --- a/packages/cursorless-vscode/src/ide/vscode/VSCodeScopeVisualizer/VscodeScopeVisualizer.ts +++ b/packages/cursorless-vscode/src/ide/vscode/VSCodeScopeVisualizer/VscodeScopeVisualizer.ts @@ -63,7 +63,6 @@ export abstract class VscodeScopeVisualizer { case ScopeSupport.supportedAndPresentInEditor: case ScopeSupport.supportedButNotPresentInEditor: return; - case ScopeSupport.supportedLegacy: case ScopeSupport.unsupported: void showError( this.ide.messages, diff --git a/packages/neovim-common/src/TestHelpers.ts b/packages/neovim-common/src/TestHelpers.ts index 1ecd686a2d..4d893ec07c 100644 --- a/packages/neovim-common/src/TestHelpers.ts +++ b/packages/neovim-common/src/TestHelpers.ts @@ -12,6 +12,4 @@ export interface NeovimTestHelpers extends TestHelpers { injectIde: (ide: IDE) => void; scopeProvider: ScopeProvider; - - runIntegrationTests(): Promise; } diff --git a/packages/vscode-common/src/TestHelpers.ts b/packages/vscode-common/src/TestHelpers.ts index 7fc9ff9598..dbbf3224c5 100644 --- a/packages/vscode-common/src/TestHelpers.ts +++ b/packages/vscode-common/src/TestHelpers.ts @@ -18,8 +18,6 @@ export interface VscodeTestHelpers extends TestHelpers { toVscodeEditor(editor: TextEditor): vscode.TextEditor; fromVscodeEditor(editor: vscode.TextEditor): TextEditor; - runIntegrationTests(): Promise; - cursorlessTalonStateJsonPath: string; cursorlessCommandHistoryDirPath: string;