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

Paredit Multicursor - Testing Utils - textNotation changes and diagnostic utils #2419

Merged
merged 8 commits into from
Mar 8, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
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
3 changes: 3 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,9 @@ Changes to Calva.

## [Unreleased]

- Internal textNotation testing system supports multiple selections, addressing [#610](https://github.com/BetterThanTomorrow/calva/issues/610)
- Add new Calva development utility commands to create textNotations from open buffers, and vice versa

## [2.0.415] - 2024-03-08

- Refactor some internal document and selection APIs in preparation for multiple selections, addressing [#610](https://github.com/BetterThanTomorrow/calva/issues/610)
Expand Down
10 changes: 10 additions & 0 deletions package.json
Original file line number Diff line number Diff line change
Expand Up @@ -1170,6 +1170,16 @@
"title": "Toggle nREPL Logging Enabled",
"category": "Calva Diagnostics"
},
{
"command": "calva.diagnostics.printTextNotationFromDocument",
"title": "Print TextNotation from the current document to Calva says",
"category": "Calva Diagnostics"
},
{
"command": "calva.diagnostics.createDocumentFromTextNotation",
"title": "Create a new Clojure Document from TextNotation",
"category": "Calva Diagnostics"
},
{
"command": "calva.linting.resolveMacroAs",
"title": "Resolve Macro As",
Expand Down
125 changes: 118 additions & 7 deletions src/cursor-doc/model.ts
Original file line number Diff line number Diff line change
@@ -1,7 +1,8 @@
import { Scanner, Token, ScannerState } from './clojure-lexer';
import { LispTokenCursor } from './token-cursor';
import { deepEqual as equal } from '../util/object';
import { isUndefined } from 'lodash';
import { isNumber, isUndefined } from 'lodash';
import { TextDocument, Selection } from 'vscode';

let scanner: Scanner;

Expand Down Expand Up @@ -65,22 +66,55 @@ export class ModelEdit<T extends ModelEditFunction> {
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;
}
const _isReversed = isReversed ?? this._anchor > this._active;
this._isReversed = _isReversed;
this._start = start ?? Math.min(anchor, this._active);
this._end = end ?? Math.max(anchor, this._active);
} else {
this._active = anchor;
const { active, anchor, start, end, isReversed } = anchorOrSelection;
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 @@ -89,11 +123,83 @@ export class ModelEditSelection {

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

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

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

get isCursor() {
return this.anchor === this.active;
}

get isSelection() {
return this.anchor !== this.active;
}

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;
}
}

get distance() {
return this._end - this._start;
}

clone() {
return new ModelEditSelection(this._anchor, this._active);
}

/**
* Returns a simple 2-item tuple representing the
* [leftmost/earliest/start position, rightmost, farthest, end position].
*/
get asRange() {
return [this.start, this.end] as [start: number, end: number];
}

/**
* Same as `ModelEditSelection.asRange` but with the leftmost item being the anchor position, and the rightmost item
* being the active position. This way, you can tell if it's reversed by checking if the leftmost item is greater
* than the rightmost item.
*/
get asDirectedRange() {
return [this.anchor, this.active] as [anchor: number, active: number];
}

/**
* Mutates itself!
* Very basic, offsets both active/anchor by a positive or negative number lol, with no attempt at clamping.
*
* Returns self for convenience
* @param offset number
*/
reposition(offset: number) {
this.active += offset;
this.anchor += offset;

this._updateDirection();

return this;
}
}

export type ModelEditOptions = {
Expand All @@ -105,6 +211,7 @@ export type ModelEditOptions = {

export interface EditableModel {
readonly lineEndingLength: number;
readonly lineEnding: string;

/**
* Performs a model edit batch.
Expand Down Expand Up @@ -135,6 +242,10 @@ export class LineInputModel implements EditableModel {
/** How many characters in the line endings of the text of this model? */
constructor(readonly lineEndingLength: number = 1, private document?: EditableDocument) {}

get lineEnding() {
return this.lineEndingLength === 1 ? '\n' : '\r\n';
}

/** The input lines. */
lines: TextLine[] = [new TextLine('', this.getStateForLine(0))];

Expand Down
33 changes: 22 additions & 11 deletions src/doc-mirror/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -25,6 +25,10 @@ export class DocumentModel implements EditableModel {
this.lineInputModel = new LineInputModel(this.lineEndingLength);
}

get lineEnding() {
return this.lineEndingLength == 2 ? '\r\n' : '\n';
}

edit(modelEdits: ModelEdit<ModelEditFunction>[], options: ModelEditOptions): Thenable<boolean> {
const editor = utilities.getActiveTextEditor(),
undoStopBefore = !!options.undoStopBefore;
Expand Down Expand Up @@ -146,21 +150,28 @@ export class MirroredDocument implements EditableDocument {
});
}

set selections(selections: ModelEditSelection[]) {
get selections(): ModelEditSelection[] {
const editor = utilities.getActiveTextEditor(),
document = editor.document,
anchor = document.positionAt(selections[0].anchor),
active = document.positionAt(selections[0].active);
editor.selections = [new vscode.Selection(anchor, active)];
editor.revealRange(new vscode.Range(active, active));
document = editor.document;
return editor.selections.map((sel) => {
const anchor = document.offsetAt(sel.anchor),
active = document.offsetAt(sel.active);
return new ModelEditSelection(anchor, active);
});
}

get selections(): ModelEditSelection[] {
set selections(selections: ModelEditSelection[]) {
const editor = utilities.getActiveTextEditor(),
document = editor.document,
anchor = document.offsetAt(editor.selections[0].anchor),
active = document.offsetAt(editor.selections[0].active);
return [new ModelEditSelection(anchor, active)];
document = editor.document;
editor.selections = selections.map((selection) => {
const anchor = document.positionAt(selection.anchor),
active = document.positionAt(selection.active);
return new vscode.Selection(anchor, active);
});

const primarySelection = selections[0];
const active = document.positionAt(primarySelection.active);
editor.revealRange(new vscode.Range(active, active));
}

public getSelectionText() {
Expand Down
Loading