]> git.lizzy.rs Git - rust.git/blobdiff - src/changes.rs
Merge pull request #128 from marcusklaas/subexpr
[rust.git] / src / changes.rs
index ba2b90d185d783f7a4b83f6db7059726c695854f..4cce45e5eb3af31a4a0f4a2198f108244fc200da 100644 (file)
 // option. This file may not be copied, modified, or distributed
 // except according to those terms.
 
+
 // TODO
-// composable changes
-// print to files (maybe that shouldn't be here, but in mod)
+// print to files
 // tests
-// docs
 
-use rope::{Rope, RopeSlice};
-use std::collections::{HashMap, BTreeMap};
-use std::collections::Bound::{Included, Unbounded};
-use syntax::codemap::{CodeMap, Span, Pos};
+use strings::string_buffer::StringBuffer;
+use std::collections::HashMap;
+use syntax::codemap::{CodeMap, Span, BytePos};
 use std::fmt;
-
+use std::fs::File;
+use std::io::{Write, stdout};
+use WriteMode;
+use NewlineStyle;
+use config::Config;
+use utils::round_up_to_power_of_two;
+
+// This is basically a wrapper around a bunch of Ropes which makes it convenient
+// to work with libsyntax. It is badly named.
 pub struct ChangeSet<'a> {
-    file_map: HashMap<String, Rope>,
-    // FIXME, we only keep a codemap around so we can have convenience methods
-    // taking Spans, it would be more resuable to factor this (and the methods)
-    // out into an adaptor.
+    file_map: HashMap<String, StringBuffer>,
     codemap: &'a CodeMap,
