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.



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



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
113
114
115
# 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 "$/setTrace"
      VOID
    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

      Addon.addons.each(&:deactivate)
      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(uri)).parse if uri
      end

      @job_queue << job
    end
  end
end