[education/rkward] /: Fix handling of carriage returns in R Console window

Thomas Friedrichsmeier null at kde.org
Sun Dec 25 10:31:54 GMT 2022


Git commit f84482ceee07e0af0a1ebb7e04ddd42bd07b993b by Thomas Friedrichsmeier.
Committed on 25/12/2022 at 10:31.
Pushed by tfry into branch 'master'.

Fix handling of carriage returns in R Console window

CCBUG: 463346

M  +2    -1    ChangeLog
M  +30   -16   rkward/rkconsole.cpp
M  +2    -0    rkward/rkconsole.h

https://invent.kde.org/education/rkward/commit/f84482ceee07e0af0a1ebb7e04ddd42bd07b993b

diff --git a/ChangeLog b/ChangeLog
index f9a8561a..bcf76bde 100644
--- a/ChangeLog
+++ b/ChangeLog
@@ -1,4 +1,5 @@
--- Fixed: Crash when renaming top level object from context menu
+- Fixed: Handling of carriage returns in R Console window (used in progress bars, importantly)
+- Fixed: Crash when renaming top level object from context menu
 
 --- Version 0.7.5 - Oct-24-2022
 - Fixed: Backend failed to start when installed in a path with spaces on Windows volumes without 8.3 support
diff --git a/rkward/rkconsole.cpp b/rkward/rkconsole.cpp
index c4697a93..5f97c527 100644
--- a/rkward/rkconsole.cpp
+++ b/rkward/rkconsole.cpp
@@ -486,6 +486,7 @@ void RKConsole::submitCommand () {
 	skip_command_display_lines = incomplete_command.count ('\n') + 1;	// incomplete command, and first line have already been shown.
 
 	doc->insertLine (doc->lines (), QString ());
+	output_cursor = doc->documentEnd();
 	if (!command.isEmpty ()) {
 		current_command = new RCommand (command, RCommand::User | RCommand::Console);
 		connect(current_command->notifier(), &RCommandNotifier::commandOutput, this, &RKConsole::newOutput);
@@ -533,11 +534,30 @@ void RKConsole::commandsListDown (bool context_sensitive) {
 	else qApp->beep ();
 }
 
+void RKConsole::rawWriteLine(const QString& line, QChar line_end) {
+	int existing_line_length = doc->lineLength(output_cursor.line());
+	if (output_cursor.column() < existing_line_length) {
+		int overwrite_end = qMin(existing_line_length, output_cursor.column() + line.length());
+		doc->removeText(KTextEditor::Range(output_cursor, KTextEditor::Cursor(output_cursor.line(), overwrite_end)));
+	}
+	doc->insertText(output_cursor, line);
+	output_cursor.setColumn(output_cursor.column() + line.length());
+	if (line_end == '\n') {
+		output_cursor.setColumn(doc->lineLength(output_cursor.line()));
+		doc->insertText(output_cursor, "\n");
+		output_cursor.setColumn(0);
+		output_cursor.setLine(output_cursor.line() + 1);
+	} else if (line_end == '\r') {
+		output_cursor.setColumn(0);
+	}
+}
+
 void RKConsole::newOutput (RCommand *command, const ROutput *output) {
 	RK_TRACE (APP);
 
 	int first_line = doc->lines () -1;
 	QString popped_line;
+	// TODO: rewrite utilizing output_cursor;
 	if (!command) {
 		// spontanteous R output, to be inserted _above_ the current command
 		// as a shortcut, we pop the last line, and reinsert in, later
@@ -545,25 +565,19 @@ void RKConsole::newOutput (RCommand *command, const ROutput *output) {
 		doc->removeLine(doc->lines() - 1);
 	}
 
-	// split by and handle carriage returns
-	const QString outstr = output->output;
+	// split by and handle carriage returns (important for progress bars)
+	const QString out = output->output;
+	int string_pos = -1;
 	int start_pos = 0;
-	int end_pos = outstr.size () - 1;
-	QChar c;
-	for (int pos = 0; pos <= end_pos; ++pos) {
-		c = output->output.at (pos);
-		if (c == '\r') {
-			/* NOTE: My first approach was to split the whole string by newlines, and insert each line separately. This allowed for a minor
-			 * optimization when hitting a carriage return (the string before the '\r' could simply be ignored, then), however it caused
-			 * around 10% slowdown when printing large amounts of output.
-			 * Thus, instead, when hitting a CR, we first insert everything before that into the document, then reset the line. */
-			doc->insertText (doc->documentEnd (), outstr.mid (start_pos, pos - start_pos));
-			doc->removeLine (doc->lines () - 1);
-			doc->insertLine (doc->lines (), QString ());
-			start_pos = pos + 1;
+	int end_pos = out.length();
+	while (++string_pos < end_pos) {
+		auto c = out.at(string_pos);
+		if (c == '\n' || c == '\r') {
+			rawWriteLine(out.mid(start_pos, string_pos - start_pos), c);
+			start_pos = string_pos+1;
 		}
 	}
-	if (start_pos <= end_pos) doc->insertText (doc->documentEnd (), outstr.mid (start_pos, end_pos - start_pos + 1));
+	if (start_pos < end_pos) rawWriteLine(out.mid(start_pos, string_pos - start_pos + 1), ' ');
 
 	int end_line = doc->lines () -1;
 	if (output->type != ROutput::Output || (!command)) {
diff --git a/rkward/rkconsole.h b/rkward/rkconsole.h
index 1753c3e9..a3f11e49 100644
--- a/rkward/rkconsole.h
+++ b/rkward/rkconsole.h
@@ -169,6 +169,8 @@ private:
 	int current_command_displayed_up_to;
 	int skip_command_display_lines;
 	bool previous_chunk_was_piped;
+	KTextEditor::Cursor output_cursor;
+	void rawWriteLine(const QString &line, QChar line_end);
 };
 
 /** A part interface to RKConsole. Provides the context-help functionality


More information about the rkward-tracker mailing list