A simple word-count command

I was recently doing some writing with a word-count limit, and I wanted a quick way to count words in Kakoune. It’s easy enough to map a key to %|wc -w<ret> but that replaces the buffer with the word-count, which is quite disruptive. Instead, I came up with this approach:

define-command word-count \
    -params 0 \
    -docstring "Count words in the current buffer" \
%{
    evaluate-commands -save-regs "^c" %{
        # Save the current selections
        execute-keys -save-regs "" Z

        # Select non-whitespace spans and count them
        execute-keys <percent>S\s+<ret>
        set-register c "%reg{#}"

        # Restore the original selections
        execute-keys -save-regs "" z

        # Report our result
        echo Word count: "%reg{c}"
    }
}
map -docstring "Word count" global user w ': word-count<ret>'

It’s a bit more complex than I expected - my first attempt worked inside a draft context so the selection-changes would automatically be undone, but that also meant the echo would be undone as well. If I save and restore the selections manually, I can run the echo outside a draft context and see the result.

Unfortunately, if I echo the result then restore the selection, the result is immediately covered by the “selections restored” message. Thus, I have to store the selection in another register, restore the selections, then echo the result so I can see it.

Did you try wordcount.kak?

define-command word-count %{
  evaluate-commands -draft %{
    execute-keys '%'
    evaluate-commands -client %val{client} echo %sh{
      printf '%s' "$kak_selection" | wc -c
    }
  }
}

I’m afraid I didn’t! This was as much about procrastinating the actual writing as it was about counting words. :wink:

Since that’s executed inside eval -draft, doesn’t %val{client} expand to the client ID of the draft context?

@Screwtapello Nope, you can just try it to see. :stuck_out_tongue:

It can be a side effect so I added an issue to support expansion naming.

One way that you can simplify this command is by letting -draft do the Z-z dance.
And let clippy report the result of course

define-command word-count \
    -params 0 \
    -docstring "Count words in the current buffer" \
%{
    evaluate-commands -save-regs "c" %{
        evaluate-commands -draft %{
            # Select non-whitespace spans and count them
            execute-keys '%S\s+<ret>'
            set-register c "%reg{#}"
        }
        # Report our result
        info "Word count: %reg{c}"
    }
}
2 Likes

OK, taking hints from both alexherbo2 and occivink, here’s another version:

define-command word-count \
    -params 0 \
    -docstring "Count words in the current buffer" \
%{
    execute-keys -draft \
        '%S\s+<ret>' \
        ": eval -client %val{client} echo Word count: <c-r>#<ret>"
}

Much simpler!

1 Like

Just grab the regex, it was a lazy copy and paste effort.

fun String.countLines(): Int {
    return Regex("\r\n|\r|\n")
        .findAll(this)
        .count() + 1
}

fun String.countParagraphs(): Int {
    return Regex("[^\r\n]+((\r|\n|\r\n)[^\r\n]+)*")
        .findAll(this)
        .count()
}

fun String.countSentences(): Int {
    return Regex("(?<=[\'\"\"A-Za-z0-9][.!?])\\s+(?=[A-Z])")
        .findAll(this)
        .count()
}

fun String.countWords(): Int {
    return Regex("\\b[^\\d\\W]+\\b")
        .findAll(this)
        .count()
}

from here countWords.kt