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

Multicursor - Refactors - EditableDocument uses selections #2414

Merged
merged 11 commits into from
Mar 2, 2024
3 changes: 3 additions & 0 deletions .vscode/extensions.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
{
"recommendations": ["esbenp.prettier-vscode", "hbenl.vscode-mocha-test-adapter"]
}
1 change: 1 addition & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,7 @@ Changes to Calva.

## [Unreleased]

- Refactor some internal document and selection APIs in preparation for multiple selections, addressing [#610](https://github.com/BetterThanTomorrow/calva/issues/610)
- [Add default Clojure associations for file extensions `.lpy`](https://github.com/BetterThanTomorrow/calva/issues/2415)

## [2.0.414] - 2024-02-24
Expand Down
2 changes: 1 addition & 1 deletion src/api/ranges.ts
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,7 @@ import * as getText from '../util/get-text';
const wrapSelectionAndTextFunction = (
f: (document: vscode.TextDocument, position: vscode.Position) => [vscode.Range, string]
) => {
return (editor = vscode.window.activeTextEditor, position = editor?.selection?.active) => {
return (editor = vscode.window.activeTextEditor, position = editor?.selections?.[0]?.active) => {
if (editor && position && editor.document && editor.document.languageId === 'clojure') {
return f(editor.document, position);
} else {
Expand Down
26 changes: 15 additions & 11 deletions src/calva-fmt/src/format.ts
Original file line number Diff line number Diff line change
Expand Up @@ -32,7 +32,7 @@ export async function indentPosition(position: vscode.Position, document: vscode
undoStopBefore: false,
})
.then((onFulfilled) => {
editor.selection = new vscode.Selection(newPosition, newPosition);
editor.selections = [new vscode.Selection(newPosition, newPosition)];
return onFulfilled;
});
} else if (delta < 0) {
Expand All @@ -43,7 +43,7 @@ export async function indentPosition(position: vscode.Position, document: vscode
undoStopBefore: false,
})
.then((onFulfilled) => {
editor.selection = new vscode.Selection(newPosition, newPosition);
editor.selections = [new vscode.Selection(newPosition, newPosition)];
return onFulfilled;
});
}
Expand Down Expand Up @@ -103,7 +103,7 @@ export async function formatPositionInfo(
extraConfig = {}
) {
const doc: vscode.TextDocument = editor.document;
const index = doc.offsetAt(editor.selection.active);
const index = doc.offsetAt(editor.selections[0].active);
const cursor = getDocument(doc).getTokenCursor(index);

const formatRange = _calculateFormatRange(extraConfig, cursor, index);
Expand Down Expand Up @@ -208,20 +208,24 @@ export async function formatPosition(
{ undoStopAfter: false, undoStopBefore: false }
)
.then((onFulfilled: boolean) => {
editor.selection = new vscode.Selection(
doc.positionAt(formattedInfo.newIndex),
doc.positionAt(formattedInfo.newIndex)
);
editor.selections = [
new vscode.Selection(
doc.positionAt(formattedInfo.newIndex),
doc.positionAt(formattedInfo.newIndex)
),
];
return onFulfilled;
});
}
if (formattedInfo) {
return new Promise((resolve, _reject) => {
if (formattedInfo.newIndex != formattedInfo.previousIndex) {
editor.selection = new vscode.Selection(
doc.positionAt(formattedInfo.newIndex),
doc.positionAt(formattedInfo.newIndex)
);
editor.selections = [
new vscode.Selection(
doc.positionAt(formattedInfo.newIndex),
doc.positionAt(formattedInfo.newIndex)
),
];
}
resolve(true);
});
Expand Down
6 changes: 3 additions & 3 deletions src/calva-fmt/src/infer.ts
Original file line number Diff line number Diff line change
Expand Up @@ -23,7 +23,7 @@ interface ResultOptions {
}

export function inferParensCommand(editor: vscode.TextEditor) {
const position: vscode.Position = editor.selection.active,
const position: vscode.Position = editor.selections[0].active,
document = editor.document,
currentText = document.getText(),
r: ResultOptions = inferParens({
Expand All @@ -37,7 +37,7 @@ export function inferParensCommand(editor: vscode.TextEditor) {
}

export function indentCommand(editor: vscode.TextEditor, spacing: string, forward: boolean = true) {
const prevPosition: vscode.Position = editor.selection.active,
const prevPosition: vscode.Position = editor.selections[0].active,
document = editor.document;
let deletedText = '',
doEdit = true;
Expand Down Expand Up @@ -71,7 +71,7 @@ export function indentCommand(editor: vscode.TextEditor, spacing: string, forwar
)
.then((_onFulfilled: boolean) => {
if (doEdit) {
const position: vscode.Position = editor.selection.active,
const position: vscode.Position = editor.selections[0].active,
currentText = document.getText(),
r: ResultOptions = inferIndents({
text: currentText,
Expand Down
2 changes: 1 addition & 1 deletion src/calva-fmt/src/providers/ontype_formatter.ts
Original file line number Diff line number Diff line change
Expand Up @@ -53,7 +53,7 @@ export class FormatOnTypeEditProvider implements vscode.OnTypeFormattingEditProv
}
const editor = util.getActiveTextEditor();

const pos = editor.selection.active;
const pos = editor.selections[0].active;
if (formatterConfig.formatOnTypeEnabled()) {
if (vscode.workspace.getConfiguration('calva.fmt').get('newIndentEngine')) {
await formatter.indentPosition(pos, document);
Expand Down
8 changes: 6 additions & 2 deletions src/clojuredocs.ts
Original file line number Diff line number Diff line change
Expand Up @@ -63,7 +63,11 @@ export function printTextToRichCommentCommand(args: { [x: string]: string }) {
async function printTextToRichComment(text: string, position?: number) {
const doc = util.getDocument({});
const mirrorDoc = docMirror.getDocument(doc);
return paredit.addRichComment(mirrorDoc, position ? position : mirrorDoc.selection.active, text);
return paredit.addRichComment(
mirrorDoc,
position ? position : mirrorDoc.selections[0].active,
text
);
}

export async function getExamplesHover(
Expand Down Expand Up @@ -175,7 +179,7 @@ async function clojureDocsLookup(
p?: vscode.Position
): Promise<DocsEntry | undefined> {
const doc = d ? d : util.getDocument({});
const position = p ? p : util.getActiveTextEditor().selection.active;
const position = p ? p : util.getActiveTextEditor().selections[0].active;
const symbol = util.getWordAtPosition(doc, position);
const [ns, _] = namespace.getNamespace(doc, p);
const session = replSession.getSession(util.getFileType(doc));
Expand Down
2 changes: 1 addition & 1 deletion src/converters.ts
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,7 @@ import * as config from './config';

function getText() {
const editor = vscode.window.activeTextEditor;
const selection = editor.selection;
const selection = editor.selections[0];
const doc = editor.document;
return doc.getText(
selection.active.isEqual(selection.anchor)
Expand Down
2 changes: 1 addition & 1 deletion src/cursor-doc/backspace-on-whitespace.ts
Original file line number Diff line number Diff line change
Expand Up @@ -10,7 +10,7 @@ export function backspaceOnWhitespace(
) {
const origIndent = getIndent(doc.model, cursor.offsetStart, config);
const onCloseToken = cursor.getToken().type === 'close';
let start = doc.selection.anchor;
let start = doc.selections[0].anchor;
let token = cursor.getToken();
if (token.type === 'ws') {
start = cursor.offsetEnd;
Expand Down
6 changes: 3 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 @@ -58,7 +58,7 @@ export function isAtLineEndInclWS(doc: EditableDocument, offset = doc.selection.

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
35 changes: 21 additions & 14 deletions src/cursor-doc/model.ts
Original file line number Diff line number Diff line change
Expand Up @@ -36,9 +36,16 @@ export class TextLine {
}

export type ModelEditFunction = 'insertString' | 'changeRange' | 'deleteRange';

export class ModelEdit {
constructor(public editFn: ModelEditFunction, public args: any[]) {}
export type ModelEditArgs<T extends ModelEditFunction> = T extends 'insertString'
? Parameters<LineInputModel['insertString']>
: T extends 'changeRange'
? Parameters<LineInputModel['changeRange']>
: T extends 'deleteRange'
? Parameters<LineInputModel['deleteRange']>
: never;

export class ModelEdit<T extends ModelEditFunction> {
constructor(public editFn: T, public args: Readonly<ModelEditArgs<T>>) {}
}

/**
Expand Down Expand Up @@ -93,7 +100,7 @@ export type ModelEditOptions = {
undoStopBefore?: boolean;
formatDepth?: number;
skipFormat?: boolean;
selection?: ModelEditSelection;
selections?: ModelEditSelection[];
};

export interface EditableModel {
Expand All @@ -104,7 +111,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<ModelEditFunction>[], options: ModelEditOptions) => Thenable<boolean>;

getText: (start: number, end: number, mustBeWithin?: boolean) => string;
getLineText: (line: number) => string;
Expand All @@ -113,7 +120,7 @@ export interface EditableModel {
}

export interface EditableDocument {
selection: ModelEditSelection;
selections: ModelEditSelection[];
model: EditableModel;
selectionStack: ModelEditSelection[];
getTokenCursor: (offset?: number, previous?: boolean) => LispTokenCursor;
Expand Down Expand Up @@ -365,7 +372,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<ModelEditFunction>[], options: ModelEditOptions): Thenable<boolean> {
return new Promise((resolve, reject) => {
for (const edit of edits) {
switch (edit.editFn) {
Expand All @@ -388,8 +395,8 @@ export class LineInputModel implements EditableModel {
break;
}
}
if (this.document && options.selection) {
this.document.selection = options.selection;
if (this.document && options.selections) {
this.document.selections = options.selections;
}
resolve(true);
});
Expand Down Expand Up @@ -544,7 +551,7 @@ export class StringDocument implements EditableDocument {
}
}

selection: ModelEditSelection;
selections: ModelEditSelection[];

model: LineInputModel;

Expand All @@ -566,16 +573,16 @@ export class StringDocument implements EditableDocument {
getSelectionText: () => string;

delete() {
const p = this.selection.anchor;
const p = this.selections[0].anchor;
return this.model.edit([new ModelEdit('deleteRange', [p, 1])], {
selection: new ModelEditSelection(p),
selections: [new ModelEditSelection(p)],
});
}

backspace() {
const p = this.selection.anchor;
const p = this.selections[0].anchor;
return this.model.edit([new ModelEdit('deleteRange', [p - 1, 1])], {
selection: new ModelEditSelection(p - 1),
selections: [new ModelEditSelection(p - 1)],
});
}
}
Loading