diff options
34 files changed, 291 insertions, 58 deletions
diff --git a/CHANGELOG.md b/CHANGELOG.md index 6b2ad25..b515dc6 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,3 +1,14 @@ +### v1.34.0 - 2024-02-24 +<a name="v1.34.0"></a> +- new `--verb-output` launch argument, dedicated to the new `:clear_output` and `:write_output` internals - Fix #825 +- verb sequences (based on `cmd`) can take arguments from the verb invocation +- don't fail launch in case of bad verb configuration, more helpful error message in such case +- faster kitty image rendering by default - Fix #789 +- `{file-git-relative}` verb argument - Thanks @VasilisManol +- modify nushell function import: `use` instead of `source` - Thanks @texastoland and @FrancescElies +- fix some resizing and flickering problems on Windows (appeared with 1.33.0) - Fix #840 +- write `installed` flag file on `--install` - Fix #837 + ### v1.33.1 - 2024-02-03 <a name="v1.33.1"></a> - fix the release's version @@ -217,7 +217,7 @@ checksum = "0d8c1fef690941d3e7788d328517591fecc684c084084702d6ff1641e993699a" [[package]] name = "broot" -version = "1.33.1" +version = "1.34.0" dependencies = [ "ahash 0.8.7", "ansi_colours", @@ -1,6 +1,6 @@ [package] name = "broot" -version = "1.33.1" +version = "1.34.0" authors = ["dystroy <denys.seguret@gmail.com>"] repository = "https://github.com/Canop/broot" homepage = "https://dystroy.org/broot" diff --git a/src/app/app.rs b/src/app/app.rs index 998afa4..b9a6cd9 100644 --- a/src/app/app.rs +++ b/src/app/app.rs @@ -858,13 +858,6 @@ impl App { handled = true; } } else if let Event::Resize(mut width, mut height) = event.event { - // I don't know why but Crossterm seems to always report an - // understimated size on Windows - #[cfg(windows)] - { - width += 1; - height += 1; - } self.screen.set_terminal_size(width, height, con); Areas::resize_all( self.panels.as_mut_slice(), diff --git a/src/app/app_context.rs b/src/app/app_context.rs index 8b56db4..35c1afa 100644 --- a/src/app/app_context.rs +++ b/src/app/app_context.rs @@ -8,6 +8,7 @@ use { errors::*, file_sum, icon::*, + kitty::TransmissionMedium, pattern::SearchModeMap, path::SpecialPaths, skin::ExtColorMap, @@ -118,6 +119,8 @@ pub struct AppContext { /// the execution of a terminal program. /// This is determined by app::run on launching the event source. pub keyboard_enhanced: bool, + + pub kitty_graphics_transmission: TransmissionMedium, } impl AppContext { @@ -216,6 +219,8 @@ impl AppContext { terminal_title_pattern, update_work_dir: config.update_work_dir.unwrap_or(true), keyboard_enhanced: false, + kitty_graphics_transmission: config.kitty_graphics_transmission + .unwrap_or_default(), }) } /// Return the --cmd argument, coming from the launch arguments (prefered) diff --git a/src/app/panel_state.rs b/src/app/panel_state.rs index dfa97d9..fd00aef 100644 --- a/src/app/panel_state.rs +++ b/src/app/panel_state.rs @@ -79,9 +79,11 @@ pub trait PanelState { /// The invocation comes from the input and may be related /// to a different verb (the verb may have been triggered /// by a key shortcut) + #[allow(clippy::too_many_arguments)] fn on_internal( &mut self, w: &mut W, + invocation_parser: Option<&InvocationParser>, internal_exec: &InternalExecution, input_invocation: Option<&VerbInvocation>, trigger_type: TriggerType, @@ -92,9 +94,11 @@ pub trait PanelState { /// a generic implementation of on_internal which may be /// called by states when they don't have a specific /// behavior to execute + #[allow(clippy::too_many_arguments)] fn on_internal_generic( &mut self, _w: &mut W, + invocation_parser: Option<&InvocationParser>, internal_exec: &InternalExecution, input_invocation: Option<&VerbInvocation>, _trigger_type: TriggerType, @@ -571,6 +575,37 @@ pub trait PanelState { Internal::print_relative_path => print::print_relative_paths(self.sel_info(app_state), con)?, Internal::refresh => CmdResult::RefreshState { clear_cache: true }, Internal::quit => CmdResult::Quit, + Internal::clear_output => { + verb_clear_output(con) + .unwrap_or_else(|e| CmdResult::DisplayError(format!("{e}"))) + } + Internal::write_output => { + let sel_info = self.sel_info(app_state); + let exec_builder = match input_invocation { + Some(inv) => { + ExecutionStringBuilder::with_invocation( + invocation_parser, + sel_info, + app_state, + inv.args.as_ref(), + ) + } + None => { + ExecutionStringBuilder::without_invocation(sel_info, app_state) + } + }; + if let Some(pattern) = internal_exec.arg.as_ref() { + let line = exec_builder.string(pattern); + verb_write(con, &line)?; + } else { + let line = input_invocation + .and_then(|inv| inv.args.as_ref()) + .map(|s| s.as_str()) + .unwrap_or(""); + verb_write(con, line)?; + } + CmdResult::Keep + } _ => CmdResult::Keep, }) } @@ -653,6 +688,7 @@ pub trait PanelState { VerbExecution::Internal(internal_exec) => { self.on_internal( w, + verb.invocation_parser.as_ref(), internal_exec, invocation, trigger_type, @@ -700,7 +736,7 @@ pub trait PanelState { } } let exec_builder = ExecutionStringBuilder::with_invocation( - &verb.invocation_parser, + verb.invocation_parser.as_ref(), sel_info, app_state, if let Some(inv) = invocation { @@ -719,7 +755,7 @@ pub trait PanelState { seq_ex: &SequenceExecution, invocation: Option<&VerbInvocation>, app_state: &mut AppState, - _cc: &CmdContext, + cc: &CmdContext, ) -> Result<CmdResult, ProgramError> { let sel_info = self.sel_info(app_state); if matches!(sel_info, SelInfo::More(_)) { @@ -729,7 +765,7 @@ pub trait PanelState { return Ok(CmdResult::error("sequences can't be executed on multiple selections")); } let exec_builder = ExecutionStringBuilder::with_invocation( - &verb.invocation_parser, + verb.invocation_parser.as_ref(), sel_info, app_state, if let Some(inv) = invocation { @@ -738,12 +774,7 @@ pub trait PanelState { None }, ); - // TODO what follows is dangerous: if an inserted group value contains the separator, - // the parsing will cut on this separator - let sequence = Sequence { - raw: exec_builder.shell_exec_string(&ExecPattern::from_string(&seq_ex.sequence.raw)), - separator: seq_ex.sequence.separator.clone(), - }; + let sequence = exec_builder.sequence(&seq_ex.sequence, &cc.app.con.verb_store); Ok(CmdResult::ExecuteSequence { sequence }) } @@ -782,6 +813,7 @@ pub trait PanelState { input_invocation, } => self.on_internal( w, + None, &InternalExecution::from_internal(*internal), input_invocation.as_ref(), TriggerType::Other, diff --git a/src/app/state_type.rs b/src/app/state_type.rs index 28eb85f..81a4961 100644 --- a/src/app/state_type.rs +++ b/src/app/state_type.rs @@ -1,10 +1,10 @@ use { - serde::Deserialize, + serde::{Deserialize, Serialize}, }; /// one of the types of state that you could /// find in a panel today -#[derive(Debug, Clone, Copy, PartialEq, Deserialize)] +#[derive(Debug, Clone, Copy, PartialEq, Deserialize, Serialize)] #[serde(rename_all = "snake_case")] pub enum PanelStateType { diff --git a/src/browser/browser_state.rs b/src/browser/browser_state.rs index 1a6e6db..e84788e 100644 --- a/src/browser/browser_state.rs +++ b/src/browser/browser_state.rs @@ -315,6 +315,7 @@ impl PanelState for BrowserState { fn on_internal( &mut self, w: &mut W, + invocation_parser: Option<&InvocationParser>, internal_exec: &InternalExecution, input_invocation: Option<&VerbInvocation>, trigger_type: TriggerType, @@ -612,6 +613,7 @@ impl PanelState for BrowserState { Internal::quit => CmdResult::Quit, _ => self.on_internal_generic( w, + invocation_parser, internal_exec, input_invocation, trigger_type, diff --git a/src/cli/args.rs b/src/cli/args.rs index 6b45cc1..0dd7e86 100644 --- a/src/cli/args.rs +++ b/src/cli/args.rs @@ -140,6 +140,10 @@ pub struct Args { #[arg(long, value_name = "path")] pub outcmd: Option<PathBuf>, + /// An optional path where to write when a verb uses `:write_output` + #[arg(long, value_name = "verb-output")] + pub verb_output: Option<PathBuf>, + /// Semicolon separated commands to execute #[arg(short, long, value_name = "cmd")] pub cmd: Option<String>, diff --git a/src/cli/mod.rs b/src/cli/mod.rs index 408ad2d..f7cfc22 100644 --- a/src/cli/mod.rs +++ b/src/cli/mod.rs @@ -158,6 +158,7 @@ pub fn run() -> Result<Option<Launchable>, ProgramError> { w.queue(EnableMouseCapture)?; } let r = app.run(&mut w, &mut context, &config); + w.flush()?; if context.capture_mouse { w.queue(DisableMouseCapture)?; } diff --git a/src/conf/conf.rs b/src/conf/conf.rs index 983058d..aaeb147 100644 --- a/src/conf/conf.rs +++ b/src/conf/conf.rs @@ -7,6 +7,7 @@ use { app::Mode, display::ColsConf, errors::{ConfError, ProgramError}, + kitty::TransmissionMedium, path::{ path_from, PathAnchor, @@ -122,6 +123,9 @@ pub struct Conf { #[serde(alias="enable-keyboard-enhancements")] pub enable_kitty_keyboard: Option<bool>, + #[serde(alias="kitty-graphics-transmission")] + pub kitty_graphics_transmission: Option<TransmissionMedium>, + // BEWARE: entries added here won't be usable unless also // added in read_file! } @@ -208,6 +212,7 @@ impl Conf { overwrite!(self, terminal_title, conf); overwrite!(self, update_work_dir, conf); overwrite!(self, enable_kitty_keyboard, conf); + overwrite!(self, kitty_graphics_transmission, conf); self.verbs.append(&mut conf.verbs); // the following maps are "additive": we can add entries from several // config files and they still make sense diff --git a/src/conf/verb_conf.rs b/src/conf/verb_conf.rs index 569b4fe..f8b7825 100644 --- a/src/conf/verb_conf.rs +++ b/src/conf/verb_conf.rs @@ -5,11 +5,11 @@ use { }, verb::*, }, - serde::Deserialize, + serde::{Deserialize, Serialize}, }; /// A deserializable verb entry in the configuration -#[derive(Default, Debug, Clone, Deserialize)] +#[derive(Default, Debug, Clone, Deserialize, Serialize)] pub struct VerbConf { pub invocation: Option<String>, @@ -26,10 +26,10 @@ pub struct VerbConf { pub key: Option<String>, - #[serde(default)] + #[serde(default, skip_serializing_if = "Vec::is_empty")] pub keys: Vec<String>, - #[serde(default)] + #[serde(default, skip_serializing_if = "Vec::is_empty")] pub extensions: Vec<String>, pub shortcut: Option<String>, @@ -38,7 +38,7 @@ pub struct VerbConf { pub from_shell: Option<bool>, - #[serde(default)] + #[serde(default, skip_serializing_if = "FileTypeCondition::is_default")] pub apply_to: FileTypeCondition, pub set_working_dir: Option<bool>, @@ -51,7 +51,7 @@ pub struct VerbConf { pub switch_terminal: Option<bool>, - #[serde(default)] + #[serde(default, skip_serializing_if = "Vec::is_empty")] pub panels: Vec<PanelStateType>, } diff --git a/src/filesystems/filesystems_state.rs b/src/filesystems/filesystems_state.rs index bbeb26d..1a11582 100644 --- a/src/filesystems/filesystems_state.rs +++ b/src/filesystems/filesystems_state.rs @@ -474,6 +474,7 @@ impl PanelState for FilesystemState { fn on_internal( &mut self, w: &mut W, + invocation_parser: Option<&InvocationParser>, internal_exec: &InternalExecution, input_invocation: Option<&VerbInvocation>, trigger_type: TriggerType, @@ -578,6 +579,7 @@ impl PanelState for FilesystemState { open_leave => CmdResult::PopStateAndReapply, _ => self.on_internal_generic( w, + invocation_parser, internal_exec, input_invocation, trigger_type, diff --git a/src/help/help_state.rs b/src/help/help_state.rs index ac50fdc..9425b57 100644 --- a/src/help/help_state.rs +++ b/src/help/help_state.rs @@ -202,6 +202,7 @@ impl PanelState for HelpState { fn on_internal( &mut self, w: &mut W, + invocation_parser: Option<&InvocationParser>, internal_exec: &InternalExecution, input_invocation: Option<&VerbInvocation>, trigger_type: TriggerType, @@ -260,6 +261,7 @@ impl PanelState for HelpState { } _ => self.on_internal_generic( w, + invocation_parser, internal_exec, input_invocation, trigger_type, diff --git a/src/image/image_view.rs b/src/image/image_view.rs index eeae252..b274a8d 100644 --- a/src/image/image_view.rs +++ b/src/image/image_view.rs @@ -119,7 +119,7 @@ impl ImageView { } self.kitty_image_id = kitty_manager - .try_print_image(w, &self.source_img, area, bg, disc.count)?; + .try_print_image(w, &self.source_img, area, bg, disc.count, disc.con)?; if self.kitty_image_id.is_some() { return Ok(()); diff --git a/src/kitty/image_renderer.rs b/src/kitty/image_renderer.rs index cecaf3b..b959c1e 100644 --- a/src/kitty/image_renderer.rs +++ b/src/kitty/image_renderer.rs @@ -24,6 +24,7 @@ use { RgbImage, RgbaImage, }, + serde::Deserialize, std::{ io::{self, Write}, }, @@ -35,12 +36,17 @@ use { /// /// Note that I didn't test yet the named shared memory /// solution offered by kitty. -#[derive(Debug)] +/// +/// Documentation: +/// https://sw.kovidgoyal.net/kitty/graphics-protocol/#the-transmission-medium +#[derive(Debug, Default, Clone, Copy, PartialEq, Eq, Deserialize)] +#[serde(rename_all = "snake_case")] pub enum TransmissionMedium { /// write a temp file, then give its path to kitty /// in the payload of the escape sequence. It's quite /// fast on SSD but a big downside is that it doesn't /// work if you're distant + #[default] TempFile, /// send the whole rgb or rgba data, encoded in base64, /// in the payloads of several escape sequence (each one @@ -49,6 +55,10 @@ pub enum TransmissionMedium { Chunks, } +pub struct KittyImageRendererOptions { + pub transmission_medium: TransmissionMedium, +} + enum ImageData<'i> { RgbRef(&'i RgbImage), RgbaRef(&'i RgbaImage), @@ -203,7 +213,7 @@ impl<'i> KittyImage<'i> { impl KittyImageRenderer { /// Called only once (at most) by the KittyManager - pub fn new() -> Option<Self> { + pub fn new(options: &KittyImageRendererOptions) -> Option<Self> { if !is_kitty_graphics_protocol_supported() { return None; } @@ -213,7 +223,7 @@ impl KittyImageRenderer { cell_width, cell_height, next_id: 1, - transmission_medium: TransmissionMedium::Chunks, + transmission_medium: options.transmission_medium, }) } /// return a new image id @@ -240,6 +250,7 @@ impl KittyImageRenderer { let img = KittyImage::new(src, area, self); debug!("transmission medium: {:?}", self.transmission_medium); + w.flush()?; match self.transmission_medium { TransmissionMedium::TempFile => img.print_with_temp_file(w)?, TransmissionMedium::Chunks => img.print_with_chunks(w)?, diff --git a/src/kitty/mod.rs b/src/kitty/mod.rs index 9fa3740..8c5360a 100644 --- a/src/kitty/mod.rs +++ b/src/kitty/mod.rs @@ -7,6 +7,7 @@ pub use { use { crate::{ + app::AppContext, display::W, errors::ProgramError, image::SourceImage, @@ -64,15 +65,21 @@ impl KittyManager { _ => None, } } - pub fn renderer(&mut self) -> Option<&mut KittyImageRenderer> { + pub fn renderer( + &mut self, + con: &AppContext, + ) -> Option<&mut KittyImageRenderer> { if matches!(self.renderer, MaybeRenderer::Disabled) { return None; } if matches!(self.renderer, MaybeRenderer::Enabled { .. }) { return self.renderer_if_tested(); } + let options = KittyImageRendererOptions { + transmission_medium: con.kitty_graphics_transmission, + }; // we're in the Untested branch - match KittyImageRenderer::new() { + match KittyImageRenderer::new(&options) { Some(renderer) => { self.renderer = MaybeRenderer::Enabled { renderer }; self.renderer_if_tested() @@ -101,8 +108,9 @@ impl KittyManager { area: &Area, bg: Color, drawing_count: usize, + con: &AppContext, ) -> Result<Option<KittyImageId>, ProgramError> { - if let Some(renderer) = self.renderer() { + if let Some(renderer) = self.renderer(con) { let img = src.optimal()?; let new_id = renderer.print(w, &img, area, bg)?; self.rendered_images.push(RenderedImage { diff --git a/src/preview/preview_state.rs b/src/preview/preview_state.rs index 4640cf7..ca27d95 100644 --- a/src/preview/preview_state.rs +++ b/src/preview/preview_state.rs @@ -304,6 +304,7 @@ impl PanelState for PreviewState { fn on_internal( &mut self, w: &mut W, + invocation_parser: Option<&InvocationParser>, internal_exec: &InternalExecution, input_invocation: Option<&VerbInvocation>, trigger_type: TriggerType, @@ -397,6 +398,7 @@ impl PanelState for PreviewState { Internal::preview_binary => self.set_mode(PreviewMode::Hex, con), _ => self.on_internal_generic( w, + invocation_parser, internal_exec, input_invocation, trigger_type, diff --git a/src/shell_install/mod.rs b/src/shell_install/mod.rs index 8b39143..c68ae58 100644 --- a/src/shell_install/mod.rs +++ b/src/shell_install/mod.rs @@ -127,11 +127,11 @@ impl ShellInstall { } } } - // even if the installation isn't really complete (for example - // when no bash file was found), we don't want to ask the user - // again, we'll assume it's done - ShellInstallState::UpToDate.write(self)?; } + // even if the installation isn't really complete (for example + // when no bash file was found), we don't want to ask the user + // again, we'll assume it's done + ShellInstallState::UpToDate.write(self)?; debug!("Starting install"); bash::install(self)?; fish::install(self)?; diff --git a/src/shell_install/nushell.rs b/src/shell_install/nushell.rs index 70c401c..9812315 100644 --- a/src/shell_install/nushell.rs +++ b/src/shell_install/nushell.rs @@ -6,7 +6,7 @@ //! In a correct installation, we have: //! - a function declaration script in ~/.local/share/broot/launcher/nushell/br/1 //! - a link to that script in ~/.config/broot/launcher/nushell/br/1 -//! - a line to source the link in ~/.config/nushell/config.nu +//! - a line to use the link in ~/.config/nushell/config.nu //! (exact paths depend on XDG variables) //! //! Please note that this function doesn't allow other commands than cd, @@ -21,7 +21,7 @@ use { }; const NAME: &str = "nushell"; -const VERSION: &str = "5"; +const VERSION: &str = "6"; const NU_FUNC: &str = r#" # Launch broot @@ -33,7 +33,7 @@ const NU_FUNC: &str = r#" # > br -hi -c "vacheblan.svg;:open_preview" .. # # See https://dystroy.org/broot/install-br/ -def --env br [ +export def --env br [ --cmd(-c): string # Semicolon separated commands to execute --color: string = "auto" # Whether to have styles and colors (auto is default and usually OK) [possible values: auto, yes, no] --conf: string # Semicolon separated paths to specific config files"), @@ -212,7 +212,7 @@ pub fn install(si: &mut ShellInstall) -> Result<(), ShellInstallError> { si.create_link(&link_path, &script_path)?; let escaped_path = link_path.to_string_lossy().replace(' ', "\\ "); - let source_line = format!("source {}", &escaped_path); + let source_line = format!("use '{}' *", &escaped_path); let sourcing_path = nushell_dir.join("config.nu"); if !sourcing_path.exists() { @@ -234,7 +234,7 @@ pub fn install(si: &mut ShellInstall) -> Result<(), ShellInstallError> { util::append_to_file(&sourcing_path, format!("\n{source_line}\n"))?; mad_print_inline!( &si.skin, - "`$0` successfully patched, you can make the function immediately available with `source $0`\n", + "`$0` successfully patched, you can make the function immediately available with `use '$0' *`\n", &sourcing_path_str, ); } diff --git a/src/shell_install/util.rs b/src/shell_install/util.rs index 34d11d1..3976f02 100644 --- a/src/shell_install/util.rs +++ b/src/shell_install/util.rs @@ -20,7 +20,6 @@ pub fn file_contains_line(path: &Path, searched_line: &str) -> Result<bool, Shel pub fn append_to_file<S: AsRef<str>>(path: &Path, content: S) -> Result<(), ShellInstallError> { let mut shellrc = OpenOptions::new() - .write(true) .append(true) .open(path) .context(&|| format!("opening {path:?} for append"))?; diff --git a/src/stage/stage_state.rs b/src/stage/stage_state.rs index 7c277a3..0c70418 100644 --- a/src/stage/stage_state.rs +++ b/src/stage/stage_state.rs @@ -443,6 +443,7 @@ impl PanelState for StageState { fn on_internal( &mut self, w: &mut W, + invocation_parser: Option<&InvocationParser>, internal_exec: &InternalExecution, input_invocation: Option<&VerbInvocation>, trigger_type: TriggerType, @@ -505,6 +506,7 @@ impl PanelState for StageState { } _ => self.on_internal_generic( w, + invocation_parser, internal_exec, input_invocation, trigger_type, diff --git a/src/verb/exec_pattern.rs b/src/verb/exec_pattern.rs index 25b0ee2..5519efb 100644 --- a/src/verb/exec_pattern.rs +++ b/src/verb/exec_pattern.rs @@ -3,7 +3,7 @@ use { path::*, verb::*, }, - serde::Deserialize, + serde::{Deserialize, Serialize}, std::{ path::Path, fmt, @@ -11,7 +11,7 @@ use { }; /// A pattern which can be expanded into an executable -#[derive(Debug, Clone, Deserialize)] +#[derive(Debug, Clone, Deserialize, Serialize)] #[serde(untagged)] pub enum ExecPattern { String(String), diff --git a/src/verb/execution_builder.rs b/src/verb/execution_builder.rs index c212793..524936d 100644 --- a/src/verb/execution_builder.rs +++ b/src/verb/execution_builder.rs @@ -2,6 +2,7 @@ use { super::*, |