buffer.rs (5971B)
1 use std::collections::HashMap; 2 use std::fs::{self, File, OpenOptions}; 3 use std::io::{self, BufRead, BufReader, Seek, SeekFrom, Write}; 4 use std::path::PathBuf; 5 6 pub struct Buffer { 7 file_path: PathBuf, 8 swap_path: PathBuf, 9 line_offsets: Vec<u64>, 10 deltas: HashMap<usize, String>, 11 modified: bool, 12 } 13 14 impl Buffer { 15 pub fn new_empty(path: PathBuf) -> Self { 16 let swap_path = path.with_extension(format!( 17 "{}.swp", 18 path.extension().and_then(|e| e.to_str()).unwrap_or("txt") 19 )); 20 Self { 21 file_path: path, 22 swap_path, 23 line_offsets: vec![0], 24 deltas: HashMap::new(), 25 modified: false, 26 } 27 } 28 29 pub fn from_path(path: PathBuf, should_recover: bool) -> io::Result<Self> { 30 let file = File::open(&path)?; 31 let mut reader = BufReader::new(file); 32 let mut line_offsets = vec![0]; 33 let mut offset = 0; 34 35 let mut buf = Vec::new(); 36 while reader.read_until(b'\n', &mut buf)? > 0 { 37 offset += buf.len() as u64; 38 line_offsets.push(offset); 39 buf.clear(); 40 } 41 42 let swap_path = path.with_extension(format!( 43 "{}.swp", 44 path.extension().and_then(|e| e.to_str()).unwrap_or("txt") 45 )); 46 47 let mut deltas = HashMap::new(); 48 let mut modified = false; 49 if swap_path.exists() && should_recover { 50 let swap_file = File::open(&swap_path)?; 51 let swap_reader = BufReader::new(swap_file); 52 for line in swap_reader.lines() { 53 let line = line?; 54 if line == "SAVE" { 55 modified = false; 56 continue; 57 } 58 if let Some((idx_str, content)) = line.split_once(':') 59 && let Ok(idx) = idx_str.parse::<usize>() 60 { 61 deltas.insert(idx, content.to_string()); 62 modified = true; 63 } 64 } 65 } else if swap_path.exists() { 66 fs::remove_file(&swap_path)?; 67 } 68 69 Ok(Self { 70 file_path: path, 71 swap_path, 72 line_offsets, 73 deltas, 74 modified, 75 }) 76 } 77 78 pub fn read_line(&self, index: usize) -> io::Result<String> { 79 if let Some(delta) = self.deltas.get(&index) { 80 return Ok(delta.clone()); 81 } 82 83 if index >= self.line_offsets.len().saturating_sub(1) { 84 return Ok(String::new()); 85 } 86 87 let mut file = File::open(&self.file_path)?; 88 file.seek(SeekFrom::Start(self.line_offsets[index]))?; 89 90 let length = self.line_offsets[index + 1] - self.line_offsets[index]; 91 let mut buf = vec![0; length as usize]; 92 io::Read::read_exact(&mut file, &mut buf)?; 93 94 Ok(String::from_utf8_lossy(&buf).to_string()) 95 } 96 97 pub fn update_line(&mut self, index: usize, content: String) { 98 self.deltas.insert(index, content); 99 self.modified = true; 100 } 101 102 pub fn sync_to_swap(&self) -> io::Result<()> { 103 let mut swap_file = OpenOptions::new() 104 .create(true) 105 .append(true) 106 .open(&self.swap_path)?; 107 108 for (idx, content) in &self.deltas { 109 writeln!(swap_file, "{}:{}", idx, content.replace(['\n', '\r'], ""))?; 110 } 111 Ok(()) 112 } 113 114 pub fn save(&mut self) -> io::Result<()> { 115 let temp_path = self.file_path.with_extension("tmp"); 116 let mut temp_file = File::create(&temp_path)?; 117 118 for i in 0..self.total_lines().max(1) { 119 let line = self.read_line(i)?; 120 let content = line.replace(['\n', '\r'], ""); 121 #[allow(clippy::write_with_newline)] 122 write!(temp_file, "{}\n", content)?; 123 } 124 125 fs::rename(temp_path, &self.file_path)?; 126 127 let mut swap_file = OpenOptions::new() 128 .create(true) 129 .append(true) 130 .open(&self.swap_path)?; 131 writeln!(swap_file, "SAVE")?; 132 133 self.modified = false; 134 Ok(()) 135 } 136 137 pub fn rollback(&mut self) -> io::Result<()> { 138 if !self.swap_path.exists() { 139 return Ok(()); 140 } 141 142 let swap_file = File::open(&self.swap_path)?; 143 let lines: Vec<String> = BufReader::new(swap_file) 144 .lines() 145 .collect::<Result<_, _>>()?; 146 147 let last_save_idx = lines.iter().rposition(|l| l == "SAVE"); 148 let target_idx = if let Some(idx) = last_save_idx { 149 lines[..idx].iter().rposition(|l| l == "SAVE") 150 } else { 151 None 152 }; 153 154 let mut new_deltas = HashMap::new(); 155 let limit = target_idx.map(|i| i + 1).unwrap_or(0); 156 157 for line in &lines[..limit] { 158 if line == "SAVE" { 159 continue; 160 } 161 if let Some((idx_str, content)) = line.split_once(':') 162 && let Ok(idx) = idx_str.parse::<usize>() 163 { 164 new_deltas.insert(idx, content.to_string()); 165 } 166 } 167 168 let mut swap_file = File::create(&self.swap_path)?; 169 for line in &lines[..limit] { 170 writeln!(swap_file, "{}", line)?; 171 } 172 173 self.deltas = new_deltas; 174 self.modified = false; 175 Ok(()) 176 } 177 178 pub fn is_modified(&self) -> bool { 179 self.modified 180 } 181 182 pub fn total_lines(&self) -> usize { 183 self.line_offsets 184 .len() 185 .saturating_sub(1) 186 .max(self.deltas.keys().map(|&k| k + 1).max().unwrap_or(0)) 187 } 188 189 pub fn remove_swap_file(&self) -> io::Result<()> { 190 if self.swap_path.exists() { 191 fs::remove_file(&self.swap_path)?; 192 } 193 Ok(()) 194 } 195 }