[education/rkward] rkward/rbackend: Cut down on the zoo of different backend request types, a bit.

Thomas Friedrichsmeier null at kde.org
Sat May 18 13:33:40 BST 2024


Git commit d7dfdf8c23b32b6bfaf82b12f474c9b8a1c11b57 by Thomas Friedrichsmeier.
Committed on 18/05/2024 at 12:21.
Pushed by tfry into branch 'master'.

Cut down on the zoo of different backend request types, a bit.

M  +41   -36   rkward/rbackend/rkrbackend.cpp
M  +10   -5    rkward/rbackend/rkrbackend.h
M  +1    -4    rkward/rbackend/rkrbackendprotocol_shared.h
M  +150  -168  rkward/rbackend/rkrinterface.cpp
M  +1    -3    rkward/rbackend/rkrinterface.h

https://invent.kde.org/education/rkward/-/commit/d7dfdf8c23b32b6bfaf82b12f474c9b8a1c11b57

diff --git a/rkward/rbackend/rkrbackend.cpp b/rkward/rbackend/rkrbackend.cpp
index 15fbda85b..61648895c 100644
--- a/rkward/rbackend/rkrbackend.cpp
+++ b/rkward/rbackend/rkrbackend.cpp
@@ -217,10 +217,10 @@ int RReadConsole (const char* prompt, unsigned char* buf, int buflen, int hist)
 	if (RKRBackend::repl_status.browser_context) {		// previously we were in a browser context. Check, whether we've left that.
 		if (RKRBackend::default_global_context == ROb(R_GlobalContext)) {
 			RKRBackend::repl_status.browser_context = RKRBackend::RKReplStatus::NotInBrowserContext;
-			RKRBackend::this_pointer->handlePlainGenericRequest (QStringList ("endBrowserContext"), false);
+			RKRBackend::this_pointer->doRCallRequest("endBrowserContext", QVariant(), RKRBackend::Asynchronous);
 		}
 	}
