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.
Files changed (109) hide show
  1. checksums.yaml +7 -0
  2. data/CHANGELOG.md +510 -0
  3. data/CODEOWNERS +2 -0
  4. data/CODE_OF_CONDUCT.md +46 -0
  5. data/CONTRIBUTING.md +54 -0
  6. data/Gemfile +53 -0
  7. data/LICENSE +201 -0
  8. data/README.md +308 -0
  9. data/Rakefile +185 -0
  10. data/bin/puppet-debugserver +8 -0
  11. data/bin/puppet-languageserver +7 -0
  12. data/bin/puppet-languageserver-sidecar +7 -0
  13. data/lib/dsp/dsp.rb +7 -0
  14. data/lib/dsp/dsp_base.rb +62 -0
  15. data/lib/dsp/dsp_protocol.rb +4619 -0
  16. data/lib/lsp/lsp.rb +10 -0
  17. data/lib/lsp/lsp_base.rb +63 -0
  18. data/lib/lsp/lsp_custom.rb +170 -0
  19. data/lib/lsp/lsp_enums.rb +143 -0
  20. data/lib/lsp/lsp_protocol.rb +2785 -0
  21. data/lib/lsp/lsp_protocol_callhierarchy.proposed.rb +239 -0
  22. data/lib/lsp/lsp_protocol_colorprovider.rb +100 -0
  23. data/lib/lsp/lsp_protocol_configuration.rb +82 -0
  24. data/lib/lsp/lsp_protocol_declaration.rb +73 -0
  25. data/lib/lsp/lsp_protocol_foldingrange.rb +129 -0
  26. data/lib/lsp/lsp_protocol_implementation.rb +75 -0
  27. data/lib/lsp/lsp_protocol_progress.rb +200 -0
  28. data/lib/lsp/lsp_protocol_selectionrange.rb +79 -0
  29. data/lib/lsp/lsp_protocol_sematictokens.proposed.rb +340 -0
  30. data/lib/lsp/lsp_protocol_typedefinition.rb +75 -0
  31. data/lib/lsp/lsp_protocol_workspacefolders.rb +174 -0
  32. data/lib/lsp/lsp_types.rb +1534 -0
  33. data/lib/puppet-debugserver/debug_session/break_points.rb +137 -0
  34. data/lib/puppet-debugserver/debug_session/flow_control.rb +161 -0
  35. data/lib/puppet-debugserver/debug_session/hook_handlers.rb +295 -0
  36. data/lib/puppet-debugserver/debug_session/puppet_session_run_mode.rb +66 -0
  37. data/lib/puppet-debugserver/debug_session/puppet_session_state.rb +122 -0
  38. data/lib/puppet-debugserver/hooks.rb +132 -0
  39. data/lib/puppet-debugserver/message_handler.rb +277 -0
  40. data/lib/puppet-debugserver/puppet_debug_session.rb +541 -0
  41. data/lib/puppet-debugserver/puppet_monkey_patches.rb +118 -0
  42. data/lib/puppet-languageserver/client_session_state.rb +119 -0
  43. data/lib/puppet-languageserver/crash_dump.rb +50 -0
  44. data/lib/puppet-languageserver/epp/validation_provider.rb +34 -0
  45. data/lib/puppet-languageserver/facter_helper.rb +25 -0
  46. data/lib/puppet-languageserver/global_queues/sidecar_queue.rb +205 -0
  47. data/lib/puppet-languageserver/global_queues/single_instance_queue.rb +126 -0
  48. data/lib/puppet-languageserver/global_queues/validation_queue.rb +102 -0
  49. data/lib/puppet-languageserver/global_queues.rb +16 -0
  50. data/lib/puppet-languageserver/manifest/completion_provider.rb +331 -0
  51. data/lib/puppet-languageserver/manifest/definition_provider.rb +99 -0
  52. data/lib/puppet-languageserver/manifest/document_symbol_provider.rb +228 -0
  53. data/lib/puppet-languageserver/manifest/folding_provider.rb +226 -0
  54. data/lib/puppet-languageserver/manifest/format_on_type_provider.rb +143 -0
  55. data/lib/puppet-languageserver/manifest/hover_provider.rb +221 -0
  56. data/lib/puppet-languageserver/manifest/signature_provider.rb +169 -0
  57. data/lib/puppet-languageserver/manifest/validation_provider.rb +127 -0
  58. data/lib/puppet-languageserver/message_handler.rb +462 -0
  59. data/lib/puppet-languageserver/providers.rb +18 -0
  60. data/lib/puppet-languageserver/puppet_helper.rb +108 -0
  61. data/lib/puppet-languageserver/puppet_lexer_helper.rb +55 -0
  62. data/lib/puppet-languageserver/puppet_monkey_patches.rb +39 -0
  63. data/lib/puppet-languageserver/puppet_parser_helper.rb +212 -0
  64. data/lib/puppet-languageserver/puppetfile/validation_provider.rb +185 -0
  65. data/lib/puppet-languageserver/server_capabilities.rb +48 -0
  66. data/lib/puppet-languageserver/session_state/document_store.rb +272 -0
  67. data/lib/puppet-languageserver/session_state/language_client.rb +239 -0
  68. data/lib/puppet-languageserver/session_state/object_cache.rb +162 -0
  69. data/lib/puppet-languageserver/sidecar_protocol.rb +532 -0
  70. data/lib/puppet-languageserver/uri_helper.rb +46 -0
  71. data/lib/puppet-languageserver-sidecar/cache/base.rb +36 -0
  72. data/lib/puppet-languageserver-sidecar/cache/filesystem.rb +111 -0
  73. data/lib/puppet-languageserver-sidecar/cache/null.rb +27 -0
  74. data/lib/puppet-languageserver-sidecar/facter_helper.rb +41 -0
  75. data/lib/puppet-languageserver-sidecar/puppet_environment_monkey_patches.rb +52 -0
  76. data/lib/puppet-languageserver-sidecar/puppet_helper.rb +281 -0
  77. data/lib/puppet-languageserver-sidecar/puppet_modulepath_monkey_patches.rb +146 -0
  78. data/lib/puppet-languageserver-sidecar/puppet_monkey_patches.rb +9 -0
  79. data/lib/puppet-languageserver-sidecar/puppet_parser_helper.rb +77 -0
  80. data/lib/puppet-languageserver-sidecar/puppet_strings_helper.rb +399 -0
  81. data/lib/puppet-languageserver-sidecar/puppet_strings_monkey_patches.rb +16 -0
  82. data/lib/puppet-languageserver-sidecar/sidecar_protocol_extensions.rb +16 -0
  83. data/lib/puppet-languageserver-sidecar/workspace.rb +89 -0
  84. data/lib/puppet_debugserver.rb +164 -0
  85. data/lib/puppet_editor_services/connection/base.rb +62 -0
  86. data/lib/puppet_editor_services/connection/stdio.rb +25 -0
  87. data/lib/puppet_editor_services/connection/tcp.rb +34 -0
  88. data/lib/puppet_editor_services/handler/base.rb +16 -0
  89. data/lib/puppet_editor_services/handler/debug_adapter.rb +63 -0
  90. data/lib/puppet_editor_services/handler/json_rpc.rb +133 -0
  91. data/lib/puppet_editor_services/logging.rb +45 -0
  92. data/lib/puppet_editor_services/protocol/base.rb +27 -0
  93. data/lib/puppet_editor_services/protocol/debug_adapter.rb +135 -0
  94. data/lib/puppet_editor_services/protocol/debug_adapter_messages.rb +171 -0
  95. data/lib/puppet_editor_services/protocol/json_rpc.rb +241 -0
  96. data/lib/puppet_editor_services/protocol/json_rpc_messages.rb +200 -0
  97. data/lib/puppet_editor_services/server/base.rb +42 -0
  98. data/lib/puppet_editor_services/server/stdio.rb +85 -0
  99. data/lib/puppet_editor_services/server/tcp.rb +349 -0
  100. data/lib/puppet_editor_services/server.rb +15 -0
  101. data/lib/puppet_editor_services/version.rb +36 -0
  102. data/lib/puppet_editor_services.rb +8 -0
  103. data/lib/puppet_languageserver.rb +263 -0
  104. data/lib/puppet_languageserver_sidecar.rb +361 -0
  105. data/puppet-debugserver +11 -0
  106. data/puppet-editor-services.gemspec +29 -0
  107. data/puppet-languageserver +15 -0
  108. data/puppet-languageserver-sidecar +14 -0
  109. 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