[education/rkward] rkward: Implement some final bits for code navigation

Thomas Friedrichsmeier null at kde.org
Mon May 26 15:06:38 BST 2025


Git commit f0d318ee7bc8726d49171237af0670f3138d962e by Thomas Friedrichsmeier.
Committed on 26/05/2025 at 14:06.
Pushed by tfry into branch 'master'.

Implement some final bits for code navigation

A  +57   -0    rkward/pages/rkward_code_navigation.rkh
M  +19   -8    rkward/windows/rkcommandeditorwindow.cpp
M  +3    -1    rkward/windows/rkcommandeditorwindowpart.rc
M  +1    -0    rkward/windows/rkhtmlwindow.cpp

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

diff --git a/rkward/pages/rkward_code_navigation.rkh b/rkward/pages/rkward_code_navigation.rkh
new file mode 100644
index 000000000..5d7ffa4ae
--- /dev/null
+++ b/rkward/pages/rkward_code_navigation.rkh
@@ -0,0 +1,57 @@
+<!DOCTYPE rkhelp>
+<!--- This file is part of the RKWard project (https://rkward.kde.org).
+SPDX-FileCopyrightText: by Thomas Friedrichsmeier <thomas.friedrichsmeier at kdemail.net>
+SPDX-FileContributor: The RKWard Team <rkward-devel at kde.org>
+SPDX-License-Identifier: GPL-2.0-or-later
+-->
+<document>
+	<title>Code Navigation</title>
+	<summary>
+The code navigation feature in script editor windows allows you to navigate R code based on its syntactical structure, e.g. quickly jumping to the next
+statement outside the current scope or to the previous top level statement. Several navgation features are deliberately close to debbuggin in R's
+<link href="rkward://rhelp/browser">browser()</link>-command, but importantly this works entirely without actually running the code.
+	</summary>
+
+	<section title="Available commands" id="commands">
+To bring up the code naviagtion mode in script (R or R markdown) window, press the key combination "Meta+n" (shortcut customizable), or select it from the <i>Run</i>-menu. Subsequent single key presses invoke the following actions:
+
+<ul>
+	<li><b>n / N</b> Go to the next (n) or previous (N) statement at the current level, or the next outer level</li>
+	<li><b>i / I</b> Like n/N, but also step into inner contexts (such as the definition of a function)</li>
+	<li><b>o / O</b> Jump directly to the next / previous statement outside the current level (e.g. the first statement after a function body)</li>
+	<li><b>t / T</b> Go to the next / previous statement at the topmost level of the script</li>
+	<li><b>c / C</b> (For R Markdown documents, only:) Go to the next / previous code chunk</li>
+	<li><b>1 / !</b> Go to the top / bottom of the document</li>
+	<li><b>s</b> Select the current statement</li>
+	<li><b>S</b> (For R Markdown documents, only:) Select add code in the current chunk</li>
+	<li><b>Backspace</b> Go back to the position before the latest action.</li>
+	<li><b>Esc</b> Exit code navigation mode, and reset the cursor / selection to the state before entering it.</li>
+	<li><b>Return</b> Exit code navigation mode, keeping current position/selection.</li>
+	<li>Code navigation will also end, if you click anywhere else in the script, switch to a different window, or the script is modified.</li>
+</ul>
+
+Finally, you can continue to use regular shortcuts, while in code navigation mode. This includes, importantly:
+<ul>
+	<li><b>Ctrl+Return</b> Run the current line or selection.</li>
+</ul>
+	</section>
+
+	<section title="Notes and Tips" id="tips">
+The 'n' action is meant to remind of the corresponding step command in the R debugger (see <link href="rkward://rhelp/browser"/>), but it is
+not the same. The most important difference being that the code navigation, described here, works on a fully static parsed representation of the script,
+and does not attempt to follow control flow, at all.
+
+Further, what exactly is a "statement", and "inner", or and "outer" context may not always be quite clear, and may not always correspond exactly to the way
+the R engine sees your code. Instead, these commands are meant to behave in a way, that we hope makes sense, intuitively. Remember to always control, visually, what you are doing, before you hit Ctrl+Enter.
+
+Most commands show a tendency to move towards outer contexts, so 'n' and 'N' are not exact opposites. Remeber you can undo your last step using Backspace, 
+if you went past the location you were looking for.
+	</section>
+
+	<related>
+<ul>
+	<li><link href="rkward://page/rkward_for_r_users"/></li>
+	<li><link href="rkward://rhelp/browser"/></li>
+</ul>
+	</related>
+</document>
diff --git a/rkward/windows/rkcommandeditorwindow.cpp b/rkward/windows/rkcommandeditorwindow.cpp
index e161dacd1..a6e9bc84e 100644
--- a/rkward/windows/rkcommandeditorwindow.cpp
+++ b/rkward/windows/rkcommandeditorwindow.cpp
@@ -75,9 +75,10 @@ class RKCodeNavigation : public QWidget {
 
 		view->installEventFilter(this);
 		if (view->window()) view->window()->installEventFilter(this);
+		connect(doc, &KTextEditor::Document::textChanged, this, &RKCodeNavigation::deleteLater);
 
 		auto box = new QVBoxLayout(this);
-		auto label = new QLabel(i18n("<b>Code Navigation</b> (<a href=\"rkward:://pages/rkward_code_navigation\">Help</a>)"));
+		auto label = new QLabel(i18n("<b>Code Navigation</b> (<a href=\"rkward://page/rkward_code_navigation\">Help</a>)"));
 		QObject::connect(label, &QLabel::linkActivated, RKWorkplace::mainWorkplace(), &RKWorkplace::openAnyUrlString);
 		box->addWidget(label);
 
@@ -98,6 +99,7 @@ class RKCodeNavigation : public QWidget {
 		initial.selection = view->selectionRange();
 		stored_positions.append(initial);
 		stored_size = size();
+		updateLabel();
 	}
 
 	void updatePos() {
@@ -140,6 +142,7 @@ class RKCodeNavigation : public QWidget {
 		// translate final position back to cursor coordinates
 		if (!newpos.selection.isEmpty()) {
 			view->setSelection(newpos.selection);
+			view->setCursorPosition(newpos.selection.start());
 		} else {
 			if (message->isVisible()) {
 				message->hide();
@@ -149,6 +152,7 @@ class RKCodeNavigation : public QWidget {
 				});
 			}
 			view->setCursorPosition(positionToCursor(newpos.pos));
+			view->setSelection(KTextEditor::Range(-1, -1, -1, -1));
 		}
 
 		updateLabel();
@@ -221,7 +225,7 @@ class RKCodeNavigation : public QWidget {
 				} else {
 					message->setText(i18nc("Keep this short", "Search hit top.\n'C' to move to previous chunk."));
 				}
-			} else{
+			} else {
 				if (command.toLower() == command) {
 					message->setText(i18nc("Keep this short", "Search hit bottom.\n'1' to move to top."));
 				} else {
@@ -243,15 +247,21 @@ class RKCodeNavigation : public QWidget {
 		} else if (from == input && event->type() == QEvent::KeyPress) {
 			auto ke = static_cast<QKeyEvent *>(event);
 			const auto text = ke->text();
-			if (ke->key() == Qt::Key_Backspace) {
-				if (stored_positions.size() > 1) {
-					navigate(stored_positions.takeLast());
-					return true;
-				}
+			if (ke->modifiers() == Qt::NoModifier && ke->key() == Qt::Key_Backspace && stored_positions.size() > 1) {
+				stored_positions.pop_back();
+				navigate(stored_positions.last());
+			} else if (ke->modifiers() == Qt::NoModifier && (ke->key() == Qt::Key_Enter || ke->key() == Qt::Key_Return)) {
+				deleteLater();
+			} else if (ke->modifiers() == Qt::NoModifier && (ke->key() == Qt::Key_Escape)) {
+				RK_ASSERT(!stored_positions.isEmpty());
+				navigate(stored_positions.first());
+				deleteLater();
 			} else if (!text.simplified().isEmpty()) {
 				handleCommand(text.back());
-				return true;
+			} else {
+				return false;
 			}
+			return true;
 		}
 		return false;
 	}
@@ -267,6 +277,7 @@ class RKCodeNavigation : public QWidget {
 			input->setText(sequence);
 		}
 	}
+
   public:
 	static void doNavigation(KTextEditor::View *view, QWidget *parent) {
 		auto w = new RKCodeNavigation(view, parent);
diff --git a/rkward/windows/rkcommandeditorwindowpart.rc b/rkward/windows/rkcommandeditorwindowpart.rc
index 32f9bf5e1..c016e3038 100644
--- a/rkward/windows/rkcommandeditorwindowpart.rc
+++ b/rkward/windows/rkcommandeditorwindowpart.rc
@@ -4,7 +4,7 @@ SPDX-FileCopyrightText: by Thomas Friedrichsmeier <thomas.friedrichsmeier at kdemai
 SPDX-FileContributor: The RKWard Team <rkward-devel at kde.org>
 SPDX-License-Identifier: GPL-2.0-or-later
 -->
-<kpartgui name="rkward_commandeditor" version="710">
+<kpartgui name="rkward_commandeditor" version="821">
 	<MenuBar>
 		<Menu name="file"><text>&File</text></Menu>  <!-- Define placeholder to work around menu ordering bug -->
 		<Menu name="edit"><text>&Edit</text>
@@ -19,6 +19,7 @@ SPDX-License-Identifier: GPL-2.0-or-later
 			<Action name="render_preview" group="postrun_actions_merge"/>
 			<Separator group="postrun_actions_merge"/>
 			<Action name="setwd_to_script" group="postrun_actions_merge"/>
+			<Action name="code_navigation" group="postrun_actions_merge"/>
 		</Menu>
 		<Menu name="settings"><text>&Settings</text>
 			<Action name="configure_commandeditor"></Action>
@@ -27,6 +28,7 @@ SPDX-License-Identifier: GPL-2.0-or-later
 
 	<ToolBar fullWidth="true" name="mainToolBar">
 		<Action name="run_block" group="posttoolbar_actions_merge"/>
+		<Action name="code_navigation" group="posttoolbar_actions_merge"/>
 		<Action name="render_preview" group="posttoolbar_actions_merge"/>
 		<Action name="setwd_to_script" group="posttoolbar_actions_merge"/>
 	</ToolBar>
diff --git a/rkward/windows/rkhtmlwindow.cpp b/rkward/windows/rkhtmlwindow.cpp
index 1a44874d0..a1e3e155b 100644
--- a/rkward/windows/rkhtmlwindow.cpp
+++ b/rkward/windows/rkhtmlwindow.cpp
@@ -465,6 +465,7 @@ void RKHTMLWindow::openRKHPage(const QUrl &url) {
 	RK_TRACE(APP);
 
 	RK_ASSERT(isRKWardUrl(url));
+	RK_DEBUG(APP, DL_DEBUG, "openRKHPage %d", qPrintable(url.url()));
 	if (url != this->url()) changeURL(url); // see ::refresh()
 	bool ok = false;
 	if ((url.host() == QLatin1String("component")) || (url.host() == QLatin1String("page"))) {



More information about the rkward-tracker mailing list