openvox-editor-services 4.0.0 → 4.0.1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- checksums.yaml +4 -4
- data/CHANGELOG.md +10 -2
- data/README.md +8 -0
- data/lib/puppet-languageserver/global_queues/sidecar_queue.rb +24 -3
- data/lib/puppet-languageserver/manifest/completion_provider.rb +50 -37
- data/lib/puppet-languageserver/manifest/completion_provider_attributes.rb +211 -0
- data/lib/puppet-languageserver/message_handler.rb +1 -1
- data/lib/puppet-languageserver-sidecar/puppet_helper.rb +10 -6
- data/lib/puppet-languageserver-sidecar/puppet_strings_helper.rb +10 -0
- data/lib/puppet-languageserver-sidecar/puppet_strings_monkey_patches.rb +1 -1
- data/lib/puppet_editor_services/version.rb +1 -1
- metadata +2 -1
checksums.yaml
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
---
|
|
2
2
|
SHA256:
|
|
3
|
-
metadata.gz:
|
|
4
|
-
data.tar.gz:
|
|
3
|
+
metadata.gz: 2482da72d66357f9710a3705a119f913b8b1f589cd8c1308fdf4267380516c14
|
|
4
|
+
data.tar.gz: 9166657dc9d5c62fd15ce34b97a89ab81db74cce077789c9d4801da61cfb7514
|
|
5
5
|
SHA512:
|
|
6
|
-
metadata.gz:
|
|
7
|
-
data.tar.gz:
|
|
6
|
+
metadata.gz: 07a236de9536aa66d4311bb1632f0686a79bca0e0a2ab167c61ddeee1f0beafab2de63efc3d710b7023c45322f74bfea39b99bd1b1def1b9f15b34e1f9f3dee2
|
|
7
|
+
data.tar.gz: 1c3138263fc890e6c0aacce1c7806a538c73ab3fbd4d488d80ed07f8d21c6ddcd780cd01da4c12049698d502def58660df19be9fa66cbce148a70e288663edce
|
data/CHANGELOG.md
CHANGED
|
@@ -1,8 +1,16 @@
|
|
|
1
1
|
# Changelog
|
|
2
2
|
|
|
3
|
-
## [4.0.
|
|
3
|
+
## [4.0.1](https://github.com/voxpupuli/openvox-editor-services/tree/4.0.1) (2026-11-09)
|
|
4
4
|
|
|
5
|
-
[Full Changelog](https://github.com/voxpupuli/openvox-editor-services/compare/
|
|
5
|
+
[Full Changelog](https://github.com/voxpupuli/openvox-editor-services/compare/v4.0.0...4.0.1)
|
|
6
|
+
|
|
7
|
+
**Fixed bugs:**
|
|
8
|
+
|
|
9
|
+
- fix: repair/extend attributes completion [\#88](https://github.com/voxpupuli/openvox-editor-services/pull/88) ([jay7x](https://github.com/jay7x))
|
|
10
|
+
|
|
11
|
+
## [v4.0.0](https://github.com/voxpupuli/openvox-editor-services/tree/v4.0.0) (2026-11-06)
|
|
12
|
+
|
|
13
|
+
[Full Changelog](https://github.com/voxpupuli/openvox-editor-services/compare/v3.1.2...v4.0.0)
|
|
6
14
|
|
|
7
15
|
**Breaking changes:**
|
|
8
16
|
|
data/README.md
CHANGED
|
@@ -11,6 +11,7 @@ The internal `Puppet` Ruby namespace and `puppet/*` protocol methods are retaine
|
|
|
11
11
|
- [How to run the Language Server for Development](#how-to-run-the-language-server-for-development)
|
|
12
12
|
- [How to run the Language Server in Production](#how-to-run-the-language-server-in-production)
|
|
13
13
|
- [Command line arguments](#command-line-arguments)
|
|
14
|
+
- [Resource attribute completion](#resource-attribute-completion)
|
|
14
15
|
- [Language Server Sidecar](#language-server-sidecar)
|
|
15
16
|
- [How to run the Language Server Sidecar for Development](#how-to-run-the-language-server-sidecar-for-development)
|
|
16
17
|
- [Example usage](#example-usage)
|
|
@@ -146,6 +147,13 @@ For the complete option list, run:
|
|
|
146
147
|
bundle exec ruby ./openvox-languageserver --help
|
|
147
148
|
```
|
|
148
149
|
|
|
150
|
+
### Resource attribute completion
|
|
151
|
+
|
|
152
|
+
Resource attribute completion aligns hashrockets to the longest attribute name in the resource.
|
|
153
|
+
The alignment padding is capped at 999 characters (`MAX_ATTRIBUTE_PADDING`) because OpenVox/Puppet does not limit identifier length.
|
|
154
|
+
This bounds memory and CPU use for pathological manifests and for client-supplied completion data.
|
|
155
|
+
Attributes longer than the cap are inserted without extra alignment padding.
|
|
156
|
+
|
|
149
157
|
## Language Server Sidecar
|
|
150
158
|
|
|
151
159
|
### How to run the Language Server Sidecar for Development
|
|
@@ -3,6 +3,7 @@
|
|
|
3
3
|
require 'puppet-languageserver/global_queues/single_instance_queue'
|
|
4
4
|
require 'puppet_editor_services/server'
|
|
5
5
|
require 'open3'
|
|
6
|
+
require 'rbconfig'
|
|
6
7
|
|
|
7
8
|
module PuppetLanguageServer
|
|
8
9
|
module GlobalQueues
|
|
@@ -37,7 +38,10 @@ module PuppetLanguageServer
|
|
|
37
38
|
|
|
38
39
|
sidecar_path = File.expand_path(File.join(__dir__, '..', '..', '..', 'openvox-languageserver-sidecar'))
|
|
39
40
|
args = ['--action', job_object.action].concat(job_object.additional_args).concat(sidecar_args_from_connection(connection))
|
|
40
|
-
|
|
41
|
+
# Use the same Ruby interpreter as the language server. Relying on a bare
|
|
42
|
+
# 'ruby' from PATH can pick up a different runtime which cannot see the
|
|
43
|
+
# Puppet agent's gems and types.
|
|
44
|
+
cmd = [RbConfig.ruby, sidecar_path].concat(args)
|
|
41
45
|
PuppetLanguageServer.log_message(:debug, "SidecarQueue Thread: Running sidecar #{cmd}")
|
|
42
46
|
stdout, stderr, status = run_sidecar(cmd)
|
|
43
47
|
PuppetLanguageServer.log_message(:warning, "SidecarQueue Thread: Calling sidecar with #{args.join(' ')} returned exitcode #{status.exitstatus}, #{stderr}")
|
|
@@ -162,8 +166,25 @@ module PuppetLanguageServer
|
|
|
162
166
|
handler.respond_to?(:session_state) ? handler.session_state : nil
|
|
163
167
|
end
|
|
164
168
|
|
|
169
|
+
# The language server removes GEM_HOME/GEM_PATH from its own environment
|
|
170
|
+
# while activating the OpenVox agent runtime. The sidecar is a child
|
|
171
|
+
# process and still needs the bundled extension gems (openvox-strings,
|
|
172
|
+
# yard, etc.), so restore them for the sidecar invocation.
|
|
173
|
+
def sidecar_gem_environment
|
|
174
|
+
gem_home = File.expand_path(File.join(__dir__, '..', '..', '..', 'gems'))
|
|
175
|
+
return {} unless Dir.exist?(gem_home)
|
|
176
|
+
|
|
177
|
+
# Preserve any GEM_PATH entries inherited from the environment (for
|
|
178
|
+
# example a user's own gems) instead of replacing them. Keep the
|
|
179
|
+
# bundled gems first so they win when the same gem is available twice.
|
|
180
|
+
existing_paths = ENV['GEM_PATH'].to_s.split(File::PATH_SEPARATOR).reject(&:empty?)
|
|
181
|
+
gem_path = ([gem_home] + existing_paths).uniq.join(File::PATH_SEPARATOR)
|
|
182
|
+
|
|
183
|
+
{ 'GEM_HOME' => gem_home, 'GEM_PATH' => gem_path }
|
|
184
|
+
end
|
|
185
|
+
|
|
165
186
|
def run_sidecar(cmd)
|
|
166
|
-
Open3.capture3(*cmd)
|
|
187
|
+
Open3.capture3(sidecar_gem_environment, *cmd)
|
|
167
188
|
|
|
168
189
|
# The following code should ONLY be uncommented when debugging acceptance tests failures.
|
|
169
190
|
# It caches the sidecar responses so tests sidecar calls will be (VERY) quick, which speeds
|
|
@@ -182,7 +203,7 @@ module PuppetLanguageServer
|
|
|
182
203
|
# ]
|
|
183
204
|
# end
|
|
184
205
|
# # Rubocop failure is here on purpose
|
|
185
|
-
# stdout,stderr,status = Open3.capture3(*cmd)
|
|
206
|
+
# stdout,stderr,status = Open3.capture3(sidecar_gem_environment, *cmd)
|
|
186
207
|
# File.write(cache_file, stdout, mode: 'wb', encoding: 'utf-8')
|
|
187
208
|
# [stdout, stderr, status]
|
|
188
209
|
end
|
|
@@ -1,12 +1,17 @@
|
|
|
1
1
|
# frozen_string_literal: true
|
|
2
2
|
|
|
3
|
+
require 'puppet-languageserver/manifest/completion_provider_attributes'
|
|
4
|
+
|
|
3
5
|
module PuppetLanguageServer
|
|
4
6
|
module Manifest
|
|
5
7
|
module CompletionProvider
|
|
8
|
+
extend CompletionProviderAttributes
|
|
9
|
+
|
|
6
10
|
def self.complete(session_state, content, line_num, char_num, options = {})
|
|
7
11
|
options = {
|
|
8
12
|
tasks_mode: false,
|
|
9
|
-
context: nil # LSP::CompletionContext object
|
|
13
|
+
context: nil, # LSP::CompletionContext object
|
|
14
|
+
file_uri: nil
|
|
10
15
|
}.merge(options)
|
|
11
16
|
items = []
|
|
12
17
|
incomplete = false
|
|
@@ -62,14 +67,33 @@ module PuppetLanguageServer
|
|
|
62
67
|
all_functions(session_state, options[:tasks_mode]) { |x| items << x }
|
|
63
68
|
|
|
64
69
|
when 'Puppet::Pops::Model::ResourceExpression'
|
|
65
|
-
# We are inside a resource definition.
|
|
70
|
+
# We are inside a resource definition. Should display all available
|
|
66
71
|
# properities and parameters.
|
|
67
72
|
|
|
73
|
+
# The attribute list is computed for the current prefix, and the
|
|
74
|
+
# client must ask again as the user keeps typing. Otherwise it reuses
|
|
75
|
+
# completion items whose document version and replacement range have
|
|
76
|
+
# gone stale.
|
|
77
|
+
incomplete = true
|
|
78
|
+
|
|
68
79
|
# Try Types first
|
|
69
80
|
# The `class` pseudo resource type is actually used to set properties/params for the puppet type
|
|
70
81
|
# specified in the resource title.
|
|
71
82
|
# Ref: https://puppet.com/docs/puppet/5.3/lang_classes.html#using-resource-like-declarations
|
|
72
83
|
item_value = item.type_name.value == 'class' && item.bodies.length == 1 ? item.bodies[0].title.value : item.type_name.value
|
|
84
|
+
|
|
85
|
+
# Align hashrockets with the longest existing attribute name. A zero
|
|
86
|
+
# padding means there are no existing attributes. A value above the
|
|
87
|
+
# cap is pathological and disables alignment entirely.
|
|
88
|
+
operations = item.bodies.flat_map { |body| body.operations || [] }
|
|
89
|
+
file_uri = options[:file_uri]
|
|
90
|
+
document_version = file_uri.nil? ? nil : session_state.documents.document_version(file_uri)
|
|
91
|
+
resource_data = attribute_resource_data(
|
|
92
|
+
content, line_num, char_num,
|
|
93
|
+
{ file_uri: file_uri, document_version: document_version, tasks_mode: options[:tasks_mode] },
|
|
94
|
+
item_value, operations
|
|
95
|
+
)
|
|
96
|
+
|
|
73
97
|
item_object = PuppetLanguageServer::PuppetHelper.get_type(session_state, item_value)
|
|
74
98
|
unless item_object.nil?
|
|
75
99
|
# Add Parameters
|
|
@@ -78,11 +102,7 @@ module PuppetLanguageServer
|
|
|
78
102
|
'label' => name.to_s,
|
|
79
103
|
'kind' => LSP::CompletionItemKind::PROPERTY,
|
|
80
104
|
'detail' => 'Parameter',
|
|
81
|
-
'data' =>
|
|
82
|
-
'type' => 'resource_parameter',
|
|
83
|
-
'param' => name.to_s,
|
|
84
|
-
'resource_type' => item_value
|
|
85
|
-
}
|
|
105
|
+
'data' => attribute_data(resource_data, 'resource_parameter', 'param', name)
|
|
86
106
|
)
|
|
87
107
|
end
|
|
88
108
|
# Add Properties
|
|
@@ -91,15 +111,12 @@ module PuppetLanguageServer
|
|
|
91
111
|
'label' => name.to_s,
|
|
92
112
|
'kind' => LSP::CompletionItemKind::PROPERTY,
|
|
93
113
|
'detail' => 'Property',
|
|
94
|
-
'data' =>
|
|
95
|
-
'type' => 'resource_property',
|
|
96
|
-
'prop' => name.to_s,
|
|
97
|
-
'resource_type' => item_value
|
|
98
|
-
}
|
|
114
|
+
'data' => attribute_data(resource_data, 'resource_property', 'prop', name)
|
|
99
115
|
)
|
|
100
116
|
end
|
|
101
117
|
# TODO: What about meta parameters?
|
|
102
118
|
end
|
|
119
|
+
|
|
103
120
|
if item_object.nil?
|
|
104
121
|
# Try Classes/Defined Types
|
|
105
122
|
item_object = PuppetLanguageServer::PuppetHelper.get_class(session_state, item_value)
|
|
@@ -110,11 +127,7 @@ module PuppetLanguageServer
|
|
|
110
127
|
'label' => name.to_s,
|
|
111
128
|
'kind' => LSP::CompletionItemKind::PROPERTY,
|
|
112
129
|
'detail' => 'Parameter',
|
|
113
|
-
'data' =>
|
|
114
|
-
'type' => 'resource_class_parameter',
|
|
115
|
-
'param' => name.to_s,
|
|
116
|
-
'resource_type' => item_value
|
|
117
|
-
}
|
|
130
|
+
'data' => attribute_data(resource_data, 'resource_class_parameter', 'param', name)
|
|
118
131
|
)
|
|
119
132
|
end
|
|
120
133
|
end
|
|
@@ -128,6 +141,7 @@ module PuppetLanguageServer
|
|
|
128
141
|
end
|
|
129
142
|
|
|
130
143
|
# BEGIN CompletionItem Helpers
|
|
144
|
+
|
|
131
145
|
def self.keywords(keywords = [], &block)
|
|
132
146
|
keywords.each do |keyword|
|
|
133
147
|
item = LSP::CompletionItem.new(
|
|
@@ -247,7 +261,7 @@ module PuppetLanguageServer
|
|
|
247
261
|
return result if item_type.nil?
|
|
248
262
|
|
|
249
263
|
result.documentation = item_type.doc unless item_type.doc.nil?
|
|
250
|
-
unless item_type.
|
|
264
|
+
unless item_type.signatures.none?
|
|
251
265
|
result.detail = item_type.signatures.map(&:key).join("\n\n")
|
|
252
266
|
# The signature provider should handle suggestions after this, so just place the cursor ready for an opening bracket
|
|
253
267
|
result.insertText = data['name'].to_s
|
|
@@ -282,48 +296,47 @@ module PuppetLanguageServer
|
|
|
282
296
|
|
|
283
297
|
result.documentation = item_type.doc unless item_type.doc.nil?
|
|
284
298
|
result.insertText = snippet
|
|
285
|
-
result.insertTextFormat = LSP::InsertTextFormat::SNIPPET
|
|
286
299
|
when 'resource_parameter'
|
|
287
300
|
item_type = PuppetLanguageServer::PuppetHelper.get_type(session_state, data['resource_type'])
|
|
288
301
|
return result if item_type.nil?
|
|
289
302
|
|
|
290
303
|
param_type = item_type.attributes[data['param'].intern]
|
|
291
|
-
|
|
292
|
-
|
|
293
|
-
|
|
294
|
-
|
|
295
|
-
|
|
304
|
+
return result if param_type.nil?
|
|
305
|
+
|
|
306
|
+
# TODO: More things?
|
|
307
|
+
result.documentation = param_type[:doc] unless param_type[:doc].nil?
|
|
308
|
+
attribute_insert_text(session_state, result, data, data['param'])
|
|
296
309
|
when 'resource_property'
|
|
297
310
|
item_type = PuppetLanguageServer::PuppetHelper.get_type(session_state, data['resource_type'])
|
|
298
311
|
return result if item_type.nil?
|
|
299
312
|
|
|
300
313
|
prop_type = item_type.attributes[data['prop'].intern]
|
|
301
|
-
|
|
302
|
-
|
|
303
|
-
|
|
304
|
-
|
|
305
|
-
|
|
314
|
+
return result if prop_type.nil?
|
|
315
|
+
|
|
316
|
+
# TODO: More things?
|
|
317
|
+
result.documentation = prop_type[:doc] unless prop_type[:doc].nil?
|
|
318
|
+
attribute_insert_text(session_state, result, data, data['prop'])
|
|
306
319
|
|
|
307
320
|
when 'resource_class'
|
|
308
321
|
item_class = PuppetLanguageServer::PuppetHelper.get_class(session_state, data['name'])
|
|
309
322
|
return result if item_class.nil?
|
|
310
323
|
|
|
311
324
|
result.insertText = "#{data['name']} { '${1:title}':\n\t$0\n}"
|
|
312
|
-
result.insertTextFormat = LSP::InsertTextFormat::SNIPPET
|
|
313
325
|
when 'resource_class_parameter'
|
|
314
326
|
item_class = PuppetLanguageServer::PuppetHelper.get_class(session_state, data['resource_type'])
|
|
315
327
|
return result if item_class.nil?
|
|
316
328
|
|
|
317
329
|
param_type = item_class.parameters[data['param']]
|
|
318
|
-
|
|
319
|
-
|
|
320
|
-
|
|
321
|
-
|
|
322
|
-
|
|
323
|
-
|
|
324
|
-
|
|
330
|
+
return result if param_type.nil?
|
|
331
|
+
|
|
332
|
+
doc = ''
|
|
333
|
+
doc += param_type[:type] unless param_type[:type].nil?
|
|
334
|
+
doc += "---\n#{param_type[:doc]}" unless param_type[:doc].nil?
|
|
335
|
+
result.documentation = doc
|
|
336
|
+
attribute_insert_text(session_state, result, data, data['param'])
|
|
325
337
|
end
|
|
326
338
|
|
|
339
|
+
result.insertTextFormat = LSP::InsertTextFormat::SNIPPET if data['type'].to_s.start_with?('resource_')
|
|
327
340
|
result
|
|
328
341
|
end
|
|
329
342
|
end
|
|
@@ -0,0 +1,211 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
module PuppetLanguageServer
|
|
4
|
+
module Manifest
|
|
5
|
+
# Helpers for completing resource attributes.
|
|
6
|
+
module CompletionProviderAttributes
|
|
7
|
+
# OpenVox/Puppet does not limit attribute name length. The longest
|
|
8
|
+
# built-in attribute is 23 characters (selinux_ignore_defaults), and the
|
|
9
|
+
# style guide recommends lines no longer than 140 characters. This cap is
|
|
10
|
+
# purely defensive: it bounds alignment padding for pathological manifests
|
|
11
|
+
# and for client-supplied completion data.
|
|
12
|
+
MAX_ATTRIBUTE_PADDING = 999
|
|
13
|
+
|
|
14
|
+
def attribute_data(resource_data, type, name_key, name)
|
|
15
|
+
name_length = name.to_s.length
|
|
16
|
+
padding = [resource_data['padding'].to_i, name_length].max
|
|
17
|
+
alignment = resource_data['alignment'] == true && padding.positive? && padding <= MAX_ATTRIBUTE_PADDING
|
|
18
|
+
padding = name_length if padding > MAX_ATTRIBUTE_PADDING
|
|
19
|
+
|
|
20
|
+
resource_data.merge(
|
|
21
|
+
'type' => type,
|
|
22
|
+
name_key => name.to_s,
|
|
23
|
+
'padding' => padding,
|
|
24
|
+
'alignment' => alignment
|
|
25
|
+
)
|
|
26
|
+
end
|
|
27
|
+
|
|
28
|
+
# Build the shared data payload for every completion item generated from
|
|
29
|
+
# one resource expression.
|
|
30
|
+
def attribute_resource_data(content, line_num, char_num, options, resource_type, operations)
|
|
31
|
+
slice = content.split("\n", -1)[line_num].to_s[0, char_num].to_s
|
|
32
|
+
indent = slice[/\A[ \t]*/] || ''
|
|
33
|
+
# An unanchored search can retry at every offset, so use rindex instead
|
|
34
|
+
# of a trailing-anchored regex on long runs of attribute-name characters.
|
|
35
|
+
boundary = slice.rindex(/[^\w-]/)
|
|
36
|
+
prefix = boundary.nil? ? slice : slice[(boundary + 1)..].to_s
|
|
37
|
+
|
|
38
|
+
padding = operations.map { |operation| operation.attribute_name.to_s.length }.max || 0
|
|
39
|
+
alignment = padding.positive? && padding <= MAX_ATTRIBUTE_PADDING
|
|
40
|
+
|
|
41
|
+
{
|
|
42
|
+
'resource_type' => resource_type,
|
|
43
|
+
'padding' => alignment ? padding : 0,
|
|
44
|
+
'alignment' => alignment,
|
|
45
|
+
'replace_range' => LSP.create_range(line_num, char_num - prefix.length, line_num, char_num),
|
|
46
|
+
'uri' => options[:file_uri],
|
|
47
|
+
'document_version' => options[:document_version],
|
|
48
|
+
'cursor_line' => line_num,
|
|
49
|
+
'cursor_char' => char_num,
|
|
50
|
+
'indent_width' => indent.length,
|
|
51
|
+
'tasks_mode' => options[:tasks_mode] == true
|
|
52
|
+
}
|
|
53
|
+
end
|
|
54
|
+
|
|
55
|
+
# Build hashrocket alignment edits for resource attributes.
|
|
56
|
+
#
|
|
57
|
+
# operations - Array of Puppet::Pops::Model::AttributeOperation
|
|
58
|
+
# cursor_line - 0-based line number of the completion, which is skipped
|
|
59
|
+
# indent_width - number of leading spaces on the completion line
|
|
60
|
+
# width - desired attribute-name column width
|
|
61
|
+
#
|
|
62
|
+
# Returns an Array of LSP::TextEdit. Returns an empty Array when
|
|
63
|
+
# alignment is not possible or not safe.
|
|
64
|
+
def alignment_edits_from_operations(operations, cursor_line, indent_width, width)
|
|
65
|
+
return [] if width.nil? || width <= 0 || width > MAX_ATTRIBUTE_PADDING
|
|
66
|
+
return [] if operations.nil? || operations.empty?
|
|
67
|
+
|
|
68
|
+
infos = []
|
|
69
|
+
operations.each do |operation|
|
|
70
|
+
name = operation.attribute_name.to_s
|
|
71
|
+
next if name.empty?
|
|
72
|
+
return [] if name.length > MAX_ATTRIBUTE_PADDING
|
|
73
|
+
|
|
74
|
+
locator = operation.respond_to?(:locator) ? operation.locator : nil
|
|
75
|
+
next if locator.nil?
|
|
76
|
+
|
|
77
|
+
source = locator.string
|
|
78
|
+
next if source.nil?
|
|
79
|
+
|
|
80
|
+
name_start = operation.offset
|
|
81
|
+
name_end = name_start + name.length
|
|
82
|
+
value_expr = operation.value_expr
|
|
83
|
+
next if value_expr.nil? || !value_expr.respond_to?(:offset)
|
|
84
|
+
|
|
85
|
+
gap = source[name_end...value_expr.offset]
|
|
86
|
+
next if gap.nil?
|
|
87
|
+
|
|
88
|
+
arrow_offset = gap.index('=>')
|
|
89
|
+
next if arrow_offset.nil?
|
|
90
|
+
|
|
91
|
+
arrow_start = name_end + arrow_offset
|
|
92
|
+
|
|
93
|
+
infos << {
|
|
94
|
+
line: locator.line_for_offset(name_start) - 1,
|
|
95
|
+
name_end_col: locator.pos_on_line(name_start) - 1 + name.length,
|
|
96
|
+
arrow_col: locator.pos_on_line(arrow_start) - 1
|
|
97
|
+
}
|
|
98
|
+
end
|
|
99
|
+
|
|
100
|
+
return [] if infos.empty?
|
|
101
|
+
|
|
102
|
+
# Match format-on-type: if two hashrockets share a line we cannot
|
|
103
|
+
# align the body safely.
|
|
104
|
+
lines = infos.map { |info| info[:line] }
|
|
105
|
+
return [] if lines.uniq.length != lines.length
|
|
106
|
+
|
|
107
|
+
target_col = infos.map { |info| info[:name_end_col] }.max + 1
|
|
108
|
+
target_col = [target_col, indent_width.to_i + width + 1].max
|
|
109
|
+
|
|
110
|
+
infos.each_with_object([]) do |info, edits|
|
|
111
|
+
# The completion snippet handles the line being completed.
|
|
112
|
+
next if info[:line] == cursor_line
|
|
113
|
+
|
|
114
|
+
whitespace = target_col - info[:name_end_col]
|
|
115
|
+
next if info[:arrow_col] - info[:name_end_col] == whitespace
|
|
116
|
+
|
|
117
|
+
edits << LSP::TextEdit.new.from_h!(
|
|
118
|
+
'newText' => ' ' * whitespace,
|
|
119
|
+
'range' => LSP.create_range(info[:line], info[:name_end_col], info[:line], info[:arrow_col])
|
|
120
|
+
)
|
|
121
|
+
end
|
|
122
|
+
end
|
|
123
|
+
|
|
124
|
+
def attribute_insert_text(session_state, item, data, name)
|
|
125
|
+
raw_padding = data['padding'].to_i
|
|
126
|
+
padding = if raw_padding.positive? && raw_padding <= MAX_ATTRIBUTE_PADDING
|
|
127
|
+
[raw_padding, name.to_s.length].max
|
|
128
|
+
else
|
|
129
|
+
name.to_s.length
|
|
130
|
+
end
|
|
131
|
+
|
|
132
|
+
content, char_num, replace_range = attribute_current_context(session_state, data)
|
|
133
|
+
item.insertText = "#{name.to_s.ljust(padding)} => ${1},$0"
|
|
134
|
+
item.textEdit = LSP::TextEdit.new.from_h!('newText' => item.insertText, 'range' => replace_range)
|
|
135
|
+
item.additionalTextEdits = nil
|
|
136
|
+
|
|
137
|
+
return if content.nil?
|
|
138
|
+
|
|
139
|
+
edits = attribute_alignment_edits(content, data, char_num, padding)
|
|
140
|
+
item.additionalTextEdits = edits unless edits.empty?
|
|
141
|
+
end
|
|
142
|
+
|
|
143
|
+
private
|
|
144
|
+
|
|
145
|
+
# Completion lists are reused while the user keeps typing, so the
|
|
146
|
+
# document version stored in the item data can lag behind the open
|
|
147
|
+
# document. When that happens, fall back to the current document and
|
|
148
|
+
# parse at the end of the completion line.
|
|
149
|
+
#
|
|
150
|
+
# Returns [content, cursor_char, replace_range].
|
|
151
|
+
def attribute_current_context(session_state, data)
|
|
152
|
+
uri = data['uri']
|
|
153
|
+
return [nil, nil, data['replace_range']] if uri.nil?
|
|
154
|
+
|
|
155
|
+
line_num = data['cursor_line'].to_i
|
|
156
|
+
content = session_state.documents.document_content(uri, data['document_version'])
|
|
157
|
+
return [content, data['cursor_char'].to_i, data['replace_range']] unless content.nil?
|
|
158
|
+
|
|
159
|
+
content = session_state.documents.document_content(uri)
|
|
160
|
+
return [nil, nil, data['replace_range']] if content.nil?
|
|
161
|
+
|
|
162
|
+
line_text = content.split("\n", -1)[line_num].to_s.rstrip
|
|
163
|
+
prefix = line_text[/[A-Za-z0-9_-]+\z/]
|
|
164
|
+
replace_range = if prefix.nil?
|
|
165
|
+
data['replace_range']
|
|
166
|
+
else
|
|
167
|
+
LSP.create_range(line_num, line_text.length - prefix.length, line_num, line_text.length)
|
|
168
|
+
end
|
|
169
|
+
[content, line_text.length, replace_range]
|
|
170
|
+
end
|
|
171
|
+
|
|
172
|
+
def attribute_alignment_edits(content, data, char_num, padding)
|
|
173
|
+
return [] unless data['alignment'] == true
|
|
174
|
+
|
|
175
|
+
raw_padding = data['padding'].to_i
|
|
176
|
+
return [] unless raw_padding.positive? && raw_padding <= MAX_ATTRIBUTE_PADDING
|
|
177
|
+
|
|
178
|
+
operations = attribute_operations(
|
|
179
|
+
content,
|
|
180
|
+
data['cursor_line'].to_i,
|
|
181
|
+
char_num.to_i,
|
|
182
|
+
data['tasks_mode'] == true
|
|
183
|
+
)
|
|
184
|
+
alignment_edits_from_operations(
|
|
185
|
+
operations,
|
|
186
|
+
data['cursor_line'].to_i,
|
|
187
|
+
data['indent_width'].to_i,
|
|
188
|
+
padding
|
|
189
|
+
)
|
|
190
|
+
rescue StandardError
|
|
191
|
+
[]
|
|
192
|
+
end
|
|
193
|
+
|
|
194
|
+
def attribute_operations(content, line_num, char_num, tasks_mode)
|
|
195
|
+
result = PuppetLanguageServer::PuppetParserHelper.object_under_cursor(
|
|
196
|
+
content, line_num, char_num,
|
|
197
|
+
multiple_attempts: true,
|
|
198
|
+
disallowed_classes: [Puppet::Pops::Model::QualifiedName, Puppet::Pops::Model::BlockExpression],
|
|
199
|
+
tasks_mode: tasks_mode,
|
|
200
|
+
remove_trigger_char: false
|
|
201
|
+
)
|
|
202
|
+
return [] if result.nil?
|
|
203
|
+
|
|
204
|
+
model = result[:model]
|
|
205
|
+
return [] unless model.instance_of?(::Puppet::Pops::Model::ResourceExpression)
|
|
206
|
+
|
|
207
|
+
model.bodies.flat_map { |body| body.operations || [] }
|
|
208
|
+
end
|
|
209
|
+
end
|
|
210
|
+
end
|
|
211
|
+
end
|
|
@@ -163,7 +163,7 @@ module PuppetLanguageServer
|
|
|
163
163
|
|
|
164
164
|
case documents.document_type(file_uri)
|
|
165
165
|
when :manifest
|
|
166
|
-
PuppetLanguageServer::Manifest::CompletionProvider.complete(session_state, content, line_num, char_num, context
|
|
166
|
+
PuppetLanguageServer::Manifest::CompletionProvider.complete(session_state, content, line_num, char_num, context: context, tasks_mode: documents.plan_file?(file_uri), file_uri: file_uri)
|
|
167
167
|
else
|
|
168
168
|
raise "Unable to provide completion on #{file_uri}"
|
|
169
169
|
end
|
|
@@ -262,16 +262,20 @@ module PuppetLanguageServerSidecar
|
|
|
262
262
|
{ relative_dir: 'manifests', glob: '/**/*.pp' } # Pretty much everything in most modules
|
|
263
263
|
],
|
|
264
264
|
datatype: [
|
|
265
|
-
{ relative_dir: 'lib/puppet/datatypes',
|
|
266
|
-
{ relative_dir: '
|
|
265
|
+
{ relative_dir: 'lib/puppet/datatypes', glob: '/**/*.rb' }, # Custom Data Types
|
|
266
|
+
{ relative_dir: 'vendor_ruby/puppet/datatypes', glob: '/**/*.rb' }, # As above, in a Puppet agent layout
|
|
267
|
+
{ relative_dir: 'types', glob: '/**/*.pp' } # Data Type aliases
|
|
267
268
|
],
|
|
268
269
|
function: [
|
|
269
|
-
{ relative_dir: 'functions',
|
|
270
|
-
{ relative_dir: 'lib/puppet/functions',
|
|
271
|
-
{ relative_dir: '
|
|
270
|
+
{ relative_dir: 'functions', glob: '/**/*.pp' }, # Contains custom functions written in the Puppet language.
|
|
271
|
+
{ relative_dir: 'lib/puppet/functions', glob: '/**/*.rb' }, # Contains functions written in Ruby for the modern Puppet::Functions API
|
|
272
|
+
{ relative_dir: 'vendor_ruby/puppet/functions', glob: '/**/*.rb' }, # As above, in a Puppet agent layout
|
|
273
|
+
{ relative_dir: 'lib/puppet/parser/functions', glob: '/**/*.rb' }, # Contains functions written in Ruby for the legacy Puppet::Parser::Functions API
|
|
274
|
+
{ relative_dir: 'vendor_ruby/puppet/parser/functions', glob: '/**/*.rb' } # As above, in a Puppet agent layout
|
|
272
275
|
],
|
|
273
276
|
type: [
|
|
274
|
-
{ relative_dir: 'lib/puppet/type',
|
|
277
|
+
{ relative_dir: 'lib/puppet/type', glob: '/{,file/}*.rb' }, # Contains Puppet resource types. Resource types like `file` can live in subdirs, hence the glob
|
|
278
|
+
{ relative_dir: 'vendor_ruby/puppet/type', glob: '/{,file/}*.rb' } # As above, in a Puppet agent layout
|
|
275
279
|
]
|
|
276
280
|
}
|
|
277
281
|
end
|
|
@@ -294,6 +294,16 @@ module PuppetLanguageServerSidecar
|
|
|
294
294
|
}
|
|
295
295
|
end
|
|
296
296
|
end
|
|
297
|
+
# Checks are parameters too. Types such as exec use `newcheck` for
|
|
298
|
+
# attributes like `creates`, `unless`, `onlyif` and `refreshonly`.
|
|
299
|
+
unless item[:checks].nil?
|
|
300
|
+
item[:checks].each do |check|
|
|
301
|
+
obj.attributes[check[:name]] = {
|
|
302
|
+
type: :param,
|
|
303
|
+
doc: check[:description]
|
|
304
|
+
}
|
|
305
|
+
end
|
|
306
|
+
end
|
|
297
307
|
|
|
298
308
|
if obj.key == 'file'
|
|
299
309
|
# Special case for file type
|
metadata
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
--- !ruby/object:Gem::Specification
|
|
2
2
|
name: openvox-editor-services
|
|
3
3
|
version: !ruby/object:Gem::Version
|
|
4
|
-
version: 4.0.
|
|
4
|
+
version: 4.0.1
|
|
5
5
|
platform: ruby
|
|
6
6
|
authors:
|
|
7
7
|
- OpenVox Project contributors
|
|
@@ -192,6 +192,7 @@ files:
|
|
|
192
192
|
- lib/puppet-languageserver/global_queues/single_instance_queue.rb
|
|
193
193
|
- lib/puppet-languageserver/global_queues/validation_queue.rb
|
|
194
194
|
- lib/puppet-languageserver/manifest/completion_provider.rb
|
|
195
|
+
- lib/puppet-languageserver/manifest/completion_provider_attributes.rb
|
|
195
196
|
- lib/puppet-languageserver/manifest/definition_provider.rb
|
|
196
197
|
- lib/puppet-languageserver/manifest/document_formatting_provider.rb
|
|
197
198
|
- lib/puppet-languageserver/manifest/document_symbol_provider.rb
|