pim

Unnamed repository; edit this file 'description' to name the repository.
Log | Files | Refs | README

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 }