Skip to content

Commit db97c1a

Browse files
committed
(GH-177) Add registrations and settings for on type formatting
Now that the Language Client can process client settings, and dynamic registrations it is now possible to setup the server to register to receive the onTypeFormatting request. This commit: * Responds to the 'puppet.editorService.formatOnType.enable' client setting * Dynamically registers the provider if dynamic registration is possible otherwise uses static registration * Adds a format_on_type method on the Language Client to track whether this feature is enabled * Adds dummy response to the `textDocument/onTypeFormatting` request * Adds tests for the client interacting with the settings
1 parent 9d13ba8 commit db97c1a

File tree

5 files changed

+249
-12
lines changed

5 files changed

+249
-12
lines changed

lib/puppet-languageserver/language_client.rb

Lines changed: 27 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -4,6 +4,9 @@ module PuppetLanguageServer
44
class LanguageClient
55
attr_reader :message_router
66

7+
# Client settings
8+
attr_reader :format_on_type
9+
710
def initialize(message_router)
811
@message_router = message_router
912
@client_capabilites = {}
@@ -17,6 +20,9 @@ def initialize(message_router)
1720
# }
1821
# ]
1922
@registrations = {}
23+
24+
# Default settings
25+
@format_on_type = false
2026
end
2127

2228
def client_capability(*names)
@@ -35,12 +41,20 @@ def parse_lsp_initialize!(initialize_params = {})
3541
@client_capabilites = initialize_params['capabilities']
3642
end
3743

38-
# Settings could be a hash or an array of hash
39-
def parse_lsp_configuration_settings!(settings = [{}])
40-
# TODO: Future use. Actually do something with the settings
41-
# settings = [settings] unless settings.is_a?(Hash)
42-
# settings.each do |hash|
43-
# end
44+
def parse_lsp_configuration_settings!(settings = {})
45+
# format on type
46+
value = safe_hash_traverse(settings, 'puppet', 'editorService', 'formatOnType', 'enable')
47+
unless value.nil? || to_boolean(value) == @format_on_type # rubocop:disable Style/GuardClause Ummm no.
48+
# Is dynamic registration available?
49+
if client_capability('textDocument', 'onTypeFormatting', 'dynamicRegistration') == true
50+
if value
51+
register_capability('textDocument/onTypeFormatting', PuppetLanguageServer::ServerCapabilites.document_on_type_formatting_options)
52+
else
53+
unregister_capability('textDocument/onTypeFormatting')
54+
end
55+
end
56+
@format_on_type = value
57+
end
4458
end
4559

4660
def capability_registrations(method)
@@ -148,12 +162,18 @@ def parse_unregister_capability_response!(response, original_request)
148162

149163
private
150164

165+
def to_boolean(value)
166+
return false if value.nil? || value == false
167+
return true if value == true
168+
value.to_s =~ %r{^(true|t|yes|y|1)$/i}
169+
end
170+
151171
def new_request_id
152172
SecureRandom.uuid
153173
end
154174

155175
def safe_hash_traverse(hash, *names)
156-
return nil if names.empty?
176+
return nil if names.empty? || hash.nil? || hash.empty?
157177
item = nil
158178
loop do
159179
name = names.shift

lib/puppet-languageserver/message_router.rb

Lines changed: 8 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -46,7 +46,11 @@ def receive_request(request)
4646
when 'initialize'
4747
PuppetLanguageServer.log_message(:debug, 'Received initialize method')
4848
client.parse_lsp_initialize!(request.params)
49-
request.reply_result('capabilities' => PuppetLanguageServer::ServerCapabilites.capabilities)
49+
# Setup static registrations if dynamic registration is not available
50+
info = {
51+
:documentOnTypeFormattingProvider => !client.client_capability('textDocument', 'onTypeFormatting', 'dynamicRegistration')
52+
}
53+
request.reply_result('capabilities' => PuppetLanguageServer::ServerCapabilites.capabilities(info))
5054

5155
when 'shutdown'
5256
PuppetLanguageServer.log_message(:debug, 'Received shutdown method')
@@ -201,6 +205,9 @@ def receive_request(request)
201205
request.reply_result(nil)
202206
end
203207

208+
when 'textDocument/onTypeFormatting'
209+
request.reply_result(nil)
210+
204211
when 'textDocument/signatureHelp'
205212
file_uri = request.params['textDocument']['uri']
206213
line_num = request.params['position']['line']

lib/puppet-languageserver/server_capabilities.rb

Lines changed: 10 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -2,10 +2,10 @@
22

33
module PuppetLanguageServer
44
module ServerCapabilites
5-
def self.capabilities
5+
def self.capabilities(options = {})
66
# https://github.com/Microsoft/language-server-protocol/blob/master/protocol.md#initialize-request
77

