puppet-editor-services 2.0.4
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- checksums.yaml +7 -0
- data/CHANGELOG.md +510 -0
- data/CODEOWNERS +2 -0
- data/CODE_OF_CONDUCT.md +46 -0
- data/CONTRIBUTING.md +54 -0
- data/Gemfile +53 -0
- data/LICENSE +201 -0
- data/README.md +308 -0
- data/Rakefile +185 -0
- data/bin/puppet-debugserver +8 -0
- data/bin/puppet-languageserver +7 -0
- data/bin/puppet-languageserver-sidecar +7 -0
- data/lib/dsp/dsp.rb +7 -0
- data/lib/dsp/dsp_base.rb +62 -0
- data/lib/dsp/dsp_protocol.rb +4619 -0
- data/lib/lsp/lsp.rb +10 -0
- data/lib/lsp/lsp_base.rb +63 -0
- data/lib/lsp/lsp_custom.rb +170 -0
- data/lib/lsp/lsp_enums.rb +143 -0
- data/lib/lsp/lsp_protocol.rb +2785 -0
- data/lib/lsp/lsp_protocol_callhierarchy.proposed.rb +239 -0
- data/lib/lsp/lsp_protocol_colorprovider.rb +100 -0
- data/lib/lsp/lsp_protocol_configuration.rb +82 -0
- data/lib/lsp/lsp_protocol_declaration.rb +73 -0
- data/lib/lsp/lsp_protocol_foldingrange.rb +129 -0
- data/lib/lsp/lsp_protocol_implementation.rb +75 -0
- data/lib/lsp/lsp_protocol_progress.rb +200 -0
- data/lib/lsp/lsp_protocol_selectionrange.rb +79 -0
- data/lib/lsp/lsp_protocol_sematictokens.proposed.rb +340 -0
- data/lib/lsp/lsp_protocol_typedefinition.rb +75 -0
- data/lib/lsp/lsp_protocol_workspacefolders.rb +174 -0
- data/lib/lsp/lsp_types.rb +1534 -0
- data/lib/puppet-debugserver/debug_session/break_points.rb +137 -0
- data/lib/puppet-debugserver/debug_session/flow_control.rb +161 -0
- data/lib/puppet-debugserver/debug_session/hook_handlers.rb +295 -0
- data/lib/puppet-debugserver/debug_session/puppet_session_run_mode.rb +66 -0
- data/lib/puppet-debugserver/debug_session/puppet_session_state.rb +122 -0
- data/lib/puppet-debugserver/hooks.rb +132 -0
- data/lib/puppet-debugserver/message_handler.rb +277 -0
- data/lib/puppet-debugserver/puppet_debug_session.rb +541 -0
- data/lib/puppet-debugserver/puppet_monkey_patches.rb +118 -0
- data/lib/puppet-languageserver/client_session_state.rb +119 -0
- data/lib/puppet-languageserver/crash_dump.rb +50 -0
- data/lib/puppet-languageserver/epp/validation_provider.rb +34 -0
- data/lib/puppet-languageserver/facter_helper.rb +25 -0
- data/lib/puppet-languageserver/global_queues/sidecar_queue.rb +205 -0
- data/lib/puppet-languageserver/global_queues/single_instance_queue.rb +126 -0
- data/lib/puppet-languageserver/global_queues/validation_queue.rb +102 -0
- data/lib/puppet-languageserver/global_queues.rb +16 -0
- data/lib/puppet-languageserver/manifest/completion_provider.rb +331 -0
- data/lib/puppet-languageserver/manifest/definition_provider.rb +99 -0
- data/lib/puppet-languageserver/manifest/document_symbol_provider.rb +228 -0
- data/lib/puppet-languageserver/manifest/folding_provider.rb +226 -0
- data/lib/puppet-languageserver/manifest/format_on_type_provider.rb +143 -0
- data/lib/puppet-languageserver/manifest/hover_provider.rb +221 -0
- data/lib/puppet-languageserver/manifest/signature_provider.rb +169 -0
- data/lib/puppet-languageserver/manifest/validation_provider.rb +127 -0
- data/lib/puppet-languageserver/message_handler.rb +462 -0
- data/lib/puppet-languageserver/providers.rb +18 -0
- data/lib/puppet-languageserver/puppet_helper.rb +108 -0
- data/lib/puppet-languageserver/puppet_lexer_helper.rb +55 -0
- data/lib/puppet-languageserver/puppet_monkey_patches.rb +39 -0
- data/lib/puppet-languageserver/puppet_parser_helper.rb +212 -0
- data/lib/puppet-languageserver/puppetfile/validation_provider.rb +185 -0
- data/lib/puppet-languageserver/server_capabilities.rb +48 -0
- data/lib/puppet-languageserver/session_state/document_store.rb +272 -0
- data/lib/puppet-languageserver/session_state/language_client.rb +239 -0
- data/lib/puppet-languageserver/session_state/object_cache.rb +162 -0
- data/lib/puppet-languageserver/sidecar_protocol.rb +532 -0
- data/lib/puppet-languageserver/uri_helper.rb +46 -0
- data/lib/puppet-languageserver-sidecar/cache/base.rb +36 -0
- data/lib/puppet-languageserver-sidecar/cache/filesystem.rb +111 -0
- data/lib/puppet-languageserver-sidecar/cache/null.rb +27 -0
- data/lib/puppet-languageserver-sidecar/facter_helper.rb +41 -0
- data/lib/puppet-languageserver-sidecar/puppet_environment_monkey_patches.rb +52 -0
- data/lib/puppet-languageserver-sidecar/puppet_helper.rb +281 -0
- data/lib/puppet-languageserver-sidecar/puppet_modulepath_monkey_patches.rb +146 -0
- data/lib/puppet-languageserver-sidecar/puppet_monkey_patches.rb +9 -0
- data/lib/puppet-languageserver-sidecar/puppet_parser_helper.rb +77 -0
- data/lib/puppet-languageserver-sidecar/puppet_strings_helper.rb +399 -0
- data/lib/puppet-languageserver-sidecar/puppet_strings_monkey_patches.rb +16 -0
- data/lib/puppet-languageserver-sidecar/sidecar_protocol_extensions.rb +16 -0
- data/lib/puppet-languageserver-sidecar/workspace.rb +89 -0
- data/lib/puppet_debugserver.rb +164 -0
- data/lib/puppet_editor_services/connection/base.rb +62 -0
- data/lib/puppet_editor_services/connection/stdio.rb +25 -0
- data/lib/puppet_editor_services/connection/tcp.rb +34 -0
- data/lib/puppet_editor_services/handler/base.rb +16 -0
- data/lib/puppet_editor_services/handler/debug_adapter.rb +63 -0
- data/lib/puppet_editor_services/handler/json_rpc.rb +133 -0
- data/lib/puppet_editor_services/logging.rb +45 -0
- data/lib/puppet_editor_services/protocol/base.rb +27 -0
- data/lib/puppet_editor_services/protocol/debug_adapter.rb +135 -0
- data/lib/puppet_editor_services/protocol/debug_adapter_messages.rb +171 -0
- data/lib/puppet_editor_services/protocol/json_rpc.rb +241 -0
- data/lib/puppet_editor_services/protocol/json_rpc_messages.rb +200 -0
- data/lib/puppet_editor_services/server/base.rb +42 -0
- data/lib/puppet_editor_services/server/stdio.rb +85 -0
- data/lib/puppet_editor_services/server/tcp.rb +349 -0
- data/lib/puppet_editor_services/server.rb +15 -0
- data/lib/puppet_editor_services/version.rb +36 -0
- data/lib/puppet_editor_services.rb +8 -0
- data/lib/puppet_languageserver.rb +263 -0
- data/lib/puppet_languageserver_sidecar.rb +361 -0
- data/puppet-debugserver +11 -0
- data/puppet-editor-services.gemspec +29 -0
- data/puppet-languageserver +15 -0
- data/puppet-languageserver-sidecar +14 -0
- metadata +240 -0
|
@@ -0,0 +1,272 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
module PuppetLanguageServer
|
|
4
|
+
module SessionState
|
|
5
|
+
# Represents a Document in the Document Store.
|
|
6
|
+
# Can be subclassed to add additional methods and helpers
|
|
7
|
+
class Document
|
|
8
|
+
attr_reader :uri, :content, :version, :tokens
|
|
9
|
+
|
|
10
|
+
# @param uri String The content of the document
|
|
11
|
+
# @param content String The content of the document
|
|
12
|
+
# @param version Integer The version the document
|
|
13
|
+
def initialize(uri, content, version)
|
|
14
|
+
@uri = uri
|
|
15
|
+
@content = content
|
|
16
|
+
@version = version
|
|
17
|
+
@tokens = nil
|
|
18
|
+
end
|
|
19
|
+
|
|
20
|
+
# Update a document with new content and version
|
|
21
|
+
# @param content String The new content for the document
|
|
22
|
+
# @param version Integer The version of the new document
|
|
23
|
+
def update(content, version)
|
|
24
|
+
@content = content
|
|
25
|
+
@version = version
|
|
26
|
+
@tokens = nil
|
|
27
|
+
end
|
|
28
|
+
|
|
29
|
+
# Subclass this!
|
|
30
|
+
def calculate_tokens!
|
|
31
|
+
[]
|
|
32
|
+
end
|
|
33
|
+
end
|
|
34
|
+
|
|
35
|
+
class EppDocument < Document; end
|
|
36
|
+
|
|
37
|
+
class ManifestDocument < Document
|
|
38
|
+
def calculate_tokens!
|
|
39
|
+
lexer = Puppet::Pops::Parser::Lexer2WithComments.new
|
|
40
|
+
lexer.lex_string(content)
|
|
41
|
+
@tokens = lexer.fullscan
|
|
42
|
+
end
|
|
43
|
+
end
|
|
44
|
+
|
|
45
|
+
class PuppetfileDocument < Document; end
|
|
46
|
+
|
|
47
|
+
class DocumentStore
|
|
48
|
+
# @param options :workspace Path to the workspace
|
|
49
|
+
def initialize(_options = {})
|
|
50
|
+
@documents = {}
|
|
51
|
+
@doc_mutex = Mutex.new
|
|
52
|
+
|
|
53
|
+
initialize_store
|
|
54
|
+
end
|
|
55
|
+
|
|
56
|
+
def set_document(uri, content, doc_version)
|
|
57
|
+
@doc_mutex.synchronize do
|
|
58
|
+
if @documents[uri].nil?
|
|
59
|
+
@documents[uri] = create_document(uri, content, doc_version)
|
|
60
|
+
else
|
|
61
|
+
@documents[uri].update(content, doc_version)
|
|
62
|
+
end
|
|
63
|
+
end
|
|
64
|
+
end
|
|
65
|
+
|
|
66
|
+
def remove_document(uri)
|
|
67
|
+
@doc_mutex.synchronize { @documents.delete(uri) }
|
|
68
|
+
nil
|
|
69
|
+
end
|
|
70
|
+
|
|
71
|
+
def clear
|
|
72
|
+
@doc_mutex.synchronize { @documents.clear }
|
|
73
|
+
end
|
|
74
|
+
|
|
75
|
+
def document(uri, doc_version = nil)
|
|
76
|
+
@doc_mutex.synchronize do
|
|
77
|
+
return nil if @documents[uri].nil?
|
|
78
|
+
return nil unless doc_version.nil? || @documents[uri].version == doc_version
|
|
79
|
+
|
|
80
|
+
@documents[uri]
|
|
81
|
+
end
|
|
82
|
+
end
|
|
83
|
+
|
|
84
|
+
def document_content(uri, doc_version = nil)
|
|
85
|
+
doc = document(uri, doc_version)
|
|
86
|
+
doc.nil? ? nil : doc.content.clone
|
|
87
|
+
end
|
|
88
|
+
|
|
89
|
+
def get_document(uri)
|
|
90
|
+
doc = @documents[uri]
|
|
91
|
+
doc.nil? ? nil : doc.content.clone
|
|
92
|
+
end
|
|
93
|
+
|
|
94
|
+
def document_tokens(uri, doc_version = nil)
|
|
95
|
+
@doc_mutex.synchronize do
|
|
96
|
+
return nil if @documents[uri].nil?
|
|
97
|
+
return nil unless doc_version.nil? || @documents[uri].version == doc_version
|
|
98
|
+
return @documents[uri].tokens unless @documents[uri].tokens.nil?
|
|
99
|
+
|
|
100
|
+
return @documents[uri].calculate_tokens!
|
|
101
|
+
end
|
|
102
|
+
end
|
|
103
|
+
|
|
104
|
+
def document_version(uri)
|
|
105
|
+
doc = document(uri)
|
|
106
|
+
doc.nil? ? nil : doc.version
|
|
107
|
+
end
|
|
108
|
+
|
|
109
|
+
def document_uris
|
|
110
|
+
@doc_mutex.synchronize { @documents.keys.dup }
|
|
111
|
+
end
|
|
112
|
+
|
|
113
|
+
def document_type(uri)
|
|
114
|
+
case uri
|
|
115
|
+
when %r{/Puppetfile$}i
|
|
116
|
+
:puppetfile
|
|
117
|
+
when /\.pp$/i
|
|
118
|
+
:manifest
|
|
119
|
+
when /\.epp$/i
|
|
120
|
+
:epp
|
|
121
|
+
else
|
|
122
|
+
:unknown
|
|
123
|
+
end
|
|
124
|
+
end
|
|
125
|
+
|
|
126
|
+
# Plan files https://puppet.com/docs/bolt/1.x/writing_plans.html#concept-4485 can exist in many places
|
|
127
|
+
# The current best detection method is as follows:
|
|
128
|
+
# "Given the full path to the .pp file, if it contains a directory called plans, AND that plans is not a sub-directory of manifests, then it is a plan file"
|
|
129
|
+
#
|
|
130
|
+
# See https://github.com/lingua-pupuli/puppet-editor-services/issues/129 for the full discussion
|
|
131
|
+
def plan_file?(uri)
|
|
132
|
+
uri_path = PuppetLanguageServer::UriHelper.uri_path(uri)
|
|
133
|
+
return false if uri_path.nil?
|
|
134
|
+
|
|
135
|
+
# For the text searching below we need a leading slash. That way
|
|
136
|
+
# we don't need to use regexes which is slower
|
|
137
|
+
uri_path = "/#{uri_path}" unless uri_path.start_with?('/')
|
|
138
|
+
if windows?
|
|
139
|
+
plans_index = uri_path.upcase.index('/PLANS/')
|
|
140
|
+
manifests_index = uri_path.upcase.index('/MANIFESTS/')
|
|
141
|
+
else
|
|
142
|
+
plans_index = uri_path.index('/plans/')
|
|
143
|
+
manifests_index = uri_path.index('/manifests/')
|
|
144
|
+
end
|
|
145
|
+
return false if plans_index.nil?
|
|
146
|
+
return true if manifests_index.nil?
|
|
147
|
+
|
|
148
|
+
plans_index < manifests_index
|
|
149
|
+
end
|
|
150
|
+
|
|
151
|
+
# Workspace management
|
|
152
|
+
WORKSPACE_CACHE_TTL_SECONDS ||= 60
|
|
153
|
+
def initialize_store(options = {})
|
|
154
|
+
@workspace_path = options[:workspace]
|
|
155
|
+
@workspace_info_cache = {
|
|
156
|
+
expires: Time.new - 120
|
|
157
|
+
}
|
|
158
|
+
end
|
|
159
|
+
|
|
160
|
+
def expire_store_information
|
|
161
|
+
@doc_mutex.synchronize do
|
|
162
|
+
@workspace_info_cache[:expires] = Time.new - 120
|
|
163
|
+
end
|
|
164
|
+
end
|
|
165
|
+
|
|
166
|
+
def store_root_path
|
|
167
|
+
store_details[:root_path]
|
|
168
|
+
end
|
|
169
|
+
|
|
170
|
+
def store_has_module_metadata?
|
|
171
|
+
store_details[:has_metadatajson]
|
|
172
|
+
end
|
|
173
|
+
|
|
174
|
+
def store_has_environmentconf?
|
|
175
|
+
store_details[:has_environmentconf]
|
|
176
|
+
end
|
|
177
|
+
|
|
178
|
+
private
|
|
179
|
+
|
|
180
|
+
# Given a path, locate a metadata.json or environment.conf file to determine where the
|
|
181
|
+
# root of the module/control repo actually is
|
|
182
|
+
def find_root_path(path)
|
|
183
|
+
return nil if path.nil?
|
|
184
|
+
|
|
185
|
+
filepath = File.expand_path(path)
|
|
186
|
+
|
|
187
|
+
if dir_exist?(filepath)
|
|
188
|
+
directory = filepath
|
|
189
|
+
elsif file_exist?(filepath)
|
|
190
|
+
directory = File.dirname(filepath)
|
|
191
|
+
else
|
|
192
|
+
return nil
|
|
193
|
+
end
|
|
194
|
+
|
|
195
|
+
until directory.nil?
|
|
196
|
+
break if file_exist?(File.join(directory, 'metadata.json')) || file_exist?(File.join(directory, 'environment.conf'))
|
|
197
|
+
|
|
198
|
+
parent = File.dirname(directory)
|
|
199
|
+
# If the parent is the same as the original, then we've reached the end of the path chain
|
|
200
|
+
directory = if parent == directory
|
|
201
|
+
nil
|
|
202
|
+
else
|
|
203
|
+
parent
|
|
204
|
+
end
|
|
205
|
+
end
|
|
206
|
+
|
|
207
|
+
directory
|
|
208
|
+
end
|
|
209
|
+
|
|
210
|
+
def store_details
|
|
211
|
+
return @workspace_info_cache unless @workspace_info_cache[:never_expires] || @workspace_info_cache[:expires] < Time.new
|
|
212
|
+
|
|
213
|
+
# TTL has expired, time to calculate the document store details
|
|
214
|
+
|
|
215
|
+
new_cache = {
|
|
216
|
+
root_path: nil,
|
|
217
|
+
has_environmentconf: false,
|
|
218
|
+
has_metadatajson: false
|
|
219
|
+
}
|
|
220
|
+
if @workspace_path.nil?
|
|
221
|
+
# If we have never been given a local workspace path on the command line then there is really no
|
|
222
|
+
# way to know where the module file system path is. Therefore the root_path is nil and assume that
|
|
223
|
+
# environment.conf and metadata.json does not exist. And don't bother trying to re-evaluate
|
|
224
|
+
new_cache[:never_expires] = true
|
|
225
|
+
else
|
|
226
|
+
root_path = find_root_path(@workspace_path)
|
|
227
|
+
if root_path.nil?
|
|
228
|
+
new_cache[:root_path] = @workspace_path
|
|
229
|
+
else
|
|
230
|
+
new_cache[:root_path] = root_path
|
|
231
|
+
new_cache[:has_metadatajson] = file_exist?(File.join(root_path, 'metadata.json'))
|
|
232
|
+
new_cache[:has_environmentconf] = file_exist?(File.join(root_path, 'environment.conf'))
|
|
233
|
+
end
|
|
234
|
+
end
|
|
235
|
+
new_cache[:expires] = Time.new + WORKSPACE_CACHE_TTL_SECONDS
|
|
236
|
+
|
|
237
|
+
@doc_mutex.synchronize do
|
|
238
|
+
@workspace_info_cache = new_cache
|
|
239
|
+
end
|
|
240
|
+
@workspace_info_cache
|
|
241
|
+
end
|
|
242
|
+
|
|
243
|
+
def file_exist?(path)
|
|
244
|
+
File.exist?(path) && !File.directory?(path)
|
|
245
|
+
end
|
|
246
|
+
|
|
247
|
+
def dir_exist?(path)
|
|
248
|
+
Dir.exist?(path)
|
|
249
|
+
end
|
|
250
|
+
|
|
251
|
+
def windows?
|
|
252
|
+
# Ruby only sets File::ALT_SEPARATOR on Windows and the Ruby standard
|
|
253
|
+
# library uses that to test what platform it's on.
|
|
254
|
+
!!File::ALT_SEPARATOR
|
|
255
|
+
end
|
|
256
|
+
|
|
257
|
+
# Creates a document object based on the Uri
|
|
258
|
+
def create_document(uri, content, doc_version)
|
|
259
|
+
case document_type(uri)
|
|
260
|
+
when :puppetfile
|
|
261
|
+
PuppetfileDocument.new(uri, content, doc_version)
|
|
262
|
+
when :manifest
|
|
263
|
+
ManifestDocument.new(uri, content, doc_version)
|
|
264
|
+
when :epp
|
|
265
|
+
EppDocument.new(uri, content, doc_version)
|
|
266
|
+
else
|
|
267
|
+
Document.new(uri, content, doc_version)
|
|
268
|
+
end
|
|
269
|
+
end
|
|
270
|
+
end
|
|
271
|
+
end
|
|
272
|
+
end
|
|
@@ -0,0 +1,239 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
module PuppetLanguageServer
|
|
4
|
+
module SessionState
|
|
5
|
+
class LanguageClient
|
|
6
|
+
attr_reader :message_handler, :format_on_type_filesize_limit, :folding_range, :folding_range_show_last_line, :use_puppetfile_resolver
|
|
7
|
+
|
|
8
|
+
# Client settings
|
|
9
|
+
attr_reader :format_on_type
|
|
10
|
+
|
|
11
|
+
DEFAULT_FORMAT_ON_TYPE_ENABLE = false
|
|
12
|
+
DEFAULT_FORMAT_ON_TYPE_FILESIZE_LIMIT = 4096
|
|
13
|
+
DEFAULT_FOLDING_RANGE_ENABLE = true # Default is true as per VS Code
|
|
14
|
+
DEFAULT_FOLDING_RANGE_SHOW_LAST_LINE = true # Default is true as per VS Code
|
|
15
|
+
DEFAULT_VALIDATE_RESOLVE_PUPPETFILES = true
|
|
16
|
+
|
|
17
|
+
def initialize(message_handler)
|
|
18
|
+
@message_handler = message_handler
|
|
19
|
+
@client_capabilites = {}
|
|
20
|
+
|
|
21
|
+
# Internal registry of dynamic registrations and their current state
|
|
22
|
+
# @registrations[ <[String] method_name>] = [
|
|
23
|
+
# {
|
|
24
|
+
# :id => [String] Request ID. Used for de-registration
|
|
25
|
+
# :registered => [Boolean] true | false
|
|
26
|
+
# :state => [Enum] :pending | :complete
|
|
27
|
+
# }
|
|
28
|
+
# ]
|
|
29
|
+
@registrations = {}
|
|
30
|
+
|
|
31
|
+
# Initial state. Not always the same as defaults
|
|
32
|
+
@folding_range = false
|
|
33
|
+
@folding_range_show_last_line = DEFAULT_FOLDING_RANGE_SHOW_LAST_LINE
|
|
34
|
+
@format_on_type = false
|
|
35
|
+
@format_on_type_filesize_limit = DEFAULT_FORMAT_ON_TYPE_FILESIZE_LIMIT
|
|
36
|
+
@use_puppetfile_resolver = DEFAULT_VALIDATE_RESOLVE_PUPPETFILES
|
|
37
|
+
end
|
|
38
|
+
|
|
39
|
+
def client_capability(*names)
|
|
40
|
+
safe_hash_traverse(@client_capabilites, *names)
|
|
41
|
+
end
|
|
42
|
+
|
|
43
|
+
def send_configuration_request
|
|
44
|
+
params = LSP::ConfigurationParams.new.from_h!('items' => [])
|
|
45
|
+
params.items << LSP::ConfigurationItem.new.from_h!('section' => 'puppet')
|
|
46
|
+
|
|
47
|
+
message_handler.protocol.send_client_request('workspace/configuration', params)
|
|
48
|
+
true
|
|
49
|
+
end
|
|
50
|
+
|
|
51
|
+
def parse_lsp_initialize!(initialize_params = {})
|
|
52
|
+
@client_capabilites = initialize_params['capabilities']
|
|
53
|
+
end
|
|
54
|
+
|
|
55
|
+
def parse_lsp_configuration_settings!(settings = {})
|
|
56
|
+
# format on type enabled
|
|
57
|
+
value = to_boolean(safe_hash_traverse(settings, 'puppet', 'editorService', 'formatOnType', 'enable'), DEFAULT_FORMAT_ON_TYPE_ENABLE)
|
|
58
|
+
unless value == @format_on_type # Ummm no.
|
|
59
|
+
# Is dynamic registration available?
|
|
60
|
+
if client_capability('textDocument', 'onTypeFormatting', 'dynamicRegistration') == true
|
|
61
|
+
if value
|
|
62
|
+
register_capability('textDocument/onTypeFormatting', PuppetLanguageServer::ServerCapabilites.document_on_type_formatting_options)
|
|
63
|
+
else
|
|
64
|
+
unregister_capability('textDocument/onTypeFormatting')
|
|
65
|
+
end
|
|
66
|
+
end
|
|
67
|
+
@format_on_type = value
|
|
68
|
+
end
|
|
69
|
+
# format on type file size
|
|
70
|
+
@format_on_type_filesize_limit = to_integer(safe_hash_traverse(settings, 'puppet', 'editorService', 'formatOnType', 'maxFileSize'), DEFAULT_FORMAT_ON_TYPE_FILESIZE_LIMIT, 0)
|
|
71
|
+
|
|
72
|
+
# use puppetfile resolver
|
|
73
|
+
@use_puppetfile_resolver = to_boolean(safe_hash_traverse(settings, 'puppet', 'validate', 'resolvePuppetfiles'), DEFAULT_VALIDATE_RESOLVE_PUPPETFILES)
|
|
74
|
+
|
|
75
|
+
# folding range enabled
|
|
76
|
+
value = to_boolean(safe_hash_traverse(settings, 'puppet', 'editorService', 'foldingRange', 'enable'), DEFAULT_FOLDING_RANGE_ENABLE) && PuppetLanguageServer::ServerCapabilites.folding_provider_supported?
|
|
77
|
+
unless value == @folding_range # Ummm no.
|
|
78
|
+
# Is dynamic registration available?
|
|
79
|
+
if client_capability('textDocument', 'foldingRange', 'dynamicRegistration') == true
|
|
80
|
+
if value
|
|
81
|
+
register_capability('textDocument/foldingRange', PuppetLanguageServer::ServerCapabilites.document_on_type_formatting_options)
|
|
82
|
+
else
|
|
83
|
+
unregister_capability('textDocument/foldingRange')
|
|
84
|
+
end
|
|
85
|
+
end
|
|
86
|
+
@folding_range = value
|
|
87
|
+
end
|
|
88
|
+
# folding range last line display
|
|
89
|
+
@folding_range_show_last_line = to_boolean(safe_hash_traverse(settings, 'puppet', 'editorService', 'foldingRange', 'showLastLine'), DEFAULT_FOLDING_RANGE_SHOW_LAST_LINE)
|
|
90
|
+
end
|
|
91
|
+
|
|
92
|
+
def capability_registrations(method)
|
|
93
|
+
return [{ registered: false, state: :complete }] if @registrations[method].nil? || @registrations[method].empty?
|
|
94
|
+
|
|
95
|
+
@registrations[method].dup
|
|
96
|
+
end
|
|
97
|
+
|
|
98
|
+
def register_capability(method, options = {})
|
|
99
|
+
id = new_request_id
|
|
100
|
+
|
|
101
|
+
PuppetLanguageServer.log_message(:info, "Attempting to dynamically register the #{method} method with id #{id}")
|
|
102
|
+
|
|
103
|
+
if @registrations[method] && @registrations[method].select { |i| i[:state] == :pending }.count > 0
|
|
104
|
+
# The protocol doesn't specify whether this is allowed and is probably per client specific. For the moment we will allow
|
|
105
|
+
# the registration to be sent but log a message that something may be wrong.
|
|
106
|
+
PuppetLanguageServer.log_message(:warn, "A dynamic registration/deregistration for the #{method} method is already in progress")
|
|
107
|
+
end
|
|
108
|
+
|
|
109
|
+
params = LSP::RegistrationParams.new.from_h!('registrations' => [])
|
|
110
|
+
params.registrations << LSP::Registration.new.from_h!('id' => id, 'method' => method, 'registerOptions' => options)
|
|
111
|
+
# Note - Don't put more than one method per request even though you can. It makes decoding errors much harder!
|
|
112
|
+
|
|
113
|
+
@registrations[method] = [] if @registrations[method].nil?
|
|
114
|
+
@registrations[method] << { registered: false, state: :pending, id: id }
|
|
115
|
+
|
|
116
|
+
message_handler.protocol.send_client_request('client/registerCapability', params)
|
|
117
|
+
true
|
|
118
|
+
end
|
|
119
|
+
|
|
120
|
+
def unregister_capability(method)
|
|
121
|
+
if @registrations[method].nil?
|
|
122
|
+
PuppetLanguageServer.log_message(:debug, "No registrations to deregister for the #{method}")
|
|
123
|
+
return true
|
|
124
|
+
end
|
|
125
|
+
|
|
126
|
+
params = LSP::UnregistrationParams.new.from_h!('unregisterations' => [])
|
|
127
|
+
@registrations[method].each do |reg|
|
|
128
|
+
next if reg[:id].nil?
|
|
129
|
+
|
|
130
|
+
PuppetLanguageServer.log_message(:warn, "A dynamic registration/deregistration for the #{method} method, with id #{reg[:id]} is already in progress") if reg[:state] == :pending
|
|
131
|
+
# Ignore registrations that don't need to be unregistered
|
|
132
|
+
next if reg[:state] == :complete && !reg[:registered]
|
|
133
|
+
|
|
134
|
+
params.unregisterations << LSP::Unregistration.new.from_h!('id' => reg[:id], 'method' => method)
|
|
135
|
+
reg[:state] = :pending
|
|
136
|
+
end
|
|
137
|
+
|
|
138
|
+
if params.unregisterations.count.zero?
|
|
139
|
+
PuppetLanguageServer.log_message(:debug, "Nothing to deregister for the #{method} method")
|
|
140
|
+
return true
|
|
141
|
+
end
|
|
142
|
+
|
|
143
|
+
message_handler.protocol.send_client_request('client/unregisterCapability', params)
|
|
144
|
+
true
|
|
145
|
+
end
|
|
146
|
+
|
|
147
|
+
def parse_register_capability_response!(response, original_request)
|
|
148
|
+
raise 'Response is not from client/registerCapability request' unless original_request.rpc_method == 'client/registerCapability'
|
|
149
|
+
|
|
150
|
+
unless response.is_successful
|
|
151
|
+
original_request.params.registrations.each do |reg|
|
|
152
|
+
# Mark the registration as completed and failed
|
|
153
|
+
@registrations[reg.method__lsp] = [] if @registrations[reg.method__lsp].nil?
|
|
154
|
+
@registrations[reg.method__lsp].select { |i| i[:id] == reg.id }.each { |i| i[:registered] = false; i[:state] = :complete } # rubocop:disable Style/Semicolon This is fine
|
|
155
|
+
end
|
|
156
|
+
return true
|
|
157
|
+
end
|
|
158
|
+
|
|
159
|
+
original_request.params.registrations.each do |reg|
|
|
160
|
+
PuppetLanguageServer.log_message(:info, "Succesfully dynamically registered the #{reg.method__lsp} method")
|
|
161
|
+
|
|
162
|
+
# Mark the registration as completed and succesful
|
|
163
|
+
@registrations[reg.method__lsp] = [] if @registrations[reg.method__lsp].nil?
|
|
164
|
+
@registrations[reg.method__lsp].select { |i| i[:id] == reg.id }.each { |i| i[:registered] = true; i[:state] = :complete } # rubocop:disable Style/Semicolon This is fine
|
|
165
|
+
|
|
166
|
+
# If we just registered the workspace/didChangeConfiguration method then
|
|
167
|
+
# also trigger a configuration request to get the initial state
|
|
168
|
+
send_configuration_request if reg.method__lsp == 'workspace/didChangeConfiguration'
|
|
169
|
+
end
|
|
170
|
+
|
|
171
|
+
true
|
|
172
|
+
end
|
|
173
|
+
|
|
174
|
+
def parse_unregister_capability_response!(response, original_request)
|
|
175
|
+
raise 'Response is not from client/unregisterCapability request' unless original_request.rpc_method == 'client/unregisterCapability'
|
|
176
|
+
|
|
177
|
+
unless response.is_successful
|
|
178
|
+
original_request.params.unregisterations.each do |reg|
|
|
179
|
+
# Mark the registration as completed and failed
|
|
180
|
+
@registrations[reg.method__lsp] = [] if @registrations[reg.method__lsp].nil?
|
|
181
|
+
@registrations[reg.method__lsp].select { |i| i[:id] == reg.id && i[:registered] }.each { |i| i[:state] = :complete }
|
|
182
|
+
@registrations[reg.method__lsp].delete_if { |i| i[:id] == reg.id && !i[:registered] }
|
|
183
|
+
end
|
|
184
|
+
return true
|
|
185
|
+
end
|
|
186
|
+
|
|
187
|
+
original_request.params.unregisterations.each do |reg|
|
|
188
|
+
PuppetLanguageServer.log_message(:info, "Succesfully dynamically unregistered the #{reg.method__lsp} method")
|
|
189
|
+
|
|
190
|
+
# Remove registrations
|
|
191
|
+
@registrations[reg.method__lsp] = [] if @registrations[reg.method__lsp].nil?
|
|
192
|
+
@registrations[reg.method__lsp].delete_if { |i| i[:id] == reg.id }
|
|
193
|
+
end
|
|
194
|
+
|
|
195
|
+
true
|
|
196
|
+
end
|
|
197
|
+
|
|
198
|
+
private
|
|
199
|
+
|
|
200
|
+
def to_boolean(value, default = false)
|
|
201
|
+
return default if value.nil?
|
|
202
|
+
return value if value == true || value == false # rubocop:disable Style/MultipleComparison
|
|
203
|
+
|
|
204
|
+
value.to_s =~ %r{^(true|t|yes|y|1)$/i}
|
|
205
|
+
end
|
|
206
|
+
|
|
207
|
+
def to_integer(value, default = nil, min = nil, max = nil)
|
|
208
|
+
return default if value.nil?
|
|
209
|
+
|
|
210
|
+
begin
|
|
211
|
+
intv = Integer(value)
|
|
212
|
+
return default if !min.nil? && intv < min
|
|
213
|
+
return default if !max.nil? && intv > max
|
|
214
|
+
|
|
215
|
+
intv
|
|
216
|
+
rescue ArgumentError
|
|
217
|
+
default
|
|
218
|
+
end
|
|
219
|
+
end
|
|
220
|
+
|
|
221
|
+
def new_request_id
|
|
222
|
+
SecureRandom.uuid
|
|
223
|
+
end
|
|
224
|
+
|
|
225
|
+
def safe_hash_traverse(hash, *names)
|
|
226
|
+
return nil if names.empty? || hash.nil? || hash.empty?
|
|
227
|
+
|
|
228
|
+
item = nil
|
|
229
|
+
loop do
|
|
230
|
+
name = names.shift
|
|
231
|
+
item = item.nil? ? hash[name] : item[name]
|
|
232
|
+
return nil if item.nil?
|
|
233
|
+
return item if names.empty?
|
|
234
|
+
end
|
|
235
|
+
nil
|
|
236
|
+
end
|
|
237
|
+
end
|
|
238
|
+
end
|
|
239
|
+
end
|
|
@@ -0,0 +1,162 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
module PuppetLanguageServer
|
|
4
|
+
module SessionState
|
|
5
|
+
class ObjectCache
|
|
6
|
+
SECTIONS = %i[class type function datatype fact].freeze
|
|
7
|
+
ORIGINS = %i[default workspace bolt].freeze
|
|
8
|
+
|
|
9
|
+
def initialize(_options = {})
|
|
10
|
+
@cache_lock = Mutex.new
|
|
11
|
+
@inmemory_cache = {}
|
|
12
|
+
# The cache consists of hash of hashes
|
|
13
|
+
# @inmemory_cache[<origin>][<section>] = [ Array of SidecarProtocol Objects ]
|
|
14
|
+
end
|
|
15
|
+
|
|
16
|
+
def import_sidecar_list!(list, section, origin)
|
|
17
|
+
return if origin.nil?
|
|
18
|
+
return if section.nil?
|
|
19
|
+
|
|
20
|
+
list = [] if list.nil?
|
|
21
|
+
|
|
22
|
+
@cache_lock.synchronize do
|
|
23
|
+
# Remove the existing items
|
|
24
|
+
remove_section_impl(section, origin)
|
|
25
|
+
# Set the list
|
|
26
|
+
@inmemory_cache[origin] = {} if @inmemory_cache[origin].nil?
|
|
27
|
+
@inmemory_cache[origin][section] = list.dup
|
|
28
|
+
end
|
|
29
|
+
nil
|
|
30
|
+
end
|
|
31
|
+
|
|
32
|
+
def remove_section!(section, origin = nil)
|
|
33
|
+
@cache_lock.synchronize do
|
|
34
|
+
remove_section_impl(section, origin)
|
|
35
|
+
end
|
|
36
|
+
nil
|
|
37
|
+
end
|
|
38
|
+
|
|
39
|
+
def remove_origin!(origin)
|
|
40
|
+
@cache_lock.synchronize do
|
|
41
|
+
@inmemory_cache[origin] = nil
|
|
42
|
+
end
|
|
43
|
+
nil
|
|
44
|
+
end
|
|
45
|
+
|
|
46
|
+
def origin_exist?(origin)
|
|
47
|
+
@cache_lock.synchronize do
|
|
48
|
+
return @inmemory_cache.key?(origin)
|
|
49
|
+
end
|
|
50
|
+
end
|
|
51
|
+
|
|
52
|
+
def section_in_origin_exist?(section, origin)
|
|
53
|
+
@cache_lock.synchronize do
|
|
54
|
+
return false if @inmemory_cache[origin].nil? || @inmemory_cache[origin].empty?
|
|
55
|
+
|
|
56
|
+
@inmemory_cache[origin].key?(section)
|
|
57
|
+
end
|
|
58
|
+
end
|
|
59
|
+
|
|
60
|
+
# section => <Type of object in the file :function, :type, :class, :datatype>
|
|
61
|
+
def object_by_name(section, name, options = {})
|
|
62
|
+
# options[:exclude_origins]
|
|
63
|
+
# options[:fuzzy_match]
|
|
64
|
+
options = {
|
|
65
|
+
exclude_origins: [],
|
|
66
|
+
fuzzy_match: false
|
|
67
|
+
}.merge(options)
|
|
68
|
+
|
|
69
|
+
name = name.intern if name.is_a?(String)
|
|
70
|
+
return nil if section.nil?
|
|
71
|
+
|
|
72
|
+
@cache_lock.synchronize do
|
|
73
|
+
@inmemory_cache.each do |origin, sections|
|
|
74
|
+
next if sections.nil? || sections[section].nil? || sections[section].empty?
|
|
75
|
+
next if options[:exclude_origins].include?(origin)
|
|
76
|
+
|
|
77
|
+
sections[section].each do |item|
|
|
78
|
+
match = options[:fuzzy_match] ? fuzzy_match?(item.key, name) : item.key == name
|
|
79
|
+
return item if match
|
|
80
|
+
end
|
|
81
|
+
end
|
|
82
|
+
end
|
|
83
|
+
nil
|
|
84
|
+
end
|
|
85
|
+
|
|
86
|
+
# Performs fuzzy text matching of Puppet Language Type names
|
|
87
|
+
# e.g 'TargetSpec' in 'Boltlib::TargetSpec'
|
|
88
|
+
# @api private
|
|
89
|
+
def fuzzy_match?(obj, test_obj)
|
|
90
|
+
value = obj.is_a?(String) ? obj.dup : obj.to_s
|
|
91
|
+
test_string = test_obj.is_a?(String) ? test_obj.dup : test_obj.to_s
|
|
92
|
+
|
|
93
|
+
# Test for equality
|
|
94
|
+
return true if value == test_string
|
|
95
|
+
|
|
96
|
+
# Test for a shortname
|
|
97
|
+
if !test_string.start_with?('::') && value.end_with?("::#{test_string}")
|
|
98
|
+
# e.g 'TargetSpec' in 'Boltlib::TargetSpec'
|
|
99
|
+
return true
|
|
100
|
+
end
|
|
101
|
+
|
|
102
|
+
false
|
|
103
|
+
end
|
|
104
|
+
|
|
105
|
+
# section => <Type of object in the file :function, :type, :class, :datatype>
|
|
106
|
+
# options[:exclude_origins]
|
|
107
|
+
def object_names_by_section(section, options = {})
|
|
108
|
+
options = {
|
|
109
|
+
exclude_origins: []
|
|
110
|
+
}.merge(options)
|
|
111
|
+
result = []
|
|
112
|
+
return result if section.nil?
|
|
113
|
+
|
|
114
|
+
@cache_lock.synchronize do
|
|
115
|
+
@inmemory_cache.each do |origin, sections|
|
|
116
|
+
next if sections.nil? || sections[section].nil? || sections[section].empty?
|
|
117
|
+
next if options[:exclude_origins].include?(origin)
|
|
118
|
+
|
|
119
|
+
result.concat(sections[section].map { |i| i.key })
|
|
120
|
+
end
|
|
121
|
+
end
|
|
122
|
+
result.uniq!
|
|
123
|
+
result.compact
|
|
124
|
+
end
|
|
125
|
+
|
|
126
|
+
# section => <Type of object in the file :function, :type, :class, :datatype>
|
|
127
|
+
def objects_by_section(section, &_block)
|
|
128
|
+
return if section.nil?
|
|
129
|
+
|
|
130
|
+
@cache_lock.synchronize do
|
|
131
|
+
@inmemory_cache.each_value do |sections|
|
|
132
|
+
next if sections.nil? || sections[section].nil? || sections[section].empty?
|
|
133
|
+
|
|
134
|
+
sections[section].each { |i| yield i.key, i }
|
|
135
|
+
end
|
|
136
|
+
end
|
|
137
|
+
end
|
|
138
|
+
|
|
139
|
+
def all_objects(&_block)
|
|
140
|
+
@cache_lock.synchronize do
|
|
141
|
+
@inmemory_cache.each_value do |sections|
|
|
142
|
+
next if sections.nil?
|
|
143
|
+
|
|
144
|
+
sections.each_value do |list|
|
|
145
|
+
list.each { |i| yield i.key, i }
|
|
146
|
+
end
|
|
147
|
+
end
|
|
148
|
+
end
|
|
149
|
+
end
|
|
150
|
+
|
|
151
|
+
private
|
|
152
|
+
|
|
153
|
+
def remove_section_impl(section, origin = nil)
|
|
154
|
+
@inmemory_cache.each do |list_origin, sections|
|
|
155
|
+
next unless origin.nil? || list_origin == origin
|
|
156
|
+
|
|
157
|
+
sections[section].clear unless sections.nil? || sections[section].nil?
|
|
158
|
+
end
|
|
159
|
+
end
|
|
160
|
+
end
|
|
161
|
+
end
|
|
162
|
+
end
|