use std::ops::Range;
use super::{App, CommandChoice, CommandChoiceKind, ComposerMode};
#[derive(Clone, Debug, PartialEq, Eq)]
pub(super) struct SkillMentionCursor {
pub(super) token: Range<usize>,
pub(super) typed: String,
}
impl SkillMentionCursor {
pub(super) fn palette_identity(&self) -> String {
format!("${}", self.typed)
}
}
impl App {
pub(super) fn complete_skill_mention(&mut self, token: &Range<usize>, name: &str) {
let edit = complete_mention(self.input_ui.text(), token, name);
let buffer = self.input_ui.buffer_mut();
buffer.replace_range(
edit.replace,
super::composer_buffer::Fragment::plain(edit.inserted),
);
buffer.set_cursor(edit.cursor);
self.input_ui.cancel_pointer_click_sequence();
}
pub(super) fn skill_mention_cursor(&self) -> Option<SkillMentionCursor> {
if !matches!(self.input_ui.composer(), ComposerMode::Input)
|| self.input_ui.shell_mode().is_some()
|| self.input_ui.command_palette_dismissed()
{
return None;
}
mention_under_cursor(self.input_ui.text(), self.input_ui.cursor())
}
pub(super) fn skill_mention_choices(
&mut self,
cursor: &SkillMentionCursor,
) -> Vec<CommandChoice> {
self.discovered_skills()
.iter()
.filter(|skill| {
skill.name.starts_with(&cursor.typed)
&& crate::skills::mention::mentionable(&skill.name)
})
.map(|skill| CommandChoice {
name: skill.name.clone(),
usage: format!("${}", skill.name),
description: skill.description.clone(),
kind: CommandChoiceKind::SkillMention {
token: cursor.token.clone(),
},
})
.collect()
}
}
pub(super) fn mention_under_cursor(text: &str, cursor: usize) -> Option<SkillMentionCursor> {
let chars = text.chars().collect::<Vec<_>>();
let cursor = cursor.min(chars.len());
let start = chars[..cursor]
.iter()
.rposition(|ch| ch.is_whitespace())
.map_or(0, |index| index + 1);
let end = chars[cursor..]
.iter()
.position(|ch| ch.is_whitespace())
.map_or(chars.len(), |offset| cursor + offset);
if start == cursor || chars[start] != '$' {
return None;
}
let name = &chars[start + 1..end];
if !name
.iter()
.all(|ch| ch.is_ascii_alphanumeric() || matches!(ch, '-' | '_' | ':'))
{
return None;
}
Some(SkillMentionCursor {
token: start..end,
typed: chars[start + 1..cursor].iter().collect(),
})
}
#[derive(Debug, PartialEq, Eq)]
pub(super) struct MentionEdit {
pub(super) replace: Range<usize>,
pub(super) inserted: String,
pub(super) cursor: usize,
}
pub(super) fn complete_mention(text: &str, token: &Range<usize>, name: &str) -> MentionEdit {
let followed_by_space = text.chars().nth(token.end).is_some_and(char::is_whitespace);
let inserted = if followed_by_space {
format!("${name}")
} else {
format!("${name} ")
};
MentionEdit {
replace: token.clone(),
inserted,
cursor: token.start + 1 + name.chars().count() + 1,
}
}
#[cfg(test)]
#[path = "skill_mention_completion_tests.rs"]
mod tests;