<commit_before>use clap::{App, AppSettings, Arg, SubCommand};
use error::Error;
use glob::glob;
use std::path::Path;
use std::process::exit;
use std::{env, fs, u64};
use tree_sitter::Language;
use tree_sitter_cli::{
    config, error, generate, highlight, loader, logger, parse, query, tags, test, test_highlight,
    wasm, web_ui,
};

const BUILD_VERSION: &'static str = env!("CARGO_PKG_VERSION");
const BUILD_SHA: Option<&'static str> = option_env!("BUILD_SHA");

fn main() {
    if let Err(e) = run() {
        if !e.message().is_empty() {
            println!("");
            eprintln!("{}", e.message());
        }
        exit(1);
    }
}

fn run() -> error::Result<()> {
    let version = if let Some(build_sha) = BUILD_SHA {
        format!("{} ({})", BUILD_VERSION, build_sha)
    } else {
        BUILD_VERSION.to_string()
    };

    let matches = App::new("tree-sitter")
        .version(version.as_str())
        .setting(AppSettings::SubcommandRequiredElseHelp)
        .author("Max Brunsfeld <maxbrunsfeld@gmail.com>")
        .about("Generates and tests parsers")
        .subcommand(SubCommand::with_name("init-config").about("Generate a default config file"))
        .subcommand(
            SubCommand::with_name("generate")
                .about("Generate a parser")
                .arg(Arg::with_name("grammar-path").index(1))
                .arg(Arg::with_name("log").long("log"))
                .arg(Arg::with_name("prev-abi").long("prev-abi"))
                .arg(
                    Arg::with_name("report-states-for-rule")
                        .long("report-states-for-rule")
                        .value_name("rule-name")
                        .takes_value(true),
                )
                .arg(Arg::with_name("no-minimize").long("no-minimize")),
        )
        .subcommand(
            SubCommand::with_name("parse")
                .about("Parse files")
                .arg(
                    Arg::with_name("path")
                        .index(1)
                        .multiple(true)
                        .required(true),
                )
                .arg(Arg::with_name("scope").long("scope").takes_value(true))
                .arg(Arg::with_name("debug").long("debug").short("d"))
                .arg(Arg::with_name("debug-graph").long("debug-graph").short("D"))
                .arg(Arg::with_name("quiet").long("quiet").short("q"))
                .arg(Arg::with_name("time").long("time").short("t"))
                .arg(Arg::with_name("allow-cancellation").long("cancel"))
                .arg(Arg::with_name("timeout").long("timeout").takes_value(true))
                .arg(
                    Arg::with_name("edits")
                        .long("edit")
                        .short("edit")
                        .takes_value(true)
                        .multiple(true)
                        .number_of_values(1),
                ),
        )
        .subcommand(
            SubCommand::with_name("query")
                .about("Search files using a syntax tree query")
                .arg(Arg::with_name("query-path").index(1).required(true))
                .arg(
                    Arg::with_name("path")
                        .index(2)
                        .multiple(true)
                        .required(true),
                )
                .arg(Arg::with_name("scope").long("scope").takes_value(true))
                .arg(Arg::with_name("captures").long("captures").short("c")),
        )
        .subcommand(
            SubCommand::with_name("tags")
                .arg(
                    Arg::with_name("format")
                        .short("f")
                        .long("format")
                        .value_name("json|protobuf")
                        .help("Determine output format (default: json)"),
                )
                .arg(Arg::with_name("quiet").long("quiet").short("q"))
                .arg(Arg::with_name("time").long("quiet").short("t"))
                .arg(Arg::with_name("scope").long("scope").takes_value(true))
                .arg(
                    Arg::with_name("inputs")
                        .help("The source file to use")
                        .index(1)
                        .required(true)
                        .multiple(true),
                )
                .arg(
                    Arg::with_name("v")
                        .short("v")
                        .multiple(true)
                        .help("Sets the level of verbosity"),
                ),
        )
        .subcommand(
            SubCommand::with_name("test")
                .about("Run a parser's tests")
                .arg(
                    Arg::with_name("filter")
                        .long("filter")
                        .short("f")
                        .takes_value(true),
                )
                .arg(Arg::with_name("debug").long("debug").short("d"))
                .arg(Arg::with_name("debug-graph").long("debug-graph").short("D")),
        )
        .subcommand(
            SubCommand::with_name("highlight")
                .about("Highlight a file")
                .arg(
                    Arg::with_name("path")
                        .index(1)
                        .multiple(true)
                        .required(true),
                )
                .arg(Arg::with_name("scope").long("scope").takes_value(true))
                .arg(Arg::with_name("html").long("html").short("h"))
                .arg(Arg::with_name("time").long("time").short("t"))
                .arg(Arg::with_name("q").short("q")),
        )
        .subcommand(
            SubCommand::with_name("build-wasm")
                .about("Compile a parser to WASM")
                .arg(
                    Arg::with_name("docker")
                        .long("docker")
                        .help("Run emscripten via docker even if it is installed locally"),
                )
                .arg(Arg::with_name("path").index(1).multiple(true)),
        )
        .subcommand(
            SubCommand::with_name("web-ui")
                .about("Test a parser interactively in the browser")
                .arg(
                    Arg::with_name("quiet")
                        .long("quiet")
                        .short("q")
                        .help("open in default browser"),
                ),
        )
        .subcommand(
            SubCommand::with_name("dump-languages")
                .about("Print info about all known language parsers"),
        )
        .get_matches();

    let home_dir = dirs::home_dir().expect("Failed to read home directory");
    let current_dir = env::current_dir().unwrap();
    let config = config::Config::load(&home_dir);
    let mut loader = loader::Loader::new(config.binary_directory.clone());

    if matches.subcommand_matches("init-config").is_some() {
        let config = config::Config::new(&home_dir);
        config.save(&home_dir)?;
    } else if let Some(matches) = matches.subcommand_matches("generate") {
        let grammar_path = matches.value_of("grammar-path");
        let report_symbol_name = matches.value_of("report-states-for-rule").or_else(|| {
            if matches.is_present("report-states") {
                Some("")
            } else {
                None
            }
        });
        if matches.is_present("log") {
            logger::init();
        }
        let prev_abi = matches.is_present("prev-abi");
        generate::generate_parser_in_directory(
            &current_dir,
            grammar_path,
            !prev_abi,
            report_symbol_name,
        )?;
    } else if let Some(matches) = matches.subcommand_matches("test") {
        let debug = matches.is_present("debug");
        let debug_graph = matches.is_present("debug-graph");
        let filter = matches.value_of("filter");
        let languages = loader.languages_at_path(&current_dir)?;
        let language = languages
            .first()
            .ok_or_else(|| "No language found".to_string())?;
        let test_dir = current_dir.join("test");

        // Run the corpus tests. Look for them at two paths: `test/corpus` and `corpus`.
        let mut test_corpus_dir = test_dir.join("corpus");
        if !test_corpus_dir.is_dir() {
            test_corpus_dir = current_dir.join("corpus");
        }
        if test_corpus_dir.is_dir() {
            test::run_tests_at_path(*language, &test_corpus_dir, debug, debug_graph, filter)?;
        }

        // Check that all of the queries are valid.
        test::check_queries_at_path(*language, &current_dir.join("queries"))?;

        // Run the syntax highlighting tests.
        let test_highlight_dir = test_dir.join("highlight");
        if test_highlight_dir.is_dir() {
            test_highlight::test_highlights(&loader, &test_highlight_dir)?;
        }
    } else if let Some(matches) = matches.subcommand_matches("parse") {
        let debug = matches.is_present("debug");
        let debug_graph = matches.is_present("debug-graph");
        let quiet = matches.is_present("quiet");
        let time = matches.is_present("time");
        let edits = matches
            .values_of("edits")
            .map_or(Vec::new(), |e| e.collect());
        let allow_cancellation = matches.is_present("allow-cancellation");
        let timeout = matches
            .value_of("timeout")
            .map_or(0, |t| u64::from_str_radix(t, 10).unwrap());
        let paths = collect_paths(matches.values_of("path").unwrap())?;
        let max_path_length = paths.iter().map(|p| p.chars().count()).max().unwrap();
        let mut has_error = false;
        loader.find_all_languages(&config.parser_directories)?;
        for path in paths {
            let path = Path::new(&path);
            let language =
                select_language(&mut loader, path, &current_dir, matches.value_of("scope"))?;
            has_error |= parse::parse_file_at_path(
                language,
                path,
                &edits,
                max_path_length,
                quiet,
                time,
                timeout,
                debug,
                debug_graph,
                allow_cancellation,
            )?;
        }
        if has_error {
            return Error::err(String::new());
        }
    } else if let Some(matches) = matches.subcommand_matches("query") {
        let ordered_captures = matches.values_of("captures").is_some();
        let paths = matches
            .values_of("path")
            .unwrap()
            .into_iter()
            .map(Path::new)
            .collect::<Vec<&Path>>();
        loader.find_all_languages(&config.parser_directories)?;
        let language = select_language(
            &mut loader,
            paths[0],
            &current_dir,
            matches.value_of("scope"),
        )?;
        let query_path = Path::new(matches.value_of("query-path").unwrap());
        query::query_files_at_paths(language, paths, query_path, ordered_captures)?;
    } else if let Some(matches) = matches.subcommand_matches("tags") {
        loader.find_all_languages(&config.parser_directories)?;
        let paths = collect_paths(matches.values_of("inputs").unwrap())?;
        tags::generate_tags(
            &loader,
            matches.value_of("scope"),
            &paths,
            matches.is_present("quiet"),
            matches.is_present("time"),
        )?;
    } else if let Some(matches) = matches.subcommand_matches("highlight") {
        loader.configure_highlights(&config.theme.highlight_names);
        loader.find_all_languages(&config.parser_directories)?;

        let time = matches.is_present("time");
        let paths = collect_paths(matches.values_of("path").unwrap())?;
        let html_mode = matches.is_present("html");
        if html_mode {
            println!("{}", highlight::HTML_HEADER);
        }

        let mut lang = None;
        if let Some(scope) = matches.value_of("scope") {
            lang = loader.language_configuration_for_scope(scope)?;
            if lang.is_none() {
                return Error::err(format!("Unknown scope '{}'", scope));
            }
        }

        for path in paths {
            let path = Path::new(&path);
            let (language, language_config) = match lang {
                Some(v) => v,
                None => match loader.language_configuration_for_file_name(path)? {
                    Some(v) => v,
                    None => {
                        eprintln!("No language found for path {:?}", path);
                        continue;
                    }
                },
            };

            if let Some(highlight_config) = language_config.highlight_config(language)? {
                let source = fs::read(path)?;
                if html_mode {
                    highlight::html(&loader, &config.theme, &source, highlight_config, time)?;
                } else {
                    highlight::ansi(&loader, &config.theme, &source, highlight_config, time)?;
                }
            } else {
                eprintln!("No syntax highlighting config found for path {:?}", path);
            }
        }

        if html_mode {
            println!("{}", highlight::HTML_FOOTER);
        }
    } else if let Some(matches) = matches.subcommand_matches("build-wasm") {
        let grammar_path = current_dir.join(matches.value_of("path").unwrap_or(""));
        wasm::compile_language_to_wasm(&grammar_path, matches.is_present("docker"))?;
    } else if let Some(matches) = matches.subcommand_matches("web-ui") {
        let open_in_browser = !matches.is_present("quiet");
        web_ui::serve(&current_dir, open_in_browser);
    } else if matches.subcommand_matches("dump-languages").is_some() {
        loader.find_all_languages(&config.parser_directories)?;
        for (configuration, language_path) in loader.get_all_language_configurations() {
            println!(
                concat!(
                    "scope: {}\n",
                    "parser: {:?}\n",
                    "highlights: {:?}\n",
                    "file_types: {:?}\n",
                    "content_regex: {:?}\n",
                    "injection_regex: {:?}\n",
                ),
                configuration.scope.as_ref().unwrap_or(&String::new()),
                language_path,
                configuration.highlights_filenames,
                configuration.file_types,
                configuration.content_regex,
                configuration.injection_regex,
            );
        }
    }

    Ok(())
}

