From 103087f88c795aa8b942134f8105140d9915bc77 Mon Sep 17 00:00:00 2001 From: Rayat Date: Sat, 19 Mar 2022 22:17:49 -0700 Subject: [PATCH] 2nd attempt multi cursor paredit start --- .vscode/settings.json | 445 +++++++++--------- src/cursor-doc/model.ts | 62 +-- src/cursor-doc/paredit.ts | 71 ++- src/doc-mirror/index.ts | 54 ++- .../unit/cursor-doc/paredit-test.ts | 13 +- 5 files changed, 333 insertions(+), 312 deletions(-) diff --git a/.vscode/settings.json b/.vscode/settings.json index c18a37eca..7a907f6d7 100644 --- a/.vscode/settings.json +++ b/.vscode/settings.json @@ -1,225 +1,224 @@ { - "mochaExplorer.files": "src/extension-test/unit/**/*-test.ts", - "mochaExplorer.require": "ts-node/register", - "testExplorer.useNativeTesting": true, - "cSpell.words": [ - "affordances", - "ahlbrecht", - "alnum", - "Alsos", - "analysing", - "arglist", - "arglists", - "autoindent", - "Batsov", - "bencode", - "betterthantomorrow", - "bhauman", - "borkdude", - "Bozhidar", - "brainer", - "brear", - "bringe", - "calva", - "Calva's", - "calvapretty", - "chmod", - "cibuilds", - "circleci", - "classpath", - "cljc", - "cljfmt", - "cljfx", - "cljify", - "cljslib", - "CLJSREPL", - "clojurians", - "Clojurists", - "cmdline", - "codeblock", - "codehilite", - "codelense", - "Cognitect", - "Configurability", - "cospaia", - "Dallo", - "datafication", - "debugable", - "debugadapter", - "Dedent", - "dedenting", - "defproject", - "defun", - "deref", - "derefed", - "derefs", - "devtool", - "devtools", - "docmirror", - "Docstring", - "Dorg", - "doseq", - "dotimes", - "Dvlaaad", - "eckstein", - "eldoc", - "Elisp", - "enablement", - "enablements", - "entrypoint", - "errored", - "ESPACEIALLY", - "être", - "eval", - "evals", - "falsesomething", - "favicon", - "fehse", - "feldman", - "figwheel", - "filipe", - "fipp", - "foob", - "FUBAR", - "gifs", - "Girardi", - "graalvm", - "gsub", - "hacky", - "highlightning", - "Hmmm", - "hopperdietzel", - "howto", - "inferer", - "infoparser", - "Insourcing", - "Intelli", - "isequal", - "jacekschae", - "Jackin", - "Jackout", - "janne", - "javafx", - "jsify", - "jszip", - "junit", - "keywordize", - "klepsch", - "kondo", - "lein", - "leiningen", - "lexing", - "Libspec", - "luminus", - "marthi's", - "missmatch", - "mkdir", - "mkdocs", - "nashorn", - "niclas", - "nmap", - "Notespace", - "nrebl", - "nrepl", - "ntqry", - "nums", - "ontype", - "openjdk", - "openjfx", - "outdent", - "OVSX", - "ozimos", - "pageview", - "paredit", - "paren", - "parens", - "parinfer", - "pidfile", - "piggieback", - "polyrepos", - "postrelease", - "pprint", - "prefs", - "preinstall", - "prepending", - "Prettiful", - "prewatch", - "Pseudoterminal", - "randr", - "REBL", - "redefs", - "refactorings", - "REPLs", - "reponame", - "rickmoynihan", - "ringe", - "rlwrap", - "sauvala", - "sbin", - "scaturro", - "schäfer", - "Sdeps", - "seqs", - "Sexp", - "Sexpr", - "sexpression", - "Sexps", - "sivertsen", - "smartparens", - "snakeyaml", - "stacktraces", - "stian", - "strömberg", - "Strömberg's", - "suitible", - "terje", - "togglemode", - "tokenizes", - "tonsky", - "tonsky's", - "truesomething", - "tsbuildinfo", - "udris", - "Uitbeijerse", - "ullrich", - "unpromote", - "unpromoted", - "unsets", - "visibles", - "vsce", - "vscodevim", - "VSIX", - "webpack'ed", - "webpacked", - "Xdebug", - "Xrunjdwp", - "xvfb", - "zprint", - "Zulip" - ], - "cSpell.languageSettings": [ - { - "languageId": [ - "clojure", - "json", - "typescript" - ], - "allowCompoundWords": false - } - ], - "peacock.color": "#e48141", - "typescript.tsdk": "node_modules/typescript/lib", - "[typescript]": { - "editor.defaultFormatter": "esbenp.prettier-vscode" - }, - "[jsonc]": { - "editor.defaultFormatter": "vscode.json-language-features" - }, - "workbench.colorCustomizations": { - "sash.hoverBorder": "#ea9f6e", - "titleBar.activeBackground": "#e48141", - "titleBar.activeForeground": "#15202b", - "titleBar.inactiveBackground": "#e4814199", - "titleBar.inactiveForeground": "#15202b99" - }, + "mochaExplorer.files": "src/extension-test/unit/**/*-test.ts", + "mochaExplorer.require": "ts-node/register", + "testExplorer.useNativeTesting": true, + "cSpell.words": [ + "affordances", + "ahlbrecht", + "alnum", + "Alsos", + "analysing", + "arglist", + "arglists", + "autoindent", + "Batsov", + "behaviour", + "bencode", + "betterthantomorrow", + "bhauman", + "borkdude", + "Bozhidar", + "brainer", + "brear", + "bringe", + "calva", + "Calva's", + "calvapretty", + "chmod", + "cibuilds", + "circleci", + "classpath", + "cljc", + "cljfmt", + "cljfx", + "cljify", + "cljslib", + "CLJSREPL", + "Clojuredocs", + "clojurians", + "Clojurists", + "cmdline", + "codeblock", + "codehilite", + "codelense", + "Cognitect", + "Configurability", + "cospaia", + "Dallo", + "datafication", + "debugable", + "debugadapter", + "Dedent", + "dedenting", + "defproject", + "defun", + "deref", + "derefed", + "derefs", + "devtool", + "devtools", + "docmirror", + "Docstring", + "Dorg", + "doseq", + "dotimes", + "Dvlaaad", + "eckstein", + "eldoc", + "Elisp", + "enablement", + "enablements", + "entrypoint", + "errored", + "ESPACEIALLY", + "être", + "eval", + "evals", + "falsesomething", + "favicon", + "fehse", + "feldman", + "figwheel", + "filipe", + "fipp", + "foob", + "FUBAR", + "gifs", + "Girardi", + "graalvm", + "gsub", + "hacky", + "highlightning", + "Hmmm", + "hopperdietzel", + "howto", + "inferer", + "infoparser", + "Insourcing", + "Intelli", + "isequal", + "jacekschae", + "Jackin", + "Jackout", + "janne", + "javafx", + "jsify", + "jszip", + "junit", + "keywordize", + "klepsch", + "kondo", + "lein", + "leiningen", + "lexing", + "Libspec", + "luminus", + "marthi's", + "missmatch", + "mkdir", + "mkdocs", + "nashorn", + "niclas", + "nmap", + "Notespace", + "nrebl", + "nrepl", + "ntqry", + "nums", + "ontype", + "openjdk", + "openjfx", + "outdent", + "OVSX", + "ozimos", + "pageview", + "paredit", + "paren", + "parens", + "parinfer", + "pidfile", + "piggieback", + "polyrepos", + "postrelease", + "pprint", + "prefs", + "preinstall", + "prepending", + "Prettiful", + "prewatch", + "Pseudoterminal", + "randr", + "REBL", + "redefs", + "refactorings", + "REPLs", + "reponame", + "rickmoynihan", + "ringe", + "rlwrap", + "sauvala", + "sbin", + "scaturro", + "schäfer", + "Sdeps", + "seqs", + "Sexp", + "Sexpr", + "sexpression", + "Sexps", + "sivertsen", + "smartparens", + "snakeyaml", + "stacktraces", + "stian", + "strömberg", + "Strömberg's", + "suitible", + "terje", + "togglemode", + "tokenizes", + "tonsky", + "tonsky's", + "truesomething", + "tsbuildinfo", + "udris", + "Uitbeijerse", + "ullrich", + "unpromote", + "unpromoted", + "unsets", + "visibles", + "vsce", + "vscodevim", + "VSIX", + "webpack'ed", + "webpacked", + "Xdebug", + "Xrunjdwp", + "xvfb", + "zprint", + "Zulip" + ], + "cSpell.languageSettings": [ + { + "languageId": [ + "clojure", + "json", + "typescript" + ], + "allowCompoundWords": false + } + ], + "githubIssues.issueBranchTitle": "wip/${user}/issue-#${issueNumber}/${sanitizedIssueTitle}", + "typescript.tsdk": "node_modules/typescript/lib", + "[typescript]": { + "editor.defaultFormatter": "esbenp.prettier-vscode" + }, + "[json]": { + "editor.defaultFormatter": "vscode.json-language-features" + }, + "[jsonc]": { + "editor.defaultFormatter": "vscode.json-language-features" + }, + "peacock.color": "#e48141", // picked from calva icon center } diff --git a/src/cursor-doc/model.ts b/src/cursor-doc/model.ts index 8c8340ca2..f104cfcc6 100644 --- a/src/cursor-doc/model.ts +++ b/src/cursor-doc/model.ts @@ -84,8 +84,8 @@ export type ModelEditOptions = { undoStopBefore?: boolean; formatDepth?: number; skipFormat?: boolean; + // selection?: ModelEditSelection; selections?: ModelEditSelection[]; - selection?: ModelEditSelection; }; export interface EditableModel { @@ -108,12 +108,24 @@ 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 + * is popped. In essence, it's sort of an undo/history/time stack for the selection of forms/text in the current document. + * + * Each item in the stack however, is not a single selection, but rather an array of selections, one for each active cursor. Recall that vscode users may have as many cursors as they like, and will expect that selection expansion/shrinking commands should work equally well for each cursor, with respect to their particular locations, as they do outside of Paredit, eg with vscode's native selection expansion/shrinking commands. + * + * A further detail is that, along the "selection/form nesting order" axis, the selections are not an undo history of + * the user's arbitrary selections anywhere in the document, but specifically of the order in which selection expansion operates. That is, if we for simplicity pretend there can only be one cursor, each selection stack is a stack whose items hold forms/s-exps, such that each item is the form immediately enclosing the previous one. As such, we can imagine traversing forward (towards the top/right) of the stack + * as representing expanding the selection of forms by each nesting level, and backwards as shrinking the selection back down to the starting form/cursor position. + * + */ selectionsStack: ModelEditSelection[][]; - selectionStack: ModelEditSelection[]; getTokenCursor: (offset?: number, previous?: boolean) => LispTokenCursor; insertString: (text: string) => void; - getSelectionText: (index?: number) => string; - getSelectionsText: () => string[]; + getSelectionText: () => string; + getSelectionTexts: () => string[]; delete: () => Thenable; backspace: () => Thenable; } @@ -383,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[0]]; } resolve(true); }); @@ -537,12 +549,11 @@ export class StringDocument implements EditableDocument { } selection: ModelEditSelection; + selections: ModelEditSelection[]; model: LineInputModel = new LineInputModel(1, this); selectionsStack: ModelEditSelection[][] = []; - selections: ModelEditSelection[] = []; - selectionStack: ModelEditSelection[] = []; getTokenCursor(offset?: number, previous?: boolean): LispTokenCursor { if (isUndefined(offset)) { @@ -557,30 +568,25 @@ export class StringDocument implements EditableDocument { this.model.insertString(0, text); } - delete() { - const edits = []; - const selections = []; - this.selections.forEach(({ anchor: p }) => { - edits.push(new ModelEdit('deleteRange', [p, 1])); - selections.push(new ModelEditSelection(p)); - }); + getSelectionTexts: () => string[]; + getSelectionText: () => string; - return this.model.edit(edits, { - selections, - }); + 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: () => string; backspace() { - const edits = []; - const selections = []; - this.selections.forEach(({ anchor: p }) => { - edits.push(new ModelEdit('deleteRange', [p - 1, 1])); - selections.push(new ModelEditSelection(p - 1)); - }); - - return this.model.edit(edits, { - selections, - }); + 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)), + } + ); } } diff --git a/src/cursor-doc/paredit.ts b/src/cursor-doc/paredit.ts index abaf72894..92b16349c 100644 --- a/src/cursor-doc/paredit.ts +++ b/src/cursor-doc/paredit.ts @@ -2,7 +2,7 @@ import { isArray, isEqual, isNumber, last, pick, property } from 'lodash'; import { validPair } from './clojure-lexer'; import { EditableDocument, ModelEdit, ModelEditSelection } from './model'; import { LispTokenCursor } from './token-cursor'; -import _ = require('lodash'); +import { last } from 'lodash'; // NB: doc.model.edit returns a Thenable, so that the vscode Editor can compose commands. // But don't put such chains in this module because that won't work in the repl-console. @@ -22,16 +22,16 @@ export function killRange( ) { const [left, right] = [Math.min(...range), Math.max(...range)]; void doc.model.edit([new ModelEdit('deleteRange', [left, right - left, [start, end]])], { - selection: new ModelEditSelection(left), + selections: [new ModelEditSelection(left)], }); } export function moveToRangeLeft(doc: EditableDocument, range: [number, number]) { - doc.selection = new ModelEditSelection(Math.min(range[0], range[1])); + doc.selections = [new ModelEditSelection(Math.min(range[0], range[1]))]; } export function moveToRangeRight(doc: EditableDocument, range: [number, number]) { - doc.selection = new ModelEditSelection(Math.max(range[0], range[1])); + doc.selections = [new ModelEditSelection(Math.max(range[0], range[1]))]; } export function selectRange(doc: EditableDocument, range: [number, number] | Array) { @@ -398,7 +398,7 @@ export function wrapSexpr( ]), ], { - selection: new ModelEditSelection(start + open.length), + selections: [new ModelEditSelection(start + open.length)], skipFormat: options.skipFormat, } ); @@ -412,7 +412,7 @@ export function wrapSexpr( new ModelEdit('insertString', [range[0], open]), ], { - selection: new ModelEditSelection(start + open.length), + selections: [new ModelEditSelection(start + open.length)], skipFormat: options.skipFormat, } ); @@ -438,7 +438,7 @@ export function rewrapSexpr( new ModelEdit('changeRange', [closeStart, closeEnd, close]), new ModelEdit('changeRange', [openStart, openEnd, open]), ], - { selection: new ModelEditSelection(end) } + { selections: [new ModelEditSelection(end)] } ); } } @@ -455,7 +455,7 @@ export function splitSexp(doc: EditableDocument, start: number = doc.selection.a if (cursor.forwardList()) { const close = cursor.getToken().raw; void doc.model.edit([new ModelEdit('changeRange', [splitPos, splitPos, `${close}${open}`])], { - selection: new ModelEditSelection(splitPos + 1), + selections: [new ModelEditSelection(splitPos + 1)], }); } } @@ -489,7 +489,7 @@ export function joinSexp( [prevEnd, prevEnd], ]), ], - { selection: new ModelEditSelection(prevEnd), formatDepth: 2 } + { selections: [new ModelEditSelection(prevEnd)], formatDepth: 2 } ); } } @@ -516,7 +516,7 @@ export function spliceSexp( new ModelEdit('changeRange', [end, end + close.raw.length, '']), new ModelEdit('changeRange', [beginning - open.raw.length, beginning, '']), ], - { undoStopBefore, selection: new ModelEditSelection(start - 1) } + { undoStopBefore, selections: [new ModelEditSelection(start - 1)] } ); } } @@ -529,7 +529,7 @@ export function killBackwardList( return doc.model.edit( [new ModelEdit('changeRange', [start, end, '', [end, end], [start, start]])], { - selection: new ModelEditSelection(start), + selections: [new ModelEditSelection(start)], } ); } @@ -552,7 +552,7 @@ export function killForwardList( [start, start], ]), ], - { selection: new ModelEditSelection(start) } + { selections: [new ModelEditSelection(start)] } ); } @@ -652,7 +652,7 @@ export function forwardBarfSexp(doc: EditableDocument, start: number = doc.selec ], start >= cursor.offsetStart ? { - selection: new ModelEditSelection(cursor.offsetStart), + selections: [new ModelEditSelection(cursor.offsetStart)], formatDepth: 2, } : { formatDepth: 2 } @@ -678,7 +678,7 @@ export function backwardBarfSexp(doc: EditableDocument, start: number = doc.sele ], start <= cursor.offsetStart ? { - selection: new ModelEditSelection(cursor.offsetStart), + selections: [new ModelEditSelection(cursor.offsetStart)], formatDepth: 2, } : { formatDepth: 2 } @@ -721,7 +721,7 @@ export function close(doc: EditableDocument, close: string, start: number = doc. // Do nothing when there is balance } else { void doc.model.edit([new ModelEdit('insertString', [start, close])], { - selection: new ModelEditSelection(start + close.length), + selections: [new ModelEditSelection(start + close.length)], }); } } @@ -748,13 +748,13 @@ export function backspace( return new Promise((resolve) => resolve(true)); } else if (doc.model.getText(p - 2, p, true) == '\\"') { return doc.model.edit([new ModelEdit('deleteRange', [p - 2, 2])], { - selection: new ModelEditSelection(p - 2), + selections: [new ModelEditSelection(p - 2)], }); } else if (prevToken.type === 'open' && nextToken.type === 'close') { return doc.model.edit( [new ModelEdit('deleteRange', [p - prevToken.raw.length, prevToken.raw.length + 1])], { - selection: new ModelEditSelection(p - prevToken.raw.length), + selections: [new ModelEditSelection(p - prevToken.raw.length)], } ); } else { @@ -782,13 +782,13 @@ export function deleteForward( const p = start; if (doc.model.getText(p, p + 2, true) == '\\"') { return doc.model.edit([new ModelEdit('deleteRange', [p, 2])], { - selection: new ModelEditSelection(p), + selections: [new ModelEditSelection(p)], }); } else if (prevToken.type === 'open' && nextToken.type === 'close') { void doc.model.edit( [new ModelEdit('deleteRange', [p - prevToken.raw.length, prevToken.raw.length + 1])], { - selection: new ModelEditSelection(p - prevToken.raw.length), + selections: [new ModelEditSelection(p - prevToken.raw.length)], } ); } else { @@ -816,7 +816,7 @@ export function stringQuote( if (cursor.getToken().type == 'close') { if (doc.model.getText(0, start).endsWith('\\')) { void doc.model.edit([new ModelEdit('changeRange', [start, start, '"'])], { - selection: new ModelEditSelection(start + 1), + selections: [new ModelEditSelection(start + 1)], }); } else { close(doc, '"', start); @@ -824,17 +824,17 @@ export function stringQuote( } else { if (doc.model.getText(0, start).endsWith('\\')) { void doc.model.edit([new ModelEdit('changeRange', [start, start, '"'])], { - selection: new ModelEditSelection(start + 1), + selections: [new ModelEditSelection(start + 1)], }); } else { void doc.model.edit([new ModelEdit('changeRange', [start, start, '\\"'])], { - selection: new ModelEditSelection(start + 2), + selections: [new ModelEditSelection(start + 2)], }); } } } else { void doc.model.edit([new ModelEdit('changeRange', [start, start, '""'])], { - selection: new ModelEditSelection(start + 1), + selections: [new ModelEditSelection(start + 1)], }); } } @@ -938,7 +938,6 @@ export function growSelectionStack( doc: EditableDocument, ranges: Array<(readonly [number, number])>, ) { - // const [start, end] = range; // Check if user has already at least once invoked "Expand Selection": if (doc.selectionsStack.length > 0) { // User indeed already has a selection set expansion history. @@ -1016,9 +1015,9 @@ export function raiseSexp( void doc.model.edit( [new ModelEdit('changeRange', [startCursor.offsetStart, endCursor.offsetEnd, raised])], { - selection: new ModelEditSelection( + selections: [new ModelEditSelection( isCaretTrailing ? startCursor.offsetStart + raised.length : startCursor.offsetStart - ), + )], } ); } @@ -1109,7 +1108,7 @@ export function transpose( [newCursorPos, newCursorPos], ]), ], - { selection: new ModelEditSelection(newCursorPos) } + { selections: [new ModelEditSelection(newCursorPos)] } ); } } @@ -1198,7 +1197,7 @@ export function dragSexprBackward( new ModelEdit('changeRange', [currentRange[0], currentRange[1], leftText]), new ModelEdit('changeRange', [backRange[0], backRange[1], currentText]), ], - { selection: new ModelEditSelection(backRange[0] + newPosOffset) } + { selections: [new ModelEditSelection(backRange[0] + newPosOffset)] } ); } } @@ -1226,9 +1225,9 @@ export function dragSexprForward( new ModelEdit('changeRange', [currentRange[0], currentRange[1], rightText]), ], { - selection: new ModelEditSelection( + selections: [new ModelEditSelection( currentRange[1] + (forwardRange[1] - currentRange[1]) - newPosOffset - ), + )], } ); } @@ -1313,7 +1312,7 @@ export function dragSexprBackwardUp(doc: EditableDocument, p = doc.selection.act new ModelEdit('insertString', [listStart, dragText, [p, p], [newCursorPos, newCursorPos]]), ], { - selection: new ModelEditSelection(newCursorPos), + selections: [new ModelEditSelection(newCursorPos)], skipFormat: false, undoStopBefore: true, } @@ -1347,7 +1346,7 @@ export function dragSexprForwardDown(doc: EditableDocument, p = doc.selection.ac new ModelEdit('deleteRange', [currentRange[0], deleteLength]), ], { - selection: new ModelEditSelection(newCursorPos), + selections: [new ModelEditSelection(newCursorPos)], skipFormat: false, undoStopBefore: true, } @@ -1380,7 +1379,7 @@ export function dragSexprForwardUp(doc: EditableDocument, p = doc.selection.acti new ModelEdit('deleteRange', [deleteStart, deleteLength]), ], { - selection: new ModelEditSelection(newCursorPos), + selections: [new ModelEditSelection(newCursorPos)], skipFormat: false, undoStopBefore: true, } @@ -1417,7 +1416,7 @@ export function dragSexprBackwardDown(doc: EditableDocument, p = doc.selection.a ]), ], { - selection: new ModelEditSelection(newCursorPos), + selections: [new ModelEditSelection(newCursorPos)], skipFormat: false, undoStopBefore: true, } @@ -1472,7 +1471,7 @@ export function addRichComment(doc: EditableDocument, p = doc.selection.active, ]), ], { - selection: new ModelEditSelection(newCursorPos), + selections: [new ModelEditSelection(newCursorPos)], skipFormat: true, undoStopBefore: false, } @@ -1500,7 +1499,7 @@ export function addRichComment(doc: EditableDocument, p = doc.selection.active, ]), ], { - selection: new ModelEditSelection(newCursorPos), + selections: [new ModelEditSelection(newCursorPos)], skipFormat: false, undoStopBefore: true, } diff --git a/src/doc-mirror/index.ts b/src/doc-mirror/index.ts index 4c9c08329..7c52662c3 100644 --- a/src/doc-mirror/index.ts +++ b/src/doc-mirror/index.ts @@ -50,8 +50,8 @@ export class DocumentModel implements EditableModel { ) .then((isFulfilled) => { if (isFulfilled) { - if (options.selection) { - this.document.selection = options.selection; + if (options.selections) { + this.document.selections = options.selections; } if (!options.skipFormat) { return formatter.formatPosition(editor, false, { @@ -142,7 +142,6 @@ export class MirroredDocument implements EditableDocument { model = new DocumentModel(this); selectionsStack: ModelEditSelection[][] = []; - selectionStack: ModelEditSelection[] = []; public getTokenCursor( offset: number = this.selections[0].active, @@ -155,35 +154,56 @@ export class MirroredDocument implements EditableDocument { const editor = utilities.tryToGetActiveTextEditor(), selection = editor.selection, wsEdit = new vscode.WorkspaceEdit(), - // TODO: prob prefer selection.active or .start + // TODO: prob prefer selection.active or .start edits = this.selections.map(({ anchor: left }) => vscode.TextEdit.insert(this.document.positionAt(left), text) ); wsEdit.set(this.document.uri, edits); void vscode.workspace.applyEdit(wsEdit).then((_v) => { - editor.selection = selection; + editor.selections = [selections[0]]; }); } - set selection(selection: ModelEditSelection) { + get selection() { + return this.selections[0]; + } + + set selection(sel: ModelEditSelection) { + this.selections = [sel]; + } + + set selections(selections: ModelEditSelection[]) { const editor = utilities.getActiveTextEditor(), - document = editor.document, - anchor = document.positionAt(selection.anchor), - active = document.positionAt(selection.active); - editor.selection = new vscode.Selection(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)); } - get selection(): ModelEditSelection { + get selections(): ModelEditSelection[] { const editor = utilities.getActiveTextEditor(), - document = editor.document, - anchor = document.offsetAt(editor.selection.anchor), - active = document.offsetAt(editor.selection.active); - return new ModelEditSelection(anchor, 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); + }); + } + + public getSelectionTexts() { + const editor = utilities.getActiveTextEditor(), + selections = editor.selections; + return selections.map((selection) => this.document.getText(selection)); } public getSelectionText(index: number = 0) { - const editor = utilities.tryToGetActiveTextEditor(), + const editor = utilities.getActiveTextEditor(), selection = editor.selections[index]; return this.document.getText(selection); } @@ -235,7 +255,7 @@ export function tryToGetDocument(doc: vscode.TextDocument) { return documents.get(doc); } -export function getDocument(doc: vscode.TextDocument) { +export function getDocument(doc: vscode.TextDocument): MirroredDocument { const mirrorDoc = tryToGetDocument(doc); if (isUndefined(mirrorDoc)) { diff --git a/src/extension-test/unit/cursor-doc/paredit-test.ts b/src/extension-test/unit/cursor-doc/paredit-test.ts index 86932d5ec..9e31e0450 100644 --- a/src/extension-test/unit/cursor-doc/paredit-test.ts +++ b/src/extension-test/unit/cursor-doc/paredit-test.ts @@ -3,6 +3,7 @@ import * as paredit from '../../../cursor-doc/paredit'; import * as model from '../../../cursor-doc/model'; import { docFromTextNotation, textAndSelection, text } from '../common/text-notation'; import { ModelEditSelection } from '../../../cursor-doc/model'; +import { last } from 'lodash'; model.initScanner(20000); @@ -587,22 +588,20 @@ describe('paredit', () => { const range = [15, 20] as [number, number]; it('should make grow selection the topmost element on the stack', () => { paredit.growSelectionStack(doc, [range]); - expect(doc.selectionStack[doc.selectionStack.length - 1]).toEqual( - new ModelEditSelection(range[0], range[1]) - ); + expect(last(doc.selectionsStack)).toEqual([new ModelEditSelection(range[0], range[1])]); }); it('get us back to where we started if we just grow, then shrink', () => { const selectionBefore = startSelection.clone(); paredit.growSelectionStack(doc, [range]); paredit.shrinkSelection(doc); - expect(doc.selectionStack[doc.selectionStack.length - 1]).toEqual(selectionBefore); + expect(last(doc.selectionsStack)).toEqual([selectionBefore]); }); it('should not add selections identical to the topmost', () => { const selectionBefore = doc.selection.clone(); paredit.growSelectionStack(doc, [range]); paredit.growSelectionStack(doc, [range]); paredit.shrinkSelection(doc); - expect(doc.selectionStack[doc.selectionStack.length - 1]).toEqual(selectionBefore); + expect(last(doc.selectionsStack)).toEqual([selectionBefore]); }); it('should have A topmost after adding A, then B, then shrinking', () => { const a = range, @@ -610,9 +609,7 @@ describe('paredit', () => { paredit.growSelectionStack(doc, [a]); paredit.growSelectionStack(doc, [b]); paredit.shrinkSelection(doc); - expect(doc.selectionStack[doc.selectionStack.length - 1]).toEqual( - new ModelEditSelection(a[0], a[1]) - ); + expect(last(doc.selectionsStack)).toEqual([new ModelEditSelection(a[0], a[1])]); }); });