Prototype for a Protobuf language server compatible with Buf.

  • By Buf
  • Last update: Jan 8, 2023
  • Comments: 8

Buf Language Server

bufls is a prototype for the beginnings of a Protobuf language server compatible with Buf modules and workspaces. This currently only supports go-to-definition.

This is a proof-of-concept that we wanted to share with the community. We do not actively maintain this, and there are no guarantees in terms of stability, but we want to hear your feedback!

For details on where we could go with this, please refer to future work.

Usage

Regardless of the LSP-compatible editor you use, you'll need to install bufls so that it's available on your $PATH.

go install github.com/bufbuild/buf-language-server/cmd/bufls@latest

Vim

With vim-lsp, the only configuration you need is the following:

Plug 'prabirshrestha/vim-lsp'

augroup LspBuf
  au!
  autocmd User lsp_setup call lsp#register_server({
      \ 'name': 'bufls',
      \ 'cmd': {server_info->['bufls', 'serve']},
      \ 'whitelist': ['proto'],
      \ })
  autocmd FileType proto nmap <buffer> gd <plug>(lsp-definition)
augroup END

Supported features

Buf's language server behaves similarly to the rest of the buf CLI. If the user has a buf.work.yaml defined, the modules defined in the workspace will take precedence over the modules specified in the buf.lock (i.e. the modules found in the module cache). The language server requires that inputs are of the protofile type.

Go to definition

Go to definition resolves the definition location of a symbol at a given text document position (i.e. textDocument/definition).

This feature is currently only implemented on the textDocument/definition endpoint. It may make sense to move this to textDocument/typeDefinition and/or textDocument/typeImplementation. The Protobuf grammar is far more limited than a programming language grammar, so not all of the semantics for each LSP endpoint apply here.

Today, this feature is only supported for messages and enums. The well-known types (WKT), and [custom] options are not yet supported.

Implementation

