Small corrections
This commit is contained in:
@@ -44,6 +44,26 @@ impl CompletionState {
|
||||
}
|
||||
}
|
||||
|
||||
struct SampleFinderState {
|
||||
query: String,
|
||||
folders: Vec<String>,
|
||||
matches: Vec<usize>,
|
||||
cursor: usize,
|
||||
active: bool,
|
||||
}
|
||||
|
||||
impl SampleFinderState {
|
||||
fn new() -> Self {
|
||||
Self {
|
||||
query: String::new(),
|
||||
folders: Vec::new(),
|
||||
matches: Vec::new(),
|
||||
cursor: 0,
|
||||
active: false,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
struct SearchState {
|
||||
query: String,
|
||||
active: bool,
|
||||
@@ -61,6 +81,7 @@ impl SearchState {
|
||||
pub struct Editor {
|
||||
text: TextArea<'static>,
|
||||
completion: CompletionState,
|
||||
sample_finder: SampleFinderState,
|
||||
search: SearchState,
|
||||
scroll_offset: Cell<u16>,
|
||||
}
|
||||
@@ -110,6 +131,7 @@ impl Editor {
|
||||
Self {
|
||||
text: TextArea::default(),
|
||||
completion: CompletionState::new(),
|
||||
sample_finder: SampleFinderState::new(),
|
||||
search: SearchState::new(),
|
||||
scroll_offset: Cell::new(0),
|
||||
}
|
||||
@@ -118,6 +140,7 @@ impl Editor {
|
||||
pub fn set_content(&mut self, lines: Vec<String>) {
|
||||
self.text = TextArea::new(lines);
|
||||
self.completion.active = false;
|
||||
self.sample_finder.active = false;
|
||||
self.search.query.clear();
|
||||
self.search.active = false;
|
||||
self.scroll_offset.set(0);
|
||||
@@ -226,6 +249,79 @@ impl Editor {
|
||||
}
|
||||
}
|
||||
|
||||
pub fn set_sample_folders(&mut self, folders: Vec<String>) {
|
||||
self.sample_finder.folders = folders;
|
||||
}
|
||||
|
||||
pub fn activate_sample_finder(&mut self) {
|
||||
self.completion.active = false;
|
||||
self.sample_finder.query.clear();
|
||||
self.sample_finder.cursor = 0;
|
||||
self.sample_finder.matches = (0..self.sample_finder.folders.len()).collect();
|
||||
self.sample_finder.active = true;
|
||||
}
|
||||
|
||||
pub fn dismiss_sample_finder(&mut self) {
|
||||
self.sample_finder.active = false;
|
||||
}
|
||||
|
||||
pub fn sample_finder_active(&self) -> bool {
|
||||
self.sample_finder.active
|
||||
}
|
||||
|
||||
pub fn sample_finder_input(&mut self, c: char) {
|
||||
self.sample_finder.query.push(c);
|
||||
self.update_sample_finder_matches();
|
||||
}
|
||||
|
||||
pub fn sample_finder_backspace(&mut self) {
|
||||
self.sample_finder.query.pop();
|
||||
self.update_sample_finder_matches();
|
||||
}
|
||||
|
||||
pub fn sample_finder_next(&mut self) {
|
||||
if self.sample_finder.cursor + 1 < self.sample_finder.matches.len() {
|
||||
self.sample_finder.cursor += 1;
|
||||
}
|
||||
}
|
||||
|
||||
pub fn sample_finder_prev(&mut self) {
|
||||
if self.sample_finder.cursor > 0 {
|
||||
self.sample_finder.cursor -= 1;
|
||||
}
|
||||
}
|
||||
|
||||
pub fn accept_sample_finder(&mut self) {
|
||||
if self.sample_finder.matches.is_empty() {
|
||||
self.sample_finder.active = false;
|
||||
return;
|
||||
}
|
||||
let idx = self.sample_finder.matches[self.sample_finder.cursor];
|
||||
let name = self.sample_finder.folders[idx].clone();
|
||||
self.text.insert_str(&name);
|
||||
self.sample_finder.active = false;
|
||||
}
|
||||
|
||||
fn update_sample_finder_matches(&mut self) {
|
||||
if self.sample_finder.query.is_empty() {
|
||||
self.sample_finder.matches = (0..self.sample_finder.folders.len()).collect();
|
||||
} else {
|
||||
let mut scored: Vec<(usize, usize)> = self
|
||||
.sample_finder
|
||||
.folders
|
||||
.iter()
|
||||
.enumerate()
|
||||
.filter_map(|(i, name)| fuzzy_match(&self.sample_finder.query, name).map(|s| (s, i)))
|
||||
.collect();
|
||||
scored.sort_by_key(|(score, _)| *score);
|
||||
self.sample_finder.matches = scored.into_iter().map(|(_, i)| i).collect();
|
||||
}
|
||||
self.sample_finder.cursor = self
|
||||
.sample_finder
|
||||
.cursor
|
||||
.min(self.sample_finder.matches.len().saturating_sub(1));
|
||||
}
|
||||
|
||||
pub fn input(&mut self, input: impl Into<tui_textarea::Input>) {
|
||||
let input: tui_textarea::Input = input.into();
|
||||
let has_modifier = input.ctrl || input.alt;
|
||||
@@ -261,7 +357,7 @@ impl Editor {
|
||||
}
|
||||
|
||||
fn update_completion(&mut self) {
|
||||
if !self.completion.enabled || self.completion.candidates.is_empty() {
|
||||
if !self.completion.enabled || self.completion.candidates.is_empty() || self.sample_finder.active {
|
||||
return;
|
||||
}
|
||||
|
||||
@@ -395,7 +491,9 @@ impl Editor {
|
||||
|
||||
frame.render_widget(Paragraph::new(lines).scroll((offset as u16, 0)), area);
|
||||
|
||||
if self.completion.active && !self.completion.matches.is_empty() {
|
||||
if self.sample_finder.active && !self.sample_finder.matches.is_empty() {
|
||||
self.render_sample_finder(frame, area, cursor_row - offset);
|
||||
} else if self.completion.active && !self.completion.matches.is_empty() {
|
||||
self.render_completion(frame, area, cursor_row - offset);
|
||||
}
|
||||
}
|
||||
@@ -511,6 +609,98 @@ impl Editor {
|
||||
|
||||
frame.render_widget(Paragraph::new(doc_lines), doc_area);
|
||||
}
|
||||
|
||||
fn render_sample_finder(&self, frame: &mut Frame, editor_area: Rect, cursor_row: usize) {
|
||||
let t = theme::get();
|
||||
let max_visible: usize = 8;
|
||||
let width: u16 = 24;
|
||||
|
||||
let visible_count = self.sample_finder.matches.len().min(max_visible);
|
||||
let total_height = visible_count as u16 + 1; // +1 for query line
|
||||
|
||||
let (_, cursor_col) = self.text.cursor();
|
||||
let popup_x = (editor_area.x + cursor_col as u16)
|
||||
.min(editor_area.x + editor_area.width.saturating_sub(width));
|
||||
|
||||
let below_y = editor_area.y + cursor_row as u16 + 1;
|
||||
let popup_y = if below_y + total_height > editor_area.y + editor_area.height {
|
||||
(editor_area.y + cursor_row as u16).saturating_sub(total_height)
|
||||
} else {
|
||||
below_y
|
||||
};
|
||||
|
||||
let area = Rect::new(popup_x, popup_y, width, total_height);
|
||||
frame.render_widget(Clear, area);
|
||||
|
||||
let bg_style = Style::default().bg(t.editor_widget.completion_bg);
|
||||
let highlight_style = Style::default()
|
||||
.fg(t.editor_widget.completion_selected)
|
||||
.add_modifier(Modifier::BOLD);
|
||||
let normal_style = Style::default().fg(t.editor_widget.completion_fg);
|
||||
|
||||
let w = width as usize;
|
||||
let mut lines: Vec<Line> = Vec::new();
|
||||
|
||||
let query_display = format!("/{}", self.sample_finder.query);
|
||||
lines.push(Line::from(Span::styled(
|
||||
format!("{query_display:<w$}"),
|
||||
highlight_style.bg(t.editor_widget.completion_bg),
|
||||
)));
|
||||
|
||||
let scroll_offset = if self.sample_finder.cursor >= max_visible {
|
||||
self.sample_finder.cursor - max_visible + 1
|
||||
} else {
|
||||
0
|
||||
};
|
||||
|
||||
for i in scroll_offset..scroll_offset + visible_count {
|
||||
let idx = self.sample_finder.matches[i];
|
||||
let name = &self.sample_finder.folders[idx];
|
||||
let style = if i == self.sample_finder.cursor {
|
||||
highlight_style
|
||||
} else {
|
||||
normal_style
|
||||
};
|
||||
let prefix = if i == self.sample_finder.cursor { "> " } else { " " };
|
||||
let display = format!("{prefix}{name:<width$}", width = w - 2);
|
||||
lines.push(Line::from(Span::styled(
|
||||
format!("{display:<w$}"),
|
||||
style.bg(t.editor_widget.completion_bg),
|
||||
)));
|
||||
}
|
||||
|
||||
// Fill rest with bg
|
||||
for _ in lines.len() as u16..total_height {
|
||||
lines.push(Line::from(Span::styled(" ".repeat(w), bg_style)));
|
||||
}
|
||||
|
||||
frame.render_widget(Paragraph::new(lines), area);
|
||||
}
|
||||
}
|
||||
|
||||
pub fn fuzzy_match(query: &str, target: &str) -> Option<usize> {
|
||||
let target_lower: Vec<char> = target.to_lowercase().chars().collect();
|
||||
let query_lower: Vec<char> = query.to_lowercase().chars().collect();
|
||||
let mut ti = 0;
|
||||
let mut score = 0;
|
||||
let mut prev_pos = 0;
|
||||
for (qi, &qc) in query_lower.iter().enumerate() {
|
||||
loop {
|
||||
if ti >= target_lower.len() {
|
||||
return None;
|
||||
}
|
||||
if target_lower[ti] == qc {
|
||||
if qi > 0 {
|
||||
score += ti - prev_pos;
|
||||
}
|
||||
prev_pos = ti;
|
||||
ti += 1;
|
||||
break;
|
||||
}
|
||||
ti += 1;
|
||||
}
|
||||
}
|
||||
Some(score)
|
||||
}
|
||||
|
||||
fn is_word_char(c: char) -> bool {
|
||||
|
||||
@@ -16,7 +16,7 @@ mod waveform;
|
||||
|
||||
pub use active_patterns::{ActivePatterns, MuteStatus};
|
||||
pub use confirm::ConfirmModal;
|
||||
pub use editor::{CompletionCandidate, Editor};
|
||||
pub use editor::{fuzzy_match, CompletionCandidate, Editor};
|
||||
pub use file_browser::FileBrowserModal;
|
||||
pub use list_select::ListSelect;
|
||||
pub use modal::ModalFrame;
|
||||
|
||||
@@ -158,12 +158,17 @@ impl<'a> SampleBrowser<'a> {
|
||||
Style::new().fg(icon_color)
|
||||
};
|
||||
|
||||
let spans = vec![
|
||||
let mut spans = vec![
|
||||
Span::raw(indent),
|
||||
Span::styled(icon, icon_style),
|
||||
Span::styled(&entry.label, label_style),
|
||||
];
|
||||
|
||||
if matches!(entry.kind, TreeLineKind::File) {
|
||||
let idx_style = Style::new().fg(colors.browser.empty_text);
|
||||
spans.push(Span::styled(format!(" {}", entry.index), idx_style));
|
||||
}
|
||||
|
||||
lines.push(Line::from(spans));
|
||||
}
|
||||
|
||||
|
||||
Reference in New Issue
Block a user