дзеркало
https://github.com/l0ng-ai/openidea.git
синхронізовано 2026-11-09 03:05:40 +02:00
Opens .ipynb files in a native notebook editor, in a project or LightEdit. - crates/ide-notebook: lossless nbformat documents (unchanged notebooks save byte-for-byte, unknown fields kept, external changes detected) and owned local kernels over ZMQ via kernelspec — the selected Python environment only needs ipykernel, no Jupyter server. - ide-host owns notebook documents per project incarnation and admits one execution batch at a time; outputs stream over IPC channels and kernel status is an IdeEvent. - src/features/notebook: virtualized Python/Markdown cells, insert/delete/move/change type, Run Cell / Run All (Shift+Enter), interrupt, restart, interpreter selection from ide-sdk discovery, shared split views. Raw/unknown cells stay read-only. - Custom editor kinds take part in dirty state, save/save all, undo/redo and Save/Don't Save/Cancel; closing editors from other features awaits that decision. - Outputs: text, tracebacks, PNG/JPEG and sanitized SVG inline; HTML sanitized in a sandboxed iframe without allow-same-origin; JavaScript output is shown as inert source. - Explicit launches (opening a notebook from the OS) win over session restoration. Closes #131 Co-authored-by: dvictor357 <[email protected]>
72 рядки
3.2 KiB
Rust
72 рядки
3.2 KiB
Rust
use super::*;
|
|
use serde_json::json;
|
|
|
|
fn frame(channel: Channel, parent: &str, kind: &str, content: Value) -> Frame {
|
|
Frame { channel, parent: parent.into(), kind: kind.into(), content, bytes: 1, _permit: None }
|
|
}
|
|
|
|
#[test]
|
|
fn completion_requires_both_channels_in_either_order_and_ignores_old_requests() {
|
|
for idle_first in [false, true] {
|
|
let mut completion = Completion::default();
|
|
let idle = frame(Channel::Iopub, "current", "status", json!({"execution_state":"idle"}));
|
|
let reply = frame(Channel::Shell, "current", "execute_reply", json!({"status":"error","execution_count":7}));
|
|
let old_idle = frame(Channel::Iopub, "old-generation", "status", json!({"execution_state":"idle"}));
|
|
let old_reply =
|
|
frame(Channel::Shell, "old-generation", "execute_reply", json!({"status":"ok","execution_count":99}));
|
|
completion.observe(&old_reply, "current").unwrap();
|
|
completion.observe(&old_idle, "current").unwrap();
|
|
assert!(!completion.complete());
|
|
let (first, second) = if idle_first { (&idle, &reply) } else { (&reply, &idle) };
|
|
completion.observe(first, "current").unwrap();
|
|
assert!(!completion.complete(), "one channel cannot finalize execution");
|
|
completion.observe(second, "current").unwrap();
|
|
assert!(completion.complete());
|
|
let result = completion.reply.unwrap();
|
|
assert_eq!(result.status, "error");
|
|
assert_eq!(result.execution_count, Some(7));
|
|
}
|
|
}
|
|
|
|
#[test]
|
|
fn wrong_channel_cannot_forge_execution_completion() {
|
|
let mut completion = Completion::default();
|
|
completion
|
|
.observe(&frame(Channel::Iopub, "run", "execute_reply", json!({"status":"ok","execution_count":2})), "run")
|
|
.unwrap();
|
|
completion.observe(&frame(Channel::Shell, "run", "status", json!({"execution_state":"idle"})), "run").unwrap();
|
|
assert!(!completion.complete());
|
|
assert!(completion.reply.is_none());
|
|
assert!(!completion.idle);
|
|
}
|
|
|
|
#[test]
|
|
fn malformed_reply_is_rejected_instead_of_reported_as_success() {
|
|
for content in [
|
|
json!({"status":"unexpected"}),
|
|
json!({"execution_count":1}),
|
|
json!({"status":"ok","execution_count":-1}),
|
|
json!({"status":"ok","execution_count":4294967296_u64}),
|
|
] {
|
|
let mut completion = Completion::default();
|
|
let error = completion.observe(&frame(Channel::Shell, "run", "execute_reply", content), "run").unwrap_err();
|
|
assert_eq!(error.kind, ide_core::error::ErrorKind::Protocol);
|
|
assert!(!completion.complete());
|
|
}
|
|
}
|
|
|
|
#[test]
|
|
fn output_budget_stops_at_byte_or_message_boundary_without_wrapping() {
|
|
let mut bytes = Budget::default();
|
|
bytes.add(MAX_OUTPUT_BYTES).unwrap();
|
|
assert_eq!(bytes.add(1).unwrap_err().kind, ide_core::error::ErrorKind::Unavailable);
|
|
let mut messages = Budget::default();
|
|
for _ in 0..MAX_OUTPUT_MESSAGES {
|
|
messages.add(0).unwrap();
|
|
}
|
|
assert_eq!(messages.add(0).unwrap_err().kind, ide_core::error::ErrorKind::Unavailable);
|
|
let mut overflow = Budget::default();
|
|
assert_eq!(overflow.add(usize::MAX).unwrap_err().kind, ide_core::error::ErrorKind::Unavailable);
|
|
assert_eq!(overflow.add(1).unwrap_err().kind, ide_core::error::ErrorKind::Unavailable);
|
|
}
|