Protobuf compilation is fast, so the implementation is currently naive. Every editor command will compile the input file (e.g. file://proto/pet/v1/pet.proto) from scratch (there isn't any caching). Simple caching is fairly straightforward, but the cache would need to be cleared whenever a file is edited during the same language server session, which would require a file watcher. For now, performance is fine as-is (even for workspaces and large modules), but we might need to revisit this later as build graphs continue to grow.

Future work

More LSP features

This is just the tip of the iceberg - there's way more that a fully-featured Protobuf language server can do for the Protobuf community. For starters, the following set of endpoints are next in line:

Go to definition

A couple features remain for full go to definition support:

  • Add go to definition support for [custom] options.
  • Add go to definition support for the well-known types (i.e. synthesize the WKT in the module cache).

Download

buf-language-server.zip

Comments(8)

  • 1

    Bump go.uber.org/zap from 1.23.0 to 1.24.0

    Bumps go.uber.org/zap from 1.23.0 to 1.24.0.

    Release notes

    Sourced from go.uber.org/zap's releases.

    v1.24.0

    Enhancements:

    • #1148[]: Add Level to both Logger and SugaredLogger that reports the current minimum enabled log level.
    • #1185[]: SugaredLogger turns errors to zap.Error automatically.

    Thanks to @​Abirdcfly, @​craigpastro, @​nnnkkk7, and @​sashamelentyev for their contributions to this release.

    #1148: uber-go/zap#1148 #1185: uber-go/zap#1185

    Changelog

    Sourced from go.uber.org/zap's changelog.

    1.24.0 (30 Nov 2022)

    Enhancements:

    • #1148[]: Add Level to both Logger and SugaredLogger that reports the current minimum enabled log level.
    • #1185[]: SugaredLogger turns errors to zap.Error automatically.

    Thanks to @​Abirdcfly, @​craigpastro, @​nnnkkk7, and @​sashamelentyev for their contributions to this release.

    #1148: https://github.coml/uber-go/zap/pull/1148 #1185: https://github.coml/uber-go/zap/pull/1185

    Commits

    Dependabot compatibility score

    Dependabot will resolve any conflicts with this PR as long as you don't alter it yourself. You can also trigger a rebase manually by commenting @dependabot rebase.


    Dependabot commands and options

    You can trigger Dependabot actions by commenting on this PR:

    • @dependabot rebase will rebase this PR
    • @dependabot recreate will recreate this PR, overwriting any edits that have been made to it
    • @dependabot merge will merge this PR after your CI passes on it
    • @dependabot squash and merge will squash and merge this PR after your CI passes on it
    • @dependabot cancel merge will cancel a previously requested merge and block automerging
    • @dependabot reopen will reopen this PR if it is closed
    • @dependabot close will close this PR and stop Dependabot recreating it. You can achieve the same result by closing it manually
    • @dependabot ignore this major version will close this PR and stop Dependabot creating any more for this major version (unless you reopen the PR or upgrade to it yourself)
    • @dependabot ignore this minor version will close this PR and stop Dependabot creating any more for this minor version (unless you reopen the PR or upgrade to it yourself)
    • @dependabot ignore this dependency will close this PR and stop Dependabot creating any more for this dependency (unless you reopen the PR or upgrade to it yourself)
  • 2

    Bump go.uber.org/zap from 1.22.0 to 1.23.0

    Bumps go.uber.org/zap from 1.22.0 to 1.23.0.

    Release notes

    Sourced from go.uber.org/zap's releases.

    v1.23.0

    Enhancements:

    • #1147[]: Add a zapcore.LevelOf function to determine the level of a LevelEnabler or Core.
    • #1155[]: Add zap.Stringers field constructor to log arrays of objects that implement String() string.

    #1147: uber-go/zap#1147 #1155: uber-go/zap#1155

    Changelog

    Sourced from go.uber.org/zap's changelog.

    1.23.0 (24 Aug 2022)

    Enhancements:

    • #1147[]: Add a zapcore.LevelOf function to determine the level of a LevelEnabler or Core.
    • #1155[]: Add zap.Stringers field constructor to log arrays of objects that implement String() string.

    #1147: uber-go/zap#1147 #1155: uber-go/zap#1155

    Commits

    Dependabot compatibility score

    Dependabot will resolve any conflicts with this PR as long as you don't alter it yourself. You can also trigger a rebase manually by commenting @dependabot rebase.


    Dependabot commands and options

    You can trigger Dependabot actions by commenting on this PR:

    • @dependabot rebase will rebase this PR
    • @dependabot recreate will recreate this PR, overwriting any edits that have been made to it
    • @dependabot merge will merge this PR after your CI passes on it
    • @dependabot squash and merge will squash and merge this PR after your CI passes on it
    • @dependabot cancel merge will cancel a previously requested merge and block automerging
    • @dependabot reopen will reopen this PR if it is closed
    • @dependabot close will close this PR and stop Dependabot recreating it. You can achieve the same result by closing it manually
    • @dependabot ignore this major version will close this PR and stop Dependabot creating any more for this major version (unless you reopen the PR or upgrade to it yourself)
    • @dependabot ignore this minor version will close this PR and stop Dependabot creating any more for this minor version (unless you reopen the PR or upgrade to it yourself)
    • @dependabot ignore this dependency will close this PR and stop Dependabot creating any more for this dependency (unless you reopen the PR or upgrade to it yourself)
  • 3

    Bump github.com/spf13/cobra from 1.5.0 to 1.6.1

    Bumps github.com/spf13/cobra from 1.5.0 to 1.6.1.

    Release notes

    Sourced from github.com/spf13/cobra's releases.

    v1.6.1

    Bug fixes 🐛

    • Fixes a panic when AddGroup isn't called before AddCommand(my-sub-command) is executed. This can happen within more complex cobra file structures that have many different inits to be executed. Now, the check for groups has been moved to ExecuteC and provides more flexibility when working with grouped commands - @​marckhouzam (and shout out to @​aawsome, @​andig and @​KINGSABRI for a deep investigation into this! 👏🏼)

    v1.6.0

    Summer 2022 Release

    Some exciting changes make their way to Cobra! Command completions continue to get better and better (including adding --help and --version automatic flags to the completions list). Grouping is now possible in your help output as well! And you can now use the OnFinalize method to cleanup things when all "work" is done. Checkout the full changelog below:


    Features 🌠

    Deprecation 👎🏼

    • ExactValidArgs is deprecated (but not being removed entirely). This is abit nuanced, so checkout #1643 for further information and the updated user_guide.md on how this may affect you (and how you can take advantage of the correct behavior in the validators): @​umarcor #1643

    Bug fixes 🐛

    Dependencies 🗳️

    Testing 🤔

    Docs ✏️

    Misc 💭

    ... (truncated)

    Commits

    Dependabot compatibility score

    Dependabot will resolve any conflicts with this PR as long as you don't alter it yourself. You can also trigger a rebase manually by commenting @dependabot rebase.


    Dependabot commands and options

    You can trigger Dependabot actions by commenting on this PR:

    • @dependabot rebase will rebase this PR
    • @dependabot recreate will recreate this PR, overwriting any edits that have been made to it
    • @dependabot merge will merge this PR after your CI passes on it
    • @dependabot squash and merge will squash and merge this PR after your CI passes on it
    • @dependabot cancel merge will cancel a previously requested merge and block automerging
    • @dependabot reopen will reopen this PR if it is closed
    • @dependabot close will close this PR and stop Dependabot recreating it. You can achieve the same result by closing it manually
    • @dependabot ignore this major version will close this PR and stop Dependabot creating any more for this major version (unless you reopen the PR or upgrade to it yourself)
    • @dependabot ignore this minor version will close this PR and stop Dependabot creating any more for this minor version (unless you reopen the PR or upgrade to it yourself)
    • @dependabot ignore this dependency will close this PR and stop Dependabot creating any more for this dependency (unless you reopen the PR or upgrade to it yourself)
  • 4

    Add `@latest` to installation instructions

    Just a minor tweak to avoid

    $ go install github.com/bufbuild/buf-language-server/cmd/bufls
    go: 'go install' requires a version when current directory is not in a module
            Try 'go install github.com/bufbuild/buf-language-server/cmd/bufls@latest' to install the latest version
    
  • 5

    Bump github.com/bufbuild/buf from 1.10.0 to 1.11.0

    Bumps github.com/bufbuild/buf from 1.10.0 to 1.11.0.

    Release notes

    Sourced from github.com/bufbuild/buf's releases.

    v1.11.0

    What's Changed

    • buf generate now batches remote plugin generation calls for improved performance.
    • Update optimize_for option in managed mode, allowing a default value for optimize_for for all files, except and override, which both behave similarly to other except and override options. Specifying an optimize_for value in the earlier versions is equivalent to having a optimize_for with that value as default.

    Full Changelog: https://github.com/bufbuild/buf/compare/v1.10.0...v1.11.0

    Changelog

    Sourced from github.com/bufbuild/buf's changelog.

    [v1.11.0] - 2022-12-19

    • buf generate now batches remote plugin generation calls for improved performance.
    • Update optimize_for option in managed mode, allowing a default value for optimize_for for all files, except and override, which both behave similarly to other except and override options. Specifying an optimize_for value in the earlier versions is equivalent to having a optimize_for with that value as default.
    Commits

    Dependabot compatibility score

    Dependabot will resolve any conflicts with this PR as long as you don't alter it yourself. You can also trigger a rebase manually by commenting @dependabot rebase.


    Dependabot commands and options

    You can trigger Dependabot actions by commenting on this PR:

    • @dependabot rebase will rebase this PR
    • @dependabot recreate will recreate this PR, overwriting any edits that have been made to it
    • @dependabot merge will merge this PR after your CI passes on it
    • @dependabot squash and merge will squash and merge this PR after your CI passes on it
    • @dependabot cancel merge will cancel a previously requested merge and block automerging
    • @dependabot reopen will reopen this PR if it is closed
    • @dependabot close will close this PR and stop Dependabot recreating it. You can achieve the same result by closing it manually
    • @dependabot ignore this major version will close this PR and stop Dependabot creating any more for this major version (unless you reopen the PR or upgrade to it yourself)
    • @dependabot ignore this minor version will close this PR and stop Dependabot creating any more for this minor version (unless you reopen the PR or upgrade to it yourself)
    • @dependabot ignore this dependency will close this PR and stop Dependabot creating any more for this dependency (unless you reopen the PR or upgrade to it yourself)
  • 6

    Bump go.uber.org/multierr from 1.8.0 to 1.9.0

    Bumps go.uber.org/multierr from 1.8.0 to 1.9.0.

    Release notes

    Sourced from go.uber.org/multierr's releases.

    v1.9.0

    • Add AppendFunc that allow passsing functions to similar to AppendInvoke.

    • Bump up yaml.v3 dependency to 3.0.1.

    Changelog

    Sourced from go.uber.org/multierr's changelog.

    v1.9.0 (2022-12-12)

    • Add AppendFunc that allow passsing functions to similar to AppendInvoke.

    • Bump up yaml.v3 dependency to 3.0.1.

    Commits

    Dependabot compatibility score

    Dependabot will resolve any conflicts with this PR as long as you don't alter it yourself. You can also trigger a rebase manually by commenting @dependabot rebase.


    Dependabot commands and options

    You can trigger Dependabot actions by commenting on this PR:

    • @dependabot rebase will rebase this PR
    • @dependabot recreate will recreate this PR, overwriting any edits that have been made to it
    • @dependabot merge will merge this PR after your CI passes on it
    • @dependabot squash and merge will squash and merge this PR after your CI passes on it
    • @dependabot cancel merge will cancel a previously requested merge and block automerging
    • @dependabot reopen will reopen this PR if it is closed
    • @dependabot close will close this PR and stop Dependabot recreating it. You can achieve the same result by closing it manually
    • @dependabot ignore this major version will close this PR and stop Dependabot creating any more for this major version (unless you reopen the PR or upgrade to it yourself)
    • @dependabot ignore this minor version will close this PR and stop Dependabot creating any more for this minor version (unless you reopen the PR or upgrade to it yourself)
    • @dependabot ignore this dependency will close this PR and stop Dependabot creating any more for this dependency (unless you reopen the PR or upgrade to it yourself)
  • 7

    goto definition being very slow?

    I've been giving buf-language-server a try.

    I added it to my ~/.config/helix/languages.toml:

    [[language]]
    name = "protobuf"
    scope = "source.proto"
    injection-regex = "protobuf"
    file-types = ["proto"]
    roots = []
    comment-token = "//"
    language-server = { command = "bufls", args = [ "serve" ] }
    indent = { tab-width = 2, unit = "  " }
    

    I typed gd on a symbol. For some reason, it takes 3-5 seconds for it to jump, even if the symbol is in the same file?

  • 8

    Would linting also be a potential candidate?

    I love the idea of a proper buf ls! I was wondering if it also would include diagnostics and formatting? It would make https://github.com/bufbuild/buf/issues/1035 probably obsolete.

    EDIT: I just saw https://github.com/bufbuild/buf/pull/1345 which fixes the mentioned issue. So oh well, not that relevant for the beginning then. If it gets merged then it should be possible to run the bufcli and bufls in parallel.