fn collect_paths<'a>(paths: impl Iterator<Item = &'a str>) -> error::Result<Vec<String>> {
    let mut result = Vec::new();

    let mut incorporate_path = |path: &str, positive| {
        if positive {
            result.push(path.to_string());
        } else {
            if let Some(index) = result.iter().position(|p| p == path) {
                result.remove(index);
            }
        }
    };

    for mut path in paths {
        let mut positive = true;
        if path.starts_with("!") {
            positive = false;
            path = path.trim_start_matches("!");
        }

        if Path::new(path).exists() {
            incorporate_path(path, positive);
        } else {
            let paths =
                glob(path).map_err(Error::wrap(|| format!("Invalid glob pattern {:?}", path)))?;
            for path in paths {
                if let Some(path) = path?.to_str() {
                    incorporate_path(path, positive);
                }
            }
        }
    }
    Ok(result)
}

fn select_language(
    loader: &mut loader::Loader,
    path: &Path,
    current_dir: &Path,
    scope: Option<&str>,
) -> Result<Language, Error> {
    if let Some(scope) = scope {
        if let Some(config) =
            loader
                .language_configuration_for_scope(scope)
                .map_err(Error::wrap(|| {
                    format!("Failed to load language for scope '{}'", scope)
                }))?
        {
            Ok(config.0)
        } else {
            return Error::err(format!("Unknown scope '{}'", scope));
        }
    } else if let Some((lang, _)) =
        loader
            .language_configuration_for_file_name(path)
            .map_err(Error::wrap(|| {
                format!(
                    "Failed to load language for file name {:?}",
                    path.file_name().unwrap()
                )
            }))?
    {
        Ok(lang)
    } else if let Some(lang) = loader
        .languages_at_path(&current_dir)
        .map_err(Error::wrap(|| {
            "Failed to load language in current directory"
        }))?
        .first()
        .cloned()
    {
        Ok(lang)
    } else {
        eprintln!("No language found");
        Error::err("No language found".to_string())
    }
}
<commit_msg>Remove unused flags from tags CLI command<commit_after>use clap::{App, AppSettings, Arg, SubCommand};
use error::Error;
use glob::glob;
use std::path::Path;
use std::process::exit;
use std::{env, fs, u64};
use tree_sitter::Language;
use tree_sitter_cli::{
    config, error, generate, highlight, loader, logger, parse, query, tags, test, test_highlight,
    wasm, web_ui,
};

const BUILD_VERSION: &'static str = env!("CARGO_PKG_VERSION");
const BUILD_SHA: Option<&'static str> = option_env!("BUILD_SHA");

fn main() {
    if let Err(e) = run() {
        if !e.message().is_empty() {
            println!("");
            eprintln!("{}", e.message());
        }
        exit(1);
    }
}

fn run() -> error::Result<()> {
    let version = if let Some(build_sha) = BUILD_SHA {
        format!("{} ({})", BUILD_VERSION, build_sha)
    } else {
        BUILD_VERSION.to_string()
    };

    let matches = App::new("tree-sitter")
        .version(version.as_str())
        .setting(AppSettings::SubcommandRequiredElseHelp)
        .author("Max Brunsfeld <maxbrunsfeld@gmail.com>")
        .about("Generates and tests parsers")
        .subcommand(SubCommand::with_name("init-config").about("Generate a default config file"))
        .subcommand(
            SubCommand::with_name("generate")
                .about("Generate a parser")
                .arg(Arg::with_name("grammar-path").index(1))
                .arg(Arg::with_name("log").long("log"))
                .arg(Arg::with_name("prev-abi").long("prev-abi"))
                .arg(
                    Arg::with_name("report-states-for-rule")
                        .long("report-states-for-rule")
                        .value_name("rule-name")
                        .takes_value(true),
                )
                .arg(Arg::with_name("no-minimize").long("no-minimize")),
        )
        .subcommand(
            SubCommand::with_name("parse")
                .about("Parse files")
                .arg(
                    Arg::with_name("path")
                        .index(1)
                        .multiple(true)
                        .required(true),
                )
                .arg(Arg::with_name("scope").long("scope").takes_value(true))
                .arg(Arg::with_name("debug").long("debug").short("d"))
                .arg(Arg::with_name("debug-graph").long("debug-graph").short("D"))
                .arg(Arg::with_name("quiet").long("quiet").short("q"))
                .arg(Arg::with_name("time").long("time").short("t"))
                .arg(Arg::with_name("allow-cancellation").long("cancel"))
                .arg(Arg::with_name("timeout").long("timeout").takes_value(true))
                .arg(
                    Arg::with_name("edits")
                        .long("edit")
                        .short("edit")
                        .takes_value(true)
                        .multiple(true)
                        .number_of_values(1),
                ),
        )
        .subcommand(
            SubCommand::with_name("query")
                .about("Search files using a syntax tree query")
                .arg(Arg::with_name("query-path").index(1).required(true))
                .arg(
                    Arg::with_name("path")
                        .index(2)
                        .multiple(true)
                        .required(true),
                )
                .arg(Arg::with_name("scope").long("scope").takes_value(true))
                .arg(Arg::with_name("captures").long("captures").short("c")),
        )
        .subcommand(
            SubCommand::with_name("tags")
                .arg(Arg::with_name("quiet").long("quiet").short("q"))
                .arg(Arg::with_name("time").long("quiet").short("t"))
                .arg(Arg::with_name("scope").long("scope").takes_value(true))
                .arg(
                    Arg::with_name("inputs")
                        .help("The source file to use")
                        .index(1)
                        .required(true)
                        .multiple(true),
                ),
        )
        .subcommand(
            SubCommand::with_name("test")
                .about("Run a parser's tests")
                .arg(
                    Arg::with_name("filter")
                        .long("filter")
                        .short("f")
                        .takes_value(true),
                )
                .arg(Arg::with_name("debug").long("debug").short("d"))
                .arg(Arg::with_name("debug-graph").long("debug-graph").short("D")),
        )
        .subcommand(
            SubCommand::with_name("highlight")
                .about("Highlight a file")
                .arg(
                    Arg::with_name("path")
                        .index(1)
                        .multiple(true)
                        .required(true),
                )
                .arg(Arg::with_name("scope").long("scope").takes_value(true))
                .arg(Arg::with_name("html").long("html").short("h"))
                .arg(Arg::with_name("time").long("time").short("t"))
                .arg(Arg::with_name("q").short("q")),
        )
        .subcommand(
            SubCommand::with_name("build-wasm")
                .about("Compile a parser to WASM")
                .arg(
                    Arg::with_name("docker")
                        .long("docker")
                        .help("Run emscripten via docker even if it is installed locally"),
                )
                .arg(Arg::with_name("path").index(1).multiple(true)),
        )
        .subcommand(
            SubCommand::with_name("web-ui")
                .about("Test a parser interactively in the browser")
                .arg(
                    Arg::with_name("quiet")
                        .long("quiet")
                        .short("q")
                        .help("open in default browser"),
                ),
        )
        .subcommand(
            SubCommand::with_name("dump-languages")
                .about("Print info about all known language parsers"),
        )
        .get_matches();

    let home_dir = dirs::home_dir().expect("Failed to read home directory");
    let current_dir = env::current_dir().unwrap();
    let config = config::Config::load(&home_dir);
    let mut loader = loader::Loader::new(config.binary_directory.clone());

    if matches.subcommand_matches("init-config").is_some() {
        let config = config::Config::new(&home_dir);
        config.save(&home_dir)?;
    } else if let Some(matches) = matches.subcommand_matches("generate") {
        let grammar_path = matches.value_of("grammar-path");
        let report_symbol_name = matches.value_of("report-states-for-rule").or_else(|| {
            if matches.is_present("report-states") {
                Some("")
            } else {
                None
            }
        });
        if matches.is_present("log") {
            logger::init();
        }
        let prev_abi = matches.is_present("prev-abi");
        generate::generate_parser_in_directory(
            &current_dir,
            grammar_path,
            !prev_abi,
            report_symbol_name,
        )?;
    } else if let Some(matches) = matches.subcommand_matches("test") {
        let debug = matches.is_present("debug");
        let debug_graph = matches.is_present("debug-graph");
        let filter = matches.value_of("filter");
        let languages = loader.languages_at_path(&current_dir)?;
        let language = languages
            .first()
            .ok_or_else(|| "No language found".to_string())?;
        let test_dir = current_dir.join("test");

        // Run the corpus tests. Look for them at two paths: `test/corpus` and `corpus`.
        let mut test_corpus_dir = test_dir.join("corpus");
        if !test_corpus_dir.is_dir() {
            test_corpus_dir = current_dir.join("corpus");
        }
        if test_corpus_dir.is_dir() {
            test::run_tests_at_path(*language, &test_corpus_dir, debug, debug_graph, filter)?;
        }

        // Check that all of the queries are valid.
        test::check_queries_at_path(*language, &current_dir.join("queries"))?;

        // Run the syntax highlighting tests.
        let test_highlight_dir = test_dir.join("highlight");
        if test_highlight_dir.is_dir() {
            test_highlight::test_highlights(&loader, &test_highlight_dir)?;
        }
    } else if let Some(matches) = matches.subcommand_matches("parse") {
        let debug = matches.is_present("debug");
        let debug_graph = matches.is_present("debug-graph");
        let quiet = matches.is_present("quiet");
        let time = matches.is_present("time");
        let edits = matches
            .values_of("edits")
            .map_or(Vec::new(), |e| e.collect());
        let allow_cancellation = matches.is_present("allow-cancellation");
        let timeout = matches
            .value_of("timeout")
            .map_or(0, |t| u64::from_str_radix(t, 10).unwrap());
        let paths = collect_paths(matches.values_of("path").unwrap())?;
        let max_path_length = paths.iter().map(|p| p.chars().count()).max().unwrap();
        let mut has_error = false;
        loader.find_all_languages(&config.parser_directories)?;
        for path in paths {
            let path = Path::new(&path);
            let language =
                select_language(&mut loader, path, &current_dir, matches.value_of("scope"))?;
            has_error |= parse::parse_file_at_path(
                language,
                path,
                &edits,
                max_path_length,
                quiet,
                time,
                timeout,
                debug,
                debug_graph,
                allow_cancellation,
            )?;
        }
        if has_error {
            return Error::err(String::new());
        }
    } else if let Some(matches) = matches.subcommand_matches("query") {
        let ordered_captures = matches.values_of("captures").is_some();
        let paths = matches
            .values_of("path")
            .unwrap()
            .into_iter()
            .map(Path::new)
            .collect::<Vec<&Path>>();
        loader.find_all_languages(&config.parser_directories)?;
        let language = select_language(
            &mut loader,
            paths[0],
            &current_dir,
            matches.value_of("scope"),
        )?;
        let query_path = Path::new(matches.value_of("query-path").unwrap());
        query::query_files_at_paths(language, paths, query_path, ordered_captures)?;
    } else if let Some(matches) = matches.subcommand_matches("tags") {
        loader.find_all_languages(&config.parser_directories)?;
        let paths = collect_paths(matches.values_of("inputs").unwrap())?;
        tags::generate_tags(
            &loader,
            matches.value_of("scope"),
            &paths,
            matches.is_present("quiet"),
            matches.is_present("time"),
        )?;
    } else if let Some(matches) = matches.subcommand_matches("highlight") {
        loader.configure_highlights(&config.theme.highlight_names);
        loader.find_all_languages(&config.parser_directories)?;

        let time = matches.is_present("time");
        let paths = collect_paths(matches.values_of("path").unwrap())?;
        let html_mode = matches.is_present("html");
        if html_mode {
            println!("{}", highlight::HTML_HEADER);
        }

        let mut lang = None;
        if let Some(scope) = matches.value_of("scope") {
            lang = loader.language_configuration_for_scope(scope)?;
            if lang.is_none() {
                return Error::err(format!("Unknown scope '{}'", scope));
            }
        }

        for path in paths {
            let path = Path::new(&path);
            let (language, language_config) = match lang {
                Some(v) => v,
                None => match loader.language_configuration_for_file_name(path)? {
                    Some(v) => v,
                    None => {
                        eprintln!("No language found for path {:?}", path);
                        continue;
                    }
                },
            };

            if let Some(highlight_config) = language_config.highlight_config(language)? {
                let source = fs::read(path)?;
                if html_mode {
                    highlight::html(&loader, &config.theme, &source, highlight_config, time)?;
                } else {
                    highlight::ansi(&loader, &config.theme, &source, highlight_config, time)?;
                }
            } else {
                eprintln!("No syntax highlighting config found for path {:?}", path);
            }
        }

        if html_mode {
            println!("{}", highlight::HTML_FOOTER);
        }
    } else if let Some(matches) = matches.subcommand_matches("build-wasm") {
        let grammar_path = current_dir.join(matches.value_of("path").unwrap_or(""));
        wasm::compile_language_to_wasm(&grammar_path, matches.is_present("docker"))?;
    } else if let Some(matches) = matches.subcommand_matches("web-ui") {
        let open_in_browser = !matches.is_present("quiet");
        web_ui::serve(&current_dir, open_in_browser);
    } else if matches.subcommand_matches("dump-languages").is_some() {
        loader.find_all_languages(&config.parser_directories)?;
        for (configuration, language_path) in loader.get_all_language_configurations() {
            println!(
                concat!(
                    "scope: {}\n",
                    "parser: {:?}\n",
                    "highlights: {:?}\n",
                    "file_types: {:?}\n",
                    "content_regex: {:?}\n",
                    "injection_regex: {:?}\n",
                ),
                configuration.scope.as_ref().unwrap_or(&String::new()),
                language_path,
                configuration.highlights_filenames,
                configuration.file_types,
                configuration.content_regex,
                configuration.injection_regex,
            );
        }
    }

    Ok(())
}

