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:
Pokey Rule 2022-10-24 09:22:17 +01:00 committed by GitHub
parent a4cee9611f
commit c4d0918452
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
23 changed files with 222 additions and 296 deletions

View File

@ -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";

View File

@ -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;
}
}

View File

@ -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":

View File

@ -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;
}

View File

@ -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[] {

View File

@ -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,
}),
};
}

View File

@ -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[] {

View File

@ -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,

View File

@ -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;
}
}
}

View File

@ -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,

View File

@ -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[] {

View File

@ -10,7 +10,7 @@ export default class WordScopeHandler extends NestedScopeHandler {
private wordTokenizer = new WordTokenizer(this.languageId);
protected getScopesInIterationScope({
protected getScopesInSearchScope({
editor,
domain,
}: TargetScope): TargetScope[] {

View File

@ -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;
}

View File

@ -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";

View File

@ -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[];
}

View File

@ -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}

View File

@ -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
);
}

View File

@ -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";

View File

@ -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}

View File

@ -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,

View File

@ -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";

View File

@ -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);
}

View File

@ -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
);
}