RubyのLSP実装の簡易比較

RubyのLSP実装の機能の簡易の比較をしてみた。対象は以下。

  • solargraph 0.47.2
  • sorbet 0.5.9204
  • ruby-lsp 0.3.6
  • typeprof 0.21.3
  • steep 1.3.0

現時点ではSorbetはRuby 2.7までしかサポートしていなくて、typeprofのLSPはRuby 3しかサポートしていない。そのためtypeprof以外をRuby 2.7で、typeprofを Ruby 3.1で動作させる。

# Gemfile.lsp27
gem 'solargraph', require: false
gem 'ruby-lsp', require: false
gem 'steep', require: false
gem 'sorbet'
gem 'sorbet-runtime'
gem 'tapioca', require: false

# Gemfile.lsp3
gem 'typeprof', require: false
# lsp_test.rb
require "language_server-protocol"

name = ARGV.shift
cmd = case name
      when "solargraph"
        %w(solargraph stdio)
      when "steep"
        %w(steep langserver)
      when "sorbet"
        %w(srb typecheck --lsp --disable-watchman)
      when "ruby-lsp"
        %w(ruby-lsp)
      when "typeprof"
        %w(typeprof --lsp --stdio)
      end
io = IO.popen(["bundle", "exec", *cmd], "w+")

LSP = LanguageServer::Protocol
writer = LSP::Transport::Io::Writer.new(io)
reader = LSP::Transport::Io::Reader.new(io)

begin
  writer.write(
    id: 0,
    method: :initialize,
    params: {
      processId: $$,
      rootUri: "file:///dummy",
      capabilities: {},
      initializationOptions: { # ruby-lspはこれがないとNoMethodErrorでコケる
        enabledFeatures: [
          :documentSymbols,
          :documentLink,
          :hover,
          :foldingRanges,
          #:semanticHighlighting, # これを入れるとNameErrorでコケる
          :diagnostics,
          :onTypeFormatting,
          :inlayHint,
          :selectionRanges,
          :formatting,
          :documentHighlights,
          :codeActions,
        ],
      },
    }
  )
  puts "[#{name}]"
  reader.read do |res|
    if res[:error]
      puts "Error"
      pp res
    else
      pp res.dig(:result, :capabilities)
    end
    break
  end

  writer.write(id: 1, method: :shutdown)
  reader.read do |res|
    # pp res
    break
  end
ensure
  io.close_write
end

実行結果:

$ rbenv shell 2.7.6
$ export BUNDLE_GEMFILE=Gemfile.lsp27
$ bundle install
...(略)...
$ bundle exec tapioca init
      create  sorbet/config
...(略)...
$ bundle exec steep init
Writing Steepfile...
$ for name in solargraph sorbet ruby-lsp steep; do ruby lsp_test.rb "$name" 2>/dev/null; done
[solargraph]
{:textDocumentSync=>2,
 :workspace=>
  {:workspaceFolders=>{:supported=>true, :changeNotifications=>true}},
 :completionProvider=>
  {:resolveProvider=>true, :triggerCharacters=>[".", ":", "@"]},
 :signatureHelpProvider=>{:triggerCharacters=>["(", ","]},
 :hoverProvider=>true,
 :documentSymbolProvider=>true,
 :definitionProvider=>true,
 :renameProvider=>{:prepareProvider=>true},
 :referencesProvider=>true,
 :workspaceSymbolProvider=>true,
 :foldingRangeProvider=>true,
 :documentHighlightProvider=>true}
[sorbet]
{:textDocumentSync=>1,
 :hoverProvider=>true,
 :completionProvider=>{:triggerCharacters=>[".", ":", "@", "#"]},
 :definitionProvider=>true,
 :typeDefinitionProvider=>true,
 :implementationProvider=>true,
 :referencesProvider=>true,
 :documentHighlightProvider=>false,
 :documentSymbolProvider=>false,
 :workspaceSymbolProvider=>true,
 :codeActionProvider=>
  {:codeActionKinds=>["quickfix", "source.fixAll.sorbet", "refactor.extract"]},
 :documentFormattingProvider=>false,
 :renameProvider=>{:prepareProvider=>true},
 :sorbetShowSymbolProvider=>true}
[ruby-lsp]
{:textDocumentSync=>{:openClose=>true, :change=>2},
 :hoverProvider=>{},
 :documentHighlightProvider=>true,
 :documentSymbolProvider=>
  {:symbolKind=>
    {:value_set=>
      [1,
       ...(略)...,
       26]},
   :hierarchicalDocumentSymbolSupport=>true},
 :codeActionProvider=>true,
 :documentLinkProvider=>{},
 :documentFormattingProvider=>true,
 :documentOnTypeFormattingProvider=>
  {:firstTriggerCharacter=>"{", :moreTriggerCharacter=>["\n", "|"]},
 :foldingRangeProvider=>{:lineFoldingOnly=>true},
 :selectionRangeProvider=>true,
 :inlayHintProvider=>{},
 :diagnosticProvider=>
  {:interFileDependencies=>false, :workspaceDiagnostics=>false}}
[steep]
{:textDocumentSync=>{:openClose=>true, :change=>2, :save=>{}},
 :completionProvider=>
  {:workDoneProgress=>true, :triggerCharacters=>[".", "@", ":"]},
 :hoverProvider=>
  {:workDoneProgress=>true, :partialResults=>true, :partialResult=>true},
 :definitionProvider=>true,
 :implementationProvider=>true,
 :workspaceSymbolProvider=>true}
$ rbenv shell 3.1.3
$ export BUNDLE_GEMFILE=Gemfile.lsp3
$ bundle install
$ ruby lsp_test.rb typeprof
[typeprof]
TypeProf for IDE is started successfully
{:textDocumentSync=>{:openClose=>true, :change=>2},
 :completionProvider=>{:triggerCharacters=>["."]},
 :signatureHelpProvider=>{:triggerCharacters=>["(", ","]},
 :codeLensProvider=>{:resolveProvider=>true},
 :executeCommandProvider=>
  {:commands=>["typeprof.createPrototypeRBS", "typeprof.enableSignature", "typeprof.disableSignature"]},
 :definitionProvider=>true,
 :typeDefinitionProvider=>true,
 :referencesProvider=>true}