Skip to content

Commit

Permalink
[preview] added large preview scrollbar
Browse files Browse the repository at this point in the history
  • Loading branch information
aslpavel committed Oct 26, 2024
1 parent 4c5b89b commit 6c9d8f9
Show file tree
Hide file tree
Showing 7 changed files with 211 additions and 85 deletions.
4 changes: 1 addition & 3 deletions Cargo.lock

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

4 changes: 2 additions & 2 deletions Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -20,8 +20,8 @@ tokio = { version = "1", features = ["full"] }
tracing = { version = "^0.1" }
tracing-subscriber = { version = "^0.3", features = ["env-filter"] }

surf_n_term = { version = "^0.17.1" }
# surf_n_term = { path = "../surf-n-term" }
# surf_n_term = { version = "^0.17.1" }
surf_n_term = { path = "../surf-n-term" }
# surf_n_term = { git = "https://github.com/aslpavel/surf-n-term.git" }

sweep = { path = "sweep-lib" }
Expand Down
4 changes: 2 additions & 2 deletions sweep-lib/src/candidate.rs
Original file line number Diff line number Diff line change
Expand Up @@ -361,7 +361,7 @@ impl Haystack for Candidate {
type Context = CandidateContext;
type View = Flex<'static>;
type Preview = HaystackBasicPreview<FieldsView>;
type PreviewLarge = HaystackBasicPreview<ProcessOutput>;
type PreviewLarge = ProcessOutput;

fn haystack_scope<S>(&self, _ctx: &Self::Context, scope: S)
where
Expand Down Expand Up @@ -447,7 +447,7 @@ impl Haystack for Candidate {
_positions: &Positions,
_theme: &Theme,
) -> Option<Self::PreviewLarge> {
Some(HaystackBasicPreview::new(ctx.preview_get(self)?, None))
ctx.preview_get(self)
}
}

Expand Down
59 changes: 49 additions & 10 deletions sweep-lib/src/haystack.rs
Original file line number Diff line number Diff line change
Expand Up @@ -2,8 +2,8 @@ use std::sync::Arc;

use either::Either;
use surf_n_term::{
view::{BoxConstraint, Text, View, ViewContext, ViewLayout, ViewMutLayout},
CellWrite, TerminalSurface,
view::{BoxConstraint, Layout, Text, View, ViewContext, ViewLayout, ViewMutLayout},
CellWrite, Position, TerminalSurface,
};

use crate::{Positions, Theme};
Expand All @@ -15,9 +15,9 @@ pub trait Haystack: std::fmt::Debug + Clone + Send + Sync + 'static {
/// Haystack context passed when generating view and preview (for example
/// [Candidate](crate::Candidate) reference resolution)
type Context: Clone + Send + Sync;
type View: View + Send + Sync;
type View: View;
type Preview: HaystackPreview;
type PreviewLarge: HaystackPreview;
type PreviewLarge: HaystackPreview + Clone;

/// Scope function is called with all characters one after another that will
/// be searchable by [Scorer]
Expand Down Expand Up @@ -49,16 +49,33 @@ pub trait Haystack: std::fmt::Debug + Clone + Send + Sync + 'static {
}
}

/// View that is used for preview, and include addition methods to make it more functional
pub trait HaystackPreview: View {
fn flex(&self) -> Option<f64>;
}

impl HaystackPreview for () {
/// Flex value when use as a child
fn flex(&self) -> Option<f64> {
None
Some(1.0)
}

/// Current preview layout
///
/// Size represents full size of the preview
/// Offset represents vertical and horizontal scroll position
fn preview_layout(&self) -> Layout {
Layout::new()
}

/// When rendering offset by specified position (used for scrolling)
///
/// Returns updated offset, default implementation is not scrollable hence
/// it is always returns `Position::origin()`
fn set_offset(&self, offset: Position) -> Position {
_ = offset;
Position::origin()
}
}

impl HaystackPreview for () {}

impl<L, R> HaystackPreview for Either<L, R>
where
L: HaystackPreview,
Expand All @@ -70,12 +87,34 @@ where
Either::Right(right) => right.flex(),
}
}

fn preview_layout(&self) -> Layout {
match self {
Either::Left(left) => left.preview_layout(),
Either::Right(right) => right.preview_layout(),
}
}

