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
|
use anyhow::{Context, Error, Result};
use crossterm::event::EventStream;
use helix_loader::VERSION_AND_GIT_HASH;
use helix_term::application::Application;
use helix_term::args::Args;
use helix_term::config::{Config, ConfigLoadError};
fn setup_logging(verbosity: u64) -> Result<()> {
let mut base_config = fern::Dispatch::new();
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(helix_loader::log_file())?);
base_config.chain(file_config).apply()?;
Ok(())
}
fn main() -> Result<()> {
let exit_code = main_impl()?;
std::process::exit(exit_code);
}
#[tokio::main]
async fn main_impl() -> Result<i32> {
let help = format!(
"\
{} {}
{}
{}
USAGE:
hx [FLAGS] [files]...
ARGS:
<files>... Sets the input file to use, position can also be specified via file[:row[:col]]
FLAGS:
-h, --help Prints help information
--tutor Loads the tutorial
--health [CATEGORY] Checks for potential errors in editor setup
CATEGORY can be a language or one of 'clipboard', 'languages'
or 'all'. 'all' is the default if not specified.
-g, --grammar {{fetch|build}} Fetches or builds tree-sitter grammars listed in languages.toml
-c, --config <file> Specifies a file to use for configuration
-v Increases logging verbosity each use for up to 3 times
--log <file> Specifies a file to use for logging
(default file: {})
-V, --version Prints version information
--vsplit Splits all given files vertically into different windows
--hsplit Splits all given files horizontally into different windows
-w, --working-dir <path> Specify an initial working directory
+N Open the first given file at line number N
",
env!("CARGO_PKG_NAME"),
VERSION_AND_GIT_HASH,
env!("CARGO_PKG_AUTHORS"),
env!("CARGO_PKG_DESCRIPTION"),
helix_loader::default_log_file().display(),
);
let mut args = Args::parse_args().context("could not parse arguments")?;
helix_loader::initialize_config_file(args.config_file.clone());
helix_loader::initialize_log_file(args.log_file.clone());
// 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!("helix {}", VERSION_AND_GIT_HASH);
std::process::exit(0);
}
if args.health {
if let Err(err) = helix_term::health::print_health(args.health_arg) {
// Piping to for example `head -10` requires special handling:
// https://stackoverflow.com/a/65760807/7115678
if err.kind() != std::io::ErrorKind::BrokenPipe {
return Err(err.into());
}
}
std::process::exit(0);
}
if args.fetch_grammars {
helix_loader::grammar::fetch_grammars()?;
return Ok(0);
}
if args.build_grammars {
helix_loader::grammar::build_grammars(None)?;
return Ok(0);
}
setup_logging(args.verbosity).context("failed to initialize logging")?;
// NOTE: Set the working directory early so the correct configuration is loaded. Be aware that
// Application::new() depends on this logic so it must be updated if this changes.
if let Some(path) = &args.working_directory {
helix_loader::set_current_working_dir(path)?;
}
// If the first file is a directory, it will be the working directory and a file picker will be opened
if let Some((path, _)) = args.files.first().filter(|p| p.0.is_dir()) {
helix_loader::set_current_working_dir(path)?;
args.open_cwd = true; // Signal Application that we want to open the picker on "."
}
let config = match Config::load_default() {
Ok(config) => config,
Err(ConfigLoadError::Error(err)) if err.kind() == std::io::ErrorKind::NotFound => {
Config::default()
}
Err(ConfigLoadError::Error(err)) => return Err(Error::new(err)),
Err(ConfigLoadError::BadConfig(err)) => {
eprintln!("Bad config: {}", err);
eprintln!("Press <ENTER> to continue with default config");
use std::io::Read;
let _ = std::io::stdin().read(&mut []);
Config::default()
}
};
let syn_loader_conf = helix_core::config::user_syntax_loader().unwrap_or_else(|err| {
eprintln!("Bad language config: {}", err);
eprintln!("Press <ENTER> to continue with default language config");
use std::io::Read;
// This waits for an enter press.
let _ = std::io::stdin().read(&mut []);
helix_core::config::default_syntax_loader()
});
// TODO: use the thread local executor to spawn the application task separately from the work pool
let mut app = Application::new(args, config, syn_loader_conf)
.context("unable to create new application")?;
let exit_code = app.run(&mut EventStream::new()).await?;
Ok(exit_code)
}
|