use super::{GlobalSearch, Preview, SearchHit, SearchKind};
use crate::components::browser::BlameState;
pub struct YankTarget {
pub repo: String,
pub path: String,
pub branch: String,
pub line: Option<u32>,
pub end: Option<u32>,
}
pub(crate) struct ExpandedFile {
pub(crate) hit: SearchHit,
pub(crate) loaded: bool,
pub(crate) preview: Preview,
}
impl GlobalSearch {
pub fn expanded_copy_target(&mut self) -> Option<(String, usize)> {
self.expanded
.as_mut()
.and_then(|exp| exp.preview.copy_target())
}
pub fn expanded_goto_line(&mut self, line: u32) -> bool {
match &mut self.expanded {
Some(exp) => {
exp.preview.set_cursor_line(line);
true
}
None => false,
}
}
pub fn expanded_branch(&self) -> Option<String> {
self.expanded.as_ref().map(|e| e.hit.branch.clone())
}
pub fn expanded_set_band(
&mut self,
path: &str,
ctx: Option<crate::components::preview::BandContext>,
) {
if let Some(exp) = &mut self.expanded
&& exp.hit.path == path
{
exp.preview.set_band(Some(path.to_string()), ctx);
}
}
pub fn yank_target(&self) -> Option<YankTarget> {
if let Some(exp) = &self.expanded {
let (line, end) = match exp.preview.visual_range() {
Some((lo, hi)) => (Some(lo), Some(hi)),
None => (exp.preview.line(), None),
};
return Some(YankTarget {
repo: exp.hit.repo.clone(),
path: exp.hit.path.clone(),
branch: exp.hit.branch.clone(),
line,
end,
});
}
self.selected_hit().map(|h| YankTarget {
repo: h.repo.clone(),
path: h.path.clone(),
branch: h.branch.clone(),
line: Some(h.line),
end: None,
})
}
pub fn query_text(&self) -> String {
self.query.value()
}
pub fn is_grep(&self) -> bool {
self.kind == SearchKind::Grep
}
pub fn blame_active(&self) -> bool {
self.expanded.as_ref().is_some_and(|e| e.preview.blaming())
}
pub fn blame_clear(&mut self) {
self.blame = None;
if let Some(exp) = &mut self.expanded {
exp.preview.set_blame(None);
}
}
pub fn blame_toggle_on(&mut self) -> bool {
match &self.expanded {
Some(exp) if exp.preview.text_line_count() > 0 => {
if matches!(&self.blame, Some(b) if !b.loading) {
self.blame_apply();
}
true
}
_ => false,
}
}
pub fn blame_needed(&self) -> Option<(String, String, String)> {
let exp = self.expanded.as_ref()?;
if exp.preview.text_line_count() == 0 {
return None;
}
match &self.blame {
None => Some((
exp.hit.repo.clone(),
exp.hit.path.clone(),
exp.hit.branch.clone(),
)),
Some(_) => None, }
}
pub fn blame_mark_loading(&mut self, path: String) {
self.blame = Some(BlameState {
path,
ranges: Vec::new(),
loading: true,
});
}
pub fn blame_loading_for(&self, path: &str) -> bool {
matches!(&self.blame, Some(b) if b.loading && b.path == path)
}
pub fn blame_store(&mut self, path: String, ranges: Vec<rootle_provider::BlameRange>) {
let active = self.blame_loading_for(&path);
self.blame = Some(BlameState {
path,
ranges,
loading: false,
});
if active {
self.blame_apply();
}
}
fn blame_apply(&mut self) {
let Some(b) = &self.blame else { return };
let Some(exp) = &mut self.expanded else {
return;
};
let lines = exp.preview.text_line_count();
if lines == 0 {
return;
}
let mut marks: Vec<Option<crate::components::preview::BlameMark>> = vec![None; lines];
for r in &b.ranges {
let start = (r.start_line as usize).saturating_sub(1);
if start < lines {
marks[start] = Some(crate::components::preview::BlameMark {
sha: r.sha.chars().take(7).collect(),
author: r.author.clone(),
});
}
}
exp.preview.set_blame(Some(marks));
}
}