diff options
Diffstat (limited to 'src')
-rw-r--r-- | src/ansi.rs | 6 | ||||
-rw-r--r-- | src/cli.rs | 13 | ||||
-rw-r--r-- | src/config.rs | 87 | ||||
-rw-r--r-- | src/display.rs | 45 | ||||
-rw-r--r-- | src/event.rs | 2 | ||||
-rw-r--r-- | src/grid/tests.rs | 12 | ||||
-rw-r--r-- | src/input.rs | 4 | ||||
-rw-r--r-- | src/lib.rs | 1 | ||||
-rw-r--r-- | src/logging.rs | 203 | ||||
-rw-r--r-- | src/macros.rs | 28 | ||||
-rw-r--r-- | src/main.rs | 35 | ||||
-rw-r--r-- | src/renderer/mod.rs | 14 | ||||
-rw-r--r-- | src/term/mod.rs | 14 |
13 files changed, 338 insertions, 126 deletions
diff --git a/src/ansi.rs b/src/ansi.rs index f1ca759a..2171f868 100644 --- a/src/ansi.rs +++ b/src/ansi.rs @@ -60,7 +60,7 @@ fn parse_rgb_color(color: &[u8]) -> Option<Rgb> { let r = parse_hex!(); let val = next!(); - if val != Some('/') { println!("val={:?}", val); return None; } + if val != Some('/') { return None; } let g = parse_hex!(); if next!() != Some('/') { return None; } let b = parse_hex!(); @@ -901,7 +901,7 @@ impl<'a, H, W> vte::Perform for Performer<'a, H, W> macro_rules! unhandled { () => {{ - warn!("[Unhandled CSI] action={:?}, args={:?}, intermediates={:?}", + debug!("[Unhandled CSI] action={:?}, args={:?}, intermediates={:?}", action, args, intermediates); return; }} @@ -1137,7 +1137,7 @@ impl<'a, H, W> vte::Perform for Performer<'a, H, W> ) { macro_rules! unhandled { () => {{ - warn!("[unhandled] esc_dispatch params={:?}, ints={:?}, byte={:?} ({:02x})", + debug!("[unhandled] esc_dispatch params={:?}, ints={:?}, byte={:?} ({:02x})", params, intermediates, byte as char, byte); return; }} @@ -31,6 +31,7 @@ pub struct Options { pub command: Option<Shell<'static>>, pub working_dir: Option<PathBuf>, pub config: Option<PathBuf>, + pub persistent_logging: bool, } impl Default for Options { @@ -46,6 +47,7 @@ impl Default for Options { command: None, working_dir: None, config: None, + persistent_logging: false, } } } @@ -71,6 +73,9 @@ impl Options { .conflicts_with("live-config-reload")) .arg(Arg::with_name("print-events") .long("print-events")) + .arg(Arg::with_name("persistent-logging") + .long("persistent-logging") + .help("Keep the log file after quitting Alacritty")) .arg(Arg::with_name("dimensions") .long("dimensions") .short("d") @@ -129,6 +134,10 @@ impl Options { options.live_config_reload = Some(false); } + if matches.is_present("persistent-logging") { + options.persistent_logging = true; + } + if let Some(mut dimensions) = matches.values_of("dimensions") { let width = dimensions.next().map(|w| w.parse().map(Column)); let height = dimensions.next().map(|h| h.parse().map(Line)); @@ -147,8 +156,8 @@ impl Options { } match matches.occurrences_of("v") { - 0 => {}, - 1 => options.log_level = log::LevelFilter::Info, + 0 if !options.print_events => {}, + 0 | 1 => options.log_level = log::LevelFilter::Info, 2 => options.log_level = log::LevelFilter::Debug, 3 | _ => options.log_level = log::LevelFilter::Trace } diff --git a/src/config.rs b/src/config.rs index ee30c085..ae1ec897 100644 --- a/src/config.rs +++ b/src/config.rs @@ -73,7 +73,7 @@ fn deserialize_duration_ms<'a, D>(deserializer: D) -> ::std::result::Result<Dura match u64::deserialize(deserializer) { Ok(threshold_ms) => Ok(Duration::from_millis(threshold_ms)), Err(err) => { - eprintln!("problem with config: {}; Using default value", err); + error!("problem with config: {}; Using default value", err); Ok(default_threshold_ms()) }, } @@ -172,7 +172,7 @@ fn deserialize_visual_bell_duration<'a, D>(deserializer: D) -> ::std::result::Re match u16::deserialize(deserializer) { Ok(duration) => Ok(duration), Err(err) => { - eprintln!("problem with config: {}; Using default value", err); + error!("problem with config: {}; Using default value", err); Ok(default_visual_bell_duration()) }, } @@ -310,19 +310,19 @@ impl<'de> Deserialize<'de> for Decorations { "none" => Ok(Decorations::None), "full" => Ok(Decorations::Full), "true" => { - eprintln!("deprecated decorations boolean value, \ + error!("deprecated decorations boolean value, \ use one of transparent|buttonless|none|full instead; \ Falling back to \"full\""); Ok(Decorations::Full) }, "false" => { - eprintln!("deprecated decorations boolean value, \ + error!("deprecated decorations boolean value, \ use one of transparent|buttonless|none|full instead; \ Falling back to \"none\""); Ok(Decorations::None) }, _ => { - eprintln!("invalid decorations value: {}; Using default value", value); + error!("invalid decorations value: {}; Using default value", value); Ok(Decorations::Full) } } @@ -336,23 +336,23 @@ impl<'de> Deserialize<'de> for Decorations { "none" => Ok(Decorations::None), "full" => Ok(Decorations::Full), "true" => { - eprintln!("deprecated decorations boolean value, \ + error!("deprecated decorations boolean value, \ use one of none|full instead; \ Falling back to \"full\""); Ok(Decorations::Full) }, "false" => { - eprintln!("deprecated decorations boolean value, \ + error!("deprecated decorations boolean value, \ use one of none|full instead; \ Falling back to \"none\""); Ok(Decorations::None) }, "transparent" | "buttonless" => { - eprintln!("macos-only decorations value: {}; Using default value", value); + error!("macos-only decorations value: {}; Using default value", value); Ok(Decorations::Full) }, _ => { - eprintln!("invalid decorations value: {}; Using default value", value); + error!("invalid decorations value: {}; Using default value", value); Ok(Decorations::Full) } } @@ -392,7 +392,7 @@ fn deserialize_padding<'a, D>(deserializer: D) -> ::std::result::Result<Delta<u8 match Delta::deserialize(deserializer) { Ok(delta) => Ok(delta), Err(err) => { - eprintln!("problem with config: {}; Using default value", err); + error!("problem with config: {}; Using default value", err); Ok(default_padding()) }, } @@ -503,6 +503,10 @@ pub struct Config { #[serde(default, deserialize_with="failure_default")] cursor: Cursor, + /// Keep the log file after quitting + #[serde(default, deserialize_with="failure_default")] + persistent_logging: bool, + // TODO: DEPRECATED #[serde(default, deserialize_with = "failure_default")] custom_cursor_colors: Option<bool>, @@ -528,7 +532,7 @@ fn failure_default_vec<'a, D, T>(deserializer: D) -> ::std::result::Result<Vec<T let vec = match Vec::<serde_yaml::Value>::deserialize(deserializer) { Ok(vec) => vec, Err(err) => { - eprintln!("problem with config: {}; Using empty vector", err); + error!("problem with config: {}; Using empty vector", err); return Ok(Vec::new()); }, }; @@ -539,7 +543,7 @@ fn failure_default_vec<'a, D, T>(deserializer: D) -> ::std::result::Result<Vec<T match T::deserialize(value) { Ok(binding) => bindings.push(binding), Err(err) => { - eprintln!("problem with config: {}; Skipping value", err); + error!("problem with config: {}; Skipping value", err); }, } } @@ -557,7 +561,7 @@ fn deserialize_tabspaces<'a, D>(deserializer: D) -> ::std::result::Result<usize, match usize::deserialize(deserializer) { Ok(value) => Ok(value), Err(err) => { - eprintln!("problem with config: {}; Using `8`", err); + error!("problem with config: {}; Using `8`", err); Ok(default_tabspaces()) }, } @@ -569,7 +573,7 @@ fn default_true_bool<'a, D>(deserializer: D) -> ::std::result::Result<bool, D::E match bool::deserialize(deserializer) { Ok(value) => Ok(value), Err(err) => { - eprintln!("problem with config: {}; Using `true`", err); + error!("problem with config: {}; Using `true`", err); Ok(true) }, } @@ -583,7 +587,7 @@ fn failure_default<'a, D, T>(deserializer: D) match T::deserialize(deserializer) { Ok(value) => Ok(value), Err(err) => { - eprintln!("problem with config: {}; Using default value", err); + error!("problem with config: {}; Using default value", err); Ok(T::default()) }, } @@ -645,7 +649,7 @@ fn deserialize_scrolling_history<'a, D>(deserializer: D) -> ::std::result::Resul match u32::deserialize(deserializer) { Ok(lines) => { if lines > MAX_SCROLLBACK_LINES { - eprintln!( + error!( "problem with config: scrollback size is {}, but expected a maximum of {}; \ Using {1} instead", lines, MAX_SCROLLBACK_LINES, @@ -656,7 +660,7 @@ fn deserialize_scrolling_history<'a, D>(deserializer: D) -> ::std::result::Resul } }, Err(err) => { - eprintln!("problem with config: {}; Using default value", err); + error!("problem with config: {}; Using default value", err); Ok(default_scrolling_history()) }, } @@ -668,7 +672,7 @@ fn deserialize_scrolling_multiplier<'a, D>(deserializer: D) -> ::std::result::Re match u8::deserialize(deserializer) { Ok(lines) => Ok(lines), Err(err) => { - eprintln!("problem with config: {}; Using default value", err); + error!("problem with config: {}; Using default value", err); Ok(default_scrolling_multiplier()) }, } @@ -710,7 +714,7 @@ impl<'a> de::Deserialize<'a> for ModsWrapper { "Shift" => res.shift = true, "Alt" | "Option" => res.alt = true, "Control" => res.ctrl = true, - _ => eprintln!("unknown modifier {:?}", modifier), + _ => error!("unknown modifier {:?}", modifier), } } @@ -831,7 +835,7 @@ impl<'a> de::Deserialize<'a> for ModeWrapper { "~AppCursor" => res.not_mode |= mode::TermMode::APP_CURSOR, "AppKeypad" => res.mode |= mode::TermMode::APP_KEYPAD, "~AppKeypad" => res.not_mode |= mode::TermMode::APP_KEYPAD, - _ => eprintln!("unknown mode {:?}", modifier), + _ => error!("unknown mode {:?}", modifier), } } @@ -1193,7 +1197,7 @@ fn deserialize_color_index<'a, D>(deserializer: D) -> ::std::result::Result<u8, match u8::deserialize(deserializer) { Ok(index) => { if index < 16 { - eprintln!( + error!( "problem with config: indexed_color's index is '{}', \ but a value bigger than 15 was expected; \ Ignoring setting", @@ -1207,7 +1211,7 @@ fn deserialize_color_index<'a, D>(deserializer: D) -> ::std::result::Result<u8, } }, Err(err) => { - eprintln!("problem with config: {}; Ignoring setting", err); + error!("problem with config: {}; Ignoring setting", err); // Return value out of range to ignore this color Ok(0) @@ -1262,7 +1266,7 @@ fn deserialize_optional_color<'a, D>(deserializer: D) -> ::std::result::Result<O }, Ok(None) => Ok(None), Err(err) => { - eprintln!("problem with config: {}; Using standard foreground color", err); + error!("problem with config: {}; Using standard foreground color", err); Ok(None) }, } @@ -1361,7 +1365,7 @@ fn rgb_from_hex<'a, D>(deserializer: D) -> ::std::result::Result<Rgb, D::Error> match rgb { Ok(rgb) => Ok(rgb), Err(err) => { - eprintln!("problem with config: {}; Using color #ff00ff", err); + error!("problem with config: {}; Using color #ff00ff", err); Ok(Rgb { r: 255, g: 0, b: 255 }) }, } @@ -1671,6 +1675,12 @@ impl Config { self.scrolling.history = history; } + /// Keep the log file after quitting Alacritty + #[inline] + pub fn persistent_logging(&self) -> bool { + self.persistent_logging + } + pub fn load_from<P: Into<PathBuf>>(path: P) -> Result<Config> { let path = path.into(); let raw = Config::read_file(path.as_path())?; @@ -1701,24 +1711,22 @@ impl Config { } fn print_deprecation_warnings(&mut self) { - use ::util::fmt; if self.dimensions.is_some() { - eprintln!("{}", fmt::Yellow("Config `dimensions` is deprecated. \ - Please use `window.dimensions` instead.")); + warn!("{}", "Config `dimensions` is deprecated. \ + Please use `window.dimensions` instead."); } if self.padding.is_some() { - eprintln!("{}", fmt::Yellow("Config `padding` is deprecated. \ - Please use `window.padding` instead.")); + warn!("{}", "Config `padding` is deprecated. Please use `window.padding` instead."); } if self.mouse.faux_scrollback_lines.is_some() { - println!("{}", fmt::Yellow("Config `mouse.faux_scrollback_lines` is deprecated. \ - Please use `mouse.faux_scrolling_lines` instead.")); + warn!("{}", "Config `mouse.faux_scrollback_lines` is deprecated. \ + Please use `mouse.faux_scrolling_lines` instead."); } if let Some(custom_cursor_colors) = self.custom_cursor_colors { - eprintln!("{}", fmt::Yellow("Config `custom_cursor_colors` is deprecated.")); + warn!("{}", "Config `custom_cursor_colors` is deprecated."); if !custom_cursor_colors { self.colors.cursor.cursor = None; @@ -1727,18 +1735,17 @@ impl Config { } if self.cursor_style.is_some() { - eprintln!("{}", fmt::Yellow("Config `cursor_style` is deprecated. \ - Please use `cursor.style` instead.")); + warn!("{}", "Config `cursor_style` is deprecated. Please use `cursor.style` instead."); } if self.hide_cursor_when_typing.is_some() { - eprintln!("{}", fmt::Yellow("Config `hide_cursor_when_typing` is deprecated. \ - Please use `mouse.hide_when_typing` instead.")); + warn!("{}", "Config `hide_cursor_when_typing` is deprecated. \ + Please use `mouse.hide_when_typing` instead."); } if self.unfocused_hollow_cursor.is_some() { - eprintln!("{}", fmt::Yellow("Config `unfocused_hollow_cursor` is deprecated. \ - Please use `cursor.unfocused_hollow` instead.")); + warn!("{}", "Config `unfocused_hollow_cursor` is deprecated. \ + Please use `cursor.unfocused_hollow` instead."); } } } @@ -1839,7 +1846,7 @@ impl DeserializeSize for Size { match size { Ok(size) => Ok(size), Err(err) => { - eprintln!("problem with config: {}; Using size 12", err); + error!("problem with config: {}; Using size 12", err); Ok(Size::new(12.)) }, } @@ -2062,7 +2069,7 @@ impl Monitor { let _ = config_tx.send(config); handler.on_config_reload(); }, - Err(err) => eprintln!("Ignoring invalid config: {}", err), + Err(err) => error!("Ignoring invalid config: {}", err), } } } diff --git a/src/display.rs b/src/display.rs index 1a145185..12842649 100644 --- a/src/display.rs +++ b/src/display.rs @@ -28,6 +28,7 @@ use renderer::{self, GlyphCache, QuadRenderer}; use term::{Term, SizeInfo, RenderableCell}; use sync::FairMutex; use window::{self, Window}; +use logging::LoggerProxy; use Rgb; #[derive(Debug)] @@ -99,6 +100,7 @@ pub struct Display { meter: Meter, font_size: font::Size, size_info: SizeInfo, + logger_proxy: LoggerProxy, } /// Can wakeup the render loop from other threads @@ -129,7 +131,11 @@ impl Display { &self.size_info } - pub fn new(config: &Config, options: &cli::Options) -> Result<Display, Error> { + pub fn new( + config: &Config, + options: &cli::Options, + logger_proxy: LoggerProxy + ) -> Result<Display, Error> { // Extract some properties from config let render_timer = config.render_timer(); @@ -218,6 +224,7 @@ impl Display { meter: Meter::new(), font_size: font::Size::new(0.), size_info, + logger_proxy, }) } @@ -420,10 +427,38 @@ impl Display { g: 0x4e, b: 0x53, }; - self.renderer - .with_api(config, &size_info, visual_bell_intensity, |mut api| { - api.render_string(&timing[..], glyph_cache, color); - }); + self.renderer.with_api(config, &size_info, visual_bell_intensity, |mut api| { + api.render_string(&timing[..], size_info.lines() - 2, glyph_cache, color); + }); + } + + // Display errors and warnings + if self.logger_proxy.errors() { + let msg = match self.logger_proxy.log_path() { + Some(path) => format!(" ERROR! See log at {} ", path), + None => " ERROR! See log in stderr ".into(), + }; + let color = Rgb { + r: 0xff, + g: 0x00, + b: 0x00, + }; + self.renderer.with_api(config, &size_info, visual_bell_intensity, |mut api| { + api.render_string(&msg, size_info.lines() - 1, glyph_cache, color); + }); + } else if self.logger_proxy.warnings() { + let msg = match self.logger_proxy.log_path() { + Some(path) => format!(" WARNING! See log at {} ", path), + None => " WARNING! See log in stderr ".into(), + }; + let color = Rgb { + r: 0xff, + g: 0xff, + b: 0x00, + }; + self.renderer.with_api(config, &size_info, visual_bell_intensity, |mut api| { + api.render_string(&msg, size_info.lines() - 1, glyph_cache, color); + }); } } diff --git a/src/event.rs b/src/event.rs index 7a1faa70..ece8ec6f 100644 --- a/src/event.rs +++ b/src/event.rs @@ -488,7 +488,7 @@ impl<N: Notify> Processor<N> { let hide_mouse = &mut self.hide_mouse; let mut process = |event| { if print_events { - println!("glutin event: {:?}", event); + info!("glutin event: {:?}", event); } Processor::handle_event( &mut processor, diff --git a/src/grid/tests.rs b/src/grid/tests.rs index e136e3b3..9cb30a7f 100644 --- a/src/grid/tests.rs +++ b/src/grid/tests.rs @@ -20,19 +20,13 @@ use index::{Point, Line, Column}; // Scroll up moves lines upwards #[test] fn scroll_up() { - println!(); - let mut grid = Grid::new(Line(10), Column(1), 0, 0); for i in 0..10 { grid[Line(i)][Column(0)] = i; } - println!("grid: {:?}", grid); - grid.scroll_up(&(Line(0)..Line(10)), Line(2), &0); - println!("grid: {:?}", grid); - assert_eq!(grid[Line(0)][Column(0)], 2); assert_eq!(grid[Line(0)].occ, 1); assert_eq!(grid[Line(1)][Column(0)], 3); @@ -58,19 +52,13 @@ fn scroll_up() { // Scroll down moves lines downwards #[test] fn scroll_down() { - println!(); - let mut grid = Grid::new(Line(10), Column(1), 0, 0); for i in 0..10 { grid[Line(i)][Column(0)] = i; } - println!("grid: {:?}", grid); - grid.scroll_down(&(Line(0)..Line(10)), Line(2), &0); - println!("grid: {:?}", grid); - assert_eq!(grid[Line(0)][Column(0)], 0); // was 8 assert_eq!(grid[Line(0)].occ, 0); assert_eq!(grid[Line(1)][Column(0)], 0); // was 9 diff --git a/src/input.rs b/src/input.rs index 600a1abe..d2f8f6b5 100644 --- a/src/input.rs +++ b/src/input.rs @@ -218,7 +218,7 @@ impl Action { .and_then(|clipboard| clipboard.load_primary() ) .map(|contents| { self.paste(ctx, &contents) }) .unwrap_or_else(|err| { - eprintln!("Error loading data from clipboard. {}", Red(err)); + error!("Error loading data from clipboard. {}", Red(err)); }); }, Action::PasteSelection => { @@ -228,7 +228,7 @@ impl Action { .and_then(|clipboard| clipboard.load_selection() ) .map(|contents| { self.paste(ctx, &contents) }) .unwrap_or_else(|err| { - warn!("Error loading data from clipboard. {}", Red(err)); + error!("Error loading data from clipboard. {}", Red(err)); }); } }, @@ -64,6 +64,7 @@ extern crate xdg; extern crate base64; extern crate terminfo; extern crate url; +extern crate time; #[macro_use] pub mod macros; diff --git a/src/logging.rs b/src/logging.rs index 10929980..5ad1dcd5 100644 --- a/src/logging.rs +++ b/src/logging.rs @@ -17,37 +17,134 @@ //! The main executable is supposed to call `initialize()` exactly once during //! startup. All logging messages are written to stdout, given that their //! log-level is sufficient for the level configured in `cli::Options`. -use log; -use std::sync; -use std::io; use cli; +use log::{self, Level}; +use time; -pub struct Logger<T> { +use std::env; +use std::fs::{self, File, OpenOptions}; +use std::io::{self, LineWriter, Stdout, Write}; +use std::path::PathBuf; +use std::process; +use std::sync::atomic::{AtomicBool, Ordering}; +use std::sync::{Arc, Mutex}; + +pub fn initialize(options: &cli::Options) -> Result<LoggerProxy, log::SetLoggerError> { + // Use env_logger if RUST_LOG environment variable is defined. Otherwise, + // use the alacritty-only logger. + if ::std::env::var("RUST_LOG").is_ok() { + ::env_logger::try_init()?; + Ok(LoggerProxy::default()) + } else { + let logger = Logger::new(options.log_level); + let proxy = logger.proxy(); + + log::set_boxed_logger(Box::new(logger))?; + + Ok(proxy) + } +} + +/// Proxy object for bidirectional communicating with the global logger. +#[derive(Clone, Default)] +pub struct LoggerProxy { + errors: Arc<AtomicBool>, + warnings: Arc<AtomicBool>, + logfile_proxy: OnDemandLogFileProxy, +} + +impl LoggerProxy { + /// Check for new logged errors. + pub fn errors(&self) -> bool { + self.errors.load(Ordering::Relaxed) + } + + /// Check for new logged warnings. + pub fn warnings(&self) -> bool { + self.warnings.load(Ordering::Relaxed) + } + + /// Get the path of the log file if it has been created. + pub fn log_path(&self) -> Option<&str> { + if self.logfile_proxy.created.load(Ordering::Relaxed) { + Some(&self.logfile_proxy.path) + } else { + None + } + } + + /// Clear log warnings/errors from the Alacritty UI. + pub fn clear(&mut self) { + self.errors.store(false, Ordering::Relaxed); + self.warnings.store(false, Ordering::Relaxed); + } + + pub fn delete_log(&mut self) { + self.logfile_proxy.delete_log(); + } +} + +struct Logger { level: log::LevelFilter, - output: sync::Mutex<T> + logfile: Mutex<OnDemandLogFile>, + stdout: Mutex<LineWriter<Stdout>>, + errors: Arc<AtomicBool>, + warnings: Arc<AtomicBool>, } -impl<T: Send + io::Write> Logger<T> { +impl Logger { // False positive, see: https://github.com/rust-lang-nursery/rust-clippy/issues/734 #[cfg_attr(feature = "cargo-clippy", allow(new_ret_no_self))] - pub fn new(output: T, level: log::LevelFilter) -> Logger<io::LineWriter<T>> { + fn new(level: log::LevelFilter) -> Self { log::set_max_level(level); + + let logfile = Mutex::new(OnDemandLogFile::new()); + let stdout = Mutex::new(LineWriter::new(io::stdout())); + Logger { level, - output: sync::Mutex::new(io::LineWriter::new(output)) + logfile, + stdout, + errors: Arc::new(AtomicBool::new(false)), + warnings: Arc::new(AtomicBool::new(false)), + } + } + + fn proxy(&self) -> LoggerProxy { + LoggerProxy { + errors: self.errors.clone(), + warnings: self.warnings.clone(), + logfile_proxy: self.logfile.lock().expect("").proxy(), } } } -impl<T: Send + io::Write> log::Log for Logger<T> { +impl log::Log for Logger { fn enabled(&self, metadata: &log::Metadata) -> bool { metadata.level() <= self.level } fn log(&self, record: &log::Record) { if self.enabled(record.metadata()) && record.target().starts_with("alacritty") { - if let Ok(ref mut writer) = self.output.lock() { - let _ = writer.write_all(format!("{}\n", record.args()).as_ref()); + let msg = format!( + "[{}] [{}] {}\n", + time::now().strftime("%F %R").unwrap(), + record.level(), + record.args() + ); + + if let Ok(ref mut logfile) = self.logfile.lock() { + let _ = logfile.write_all(msg.as_ref()); + } + + if let Ok(ref mut stdout) = self.stdout.lock() { + let _ = stdout.write_all(msg.as_ref()); + } + + match record.level() { + Level::Error => self.errors.store(true, Ordering::Relaxed), + Level::Warn => self.warnings.store(true, Ordering::Relaxed), + _ => (), } } } @@ -55,12 +152,82 @@ impl<T: Send + io::Write> log::Log for Logger<T> { fn flush(&self) {} } -pub fn initialize(options: &cli::Options) -> Result<(), log::SetLoggerError> { - // Use env_logger if RUST_LOG environment variable is defined. Otherwise, - // use the alacritty-only logger. - if ::std::env::var("RUST_LOG").is_ok() { - ::env_logger::try_init() - } else { - log::set_boxed_logger(Box::new(Logger::new(io::stdout(), options.log_level))) +#[derive(Clone, Default)] +struct OnDemandLogFileProxy { + created: Arc<AtomicBool>, + path: String, +} + +impl OnDemandLogFileProxy { + fn delete_log(&mut self) { + if self.created.load(Ordering::Relaxed) && fs::remove_file(&self.path).is_ok() { + let _ = writeln!(io::stdout(), "Deleted log file at {:?}", self.path); + self.created.store(false, Ordering::Relaxed); + } + } +} + +struct OnDemandLogFile { + file: Option<LineWriter<File>>, + created: Arc<AtomicBool>, + path: PathBuf, +} + +impl OnDemandLogFile { + fn new() -> Self { + let mut path = env::temp_dir(); + path.push(format!("Alacritty-{}.log", process::id())); + + OnDemandLogFile { + path, + file: None, + created: Arc::new(AtomicBool::new(false)), + } + } + + fn file(&mut self) -> Result<&mut LineWriter<File>, io::Error> { + // Allow to recreate the file if it has been deleted at runtime + if self.file.is_some() && !self.path.as_path().exists() { + self.file = None; + } + + // Create the file if it doesn't exist yet + if self.file.is_none() { + let file = OpenOptions::new() + .append(true) + .create(true) + .open(&self.path); + + match file { + Ok(file) => { + self.file = Some(io::LineWriter::new(file)); + self.created.store(true, Ordering::Relaxed); + let _ = writeln!(io::stdout(), "Created log file at {:?}", self.path); + } + Err(e) => { + let _ = writeln!(io::stdout(), "Unable to create log file: {}", e); + return Err(e); + } + } + } + + Ok(self.file.as_mut().unwrap()) + } + + fn proxy(&self) -> OnDemandLogFileProxy { + OnDemandLogFileProxy { + created: self.created.clone(), + path: self.path.to_string_lossy().to_string(), + } + } +} + +impl Write for OnDemandLogFile { + fn write(&mut self, buf: &[u8]) -> Result<usize, io::Error> { + self.file()?.write(buf) + } + + fn flush(&mut self) -> Result<(), io::Error> { + self.file()?.flush() } } diff --git a/src/macros.rs b/src/macros.rs index 464110e6..519f8b6a 100644 --- a/src/macros.rs +++ b/src/macros.rs @@ -15,33 +15,7 @@ #[macro_export] macro_rules! die { ($($arg:tt)*) => {{ - eprintln!($($arg)*); + error!($($arg)*); ::std::process::exit(1); }} } - -#[macro_export] -macro_rules! maybe { - ($option:expr) => { - match $option { - Some(value) => value, - None => return None, - } - } -} - -#[macro_export] -macro_rules! print { - ($($arg:tt)*) => {{ - use std::io::Write; - let _ = write!(::std::io::stdout(), $($arg)*); - }}; -} - -#[macro_export] -macro_rules! eprint { - ($($arg:tt)*) => {{ - use std::io::Write; - let _ = write!(::std::io::stderr(), $($arg)*); - }}; -} diff --git a/src/main.rs b/src/main.rs index 11adb396..a44c8eb3 100644 --- a/src/main.rs +++ b/src/main.rs @@ -52,7 +52,7 @@ use alacritty::event; use alacritty::event_loop::{self, EventLoop, Msg}; #[cfg(target_os = "macos")] use alacritty::locale; -use alacritty::logging; +use alacritty::logging::{self, LoggerProxy}; use alacritty::sync::FairMutex; use alacritty::term::Term; use alacritty::tty::{self, process_should_exit}; @@ -65,8 +65,13 @@ fn main() { #[cfg(windows)] unsafe { AttachConsole(ATTACH_PARENT_PROCESS); } - // Load command line options and config + // Load command line options let options = cli::Options::load(); + + // Initialize the logger as soon as possible as to capture output from other subsystems + let logger_proxy = logging::initialize(&options).expect("Unable to initialize logger"); + + // Load configuration file let config = load_config(&options).update_dynamic_title(&options); // Switch to home directory @@ -77,11 +82,9 @@ fn main() { locale::set_locale_environment(); // Run alacritty - if let Err(err) = run(config, &options) { + if let Err(err) = run(config, &options, logger_proxy) { die!("Alacritty encountered an unrecoverable error:\n\n\t{}\n", Red(err)); } - - info!("Goodbye."); } /// Load configuration @@ -98,7 +101,7 @@ fn load_config(options: &cli::Options) -> Config { }); Config::load_from(&*config_path).unwrap_or_else(|err| { - eprintln!("Error: {}; Loading default config", err); + error!("Error: {}; Loading default config", err); Config::default() }) } @@ -107,10 +110,11 @@ fn load_config(options: &cli::Options) -> Config { /// /// Creates a window, the terminal state, pty, I/O event loop, input processor, /// config change monitor, and runs the main display loop. -fn run(mut config: Config, options: &cli::Options) -> Result<(), Box<Error>> { - // Initialize the logger first as to capture output from other subsystems - logging::initialize(options)?; - +fn run( + mut config: Config, + options: &cli::Options, + mut logger_proxy: LoggerProxy, +) -> Result<(), Box<Error>> { info!("Welcome to Alacritty."); if let Some(config_path) = config.path() { info!("Configuration loaded from {}", config_path.display()); @@ -122,7 +126,7 @@ fn run(mut config: Config, options: &cli::Options) -> Result<(), Box<Error>> { // Create a display. // // The display manages a window and can draw the terminal - let mut display = Display::new(&config, options)?; + let mut display = Display::new(&config, options, logger_proxy.clone())?; info!( "PTY Dimensions: {:?} x {:?}", @@ -135,7 +139,8 @@ fn run(mut config: Config, options: &cli::Options) -> Result<(), Box<Error>> { // This object contains all of the state about what's being displayed. It's // wrapped in a clonable mutex since both the I/O loop and display need to // access it. - let terminal = Term::new(&config, display.size().to_owned()); + let mut terminal = Term::new(&config, display.size().to_owned()); + terminal.set_logger_proxy(logger_proxy.clone()); let terminal = Arc::new(FairMutex::new(terminal)); // Find the window ID for setting $WINDOWID @@ -258,5 +263,11 @@ fn run(mut config: Config, options: &cli::Options) -> Result<(), Box<Error>> { #[cfg(windows)] unsafe { FreeConsole(); } + info!("Goodbye."); + + if !options.persistent_logging && !config.persistent_logging() { + logger_proxy.delete_log(); + } + Ok(()) } diff --git a/src/renderer/mod.rs b/src/renderer/mod.rs index 1a193caf..ef5a1e76 100644 --- a/src/renderer/mod.rs +++ b/src/renderer/mod.rs @@ -817,10 +817,16 @@ impl<'a> RenderApi<'a> { self.batch.clear(); } - /// Render a string in a predefined location. Used for printing render time for profiling and - /// optimization. - pub fn render_string(&mut self, string: &str, glyph_cache: &mut GlyphCache, color: Rgb) { - let line = Line(23); + + /// Render a string in a variable location. Used for printing the render timer, warnings and + /// errors. + pub fn render_string( + &mut self, + string: &str, + line: Line, + glyph_cache: &mut GlyphCache, + color: Rgb + ) { let col = Column(0); let cells = string diff --git a/src/term/mod.rs b/src/term/mod.rs index 39213ee2..0f228f86 100644 --- a/src/term/mod.rs +++ b/src/term/mod.rs @@ -31,6 +31,7 @@ use config::{Config, VisualBellAnimation}; use {MouseCursor, Rgb}; use copypasta::{Clipboard, Load, Store}; use input::FONT_SIZE_STEP; +use logging::LoggerProxy; pub mod cell; pub mod color; @@ -810,6 +811,9 @@ pub struct Term { /// Automatically scroll to bottom when new lines are added auto_scroll: bool, + + /// Proxy object for clearing displayed errors and warnings + logger_proxy: Option<LoggerProxy>, } /// Terminal size info @@ -936,9 +940,14 @@ impl Term { dynamic_title: config.dynamic_title(), tabspaces, auto_scroll: config.scrolling().auto_scroll, + logger_proxy: None, } } + pub fn set_logger_proxy(&mut self, logger_proxy: LoggerProxy) { + self.logger_proxy = Some(logger_proxy); + } + pub fn change_font_size(&mut self, delta: f32) { // Saturating addition with minimum font size FONT_SIZE_STEP let new_size = self.font_size + Size::new(delta); @@ -1822,6 +1831,11 @@ impl ansi::Handler for Term { } }, ansi::ClearMode::All => { + // Clear displayed errors and warnings + if let Some(ref mut logger_proxy) = self.logger_proxy { + logger_proxy.clear(); + } + self.grid.region_mut(..).each(|c| c.reset(&template)); }, ansi::ClearMode::Above => { |