yoda-language-server 0.6.2 → 0.7.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
checksums.yaml CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: 45f3bb7e65e24213b3d1c0e60110c08498d765e6a27732800d2c02e3508b03e4
4
- data.tar.gz: 4e99d7cba046557256be92cfcf747e194086c52dedd94e7db344cc6fb8e9a2b1
3
+ metadata.gz: 3c06ab67892292a50ff422a252b794cf49f38dad60a8b32e7faf14936a2b648e
4
+ data.tar.gz: 20546678432670fae9b613a6d55e23f6ee3b2c544d6a6b62ff3ff80d6bd3e376
5
5
  SHA512:
6
- metadata.gz: a278106001664f9468805400c571a333765c16f9557345d9295e2afee2a1a1a82e7d3a3022aaaefee04c7ee6b2c9d3dd23b3f634bca35c5150d5f99dd7cc8916
7
- data.tar.gz: d13b8b7bde1b5b0dc7028f8df270186e767bae83e901885288b677ca64c9e0326ef788eed7c1578a5a0b359fb33716c89fdad2f203244fb3b14eb796d3fca465
6
+ metadata.gz: 0eac21b1b1b73f3d3864c2cdb37c2f8df39c9be6841518b59282d5b89c8b64d54972c9c8b9318d35273250c9c740465d7c2156098e0629ee0df831a05ad41e2a
7
+ data.tar.gz: 9b9991e147783643687d6c259c45c9ab45cfa009df5a60a23f1dce36571c5dc9a61d8a12bb70d57a79f30bf2288ed24933ea84e4a54c0df583b820ed54357cc6
data/client/atom/main.js CHANGED
@@ -3,10 +3,10 @@ const { spawn } = require('child_process')
3
3
  const { resolve } = require('path')
4
4
 
5
5
  const busyMessages = {
6
- text_document_hover: 'Preparing hover help',
7
- text_document_signature_help: 'Preparing signature help',
8
- text_document_completion: 'Completing',
9
- text_document_definition: 'Finding definition',
6
+ 'textDocument/hover': 'Preparing hover help',
7
+ 'textDocument/signatureHelp': 'Preparing signature help',
8
+ 'textDocument/completion': 'Completing',
9
+ 'textDocument/definition': 'Finding definition',
10
10
  };
11
11
 
