Better newline behaviour
This commit is contained in:
parent
551b2816b3
commit
0912450513
4 changed files with 96 additions and 4 deletions
|
|
@ -34,6 +34,7 @@ pub enum Action {
|
||||||
CommandStart(&'static str), // Start a new command
|
CommandStart(&'static str), // Start a new command
|
||||||
GotoLine(isize), // Go to the specified file line
|
GotoLine(isize), // Go to the specified file line
|
||||||
SelectToken, // Fully select the token under the cursor
|
SelectToken, // Fully select the token under the cursor
|
||||||
|
SelectAll, // Fully select the entire input
|
||||||
Save, // Save the current buffer
|
Save, // Save the current buffer
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -190,6 +191,22 @@ impl RawEvent {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
pub fn to_select_all(&self) -> Option<Action> {
|
||||||
|
if matches!(
|
||||||
|
&self.0,
|
||||||
|
TerminalEvent::Key(KeyEvent {
|
||||||
|
code: KeyCode::Char('a'),
|
||||||
|
modifiers: KeyModifiers::CONTROL,
|
||||||
|
kind: KeyEventKind::Press,
|
||||||
|
..
|
||||||
|
})
|
||||||
|
) {
|
||||||
|
Some(Action::SelectAll)
|
||||||
|
} else {
|
||||||
|
None
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
pub fn to_indent(&self) -> Option<Action> {
|
pub fn to_indent(&self) -> Option<Action> {
|
||||||
if let TerminalEvent::Key(KeyEvent {
|
if let TerminalEvent::Key(KeyEvent {
|
||||||
code: c @ (KeyCode::Tab | KeyCode::BackTab),
|
code: c @ (KeyCode::Tab | KeyCode::BackTab),
|
||||||
|
|
|
||||||
61
src/state.rs
61
src/state.rs
|
|
@ -241,6 +241,14 @@ impl Buffer {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
pub fn select_all_cursor(&mut self, cursor_id: CursorId) {
|
||||||
|
let Some(cursor) = self.cursors.get_mut(cursor_id) else {
|
||||||
|
return;
|
||||||
|
};
|
||||||
|
cursor.base = 0;
|
||||||
|
cursor.pos = self.text.chars().len();
|
||||||
|
}
|
||||||
|
|
||||||
fn indent_at(&mut self, mut pos: usize, forward: bool) {
|
fn indent_at(&mut self, mut pos: usize, forward: bool) {
|
||||||
const TAB_ALIGN: usize = 4;
|
const TAB_ALIGN: usize = 4;
|
||||||
|
|
||||||
|
|
@ -385,6 +393,18 @@ impl Buffer {
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
|
pub fn insert_after(&mut self, cursor_id: CursorId, chars: impl IntoIterator<Item = char>) {
|
||||||
|
let Some(cursor) = self.cursors.get(cursor_id) else {
|
||||||
|
return;
|
||||||
|
};
|
||||||
|
let old_cursor = *cursor;
|
||||||
|
self.insert(old_cursor.pos, chars);
|
||||||
|
let Some(cursor) = self.cursors.get_mut(cursor_id) else {
|
||||||
|
return;
|
||||||
|
};
|
||||||
|
*cursor = old_cursor;
|
||||||
|
}
|
||||||
|
|
||||||
pub fn enter(&mut self, cursor_id: CursorId, chars: impl IntoIterator<Item = char>) {
|
pub fn enter(&mut self, cursor_id: CursorId, chars: impl IntoIterator<Item = char>) {
|
||||||
let Some(cursor) = self.cursors.get(cursor_id) else {
|
let Some(cursor) = self.cursors.get(cursor_id) else {
|
||||||
return;
|
return;
|
||||||
|
|
@ -448,6 +468,47 @@ impl Buffer {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
pub fn newline(&mut self, cursor_id: CursorId) {
|
||||||
|
let Some(cursor) = self.cursors.get(cursor_id) else {
|
||||||
|
return;
|
||||||
|
};
|
||||||
|
let line_start = self.text.to_pos([0, self.text.to_coord(cursor.pos)[1]]);
|
||||||
|
let is_block = if let Some(last_pos) = cursor
|
||||||
|
.selection()
|
||||||
|
.map_or(cursor.pos, |s| s.start)
|
||||||
|
.checked_sub(1)
|
||||||
|
&& let Some(last_char) = self.text.chars().get(last_pos)
|
||||||
|
&& let Some((l, r)) = [('(', ')'), ('[', ']'), ('{', '}')]
|
||||||
|
.iter()
|
||||||
|
.find(|(l, _)| l == last_char)
|
||||||
|
&& let next_pos = cursor.selection().map_or(cursor.pos, |s| s.end)
|
||||||
|
&& let next_char = self.text.chars().get(next_pos)
|
||||||
|
{
|
||||||
|
Some((*r, next_char == Some(r)))
|
||||||
|
} else {
|
||||||
|
None
|
||||||
|
};
|
||||||
|
|
||||||
|
self.enter(cursor_id, ['\n']);
|
||||||
|
|
||||||
|
// Indent to same level as last line
|
||||||
|
if let Some(chars) = self.text.chars().get(line_start..) {
|
||||||
|
let indent = chars
|
||||||
|
.iter()
|
||||||
|
.take_while(|c| [' ', '\t'].contains(c))
|
||||||
|
.copied()
|
||||||
|
.collect::<Vec<_>>();
|
||||||
|
self.enter(cursor_id, indent.iter().copied());
|
||||||
|
// If the last character was the start of a block, perform an additional indent
|
||||||
|
if let Some((r, is_complete)) = is_block {
|
||||||
|
self.indent(cursor_id, true);
|
||||||
|
// If the block was not already completed, complete it (TODO: make configurable!)
|
||||||
|
let tail = if !is_complete { Some(r) } else { None };
|
||||||
|
self.insert_after(cursor_id, core::iter::once('\n').chain(indent).chain(tail));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
pub fn start_session(&mut self) -> CursorId {
|
pub fn start_session(&mut self) -> CursorId {
|
||||||
self.cursors.insert(Cursor::default())
|
self.cursors.insert(Cursor::default())
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -62,6 +62,7 @@ impl Input {
|
||||||
.map(Action::Char)
|
.map(Action::Char)
|
||||||
.or_else(|| e.to_move())
|
.or_else(|| e.to_move())
|
||||||
.or_else(|| e.to_select_token())
|
.or_else(|| e.to_select_token())
|
||||||
|
.or_else(|| e.to_select_all())
|
||||||
.or_else(|| e.to_indent())
|
.or_else(|| e.to_indent())
|
||||||
}) {
|
}) {
|
||||||
Some(Action::Char(c)) => {
|
Some(Action::Char(c)) => {
|
||||||
|
|
@ -69,6 +70,8 @@ impl Input {
|
||||||
buffer.backspace(cursor_id);
|
buffer.backspace(cursor_id);
|
||||||
} else if c == '\x7F' {
|
} else if c == '\x7F' {
|
||||||
buffer.delete(cursor_id);
|
buffer.delete(cursor_id);
|
||||||
|
} else if c == '\n' {
|
||||||
|
buffer.newline(cursor_id);
|
||||||
} else {
|
} else {
|
||||||
buffer.enter(cursor_id, [c]);
|
buffer.enter(cursor_id, [c]);
|
||||||
}
|
}
|
||||||
|
|
@ -98,6 +101,10 @@ impl Input {
|
||||||
buffer.select_token_cursor(cursor_id);
|
buffer.select_token_cursor(cursor_id);
|
||||||
Ok(Resp::handled(None))
|
Ok(Resp::handled(None))
|
||||||
}
|
}
|
||||||
|
Some(Action::SelectAll) => {
|
||||||
|
buffer.select_all_cursor(cursor_id);
|
||||||
|
Ok(Resp::handled(None))
|
||||||
|
}
|
||||||
_ => Err(event),
|
_ => Err(event),
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -103,10 +103,17 @@ impl Element<()> for Root {
|
||||||
self.tasks
|
self.tasks
|
||||||
.push(Task::Prompt(Prompt::new(&format!("{cmd} "))));
|
.push(Task::Prompt(Prompt::new(&format!("{cmd} "))));
|
||||||
}
|
}
|
||||||
Action::Cancel => self.tasks.push(Task::Confirm(Confirm {
|
Action::Cancel => {
|
||||||
label: Label("Are you sure you wish to quit? (y/n)".to_string()),
|
let unsaved = state.buffers.values().filter(|b| b.unsaved).count();
|
||||||
action: Action::Quit,
|
if unsaved == 0 {
|
||||||
})),
|
return Ok(Resp::end(None));
|
||||||
|
} else {
|
||||||
|
self.tasks.push(Task::Confirm(Confirm {
|
||||||
|
label: Label(format!("Are you sure you wish to quit? (y/n). Note that {} files are unsaved!", unsaved)),
|
||||||
|
action: Action::Quit,
|
||||||
|
}));
|
||||||
|
}
|
||||||
|
}
|
||||||
Action::Show(title, text) => self.tasks.push(Task::Show(Show {
|
Action::Show(title, text) => self.tasks.push(Task::Show(Show {
|
||||||
title,
|
title,
|
||||||
label: Label(text),
|
label: Label(text),
|
||||||
|
|
|
||||||
Loading…
Add table
Reference in a new issue