// 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(())
- }
+ }
}