[SOLVED] How to move/expand to the beginning of line/non blank depending on where your cursor is

Lately, I’ve been trying to configure kakoune to behave more similar to non-modal editors in some ways (I’ll write a blog about my ideas at some point). I use this because I use Kakoune and vscode as my main editors and it can be a bit difficult to change between them.

Right now, I want to make the <home> button move to the first non-blank character (as gi does) if it is not there and to the beginning of the line if it’s there. This way, you can press once to go to the beginning of your code, as you’d usually do, or press twice to actually go to the beginning of the line.

This should work with multiple cursors and <s-home> should extend to the same places as above.

Any clever and simple ideas from more expert people?

There are two ways you can go, either store that last keystroke was <home> in some option an test that to select the command to run (can be done in shell, left as an exercise to reader).

Or check if the cursor is only preceeded by blanks, if so go to begin of line, else go first non blank which I think is better because its only dependent of selection state instead of depending on the last key that was hit.

On way to do that is to use the try … catch command to implement the check:

eval -itersel %{ # run each selection independently so that the test does not just remove failing selections
  try %{ 
    exec -draft %{ <a-h><a-k>\A\h+.\z<ret> } # check that the preceeding characters are horizontal whitespaces
    exec gh # if the previous line does not fail, go to begining of line
  } catch %{
    exec gi # if it failed, go to first non blank character
}}

Wrap that into a command and map your <home> key to :my-command<ret> and you should be good to go.

1 Like

Thank you very much @mawww, I wasn’t aware of the use try-catch and -draft mode for this purpose. The code I ended up adding to my kakrc is the following, in case anyone is interested:

# Home moves/expand to the begining of line/non blank depending on position
define-command -override -hidden home-movement %{
    # Run each selection independently so that the
    # test does not just remove failing selections.
    eval -itersel %{ 
        try %{
            exec -draft %{ <a-h><a-k>\A\h+.\z<ret> } # check that the preceeding characters are horizontal whitespaces
            exec gh # if the previous line does not fail, go to begining of line
        } catch %{
            exec gi # if it failed, go to first non blank character
        }
    }
}
define-command -override -hidden home-expansion %{
    # Run each selection independently so that the
    # test does not just remove failing selections.
    eval -itersel %{ 
        try %{
            exec -draft %{ <a-h><a-k>\A\h+.\z<ret> } # check that the preceeding characters are horizontal whitespaces
            exec Gh # if the previous line does not fail, go to begining of line
        } catch %{
            exec Gi # if it failed, go to first non blank character
        }
    }
}
map global insert <home>      "<a-;>: home-movement<ret>"
map global insert <s-home>    "<a-;>: home-expansion<ret>"
map global normal <home>      ": home-movement<ret>"
map global normal <s-home>    ": home-expansion<ret>"

You can derive SelectMode::Extend from SelectMode::Replace with the following command:

define-command evaluate-commands-extend -params .. %{
  evaluate-commands -save-regs '^' %{
    execute-keys ';'
    execute-keys -save-regs '' Z
    evaluate-commands -verbatim -- %arg{@}
    execute-keys '<a-z>u'
  }
}

map global normal <s-home> ': evaluate-commands-extend select-to-line-begin-smart<ret>'

That way, you don’t have to explicitly implement the extend commands.

1 Like

Seems interesting :thinking:

Check select_coord() in normal.cc.

Another alternative would be to take 1 parameter for the g/G key and pass it in the mapping:

define-command -override -hidden -params 1 home-movement %{
    ...
    exec %arg{1} h
    ...
    exec %arg{1} i
    ...
}

map global insert <home>      "<a-;>: home-movement g<ret>"
map global insert <s-home>    "<a-;>: home-movement G<ret>"

Is there a way to call internal functions or was it for me to understand how it’s implemented?

No there is no way to call internal functions; it was just to show you how it is implemented.