Skip to content

Instantly share code, notes, and snippets.

@tenderlove
Created April 20, 2023 16:36
Show Gist options
  • Star 9 You must be signed in to star a gist
  • Fork 0 You must be signed in to fork a gist
  • Save tenderlove/9a18be7a27ba7a074a8ba8fbf554e794 to your computer and use it in GitHub Desktop.
Save tenderlove/9a18be7a27ba7a074a8ba8fbf554e794 to your computer and use it in GitHub Desktop.
#!/Users/aaron/.rubies/arm64/ruby-trunk/bin/ruby
# This is a demo language server for Ruby, written in Ruby. It just checks
# the syntax of Ruby programs when you save them.
#
# Configure this in Vim by adding the vim-lsp plugin, then doing this
# in your vimrc:
#
# au User lsp_setup
# \ lsp#register_server({
# \ 'name': 'LSP Test',
# \ 'cmd': ["/Users/aaron/git/lsp-stream/syntax-check.rb"],
# \ 'allowlist': ['ruby'],
# \ })
#
# Make sure this script is executable and also update the paths to the script
# as well as the #! above
require "json"
module LSP
class Writer
def initialize
@io = $stdout.binmode
end
def write response
str = JSON.dump(response.merge("jsonrpc" => "2.0"))
@io.write "Content-Length: #{str.bytesize}\r\n"
@io.write "\r\n"
@io.write str
@io.flush
end
end
class Reader
def initialize
@io = $stdin.binmode
end
def read
buffer = @io.gets("\r\n\r\n")
content_length = buffer.match(/Content-Length: (\d+)/i)[1].to_i
message = @io.read(content_length)
JSON.parse message, symbolize_names: true
end
end
class Events
DISPATCH = {
"initialize" => :on_initialize,
"textDocument/didSave" => :did_save
}
def handle method, message, writer
send DISPATCH.fetch(method) { :unknown }, message, writer
end
def on_initialize message, writer
result = {
"capabilities" => {
"textDocumentSync" => { "openClose" => true, "change" => 1,"save" => true }
}
}
writer.write(id: message[:id], result: result)
end
def check_syntax file
RubyVM::InstructionSequence.compile_file(file)
nil
rescue SyntaxError => e
e # only return syntax errors
rescue Exception
nil # ignore anything else
end
def did_save message, writer
doc = message.dig(:params, :textDocument)
file = doc[:uri].delete_prefix("file://")
result = { :uri => doc[:uri], :diagnostics => [ ] }
error = check_syntax file
if error
line_number = error.message[/(?<=:)\d+/].to_i
line = File.readlines(file)[line_number - 1]
result = {
:uri => doc[:uri],
:diagnostics => [
{
"range" => {
"start" => { "character" => 0, "line" => line_number - 1 },
"end" => { "character" => line.bytesize, "line" => line_number - 1 },
},
"message" => error.message.lines.first,
"severity" => 1
},
],
}
end
writer.write(method: "textDocument/publishDiagnostics", params: result)
end
def unknown message, writer; end
end
def self.run
reader = Reader.new
writer = Writer.new
# Handle events
subscriber = LSP::Events.new
loop do
# Read an event
message = reader.read
# Ask the handler to handle the event
subscriber.handle message[:method], message, writer
end
end
end
LSP.run
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment