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,462 @@
1
+ # frozen_string_literal: true
2
+
3
+ require 'puppet_editor_services/handler/json_rpc'
4
+ require 'puppet_editor_services/protocol/json_rpc_messages'
5
+ require 'puppet-languageserver/server_capabilities'
6
+ require 'puppet-languageserver/client_session_state'
7
+ require 'puppet-languageserver/global_queues'
8
+
9
+ module PuppetLanguageServer
10
+ class MessageHandler < PuppetEditorServices::Handler::JsonRPC
11
+ def initialize(*_)
12
+ super
13
+ @session_state = ClientSessionState.new(self)
14
+ end
15
+
16
+ def session_state # rubocop:disable Style/TrivialAccessors During the refactor, this is fine.
17
+ @session_state
18
+ end
19
+
20
+ def language_client
21
+ session_state.language_client
22
+ end
23
+
24
+ def documents
25
+ session_state.documents
26
+ end
27
+
28
+ def request_initialize(_, json_rpc_message)
29
+ PuppetLanguageServer.log_message(:debug, 'Received initialize method')
30
+
31
+ 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?
34
+ # Setup static registrations if dynamic registration is not available
35
+ info = {
36
+ documentOnTypeFormattingProvider: !language_client.client_capability('textDocument', 'onTypeFormatting', 'dynamicRegistration'),
37
+ foldingRangeProvider: static_folding_provider
38
+ }
39
+
40
+ # Configure the document store
41
+ documents.initialize_store(
42
+ workspace: workspace_root_from_initialize_params(json_rpc_message.params)
43
+ )
44
+
45
+ # Initiate loading the object_cache
46
+ session_state.load_default_data!
47
+ session_state.load_static_data!
48
+
49
+ # Initiate loading of the workspace if needed
50
+ session_state.load_workspace_data! if documents.store_has_module_metadata? || documents.store_has_environmentconf?
51
+
52
+ { 'capabilities' => PuppetLanguageServer::ServerCapabilites.capabilities(info) }
53
+ end
54
+
55
+ def request_shutdown(_, _json_rpc_message)
56
+ PuppetLanguageServer.log_message(:debug, 'Received shutdown method')
57
+ nil
58
+ end
59
+
60
+ def request_puppet_getversion(_, _json_rpc_message)
61
+ LSP::PuppetVersion.new(
62
+ 'languageServerVersion' => PuppetEditorServices.version,
63
+ 'puppetVersion' => Puppet.version,
64
+ 'facterVersion' => Facter.version,
65
+ 'factsLoaded' => session_state.facts_loaded?,
66
+ 'functionsLoaded' => session_state.default_functions_loaded?,
67
+ 'typesLoaded' => session_state.default_types_loaded?,
68
+ 'classesLoaded' => session_state.default_classes_loaded?
69
+ )
70
+ end
71
+
72
+ def request_puppet_getfacts(_, _json_rpc_message)
73
+ results = PuppetLanguageServer::FacterHelper.facts_to_hash(session_state)
74
+ LSP::PuppetFactResponse.new('facts' => results)
75
+ end
76
+
77
+ def request_puppet_getresource(_, json_rpc_message)
78
+ type_name = json_rpc_message.params['typename']
79
+ title = json_rpc_message.params['title']
80
+ return LSP::PuppetResourceResponse.new('error' => 'Missing Typename') if type_name.nil?
81
+
82
+ resource_list = PuppetLanguageServer::PuppetHelper.get_puppet_resource(session_state, type_name, title, documents.store_root_path)
83
+ return LSP::PuppetResourceResponse.new('data' => '') if resource_list.nil? || resource_list.length.zero?
84
+
85
+ content = "#{resource_list.map(&:manifest).join("\n\n")}\n"
86
+ LSP::PuppetResourceResponse.new('data' => content)
87
+ end
88
+
89
+ def request_puppet_compilenodegraph(_, json_rpc_message)
90
+ file_uri = json_rpc_message.params['external']
91
+ return LSP::PuppetNodeGraphResponse.new('error' => 'Files of this type can not be used to create a node graph.') unless documents.document_type(file_uri) == :manifest
92
+
93
+ document = documents.document(file_uri)
94
+
95
+ begin
96
+ node_graph = PuppetLanguageServer::PuppetHelper.get_node_graph(session_state, document.content, documents.store_root_path)
97
+ LSP::PuppetNodeGraphResponse.new('vertices' => node_graph.vertices,
98
+ 'edges' => node_graph.edges,
99
+ 'error' => node_graph.error_content)
100
+ rescue StandardError => e
101
+ PuppetLanguageServer.log_message(:error, "(puppet/compileNodeGraph) Error generating node graph. #{e}")
102
+ LSP::PuppetNodeGraphResponse.new('error' => 'An internal error occured while generating the the node graph. Please see the debug log files for more information.')
103
+ end
104
+ end
105
+
106
+ def request_puppetfile_getdependencies(_, json_rpc_message)
107
+ file_uri = json_rpc_message.params['uri']
108
+ return LSP::PuppetfileDependencyResponse.new('error' => 'Must be a puppetfile in order to find dependencies.') unless documents.document_type(file_uri) == :puppetfile
109
+
110
+ document = documents.document(file_uri)
111
+
112
+ result = []
113
+ begin
114
+ result = PuppetLanguageServer::Puppetfile::ValidationProvider.find_dependencies(document.content)
115
+ rescue StandardError => e
116
+ PuppetLanguageServer.log_message(:error, "(puppetfile/getdependencies) Error parsing puppetfile. #{e}")
117
+ return LSP::PuppetfileDependencyResponse.new('error' => 'An internal error occured while parsing the puppetfile. Please see the debug log files for more information.')
118
+ end
119
+
120
+ LSP::PuppetfileDependencyResponse.new('dependencies' => result)
121
+ end
122
+
123
+ def request_puppet_fixdiagnosticerrors(_, json_rpc_message)
124
+ formatted_request = LSP::PuppetFixDiagnosticErrorsRequest.new(json_rpc_message.params)
125
+ file_uri = formatted_request.documentUri
126
+ content = documents.document_content(file_uri)
127
+
128
+ case documents.document_type(file_uri)
129
+ when :manifest
130
+ changes, new_content = PuppetLanguageServer::Manifest::ValidationProvider.fix_validate_errors(session_state, content)
131
+ else
132
+ raise "Unable to fixDiagnosticErrors on #{file_uri}"
133
+ end
134
+
135
+ LSP::PuppetFixDiagnosticErrorsResponse.new(
136
+ 'documentUri' => formatted_request.documentUri,
137
+ 'fixesApplied' => changes,
138
+ 'newContent' => changes > 0 || formatted_request.alwaysReturnContent ? new_content : nil
139
+ )
140
+ rescue StandardError => e
141
+ PuppetLanguageServer.log_message(:error, "(puppet/fixDiagnosticErrors) #{e}")
142
+ unless formatted_request.nil?
143
+ LSP::PuppetFixDiagnosticErrorsResponse.new(
144
+ 'documentUri' => formatted_request.documentUri,
145
+ 'fixesApplied' => 0,
146
+ 'newContent' => formatted_request.alwaysReturnContent ? content : nil
147
+ )
148
+ end
149
+ end
150
+
151
+ def request_textdocument_completion(_, json_rpc_message)
152
+ file_uri = json_rpc_message.params['textDocument']['uri']
153
+ line_num = json_rpc_message.params['position']['line']
154
+ char_num = json_rpc_message.params['position']['character']
155
+ content = documents.document_content(file_uri)
156
+ context = json_rpc_message.params['context'].nil? ? nil : LSP::CompletionContext.new(json_rpc_message.params['context'])
157
+
158
+ case documents.document_type(file_uri)
159
+ when :manifest
160
+ PuppetLanguageServer::Manifest::CompletionProvider.complete(session_state, content, line_num, char_num, context: context, tasks_mode: documents.plan_file?(file_uri))
161
+ else
162
+ raise "Unable to provide completion on #{file_uri}"
163
+ end
164
+ rescue StandardError => e
165
+ PuppetLanguageServer.log_message(:error, "(textDocument/completion) #{e}")
166
+ LSP::CompletionList.new('isIncomplete' => false, 'items' => [])
167
+ end
168
+
169
+ def request_textdocument_foldingrange(_, json_rpc_message)
170
+ return nil unless language_client.folding_range
171
+
172
+ file_uri = json_rpc_message.params['textDocument']['uri']
173
+ case documents.document_type(file_uri)
174
+ when :manifest
175
+ PuppetLanguageServer::Manifest::FoldingProvider.instance.folding_ranges(documents.document_tokens(file_uri))
176
+ else
177
+ raise "Unable to provide folding ranages on #{file_uri}"
178
+ end
179
+ rescue StandardError => e
180
+ PuppetLanguageServer.log_message(:error, "(textDocument/foldingRange) #{e}")
181
+ nil
182
+ end
183
+
184
+ def request_completionitem_resolve(_, json_rpc_message)
185
+ PuppetLanguageServer::Manifest::CompletionProvider.resolve(session_state, LSP::CompletionItem.new(json_rpc_message.params))
186
+ rescue StandardError => e
187
+ PuppetLanguageServer.log_message(:error, "(completionItem/resolve) #{e}")
188
+ # Spit back the same params if an error happens
189
+ json_rpc_message.params
190
+ end
191
+
192
+ def request_textdocument_hover(_, json_rpc_message)
193
+ file_uri = json_rpc_message.params['textDocument']['uri']
194
+ line_num = json_rpc_message.params['position']['line']
195
+ char_num = json_rpc_message.params['position']['character']
196
+ content = documents.document_content(file_uri)
197
+ case documents.document_type(file_uri)
198
+ when :manifest
199
+ PuppetLanguageServer::Manifest::HoverProvider.resolve(session_state, content, line_num, char_num, tasks_mode: documents.plan_file?(file_uri))
200
+ else
201
+ raise "Unable to provide hover on #{file_uri}"
202
+ end
203
+ rescue StandardError => e
204
+ PuppetLanguageServer.log_message(:error, "(textDocument/hover) #{e}")
205
+ nil
206
+ end
207
+
208
+ def request_textdocument_definition(_, json_rpc_message)
209
+ file_uri = json_rpc_message.params['textDocument']['uri']
210
+ line_num = json_rpc_message.params['position']['line']
211
+ char_num = json_rpc_message.params['position']['character']
212
+ content = documents.document_content(file_uri)
213
+
214
+ case documents.document_type(file_uri)
215
+ when :manifest
216
+ PuppetLanguageServer::Manifest::DefinitionProvider.find_definition(session_state, content, line_num, char_num, tasks_mode: documents.plan_file?(file_uri))
217
+ else
218
+ raise "Unable to provide definition on #{file_uri}"
219
+ end
220
+ rescue StandardError => e
221
+ PuppetLanguageServer.log_message(:error, "(textDocument/definition) #{e}")
222
+ nil
223
+ end
224
+
225
+ def request_textdocument_documentsymbol(_, json_rpc_message)
226
+ file_uri = json_rpc_message.params['textDocument']['uri']
227
+ content = documents.document_content(file_uri)
228
+
229
+ case documents.document_type(file_uri)
230
+ when :manifest
231
+ PuppetLanguageServer::Manifest::DocumentSymbolProvider.extract_document_symbols(content, tasks_mode: documents.plan_file?(file_uri))
232
+ else
233
+ raise "Unable to provide definition on #{file_uri}"
234
+ end
235
+ rescue StandardError => e
236
+ PuppetLanguageServer.log_message(:error, "(textDocument/documentSymbol) #{e}")
237
+ nil
238
+ end
239
+
240
+ def request_textdocument_ontypeformatting(_, json_rpc_message)
241
+ return nil unless language_client.format_on_type
242
+
243
+ file_uri = json_rpc_message.params['textDocument']['uri']
244
+ line_num = json_rpc_message.params['position']['line']
245
+ char_num = json_rpc_message.params['position']['character']
246
+ content = documents.document_content(file_uri)
247
+
248
+ case documents.document_type(file_uri)
249
+ when :manifest
250
+ PuppetLanguageServer::Manifest::FormatOnTypeProvider.instance.format(
251
+ content,
252
+ line_num,
253
+ char_num,
254
+ json_rpc_message.params['ch'],
255
+ json_rpc_message.params['options'],
256
+ language_client.format_on_type_filesize_limit
257
+ )
258
+ else
259
+ raise "Unable to format on type on #{file_uri}"
260
+ end
261
+ rescue StandardError => e
262
+ PuppetLanguageServer.log_message(:error, "(textDocument/onTypeFormatting) #{e}")
263
+ nil
264
+ end
265
+
266
+ def request_textdocument_signaturehelp(_, json_rpc_message)
267
+ file_uri = json_rpc_message.params['textDocument']['uri']
268
+ line_num = json_rpc_message.params['position']['line']
269
+ char_num = json_rpc_message.params['position']['character']
270
+ content = documents.document_content(file_uri)
271
+
272
+ case documents.document_type(file_uri)
273
+ when :manifest
274
+ PuppetLanguageServer::Manifest::SignatureProvider.signature_help(
275
+ session_state,
276
+ content,
277
+ line_num,
278
+ char_num,
279
+ tasks_mode: documents.plan_file?(file_uri)
280
+ )
281
+ else
282
+ raise "Unable to provide signatures on #{file_uri}"
283
+ end
284
+ rescue StandardError => e
285
+ PuppetLanguageServer.log_message(:error, "(textDocument/signatureHelp) #{e}")
286
+ nil
287
+ end
288
+
289
+ def request_workspace_symbol(_, json_rpc_message)
290
+ result = []
291
+ result.concat(PuppetLanguageServer::Manifest::DocumentSymbolProvider.workspace_symbols(json_rpc_message.params['query'], session_state.object_cache))
292
+ result
293
+ rescue StandardError => e
294
+ PuppetLanguageServer.log_message(:error, "(workspace/symbol) #{e}")
295
+ []
296
+ end
297
+
298
+ def notification_initialized(_, _json_rpc_message)
299
+ PuppetLanguageServer.log_message(:info, 'Client has received initialization')
300
+ # Raise a warning if the Puppet version is mismatched
301
+ server_options = protocol.connection.server.server_options
302
+ unless server_options[:puppet_version].nil? || server_options[:puppet_version] == Puppet.version
303
+ protocol.encode_and_send(
304
+ ::PuppetEditorServices::Protocol::JsonRPCMessages.new_notification(
305
+ 'window/showMessage',
306
+ 'type' => LSP::MessageType::WARNING,
307
+ 'message' => "Unable to use Puppet version '#{server_options[:puppet_version]}' as it is not available. Using version '#{Puppet.version}' instead."
308
+ )
309
+ )
310
+ end
311
+
312
+ # Register for workspace setting changes if it's supported
313
+ if language_client.client_capability('workspace', 'didChangeConfiguration', 'dynamicRegistration') == true
314
+ language_client.register_capability('workspace/didChangeConfiguration')
315
+ else
316
+ PuppetLanguageServer.log_message(:debug, 'Client does not support didChangeConfiguration dynamic registration. Using push method for configuration change detection.')
317
+ end
318
+ end
319
+
320
+ def notification_exit(_, _json_rpc_message)
321
+ PuppetLanguageServer.log_message(:info, 'Received exit notification. Closing connection to client...')
322
+ protocol.connection.close
323
+ end
324
+
325
+ def notification_textdocument_didopen(client_handler_id, json_rpc_message)
326
+ PuppetLanguageServer.log_message(:info, 'Received textDocument/didOpen notification.')
327
+ file_uri = json_rpc_message.params['textDocument']['uri']
328
+ content = json_rpc_message.params['textDocument']['text']
329
+ doc_version = json_rpc_message.params['textDocument']['version']
330
+ documents.set_document(file_uri, content, doc_version)
331
+ enqueue_validation(file_uri, doc_version, client_handler_id)
332
+ end
333
+
334
+ def notification_textdocument_didclose(client_handler_id, json_rpc_message)
335
+ PuppetLanguageServer.log_message(:info, 'Received textDocument/didClose notification.')
336
+ file_uri = json_rpc_message.params['textDocument']['uri']
337
+ documents.remove_document(file_uri)
338
+ enqueue_validation(file_uri, nil, client_handler_id)
339
+ end
340
+
341
+ def notification_textdocument_didchange(client_handler_id, json_rpc_message)
342
+ PuppetLanguageServer.log_message(:info, 'Received textDocument/didChange notification.')
343
+ file_uri = json_rpc_message.params['textDocument']['uri']
344
+ content = json_rpc_message.params['contentChanges'][0]['text'] # TODO: Bad hardcoding zero
345
+ doc_version = json_rpc_message.params['textDocument']['version']
346
+ documents.set_document(file_uri, content, doc_version)
347
+ enqueue_validation(file_uri, doc_version, client_handler_id)
348
+ end
349
+
350
+ def notification_textdocument_didsave(_, _json_rpc_message)
351
+ PuppetLanguageServer.log_message(:info, 'Received textDocument/didSave notification.')
352
+ # Expire the store cache so that the store information can re-evaluated
353
+ documents.expire_store_information
354
+ if documents.store_has_module_metadata? || documents.store_has_environmentconf?
355
+ # Load the workspace information
356
+ session_state.load_workspace_data!
357
+ else
358
+ # Purge the workspace information
359
+ session_state.purge_workspace_data!
360
+ end
361
+ end
362
+
363
+ def notification_workspace_didchangeconfiguration(_, json_rpc_message)
364
+ if json_rpc_message.params.key?('settings') && json_rpc_message.params['settings'].nil?
365
+ # This is a notification from a dynamic registration. Need to send a workspace/configuration
366
+ # request to get the actual configuration
367
+ language_client.send_configuration_request
368
+ else
369
+ language_client.parse_lsp_configuration_settings!(json_rpc_message.params['settings'])
370
+ end
371
+ end
372
+
373
+ def response_client_registercapability(_, json_rpc_message, original_request)
374
+ language_client.parse_register_capability_response!(json_rpc_message, original_request)
375
+ end
376
+
377
+ def response_client_unregistercapability(_, json_rpc_message, original_request)
378
+ language_client.parse_unregister_capability_response!(json_rpc_message, original_request)
379
+ end
380
+
381
+ def response_workspace_configuration(_, json_rpc_message, original_request)
382
+ return unless json_rpc_message.is_successful
383
+
384
+ original_request.params.items.each_with_index do |item, index|
385
+ # The response from the client strips the section name so we need to re-add it
386
+ language_client.parse_lsp_configuration_settings!(item.section => json_rpc_message.result[index])
387
+ end
388
+ end
389
+
390
+ def unhandled_exception(error, options)
391
+ super
392
+ PuppetLanguageServer::CrashDump.write_crash_file(error, session_state, nil, options)
393
+ end
394
+
395
+ private
396
+
397
+ def enqueue_validation(file_uri, doc_version, client_handler_id, options = {})
398
+ if documents.document_type(file_uri) == :puppetfile
399
+ options[:resolve_puppetfile] = language_client.use_puppetfile_resolver
400
+ options[:puppet_version] = Puppet.version
401
+ options[:module_path] = PuppetLanguageServer::PuppetHelper.module_path
402
+ end
403
+ GlobalQueues.validate_queue.enqueue(file_uri, doc_version, client_handler_id, options)
404
+ end
405
+
406
+ def workspace_root_from_initialize_params(params)
407
+ if params.key?('workspaceFolders')
408
+ return nil if params['workspaceFolders'].nil? || params['workspaceFolders'].empty?
409
+
410
+ # We don't support multiple workspace folders yet, so just select the first one
411
+ return UriHelper.uri_path(params['workspaceFolders'][0]['uri'])
412
+ end
413
+ return UriHelper.uri_path(params['rootUri']) if params.key?('rootUri') && !params['rootUri'].nil?
414
+
415
+ params['rootPath']
416
+ end
417
+ end
418
+
419
+ class DisabledMessageHandler < PuppetEditorServices::Handler::JsonRPC
420
+ def request_initialize(_, _json_rpc_message)
421
+ PuppetLanguageServer.log_message(:debug, 'Received initialize method')
422
+ # If the Language Server is not active then we can not respond to any capability
423
+ { 'capabilities' => PuppetLanguageServer::ServerCapabilites.no_capabilities }
424
+ end
425
+
426
+ def request_shutdown(_, _json_rpc_message)
427
+ PuppetLanguageServer.log_message(:debug, 'Received shutdown method')
428
+ nil
429
+ end
430
+
431
+ def request_puppet_getversion(_, _json_rpc_message)
432
+ # Clients may use the getVersion request to figure out when the server has "finished" loading. In this
433
+ # case just fake the response that we are fully loaded with unknown gem versions
434
+ LSP::PuppetVersion.new(
435
+ 'languageServerVersion' => PuppetEditorServices.version,
436
+ 'puppetVersion' => 'Unknown',
437
+ 'facterVersion' => 'Unknown',
438
+ 'factsLoaded' => true,
439
+ 'functionsLoaded' => true,
440
+ 'typesLoaded' => true,
441
+ 'classesLoaded' => true
442
+ )
443
+ end
444
+
445
+ def notification_initialized(_, _json_rpc_message)
446
+ PuppetLanguageServer.log_message(:info, 'Client has received initialization')
447
+
448
+ protocol.encode_and_send(
449
+ ::PuppetEditorServices::Protocol::JsonRPCMessages.new_notification(
450
+ 'window/showMessage',
451
+ 'type' => LSP::MessageType::WARNING,
452
+ 'message' => 'An error occured while starting the Language Server. The server has been disabled.'
453
+ )
454
+ )
455
+ end
456
+
457
+ def notification_exit(_, _json_rpc_message)
458
+ PuppetLanguageServer.log_message(:info, 'Received exit notification. Closing connection to client...')
459
+ protocol.close_connection
460
+ end
461
+ end
462
+ end
@@ -0,0 +1,18 @@
1
+ # frozen_string_literal: true
2
+
3
+ %w[
4
+ epp/validation_provider
5
+ manifest/completion_provider
6
+ manifest/definition_provider
7
+ manifest/document_symbol_provider
8
+ manifest/folding_provider
9
+ manifest/format_on_type_provider
10
+ manifest/signature_provider
11
+ manifest/validation_provider
12
+ manifest/hover_provider
13
+ puppetfile/validation_provider
14
+ ].each do |lib|
15
+ require "puppet-languageserver/#{lib}"
16
+ rescue LoadError
17
+ require File.expand_path(File.join(File.dirname(__FILE__), lib))
18
+ end
@@ -0,0 +1,108 @@
1
+ # frozen_string_literal: true
2
+
3
+ require 'pathname'
4
+ require 'tempfile'
5
+ require 'puppet-languageserver/session_state/object_cache'
6
+ require 'puppet-languageserver/global_queues'
7
+
8
+ module PuppetLanguageServer
9
+ module PuppetHelper
10
+ def self.module_path
11
+ return @module_path unless @module_path.nil?
12
+
13
+ # TODO: It would be nice if this wasn't using the whole puppet environment to calculate the modulepath directoties
14
+ # In the meantime memoize it. Currently you can't change the modulepath mid-process.
15
+ begin
16
+ env = Puppet.lookup(:environments).get!(Puppet.settings[:environment])
17
+ rescue Puppet::Environments::EnvironmentNotFound, StandardError
18
+ env = Puppet.lookup(:current_environment)
19
+ end
20
+ return [] if env.nil?
21
+
22
+ @module_path = env.modulepath
23
+ end
24
+
25
+ # Node Graph
26
+ def self.get_node_graph(session_state, content, local_workspace)
27
+ with_temporary_file(content) do |filepath|
28
+ ap = PuppetLanguageServer::Sidecar::Protocol::ActionParams.new
29
+ ap['source'] = filepath
30
+
31
+ args = ["--action-parameters=#{ap.to_json}"]
32
+ args << "--local-workspace=#{local_workspace}" unless local_workspace.nil?
33
+
34
+ sidecar_queue.execute('node_graph', args, false, session_state.connection_id)
35
+ end
36
+ end
37
+
38
+ def self.get_puppet_resource(session_state, typename, title, local_workspace)
39
+ ap = PuppetLanguageServer::Sidecar::Protocol::ActionParams.new
40
+ ap['typename'] = typename
41
+ ap['title'] = title unless title.nil?
42
+
43
+ args = ["--action-parameters=#{ap.to_json}"]
44
+ args << "--local-workspace=#{local_workspace}" unless local_workspace.nil?
45
+
46
+ sidecar_queue.execute('resource_list', args, false, session_state.connection_id)
47
+ end
48
+
49
+ def self.get_type(session_state, name)
50
+ session_state.object_cache.object_by_name(:type, name)
51
+ end
52
+
53
+ def self.type_names(session_state)
54
+ session_state.object_cache.object_names_by_section(:type).map(&:to_s)
55
+ end
56
+
57
+ def self.function(session_state, name, tasks_mode = false)
58
+ exclude_origins = tasks_mode ? [] : [:bolt]
59
+ session_state.object_cache.object_by_name(
60
+ :function,
61
+ name,
62
+ fuzzy_match: true,
63
+ exclude_origins: exclude_origins
64
+ )
65
+ end
66
+
67
+ def self.function_names(session_state, tasks_mode = false)
68
+ exclude_origins = tasks_mode ? [] : [:bolt]
69
+ session_state.object_cache.object_names_by_section(:function, exclude_origins: exclude_origins).map(&:to_s)
70
+ end
71
+
72
+ def self.get_class(session_state, name)
73
+ session_state.object_cache.object_by_name(:class, name)
74
+ end
75
+
76
+ def self.class_names(session_state)
77
+ session_state.object_cache.object_names_by_section(:class).map(&:to_s)
78
+ end
79
+
80
+ def self.datatype(session_state, name, tasks_mode = false)
81
+ exclude_origins = tasks_mode ? [] : [:bolt]
82
+ session_state.object_cache.object_by_name(
83
+ :datatype,
84
+ name,
85
+ fuzzy_match: true,
86
+ exclude_origins: exclude_origins
87
+ )
88
+ end
89
+
90
+ def self.sidecar_queue
91
+ PuppetLanguageServer::GlobalQueues.sidecar_queue
92
+ end
93
+
94
+ def self.with_temporary_file(content)
95
+ tempfile = Tempfile.new('langserver-sidecar')
96
+ tempfile.open
97
+
98
+ tempfile.write(content)
99
+
100
+ tempfile.close
101
+
102
+ yield tempfile.path
103
+ ensure
104
+ tempfile.delete if tempfile
105
+ end
106
+ private_class_method :with_temporary_file
107
+ end
108
+ end
@@ -0,0 +1,55 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Puppet
4
+ module Pops
5
+ module Parser
6
+ # This Lexer adds code to create comment tokens.
7
+ # The default lexer just throws them out
8
+ # Ref - https://github.com/puppetlabs/puppet-specifications/blob/master/language/lexical_structure.md#comments
9
+ class Lexer2WithComments < Puppet::Pops::Parser::Lexer2
10
+ # The PATTERN_COMMENT in lexer2 also consumes the trailing \r in the token and
11
+ # we don't want that.
12
+ PATTERN_COMMENT_NO_WS = /#[^\r\n]*/.freeze
13
+
14
+ TOKEN_COMMENT = [:COMMENT, '#', 1].freeze
15
+ TOKEN_MLCOMMENT = [:MLCOMMENT, nil, 0].freeze
16
+
17
+ def initialize
18
+ super
19
+
20
+ # Remove the selector for line comments so we can add our own
21
+ @new_selector = @selector.reject { |k, _v| k == '#' }
22
+
23
+ # Add code to scan line comments
24
+ @new_selector['#'] = lambda {
25
+ scn = @scanner
26
+ before = scn.pos
27
+ value = scn.scan(PATTERN_COMMENT_NO_WS)
28
+
29
+ if value
30
+ emit_completed([:TOKEN_COMMENT, value[1..-1].freeze, scn.pos - before], before)
31
+ else
32
+ # It's probably not possible to EVER get here ... but just incase
33
+ emit(TOKEN_COMMENT, before)
34
+ end
35
+ }.freeze
36
+
37
+ # Add code to scan multi-line comments
38
+ old_lambda = @new_selector['/']
39
+ @new_selector['/'] = lambda {
40
+ scn = @scanner
41
+ la = scn.peek(2)
42
+ if la[1] == '*'
43
+ before = scn.pos
44
+ value = scn.scan(PATTERN_MLCOMMENT)
45
+ return emit_completed([:TOKEN_MLCOMMENT, value[2..-3].freeze, scn.pos - before], before) if value
46
+ end
47
+ old_lambda.call
48
+ }.freeze
49
+ @new_selector.freeze
50
+ @selector = @new_selector
51
+ end
52
+ end
53
+ end
54
+ end
55
+ end
@@ -0,0 +1,39 @@
1
+ # frozen_string_literal: true
2
+
3
+ # Monkey Patch the Puppet language parser so we can globally lock any changes to the
4
+ # global setting Puppet[:tasks]. We need to manage this so we can switch between
5
+ # parsing modes. Unfortunately we can't do this as method parameter, only via the
6
+ # global Puppet settings which is not thread safe
7
+ $PuppetParserMutex = Mutex.new # rubocop:disable Style/GlobalVars
8
+ module Puppet
9
+ module Pops
10
+ module Parser
11
+ class Parser
12
+ def singleton_parse_string(code, task_mode = false, path = nil)
13
+ $PuppetParserMutex.synchronize do # rubocop:disable Style/GlobalVars
14
+ original_taskmode = Puppet[:tasks] if Puppet.tasks_supported?
15
+ Puppet[:tasks] = task_mode if Puppet.tasks_supported?
16
+ return parse_string(code, path)
17
+ ensure
18
+ Puppet[:tasks] = original_taskmode if Puppet.tasks_supported?
19
+ end
20
+ end
21
+ end
22
+ end
23
+ end
24
+ end
25
+
26
+ module Puppet
27
+ # Tasks first appeared in Puppet 5.4.0
28
+ def self.tasks_supported?
29
+ Gem::Version.new(Puppet.version) >= Gem::Version.new('5.4.0')
30
+ end
31
+ end
32
+
33
+ # MUST BE LAST!!!!!!
34
+ # Suppress any warning messages to STDOUT. It can pollute stdout when running in STDIO mode
35
+ Puppet::Util::Log.newdesttype :null_logger do
36
+ def handle(msg)
37
+ PuppetLanguageServer.log_message(:debug, "[PUPPET LOG] [#{msg.level}] #{msg.message}")
38
+ end
39
+ end