<commit_before>// Small functions of utility
use std::cmp;
use std::path;
use std::rc::Rc;

use core::{IndexedSource, Session, SessionExt, Location, LocationExt, Point};
use core::SearchType::{self, ExactMatch, StartsWith};

#[cfg(unix)]
pub const PATH_SEP: char = ':';
#[cfg(windows)]
pub const PATH_SEP: char = ';';

pub fn is_pattern_char(c: char) -> bool {
    c.is_alphanumeric() || c.is_whitespace() || (c == '_') || (c == ':') || (c == '.')
}

pub fn is_search_expr_char(c: char) -> bool {
    c.is_alphanumeric() || (c == '_') || (c == ':') || (c == '.')
}

pub fn is_ident_char(c: char) -> bool {
    c.is_alphanumeric() || (c == '_') || (c == '!')
}

/// Searches for `needle` as a standalone identifier in `haystack`. To be considered a match,
/// the `needle` must occur either at the beginning of `haystack` or after a non-identifier
/// character.
pub fn txt_matches(stype: SearchType, needle: &str, haystack: &str) -> bool {
    match stype {
        ExactMatch => {
            let n_len = needle.len();
            let h_len = haystack.len();

            if n_len == 0 {
                return true;
            }

            // PD: switch to use .match_indices() when that stabilizes
            let mut n=0;
            while let Some(n1) = haystack[n..].find(needle) {
                n += n1;
                if (n == 0  || !is_ident_char(char_at(haystack, n-1))) &&
                    (n+n_len == h_len || !is_ident_char(char_at(haystack, n+n_len))) {
                    return true;
                }
                n += 1;
            }
            false
        },
        StartsWith => {
            if needle.is_empty() {
                return true;
            }

            // PD: switch to use .match_indices() when that stabilizes
            let mut n=0;
            while let Some(n1) = haystack[n..].find(needle) {
                n += n1;
                if n == 0  || !is_ident_char(char_at(haystack, n-1)) {
                    return true;
                }
                n += 1;
            }
            false
        }
    }
}

pub fn symbol_matches(stype: SearchType, searchstr: &str, candidate: &str) -> bool {
   match stype {
        ExactMatch => searchstr == candidate,
        StartsWith => candidate.starts_with(searchstr)
    }
}

/// Try to valid if the given scope contains a valid closure arg scope.
pub fn closure_valid_arg_scope(scope_src: &str) -> Option<(usize, usize, &str)> {
    // Try to find the left and right pipe, if one or both are not present, this is not a valid
    // closure definition
    let left_pipe = if let Some(pos) = scope_src.find('|') { pos } else { return None; };
    let rest_scope = &scope_src[left_pipe + 1..];
    let right_pipe = if let Some(pos) = rest_scope.find('|') {
        left_pipe + 1 + pos
    } else {
        return None;
    };

    let pipe_scope = &scope_src[left_pipe..right_pipe+1];

    // For each '{' increase the counter by one and for each '}' decrease the counter by one
    // If we have a equal number of curly brackets, we should get 0 as result
    let curly_brackets = pipe_scope.chars().fold(0,
                           |count, c| {
                               if c == '{' {
                                   count + 1
                               } else if c == '}' {
                                   count - 1
                               } else {
                                   count
                               }
                           });

    // If we found an unequal number of curly brackets in the scope, this can not be a valid
    // closure definition
    if curly_brackets != 0 {
        return None;
    }

    // If we find a ';' --> no closure definition
    if pipe_scope.contains(';') {
        return None;
    }

    Some((left_pipe, right_pipe, pipe_scope))
}

// pub fn get_backtrace() -> String {
//     let mut m = std::old_io::MemWriter::new();
//     let s = std::rt::backtrace::write(&mut m)
//         .ok().map_or("NO backtrace".to_string(),
//                      |_| String::from_utf8_lossy(m.get_ref()).to_string());
//     return s;
// }

#[test]
fn txt_matches_matches_stuff() {
    assert_eq!(true, txt_matches(ExactMatch, "Vec","Vec"));
    assert_eq!(true, txt_matches(StartsWith, "Vec","Vector"));
    assert_eq!(false, txt_matches(ExactMatch, "Vec","use Vector"));
    assert_eq!(true, txt_matches(StartsWith, "Vec","use Vector"));
    assert_eq!(false, txt_matches(StartsWith, "Vec","use aVector"));
    assert_eq!(true, txt_matches(ExactMatch, "Vec","use Vec"));
}

