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