use ratatui::crossterm::event::{KeyCode, KeyEvent, KeyModifiers};
use super::app::App;
use super::editor::Mode;
use crate::lang::{LineKind, ParsedLine, is_ident_continue, is_ident_start};
const SUM: &str = "sum";
pub fn candidates(parsed: &[ParsedLine], cursor_line: usize) -> Vec<String> {
let end = cursor_line.min(parsed.len());
let mut out: Vec<String> = Vec::new();
for line in parsed[..end].iter().rev() {
if let LineKind::Assignment { name, .. } = &line.kind
&& !name.is_empty()
&& name != SUM
&& !out.contains(name)
{
out.push(name.clone());
}
}
out.push(SUM.to_string());
out
}
pub fn prefix(line: &str, cursor_col: usize) -> Option<(String, usize)> {
let chars: Vec<char> = line.chars().collect();
let col = cursor_col.min(chars.len());
if chars[..col].contains(&'#') {
return None;
}
if let Some(eq) = chars.iter().position(|&c| c == '=')
&& col <= eq
{
return None;
}
if chars.get(col).copied().is_some_and(is_ident_continue) {
return None;
}
let mut start = col;
while start > 0 && is_ident_continue(chars[start - 1]) {
start -= 1;
}
if start == col || !is_ident_start(chars[start]) {
return None;
}
Some((chars[start..col].iter().collect(), start))
}
#[derive(Debug, Clone, PartialEq, Eq)]
pub struct Completion {
pub matches: Vec<String>,
pub selected: usize,
pub prefix: String,
pub start: usize,
}
impl Completion {
pub fn refresh(parsed: &[ParsedLine], line: &str, row: usize, col: usize) -> Option<Self> {
let (prefix, start) = prefix(line, col)?;
let matches: Vec<String> = candidates(parsed, row)
.into_iter()
.filter(|c| c.len() > prefix.len() && c.starts_with(&prefix))
.collect();
if matches.is_empty() {
return None;
}
Some(Self {
matches,
selected: 0,
prefix,
start,
})
}
pub fn current(&self) -> &str {
&self.matches[self.selected]
}
pub fn cycle(&mut self, delta: isize) {
let n = self.matches.len() as isize;
self.selected = (self.selected as isize + delta).rem_euclid(n) as usize;
}
pub fn ghost_text(&self) -> &str {
&self.current()[self.prefix.len()..]
}
}
impl App {
pub(super) fn refresh_completion(&mut self) {
self.completion = if self.mode() == Mode::Insert {
let (row, col) = self.cursor();
Completion::refresh(&self.parsed, self.current_line(), row, col)
} else {
None
};
}
pub(super) fn accept_completion(&mut self) {
let Some(c) = self.completion.take() else {
return;
};
for ch in c.ghost_text().chars() {
let mods = if ch.is_uppercase() {
KeyModifiers::SHIFT
} else {
KeyModifiers::NONE
};
self.editor.send(KeyEvent::new(KeyCode::Char(ch), mods));
}
}
pub(super) fn cycle_completion(&mut self, delta: isize) {
if let Some(c) = &mut self.completion {
c.cycle(delta);
}
}
}