Module: PuppetLanguageServer::Manifest::CompletionProvider

Defined in:
lib/puppet-languageserver/manifest/completion_provider.rb

Class Method Summary collapse

Class Method Details

.all_facts(session_state, &block) ⇒ Object



146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
# File 'lib/puppet-languageserver/manifest/completion_provider.rb', line 146

def self.all_facts(session_state, &block)
  PuppetLanguageServer::FacterHelper.fact_names(session_state).each do |name|
    item = LSP::CompletionItem.new(
      'label' => name.to_s,
      'insertText' => "'#{name}'",
      'kind' => LSP::CompletionItemKind::VARIABLE,
      'detail' => 'Fact',
      'data' => {
        'type' => 'variable_expr_fact',
        'expr' => name
      }
    )
    yield(item) if block
  end
end

.all_functions(session_state, tasks_mode, &block) ⇒ Object



187
188
189
190
191
192
193
194
195
196
197
198
199
200
# File 'lib/puppet-languageserver/manifest/completion_provider.rb', line 187

def self.all_functions(session_state, tasks_mode, &block)
  PuppetLanguageServer::PuppetHelper.function_names(session_state, tasks_mode).each do |name|
    item = LSP::CompletionItem.new(
      'label' => name.to_s,
      'kind' => LSP::CompletionItemKind::FUNCTION,
      'detail' => 'Function',
      'data' => {
        'type' => 'function',
        'name' => name.to_s
      }
    )
    yield(item) if block
  end
end

.all_resources(session_state, &block) ⇒ Object



162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
# File 'lib/puppet-languageserver/manifest/completion_provider.rb', line 162

def self.all_resources(session_state, &block)
  # Find Puppet Types
  PuppetLanguageServer::PuppetHelper.type_names(session_state).each do |pup_type|
    item = LSP::CompletionItem.new(
      'label' => pup_type,
      'kind' => LSP::CompletionItemKind::MODULE,
      'detail' => 'Resource',
      'data' => {
        'type' => 'resource_type',
        'name' => pup_type
      }
    )
    yield(item) if block
  end
  # Find Puppet Classes/Defined Types
  PuppetLanguageServer::PuppetHelper.class_names(session_state).each do |pup_class|
    item = LSP::CompletionItem.new('label' => pup_class,
                                   'kind' => LSP::CompletionItemKind::MODULE,
                                   'detail' => 'Resource',
                                   'data' => { 'type' => 'resource_class',
                                               'name' => pup_class })
    yield(item) if block
  end
end

.complete(session_state, content, line_num, char_num, options = {}) ⇒ Object



6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
# File 'lib/puppet-languageserver/manifest/completion_provider.rb', line 6

