snacks.nvim icon indicating copy to clipboard operation
snacks.nvim copied to clipboard

feature(dashboard): Disable caching for terminal command

Open UnaTried opened this issue 8 months ago • 22 comments

Did you check the docs?

  • [x] I have read all the snacks.nvim docs

Is your feature request related to a problem? Please describe.

Snacks.nvim's dashboard module caches the dashboard per folder. So if I have a command like fortune, it shows the same thing for multiple nvim's or terminals.

Describe the solution you'd like

The cache option disables the per folder caching of the command output, so if the command output is always different, it doesn't show the same output from last time.

require("snacks").setup({
    dashboard = {
	sections = {
            {,
                section = "terminal",
                cmd = 'FORTUNE=$(fortune -n 25 -s) && printf "%*s\n" $(( ($(tput cols) + ${#FORTUNE}) / 2 )) "$FORTUNE"',
                cache = false,
            },
        }
    }
})

Describe alternatives you've considered

Creating a temporary directory, going into it, then going back into the original directory.

Additional context

I think that should be all! Actually... I am just a random guy

UnaTried avatar Mar 27 '25 11:03 UnaTried

I believe if you set ttl = 0 it should do what you want.

dpetka2001 avatar Mar 27 '25 12:03 dpetka2001

That worked!!! What does ttl exactly do?

UnaTried avatar Mar 27 '25 12:03 UnaTried

It defines the amount of time the cache will be preserved if I'm not mistaken.

dpetka2001 avatar Mar 27 '25 12:03 dpetka2001

Wait, when I resize the window it reruns the command

UnaTried avatar Mar 27 '25 12:03 UnaTried

That's probaly how it works. It must have an autocmd that does this. No idea.

dpetka2001 avatar Mar 27 '25 12:03 dpetka2001

Well then this feature should be added.

UnaTried avatar Mar 27 '25 12:03 UnaTried

When Neovim is resized then the Dashboard gets updated. That means the sections will be recalculated and the terminal section will run the command again. With ttl = 0, that means that whenever the command is run a different output will be available. I thought that's what you wanted.

dpetka2001 avatar Mar 27 '25 13:03 dpetka2001

Sorry, was at a doctors appointment. I actually wanted to use a different output per Dashboard. Which means when it recalculates size and sections, then the command should not run again. But when I do nvim in one terminal and nvim in another terminal, in the same folder, or nvim twice in the same terminal, in the same folder, it should use a different output. That also when Snacks.dashboard() (instead of nvim) is used.

UnaTried avatar Mar 27 '25 16:03 UnaTried

Which means when it recalculates size and sections, then the command should not run again.

I don't believe that's possible with how the current Dashboard is implemented. Of course I might be mistaken, so you better wait for maintainer's input on that.

dpetka2001 avatar Mar 27 '25 16:03 dpetka2001

That is how the cache is created. I think if you would add an option and check if it's true, then you could stop this process

local cache_parts = {
      table.concat(type(cmd) == "table" and cmd or { cmd }, " "),
      uv.cwd(),
      opts.random and math.random(1, opts.random) or "",
    }
    local hashed_cache_key = vim.fn.sha256(table.concat(cache_parts, "."))

    local cache_dir = vim.fn.stdpath("cache") .. "/snacks"
    local cache_file = cache_dir .. "/" .. hashed_cache_key .. ".txt"
    local stat = uv.fs_stat(cache_file)
    local buf = vim.api.nvim_create_buf(false, true)
    local chan = vim.api.nvim_open_term(buf, {})

    local function send(data, refresh)
      vim.api.nvim_chan_send(chan, data)
      if refresh then
        -- HACK: this forces a refresh of the terminal buffer and prevents flickering
        vim.bo[buf].scrollback = 9999
        vim.bo[buf].scrollback = 9998
      end
    end

    local jid, stopped ---@type number?, boolean?
    local has_cache = stat and stat.type == "file" and stat.size > 0
    local is_expired = has_cache and stat and os.time() - stat.mtime.sec >= ttl
    if has_cache and stat then
      local fin = assert(uv.fs_open(cache_file, "r", 438))
      send(uv.fs_read(fin, stat.size, 0) or "", true)
      uv.fs_close(fin)
    end
    if not has_cache or is_expired then
      local output, recording = {}, assert(uv.new_timer())
      -- record output for max 5 seconds. otherwise assume its streaming
      recording:start(5000, 0, function()
        output = {}
      end)
      local first = true
      jid = vim.fn.jobstart(cmd, {
        height = height,
        width = width,
        pty = true,
        on_stdout = function(_, data)
          data = table.concat(data, "\n")
          if recording:is_active() then
            table.insert(output, data)
          end
That is how the cache is **created**. I think if you would add an option and check if it's true, then you could stop this process
          if first and has_cache then -- clear the screen if cache was expired
            first = false
            data = "\27[2J\27[H" .. data -- clear screen
          end
          pcall(send, data)
        end,
        on_exit = function(_, code)
          if not recording:is_active() or stopped then
            return
          end
          if code ~= 0 then
            Snacks.notify.error(
              ("Terminal **cmd** `%s` failed with code `%d`:\n- `vim.o.shell = %q`\n\nOutput:\n%s"):format(
                cmd,
                code,
                vim.o.shell,
                vim.trim(table.concat(output, ""))
              )
            )
          elseif ttl > 0 then -- save the output
            vim.fn.mkdir(cache_dir, "p")
            local fout = assert(uv.fs_open(cache_file, "w", 438))
            uv.fs_write(fout, table.concat(output, ""))
            uv.fs_close(fout)
          end
        end,
      })

