Conversation
DavisVaughan
left a comment
There was a problem hiding this comment.
I'd like to get @lionel-'s opinion on a few questions about where things should live, but otherwise seems good
| # is not yet available (e.g. during development with mismatched builds). | ||
| if (!is.null(source_uri)) { | ||
| pushed <- tryCatch( | ||
| { .ps.Call("ps_graphics_push_source_context", source_uri); TRUE }, |
There was a problem hiding this comment.
Can you please reformat with Air? I should probably turn that on as a CI check...
There was a problem hiding this comment.
Yeah, it looks like a lot of the .R files are not air-compliant. I went ahead and formatted the whole workspace (LMK if you'd like to keep the changes scoped to just this new file)
| error = function(e) FALSE | ||
| ) | ||
| if (pushed) { | ||
| on.exit(tryCatch(.ps.Call("ps_graphics_pop_source_context"), error = function(e) NULL), add = TRUE) |
There was a problem hiding this comment.
We are trying to use defer() instead of on.exit(), which comes with the right defaults
|
|
||
| # Try to resolve the file URI early so we can attribute plots to this | ||
| # source file. This is best-effort; if it fails we proceed without attribution. | ||
| source_uri <- tryCatch(path_to_file_uri(file), error = function(e) NULL) |
There was a problem hiding this comment.
I see no reason to not just consistently call it uri everywhere rather than renaming it later on
| // Push execution context to graphics device for plot attribution | ||
| // Push execution context to graphics device for plot attribution. | ||
| // Extract code_location from the execute request for source file origin. | ||
| let code_location = exec_req.code_location().log_err().flatten().map(|mut loc| { |
There was a problem hiding this comment.
@lionel- should code_location() just normalize() the URI internally? It seems like the one other place we call code_location() also normalizes after the fact.
There was a problem hiding this comment.
That's a good idea!
code_location() lives in Amalthea and ExtUrl::normalize() is in Ark, so a nice place to implement it would be on the ExtUrl extension trait:
let code_location = ExtUrl::from_execute_request(&exec_req);There was a problem hiding this comment.
Actually I realised while working on #1082 that we should not normalise/canonicalise URIs that flow back to the frontend, because it might think it's a different file. I noticed Positron would open a canonicalised path in a new editor instead of reusing an existing one.
Fixed in the linked PR.
| /// Push a source file URI onto the source context stack. | ||
| /// Called from the `source()` hook when entering a sourced file. | ||
| #[harp::register] | ||
| unsafe extern "C-unwind" fn ps_graphics_push_source_context(uri: SEXP) -> anyhow::Result<SEXP> { | ||
| let uri_str: String = RObject::view(uri).try_into()?; | ||
| DEVICE_CONTEXT.with_borrow(|cell| cell.push_source_context(uri_str)); | ||
| Ok(harp::r_null()) | ||
| } | ||
|
|
||
| /// Pop a source file URI from the source context stack. | ||
| /// Called from the `source()` hook when leaving a sourced file. | ||
| #[harp::register] | ||
| unsafe extern "C-unwind" fn ps_graphics_pop_source_context() -> anyhow::Result<SEXP> { | ||
| DEVICE_CONTEXT.with_borrow(|cell| cell.pop_source_context()); | ||
| Ok(harp::r_null()) | ||
| } |
There was a problem hiding this comment.
IIUC the desire is to be able to query the name of the file that is currently being source()-ed at any time while inside the execution of that file.
That doesn't feel super specific to plots, so I would not be against that being a generic feature that lives in Console instead.
@lionel- what do you think?
There was a problem hiding this comment.
That makes sense to me, although I can't think of a reason to do it proactively.
I was initially thinking it may help with transitions of comms to Console thread, as the stack could be made available via the comm context. But the graphics device is not a comm so I don't think that will help there.
Feel free to add the source context to the Console though Jonathan.
In that case it might make sense to include the execute request's location in the stack so that priority resolution is consistently handled.
There was a problem hiding this comment.
Thanks, agree that could be helpful but won't do it proactively here!
| fn new_positron_page(&self) { | ||
| self.is_new_page.replace(true); | ||
| self.id.replace(Self::new_id()); | ||
| *self.pending_origin.borrow_mut() = None; |
There was a problem hiding this comment.
Use self.pending_origin.replace(None) like we've done for the other fields?
Here and elsewhere.
| // Clear any unconsumed pending origin so it doesn't leak into the | ||
| // next execute request. `process_changes()` above already consumed it | ||
| // if this execution produced a new plot; this handles the case where | ||
| // drawing occurred (e.g. `lines()` inside `source()`) but only as an | ||
| // update to an existing plot, leaving the pending origin unclaimed. | ||
| *cell.pending_origin.borrow_mut() = None; |
There was a problem hiding this comment.
Maybe we want set_pending_origin() and clear_pending_origin() to go along with set_execution_context() and clear_execution_context()? That probably cleans this up a little bit.
| args$catch.aborts <- NULL | ||
| } | ||
|
|
||
| # Try to resolve the file URI early so we can attribute plots to this |
There was a problem hiding this comment.
Since @lionel- is the author of this bit I think he should sign off on the changes in this file
Co-authored-by: Davis Vaughan <davis@rstudio.com>
lionel-
left a comment
There was a problem hiding this comment.
That will be a nice feature!
The design is sound and the implementation looks nice.
| if (pushed) { | ||
| defer(tryCatch( | ||
| .ps.Call("ps_graphics_pop_source_context"), | ||
| error = function(e) NULL | ||
| )) | ||
| } |
There was a problem hiding this comment.
nit: This could equivalently be run unconditionally in the try branch, after the .Call returns.
| /// Checks three sources in priority order: | ||
| /// 1. Source context stack (inside `source("file.R")`) -- file-level, no range | ||
| /// 2. Execute request's `code_location` (code run from a file via the IDE) -- with range | ||
| /// 3. None (code typed at the console) |
| /// Push a source file URI onto the source context stack. | ||
| /// Called from the `source()` hook when entering a sourced file. | ||
| #[harp::register] | ||
| unsafe extern "C-unwind" fn ps_graphics_push_source_context(uri: SEXP) -> anyhow::Result<SEXP> { | ||
| let uri_str: String = RObject::view(uri).try_into()?; | ||
| DEVICE_CONTEXT.with_borrow(|cell| cell.push_source_context(uri_str)); | ||
| Ok(harp::r_null()) | ||
| } | ||
|
|
||
| /// Pop a source file URI from the source context stack. | ||
| /// Called from the `source()` hook when leaving a sourced file. | ||
| #[harp::register] | ||
| unsafe extern "C-unwind" fn ps_graphics_pop_source_context() -> anyhow::Result<SEXP> { | ||
| DEVICE_CONTEXT.with_borrow(|cell| cell.pop_source_context()); | ||
| Ok(harp::r_null()) | ||
| } |
There was a problem hiding this comment.
That makes sense to me, although I can't think of a reason to do it proactively.
I was initially thinking it may help with transitions of comms to Console thread, as the stack could be made available via the comm context. But the graphics device is not a comm so I don't think that will help there.
Feel free to add the source context to the Console though Jonathan.
In that case it might make sense to include the execute request's location in the stack so that priority resolution is consistently handled.
|
|
||
| /// The origin (source) of a plot | ||
| #[derive(Clone, Debug, Serialize, Deserialize, PartialEq)] | ||
| pub struct PlotOrigin { |
There was a problem hiding this comment.
Since the structure is very similar to CodeLocation, I think I would have expected PlotLocation. I guess Origin has a slightly different nuance though, and indicates the original location might have changed.
There was a problem hiding this comment.
Yes, it's hard to name this correctly! I think PlotLocation might be misread as "where the plot itself is located" vs "where the plot came from"
| // Push execution context to graphics device for plot attribution | ||
| // Push execution context to graphics device for plot attribution. | ||
| // Extract code_location from the execute request for source file origin. | ||
| let code_location = exec_req.code_location().log_err().flatten().map(|mut loc| { |
There was a problem hiding this comment.
That's a good idea!
code_location() lives in Amalthea and ExtUrl::normalize() is in Ark, so a nice place to implement it would be on the ExtUrl extension trait:
let code_location = ExtUrl::from_execute_request(&exec_req);| "Metadata should contain kind 'plot', got:\n{result}" | ||
| ); | ||
| } | ||
|
|
There was a problem hiding this comment.
Nice to see the new test infra put to good use!
I think it'd be worth testing the use case of sourcing a file A that sources a file B, to exercise the stacked aspect of locations.
Companion to posit-dev/positron#12168; adds additional metadata to plots annotating where they originated, to make it easy to navigate to that location. See that PR for more info.
For linewise execution, this borrows from the source location infrastructure @lionel- set up already for debugging; for execution via
source(), some slightly more involved work is needed to store the source context so we can get at it later.