1 /*
2  * SessionUserCommands.cpp
3  *
4  * Copyright (C) 2021 by RStudio, PBC
5  *
6  * Unless you have received this program directly from RStudio pursuant
7  * to the terms of a commercial license agreement with RStudio, then
8  * this program is licensed to you under the terms of version 3 of the
9  * GNU Affero General Public License. This program is distributed WITHOUT
10  * ANY EXPRESS OR IMPLIED WARRANTY, INCLUDING THOSE OF NON-INFRINGEMENT,
11  * MERCHANTABILITY OR FITNESS FOR A PARTICULAR PURPOSE. Please refer to the
12  * AGPL (http://www.gnu.org/licenses/agpl-3.0.txt) for more details.
13  *
14  */
15 
16 // #define RSTUDIO_ENABLE_DEBUG_MACROS
17 #define RSTUDIO_DEBUG_LABEL "commands"
18 #include <core/Macros.hpp>
19 
20 #include "SessionUserCommands.hpp"
21 
22 #include <boost/bind/bind.hpp>
23 
24 #include <shared_core/Error.hpp>
25 #include <core/Exec.hpp>
26 
27 #include <core/system/Xdg.hpp>
28 
29 #include <r/RExec.hpp>
30 #include <r/RSexp.hpp>
31 #include <r/RRoutines.hpp>
32 
33 #include <session/SessionModuleContext.hpp>
34 
35 namespace rstudio {
36 namespace session {
37 namespace modules {
38 namespace user_commands {
39 
40 using namespace rstudio::core;
41 using namespace boost::placeholders;
42 
43 namespace {
44 
noSuchSymbol(const std::string symbol,const ErrorLocation & location)45 Error noSuchSymbol(const std::string symbol, const ErrorLocation& location)
46 {
47    Error error(r::errc::SymbolNotFoundError, location);
48    error.addProperty("symbol", symbol);
49    LOG_ERROR(error);
50    return error;
51 }
52 
executeUserCommand(const json::JsonRpcRequest & request,json::JsonRpcResponse * pResponse)53 Error executeUserCommand(const json::JsonRpcRequest& request,
54                          json::JsonRpcResponse* pResponse)
55 {
56    // Read JSON params
57    std::string name;
58    Error error = json::readParams(request.params, &name);
59 
60    if (error)
61    {
62       LOG_ERROR(error);
63       return error;
64    }
65 
66    // Locate the function with this name
67    SEXP userCommandsEnvSEXP = r::sexp::findVar(".rs.userCommands", R_GlobalEnv);
68    if (userCommandsEnvSEXP == R_UnboundValue)
69       return noSuchSymbol(".rs.userCommands", ERROR_LOCATION);
70 
71    // Get the function bound in this environment
72    SEXP fnSEXP = r::sexp::findVar(name, userCommandsEnvSEXP);
73    if (fnSEXP == R_UnboundValue)
74       return noSuchSymbol(name, ERROR_LOCATION);
75 
76    // Execute function
77    r::sexp::Protect protect;
78    SEXP resultSEXP = R_NilValue;
79 
80    r::exec::RFunction userCommand(fnSEXP);
81    error = userCommand.call(&resultSEXP, &protect);
82 
83    if (error)
84    {
85       LOG_ERROR(error);
86       return error;
87    }
88 
89    return Success();
90 }
91 
rs_registerUserCommand(SEXP nameSEXP,SEXP shortcutsSEXP)92 SEXP rs_registerUserCommand(SEXP nameSEXP, SEXP shortcutsSEXP)
93 {
94    std::string name = r::sexp::safeAsString(nameSEXP);
95 
96    std::vector<std::string> shortcuts;
97    if (!r::sexp::fillVectorString(shortcutsSEXP, &shortcuts))
98       return R_NilValue;
99 
100    json::Object jsonData;
101    jsonData["name"] = name;
102    jsonData["shortcuts"] = json::toJsonArray(shortcuts);
103 
104    ClientEvent event(client_events::kRegisterUserCommand, jsonData);
105    module_context::enqueClientEvent(event);
106 
107    return R_NilValue;
108 }
109 
onDeferredInit(bool newSession)110 void onDeferredInit(bool newSession)
111 {
112    r::exec::RFunction loadUserCommands(".rs.loadUserCommands");
113    loadUserCommands.addParam("keybindingPath",
114          core::system::xdg::userConfigDir().completePath("keybindings").getAbsolutePath());
115 
116    Error error = loadUserCommands.call();
117    if (error)
118       LOG_ERROR(error);
119 }
120 
121 } // anonymous namespace
122 
initialize()123 Error initialize()
124 {
125    using boost::bind;
126    using namespace module_context;
127 
128    events().onDeferredInit.connect(onDeferredInit);
129 
130    RS_REGISTER_CALL_METHOD(rs_registerUserCommand, 2);
131 
132    ExecBlock initBlock;
133    initBlock.addFunctions()
134          (bind(sourceModuleRFile, "SessionUserCommands.R"))
135          (bind(registerRpcMethod, "execute_user_command", executeUserCommand))
136    ;
137    return initBlock.execute();
138 }
139 
140 } // namespace user_commands
141 } // namespace modules
142 } // namespace session
143 } // namespace rstudio
144