Making a vi layer and the leader plugin

So I want to make a layer that emulates the vi movement and editing commands.
I was thinking it would be best to use the Leader plugin to make a lookup table aliasing each vi command to a sequence of keys that does what I need it to.
For instance 2dw would get mapped to shift-control right arrow right arrow delete.

Now do I need to separately define 2dw, 3dw, 4dw, etc. or is there a way to recognize a one or two digit number typed and repeat a command that many times?

I don’t want to have to enter the leader key for each new command - is there a way to make it automatic?

One possibility might be to set up a macro where the “switch to vi layer” key automatically presses the leader key and then each defined sequence ends with the leader key - but is there a time delay to enter a sequence after pressing the leader key?

Or is there a better way to do this than using Leader?

With the Leader plugin, yes.

I think the most practical thing would be to have a “vi layer”, where you have a couple of macros: numbers would be macros that do nothing, but accumulate the numbers you press. d would flip a boolean that will signal deletion, but do nothing otherwise. w would be the trigger that executes the command, by looking at the boolean that d set, and repeating the command N times.

Something along these lines:

static struct {
  uint8_t repeat_number;
  enum {
    NOTHING,
    DELETE
  } command;
} vi_state;

// add the following to the macro enum:
enum {
  VI_0,
  VI_1,
  // ...
  VI_D,
  VI_W,
};

void macroViNumber(uint8_t macro_index, uint8_t key_state) {
  if (!keyToggledOn(key_state))
    return;
  vi_state.repeat_number = vi_state.repeat_number * 10 + (macro_index - VI_0);
}

void viExecute(void) {
  switch (vi_state.command) {
    case NOTHING:
      break;
    case DELETE:
      // Press Shift + Control
      Macro.play(MACRO(D(LeftShift), D(LeftControl)));
      // Press & Release RightArrow N times
      for (uint8_t i = 0; i < vi_state.repeat_number; i++) {
        Macro.play(MACRO(T(RightArrow)));
      }
      // Release Shift + Control, then tap Delete
      Macro.play(MACRO(U(LeftShift), U(LeftControl), T(Delete)));
      break;
  }
}

void macroViCommand(uint8_t macro_index, uint8_t key_state) {
  if (!keyToggledOn(key_state))
    return;
  switch (macro_index) {
    case VI_D:
      vi_state.command = DELETE;
      break;
    case VI_W: {
      viExecute();
      vi_state.command = NOTHING;
      vi_state.repeat_number = 0;
    }
}

const macro_t *macroAction(uint8_t macroIndex, uint8_t keyState) {
  switch (macroIndex) {
    case VI_0 ... VI_9:
      macroViNumber(macroIndex, keyState);
      break;
    case VI_D:
    case VI_W:
      macroViCommand(macroIndex, keyState);
      break;
  }

  return MACRO_NONE;
}

// On the VI layer:
M(VI_1), M(VI_2), ....
2 Likes

Belatedly, I would also point to Kaleidoscope-PrefixLayer; it won’t do everything you want out of the box but it may be a better start than Leader

I would think the biggest hurdle would be that you can’t delete words because the keyboard has no idea where the words are on the screen. You would have to find a way to communicate back to the keyboard where the next is.

You can definitely delete one word at a time, by issuing option-delete on a Mac, ctrl-w in many Unix contexts, etc.