Skip to content

Commit f8a6671

Browse files
committed
(GH-306) Add Puppet Manifest Folding provider to Server
Now that there is a Puppet Manifest Folding provider available, the server capabilities need to be updated so that clients know to use it. This commit: * Creates a method on ServerCapabilities to determine if folding is supported by the server * Updates the Service Capabilities to advertise the 'foldingRangeProvider' capavility if it is supported but not dynamic registrations * Updates the Client Settings to send dynamic registrations 'textDocument/foldingRange' if supported * Adds a two new settings for Folding: Enabled and Show Last Line * Refactors the Language Client settings to make it easier to set defaults * Updated Server to respond to 'textDocument/foldingRange' requests Adds many tests for the message handler and language client for the new provider
1 parent 2250a4b commit f8a6671

File tree

5 files changed

+249
-60
lines changed

5 files changed

+249
-60
lines changed

lib/puppet-languageserver/message_handler.rb

Lines changed: 19 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -29,9 +29,12 @@ def request_initialize(_, json_rpc_message)
2929
PuppetLanguageServer.log_message(:debug, 'Received initialize method')
3030

3131
language_client.parse_lsp_initialize!(json_rpc_message.params)
32+
static_folding_provider = !language_client.client_capability('textDocument', 'foldingRange', 'dynamicRegistration') &&
33+
PuppetLanguageServer::ServerCapabilites.folding_provider_supported?
3234
# Setup static registrations if dynamic registration is not available
3335
info = {
34-
:documentOnTypeFormattingProvider => !language_client.client_capability('textDocument', 'onTypeFormatting', 'dynamicRegistration')
36+
:documentOnTypeFormattingProvider => !language_client.client_capability('textDocument', 'onTypeFormatting', 'dynamicRegistration'),
37+
:foldingRangeProvider => static_folding_provider
3538
}
3639

3740
# Configure the document store
@@ -162,6 +165,20 @@ def request_textdocument_completion(_, json_rpc_message)
162165
LSP::CompletionList.new('isIncomplete' => false, 'items' => [])
163166
end
164167

168+
def request_textdocument_foldingrange(_, json_rpc_message)
169+
return nil unless language_client.folding_range
170+
file_uri = json_rpc_message.params['textDocument']['uri']
171+
case documents.document_type(file_uri)
172+
when :manifest
173+
PuppetLanguageServer::Manifest::FoldingProvider.instance.folding_ranges(documents.document_tokens(file_uri))
174+
else
175+
raise "Unable to provide folding ranages on #{file_uri}"
176+
end
177+
rescue StandardError => e
178+
PuppetLanguageServer.log_message(:error, "(textDocument/foldingRange) #{e}")
179+
nil
180+
end
181+
165182
def request_completionitem_resolve(_, json_rpc_message)
166183
PuppetLanguageServer::Manifest::CompletionProvider.resolve(session_state, LSP::CompletionItem.new(json_rpc_message.params))
167184
rescue StandardError => e
@@ -285,6 +302,7 @@ def notification_initialized(_, _json_rpc_message)
285302
"Unable to use Puppet version '#{server_options[:puppet_version]}' as it is not available. Using version '#{Puppet.version}' instead."
286303
)
287304
end
305+
288306
# Register for workspace setting changes if it's supported
289307
if language_client.client_capability('workspace', 'didChangeConfiguration', 'dynamicRegistration') == true
290308
language_client.register_capability('workspace/didChangeConfiguration')

lib/puppet-languageserver/server_capabilities.rb

Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -4,6 +4,10 @@
44

55
module PuppetLanguageServer
66
module ServerCapabilites
7+
def self.folding_provider_supported?
8+
@folding_provider ||= PuppetLanguageServer::Manifest::FoldingProvider.supported?
9+
end
10+
711
def self.capabilities(options = {})
812
# https://github.com/Microsoft/language-server-protocol/blob/master/protocol.md#initialize-request
913

