use std::path::PathBuf;
use std::sync::Arc;
use std::sync::atomic::AtomicBool;
use std::thread::JoinHandle;
use hjkl_buffer::Buffer;
use hjkl_picker::{FileSource, PickerAction, PickerLogic, RequeryMode, RgSource};
use crate::picker_action::AppAction;
pub struct FileSourceWithOpen {
inner: FileSource,
}
impl FileSourceWithOpen {
pub fn new(root: PathBuf) -> Self {
Self {
inner: FileSource::new(root),
}
}
}
impl PickerLogic for FileSourceWithOpen {
fn title(&self) -> &str {
self.inner.title()
}
fn item_count(&self) -> usize {
self.inner.item_count()
}
fn label(&self, idx: usize) -> String {
self.inner.label(idx)
}
fn match_text(&self, idx: usize) -> String {
self.inner.match_text(idx)
}
fn has_preview(&self) -> bool {
self.inner.has_preview()
}
fn preview(&self, idx: usize) -> (Buffer, String) {
self.inner.preview(idx)
}
fn preview_path(&self, idx: usize) -> Option<PathBuf> {
self.inner.preview_path(idx)
}
fn select(&self, idx: usize) -> PickerAction {
match self
.inner
.items
.lock()
.ok()
.and_then(|g| g.get(idx).cloned())
{
Some(p) => PickerAction::Custom(Box::new(AppAction::OpenPath(p))),
None => PickerAction::None,
}
}
fn requery_mode(&self) -> RequeryMode {
self.inner.requery_mode()
}
fn label_match_positions(&self, idx: usize, query: &str, label: &str) -> Option<Vec<usize>> {
self.inner.label_match_positions(idx, query, label)
}
fn enumerate(
&mut self,
query: Option<&str>,
cancel: Arc<AtomicBool>,
) -> Option<JoinHandle<()>> {
self.inner.enumerate(query, cancel)
}
}
pub struct RgSourceWithOpen {
inner: RgSource,
root: PathBuf,
}
impl RgSourceWithOpen {
pub fn new(root: PathBuf) -> Self {
Self {
inner: RgSource::new(root.clone()),
root,
}
}
}
impl PickerLogic for RgSourceWithOpen {
fn title(&self) -> &str {
self.inner.title()
}
fn requery_mode(&self) -> RequeryMode {
self.inner.requery_mode()
}
fn item_count(&self) -> usize {
self.inner.item_count()
}
fn label(&self, idx: usize) -> String {
self.inner.label(idx)
}
fn match_text(&self, idx: usize) -> String {
self.inner.match_text(idx)
}
fn has_preview(&self) -> bool {
self.inner.has_preview()
}
fn preview(&self, idx: usize) -> (Buffer, String) {
self.inner.preview(idx)
}
fn preview_path(&self, idx: usize) -> Option<PathBuf> {
self.inner.preview_path(idx)
}
fn preview_top_row(&self, idx: usize) -> usize {
self.inner.preview_top_row(idx)
}
fn preview_match_row(&self, idx: usize) -> Option<usize> {
self.inner.preview_match_row(idx)
}
fn select(&self, idx: usize) -> PickerAction {
match self
.inner
.items
.lock()
.ok()
.and_then(|g| g.get(idx).map(|m| (m.path.clone(), m.line)))
{
Some((path, line)) if !path.as_os_str().is_empty() => PickerAction::Custom(Box::new(
AppAction::OpenPathAtLine(self.root.join(path), line),
)),
_ => PickerAction::None,
}
}
fn label_match_positions(&self, idx: usize, query: &str, label: &str) -> Option<Vec<usize>> {
self.inner.label_match_positions(idx, query, label)
}
fn enumerate(
&mut self,
query: Option<&str>,
cancel: Arc<AtomicBool>,
) -> Option<JoinHandle<()>> {
self.inner.enumerate(query, cancel)
}
}
#[derive(Clone)]
pub struct BufferEntry {
pub idx: usize,
pub name: String,
pub dirty: bool,
pub content: String,
pub path: Option<PathBuf>,
pub cursor_row: usize,
pub window_start: usize,
}
pub struct BufferSource {
pub entries: Vec<BufferEntry>,
}
impl BufferSource {
pub fn new<S>(
slots: &[S],
name_of: impl Fn(&S) -> String,
dirty_of: impl Fn(&S) -> bool,
content_of: impl Fn(&S) -> String,
path_of: impl Fn(&S) -> Option<PathBuf>,
cursor_row_of: impl Fn(&S) -> usize,
window_start_of: impl Fn(&S) -> usize,
) -> Self {
let entries = slots
.iter()
.enumerate()
.map(|(idx, s)| BufferEntry {
idx,
name: name_of(s),
dirty: dirty_of(s),
content: content_of(s),
path: path_of(s),
cursor_row: cursor_row_of(s),
window_start: window_start_of(s),
})
.collect();
Self { entries }
}
}
impl PickerLogic for BufferSource {
fn title(&self) -> &str {
"buffers"
}
fn item_count(&self) -> usize {
self.entries.len()
}
fn label(&self, idx: usize) -> String {
match self.entries.get(idx) {
Some(e) => {
if e.dirty {
format!("● {}", e.name)
} else {
format!(" {}", e.name)
}
}
None => String::new(),
}
}
fn match_text(&self, idx: usize) -> String {
self.label(idx)
}
fn has_preview(&self) -> bool {
true
}
fn preview(&self, idx: usize) -> (Buffer, String) {
match self.entries.get(idx) {
Some(e) => {
let mut buf = Buffer::from_str(&e.content);
buf.set_cursor(hjkl_buffer::Position {
row: e.cursor_row,
col: 0,
});
(buf, String::new())
}
None => (Buffer::new(), String::new()),
}
}
fn preview_path(&self, idx: usize) -> Option<PathBuf> {
self.entries.get(idx).and_then(|e| e.path.clone())
}
fn preview_top_row(&self, idx: usize) -> usize {
self.entries
.get(idx)
.map(|e| e.cursor_row.saturating_sub(2))
.unwrap_or(0)
}
fn preview_match_row(&self, idx: usize) -> Option<usize> {
self.entries.get(idx).map(|e| e.cursor_row)
}
fn preview_line_offset(&self, idx: usize) -> usize {
self.entries.get(idx).map(|e| e.window_start).unwrap_or(0)
}
fn select(&self, idx: usize) -> PickerAction {
match self.entries.get(idx) {
Some(e) => PickerAction::Custom(Box::new(AppAction::SwitchSlot(e.idx))),
None => PickerAction::None,
}
}
fn enumerate(
&mut self,
_query: Option<&str>,
_cancel: Arc<AtomicBool>,
) -> Option<JoinHandle<()>> {
None
}
}
pub struct DiagEntry {
pub label: String,
pub start_row: usize,
pub start_col: usize,
}
pub struct DiagSource {
entries: Vec<DiagEntry>,
}
impl DiagSource {
pub fn new(entries: Vec<DiagEntry>) -> Self {
Self { entries }
}
}
impl PickerLogic for DiagSource {
fn title(&self) -> &str {
"diagnostics"
}
fn item_count(&self) -> usize {
self.entries.len()
}
fn label(&self, idx: usize) -> String {
self.entries
.get(idx)
.map(|e| e.label.clone())
.unwrap_or_default()
}
fn match_text(&self, idx: usize) -> String {
self.label(idx)
}
fn has_preview(&self) -> bool {
false
}
fn select(&self, idx: usize) -> PickerAction {
match self.entries.get(idx) {
Some(e) => {
PickerAction::Custom(Box::new(AppAction::JumpToRowCol(e.start_row, e.start_col)))
}
None => PickerAction::None,
}
}
fn enumerate(
&mut self,
_query: Option<&str>,
_cancel: Arc<AtomicBool>,
) -> Option<JoinHandle<()>> {
None
}
}
pub struct StaticListSource {
title: String,
entries: Vec<(String, AppAction)>,
}
impl StaticListSource {
pub fn new(title: String, entries: Vec<(String, AppAction)>) -> Self {
Self { title, entries }
}
}
impl PickerLogic for StaticListSource {
fn title(&self) -> &str {
&self.title
}
fn item_count(&self) -> usize {
self.entries.len()
}
fn label(&self, idx: usize) -> String {
self.entries
.get(idx)
.map(|(l, _)| l.clone())
.unwrap_or_default()
}
fn match_text(&self, idx: usize) -> String {
self.label(idx)
}
fn has_preview(&self) -> bool {
false
}
fn select(&self, idx: usize) -> PickerAction {
match self.entries.get(idx) {
Some((_, action)) => {
let boxed: Box<dyn std::any::Any + Send> = match action {
AppAction::OpenPath(p) => Box::new(AppAction::OpenPath(p.clone())),
AppAction::OpenPathAtLine(p, l) => {
Box::new(AppAction::OpenPathAtLine(p.clone(), *l))
}
AppAction::JumpToRowCol(r, c) => Box::new(AppAction::JumpToRowCol(*r, *c)),
AppAction::SwitchSlot(i) => Box::new(AppAction::SwitchSlot(*i)),
AppAction::ShowCommit(s) => Box::new(AppAction::ShowCommit(s.clone())),
AppAction::CheckoutBranch(s) => Box::new(AppAction::CheckoutBranch(s.clone())),
AppAction::CheckoutTag(s) => Box::new(AppAction::CheckoutTag(s.clone())),
AppAction::FetchRemote(s) => Box::new(AppAction::FetchRemote(s.clone())),
AppAction::StashApply(i) => Box::new(AppAction::StashApply(*i)),
AppAction::StashPop(i) => Box::new(AppAction::StashPop(*i)),
AppAction::StashDrop(i) => Box::new(AppAction::StashDrop(*i)),
AppAction::ApplyCodeAction(i) => Box::new(AppAction::ApplyCodeAction(*i)),
AppAction::AnvilInstall(s) => Box::new(AppAction::AnvilInstall(s.clone())),
AppAction::AnvilUninstall(s) => Box::new(AppAction::AnvilUninstall(s.clone())),
AppAction::AnvilUpdate(s) => Box::new(AppAction::AnvilUpdate(s.clone())),
AppAction::AnvilNoOp(s) => Box::new(AppAction::AnvilNoOp(s.clone())),
};
PickerAction::Custom(boxed)
}
None => PickerAction::None,
}
}
fn enumerate(
&mut self,
_query: Option<&str>,
_cancel: Arc<AtomicBool>,
) -> Option<JoinHandle<()>> {
None
}
}
#[derive(Debug, Clone)]
#[allow(dead_code)]
pub enum AnvilState {
Installed { version: String },
Outdated {
installed_version: String,
available_version: String,
},
Available,
}
#[derive(Debug, Clone)]
#[allow(dead_code)]
pub struct AnvilPickerItem {
pub name: String,
pub category: hjkl_anvil::ToolCategory,
pub description: String,
pub state: AnvilState,
pub registry_version: String,
}
impl AnvilPickerItem {
pub fn label(&self) -> String {
let state_col = match &self.state {
AnvilState::Installed { version } => format!("[installed @ {version}]"),
AnvilState::Outdated {
available_version, ..
} => format!("[outdated \u{2192} {available_version}]"),
AnvilState::Available => "[available]".to_string(),
};
let cat = format!("{:?}", self.category).to_lowercase();
format!(
"{:<30} {:<20} {:<12} \u{2014} {}",
state_col, self.name, cat, self.description
)
}
}
pub struct AnvilPickerSource {
pub items: Vec<AnvilPickerItem>,
}
impl AnvilPickerSource {
pub fn from_registry(registry: &hjkl_anvil::Registry) -> Self {
let items = registry
.names()
.filter_map(|name| {
let spec = registry.get(name)?;
let rev = hjkl_anvil::store::read_rev(name).ok().flatten();
let state = match rev {
None => AnvilState::Available,
Some(r) => {
if r.version == spec.version {
AnvilState::Installed {
version: r.version.clone(),
}
} else {
AnvilState::Outdated {
installed_version: r.version.clone(),
available_version: spec.version.clone(),
}
}
}
};
Some(AnvilPickerItem {
name: name.to_string(),
category: spec.category,
description: spec.description.clone(),
state,
registry_version: spec.version.clone(),
})
})
.collect();
Self { items }
}
}
impl PickerLogic for AnvilPickerSource {
fn title(&self) -> &str {
"anvil tools"
}
fn item_count(&self) -> usize {
self.items.len()
}
fn label(&self, idx: usize) -> String {
self.items.get(idx).map(|i| i.label()).unwrap_or_default()
}
fn match_text(&self, idx: usize) -> String {
self.items
.get(idx)
.map(|i| {
let cat = format!("{:?}", i.category).to_lowercase();
format!("{} {} {}", i.name, cat, i.description)
})
.unwrap_or_default()
}
fn has_preview(&self) -> bool {
false
}
fn select(&self, idx: usize) -> PickerAction {
match self.items.get(idx) {
Some(item) => {
let action = match &item.state {
AnvilState::Available | AnvilState::Outdated { .. } => {
AppAction::AnvilInstall(item.name.clone())
}
AnvilState::Installed { .. } => AppAction::AnvilNoOp(item.name.clone()),
};
PickerAction::Custom(Box::new(action))
}
None => PickerAction::None,
}
}
fn enumerate(
&mut self,
_query: Option<&str>,
_cancel: Arc<AtomicBool>,
) -> Option<JoinHandle<()>> {
None
}
}