-
Notifications
You must be signed in to change notification settings - Fork 15
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
Conversation
There was a problem hiding this 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" { |
There was a problem hiding this comment.
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.
There was a problem hiding this comment.
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
.
There was a problem hiding this comment.
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.
Would you mind going into vroom and doing
but for me it no longer opens an external browser showing the help page (my exact reprex used vctrs and I see
|
There was a problem hiding this 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!
crates/ark/src/browser.rs
Outdated
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); | ||
} | ||
} |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
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?)
There was a problem hiding this comment.
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);
}
There was a problem hiding this comment.
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.
crates/ark/src/help/message.rs
Outdated
/// The content help type. Must be one of 'html', 'markdown', or 'url'. | ||
pub kind: String, |
There was a problem hiding this comment.
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?
There was a problem hiding this comment.
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.
/** | ||
* Start the help handler. Returns a channel for sending help requests to | ||
* the help thread. | ||
* | ||
* - `comm`: The socket for communicating with the front end. | ||
*/ |
There was a problem hiding this comment.
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

There was a problem hiding this comment.
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; |
There was a problem hiding this comment.
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!
There was a problem hiding this comment.
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.
// 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(); |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Nice
crates/ark/src/interface.rs
Outdated
pub help_tx: Option<Sender<HelpRequest>>, | ||
pub help_rx: Option<Receiver<HelpReply>>, |
There was a problem hiding this comment.
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
?
There was a problem hiding this comment.
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.
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:
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.