Skip to content

Commit d17fa04

Browse files
committed
feat: make rg search more convenient
1 parent 41e5717 commit d17fa04

File tree

9 files changed

+137
-16
lines changed

9 files changed

+137
-16
lines changed

yazi-actor/src/mgr/peek.rs

Lines changed: 3 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -2,7 +2,7 @@ use anyhow::Result;
22
use yazi_macro::succ;
33
use yazi_parser::mgr::PeekOpt;
44
use yazi_proxy::HIDER;
5-
use yazi_shared::data::Data;
5+
use yazi_shared::{data::Data, url::AsUrl};
66

77
use crate::{Actor, Ctx};
88

@@ -25,7 +25,8 @@ impl Actor for Peek {
2525
let folder = cx.tab().hovered_folder().map(|f| (f.offset, f.cha));
2626

2727
if !cx.tab().preview.same_url(&hovered.url) {
28-
cx.tab_mut().preview.skip = folder.map(|f| f.0).unwrap_or_default();
28+
// set matched line on the top of preview window
29+
cx.tab_mut().preview.skip = folder.map(|f| f.0).unwrap_or_else(|| hovered.url.as_url().line().map_or(0, |l| l.saturating_sub(1)));
2930
}
3031
if !cx.tab().preview.same_file(&hovered, &mime) {
3132
cx.tab_mut().preview.reset();

yazi-actor/src/mgr/reveal.rs

Lines changed: 34 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -2,7 +2,7 @@ use anyhow::Result;
22
use yazi_fs::{File, FilesOp};
33
use yazi_macro::{act, render, succ};
44
use yazi_parser::mgr::RevealOpt;
5-
use yazi_shared::{data::Data, url::UrlLike};
5+
use yazi_shared::{data::Data, url::{AsUrl, UrlLike}};
66

77
use crate::{Actor, Ctx};
88

@@ -14,6 +14,15 @@ impl Actor for Reveal {
1414
const NAME: &str = "reveal";
1515

1616
fn act(cx: &mut Ctx, opt: Self::Options) -> Result<Data> {
17+
match cx.cwd().is_search() {
18+
true => Self::reveal_search(cx, opt),
19+
false => Self::reveal_regular(cx, opt),
20+
}
21+
}
22+
}
23+
24+
impl Reveal {
25+
fn reveal_regular(cx: &mut Ctx, opt: RevealOpt) -> Result<Data> {
1726
let Some((parent, child)) = opt.target.pair() else { succ!() };
1827

1928
// Cd to the parent directory
@@ -37,4 +46,28 @@ impl Actor for Reveal {
3746
act!(mgr:watch, cx)?;
3847
succ!();
3948
}
49+
50+
fn reveal_search(cx: &mut Ctx, opt: RevealOpt) -> Result<Data> {
51+
let cwd = cx.cwd().clone();
52+
let tab = cx.tab_mut();
53+
let pos = tab.current.files.iter().position(|f| f.url == opt.target);
54+
55+
if let Some(pos) = pos {
56+
let delta = pos as isize - tab.current.cursor as isize;
57+
tab.current.arrow(delta);
58+
} else if !opt.no_dummy {
59+
let op = FilesOp::Creating(cwd, vec![File::from_dummy(&opt.target, None)]);
60+
tab.current.update_pub(tab.id, op);
61+
62+
if let Some(pos) = tab.current.files.iter().position(|f| f.url == opt.target) {
63+
let delta = pos as isize - tab.current.cursor as isize;
64+
tab.current.arrow(delta);
65+
}
66+
}
67+
68+
act!(mgr:hover, cx, None)?;
69+
act!(mgr:peek, cx)?;
70+
act!(mgr:watch, cx)?;
71+
succ!();
72+
}
4073
}

yazi-binding/src/macros.rs

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -238,5 +238,10 @@ macro_rules! impl_file_methods {
238238
// TODO: use a cache
239239
Ok(yazi_config::THEME.icon.matches(me).map(Icon::from))
240240
});
241+
242+
$methods.add_method("line", |_, me, ()| {
243+
use yazi_shared::url::AsUrl;
244+
Ok(me.url.as_url().line().map(|v| v as i64))
245+
});
241246
};
242247
}

