blob: 842231b5f9515643f6fcada803e214a1942344e0 [file] [log] [blame]
use crate::ast_types::{GenericsArgs, ImplHeader, Pat, TraitBounds, Ty, TypeParameter};
use crate::codecleaner;
use crate::codeiter::StmtIndicesIter;
use crate::matchers::ImportInfo;
use crate::project_model::ProjectModelProvider;
use rls_span;
use std::cell::RefCell;
use std::cmp::Ordering;
use std::collections::HashMap;
use std::fs::File;
use std::io;
use std::io::Read;
use std::iter::{Fuse, Iterator};
use std::ops::{Deref, Range};
use std::rc::Rc;
use std::{fmt, vec};
use std::{path, str};
use rustc_span::source_map;
use crate::ast;
use crate::fileres;
use crate::nameres;
use crate::primitive::PrimKind;
use crate::scopes;
use crate::util;
/// Within a [`Match`], specifies what was matched
///
/// [`Match`]: struct.Match.html
#[derive(Clone, Debug, PartialEq)]
pub enum MatchType {
Struct(Box<GenericsArgs>),
Module,
MatchArm,
Function,
Method(Option<Box<GenericsArgs>>),
Crate,
Let(BytePos),
IfLet(BytePos),
WhileLet(BytePos),
For(BytePos),
StructField,
Enum(Box<GenericsArgs>),
Union(Box<GenericsArgs>),
/// EnumVariant needs to have Enum type to complete methods
EnumVariant(Option<Box<Match>>),
UseAlias(Box<Match>),
AssocType,
Type,
FnArg(Box<(Pat, Option<Ty>)>),
Trait,
Const,
Static,
Macro,
Builtin(PrimKind),
/// fn f<T: Clone> or fn f(a: impl Clone) with its trait bounds
TypeParameter(Box<TraitBounds>),
}
impl MatchType {
pub fn is_function(&self) -> bool {
match self {
MatchType::Function | MatchType::Method(_) => true,
_ => false,
}
}
pub fn is_enum(&self) -> bool {
match self {
MatchType::Enum(_) => true,
_ => false,
}
}
pub fn is_struct(&self) -> bool {
match self {
MatchType::Struct(_) => true,
_ => false,
}
}
}
impl fmt::Display for MatchType {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
match self {
MatchType::Struct(_) => write!(f, "Struct"),
MatchType::Union(_) => write!(f, "Union"),
MatchType::Method(_) => write!(f, "Method"),
MatchType::IfLet(_) => write!(f, "IfLet"),
MatchType::Let(_) => write!(f, "Let"),
MatchType::WhileLet(_) => write!(f, "WhileLet"),
MatchType::For(_) => write!(f, "For"),
MatchType::Enum(_) => write!(f, "Enum"),
MatchType::EnumVariant(_) => write!(f, "EnumVariant"),
MatchType::TypeParameter(_) => write!(f, "TypeParameter"),
MatchType::FnArg(_) => write!(f, "FnArg"),
MatchType::Type => write!(f, "Type"),
MatchType::UseAlias(_) => write!(f, "UseAlias"),
_ => fmt::Debug::fmt(self, f),
}
}
}
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub enum SearchType {
ExactMatch,
StartsWith,
}
mod declare_namespace {
// (kngwyu) I reserved Crate, Mod or other names for future usage(like for #830)
// but, currently they're not used and... I'm not sure they're useful:)
#![allow(non_upper_case_globals, unused)]
bitflags! {
/// Type context
pub struct Namespace: u32 {
const Crate = 0b0000000000001;
const Mod = 0b0000000000010;
const Space = 0b0000000000011;
const Enum = 0b0000000000100;
const Struct = 0b0000000001000;
const Union = 0b0000000010000;
const Trait = 0b0000000100000;
const TypeDef = 0b0000001000000;
const HasField = 0b0000001011100;
const Type = 0b0000001111100;
const PathParen = 0b0000001111111;
const Const = 0b0000010000000;
const Static = 0b0000100000000;
const Func = 0b0001000000000;
// for use_extern_macros
const Macro = 0b0010000000000;
const Impl = 0b0001110000000;
const PathChild = 0b0011110000000;
const Path = 0b0011111111111;
const Primitive = 0b0100000000000;
const StdMacro = 0b1000000000000;
const Global = 0b1100000000000;
}
}
}
pub use self::declare_namespace::Namespace;
#[derive(Debug, Clone, Copy)]
pub enum CompletionType {
Field,
Path,
}
/// 0-based byte offset in a file.
#[derive(
Clone,
Copy,
Debug,
Default,
Eq,
PartialEq,
Ord,
PartialOrd,
Hash,
Index,
From,
Add,
Sub,
AddAssign,
SubAssign,
)]
pub struct BytePos(pub usize);
impl From<u32> for BytePos {
fn from(u: u32) -> Self {
BytePos(u as usize)
}
}
impl BytePos {
pub const ZERO: BytePos = BytePos(0);
/// returns self - 1
pub fn decrement(&self) -> Self {
BytePos(self.0 - 1)
}
pub fn checked_sub(&self, sub: impl Into<Self>) -> Option<Self> {
self.0.checked_sub(sub.into().0).map(BytePos)
}
pub fn try_decrement(&self) -> Option<Self> {
self.0.checked_sub(1).map(BytePos)
}
/// returns self + 1
pub fn increment(&self) -> Self {
BytePos(self.0 + 1)
}
}
impl fmt::Display for BytePos {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
write!(f, "{}", self.0)
}
}
/// 0-based byte range in a file.
#[derive(Clone, Copy, Default, Eq, PartialEq, Hash)]
pub struct ByteRange {
/// start of byte position in codes(inclusive)
pub start: BytePos,
/// end of byte position in codes(exclusive)
pub end: BytePos,
}
impl ByteRange {
/// returns new ByteRange from start and end
pub fn new<P: Into<BytePos>>(start: P, end: P) -> Self {
ByteRange {
start: start.into(),
end: end.into(),
}
}
/// returns the length of the range
#[inline]
pub fn len(&self) -> usize {
(self.end - self.start).0
}
/// returns if the range contains `point` or not
#[inline]
pub fn contains(&self, point: BytePos) -> bool {
self.start <= point && point < self.end
}
/// returns if the range contains `point` (except its start point)
#[inline]
pub fn contains_exclusive(&self, point: BytePos) -> bool {
self.start < point && point < self.end
}
/// returns the new range with which its start is `self.start + shift`,
/// its end is `self.end + shift`
#[inline]
pub fn shift<P: Into<BytePos>>(&self, shift: P) -> Self {
let shift = shift.into();
ByteRange {
start: self.start + shift,
end: self.end + shift,
}
}
/// convert the range to `std::ops::Range`
#[inline]
pub fn to_range(&self) -> Range<usize> {
self.start.0..self.end.0
}
}
impl PartialEq<BytePos> for ByteRange {
fn eq(&self, other: &BytePos) -> bool {
self.contains(*other)
}
}
impl PartialOrd<BytePos> for ByteRange {
fn partial_cmp(&self, other: &BytePos) -> Option<Ordering> {
if *other < self.start {
Some(Ordering::Greater)
} else if *other >= self.end {
Some(Ordering::Less)
} else {
Some(Ordering::Equal)
}
}
}
impl From<source_map::Span> for ByteRange {
fn from(span: source_map::Span) -> Self {
let (lo, hi) = ast::destruct_span(span);
ByteRange::new(lo, hi)
}
}
impl fmt::Debug for ByteRange {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
write!(f, "ByteRange({}..{})", self.start.0, self.end.0)
}
}
/// Row and Column position in a file
// for backward compatibility, we use 1-index row and 0-indexed column here
#[derive(Clone, Copy, Debug, Hash, PartialEq, Eq, PartialOrd, Ord)]
pub struct Coordinate {
pub row: rls_span::Row<rls_span::OneIndexed>,
pub col: rls_span::Column<rls_span::ZeroIndexed>,
}
impl Coordinate {
/// construct new Coordinate
pub fn new(row: u32, col: u32) -> Self {
Coordinate {
row: rls_span::Row::<rls_span::OneIndexed>::new_one_indexed(row),
col: rls_span::Column::<rls_span::ZeroIndexed>::new_zero_indexed(col),
}
}
/// start point of the file
pub fn start() -> Self {
Coordinate::new(1, 0)
}
}
/// Context, source, and etc. for detected completion or definition
#[derive(Clone, PartialEq)]
pub struct Match {
pub matchstr: String,
pub filepath: path::PathBuf,
pub point: BytePos,
pub coords: Option<Coordinate>,
pub local: bool,
pub mtype: MatchType,
pub contextstr: String,
pub docs: String,
}
impl Match {
/// Checks if two matches can be considered the same for deduplication purposes.
///
/// This could be the basis for a `PartialEq` implementation in the future,
/// but in the interest of minimizing the crate's public API surface it's exposed
/// as a private method for now.
fn is_same_as(&self, other: &Match) -> bool {
self.point == other.point
&& self.matchstr == other.matchstr
&& self.filepath == other.filepath
}
pub(crate) fn to_generics(&self) -> Option<&GenericsArgs> {
match &self.mtype {
MatchType::Struct(gen_arg) | MatchType::Enum(gen_arg) => Some(gen_arg.as_ref()),
MatchType::Method(gen_arg) => gen_arg.as_ref().map(AsRef::as_ref),
_ => None,
}
}
pub(crate) fn into_generics(self) -> Option<GenericsArgs> {
match self.mtype {
MatchType::Struct(gen_arg) | MatchType::Enum(gen_arg) => Some(*gen_arg),
MatchType::Method(gen_arg) => gen_arg.map(|x| *x),
_ => None,
}
}
pub(crate) fn generics(&self) -> impl Iterator<Item = &TypeParameter> {
let opt = match self.mtype {
MatchType::Struct(ref gen_arg) | MatchType::Enum(ref gen_arg) => Some(gen_arg),
MatchType::Method(ref gen_arg) => gen_arg.as_ref(),
_ => None,
};
opt.into_iter().flat_map(|gen_arg| gen_arg.args())
}
pub(crate) fn resolved_generics(&self) -> impl Iterator<Item = &Ty> {
let opt = match self.mtype {
MatchType::Struct(ref gen_arg) | MatchType::Enum(ref gen_arg) => Some(gen_arg),
MatchType::Method(ref gen_arg) => gen_arg.as_ref(),
_ => None,
};
opt.into_iter()
.flat_map(|gen_arg| gen_arg.args())
.filter_map(|ty_param| ty_param.resolved.as_ref())
}
pub(crate) fn resolve_generics(&mut self, types: &[Ty]) {
match self.mtype {
MatchType::Struct(ref mut gen_arg) | MatchType::Enum(ref mut gen_arg) => {
gen_arg.apply_types(types);
}
_ => {}
};
}
// currently we can't resolve method's type parameter
pub(crate) fn generics_mut(&mut self) -> impl Iterator<Item = &mut TypeParameter> {
let opt = match &mut self.mtype {
MatchType::Struct(gen_arg) | MatchType::Enum(gen_arg) => Some(&mut **gen_arg),
_ => None,
};
opt.into_iter().flat_map(|gen_arg| gen_arg.args_mut())
}
}
/// The cursor position used by public search methods
#[derive(Debug, Clone, Copy)]
pub enum Location {
/// A byte offset in the file
Point(BytePos),
/// 1-based line and column indices.
Coords(Coordinate),
}
impl From<BytePos> for Location {
fn from(val: BytePos) -> Location {
Location::Point(val)
}
}
impl From<usize> for Location {
fn from(val: usize) -> Location {
Location::Point(BytePos(val))
}
}
impl From<Coordinate> for Location {
fn from(val: Coordinate) -> Location {
Location::Coords(val)
}
}
/// Internal cursor methods
pub trait LocationExt {
fn to_point(&self, src: &RawSource) -> Option<BytePos>;
fn to_coords(&self, src: &RawSource) -> Option<Coordinate>;
}
impl LocationExt for Location {
fn to_point(&self, src: &RawSource) -> Option<BytePos> {
match *self {
Location::Point(val) => Some(val),
Location::Coords(ref coords) => src.coords_to_point(coords),
}
}
fn to_coords(&self, src: &RawSource) -> Option<Coordinate> {
match *self {
Location::Coords(val) => Some(val),
Location::Point(point) => src.point_to_coords(point),
}
}
}
impl fmt::Debug for Match {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
write!(
f,
"Match [{:?}, {:?}, {:?}, {:?}, {:?}, |{}|]",
self.matchstr,
self.filepath.display(),
self.point,
self.local,
self.mtype,
self.contextstr
)
}
}
#[derive(Clone, PartialEq)]
pub struct Scope {
pub filepath: path::PathBuf,
pub point: BytePos,
}
impl Scope {
pub fn new(path: path::PathBuf, pos: BytePos) -> Self {
Scope {
filepath: path,
point: pos,
}
}
pub fn from_match(m: &Match) -> Scope {
Scope {
filepath: m.filepath.clone(),
point: m.point,
}
}
}
impl fmt::Debug for Scope {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
write!(f, "Scope [{:?}, {:?}]", self.filepath.display(), self.point)
}
}
#[derive(Clone, Debug)]
pub struct RawSource {
pub code: String,
pub lines: RefCell<Vec<ByteRange>>,
}
impl RawSource {
pub fn new(s: String) -> Self {
RawSource {
code: s,
lines: Default::default(),
}
}
fn cache_lineoffsets(&self) {
if self.lines.borrow().len() != 0 {
return;
}
let mut before = 0;
*self.lines.borrow_mut() = self
.code
.split('\n')
.map(|line| {
let len = line.len() + 1;
let res = ByteRange::new(before, before + len);
before += len;
res
})
.collect();
}
pub fn coords_to_point(&self, coords: &Coordinate) -> Option<BytePos> {
self.cache_lineoffsets();
self.lines
.borrow()
.get(coords.row.zero_indexed().0 as usize)
.and_then(|&range| {
let col = coords.col.0 as usize;
if col < range.len() {
Some(range.start + col.into())
} else {
None
}
})
}
pub fn point_to_coords(&self, point: BytePos) -> Option<Coordinate> {
self.cache_lineoffsets();
let lines = self.lines.borrow();
lines
.binary_search_by(|range| range.partial_cmp(&point).unwrap())
.ok()
.map(|idx| Coordinate::new(idx as u32 + 1, (point - lines[idx].start).0 as u32))
}
}
#[derive(Clone, Debug)]
pub struct MaskedSource {
pub code: String,
}
#[derive(Clone, Copy, Debug)]
pub struct Src<'c> {
pub src: &'c MaskedSource,
pub range: ByteRange,
}
impl MaskedSource {
pub fn new(src: &str) -> MaskedSource {
let idx: Vec<_> = codecleaner::code_chunks(&src).collect();
let code = scopes::mask_comments(src, &idx);
MaskedSource { code }
}
pub fn as_src(&self) -> Src<'_> {
self.get_src_from_start(BytePos::ZERO)
}
pub fn get_src_from_start(&self, new_start: BytePos) -> Src<'_> {
Src {
src: self,
range: ByteRange::new(new_start, self.len().into()),
}
}
}
pub struct MatchIter<'c> {
session: &'c Session<'c>,
matches: vec::IntoIter<Match>,
}
impl<'c> Iterator for MatchIter<'c> {
type Item = Match;
fn next(&mut self) -> Option<Match> {
self.matches.next().map(|mut m| {
if m.coords.is_none() {
let point = m.point;
let src = self.session.load_raw_file(m.filepath.as_path());
m.coords = src.point_to_coords(point);
}
m
})
}
}
#[test]
fn coords_to_point_works() {
let src = "
fn myfn() {
let a = 3;
print(a);
}";
let src = RawSource::new(src.into());
assert_eq!(
src.coords_to_point(&Coordinate::new(3, 5)),
Some(BytePos(18))
);
}
#[test]
fn coords_to_point_lf_newline() {
let src = "\n\
fn myfn() {\n\
let a = 3;\n\
print(a);\n\
}\n";
let src = RawSource::new(src.into());
assert_eq!(
src.coords_to_point(&Coordinate::new(3, 5)),
Some(BytePos(18))
);
}
#[test]
fn coords_to_point_crlf_newline() {
let src = "\r\n\
fn myfn() {\r\n\
let a = 3;\r\n\
print(a);\r\n\
}\r\n";
let src = RawSource::new(src.into());
assert_eq!(
src.coords_to_point(&Coordinate::new(3, 5)),
Some(BytePos(20))
);
}
#[test]
fn test_point_to_coords() {
let src = "
fn myfn(b:usize) {
let a = 3;
if b == 12 {
let a = 24;
do_something_with(a);
}
do_something_with(a);
}
";
fn round_trip_point_and_coords(src: &str, lineno: usize, charno: usize) {
let raw_src = RawSource::new(src.to_owned());
let point = raw_src
.coords_to_point(&Coordinate::new(lineno as u32, charno as u32))
.unwrap();
let coords = raw_src.point_to_coords(point).unwrap();
assert_eq!(coords, Coordinate::new(lineno as u32, charno as u32));
}
round_trip_point_and_coords(src, 4, 5);
}
impl<'c> Src<'c> {
pub fn iter_stmts(&self) -> Fuse<StmtIndicesIter<'_>> {
StmtIndicesIter::from_parts(self)
}
pub fn shift_start(&self, shift: BytePos) -> Src<'c> {
Src {
src: self.src,
range: ByteRange::new(self.range.start + shift, self.range.end),
}
}
pub fn change_length(&self, new_length: BytePos) -> Src<'c> {
Src {
src: self.src,
range: ByteRange::new(self.range.start, self.range.start + new_length),
}
}
pub fn shift_range(&self, new_range: ByteRange) -> Src<'c> {
Src {
src: self.src,
range: new_range.shift(self.range.start),
}
}
}
pub struct RangedRawSrc {
inner: Rc<RawSource>,
range: ByteRange,
}
impl Deref for RangedRawSrc {
type Target = str;
fn deref(&self) -> &str {
&self.inner.code[self.range.to_range()]
}
}
impl Deref for RawSource {
type Target = str;
fn deref(&self) -> &str {
&self.code
}
}
impl Deref for MaskedSource {
type Target = str;
fn deref(&self) -> &str {
&self.code
}
}
impl<'c> Deref for Src<'c> {
type Target = str;
fn deref(&self) -> &str {
&self.src.code[self.range.to_range()]
}
}
/// Caches file contents for re-use between sessions.
///
/// The file cache is an opaque blob outside of racer which contains maps of loaded and masked
/// files.
pub struct FileCache {
/// raw source for cached files
raw_map: RefCell<HashMap<path::PathBuf, Rc<RawSource>>>,
/// masked source for cached files
///
/// a version with comments and strings replaced by spaces, so that they
/// aren't found when scanning the source for signatures.
masked_map: RefCell<HashMap<path::PathBuf, Rc<MaskedSource>>>,
/// The file loader
pub(crate) loader: Box<dyn FileLoader>,
}
/// Used by the FileCache for loading files
///
/// Implement one of these and pass it to `FileCache::new()` to override Racer's
/// file loading behavior.
pub trait FileLoader {
/// Load a single file
fn load_file(&self, path: &path::Path) -> io::Result<String>;
}
/// Provide a blanket impl for Arc<T> since Rls uses that
impl<T: FileLoader> FileLoader for ::std::sync::Arc<T> {
fn load_file(&self, path: &path::Path) -> io::Result<String> {
(&self as &T).load_file(path)
}
}
/// The default file loader
///
/// Private since this shouldn't be needed outside of racer
struct DefaultFileLoader;
impl FileLoader for DefaultFileLoader {
fn load_file(&self, path: &path::Path) -> io::Result<String> {
let mut rawbytes = Vec::new();
let mut f = File::open(path)?;
f.read_to_end(&mut rawbytes)?;
// skip BOM bytes, if present
if rawbytes.len() > 2 && rawbytes[0..3] == [0xEF, 0xBB, 0xBF] {
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))
}
}
}
impl Default for FileCache {
fn default() -> FileCache {
FileCache::new(DefaultFileLoader)
}
}
impl FileCache {
/// Create a new file cache
///
/// In order to load files into the cache, please see
/// [`Session::cache_file_contents()`]
///
/// [`Session::cache_file_contents()`]: struct.Session.html#method.cache_file_contents
pub fn new<L: FileLoader + 'static>(loader: L) -> FileCache {
FileCache {
raw_map: RefCell::new(HashMap::new()),
masked_map: RefCell::new(HashMap::new()),
loader: Box::new(loader),
}
}
/// Remove specific files from the cache
///
/// Returns true if a file was removed
pub fn remove_file<P: AsRef<path::Path>>(&self, path: &P) -> bool {
let path = path.as_ref();
let mut raw = self.raw_map.borrow_mut();
let mut masked = self.masked_map.borrow_mut();
raw.remove(path).is_some() || masked.remove(path).is_some()
}
/// Add/Replace a file in both versions.
fn cache_file_contents<P, T>(&self, filepath: P, buf: T)
where
T: Into<String>,
P: Into<path::PathBuf>,
{
let pathbuf = filepath.into();
let src = buf.into();
let masked_src = MaskedSource::new(&src);
self.raw_map
.borrow_mut()
.insert(pathbuf.clone(), Rc::new(RawSource::new(src)));
self.masked_map
.borrow_mut()
.insert(pathbuf, Rc::new(masked_src));
}
fn load_file(&self, filepath: &path::Path) -> Rc<RawSource> {
if let Some(src) = self.raw_map.borrow().get(filepath) {
return src.clone();
}
// nothing found, insert into cache
// Ugh, really need handle results on all these methods :(
let source = self
.loader
.load_file(filepath)
.expect(&format!("Failed load file {:?}", filepath));
let source = Rc::new(RawSource::new(source));
self.raw_map
.borrow_mut()
.insert(filepath.to_path_buf(), Rc::clone(&source));
source
}
fn load_file_and_mask_comments(&self, filepath: &path::Path) -> Rc<MaskedSource> {
if let Some(src) = self.masked_map.borrow().get(filepath) {
return src.clone();
}
// nothing found, insert into cache
let src = self.load_file(filepath);
let msrc = Rc::new(MaskedSource::new(&src.code));
self.masked_map
.borrow_mut()
.insert(filepath.to_path_buf(), msrc.clone());
msrc
}
}
/// Private methods for the Session type
pub trait SessionExt {
/// Request that a file is loaded into the cache
///
/// This API is unstable and should not be used outside of Racer
fn load_raw_file(&self, _: &path::Path) -> Rc<RawSource>;
/// ranged version of load_raw_file
fn load_raw_src_ranged(&self, src: &Src<'_>, _: &path::Path) -> RangedRawSrc;
/// Request that a file is loaded into the cache with comments masked
///
/// This API is unstable and should not be used outside of Racer
fn load_source_file(&self, _: &path::Path) -> Rc<MaskedSource>;
}
/// Context for a Racer operation
pub struct Session<'c> {
/// Cache for files
///
/// The file cache is used within a session to prevent multiple reads. It is
/// borrowed here in order to support reuse across Racer operations.
cache: &'c FileCache,
/// Cache for generic impls
pub generic_impls: RefCell<HashMap<(path::PathBuf, BytePos), Vec<Rc<ImplHeader>>>>,
pub project_model: Box<dyn ProjectModelProvider + 'c>,
}
impl<'c> fmt::Debug for Session<'c> {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
write!(f, "Session {{ .. }}")
}
}
impl<'c> Session<'c> {
/// Create a Session for use in Racer operations
///
/// * `cache` is a reference to a `FileCache`. It's take by reference for
/// use across racer operations.
///
/// # Examples
///
/// ```
/// extern crate racer;
///
/// let cache = racer::FileCache::default();
/// let session = racer::Session::new(&cache, None);
/// ```
///
/// [`FileCache`]: struct.FileCache.html
#[cfg(feature = "metadata")]
pub fn new(cache: &'c FileCache, project_path: Option<&path::Path>) -> Session<'c> {
let project_model = crate::metadata::project_model(project_path);
Session::with_project_model(cache, project_model)
}
pub fn with_project_model(
cache: &'c FileCache,
project_model: Box<dyn ProjectModelProvider + 'c>,
) -> Session<'c> {
Session {
cache,
generic_impls: Default::default(),
project_model,
}
}
/// Specify the contents of a file to be used in completion operations
///
/// The path to the file and the file's contents must both be specified.
///
/// # Examples
///
/// ```
/// extern crate racer;
///
/// let cache = racer::FileCache::default();
/// let session = racer::Session::new(&cache, None);
///
/// session.cache_file_contents("foo.rs", "pub struct Foo;\\n");
/// ```
pub fn cache_file_contents<T, P>(&self, filepath: P, buf: T)
where
T: Into<String>,
P: Into<path::PathBuf>,
{
self.cache.cache_file_contents(filepath, buf);
}
pub fn contains_file<P: AsRef<path::Path>>(&self, path: P) -> bool {
let path = path.as_ref();
let raw = self.cache.raw_map.borrow();
let masked = self.cache.masked_map.borrow();
raw.contains_key(path) && masked.contains_key(path)
}
}
impl<'c> SessionExt for Session<'c> {
fn load_raw_file(&self, filepath: &path::Path) -> Rc<RawSource> {
self.cache.load_file(filepath)
}
fn load_raw_src_ranged(&self, src: &Src<'_>, filepath: &path::Path) -> RangedRawSrc {
let inner = self.cache.load_file(filepath);
RangedRawSrc {
inner,
range: src.range,
}
}
fn load_source_file(&self, filepath: &path::Path) -> Rc<MaskedSource> {
self.cache.load_file_and_mask_comments(filepath)
}
}
/// Get the racer point of a line/character number pair for a file.
pub fn to_point<P>(coords: Coordinate, path: P, session: &Session<'_>) -> Option<BytePos>
where
P: AsRef<path::Path>,
{
Location::from(coords).to_point(&session.load_raw_file(path.as_ref()))
}
/// Get the racer point of a line/character number pair for a file.
pub fn to_coords<P>(point: BytePos, path: P, session: &Session<'_>) -> Option<Coordinate>
where
P: AsRef<path::Path>,
{
Location::from(point).to_coords(&session.load_raw_file(path.as_ref()))
}
/// Find completions for a fully qualified name like `std::io::`
///
/// Searchs are started relative to `path`.
///
/// * `query` - is the fqn to search for
/// * `path` - the directory to start searching in
/// * `session` - reference to a racer::Session
///
/// ```no_run
/// extern crate racer;
///
/// let path = std::path::Path::new(".");
/// let cache = racer::FileCache::default();
/// let session = racer::Session::new(&cache, Some(path));
///
/// let m = racer::complete_fully_qualified_name(
/// "std::fs::canon",
/// &path,
/// &session
/// ).next().unwrap();
///
/// assert_eq!(&m.matchstr[..], "canonicalize");
/// assert_eq!(m.mtype, racer::MatchType::Function);
/// ```
#[inline]
pub fn complete_fully_qualified_name<'c, S, P>(
query: S,
path: P,
session: &'c Session<'_>,
) -> MatchIter<'c>
where
S: AsRef<str>,
P: AsRef<path::Path>,
{
let mut matches = complete_fully_qualified_name_(query.as_ref(), path.as_ref(), session);
matches.dedup_by(|a, b| a.is_same_as(b));
MatchIter {
matches: matches.into_iter(),
session,
}
}
/// Actual implementation without generic bounds
fn complete_fully_qualified_name_(
query: &str,
path: &path::Path,
session: &Session<'_>,
) -> Vec<Match> {
let p: Vec<&str> = query.split("::").collect();
let mut matches = Vec::new();
for m in nameres::do_file_search(p[0], path, session) {
if p.len() == 1 {
matches.push(m);
} else {
let external_search_matches = nameres::do_external_search(
&p[1..],
&m.filepath,
m.point,
SearchType::StartsWith,
Namespace::Path,
&session,
);
for m in external_search_matches {
matches.push(m);
}
}
}
matches
}
/// Search for completion at position in a file
///
/// * `src` - the file contents to search in
/// * `filepath` - path to file containing `src`
/// * `pos` - byte offset in file with path/expr to complete
/// * `session` - a racer::Session
///
/// # Examples
///
/// ```
/// extern crate racer;
///
/// # fn main() {
/// let src = "
/// fn apple() {
/// }
///
/// fn main() {
/// let b = ap
/// }";
///
/// println!("{:?}", src);
///
/// let cache = racer::FileCache::default();
/// let session = racer::Session::new(&cache, None);
///
/// session.cache_file_contents("lib.rs", src);
///
/// let got = racer::complete_from_file("lib.rs", racer::Location::from(43), &session)
/// .nth(0).unwrap();
/// assert_eq!("apple", got.matchstr);
/// assert_eq!(got.mtype, racer::MatchType::Function);
///
/// # }
/// ```
pub fn complete_from_file<'c, P, C>(
filepath: P,
cursor: C,
session: &'c Session<'_>,
) -> MatchIter<'c>
where
P: AsRef<path::Path>,
C: Into<Location>,
{
let mut matches = complete_from_file_(filepath.as_ref(), cursor.into(), session);
matches.sort_by(|a, b| a.matchstr.cmp(&b.matchstr).then(a.point.cmp(&b.point)));
matches.dedup_by(|a, b| a.is_same_as(b));
MatchIter {
matches: matches.into_iter(),
session,
}
}
fn complete_from_file_(
filepath: &path::Path,
cursor: Location,
session: &Session<'_>,
) -> Vec<Match> {
let src = session.load_source_file(filepath);
let raw_src = session.load_raw_file(filepath);
let src_text = &src.as_src()[..];
// TODO return result
let pos = match cursor.to_point(&raw_src) {
Some(pos) => pos,
None => {
debug!("Failed to convert cursor to point");
return Vec::new();
}
};
let start = scopes::get_start_of_search_expr(src_text, pos);
let expr = &src_text[start.0..pos.0];
let (contextstr, searchstr, completetype) = scopes::split_into_context_and_completion(expr);
debug!(
"{:?}: contextstr is |{}|, searchstr is |{}|",
completetype, contextstr, searchstr
);
let mut out = Vec::new();
match completetype {
CompletionType::Path => {
let (stmtstart, stmt) = &scopes::get_current_stmt(src.as_src(), pos);
debug!("Complete path with stmt: {:?}", stmt);
// when in the function ident position, only look for methods
// from a trait to complete.
if util::in_fn_name(stmt) {
trace!("Path is in fn declaration: `{}`", expr);
return nameres::resolve_method(
pos,
src.as_src(),
expr,
filepath,
SearchType::StartsWith,
session,
&ImportInfo::default(),
);
}
let (path, namespace) = if let Some(use_start) = scopes::use_stmt_start(stmt) {
let path = scopes::construct_path_from_use_tree(&stmt[use_start.0..]);
(path, Namespace::Path)
} else if scopes::is_extern_crate(stmt) {
return fileres::search_crate_names(
searchstr,
SearchType::StartsWith,
filepath,
false,
session,
);
} else if let Some(str_path) = scopes::is_in_struct_ctor(src.as_src(), *stmtstart, pos)
{
let path = scopes::expr_to_path(&src[str_path.to_range()]).0;
return nameres::get_struct_fields(
&path,
searchstr,
filepath,
pos,
SearchType::StartsWith,
session,
);
} else {
scopes::expr_to_path(expr)
};
debug!("path: {:?}, prefix: {:?}", path, path.prefix);
out.extend(nameres::resolve_path(
&path,
filepath,
pos,
SearchType::StartsWith,
namespace,
session,
&ImportInfo::default(),
));
}
CompletionType::Field => {
let context = ast::get_type_of(contextstr.to_owned(), filepath, pos, session);
debug!("complete_from_file context is {:?}", context);
if let Some(ty) = context {
out.extend(nameres::get_field_matches_from_ty(
ty,
searchstr,
SearchType::StartsWith,
session,
));
}
}
}
out
}
/// Finds if the statement where cursor lies is a `use` statement.
///
/// # Examples
///
/// ```
/// extern crate racer;
/// extern crate env_logger;
///
///
/// # fn main() {
/// let _ = env_logger::init();
/// let cache = racer::FileCache::default();
/// let session = racer::Session::new(&cache, None);
///
/// // This is the file where we request completion from
/// let src = stringify! {
/// use sub::foo;
/// use sub::{
/// bar
/// };
/// pub(crate) use sub::baz;
/// };
///
/// // Load files into cache to prevent trying to read from disk
/// session.cache_file_contents("lib.rs", src);
///
/// assert_eq!(racer::is_use_stmt("lib.rs", racer::Location::from(9), &session), true);
/// assert_eq!(racer::is_use_stmt("lib.rs", racer::Location::from(28), &session), true);
/// assert_eq!(racer::is_use_stmt("lib.rs", racer::Location::from(5000), &session), false);
/// # }
/// ```
pub fn is_use_stmt<P, C>(file_path: P, cursor: C, session: &Session<'_>) -> bool
where
P: AsRef<path::Path>,
C: Into<Location>,
{
let file_path = file_path.as_ref();
let src = session.load_source_file(file_path);
let raw_src = session.load_raw_file(file_path);
let pos = match cursor.into().to_point(&raw_src) {
Some(pos) => pos,
None => return false,
};
if src.bytes().len() <= pos.0 {
return false;
}
let line = &scopes::get_current_stmt(src.as_src(), pos).1;
scopes::use_stmt_start(line).is_some()
}
/// Find the definition for item at given a file, source, and cursor index
///
/// # Examples
///
/// ```
/// extern crate racer;
/// extern crate env_logger;
///
/// use std::path::Path;
///
/// # fn main() {
/// let _ = env_logger::init();
/// let cache = racer::FileCache::default();
/// let session = racer::Session::new(&cache, None);
///
/// // This is the file where we request completion from
/// let src = r"
/// mod sub;
/// use sub::foo;
/// fn main() {
/// foo();
/// };
/// ";
///
/// // This is the submodule where the definition is found
/// let sub = r"pub fn foo() {}";
///
/// // Load files into cache to prevent trying to read from disk
/// session.cache_file_contents("sub.rs", sub);
/// session.cache_file_contents("lib.rs", src);
///
/// // Search for the definition. 52 is the byte offset in `src`.
/// // Specifically, this asks for the definition of `foo()`.
/// let m = racer::find_definition("lib.rs", racer::Location::from(52), &session)
/// .expect("find definition returns a match");
///
/// // Should have found definition in the "sub.rs" file
/// assert_eq!(m.filepath, Path::new("sub.rs"));
/// // The definition should be for foo
/// assert_eq!(&m.matchstr[..], "foo");
/// // The definition should be a function
/// assert_eq!(m.mtype, racer::MatchType::Function);
/// # }
/// ```
pub fn find_definition<P, C>(filepath: P, cursor: C, session: &Session<'_>) -> Option<Match>
where
P: AsRef<path::Path>,
C: Into<Location>,
{
find_definition_(filepath.as_ref(), cursor.into(), session).map(|mut m| {
if m.coords.is_none() {
let point = m.point;
let src = session.load_raw_file(m.filepath.as_path());
m.coords = src.point_to_coords(point);
}
m
})
}
pub fn find_definition_(
filepath: &path::Path,
cursor: Location,
session: &Session<'_>,
) -> Option<Match> {
let src = session.load_source_file(filepath);
let src_txt = &src[..];
// TODO return result
let pos = match cursor.to_point(&session.load_raw_file(filepath)) {
Some(pos) => pos,
None => {
debug!("Failed to convert cursor to point");
return None;
}
};
// Make sure `src` is in the cache
let range = scopes::expand_search_expr(src_txt, pos);
let expr = &src[range.to_range()];
let (contextstr, searchstr, completetype) = scopes::split_into_context_and_completion(expr);
debug!(
"find_definition_ for |{:?}| |{:?}| {:?}",
contextstr, searchstr, completetype
);
match completetype {
CompletionType::Path => {
let (stmtstart, stmt) = &scopes::get_current_stmt(src.as_src(), range.end);
let (path, namespace) = if let Some(use_start) = scopes::use_stmt_start(stmt) {
let path = scopes::construct_path_from_use_tree(&stmt[use_start.0..]);
(path, Namespace::Path)
} else if let Some(str_path) = scopes::is_in_struct_ctor(src.as_src(), *stmtstart, pos)
{
let path = scopes::expr_to_path(&src[str_path.to_range()]).0;
return nameres::get_struct_fields(
&path,
searchstr,
filepath,
pos,
SearchType::StartsWith,
session,
)
.into_iter()
.next();
} else {
scopes::expr_to_path(expr)
};
debug!("[find_definition_] Path: {:?}", path);
nameres::resolve_path(
&path,
filepath,
pos,
SearchType::ExactMatch,
namespace,
session,
&ImportInfo::default(),
)
.into_iter()
.nth(0)
}
CompletionType::Field => {
let context = ast::get_type_of(contextstr.to_owned(), filepath, pos, session);
debug!("context is {:?}", context);
let only_method = src[range.end.0..].starts_with('(');
context.and_then(|ty| {
nameres::get_field_matches_from_ty(ty, searchstr, SearchType::ExactMatch, session)
.into_iter()
.filter(|m| !only_method || m.mtype.is_function())
.next()
})
}
}
}
#[cfg(test)]
mod tests {
use super::FileCache;
use super::{Session, SessionExt};
use std::path::Path;
#[test]
fn overwriting_cached_files() {
let src1 = "src1";
let src2 = "src2";
let src3 = "src3";
let src4 = "src4";
// Need session and path to cache files
let path = Path::new("not_on_disk");
let cache = FileCache::default();
// Cache contents for a file and assert that load_file and load_file_and_mask_comments return
// the newly cached contents.
macro_rules! cache_and_assert {
($src: ident) => {{
let session = Session::new(&cache, Some(path));
session.cache_file_contents(path, $src);
assert_eq!($src, &session.load_raw_file(path)[..]);
assert_eq!($src, &session.load_source_file(path).code[..]);
}};
}
// Check for all srcN
cache_and_assert!(src1);
cache_and_assert!(src2);
cache_and_assert!(src3);
cache_and_assert!(src4);
}
}