Line data Source code
1 : //* This file is part of the MOOSE framework
2 : //* https://mooseframework.inl.gov
3 : //*
4 : //* All rights reserved, see COPYRIGHT for full restrictions
5 : //* https://github.com/idaholab/moose/blob/master/COPYRIGHT
6 : //*
7 : //* Licensed under LGPL 2.1, please see LICENSE for details
8 : //* https://www.gnu.org/licenses/lgpl-2.1.html
9 :
10 : #include "MooseServer.h"
11 : #include "Moose.h"
12 : #include "AppFactory.h"
13 : #include "Syntax.h"
14 : #include "ActionFactory.h"
15 : #include "Factory.h"
16 : #include "InputParameters.h"
17 : #include "MooseUtils.h"
18 : #include "MooseEnum.h"
19 : #include "MultiMooseEnum.h"
20 : #include "ExecFlagEnum.h"
21 : #include "JsonSyntaxTree.h"
22 : #include "FileLineInfo.h"
23 : #include "CommandLine.h"
24 : #include "pcrecpp.h"
25 : #include "hit/hit.h"
26 : #include "wasphit/HITInterpreter.h"
27 : #include "waspcore/utils.h"
28 : #include <algorithm>
29 : #include <vector>
30 : #include <sstream>
31 : #include <iostream>
32 : #include <functional>
33 :
34 1 : MooseServer::MooseServer(MooseApp & moose_app)
35 1 : : _moose_app(moose_app),
36 1 : _connection(std::make_shared<wasp::lsp::IOStreamConnection>(this)),
37 2 : _formatting_tab_size(0)
38 : {
39 : // set server capabilities to receive full input text when changed
40 1 : server_capabilities[wasp::lsp::m_text_doc_sync] = wasp::DataObject();
41 1 : server_capabilities[wasp::lsp::m_text_doc_sync][wasp::lsp::m_open_close] = true;
42 1 : server_capabilities[wasp::lsp::m_text_doc_sync][wasp::lsp::m_change] = wasp::lsp::m_change_full;
43 :
44 : // notify completion, symbol, formatting, definition capabilities support
45 1 : server_capabilities[wasp::lsp::m_completion_provider] = wasp::DataObject();
46 1 : server_capabilities[wasp::lsp::m_completion_provider][wasp::lsp::m_resolve_provider] = false;
47 1 : server_capabilities[wasp::lsp::m_doc_symbol_provider] = true;
48 1 : server_capabilities[wasp::lsp::m_doc_format_provider] = true;
49 1 : server_capabilities[wasp::lsp::m_definition_provider] = true;
50 1 : server_capabilities[wasp::lsp::m_references_provider] = true;
51 1 : server_capabilities[wasp::lsp::m_hover_provider] = true;
52 1 : }
53 :
54 : bool
55 7 : MooseServer::parseDocumentForDiagnostics(wasp::DataArray & diagnosticsList)
56 : {
57 : // strip prefix from document uri if it exists to get parse file path
58 7 : std::string parse_file_path = document_path;
59 7 : pcrecpp::RE("(.*://)(.*)").Replace("\\2", &parse_file_path);
60 :
61 : // copy parent application parameters and modify to set up input check
62 7 : InputParameters app_params = _moose_app.parameters();
63 14 : app_params.set<std::shared_ptr<Parser>>("_parser") =
64 21 : std::make_shared<Parser>(parse_file_path, document_text);
65 :
66 7 : auto command_line = std::make_unique<CommandLine>(_moose_app.commandLine()->getArguments());
67 7 : if (command_line->hasArgument("--language-server"))
68 0 : command_line->removeArgument("--language-server");
69 7 : command_line->addArgument("--check-input");
70 7 : command_line->addArgument("--error-unused");
71 7 : command_line->addArgument("--error");
72 7 : command_line->addArgument("--color=off");
73 7 : command_line->addArgument("--disable-perf-graph-live");
74 7 : command_line->parse();
75 7 : app_params.set<std::shared_ptr<CommandLine>>("_command_line") = std::move(command_line);
76 :
77 : // create new application with parameters modified for input check run
78 28 : _check_apps[document_path] = AppFactory::instance().createShared(
79 28 : _moose_app.type(), _moose_app.name(), app_params, _moose_app.getCommunicator()->get());
80 :
81 : // add updated document text to map associated with current document path
82 7 : _path_to_text[document_path] = document_text;
83 :
84 : // enable exceptions to be thrown for errors and cache initial setting
85 7 : bool cached_throw_on_error = Moose::_throw_on_error;
86 7 : Moose::_throw_on_error = true;
87 :
88 7 : bool pass = true;
89 :
90 : // run input check application converting caught errors to diagnostics
91 : try
92 : {
93 12 : getCheckApp()->run();
94 : }
95 5 : catch (std::exception & err)
96 : {
97 5 : int line_number = 1;
98 5 : int column_number = 1;
99 :
100 5 : std::istringstream caught_msg(err.what());
101 :
102 : // walk over caught message line by line adding each as a diagnostic
103 30 : for (std::string error_line; std::getline(caught_msg, error_line);)
104 : {
105 : // check if this error line already has the input file path prefix
106 25 : if (error_line.rfind(parse_file_path + ":", 0) == 0)
107 : {
108 : // strip input file path and colon prefix off of this error line
109 16 : error_line.erase(0, parse_file_path.size() + 1);
110 :
111 : int match_line_number;
112 : int match_column_number;
113 16 : std::string match_error_line;
114 :
115 : // get line and column number from this error line if both exist
116 32 : if (pcrecpp::RE("^(\\d+)\\.(\\d+)\\-?\\d*:\\s*(.*)$")
117 16 : .FullMatch(error_line, &match_line_number, &match_column_number, &match_error_line))
118 : {
119 15 : line_number = match_line_number;
120 15 : column_number = match_column_number;
121 15 : error_line = match_error_line;
122 : }
123 :
124 : // otherwise get line number off of this error line if it exists
125 2 : else if (pcrecpp::RE("^(\\d+):\\s*(.*)$")
126 1 : .FullMatch(error_line, &match_line_number, &match_error_line))
127 : {
128 1 : line_number = match_line_number;
129 1 : column_number = 1;
130 1 : error_line = match_error_line;
131 : }
132 16 : }
133 :
134 : // skip adding diagnostic when message is empty or only whitespace
135 25 : if (error_line.find_first_not_of(" \t") == std::string::npos)
136 3 : continue;
137 :
138 : // build zero based line and column diagnostic and add to the list
139 22 : diagnosticsList.push_back(wasp::DataObject());
140 22 : wasp::DataObject * diagnostic = diagnosticsList.back().to_object();
141 22 : pass &= wasp::lsp::buildDiagnosticObject(*diagnostic,
142 : errors,
143 : line_number - 1,
144 : column_number - 1,
145 : line_number - 1,
146 : column_number - 1,
147 : 1,
148 : "moose_srv",
149 : "check_inp",
150 : error_line);
151 5 : }
152 5 : }
153 :
154 : // reset behavior of exceptions thrown for errors with initial setting
155 7 : Moose::_throw_on_error = cached_throw_on_error;
156 :
157 7 : return pass;
158 7 : }
159 :
160 : bool
161 6 : MooseServer::updateDocumentTextChanges(const std::string & replacement_text,
162 : int /* start_line */,
163 : int /* start_character */,
164 : int /* end_line */,
165 : int /* end_character*/,
166 : int /* range_length*/)
167 : {
168 : // replacement text swaps full document as indicated in server capabilities
169 6 : document_text = replacement_text;
170 :
171 6 : return true;
172 : }
173 :
174 : bool
175 13 : MooseServer::gatherDocumentCompletionItems(wasp::DataArray & completionItems,
176 : bool & is_incomplete,
177 : int line,
178 : int character)
179 : {
180 : // add only root level blocks to completion list when parser root is null
181 13 : if (!rootIsValid())
182 0 : return addSubblocksToList(completionItems, "/", line, character, line, character, "", false);
183 :
184 : // lambdas that will be used for checking completion request context type
185 39 : auto is_request_in_open_block = [](wasp::HITNodeView request_context) {
186 39 : return request_context.type() == wasp::OBJECT || request_context.type() == wasp::DOCUMENT_ROOT;
187 : };
188 22 : auto is_request_on_param_decl = [](wasp::HITNodeView request_context)
189 : {
190 30 : return request_context.type() == wasp::DECL && request_context.has_parent() &&
191 30 : (request_context.parent().type() == wasp::KEYED_VALUE ||
192 48 : request_context.parent().type() == wasp::ARRAY);
193 : };
194 37 : auto is_request_on_block_decl = [](wasp::HITNodeView request_context)
195 : {
196 51 : return request_context.type() == wasp::DECL && request_context.has_parent() &&
197 51 : request_context.parent().type() == wasp::OBJECT;
198 : };
199 :
200 : // get document tree root used to find node under request line and column
201 13 : wasp::HITNodeView view_root = getRoot().getNodeView();
202 13 : wasp::HITNodeView request_context;
203 :
204 : // find node under request location if it is not past all defined content
205 14 : if (line + 1 < (int)view_root.last_line() ||
206 1 : (line + 1 == (int)view_root.last_line() && character <= (int)view_root.last_column()))
207 13 : request_context = wasp::findNodeUnderLineColumn(view_root, line + 1, character + 1);
208 :
209 : // otherwise find last node in document with last line and column of tree
210 : else
211 : {
212 : request_context =
213 0 : wasp::findNodeUnderLineColumn(view_root, view_root.last_line(), view_root.last_column());
214 :
215 : // change context to be parent block or grandparent if block terminator
216 0 : wasp::HITNodeView object_context = request_context;
217 0 : while (object_context.type() != wasp::OBJECT && object_context.has_parent())
218 0 : object_context = object_context.parent();
219 0 : if (request_context.type() == wasp::OBJECT_TERM && object_context.has_parent())
220 0 : object_context = object_context.parent();
221 0 : request_context = object_context;
222 0 : }
223 :
224 : // change context to equal sign if it is preceding node and in open block
225 13 : if (is_request_in_open_block(request_context))
226 : {
227 3 : wasp::HITNodeView backup_context = request_context;
228 4 : for (int backup_char = character; backup_context == request_context && --backup_char > 0;)
229 1 : backup_context = wasp::findNodeUnderLineColumn(request_context, line + 1, backup_char + 1);
230 3 : if (backup_context.type() == wasp::ASSIGN || backup_context.type() == wasp::OVERRIDE_ASSIGN)
231 1 : request_context = backup_context;
232 3 : }
233 :
234 : // use request context type to set up replacement range and prefix filter
235 13 : int replace_line_beg = line;
236 13 : int replace_char_beg = character;
237 13 : int replace_line_end = line;
238 13 : int replace_char_end = character;
239 13 : std::string filtering_prefix;
240 13 : if (request_context.type() == wasp::DECL || request_context.type() == wasp::VALUE)
241 : {
242 : // completion on existing block name, parameter name, or value replaces
243 9 : replace_line_beg = request_context.line() - 1;
244 9 : replace_char_beg = request_context.column() - 1;
245 9 : replace_line_end = request_context.last_line() - 1;
246 9 : replace_char_end = request_context.last_column();
247 9 : filtering_prefix = request_context.data();
248 :
249 : // empty block name columns are same as bracket so bump replace columns
250 9 : if (is_request_on_block_decl(request_context) && filtering_prefix.empty())
251 : {
252 1 : replace_char_beg++;
253 1 : replace_char_end++;
254 : }
255 : }
256 :
257 : // get name of request context direct parent node so it can be used later
258 13 : const auto & parent_name = request_context.has_parent() ? request_context.parent().name() : "";
259 :
260 : // get object context and value of type parameter for request if provided
261 13 : wasp::HITNodeView object_context = request_context;
262 33 : while (object_context.type() != wasp::OBJECT && object_context.has_parent())
263 20 : object_context = object_context.parent();
264 13 : if (is_request_on_block_decl(request_context))
265 2 : object_context = object_context.parent();
266 13 : const std::string & object_path = object_context.path();
267 13 : wasp::HITNodeView type_node = object_context.first_child_by_name("type");
268 : const std::string & object_type =
269 13 : type_node.is_null() ? "" : wasp::strip_quotes(hit::extractValue(type_node.data()));
270 :
271 : // get set of all parameter and subblock names already specified in input
272 13 : std::set<std::string> existing_params, existing_subblocks;
273 13 : getExistingInput(object_context, existing_params, existing_subblocks);
274 :
275 : // set used to gather all parameters valid from object context of request
276 13 : InputParameters valid_params = emptyInputParameters();
277 :
278 : // set used to gather MooseObjectAction tasks to verify object parameters
279 13 : std::set<std::string> obj_act_tasks;
280 :
281 : // get set of global parameters, action parameters, and object parameters
282 13 : getAllValidParameters(valid_params, object_path, object_type, obj_act_tasks);
283 :
284 13 : bool pass = true;
285 :
286 : // add gathered parameters to completion list with input range and prefix
287 13 : if (is_request_in_open_block(request_context) || is_request_on_param_decl(request_context))
288 4 : pass &= addParametersToList(completionItems,
289 : valid_params,
290 : existing_params,
291 : replace_line_beg,
292 : replace_char_beg,
293 : replace_line_end,
294 : replace_char_end,
295 : filtering_prefix);
296 :
297 : // add all valid subblocks to completion list with input range and prefix
298 35 : if (is_request_in_open_block(request_context) || is_request_on_param_decl(request_context) ||
299 22 : is_request_on_block_decl(request_context))
300 12 : pass &= addSubblocksToList(completionItems,
301 : object_path,
302 : replace_line_beg,
303 : replace_char_beg,
304 : replace_line_end,
305 : replace_char_end,
306 : filtering_prefix,
307 6 : is_request_on_block_decl(request_context));
308 :
309 : // add valid parameter value options to completion list using input range
310 8 : if ((request_context.type() == wasp::VALUE || request_context.type() == wasp::ASSIGN ||
311 27 : request_context.type() == wasp::OVERRIDE_ASSIGN) &&
312 20 : valid_params.getParametersList().count(parent_name))
313 7 : pass &= addValuesToList(completionItems,
314 : valid_params,
315 : existing_params,
316 : existing_subblocks,
317 : parent_name,
318 : obj_act_tasks,
319 : object_path,
320 : replace_line_beg,
321 : replace_char_beg,
322 : replace_line_end,
323 : replace_char_end);
324 :
325 13 : is_incomplete = !pass;
326 :
327 13 : return pass;
328 13 : }
329 :
330 : void
331 13 : MooseServer::getExistingInput(wasp::HITNodeView parent_node,
332 : std::set<std::string> & existing_params,
333 : std::set<std::string> & existing_subblocks)
334 : {
335 : // gather names of all parameters and subblocks provided in input context
336 179 : for (auto itr = parent_node.begin(); itr != parent_node.end(); itr.next())
337 : {
338 83 : auto child_node = itr.get();
339 :
340 : // add key value or array type as parameter and object type as subblock
341 83 : if (child_node.type() == wasp::KEYED_VALUE || child_node.type() == wasp::ARRAY)
342 25 : existing_params.insert(child_node.name());
343 58 : else if (child_node.type() == wasp::OBJECT)
344 12 : existing_subblocks.insert(child_node.name());
345 96 : }
346 13 : }
347 :
348 : void
349 1150 : MooseServer::getAllValidParameters(InputParameters & valid_params,
350 : const std::string & object_path,
351 : const std::string & object_type,
352 : std::set<std::string> & obj_act_tasks)
353 : {
354 : // gather global parameters then action parameters then object parameters
355 1150 : valid_params += Moose::Builder::validParams();
356 1150 : getActionParameters(valid_params, object_path, obj_act_tasks);
357 1150 : getObjectParameters(valid_params, object_type, obj_act_tasks);
358 1150 : }
359 :
360 : void
361 1150 : MooseServer::getActionParameters(InputParameters & valid_params,
362 : const std::string & object_path,
363 : std::set<std::string> & obj_act_tasks)
364 : {
365 1150 : Syntax & syntax = getCheckApp()->syntax();
366 1150 : ActionFactory & action_factory = getCheckApp()->getActionFactory();
367 :
368 : // get registered syntax path identifier using actual object context path
369 : bool is_parent;
370 1150 : std::string registered_syntax = syntax.isAssociated(object_path, &is_parent);
371 :
372 : // use is_parent to skip action parameters when not explicitly registered
373 1150 : if (!is_parent)
374 : {
375 : // get action objects associated with registered syntax path identifier
376 1101 : auto action_range = syntax.getActions(registered_syntax);
377 :
378 : // traverse action objects for syntax to gather valid action parameters
379 2222 : for (auto action_iter = action_range.first; action_iter != action_range.second; action_iter++)
380 : {
381 1121 : const std::string & action_name = action_iter->second._action;
382 :
383 : // use action name to get set of valid parameters from action factory
384 1121 : InputParameters action_params = action_factory.getValidParams(action_name);
385 :
386 : // gather all MooseObjectAction tasks for verifying object parameters
387 1121 : if (action_params.have_parameter<bool>("isObjectAction"))
388 : {
389 1088 : if (action_params.get<bool>("isObjectAction"))
390 : {
391 1088 : std::set<std::string> tasks_by_actions = action_factory.getTasksByAction(action_name);
392 1088 : obj_act_tasks.insert(tasks_by_actions.begin(), tasks_by_actions.end());
393 1088 : }
394 :
395 : // filter parameter from completion list as it is not used in input
396 1088 : action_params.remove("isObjectAction");
397 : }
398 :
399 : // add parameters from action to full valid collection being gathered
400 1121 : valid_params += action_params;
401 1121 : }
402 : }
403 1150 : }
404 :
405 : void
406 1150 : MooseServer::getObjectParameters(InputParameters & valid_params,
407 : std::string object_type,
408 : const std::set<std::string> & obj_act_tasks)
409 : {
410 1150 : Syntax & syntax = getCheckApp()->syntax();
411 1150 : Factory & factory = getCheckApp()->getFactory();
412 :
413 : // use type parameter default if it exists and is not provided from input
414 1165 : if (object_type.empty() && valid_params.have_parameter<std::string>("type") &&
415 1165 : !valid_params.get<std::string>("type").empty())
416 : {
417 5 : object_type = valid_params.get<std::string>("type");
418 :
419 : // make type parameter not required in input since it has default value
420 5 : valid_params.makeParamNotRequired("type");
421 : }
422 :
423 : // check if object type has been registered to prevent unregistered error
424 1150 : if (factory.isRegistered(object_type))
425 : {
426 : // use object type to get set of valid parameters registered in factory
427 1079 : InputParameters object_params = factory.getValidParams(object_type);
428 :
429 : // check if object has base associated with any MooseObjectAction tasks
430 1079 : if (object_params.have_parameter<std::string>("_moose_base"))
431 : {
432 1079 : const std::string & moose_base = object_params.get<std::string>("_moose_base");
433 :
434 2128 : for (const auto & obj_act_task : obj_act_tasks)
435 : {
436 1081 : if (syntax.verifyMooseObjectTask(moose_base, obj_act_task))
437 : {
438 : // add parameters from object to valid collection if base matches
439 32 : valid_params += object_params;
440 32 : break;
441 : }
442 : }
443 : }
444 1079 : }
445 :
446 : // make parameters from list of those set by action not required in input
447 1150 : if (valid_params.have_parameter<std::vector<std::string>>("_object_params_set_by_action"))
448 : {
449 6 : auto names = valid_params.get<std::vector<std::string>>("_object_params_set_by_action");
450 12 : for (const auto & name : names)
451 6 : valid_params.makeParamNotRequired(name);
452 :
453 : // filter parameter from completion list since it is not used for input
454 6 : valid_params.remove("_object_params_set_by_action");
455 6 : }
456 1150 : }
457 :
458 : bool
459 4 : MooseServer::addParametersToList(wasp::DataArray & completionItems,
460 : const InputParameters & valid_params,
461 : const std::set<std::string> & existing_params,
462 : int replace_line_beg,
463 : int replace_char_beg,
464 : int replace_line_end,
465 : int replace_char_end,
466 : const std::string & filtering_prefix)
467 : {
468 4 : bool pass = true;
469 :
470 : // walk over collection of all valid parameters and build completion list
471 264 : for (const auto & valid_params_iter : valid_params)
472 : {
473 260 : const std::string & param_name = valid_params_iter.first;
474 260 : bool deprecated = valid_params.isParamDeprecated(param_name);
475 260 : bool is_private = valid_params.isPrivate(param_name);
476 :
477 : // filter out parameters that are deprecated, private, or already exist
478 260 : if (deprecated || is_private || existing_params.count(param_name))
479 202 : continue;
480 :
481 : // filter out parameters that do not begin with prefix if one was given
482 176 : if (param_name.rfind(filtering_prefix, 0) != 0)
483 118 : continue;
484 :
485 : // process parameter description and type to use in input default value
486 58 : std::string dirty_type = valid_params.type(param_name);
487 58 : std::string clean_type = MooseUtils::prettyCppType(dirty_type);
488 58 : std::string basic_type = JsonSyntaxTree::basicCppType(clean_type);
489 58 : std::string doc_string = valid_params.getDocString(param_name);
490 58 : MooseUtils::escape(doc_string);
491 :
492 : // use basic type to decide if parameter is array and quotes are needed
493 58 : bool is_array = basic_type.compare(0, 6, "Array:") == 0;
494 :
495 : // remove any array prefixes from basic type string and leave base type
496 58 : pcrecpp::RE("(Array:)*(.*)").GlobalReplace("\\2", &basic_type);
497 :
498 : // prepare clean cpp type string to be used for key to find input paths
499 58 : pcrecpp::RE(".+<([A-Za-z0-9_' ':]*)>.*").GlobalReplace("\\1", &clean_type);
500 :
501 : // decide completion item kind that client may use to display list icon
502 58 : int complete_kind = getCompletionItemKind(valid_params, param_name, clean_type, true);
503 :
504 : // default value for completion to be built using parameter information
505 58 : std::string default_value;
506 :
507 : // first if parameter default is set then use it to build default value
508 58 : if (valid_params.isParamValid(param_name))
509 : {
510 32 : default_value = JsonSyntaxTree::buildOutputString(valid_params_iter);
511 32 : default_value = MooseUtils::trim(default_value);
512 : }
513 :
514 : // otherwise if parameter has coupled default then use as default value
515 26 : else if (valid_params.hasDefaultCoupledValue(param_name))
516 : {
517 0 : std::ostringstream oss;
518 0 : oss << valid_params.defaultCoupledValue(param_name);
519 0 : default_value = oss.str();
520 0 : }
521 :
522 : // switch 1 to true or 0 to false if boolean parameter as default value
523 58 : if (basic_type == "Boolean" && default_value == "1")
524 5 : default_value = "true";
525 53 : else if (basic_type == "Boolean" && default_value == "0")
526 10 : default_value = "false";
527 :
528 : // wrap default value with single quotes if it exists and type is array
529 58 : std::string array_quote = is_array && !default_value.empty() ? "'" : "";
530 :
531 : // choose format of insertion text based on if client supports snippets
532 : int text_format;
533 58 : std::string insert_text;
534 58 : if (client_snippet_support && !default_value.empty())
535 : {
536 25 : text_format = wasp::lsp::m_text_format_snippet;
537 25 : insert_text = param_name + " = " + array_quote + "${1:" + default_value + "}" + array_quote;
538 : }
539 : else
540 : {
541 33 : text_format = wasp::lsp::m_text_format_plaintext;
542 33 : insert_text = param_name + " = " + array_quote + default_value + array_quote;
543 : }
544 : // finally build full insertion from parameter name, quote, and default
545 :
546 : // add parameter label, insert text, and description to completion list
547 58 : completionItems.push_back(wasp::DataObject());
548 58 : wasp::DataObject * item = completionItems.back().to_object();
549 58 : pass &= wasp::lsp::buildCompletionObject(*item,
550 : errors,
551 : param_name,
552 : replace_line_beg,
553 : replace_char_beg,
554 : replace_line_end,
555 : replace_char_end,
556 : insert_text,
557 : complete_kind,
558 : "",
559 : doc_string,
560 : false,
561 : false,
562 : text_format);
563 58 : }
564 :
565 4 : return pass;
566 : }
567 :
568 : bool
569 6 : MooseServer::addSubblocksToList(wasp::DataArray & completionItems,
570 : const std::string & object_path,
571 : int replace_line_beg,
572 : int replace_char_beg,
573 : int replace_line_end,
574 : int replace_char_end,
575 : const std::string & filtering_prefix,
576 : bool request_on_block_decl)
577 : {
578 6 : Syntax & syntax = getCheckApp()->syntax();
579 :
580 : // set used to prevent reprocessing syntax paths for more than one action
581 6 : std::set<std::string> syntax_paths_processed;
582 :
583 : // build map of all syntax paths to names for subblocks and save to reuse
584 6 : if (_syntax_to_subblocks.empty())
585 : {
586 107 : for (const auto & syntax_path_iter : syntax.getAssociatedActions())
587 : {
588 106 : std::string syntax_path = "/" + syntax_path_iter.first;
589 :
590 : // skip current syntax path if already processed for different action
591 106 : if (!syntax_paths_processed.insert(syntax_path).second)
592 15 : continue;
593 :
594 : // walk backward through syntax path adding subblock names to parents
595 275 : for (std::size_t last_sep; (last_sep = syntax_path.find_last_of("/")) != std::string::npos;)
596 : {
597 184 : std::string subblock_name = syntax_path.substr(last_sep + 1);
598 184 : syntax_path = syntax_path.substr(0, last_sep);
599 184 : _syntax_to_subblocks[syntax_path].insert(subblock_name);
600 184 : }
601 106 : }
602 : }
603 :
604 : // get registered syntax from object path using map of paths to subblocks
605 6 : std::string registered_syntax = syntax.isAssociated(object_path, nullptr, _syntax_to_subblocks);
606 :
607 6 : bool pass = true;
608 :
609 : // walk over subblock names if found or at root and build completion list
610 6 : if (!registered_syntax.empty() || object_path == "/")
611 : {
612 : // choose format of insertion text based on if client supports snippets
613 5 : int text_format = client_snippet_support ? wasp::lsp::m_text_format_snippet
614 : : wasp::lsp::m_text_format_plaintext;
615 :
616 81 : for (const auto & subblock_name : _syntax_to_subblocks[registered_syntax])
617 : {
618 : // filter subblock if it does not begin with prefix and one was given
619 76 : if (subblock_name != "*" && subblock_name.rfind(filtering_prefix, 0) != 0)
620 5 : continue;
621 :
622 71 : std::string doc_string;
623 71 : std::string insert_text;
624 : int complete_kind;
625 :
626 : // build required parameter list for each block to use in insert text
627 71 : const std::string full_block_path = object_path + "/" + subblock_name;
628 71 : const std::string req_params = getRequiredParamsText(full_block_path, "", {}, " ");
629 :
630 : // customize description and insert text for star and named subblocks
631 71 : if (subblock_name == "*")
632 : {
633 2 : doc_string = "custom user named block";
634 4 : insert_text = (request_on_block_decl ? "" : "[") +
635 8 : (filtering_prefix.size() ? filtering_prefix : "block_name") + "]" +
636 4 : req_params + "\n " + (client_snippet_support ? "$0" : "") + "\n[]";
637 2 : complete_kind = wasp::lsp::m_comp_kind_variable;
638 : }
639 : else
640 : {
641 69 : doc_string = "application named block";
642 138 : insert_text = (request_on_block_decl ? "" : "[") + subblock_name + "]" + req_params +
643 138 : "\n " + (client_snippet_support ? "$0" : "") + "\n[]";
644 69 : complete_kind = wasp::lsp::m_comp_kind_struct;
645 : }
646 :
647 : // add subblock name, insert text, and description to completion list
648 71 : completionItems.push_back(wasp::DataObject());
649 71 : wasp::DataObject * item = completionItems.back().to_object();
650 71 : pass &= wasp::lsp::buildCompletionObject(*item,
651 : errors,
652 : subblock_name,
653 : replace_line_beg,
654 : replace_char_beg,
655 : replace_line_end,
656 : replace_char_end,
657 : insert_text,
658 : complete_kind,
659 : "",
660 : doc_string,
661 : false,
662 : false,
663 : text_format);
664 71 : }
665 : }
666 :
667 6 : return pass;
668 6 : }
669 :
670 : bool
671 7 : MooseServer::addValuesToList(wasp::DataArray & completionItems,
672 : const InputParameters & valid_params,
673 : const std::set<std::string> & existing_params,
674 : const std::set<std::string> & existing_subblocks,
675 : const std::string & param_name,
676 : const std::set<std::string> & obj_act_tasks,
677 : const std::string & object_path,
678 : int replace_line_beg,
679 : int replace_char_beg,
680 : int replace_line_end,
681 : int replace_char_end)
682 : {
683 7 : Syntax & syntax = getCheckApp()->syntax();
684 7 : Factory & factory = getCheckApp()->getFactory();
685 :
686 : // get clean type for path associations and basic type for boolean values
687 7 : std::string dirty_type = valid_params.type(param_name);
688 7 : std::string clean_type = MooseUtils::prettyCppType(dirty_type);
689 7 : std::string basic_type = JsonSyntaxTree::basicCppType(clean_type);
690 :
691 : // remove any array prefixes from basic type string and replace with base
692 7 : pcrecpp::RE("(Array:)*(.*)").GlobalReplace("\\2", &basic_type);
693 :
694 : // prepare clean cpp type string to be used for a key to find input paths
695 7 : pcrecpp::RE(".+<([A-Za-z0-9_' ':]*)>.*").GlobalReplace("\\1", &clean_type);
696 :
697 : // decide completion item kind that client may use to display a list icon
698 7 : int complete_kind = getCompletionItemKind(valid_params, param_name, clean_type, false);
699 :
700 : // map used to gather options and descriptions for value completion items
701 7 : std::map<std::string, std::string> options_and_descs;
702 :
703 : // first if parameter name is active or inactive then use input subblocks
704 7 : if (param_name == "active" || param_name == "inactive")
705 3 : for (const auto & subblock_name : existing_subblocks)
706 2 : options_and_descs[subblock_name] = "subblock name";
707 :
708 : // otherwise if parameter type is boolean then use true and false strings
709 6 : else if (basic_type == "Boolean")
710 : {
711 1 : options_and_descs["true"];
712 1 : options_and_descs["false"];
713 : }
714 :
715 : // otherwise if parameter type is one of the enums then use valid options
716 5 : else if (valid_params.have_parameter<MooseEnum>(param_name))
717 2 : getEnumsAndDocs(valid_params.get<MooseEnum>(param_name), options_and_descs);
718 3 : else if (valid_params.have_parameter<MultiMooseEnum>(param_name))
719 0 : getEnumsAndDocs(valid_params.get<MultiMooseEnum>(param_name), options_and_descs);
720 3 : else if (valid_params.have_parameter<ExecFlagEnum>(param_name))
721 0 : getEnumsAndDocs(valid_params.get<ExecFlagEnum>(param_name), options_and_descs);
722 3 : else if (valid_params.have_parameter<std::vector<MooseEnum>>(param_name))
723 0 : getEnumsAndDocs(valid_params.get<std::vector<MooseEnum>>(param_name)[0], options_and_descs);
724 :
725 : // otherwise if parameter name is type then use all verified object names
726 3 : else if (param_name == "type")
727 : {
728 : // walk over entire set of objects that have been registered in factory
729 1055 : for (const auto & objects_iter : factory.registeredObjects())
730 : {
731 1054 : const std::string & object_name = objects_iter.first;
732 1054 : const InputParameters & object_params = objects_iter.second->buildParameters();
733 :
734 : // build required parameter list for each block to use in insert text
735 1054 : std::string req_params = getRequiredParamsText(object_path, object_name, existing_params, "");
736 1054 : req_params += !req_params.empty() ? "\n" : "";
737 :
738 : // check if object has registered base parameter that can be verified
739 1054 : if (!object_params.have_parameter<std::string>("_moose_base"))
740 0 : continue;
741 1054 : const std::string & moose_base = object_params.get<std::string>("_moose_base");
742 :
743 : // walk over gathered MooseObjectAction tasks and add if base matches
744 2101 : for (const auto & obj_act_task : obj_act_tasks)
745 : {
746 1054 : if (!syntax.verifyMooseObjectTask(moose_base, obj_act_task))
747 1047 : continue;
748 7 : std::string type_description = object_params.getClassDescription();
749 7 : MooseUtils::escape(type_description);
750 7 : options_and_descs[object_name + req_params] = type_description;
751 7 : break;
752 7 : }
753 1054 : }
754 : }
755 :
756 : // otherwise if parameter type has any associated syntax then use lookups
757 : else
758 : {
759 : // build map of parameter types to input lookup paths and save to reuse
760 2 : if (_type_to_input_paths.empty())
761 : {
762 27 : for (const auto & associated_types_iter : syntax.getAssociatedTypes())
763 : {
764 26 : const std::string & type = associated_types_iter.second;
765 26 : const std::string & path = associated_types_iter.first;
766 26 : _type_to_input_paths[type].insert(path);
767 : }
768 : }
769 :
770 : // check for input lookup paths that are associated with parameter type
771 2 : const auto & input_path_iter = _type_to_input_paths.find(clean_type);
772 :
773 2 : if (input_path_iter != _type_to_input_paths.end())
774 : {
775 2 : wasp::HITNodeView view_root = getRoot().getNodeView();
776 :
777 : // walk over all syntax paths that are associated with parameter type
778 5 : for (const auto & input_path : input_path_iter->second)
779 : {
780 : // use wasp siren to gather all input values at current lookup path
781 3 : wasp::SIRENInterpreter<> selector;
782 3 : if (!selector.parseString(input_path))
783 0 : continue;
784 3 : wasp::SIRENResultSet<wasp::HITNodeView> results;
785 3 : std::size_t count = selector.evaluate(view_root, results);
786 :
787 : // walk over results and add each input value found at current path
788 24 : for (std::size_t i = 0; i < count; i++)
789 21 : if (results.adapted(i).type() == wasp::OBJECT)
790 8 : options_and_descs[results.adapted(i).name()] = "from /" + input_path;
791 3 : }
792 2 : }
793 : }
794 :
795 7 : bool pass = true;
796 :
797 : // walk over pairs of options with descriptions and build completion list
798 33 : for (const auto & option_and_desc : options_and_descs)
799 : {
800 26 : const std::string & insert_text = option_and_desc.first;
801 26 : const std::string & option_name = insert_text.substr(0, insert_text.find('\n'));
802 26 : const std::string & description = option_and_desc.second;
803 :
804 : // add option name, insertion range, and description to completion list
805 26 : completionItems.push_back(wasp::DataObject());
806 26 : wasp::DataObject * item = completionItems.back().to_object();
807 26 : pass &= wasp::lsp::buildCompletionObject(*item,
808 : errors,
809 : option_name,
810 : replace_line_beg,
811 : replace_char_beg,
812 : replace_line_end,
813 : replace_char_end,
814 : insert_text,
815 : complete_kind,
816 : "",
817 : description,
818 : false,
819 : false,
820 : wasp::lsp::m_text_format_plaintext);
821 26 : }
822 :
823 7 : return pass;
824 7 : }
825 :
826 : template <typename MooseEnumType>
827 : void
828 5 : MooseServer::getEnumsAndDocs(MooseEnumType & moose_enum_param,
829 : std::map<std::string, std::string> & options_and_descs)
830 : {
831 : // get map that contains any documentation strings provided for each item
832 5 : const auto & enum_docs = moose_enum_param.getItemDocumentation();
833 :
834 : // walk over enums filling map with options and any provided descriptions
835 41 : for (const auto & item : moose_enum_param.items())
836 36 : options_and_descs[item.name()] = enum_docs.count(item) ? enum_docs.at(item) : "";
837 5 : }
838 :
839 : bool
840 2 : MooseServer::gatherDocumentDefinitionLocations(wasp::DataArray & definitionLocations,
841 : int line,
842 : int character)
843 : {
844 2 : Factory & factory = getCheckApp()->getFactory();
845 :
846 : // return without any definition locations added when parser root is null
847 2 : if (!rootIsValid())
848 0 : return true;
849 :
850 : // find hit node for zero based request line and column number from input
851 2 : wasp::HITNodeView view_root = getRoot().getNodeView();
852 : wasp::HITNodeView request_context =
853 2 : wasp::findNodeUnderLineColumn(view_root, line + 1, character + 1);
854 :
855 : // return without any definition locations added when node not value type
856 2 : if (request_context.type() != wasp::VALUE)
857 0 : return true;
858 :
859 : // get name of parameter node parent of value and value string from input
860 2 : std::string param_name = request_context.has_parent() ? request_context.parent().name() : "";
861 2 : std::string val_string = request_context.last_as_string();
862 :
863 : // add source code location if type parameter with registered object name
864 2 : if (param_name == "type" && factory.isRegistered(val_string))
865 : {
866 : // get file path and line number of source code registering object type
867 1 : FileLineInfo file_line_info = factory.getLineInfo(val_string);
868 :
869 : // return without any definition locations added if file cannot be read
870 2 : if (!file_line_info.isValid() ||
871 2 : !MooseUtils::checkFileReadable(file_line_info.file(), false, false, false))
872 0 : return true;
873 :
874 : // add file scheme prefix to front of file path to build definition uri
875 1 : auto location_uri = wasp::lsp::m_uri_prefix + file_line_info.file();
876 :
877 : // add file uri and zero based line and column range to definition list
878 1 : definitionLocations.push_back(wasp::DataObject());
879 1 : wasp::DataObject * location = definitionLocations.back().to_object();
880 3 : return wasp::lsp::buildLocationObject(*location,
881 : errors,
882 : location_uri,
883 1 : file_line_info.line() - 1,
884 : 0,
885 1 : file_line_info.line() - 1,
886 1 : 1000);
887 1 : }
888 :
889 : // get object context and value of type parameter for request if provided
890 1 : wasp::HITNodeView object_context = request_context;
891 3 : while (object_context.type() != wasp::OBJECT && object_context.has_parent())
892 2 : object_context = object_context.parent();
893 1 : const std::string & object_path = object_context.path();
894 1 : wasp::HITNodeView type_node = object_context.first_child_by_name("type");
895 : const std::string & object_type =
896 1 : type_node.is_null() ? "" : wasp::strip_quotes(hit::extractValue(type_node.data()));
897 :
898 : // set used to gather all parameters valid from object context of request
899 1 : InputParameters valid_params = emptyInputParameters();
900 :
901 : // set used to gather MooseObjectAction tasks to verify object parameters
902 1 : std::set<std::string> obj_act_tasks;
903 :
904 : // get set of global parameters, action parameters, and object parameters
905 1 : getAllValidParameters(valid_params, object_path, object_type, obj_act_tasks);
906 :
907 : // set used to gather nodes from input lookups custom sorted by locations
908 : SortedLocationNodes location_nodes(
909 0 : [](const wasp::HITNodeView & l, const wasp::HITNodeView & r)
910 : {
911 7 : const std::string & l_file = l.node_pool()->stream_name();
912 7 : const std::string & r_file = r.node_pool()->stream_name();
913 17 : return (l_file < r_file || (l_file == r_file && l.line() < r.line()) ||
914 17 : (l_file == r_file && l.line() == r.line() && l.column() < r.column()));
915 1 : });
916 :
917 : // gather all lookup path nodes matching value if parameter name is valid
918 28 : for (const auto & valid_params_iter : valid_params)
919 : {
920 28 : if (valid_params_iter.first == param_name)
921 : {
922 : // get cpp type and prepare string for use as key finding input paths
923 1 : std::string dirty_type = valid_params.type(param_name);
924 1 : std::string clean_type = MooseUtils::prettyCppType(dirty_type);
925 1 : pcrecpp::RE(".+<([A-Za-z0-9_' ':]*)>.*").GlobalReplace("\\1", &clean_type);
926 :
927 : // get set of nodes from associated path lookups matching input value
928 1 : getInputLookupDefinitionNodes(location_nodes, clean_type, val_string);
929 1 : break;
930 1 : }
931 : }
932 :
933 : // add parameter declarator to set if none were gathered by input lookups
934 1 : if (location_nodes.empty() && request_context.has_parent() &&
935 1 : request_context.parent().child_count_by_name("decl"))
936 0 : location_nodes.insert(request_context.parent().first_child_by_name("decl"));
937 :
938 : // add locations to definition list using lookups or parameter declarator
939 1 : return addLocationNodesToList(definitionLocations, location_nodes);
940 2 : }
941 :
942 : void
943 1 : MooseServer::getInputLookupDefinitionNodes(SortedLocationNodes & location_nodes,
944 : const std::string & clean_type,
945 : const std::string & val_string)
946 : {
947 1 : Syntax & syntax = getCheckApp()->syntax();
948 :
949 : // build map from parameter types to input lookup paths and save to reuse
950 1 : if (_type_to_input_paths.empty())
951 : {
952 0 : for (const auto & associated_types_iter : syntax.getAssociatedTypes())
953 : {
954 0 : const std::string & type = associated_types_iter.second;
955 0 : const std::string & path = associated_types_iter.first;
956 0 : _type_to_input_paths[type].insert(path);
957 : }
958 : }
959 :
960 : // find set of input lookup paths that are associated with parameter type
961 1 : const auto & input_path_iter = _type_to_input_paths.find(clean_type);
962 :
963 : // return without any definition locations added when no paths associated
964 1 : if (input_path_iter == _type_to_input_paths.end())
965 0 : return;
966 :
967 : // get root node from input to use in input lookups with associated paths
968 1 : wasp::HITNodeView view_root = getRoot().getNodeView();
969 :
970 : // walk over all syntax paths that are associated with parameter type
971 3 : for (const auto & input_path : input_path_iter->second)
972 : {
973 : // use wasp siren to gather all nodes from current lookup path in input
974 2 : wasp::SIRENInterpreter<> selector;
975 2 : if (!selector.parseString(input_path))
976 0 : continue;
977 2 : wasp::SIRENResultSet<wasp::HITNodeView> results;
978 2 : std::size_t count = selector.evaluate(view_root, results);
979 :
980 : // walk over results and add nodes that have name matching value to set
981 17 : for (std::size_t i = 0; i < count; i++)
982 18 : if (results.adapted(i).type() == wasp::OBJECT && results.adapted(i).name() == val_string &&
983 18 : results.adapted(i).child_count_by_name("decl"))
984 3 : location_nodes.insert(results.adapted(i).first_child_by_name("decl"));
985 2 : }
986 1 : }
987 :
988 : bool
989 2 : MooseServer::addLocationNodesToList(wasp::DataArray & defsOrRefsLocations,
990 : const SortedLocationNodes & location_nodes)
991 : {
992 2 : bool pass = true;
993 :
994 : // walk over set of sorted nodes provided to add and build locations list
995 11 : for (const auto & location_nodes_iter : location_nodes)
996 : {
997 : // add file scheme prefix onto front of file path to build location uri
998 9 : auto location_uri = wasp::lsp::m_uri_prefix + location_nodes_iter.node_pool()->stream_name();
999 :
1000 : // add file uri with zero based line and column range to locations list
1001 9 : defsOrRefsLocations.push_back(wasp::DataObject());
1002 9 : wasp::DataObject * location = defsOrRefsLocations.back().to_object();
1003 45 : pass &= wasp::lsp::buildLocationObject(*location,
1004 : errors,
1005 : location_uri,
1006 9 : location_nodes_iter.line() - 1,
1007 9 : location_nodes_iter.column() - 1,
1008 9 : location_nodes_iter.last_line() - 1,
1009 9 : location_nodes_iter.last_column());
1010 9 : }
1011 :
1012 2 : return pass;
1013 : }
1014 :
1015 : bool
1016 6 : MooseServer::getHoverDisplayText(std::string & display_text, int line, int character)
1017 : {
1018 6 : Factory & factory = getCheckApp()->getFactory();
1019 6 : Syntax & syntax = getCheckApp()->syntax();
1020 :
1021 : // return and leave display text as empty string when parser root is null
1022 6 : if (!rootIsValid())
1023 0 : return true;
1024 :
1025 : // find hit node for zero based request line and column number from input
1026 6 : wasp::HITNodeView view_root = getRoot().getNodeView();
1027 : wasp::HITNodeView request_context =
1028 6 : wasp::findNodeUnderLineColumn(view_root, line + 1, character + 1);
1029 :
1030 : // return and leave display text as empty string when not on key or value
1031 4 : if ((request_context.type() != wasp::DECL && request_context.type() != wasp::VALUE) ||
1032 16 : !request_context.has_parent() ||
1033 12 : (request_context.parent().type() != wasp::KEYED_VALUE &&
1034 8 : request_context.parent().type() != wasp::ARRAY))
1035 1 : return true;
1036 :
1037 : // get name of parameter node and value string that is specified in input
1038 5 : std::string paramkey = request_context.parent().name();
1039 5 : std::string paramval = request_context.last_as_string();
1040 :
1041 : // get object context path and object type value for request if it exists
1042 5 : wasp::HITNodeView object_context = request_context;
1043 15 : while (object_context.type() != wasp::OBJECT && object_context.has_parent())
1044 10 : object_context = object_context.parent();
1045 5 : const std::string object_path = object_context.path();
1046 5 : wasp::HITNodeView type_node = object_context.first_child_by_name("type");
1047 : const std::string object_type =
1048 5 : type_node.is_null() ? "" : wasp::strip_quotes(hit::extractValue(type_node.data()));
1049 :
1050 : // gather global, action, and object parameters in request object context
1051 5 : InputParameters valid_params = emptyInputParameters();
1052 5 : std::set<std::string> obj_act_tasks;
1053 5 : getAllValidParameters(valid_params, object_path, object_type, obj_act_tasks);
1054 :
1055 : // use class description as display text when request is valid type value
1056 5 : if (request_context.type() == wasp::VALUE && paramkey == "type" && factory.isRegistered(paramval))
1057 : {
1058 1 : const InputParameters & object_params = factory.getValidParams(paramval);
1059 1 : if (object_params.have_parameter<std::string>("_moose_base"))
1060 : {
1061 1 : const std::string moose_base = object_params.get<std::string>("_moose_base");
1062 1 : for (const auto & obj_act_task : obj_act_tasks)
1063 : {
1064 1 : if (syntax.verifyMooseObjectTask(moose_base, obj_act_task))
1065 : {
1066 1 : display_text = object_params.getClassDescription();
1067 1 : break;
1068 : }
1069 : }
1070 1 : }
1071 1 : }
1072 :
1073 : // use item documentation as display text when request is enum type value
1074 4 : else if (request_context.type() == wasp::VALUE)
1075 : {
1076 3 : std::map<std::string, std::string> options_and_descs;
1077 3 : if (valid_params.have_parameter<MooseEnum>(paramkey))
1078 1 : getEnumsAndDocs(valid_params.get<MooseEnum>(paramkey), options_and_descs);
1079 2 : else if (valid_params.have_parameter<MultiMooseEnum>(paramkey))
1080 1 : getEnumsAndDocs(valid_params.get<MultiMooseEnum>(paramkey), options_and_descs);
1081 1 : else if (valid_params.have_parameter<ExecFlagEnum>(paramkey))
1082 1 : getEnumsAndDocs(valid_params.get<ExecFlagEnum>(paramkey), options_and_descs);
1083 0 : else if (valid_params.have_parameter<std::vector<MooseEnum>>(paramkey))
1084 0 : getEnumsAndDocs(valid_params.get<std::vector<MooseEnum>>(paramkey)[0], options_and_descs);
1085 3 : if (options_and_descs.count(paramval))
1086 3 : display_text = options_and_descs.find(paramval)->second;
1087 3 : }
1088 :
1089 : // use parameter documentation as display text when request is valid name
1090 1 : else if (request_context.type() == wasp::DECL && valid_params.getParametersList().count(paramkey))
1091 1 : display_text = valid_params.getDocString(paramkey);
1092 :
1093 5 : MooseUtils::escape(display_text);
1094 5 : return true;
1095 6 : }
1096 :
1097 : bool
1098 1 : MooseServer::gatherDocumentReferencesLocations(wasp::DataArray & referencesLocations,
1099 : int line,
1100 : int character,
1101 : bool include_declaration)
1102 : {
1103 1 : Syntax & syntax = getCheckApp()->syntax();
1104 :
1105 : // return without adding any reference locations when parser root is null
1106 1 : if (!rootIsValid())
1107 0 : return true;
1108 :
1109 : // find hit node for zero based request line and column number from input
1110 1 : wasp::HITNodeView view_root = getRoot().getNodeView();
1111 : wasp::HITNodeView request_context =
1112 1 : wasp::findNodeUnderLineColumn(view_root, line + 1, character + 1);
1113 :
1114 : // return without adding any references when request not block declarator
1115 1 : if ((request_context.type() != wasp::DECL && request_context.type() != wasp::DOT_SLASH &&
1116 1 : request_context.type() != wasp::LBRACKET && request_context.type() != wasp::RBRACKET) ||
1117 2 : !request_context.has_parent() || request_context.parent().type() != wasp::OBJECT)
1118 0 : return true;
1119 :
1120 : // get input path and block name of declarator located at request context
1121 1 : const std::string & block_path = request_context.parent().path();
1122 1 : const std::string & block_name = request_context.parent().name();
1123 :
1124 : // build map from input lookup paths to parameter types and save to reuse
1125 1 : if (_input_path_to_types.empty())
1126 27 : for (const auto & associated_types_iter : syntax.getAssociatedTypes())
1127 : {
1128 26 : const std::string & path = associated_types_iter.first;
1129 26 : const std::string & type = associated_types_iter.second;
1130 26 : _input_path_to_types[path].insert(type);
1131 : }
1132 :
1133 : // get registered syntax from block path with map of input paths to types
1134 : bool is_parent;
1135 1 : std::string registered_syntax = syntax.isAssociated(block_path, &is_parent, _input_path_to_types);
1136 :
1137 : // return without adding any references if syntax has no types associated
1138 1 : if (is_parent || !_input_path_to_types.count(registered_syntax))
1139 0 : return true;
1140 :
1141 : // get set of parameter types which are associated with registered syntax
1142 1 : const std::set<std::string> & target_types = _input_path_to_types.at(registered_syntax);
1143 :
1144 : // set used to gather nodes collected by value custom sorted by locations
1145 : SortedLocationNodes match_nodes(
1146 0 : [](const wasp::HITNodeView & l, const wasp::HITNodeView & r)
1147 : {
1148 19 : const std::string & l_file = l.node_pool()->stream_name();
1149 19 : const std::string & r_file = r.node_pool()->stream_name();
1150 47 : return (l_file < r_file || (l_file == r_file && l.line() < r.line()) ||
1151 47 : (l_file == r_file && l.line() == r.line() && l.column() < r.column()));
1152 1 : });
1153 :
1154 : // walk input recursively and gather all nodes that match value and types
1155 1 : getNodesByValueAndTypes(match_nodes, view_root, block_name, target_types);
1156 :
1157 : // return without adding any references if no nodes match value and types
1158 1 : if (match_nodes.empty())
1159 0 : return true;
1160 :
1161 : // add request context node to set if declaration inclusion was specified
1162 1 : if (include_declaration && request_context.parent().child_count_by_name("decl"))
1163 1 : match_nodes.insert(request_context.parent().first_child_by_name("decl"));
1164 :
1165 : // add locations to references list with nodes that match value and types
1166 1 : return addLocationNodesToList(referencesLocations, match_nodes);
1167 1 : }
1168 :
1169 : void
1170 26 : MooseServer::getNodesByValueAndTypes(SortedLocationNodes & match_nodes,
1171 : wasp::HITNodeView view_parent,
1172 : const std::string & target_value,
1173 : const std::set<std::string> & target_types)
1174 : {
1175 : // walk over children of context to gather nodes matching value and types
1176 260 : for (const auto & view_child : view_parent)
1177 : {
1178 : // check for parameter type match if node is value matching target data
1179 117 : if (view_child.type() == wasp::VALUE && view_child.to_string() == target_value)
1180 : {
1181 : // get object context path and object type value of node if it exists
1182 6 : wasp::HITNodeView object_context = view_child;
1183 18 : while (object_context.type() != wasp::OBJECT && object_context.has_parent())
1184 12 : object_context = object_context.parent();
1185 6 : const std::string object_path = object_context.path();
1186 6 : wasp::HITNodeView type_node = object_context.first_child_by_name("type");
1187 : const std::string object_type =
1188 6 : type_node.is_null() ? "" : wasp::strip_quotes(hit::extractValue(type_node.data()));
1189 :
1190 : // gather global, action, and object parameters for context of object
1191 6 : InputParameters valid_params = emptyInputParameters();
1192 6 : std::set<std::string> obj_act_tasks;
1193 6 : getAllValidParameters(valid_params, object_path, object_type, obj_act_tasks);
1194 :
1195 : // get name from parent of current value node which is parameter node
1196 6 : std::string param_name = view_child.has_parent() ? view_child.parent().name() : "";
1197 :
1198 : // get type of parameter and prepare string to check target set match
1199 6 : std::string dirty_type = valid_params.type(param_name);
1200 6 : std::string clean_type = MooseUtils::prettyCppType(dirty_type);
1201 6 : pcrecpp::RE(".+<([A-Za-z0-9_' ':]*)>.*").GlobalReplace("\\1", &clean_type);
1202 :
1203 : // add input node to collection if its type is also in set of targets
1204 6 : if (target_types.count(clean_type))
1205 5 : match_nodes.insert(view_child);
1206 6 : }
1207 :
1208 : // recurse deeper into input to search for matches if node has children
1209 117 : if (!view_child.is_leaf())
1210 25 : getNodesByValueAndTypes(match_nodes, view_child, target_value, target_types);
1211 143 : }
1212 26 : }
1213 :
1214 : bool
1215 1 : MooseServer::gatherDocumentFormattingTextEdits(wasp::DataArray & formattingTextEdits,
1216 : int tab_size,
1217 : bool /* insert_spaces */)
1218 : {
1219 : // strip scheme prefix from document uri if it exists for parse file path
1220 1 : std::string parse_file_path = document_path;
1221 1 : pcrecpp::RE("(.*://)(.*)").Replace("\\2", &parse_file_path);
1222 :
1223 : // input check expanded any brace expressions in cached tree so reprocess
1224 1 : std::stringstream input_errors, input_stream(getDocumentText());
1225 1 : wasp::DefaultHITInterpreter interpreter(input_errors);
1226 :
1227 : // return without adding any formatting text edits if input parsing fails
1228 1 : if (!interpreter.parseStream(input_stream, parse_file_path))
1229 0 : return true;
1230 :
1231 : // return without adding any formatting text edits if parser root is null
1232 1 : if (interpreter.root().is_null())
1233 0 : return true;
1234 :
1235 : // get input root node line and column range to represent entire document
1236 1 : wasp::HITNodeView view_root = interpreter.root();
1237 1 : int document_start_line = view_root.line() - 1;
1238 1 : int document_start_char = view_root.column() - 1;
1239 1 : int document_last_line = view_root.last_line() - 1;
1240 1 : int document_last_char = view_root.last_column();
1241 :
1242 : // set number of spaces for indentation and build formatted document text
1243 1 : _formatting_tab_size = tab_size;
1244 1 : std::size_t starting_line = view_root.line() - 1;
1245 1 : std::string document_format = formatDocument(view_root, starting_line, 0);
1246 :
1247 : // add formatted text with whole line and column range to formatting list
1248 1 : formattingTextEdits.push_back(wasp::DataObject());
1249 1 : wasp::DataObject * item = formattingTextEdits.back().to_object();
1250 1 : bool pass = wasp::lsp::buildTextEditObject(*item,
1251 : errors,
1252 : document_start_line,
1253 : document_start_char,
1254 : document_last_line,
1255 : document_last_char,
1256 : document_format);
1257 1 : return pass;
1258 1 : }
1259 :
1260 : std::string
1261 11 : MooseServer::formatDocument(wasp::HITNodeView parent, std::size_t & prev_line, std::size_t level)
1262 : {
1263 : // build string of newline and indentation spaces from level and tab size
1264 11 : std::string newline_indent = "\n" + std::string(level * _formatting_tab_size, ' ');
1265 :
1266 : // lambda to format include data by replacing consecutive spaces with one
1267 1 : auto collapse_spaces = [](std::string string_copy)
1268 : {
1269 1 : pcrecpp::RE("\\s+").Replace(" ", &string_copy);
1270 1 : return string_copy;
1271 : };
1272 :
1273 : // formatted string that will be built recursively by appending each call
1274 11 : std::string format_string;
1275 :
1276 : // walk over all children of this node context and build formatted string
1277 90 : for (const auto i : make_range(parent.child_count()))
1278 : {
1279 : // walk must be index based to catch file include and skip its children
1280 79 : wasp::HITNodeView child = parent.child_at(i);
1281 :
1282 : // add blank line if necessary after previous line and before this line
1283 79 : std::string blank = child.line() > prev_line + 1 ? "\n" : "";
1284 :
1285 : // format include directive with indentation and collapse extra spacing
1286 79 : if (child.type() == wasp::FILE)
1287 1 : format_string += blank + newline_indent + MooseUtils::trim(collapse_spaces(child.data()));
1288 :
1289 : // format normal comment with indentation and inline comment with space
1290 78 : else if (child.type() == wasp::COMMENT)
1291 10 : format_string += (child.line() == prev_line ? " " : blank + newline_indent) +
1292 15 : MooseUtils::trim(child.data());
1293 :
1294 : // format object recursively with indentation and without legacy syntax
1295 73 : else if (child.type() == wasp::OBJECT)
1296 20 : format_string += blank + newline_indent + "[" + child.name() + "]" +
1297 30 : formatDocument(child, prev_line, level + 1) + newline_indent + "[]";
1298 :
1299 : // format keyed value with indentation and calling reusable hit methods
1300 63 : else if (child.type() == wasp::KEYED_VALUE || child.type() == wasp::ARRAY)
1301 : {
1302 17 : const std::string prefix = newline_indent + child.name() + " = ";
1303 :
1304 17 : const std::string render_val = hit::extractValue(child.data());
1305 17 : std::size_t val_column = child.child_count() > 2 ? child.child_at(2).column() : 0;
1306 17 : std::size_t prefix_len = prefix.size() - 1;
1307 :
1308 17 : format_string += blank + prefix + hit::formatValue(render_val, val_column, prefix_len);
1309 17 : }
1310 :
1311 : // set previous line reference used for blank lines and inline comments
1312 79 : prev_line = child.last_line();
1313 79 : }
1314 :
1315 : // remove leading newline if this is level zero returning entire document
1316 22 : return level != 0 ? format_string : format_string.substr(1);
1317 11 : }
1318 :
1319 : bool
1320 2 : MooseServer::gatherDocumentSymbols(wasp::DataArray & documentSymbols)
1321 : {
1322 : // return prior to starting document symbol tree when parser root is null
1323 2 : if (!rootIsValid())
1324 0 : return true;
1325 :
1326 2 : wasp::HITNodeView view_root = getRoot().getNodeView();
1327 :
1328 2 : bool pass = true;
1329 :
1330 : // walk over all children of root node context and build document symbols
1331 12 : for (const auto i : make_range(view_root.child_count()))
1332 : {
1333 : // walk must be index based to catch file include and skip its children
1334 10 : wasp::HITNodeView view_child = view_root.child_at(i);
1335 :
1336 : // set up name, zero based line and column range, kind, and detail info
1337 10 : std::string name = view_child.name();
1338 10 : int line = view_child.line() - 1;
1339 10 : int column = view_child.column() - 1;
1340 10 : int last_line = view_child.last_line() - 1;
1341 10 : int last_column = view_child.last_column();
1342 10 : int symbol_kind = getDocumentSymbolKind(view_child);
1343 : std::string detail =
1344 20 : !view_child.first_child_by_name("type").is_null()
1345 14 : ? wasp::strip_quotes(hit::extractValue(view_child.first_child_by_name("type").data()))
1346 14 : : "";
1347 :
1348 : // build document symbol object from node child info and push to array
1349 10 : documentSymbols.push_back(wasp::DataObject());
1350 10 : wasp::DataObject * data_child = documentSymbols.back().to_object();
1351 10 : pass &= wasp::lsp::buildDocumentSymbolObject(*data_child,
1352 : errors,
1353 20 : (name.empty() ? "void" : name),
1354 : detail,
1355 : symbol_kind,
1356 : false,
1357 : line,
1358 : column,
1359 : last_line,
1360 : last_column,
1361 : line,
1362 : column,
1363 : last_line,
1364 : last_column);
1365 :
1366 : // call method to recursively fill document symbols for each node child
1367 10 : pass &= traverseParseTreeAndFillSymbols(view_child, *data_child);
1368 10 : }
1369 :
1370 2 : return pass;
1371 2 : }
1372 :
1373 : bool
1374 176 : MooseServer::traverseParseTreeAndFillSymbols(wasp::HITNodeView view_parent,
1375 : wasp::DataObject & data_parent)
1376 : {
1377 : // return without adding any children if parent node is file include type
1378 176 : if (wasp::is_nested_file(view_parent))
1379 0 : return true;
1380 :
1381 176 : bool pass = true;
1382 :
1383 : // walk over all children of this node context and build document symbols
1384 342 : for (const auto i : make_range(view_parent.child_count()))
1385 : {
1386 : // walk must be index based to catch file include and skip its children
1387 166 : wasp::HITNodeView view_child = view_parent.child_at(i);
1388 :
1389 : // set up name, zero based line and column range, kind, and detail info
1390 166 : std::string name = view_child.name();
1391 166 : int line = view_child.line() - 1;
1392 166 : int column = view_child.column() - 1;
1393 166 : int last_line = view_child.last_line() - 1;
1394 166 : int last_column = view_child.last_column();
1395 166 : int symbol_kind = getDocumentSymbolKind(view_child);
1396 : std::string detail =
1397 332 : !view_child.first_child_by_name("type").is_null()
1398 168 : ? wasp::strip_quotes(hit::extractValue(view_child.first_child_by_name("type").data()))
1399 168 : : "";
1400 :
1401 : // build document symbol object from node child info and push to array
1402 166 : wasp::DataObject & data_child = wasp::lsp::addDocumentSymbolChild(data_parent);
1403 166 : pass &= wasp::lsp::buildDocumentSymbolObject(data_child,
1404 : errors,
1405 332 : (name.empty() ? "void" : name),
1406 : detail,
1407 : symbol_kind,
1408 : false,
1409 : line,
1410 : column,
1411 : last_line,
1412 : last_column,
1413 : line,
1414 : column,
1415 : last_line,
1416 : last_column);
1417 :
1418 : // call method to recursively fill document symbols for each node child
1419 166 : pass &= traverseParseTreeAndFillSymbols(view_child, data_child);
1420 166 : }
1421 :
1422 176 : return pass;
1423 : }
1424 :
1425 : int
1426 65 : MooseServer::getCompletionItemKind(const InputParameters & valid_params,
1427 : const std::string & param_name,
1428 : const std::string & clean_type,
1429 : bool is_param)
1430 : {
1431 : // set up completion item kind value that client may use for icon in list
1432 65 : auto associated_types = getCheckApp()->syntax().getAssociatedTypes();
1433 66 : if (is_param && valid_params.isParamRequired(param_name) &&
1434 1 : !valid_params.isParamValid(param_name))
1435 1 : return wasp::lsp::m_comp_kind_event;
1436 64 : else if (param_name == "active" || param_name == "inactive")
1437 5 : return wasp::lsp::m_comp_kind_class;
1438 59 : else if (clean_type == "bool")
1439 16 : return wasp::lsp::m_comp_kind_interface;
1440 43 : else if (valid_params.have_parameter<MooseEnum>(param_name) ||
1441 37 : valid_params.have_parameter<MultiMooseEnum>(param_name) ||
1442 116 : valid_params.have_parameter<ExecFlagEnum>(param_name) ||
1443 36 : valid_params.have_parameter<std::vector<MooseEnum>>(param_name))
1444 7 : return is_param ? wasp::lsp::m_comp_kind_enum : wasp::lsp::m_comp_kind_enum_member;
1445 36 : else if (param_name == "type")
1446 2 : return wasp::lsp::m_comp_kind_type_param;
1447 34 : else if (std::find_if(associated_types.begin(),
1448 : associated_types.end(),
1449 860 : [&](const auto & entry)
1450 928 : { return entry.second == clean_type; }) != associated_types.end())
1451 2 : return wasp::lsp::m_comp_kind_reference;
1452 : else
1453 32 : return is_param ? wasp::lsp::m_comp_kind_keyword : wasp::lsp::m_comp_kind_value;
1454 65 : }
1455 :
1456 : int
1457 176 : MooseServer::getDocumentSymbolKind(wasp::HITNodeView symbol_node)
1458 : {
1459 : // lambdas that check if parameter is a boolean or number for symbol kind
1460 14 : auto is_boolean = [](wasp::HITNodeView symbol_node)
1461 : {
1462 : bool convert;
1463 14 : std::istringstream iss(MooseUtils::toLower(symbol_node.last_as_string()));
1464 28 : return (iss >> std::boolalpha >> convert && !iss.fail());
1465 14 : };
1466 12 : auto is_number = [](wasp::HITNodeView symbol_node)
1467 : {
1468 : double convert;
1469 12 : std::istringstream iss(symbol_node.last_as_string());
1470 24 : return (iss >> convert && iss.eof());
1471 12 : };
1472 :
1473 : // set up document symbol kind value that client may use for outline icon
1474 176 : if (symbol_node.type() == wasp::OBJECT)
1475 16 : return wasp::lsp::m_symbol_kind_struct;
1476 160 : else if (symbol_node.type() == wasp::FILE)
1477 0 : return wasp::lsp::m_symbol_kind_file;
1478 160 : else if (symbol_node.type() == wasp::ARRAY)
1479 2 : return wasp::lsp::m_symbol_kind_array;
1480 158 : else if (symbol_node.type() == wasp::KEYED_VALUE && symbol_node.name() == std::string("type"))
1481 6 : return wasp::lsp::m_symbol_kind_type_param;
1482 152 : else if (symbol_node.type() == wasp::KEYED_VALUE && is_boolean(symbol_node))
1483 2 : return wasp::lsp::m_symbol_kind_boolean;
1484 150 : else if (symbol_node.type() == wasp::KEYED_VALUE && is_number(symbol_node))
1485 2 : return wasp::lsp::m_symbol_kind_number;
1486 148 : else if (symbol_node.type() == wasp::KEYED_VALUE)
1487 10 : return wasp::lsp::m_symbol_kind_key;
1488 138 : else if (symbol_node.type() == wasp::VALUE)
1489 26 : return wasp::lsp::m_symbol_kind_string;
1490 : else
1491 112 : return wasp::lsp::m_symbol_kind_property;
1492 : }
1493 :
1494 : std::string
1495 1125 : MooseServer::getRequiredParamsText(const std::string & subblock_path,
1496 : const std::string & subblock_type,
1497 : const std::set<std::string> & existing_params,
1498 : const std::string & indent_spaces)
1499 : {
1500 : // gather global, action, and object parameters in request object context
1501 1125 : InputParameters valid_params = emptyInputParameters();
1502 1125 : std::set<std::string> obj_act_tasks;
1503 1125 : getAllValidParameters(valid_params, subblock_path, subblock_type, obj_act_tasks);
1504 :
1505 : // walk over collection of all parameters and build text of ones required
1506 1125 : std::string required_param_text;
1507 15965 : for (const auto & valid_params_iter : valid_params)
1508 : {
1509 : // skip parameter if deprecated, private, defaulted, optional, existing
1510 14840 : const std::string & param_name = valid_params_iter.first;
1511 29656 : if (!valid_params.isParamDeprecated(param_name) && !valid_params.isPrivate(param_name) &&
1512 30722 : !valid_params.isParamValid(param_name) && valid_params.isParamRequired(param_name) &&
1513 1066 : !existing_params.count(param_name))
1514 12 : required_param_text += "\n" + indent_spaces + param_name + " = ";
1515 : }
1516 :
1517 2250 : return required_param_text;
1518 1125 : }
1519 :
1520 : bool
1521 24 : MooseServer::rootIsValid() const
1522 : {
1523 72 : return getCheckApp() && getCheckApp()->builder().root() &&
1524 72 : !getCheckApp()->builder().root()->getNodeView().is_null();
1525 : }
1526 :
1527 : hit::Node &
1528 27 : MooseServer::getRoot()
1529 : {
1530 : mooseAssert(rootIsValid(), "Not valid");
1531 27 : return *getCheckApp()->builder().root();
1532 : }
1533 :
1534 : std::shared_ptr<MooseApp>
1535 4807 : MooseServer::getCheckApp() const
1536 : {
1537 : mooseAssert(_check_apps.count(document_path), "No check app for path");
1538 4807 : return _check_apps.at(document_path);
1539 : }
1540 :
1541 : const std::string &
1542 1 : MooseServer::getDocumentText() const
1543 : {
1544 : mooseAssert(_path_to_text.count(document_path), "No text for path");
1545 1 : return _path_to_text.at(document_path);
1546 : }
|