-	
+
 	if ((!RKRBackend::repl_status.browser_context) && (RKRBackend::repl_status.eval_depth == 0)) {
 		while (true) {
 			if (RKRBackend::this_pointer->isKilled() || (RKRBackend::repl_status.user_command_status == RKRBackend::RKReplStatus::NoUserCommand)) {
@@ -598,8 +598,8 @@ int RChooseFile(int isnew, char *buf, int len) {
 	RK_TRACE (RBACKEND);
 
 	QStringList params;
-	params << "choosefile" << QString() /* caption */ << QString() /* initial */ << "*" /* filter */ << (isnew ? "newfile" : "file");
-	auto res = RKRBackend::this_pointer->handlePlainGenericRequest(params, true);
+	params << QString() /* caption */ << QString() /* initial */ << "*" /* filter */ << (isnew ? "newfile" : "file");
+	auto res = RKRBackend::this_pointer->doRCallRequest("choosefile", params, RKRBackend::Synchronous);
 
 	QByteArray localres = RKTextCodec::toNative(res.ret.toString());
 	qstrncpy ((char *) buf, localres.data(), len);
@@ -890,13 +890,14 @@ SEXP doSubstackCall (SEXP _call, SEXP _args) {
 	} */
 
 	// For now, for simplicity, assume args are always strings, although possibly nested in lists
-	auto ret = RKRBackend::this_pointer->handleRequestWithSubcommands(call, RKRSupport::SEXPToNestedStrings(_args));
+	auto ret = RKRBackend::this_pointer->doRCallRequest(call, RKRSupport::SEXPToNestedStrings(_args), RKRBackend::SynchronousWithSubcommands);
 	if (!ret.warning.isEmpty()) RFn::Rf_warning("%s", RKTextCodec::toNative(ret.warning).constData());  // print warnings, first, as errors will cause a stop
 	if (!ret.error.isEmpty()) RFn::Rf_error("%s", RKTextCodec::toNative(ret.error).constData());
 
 	return RKRSupport::QVariantToSEXP(ret.ret);
 }
 
+// TODO: remove this entry point in favor of doSubstackCall
 SEXP doPlainGenericRequest (SEXP call, SEXP synchronous) {
 	RK_TRACE (RBACKEND);
 
@@ -1416,10 +1417,8 @@ void RKRBackend::catToOutputFile (const QString &out) {
 void RKRBackend::printCommand (const QString &command) {
 	RK_TRACE (RBACKEND);
 
-	QStringList params ("highlightRCode");
-	params.append (command);
-	QString highlighted = handlePlainGenericRequest(params, true).ret.toString();
-	catToOutputFile (highlighted);
+	QString highlighted = doRCallRequest("highlightRCode", command, RKRBackend::Synchronous).ret.toString();
+	catToOutputFile(highlighted);
 }
 
 void RKRBackend::startOutputCapture () {
@@ -1568,14 +1567,17 @@ RCommandProxy* RKRBackend::fetchNextCommand () {
 	return (handleRequest (&req, false));
 }
 
-GenericRRequestResult RKRBackend::handleRequestWithSubcommands(const QString &call, const QVariant &params) {
+GenericRRequestResult RKRBackend::doRCallRequest(const QString &call, const QVariant &params, RequestFlags flags) {
 	RK_TRACE (RBACKEND);
 
-	RBackendRequest request(true, RBackendRequest::GenericRequestWithSubcommands);
+	bool synchronous = flags != Asynchronous;
+	RBackendRequest request(synchronous, RBackendRequest::RCallRequest);
 	request.params["call"] = call;
 	if (!params.isNull()) request.params["args"] = params;
-	request.command = current_command;
-	request.subcommandrequest = new RBackendRequest(true, RBackendRequest::OtherRequest);
+	if (flags == SynchronousWithSubcommands) {
+		request.params["cid"] = current_command->id;
+		request.subcommandrequest = new RBackendRequest(true, RBackendRequest::OtherRequest);
+	}
 	handleRequest(&request);
 	delete request.subcommandrequest;
 	return request.getResult();
@@ -1588,28 +1590,31 @@ QString getLibLoc() {
 GenericRRequestResult RKRBackend::handlePlainGenericRequest (const QStringList &parameters, bool synchronous) {
 	RK_TRACE (RBACKEND);
 
-	RBackendRequest request (synchronous, RBackendRequest::PlainGenericRequest);
-	if (parameters.value (0) == "getSessionInfo") {
-		QStringList dummy = parameters;
-		dummy.append (RKRBackendProtocolBackend::backendDebugFile ());
-// NOTE: R_SVN_REVISON used to be a string, but has changed to numeric constant in R 3.0.0. QString::arg() handles both.
-		dummy.append (QString (R_MAJOR "." R_MINOR " " R_STATUS " (" R_YEAR "-" R_MONTH "-" R_DAY " r%1)").arg (R_SVN_REVISION));
-		request.params["call"] = dummy;
-	} else if (parameters.value (0) == "set.output.file") {
-		output_file = parameters.value (1);
-		if (parameters.length () > 2) {
-			RK_ASSERT (parameters.value (2) == "SILENT");
-			return GenericRRequestResult();  // For automated testing and previews. The frontend should not be notified, here
-		}
-		request.params["call"] = parameters;
-	} else if (parameters.value(0) == "home") {
-		if (parameters.value(1) == "home") return GenericRRequestResult(RKRBackendProtocolBackend::dataDir());
-		else if (parameters.value(1) == "lib") return GenericRRequestResult(getLibLoc());
-	} else {
-		request.params["call"] = parameters;
+	const QString call = parameters.value(0);
+	const QStringList args = parameters.mid(1);
+	if ((call == "set.output.file") && (args.value(1) == "SILENT")) {
+		return GenericRRequestResult();  // For automated testing and previews. The frontend should not be notified, here
+	} else if (call == "home") {
+		if (args.value(0) == "home") return GenericRRequestResult(RKRBackendProtocolBackend::dataDir());
+		else if (args.value(0) == "lib") return GenericRRequestResult(getLibLoc());
 	}
-	handleRequest (&request);
-	return request.getResult();
+
+	auto res = doRCallRequest(call, QVariant(args), synchronous ? Synchronous : Asynchronous);
+
+	if (call == "getSessionInfo") {
+		// Non-translatable on purpose. This is meant for posting to the bug tracker, mostly.
+		QStringList lines("-- Frontend --");
+		lines.append(res.ret.toStringList());
+		lines.append(QString());
+		lines.append("-- Backend --");
+		lines.append("Debug message file (this may contain relevant diagnostic output in case of trouble):");
+		lines.append(RKRBackendProtocolBackend::backendDebugFile());
+		lines.append(QString());
+		// NOTE: R_SVN_REVISON used to be a string, but has changed to numeric constant in R 3.0.0. QString::arg() handles both.
+		lines.append(QString("R version (compile time): %1").arg(QString(R_MAJOR "." R_MINOR " " R_STATUS " (" R_YEAR "-" R_MONTH "-" R_DAY " r%1)").arg(R_SVN_REVISION)));
+		res.ret = QVariant(lines);
+	}
+	return res;
 }
 
 void RKRBackend::initialize (const QString &locale_dir) {
@@ -1686,7 +1691,7 @@ void RKRBackend::checkObjectUpdatesNeeded (bool check_list) {
 			QVariantList args;
 			args.append(QVariant(toplevel_env_names));
 			args.append(QVariant(loaded_namespaces));
-			handleRequestWithSubcommands("syncenvs", args);
+			doRCallRequest("syncenvs", args, SynchronousWithSubcommands);
 		} 
 	}
 
@@ -1696,7 +1701,7 @@ void RKRBackend::checkObjectUpdatesNeeded (bool check_list) {
 		args.append(changes.added);
 		args.append(changes.removed);
 		args.append(changes.changed);
-		handleRequestWithSubcommands("sync", args);
+		doRCallRequest("sync", args, SynchronousWithSubcommands);
 	}
 }
 
diff --git a/rkward/rbackend/rkrbackend.h b/rkward/rbackend/rkrbackend.h
index 6957b81e2..a34cecad6 100644
--- a/rkward/rbackend/rkrbackend.h
+++ b/rkward/rbackend/rkrbackend.h
@@ -93,11 +93,16 @@ public:
 	RCommandProxy *runDirectCommand (const QString &command, RCommand::CommandTypes datatype); 
 
 	void handleRequest(RBackendRequest *request) { handleRequest (request, true); };
-/** A relic of history. In contrast to handlePlainGenericRequest(), these requests support running sub-commands. However, the remaining requests which are currently handled this way
-should probably be converted to dedicated RKRBackendRequest's in the future. See also handlePlainGenericRequest(). */
-	GenericRRequestResult handleRequestWithSubcommands(const QString &call, const QVariant &args=QVariant());
-/** Sends a request to the frontend and returns the result (empty in case of asynchronous requests). Note that this function has considerable overlap with
-handleHistoricalSubstackRequest(). Exactly which requests get handled by which function is somewhat arbitrary, ATM. However, request that do not need sub-commands to be run, should generally be converted to use handlePlainGenericRequest(). (And probably all historicalSubstackRequests should be replaced!) */
+
+	enum RequestFlags {
+		Asynchronous,
+		Synchronous,
+		SynchronousWithSubcommands
+	};
+
+/** Sends a request to the frontend and returns the result (empty in case of asynchronous requests). */
+	GenericRRequestResult doRCallRequest(const QString &call, const QVariant &args, RequestFlags flags);
+/** TODO: merge with the above. For calls that are known not to require a subcommand */
 	GenericRRequestResult handlePlainGenericRequest(const QStringList &parameters, bool synchronous);
 	RCommandProxy* fetchNextCommand ();
 
diff --git a/rkward/rbackend/rkrbackendprotocol_shared.h b/rkward/rbackend/rkrbackendprotocol_shared.h
index 0c0410920..5556129d9 100644
--- a/rkward/rbackend/rkrbackendprotocol_shared.h
+++ b/rkward/rbackend/rkrbackendprotocol_shared.h
@@ -35,10 +35,7 @@ public:
 		ReadLine,      // 4
 		CommandOut,                  /**< Request the next command, and notify about the result of the previus. TODO split. */
 		Started,
-		EvalRequest,
-		CallbackRequest,
-		GenericRequestWithSubcommands,   // 9
-		PlainGenericRequest,
+		RCallRequest,   // 7
 		SetParamsFromBackend,
 		Debugger,
 		CommandLineIn,	/**< The next line of the current user command has been submitted in the backend. */
diff --git a/rkward/rbackend/rkrinterface.cpp b/rkward/rbackend/rkrinterface.cpp
index 0cfe2e161..a04ed2643 100644
--- a/rkward/rbackend/rkrinterface.cpp
+++ b/rkward/rbackend/rkrinterface.cpp
@@ -351,24 +351,39 @@ void RInterface::handleRequest (RBackendRequest* request) {
 			handleCommandOut (command);
 		}
 		tryNextCommand ();
-	} else if (request->type == RBackendRequest::GenericRequestWithSubcommands) {
-		RCommandProxy *cproxy = request->takeCommand();
-		RCommand *parent = nullptr;
-		for (int i = all_current_commands.size () - 1; i >= 0; --i) {
-			if (all_current_commands[i]->id () == cproxy->id) {
-				parent = all_current_commands[i];
-				break;
+	} else if (request->type == RBackendRequest::RCallRequest) {
+		const auto params = request->params;
+
+		auto subcommandrequest = request->subcommandrequest;
+		RCommandChain *in_chain = RCommandStack::regular_stack;
+		if (subcommandrequest) {
+			int id = params.value("cid").toInt();
+			RCommand *parent = nullptr;
+			for (int i = all_current_commands.size() - 1; i >= 0; --i) {
+				if (all_current_commands[i]->id() == id) {
+					parent = all_current_commands[i];
+					break;
+				}
+			}
+			if (!parent) {
+				// This can happen for Tcl events. Create a dummy command on the stack to keep things looping.
+				parent = new RCommand (QString (), RCommand::App | RCommand::EmptyCommand | RCommand::Sync);
+				RCommandStack::issueCommand(parent, nullptr);
+				all_current_commands.append(parent);
+				dummy_command_on_stack = parent;	// so we can get rid of it again, after it's sub-commands have finished
 			}
+			in_chain = openSubcommandChain(parent);
+			RK_DEBUG(RBACKEND, DL_DEBUG, "started sub-command chain (%p) for command %s", in_chain, qPrintable(parent->command()));
+
+			command_requests.append(request->subcommandrequest);
+			request->subcommandrequest = nullptr;  // it is now a separate request. Make sure we won't try to send it back as part of this one.
 		}
-		delete cproxy;
-		RK_ASSERT(request->subcommandrequest);
-		command_requests.append(request->subcommandrequest);
-		request->subcommandrequest = nullptr;  // it is now a separate request. Make sure we won't try to send it back as part of this one.
-		processHistoricalSubstackRequest(request->params["call"].toString(), request->params.value("args"), parent, request);
+
+		request->setResult(processRCallRequest(params.value("call").toString(), params.value("args"), in_chain));
+
+		if (subcommandrequest) closeChain(in_chain);
+
 		RKRBackendProtocolFrontend::setRequestCompleted(request);
-	} else if (request->type == RBackendRequest::PlainGenericRequest) {
-		request->setResult(processPlainGenericRequest(request->params["call"].toStringList()));
-		RKRBackendProtocolFrontend::setRequestCompleted (request);
 	} else if (request->type == RBackendRequest::Started) {
 		// The backend thread has finished basic initialization, but we still have more to do...
 		startup_errors = request->params["message"].toString ();
@@ -564,59 +579,127 @@ void RInterface::pauseProcessing (bool pause) {
 	else locked -= locked & User;
 }
 
-GenericRRequestResult RInterface::processPlainGenericRequest(const QStringList &calllist) {
+GenericRRequestResult RInterface::processRCallRequest (const QString &call, const QVariant &args, RCommandChain *in_chain) {
 	RK_TRACE (RBACKEND);
 
-	QString call = calllist.value (0);
-	if (call == "set.output.file") {
-		RK_ASSERT (calllist.count () == 2);
-		RKOutputWindowManager::self ()->setCurrentOutputPath (calllist.value (1));
+	if (call == "sync") {
+		QVariantList al = args.toList();  // added, removed, changed
+		RObjectList::getGlobalEnv()->updateFromR(in_chain, al.value(0).toStringList(), al.value(1).toStringList());
+		QStringList changed = al.value(2).toStringList();
+		RK_DEBUG(RBACKEND, DL_DEBUG, "symbols added %s, removed %s, changed %s", qPrintable(al.value(0).toStringList().join(",")), qPrintable(al.value(1).toStringList().join(",")), qPrintable(al.value(2).toStringList().join(",")));
+		for (int i = 0; i < changed.count (); ++i) {
+			QString object_name = changed[i];
+			RObject *obj = RObjectList::getObjectList ()->findObject (object_name);
+			if (obj) {
+				RK_DEBUG(RBACKEND, DL_DEBUG, "triggering update for symbol %s", qPrintable(object_name));
+				obj->markDataDirty ();
+				obj->updateFromR (in_chain);
+			} else {
+				RK_DEBUG (RBACKEND, DL_WARNING, "lookup failed for changed symbol %s", qPrintable(object_name));
+			}
+		}
+		return GenericRRequestResult();
+	}
+	if (call == "syncenvs") {
+		QVariantList al = args.toList();  // envs, namespaces
+		RK_DEBUG (RBACKEND, DL_DEBUG, "triggering update of object list");
+		RObjectList::getObjectList ()->updateFromR (in_chain, al.value(0).toStringList(), al.value(1).toStringList());
+		return GenericRRequestResult();
+	}
+
+	// NOTE: all requests below pass all arguments as single stringlist (although that may worth changing)
+	QStringList arglist = args.toStringList();
+	if (false) {  // syntax dummy
+#ifndef DISABLE_RKWINDOWCATCHER
+	// NOTE: WARNING: When converting these to PlainGenericRequests, the occasional "error, figure margins too large" starts coming up, again. Not sure, why.
+	} else if (call == "startOpenX11") {
+		RK_ASSERT(arglist.count() == 1);
+		RKWindowCatcher::instance()->start(arglist.value(0).toInt());
+	} else if (call == "endOpenX11") {
+		RK_ASSERT(arglist.count() == 1);
+		RKWindowCatcher::instance()->stop(arglist.value(0).toInt());
+	} else if (call == "updateDeviceHistory") {
+		if (!arglist.isEmpty()) {
+			RKWindowCatcher::instance()->updateHistory(arglist);
+		}
+	} else if (call == "killDevice") {
+		RK_ASSERT(arglist.count() == 1);
+		RKWindowCatcher::instance()->killDevice(arglist.value(0).toInt());
+#endif // DISABLE_RKWINDOWCATCHER
+	} else if (call == "edit") {
+		RK_ASSERT(!arglist.isEmpty());
+		new RKEditObjectAgent (arglist, in_chain);
+	} else if (call == "require") {
+		RK_ASSERT (!arglist.isEmpty());
+		QString lib_name = arglist.value(0);
+		KMessageBox::information(nullptr, i18n("The R-backend has indicated that in order to carry out the current task it needs the package '%1', which is not currently installed. We will open the package-management tool, and there you can try to locate and install the needed package.", lib_name), i18n("Require package '%1'", lib_name));
+		RKLoadLibsDialog::showInstallPackagesModal(nullptr, in_chain, QStringList(lib_name));
+	} else if (call == "doPlugin") {
+		if (arglist.count () >= 2) {
+			QString message;
+			bool ok;
+			RKComponentMap::ComponentInvocationMode mode = RKComponentMap::ManualSubmit;
+			if (arglist[1] == "auto") mode = RKComponentMap::AutoSubmit;
+			else if (arglist[1] == "submit") mode = RKComponentMap::AutoSubmitOrFail;
+			ok = RKComponentMap::invokeComponent(arglist[0], arglist.mid(2), mode, &message, in_chain);
+
+			if (!message.isEmpty ()) {
+				return (GenericRRequestResult(QVariant(), ok ? message : QString(), !ok ? message : QString()));
+			}
+		} else {
+			RK_ASSERT (false);
+		}
+	} else if (call == QStringLiteral ("output")) {
+		return(RKOutputDirectory::handleRCall(arglist, in_chain));
+	} else if (call == "set.output.file") {
+		RK_ASSERT(arglist.count () == 1);
+		RKOutputWindowManager::self()->setCurrentOutputPath(arglist.value(0));
 	} else if (call == "wdChange") {
 		// in case of separate processes, apply new working directory in frontend, too.
-		QDir::setCurrent (calllist.value (1));
+		QDir::setCurrent(arglist.value(0));
 		Q_EMIT backendWorkdirChanged();
 	} else if (call == "highlightRCode") {
-		return GenericRRequestResult(RKCommandHighlighter::commandToHTML(calllist.mid(1).join('\n')));
+		return GenericRRequestResult(RKCommandHighlighter::commandToHTML(arglist.join('\n')));
 	} else if (call == "quit") {
-		RKWardMainWindow::getMain ()->close ();
+		RKWardMainWindow::getMain()->close();
 		// if we're still alive, quitting was canceled
 		return GenericRRequestResult::makeError(i18n("Quitting was canceled"));
 	} else if (call == "preLocaleChange") {
 		int res = KMessageBox::warningContinueCancel(nullptr, i18n("A command in the R backend is trying to change the character encoding. While RKWard offers support for this, and will try to adjust to the new locale, this operation may cause subtle bugs, if data windows are currently open. Also the feature is not well tested, yet, and it may be advisable to save your workspace before proceeding.\nIf you have any data editor opened, or in any doubt, it is recommended to close those first (this will probably be auto-detected in later versions of RKWard). In this case, please choose 'Cancel' now, then close the data windows, save, and retry."), i18n("Locale change"));
 		if (res != KMessageBox::Continue) return GenericRRequestResult::makeError(i18n("Changing the locale was canceled by user"));
 	} else if (call == "listPlugins") {
-		RK_ASSERT (calllist.count () == 1);
+		RK_ASSERT(arglist.isEmpty());
 		return GenericRRequestResult(RKComponentMap::getMap()->listPlugins());
 	} else if (call == "setPluginStatus") {
-		QStringList params = calllist.mid (1);
-		RK_ASSERT ((params.size () % 3) == 0);
-		const int rows = params.size () / 3;
-		QStringList ids = params.mid (0, rows);
-		QStringList contexts = params.mid (rows, rows);
-		QStringList visible = params.mid (rows*2, rows);
-		RKComponentMap::getMap ()->setPluginStatus (ids, contexts, visible);
+		// TODO: Pass args in a saner way
+		RK_ASSERT((arglist.size() % 3) == 0);
+		const int rows = arglist.size() / 3;
+		QStringList ids = arglist.mid(0, rows);
+		QStringList contexts = arglist.mid(rows, rows);
+		QStringList visible = arglist.mid(rows*2, rows);
+		RKComponentMap::getMap()->setPluginStatus(ids, contexts, visible);
 	} else if (call == "loadPluginMaps") {
-		bool force = (calllist.value (1) == "force");
-		bool reload = (calllist.value (2) == "reload");
-		RKSettingsModulePlugins::registerPluginMaps (calllist.mid (3), force ? RKSettingsModulePlugins::ForceActivate : RKSettingsModulePlugins::AutoActivateIfNew, reload);
+		bool force = (arglist.value(0) == "force");
+		bool reload = (arglist.value(1) == "reload");
+		RKSettingsModulePlugins::registerPluginMaps(arglist.mid(2), force ? RKSettingsModulePlugins::ForceActivate : RKSettingsModulePlugins::AutoActivateIfNew, reload);
 	} else if (call == "updateInstalledPackagesList") {
-		RKSessionVars::instance ()->setInstalledPackages (calllist.mid (1));
+		RKSessionVars::instance ()->setInstalledPackages(arglist);
 	} else if (call == "showHTML") {
-		RK_ASSERT (calllist.count () == 2);
-		RKWorkplace::mainWorkplace ()->openHelpWindow (QUrl::fromUserInput (calllist.value (1), QDir::currentPath (), QUrl::AssumeLocalFile));
+		RK_ASSERT(arglist.size() == 1);
+		RKWorkplace::mainWorkplace()->openHelpWindow(QUrl::fromUserInput(arglist.value(0), QDir::currentPath (), QUrl::AssumeLocalFile));
 	} else if (call == "select.list") {
-		QString title = calllist.value (1);
-		bool multiple = (calllist.value (2) == "multi");
-		int num_preselects = calllist.value (3).toInt ();
-		QStringList preselects = calllist.mid (4, num_preselects);
-		QStringList choices = calllist.mid (4 + num_preselects);
-
-		QStringList results = RKSelectListDialog::doSelect (QApplication::activeWindow(), title, choices, preselects, multiple);
-		if (results.isEmpty ()) results.append ("");	// R wants to have it that way
+		QString title = arglist.value(0);
+		bool multiple = (arglist.value(1) == "multi");
+		int num_preselects = arglist.value(2).toInt ();
+		QStringList preselects = arglist.mid(3, num_preselects);
+		QStringList choices = arglist.mid(3 + num_preselects);
+
+		QStringList results = RKSelectListDialog::doSelect(QApplication::activeWindow(), title, choices, preselects, multiple);
+		if (results.isEmpty()) results.append("");	// R wants to have it that way
 		return GenericRRequestResult(results);
 	} else if (call == "choosefile") {
-		QFileDialog d(nullptr, calllist.value(1));  // caption
-		QString initial = calllist.value(2);
+		QFileDialog d(nullptr, arglist.value(0));  // caption
+		QString initial = arglist.value(1);
 		QString cat;
 		if (initial.startsWith('#')) {
 			cat = initial.mid(1);
@@ -624,12 +707,12 @@ GenericRRequestResult RInterface::processPlainGenericRequest(const QStringList &
 		}
 
 		d.setDirectory(initial);
-		QString filter = calllist.value(3);
+		QString filter = arglist.value(2);
 		if (!filter.isEmpty()) {
 			if (!filter.contains('(')) filter += '(' + filter + ')';
 			d.setNameFilter(filter);
 		}
-		QString mode = calllist.value(4);
+		QString mode = arglist.value(3);
 		if (mode == "file") d.setFileMode(QFileDialog::ExistingFile);
 		else if (mode == "files") d.setFileMode(QFileDialog::ExistingFiles);
 		else if (mode == "dir") d.setFileMode(QFileDialog::Directory);
@@ -646,44 +729,35 @@ GenericRRequestResult RInterface::processPlainGenericRequest(const QStringList &
 
 		return GenericRRequestResult(res);
 	} else if (call == "commandHistory") {
-		if (calllist.value (1) == "get") {
+		if (arglist.value(0) == "get") {
 			return GenericRRequestResult(RKConsole::mainConsole()->commandHistory());
 		} else {
-			RKConsole::mainConsole ()->setCommandHistory (calllist.mid (2), calllist.value (1) == "append");
+			RKConsole::mainConsole()->setCommandHistory(arglist.mid(1), arglist.value(0) == "append");
 		}
 	} else if (call == "getWorkspaceUrl") {
 		QUrl url = RKWorkplace::mainWorkplace ()->workspaceURL ();
 		if (!url.isEmpty()) return GenericRRequestResult(url.url());
 	} else if (call == "workplace.layout") {
-		if (calllist.value (1) == "set") {
-			if (calllist.value (2) == "close") RKWorkplace::mainWorkplace ()->closeAll ();
-			QStringList list = calllist.mid (3);
-			RKWorkplace::mainWorkplace ()->restoreWorkplace (list);
+		if (arglist.value(0) == "set") {
+			if (arglist.value(1) == "close") RKWorkplace::mainWorkplace ()->closeAll ();
+			QStringList list = arglist.mid(2);
+			RKWorkplace::mainWorkplace()->restoreWorkplace(list);
 		} else {
-			RK_ASSERT (calllist.value (1) == "get");
-			return GenericRRequestResult(RKWorkplace::mainWorkplace ()->makeWorkplaceDescription ());
+			RK_ASSERT(arglist.value(0) == "get");
+			return GenericRRequestResult(RKWorkplace::mainWorkplace()->makeWorkplaceDescription());
 		}
 	} else if (call == "set.window.placement.hint") {
-		RKWorkplace::mainWorkplace ()->setWindowPlacementOverrides (calllist.value (1), calllist.value (2), calllist.value (3));
+		RKWorkplace::mainWorkplace ()->setWindowPlacementOverrides(arglist.value(0), arglist.value(1), arglist.value(2));
 	} else if (call == "getSessionInfo") {
-		// Non-translatable on purpose. This is meant for posting to the bug tracker, mostly.
-		QStringList lines ("-- Frontend --");
-		lines.append (RKSessionVars::frontendSessionInfo ());
-		lines.append (QString ());
-		lines.append ("-- Backend --");
-		lines.append ("Debug message file (this may contain relevant diagnostic output in case of trouble):");
-		lines.append (calllist.value (1));
-		lines.append (QString ());
-		lines.append ("R version (compile time): " + calllist.value (2));
-		return GenericRRequestResult(lines);
+		return GenericRRequestResult(RKSessionVars::frontendSessionInfo());
 	} else if (call == "recordCommands") {
-		RK_ASSERT (calllist.count () == 3);
-		QString filename = calllist.value (1);
-		bool unfiltered = (calllist.value (2) == "include.all");
+		RK_ASSERT(arglist.count() == 2);
+		QString filename = arglist.value(0);
+		bool unfiltered = (arglist.value(1) == "include.all");
 
-		if (filename.isEmpty ()) {
+		if (filename.isEmpty()) {
 			command_logfile_mode = NotRecordingCommands;
-			command_logfile.close ();
+			command_logfile.close();
 		} else {
 			if (command_logfile_mode != NotRecordingCommands) {
 				return GenericRRequestResult(QVariant(), i18n("Attempt to start recording, while already recording commands. Ignoring."));
@@ -699,11 +773,11 @@ GenericRRequestResult RInterface::processPlainGenericRequest(const QStringList &
 			}
 		}
 	} else if (call == "printPreview") {
-		RKPrintAgent::printPostscript (calllist.value (1), true);
+		RKPrintAgent::printPostscript(arglist.value(0), true);
 	} else if (call == "endBrowserContext") {
-		RKDebugHandler::instance ()->endDebug ();
+		RKDebugHandler::instance ()->endDebug();
 	} else if (call == "switchLanguage") {
-		RKMessageCatalog::switchLanguage (calllist.value (1));
+		RKMessageCatalog::switchLanguage(arglist.value(0));
 	} else {
 		return GenericRRequestResult::makeError(i18n("Error: unrecognized request '%1'", call));
 	}
@@ -712,98 +786,6 @@ GenericRRequestResult RInterface::processPlainGenericRequest(const QStringList &
 	return GenericRRequestResult();
 }
 
-void RInterface::processHistoricalSubstackRequest (const QString &call, const QVariant &args, RCommand *parent_command, RBackendRequest *request) {
-	RK_TRACE (RBACKEND);
-
-	RCommandChain *in_chain;
-	if (!parent_command) {
-		// This can happen for Tcl events. Create a dummy command on the stack to keep things looping.
-		parent_command = new RCommand (QString (), RCommand::App | RCommand::EmptyCommand | RCommand::Sync);
-		RCommandStack::issueCommand(parent_command, nullptr);
-		all_current_commands.append(parent_command);
-		dummy_command_on_stack = parent_command;	// so we can get rid of it again, after it's sub-commands have finished
-	}
-	in_chain = openSubcommandChain (parent_command);
-	RK_DEBUG (RBACKEND, DL_DEBUG, "started sub-command chain (%p) for command %s", in_chain, qPrintable (parent_command->command ()));
-
-	if (call == "sync") {
-		QVariantList al = args.toList();  // added, removed, changed
-		RObjectList::getGlobalEnv()->updateFromR(in_chain, al.value(0).toStringList(), al.value(1).toStringList());
-		QStringList changed = al.value(2).toStringList();
-		RK_DEBUG(RBACKEND, DL_DEBUG, "symbols added %s, removed %s, changed %s", qPrintable(al.value(0).toStringList().join(",")), qPrintable(al.value(1).toStringList().join(",")), qPrintable(al.value(2).toStringList().join(",")));
-		for (int i = 0; i < changed.count (); ++i) {
-			QString object_name = changed[i];
-			RObject *obj = RObjectList::getObjectList ()->findObject (object_name);
-			if (obj) {
-				RK_DEBUG(RBACKEND, DL_DEBUG, "triggering update for symbol %s", qPrintable(object_name));
-				obj->markDataDirty ();
-				obj->updateFromR (in_chain);
-			} else {
-				RK_DEBUG (RBACKEND, DL_WARNING, "lookup failed for changed symbol %s", qPrintable(object_name));
-			}
-		}
-		closeChain(in_chain);
-		return;
-	}
-	if (call == "syncenvs") {
-		QVariantList al = args.toList();  // envs, namespaces
-		RK_DEBUG (RBACKEND, DL_DEBUG, "triggering update of object list");
-		RObjectList::getObjectList ()->updateFromR (in_chain, al.value(0).toStringList(), al.value(1).toStringList());
-		closeChain(in_chain);
-		return;
-	}
-
-	// NOTE: all requests below are wrapped from R commands, and pass all arguments as stringlist (although that may worth changing
-	QStringList arglist = args.toStringList();
-	if (false) {  // syntax dummy
-#ifndef DISABLE_RKWINDOWCATCHER
-	// NOTE: WARNING: When converting these to PlainGenericRequests, the occasional "error, figure margins too large" starts coming up, again. Not sure, why.
- 	} else if (call == "startOpenX11") {
-		RK_ASSERT(arglist.count() == 1);
-		RKWindowCatcher::instance()->start(arglist.value(0).toInt());
- 	} else if (call == "endOpenX11") {
-		RK_ASSERT(arglist.count() == 1);
-		RKWindowCatcher::instance()->stop(arglist.value (0).toInt());
-	} else if (call == "updateDeviceHistory") {
-		if (!arglist.isEmpty()) {
-			RKWindowCatcher::instance()->updateHistory(arglist);
-		}
-	} else if (call == "killDevice") {
-		RK_ASSERT(arglist.count() == 1);
-		RKWindowCatcher::instance()->killDevice(arglist.value(0).toInt());
-#endif // DISABLE_RKWINDOWCATCHER
-	} else if (call == "edit") {
-		RK_ASSERT(!arglist.isEmpty());
-		new RKEditObjectAgent (arglist, in_chain);
-	} else if (call == "require") {
-		RK_ASSERT (!arglist.isEmpty());
-		QString lib_name = arglist.value(0);
-		KMessageBox::information(nullptr, i18n("The R-backend has indicated that in order to carry out the current task it needs the package '%1', which is not currently installed. We will open the package-management tool, and there you can try to locate and install the needed package.", lib_name), i18n("Require package '%1'", lib_name));
-		RKLoadLibsDialog::showInstallPackagesModal(nullptr, in_chain, QStringList(lib_name));
-	} else if (call == "doPlugin") {
-		if (arglist.count () >= 2) {
-			QString message;
-			bool ok;
-			RKComponentMap::ComponentInvocationMode mode = RKComponentMap::ManualSubmit;
-			if (arglist[1] == "auto") mode = RKComponentMap::AutoSubmit;
-			else if (arglist[1] == "submit") mode = RKComponentMap::AutoSubmitOrFail;
-			ok = RKComponentMap::invokeComponent(arglist[0], arglist.mid(2), mode, &message, in_chain);
-
-			if (!message.isEmpty ()) {
-				request->setResult(GenericRRequestResult(QVariant(), ok ? message : QString(), !ok ? message : QString()));
-			}
-		} else {
-			RK_ASSERT (false);
-		}
-	} else if (call == QStringLiteral ("output")) {
-		request->setResult(RKOutputDirectory::handleRCall(arglist, in_chain));
-	} else {
-		request->setResult(GenericRRequestResult::makeError(i18n("Unrecognized call '%1'", call)));
-	}
-	
-	closeChain (in_chain);
-}
-
 int addButtonToBox (QDialog *dialog, QDialogButtonBox *box, QDialogButtonBox::StandardButton which, const QString &text, const QString &def_text, bool is_default) {
 	if (text.isEmpty ()) return 0;
 	QPushButton *button = box->addButton (which);
diff --git a/rkward/rbackend/rkrinterface.h b/rkward/rbackend/rkrinterface.h
index ea936115c..b4e5f9a6b 100644
--- a/rkward/rbackend/rkrinterface.h
+++ b/rkward/rbackend/rkrinterface.h
@@ -95,9 +95,7 @@ private:
 	} command_logfile_mode;
 
 /** helper function to handle backend requests that (may) involve running additional R-"sub"-commands. TODO; This should probably be merged with processRBackendRequest.*/
-	void processHistoricalSubstackRequest (const QString &call, const QVariant &args, RCommand *parent_command, RBackendRequest *request);
-/** helper function to handle the bulk backend of requests that do not involve running sub-commands */
-	GenericRRequestResult processPlainGenericRequest (const QStringList &calllist);
+	GenericRRequestResult processRCallRequest (const QString &call, const QVariant &args, RCommandChain *in_chain);
 /** helper function to handle backend requests that do not inolve running sub-commands. */
 	void processRBackendRequest (RBackendRequest *request);
 



More information about the rkward-tracker mailing list