8-
{
8+
value = {
99
'textDocumentSync' => LSP::TextDocumentSyncKind::FULL,
1010
'hoverProvider' => true,
1111
'completionProvider' => {
@@ -19,6 +19,14 @@ def self.capabilities
1919
'triggerCharacters' => ['(', ',']
2020
}
2121
}
22+
value['documentOnTypeFormattingProvider'] = document_on_type_formatting_options if options[:documentOnTypeFormattingProvider]
23+
value
24+
end
25+
26+
def self.document_on_type_formatting_options
27+
{
28+
'firstTriggerCharacter' => '>'
29+
}
2230
end
2331

2432
def self.no_capabilities

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

Lines changed: 121 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,101 @@
11
require 'spec_helper'
22

3+
def pretty_value(value)
4+
value.nil? ? 'nil' : value.to_s
5+
end
6+
7+
# Requires
8+
# :settings : A hashtable of the inbound settings
9+
# :setting_value : The value that will be set to
10+
RSpec.shared_examples "a client setting" do |method_name|
11+
[
12+
{ :from => false, :setting => nil, :expected_setting => false },
13+
{ :from => false, :setting => false, :expected_setting => false },
14+
{ :from => false, :setting => true, :expected_setting => true },
15+
{ :from => true, :setting => nil, :expected_setting => true },
16+
{ :from => true, :setting => false, :expected_setting => false },
17+
{ :from => true, :setting => true, :expected_setting => true },
18+
].each do |testcase|
19+
context "When it transitions from #{pretty_value(testcase[:from])} with a setting value of #{pretty_value(testcase[:setting])}" do
20+
let(:setting_value) { testcase[:setting] }
21+
22+
before(:each) do
23+
subject.instance_variable_set("@#{method_name}".intern, testcase[:from])
24+
end
25+
26+
it "should have a cached value to #{testcase[:expected_setting]}" do
27+
expect(subject.send(method_name)).to eq(testcase[:from])
28+
29+
subject.parse_lsp_configuration_settings!(settings)
30+
expect(subject.send(method_name)).to eq(testcase[:expected_setting])
31+
end
32+
end
33+
end
34+
end
35+
36+
# Requires
37+
# :settings : A hashtable of the inbound settings
38+
# :setting_value : The value that will be set to
39+
RSpec.shared_examples "a setting with dynamic registrations" do |method_name, dynamic_reg, registration_method|
40+
[
41+
{ :from => false, :setting => nil, :noop => true },
42+
{ :from => false, :setting => false, :noop => true },
43+
{ :from => false, :setting => true, :register => true },
44+
{ :from => true, :setting => nil, :noop => true },
45+
{ :from => true, :setting => false, :unregister => true },
46+
{ :from => true, :setting => true, :noop => true },
47+
].each do |testcase|
48+
context "When it transitions from #{pretty_value(testcase[:from])} with a setting value of #{pretty_value(testcase[:setting])}" do
49+
let(:setting_value) { testcase[:setting] }
50+
51+
before(:each) do
52+
subject.instance_variable_set("@#{method_name}".intern, testcase[:from])
53+
end
54+
55+
it 'should not call any capabilities', :if => testcase[:noop] do
56+
expect(subject).to receive(:client_capability).exactly(0).times
57+
expect(subject).to receive(:register_capability).exactly(0).times
58+
expect(subject).to receive(:unregister_capability).exactly(0).times
59+
60+
subject.parse_lsp_configuration_settings!(settings)
61+
end
62+
63+
context "when dynamic registration is not supported", :unless => testcase[:noop] do
64+
before(:each) do
65+
expect(subject).to receive(:client_capability).with(*dynamic_reg).and_return(false)
66+
end
67+
68+
it 'should not call any registration or unregistrations' do
69+
expect(subject).to receive(:register_capability).exactly(0).times
70+
expect(subject).to receive(:unregister_capability).exactly(0).times
71+
72+
subject.parse_lsp_configuration_settings!(settings)
73+
end
74+
end
75+
76+
context "when dynamic registration is supported", :unless => testcase[:noop] do
77+
before(:each) do
78+
expect(subject).to receive(:client_capability).with(*dynamic_reg).and_return(true)
79+
end
80+
81+
it "should register #{registration_method}", :if => testcase[:register] do
82+
expect(subject).to receive(:register_capability).with(registration_method, Object)
83+
expect(subject).to receive(:unregister_capability).exactly(0).times
84+
85+
subject.parse_lsp_configuration_settings!(settings)
86+
end
87+
88+
it "should unregister #{registration_method}", :if => testcase[:unregister] do
89+
expect(subject).to receive(:unregister_capability).with(registration_method)
90+
expect(subject).to receive(:register_capability).exactly(0).times
91+
92+
subject.parse_lsp_configuration_settings!(settings)
93+
end
94+
end
95+
end
96+
end
97+
end
98+
399
describe 'PuppetLanguageServer::LanguageClient' do
4100
let(:json_rpc_handler) { MockJSONRPCHandler.new }
5101
let(:message_router) { MockMessageRouter.new.tap { |i| i.json_rpc_handler = json_rpc_handler } }
@@ -146,6 +242,12 @@
146242
allow(PuppetLanguageServer).to receive(:log_message)
147243
end
148244

245+
describe '#format_on_type' do
246+
it 'should be false by default' do
247+
expect(subject.format_on_type).to eq(false)
248+
end
249+
end
250+
149251
describe '#client_capability' do
150252
before(:each) do
151253
subject.parse_lsp_initialize!(initialize_params)
@@ -181,7 +283,25 @@
181283
# end
182284

183285
describe '#parse_lsp_configuration_settings!' do
184-
# TODO: Future use.
286+
describe 'puppet.editorService.formatOnType.enable' do
287+
let(:settings) do
288+
{ 'puppet' => {
289+
'editorService' => {
290+
'formatOnType' => {
291+
'enable' => setting_value
292+
}
293+
}
294+
}
295+
}
296+
end
297+
298+
it_behaves_like 'a client setting', :format_on_type
299+
300+
it_behaves_like 'a setting with dynamic registrations',
301+
:format_on_type,
302+
['textDocument', 'onTypeFormatting', 'dynamicRegistration'],
303+
'textDocument/onTypeFormatting'
304+
end
185305
end
186306

187307
describe '#capability_registrations' do

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

Lines changed: 83 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -18,6 +18,12 @@
1818
match { |actual| !actual.send(method_name).nil? }
1919
end
2020

21+
RSpec::Matchers.define :server_capability do |name|
22+
match do |actual|
23+
actual['capabilities'] && actual['capabilities'][name]
24+
end
25+
end
26+
2127
describe '#documents' do
2228
it 'should respond to documents method' do
2329
expect(subject).to respond_to(:documents)
@@ -67,7 +73,7 @@
6773
# initialize - https://github.com/Microsoft/language-server-protocol/blob/master/protocol.md#initialize
6874
context 'given an initialize request' do
6975
let(:request_rpc_method) { 'initialize' }
70-
let(:request_params) { { 'cap1' => 'value1' } }
76+
let(:request_params) { { 'capabilities' => { 'cap1' => 'value1' } } }
7177
it 'should reply with capabilites' do
7278
expect(request).to receive(:reply_result).with(hash_including('capabilities'))
7379

@@ -79,6 +85,55 @@
7985

8086
subject.receive_request(request)
8187
end
88+
89+
context 'when onTypeFormatting does support dynamic registration' do
90+
let(:request_params) do
91+
{ 'capabilities' => {
92+
'textDocument' => {
93+
'onTypeFormatting' => {
94+
'dynamicRegistration' => true
95+
}
96+
}
97+
}
98+
}
99+
end
100+
101+
it 'should statically register a documentOnTypeFormattingProvider' do
102+
expect(request).to_not receive(:reply_result).with(server_capability('documentOnTypeFormattingProvider'))
103+
allow(request).to receive(:reply_result)
104+
105+
subject.receive_request(request)
106+
end
107+
end
108+
109+
context 'when onTypeFormatting does not support dynamic registration' do
110+
let(:request_params) do
111+
{ 'capabilities' => {
112+
'textDocument' => {
113+
'onTypeFormatting' => {
114+
'dynamicRegistration' => false
115+
}
116+
}
117+
}
118+
}
119+
end
120+
121+
it 'should statically register a documentOnTypeFormattingProvider' do
122+
expect(request).to receive(:reply_result).with(server_capability('documentOnTypeFormattingProvider'))
123+
124+
subject.receive_request(request)
125+
end
126+
end
127+
128+
context 'when onTypeFormatting does not specify dynamic registration' do
129+
let(:request_params) { {} }
130+
131+
it 'should statically register a documentOnTypeFormattingProvider' do
132+
expect(request).to receive(:reply_result).with(server_capability('documentOnTypeFormattingProvider'))
133+
134+
subject.receive_request(request)
135+
end
136+
end
82137
end
83138

84139
# shutdown - https://github.com/Microsoft/language-server-protocol/blob/master/protocol.md#shutdown
@@ -800,6 +855,33 @@
800855
end
801856
end
802857

858+
# textDocument/onTypeFormatting - https://microsoft.github.io/language-server-protocol/specification#textDocument_onTypeFormatting
859+
context 'given a textDocument/onTypeFormatting request' do
860+
let(:request_rpc_method) { 'textDocument/onTypeFormatting' }
861+
let(:file_uri) { MANIFEST_FILENAME }
862+
let(:line_num) { 1 }
863+
let(:char_num) { 6 }
864+
let(:trigger_char) { '>' }
865+
let(:formatting_options) { { 'tabSize' => 2, 'insertSpaces' => true} }
866+
let(:request_params) { {
867+
'textDocument' => {
868+
'uri' => file_uri
869+
},
870+
'position' => {
871+
'line' => line_num,
872+
'character' => char_num,
873+
},
874+
'ch' => trigger_char,
875+
'options' => formatting_options
876+
} }
877+
878+
it 'should not log an error message' do
879+
expect(PuppetLanguageServer).to_not receive(:log_message).with(:error,"Unknown RPC method #{request_rpc_method}")
880+
881+
subject.receive_request(request)
882+
end
883+
end
884+
803885
context 'given an unknown request' do
804886
let(:request_rpc_method) { 'unknown_request_method' }
805887

0 commit comments

Comments
 (0)