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