@@ -22,6 +26,7 @@ def self.capabilities(options = {})
2226
}
2327
}
2428
value['documentOnTypeFormattingProvider'] = document_on_type_formatting_options if options[:documentOnTypeFormattingProvider]
29+
value['foldingRangeProvider'] = folding_range_provider_options if options[:foldingRangeProvider]
2530
value
2631
end
2732

@@ -31,6 +36,10 @@ def self.document_on_type_formatting_options
3136
}
3237
end
3338

39+
def self.folding_range_provider_options
40+
true
41+
end
42+
3443
def self.no_capabilities
3544
# Any empty hash denotes no capabilities at all
3645
{

lib/puppet-languageserver/session_state/language_client.rb

Lines changed: 38 additions & 13 deletions
Original file line numberDiff line numberDiff line change
@@ -8,8 +8,16 @@ class LanguageClient
88
# Client settings
99
attr_reader :format_on_type
1010
attr_reader :format_on_type_filesize_limit
11+
attr_reader :folding_range
12+
attr_reader :folding_range_show_last_line
1113
attr_reader :use_puppetfile_resolver
1214

15+
DEFAULT_FORMAT_ON_TYPE_ENABLE = false
16+
DEFAULT_FORMAT_ON_TYPE_FILESIZE_LIMIT = 4096
17+
DEFAULT_FOLDING_RANGE_ENABLE = true # Default is true as per VS Code
18+
DEFAULT_FOLDING_RANGE_SHOW_LAST_LINE = true # Default is true as per VS Code
19+
DEFAULT_VALIDATE_RESOLVE_PUPPETFILES = true
20+
1321
def initialize(message_handler)
1422
@message_handler = message_handler
1523
@client_capabilites = {}
@@ -24,10 +32,12 @@ def initialize(message_handler)
2432
# ]
2533
@registrations = {}
2634

27-
# Default settings
35+
# Initial state. Not always the same as defaults
36+
@folding_range = false
37+
@folding_range_show_last_line = DEFAULT_FOLDING_RANGE_SHOW_LAST_LINE
2838
@format_on_type = false
29-
@format_on_type_filesize_limit = 4096
30-
@use_puppetfile_resolver = true
39+
@format_on_type_filesize_limit = DEFAULT_FORMAT_ON_TYPE_FILESIZE_LIMIT
40+
@use_puppetfile_resolver = DEFAULT_VALIDATE_RESOLVE_PUPPETFILES
3141
end
3242

3343
def client_capability(*names)
@@ -47,9 +57,9 @@ def parse_lsp_initialize!(initialize_params = {})
4757
end
4858

4959
def parse_lsp_configuration_settings!(settings = {})
50-
# format on type
51-
value = safe_hash_traverse(settings, 'puppet', 'editorService', 'formatOnType', 'enable')
52-
unless value.nil? || to_boolean(value) == @format_on_type # rubocop:disable Style/GuardClause Ummm no.
60+
# format on type enabled
61+
value = to_boolean(safe_hash_traverse(settings, 'puppet', 'editorService', 'formatOnType', 'enable'), DEFAULT_FORMAT_ON_TYPE_ENABLE)
62+
unless value == @format_on_type # rubocop:disable Style/GuardClause Ummm no.
5363
# Is dynamic registration available?
5464
if client_capability('textDocument', 'onTypeFormatting', 'dynamicRegistration') == true
5565
if value
@@ -61,11 +71,26 @@ def parse_lsp_configuration_settings!(settings = {})
6171
@format_on_type = value
6272
end
6373
# format on type file size
64-
value = safe_hash_traverse(settings, 'puppet', 'editorService', 'formatOnType', 'maxFileSize')
65-
@format_on_type_filesize_limit = to_integer(value, 4096, 0)
74+
@format_on_type_filesize_limit = to_integer(safe_hash_traverse(settings, 'puppet', 'editorService', 'formatOnType', 'maxFileSize'), DEFAULT_FORMAT_ON_TYPE_FILESIZE_LIMIT, 0)
75+
6676
# use puppetfile resolver
67-
value = safe_hash_traverse(settings, 'puppet', 'validate', 'resolvePuppetfiles')
68-
@use_puppetfile_resolver = to_boolean(value)
77+
@use_puppetfile_resolver = to_boolean(safe_hash_traverse(settings, 'puppet', 'validate', 'resolvePuppetfiles'), DEFAULT_VALIDATE_RESOLVE_PUPPETFILES)
78+
79+
# folding range enabled
80+
value = to_boolean(safe_hash_traverse(settings, 'puppet', 'editorService', 'foldingRange', 'enable'), DEFAULT_FOLDING_RANGE_ENABLE) && PuppetLanguageServer::ServerCapabilites.folding_provider_supported?
81+
unless value == @folding_range # rubocop:disable Style/GuardClause Ummm no.
82+
# Is dynamic registration available?
83+
if client_capability('textDocument', 'foldingRange', 'dynamicRegistration') == true
84+
if value
85+
register_capability('textDocument/foldingRange', PuppetLanguageServer::ServerCapabilites.document_on_type_formatting_options)
86+
else
87+
unregister_capability('textDocument/foldingRange')
88+
end
89+
end
90+
@folding_range = value
91+
end
92+
# folding range last line display
93+
@folding_range_show_last_line = to_boolean(safe_hash_traverse(settings, 'puppet', 'editorService', 'foldingRange', 'showLastLine'), DEFAULT_FOLDING_RANGE_SHOW_LAST_LINE)
6994
end
7095

7196
def capability_registrations(method)
@@ -173,9 +198,9 @@ def parse_unregister_capability_response!(response, original_request)
173198

174199
private
175200

176-
def to_boolean(value)
177-
return false if value.nil? || value == false
178-
return true if value == true
201+
def to_boolean(value, default = false)
202+
return default if value.nil?
203+
return value if value == true || value == false # rubocop:disable Style/MultipleComparison
179204
value.to_s =~ %r{^(true|t|yes|y|1)$/i}
180205
end
181206

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

Lines changed: 78 additions & 35 deletions
Original file line numberDiff line numberDiff line change
@@ -54,10 +54,77 @@
5454

5555
# initialize - https://github.com/Microsoft/language-server-protocol/blob/master/protocol.md#initialize
5656
describe '.request_initialize' do
57-
#context 'given an initialize request' do
5857
let(:request_rpc_method) { 'initialize' }
5958
let(:request_params) { { 'capabilities' => { 'cap1' => 'value1' } } }
6059

60+
RSpec.shared_examples 'dynamically registered provider' do |short_client_cap, client_cap, server_cap_name|
61+
def client_cap_hash(client_cap_string, dynamic_reg)
62+
client_cap_string.split('/').reverse.reduce(dynamic_reg) { |memo, obj| { obj => memo } }
63+
end
64+
65+
context "when #{short_client_cap} does support dynamic registration" do
66+
let(:request_params) do
67+
{ 'capabilities' => client_cap_hash(client_cap, true) }
68+
end
69+
70+
it "should not statically register a #{server_cap_name}" do
71+
expect(subject.request_initialize(connection_id, request_message)).to_not server_capability(server_cap_name)
72+
end
73+
end
74+
75+
context "when #{short_client_cap} does not support dynamic registration" do
76+
let(:request_params) do
77+
{ 'capabilities' => client_cap_hash(client_cap, false) }
78+
end
79+
80+
it "should statically register a #{server_cap_name}" do
81+
expect(subject.request_initialize(connection_id, request_message)).to server_capability(server_cap_name)
82+
end
83+
end
84+
85+
context "when #{short_client_cap} does not specify dynamic registration" do
86+
let(:request_params) { {} }
87+
88+
it "should statically register a #{server_cap_name}" do
89+
expect(subject.request_initialize(connection_id, request_message)).to server_capability(server_cap_name)
90+
end
91+
end
92+
end
93+
94+
RSpec.shared_examples 'never registered provider' do |short_client_cap, client_cap, server_cap_name|
95+
def client_cap_hash(client_cap_string, dynamic_reg)
96+
client_cap_string.split('/').reverse.reduce(dynamic_reg) { |memo, obj| { obj => memo } }
97+
end
98+
99+
context "when #{short_client_cap} does support dynamic registration" do
100+
let(:request_params) do
101+
{ 'capabilities' => client_cap_hash(client_cap, true) }
102+
end
103+
104+
it "should not statically register a #{server_cap_name}" do
105+
expect(subject.request_initialize(connection_id, request_message)).to_not server_capability(server_cap_name)
106+
end
107+
end
108+
109+
context "when #{short_client_cap} does not support dynamic registration" do
110+
let(:request_params) do
111+
{ 'capabilities' => client_cap_hash(client_cap, false) }
112+
end
113+
114+
it "should not statically register a #{server_cap_name}" do
115+
expect(subject.request_initialize(connection_id, request_message)).to_not server_capability(server_cap_name)
116+
end
117+
end
118+
119+
context "when #{short_client_cap} does not specify dynamic registration" do
120+
let(:request_params) { {} }
121+
122+
it "should not statically register a #{server_cap_name}" do
123+
expect(subject.request_initialize(connection_id, request_message)).to_not server_capability(server_cap_name)
124+
end
125+
end
126+
end
127+
61128
it 'should reply with capabilites' do
62129
expect(subject.request_initialize(connection_id, request_message)['capabilities']).to_not be_nil
63130
end
@@ -67,46 +134,22 @@
67134
subject.request_initialize(connection_id, request_message)
68135
end
69136

70-
context 'when onTypeFormatting does support dynamic registration' do
71-
let(:request_params) do
72-
{ 'capabilities' => {
73-
'textDocument' => {
74-
'onTypeFormatting' => {
75-
'dynamicRegistration' => true
76-
}
77-
}
78-
}
79-
}
80-
end
81-
82-
it 'should not statically register a documentOnTypeFormattingProvider' do
83-
expect(subject.request_initialize(connection_id, request_message)).to_not server_capability('documentOnTypeFormattingProvider')
84-
end
85-
end
137+
include_examples 'dynamically registered provider', 'onTypeFormatting', 'textDocument/onTypeFormatting/dynamicRegistration', 'documentOnTypeFormattingProvider'
86138

87-
context 'when onTypeFormatting does not support dynamic registration' do
88-
let(:request_params) do
89-
{ 'capabilities' => {
90-
'textDocument' => {
91-
'onTypeFormatting' => {
92-
'dynamicRegistration' => false
93-
}
94-
}
95-
}
96-
}
139+
context 'When folding is supported' do
140+
before(:each) do
141+
allow(PuppetLanguageServer::ServerCapabilites).to receive(:folding_provider_supported?).and_return(true)
97142
end
98143

99-
it 'should statically register a documentOnTypeFormattingProvider' do
100-
expect(subject.request_initialize(connection_id, request_message)).to server_capability('documentOnTypeFormattingProvider')
101-
end
144+
include_examples 'dynamically registered provider', 'foldingRange', 'textDocument/foldingRange/dynamicRegistration', 'foldingRangeProvider'
102145
end
103146

104-
context 'when onTypeFormatting does not specify dynamic registration' do
105-
let(:request_params) { {} }
106-
107-
it 'should statically register a documentOnTypeFormattingProvider' do
108-
expect(subject.request_initialize(connection_id, request_message)).to server_capability('documentOnTypeFormattingProvider')
147+
context 'When folding is not supported' do
148+
before(:each) do
149+
allow(PuppetLanguageServer::ServerCapabilites).to receive(:folding_provider_supported?).and_return(false)
109150
end
151+
152+
include_examples 'never registered provider', 'foldingRange', 'textDocument/foldingRange/dynamicRegistration', 'foldingRangeProvider'
110153
end
111154
end
112155

0 commit comments

Comments
 (0)