Skip to content

Implement help provider for R #113

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Merged
merged 28 commits into from
Oct 19, 2023
Merged

Implement help provider for R #113

merged 28 commits into from
Oct 19, 2023

Conversation

jmcphers
Copy link
Contributor

This change implements a help provider for R that can suggest a help topic appropriate to the cursor position and, given a help topic, show help for that topic in the Help pane.

The change is large because the implementation required adding a new channel over which the help topic request could be delivered, which in turn required some somewhat heavy-handed refactoring of how we handle Help. The good news, however, is that I was able to clean up some old TODOs and reduce the reliance on global state; we now only start the Help backend when Positron needs it, and don't need to start the HTTP help server when using a non-Positron front end. Like Environment, we now only engage the Help machinery on demand.

There are two new pieces of functionality here:

  • A Help comm that sends notifications of new help content, and has a single RPC that can be used for Positron to ask for help to be shown on a given topic.
  • A new LSP method, textDocument/helpTopic, which is used to query for a Help topic appropriate for the cursor position.

Must not be merged before posit-dev/positron#1593. See that PR for the front-end Help implementation.

Note

The helpTopic implementation here is little more than a proof of concept or stub; it just looks for the nearest identifier at or preceding the cursor. This works as long as you mash F1 directly on or near the keyword you want help on, but it also picks up local variable names and other stuff. I'll improve it in a follow up PR, and/or we can tackle it in a separate effort. The goal here is to have a proof of concept so we can unblock getting this work done for Python.

@DavisVaughan DavisVaughan self-requested a review October 17, 2023 19:45
Copy link
Contributor

@lionel- lionel- left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Looks good! Just a missing r_task() (see comment inline).

let mut node = cursor.find_leaf(point);

// Find the nearest node that is an identifier.
while node.kind() != "identifier" {
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This is where we will need a "find function call at point" to avoid foo(bar<> = ) looking up bar instead of foo right? Probably worth extracting this in a helper function that could be reused elsewhere and tested for edge cases more easily.

Probably also need to scope the search to the nearest call so that foo(bar()(baz =<> )) doesn't return foo but instead returns nil.

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Less important: In some cases we might still want to look up help for simple symbols, such as list(NULL<>) where we'd show ?NULL instead of ?list.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Yes, these and other improvements need to be made in the topic detector! Will tackle that as a follow up.

@DavisVaughan
Copy link
Contributor

DavisVaughan commented Oct 18, 2023

Would you mind going into vroom and doing devtools::load_all() followed by ?vroom. It should say

ℹ Rendering development documentation for "vroom"

but for me it no longer opens an external browser showing the help page

(my exact reprex used vctrs and ?vec_sort but i know you have vroom locally)

I see

[R] 2023-10-18T21:01:18.791134000Z [ark-unknown] WARN crates/ark/src/help/r_help.rs:132: Error handling Help request: Help URL '/var/folders/by/4wf2qrmn4_j93s5k5k5fzrx00000gp/T/Rtmpw0UYqk/.R/doc/html/vec_order.html' doesn't have expected prefix 'http://127.0.0.1:15295/'
[R] 
[R] Occurred at:
[R]    5: ark::help::r_help::RHelp::show_help_url
[R]              at /Users/davis/files/programming/positron/amalthea/crates/ark/src/help/r_help.rs:213:24

Copy link
Contributor

@DavisVaughan DavisVaughan left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

There is currently a bug with dev help, which did used to "work" by opening in an external browser. But otherwise looks good!

Comment on lines 34 to 38
if let Some(help_tx) = help_tx {
if let Err(err) = help_tx.send(HelpRequest::ShowHelpUrl(url.to_string())) {
log::error!("Failed to send help message: {}", err);
}
}
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Suggested change
if let Some(help_tx) = help_tx {
if let Err(err) = help_tx.send(HelpRequest::ShowHelpUrl(url.to_string())) {
log::error!("Failed to send help message: {}", err);
}
}
let help_tx = unwrap!(help_tx, None => {
log::error!("Failed to access `help_tx`");
return Ok(true);
});
let message = HelpRequest::ShowHelpUrl(url.to_string());
let _ = unwrap!(help_tx.send(message), Err(err) => {
log::error!("Failed to send help message: {err:?}");
return Ok(true);
});

Very much a nit, but I have come to prefer keeping as flat a code structure as possible (either with unwrap! or match) + using early returns in the unexpected cases.

(at the very least applying the {err:?} here too is worth it)

(when do we ever return false from this function?)

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I also like this alternative too (we are still figuring out the most idiomatic approaches, but I think this is probably close)

    let Some(help_tx) = help_tx else {
        log::error!("Failed to access `help_tx`");
        return Ok(true);
    };

    let message = HelpRequest::ShowHelpUrl(url.to_string());

    if let Err(err) = help_tx.send(message) {
        log::error!("Failed to send help message: {err:?}");
        return Ok(true);
    }

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Thanks, I've refactored as suggested!

(when do we ever return false from this function?)

Turning this into a fire-and-forget request to the help thread means we can't tell if it decided to handle the URL or not (which is what the boolean indicates). I realized after you mentioned the vroom example that this means we don't know whether or not to perform the fallback behavior of using open, so I restored the previous behavior by adding a reply in b5e7ecd.

Comment on lines 56 to 57
/// The content help type. Must be one of 'html', 'markdown', or 'url'.
pub kind: String,
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Probably worth a HelpContentKind enum?

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Good idea; this was inherited from events.yaml which didn't have enums.

Comment on lines +42 to +47
/**
* Start the help handler. Returns a channel for sending help requests to
* the help thread.
*
* - `comm`: The socket for communicating with the front end.
*/
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I am sad to report that if you use these block style comments over /// doc string comments then rust-analyzer shows them in this horrible way where each line looks like a bullet 😆 rust-lang/rust-analyzer#1759

Screenshot 2023-10-18 at 4 21 16 PM

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Hah, that's awesome!

// number when it starts, so if it's still at the default value (0), it
// hasn't started.
let mut started = false;
let r_help_port: u16;
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I didn't know you could delay the first assignment like this! Cool!

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The Rust linter actually made me do this after it realized the code below this guaranteed the variable would be assigned exactly once. Really nice.

Comment on lines -230 to +228
// TODO: Should starting the R help server proxy really be here?
// Are we sure we want our own server when ark runs in a Jupyter notebook?
// Moving this requires detangling `help_server_port` from
// `modules::initialize()`, which seems doable.
// Start R help server proxy
help_proxy::start(r_module_info.help_server_port);
modules::initialize().unwrap();
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Nice

Comment on lines 285 to 286
pub help_tx: Option<Sender<HelpRequest>>,
pub help_rx: Option<Receiver<HelpReply>>,
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Should these names be more specific now? i.e. help_request_tx and help_reply_rx?

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I like the terser forms, but could also be convinced to rename 'em.

@jmcphers jmcphers merged commit 27734a7 into main Oct 19, 2023
@DavisVaughan DavisVaughan deleted the feature/help-topic-provider branch October 14, 2024 12:40
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

Successfully merging this pull request may close these issues.

3 participants