fn collect_paths<'a>(paths: impl Iterator<Item = &'a str>) -> error::Result<Vec<String>> {
    let mut result = Vec::new();

    let mut incorporate_path = |path: &str, positive| {
        if positive {
            result.push(path.to_string());
        } else {
            if let Some(index) = result.iter().position(|p| p == path) {
                result.remove(index);
            }
        }
    };

    for mut path in paths {
        let mut positive = true;
        if path.starts_with("!") {
            positive = false;
            path = path.trim_start_matches("!");
        }

        if Path::new(path).exists() {
            incorporate_path(path, positive);
        } else {
            let paths =
                glob(path).map_err(Error::wrap(|| format!("Invalid glob pattern {:?}", path)))?;
            for path in paths {
                if let Some(path) = path?.to_str() {
                    incorporate_path(path, positive);
                }
            }
        }
    }
    Ok(result)
}

fn select_language(
    loader: &mut loader::Loader,
    path: &Path,
    current_dir: &Path,
    scope: Option<&str>,
) -> Result<Language, Error> {
    if let Some(scope) = scope {
        if let Some(config) =
            loader
                .language_configuration_for_scope(scope)
                .map_err(Error::wrap(|| {
                    format!("Failed to load language for scope '{}'", scope)
                }))?
        {
            Ok(config.0)
        } else {
            return Error::err(format!("Unknown scope '{}'", scope));
        }
    } else if let Some((lang, _)) =
        loader
            .language_configuration_for_file_name(path)
            .map_err(Error::wrap(|| {
                format!(
                    "Failed to load language for file name {:?}",
                    path.file_name().unwrap()
                )
            }))?
    {
        Ok(lang)
    } else if let Some(lang) = loader
        .languages_at_path(&current_dir)
        .map_err(Error::wrap(|| {
            "Failed to load language in current directory"
        }))?
        .first()
        .cloned()
    {
        Ok(lang)
    } else {
        eprintln!("No language found");
        Error::err("No language found".to_string())
    }
}
<|endoftext|>


<commit_before>import std::time;
import time::tm;
import mre::model::{model, error};

// Create a class to act as our model. Unfortunately Rust's classes aren't
// finished yet, and are missing a couple features that would help clean up
// models. First, there's no way to mix in implementation of functions, so we
// need to duplicate some code that's common across all models. Second, there
// is no way to have multiple constructors or static functions, so we need to
// move error handling out into a wrapper function. So to keep the api clean,
// we cheat and hide the class so we can make a function that acts like what we
// want.
class _person {
    let model: model;

    new(model: model) {
        self.model = model;
    }

    fn id() -> @str {
        self.model._id
    }

    fn timestamp() -> @str {
        self.model.get_str("timestamp")
    }

    fn set_timestamp(timestamp: @str) -> bool {
        self.model.set_str("timestamp", timestamp)
    }

    fn name() -> @str {
        self.model.get_str("name")
    }

    fn set_name(name: @str) -> bool {
        self.model.set_str("name", name)
    }

    fn create() -> result<(), error> {
        import model::model;
        self.model.create()
    }

    fn save() -> result<(), error> {
        import model::model;
        self.model.save()
    }

    fn delete() {
        import model::model;
        self.model.delete()
    }
}

// We want the type named "person", but we don't "person" to be the built-in
// class constructor.
type person = _person;

// Create a new person model.
fn person(es: client, name: @str) -> person {
    // We'll let ES come up with a unique id.
    let person = _person(model(es, @"helloeveryone", @"person", @""));

    person.set_name(name);
    person.set_timestamp(@time::now().rfc3339());

    person
}

// Return the last 100 people we have said hello to.
fn last_50(es: client) -> [person] {
    // Sort by reverse timestamp.
    model::search(es) { |bld|
        bld
            .set_indices(["helloeveryone"])
            .set_types(["person"])
            .set_source(*json_dict_builder()
                .insert("size", 50.0)
                .insert_list("sort") { |bld|
                    bld.push_dict { |bld|
                        bld.insert("timestamp", "desc");
                    };
                }
            );
    }.map { |model| _person(model) }
}
<commit_msg>Code cleanup.<commit_after>import std::time;
import time::tm;
import mre::model::{model, error};

// Create a class to act as our model. Unfortunately Rust's classes aren't
// finished yet, and are missing a couple features that would help clean up
// models. First, there's no way to mix in implementation of functions, so we
// need to duplicate some code that's common across all models. Second, there
// is no way to have multiple constructors or static functions, so we need to
// move error handling out into a wrapper function. So to keep the api clean,
// we cheat and hide the class so we can make a function that acts like what we
// want.
class _person {
    let model: model;

    new(model: model) {
        self.model = model;
    }

    fn id() -> @str {
        self.model._id
    }

    fn timestamp() -> @str {
        self.model.get_str("timestamp")
    }

    fn set_timestamp(timestamp: @str) -> bool {
        self.model.set_str("timestamp", timestamp)
    }

    fn name() -> @str {
        self.model.get_str("name")
    }

    fn set_name(name: @str) -> bool {
        self.model.set_str("name", name)
    }

    fn create() -> result<(), error> {
        self.model.create()
    }

    fn save() -> result<(), error> {
        self.model.save()
    }

    fn delete() {
        self.model.delete()
    }
}

// We want the type named "person", but we don't "person" to be the built-in
// class constructor.
type person = _person;

// Create a new person model.
fn person(es: client, name: @str) -> person {
    // We'll let ES come up with a unique id.
    let person = _person(model(es, @"helloeveryone", @"person", @""));

    person.set_name(name);
    person.set_timestamp(@time::now().rfc3339());

    person
}

// Return the last 100 people we have said hello to.
fn last_50(es: client) -> [person] {
    // Sort by reverse timestamp.
    model::search(es) { |bld|
        bld
            .set_indices(["helloeveryone"])
            .set_types(["person"])
            .set_source(*json_dict_builder()
                .insert("size", 50.0)
                .insert_list("sort") { |bld|
                    bld.push_dict { |bld|
                        bld.insert("timestamp", "desc");
                    };
                }
            );
    }.map { |model| _person(model) }
}
<|endoftext|>


