Skip to content

Editor integration#

The Buf CLI ships an LSP server for Protobuf via buf lsp serve, which IDEs and editors use to provide navigation, completion, formatting, and live lint diagnostics on .proto files.

First-party integrations exist for VS Code, Vim and Neovim, IntelliJ, and Zed; any LSP-aware editor can also connect to buf lsp serve directly.

Capabilities#

Navigation

  • Jump to a symbol’s definition or references.
  • Search for messages, services, fields, and other declarations across the workspace.
  • Hover any symbol to see its definition and leading comments.

Editing

  • Format .proto files (matches buf format).
  • Auto-complete imports and symbol names.
  • Rename a symbol everywhere it’s referenced.

Diagnostics

  • Lint errors surfaced inline as you type, against the configured lint rules.

Code actions

  • Organize imports: add missing imports, remove unused ones, sort the rest.
  • Deprecate a package, message, enum, service, RPC, field, oneof, or enum value.
  • Quick-fix a lint diagnostic by inserting a // buf:lint:ignore comment.

Prerequisites#

The LSP server is invoked by editors as buf lsp serve, so the Buf CLI must be installed and on PATH.

The LSP server is GA. The wire protocol is stable; behavior changes between releases are unlikely but possible.

VS Code#

Install the Buf extension from the VS Code Marketplace. The extension uses the Buf LSP server automatically; no further configuration is needed.

If you have other Protobuf extensions installed (vscode-proto, for example), disable or remove them before installing the Buf extension.

IntelliJ#

The Buf for Protocol Buffers plugin is available for every IntelliJ-based IDE. Install it from the Settings → Plugins panel. The plugin is powered by the Buf LSP server and provides navigation, completion, formatting, syntax highlighting, and lint diagnostics on .proto files.

Zed#

Zed ships built-in support for the Buf LSP through its Proto extension. Point Zed at the Buf language server in settings.json:

{
  "languages": {
    "Proto": {
      "language_servers": ["buf"]
    }
  }
}

The integration takes effect when you open a .proto file.

Vim and Neovim#

Both Vim and Neovim work with buf lsp serve once the editor is configured to recognize Buf’s .proto files (and, optionally, buf.yaml and friends) as LSP-managed.

Before adding the LSP configuration, remove these older Buf-related plugins and ALE hooks if they’re enabled:

  • uarun/vim-protobuf
  • bufbuild/vim-buf (deprecated in favor of LSP)
  • ALE’s buf-lint linter
  • ALE’s buf-format fixer

The configurations below register the Buf LSP, attach it to .proto files, set up format-on-save, and map common LSP actions (go-to-definition, hover, rename, code actions). They also register a buf-config Vim file type for buf.yaml, buf.gen.yaml, buf.policy.yaml, and buf.lock; this is required for the LSP to attach to those files, not just for syntax highlighting.

Drop the relevant snippet into your editor’s config file (~/.vimrc for Vim, ~/.config/nvim/init.lua for Neovim).

Vim (.vimrc)
" Automatically install vim-plug.
" https://github.com/junegunn/vim-plug/wiki/tips#automatic-installation
let data_dir = has('nvim') ? stdpath('data') . '/site' : '~/.vim'
if empty(glob(data_dir . '/autoload/plug.vim'))
  silent execute '!curl -fLo '.data_dir.'/autoload/plug.vim --create-dirs  https://raw.githubusercontent.com/junegunn/vim-plug/master/plug.vim'
  autocmd VimEnter * PlugInstall --sync | source $MYVIMRC
endif

" Register plugins.
call plug#begin()

" Register required LSP plugins.
" By default, vim-lsp provides diagnostics and a variety of commands corresponding to LSP capabilities.
Plug 'prabirshrestha/vim-lsp'
Plug 'mattn/vim-lsp-settings'

" Autocomplete plugins; installing these will automatically register
" the Buf Language Server as a completion provider.
"
" asyncomplete + asyncomplete-lsp will automatically show a popup menu when
" the Buf Language Server notifies vim-lsp that it has completion suggestions.
"
" Once the popup menu is showing, use <control-n> and <control-p> to move
" through the popup menu suggestions, and <control-y> to select a suggestion.
" Check out asyncomplete's README for examples on how to set up alternative mappings for these keys:
" https://github.com/prabirshrestha/asyncomplete.vim?tab=readme-ov-file#tab-completion
"
" For more details on vim's completion, see `:help popupmenu-completion`
Plug 'prabirshrestha/asyncomplete.vim'
Plug 'prabirshrestha/asyncomplete-lsp.vim'

call plug#end()

" Register the buf-config filetype for Buf configuration files.
augroup buf_config_detect
    au!
    autocmd BufNewFile,BufRead buf.yaml,buf.gen.yaml,buf.policy.yaml,buf.lock setfiletype buf-config
    autocmd FileType buf-config setlocal syntax=yaml
augroup END

" Create a function that we will enable via autocmd for LSP-enabled buffers.
function! s:on_lsp_buffer_enabled() abort
    " Use LSP completion suggestions for completions
    setlocal omnifunc=lsp#complete
    " Show diagnostics in the signcolumn
    setlocal signcolumn=yes
    " Use the LSP's tagfunc, if available.
    if exists('+tagfunc') | setlocal tagfunc=lsp#tagfunc | endif

    " Register mappings for various LSP capabilities
    nmap <buffer> gd <plug>(lsp-definition)
    nmap <buffer> gs <plug>(lsp-document-symbol-search)
    nmap <buffer> gS <plug>(lsp-workspace-symbol-search)
    nmap <buffer> grr <plug>(lsp-references)
    nmap <buffer> grt <plug>(lsp-type-definition)
    nmap <buffer> grn <plug>(lsp-rename)
    nmap <buffer> gra <plug>(lsp-code-action)
    nmap <buffer> [d <plug>(lsp-previous-diagnostic)
    nmap <buffer> ]d <plug>(lsp-next-diagnostic)
    nmap <buffer> K <plug>(lsp-hover)

    " Format on save
    autocmd! BufWritePre <buffer> call execute('LspDocumentFormatSync')
