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

Generated by: LCOV version 1.14