use super::*;
use std::io;
use std::path::Path;
use racer::{BytePos, FileCache, Location, Match};
const LIBRS: &str = "lib.rs";
pub struct CodeCompleter {
last_code: String,
split: std::ops::Range<usize>,
}
impl CodeCompleter {
pub fn build<T>(repl_data: &crate::repl::ReplData<T>) -> Self {
let (last_code, map) = crate::code::construct_source_code(
repl_data.mods_map(),
repl_data.linking(),
repl_data.static_files(),
);
let split = map.get(repl_data.current_mod()).cloned().unwrap_or(0..0);
CodeCompleter { last_code, split }
}
pub fn word_break(line: &str) -> usize {
word_break_start(line, &[' ', ':', '.'])
}
pub fn complete(&self, injection: &str, limit: Option<usize>, cache: &CodeCache) -> Vec<Match> {
let limit = limit.unwrap_or(std::usize::MAX);
let session = racer::Session::new(&cache.cache, None);
let (contents, pos) = self.inject(injection);
session.cache_file_contents(LIBRS, contents);
racer::complete_from_file(LIBRS, Location::Point(pos), &session)
.take(limit)
.collect()
}
fn inject(&self, injection: &str) -> (String, BytePos) {
let cap = self.last_code.len() + self.split.start - self.split.end + injection.len();
let mut s = String::with_capacity(cap);
s.push_str(&self.last_code[..self.split.start]);
s.push_str(injection);
s.push_str(&self.last_code[self.split.end..]);
debug_assert_eq!(s.len(), cap);
let pos = (self.split.start + injection.len()).into();
(s, pos)
}
}
pub struct CodeCache {
cache: FileCache,
}
impl CodeCache {
pub fn new() -> Result<Self, (Self, &'static str)> {
use racer::RustSrcPathError::*;
let cache = Self {
cache: FileCache::new(PapyrusCodeFileLoader),
};
match racer::get_rust_src_path() {
Ok(_) => Ok(cache),
Err(Missing) => Err((cache, "rust source code does not exist")),
Err(DoesNotExist(_)) => Err((cache, "rust source code path does not exist")),
Err(NotRustSourceTree(_)) => Err((
cache,
"rust source code path does not have valid rustc source",
)),
}
}
}
struct PapyrusCodeFileLoader;
impl racer::FileLoader for PapyrusCodeFileLoader {
fn load_file(&self, path: &Path) -> io::Result<String> {
use std::fs::File;
use std::io::Read;
if path == Path::new(LIBRS) {
Ok(String::new())
} else {
let mut rawbytes = Vec::new();
let mut f = File::open(path)?;
f.read_to_end(&mut rawbytes)?;
if rawbytes.len() > 2 && rawbytes[0..3] == [0xEF, 0xBB, 0xBF] {
std::str::from_utf8(&rawbytes[3..])
.map(|s| s.to_owned())
.map_err(|err| io::Error::new(io::ErrorKind::Other, err))
} else {
String::from_utf8(rawbytes).map_err(|err| io::Error::new(io::ErrorKind::Other, err))
}
}
}
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn inject_test() {
let cc = CodeCompleter {
last_code: String::from("Hello morld"),
split: 5..7, };
let (s, pos) = cc.inject(", w");
assert_eq!(&s, "Hello, world");
assert_eq!(pos, BytePos(8));
let cc = CodeCompleter {
last_code: String::from("Hello"),
split: 5..5, };
let (s, pos) = cc.inject(", world");
assert_eq!(&s, "Hello, world");
assert_eq!(pos, BytePos(12));
let cc = CodeCompleter {
last_code: String::from(", world"),
split: 0..0, };
let (s, pos) = cc.inject("Hello");
assert_eq!(&s, "Hello, world");
assert_eq!(pos, BytePos(5));
let cc = CodeCompleter {
last_code: String::from("Hello, worm"),
split: 10..11, };
let (s, pos) = cc.inject("ld");
assert_eq!(&s, "Hello, world");
assert_eq!(pos, BytePos(12));
}
#[test]
fn complete_test() {
let cc = CodeCompleter {
last_code: String::from("fn apple() {} \n\n fn main() { }"),
split: 29..29,
};
let (s, _) = cc.inject("ap");
assert_eq!(&s, "fn apple() {} \n\n fn main() { ap }");
let matches = cc.complete("ap", None, &CodeCache::new().unwrap_or_else(|e| e.0));
assert_eq!(matches.get(0).map(|x| x.matchstr.as_str()), Some("apple"));
}
#[test]
fn complete_through_repl() {
use crate::repl::*;
let src = "#[derive(Default)]
struct MyStruct {
string: String,
number: i32
}
impl MyStruct {
fn new() -> Self {
MyStruct::default()
}
fn number(&self) -> i32 {
self.number
}
}";
let cache = CodeCache::new().unwrap_or_else(|e| e.0);
let mut repl = Repl::default();
repl.line_input(src);
let repl = match repl.read() {
ReadResult::Eval(e) => e.eval(&mut ()).repl.print().0,
ReadResult::Read(_) => panic!("should have evaluated"),
};
println!("{}", repl.output());
let cmpltr = CodeCompleter::build(&repl.data);
dbg!(&cmpltr.last_code);
let completions = cmpltr.complete("MyStruct::", None, &cache);
dbg!(&completions);
assert_eq!(completions.get(0).map(|x| x.matchstr.as_str()), Some("new"));
assert_eq!(completions.get(1), None);
let completions = cmpltr.complete("let mystruct = MyStruct; mystruct.", None, &cache);
dbg!(&completions);
assert_eq!(
completions.get(0).map(|x| x.matchstr.as_str()),
Some("number")
); assert_eq!(
completions.get(1).map(|x| x.matchstr.as_str()),
Some("number")
); assert_eq!(
completions.get(2).map(|x| x.matchstr.as_str()),
Some("string")
); assert_eq!(completions.get(3), None);
}
}