Displaying all selections in a single window?

When performing multi-selection editing on a large scale, I have always wondered if it is possible to display all selections in a single window, in a diff-like format:

<row>,<column>
selection1
<row>,<column>
selection2
...

In this way, I can view all (or at least as many as possible) selections at the same time, so that I can be more confident that my consequent editing won’t fail at some particular selections.

So I wonder:

  • does this feature seem desirable?
  • are there any plugins available that performs a similar task? If not, is it possible to implement this feature as a plugin (I once tried, but find the “writing edited contents back to original buffer” part difficult)
1 Like

That does seem pretty cool - allowing the summary buffer to be edited would be neat, but even if it just showed each selection in-context, live-updating as the selections changed in the main buffer, that would give you something to look at to quickly verify your edits.

In the mean time, I use the ( and ) keys to quickly cycle through all my selections and check that they’re doing the right thing.

That’s a cool idea. Pure kakscript using recursion (since maybe this should become a live-update hook… and because this is what I’m currently toying with):

def -params .. info-sel %{
  _sels2desc
  info -title Selections %opt{_str}
} -override

def -params .. preview-sel %{
  eval -save-regs c %{
    _sels2desc
    reg c %opt{_str}
    edit -scratch *selections*
    exec %{ggGed"cP}  #"unbreak hl
  }
} -override

### kakscript library
  decl -hidden str-list _slist
  decl -hidden str      _str

  def -params 0 nargs-0-0 %{nop --} -override  # fail if nargs != 0

  def -params 1.. shift-1-1 %{  # shift arg{1}; result in _slist
    set window _slist %arg{@}
    set -remove window _slist %arg{1}
  } -override

  def -params 2.. arg2reg-2 %{ reg %arg{1} %arg{2} } -override
###

decl -hidden str-list _sels2desc_dot_slist
decl -hidden str-list _sels2desc_z_slist

def -params .. _sels2desc-1 %{
  try %{ nargs-0-0 %opt{_sels2desc_z_slist} } catch %{
    arg2reg-2 c %opt{_sels2desc_dot_slist}
    arg2reg-2 d %opt{_sels2desc_z_slist}
    set -add window _str "%reg{d}
%reg{c}
"

    shift-1-1 %opt{_sels2desc_dot_slist}
    set window _sels2desc_dot_slist %opt{_slist}
    shift-1-1 %opt{_sels2desc_z_slist}
    set window _sels2desc_z_slist %opt{_slist}
    _sels2desc-1  # recurse after shifting both lists
  }
} -override -hidden

def -params .. _sels2desc %{
  set window _sels2desc_dot_slist
  eval -itersel %{ set -add window _sels2desc_dot_slist %reg{.} }

  shift-1-1 %reg{z}
  set window _sels2desc_z_slist %opt{_slist}

  set window _str ''; eval -draft -verbatim -save-regs 'cd' _sels2desc-1
} -hidden

Now, what exactly would mean to edit *selections* and update the original buffer?

That’s the tricky part (in terms of implementing this feature as a plugin). Idealy, *selections* is divided into separate small regions each corresponding to some part of the original buffer, and writing *selection* simply replace the corresponding parts of the original buffer with the new content of each small region.

But I have no idea how to separate a buffer into individual small parts in kak. Perhaps one can use kak’s range-spec type can be used, but I am not sure how robust it is and the exact mechanism how these ranges are updated when, for example, part of them are deleted.

Yes… I think there are two possible results of editing *selections*:

  1. edit the selected text → change the text of the original buffer
  2. change the selection columns → extend / shrink selection in original, but don’t touch text.

The first seems… maybe harder (and this is the one you seem to want).

For the second, however (which @Screwtapello seems to have in mind), it’s enough to prefix text lines (but not rangespec lines) with ##, then when update-original is invoked, strip the comments and generate a command that loads the ranges into a register, then execute z.

I think I’ll try to do it in pure kakscript — the code above needs only a few changes (it works as it stands, BTW).

But even for the second case, there still remain some difficulties. For example, what if the user pressed %?

Perhaps the following logic applies: calculate for each selection a surrounding context, and merge those contexts that overlaps (for example two selections on the same line will be displayed in the same small region).

As for live updating, perhaps *selection* can sever as a alternative view of the original buffer (like two windows displaying the same buffer), and the user’s operations are all forwarded to the original buffer. Not diving into the details yet, though.

my two cents,

might give you some more ideas to work with as well and for a few more Alex came up with this for multiple cursors.

A single buffer would definitely would be a great feature to have on large files. Cool guys, bye :wave:

OK, here’s a starting point:

At this point, it only displays selections in an infobox or a buffer. It works with toolsclient (sel-editor-live-enable / -disable)

It’s pure kakscript, which is what I’m currently intrested in — I want to see how far one can jailbreak from kakscript’s straitjacket. I’ve managed to shift arguments, work with lists, recurse (and stop), implement foreach, and I have some other ideas.

It can now update selection ranges (or delete / add selections) from the *selections* buffer. I’ve opened a separate thread for this plugin.

I have also made an attempt to this. I am using shell expansion heavily, though.

There are some differences in behavior. My attempt displays not only the selection, but a (configurable) small region around each selection. screenshot:

Currently it has a poor performance (due to frequent whole buffer operation), and update of the live buffer is not triggered in user modes or prompt modes. I’ll just paste it here as a snippet for now, too see if anyone can get some inspiration from this.

declare-option -docstring %{
    Number of rows to display before each selection
    in viewsel buffer
} int viewsel_n_rows_before 1

declare-option -docstring %{
    Number of rows to display after each selection
    in viewsel buffer
} int viewsel_n_rows_after  1

declare-option -hidden str viewsel_client ""


define-command viewsel -docstring %{
    view all selections in a temporary buffer
} %{
    evaluate-commands -save-regs 'a'  %sh{
        bufname=$kak_bufname
        buf_lines=$kak_buf_line_count

        nr_before=$kak_opt_viewsel_n_rows_before
        nr_after=$kak_opt_viewsel_n_rows_after

        viewsel_buf="*viewsel@$bufname*"
        echo "evaluate-commands -try-client $kak_opt_viewsel_client %{"
        echo "edit -scratch $viewsel_buf"
        echo "}"

        echo "evaluate-commands -buffer $viewsel_buf %{"
        echo "set-option buffer filetype $kak_opt_filetype"
        echo "}"

        # copy buffer content to viewsel buffer
        echo -n "execute-keys -buffer $bufname %{"
        echo -n "%\"ay"
        echo "}"

        echo "execute-keys -buffer $viewsel_buf %{"
        echo -n "%d\"aP"
        # remove trailing newline
        echo -n "gexd"
        echo "}"

        # restore selections for the viewsel buffer
        echo "evaluate-commands -try-client $kak_opt_viewsel_client %{"
        echo "select $kak_selections_desc"
        echo "}"

        # add annotations and remove unview contents
        # sort selections from begin to end
        sels=$(for entry in $kak_selections_desc; do
            echo $entry
        done | sort -n)


        cur_row=0

        # delete everything that is too far away from any selection
        # invariant:
        # - either $cur_row is 0,
        #   or the last row of the last region (inclusive)
        echo -n "execute-keys -draft -buffer $viewsel_buf %{"
        echo -n "gg"
        for sel in $sels; do
            beg=${sel%%,*}
            end=${sel##*,}

            row_s=${beg%%\.*}
            row_e=${end%%\.*}

            view_s=$(( row_s - nr_before ))
            view_e=$(( row_e + nr_after  ))
            if [ "$view_e" -gt "$buf_lines" ]; then
                view_e=$buf_lines
            fi
            if [ "$view_s" -le "$((cur_row + 1))" ]; then
                # Two regions around two selection overlap,
                # hence merge the two regions
                if [ "$cur_row" -eq 0 ]; then
                    echo -n "O== #1 =============<esc>"
                fi
                if [ "$view_e" -gt "$cur_row" ]; then
                    echo -n "$((view_e - cur_row))j"
                fi
            else
                # A new region should be created.
                # Delete everything until the start of new region
                if [ "$cur_row" -ne 0 ]; then
                     echo -n "j"
                fi
                if [ "$view_s" -gt "$((cur_row + 2))" ]; then
                    echo -n "$((view_s - cur_row - 2))J"
                fi
                echo -n "<a-x>d"
                # add indicator
                echo -n "O== #$view_s =============<esc>"
                echo -n "$((view_e - view_s + 1))j"
            fi
            cur_row=$view_e
        done
        echo -n "gllGed"
        echo "}"
    }
}



define-command viewsel-live -docstring %{
    view all selections live in a temporary buffer
} %{
    evaluate-commands %sh{
        echo "set-option buffer viewsel_client $kak_client"

        echo "hook global ClientClose $kak_client %{"
        echo "evaluate-commands -buffer $kak_bufname %{"
        echo "remove-hooks buffer viewsel-live"
        echo "}}"

        for hook in NormalKey InsertKey; do
            echo "hook -group viewsel-live buffer $hook .* viewsel"
        done

        echo "viewsel"
    }
}

2 Likes