[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