openvox-editor-services 3.0.0

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 (108) hide show
  1. checksums.yaml +7 -0
  2. data/CHANGELOG.md +183 -0
  3. data/Gemfile +55 -0
  4. data/LICENSE +201 -0
  5. data/README.md +305 -0
  6. data/Rakefile +260 -0
  7. data/bin/openvox-debugserver +8 -0
  8. data/bin/openvox-languageserver +8 -0
  9. data/bin/openvox-languageserver-sidecar +8 -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 +178 -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/openfact_runtime.rb +26 -0
  34. data/lib/openvox_runtime.rb +61 -0
  35. data/lib/puppet-debugserver/debug_session/break_points.rb +137 -0
  36. data/lib/puppet-debugserver/debug_session/flow_control.rb +161 -0
  37. data/lib/puppet-debugserver/debug_session/hook_handlers.rb +295 -0
  38. data/lib/puppet-debugserver/debug_session/puppet_session_run_mode.rb +66 -0
  39. data/lib/puppet-debugserver/debug_session/puppet_session_state.rb +122 -0
  40. data/lib/puppet-debugserver/hooks.rb +132 -0
  41. data/lib/puppet-debugserver/message_handler.rb +277 -0
  42. data/lib/puppet-debugserver/puppet_debug_session.rb +541 -0
  43. data/lib/puppet-debugserver/puppet_monkey_patches.rb +118 -0
  44. data/lib/puppet-languageserver/client_session_state.rb +119 -0
  45. data/lib/puppet-languageserver/crash_dump.rb +50 -0
  46. data/lib/puppet-languageserver/epp/validation_provider.rb +34 -0
  47. data/lib/puppet-languageserver/facter_helper.rb +25 -0
  48. data/lib/puppet-languageserver/global_queues/sidecar_queue.rb +205 -0
  49. data/lib/puppet-languageserver/global_queues/single_instance_queue.rb +126 -0
  50. data/lib/puppet-languageserver/global_queues/validation_queue.rb +102 -0
  51. data/lib/puppet-languageserver/global_queues.rb +16 -0
  52. data/lib/puppet-languageserver/manifest/completion_provider.rb +331 -0
  53. data/lib/puppet-languageserver/manifest/definition_provider.rb +99 -0
  54. data/lib/puppet-languageserver/manifest/document_symbol_provider.rb +228 -0
  55. data/lib/puppet-languageserver/manifest/folding_provider.rb +226 -0
  56. data/lib/puppet-languageserver/manifest/format_on_type_provider.rb +143 -0
  57. data/lib/puppet-languageserver/manifest/hover_provider.rb +221 -0
  58. data/lib/puppet-languageserver/manifest/signature_provider.rb +169 -0
  59. data/lib/puppet-languageserver/manifest/validation_provider.rb +130 -0
  60. data/lib/puppet-languageserver/message_handler.rb +470 -0
  61. data/lib/puppet-languageserver/providers.rb +18 -0
  62. data/lib/puppet-languageserver/puppet_helper.rb +108 -0
  63. data/lib/puppet-languageserver/puppet_lexer_helper.rb +55 -0
  64. data/lib/puppet-languageserver/puppet_lint.rb +9 -0
  65. data/lib/puppet-languageserver/puppet_monkey_patches.rb +39 -0
  66. data/lib/puppet-languageserver/puppet_parser_helper.rb +212 -0
  67. data/lib/puppet-languageserver/puppetfile/validation_provider.rb +185 -0
  68. data/lib/puppet-languageserver/server_capabilities.rb +48 -0
  69. data/lib/puppet-languageserver/session_state/document_store.rb +272 -0
  70. data/lib/puppet-languageserver/session_state/language_client.rb +239 -0
  71. data/lib/puppet-languageserver/session_state/object_cache.rb +162 -0
  72. data/lib/puppet-languageserver/sidecar_protocol.rb +532 -0
  73. data/lib/puppet-languageserver/uri_helper.rb +46 -0
  74. data/lib/puppet-languageserver-sidecar/cache/base.rb +36 -0
  75. data/lib/puppet-languageserver-sidecar/cache/filesystem.rb +111 -0
  76. data/lib/puppet-languageserver-sidecar/cache/null.rb +27 -0
  77. data/lib/puppet-languageserver-sidecar/facter_helper.rb +41 -0
  78. data/lib/puppet-languageserver-sidecar/puppet_environment_monkey_patches.rb +52 -0
  79. data/lib/puppet-languageserver-sidecar/puppet_helper.rb +281 -0
  80. data/lib/puppet-languageserver-sidecar/puppet_modulepath_monkey_patches.rb +146 -0
  81. data/lib/puppet-languageserver-sidecar/puppet_monkey_patches.rb +9 -0
  82. data/lib/puppet-languageserver-sidecar/puppet_parser_helper.rb +77 -0
  83. data/lib/puppet-languageserver-sidecar/puppet_strings_helper.rb +399 -0
  84. data/lib/puppet-languageserver-sidecar/puppet_strings_monkey_patches.rb +16 -0
  85. data/lib/puppet-languageserver-sidecar/sidecar_protocol_extensions.rb +16 -0
  86. data/lib/puppet-languageserver-sidecar/workspace.rb +89 -0
  87. data/lib/puppet_debugserver.rb +163 -0
  88. data/lib/puppet_editor_services/connection/base.rb +62 -0
  89. data/lib/puppet_editor_services/connection/stdio.rb +25 -0
  90. data/lib/puppet_editor_services/connection/tcp.rb +34 -0
  91. data/lib/puppet_editor_services/handler/base.rb +16 -0
  92. data/lib/puppet_editor_services/handler/debug_adapter.rb +63 -0
  93. data/lib/puppet_editor_services/handler/json_rpc.rb +133 -0
  94. data/lib/puppet_editor_services/logging.rb +45 -0
  95. data/lib/puppet_editor_services/protocol/base.rb +27 -0
  96. data/lib/puppet_editor_services/protocol/debug_adapter.rb +135 -0
  97. data/lib/puppet_editor_services/protocol/debug_adapter_messages.rb +171 -0
  98. data/lib/puppet_editor_services/protocol/json_rpc.rb +241 -0
  99. data/lib/puppet_editor_services/protocol/json_rpc_messages.rb +200 -0
  100. data/lib/puppet_editor_services/server/base.rb +42 -0
  101. data/lib/puppet_editor_services/server/stdio.rb +85 -0
  102. data/lib/puppet_editor_services/server/tcp.rb +349 -0
  103. data/lib/puppet_editor_services/server.rb +15 -0
  104. data/lib/puppet_editor_services/version.rb +36 -0
  105. data/lib/puppet_editor_services.rb +8 -0
  106. data/lib/puppet_languageserver.rb +265 -0
  107. data/lib/puppet_languageserver_sidecar.rb +364 -0
  108. metadata +256 -0
