Create an Auto-Save Script With Delay Feature

Introduction

Do you want to save your code before the destroying of the world?

You may say:” That’s easy, I have ability to create an autocmd of TextChanged event to save it”. But it’s too frequent sometimes. Therefore, I am going to tell you how to create a mini auto-save script with delay feature, which is useful.

Thinking

The main means to achieve the goal is to save the buffer after some events with autocmd function and then start a timer. Before the timer ends, the script won’t save the buffer anymore.

What’s more, I hope each buffer use a different timer.

Let’s start

You’re able to run help command in neovim to check more details if necessary

In order to make coding easier, we will use the following variables in all code blocks.

1
2
local api = vim.api
local fn = vim.fn

Autocmd

First of all, let’s create a simple autocmd to save the buffer and show the hint for user after each InsertLeave and TextChanged event.

1
2
3
4
5
6
7
8
9
local autosave = api.nvim_create_augroup("autosave", { clear = true })
api.nvim_create_autocmd({ "InsertLeave", "TextChanged" }, {
pattern = "*",
group = autosave,
callback = function(ctx)
vim.cmd("silent w")
vim.notify("Saved at " .. os.date("%H:%M:%S"))
end,
})

Delay

In this tutorial, we use vim.defer_fn to do timer. And we use nvim_buf_set_var and nvim_buf_get_var to mark or get if a buffer is queued. If a buffer is queued, it cannot be saved.

Now, we should know if the buffer is queued. And the script only save the code when the buffer isn’t queued .So add the following codes into callback function.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
-- get the queued value of the buffer
local ok, queued = pcall(api.nvim_buf_get_var, ctx.buf, "autosave_queued")
-- if cannot get/set buffer variable, end the callback
if not ok then
return
end

-- only save the buffer if the buffer isn't queued
if not queued then
vim.cmd("silent w")
vim.notify("Saved at " .. os.date("%H:%M:%S"))
-- let the buffer queued after saving it
api.nvim_buf_set_var(ctx.buf, "autosave_queued", true)
end

Then, we’re supposed to cancle the buffer’s queued status with timer. You may want to use the following code to do it after delay.

1
2
3
4
5
vim.defer_fn(function()
if api.nvim_buf_is_valid(ctx.buf) then
api.nvim_buf_set_var(ctx.buf, "autosave_queued", false)
end
end, delay)

However, there is a big problem here. We aim at saving the code once at most every delay, but the fact is that, if we tigger the events many times, we will create many timers to set autosave_queued false. So, we need to create another variable to block the timer be like:

1
2
3
4
5
6
7
8
9
10
11
12
local block = api.nvim_buf_get_var(ctx.buf, "autosave_block")
if not block then
api.nvim_buf_set_var(ctx.buf, "autosave_block", true)
vim.defer_fn(function()
-- check if the buffer valid
-- because buffer may disappear after delay
if api.nvim_buf_is_valid(ctx.buf) then
api.nvim_buf_set_var(ctx.buf, "autosave_queued", false)
api.nvim_buf_set_var(ctx.buf, "autosave_block", false)
end
end, delay)
end

Initialization

At last, we ought to initialize the autosave_queued and autosave_block variable after entering a new buffer.

1
2
3
4
5
6
7
8
api.nvim_create_autocmd("BufRead", {
pattern = "*",
group = autosave,
callback = function(ctx)
api.nvim_buf_set_var(ctx.buf, "autosave_queued", false)
api.nvim_buf_set_var(ctx.buf, "autosave_block", false)
end,
})

Full Code

Click to show the full code.
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
local api = vim.api
local fn = vim.fn

local delay = 250 -- ms

local autosave = api.nvim_create_augroup("autosave", { clear = true })
-- Initialization
api.nvim_create_autocmd("BufRead", {
pattern = "*",
group = autosave,
callback = function(ctx)
api.nvim_buf_set_var(ctx.buf, "autosave_queued", false)
api.nvim_buf_set_var(ctx.buf, "autosave_block", false)
end,
})

api.nvim_create_autocmd({ "InsertLeave", "TextChanged" }, {
pattern = "*",
group = autosave,
callback = function(ctx)
-- conditions that donnot do autosave
local disabled_ft = { "acwrite", "oil" }
if
not vim.bo.modified
or fn.findfile(ctx.file, ".") == "" -- a new file
or ctx.file:match("wezterm.lua")
or vim.tbl_contains(disabled_ft, vim.bo[ctx.buf].ft)
then
return
end

local ok, queued = pcall(api.nvim_buf_get_var, ctx.buf, "autosave_queued")
if not ok then
return
end

if not queued then
vim.cmd("silent w")
api.nvim_buf_set_var(ctx.buf, "autosave_queued", true)
vim.notify("Saved at " .. os.date("%H:%M:%S"))
end

local block = api.nvim_buf_get_var(ctx.buf, "autosave_block")
if not block then
api.nvim_buf_set_var(ctx.buf, "autosave_block", true)
vim.defer_fn(function()
if api.nvim_buf_is_valid(ctx.buf) then
api.nvim_buf_set_var(ctx.buf, "autosave_queued", false)
api.nvim_buf_set_var(ctx.buf, "autosave_block", false)
end
end, delay)
end
end,
})

Ending

Now, an awesome auto-save script have been created, which is customizable. You can add other features into it such as some conditions to save, some callbacks before/after saving( e.g. formatting ), etc.

And you can check here to see my script.

If you have any ideas or find some issues, feel free to comment below!


Create an Auto-Save Script With Delay Feature
https://xxiaoa.github.io/posts/15210477/
Author
XXiaoA
Posted on
October 22, 2022
Licensed under