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