Skip to content

Commit

Permalink
Make most multi cursor work, tons of tests changes
Browse files Browse the repository at this point in the history
  • Loading branch information
riotrah committed Mar 28, 2022
1 parent 767c3c9 commit f53a13f
Show file tree
Hide file tree
Showing 15 changed files with 1,823 additions and 1,290 deletions.
7 changes: 4 additions & 3 deletions src/calva-fmt/src/providers/ontype_formatter.ts
Original file line number Diff line number Diff line change
Expand Up @@ -7,7 +7,7 @@ import { getConfig } from '../../../config';
import * as util from '../../../utilities';

export class FormatOnTypeEditProvider implements vscode.OnTypeFormattingEditProvider {
async provideOnTypeFormattingEdits(
provideOnTypeFormattingEdits(
document: vscode.TextDocument,
_position: vscode.Position,
ch: string,
Expand All @@ -22,10 +22,11 @@ export class FormatOnTypeEditProvider implements vscode.OnTypeFormattingEditProv
if (tokenCursor.withinComment()) {
return undefined;
}
return paredit.backspace(mDoc).then((fulfilled) => {
paredit.close(mDoc, ch);
void paredit.backspace(mDoc).then((fulfilled) => {
void paredit.close(mDoc, ch);
return undefined;
});
return;
} else {
return undefined;
}
Expand Down
7 changes: 4 additions & 3 deletions src/cursor-doc/cursor-context.ts
Original file line number Diff line number Diff line change
Expand Up @@ -15,7 +15,7 @@ export type CursorContext = typeof allCursorContexts[number];
* Returns true if documentOffset is either at the first char of the token under the cursor, or
* in the whitespace between the token and the first preceding EOL, otherwise false
*/
export function isAtLineStartInclWS(doc: EditableDocument, offset = doc.selection.active) {
export function isAtLineStartInclWS(doc: EditableDocument, offset = doc.selections[0].active) {
const tokenCursor = doc.getTokenCursor(offset);
let startOfLine = false;
// only at start if we're in ws, or at the 1st char of a non-ws sexp
Expand All @@ -33,7 +33,7 @@ export function isAtLineStartInclWS(doc: EditableDocument, offset = doc.selectio
* Returns true if position is after the last char of the last lisp token on the line, including
* any trailing whitespace or EOL, otherwise false
*/
export function isAtLineEndInclWS(doc: EditableDocument, offset = doc.selection.active) {
export function isAtLineEndInclWS(doc: EditableDocument, offset = doc.selections[0].active) {
const tokenCursor = doc.getTokenCursor(offset);
if (tokenCursor.getToken().type === 'eol') {
return true;
Expand All @@ -56,9 +56,10 @@ export function isAtLineEndInclWS(doc: EditableDocument, offset = doc.selection.
return false;
}

// TODO: setting the vscode ext context for cursor context might not work for multi-cursor
export function determineContexts(
doc: EditableDocument,
offset = doc.selection.active
offset = doc.selections[0].active
): CursorContext[] {
const tokenCursor = doc.getTokenCursor(offset);
const contexts: CursorContext[] = [];
Expand Down
20 changes: 20 additions & 0 deletions src/cursor-doc/cursor-doc-utils.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,20 @@
import { EditableDocument, ModelEditSelection } from './model';

export function selectionToRange(
selection: ModelEditSelection,
assumeDirection: 'ltr' | 'rtl' = undefined
) {
const { anchor, active } = selection;
switch (assumeDirection) {
case 'ltr':
return [anchor, active];
case 'rtl':
return [active, anchor];
case undefined:
default: {
const start = 'start' in selection ? selection.start : Math.min(anchor, active);
const end = 'end' in selection ? selection.end : Math.max(anchor, active);
return [start, end];
}
}
}
182 changes: 146 additions & 36 deletions src/cursor-doc/model.ts
Original file line number Diff line number Diff line change
@@ -1,7 +1,8 @@
import { isUndefined, max, min } from 'lodash';
import { isUndefined, max, min, isNumber } from 'lodash';
import { deepEqual as equal } from '../util/object';
import { Scanner, ScannerState, Token } from './clojure-lexer';
import { LispTokenCursor } from './token-cursor';
import type { Selection, TextDocument } from 'vscode';

let scanner: Scanner;

Expand Down Expand Up @@ -45,26 +46,59 @@ export class ModelEdit {
*
* 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.
*/

export class ModelEditSelection {
private _anchor: number;
private _active: number;

constructor(anchor: number, active?: number) {
this._anchor = anchor;
if (active !== undefined) {
this._active = active;
private _start: number;
private _end: number;
private _isReversed: boolean;

constructor(anchor: number, active?: number, start?: number, end?: number, isReversed?: boolean);
constructor(selection: Selection, doc: TextDocument);
constructor(
anchorOrSelection: number | Selection,
activeOrDoc?: number | TextDocument,
start?: number,
end?: number,
isReversed?: boolean
) {
if (isNumber(anchorOrSelection)) {
const anchor = anchorOrSelection;
this._anchor = anchor;
if (activeOrDoc !== undefined && isNumber(activeOrDoc)) {
this._active = activeOrDoc;
} else {
this._active = anchor;
}
isReversed = isReversed ?? this._anchor > this._active;
this._isReversed = isReversed;
this._start = start ?? isReversed ? this._active : Math.min(anchor, this._active);
this._end = end ?? isReversed ? anchor : Math.max(anchor, this._active);
} else {
this._active = anchor;
const { active, anchor, start, end, isReversed } = anchorOrSelection;
// const doc = getActiveTextEditor().document;
const doc = activeOrDoc as TextDocument;
this._active = doc.offsetAt(active);
this._anchor = doc.offsetAt(anchor);
this._start = doc.offsetAt(start);
this._end = doc.offsetAt(end);
this._isReversed = isReversed;
}
}

private _updateDirection() {
this._start = Math.min(this._anchor, this._active);
this._end = Math.max(this._anchor, this._active);
this._isReversed = this._active < this._anchor;
}

get anchor() {
return this._anchor;
}

set anchor(v: number) {
this._anchor = v;
this._updateDirection();
}

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

set active(v: number) {
this._active = v;
this._updateDirection();
}

get start() {
this._updateDirection();
return this._start;
}

/* set start(v: number) {
// TODO: figure out .start setter logic
this._start = v;
if (this._start === this._anchor) {
this._isReversed = false;
} else if (this._start === this._active) {
this._isReversed = true;
} else if (this._isReversed) {
this._active = this._start;
} else if (!this._isReversed) {
this._anchor = this._start;
}
} */

get end() {
this._updateDirection();
return this._end;
}

/* set end(v: number) {
// TODO: figure out .end setter logic
// TODO: figure out .start setter logic
this.end = v;
if (this._end < this._start) {
this._start;
}
if (this.end === this._anchor) {
this._isReversed = true;
} else if (this.end === this._active) {
this._isReversed = false;
} else if (this._isReversed) {
this._anchor = this.end;
} else if (!this._isReversed) {
this._active = this.end;
}
} */

get isReversed() {
this._updateDirection();
return this._isReversed;
}

set isReversed(isReversed: boolean) {
this._isReversed = isReversed;
if (this._isReversed) {
this._start = this._active;
this._end = this._anchor;
} else {
this._start = this._anchor;
this._end = this._active;
}
}

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

export type ModelEditResult = {
edits: ModelEdit[];
selections: ModelEditSelection[];
success: boolean;
};
export interface EditableModel {
readonly lineEndingLength: number;

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

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

export interface EditableDocument {
selection: ModelEditSelection;
selections: ModelEditSelection[];
model: EditableModel;
// selectionStack: ModelEditSelection[];
/**
* 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
* 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
Expand All @@ -124,10 +222,10 @@ export interface EditableDocument {
selectionsStack: ModelEditSelection[][];
getTokenCursor: (offset?: number, previous?: boolean) => LispTokenCursor;
insertString: (text: string) => void;
getSelectionText: () => string;
getSelectionTexts: () => string[];
delete: () => Thenable<boolean>;
backspace: () => Thenable<boolean>;
getSelectionText: (index: number) => string;
delete: (index?: number) => Thenable<ModelEditResult>;
backspace: (index?: number) => Thenable<ModelEditResult>;
}

/** The underlying model for the REPL readline. */
Expand Down Expand Up @@ -372,7 +470,7 @@ export class LineInputModel implements EditableModel {
* Doesn't need to be atomic in the LineInputModel.
* @param edits
*/
edit(edits: ModelEdit[], options: ModelEditOptions): Thenable<boolean> {
edit(edits: ModelEdit[], options: ModelEditOptions): Thenable<ModelEditResult> {
return new Promise((resolve, reject) => {
for (const edit of edits) {
switch (edit.editFn) {
Expand All @@ -396,9 +494,9 @@ export class LineInputModel implements EditableModel {
}
}
if (this.document && options.selections) {
this.document.selections = [options.selections[0]];
this.document.selections = options.selections;
}
resolve(true);
resolve({ edits, selections: options.selections, success: true });
});
}

Expand Down Expand Up @@ -548,7 +646,6 @@ export class StringDocument implements EditableDocument {
}
}

selection: ModelEditSelection;
selections: ModelEditSelection[];

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

getSelectionsText: () => string[];
insertString(text: string) {
this.model.insertString(0, text);
}

getSelectionTexts: () => string[];
getSelectionText: () => string;

delete() {
return this.model.edit(
[this.selection].map(({ anchor: p }) => new ModelEdit('deleteRange', [p, 1])),
{
selections: this.selections.map(({ anchor: p }) => new ModelEditSelection(p)),
}
);
getSelectionText: (index: number) => string;

delete(index?: number) {
if (isUndefined(index)) {
return this.model.edit(
this.selections.map(({ anchor: p }) => new ModelEdit('deleteRange', [p, 1])),
{
selections: this.selections.map(({ anchor: p }) => new ModelEditSelection(p)),
}
);
} else {
return this.model.edit([new ModelEdit('deleteRange', [(this.selections[index].anchor, 1)])], {
selections: [new ModelEditSelection(this.selections[index].anchor)],
});
}
}
getSelectionText: () => string;

backspace() {
return this.model.edit(
[this.selection].map(({ anchor: p }) => new ModelEdit('deleteRange', [p - 1, 1])),
{
selections: [this.selection].map(({ anchor: p }) => new ModelEditSelection(p - 1)),
}
);
backspace(index?: number) {
if (isUndefined(index)) {
return this.model.edit(
this.selections.map(({ anchor: p }) => new ModelEdit('deleteRange', [p - 1, 1])),
{
selections: this.selections.map(({ anchor: p }) => new ModelEditSelection(p - 1)),
}
);
} else {
return this.model.edit(
[new ModelEdit('deleteRange', [this.selections[index].anchor - 1, 1])],
{
selections: [new ModelEditSelection(this.selections[index].anchor - 1)],
}
);
}
}
}
Loading

0 comments on commit f53a13f

Please sign in to comment.