Class: TRuby::LSPServer

Inherits:
Object
  • Object
show all
Defined in:
lib/t_ruby/lsp_server.rb

Overview

LSP (Language Server Protocol) Server for T-Ruby Provides IDE integration with autocomplete, diagnostics, and navigation

Defined Under Namespace

Modules: CompletionItemKind, DiagnosticSeverity, ErrorCodes, SemanticTokenModifiers, SemanticTokenTypes

Constant Summary collapse

VERSION =
"0.1.0"
SEMANTIC_TOKEN_TYPES =

Token type names for capability registration

%w[
  namespace type class enum interface struct typeParameter
  parameter variable property enumMember event function method
  macro keyword modifier comment string number regexp operator
].freeze
SEMANTIC_TOKEN_MODIFIERS =

Token modifier names

%w[
  declaration definition readonly static deprecated
  abstract async modification documentation defaultLibrary
].freeze
BUILT_IN_TYPES =

Built-in types for completion

%w[String Integer Boolean Array Hash Symbol void nil].freeze
TYPE_KEYWORDS =

Type keywords for completion

%w[type interface def end].freeze

Instance Method Summary collapse

Constructor Details

#initialize(input: $stdin, output: $stdout) ⇒ LSPServer

Returns a new instance of LSPServer.



118
119
120
121
122
123
124
125
# File 'lib/t_ruby/lsp_server.rb', line 118

def initialize(input: $stdin, output: $stdout)
  @input = input
  @output = output
  @documents = {}
  @initialized = false
  @shutdown_requested = false
  @type_alias_registry = TypeAliasRegistry.new
end

Instance Method Details

#handle_message(message) ⇒ Object

Handle an incoming message



187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
# File 'lib/t_ruby/lsp_server.rb', line 187

def handle_message(message)
  return error_response(nil, ErrorCodes::PARSE_ERROR, "Parse error") if message["error"]

  method = message["method"]
  params = message["params"] || {}
  id = message["id"]

  # Check if server is initialized for non-init methods
  if !@initialized && method != "initialize" && method != "exit"
    return error_response(id, ErrorCodes::SERVER_NOT_INITIALIZED, "Server not initialized")
  end

  result = dispatch_method(method, params, id)

  # For notifications (no id), don't send a response
  return nil if id.nil?

  if result.is_a?(Hash) && result[:error]
    error_response(id, result[:error][:code], result[:error][:message])
  else
    success_response(id, result)
  end
end

#read_messageObject

Read a single LSP message from input



139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
# File 'lib/t_ruby/lsp_server.rb', line 139

def read_message
  # Read headers
  headers = {}
  loop do
    line = @input.gets
    return nil if line.nil?

    line = line.strip
    break if line.empty?

    if line =~ /^([^:]+):\s*(.+)$/
      headers[Regexp.last_match(1)] = Regexp.last_match(2)
    end
  end

  content_length = headers["Content-Length"]&.to_i
  return nil unless content_length&.positive?

  # Read content
  content = @input.read(content_length)
  return nil if content.nil?

  JSON.parse(content)
rescue JSON::ParserError => e
  { "error" => "Parse error: #{e.message}" }
end

#runObject

Main run loop for the LSP server



128
129
130
131
132
133
134
135
136
# File 'lib/t_ruby/lsp_server.rb', line 128

def run
  loop do
    message = read_message
    break if message.nil?

    response = handle_message(message)
    send_response(response) if response
  end
end

#send_notification(method, params) ⇒ Object

Send a notification (no response expected)



177
178
179
180
181
182
183
184
# File 'lib/t_ruby/lsp_server.rb', line 177

def send_notification(method, params)
  notification = {
    "jsonrpc" => "2.0",
    "method" => method,
    "params" => params,
  }
  send_response(notification)
end

#send_response(response) ⇒ Object

Send a response message



167
168
169
170
171
172
173
174
# File 'lib/t_ruby/lsp_server.rb', line 167

def send_response(response)
  return if response.nil?

  content = JSON.generate(response)
  message = "Content-Length: #{content.bytesize}\r\n\r\n#{content}"
  @output.write(message)
  @output.flush
end