# Adding a live word count to the modeline

**URL:** <https://discuss.kakoune.com/t/adding-a-live-word-count-to-the-modeline/2946>\
**Category:** Resources\
**Created:** [July 15, 2026, 6:38pm UTC](https://discuss.kakoune.com/t/adding-a-live-word-count-to-the-modeline/2946 "2026-07-15T18:38:00Z")\
**Posts on this page:** 13\
**Page:** 1

<div class="post-metadata">

**Author:** ![benjaminwil](https://discuss.kakoune.com/user_avatar/discuss.kakoune.com/benjaminwil/32/1657_2.png) [@benjaminwil](https://discuss.kakoune.com/u/benjaminwil)\
**Post date:** [July 15, 2026, 6:38pm UTC](https://discuss.kakoune.com/t/adding-a-live-word-count-to-the-modeline/2946/1 "2026-07-15T18:38:00Z")

</div>

Yesterday, I decided I wanted a live word count in my modeline. Specifically for when I’m editing Markdown files. I hadn’t touched the modeline, or any KakScript, in quite a while, so I wrote this all out as a blog post for myself. Hopefully, this will help me keep some of this stuff in my head a little longer. And maybe someone reading this will also find it useful.

If you have any feedback or criticism on the blog post, I welcome it. Here’s the link:

[https://benjaminwil.info/weblog/kakoune-modeline-word-count/](https://benjaminwil.info/weblog/kakoune-modeline-word-count/)

---

<div class="post-metadata">

**Author:** ![Screwtapello](https://discuss.kakoune.com/user_avatar/discuss.kakoune.com/screwtapello/32/12_2.png) [@Screwtapello](https://discuss.kakoune.com/u/Screwtapello)\
**Post date:** [July 19, 2026, 11:38am UTC](https://discuss.kakoune.com/t/adding-a-live-word-count-to-the-modeline/2946/2 "2026-07-19T11:38:25Z")

</div>

It took me a little while to figure out what was going on with the command FIFO and response FIFO interaction, but it makes sense. I might have written the buffer to a temporary file, or copied the buffer to a scratch buffer, piped the whole thing to `wc`, then yanked the result, but your way is just as good and probably more straightforward.

One thing that did confuse me was under the heading “Conditionally adding this conditional functionality”. In your example code you have `evaluate-commands %sh{ ... }` and inside the shell block, you launch another shell just to run `echo`? Couldn’t you just do something like:

```auto
try %{
    source "%val{client_env_PWD}/.kakrc"
} catch %{
    echo -debug cannot autoload "%val{client_env_PWD}/.kakrc"
}

```

…and skip the shells entirely?

---

<div class="post-metadata">

**Author:** ![benjaminwil](https://discuss.kakoune.com/user_avatar/discuss.kakoune.com/benjaminwil/32/1657_2.png) [@benjaminwil](https://discuss.kakoune.com/u/benjaminwil)\
**Post date:** [July 19, 2026, 7:43pm UTC](https://discuss.kakoune.com/t/adding-a-live-word-count-to-the-modeline/2946/3 "2026-07-19T19:43:12Z")

</div>

Thanks so much for the feedback, @Screwtapello! I intend to edit and improve this blog post. Do you mind if I credit/thank you in it?

Interesting, I hadn’t considered using a scratch buffer. I may want to try that strategy for some other things in the future.

I do think using the Kakoune-managed FIFOs is pretty straightforward, and maybe more “secure”? (I could be mistaken. It seems like using `$kak_response_fifo` means that the tempfiles are deleted as soon as the shell expansion has completed, whereas using `mkfifo` may leave them on disk for some time.)

Thanks for pointing out that issue with my `.kakrc` loader. I actually think I want to revisit that for a couple of reasons:

1. I like your proposed version better, obviously.
2. I actually don’t want the `cannot load "<filename>"` debug message to be displayed if the file doesn’t exist; I only want it to be displayed if the file exists but could not be loaded due to an error inside of it.

I am not sure if I knew about `%val{client_env_<variable>}`, and that’s going to be really nice. I just quickly adapted this loader from the loader I use for getting all my Kakoune runtime configuration (i.e. a `find -L "$kak_runtime/autoload" -name "*.kak" -exec ...`), which I believe needs to be inside of a `%sh{}`. If anyone can think of a clever way to do a full runtime `*.kak` load without a shell expansion, let me know.

> **See the full $kak\_runtime loader; kinda off-topic**
>
> ```
> evaluate-commands %sh{
> find -L "$kak_runtime/autoload" -name "*.kak" -exec \
> sh -c 'echo "try %{ source $1 } \
> catch %{ echo -debug cannot autoload $1 }"' _ {} \;
> }
> 
> ```

---

<div class="post-metadata">

**Author:** ![Screwtapello](https://discuss.kakoune.com/user_avatar/discuss.kakoune.com/screwtapello/32/12_2.png) [@Screwtapello](https://discuss.kakoune.com/u/Screwtapello)\
**Post date:** [July 20, 2026, 9:09am UTC](https://discuss.kakoune.com/t/adding-a-live-word-count-to-the-modeline/2946/4 "2026-07-20T09:09:34Z")

</div>

> Do you mind if I credit/thank you in it?

I don’t mind at all!

> It seems like using `$kak_response_fifo` means that the tempfiles are deleted as soon as the shell expansion has completed, whereas using `mkfifo` may leave them on disk for some time.

That is true. If you use `mkfifo`, you can clean them up yourself with the [trap](https://pubs.opengroup.org/onlinepubs/9799919799/utilities/V3_chap02.html#tag_19_29) command, but it’s convenient to let Kakoune do the cleanup.

> I only want it to be displayed if the file exists but could not be loaded due to an error inside of it.

Ah, Kakoune indeed doesn’t provide a way to test for the existence of a file, apart from launching a shell to run `test -f` or whatever. So I don’t think it’s possible to meet your new functional requirements without at least one shell process.

> If anyone can think of a clever way to do a full runtime `*.kak` load without a shell expansion, let me know.

The system-wide `kakrc` uses the `find` shell command to load the standard library, so that’s not a bad way to do it.

---

<div class="post-metadata">

**Author:** ![benjaminwil](https://discuss.kakoune.com/user_avatar/discuss.kakoune.com/benjaminwil/32/1657_2.png) [@benjaminwil](https://discuss.kakoune.com/u/benjaminwil)\
**Post date:** [July 22, 2026, 8:42pm UTC](https://discuss.kakoune.com/t/adding-a-live-word-count-to-the-modeline/2946/5 "2026-07-22T20:42:50Z")

</div>

Thanks again for your feedback. I made some small tweaks to the article as a result.

I’ve been using this without issue for over a week now. It’s nice. If anything, I would like to generalize the abstraction for conditionally adding other things to my modeline easily. 🙂

---

<div class="post-metadata">

**Author:** ![alexherbo2](https://discuss.kakoune.com/user_avatar/discuss.kakoune.com/alexherbo2/32/1140_2.png) [@alexherbo2](https://discuss.kakoune.com/u/alexherbo2)\
**Post date:** [July 23, 2026, 11:09am UTC](https://discuss.kakoune.com/t/adding-a-live-word-count-to-the-modeline/2946/6 "2026-07-23T11:09:25Z")

</div>

I’m wondering if you cannot get rid of the shell call by using a long living process and writing to a fifo.

---

<div class="post-metadata">

**Author:** ![benjaminwil](https://discuss.kakoune.com/user_avatar/discuss.kakoune.com/benjaminwil/32/1657_2.png) [@benjaminwil](https://discuss.kakoune.com/u/benjaminwil)\
**Post date:** [July 23, 2026, 4:26pm UTC](https://discuss.kakoune.com/t/adding-a-live-word-count-to-the-modeline/2946/7 "2026-07-23T16:26:09Z")

</div>

@alexherbo2 Don’t I still need the shell process for `wc`? I’m curious if you have an example of what you mean, even if it’s for some other piece of functionality.

---

<div class="post-metadata">

**Author:** ![ftonneau](https://discuss.kakoune.com/user_avatar/discuss.kakoune.com/ftonneau/32/295_2.png) [@ftonneau](https://discuss.kakoune.com/u/ftonneau)\
**Post date:** [July 23, 2026, 7:02pm UTC](https://discuss.kakoune.com/t/adding-a-live-word-count-to-the-modeline/2946/8 "2026-07-23T19:02:57Z")

</div>

@alexherbo2 @benjaminwil

If the idea is to remove shell calls (and I do think that in some cases, this **is** a good idea), why not stay inside Kakoune and select the whole buffer, then all words (i.e., \S+) in a draft context? Word count will be available as %val{selection\_count} (unless the selection command fails, of course, in which case word count should be 0).

EDIT: The idea would be to have a dedicated option, word\_count, and update it on a hook. The updating command would `try` to select %, then \S+, and (if succeeding), set word\_count to %val(selection\_count); otherwise (i.e., on `catch`), set word\_count to 0.

---

<div class="post-metadata">

**Author:** ![benjaminwil](https://discuss.kakoune.com/user_avatar/discuss.kakoune.com/benjaminwil/32/1657_2.png) [@benjaminwil](https://discuss.kakoune.com/u/benjaminwil)\
**Post date:** [July 24, 2026, 7:31pm UTC](https://discuss.kakoune.com/t/adding-a-live-word-count-to-the-modeline/2946/9 "2026-07-24T19:31:39Z")

</div>

@ftonneau Ah, great idea. I was able to swap out the version in my blog post to one that pretty much does what you suggested:

```auto
declare-option -hidden str modeline_buf_word_count_formatted ''
set-option -add global modelinefmt '%opt{modeline_buf_word_count_formatted}'

define-command update-modeline-buf-word-count %{
    try %{
        evaluate-commands -draft %{
            execute-keys '%s\S+<ret>'
            execute-keys ':set-option buffer modeline_buf_word_count_formatted '\
                '" [%val{selection_count}] "<ret>'
        }
    } catch %{
      set-option buffer modeline_buf_word_count_formatted ' [0] '
    }
}

hook global WinSetOption filetype=markdown %{
    hook buffer InsertIdle .* %{ update-modeline-buf-word-count }
    hook buffer NormalIdle .* %{ update-modeline-buf-word-count }
}

```

I think it would be nice to:

- Use an `int` option for `word_count` instead of building a formatted string.
- Not need to use `execute-keys` to specify the `set-option` in the draft context.

But it seems like both of those things aren’t easy to do. (At least until I brush up on more KakScript?)

It’s nice that even a document with 400,000 words in it, this method is just as performant as the `wc --words` was. I would like to update my blog post with this other method of achieving this functionality. @ftonneau is it okay if I credit you for the help, as well?

---

<div class="post-metadata">

**Author:** ![ftonneau](https://discuss.kakoune.com/user_avatar/discuss.kakoune.com/ftonneau/32/295_2.png) [@ftonneau](https://discuss.kakoune.com/u/ftonneau)\
**Post date:** [July 24, 2026, 9:13pm UTC](https://discuss.kakoune.com/t/adding-a-live-word-count-to-the-modeline/2946/10 "2026-07-24T21:13:21Z")

</div>

Glad to read your information about performance.

About your questions: adapting your notation, I solved them below for you 🙂

```
declare-option -hidden int modeline_buf_word_count 0
set-option -add global modelinefmt ' - W:%opt{modeline_buf_word_count}'

define-command update-modeline-buf-word-count %{
    try %{
        evaluate-commands -draft %{
            execute-keys <percent> s\S+ <ret>
            set-option buffer modeline_buf_word_count %val{selection_count}
        }
    } catch %{
        set-option buffer modeline_buf_word_count 0
    }
}

hook global WinSetOption filetype=.* %{
    hook buffer InsertIdle .* update-modeline-buf-word-count
    hook buffer NormalIdle .* update-modeline-buf-word-count
}

```

As you can see, there is no need to declare the word-count option as a string; an int is fine because the modeline itself can take care of string formatting. Neither is there any need to prefix `set-option buffer...` with `execute-keys`. `set-option ...` is perfectly fine as a command, that you can insert as is into your `try`block just as you did in the `catch` block.

A few comments on `execute-keys ...`: personally, I prefer to cut down on quoting. So, I removed the single quotes, but for this to work I had to replace the literal `%` by its keyname, `<percent>` (otherwise Kakoune’s parser will complain). For a list of valid keynames, see `:doc mappings`. Also, it is legal to insert blanks between keynames, as I did to increase legibility.

Yes, feel free to credit :-).

Btw 1: I replaced the markdown filetype by a regex matching anything, as I was in a hurry to test the script. You’ll probably want to replace `.*`by `markdown`.

Btw 2: If you want word counts to be available only in markdown documents, you should probably move `set-option -add global modelinefmt` into ` hook global WinSetOption ...` EDIT: in that case, `global`should be replaced by `buffer`.

---

<div class="post-metadata">

**Author:** ![benjaminwil](https://discuss.kakoune.com/user_avatar/discuss.kakoune.com/benjaminwil/32/1657_2.png) [@benjaminwil](https://discuss.kakoune.com/u/benjaminwil)\
**Post date:** [July 28, 2026, 9:53pm UTC](https://discuss.kakoune.com/t/adding-a-live-word-count-to-the-modeline/2946/11 "2026-07-28T21:53:24Z")

</div>

@ftonneau Thanks so much. It’s valuable for me to see these kinds of examples.

> Btw 2: If you want word counts to be available only in markdown documents, you should probably move `set-option -add global modelinefmt` into ` hook global WinSetOption ...` EDIT: in that case, `global`should be replaced by `buffer`.

Yeah, this is _so_ much nicer than conditionally setting an empty string via an option declared as a string 🥲

```diff
 declare-option -hidden int modeline_buf_word_count 0
-set-option -add global modelinefmt ' - W:%opt{modeline_buf_word_count}'

# ...

 hook global WinSetOption filetype=markdown %{
+ set-option -add buffer modelinefmt ' - W:%opt{modeline_buf_word_count}'
     hook buffer InsertIdle .* update-modeline-buf-word-count
     hook buffer NormalIdle .* update-modeline-buf-word-count
 }

```

---

<div class="post-metadata">

**Author:** ![benjaminwil](https://discuss.kakoune.com/user_avatar/discuss.kakoune.com/benjaminwil/32/1657_2.png) [@benjaminwil](https://discuss.kakoune.com/u/benjaminwil)\
**Post date:** [July 29, 2026, 12:13am UTC](https://discuss.kakoune.com/t/adding-a-live-word-count-to-the-modeline/2946/12 "2026-07-29T00:13:13Z")

</div>

I significantly updated the post to describe both my initial strategy and the much-simpler strategy using `%val{selection_count}`. Thanks again, everyone, for the feedback.

---

<div class="post-metadata">

**Author:** ![alexherbo2](https://discuss.kakoune.com/user_avatar/discuss.kakoune.com/alexherbo2/32/1140_2.png) [@alexherbo2](https://discuss.kakoune.com/u/alexherbo2)\
**Post date:** [August 12, 2026, 12:23pm UTC](https://discuss.kakoune.com/t/adding-a-live-word-count-to-the-modeline/2946/13 "2026-08-12T12:23:22Z")

</div>

I’ve added an example here: [How to write an asynchronous plugin? - #4 by alexherbo2](https://discuss.kakoune.com/t/how-to-write-an-asynchronous-plugin/2955/4)
