Skip to content

Commit

Permalink
Inspect (#57)
Browse files Browse the repository at this point in the history
* +TODO

* wire inspect

* use text/html

* initial inspect impl

* unskip code_inspect_sample

* rm ggplot2 based example
  • Loading branch information
romainfrancois authored Dec 6, 2023
1 parent 201e9f3 commit 7ce6ae6
Show file tree
Hide file tree
Showing 4 changed files with 87 additions and 11 deletions.
1 change: 1 addition & 0 deletions share/jupyter/kernels/xr/resources/completion.R
Original file line number Diff line number Diff line change
Expand Up @@ -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)))

}
69 changes: 69 additions & 0 deletions share/jupyter/kernels/xr/resources/inspect.R
Original file line number Diff line number Diff line change
@@ -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' = '<h1>%s:</h1>\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)
}
23 changes: 15 additions & 8 deletions src/xinterpreter.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -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() {
Expand Down
5 changes: 2 additions & 3 deletions test/test_xr_kernel.py
Original file line number Diff line number Diff line change
Expand Up @@ -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 + "]
Expand Down

0 comments on commit 7ce6ae6

Please sign in to comment.