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 + "]