extern crate rls_span as span;
#[macro_use]
extern crate log;
use std::collections::HashMap;
use std::fmt;
use std::fs;
use std::io::Read;
use std::marker::PhantomData;
use std::mem;
use std::path::{Path, PathBuf};
use std::sync::Mutex;
use std::thread::{self, Thread};
#[cfg(test)]
mod test;
macro_rules! try_opt_loc {
($e:expr) => {
match $e {
Some(e) => e,
None => return Err(Error::BadLocation),
}
};
}
pub struct Vfs<U = ()>(VfsInternal<RealFileLoader, U>);
#[derive(Debug, Hash, PartialEq, Eq, PartialOrd, Ord)]
pub struct SpanData {
pub span: span::Span<span::ZeroIndexed>,
pub len: Option<u64>,
}
#[derive(Debug, Hash, PartialEq, Eq, PartialOrd, Ord)]
pub enum VfsSpan {
UnicodeScalarValue(SpanData),
Utf16CodeUnit(SpanData),
}
impl VfsSpan {
pub fn from_usv(span: span::Span<span::ZeroIndexed>, len: Option<u64>) -> VfsSpan {
VfsSpan::UnicodeScalarValue(SpanData { span, len })
}
pub fn from_utf16(span: span::Span<span::ZeroIndexed>, len: Option<u64>) -> VfsSpan {
VfsSpan::Utf16CodeUnit(SpanData { span, len })
}
pub fn byte_in_str(&self, s: &str, c: span::Column<span::ZeroIndexed>) -> Result<usize, Error> {
match self {
VfsSpan::UnicodeScalarValue(..) => byte_in_str(s, c),
VfsSpan::Utf16CodeUnit(..) => byte_in_str_utf16(s, c),
}
}
fn as_inner(&self) -> &SpanData {
match self {
VfsSpan::UnicodeScalarValue(span) => span,
VfsSpan::Utf16CodeUnit(span) => span,
}
}
pub fn span(&self) -> &span::Span<span::ZeroIndexed> {
&self.as_inner().span
}
pub fn len(&self) -> Option<u64> {
self.as_inner().len
}
}
#[derive(Debug)]
pub enum Change {
AddFile { file: PathBuf, text: String },
ReplaceText {
span: VfsSpan,
text: String,
},
}
impl Change {
fn file(&self) -> &Path {
match *self {
Change::AddFile { ref file, .. } => file.as_ref(),
Change::ReplaceText { ref span, .. } => span.span().file.as_ref(),
}
}
}
#[derive(Debug, Clone, Eq, PartialEq)]
pub enum Error {
OutOfSync(PathBuf),
Io(Option<PathBuf>, Option<String>),
UncommittedChanges(PathBuf),
BadLocation,
FileNotCached,
NoUserDataForFile,
BadFileKind,
InternalError(&'static str),
}
impl ::std::error::Error for Error {
fn description(&self) -> &str {
match *self {
Error::OutOfSync(ref _path_buf) => "file out of sync with filesystem",
Error::Io(ref _path_buf, ref _message) => "io::Error reading or writing path",
Error::UncommittedChanges(ref _path_buf) => {
"changes exist which have not been written to disk"
}
Error::BadLocation => "client specified location not existing within a file",
Error::FileNotCached => "requested file was not cached in the VFS",
Error::NoUserDataForFile => "file is cached but there is no user data for it",
Error::BadFileKind => {
"file is not the correct kind for the operation (e.g., text op on binary file)"
}
Error::InternalError(_) => "internal error",
}
}
}
impl Into<String> for Error {
fn into(self) -> String {
::std::error::Error::description(&self).to_owned()
}
}
impl fmt::Display for Error {
fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
match *self {
Error::OutOfSync(ref path_buf) => {
write!(f, "file {} out of sync with filesystem", path_buf.display())
}
Error::UncommittedChanges(ref path_buf) => {
write!(f, "{} has uncommitted changes", path_buf.display())
}
Error::InternalError(e) => write!(f, "internal error: {}", e),
Error::BadLocation
| Error::FileNotCached
| Error::NoUserDataForFile
| Error::Io(..)
| Error::BadFileKind => f.write_str(::std::error::Error::description(self)),
}
}
}
impl<U> Vfs<U> {
pub fn new() -> Vfs<U> {
Vfs(VfsInternal::<RealFileLoader, U>::new())
}
pub fn file_saved(&self, path: &Path) -> Result<(), Error> {
self.0.file_saved(path)
}
pub fn flush_file(&self, path: &Path) -> Result<(), Error> {
self.0.flush_file(path)
}
pub fn file_is_synced(&self, path: &Path) -> Result<bool, Error> {
self.0.file_is_synced(path)
}
pub fn on_changes(&self, changes: &[Change]) -> Result<(), Error> {
self.0.on_changes(changes)
}
pub fn get_cached_files(&self) -> HashMap<PathBuf, String> {
self.0.get_cached_files()
}
pub fn get_changes(&self) -> HashMap<PathBuf, String> {
self.0.get_changes()
}
pub fn has_changes(&self) -> bool {
self.0.has_changes()
}
pub fn set_file(&self, path: &Path, text: &str) {
self.0.set_file(path, text)
}
pub fn load_file(&self, path: &Path) -> Result<FileContents, Error> {
self.0.load_file(path)
}
pub fn load_line(
&self,
path: &Path,
line: span::Row<span::ZeroIndexed>,
) -> Result<String, Error> {
self.0.load_line(path, line)
}
pub fn load_lines(
&self,
path: &Path,
line_start: span::Row<span::ZeroIndexed>,
line_end: span::Row<span::ZeroIndexed>,
) -> Result<String, Error> {
self.0.load_lines(path, line_start, line_end)
}
pub fn load_span(&self, span: span::Span<span::ZeroIndexed>) -> Result<String, Error> {
self.0.load_span(span)
}
pub fn for_each_line<F>(&self, path: &Path, f: F) -> Result<(), Error>
where
F: FnMut(&str, usize) -> Result<(), Error>,
{
self.0.for_each_line(path, f)
}
pub fn write_file(&self, path: &Path) -> Result<(), Error> {
self.0.write_file(path)
}
pub fn set_user_data(&self, path: &Path, data: Option<U>) -> Result<(), Error> {
self.0.set_user_data(path, data)
}
pub fn with_user_data<F, R>(&self, path: &Path, f: F) -> Result<R, Error>
where
F: FnOnce(Result<(Option<&str>, &mut U), Error>) -> Result<R, Error>,
{
self.0.with_user_data(path, f)
}
pub fn ensure_user_data<F>(&self, path: &Path, f: F) -> Result<(), Error>
where
F: FnOnce(Option<&str>) -> Result<U, Error>,
{
self.0.ensure_user_data(path, f)
}
pub fn clear(&self) {
self.0.clear()
}
}
struct VfsInternal<T, U> {
files: Mutex<HashMap<PathBuf, File<U>>>,
pending_files: Mutex<HashMap<PathBuf, Vec<Thread>>>,
loader: PhantomData<T>,
}
impl<T: FileLoader, U> VfsInternal<T, U> {
fn new() -> VfsInternal<T, U> {
VfsInternal {
files: Mutex::new(HashMap::new()),
pending_files: Mutex::new(HashMap::new()),
loader: PhantomData,
}
}
fn clear(&self) {
let mut pending_files = self.pending_files.lock().unwrap();
let mut files = self.files.lock().unwrap();
*files = HashMap::new();
let mut new_pending_files = HashMap::new();
mem::swap(&mut *pending_files, &mut new_pending_files);
for ts in new_pending_files.values() {
for t in ts {
t.unpark();
}
}
}
fn file_saved(&self, path: &Path) -> Result<(), Error> {
let mut files = self.files.lock().unwrap();
if let Some(ref mut f) = files.get_mut(path) {
match f.kind {
FileKind::Text(ref mut f) => f.changed = false,
FileKind::Binary(_) => return Err(Error::BadFileKind),
}
}
Ok(())
}
fn flush_file(&self, path: &Path) -> Result<(), Error> {
loop {
let mut pending_files = self.pending_files.lock().unwrap();
let mut files = self.files.lock().unwrap();
if !pending_files.contains_key(path) {
files.remove(path);
return Ok(());
}
pending_files.get_mut(path).unwrap().push(thread::current());
thread::park();
}
}
fn file_is_synced(&self, path: &Path) -> Result<bool, Error> {
let files = self.files.lock().unwrap();
match files.get(path) {
Some(f) => Ok(!f.changed()),
None => Err(Error::FileNotCached),
}
}
fn on_changes(&self, changes: &[Change]) -> Result<(), Error> {
trace!("on_changes: {:?}", changes);
for (file_name, changes) in coalesce_changes(changes) {
let path = Path::new(file_name);
{
let mut files = self.files.lock().unwrap();
if let Some(file) = files.get_mut(Path::new(path)) {
file.make_change(&changes)?;
continue;
}
}
let mut file = T::read(Path::new(path))?;
file.make_change(&changes)?;
let mut files = self.files.lock().unwrap();
files.insert(path.to_path_buf(), file);
}
Ok(())
}
fn set_file(&self, path: &Path, text: &str) {
let file = File {
kind: FileKind::Text(TextFile {
text: text.to_owned(),
line_indices: make_line_indices(text),
changed: true,
}),
user_data: None,
};
loop {
let mut pending_files = self.pending_files.lock().unwrap();
let mut files = self.files.lock().unwrap();
if !pending_files.contains_key(path) {
files.insert(path.to_owned(), file);
return;
}
pending_files.get_mut(path).unwrap().push(thread::current());
thread::park();
}
}
fn get_cached_files(&self) -> HashMap<PathBuf, String> {
let files = self.files.lock().unwrap();
files
.iter()
.filter_map(|(p, f)| match f.kind {
FileKind::Text(ref f) => Some((p.clone(), f.text.clone())),
FileKind::Binary(_) => None,
})
.collect()
}
fn get_changes(&self) -> HashMap<PathBuf, String> {
let files = self.files.lock().unwrap();
files
.iter()
.filter_map(|(p, f)| match f.kind {
FileKind::Text(ref f) if f.changed => Some((p.clone(), f.text.clone())),
_ => None,
})
.collect()
}
fn has_changes(&self) -> bool {
let files = self.files.lock().unwrap();
files.values().any(|f| f.changed())
}
fn load_line(&self, path: &Path, line: span::Row<span::ZeroIndexed>) -> Result<String, Error> {
self.ensure_file(path, |f| f.load_line(line).map(|s| s.to_owned()))
}
fn load_lines(
&self,
path: &Path,
line_start: span::Row<span::ZeroIndexed>,
line_end: span::Row<span::ZeroIndexed>,
) -> Result<String, Error> {
self.ensure_file(path, |f| f.load_lines(line_start, line_end).map(|s| s.to_owned()))
}
fn load_span(&self, span: span::Span<span::ZeroIndexed>) -> Result<String, Error> {
self.ensure_file(&span.file, |f| f.load_range(span.range).map(|s| s.to_owned()))
}
fn for_each_line<F>(&self, path: &Path, f: F) -> Result<(), Error>
where
F: FnMut(&str, usize) -> Result<(), Error>,
{
self.ensure_file(path, |file| file.for_each_line(f))
}
fn load_file(&self, path: &Path) -> Result<FileContents, Error> {
self.ensure_file(path, |f| Ok(f.contents()))
}
fn ensure_file<F, R>(&self, path: &Path, f: F) -> Result<R, Error>
where
F: FnOnce(&File<U>) -> Result<R, Error>,
{
loop {
{
let mut pending_files = self.pending_files.lock().unwrap();
let files = self.files.lock().unwrap();
if files.contains_key(path) {
return f(&files[path]);
}
if !pending_files.contains_key(path) {
pending_files.insert(path.to_owned(), vec![]);
break;
}
pending_files.get_mut(path).unwrap().push(thread::current());
}
thread::park();
}
let file = T::read(path);
let mut pending_files = self.pending_files.lock().unwrap();
let mut files = self.files.lock().unwrap();
match file {
Ok(file) => {
files.insert(path.to_owned(), file);
let ts = pending_files.remove(path).unwrap();
for t in ts {
t.unpark();
}
}
Err(e) => {
let ts = pending_files.remove(path).unwrap();
for t in ts {
t.unpark();
}
return Err(e);
}
}
f(&files[path])
}
fn write_file(&self, path: &Path) -> Result<(), Error> {
let file = {
let mut files = self.files.lock().unwrap();
match files.get_mut(path) {
Some(f) => {
if let FileKind::Text(ref mut f) = f.kind {
f.changed = false;
}
f.kind.clone()
}
None => return Err(Error::FileNotCached),
}
};
T::write(path, &file)?;
Ok(())
}
pub fn set_user_data(&self, path: &Path, data: Option<U>) -> Result<(), Error> {
let mut files = self.files.lock().unwrap();
match files.get_mut(path) {
Some(ref mut f) => {
f.user_data = data;
Ok(())
}
None => Err(Error::FileNotCached),
}
}
pub fn with_user_data<F, R>(&self, path: &Path, f: F) -> Result<R, Error>
where
F: FnOnce(Result<(Option<&str>, &mut U), Error>) -> Result<R, Error>,
{
let mut files = self.files.lock().unwrap();
let file = match files.get_mut(path) {
Some(f) => f,
None => return f(Err(Error::FileNotCached)),
};
let result = f(match file.user_data {
Some(ref mut u) => {
let text = match file.kind {
FileKind::Text(ref f) => Some(&f.text as &str),
FileKind::Binary(_) => None,
};
Ok((text, u))
}
None => Err(Error::NoUserDataForFile),
});
if let Err(Error::NoUserDataForFile) = result {
file.user_data = None;
}
result
}
pub fn ensure_user_data<F>(&self, path: &Path, f: F) -> Result<(), Error>
where
F: FnOnce(Option<&str>) -> Result<U, Error>,
{
let mut files = self.files.lock().unwrap();
match files.get_mut(path) {
Some(ref mut file) => {
if let None = file.user_data {
let text = match file.kind {
FileKind::Text(ref f) => Some(&f.text as &str),
FileKind::Binary(_) => None,
};
match f(text) {
Ok(u) => {
file.user_data = Some(u);
Ok(())
}
Err(Error::NoUserDataForFile) => {
file.user_data = None;
Ok(())
}
Err(e) => Err(e),
}
} else {
Ok(())
}
}
None => Err(Error::FileNotCached),
}
}
}
fn coalesce_changes<'a>(changes: &'a [Change]) -> HashMap<&'a Path, Vec<&'a Change>> {
let mut result = HashMap::new();
for c in changes {
result.entry(&*c.file()).or_insert(vec![]).push(c);
}
result
}
fn make_line_indices(text: &str) -> Vec<u32> {
let mut result = vec![0];
for (i, b) in text.bytes().enumerate() {
if b == 0xA {
result.push((i + 1) as u32);
}
}
result.push(text.len() as u32);
result
}
#[derive(Clone)]
enum FileKind {
Text(TextFile),
Binary(Vec<u8>),
}
impl FileKind {
fn as_bytes(&self) -> &[u8] {
match *self {
FileKind::Text(ref t) => t.text.as_bytes(),
FileKind::Binary(ref b) => b,
}
}
}
#[derive(Debug, PartialEq)]
pub enum FileContents {
Text(String),
Binary(Vec<u8>),
}
#[derive(Clone)]
struct TextFile {
text: String,
line_indices: Vec<u32>,
changed: bool,
}
struct File<U> {
kind: FileKind,
user_data: Option<U>,
}
impl<U> File<U> {
fn contents(&self) -> FileContents {
match self.kind {
FileKind::Text(ref t) => FileContents::Text(t.text.clone()),
FileKind::Binary(ref b) => FileContents::Binary(b.clone()),
}
}
fn make_change(&mut self, changes: &[&Change]) -> Result<(), Error> {
match self.kind {
FileKind::Text(ref mut t) => {
self.user_data = None;
t.make_change(changes)
}
FileKind::Binary(_) => Err(Error::BadFileKind),
}
}
fn load_line(&self, line: span::Row<span::ZeroIndexed>) -> Result<&str, Error> {
match self.kind {
FileKind::Text(ref t) => t.load_line(line),
FileKind::Binary(_) => Err(Error::BadFileKind),
}
}
fn load_lines(
&self,
line_start: span::Row<span::ZeroIndexed>,
line_end: span::Row<span::ZeroIndexed>,
) -> Result<&str, Error> {
match self.kind {
FileKind::Text(ref t) => t.load_lines(line_start, line_end),
FileKind::Binary(_) => Err(Error::BadFileKind),
}
}
fn load_range(&self, range: span::Range<span::ZeroIndexed>) -> Result<&str, Error> {
match self.kind {
FileKind::Text(ref t) => t.load_range(range),
FileKind::Binary(_) => Err(Error::BadFileKind),
}
}
fn for_each_line<F>(&self, f: F) -> Result<(), Error>
where
F: FnMut(&str, usize) -> Result<(), Error>,
{
match self.kind {
FileKind::Text(ref t) => t.for_each_line(f),
FileKind::Binary(_) => Err(Error::BadFileKind),
}
}
fn changed(&self) -> bool {
match self.kind {
FileKind::Text(ref t) => t.changed,
FileKind::Binary(_) => false,
}
}
}
impl TextFile {
fn make_change(&mut self, changes: &[&Change]) -> Result<(), Error> {
trace!("TextFile::make_change");
for c in changes {
trace!("TextFile::make_change: {:?}", c);
let new_text = match **c {
Change::ReplaceText { span: ref vfs_span, ref text } => {
let (span, len) = (vfs_span.span(), vfs_span.len());
let range = {
let first_line = self.load_line(span.range.row_start)?;
let byte_start = self.line_indices[span.range.row_start.0 as usize]
+ vfs_span.byte_in_str(first_line, span.range.col_start)? as u32;
let byte_end = if let Some(len) = len {
byte_start
+ vfs_span.byte_in_str(
&self.text[byte_start as usize..],
span::Column::new_zero_indexed(len as u32),
)? as u32
} else {
let last_line = self.load_line(span.range.row_end)?;
self.line_indices[span.range.row_end.0 as usize]
+ vfs_span.byte_in_str(last_line, span.range.col_end)? as u32
};
(byte_start, byte_end)
};
let mut new_text = self.text[..range.0 as usize].to_owned();
new_text.push_str(text);
new_text.push_str(&self.text[range.1 as usize..]);
new_text
}
Change::AddFile { file: _, ref text } => text.to_owned(),
};
self.text = new_text;
self.line_indices = make_line_indices(&self.text);
}
self.changed = true;
Ok(())
}
fn load_line(&self, line: span::Row<span::ZeroIndexed>) -> Result<&str, Error> {
let start = *try_opt_loc!(self.line_indices.get(line.0 as usize));
let end = *try_opt_loc!(self.line_indices.get(line.0 as usize + 1));
if (end as usize) <= self.text.len() && start <= end {
Ok(&self.text[start as usize..end as usize])
} else {
Err(Error::BadLocation)
}
}
fn load_lines(
&self,
line_start: span::Row<span::ZeroIndexed>,
line_end: span::Row<span::ZeroIndexed>,
) -> Result<&str, Error> {
let line_start = line_start.0 as usize;
let mut line_end = line_end.0 as usize;
if line_end >= self.line_indices.len() {
line_end = self.line_indices.len() - 1;
}
let start = (*try_opt_loc!(self.line_indices.get(line_start))) as usize;
let end = (*try_opt_loc!(self.line_indices.get(line_end))) as usize;
if (end) <= self.text.len() && start <= end {
Ok(&self.text[start..end])
} else {
Err(Error::BadLocation)
}
}
fn load_range(&self, range: span::Range<span::ZeroIndexed>) -> Result<&str, Error> {
let line_start = range.row_start.0 as usize;
let mut line_end = range.row_end.0 as usize;
if line_end >= self.line_indices.len() {
line_end = self.line_indices.len() - 1;
}
let start = (*try_opt_loc!(self.line_indices.get(line_start))) as usize;
let start = start + range.col_start.0 as usize;
let end = (*try_opt_loc!(self.line_indices.get(line_end))) as usize;
let end = end + range.col_end.0 as usize;
if (end) <= self.text.len() && start <= end {
Ok(&self.text[start..end])
} else {
Err(Error::BadLocation)
}
}
fn for_each_line<F>(&self, mut f: F) -> Result<(), Error>
where
F: FnMut(&str, usize) -> Result<(), Error>,
{
let mut line_iter = self.line_indices.iter();
let mut start = *line_iter.next().unwrap() as usize;
for (i, idx) in line_iter.enumerate() {
let idx = *idx as usize;
f(&self.text[start..idx], i)?;
start = idx;
}
Ok(())
}
}
fn byte_in_str(s: &str, c: span::Column<span::ZeroIndexed>) -> Result<usize, Error> {
for (i, (b, _)) in s.char_indices().chain(Some((s.len(), '\0')).into_iter()).enumerate() {
if c.0 as usize == i {
return Ok(b);
}
}
return Err(Error::InternalError("Out of bounds access in `byte_in_str`"));
}
fn byte_in_str_utf16(s: &str, c: span::Column<span::ZeroIndexed>) -> Result<usize, Error> {
let (mut utf8_offset, mut utf16_offset) = (0, 0);
let target_utf16_offset = c.0 as usize;
for chr in s.chars().chain(std::iter::once('\0')) {
if utf16_offset > target_utf16_offset {
break;
} else if utf16_offset == target_utf16_offset {
return Ok(utf8_offset);
}
utf8_offset += chr.len_utf8();
utf16_offset += chr.len_utf16();
}
return Err(Error::InternalError("UTF-16 code unit offset is not at `str` char boundary"));
}
trait FileLoader {
fn read<U>(file_name: &Path) -> Result<File<U>, Error>;
fn write(file_name: &Path, file: &FileKind) -> Result<(), Error>;
}
struct RealFileLoader;
impl FileLoader for RealFileLoader {
fn read<U>(file_name: &Path) -> Result<File<U>, Error> {
let mut file = match fs::File::open(file_name) {
Ok(f) => f,
Err(_) => {
return Err(Error::Io(
Some(file_name.to_owned()),
Some(format!("Could not open file: {}", file_name.display())),
));
}
};
let mut buf = vec![];
if let Err(_) = file.read_to_end(&mut buf) {
return Err(Error::Io(
Some(file_name.to_owned()),
Some(format!("Could not read file: {}", file_name.display())),
));
}
match String::from_utf8(buf) {
Ok(s) => Ok(File {
kind: FileKind::Text(TextFile {
line_indices: make_line_indices(&s),
text: s,
changed: false,
}),
user_data: None,
}),
Err(e) => Ok(File { kind: FileKind::Binary(e.into_bytes()), user_data: None }),
}
}
fn write(file_name: &Path, file: &FileKind) -> Result<(), Error> {
use std::io::Write;
macro_rules! try_io {
($e:expr) => {
match $e {
Ok(e) => e,
Err(e) => {
return Err(Error::Io(Some(file_name.to_owned()), Some(e.to_string())));
}
}
};
}
let mut out = try_io!(::std::fs::File::create(file_name));
try_io!(out.write_all(file.as_bytes()));
Ok(())
}
}
#[cfg(test)]
mod tests {
use span::Column;
#[test]
fn byte_in_str_utf16() {
use super::byte_in_str_utf16;
assert_eq!(
'😢'.len_utf8(),
byte_in_str_utf16("😢a", Column::new_zero_indexed('😢'.len_utf16() as u32))
.unwrap()
);
assert!(byte_in_str_utf16("😢", Column::new_zero_indexed(1)).is_err());
}
}