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 "Parser.h"
25 : #include "FEProblemBase.h"
26 : #include "PiecewiseBase.h"
27 : #include "Distribution.h"
28 : #include "ActionWarehouse.h"
29 : #include "MaterialPropertyRegistry.h"
30 : #include "MaterialBase.h"
31 : #include "MaterialWarehouse.h"
32 : #include "MooseObjectWarehouse.h"
33 : #include "OutputWarehouse.h"
34 : #include "Output.h"
35 : #include "UserObject.h"
36 : #include "TheWarehouse.h"
37 : #include "NonlinearSystemBase.h"
38 : #include "AuxiliarySystem.h"
39 : #include "pcrecpp.h"
40 : #include "hit/hit.h"
41 : #include "wasphit/HITInterpreter.h"
42 : #include "waspcore/utils.h"
43 : #include "waspplot/CustomPlotSerialization.h"
44 : #include <algorithm>
45 : #include <vector>
46 : #include <sstream>
47 : #include <iostream>
48 : #include <functional>
49 : #include <filesystem>
50 :
51 2 : MooseServer::MooseServer(MooseApp & moose_app)
52 2 : : _moose_app(moose_app),
53 2 : _connection(std::make_shared<wasp::lsp::IOStreamConnection>(this)),
54 2 : _formatting_tab_size(0),
55 2 : _dist_plot_num_points(200),
56 4 : _dist_plot_quantile_bound(1e-3)
57 : {
58 : // add all implemented server capabilities to notify client in initialize
59 2 : enableFullSync();
60 2 : enableSymbols();
61 2 : enableCompletion();
62 2 : enableDefinition();
63 2 : enableReferences();
64 2 : enableFormatting();
65 2 : enableHover();
66 4 : enableExtension("plotting");
67 4 : enableExtension("watcherRegistration");
68 2 : }
69 :
70 : bool
71 30 : MooseServer::parseDocumentForDiagnostics(wasp::DataArray & diagnosticsList)
72 : {
73 : // Reset old parsers and applications if we have them
74 30 : if (const auto it = _check_state.find(document_path); it != _check_state.end())
75 28 : _check_state.erase(it);
76 :
77 : // strip prefix from document uri if it exists to get parse file path
78 30 : std::string parse_file_path = document_path;
79 30 : pcrecpp::RE("(.*://)(.*)").Replace("\\2", &parse_file_path);
80 :
81 30 : bool pass = true;
82 :
83 : // Adds a single diagnostic
84 76 : const auto diagnostic = [this, &diagnosticsList, &pass](const std::string & message,
85 : const int start_line,
86 : const int start_column,
87 : const std::optional<int> end_line = {},
88 : const std::optional<int> end_column = {})
89 : {
90 76 : diagnosticsList.push_back(wasp::DataObject());
91 76 : auto & diagnostic = *diagnosticsList.back().to_object();
92 508 : pass &= wasp::lsp::buildDiagnosticObject(diagnostic,
93 : errors,
94 : start_line,
95 : start_column,
96 140 : end_line ? *end_line : start_line,
97 140 : end_column ? *end_column : start_column,
98 : 1,
99 : "moose_srv",
100 : "check_inp",
101 : message);
102 76 : };
103 :
104 : // Adds a diagnostic on line zero
105 2 : const auto zero_line_diagnostic = [&diagnostic](const std::string & message)
106 2 : { diagnostic(message, 0, 0); };
107 :
108 : // Adds a diagnostic from a hit node, if the context of the hit node is valid
109 12 : const auto hit_node_diagnostic = [&zero_line_diagnostic, &diagnostic, &parse_file_path](
110 : const hit::Node * const node, const std::string & message)
111 : {
112 : // No node, root node, wrong file, or no line information: line zero diagnostic
113 22 : if (!node || node->isRoot() || node->filename() != parse_file_path || !node->line() ||
114 10 : !node->column())
115 2 : zero_line_diagnostic(message);
116 : // Have file and line context, diagnostic there
117 : else
118 10 : diagnostic(message, node->line() - 1, node->column() - 1);
119 12 : };
120 :
121 : // Adds a diagnostic from a hit::ErrorMessage if the context is valid
122 : const auto hit_error_message_diagnostic =
123 62 : [&diagnostic, &zero_line_diagnostic, &parse_file_path](const hit::ErrorMessage & err)
124 : {
125 : // Has a filename
126 62 : if (err.filename)
127 : {
128 : // For the open file
129 62 : if (*err.filename == parse_file_path)
130 : {
131 : // Has line information that is valid
132 124 : if (err.lineinfo && err.lineinfo->start_line && err.lineinfo->start_column &&
133 124 : err.lineinfo->end_line && err.lineinfo->end_column)
134 : {
135 0 : diagnostic(err.message,
136 62 : err.lineinfo->start_line - 1,
137 62 : err.lineinfo->start_column - 1,
138 62 : err.lineinfo->end_line - 1,
139 62 : err.lineinfo->end_column - 1);
140 62 : return;
141 : }
142 : }
143 : // Has a file but not for this file, no diagnostic
144 : else
145 0 : return;
146 : }
147 :
148 : // Don't have a filename, or have a filename that is this file without line info
149 0 : zero_line_diagnostic(err.prefixed_message);
150 30 : };
151 :
152 : // Runs a try catch loop with the given action, collecting diagnostics
153 : // from the known exceptions; returns a bool that is true if we executed
154 : // without throwing anything
155 88 : const auto try_catch = [&hit_error_message_diagnostic,
156 : &hit_node_diagnostic,
157 : &zero_line_diagnostic](const auto & action) -> bool
158 : {
159 88 : Moose::ScopedThrowOnError scoped_throw_on_error;
160 :
161 : try
162 : {
163 88 : action();
164 : }
165 : // Will be thrown from the Parser while building the tree or
166 : // by the builder while building the input parameters
167 40 : catch (Parser::Error & err)
168 : {
169 76 : for (const auto & error_message : err.error_messages)
170 62 : hit_error_message_diagnostic(error_message);
171 : }
172 : // Will be thrown by mooseError() when _throw_on_error is set
173 : // to true, hopefully with hit node context
174 12 : catch (MooseRuntimeError & err)
175 : {
176 36 : hit_node_diagnostic(err.getNode(), err.what());
177 : }
178 : // General catch all for everything else without context
179 0 : catch (std::exception & err)
180 : {
181 0 : zero_line_diagnostic(err.what());
182 : }
183 :
184 : // continue to build app if parsing fails and run app if building fails
185 : // so that problem is there for plotting and warehouse based completion
186 88 : return true;
187 118 : };
188 :
189 : // Setup command line (needed by the Parser)
190 30 : auto command_line = std::make_unique<CommandLine>(_moose_app.commandLine()->getArguments());
191 90 : if (command_line->hasArgument("--language-server"))
192 0 : command_line->removeArgument("--language-server");
193 60 : command_line->addArgument("--check-input");
194 60 : command_line->addArgument("--error-unused");
195 60 : command_line->addArgument("--error");
196 60 : command_line->addArgument("--color=off");
197 60 : command_line->addArgument("--disable-perf-graph-live");
198 30 : command_line->parse();
199 :
200 : // Setup the parser that will be used in the app
201 30 : auto parser = std::make_shared<Parser>(parse_file_path, document_text);
202 : mooseAssert(parser->getInputFileNames()[0] == parse_file_path, "Should be consistent");
203 30 : parser->setAppType(_moose_app.type());
204 30 : parser->setCommandLineParams(command_line->buildHitParams());
205 30 : parser->setThrowOnError(true);
206 :
207 : // Try to parse the document
208 60 : const bool parse_success = try_catch([&parser]() { parser->parse(); });
209 : // If the Parser has a valid root, store it because we can use it
210 : // in the future (hover text etc with a partially complete document)
211 30 : CheckState * state = nullptr;
212 30 : if (auto parser_root_ptr = parser->queryRoot();
213 30 : parser_root_ptr && !parser_root_ptr->getNodeView().is_null())
214 : {
215 30 : auto it_inserted_pair = _check_state.emplace(document_path, parser);
216 : mooseAssert(it_inserted_pair.second, "Should not already exist");
217 30 : state = &it_inserted_pair.first->second;
218 : }
219 : // We have no root or an empty document, nothing else to do
220 : else
221 0 : return true;
222 :
223 : // Failed to parse, don't bother building the app. But... we might
224 : // have a root node at least!
225 30 : if (!parse_success)
226 0 : return pass;
227 :
228 : // Try to instantiate the application
229 30 : std::unique_ptr<MooseApp> app = nullptr;
230 30 : const auto do_build_app = [this, &parse_file_path, &diagnostic, &parser, &command_line, &app]()
231 : {
232 : // get app type from parser which is Application block type if provided
233 30 : const std::string & app_type = parser->getAppType();
234 :
235 : // error if Application type specified in input has not been registered
236 30 : if (!AppFactory::instance().isRegistered(app_type))
237 : {
238 : // get line and column range of Application/type/value for diagnostic
239 2 : int error_line_beg = 0, error_char_beg = 0, error_line_end = 0, error_char_end = 0;
240 4 : if (auto app_type_field = parser->getRoot().find("Application/type");
241 2 : app_type_field && app_type_field->filename() == parse_file_path)
242 : {
243 6 : auto app_type_value = app_type_field->getNodeView().first_child_by_name("value");
244 2 : if (!app_type_value.is_null())
245 : {
246 2 : error_line_beg = app_type_value.line() - 1;
247 2 : error_char_beg = app_type_value.column() - 1;
248 2 : error_line_end = app_type_value.last_line() - 1;
249 2 : error_char_end = app_type_value.last_column();
250 : }
251 2 : }
252 :
253 : // build error message string with available app types for diagnostic
254 2 : std::vector<std::string> app_types;
255 6 : for (const auto & apps_iter : AppFactory::instance().registeredObjects())
256 4 : app_types.push_back(apps_iter.first);
257 4 : const auto message = "'" + app_type + "' is not a registered application type. Registered" +
258 6 : " application types are [" + MooseUtils::join(app_types, ", ") + "].";
259 :
260 : // add diagnostic to get reported for Application/type not registered
261 2 : diagnostic(message, error_line_beg, error_char_beg, error_line_end, error_char_end);
262 2 : return;
263 2 : }
264 :
265 : // set up options from app type parameters with parser and command line
266 28 : InputParameters app_params = AppFactory::instance().getValidParams(app_type);
267 56 : app_params.set<std::shared_ptr<Parser>>("_parser") = parser;
268 56 : app_params.set<std::shared_ptr<CommandLine>>("_command_line") = std::move(command_line);
269 :
270 : // create application to use for diagnostic checks and input assistance
271 84 : app = AppFactory::instance().create(
272 84 : app_type, AppFactory::main_app_name, app_params, _moose_app.getCommunicator()->get());
273 28 : };
274 30 : if (!try_catch(do_build_app))
275 : {
276 0 : if (app)
277 0 : app.reset();
278 0 : return pass;
279 : }
280 :
281 : // do not attempt to run application if it is null after build from error
282 30 : if (app)
283 : {
284 : // store application when it is valid and then run it to invoke builder
285 28 : state->app = std::move(app);
286 28 : const auto do_run_app = [this]() { getCheckApp().run(); };
287 28 : if (!try_catch(do_run_app))
288 0 : state->app.reset();
289 : }
290 :
291 : // add all resource files of document that will be registered with client
292 30 : addResourcesForDocument();
293 :
294 30 : return pass;
295 30 : }
296 :
297 : void
298 30 : MooseServer::addResourcesForDocument()
299 : {
300 : // return without any resources added for document if parser root is null
301 30 : auto root_ptr = queryRoot();
302 30 : if (!root_ptr)
303 0 : return;
304 30 : auto & root = *root_ptr;
305 :
306 : // return without document resources added if client does not watch files
307 30 : if (!client_watcher_support)
308 0 : return;
309 :
310 : // get input parse tree root node to be used for gathering resource files
311 30 : wasp::HITNodeView view_root = root.getNodeView();
312 30 : std::set<std::string> include_paths, filename_vals, resource_uris;
313 :
314 : // gather paths of include inputs and add to resource uris if files exist
315 30 : view_root.node_pool()->descendant_include_paths(include_paths);
316 48 : for (const auto & include_path : include_paths)
317 : {
318 18 : auto normalized = std::filesystem::path(include_path).lexically_normal().string();
319 18 : if (MooseUtils::checkFileReadable(normalized, false, false, false))
320 18 : resource_uris.insert(wasp::lsp::prefixUriScheme(normalized));
321 18 : }
322 :
323 : // gather paths of FileName types and add to resource uris if files exist
324 30 : getFileNameTypeValues(filename_vals, view_root);
325 46 : for (const auto & filename_val : filename_vals)
326 : {
327 16 : auto input_path = wasp::lsp::removeUriScheme(document_path);
328 16 : auto input_base = std::filesystem::path(input_path).parent_path();
329 16 : auto fname_path = std::filesystem::path(filename_val);
330 16 : auto fname_absl = fname_path.is_absolute() ? fname_path : (input_base / fname_path);
331 16 : auto normalized = fname_absl.lexically_normal().string();
332 16 : if (MooseUtils::checkFileReadable(normalized, false, false, false))
333 16 : resource_uris.insert(wasp::lsp::prefixUriScheme(normalized));
334 16 : }
335 :
336 : // add collection of all gathered paths as resources for current document
337 30 : setResourcesForBase(document_path, resource_uris);
338 30 : }
339 :
340 : void
341 382 : MooseServer::getFileNameTypeValues(std::set<std::string> & filename_vals, wasp::HITNodeView parent)
342 : {
343 : // cache set of FileName types for parameters that contain resource files
344 : static const std::set<std::string> filename_types = {
345 382 : "FileName", "FileNameNoExtension", "MeshFileName", "MatrixFileName"};
346 :
347 : // walk over children in tree and skip any nodes that are not object type
348 2596 : for (const auto & child : parent)
349 : {
350 2214 : if (child.type() == wasp::OBJECT)
351 : {
352 : // get object context path and object type value of node if it exists
353 352 : wasp::HITNodeView object_node = child;
354 352 : const std::string object_path = object_node.path();
355 352 : wasp::HITNodeView type_node = object_node.first_child_by_name("type");
356 : const std::string object_type =
357 536 : type_node.is_null() ? "" : wasp::strip_quotes(hit::extractValue(type_node.data()));
358 :
359 : // gather global, action, and object parameters for context of object
360 352 : InputParameters valid_params = emptyInputParameters();
361 352 : std::set<std::string> obj_act_tasks;
362 352 : getAllValidParameters(valid_params, object_path, object_type, obj_act_tasks);
363 :
364 : // walk over children and skip any nodes that are not parameter types
365 2368 : for (const auto & child : object_node)
366 : {
367 2016 : if (child.type() == wasp::KEYED_VALUE || child.type() == wasp::ARRAY)
368 : {
369 : // get name of node to use for finding in set of valid parameters
370 454 : wasp::HITNodeView param_node = child;
371 454 : std::string param_name = param_node.name();
372 :
373 : // add parameter values to collection if valid with FileName type
374 454 : if (valid_params.getParametersList().count(param_name))
375 : {
376 : // get parameter type and prepare to check if in FileName types
377 444 : std::string dirty_type = valid_params.type(param_name);
378 444 : std::string clean_type = MooseUtils::prettyCppType(dirty_type);
379 444 : pcrecpp::RE(".+<([A-Za-z0-9_' ':]*)>.*").GlobalReplace("\\1", &clean_type);
380 :
381 : // add parameter values to set if type is one of FileName types
382 444 : if (filename_types.count(clean_type))
383 64 : for (const auto & child : param_node)
384 48 : if (child.type() == wasp::VALUE)
385 64 : filename_vals.insert(child.to_string());
386 444 : }
387 454 : }
388 2368 : }
389 :
390 : // recurse deeper into input and continue search since node is object
391 352 : getFileNameTypeValues(filename_vals, object_node);
392 352 : }
393 2596 : }
394 382 : }
395 :
396 : bool
397 44 : MooseServer::gatherDocumentCompletionItems(wasp::DataArray & completionItems,
398 : bool & is_incomplete,
399 : int line,
400 : int character)
401 : {
402 44 : auto root_ptr = queryRoot();
403 :
404 : // add only root level blocks to completion list when parser root is null
405 44 : if (!root_ptr)
406 0 : return addSubblocksToList(completionItems, "/", line, character, line, character, "", false);
407 44 : auto & root = *root_ptr;
408 :
409 : // lambdas that will be used for checking completion request context type
410 132 : auto is_request_in_open_block = [](wasp::HITNodeView request_context) {
411 132 : return request_context.type() == wasp::OBJECT || request_context.type() == wasp::DOCUMENT_ROOT;
412 : };
413 80 : auto is_request_on_param_decl = [](wasp::HITNodeView request_context)
414 : {
415 96 : return request_context.type() == wasp::DECL && request_context.has_parent() &&
416 96 : (request_context.parent().type() == wasp::KEYED_VALUE ||
417 168 : request_context.parent().type() == wasp::ARRAY);
418 : };
419 114 : auto is_request_on_block_decl = [](wasp::HITNodeView request_context)
420 : {
421 142 : return request_context.type() == wasp::DECL && request_context.has_parent() &&
422 142 : request_context.parent().type() == wasp::OBJECT;
423 : };
424 :
425 : // get document tree root used to find node under request line and column
426 44 : wasp::HITNodeView view_root = root.getNodeView();
427 44 : wasp::HITNodeView request_context;
428 :
429 : // find node under request location if it is not past all defined content
430 46 : if (line + 1 < (int)view_root.last_line() ||
431 2 : (line + 1 == (int)view_root.last_line() && character <= (int)view_root.last_column()))
432 44 : request_context = wasp::findNodeUnderLineColumn(view_root, line + 1, character + 1);
433 :
434 : // otherwise find last node in document with last line and column of tree
435 : else
436 : {
437 : request_context =
438 0 : wasp::findNodeUnderLineColumn(view_root, view_root.last_line(), view_root.last_column());
439 :
440 : // change context to be parent block or grandparent if block terminator
441 0 : wasp::HITNodeView object_context = request_context;
442 0 : while (object_context.type() != wasp::OBJECT && object_context.has_parent())
443 0 : object_context = object_context.parent();
444 0 : if (request_context.type() == wasp::OBJECT_TERM && object_context.has_parent())
445 0 : object_context = object_context.parent();
446 0 : request_context = object_context;
447 0 : }
448 :
449 : // change context to equal sign if it is preceding node and in open block
450 44 : if (is_request_in_open_block(request_context))
451 : {
452 20 : wasp::HITNodeView backup_context = request_context;
453 36 : for (int backup_char = character; backup_context == request_context && --backup_char > 0;)
454 16 : backup_context = wasp::findNodeUnderLineColumn(request_context, line + 1, backup_char + 1);
455 20 : if (backup_context.type() == wasp::ASSIGN || backup_context.type() == wasp::OVERRIDE_ASSIGN)
456 16 : request_context = backup_context;
457 20 : }
458 :
459 : // use request context type to set up replacement range and prefix filter
460 44 : int replace_line_beg = line;
461 44 : int replace_char_beg = character;
462 44 : int replace_line_end = line;
463 44 : int replace_char_end = character;
464 44 : std::string filtering_prefix;
465 44 : if (request_context.type() == wasp::DECL || request_context.type() == wasp::VALUE)
466 : {
467 : // completion on existing block name, parameter name, or value replaces
468 22 : replace_line_beg = request_context.line() - 1;
469 22 : replace_char_beg = request_context.column() - 1;
470 22 : replace_line_end = request_context.last_line() - 1;
471 22 : replace_char_end = request_context.last_column();
472 22 : filtering_prefix = request_context.data();
473 :
474 : // empty block name columns are same as bracket so bump replace columns
475 22 : if (is_request_on_block_decl(request_context) && filtering_prefix.empty())
476 : {
477 2 : replace_char_beg++;
478 2 : replace_char_end++;
479 : }
480 : }
481 :
482 : // get name of request context direct parent node so it can be used later
483 44 : const auto & parent_name = request_context.has_parent() ? request_context.parent().name() : "";
484 :
485 : // get object context and value of type parameter for request if provided
486 44 : wasp::HITNodeView object_context = request_context;
487 120 : while (object_context.type() != wasp::OBJECT && object_context.has_parent())
488 76 : object_context = object_context.parent();
489 44 : if (is_request_on_block_decl(request_context))
490 4 : object_context = object_context.parent();
491 44 : const std::string & object_path = object_context.path();
492 44 : wasp::HITNodeView type_node = object_context.first_child_by_name("type");
493 : const std::string & object_type =
494 54 : type_node.is_null() ? "" : wasp::strip_quotes(hit::extractValue(type_node.data()));
495 :
496 : // get set of all parameter and subblock names already specified in input
497 44 : std::set<std::string> existing_params, existing_subblocks;
498 44 : getExistingInput(object_context, existing_params, existing_subblocks);
499 :
500 : // set used to gather all parameters valid from object context of request
501 44 : InputParameters valid_params = emptyInputParameters();
502 :
503 : // set used to gather MooseObjectAction tasks to verify object parameters
504 44 : std::set<std::string> obj_act_tasks;
505 :
506 : // get set of global parameters, action parameters, and object parameters
507 44 : getAllValidParameters(valid_params, object_path, object_type, obj_act_tasks);
508 :
509 44 : bool pass = true;
510 :
511 : // add gathered parameters to completion list with input range and prefix
512 44 : if (is_request_in_open_block(request_context) || is_request_on_param_decl(request_context))
513 8 : pass &= addParametersToList(completionItems,
514 : valid_params,
515 : existing_params,
516 : replace_line_beg,
517 : replace_char_beg,
518 : replace_line_end,
519 : replace_char_end,
520 : filtering_prefix);
521 :
522 : // add all valid subblocks to completion list with input range and prefix
523 124 : if (is_request_in_open_block(request_context) || is_request_on_param_decl(request_context) ||
524 80 : is_request_on_block_decl(request_context))
525 24 : pass &= addSubblocksToList(completionItems,
526 : object_path,
527 : replace_line_beg,
528 : replace_char_beg,
529 : replace_line_end,
530 : replace_char_end,
531 : filtering_prefix,
532 12 : is_request_on_block_decl(request_context));
533 :
534 : // add valid parameter value options to completion list using input range
535 30 : if ((request_context.type() == wasp::VALUE || request_context.type() == wasp::ASSIGN ||
536 122 : request_context.type() == wasp::OVERRIDE_ASSIGN) &&
537 140 : valid_params.getParametersList().count(parent_name))
538 64 : pass &= addValuesToList(completionItems,
539 : valid_params,
540 : existing_params,
541 : existing_subblocks,
542 : parent_name,
543 : obj_act_tasks,
544 : object_path,
545 : replace_line_beg,
546 : replace_char_beg,
547 : replace_line_end,
548 : replace_char_end);
549 :
550 44 : is_incomplete = !pass;
551 :
552 44 : return pass;
553 44 : }
554 :
555 : void
556 44 : MooseServer::getExistingInput(wasp::HITNodeView parent_node,
557 : std::set<std::string> & existing_params,
558 : std::set<std::string> & existing_subblocks)
559 : {
560 : // gather names of all parameters and subblocks provided in input context
561 352 : for (auto itr = parent_node.begin(); itr != parent_node.end(); itr.next())
562 : {
563 308 : auto child_node = itr.get();
564 :
565 : // add key value or array type as parameter and object type as subblock
566 308 : if (child_node.type() == wasp::KEYED_VALUE || child_node.type() == wasp::ARRAY)
567 192 : existing_params.insert(child_node.name());
568 212 : else if (child_node.type() == wasp::OBJECT)
569 52 : existing_subblocks.insert(child_node.name());
570 352 : }
571 44 : }
572 :
573 : void
574 5592 : MooseServer::getAllValidParameters(InputParameters & valid_params,
575 : const std::string & object_path,
576 : const std::string & object_type,
577 : std::set<std::string> & obj_act_tasks)
578 : {
579 : // gather global parameters then action parameters then object parameters
580 5592 : valid_params += Moose::Builder::validParams();
581 5592 : getActionParameters(valid_params, object_path, obj_act_tasks);
582 5592 : getObjectParameters(valid_params, object_type, obj_act_tasks);
583 5592 : }
584 :
585 : void
586 5592 : MooseServer::getActionParameters(InputParameters & valid_params,
587 : const std::string & object_path,
588 : std::set<std::string> & obj_act_tasks)
589 : {
590 5592 : Syntax & syntax = getRegistrationApp().syntax();
591 5592 : ActionFactory & action_factory = getRegistrationApp().getActionFactory();
592 :
593 : // get registered syntax path identifier using actual object context path
594 : bool is_parent;
595 5592 : std::string registered_syntax = syntax.isAssociated(object_path, &is_parent);
596 :
597 : // use is_parent to skip action parameters when not explicitly registered
598 5592 : if (!is_parent)
599 : {
600 : // get action objects associated with registered syntax path identifier
601 5390 : auto action_range = syntax.getActions(registered_syntax);
602 :
603 : // traverse action objects for syntax to gather valid action parameters
604 11072 : for (auto action_iter = action_range.first; action_iter != action_range.second; action_iter++)
605 : {
606 5682 : const std::string & action_name = action_iter->second._action;
607 :
608 : // use action name to get set of valid parameters from action factory
609 5682 : InputParameters action_params = action_factory.getValidParams(action_name);
610 :
611 : // gather all MooseObjectAction tasks for verifying object parameters
612 5682 : if (action_params.have_parameter<bool>("isObjectAction"))
613 : {
614 5346 : if (action_params.get<bool>("isObjectAction"))
615 : {
616 5346 : std::set<std::string> tasks_by_actions = action_factory.getTasksByAction(action_name);
617 5346 : obj_act_tasks.insert(tasks_by_actions.begin(), tasks_by_actions.end());
618 5346 : }
619 :
620 : // filter parameter from completion list as it is not used in input
621 5346 : action_params.remove("isObjectAction");
622 : }
623 :
624 : // add parameters from action to full valid collection being gathered
625 5682 : valid_params += action_params;
626 5682 : }
627 : }
628 5592 : }
629 :
630 : void
631 5592 : MooseServer::getObjectParameters(InputParameters & valid_params,
632 : std::string object_type,
633 : const std::set<std::string> & obj_act_tasks)
634 : {
635 5592 : Syntax & syntax = getRegistrationApp().syntax();
636 5592 : Factory & factory = getRegistrationApp().getFactory();
637 :
638 : // use type parameter default if it exists and is not provided from input
639 5696 : if (object_type.empty() && valid_params.have_parameter<std::string>("type") &&
640 5696 : !valid_params.get<std::string>("type").empty())
641 : {
642 86 : object_type = valid_params.get<std::string>("type");
643 :
644 : // make type parameter not required in input since it has default value
645 172 : valid_params.makeParamNotRequired("type");
646 : }
647 :
648 : // check if object type has been registered to prevent unregistered error
649 5592 : if (factory.isRegistered(object_type))
650 : {
651 : // use object type to get set of valid parameters registered in factory
652 5328 : InputParameters object_params = factory.getValidParams(object_type);
653 :
654 : // check if object has base associated with any MooseObjectAction tasks
655 5328 : if (object_params.hasBase())
656 : {
657 5328 : const std::string & moose_base = object_params.getBase();
658 :
659 10360 : for (const auto & obj_act_task : obj_act_tasks)
660 : {
661 5350 : if (syntax.verifyMooseObjectTask(moose_base, obj_act_task))
662 : {
663 : // add parameters from object to valid collection if base matches
664 318 : valid_params += object_params;
665 318 : break;
666 : }
667 : }
668 : }
669 5328 : }
670 :
671 : // make parameters from list of those set by action not required in input
672 5592 : if (valid_params.have_parameter<std::vector<std::string>>("_object_params_set_by_action"))
673 : {
674 20 : auto names = valid_params.get<std::vector<std::string>>("_object_params_set_by_action");
675 40 : for (const auto & name : names)
676 20 : valid_params.makeParamNotRequired(name);
677 :
678 : // filter parameter from completion list since it is not used for input
679 20 : valid_params.remove("_object_params_set_by_action");
680 20 : }
681 5592 : }
682 :
683 : bool
684 8 : MooseServer::addParametersToList(wasp::DataArray & completionItems,
685 : const InputParameters & valid_params,
686 : const std::set<std::string> & existing_params,
687 : int replace_line_beg,
688 : int replace_char_beg,
689 : int replace_line_end,
690 : int replace_char_end,
691 : const std::string & filtering_prefix)
692 : {
693 8 : bool pass = true;
694 :
695 : // walk over collection of all valid parameters and build completion list
696 524 : for (const auto & valid_params_iter : valid_params)
697 : {
698 516 : const std::string & param_name = valid_params_iter.first;
699 516 : bool deprecated = valid_params.isParamDeprecated(param_name);
700 516 : bool is_private = valid_params.isPrivate(param_name);
701 :
702 : // filter out parameters that are deprecated, private, or already exist
703 516 : if (deprecated || is_private || existing_params.count(param_name))
704 400 : continue;
705 :
706 : // filter out parameters that do not begin with prefix if one was given
707 352 : if (param_name.rfind(filtering_prefix, 0) != 0)
708 236 : continue;
709 :
710 : // process parameter description and type to use in input default value
711 116 : std::string dirty_type = valid_params.type(param_name);
712 116 : std::string clean_type = MooseUtils::prettyCppType(dirty_type);
713 116 : std::string basic_type = JsonSyntaxTree::basicCppType(clean_type);
714 116 : std::string doc_string = valid_params.getDocString(param_name);
715 116 : MooseUtils::escape(doc_string);
716 :
717 : // use basic type to decide if parameter is array and quotes are needed
718 116 : bool is_array = basic_type.compare(0, 6, "Array:") == 0;
719 :
720 : // remove any array prefixes from basic type string and leave base type
721 116 : pcrecpp::RE("(Array:)*(.*)").GlobalReplace("\\2", &basic_type);
722 :
723 : // prepare clean cpp type string to be used for key to find input paths
724 116 : pcrecpp::RE(".+<([A-Za-z0-9_' ':]*)>.*").GlobalReplace("\\1", &clean_type);
725 :
726 : // decide completion item kind that client may use to display list icon
727 116 : int complete_kind = getCompletionItemKind(valid_params, param_name, clean_type, true);
728 :
729 : // default value for completion to be built using parameter information
730 116 : std::string default_value;
731 :
732 : // first if parameter default is set then use it to build default value
733 116 : if (valid_params.isParamValid(param_name))
734 : {
735 64 : default_value = JsonSyntaxTree::buildOutputString(valid_params_iter);
736 128 : default_value = MooseUtils::trim(default_value);
737 : }
738 :
739 : // otherwise if parameter has coupled default then use as default value
740 52 : else if (valid_params.hasDefaultCoupledValue(param_name))
741 : {
742 0 : std::ostringstream oss;
743 0 : oss << valid_params.defaultCoupledValue(param_name);
744 0 : default_value = oss.str();
745 0 : }
746 :
747 : // switch 1 to true or 0 to false if boolean parameter as default value
748 116 : if (basic_type == "Boolean" && default_value == "1")
749 12 : default_value = "true";
750 104 : else if (basic_type == "Boolean" && default_value == "0")
751 18 : default_value = "false";
752 :
753 : // wrap default value with single quotes if it exists and type is array
754 116 : std::string array_quote = is_array && !default_value.empty() ? "'" : "";
755 :
756 : // choose format of insertion text based on if client supports snippets
757 : int text_format;
758 116 : std::string insert_text;
759 116 : if (client_snippet_support && !default_value.empty())
760 : {
761 50 : text_format = wasp::lsp::m_text_format_snippet;
762 50 : insert_text = param_name + " = " + array_quote + "${1:" + default_value + "}" + array_quote;
763 : }
764 : else
765 : {
766 66 : text_format = wasp::lsp::m_text_format_plaintext;
767 66 : insert_text = param_name + " = " + array_quote + default_value + array_quote;
768 : }
769 : // finally build full insertion from parameter name, quote, and default
770 :
771 : // add parameter label, insert text, and description to completion list
772 116 : completionItems.push_back(wasp::DataObject());
773 116 : wasp::DataObject * item = completionItems.back().to_object();
774 116 : pass &= wasp::lsp::buildCompletionObject(*item,
775 : errors,
776 : param_name,
777 : replace_line_beg,
778 : replace_char_beg,
779 : replace_line_end,
780 : replace_char_end,
781 : insert_text,
782 : complete_kind,
783 : "",
784 : doc_string,
785 : false,
786 : false,
787 : text_format);
788 116 : }
789 :
790 8 : return pass;
791 : }
792 :
793 : bool
794 12 : MooseServer::addSubblocksToList(wasp::DataArray & completionItems,
795 : const std::string & object_path,
796 : int replace_line_beg,
797 : int replace_char_beg,
798 : int replace_line_end,
799 : int replace_char_end,
800 : const std::string & filtering_prefix,
801 : bool request_on_block_decl)
802 : {
803 12 : Syntax & syntax = getRegistrationApp().syntax();
804 :
805 : // set used to prevent reprocessing syntax paths for more than one action
806 12 : std::set<std::string> syntax_paths_processed;
807 :
808 : // build map of all syntax paths to names for subblocks and save to reuse
809 12 : auto & metadata = getSyntaxMetadata();
810 12 : if (metadata.syntax_to_subblocks.empty())
811 : {
812 222 : for (const auto & syntax_path_iter : syntax.getAssociatedActions())
813 : {
814 220 : std::string syntax_path = "/" + syntax_path_iter.first;
815 :
816 : // skip current syntax path if already processed for different action
817 220 : if (!syntax_paths_processed.insert(syntax_path).second)
818 30 : continue;
819 :
820 : // walk backward through syntax path adding subblock names to parents
821 580 : for (std::size_t last_sep; (last_sep = syntax_path.find_last_of("/")) != std::string::npos;)
822 : {
823 390 : std::string subblock_name = syntax_path.substr(last_sep + 1);
824 390 : syntax_path = syntax_path.substr(0, last_sep);
825 390 : metadata.syntax_to_subblocks[syntax_path].insert(subblock_name);
826 390 : }
827 220 : }
828 : }
829 :
830 : // get registered syntax from object path using map of paths to subblocks
831 12 : auto registered_syntax = syntax.isAssociated(object_path, nullptr, metadata.syntax_to_subblocks);
832 :
833 12 : bool pass = true;
834 :
835 : // walk over subblock names if found or at root and build completion list
836 12 : if (!registered_syntax.empty() || object_path == "/")
837 : {
838 : // choose format of insertion text based on if client supports snippets
839 10 : int text_format = client_snippet_support ? wasp::lsp::m_text_format_snippet
840 : : wasp::lsp::m_text_format_plaintext;
841 :
842 166 : for (const auto & subblock_name : metadata.syntax_to_subblocks[registered_syntax])
843 : {
844 : // filter subblock if it does not begin with prefix and one was given
845 156 : if (subblock_name != "*" && subblock_name.rfind(filtering_prefix, 0) != 0)
846 10 : continue;
847 :
848 146 : std::string doc_string;
849 146 : std::string insert_text;
850 : int complete_kind;
851 :
852 : // build required parameter list for each block to use in insert text
853 146 : const std::string full_block_path = object_path + "/" + subblock_name;
854 438 : const std::string req_params = getRequiredParamsText(full_block_path, "", {}, " ");
855 :
856 : // customize description and insert text for star and named subblocks
857 146 : if (subblock_name == "*")
858 : {
859 4 : doc_string = "custom user named block";
860 8 : insert_text = (request_on_block_decl ? "" : "[") +
861 18 : (filtering_prefix.size() ? filtering_prefix : "block_name") + "]" +
862 8 : req_params + "\n " + (client_snippet_support ? "$0" : "") + "\n[]";
863 4 : complete_kind = wasp::lsp::m_comp_kind_variable;
864 : }
865 : else
866 : {
867 142 : doc_string = "application named block";
868 284 : insert_text = (request_on_block_decl ? "" : "[") + subblock_name + "]" + req_params +
869 284 : "\n " + (client_snippet_support ? "$0" : "") + "\n[]";
870 142 : complete_kind = wasp::lsp::m_comp_kind_struct;
871 : }
872 :
873 : // add subblock name, insert text, and description to completion list
874 146 : completionItems.push_back(wasp::DataObject());
875 146 : wasp::DataObject * item = completionItems.back().to_object();
876 146 : pass &= wasp::lsp::buildCompletionObject(*item,
877 : errors,
878 : subblock_name,
879 : replace_line_beg,
880 : replace_char_beg,
881 : replace_line_end,
882 : replace_char_end,
883 : insert_text,
884 : complete_kind,
885 : "",
886 : doc_string,
887 : false,
888 : false,
889 : text_format);
890 146 : }
891 : }
892 :
893 12 : return pass;
894 12 : }
895 :
896 : bool
897 32 : MooseServer::addValuesToList(wasp::DataArray & completionItems,
898 : const InputParameters & valid_params,
899 : const std::set<std::string> & existing_params,
900 : const std::set<std::string> & existing_subblocks,
901 : const std::string & param_name,
902 : const std::set<std::string> & obj_act_tasks,
903 : const std::string & object_path,
904 : int replace_line_beg,
905 : int replace_char_beg,
906 : int replace_line_end,
907 : int replace_char_end)
908 : {
909 32 : Syntax & syntax = getRegistrationApp().syntax();
910 32 : Factory & factory = getRegistrationApp().getFactory();
911 :
912 : // get clean type for path associations and basic type for boolean values
913 32 : std::string dirty_type = valid_params.type(param_name);
914 32 : std::string clean_type = MooseUtils::prettyCppType(dirty_type);
915 32 : std::string basic_type = JsonSyntaxTree::basicCppType(clean_type);
916 :
917 : // remove any array prefixes from basic type string and replace with base
918 32 : pcrecpp::RE("(Array:)*(.*)").GlobalReplace("\\2", &basic_type);
919 :
920 : // prepare clean cpp type string to be used for a key to find input paths
921 32 : pcrecpp::RE(".+<([A-Za-z0-9_' ':]*)>.*").GlobalReplace("\\1", &clean_type);
922 :
923 : // decide completion item kind that client may use to display a list icon
924 32 : int complete_kind = getCompletionItemKind(valid_params, param_name, clean_type, false);
925 :
926 : // map used to gather options and descriptions for value completion items
927 32 : std::map<std::string, std::string> options_and_descs;
928 :
929 : // first if parameter name is active or inactive then use input subblocks
930 32 : if (param_name == "active" || param_name == "inactive")
931 6 : for (const auto & subblock_name : existing_subblocks)
932 4 : options_and_descs[subblock_name] = "subblock name";
933 :
934 : // otherwise if parameter type is boolean then use true and false strings
935 30 : else if (basic_type == "Boolean")
936 : {
937 4 : options_and_descs["true"];
938 4 : options_and_descs["false"];
939 : }
940 :
941 : // otherwise if parameter type is one of the enums then use valid options
942 28 : else if (valid_params.have_parameter<MooseEnum>(param_name))
943 4 : getEnumsAndDocs(valid_params.get<MooseEnum>(param_name), options_and_descs);
944 24 : else if (valid_params.have_parameter<MultiMooseEnum>(param_name))
945 0 : getEnumsAndDocs(valid_params.get<MultiMooseEnum>(param_name), options_and_descs);
946 24 : else if (valid_params.have_parameter<ExecFlagEnum>(param_name))
947 0 : getEnumsAndDocs(valid_params.get<ExecFlagEnum>(param_name), options_and_descs);
948 24 : else if (valid_params.have_parameter<std::vector<MooseEnum>>(param_name))
949 0 : getEnumsAndDocs(valid_params.get<std::vector<MooseEnum>>(param_name)[0], options_and_descs);
950 :
951 : // otherwise if parameter is Application type then use all available apps
952 24 : else if (object_path == "/Application" && param_name == "type")
953 : {
954 6 : for (const auto & apps_iter : AppFactory::instance().registeredObjects())
955 : {
956 4 : const std::string & app_name = apps_iter.first;
957 4 : const InputParameters & app_params = apps_iter.second->buildParameters();
958 4 : std::string app_description = app_params.getClassDescription();
959 4 : MooseUtils::escape(app_description);
960 4 : options_and_descs[app_name] = app_description;
961 4 : }
962 : }
963 :
964 : // otherwise if parameter name is type then use all verified object names
965 22 : else if (param_name == "type")
966 : {
967 : // walk over entire set of objects that have been registered in factory
968 5030 : for (const auto & objects_iter : factory.registeredObjects())
969 : {
970 5026 : const std::string & object_name = objects_iter.first;
971 5026 : const InputParameters & object_params = objects_iter.second->buildParameters();
972 :
973 : // build required parameter list for each block to use in insert text
974 5026 : std::string req_params = getRequiredParamsText(object_path, object_name, existing_params, "");
975 15074 : req_params += req_params.size() ? "\n" + std::string(client_snippet_support ? "$0" : "") : "";
976 :
977 : // check if object has registered base parameter that can be verified
978 5026 : if (!object_params.hasBase())
979 0 : continue;
980 5026 : const std::string & moose_base = object_params.getBase();
981 :
982 : // walk over gathered MooseObjectAction tasks and add if base matches
983 10036 : for (const auto & obj_act_task : obj_act_tasks)
984 : {
985 5026 : if (!syntax.verifyMooseObjectTask(moose_base, obj_act_task))
986 5010 : continue;
987 16 : std::string type_description = object_params.getClassDescription();
988 16 : MooseUtils::escape(type_description);
989 16 : options_and_descs[object_name + req_params] = type_description;
990 16 : break;
991 16 : }
992 5026 : }
993 : }
994 :
995 : // otherwise if parameter type has any associated syntax then use lookups
996 : else
997 : {
998 : // build map of parameter types to input lookup paths and save to reuse
999 18 : auto & metadata = getSyntaxMetadata();
1000 18 : if (metadata.type_to_input_paths.empty())
1001 : {
1002 58 : for (const auto & associated_types_iter : syntax.getAssociatedTypes())
1003 : {
1004 56 : const std::string & type = associated_types_iter.second;
1005 56 : const std::string & path = associated_types_iter.first;
1006 56 : metadata.type_to_input_paths[type].insert(path);
1007 : }
1008 : }
1009 :
1010 : // check for input lookup paths that are associated with parameter type
1011 18 : const auto & input_path_iter = metadata.type_to_input_paths.find(clean_type);
1012 :
1013 18 : if (input_path_iter != metadata.type_to_input_paths.end())
1014 : {
1015 16 : wasp::HITNodeView view_root = getRoot().getNodeView();
1016 :
1017 : // walk over all syntax paths that are associated with parameter type
1018 42 : for (const auto & input_path : input_path_iter->second)
1019 : {
1020 : // use wasp siren to gather all input values at current lookup path
1021 26 : wasp::SIRENInterpreter<> selector;
1022 52 : if (!selector.parseString(input_path))
1023 0 : continue;
1024 26 : wasp::SIRENResultSet<wasp::HITNodeView> results;
1025 26 : std::size_t count = selector.evaluate(view_root, results);
1026 :
1027 : // walk over results and add each input value found at current path
1028 144 : for (std::size_t i = 0; i < count; i++)
1029 118 : if (results.adapted(i).type() == wasp::OBJECT)
1030 102 : options_and_descs[results.adapted(i).name()] = "from /" + input_path;
1031 26 : }
1032 16 : }
1033 :
1034 : // warehouse based completion is unavailable if problem failed to build
1035 : // input lookup based completion works even when problem fails to build
1036 : // so warehouse completion supplements lookups rather than replacing it
1037 18 : addObjectsFromWarehouses(clean_type, options_and_descs);
1038 : }
1039 :
1040 : // choose format of insertion text based on if client has snippet support
1041 32 : int text_format = client_snippet_support ? wasp::lsp::m_text_format_snippet
1042 : : wasp::lsp::m_text_format_plaintext;
1043 :
1044 32 : bool pass = true;
1045 :
1046 : // walk over pairs of options with descriptions and build completion list
1047 142 : for (const auto & option_and_desc : options_and_descs)
1048 : {
1049 110 : const std::string & insert_text = option_and_desc.first;
1050 110 : const std::string & option_name = insert_text.substr(0, insert_text.find('\n'));
1051 110 : const std::string & description = option_and_desc.second;
1052 :
1053 : // add option name, insertion range, and description to completion list
1054 110 : completionItems.push_back(wasp::DataObject());
1055 110 : wasp::DataObject * item = completionItems.back().to_object();
1056 110 : pass &= wasp::lsp::buildCompletionObject(*item,
1057 : errors,
1058 : option_name,
1059 : replace_line_beg,
1060 : replace_char_beg,
1061 : replace_line_end,
1062 : replace_char_end,
1063 : insert_text,
1064 : complete_kind,
1065 : "",
1066 : description,
1067 : false,
1068 : false,
1069 : text_format);
1070 110 : }
1071 :
1072 32 : return pass;
1073 32 : }
1074 :
1075 : template <typename MooseEnumType>
1076 : void
1077 8 : MooseServer::getEnumsAndDocs(MooseEnumType & moose_enum_param,
1078 : std::map<std::string, std::string> & options_and_descs)
1079 : {
1080 : // get map that contains any documentation strings provided for each item
1081 8 : const auto & enum_docs = moose_enum_param.getItemDocumentation();
1082 :
1083 : // walk over enums filling map with options and any provided descriptions
1084 68 : for (const auto & item : moose_enum_param.items())
1085 148 : options_and_descs[item.name()] = enum_docs.count(item) ? enum_docs.at(item) : "";
1086 8 : }
1087 :
1088 : void
1089 18 : MooseServer::addObjectsFromWarehouses(const std::string & param_type,
1090 : std::map<std::string, std::string> & options_and_descs)
1091 : {
1092 : // get check app of document and return with no items if its build failed
1093 18 : auto app_ptr = queryCheckApp();
1094 18 : if (!app_ptr)
1095 0 : return;
1096 :
1097 : // get problem from action warehouse and return without any items if null
1098 18 : std::shared_ptr<FEProblemBase> & problem = app_ptr->actionWarehouse().problemBase();
1099 18 : if (!problem)
1100 4 : return;
1101 :
1102 14 : if (param_type == "NonlinearVariableName")
1103 : {
1104 4 : for (const auto i : make_range(problem->numNonlinearSystems()))
1105 8 : for (const auto & nls_var_name : problem->getNonlinearSystemBase(i).getVariableNames())
1106 6 : options_and_descs[nls_var_name] = "from NonlinearSystem VariableWarehouse";
1107 : }
1108 12 : else if (param_type == "AuxVariableName")
1109 : {
1110 6 : for (const auto & aux_var_name : problem->getAuxiliarySystem().getVariableNames())
1111 4 : options_and_descs[aux_var_name] = "from AuxiliarySystem VariableWarehouse";
1112 : }
1113 10 : else if (param_type == "VariableName")
1114 : {
1115 0 : for (const auto i : make_range(problem->numNonlinearSystems()))
1116 0 : for (const auto & nls_var_name : problem->getNonlinearSystemBase(i).getVariableNames())
1117 0 : options_and_descs[nls_var_name] = "from NonlinearSystem VariableWarehouse";
1118 0 : for (const auto & aux_var_name : problem->getAuxiliarySystem().getVariableNames())
1119 0 : options_and_descs[aux_var_name] = "from AuxiliarySystem VariableWarehouse";
1120 : }
1121 10 : else if (param_type == "MaterialPropertyName")
1122 : {
1123 2 : const auto & mat_prop_registry = problem->getMaterialPropertyRegistry();
1124 : const std::vector<std::string> mat_prop_names(mat_prop_registry.idsToNamesBegin(),
1125 2 : mat_prop_registry.idsToNamesEnd());
1126 14 : for (const auto & mat_prop_name : mat_prop_names)
1127 12 : options_and_descs[mat_prop_name] = "from MaterialPropertyRegistry";
1128 2 : }
1129 8 : else if (param_type == "MaterialName")
1130 : {
1131 8 : for (const auto & material : problem->getMaterialWarehouse().getObjects())
1132 6 : options_and_descs[material->name()] = "from MaterialWarehouse";
1133 : }
1134 6 : else if (param_type == "FunctionName")
1135 : {
1136 6 : for (const auto & function : problem->getFunctionWarehouse().getObjects())
1137 4 : options_and_descs[function->name()] = "from FunctionWarehouse";
1138 : }
1139 4 : else if (param_type == "OutputName")
1140 : {
1141 12 : for (const auto & output_name : app_ptr->getOutputWarehouse().getOutputNames<Output>())
1142 12 : options_and_descs[output_name] = "from OutputWarehouse";
1143 6 : for (const auto & reserved_name : app_ptr->getOutputWarehouse().getReservedNames())
1144 4 : options_and_descs[reserved_name] = "from reserved names in OutputWarehouse";
1145 : }
1146 2 : else if (param_type == "UserObjectName")
1147 : {
1148 2 : std::vector<UserObject *> user_objects;
1149 2 : problem->theWarehouse()
1150 2 : .query()
1151 2 : .condition<AttribSystem>("UserObject")
1152 4 : .condition<AttribThread>(0)
1153 2 : .queryIntoUnsorted(user_objects);
1154 6 : for (const auto & user_object : user_objects)
1155 4 : options_and_descs[user_object->name()] = "from UserObjectWarehouse";
1156 2 : }
1157 : }
1158 :
1159 : bool
1160 6 : MooseServer::gatherDocumentDefinitionLocations(wasp::DataArray & definitionLocations,
1161 : int line,
1162 : int character)
1163 : {
1164 6 : Factory & factory = getRegistrationApp().getFactory();
1165 :
1166 : // return without any definition locations added when parser root is null
1167 6 : auto root_ptr = queryRoot();
1168 6 : if (!root_ptr)
1169 0 : return true;
1170 6 : auto & root = *root_ptr;
1171 :
1172 : // find hit node for zero based request line and column number from input
1173 6 : wasp::HITNodeView view_root = root.getNodeView();
1174 : wasp::HITNodeView request_context =
1175 6 : wasp::findNodeUnderLineColumn(view_root, line + 1, character + 1);
1176 :
1177 : // return without any definition locations added when node not value type
1178 6 : if (request_context.type() != wasp::VALUE)
1179 0 : return true;
1180 :
1181 : // get name of parameter node parent of value and value string from input
1182 6 : std::string param_name = request_context.has_parent() ? request_context.parent().name() : "";
1183 6 : std::string val_string = request_context.last_as_string();
1184 :
1185 : // add source code location if type parameter with registered object name
1186 6 : if (param_name == "type" && factory.isRegistered(val_string))
1187 : {
1188 : // get file path and line number of source code registering object type
1189 4 : FileLineInfo file_line_info = factory.getLineInfo(val_string);
1190 :
1191 : // return without any definition locations added if file cannot be read
1192 8 : if (!file_line_info.isValid() ||
1193 8 : !MooseUtils::checkFileReadable(file_line_info.file(), false, false, false))
1194 0 : return true;
1195 :
1196 : // add file scheme prefix to front of file path to build definition uri
1197 4 : auto location_uri = wasp::lsp::m_uri_prefix + file_line_info.file();
1198 :
1199 : // add file uri and zero based line and column range to definition list
1200 4 : definitionLocations.push_back(wasp::DataObject());
1201 4 : wasp::DataObject * location = definitionLocations.back().to_object();
1202 12 : return wasp::lsp::buildLocationObject(*location,
1203 : errors,
1204 : location_uri,
1205 4 : file_line_info.line() - 1,
1206 : 0,
1207 4 : file_line_info.line() - 1,
1208 4 : 1000);
1209 4 : }
1210 :
1211 : // get object context and value of type parameter for request if provided
1212 2 : wasp::HITNodeView object_context = request_context;
1213 6 : while (object_context.type() != wasp::OBJECT && object_context.has_parent())
1214 4 : object_context = object_context.parent();
1215 2 : const std::string & object_path = object_context.path();
1216 2 : wasp::HITNodeView type_node = object_context.first_child_by_name("type");
1217 : const std::string & object_type =
1218 2 : type_node.is_null() ? "" : wasp::strip_quotes(hit::extractValue(type_node.data()));
1219 :
1220 : // set used to gather all parameters valid from object context of request
1221 2 : InputParameters valid_params = emptyInputParameters();
1222 :
1223 : // set used to gather MooseObjectAction tasks to verify object parameters
1224 2 : std::set<std::string> obj_act_tasks;
1225 :
1226 : // get set of global parameters, action parameters, and object parameters
1227 2 : getAllValidParameters(valid_params, object_path, object_type, obj_act_tasks);
1228 :
1229 : // set used to gather nodes from input lookups custom sorted by locations
1230 : SortedLocationNodes location_nodes(
1231 0 : [](const wasp::HITNodeView & l, const wasp::HITNodeView & r)
1232 : {
1233 14 : const std::string & l_file = l.node_pool()->stream_name();
1234 14 : const std::string & r_file = r.node_pool()->stream_name();
1235 34 : return (l_file < r_file || (l_file == r_file && l.line() < r.line()) ||
1236 34 : (l_file == r_file && l.line() == r.line() && l.column() < r.column()));
1237 2 : });
1238 :
1239 : // gather all lookup path nodes matching value if parameter name is valid
1240 52 : for (const auto & valid_params_iter : valid_params)
1241 : {
1242 52 : if (valid_params_iter.first == param_name)
1243 : {
1244 : // get cpp type and prepare string for use as key finding input paths
1245 2 : std::string dirty_type = valid_params.type(param_name);
1246 2 : std::string clean_type = MooseUtils::prettyCppType(dirty_type);
1247 2 : pcrecpp::RE(".+<([A-Za-z0-9_' ':]*)>.*").GlobalReplace("\\1", &clean_type);
1248 :
1249 : // get set of nodes from associated path lookups matching input value
1250 2 : getInputLookupDefinitionNodes(location_nodes, clean_type, val_string);
1251 2 : break;
1252 2 : }
1253 : }
1254 :
1255 : // add parameter declarator to set if none were gathered by input lookups
1256 2 : if (location_nodes.empty() && request_context.has_parent() &&
1257 2 : request_context.parent().child_count_by_name("decl"))
1258 0 : location_nodes.insert(request_context.parent().first_child_by_name("decl"));
1259 :
1260 : // add locations to definition list using lookups or parameter declarator
1261 2 : return addLocationNodesToList(definitionLocations, location_nodes);
1262 6 : }
1263 :
1264 : void
1265 2 : MooseServer::getInputLookupDefinitionNodes(SortedLocationNodes & location_nodes,
1266 : const std::string & clean_type,
1267 : const std::string & val_string)
1268 : {
1269 2 : Syntax & syntax = getRegistrationApp().syntax();
1270 :
1271 : // build map from parameter types to input lookup paths and save to reuse
1272 2 : auto & metadata = getSyntaxMetadata();
1273 2 : if (metadata.type_to_input_paths.empty())
1274 : {
1275 0 : for (const auto & associated_types_iter : syntax.getAssociatedTypes())
1276 : {
1277 0 : const std::string & type = associated_types_iter.second;
1278 0 : const std::string & path = associated_types_iter.first;
1279 0 : metadata.type_to_input_paths[type].insert(path);
1280 : }
1281 : }
1282 :
1283 : // find set of input lookup paths that are associated with parameter type
1284 2 : const auto & input_path_iter = metadata.type_to_input_paths.find(clean_type);
1285 :
1286 : // return without any definition locations added when no paths associated
1287 2 : if (input_path_iter == metadata.type_to_input_paths.end())
1288 0 : return;
1289 :
1290 : // get root node from input to use in input lookups with associated paths
1291 2 : wasp::HITNodeView view_root = getRoot().getNodeView();
1292 :
1293 : // walk over all syntax paths that are associated with parameter type
1294 6 : for (const auto & input_path : input_path_iter->second)
1295 : {
1296 : // use wasp siren to gather all nodes from current lookup path in input
1297 4 : wasp::SIRENInterpreter<> selector;
1298 8 : if (!selector.parseString(input_path))
1299 0 : continue;
1300 4 : wasp::SIRENResultSet<wasp::HITNodeView> results;
1301 4 : std::size_t count = selector.evaluate(view_root, results);
1302 :
1303 : // walk over results and add nodes that have name matching value to set
1304 34 : for (std::size_t i = 0; i < count; i++)
1305 36 : if (results.adapted(i).type() == wasp::OBJECT && results.adapted(i).name() == val_string &&
1306 48 : results.adapted(i).child_count_by_name("decl"))
1307 18 : location_nodes.insert(results.adapted(i).first_child_by_name("decl"));
1308 4 : }
1309 2 : }
1310 :
1311 : bool
1312 6 : MooseServer::addLocationNodesToList(wasp::DataArray & defsOrRefsLocations,
1313 : const SortedLocationNodes & location_nodes)
1314 : {
1315 6 : bool pass = true;
1316 :
1317 : // walk over set of sorted nodes provided to add and build locations list
1318 22 : for (const auto & location_nodes_iter : location_nodes)
1319 : {
1320 : // add file scheme prefix onto front of file path to build location uri
1321 16 : auto location_uri = wasp::lsp::m_uri_prefix + location_nodes_iter.node_pool()->stream_name();
1322 :
1323 : // add file uri with zero based line and column range to locations list
1324 16 : defsOrRefsLocations.push_back(wasp::DataObject());
1325 16 : wasp::DataObject * location = defsOrRefsLocations.back().to_object();
1326 80 : pass &= wasp::lsp::buildLocationObject(*location,
1327 : errors,
1328 : location_uri,
1329 16 : location_nodes_iter.line() - 1,
1330 16 : location_nodes_iter.column() - 1,
1331 16 : location_nodes_iter.last_line() - 1,
1332 16 : location_nodes_iter.last_column());
1333 16 : }
1334 :
1335 6 : return pass;
1336 : }
1337 :
1338 : bool
1339 16 : MooseServer::getHoverDisplayText(std::string & display_text, int line, int character)
1340 : {
1341 16 : Factory & factory = getRegistrationApp().getFactory();
1342 16 : Syntax & syntax = getRegistrationApp().syntax();
1343 :
1344 : // return and leave display text as empty string when parser root is null
1345 16 : auto root_ptr = queryRoot();
1346 16 : if (!root_ptr)
1347 0 : return true;
1348 16 : auto & root = *root_ptr;
1349 :
1350 : // find hit node for zero based request line and column number from input
1351 16 : wasp::HITNodeView view_root = root.getNodeView();
1352 : wasp::HITNodeView request_context =
1353 16 : wasp::findNodeUnderLineColumn(view_root, line + 1, character + 1);
1354 :
1355 : // return and leave display text as empty string when not on key or value
1356 10 : if ((request_context.type() != wasp::DECL && request_context.type() != wasp::VALUE) ||
1357 42 : !request_context.has_parent() ||
1358 32 : (request_context.parent().type() != wasp::KEYED_VALUE &&
1359 20 : request_context.parent().type() != wasp::ARRAY))
1360 2 : return true;
1361 :
1362 : // get name of parameter node and value string that is specified in input
1363 14 : std::string paramkey = request_context.parent().name();
1364 14 : std::string paramval = request_context.last_as_string();
1365 :
1366 : // get object context path and object type value for request if it exists
1367 14 : wasp::HITNodeView object_context = request_context;
1368 42 : while (object_context.type() != wasp::OBJECT && object_context.has_parent())
1369 28 : object_context = object_context.parent();
1370 14 : const std::string object_path = object_context.path();
1371 14 : wasp::HITNodeView type_node = object_context.first_child_by_name("type");
1372 : const std::string object_type =
1373 14 : type_node.is_null() ? "" : wasp::strip_quotes(hit::extractValue(type_node.data()));
1374 :
1375 : // gather global, action, and object parameters in request object context
1376 14 : InputParameters valid_params = emptyInputParameters();
1377 14 : std::set<std::string> obj_act_tasks;
1378 14 : getAllValidParameters(valid_params, object_path, object_type, obj_act_tasks);
1379 :
1380 : // use class description as display text when request is Application type
1381 24 : if (request_context.type() == wasp::VALUE && paramkey == "type" &&
1382 24 : object_path == "/Application" && AppFactory::instance().isRegistered(paramval))
1383 : {
1384 2 : InputParameters app_params = AppFactory::instance().getValidParams(paramval);
1385 2 : display_text = app_params.getClassDescription();
1386 2 : MooseUtils::escape(display_text);
1387 2 : }
1388 :
1389 : // use class description as display text when request is valid type value
1390 16 : else if (request_context.type() == wasp::VALUE && paramkey == "type" &&
1391 4 : factory.isRegistered(paramval))
1392 : {
1393 4 : const InputParameters & object_params = factory.getValidParams(paramval);
1394 4 : if (object_params.hasBase())
1395 : {
1396 4 : const std::string & moose_base = object_params.getBase();
1397 4 : for (const auto & obj_act_task : obj_act_tasks)
1398 : {
1399 4 : if (syntax.verifyMooseObjectTask(moose_base, obj_act_task))
1400 : {
1401 4 : display_text = object_params.getClassDescription();
1402 4 : MooseUtils::escape(display_text);
1403 4 : break;
1404 : }
1405 : }
1406 : }
1407 4 : }
1408 :
1409 : // use item documentation as display text when request is enum type value
1410 8 : else if (request_context.type() == wasp::VALUE)
1411 : {
1412 4 : std::map<std::string, std::string> options_and_descs;
1413 4 : if (valid_params.have_parameter<MooseEnum>(paramkey))
1414 2 : getEnumsAndDocs(valid_params.get<MooseEnum>(paramkey), options_and_descs);
1415 2 : else if (valid_params.have_parameter<MultiMooseEnum>(paramkey))
1416 0 : getEnumsAndDocs(valid_params.get<MultiMooseEnum>(paramkey), options_and_descs);
1417 2 : else if (valid_params.have_parameter<ExecFlagEnum>(paramkey))
1418 2 : getEnumsAndDocs(valid_params.get<ExecFlagEnum>(paramkey), options_and_descs);
1419 0 : else if (valid_params.have_parameter<std::vector<MooseEnum>>(paramkey))
1420 0 : getEnumsAndDocs(valid_params.get<std::vector<MooseEnum>>(paramkey)[0], options_and_descs);
1421 4 : if (options_and_descs.count(paramval))
1422 : {
1423 4 : display_text = options_and_descs.find(paramval)->second;
1424 4 : MooseUtils::escape(display_text);
1425 : }
1426 4 : }
1427 :
1428 : // use parameter documentation as display text when request is valid name
1429 4 : else if (request_context.type() == wasp::DECL && valid_params.getParametersList().count(paramkey))
1430 : {
1431 4 : display_text = valid_params.getDocString(paramkey);
1432 4 : MooseUtils::escape(display_text);
1433 :
1434 : // add units information to hover text if it is specified for parameter
1435 4 : std::string doc_units = valid_params.getDocUnit(paramkey);
1436 4 : if (!doc_units.empty())
1437 2 : display_text += "\n\nUnits: " + doc_units;
1438 :
1439 : // add range information to hover text if it is specified for parameter
1440 4 : if (valid_params.isRangeChecked(paramkey))
1441 : {
1442 2 : std::string doc_range = valid_params.rangeCheckedFunction(paramkey);
1443 2 : if (!doc_range.empty())
1444 2 : display_text += "\n\nRange: " + doc_range;
1445 2 : }
1446 4 : }
1447 :
1448 14 : return true;
1449 16 : }
1450 :
1451 : bool
1452 4 : MooseServer::gatherDocumentReferencesLocations(wasp::DataArray & referencesLocations,
1453 : int line,
1454 : int character,
1455 : bool include_declaration)
1456 : {
1457 4 : Syntax & syntax = getRegistrationApp().syntax();
1458 :
1459 : // return without adding any reference locations when parser root is null
1460 4 : auto root_ptr = queryRoot();
1461 4 : if (!root_ptr)
1462 0 : return true;
1463 4 : auto & root = *root_ptr;
1464 :
1465 : // find hit node for zero based request line and column number from input
1466 4 : wasp::HITNodeView view_root = root.getNodeView();
1467 : wasp::HITNodeView request_context =
1468 4 : wasp::findNodeUnderLineColumn(view_root, line + 1, character + 1);
1469 :
1470 : // return without adding any references when request not block declarator
1471 2 : if ((request_context.type() != wasp::DECL && request_context.type() != wasp::DOT_SLASH &&
1472 2 : request_context.type() != wasp::LBRACKET && request_context.type() != wasp::RBRACKET) ||
1473 6 : !request_context.has_parent() || request_context.parent().type() != wasp::OBJECT)
1474 0 : return true;
1475 :
1476 : // get input path and block name of declarator located at request context
1477 4 : const std::string & inp_path = request_context.parent().path();
1478 4 : const std::string & inp_name = request_context.parent().name();
1479 :
1480 : // build map from input lookup paths to parameter types and save to reuse
1481 4 : auto & metadata = getSyntaxMetadata();
1482 4 : if (metadata.input_path_to_types.empty())
1483 116 : for (const auto & associated_types_iter : syntax.getAssociatedTypes())
1484 : {
1485 112 : const std::string & path = associated_types_iter.first;
1486 112 : const std::string & type = associated_types_iter.second;
1487 112 : metadata.input_path_to_types[path].insert(type);
1488 : }
1489 :
1490 : // get registered syntax from block path with map of input paths to types
1491 : bool is_parent;
1492 4 : auto registered_syntax = syntax.isAssociated(inp_path, &is_parent, metadata.input_path_to_types);
1493 :
1494 : // return without adding any references if syntax has no types associated
1495 4 : if (is_parent || !metadata.input_path_to_types.count(registered_syntax))
1496 0 : return true;
1497 :
1498 : // get set of parameter types which are associated with registered syntax
1499 4 : const std::set<std::string> & target_types = metadata.input_path_to_types.at(registered_syntax);
1500 :
1501 : // set used to gather nodes collected by value custom sorted by locations
1502 : SortedLocationNodes match_nodes(
1503 0 : [](const wasp::HITNodeView & l, const wasp::HITNodeView & r)
1504 : {
1505 14 : const std::string & l_file = l.node_pool()->stream_name();
1506 14 : const std::string & r_file = r.node_pool()->stream_name();
1507 22 : return (l_file < r_file || (l_file == r_file && l.line() < r.line()) ||
1508 22 : (l_file == r_file && l.line() == r.line() && l.column() < r.column()));
1509 4 : });
1510 :
1511 : // walk input recursively and gather all nodes that match value and types
1512 4 : getNodesByValueAndTypes(match_nodes, view_root, inp_name, target_types);
1513 :
1514 : // return without adding any references if no nodes match value and types
1515 4 : if (match_nodes.empty())
1516 0 : return true;
1517 :
1518 : // add request context node to set if declaration inclusion was specified
1519 12 : if (include_declaration && request_context.parent().child_count_by_name("decl"))
1520 12 : match_nodes.insert(request_context.parent().first_child_by_name("decl"));
1521 :
1522 : // add locations to references list with nodes that match value and types
1523 4 : return addLocationNodesToList(referencesLocations, match_nodes);
1524 4 : }
1525 :
1526 : void
1527 100 : MooseServer::getNodesByValueAndTypes(SortedLocationNodes & match_nodes,
1528 : wasp::HITNodeView view_parent,
1529 : const std::string & target_value,
1530 : const std::set<std::string> & target_types)
1531 : {
1532 : // walk over children of context to gather nodes matching value and types
1533 528 : for (const auto & view_child : view_parent)
1534 : {
1535 : // check for parameter type match if node is value matching target data
1536 428 : if (view_child.type() == wasp::VALUE && view_child.to_string() == target_value)
1537 : {
1538 : // get object context path and object type value of node if it exists
1539 8 : wasp::HITNodeView object_context = view_child;
1540 24 : while (object_context.type() != wasp::OBJECT && object_context.has_parent())
1541 16 : object_context = object_context.parent();
1542 8 : const std::string object_path = object_context.path();
1543 8 : wasp::HITNodeView type_node = object_context.first_child_by_name("type");
1544 : const std::string object_type =
1545 8 : type_node.is_null() ? "" : wasp::strip_quotes(hit::extractValue(type_node.data()));
1546 :
1547 : // gather global, action, and object parameters for context of object
1548 8 : InputParameters valid_params = emptyInputParameters();
1549 8 : std::set<std::string> obj_act_tasks;
1550 8 : getAllValidParameters(valid_params, object_path, object_type, obj_act_tasks);
1551 :
1552 : // get name from parent of current value node which is parameter node
1553 8 : std::string param_name = view_child.has_parent() ? view_child.parent().name() : "";
1554 :
1555 : // get type of parameter and prepare string to check target set match
1556 8 : std::string dirty_type = valid_params.type(param_name);
1557 8 : std::string clean_type = MooseUtils::prettyCppType(dirty_type);
1558 8 : pcrecpp::RE(".+<([A-Za-z0-9_' ':]*)>.*").GlobalReplace("\\1", &clean_type);
1559 :
1560 : // add input node to collection if its type is also in set of targets
1561 8 : if (target_types.count(clean_type))
1562 6 : match_nodes.insert(view_child);
1563 8 : }
1564 :
1565 : // recurse deeper into input to search for matches if node has children
1566 428 : if (!view_child.is_leaf())
1567 96 : getNodesByValueAndTypes(match_nodes, view_child, target_value, target_types);
1568 528 : }
1569 100 : }
1570 :
1571 : bool
1572 2 : MooseServer::gatherDocumentFormattingTextEdits(wasp::DataArray & formattingTextEdits,
1573 : int tab_size,
1574 : bool /* insert_spaces */)
1575 : {
1576 : // strip scheme prefix from document uri if it exists for parse file path
1577 2 : std::string parse_file_path = document_path;
1578 2 : pcrecpp::RE("(.*://)(.*)").Replace("\\2", &parse_file_path);
1579 :
1580 : // input check expanded any brace expressions in cached tree so reprocess
1581 2 : std::stringstream input_errors, input_stream(getDocumentText());
1582 2 : wasp::DefaultHITInterpreter interpreter(input_errors);
1583 :
1584 : // return without adding any formatting text edits if input parsing fails
1585 2 : if (!interpreter.parseStream(input_stream, parse_file_path))
1586 0 : return true;
1587 :
1588 : // return without adding any formatting text edits if parser root is null
1589 2 : if (interpreter.root().is_null())
1590 0 : return true;
1591 :
1592 : // get input root node line and column range to represent entire document
1593 2 : wasp::HITNodeView view_root = interpreter.root();
1594 2 : int document_start_line = view_root.line() - 1;
1595 2 : int document_start_char = view_root.column() - 1;
1596 2 : int document_last_line = view_root.last_line() - 1;
1597 2 : int document_last_char = view_root.last_column();
1598 :
1599 : // set number of spaces for indentation and build formatted document text
1600 2 : _formatting_tab_size = tab_size;
1601 2 : std::size_t starting_line = view_root.line() - 1;
1602 2 : std::string document_format = formatDocument(view_root, starting_line, 0);
1603 :
1604 : // remove beginning newline character from formatted document text string
1605 2 : document_format.erase(0, 1);
1606 :
1607 : // add formatted text with whole line and column range to formatting list
1608 2 : formattingTextEdits.push_back(wasp::DataObject());
1609 2 : wasp::DataObject * item = formattingTextEdits.back().to_object();
1610 2 : bool pass = wasp::lsp::buildTextEditObject(*item,
1611 : errors,
1612 : document_start_line,
1613 : document_start_char,
1614 : document_last_line,
1615 : document_last_char,
1616 : document_format);
1617 2 : return pass;
1618 2 : }
1619 :
1620 : std::string
1621 26 : MooseServer::formatDocument(wasp::HITNodeView parent, std::size_t & prev_line, std::size_t level)
1622 : {
1623 : // build string of newline and indentation spaces from level and tab size
1624 26 : std::string newline_indent = "\n" + std::string(level * _formatting_tab_size, ' ');
1625 :
1626 : // lambda to format include data by replacing consecutive spaces with one
1627 2 : auto collapse_spaces = [](std::string string_copy)
1628 : {
1629 2 : pcrecpp::RE("\\s+").Replace(" ", &string_copy);
1630 2 : return string_copy;
1631 : };
1632 :
1633 : // formatted string that will be built recursively by appending each call
1634 26 : std::string format_string;
1635 :
1636 : // walk over all children of this node context and build formatted string
1637 192 : for (const auto i : make_range(parent.child_count()))
1638 : {
1639 : // walk must be index based to catch file include and skip its children
1640 166 : wasp::HITNodeView child = parent.child_at(i);
1641 :
1642 : // get declarator to address shorthand syntax object with no declarator
1643 664 : auto decl = child.child_count_by_name("decl") ? child.first_child_by_name("decl").data() : "";
1644 :
1645 : // add blank line if necessary after previous line and before this line
1646 166 : std::string blank = child.line() > prev_line + 1 ? "\n" : "";
1647 :
1648 : // format include directive with indentation and collapse extra spacing
1649 166 : if (child.type() == wasp::FILE)
1650 4 : format_string += blank + newline_indent + MooseUtils::trim(collapse_spaces(child.data()));
1651 :
1652 : // format normal comment with indentation and inline comment with space
1653 164 : else if (child.type() == wasp::COMMENT)
1654 26 : format_string += (child.line() == prev_line ? " " : blank + newline_indent) +
1655 40 : MooseUtils::trim(child.data());
1656 :
1657 : // pass object with no declarator through without increased indentation
1658 154 : else if (child.type() == wasp::OBJECT && decl.empty())
1659 4 : format_string += formatDocument(child, prev_line, level);
1660 :
1661 : // format object recursively with indentation and without legacy syntax
1662 150 : else if (child.type() == wasp::OBJECT)
1663 40 : format_string += blank + newline_indent + "[" + decl + "]" +
1664 60 : formatDocument(child, prev_line, level + 1) + newline_indent + "[]";
1665 :
1666 : // format keyed value with indentation and calling reusable hit methods
1667 130 : else if (child.type() == wasp::KEYED_VALUE || child.type() == wasp::ARRAY)
1668 : {
1669 106 : const std::string assign = wasp::is_override(child) ? child.child_at(1).data() : "=";
1670 38 : const std::string prefix = newline_indent + decl + " " + assign + " ";
1671 :
1672 38 : const std::string render_val = hit::extractValue(child.data());
1673 38 : std::size_t val_column = child.child_count() > 2 ? child.child_at(2).column() : 0;
1674 38 : std::size_t prefix_len = prefix.size() - 1;
1675 :
1676 38 : format_string += blank + prefix + hit::formatValue(render_val, val_column, prefix_len);
1677 38 : }
1678 :
1679 : // set previous line reference used for blank lines and inline comments
1680 166 : prev_line = child.last_line();
1681 166 : }
1682 :
1683 : // return formatted text string that gets appended to each recursive call
1684 52 : return format_string;
1685 26 : }
1686 :
1687 : bool
1688 4 : MooseServer::gatherDocumentSymbols(wasp::DataArray & documentSymbols)
1689 : {
1690 : // return prior to starting document symbol tree when parser root is null
1691 4 : auto root_ptr = queryRoot();
1692 4 : if (!root_ptr)
1693 0 : return true;
1694 4 : auto & root = *root_ptr;
1695 :
1696 4 : wasp::HITNodeView view_root = root.getNodeView();
1697 :
1698 4 : bool pass = true;
1699 :
1700 : // walk over all children of root node context and build document symbols
1701 24 : for (const auto i : make_range(view_root.child_count()))
1702 : {
1703 : // walk must be index based to catch file include and skip its children
1704 20 : wasp::HITNodeView view_child = view_root.child_at(i);
1705 :
1706 : // set up name, zero based line and column range, kind, and detail info
1707 20 : std::string name = view_child.name();
1708 20 : int line = view_child.line() - 1;
1709 20 : int column = view_child.column() - 1;
1710 20 : int last_line = view_child.last_line() - 1;
1711 20 : int last_column = view_child.last_column();
1712 20 : int symbol_kind = getDocumentSymbolKind(view_child);
1713 : std::string detail =
1714 40 : !view_child.first_child_by_name("type").is_null()
1715 36 : ? wasp::strip_quotes(hit::extractValue(view_child.first_child_by_name("type").data()))
1716 52 : : "";
1717 :
1718 : // build document symbol object from node child info and push to array
1719 20 : documentSymbols.push_back(wasp::DataObject());
1720 20 : wasp::DataObject * data_child = documentSymbols.back().to_object();
1721 20 : pass &= wasp::lsp::buildDocumentSymbolObject(*data_child,
1722 : errors,
1723 40 : (name.empty() ? "void" : name),
1724 : detail,
1725 : symbol_kind,
1726 : false,
1727 : line,
1728 : column,
1729 : last_line,
1730 : last_column,
1731 : line,
1732 : column,
1733 : last_line,
1734 : last_column);
1735 :
1736 : // call method to recursively fill document symbols for each node child
1737 20 : pass &= traverseParseTreeAndFillSymbols(view_child, *data_child);
1738 20 : }
1739 :
1740 4 : return pass;
1741 4 : }
1742 :
1743 : bool
1744 352 : MooseServer::traverseParseTreeAndFillSymbols(wasp::HITNodeView view_parent,
1745 : wasp::DataObject & data_parent)
1746 : {
1747 : // return without adding any children if parent node is file include type
1748 352 : if (wasp::is_nested_file(view_parent))
1749 0 : return true;
1750 :
1751 352 : bool pass = true;
1752 :
1753 : // walk over all children of this node context and build document symbols
1754 684 : for (const auto i : make_range(view_parent.child_count()))
1755 : {
1756 : // walk must be index based to catch file include and skip its children
1757 332 : wasp::HITNodeView view_child = view_parent.child_at(i);
1758 :
1759 : // set up name, zero based line and column range, kind, and detail info
1760 332 : std::string name = view_child.name();
1761 332 : int line = view_child.line() - 1;
1762 332 : int column = view_child.column() - 1;
1763 332 : int last_line = view_child.last_line() - 1;
1764 332 : int last_column = view_child.last_column();
1765 332 : int symbol_kind = getDocumentSymbolKind(view_child);
1766 : std::string detail =
1767 664 : !view_child.first_child_by_name("type").is_null()
1768 340 : ? wasp::strip_quotes(hit::extractValue(view_child.first_child_by_name("type").data()))
1769 992 : : "";
1770 :
1771 : // build document symbol object from node child info and push to array
1772 332 : wasp::DataObject & data_child = wasp::lsp::addDocumentSymbolChild(data_parent);
1773 332 : pass &= wasp::lsp::buildDocumentSymbolObject(data_child,
1774 : errors,
1775 664 : (name.empty() ? "void" : name),
1776 : detail,
1777 : symbol_kind,
1778 : false,
1779 : line,
1780 : column,
1781 : last_line,
1782 : last_column,
1783 : line,
1784 : column,
1785 : last_line,
1786 : last_column);
1787 :
1788 : // call method to recursively fill document symbols for each node child
1789 332 : pass &= traverseParseTreeAndFillSymbols(view_child, data_child);
1790 332 : }
1791 :
1792 352 : return pass;
1793 : }
1794 :
1795 : int
1796 148 : MooseServer::getCompletionItemKind(const InputParameters & valid_params,
1797 : const std::string & param_name,
1798 : const std::string & clean_type,
1799 : bool is_param)
1800 : {
1801 : // set up completion item kind value that client may use for icon in list
1802 148 : auto associated_types = getRegistrationApp().syntax().getAssociatedTypes();
1803 150 : if (is_param && valid_params.isParamRequired(param_name) &&
1804 2 : !valid_params.isParamValid(param_name))
1805 2 : return wasp::lsp::m_comp_kind_event;
1806 146 : else if (param_name == "active" || param_name == "inactive")
1807 10 : return wasp::lsp::m_comp_kind_class;
1808 136 : else if (clean_type == "bool")
1809 32 : return wasp::lsp::m_comp_kind_interface;
1810 104 : else if (valid_params.have_parameter<MooseEnum>(param_name) ||
1811 92 : valid_params.have_parameter<MultiMooseEnum>(param_name) ||
1812 286 : valid_params.have_parameter<ExecFlagEnum>(param_name) ||
1813 90 : valid_params.have_parameter<std::vector<MooseEnum>>(param_name))
1814 14 : return is_param ? wasp::lsp::m_comp_kind_enum : wasp::lsp::m_comp_kind_enum_member;
1815 90 : else if (param_name == "type")
1816 8 : return wasp::lsp::m_comp_kind_type_param;
1817 82 : else if (std::find_if(associated_types.begin(),
1818 : associated_types.end(),
1819 2058 : [&](const auto & entry)
1820 2222 : { return entry.second == clean_type; }) != associated_types.end())
1821 16 : return wasp::lsp::m_comp_kind_reference;
1822 : else
1823 66 : return is_param ? wasp::lsp::m_comp_kind_keyword : wasp::lsp::m_comp_kind_value;
1824 148 : }
1825 :
1826 : int
1827 352 : MooseServer::getDocumentSymbolKind(wasp::HITNodeView symbol_node)
1828 : {
1829 : // lambdas that check if parameter is a boolean or number for symbol kind
1830 28 : auto is_boolean = [](wasp::HITNodeView symbol_node)
1831 : {
1832 : bool convert;
1833 28 : std::istringstream iss(MooseUtils::toLower(symbol_node.last_as_string()));
1834 56 : return (iss >> std::boolalpha >> convert && !iss.fail());
1835 28 : };
1836 24 : auto is_number = [](wasp::HITNodeView symbol_node)
1837 : {
1838 : double convert;
1839 24 : std::istringstream iss(symbol_node.last_as_string());
1840 48 : return (iss >> convert && iss.eof());
1841 24 : };
1842 :
1843 : // set up document symbol kind value that client may use for outline icon
1844 352 : if (symbol_node.type() == wasp::OBJECT)
1845 32 : return wasp::lsp::m_symbol_kind_struct;
1846 320 : else if (symbol_node.type() == wasp::FILE)
1847 0 : return wasp::lsp::m_symbol_kind_file;
1848 320 : else if (symbol_node.type() == wasp::ARRAY)
1849 4 : return wasp::lsp::m_symbol_kind_array;
1850 396 : else if (symbol_node.type() == wasp::KEYED_VALUE && symbol_node.name() == std::string("type"))
1851 12 : return wasp::lsp::m_symbol_kind_type_param;
1852 304 : else if (symbol_node.type() == wasp::KEYED_VALUE && is_boolean(symbol_node))
1853 4 : return wasp::lsp::m_symbol_kind_boolean;
1854 300 : else if (symbol_node.type() == wasp::KEYED_VALUE && is_number(symbol_node))
1855 4 : return wasp::lsp::m_symbol_kind_number;
1856 296 : else if (symbol_node.type() == wasp::KEYED_VALUE)
1857 20 : return wasp::lsp::m_symbol_kind_key;
1858 276 : else if (symbol_node.type() == wasp::VALUE)
1859 52 : return wasp::lsp::m_symbol_kind_string;
1860 : else
1861 224 : return wasp::lsp::m_symbol_kind_property;
1862 : }
1863 :
1864 : std::string
1865 5172 : MooseServer::getRequiredParamsText(const std::string & subblock_path,
1866 : const std::string & subblock_type,
1867 : const std::set<std::string> & existing_params,
1868 : const std::string & indent_spaces)
1869 : {
1870 : // gather global, action, and object parameters in request object context
1871 5172 : InputParameters valid_params = emptyInputParameters();
1872 5172 : std::set<std::string> obj_act_tasks;
1873 5172 : getAllValidParameters(valid_params, subblock_path, subblock_type, obj_act_tasks);
1874 :
1875 : // walk over collection of all parameters and build text of ones required
1876 5172 : std::string required_param_text;
1877 5172 : std::size_t param_index = 1;
1878 80433 : for (const auto & valid_params_iter : valid_params)
1879 : {
1880 : // skip parameter if deprecated, private, defaulted, optional, existing
1881 75261 : const std::string & param_name = valid_params_iter.first;
1882 150484 : if (!valid_params.isParamDeprecated(param_name) && !valid_params.isPrivate(param_name) &&
1883 155536 : !valid_params.isParamValid(param_name) && valid_params.isParamRequired(param_name) &&
1884 5052 : !existing_params.count(param_name))
1885 : {
1886 22 : std::string tab_stop = client_snippet_support ? "$" + std::to_string(param_index++) : "";
1887 22 : required_param_text += "\n" + indent_spaces + param_name + " = " + tab_stop;
1888 22 : }
1889 : }
1890 :
1891 10344 : return required_param_text;
1892 5172 : }
1893 :
1894 : bool
1895 16 : MooseServer::gatherExtensionResponses(wasp::DataArray & extensionResponses,
1896 : const std::string & extensionMethod,
1897 : int line,
1898 : int character)
1899 : {
1900 : // use appropriate method to fill response based on extension method name
1901 16 : bool pass = true;
1902 16 : if (extensionMethod == "plotting")
1903 16 : pass = gatherPlottingResponses(extensionResponses, line, character);
1904 16 : return pass;
1905 : }
1906 :
1907 : bool
1908 16 : MooseServer::gatherPlottingResponses(wasp::DataArray & plotting_responses, int line, int character)
1909 : {
1910 : // return without adding any plot response objects if parser root is null
1911 16 : auto root_ptr = queryRoot();
1912 16 : if (!root_ptr)
1913 0 : return true;
1914 16 : auto & root = *root_ptr;
1915 :
1916 : // find hit node for zero based request line and column number from input
1917 16 : wasp::HITNodeView view_root = root.getNodeView();
1918 : wasp::HITNodeView request_context =
1919 16 : wasp::findNodeUnderLineColumn(view_root, line + 1, character + 1);
1920 :
1921 : // get object context and value of type parameter for request if provided
1922 16 : wasp::HITNodeView object_context = request_context;
1923 48 : while (object_context.type() != wasp::OBJECT && object_context.has_parent())
1924 32 : object_context = object_context.parent();
1925 16 : const std::string & object_name = object_context.name();
1926 16 : wasp::HITNodeView type_node = object_context.first_child_by_name("type");
1927 : const std::string & object_type =
1928 16 : type_node.is_null() ? "" : wasp::strip_quotes(hit::extractValue(type_node.data()));
1929 :
1930 : // get check app of document and return with no plots if its build failed
1931 16 : auto app_ptr = queryCheckApp();
1932 16 : if (!app_ptr)
1933 0 : return true;
1934 :
1935 : // get problem from action warehouse and return without any plots if null
1936 16 : std::shared_ptr<FEProblemBase> & problem = app_ptr->actionWarehouse().problemBase();
1937 16 : if (!problem)
1938 0 : return true;
1939 :
1940 : // check problem to build function plot if request is from function block
1941 16 : if (problem->hasFunction(object_name))
1942 12 : buildFuncPlotResponse(plotting_responses, *problem, object_name, object_type);
1943 :
1944 : // check problem to build PDF and CDF plots if request is in distribution
1945 4 : else if (problem->hasDistribution(object_name))
1946 4 : buildDistPlotResponses(plotting_responses, *problem, object_name, object_type);
1947 :
1948 16 : return true;
1949 16 : }
1950 :
1951 : void
1952 12 : MooseServer::buildFuncPlotResponse(wasp::DataArray & plotting_responses,
1953 : FEProblemBase & problem,
1954 : const std::string & object_name,
1955 : const std::string & object_type)
1956 : {
1957 : // get function from problem and return with no plots added if wrong type
1958 12 : const auto * pw_func = dynamic_cast<const PiecewiseBase *>(&problem.getFunction(object_name));
1959 12 : if (!pw_func)
1960 0 : return;
1961 :
1962 : // return without adding plot response objects when function size is zero
1963 12 : if (pw_func->functionSize() == 0)
1964 0 : return;
1965 :
1966 : // walk over piecewise function and gather keys and values for line graph
1967 12 : std::vector<double> graph_keys, graph_vals;
1968 76 : for (std::size_t i = 0; i < pw_func->functionSize(); i++)
1969 : {
1970 64 : graph_keys.push_back(pw_func->domain(i));
1971 64 : graph_vals.push_back(pw_func->range(i));
1972 : }
1973 :
1974 : // build CustomPlot object from function data then serialize for response
1975 12 : std::string plot_title = object_name + " " + object_type + " Function";
1976 24 : std::string x_axis_label = "abscissa values";
1977 12 : std::string y_axis_label = "ordinate values";
1978 12 : wasp::CustomPlot plot_object;
1979 12 : buildLineGraphPlot(plot_object, plot_title, x_axis_label, y_axis_label, graph_keys, graph_vals);
1980 12 : plotting_responses.push_back(wasp::serializeCustomPlot(plot_object));
1981 12 : }
1982 :
1983 : void
1984 4 : MooseServer::buildDistPlotResponses(wasp::DataArray & plotting_responses,
1985 : FEProblemBase & problem,
1986 : const std::string & object_name,
1987 : const std::string & object_type)
1988 : {
1989 : // get distribution from problem that is registered for given object name
1990 4 : const Distribution & dist = problem.getDistribution(object_name);
1991 :
1992 : // pick plot x-range using quantiles to be generic for distribution types
1993 4 : const double min_x = dist.quantile(_dist_plot_quantile_bound);
1994 4 : const double max_x = dist.quantile(1.0 - _dist_plot_quantile_bound);
1995 4 : const double del_x = (max_x - min_x) / (_dist_plot_num_points - 1);
1996 :
1997 : // return without any plots added if any calculated values are not finite
1998 4 : if (!std::isfinite(min_x) || !std::isfinite(max_x) || max_x <= min_x || !std::isfinite(del_x))
1999 0 : return;
2000 :
2001 : // use uniform grid of x-axis graph keys to sample plot values for y-axis
2002 8 : std::vector<double> graph_keys(_dist_plot_num_points);
2003 8 : std::vector<double> pdf_values(_dist_plot_num_points);
2004 4 : std::vector<double> cdf_values(_dist_plot_num_points);
2005 :
2006 : // calculate PDF values and CDF values for each key within range of graph
2007 52 : for (std::size_t i = 0; i < _dist_plot_num_points; i++)
2008 : {
2009 48 : graph_keys[i] = min_x + (i * del_x);
2010 48 : pdf_values[i] = dist.pdf(graph_keys[i]);
2011 48 : cdf_values[i] = dist.cdf(graph_keys[i]);
2012 :
2013 : // return without any plots added if any PDF or CDF value is not finite
2014 48 : if (!std::isfinite(pdf_values[i]) || !std::isfinite(cdf_values[i]))
2015 0 : return;
2016 : }
2017 :
2018 : // lambda to build CustomPlot object for distribution and add to response
2019 8 : auto add_dist_to_plot = [&](const std::string & dist_type, const std::vector<double> & graph_vals)
2020 : {
2021 8 : std::string plot_title = object_name + " " + object_type + " " + dist_type + " Distribution";
2022 8 : std::string x_axis_label = "x values";
2023 8 : std::string y_axis_label = dist_type + " values";
2024 8 : wasp::CustomPlot plot_object;
2025 8 : buildLineGraphPlot(plot_object, plot_title, x_axis_label, y_axis_label, graph_keys, graph_vals);
2026 8 : plotting_responses.push_back(wasp::serializeCustomPlot(plot_object));
2027 12 : };
2028 :
2029 : // build CustomPlot object for PDF values, serialize, and add to response
2030 8 : add_dist_to_plot("PDF", pdf_values);
2031 :
2032 : // build CustomPlot object for CDF values, serialize, and add to response
2033 4 : add_dist_to_plot("CDF", cdf_values);
2034 4 : }
2035 :
2036 : void
2037 20 : MooseServer::buildLineGraphPlot(wasp::CustomPlot & plot_object,
2038 : const std::string & plot_title,
2039 : const std::string & x_axis_label,
2040 : const std::string & y_axis_label,
2041 : const std::vector<double> & graph_keys,
2042 : const std::vector<double> & graph_vals)
2043 : {
2044 : // axis ranges
2045 20 : double min_key = *std::min_element(graph_keys.begin(), graph_keys.end());
2046 20 : double max_key = *std::max_element(graph_keys.begin(), graph_keys.end());
2047 20 : double min_val = *std::min_element(graph_vals.begin(), graph_vals.end());
2048 20 : double max_val = *std::max_element(graph_vals.begin(), graph_vals.end());
2049 :
2050 : // widen extents
2051 20 : double pad_factor = 0.05;
2052 20 : double pad_x_axis = (max_key - min_key) * pad_factor;
2053 20 : double pad_y_axis = (max_val - min_val) * pad_factor;
2054 20 : if (pad_y_axis == 0)
2055 0 : pad_y_axis = pad_factor;
2056 20 : min_key -= pad_x_axis;
2057 20 : max_key += pad_x_axis;
2058 20 : min_val -= pad_y_axis;
2059 20 : max_val += pad_y_axis;
2060 :
2061 : // plot setup
2062 20 : plot_object.title().text(plot_title);
2063 20 : plot_object.title().font().pointsize(18);
2064 20 : plot_object.title().visible(true);
2065 20 : plot_object.legend().visible(false);
2066 :
2067 : // plot x-axis
2068 20 : plot_object.x1Axis().label(x_axis_label);
2069 20 : plot_object.x1Axis().rangeMin(min_key);
2070 20 : plot_object.x1Axis().rangeMax(max_key);
2071 20 : plot_object.x1Axis().scaleType(wasp::CustomPlot::stLinear);
2072 20 : plot_object.x1Axis().labelType(wasp::CustomPlot::ltNumber);
2073 20 : plot_object.x1Axis().labelFont().pointsize(18);
2074 20 : plot_object.x1Axis().tickLabelFont().pointsize(16);
2075 :
2076 : // plot y-axis
2077 20 : plot_object.y1Axis().label(y_axis_label);
2078 20 : plot_object.y1Axis().rangeMin(min_val);
2079 20 : plot_object.y1Axis().rangeMax(max_val);
2080 20 : plot_object.y1Axis().scaleType(wasp::CustomPlot::stLinear);
2081 20 : plot_object.y1Axis().labelType(wasp::CustomPlot::ltNumber);
2082 20 : plot_object.y1Axis().labelFont().pointsize(18);
2083 20 : plot_object.y1Axis().tickLabelFont().pointsize(16);
2084 :
2085 : // graph series
2086 20 : auto line_graph = std::make_shared<wasp::CustomPlot::Graph>();
2087 20 : line_graph->keys() = graph_keys;
2088 20 : line_graph->values() = graph_vals;
2089 20 : line_graph->scatterShape(wasp::CustomPlot::ssDisc);
2090 20 : plot_object.series().push_back(line_graph);
2091 20 : }
2092 :
2093 : const hit::Node *
2094 138 : MooseServer::queryRoot() const
2095 : {
2096 138 : if (const auto parser_ptr = queryCheckParser())
2097 : {
2098 : #ifndef NDEBUG
2099 : if (const auto app_ptr = queryCheckApp())
2100 : mooseAssert(&app_ptr->parser() == parser_ptr, "App should have this parser");
2101 : #endif
2102 138 : if (const auto root_ptr = parser_ptr->queryRoot())
2103 138 : if (!root_ptr->getNodeView().is_null())
2104 138 : return root_ptr;
2105 : }
2106 0 : return nullptr;
2107 : }
2108 :
2109 : const MooseServer::CheckState *
2110 22878 : MooseServer::queryCheckState() const
2111 : {
2112 22878 : const auto it = _check_state.find(document_path);
2113 22878 : return it == _check_state.end() ? nullptr : &it->second;
2114 : }
2115 :
2116 : MooseServer::CheckState *
2117 0 : MooseServer::queryCheckState()
2118 : {
2119 0 : return const_cast<MooseServer::CheckState *>(std::as_const(*this).queryCheckState());
2120 : }
2121 :
2122 : const Parser *
2123 138 : MooseServer::queryCheckParser() const
2124 : {
2125 138 : const auto state = queryCheckState();
2126 138 : return state ? state->parser.get() : nullptr;
2127 : }
2128 :
2129 : Parser *
2130 0 : MooseServer::queryCheckParser()
2131 : {
2132 0 : return const_cast<Parser *>(std::as_const(*this).queryCheckParser());
2133 : }
2134 :
2135 : const MooseApp *
2136 22740 : MooseServer::queryCheckApp() const
2137 : {
2138 22740 : if (auto state = queryCheckState())
2139 22740 : return state->app.get();
2140 0 : return nullptr;
2141 : }
2142 :
2143 : MooseApp *
2144 22740 : MooseServer::queryCheckApp()
2145 : {
2146 22740 : return const_cast<MooseApp *>(std::as_const(*this).queryCheckApp());
2147 : }
2148 :
2149 : MooseApp &
2150 34 : MooseServer::getCheckApp()
2151 : {
2152 34 : if (auto app_ptr = queryCheckApp())
2153 : {
2154 34 : auto & app = *app_ptr;
2155 : mooseAssert(queryCheckParser(), "Should have a parser");
2156 : mooseAssert(&app.parser() == queryCheckParser(), "Parser should be the app's parser");
2157 34 : return app;
2158 : }
2159 0 : mooseError("MooseServer::getCheckApp(): App not available");
2160 : }
2161 :
2162 : MooseApp &
2163 22672 : MooseServer::getRegistrationApp()
2164 : {
2165 22672 : if (auto * app = queryCheckApp())
2166 22640 : return *app;
2167 32 : return _moose_app;
2168 : }
2169 :
2170 : MooseServer::SyntaxMetadata &
2171 36 : MooseServer::getSyntaxMetadata()
2172 : {
2173 36 : return _app_type_to_syntax_metadata[getRegistrationApp().type()];
2174 : }
2175 :
2176 : const hit::Node &
2177 18 : MooseServer::getRoot() const
2178 : {
2179 18 : if (auto root_ptr = queryRoot())
2180 18 : return *root_ptr;
2181 0 : mooseError("MooseServer::getRoot(): Root not available");
2182 : }
|