Class: Ibex::LSP::Server

Inherits:
Object
  • Object
show all
Includes:
RequestHandlers
Defined in:
lib/ibex/lsp/server.rb,
sig/ibex/lsp/server.rbs

Overview

Coordinates JSON-RPC lifecycle, request responses, and protocol-safe logging.

Constant Summary collapse

REQUEST_METHODS =

Returns:

  • (Array[String])
%w[
  initialize shutdown textDocument/definition textDocument/references textDocument/prepareRename
  textDocument/rename textDocument/hover
].freeze
NOTIFICATION_METHODS =

Signature:

  • Array[String]

Returns:

  • (Array[String])
%w[
  initialized exit textDocument/didOpen textDocument/didChange textDocument/didSave textDocument/didClose
  $/cancelRequest
].freeze

Constants included from RequestHandlers

RequestHandlers::HANDLERS

Instance Method Summary collapse

Methods included from RequestHandlers

#cancel_request, #dispatch

Methods included from RequestSupport

#hash_member, #integer_member, #params_hash, #publish, #require_running!, #require_state!, #stale_publication?, #store, #string_member, #workspace

Methods included from NavigationHandlers

#definition, #hover, #prepare_rename, #references, #rename, #with_index

Methods included from DocumentHandlers

#did_change, #did_close, #did_open, #did_save

Methods included from InitializationHandlers

#capabilities, #exit_notification, #initialization_roots, #initialize_workspace, #initialized_notification, #shutdown_request

Constructor Details

#initialize(stdin:, stdout:, stderr:) ⇒ Server

Returns a new instance of Server.

RBS:

  • (stdin: untyped, stdout: untyped, stderr: untyped) -> void

Parameters:

  • stdin: (Object)
  • stdout: (Object)
  • stderr: (Object)


19
20
21
22
23
24
25
# File 'lib/ibex/lsp/server.rb', line 19

def initialize(stdin:, stdout:, stderr:)
  @transport = Transport.new(stdin, stdout)
  @stderr = stderr
  @state = :uninitialized
  @exit_requested = false
  @exit_status = nil #: Integer?
end

Instance Method Details

#log(message) ⇒ void

This method returns an undefined value.

RBS:

  • (String message) -> void

Parameters:

  • message (String)


141
142
143
# File 'lib/ibex/lsp/server.rb', line 141

def log(message)
  @stderr.puts(message)
end

#notification_envelope?(message) ⇒ Boolean

RBS:

  • (Hash[String, untyped] message) -> bool

Parameters:

  • message (Hash[String, untyped])

Returns:

  • (Boolean)


76
77
78
# File 'lib/ibex/lsp/server.rb', line 76

def notification_envelope?(message)
  !message.key?("id") && message["jsonrpc"] == "2.0" && message["method"].is_a?(String)
end

#process_message(message) ⇒ void

This method returns an undefined value.

RBS:

  • (Hash[String, untyped] message) -> void

Parameters:

  • message (Hash[String, untyped])


51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
# File 'lib/ibex/lsp/server.rb', line 51

def process_message(message)
  request_id = safe_request_id(message)
  notification = notification_envelope?(message)
  request_id = validate_envelope(message)
  method = message.fetch("method")
  validate_message_kind!(method, message.key?("id"))
  return if request_id.nil? && method.start_with?("$/")

  validate_lifecycle!(method)
  result = dispatch(method, message["params"])
  send_result(request_id, result) if request_id
rescue ProtocolError => e
  notification ? log(e.message) : send_error(request_id, e)
rescue StandardError => e
  error = ProtocolError.new("internal error: #{e.message}", code: -32_603)
  notification ? log(error.message) : send_error(request_id, error)
end

#read_messageHash[String, untyped]?

RBS:

  • () -> Hash[String, untyped]?

Returns:

  • (Hash[String, untyped], nil)


41
42
43
44
45
46
47
48
# File 'lib/ibex/lsp/server.rb', line 41

def read_message
  @transport.read_message
rescue ProtocolError => e
  send_error(nil, e)
  @exit_requested = true if e.fatal
  retry unless e.fatal
  nil
end

