feat: Renderer including Terminal and Panes

This commit is contained in:
ynqa
2024-12-26 23:41:37 +09:00
parent 0423814937
commit 22a7ecf291
6 changed files with 147 additions and 131 deletions
+5 -3
View File
@@ -19,15 +19,16 @@ mod editor;
use editor::Editor;
mod json;
use json::JsonProvider;
mod search;
use search::{IncrementalSearcher, SearchProvider};
mod processor;
use processor::{
init::ViewInitializer, monitor::ContextMonitor, spinner::SpinnerSpawner, Context, Processor,
ViewProvider, Visualizer,
};
mod prompt;
use prompt::{PaneIndex, EMPTY_PANE, PANE_SIZE};
mod render;
use render::{PaneIndex, Renderer, EMPTY_PANE};
mod search;
use search::{IncrementalSearcher, SearchProvider};
/// JSON navigator and interactive filter leveraging jq
#[derive(Parser)]
@@ -208,6 +209,7 @@ async fn main() -> anyhow::Result<()> {
},
100,
50000,
args.no_hint,
)
.await?;
+21 -18
View File
@@ -2,10 +2,10 @@ use std::sync::Arc;
use async_trait::async_trait;
use crossterm::event::Event;
use promkit::{pane::Pane, terminal::Terminal};
use promkit::pane::Pane;
use tokio::{sync::Mutex, task::JoinHandle};
use crate::{PaneIndex, EMPTY_PANE, PANE_SIZE};
use crate::{PaneIndex, Renderer, EMPTY_PANE};
pub(crate) mod init;
pub(crate) mod monitor;
pub(crate) mod spinner;
@@ -63,8 +63,7 @@ impl Processor {
&self,
query: String,
shared_visualizer: Arc<Mutex<impl Visualizer>>,
shared_terminal: Arc<Mutex<Terminal>>,
shared_panes: Arc<Mutex<[Pane; PANE_SIZE]>>,
shared_renderer: Arc<Mutex<Renderer>>,
) -> JoinHandle<()> {
let shared = self.shared.clone();
tokio::spawn(async move {
@@ -83,14 +82,22 @@ impl Processor {
};
{
let mut panes = shared_panes.lock().await;
// TODO: error handling
let _ = shared_renderer.lock().await.update_and_draw([
(
PaneIndex::ProcessorGuide,
maybe_guide.unwrap_or(EMPTY_PANE.to_owned()),
),
(
PaneIndex::Processor,
maybe_resp.unwrap_or(EMPTY_PANE.to_owned()),
),
]);
}
{
let mut shared_state = shared.lock().await;
let mut terminal = shared_terminal.lock().await;
panes[PaneIndex::ProcessorGuide as usize] =
maybe_guide.unwrap_or(EMPTY_PANE.to_owned());
panes[PaneIndex::Processor as usize] = maybe_resp.unwrap_or(EMPTY_PANE.to_owned());
shared_state.state = State::Idle;
let _ = terminal.draw(&*panes);
}
})
}
@@ -100,8 +107,7 @@ impl Processor {
shared_visualizer: Arc<Mutex<impl Visualizer>>,
area: (u16, u16),
query: String,
shared_terminal: Arc<Mutex<Terminal>>,
shared_panes: Arc<Mutex<[Pane; PANE_SIZE]>>,
shared_renderer: Arc<Mutex<Renderer>>,
) {
{
let mut shared_state = self.shared.lock().await;
@@ -111,8 +117,7 @@ impl Processor {
}
}
let process_task =
self.spawn_process_task(query, shared_visualizer, shared_terminal, shared_panes);
let process_task = self.spawn_process_task(query, shared_visualizer, shared_renderer);
{
let mut shared_state = self.shared.lock().await;
@@ -124,8 +129,7 @@ impl Processor {
&self,
shared_visualizer: Arc<Mutex<impl Visualizer>>,
query: String,
shared_terminal: Arc<Mutex<Terminal>>,
shared_panes: Arc<Mutex<[Pane; PANE_SIZE]>>,
shared_renderer: Arc<Mutex<Renderer>>,
) {
{
let mut shared_state = self.shared.lock().await;
@@ -134,8 +138,7 @@ impl Processor {
}
}
let process_task =
self.spawn_process_task(query, shared_visualizer, shared_terminal, shared_panes);
let process_task = self.spawn_process_task(query, shared_visualizer, shared_renderer);
{
let mut shared_state = self.shared.lock().await;
+10 -10
View File
@@ -1,10 +1,9 @@
use std::sync::Arc;
use promkit::{pane::Pane, terminal::Terminal};
use tokio::sync::Mutex;
use super::{Context, State, ViewProvider, Visualizer};
use crate::{PaneIndex, PANE_SIZE};
use crate::{PaneIndex, Renderer};
pub struct ViewInitializer {
shared: Arc<Mutex<Context>>,
@@ -20,8 +19,7 @@ impl ViewInitializer {
provider: &'a mut T,
item: &'static str,
area: (u16, u16),
shared_terminal: Arc<Mutex<Terminal>>,
shared_panes: Arc<Mutex<[Pane; PANE_SIZE]>>,
shared_renderer: Arc<Mutex<Renderer>>,
) -> anyhow::Result<impl Visualizer + 'a> {
{
let mut shared_state = self.shared.lock().await;
@@ -35,13 +33,15 @@ impl ViewInitializer {
let pane = visualizer.create_init_pane(area).await;
{
let mut panes = shared_panes.lock().await;
let mut shared_state = self.shared.lock().await;
let mut terminal = shared_terminal.lock().await;
panes[PaneIndex::Processor as usize] = pane;
shared_state.state = State::Idle;
// TODO: error handling
let _ = terminal.draw(&*panes);
let _ = shared_renderer
.lock()
.await
.update_and_draw([(PaneIndex::Processor, pane)]);
}
{
let mut shared_state = self.shared.lock().await;
shared_state.state = State::Idle;
}
Ok(visualizer)
+8 -9
View File
@@ -1,10 +1,10 @@
use std::sync::Arc;
use promkit::{pane::Pane, terminal::Terminal};
use promkit::pane::Pane;
use tokio::{sync::Mutex, task::JoinHandle, time::Duration};
use super::{Context, State};
use crate::PaneIndex;
use crate::{PaneIndex, Renderer};
const LOADING_FRAMES: [&str; 10] = ["", "", "", "", "", "", "", "", "", ""];
@@ -19,8 +19,7 @@ impl SpinnerSpawner {
pub fn spawn_spin_task(
&self,
spin_panes: Arc<Mutex<[Pane]>>,
spin_terminal: Arc<Mutex<Terminal>>,
shared_renderer: Arc<Mutex<Renderer>>,
spin_duration: Duration,
) -> JoinHandle<()> {
let shared = self.shared.clone();
@@ -39,18 +38,18 @@ impl SpinnerSpawner {
frame_index = (frame_index + 1) % LOADING_FRAMES.len();
let loading_pane = Pane::new(
let pane = Pane::new(
vec![promkit::grapheme::StyledGraphemes::from(
LOADING_FRAMES[frame_index],
)],
0,
);
{
let mut panes = spin_panes.lock().await;
let mut terminal = spin_terminal.lock().await;
panes[PaneIndex::Processor as usize] = loading_pane;
// TODO: error handling
let _ = terminal.draw(&panes);
let _ = shared_renderer
.lock()
.await
.update_and_draw([(PaneIndex::Processor, pane)]);
}
}
})
+53 -91
View File
@@ -1,8 +1,4 @@
use std::{
io,
sync::{Arc, LazyLock},
time::Duration,
};
use std::{io, sync::Arc, time::Duration};
use arboard::Clipboard;
use crossterm::{
@@ -14,17 +10,15 @@ use crossterm::{
};
use futures::StreamExt;
use futures_timer::Delay;
use promkit::{
listbox, pane::Pane, style::StyleBuilder, terminal::Terminal, text, text_editor, PaneFactory,
};
use promkit::{listbox, style::StyleBuilder, text, text_editor, PaneFactory};
use tokio::{
sync::{mpsc, Mutex, RwLock},
task::JoinHandle,
};
use crate::{
Context, ContextMonitor, Editor, IncrementalSearcher, Processor, SearchProvider,
SpinnerSpawner, ViewInitializer, ViewProvider, Visualizer,
Context, ContextMonitor, Editor, IncrementalSearcher, PaneIndex, Processor, Renderer,
SearchProvider, SpinnerSpawner, ViewInitializer, ViewProvider, Visualizer, EMPTY_PANE,
};
fn spawn_debouncer<T: Send + 'static>(
@@ -78,18 +72,6 @@ fn copy_to_clipboard(content: &str) -> text::State {
}
}
#[derive(Debug)]
pub enum PaneIndex {
Editor = 0,
Guide = 1,
ProcessorGuide = 2,
Search = 3,
Processor = 4,
}
pub const PANE_SIZE: usize = PaneIndex::Processor as usize + 1;
pub static EMPTY_PANE: LazyLock<Pane> = LazyLock::new(|| Pane::new(vec![], 0));
pub enum Focus {
Editor,
Processor,
@@ -106,6 +88,7 @@ pub async fn run<T: ViewProvider + SearchProvider>(
listbox_state: listbox::State,
search_result_chunk_size: usize,
search_load_chunk_size: usize,
no_hint: bool,
) -> anyhow::Result<()> {
enable_raw_mode()?;
execute!(io::stdout(), cursor::Hide)?;
@@ -116,23 +99,19 @@ pub async fn run<T: ViewProvider + SearchProvider>(
let loading_suggestions_task = searcher.spawn_load_task::<T>(item, search_load_chunk_size);
let editor = Editor::new(text_editor_state, searcher);
let mut init_terminal = Terminal {
position: cursor::position()?,
};
let init_panes = [
editor.create_editor_pane(size.0, size.1),
EMPTY_PANE.to_owned(),
EMPTY_PANE.to_owned(),
EMPTY_PANE.to_owned(),
EMPTY_PANE.to_owned(),
];
init_terminal.draw(&init_panes)?;
let shared_renderer = Arc::new(Mutex::new(Renderer::try_init_draw(
[
editor.create_editor_pane(size.0, size.1),
EMPTY_PANE.to_owned(),
EMPTY_PANE.to_owned(),
EMPTY_PANE.to_owned(),
EMPTY_PANE.to_owned(),
],
no_hint,
)?));
let ctx = Arc::new(Mutex::new(Context::new(size)));
let shared_terminal = Arc::new(Mutex::new(init_terminal));
let shared_panes: Arc<Mutex<[Pane; PANE_SIZE]>> = Arc::new(Mutex::new(init_panes));
let (last_query_tx, mut last_query_rx) = mpsc::channel(1);
let (debounce_query_tx, debounce_query_rx) = mpsc::channel(1);
let query_debouncer =
@@ -144,11 +123,7 @@ pub async fn run<T: ViewProvider + SearchProvider>(
spawn_debouncer(debounce_resize_rx, last_resize_tx, resize_debounce_duration);
let spinner_spawner = SpinnerSpawner::new(ctx.clone());
let spinning = spinner_spawner.spawn_spin_task(
shared_panes.clone(),
shared_terminal.clone(),
spin_duration,
);
let spinning = spinner_spawner.spawn_spin_task(shared_renderer.clone(), spin_duration);
let mut focus = Focus::Editor;
let (editor_event_tx, mut editor_event_rx) = mpsc::channel::<Event>(1);
@@ -162,18 +137,11 @@ pub async fn run<T: ViewProvider + SearchProvider>(
let processor = Processor::new(ctx.clone());
let context_monitor = ContextMonitor::new(ctx.clone());
let initializer = ViewInitializer::new(ctx.clone());
let initializing = initializer.initialize(
provider,
item,
size,
shared_terminal.clone(),
shared_panes.clone(),
);
let initializing = initializer.initialize(provider, item, size, shared_renderer.clone());
let main_task: JoinHandle<anyhow::Result<()>> = {
let mut stream = EventStream::new();
let shared_terminal = shared_terminal.clone();
let shared_panes = shared_panes.clone();
let shared_renderer = shared_renderer.clone();
tokio::spawn(async move {
'main: loop {
tokio::select! {
@@ -215,10 +183,9 @@ pub async fn run<T: ViewProvider + SearchProvider>(
}.create_pane(size.0, size.1);
}
{
let mut panes = shared_panes.lock().await;
let mut terminal = shared_terminal.lock().await;
panes[PaneIndex::Guide as usize] = pane;
terminal.draw(&*panes)?;
shared_renderer.lock().await.update_and_draw([
(PaneIndex::Guide, pane),
])?;
}
},
Event::Key(KeyEvent {
@@ -245,10 +212,9 @@ pub async fn run<T: ViewProvider + SearchProvider>(
}.create_pane(size.0, size.1);
}
{
let mut panes = shared_panes.lock().await;
let mut terminal = shared_terminal.lock().await;
panes[PaneIndex::Guide as usize] = pane;
terminal.draw(&*panes)?;
shared_renderer.lock().await.update_and_draw([
(PaneIndex::Guide, pane),
])?;
}
},
Focus::Processor => {
@@ -278,8 +244,7 @@ pub async fn run<T: ViewProvider + SearchProvider>(
};
let editor_task: JoinHandle<anyhow::Result<()>> = {
let shared_terminal = shared_terminal.clone();
let shared_panes = shared_panes.clone();
let shared_renderer = shared_renderer.clone();
let shared_editor = shared_editor.clone();
tokio::spawn(async move {
loop {
@@ -293,10 +258,9 @@ pub async fn run<T: ViewProvider + SearchProvider>(
let size = terminal::size()?;
let pane = guide.create_pane(size.0, size.1);
{
let mut panes = shared_panes.lock().await;
let mut terminal = shared_terminal.lock().await;
panes[PaneIndex::Guide as usize] = pane;
terminal.draw(&*panes)?;
shared_renderer.lock().await.update_and_draw([
(PaneIndex::Guide, pane),
])?;
}
}
Some(event) = editor_event_rx.recv() => {
@@ -319,12 +283,11 @@ pub async fn run<T: ViewProvider + SearchProvider>(
)
};
{
let mut panes = shared_panes.lock().await;
let mut terminal = shared_terminal.lock().await;
panes[PaneIndex::Editor as usize] = editor_pane;
panes[PaneIndex::Guide as usize] = guide_pane;
panes[PaneIndex::Search as usize] = searcher_pane;
terminal.draw(&*panes)?;
shared_renderer.lock().await.update_and_draw([
(PaneIndex::Editor, editor_pane),
(PaneIndex::Guide, guide_pane),
(PaneIndex::Search, searcher_pane),
])?;
}
}
else => {
@@ -337,8 +300,7 @@ pub async fn run<T: ViewProvider + SearchProvider>(
};
let processor_task: JoinHandle<anyhow::Result<()>> = {
let shared_terminal = shared_terminal.clone();
let shared_panes = shared_panes.clone();
let shared_renderer = shared_renderer.clone();
let shared_editor = shared_editor.clone();
let visualizer = initializing.await?;
let shared_visualizer = Arc::new(Mutex::new(visualizer));
@@ -351,10 +313,9 @@ pub async fn run<T: ViewProvider + SearchProvider>(
let size = terminal::size()?;
let pane = guide.create_pane(size.0, size.1);
{
let mut panes = shared_panes.lock().await;
let mut terminal = shared_terminal.lock().await;
panes[PaneIndex::Guide as usize] = pane;
terminal.draw(&*panes)?;
shared_renderer.lock().await.update_and_draw([
(PaneIndex::Guide, pane),
])?;
}
}
Some(event) = processor_event_rx.recv() => {
@@ -363,31 +324,33 @@ pub async fn run<T: ViewProvider + SearchProvider>(
visualizer.create_pane_from_event((size.0, size.1), &event).await
};
{
let mut panes = shared_panes.lock().await;
let mut terminal = shared_terminal.lock().await;
panes[PaneIndex::Processor as usize] = pane;
terminal.draw(&*panes)?;
shared_renderer.lock().await.update_and_draw([
(PaneIndex::Processor, pane),
])?;
}
}
Some(query) = last_query_rx.recv() => {
processor.render_result(
shared_visualizer.clone(),
query,
shared_terminal.clone(),
shared_panes.clone(),
shared_renderer.clone(),
).await;
}
Some(area) = last_resize_rx.recv() => {
let pane = {
let (editor_pane, guide_pane, searcher_pane) = {
let editor = shared_editor.read().await;
editor.create_editor_pane(area.0, area.1)
(
editor.create_editor_pane(size.0, size.1),
editor.create_guide_pane(size.0, size.1),
editor.create_searcher_pane(size.0, size.1),
)
};
{
let mut panes = shared_panes.lock().await;
let mut terminal = shared_terminal.lock().await;
panes[PaneIndex::Editor as usize] = pane;
panes[PaneIndex::Search as usize] = Pane::new(vec![], 0);
terminal.draw(&*panes)?;
shared_renderer.lock().await.update_and_draw([
(PaneIndex::Editor, editor_pane),
(PaneIndex::Guide, guide_pane),
(PaneIndex::Search, searcher_pane),
])?;
}
let text = {
let editor = shared_editor.read().await;
@@ -397,8 +360,7 @@ pub async fn run<T: ViewProvider + SearchProvider>(
shared_visualizer.clone(),
area,
text,
shared_terminal.clone(),
shared_panes.clone(),
shared_renderer.clone(),
).await;
}
else => {
+50
View File
@@ -0,0 +1,50 @@
use std::sync::LazyLock;
use crossterm::{self, cursor};
use promkit::{pane::Pane, terminal::Terminal};
#[derive(Debug, PartialEq)]
pub enum PaneIndex {
Editor = 0,
Guide = 1,
ProcessorGuide = 2,
Search = 3,
Processor = 4,
}
pub const PANE_SIZE: usize = PaneIndex::Processor as usize + 1;
pub static EMPTY_PANE: LazyLock<Pane> = LazyLock::new(|| Pane::new(vec![], 0));
pub struct Renderer {
no_hint: bool,
terminal: Terminal,
panes: [Pane; PANE_SIZE],
}
impl Renderer {
pub fn try_init_draw(init_panes: [Pane; PANE_SIZE], no_hint: bool) -> anyhow::Result<Self> {
let mut ret = Self {
no_hint,
terminal: Terminal {
position: cursor::position()?,
},
panes: init_panes,
};
ret.terminal.draw(&ret.panes)?;
Ok(ret)
}
pub fn update_and_draw<I: IntoIterator<Item = (PaneIndex, Pane)>>(
&mut self,
iter: I,
) -> anyhow::Result<()> {
for (index, pane) in iter {
if self.no_hint && (index == PaneIndex::Guide || index == PaneIndex::ProcessorGuide) {
continue;
}
self.panes[index as usize] = pane;
}
self.terminal.draw(&self.panes)?;
Ok(())
}
}