<commit_before>// https://leetcode.com/problems/minimum-hours-of-training-to-win-a-competition/
pub fn min_number_of_hours(
    initial_energy: i32,
    initial_experience: i32,
    energy: Vec<i32>,
    experience: Vec<i32>,
) -> i32 {
    let total_energy: i32 = energy.into_iter().sum();
    let energy_required = total_energy - initial_energy + 1;
    let mut experience_required = 0;
    let mut total_experience = initial_experience;
    for exp in experience.into_iter() {
        if exp >= total_experience {
            let training = exp - total_experience + 1;
            experience_required += training;
            total_experience += training;
        } else {
            total_experience += exp;
        }
    }
    energy_required + experience_required
}

fn main() {
    // println!(
    //     "{}",
    //     min_number_of_hours(5, 3, vec![1, 4, 3, 2], vec![2, 6, 3, 1])
    // ); // 8
    // println!("{}", min_number_of_hours(2, 4, vec![1], vec![3])); // 0
    // println!(
    //     "{}",
    //     min_number_of_hours(1, 1, vec![1, 1, 1, 1], vec![1, 1, 1, 50])
    // ); // 51
    println!("{}", min_number_of_hours(1, 1, vec![1, 1], vec![1, 50])); // 51
                                                                        // println!("{}", min_number_of_hours(5, 3, vec![1, 4], vec![2, 5])); // 2
}
<commit_msg>Solve problem 2383<commit_after>// https://leetcode.com/problems/minimum-hours-of-training-to-win-a-competition/
pub fn min_number_of_hours(
    initial_energy: i32,
    initial_experience: i32,
    energy: Vec<i32>,
    experience: Vec<i32>,
) -> i32 {
    let total_energy: i32 = energy.into_iter().sum();
    let energy_required = if total_energy >= initial_energy {
        total_energy - initial_energy + 1
    } else {
        0
    };
    let mut experience_required = 0;
    let mut total_experience = initial_experience;
    for exp in experience.into_iter() {
        if exp >= total_experience {
            let training = exp - total_experience + 1;
            experience_required += training;
            total_experience += training;
        }
        total_experience += exp;
    }
    energy_required + experience_required
}

fn main() {
    println!(
        "{}",
        min_number_of_hours(5, 3, vec![1, 4, 3, 2], vec![2, 6, 3, 1])
    ); // 8
    println!("{}", min_number_of_hours(2, 4, vec![1], vec![3])); // 0
    println!(
        "{}",
        min_number_of_hours(1, 1, vec![1, 1, 1, 1], vec![1, 1, 1, 50])
    ); // 51
    println!("{}", min_number_of_hours(1, 1, vec![1, 1], vec![1, 50])); // 51
    println!("{}", min_number_of_hours(5, 3, vec![1, 4], vec![2, 5])); // 2
}
<|endoftext|>


<commit_before>// STD Dependencies -----------------------------------------------------------
use std::fs::File;

// Internal Dependencies ------------------------------------------------------
use super::{Chunk, Track};

// Audio Writer Trait ---------------------------------------------------------
pub trait AudioWriter: Send {
    fn write_header(&mut self, &mut File, &Track);
    fn write_chunk(&mut self, &mut File, Chunk);
}

// Ogg Writer Implementation --------------------------------------------------
pub struct OggWriter;

impl AudioWriter for OggWriter {

    fn write_header(&mut self, file: &mut File, track: &Track) {

    }

    fn write_chunk(&mut self, file: &mut File, chunk: Chunk) {

    }

}

<commit_msg>Remove unused arguments for the time being.<commit_after>// STD Dependencies -----------------------------------------------------------
use std::fs::File;

// Internal Dependencies ------------------------------------------------------
use super::{Chunk, Track};

// Audio Writer Trait ---------------------------------------------------------
pub trait AudioWriter: Send {
    fn write_header(&mut self, &mut File, &Track);
    fn write_chunk(&mut self, &mut File, Chunk);
}

// Ogg Writer Implementation --------------------------------------------------
pub struct OggWriter;

impl AudioWriter for OggWriter {

    fn write_header(&mut self, _: &mut File, _: &Track) {

    }

    fn write_chunk(&mut self, _: &mut File, _: Chunk) {

    }

}

<|endoftext|>


<commit_before>use std::result::Result;
use std::error::Error;
use serde::*;
use serde_json::*;

use super::super::tool::*;
use super::super::environment::*;

///
/// Represents a tool made from a function
///
pub struct FnTool<TIn: Deserialize, TOut: Serialize, TErr: Serialize> {
    function: Box<Fn(TIn) -> Result<TOut, TErr>>
}

///
/// Creates a Tool from a function
///
pub fn make_tool<TIn: Deserialize, TOut: Serialize, TErr: Serialize, F: 'static+Fn(TIn) -> Result<TOut, TErr>>(function: F) -> FnTool<TIn, TOut, TErr> {
    FnTool { function: Box::new(function) }
}

impl<TIn, TOut, TErr> Tool for FnTool<TIn, TOut, TErr>
where TIn: Deserialize, TOut: Serialize, TErr: Serialize {
    fn invoke_json(&self, input: Value, _environment: &Environment) -> Result<Value, Value> {
        // Decode
        let input_decoded = from_value::<TIn>(input);

        // Chain into the tool itself
        match input_decoded {
            Ok(input_decoded) => {
                // Successfully decoded as the tool's input format
                let result = (self.function)(input_decoded);

                // Encode the error or the result
                // The encoding can go wrong, and we need to handle it slightly differently for the error or the success case
                match result {
                    Ok(res) => {
                        let encoded = to_value(res);
                        match encoded {
                            Ok(final_value) => Ok(final_value),
                            Err(erm)        => Err(json![{
                                "error":        "JSON encode failed",
                                "description":  erm.description()
                            }])
                        }
                    },

                    Err(erm) => {
                        let encoded = to_value(erm);
                        match encoded {
                            Ok(final_value) => Ok(final_value),
                            Err(erm)        => Err(json![{
                                "error":        "Error encode failed",
                                "description":  erm.description()
                            }])
                        }
                    }
                }
            },

            Err(input_error) => {
                // Input does not match the format expected by the tool
                Err(json![{
                    "error":        "JSON input decode failed",
                    "description":  input_error.description(),
                }])
            }
        }
    }
}

#[cfg(test)]
mod test {
    use super::super::super::*;
    use super::super::*;
    use std::result::Result;
    use serde_json::*;

    #[derive(Serialize, Deserialize)]
    struct TestIn {
        input: i32
    }

    #[derive(Serialize, Deserialize, PartialEq, Eq)]
    struct TestOut {
        output: i32
    }

    fn test_tool(x: TestIn) -> Result<TestOut, ()> {
        Ok(TestOut { output: x.input + 1 })
    }

    #[test]
    fn can_call_tool_via_json_interface() {
        let tool        = make_tool(test_tool);
        let environment = EmptyEnvironment::new();
        let result      = tool.invoke_json(json![{ "input": 4 }], &environment);

        assert!(result == Ok(json![{ "output": 5 }]));
    }
}
<commit_msg>Add make_pure_tool so we don’t always need to specify an error case<commit_after>use std::result::Result;
use std::error::Error;
use serde::*;
use serde_json::*;

use super::super::tool::*;
use super::super::environment::*;

///
/// Represents a tool made from a function
///
pub struct FnTool<TIn: Deserialize, TOut: Serialize, TErr: Serialize> {
    function: Box<Fn(TIn) -> Result<TOut, TErr>>
}

///
/// Creates a Tool from a function that can produce an error but does not use an environment
///
pub fn make_tool<TIn: Deserialize, TOut: Serialize, TErr: Serialize, F: 'static+Fn(TIn) -> Result<TOut, TErr>>(function: F) -> FnTool<TIn, TOut, TErr> {
    FnTool { function: Box::new(function) }
}

///
/// Creates a Tool from a function that cannot produce an error and doesn't use an environment
///
pub fn make_pure_tool<TIn: Deserialize, TOut: Serialize, F: 'static+Fn(TIn) -> TOut>(function: F) -> FnTool<TIn, TOut, ()> {
    make_tool(move |input| { Ok(function(input)) })
}

impl<TIn, TOut, TErr> Tool for FnTool<TIn, TOut, TErr>
where TIn: Deserialize, TOut: Serialize, TErr: Serialize {
    fn invoke_json(&self, input: Value, _environment: &Environment) -> Result<Value, Value> {
        // Decode
        let input_decoded = from_value::<TIn>(input);

        // Chain into the tool itself
        match input_decoded {
            Ok(input_decoded) => {
                // Successfully decoded as the tool's input format
                let result = (self.function)(input_decoded);

                // Encode the error or the result
                // The encoding can go wrong, and we need to handle it slightly differently for the error or the success case
                match result {
                    Ok(res) => {
                        let encoded = to_value(res);
                        match encoded {
                            Ok(final_value) => Ok(final_value),
                            Err(erm)        => Err(json![{
                                "error":        "JSON encode failed",
                                "description":  erm.description()
                            }])
                        }
                    },

                    Err(erm) => {
                        let encoded = to_value(erm);
                        match encoded {
                            Ok(final_value) => Ok(final_value),
                            Err(erm)        => Err(json![{
                                "error":        "Error encode failed",
                                "description":  erm.description()
                            }])
                        }
                    }
                }
            },

            Err(input_error) => {
                // Input does not match the format expected by the tool
                Err(json![{
                    "error":        "JSON input decode failed",
                    "description":  input_error.description(),
                }])
            }
        }
    }
}

#[cfg(test)]
mod test {
    use super::super::super::*;
    use super::super::*;
    use std::result::Result;
    use serde_json::*;

    #[derive(Serialize, Deserialize)]
    struct TestIn {
        input: i32
    }

    #[derive(Serialize, Deserialize, PartialEq, Eq)]
    struct TestOut {
        output: i32
    }

    fn test_tool(x: TestIn) -> Result<TestOut, ()> {
        Ok(TestOut { output: x.input + 1 })
    }

    #[test]
    fn can_call_tool_via_json_interface() {
        let tool        = make_tool(test_tool);
        let environment = EmptyEnvironment::new();
        let result      = tool.invoke_json(json![{ "input": 4 }], &environment);

        assert!(result == Ok(json![{ "output": 5 }]));
    }
}
<|endoftext|>


<commit_before>extern crate tokio_core;

use rusoto_credential::{InstanceMetadataProvider, ProvideAwsCredentials};
use std::time::Duration;
use tokio_core::reactor::Core;

