-
Notifications
You must be signed in to change notification settings - Fork 26
Expand file tree
/
Copy pathviewer.rs
More file actions
130 lines (116 loc) · 4.4 KB
/
viewer.rs
File metadata and controls
130 lines (116 loc) · 4.4 KB
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
//
// viewer.rs
//
// Copyright (C) 2023-2024 Posit Software, PBC. All rights reserved.
//
//
use amalthea::comm::ui_comm::ShowHtmlFileDestination;
use amalthea::comm::ui_comm::ShowHtmlFileParams;
use amalthea::comm::ui_comm::UiFrontendEvent;
use amalthea::socket::iopub::IOPubMessage;
use amalthea::wire::display_data::DisplayData;
use anyhow::Result;
use crossbeam::channel::Sender;
use harp::object::RObject;
use libr::R_NilValue;
use libr::SEXP;
use crate::interface::RMain;
use crate::interface::SessionMode;
/// Emit HTML output on IOPub for delivery to the client
///
/// - `iopub_tx` - The IOPub channel to send the output on
/// - `path` - The path to the HTML file to display
/// - `kind` - The kind of the HTML widget
fn emit_html_output_jupyter(
iopub_tx: Sender<IOPubMessage>,
path: String,
kind: String,
) -> Result<()> {
// Read the contents of the file
let contents = std::fs::read_to_string(path)?;
// Create the output object
let output = serde_json::json!({
"text/html": contents,
"text/plain": format!("<{} HTML Widget>", kind),
});
// Emit the HTML output on IOPub for delivery to the client
let message = IOPubMessage::DisplayData(DisplayData {
data: output,
metadata: serde_json::Value::Null,
transient: serde_json::Value::Null,
});
iopub_tx.send(message)?;
Ok(())
}
#[harp::register]
pub unsafe extern "C-unwind" fn ps_html_viewer(
url: SEXP,
label: SEXP,
height: SEXP,
destination: SEXP,
) -> anyhow::Result<SEXP> {
// Convert url to a string; note that we are only passed URLs that
// correspond to files in the temporary directory.
let path = RObject::view(url).to::<String>();
let label = match RObject::view(label).to::<String>() {
Ok(label) => label,
Err(_) => String::from("R"),
};
match path {
Ok(path) => {
// Emit HTML output
let main = RMain::get();
let iopub_tx = main.get_iopub_tx().clone();
match main.session_mode() {
SessionMode::Notebook | SessionMode::Background => {
// In notebook mode, send the output as a Jupyter display_data message
if let Err(err) = emit_html_output_jupyter(iopub_tx, path, label) {
log::error!("Failed to emit HTML output: {:?}", err);
}
},
SessionMode::Console => {
let destination = match RObject::view(destination).to::<String>() {
Ok(s) => s.parse::<ShowHtmlFileDestination>().unwrap_or_else(|_| {
log::warn!(
"`destination` must be one of 'plot', 'editor', or 'viewer'"
);
ShowHtmlFileDestination::Viewer
}),
Err(err) => {
log::warn!(
"Can't convert `destination` to a string, using 'viewer' as a fallback: {err}"
);
ShowHtmlFileDestination::Viewer
},
};
let height = RObject::view(height).to::<i32>();
let height = match height {
Ok(height) => height.into(),
Err(err) => {
log::warn!("Can't convert `height` into an i32, using `0` as a fallback: {err:?}");
0
},
};
let params = ShowHtmlFileParams {
path,
title: label,
height,
destination,
};
let event = UiFrontendEvent::ShowHtmlFile(params);
// TODO: What's the right thing to do in `Console` mode when
// we aren't connected to Positron? Right now we error.
let ui_comm_tx = main
.get_ui_comm_tx()
.ok_or_else(|| anyhow::anyhow!("UI comm not connected."))?;
ui_comm_tx.send_event(event);
},
}
},
Err(err) => {
log::error!("Attempt to view invalid path {:?}: {:?}", url, err);
},
}
// No return value
Ok(R_NilValue)
}