yazi-fs/src/splatter.rs

Lines changed: 33 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -2,7 +2,7 @@
22
use std::os::unix::ffi::{OsStrExt, OsStringExt};
33
#[cfg(windows)]
44
use std::os::windows::ffi::{OsStrExt, OsStringExt};
5-
use std::{cell::Cell, ffi::{OsStr, OsString}, iter::{self, Peekable}, mem};
5+
use std::{cell::Cell, ffi::{OsStr, OsString}, fs::OpenOptions, io::Write, iter::{self, Peekable}, mem};
66

77
use yazi_shared::url::{AsUrl, Url, UrlCow};
88

@@ -81,6 +81,8 @@ where
8181
Some('d') | Some('D') => self.visit_dirname(it, buf),
8282
Some('t') | Some('T') => self.visit_tab(it, buf),
8383
Some('y') | Some('Y') => self.visit_yanked(it, buf),
84+
Some('l') | Some('L') => self.visit_line_number(it, buf),
85+
Some('c') | Some('C') => self.visit_column_number(it, buf),
8486
Some('%') => self.visit_escape(it, buf),
8587
Some('*') => self.visit_selected(it, buf), // TODO: remove this
8688
Some(c) if c.is_ascii_digit() => self.visit_digit(it, buf),
@@ -192,6 +194,36 @@ where
192194
}
193195
}
194196

197+
fn visit_line_number(&mut self, it: &mut Iter, buf: &mut Buf) {
198+
it.next();
199+
let idx = self.consume_digit(it);
200+
201+
let line_str = self
202+
.src
203+
.selected(self.tab, idx)
204+
.next()
205+
.and_then(|url| url.line())
206+
.map(|line| line.to_string())
207+
.unwrap_or_else(|| "".to_string());
208+
209+
cue(buf, OsStr::new(&line_str));
210+
}
211+
212+
fn visit_column_number(&mut self, it: &mut Iter, buf: &mut Buf) {
213+
it.next();
214+
let idx = self.consume_digit(it);
215+
216+
let col_str = self
217+
.src
218+
.selected(self.tab, idx)
219+
.next()
220+
.and_then(|url| url.column())
221+
.map(|col| col.to_string())
222+
.unwrap_or_else(|| "".to_string());
223+
224+
cue(buf, OsStr::new(&col_str));
225+
}
226+
195227
fn visit_escape(&mut self, it: &mut Iter, buf: &mut Buf) { buf.push(it.next().unwrap()); }
196228