#[test]
#[ignore]
fn it_fetches_basic_role() {
    // set env vars to point to local provider
    let mut provider = InstanceMetadataProvider::new();
    provider.set_timeout(Duration::from_secs(5));
    provider.set_to_localhost("8080");

    let creds_future = provider.credentials();
    let mut core = Core::new().unwrap();
    let creds = match core.run(creds_future) {
        Ok(creds) => creds,
        Err(e) => panic!("Got error: {:?}", e),
    };

    assert_eq!(creds.aws_access_key_id(), "Access_key_id_value");
    assert_eq!(creds.aws_secret_access_key(), "Secret_access_key_value");
}
<commit_msg>Add note on why integration test is ignored, check token and expiration for instance profile integration test.<commit_after>extern crate tokio_core;

use rusoto_credential::{InstanceMetadataProvider, ProvideAwsCredentials};
use std::time::Duration;
use tokio_core::reactor::Core;

// This test is marked ignored because it requires special setup.
// It's run with the `credential_integration_test` Makefile target.
#[test]
#[ignore]
fn it_fetches_basic_role() {
    // set env vars to point to local provider
    let mut provider = InstanceMetadataProvider::new();
    provider.set_timeout(Duration::from_secs(5));
    provider.set_to_localhost("8080");

    let creds_future = provider.credentials();
    let mut core = Core::new().unwrap();
    let creds = match core.run(creds_future) {
        Ok(creds) => creds,
        Err(e) => panic!("Got error: {:?}", e),
    };

    assert_eq!(creds.aws_access_key_id(), "Access_key_id_value");
    assert_eq!(creds.aws_secret_access_key(), "Secret_access_key_value");
    assert_eq!(creds.token().as_ref(), Some(&"AAAAA".to_string()));
    let dt = match creds.expires_at().as_ref() {
        Some(d) => d.to_string(),
        None => panic!("Expiration should be present"),
    };
    assert_eq!(dt, "2015-08-04 06:32:37 UTC");
}
<|endoftext|>


<commit_before>use test_tools_local::*;

tests_impls!
{
  fn dependencies_check()
  {
    let manifest_dir = std::path::PathBuf::from( env!( "CARGO_MANIFEST_DIR" ) );
    let mut manifest_path = manifest_dir.clone();
    manifest_path.push( "Cargo.toml" );
    let read = std::fs::read_to_string( manifest_path ).unwrap();
    let manifest = read.parse::< toml_edit::Document >().unwrap();
    let dependencies = &manifest[ "dependencies" ];

    let root_dirs = [ "alias", "move", "rust" ];

    let _ = root_dirs.iter().map( | path |
    {
      let mut current_root = manifest_dir.clone();
      current_root.push( ".." );
      current_root.push( ".." );
      current_root.push( path );
      let current_root = current_root.canonicalize().unwrap();

      let dirs = std::fs::read_dir( &current_root ).unwrap();
      for p in dirs
      {
        let entry = p.unwrap();
        let file_type = entry.file_type().unwrap();
        if file_type.is_dir()
        {
          let mut dependency_name = entry.file_name().into_string().unwrap();
          dependency_name.push_str( "_local" );
          if !dependency_name.starts_with( '_' )
          {
            assert!
            (
              dependencies.get( &dependency_name ).is_some(),
              "local version of dependency :: {} is not included",
              &dependency_name
            );
          }
        }
      }
    }).collect::< Vec< _ > >();
  }
}

tests_index!
{
  dependencies_check,
}
<commit_msg>Extend test routine `dependencies_check`, check all dependencies and print informative error message<commit_after>use test_tools_local::*;

