<commit_before>use std::path::{PathBuf, Path};
use std::collections::{HashSet, HashMap};
use std::fs::File;
use std::io::Read;
use std::io::{BufReader, BufRead};
use cargo::core::{Workspace, Package};
use cargo::sources::PathSource;
use cargo::util::Config as CargoConfig;
use syn::{parse_file, Item, ItemMod, ItemFn, Ident, File as SFile};
use proc_macro2::Span;
use regex::Regex;
use config::Config;

/// Represents the results of analysis of a single file. Does not store the file
/// in question as this is expected to be maintained by the user.
#[derive(Clone, Debug)]
pub struct LineAnalysis {
    /// This represents lines that should be ignored in coverage
    /// but may be identifed as coverable in the DWARF tables
    pub ignore: HashSet<usize>,
    /// This represents lines that should be included in coverage
    /// But may be ignored.
    pub cover: HashSet<usize>,
}

/// When the LineAnalysis results are mapped to their files there needs to be
/// an easy way to get the information back. For the container used implement
/// this trait
pub trait SourceAnalysisQuery {
    fn should_ignore(&self, path: &Path, l:&usize) -> bool;
}

impl SourceAnalysisQuery for HashMap<PathBuf, LineAnalysis> {

    fn should_ignore(&self, path: &Path, l:&usize) -> bool {
        if self.contains_key(path) {
            self.get(path).unwrap().ignore.contains(l)
        } else {
            false
        }
    }

}

impl LineAnalysis {
    fn new() -> LineAnalysis {
        LineAnalysis {
            ignore: HashSet::new(),
            cover: HashSet::new()
        }
    }

    /// Adds the lines of the provided span to the ignore set
    pub fn ignore_span(&mut self, span: &Span) {
        for i in span.start().line..(span.end().line+1) {
            println!("Ignoring line {}", i);
            self.ignore.insert(i);
            if self.cover.contains(&i) {
                self.cover.remove(&i);
            }
        }
    }

    /// Adds the lines of the provided span to the cover set
    pub fn cover_span(&mut self, span: &Span) {
        for i in span.start().line..(span.end().line +1) {
            if !self.ignore.contains(&i) {
                self.cover.insert(i);
            }
        }
    }

    pub fn should_ignore(&self, line: &usize) -> bool {
        self.ignore.contains(line)
    }

    fn add_to_ignore(&mut self, lines: &[usize]) {
        for l in lines {
            self.ignore.insert(*l);
            if self.cover.contains(l) {
                self.cover.remove(l);
            }
        }
    }

    fn add_to_cover(&mut self, lines: &[usize]) {
        for l in lines {
            if !self.ignore.contains(l) {
                self.cover.insert(*l);
            }
        }
    }
}

/// Returns a list of files and line numbers to ignore (not indexes!)
pub fn get_line_analysis(project: &Workspace, config: &Config) -> HashMap<PathBuf, LineAnalysis> {
    let mut result: HashMap<PathBuf, LineAnalysis> = HashMap::new();
    // Members iterates over all non-virtual packages in the workspace
    for pkg in project.members() {
        if config.packages.is_empty() || config.packages.contains(&pkg.name().to_string()) {
            analyse_package(pkg, &config, project.config(), &mut result);
        }
    }
    result
}

fn analyse_lib_rs(file: &Path, result: &mut HashMap<PathBuf, LineAnalysis>) {
    if let Ok(f) = File::open(file) {
        let mut read_file = BufReader::new(f);
        if let Some(Ok(first)) = read_file.lines().nth(0) {
            if !(first.starts_with("pub") || first.starts_with("fn")) {
                let file = file.to_path_buf();
                if result.contains_key(&file) {
                    let l = result.get_mut(&file).unwrap();
                    l.add_to_ignore(&[1]);
                } else {
                    let mut l = LineAnalysis::new();
                    l.add_to_ignore(&[1]);
                    result.insert(file, l);
                }
            }
        }
    }
}

fn analyse_package(pkg: &Package,
                   config:&Config,
                   cargo_conf: &CargoConfig,
                   result: &mut HashMap<PathBuf, LineAnalysis>) {

    let mut src = PathSource::new(pkg.root(), pkg.package_id().source_id(), cargo_conf);
    if let Ok(package) = src.root_package() {
        for target in package.targets() {
            let path = target.src_path();
            let file = match path.to_str() {
                Some(s) => s,
                _ => continue
            };
            let skip_cause_test = config.ignore_tests &&
                                  path.starts_with(pkg.root().join("tests"));
            let skip_cause_example = path.starts_with(pkg.root().join("examples"));
            if !(skip_cause_test || skip_cause_example)  {
                let file = File::open(file);
                let mut file = match file {
                    Ok(f) => f,
                    _ => continue,
                };
                let mut content = String::new();
                let _ = file.read_to_string(&mut content);
                let file = parse_file(&content);
                if let Ok(file) = file {
                    let mut analysis = LineAnalysis::new();
                    process_items(&file.items, config, &mut analysis);
                    // Check there's no conflict!
                    result.insert(path.to_path_buf(), analysis);
                }
            }
            // This could probably be done with the DWARF if I could find a discriminating factor
            // to why lib.rs:1 shows up as a real line!
            if file.ends_with("src/lib.rs") {
                analyse_lib_rs(path, result);
            }
        }
    }
}

fn process_items(items: &[Item], config: &Config, analysis: &mut LineAnalysis) {
    for item in items {
        match item {
            Item::ExternCrate(i) => analysis.ignore_span(&i.extern_token.0),
            Item::Use(i) => analysis.ignore_span(&i.use_token.0),
            Item::Mod(i) => visit_mod(i, analysis, config),
            Item::Fn(i) => visit_fn(i, analysis, config),
            _ =>{}
        }
    }
}

fn visit_mod(module: &ItemMod, analysis: &mut LineAnalysis, config: &Config) {
    // Need to read the nested meta.. But this should work for fns
    let test_mod = module.attrs.iter()
                               .map(|ref x| x.interpret_meta())
                               .filter(|ref x| x.is_some())
                               .map(|x| x.unwrap())
                               .any(|x| x.name() == Ident::from("test"));
    if test_mod && config.ignore_tests {
        analysis.ignore_span(&module.mod_token.0);
    }
    if let Some((_, ref items)) = module.content {
        process_items(items, config, analysis);
    }
}

fn visit_fn(func: &ItemFn, analysis: &mut LineAnalysis, config: &Config) {
    // Need to read the nested meta.. But this should work for fns
    let test_mod = func.attrs.iter()
                             .map(|ref x| x.interpret_meta())
                             .filter(|ref x| x.is_some())
                             .map(|x| x.unwrap())
                             .any(|x| x.name() == Ident::from("test"));
    println!("visit fn {}", test_mod);
    if test_mod && config.ignore_tests {
        analysis.ignore_span(&func.decl.fn_token.0);
    }
}

#[cfg(test)]
mod tests {
    use super::*;
    use syntex_syntax::parse::filemap_to_parser;
    use syntex_syntax::parse::parser::Parser;

    struct TestContext {
        conf: Config,
        codemap: Rc<CodeMap>,
        parse_session: ParseSess
    }

    impl TestContext {
        fn generate_parser(&self, filename: &str, src_string: &str) -> Parser {
            let filemap = self.codemap.new_filemap(filename.to_string(),
                                                   src_string.to_string());
            filemap_to_parser(&self.parse_session, filemap)
        }
    }

    impl Default for TestContext {
        fn default() -> TestContext {
            let codemap = Rc::new(CodeMap::new(FilePathMapping::empty()));
            let handler = Handler::with_tty_emitter(ColorConfig::Auto, false, false, Some(codemap.clone()));
            let parse_session = ParseSess::with_span_handler(handler, codemap.clone());
            TestContext {
                conf: Config::default(),
                codemap: codemap,
                parse_session: parse_session
            }
        }
    }

    fn parse_crate(ctx: &TestContext, parser: &mut Parser) -> Vec<usize>  {
        let krate = parser.parse_crate_mod();
        assert!(krate.is_ok());
        let krate = krate.unwrap();
        let unused: HashSet<PathBuf> = HashSet::new();
        let mut visitor = CoverageVisitor::from_session(&ctx.parse_session, &unused, &ctx.conf);
        visitor.visit_mod(&krate.module, krate.span, &krate.attrs, NodeId::new(0));
        visitor.lines.iter().map(|x| x.1).collect::<Vec<_>>()
    }

    #[test]
    fn filter_str_literals() {
        let ctx = TestContext::default();
        let mut parser = ctx.generate_parser("literals.rs", "fn test() {\nwriteln!(#\"test\n\ttest\n\ttest\"#);\n}\n");
        let lines = parse_crate(&ctx, &mut parser);
        assert!(lines.len() > 1);
        assert!(lines.contains(&3));
        assert!(lines.contains(&4));

        let ctx = TestContext::default();
        let mut parser = ctx.generate_parser("literals.rs", "fn test() {\nwrite(\"test\ntest\ntest\");\n}\nfn write(s:&str){}");
        let lines = parse_crate(&ctx, &mut parser);
        assert!(lines.len() > 1);
        assert!(lines.contains(&3));
        assert!(lines.contains(&4));

        let ctx = TestContext::default();
        let mut parser = ctx.generate_parser("literals.rs", "\n\nfn test() {\nwriteln!(\n#\"test\"#\n);\n}\n");
        let lines = parse_crate(&ctx, &mut parser);
        assert!(lines.contains(&5));
    }

    #[test]
    fn filter_struct_members() {
        let ctx = TestContext::default();
        let mut parser = ctx.generate_parser("struct_test.rs", "#[derive(Debug)]\npub struct Struct {\npub i: i32,\nj:String,\n}");
        let lines = parse_crate(&ctx, &mut parser);

        assert_eq!(lines.len(), 3);
        assert!(lines.contains(&1));
        assert!(lines.contains(&3));
        assert!(lines.contains(&4));
    }

    #[test]
    fn filter_struct_consts() {
        let ctx = TestContext::default();
        let mut parser = ctx.generate_parser("struct_test.rs",
                                             "struct T{x:String, y:i32}\nfn test()-> T {\nT{\nx:\"hello\".to_string(),\ny:4,\n}\n}");

        let lines = parse_crate(&ctx, &mut parser);
        assert!(lines.contains(&5));
    }

    #[test]
    fn filter_mods() {
        let ctx = TestContext::default();
        let mut parser = ctx.generate_parser("test.rs", "mod foo {\nfn double(x:i32)->i32 {\n x*2\n}\n}");
        let lines = parse_crate(&ctx, &mut parser);
        assert!(!lines.contains(&3));

        let mut parser = ctx.generate_parser("test.rs", "mod foo{}");
        let lines = parse_crate(&ctx, &mut parser);
        assert!(lines.contains(&1));
    }

