1use std::path::PathBuf;
4
5use clap::Parser;
6use env_logger::Env;
7
8use crate::{config::Config, error::LSError, server, text};
9
10#[derive(Parser, Debug)]
12#[command(name = "backtrace-ls")]
13#[command(about = "LSP server for showing test failures as diagnostics")]
14pub struct Args {
15 #[arg(long = "path")]
18 pub workspace_root: Option<PathBuf>,
19
20 #[arg(long = "text")]
23 pub text_mode: bool,
24
25 #[arg(long = "verbose", short = 'v')]
28 pub verbose: bool,
29
30 #[arg(long = "runner")]
33 pub runner: Option<String>,
34}
35
36pub async fn run() -> Result<(), LSError> {
40 let args = Args::parse();
41
42 let default_level = if args.verbose {
44 "backtrace_ls=debug"
45 } else {
46 "backtrace_ls=info"
47 };
48 let l = Env::default().default_filter_or(default_level);
49 env_logger::Builder::from_env(l)
50 .format(format_log_record)
51 .init();
52 log::debug!("Starting logger with level");
53
54 let config = Config {
55 workspace_root: args.workspace_root,
56 runner: args.runner,
57 ..Config::default()
58 };
59
60 if args.text_mode {
61 text::run(config).await
62 } else {
63 server::run(config).await
64 }
65}
66use std::{env::current_dir, io, io::Write};
67
68use env_logger::fmt::Formatter;
69
70#[cfg(test)]
73pub fn init_env_log() {
74 env_logger::builder()
75 .format(format_log_record)
76 .is_test(true)
77 .try_init()
78 .ok();
79}
80
81pub fn init_log() {
82 env_logger::builder()
83 .format(format_log_record)
84 .filter_level(log::LevelFilter::Debug)
85 .try_init()
86 .ok();
87}
88
89fn format_log_record(buf: &mut Formatter, record: &log::Record) -> io::Result<()> {
90 let relative_file = get_relative_file_path(record);
91 let (color_start, color_end) = get_level_colors(record.level());
92
93 writeln!(
94 buf,
95 "{}{}:{} {}{}",
96 color_start,
97 relative_file,
98 record.line().unwrap_or(0),
99 record.args(),
100 color_end
101 )
102}
103
104fn get_relative_file_path<'a>(record: &'a log::Record) -> &'a str {
105 let file = record.file().unwrap_or("unknown");
106 current_dir()
107 .ok()
108 .and_then(|cwd| file.strip_prefix(&*cwd.to_string_lossy()))
109 .unwrap_or(file)
110 .trim_start_matches('/')
111}
112
113const fn get_level_colors(level: log::Level) -> (&'static str, &'static str) {
114 match level {
115 log::Level::Error => ("\x1b[91m", "\x1b[0m"), log::Level::Warn => ("\x1b[93m", "\x1b[0m"), log::Level::Info => ("\x1b[34m", "\x1b[0m"), log::Level::Debug => ("\x1b[96m", "\x1b[0m"), log::Level::Trace => ("\x1b[90m", "\x1b[0m"), }
121}