[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