venus_server/
session.rs

1//! Notebook session management.
2//!
3//! Manages the state of an active notebook session including
4//! compilation, execution, and output caching.
5
6use std::collections::HashMap;
7use std::hash::{Hash, Hasher};
8use std::path::{Path, PathBuf};
9use std::sync::Arc;
10use std::sync::atomic::{AtomicBool, Ordering};
11use std::time::{Duration, Instant};
12
13use tokio::sync::{RwLock, broadcast};
14use venus_core::widgets::{WidgetDef, WidgetValue};
15use venus_core::compile::{
16    CellCompiler, CompilationResult, CompilerConfig, ToolchainManager, UniverseBuilder,
17};
18use venus_core::execute::{ExecutorKillHandle, ProcessExecutor};
19use venus_core::graph::{CellId, CellInfo, CellParser, CellType, DefinitionCell, GraphEngine, MarkdownCell, MoveDirection, SourceEditor};
20use venus_core::paths::NotebookDirs;
21
22use crate::error::{ServerError, ServerResult};
23use crate::protocol::{CellOutput, CellState, CellStatus, ServerMessage};
24use crate::undo::{UndoManager, UndoableOperation};
25use venus_core::state::BoxedOutput;
26
27/// Find workspace root by walking up from notebook path to find Cargo.toml.
28/// Returns (workspace_root, cargo_toml_path).
29fn find_workspace_root(notebook_path: &Path) -> (Option<PathBuf>, Option<PathBuf>) {
30    let mut current = notebook_path.parent();
31
32    while let Some(dir) = current {
33        let cargo_toml = dir.join("Cargo.toml");
34        if cargo_toml.exists() {
35            return (Some(dir.to_path_buf()), Some(cargo_toml));
36        }
37        current = dir.parent();
38    }
39
40    (None, None)
41}
42
43/// Shared interrupt flag that can be checked without locks.
44pub type InterruptFlag = Arc<AtomicBool>;
45
46/// Capacity for the broadcast channel.
47/// 256 messages should be sufficient for normal notebook operation.
48/// If clients fall behind, older messages will be dropped.
49const MESSAGE_CHANNEL_CAPACITY: usize = 256;
50
51/// A notebook session.
52pub struct NotebookSession {
53    /// Path to the notebook file.
54    path: PathBuf,
55
56    /// Path to workspace Cargo.toml (if found).
57    workspace_cargo_toml: Option<PathBuf>,
58
59    /// Parsed code cells.
60    cells: Vec<CellInfo>,
61
62    /// Parsed markdown cells.
63    markdown_cells: Vec<MarkdownCell>,
64
65    /// Parsed definition cells (imports, types, helpers).
66    definition_cells: Vec<DefinitionCell>,
67
68    /// Dependency graph.
69    graph: GraphEngine,
70
71    /// Cell states for clients (both code and markdown).
72    cell_states: HashMap<CellId, CellState>,
73
74    /// Toolchain manager.
75    toolchain: ToolchainManager,
76
77    /// Compiler configuration.
78    config: CompilerConfig,
79
80    /// Universe path (compiled dependencies).
81    universe_path: Option<PathBuf>,
82
83    /// Dependencies hash for cache invalidation.
84    deps_hash: u64,
85
86    /// Broadcast channel for server messages.
87    tx: broadcast::Sender<ServerMessage>,
88
89    /// Whether an execution is in progress.
90    executing: bool,
91
92    /// Cached cell outputs for dependency passing.
93    /// Maps cell ID to its serialized output.
94    cell_outputs: HashMap<CellId, Arc<BoxedOutput>>,
95
96    /// Process-based executor for isolated cell execution.
97    /// Uses worker processes that can be killed for true interruption.
98    executor: ProcessExecutor,
99
100    /// Optional execution timeout for execute_all.
101    /// After this duration, the executor kills the current worker.
102    execution_timeout: Option<Duration>,
103
104    /// Shared flag indicating if current execution was interrupted by user.
105    /// When true, errors should be reported as "interrupted" not as failures.
106    /// This is shared with AppState so interrupt handler can set it.
107    interrupted: InterruptFlag,
108
109    /// Widget values per cell.
110    /// Maps cell ID -> widget ID -> current value.
111    widget_values: HashMap<CellId, HashMap<String, WidgetValue>>,
112
113    /// Widget definitions per cell (from last execution).
114    /// Used to send widget state to newly connected clients.
115    widget_defs: HashMap<CellId, Vec<WidgetDef>>,
116
117    /// Execution history per cell.
118    /// Stores both serialized output (for dependent cells) and display output.
119    cell_output_history: HashMap<CellId, Vec<OutputHistoryEntry>>,
120
121    /// Current history index per cell.
122    cell_history_index: HashMap<CellId, usize>,
123
124    /// Undo/redo manager for cell operations.
125    undo_manager: UndoManager,
126
127    /// Pending edits from the editor (not yet saved to disk).
128    /// These are saved to disk when the cell is executed.
129    pending_edits: HashMap<CellId, String>,
130}
131
132/// Maximum number of history entries per cell.
133const MAX_HISTORY_PER_CELL: usize = 10;
134
135/// A single history entry for a cell's execution.
136#[derive(Clone)]
137pub struct OutputHistoryEntry {
138    /// Serialized output for passing to dependent cells.
139    pub serialized: Arc<BoxedOutput>,
140    /// Display output for the frontend.
141    pub display: CellOutput,
142    /// Timestamp when this execution completed.
143    pub timestamp: u64,
144}
145
146/// Thread-safe session handle.
147pub type SessionHandle = Arc<RwLock<NotebookSession>>;
148
149impl NotebookSession {
150    /// Create a new notebook session.
151    ///
152    /// Uses process isolation for cell execution, allowing true interruption
153    /// by killing worker processes.
154    ///
155    /// The `interrupted` flag is shared with AppState so the interrupt handler
156    /// can set it without needing the session lock.
157    pub fn new(
158        path: impl AsRef<Path>,
159        interrupted: InterruptFlag,
160    ) -> ServerResult<(Self, broadcast::Receiver<ServerMessage>)> {
161        let path = path.as_ref().canonicalize().map_err(|e| ServerError::Io {
162            path: path.as_ref().to_path_buf(),
163            message: e.to_string(),
164        })?;
165
166        // Find workspace Cargo.toml (if it exists)
167        let (_workspace_root, workspace_cargo_toml) = find_workspace_root(&path);
168
169        // Set up directories using shared abstraction
170        let dirs = NotebookDirs::from_notebook_path(&path)?;
171
172        let toolchain = ToolchainManager::new()?;
173        let config = CompilerConfig::for_notebook(&dirs);
174
175        let (tx, rx) = broadcast::channel(MESSAGE_CHANNEL_CAPACITY);
176
177        // Create process executor with warm worker pool
178        let executor = ProcessExecutor::new(&dirs.state_dir)?;
179
180        let mut session = Self {
181            path,
182            workspace_cargo_toml,
183            cells: Vec::new(),
184            markdown_cells: Vec::new(),
185            definition_cells: Vec::new(),
186            graph: GraphEngine::new(),
187            cell_states: HashMap::new(),
188            toolchain,
189            config,
190            universe_path: None,
191            deps_hash: 0,
192            tx,
193            executing: false,
194            cell_outputs: HashMap::new(),
195            executor,
196            execution_timeout: None,
197            interrupted,
198            widget_values: HashMap::new(),
199            widget_defs: HashMap::new(),
200            cell_output_history: HashMap::new(),
201            cell_history_index: HashMap::new(),
202            undo_manager: UndoManager::new(),
203            pending_edits: HashMap::new(),
204        };
205
206        session.reload()?;
207
208        Ok((session, rx))
209    }
210
211    /// Get the notebook path.
212    pub fn path(&self) -> &Path {
213        &self.path
214    }
215
216    /// Subscribe to server messages.
217    pub fn subscribe(&self) -> broadcast::Receiver<ServerMessage> {
218        self.tx.subscribe()
219    }
220
221    /// Get a cell by ID.
222    fn get_cell(&self, cell_id: CellId) -> Option<&CellInfo> {
223        self.cells.iter().find(|c| c.id == cell_id)
224    }
225
226    /// Set the status of a code cell.
227    fn set_cell_status(&mut self, cell_id: CellId, status: CellStatus) {
228        if let Some(CellState::Code { status: cell_status, .. }) = self.cell_states.get_mut(&cell_id) {
229            *cell_status = status;
230        }
231    }
232
233    /// Broadcast a server message, ignoring send failures.
234    pub fn broadcast(&self, msg: ServerMessage) {
235        let _ = self.tx.send(msg);
236    }
237
238    /// Reload the notebook from disk.
239    pub fn reload(&mut self) -> ServerResult<()> {
240        let source = std::fs::read_to_string(&self.path)?;
241
242        // Parse cells (code, markdown, and definitions)
243        let mut parser = CellParser::new();
244        let parse_result = parser.parse_file(&self.path)?;
245        self.cells = parse_result.code_cells;
246        self.markdown_cells = parse_result.markdown_cells;
247        self.definition_cells = parse_result.definition_cells;
248
249        // Build graph and update code cells with real IDs (parser returns placeholder IDs)
250        self.graph = GraphEngine::new();
251        for cell in &mut self.cells {
252            let real_id = self.graph.add_cell(cell.clone());
253            cell.id = real_id;
254        }
255        self.graph.resolve_dependencies()?;
256
257        // Assign unique IDs to markdown cells (they don't participate in the dependency graph)
258        let mut next_id = if let Some(max_code_id) = self.cells.iter().map(|c| c.id.as_usize()).max() {
259            max_code_id + 1
260        } else {
261            0
262        };
263        for md_cell in &mut self.markdown_cells {
264            md_cell.id = CellId::new(next_id);
265            next_id += 1;
266        }
267
268        // Assign unique IDs to definition cells
269        for def_cell in &mut self.definition_cells {
270            def_cell.id = CellId::new(next_id);
271            next_id += 1;
272        }
273
274        // Write virtual notebook.rs file for LSP analysis BEFORE building universe
275        // This ensures the file exists when universe is compiled (lib.rs includes `pub mod notebook;`)
276        if let Err(e) = self.write_virtual_notebook_file() {
277            tracing::warn!("Failed to write virtual notebook file: {}", e);
278        }
279
280        // Build universe (always needed for bincode/serde runtime)
281        let mut universe_builder =
282            UniverseBuilder::new(self.config.clone(), self.toolchain.clone(), self.workspace_cargo_toml.clone());
283        universe_builder.parse_dependencies(&source, &self.definition_cells)?;
284
285        self.universe_path = Some(universe_builder.build()?);
286        self.deps_hash = universe_builder.deps_hash();
287
288        // Update cell states
289        self.update_cell_states();
290
291        // NOTE: We do NOT broadcast state here because reload() is called by the file watcher
292        // when the notebook file changes (e.g., editor auto-save). Broadcasting on every file
293        // change causes the UI to refresh continuously. Instead, each cell operation (insert,
294        // edit, delete, etc.) explicitly broadcasts state after calling reload().
295
296        Ok(())
297    }
298
299    /// Strip the first heading from a doc comment (since it's used as display name).
300    ///
301    /// If the doc comment starts with `# Heading`, removes that line and returns
302    /// the rest of the content. Otherwise returns the original content.
303    fn strip_display_name_from_description(doc_comment: &Option<String>) -> Option<String> {
304        doc_comment.as_ref().and_then(|doc| {
305            let lines: Vec<&str> = doc.lines().collect();
306
307            // Find the first heading line
308            if let Some(first_line) = lines.first() {
309                let trimmed = first_line.trim();
310                if trimmed.starts_with('#') {
311                    // Skip the heading line and return the rest
312                    let remaining: Vec<&str> = lines.iter().skip(1).copied()
313                        .collect();
314
315                    // Trim leading empty lines
316                    let trimmed_lines: Vec<&str> = remaining.iter()
317                        .skip_while(|line| line.trim().is_empty()).copied()
318                        .collect();
319
320                    if trimmed_lines.is_empty() {
321                        return None;
322                    }
323
324                    return Some(trimmed_lines.join("\n"));
325                }
326            }
327
328            // No heading found, return original
329            Some(doc.clone())
330        })
331    }
332
333    /// Update cell states from parsed cells.
334    fn update_cell_states(&mut self) {
335        let mut new_states = HashMap::new();
336
337        // Add code cells
338        for cell in &self.cells {
339            let existing = self.cell_states.get(&cell.id);
340
341            // Extract status, output, dirty from existing state if it's a code cell
342            let (status, output, dirty) = if let Some(CellState::Code { status, output, dirty, .. }) = existing {
343                (*status, output.clone(), *dirty)
344            } else {
345                // New cells start pristine: no output, not dirty
346                (CellStatus::default(), None, false)
347            };
348
349            let state = CellState::Code {
350                id: cell.id,
351                name: cell.name.clone(),
352                display_name: cell.display_name.clone(),
353                source: cell.source_code.clone(),
354                description: Self::strip_display_name_from_description(&cell.doc_comment),
355                return_type: cell.return_type.clone(),
356                dependencies: cell
357                    .dependencies
358                    .iter()
359                    .map(|d| d.param_name.clone())
360                    .collect(),
361                status,
362                output,
363                dirty,
364            };
365            new_states.insert(cell.id, state);
366        }
367
368        // Add markdown cells
369        for md_cell in &self.markdown_cells {
370            let state = CellState::Markdown {
371                id: md_cell.id,
372                content: md_cell.content.clone(),
373            };
374            new_states.insert(md_cell.id, state);
375        }
376
377        // Add definition cells
378        for def_cell in &self.definition_cells {
379            let state = CellState::Definition {
380                id: def_cell.id,
381                content: def_cell.content.clone(),
382                definition_type: def_cell.definition_type,
383                doc_comment: def_cell.doc_comment.clone(),
384            };
385            new_states.insert(def_cell.id, state);
386        }
387
388        self.cell_states = new_states;
389    }
390
391    /// Write virtual notebook.rs file for LSP analysis.
392    /// This file contains all cell content in source order so rust-analyzer can analyze it.
393    /// Collect all cells (code and definition) in source order.
394    /// Returns a vector of (cell_id, start_line, cell_type) tuples sorted by line number.
395    /// Includes all cell types: code, markdown, and definition cells.
396    fn collect_cells_in_source_order(&self) -> Vec<(CellId, usize, CellType)> {
397        let mut all_cells: Vec<(CellId, usize, CellType)> = Vec::new();
398
399        for cell in &self.cells {
400            all_cells.push((cell.id, cell.span.start_line, CellType::Code));
401        }
402
403        for md_cell in &self.markdown_cells {
404            all_cells.push((md_cell.id, md_cell.span.start_line, CellType::Markdown));
405        }
406
407        for def_cell in &self.definition_cells {
408            all_cells.push((def_cell.id, def_cell.span.start_line, CellType::Definition));
409        }
410
411        all_cells.sort_by_key(|(_, line, _)| *line);
412        all_cells
413    }
414
415    fn write_virtual_notebook_file(&self) -> std::io::Result<()> {
416        use std::fs;
417
418        let dirs = NotebookDirs::from_notebook_path(&self.path)
419            .map_err(|e| std::io::Error::other(e.to_string()))?;
420        let universe_src = dirs.build_dir.join("universe").join("src");
421        fs::create_dir_all(&universe_src)?;
422
423        let mut lines = Vec::new();
424        let all_cells = self.collect_cells_in_source_order();
425
426        // Build combined source
427        for (cell_id, _, cell_type) in all_cells {
428            match cell_type {
429                CellType::Code => {
430                    if let Some(cell) = self.cells.iter().find(|c| c.id == cell_id) {
431                        lines.push(cell.source_code.clone());
432                        lines.push(String::new()); // Empty line between cells
433                    }
434                }
435                CellType::Definition => {
436                    if let Some(def_cell) = self.definition_cells.iter().find(|c| c.id == cell_id) {
437                        lines.push(def_cell.content.clone());
438                        lines.push(String::new()); // Empty line between cells
439                    }
440                }
441                _ => {} // Ignore markdown cells
442            }
443        }
444
445        let content = lines.join("\n");
446        fs::write(universe_src.join("notebook.rs"), content)?;
447
448        Ok(())
449    }
450
451    /// Get the full notebook state.
452    /// Returns a snapshot of the current notebook state for UI rendering.
453    /// Note: The virtual notebook.rs file for LSP is written during reload(), not here.
454    pub fn get_state(&self) -> ServerMessage {
455        // Source order: all cells (code + markdown + definition) in the order they appear in the .rs file
456        let all_cells = self.collect_cells_in_source_order();
457        let source_order: Vec<CellId> = all_cells.into_iter().map(|(id, _, _)| id).collect();
458
459        // Execution order: topologically sorted for dependency resolution (code cells only)
460        let execution_order = match self.graph.topological_order() {
461            Ok(order) => order,
462            Err(e) => {
463                tracing::error!("Failed to compute execution order: {}", e);
464                Vec::new()
465            }
466        };
467
468        // Find workspace root by walking up from notebook path to find Cargo.toml
469        let (workspace_root, cargo_toml_path) = find_workspace_root(&self.path);
470
471        ServerMessage::NotebookState {
472            path: self.path.display().to_string(),
473            cells: self.cell_states.values().cloned().collect(),
474            source_order,
475            execution_order,
476            workspace_root: workspace_root.map(|p| p.display().to_string()),
477            cargo_toml_path: cargo_toml_path.map(|p| p.display().to_string()),
478        }
479    }
480
481    /// Store a pending edit from the editor (not yet saved to disk).
482    ///
483    /// The edit will be saved to disk when the cell is executed.
484    pub fn store_pending_edit(&mut self, cell_id: CellId, source: String) {
485        self.pending_edits.insert(cell_id, source);
486    }
487
488    /// Execute a specific cell.
489    ///
490    /// Uses process isolation - the cell runs in a worker process that can
491    /// be killed immediately for interruption.
492    pub async fn execute_cell(&mut self, cell_id: CellId) -> ServerResult<()> {
493        // Get cell name before potential reload (IDs change after reload!)
494        let cell_name = self
495            .get_cell(cell_id)
496            .map(|c| c.name.clone())
497            .ok_or(ServerError::CellNotFound(cell_id))?;
498
499        // Save pending edit to disk before executing
500        if let Some(new_source) = self.pending_edits.remove(&cell_id) {
501            self.edit_cell(cell_id, new_source)?;
502        }
503        if self.executing {
504            return Err(ServerError::ExecutionInProgress);
505        }
506
507        // After reload(), cell IDs change! Find cell by name instead
508        let cell = self
509            .cells
510            .iter()
511            .find(|c| c.name == cell_name)
512            .ok_or(ServerError::CellNotFound(cell_id))?
513            .clone();
514
515        let cell_id = cell.id; // Use the NEW ID after reload
516
517        self.executing = true;
518
519        // Reset interrupted flag at the start of each execution
520        self.interrupted.store(false, Ordering::SeqCst);
521
522        // Check if all dependencies have outputs available
523        let missing_deps: Vec<&str> = cell
524            .dependencies
525            .iter()
526            .filter(|dep| {
527                let producer = self.cells.iter().find(|c| c.name == dep.param_name);
528                match producer {
529                    Some(c) => !self.cell_outputs.contains_key(&c.id),
530                    None => true,
531                }
532            })
533            .map(|d| d.param_name.as_str())
534            .collect();
535
536        if !missing_deps.is_empty() {
537            self.set_cell_status(cell_id, CellStatus::Error);
538            self.broadcast(ServerMessage::CellError {
539                cell_id,
540                error: format!(
541                    "Missing dependencies: {}. Run dependent cells first.",
542                    missing_deps.join(", ")
543                ),
544                location: None,
545            });
546            self.executing = false;
547            return Ok(());
548        }
549
550        // Compile
551        self.set_cell_status(cell_id, CellStatus::Compiling);
552
553        let mut compiler = CellCompiler::new(self.config.clone(), self.toolchain.clone());
554        if let Some(ref up) = self.universe_path {
555            compiler = compiler.with_universe(up.clone());
556        }
557
558        let result = compiler.compile(&cell, self.deps_hash);
559
560        match result {
561            CompilationResult::Success(compiled) | CompilationResult::Cached(compiled) => {
562                // Execute
563                self.set_cell_status(cell_id, CellStatus::Running);
564                self.broadcast(ServerMessage::CellStarted { cell_id });
565
566                let start = Instant::now();
567
568                // Register the compiled cell with the executor
569                self.executor.register_cell(compiled, cell.dependencies.len());
570
571                // Gather dependency outputs in the order the cell expects them
572                let inputs: Vec<Arc<BoxedOutput>> = cell
573                    .dependencies
574                    .iter()
575                    .filter_map(|dep| {
576                        self.cells
577                            .iter()
578                            .find(|c| c.name == dep.param_name)
579                            .and_then(|c| self.cell_outputs.get(&c.id).cloned())
580                    })
581                    .collect();
582
583                // Get ALL widget values from all cells (widgets can be in any cell)
584                let widget_values = self.get_all_widget_values();
585                let widget_values_json = if widget_values.is_empty() {
586                    Vec::new()
587                } else {
588                    serde_json::to_vec(&widget_values).unwrap_or_default()
589                };
590
591                // Execute the cell in an isolated worker process with widget values
592                let exec_result = self.executor.execute_cell_with_widgets(
593                    cell_id,
594                    &inputs,
595                    widget_values_json,
596                );
597
598                let duration = start.elapsed();
599
600                match exec_result {
601                    Ok((output, widgets_json)) => {
602                        // Check if output changed (for smart dirty marking)
603                        let old_hash = self.cell_outputs.get(&cell_id)
604                            .map(|old| Self::output_hash(old));
605                        let new_hash = Self::output_hash(&output);
606                        let output_changed = old_hash.is_none_or(|h| h != new_hash);
607
608                        // Store output for dependent cells
609                        let output_arc = Arc::new(output);
610                        self.cell_outputs.insert(cell_id, output_arc.clone());
611
612                        // Also store in executor state for consistency
613                        self.executor.state_mut().store_output(cell_id, (*output_arc).clone());
614
615                        // Parse and store widget definitions
616                        let widgets: Vec<WidgetDef> = if widgets_json.is_empty() {
617                            Vec::new()
618                        } else {
619                            serde_json::from_slice(&widgets_json).unwrap_or_default()
620                        };
621                        self.store_widget_defs(cell_id, widgets.clone());
622
623                        let cell_output = CellOutput {
624                            text: output_arc.display_text().map(|s| s.to_string()),
625                            html: None,
626                            image: None,
627                            json: None,
628                            widgets,
629                        };
630
631                        // Add to history
632                        self.add_to_history(cell_id, output_arc.clone(), cell_output.clone());
633
634                        if let Some(state) = self.cell_states.get_mut(&cell_id) {
635                            state.set_status(CellStatus::Success);
636                            state.set_output(Some(cell_output.clone()));
637                            state.set_dirty(false);
638                        }
639
640                        // Mark dependents dirty if output changed
641                        if output_changed {
642                            let dirty_cells = self.mark_dependents_dirty_and_get(cell_id);
643                            for dirty_id in dirty_cells {
644                                self.broadcast(ServerMessage::CellDirty { cell_id: dirty_id });
645                            }
646                        }
647
648                        self.broadcast(ServerMessage::CellCompleted {
649                            cell_id,
650                            duration_ms: duration.as_millis() as u64,
651                            output: Some(cell_output),
652                        });
653                    }
654                    Err(e) => {
655                        // Check if this was an abort or user-initiated interrupt
656                        let was_interrupted = self.interrupted.swap(false, Ordering::SeqCst);
657                        if matches!(e, venus_core::Error::Aborted) || was_interrupted {
658                            // Send friendly "interrupted" message instead of error
659                            self.set_cell_status(cell_id, CellStatus::Idle);
660                            self.broadcast(ServerMessage::ExecutionAborted { cell_id: Some(cell_id) });
661                        } else {
662                            self.set_cell_status(cell_id, CellStatus::Error);
663                            self.broadcast(ServerMessage::CellError {
664                                cell_id,
665                                error: e.to_string(),
666                                location: None,
667                            });
668                        }
669                    }
670                }
671            }
672            CompilationResult::Failed { errors, .. } => {
673                self.set_cell_status(cell_id, CellStatus::Error);
674
675                let compile_errors = errors
676                    .iter()
677                    .map(|e| crate::protocol::CompileErrorInfo {
678                        message: e.message.clone(),
679                        code: e.code.clone(),
680                        location: e.spans.first().map(|s| crate::protocol::SourceLocation {
681                            line: s.location.line as u32,
682                            column: s.location.column as u32,
683                            end_line: s.end_location.as_ref().map(|l| l.line as u32),
684                            end_column: s.end_location.as_ref().map(|l| l.column as u32),
685                        }),
686                        rendered: e.rendered.clone(),
687                    })
688                    .collect();
689
690                self.broadcast(ServerMessage::CompileError {
691                    cell_id,
692                    errors: compile_errors,
693                });
694            }
695        }
696
697        self.executing = false;
698        Ok(())
699    }
700
701    /// Execute all cells in order.
702    ///
703    /// If `execution_timeout` is set, kills the worker process after that duration.
704    /// Unlike cooperative cancellation, this immediately terminates the cell.
705    pub async fn execute_all(&mut self) -> ServerResult<()> {
706        let order = self.graph.topological_order()?;
707        let start = Instant::now();
708        let timeout = self.execution_timeout;
709
710        for cell_id in order {
711            // Check timeout before each cell
712            if timeout.is_some_and(|max_duration| start.elapsed() > max_duration) {
713                self.executor.abort();
714                self.broadcast(ServerMessage::ExecutionAborted { cell_id: Some(cell_id) });
715                return Err(ServerError::ExecutionTimeout);
716            }
717
718            self.execute_cell(cell_id).await?;
719        }
720        Ok(())
721    }
722
723    /// Mark a cell as dirty (needs re-execution).
724    ///
725    /// Only marks cells as dirty if they have existing output (data).
726    /// Cells without output remain pristine (no border).
727    pub fn mark_dirty(&mut self, cell_id: CellId) {
728        // Mark the edited cell as dirty only if it has output
729        if self.cell_outputs.contains_key(&cell_id) {
730            if let Some(state) = self.cell_states.get_mut(&cell_id) {
731                state.set_dirty(true);
732            }
733        }
734
735        // Also mark dependents as dirty (only those with output)
736        let dependents = self.graph.invalidated_cells(cell_id);
737        for dep_id in dependents {
738            if self.cell_outputs.contains_key(&dep_id) {
739                if let Some(state) = self.cell_states.get_mut(&dep_id) {
740                    state.set_dirty(true);
741                }
742            }
743        }
744    }
745
746    /// Check if execution is in progress.
747    pub fn is_executing(&self) -> bool {
748        self.executing
749    }
750
751    /// Abort the current execution immediately.
752    ///
753    /// Unlike cooperative cancellation, this **kills the worker process**,
754    /// providing true interruption even for long-running computations.
755    /// Returns `true` if there was an execution in progress to abort.
756    pub fn abort(&mut self) -> bool {
757        if self.executing {
758            // Kill the worker process - this is immediate
759            self.executor.abort();
760            self.broadcast(ServerMessage::ExecutionAborted { cell_id: None });
761            self.executing = false;
762            true
763        } else {
764            false
765        }
766    }
767
768    /// Set the execution timeout for execute_all.
769    ///
770    /// When set, execute_all will kill the worker process after this duration,
771    /// providing immediate interruption of even long-running cells.
772    pub fn set_execution_timeout(&mut self, timeout: Option<Duration>) {
773        self.execution_timeout = timeout;
774    }
775
776    /// Get the current execution timeout.
777    pub fn execution_timeout(&self) -> Option<Duration> {
778        self.execution_timeout
779    }
780
781    /// Set the interrupted flag.
782    ///
783    /// When true, execution errors will be reported as "interrupted"
784    /// rather than as failures, showing a friendly message to users.
785    pub fn set_interrupted(&mut self, value: bool) {
786        self.interrupted.store(value, Ordering::SeqCst);
787    }
788
789    /// Get a kill handle for the executor.
790    ///
791    /// This handle can be used from another task to kill the current execution
792    /// without needing to acquire the session lock.
793    pub fn get_kill_handle(&self) -> Option<ExecutorKillHandle> {
794        self.executor.get_kill_handle()
795    }
796
797    /// Restart the kernel: kill WorkerPool, spin up new one, clear memory state, preserve source.
798    ///
799    /// This clears all execution state including:
800    /// - Cell outputs and output history
801    /// - Widget values
802    /// - Cached serialized outputs
803    /// - Cell execution status (all cells reset to Idle)
804    ///
805    /// Source code and cell definitions are preserved.
806    pub fn restart_kernel(&mut self) -> ServerResult<()> {
807        // Abort any running execution first
808        if self.executing {
809            self.abort();
810        }
811
812        // Reload notebook from disk (picks up any file changes)
813        self.reload()?;
814
815        // Shutdown old executor and worker pool
816        self.executor.shutdown();
817
818        // Reconstruct state directory path
819        let dirs = NotebookDirs::from_notebook_path(&self.path)?;
820
821        // Create new ProcessExecutor with warm worker pool
822        self.executor = ProcessExecutor::new(&dirs.state_dir)?;
823
824        // Clear all execution state
825        self.cell_outputs.clear();
826        self.widget_values.clear();
827        self.widget_defs.clear();
828        self.cell_output_history.clear();
829        self.cell_history_index.clear();
830
831        // Reset all cell states to Idle and clear outputs
832        for state in self.cell_states.values_mut() {
833            state.set_status(CellStatus::Idle);
834            state.clear_output();
835            state.set_dirty(false);
836        }
837
838        // Broadcast kernel restarted message
839        self.broadcast(ServerMessage::KernelRestarted { error: None });
840
841        // Send updated state to all clients
842        let state_msg = self.get_state();
843        self.broadcast(state_msg);
844
845        Ok(())
846    }
847
848    /// Clear all cell outputs without restarting the kernel.
849    ///
850    /// This clears the display outputs but preserves:
851    /// - Worker pool and execution state
852    /// - Widget values
853    /// - Cell source code
854    ///
855    /// All cells are reset to pristine state (no output, not dirty).
856    pub fn clear_outputs(&mut self) {
857        // Clear outputs from cell states - back to pristine (not dirty)
858        for state in self.cell_states.values_mut() {
859            state.clear_output();
860            state.set_dirty(false); // Pristine - no data, no dirty
861            state.set_status(CellStatus::Idle);
862        }
863
864        // Clear cached outputs
865        self.cell_outputs.clear();
866        let _ = self.executor.state_mut().clear();
867
868        // Clear output history
869        self.cell_output_history.clear();
870        self.cell_history_index.clear();
871
872        // Broadcast outputs cleared message
873        self.broadcast(ServerMessage::OutputsCleared { error: None });
874
875        // Send updated state to all clients
876        let state_msg = self.get_state();
877        self.broadcast(state_msg);
878    }
879
880    /// Get IDs of all dirty cells in topological order.
881    pub fn get_dirty_cell_ids(&self) -> Vec<CellId> {
882        let order = match self.graph.topological_order() {
883            Ok(order) => order,
884            Err(e) => {
885                tracing::error!("Failed to compute order for dirty cells: {}", e);
886                Vec::new()
887            }
888        };
889        order
890            .into_iter()
891            .filter(|id| self.cell_states.get(id).is_some_and(|state| state.is_dirty()))
892            .collect()
893    }
894
895    /// Update a widget value for a cell.
896    ///
897    /// This stores the new value but does NOT trigger re-execution.
898    /// The user must explicitly run the cell to see the effect.
899    pub fn update_widget_value(&mut self, cell_id: CellId, widget_id: String, value: WidgetValue) {
900        self.widget_values
901            .entry(cell_id)
902            .or_default()
903            .insert(widget_id, value);
904    }
905
906    /// Get widget values for a cell.
907    pub fn get_widget_values(&self, cell_id: CellId) -> HashMap<String, WidgetValue> {
908        self.widget_values
909            .get(&cell_id)
910            .cloned()
911            .unwrap_or_default()
912    }
913
914    /// Get ALL widget values from all cells, flattened into a single map.
915    /// Widget IDs should be unique across the notebook.
916    pub fn get_all_widget_values(&self) -> HashMap<String, WidgetValue> {
917        let mut all_values = HashMap::new();
918        for cell_widgets in self.widget_values.values() {
919            for (widget_id, value) in cell_widgets {
920                all_values.insert(widget_id.clone(), value.clone());
921            }
922        }
923        all_values
924    }
925
926    /// Get widget definitions for a cell.
927    pub fn get_widget_defs(&self, cell_id: CellId) -> Vec<WidgetDef> {
928        self.widget_defs
929            .get(&cell_id)
930            .cloned()
931            .unwrap_or_default()
932    }
933
934    /// Store widget definitions from cell execution.
935    fn store_widget_defs(&mut self, cell_id: CellId, widgets: Vec<WidgetDef>) {
936        if widgets.is_empty() {
937            self.widget_defs.remove(&cell_id);
938        } else {
939            self.widget_defs.insert(cell_id, widgets);
940        }
941    }
942
943    /// Add an execution result to history.
944    fn add_to_history(&mut self, cell_id: CellId, serialized: Arc<BoxedOutput>, display: CellOutput) {
945        use std::time::{SystemTime, UNIX_EPOCH};
946
947        let timestamp = SystemTime::now()
948            .duration_since(UNIX_EPOCH)
949            .unwrap_or_default()
950            .as_millis() as u64;
951
952        let entry = OutputHistoryEntry {
953            serialized,
954            display,
955            timestamp,
956        };
957
958        let history = self.cell_output_history.entry(cell_id).or_default();
959        history.push(entry);
960
961        // Trim if too long
962        while history.len() > MAX_HISTORY_PER_CELL {
963            history.remove(0);
964        }
965
966        // Set current index to the latest entry
967        self.cell_history_index.insert(cell_id, history.len() - 1);
968    }
969
970    /// Select a history entry for a cell, making it the current output.
971    /// Returns the display output if successful.
972    pub fn select_history_entry(&mut self, cell_id: CellId, index: usize) -> Option<CellOutput> {
973        // Clone what we need before doing any mutations (to avoid borrow conflicts)
974        let (serialized, display) = {
975            let history = self.cell_output_history.get(&cell_id)?;
976            let entry = history.get(index)?;
977            (entry.serialized.clone(), entry.display.clone())
978        };
979
980        // Update the current output for dependent cells
981        self.cell_outputs.insert(cell_id, serialized.clone());
982        self.executor.state_mut().store_output(cell_id, (*serialized).clone());
983
984        // Update the cell state
985        if let Some(state) = self.cell_states.get_mut(&cell_id) {
986            state.set_output(Some(display.clone()));
987        }
988
989        // Update history index
990        self.cell_history_index.insert(cell_id, index);
991
992        // Mark dependent cells as dirty
993        let _ = self.mark_dependents_dirty_and_get(cell_id);
994
995        Some(display)
996    }
997
998    /// Mark all cells that depend on the given cell as dirty.
999    ///
1000    /// Only marks cells that have existing output (data). Cells without
1001    /// output remain pristine (no border) since they haven't been executed yet.
1002    /// Returns the list of cells that were marked dirty.
1003    fn mark_dependents_dirty_and_get(&mut self, cell_id: CellId) -> Vec<CellId> {
1004        // Use the graph's invalidated_cells which returns all dependents
1005        let dependents = self.graph.invalidated_cells(cell_id);
1006        let mut dirty_cells = Vec::new();
1007
1008        // Skip the first one (the changed cell itself) and mark the rest as dirty
1009        // BUT only if they have output (data) - pristine cells stay pristine
1010        for dep_id in dependents.into_iter().skip(1) {
1011            // Only mark dirty if cell has output (has been executed before)
1012            if self.cell_outputs.contains_key(&dep_id) {
1013                if let Some(state) = self.cell_states.get_mut(&dep_id) {
1014                    state.set_dirty(true);
1015                    dirty_cells.push(dep_id);
1016                }
1017            }
1018        }
1019        dirty_cells
1020    }
1021
1022    /// Compute a hash of output bytes for change detection.
1023    fn output_hash(output: &BoxedOutput) -> u64 {
1024        let mut hasher = rustc_hash::FxHasher::default();
1025        output.bytes().hash(&mut hasher);
1026        hasher.finish()
1027    }
1028
1029    /// Get history count for a cell.
1030    pub fn get_history_count(&self, cell_id: CellId) -> usize {
1031        self.cell_output_history.get(&cell_id).map(|h| h.len()).unwrap_or(0)
1032    }
1033
1034    /// Get current history index for a cell.
1035    pub fn get_history_index(&self, cell_id: CellId) -> usize {
1036        self.cell_history_index.get(&cell_id).copied().unwrap_or(0)
1037    }
1038
1039    /// Get reference to cell states.
1040    pub fn cell_states(&self) -> &HashMap<CellId, CellState> {
1041        &self.cell_states
1042    }
1043
1044    /// Insert a new cell after the specified cell.
1045    ///
1046    /// Modifies the source file and triggers a reload.
1047    /// Returns the name of the newly created cell.
1048    pub fn insert_cell(&mut self, after_cell_id: Option<CellId>) -> ServerResult<String> {
1049        // Convert CellId to cell name if provided
1050        let after_name = after_cell_id.and_then(|id| {
1051            self.cells.iter().find(|c| c.id == id).map(|c| c.name.clone())
1052        });
1053
1054        // Load and edit the source file
1055        let mut editor = SourceEditor::load(&self.path)?;
1056        let new_name = editor.insert_cell(after_name.as_deref())?;
1057        editor.save()?;
1058
1059        // Record for undo (with position for redo)
1060        self.undo_manager.record(UndoableOperation::InsertCell {
1061            cell_name: new_name.clone(),
1062            after_cell_name: after_name,
1063        });
1064
1065        // File watcher will trigger reload, but we can also reload now
1066        // to ensure immediate consistency
1067        self.reload()?;
1068
1069        Ok(new_name)
1070    }
1071
1072    /// Delete a cell from the notebook.
1073    ///
1074    /// Modifies the .rs source file and reloads the notebook.
1075    pub fn delete_cell(&mut self, cell_id: CellId) -> ServerResult<()> {
1076        // Find the cell name
1077        let cell_name = self.cells
1078            .iter()
1079            .find(|c| c.id == cell_id)
1080            .map(|c| c.name.clone())
1081            .ok_or_else(|| ServerError::CellNotFound(cell_id))?;
1082
1083        // Check if any other cells depend on this cell
1084        let dependents: Vec<String> = self.cells
1085            .iter()
1086            .filter(|c| c.id != cell_id) // Don't check self
1087            .filter(|c| {
1088                c.dependencies
1089                    .iter()
1090                    .any(|dep| dep.param_name == cell_name)
1091            })
1092            .map(|c| c.name.clone())
1093            .collect();
1094
1095        if !dependents.is_empty() {
1096            return Err(ServerError::InvalidOperation(format!(
1097                "Cannot delete cell '{}' because it is used by: {}",
1098                cell_name,
1099                dependents.join(", ")
1100            )));
1101        }
1102
1103        // Load and edit the source file
1104        let mut editor = SourceEditor::load(&self.path)?;
1105
1106        // Capture source and position before deletion (for undo)
1107        let source = editor.get_cell_source(&cell_name)?;
1108        let after_cell_name = editor.get_previous_cell_name(&cell_name)?;
1109
1110        editor.delete_cell(&cell_name)?;
1111        editor.save()?;
1112
1113        // Record for undo
1114        self.undo_manager.record(UndoableOperation::DeleteCell {
1115            cell_name: cell_name.clone(),
1116            source,
1117            after_cell_name,
1118        });
1119
1120        // Reload to update in-memory state
1121        self.reload()?;
1122
1123        Ok(())
1124    }
1125
1126    /// Duplicate a cell in the notebook.
1127    ///
1128    /// Creates a copy of the cell with a unique name.
1129    /// Returns the name of the new cell.
1130    pub fn duplicate_cell(&mut self, cell_id: CellId) -> ServerResult<String> {
1131        // Find the cell name
1132        let cell_name = self.cells
1133            .iter()
1134            .find(|c| c.id == cell_id)
1135            .map(|c| c.name.clone())
1136            .ok_or_else(|| ServerError::CellNotFound(cell_id))?;
1137
1138        // Load and edit the source file
1139        let mut editor = SourceEditor::load(&self.path)?;
1140        let new_name = editor.duplicate_cell(&cell_name)?;
1141        editor.save()?;
1142
1143        // Record for undo
1144        self.undo_manager.record(UndoableOperation::DuplicateCell {
1145            original_cell_name: cell_name,
1146            new_cell_name: new_name.clone(),
1147        });
1148
1149        // Reload to update in-memory state
1150        self.reload()?;
1151
1152        Ok(new_name)
1153    }
1154
1155    /// Move a cell up or down in the notebook.
1156    ///
1157    /// Modifies the .rs source file and reloads the notebook.
1158    pub fn move_cell(&mut self, cell_id: CellId, direction: MoveDirection) -> ServerResult<()> {
1159        // Find the cell name
1160        let cell_name = self.cells
1161            .iter()
1162            .find(|c| c.id == cell_id)
1163            .map(|c| c.name.clone())
1164            .ok_or_else(|| ServerError::CellNotFound(cell_id))?;
1165
1166        // Load and edit the source file
1167        let mut editor = SourceEditor::load(&self.path)?;
1168        editor.move_cell(&cell_name, direction)?;
1169        editor.save()?;
1170
1171        // Record for undo
1172        self.undo_manager.record(UndoableOperation::MoveCell {
1173            cell_name,
1174            direction,
1175        });
1176
1177        // Reload to update in-memory state
1178        self.reload()?;
1179
1180        Ok(())
1181    }
1182
1183    /// Edit a code cell's source.
1184    ///
1185    /// Modifies the .rs source file and reloads the notebook.
1186    pub fn edit_cell(&mut self, cell_id: CellId, new_source: String) -> ServerResult<()> {
1187        // Find the cell
1188        let cell = self.cells
1189            .iter()
1190            .find(|c| c.id == cell_id)
1191            .ok_or_else(|| ServerError::CellNotFound(cell_id))?;
1192
1193        let cell_name = cell.name.clone();
1194        let old_source = cell.source_code.clone();
1195
1196        // Load and edit the source file
1197        let mut editor = SourceEditor::load(&self.path)?;
1198
1199        // Reconstruct complete cell (doc comments + #[venus::cell] + function) and get FRESH line numbers
1200        let (reconstructed, start_line, end_line) = editor.reconstruct_and_get_span(&cell_name, &new_source)?;
1201
1202        tracing::info!("Editing cell '{}' lines {}-{}, reconstructed length: {}", cell_name, start_line, end_line, reconstructed.len());
1203        editor.edit_raw_code(start_line, end_line, &reconstructed)?;
1204        editor.save()?;
1205
1206        // Record for undo
1207        self.undo_manager.record(UndoableOperation::EditCell {
1208            cell_id,
1209            start_line,
1210            end_line,
1211            old_source,
1212            new_source: new_source.clone(),
1213        });
1214
1215        // Reload to update in-memory state
1216        // Save outputs by name BEFORE reload (IDs will change)
1217        let outputs_by_name: HashMap<String, Arc<BoxedOutput>> = self.cells.iter()
1218            .filter_map(|c| self.cell_outputs.get(&c.id).map(|o| (c.name.clone(), o.clone())))
1219            .collect();
1220
1221        self.reload()?;
1222
1223        // Restore outputs with NEW IDs (except for the edited cell)
1224        self.cell_outputs.clear();
1225        for cell in &self.cells {
1226            if cell.name != cell_name {
1227                if let Some(output) = outputs_by_name.get(&cell.name) {
1228                    self.cell_outputs.insert(cell.id, output.clone());
1229                }
1230            }
1231        }
1232
1233        Ok(())
1234    }
1235
1236    /// Rename a cell's display name.
1237    ///
1238    /// Updates the cell's doc comment with the new display name and reloads the notebook.
1239    pub fn rename_cell(&mut self, cell_id: CellId, new_display_name: String) -> ServerResult<()> {
1240        // Find the cell name and current display name
1241        let (cell_name, old_display_name) = self.cells
1242            .iter()
1243            .find(|c| c.id == cell_id)
1244            .map(|c| (c.name.clone(), c.display_name.clone()))
1245            .ok_or_else(|| ServerError::CellNotFound(cell_id))?;
1246
1247        // Load and edit the source file
1248        let mut editor = SourceEditor::load(&self.path)?;
1249        editor.rename_cell(&cell_name, &new_display_name)?;
1250        editor.save()?;
1251
1252        // Record for undo
1253        self.undo_manager.record(UndoableOperation::RenameCell {
1254            cell_name,
1255            old_display_name,
1256            new_display_name,
1257        });
1258
1259        // Reload to update in-memory state
1260        self.reload()?;
1261
1262        Ok(())
1263    }
1264
1265    /// Insert a new markdown cell.
1266    ///
1267    /// Modifies the .rs source file and reloads the notebook.
1268    pub fn insert_markdown_cell(&mut self, content: String, after_cell_id: Option<CellId>) -> ServerResult<()> {
1269        // Convert cell ID to line number if provided
1270        let after_line = after_cell_id.and_then(|id| {
1271            // Try to find in code cells
1272            self.cells.iter().find(|c| c.id == id)
1273                .map(|c| c.span.end_line)
1274                .or_else(|| {
1275                    // Try to find in markdown cells
1276                    self.markdown_cells.iter().find(|m| m.id == id)
1277                        .map(|m| m.span.end_line)
1278                })
1279        });
1280
1281        // Load and edit the source file
1282        let mut editor = SourceEditor::load(&self.path)?;
1283        editor.insert_markdown_cell(&content, after_line)?;
1284
1285        // Get the line range of the newly inserted cell (approximate)
1286        let start_line = after_line.map(|l| l + 1).unwrap_or(0);
1287        let line_count = content.lines().count();
1288        let end_line = start_line + line_count;
1289
1290        editor.save()?;
1291
1292        // Record for undo
1293        self.undo_manager.record(UndoableOperation::InsertMarkdownCell {
1294            start_line,
1295            end_line,
1296            content: content.clone(),
1297        });
1298
1299        // Reload to update in-memory state
1300        self.reload()?;
1301
1302        Ok(())
1303    }
1304
1305    /// Edit a markdown cell's content.
1306    ///
1307    /// Modifies the .rs source file and reloads the notebook.
1308    pub fn edit_markdown_cell(&mut self, cell_id: CellId, new_content: String) -> ServerResult<()> {
1309        // Find the markdown cell
1310        let md_cell = self.markdown_cells
1311            .iter()
1312            .find(|m| m.id == cell_id)
1313            .ok_or_else(|| ServerError::CellNotFound(cell_id))?;
1314
1315        let start_line = md_cell.span.start_line;
1316        let end_line = md_cell.span.end_line;
1317        let old_content = md_cell.content.clone();
1318        let is_module_doc = md_cell.is_module_doc;
1319
1320        // Load and edit the source file
1321        let mut editor = SourceEditor::load(&self.path)?;
1322        editor.edit_markdown_cell(start_line, end_line, &new_content, is_module_doc)?;
1323        editor.save()?;
1324
1325        // Record for undo
1326        self.undo_manager.record(UndoableOperation::EditMarkdownCell {
1327            start_line,
1328            end_line,
1329            old_content,
1330            new_content,
1331            is_module_doc,
1332        });
1333
1334        // Reload to update in-memory state
1335        self.reload()?;
1336
1337        Ok(())
1338    }
1339
1340    /// Delete a markdown cell.
1341    ///
1342    /// Modifies the .rs source file and reloads the notebook.
1343    pub fn delete_markdown_cell(&mut self, cell_id: CellId) -> ServerResult<()> {
1344        // Find the markdown cell
1345        let md_cell = self.markdown_cells
1346            .iter()
1347            .find(|m| m.id == cell_id)
1348            .ok_or_else(|| ServerError::CellNotFound(cell_id))?;
1349
1350        let start_line = md_cell.span.start_line;
1351        let end_line = md_cell.span.end_line;
1352        let content = md_cell.content.clone();
1353
1354        // Load and edit the source file
1355        let mut editor = SourceEditor::load(&self.path)?;
1356        editor.delete_markdown_cell(start_line, end_line)?;
1357        editor.save()?;
1358
1359        // Record for undo
1360        self.undo_manager.record(UndoableOperation::DeleteMarkdownCell {
1361            start_line,
1362            content,
1363        });
1364
1365        // Reload to update in-memory state
1366        self.reload()?;
1367
1368        Ok(())
1369    }
1370
1371    /// Move a markdown cell up or down.
1372    ///
1373    /// Modifies the .rs source file and reloads the notebook.
1374    pub fn move_markdown_cell(&mut self, cell_id: CellId, direction: MoveDirection) -> ServerResult<()> {
1375        // Find the markdown cell
1376        let md_cell = self.markdown_cells
1377            .iter()
1378            .find(|m| m.id == cell_id)
1379            .ok_or_else(|| ServerError::CellNotFound(cell_id))?;
1380
1381        let start_line = md_cell.span.start_line;
1382        let end_line = md_cell.span.end_line;
1383
1384        // Load and edit the source file
1385        let mut editor = SourceEditor::load(&self.path)?;
1386        editor.move_markdown_cell(start_line, end_line, direction)?;
1387        editor.save()?;
1388
1389        // Record for undo
1390        self.undo_manager.record(UndoableOperation::MoveMarkdownCell {
1391            start_line,
1392            end_line,
1393            direction,
1394        });
1395
1396        // Reload to update in-memory state
1397        self.reload()?;
1398
1399        Ok(())
1400    }
1401
1402    /// Infer the definition type from content for validation.
1403    ///
1404    /// Provides early error detection when users specify an incorrect definition type.
1405    /// This is a best-effort heuristic based on content analysis.
1406    ///
1407    /// Returns `None` if the type cannot be reliably inferred.
1408    fn infer_definition_type(content: &str) -> Option<venus_core::graph::DefinitionType> {
1409        use venus_core::graph::DefinitionType;
1410
1411        let trimmed = content.trim();
1412
1413        // Check for import statements (use declarations)
1414        if trimmed.starts_with("use ") || trimmed.starts_with("pub use ") {
1415            return Some(DefinitionType::Import);
1416        }
1417
1418        // Check for struct definitions
1419        if trimmed.contains("struct ") {
1420            return Some(DefinitionType::Struct);
1421        }
1422
1423        // Check for enum definitions
1424        if trimmed.contains("enum ") {
1425            return Some(DefinitionType::Enum);
1426        }
1427
1428        // Check for type alias (but not inside a function)
1429        if trimmed.contains("type ") && !trimmed.contains("fn ") {
1430            return Some(DefinitionType::TypeAlias);
1431        }
1432
1433        // Check for function definitions (without #[venus::cell])
1434        if trimmed.contains("fn ") && !trimmed.contains("#[venus::cell]") {
1435            return Some(DefinitionType::HelperFunction);
1436        }
1437
1438        None
1439    }
1440
1441    /// Validate that the declared definition type matches the content.
1442    ///
1443    /// Provides a warning if there's a mismatch, but doesn't fail the operation
1444    /// since the actual parsing will catch any real errors during universe build.
1445    ///
1446    /// Returns `Ok(())` if valid or cannot be validated, `Err` only for clear mismatches.
1447    fn validate_definition_type(
1448        content: &str,
1449        declared_type: venus_core::graph::DefinitionType,
1450    ) -> ServerResult<()> {
1451        if let Some(inferred_type) = Self::infer_definition_type(content)
1452            && std::mem::discriminant(&inferred_type) != std::mem::discriminant(&declared_type) {
1453                tracing::warn!(
1454                    "Definition type mismatch: declared {:?} but content suggests {:?}",
1455                    declared_type,
1456                    inferred_type
1457                );
1458                // For now, just warn - don't fail the operation
1459                // The universe build will catch actual syntax errors
1460            }
1461        Ok(())
1462    }
1463
1464    /// Insert a new definition cell.
1465    ///
1466    /// Modifies the .rs source file and reloads the notebook.
1467    /// Returns the ID of the newly inserted definition cell.
1468    ///
1469    /// Validates that the definition type matches the content before insertion.
1470    pub fn insert_definition_cell(
1471        &mut self,
1472        content: String,
1473        definition_type: venus_core::graph::DefinitionType,
1474        after_cell_id: Option<CellId>,
1475    ) -> ServerResult<CellId> {
1476        // Validate definition type matches content
1477        Self::validate_definition_type(&content, definition_type)?;
1478        // Convert cell ID to line number if provided
1479        let after_line = after_cell_id.and_then(|id| {
1480            // Try to find in code cells
1481            self.cells.iter().find(|c| c.id == id)
1482                .map(|c| c.span.end_line)
1483                .or_else(|| {
1484                    // Try to find in markdown cells
1485                    self.markdown_cells.iter().find(|m| m.id == id)
1486                        .map(|m| m.span.end_line)
1487                })
1488                .or_else(|| {
1489                    // Try to find in definition cells
1490                    self.definition_cells.iter().find(|d| d.id == id)
1491                        .map(|d| d.span.end_line)
1492                })
1493        });
1494
1495        // Use insert_raw_code which writes raw Rust code without // prefix
1496        let mut editor = SourceEditor::load(&self.path)?;
1497        editor.insert_raw_code(&content, after_line)?;
1498
1499        let start_line = after_line.map(|l| l + 1).unwrap_or(0);
1500        let line_count = content.lines().count();
1501        let end_line = start_line + line_count;
1502
1503        editor.save()?;
1504
1505        // Record for undo
1506        self.undo_manager.record(UndoableOperation::InsertDefinitionCell {
1507            start_line,
1508            end_line,
1509            content: content.clone(),
1510            definition_type,
1511        });
1512
1513        // Reload to update in-memory state
1514        self.reload()?;
1515
1516        // Find the newly inserted definition cell (it should be at the expected line)
1517        let new_cell_id = self.definition_cells
1518            .iter()
1519            .find(|d| d.span.start_line >= start_line && d.span.start_line <= end_line)
1520            .map(|d| d.id)
1521            .ok_or_else(|| ServerError::InvalidOperation("Failed to find inserted definition cell".to_string()))?;
1522
1523        Ok(new_cell_id)
1524    }
1525
1526    /// Edit a definition cell's content.
1527    ///
1528    /// Modifies the .rs source file and reloads the notebook.
1529    /// Returns a list of cells that are now dirty due to the definition change.
1530    pub fn edit_definition_cell(&mut self, cell_id: CellId, new_content: String) -> ServerResult<Vec<CellId>> {
1531        // Find the definition cell
1532        let def_cell = self.definition_cells
1533            .iter()
1534            .find(|d| d.id == cell_id)
1535            .ok_or_else(|| ServerError::CellNotFound(cell_id))?;
1536
1537        let start_line = def_cell.span.start_line;
1538        let end_line = def_cell.span.end_line;
1539        let old_content = def_cell.content.clone();
1540
1541        // Load and edit the source file
1542        let mut editor = SourceEditor::load(&self.path)?;
1543        // Use edit_raw_code which edits raw Rust code without // prefix
1544        editor.edit_raw_code(start_line, end_line, &new_content)?;
1545        editor.save()?;
1546
1547        // Record for undo
1548        self.undo_manager.record(UndoableOperation::EditDefinitionCell {
1549            cell_id,
1550            start_line,
1551            end_line,
1552            old_content,
1553            new_content: new_content.clone(),
1554        });
1555
1556        // Reload to update in-memory state (rebuilds universe with new definitions)
1557        self.reload()?;
1558
1559        // Mark ALL executable cells as dirty (only if they have output - pristine cells stay pristine)
1560        let dirty_cells: Vec<CellId> = self.cells.iter()
1561            .filter(|c| self.cell_outputs.contains_key(&c.id))  // Only cells with output
1562            .map(|c| c.id)
1563            .collect();
1564        for &cell_id in &dirty_cells {
1565            if let Some(state) = self.cell_states.get_mut(&cell_id) {
1566                state.set_dirty(true);
1567            }
1568        }
1569
1570        Ok(dirty_cells)
1571    }
1572
1573    /// Delete a definition cell.
1574    ///
1575    /// Modifies the .rs source file and reloads the notebook.
1576    pub fn delete_definition_cell(&mut self, cell_id: CellId) -> ServerResult<()> {
1577        // Find the definition cell
1578        let def_cell = self.definition_cells
1579            .iter()
1580            .find(|d| d.id == cell_id)
1581            .ok_or_else(|| ServerError::CellNotFound(cell_id))?;
1582
1583        let start_line = def_cell.span.start_line;
1584        let end_line = def_cell.span.end_line;
1585        let content = def_cell.content.clone();
1586        let definition_type = def_cell.definition_type;
1587
1588        // Load and edit the source file
1589        let mut editor = SourceEditor::load(&self.path)?;
1590        editor.delete_markdown_cell(start_line, end_line)?;
1591        editor.save()?;
1592
1593        // Record for undo
1594        self.undo_manager.record(UndoableOperation::DeleteDefinitionCell {
1595            start_line,
1596            end_line,
1597            content,
1598            definition_type,
1599        });
1600
1601        // Reload to update in-memory state
1602        self.reload()?;
1603
1604        Ok(())
1605    }
1606
1607    /// Move a definition cell up or down.
1608    ///
1609    /// Modifies the .rs source file and reloads the notebook.
1610    pub fn move_definition_cell(&mut self, cell_id: CellId, direction: MoveDirection) -> ServerResult<()> {
1611        // Find the definition cell
1612        let def_cell = self.definition_cells
1613            .iter()
1614            .find(|d| d.id == cell_id)
1615            .ok_or_else(|| ServerError::CellNotFound(cell_id))?;
1616
1617        let start_line = def_cell.span.start_line;
1618        let end_line = def_cell.span.end_line;
1619
1620        // Load and edit the source file
1621        let mut editor = SourceEditor::load(&self.path)?;
1622        editor.move_markdown_cell(start_line, end_line, direction)?;
1623        editor.save()?;
1624
1625        // Record for undo
1626        self.undo_manager.record(UndoableOperation::MoveDefinitionCell {
1627            start_line,
1628            end_line,
1629            direction,
1630        });
1631
1632        // Reload to update in-memory state
1633        self.reload()?;
1634
1635        Ok(())
1636    }
1637
1638    /// Undo the last cell management operation.
1639    ///
1640    /// Returns a description of what was undone, or an error if undo failed.
1641    pub fn undo(&mut self) -> ServerResult<String> {
1642        let operation = self.undo_manager.pop_undo()
1643            .ok_or_else(|| ServerError::InvalidOperation("Nothing to undo".to_string()))?;
1644
1645        let description = operation.undo_description();
1646
1647        // Execute the reverse operation
1648        let mut editor = SourceEditor::load(&self.path)?;
1649
1650        match &operation {
1651            UndoableOperation::InsertCell { cell_name, .. } => {
1652                // Undo insert = delete
1653                editor.delete_cell(cell_name)?;
1654            }
1655            UndoableOperation::DeleteCell { source, after_cell_name, .. } => {
1656                // Undo delete = restore
1657                editor.restore_cell(source, after_cell_name.as_deref())?;
1658            }
1659            UndoableOperation::DuplicateCell { new_cell_name, .. } => {
1660                // Undo duplicate = delete the new cell
1661                editor.delete_cell(new_cell_name)?;
1662            }
1663            UndoableOperation::MoveCell { cell_name, direction } => {
1664                // Undo move = move in opposite direction
1665                let reverse_direction = match direction {
1666                    MoveDirection::Up => MoveDirection::Down,
1667                    MoveDirection::Down => MoveDirection::Up,
1668                };
1669                editor.move_cell(cell_name, reverse_direction)?;
1670            }
1671            UndoableOperation::RenameCell { cell_name, old_display_name, .. } => {
1672                // Undo rename = restore old display name
1673                editor.rename_cell(cell_name, old_display_name)?;
1674            }
1675            UndoableOperation::EditCell { start_line, end_line, old_source, .. } => {
1676                // Undo edit = restore old source
1677                editor.edit_raw_code(*start_line, *end_line, old_source)?;
1678            }
1679            UndoableOperation::InsertMarkdownCell { start_line, end_line, .. } => {
1680                // Undo insert markdown = delete it
1681                editor.delete_markdown_cell(*start_line, *end_line)?;
1682            }
1683            UndoableOperation::EditMarkdownCell { start_line, end_line, old_content, is_module_doc, .. } => {
1684                // Undo edit markdown = restore old content
1685                editor.edit_markdown_cell(*start_line, *end_line, old_content, *is_module_doc)?;
1686            }
1687            UndoableOperation::DeleteMarkdownCell { start_line, content } => {
1688                // Undo delete markdown = restore it
1689                let after_line = if *start_line > 0 { Some(start_line - 1) } else { None };
1690                editor.insert_markdown_cell(content, after_line)?;
1691            }
1692            UndoableOperation::MoveMarkdownCell { start_line, end_line, direction } => {
1693                // Undo move markdown = move in opposite direction
1694                let reverse_direction = match direction {
1695                    MoveDirection::Up => MoveDirection::Down,
1696                    MoveDirection::Down => MoveDirection::Up,
1697                };
1698                editor.move_markdown_cell(*start_line, *end_line, reverse_direction)?;
1699            }
1700            UndoableOperation::InsertDefinitionCell { start_line, end_line, .. } => {
1701                // Undo insert definition = delete it
1702                editor.delete_markdown_cell(*start_line, *end_line)?;
1703            }
1704            UndoableOperation::EditDefinitionCell { start_line, end_line, old_content, .. } => {
1705                // Undo edit definition = restore old content
1706                editor.edit_markdown_cell(*start_line, *end_line, old_content, false)?;
1707            }
1708            UndoableOperation::DeleteDefinitionCell { start_line, content, .. } => {
1709                // Undo delete definition = restore it
1710                let after_line = if *start_line > 0 { Some(start_line - 1) } else { None };
1711                editor.insert_markdown_cell(content, after_line)?;
1712            }
1713            UndoableOperation::MoveDefinitionCell { start_line, end_line, direction } => {
1714                // Undo move definition = move in opposite direction
1715                let reverse_direction = match direction {
1716                    MoveDirection::Up => MoveDirection::Down,
1717                    MoveDirection::Down => MoveDirection::Up,
1718                };
1719                editor.move_markdown_cell(*start_line, *end_line, reverse_direction)?;
1720            }
1721        }
1722
1723        editor.save()?;
1724
1725        // Record for redo
1726        self.undo_manager.record_redo(operation);
1727
1728        // Reload to update in-memory state
1729        self.reload()?;
1730
1731        Ok(description)
1732    }
1733
1734    /// Redo the last undone operation.
1735    ///
1736    /// Returns a description of what was redone, or an error if redo failed.
1737    pub fn redo(&mut self) -> ServerResult<String> {
1738        let operation = self.undo_manager.pop_redo()
1739            .ok_or_else(|| ServerError::InvalidOperation("Nothing to redo".to_string()))?;
1740
1741        let description = operation.description();
1742
1743        // Execute the original operation
1744        let mut editor = SourceEditor::load(&self.path)?;
1745
1746        match &operation {
1747            UndoableOperation::InsertCell { after_cell_name, .. } => {
1748                // Re-insert at the original position
1749                let _ = editor.insert_cell(after_cell_name.as_deref())?;
1750            }
1751            UndoableOperation::DeleteCell { cell_name, .. } => {
1752                // Redo delete = delete again
1753                editor.delete_cell(cell_name)?;
1754            }
1755            UndoableOperation::DuplicateCell { original_cell_name, .. } => {
1756                // Redo duplicate = duplicate again (new name will be generated)
1757                let _ = editor.duplicate_cell(original_cell_name)?;
1758            }
1759            UndoableOperation::MoveCell { cell_name, direction } => {
1760                // Redo move = move in same direction
1761                editor.move_cell(cell_name, *direction)?;
1762            }
1763            UndoableOperation::RenameCell { cell_name, new_display_name, .. } => {
1764                // Redo rename = apply new display name again
1765                editor.rename_cell(cell_name, new_display_name)?;
1766            }
1767            UndoableOperation::EditCell { start_line, end_line, new_source, .. } => {
1768                // Redo edit = apply new source again
1769                editor.edit_raw_code(*start_line, *end_line, new_source)?;
1770            }
1771            UndoableOperation::InsertMarkdownCell { start_line, content, .. } => {
1772                // Redo insert markdown = insert again at original position
1773                let after_line = if *start_line > 0 { Some(start_line - 1) } else { None };
1774                editor.insert_markdown_cell(content, after_line)?;
1775            }
1776            UndoableOperation::EditMarkdownCell { start_line, end_line, new_content, is_module_doc, .. } => {
1777                // Redo edit markdown = apply new content again
1778                editor.edit_markdown_cell(*start_line, *end_line, new_content, *is_module_doc)?;
1779            }
1780            UndoableOperation::DeleteMarkdownCell { start_line, content } => {
1781                // Redo delete markdown = delete again
1782                // We need to find the end line by counting content lines
1783                let line_count = content.lines().count();
1784                let end_line = start_line + line_count;
1785                editor.delete_markdown_cell(*start_line, end_line)?;
1786            }
1787            UndoableOperation::MoveMarkdownCell { start_line, end_line, direction } => {
1788                // Redo move markdown = move in same direction
1789                editor.move_markdown_cell(*start_line, *end_line, *direction)?;
1790            }
1791            UndoableOperation::InsertDefinitionCell { start_line, content, .. } => {
1792                // Redo insert definition = insert again at original position
1793                let after_line = if *start_line > 0 { Some(start_line - 1) } else { None };
1794                editor.insert_markdown_cell(content, after_line)?;
1795            }
1796            UndoableOperation::EditDefinitionCell { start_line, end_line, new_content, .. } => {
1797                // Redo edit definition = apply new content again
1798                editor.edit_markdown_cell(*start_line, *end_line, new_content, false)?;
1799            }
1800            UndoableOperation::DeleteDefinitionCell { start_line, content, .. } => {
1801                // Redo delete definition = delete again
1802                let line_count = content.lines().count();
1803                let end_line = start_line + line_count;
1804                editor.delete_markdown_cell(*start_line, end_line)?;
1805            }
1806            UndoableOperation::MoveDefinitionCell { start_line, end_line, direction } => {
1807                // Redo move definition = move in same direction
1808                editor.move_markdown_cell(*start_line, *end_line, *direction)?;
1809            }
1810        }
1811
1812        editor.save()?;
1813
1814        // Record for undo (so we can undo the redo)
1815        self.undo_manager.record(operation);
1816
1817        // Reload to update in-memory state
1818        self.reload()?;
1819
1820        Ok(description)
1821    }
1822
1823    /// Get the current undo/redo state.
1824    pub fn get_undo_redo_state(&self) -> ServerMessage {
1825        ServerMessage::UndoRedoState {
1826            can_undo: self.undo_manager.can_undo(),
1827            can_redo: self.undo_manager.can_redo(),
1828            undo_description: self.undo_manager.undo_description(),
1829            redo_description: self.undo_manager.redo_description(),
1830        }
1831    }
1832
1833    /// Clear undo/redo history.
1834    ///
1835    /// Called when the file is externally modified.
1836    pub fn clear_undo_history(&mut self) {
1837        self.undo_manager.clear();
1838    }
1839}
1840
1841#[cfg(test)]
1842mod tests {
1843    use super::*;
1844
1845    #[test]
1846    fn test_session_creation() {
1847        // This would require a real notebook file, so we just test the types compile
1848        let (tx, _rx) = broadcast::channel::<ServerMessage>(16);
1849        drop(tx);
1850    }
1851}