mhartington/formatter.nvim

Run vim.lsp.buf.format() by default if formatting is not configured for a specific language

Opened this issue ยท 11 comments

Is it possible to run the vim.lsp.buf.format() on all languages that is not configured with formatter?

Example usecase would be that I don't want to configure every single language to have formatting work. In my case I have a prisma file that I would like to use the normal builtin formatting from neovim/lsp vs setting up the formatting manually.

Which configuration?
Type (custom or builtin):
Filetype: builtin
Formatter: builtin

require("formatter").setup({
	logging = true,
	log_level = vim.log.levels.WARN,
	-- All formatter configurations are opt-in
	filetype = {
		lua = {
			require("formatter.filetypes.lua").stylua,
		},
		typescriptreact = {
			require("formatter.filetypes.typescript").prettier,
		},
		dart = {
			require("formatter.filetypes.dart").dartformat,
		},
		["*"] = {
			-- "formatter.filetypes.any" defines default configurations for any
			-- filetype
			require("formatter.filetypes.any").remove_trailing_whitespace,
		},
	},
})

Expected behavior
Be able to run a method on all languages that are not configured, such as:

function()
  vim.lsp.buf.format()
end

Actual behaviour
Not sure how to achieve it

Nohac commented

I second this, it would be great to have a require("formatter.filetypes.any").lsp or something similar.

I tested the following workaround:

["*"] = {
  vim.lsp.buf.format
},

This sort of worked, but in some scenarios it would give me the following error message:

"src/main.rs" 107L, 2933B written                                                                              
Error detected while processing BufWritePost Autocommands for "*":
E5108: Error executing lua .../share/nvim/lazy/formatter.nvim/lua/formatter/format.lua:61: Index out of bounds
stack traceback:
        [C]: in function 'get_lines'
        .../share/nvim/lazy/formatter.nvim/lua/formatter/format.lua:61: in function 'start_task'
        .../share/nvim/lazy/formatter.nvim/lua/formatter/format.lua:49: in function 'format'
        [string ":lua"]:1: in main chunk

It will also output this if there's no matching lsp:

[LSP] Format request failed, no matching language servers.

Edit: It seems like when vim.lsp.buf.format is ran by formatter.nvim it freaks out whenever the number of lines changes after formatting, it works fine if the number of lines stays the same.

I second this, it would be great to have a require("formatter.filetypes.any").lsp or something similar.

I tested the following workaround:

["*"] = {
  vim.lsp.buf.format
},

You can do the following I believe, but that would end up running both my formatting in the specific language and then this the lsp formatter which would potentially do the same.

["*"] = {
  function()
    vim.lsp.buf.format
  end
},

I would want something that would run only on languages that is not configured

As a workaround I've come up with the following solution:

local settings = {
	lua = { require("formatter.filetypes.lua").stylua },
	typescript = { require("formatter.filetypes.typescript").prettier },
	json = { require("formatter.filetypes.json").fixjson },

	["*"] = {
		require("formatter.filetypes.any").remove_trailing_whitespace,
	},
}

require("formatter").setup({
	logging = false,
	log_level = vim.log.levels.WARN,
	filetype = settings,
})

vim.keymap.set("n", "<leader>f", function()
	if settings[vim.bo.filetype] ~= nil then
		vim.cmd([[Format]])
	else
		vim.lsp.buf.format()
	end
end)

@nlsnightmare this works perfectly for me, thanks!

I will still leave this commit open in case there is a more integrated solution, otherwise close it if auther feels like that is the recommended approach.

i'm trying something similar, checking if the lsp has native formatting enabled if not then use the plugin.

Something like this in my lsp config on_attach
ignore the print statements... just for me to know which lsp has it

      vim.api.nvim_buf_create_user_command(bufnr, "Format", function(_)
        if vim.lsp.buf.formatting then
          print("using native formatter")
          vim.lsp.buf.formatting({ async = true })
        else
          print("using 3rd party formatter")
          require('formatter.format').format({async=true})
        end
      end, { desc = "Format current buffer with LSP" })
    end

Then call it with nmap("<leader>f", vim.cmd.Format, "Format Document")