@@ -0,0 +1,470 @@
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 'openvox_runtime'
6
+ require 'openfact_runtime'
7
+ require 'puppet-languageserver/server_capabilities'
8
+ require 'puppet-languageserver/client_session_state'
9
+ require 'puppet-languageserver/global_queues'
10
+
11
+ module PuppetLanguageServer
12
+ class MessageHandler < PuppetEditorServices::Handler::JsonRPC
13
+ def initialize(*_)
14
+ super
15
+ @session_state = ClientSessionState.new(self)
16
+ end
17
+
18
+ def session_state # rubocop:disable Style/TrivialAccessors -- During the refactor, this is fine.
19
+ @session_state
20
+ end
21
+
22
+ def language_client
23
+ session_state.language_client
24
+ end
25
+
26
+ def documents
27
+ session_state.documents
28
+ end
29
+
30
+ def request_initialize(_, json_rpc_message)
31
+ PuppetLanguageServer.log_message(:debug, 'Received initialize method')
32
+
33
+ language_client.parse_lsp_initialize!(json_rpc_message.params)
34
+ static_folding_provider = !language_client.client_capability('textDocument', 'foldingRange', 'dynamicRegistration') &&
35
+ PuppetLanguageServer::ServerCapabilites.folding_provider_supported?
36
+ # Setup static registrations if dynamic registration is not available
37
+ info = {
38
+ documentOnTypeFormattingProvider: !language_client.client_capability('textDocument', 'onTypeFormatting', 'dynamicRegistration'),
39
+ foldingRangeProvider: static_folding_provider
40
+ }
41
+
42
+ # Configure the document store
43
+ documents.initialize_store(
44
+ workspace: workspace_root_from_initialize_params(json_rpc_message.params)
45
+ )
46
+
47
+ # Initiate loading the object_cache
48
+ session_state.load_default_data!
49
+ session_state.load_static_data!
50
+
51
+ # Initiate loading of the workspace if needed
52
+ session_state.load_workspace_data! if documents.store_has_module_metadata? || documents.store_has_environmentconf?
53
+
54
+ { 'capabilities' => PuppetLanguageServer::ServerCapabilites.capabilities(info) }
55
+ end
56
+
57
+ def request_shutdown(_, _json_rpc_message)
58
+ PuppetLanguageServer.log_message(:debug, 'Received shutdown method')
59
+ nil
60
+ end
61
+
62
+ def request_puppet_getversion(_, _json_rpc_message)
63
+ LSP::PuppetVersion.new(
64
+ 'languageServerVersion' => PuppetEditorServices.version,
65
+ 'runtimeName' => OpenVoxRuntime.gem_name,
66
+ 'runtimeGemVersion' => OpenVoxRuntime.gem_version,
67
+ 'puppetVersion' => Puppet.version,
68
+ 'factRuntimeName' => OpenFactRuntime.gem_name,
69
+ 'factRuntimeGemVersion' => OpenFactRuntime.gem_version,
70
+ 'facterVersion' => Facter.version,
71
+ 'factsLoaded' => session_state.facts_loaded?,
72
+ 'functionsLoaded' => session_state.default_functions_loaded?,
73
+ 'typesLoaded' => session_state.default_types_loaded?,
74
+ 'classesLoaded' => session_state.default_classes_loaded?
75
+ )
76
+ end
77
+
78
+ def request_puppet_getfacts(_, _json_rpc_message)
79
+ results = PuppetLanguageServer::FacterHelper.facts_to_hash(session_state)
80
+ LSP::PuppetFactResponse.new('facts' => results)
81
+ end
82
+
83
+ def request_puppet_getresource(_, json_rpc_message)
84
+ type_name = json_rpc_message.params['typename']
85
+ title = json_rpc_message.params['title']
86
+ return LSP::PuppetResourceResponse.new('error' => 'Missing Typename') if type_name.nil?
87
+
88
+ resource_list = PuppetLanguageServer::PuppetHelper.get_puppet_resource(session_state, type_name, title, documents.store_root_path)
89
+ return LSP::PuppetResourceResponse.new('data' => '') if resource_list.nil? || resource_list.length.zero?
90
+
91
+ content = "#{resource_list.map(&:manifest).join("\n\n")}\n"
92
+ LSP::PuppetResourceResponse.new('data' => content)
93
+ end
94
+
95
+ def request_puppet_compilenodegraph(_, json_rpc_message)
96
+ file_uri = json_rpc_message.params['external']
97
+ 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
98
+
99
+ document = documents.document(file_uri)
100
+
101
+ begin
102
+ node_graph = PuppetLanguageServer::PuppetHelper.get_node_graph(session_state, document.content, documents.store_root_path)
103
+ LSP::PuppetNodeGraphResponse.new('vertices' => node_graph.vertices,
104
+ 'edges' => node_graph.edges,
105
+ 'error' => node_graph.error_content)
106
+ rescue StandardError => e
107
+ PuppetLanguageServer.log_message(:error, "(puppet/compileNodeGraph) Error generating node graph. #{e}")
108
+ LSP::PuppetNodeGraphResponse.new('error' => 'An internal error occured while generating the the node graph. Please see the debug log files for more information.')
109
+ end
110
+ end
111
+
112
+ def request_puppetfile_getdependencies(_, json_rpc_message)
113
+ file_uri = json_rpc_message.params['uri']
114
+ return LSP::PuppetfileDependencyResponse.new('error' => 'Must be a puppetfile in order to find dependencies.') unless documents.document_type(file_uri) == :puppetfile
115
+
116
+ document = documents.document(file_uri)
117
+
118
+ result = []
119
+ begin
120
+ result = PuppetLanguageServer::Puppetfile::ValidationProvider.find_dependencies(document.content)
121
+ rescue StandardError => e
122
+ PuppetLanguageServer.log_message(:error, "(puppetfile/getdependencies) Error parsing puppetfile. #{e}")
123
+ return LSP::PuppetfileDependencyResponse.new('error' => 'An internal error occured while parsing the puppetfile. Please see the debug log files for more information.')
124
+ end
125
+
126
+ LSP::PuppetfileDependencyResponse.new('dependencies' => result)
127
+ end
128
+
129
+ def request_puppet_fixdiagnosticerrors(_, json_rpc_message)
130
+ formatted_request = LSP::PuppetFixDiagnosticErrorsRequest.new(json_rpc_message.params)
131
+ file_uri = formatted_request.documentUri
132
+ content = documents.document_content(file_uri)
133
+
134
+ case documents.document_type(file_uri)
135
+ when :manifest
136
+ changes, new_content = PuppetLanguageServer::Manifest::ValidationProvider.fix_validate_errors(session_state, content)
137
+ else
138
+ raise "Unable to fixDiagnosticErrors on #{file_uri}"
139
+ end
140
+
141
+ LSP::PuppetFixDiagnosticErrorsResponse.new(
142
+ 'documentUri' => formatted_request.documentUri,
143
+ 'fixesApplied' => changes,
144
+ 'newContent' => changes > 0 || formatted_request.alwaysReturnContent ? new_content : nil
145
+ )
146
+ rescue StandardError => e
147
+ PuppetLanguageServer.log_message(:error, "(puppet/fixDiagnosticErrors) #{e}")
148
+ unless formatted_request.nil?
149
+ LSP::PuppetFixDiagnosticErrorsResponse.new(
150
+ 'documentUri' => formatted_request.documentUri,
151
+ 'fixesApplied' => 0,
152
+ 'newContent' => formatted_request.alwaysReturnContent ? content : nil
153
+ )
154
+ end
155
+ end
156
+
157
+ def request_textdocument_completion(_, json_rpc_message)
158
+ file_uri = json_rpc_message.params['textDocument']['uri']
159
+ line_num = json_rpc_message.params['position']['line']
160
+ char_num = json_rpc_message.params['position']['character']
161
+ content = documents.document_content(file_uri)
162
+ context = json_rpc_message.params['context'].nil? ? nil : LSP::CompletionContext.new(json_rpc_message.params['context'])
163
+
164
+ case documents.document_type(file_uri)
165
+ when :manifest
166
+ PuppetLanguageServer::Manifest::CompletionProvider.complete(session_state, content, line_num, char_num, context:, tasks_mode: documents.plan_file?(file_uri))
167
+ else
168
+ raise "Unable to provide completion on #{file_uri}"
169
+ end
170
+ rescue StandardError => e
171
+ PuppetLanguageServer.log_message(:error, "(textDocument/completion) #{e}")
172
+ LSP::CompletionList.new('isIncomplete' => false, 'items' => [])
173
+ end
174
+
175
+ def request_textdocument_foldingrange(_, json_rpc_message)
176
+ return nil unless language_client.folding_range
177
+
178
+ file_uri = json_rpc_message.params['textDocument']['uri']
179
+ case documents.document_type(file_uri)
180
+ when :manifest
181
+ PuppetLanguageServer::Manifest::FoldingProvider.instance.folding_ranges(documents.document_tokens(file_uri))
182
+ else
183
+ raise "Unable to provide folding ranages on #{file_uri}"
184
+ end
185
+ rescue StandardError => e
186
+ PuppetLanguageServer.log_message(:error, "(textDocument/foldingRange) #{e}")
187
+ nil
188
+ end
189
+
190
+ def request_completionitem_resolve(_, json_rpc_message)
191
+ PuppetLanguageServer::Manifest::CompletionProvider.resolve(session_state, LSP::CompletionItem.new(json_rpc_message.params))
192
+ rescue StandardError => e
193
+ PuppetLanguageServer.log_message(:error, "(completionItem/resolve) #{e}")
194
+ # Spit back the same params if an error happens
195
+ json_rpc_message.params
196
+ end
197
+
198
+ def request_textdocument_hover(_, json_rpc_message)
199
+ file_uri = json_rpc_message.params['textDocument']['uri']
200
+ line_num = json_rpc_message.params['position']['line']
201
+ char_num = json_rpc_message.params['position']['character']
202
+ content = documents.document_content(file_uri)
203
+ case documents.document_type(file_uri)
204
+ when :manifest
205
+ PuppetLanguageServer::Manifest::HoverProvider.resolve(session_state, content, line_num, char_num, tasks_mode: documents.plan_file?(file_uri))
206
+ else
207
+ raise "Unable to provide hover on #{file_uri}"
208
+ end
209
+ rescue StandardError => e
210
+ PuppetLanguageServer.log_message(:error, "(textDocument/hover) #{e}")
211
+ nil
212
+ end
213
+
214
+ def request_textdocument_definition(_, json_rpc_message)
215
+ file_uri = json_rpc_message.params['textDocument']['uri']
216
+ line_num = json_rpc_message.params['position']['line']
217
+ char_num = json_rpc_message.params['position']['character']
218
+ content = documents.document_content(file_uri)
219
+
220
+ case documents.document_type(file_uri)
221
+ when :manifest
222
+ PuppetLanguageServer::Manifest::DefinitionProvider.find_definition(session_state, content, line_num, char_num, tasks_mode: documents.plan_file?(file_uri))
223
+ else
224
+ raise "Unable to provide definition on #{file_uri}"
225
+ end
226
+ rescue StandardError => e
227
+ PuppetLanguageServer.log_message(:error, "(textDocument/definition) #{e}")
228
+ nil
229
+ end
230
+
231
+ def request_textdocument_documentsymbol(_, json_rpc_message)
232
+ file_uri = json_rpc_message.params['textDocument']['uri']
233
+ content = documents.document_content(file_uri)
234
+
235
+ case documents.document_type(file_uri)
236
+ when :manifest
237
+ PuppetLanguageServer::Manifest::DocumentSymbolProvider.extract_document_symbols(content, tasks_mode: documents.plan_file?(file_uri))
238
+ else
239
+ raise "Unable to provide definition on #{file_uri}"
240
+ end
241
+ rescue StandardError => e
242
+ PuppetLanguageServer.log_message(:error, "(textDocument/documentSymbol) #{e}")
243
+ nil
244
+ end
245
+
246
+ def request_textdocument_ontypeformatting(_, json_rpc_message)
247
+ return nil unless language_client.format_on_type
248
+
249
+ file_uri = json_rpc_message.params['textDocument']['uri']
250
+ line_num = json_rpc_message.params['position']['line']
251
+ char_num = json_rpc_message.params['position']['character']
252
+ content = documents.document_content(file_uri)
253
+
254
+ case documents.document_type(file_uri)
255
+ when :manifest
256
+ PuppetLanguageServer::Manifest::FormatOnTypeProvider.instance.format(
257
+ content,
258
+ line_num,
259
+ char_num,
260
+ json_rpc_message.params['ch'],
261
+ json_rpc_message.params['options'],
262
+ language_client.format_on_type_filesize_limit
263
+ )
264
+ else
265
+ raise "Unable to format on type on #{file_uri}"
266
+ end
267
+ rescue StandardError => e
268
+ PuppetLanguageServer.log_message(:error, "(textDocument/onTypeFormatting) #{e}")
269
+ nil
270
+ end
271
+
272
+ def request_textdocument_signaturehelp(_, json_rpc_message)
273
+ file_uri = json_rpc_message.params['textDocument']['uri']
274
+ line_num = json_rpc_message.params['position']['line']
275
+ char_num = json_rpc_message.params['position']['character']
276
+ content = documents.document_content(file_uri)
277
+
278
+ case documents.document_type(file_uri)
279
+ when :manifest
280
+ PuppetLanguageServer::Manifest::SignatureProvider.signature_help(
281
+ session_state,
282
+ content,
283
+ line_num,
284
+ char_num,
285
+ tasks_mode: documents.plan_file?(file_uri)
286
+ )
287
+ else
288
+ raise "Unable to provide signatures on #{file_uri}"
289
+ end
290
+ rescue StandardError => e
291
+ PuppetLanguageServer.log_message(:error, "(textDocument/signatureHelp) #{e}")
292
+ nil
293
+ end
294
+
295
+ def request_workspace_symbol(_, json_rpc_message)
296
+ result = []
297
+ result.concat(PuppetLanguageServer::Manifest::DocumentSymbolProvider.workspace_symbols(json_rpc_message.params['query'], session_state.object_cache))
298
+ result
299
+ rescue StandardError => e
300
+ PuppetLanguageServer.log_message(:error, "(workspace/symbol) #{e}")
301
+ []
302
+ end
303
+
304
+ def notification_initialized(_, _json_rpc_message)
305
+ PuppetLanguageServer.log_message(:info, 'Client has received initialization')
306
+ # Raise a warning if the OpenVox version is mismatched
307
+ server_options = protocol.connection.server.server_options
308
+ unless server_options[:puppet_version].nil? || server_options[:puppet_version] == Puppet.version
309
+ protocol.encode_and_send(
310
+ ::PuppetEditorServices::Protocol::JsonRPCMessages.new_notification(
311
+ 'window/showMessage',
312
+ 'type' => LSP::MessageType::WARNING,
313
+ 'message' => "Unable to use OpenVox version '#{server_options[:puppet_version]}' as it is not available. Using version '#{Puppet.version}' instead."
314
+ )
315
+ )
316
+ end
317
+
318
+ # Register for workspace setting changes if it's supported
319
+ if language_client.client_capability('workspace', 'didChangeConfiguration', 'dynamicRegistration') == true
320
+ language_client.register_capability('workspace/didChangeConfiguration')
321
+ else
322
+ PuppetLanguageServer.log_message(:debug, 'Client does not support didChangeConfiguration dynamic registration. Using push method for configuration change detection.')
323
+ end
324
+ end
325
+
326
+ def notification_exit(_, _json_rpc_message)
327
+ PuppetLanguageServer.log_message(:info, 'Received exit notification. Closing connection to client...')
328
+ protocol.connection.close
329
+ end
330
+
331
+ def notification_textdocument_didopen(client_handler_id, json_rpc_message)
332
+ PuppetLanguageServer.log_message(:info, 'Received textDocument/didOpen notification.')
333
+ file_uri = json_rpc_message.params['textDocument']['uri']
334
+ content = json_rpc_message.params['textDocument']['text']
335
+ doc_version = json_rpc_message.params['textDocument']['version']
336
+ documents.set_document(file_uri, content, doc_version)
337
+ enqueue_validation(file_uri, doc_version, client_handler_id)
338
+ end
339
+
340
+ def notification_textdocument_didclose(client_handler_id, json_rpc_message)
341
+ PuppetLanguageServer.log_message(:info, 'Received textDocument/didClose notification.')
342
+ file_uri = json_rpc_message.params['textDocument']['uri']
343
+ documents.remove_document(file_uri)
344
+ enqueue_validation(file_uri, nil, client_handler_id)
345
+ end
346
+
347
+ def notification_textdocument_didchange(client_handler_id, json_rpc_message)
348
+ PuppetLanguageServer.log_message(:info, 'Received textDocument/didChange notification.')
349
+ file_uri = json_rpc_message.params['textDocument']['uri']
350
+ content = json_rpc_message.params['contentChanges'][0]['text'] # TODO: Bad hardcoding zero
351
+ doc_version = json_rpc_message.params['textDocument']['version']
352
+ documents.set_document(file_uri, content, doc_version)
353
+ enqueue_validation(file_uri, doc_version, client_handler_id)
354
+ end
355
+
356
+ def notification_textdocument_didsave(_, _json_rpc_message)
357
+ PuppetLanguageServer.log_message(:info, 'Received textDocument/didSave notification.')
358
+ # Expire the store cache so that the store information can re-evaluated
359
+ documents.expire_store_information
360
+ if documents.store_has_module_metadata? || documents.store_has_environmentconf?
361
+ # Load the workspace information
362
+ session_state.load_workspace_data!
363
+ else
364
+ # Purge the workspace information
365
+ session_state.purge_workspace_data!
366
+ end
367
+ end
368
+
369
+ def notification_workspace_didchangeconfiguration(_, json_rpc_message)
370
+ if json_rpc_message.params.key?('settings') && json_rpc_message.params['settings'].nil?
371
+ # This is a notification from a dynamic registration. Need to send a workspace/configuration
372
+ # request to get the actual configuration
373
+ language_client.send_configuration_request
374
+ else
375
+ language_client.parse_lsp_configuration_settings!(json_rpc_message.params['settings'])
376
+ end
377
+ end
378
+
379
+ def response_client_registercapability(_, json_rpc_message, original_request)
380
+ language_client.parse_register_capability_response!(json_rpc_message, original_request)
381
+ end
382
+
383
+ def response_client_unregistercapability(_, json_rpc_message, original_request)
384
+ language_client.parse_unregister_capability_response!(json_rpc_message, original_request)
385
+ end
386
+
387
+ def response_workspace_configuration(_, json_rpc_message, original_request)
388
+ return unless json_rpc_message.is_successful
389
+
390
+ original_request.params.items.each_with_index do |item, index|
391
+ # The response from the client strips the section name so we need to re-add it
392
+ language_client.parse_lsp_configuration_settings!(item.section => json_rpc_message.result[index])
393
+ end
394
+ end
395
+
396
+ def unhandled_exception(error, options)
397
+ super
398
+ PuppetLanguageServer::CrashDump.write_crash_file(error, session_state, nil, options)
399
+ end
400
+
401
+ private
402
+
403
+ def enqueue_validation(file_uri, doc_version, client_handler_id, options = {})
404
+ if documents.document_type(file_uri) == :puppetfile
405
+ options[:resolve_puppetfile] = language_client.use_puppetfile_resolver
406
+ options[:puppet_version] = Puppet.version
407
+ options[:module_path] = PuppetLanguageServer::PuppetHelper.module_path
408
+ end
409
+ GlobalQueues.validate_queue.enqueue(file_uri, doc_version, client_handler_id, options)
410
+ end
411
+
412
+ def workspace_root_from_initialize_params(params)
413
+ if params.key?('workspaceFolders')
414
+ return nil if params['workspaceFolders'].nil? || params['workspaceFolders'].empty?
415
+
416
+ # We don't support multiple workspace folders yet, so just select the first one
417
+ return UriHelper.uri_path(params['workspaceFolders'][0]['uri'])
418
+ end
419
+ return UriHelper.uri_path(params['rootUri']) if params.key?('rootUri') && !params['rootUri'].nil?
420
+
421
+ params['rootPath']
422
+ end
423
+ end
424
+
425
+ class DisabledMessageHandler < PuppetEditorServices::Handler::JsonRPC
426
+ def request_initialize(_, _json_rpc_message)
427
+ PuppetLanguageServer.log_message(:debug, 'Received initialize method')
428
+ # If the Language Server is not active then we can not respond to any capability
429
+ { 'capabilities' => PuppetLanguageServer::ServerCapabilites.no_capabilities }
430
+ end
431
+
432
+ def request_shutdown(_, _json_rpc_message)
433
+ PuppetLanguageServer.log_message(:debug, 'Received shutdown method')
434
+ nil
435
+ end
436
+
437
+ def request_puppet_getversion(_, _json_rpc_message)
438
+ # Clients may use the getVersion request to figure out when the server has "finished" loading. In this
439
+ # case just fake the response that we are fully loaded with unknown gem versions
440
+ LSP::PuppetVersion.new(
441
+ 'languageServerVersion' => PuppetEditorServices.version,
442
+ 'runtimeName' => 'openvox',
443
+ 'runtimeGemVersion' => 'Unknown',
444
+ 'puppetVersion' => 'Unknown',
445
+ 'facterVersion' => 'Unknown',
446
+ 'factsLoaded' => true,
447
+ 'functionsLoaded' => true,
448
+ 'typesLoaded' => true,
449
+ 'classesLoaded' => true
450
+ )
451
+ end
452
+
453
+ def notification_initialized(_, _json_rpc_message)
454
+ PuppetLanguageServer.log_message(:info, 'Client has received initialization')
455
+
456
+ protocol.encode_and_send(
457
+ ::PuppetEditorServices::Protocol::JsonRPCMessages.new_notification(
458
+ 'window/showMessage',
459
+ 'type' => LSP::MessageType::WARNING,
460
+ 'message' => 'An error occured while starting the Language Server. The server has been disabled.'
461
+ )
462
+ )
463
+ end
464
+
465
+ def notification_exit(_, _json_rpc_message)
466
+ PuppetLanguageServer.log_message(:info, 'Received exit notification. Closing connection to client...')
467
+ protocol.close_connection
468
+ end
469
+ end
470
+ 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:
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:).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:
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]*/
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,9 @@
1
+ # frozen_string_literal: true
2
+
3
+ require 'puppet-lint'
4
+
5
+ # puppet-lint discovers installed plugin gems through RubyGems metadata. The
6
+ # packaged language server uses vendored source trees instead, so load their
7
+ # checks explicitly.
8
+ vendored_plugins = File.expand_path('../../vendor/puppet-lint-*/lib/puppet-lint/plugins/**/*.rb', __dir__)
9
+ Dir[vendored_plugins].sort.each { |plugin| require plugin }