Skip to content

Commit f53a13f

Browse files
committed
Make most multi cursor work, tons of tests changes
1 parent 767c3c9 commit f53a13f

File tree

15 files changed

+1823
-1290
lines changed

15 files changed

+1823
-1290
lines changed

src/calva-fmt/src/providers/ontype_formatter.ts

Lines changed: 4 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -7,7 +7,7 @@ import { getConfig } from '../../../config';
77
import * as util from '../../../utilities';
88

99
export class FormatOnTypeEditProvider implements vscode.OnTypeFormattingEditProvider {
10-
async provideOnTypeFormattingEdits(
10+
provideOnTypeFormattingEdits(
1111
document: vscode.TextDocument,
1212
_position: vscode.Position,
1313
ch: string,
@@ -22,10 +22,11 @@ export class FormatOnTypeEditProvider implements vscode.OnTypeFormattingEditProv
2222
if (tokenCursor.withinComment()) {
2323
return undefined;
2424
}
25-
return paredit.backspace(mDoc).then((fulfilled) => {
26-
paredit.close(mDoc, ch);
25+
void paredit.backspace(mDoc).then((fulfilled) => {
26+
void paredit.close(mDoc, ch);
2727
return undefined;
2828
});
29+
return;
2930
} else {
3031
return undefined;
3132
}

src/cursor-doc/cursor-context.ts

Lines changed: 4 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -15,7 +15,7 @@ export type CursorContext = typeof allCursorContexts[number];
1515
* Returns true if documentOffset is either at the first char of the token under the cursor, or
1616
* in the whitespace between the token and the first preceding EOL, otherwise false
1717
*/
18-
export function isAtLineStartInclWS(doc: EditableDocument, offset = doc.selection.active) {
18+
export function isAtLineStartInclWS(doc: EditableDocument, offset = doc.selections[0].active) {
1919
const tokenCursor = doc.getTokenCursor(offset);
2020
let startOfLine = false;
2121
// only at start if we're in ws, or at the 1st char of a non-ws sexp
@@ -33,7 +33,7 @@ export function isAtLineStartInclWS(doc: EditableDocument, offset = doc.selectio
3333
* Returns true if position is after the last char of the last lisp token on the line, including
3434
* any trailing whitespace or EOL, otherwise false
3535
*/
36-
export function isAtLineEndInclWS(doc: EditableDocument, offset = doc.selection.active) {
36+
export function isAtLineEndInclWS(doc: EditableDocument, offset = doc.selections[0].active) {
3737
const tokenCursor = doc.getTokenCursor(offset);
3838
if (tokenCursor.getToken().type === 'eol') {
3939
return true;
@@ -56,9 +56,10 @@ export function isAtLineEndInclWS(doc: EditableDocument, offset = doc.selection.
5656
return false;
5757
}
5858

59+
// TODO: setting the vscode ext context for cursor context might not work for multi-cursor
5960
export function determineContexts(
6061
doc: EditableDocument,
61-
offset = doc.selection.active
62+
offset = doc.selections[0].active
6263
): CursorContext[] {
6364
const tokenCursor = doc.getTokenCursor(offset);
6465
const contexts: CursorContext[] = [];

src/cursor-doc/cursor-doc-utils.ts

Lines changed: 20 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,20 @@
1+
import { EditableDocument, ModelEditSelection } from './model';
2+
3+
export function selectionToRange(
4+
selection: ModelEditSelection,
5+
assumeDirection: 'ltr' | 'rtl' = undefined
6+
) {
7+
const { anchor, active } = selection;
8+
switch (assumeDirection) {
9+
case 'ltr':
10+
return [anchor, active];
11+
case 'rtl':
12+
return [active, anchor];
13+
case undefined:
14+
default: {
15+
const start = 'start' in selection ? selection.start : Math.min(anchor, active);
16+
const end = 'end' in selection ? selection.end : Math.max(anchor, active);
17+
return [start, end];
18+
}
19+
}
20+
}

src/cursor-doc/model.ts

Lines changed: 146 additions & 36 deletions
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,8 @@
1-
import { isUndefined, max, min } from 'lodash';
1+
import { isUndefined, max, min, isNumber } from 'lodash';
22
import { deepEqual as equal } from '../util/object';
33
import { Scanner, ScannerState, Token } from './clojure-lexer';
44
import { LispTokenCursor } from './token-cursor';
5+
import type { Selection, TextDocument } from 'vscode';
56

67
let scanner: Scanner;
78

@@ -45,26 +46,59 @@ export class ModelEdit {
4546
*
4647
* This will be in line with vscode when it comes to anchor/active, but introduce our own terminology for the span of the selection. It will also keep the tradition of paredit with backward/forward and up/down.
4748
*/
48-
4949
export class ModelEditSelection {
5050
private _anchor: number;
5151
private _active: number;
52-
53-
constructor(anchor: number, active?: number) {
54-
this._anchor = anchor;
55-
if (active !== undefined) {
56-
this._active = active;
52+
private _start: number;
53+
private _end: number;
54+
private _isReversed: boolean;
55+
56+
constructor(anchor: number, active?: number, start?: number, end?: number, isReversed?: boolean);
57+
constructor(selection: Selection, doc: TextDocument);
58+
constructor(
59+
anchorOrSelection: number | Selection,
60+
activeOrDoc?: number | TextDocument,
61+
start?: number,
62+
end?: number,
63+
isReversed?: boolean
64+
) {
65+
if (isNumber(anchorOrSelection)) {
66+
const anchor = anchorOrSelection;
67+
this._anchor = anchor;
68+
if (activeOrDoc !== undefined && isNumber(activeOrDoc)) {
69+
this._active = activeOrDoc;
70+
} else {
71+
this._active = anchor;
72+
}
73+
isReversed = isReversed ?? this._anchor > this._active;
74+
this._isReversed = isReversed;
75+
this._start = start ?? isReversed ? this._active : Math.min(anchor, this._active);
76+
this._end = end ?? isReversed ? anchor : Math.max(anchor, this._active);
5777
} else {
58-
this._active = anchor;
78+
const { active, anchor, start, end, isReversed } = anchorOrSelection;
79+
// const doc = getActiveTextEditor().document;
80+
const doc = activeOrDoc as TextDocument;
81+
this._active = doc.offsetAt(active);
82+
this._anchor = doc.offsetAt(anchor);
83+
this._start = doc.offsetAt(start);
84+
this._end = doc.offsetAt(end);
85+
this._isReversed = isReversed;
5986
}
6087
}
6188

89+
private _updateDirection() {
90+
this._start = Math.min(this._anchor, this._active);
91+
this._end = Math.max(this._anchor, this._active);
92+
this._isReversed = this._active < this._anchor;
93+
}
94+
6295
get anchor() {
6396
return this._anchor;
6497
}
6598

6699
set anchor(v: number) {
67100
this._anchor = v;
101+
this._updateDirection();
68102
}
69103

70104
get active() {
@@ -73,6 +107,67 @@ export class ModelEditSelection {
73107

74108
set active(v: number) {
75109
this._active = v;
110+
this._updateDirection();
111+
}
112+
113+
get start() {
114+
this._updateDirection();
115+
return this._start;
116+
}
117+
118+
/* set start(v: number) {
119+
// TODO: figure out .start setter logic
120+
this._start = v;
121+
if (this._start === this._anchor) {
122+
this._isReversed = false;
123+
} else if (this._start === this._active) {
124+
this._isReversed = true;
125+
} else if (this._isReversed) {
126+
this._active = this._start;
127+
} else if (!this._isReversed) {
128+
this._anchor = this._start;
129+
}
130+
} */
131+
132+
get end() {
133+
this._updateDirection();
134+
return this._end;
135+
}
136+
137+
/* set end(v: number) {
138+
// TODO: figure out .end setter logic
139+
// TODO: figure out .start setter logic
140+
this.end = v;
141+
142+
if (this._end < this._start) {
143+
this._start;
144+
}
145+
146+
if (this.end === this._anchor) {
147+
this._isReversed = true;
148+
} else if (this.end === this._active) {
149+
this._isReversed = false;
150+
} else if (this._isReversed) {
151+
this._anchor = this.end;
152+
} else if (!this._isReversed) {
153+
this._active = this.end;
154+
}
155+
} */
156+
157+
get isReversed() {
158+
this._updateDirection();
159+
return this._isReversed;
160+
}
161+
162+
set isReversed(isReversed: boolean) {
163+
this._isReversed = isReversed;
164+
if (this._isReversed) {
165+
this._start = this._active;
166+
this._end = this._anchor;
167+
} else {
168+
this._start = this._anchor;
169+
this._end = this._active;
170+
}
76171
}
77172

78173
clone() {
@@ -88,6 +183,11 @@ export type ModelEditOptions = {
88183
selections?: ModelEditSelection[];
89184
};
90185

186+
export type ModelEditResult = {
187+
edits: ModelEdit[];
188+
selections: ModelEditSelection[];
189+
success: boolean;
190+
};
91191
export interface EditableModel {
92192
readonly lineEndingLength: number;
93193

@@ -96,7 +196,7 @@ export interface EditableModel {
96196
* For some EditableModel's these are performed as one atomic set of edits.
97197
* @param edits
98198
*/
99-
edit: (edits: ModelEdit[], options: ModelEditOptions) => Thenable<boolean>;
199+
edit: (edits: ModelEdit[], options: ModelEditOptions) => Thenable<ModelEditResult>;
100200

101201
getText: (start: number, end: number, mustBeWithin?: boolean) => string;
102202
getLineText: (line: number) => string;
@@ -105,10 +205,8 @@ export interface EditableModel {
105205
}
106206

107207
export interface EditableDocument {
108-
selection: ModelEditSelection;
109208
selections: ModelEditSelection[];
110209
model: EditableModel;
111-
// selectionStack: ModelEditSelection[];
112210
/**
113211
* A stack of selections - that is, a 2d array, where the outer array index is a point in "selection/form nesting order" and the inner array index is which cursor that ModelEditSelection belongs to. That "selection/form nesting order" axis can be thought of as the axis for time, or something close to that. That is, .selectionStacks
114212
* is only used when the user invokes the "Expand Selection" or "Shrink Selection" Paredit commands, such that each time the user invokes "Expand", it pushes an item onto the stack. Similarly, when "Shrink" is invoked, the last item
@@ -124,10 +222,10 @@ export interface EditableDocument {
124222
selectionsStack: ModelEditSelection[][];
125223
getTokenCursor: (offset?: number, previous?: boolean) => LispTokenCursor;
126224
insertString: (text: string) => void;
127-
getSelectionText: () => string;
128225
getSelectionTexts: () => string[];
129-
delete: () => Thenable<boolean>;
130-
backspace: () => Thenable<boolean>;
226+
getSelectionText: (index: number) => string;
227+
delete: (index?: number) => Thenable<ModelEditResult>;
228+
backspace: (index?: number) => Thenable<ModelEditResult>;
131229
}
132230

133231
/** The underlying model for the REPL readline. */
@@ -372,7 +470,7 @@ export class LineInputModel implements EditableModel {
372470
* Doesn't need to be atomic in the LineInputModel.
373471
* @param edits
374472
*/
375-
edit(edits: ModelEdit[], options: ModelEditOptions): Thenable<boolean> {
473+
edit(edits: ModelEdit[], options: ModelEditOptions): Thenable<ModelEditResult> {
376474
return new Promise((resolve, reject) => {
377475
for (const edit of edits) {
378476
switch (edit.editFn) {
@@ -396,9 +494,9 @@ export class LineInputModel implements EditableModel {
396494
}
397495
}
398496
if (this.document && options.selections) {
399-
this.document.selections = [options.selections[0]];
497+
this.document.selections = options.selections;
400498
}
401-
resolve(true);
499+
resolve({ edits, selections: options.selections, success: true });
402500
});
403501
}
404502

@@ -548,7 +646,6 @@ export class StringDocument implements EditableDocument {
548646
}
549647
}
550648

551-
selection: ModelEditSelection;
552649
selections: ModelEditSelection[];
553650

554651
model: LineInputModel = new LineInputModel(1, this);
@@ -563,30 +660,43 @@ export class StringDocument implements EditableDocument {
563660
return this.model.getTokenCursor(offset);
564661
}
565662

566-
getSelectionsText: () => string[];
567663
insertString(text: string) {
568664
this.model.insertString(0, text);
569665
}
570666

571667
getSelectionTexts: () => string[];
572-
getSelectionText: () => string;
573-
574-
delete() {
575-
return this.model.edit(
576-
[this.selection].map(({ anchor: p }) => new ModelEdit('deleteRange', [p, 1])),
577-
{
578-
selections: this.selections.map(({ anchor: p }) => new ModelEditSelection(p)),
579-
}
580-
);
668+
getSelectionText: (index: number) => string;
669+
670+
delete(index?: number) {
671+
if (isUndefined(index)) {
672+
return this.model.edit(
673+
this.selections.map(({ anchor: p }) => new ModelEdit('deleteRange', [p, 1])),
674+
{
675+
selections: this.selections.map(({ anchor: p }) => new ModelEditSelection(p)),
676+
}
677+
);
678+
} else {
679+
return this.model.edit([new ModelEdit('deleteRange', [(this.selections[index].anchor, 1)])], {
680+
selections: [new ModelEditSelection(this.selections[index].anchor)],
681+
});
682+
}
581683
}
582-
getSelectionText: () => string;
583684

584-
backspace() {
585-
return this.model.edit(
586-
[this.selection].map(({ anchor: p }) => new ModelEdit('deleteRange', [p - 1, 1])),
587-
{
588-
selections: [this.selection].map(({ anchor: p }) => new ModelEditSelection(p - 1)),
589-
}
590-
);
685+
backspace(index?: number) {
686+
if (isUndefined(index)) {
687+
return this.model.edit(
688+
this.selections.map(({ anchor: p }) => new ModelEdit('deleteRange', [p - 1, 1])),
689+
{
690+
selections: this.selections.map(({ anchor: p }) => new ModelEditSelection(p - 1)),
691+
}
692+
);
693+
} else {
694+
return this.model.edit(
695+
[new ModelEdit('deleteRange', [this.selections[index].anchor - 1, 1])],
696+
{
697+
selections: [new ModelEditSelection(this.selections[index].anchor - 1)],
698+
}
699+
);
700+
}
591701
}
592702
}

0 commit comments

Comments
 (0)