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