@@ -95,6 +95,37 @@ impl BufferEntry { |
| 95 | 95 | } |
| 96 | 96 | } |
| 97 | 97 | |
| 98 | + /// Create an empty buffer for a new file that doesn't exist yet |
| 99 | + pub fn new_file(path: &Path, workspace_root: &Path) -> Self { |
| 100 | + let buffer = Buffer::new(); |
| 101 | + let is_orphan = !path.starts_with(workspace_root); |
| 102 | + |
| 103 | + // Store relative path for workspace files, absolute for orphans |
| 104 | + let stored_path = if is_orphan { |
| 105 | + path.to_path_buf() |
| 106 | + } else { |
| 107 | + path.strip_prefix(workspace_root) |
| 108 | + .unwrap_or(path) |
| 109 | + .to_path_buf() |
| 110 | + }; |
| 111 | + |
| 112 | + // Detect language for syntax highlighting |
| 113 | + let mut highlighter = Highlighter::new(); |
| 114 | + if let Some(filename) = path.file_name().and_then(|n| n.to_str()) { |
| 115 | + highlighter.detect_language(filename); |
| 116 | + } |
| 117 | + |
| 118 | + Self { |
| 119 | + path: Some(stored_path), |
| 120 | + buffer, |
| 121 | + history: History::new(), |
| 122 | + highlighter, |
| 123 | + is_orphan, |
| 124 | + saved_hash: None, // Not saved yet - will prompt on close |
| 125 | + saved_len: None, |
| 126 | + } |
| 127 | + } |
| 128 | + |
| 98 | 129 | pub fn from_file(path: &Path, workspace_root: &Path) -> Result<Self> { |
| 99 | 130 | let mut buffer = Buffer::load(path)?; |
| 100 | 131 | let saved_hash = Some(buffer.content_hash()); // Hash at load time |
@@ -237,6 +268,16 @@ impl Tab { |
| 237 | 268 | }) |
| 238 | 269 | } |
| 239 | 270 | |
| 271 | + /// Create a tab for a new file that doesn't exist yet |
| 272 | + pub fn new_file(path: &Path, workspace_root: &Path) -> Self { |
| 273 | + let buffer_entry = BufferEntry::new_file(path, workspace_root); |
| 274 | + Self { |
| 275 | + buffers: vec![buffer_entry], |
| 276 | + panes: vec![Pane::new()], |
| 277 | + active_pane: 0, |
| 278 | + } |
| 279 | + } |
| 280 | + |
| 240 | 281 | /// Create a tab from string content (for diff views, etc.) |
| 241 | 282 | pub fn from_content(content: &str, display_name: &str) -> Self { |
| 242 | 283 | let buffer_entry = BufferEntry::from_content(content, Some(display_name)); |
@@ -627,9 +668,12 @@ impl Workspace { |
| 627 | 668 | |
| 628 | 669 | let mut workspace = Self::open(root)?; |
| 629 | 670 | |
| 630 | | - // Open the file in a tab |
| 671 | + // Open the file in a tab (or create new file if it doesn't exist) |
| 631 | 672 | if abs_path.exists() { |
| 632 | 673 | workspace.open_file(&abs_path)?; |
| 674 | + } else if abs_path.extension().is_some() || abs_path.file_name().is_some() { |
| 675 | + // Path looks like a file (has extension or filename) - create new file buffer |
| 676 | + workspace.open_new_file(&abs_path)?; |
| 633 | 677 | } |
| 634 | 678 | |
| 635 | 679 | Ok(workspace) |
@@ -707,6 +751,14 @@ impl Workspace { |
| 707 | 751 | Ok(()) |
| 708 | 752 | } |
| 709 | 753 | |
| 754 | + /// Open a new file (doesn't exist yet) in a new tab |
| 755 | + pub fn open_new_file(&mut self, path: &Path) -> Result<()> { |
| 756 | + let tab = Tab::new_file(path, &self.root); |
| 757 | + self.tabs.push(tab); |
| 758 | + self.active_tab = self.tabs.len() - 1; |
| 759 | + Ok(()) |
| 760 | + } |
| 761 | + |
| 710 | 762 | /// Open a file in a vertical split pane in the current tab |
| 711 | 763 | pub fn open_file_in_vsplit(&mut self, path: &Path) -> Result<()> { |
| 712 | 764 | self.tabs[self.active_tab].split_vertical_with_file(path, &self.root) |