tests_impls!
{
  fn dependencies_check()
  {
    let manifest_dir = std::path::PathBuf::from( env!( "CARGO_MANIFEST_DIR" ) );
    let mut manifest_path = manifest_dir.clone();
    manifest_path.push( "Cargo.toml" );
    let read = std::fs::read_to_string( &manifest_path ).unwrap();
    let manifest = read.parse::< toml_edit::Document >().unwrap();
    let dependencies = &manifest[ "dependencies" ];

    let root_dirs = [ "alias", "move", "rust" ];

    let non_existed = root_dirs.iter().filter_map( | path |
    {
      let mut current_root = manifest_dir.clone();
      current_root.push( ".." );
      current_root.push( ".." );
      current_root.push( path );
      let current_root = current_root.canonicalize().unwrap();

      let dirs = std::fs::read_dir( &current_root ).unwrap();
      let mut missed = vec![];
      for p in dirs
      {
        let entry = p.unwrap();
        let file_type = entry.file_type().unwrap();
        if file_type.is_dir()
        {
          let dependency_name = entry.file_name().into_string().unwrap();
          let mut local_dependency_name = dependency_name.clone();
          local_dependency_name.push_str( "_local" );
          if !local_dependency_name.starts_with( '_' )
          {
            if dependencies.get( &local_dependency_name ).is_none()
            {
              missed.push( ( path, dependency_name ) );
            }
          }
        }
      }

      if missed.len() > 0
      {
        return Some( missed );
      }

      None
    }).collect::< Vec< _ > >();

    /* */

    if non_existed.len() > 0
    {
      let header = format!
      (
        "\nManifest of integration test at {:?} has some missed dependencies.\nPlease, insert next code to manifest:\n```",
        manifest_path.to_str().unwrap()
      );
      let mut msg = String::from( header );

      for v in non_existed
      {
        for ( root, dependency ) in v
        {
          let dependencies_string = format!
          (
"
{dep}_local = {{ version = \"*\", path = \"../../{root}/{dep}\", package = \"{dep}\" }}
{dep}_published = {{ version = \"*\", package = \"{dep}\" }}
",
            root = root,
            dep = dependency
          );
          msg.push_str( &dependencies_string );
        }
      }
      msg.push_str( "```\n" );

      assert!( false, "{}", &msg );
    }
  }
}

tests_index!
{
  dependencies_check,
}
<|endoftext|>


<commit_before>// functions to ask the user for data, with crate:spinner

use std::io::stdin;
use std::io::BufRead;
use std::io::BufReader;

use regex::Regex;
use ansi_term::Colour::*;

/// Ask the user for a Yes/No answer. Optionally provide a default value. If none is provided, this
/// keeps loop{}ing
pub fn ask_bool(s: &str, default: Option<bool>) -> bool {
    ask_bool_(s, default, &mut BufReader::new(stdin()))
}

fn ask_bool_<R: BufRead>(s: &str, default: Option<bool>, input: &mut R) -> bool {
    lazy_static! {
        static ref R_YES: Regex = Regex::new(r"^[Yy]$").unwrap();
        static ref R_NO: Regex  = Regex::new(r"^[Nn]$").unwrap();
    }

    loop {
        ask_question(s, false);
        if match default { Some(s) => s, _ => true } {
            println!(" [Yn]: ");
        } else {
            println!(" [yN]: ");
        }

        let mut s = String::new();
        let _     = input.read_line(&mut s);

        if R_YES.is_match(&s[..]) {
            return true
        } else if R_NO.is_match(&s[..]) {
            return false
        } else {
            if default.is_some() {
                return default.unwrap();
            }

            // else again...
        }
    }
}

/// Ask the user for an unsigned number. Optionally provide a default value. If none is provided,
/// this keeps loop{}ing
pub fn ask_uint(s: &str, default: Option<u64>) -> u64 {
    ask_uint_(s, default, &mut BufReader::new(stdin()))
}

fn ask_uint_<R: BufRead>(s: &str, default: Option<u64>, input: &mut R) -> u64 {
    use std::str::FromStr;

    loop {
        ask_question(s, false);

        let mut s = String::new();
        let _     = input.read_line(&mut s);

        let u : Result<u64, _> = FromStr::from_str(&s[..]);
        match u {
            Ok(u)  => { return u; },
            Err(_) => {
                if default.is_some() {
                    return default.unwrap();
                } // else keep looping
            }
        }
    }
}

/// Ask the user for a String.
///
/// If `permit_empty` is set to false, the default value will be returned if the user inserts an
/// empty string.
///
/// If the `permit_empty` value is true, the `default` value is never returned.
///
/// If the `permit_multiline` is set to true, the `prompt` will be displayed before each input line.
///
/// If the `eof` parameter is `None`, the input ends as soon as there is an empty line input from
/// the user. If the parameter is `Some(text)`, the input ends if the input line is equal to `text`.
pub fn ask_string(s: &str,
                  default: Option<String>,
                  permit_empty: bool,
                  permit_multiline: bool,
                  eof: Option<&str>,
                  prompt: &str)
    -> String
{
    ask_string_(s,
                default,
                permit_empty,
                permit_multiline,
                eof,
                prompt,
                &mut BufReader::new(stdin()))
}

pub fn ask_string_<R: BufRead>(s: &str,
                               default: Option<String>,
                               permit_empty: bool,
                               permit_multiline: bool,
                               eof: Option<&str>,
                               prompt: &str,
                               input: &mut R)
    -> String
{
    let mut v = vec![];
    loop {
        ask_question(s, true);
        print!("{}", prompt);

        let mut s = String::new();
        let _     = input.read_line(&mut s);

        if permit_multiline {
            if permit_multiline && eof.map(|e| e == s).unwrap_or(false) {
                return v.join("\n");
            }

            if permit_empty || v.len() != 0 {
                v.push(s);
            }
            print!("{}", prompt);
        } else {
            if s.len() == 0 && permit_empty {
                return s;
            } else if s.len() == 0 && !permit_empty {
                if default.is_some() {
                    return default.unwrap();
                } else {
                    continue;
                }
            } else {
                return s;
            }
        }
    }
}

pub fn ask_enum<E: From<String>>(s: &str) -> E {
    unimplemented!()
}

/// Helper function to print a imag question string. The `question` argument may not contain a
/// trailing questionmark.
///
/// The `nl` parameter can be used to configure whether a newline character should be printed
pub fn ask_question(question: &str, nl: bool) {
    if nl {
        println!("[imag]: {}?", Yellow.paint(question));
    } else {
        print!("[imag]: {}?", Yellow.paint(question));
    }
}

#[cfg(test)]
mod test {
    use std::io::BufReader;

    use super::ask_bool_;
    use super::ask_uint_;

    #[test]
    fn test_ask_bool_nodefault_yes() {
        let question = "Is this true";
        let default  = None;
        let answers  = "\n\n\n\n\ny";

        assert!(ask_bool_(question, default, &mut BufReader::new(answers.as_bytes())));
    }

    #[test]
    fn test_ask_bool_nodefault_no() {
        let question = "Is this true";
        let default  = None;
        let answers  = "n";

        assert!(false == ask_bool_(question, default, &mut BufReader::new(answers.as_bytes())));
    }

    #[test]
    fn test_ask_bool_default_no() {
        let question = "Is this true";
        let default  = Some(false);
        let answers  = "n";

        assert!(false == ask_bool_(question, default, &mut BufReader::new(answers.as_bytes())));
    }

    #[test]
    fn test_ask_bool_default_yes() {
        let question = "Is this true";
        let default  = Some(true);
        let answers  = "y";

        assert!(true == ask_bool_(question, default, &mut BufReader::new(answers.as_bytes())));
    }

    #[test]
    fn test_ask_bool_default_yes_answer_no() {
        let question = "Is this true";
        let default  = Some(true);
        let answers  = "n";

        assert!(false == ask_bool_(question, default, &mut BufReader::new(answers.as_bytes())));
    }

    #[test]
    fn test_ask_bool_default_no_answer_yes() {
        let question = "Is this true";
        let default  = Some(false);
        let answers  = "y";

        assert!(true == ask_bool_(question, default, &mut BufReader::new(answers.as_bytes())));
    }

    #[test]
    fn test_ask_bool_default_no_without_answer() {
        let question = "Is this true";
        let default  = Some(false);
        let answers  = "\n";

        assert!(false == ask_bool_(question, default, &mut BufReader::new(answers.as_bytes())));
    }

    #[test]
    fn test_ask_bool_default_yes_without_answer() {
        let question = "Is this true";
        let default  = Some(true);
        let answers  = "\n";

        assert!(true == ask_bool_(question, default, &mut BufReader::new(answers.as_bytes())));
    }

    #[test]
    fn test_ask_uint_nodefault() {
        let question = "Is this 1";
        let default  = None;
        let answers  = "1";

        assert!(1 == ask_uint_(question, default, &mut BufReader::new(answers.as_bytes())));
    }

    #[test]
    fn test_ask_uint_default() {
        let question = "Is this 1";
        let default  = Some(1);
        let answers  = "1";

        assert!(1 == ask_uint_(question, default, &mut BufReader::new(answers.as_bytes())));
    }

    #[test]
    fn test_ask_uint_default_2_input_1() {
        let question = "Is this 1";
        let default  = Some(2);
        let answers  = "1";

        assert!(1 == ask_uint_(question, default, &mut BufReader::new(answers.as_bytes())));
    }

    #[test]
    fn test_ask_uint_default_2_noinput() {
        let question = "Is this 1";
        let default  = Some(2);
        let answers  = "\n";

        assert!(2 == ask_uint_(question, default, &mut BufReader::new(answers.as_bytes())));
    }

    #[test]
    fn test_ask_uint_default_2_several_noinput() {
        let question = "Is this 1";
        let default  = Some(2);
        let answers  = "\n\n\n\n";

        assert!(2 == ask_uint_(question, default, &mut BufReader::new(answers.as_bytes())));
    }

    #[test]
    fn test_ask_uint_default_2_wrong_input() {
        let question = "Is this 1";
        let default  = Some(2);
        let answers  = "\n\n\nasfb\nsakjf\naskjf\n-2";

        assert!(2 == ask_uint_(question, default, &mut BufReader::new(answers.as_bytes())));
    }

}
<commit_msg>Remove ask_enum()<commit_after>// functions to ask the user for data, with crate:spinner

use std::io::stdin;
use std::io::BufRead;
use std::io::BufReader;

use regex::Regex;
use ansi_term::Colour::*;

/// Ask the user for a Yes/No answer. Optionally provide a default value. If none is provided, this
/// keeps loop{}ing
pub fn ask_bool(s: &str, default: Option<bool>) -> bool {
    ask_bool_(s, default, &mut BufReader::new(stdin()))
}

fn ask_bool_<R: BufRead>(s: &str, default: Option<bool>, input: &mut R) -> bool {
    lazy_static! {
        static ref R_YES: Regex = Regex::new(r"^[Yy]$").unwrap();
        static ref R_NO: Regex  = Regex::new(r"^[Nn]$").unwrap();
    }

    loop {
        ask_question(s, false);
        if match default { Some(s) => s, _ => true } {
            println!(" [Yn]: ");
        } else {
            println!(" [yN]: ");
        }

        let mut s = String::new();
        let _     = input.read_line(&mut s);

        if R_YES.is_match(&s[..]) {
            return true
        } else if R_NO.is_match(&s[..]) {
            return false
        } else {
            if default.is_some() {
                return default.unwrap();
            }

            // else again...
        }
    }
}

/// Ask the user for an unsigned number. Optionally provide a default value. If none is provided,
/// this keeps loop{}ing
pub fn ask_uint(s: &str, default: Option<u64>) -> u64 {
    ask_uint_(s, default, &mut BufReader::new(stdin()))
}

fn ask_uint_<R: BufRead>(s: &str, default: Option<u64>, input: &mut R) -> u64 {
    use std::str::FromStr;

    loop {
        ask_question(s, false);

        let mut s = String::new();
        let _     = input.read_line(&mut s);

        let u : Result<u64, _> = FromStr::from_str(&s[..]);
        match u {
            Ok(u)  => { return u; },
            Err(_) => {
                if default.is_some() {
                    return default.unwrap();
                } // else keep looping
            }
        }
    }
}

/// Ask the user for a String.
///
/// If `permit_empty` is set to false, the default value will be returned if the user inserts an
/// empty string.
///
/// If the `permit_empty` value is true, the `default` value is never returned.
///
/// If the `permit_multiline` is set to true, the `prompt` will be displayed before each input line.
///
/// If the `eof` parameter is `None`, the input ends as soon as there is an empty line input from
/// the user. If the parameter is `Some(text)`, the input ends if the input line is equal to `text`.
pub fn ask_string(s: &str,
                  default: Option<String>,
                  permit_empty: bool,
                  permit_multiline: bool,
                  eof: Option<&str>,
                  prompt: &str)
    -> String
{
    ask_string_(s,
                default,
                permit_empty,
                permit_multiline,
                eof,
                prompt,
                &mut BufReader::new(stdin()))
}

pub fn ask_string_<R: BufRead>(s: &str,
                               default: Option<String>,
                               permit_empty: bool,
                               permit_multiline: bool,
                               eof: Option<&str>,
                               prompt: &str,
                               input: &mut R)
    -> String
{
    let mut v = vec![];
    loop {
        ask_question(s, true);
        print!("{}", prompt);

        let mut s = String::new();
        let _     = input.read_line(&mut s);

        if permit_multiline {
            if permit_multiline && eof.map(|e| e == s).unwrap_or(false) {
                return v.join("\n");
            }

            if permit_empty || v.len() != 0 {
                v.push(s);
            }
            print!("{}", prompt);
        } else {
            if s.len() == 0 && permit_empty {
                return s;
            } else if s.len() == 0 && !permit_empty {
                if default.is_some() {
                    return default.unwrap();
                } else {
                    continue;
                }
            } else {
                return s;
            }
        }
    }
}

/// Helper function to print a imag question string. The `question` argument may not contain a
/// trailing questionmark.
///
/// The `nl` parameter can be used to configure whether a newline character should be printed
pub fn ask_question(question: &str, nl: bool) {
    if nl {
        println!("[imag]: {}?", Yellow.paint(question));
    } else {
        print!("[imag]: {}?", Yellow.paint(question));
    }
}

#[cfg(test)]
mod test {
    use std::io::BufReader;

    use super::ask_bool_;
    use super::ask_uint_;

    #[test]
    fn test_ask_bool_nodefault_yes() {
        let question = "Is this true";
        let default  = None;
        let answers  = "\n\n\n\n\ny";

        assert!(ask_bool_(question, default, &mut BufReader::new(answers.as_bytes())));
    }

    #[test]
    fn test_ask_bool_nodefault_no() {
        let question = "Is this true";
        let default  = None;
        let answers  = "n";

        assert!(false == ask_bool_(question, default, &mut BufReader::new(answers.as_bytes())));
    }

    #[test]
    fn test_ask_bool_default_no() {
        let question = "Is this true";
        let default  = Some(false);
        let answers  = "n";

        assert!(false == ask_bool_(question, default, &mut BufReader::new(answers.as_bytes())));
    }

    #[test]
    fn test_ask_bool_default_yes() {
        let question = "Is this true";
        let default  = Some(true);
        let answers  = "y";

        assert!(true == ask_bool_(question, default, &mut BufReader::new(answers.as_bytes())));
    }

    #[test]
    fn test_ask_bool_default_yes_answer_no() {
        let question = "Is this true";
        let default  = Some(true);
        let answers  = "n";

        assert!(false == ask_bool_(question, default, &mut BufReader::new(answers.as_bytes())));
    }

    #[test]
    fn test_ask_bool_default_no_answer_yes() {
        let question = "Is this true";
        let default  = Some(false);
        let answers  = "y";

        assert!(true == ask_bool_(question, default, &mut BufReader::new(answers.as_bytes())));
    }

    #[test]
    fn test_ask_bool_default_no_without_answer() {
        let question = "Is this true";
        let default  = Some(false);
        let answers  = "\n";

        assert!(false == ask_bool_(question, default, &mut BufReader::new(answers.as_bytes())));
    }

    #[test]
    fn test_ask_bool_default_yes_without_answer() {
        let question = "Is this true";
        let default  = Some(true);
        let answers  = "\n";

        assert!(true == ask_bool_(question, default, &mut BufReader::new(answers.as_bytes())));
    }

    #[test]
    fn test_ask_uint_nodefault() {
        let question = "Is this 1";
        let default  = None;
        let answers  = "1";

        assert!(1 == ask_uint_(question, default, &mut BufReader::new(answers.as_bytes())));
    }

    #[test]
    fn test_ask_uint_default() {
        let question = "Is this 1";
        let default  = Some(1);
        let answers  = "1";

        assert!(1 == ask_uint_(question, default, &mut BufReader::new(answers.as_bytes())));
    }

    #[test]
    fn test_ask_uint_default_2_input_1() {
        let question = "Is this 1";
        let default  = Some(2);
        let answers  = "1";

        assert!(1 == ask_uint_(question, default, &mut BufReader::new(answers.as_bytes())));
    }

    #[test]
    fn test_ask_uint_default_2_noinput() {
        let question = "Is this 1";
        let default  = Some(2);
        let answers  = "\n";

        assert!(2 == ask_uint_(question, default, &mut BufReader::new(answers.as_bytes())));
    }

    #[test]
    fn test_ask_uint_default_2_several_noinput() {
        let question = "Is this 1";
        let default  = Some(2);
        let answers  = "\n\n\n\n";

        assert!(2 == ask_uint_(question, default, &mut BufReader::new(answers.as_bytes())));
    }

    #[test]
    fn test_ask_uint_default_2_wrong_input() {
        let question = "Is this 1";
        let default  = Some(2);
        let answers  = "\n\n\nasfb\nsakjf\naskjf\n-2";

        assert!(2 == ask_uint_(question, default, &mut BufReader::new(answers.as_bytes())));
    }

}
<|endoftext|>


<commit_before>use parking_lot::RwLock;
use std::any::Any;

use std::rc::Rc;

use super::Mode;

use crate::core::codepointinfo::CodepointInfo;

use crate::core::editor::register_input_stage_action;
use crate::core::editor::InputStageActionMap;
use crate::core::event::input_map::build_input_event_map;
use crate::core::Editor;
use crate::core::EditorEnv;

use crate::core::view;
use crate::core::view::ContentFilter;
use crate::core::view::FilterIo;
use crate::core::view::LayoutEnv;

use crate::core::view::View;

use crate::core::view::ViewEvent;
use crate::core::view::ViewEventDestination;
use crate::core::view::ViewEventSource;

use crate::core::event::*;

static VSCROLLBAR_INPUT_MAP: &str = r#"
[
  {
    "events": [
     { "in": [{ "pointer-motion": "" }], "action": "vscrollbar:input-event" },
     { "default": [],                    "action": "vscrollbar:input-event" }
   ]
  }

]"#;

pub struct VscrollbarMode {
    // add common fields
}
pub struct VscrollbarModeContext {
    pub target_vid: view::Id,

    pub percent: f64,
    pub percent_end: f64,
    pub scroll_start: usize,
    pub scroll_end: usize,
    pub selected: bool,
    pub t: std::time::Instant,
}

impl<'a> Mode for VscrollbarMode {
    fn name(&self) -> &'static str {
        &"vscrollbar-mode"
    }

    fn build_action_map(&self) -> InputStageActionMap<'static> {
        let mut map = InputStageActionMap::new();
        Self::register_input_stage_actions(&mut map);
        map
    }

    fn alloc_ctx(&self) -> Box<dyn Any> {
        dbg_println!("alloc vscrollbar-mode ctx");
        let ctx = VscrollbarModeContext {
            target_vid: view::Id(0),
            percent: 0.0,
            percent_end: 0.0,
            scroll_start: 0,
            scroll_end: 0,
            selected: false,
            t: std::time::Instant::now(),
        };
        Box::new(ctx)
    }

    fn configure_view(
        &mut self,
        _editor: &mut Editor<'static>,
        _env: &mut EditorEnv<'static>,
        view: &mut View<'static>,
    ) {
        // setup input map for core actions
        let input_map = build_input_event_map(VSCROLLBAR_INPUT_MAP).unwrap();
        let mut input_map_stack = view.input_ctx.input_map.as_ref().borrow_mut();
        input_map_stack.push(input_map);

        view.compose_content_filters
            .borrow_mut()
            .push(Box::new(VscrollbarModeComposeFilter::new()));
    }

    fn on_view_event(
        &self,
        editor: &mut Editor<'static>,
        _env: &mut EditorEnv<'static>,
        src: ViewEventSource,
        dst: ViewEventDestination,
        _event: &ViewEvent,
        _parent: Option<&mut View<'static>>,
    ) {
        let src = editor.view_map.get(&src.id).unwrap().write();

        let dim = src.screen.read().dimension();

        let mut dst = editor.view_map.get(&dst.id).unwrap().write();

        let mut mode_ctx = dst.mode_ctx_mut::<VscrollbarModeContext>("vscrollbar-mode");

        mode_ctx.target_vid = src.id;

        let doc = src.document.as_ref().unwrap();
        let doc = doc.read();
        let doc_size = doc.size();

        let off = src.start_offset as f64 / doc_size as f64;
        let off2 = src.end_offset as f64 / doc_size as f64;

        mode_ctx.percent = off * 100.0;
        mode_ctx.percent_end = off2 * 100.0;

        let height = dim.1 as f64 / 100.0;
        mode_ctx.scroll_start = (height * mode_ctx.percent) as usize;
        mode_ctx.scroll_start = std::cmp::min(dim.1.saturating_sub(1), mode_ctx.scroll_start);
        mode_ctx.scroll_end = (height * mode_ctx.percent_end) as usize;
        mode_ctx.scroll_end = if mode_ctx.scroll_end == mode_ctx.scroll_start {
            mode_ctx.scroll_start + 1
        } else {
            mode_ctx.scroll_end
        };
        mode_ctx.scroll_end = std::cmp::min(dim.1, mode_ctx.scroll_end);

        dbg_println!("SCROLLBAR: mode_ctx.percent {}", mode_ctx.percent);
        dbg_println!("SCROLLBAR: mode_ctx.percent_end {}", mode_ctx.percent_end);
    }
}

impl VscrollbarMode {
    pub fn new() -> Self {
        dbg_println!("VscrollbarMode");
        VscrollbarMode {}
    }

    pub fn register_input_stage_actions<'a>(mut map: &'a mut InputStageActionMap<'a>) {
        register_input_stage_action(&mut map, "vscrollbar:input-event", vscrollbar_input_event);
    }
}