def self.complete(session_state, content, line_num, char_num, options = {})
  options = {
    tasks_mode: false,
    context: nil # LSP::CompletionContext object
  }.merge(options)
  items = []
  incomplete = false
  is_trigger_char = !options[:context].nil? && options[:context].triggerKind == LSP::CompletionTriggerKind::TRIGGERCHARACTER

  result = PuppetLanguageServer::PuppetParserHelper.object_under_cursor(content, line_num, char_num,
                                                                        multiple_attempts: true,
                                                                        disallowed_classes: [Puppet::Pops::Model::QualifiedName, Puppet::Pops::Model::BlockExpression],
                                                                        tasks_mode: options[:tasks_mode],
                                                                        remove_trigger_char: is_trigger_char)
  if result.nil?
    # We are in the root of the document.

    # Add keywords
    keywords(%w[class define node application site]) { |x| items << x }
    keywords(%w[plan]) { |x| items << x } if options[:tasks_mode]

    # Add resources
    all_resources(session_state) { |x| items << x }

    all_functions(session_state, options[:tasks_mode]) { |x| items << x }

    response = LSP::CompletionList.new
    response.items = items
    response.isIncomplete = incomplete
    return response
  end

  item = result[:model]

  case item.class.to_s
  when 'Puppet::Pops::Model::VariableExpression'
    expr = item.expr.value

    # Complete for `$facts[...`
    all_facts(session_state) { |x| items << x } if expr == 'facts'

  when 'Puppet::Pops::Model::HostClassDefinition', 'Puppet::Pops::Model::ResourceTypeDefinition'
    # We are in the root of a `class` or `define` statement

    # Add keywords
    keywords(%w[require contain]) { |x| items << x }

    # Add resources
    all_resources(session_state) { |x| items << x }

  when 'Puppet::Pops::Model::PlanDefinition'
    # We are in the root of a `plan` statement

    # Add resources
    all_resources(session_state) { |x| items << x }

    all_functions(session_state, options[:tasks_mode]) { |x| items << x }

  when 'Puppet::Pops::Model::ResourceExpression'
    # We are inside a resource definition.  Should display all available
    # properities and parameters.

    # Try Types first
    # The `class` pseudo resource type is actually used to set properties/params for the puppet type
    # specified in the resource title.
    # Ref: https://puppet.com/docs/puppet/5.3/lang_classes.html#using-resource-like-declarations
    item_value = item.type_name.value == 'class' && item.bodies.length == 1 ? item.bodies[0].title.value : item.type_name.value
    item_object = PuppetLanguageServer::PuppetHelper.get_type(session_state, item_value)
    unless item_object.nil?
      # Add Parameters
      item_object.attributes.select { |_name, data| data[:type] == :param }.each_key do |name|
        items << LSP::CompletionItem.new(
          'label' => name.to_s,
          'kind' => LSP::CompletionItemKind::PROPERTY,
          'detail' => 'Parameter',
          'data' => {
            'type' => 'resource_parameter',
            'param' => name.to_s,
            'resource_type' => item_value
          }
        )
      end
      # Add Properties
      item_object.attributes.select { |_name, data| data[:type] == :property }.each_key do |name|
        items << LSP::CompletionItem.new(
          'label' => name.to_s,
          'kind' => LSP::CompletionItemKind::PROPERTY,
          'detail' => 'Property',
          'data' => {
            'type' => 'resource_property',
            'prop' => name.to_s,
            'resource_type' => item_value
          }
        )
      end
      # TODO: What about meta parameters?
    end
    if item_object.nil?
      # Try Classes/Defined Types
      item_object = PuppetLanguageServer::PuppetHelper.get_class(session_state, item_value)
      unless item_object.nil?
        # Add Parameters
        item_object.parameters.each_key do |name|
          items << LSP::CompletionItem.new(
            'label' => name.to_s,
            'kind' => LSP::CompletionItemKind::PROPERTY,
            'detail' => 'Parameter',
            'data' => {
              'type' => 'resource_class_parameter',
              'param' => name.to_s,
              'resource_type' => item_value
            }
          )
        end
      end
    end
  end

  response = LSP::CompletionList.new
  response.items = items
  response.isIncomplete = incomplete
  response
end

.keywords(keywords = [], &block) ⇒ Object

BEGIN CompletionItem Helpers



131
132
133
134
135
136
137
138
139
140
141
142
143
144
# File 'lib/puppet-languageserver/manifest/completion_provider.rb', line 131

def self.keywords(keywords = [], &block)
  keywords.each do |keyword|
    item = LSP::CompletionItem.new(
      'label' => keyword,
      'kind' => LSP::CompletionItemKind::KEYWORD,
      'detail' => 'Keyword',
      'data' => {
        'type' => 'keyword',
        'name' => keyword
      }
    )
    yield(item) if block
  end
end

.resolve(session_state, completion_item) ⇒ Object

completion_item is an instance of LSP::CompletionItem



204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
# File 'lib/puppet-languageserver/manifest/completion_provider.rb', line 204