    #[test]
    fn filter_macros() {
        let ctx = TestContext::default();
        let mut parser = ctx.generate_parser("test.rs", "\n\nfn unused() {\nunimplemented!();\n}");

        let lines = parse_crate(&ctx, &mut parser);
        // Braces should be ignored so number could be higher
        assert!(lines.len() >= 1);
        assert!(lines.contains(&4));

        let mut parser = ctx.generate_parser("test.rs", "fn unused() {\nunreachable!();\n}");
        let lines = parse_crate(&ctx, &mut parser);
        assert!(lines.len() >= 1);
        assert!(lines.contains(&2));

        let mut parser = ctx.generate_parser("test.rs", "fn unused() {\nprintln!(\"text\");\n}");
        let lines = parse_crate(&ctx, &mut parser);
        assert!(!lines.contains(&2));
    }

    #[test]
    fn filter_tests() {
        let ctx = TestContext::default();
        let src_string = "#[cfg(test)]\nmod tests {\n fn boo(){\nassert!(true);\n}\n}";
        let mut parser = ctx.generate_parser("test.rs", src_string);
        let lines = parse_crate(&ctx, &mut parser);
        assert!(!lines.contains(&4));

        let mut ctx = TestContext::default();
        ctx.conf.ignore_tests = true;
        let mut parser = ctx.generate_parser("test.rs", src_string);
        let lines = parse_crate(&ctx, &mut parser);
        assert!(lines.contains(&4));

        let ctx = TestContext::default();
        let src_string = "#[test]\nfn mytest() { \n assert!(true);\n}";
        let mut parser = ctx.generate_parser("test.rs", src_string);
        let lines = parse_crate(&ctx, &mut parser);
        assert!(!lines.contains(&2));
        assert!(!lines.contains(&3));

        let mut ctx = TestContext::default();
        ctx.conf.ignore_tests = true;
        let mut parser = ctx.generate_parser("test.rs", src_string);
        let lines = parse_crate(&ctx, &mut parser);
        assert!(lines.contains(&2));
        assert!(lines.contains(&3));

    }
}
<commit_msg>Ignoring test modules and fns works.<commit_after>use std::path::{PathBuf, Path};
use std::collections::{HashSet, HashMap};
use std::fs::File;
use std::io::Read;
use std::io::{BufReader, BufRead};
use cargo::core::{Workspace, Package};
use cargo::sources::PathSource;
use cargo::util::Config as CargoConfig;
use syn::{parse_file, Item, ItemMod, ItemFn, Ident, Meta, NestedMeta, File as SFile};
use proc_macro2::Span;
use regex::Regex;
use config::Config;

/// Represents the results of analysis of a single file. Does not store the file
/// in question as this is expected to be maintained by the user.
#[derive(Clone, Debug)]
pub struct LineAnalysis {
    /// This represents lines that should be ignored in coverage
    /// but may be identifed as coverable in the DWARF tables
    pub ignore: HashSet<usize>,
    /// This represents lines that should be included in coverage
    /// But may be ignored.
    pub cover: HashSet<usize>,
}

/// When the LineAnalysis results are mapped to their files there needs to be
/// an easy way to get the information back. For the container used implement
/// this trait
pub trait SourceAnalysisQuery {
    fn should_ignore(&self, path: &Path, l:&usize) -> bool;
}

impl SourceAnalysisQuery for HashMap<PathBuf, LineAnalysis> {

    fn should_ignore(&self, path: &Path, l:&usize) -> bool {
        if self.contains_key(path) {
            self.get(path).unwrap().ignore.contains(l)
        } else {
            false
        }
    }

}

impl LineAnalysis {
    fn new() -> LineAnalysis {
        LineAnalysis {
            ignore: HashSet::new(),
            cover: HashSet::new()
        }
    }

    /// Adds the lines of the provided span to the ignore set
    pub fn ignore_span(&mut self, span: &Span) {
        for i in span.start().line..(span.end().line+1) {
            self.ignore.insert(i);
            if self.cover.contains(&i) {
                self.cover.remove(&i);
            }
        }
    }

    /// Adds the lines of the provided span to the cover set
    pub fn cover_span(&mut self, span: &Span) {
        for i in span.start().line..(span.end().line +1) {
            if !self.ignore.contains(&i) {
                self.cover.insert(i);
            }
        }
    }

    pub fn should_ignore(&self, line: &usize) -> bool {
        self.ignore.contains(line)
    }

    fn add_to_ignore(&mut self, lines: &[usize]) {
        for l in lines {
            self.ignore.insert(*l);
            if self.cover.contains(l) {
                self.cover.remove(l);
            }
        }
    }

    fn add_to_cover(&mut self, lines: &[usize]) {
        for l in lines {
            if !self.ignore.contains(l) {
                self.cover.insert(*l);
            }
        }
    }
}

/// Returns a list of files and line numbers to ignore (not indexes!)
pub fn get_line_analysis(project: &Workspace, config: &Config) -> HashMap<PathBuf, LineAnalysis> {
    let mut result: HashMap<PathBuf, LineAnalysis> = HashMap::new();
    // Members iterates over all non-virtual packages in the workspace
    for pkg in project.members() {
        if config.packages.is_empty() || config.packages.contains(&pkg.name().to_string()) {
            analyse_package(pkg, &config, project.config(), &mut result);
        }
    }
    result
}

fn analyse_lib_rs(file: &Path, result: &mut HashMap<PathBuf, LineAnalysis>) {
    if let Ok(f) = File::open(file) {
        let mut read_file = BufReader::new(f);
        if let Some(Ok(first)) = read_file.lines().nth(0) {
            if !(first.starts_with("pub") || first.starts_with("fn")) {
                let file = file.to_path_buf();
                if result.contains_key(&file) {
                    let l = result.get_mut(&file).unwrap();
                    l.add_to_ignore(&[1]);
                } else {
                    let mut l = LineAnalysis::new();
                    l.add_to_ignore(&[1]);
                    result.insert(file, l);
                }
            }
        }
    }
}

fn analyse_package(pkg: &Package,
                   config:&Config,
                   cargo_conf: &CargoConfig,
                   result: &mut HashMap<PathBuf, LineAnalysis>) {

    let mut src = PathSource::new(pkg.root(), pkg.package_id().source_id(), cargo_conf);
    if let Ok(package) = src.root_package() {
        for target in package.targets() {
            let path = target.src_path();
            let file = match path.to_str() {
                Some(s) => s,
                _ => continue
            };
            let skip_cause_test = config.ignore_tests &&
                                  path.starts_with(pkg.root().join("tests"));
            let skip_cause_example = path.starts_with(pkg.root().join("examples"));
            if !(skip_cause_test || skip_cause_example)  {
                let file = File::open(file);
                let mut file = match file {
                    Ok(f) => f,
                    _ => continue,
                };
                let mut content = String::new();
                let _ = file.read_to_string(&mut content);
                let file = parse_file(&content);
                if let Ok(file) = file {
                    let mut analysis = LineAnalysis::new();
                    process_items(&file.items, config, &mut analysis);
                    // Check there's no conflict!
                    result.insert(path.to_path_buf(), analysis);
                }
            }
            // This could probably be done with the DWARF if I could find a discriminating factor
            // to why lib.rs:1 shows up as a real line!
            if file.ends_with("src/lib.rs") {
                analyse_lib_rs(path, result);
            }
        }
    }
}

fn process_items(items: &[Item], config: &Config, analysis: &mut LineAnalysis) {
    for item in items {
        match item {
            Item::ExternCrate(i) => analysis.ignore_span(&i.extern_token.0),
            Item::Use(i) => analysis.ignore_span(&i.use_token.0),
            Item::Mod(i) => visit_mod(i, analysis, config),
            Item::Fn(i) => visit_fn(i, analysis, config),
            _ =>{}
        }
    }
}

fn visit_mod(module: &ItemMod, analysis: &mut LineAnalysis, config: &Config) {
    // Need to read the nested meta.. But this should work for fns
    let test_attr = NestedMeta::Meta(Meta::Word(Ident::from("test")));
    let mut check_insides = true;
    for attr in &module.attrs {
        if let Some(Meta::List(ref ml)) = attr.interpret_meta() {
            if ml.ident != Ident::from("cfg") {
                continue;
            }
            for nested in &ml.nested {
                if let NestedMeta::Meta(Meta::Word(i)) = nested {
                    if i == &Ident::from("test") {
                        check_insides = false;
                        analysis.ignore_span(&module.mod_token.0);
                        if let Some((ref braces, _)) = module.content {
                            analysis.ignore_span(&braces.0);
                        }
                    }
                }
            }
        }
    }
    if check_insides {
        if let Some((_, ref items)) = module.content {
            process_items(items, config, analysis);
        }
    }
}

fn visit_fn(func: &ItemFn, analysis: &mut LineAnalysis, config: &Config) {
    // Need to read the nested meta.. But this should work for fns
    let test_mod = func.attrs.iter()
                             .map(|ref x| x.interpret_meta())
                             .filter(|ref x| x.is_some())
                             .map(|x| x.unwrap())
                             .any(|x| x.name() == Ident::from("test"));
    if test_mod && config.ignore_tests {
        analysis.ignore_span(&func.decl.fn_token.0);
        analysis.ignore_span(&func.block.brace_token.0);
    }
}

#[cfg(test)]
mod tests {
    use super::*;
    use syntex_syntax::parse::filemap_to_parser;
    use syntex_syntax::parse::parser::Parser;

    struct TestContext {
        conf: Config,
        codemap: Rc<CodeMap>,
        parse_session: ParseSess
    }

    impl TestContext {
        fn generate_parser(&self, filename: &str, src_string: &str) -> Parser {
            let filemap = self.codemap.new_filemap(filename.to_string(),
                                                   src_string.to_string());
            filemap_to_parser(&self.parse_session, filemap)
        }
    }

    impl Default for TestContext {
        fn default() -> TestContext {
            let codemap = Rc::new(CodeMap::new(FilePathMapping::empty()));
            let handler = Handler::with_tty_emitter(ColorConfig::Auto, false, false, Some(codemap.clone()));
            let parse_session = ParseSess::with_span_handler(handler, codemap.clone());
            TestContext {
                conf: Config::default(),
                codemap: codemap,
                parse_session: parse_session
            }
        }
    }

    fn parse_crate(ctx: &TestContext, parser: &mut Parser) -> Vec<usize>  {
        let krate = parser.parse_crate_mod();
        assert!(krate.is_ok());
        let krate = krate.unwrap();
        let unused: HashSet<PathBuf> = HashSet::new();
        let mut visitor = CoverageVisitor::from_session(&ctx.parse_session, &unused, &ctx.conf);
        visitor.visit_mod(&krate.module, krate.span, &krate.attrs, NodeId::new(0));
        visitor.lines.iter().map(|x| x.1).collect::<Vec<_>>()
    }

