Basic search mechanism in editor
This commit is contained in:
@@ -5,4 +5,5 @@ edition = "2021"
|
||||
|
||||
[dependencies]
|
||||
ratatui = "0.29"
|
||||
tui-textarea = "0.7"
|
||||
regex = "1"
|
||||
tui-textarea = { version = "0.7", features = ["search"] }
|
||||
|
||||
@@ -40,9 +40,24 @@ impl CompletionState {
|
||||
}
|
||||
}
|
||||
|
||||
struct SearchState {
|
||||
query: String,
|
||||
active: bool,
|
||||
}
|
||||
|
||||
impl SearchState {
|
||||
fn new() -> Self {
|
||||
Self {
|
||||
query: String::new(),
|
||||
active: false,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
pub struct Editor {
|
||||
text: TextArea<'static>,
|
||||
completion: CompletionState,
|
||||
search: SearchState,
|
||||
}
|
||||
|
||||
impl Default for Editor {
|
||||
@@ -56,12 +71,15 @@ impl Editor {
|
||||
Self {
|
||||
text: TextArea::default(),
|
||||
completion: CompletionState::new(),
|
||||
search: SearchState::new(),
|
||||
}
|
||||
}
|
||||
|
||||
pub fn set_content(&mut self, lines: Vec<String>) {
|
||||
self.text = TextArea::new(lines);
|
||||
self.completion.active = false;
|
||||
self.search.query.clear();
|
||||
self.search.active = false;
|
||||
}
|
||||
|
||||
pub fn set_candidates(&mut self, candidates: Vec<CompletionCandidate>) {
|
||||
@@ -99,6 +117,62 @@ impl Editor {
|
||||
}
|
||||
}
|
||||
|
||||
pub fn activate_search(&mut self) {
|
||||
self.search.active = true;
|
||||
self.completion.active = false;
|
||||
}
|
||||
|
||||
pub fn search_active(&self) -> bool {
|
||||
self.search.active
|
||||
}
|
||||
|
||||
pub fn search_query(&self) -> &str {
|
||||
&self.search.query
|
||||
}
|
||||
|
||||
pub fn search_input(&mut self, c: char) {
|
||||
self.search.query.push(c);
|
||||
self.apply_search_pattern();
|
||||
}
|
||||
|
||||
pub fn search_backspace(&mut self) {
|
||||
self.search.query.pop();
|
||||
self.apply_search_pattern();
|
||||
}
|
||||
|
||||
pub fn search_confirm(&mut self) {
|
||||
self.search.active = false;
|
||||
}
|
||||
|
||||
pub fn search_clear(&mut self) {
|
||||
self.search.query.clear();
|
||||
self.search.active = false;
|
||||
let _ = self.text.set_search_pattern("");
|
||||
}
|
||||
|
||||
pub fn search_next(&mut self) -> bool {
|
||||
if self.search.query.is_empty() {
|
||||
return false;
|
||||
}
|
||||
self.text.search_forward(false)
|
||||
}
|
||||
|
||||
pub fn search_prev(&mut self) -> bool {
|
||||
if self.search.query.is_empty() {
|
||||
return false;
|
||||
}
|
||||
self.text.search_back(false)
|
||||
}
|
||||
|
||||
fn apply_search_pattern(&mut self) {
|
||||
if self.search.query.is_empty() {
|
||||
let _ = self.text.set_search_pattern("");
|
||||
} else {
|
||||
let pattern = format!("(?i){}", regex::escape(&self.search.query));
|
||||
let _ = self.text.set_search_pattern(&pattern);
|
||||
}
|
||||
}
|
||||
|
||||
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;
|
||||
|
||||
28
src/input.rs
28
src/input.rs
@@ -385,10 +385,23 @@ fn handle_modal_input(ctx: &mut InputContext, key: KeyEvent) -> InputResult {
|
||||
},
|
||||
Modal::Editor => {
|
||||
let ctrl = key.modifiers.contains(KeyModifiers::CONTROL);
|
||||
let editor = &mut ctx.app.editor_ctx.editor;
|
||||
|
||||
if editor.search_active() {
|
||||
match key.code {
|
||||
KeyCode::Esc => editor.search_clear(),
|
||||
KeyCode::Enter => editor.search_confirm(),
|
||||
KeyCode::Backspace => editor.search_backspace(),
|
||||
KeyCode::Char(c) if !ctrl => editor.search_input(c),
|
||||
_ => {}
|
||||
}
|
||||
return InputResult::Continue;
|
||||
}
|
||||
|
||||
match key.code {
|
||||
KeyCode::Esc => {
|
||||
if ctx.app.editor_ctx.editor.completion_active() {
|
||||
ctx.app.editor_ctx.editor.dismiss_completion();
|
||||
if editor.completion_active() {
|
||||
editor.dismiss_completion();
|
||||
} else {
|
||||
ctx.dispatch(AppCommand::SaveEditorToStep);
|
||||
ctx.dispatch(AppCommand::CompileCurrentStep);
|
||||
@@ -399,8 +412,17 @@ fn handle_modal_input(ctx: &mut InputContext, key: KeyEvent) -> InputResult {
|
||||
ctx.dispatch(AppCommand::SaveEditorToStep);
|
||||
ctx.dispatch(AppCommand::CompileCurrentStep);
|
||||
}
|
||||
KeyCode::Char('f') if ctrl => {
|
||||
editor.activate_search();
|
||||
}
|
||||
KeyCode::Char('n') if ctrl => {
|
||||
editor.search_next();
|
||||
}
|
||||
KeyCode::Char('p') if ctrl => {
|
||||
editor.search_prev();
|
||||
}
|
||||
_ => {
|
||||
ctx.app.editor_ctx.editor.input(Event::Key(key));
|
||||
editor.input(Event::Key(key));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -570,8 +570,30 @@ fn render_modal(frame: &mut Frame, app: &App, snapshot: &SequencerSnapshot, term
|
||||
highlight::highlight_line_with_runtime(line, &exec, &sel)
|
||||
};
|
||||
|
||||
let show_search = app.editor_ctx.editor.search_active()
|
||||
|| !app.editor_ctx.editor.search_query().is_empty();
|
||||
|
||||
let (search_area, editor_area, hint_area) = if show_search {
|
||||
let search_area = Rect::new(inner.x, inner.y, inner.width, 1);
|
||||
let editor_area = Rect::new(inner.x, inner.y + 1, inner.width, inner.height.saturating_sub(2));
|
||||
let hint_area = Rect::new(inner.x, inner.y + 1 + editor_area.height, inner.width, 1);
|
||||
(Some(search_area), editor_area, hint_area)
|
||||
} else {
|
||||
let editor_area = Rect::new(inner.x, inner.y, inner.width, inner.height.saturating_sub(1));
|
||||
let hint_area = Rect::new(inner.x, inner.y + editor_area.height, inner.width, 1);
|
||||
(None, editor_area, hint_area)
|
||||
};
|
||||
|
||||
if let Some(sa) = search_area {
|
||||
let style = if app.editor_ctx.editor.search_active() {
|
||||
Style::default().fg(Color::Yellow)
|
||||
} else {
|
||||
Style::default().fg(Color::DarkGray)
|
||||
};
|
||||
let cursor = if app.editor_ctx.editor.search_active() { "_" } else { "" };
|
||||
let text = format!("/{}{}", app.editor_ctx.editor.search_query(), cursor);
|
||||
frame.render_widget(Paragraph::new(Line::from(Span::styled(text, style))), sa);
|
||||
}
|
||||
|
||||
if let Some(kind) = flash_kind {
|
||||
let bg = match kind {
|
||||
@@ -586,17 +608,22 @@ fn render_modal(frame: &mut Frame, app: &App, snapshot: &SequencerSnapshot, term
|
||||
|
||||
let dim = Style::default().fg(Color::DarkGray);
|
||||
let key = Style::default().fg(Color::Yellow);
|
||||
let hint = Line::from(vec![
|
||||
let hint = if app.editor_ctx.editor.search_active() {
|
||||
Line::from(vec![
|
||||
Span::styled("Enter", key), Span::styled(" confirm ", dim),
|
||||
Span::styled("Esc", key), Span::styled(" cancel", dim),
|
||||
])
|
||||
} else {
|
||||
Line::from(vec![
|
||||
Span::styled("Esc", key), Span::styled(" save ", dim),
|
||||
Span::styled("C-e", key), Span::styled(" eval ", dim),
|
||||
Span::styled("C-f", key), Span::styled(" find ", dim),
|
||||
Span::styled("C-n", key), Span::styled("/", dim),
|
||||
Span::styled("C-p", key), Span::styled(" next/prev ", dim),
|
||||
Span::styled("C-u", key), Span::styled("/", dim),
|
||||
Span::styled("C-r", key), Span::styled(" undo/redo ", dim),
|
||||
Span::styled("C-j", key), Span::styled("/", dim),
|
||||
Span::styled("C-k", key), Span::styled(" del-bol/eol ", dim),
|
||||
Span::styled("C-x", key), Span::styled("/", dim),
|
||||
Span::styled("C-c", key), Span::styled("/", dim),
|
||||
Span::styled("C-y", key), Span::styled(" cut/copy/paste ", dim),
|
||||
]);
|
||||
Span::styled("C-r", key), Span::styled(" undo/redo", dim),
|
||||
])
|
||||
};
|
||||
frame.render_widget(Paragraph::new(hint).alignment(Alignment::Right), hint_area);
|
||||
}
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user