[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