Vinicius Stock
2 years of the Ruby LSP
Current state and future
Slide 2
Slide 2 text
Vinicius Stock
Sta
ff
dev @ Ruby DX team
Shopify
X: @vinistock
GitHub: @vinistock
https://vinistock.com
Slide 3
Slide 3 text
What is the Ruby LSP?
Slide 4
Slide 4 text
Ruby LSP
https://github.com/Shopify/vscode-ruby-lsp
https://github.com/Shopify/ruby-lsp
https://code.visualstudio.com/docs/languages/ruby
A language server for Ruby made in Ruby
Slide 5
Slide 5 text
Definition, hover and completion
Slide 6
Slide 6 text
Code lens
Slide 7
Slide 7 text
Formatting, diagnostics and quick fixes
Slide 8
Slide 8 text
Dependencies view
Slide 9
Slide 9 text
What is a language server?
Slide 10
Slide 10 text
It's a separate process that
serves features for editors
require "open3"
stdin, stderr, stdout, wait_thr =
Open3.popen3("ruby-lsp")
# Uses stdin to send requests
# Uses stdout to receive responses
Slide 13
Slide 13 text
require "open3"
stdin, stderr, stdout, wait_thr =
Open3.popen3("ruby-lsp")
# Uses stdin to send requests
# Uses stdout to receive responses
# Communication happens using JSON
Slide 14
Slide 14 text
require "open3"
stdin, stderr, stdout, wait_thr =
Open3.popen3("ruby-lsp")
# Uses stdin to send requests
# Uses stdout to receive responses
# Communication happens using JSON
# Prints all stderr to the editor's output
Slide 15
Slide 15 text
Notifications Requests
No response expected Response expected
Slide 16
Slide 16 text
EDITOR SERVER
STDOUT
STDIN
textDocument/didOpen
class Foo
end
textDocument/foldingRange
textDocument/foldingRange
textDocument/didChange
class Foo
def bar
end
end
textDocument/foldingRange
textDocument/foldingRange
Slide 17
Slide 17 text
Open or edit
fi
le
De
fi
nition, references,
rename
Notify the
server
Automatic Manual
Trigger automatic
requests
Trigger desired
request
Slide 18
Slide 18 text
What's the advantage of language servers?
Slide 19
Slide 19 text
Vim
Ruby
plugin
Sublime
VS Code
Emacs
Ruby
plugin
Ruby
plugin
Ruby
plugin
Go plugin
Go plugin
Go plugin
Go plugin
Rust
plugin
Rust
plugin
Rust
plugin
Rust
plugin
Slide 20
Slide 20 text
Vim
Ruby
LSP
Sublime
VS Code
Emacs
Slide 21
Slide 21 text
How does the Ruby LSP
understands code?
Analyzing Ruby code
Slide 22
Slide 22 text
class Foo
def bar
end
end
{
"textDocument": {
"uri": "file:
/
/
/
foo.rb"
}
}
[
{
"startLine": 1,
"endLine": 1,
"kind": "region"
}
]
Turning a Ruby code string into a
useful object
Parsing
Slide 25
Slide 25 text
class Foo
def bar
"Hey!"
end
end
CLASS
CONST (Foo) DEF (bar)
STRING (Hey!)
Slide 26
Slide 26 text
Double dispatch visitor
Allows traversing the AST with
dedicated logic in an organized way
Slide 27
Slide 27 text
class Visitor
def visit(node)
node.accept(self)
end
end
Slide 28
Slide 28 text
class ClassNode
def accept(visitor)
visitor.visit_class_node(self)
end
end
Slide 29
Slide 29 text
class Visitor
def visit_child_nodes(node)
node.child_nodes.each do |child|
visit(child)
end
end
alias_method :visit_class_node,
:visit_child_nodes
alias_method :visit_module_node,
:visit_child_nodes
end
Slide 30
Slide 30 text
class MyVisitor < Prism
:
:
Visitor
def visit_class_node(node)
puts "Hi,
#
{
node.constant_path.full_name}"
super
end
end
Observer
Allows us to have multiple concerns
triggered by the same events
Slide 33
Slide 33 text
dispatcher = Dispatcher.new
dispatcher.on(:certain_event) do
do_something!
end
Slide 34
Slide 34 text
dispatcher = Dispatcher.new
dispatcher.on(:certain_event) do
do_something!
end
dispatcher.fire(:certain_event)
Slide 35
Slide 35 text
Ruby LSP uses a mix of both
Slide 36
Slide 36 text
The dispatcher is a visitor that
fi
res events for each node
encountered in the AST
Slide 37
Slide 37 text
class Dispatcher < Prism
:
:
Visitor
def initialize
@listeners = Hash.new do |h, k|
h[k] = []
end
end
end
Slide 38
Slide 38 text
class Dispatcher < Prism
:
:
Visitor
def register(listener, *events)
events.each do |e|
@listeners[e]
<
<
listener
end
end
end
Slide 39
Slide 39 text
class Dispatcher < Prism
:
:
Visitor
def visit_class_node(node)
@listeners[:on_class_node].each do |l|
l.on_class_node(node)
end
super
end
end
Slide 40
Slide 40 text
All features are listeners that respond to
speci
fi
c nodes being found in the AST
Slide 41
Slide 41 text
class Foo
def bar
"Hey!"
end
end
Slide 42
Slide 42 text
CLASS
CONST (Foo) DEF (bar)
STRING (Hey!)
Listener 1
classes
Dispatcher
Listener 2
classes and
methods
Event
Event
Event
Slide 43
Slide 43 text
The Ruby LSP computes 5 distinct features
in a single traversal of the AST
Slide 44
Slide 44 text
Traversing our ~270 line implementation of
folding range once produces ~1000 visits
Slide 45
Slide 45 text
Implementing folding range
Creating a listener to fold class
declarations
Slide 46
Slide 46 text
class FoldingRange
def initialize(dispatcher)
dispatcher.register(
self,
:on_class_node
)
@ranges = []
end
end
Slide 47
Slide 47 text
class FoldingRange
def on_class_node(node)
loc = node.location
@ranges
<
<
{
startLine: loc.start_line,
endLine: loc.end_line,
kind: "region"
}
end
end
ast = Prism.parse_file("foo.rb").value
dispatcher = Prism
:
:
Dispatcher.new
listener = FoldingRange.new(dispatcher)
dispatcher.dispatch(ast)
# The listener's response will be populated
# with all ranges
Slide 53
Slide 53 text
How would we compute many
features during the same traversal of
the AST?
And we concluded our folding range
implementation 🎉
Slide 56
Slide 56 text
What's next for the Ruby LSP?
• Making Ruby activation more robust and
improving error handling
• Full method support for de
fi
nition, hover and
completion and signature help
• Refactors
Slide 57
Slide 57 text
• Rename
• Occurrences
• Show type hierarchy
• More debugging features
What's next for the Ruby LSP?
Slide 58
Slide 58 text
Great DX comes from good
integration
Future bets
Slide 59
Slide 59 text
The Ruby tooling ecosystem
is fragmented
Future bets
Slide 60
Slide 60 text
Many formatters, linters, type checkers, test
frameworks, documentation tools, version
managers, web frameworks...
Future bets
Slide 61
Slide 61 text
We want to provide a one
click set up experience
Future bets
Slide 62
Slide 62 text
You get features based on the tools your
application uses without having to
con
fi
gure anything
Future bets
Slide 63
Slide 63 text
Addons
• A way for other gems to enhance Ruby LSP features
• The Ruby LSP should automatically detect which addon
to load based on your project's dependencies
• Rails addon: https://github.com/Shopify/ruby-lsp-rails
Slide 64
Slide 64 text
Challenges with addons
• How to detect which addons to install based on the
dependency?
• Should the addons be embedded in the gems they are
related to? Or should they be separate gems?
• How do we allow for con
fi
guration that is editor
agnostic? A `~/.ruby-lsprc`
fi
le?
Slide 65
Slide 65 text
We need your help
Slide 66
Slide 66 text
We can de
fi
nitely have a one-click set up experience
Slide 67
Slide 67 text
Thank you!
Slide 68
Slide 68 text
• https://github.com/Shopify/vscode-ruby-lsp
• https://github.com/Shopify/ruby-lsp
• https://github.com/Shopify/ruby-lsp-rails
• Font: https://github.com/microsoft/cascadia-code
• https://microsoft.github.io/language-server-protocol/speci
fi
cations/lsp/
3.17/speci
fi
cation/
• All videos/screenshots made with VS Code
References