Skip to content
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

Improved update selections #306

Merged
merged 48 commits into from
Nov 7, 2021
Merged
Show file tree
Hide file tree
Changes from 19 commits
Commits
Show all changes
48 commits
Select commit Hold shift + click to select a range
ea84754
Initial work
pokey Sep 11, 2021
82620c0
Initial new vesrion
pokey Sep 21, 2021
e5661eb
Tweaks
pokey Sep 22, 2021
1f64fd6
More work
pokey Sep 23, 2021
f90f0da
More implementation
pokey Sep 24, 2021
12c71b5
MOre stuff
pokey Sep 24, 2021
3b39f22
Some stuff
pokey Oct 8, 2021
8a28109
More stuff
pokey Oct 8, 2021
e73911a
Add get offsets for delete or replace
pokey Oct 13, 2021
3af3419
Fixes
pokey Oct 18, 2021
e25f7c9
Initial running version
pokey Oct 28, 2021
d3e12da
Big refactor
pokey Oct 28, 2021
758af7e
Move to graph-based approach
pokey Oct 28, 2021
a32e74b
Initial working version
pokey Oct 29, 2021
fd48593
Minor fixes
pokey Oct 29, 2021
244ce51
Working selection updater for NavigationMap
pokey Oct 31, 2021
842d51e
cleanup
pokey Oct 31, 2021
ab7d5d4
Fix CI
pokey Oct 31, 2021
99c6a04
Fix CI again
pokey Oct 31, 2021
fd837d8
Improve comments
pokey Nov 2, 2021
e925391
Cleanup
pokey Nov 2, 2021
c8b2404
Rename
pokey Nov 2, 2021
e5ad5c5
More renaming
pokey Nov 2, 2021
811023f
Navigation map disposal
pokey Nov 2, 2021
83abf91
Add documentation to graph
pokey Nov 2, 2021
538e993
More documentation
pokey Nov 2, 2021
02f4f1d
Add comments
pokey Nov 2, 2021
0c2ed1b
More comments
pokey Nov 3, 2021
c5f01ff
Fix comment
pokey Nov 3, 2021
240d99e
More doc updates
pokey Nov 3, 2021
07803b1
More cleanup
pokey Nov 3, 2021
dcf9521
Doc string
pokey Nov 3, 2021
8499d82
Improve bring; add a bunch of tests
pokey Nov 3, 2021
0bb155d
Delete useless test
pokey Nov 3, 2021
1dd8f3c
Delete another test
pokey Nov 3, 2021
33f6c4f
Add navigation map tests
pokey Nov 4, 2021
e1f94dc
Move marks to initial state
pokey Nov 4, 2021
e9e90d0
Get tests working
pokey Nov 4, 2021
96854ec
Remove transient transorm recorded tests
pokey Nov 4, 2021
12d8874
Add docs for navigation map tests
pokey Nov 4, 2021
74ce565
Add more tests
pokey Nov 4, 2021
2fd2e2d
Remove unnecessary test
pokey Nov 4, 2021
e057c59
Add comments
pokey Nov 4, 2021
801a994
Be more robust to errors in the testcase recording
pokey Nov 4, 2021
c1b4077
Test multiple inserts in one atomic edit
pokey Nov 4, 2021
c60ff42
Take note of marks that we care about
pokey Nov 5, 2021
2f591fc
Transform test cases
pokey Nov 5, 2021
46b787a
Reorder fixture fields
pokey Nov 6, 2021
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
27 changes: 20 additions & 7 deletions src/actions/BringMoveSwap.ts
Original file line number Diff line number Diff line change
Expand Up @@ -11,9 +11,13 @@ import update from "immutability-helper";
import displayPendingEditDecorations from "../util/editDisplayUtils";
import { performOutsideAdjustment } from "../util/performInsideOutsideAdjustment";
import { flatten, zip } from "lodash";
import { Selection, TextEditor, Range } from "vscode";
import { performEditsAndUpdateSelections } from "../util/updateSelections";
import { Selection, TextEditor, Range, DecorationRangeBehavior } from "vscode";

import { getTextWithPossibleDelimiter } from "../util/getTextWithPossibleDelimiter";
import {
getSelectionInfo,
performEditsAndUpdateFullSelectionInfos,
} from "../core/updateSelections/updateSelections";

type ActionType = "bring" | "move" | "swap";

Expand Down Expand Up @@ -104,7 +108,6 @@ class BringMoveSwap implements Action {
editor: destination.selection.editor,
originalSelection: destination,
isSource: false,
extendOnEqualEmptyRange: true,
},
];

