Class: RubyLsp::Server

Inherits:
Object
  • Object
show all
Extended by:
T::Sig
Defined in:
lib/ruby_lsp/server.rb

Overview

rubocop:enable RubyLsp/UseLanguageServerAliases

Instance Method Summary collapse

Constructor Details

#initializeServer

Returns a new instance of Server.

[View source]

15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
# File 'lib/ruby_lsp/server.rb', line 15

def initialize
  @writer = T.let(Transport::Stdio::Writer.new, Transport::Stdio::Writer)
  @reader = T.let(Transport::Stdio::Reader.new, Transport::Stdio::Reader)
  @store = T.let(Store.new, Store)

  # The job queue is the actual list of requests we have to process
  @job_queue = T.let(Thread::Queue.new, Thread::Queue)
  # The jobs hash is just a way of keeping a handle to jobs based on the request ID, so we can cancel them
  @jobs = T.let({}, T::Hash[T.any(String, Integer), Job])
  @mutex = T.let(Mutex.new, Mutex)
  @worker = T.let(new_worker, Thread)

  # The messages queue includes requests and notifications to be sent to the client
  @message_queue = T.let(Thread::Queue.new, Thread::Queue)

  # The executor is responsible for executing requests
  @executor = T.let(Executor.new(@store, @message_queue), Executor)

  # Create a thread to watch the messages queue and send them to the client
  @message_dispatcher = T.let(
    Thread.new do
      current_request_id = 1

      loop do
        message = @message_queue.pop
        break if message.nil?

        @mutex.synchronize do
          case message
          when Notification
            @writer.write(method: message.message, params: message.params)
          when Request
            @writer.write(id: current_request_id, method: message.message, params: message.params)
            current_request_id += 1
          end
        end
      end
    end,
    Thread,
  )

  Thread.main.priority = 1
end

Instance Method Details

#startObject

[View source]

60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
# File 'lib/ruby_lsp/server.rb', line 60

def start
  warn("Starting Ruby LSP...")

  # Requests that have to be executed sequentially or in the main process are implemented here. All other requests
  # fall under the else branch which just pushes requests to the queue
  @reader.read do |request|
    case request[:method]
    when "initialize", "initialized", "textDocument/didOpen", "textDocument/didClose", "textDocument/didChange"
      result = @executor.execute(request)
      finalize_request(result, request)
    when "$/cancelRequest"
      # Cancel the job if it's still in the queue
      @mutex.synchronize { @jobs[request[:params][:id]]&.cancel }
    when "shutdown"
      warn("Shutting down Ruby LSP...")

      @message_queue.close
      # Close the queue so that we can no longer receive items
      @job_queue.close
      # Clear any remaining jobs so that the thread can terminate
      @job_queue.clear
      @jobs.clear
      # Wait until the thread is finished
      @worker.join
      @message_dispatcher.join
      @store.clear

      finalize_request(Result.new(response: nil), request)
    when "exit"
      # We return zero if shutdown has already been received or one otherwise as per the recommendation in the spec
      # https://microsoft.github.io/language-server-protocol/specification/#exit
      status = @store.empty? ? 0 : 1
      warn("Shutdown complete with status #{status}")
      exit(status)
    else
      # Default case: push the request to the queue to be executed by the worker
      job = Job.new(request: request, cancelled: false)

      @mutex.synchronize do
        # Remember a handle to the job, so that we can cancel it
        @jobs[request[:id]] = job

        # We must parse the document under a mutex lock or else we might switch threads and accept text edits in the
        # source. Altering the source reference during parsing will put the parser in an invalid internal state,
        # since it started parsing with one source but then it changed in the middle
        uri = request.dig(:params, :textDocument, :uri)
        @store.get(uri).parse if uri
      end

      @job_queue << job
    end
  end
end