[education/rkward] rkward/rbackend: Feed CommandLineIn-events directly through the output stream (reduces blocking)

Thomas Friedrichsmeier null at kde.org
Sun Sep 14 19:26:16 BST 2025


Git commit 1456c7df5c0f5eab1c0b185ecc0da7e2d24b190c by Thomas Friedrichsmeier.
Committed on 07/09/2025 at 06:13.
Pushed by tfry into branch 'master'.

Feed CommandLineIn-events directly through the output stream (reduces blocking)

M  +5    -4    rkward/rbackend/rcommand.h
M  +4    -5    rkward/rbackend/rkrbackend.cpp
M  +1    -1    rkward/rbackend/rkrbackendprotocol_shared.cpp
M  +5    -7    rkward/rbackend/rkrbackendprotocol_shared.h
M  +11   -9    rkward/rbackend/rkrinterface.cpp

https://invent.kde.org/education/rkward/-/commit/1456c7df5c0f5eab1c0b185ecc0da7e2d24b190c

diff --git a/rkward/rbackend/rcommand.h b/rkward/rbackend/rcommand.h
index 8184b9c07..7d6648cad 100644
--- a/rkward/rbackend/rcommand.h
+++ b/rkward/rbackend/rcommand.h
@@ -48,10 +48,11 @@ class RCommandChain {
 QString is, that additionally we store information on whether the output was "normal", "warning", or an "error". */
 struct ROutput {
 	enum ROutputType {
-		NoOutput, /**< No output. Rarely used. */
-		Output,   /**< normal output */
-		Warning,  /**< R warning */
-		Error     /**< R error */
+		NoOutput,     /**< No output. Rarely used. */
+		Output,       /**< normal output */
+		Warning,      /**< R warning */
+		Error,        /**< R error */
+		CommandLineIn /**< Special type for marking that a source line should be interleaved with the output at this point */
 	};
 	ROutput() : type(NoOutput) {};
 	ROutput(ROutputType type, const QString &output) : type(type), output(output) {};
diff --git a/rkward/rbackend/rkrbackend.cpp b/rkward/rbackend/rkrbackend.cpp
index 676cc6e88..080a0b6bf 100644
--- a/rkward/rbackend/rkrbackend.cpp
+++ b/rkward/rbackend/rkrbackend.cpp
@@ -201,11 +201,10 @@ void RKTransmitNextUserCommandChunk(unsigned char *buf, int buflen) {
 	RKRBackend::repl_status.user_command_status = RKRBackend::RKReplStatus::UserCommandTransmitted;
 
 	if (reached_newline || reached_eof) {
-		// Making this request synchronous is a bit painful. However, without this, it's extremely difficult to get correct interleaving of output and command lines
-		RKRSupport::InterruptSuspension susp; // This could also result in interrupts, in corner cases, so lets suspend those, for the minute
-		RBackendRequest req(true, RBackendRequest::CommandLineIn);
-		req.params[QStringLiteral("commandid")] = RKRBackend::this_pointer->current_command->id;
-		RKRBackend::this_pointer->handleRequest(&req);
+		// feed a marker into the output stream that a source line of the command shall be interleaved into the output, at this point (if running in the console)
+		RKRBackend::this_pointer->fetchStdoutStderr(true);
+		// NOTE: Output literal is not used, but 0-length "output" would be discarded
+		RKRBackend::this_pointer->handleOutput(u"\n"_s, 1, ROutput::CommandLineIn, false);
 	}
 }
 
diff --git a/rkward/rbackend/rkrbackendprotocol_shared.cpp b/rkward/rbackend/rkrbackendprotocol_shared.cpp
index 9a41fad19..31710194c 100644
--- a/rkward/rbackend/rkrbackendprotocol_shared.cpp
+++ b/rkward/rbackend/rkrbackendprotocol_shared.cpp
@@ -145,7 +145,7 @@ QString RKROutputBuffer::popOutputCapture(bool highlighted) {
 void appendToOutputList(ROutputList *list, const QString &output, ROutput::ROutputType output_type) {
 	// No trace
 	// Merge with previous output fragment, if of the same type
-	if (!list->isEmpty() && list->last().type == output_type) {
+	if (!list->isEmpty() && list->last().type == output_type && output_type != ROutput::CommandLineIn) {
 		list->last().output.append(output);
 	} else {
 		QString spaced = output;
diff --git a/rkward/rbackend/rkrbackendprotocol_shared.h b/rkward/rbackend/rkrbackendprotocol_shared.h
index 3c675f58e..7e1dacf17 100644
--- a/rkward/rbackend/rkrbackendprotocol_shared.h
+++ b/rkward/rbackend/rkrbackendprotocol_shared.h
@@ -39,13 +39,11 @@ class RBackendRequest {
 		RCallRequest, // 7
 		SetParamsFromBackend,
 		Debugger,
-		CommandLineIn, /**< The next line of the current user command has been submitted in the backend. */
-		Output,
-		/**< A piece of output. Note: If the backend runs in a single process, output is handled in a pull fashion, instead of using requests. */ // 11
-		Interrupt,                                                                                                                                /**< Interrupt evaluation. This request type originates in the frontend, not the backend. */
-		PriorityCommand,                                                                                                                          /**< Send a command to be run during R's event processing. This request type originates in the frontend, not the backend. */
-		OutputStartedNotification,                                                                                                                /**< Only used in the frontend: Notification that a new bit of output has arrived. Used to trigger flushing after a timeout. */
-		OtherRequest                                                                                                                              /**< Any other type of request. Note: which requests are in the enum, and which are not has mostly historical reasons. @see params */
+		Output,                    /**< A piece of output. Note: If the backend runs in a single process, output is handled in a pull fashion, instead of using requests. */
+		Interrupt,                 /**< Interrupt evaluation. This request type originates in the frontend, not the backend. */
+		PriorityCommand,           /**< Send a command to be run during R's event processing. This request type originates in the frontend, not the backend. */
+		OutputStartedNotification, /**< Only used in the frontend: Notification that a new bit of output has arrived. Used to trigger flushing after a timeout. */
+		OtherRequest               /**< Any other type of request. Note: which requests are in the enum, and which are not has mostly historical reasons. @see params */
 	};
 
 	RBackendRequest(bool synchronous, RCallbackType type);
diff --git a/rkward/rbackend/rkrinterface.cpp b/rkward/rbackend/rkrinterface.cpp
index 8df7b22ea..e37b17811 100644
--- a/rkward/rbackend/rkrinterface.cpp
+++ b/rkward/rbackend/rkrinterface.cpp
@@ -471,6 +471,16 @@ void RInterface::flushOutput(bool forced) {
 	const ROutputList list = backendprotocol->flushOutput(forced);
 
 	for (const ROutput &output : list) {
+		if (output.type == ROutput::CommandLineIn) {
+			RCommand *command = all_current_commands.value(0, nullptr); // User command will always be the first.
+			if ((command == nullptr) || (!command->type() & RCommand::User)) {
+				RK_ASSERT(false);
+			} else {
+				command->commandLineIn();
+			}
+			continue;
+		}
+
 		if (all_current_commands.isEmpty()) {
 			RK_DEBUG(RBACKEND, DL_DEBUG, "output without receiver'%s'", qPrintable(output.output));
 			if (RKConsole::mainConsole()) RKConsole::mainConsole()->insertSpontaneousROutput(output); // the "if" is to prevent crash, should output arrive during exit
@@ -829,15 +839,7 @@ void RInterface::processRBackendRequest(RBackendRequest *request) {
 	// first, copy out the type. Allows for easier typing below
 	RBackendRequest::RCallbackType type = request->type;
 
-	if (type == RBackendRequest::CommandLineIn) {
-		int id = request->params[QStringLiteral("commandid")].toInt();
-		RCommand *command = all_current_commands.value(0, nullptr); // User command will always be the first.
-		if ((command == nullptr) || (command->id() != id)) {
-			RK_ASSERT(false);
-		} else {
-			command->commandLineIn();
-		}
-	} else if (type == RBackendRequest::ShowMessage) {
+	if (type == RBackendRequest::ShowMessage) {
 		QString caption = request->params[QStringLiteral("caption")].toString();
 		QString message = request->params[QStringLiteral("message")].toString();
 		QString button_yes = request->params[QStringLiteral("button_yes")].toString();



More information about the rkward-tracker mailing list