// TODO?: mode:on_button_press(btn, x,y) ...
// TODO?: mode:on_button_release(btn ?) ...
// TODO?: mode:on_pointer_drag(btn, x,y)

pub fn vscrollbar_input_event(editor: &mut Editor, env: &mut EditorEnv, view: &Rc<RwLock<View>>) {
    let mut v = view.write();

    let evt = v.input_ctx.trigger.last();
    match evt {
        Some(InputEvent::ButtonPress(ref button_event)) => match button_event {
            ButtonEvent {
                mods:
                    KeyModifiers {
                        ctrl: _,
                        alt: _,
                        shift: _,
                    },
                x,
                y,
                button,
            } => {
                dbg_println!("VSCROLLBAR btn press evt {} {}x{}", button, x, y);

                if *button == 0 {
                    let y = *y as usize;
                    let mut mode_ctx = v.mode_ctx_mut::<VscrollbarModeContext>("vscrollbar-mode");
                    if y >= mode_ctx.scroll_start && y < mode_ctx.scroll_end {
                        mode_ctx.selected = true;
                        env.focus_locked_on = Some(v.id);
                    }
                    return;
                }
            }
        },

        Some(InputEvent::ButtonRelease(ref button_event)) => match button_event {
            ButtonEvent {
                mods:
                    KeyModifiers {
                        ctrl: _,
                        alt: _,
                        shift: _,
                    },
                x,
                y,
                button,
            } => {
                dbg_println!("VSCROLLBAR btn release evt {} {}x{}", button, x, y);

                if *button == 0 {
                    let mode_ctx = v.mode_ctx_mut::<VscrollbarModeContext>("vscrollbar-mode");
                    mode_ctx.selected = false;
                    env.focus_locked_on = None;
                }
            }
        },

        Some(InputEvent::PointerMotion(PointerEvent { x, y, mods: _ })) => {
            dbg_println!("VSCROLLBAR CLIPPING x {} y {}", x, y);
            let target_vid = {
                let mode_ctx = v.mode_ctx::<VscrollbarModeContext>("vscrollbar-mode");
                if !mode_ctx.selected {
                    return;
                }

                if mode_ctx.target_vid == view::Id(0) {
                    return;
                }
                mode_ctx.target_vid
            };

            let dim = v.screen.read().dimension();
            let mut dst = editor.view_map.get(&target_vid).unwrap().write();

            let doc_size = {
                let doc = dst.document.as_ref().unwrap();
                let doc = doc.read();
                std::cmp::max(1, doc.size()) // avoid div by zero
            };

            let y = std::cmp::max(0, *y) as usize; //  coordinates can be negative

            let y = if y < dim.1.saturating_sub(1) {
                y
            } else {
                dim.1.saturating_sub(1)
            };

            let percent = y as f64 / dim.1 as f64;
            let percent = if y < dim.1.saturating_sub(1) {
                percent
            } else {
                1.0
            };

            dbg_println!("SCROLLBAR H: {}", dim.1);
            dbg_println!("SCROLLBAR Y: {}", y);
            dbg_println!("SCROLLBAR percent: {}", percent);

            // set target's offset
            // the scrollbar dimension are recomputed in on_view_event
            let offset = (doc_size as f64 * percent) as u64;
            dst.start_offset = offset;

            // TODO(ceg): push action
            // center + scroll-down (h / 2)

            // TODO(ceg): update scrollbar.subscription()
            // view.on_event(ViewEvent { ChangeOffset{ offset } });
            // view.on_request(src, dsc, ViewEvent { ChangeOffset{ offset } });
        }

        _ => {
            dbg_println!("VSCROLLBAR unhandled event {:?}", evt);
            return;
        }
    };

    // TODO(ceg): move scrollbar
    {
        let mode_ctx = v.mode_ctx_mut::<VscrollbarModeContext>("vscrollbar-mode");
        if !mode_ctx.selected {
            return;
        }
    }
}

///////////////////////////////////////////////////////////////////////////////////////////////////

pub struct VscrollbarModeComposeFilter {}

impl VscrollbarModeComposeFilter {
    pub fn new() -> Self {
        VscrollbarModeComposeFilter {}
    }
}

impl ContentFilter<'_> for VscrollbarModeComposeFilter {
    fn name(&self) -> &'static str {
        &"vscrollbar-compose-filter"
    }

    fn run(
        &mut self,
        view: &View,
        env: &mut LayoutEnv,
        _filter_in: &Vec<FilterIo>,
        _filter_out: &mut Vec<FilterIo>,
    ) {
        let mode_ctx = view.mode_ctx::<VscrollbarModeContext>("vscrollbar-mode");
        let mut cpi = CodepointInfo::new();
        cpi.style.bg_color = (45, 49, 54);
        cpi.cp = ' ';
        cpi.displayed_cp = ' ';
        loop {
            let (b, _) = env.screen.push(cpi.clone());
            if !b {
                break;
            }
        }

        dbg_println!("SCROLLBAR height {}", env.screen.height());
        dbg_println!("SCROLLBAR start {}", mode_ctx.scroll_start);
        dbg_println!("SCROLLBAR end {}", mode_ctx.scroll_end);

        for i in mode_ctx.scroll_start..mode_ctx.scroll_end {
            if let Some(cpi) = env.screen.get_cpinfo_mut(0, i) {
                cpi.displayed_cp = ' ';
                cpi.style.is_selected = true;
                let add = 25;
                cpi.style.bg_color = (45 + add, 49 + add, 54 + add);
                if mode_ctx.selected {
                    let add = 50;
                    cpi.style.bg_color = (45 + add, 49 + add, 54 + add);
                } else {
                    let add = 25;
                    cpi.style.bg_color = (45 + add, 49 + add, 54 + add);
                }
            }
        }

        env.quit = true;
    }

    fn finish(&mut self, _view: &View, _env: &mut LayoutEnv) {}
}
<commit_msg>event handling: scrollbar: set focus on scrollbar's target view<commit_after>use parking_lot::RwLock;
use std::any::Any;