fn set_offset(&self, offset: Position) -> Position {
match self {
Either::Left(left) => left.set_offset(offset),
Either::Right(right) => right.set_offset(offset),
}
}
}

impl HaystackPreview for Arc<dyn HaystackPreview> {
impl<T: HaystackPreview + ?Sized> HaystackPreview for Arc<T> {
fn flex(&self) -> Option<f64> {
(**self).flex()
}

fn preview_layout(&self) -> Layout {
(**self).preview_layout()
}

fn set_offset(&self, offset: Position) -> Position {
(**self).set_offset(offset)
}
}

pub struct HaystackDefaultView {
Expand Down
4 changes: 2 additions & 2 deletions sweep-lib/src/scorer.rs
Original file line number Diff line number Diff line change
Expand Up @@ -67,7 +67,7 @@ impl<'a, S: Scorer> Scorer for &'a S {
}
}

impl Scorer for Box<dyn Scorer> {
impl<T: Scorer + ?Sized> Scorer for Box<T> {
fn name(&self) -> &str {
(**self).name()
}
Expand All @@ -79,7 +79,7 @@ impl Scorer for Box<dyn Scorer> {
}
}

impl Scorer for Arc<dyn Scorer> {
impl<T: Scorer + ?Sized> Scorer for Arc<T> {
fn name(&self) -> &str {
(**self).name()
}
Expand Down
147 changes: 125 additions & 22 deletions sweep-lib/src/sweep.rs
Original file line number Diff line number Diff line change
Expand Up @@ -22,7 +22,7 @@ use std::{
mem,
ops::Deref,
sync::{
atomic::{AtomicBool, Ordering},
atomic::{AtomicBool, AtomicUsize, Ordering},
Arc, RwLock,
},
thread::{Builder, JoinHandle},
Expand All @@ -31,12 +31,13 @@ use std::{
use surf_n_term::{
encoder::ColorDepth,
view::{
Align, Container, Flex, IntoView, Layout, Margins, Text, Tree, TreeId, TreeView, View,
ViewCache, ViewContext, ViewDeserializer, ViewLayoutStore,
Align, Axis, Container, Flex, IntoView, Layout, Margins, ScrollBarFn, ScrollBarPosition,
Text, Tree, TreeId, TreeView, View, ViewCache, ViewContext, ViewDeserializer,
ViewLayoutStore,
},
CellWrite, Glyph, Key, KeyChord, KeyMap, KeyMod, KeyName, Position, Size, SystemTerminal,
Terminal, TerminalAction, TerminalCommand, TerminalEvent, TerminalSize, TerminalSurfaceExt,
TerminalWaker,
CellWrite, Face, FaceAttrs, Glyph, Key, KeyChord, KeyMap, KeyMod, KeyName, Position, Size,
SystemTerminal, Terminal, TerminalAction, TerminalCommand, TerminalEvent, TerminalSize,
TerminalSurfaceExt, TerminalWaker,
};
use tokio::{
io::{AsyncRead, AsyncWrite},
Expand Down Expand Up @@ -706,6 +707,8 @@ enum SweepAction {
Help,
ScorerNext,
PreviewToggle,
PreviewLineNext,
PreviewLinePrev,
Input(InputAction),
List(ListAction),
}
Expand Down Expand Up @@ -791,17 +794,43 @@ impl SweepAction {
name: "sweep.preview.toggle".to_owned(),
description: "Toggle preview for an item".to_owned(),
},
PreviewLineNext => ActionDesc {
chords: vec![KeyChord::from_iter([Key {
name: KeyName::Char('j'),
mode: KeyMod::ALT,
}])],
name: "sweep.preview.line.next".to_owned(),
description: "Scroll preview one line down".to_owned(),
},
PreviewLinePrev => ActionDesc {
chords: vec![KeyChord::from_iter([Key {
name: KeyName::Char('k'),
mode: KeyMod::ALT,
}])],
name: "sweep.preview.line.prev".to_owned(),
description: "Scroll preview one line up".to_owned(),
},
Input(input_action) => input_action.description(),
List(list_action) => list_action.description(),
}
}

fn all() -> impl Iterator<Item = SweepAction> {
use SweepAction::*;
[Select, Mark, MarkAll, Quit, Help, ScorerNext, PreviewToggle]
.into_iter()
.chain(InputAction::all().map(Input))
.chain(ListAction::all().map(List))
[
Select,
Mark,
MarkAll,
Quit,
Help,
ScorerNext,
PreviewToggle,
PreviewLineNext,
PreviewLinePrev,
]
.into_iter()
.chain(InputAction::all().map(Input))
.chain(ListAction::all().map(List))
}
}

Expand Down Expand Up @@ -833,8 +862,10 @@ struct SweepState<H: Haystack> {
marked: Arc<RwLock<MarkedItems<H>>>,
// ranker
ranker: Ranker<H>,
// Filed refs (fields that can be used as a base)
// haystack context
haystack_context: H::Context,
// cached large preview of the current item
preview_large: Option<SweepPreview<H::PreviewLarge>>,
}

/// Event generated by key handling
Expand Down Expand Up @@ -912,28 +943,30 @@ where
marked: Default::default(),
ranker,
haystack_context,
preview_large: None,
}
}

// get preview of the currently pointed haystack item
fn preview(&self) -> Option<H::Preview> {
self.list.current().and_then(|item| {
item.item
.haystack
.preview(&self.haystack_context, &item.item.positions, &self.theme)
})
let item = self.list.current()?;
item.item
.haystack
.preview(&self.haystack_context, &item.item.positions, &self.theme)
}

// get large preview for currently pointed haystack item
fn preview_large(&self) -> Option<H::PreviewLarge> {
let result = self.list.current().and_then(|item| {
item.item.haystack.preview_large(
fn preview_large(&mut self) -> Option<SweepPreview<H::PreviewLarge>> {
let item = self.list.current()?;
if !matches!(&self.preview_large, Some(preview) if preview.id == item.item.id) {
let preview = item.item.haystack.preview_large(
&self.haystack_context,
&item.item.positions,
&self.theme,
)
});
result
)?;
self.preview_large = Some(SweepPreview::new(item.item.id, self.theme.clone(), preview));
}
self.preview_large.clone()
}

// update theme
Expand Down Expand Up @@ -1067,6 +1100,22 @@ where
show_preview: !self.theme.show_preview,
..self.theme.clone()
}),
SweepAction::PreviewLineNext => {
self.preview_large.as_ref().map(|preview| {
let layout = preview.preview.preview_layout();
let mut offset = layout.position();
offset.row = layout.size().height.min(offset.row + 1);
preview.preview.set_offset(offset);
});
}
SweepAction::PreviewLinePrev => {
self.preview_large.as_ref().map(|preview| {
let layout = preview.preview.preview_layout();
let mut offset = layout.position();
offset.row = offset.row.saturating_sub(1);
preview.preview.set_offset(offset);
});
}
}
Nothing
}
Expand Down Expand Up @@ -1841,6 +1890,60 @@ impl std::str::FromStr for SweepLayoutSize {
}
}

