Added word movement support
This commit is contained in:
parent
bc2cff34d4
commit
63c420c65b
6 changed files with 86 additions and 51 deletions
|
|
@ -14,7 +14,7 @@ pub enum Dir {
|
||||||
pub enum Action {
|
pub enum Action {
|
||||||
Char(char), // Insert a character
|
Char(char), // Insert a character
|
||||||
Indent(bool), // Indent (indent vs deindent)
|
Indent(bool), // Indent (indent vs deindent)
|
||||||
Move(Dir, bool, bool), // Move the cursor (dir, page, retain_base)
|
Move(Dir, bool, bool, bool), // Move the cursor (dir, page, retain_base, word)
|
||||||
PaneMove(Dir), // Move panes
|
PaneMove(Dir), // Move panes
|
||||||
PaneOpen(Dir), // Create a new pane
|
PaneOpen(Dir), // Create a new pane
|
||||||
PaneClose, // Close the current pane
|
PaneClose, // Close the current pane
|
||||||
|
|
@ -158,11 +158,8 @@ impl RawEvent {
|
||||||
return None;
|
return None;
|
||||||
};
|
};
|
||||||
|
|
||||||
let retain_base = match *modifiers {
|
let retain_base = modifiers.contains(KeyModifiers::SHIFT);
|
||||||
KeyModifiers::NONE => false,
|
let word = modifiers.contains(KeyModifiers::CONTROL);
|
||||||
KeyModifiers::SHIFT => true,
|
|
||||||
_ => return None,
|
|
||||||
};
|
|
||||||
|
|
||||||
let (dir, page) = match code {
|
let (dir, page) = match code {
|
||||||
KeyCode::PageUp => (Dir::Up, true),
|
KeyCode::PageUp => (Dir::Up, true),
|
||||||
|
|
@ -174,7 +171,7 @@ impl RawEvent {
|
||||||
_ => return None,
|
_ => return None,
|
||||||
};
|
};
|
||||||
|
|
||||||
Some(Action::Move(dir, page, retain_base))
|
Some(Action::Move(dir, page, retain_base, word))
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn to_select_token(&self) -> Option<Action> {
|
pub fn to_select_token(&self) -> Option<Action> {
|
||||||
|
|
|
||||||
29
src/state.rs
29
src/state.rs
|
|
@ -267,6 +267,7 @@ impl Buffer {
|
||||||
dir: Dir,
|
dir: Dir,
|
||||||
dist: [usize; 2],
|
dist: [usize; 2],
|
||||||
retain_base: bool,
|
retain_base: bool,
|
||||||
|
word: bool,
|
||||||
) {
|
) {
|
||||||
let Some(cursor) = self.cursors.get_mut(cursor_id) else {
|
let Some(cursor) = self.cursors.get_mut(cursor_id) else {
|
||||||
return;
|
return;
|
||||||
|
|
@ -275,6 +276,14 @@ impl Buffer {
|
||||||
Dir::Left => {
|
Dir::Left => {
|
||||||
cursor.pos = if !retain_base && cursor.base < cursor.pos {
|
cursor.pos = if !retain_base && cursor.base < cursor.pos {
|
||||||
cursor.base
|
cursor.base
|
||||||
|
} else if let (true, Some(mut pos)) = (word, cursor.pos.checked_sub(1)) {
|
||||||
|
let class = self.text.chars().get(pos).copied().map(classify);
|
||||||
|
loop {
|
||||||
|
pos = match pos.checked_sub(1) {
|
||||||
|
Some(pos) if self.text.chars().get(pos).copied().map(classify) == class => pos,
|
||||||
|
_ => break pos,
|
||||||
|
}
|
||||||
|
}
|
||||||
} else {
|
} else {
|
||||||
cursor.pos.saturating_sub(dist[0])
|
cursor.pos.saturating_sub(dist[0])
|
||||||
};
|
};
|
||||||
|
|
@ -283,6 +292,16 @@ impl Buffer {
|
||||||
Dir::Right => {
|
Dir::Right => {
|
||||||
cursor.pos = if !retain_base && cursor.base > cursor.pos {
|
cursor.pos = if !retain_base && cursor.base > cursor.pos {
|
||||||
cursor.base
|
cursor.base
|
||||||
|
} else if word {
|
||||||
|
let mut pos = cursor.pos;
|
||||||
|
let class = self.text.chars().get(pos).copied().map(classify);
|
||||||
|
loop {
|
||||||
|
pos = if self.text.chars().get(pos).copied().map(classify) == class {
|
||||||
|
pos + 1
|
||||||
|
} else {
|
||||||
|
break pos
|
||||||
|
};
|
||||||
|
}
|
||||||
} else {
|
} else {
|
||||||
(cursor.pos + dist[0]).min(self.text.chars.len())
|
(cursor.pos + dist[0]).min(self.text.chars.len())
|
||||||
};
|
};
|
||||||
|
|
@ -404,6 +423,16 @@ impl Buffer {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// CLassify the character by property
|
||||||
|
fn classify(c: char) -> u8 {
|
||||||
|
match c {
|
||||||
|
// c if c.is_ascii_whitespace() => 0,
|
||||||
|
c if c.is_alphanumeric() || c == '_' => 1,
|
||||||
|
_ => 2,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
pub struct State {
|
pub struct State {
|
||||||
pub buffers: HopSlotMap<BufferId, Buffer>,
|
pub buffers: HopSlotMap<BufferId, Buffer>,
|
||||||
pub tick: u64,
|
pub tick: u64,
|
||||||
|
|
|
||||||
|
|
@ -35,6 +35,7 @@ impl Default for BorderTheme {
|
||||||
pub struct Theme {
|
pub struct Theme {
|
||||||
pub ui_bg: Color,
|
pub ui_bg: Color,
|
||||||
pub select_bg: Color,
|
pub select_bg: Color,
|
||||||
|
pub line_select_bg: Color,
|
||||||
pub unfocus_select_bg: Color,
|
pub unfocus_select_bg: Color,
|
||||||
pub search_result_bg: Color,
|
pub search_result_bg: Color,
|
||||||
pub margin_bg: Color,
|
pub margin_bg: Color,
|
||||||
|
|
@ -69,8 +70,9 @@ impl Default for Theme {
|
||||||
Self {
|
Self {
|
||||||
ui_bg: Color::AnsiValue(235),
|
ui_bg: Color::AnsiValue(235),
|
||||||
select_bg: Color::AnsiValue(23),
|
select_bg: Color::AnsiValue(23),
|
||||||
|
line_select_bg: Color::AnsiValue(8),
|
||||||
unfocus_select_bg: Color::AnsiValue(240),
|
unfocus_select_bg: Color::AnsiValue(240),
|
||||||
search_result_bg: Color::AnsiValue(124),
|
search_result_bg: Color::AnsiValue(66),
|
||||||
margin_bg: Color::Reset,
|
margin_bg: Color::Reset,
|
||||||
margin_line_num: Color::AnsiValue(245),
|
margin_line_num: Color::AnsiValue(245),
|
||||||
border: BorderTheme::default(),
|
border: BorderTheme::default(),
|
||||||
|
|
|
||||||
|
|
@ -124,7 +124,7 @@ impl Visual for Doc {
|
||||||
[0, 0],
|
[0, 0],
|
||||||
[frame.size()[0], frame.size()[1].saturating_sub(search_h)],
|
[frame.size()[0], frame.size()[1].saturating_sub(search_h)],
|
||||||
)
|
)
|
||||||
.with_focus(self.search.is_none())
|
.with_focus(true/*self.search.is_none()*/)
|
||||||
.with(|f| {
|
.with(|f| {
|
||||||
self.input.render(
|
self.input.render(
|
||||||
state,
|
state,
|
||||||
|
|
@ -208,7 +208,7 @@ impl Search {
|
||||||
return Ok(Resp::end(None));
|
return Ok(Resp::end(None));
|
||||||
}
|
}
|
||||||
Some(Action::Go) => return Ok(Resp::end(None)),
|
Some(Action::Go) => return Ok(Resp::end(None)),
|
||||||
Some(Action::Move(dir, false, _)) => {
|
Some(Action::Move(dir, false, false, false)) => {
|
||||||
match dir {
|
match dir {
|
||||||
Dir::Up => {
|
Dir::Up => {
|
||||||
self.selected = (self.selected + self.results.len().saturating_sub(1))
|
self.selected = (self.selected + self.results.len().saturating_sub(1))
|
||||||
|
|
|
||||||
|
|
@ -75,13 +75,13 @@ impl Input {
|
||||||
self.refocus(buffer, cursor_id);
|
self.refocus(buffer, cursor_id);
|
||||||
Ok(Resp::handled(None))
|
Ok(Resp::handled(None))
|
||||||
}
|
}
|
||||||
Some(Action::Move(dir, page, retain_base)) => {
|
Some(Action::Move(dir, page, retain_base, word)) => {
|
||||||
let dist = if page {
|
let dist = if page {
|
||||||
self.last_size.map(|s| s.saturating_sub(3).max(1))
|
self.last_size.map(|s| s.saturating_sub(3).max(1))
|
||||||
} else {
|
} else {
|
||||||
[1, 1]
|
[1, 1]
|
||||||
};
|
};
|
||||||
buffer.move_cursor(cursor_id, dir, dist, retain_base);
|
buffer.move_cursor(cursor_id, dir, dist, retain_base, word);
|
||||||
self.refocus(buffer, cursor_id);
|
self.refocus(buffer, cursor_id);
|
||||||
Ok(Resp::handled(None))
|
Ok(Resp::handled(None))
|
||||||
}
|
}
|
||||||
|
|
@ -171,42 +171,49 @@ impl Input {
|
||||||
let mut frame = frame.rect([margin_w, i], [!0, 1]);
|
let mut frame = frame.rect([margin_w, i], [!0, 1]);
|
||||||
for i in 0..frame.size()[0] {
|
for i in 0..frame.size()[0] {
|
||||||
let coord = self.focus[0] + i as isize;
|
let coord = self.focus[0] + i as isize;
|
||||||
if (0..line.len() as isize).contains(&coord) {
|
let line_selected = (line_pos..line_pos + line.len()).contains(&cursor.pos);
|
||||||
let pos = line_pos + coord as usize;
|
let pos = if i < line.len() {
|
||||||
let selected = cursor.selection().map_or(false, |s| s.contains(&pos));
|
Some(line_pos + coord as usize)
|
||||||
let (fg, c) = match line[coord as usize] {
|
} else {
|
||||||
'\n' if selected => (state.theme.whitespace, '⮠'),
|
None
|
||||||
c => {
|
};
|
||||||
if let Some(fg) = buffer
|
let selected = cursor.selection().zip(pos).map_or(false, |(s, pos)| s.contains(&pos));
|
||||||
.highlights
|
let (fg, c) = match line.get(coord as usize).copied() {
|
||||||
.as_ref()
|
Some('\n') if selected => (state.theme.whitespace, '⮠'),
|
||||||
.and_then(|hl| hl.get_at(pos))
|
Some(c) => {
|
||||||
.map(|tok| state.theme.token_color(tok.kind))
|
if let Some(fg) = buffer
|
||||||
{
|
.highlights
|
||||||
(fg, c)
|
.as_ref()
|
||||||
} else {
|
.and_then(|hl| hl.get_at(pos?))
|
||||||
(state.theme.text, c)
|
.map(|tok| state.theme.token_color(tok.kind))
|
||||||
}
|
{
|
||||||
|
(fg, c)
|
||||||
|
} else {
|
||||||
|
(state.theme.text, c)
|
||||||
}
|
}
|
||||||
};
|
}
|
||||||
let bg = if let Some(s) = search {
|
None => (Color::Reset, ' '),
|
||||||
match s.contains(pos) {
|
};
|
||||||
Some(true) => state.theme.select_bg,
|
let bg = match search.map(|s| s.contains(pos?)) {
|
||||||
Some(false) => state.theme.search_result_bg,
|
Some(Some(true)) => state.theme.select_bg,
|
||||||
None => Color::Reset,
|
Some(Some(false)) => state.theme.search_result_bg,
|
||||||
|
Some(None) if line_selected && frame.has_focus() => state.theme.line_select_bg,
|
||||||
|
_ => if selected {
|
||||||
|
if frame.has_focus() {
|
||||||
|
state.theme.select_bg
|
||||||
|
} else {
|
||||||
|
state.theme.unfocus_select_bg
|
||||||
}
|
}
|
||||||
} else if !selected {
|
} else if line_selected && frame.has_focus() {
|
||||||
Color::Reset
|
state.theme.line_select_bg
|
||||||
} else if frame.has_focus() {
|
|
||||||
state.theme.select_bg
|
|
||||||
} else {
|
} else {
|
||||||
state.theme.unfocus_select_bg
|
Color::Reset
|
||||||
};
|
},
|
||||||
frame
|
};
|
||||||
.with_bg(bg)
|
frame
|
||||||
.with_fg(fg)
|
.with_bg(bg)
|
||||||
.text([i as isize, 0], c.encode_utf8(&mut [0; 4]));
|
.with_fg(fg)
|
||||||
}
|
.text([i as isize, 0], c.encode_utf8(&mut [0; 4]));
|
||||||
}
|
}
|
||||||
|
|
||||||
// Set cursor position
|
// Set cursor position
|
||||||
|
|
|
||||||
|
|
@ -149,12 +149,12 @@ impl<T> Options<T> {
|
||||||
impl<T: Clone> Element<T> for Options<T> {
|
impl<T: Clone> Element<T> for Options<T> {
|
||||||
fn handle(&mut self, state: &mut State, event: Event) -> Result<Resp<T>, Event> {
|
fn handle(&mut self, state: &mut State, event: Event) -> Result<Resp<T>, Event> {
|
||||||
match event.to_action(|e| e.to_go().or_else(|| e.to_move())) {
|
match event.to_action(|e| e.to_go().or_else(|| e.to_move())) {
|
||||||
Some(Action::Move(Dir::Up, false, _)) => {
|
Some(Action::Move(dir, false, false, false)) => {
|
||||||
self.selected = (self.selected + self.ranking.len() - 1) % self.ranking.len();
|
match dir {
|
||||||
Ok(Resp::handled(None))
|
Dir::Up => self.selected = (self.selected + self.ranking.len() - 1) % self.ranking.len(),
|
||||||
}
|
Dir::Down => self.selected = (self.selected + 1) % self.ranking.len(),
|
||||||
Some(Action::Move(Dir::Down, false, _)) => {
|
_ => return Err(event),
|
||||||
self.selected = (self.selected + 1) % self.ranking.len();
|
}
|
||||||
Ok(Resp::handled(None))
|
Ok(Resp::handled(None))
|
||||||
}
|
}
|
||||||
Some(Action::Go) => {
|
Some(Action::Go) => {
|
||||||
|
|
|
||||||
Loading…
Add table
Reference in a new issue