lukas-reineke / lsp-format.nvim

A wrapper around Neovims native LSP formatting.
559 stars 27 forks source link

Formatting from Insert mode #42

Closed tombh closed 2 years ago

tombh commented 2 years ago

I notice that formatting is ignored in Insert mode. I know it sounds crazy but I don't use Neovim for its modal editing, and in fact am permanently in Insert mode. Would it be possible to make formatting in Insert mode a configurable option?

lukas-reineke commented 2 years ago

The check for insert mode is needed because changedtick is not updated from insert mode. So without it the format result could overwrite changes you made.

I don't want that to ever happen, so I won't make this an option, sorry.

You could turn on sync formatting for all formatters, and write a small function that formats and puts you back into insert mode to work around this.

tombh commented 2 years ago

Interesting, I hadn't thought of that. Thanks for the idea of a fix.

Just out of curiosity, what's the difference between document manipulation in Normal mode and Insert mode? Eg; dd in Normal mode doesn't enter Insert mode to remove the line right? So isn't it also possible for async formatting to overwrite changes in Nornal mode?

Also, what do you think about falling back to sync formatting in Insert mode?

M._format = function(bufnr, client, format_options)
    vim.b.format_changedtick = vim.b.changedtick
    local params = vim.lsp.util.make_formatting_params(format_options)
    local method = "textDocument/formatting"
    local timeout_ms = 2000
-   if format_options.sync then
+   if format_options.sync or vim.startswith(vim.api.nvim_get_mode().mode, "i") then
        local result = client.request_sync(method, params, timeout_ms, bufnr) or {}
        M._handler(result.err, result.result, { client_id = client.id, bufnr = bufnr })
    else
        client.request(method, params, M._handler, bufnr)
    end
end

It's still slightly unexpected behaviour, but maybe it's better to fallback to formatting rather than not?

CKolkey commented 2 years ago

Correct, dd in normal mode doesn't enter insert mode. It will, however, increment the changedtick buffer variable. In the async callback, If the changedtick value for the buffer is different than when the formatting request was initiated (captured via vim.b.format_changedtick = vim.b.changedtick in the M._format function), formatting isn't applied.

lukas-reineke commented 2 years ago

what do you think about falling back to sync formatting in Insert mode?

Sync formatting will block the editor. Especially in insert mode, that should not happen.

maybe it's better to fallback to formatting rather than not?

No, formatting should never overwrite user changes. And while you are in insert mode, there is no way to tell if there are changes or not. So the safe thing is to not format.

You should get pretty close to what you want with writing your own function that just puts you back into insert mode when formatting is done. But I'm pretty sure you are the only one who would use this, so this is not something I will add to the plugin.

tombh commented 2 years ago

Sync formatting will block the editor. Especially in insert mode, that should not happen.

If you're consciously choosing to save from insert mode, why is this a problem?

No, formatting should never overwrite user changes.

Of course.

And while you are in insert mode, there is no way to tell if there are changes or not. So the safe thing is to not format.

That makes no sense. The obvious thing to do is what every other editor does: block.

But I'm pretty sure you are the only one who would use this, so this is not something I will add to the plugin.

Well, this is perspective fulfils itself. I think it's worth bearing in my mind that a non-trivial number of people use Vim without Normal mode https://github.com/tombh/novim-mode

I'm sorry if I come across as annoyed. You're doing all this work for free, I have no right to ask anything from you. It's just that I love the terminal, I don't want to use Emacs and I don't like Vim's Normal mode editing. Nowadays Vim's modal editing is really not what is most interesting about it. It has by far the best plugin ecosystem. Why should life be made so hard for people like me? Do I really have to go back to the world of Vscode? 🥺

lukas-reineke commented 2 years ago

The obvious thing to do is what every other editor does: block.

And you can do that with the sync option. You'll just have to write your own function to leave insert mode and put you back once formatting is done. It should be pretty easy to do.

I really don't want to make your life hard, but this is open source. I don't have the time to write and maintain custom features that are only used by a small minority of users.

tombh commented 2 years ago

Yes, that's certainly doable, but because I don't want to intercept my CTRL+S-to-save logic, I'm just back to managing my own BufWritePre autocommand. Which defeats much of the advantages of leaning on a plugin to just do the right thing.