    #[test]
    fn filter_str_literals() {
        let ctx = TestContext::default();
        let mut parser = ctx.generate_parser("literals.rs", "fn test() {\nwriteln!(#\"test\n\ttest\n\ttest\"#);\n}\n");
        let lines = parse_crate(&ctx, &mut parser);
        assert!(lines.len() > 1);
        assert!(lines.contains(&3));
        assert!(lines.contains(&4));

        let ctx = TestContext::default();
        let mut parser = ctx.generate_parser("literals.rs", "fn test() {\nwrite(\"test\ntest\ntest\");\n}\nfn write(s:&str){}");
        let lines = parse_crate(&ctx, &mut parser);
        assert!(lines.len() > 1);
        assert!(lines.contains(&3));
        assert!(lines.contains(&4));

        let ctx = TestContext::default();
        let mut parser = ctx.generate_parser("literals.rs", "\n\nfn test() {\nwriteln!(\n#\"test\"#\n);\n}\n");
        let lines = parse_crate(&ctx, &mut parser);
        assert!(lines.contains(&5));
    }

    #[test]
    fn filter_struct_members() {
        let ctx = TestContext::default();
        let mut parser = ctx.generate_parser("struct_test.rs", "#[derive(Debug)]\npub struct Struct {\npub i: i32,\nj:String,\n}");
        let lines = parse_crate(&ctx, &mut parser);

        assert_eq!(lines.len(), 3);
        assert!(lines.contains(&1));
        assert!(lines.contains(&3));
        assert!(lines.contains(&4));
    }

    #[test]
    fn filter_struct_consts() {
        let ctx = TestContext::default();
        let mut parser = ctx.generate_parser("struct_test.rs",
                                             "struct T{x:String, y:i32}\nfn test()-> T {\nT{\nx:\"hello\".to_string(),\ny:4,\n}\n}");

        let lines = parse_crate(&ctx, &mut parser);
        assert!(lines.contains(&5));
    }

    #[test]
    fn filter_mods() {
        let ctx = TestContext::default();
        let mut parser = ctx.generate_parser("test.rs", "mod foo {\nfn double(x:i32)->i32 {\n x*2\n}\n}");
        let lines = parse_crate(&ctx, &mut parser);
        assert!(!lines.contains(&3));

        let mut parser = ctx.generate_parser("test.rs", "mod foo{}");
        let lines = parse_crate(&ctx, &mut parser);
        assert!(lines.contains(&1));
    }

    #[test]
    fn filter_macros() {
        let ctx = TestContext::default();
        let mut parser = ctx.generate_parser("test.rs", "\n\nfn unused() {\nunimplemented!();\n}");

        let lines = parse_crate(&ctx, &mut parser);
        // Braces should be ignored so number could be higher
        assert!(lines.len() >= 1);
        assert!(lines.contains(&4));

        let mut parser = ctx.generate_parser("test.rs", "fn unused() {\nunreachable!();\n}");
        let lines = parse_crate(&ctx, &mut parser);
        assert!(lines.len() >= 1);
        assert!(lines.contains(&2));

        let mut parser = ctx.generate_parser("test.rs", "fn unused() {\nprintln!(\"text\");\n}");
        let lines = parse_crate(&ctx, &mut parser);
        assert!(!lines.contains(&2));
    }

    #[test]
    fn filter_tests() {
        let ctx = TestContext::default();
        let src_string = "#[cfg(test)]\nmod tests {\n fn boo(){\nassert!(true);\n}\n}";
        let mut parser = ctx.generate_parser("test.rs", src_string);
        let lines = parse_crate(&ctx, &mut parser);
        assert!(!lines.contains(&4));

        let mut ctx = TestContext::default();
        ctx.conf.ignore_tests = true;
        let mut parser = ctx.generate_parser("test.rs", src_string);
        let lines = parse_crate(&ctx, &mut parser);
        assert!(lines.contains(&4));

        let ctx = TestContext::default();
        let src_string = "#[test]\nfn mytest() { \n assert!(true);\n}";
        let mut parser = ctx.generate_parser("test.rs", src_string);
        let lines = parse_crate(&ctx, &mut parser);
        assert!(!lines.contains(&2));
        assert!(!lines.contains(&3));

        let mut ctx = TestContext::default();
        ctx.conf.ignore_tests = true;
        let mut parser = ctx.generate_parser("test.rs", src_string);
        let lines = parse_crate(&ctx, &mut parser);
        assert!(lines.contains(&2));
        assert!(lines.contains(&3));

    }
}
<|endoftext|>


<commit_before>// Copyright 2016 Johannes Köster.
// Licensed under the MIT license (http://opensource.org/licenses/MIT)
// This file may not be copied, modified, or distributed
// except according to those terms.

//! Support for discrete probability distributions in terms of cumulative distribution
//! functions (CDF).

use std::f64;
use std::iter;
use std::slice;
use std::ops::Range;

use num::traits::{cast, NumCast};
use itertools::Itertools;

use stats::LogProb;

#[derive(Debug, Clone)]
#[cfg_attr(feature = "serde_macros", derive(Serialize, Deserialize))]
pub struct Entry<T: Ord> {
    pub value: T,
    pub prob: LogProb
}

impl<T: Ord> Entry<T> {
    pub fn new(value: T, prob: LogProb) -> Self {
        Entry { value: value, prob: prob }
    }
}

/// Implementation of a cumulative distribution function.
#[derive(Debug, Clone)]
#[cfg_attr(feature = "serde_macros", derive(Serialize, Deserialize))]
pub struct CDF<T: Ord> {
    inner: Vec<Entry<T>>
}

impl<T: Ord> CDF<T> {
    /// Create CDF from given probability mass function (PMF). The PMF may contain duplicate values
    /// the probabilities of which are summed during generation of the CDF.
    ///
    /// # Arguments
    ///
    /// * `pmf` - the PMF as a vector of `Entry` objects
    pub fn from_pmf(mut entries: Vec<Entry<T>>) -> Self {
        entries.sort_by(|a, b| a.value.cmp(&b.value));
        let mut inner: Vec<Entry<T>> = Vec::new();
        for mut e in entries.into_iter() {
            let p = inner.last().map_or(LogProb::ln_zero(), |e| e.prob).ln_add_exp(e.prob);
            if !inner.is_empty() && inner.last().unwrap().value == e.value {
                inner.last_mut().unwrap().prob = p;
            }
            else {
                e.prob = p;
                inner.push(e);
            }
        }
        let mut cdf = CDF {
            inner: inner
        };

        if relative_eq!(*cdf.total_prob(), *LogProb::ln_one()) && *cdf.total_prob() > *LogProb::ln_one() {
            cdf.inner.last_mut().unwrap().prob = LogProb::ln_one();
        }

        cdf
    }

    /// Create CDF from iterator. This can be used to replace the values of a CDF.
    pub fn from_cdf<I: Iterator<Item = Entry<T>>>(entries: I) -> Self {
        CDF { inner: entries.collect_vec() }
    }

    /// Reduce CDF by omitting values with zero probability.
    pub fn reduce(self) -> Self {
        let mut inner = Vec::new();
        let mut last = LogProb::ln_zero();
        for e in self.inner.into_iter() {
            if last != e.prob {
                last = e.prob;
                inner.push(e);
            }
        }
        CDF { inner: inner }
    }

    /// Downsample CDF to n entries.
    pub fn sample(mut self, n: usize) -> Self {
        if self.inner.len() <= n {
            self
        }
        else {
            let s = self.inner.len() / (n - 1);
            let last = self.inner.pop().unwrap();
            let mut inner = self.inner.into_iter().step(s).collect_vec();
            inner.push(last);
            CDF {
                inner: inner
            }
        }
    }

    /// Provide iterator.
    pub fn iter(&self) -> slice::Iter<Entry<T>>{
        self.inner.iter()
    }

    /// Iterator over corresponding PMF.
    pub fn iter_pmf<'a>(&'a self) -> CDFPMFIter<'a, T> {
        fn cdf_to_pmf<'a, G: Ord>(last_prob: &mut LogProb, e: &'a Entry<G>) -> Option<Entry<&'a G>> {
            let prob = e.prob.ln_sub_exp(*last_prob);
            *last_prob = e.prob;
            Some(Entry::new(&e.value, prob))
        }
        self.inner.iter().scan(LogProb::ln_zero(), cdf_to_pmf)
    }

    /// Get cumulative probability for a given value. If the value is not present,
    /// return the probability of the previous value. Complexity O(log n).
    pub fn get(&self, value: &T) -> Option<LogProb> {
        if self.inner.is_empty() {
            None
        }
        else {
            Some(match self.inner.binary_search_by(|e| e.value.partial_cmp(value).unwrap()) {
                Ok(i) => self.inner[i].prob,
                Err(i) => if i > 0 { self.inner[i - 1].prob } else { LogProb::ln_zero() }
            })
        }
    }

    /// Get probability (i.e. probability mass) for a given value. Complexity O(log n).
    pub fn get_pmf(&self, value: &T) -> Option<LogProb> {
        if self.inner.is_empty() {
            None
        }
        else {
            Some(match self.inner.binary_search_by(|e| e.value.partial_cmp(value).unwrap()) {
                Ok(i) => if i > 0 { self.inner[i].prob.ln_sub_exp(self.inner[i - 1].prob) } else { self.inner[0].prob },
                Err(i) => if i > 0 { self.inner[i - 1].prob } else { LogProb::ln_zero() }
            })
        }
    }

    /// Return total probability.
    pub fn total_prob(&self) -> LogProb {
        self.inner.last().map_or(LogProb::ln_zero(), |e| e.prob)
    }

    /// Return maximum a posteriori probability estimate (MAP).
    pub fn map(&self) -> Option<&T> {
        if let Some(mut max) = self.iter_pmf().next() {
            for e in self.iter_pmf() {
                if e.prob >= max.prob {
                    max = e;
                }
            }
            Some(&max.value)
        } else {
            None
        }
    }

    /// Return 95% credible interval.
    pub fn credible_interval(&self) -> Range<&T> {
        let p_lower = LogProb(0.025f64.ln());
        let p_upper = LogProb(0.095f64.ln());
        let lower = self.inner.binary_search_by(|e| e.prob.partial_cmp(&p_lower).unwrap()).unwrap_or_else(|i| i);
        let upper = self.inner.binary_search_by(|e| e.prob.partial_cmp(&p_upper).unwrap()).unwrap_or_else(|i| i - 1);

        &self.inner[lower].value..&self.inner[upper].value
    }

    /// Number of entries in the CDF.
    pub fn len(&self) -> usize {
        self.inner.len()
    }
}

impl<T: NumCast + Clone + Ord> CDF<T> {
    /// Calculate expected value.
    pub fn expected_value(&self) -> f64 {
        self.iter_pmf().map(|e| {
            cast::<T, f64>(e.value.clone()).unwrap() * e.prob.exp()
        }).fold(0.0f64, |s, e| s + e)
    }

