feat: wip store search_idx in preview to be usable in code.lua

This commit is contained in:
Jed 2026-06-08 23:42:15 +02:00
parent ba43e04fb8
commit 947e734860
6 changed files with 156 additions and 63 deletions

View file

@ -15,7 +15,9 @@ pub(super) struct Preview {
impl Deref for Preview {
type Target = yazi_core::tab::Preview;
fn deref(&self) -> &Self::Target { &self.tab.preview }
fn deref(&self) -> &Self::Target {
&self.tab.preview
}
}
impl Preview {
@ -27,6 +29,7 @@ impl Preview {
impl UserData for Preview {
fn add_fields<F: UserDataFields<Self>>(fields: &mut F) {
fields.add_field_method_get("skip", |_, me| Ok(me.skip));
fields.add_field_method_get("search_idx", |_, me| Ok(me.search_idx));
cached_field!(fields, folder, |_, me| {
me.tab
.hovered_folder()

View file

@ -1,7 +1,7 @@
use anyhow::Result;
use yazi_macro::succ;
use yazi_parser::mgr::PeekForm;
use yazi_shared::data::Data;
use yazi_shared::{data::Data, url::UrlLike};
use crate::{Actor, Ctx};
@ -37,8 +37,8 @@ impl Actor for Peek {
succ!();
}
let preview = &mut cx.tab_mut().preview;
if let Some(skip) = form.skip {
let preview = &mut cx.tab_mut().preview;
if form.upper_bound {
preview.skip = preview.skip.min(skip);
} else {
@ -46,6 +46,15 @@ impl Actor for Peek {
}
}
tracing::debug!("peeeek boy before set stuff");
match form.search_idx {
Some(index) => {
tracing::debug!("search index setting {}", index);
preview.search_idx = Some(index);
}
None => {}
}
if hovered.is_dir() {
cx.tab_mut().preview.go_folder(hovered, folder.map(|(_, cha)| cha), mime, form.force);
} else {

View file

@ -6,20 +6,27 @@ use yazi_adapter::ADAPTOR;
use yazi_config::{LAYOUT, YAZI};
use yazi_fs::{File, Files, FilesOp, cha::Cha};
use yazi_macro::render;
use yazi_runner::{RUNNER, previewer::{PeekError, PeekJob}};
use yazi_shared::{pool::Symbol, url::{UrlBuf, UrlLike}};
use yazi_runner::{
RUNNER,
previewer::{PeekError, PeekJob},
};
use yazi_shared::{
pool::Symbol,
url::{UrlBuf, UrlLike},
};
use yazi_vfs::{VfsFiles, VfsFilesOp};
use crate::{AppProxy, Highlighter, MgrProxy, tab::PreviewLock};
#[derive(Default)]
#[derive(Default, Debug)]
pub struct Preview {
pub lock: Option<PreviewLock>,
pub skip: usize,
pub search_idx: Option<usize>,
handle: Option<JoinHandle<()>>,
handle: Option<JoinHandle<()>>,
pub folder_lock: Option<UrlBuf>,
folder_loader: Option<JoinHandle<()>>,
folder_loader: Option<JoinHandle<()>>,
}
impl Preview {
@ -35,8 +42,12 @@ impl Preview {
};
self.abort();
// let search_first_occurrence = self.get_first_search_occurrence(&file.url);
let job = PeekJob { previewer, file, mime, skip: self.skip };
tracing::debug!("preview {:?}", self);
self.handle = Some(tokio::spawn(async move {
let mut rx = RUNNER.peek(&job).await;
match rx.recv().await.unwrap_or(Err(PeekError::Cancelled)) {
@ -85,7 +96,9 @@ impl Preview {
}
pub fn reset(&mut self) {
self.search_idx = None;
self.abort();
ADAPTOR.get().image_hide().ok();
render!(self.lock.take().is_some())
}
@ -95,7 +108,9 @@ impl Preview {
ADAPTOR.get().image_hide().ok();
}
pub fn same_url(&self, url: &UrlBuf) -> bool { matches!(&self.lock, Some(l) if l.url == *url) }
pub fn same_url(&self, url: &UrlBuf) -> bool {
matches!(&self.lock, Some(l) if l.url == *url)
}
pub fn same_file(&self, file: &File, mime: &str) -> bool {
self.same_url(&file.url)
@ -106,5 +121,7 @@ impl Preview {
self.same_file(file, mime) && matches!(&self.lock, Some(l) if l.skip == self.skip)
}
pub fn same_folder(&self, url: &UrlBuf) -> bool { self.folder_lock.as_ref() == Some(url) }
pub fn same_folder(&self, url: &UrlBuf) -> bool {
self.folder_lock.as_ref() == Some(url)
}
}

View file

@ -3,31 +3,39 @@ use yazi_shared::{event::ActionCow, url::UrlBuf};
#[derive(Debug, Default)]
pub struct PeekForm {
pub skip: Option<usize>,
pub force: bool,
pub only_if: Option<UrlBuf>,
pub skip: Option<usize>,
pub force: bool,
pub only_if: Option<UrlBuf>,
pub upper_bound: bool,
pub search_idx: Option<usize>,
}
impl From<ActionCow> for PeekForm {
fn from(mut a: ActionCow) -> Self {
Self {
skip: a.first().ok(),
force: a.bool("force"),
only_if: a.take("only-if").ok(),
skip: a.first().ok(),
force: a.bool("force"),
only_if: a.take("only-if").ok(),
upper_bound: a.bool("upper-bound"),
search_idx: a.take("search_idx").ok(),
}
}
}
impl From<bool> for PeekForm {
fn from(force: bool) -> Self { Self { force, ..Default::default() } }
fn from(force: bool) -> Self {
Self { force, ..Default::default() }
}
}
impl FromLua for PeekForm {
fn from_lua(_: Value, _: &Lua) -> mlua::Result<Self> { Err("unsupported".into_lua_err()) }
fn from_lua(_: Value, _: &Lua) -> mlua::Result<Self> {
Err("unsupported".into_lua_err())
}
}
impl IntoLua for PeekForm {
fn into_lua(self, _: &Lua) -> mlua::Result<Value> { Err("unsupported".into_lua_err()) }
fn into_lua(self, _: &Lua) -> mlua::Result<Value> {
Err("unsupported".into_lua_err())
}
}

View file

@ -8,7 +8,6 @@ local function get_search_occurrences(url)
local lines = {}
for occurrence in occurrences:gmatch("[^,]+") do
local line, col = occurrence:match("(%d+)-(%d+)")
ya.dbg("line col:", line, col)
table.insert(lines, { tonumber(line), tonumber(col) })
end
@ -19,35 +18,38 @@ local function get_search_occurrences(url)
return lines
end
local function get_next_occurrence(occurrences, current_line, direction)
local step = direction == "up" and -1 or 1
local start = direction == "up" and #occurrences or 1
local stop = direction == "up" and 1 or #occurrences
for i = start, stop, step do
local line = occurrences[i]
if go_up and line >= current_line then
return line
end
if direction == "down" and line <= current_line then
return line
end
-- TODO: Handle return to first element or last one
end
end
-- local function get_next_occurrence(occurrences, current_line, direction)
-- local step = direction == "up" and -1 or 1
-- local start = direction == "up" and #occurrences or 1
-- local stop = direction == "up" and 1 or #occurrences
--
-- for i = start, stop, step do
-- local line = occurrences[i]
-- if go_up and line >= current_line then
-- return line
-- end
-- if direction == "down" and line <= current_line then
-- return line
-- end
--
-- -- TODO: Handle return to first element or last one
-- end
-- end
function M:peek(job)
local search_occurrences = get_search_occurrences(job.file.url)
local search_idx = cx.active.preview.search_idx
ya.dbg(" search index", search_idx)
local occurrence
if search_occurrences and search_idx then
occurrence = search_occurrences[search_idx]
end
ya.dgb("Peek Occurrence:", occurrence)
-- Todo: Pass the occurrence for highlighting
local err, bound = ya.preview_code(job)
ya.dbg("PEEKING broooo", job.file.url, bound)
if bound then
local search_occurrences = get_search_occurrences(job.file.url)
if search_occurrences then
local line = search_occurrences[1][1]
ya.dbg("liiiiine: ", line)
ya.emit("peek", { math.max(0, search_occurrences[1][1] - 1), only_if = job.file.url })
return
end
ya.emit("peek", { bound, only_if = job.file.url, upper_bound = true })
elseif err and not err:find("cancelled", 1, true) then
require("empty").msg(job, err)
@ -55,8 +57,11 @@ function M:peek(job)
end
function M:seek(job)
ya.dbg("UNITS IN SEEK ", job.units)
local direction = job.units > 0 and "down" or "up" end
local direction = job.units > 0 and "down" or "up"
local search_idx = cx.active.preview.search_idx
ya.dbg(" search index", search_idx)
local h = cx.active.current.hovered
if not h or h.url ~= job.file.url then
return
@ -64,20 +69,21 @@ function M:seek(job)
local search_occurrences = get_search_occurrences(job.file.url)
if search_occurrences then
local current_line = cx.active.preview.skip + 1
local next_occurrence = get_next_occurrence(search_occurrences, current_line, direction)
for _, occurrence in ipairs(search_occurrences) do
local search_line = occurrence[1]
-- local col = occurrence[2]
if search_line >= current_line then
ya.emit("peek", { math.max(0, search_line - 1), only_if = job.file.url })
return
end
end
search_idx = (search_idx or 0) + 1
-- local current_line = cx.active.preview.skip + 1
-- ya.dbg("Current line:", current_line)
-- local next_occurrence = get_next_occurrence(search_occurrences, current_line, direction)
-- for _, occurrence in ipairs(search_occurrences) do
-- local search_line = occurrence[1]
-- -- local col = occurrence[2]
--
-- if search_line >= current_line then
-- ya.emit("peek", { math.max(0, search_line - 1), only_if = job.file.url })
-- return
-- end
-- end
end
-- ya.emit("peek", { math.max(0, line - 1), only_if = job.file.url })
local step = math.floor(job.units * job.area.h / 10)
@ -86,6 +92,7 @@ function M:seek(job)
ya.emit("peek", {
math.max(0, cx.active.preview.skip + step),
only_if = job.file.url,
search_idx,
})
end

View file

@ -1,5 +1,9 @@
use mlua::{ExternalError, Function, IntoLuaMulti, Lua, Table, Value};
use yazi_binding::{Error, elements::{Area, Renderable, Text}};
// use ratatui::style::Color;
use yazi_binding::{
Error,
elements::{Area, HighlightPosition, Renderable, Text},
};
use yazi_core::{Highlighter, MgrProxy, tab::PreviewLock};
use yazi_fs::FsUrl;
use yazi_runner::previewer::PeekError;
@ -14,10 +18,17 @@ impl Utils {
pub(super) fn preview_code(lua: &Lua) -> mlua::Result<Function> {
lua.create_async_function(|lua, t: Table| async move {
let area: Area = t.raw_get("area")?;
let mut lock = PreviewLock::try_from(t)?;
// let position: Option<HighlightPosition> = t.raw_get("position").ok();
let mut lock = PreviewLock::try_from(t)?;
let path = lock.url.as_url().unified_path();
let inner = match Highlighter::oneshot(path, lock.skip, area.size()).await {
// let skip = position.map(|p| p.line).unwrap_or(lock.skip);
let size = area.size();
// tracing::debug!("skip size: {size}");
let inner = match Highlighter::oneshot(path, lock.skip, size).await {
Ok(text) => text,
Err(e @ PeekError::Exceeded(max)) => return (e, max).into_lua_multi(&lua),
Err(e) => {
@ -25,6 +36,8 @@ impl Utils {
}
};
// tracing::debug!("Inner: {inner}");
lock.data = vec![Renderable::Text(Text { area, inner, ..Default::default() })];
MgrProxy::update_peeked(lock);
@ -32,6 +45,8 @@ impl Utils {
})
}
// Note: You need to implement or update Highlighter::oneshot_with_highlight to accept line/column and highlight accordingly.
pub(super) fn preview_widget(lua: &Lua) -> mlua::Result<Function> {
lua.create_async_function(|_, (t, value): (Table, Value)| async move {
let mut lock = PreviewLock::try_from(t)?;
@ -59,3 +74,37 @@ impl Utils {
})
}
}
// fn apply_highlight(text: &mut Text, column: usize, length: usize) {
// use ratatui::text::Span;
// if length == 0 {
// return;
// }
// let Some(line) = text.inner.lines.first_mut() else { return };
// let mut new_spans = Vec::new();
// let mut char_pos = 0usize;
// for span in std::mem::take(&mut line.spans) {
// let n = span.content.chars().count();
// let range = char_pos..char_pos + n;
// if range.end <= column || range.start >= column + length || n == 0 {
// new_spans.push(span);
// } else {
// let chars: Vec<char> = span.content.chars().collect();
// let hl_start = column.saturating_sub(char_pos).min(n);
// let hl_end = (column + length).saturating_sub(char_pos).min(n);
// if hl_start > 0 {
// new_spans.push(Span { content: chars[..hl_start].iter().collect(), style: span.style });
// }
// {
// let mut hl_style = span.style;
// hl_style.bg = hl_style.bg.or(Some(Color::Yellow));
// new_spans.push(Span { content: chars[hl_start..hl_end].iter().collect(), style: hl_style });
// }
// if hl_end < n {
// new_spans.push(Span { content: chars[hl_end..].iter().collect(), style: span.style });
// }
// }
// char_pos = range.end;
// }
// line.spans = new_spans;
// }