Fork me on GitHub
#calva
<
2022-09-12
>
skylize14:09:28

One bit of behavior from Format As You Type drives me bonkers. That is removal of whitespace to the left of the cursor after backspacing through a wrapping char.

skylize14:09:40

Example cases. | represents cursor, not pipe char.

(defn foo (|))

;; type backspace
;; ==>

(defn foo|)
(defn foo
  (|))

;; type backspace
;; ==>

(defn foo|)
My next action in these examples will be typing [ in place of the accidental (. But whitespace gets collapsed, so instead of ( I must type <space> [ or enter [. I'm not too concerned about the one extra keystroke. The problem is this continues to disorient me every time it happens, especially for the second example. Since the line I was trying to type into suddenly disappears, I have do a whole big thing in my head to remember where I planned to type what and why, and how to get there ("Oh yeah, just press enter, duh."). Is it possible I might somehow set up a special case here? I would like auto formatting to ignore any whitespace to the immediate left of the cursor. (Ideally manually triggering format would override the special case. But that shouldn't matter too much, because typically it would still auto format later, once the cursor is somewhere else.)

3
pez15:09:01

I think it is a bit tricky to achieve, but it could be worth a try. If there isn't an issue about it already, please file one. Otherwise provide extra input/context if needed. FYI. I don't think it is FormatAsYouType doing this. Rather it is ParEdit that formats after it has edited the form. You should be able to confirm by disabling format as you type. If you like to give it a go with a PR, that is also welcome, of course. ❤️

skylize15:09:31

yep. paredit.

cmdrdats17:09:18

+1 - this issue gets me every time 🙈

skylize23:09:16

I found that running backspace on the opening of an empty bracket pair only attempts to delete the 2 characters making up the pair.

export async function backspace( ... ): Promise<boolean> {
...
  return doc.model.edit(
    // complicated way to say "delete 1 char before cursor to 1 char after cursor"
    [new ModelEdit('deleteRange', [p - prevToken.raw.length, prevToken.raw.length + 1])], 
        {
          selection: new ModelEditSelection(p - prevToken.raw.length),
        }
   );
}
... }
I can track it with the debugger to calling into the VS Code API to make the edit. And here again, it only specifies a 2 character range to delete.
export class DocumentModel implements EditableModel { ...
  private deleteEdit( ... ) {
    const editor = utilities.getActiveTextEditor(), 
      document = editor.document,
      // example range:  {_start: {_line: 10, character 5}, _end {_line: 10, character 7}
      range = new vscode.Range(document.positionAt(offset), document.positionAt(offset + count)); 
    builder.delete(range);
  }
... }
From there it descends into the depths of the impenetrable and partially-minified internals of the Code API, with no visible effect until it finally unrolls back to the backspace function. It finally commits an edit right at then end of this function.
export async function backspace( ... ): Promise<boolean> { ...
  if { ... }
  else { 
    ...
  }  // Edit happens here
}
But all the whitespace to the left gets deleted too at that same moment, even though I found nothing in the call stack that seems to request it. :thinking_face: Happens with editor.formatOnType, [clojure].editor.formatOnType, and calva.fmt.formatAsYouType all set to false.

pez06:09:23

Yes, I'm sorry, this is a bit hard to follow. It is the formatter doing this, I'm pretty sure. Paredit doesn't care about the format-on-type settings. Each paredit command decides wether there should be formatting, and how deep, via ModelEditOptions. The actual edit happens on the DocumentModel in doc-mirror/index.ts. https://github.com/BetterThanTomorrow/calva/blob/dev/src/doc-mirror/index.ts#L57 You should be able to confirm by sending along skipFormat: true, in the ModelEditOptions. (It's the object with. the selection: key in that backspace() function.)

skylize23:09:17

I'm betting cljfmt is doing this, based on the rule :remove-surrounding-whitespace?. Maybe`backspace` could capture any whitespace to the left and then manually write it back in afterward?

skylize23:09:45

> Each paredit command decides wether there should be formatting, and how deep, via ModelEditOptions. Oh I didn't catch that part. 👍 So that means, in the case of deleting an open bracket (which we already have as a tested case), we should be able to disable the rule or just bypass formatting altogether.

pez04:09:19

Yes, pass skipFormat: true in options and it shouldn't do the format step.