-    pub count: u64,
-    // TODO we need to map the start and end of spans differently
-    // TODO needs to be per file
-    adjusts: BTreeMap<usize, Adjustment>,
-}
-
-// An extent over which we must adjust the position values.
-#[derive(Show, Clone, Eq, PartialEq)]
-struct Adjustment {
-    // Start is implicit, given by its position in the map.
-    end: usize,
-    delta: isize,
-}
-
-impl Adjustment {
-    fn chop_left(&self, new_end: usize) -> Adjustment {
-        Adjustment {
-            end: new_end,
-            delta: self.delta,
-        }
-    }
-
-    fn move_left(&self, mov: usize) -> Adjustment {
-        assert!(self.delta > mov);
-        Adjustment {
-            end: self.end,
-            delta: self.delta - mov,
-        }
-    }
-}
-
-pub struct FileIterator<'c, 'a: 'c> {
-    change_set: &'c ChangeSet<'a>,
-    keys: Vec<&'c String>,
-    cur_key: usize,
+    file_spans: Vec<(u32, u32)>,
 }
 
 impl<'a> ChangeSet<'a> {
+    // Create a new ChangeSet for a given libsyntax CodeMap.
     pub fn from_codemap(codemap: &'a CodeMap) -> ChangeSet<'a> {
-        let mut result = ChangeSet {
-            file_map: HashMap::new(),
-            codemap: codemap,
-            count: 0,
-            adjusts: BTreeMap::new(),
-        };
+        let mut result = ChangeSet { file_map: HashMap::new(),
+                                     codemap: codemap,
+                                     file_spans: Vec::with_capacity(codemap.files.borrow().len()), };
 
         for f in codemap.files.borrow().iter() {
-            let contents = Rope::from_string(f.src.clone());
-            result.file_map.insert(f.name.clone(), contents);
+            // Use the length of the file as a heuristic for how much space we
+            // need. Round to the next power of two.
+            let buffer_cap = round_up_to_power_of_two(f.src.as_ref().unwrap().len());
+
+            result.file_map.insert(f.name.clone(), StringBuffer::with_capacity(buffer_cap));
+            result.file_spans.push((f.start_pos.0, f.end_pos.0));
         }
 
+        result.file_spans.sort();
+
         result
     }
 
-    // start and end are unadjusted.
-    pub fn change(&mut self, file_name: &str, start: usize, end: usize, text: String) {
-        println!("change: {}:{}-{} \"{}\"", file_name, start, end, text);
+    pub fn filespans_for_span(&self, start: BytePos, end: BytePos) -> Vec<(u32, u32)> {
+        assert!(start.0 <= end.0);
 
-        let new_len = text.len();
-        self.count += 1;
+        if self.file_spans.len() == 0 {
+            return Vec::new();
+        }
 
-        let (key_start, adj_start, abs_start): (Option<usize>, Option<Adjustment>, usize) = {
-            let before_start = self.adjusts.range(Unbounded, Included(&start)).next_back();
-            match before_start {
-                Some((k, a)) if a.end > start => (Some(*k), Some(a.clone()), (start as isize + a.delta) as usize),
-                _ => (None, None, start)
-            }
+        // idx is the index into file_spans which indicates the current file, we
+        // with the file start denotes.
+        let mut idx = match self.file_spans.binary_search(&(start.0, ::std::u32::MAX)) {
+            Ok(i) => i,
+            Err(0) => 0,
+            Err(i) => i - 1,
         };
-        let (key_end, adj_end, abs_end) = {
-            let before_end = self.adjusts.range(Unbounded, Included(&end)).next_back();
-            match before_end {
-                Some((k, a)) if a.end > end => (Some(*k), Some(a.clone()), (end as isize + a.delta) as usize),
-                _ => (None, None, end)
-            }
-        };
-
-        {
-            let file = &mut self.file_map[*file_name];
-
-            println!("change: absolute values {}-{}, replaces \"{}\"",
-                   abs_start, abs_end, file.slice(abs_start..abs_end));
-
-            file.remove(abs_start, abs_end);
-            file.insert(abs_start, text);
-
-            // Record the changed locations.
-            // TODO what if there is a change to the right of end? - need to iterate over all changes to the right :-(
-            match (key_start, key_end) {
-                (None, None) => {
-                    // Factor this out?
-                    let old_len = end as isize - start as isize;
-                    let delta = new_len as isize - old_len;
-                    self.adjusts.insert(end, Adjustment { end: file.len(), delta: delta });
-                }
-                (Some(k), None) => {
-                    // Adjust the old change.
-                    self.adjusts[k] = adj_start.unwrap().chop_left(end);
-
-                    // Add the new one.
-                    let old_len = end as isize - start as isize;
-                    let delta = new_len as isize - old_len;
-                    self.adjusts.insert(end, Adjustment { end: file.len(), delta: delta });
-                }
-                (None, Some(k)) => {
-                    let old_len = end as isize - start as isize;
-                    let delta = new_len as isize - old_len;
 
-                    // Adjust the old change.
-                    // TODO only if we move left, but what if moving right?
-                    self.adjusts[abs_end] = adj_end.unwrap().move_left(TODO);
-                    self.adjusts.remove(&k);
+        let mut result = Vec::new();
+        let mut start = start.0;
+        loop {
+            let cur_file = &self.file_spans[idx];
+            idx += 1;
 
-                    // Add the new one.
-                    self.adjusts.insert(end, Adjustment { end: file.len(), delta: delta });
-                }
-                _ => {
-                    println!("{}", file);
-                    panic!();
+            if idx >= self.file_spans.len() || start >= end.0 {
+                if start < end.0 {
+                    result.push((start, end.0));
                 }
+                return result;
             }
+
+            let end = ::std::cmp::min(cur_file.1 - 1, end.0);
+            if start < end {
+                result.push((start, end));
+            }
+            start = self.file_spans[idx].0;
         }
+    }
 
-        debug_assert!(self.verify_adjustments(), "Bad change, created an overlapping adjustment");
+    pub fn push_str(&mut self, filename: &str, text: &str) {
+        let buf = self.file_map.get_mut(&*filename).unwrap();
+        buf.push_str(text)
     }
 
-    // Intended for debugging.
-    fn verify_adjustments(&self) -> bool {
-        let mut prev_end = 0;
-        let mut prev_delta = 0;
-        for (&k, a) in self.adjusts.iter() {
-            if k < prev_end {
-                debug!("Found bad adjustment at start {}, overlaps with previous adjustment", k);
-                return false;
-            }
-            if k as isize + a.delta < 0 {
-                debug!("Found bad adjustment at start {}, absolute start < 0", k);
-                return false;
-            }
-            if k as isize + a.delta < prev_end as isize + prev_delta {
-                debug!("Found bad adjustment at start {}, \
-                        projection overlaps with previous projection", k);
-                return false;
-            }
-            // TODO Check end + delta <= file.len - needs per file
+    pub fn push_str_span(&mut self, span: Span, text: &str) {
+        let file_name = self.codemap.span_to_filename(span);
+        self.push_str(&file_name, text)
+    }
 
-            prev_end = a.end;
-            prev_delta = a.delta;
+    // Fetch the output buffer for the given file name.
+    // Panics on unknown files.
+    pub fn get(&mut self, file_name: &str) -> &StringBuffer {
+        self.file_map.get(file_name).unwrap()
+    }
+
+    // Fetch a mutable reference to the output buffer for the given file name.
+    // Panics on unknown files.
+    pub fn get_mut(&mut self, file_name: &str) -> &mut StringBuffer {
+        self.file_map.get_mut(file_name).unwrap()
+    }
+
+    pub fn cur_offset(&mut self, filename: &str) -> usize {
+        self.file_map[&*filename].cur_offset()
+    }
+
+    pub fn cur_offset_span(&mut self, span: Span) -> usize {
+        let filename = self.codemap.span_to_filename(span);
+        self.cur_offset(&filename)
+    }
+
+    // Return an iterator over the entire changed text.
+    pub fn text<'c>(&'c self) -> FileIterator<'c, 'a> {
+        FileIterator { change_set: self, keys: self.file_map.keys().collect(), cur_key: 0 }
+    }
+
+    // Append a newline to the end of each file.
+    pub fn append_newlines(&mut self) {
+        for (_, s) in self.file_map.iter_mut() {
+            s.push_str("\n");
         }
-        true
     }
 
-    // span is unadjusted.
-    pub fn change_span(&mut self, span: Span, text: String) {
-        let l_loc = self.codemap.lookup_char_pos(span.lo);
-        let file_offset = l_loc.file.start_pos.0;
-        self.change(&l_loc.file.name[],
-                    (span.lo.0 - file_offset) as usize,
-                    (span.hi.0 - file_offset) as usize,
-                    text)
+    pub fn write_all_files(&self,
+                           mode: WriteMode,
+                           config: &Config)
+                           -> Result<(HashMap<String, String>), ::std::io::Error> {
+        let mut result = HashMap::new();
+        for filename in self.file_map.keys() {
+            let one_result = try!(self.write_file(filename, mode, config));
+            if let Some(r) = one_result {
+                result.insert(filename.clone(), r);
+            }
+        }
+
+        Ok(result)
     }
 
-    // start and end are unadjusted.
-    pub fn slice(&self, file_name: &str, start: usize, end: usize) -> RopeSlice {
-        // TODO refactor with change?
-        let abs_start = {
-            let before_start = self.adjusts.range(Unbounded, Included(&start)).next_back();
-            match before_start {
-                Some((k, ref a)) if a.end > start => (start as isize + a.delta) as usize,
-                _ => start
+    pub fn write_file(&self,
+                      filename: &str,
+                      mode: WriteMode,
+                      config: &Config)
+                      -> Result<Option<String>, ::std::io::Error> {
+        let text = &self.file_map[filename];
+
+        // prints all newlines either as `\n` or as `\r\n`
+        fn write_system_newlines<T>(mut writer: T,
+                                    text: &StringBuffer,
+                                    config: &Config)
+                                    -> Result<(), ::std::io::Error>
+            where T: Write
+        {
+            match config.newline_style {
+                NewlineStyle::Unix => write!(writer, "{}", text),
+                NewlineStyle::Windows => {
+                    for (c, _) in text.chars() {
+                        match c {
+                            '\n' => try!(write!(writer, "\r\n")),
+                            '\r' => continue,
+                            c => try!(write!(writer, "{}", c)),
+                        }
+                    }
+                    Ok(())
+                },
             }
-        };
-        let abs_end = {
-            let before_end = self.adjusts.range(Unbounded, Included(&end)).next_back();
-            match before_end {
-                Some((k, ref a)) if a.end > end => (end as isize + a.delta) as usize,
-                _ => end
+        }
+
+        match mode {
+            WriteMode::Overwrite => {
+                // Do a little dance to make writing safer - write to a temp file
+                // rename the original to a .bk, then rename the temp file to the
+                // original.
+                let tmp_name = filename.to_owned() + ".tmp";
+                let bk_name = filename.to_owned() + ".bk";
+                {
+                    // Write text to temp file
+                    let tmp_file = try!(File::create(&tmp_name));
+                    try!(write_system_newlines(tmp_file, text, config));
+                }
+
+                try!(::std::fs::rename(filename, bk_name));
+                try!(::std::fs::rename(tmp_name, filename));
             }
-        };
+            WriteMode::NewFile(extn) => {
+                let filename = filename.to_owned() + "." + extn;
+                let file = try!(File::create(&filename));
+                try!(write_system_newlines(file, text, config));
+            }
+            WriteMode::Display => {
+                println!("{}:\n", filename);
+                let stdout = stdout();
+                let stdout_lock = stdout.lock();
+                try!(write_system_newlines(stdout_lock, text, config));
+            }
+            WriteMode::Return(_) => {
+                // io::Write is not implemented for String, working around with Vec<u8>
+                let mut v = Vec::new();
+                try!(write_system_newlines(&mut v, text, config));
+                // won't panic, we are writing correct utf8
+                return Ok(Some(String::from_utf8(v).unwrap()));
+            }
+        }
 
-        let file = &self.file_map[*file_name];
-        file.slice(abs_start..abs_end)
+        Ok(None)
     }
 
-    // span is unadjusted.
-    pub fn slice_span(&self, span:Span) -> RopeSlice {
-        let l_loc = self.codemap.lookup_char_pos(span.lo);
-        let file_offset = l_loc.file.start_pos.0;
-        self.slice(&l_loc.file.name[],
-                   (span.lo.0 - file_offset) as usize,
-                   (span.hi.0 - file_offset) as usize)
+    pub fn is_changed(&self, filename: &str) -> bool {
+        self.file_map.get(filename).expect("Unknown filename").len != 0
     }
+}
 
-    pub fn text<'c>(&'c self) -> FileIterator<'c, 'a> {
-        FileIterator {
-            change_set: self,
-            keys: self.file_map.keys().collect(),
-            cur_key: 0,
-        }
-    }
+// Iterates over each file in the ChangSet. Yields the filename and the changed
+// text for that file.
+pub struct FileIterator<'c, 'a: 'c> {
+    change_set: &'c ChangeSet<'a>,
+    keys: Vec<&'c String>,
+    cur_key: usize,
 }
 
 impl<'c, 'a> Iterator for FileIterator<'c, 'a> {
-    type Item = (&'c str, &'c Rope);
-    fn next(&mut self) -> Option<(&'c str, &'c Rope)> {
+    type Item = (&'c str, &'c StringBuffer);
+
+    fn next(&mut self) -> Option<(&'c str, &'c StringBuffer)> {
         if self.cur_key >= self.keys.len() {
             return None;
         }
 
         let key = self.keys[self.cur_key];
         self.cur_key += 1;
-        return Some((&key[], &self.change_set.file_map[*key]))
+        return Some((&key, &self.change_set.file_map[&*key]))
     }
 }
 
 impl<'a> fmt::Display for ChangeSet<'a> {
+    // Prints the entire changed text.
     fn fmt(&self, fmt: &mut fmt::Formatter) -> Result<(), fmt::Error> {
         for (f, r) in self.text() {
             try!(write!(fmt, "{}:\n", f));
-            try!(write!(fmt, "{}", r));
+            try!(write!(fmt, "{}\n\n", r));
         }
         Ok(())
-    }    
+    }
 }