197229
fn visit_unknown(&mut self, it: &mut Iter, buf: &mut Buf) {

yazi-plugin/preset/components/entity.lua

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -43,6 +43,13 @@ end
4343

4444
function Entity:highlights()
4545
local name, p = self._file.name, ui.printable
46+
47+
local ok, line = pcall(function() return self._file:line() end)
48+
if ok and line then
49+
local path_str = tostring(self._file.path)
50+
name = name .. ":" .. line .. " (" .. path_str .. ")"
51+
end
52+
4653
local highlights = self._file:highlights()
4754
if not highlights or #highlights == 0 then
4855
return p(name)

yazi-plugin/src/external/rg.rs

Lines changed: 23 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -2,8 +2,8 @@ use std::process::Stdio;
22

33
use anyhow::Result;
44
use tokio::{io::{AsyncBufReadExt, BufReader}, process::Command, sync::mpsc::{self, UnboundedReceiver}};
5-
use yazi_fs::{File, FsUrl};
6-
use yazi_shared::url::{AsUrl, UrlBuf, UrlLike};
5+
use yazi_fs::{File};
6+
use yazi_shared::url::{UrlBuf, UrlLike};
77
use yazi_vfs::VfsFile;
88

99
pub struct RgOpt {
@@ -13,13 +13,24 @@ pub struct RgOpt {
1313
pub args: Vec<String>,
1414
}
1515

16+
fn parse_rg_line_column(line: &str) -> Option<(&str, usize, usize)> {
17+
let mut parts = line.split(':');
18+
19+
let (f, l, c) = (parts.next()?, parts.next()?, parts.next()?);
20+
if f.is_empty() { return None; }
21+
22+
Some((f, l.parse().ok()?, c.parse().ok()?))
23+
}
24+
1625
pub fn rg(opt: RgOpt) -> Result<UnboundedReceiver<File>> {
26+
let subject = opt.subject.clone();
27+
let cwd = opt.cwd.clone();
28+
1729
let mut child = Command::new("rg")
18-
.args(["--color=never", "--files-with-matches", "--smart-case"])
30+
.args(["--color=never", "--line-number", "--no-heading", "--smart-case", "--column"])
1931
.arg(if opt.hidden { "--hidden" } else { "--no-hidden" })
2032
.args(opt.args)
2133
.arg(opt.subject)
22-
.arg(&*opt.cwd.as_url().unified_path())
2334
.kill_on_drop(true)
2435
.stdout(Stdio::piped())
2536
.stderr(Stdio::null())
@@ -30,9 +41,14 @@ pub fn rg(opt: RgOpt) -> Result<UnboundedReceiver<File>> {
3041

3142
tokio::spawn(async move {
3243
while let Ok(Some(line)) = it.next_line().await {
33-
let Ok(url) = opt.cwd.try_join(line) else {
34-
continue;
35-
};
44+
let Some((fp, l, c)) = parse_rg_line_column(&line) else { continue };
45+
46+
let url = cwd.try_join(fp)
47+
.ok()
48+
.and_then(|u| u.to_search(&format!("{}#{}:{}", subject, l, c)).ok());
49+
50+
let Some(url) = url else { continue };
51+
3652
if let Ok(file) = File::new(url).await {
3753
tx.send(file).ok();
3854
}

yazi-plugin/src/utils/preview.rs

Lines changed: 1 addition & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -18,10 +18,7 @@ impl Utils {
1818
let path = lock.url.as_url().unified_path();
1919
let inner = match Highlighter::new(path).highlight(lock.skip, area.size()).await {
2020
Ok(text) => text,
21-
Err(e @ PeekError::Exceed(max)) => return (e.to_string(), max).into_lua_multi(&lua),
22-
Err(e @ PeekError::Unexpected(_)) => {
23-
return e.to_string().into_lua_multi(&lua);
24-
}
21+
Err(e) => return e.to_string().into_lua_multi(&lua),
2522
};
2623

2724
lock.data = vec![Renderable::Text(Text {

yazi-shared/src/scheme/encode.rs

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -14,7 +14,7 @@ impl<'a> From<crate::url::Encode<'a>> for Encode<'a> {
1414
impl<'a> Encode<'a> {
1515
#[inline]
1616
pub fn domain<'s>(s: &'s str) -> PercentEncode<'s> {
17-
const SET: &AsciiSet = &CONTROLS.add(b'/').add(b':');
17+
const SET: &AsciiSet = &CONTROLS.add(b'/').add(b':').add(b'#');
1818
percent_encode(s.as_bytes(), SET)
1919
}
2020

yazi-shared/src/url/url.rs

Lines changed: 30 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -86,6 +86,36 @@ impl<'a> Url<'a> {
8686
})
8787
}
8888

89+
#[inline]
90+
pub fn line(self) -> Option<usize> {
91+
let Self::Search { domain, .. } = self else { return None };
92+
let Some(hash_pos) = domain.find('#') else {
93+
return None
94+
};
95+
96+
let frag = &domain[hash_pos + 1..];
97+
if let Some(colon_pos) = frag.find(':') {
98+
frag[..colon_pos].parse::<usize>().ok()
99+
} else {
100+
frag.parse::<usize>().ok()
101+
}
102+
}
103+
104+
#[inline]
105+
pub fn column(self) -> Option<usize> {
106+
let Self::Search { domain, .. } = self else { return None };
107+
let Some(hash_pos) = domain.find('#') else {
108+
return None
109+
};
110+
111+
let frag = &domain[hash_pos + 1..];
112+
if let Some(colon_pos) = frag.find(':') {
113+
frag[colon_pos + 1..].parse::<usize>().ok()
114+
} else {
115+
None
116+
}
117+
}
118+
89119
#[inline]
90120
pub fn has_base(self) -> bool {
91121
match self {

0 commit comments

Comments
 (0)