Skip to content
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

Split inputs by newlines #536

Merged
merged 36 commits into from
Oct 4, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
36 commits
Select commit Hold shift + click to select a range
fd13570
Add `lines()` util to preserve trailing lines
lionel- Sep 18, 2024
0587617
Draft `handle_input()`
lionel- Sep 18, 2024
3da6106
WIP handling for pending line in `read_console()`
DavisVaughan Sep 18, 2024
75b7cad
Fix output of intermediate expressions
lionel- Sep 19, 2024
da79ecf
Make detection of prompt types more robust
lionel- Sep 19, 2024
d709454
Rework `lines()` to return an iterator
DavisVaughan Sep 19, 2024
7a444e6
Flesh out safety bullet
DavisVaughan Sep 19, 2024
f290f85
Clean up `buffer_overflow_call()`
DavisVaughan Sep 19, 2024
bc67135
Remove comment
DavisVaughan Sep 19, 2024
4591929
Update comment about incomplete input handling
DavisVaughan Sep 19, 2024
85acec5
Throw an R error if we see incomplete user inputs
DavisVaughan Sep 19, 2024
f85f3f8
Add a note about input replies and incomplete results
DavisVaughan Sep 19, 2024
9512c70
Add more comments about input handling
lionel- Sep 23, 2024
df5f3f4
Return error from `check_console_input()`
lionel- Sep 23, 2024
fb4e776
Document the ReadConsole state machine
lionel- Sep 23, 2024
b65d6a7
Panic when an incomplete prompt is detected in `ReadConsole`
lionel- Sep 23, 2024
f65d942
Document current issue with large input errors
lionel- Sep 23, 2024
2449def
Tweak tests
lionel- Oct 3, 2024
c99d6b2
Set `R_CLI_HIDE_CURSOR` after all
DavisVaughan Oct 3, 2024
bf3e54b
Return an `amalthea::Result<()>` from `on_console_input()`
DavisVaughan Oct 3, 2024
373753d
We are happy with this being an error
DavisVaughan Oct 3, 2024
8f04b89
Update some documentation
DavisVaughan Oct 3, 2024
d93843c
Clean up long single line test
DavisVaughan Oct 3, 2024
e1d1163
Add basic stdin test, and stdin buffer overflow test
DavisVaughan Oct 3, 2024
9dc9921
Turn off stack limit on Windows too
DavisVaughan Oct 3, 2024
47425da
Collect IOPub Stream messages in batches
DavisVaughan Oct 3, 2024
ff40107
Correct ordering in `report_error!`
DavisVaughan Oct 3, 2024
e373444
Group structs and impls together
lionel- Oct 4, 2024
c5324e6
Also test for prefix stream
lionel- Oct 4, 2024
adaf4e7
Mention fix in changelog
lionel- Oct 4, 2024
d26693d
Add tests for incompete inputs and browser prompts
lionel- Oct 4, 2024
1255911
Improve readline prompt detection in browser sessions
lionel- Oct 4, 2024
7ee973b
Add test for errors in browser
lionel- Oct 4, 2024
4aab367
Update comments
lionel- Oct 4, 2024
191c964
Add test for readline in browser sessions
lionel- Oct 4, 2024
4f3f13b
Move `r_task()` initialization after R was set up
lionel- Oct 4, 2024
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 2 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,8 @@

# 0.1.9000

