mirror of
https://github.com/sxyazi/yazi.git
synced 2026-07-25 08:41:05 +00:00
feat: highlight the inputs when highlighting mode turns on (#25)
This commit is contained in:
parent
2f32ca17cb
commit
dadc408a8e
12 changed files with 129 additions and 68 deletions
|
|
@ -1,6 +1,7 @@
|
||||||
use core::input::InputMode;
|
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 shared::Term;
|
||||||
|
|
||||||
use crate::Ctx;
|
use crate::Ctx;
|
||||||
|
|
@ -18,8 +19,14 @@ impl<'a> Widget for Input<'a> {
|
||||||
let input = &self.cx.input;
|
let input = &self.cx.input;
|
||||||
let area = input.area();
|
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);
|
Clear.render(area, buf);
|
||||||
Paragraph::new(input.value())
|
Paragraph::new(value)
|
||||||
.block(
|
.block(
|
||||||
Block::new()
|
Block::new()
|
||||||
.borders(Borders::ALL)
|
.borders(Borders::ALL)
|
||||||
|
|
|
||||||
24
core/src/highlighter.rs
Normal file
24
core/src/highlighter.rs
Normal file
|
|
@ -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<SyntaxSet> = OnceLock::new();
|
||||||
|
static SYNTECT_THEME: OnceLock<Theme> = 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<Theme> {
|
||||||
|
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)
|
||||||
|
}
|
||||||
|
|
@ -6,39 +6,37 @@ use shared::CharKind;
|
||||||
use tokio::sync::oneshot::Sender;
|
use tokio::sync::oneshot::Sender;
|
||||||
use unicode_width::UnicodeWidthStr;
|
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};
|
use crate::{external, Position};
|
||||||
|
|
||||||
#[derive(Default)]
|
#[derive(Default)]
|
||||||
pub struct Input {
|
pub struct Input {
|
||||||
snaps: InputSnaps,
|
snaps: InputSnaps,
|
||||||
|
pub visible: bool,
|
||||||
|
|
||||||
title: String,
|
title: String,
|
||||||
position: (u16, u16),
|
position: (u16, u16),
|
||||||
callback: Option<Sender<Result<String>>>,
|
callback: Option<Sender<Result<String>>>,
|
||||||
|
|
||||||
pub visible: bool,
|
// Shell
|
||||||
}
|
pub(super) highlight: bool,
|
||||||
|
|
||||||
pub struct InputOpt {
|
|
||||||
pub title: String,
|
|
||||||
pub value: String,
|
|
||||||
pub position: Position,
|
|
||||||
}
|
}
|
||||||
|
|
||||||
impl Input {
|
impl Input {
|
||||||
pub fn show(&mut self, opt: InputOpt, tx: Sender<Result<String>>) {
|
pub fn show(&mut self, opt: InputOpt, tx: Sender<Result<String>>) {
|
||||||
self.close(false);
|
self.close(false);
|
||||||
self.snaps.reset(opt.value);
|
self.snaps.reset(opt.value);
|
||||||
|
self.visible = true;
|
||||||
|
|
||||||
self.title = opt.title;
|
self.title = opt.title;
|
||||||
self.position = match opt.position {
|
self.position = match opt.position {
|
||||||
Position::Coords(x, y) => (x, y),
|
Position::Coords(x, y) => (x, y),
|
||||||
_ => unimplemented!(),
|
_ => unreachable!(),
|
||||||
};
|
};
|
||||||
|
|
||||||
self.callback = Some(tx);
|
self.callback = Some(tx);
|
||||||
self.visible = true;
|
|
||||||
|
// Shell
|
||||||
|
self.highlight = opt.highlight;
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn close(&mut self, submit: bool) -> bool {
|
pub fn close(&mut self, submit: bool) -> bool {
|
||||||
|
|
|
||||||
|
|
@ -1,11 +1,15 @@
|
||||||
mod input;
|
mod input;
|
||||||
mod mode;
|
mod mode;
|
||||||
mod op;
|
mod op;
|
||||||
|
mod option;
|
||||||
|
mod shell;
|
||||||
mod snap;
|
mod snap;
|
||||||
mod snaps;
|
mod snaps;
|
||||||
|
|
||||||
pub use input::*;
|
pub use input::*;
|
||||||
pub use mode::*;
|
pub use mode::*;
|
||||||
use op::*;
|
use op::*;
|
||||||
|
pub use option::*;
|
||||||
|
pub use shell::*;
|
||||||
use snap::*;
|
use snap::*;
|
||||||
use snaps::*;
|
use snaps::*;
|
||||||
|
|
|
||||||
38
core/src/input/option.rs
Normal file
38
core/src/input/option.rs
Normal file
|
|
@ -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<str>) -> Self {
|
||||||
|
Self {
|
||||||
|
title: title.as_ref().to_owned(),
|
||||||
|
value: String::new(),
|
||||||
|
position: Position::Top,
|
||||||
|
highlight: false,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn hovered(title: impl AsRef<str>) -> 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<str>) -> Self {
|
||||||
|
self.value = value.as_ref().to_owned();
|
||||||
|
self
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn with_highlight(mut self) -> Self {
|
||||||
|
self.highlight = true;
|
||||||
|
self
|
||||||
|
}
|
||||||
|
}
|
||||||
22
core/src/input/shell.rs
Normal file
22
core/src/input/shell.rs
Normal file
|
|
@ -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<String> {
|
||||||
|
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")
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
@ -2,6 +2,7 @@ mod blocker;
|
||||||
mod event;
|
mod event;
|
||||||
pub mod external;
|
pub mod external;
|
||||||
pub mod files;
|
pub mod files;
|
||||||
|
mod highlighter;
|
||||||
pub mod input;
|
pub mod input;
|
||||||
pub mod manager;
|
pub mod manager;
|
||||||
pub mod position;
|
pub mod position;
|
||||||
|
|
@ -11,4 +12,5 @@ pub mod which;
|
||||||
|
|
||||||
pub use blocker::*;
|
pub use blocker::*;
|
||||||
pub use event::*;
|
pub use event::*;
|
||||||
|
pub use highlighter::*;
|
||||||
pub use position::*;
|
pub use position::*;
|
||||||
|
|
|
||||||
|
|
@ -98,11 +98,8 @@ impl Manager {
|
||||||
}
|
}
|
||||||
|
|
||||||
tokio::spawn(async move {
|
tokio::spawn(async move {
|
||||||
let result = emit!(Input(InputOpt {
|
let result =
|
||||||
title: format!("There are {tasks} tasks running, sure to quit? (y/N)"),
|
emit!(Input(InputOpt::top("There are {tasks} tasks running, sure to quit? (y/N)")));
|
||||||
value: "".to_string(),
|
|
||||||
position: Position::Top,
|
|
||||||
}));
|
|
||||||
|
|
||||||
if let Ok(choice) = result.await {
|
if let Ok(choice) = result.await {
|
||||||
if choice.to_lowercase() == "y" {
|
if choice.to_lowercase() == "y" {
|
||||||
|
|
@ -178,11 +175,7 @@ impl Manager {
|
||||||
pub fn create(&self) -> bool {
|
pub fn create(&self) -> bool {
|
||||||
let cwd = self.current().cwd.clone();
|
let cwd = self.current().cwd.clone();
|
||||||
tokio::spawn(async move {
|
tokio::spawn(async move {
|
||||||
let result = emit!(Input(InputOpt {
|
let result = emit!(Input(InputOpt::top("Create:")));
|
||||||
title: "Create:".to_string(),
|
|
||||||
value: "".to_string(),
|
|
||||||
position: Position::Top,
|
|
||||||
}));
|
|
||||||
|
|
||||||
if let Ok(name) = result.await {
|
if let Ok(name) = result.await {
|
||||||
let path = cwd.join(&name);
|
let path = cwd.join(&name);
|
||||||
|
|
@ -217,11 +210,9 @@ impl Manager {
|
||||||
};
|
};
|
||||||
|
|
||||||
tokio::spawn(async move {
|
tokio::spawn(async move {
|
||||||
let result = emit!(Input(InputOpt {
|
let result = emit!(Input(
|
||||||
title: "Rename:".to_string(),
|
InputOpt::hovered("Rename:").with_value(hovered.file_name().unwrap().to_string_lossy())
|
||||||
value: hovered.file_name().unwrap().to_string_lossy().to_string(),
|
));
|
||||||
position: Position::Hovered,
|
|
||||||
}));
|
|
||||||
|
|
||||||
if let Ok(new) = result.await {
|
if let Ok(new) = result.await {
|
||||||
let to = hovered.parent().unwrap().join(new);
|
let to = hovered.parent().unwrap().join(new);
|
||||||
|
|
@ -235,11 +226,7 @@ impl Manager {
|
||||||
|
|
||||||
pub fn shell(&self, block: bool) -> bool {
|
pub fn shell(&self, block: bool) -> bool {
|
||||||
tokio::spawn(async move {
|
tokio::spawn(async move {
|
||||||
let result = emit!(Input(InputOpt {
|
let result = emit!(Input(InputOpt::top("Shell:").with_highlight()));
|
||||||
title: "Shell:".to_string(),
|
|
||||||
value: "".to_string(),
|
|
||||||
position: Position::Top,
|
|
||||||
}));
|
|
||||||
|
|
||||||
if let Ok(cmd) = result.await {
|
if let Ok(cmd) = result.await {
|
||||||
emit!(Open(
|
emit!(Open(
|
||||||
|
|
|
||||||
|
|
@ -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 adaptor::{Adaptor, Image};
|
||||||
use anyhow::{anyhow, bail, Result};
|
use anyhow::{anyhow, bail, Result};
|
||||||
use config::{PREVIEW, THEME};
|
use config::PREVIEW;
|
||||||
use ratatui::prelude::Rect;
|
use ratatui::prelude::Rect;
|
||||||
use shared::{tty_size, MimeKind};
|
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 tokio::{fs, task::JoinHandle};
|
||||||
|
|
||||||
use super::{ALL_RATIO, CURRENT_RATIO, PARENT_RATIO, PREVIEW_BORDER, PREVIEW_MARGIN, PREVIEW_RATIO};
|
use super::{ALL_RATIO, CURRENT_RATIO, PARENT_RATIO, PREVIEW_BORDER, PREVIEW_MARGIN, PREVIEW_RATIO};
|
||||||
use crate::{emit, external, files::{Files, FilesOp}};
|
use crate::{emit, external, files::{Files, FilesOp}, highlighter};
|
||||||
|
|
||||||
static SYNTECT_SYNTAX: OnceLock<SyntaxSet> = OnceLock::new();
|
|
||||||
static SYNTECT_THEME: OnceLock<Theme> = OnceLock::new();
|
|
||||||
|
|
||||||
#[derive(Default)]
|
#[derive(Default)]
|
||||||
pub struct Preview {
|
pub struct Preview {
|
||||||
|
|
@ -157,21 +154,12 @@ impl Preview {
|
||||||
|
|
||||||
pub async fn highlight(path: &Path, incr: Arc<AtomicUsize>) -> Result<String> {
|
pub async fn highlight(path: &Path, incr: Arc<AtomicUsize>) -> Result<String> {
|
||||||
let tick = incr.load(Ordering::Relaxed);
|
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<Theme> {
|
|
||||||
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 path = path.to_path_buf();
|
||||||
let spaces = " ".repeat(PREVIEW.tab_size as usize);
|
let spaces = " ".repeat(PREVIEW.tab_size as usize);
|
||||||
|
|
||||||
|
let (syntaxes, theme) = highlighter();
|
||||||
tokio::task::spawn_blocking(move || -> Result<String> {
|
tokio::task::spawn_blocking(move || -> Result<String> {
|
||||||
let mut h = HighlightFile::new(path, syntax, theme)?;
|
let mut h = HighlightFile::new(path, syntaxes, theme)?;
|
||||||
let mut line = String::new();
|
let mut line = String::new();
|
||||||
let mut buf = String::new();
|
let mut buf = String::new();
|
||||||
|
|
||||||
|
|
@ -183,8 +171,8 @@ impl Preview {
|
||||||
|
|
||||||
i -= 1;
|
i -= 1;
|
||||||
line = line.replace('\t', &spaces);
|
line = line.replace('\t', &spaces);
|
||||||
let regions = h.highlight_lines.highlight_line(&line, syntax)?;
|
let regions = h.highlight_lines.highlight_line(&line, syntaxes)?;
|
||||||
buf.push_str(&as_24_bit_terminal_escaped(®ions[..], false));
|
buf.push_str(&as_24_bit_terminal_escaped(®ions, false));
|
||||||
line.clear();
|
line.clear();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -5,7 +5,7 @@ use shared::Defer;
|
||||||
use tokio::task::JoinHandle;
|
use tokio::task::JoinHandle;
|
||||||
|
|
||||||
use super::{Folder, Mode, Preview};
|
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 struct Tab {
|
||||||
pub(super) mode: Mode,
|
pub(super) mode: Mode,
|
||||||
|
|
@ -186,12 +186,7 @@ impl Tab {
|
||||||
let hidden = self.current.files.show_hidden;
|
let hidden = self.current.files.show_hidden;
|
||||||
|
|
||||||
self.search = Some(tokio::spawn(async move {
|
self.search = Some(tokio::spawn(async move {
|
||||||
let subject = emit!(Input(InputOpt {
|
let subject = emit!(Input(InputOpt::top("Search:"))).await?;
|
||||||
title: "Search:".to_string(),
|
|
||||||
value: "".to_string(),
|
|
||||||
position: Position::Top,
|
|
||||||
}))
|
|
||||||
.await?;
|
|
||||||
|
|
||||||
let mut rx = if grep {
|
let mut rx = if grep {
|
||||||
external::rg(external::RgOpt { cwd: cwd.clone(), hidden, subject })
|
external::rg(external::RgOpt { cwd: cwd.clone(), hidden, subject })
|
||||||
|
|
|
||||||
|
|
@ -33,7 +33,7 @@ impl Select {
|
||||||
self.items = opt.items;
|
self.items = opt.items;
|
||||||
self.position = match opt.position {
|
self.position = match opt.position {
|
||||||
Position::Coords(x, y) => (x, y),
|
Position::Coords(x, y) => (x, y),
|
||||||
_ => unimplemented!(),
|
_ => unreachable!(),
|
||||||
};
|
};
|
||||||
self.callback = Some(tx);
|
self.callback = Some(tx);
|
||||||
self.visible = true;
|
self.visible = true;
|
||||||
|
|
|
||||||
|
|
@ -7,7 +7,7 @@ use tokio::{io::AsyncReadExt, select, sync::mpsc, time};
|
||||||
use tracing::trace;
|
use tracing::trace;
|
||||||
|
|
||||||
use super::{task::TaskSummary, Scheduler, TASKS_PADDING, TASKS_PERCENT};
|
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 {
|
pub struct Tasks {
|
||||||
scheduler: Arc<Scheduler>,
|
scheduler: Arc<Scheduler>,
|
||||||
|
|
@ -185,11 +185,7 @@ impl Tasks {
|
||||||
pub fn file_remove(&self, targets: Vec<PathBuf>, permanently: bool) -> bool {
|
pub fn file_remove(&self, targets: Vec<PathBuf>, permanently: bool) -> bool {
|
||||||
let scheduler = self.scheduler.clone();
|
let scheduler = self.scheduler.clone();
|
||||||
tokio::spawn(async move {
|
tokio::spawn(async move {
|
||||||
let result = emit!(Input(InputOpt {
|
let result = emit!(Input(InputOpt::hovered("Are you sure delete these files? (y/N)")));
|
||||||
title: "Are you sure delete these files? (y/N)".to_string(),
|
|
||||||
value: "".to_string(),
|
|
||||||
position: Position::Hovered,
|
|
||||||
}));
|
|
||||||
|
|
||||||
if let Ok(choice) = result.await {
|
if let Ok(choice) = result.await {
|
||||||
if choice.to_lowercase() != "y" {
|
if choice.to_lowercase() != "y" {
|
||||||
|
|
|
||||||
Loading…
Add table
Reference in a new issue