From dadc408a8e9d787f83ca1881227fecd51f2f1ecc Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E4=B8=89=E5=92=B2=E9=9B=85=20=C2=B7=20Misaki=20Masa?= Date: Sun, 6 Aug 2023 17:32:34 +0800 Subject: [PATCH] feat: highlight the inputs when highlighting mode turns on (#25) --- app/src/input/input.rs | 11 +++++++++-- core/src/highlighter.rs | 24 +++++++++++++++++++++++ core/src/input/input.rs | 22 ++++++++++----------- core/src/input/mod.rs | 4 ++++ core/src/input/option.rs | 38 +++++++++++++++++++++++++++++++++++++ core/src/input/shell.rs | 22 +++++++++++++++++++++ core/src/lib.rs | 2 ++ core/src/manager/manager.rs | 27 +++++++------------------- core/src/manager/preview.rs | 28 ++++++++------------------- core/src/manager/tab.rs | 9 ++------- core/src/select/select.rs | 2 +- core/src/tasks/tasks.rs | 8 ++------ 12 files changed, 129 insertions(+), 68 deletions(-) create mode 100644 core/src/highlighter.rs create mode 100644 core/src/input/option.rs create mode 100644 core/src/input/shell.rs diff --git a/app/src/input/input.rs b/app/src/input/input.rs index d7856746..e5508469 100644 --- a/app/src/input/input.rs +++ b/app/src/input/input.rs @@ -1,6 +1,7 @@ use core::input::InputMode; -use ratatui::{buffer::Buffer, layout::Rect, style::{Color, Style}, text::Line, widgets::{Block, BorderType, Borders, Clear, Paragraph, Widget}}; +use ansi_to_tui::IntoText; +use ratatui::{buffer::Buffer, layout::Rect, style::{Color, Style}, text::{Line, Text}, widgets::{Block, BorderType, Borders, Clear, Paragraph, Widget}}; use shared::Term; use crate::Ctx; @@ -18,8 +19,14 @@ impl<'a> Widget for Input<'a> { let input = &self.cx.input; let area = input.area(); + let value = if let Ok(v) = input.value_pretty() { + v.into_text().unwrap() + } else { + Text::from(input.value()) + }; + Clear.render(area, buf); - Paragraph::new(input.value()) + Paragraph::new(value) .block( Block::new() .borders(Borders::ALL) diff --git a/core/src/highlighter.rs b/core/src/highlighter.rs new file mode 100644 index 00000000..5ba036fc --- /dev/null +++ b/core/src/highlighter.rs @@ -0,0 +1,24 @@ +use std::{fs::File, io::BufReader, sync::OnceLock}; + +use anyhow::Result; +use config::THEME; +use syntect::{dumps::from_uncompressed_data, highlighting::{Theme, ThemeSet}, parsing::SyntaxSet}; + +static SYNTECT_SYNTAX: OnceLock = OnceLock::new(); +static SYNTECT_THEME: OnceLock = OnceLock::new(); + +#[inline] +pub fn highlighter() -> (&'static SyntaxSet, &'static Theme) { + let syntaxes = + SYNTECT_SYNTAX.get_or_init(|| from_uncompressed_data(yazi_prebuild::syntaxes()).unwrap()); + + let theme = SYNTECT_THEME.get_or_init(|| { + let from_file = || -> Result { + let file = File::open(&THEME.preview.syntect_theme)?; + Ok(ThemeSet::load_from_reader(&mut BufReader::new(file))?) + }; + from_file().unwrap_or_else(|_| ThemeSet::load_defaults().themes["base16-ocean.dark"].clone()) + }); + + (syntaxes, theme) +} diff --git a/core/src/input/input.rs b/core/src/input/input.rs index 1a132bab..9f25fc44 100644 --- a/core/src/input/input.rs +++ b/core/src/input/input.rs @@ -6,39 +6,37 @@ use shared::CharKind; use tokio::sync::oneshot::Sender; use unicode_width::UnicodeWidthStr; -use super::{mode::InputMode, op::InputOp, InputSnap, InputSnaps}; +use super::{mode::InputMode, op::InputOp, InputOpt, InputSnap, InputSnaps}; use crate::{external, Position}; #[derive(Default)] pub struct Input { - snaps: InputSnaps, + snaps: InputSnaps, + pub visible: bool, title: String, position: (u16, u16), callback: Option>>, - pub visible: bool, -} - -pub struct InputOpt { - pub title: String, - pub value: String, - pub position: Position, + // Shell + pub(super) highlight: bool, } impl Input { pub fn show(&mut self, opt: InputOpt, tx: Sender>) { self.close(false); self.snaps.reset(opt.value); + self.visible = true; self.title = opt.title; self.position = match opt.position { Position::Coords(x, y) => (x, y), - _ => unimplemented!(), + _ => unreachable!(), }; - self.callback = Some(tx); - self.visible = true; + + // Shell + self.highlight = opt.highlight; } pub fn close(&mut self, submit: bool) -> bool { diff --git a/core/src/input/mod.rs b/core/src/input/mod.rs index 412dd600..9fa10f1a 100644 --- a/core/src/input/mod.rs +++ b/core/src/input/mod.rs @@ -1,11 +1,15 @@ mod input; mod mode; mod op; +mod option; +mod shell; mod snap; mod snaps; pub use input::*; pub use mode::*; use op::*; +pub use option::*; +pub use shell::*; use snap::*; use snaps::*; diff --git a/core/src/input/option.rs b/core/src/input/option.rs new file mode 100644 index 00000000..fef77620 --- /dev/null +++ b/core/src/input/option.rs @@ -0,0 +1,38 @@ +use crate::Position; + +pub struct InputOpt { + pub title: String, + pub value: String, + pub position: Position, + pub highlight: bool, +} + +impl InputOpt { + pub fn top(title: impl AsRef) -> Self { + Self { + title: title.as_ref().to_owned(), + value: String::new(), + position: Position::Top, + highlight: false, + } + } + + pub fn hovered(title: impl AsRef) -> Self { + Self { + title: title.as_ref().to_owned(), + value: String::new(), + position: Position::Hovered, + highlight: false, + } + } + + pub fn with_value(mut self, value: impl AsRef) -> Self { + self.value = value.as_ref().to_owned(); + self + } + + pub fn with_highlight(mut self) -> Self { + self.highlight = true; + self + } +} diff --git a/core/src/input/shell.rs b/core/src/input/shell.rs new file mode 100644 index 00000000..ef977fab --- /dev/null +++ b/core/src/input/shell.rs @@ -0,0 +1,22 @@ +use anyhow::{bail, Result}; +use syntect::{easy::HighlightLines, util::as_24_bit_terminal_escaped}; + +use super::Input; +use crate::highlighter; + +impl Input { + pub fn value_pretty(&self) -> Result { + if !self.highlight { + bail!("Highlighting is disabled") + } + + let (syntaxes, theme) = highlighter(); + if let Some(syntax) = syntaxes.find_syntax_by_name("Bourne Again Shell (bash)") { + let mut h = HighlightLines::new(syntax, theme); + let regions = h.highlight_line(self.value(), syntaxes)?; + return Ok(as_24_bit_terminal_escaped(®ions, false)); + } + + bail!("Failed to find syntax") + } +} diff --git a/core/src/lib.rs b/core/src/lib.rs index a2a124e9..c34ce75d 100644 --- a/core/src/lib.rs +++ b/core/src/lib.rs @@ -2,6 +2,7 @@ mod blocker; mod event; pub mod external; pub mod files; +mod highlighter; pub mod input; pub mod manager; pub mod position; @@ -11,4 +12,5 @@ pub mod which; pub use blocker::*; pub use event::*; +pub use highlighter::*; pub use position::*; diff --git a/core/src/manager/manager.rs b/core/src/manager/manager.rs index ad191672..e78d81de 100644 --- a/core/src/manager/manager.rs +++ b/core/src/manager/manager.rs @@ -98,11 +98,8 @@ impl Manager { } tokio::spawn(async move { - let result = emit!(Input(InputOpt { - title: format!("There are {tasks} tasks running, sure to quit? (y/N)"), - value: "".to_string(), - position: Position::Top, - })); + let result = + emit!(Input(InputOpt::top("There are {tasks} tasks running, sure to quit? (y/N)"))); if let Ok(choice) = result.await { if choice.to_lowercase() == "y" { @@ -178,11 +175,7 @@ impl Manager { pub fn create(&self) -> bool { let cwd = self.current().cwd.clone(); tokio::spawn(async move { - let result = emit!(Input(InputOpt { - title: "Create:".to_string(), - value: "".to_string(), - position: Position::Top, - })); + let result = emit!(Input(InputOpt::top("Create:"))); if let Ok(name) = result.await { let path = cwd.join(&name); @@ -217,11 +210,9 @@ impl Manager { }; tokio::spawn(async move { - let result = emit!(Input(InputOpt { - title: "Rename:".to_string(), - value: hovered.file_name().unwrap().to_string_lossy().to_string(), - position: Position::Hovered, - })); + let result = emit!(Input( + InputOpt::hovered("Rename:").with_value(hovered.file_name().unwrap().to_string_lossy()) + )); if let Ok(new) = result.await { let to = hovered.parent().unwrap().join(new); @@ -235,11 +226,7 @@ impl Manager { pub fn shell(&self, block: bool) -> bool { tokio::spawn(async move { - let result = emit!(Input(InputOpt { - title: "Shell:".to_string(), - value: "".to_string(), - position: Position::Top, - })); + let result = emit!(Input(InputOpt::top("Shell:").with_highlight())); if let Ok(cmd) = result.await { emit!(Open( diff --git a/core/src/manager/preview.rs b/core/src/manager/preview.rs index 1606470f..65adce72 100644 --- a/core/src/manager/preview.rs +++ b/core/src/manager/preview.rs @@ -1,18 +1,15 @@ -use std::{fs::File, io::{BufRead, BufReader}, mem, path::{Path, PathBuf}, sync::{atomic::{AtomicUsize, Ordering}, Arc, OnceLock}}; +use std::{io::BufRead, mem, path::{Path, PathBuf}, sync::{atomic::{AtomicUsize, Ordering}, Arc}}; use adaptor::{Adaptor, Image}; use anyhow::{anyhow, bail, Result}; -use config::{PREVIEW, THEME}; +use config::PREVIEW; use ratatui::prelude::Rect; use shared::{tty_size, MimeKind}; -use syntect::{dumps::from_uncompressed_data, easy::HighlightFile, highlighting::{Theme, ThemeSet}, parsing::SyntaxSet, util::as_24_bit_terminal_escaped}; +use syntect::{easy::HighlightFile, util::as_24_bit_terminal_escaped}; use tokio::{fs, task::JoinHandle}; use super::{ALL_RATIO, CURRENT_RATIO, PARENT_RATIO, PREVIEW_BORDER, PREVIEW_MARGIN, PREVIEW_RATIO}; -use crate::{emit, external, files::{Files, FilesOp}}; - -static SYNTECT_SYNTAX: OnceLock = OnceLock::new(); -static SYNTECT_THEME: OnceLock = OnceLock::new(); +use crate::{emit, external, files::{Files, FilesOp}, highlighter}; #[derive(Default)] pub struct Preview { @@ -157,21 +154,12 @@ impl Preview { pub async fn highlight(path: &Path, incr: Arc) -> Result { let tick = incr.load(Ordering::Relaxed); - let syntax = - SYNTECT_SYNTAX.get_or_init(|| from_uncompressed_data(yazi_prebuild::syntaxes()).unwrap()); - let theme = SYNTECT_THEME.get_or_init(|| { - let from_file = || -> Result { - let file = File::open(&THEME.preview.syntect_theme)?; - Ok(ThemeSet::load_from_reader(&mut BufReader::new(file))?) - }; - from_file().unwrap_or_else(|_| ThemeSet::load_defaults().themes["base16-ocean.dark"].clone()) - }); - let path = path.to_path_buf(); let spaces = " ".repeat(PREVIEW.tab_size as usize); + let (syntaxes, theme) = highlighter(); tokio::task::spawn_blocking(move || -> Result { - let mut h = HighlightFile::new(path, syntax, theme)?; + let mut h = HighlightFile::new(path, syntaxes, theme)?; let mut line = String::new(); let mut buf = String::new(); @@ -183,8 +171,8 @@ impl Preview { i -= 1; line = line.replace('\t', &spaces); - let regions = h.highlight_lines.highlight_line(&line, syntax)?; - buf.push_str(&as_24_bit_terminal_escaped(®ions[..], false)); + let regions = h.highlight_lines.highlight_line(&line, syntaxes)?; + buf.push_str(&as_24_bit_terminal_escaped(®ions, false)); line.clear(); } diff --git a/core/src/manager/tab.rs b/core/src/manager/tab.rs index 59f14261..c858cfd4 100644 --- a/core/src/manager/tab.rs +++ b/core/src/manager/tab.rs @@ -5,7 +5,7 @@ use shared::Defer; use tokio::task::JoinHandle; use super::{Folder, Mode, Preview}; -use crate::{emit, external::{self, FzfOpt, ZoxideOpt}, files::{File, Files, FilesOp}, input::InputOpt, Event, Position, BLOCKER}; +use crate::{emit, external::{self, FzfOpt, ZoxideOpt}, files::{File, Files, FilesOp}, input::InputOpt, Event, BLOCKER}; pub struct Tab { pub(super) mode: Mode, @@ -186,12 +186,7 @@ impl Tab { let hidden = self.current.files.show_hidden; self.search = Some(tokio::spawn(async move { - let subject = emit!(Input(InputOpt { - title: "Search:".to_string(), - value: "".to_string(), - position: Position::Top, - })) - .await?; + let subject = emit!(Input(InputOpt::top("Search:"))).await?; let mut rx = if grep { external::rg(external::RgOpt { cwd: cwd.clone(), hidden, subject }) diff --git a/core/src/select/select.rs b/core/src/select/select.rs index 6142344b..2205c6a0 100644 --- a/core/src/select/select.rs +++ b/core/src/select/select.rs @@ -33,7 +33,7 @@ impl Select { self.items = opt.items; self.position = match opt.position { Position::Coords(x, y) => (x, y), - _ => unimplemented!(), + _ => unreachable!(), }; self.callback = Some(tx); self.visible = true; diff --git a/core/src/tasks/tasks.rs b/core/src/tasks/tasks.rs index 36a78404..c3e31ae3 100644 --- a/core/src/tasks/tasks.rs +++ b/core/src/tasks/tasks.rs @@ -7,7 +7,7 @@ use tokio::{io::AsyncReadExt, select, sync::mpsc, time}; use tracing::trace; use super::{task::TaskSummary, Scheduler, TASKS_PADDING, TASKS_PERCENT}; -use crate::{emit, files::{File, Files}, input::InputOpt, Position, BLOCKER}; +use crate::{emit, files::{File, Files}, input::InputOpt, BLOCKER}; pub struct Tasks { scheduler: Arc, @@ -185,11 +185,7 @@ impl Tasks { pub fn file_remove(&self, targets: Vec, permanently: bool) -> bool { let scheduler = self.scheduler.clone(); tokio::spawn(async move { - let result = emit!(Input(InputOpt { - title: "Are you sure delete these files? (y/N)".to_string(), - value: "".to_string(), - position: Position::Hovered, - })); + let result = emit!(Input(InputOpt::hovered("Are you sure delete these files? (y/N)"))); if let Ok(choice) = result.await { if choice.to_lowercase() != "y" {