- Sending long inputs of more than 4096 bytes no longer fails (posit-dev/positron#4745).

- Jupyter: Fixed a bug in the kernel-info reply where the `pygments_lexer` field
would be set incorrectly to `""` (#553).

Expand Down
4 changes: 4 additions & 0 deletions crates/amalthea/src/error.rs
Original file line number Diff line number Diff line change
Expand Up @@ -43,6 +43,7 @@ pub enum Error {
UnknownCommId(String),
InvalidCommMessage(String, String, String),
InvalidInputRequest(String),
InvalidConsoleInput(String),
}

impl std::error::Error for Error {}
Expand Down Expand Up @@ -193,6 +194,9 @@ impl fmt::Display for Error {
Error::InvalidInputRequest(message) => {
write!(f, "{message}")
},
Error::InvalidConsoleInput(message) => {
write!(f, "{message}")
},
}
}
}
Expand Down
82 changes: 67 additions & 15 deletions crates/amalthea/src/fixtures/dummy_frontend.rs
Original file line number Diff line number Diff line change
Expand Up @@ -13,6 +13,7 @@ use crate::session::Session;
use crate::socket::socket::Socket;
use crate::wire::execute_input::ExecuteInput;
use crate::wire::execute_request::ExecuteRequest;
use crate::wire::input_reply::InputReply;
use crate::wire::jupyter_message::JupyterMessage;
use crate::wire::jupyter_message::Message;
use crate::wire::jupyter_message::ProtocolMessage;
Expand All @@ -36,6 +37,10 @@ pub struct DummyFrontend {
heartbeat_port: u16,
}

pub struct ExecuteRequestOptions {
pub allow_stdin: bool,
}

impl DummyFrontend {
pub fn new() -> Self {
use rand::Rng;
Expand Down Expand Up @@ -139,13 +144,13 @@ impl DummyFrontend {
id
}

pub fn send_execute_request(&self, code: &str) -> String {
pub fn send_execute_request(&self, code: &str, options: ExecuteRequestOptions) -> String {
self.send_shell(ExecuteRequest {
code: String::from(code),
silent: false,
store_history: true,
user_expressions: serde_json::Value::Null,
allow_stdin: false,
allow_stdin: options.allow_stdin,
stop_on_error: false,
})
}
Expand Down Expand Up @@ -248,22 +253,48 @@ impl DummyFrontend {
})
}

pub fn recv_iopub_stream_stdout(&self) -> String {
let msg = self.recv_iopub();

assert_matches!(msg, Message::StreamOutput(data) => {
assert_eq!(data.content.name, Stream::Stdout);
data.content.text
})
pub fn recv_iopub_stream_stdout(&self, expect: &str) {
self.recv_iopub_stream(expect, Stream::Stdout)
}

pub fn recv_iopub_stream_stderr(&self) -> String {
let msg = self.recv_iopub();
pub fn recv_iopub_stream_stderr(&self, expect: &str) {
self.recv_iopub_stream(expect, Stream::Stderr)
}

assert_matches!(msg, Message::StreamOutput(data) => {
assert_eq!(data.content.name, Stream::Stderr);
data.content.text
})
/// Receive from IOPub Stream
///
/// Stdout and Stderr Stream messages are buffered, so to reliably test against them
/// we have to collect the messages in batches on the receiving end and compare against
/// an expected message.
fn recv_iopub_stream(&self, expect: &str, stream: Stream) {
let mut out = String::new();

loop {
// Receive a piece of stream output (with a timeout)
let msg = self.recv_iopub();

// Assert its type
let piece = assert_matches!(msg, Message::StreamOutput(data) => {
assert_eq!(data.content.name, stream);
data.content.text
});

// Add to what we've already collected
out += piece.as_str();

if out == expect {
// Done, found the entire `expect` string
return;
}

if !expect.starts_with(out.as_str()) {
// Something is wrong, message doesn't match up
panic!("Expected IOPub stream of '{expect}'. Actual stream of '{out}'.");
}

// We have a prefix of `expect`, but not the whole message yet.
// Wait on the next IOPub Stream message.
DavisVaughan marked this conversation as resolved.
Show resolved Hide resolved
}
}

/// Receive from IOPub and assert ExecuteResult message. Returns compulsory
Expand All @@ -276,6 +307,21 @@ impl DummyFrontend {
})
}

/// Receive from Stdin and assert `InputRequest` message.
/// Returns the `prompt`.
pub fn recv_stdin_input_request(&self) -> String {
let msg = self.recv_stdin();

assert_matches!(msg, Message::InputRequest(data) => {
data.content.prompt
})
}

/// Send back an `InputReply` to an `InputRequest` over Stdin
pub fn send_stdin_input_reply(&self, value: String) {
self.send_stdin(InputReply { value })
}

/// Receives a (raw) message from the heartbeat socket
pub fn recv_heartbeat(&self) -> zmq::Message {
let mut msg = zmq::Message::new();
Expand Down Expand Up @@ -339,3 +385,9 @@ impl DummyFrontend {
}
}
}

