From a59f48736f007eda086d0c2ccb64759097bb1507 Mon Sep 17 00:00:00 2001 From: alice pellerin Date: Fri, 10 Apr 2026 21:51:19 -0500 Subject: [PATCH] improve ui for opening files, read stdin if no files (unless on macos :/) --- src/app/mod.rs | 67 ++++++++++++++++++++++++++++++++++++++++------- src/buffer/mod.rs | 12 ++++++--- 2 files changed, 65 insertions(+), 14 deletions(-) diff --git a/src/app/mod.rs b/src/app/mod.rs index c5023c9..32b52a6 100644 --- a/src/app/mod.rs +++ b/src/app/mod.rs @@ -1,10 +1,12 @@ -use std::{env::{self}, io::{self}, process::exit, time::Duration}; +use std::{env, io, path::PathBuf, process::exit, time::Duration}; use crossterm::{ExecutableCommand, event::{self, DisableMouseCapture, Event, KeyCode, KeyEvent, KeyModifiers, MouseEvent, MouseEventKind}, terminal::window_size}; use ratatui::{DefaultTerminal, style::Stylize, text::Span}; use crate::{BYTES_PER_LINE, action::AppAction, buffer::Buffer, config::{Config, ConfigInitError}, cursor::Cursor}; mod widget; +const MACOS_STDIN_BROKEN_MESSAGE: &str = "reading from stdin on macOS does not work due to a limitation in crossterm. see https://github.com/crossterm-rs/crossterm/issues/396"; + pub struct App { pub config: Config, @@ -52,15 +54,44 @@ impl App { config.unwrap_or_default() }; - let buffers: Vec = env::args() - .skip(1) - .map(Into::into) - .map(Buffer::new) - .collect(); + let mut error_alert: Option = None; - if buffers.is_empty() { - println!("please provide at least one file as input"); - exit(1); + let mut buffers: Vec = env::args() + .skip(1) + .map(Into::into) + .filter_map(|path: PathBuf| { + Buffer::from_file_at(path.clone()) + .inspect_err(|error| { + error_alert = Some( + Span::raw(format!("error reading '{}': {error}", path.display())).red() + ); + }) + .ok() + }) + .collect(); + + if env::args().len() <= 1 { + #[cfg(target_os = "macos")] { + eprintln!("{MACOS_STDIN_BROKEN_MESSAGE}"); + exit(1); + } + + #[cfg(not(target_os = "macos"))] { + use io::{Read, stdin}; + + let mut standard_input = Vec::new(); + stdin().read_to_end(&mut standard_input).unwrap(); + buffers.push(Buffer::new("-".into(), standard_input)); + } + } + + if let Some(error_alert) = error_alert { + if buffers.is_empty() { + eprintln!("{error_alert}"); + exit(1); + } else { + buffers[0].alert_message = error_alert; + } } let window_size = WindowSize { @@ -98,7 +129,23 @@ impl App { } pub fn handle_event(&mut self, terminal: &mut DefaultTerminal) { - match event::read().unwrap() { + let event = event::read() + .inspect_err(|error| { + #[cfg(target_os = "macos")] { + use io::ErrorKind; + + if error.kind() == ErrorKind::Other { + let error_message = error.to_string(); + if error_message == "Failed to initialize input reader" { + eprintln!("{MACOS_STDIN_BROKEN_MESSAGE}"); + exit(1); + } + } + } + }) + .unwrap(); + + match event { Event::Resize(_, height) => { self.window_size.rows = height as usize; diff --git a/src/buffer/mod.rs b/src/buffer/mod.rs index 2cf9f55..8484e1f 100644 --- a/src/buffer/mod.rs +++ b/src/buffer/mod.rs @@ -1,5 +1,5 @@ use core::slice::GetDisjointMutIndex; -use std::{collections::HashSet, fs::File, io::Read, path::PathBuf}; +use std::{collections::HashSet, fs::File, io::{self, Read}, path::PathBuf}; use crossterm::event::KeyEvent; use ratatui::{layout::{Constraint, Rect}, style::{Color, Style, Stylize}, text::Span, widgets::{Block, Borders, Clear, Widget}}; use serde::{Deserialize, Serialize}; @@ -171,11 +171,15 @@ impl Widget for Popup { } impl Buffer { - pub fn new(file_path: PathBuf) -> Self { - let file = File::open(&file_path); + pub fn from_file_at(file_path: PathBuf) -> io::Result { + let mut file = File::open(&file_path)?; let mut contents = Vec::new(); - file.unwrap().read_to_end(&mut contents).unwrap(); + file.read_to_end(&mut contents)?; + Ok(Self::new(file_path, contents)) + } + + pub fn new(file_path: PathBuf, contents: Vec) -> Self { Self { file_name: file_path.file_name().unwrap().to_str().unwrap().to_owned(), file_path,