#[test]
fn txt_matches_matches_methods() {
    assert_eq!(true, txt_matches(StartsWith, "do_st", "fn do_stuff"));
    assert_eq!(true, txt_matches(StartsWith, "do_st", "pub fn do_stuff"));
    assert_eq!(true, txt_matches(StartsWith, "do_st", "pub(crate) fn do_stuff"));
    assert_eq!(true, txt_matches(StartsWith, "do_st", "pub(in codegen) fn do_stuff"));
}


/// Given a string and index, return span of identifier
///
/// `pos` is coerced to be within `s`. Note that `expand_ident` only backtracks.
/// If the provided `pos` is in the middle of an identifier, the returned
/// `(start, end)` will have `end` = `pos`.
///
/// # Examples
///
/// ```
/// extern crate racer;
///
/// let src = "let x = this_is_an_identifier;";
/// let pos = racer::Location::Point(29);
/// let path = "lib.rs";
///
/// let cache = racer::FileCache::default();
/// let session = racer::Session::new(&cache);
///
/// session.cache_file_contents(path, src);
///
/// let expanded = racer::expand_ident(path, pos, &session).unwrap();
/// assert_eq!("this_is_an_identifier", expanded.ident());
/// ```
pub fn expand_ident<P, C>(
    filepath: P,
    cursor: C,
    session: &Session
) -> Option<ExpandedIdent>
    where P: AsRef<::std::path::Path>,
          C: Into<Location>
{
    let cursor = cursor.into();
    let indexed_source = session.load_file(filepath.as_ref());
    let (start, pos) = {
        let s = &indexed_source.code[..];
        let pos = match cursor.to_point(&indexed_source) {
            Some(pos) => pos,
            None => {
                debug!("Failed to convert cursor to point");
                return None;
            }
        };

        // TODO: Would this better be an assertion ? Why are out-of-bound values getting here ?
        // They are coming from the command-line, question is, if they should be handled beforehand
        // clamp pos into allowed range
        let pos = cmp::min(s.len(), pos);
        let sb = &s[..pos];
        let mut start = pos;

        // backtrack to find start of word
        for (i, c) in sb.char_indices().rev() {
            if !is_ident_char(c) {
                break;
            }
            start = i;
        }

        (start, pos)
    };

    Some(ExpandedIdent {
        src: indexed_source,
        start: start,
        pos: pos,
    })
}

pub struct ExpandedIdent {
    src: Rc<IndexedSource>,
    start: Point,
    pos: Point,
}

impl ExpandedIdent {
    pub fn ident(&self) -> &str {
        &self.src.code[self.start..self.pos]
    }

    pub fn start(&self) -> Point {
        self.start
    }

    pub fn pos(&self) -> Point {
        self.pos
    }
}

pub fn find_ident_end(s: &str, pos: Point) -> Point {
    // find end of word
    let sa = &s[pos..];
    for (i, c) in sa.char_indices() {
        if !is_ident_char(c) {
            return pos + i;
        }
    }
    s.len()
}

#[test]
fn find_ident_end_ascii() {
    assert_eq!(5, find_ident_end("ident", 0));
    assert_eq!(6, find_ident_end("(ident)", 1));
    assert_eq!(17, find_ident_end("let an_identifier = 100;", 4));
}

#[test]
fn find_ident_end_unicode() {
    assert_eq!(7, find_ident_end("num_µs", 0));
    assert_eq!(10, find_ident_end("ends_in_µ", 0));
}

// PD: short term replacement for .char_at() function. Should be replaced once
// that stabilizes
pub fn char_at(src: &str, i: usize) -> char {
    src[i..].chars().next().unwrap()
}

/// Error type returned from [`check_rust_src_env_var()`]
///
/// [`check_rust_src_env_var()`]: fn.check_rust_src_env_var.html
#[derive(Debug, PartialEq)]
pub enum RustSrcPathError {
    Missing,
    DoesNotExist(path::PathBuf),
    NotRustSourceTree(path::PathBuf),
}

impl ::std::error::Error for RustSrcPathError {
    fn cause(&self) -> Option<&::std::error::Error> {
        None
    }

    fn description(&self) -> &str {
        match *self {
            RustSrcPathError::Missing => "RUSTC_SRC_PATH not set or not found in sysroot",
            RustSrcPathError::DoesNotExist(_) => "RUSTC_SRC_PATH does not exist on file system",
            RustSrcPathError::NotRustSourceTree(_) => "RUSTC_SRC_PATH isn't a rustc source tree",
        }
    }
}

