mod insert;
mod prompt;
mod visual;
use anyhow::Result;
use crossterm::event::{KeyCode, KeyEvent, KeyModifiers};
use crate::action::PromptKind;
use crate::finder::FuzzyKind;
use crate::mode::Mode;
use crate::buffer_ref::BufferRef;
use super::{App, eval};
impl App {
pub fn handle_key(&mut self, key: KeyEvent) -> Result<()> {
if self.prompt.is_open() {
return self.handle_prompt_key(key);
}
if self.jump_state.is_some() {
self.handle_jump_key(key);
return Ok(());
}
if key.modifiers.contains(KeyModifiers::CONTROL) && key.code == KeyCode::Char('c') {
self.should_quit = true;
return Ok(());
}
if matches!(self.mode, Mode::Normal)
&& key.code == KeyCode::Esc
&& self.toast.level() == crate::app::Level::Error
&& !self.toast.text().is_empty()
{
self.clear_toast();
return Ok(());
}
match self.mode {
Mode::Insert => return self.handle_insert_key(key),
Mode::Visual | Mode::VisualLine | Mode::VisualBlock => {
return self.handle_visual_key(key);
}
Mode::Normal => {}
}
match eval::tokenize(&self.config.keymap, &self.tokens, self.mode, key) {
Some(t) => self.tokens.push(t),
None => {
self.tokens.clear();
return Ok(());
}
}
match eval::classify(&self.tokens) {
eval::Parse::Complete(expr) => {
self.tokens.clear();
self.evaluate(expr, crate::action::Ctx::default())?;
}
eval::Parse::Incomplete => {}
eval::Parse::Invalid => self.tokens.clear(),
}
Ok(())
}
pub(in crate::app) fn enter_mode(&mut self, mode: Mode) {
if mode.is_visual() && !self.mode.is_visual() {
self.visual_anchor = Some(self.buffer.cursor);
} else if !mode.is_visual() {
self.visual_anchor = None;
}
if mode == Mode::Normal {
self.buffer.clamp_col(false);
}
self.mode = mode;
}
pub(in crate::app) fn open_prompt(&mut self, kind: PromptKind) {
match kind {
PromptKind::Command => self.prompt.open_command(),
PromptKind::Search { forward } => self.prompt.open_search(forward),
PromptKind::Fuzzy(FuzzyKind::Files { ignore }) => {
self.prompt.open_files(&self.startup_cwd, ignore)
}
PromptKind::Fuzzy(FuzzyKind::Lines) => self.prompt.open_lines(&self.buffer.lines),
PromptKind::Fuzzy(FuzzyKind::Buffers) => self.open_buffer_picker(),
PromptKind::Fuzzy(FuzzyKind::Locations) => {}
}
}
fn open_buffer_picker(&mut self) {
let cwd = &self.startup_cwd;
let current_path = self
.buffer
.path
.as_ref()
.and_then(|p| p.canonicalize().ok());
let on_scratch = self.buffer.path.is_none();
let active_dirty = self.buffer.dirty;
let active_vcs_changed = self.buffer.has_vcs_changes();
let vcs_set = crate::vcs::changed_files(cwd);
let (items, refs): (Vec<_>, Vec<_>) = self
.opened_paths
.iter()
.rev() .map(|r| {
let (label, is_current) = match r {
BufferRef::Scratch => ("[scratch]".to_string(), on_scratch),
BufferRef::File(p) => {
let rel = p
.strip_prefix(cwd)
.unwrap_or(p)
.to_string_lossy()
.to_string();
let is_current = current_path.as_ref() == Some(p);
(rel, is_current)
}
};
let entry_dirty = if is_current {
active_dirty
} else {
self.sleeping.get(r).is_some_and(|b| b.dirty)
};
let entry_vcs = match r {
BufferRef::Scratch => false,
BufferRef::File(p) => {
if is_current {
active_vcs_changed
} else {
vcs_set.contains(p) || entry_dirty
}
}
};
let cur_col = if is_current { '%' } else { ' ' };
let vcs_col = if entry_vcs { '~' } else { ' ' };
let mod_col = if entry_dirty { '+' } else { ' ' };
let display = format!("{}{}{} {}", cur_col, vcs_col, mod_col, label);
(display, r.clone())
})
.unzip();
self.prompt.open_buffers(items, refs);
}
}