mirror of
https://github.com/cursorless-dev/cursorless.git
synced 2024-10-05 05:17:38 +03:00
Simplify default iteration scopes; file scopeHandler (#1075)
* Started migrating scope stages into handlers * cleanup * more clean up * Tweaks * Simplify default iteration scopes; file scopeHandler * Restore "every file" test * Tweak * More tweaks * More simplification * cleanup * Disallow undefined iterationScopeType * tweak * Update src/processTargets/modifiers/scopeHandlers/DocumentScopeHandler.ts Co-authored-by: Andreas Arvidsson <andreas.arvidsson87@gmail.com>
This commit is contained in:
parent
a4cee9611f
commit
c4d0918452
@ -5,7 +5,7 @@ import { Offsets } from "../../processTargets/modifiers/surroundingPair/types";
|
||||
import isTesting from "../../testUtil/isTesting";
|
||||
import { Target } from "../../typings/target.types";
|
||||
import { Graph } from "../../typings/Types";
|
||||
import { getDocumentRange } from "../../util/range";
|
||||
import { getDocumentRange } from "../../util/rangeUtils";
|
||||
import { selectionFromRange } from "../../util/selectionUtils";
|
||||
import { Action, ActionReturnValue } from "../actions.types";
|
||||
import { constructSnippetBody } from "./constructSnippetBody";
|
||||
|
@ -1,10 +1,11 @@
|
||||
import { Range } from "vscode";
|
||||
import { NoContainingScopeError } from "../../errors";
|
||||
import type { Target } from "../../typings/target.types";
|
||||
import type { EveryScopeModifier } from "../../typings/targetDescriptor.types";
|
||||
import type { ProcessedTargetsContext } from "../../typings/Types";
|
||||
import getModifierStage from "../getModifierStage";
|
||||
import type { ModifierStage } from "../PipelineStages.types";
|
||||
import getLegacyScopeStage from "./getLegacyScopeStage";
|
||||
import { getPreferredScope, getRightScope } from "./getPreferredScope";
|
||||
import getScopeHandler from "./scopeHandlers/getScopeHandler";
|
||||
import { ScopeHandler } from "./scopeHandlers/scopeHandler.types";
|
||||
|
||||
@ -19,39 +20,27 @@ import { ScopeHandler } from "./scopeHandlers/scopeHandler.types";
|
||||
*
|
||||
* 1. If target has an explicit range, just return all targets returned from
|
||||
* {@link ScopeHandler.getScopesOverlappingRange}.
|
||||
* 2. Otherwise, get the iteration scope for the start of the input target.
|
||||
* 3. If two iteration scopes touch the start position, choose the preferred one
|
||||
* if input target has empty content range, otherwise prefer the rightmost
|
||||
* one, as that will have an overlap with the target input content range.
|
||||
* 3. If the domain of the iteration scope doesn't contain the end of the input
|
||||
* target, we error, because this situation shouldn't really happen, as
|
||||
* targets without explicit range tend to be small.
|
||||
* 4. Return all targets in the iteration scope
|
||||
* 2. Otherwise, expand to the containing instance of
|
||||
* {@link ScopeHandler.iterationScopeType}, and then return all targets
|
||||
* returned from {@link ScopeHandler.getScopesOverlappingRange} when applied
|
||||
* to the expanded target's {@link Target.contentRange}.
|
||||
*/
|
||||
export class EveryScopeStage implements ModifierStage {
|
||||
constructor(private modifier: EveryScopeModifier) {}
|
||||
|
||||
run(context: ProcessedTargetsContext, target: Target): Target[] {
|
||||
const scopeHandler = getScopeHandler(
|
||||
this.modifier.scopeType,
|
||||
target.editor.document.languageId
|
||||
);
|
||||
const { scopeType } = this.modifier;
|
||||
const { editor, isReversed } = target;
|
||||
|
||||
const scopeHandler = getScopeHandler(scopeType, editor.document.languageId);
|
||||
|
||||
if (scopeHandler == null) {
|
||||
return getLegacyScopeStage(this.modifier).run(context, target);
|
||||
}
|
||||
|
||||
return target.hasExplicitRange
|
||||
? this.handleExplicitRangeTarget(scopeHandler, target)
|
||||
: this.handleNoExplicitRangeTarget(scopeHandler, target);
|
||||
}
|
||||
|
||||
private handleExplicitRangeTarget(
|
||||
scopeHandler: ScopeHandler,
|
||||
target: Target
|
||||
): Target[] {
|
||||
const { editor, isReversed, contentRange: range } = target;
|
||||
const { scopeType } = this.modifier;
|
||||
const range = target.hasExplicitRange
|
||||
? target.contentRange
|
||||
: this.getDefaultIterationRange(context, scopeHandler, target);
|
||||
|
||||
const scopes = scopeHandler.getScopesOverlappingRange(editor, range);
|
||||
|
||||
@ -62,41 +51,17 @@ export class EveryScopeStage implements ModifierStage {
|
||||
return scopes.map((scope) => scope.getTarget(isReversed));
|
||||
}
|
||||
|
||||
private handleNoExplicitRangeTarget(
|
||||
getDefaultIterationRange(
|
||||
context: ProcessedTargetsContext,
|
||||
scopeHandler: ScopeHandler,
|
||||
target: Target
|
||||
): Target[] {
|
||||
const {
|
||||
editor,
|
||||
isReversed,
|
||||
contentRange: { start, end },
|
||||
} = target;
|
||||
const { scopeType } = this.modifier;
|
||||
): Range {
|
||||
const containingIterationScopeModifier = getModifierStage({
|
||||
type: "containingScope",
|
||||
scopeType: scopeHandler.iterationScopeType,
|
||||
});
|
||||
|
||||
const startIterationScopes =
|
||||
scopeHandler.getIterationScopesTouchingPosition(editor, start);
|
||||
|
||||
// If target is empty, use the preferred scope; otherwise use the rightmost
|
||||
// scope, as that one will have non-empty intersection with input target
|
||||
// content range
|
||||
const startIterationScope = end.isEqual(start)
|
||||
? getPreferredScope(startIterationScopes)
|
||||
: getRightScope(startIterationScopes);
|
||||
|
||||
if (startIterationScope == null) {
|
||||
throw new NoContainingScopeError(scopeType.type);
|
||||
}
|
||||
|
||||
if (!startIterationScope.domain.contains(end)) {
|
||||
// NB: This shouldn't really happen, because our weak scopes are
|
||||
// generally no bigger than a token.
|
||||
throw new Error(
|
||||
"Canonical iteration scope domain must include entire input range"
|
||||
);
|
||||
}
|
||||
|
||||
return startIterationScope
|
||||
.getScopes()
|
||||
.map((scope) => scope.getTarget(isReversed));
|
||||
return containingIterationScopeModifier.run(context, target)[0]
|
||||
.contentRange;
|
||||
}
|
||||
}
|
||||
|
@ -10,7 +10,6 @@ import ContainingSyntaxScopeStage, {
|
||||
SimpleContainingScopeModifier,
|
||||
SimpleEveryScopeModifier,
|
||||
} from "./scopeTypeStages/ContainingSyntaxScopeStage";
|
||||
import DocumentStage from "./scopeTypeStages/DocumentStage";
|
||||
import NotebookCellStage from "./scopeTypeStages/NotebookCellStage";
|
||||
import ParagraphStage from "./scopeTypeStages/ParagraphStage";
|
||||
import {
|
||||
@ -40,8 +39,6 @@ export default function getLegacyScopeStage(
|
||||
switch (modifier.scopeType.type) {
|
||||
case "notebookCell":
|
||||
return new NotebookCellStage(modifier);
|
||||
case "document":
|
||||
return new DocumentStage(modifier);
|
||||
case "paragraph":
|
||||
return new ParagraphStage(modifier);
|
||||
case "nonWhitespaceSequence":
|
||||
|
@ -1,4 +1,4 @@
|
||||
import { Scope } from "./scopeHandlers/scope.types";
|
||||
import { TargetScope } from "./scopeHandlers/scope.types";
|
||||
|
||||
/**
|
||||
* Given a list of scopes, returns the preferred scope, or `undefined` if
|
||||
@ -7,7 +7,9 @@ import { Scope } from "./scopeHandlers/scope.types";
|
||||
* @param scopes A list of scopes to choose from
|
||||
* @returns A single preferred scope, or `undefined` if {@link scopes} is empty
|
||||
*/
|
||||
export function getPreferredScope<T extends Scope>(scopes: T[]): T | undefined {
|
||||
export function getPreferredScope(
|
||||
scopes: TargetScope[]
|
||||
): TargetScope | undefined {
|
||||
return getRightScope(scopes);
|
||||
}
|
||||
|
||||
@ -17,7 +19,7 @@ export function getPreferredScope<T extends Scope>(scopes: T[]): T | undefined {
|
||||
* @param scopes A list of scopes to choose from
|
||||
* @returns A single preferred scope, or `undefined` if {@link scopes} is empty
|
||||
*/
|
||||
export function getLeftScope<T extends Scope>(scopes: T[]): T | undefined {
|
||||
export function getLeftScope(scopes: TargetScope[]): TargetScope | undefined {
|
||||
return getScopeHelper(scopes, (scope1, scope2) =>
|
||||
scope1.domain.start.isBefore(scope2.domain.start)
|
||||
);
|
||||
@ -29,16 +31,16 @@ export function getLeftScope<T extends Scope>(scopes: T[]): T | undefined {
|
||||
* @param scopes A list of scopes to choose from
|
||||
* @returns A single preferred scope, or `undefined` if {@link scopes} is empty
|
||||
*/
|
||||
export function getRightScope<T extends Scope>(scopes: T[]): T | undefined {
|
||||
export function getRightScope(scopes: TargetScope[]): TargetScope | undefined {
|
||||
return getScopeHelper(scopes, (scope1, scope2) =>
|
||||
scope1.domain.start.isAfter(scope2.domain.start)
|
||||
);
|
||||
}
|
||||
|
||||
function getScopeHelper<T extends Scope>(
|
||||
scopes: T[],
|
||||
isScope1Preferred: (scope1: Scope, scope2: Scope) => boolean
|
||||
): T | undefined {
|
||||
function getScopeHelper(
|
||||
scopes: TargetScope[],
|
||||
isScope1Preferred: (scope1: TargetScope, scope2: TargetScope) => boolean
|
||||
): TargetScope | undefined {
|
||||
if (scopes.length === 0) {
|
||||
return undefined;
|
||||
}
|
||||
|
@ -8,7 +8,7 @@ export default class CharacterScopeHandler extends NestedScopeHandler {
|
||||
public readonly scopeType = { type: "character" } as const;
|
||||
public readonly iterationScopeType = { type: "token" } as const;
|
||||
|
||||
protected getScopesInIterationScope({
|
||||
protected getScopesInSearchScope({
|
||||
editor,
|
||||
domain,
|
||||
}: TargetScope): TargetScope[] {
|
||||
|
@ -0,0 +1,59 @@
|
||||
import { Position, Range, TextEditor } from "vscode";
|
||||
import { Direction, ScopeType } from "../../../typings/targetDescriptor.types";
|
||||
import { getDocumentRange } from "../../../util/rangeUtils";
|
||||
import { DocumentTarget } from "../../targets";
|
||||
import { OutOfRangeError } from "../targetSequenceUtils";
|
||||
import NotHierarchicalScopeError from "./NotHierarchicalScopeError";
|
||||
import { TargetScope } from "./scope.types";
|
||||
import { ScopeHandler } from "./scopeHandler.types";
|
||||
|
||||
export default class DocumentScopeHandler implements ScopeHandler {
|
||||
public readonly scopeType = { type: "document" } as const;
|
||||
public readonly iterationScopeType = { type: "document" } as const;
|
||||
|
||||
constructor(_scopeType: ScopeType, _languageId: string) {
|
||||
// Empty
|
||||
}
|
||||
|
||||
getScopesTouchingPosition(
|
||||
editor: TextEditor,
|
||||
_position: Position,
|
||||
ancestorIndex: number = 0
|
||||
): TargetScope[] {
|
||||
if (ancestorIndex !== 0) {
|
||||
throw new NotHierarchicalScopeError(this.scopeType);
|
||||
}
|
||||
|
||||
return [getDocumentScope(editor)];
|
||||
}
|
||||
|
||||
getScopesOverlappingRange(editor: TextEditor, _range: Range): TargetScope[] {
|
||||
return [getDocumentScope(editor)];
|
||||
}
|
||||
|
||||
getScopeRelativeToPosition(
|
||||
_editor: TextEditor,
|
||||
_position: Position,
|
||||
_offset: number,
|
||||
_direction: Direction
|
||||
): TargetScope {
|
||||
// NB: offset will always be greater than or equal to 1, so this will be an
|
||||
// error
|
||||
throw new OutOfRangeError();
|
||||
}
|
||||
}
|
||||
|
||||
function getDocumentScope(editor: TextEditor): TargetScope {
|
||||
const contentRange = getDocumentRange(editor.document);
|
||||
|
||||
return {
|
||||
editor,
|
||||
domain: contentRange,
|
||||
getTarget: (isReversed) =>
|
||||
new DocumentTarget({
|
||||
editor,
|
||||
isReversed,
|
||||
contentRange,
|
||||
}),
|
||||
};
|
||||
}
|
@ -10,7 +10,7 @@ export default class IdentifierScopeHandler extends NestedScopeHandler {
|
||||
|
||||
private regex: RegExp = getMatcher(this.languageId).identifierMatcher;
|
||||
|
||||
protected getScopesInIterationScope({
|
||||
protected getScopesInSearchScope({
|
||||
editor,
|
||||
domain,
|
||||
}: TargetScope): TargetScope[] {
|
||||
|
@ -1,20 +1,19 @@
|
||||
import { range } from "lodash";
|
||||
import { Position, Range, TextEditor } from "vscode";
|
||||
import { Direction } from "../../../typings/targetDescriptor.types";
|
||||
import { getDocumentRange } from "../../../util/range";
|
||||
import { Direction, ScopeType } from "../../../typings/targetDescriptor.types";
|
||||
import { LineTarget } from "../../targets";
|
||||
import { OutOfRangeError } from "../targetSequenceUtils";
|
||||
import NotHierarchicalScopeError from "./NotHierarchicalScopeError";
|
||||
import type { IterationScope, TargetScope } from "./scope.types";
|
||||
import type { TargetScope } from "./scope.types";
|
||||
import type { ScopeHandler } from "./scopeHandler.types";
|
||||
|
||||
export default class LineScopeHandler implements ScopeHandler {
|
||||
public readonly scopeType = { type: "line" } as const;
|
||||
public readonly iterationScopeType = { type: "document" } as const;
|
||||
|
||||
constructor(
|
||||
public readonly scopeType: { type: "line" },
|
||||
protected languageId: string
|
||||
) {}
|
||||
constructor(_scopeType: ScopeType, _languageId: string) {
|
||||
// Empty
|
||||
}
|
||||
|
||||
getScopesTouchingPosition(
|
||||
editor: TextEditor,
|
||||
@ -37,22 +36,6 @@ export default class LineScopeHandler implements ScopeHandler {
|
||||
);
|
||||
}
|
||||
|
||||
getIterationScopesTouchingPosition(
|
||||
editor: TextEditor,
|
||||
_position: Position
|
||||
): IterationScope[] {
|
||||
return [
|
||||
{
|
||||
editor,
|
||||
domain: getDocumentRange(editor.document),
|
||||
getScopes: () =>
|
||||
range(editor.document.lineCount).map((lineNumber) =>
|
||||
lineNumberToScope(editor, lineNumber)
|
||||
),
|
||||
},
|
||||
];
|
||||
}
|
||||
|
||||
getScopeRelativeToPosition(
|
||||
editor: TextEditor,
|
||||
position: Position,
|
||||
|
@ -7,7 +7,7 @@ import type {
|
||||
import { getLeftScope, getRightScope } from "../getPreferredScope";
|
||||
import { OutOfRangeError } from "../targetSequenceUtils";
|
||||
import NotHierarchicalScopeError from "./NotHierarchicalScopeError";
|
||||
import type { IterationScope, TargetScope } from "./scope.types";
|
||||
import type { TargetScope } from "./scope.types";
|
||||
import type { ScopeHandler } from "./scopeHandler.types";
|
||||
|
||||
/**
|
||||
@ -21,34 +21,45 @@ import type { ScopeHandler } from "./scopeHandler.types";
|
||||
export default abstract class NestedScopeHandler implements ScopeHandler {
|
||||
public abstract readonly iterationScopeType: ScopeType;
|
||||
|
||||
/**
|
||||
* We expand to this scope type before looking for instances of the scope type
|
||||
* handled by this scope handler. In most cases the iteration scope will
|
||||
* suffice, but in some cases you want them to diverge. For example, you
|
||||
* might want the default iteration scope to be `"file"`, but you don't need
|
||||
* to expand to the file just to find instances of the given scope type.
|
||||
*/
|
||||
protected get searchScopeType(): ScopeType {
|
||||
return this.iterationScopeType;
|
||||
}
|
||||
|
||||
/**
|
||||
* This function is the only function that needs to be defined in the derived
|
||||
* type. It should just return a list of all child scope types in the given
|
||||
* parent scope type.
|
||||
* @param iterationScope An instance of the parent scope type from which to
|
||||
* @param searchScope An instance of the parent scope type from which to
|
||||
* return all child target scopes
|
||||
* @returns A list of all child scope types in the given parent scope type
|
||||
*/
|
||||
protected abstract getScopesInIterationScope(
|
||||
iterationScope: TargetScope
|
||||
protected abstract getScopesInSearchScope(
|
||||
searchScope: TargetScope
|
||||
): TargetScope[];
|
||||
|
||||
private _iterationScopeHandler: ScopeHandler | undefined;
|
||||
private _searchScopeHandler: ScopeHandler | undefined;
|
||||
|
||||
constructor(
|
||||
public readonly scopeType: ScopeType,
|
||||
protected languageId: string
|
||||
) {}
|
||||
|
||||
private get iterationScopeHandler(): ScopeHandler {
|
||||
if (this._iterationScopeHandler == null) {
|
||||
this._iterationScopeHandler = getScopeHandler(
|
||||
this.iterationScopeType,
|
||||
private get searchScopeHandler(): ScopeHandler {
|
||||
if (this._searchScopeHandler == null) {
|
||||
this._searchScopeHandler = getScopeHandler(
|
||||
this.searchScopeType,
|
||||
this.languageId
|
||||
)!;
|
||||
}
|
||||
|
||||
return this._iterationScopeHandler;
|
||||
return this._searchScopeHandler;
|
||||
}
|
||||
|
||||
getScopesTouchingPosition(
|
||||
@ -60,39 +71,22 @@ export default abstract class NestedScopeHandler implements ScopeHandler {
|
||||
throw new NotHierarchicalScopeError(this.scopeType);
|
||||
}
|
||||
|
||||
return this.iterationScopeHandler
|
||||
return this.searchScopeHandler
|
||||
.getScopesTouchingPosition(editor, position)
|
||||
.flatMap((iterationScope) =>
|
||||
this.getScopesInIterationScope(iterationScope)
|
||||
)
|
||||
.flatMap((searchScope) => this.getScopesInSearchScope(searchScope))
|
||||
.filter(({ domain }) => domain.contains(position));
|
||||
}
|
||||
|
||||
getScopesOverlappingRange(editor: TextEditor, range: Range): TargetScope[] {
|
||||
return this.iterationScopeHandler
|
||||
return this.searchScopeHandler
|
||||
.getScopesOverlappingRange(editor, range)
|
||||
.flatMap((iterationScope) =>
|
||||
this.getScopesInIterationScope(iterationScope)
|
||||
)
|
||||
.flatMap((searchScope) => this.getScopesInSearchScope(searchScope))
|
||||
.filter(({ domain }) => {
|
||||
const intersection = domain.intersection(range);
|
||||
return intersection != null && !intersection.isEmpty;
|
||||
});
|
||||
}
|
||||
|
||||
getIterationScopesTouchingPosition(
|
||||
editor: TextEditor,
|
||||
position: Position
|
||||
): IterationScope[] {
|
||||
return this.iterationScopeHandler
|
||||
.getScopesTouchingPosition(editor, position)
|
||||
.map((iterationScope) => ({
|
||||
domain: iterationScope.domain,
|
||||
editor,
|
||||
getScopes: () => this.getScopesInIterationScope(iterationScope),
|
||||
}));
|
||||
}
|
||||
|
||||
getScopeRelativeToPosition(
|
||||
editor: TextEditor,
|
||||
position: Position,
|
||||
@ -120,7 +114,7 @@ export default abstract class NestedScopeHandler implements ScopeHandler {
|
||||
|
||||
/**
|
||||
* Yields groups of scopes for use in {@link getScopeRelativeToPosition}.
|
||||
* Begins by returning a list of all scopes in the iteration scope containing
|
||||
* Begins by returning a list of all scopes in the search scope containing
|
||||
* {@link position} that are after {@link position} (before if
|
||||
* {@link direction} is `"backward"`).
|
||||
*
|
||||
@ -138,18 +132,18 @@ export default abstract class NestedScopeHandler implements ScopeHandler {
|
||||
position: Position,
|
||||
direction: Direction
|
||||
): Generator<TargetScope[], void, unknown> {
|
||||
const containingIterationScopes =
|
||||
this.iterationScopeHandler.getScopesTouchingPosition(editor, position);
|
||||
const containingSearchScopes =
|
||||
this.searchScopeHandler.getScopesTouchingPosition(editor, position);
|
||||
|
||||
const containingIterationScope =
|
||||
const containingSearchScope =
|
||||
direction === "forward"
|
||||
? getRightScope(containingIterationScopes)
|
||||
: getLeftScope(containingIterationScopes);
|
||||
? getRightScope(containingSearchScopes)
|
||||
: getLeftScope(containingSearchScopes);
|
||||
|
||||
let currentPosition = position;
|
||||
|
||||
if (containingIterationScope != null) {
|
||||
yield this.getScopesInIterationScope(containingIterationScope).filter(
|
||||
if (containingSearchScope != null) {
|
||||
yield this.getScopesInSearchScope(containingSearchScope).filter(
|
||||
({ domain }) =>
|
||||
direction === "forward"
|
||||
? domain.start.isAfterOrEqual(position)
|
||||
@ -157,11 +151,11 @@ export default abstract class NestedScopeHandler implements ScopeHandler {
|
||||
);
|
||||
|
||||
// Move current position past containing scope so that asking for next
|
||||
// parent iteration scope won't just give us back the same on
|
||||
// parent search scope won't just give us back the same on
|
||||
currentPosition =
|
||||
direction === "forward"
|
||||
? containingIterationScope.domain.end
|
||||
: containingIterationScope.domain.start;
|
||||
? containingSearchScope.domain.end
|
||||
: containingSearchScope.domain.start;
|
||||
}
|
||||
|
||||
while (true) {
|
||||
@ -170,22 +164,21 @@ export default abstract class NestedScopeHandler implements ScopeHandler {
|
||||
// cases it will be more efficient not to ask parent to walk past the same
|
||||
// scopes over and over again. Eg for surrounding pair this can help us.
|
||||
// For line it makes no difference.
|
||||
const iterationScope =
|
||||
this.iterationScopeHandler.getScopeRelativeToPosition(
|
||||
editor,
|
||||
currentPosition,
|
||||
1,
|
||||
direction
|
||||
);
|
||||
const searchScope = this.searchScopeHandler.getScopeRelativeToPosition(
|
||||
editor,
|
||||
currentPosition,
|
||||
1,
|
||||
direction
|
||||
);
|
||||
|
||||
yield this.getScopesInIterationScope(iterationScope);
|
||||
yield this.getScopesInSearchScope(searchScope);
|
||||
|
||||
// Move current position past the scope we just used so that asking for next
|
||||
// parent iteration scope won't just give us back the same on
|
||||
// parent search scope won't just give us back the same on
|
||||
currentPosition =
|
||||
direction === "forward"
|
||||
? iterationScope.domain.end
|
||||
: iterationScope.domain.start;
|
||||
? searchScope.domain.end
|
||||
: searchScope.domain.start;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -3,16 +3,19 @@ import {
|
||||
Direction,
|
||||
SurroundingPairScopeType,
|
||||
} from "../../../typings/targetDescriptor.types";
|
||||
import { IterationScope, TargetScope } from "./scope.types";
|
||||
import { TargetScope } from "./scope.types";
|
||||
import { ScopeHandler } from "./scopeHandler.types";
|
||||
|
||||
export default class SurroundingPairScopeHandler implements ScopeHandler {
|
||||
public readonly iterationScopeType = undefined;
|
||||
public readonly iterationScopeType;
|
||||
|
||||
constructor(
|
||||
public readonly scopeType: SurroundingPairScopeType,
|
||||
_languageId: string
|
||||
) {}
|
||||
) {
|
||||
// FIXME: Figure out the actual iteration scope type
|
||||
this.iterationScopeType = this.scopeType;
|
||||
}
|
||||
|
||||
getScopesTouchingPosition(
|
||||
_editor: TextEditor,
|
||||
@ -30,14 +33,6 @@ export default class SurroundingPairScopeHandler implements ScopeHandler {
|
||||
throw new Error("Method not implemented.");
|
||||
}
|
||||
|
||||
getIterationScopesTouchingPosition(
|
||||
_editor: TextEditor,
|
||||
_position: Position
|
||||
): IterationScope[] {
|
||||
// TODO: Return inside strict containing pair
|
||||
throw new Error("Method not implemented.");
|
||||
}
|
||||
|
||||
getScopeRelativeToPosition(
|
||||
_editor: TextEditor,
|
||||
_position: Position,
|
||||
|
@ -8,7 +8,7 @@ export default class TokenScopeHandler extends NestedScopeHandler {
|
||||
public readonly scopeType: ScopeType = { type: "token" };
|
||||
public readonly iterationScopeType: ScopeType = { type: "line" };
|
||||
|
||||
protected getScopesInIterationScope({
|
||||
protected getScopesInSearchScope({
|
||||
editor,
|
||||
domain,
|
||||
}: TargetScope): TargetScope[] {
|
||||
|
@ -10,7 +10,7 @@ export default class WordScopeHandler extends NestedScopeHandler {
|
||||
|
||||
private wordTokenizer = new WordTokenizer(this.languageId);
|
||||
|
||||
protected getScopesInIterationScope({
|
||||
protected getScopesInSearchScope({
|
||||
editor,
|
||||
domain,
|
||||
}: TargetScope): TargetScope[] {
|
||||
|
@ -1,5 +1,6 @@
|
||||
import {
|
||||
CharacterScopeHandler,
|
||||
DocumentScopeHandler,
|
||||
IdentifierScopeHandler,
|
||||
LineScopeHandler,
|
||||
TokenScopeHandler,
|
||||
@ -39,7 +40,9 @@ export default function getScopeHandler(
|
||||
case "identifier":
|
||||
return new IdentifierScopeHandler(scopeType, languageId);
|
||||
case "line":
|
||||
return new LineScopeHandler(scopeType as { type: "line" }, languageId);
|
||||
return new LineScopeHandler(scopeType, languageId);
|
||||
case "document":
|
||||
return new DocumentScopeHandler(scopeType, languageId);
|
||||
default:
|
||||
return undefined;
|
||||
}
|
||||
|
@ -10,5 +10,7 @@ export * from "./WordScopeHandler";
|
||||
export { default as WordScopeHandler } from "./WordScopeHandler";
|
||||
export * from "./TokenScopeHandler";
|
||||
export { default as TokenScopeHandler } from "./TokenScopeHandler";
|
||||
export * from "./DocumentScopeHandler";
|
||||
export { default as DocumentScopeHandler } from "./DocumentScopeHandler";
|
||||
export * from "./getScopeHandler";
|
||||
export { default as getScopeHandler } from "./getScopeHandler";
|
||||
|
@ -2,12 +2,15 @@ import type { Range, TextEditor } from "vscode";
|
||||
import type { Target } from "../../../typings/target.types";
|
||||
|
||||
/**
|
||||
* A range in the document within which a particular scope is considered the
|
||||
* canonical instance of the given scope type. We use this type both to define
|
||||
* the domain within which a target is canonical, and the domain within which an
|
||||
* iteration scope is canonical.
|
||||
* Represents a scope, which is a specific instantiation of a scope type,
|
||||
* eg a specific function, or a specific line or range of lines. Contains
|
||||
* {@link getTarget}, which represents the actual scope, as well as
|
||||
* {@link domain}, which represents the range within which the given scope is
|
||||
* canonical. For example, a scope representing the type of a parameter will
|
||||
* have the entire parameter as its domain, so that one can say "take type"
|
||||
* from anywhere within the parameter.
|
||||
*/
|
||||
export interface Scope {
|
||||
export interface TargetScope {
|
||||
/**
|
||||
* The text editor containing {@link domain}.
|
||||
*/
|
||||
@ -28,39 +31,9 @@ export interface Scope {
|
||||
* works from anywhere within the given class.
|
||||
*/
|
||||
readonly domain: Range;
|
||||
}
|
||||
|
||||
/**
|
||||
* Represents a scope, which is a specific instantiation of a scope type,
|
||||
* eg a specific function, or a specific line or range of lines. Contains
|
||||
* {@link getTarget}, which represents the actual scope, as well as
|
||||
* {@link domain}, which represents the range within which the given scope is
|
||||
* canonical. For example, a scope representing the type of a parameter will
|
||||
* have the entire parameter as its domain, so that one can say "take type"
|
||||
* from anywhere within the parameter.
|
||||
*/
|
||||
export interface TargetScope extends Scope {
|
||||
/**
|
||||
* The target corresponding to this scope.
|
||||
*/
|
||||
getTarget(isReversed: boolean): Target;
|
||||
}
|
||||
|
||||
/**
|
||||
* Represents an iteration scope, which is a domain containing one or more
|
||||
* scopes that are considered siblings for use with `"every"`. This type
|
||||
* contains {@link getScopes}, which is a list of the actual scopes, as well as
|
||||
* {@link domain}, which represents the range within which the given iteration
|
||||
* scope is canonical. For example, an iteration scope for the scope type
|
||||
* `functionOrParameter` might have a class as its domain and its targets would
|
||||
* be the functions in the class. This way one can say "take every funk" from
|
||||
* anywhere within the class.
|
||||
*/
|
||||
export interface IterationScope extends Scope {
|
||||
/**
|
||||
* The scopes in the given iteration scope. Note that each scope has its own
|
||||
* domain. We make this a function so that the scopes can be returned
|
||||
* lazily.
|
||||
*/
|
||||
getScopes(): TargetScope[];
|
||||
}
|
||||
|
@ -3,7 +3,7 @@ import type {
|
||||
Direction,
|
||||
ScopeType,
|
||||
} from "../../../typings/targetDescriptor.types";
|
||||
import type { TargetScope, IterationScope } from "./scope.types";
|
||||
import type { TargetScope } from "./scope.types";
|
||||
|
||||
/**
|
||||
* Represents a scope type. The functions in this interface allow us to find
|
||||
@ -37,16 +37,11 @@ export interface ScopeHandler {
|
||||
readonly scopeType: ScopeType;
|
||||
|
||||
/**
|
||||
* The scope type of the iteration scope of this scope type, or `undefined` if
|
||||
* there is no scope type corresponding to the iteration scope. Note that
|
||||
* even when this property is `undefined`, all scope types should have an
|
||||
* iteration scope; it just may not correspond to one of our first-class scope
|
||||
* types.
|
||||
*
|
||||
* FIXME: Revisit this; maybe we should always find a way to make the
|
||||
* iteration scope a scope type.
|
||||
* The scope type of the default iteration scope of this scope type. This
|
||||
* scope type will be used when the input target has no explicit range (ie
|
||||
* {@link Target.hasExplicitRange} is `false`).
|
||||
*/
|
||||
readonly iterationScopeType: ScopeType | undefined;
|
||||
readonly iterationScopeType: ScopeType;
|
||||
|
||||
/**
|
||||
* Return all scope(s) touching the given position. A scope is considered to
|
||||
@ -107,38 +102,6 @@ export interface ScopeHandler {
|
||||
*/
|
||||
getScopesOverlappingRange(editor: TextEditor, range: Range): TargetScope[];
|
||||
|
||||
/**
|
||||
* Returns all iteration scopes touching {@link position}. For example, if
|
||||
* scope type is `namedFunction`, and {@link position} is inside a class, the
|
||||
* iteration scope would contain a list of functions in the class. An
|
||||
* iteration scope is considered to touch a position if its domain contains
|
||||
* the position or is directly adjacent to the position. In other words,
|
||||
* return all iteration scopes for which the following is true:
|
||||
*
|
||||
* ```typescript
|
||||
* iterationScope.domain.start <= position && iterationScope.domain.end >= position
|
||||
* ```
|
||||
*
|
||||
* If the position is directly adjacent to two iteration scopes, return both.
|
||||
* If no iteration scope touches the given position, return an empty list.
|
||||
*
|
||||
* Note that if the iteration scope type is hierarchical, return only minimal
|
||||
* iteration scopes, ie if iteration scope A and iteration scope B both touch
|
||||
* {@link position}, and iteration scope A contains iteration scope B, return
|
||||
* iteration scope B but not iteration scope A.
|
||||
*
|
||||
* FIXME: We may want to remove this function and just call
|
||||
* `iterationScope.getScopesTouchingPosition`, then run
|
||||
* `getScopesOverlappingRange` on that range.
|
||||
*
|
||||
* @param editor The editor containing {@link position}
|
||||
* @param position The position from which to expand
|
||||
*/
|
||||
getIterationScopesTouchingPosition(
|
||||
editor: TextEditor,
|
||||
position: Position
|
||||
): IterationScope[];
|
||||
|
||||
/**
|
||||
* Returns a scope before or after {@link position}, depending on
|
||||
* {@link direction}. If {@link direction} is `"forward"` and {@link offset}
|
||||
|
@ -1,30 +0,0 @@
|
||||
import { Position, Range, TextEditor } from "vscode";
|
||||
import type { Target } from "../../../typings/target.types";
|
||||
import type {
|
||||
ContainingScopeModifier,
|
||||
EveryScopeModifier,
|
||||
} from "../../../typings/targetDescriptor.types";
|
||||
import type { ProcessedTargetsContext } from "../../../typings/Types";
|
||||
import type { ModifierStage } from "../../PipelineStages.types";
|
||||
import { DocumentTarget } from "../../targets";
|
||||
|
||||
export default class implements ModifierStage {
|
||||
constructor(private modifier: ContainingScopeModifier | EveryScopeModifier) {}
|
||||
|
||||
run(context: ProcessedTargetsContext, target: Target): DocumentTarget[] {
|
||||
return [
|
||||
new DocumentTarget({
|
||||
editor: target.editor,
|
||||
isReversed: target.isReversed,
|
||||
contentRange: getDocumentRange(target.editor),
|
||||
}),
|
||||
];
|
||||
}
|
||||
}
|
||||
|
||||
function getDocumentRange(editor: TextEditor) {
|
||||
return new Range(
|
||||
new Position(0, 0),
|
||||
editor.document.lineAt(editor.document.lineCount - 1).range.end
|
||||
);
|
||||
}
|
@ -5,7 +5,7 @@ import {
|
||||
SurroundingPairName,
|
||||
SurroundingPairScopeType,
|
||||
} from "../../../typings/targetDescriptor.types";
|
||||
import { getDocumentRange } from "../../../util/range";
|
||||
import { getDocumentRange } from "../../../util/rangeUtils";
|
||||
import { matchAll } from "../../../util/regex";
|
||||
import { extractSelectionFromSurroundingPairOffsets } from "./extractSelectionFromSurroundingPairOffsets";
|
||||
import { findSurroundingPairCore } from "./findSurroundingPairCore";
|
||||
|
@ -0,0 +1,22 @@
|
||||
languageId: plaintext
|
||||
command:
|
||||
spokenForm: clear next file
|
||||
version: 3
|
||||
targets:
|
||||
- type: primitive
|
||||
modifiers:
|
||||
- type: relativeScope
|
||||
scopeType: {type: document}
|
||||
offset: 1
|
||||
length: 1
|
||||
direction: forward
|
||||
usePrePhraseSnapshot: true
|
||||
action: {name: clearAndSetSelection}
|
||||
initialState:
|
||||
documentContents: aaa
|
||||
selections:
|
||||
- anchor: {line: 0, character: 0}
|
||||
active: {line: 0, character: 0}
|
||||
marks: {}
|
||||
fullTargets: [{type: primitive, mark: {type: cursor}, modifiers: [{type: relativeScope, scopeType: {type: document}, offset: 1, length: 1, direction: forward}]}]
|
||||
thrownError: {name: OutOfRangeError}
|
@ -147,35 +147,29 @@ async function runTest(file: string) {
|
||||
|
||||
disposeFakeIde();
|
||||
|
||||
if (fixture.thrownError != null) {
|
||||
throw Error(
|
||||
`Expected error ${fixture.thrownError.name} but none was thrown`
|
||||
);
|
||||
}
|
||||
|
||||
if (fixture.postCommandSleepTimeMs != null) {
|
||||
await sleepWithBackoff(fixture.postCommandSleepTimeMs);
|
||||
}
|
||||
|
||||
const marks =
|
||||
fixture.finalState!.marks == null
|
||||
fixture.finalState?.marks == null
|
||||
? undefined
|
||||
: marksToPlainObject(
|
||||
extractTargetedMarks(
|
||||
Object.keys(fixture.finalState!.marks) as string[],
|
||||
Object.keys(fixture.finalState.marks) as string[],
|
||||
readableHatMap
|
||||
)
|
||||
);
|
||||
|
||||
if (fixture.finalState!.clipboard == null) {
|
||||
if (fixture.finalState?.clipboard == null) {
|
||||
excludeFields.push("clipboard");
|
||||
}
|
||||
|
||||
if (fixture.finalState!.thatMark == null) {
|
||||
if (fixture.finalState?.thatMark == null) {
|
||||
excludeFields.push("thatMark");
|
||||
}
|
||||
|
||||
if (fixture.finalState!.sourceMark == null) {
|
||||
if (fixture.finalState?.sourceMark == null) {
|
||||
excludeFields.push("sourceMark");
|
||||
}
|
||||
|
||||
@ -208,6 +202,12 @@ async function runTest(file: string) {
|
||||
|
||||
await fsp.writeFile(file, serialize(outputFixture));
|
||||
} else {
|
||||
if (fixture.thrownError != null) {
|
||||
throw Error(
|
||||
`Expected error ${fixture.thrownError.name} but none was thrown`
|
||||
);
|
||||
}
|
||||
|
||||
assert.deepStrictEqual(
|
||||
resultState,
|
||||
fixture.finalState,
|
||||
|
@ -8,7 +8,7 @@ import HatTokenMap from "../core/HatTokenMap";
|
||||
import { injectSpyIde, SpyInfo } from "../ide/spies/SpyIDE";
|
||||
import { DecoratedSymbolMark } from "../typings/targetDescriptor.types";
|
||||
import { Graph } from "../typings/Types";
|
||||
import { getDocumentRange } from "../util/range";
|
||||
import { getDocumentRange } from "../util/rangeUtils";
|
||||
import sleep from "../util/sleep";
|
||||
import { extractTargetedMarks } from "./extractTargetedMarks";
|
||||
import serialize from "./serialize";
|
||||
|
@ -1,14 +0,0 @@
|
||||
import { Range, TextDocument } from "vscode";
|
||||
|
||||
/**
|
||||
* Get a range that corresponds to the entire contents of the given document.
|
||||
*
|
||||
* @param document The document to consider
|
||||
* @returns A range corresponding to the entire document contents
|
||||
*/
|
||||
export function getDocumentRange(document: TextDocument) {
|
||||
const firstLine = document.lineAt(0);
|
||||
const lastLine = document.lineAt(document.lineCount - 1);
|
||||
|
||||
return new Range(firstLine.range.start, lastLine.range.end);
|
||||
}
|
@ -1,4 +1,4 @@
|
||||
import { Position, Range, TextEditor } from "vscode";
|
||||
import { Position, Range, TextDocument, TextEditor } from "vscode";
|
||||
|
||||
export function isAtEndOfLine(editor: TextEditor, position: Position) {
|
||||
const endLine = editor.document.lineAt(position);
|
||||
@ -50,3 +50,16 @@ export function getRangeLength(editor: TextEditor, range: Range) {
|
||||
export function strictlyContains(range1: Range, range2: Range): boolean {
|
||||
return range1.start.isBefore(range2.start) && range1.end.isAfter(range2.end);
|
||||
}
|
||||
|
||||
/**
|
||||
* Get a range that corresponds to the entire contents of the given document.
|
||||
*
|
||||
* @param document The document to consider
|
||||
* @returns A range corresponding to the entire document contents
|
||||
*/
|
||||
export function getDocumentRange(document: TextDocument) {
|
||||
return new Range(
|
||||
new Position(0, 0),
|
||||
document.lineAt(document.lineCount - 1).range.end
|
||||
);
|
||||
}
|
||||
|
Loading…
Reference in New Issue
Block a user