Skip to content

Commit 4f0a80f

Browse files
authored
Merge pull request #185 from glennsarti/format-on-type
(GH-177) Add registrations and settings for on type formatting
2 parents 58b62de + db97c1a commit 4f0a80f

File tree

5 files changed

+268
-24
lines changed

5 files changed

+268
-24
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: 20 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -46,15 +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)
50-
unless server_options[:puppet_version].nil? || server_options[:puppet_version] == Puppet.version
51-
# Add a minor delay before sending the notification to give the client some processing time
52-
sleep(0.5)
53-
json_rpc_handler.send_show_message_notification(
54-
LSP::MessageType::WARNING,
55-
"Unable to use Puppet version '#{server_options[:puppet_version]}' as it is not available. Using version '#{Puppet.version}' instead."
56-
)
57-
end
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))
5854

5955
when 'shutdown'
6056
PuppetLanguageServer.log_message(:debug, 'Received shutdown method')
@@ -209,6 +205,9 @@ def receive_request(request)
209205
request.reply_result(nil)
210206
end
211207

208+
when 'textDocument/onTypeFormatting'
209+
request.reply_result(nil)
210+
212211
when 'textDocument/signatureHelp'
213212
file_uri = request.params['textDocument']['uri']
214213
line_num = request.params['position']['line']
@@ -253,6 +252,14 @@ def receive_notification(method, params)
253252
case method
254253
when 'initialized'
255254
PuppetLanguageServer.log_message(:info, 'Client has received initialization')
255+
# Raise a warning if the Puppet version is mismatched
256+
unless server_options[:puppet_version].nil? || server_options[:puppet_version] == Puppet.version
257+
json_rpc_handler.send_show_message_notification(
258+
LSP::MessageType::WARNING,
259+
"Unable to use Puppet version '#{server_options[:puppet_version]}' as it is not available. Using version '#{Puppet.version}' instead."
260+
)
261+
end
262+
# Register for workspace setting changes if it's supported
256263
if client.client_capability('workspace', 'didChangeConfiguration', 'dynamicRegistration') == true
257264
client.register_capability('workspace/didChangeConfiguration')
258265
else
@@ -325,7 +332,10 @@ def receive_response(response, original_request)
325332
client.parse_unregister_capability_response!(response, original_request)
326333
when 'workspace/configuration'
327334
return unless receive_response_succesful?(response)
328-
client.parse_lsp_configuration_settings!(response['result'])
335+
original_request['params'].items.each_with_index do |item, index|
336+
# The response from the client strips the section name so we need to re-add it
337+
client.parse_lsp_configuration_settings!(item.section => response['result'][index])
338+
end
329339
else
330340
super
331341
end

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

0 commit comments

Comments
 (0)