12
12
  class YodaClient extends AutoLanguageClient {
@@ -57,10 +57,10 @@ class YodaClient extends AutoLanguageClient {
57
57
  switch (eventBody.type) {
58
58
  case 'initialization':
59
59
  return this.handleInitializationEvent(eventBody);
60
- case 'text_document_hover':
61
- case 'text_document_completion':
62
- case 'text_document_signature_help':
63
- case 'text_document_completion':
60
+ case 'textDocument/hover':
61
+ case 'textDocument/completion':
62
+ case 'textDocument/signatureHelp':
63
+ case 'textDocument/completion':
64
64
  return this.handleBusyEvent(eventBody.type, eventBody);
65
65
  default:
66
66
  }
@@ -74,17 +74,24 @@ class YodaClient extends AutoLanguageClient {
74
74
  }
75
75
  }
76
76
 
77
- handleBusyEvent(handlerName, { phase, message }) {
77
+ handleBusyEvent(handlerName, { phase, id }) {
78
78
  switch (phase) {
79
79
  case 'begin':
80
80
  if (!this.busyHandlers[handlerName]) {
81
- this.busyHandlers[handlerName] = this.busySignalService.reportBusy("(Yoda) " + busyMessages[handlerName]);
81
+ this.busyHandlers[handlerName] = {
82
+ handler: this.busySignalService.reportBusy("(Yoda) " + (busyMessages[handlerName] || 'Processing')),
83
+ ids: new Set([]),
84
+ };
85
+ if (id) { this.busyHandlers[handlerName].ids.add(id); }
82
86
  }
83
87
  break;
84
88
  case 'end':
85
89
  if (this.busyHandlers[handlerName]) {
86
- this.busyHandlers[handlerName].dispose();
87
- this.busyHandlers[handlerName] = null;
90
+ if (id) { this.busyHandlers[handlerName].ids.delete(id); }
91
+ if (!this.busyHandlers[handlerName].ids.size) {
92
+ this.busyHandlers[handlerName].handler.dispose();
93
+ this.busyHandlers[handlerName] = null;
94
+ }
88
95
  }
89
96
  break;
90
97
  }
data/lib/yoda/commands.rb CHANGED
@@ -11,6 +11,7 @@ module Yoda
11
11
 
12
12
  class Top < Thor
13
13
  class_option :log_level, type: :string, desc: 'Set log level (debug info warn error fatal)'
14
+ class_option :profile, type: :boolean, desc: 'Set log level (debug info warn error fatal)'
14
15
 
15
16
  desc 'setup', 'Setup indexes for current Ruby version and project gems'
16
17
  option :force_build, type: :boolean, desc: "If enabled, (re)build current project's index forcibly"
@@ -41,11 +42,28 @@ module Yoda
41
42
 
42
43
  def process_class_options
43
44
  set_log_level
45
+ use_profiler_if_enabled
44
46
  end
45
47
 
46
48
  def set_log_level
47
49
  Yoda::Logger.log_level = options[:log_level].to_sym if options[:log_level]
48
50
  end
51
+
52
+ def use_profiler_if_enabled
53
+ if options[:profile]
54
+ require 'stackprof'
55
+ require 'securerandom'
56
+ Logger.info('Enabled profiler')
57
+ StackProf.start(mode: :wall, raw: true)
58
+
59
+ at_exit do
60
+ StackProf.stop
61
+ tmpfile_path = File.expand_path(SecureRandom.hex(12), Dir.tmpdir)
62
+ StackProf.results(tmpfile_path)
63
+ Logger.fatal("Dumped file to: #{tmpfile_path}")
64
+ end
65
+ end
66
+ end
49
67
  end
50
68
  end
51
69
  end
@@ -1,3 +1,5 @@
1
+ require 'concurrent'
2
+
1
3
  module Yoda
2
4
  class Instrument
3
5
  class Subscription
@@ -57,9 +59,19 @@ module Yoda
57
59
  # @return [Array<Subscription>]
58
60
  attr_reader :subscriptions
59
61
 
60
- # @return [Instrument]
61
- def self.instance
62
- @instance ||= new
62
+ class << self
63
+ # Returns Instrument instance (thread local).
64
+ # @return [Instrument]
65
+ def instance
66
+ local.value
67
+ end
68
+
69
+ private
70
+
71
+ # @return [Concurrent::ThreadLocalVar<Instrument>]
72
+ def local
73
+ @local ||= Concurrent::ThreadLocalVar.new { Instrument.new }
74
+ end
63
75
  end
64
76
 
65
77
  def initialize
data/lib/yoda/server.rb CHANGED
@@ -3,67 +3,41 @@ require 'securerandom'
3
3
 
4
4
  module Yoda
5
5
  class Server
6
+ require 'yoda/server/session'
7
+ require 'yoda/server/file_store'
8
+ require 'yoda/server/concurrent_writer'
6
9
  require 'yoda/server/notifier'
7
- require 'yoda/server/completion_provider'
8
- require 'yoda/server/signature_provider'
9
- require 'yoda/server/hover_provider'
10
- require 'yoda/server/definition_provider'
11
- require 'yoda/server/initialization_provider'
10
+ require 'yoda/server/providers'
11
+ require 'yoda/server/root_handler'
12
+ require 'yoda/server/lifecycle_handler'
12
13
  require 'yoda/server/deserializer'
13
- require 'yoda/server/session'
14
-
15
- LSP = ::LanguageServer::Protocol
16
14
 
17
- def self.deserialize(hash)
18
- Deserializer.new.deserialize(hash || {})
19
- end
20
-
21
- # @type ::LanguageServer::Protocol::Transport::Stdio::Reader
15
+ # @return [::LanguageServer::Protocol::Transport::Stdio::Reader]
22
16
  attr_reader :reader
23
17
 
24
- # @type ::LanguageServer::Protocol::Transport::Stdio::Writer
18
+ # @return [ConcurrentWriter]
25
19
  attr_reader :writer
26
20
 
27
- # @return [Responser]
28
- attr_reader :session
29
-
30
- # @type CompletionProvider
31
- attr_reader :completion_provider
32
-
33
- # @type HoverProvider
34
- attr_reader :hover_provider
35
-
36
- # @type SignatureProvider
37
- attr_reader :signature_provider
21
+ # @return [RootHandler]
22
+ attr_reader :root_handler
38
23
 
39
- # @type DefinitionProvider
40
- attr_reader :definition_provider
24
+ # Use this value as return value for notification handling
25
+ NO_RESPONSE = :no_response
41
26
 
42
- # @return [Array<Hash>]
43
- attr_reader :after_notifications
44
-
45
- def initialize
46
- @reader = LSP::Transport::Stdio::Reader.new
47
- @writer = LSP::Transport::Stdio::Writer.new
48
- @after_notifications = []
49
- end
50
-
51
- # @return [Notifier]
52
- def notifier
53
- @notifier ||= Notifier.new(self)
27
+ def initialize(
28
+ reader: LanguageServer::Protocol::Transport::Stdio::Reader.new,
29
+ writer: LanguageServer::Protocol::Transport::Stdio::Writer.new,
30
+ root_handler_class: RootHandler
31
+ )
32
+ @reader = reader
33
+ @writer = ConcurrentWriter.new(writer)
34
+ @root_handler = RootHandler.new(writer: @writer)
54
35
  end
55
36
 
56
37
  def run
57
38
  reader.read do |request|
58
39
  begin
59
- if result = callback(request)
60
- if result.is_a?(LanguageServer::Protocol::Interface::ResponseError)
61
- send_error(id: request[:id], error: result)
62
- else
63
- send_response(id: request[:id], result: result)
64
- end
65
- end
66
- process_after_notifications if session&.client_initialized
40
+ root_handler.handle(id: request[:id], method: request[:method].to_sym, params: deserialize(request[:params]))
67
41
  rescue StandardError => ex
68
42
  Logger.warn ex
69
43
  Logger.warn ex.backtrace
@@ -71,180 +45,8 @@ module Yoda
71
45
  end
72
46
  end
73
47
 
74
- def process_after_notifications
75
- while notification = after_notifications.pop
76
- send_notification(**notification)
77
- end
78
- end
79
-
80
- # @param method [String]
81
- # @param params [Object]
82
- def send_notification(method:, params:)
83
- writer.write(method: method, params: params)
84
- end
85
-
86
- # @param id [String]
87
- # @param result [Object]
88
- def send_response(id:, result:)
89
- writer.write(id: id, result: result)
90
- end
91
-
92
- # @param method [String]
93
- # @param error [Object]
94
- def send_error(id:, error:)
95
- writer.write(id: id, error: error)
96
- end
97
-
98
- def callback(request)
99
- if method_name = resolve(request_registrations, request[:method])
100
- send(method_name, self.class.deserialize(request[:params] || {}))
101
- elsif method_name = resolve(notification_registrations, request[:method])
102
- send(method_name, self.class.deserialize(request[:params] || {}))
103
- nil
104
- end
105
- end
106
-
107
- # @param hash [Hash]
108
- # @param key [String, Symbol]
109
- # @return [Symbol, nil]
110
- def resolve(hash, key)
111
- resolved = key.to_s.split('/').reduce(hash) do |scope, key|
112
- (scope || {})[key.to_sym]
113
- end
114
- resolved.is_a?(Symbol) && resolved
115
- end
116
-
117
- def request_registrations
118
- {
119
- initialize: :handle_initialize,
120
- shutdown: :handle_shutdown,
121
- textDocument: {
122
- completion: :handle_text_document_completion,
123
- hover: :handle_text_document_hover,
124
- signatureHelp: :handle_text_document_signature_help,
125
- definition: :handle_text_document_definition,
126
- },
127
- }
128
- end
129
-
130
- def notification_registrations
131
- {
132
- initialized: :handle_initialized,
133
- exit: :handle_exit,
134
- textDocument: {
135
- didChange: :handle_text_document_did_change,
136
- didOpen: :handle_text_document_did_open,
137
- didSave: :handle_text_document_did_save,
138
- },
139
- }
140
- end
141
-
142
- def handle_initialize(params)
143
- @session = Session.new(params[:root_uri])
144
- @completion_provider = CompletionProvider.new(@session)
145
- @hover_provider = HoverProvider.new(@session)
146
- @signature_provider = SignatureProvider.new(@session)
147
- @definition_provider = DefinitionProvider.new(@session)
148
-
149
- (InitializationProvider.new(session: session, notifier: notifier).provide || []).each { |notification| after_notifications.push(notification) }
150
-
151
- LSP::Interface::InitializeResult.new(
152
- capabilities: LSP::Interface::ServerCapabilities.new(
153
- text_document_sync: LSP::Interface::TextDocumentSyncOptions.new(
154
- change: LSP::Constant::TextDocumentSyncKind::FULL,
155
- save: LSP::Interface::SaveOptions.new(
156
- include_text: true,
157
- ),
158
- ),
159
- completion_provider: LSP::Interface::CompletionOptions.new(
160
- resolve_provider: false,
161
- trigger_characters: ['.', '@', '[', ':', '!', '<'],
162
- ),
163
- hover_provider: true,
164
- definition_provider: true,
165
- signature_help_provider: LSP::Interface::SignatureHelpOptions.new(
166
- trigger_characters: ['(', ','],
167
- ),
168
- ),
169
- )
170
- rescue => e
171
- LanguageServer::Protocol::Interface::ResponseError.new(
172
- message: "Failed to initialize yoda: #{e.class} #{e.message}",
173
- code: LanguageServer::Protocol::Constant::InitializeError::UNKNOWN_PROTOCOL_VERSION,
174
- data: LanguageServer::Protocol::Interface::InitializeError.new(retry: false),
175
- )
176
- end
177
-
178
- def handle_initialized(_params)
179
- session.client_initialized = true
180
- end
181
-
182
- def handle_shutdown(_params)
183
- end
184
-
185
- def handle_exit(_params)
186
- end
187
-
188
- module TextDocument
189
- module CompletionTrigggerKind
190
- INVOKED = 1
191
- TRIGGER_CHARACTER = 2
192
- end
193
-
194
- def handle_text_document_did_open(params)
195
- uri = params[:text_document][:uri]
196
- text = params[:text_document][:text]
197
- session.file_store.store(uri, text)
198
- end
199
-
200
- def handle_text_document_did_save(params)
201
- uri = params[:text_document][:uri]
202
-
203
- session.reparse_doc(uri)
204
- end
205
-
206
- def handle_text_document_did_change(params)
207
- uri = params[:text_document][:uri]
208
- text = params[:content_changes].first[:text]
209
- session.file_store.store(uri, text)
210
- end
211
-
212
- def handle_text_document_completion(params)
213
- uri = params[:text_document][:uri]
214
- position = params[:position]
215
-
216
- notifier.busy(type: :text_document_completion) do
217
- completion_provider&.complete(uri, position)
218
- end
219
- end
220
-
221
- def handle_text_document_hover(params)
222
- uri = params[:text_document][:uri]
223
- position = params[:position]
224
-
225
- notifier.busy(type: :text_document_hover) do
226
- hover_provider&.request_hover(uri, position)
227
- end
228
- end
229
-
230
- def handle_text_document_signature_help(params)
231
- uri = params[:text_document][:uri]
232
- position = params[:position]
233
-
234
- notifier.busy(type: :text_document_signature_help) do
235
- signature_provider&.provide(uri, position)
236
- end
237
- end
238
-
239
- def handle_text_document_definition(params)
240
- uri = params[:text_document][:uri]
241
- position = params[:position]
242
-
243
- notifier.busy(type: :text_document_definition) do
244
- definition_provider&.provide(uri, position)
245
- end
246
- end
48
+ def deserialize(hash)
49
+ Deserializer.new.deserialize(hash || {})
247
50
  end
248
- include TextDocument
249
51
  end
250
52
  end
@@ -0,0 +1,16 @@
1
+ module Yoda
2
+ class Server
3
+ # Wrapper class for writer to make thread safe
4
+ class ConcurrentWriter
5
+ # @param [::LanguageServer::Protocol::Transport::Stdio::Writer]
6
+ def initialize(channel)
7
+ @channel = channel
8
+ @mutex = Mutex.new
9
+ end
10
+
11
+ def write(*args)
12
+ @mutex.synchronize { @channel.write(*args) }
13
+ end
14
+ end
15
+ end
16
+ end
@@ -0,0 +1,57 @@
1
+ require 'concurrent'
2
+
3
+ module Yoda
4
+ class Server
5
+ class FileStore
6
+ def initialize
7
+ @cache = Concurrent::Map.new
8
+ end
9
+
10
+ # @param uri_string [String]
11
+ # @return [String, nil]
12
+ def get(uri_string)
13
+ @cache[uri_string]
14
+ end
15
+
16
+ # @param uri_string [String]
17
+ # @param text [String]
18
+ def store(uri_string, text)
19
+ return unless program_file_uri?(uri_string)
20
+ @cache[uri_string] = text
21
+ end
22
+
23
+ # @param uri_string [String]
24
+ def load(uri_string)
25
+ store(uri_string, read(uri_string))
26
+ end
27
+
28
+ # @param uri_string [String]
29
+ def read(uri_string)
30
+ path = self.class.path_of_uri(uri_string)
31
+ fail ArgumentError unless path
32
+ File.read(path)
33
+ end
34
+
35
+ # @param path [String]
36
+ def self.uri_of_path(path)
37
+ "file://#{File.expand_path(path)}"
38
+ end
39
+
40
+ # @param uri_string [String]
41
+ def self.path_of_uri(uri_string)
42
+ uri = URI.parse(uri_string)
43
+ return nil unless uri.scheme == 'file'
44
+ uri.path
45
+ rescue URI::InvalidURIError
46
+ nil
47
+ end
48
+
49
+ # @param uri_string [String]
50
+ def program_file_uri?(uri_string)
51
+ %w(.c .rb).include?(File.extname(URI.parse(uri_string).path))
52
+ rescue URI::InvalidURIError => _e
53
+ false
54
+ end
55
+ end
56
+ end
57
+ end