Skip to content

Add tree-sitter related node support #1430

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Merged
merged 8 commits into from
Apr 19, 2023
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 0 additions & 1 deletion packages/cursorless-engine/src/languages/ruby.ts
Original file line number Diff line number Diff line change
Expand Up @@ -175,7 +175,6 @@ const nodeMatchers: Partial<
"argument_list",
),
collectionKey: trailingMatcher(["pair[key]"], [":"]),
className: "class[name]",
name: [
"assignment[left]",
"operator_assignment[left]",
Expand Down
Original file line number Diff line number Diff line change
@@ -1,18 +1,13 @@
import type { ContainingScopeModifier, Direction } from "@cursorless/common";
import {
ContainingScopeModifier,
NoContainingScopeError,
Position,
TextEditor,
} from "@cursorless/common";
import type { ProcessedTargetsContext } from "../../typings/Types";
import type { Target } from "../../typings/target.types";
import { ModifierStageFactory } from "../ModifierStageFactory";
import type { ModifierStage } from "../PipelineStages.types";
import { constructScopeRangeTarget } from "./constructScopeRangeTarget";
import { getContainingScope } from "./getContainingScope";
import { ScopeHandlerFactory } from "./scopeHandlers/ScopeHandlerFactory";
import { TargetScope } from "./scopeHandlers/scope.types";
import { ScopeHandler } from "./scopeHandlers/scopeHandler.types";
import { getContainingScopeTarget } from "./getContainingScopeTarget";

/**
* This modifier stage expands from the input target to the smallest containing
Expand Down Expand Up @@ -40,11 +35,6 @@ export class ContainingScopeStage implements ModifierStage {
) {}

run(context: ProcessedTargetsContext, target: Target): Target[] {
const {
isReversed,
editor,
contentRange: { start, end },
} = target;
const { scopeType, ancestorIndex = 0 } = this.modifier;

const scopeHandler = this.scopeHandlerFactory.create(
Expand All @@ -58,131 +48,16 @@ export class ContainingScopeStage implements ModifierStage {
.run(context, target);
}

if (end.isEqual(start)) {
// Input target is empty; return the preferred scope touching target
let scope = getPreferredScopeTouchingPosition(
scopeHandler,
editor,
start,
);

if (scope == null) {
throw new NoContainingScopeError(this.modifier.scopeType.type);
}

if (ancestorIndex > 0) {
scope = expandFromPosition(
scopeHandler,
editor,
scope.domain.end,
"forward",
ancestorIndex - 1,
);
}

if (scope == null) {
throw new NoContainingScopeError(this.modifier.scopeType.type);
}

return [scope.getTarget(isReversed)];
}

const startScope = expandFromPosition(
scopeHandler,
editor,
start,
"forward",
ancestorIndex,
);

if (startScope == null) {
throw new NoContainingScopeError(this.modifier.scopeType.type);
}

if (startScope.domain.contains(end)) {
return [startScope.getTarget(isReversed)];
}

const endScope = expandFromPosition(
const containingScope = getContainingScopeTarget(
target,
scopeHandler,
editor,
end,
"backward",
ancestorIndex,
);

if (endScope == null) {
if (containingScope == null) {
throw new NoContainingScopeError(this.modifier.scopeType.type);
}

return [constructScopeRangeTarget(isReversed, startScope, endScope)];
return [containingScope];
}
}

function expandFromPosition(
scopeHandler: ScopeHandler,
editor: TextEditor,
position: Position,
direction: Direction,
ancestorIndex: number,
): TargetScope | undefined {
let nextAncestorIndex = 0;
for (const scope of scopeHandler.generateScopes(editor, position, direction, {
containment: "required",
})) {
if (nextAncestorIndex === ancestorIndex) {
return scope;
}

// Because containment is required, and we are moving in a consistent
// direction (ie forward or backward), each scope will be progressively
// larger
nextAncestorIndex += 1;
}

return undefined;
}

function getPreferredScopeTouchingPosition(
scopeHandler: ScopeHandler,
editor: TextEditor,
position: Position,
): TargetScope | undefined {
const forwardScope = getContainingScope(
scopeHandler,
editor,
position,
"forward",
);

if (forwardScope == null) {
return getContainingScope(scopeHandler, editor, position, "backward");
}

if (
scopeHandler.isPreferredOver == null ||
forwardScope.domain.start.isBefore(position)
) {
return forwardScope;
}

const backwardScope = getContainingScope(
scopeHandler,
editor,
position,
"backward",
);

// If there is no backward scope, or if the backward scope is an ancestor of
// forward scope, return forward scope
if (
backwardScope == null ||
backwardScope.domain.contains(forwardScope.domain)
) {
return forwardScope;
}

return scopeHandler.isPreferredOver(backwardScope, forwardScope) ?? false
? backwardScope
: forwardScope;
}
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,7 @@ import type { ProcessedTargetsContext } from "../../typings/Types";
import type { Target } from "../../typings/target.types";
import { ModifierStageFactory } from "../ModifierStageFactory";
import type { ModifierStage } from "../PipelineStages.types";
import { getContainingScopeTarget } from "./getContainingScopeTarget";
import { ScopeHandlerFactory } from "./scopeHandlers/ScopeHandlerFactory";
import getScopesOverlappingRange from "./scopeHandlers/getScopesOverlappingRange";
import { TargetScope } from "./scopeHandlers/scope.types";
Expand Down Expand Up @@ -77,7 +78,11 @@ export class EveryScopeStage implements ModifierStage {
scopes = getScopesOverlappingRange(
scopeHandler,
editor,
this.getDefaultIterationRange(context, scopeHandler, target),
this.getDefaultIterationRange(
scopeHandler,
this.scopeHandlerFactory,
target,
),
);
}

Expand All @@ -89,16 +94,30 @@ export class EveryScopeStage implements ModifierStage {
}

getDefaultIterationRange(
context: ProcessedTargetsContext,
scopeHandler: ScopeHandler,
scopeHandlerFactory: ScopeHandlerFactory,
target: Target,
): Range {
const containingIterationScopeModifier = this.modifierStageFactory.create({
type: "containingScope",
scopeType: scopeHandler.iterationScopeType,
});
const iterationScopeHandler = scopeHandlerFactory.create(
scopeHandler.iterationScopeType,
target.editor.document.languageId,
);

if (iterationScopeHandler == null) {
throw Error("Could not find iteration scope handler");
}

const iterationScopeTarget = getContainingScopeTarget(
target,
iterationScopeHandler,
);

if (iterationScopeTarget == null) {
throw new NoContainingScopeError(
`iteration scope for ${scopeHandler.scopeType!.type}`,
);
}

return containingIterationScopeModifier.run(context, target)[0]
.contentRange;
return iterationScopeTarget.contentRange;
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,149 @@
import { Direction, Position, TextEditor } from "@cursorless/common";
import type { Target } from "../../typings/target.types";
import { constructScopeRangeTarget } from "./constructScopeRangeTarget";
import { getContainingScope } from "./getContainingScope";
import { TargetScope } from "./scopeHandlers/scope.types";
import { ScopeHandler } from "./scopeHandlers/scopeHandler.types";

/**
* Finds the containing scope of the target for the given scope handler
*
* @param target The target to find the containing scope of
* @param scopeHandler The scope handler for the scope type to find containing scope of
* @param ancestorIndex How many ancestors to go up. 0 means the immediate containing scope
* @returns A target representing the containing scope, or undefined if no containing scope found
*/
export function getContainingScopeTarget(
Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Factored out the core implementation of ContainingScopeStage so that EveryScopeStage could use it directly for expanding to containing iteration scope, because iteration scopes don't necessarily have a well-defined scope type, so it's easier if it can just pass in a scope handler rather than making a dummy scope type just for this purpose

target: Target,
scopeHandler: ScopeHandler,
ancestorIndex: number = 0,
): Target | undefined {
const {
isReversed,
editor,
contentRange: { start, end },
} = target;

if (end.isEqual(start)) {
// Input target is empty; return the preferred scope touching target
let scope = getPreferredScopeTouchingPosition(scopeHandler, editor, start);

if (scope == null) {
return undefined;
}

if (ancestorIndex > 0) {
scope = expandFromPosition(
scopeHandler,
editor,
scope.domain.end,
"forward",
ancestorIndex - 1,
);
}

if (scope == null) {
return undefined;
}

return scope.getTarget(isReversed);
}

const startScope = expandFromPosition(
scopeHandler,
editor,
start,
"forward",
ancestorIndex,
);

if (startScope == null) {
return undefined;
}

if (startScope.domain.contains(end)) {
return startScope.getTarget(isReversed);
}

const endScope = expandFromPosition(
scopeHandler,
editor,
end,
"backward",
ancestorIndex,
);

if (endScope == null) {
return undefined;
}

return constructScopeRangeTarget(isReversed, startScope, endScope);
}

function expandFromPosition(
scopeHandler: ScopeHandler,
editor: TextEditor,
position: Position,
direction: Direction,
ancestorIndex: number,
): TargetScope | undefined {
let nextAncestorIndex = 0;
for (const scope of scopeHandler.generateScopes(editor, position, direction, {
containment: "required",
})) {
if (nextAncestorIndex === ancestorIndex) {
return scope;
}

// Because containment is required, and we are moving in a consistent
// direction (ie forward or backward), each scope will be progressively
// larger
nextAncestorIndex += 1;
}

return undefined;
}

function getPreferredScopeTouchingPosition(
scopeHandler: ScopeHandler,
editor: TextEditor,
position: Position,
): TargetScope | undefined {
const forwardScope = getContainingScope(
scopeHandler,
editor,
position,
"forward",
);

if (forwardScope == null) {
return getContainingScope(scopeHandler, editor, position, "backward");
}

if (
scopeHandler.isPreferredOver == null ||
forwardScope.domain.start.isBefore(position)
) {
return forwardScope;
}

const backwardScope = getContainingScope(
scopeHandler,
editor,
position,
"backward",
);

// If there is no backward scope, or if the backward scope is an ancestor of
// forward scope, return forward scope
if (
backwardScope == null ||
backwardScope.domain.contains(forwardScope.domain)
) {
return forwardScope;
}

return scopeHandler.isPreferredOver(backwardScope, forwardScope) ?? false
? backwardScope
: forwardScope;
}
Loading