Skip to content

New interface for performing edits and updating ranges #2551

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 12 commits into from
Jul 24, 2024
Merged
18 changes: 11 additions & 7 deletions packages/cursorless-engine/src/actions/BreakLine.ts
Original file line number Diff line number Diff line change
Expand Up @@ -7,7 +7,7 @@ import {
} from "@cursorless/common";
import { flatten, zip } from "lodash-es";
import type { RangeUpdater } from "../core/updateSelections/RangeUpdater";
import { performEditsAndUpdateRanges } from "../core/updateSelections/updateSelections";
import { performEditsAndUpdateSelections } from "../core/updateSelections/updateSelections";
import { ide } from "../singletons/ide.singleton";
import { Target } from "../typings/target.types";
import { flashTargets, runOnTargetsForEachEditor } from "../util/targetUtils";
Expand All @@ -25,13 +25,17 @@ export class BreakLine {
await runOnTargetsForEachEditor(targets, async (editor, targets) => {
const contentRanges = targets.map(({ contentRange }) => contentRange);
const edits = getEdits(editor, contentRanges);
const editableEditor = ide().getEditableTextEditor(editor);

const [updatedRanges] = await performEditsAndUpdateRanges(
this.rangeUpdater,
ide().getEditableTextEditor(editor),
edits,
[contentRanges],
);
const { contentRanges: updatedRanges } =
await performEditsAndUpdateSelections({
rangeUpdater: this.rangeUpdater,
editor: editableEditor,
edits,
selections: {
contentRanges,
},
});
Comment on lines +30 to +38
Copy link
Member

Choose a reason for hiding this comment

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

tempting to have shorthand

Suggested change
const { contentRanges: updatedRanges } =
await performEditsAndUpdateSelections({
rangeUpdater: this.rangeUpdater,
editor: editableEditor,
edits,
selections: {
contentRanges,
},
});
const updatedRanges =
await performEditsAndUpdateSelections({
rangeUpdater: this.rangeUpdater,
editor: editableEditor,
edits,
selections: contentRanges,
});

but that might be getting a bit too overloaded 😅

Copy link
Member Author

Choose a reason for hiding this comment

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

Yeah I think that is to much


return zip(targets, updatedRanges).map(([target, range]) => ({
editor: target!.editor,
Expand Down
91 changes: 28 additions & 63 deletions packages/cursorless-engine/src/actions/BringMoveSwap.ts
Original file line number Diff line number Diff line change
Expand Up @@ -7,10 +7,7 @@ import {
} from "@cursorless/common";
import { flatten } from "lodash-es";
import { RangeUpdater } from "../core/updateSelections/RangeUpdater";
import {
getSelectionInfo,
performEditsAndUpdateFullSelectionInfos,
} from "../core/updateSelections/updateSelections";
import { performEditsAndUpdateSelections } from "../core/updateSelections/updateSelections";
import { ide } from "../singletons/ide.singleton";
import { EditWithRangeUpdater } from "../typings/Types";
import { Destination, Target } from "../typings/target.types";
Expand Down Expand Up @@ -158,64 +155,35 @@ abstract class BringMoveSwap {
? edits
: edits.filter(({ isSource }) => !isSource);

// Sources should be closedClosed, because they should be logically
// the same as the original source.
const sourceEditSelectionInfos = sourceEdits.map(
({ edit: { range }, originalTarget }) =>
getSelectionInfo(
editor.document,
range.toSelection(originalTarget.isReversed),
RangeExpansionBehavior.closedClosed,
),
);

// Destinations should be openOpen, because they should grow to contain
// the new text.
const destinationEditSelectionInfos = destinationEdits.map(
({ edit: { range }, originalTarget }) =>
getSelectionInfo(
editor.document,
range.toSelection(originalTarget.isReversed),
RangeExpansionBehavior.openOpen,
),
);

const cursorSelectionInfos = editor.selections.map((selection) =>
getSelectionInfo(
editor.document,
selection,
RangeExpansionBehavior.closedClosed,
),
const sourceEditRanges = sourceEdits.map(({ edit }) => edit.range);
const destinationEditRanges = destinationEdits.map(
({ edit }) => edit.range,
);

const editableEditor = ide().getEditableTextEditor(editor);

const [
updatedSourceEditSelections,
updatedDestinationEditSelections,
cursorSelections,
]: Selection[][] = await performEditsAndUpdateFullSelectionInfos(
this.rangeUpdater,
editableEditor,
filteredEdits.map(({ edit }) => edit),
[
sourceEditSelectionInfos,
destinationEditSelectionInfos,
cursorSelectionInfos,
],
);

// NB: We set the selections here because we don't trust vscode to
// properly move the cursor on a bring. Sometimes it will smear an
// empty selection
await editableEditor.setSelections(cursorSelections);
const {
sourceEditRanges: updatedSourceEditRanges,
destinationEditRanges: updatedDestinationEditRanges,
} = await performEditsAndUpdateSelections({
rangeUpdater: this.rangeUpdater,
editor: editableEditor,
edits: filteredEdits.map(({ edit }) => edit),
selections: {
// Sources should be closedClosed, because they should be logically
// the same as the original source.
sourceEditRanges,
// Destinations should be openOpen, because they should grow to contain
// the new text.
destinationEditRanges: {
selections: destinationEditRanges,
behavior: RangeExpansionBehavior.openOpen,
},
},
});

const marks = [
...this.getMarks(sourceEdits, updatedSourceEditSelections),
...this.getMarks(
destinationEdits,
updatedDestinationEditSelections,
),
...this.getMarks(sourceEdits, updatedSourceEditRanges),
...this.getMarks(destinationEdits, updatedDestinationEditRanges),
];

// Restore original order before split into source and destination
Expand All @@ -231,13 +199,10 @@ abstract class BringMoveSwap {
);
}

private getMarks(
edits: ExtendedEdit[],
selections: Selection[],
): MarkEntry[] {
private getMarks(edits: ExtendedEdit[], ranges: Range[]): MarkEntry[] {
return edits.map((edit, index): MarkEntry => {
const selection = selections[index];
const range = edit.edit.updateRange(selection);
const originalRange = ranges[index];
const range = edit.edit.updateRange(originalRange);
const target = edit.originalTarget;
return {
editor: edit.editor,
Expand Down
22 changes: 14 additions & 8 deletions packages/cursorless-engine/src/actions/CallbackAction.ts
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,7 @@ import { EditableTextEditor, FlashStyle, TextEditor } from "@cursorless/common";
import { flatten } from "lodash-es";
import { selectionToStoredTarget } from "../core/commandRunner/selectionToStoredTarget";
import { RangeUpdater } from "../core/updateSelections/RangeUpdater";
import { callFunctionAndUpdateSelections } from "../core/updateSelections/updateSelections";
import { performEditsAndUpdateSelections } from "../core/updateSelections/updateSelections";
import { ide } from "../singletons/ide.singleton";
import { Target } from "../typings/target.types";
import {
Expand Down Expand Up @@ -97,13 +97,19 @@ export class CallbackAction {
});
}

const [updatedOriginalSelections, updatedTargetSelections] =
await callFunctionAndUpdateSelections(
this.rangeUpdater,
() => options.callback(editableEditor, targets),
editor.document,
[originalSelections, targetSelections],
);
const {
originalSelections: updatedOriginalSelections,
targetSelections: updatedTargetSelections,
} = await performEditsAndUpdateSelections({
rangeUpdater: this.rangeUpdater,
editor: editableEditor,
callback: () => options.callback(editableEditor, targets),
preserveCursorSelections: true,
selections: {
originalSelections,
targetSelections,
},
});

// Reset original selections
if (options.setSelection && options.restoreSelection) {
Expand Down
59 changes: 26 additions & 33 deletions packages/cursorless-engine/src/actions/EditNew/runEditTargets.ts
Original file line number Diff line number Diff line change
@@ -1,11 +1,7 @@
import {
EditableTextEditor,
RangeExpansionBehavior,
Selection,
} from "@cursorless/common";
import { EditableTextEditor, RangeExpansionBehavior } from "@cursorless/common";
import { zip } from "lodash-es";
import { RangeUpdater } from "../../core/updateSelections/RangeUpdater";
import { performEditsAndUpdateSelectionsWithBehavior } from "../../core/updateSelections/updateSelections";
import { performEditsAndUpdateSelections } from "../../core/updateSelections/updateSelections";
import { EditDestination, State } from "./EditNew.types";

/**
Expand Down Expand Up @@ -46,10 +42,6 @@ export async function runEditTargets(
destination.destination.constructChangeEdit(""),
);

const thatSelections = {
selections: state.thatRanges.map((r) => r.toSelection(false)),
};

// We need to remove undefined cursor locations. Note that these undefined
// locations will be the locations where our edit targets will go. The only
// cursor positions defined at this point will have come from command targets
Expand All @@ -59,44 +51,45 @@ export async function runEditTargets(
.filter(({ range }) => range != null);

const cursorIndices = cursorInfos.map(({ index }) => index);
const cursorRanges = cursorInfos.map(({ range }) => range!);
const editRanges = edits.map((edit) => edit.range);

const cursorSelections = {
selections: cursorInfos.map(({ range }) => range!.toSelection(false)),
};

const editSelections = {
selections: edits.map((edit) => edit.range.toSelection(false)),
rangeBehavior: RangeExpansionBehavior.openOpen,
};

const [
updatedThatSelections,
updatedCursorSelections,
updatedEditSelections,
]: Selection[][] = await performEditsAndUpdateSelectionsWithBehavior(
const {
thatRanges: updatedThatRanges,
cursorRanges: updatedCursorRanges,
editRanges: updatedEditRanges,
} = await performEditsAndUpdateSelections({
rangeUpdater,
editor,
edits,
[thatSelections, cursorSelections, editSelections],
);
preserveCursorSelections: true,
selections: {
thatRanges: state.thatRanges,
cursorRanges,
editRanges: {
selections: editRanges,
behavior: RangeExpansionBehavior.openOpen,
},
},
});

const updatedCursorRanges = [...state.cursorRanges];
const finalCursorRanges = [...state.cursorRanges];

// Update the cursor positions for the command targets
zip(cursorIndices, updatedCursorSelections).forEach(([index, selection]) => {
updatedCursorRanges[index!] = selection;
zip(cursorIndices, updatedCursorRanges).forEach(([index, range]) => {
finalCursorRanges[index!] = range;
});

// Add cursor positions for our edit targets.
destinations.forEach((delimiterTarget, index) => {
const edit = edits[index];
const range = edit.updateRange(updatedEditSelections[index]);
updatedCursorRanges[delimiterTarget.index] = range;
const range = edit.updateRange(updatedEditRanges[index]);
finalCursorRanges[delimiterTarget.index] = range;
});

return {
destinations: state.destinations,
thatRanges: updatedThatSelections,
cursorRanges: updatedCursorRanges,
thatRanges: updatedThatRanges,
cursorRanges: finalCursorRanges,
};
}
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
import { CommandCapabilities, EditableTextEditor } from "@cursorless/common";
import { RangeUpdater } from "../../core/updateSelections/RangeUpdater";
import { callFunctionAndUpdateRanges } from "../../core/updateSelections/updateSelections";
import { performEditsAndUpdateSelections } from "../../core/updateSelections/updateSelections";
import { EditDestination, State } from "./EditNew.types";

/**
Expand Down Expand Up @@ -39,27 +39,33 @@ export async function runInsertLineAfterTargets(
const contentRanges = destinations.map(
({ destination }) => destination.contentRange,
);
const targetRanges = state.destinations.map(
({ contentRange }) => contentRange,
);

const callback = async () => {
if (acceptsLocation) {
await editor.insertLineAfter(contentRanges);
} else {
await editor.setSelections(
contentRanges.map((range) => range.toSelection(false)),
);
await editor.focus();
await editor.insertLineAfter();
}
};

const [updatedTargetRanges, updatedThatRanges] =
await callFunctionAndUpdateRanges(
const { targetRanges: updatedTargetRanges, thatRanges: updatedThatRanges } =
await performEditsAndUpdateSelections({
rangeUpdater,
async () => {
if (acceptsLocation) {
await editor.insertLineAfter(contentRanges);
} else {
await editor.setSelections(
contentRanges.map((range) => range.toSelection(false)),
);
await editor.focus();
await editor.insertLineAfter();
}
editor,
callback,
preserveCursorSelections: true,
selections: {
targetRanges,
thatRanges: state.thatRanges,
},
editor.document,
[
state.destinations.map(({ contentRange }) => contentRange),
state.thatRanges,
],
);
});

// For each of the given command targets, the cursor will go where it ended
// up after running the command. We add it to the state so that any
Expand Down
Loading
Loading