#[derive(Clone)]
struct SweepPreview<P> {
id: RankedItemId,
theme: Theme,
preview: P,
height: Arc<AtomicUsize>,
}

impl<P> SweepPreview<P> {
fn new(id: RankedItemId, theme: Theme, preview: P) -> Self {
Self {
id,
preview,
theme,
height: Arc::new(AtomicUsize::new(0)),
}
}
}

impl<P> IntoView for SweepPreview<P>
where
P: HaystackPreview + Clone + 'static,
{
type View = Flex<'static>;

fn into_view(self) -> Self::View {
let preview = self.preview.clone().trace_layout({
let height = self.height.clone();
move |_, layout| {
height.store(layout.size().height, Ordering::Relaxed);
}
});
let scrollbar = ScrollBarFn::new(
Axis::Vertical,
Face::new(Some(self.theme.accent), None, FaceAttrs::default()),
{
let layout = self.preview.preview_layout();
let height = self.height.clone();
move || {
ScrollBarPosition::from_counts(
layout.size().height,
layout.position().row,
height.load(Ordering::Relaxed),
)
}
},
);
let view = Flex::row()
.add_flex_child(1.0, preview)
.add_child(scrollbar);
view
}
}

#[cfg(test)]
mod tests {
use super::*;
Expand Down
Loading

0 comments on commit 6c9d8f9

Please sign in to comment.