Skip to content

Commit 009e5bf

Browse files
authored
Merge pull request #193 from glennsarti/add-find-definition
(GH-166) Add find/peek definition capability to language server
2 parents 817fb03 + 2aea8d8 commit 009e5bf

File tree

20 files changed

+598
-11
lines changed

20 files changed

+598
-11
lines changed

client/README.md

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -33,6 +33,7 @@ Create or open any Puppet manifest with the extension `.pp` or `.epp` and the ex
3333
- Code snippets
3434
- Linting
3535
- IntelliSense for resources, parameters and more
36+
- Go to Definition of functions, types and classes
3637
- Validation of `metadata.json` files
3738
- Import from `puppet resource` directly into manifests
3839
- Node graph preview

client/src/connection.ts

Lines changed: 10 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -281,23 +281,28 @@ export class ConnectionManager implements IConnectionManager {
281281
// After 30 seonds timeout the progress
282282
if (count >= 30 || connectionManager.languageClient == undefined) {
283283
clearInterval(handle);
284-
connectionManager.setConnectionStatus(lastVersionResponse.puppetVersion, ConnectionStatus.Running);
284+
this.setConnectionStatus(lastVersionResponse.puppetVersion, ConnectionStatus.Running);
285285
resolve();
286+
return;
286287
}
287288

288289
connectionManager.languageClient.sendRequest(messages.PuppetVersionRequest.type).then((versionDetails) => {
289290
lastVersionResponse = versionDetails
290-
if (versionDetails.factsLoaded && versionDetails.functionsLoaded && versionDetails.typesLoaded) {
291+
if (!connectionManager.connectionConfiguration.preLoadPuppet || (versionDetails.factsLoaded &&
292+
versionDetails.functionsLoaded &&
293+
versionDetails.typesLoaded &&
294+
versionDetails.classesLoaded)) {
291295
clearInterval(handle);
292-
connectionManager.setConnectionStatus(lastVersionResponse.puppetVersion, ConnectionStatus.Running);
296+
this.setConnectionStatus(lastVersionResponse.puppetVersion, ConnectionStatus.Running);
293297
resolve();
294298
} else {
295299
let progress = 0;
296300

297301
if (versionDetails.factsLoaded) { progress++; }
298302
if (versionDetails.functionsLoaded) { progress++; }
299303
if (versionDetails.typesLoaded) { progress++; }
300-
progress = Math.round(progress / 3.0 * 100);
304+
if (versionDetails.classesLoaded) { progress++; }
305+
progress = Math.round(progress / 4.0 * 100);
301306

302307
this.setConnectionStatus("Loading Puppet (" + progress.toString() + "%)", ConnectionStatus.Starting);
303308
}
@@ -354,6 +359,7 @@ export class ConnectionManager implements IConnectionManager {
354359
}
355360

356361
private setConnectionStatus(statusText: string, status: ConnectionStatus): void {
362+
console.log(statusText)
357363
// Set color and icon for 'Running' by default
358364
var statusIconText = "$(terminal) ";
359365
var statusColor = "#affc74";

client/src/messages.ts

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -11,6 +11,7 @@ export interface PuppetVersionDetails {
1111
factsLoaded: boolean;
1212
functionsLoaded: boolean;
1313
typesLoaded: boolean;
14+
classesLoaded: boolean;
1415
}
1516

1617
export interface PuppetResourceRequestParams {

server/lib/languageserver/languageserver.rb

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,4 @@
1-
%w[constants diagnostic completion_list completion_item hover puppet_version puppet_compilation].each do |lib|
1+
%w[constants diagnostic completion_list completion_item hover location puppet_version puppet_compilation].each do |lib|
22
begin
33
require "languageserver/#{lib}"
44
rescue LoadError
Lines changed: 30 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,30 @@
1+
module LanguageServer
2+
# /**
3+
# * The result of a hover request.
4+
# */
5+
# export interface Location {
6+
# uri: string;
7+
# range: Range;
8+
# }
9+
10+
module Location
11+
def self.create(options)
12+
result = {}
13+
raise('uri is a required field for Location') if options['uri'].nil?
14+
15+
result['uri'] = options['uri']
16+
result['range'] = {
17+
'start' => {
18+
'line' => options['fromline'],
19+
'character' => options['fromchar']
20+
},
21+
'end' => {
22+
'line' => options['toline'],
23+
'character' => options['tochar']
24+
}
25+
}
26+
27+
result
28+
end
29+
end
30+
end

server/lib/languageserver/puppet_version.rb

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -7,6 +7,7 @@ module PuppetVersion
77
# factsLoaded: boolean;
88
# functionsLoaded: boolean;
99
# typesLoaded: boolean;
10+
# classesLoaded: boolean;
1011
# }
1112

1213
def self.create(options)
@@ -20,7 +21,8 @@ def self.create(options)
2021
result['factsLoaded'] = options['factsLoaded'] unless options['factsLoaded'].nil?
2122
result['functionsLoaded'] = options['functionsLoaded'] unless options['functionsLoaded'].nil?
2223
result['typesLoaded'] = options['typesLoaded'] unless options['typesLoaded'].nil?
23-
24+
result['classesLoaded'] = options['classesLoaded'] unless options['classesLoaded'].nil?
25+
2426
result['languageServerVersion'] = PuppetVSCode.version
2527

2628
result

server/lib/puppet-languageserver.rb

Lines changed: 5 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -5,8 +5,8 @@
55
require 'languageserver/languageserver'
66
require 'puppet-vscode'
77

8-
%w[json_rpc_handler message_router server_capabilities document_validator
9-
puppet_parser_helper puppet_helper facter_helper completion_provider hover_provider].each do |lib|
8+
%w[json_rpc_handler message_router server_capabilities document_validator puppet_parser_helper puppet_helper
9+
facter_helper completion_provider hover_provider definition_provider puppet_monkey_patches].each do |lib|
1010
begin
1111
require "puppet-languageserver/#{lib}"
1212
rescue LoadError
@@ -126,6 +126,9 @@ def self.init_puppet_worker(options)
126126

127127
log_message(:info, 'Preloading Functions (Async)...')
128128
PuppetLanguageServer::PuppetHelper.load_functions_async
129+
130+
log_message(:info, 'Preloading Classes (Async)...')
131+
PuppetLanguageServer::PuppetHelper.load_classes_async
129132
else
130133
log_message(:info, 'Skipping preloading Puppet')
131134
end
Lines changed: 98 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,98 @@
1+
module PuppetLanguageServer
2+
module DefinitionProvider
3+
def self.find_definition(content, line_num, char_num)
4+
result = PuppetLanguageServer::PuppetParserHelper.object_under_cursor(content, line_num, char_num, false, [Puppet::Pops::Model::BlockExpression])
5+
6+
return nil if result.nil?
7+
8+
path = result[:path]
9+
item = result[:model]
10+
11+
response = []
12+
case item.class.to_s
13+
when 'Puppet::Pops::Model::CallNamedFunctionExpression'
14+
func_name = item.functor_expr.value
15+
response << function_name(resource_name)
16+
17+
when 'Puppet::Pops::Model::LiteralString'
18+
# LiteralString could be anything. Context is the key here
19+
parent = path.last
20+
21+
# What if it's a resource name. Then the Literal String must be the same as the Resource Title
22+
# e.g.
23+
# class { 'testclass': <--- testclass would be the LiteralString inside a ResourceBody
24+
# }
25+
if !parent.nil? &&
26+
parent.class.to_s == 'Puppet::Pops::Model::ResourceBody' &&
27+
parent.title.value == item.value
28+
resource_name = item.value
29+
response << type_or_class(resource_name)
30+
end
31+
32+
when 'Puppet::Pops::Model::QualifiedName'
33+
# Qualified names could be anything. Context is the key here
34+
parent = path.last
35+
36+
# What if it's a function name. Then the Qualified name must be the same as the function name
37+
if !parent.nil? &&
38+
parent.class.to_s == 'Puppet::Pops::Model::CallNamedFunctionExpression' &&
39+
parent.functor_expr.value == item.value
40+
func_name = item.value
41+
response << function_name(func_name)
42+
end
43+
# What if it's an "include <class>" call
44+
if !parent.nil? && parent.class.to_s == 'Puppet::Pops::Model::CallNamedFunctionExpression' && parent.functor_expr.value == 'include'
45+
resource_name = item.value
46+
response << type_or_class(resource_name)
47+
end
48+
# What if it's the name of a resource type or class
49+
if !parent.nil? && parent.class.to_s == 'Puppet::Pops::Model::ResourceExpression'
50+
resource_name = item.value
51+
response << type_or_class(resource_name)
52+
end
53+
54+
when 'Puppet::Pops::Model::ResourceExpression'
55+
resource_name = item.type_name.value
56+
response << type_or_class(resource_name)
57+
58+
else
59+
raise "Unable to generate Defintion information for object of type #{item.class}"
60+
end
61+
62+
response.compact
63+
end
64+
65+
private
66+
def self.type_or_class(resource_name)
67+
# Strip the leading double-colons for root resource names
68+
resource_name = resource_name.slice(2, resource_name.length - 2) if resource_name.start_with?('::')
69+
location = PuppetLanguageServer::PuppetHelper.type_load_info(resource_name)
70+
location = PuppetLanguageServer::PuppetHelper.class_load_info(resource_name) if location.nil?
71+
unless location.nil?
72+
return LanguageServer::Location.create({
73+
'uri' => 'file:///' + location['source'],
74+
'fromline' => location['line'],
75+
'fromchar' => 0,
76+
'toline' => location['line'],
77+
'tochar' => 1024,
78+
})
79+
end
80+
nil
81+
end
82+
83+
def self.function_name(func_name)
84+
location = PuppetLanguageServer::PuppetHelper.function_load_info(func_name)
85+
unless location.nil?
86+
return LanguageServer::Location.create({
87+
'uri' => 'file:///' + location['source'],
88+
'fromline' => location['line'],
89+
'fromchar' => 0,
90+
'toline' => location['line'],
91+
'tochar' => 1024,
92+
})
93+
end
94+
nil
95+
end
96+
97+
end
98+
end

server/lib/puppet-languageserver/message_router.rb

Lines changed: 16 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -101,7 +101,8 @@ def receive_request(request)
101101
'facterVersion' => Facter.version,
102102
'factsLoaded' => PuppetLanguageServer::FacterHelper.facts_loaded?,
103103
'functionsLoaded' => PuppetLanguageServer::PuppetHelper.functions_loaded?,
104-
'typesLoaded' => PuppetLanguageServer::PuppetHelper.types_loaded?))
104+
'typesLoaded' => PuppetLanguageServer::PuppetHelper.types_loaded?,
105+
'classesLoaded' => PuppetLanguageServer::PuppetHelper.classes_loaded?))
105106

106107
when 'puppet/getResource'
107108
type_name = request.params['typename']
@@ -184,6 +185,20 @@ def receive_request(request)
184185
PuppetLanguageServer.log_message(:error, "(textDocument/hover) #{exception}")
185186
request.reply_result(LanguageServer::Hover.create_nil_response)
186187
end
188+
189+
when 'textDocument/definition'
190+
file_uri = request.params['textDocument']['uri']
191+
line_num = request.params['position']['line']
192+
char_num = request.params['position']['character']
193+
content = documents.document(file_uri)
194+
begin
195+
#raise "Not Implemented"
196+
request.reply_result(PuppetLanguageServer::DefinitionProvider.find_definition(content, line_num, char_num))
197+
rescue StandardError => exception
198+
PuppetLanguageServer.log_message(:error, "(textDocument/definition) #{exception}")
199+
request.reply_result($null)
200+
end
201+
187202
else
188203
PuppetLanguageServer.log_message(:error, "Unknown RPC method #{request.rpc_method}")
189204
end

0 commit comments

Comments
 (0)