Class: Spoom::LSP::Client

Inherits:
Object
  • Object
show all
Defined in:
lib/spoom/sorbet/lsp.rb

Instance Method Summary collapse

Constructor Details

#initialize(sorbet_cmd, *sorbet_args, path: ".") ⇒ Client

Returns a new instance of Client.



14
15
16
17
18
19
20
21
# File 'lib/spoom/sorbet/lsp.rb', line 14

def initialize(sorbet_cmd, *sorbet_args, path: ".")
  @id = 0
  Bundler.with_clean_env do
    opts = {}
    opts[:chdir] = path
    @in, @out, @err, @status = Open3.popen3([sorbet_cmd, *sorbet_args].join(" "), opts)
  end
end

Instance Method Details

#closeObject



188
189
190
191
192
193
# File 'lib/spoom/sorbet/lsp.rb', line 188

def close
  send(Request.new(next_id, "shutdown", nil))
  @in.close
  @out.close
  @err.close
end

#definitions(uri, line, column) ⇒ Object



110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
# File 'lib/spoom/sorbet/lsp.rb', line 110

def definitions(uri, line, column)
  json = send(Request.new(
    next_id,
    'textDocument/definition',
    {
      'textDocument' => {
        'uri' => uri,
      },
      'position' => {
        'line' => line,
        'character' => column,
      },
    }
  ))
  json['result'].map { |loc| Location.from_json(loc) }
end

#document_symbols(uri) ⇒ Object



175
176
177
178
179
180
181
182
183
184
185
186
# File 'lib/spoom/sorbet/lsp.rb', line 175

def document_symbols(uri)
  json = send(Request.new(
    next_id,
    'textDocument/documentSymbol',
    {
      'textDocument' => {
        'uri' => uri,
      },
    }
  ))
  json['result'].map { |loc| DocumentSymbol.from_json(loc) }
end

#hover(uri, line, column) ⇒ Object



75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
# File 'lib/spoom/sorbet/lsp.rb', line 75

def hover(uri, line, column)
  json = send(Request.new(
    next_id,
    'textDocument/hover',
    {
      'textDocument' => {
        'uri' => uri,
      },
      'position' => {
        'line' => line,
        'character' => column,
      },
    }
  ))
  return nil unless json['result']
  Hover.from_json(json['result'])
end

#next_idObject



23
24
25
# File 'lib/spoom/sorbet/lsp.rb', line 23

def next_id
  @id += 1
end

#open(workspace_path) ⇒ Object

LSP requests

Raises:



60
61
62
63
64
65
66
67
68
69
70
71
72
73
# File 'lib/spoom/sorbet/lsp.rb', line 60

def open(workspace_path)
  raise Error::AlreadyOpen, "Error: CLI already opened" if @open
  send(Request.new(
    next_id,
    'initialize',
    {
      'rootPath' => workspace_path,
      'rootUri' => "file://#{workspace_path}",
      'capabilities' => {},
    },
  ))
  send(Notification.new('initialized', {}))
  @open = true
end

#readObject



46
47
48
49
50
51
52
53
54
55
56
# File 'lib/spoom/sorbet/lsp.rb', line 46

def read
  json = JSON.parse(read_raw)

  # Handle error in the LSP protocol
  raise ResponseError.from_json(json['error']) if json['error']

  # Handle typechecking errors
  raise Error::Diagnostics.from_json(json['params']) if json['method'] == "textDocument/publishDiagnostics"

  json
end

#read_rawObject

Raises:



36
37
38
39
40
41
42
43
44
# File 'lib/spoom/sorbet/lsp.rb', line 36

def read_raw
  header = @out.gets

  # Sorbet returned an error and forgot to answer
  raise Error::BadHeaders, "bad response headers" unless header&.match?(/Content-Length: /)

  len = header.slice(::Range.new(16, nil)).to_i
  @out.read(len + 2) # +2 'cause of the final \r\n
end

#references(uri, line, column, include_decl = true) ⇒ Object



144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
# File 'lib/spoom/sorbet/lsp.rb', line 144

def references(uri, line, column, include_decl = true)
  json = send(Request.new(
    next_id,
    'textDocument/references',
    {
      'textDocument' => {
        'uri' => uri,
      },
      'position' => {
        'line' => line,
        'character' => column,
      },
      'context' => {
        'includeDeclaration' => include_decl,
      },
    }
  ))
  json['result'].map { |loc| Location.from_json(loc) }
end

#send(message) ⇒ Object



31
32
33
34
# File 'lib/spoom/sorbet/lsp.rb', line 31

def send(message)
  send_raw(message.to_json)
  read if message.is_a?(Request)
end

#send_raw(json_string) ⇒ Object



27
28
29
# File 'lib/spoom/sorbet/lsp.rb', line 27

def send_raw(json_string)
  @in.puts("Content-Length:#{json_string.length}\r\n\r\n#{json_string}")
end

#signatures(uri, line, column) ⇒ Object



93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
# File 'lib/spoom/sorbet/lsp.rb', line 93

def signatures(uri, line, column)
  json = send(Request.new(
    next_id,
    'textDocument/signatureHelp',
    {
      'textDocument' => {
        'uri' => uri,
      },
      'position' => {
        'line' => line,
        'character' => column,
      },
    }
  ))
  json['result']['signatures'].map { |loc| SignatureHelp.from_json(loc) }
end

#symbols(query) ⇒ Object



164
165
166
167
168
169
170
171
172
173
# File 'lib/spoom/sorbet/lsp.rb', line 164

def symbols(query)
  json = send(Request.new(
    next_id,
    'workspace/symbol',
    {
      'query' => query,
    }
  ))
  json['result'].map { |loc| DocumentSymbol.from_json(loc) }
end

#type_definitions(uri, line, column) ⇒ Object



127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
# File 'lib/spoom/sorbet/lsp.rb', line 127

def type_definitions(uri, line, column)
  json = send(Request.new(
    next_id,
    'textDocument/typeDefinition',
    {
      'textDocument' => {
        'uri' => uri,
      },
      'position' => {
        'line' => line,
        'character' => column,
      },
    }
  ))
  json['result'].map { |loc| Location.from_json(loc) }
end