    /// Calculate variance.
    pub fn variance(&self) -> f64 {
        let ev = self.expected_value();
        self.iter_pmf().map(|e| {
                (cast::<T, f64>(e.value.clone()).unwrap() - ev).powi(2) * e.prob.exp()
        }).fold(0.0, |s, e| s + e)
    }

    /// Calculate standard deviation.
    pub fn standard_deviation(&self) -> f64 {
        self.variance().sqrt()
    }
}

pub type CDFPMFIter<'a, T> = iter::Scan<slice::Iter<'a, Entry<T>>, LogProb, fn(&mut LogProb, &'a Entry<T>) -> Option<Entry<&'a T>>>;

#[cfg(test)]
mod test {
    use super::*;
    use stats::LogProb;
    use ordered_float::NotNaN;

    #[test]
    fn test_cdf() {
        let mut pmf = vec![Entry::new(NotNaN::new(0.0).unwrap(), LogProb(0.1f64.ln()))];
        for i in 0..9 {
            pmf.push(Entry::new(NotNaN::new(i as f64).unwrap(), LogProb(0.1f64.ln())));
        }
        println!("{:?}", pmf);

        let cdf = CDF::from_pmf(pmf.clone());
        println!("{:?}", cdf);
        for e in pmf.iter().skip(2) {
            assert_ulps_eq!(*e.prob, *cdf.get_pmf(&e.value).unwrap(), epsilon = 0.0000000000001);
        }
        assert_relative_eq!(*cdf.total_prob(), 1.0f64.ln());
        assert_relative_eq!(*cdf.get(&NotNaN::new(1.0).unwrap()).unwrap(), 0.3f64.ln(), epsilon = 0.00000001);
    }
}
<commit_msg>Remove superfluous into_iter calls.<commit_after>// Copyright 2016 Johannes Köster.
// Licensed under the MIT license (http://opensource.org/licenses/MIT)
// This file may not be copied, modified, or distributed
// except according to those terms.

//! Support for discrete probability distributions in terms of cumulative distribution
//! functions (CDF).

use std::f64;
use std::iter;
use std::slice;
use std::ops::Range;

use num::traits::{cast, NumCast};
use itertools::Itertools;

use stats::LogProb;

#[derive(Debug, Clone)]
#[cfg_attr(feature = "serde_macros", derive(Serialize, Deserialize))]
pub struct Entry<T: Ord> {
    pub value: T,
    pub prob: LogProb
}

impl<T: Ord> Entry<T> {
    pub fn new(value: T, prob: LogProb) -> Self {
        Entry { value: value, prob: prob }
    }
}

/// Implementation of a cumulative distribution function.
#[derive(Debug, Clone)]
#[cfg_attr(feature = "serde_macros", derive(Serialize, Deserialize))]
pub struct CDF<T: Ord> {
    inner: Vec<Entry<T>>
}

impl<T: Ord> CDF<T> {
    /// Create CDF from given probability mass function (PMF). The PMF may contain duplicate values
    /// the probabilities of which are summed during generation of the CDF.
    ///
    /// # Arguments
    ///
    /// * `pmf` - the PMF as a vector of `Entry` objects
    pub fn from_pmf(mut entries: Vec<Entry<T>>) -> Self {
        entries.sort_by(|a, b| a.value.cmp(&b.value));
        let mut inner: Vec<Entry<T>> = Vec::new();
        for mut e in entries {
            let p = inner.last().map_or(LogProb::ln_zero(), |e| e.prob).ln_add_exp(e.prob);
            if !inner.is_empty() && inner.last().unwrap().value == e.value {
                inner.last_mut().unwrap().prob = p;
            }
            else {
                e.prob = p;
                inner.push(e);
            }
        }
        let mut cdf = CDF {
            inner: inner
        };

        if relative_eq!(*cdf.total_prob(), *LogProb::ln_one()) && *cdf.total_prob() > *LogProb::ln_one() {
            cdf.inner.last_mut().unwrap().prob = LogProb::ln_one();
        }

        cdf
    }

    /// Create CDF from iterator. This can be used to replace the values of a CDF.
    pub fn from_cdf<I: Iterator<Item = Entry<T>>>(entries: I) -> Self {
        CDF { inner: entries.collect_vec() }
    }

    /// Reduce CDF by omitting values with zero probability.
    pub fn reduce(self) -> Self {
        let mut inner = Vec::new();
        let mut last = LogProb::ln_zero();
        for e in self.inner {
            if last != e.prob {
                last = e.prob;
                inner.push(e);
            }
        }
        CDF { inner: inner }
    }

    /// Downsample CDF to n entries.
    pub fn sample(mut self, n: usize) -> Self {
        if self.inner.len() <= n {
            self
        }
        else {
            let s = self.inner.len() / (n - 1);
            let last = self.inner.pop().unwrap();
            let mut inner = self.inner.into_iter().step(s).collect_vec();
            inner.push(last);
            CDF {
                inner: inner
            }
        }
    }

    /// Provide iterator.
    pub fn iter(&self) -> slice::Iter<Entry<T>>{
        self.inner.iter()
    }

    /// Iterator over corresponding PMF.
    pub fn iter_pmf<'a>(&'a self) -> CDFPMFIter<'a, T> {
        fn cdf_to_pmf<'a, G: Ord>(last_prob: &mut LogProb, e: &'a Entry<G>) -> Option<Entry<&'a G>> {
            let prob = e.prob.ln_sub_exp(*last_prob);
            *last_prob = e.prob;
            Some(Entry::new(&e.value, prob))
        }
        self.inner.iter().scan(LogProb::ln_zero(), cdf_to_pmf)
    }

    /// Get cumulative probability for a given value. If the value is not present,
    /// return the probability of the previous value. Complexity O(log n).
    pub fn get(&self, value: &T) -> Option<LogProb> {
        if self.inner.is_empty() {
            None
        }
        else {
            Some(match self.inner.binary_search_by(|e| e.value.partial_cmp(value).unwrap()) {
                Ok(i) => self.inner[i].prob,
                Err(i) => if i > 0 { self.inner[i - 1].prob } else { LogProb::ln_zero() }
            })
        }
    }

    /// Get probability (i.e. probability mass) for a given value. Complexity O(log n).
    pub fn get_pmf(&self, value: &T) -> Option<LogProb> {
        if self.inner.is_empty() {
            None
        }
        else {
            Some(match self.inner.binary_search_by(|e| e.value.partial_cmp(value).unwrap()) {
                Ok(i) => if i > 0 { self.inner[i].prob.ln_sub_exp(self.inner[i - 1].prob) } else { self.inner[0].prob },
                Err(i) => if i > 0 { self.inner[i - 1].prob } else { LogProb::ln_zero() }
            })
        }
    }

    /// Return total probability.
    pub fn total_prob(&self) -> LogProb {
        self.inner.last().map_or(LogProb::ln_zero(), |e| e.prob)
    }

    /// Return maximum a posteriori probability estimate (MAP).
    pub fn map(&self) -> Option<&T> {
        if let Some(mut max) = self.iter_pmf().next() {
            for e in self.iter_pmf() {
                if e.prob >= max.prob {
                    max = e;
                }
            }
            Some(&max.value)
        } else {
            None
        }
    }

    /// Return 95% credible interval.
    pub fn credible_interval(&self) -> Range<&T> {
        let p_lower = LogProb(0.025f64.ln());
        let p_upper = LogProb(0.095f64.ln());
        let lower = self.inner.binary_search_by(|e| e.prob.partial_cmp(&p_lower).unwrap()).unwrap_or_else(|i| i);
        let upper = self.inner.binary_search_by(|e| e.prob.partial_cmp(&p_upper).unwrap()).unwrap_or_else(|i| i - 1);

        &self.inner[lower].value..&self.inner[upper].value
    }

    /// Number of entries in the CDF.
    pub fn len(&self) -> usize {
        self.inner.len()
    }
}

impl<T: NumCast + Clone + Ord> CDF<T> {
    /// Calculate expected value.
    pub fn expected_value(&self) -> f64 {
        self.iter_pmf().map(|e| {
            cast::<T, f64>(e.value.clone()).unwrap() * e.prob.exp()
        }).fold(0.0f64, |s, e| s + e)
    }

    /// Calculate variance.
    pub fn variance(&self) -> f64 {
        let ev = self.expected_value();
        self.iter_pmf().map(|e| {
                (cast::<T, f64>(e.value.clone()).unwrap() - ev).powi(2) * e.prob.exp()
        }).fold(0.0, |s, e| s + e)
    }

    /// Calculate standard deviation.
    pub fn standard_deviation(&self) -> f64 {
        self.variance().sqrt()
    }
}

