Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

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

Open
RobertBrunhage opened this issue Jul 17, 2023 · 11 comments
Assignees

Comments

@RobertBrunhage
Copy link

RobertBrunhage commented Jul 17, 2023

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

@RobertBrunhage RobertBrunhage changed the title Run NeoVim formatter by default if Formatting is not configured for a specific language Run vim.lsp.buf.format() by default if formatting is not configured for a specific language Jul 17, 2023
@Nohac
Copy link

Nohac commented Jul 18, 2023

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.

@RobertBrunhage
Copy link
Author

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

@nlsnightmare
Copy link

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)

@RobertBrunhage
Copy link
Author

@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.

@mvaldes14
Copy link

mvaldes14 commented Jul 26, 2023

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 :)

@mortymacs
Copy link

mortymacs commented Aug 12, 2023

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.

@neolight1010
Copy link

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

@abmantis
Copy link

@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.

@mortymacs
Copy link

mortymacs commented Sep 21, 2023

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
},

@abmantis
Copy link

abmantis commented Sep 22, 2023

@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!

@demizer
Copy link

demizer commented Apr 5, 2024

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.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

No branches or pull requests

9 participants