def self.resolve(session_state, completion_item)
  result = completion_item.clone
  data = result.data
  case data['type']
  when 'variable_expr_fact'
    value = PuppetLanguageServer::FacterHelper.fact_value(session_state, data['expr'])
    # TODO: More things?
    result.documentation = value.to_s

  when 'keyword'
    case data['name']
    when 'class'
      result.documentation = 'Classes are named blocks of Puppet code that are stored in modules for later use and ' \
                             'are not applied until they are invoked by name. They can be added to a node’s catalog ' \
                             'by either declaring them in your manifests or assigning them from an ENC.'
      result.insertText = "# Class: $1\n#\n#\nclass ${1:name} {\n\t${2:# resources}\n}$0"
      result.insertTextFormat = LSP::InsertTextFormat::SNIPPET
    when 'define'
      result.documentation = 'Defined resource types (also called defined types or defines) are blocks of Puppet code ' \
                             'that can be evaluated multiple times with different parameters. Once defined, they act ' \
                             'like a new resource type: you can cause the block to be evaluated by declaring a resource ' \
                             'of that new resource type.'
      result.insertText = "define ${1:name} () {\n\t${2:# resources}\n}$0"
      result.insertTextFormat = LSP::InsertTextFormat::SNIPPET
    when 'application'
      result.detail = 'Orchestrator'
      result.documentation = 'Application definitions are a lot like a defined resource type except that instead of defining ' \
                             'a chunk of reusable configuration that applies to a single node, the application definition ' \
                             'operates at a higher level. The components you declare inside an application can be individually ' \
                             'assigned to separate nodes you manage with Puppet.'
      result.insertText = "application ${1:name} () {\n\t${2:# resources}\n}$0"
      result.insertTextFormat = LSP::InsertTextFormat::SNIPPET
    when 'site'
      result.detail = 'Orchestrator'
      result.documentation = 'Within the site block, applications are declared like defined types. They can be declared any ' \
                             'number of times, but their type and title combination must be unique within an environment.'
      result.insertText = "site ${1:name} () {\n\t${2:# applications}\n}$0"
      result.insertTextFormat = LSP::InsertTextFormat::SNIPPET
    end

  when 'function'
    # We don't know if this resolution is coming from a plan or not, so just assume it is
    item_type = PuppetLanguageServer::PuppetHelper.function(session_state, data['name'], true)
    return result if item_type.nil?

    result.documentation = item_type.doc unless item_type.doc.nil?
    unless item_type.nil? || item_type.signatures.count.zero?
      result.detail = item_type.signatures.map(&:key).join("\n\n")
      # The signature provider should handle suggestions after this, so just place the cursor ready for an opening bracket
      result.insertText = data['name'].to_s
      result.insertTextFormat = LSP::InsertTextFormat::PLAINTEXT
    end

  when 'resource_type'
    item_type = PuppetLanguageServer::PuppetHelper.get_type(session_state, data['name'])
    return result if item_type.nil?

    attr_names = []
    # Add required attributes.  Ignore namevars as they come from the resource title
    item_type.attributes.each { |name, item| attr_names.push(name.to_s) if item[:required?] && item[:isnamevar?] != true }
    # Remove the'ensure' param/property for now, and we'll re-add later
    attr_names.reject! { |item| item == 'ensure' }
    # The param/property list should initially sorted alphabetically
    attr_names.sort!
    # Add the 'ensure' param/property at the top if the resource supports it
    attr_names.insert(0, 'ensure') unless item_type.attributes.keys.find_index(:ensure).nil?
    # Get the longest string length for later hash-rocket padding
    max_length = -1
    attr_names.each { |name| max_length = name.length if name.length > max_length }

    # Generate the text snippet
    snippet = "#{data['name']} { '${1:title}':\n"
    attr_names.each_index do |index|
      name = attr_names[index]
      value_text = (name == 'ensure') ? 'present' : 'value' # rubocop:disable Style/TernaryParentheses  In this case it's easier to read.
      snippet += "\t#{name.ljust(max_length, ' ')} => '${#{index + 2}:#{value_text}}'\n"
    end
    snippet += '}'

    result.documentation = item_type.doc unless item_type.doc.nil?
    result.insertText = snippet
    result.insertTextFormat = LSP::InsertTextFormat::SNIPPET
  when 'resource_parameter'
    item_type = PuppetLanguageServer::PuppetHelper.get_type(session_state, data['resource_type'])
    return result if item_type.nil?

    param_type = item_type.attributes[data['param'].intern]
    unless param_type.nil?
      # TODO: More things?
      result.documentation = param_type[:doc] unless param_type[:doc].nil?
      result.insertText = "#{data['param']} => ,"
    end
  when 'resource_property'
    item_type = PuppetLanguageServer::PuppetHelper.get_type(session_state, data['resource_type'])
    return result if item_type.nil?

    prop_type = item_type.attributes[data['prop'].intern]
    unless prop_type.nil?
      # TODO: More things?
      result.documentation = prop_type[:doc] unless prop_type[:doc].nil?
      result.insertText = "#{data['prop']} => ,"
    end

  when 'resource_class'
    item_class = PuppetLanguageServer::PuppetHelper.get_class(session_state, data['name'])
    return result if item_class.nil?

    result.insertText = "#{data['name']} { '${1:title}':\n\t$0\n}"
    result.insertTextFormat = LSP::InsertTextFormat::SNIPPET
  when 'resource_class_parameter'
    item_class = PuppetLanguageServer::PuppetHelper.get_class(session_state, data['resource_type'])
    return result if item_class.nil?

    param_type = item_class.parameters[data['param']]
    unless param_type.nil?
      doc = ''
      doc += param_type[:type] unless param_type[:type].nil?
      doc += "---\n#{param_type[:doc]}" unless param_type[:doc].nil?
      result.documentation = doc
      result.insertText = "#{data['param']} => ,"
    end
  end

  result
end