im struggling to make it work with the require tho so i need to research more on how to call it properly :)

I also have the same problem. For example, I mentioned in my lua settings that file "keymap.lua" shouldn't be formatted and it works fine, but in the ["*"] part the vim.lsp.buf.format doesn't know the condition and it formats the "keymap.lua" file.

local util = require("formatter.util")
require("formatter").setup({
  logging = true,
  log_level = vim.log.levels.ERROR,
  filetype = {
    lua = {
      function()
        -- Ignore files.
        if
          util.get_current_buffer_file_name() == "keymap.lua" or util.get_current_buffer_file_name() == "theme.lua"
        then
          return nil
        end
        return {
          exe = "stylua",
          args = {
            "--column-width",
            "120",
            "--indent-type",
            "Spaces",
            "--indent-width",
            "2",
            "--search-parent-directories",
            "--stdin-filepath",
            util.escape_path(util.get_current_buffer_file_path()),
            "--",
            "-",
          },
          stdin = true,
        }
      end,
    },

    ["*"] = {
      require("formatter.filetypes.any").remove_trailing_whitespace,
      function()
        vim.lsp.buf.format({ async = true })
      end,
    },
  },
})

I could fix it by this way:

    ["*"] = {
      require("formatter.filetypes.any").remove_trailing_whitespace,
      function()
        -- Ignore already configured types.
        local defined_types = require("formatter.config").values.filetype
        if defined_types[vim.bo.filetype] ~= nil then
          return nil
        end
        vim.lsp.buf.format({ async = true })
      end,
    },

But it would be great if we have something for this situation, or at least having a util function for validation like this.
I can send a PR if it's acceptable approach by you.

@mortymacs' solution works great! I think adding this or a similar option as a default any-filetype-config could be great.

@mortymacs I wanted to add support for selection format, if in selection mode, by calling:

vim.lsp.buf.format({
    range = {
        ["start"] = vim.api.nvim_buf_get_mark(0, "<"),
        ["end"] = vim.api.nvim_buf_get_mark(0, ">"),
    }
})

But I still want to allow doing normal format if nothing is selected. But I cant find a way to find if it was in visual mode to decide what command version to call. Calling vim.api.nvim_get_mode().mode always returns "n". I think it is because when it reaches that function it already ran exited visual mode.

vim.api.nvim_get_mode().mode

@abmantis the function and the attribute you mentioned vim.api.nvim_get_mode().mode, works exactly as you want. I tested in different modes and it returned the correct value.

function PrintCurrentMode() 
  if vim.api.nvim_get_mode().mode == "v" then
    print("VISUAL")
  else
    print("OTHER")
  end
end

So, in your case, it would be something like this:

["*"] = {
  -- other functions before it
  function()
    if vim.api.nvim_get_mode().mode == "v" then
      vim.lsp.buf.format({
        range = {
          ["start"] = vim.api.nvim_buf_get_mark(0, "<"),
          ["end"] = vim.api.nvim_buf_get_mark(0, ">"),
        }
      })
    else
      vim.lsp.buf.format({ async = true })
    end
  end,
  -- other functions after it
},

@mortymacs For some reason mine always returns 'n'. It seems that the buffer returns to normal before reaching that part of the code. This is what I have:

require("formatter").setup {
  filetype = {
    ["*"] = {
      function()
        print(vim.api.nvim_get_mode().mode)
        if vim.api.nvim_get_mode().mode == "v" then
          vim.lsp.buf.format({
            range = {
              ["start"] = vim.api.nvim_buf_get_mark(0, "<"),
              ["end"] = vim.api.nvim_buf_get_mark(0, ">"),
            }
          })
        else
          vim.lsp.buf.format({ async = true })
        end
      end,
    },
  },
}

EDIT: It seems that if I use a key map instead of calling the command :Format, it works! Thanks!

A simple integration I did for wgsl_analyzer:

require("formatter").setup {
	filetype = {
		wgsl = {
			function()
				vim.lsp.buf.format()
			end,
		},
        },
}

I don't mind duplicating code for other LSP formatters if they come along.