[education/rkward] rkward: Add navigation between code chunks
Thomas Friedrichsmeier
null at kde.org
Mon May 26 15:06:38 BST 2025
Git commit 6d8b6e07e01c5f7e8dc923b25c580e793447c19f by Thomas Friedrichsmeier.
Committed on 26/05/2025 at 14:06.
Pushed by tfry into branch 'master'.
Add navigation between code chunks
M +44 -2 rkward/autotests/rkparsedscript_test.cpp
M +42 -2 rkward/misc/rkparsedscript.cpp
M +4 -0 rkward/misc/rkparsedscript.h
https://invent.kde.org/education/rkward/-/commit/6d8b6e07e01c5f7e8dc923b25c580e793447c19f
diff --git a/rkward/autotests/rkparsedscript_test.cpp b/rkward/autotests/rkparsedscript_test.cpp
index ae282a439..6e07e89a6 100644
--- a/rkward/autotests/rkparsedscript_test.cpp
+++ b/rkward/autotests/rkparsedscript_test.cpp
@@ -61,7 +61,10 @@ class RKParsedScriptTest : public QObject {
void sanityTestHelper() {
for (int startpos = 0; startpos < script.length(); ++startpos) {
- const auto ctx0 = ps.contextAtPos(startpos);
+ QVERIFY(ps.contextAtPos(startpos).valid());
+ }
+ for (unsigned int i = 0; i < ps.context_list.size(); ++i) {
+ RKParsedScript::ContextIndex ctx0(i);
auto ctx = ctx0;
while (ctx.valid())
ctx = ps.nextContext(ctx);
@@ -74,6 +77,20 @@ class RKParsedScriptTest : public QObject {
ctx = ctx0;
while (ctx.valid())
ctx = ps.nextStatement(ctx);
+ ctx = ps.firstContextInStatement(ctx0); // NOTE: This one may stay at the same position
+ ctx = ctx0;
+ while (ctx.valid())
+ ctx = ps.nextOuter(ctx);
+ ctx = ctx0;
+ while (ctx.valid())
+ ctx = ps.nextToplevel(ctx);
+ ctx = ctx0;
+ while (ctx.valid())
+ ctx = ps.nextStatementOrInner(ctx);
+ ctx = ctx0;
+ while (ctx.valid())
+ ctx = ps.nextCodeChunk(ctx);
+
ctx = ctx0;
while (ctx.valid())
ctx = ps.prevContext(ctx);
@@ -86,6 +103,20 @@ class RKParsedScriptTest : public QObject {
ctx = ctx0;
while (ctx.valid())
ctx = ps.prevStatement(ctx);
+ ctx = ps.lastContextInStatement(ctx0); // May stay in same position
+ ctx = ctx0;
+ while (ctx.valid())
+ ctx = ps.prevOuter(ctx);
+ ctx = ctx0;
+ while (ctx.valid())
+ ctx = ps.prevToplevel(ctx);
+ ctx = ctx0;
+ while (ctx.valid())
+ ctx = ps.prevStatementOrInner(ctx);
+ ctx = ctx0;
+ while (ctx.valid())
+ ctx = ps.prevCodeChunk(ctx);
+
ctx = ctx0;
while (ctx.valid())
ctx = ps.parentRegion(ctx);
@@ -274,7 +305,18 @@ class RKParsedScriptTest : public QObject {
ctx = moveAndCheck(ps.prevStatement(ctx), u"symb13"_s);
ctx = moveAndCheck(ps.prevStatement(ctx), u"symb11"_s);
QVERIFY(!ps.prevStatement(ctx).valid());
- //testLog("%s", qPrintable(ps.serialize()));
+
+ ctx = ps.contextAtPos(script.indexOf(u".some"));
+ ctx = moveAndCheck(ps.nextCodeChunk(ctx), u"inline"_s);
+ ctx = moveAndCheck(ps.nextCodeChunk(ctx), u"symb01"_s);
+ ctx = moveAndCheck(ps.nextCodeChunk(ctx), u"symb11"_s);
+ QVERIFY(!ps.nextCodeChunk(ctx).valid());
+
+ ctx = ps.contextAtPos(script.indexOf(u"symb13"));
+ ctx = moveAndCheck(ps.prevCodeChunk(ctx), u"symb01"_s);
+ ctx = moveAndCheck(ps.prevCodeChunk(ctx), u"inline"_s);
+ ctx = moveAndCheck(ps.prevCodeChunk(ctx), u".some"_s);
+ QVERIFY(!ps.prevCodeChunk(ctx).valid());
}
};
diff --git a/rkward/misc/rkparsedscript.cpp b/rkward/misc/rkparsedscript.cpp
index 71f3dcbfd..956641b79 100644
--- a/rkward/misc/rkparsedscript.cpp
+++ b/rkward/misc/rkparsedscript.cpp
@@ -17,7 +17,7 @@ RKParsedScript::RKParsedScript(const QString &content, bool rmd) : prevtype(None
context_list.reserve(200); // just a very wild guess.
if (rmd) {
- context_list.emplace_back(Top, -1, content.size());
+ context_list.emplace_back(None, -1, content.size());
int i = -1;
while (i < content.size()) {
i = addNextMarkdownChunk(i, content);
@@ -332,6 +332,46 @@ RKParsedScript::ContextIndex RKParsedScript::prevOuter(const ContextIndex from)
return firstContextInStatement(parentRegion(from));
}
+RKParsedScript::ContextIndex RKParsedScript::nextCodeChunk(const ContextIndex from) const {
+ RK_TRACE(MISC);
+ if (!from.valid()) return ContextIndex();
+ // NOTE: not using nextContext() for iterating, here, as that stops at Top regions.
+ unsigned int i = from.index;
+ do {
+ if (context_list.at(i).type == Top) break;
+ } while (++i < context_list.size());
+ // yes, we want this twice, as chunks are delimited by a top context at start and end
+ while (++i < context_list.size()) {
+ if (context_list.at(i).type == Top) break;
+ }
+ do {
+ ++i;
+ } while (i < context_list.size() && context_list.at(i).type == Delimiter);
+ if (i < context_list.size()) return ContextIndex(i);
+ return ContextIndex();
+}
+
+RKParsedScript::ContextIndex RKParsedScript::prevCodeChunk(const ContextIndex from) const {
+ RK_TRACE(MISC);
+ if (!from.valid()) return ContextIndex();
+ // NOTE: not using nextContext() for iterating, here, as that stops at Top regions.
+ int i = from.index;
+ do {
+ if (context_list.at(i).type == Top) break;
+ } while (--i >= 0);
+ // yes, we want this twice, as chunks are delimited by a top context at start and end
+ while (--i >= 0) {
+ if (context_list.at(i).type == Top) break;
+ }
+ do {
+ --i;
+ } while (i >= 0 && context_list.at(i).type != Top);
+ do {
+ ++i;
+ } while (i < context_list.size() && context_list.at(i).type == Delimiter);
+ return ContextIndex(i);
+}
+
RKParsedScript::ContextIndex RKParsedScript::nextToplevel(const ContextIndex from) const {
RK_TRACE(MISC);
auto ctx = from;
@@ -384,7 +424,7 @@ RKParsedScript::ContextIndex RKParsedScript::prevStatementOrInner(const ContextI
auto candidate = nextContext(cparent);
while (true) {
ci = nextStatementOrInner(candidate);
- if (ci.index >= from.index) return candidate;
+ if (!ci.valid() || ci.index >= from.index) return candidate;
candidate = ci;
}
}
diff --git a/rkward/misc/rkparsedscript.h b/rkward/misc/rkparsedscript.h
index 6c2ecbefe..2e5635354 100644
--- a/rkward/misc/rkparsedscript.h
+++ b/rkward/misc/rkparsedscript.h
@@ -101,6 +101,10 @@ class RKParsedScript {
ContextIndex nextStatementOrInner(const ContextIndex from) const;
ContextIndex prevStatementOrInner(const ContextIndex from) const;
+ /** Next code Region in R Markdown document */
+ ContextIndex nextCodeChunk(const ContextIndex from) const;
+ ContextIndex prevCodeChunk(const ContextIndex from) const;
+
/** retrieve the context at the given index. Safe to call, even with an invalid index
* (in which case the outermost context will be returned). */
const Context &getContext(const ContextIndex index) const {
More information about the rkward-tracker
mailing list