diff --git a/share/jupyter/kernels/xr/resources/completion.R b/share/jupyter/kernels/xr/resources/completion.R
index 379bc94..9a5c7fa 100644
--- a/share/jupyter/kernels/xr/resources/completion.R
+++ b/share/jupyter/kernels/xr/resources/completion.R
@@ -29,6 +29,7 @@ complete <- function(code, cursor_pos = nchar(code)) {
start_position <- chars_before_line + info$start
comps <- utils:::.retrieveCompletions()
+ # TODO: use jsonlite::toJSON() here
list(comps, c(start_position, start_position + nchar(info$token)))
}
\ No newline at end of file
diff --git a/share/jupyter/kernels/xr/resources/inspect.R b/share/jupyter/kernels/xr/resources/inspect.R
new file mode 100644
index 0000000..ac790c2
--- /dev/null
+++ b/share/jupyter/kernels/xr/resources/inspect.R
@@ -0,0 +1,69 @@
+inspect <- function(code, cursor_pos) {
+ # This is the approach used in IRkernel, it would perhaps
+ # be better to use parsing instead, e.g. with the internal R
+ # parser, but the expression has to be complete, or a more
+ # forgiving parser, e.g. tree-sitter and the grammar for R:
+ # https://github.com/r-lib/tree-sitter-r/tree/next
+
+ # Get token under the `cursor_pos`.
+ # Since `.guessTokenFromLine()` does not check the characters after `cursor_pos`
+ # check them by a loop. Use get since R CMD check does not like :::
+ token <- ''
+ for (i in seq(cursor_pos, nchar(code))) {
+ token_candidate <- utils:::.guessTokenFromLine(code, i)
+ if (nchar(token_candidate) == 0) break
+ token <- token_candidate
+ }
+
+ # Function to add a section to content.
+ title_templates <- list(
+ 'text/plain' = '# %s:\n',
+ 'text/html' = '
%s:
\n'
+ )
+
+ add_new_section <- function(data, section_name, new_data) {
+ for (mime in names(title_templates)) {
+ new_content <- new_data[[mime]]
+ if (is.null(new_content)) next
+ title <- sprintf(title_templates[[mime]], section_name)
+ # use paste0 since sprintf cannot deal with format strings > 8192 bytes
+ data[[mime]] <- paste0(data[[mime]], title, new_content, '\n', sep = '\n')
+ }
+ return(data)
+ }
+
+ data <- namedlist()
+ if (nchar(token) != 0) {
+ # In many cases `get(token)` works, but it does not
+ # in the cases such as `token` is a numeric constant or a reserved word.
+ # Therefore `eval()` is used here.
+ obj <- tryCatch(eval(parse(text = token), envir = .GlobalEnv), error = function(e) NULL)
+ class_data <- if (!is.null(obj)) IRdisplay::prepare_mimebundle(class(obj))$data
+ print_data <- if (!is.null(obj)) IRdisplay::prepare_mimebundle(obj)$data
+
+ # `help(token)` is not used here because it does not works
+ # in the cases `token` is in `pkg::topic`or `pkg:::topic` form.
+ help_data <- tryCatch({
+ help_obj <- eval(parse(text = paste0('?', token)))
+ if (length(help_obj) > 0) {
+ IRdisplay::prepare_mimebundle(help_obj)$data
+ }
+ }, error = function(e) NULL)
+
+ # only show help if we have a function
+ if ('function' %in% class(obj) && !is.null(help_data)) {
+ data <- help_data
+ } else {
+ # any of those that are NULL are automatically skipped
+ data <- add_new_section(data, 'Class attribute', class_data)
+ data <- add_new_section(data, 'Printed form', print_data)
+ data <- add_new_section(data, 'Help document', help_data)
+ }
+ }
+
+ for (mime in names(data)) {
+ data[[mime]] <- unbox(data[[mime]])
+ }
+
+ list(found = length(data) > 0L, data = toJSON(data), metadata = NULL)
+}
diff --git a/src/xinterpreter.cpp b/src/xinterpreter.cpp
index 63ee281..5ab9719 100644
--- a/src/xinterpreter.cpp
+++ b/src/xinterpreter.cpp
@@ -190,16 +190,23 @@ nl::json interpreter::complete_request_impl(const std::string& code, int cursor_
return reply;
}
-nl::json interpreter::inspect_request_impl(const std::string& /*code*/,
- int /*cursor_pos*/,
- int /*detail_level*/)
+nl::json interpreter::inspect_request_impl(const std::string& code, int cursor_pos, int /*detail_level*/)
{
+
+ SEXP code_ = PROTECT(Rf_mkString(code.c_str()));
+ SEXP cursor_pos_ = PROTECT(Rf_ScalarInteger(cursor_pos));
+
+ SEXP result = PROTECT(r::invoke_xeusr_fn("inspect", code_, cursor_pos_));
+ bool found = LOGICAL_ELT(VECTOR_ELT(result, 0), 0);
+ if (!found) {
+ UNPROTECT(3);
+ return xeus::create_inspect_reply(false);
+ }
+
+ auto data = nl::json::parse(CHAR(STRING_ELT(VECTOR_ELT(result, 1), 0)));
- return xeus::create_inspect_reply(true/*found*/,
- {{std::string("text/plain"), std::string("hello!")}}, /*data*/
- {{std::string("text/plain"), std::string("hello!")}} /*meta-data*/
- );
-
+ UNPROTECT(3); // result, code_, cursor_pos_
+ return xeus::create_inspect_reply(found, data);
}
void interpreter::shutdown_request_impl() {
diff --git a/test/test_xr_kernel.py b/test/test_xr_kernel.py
index d9d39a9..3c48969 100644
--- a/test/test_xr_kernel.py
+++ b/test/test_xr_kernel.py
@@ -21,12 +21,11 @@ class KernelTests(jupyter_kernel_test.KernelTests):
]
code_execute_result = [{"code": "6*7", "result": ["[1] 42"]}]
code_display_data = [
- {"code": "plot(0)", "mime": "image/png"},
- {"code": "ggplot2::ggplot(iris, ggplot2::aes(Sepal.Length, Sepal.Width)) + ggplot2::geom_point()", "mime": "image/png"}
+ {"code": "plot(0)", "mime": "image/png"}
]
# code_page_something = "?cat"
- # code_inspect_sample = "print"
+ code_inspect_sample = "print"
complete_code_samples = ["fun()", "1 + 2", "a %>% b", "a |> b()", "a |> b(c = _)"]
incomplete_code_samples = ["fun(", "1 + "]