Skip to content

Commit b0e3a67

Browse files
committed
(GH-177) Add unregistering of capabilities
Previously client capabilities could be dynamically registered. This commit adds the logic and track to dynamically unregister a capability; * Adds the unregister_capability and parse_unregister_capability_response! methods * Adds the client/unregisterCapability notification handler * Adds tests for unregistering
1 parent 9362435 commit b0e3a67

File tree

3 files changed

+199
-2
lines changed

3 files changed

+199
-2
lines changed

lib/puppet-languageserver/language_client.rb

Lines changed: 51 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -44,7 +44,7 @@ def parse_lsp_configuration_settings!(settings = [{}])
4444
end
4545

4646
def capability_registrations(method)
47-
return [{ :registered => false, :state => :complete }] if @registrations[method].nil?
47+
return [{ :registered => false, :state => :complete }] if @registrations[method].nil? || @registrations[method].empty?
4848
@registrations[method].dup
4949
end
5050

@@ -56,7 +56,7 @@ def register_capability(method, options = {})
5656
if @registrations[method] && @registrations[method].select { |i| i[:state] == :pending }.count > 0
5757
# The protocol doesn't specify whether this is allowed and is probably per client specific. For the moment we will allow
5858
# the registration to be sent but log a message that something may be wrong.
59-
PuppetLanguageServer.log_message(:warn, "A dynamic registration for the #{method} method is already in progress")
59+
PuppetLanguageServer.log_message(:warn, "A dynamic registration/deregistration for the #{method} method is already in progress")
6060
end
6161

6262
params = LSP::RegistrationParams.new.from_h!('registrations' => [])
@@ -70,6 +70,31 @@ def register_capability(method, options = {})
7070
true
7171
end
7272

73+
def unregister_capability(method)
74+
if @registrations[method].nil?
75+
PuppetLanguageServer.log_message(:debug, "No registrations to deregister for the #{method}")
76+
return true
77+
end
78+
79+
params = LSP::UnregistrationParams.new.from_h!('unregisterations' => [])
80+
@registrations[method].each do |reg|
81+
next if reg[:id].nil?
82+
PuppetLanguageServer.log_message(:warn, "A dynamic registration/deregistration for the #{method} method, with id #{reg[:id]} is already in progress") if reg[:state] == :pending
83+
# Ignore registrations that don't need to be unregistered
84+
next if reg[:state] == :complete && !reg[:registered]
85+
params.unregisterations << LSP::Unregistration.new.from_h!('id' => reg[:id], 'method' => method)
86+
reg[:state] = :pending
87+
end
88+
89+
if params.unregisterations.count.zero?
90+
PuppetLanguageServer.log_message(:debug, "Nothing to deregister for the #{method} method")
91+
return true
92+
end
93+
94+
message_router.json_rpc_handler.send_client_request('client/unregisterCapability', params)
95+
true
96+
end
97+
7398
def parse_register_capability_response!(response, original_request)
7499
raise 'Response is not from client/registerCapability request' unless original_request['method'] == 'client/registerCapability'
75100

@@ -97,6 +122,30 @@ def parse_register_capability_response!(response, original_request)
97122
true
98123
end
99124

125+
def parse_unregister_capability_response!(response, original_request)
126+
raise 'Response is not from client/unregisterCapability request' unless original_request['method'] == 'client/unregisterCapability'
127+
128+
unless response.key?('result')
129+
original_request['params'].unregisterations.each do |reg|
130+
# Mark the registration as completed and failed
131+
@registrations[reg.method__lsp] = [] if @registrations[reg.method__lsp].nil?
132+
@registrations[reg.method__lsp].select { |i| i[:id] == reg.id && i[:registered] }.each { |i| i[:state] = :complete }
133+
@registrations[reg.method__lsp].delete_if { |i| i[:id] == reg.id && !i[:registered] }
134+
end
135+
return true
136+
end
137+
138+
original_request['params'].unregisterations.each do |reg|
139+
PuppetLanguageServer.log_message(:info, "Succesfully dynamically unregistered the #{reg.method__lsp} method")
140+
141+
# Remove registrations
142+
@registrations[reg.method__lsp] = [] if @registrations[reg.method__lsp].nil?
143+
@registrations[reg.method__lsp].delete_if { |i| i[:id] == reg.id }
144+
end
145+
146+
true
147+
end
148+
100149
private
101150

102151
def new_request_id

lib/puppet-languageserver/message_router.rb

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -321,6 +321,8 @@ def receive_response(response, original_request)
321321
case original_request['method']
322322
when 'client/registerCapability'
323323
client.parse_register_capability_response!(response, original_request)
324+
when 'client/unregisterCapability'
325+
client.parse_unregister_capability_response!(response, original_request)
324326
when 'workspace/configuration'
325327
return unless receive_response_succesful?(response)
326328
client.parse_lsp_configuration_settings!(response['result'])

spec/languageserver/unit/puppet-languageserver/language_client_spec.rb

Lines changed: 146 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -331,6 +331,72 @@
331331
end
332332
end
333333

