neoclide / coc.nvim

Nodejs extension host for vim & neovim, load extensions like VSCode and host language servers.
Other
24.32k stars 955 forks source link

onTypeFormatting first sending after you exit insert mode #1363

Closed ckipp01 closed 4 years ago

ckipp01 commented 4 years ago

In Metals, the Scala language server, we have the ability to insert a | in the correct position on a multi-line string. For example, in the code below, if the user hits enter, they'd drop down to the next line and a new | will automatically be inserted in the correct space. This is implemented using the textDocument/onTypeFormatting message.

Example code snippet:

package example

object Example extends App {

  """|<enter>
     |""".stripMargin
}

While testing this out with coc.nvim by enabling the coc.preferences.formatOnType and tailing the lsp logs, the onTypeFormatting is sent correctly, but it's not sent after the <enter> but rather first after you exit insert mode. By doing so, the position in the params that are sent along are where the cursor is when you enter normal mode, which then causes the server to correctly no longer consider the user in a multi-line string context leading it to respond with no textEdit like it would in VS Code.

It seems to be that you can enter insert mode and hit enter in the position marked above, enter a range of characters, and then exit. Only then the onTypeFormatting is sent when really an entire range has been implemented.

This was originally reported here as we weren't sure if it was a bug in Metals or not. Was this implemented this way to only send after you exit insert mode by coc.nvim purposefully? By doing so, the request that is sent differs from what VS Code would send for example.

VS Code Example:

[Trace - 02:32:55 PM] Received request 'textDocument/onTypeFormatting - (5)'
Params: {
  "position": {
    "line": 6,
    "character": 2
  },
  "ch": "\n",
  "textDocument": {
    "uri": "file:///Users/ckipp/Documents/scala-workspace/test-project/src/main/scala/example/Hello.scala"
  },
  "options": {
    "tabSize": 2,
    "insertSpaces": true
  }
}

coc.nvim Example:

[Trace - 02:31:38 PM] Received request 'textDocument/onTypeFormatting - (3)'
Params: {
  "position": {
    "line": 7,
    "character": 0
  },
  "ch": "\n",
  "textDocument": {
    "uri": "file:///Users/ckipp/Documents/scala-workspace/test-project/src/main/scala/example/Hello.scala"
  },
  "options": {
    "tabSize": 2,
    "insertSpaces": true
  }
}

As you can see, the positions are different causing them to be interpreted differently. I figured you may be able to answer this question without a minimal .vimrc, but if you'd like a reproduction, please let me know and I can provide instructions on how to install Metals and provide a minimal .vimrc in order to demonstrate this.

ckipp01 commented 4 years ago

Hi, thanks for the quick response on this, but the change you pushed doesn't actually address the core problem. The change you pushed changes the position when you leave insert mode, and then fires the onTypeFormatting. However, in this situation, the originally new line that is sent in while in insert mode should trigger the onTypeFormatting request, but it doesn't. I'm not sure if you don't recognize that the \n as something that should trigger it or if you never trigger it while in insert mode, but moment that the user hits <enter> it should be triggered. This change doesn't address that.

chemzqm commented 4 years ago

@ckipp01 you have to remap <CR> yourself, try update coc.nvim to latest release and checkout :h coc#on_enter()

ckipp01 commented 4 years ago

ahhh, I misunderstood that when I first read it. Just added the mapping in there and tried it. This does indeed work as expected now. Thanks!

ckipp01 commented 4 years ago

I wanted to follow up on this as it still doesn't work quite as expected. For example, as you can see in the gif below, if you exit insert mode while on a line that will be part of a formatOnType edit, coc.nvim is sending in a \n even though the user isn't sending in a new line. This new line is hapening because I have

    inoremap <silent><expr> <cr> pumvisible() ? coc#_select_confirm()
                \: "\<C-g>u\<CR>\<c-r>=coc#on_enter()\<CR>"

set in my .vimrc to send in an enter. With this on, it will work if the line is blank and I'm sending in an enter, but not if there is text on the line like so

2019-11-30 08 32 12

Also, then in the logs you can see that a new line was sent to the server when a new line was never entered.

[Trace - 08:29:39 AM] Received request 'textDocument/onTypeFormatting - (3)'
Params: {
  "position": {
    "line": 6,
    "character": 10
  },
  "ch": "\n",
  "textDocument": {
    "uri": "file:///Users/ckipp/Documents/scala-workspace/test-project/src/main/scala/example/Hello.scala"
  },
  "options": {
    "tabSize": 2,
    "insertSpaces": true
  }
}

Is there a way to be able to correctly type on this line and only send a return when the user actually hits enter?

ckipp01 commented 4 years ago

I wanted to follow up on this again. I know that it was closed, but it actually still hasn't been properly addressed. For example, if you look at the gif below, the moment I leave insert mode, an onTypeFormatting request is sent to the server telling it a "\n" was entered, which is incorrect. The server then correctly responds with just a | which assumes that you're on a new line, but not.

2019-12-29 11 45 15

Here are the logs that are being sent from the gif.

Params: {
  "position": {
    "line": 7,
    "character": 10
  },
  "ch": "\n",
  "textDocument": {
    "uri": "file:///Users/ckipp/Documents/scala-workspace/test-project/src/main/scala/example/Hello.scala"
  },
  "options": {
    "tabSize": 2,
    "insertSpaces": true
  }
}

I don't think the request should be sent when you first leave insert mode, but rather when you are actually typing. That seems to be what the spec implies.

ckipp01 commented 4 years ago

Looking a bit more into it, it seems that this is the actual issue here

https://github.com/neoclide/coc.nvim/blob/d30bb256e537b38bcc7408bb742fa442b25e6a8a/src/handler/index.ts#L208

On InsertLeave a new line is being sent. Is there a way to no send a newline on leave? Why is that necessary?

chemzqm commented 4 years ago

I don't think the request should be sent when you first leave insert mode, but rather when you are actually typing. That seems to be what the spec implies.

We tell the server \n is inserted so that the server could format current line when using <esc>o to insert new line.

chemzqm commented 4 years ago

@ckipp01 that line already have |, so I think the language server should improve it's implementation.

ckipp01 commented 4 years ago

We tell the server \n is inserted so that the server could format the current line when using o to insert new line.

Sure, but if <esc> is hit, but then not followed up by an o, then the \n is being sent in, but never actually executed, which could (and for this situation is) causing problems.

chemzqm commented 4 years ago

Sure, but if <esc> is hit, but then not followed up by an o, then the \n is being sent in, but never actually executed, which could (and for this situaion is) causing problems.

But it makes sense for language server like tsserver to add extra spaces for you after type <esc>, we can add an option to disable this behavior.

ckipp01 commented 4 years ago

Just tested it out. That works perfectly. Thanks for implimenting it!