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