Expand Down Expand Up @@ -134,7 +137,6 @@ class BringMoveSwap implements Action {
editor: source.selection.editor,
originalSelection: source,
isSource: true,
extendOnEqualEmptyRange: true,
});
}

Expand All @@ -156,10 +158,21 @@ class BringMoveSwap implements Action {
? edits
: edits.filter(({ isSource }) => !isSource);

const selectionInfos = edits.map(({ originalSelection }) =>
getSelectionInfo(
editor.document,
originalSelection.selection.selection,
DecorationRangeBehavior.OpenOpen
Copy link
Member Author

Choose a reason for hiding this comment

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

Just specify here that the target range is open on both ends and it will then automatically expand to include inserted text

)
);

const [updatedSelections]: Selection[][] =
await performEditsAndUpdateSelections(editor, filteredEdits, [
edits.map((edit) => edit.originalSelection.selection.selection),
]);
await performEditsAndUpdateFullSelectionInfos(
this.graph.selectionUpdater,
editor,
filteredEdits,
[selectionInfos]
);

return edits.map((edit, index) => {
const selection = updatedSelections[index];
Expand Down
10 changes: 7 additions & 3 deletions src/actions/CommandAction.ts
Original file line number Diff line number Diff line change
Expand Up @@ -14,11 +14,14 @@ import {
setSelectionsAndFocusEditor,
} from "../util/setSelectionsAndFocusEditor";
import { flatten } from "lodash";
import { callFunctionAndUpdateSelections } from "../util/updateSelections";

import { ensureSingleEditor } from "../util/targetUtils";
import { callFunctionAndUpdateSelections } from "../core/updateSelections/updateSelections";

export default class CommandAction implements Action {
getTargetPreferences: () => ActionPreferences[] = () => [{ insideOutsideType: "inside" }];
getTargetPreferences: () => ActionPreferences[] = () => [
{ insideOutsideType: "inside" },
];
private ensureSingleEditor: boolean;

constructor(
Expand Down Expand Up @@ -46,8 +49,9 @@ export default class CommandAction implements Action {

const [updatedOriginalSelections, updatedTargetSelections] =
await callFunctionAndUpdateSelections(
this.graph.selectionUpdater,
() => commands.executeCommand(this.command),
editor,
editor.document,
[originalSelections, targetSelections]
);

Expand Down
19 changes: 13 additions & 6 deletions src/actions/CopyLines.ts
Original file line number Diff line number Diff line change
Expand Up @@ -6,15 +6,17 @@ import {
TypedSelection,
} from "../typings/Types";
import { Range, Selection, TextEditor } from "vscode";
import { performEditsAndUpdateSelections } from "../util/updateSelections";
import { displayPendingEditDecorationsForSelection } from "../util/editDisplayUtils";
import { runOnTargetsForEachEditor } from "../util/targetUtils";
import { flatten } from "lodash";
import unifyRanges from "../util/unifyRanges";
import expandToContainingLine from "../util/expandToContainingLine";
import { performEditsAndUpdateSelections } from "../core/updateSelections/updateSelections";

class CopyLines implements Action {
getTargetPreferences: () => ActionPreferences[] = () => [{ insideOutsideType: "inside" }];
getTargetPreferences: () => ActionPreferences[] = () => [
{ insideOutsideType: "inside" },
];

constructor(private graph: Graph, private isUp: boolean) {
this.run = this.run.bind(this);
Expand Down Expand Up @@ -63,10 +65,15 @@ class CopyLines implements Action {
const edits = this.getEdits(editor, ranges);

const [updatedSelections, copySelections] =
await performEditsAndUpdateSelections(editor, edits, [
targets.map((target) => target.selection.selection),
ranges.map(({ range }) => new Selection(range.start, range.end)),
]);
await performEditsAndUpdateSelections(
this.graph.selectionUpdater,
editor,
edits,
[
targets.map((target) => target.selection.selection),
ranges.map(({ range }) => new Selection(range.start, range.end)),
]
);

editor.revealRange(updatedSelections[0]);

Expand Down
7 changes: 5 additions & 2 deletions src/actions/Delete.ts
Original file line number Diff line number Diff line change
Expand Up @@ -8,10 +8,12 @@ import {
import { runOnTargetsForEachEditor } from "../util/targetUtils";
import displayPendingEditDecorations from "../util/editDisplayUtils";
import { flatten } from "lodash";
import { performEditsAndUpdateSelections } from "../util/updateSelections";
import { performEditsAndUpdateSelections } from "../core/updateSelections/updateSelections";

export default class Delete implements Action {
getTargetPreferences: () => ActionPreferences[] = () => [{ insideOutsideType: "outside" }];
getTargetPreferences: () => ActionPreferences[] = () => [
{ insideOutsideType: "outside" },
];

constructor(private graph: Graph) {
this.run = this.run.bind(this);
Expand All @@ -36,6 +38,7 @@ export default class Delete implements Action {
}));

const [updatedSelections] = await performEditsAndUpdateSelections(
this.graph.selectionUpdater,
editor,
edits,
[targets.map((target) => target.selection.selection)]
Expand Down
21 changes: 14 additions & 7 deletions src/actions/InsertEmptyLines.ts
Original file line number Diff line number Diff line change
Expand Up @@ -8,11 +8,13 @@ import {
import { Selection, Range } from "vscode";
import { displayPendingEditDecorationsForSelection } from "../util/editDisplayUtils";
import { runOnTargetsForEachEditor } from "../util/targetUtils";
import { performEditsAndUpdateSelections } from "../util/updateSelections";
import { flatten } from "lodash";
import { performEditsAndUpdateSelections } from "../core/updateSelections/updateSelections";

class InsertEmptyLines implements Action {
getTargetPreferences: () => ActionPreferences[] = () => [{ insideOutsideType: "inside" }];
getTargetPreferences: () => ActionPreferences[] = () => [
{ insideOutsideType: "inside" },
];

constructor(
private graph: Graph,
Expand Down Expand Up @@ -53,11 +55,16 @@ class InsertEmptyLines implements Action {
const edits = this.getEdits(ranges);

const [updatedSelections, lineSelections, updatedOriginalSelections] =
await performEditsAndUpdateSelections(editor, edits, [
targets.map((target) => target.selection.selection),
ranges.map((range) => new Selection(range.start, range.end)),
editor.selections,
]);
await performEditsAndUpdateSelections(
this.graph.selectionUpdater,
editor,
edits,
[
targets.map((target) => target.selection.selection),
ranges.map((range) => new Selection(range.start, range.end)),
editor.selections,
]
);

editor.selections = updatedOriginalSelections;

Expand Down
7 changes: 5 additions & 2 deletions src/actions/Replace.ts
Original file line number Diff line number Diff line change
Expand Up @@ -9,12 +9,14 @@ import displayPendingEditDecorations from "../util/editDisplayUtils";
import { runForEachEditor } from "../util/targetUtils";
import { flatten, zip } from "lodash";
import { maybeAddDelimiter } from "../util/getTextWithPossibleDelimiter";
import { performEditsAndUpdateSelections } from "../util/updateSelections";
import { performEditsAndUpdateSelections } from "../core/updateSelections/updateSelections";

type RangeGenerator = { start: number };

export default class implements Action {
getTargetPreferences: () => ActionPreferences[] = () => [{ insideOutsideType: null }];
getTargetPreferences: () => ActionPreferences[] = () => [
{ insideOutsideType: null },
];

constructor(private graph: Graph) {
this.run = this.run.bind(this);
Expand Down Expand Up @@ -65,6 +67,7 @@ export default class implements Action {
(edit) => edit.editor,
async (editor, edits) => {
const [updatedSelections] = await performEditsAndUpdateSelections(
this.graph.selectionUpdater,
editor,
edits,
[targets.map((target) => target.selection.selection)]
Expand Down
112 changes: 71 additions & 41 deletions src/actions/Wrap.ts
Original file line number Diff line number Diff line change
@@ -1,17 +1,21 @@
import { Selection } from "vscode";
import { DecorationRangeBehavior, Selection } from "vscode";
import { flatten } from "lodash";
import {
Action,
ActionPreferences,
ActionReturnValue,
Edit,
Graph,
SelectionWithEditor,
TypedSelection,
} from "../typings/Types";
import { runOnTargetsForEachEditor } from "../util/targetUtils";
import { decorationSleep } from "../util/editDisplayUtils";
import { performEditsAndUpdateSelections } from "../util/updateSelections";
import { selectionWithEditorFromPositions } from "../util/selectionUtils";
import { FullSelectionInfo } from "../typings/updateSelections";
import {
getSelectionInfo,
performEditsAndUpdateFullSelectionInfos,
} from "../core/updateSelections/updateSelections";

export default class Wrap implements Action {
getTargetPreferences: () => ActionPreferences[] = () => [
Expand All @@ -31,62 +35,88 @@ export default class Wrap implements Action {
await runOnTargetsForEachEditor<SelectionWithEditor[]>(
targets,
async (editor, targets) => {
const edits = targets.flatMap((target) => [
const { document } = editor;
const boundaries = targets.map((target) => ({
start: new Selection(
target.selection.selection.start,
target.selection.selection.start
),
end: new Selection(
target.selection.selection.end,
target.selection.selection.end
),
}));

const edits: Edit[] = boundaries.flatMap(({ start, end }) => [
{
text: left,
range: new Selection(
target.selection.selection.start,
target.selection.selection.start
),
range: start,
},
{
text: right,
dontMoveOnEqualStart: true,
range: new Selection(
target.selection.selection.end,
target.selection.selection.end
),
range: end,
isReplace: true,
Copy link
Member Author

Choose a reason for hiding this comment

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

We use isReplace to signal that it shouldn't push empty selections to the right

},
]);

const [updatedOriginalSelections, updatedTargetsSelections] =
await performEditsAndUpdateSelections(editor, edits, [
editor.selections,
targets.map((target) => target.selection.selection),
]);
const delimiterSelectionInfos: FullSelectionInfo[] =
Copy link
Member Author

Choose a reason for hiding this comment

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

I think this file turned out really nicely. Notice how we now just give a few ranges to the selection updater and it takes care of all the complex logic:

  • For the delimiters that we'll be highlighting, we give it an empty range on the left, that is open at the left, so that it will expand to include the left delimiter, but not the right delimiter (in the case that we're wrapping an empty range). We give it an empty range on the right that is open on the right for the right delimiter highlight

boundaries.flatMap(({ start, end }) => {
return [
getSelectionInfo(
document,
start,
DecorationRangeBehavior.OpenClosed
),
getSelectionInfo(
document,
end,
DecorationRangeBehavior.ClosedOpen
),
];
});

editor.selections = updatedOriginalSelections;
const cursorSelectionInfos = editor.selections.map((selection) =>
Copy link
Member Author

Choose a reason for hiding this comment

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

  • For the new cursor selection, we give it a closed range so it won't include delimiters

getSelectionInfo(
document,
selection,
DecorationRangeBehavior.ClosedClosed
)
);

const updatedSelections = updatedTargetsSelections.flatMap(
({ start, end }) => [
new Selection(
start.translate({ characterDelta: -left.length }),
start
),
new Selection(
end,
end.translate({ characterDelta: right.length })
),
]
const thatMarkSelectionInfos = targets.map(
Copy link
Member Author

Choose a reason for hiding this comment

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

For the that mark we give it same range as cursor, but make it open so that it will include both delimiters

({ selection: { selection } }) =>
getSelectionInfo(
document,
selection,
DecorationRangeBehavior.OpenOpen
)
);

const [delimiterSelections, cursorSelections, thatMarkSelections] =
await performEditsAndUpdateFullSelectionInfos(
this.graph.selectionUpdater,
editor,
edits,
[
delimiterSelectionInfos,
cursorSelectionInfos,
thatMarkSelectionInfos,
]
);

editor.selections = cursorSelections;

editor.setDecorations(
this.graph.editStyles.justAdded.token,
updatedSelections
delimiterSelections
);
await decorationSleep();

editor.setDecorations(this.graph.editStyles.justAdded.token, []);

return targets.map((target, index) => {
const start = updatedSelections[index * 2].start;
const end = updatedSelections[index * 2 + 1].end;
return selectionWithEditorFromPositions(
target.selection,
start,
end
);
});
return thatMarkSelections.map((selection) => ({
editor,
selection,
}));
}
)
);
Expand Down
5 changes: 3 additions & 2 deletions src/actions/WrapWithSnippet.ts
Original file line number Diff line number Diff line change
@@ -1,4 +1,5 @@
import { commands } from "vscode";
import { callFunctionAndUpdateSelections } from "../core/updateSelections/updateSelections";
import { SnippetDefinition } from "../typings/snippet";
import {
Action,
Expand All @@ -9,7 +10,6 @@ import {
} from "../typings/Types";
import displayPendingEditDecorations from "../util/editDisplayUtils";
import { ensureSingleEditor } from "../util/targetUtils";
import { callFunctionAndUpdateSelections } from "../util/updateSelections";
import {
Placeholder,
SnippetParser,
Expand Down Expand Up @@ -98,11 +98,12 @@ export default class WrapWithSnippet implements Action {
// NB: We used the command "editor.action.insertSnippet" instead of calling editor.insertSnippet
// because the latter doesn't support special variables like CLIPBOARD
const [updatedTargetSelections] = await callFunctionAndUpdateSelections(
this.graph.selectionUpdater,
() =>
commands.executeCommand("editor.action.insertSnippet", {
snippet: snippetString,
}),
editor,
editor.document,
[targetSelections]
);

Expand Down
Loading