Skip to content

Editor integration and LSP#

Buf provides the world's best tooling for Protobuf, and as such brings deep integration with many common editors to make Protobuf a first-class language in the IDE of your choice.

Most of these integrations are powered by Buf's Language Server Protocol (LSP) server for Protocol Buffers via buf lsp serve. This is the world's first complete LSP server for Protobuf, and makes Protobuf a first-class language in your IDE of choice.

The Buf LSP server supports:

  • Go to definition
  • Go to references
  • Auto-complete
  • Contextual information on mouse hover
  • Protobuf file formatting
  • Syntax highlighting
  • Workspace / Document symbols

Our VS Code, Vim, and Neovim integrations automatically use the Buf LSP server, and LSP servers can be configured with most popular editors out of the box.

VS Code#

Install vscode-buf via the in-editor extension browser under the name "Buf" or manually via the extension page. If you have other plugins installed for .proto files, like vscode-proto, we recommend disabling or removing them first.

Vim and Neovim#

Vim and Neovim support is provided via the Buf LSP server.

If you are using these existing plugins, remove them before adding Buf LSP server support:

  • uarun/vim-protobuf
  • bufbuild/vim-buf (deprecated in favor of LSP)

Additionally, disable the ALE buf-lint linter and buf-format fixer if enabled.

Vim and Neovim are typically configured via Lua or Vimscript configuration files These files are typically located at:

  • Vim:
    • ~/.vimrc (Vimscript only)
  • Neovim:
    • ~/.config/nvim/init.lua (Lua)
    • ~/.vimrc (Vimscript)
    • ~/.config/nvim/init.vim (Vimscript)

Here are example configuration files for auto-complete, automatic formatting, and mappings corresponding to the Buf LSP features. Refer to Vim and Neovim's configuration documentation for more details. Vim configuration, like Vim itself, takes a lifetime to master!

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

" 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> [d <plug>(lsp-previous-diagnostic)
    nmap <buffer> ]d <plug>(lsp-next-diagnostic)
    nmap <buffer> K <plug>(lsp-hover)

    " Format on save
    autocmd! BufWritePre *.proto 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 buf_ls as an LSP server.
vim.lsp.config('buf_ls', {
  -- Command and arguments to start the server.
  cmd = { 'buf', 'lsp', 'serve' },
  -- Filetypes to automatically attach to.
  filetypes = { 'proto' },
  -- Set the workspace for the LSP at 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 the 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'})

    -- See :help lspdefaults for more details, including:
    --
    -- * K is mapped to hover
    -- * 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

    -- 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
})
Neovim (.vimrc)
" Inline lua will only work with Neovim; prevent the below from
" executing if Vim or Vi is used.
if has('nvim')
lua << EOF
-- Register buf_ls as an LSP server.
vim.lsp.config('buf_ls', {
  -- Command and arguments to start the server.
  cmd = { 'buf', 'lsp', 'serve' },
  -- Filetypes to automatically attach to.
  filetypes = { 'proto' },
  -- Set the workspace for the LSP at 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 the 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'})

    -- See :help lspdefaults for more details, including:
    --
    -- * K is mapped to hover
    -- * 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

    -- 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
})
EOF
endif

IntelliJ#

The plugin for all IntelliJ-based IDEs is available on the JetBrains Plugin Marketplace.

You can install it from the settings window of your IDE. The plugin supports linting and formatting for your .proto files, as well as navigation, syntax highlighting, and more.

Recommendations#

EditorConfig#

If you use EditorConfig files to enforce consistent styles in your code, we recommend these settings for your .proto files:

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

These settings aren't semantically meaningful in Protobuf but are commonly used throughout the ecosystem.