pub type CDFPMFIter<'a, T> = iter::Scan<slice::Iter<'a, Entry<T>>, LogProb, fn(&mut LogProb, &'a Entry<T>) -> Option<Entry<&'a T>>>;

#[cfg(test)]
mod test {
    use super::*;
    use stats::LogProb;
    use ordered_float::NotNaN;

    #[test]
    fn test_cdf() {
        let mut pmf = vec![Entry::new(NotNaN::new(0.0).unwrap(), LogProb(0.1f64.ln()))];
        for i in 0..9 {
            pmf.push(Entry::new(NotNaN::new(i as f64).unwrap(), LogProb(0.1f64.ln())));
        }
        println!("{:?}", pmf);

        let cdf = CDF::from_pmf(pmf.clone());
        println!("{:?}", cdf);
        for e in pmf.iter().skip(2) {
            assert_ulps_eq!(*e.prob, *cdf.get_pmf(&e.value).unwrap(), epsilon = 0.0000000000001);
        }
        assert_relative_eq!(*cdf.total_prob(), 1.0f64.ln());
        assert_relative_eq!(*cdf.get(&NotNaN::new(1.0).unwrap()).unwrap(), 0.3f64.ln(), epsilon = 0.00000001);
    }
}
<|endoftext|>


<commit_before>use core::intrinsics::*;
use core::ops::FnOnce;

#[cfg(not(test))]
#[inline(always)]
/// NOP instruction
pub fn nop() {
  unsafe { asm!("nop" :::: "volatile"); }
}

#[cfg(test)]
/// NOP instruction (mock)
pub fn nop() {
}

#[cfg(not(test))]
#[inline(always)]
/// WFI instruction
pub unsafe fn wfi() {
    asm!("wfi" :::: "volatile");
}

#[cfg(test)]
/// WFI instruction (mock)
pub unsafe fn wfi() {
}

pub unsafe fn atomic<F,R>(f: F) -> R where F: FnOnce() -> R {
    // Set PRIMASK
    asm!("cpsid i" :::: "volatile");

    let res = f();

    // Unset PRIMASK
    asm!("cpsie i" :::: "volatile");
    return res;
}

#[cfg(not(test))]
#[lang="eh_personality"]
pub extern fn eh_personality() {}

#[doc(hidden)]
#[no_mangle]
pub unsafe extern fn __aeabi_unwind_cpp_pr0() {
  abort();
}

#[doc(hidden)]
#[no_mangle]
pub unsafe extern fn __aeabi_unwind_cpp_pr1() {
  abort();
}

<commit_msg>Remove stale definitions for __aeabi_unwind_cpp_*<commit_after>use core::intrinsics::*;
use core::ops::FnOnce;

#[cfg(not(test))]
#[inline(always)]
/// NOP instruction
pub fn nop() {
  unsafe { asm!("nop" :::: "volatile"); }
}

#[cfg(test)]
/// NOP instruction (mock)
pub fn nop() {
}

#[cfg(not(test))]
#[inline(always)]
/// WFI instruction
pub unsafe fn wfi() {
    asm!("wfi" :::: "volatile");
}

#[cfg(test)]
/// WFI instruction (mock)
pub unsafe fn wfi() {
}

pub unsafe fn atomic<F,R>(f: F) -> R where F: FnOnce() -> R {
    // Set PRIMASK
    asm!("cpsid i" :::: "volatile");

    let res = f();

    // Unset PRIMASK
    asm!("cpsie i" :::: "volatile");
    return res;
}

#[cfg(not(test))]
#[lang="eh_personality"]
pub extern fn eh_personality() {}

<|endoftext|>


<commit_before>#[macro_export]
macro_rules! composite {
    ( $( $arg:expr ),+ ) => {
        {
            use $crate::component::{Component, SubComponent};
            use $crate::Error;
            use ::futures::{Poll, Async};

            struct CompositeComponent {
                components: Vec<SubComponent>,
                stream: CompositeComponentStream,
            }

            impl Component for CompositeComponent {
                type Error = Error;
                type Stream = CompositeComponentStream;

                fn init(&mut self) -> Result<(), Error> {
                    for component in self.components.iter_mut() {
                        component.0.init()?;
                    }
                    Ok(())
                }

                fn stream(self, handle: Handle) -> Self::Stream {
                    CompositeComponentStream {
                        states: vec![String::new(); self.components.len()],
                        streams: self.components
                            .into_iter()
                            .enumerate()
                            .map(|(id, c)| {
                                Box::new(c.0.into_stream(handle.clone()).map(move |s| (id, s))) as
                                    Box<Stream<Item = (usize, String), Error = Error>>
                            })
                            .collect(),
                    }
                }
            }

            struct CompositeComponentStream {
                streams: Vec<Box<Stream<Item=(usize, String), Error=Error>>>,
                states: Vec<String>,
            };

            impl Stream for CompositeComponentStream {
                type Item = String;
                type Error = Error;

                fn poll(&mut self) -> Poll<Option<String>, Error> {
                    let mut do_update = false;
                    for stream in self.streams.iter_mut() {
                        match stream.poll() {
                            Ok(Async::Ready(Some((id, update)))) => {
                                do_update = true;
                                self.states[id] = update;
                            },
                            Err(err) => return Err(err),
                            _ => {},
                        }
                    }

                    match do_update {
                        true => Ok(Async::Ready(Some(self.states.concat()))),
                        false => Ok(Async::NotReady),
                    }
                }
            }

            CompositeComponent {
                components: vec![$( SubComponent::from($arg) ),+],
                stream: CompositeComponentStream {
                    streams: vec![],
                    states: vec![],
                }
            }
        }
    };
}
<commit_msg>Remove `stream` field as unused.<commit_after>#[macro_export]
macro_rules! composite {
    ( $( $arg:expr ),+ ) => {
        {
            use $crate::component::{Component, SubComponent};
            use $crate::Error;
            use ::futures::{Poll, Async};

            struct CompositeComponent {
                components: Vec<SubComponent>,
            }

            impl Component for CompositeComponent {
                type Error = Error;
                type Stream = CompositeComponentStream;

                fn init(&mut self) -> Result<(), Error> {
                    for component in self.components.iter_mut() {
                        component.0.init()?;
                    }
                    Ok(())
                }

                fn stream(self, handle: Handle) -> Self::Stream {
                    CompositeComponentStream {
                        states: vec![String::new(); self.components.len()],
                        streams: self.components
                            .into_iter()
                            .enumerate()
                            .map(|(id, c)| {
                                Box::new(c.0.into_stream(handle.clone()).map(move |s| (id, s))) as
                                    Box<Stream<Item = (usize, String), Error = Error>>
                            })
                            .collect(),
                    }
                }
            }

            struct CompositeComponentStream {
                streams: Vec<Box<Stream<Item=(usize, String), Error=Error>>>,
                states: Vec<String>,
            };

            impl Stream for CompositeComponentStream {
                type Item = String;
                type Error = Error;

                fn poll(&mut self) -> Poll<Option<String>, Error> {
                    let mut do_update = false;
                    for stream in self.streams.iter_mut() {
                        match stream.poll() {
                            Ok(Async::Ready(Some((id, update)))) => {
                                do_update = true;
                                self.states[id] = update;
                            },
                            Err(err) => return Err(err),
                            _ => {},
                        }
                    }

                    match do_update {
                        true => Ok(Async::Ready(Some(self.states.concat()))),
                        false => Ok(Async::NotReady),
                    }
                }
            }

            CompositeComponent {
                components: vec![$( SubComponent::from($arg) ),+],
            }
        }
    };
}
<|endoftext|>


<commit_before>use unicode_segmentation::UnicodeSegmentation;
use unicode_width::UnicodeWidthStr;
use odds::vec::VecExt;

use {Printer, With, XY};
use direction::Direction;
use vec::Vec2;
use event::{Event, EventResult, Key};
use utils::{LinesIterator, Row, prefix_length};
use view::{ScrollBase, SizeCache, View};
use theme::{ColorStyle, Effect};

/// Multi-lines text editor.
///
/// A `TextArea` by itself doesn't have a well-defined size.
/// You should wrap it in a `BoxView` to control its size.
pub struct TextArea {
    // TODO: use a smarter data structure (rope?)
    content: String,
    /// Byte offsets within `content` representing text rows
    rows: Vec<Row>,

    /// When `false`, we don't take any input.
    enabled: bool,

    /// Base for scrolling features
    scrollbase: ScrollBase,

    /// Cache to avoid re-computing layout on no-op events
    last_size: Option<XY<SizeCache>>,

    /// Byte offset of the currently selected grapheme.
    cursor: usize,
}

fn make_rows(text: &str, width: usize) -> Vec<Row> {
    LinesIterator::new(text, width)
        .show_spaces()
        .collect()
}

impl TextArea {
    /// Creates a new, empty TextArea.
    pub fn new() -> Self {
        TextArea {
            content: String::new(),
            rows: vec![Row {
                           start: 0,
                           end: 0,
                           width: 0,
                       }],
            enabled: true,
            scrollbase: ScrollBase::new().right_padding(0),
            last_size: None,
            cursor: 0,
        }
    }

    /// Retrieves the content of the view.
    pub fn get_content(&self) -> &str {
        &self.content
    }

    fn invalidate(&mut self) {
        self.last_size = None;
    }

    /// Sets the content of the view.
    pub fn set_content<S: Into<String>>(&mut self, content: S) {
        self.content = content.into();
        if let Some(size) = self.last_size.map(|s| s.map(|s| s.value)) {
            self.compute_rows(size);
        }
    }

    /// Sets the content of the view.
    ///
    /// Chainable variant.
    pub fn content<S: Into<String>>(self, content: S) -> Self {
        self.with(|s| s.set_content(content))
    }

    /// Finds the row containing the grapheme at the given offset
    fn row_at(&self, offset: usize) -> usize {
        // println_stderr!("Offset: {}", offset);
        self.rows
            .iter()
            .enumerate()
            .take_while(|&(_, row)| row.start <= offset)
            .map(|(i, _)| i)
            .last()
            .unwrap()
    }

    fn col_at(&self, offset: usize) -> usize {
        let row_id = self.row_at(offset);
        let row = self.rows[row_id];
        // Number of cells to the left of the cursor
        self.content[row.start..offset].width()
    }

    /// Finds the row containing the cursor
    fn selected_row(&self) -> usize {
        self.row_at(self.cursor)
    }

    fn page_up(&mut self) {
        for _ in 0..5 {
            self.move_up();
        }
    }

    fn page_down(&mut self) {
        for _ in 0..5 {
            self.move_down();
        }
    }

    fn move_up(&mut self) {
        let row_id = self.selected_row();
        if row_id == 0 {
            return;
        }

        // Number of cells to the left of the cursor
        let x = self.col_at(self.cursor);

        let prev_row = self.rows[row_id - 1];
        let prev_text = &self.content[prev_row.start..prev_row.end];
        let offset = prefix_length(prev_text.graphemes(true), x, "");
        self.cursor = prev_row.start + offset;
    }

    fn move_down(&mut self) {
        let row_id = self.selected_row();
        if row_id + 1 == self.rows.len() {
            return;
        }
        let x = self.col_at(self.cursor);

        let next_row = self.rows[row_id + 1];
        let next_text = &self.content[next_row.start..next_row.end];
        let offset = prefix_length(next_text.graphemes(true), x, "");
        self.cursor = next_row.start + offset;
    }

    /// Moves the cursor to the left.
    ///
    /// Wraps the previous line if required.
    fn move_left(&mut self) {
        let len = {
            // We don't want to utf8-parse the entire content.
            // So restrict to the last row.
            let mut row = self.selected_row();
            if self.rows[row].start == self.cursor {
                row -= 1;
            }

            let text = &self.content[self.rows[row].start..self.cursor];
            text.graphemes(true)
                .last()
                .unwrap()
                .len()
        };
        self.cursor -= len;
    }

    /// Moves the cursor to the right.
    ///
    /// Jumps to the next line is required.
    fn move_right(&mut self) {
        let len = self.content[self.cursor..]
            .graphemes(true)
            .next()
            .unwrap()
            .len();
        self.cursor += len;
    }

    fn is_cache_valid(&self, size: Vec2) -> bool {
        match self.last_size {
            None => false,
            Some(ref last) => last.x.accept(size.x) && last.y.accept(size.y),
        }
    }

    fn fix_ghost_row(&mut self) {
        if self.rows.is_empty() ||
           self.rows.last().unwrap().end != self.content.len() {
            // Add a fake, empty row at the end.
            self.rows.push(Row {
                start: self.content.len(),
                end: self.content.len(),
                width: 0,
            });
        }
    }

    fn compute_rows(&mut self, size: Vec2) {
        if self.is_cache_valid(size) {
            return;
        }
        // println_stderr!("Computing! Oh yeah!");

        let mut available = size.x;
        self.rows = make_rows(&self.content, available);
        self.fix_ghost_row();
        if self.rows.len() > size.y {
            available -= 1;
            // Doh :(
            self.rows = make_rows(&self.content, available);
            self.fix_ghost_row();
        }

        if !self.rows.is_empty() {
            self.last_size = Some(SizeCache::build(size, size));
        }
    }

    fn backspace(&mut self) {
        if self.cursor != 0 {
            self.move_left();
            self.delete();
        }
    }

    fn delete(&mut self) {
        if self.cursor == self.content.len() {
            return;
        }
        // println_stderr!("Rows: {:?}", self.rows);
        let len = self.content[self.cursor..]
            .graphemes(true)
            .next()
            .unwrap()
            .len();
        let start = self.cursor;
        let end = self.cursor + len;
        // println_stderr!("Start/end: {}/{}", start, end);
        // println_stderr!("Content: `{}`", self.content);
        for _ in self.content.drain(start..end) {}
        // println_stderr!("Content: `{}`", self.content);

        let selected_row = self.selected_row();
        // println_stderr!("Selected row: {}", selected_row);
        if self.cursor == self.rows[selected_row].end {
            // We're removing an (implicit) newline.
            // This means merging two rows.
            let new_end = self.rows[selected_row + 1].end;
            self.rows[selected_row].end = new_end;
            self.rows.remove(selected_row + 1);
        }
        self.rows[selected_row].end -= len;

        // update all the rows downstream
        for row in &mut self.rows.iter_mut().skip(1 + selected_row) {
            row.rev_shift(len);
        }
        // println_stderr!("Rows: {:?}", self.rows);

        self.fix_damages();
        // println_stderr!("Rows: {:?}", self.rows);
    }

    fn insert(&mut self, ch: char) {

        // First, we inject the data, but keep the cursor unmoved
        // (So the cursor is to the left of the injected char)
        self.content.insert(self.cursor, ch);

        // Then, we shift the indexes of every row after this one.
        let shift = ch.len_utf8();

        // The current row grows, every other is just shifted.
        let selected_row = self.selected_row();
        self.rows[selected_row].end += shift;

        for row in &mut self.rows.iter_mut().skip(1 + selected_row) {
            row.shift(shift);
        }
        self.cursor += shift;

        // Finally, rows may not have the correct width anymore, so fix them.
        self.fix_damages();
    }

    /// Fix a damage located at the cursor.
    ///
    /// The only damages are assumed to have occured around the cursor.
    fn fix_damages(&mut self) {
        if self.last_size.is_none() {
            // If we don't know our size, it means we'll get a layout command soon.
            // So no need to do that here.
            return;
        }

        let size = self.last_size.unwrap().map(|s| s.value);

        // Find affected text.
        // We know the damage started at this row, so it'll need to go.
        let mut first_row = self.selected_row();
        // Actually, if possible, also re-compute the previous row.
        // Indeed, the previous row may have been cut short, and if we now
        // break apart a big word, maybe the first half can go up one level.
        if first_row > 0 {
            first_row -= 1;
        }
        // The
        let first_byte = self.rows[first_row].start;

        // We don't need to go beyond a newline.
        // If we don't find one, end of the text it is.
        // println_stderr!("Cursor: {}", self.cursor);
        let last_byte = self.content[self.cursor..]
            .find('\n')
            .map(|i| 1 + i + self.cursor);
        let last_row = last_byte.map_or(self.rows.len(), |last_byte| self.row_at(last_byte));
        let last_byte = last_byte.unwrap_or(self.content.len());

        // println_stderr!("Content: `{}` (len={})",
        //                 self.content,
        //                 self.content.len());
        // println_stderr!("start/end: {}/{}", first_byte, last_byte);
        // println_stderr!("start/end rows: {}/{}", first_row, last_row);

        // Do we have access to the entire width?...
        let mut available = size.x;

        let scrollable = self.rows.len() > size.y;
        if scrollable {
            // ... not if a scrollbar is there
            available -= 1;
        }

        // First attempt, if scrollbase status didn't change.
        // println_stderr!("Rows: {:?}", self.rows);
        let new_rows = make_rows(&self.content[first_byte..last_byte],
                                     available);
        // How much did this add?
        // println_stderr!("New rows: {:?}", new_rows);
        // println_stderr!("{}-{}", first_row, last_row);
        let new_row_count = self.rows.len() + new_rows.len() + first_row -
                            last_row;
        if !scrollable && new_row_count > size.y {
            // We just changed scrollable status.
            // This changes everything.
            // TODO: compute_rows() currently makes a scroll-less attempt.
            // Here, we know it's just no gonna happen.
            self.invalidate();
            self.compute_rows(size);
            self.scrollbase.set_heights(size.y, self.rows.len());
            return;
        }

        // Otherwise, replace stuff.
        let affected_rows = first_row..last_row;
        let replacement_rows = new_rows.into_iter()
            .map(|row| row.shifted(first_byte));
        self.rows.splice(affected_rows, replacement_rows);
        self.fix_ghost_row();
        self.scrollbase.set_heights(size.y, self.rows.len());
    }
}

impl View for TextArea {
    fn draw(&self, printer: &Printer) {
        printer.with_color(ColorStyle::Secondary, |printer| {
            let effect = if self.enabled {
                Effect::Reverse
            } else {
                Effect::Simple
            };

            let w = if self.scrollbase.scrollable() {
                printer.size.x - 1
            } else {
                printer.size.x
            };
            printer.with_effect(effect, |printer| {
                for y in 0..printer.size.y {
                    printer.print_hline((0, y), w, " ");
                }
            });

            // println_stderr!("Content: `{}`", &self.content);
            self.scrollbase.draw(printer, |printer, i| {
                // println_stderr!("Drawing row {}", i);
                let row = &self.rows[i];
                // println_stderr!("row: {:?}", row);
                let text = &self.content[row.start..row.end];
                // println_stderr!("row text: `{}`", text);
                printer.with_effect(effect, |printer| {
                    printer.print((0, 0), text);
                });

                if printer.focused && i == self.selected_row() {
                    let cursor_offset = self.cursor - row.start;
                    let c = if cursor_offset == text.len() {
                        "_"
                    } else {
                        text[cursor_offset..]
                            .graphemes(true)
                            .next()
                            .expect("Found no char!")
                    };
                    let offset = text[..cursor_offset].width();
                    printer.print((offset, 0), c);
                }

            });
        });
    }

    fn on_event(&mut self, event: Event) -> EventResult {
        match event {
            Event::Char(ch) => self.insert(ch),
            Event::Key(Key::Enter) => self.insert('\n'),
            Event::Key(Key::Backspace) if self.cursor > 0 => self.backspace(),
            Event::Key(Key::Del) if self.cursor < self.content.len() => {
                self.delete()
            }

            Event::Key(Key::End) => {
                let row = self.selected_row();
                self.cursor = self.rows[row].end;
                if row + 1 < self.rows.len() &&
                   self.cursor == self.rows[row + 1].start {
                    self.move_left();
                }
            }
            Event::Ctrl(Key::Home) => self.cursor = 0,
            Event::Ctrl(Key::End) => self.cursor = self.content.len(),
            Event::Key(Key::Home) => {
                self.cursor = self.rows[self.selected_row()].start
            }
            Event::Key(Key::Up) if self.selected_row() > 0 => self.move_up(),
            Event::Key(Key::Down) if self.selected_row() + 1 <
                                     self.rows.len() => self.move_down(),
            Event::Key(Key::PageUp) => self.page_up(),
            Event::Key(Key::PageDown) => self.page_down(),
            Event::Key(Key::Left) if self.cursor > 0 => self.move_left(),
            Event::Key(Key::Right) if self.cursor < self.content.len() => {
                self.move_right()
            }
            _ => return EventResult::Ignored,
        }

        // println_stderr!("Rows: {:?}", self.rows);
        let focus = self.selected_row();
        self.scrollbase.scroll_to(focus);

        EventResult::Consumed(None)
    }

    fn take_focus(&mut self, _: Direction) -> bool {
        self.enabled
    }

    fn layout(&mut self, size: Vec2) {
        self.compute_rows(size);
        self.scrollbase.set_heights(size.y, self.rows.len());
    }
}
<commit_msg>Fix clippy warning<commit_after>use unicode_segmentation::UnicodeSegmentation;
use unicode_width::UnicodeWidthStr;
use odds::vec::VecExt;

use {Printer, With, XY};
use direction::Direction;
use vec::Vec2;
use event::{Event, EventResult, Key};
use utils::{LinesIterator, Row, prefix_length};
use view::{ScrollBase, SizeCache, View};
use theme::{ColorStyle, Effect};

/// Multi-lines text editor.
///
/// A `TextArea` by itself doesn't have a well-defined size.
/// You should wrap it in a `BoxView` to control its size.
pub struct TextArea {
    // TODO: use a smarter data structure (rope?)
    content: String,
    /// Byte offsets within `content` representing text rows
    rows: Vec<Row>,

    /// When `false`, we don't take any input.
    enabled: bool,

    /// Base for scrolling features
    scrollbase: ScrollBase,

    /// Cache to avoid re-computing layout on no-op events
    last_size: Option<XY<SizeCache>>,

    /// Byte offset of the currently selected grapheme.
    cursor: usize,
}

fn make_rows(text: &str, width: usize) -> Vec<Row> {
    LinesIterator::new(text, width)
        .show_spaces()
        .collect()
}

new_default!(TextArea);

impl TextArea {
    /// Creates a new, empty TextArea.
    pub fn new() -> Self {
        TextArea {
            content: String::new(),
            rows: vec![Row {
                           start: 0,
                           end: 0,
                           width: 0,
                       }],
            enabled: true,
            scrollbase: ScrollBase::new().right_padding(0),
            last_size: None,
            cursor: 0,
        }
    }

    /// Retrieves the content of the view.
    pub fn get_content(&self) -> &str {
        &self.content
    }

    fn invalidate(&mut self) {
        self.last_size = None;
    }

    /// Sets the content of the view.
    pub fn set_content<S: Into<String>>(&mut self, content: S) {
        self.content = content.into();
        if let Some(size) = self.last_size.map(|s| s.map(|s| s.value)) {
            self.compute_rows(size);
        }
    }

    /// Sets the content of the view.
    ///
    /// Chainable variant.
    pub fn content<S: Into<String>>(self, content: S) -> Self {
        self.with(|s| s.set_content(content))
    }

    /// Finds the row containing the grapheme at the given offset
    fn row_at(&self, offset: usize) -> usize {
        // println_stderr!("Offset: {}", offset);
        self.rows
            .iter()
            .enumerate()
            .take_while(|&(_, row)| row.start <= offset)
            .map(|(i, _)| i)
            .last()
            .unwrap()
    }

    fn col_at(&self, offset: usize) -> usize {
        let row_id = self.row_at(offset);
        let row = self.rows[row_id];
        // Number of cells to the left of the cursor
        self.content[row.start..offset].width()
    }

    /// Finds the row containing the cursor
    fn selected_row(&self) -> usize {
        self.row_at(self.cursor)
    }

    fn page_up(&mut self) {
        for _ in 0..5 {
            self.move_up();
        }
    }

    fn page_down(&mut self) {
        for _ in 0..5 {
            self.move_down();
        }
    }

    fn move_up(&mut self) {
        let row_id = self.selected_row();
        if row_id == 0 {
            return;
        }

        // Number of cells to the left of the cursor
        let x = self.col_at(self.cursor);

        let prev_row = self.rows[row_id - 1];
        let prev_text = &self.content[prev_row.start..prev_row.end];
        let offset = prefix_length(prev_text.graphemes(true), x, "");
        self.cursor = prev_row.start + offset;
    }

    fn move_down(&mut self) {
        let row_id = self.selected_row();
        if row_id + 1 == self.rows.len() {
            return;
        }
        let x = self.col_at(self.cursor);

        let next_row = self.rows[row_id + 1];
        let next_text = &self.content[next_row.start..next_row.end];
        let offset = prefix_length(next_text.graphemes(true), x, "");
        self.cursor = next_row.start + offset;
    }

    /// Moves the cursor to the left.
    ///
    /// Wraps the previous line if required.
    fn move_left(&mut self) {
        let len = {
            // We don't want to utf8-parse the entire content.
            // So restrict to the last row.
            let mut row = self.selected_row();
            if self.rows[row].start == self.cursor {
                row -= 1;
            }

            let text = &self.content[self.rows[row].start..self.cursor];
            text.graphemes(true)
                .last()
                .unwrap()
                .len()
        };
        self.cursor -= len;
    }

    /// Moves the cursor to the right.
    ///
    /// Jumps to the next line is required.
    fn move_right(&mut self) {
        let len = self.content[self.cursor..]
            .graphemes(true)
            .next()
            .unwrap()
            .len();
        self.cursor += len;
    }

    fn is_cache_valid(&self, size: Vec2) -> bool {
        match self.last_size {
            None => false,
            Some(ref last) => last.x.accept(size.x) && last.y.accept(size.y),
        }
    }

    fn fix_ghost_row(&mut self) {
        if self.rows.is_empty() ||
           self.rows.last().unwrap().end != self.content.len() {
            // Add a fake, empty row at the end.
            self.rows.push(Row {
                start: self.content.len(),
                end: self.content.len(),
                width: 0,
            });
        }
    }

    fn compute_rows(&mut self, size: Vec2) {
        if self.is_cache_valid(size) {
            return;
        }
        // println_stderr!("Computing! Oh yeah!");

        let mut available = size.x;
        self.rows = make_rows(&self.content, available);
        self.fix_ghost_row();
        if self.rows.len() > size.y {
            available -= 1;
            // Doh :(
            self.rows = make_rows(&self.content, available);
            self.fix_ghost_row();
        }

        if !self.rows.is_empty() {
            self.last_size = Some(SizeCache::build(size, size));
        }
    }

    fn backspace(&mut self) {
        if self.cursor != 0 {
            self.move_left();
            self.delete();
        }
    }

    fn delete(&mut self) {
        if self.cursor == self.content.len() {
            return;
        }
        // println_stderr!("Rows: {:?}", self.rows);
        let len = self.content[self.cursor..]
            .graphemes(true)
            .next()
            .unwrap()
            .len();
        let start = self.cursor;
        let end = self.cursor + len;
        // println_stderr!("Start/end: {}/{}", start, end);
        // println_stderr!("Content: `{}`", self.content);
        for _ in self.content.drain(start..end) {}
        // println_stderr!("Content: `{}`", self.content);

        let selected_row = self.selected_row();
        // println_stderr!("Selected row: {}", selected_row);
        if self.cursor == self.rows[selected_row].end {
            // We're removing an (implicit) newline.
            // This means merging two rows.
            let new_end = self.rows[selected_row + 1].end;
            self.rows[selected_row].end = new_end;
            self.rows.remove(selected_row + 1);
        }
        self.rows[selected_row].end -= len;

        // update all the rows downstream
        for row in &mut self.rows.iter_mut().skip(1 + selected_row) {
            row.rev_shift(len);
        }
        // println_stderr!("Rows: {:?}", self.rows);

        self.fix_damages();
        // println_stderr!("Rows: {:?}", self.rows);
    }

    fn insert(&mut self, ch: char) {

        // First, we inject the data, but keep the cursor unmoved
        // (So the cursor is to the left of the injected char)
        self.content.insert(self.cursor, ch);

        // Then, we shift the indexes of every row after this one.
        let shift = ch.len_utf8();

        // The current row grows, every other is just shifted.
        let selected_row = self.selected_row();
        self.rows[selected_row].end += shift;

        for row in &mut self.rows.iter_mut().skip(1 + selected_row) {
            row.shift(shift);
        }
        self.cursor += shift;

        // Finally, rows may not have the correct width anymore, so fix them.
        self.fix_damages();
    }

    /// Fix a damage located at the cursor.
    ///
    /// The only damages are assumed to have occured around the cursor.
    fn fix_damages(&mut self) {
        if self.last_size.is_none() {
            // If we don't know our size, it means we'll get a layout command soon.
            // So no need to do that here.
            return;
        }

        let size = self.last_size.unwrap().map(|s| s.value);

        // Find affected text.
        // We know the damage started at this row, so it'll need to go.
        let mut first_row = self.selected_row();
        // Actually, if possible, also re-compute the previous row.
        // Indeed, the previous row may have been cut short, and if we now
        // break apart a big word, maybe the first half can go up one level.
        if first_row > 0 {
            first_row -= 1;
        }
        // The
        let first_byte = self.rows[first_row].start;

        // We don't need to go beyond a newline.
        // If we don't find one, end of the text it is.
        // println_stderr!("Cursor: {}", self.cursor);
        let last_byte = self.content[self.cursor..]
            .find('\n')
            .map(|i| 1 + i + self.cursor);
        let last_row = last_byte.map_or(self.rows.len(), |last_byte| self.row_at(last_byte));
        let last_byte = last_byte.unwrap_or(self.content.len());

        // println_stderr!("Content: `{}` (len={})",
        //                 self.content,
        //                 self.content.len());
        // println_stderr!("start/end: {}/{}", first_byte, last_byte);
        // println_stderr!("start/end rows: {}/{}", first_row, last_row);

        // Do we have access to the entire width?...
        let mut available = size.x;

        let scrollable = self.rows.len() > size.y;
        if scrollable {
            // ... not if a scrollbar is there
            available -= 1;
        }

        // First attempt, if scrollbase status didn't change.
        // println_stderr!("Rows: {:?}", self.rows);
        let new_rows = make_rows(&self.content[first_byte..last_byte],
                                     available);
        // How much did this add?
        // println_stderr!("New rows: {:?}", new_rows);
        // println_stderr!("{}-{}", first_row, last_row);
        let new_row_count = self.rows.len() + new_rows.len() + first_row -
                            last_row;
        if !scrollable && new_row_count > size.y {
            // We just changed scrollable status.
            // This changes everything.
            // TODO: compute_rows() currently makes a scroll-less attempt.
            // Here, we know it's just no gonna happen.
            self.invalidate();
            self.compute_rows(size);
            self.scrollbase.set_heights(size.y, self.rows.len());
            return;
        }

        // Otherwise, replace stuff.
        let affected_rows = first_row..last_row;
        let replacement_rows = new_rows.into_iter()
            .map(|row| row.shifted(first_byte));
        self.rows.splice(affected_rows, replacement_rows);
        self.fix_ghost_row();
        self.scrollbase.set_heights(size.y, self.rows.len());
    }
}

impl View for TextArea {
    fn draw(&self, printer: &Printer) {
        printer.with_color(ColorStyle::Secondary, |printer| {
            let effect = if self.enabled {
                Effect::Reverse
            } else {
                Effect::Simple
            };

            let w = if self.scrollbase.scrollable() {
                printer.size.x - 1
            } else {
                printer.size.x
            };
            printer.with_effect(effect, |printer| {
                for y in 0..printer.size.y {
                    printer.print_hline((0, y), w, " ");
                }
            });

            // println_stderr!("Content: `{}`", &self.content);
            self.scrollbase.draw(printer, |printer, i| {
                // println_stderr!("Drawing row {}", i);
                let row = &self.rows[i];
                // println_stderr!("row: {:?}", row);
                let text = &self.content[row.start..row.end];
                // println_stderr!("row text: `{}`", text);
                printer.with_effect(effect, |printer| {
                    printer.print((0, 0), text);
                });

                if printer.focused && i == self.selected_row() {
                    let cursor_offset = self.cursor - row.start;
                    let c = if cursor_offset == text.len() {
                        "_"
                    } else {
                        text[cursor_offset..]
                            .graphemes(true)
                            .next()
                            .expect("Found no char!")
                    };
                    let offset = text[..cursor_offset].width();
                    printer.print((offset, 0), c);
                }

            });
        });
    }

    fn on_event(&mut self, event: Event) -> EventResult {
        match event {
            Event::Char(ch) => self.insert(ch),
            Event::Key(Key::Enter) => self.insert('\n'),
            Event::Key(Key::Backspace) if self.cursor > 0 => self.backspace(),
            Event::Key(Key::Del) if self.cursor < self.content.len() => {
                self.delete()
            }

            Event::Key(Key::End) => {
                let row = self.selected_row();
                self.cursor = self.rows[row].end;
                if row + 1 < self.rows.len() &&
                   self.cursor == self.rows[row + 1].start {
                    self.move_left();
                }
            }
            Event::Ctrl(Key::Home) => self.cursor = 0,
            Event::Ctrl(Key::End) => self.cursor = self.content.len(),
            Event::Key(Key::Home) => {
                self.cursor = self.rows[self.selected_row()].start
            }
            Event::Key(Key::Up) if self.selected_row() > 0 => self.move_up(),
            Event::Key(Key::Down) if self.selected_row() + 1 <
                                     self.rows.len() => self.move_down(),
            Event::Key(Key::PageUp) => self.page_up(),
            Event::Key(Key::PageDown) => self.page_down(),
            Event::Key(Key::Left) if self.cursor > 0 => self.move_left(),
            Event::Key(Key::Right) if self.cursor < self.content.len() => {
                self.move_right()
            }
            _ => return EventResult::Ignored,
        }

        // println_stderr!("Rows: {:?}", self.rows);
        let focus = self.selected_row();
        self.scrollbase.scroll_to(focus);

        EventResult::Consumed(None)
    }

    fn take_focus(&mut self, _: Direction) -> bool {
        self.enabled
    }

    fn layout(&mut self, size: Vec2) {
        self.compute_rows(size);
        self.scrollbase.set_heights(size.y, self.rows.len());
    }
}
<|endoftext|>


<commit_before>use std::error;
use std::f64;
use std::fs::File;
use std::io::Read;
use std::result;
use std::str::FromStr;
use std::time::Duration;

use tokio_timer::Timer;

use Cnx;
use errors::*;
use text::{Attributes, Color, Text};

#[derive(Clone, Debug, Eq, PartialEq)]
enum Status {
    Full,
    Charging,
    Discharging,
    Unknown,
}

impl FromStr for Status {
    type Err = Error;

    fn from_str(s: &str) -> result::Result<Self, Self::Err> {
        match s {
            "Full" => Ok(Status::Full),
            "Charging" => Ok(Status::Charging),
            "Discharging" => Ok(Status::Discharging),
            "Unknown" => Ok(Status::Unknown),
            _ => bail!("Unknown Status: {}", s),
        }
    }
}

/// Shows battery charge percentage and (dis)charge time.
///
/// This widget shows the battery's current charge percentage and the amount of
/// remaining (dis)charge time, depending on whether the battery is charging or
/// discharging. The format of the output is `(PP% HH:MM)`.
///
/// When the battery has less than 10% charge remaining, the widget's text will
/// change to the specified `warning_color`.
///
/// Battery charge information is read from [`/sys/class/power_supply/BAT0/`].
///
/// [`/sys/class/power_supply/BAT0/`]: https://www.kernel.org/doc/Documentation/power/power_supply_class.txt
pub struct Battery {
    timer: Timer,
    update_interval: Duration,
    battery: String,
    attr: Attributes,
    warning_color: Color,
}

impl Battery {
    ///  Creates a new Battery widget.
    ///
    ///  Creates a new `Battery` widget, whose text will be displayed with the
    ///  given [`Attributes`]. The caller can provide use the `warning_color`
    ///  argument, to control the [`Color`] of the text once the battery has
    ///  less than 10% charge remaining.
    ///
    ///  The [`Cnx`] instance is borrowed during construction in order to get
    ///  access to handles of its event loop. However, it is not borrowed for
    ///  the lifetime of the widget. See the [`cnx_add_widget!()`] for more
    ///  discussion about the lifetime of the borrow.
    ///
    /// [`Attributes`]: ../text/struct.Attributes.html
    /// [`Color`]: ../text/struct.Color.html
    /// [`Cnx`]: ../struct.Cnx.html
    /// [`cnx_add_widget!()`]: ../macro.cnx_add_widget.html
    ///
    /// # Examples
    ///
    /// ```
    /// # #[macro_use]
    /// # extern crate cnx;
    /// #
    /// # use cnx::*;
    /// # use cnx::text::*;
    /// # use cnx::widgets::*;
    /// #
    /// # fn run() -> ::cnx::errors::Result<()> {
    /// let attr = Attributes {
    ///     font: Font::new("SourceCodePro 21"),
    ///     fg_color: Color::white(),
    ///     bg_color: None,
    ///     padding: Padding::new(8.0, 8.0, 0.0, 0.0),
    /// };
    ///
    /// let mut cnx = Cnx::new(Position::Top)?;
    /// cnx_add_widget!(cnx, Battery::new(&cnx, attr.clone(), Color::red()));
    /// # Ok(())
    /// # }
    /// # fn main() { run().unwrap(); }
    /// ```
    pub fn new(cnx: &Cnx, attr: Attributes, warning_color: Color) -> Battery {
        Battery {
            timer: cnx.timer(),
            update_interval: Duration::from_secs(60),
            battery: "BAT0".to_owned(),
            attr,
            warning_color,
        }
    }

    fn load_value_inner<T>(&self, file: &str) -> Result<T>
    where
        T: FromStr,
        <T as FromStr>::Err: error::Error + Send + 'static,
    {
        let path = format!("/sys/class/power_supply/{}/{}", self.battery, file);
        let mut file = File::open(path)?;
        let mut contents = String::new();
        file.read_to_string(&mut contents)?;
        FromStr::from_str(contents.trim()).chain_err(|| "Failed to parse value")
    }

    fn load_value<T>(&self, file: &str) -> Result<T>
    where
        T: FromStr,
        <T as FromStr>::Err: error::Error + Send + 'static,
    {
        self.load_value_inner(file)
            .chain_err(|| format!("Could not load value from battery status file: {}", file))
    }

    fn tick(&self) -> Result<Vec<Text>> {
        let full: f64 = self.load_value("energy_full")?;
        let now: f64 = self.load_value("energy_now")?;
        let percentage = (now / full) * 100.0;

        // If we're discharging, show time to empty.
        // If we're charging, show time to full.
        let power: f64 = self.load_value("power_now")?;
        let status: Status = self.load_value("status")?;
        let time = match status {
            Status::Discharging => now / power,
            Status::Charging => (full - now) / power,
            _ => 0.0,
        };
        let hours = time as u64;
        let minutes = (time * 60.0) as u64 % 60;

        let text = format!(
            "({percentage:.0}% - {hours}:{minutes:02})",
            percentage = percentage,
            hours = hours,
            minutes = minutes
        );

        // If we're discharging and have <=10% left, then render with a
        // special warning color.
        let mut attr = self.attr.clone();
        if status == Status::Discharging && percentage <= 10.0 {
            attr.fg_color = self.warning_color.clone()
        }

        Ok(vec![
            Text {
                attr,
                text,
                stretch: false,
            },
        ])
    }
}

timer_widget!(Battery, timer, update_interval, tick);
<commit_msg>Update battery files.<commit_after>use std::error;
use std::f64;
use std::fs::File;
use std::io::Read;
use std::result;
use std::str::FromStr;
use std::time::Duration;

use tokio_timer::Timer;

use Cnx;
use errors::*;
use text::{Attributes, Color, Text};

#[derive(Clone, Debug, Eq, PartialEq)]
enum Status {
    Full,
    Charging,
    Discharging,
    Unknown,
}

impl FromStr for Status {
    type Err = Error;

    fn from_str(s: &str) -> result::Result<Self, Self::Err> {
        match s {
            "Full" => Ok(Status::Full),
            "Charging" => Ok(Status::Charging),
            "Discharging" => Ok(Status::Discharging),
            "Unknown" => Ok(Status::Unknown),
            _ => bail!("Unknown Status: {}", s),
        }
    }
}

/// Shows battery charge percentage and (dis)charge time.
///
/// This widget shows the battery's current charge percentage and the amount of
/// remaining (dis)charge time, depending on whether the battery is charging or
/// discharging. The format of the output is `(PP% HH:MM)`.
///
/// When the battery has less than 10% charge remaining, the widget's text will
/// change to the specified `warning_color`.
///
/// Battery charge information is read from [`/sys/class/power_supply/BAT0/`].
///
/// [`/sys/class/power_supply/BAT0/`]: https://www.kernel.org/doc/Documentation/power/power_supply_class.txt
pub struct Battery {
    timer: Timer,
    update_interval: Duration,
    battery: String,
    attr: Attributes,
    warning_color: Color,
}

impl Battery {
    ///  Creates a new Battery widget.
    ///
    ///  Creates a new `Battery` widget, whose text will be displayed with the
    ///  given [`Attributes`]. The caller can provide use the `warning_color`
    ///  argument, to control the [`Color`] of the text once the battery has
    ///  less than 10% charge remaining.
    ///
    ///  The [`Cnx`] instance is borrowed during construction in order to get
    ///  access to handles of its event loop. However, it is not borrowed for
    ///  the lifetime of the widget. See the [`cnx_add_widget!()`] for more
    ///  discussion about the lifetime of the borrow.
    ///
    /// [`Attributes`]: ../text/struct.Attributes.html
    /// [`Color`]: ../text/struct.Color.html
    /// [`Cnx`]: ../struct.Cnx.html
    /// [`cnx_add_widget!()`]: ../macro.cnx_add_widget.html
    ///
    /// # Examples
    ///
    /// ```
    /// # #[macro_use]
    /// # extern crate cnx;
    /// #
    /// # use cnx::*;
    /// # use cnx::text::*;
    /// # use cnx::widgets::*;
    /// #
    /// # fn run() -> ::cnx::errors::Result<()> {
    /// let attr = Attributes {
    ///     font: Font::new("SourceCodePro 21"),
    ///     fg_color: Color::white(),
    ///     bg_color: None,
    ///     padding: Padding::new(8.0, 8.0, 0.0, 0.0),
    /// };
    ///
    /// let mut cnx = Cnx::new(Position::Top)?;
    /// cnx_add_widget!(cnx, Battery::new(&cnx, attr.clone(), Color::red()));
    /// # Ok(())
    /// # }
    /// # fn main() { run().unwrap(); }
    /// ```
    pub fn new(cnx: &Cnx, attr: Attributes, warning_color: Color) -> Battery {
        Battery {
            timer: cnx.timer(),
            update_interval: Duration::from_secs(60),
            battery: "BAT0".to_owned(),
            attr,
            warning_color,
        }
    }

    fn load_value_inner<T>(&self, file: &str) -> Result<T>
    where
        T: FromStr,
        <T as FromStr>::Err: error::Error + Send + 'static,
    {
        let path = format!("/sys/class/power_supply/{}/{}", self.battery, file);
        let mut file = File::open(path)?;
        let mut contents = String::new();
        file.read_to_string(&mut contents)?;
        FromStr::from_str(contents.trim()).chain_err(|| "Failed to parse value")
    }

    fn load_value<T>(&self, file: &str) -> Result<T>
    where
        T: FromStr,
        <T as FromStr>::Err: error::Error + Send + 'static,
    {
        self.load_value_inner(file)
            .chain_err(|| format!("Could not load value from battery status file: {}", file))
    }

    fn tick(&self) -> Result<Vec<Text>> {
        let full: f64 = self.load_value("charge_full")?;
        let now: f64 = self.load_value("charge_now")?;
        let percentage = (now / full) * 100.0;

        // If we're discharging, show time to empty.
        // If we're charging, show time to full.
        let power: f64 = self.load_value("current_avg")?;
        let status: Status = self.load_value("status")?;
        let time = match status {
            Status::Discharging => now / power,
            Status::Charging => (full - now) / power,
            _ => 0.0,
        };
        let hours = time as u64;
        let minutes = (time * 60.0) as u64 % 60;

        let text = format!(
            "({percentage:.0}% - {hours}:{minutes:02})",
            percentage = percentage,
            hours = hours,
            minutes = minutes
        );

        // If we're discharging and have <=10% left, then render with a
        // special warning color.
        let mut attr = self.attr.clone();
        if status == Status::Discharging && percentage <= 10.0 {
            attr.fg_color = self.warning_color.clone()
        }

        Ok(vec![
            Text {
                attr,
                text,
                stretch: false,
            },
        ])
    }
}

timer_widget!(Battery, timer, update_interval, tick);
<|endoftext|>