endfunction

augroup lsp_install
    au!
    " call s:on_lsp_buffer_enabled only for languages that has the server registered.
    autocmd User lsp_buffer_enabled call s:on_lsp_buffer_enabled()
augroup END
Neovim (init.lua)
-- Register the buf-config filetype for Buf configuration files.
vim.filetype.add({
  filename = {
    ['buf.yaml'] = 'buf-config',
    ['buf.gen.yaml'] = 'buf-config',
    ['buf.policy.yaml'] = 'buf-config',
    ['buf.lock'] = 'buf-config',
  },
})
-- Optionally, use Treesitter's YAML parser for syntax highlighting.
-- vim.treesitter.language.register('yaml', 'buf-config')

-- Register buf_ls as an LSP server.
-- If you use nvim-lspconfig (https://github.com/neovim/nvim-lspconfig), you can
-- replace the vim.lsp.config() and vim.lsp.enable() calls below with:
--   require('lspconfig').buf_ls.setup({})
vim.lsp.config('buf_ls', {
  -- Command and arguments to start the server.
  cmd = { 'buf', 'lsp', 'serve' },
  -- Filetypes to automatically attach to.
  filetypes = { 'proto', 'buf-config' },
  -- Set the workspace for the LSP to the directory of the first matching file.
  root_markers = { 'buf.yaml', '.git' },
})
-- Enable buf_ls configuration.
vim.lsp.enable('buf_ls')

-- Create mappings and enable format on save for LSP-enabled files.
vim.api.nvim_create_autocmd('LspAttach', {
  callback = function(args)
    -- Type 'gd' in normal mode to go to definition.
    vim.keymap.set('n', 'gd', vim.lsp.buf.definition, {buffer = args.buf, desc = 'Go to definition'})
    -- NOTE: In Neovim, many LSP and diagnostic mappings and options are set by
    -- default and do not need to be mapped explicitly.
    --
    -- Uncomment below to override these default mappings:

    --vim.keymap.set('n', 'grr', vim.lsp.buf.references, {buffer = args.buf, desc = 'References'})
    --vim.keymap.set('n', 'grt', vim.lsp.buf.type_definition, {buffer = args.buf, desc = 'Type definition'})
    --vim.keymap.set('n', 'gO', vim.lsp.buf.document_symbol, {buffer = args.buf, desc = 'Document symbol'})
    --vim.keymap.set('n', 'grn', vim.lsp.buf.rename, {buffer = args.buf, desc = 'Rename'})
    --vim.keymap.set('n', 'gra', vim.lsp.buf.code_action, {buffer = args.buf, desc = 'Code action'})

    -- See :help lspdefaults for more details, including:
    --
    -- * K is mapped to hover
    -- * grn is mapped to rename
    -- * gra is mapped to code action (organize imports, deprecations, quick fixes)
    -- * omnifunc and tagfunc are automatically set
    -- * formatexpr is automatically set to use the LSP's formatter
    --
    -- Diagnostics (see `:help diagnostic-defaults`):
    --
    -- * ]d/[d jumps to next/prev diagnostic, respectively
    -- * ]D/[D jumps to first/last diagnostics in buffer, respectively
    -- * <C-w>d shows diagnostic at the cursor in a floating window
    --
    -- Uncomment below for a basic example diagnostic configuration that underlines locations and shows virtual text.

    --vim.diagnostic.config({
    --  virtual_text = true,
    --  underline = true
    --})

    -- Format on save.
    vim.api.nvim_create_autocmd('BufWritePre', {buffer = args.buf, callback = vim.lsp.buf.format})

    -- Set up autocompletion.
    --
    -- A popup menu will be shown when the Buf Language Server notifies Neovim that it has
    -- completion suggestions. Use <control-n>/<control-p> to move through suggested completions
    -- and <control-y> to select a completion.
    --
    -- For more details on Neovim's completion, see `:help popupmenu-completion`
    local client = vim.lsp.get_client_by_id(args.data.client_id)
    if client:supports_method('textDocument/completion') then
      vim.lsp.completion.enable(true, client.id, args.buf, { autotrigger = true })
      -- Request completions manually with <control-space>.
      vim.keymap.set('i', '<c-space>', vim.lsp.completion.get, {buffer = args.buf, desc = 'Manually request completions'})
    end
  end
})

If you maintain Neovim configuration in a .vimrc instead of init.lua, wrap the Lua block above in lua << EOFEOF.

Emacs#

Emacs has no first-party Buf plugin, but buf lsp serve works with both lsp-mode and eglot. Configure either client to launch buf lsp serve for .proto buffers.

EditorConfig#

If your project uses EditorConfig to standardize whitespace, these settings match what buf format produces:

[*.proto]
indent_size = 2
indent_style = space
insert_final_newline = true

The settings aren’t semantically meaningful in Protobuf but are commonly applied across the ecosystem.

Further reading#

  • buf lint: the rules surfaced as inline diagnostics in your editor.
  • buf format: the formatter that powers format-on-save.
  • buf breaking: breaking-change detection at the CLI; not surfaced through the LSP today.