pull/11265/merge
Nikolay Minaev 2025-05-09 18:44:53 +00:00 committed by GitHub
commit 603feba5f8
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
10 changed files with 125 additions and 6 deletions

View File

@ -743,6 +743,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 {
self.changes.changes_iter()
}

View File

@ -351,6 +351,7 @@ impl MappableCommand {
extend_prev_char, "Extend to previous occurrence of char",
repeat_last_motion, "Repeat last motion",
replace, "Replace with new char",
overtype_mode, "Enter overtype mode",
switch_case, "Switch (toggle) case",
switch_to_uppercase, "Switch to uppercase",
switch_to_lowercase, "Switch to lowercase",
@ -3012,6 +3013,29 @@ fn insert_mode(cx: &mut Context) {
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
fn append_mode(cx: &mut Context) {
enter_insert_mode(cx);
@ -4117,7 +4141,7 @@ pub mod insert {
Some(transaction)
}
use helix_core::auto_pairs;
use helix_core::{auto_pairs, graphemes::nth_next_grapheme_boundary};
use helix_view::editor::SmartTabConfig;
pub fn insert_char(cx: &mut Context, c: char) {
@ -4139,6 +4163,23 @@ pub mod insert {
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) {
let (view, doc) = current_ref!(cx.editor);
let view_id = view.id;
@ -4706,7 +4747,7 @@ pub(crate) fn paste_bracketed_value(cx: &mut Context, contents: String) {
let count = cx.count();
let paste = match cx.editor.mode {
Mode::Insert | Mode::Select => Paste::Cursor,
Mode::Normal => Paste::Before,
Mode::Normal | Mode::Overtype => Paste::Before,
};
let (view, doc) = current!(cx.editor);
paste_impl(&[contents], doc, view, paste, count, cx.editor.mode);

View File

@ -336,6 +336,7 @@ pub fn default() -> HashMap<Mode, KeyTrie> {
"C-a" => increment,
"C-x" => decrement,
"ins" => insert_mode,
});
let mut select = normal.clone();
select.merge_nodes(keymap!({ "Select mode"
@ -393,6 +394,28 @@ pub fn default() -> HashMap<Mode, KeyTrie> {
"tab" => smart_tab,
"S-tab" => insert_tab,
"up" => move_visual_line_up,
"down" => move_visual_line_down,
"left" => move_char_left,
"right" => move_char_right,
"pageup" => page_up,
"pagedown" => page_down,
"home" => goto_line_start,
"end" => goto_line_end_newline,
"ins" => overtype_mode,
});
let overtype = keymap!({ "Overtype mode"
"ins" => insert_mode,
"esc" => normal_mode,
"C-s" => commit_undo_checkpoint,
"C-w" | "A-backspace" => delete_word_backward,
"A-d" | "A-del" => delete_word_forward,
"C-h" | "backspace" | "S-backspace" => delete_char_backward,
"C-d" | "del" => delete_char_forward,
"C-j" | "ret" => insert_newline,
"up" => move_visual_line_up,
"down" => move_visual_line_down,
"left" => move_char_left,
@ -406,5 +429,6 @@ pub fn default() -> HashMap<Mode, KeyTrie> {
Mode::Normal => normal,
Mode::Select => select,
Mode::Insert => insert,
Mode::Overtype => overtype,
)
}

View File

@ -491,6 +491,7 @@ impl EditorView {
let cursor_scope = match mode {
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::Normal => theme.find_scope_index_exact("ui.cursor.normal"),
}
@ -498,6 +499,7 @@ impl EditorView {
let primary_cursor_scope = match mode {
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::Normal => theme.find_scope_index_exact("ui.cursor.primary.normal"),
}
@ -930,6 +932,33 @@ impl EditorView {
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) {
if let Some(keyresult) = self.handle_keymap_event(Mode::Insert, cx, event) {
match keyresult {
@ -1471,6 +1500,9 @@ impl Component for EditorView {
self.last_insert.1.push(InsertEvent::Key(key));
}
}
Mode::Overtype => {
self.overtype_mode(&mut cx, key);
}
mode => self.command_mode(mode, &mut cx, key),
}
}

View File

@ -180,6 +180,7 @@ where
if visible {
match context.editor.mode() {
Mode::Insert => &modenames.insert,
Mode::Overtype => &modenames.overtype,
Mode::Select => &modenames.select,
Mode::Normal => &modenames.normal,
}
@ -191,6 +192,7 @@ where
if visible && config.color_modes {
match context.editor.mode() {
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::Normal => Some(context.editor.theme.get("ui.statusline.normal")),
}

View File

@ -66,6 +66,7 @@ pub enum Mode {
Normal = 0,
Select = 1,
Insert = 2,
Overtype = 3,
}
impl Display for Mode {
@ -74,6 +75,7 @@ impl Display for Mode {
Mode::Normal => f.write_str("normal"),
Mode::Select => f.write_str("select"),
Mode::Insert => f.write_str("insert"),
Mode::Overtype => f.write_str("overtype"),
}
}
}
@ -86,6 +88,7 @@ impl FromStr for Mode {
"normal" => Ok(Mode::Normal),
"select" => Ok(Mode::Select),
"insert" => Ok(Mode::Insert),
"overtype" => Ok(Mode::Overtype),
_ => bail!("Invalid mode '{}'", s),
}
}

View File

@ -534,6 +534,7 @@ impl Default for StatusLineConfig {
pub struct ModeConfig {
pub normal: String,
pub insert: String,
pub overtype: String,
pub select: String,
}
@ -542,6 +543,7 @@ impl Default for ModeConfig {
Self {
normal: String::from("NOR"),
insert: String::from("INS"),
overtype: String::from("REP"),
select: String::from("SEL"),
}
}
@ -617,7 +619,7 @@ pub enum StatusLineElement {
// 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.
#[derive(Debug, Clone, PartialEq, Eq)]
pub struct CursorShapeConfig([CursorKind; 3]);
pub struct CursorShapeConfig([CursorKind; 4]);
impl CursorShapeConfig {
pub fn from_mode(&self, mode: Mode) -> CursorKind {
@ -636,6 +638,7 @@ impl<'de> Deserialize<'de> for CursorShapeConfig {
into_cursor(Mode::Normal),
into_cursor(Mode::Select),
into_cursor(Mode::Insert),
into_cursor(Mode::Overtype),
]))
}
}
@ -646,7 +649,7 @@ impl Serialize for CursorShapeConfig {
S: serde::Serializer,
{
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 {
map.serialize_entry(&mode, &self.from_mode(mode))?;
}
@ -655,7 +658,7 @@ impl Serialize for CursorShapeConfig {
}
impl std::ops::Deref for CursorShapeConfig {
type Target = [CursorKind; 3];
type Target = [CursorKind; 4];
fn deref(&self) -> &Self::Target {
&self.0
@ -664,7 +667,7 @@ impl std::ops::Deref for CursorShapeConfig {
impl Default for CursorShapeConfig {
fn default() -> Self {
Self([CursorKind::Block; 3])
Self([CursorKind::Block; 4])
}
}

View File

@ -84,6 +84,9 @@
"ui.statusline.insert" = { fg = "bg0", bg = "statusline2", modifiers = [
"bold",
] }
"ui.statusline.overtype" = { fg = "bg0", bg = "statusline3", modifiers = [
"bold",
] }
"ui.statusline.select" = { fg = "bg0", bg = "blue", modifiers = ["bold"] }
"ui.bufferline" = { fg = "grey2", bg = "bg3" }
"ui.bufferline.active" = { fg = "bg0", bg = "statusline1", modifiers = [

View File

@ -83,6 +83,9 @@
"ui.statusline.insert" = { fg = "bg0", bg = "statusline2", modifiers = [
"bold",
] }
"ui.statusline.overtype" = { fg = "bg0", bg = "statusline3", modifiers = [
"bold",
] }
"ui.statusline.select" = { fg = "bg0", bg = "blue", modifiers = ["bold"] }
"ui.bufferline" = { fg = "grey2", bg = "bg3" }
"ui.bufferline.active" = { fg = "bg0", bg = "statusline1", modifiers = [

View File

@ -102,6 +102,7 @@
"ui.statusline" = { fg = "fg1", bg = "bg2" }
"ui.statusline.inactive" = { fg = "fg4", bg = "bg2" }
"ui.statusline.insert" = { fg = "bg1", bg = "blue1", modifiers = ["bold"] }
"ui.statusline.overtype" = { fg = "bg1", bg = "red1", modifiers = ["bold"] }
"ui.statusline.normal" = { fg = "bg1", bg = "fg3", modifiers = ["bold"] }
"ui.statusline.select" = { fg = "bg1", bg = "orange1", modifiers = ["bold"] }