#runInteger

RBS:

  • () -> Integer

Returns:

  • (Integer)


28
29
30
31
32
33
34
35
36
# File 'lib/ibex/lsp/server.rb', line 28

def run
  until @exit_requested
    message = read_message
    break unless message

    process_message(message)
  end
  @exit_status || 1
end

#safe_request_id(message) ⇒ String, ...

RBS:

  • (Hash[String, untyped] message) -> (String | Integer)?

Parameters:

  • message (Hash[String, untyped])

Returns:

  • (String, Integer, nil)


70
71
72
73
# File 'lib/ibex/lsp/server.rb', line 70

def safe_request_id(message)
  value = message["id"]
  value if valid_request_id?(value)
end

#send_error(request_id, error) ⇒ void

This method returns an undefined value.

RBS:

  • (String | Integer | nil request_id, ProtocolError error) -> void

Parameters:



131
132
133
134
135
136
137
138
# File 'lib/ibex/lsp/server.rb', line 131

def send_error(request_id, error)
  @transport.write_message(
    "jsonrpc" => "2.0", "id" => request_id,
    "error" => { "code" => error.code, "message" => error.message }
  )
rescue ProtocolError => e
  log(e.message)
end

#send_result(request_id, result) ⇒ void

This method returns an undefined value.

RBS:

  • (String | Integer request_id, untyped result) -> void

Parameters:

  • request_id (String, Integer)
  • result (Object)


124
125
126
127
128
# File 'lib/ibex/lsp/server.rb', line 124

def send_result(request_id, result)
  @transport.write_message("jsonrpc" => "2.0", "id" => request_id, "result" => result)
rescue ProtocolError => e
  send_error(request_id, e)
end

#valid_request_id?(value) ⇒ Boolean

RBS:

  • (untyped value) -> bool

Parameters:

  • value (Object)

Returns:

  • (Boolean)


106
107
108
109
110
# File 'lib/ibex/lsp/server.rb', line 106

def valid_request_id?(value)
  return false unless value.is_a?(String) || value.is_a?(Integer)

  value.to_s.bytesize <= Limits::MAX_REQUEST_ID_BYTES
end

#validate_envelope(message) ⇒ String, ...

RBS:

  • (Hash[String, untyped] message) -> (String | Integer)?

Parameters:

  • message (Hash[String, untyped])

Returns:

  • (String, Integer, nil)


91
92
93
94
95
96
97
98
99
100
101
102
103
# File 'lib/ibex/lsp/server.rb', line 91

def validate_envelope(message)
  unless message["jsonrpc"] == "2.0" && message["method"].is_a?(String)
    raise ProtocolError.new("invalid JSON-RPC request", code: -32_600)
  end
  return nil unless message.key?("id")

  request_id = message["id"]
  unless valid_request_id?(request_id)
    raise ProtocolError.new("request id must be a bounded string or integer", code: -32_600)
  end

  request_id
end

#validate_lifecycle!(method) ⇒ void

This method returns an undefined value.

RBS:

  • (String method) -> void

Parameters:

  • method (String)


113
114
115
116
117
118
119
120
121
# File 'lib/ibex/lsp/server.rb', line 113

def validate_lifecycle!(method)
  return if method == "exit"
  return if @state == :uninitialized && method == "initialize"
  return if @state == :initialized && %w[initialized shutdown].include?(method)
  return if @state == :running

  code = @state == :uninitialized ? -32_002 : -32_600
  raise ProtocolError.new("request is invalid in #{@state} server state", code: code)
end

#validate_message_kind!(method, request) ⇒ void

This method returns an undefined value.

RBS:

  • (String method, bool request) -> void

Parameters:

  • method (String)
  • request (Boolean)


81
82
83
84
85
86
87
88
# File 'lib/ibex/lsp/server.rb', line 81

def validate_message_kind!(method, request)
  if REQUEST_METHODS.include?(method) && !request
    raise ProtocolError.new("#{method} must be a request", code: -32_600)
  end
  return unless NOTIFICATION_METHODS.include?(method) && request

  raise ProtocolError.new("#{method} must be a notification", code: -32_600)
end