aboutsummaryrefslogtreecommitdiff
path: root/helix-term/src/main.rs
blob: 432f2a200ab98330465df46558d12182cf9eb129 (plain) (blame)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
#![allow(unused)]

mod application;
mod commands;
mod compositor;
mod keymap;
mod ui;

use application::Application;

use helix_core::config_dir;

use std::path::PathBuf;

use anyhow::{Context, Error, Result};

fn setup_logging(verbosity: u64) -> Result<()> {
    let mut base_config = fern::Dispatch::new();

    // Let's say we depend on something which whose "info" level messages are too
    // verbose to include in end-user output. If we don't need them,
    // let's not include them.
    // .level_for("overly-verbose-target", log::LevelFilter::Warn)

    base_config = match verbosity {
        0 => base_config.level(log::LevelFilter::Warn),
        1 => base_config.level(log::LevelFilter::Info),
        2 => base_config.level(log::LevelFilter::Debug),
        _3_or_more => base_config.level(log::LevelFilter::Trace),
    };

    // Separate file config so we can include year, month and day in file logs
    let file_config = fern::Dispatch::new()
        .format(|out, message, record| {
            out.finish(format_args!(
                "{} {} [{}] {}",
                chrono::Local::now().format("%Y-%m-%dT%H:%M:%S%.3f"),
                record.target(),
                record.level(),
                message
            ))
        })
        .chain(fern::log_file(config_dir().join("helix.log"))?);

    base_config.chain(file_config).apply()?;

    Ok(())
}

pub struct Args {
    display_help: bool,
    display_version: bool,
    verbosity: u64,
    files: Vec<PathBuf>,
}

fn parse_args(mut args: Args) -> Result<Args> {
    let argv: Vec<String> = std::env::args().collect();
    let mut iter = argv.iter();

    iter.next(); // skip the program, we don't care about that

    loop {
        match iter.next() {
            Some(arg) if arg == "--" => break, // stop parsing at this point treat the remaining as files
            Some(arg) if arg == "--version" => args.display_version = true,
            Some(arg) if arg == "--help" => args.display_help = true,
            Some(arg) if arg.starts_with("--") => {
                return Err(Error::msg(format!(
                    "unexpected double dash argument: {}",
                    arg
                )))
            }
            Some(arg) if arg.starts_with('-') => {
                let arg = arg.as_str().get(1..).unwrap().chars();
                for chr in arg {
                    match chr {
                        'v' => args.verbosity += 1,
                        'V' => args.display_version = true,
                        'h' => args.display_help = true,
                        _ => return Err(Error::msg(format!("unexpected short arg {}", chr))),
                    }
                }
            }
            Some(arg) => args.files.push(PathBuf::from(arg)),
            None => break, // No more arguments to reduce
        }
    }

    // push the remaining args, if any to the files
    for filename in iter {
        args.files.push(PathBuf::from(filename));
    }

    Ok(args)
}

#[tokio::main]
async fn main() -> Result<()> {
    let help = format!(
        "\
{} {}
{}
{}

USAGE:
    hx [FLAGS] [files]...

ARGS:
    <files>...    Sets the input file to use

FLAGS:
    -h, --help       Prints help information
    -v               Increases logging verbosity each use for up to 3 times
    -V, --version    Prints version information
",
        env!("CARGO_PKG_NAME"),
        env!("CARGO_PKG_VERSION"),
        env!("CARGO_PKG_AUTHORS"),
        env!("CARGO_PKG_DESCRIPTION"),
    );

    let mut args: Args = Args {
        display_help: false,
        display_version: false,
        verbosity: 0,
        files: [].to_vec(),
    };

    args = parse_args(args).context("could not parse arguments")?;

    // Help has a higher priority and should be handled separately.
    if args.display_help {
        print!("{}", help);
        std::process::exit(0);
    }

    if args.display_version {
        println!("{} {}", env!("CARGO_PKG_NAME"), env!("CARGO_PKG_VERSION"));
        std::process::exit(0);
    }

    let conf_dir = config_dir();

    if !conf_dir.exists() {
        std::fs::create_dir(&conf_dir);
    }

    setup_logging(args.verbosity).context("failed to initialize logging")?;

    // initialize language registry
    use helix_core::syntax::{Loader, LOADER};

    // load $HOME/.config/helix/languages.toml, fallback to default config
    let config = std::fs::read(config_dir().join("languages.toml"));
    let toml = config
        .as_deref()
        .unwrap_or(include_bytes!("../../languages.toml"));

    let config = toml::from_slice(toml).context("Could not parse languages.toml")?;
    LOADER.get_or_init(|| Loader::new(config));

    // TODO: use the thread local executor to spawn the application task separately from the work pool
    let mut app = Application::new(args).context("unable to create new appliction")?;
    app.run().await;

    Ok(())
}