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