LCOV - code coverage report
Current view: top level - src/base - MooseServer.C (source / functions) Hit Total Coverage
Test: idaholab/moose framework: fef103 Lines: 738 802 92.0 %
Date: 2025-09-03 20:01:23 Functions: 54 57 94.7 %
Legend: Lines: hit not hit

          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             : }

Generated by: LCOV version 1.14