impl ::std::fmt::Display for RustSrcPathError {
    fn fmt(&self, f: &mut ::std::fmt::Formatter) -> ::std::fmt::Result {
        match *self {
            RustSrcPathError::Missing => {
                write!(f, "RUST_SRC_PATH environment variable must be set to \
                           point to the src directory of a rust checkout. \
                           E.g. \"/home/foouser/src/rust/src\"")
            },
            RustSrcPathError::DoesNotExist(ref path) => {
                write!(f, "racer can't find the directory pointed to by the \
                           RUST_SRC_PATH variable \"{:?}\". Try using an \
                           absolute fully qualified path and make sure it \
                           points to the src directory of a rust checkout - \
                           e.g. \"/home/foouser/src/rust/src\".", path)
            },
            RustSrcPathError::NotRustSourceTree(ref path) => {
                write!(f, "Unable to find libstd under RUST_SRC_PATH. N.B. \
                           RUST_SRC_PATH variable needs to point to the *src* \
                           directory inside a rust checkout e.g. \
                           \"/home/foouser/src/rust/src\". \
                           Current value \"{:?}\"", path)
            },
        }
    }
}

fn check_rust_sysroot() -> Option<path::PathBuf> {
    use ::std::process::Command;
    let mut cmd = Command::new("rustc");
    cmd.arg("--print").arg("sysroot");

    if let Ok(output) = cmd.output() {
        if let Ok(s) = String::from_utf8(output.stdout) {
            let sysroot = path::Path::new(s.trim());
            let srcpath = sysroot.join("lib/rustlib/src/rust/src");
            if srcpath.exists() {
                return Some(srcpath);
            }
        }
    }<commit_msg>Added debug printing to explore what goes wrong in the test environment.<commit_after>// Small functions of utility
use std::cmp;
use std::path;
use std::rc::Rc;

use core::{IndexedSource, Session, SessionExt, Location, LocationExt, Point};
use core::SearchType::{self, ExactMatch, StartsWith};

#[cfg(unix)]
pub const PATH_SEP: char = ':';
#[cfg(windows)]
pub const PATH_SEP: char = ';';

pub fn is_pattern_char(c: char) -> bool {
    c.is_alphanumeric() || c.is_whitespace() || (c == '_') || (c == ':') || (c == '.')
}

pub fn is_search_expr_char(c: char) -> bool {
    c.is_alphanumeric() || (c == '_') || (c == ':') || (c == '.')
}

pub fn is_ident_char(c: char) -> bool {
    c.is_alphanumeric() || (c == '_') || (c == '!')
}

/// Searches for `needle` as a standalone identifier in `haystack`. To be considered a match,
/// the `needle` must occur either at the beginning of `haystack` or after a non-identifier
/// character.
pub fn txt_matches(stype: SearchType, needle: &str, haystack: &str) -> bool {
    match stype {
        ExactMatch => {
            let n_len = needle.len();
            let h_len = haystack.len();

            if n_len == 0 {
                return true;
            }

            // PD: switch to use .match_indices() when that stabilizes
            let mut n=0;
            while let Some(n1) = haystack[n..].find(needle) {
                n += n1;
                if (n == 0  || !is_ident_char(char_at(haystack, n-1))) &&
                    (n+n_len == h_len || !is_ident_char(char_at(haystack, n+n_len))) {
                    return true;
                }
                n += 1;
            }
            false
        },
        StartsWith => {
            if needle.is_empty() {
                return true;
            }

            // PD: switch to use .match_indices() when that stabilizes
            let mut n=0;
            while let Some(n1) = haystack[n..].find(needle) {
                n += n1;
                if n == 0  || !is_ident_char(char_at(haystack, n-1)) {
                    return true;
                }
                n += 1;
            }
            false
        }
    }
}

pub fn symbol_matches(stype: SearchType, searchstr: &str, candidate: &str) -> bool {
   match stype {
        ExactMatch => searchstr == candidate,
        StartsWith => candidate.starts_with(searchstr)
    }
}

/// Try to valid if the given scope contains a valid closure arg scope.
pub fn closure_valid_arg_scope(scope_src: &str) -> Option<(usize, usize, &str)> {
    // Try to find the left and right pipe, if one or both are not present, this is not a valid
    // closure definition
    let left_pipe = if let Some(pos) = scope_src.find('|') { pos } else { return None; };
    let rest_scope = &scope_src[left_pipe + 1..];
    let right_pipe = if let Some(pos) = rest_scope.find('|') {
        left_pipe + 1 + pos
    } else {
        return None;
    };

    let pipe_scope = &scope_src[left_pipe..right_pipe+1];

    // For each '{' increase the counter by one and for each '}' decrease the counter by one
    // If we have a equal number of curly brackets, we should get 0 as result
    let curly_brackets = pipe_scope.chars().fold(0,
                           |count, c| {
                               if c == '{' {
                                   count + 1
                               } else if c == '}' {
                                   count - 1
                               } else {
                                   count
                               }
                           });

    // If we found an unequal number of curly brackets in the scope, this can not be a valid
    // closure definition
    if curly_brackets != 0 {
        return None;
    }

    // If we find a ';' --> no closure definition
    if pipe_scope.contains(';') {
        return None;
    }

    Some((left_pipe, right_pipe, pipe_scope))
}

// pub fn get_backtrace() -> String {
//     let mut m = std::old_io::MemWriter::new();
//     let s = std::rt::backtrace::write(&mut m)
//         .ok().map_or("NO backtrace".to_string(),
//                      |_| String::from_utf8_lossy(m.get_ref()).to_string());
//     return s;
// }

#[test]
fn txt_matches_matches_stuff() {
    assert_eq!(true, txt_matches(ExactMatch, "Vec","Vec"));
    assert_eq!(true, txt_matches(StartsWith, "Vec","Vector"));
    assert_eq!(false, txt_matches(ExactMatch, "Vec","use Vector"));
    assert_eq!(true, txt_matches(StartsWith, "Vec","use Vector"));
    assert_eq!(false, txt_matches(StartsWith, "Vec","use aVector"));
    assert_eq!(true, txt_matches(ExactMatch, "Vec","use Vec"));
}

#[test]
fn txt_matches_matches_methods() {
    assert_eq!(true, txt_matches(StartsWith, "do_st", "fn do_stuff"));
    assert_eq!(true, txt_matches(StartsWith, "do_st", "pub fn do_stuff"));
    assert_eq!(true, txt_matches(StartsWith, "do_st", "pub(crate) fn do_stuff"));
    assert_eq!(true, txt_matches(StartsWith, "do_st", "pub(in codegen) fn do_stuff"));
}


/// Given a string and index, return span of identifier
///
/// `pos` is coerced to be within `s`. Note that `expand_ident` only backtracks.
/// If the provided `pos` is in the middle of an identifier, the returned
/// `(start, end)` will have `end` = `pos`.
///
/// # Examples
///
/// ```
/// extern crate racer;
///
/// let src = "let x = this_is_an_identifier;";
/// let pos = racer::Location::Point(29);
/// let path = "lib.rs";
///
/// let cache = racer::FileCache::default();
/// let session = racer::Session::new(&cache);
///
/// session.cache_file_contents(path, src);
///
/// let expanded = racer::expand_ident(path, pos, &session).unwrap();
/// assert_eq!("this_is_an_identifier", expanded.ident());
/// ```
pub fn expand_ident<P, C>(
    filepath: P,
    cursor: C,
    session: &Session
) -> Option<ExpandedIdent>
    where P: AsRef<::std::path::Path>,
          C: Into<Location>
{
    let cursor = cursor.into();
    let indexed_source = session.load_file(filepath.as_ref());
    let (start, pos) = {
        let s = &indexed_source.code[..];
        let pos = match cursor.to_point(&indexed_source) {
            Some(pos) => pos,
            None => {
                debug!("Failed to convert cursor to point");
                return None;
            }
        };

        // TODO: Would this better be an assertion ? Why are out-of-bound values getting here ?
        // They are coming from the command-line, question is, if they should be handled beforehand
        // clamp pos into allowed range
        let pos = cmp::min(s.len(), pos);
        let sb = &s[..pos];
        let mut start = pos;

        // backtrack to find start of word
        for (i, c) in sb.char_indices().rev() {
            if !is_ident_char(c) {
                break;
            }
            start = i;
        }

        (start, pos)
    };

    Some(ExpandedIdent {
        src: indexed_source,
        start: start,
        pos: pos,
    })
}

pub struct ExpandedIdent {
    src: Rc<IndexedSource>,
    start: Point,
    pos: Point,
}

impl ExpandedIdent {
    pub fn ident(&self) -> &str {
        &self.src.code[self.start..self.pos]
    }

    pub fn start(&self) -> Point {
        self.start
    }

    pub fn pos(&self) -> Point {
        self.pos
    }
}

pub fn find_ident_end(s: &str, pos: Point) -> Point {
    // find end of word
    let sa = &s[pos..];
    for (i, c) in sa.char_indices() {
        if !is_ident_char(c) {
            return pos + i;
        }
    }
    s.len()
}

#[test]
fn find_ident_end_ascii() {
    assert_eq!(5, find_ident_end("ident", 0));
    assert_eq!(6, find_ident_end("(ident)", 1));
    assert_eq!(17, find_ident_end("let an_identifier = 100;", 4));
}

#[test]
fn find_ident_end_unicode() {
    assert_eq!(7, find_ident_end("num_µs", 0));
    assert_eq!(10, find_ident_end("ends_in_µ", 0));
}

// PD: short term replacement for .char_at() function. Should be replaced once
// that stabilizes
pub fn char_at(src: &str, i: usize) -> char {
    src[i..].chars().next().unwrap()
}

/// Error type returned from [`check_rust_src_env_var()`]
///
/// [`check_rust_src_env_var()`]: fn.check_rust_src_env_var.html
#[derive(Debug, PartialEq)]
pub enum RustSrcPathError {
    Missing,
    DoesNotExist(path::PathBuf),
    NotRustSourceTree(path::PathBuf),
}

impl ::std::error::Error for RustSrcPathError {
    fn cause(&self) -> Option<&::std::error::Error> {
        None
    }

    fn description(&self) -> &str {
        match *self {
            RustSrcPathError::Missing => "RUSTC_SRC_PATH not set or not found in sysroot",
            RustSrcPathError::DoesNotExist(_) => "RUSTC_SRC_PATH does not exist on file system",
            RustSrcPathError::NotRustSourceTree(_) => "RUSTC_SRC_PATH isn't a rustc source tree",
        }
    }
}

impl ::std::fmt::Display for RustSrcPathError {
    fn fmt(&self, f: &mut ::std::fmt::Formatter) -> ::std::fmt::Result {
        match *self {
            RustSrcPathError::Missing => {
                write!(f, "RUST_SRC_PATH environment variable must be set to \
                           point to the src directory of a rust checkout. \
                           E.g. \"/home/foouser/src/rust/src\"")
            },
            RustSrcPathError::DoesNotExist(ref path) => {
                write!(f, "racer can't find the directory pointed to by the \
                           RUST_SRC_PATH variable \"{:?}\". Try using an \
                           absolute fully qualified path and make sure it \
                           points to the src directory of a rust checkout - \
                           e.g. \"/home/foouser/src/rust/src\".", path)
            },
            RustSrcPathError::NotRustSourceTree(ref path) => {
                write!(f, "Unable to find libstd under RUST_SRC_PATH. N.B. \
                           RUST_SRC_PATH variable needs to point to the *src* \
                           directory inside a rust checkout e.g. \
                           \"/home/foouser/src/rust/src\". \
                           Current value \"{:?}\"", path)
            },
        }
    }
}

fn check_rust_sysroot() -> Option<path::PathBuf> {
    use ::std::process::Command;
    let mut cmd = Command::new("rustc");
    cmd.arg("--print").arg("sysroot");

    if let Ok(output) = cmd.output() {
        if let Ok(s) = String::from_utf8(output.stdout) {
            let sysroot = path::Path::new(s.trim());
            let srcpath = sysroot.join("lib/rustlib/src/rust/src");
            if srcpath.exists() {
                return Some(srcpath);
            }
        }
    }
    eprintln!("Calling rustc failed! PATH={:?}", ::std::env::var("PATH"));
{'fraction_non_alphanumeric': 0.14731467473524962, 'fraction_numerical': 0.0037821482602118004, 'mean_word_length': 1.9961756373937678, 'pattern_counts': {'":': 0, '<': 19, '<?xml version=': 0, '>': 79, 'https://': 0, 'lorem ipsum': 0, 'www.': 0, 'xml': 0}, 'pii_count': 0, 'substrings_counts': 0, 'word_list_counts': {'cursed_substrings.json': 14, 'profanity_word_list.json': 0, 'sexual_word_list.json': 0, 'zh_pornsignals.json': 0}}
{'dir': 'git-commits-cleaned', 'id': '2082878', 'n_tokens_mistral': 6776, 'n_tokens_neox': 6620, 'n_words': 1752}