Skip to main content

soma_core/cli/
editor.rs

1// GPT-4: High-performance inline editor for CLI edit modification with enhanced interface
2use crate::edit_control::{EditModification, ModifiableEdit, EditModificationInterface, ViewMode, InterfaceState};
3use crossterm::{
4    cursor,
5    event::{self, Event, KeyCode, KeyEvent, KeyModifiers},
6    execute,
7    style::{Color, ResetColor, SetForegroundColor},
8    terminal::{self, ClearType},
9};
10use std::io::{self, stdout, Write};
11
12/// Enhanced InlineEditor with EditModificationInterface integration
13pub struct InlineEditor {
14    interface: EditModificationInterface,
15    content: Vec<String>,
16    cursor_row: usize,
17    cursor_col: usize,
18    scroll_offset: usize,
19    modified: bool,
20    show_help: bool,
21    show_history: bool,
22}
23
24impl InlineEditor {
25    /// Create new enhanced editor with EditModificationInterface
26    pub fn new(modifiable_edit: ModifiableEdit) -> Self {
27        let interface = EditModificationInterface::new(modifiable_edit.clone());
28        let content: Vec<String> = modifiable_edit
29            .base_edit
30            .new_code
31            .lines()
32            .map(|s| s.to_string())
33            .collect();
34
35        Self {
36            interface,
37            content,
38            cursor_row: 0,
39            cursor_col: 0,
40            scroll_offset: 0,
41            modified: false,
42            show_help: false,
43            show_history: false,
44        }
45    }
46
47    /// Enhanced edit interface with multiple view modes
48    pub fn edit(&mut self) -> io::Result<Option<EditModificationInterface>> {
49        terminal::enable_raw_mode()?;
50
51        let result = loop {
52            self.render()?;
53
54            if let Event::Key(key_event) = event::read()? {
55                match self.handle_key(key_event)? {
56                    EditorAction::Exit => {
57                        break Ok(None);
58                    }
59                    EditorAction::Save => {
60                        break Ok(Some(self.interface.clone()));
61                    }
62                    EditorAction::Continue => {}
63                }
64            }
65        };
66
67        terminal::disable_raw_mode()?;
68        result
69    }
70
71    /// Enhanced rendering with multiple view modes and syntax highlighting
72    fn render(&self) -> io::Result<()> {
73        execute!(
74            stdout(),
75            terminal::Clear(ClearType::All),
76            cursor::MoveTo(0, 0)
77        )?;
78
79        // Header with interface state
80        self.render_header()?;
81
82        match self.interface.view_mode {
83            ViewMode::Single => self.render_single_view()?,
84            ViewMode::SideBySide => self.render_side_by_side_view()?,
85            ViewMode::Unified => self.render_unified_diff_view()?,
86            ViewMode::FullScreen => self.render_fullscreen_view()?,
87        }
88
89        // Footer with shortcuts and status
90        self.render_footer()?;
91
92        // Position cursor
93        self.position_cursor()?;
94
95        stdout().flush()
96    }
97
98    fn render_header(&self) -> io::Result<()> {
99        execute!(stdout(), SetForegroundColor(Color::Cyan))?;
100        println!("🚀 SOMA EditModificationInterface v2.0 - Issue #17");
101        execute!(stdout(), ResetColor)?;
102        
103        println!(
104            "📁 File: {} | Lines: {}-{} | State: {:?} | View: {:?}",
105            self.interface.modifiable_edit.base_edit.file,
106            self.interface.modifiable_edit.base_edit.line_range.0,
107            self.interface.modifiable_edit.base_edit.line_range.1,
108            self.interface.interface_state,
109            self.interface.view_mode
110        );
111
112        if self.show_help {
113            self.render_help_panel()?;
114        }
115
116        if self.show_history {
117            self.render_history_panel()?;
118        }
119
120        println!(); // Separator
121        Ok(())
122    }
123
124    fn render_help_panel(&self) -> io::Result<()> {
125        execute!(stdout(), SetForegroundColor(Color::Yellow))?;
126        println!("┌─ Help Panel ─────────────────────────────────────────────────────────┐");
127        println!("│ Ctrl+S: Save   Ctrl+Q: Quit   Ctrl+Z: Undo   Ctrl+Y: Redo            │");
128        println!("│ Ctrl+H: Help   Ctrl+R: History   Ctrl+V: View Mode   Ctrl+T: Syntax   │");
129        println!("│ Ctrl+C: Compare   Ctrl+P: Preview   Arrow Keys: Navigate              │");
130        println!("└──────────────────────────────────────────────────────────────────────┘");
131        execute!(stdout(), ResetColor)?;
132        Ok(())
133    }
134
135    fn render_history_panel(&self) -> io::Result<()> {
136        execute!(stdout(), SetForegroundColor(Color::Green))?;
137        println!("┌─ Modification History ───────────────────────────────────────────────┐");
138        
139        let history = self.interface.get_history_summary();
140        for (_i, entry) in history.iter().take(5).enumerate() {
141            println!("│ {:<68} │", entry);
142        }
143        
144        if history.len() > 5 {
145            println!("│ ... and {} more entries                                          │", history.len() - 5);
146        }
147        
148        println!("└──────────────────────────────────────────────────────────────────────┘");
149        execute!(stdout(), ResetColor)?;
150        Ok(())
151    }
152
153    fn render_single_view(&self) -> io::Result<()> {
154        let visible_lines = if self.show_help || self.show_history { 15 } else { 20 };
155        let start = self.scroll_offset;
156        let end = (start + visible_lines).min(self.content.len());
157
158        for (idx, line) in self.content[start..end].iter().enumerate() {
159            let line_num = start + idx + 1;
160            let marker = if start + idx == self.cursor_row { ">" } else { " " };
161
162            if self.interface.syntax_highlighting {
163                self.render_syntax_highlighted_line(line_num, marker, line)?;
164            } else {
165                println!("{:3} {} {}", line_num, marker, line);
166            }
167        }
168
169        Ok(())
170    }
171
172    fn render_side_by_side_view(&self) -> io::Result<()> {
173        let original_lines: Vec<&str> = self.interface.modifiable_edit.base_edit.new_code.lines().collect();
174        let current_lines = &self.content;
175        
176        execute!(stdout(), SetForegroundColor(Color::Blue))?;
177        println!("┌─ Original ──────────────────────┬─ Modified ──────────────────────┐");
178        execute!(stdout(), ResetColor)?;
179
180        let max_lines = original_lines.len().max(current_lines.len());
181        let visible_lines = 15;
182        let start = self.scroll_offset;
183        let end = (start + visible_lines).min(max_lines);
184
185        for i in start..end {
186            let orig = original_lines.get(i).unwrap_or(&"");
187            let curr = current_lines.get(i).map(|s| s.as_str()).unwrap_or("");
188            
189            let marker = if i == self.cursor_row { ">" } else { " " };
190            
191            if *orig != curr {
192                execute!(stdout(), SetForegroundColor(Color::Red))?;
193            }
194            
195            println!("│{:3}{} {:<26}│{:3}{} {:<26}│", 
196                     i + 1, marker, orig.chars().take(26).collect::<String>(),
197                     i + 1, marker, curr.chars().take(26).collect::<String>());
198            
199            execute!(stdout(), ResetColor)?;
200        }
201
202        println!("└─────────────────────────────────┴─────────────────────────────────┘");
203        Ok(())
204    }
205
206    fn render_unified_diff_view(&self) -> io::Result<()> {
207        execute!(stdout(), SetForegroundColor(Color::Magenta))?;
208        println!("┌─ Unified Diff View ──────────────────────────────────────────────────┐");
209        execute!(stdout(), ResetColor)?;
210
211        let diff_summary = self.interface.get_diff_summary();
212        execute!(stdout(), SetForegroundColor(Color::Green))?;
213        println!("│ +{} lines added, -{} removed, ~{} modified                       │", 
214                 diff_summary.lines_added, diff_summary.lines_removed, diff_summary.lines_modified);
215        execute!(stdout(), ResetColor)?;
216
217        // Simple diff display (could be enhanced with proper diff algorithm)
218        let original_lines: Vec<&str> = self.interface.modifiable_edit.base_edit.new_code.lines().collect();
219        
220        for (i, line) in self.content.iter().enumerate() {
221            let orig = original_lines.get(i).unwrap_or(&"");
222            
223            if line != orig {
224                execute!(stdout(), SetForegroundColor(Color::Red))?;
225                println!("│ -{:<66} │", orig.chars().take(66).collect::<String>());
226                execute!(stdout(), SetForegroundColor(Color::Green))?;
227                println!("│ +{:<66} │", line.chars().take(66).collect::<String>());
228                execute!(stdout(), ResetColor)?;
229            } else {
230                println!("│  {:<66} │", line.chars().take(66).collect::<String>());
231            }
232        }
233
234        println!("└──────────────────────────────────────────────────────────────────────┘");
235        Ok(())
236    }
237
238    fn render_fullscreen_view(&self) -> io::Result<()> {
239        // Full terminal height for content
240        let visible_lines = 25;
241        let start = self.scroll_offset;
242        let end = (start + visible_lines).min(self.content.len());
243
244        for (idx, line) in self.content[start..end].iter().enumerate() {
245            let line_num = start + idx + 1;
246            let marker = if start + idx == self.cursor_row { ">" } else { " " };
247            println!("{:4} {} {}", line_num, marker, line);
248        }
249
250        Ok(())
251    }
252
253    fn render_syntax_highlighted_line(&self, line_num: usize, marker: &str, line: &str) -> io::Result<()> {
254        print!("{:3} {} ", line_num, marker);
255        
256        // Simple syntax highlighting for Rust
257        let mut in_string = false;
258        let mut in_comment = false;
259        
260        let mut chars = line.chars().peekable();
261        while let Some(ch) = chars.next() {
262            if in_comment {
263                execute!(stdout(), SetForegroundColor(Color::DarkGreen))?;
264                print!("{}", ch);
265            } else if in_string {
266                execute!(stdout(), SetForegroundColor(Color::Yellow))?;
267                print!("{}", ch);
268                if ch == '"' && chars.peek() != Some(&'\\') {
269                    in_string = false;
270                    execute!(stdout(), ResetColor)?;
271                }
272            } else {
273                match ch {
274                    '/' if chars.peek() == Some(&'/') => {
275                        in_comment = true;
276                        execute!(stdout(), SetForegroundColor(Color::DarkGreen))?;
277                        print!("{}", ch);
278                    }
279                    '"' => {
280                        in_string = true;
281                        execute!(stdout(), SetForegroundColor(Color::Yellow))?;
282                        print!("{}", ch);
283                    }
284                    'f' if line.starts_with("fn ") => {
285                        execute!(stdout(), SetForegroundColor(Color::Blue))?;
286                        print!("{}", ch);
287                    }
288                    _ => {
289                        execute!(stdout(), ResetColor)?;
290                        print!("{}", ch);
291                    }
292                }
293            }
294        }
295        
296        execute!(stdout(), ResetColor)?;
297        println!();
298        Ok(())
299    }
300
301    fn render_footer(&self) -> io::Result<()> {
302        let diff_summary = self.interface.get_diff_summary();
303        
304        execute!(stdout(), SetForegroundColor(Color::Cyan))?;
305        println!("─────────────────────────────────────────────────────────────────────────");
306        execute!(stdout(), ResetColor)?;
307        
308        println!(
309            "Cursor: {}:{} | Modified: {} | Changes: {} | Undo: {} | Redo: {} | Syntax: {}",
310            self.cursor_row + 1,
311            self.cursor_col + 1,
312            if self.modified { "YES" } else { "NO" },
313            diff_summary.total_changes,
314            if self.interface.can_undo() { "YES" } else { "NO" },
315            if self.interface.can_redo() { "YES" } else { "NO" },
316            if self.interface.syntax_highlighting { "ON" } else { "OFF" }
317        );
318
319        if let Some(desc) = self.interface.current_snapshot_description() {
320            println!("Current: {}", desc);
321        }
322
323        Ok(())
324    }
325
326    fn position_cursor(&self) -> io::Result<()> {
327        let header_offset = if self.show_help { 9 } else if self.show_history { 8 } else { 4 };
328        let display_row = header_offset + self.cursor_row - self.scroll_offset;
329        let display_col = match self.interface.view_mode {
330            ViewMode::SideBySide => self.cursor_col + 38, // Account for side-by-side layout
331            _ => self.cursor_col + 6, // Account for line numbers
332        };
333        
334        execute!(
335            stdout(),
336            cursor::MoveTo(display_col as u16, display_row as u16)
337        )?;
338        
339        Ok(())
340    }
341
342    /// Enhanced key handling with new features
343    fn handle_key(&mut self, key: KeyEvent) -> io::Result<EditorAction> {
344        match (key.code, key.modifiers) {
345            // Core actions
346            (KeyCode::Char('q'), KeyModifiers::CONTROL) => Ok(EditorAction::Exit),
347            (KeyCode::Char('s'), KeyModifiers::CONTROL) => {
348                self.save_current_changes();
349                Ok(EditorAction::Save)
350            },
351            
352            // Undo/Redo
353            (KeyCode::Char('z'), KeyModifiers::CONTROL) => {
354                if self.interface.undo() {
355                    self.update_content_from_interface();
356                }
357                Ok(EditorAction::Continue)
358            },
359            (KeyCode::Char('y'), KeyModifiers::CONTROL) => {
360                if self.interface.redo() {
361                    self.update_content_from_interface();
362                }
363                Ok(EditorAction::Continue)
364            },
365            
366            // Interface controls
367            (KeyCode::Char('h'), KeyModifiers::CONTROL) => {
368                self.show_help = !self.show_help;
369                Ok(EditorAction::Continue)
370            },
371            (KeyCode::Char('r'), KeyModifiers::CONTROL) => {
372                self.show_history = !self.show_history;
373                Ok(EditorAction::Continue)
374            },
375            (KeyCode::Char('t'), KeyModifiers::CONTROL) => {
376                self.interface.toggle_syntax_highlighting();
377                Ok(EditorAction::Continue)
378            },
379            (KeyCode::Char('v'), KeyModifiers::CONTROL) => {
380                self.cycle_view_mode();
381                Ok(EditorAction::Continue)
382            },
383            (KeyCode::Char('c'), KeyModifiers::CONTROL) => {
384                self.interface.set_state(InterfaceState::Comparing);
385                self.interface.set_view_mode(ViewMode::SideBySide);
386                Ok(EditorAction::Continue)
387            },
388            (KeyCode::Char('p'), KeyModifiers::CONTROL) => {
389                self.interface.set_state(InterfaceState::Previewing);
390                self.interface.set_view_mode(ViewMode::Unified);
391                Ok(EditorAction::Continue)
392            },
393            
394            // Navigation (unchanged)
395            (KeyCode::Up, _) => {
396                if self.cursor_row > 0 {
397                    self.cursor_row -= 1;
398                    self.adjust_cursor_bounds();
399                    self.adjust_scroll();
400                }
401                Ok(EditorAction::Continue)
402            }
403            (KeyCode::Down, _) => {
404                if self.cursor_row < self.content.len().saturating_sub(1) {
405                    self.cursor_row += 1;
406                    self.adjust_cursor_bounds();
407                    self.adjust_scroll();
408                }
409                Ok(EditorAction::Continue)
410            }
411            (KeyCode::Left, _) => {
412                if self.cursor_col > 0 {
413                    self.cursor_col -= 1;
414                } else if self.cursor_row > 0 {
415                    self.cursor_row -= 1;
416                    self.cursor_col = self
417                        .content
418                        .get(self.cursor_row)
419                        .map(|s| s.len())
420                        .unwrap_or(0);
421                    self.adjust_scroll();
422                }
423                Ok(EditorAction::Continue)
424            }
425            (KeyCode::Right, _) => {
426                let line_len = self
427                    .content
428                    .get(self.cursor_row)
429                    .map(|s| s.len())
430                    .unwrap_or(0);
431                if self.cursor_col < line_len {
432                    self.cursor_col += 1;
433                } else if self.cursor_row < self.content.len().saturating_sub(1) {
434                    self.cursor_row += 1;
435                    self.cursor_col = 0;
436                    self.adjust_scroll();
437                }
438                Ok(EditorAction::Continue)
439            }
440            (KeyCode::Home, _) => {
441                self.cursor_col = 0;
442                Ok(EditorAction::Continue)
443            }
444            (KeyCode::End, _) => {
445                self.cursor_col = self
446                    .content
447                    .get(self.cursor_row)
448                    .map(|s| s.len())
449                    .unwrap_or(0);
450                Ok(EditorAction::Continue)
451            }
452            
453            // Text editing (unchanged)
454            (KeyCode::Enter, _) => {
455                self.insert_newline();
456                Ok(EditorAction::Continue)
457            }
458            (KeyCode::Char(c), _) => {
459                self.insert_char(c);
460                Ok(EditorAction::Continue)
461            }
462            (KeyCode::Backspace, _) => {
463                self.delete_char();
464                Ok(EditorAction::Continue)
465            }
466            (KeyCode::Delete, _) => {
467                self.delete_char_forward();
468                Ok(EditorAction::Continue)
469            }
470            _ => Ok(EditorAction::Continue),
471        }
472    }
473
474    fn cycle_view_mode(&mut self) {
475        let new_mode = match self.interface.view_mode {
476            ViewMode::Single => ViewMode::SideBySide,
477            ViewMode::SideBySide => ViewMode::Unified,
478            ViewMode::Unified => ViewMode::FullScreen,
479            ViewMode::FullScreen => ViewMode::Single,
480        };
481        self.interface.set_view_mode(new_mode);
482    }
483
484    fn save_current_changes(&mut self) {
485        if self.modified {
486            let new_content = self.content.join("\n");
487            let old_content = self.interface.modifiable_edit.compute_final_code();
488            
489            if new_content != old_content {
490                self.interface.add_modification(
491                    EditModification::CodeChange {
492                        line: 0,
493                        old: old_content,
494                        new: new_content,
495                    },
496                    "Manual edit changes".to_string(),
497                );
498                self.modified = false;
499            }
500        }
501    }
502
503    fn update_content_from_interface(&mut self) {
504        let new_content = self.interface.modifiable_edit.compute_final_code();
505        self.content = new_content.lines().map(|s| s.to_string()).collect();
506        self.modified = false;
507    }
508
509    fn adjust_cursor_bounds(&mut self) {
510        if let Some(line) = self.content.get(self.cursor_row) {
511            if self.cursor_col > line.len() {
512                self.cursor_col = line.len();
513            }
514        }
515    }
516
517    fn adjust_scroll(&mut self) {
518        let visible_lines = 20;
519        if self.cursor_row < self.scroll_offset {
520            self.scroll_offset = self.cursor_row;
521        } else if self.cursor_row >= self.scroll_offset + visible_lines {
522            self.scroll_offset = self.cursor_row - visible_lines + 1;
523        }
524    }
525
526    fn insert_char(&mut self, c: char) {
527        if self.cursor_row < self.content.len() {
528            self.content[self.cursor_row].insert(self.cursor_col, c);
529            self.cursor_col += 1;
530            self.modified = true;
531        }
532    }
533
534    fn insert_newline(&mut self) {
535        if self.cursor_row < self.content.len() {
536            let current_line = self.content[self.cursor_row].clone();
537            let (left, right) = current_line.split_at(self.cursor_col);
538
539            self.content[self.cursor_row] = left.to_string();
540            self.content.insert(self.cursor_row + 1, right.to_string());
541
542            self.cursor_row += 1;
543            self.cursor_col = 0;
544            self.modified = true;
545            self.adjust_scroll();
546        }
547    }
548
549    fn delete_char(&mut self) {
550        if self.cursor_col > 0 && self.cursor_row < self.content.len() {
551            self.content[self.cursor_row].remove(self.cursor_col - 1);
552            self.cursor_col -= 1;
553            self.modified = true;
554        } else if self.cursor_row > 0 {
555            // Merge with previous line
556            let current_line = self.content.remove(self.cursor_row);
557            self.cursor_row -= 1;
558            self.cursor_col = self.content[self.cursor_row].len();
559            self.content[self.cursor_row].push_str(&current_line);
560            self.modified = true;
561            self.adjust_scroll();
562        }
563    }
564
565    fn delete_char_forward(&mut self) {
566        if self.cursor_row < self.content.len() {
567            let line_len = self.content[self.cursor_row].len();
568            if self.cursor_col < line_len {
569                self.content[self.cursor_row].remove(self.cursor_col);
570                self.modified = true;
571            } else if self.cursor_row < self.content.len() - 1 {
572                // Merge with next line
573                let next_line = self.content.remove(self.cursor_row + 1);
574                self.content[self.cursor_row].push_str(&next_line);
575                self.modified = true;
576            }
577        }
578    }
579}
580
581#[derive(Debug)]
582enum EditorAction {
583    Continue,
584    Save,
585    Exit,
586}
587
588#[cfg(test)]
589mod tests {
590    use super::*;
591    use crate::agents::gpt4_agent::ProposedEdit;
592
593    fn create_test_edit() -> ModifiableEdit {
594        let proposed = ProposedEdit {
595            file: "test.rs".to_string(),
596            line_range: (10, 15),
597            new_code: "fn test() {\n    println!(\"Hello\");\n}".to_string(),
598            reason: "Test function".to_string(),
599            confidence: 0.9,
600        };
601        ModifiableEdit::from_proposed_edit(proposed)
602    }
603
604    #[test]
605    fn test_editor_creation() {
606        let edit = create_test_edit();
607        let editor = InlineEditor::new(edit);
608
609        assert_eq!(editor.content.len(), 3);
610        assert_eq!(editor.cursor_row, 0);
611        assert_eq!(editor.cursor_col, 0);
612        assert!(!editor.modified);
613    }
614
615    #[test]
616    fn test_text_insertion() {
617        let edit = create_test_edit();
618        let mut editor = InlineEditor::new(edit);
619
620        editor.insert_char('X');
621        assert!(editor.modified);
622        assert!(editor.content[0].starts_with('X'));
623        assert_eq!(editor.cursor_col, 1);
624    }
625
626    #[test]
627    fn test_newline_insertion() {
628        let edit = create_test_edit();
629        let mut editor = InlineEditor::new(edit);
630
631        let initial_lines = editor.content.len();
632        editor.insert_newline();
633
634        assert_eq!(editor.content.len(), initial_lines + 1);
635        assert_eq!(editor.cursor_row, 1);
636        assert_eq!(editor.cursor_col, 0);
637        assert!(editor.modified);
638    }
639
640    #[test]
641    fn test_cursor_movement() {
642        let edit = create_test_edit();
643        let mut editor = InlineEditor::new(edit);
644
645        // Move right
646        editor.cursor_col = 5;
647        editor.adjust_cursor_bounds();
648
649        // Move down
650        editor.cursor_row = 1;
651        editor.adjust_cursor_bounds();
652
653        assert!(editor.cursor_col <= editor.content[editor.cursor_row].len());
654    }
655}