Skip to content

Commit

Permalink
key_inputに応じてtargetを絞り込み & sortして表示する
Browse files Browse the repository at this point in the history
  • Loading branch information
kyu08 committed Nov 23, 2023
1 parent 021fe6f commit 1abfc9c
Show file tree
Hide file tree
Showing 4 changed files with 143 additions and 33 deletions.
1 change: 1 addition & 0 deletions Cargo.lock

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

1 change: 1 addition & 0 deletions Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -16,3 +16,4 @@ colored = "2"
ratatui = "0.23.0"
crossterm = "0.27.0"
serde = { version = "1.0.181", features = ["derive"] }
fuzzy-matcher = "0.3.7"
46 changes: 38 additions & 8 deletions src/usecases/fzf_make_ratatui/app.rs
Original file line number Diff line number Diff line change
@@ -1,3 +1,5 @@
use crate::models::makefile::Makefile;

use super::ui::ui;
use crossterm::{
event::{DisableMouseCapture, EnableMouseCapture, KeyCode},
Expand All @@ -8,7 +10,7 @@ use ratatui::{
backend::{Backend, CrosstermBackend},
Terminal,
};
use std::{error::Error, io};
use std::{error::Error, io, process};

pub enum CurrentPain {
Main,
Expand All @@ -28,21 +30,42 @@ impl CurrentPain {
enum Message {
MoveToNextPain,
Quit,
KeyInput(String),
Backspace, // TODO: Delegate to rhysd/tui-textarea
}

pub struct Model {
pub key_input: String,
pub current_pain: CurrentPain, // the current screen the user is looking at, and will later determine what is rendered.
pub should_quit: bool,
pub makefile: Makefile,
}

impl Model {
pub fn new() -> Model {
Model {
pub fn new() -> Result<Self, Box<dyn Error>> {
let makefile = match Makefile::create_makefile() {
Err(e) => {
println!("[ERR] {}", e);
process::exit(1)
}
Ok(f) => f,
};

Ok(Model {
key_input: String::new(),
current_pain: CurrentPain::Main,
should_quit: false,
}
makefile,
})
}

pub fn update_key_input(&mut self, key_input: String) -> String {
self.key_input.clone() + &key_input
}
pub fn pop(&mut self) -> String {
let mut origin = self.key_input.clone();
origin.pop();
origin
}
}

Expand All @@ -54,8 +77,9 @@ pub fn main() -> Result<(), Box<dyn Error>> {
let backend = CrosstermBackend::new(stderr);
let mut terminal = Terminal::new(backend)?;

let mut model = Model::new();
let _ = run(&mut terminal, &mut model); // TODO: error handling
if let Ok(mut model) = Model::new() {
let _ = run(&mut terminal, &mut model); // TODO: error handling
}

disable_raw_mode()?;
execute!(
Expand Down Expand Up @@ -93,8 +117,8 @@ fn handle_event(model: &Model) -> io::Result<Option<Message>> {
KeyCode::Esc => Some(Message::Quit),
_ => match model.current_pain {
CurrentPain::Main => match key.code {
KeyCode::Char('e') => Some(Message::MoveToNextPain),
KeyCode::Tab => Some(Message::MoveToNextPain),
KeyCode::Backspace => Some(Message::Backspace),
KeyCode::Char(char) => Some(Message::KeyInput(char.to_string())),
_ => None,
},
CurrentPain::History => match key.code {
Expand All @@ -121,6 +145,12 @@ fn update(model: &mut Model, message: Option<Message>) -> &mut Model {
Some(Message::Quit) => {
model.should_quit = true;
}
Some(Message::KeyInput(key_input)) => {
model.key_input = model.update_key_input(key_input);
}
Some(Message::Backspace) => {
model.key_input = model.pop();
}
None => {}
}
model
Expand Down
128 changes: 103 additions & 25 deletions src/usecases/fzf_make_ratatui/ui.rs
Original file line number Diff line number Diff line change
@@ -1,12 +1,16 @@
use fuzzy_matcher::skim::SkimMatcherV2;
use fuzzy_matcher::FuzzyMatcher;
use ratatui::{
backend::Backend,
layout::{Constraint, Direction, Layout},
style::{Color, Style},
text::{Line, Span},
widgets::{Block, Borders, Paragraph},
widgets::{Block, Borders, List, ListItem, Paragraph},
Frame,
};

use crate::models::makefile::Makefile;

use super::app::Model;

pub fn ui<B: Backend>(f: &mut Frame<B>, model: &Model) {
Expand All @@ -28,19 +32,35 @@ pub fn ui<B: Backend>(f: &mut Frame<B>, model: &Model) {
.constraints([Constraint::Percentage(50), Constraint::Percentage(50)])
.split(fzf_make_chunks[0]);

let list = rounded_border_block("Preview", model.current_pain.is_main());
f.render_widget(list, fzf_make_preview_chunks[0]);
let list = rounded_border_block("Targets", model.current_pain.is_main());
f.render_widget(list, fzf_make_preview_chunks[1]);
let list = rounded_border_block("Input", model.current_pain.is_main());
f.render_widget(list, fzf_make_chunks[1]);

let title_block = rounded_border_block("History", model.current_pain.is_history());
f.render_widget(title_block, fzf_preview_and_history_chunks[1]);
f.render_widget(
// TODO: ハイライトしてtargetの内容を表示する
rounded_border_block("Preview", model.current_pain.is_main()),
fzf_make_preview_chunks[0],
);
f.render_widget(
targets_block(
"Targets",
model.key_input.clone(),
model.makefile.clone(),
model.current_pain.is_main(),
),
fzf_make_preview_chunks[1],
);
f.render_widget(
// NOTE: To show cursor, use rhysd/tui-textarea
input_block("Input", &model.key_input, model.current_pain.is_main()),
fzf_make_chunks[1],
);
f.render_widget(
rounded_border_block("History", model.current_pain.is_history()),
fzf_preview_and_history_chunks[1],
);

let hint_text = match model.current_pain {
super::app::CurrentPain::Main => "<esc>: to quit, <tab> move to next tab",
super::app::CurrentPain::History => "q / <esc>: to quit, <tab> move to next tab",
super::app::CurrentPain::Main => {
"(Any key except the following): Narrow down targets, <esc>: Quit, <tab> Move to next tab"
}
super::app::CurrentPain::History => "q / <esc>: Quit, <tab> Move to next tab",
};
let current_keys_hint = { Span::styled(hint_text, Style::default()) };

Expand All @@ -49,19 +69,77 @@ pub fn ui<B: Backend>(f: &mut Frame<B>, model: &Model) {
f.render_widget(key_notes_footer, main_chunks[1]);
}

fn input_block<'a>(title: &'a str, target_input: &'a str, is_current: bool) -> Paragraph<'a> {
let fg_color = if is_current {
Color::Yellow
} else {
Color::default()
};

Paragraph::new(Line::from(target_input))
.block(
Block::default()
.title(title)
.borders(Borders::ALL)
.border_type(ratatui::widgets::BorderType::Rounded)
.border_style(Style::default().fg(fg_color))
.style(Style::default())
.padding(ratatui::widgets::Padding::new(2, 0, 0, 0)),
)
.style(Style::default())
}
fn targets_block(title: &str, key_input: String, makefile: Makefile, is_current: bool) -> List<'_> {
// TODO: 選択する
let fg_color = if is_current {
Color::Yellow
} else {
Color::default()
};

let matcher = SkimMatcherV2::default();
let mut filtered_list: Vec<(Option<i64>, String)> = makefile
.to_targets_string()
.into_iter()
.map(|target| match matcher.fuzzy_indices(&target, &key_input) {
Some((score, _)) => (Some(score), target), // TODO: highligh matched part
None => (None, target),
})
.filter(|(score, _)| score.is_some())
.collect();

filtered_list.sort_by(|(score1, _), (score2, _)| score1.cmp(score2));
filtered_list.reverse();

// Sort filtered_list by first element of tuple
let list: Vec<ListItem> = filtered_list
.into_iter()
.map(|(_, target)| ListItem::new(target).style(Style::default().fg(Color::Yellow)))
.collect();

List::new(list)
.block(
Block::default()
.title(title)
.borders(Borders::ALL)
.border_type(ratatui::widgets::BorderType::Rounded)
.border_style(Style::default().fg(fg_color))
.style(Style::default())
.padding(ratatui::widgets::Padding::new(2, 0, 0, 0)),
)
.style(Style::default())
}

fn rounded_border_block(title: &str, is_current: bool) -> Block {
if is_current {
Block::default()
.title(title)
.borders(Borders::ALL)
.border_type(ratatui::widgets::BorderType::Rounded)
.border_style(Style::default().fg(Color::Yellow))
.style(Style::default())
let fg_color = if is_current {
Color::Yellow
} else {
Block::default()
.title(title)
.borders(Borders::ALL)
.border_type(ratatui::widgets::BorderType::Rounded)
.style(Style::default())
}
Color::default()
};

Block::default()
.title(title)
.borders(Borders::ALL)
.border_type(ratatui::widgets::BorderType::Rounded)
.border_style(Style::default().fg(fg_color))
.style(Style::default())
}

0 comments on commit 1abfc9c

Please sign in to comment.