diff --git a/src/calva-fmt/src/config.ts b/src/calva-fmt/src/config.ts index 9fcaee15f..6f72b58da 100644 --- a/src/calva-fmt/src/config.ts +++ b/src/calva-fmt/src/config.ts @@ -90,11 +90,5 @@ function _updateConfig(): FormatConfig { export function onConfigurationChanged(e: vscode.ConfigurationChangeEvent) { if (e.affectsConfiguration("calva.fmt")) { updateConfig(); - if (e.affectsConfiguration("calva.fmt.experimental.inferParensAsYouType")) { - const parinferOn = getConfig()['infer-parens-as-you-type']; - docMirror.getDocuments().forEach((doc: docMirror.MirroredDocument, _key: any) => { - doc.model.performInferParens = parinferOn; - }); - } } } \ No newline at end of file diff --git a/src/cursor-doc/model.ts b/src/cursor-doc/model.ts index e7ddf2371..28813ad82 100644 --- a/src/cursor-doc/model.ts +++ b/src/cursor-doc/model.ts @@ -99,7 +99,6 @@ export interface EditableModel { */ edit: (edits: ModelEdit[], options: ModelEditOptions) => Thenable; parinferReadiness: parinfer.ParinferReadiness, - performInferParens: boolean; isWritable: boolean; getText: (start: number, end: number, mustBeWithin?: boolean) => string; getLineText: (line: number) => string; @@ -487,3 +486,48 @@ export class LineInputModel implements EditableModel { } +export class StringDocument implements EditableDocument { + constructor(contents: string) { + this.insertString(contents); + } + + selectionLeft: number; + selectionRight: number; + + get selection() { + return new ModelEditSelection(this.selectionLeft, this.selectionRight); + } + + set selection(sel: ModelEditSelection) { + this.selectionLeft = sel.anchor; + this.selectionRight = sel.active; + } + + model: LineInputModel = new LineInputModel(1, this); + + selectionStack: ModelEditSelection[] = []; + + getTokenCursor(offset?: number, previous?: boolean): LispTokenCursor { + return this.model.getTokenCursor(offset); + }; + + insertString(text: string) { + this.model.insertString(0, text); + }; + + getSelectionText: () => string; + + delete() { + const p = this.selectionLeft; + return this.model.edit([ + new ModelEdit('deleteRange', [p, 1]) + ], { selection: new ModelEditSelection(p) }); + }; + + backspace() { + const p = this.selectionLeft; + return this.model.edit([ + new ModelEdit('deleteRange', [p - 1, 1]) + ], { selection: new ModelEditSelection(p - 1) }); + }; +} \ No newline at end of file diff --git a/src/doc-mirror/index.ts b/src/doc-mirror/index.ts index 34e9fbafd..49c17f56c 100644 --- a/src/doc-mirror/index.ts +++ b/src/doc-mirror/index.ts @@ -3,7 +3,7 @@ import * as vscode from "vscode" import * as utilities from '../utilities'; import * as formatter from '../calva-fmt/src/format'; import { LispTokenCursor } from "../cursor-doc/token-cursor"; -import { ModelEdit, EditableDocument, EditableModel, ModelEditOptions, LineInputModel, ModelEditSelection } from "../cursor-doc/model"; +import { ModelEdit, EditableDocument, EditableModel, ModelEditOptions, LineInputModel, ModelEditSelection, StringDocument } from "../cursor-doc/model"; import * as parinfer from "../calva-fmt/src/infer"; import * as formatConfig from '../calva-fmt/src/config'; import statusbar from '../statusbar'; @@ -29,8 +29,6 @@ export class DocumentModel implements EditableModel { this._parinferReadiness = readiness; } - performInferParens = formatConfig.getConfig()["infer-parens-as-you-type"]; - isWritable = false; constructor(private document: MirroredDocument) { @@ -43,9 +41,6 @@ export class DocumentModel implements EditableModel { const undoStopBefore = !!options.undoStopBefore; return editor.edit(builder => { for (const modelEdit of modelEdits) { - if (!options.performInferParens) { - this.document.model.performInferParens = false; - } switch (modelEdit.editFn) { case 'insertString': this.insertEdit.apply(this, [builder, ...modelEdit.args]); @@ -116,6 +111,9 @@ export class DocumentModel implements EditableModel { export class MirroredDocument implements EditableDocument { constructor(public document: vscode.TextDocument) { } + parensInferred = false; + rangeFormatted = false; + get selectionLeft(): number { return this.document.offsetAt(vscode.window.activeTextEditor.selection.anchor); } @@ -180,7 +178,7 @@ function processChanges(event: vscode.TextDocumentChangeEvent) { const parinferOn = formatConfig.getConfig()["infer-parens-as-you-type"]; const formatAsYouTypeOn = formatConfig.getConfig()["format-as-you-type"]; const performFormatAsYouType = formatAsYouTypeOn && event.reason != vscode.TextDocumentChangeReason.Undo; - const performInferParens = parinferOn && event.reason != vscode.TextDocumentChangeReason.Undo && model.performInferParens; + const performInferParens = parinferOn && event.reason != vscode.TextDocumentChangeReason.Undo && !mirroredDoc.parensInferred; let performHealthCheck = !performFormatAsYouType; const edits: ModelEdit[] = event.contentChanges.map(change => { // vscode may have a \r\n marker, so it's line offsets are all wrong. @@ -192,13 +190,19 @@ function processChanges(event: vscode.TextDocumentChangeEvent) { performInferParens: !vscode.TextDocumentChangeReason.Undo }).then(async _v => { if (event.document === vscode.window.activeTextEditor?.document) { - if (performFormatAsYouType) { + if (performFormatAsYouType && !mirroredDoc.rangeFormatted) { if (event.contentChanges.length === 1 && event.contentChanges[0].text.match(/[\[\](){}]/)) { const change = event.contentChanges[0]; const start = event.document.offsetAt(change.range.start); const formatForwardIndex = formatter.indexForFormatForward(mirroredDoc); const end = formatForwardIndex !== mirroredDoc.selection.active ? formatForwardIndex + 1 : mirroredDoc.selection.active; - await formatter.formatRangeEditableDoc(mirroredDoc, [start, end], true); + const checkDoc = new StringDocument(mirroredDoc.model.getText(start, end)); + if (parinfer.getParinferReadiness(checkDoc).isStructureHealthy) { + await formatter.formatRangeEditableDoc(mirroredDoc, [start, end], true); + } else { + await formatter.formatForward(mirroredDoc); + } + mirroredDoc.rangeFormatted = true; } else { await formatter.formatForward(mirroredDoc); } @@ -206,6 +210,7 @@ function processChanges(event: vscode.TextDocumentChangeEvent) { } if ((mirroredDoc.model.parinferReadiness.isIndentationHealthy || performHealthCheck) && performInferParens) { await parinfer.inferParens(mirroredDoc); + mirroredDoc.parensInferred = true; } if (!performFormatAsYouType) { performHealthCheck = true; @@ -216,9 +221,8 @@ function processChanges(event: vscode.TextDocumentChangeEvent) { statusBar.update(vscode.window.activeTextEditor?.document); } }); - if (event.contentChanges.length > 0) { - model.performInferParens = formatConfig.getConfig()["infer-parens-as-you-type"]; - } + mirroredDoc.parensInferred = false; + mirroredDoc.rangeFormatted = false; model.lineInputModel.flushChanges() // we must clear out the repaint cache data, since we don't use it. diff --git a/src/extension-test/unit/common/mock.ts b/src/extension-test/unit/common/mock.ts index 21bcf542c..4a464ce47 100644 --- a/src/extension-test/unit/common/mock.ts +++ b/src/extension-test/unit/common/mock.ts @@ -4,8 +4,6 @@ import { LispTokenCursor } from '../../../cursor-doc/token-cursor' model.initScanner(20000); export class MockDocument implements model.EditableDocument { - isIndentationHealthy = true; - isStructureHealthy = true; selectionLeft: number; selectionRight: number;