UnaTried avatar Mar 27 '25 16:03 UnaTried

Something like this maybe?

if not has_cache or is_expired and cache then
      local output, recording = {}, assert(uv.new_timer())
      -- record output for max 5 seconds. otherwise assume its streaming
      recording:start(5000, 0, function()
        output = {}
      end)
      local first = true
      jid = vim.fn.jobstart(cmd, {
        height = height,
        width = width,
        pty = true,
        on_stdout = function(_, data)
          data = table.concat(data, "\n")
          if recording:is_active() then
            table.insert(output, data)
          end
That is how the cache is **created**. I think if you would add an option and check if it's true, then you could stop this process
          if first and has_cache then -- clear the screen if cache was expired
            first = false
            data = "\27[2J\27[H" .. data -- clear screen
          end
          pcall(send, data)
        end,
        on_exit = function(_, code)
          if not recording:is_active() or stopped then
            return
          end
          if code ~= 0 then
            Snacks.notify.error(
              ("Terminal **cmd** `%s` failed with code `%d`:\n- `vim.o.shell = %q`\n\nOutput:\n%s"):format(
                cmd,
                code,
                vim.o.shell,
                vim.trim(table.concat(output, ""))
              )
            )
          elseif ttl > 0 then -- save the output
            vim.fn.mkdir(cache_dir, "p")
            local fout = assert(uv.fs_open(cache_file, "w", 438))
            uv.fs_write(fout, table.concat(output, ""))
            uv.fs_close(fout)
          end
        end,

UnaTried avatar Mar 27 '25 16:03 UnaTried

Even without cache the command will be rerun when updating the terminal section when the window will get resized and will thus give you a different output when you resize.

dpetka2001 avatar Mar 27 '25 16:03 dpetka2001

But only when ttl is set to 0, my code checks if cache is set to true beforehand.

UnaTried avatar Mar 27 '25 17:03 UnaTried

Yes, you have a point about that. Didn't think it through.

However, I'm just a simple user like you, so it would be best to wait for maintainer's feedback on that.

dpetka2001 avatar Mar 27 '25 17:03 dpetka2001

I do know that, but at least we have an idea of how this would work! But @folke hasn't been active that much recently...

UnaTried avatar Mar 27 '25 17:03 UnaTried

He probably needs a long break to avoid burn-out. I'm sure he will either come back or make a proper announcement if he doesn't intend to. I remember last year he took off 1,5-2 months hiatus before coming back. We just have to be patient.

dpetka2001 avatar Mar 27 '25 17:03 dpetka2001

Next week I'll do some maintenance. I've been mostly doing lots of training for a big ultra I'm running in September in Kenia. I also have two new vacations lined up. In two weeks from now, two weeks Madeira and then a week after that I'm leaving for Borneo. Mid May I'll be back doing more Neovim stuff. Till at least end of June. In the summer I also have quite a lot of trips planned.

folke avatar Mar 27 '25 17:03 folke

I hope you have a lot of fun!

UnaTried avatar Mar 27 '25 17:03 UnaTried

This issue is stale because it has been open 30 days with no activity. Remove stale label or comment or this will be closed in 7 days.

github-actions[bot] avatar Apr 27 '25 02:04 github-actions[bot]

Man I hate the stale bot

UnaTried avatar Apr 27 '25 05:04 UnaTried

This issue is stale because it has been open 30 days with no activity. Remove stale label or comment or this will be closed in 7 days.

github-actions[bot] avatar May 28 '25 02:05 github-actions[bot]

no

UnaTried avatar May 28 '25 04:05 UnaTried

This issue is stale because it has been open 30 days with no activity. Remove stale label or comment or this will be closed in 7 days.

github-actions[bot] avatar Jun 28 '25 02:06 github-actions[bot]

Man please be quietttttt

UnaTried avatar Jun 28 '25 17:06 UnaTried

This issue is stale because it has been open 30 days with no activity. Remove stale label or comment or this will be closed in 7 days.

github-actions[bot] avatar Jul 30 '25 02:07 github-actions[bot]

No.

UnaTried avatar Jul 30 '25 12:07 UnaTried

This issue is stale because it has been open 30 days with no activity. Remove stale label or comment or this will be closed in 7 days.

github-actions[bot] avatar Sep 01 '25 02:09 github-actions[bot]

BlaBlaCar

UnaTried avatar Sep 01 '25 18:09 UnaTried

soon will be stale

UnaTried avatar Sep 30 '25 13:09 UnaTried

You can add random = 100 or whatever. or use ttl = 0.

folke avatar Sep 30 '25 13:09 folke