yoda-language-server 0.6.2 → 0.7.0

Sign up to get free protection for your applications and to get access to all the features.
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