diff --git a/packages/cursorless-engine/src/actions/BringMoveSwap.ts b/packages/cursorless-engine/src/actions/BringMoveSwap.ts index 63f53fb748..982e28bf7f 100644 --- a/packages/cursorless-engine/src/actions/BringMoveSwap.ts +++ b/packages/cursorless-engine/src/actions/BringMoveSwap.ts @@ -1,4 +1,9 @@ -import type { Range, Selection, TextEditor } from "@cursorless/common"; +import type { + GeneralizedRange, + Range, + Selection, + TextEditor, +} from "@cursorless/common"; import { FlashStyle, RangeExpansionBehavior } from "@cursorless/common"; import { flatten } from "lodash-es"; import type { RangeUpdater } from "../core/updateSelections/RangeUpdater"; @@ -8,8 +13,8 @@ import type { EditWithRangeUpdater } from "../typings/Types"; import type { Destination, Target } from "../typings/target.types"; import { flashTargets, - getContentRange, runForEachEditor, + toGeneralizedRange, } from "../util/targetUtils"; import { unifyRemovalTargets } from "../util/unifyRanges"; import type { ActionReturnValue } from "./actions.types"; @@ -34,7 +39,7 @@ abstract class BringMoveSwap { protected abstract decoration: { sourceStyle: FlashStyle; destinationStyle: FlashStyle; - getSourceRangeCallback: (target: Target) => Range; + getSourceRangeCallback?: (target: Target) => GeneralizedRange; }; constructor( @@ -209,8 +214,12 @@ abstract class BringMoveSwap { } protected async decorateThatMark(thatMark: MarkEntry[]) { - const getRange = (target: Target) => - thatMark.find((t) => t.target === target)!.selection; + const getRange = (target: Target) => { + return toGeneralizedRange( + target, + thatMark.find((t) => t.target === target)!.selection, + ); + }; return Promise.all([ flashTargets( ide(), @@ -250,7 +259,6 @@ export class Bring extends BringMoveSwap { decoration = { sourceStyle: FlashStyle.referenced, destinationStyle: FlashStyle.pendingModification0, - getSourceRangeCallback: getContentRange, }; constructor(rangeUpdater: RangeUpdater) { @@ -320,7 +328,6 @@ export class Swap extends BringMoveSwap { decoration = { sourceStyle: FlashStyle.pendingModification1, destinationStyle: FlashStyle.pendingModification0, - getSourceRangeCallback: getContentRange, }; constructor(rangeUpdater: RangeUpdater) { diff --git a/packages/cursorless-engine/src/actions/CopyToClipboard.ts b/packages/cursorless-engine/src/actions/CopyToClipboard.ts index 9b5dee05e3..c16d93236d 100644 --- a/packages/cursorless-engine/src/actions/CopyToClipboard.ts +++ b/packages/cursorless-engine/src/actions/CopyToClipboard.ts @@ -29,12 +29,7 @@ export class CopyToClipboard implements SimpleAction { } if (options.showDecorations) { - await flashTargets( - ide(), - targets, - FlashStyle.referenced, - (target) => target.contentRange, - ); + await flashTargets(ide(), targets, FlashStyle.referenced); } // FIXME: We should really keep track of the number of targets from the diff --git a/packages/cursorless-engine/src/actions/CutToClipboard.ts b/packages/cursorless-engine/src/actions/CutToClipboard.ts index a5c93f114a..a9c4f6f7b9 100644 --- a/packages/cursorless-engine/src/actions/CutToClipboard.ts +++ b/packages/cursorless-engine/src/actions/CutToClipboard.ts @@ -1,14 +1,9 @@ -import type { FlashDescriptor } from "@cursorless/common"; -import { - FlashStyle, - Range, - toCharacterRange, - toLineRange, -} from "@cursorless/common"; +import type { CharacterRange, FlashDescriptor } from "@cursorless/common"; +import { FlashStyle, Range, toCharacterRange } from "@cursorless/common"; import { ide } from "../singletons/ide.singleton"; import type { Target } from "../typings/target.types"; import type { Actions } from "./Actions"; -import type { SimpleAction, ActionReturnValue } from "./actions.types"; +import type { ActionReturnValue, SimpleAction } from "./actions.types"; export class CutToClipboard implements SimpleAction { constructor(private actions: Actions) { @@ -16,42 +11,7 @@ export class CutToClipboard implements SimpleAction { } async run(targets: Target[]): Promise { - await ide().flashRanges( - targets.flatMap((target) => { - const { editor, contentRange } = target; - const removalHighlightRange = target.getRemovalHighlightRange(); - - if (target.isLine) { - return [ - { - editor, - range: toCharacterRange(contentRange), - style: FlashStyle.referenced, - }, - { - editor, - range: toLineRange(removalHighlightRange), - style: FlashStyle.pendingDelete, - }, - ]; - } - - return [ - { - editor, - range: toCharacterRange(contentRange), - style: FlashStyle.referenced, - }, - ...getOutsideOverflow(contentRange, removalHighlightRange).map( - (overflow): FlashDescriptor => ({ - editor, - range: toCharacterRange(overflow), - style: FlashStyle.pendingDelete, - }), - ), - ]; - }), - ); + await ide().flashRanges(targets.flatMap(getFlashDescriptors)); const options = { showDecorations: false }; @@ -63,8 +23,44 @@ export class CutToClipboard implements SimpleAction { } } +function getFlashDescriptors(target: Target): FlashDescriptor[] { + const { editor, contentRange } = target; + const removalHighlightRange = target.getRemovalHighlightRange(); + + const flashDescriptors: FlashDescriptor[] = [ + { + editor, + range: toCharacterRange(contentRange), + style: FlashStyle.referenced, + }, + ]; + + if (removalHighlightRange.type === "line") { + flashDescriptors.push({ + editor, + range: removalHighlightRange, + style: FlashStyle.pendingDelete, + }); + } else { + flashDescriptors.push( + ...getOutsideOverflow(contentRange, removalHighlightRange).map( + (overflow): FlashDescriptor => ({ + editor, + range: toCharacterRange(overflow), + style: FlashStyle.pendingDelete, + }), + ), + ); + } + + return flashDescriptors; +} + /** Get the possible leading and trailing overflow ranges of the outside range compared to the inside range */ -function getOutsideOverflow(insideRange: Range, outsideRange: Range): Range[] { +function getOutsideOverflow( + insideRange: Range, + outsideRange: CharacterRange, +): Range[] { const { start: insideStart, end: insideEnd } = insideRange; const { start: outsideStart, end: outsideEnd } = outsideRange; const result = []; diff --git a/packages/cursorless-engine/src/actions/Highlight.ts b/packages/cursorless-engine/src/actions/Highlight.ts index c6a27b2956..4b0c341441 100644 --- a/packages/cursorless-engine/src/actions/Highlight.ts +++ b/packages/cursorless-engine/src/actions/Highlight.ts @@ -33,7 +33,9 @@ export default class Highlight { ide().setHighlightRanges( highlightId, editor, - targets.map(toGeneralizedRange), + targets.map((target) => + toGeneralizedRange(target, target.contentRange), + ), ), ); } diff --git a/packages/cursorless-engine/src/actions/ToggleBreakpoint.ts b/packages/cursorless-engine/src/actions/ToggleBreakpoint.ts index c7c17a42cc..4bbe0d4cfb 100644 --- a/packages/cursorless-engine/src/actions/ToggleBreakpoint.ts +++ b/packages/cursorless-engine/src/actions/ToggleBreakpoint.ts @@ -25,7 +25,9 @@ export default class ToggleBreakpoint implements SimpleAction { await flashTargets(ide(), thatTargets, FlashStyle.referenced); await runOnTargetsForEachEditor(targets, async (editor, targets) => { - const generalizedRanges = targets.map(toGeneralizedRange); + const generalizedRanges = targets.map((target) => + toGeneralizedRange(target, target.contentRange), + ); await ide() .getEditableTextEditor(editor) diff --git a/packages/cursorless-engine/src/processTargets/targets/BaseTarget.ts b/packages/cursorless-engine/src/processTargets/targets/BaseTarget.ts index 59338d6524..3b8e5f99f8 100644 --- a/packages/cursorless-engine/src/processTargets/targets/BaseTarget.ts +++ b/packages/cursorless-engine/src/processTargets/targets/BaseTarget.ts @@ -1,5 +1,6 @@ import type { EnforceUndefined, + GeneralizedRange, InsertionMode, Range, Selection, @@ -14,6 +15,7 @@ import type { JoinAsType, Target, } from "../../typings/target.types"; +import { toGeneralizedRange } from "../../util/targetUtils"; import { DestinationImpl } from "./DestinationImpl"; import { createContinuousRange } from "./util/createContinuousRange"; @@ -97,8 +99,8 @@ export abstract class BaseTarget< }; } - getRemovalHighlightRange(): Range { - return this.getRemovalRange(); + getRemovalHighlightRange(): GeneralizedRange { + return toGeneralizedRange(this, this.getRemovalRange()); } withThatTarget(thatTarget: Target): Target { diff --git a/packages/cursorless-engine/src/processTargets/targets/BoundedParagraphTarget.ts b/packages/cursorless-engine/src/processTargets/targets/BoundedParagraphTarget.ts index 70456e0d57..24cc2ba49b 100644 --- a/packages/cursorless-engine/src/processTargets/targets/BoundedParagraphTarget.ts +++ b/packages/cursorless-engine/src/processTargets/targets/BoundedParagraphTarget.ts @@ -1,9 +1,9 @@ -import type { Range } from "@cursorless/common"; +import { toLineRange, type Range } from "@cursorless/common"; +import type { InteriorTarget, ParagraphTarget } from "."; +import { expandToFullLine } from "../../util/rangeUtils"; import type { MinimumTargetParameters } from "./BaseTarget"; import { BaseTarget } from "./BaseTarget"; import { LineTarget } from "./LineTarget"; -import { expandToFullLine } from "../../util/rangeUtils"; -import type { InteriorTarget, ParagraphTarget } from "."; interface BoundedParagraphTargetParameters extends MinimumTargetParameters { readonly paragraphTarget: ParagraphTarget; @@ -86,16 +86,20 @@ export class BoundedParagraphTarget extends BaseTarget { + if (this.startLineGap < 1 || this.endLineGap < 1) { + return this.getRemovalRange(); + } - const delimiterTarget = - this.getTrailingDelimiterTarget() ?? this.getLeadingDelimiterTarget(); + const delimiterTarget = + this.getTrailingDelimiterTarget() ?? this.getLeadingDelimiterTarget(); + + return delimiterTarget != null + ? this.fullLineContentRange.union(delimiterTarget.contentRange) + : this.fullLineContentRange; + })(); - return delimiterTarget != null - ? this.fullLineContentRange.union(delimiterTarget.contentRange) - : this.fullLineContentRange; + return toLineRange(range); } maybeCreateRichRangeTarget( diff --git a/packages/cursorless-engine/src/processTargets/targets/LineTarget.ts b/packages/cursorless-engine/src/processTargets/targets/LineTarget.ts index 29ac83f9dc..6dd5d4258a 100644 --- a/packages/cursorless-engine/src/processTargets/targets/LineTarget.ts +++ b/packages/cursorless-engine/src/processTargets/targets/LineTarget.ts @@ -1,11 +1,11 @@ import type { TextEditor } from "@cursorless/common"; -import { Position, Range } from "@cursorless/common"; +import { Position, Range, toLineRange } from "@cursorless/common"; +import { expandToFullLine } from "../../util/rangeUtils"; +import { tryConstructTarget } from "../../util/tryConstructTarget"; import type { CommonTargetParameters } from "./BaseTarget"; import { BaseTarget } from "./BaseTarget"; -import { expandToFullLine } from "../../util/rangeUtils"; import { tryConstructPlainTarget } from "./PlainTarget"; import { createContinuousLineRange } from "./util/createContinuousRange"; -import { tryConstructTarget } from "../../util/tryConstructTarget"; export class LineTarget extends BaseTarget { type = "LineTarget"; @@ -42,7 +42,9 @@ export class LineTarget extends BaseTarget { : contentRemovalRange.union(delimiterTarget.contentRange); } - getRemovalHighlightRange = () => this.fullLineContentRange; + getRemovalHighlightRange = () => { + return toLineRange(this.fullLineContentRange); + }; maybeCreateRichRangeTarget( isReversed: boolean, diff --git a/packages/cursorless-engine/src/processTargets/targets/ParagraphTarget.ts b/packages/cursorless-engine/src/processTargets/targets/ParagraphTarget.ts index 445cde7439..76c492cf5e 100644 --- a/packages/cursorless-engine/src/processTargets/targets/ParagraphTarget.ts +++ b/packages/cursorless-engine/src/processTargets/targets/ParagraphTarget.ts @@ -1,10 +1,14 @@ -import type { TextDocument, TextEditor, TextLine } from "@cursorless/common"; -import { Position, Range } from "@cursorless/common"; +import type { + GeneralizedRange, + TextDocument, + TextEditor, + TextLine, +} from "@cursorless/common"; +import { Position, Range, toLineRange } from "@cursorless/common"; +import { expandToFullLine } from "../../util/rangeUtils"; import type { CommonTargetParameters } from "./BaseTarget"; import { BaseTarget } from "./BaseTarget"; -import { LineTarget } from "./LineTarget"; -import { expandToFullLine } from "../../util/rangeUtils"; -import { constructLineTarget } from "./LineTarget"; +import { constructLineTarget, LineTarget } from "./LineTarget"; import { createContinuousLineRange } from "./util/createContinuousRange"; export class ParagraphTarget extends BaseTarget { @@ -59,13 +63,16 @@ export class ParagraphTarget extends BaseTarget { return expandToFullLine(this.editor, this.contentRange); } - getRemovalHighlightRange() { + getRemovalHighlightRange(): GeneralizedRange { const delimiterTarget = this.getTrailingDelimiterTarget() ?? this.getLeadingDelimiterTarget(); - return delimiterTarget != null - ? this.fullLineContentRange.union(delimiterTarget.contentRange) - : this.fullLineContentRange; + const range = + delimiterTarget != null + ? this.fullLineContentRange.union(delimiterTarget.contentRange) + : this.fullLineContentRange; + + return toLineRange(range); } maybeCreateRichRangeTarget( diff --git a/packages/cursorless-engine/src/scopeProviders/getTargetRanges.ts b/packages/cursorless-engine/src/scopeProviders/getTargetRanges.ts index c18c9881c5..092f79b600 100644 --- a/packages/cursorless-engine/src/scopeProviders/getTargetRanges.ts +++ b/packages/cursorless-engine/src/scopeProviders/getTargetRanges.ts @@ -1,14 +1,11 @@ import type { TargetRanges } from "@cursorless/common"; -import { toCharacterRange, toLineRange } from "@cursorless/common"; import type { Target } from "../typings/target.types"; export function getTargetRanges(target: Target): TargetRanges { return { contentRange: target.contentRange, removalRange: target.getRemovalRange(), - removalHighlightRange: target.isLine - ? toLineRange(target.getRemovalHighlightRange()) - : toCharacterRange(target.getRemovalHighlightRange()), + removalHighlightRange: target.getRemovalHighlightRange(), leadingDelimiter: getOptionalTarget(target.getLeadingDelimiterTarget()), trailingDelimiter: getOptionalTarget(target.getTrailingDelimiterTarget()), interior: target.getInterior()?.map(getTargetRanges), diff --git a/packages/cursorless-engine/src/typings/target.types.ts b/packages/cursorless-engine/src/typings/target.types.ts index 442dc99746..8e44d3da5d 100644 --- a/packages/cursorless-engine/src/typings/target.types.ts +++ b/packages/cursorless-engine/src/typings/target.types.ts @@ -7,6 +7,7 @@ import type { ModifyIfUntypedStage } from "../processTargets/modifiers/ConditionalModifierStages"; // eslint-disable-next-line @typescript-eslint/no-unused-vars, unused-imports/no-unused-imports import type { + GeneralizedRange, InsertionMode, Range, Selection, @@ -156,7 +157,7 @@ export interface Target { * we want to highlight the line that they were on when they said `"chuck * line"`, as that is logically the line they've deleted. */ - getRemovalHighlightRange(): Range; + getRemovalHighlightRange(): GeneralizedRange; withThatTarget(thatTarget: Target): Target; withContentRange(contentRange: Range): Target; diff --git a/packages/cursorless-engine/src/util/targetUtils.ts b/packages/cursorless-engine/src/util/targetUtils.ts index 2387c5db7a..c57e8d5e4b 100644 --- a/packages/cursorless-engine/src/util/targetUtils.ts +++ b/packages/cursorless-engine/src/util/targetUtils.ts @@ -1,5 +1,4 @@ import type { - FlashDescriptor, FlashStyle, GeneralizedRange, IDE, @@ -91,10 +90,6 @@ function groupForEachEditor( }); } -export function getContentRange(target: Target) { - return target.contentRange; -} - export function createThatMark( targets: Target[], ranges?: Range[], @@ -114,33 +109,28 @@ export function createThatMark( return thatMark; } -export function toGeneralizedRange(target: Target): GeneralizedRange { - const range = target.contentRange; - +export function toGeneralizedRange( + target: Target, + range: Range, +): GeneralizedRange { return target.isLine ? toLineRange(range) : toCharacterRange(range); } +function toGeneralizedContentRange(target: Target): GeneralizedRange { + return toGeneralizedRange(target, target.contentRange); +} + export function flashTargets( ide: IDE, targets: Target[], style: FlashStyle, - getRange: (target: Target) => Range | undefined = getContentRange, + getRange: (target: Target) => GeneralizedRange = toGeneralizedContentRange, ) { return ide.flashRanges( - targets - .map((target) => { - const range = getRange(target); - - if (range == null) { - return null; - } - - return { - editor: target.editor, - range: target.isLine ? toLineRange(range) : toCharacterRange(range), - style, - }; - }) - .filter((flash): flash is FlashDescriptor => flash != null), + targets.map((target) => ({ + editor: target.editor, + range: getRange(target), + style, + })), ); }