use std::rc::Rc;

use super::Mode;

use crate::core::codepointinfo::CodepointInfo;

use crate::core::editor::register_input_stage_action;
use crate::core::editor::InputStageActionMap;
use crate::core::event::input_map::build_input_event_map;
use crate::core::Editor;
use crate::core::EditorEnv;

use crate::core::view;
use crate::core::view::ContentFilter;
use crate::core::view::FilterIo;
use crate::core::view::LayoutEnv;

use crate::core::view::View;

use crate::core::view::ViewEvent;
use crate::core::view::ViewEventDestination;
use crate::core::view::ViewEventSource;

use crate::core::event::*;

static VSCROLLBAR_INPUT_MAP: &str = r#"
[
  {
    "events": [
     { "in": [{ "pointer-motion": "" }], "action": "vscrollbar:input-event" },
     { "default": [],                    "action": "vscrollbar:input-event" }
   ]
  }

]"#;

pub struct VscrollbarMode {
    // add common fields
}
pub struct VscrollbarModeContext {
    pub target_vid: view::Id,

    pub percent: f64,
    pub percent_end: f64,
    pub scroll_start: usize,
    pub scroll_end: usize,
    pub selected: bool,
    pub t: std::time::Instant,
}

impl<'a> Mode for VscrollbarMode {
    fn name(&self) -> &'static str {
        &"vscrollbar-mode"
    }

    fn build_action_map(&self) -> InputStageActionMap<'static> {
        let mut map = InputStageActionMap::new();
        Self::register_input_stage_actions(&mut map);
        map
    }

    fn alloc_ctx(&self) -> Box<dyn Any> {
        dbg_println!("alloc vscrollbar-mode ctx");
        let ctx = VscrollbarModeContext {
            target_vid: view::Id(0),
            percent: 0.0,
            percent_end: 0.0,
            scroll_start: 0,
            scroll_end: 0,
            selected: false,
            t: std::time::Instant::now(),
        };
        Box::new(ctx)
    }

    fn configure_view(
        &mut self,
        _editor: &mut Editor<'static>,
        _env: &mut EditorEnv<'static>,
        view: &mut View<'static>,
    ) {
        // setup input map for core actions
        let input_map = build_input_event_map(VSCROLLBAR_INPUT_MAP).unwrap();
        let mut input_map_stack = view.input_ctx.input_map.as_ref().borrow_mut();
        input_map_stack.push(input_map);

        view.compose_content_filters
            .borrow_mut()
            .push(Box::new(VscrollbarModeComposeFilter::new()));
    }

    fn on_view_event(
        &self,
        editor: &mut Editor<'static>,
        env: &mut EditorEnv<'static>,
        src: ViewEventSource,
        dst: ViewEventDestination,
        _event: &ViewEvent,
        _parent: Option<&mut View<'static>>,
    ) {
        let src = editor.view_map.get(&src.id).unwrap().write();

        let dim = src.screen.read().dimension();

        let mut dst = editor.view_map.get(&dst.id).unwrap().write();

        let mut mode_ctx = dst.mode_ctx_mut::<VscrollbarModeContext>("vscrollbar-mode");

        mode_ctx.target_vid = src.id;

        let doc = src.document.as_ref().unwrap();
        let doc = doc.read();
        let doc_size = doc.size();

        let off = src.start_offset as f64 / doc_size as f64;
        let off2 = src.end_offset as f64 / doc_size as f64;

        mode_ctx.percent = off * 100.0;
        mode_ctx.percent_end = off2 * 100.0;

        let height = dim.1 as f64 / 100.0;
        mode_ctx.scroll_start = (height * mode_ctx.percent) as usize;
        mode_ctx.scroll_start = std::cmp::min(dim.1.saturating_sub(1), mode_ctx.scroll_start);
        mode_ctx.scroll_end = (height * mode_ctx.percent_end) as usize;
        mode_ctx.scroll_end = if mode_ctx.scroll_end == mode_ctx.scroll_start {
            mode_ctx.scroll_start + 1
        } else {
            mode_ctx.scroll_end
        };
        mode_ctx.scroll_end = std::cmp::min(dim.1, mode_ctx.scroll_end);

        dbg_println!("SCROLLBAR: mode_ctx.percent {}", mode_ctx.percent);
        dbg_println!("SCROLLBAR: mode_ctx.percent_end {}", mode_ctx.percent_end);

        env.focus_changed_to = Some(mode_ctx.target_vid);
    }
}

impl VscrollbarMode {
    pub fn new() -> Self {
        dbg_println!("VscrollbarMode");
        VscrollbarMode {}
    }

    pub fn register_input_stage_actions<'a>(mut map: &'a mut InputStageActionMap<'a>) {
        register_input_stage_action(&mut map, "vscrollbar:input-event", vscrollbar_input_event);
    }
}

// TODO?: mode:on_button_press(btn, x,y) ...
// TODO?: mode:on_button_release(btn ?) ...
// TODO?: mode:on_pointer_drag(btn, x,y)

pub fn vscrollbar_input_event(editor: &mut Editor, env: &mut EditorEnv, view: &Rc<RwLock<View>>) {
    let mut v = view.write();

    let evt = v.input_ctx.trigger.last();
    match evt {
        Some(InputEvent::ButtonPress(ref button_event)) => match button_event {
            ButtonEvent {
                mods:
                    KeyModifiers {
                        ctrl: _,
                        alt: _,
                        shift: _,
                    },
                x,
                y,
                button,
            } => {
                dbg_println!("VSCROLLBAR btn press evt {} {}x{}", button, x, y);

                if *button == 0 {
                    let y = *y as usize;
                    let mut mode_ctx = v.mode_ctx_mut::<VscrollbarModeContext>("vscrollbar-mode");
                    if y >= mode_ctx.scroll_start && y < mode_ctx.scroll_end {
                        mode_ctx.selected = true;
                        env.focus_locked_on = Some(v.id);
                    }
                    return;
                }
            }
        },

        Some(InputEvent::ButtonRelease(ref button_event)) => match button_event {
            ButtonEvent {
                mods:
                    KeyModifiers {
                        ctrl: _,
                        alt: _,
                        shift: _,
                    },
                x,
                y,
                button,
            } => {
                dbg_println!("VSCROLLBAR btn release evt {} {}x{}", button, x, y);

                if *button == 0 {
                    let mode_ctx = v.mode_ctx_mut::<VscrollbarModeContext>("vscrollbar-mode");
                    mode_ctx.selected = false;
                    env.focus_locked_on = None;
                }
            }
        },

        Some(InputEvent::PointerMotion(PointerEvent { x, y, mods: _ })) => {
            dbg_println!("VSCROLLBAR CLIPPING x {} y {}", x, y);
            let target_vid = {
                let mode_ctx = v.mode_ctx::<VscrollbarModeContext>("vscrollbar-mode");
                if !mode_ctx.selected {
                    return;
                }

                if mode_ctx.target_vid == view::Id(0) {
                    return;
                }
                mode_ctx.target_vid
            };

            let dim = v.screen.read().dimension();
            let mut dst = editor.view_map.get(&target_vid).unwrap().write();

            let doc_size = {
                let doc = dst.document.as_ref().unwrap();
                let doc = doc.read();
                std::cmp::max(1, doc.size()) // avoid div by zero
            };

            let y = std::cmp::max(0, *y) as usize; //  coordinates can be negative

            let y = if y < dim.1.saturating_sub(1) {
                y
            } else {
                dim.1.saturating_sub(1)
            };

            let percent = y as f64 / dim.1 as f64;
            let percent = if y < dim.1.saturating_sub(1) {
                percent
            } else {
                1.0
            };

            dbg_println!("SCROLLBAR H: {}", dim.1);
            dbg_println!("SCROLLBAR Y: {}", y);
            dbg_println!("SCROLLBAR percent: {}", percent);

            // set target's offset
            // the scrollbar dimension are recomputed in on_view_event
            let offset = (doc_size as f64 * percent) as u64;
            dst.start_offset = offset;

            // TODO(ceg): push action
            // center + scroll-down (h / 2)

            // TODO(ceg): update scrollbar.subscription()
            // view.on_event(ViewEvent { ChangeOffset{ offset } });
            // view.on_request(src, dsc, ViewEvent { ChangeOffset{ offset } });
        }

        _ => {
            dbg_println!("VSCROLLBAR unhandled event {:?}", evt);
            return;
        }
    };

    // TODO(ceg): move scrollbar
    {
        let mode_ctx = v.mode_ctx_mut::<VscrollbarModeContext>("vscrollbar-mode");
        if !mode_ctx.selected {
            return;
        }
    }
}

///////////////////////////////////////////////////////////////////////////////////////////////////

pub struct VscrollbarModeComposeFilter {}

impl VscrollbarModeComposeFilter {
    pub fn new() -> Self {
        VscrollbarModeComposeFilter {}
    }
}

impl ContentFilter<'_> for VscrollbarModeComposeFilter {
    fn name(&self) -> &'static str {
        &"vscrollbar-compose-filter"
    }

    fn run(
        &mut self,
        view: &View,
        env: &mut LayoutEnv,
        _filter_in: &Vec<FilterIo>,
        _filter_out: &mut Vec<FilterIo>,
    ) {
        let mode_ctx = view.mode_ctx::<VscrollbarModeContext>("vscrollbar-mode");
        let mut cpi = CodepointInfo::new();
        cpi.style.bg_color = (45, 49, 54);
        cpi.cp = ' ';
        cpi.displayed_cp = ' ';
        loop {
            let (b, _) = env.screen.push(cpi.clone());
            if !b {
                break;
            }
        }

        dbg_println!("SCROLLBAR height {}", env.screen.height());
        dbg_println!("SCROLLBAR start {}", mode_ctx.scroll_start);
        dbg_println!("SCROLLBAR end {}", mode_ctx.scroll_end);

        for i in mode_ctx.scroll_start..mode_ctx.scroll_end {
            if let Some(cpi) = env.screen.get_cpinfo_mut(0, i) {
                cpi.displayed_cp = ' ';
                cpi.style.is_selected = true;
                let add = 25;
                cpi.style.bg_color = (45 + add, 49 + add, 54 + add);
                if mode_ctx.selected {
                    let add = 50;
                    cpi.style.bg_color = (45 + add, 49 + add, 54 + add);
                } else {
                    let add = 25;
                    cpi.style.bg_color = (45 + add, 49 + add, 54 + add);
                }
            }
        }

        env.quit = true;
    }

    fn finish(&mut self, _view: &View, _env: &mut LayoutEnv) {}
}
<|endoftext|>