Class: Textbringer::LSP::Client

Inherits:
Object
  • Object
show all
Defined in:
lib/textbringer/lsp/client.rb

Defined Under Namespace

Classes: ServerError, TimeoutError

Instance Attribute Summary collapse

Instance Method Summary collapse

Constructor Details

#initialize(command:, args: [], root_path:, server_name: nil, workspace_folders: nil) ⇒ Client



13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
# File 'lib/textbringer/lsp/client.rb', line 13

def initialize(command:, args: [], root_path:, server_name: nil, workspace_folders: nil)
  @command = command
  @args = args
  @root_path = root_path
  @server_name = server_name || command
  @workspace_folders = Array(workspace_folders || @root_path)
  @stdin = nil
  @stdout = nil
  @stderr = nil
  @wait_thr = nil
  @request_id = 0
  @pending_requests = {}
  @running = false
  @initialized = false
  @reader_thread = nil
  @mutex = Mutex.new
  @open_documents = {}
  @server_capabilities = {}
end

Instance Attribute Details

#root_pathObject (readonly)

Returns the value of attribute root_path.



11
12
13
# File 'lib/textbringer/lsp/client.rb', line 11

def root_path
  @root_path
end

#server_capabilitiesObject (readonly)

Returns the value of attribute server_capabilities.



11
12
13
# File 'lib/textbringer/lsp/client.rb', line 11

def server_capabilities
  @server_capabilities
end

#server_nameObject (readonly)

Returns the value of attribute server_name.



11
12
13
# File 'lib/textbringer/lsp/client.rb', line 11

def server_name
  @server_name
end

Instance Method Details

#completion(uri:, line:, character:, context: nil, &callback) ⇒ Object

Completion



131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
# File 'lib/textbringer/lsp/client.rb', line 131

def completion(uri:, line:, character:, context: nil, &callback)
  return unless @initialized

  params = {
    textDocument: { uri: uri },
    position: { line: line, character: character }
  }
  params[:context] = context if context

  send_request("textDocument/completion", params) do |result, error|
    if error
      callback.call(nil, error) if callback
    else
      items = normalize_completion_result(result)
      callback.call(items, nil) if callback
    end
  end
end

#did_change(uri:, version:, text: nil, range: nil, range_length: nil) ⇒ Object



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
# File 'lib/textbringer/lsp/client.rb', line 89

def did_change(uri:, version:, text: nil, range: nil, range_length: nil)
  return unless @initialized
  return unless @open_documents.key?(uri)

  # Support both full and incremental sync
  content_change = if range
                     # Incremental change
                     change = { range: range }
                     change[:rangeLength] = range_length if range_length
                     change[:text] = text if text
                     change
                   else
                     # Full document sync
                     { text: text }
                   end

  send_notification("textDocument/didChange", {
    textDocument: {
      uri: uri,
      version: version
    },
    contentChanges: [content_change]
  })
  @open_documents[uri] = version
end

#did_close(uri:) ⇒ Object



115
116
117
118
119
120
121
122
123
# File 'lib/textbringer/lsp/client.rb', line 115

def did_close(uri:)
  return unless @initialized
  return unless @open_documents.key?(uri)

  send_notification("textDocument/didClose", {
    textDocument: { uri: uri }
  })
  @open_documents.delete(uri)
end

#did_open(uri:, language_id:, version:, text:) ⇒ Object

Document synchronization



75
76
77
78
79
80
81
82
83
84
85
86
87
# File 'lib/textbringer/lsp/client.rb', line 75

def did_open(uri:, language_id:, version:, text:)
  return unless @initialized

  send_notification("textDocument/didOpen", {
    textDocument: {
      uri: uri,
      languageId: language_id,
      version: version,
      text: text
    }
  })
  @open_documents[uri] = version
end

#document_open?(uri) ⇒ Boolean



125
126
127
# File 'lib/textbringer/lsp/client.rb', line 125

def document_open?(uri)
  @open_documents.key?(uri)
end

#initialized?Boolean



69
70
71
# File 'lib/textbringer/lsp/client.rb', line 69

def initialized?
  @initialized
end

#running?Boolean



65
66
67
# File 'lib/textbringer/lsp/client.rb', line 65

def running?
  @running
end

#signature_help(uri:, line:, character:, context: nil, &callback) ⇒ Object

Signature Help



152
153
154
155
156
157
158
159
160
161
162
163
164
# File 'lib/textbringer/lsp/client.rb', line 152

def signature_help(uri:, line:, character:, context: nil, &callback)
  return unless @initialized

  params = {
    textDocument: { uri: uri },
    position: { line: line, character: character }
  }
  params[:context] = context if context

  send_request("textDocument/signatureHelp", params) do |result, error|
    callback.call(result, error) if callback
  end
end

#startObject



33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
# File 'lib/textbringer/lsp/client.rb', line 33

def start
  return if @running

  begin
    # Use Bundler's clean environment if available
    if defined?(Bundler)
      Bundler.with_unbundled_env do
        @stdin, @stdout, @stderr, @wait_thr =
          Open3.popen3(@command, *@args)
      end
    else
      @stdin, @stdout, @stderr, @wait_thr =
        Open3.popen3(@command, *@args)
    end
  rescue Errno::ENOENT
    Utils.message("LSP server command not found: #{@command}")
    return
  end

  @running = true
  initialize_server_sync
  start_reader_thread
end

#stopObject



57
58
59
60
61
62
63
# File 'lib/textbringer/lsp/client.rb', line 57

def stop
  return unless @running

  shutdown
  exit_server
  cleanup
end