From f4f61e8ab8d442dbd208c9d90d1a64e84007a08b Mon Sep 17 00:00:00 2001 From: Steve Baker Date: Mon, 31 Jan 2022 13:32:57 +0100 Subject: [PATCH] Add trimEdit back so empty edits don't get made (#308) Also fixes some of the arithmetic, which was wrong. And converts Edit into a class so it can have the .empty and .isEmpty members. The Fable.Core.AttachMembers attribute is then needed so the property names are not mangled in JS. --- core/Main.fs | 4 ++-- core/Selections.fs | 19 ++++++++++++++- core/Types.fs | 53 +++++++++++++++++++++--------------------- core/Wrapping.fs | 4 +--- vscode/src/AutoWrap.ts | 2 +- vscode/src/Common.ts | 2 +- vscode/src/Core.ts | 4 +++- 7 files changed, 52 insertions(+), 36 deletions(-) diff --git a/core/Main.fs b/core/Main.fs index a816911..7134e28 100644 --- a/core/Main.fs +++ b/core/Main.fs @@ -42,7 +42,7 @@ let strWidth tabSize str = Line.strWidth tabSize str /// The client must supply the new text that was inserted in the edit, as well /// as the position where it was inserted let maybeAutoWrap file settings newText (pos: Position) (getLine: Func) = - let noEdit = { startLine=0; endLine=0; lines = [||]; selections = [||] } + let noEdit = Edit.empty if String.IsNullOrEmpty(newText) then noEdit // If column < 1 we never wrap @@ -75,4 +75,4 @@ let maybeAutoWrap file settings newText (pos: Position) (getLine: Func LineRange list -> ParseResult -> unit loop selections parseResult.startLine parseResult.blocks parseResult.originalLines +/// Trims all unchanged lines from the start and end of an edit. +let private trimEdit (originalLines: Nonempty) (edit : Edit) : Edit = + let originalLinesArray = originalLines |> Nonempty.toList |> List.toArray + + let mutable s = 0 + while s < edit.lines.Length && s <= edit.endLine - edit.startLine + && originalLinesArray[edit.startLine + s] = edit.lines[s] + do s <- s + 1 + + let mutable e = 0 + while e < edit.lines.Length - s && e <= edit.endLine - edit.startLine - s + && originalLinesArray[edit.endLine - e] = edit.lines[edit.lines.Length - 1 - e] + do e <- e + 1 + + Edit (edit.startLine + s, edit.endLine - e, + Array.sub edit.lines s (edit.lines.Length - s - e), edit.selections) let wrapSelected : Nonempty -> Selection seq -> Context -> Edit = @@ -190,4 +206,5 @@ let wrapSelected : Nonempty -> Selection seq -> Context -> Edit = let parseResult = {startLine = 0; originalLines = originalLines; blocks = context.getBlocks()} processBlocks context selectionRanges parseResult - {(context.output.toEdit ()) with selections = Seq.toArray selections} + let edit = context.output.toEdit () |> trimEdit originalLines + edit.withSelections (Seq.toArray selections) diff --git a/core/Types.fs b/core/Types.fs index 0206d48..b46d31f 100644 --- a/core/Types.fs +++ b/core/Types.fs @@ -13,34 +13,33 @@ type File = { language: string; path: string; getMarkers: Func } /// Settings passed in from the editor type Settings = { - column : int - tabWidth : int - doubleSentenceSpacing : bool - reformat : bool - wholeComment : bool + column : int + tabWidth : int + doubleSentenceSpacing : bool + reformat : bool + wholeComment : bool } -type Position = { - line : int - character : int -} - -type Selection = { - anchor : Position - active : Position -} - -/// Edit object to be passed out to the editor -type Edit = { - startLine : int - endLine : int - lines : array - /// In a standard wrap this is the same as the selections passed in to - /// wrapping. For an auto-wrap this is the normal selection position for - /// after the just-done edit (before wrapping). These selections still need - /// adjusting to be in the expected places after a wrap (both clients do - /// this themselves). In the future we might do this in Core instead. - selections: array -} +type Position = { line: int; character: int } + +type Selection = { anchor: Position; active: Position } + +/// Edit object to be passed out to the editor. If endLine < startLine and lines is empty, +/// then it's a no-op edit. +[] +type Edit (startLine_, endLine_, lines_, selections_) = + member _.startLine : int = startLine_ + member _.endLine : int = endLine_ + member _.lines : array = lines_ + /// In a standard wrap this is the same as the selections passed in to wrapping. For an + /// auto-wrap this is the normal selection position for after the just-done edit (before + /// wrapping). These selections still need adjusting to be in the expected places after + /// a wrap (both clients do this themselves). In the future we might do this in Core + /// instead. + member _.selections : array = selections_ + + member _.isEmpty = endLine_ < startLine_ && lines_.Length = 0 + static member empty = Edit (0, -1, [||], [||]) + member _.withSelections newSels = Edit (startLine_, endLine_, lines_, newSels) type DocState = { filePath: string; version: int; selections: Selection[] } diff --git a/core/Wrapping.fs b/core/Wrapping.fs index 2beb4ed..7cab8fc 100644 --- a/core/Wrapping.fs +++ b/core/Wrapping.fs @@ -149,6 +149,4 @@ type OutputBuffer(settings : Settings) = linesConsumed <- linesConsumed + size contents member _.toEdit () = - { startLine = startLine; endLine = startLine + linesConsumed - 1 - lines = List.toArray (List.rev outputLines); selections = [||] - } + Edit (startLine, startLine + linesConsumed - 1, List.toArray (List.rev outputLines), [||]) diff --git a/vscode/src/AutoWrap.ts b/vscode/src/AutoWrap.ts index 1a2e212..e8ccfd3 100644 --- a/vscode/src/AutoWrap.ts +++ b/vscode/src/AutoWrap.ts @@ -31,7 +31,7 @@ const checkChange = (e: TextDocumentChangeEvent) => { // maybeAutoWrap does more checks: that newText isn't empty, but is only // whitespace. Don't call this in a promise: it causes timing issues. const edit = maybeAutoWrap(file, settings, newText, range.start, docLine(doc)) - return applyEdit(editor, edit).then(null, catchErr) + if (!edit.isEmpty) return applyEdit(editor, edit).then(null, catchErr) } catch(err) { catchErr(err) } } diff --git a/vscode/src/Common.ts b/vscode/src/Common.ts index 5349f3a..0dd90f5 100644 --- a/vscode/src/Common.ts +++ b/vscode/src/Common.ts @@ -12,7 +12,7 @@ const vscodeSelection = s => /** Applies an edit to the document. Also fixes the selections afterwards. If * the edit is empty this is a no-op */ export function applyEdit (editor, edit) { - if (!edit.lines.length) return Promise.resolve() + if (edit.isEmpty) return Promise.resolve() const selections = edit.selections.map(vscodeSelection) const doc = editor.document diff --git a/vscode/src/Core.ts b/vscode/src/Core.ts index cb654e1..e25fcd7 100644 --- a/vscode/src/Core.ts +++ b/vscode/src/Core.ts @@ -16,8 +16,10 @@ export interface DocType { export interface Edit { startLine: number endLine: number - lines: string[] + lines: readonly string[] selections: readonly Selection[] + + isEmpty: boolean } export interface Position { line: number, character: number }