vamolessa / pepper

simple and opinionated modal code editor for your terminal
https://vamolessa.github.io/pepper/
373 stars 15 forks source link

New command: replace character(s) #15

Closed leahneukirchen closed 2 years ago

leahneukirchen commented 3 years ago

I think replacing a single character is a common enough operation to be useful, even if it's orthogonal to the editing operations. I think it currently cannot conveniently be defined as a macro. Kakoune has the r command which replaces every character in the selection with the next char entered, which works nicely with default-1 selections. The Helix editor has a r command which works like in vi, I think.

Perhaps pepper could implement Kakoune's r as gr (like vim, replace whole selection by char), then one can define vi-style r as a macro lcvhgr.

vamolessa commented 3 years ago

That's interesting. Would you mind listing your common usages? I think I never used such command even during my vim time.

At least at first I'm kinda hesitant to implement it as it seems kinda specific to insert a single char. Also, I'm planning on exposing a plugin api soon, so this could be a nice candidate for a plugin implemented command.

leahneukirchen commented 3 years ago

Well, it's the fastest way to fix a single-letter typo, I also use it to increment a number <9 ;)

Currently changing a single letter is a bit tricky, and requires at least i<DEL>X<ESC>, and I don't like typing DEL. :D

vamolessa commented 2 years ago

In the next update, it will be possible to create plugins that act like modes. With that, you'll be able to create a 'replace char' mode that can be entered from normal mode (via a keybind if you wish).

This is a snippet that implements it:

use pepper::{
    buffer_view::{CursorMovement, CursorMovementKind},
    editor::EditorFlow,
    mode::ModeKind,
    platform::Key,
    plugin::{Plugin, PluginDefinition},
};

pub static PLUGIN: PluginDefinition = PluginDefinition {
    instantiate: |handle, ctx| {
        ctx.editor
            .commands
            .register(Some(handle), "replace-char", &[], |ctx, io| {
                let buffer_view_handle = io.current_buffer_view_handle(ctx)?;
                let buffer_view = ctx.editor.buffer_views.get(buffer_view_handle);
                buffer_view.delete_text_in_cursor_ranges(
                    &mut ctx.editor.buffers,
                    &mut ctx.editor.word_database,
                    &mut ctx.editor.events,
                );

                ctx.editor.enter_plugin_mode(io.plugin_handle());
                Ok(())
            });

        Some(Plugin {
            on_keys: |_, ctx, client_handle, keys| {
                let handle = match ctx.clients.get(client_handle).buffer_view_handle() {
                    Some(handle) => handle,
                    None => {
                        ctx.editor.enter_mode(ModeKind::default());
                        return Some(EditorFlow::Continue);
                    }
                };

                if let Key::Char(c) = keys.next(&ctx.editor.buffered_keys) {
                    let buffer_view = ctx.editor.buffer_views.get_mut(handle);
                    buffer_view.move_cursors(
                        &ctx.editor.buffers,
                        CursorMovement::ColumnsForward(1),
                        CursorMovementKind::PositionOnly,
                        ctx.editor.config.tab_size,
                    );
                    buffer_view.delete_text_in_cursor_ranges(
                        &mut ctx.editor.buffers,
                        &mut ctx.editor.word_database,
                        &mut ctx.editor.events,
                    );
                    ctx.trigger_event_handlers();

                    let mut buf = [0; std::mem::size_of::<char>()];
                    let text = c.encode_utf8(&mut buf);

                    let buffer_view = ctx.editor.buffer_views.get_mut(handle);
                    buffer_view.insert_text_at_cursor_positions(
                        &mut ctx.editor.buffers,
                        &mut ctx.editor.word_database,
                        text,
                        &mut ctx.editor.events,
                    );
                    ctx.trigger_event_handlers();

                    let buffer_view = ctx.editor.buffer_views.get_mut(handle);
                    buffer_view.move_cursors(
                        &ctx.editor.buffers,
                        CursorMovement::ColumnsBackward(1),
                        CursorMovementKind::PositionAndAnchor,
                        ctx.editor.config.tab_size,
                    );
                }

                ctx.editor.enter_mode(ModeKind::default());
                Some(EditorFlow::Continue)
            },
            ..Default::default()
        })
    },
    help_pages: &[],
};

You can also check an example of how to glue plugins together with the editor here: https://github.com/vamolessa/pepper/blob/master/mine/src/main.rs

vamolessa commented 2 years ago

The update is now live! Will close this for now.