I manage my own open source projects and so I know only too well the various flak that can come your way for nothing more than having had the generosity of creating something for free. I'm sorry for doing the very thing I myself roll my eyes at.

I totally understand and respect your choice to not do something you don't want to do in your own project.

What I want to get off my chest though is a wider issue I have with Vim culture as a whole. One of the main reasons I don't want to use Normal mode in Vim is because of what I perceive as a somewhat elitist culture that surrounds it. The "How Do You Exit Vim" meme is funny, but also based in some truth. Whether it's openly admitted or not, there's an underlying perception that Vim separates the "real" hackers from the amateurs. Whilst that might indeed be valid, there's a danger that that kind of attitude can stifle community growth and technical innovation. The all-too familiar "religious" critique.

I think it was precisely these concerns that inspired the existence of Neovim. What a welcome breath of fresh air Lua is. Thank goodness. Neovim is embracing a broader audience, whilst still being essentially a Vim. Namely an editor that encourages and celebrates customisation. What unites us is our love of tweaking, of getting our development environment just exactly how we like it. And that includes all kinds of weird and wonderful approaches, some more traditionally Normal Mode-based than others.

I don't want Neovim to stop supporting Normal Mode. Just because I don't particularly resonate with modal editing doesn't mean I don't support other's right to do so. I genuinely love Neovim. And am I able to feel that without having used Normal Mode in nearly a decade. Sure, Vims don't excel at such an approach but neither do they abundantly prevent it either. There is so much more to Vim than just Normal Mode. That genie cannot be put back in the bottle.

So I'm not frustrated that you don't want to do work that you have no appetite for. That I totally understand and accept. What gets me is the curiosity of a blinkered Vim culture that, at least to my eyes, almost religiously eschews any Insert Mode customisations that begin to resemble conventional editors. As if it's a sacrilegious slight against Vim's fundamental values.

Again, you are fully in your right to do whatever you want with this project. It's just unfortunate that you've gotten in the crossfire for my own basically religiously held views. It's just striking to me that all my feelings can be summed up in one small diff:

@@ -144,13 +144,12 @@
     if not vim.api.nvim_buf_is_loaded(ctx.bufnr) then
         vim.fn.bufload(ctx.bufnr)
         vim.api.nvim_buf_set_var(ctx.bufnr, "format_changedtick", vim.api.nvim_buf_get_var(ctx.bufnr, "changedtick"))
     elseif
         vim.api.nvim_buf_get_var(ctx.bufnr, "format_changedtick")
             ~= vim.api.nvim_buf_get_var(ctx.bufnr, "changedtick")
-        or vim.startswith(vim.api.nvim_get_mode().mode, "i")
     then
         M._next()
         return
     end
@@ -164,13 +163,13 @@
-    if format_options.sync then
+    if format_options.sync or vim.startswith(vim.api.nvim_get_mode().mode, "i") then
         local result = client.request_sync(method, params, timeout_ms, bufnr) or {}
         M._handler(result.err, result.result, { client_id = client.id, bufnr = bufnr })

I've come across so much resistance to my way of using Vim over the years, but never I have I seen it so succinctly written in code before. It's true that only a small number of users would benefit from these changes. And here it is, an actual moment where I get to see what causes that fact in the first place.

Like I say, and it can't be said enough, you're not letting a single soul down by not being motivated to support this. And that's a religious view I think we both share: do not give a second thought to feeling obligated to do free work you don't want to. I'm just sorry you've had to bear the brunt of my misgivings about all this. The worst outcome would be that I diminished your passion for open source in general.

lukas-reineke commented 2 years ago

I don't not want to support this because I don't agree with your choice to stay in insert mode. I don't want to support this because it is not easy to fix.

Your example does not cover the case where a user goes into insert mode after the format request was started. Like in this issue https://github.com/lukas-reineke/lsp-format.nvim/issues/21

This has nothing to do with being elitist. I do want everyone to be able to use Neovim and my plugins. But this is a complex problem to fix from the plugin side, and not a lot of users would benefit from it.

If you make a PR that fixes this, and covers all the edge cases, I'd be happy to merge it. I won't work on this myself.