334+
describe '#unregister_capability' do
335+
let(:method_name) { 'mockMethod' }
336+
337+
before(:each) do
338+
# Mock an already succesful registration
339+
subject.instance_variable_set(:@registrations, {
340+
method_name => [{ :id => 'id001', :state => :complete, :registered => true }]
341+
})
342+
end
343+
344+
it 'should send a client request and return true' do
345+
expect(json_rpc_handler).to receive(:send_client_request).with('client/unregisterCapability', Object)
346+
expect(subject.unregister_capability(method_name)).to eq(true)
347+
end
348+
349+
it 'should include the method to register' do
350+
subject.unregister_capability(method_name)
351+
expect(json_rpc_handler.connection.buffer).to include("\"method\":\"#{method_name}\"")
352+
end
353+
354+
it 'should log a message if a registration is already in progress' do
355+
allow(json_rpc_handler).to receive(:send_client_request)
356+
expect(PuppetLanguageServer).to receive(:log_message).with(:warn, /#{method_name}/)
357+
358+
subject.unregister_capability(method_name)
359+
subject.unregister_capability(method_name)
360+
end
361+
362+
it 'should not log a message if a previous registration completed' do
363+
req_method_name = nil
364+
req_method_params = nil
365+
# Remember the registration so we can fake a response later
366+
allow(json_rpc_handler).to receive(:send_client_request) do |n, p|
367+
req_method_name = n
368+
req_method_params = p
369+
end
370+
371+
expect(PuppetLanguageServer).to_not receive(:log_message).with(:warn, /#{method_name}/)
372+
# Send as registration request
373+
subject.unregister_capability(method_name)
374+
# Mock a valid response
375+
response = { 'jsonrpc'=>'2.0', 'id'=> 0, 'result' => nil }
376+
original_request = { 'jsonrpc'=>'2.0', 'id' => 0, 'method' => req_method_name, 'params' => req_method_params }
377+
378+
subject.parse_unregister_capability_response!(response, original_request)
379+
380+
subject.unregister_capability(method_name)
381+
end
382+
383+
it 'should not deregister methods that have not been registerd' do
384+
expect(json_rpc_handler).to_not receive(:send_client_request)
385+
386+
subject.unregister_capability('unknown')
387+
end
388+
389+
it 'should not deregister methods that are no longer registerd' do
390+
expect(json_rpc_handler).to_not receive(:send_client_request)
391+
392+
subject.instance_variable_set(:@registrations, {
393+
method_name => [{ :id => 'id001', :state => :complete, :registered => false }]
394+
})
395+
396+
subject.unregister_capability(method_name)
397+
end
398+
end
399+
334400
describe '#parse_register_capability_response!' do
335401
let(:request_id) { 0 }
336402
let(:response_result) { nil }
@@ -390,4 +456,84 @@
390456
end
391457
end
392458
end
459+
460+
describe '#parse_unregister_capability_response!' do
461+
let(:request_id) { 0 }
462+
let(:response_result) { nil }
463+
let(:response) { {'jsonrpc'=>'2.0', 'id'=> request_id, 'result' => response_result } }
464+
let(:original_request) { {'jsonrpc'=>'2.0', 'id'=> request_id, 'method' => request_method, 'params' => request_params} }
465+
let(:method_name) { 'validMethod' }
466+
let(:initial_registration) { true }
467+
468+
before(:each) do
469+
# Mock an already succesful registration
470+
subject.instance_variable_set(:@registrations, {
471+
method_name => [{ :id => 'id001', :state => :complete, :registered => initial_registration }]
472+
})
473+
end
474+
475+
context 'Given an original request that is not an unregistration' do
476+
let(:request_method) { 'mockMethod' }
477+
let(:request_params) { {} }
478+
479+
it 'should raise an error if the original request was not a registration' do
480+
expect{ subject.parse_unregister_capability_response!(response, original_request) }.to raise_error(/client\/unregisterCapability/)
481+
end
482+
end
483+
484+
context 'Given a valid original request' do
485+
let(:request_method) { 'client/unregisterCapability' }
486+
let(:request_params) do
487+
params = LSP::UnregistrationParams.new.from_h!('unregisterations' => [])
488+
params.unregisterations << LSP::Unregistration.new.from_h!('id' => 'id001', 'method' => method_name)
489+
params
490+
end
491+
492+
before(:each) do
493+
# Mimic an unregistration that is in progress
494+
subject.instance_variable_set(:@registrations, {
495+
method_name => [{ :id => 'id001', :state => :pending, :registered => initial_registration }]
496+
})
497+
end
498+
499+
context 'that failed' do
500+
before(:each) do
501+
response.delete('result') if response.key?('result')
502+
response['error'] = { 'code' => -1, 'message' => 'mock message' }
503+
end
504+
505+
context 'and was previously registered' do
506+
it 'should retain that it is registered' do
507+
subject.parse_unregister_capability_response!(response, original_request)
508+
509+
expect(subject.capability_registrations(method_name)).to eq([{:id=>"id001", :registered=>true, :state=>:complete}])
510+
end
511+
end
512+
513+
context 'and was not previously registered' do
514+
let(:initial_registration) { false }
515+
516+
it 'should no longer be in the registration list' do
517+
subject.parse_unregister_capability_response!(response, original_request)
518+
519+
expect(subject.capability_registrations(method_name)).to eq([{ :registered => false, :state => :complete }])
520+
end
521+
end
522+
end
523+
524+
context 'that succeeded' do
525+
it 'should log the registration' do
526+
expect(PuppetLanguageServer).to receive(:log_message).with(:info, /validMethod/)
527+
528+
subject.parse_unregister_capability_response!(response, original_request)
529+
end
530+
531+
it 'should no longer be in the registration list' do
532+
subject.parse_unregister_capability_response!(response, original_request)
533+
534+
expect(subject.capability_registrations(method_name)).to eq([{ :registered => false, :state => :complete }])
535+
end
536+
end
537+
end
538+
end
393539
end

0 commit comments

Comments
 (0)