mirror of https://github.com/helix-editor/helix
add 'overtype' edit mode
parent
dbaa636683
commit
d42b199ccc
|
@ -727,6 +727,13 @@ impl Transaction {
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// Replace text at each selection head.
|
||||||
|
pub fn replace(doc: &Rope, selection: &Selection, text: Tendril) -> Self {
|
||||||
|
Self::change_by_selection(doc, selection, |range| {
|
||||||
|
(range.from(), range.to(), Some(text.clone()))
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
pub fn changes_iter(&self) -> ChangeIterator {
|
pub fn changes_iter(&self) -> ChangeIterator {
|
||||||
self.changes.changes_iter()
|
self.changes.changes_iter()
|
||||||
}
|
}
|
||||||
|
|
|
@ -291,6 +291,7 @@ impl MappableCommand {
|
||||||
extend_prev_char, "Extend to previous occurrence of char",
|
extend_prev_char, "Extend to previous occurrence of char",
|
||||||
repeat_last_motion, "Repeat last motion",
|
repeat_last_motion, "Repeat last motion",
|
||||||
replace, "Replace with new char",
|
replace, "Replace with new char",
|
||||||
|
overtype_mode, "Enter overtype mode",
|
||||||
switch_case, "Switch (toggle) case",
|
switch_case, "Switch (toggle) case",
|
||||||
switch_to_uppercase, "Switch to uppercase",
|
switch_to_uppercase, "Switch to uppercase",
|
||||||
switch_to_lowercase, "Switch to lowercase",
|
switch_to_lowercase, "Switch to lowercase",
|
||||||
|
@ -2788,6 +2789,29 @@ fn insert_mode(cx: &mut Context) {
|
||||||
doc.set_selection(view.id, selection);
|
doc.set_selection(view.id, selection);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
fn enter_overtype_mode(cx: &mut Context) {
|
||||||
|
cx.editor.mode = Mode::Overtype;
|
||||||
|
}
|
||||||
|
|
||||||
|
// inserts at the start of each selection
|
||||||
|
fn overtype_mode(cx: &mut Context) {
|
||||||
|
enter_overtype_mode(cx);
|
||||||
|
let (view, doc) = current!(cx.editor);
|
||||||
|
|
||||||
|
log::trace!(
|
||||||
|
"entering replace mode with sel: {:?}, text: {:?}",
|
||||||
|
doc.selection(view.id),
|
||||||
|
doc.text().to_string()
|
||||||
|
);
|
||||||
|
|
||||||
|
let selection = doc
|
||||||
|
.selection(view.id)
|
||||||
|
.clone()
|
||||||
|
.transform(|range| Range::new(range.to(), range.from()));
|
||||||
|
|
||||||
|
doc.set_selection(view.id, selection);
|
||||||
|
}
|
||||||
|
|
||||||
// inserts at the end of each selection
|
// inserts at the end of each selection
|
||||||
fn append_mode(cx: &mut Context) {
|
fn append_mode(cx: &mut Context) {
|
||||||
enter_insert_mode(cx);
|
enter_insert_mode(cx);
|
||||||
|
@ -3746,7 +3770,7 @@ pub mod insert {
|
||||||
Some(transaction)
|
Some(transaction)
|
||||||
}
|
}
|
||||||
|
|
||||||
use helix_core::auto_pairs;
|
use helix_core::{auto_pairs, graphemes::nth_next_grapheme_boundary};
|
||||||
use helix_view::editor::SmartTabConfig;
|
use helix_view::editor::SmartTabConfig;
|
||||||
|
|
||||||
pub fn insert_char(cx: &mut Context, c: char) {
|
pub fn insert_char(cx: &mut Context, c: char) {
|
||||||
|
@ -3768,6 +3792,23 @@ pub mod insert {
|
||||||
helix_event::dispatch(PostInsertChar { c, cx });
|
helix_event::dispatch(PostInsertChar { c, cx });
|
||||||
}
|
}
|
||||||
|
|
||||||
|
pub fn replace_char(cx: &mut Context, c: char) {
|
||||||
|
let (view, doc) = current!(cx.editor);
|
||||||
|
let text = doc.text();
|
||||||
|
let selection = doc.selection(view.id);
|
||||||
|
let slice = text.slice(..);
|
||||||
|
let selection_update = selection.clone().transform(|range| {
|
||||||
|
let new_pos = nth_next_grapheme_boundary(slice, range.cursor(slice), 1);
|
||||||
|
range.put_cursor(slice, new_pos, false)
|
||||||
|
});
|
||||||
|
let mut t = Tendril::new();
|
||||||
|
t.push(c);
|
||||||
|
let transaction = Transaction::replace(text, &selection, t);
|
||||||
|
doc.apply(&transaction, view.id);
|
||||||
|
doc.append_changes_to_history(view);
|
||||||
|
doc.set_selection(view.id, selection_update);
|
||||||
|
}
|
||||||
|
|
||||||
pub fn smart_tab(cx: &mut Context) {
|
pub fn smart_tab(cx: &mut Context) {
|
||||||
let (view, doc) = current_ref!(cx.editor);
|
let (view, doc) = current_ref!(cx.editor);
|
||||||
let view_id = view.id;
|
let view_id = view.id;
|
||||||
|
@ -4276,7 +4317,7 @@ pub(crate) fn paste_bracketed_value(cx: &mut Context, contents: String) {
|
||||||
let count = cx.count();
|
let count = cx.count();
|
||||||
let paste = match cx.editor.mode {
|
let paste = match cx.editor.mode {
|
||||||
Mode::Insert | Mode::Select => Paste::Cursor,
|
Mode::Insert | Mode::Select => Paste::Cursor,
|
||||||
Mode::Normal => Paste::Before,
|
Mode::Normal | Mode::Overtype => Paste::Before,
|
||||||
};
|
};
|
||||||
let (view, doc) = current!(cx.editor);
|
let (view, doc) = current!(cx.editor);
|
||||||
paste_impl(&[contents], doc, view, paste, count, cx.editor.mode);
|
paste_impl(&[contents], doc, view, paste, count, cx.editor.mode);
|
||||||
|
|
|
@ -395,9 +395,13 @@ pub fn default() -> HashMap<Mode, KeyTrie> {
|
||||||
"home" => goto_line_start,
|
"home" => goto_line_start,
|
||||||
"end" => goto_line_end_newline,
|
"end" => goto_line_end_newline,
|
||||||
});
|
});
|
||||||
|
let overtype = keymap!({ "Overtype mode"
|
||||||
|
"esc" => normal_mode,
|
||||||
|
});
|
||||||
hashmap!(
|
hashmap!(
|
||||||
Mode::Normal => normal,
|
Mode::Normal => normal,
|
||||||
Mode::Select => select,
|
Mode::Select => select,
|
||||||
Mode::Insert => insert,
|
Mode::Insert => insert,
|
||||||
|
Mode::Overtype => overtype,
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
|
@ -488,6 +488,7 @@ impl EditorView {
|
||||||
|
|
||||||
let cursor_scope = match mode {
|
let cursor_scope = match mode {
|
||||||
Mode::Insert => theme.find_scope_index_exact("ui.cursor.insert"),
|
Mode::Insert => theme.find_scope_index_exact("ui.cursor.insert"),
|
||||||
|
Mode::Overtype => theme.find_scope_index_exact("ui.cursor.overtype"),
|
||||||
Mode::Select => theme.find_scope_index_exact("ui.cursor.select"),
|
Mode::Select => theme.find_scope_index_exact("ui.cursor.select"),
|
||||||
Mode::Normal => theme.find_scope_index_exact("ui.cursor.normal"),
|
Mode::Normal => theme.find_scope_index_exact("ui.cursor.normal"),
|
||||||
}
|
}
|
||||||
|
@ -495,6 +496,7 @@ impl EditorView {
|
||||||
|
|
||||||
let primary_cursor_scope = match mode {
|
let primary_cursor_scope = match mode {
|
||||||
Mode::Insert => theme.find_scope_index_exact("ui.cursor.primary.insert"),
|
Mode::Insert => theme.find_scope_index_exact("ui.cursor.primary.insert"),
|
||||||
|
Mode::Overtype => theme.find_scope_index_exact("ui.cursor.overtype"),
|
||||||
Mode::Select => theme.find_scope_index_exact("ui.cursor.primary.select"),
|
Mode::Select => theme.find_scope_index_exact("ui.cursor.primary.select"),
|
||||||
Mode::Normal => theme.find_scope_index_exact("ui.cursor.primary.normal"),
|
Mode::Normal => theme.find_scope_index_exact("ui.cursor.primary.normal"),
|
||||||
}
|
}
|
||||||
|
@ -908,6 +910,33 @@ impl EditorView {
|
||||||
None
|
None
|
||||||
}
|
}
|
||||||
|
|
||||||
|
fn overtype_mode(&mut self, cx: &mut commands::Context, event: KeyEvent) {
|
||||||
|
if let Some(keyresult) = self.handle_keymap_event(Mode::Overtype, cx, event) {
|
||||||
|
match keyresult {
|
||||||
|
KeymapResult::NotFound => {
|
||||||
|
if let Some(ch) = event.char() {
|
||||||
|
commands::insert::replace_char(cx, ch)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
KeymapResult::Cancelled(pending) => {
|
||||||
|
for ev in pending {
|
||||||
|
match ev.char() {
|
||||||
|
Some(ch) => commands::insert::replace_char(cx, ch),
|
||||||
|
None => {
|
||||||
|
if let KeymapResult::Matched(command) =
|
||||||
|
self.keymaps.get(Mode::Overtype, ev)
|
||||||
|
{
|
||||||
|
command.execute(cx);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
_ => unreachable!(),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
fn insert_mode(&mut self, cx: &mut commands::Context, event: KeyEvent) {
|
fn insert_mode(&mut self, cx: &mut commands::Context, event: KeyEvent) {
|
||||||
if let Some(keyresult) = self.handle_keymap_event(Mode::Insert, cx, event) {
|
if let Some(keyresult) = self.handle_keymap_event(Mode::Insert, cx, event) {
|
||||||
match keyresult {
|
match keyresult {
|
||||||
|
@ -1406,6 +1435,9 @@ impl Component for EditorView {
|
||||||
self.last_insert.1.push(InsertEvent::Key(key));
|
self.last_insert.1.push(InsertEvent::Key(key));
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
Mode::Overtype => {
|
||||||
|
self.overtype_mode(&mut cx, key);
|
||||||
|
}
|
||||||
mode => self.command_mode(mode, &mut cx, key),
|
mode => self.command_mode(mode, &mut cx, key),
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -180,6 +180,7 @@ where
|
||||||
if visible {
|
if visible {
|
||||||
match context.editor.mode() {
|
match context.editor.mode() {
|
||||||
Mode::Insert => &modenames.insert,
|
Mode::Insert => &modenames.insert,
|
||||||
|
Mode::Overtype => &modenames.overtype,
|
||||||
Mode::Select => &modenames.select,
|
Mode::Select => &modenames.select,
|
||||||
Mode::Normal => &modenames.normal,
|
Mode::Normal => &modenames.normal,
|
||||||
}
|
}
|
||||||
|
@ -191,6 +192,7 @@ where
|
||||||
if visible && config.color_modes {
|
if visible && config.color_modes {
|
||||||
match context.editor.mode() {
|
match context.editor.mode() {
|
||||||
Mode::Insert => Some(context.editor.theme.get("ui.statusline.insert")),
|
Mode::Insert => Some(context.editor.theme.get("ui.statusline.insert")),
|
||||||
|
Mode::Overtype => Some(context.editor.theme.get("ui.statusline.overtype")),
|
||||||
Mode::Select => Some(context.editor.theme.get("ui.statusline.select")),
|
Mode::Select => Some(context.editor.theme.get("ui.statusline.select")),
|
||||||
Mode::Normal => Some(context.editor.theme.get("ui.statusline.normal")),
|
Mode::Normal => Some(context.editor.theme.get("ui.statusline.normal")),
|
||||||
}
|
}
|
||||||
|
|
|
@ -55,6 +55,7 @@ pub enum Mode {
|
||||||
Normal = 0,
|
Normal = 0,
|
||||||
Select = 1,
|
Select = 1,
|
||||||
Insert = 2,
|
Insert = 2,
|
||||||
|
Overtype = 3,
|
||||||
}
|
}
|
||||||
|
|
||||||
impl Display for Mode {
|
impl Display for Mode {
|
||||||
|
@ -63,6 +64,7 @@ impl Display for Mode {
|
||||||
Mode::Normal => f.write_str("normal"),
|
Mode::Normal => f.write_str("normal"),
|
||||||
Mode::Select => f.write_str("select"),
|
Mode::Select => f.write_str("select"),
|
||||||
Mode::Insert => f.write_str("insert"),
|
Mode::Insert => f.write_str("insert"),
|
||||||
|
Mode::Overtype => f.write_str("overtype"),
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -75,6 +77,7 @@ impl FromStr for Mode {
|
||||||
"normal" => Ok(Mode::Normal),
|
"normal" => Ok(Mode::Normal),
|
||||||
"select" => Ok(Mode::Select),
|
"select" => Ok(Mode::Select),
|
||||||
"insert" => Ok(Mode::Insert),
|
"insert" => Ok(Mode::Insert),
|
||||||
|
"overtype" => Ok(Mode::Overtype),
|
||||||
_ => bail!("Invalid mode '{}'", s),
|
_ => bail!("Invalid mode '{}'", s),
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -501,6 +501,7 @@ impl Default for StatusLineConfig {
|
||||||
pub struct ModeConfig {
|
pub struct ModeConfig {
|
||||||
pub normal: String,
|
pub normal: String,
|
||||||
pub insert: String,
|
pub insert: String,
|
||||||
|
pub overtype: String,
|
||||||
pub select: String,
|
pub select: String,
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -509,6 +510,7 @@ impl Default for ModeConfig {
|
||||||
Self {
|
Self {
|
||||||
normal: String::from("NOR"),
|
normal: String::from("NOR"),
|
||||||
insert: String::from("INS"),
|
insert: String::from("INS"),
|
||||||
|
overtype: String::from("REP"),
|
||||||
select: String::from("SEL"),
|
select: String::from("SEL"),
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -584,7 +586,7 @@ pub enum StatusLineElement {
|
||||||
// Cursor shape is read and used on every rendered frame and so needs
|
// Cursor shape is read and used on every rendered frame and so needs
|
||||||
// to be fast. Therefore we avoid a hashmap and use an enum indexed array.
|
// to be fast. Therefore we avoid a hashmap and use an enum indexed array.
|
||||||
#[derive(Debug, Clone, PartialEq, Eq)]
|
#[derive(Debug, Clone, PartialEq, Eq)]
|
||||||
pub struct CursorShapeConfig([CursorKind; 3]);
|
pub struct CursorShapeConfig([CursorKind; 4]);
|
||||||
|
|
||||||
impl CursorShapeConfig {
|
impl CursorShapeConfig {
|
||||||
pub fn from_mode(&self, mode: Mode) -> CursorKind {
|
pub fn from_mode(&self, mode: Mode) -> CursorKind {
|
||||||
|
@ -603,6 +605,7 @@ impl<'de> Deserialize<'de> for CursorShapeConfig {
|
||||||
into_cursor(Mode::Normal),
|
into_cursor(Mode::Normal),
|
||||||
into_cursor(Mode::Select),
|
into_cursor(Mode::Select),
|
||||||
into_cursor(Mode::Insert),
|
into_cursor(Mode::Insert),
|
||||||
|
into_cursor(Mode::Overtype),
|
||||||
]))
|
]))
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -613,7 +616,7 @@ impl Serialize for CursorShapeConfig {
|
||||||
S: serde::Serializer,
|
S: serde::Serializer,
|
||||||
{
|
{
|
||||||
let mut map = serializer.serialize_map(Some(self.len()))?;
|
let mut map = serializer.serialize_map(Some(self.len()))?;
|
||||||
let modes = [Mode::Normal, Mode::Select, Mode::Insert];
|
let modes = [Mode::Normal, Mode::Select, Mode::Insert, Mode::Overtype];
|
||||||
for mode in modes {
|
for mode in modes {
|
||||||
map.serialize_entry(&mode, &self.from_mode(mode))?;
|
map.serialize_entry(&mode, &self.from_mode(mode))?;
|
||||||
}
|
}
|
||||||
|
@ -622,7 +625,7 @@ impl Serialize for CursorShapeConfig {
|
||||||
}
|
}
|
||||||
|
|
||||||
impl std::ops::Deref for CursorShapeConfig {
|
impl std::ops::Deref for CursorShapeConfig {
|
||||||
type Target = [CursorKind; 3];
|
type Target = [CursorKind; 4];
|
||||||
|
|
||||||
fn deref(&self) -> &Self::Target {
|
fn deref(&self) -> &Self::Target {
|
||||||
&self.0
|
&self.0
|
||||||
|
@ -631,7 +634,7 @@ impl std::ops::Deref for CursorShapeConfig {
|
||||||
|
|
||||||
impl Default for CursorShapeConfig {
|
impl Default for CursorShapeConfig {
|
||||||
fn default() -> Self {
|
fn default() -> Self {
|
||||||
Self([CursorKind::Block; 3])
|
Self([CursorKind::Block; 4])
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
Loading…
Reference in New Issue