[education/rkward] /: Allow registering user preview functions for script windows
Thomas Friedrichsmeier
null at kde.org
Mon Jan 12 19:55:45 GMT 2026
Git commit 3ad06e225072889e11e04481e92bad925bff0ed9 by Thomas Friedrichsmeier.
Committed on 12/01/2026 at 19:55.
Pushed by tfry into branch 'master'.
Allow registering user preview functions for script windows
M +1 -1 VERSION.cmake
M +3 -0 rkward/rbackend/rkrinterface.cpp
M +1 -1 rkward/rbackend/rpackages/rkward/DESCRIPTION
M +1 -0 rkward/rbackend/rpackages/rkward/NAMESPACE
M +48 -0 rkward/rbackend/rpackages/rkward/R/rk.preview-functions.R
M +1 -1 rkward/rbackend/rpackages/rkward/man/rk.eval.as.preview.Rd
A +57 -0 rkward/rbackend/rpackages/rkward/man/rk.register.script.preview.Rd
A +20 -0 rkward/rbackend/rpackages/rkward/man/rk.render.markdown.preview.Rd
M +20 -2 rkward/windows/rkcommandeditorwindow.cpp
M +2 -0 rkward/windows/rkcommandeditorwindow.h
https://invent.kde.org/education/rkward/-/commit/3ad06e225072889e11e04481e92bad925bff0ed9
diff --git a/VERSION.cmake b/VERSION.cmake
index afb316a9f..f6099e76e 100644
--- a/VERSION.cmake
+++ b/VERSION.cmake
@@ -1,3 +1,3 @@
# DO NOT CHANGE THIS FILE MANUALLY!
# It will be overwritten by scripts/set_dist_version.sh
-SET(RKVERSION_NUMBER 0.8.2z+0.8.3+devel3)
+SET(RKVERSION_NUMBER 0.8.2z+0.8.3+devel4)
diff --git a/rkward/rbackend/rkrinterface.cpp b/rkward/rbackend/rkrinterface.cpp
index 583156007..0bae9f0d9 100644
--- a/rkward/rbackend/rkrinterface.cpp
+++ b/rkward/rbackend/rkrinterface.cpp
@@ -34,6 +34,7 @@ SPDX-License-Identifier: GPL-2.0-or-later
#include "../settings/rksettingsmoduleplugins.h"
#include "../settings/rksettingsmoduler.h"
#include "../windows/rcontrolwindow.h"
+#include "../windows/rkcommandeditorwindow.h"
#include "../windows/rkcommandlog.h"
#include "../windows/rkhtmlwindow.h"
#include "../windows/rkwindowcatcher.h"
@@ -743,6 +744,8 @@ GenericRRequestResult RInterface::processRCallRequest(const QString &call, const
dialog->addRCommand(command, true);
issueCommand(command, in_chain);
dialog->doNonModal(true);
+ } else if (call == QLatin1String("registerScriptPreviewMode")) {
+ RKCommandEditorWindow::registerUserPreviewMode(arglist.value(0), arglist.value(1), arglist.value(2), arglist.value(3));
} else if (call == QLatin1String("rstarted")) {
// The backend thread has finished basic initialization, but we still have more to do...
backend_error.message.append(args.toString());
diff --git a/rkward/rbackend/rpackages/rkward/DESCRIPTION b/rkward/rbackend/rpackages/rkward/DESCRIPTION
index 751b3e55c..686792624 100755
--- a/rkward/rbackend/rpackages/rkward/DESCRIPTION
+++ b/rkward/rbackend/rpackages/rkward/DESCRIPTION
@@ -15,7 +15,7 @@ LazyLoad: yes
Authors at R: c(person(given="Thomas", family="Friedrichsmeier", email="thomas.friedrichsmeier at kdemail.net", role=c("aut")), person(given="the RKWard", family="team",
email="rkward at kde.org", role=c("cre","aut")))
Version: 0.8.3
-Date: 2025-11-02
+Date: 2025-12-13
RoxygenNote: 7.3.3
Collate:
'base_overrides.R'
diff --git a/rkward/rbackend/rpackages/rkward/NAMESPACE b/rkward/rbackend/rpackages/rkward/NAMESPACE
index 4fa6f0687..b857046a5 100644
--- a/rkward/rbackend/rpackages/rkward/NAMESPACE
+++ b/rkward/rbackend/rpackages/rkward/NAMESPACE
@@ -100,6 +100,7 @@ export(rk.print.literal)
export(rk.printer.device)
export(rk.record.commands)
export(rk.record.plot)
+export(rk.register.script.preview)
export(rk.relative.src.line)
export(rk.removethis.plot)
export(rk.rename.in.container)
diff --git a/rkward/rbackend/rpackages/rkward/R/rk.preview-functions.R b/rkward/rbackend/rpackages/rkward/R/rk.preview-functions.R
index 957b3a9eb..399150578 100644
--- a/rkward/rbackend/rpackages/rkward/R/rk.preview-functions.R
+++ b/rkward/rbackend/rpackages/rkward/R/rk.preview-functions.R
@@ -214,3 +214,51 @@ function registerPlot(element) {
# clean up is done via on.exit handlers, above
invisible()
}
+
+#' Register a custom preview mode for script windows
+#'
+#' @param id An id string to allow subsequent modification or deletion of the preview mode
+#' @param label The label shown in the GUI for this preview mode
+#' @param file.extension The input file (esp. if previously unsaved) will be given this file extension. May be relevant for
+#' certain external tools.
+#' @param statement The name of a function to run, when the preview needs to be updated, given as a string (see details).
+#'
+#' @details The key parameter is \code{statement}, which is string that will be evaulated as a function, usually the name of a
+#' custom function inside \code{globalenv()}. If this is an empty string, the preview mode is removed.
+#'
+#' The given function string needs to be callable with the following function signature:
+#' \code{my_function(input.file, output.dir, preview.id, ...)}. Here,
+#'
+#' \itemize{
+#' \item{\code{input.file} character string specifying the (temporary) input file with the script's contents}
+#' \item{\code{output.dir} character string specifying a directory suitable for keeping temporary files related
+#' to this preview. This will be cleared, automatically, as needed.}
+#' \item{\code{preview.id} a unique id of the preview window. This could be used to keep temporary data, for instance.}
+#' \item{\code{...} for future extension}
+#' }
+#'
+#' As a final step the preview funtion will generally call either \code{rk.show.html()}, or \code{rk.show.pdf()}, but it could
+#' also show data using \code{rk.edit}, or produce a plot. Some care has to be taken in order not to interfere with existing
+#' device windows, or output windows. Looking at the implementation of e.g. \code{rk.eval.as.preview()} may be instructive.
+#' (This feature is targetted at power users, expecting them to consider and deal with any side-effects, themselves.)
+#'
+#' @examples
+#' \dontrun{
+#' my_csvpreview <- function(infile, outdir, previewid, ...) {
+#' require("R2HTML")
+#' suppressWarnings({
+#' data <- read.csv(infile)
+#' })
+#' outfile <- paste0(outdir, "/out.html")
+#' unlink(outfile) # clear previous output
+#' HTML(data, file=outfile)
+#' rk.show.html(outfile)
+#' }
+#' rk.register.script.preview("csv", "Preview CSV data", ".csv", "my_csvpreview"))
+#' }
+#'
+#' @rdname rk.register.script.preview
+#' @export
+rk.register.script.preview <- function(id, label, file.extension=".R", statement) {
+ .rk.call("registerScriptPreviewMode", c(id, label, file.extension, statement))
+}
diff --git a/rkward/rbackend/rpackages/rkward/man/rk.eval.as.preview.Rd b/rkward/rbackend/rpackages/rkward/man/rk.eval.as.preview.Rd
index e9ce26afa..e32cbfd97 100644
--- a/rkward/rbackend/rpackages/rkward/man/rk.eval.as.preview.Rd
+++ b/rkward/rbackend/rpackages/rkward/man/rk.eval.as.preview.Rd
@@ -1,5 +1,5 @@
% Generated by roxygen2: do not edit by hand
-% Please edit documentation in R/rk.filename-functions.R
+% Please edit documentation in R/rk.preview-functions.R
\name{rk.eval.as.preview}
\alias{rk.eval.as.preview}
\title{Evaluate the given input file, recording a transcript to an HTML output file (including on-screen plots)}
diff --git a/rkward/rbackend/rpackages/rkward/man/rk.register.script.preview.Rd b/rkward/rbackend/rpackages/rkward/man/rk.register.script.preview.Rd
new file mode 100644
index 000000000..9b01acdd9
--- /dev/null
+++ b/rkward/rbackend/rpackages/rkward/man/rk.register.script.preview.Rd
@@ -0,0 +1,57 @@
+% Generated by roxygen2: do not edit by hand
+% Please edit documentation in R/rk.preview-functions.R
+\name{rk.register.script.preview}
+\alias{rk.register.script.preview}
+\title{Register a custom preview mode for script windows}
+\usage{
+rk.register.script.preview(id, label, file.extension = ".R", statement)
+}
+\arguments{
+\item{id}{An id string to allow subsequent modification or deletion of the preview mode}
+
+\item{label}{The label shown in the GUI for this preview mode}
+
+\item{file.extension}{The input file (esp. if previously unsaved) will be given this file extension. May be relevant for
+certain external tools.}
+
+\item{statement}{The name of a function to run, when the preview needs to be updated, given as a string (see details).}
+}
+\description{
+Register a custom preview mode for script windows
+}
+\details{
+The key parameter is \code{statement}, which is string that will be evaulated as a function, usually the name of a
+ custom function inside \code{globalenv()}. If this is an empty string, the preview mode is removed.
+
+ The given function string needs to be callable with the following function signature:
+ \code{my_function(input.file, output.dir, preview.id, ...)}. Here,
+
+ \itemize{
+ \item{\code{input.file} character string specifying the (temporary) input file with the script's contents}
+ \item{\code{output.dir} character string specifying a directory suitable for keeping temporary files related
+ to this preview. This will be cleared, automatically, as needed.}
+ \item{\code{preview.id} a unique id of the preview window. This could be used to keep temporary data, for instance.}
+ \item{\code{...} for future extension}
+ }
+
+ As a final step the preview funtion will generally call either \code{rk.show.html()}, or \code{rk.show.pdf()}, but it could
+ also show data using \code{rk.edit}, or produce a plot. Some care has to be taken in order not to interfere with existing
+ device windows, or output windows. Looking at the implementation of e.g. \code{rk.eval.as.preview()} may be instructive.
+ (This feature is targetted at power users, expecting them to consider and deal with any side-effects, themselves.)
+}
+\examples{
+\dontrun{
+my_csvpreview <- function(infile, outdir, previewid, ...) {
+ require("R2HTML")
+ suppressWarnings({
+ data <- read.csv(infile)
+ })
+ outfile <- paste0(outdir, "/out.html")
+ unlink(outfile) # clear previous output
+ HTML(data, file=outfile)
+ rk.show.html(outfile)
+}
+rk.register.script.preview("csv", "Preview CSV data", ".csv", "my_csvpreview"))
+}
+
+}
diff --git a/rkward/rbackend/rpackages/rkward/man/rk.render.markdown.preview.Rd b/rkward/rbackend/rpackages/rkward/man/rk.render.markdown.preview.Rd
new file mode 100644
index 000000000..dd8896b50
--- /dev/null
+++ b/rkward/rbackend/rpackages/rkward/man/rk.render.markdown.preview.Rd
@@ -0,0 +1,20 @@
+% Generated by roxygen2: do not edit by hand
+% Please edit documentation in R/rk.preview-functions.R
+\name{rk.render.markdown.preview}
+\alias{rk.render.markdown.preview}
+\title{Render a preview of the given R markdown file}
+\usage{
+rk.render.markdown.preview(infile, outdir, ...)
+}
+\arguments{
+\item{infile}{The input Rmd file, specified as a character string.}
+
+\item{outdir}{The directory for output. The directory is not cleared, but existing files inside it may be overwritten without notice!}
+
+\item{...}{Additional parameters to pass to \link{rmarkdown::render}. Notably this may include \code{utput_format="html_document"} or
+\code{output_format="pdf_document"}.}
+}
+\description{
+This function is mostly targetted at providing a preview inside a script editor window. For other purposes
+it will generally make more sense to call \link{rmarkdown::render}, directly.
+}
diff --git a/rkward/windows/rkcommandeditorwindow.cpp b/rkward/windows/rkcommandeditorwindow.cpp
index 3367e8411..79deb58b9 100644
--- a/rkward/windows/rkcommandeditorwindow.cpp
+++ b/rkward/windows/rkcommandeditorwindow.cpp
@@ -154,8 +154,9 @@ class RKPreviewModeSelector : public QWidgetAction {
h->addLayout(r);
auto preview_mode_button_group = new QButtonGroup(form);
- for (auto *mode : std::as_const(win->preview_modes)) {
- auto m = (mode == win->preview_modes.first()) ? nullptr : mode;
+ const auto allmodes = RKCommandEditorWindow::preview_modes + RKCommandEditorWindow::user_preview_modes.values();
+ for (auto *mode : allmodes) {
+ auto m = (mode == allmodes.first()) ? nullptr : mode;
auto button = new QRadioButton(mode->label);
connect(win->m_doc, &KTextEditor::Document::highlightingModeChanged, button, [button, mode](KTextEditor::Document *doc) {
button->setVisible(mode->validator(doc));
@@ -293,6 +294,7 @@ RKCommandEditorWindowPart::~RKCommandEditorWindowPart() {
// static
QMap<QString, KTextEditor::Document *> RKCommandEditorWindow::unnamed_documents;
QList<RKPreviewMode *> RKCommandEditorWindow::preview_modes;
+QHash<QString, RKPreviewMode *> RKCommandEditorWindow::user_preview_modes;
KTextEditor::Document *createDocument(bool with_signals) {
KTextEditor::Document *ret = KTextEditor::Editor::instance()->createDocument(RKWardMainWindow::getMain());
@@ -777,6 +779,22 @@ void RKCommandEditorWindow::discardPreview() {
}
}
+// static
+void RKCommandEditorWindow::registerUserPreviewMode(const QString &id, const QString &label, const QString &inext, const QString &command) {
+ RK_TRACE(COMMANDEDITOR);
+ delete user_preview_modes.take(id);
+ if (command.isEmpty()) return;
+ auto m = new RKPreviewMode(label, QIcon(), inext);
+ m->command = [command](RKCommandEditorWindow *win, const QString &infile, const QString &outdir, const QString &preview_id) {
+ return command + QStringLiteral("(%1, %2, %3)").arg(RObject::rQuote(infile), RObject::rQuote(outdir), RObject::rQuote(preview_id));
+ };
+ m->validator = [](KTextEditor::Document *doc) -> bool {
+ return true;
+ };
+ user_preview_modes.insert(id, m);
+ // TODO udate/notify existing selector widgets
+}
+
// static
void RKCommandEditorWindow::initPreviewModes() {
if (!preview_modes.isEmpty()) return;
diff --git a/rkward/windows/rkcommandeditorwindow.h b/rkward/windows/rkcommandeditorwindow.h
index 47384384d..2232aa1d5 100644
--- a/rkward/windows/rkcommandeditorwindow.h
+++ b/rkward/windows/rkcommandeditorwindow.h
@@ -110,6 +110,7 @@ class RKCommandEditorWindow : public RKMDIWindow, public RKScriptContextProvider
void highlightLine(int linenum);
/** Returns the (main, non-preview) texteditor view in this editor. */
KTextEditor::View *getView() const { return m_view; };
+ static void registerUserPreviewMode(const QString &id, const QString &label, const QString &inext, const QString &command);
public Q_SLOTS:
/** update Tab caption according to the current url. Display the filename-component of the URL, or - if not available - a more elaborate description of the url. Also appends a "[modified]" if appropriate */
void updateCaption();
@@ -186,6 +187,7 @@ class RKCommandEditorWindow : public RKMDIWindow, public RKScriptContextProvider
QAction *action_run_all;
QAction *action_run_current;
static QList<RKPreviewMode *> preview_modes;
+ static QHash<QString, RKPreviewMode *> user_preview_modes;
QAction *action_preview_as_you_type;
QAction *action_setwd_to_script;
More information about the rkward-tracker
mailing list