Added SelectToken action

This commit is contained in:
Joshua Barretto 2025-06-16 00:29:51 +01:00
parent 3e3755c0b5
commit 8bd6a70968
4 changed files with 87 additions and 18 deletions

View file

@ -32,6 +32,7 @@ pub enum Action {
OpenFile(PathBuf), // Open the file and switch the current pane to it
CommandStart(&'static str), // Start a new command
GotoLine(isize), // Go to the specified file line
SelectToken, // Fully select the token under the cursor
}
#[derive(Debug)]
@ -174,6 +175,22 @@ impl RawEvent {
Some(Action::Move(dir, page, retain_base))
}
pub fn to_select_token(&self) -> Option<Action> {
if matches!(
&self.0,
TerminalEvent::Key(KeyEvent {
code: KeyCode::Char(' '),
modifiers: KeyModifiers::CONTROL,
kind: KeyEventKind::Press,
..
})
) {
Some(Action::SelectToken)
} else {
None
}
}
pub fn to_open_prompt(&self) -> Option<Action> {
if matches!(
&self.0,

View file

@ -85,8 +85,12 @@ impl Highlighter {
pub fn rust() -> Self {
Self::new_from_regex([
(TokenKind::Doc, r"\/\/[\/!][^\n]*$"),
(TokenKind::Comment, r"\/\/[^$]*$"),
// Both kinds of comments match multiple lines
(
TokenKind::Doc,
r"\/\/[\/!][^\n]*$(\n[[:space:]]\/\/[\/!][^\n]*$)*",
),
(TokenKind::Comment, r"\/\/[^$]*$(\n[[:space:]]\/\/[^$]*$)*"),
// Multi-line comment
(TokenKind::Comment, r"\/\*[^(\*\/)]*\*\/"),
(
@ -105,7 +109,7 @@ impl Highlighter {
// Primitives
(
TokenKind::Type,
r"\b[(u8)(u16)(u32)(u64)(u128)(i8)(i16)(i32)(i64)(i128)(usize)(isize)(bool)(str)(char)]\b",
r"\b[(u8)(u16)(u32)(u64)(u128)(i8)(i16)(i32)(i64)(i128)(usize)(isize)(bool)(str)(char)(f16)(f32)(f64)(f128)]\b",
),
// "foo" or b"foo" or r#"foo"#
(TokenKind::String, r#"b?r?(#*)@("[(\\")[^("~)]]*("~))"#),
@ -132,7 +136,7 @@ impl Highlighter {
])
}
fn highlight_str(&self, mut s: &[char]) -> Vec<(Range<usize>, TokenKind)> {
fn highlight_str(&self, mut s: &[char]) -> Vec<Token> {
let mut tokens = Vec::new();
let mut i = 0;
loop {
@ -142,7 +146,10 @@ impl Highlighter {
.enumerate()
.find_map(|(i, r)| Some((i, r.matches(s)?)))
{
tokens.push((i..i + n, self.entries[idx]));
tokens.push(Token {
kind: self.entries[idx],
range: i..i + n,
});
n
} else if !s.is_empty() {
1
@ -166,21 +173,31 @@ impl Highlighter {
pub struct Highlights {
pub highlighter: Highlighter,
tokens: Vec<(Range<usize>, TokenKind)>,
tokens: Vec<Token>,
}
#[derive(Clone)]
pub struct Token {
pub kind: TokenKind,
pub range: Range<usize>,
}
impl Highlights {
pub fn insert(&mut self, at: usize, s: &str) {}
pub fn get_at(&self, pos: usize) -> Option<TokenKind> {
pub fn get_at(&self, pos: usize) -> Option<&Token> {
let idx = self.tokens
.binary_search_by_key(&pos, |(r, _)| r.start)
.binary_search_by_key(&pos, |tok| tok.range.start)
// .ok()?
.unwrap_or_else(|p| p.saturating_sub(1))
// .saturating_sub(1)
;
let (r, tok) = self.tokens.get(idx)?;
if r.contains(&pos) { Some(*tok) } else { None }
let tok = self.tokens.get(idx)?;
if tok.range.contains(&pos) {
Some(tok)
} else {
None
}
}
}
@ -279,16 +296,12 @@ impl State<'_> {
let mut times = 0;
loop {
let pos = self.pos;
if times >= *at_most {
break;
} else if self.attempt(x).is_none() {
break;
if times >= *at_most || self.attempt(x).is_none() {
break (times >= *at_least).then_some(());
}
assert_ne!(pos, self.pos, "{x:?}");
times += 1;
}
if times >= *at_least { Some(()) } else { None }
}
Regex::Delim(d, r) => {
let old_pos = self.pos;

View file

@ -185,6 +185,36 @@ impl Buffer {
cursor.base = cursor.pos;
}
pub fn select_token_cursor(&mut self, cursor_id: CursorId) {
let Some(cursor) = self.cursors.get_mut(cursor_id) else {
return;
};
if let Some(tok) = self
.highlights
.as_ref()
// Choose the longest token that the cursor is touching
.and_then(|hl| {
let a = hl.get_at(cursor.pos);
let b = hl.get_at(cursor.pos.saturating_sub(1));
a.zip(b)
.map(|(a, b)| {
if a.range.end - a.range.start > b.range.end - b.range.start {
a
} else {
b
}
})
.or(a)
.or(b)
})
{
cursor.base = tok.range.start;
cursor.pos = tok.range.end;
} else {
// TODO: Bell
}
}
pub fn move_cursor(
&mut self,
cursor_id: CursorId,

View file

@ -55,7 +55,12 @@ impl Input {
cursor_id: CursorId,
event: Event,
) -> Result<Resp, Event> {
match event.to_action(|e| e.to_char().map(Action::Char).or_else(|| e.to_move())) {
match event.to_action(|e| {
e.to_char()
.map(Action::Char)
.or_else(|| e.to_move())
.or_else(|| e.to_select_token())
}) {
Some(Action::Char(c)) => {
if c == '\x08' {
buffer.backspace(cursor_id);
@ -82,6 +87,10 @@ impl Input {
self.refocus(buffer, cursor_id);
Ok(Resp::handled(None))
}
Some(Action::SelectToken) => {
buffer.select_token_cursor(cursor_id);
Ok(Resp::handled(None))
}
_ => Err(event),
}
}
@ -165,7 +174,7 @@ impl Input {
.highlights
.as_ref()
.and_then(|hl| hl.get_at(pos))
.map(|tok| state.theme.token_color(tok))
.map(|tok| state.theme.token_color(tok.kind))
{
(fg, c)
} else {