impl Default for ExecuteRequestOptions {
fn default() -> Self {
Self { allow_stdin: false }
}
}
2 changes: 1 addition & 1 deletion crates/amalthea/src/kernel.rs
Original file line number Diff line number Diff line change
Expand Up @@ -38,7 +38,7 @@ use crate::wire::jupyter_message::Message;
use crate::wire::jupyter_message::OutboundMessage;

macro_rules! report_error {
($($arg:tt)+) => (if cfg!(debug_assertions) { log::error!($($arg)+) } else { panic!($($arg)+) })
($($arg:tt)+) => (if cfg!(debug_assertions) { panic!($($arg)+) } else { log::error!($($arg)+) })
}

/// A Kernel represents a unique Jupyter kernel session and is the host for all
Expand Down
21 changes: 7 additions & 14 deletions crates/ark/src/analysis/input_boundaries.rs
Original file line number Diff line number Diff line change
Expand Up @@ -65,20 +65,13 @@ impl InputBoundary {
/// an invalid one, and invalid inputs are always trailing).
/// - There is only one incomplete and one invalid input in a set of inputs.
pub fn input_boundaries(text: &str) -> anyhow::Result<Vec<InputBoundary>> {
let mut lines: Vec<&str> = text.lines().collect();

// Rectify for `lines()` ignoring trailing empty lines
match text.chars().last() {
Some(last) if last == '\n' => lines.push(""),
None => lines.push(""),
_ => {},
}

// Create a duplicate vector of lines on the R side too so we don't have to
// reallocate memory each time we parse a new subset of lines
let lines_r = CharacterVector::create(lines.iter());
let lines: Vec<&str> = crate::strings::lines(text).collect();
let n_lines: u32 = lines.len().try_into()?;

// Create the vector on the R side so we don't have to reallocate memory
// for the elements each time we parse a new subset of lines
let lines = CharacterVector::create(lines);

let mut complete: Vec<LineRange> = vec![];
let mut incomplete: Option<LineRange> = None;
let mut invalid: Option<LineRange> = None;
Expand Down Expand Up @@ -120,9 +113,9 @@ pub fn input_boundaries(text: &str) -> anyhow::Result<Vec<InputBoundary>> {
// Grab all code up to current line. We don't slice the vector in the
// first iteration as it's not needed.
let subset = if current_line == n_lines - 1 {
lines_r.clone()
lines.clone()
} else {
CharacterVector::try_from(&lines_r.slice()[..=current_line as usize])?
CharacterVector::try_from(&lines.slice()[..=current_line as usize])?
};

// Parse within source file to get source references
Expand Down
7 changes: 7 additions & 0 deletions crates/ark/src/fixtures/dummy_frontend.rs
Original file line number Diff line number Diff line change
Expand Up @@ -55,6 +55,13 @@ impl DummyArkFrontend {
let frontend = DummyFrontend::new();
let connection_file = frontend.get_connection_file();

// We don't want cli to try and restore the cursor, it breaks our tests
// by adding unecessary ANSI escapes. We don't need this in Positron because
// cli also checks `isatty(stdout())`, which is false in Positron because
// we redirect stdout.
// https://github.com/r-lib/cli/blob/1220ed092c03e167ff0062e9839c81d7258a4600/R/onload.R#L33-L40
unsafe { std::env::set_var("R_CLI_HIDE_CURSOR", "false") };

// Start the kernel in this thread so that panics are propagated
crate::start::start_kernel(
connection_file,
Expand Down
Loading
Loading