Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
93 changes: 59 additions & 34 deletions .luacheckrc
Original file line number Diff line number Diff line change
Expand Up @@ -2,66 +2,91 @@

std = {
globals = {
"vim",
"table",
"string",
"math",
"os",
"io",
'vim',
'table',
'string',
'math',
'os',
'io',
'package',
},
read_globals = {
"jit",
"require",
"pcall",
"type",
"ipairs",
"pairs",
"tostring",
"tonumber",
"error",
"assert",
"_VERSION",
'jit',
'require',
'pcall',
'type',
'ipairs',
'pairs',
'tostring',
'tonumber',
'error',
'assert',
'_VERSION',
},
}

-- Patterns for files to exclude
exclude_files = {
".luarocks/*",
"lua/plenary/*",
"tests/plenary/*",
'.luarocks/*',
'lua/plenary/*',
'tests/plenary/*',
}

-- Special configuration for scripts
files["scripts/**/*.lua"] = {
files['scripts/**/*.lua'] = {
globals = {
"print", "arg",
'print',
'arg',
},
}

-- Special configuration for test files
files["tests/**/*.lua"] = {
files['tests/**/*.lua'] = {
-- Allow common globals used in testing
globals = {
-- Common testing globals
"describe", "it", "before_each", "after_each", "teardown", "pending", "spy", "stub", "mock",
'describe',
'it',
'before_each',
'after_each',
'teardown',
'pending',
'spy',
'stub',
'mock',
-- Lua standard utilities used in tests
"print", "dofile",
'print',
'dofile',
-- Test helpers
"test", "expect",
'test',
'expect',
-- Global test state (allow modification)
"_G",
'_G',
},

-- Define fields for assert from luassert
read_globals = {
assert = {
fields = {
"is_true", "is_false", "is_nil", "is_not_nil", "equals",
"same", "near", "matches", "has_error",
"truthy", "falsy", "has", "has_no", "is_string", "is_number",
"is_function", "is_table"
}
}
'is_true',
'is_false',
'is_nil',
'is_not_nil',
'equals',
'same',
'near',
'matches',
'has_error',
'truthy',
'falsy',
'has',
'has_no',
'is_string',
'is_number',
'is_function',
'is_table',
},
},
},

-- For test files only, ignore unused arguments as they're often used for mock callbacks
Expand All @@ -87,6 +112,6 @@ max_line_length = 120
max_cyclomatic_complexity = 20

-- Override settings for specific files
files["lua/rovo-dev/config.lua"] = {
files['lua/rovo-dev/config.lua'] = {
max_cyclomatic_complexity = 30, -- The validate_config function has high complexity due to many validation checks
}
44 changes: 38 additions & 6 deletions lua/rovo-dev/file_refresh.lua
Original file line number Diff line number Diff line change
Expand Up @@ -41,6 +41,23 @@ function M.setup(config)
return false
end

-- Reset deletion notification flag when the user writes the buffer (recreates file)
vim.api.nvim_create_autocmd('BufWritePost', {
group = augroup,
callback = function(args)
local buf = args.buf
local name = vim.api.nvim_buf_get_name(buf)
if not name or name == '' then
return
end
-- If the file now exists on disk, clear any previous deletion notification flag
if vim.fn.filereadable(name) == 1 then
vim.b[buf].rovo_dev_deletion_notified = nil
end
end,
desc = 'Rovo Dev: reset deletion notification on write',
})

-- Notify when buffers were reloaded from disk
vim.api.nvim_create_autocmd('FileChangedShellPost', {
group = augroup,
Expand All @@ -53,12 +70,27 @@ function M.setup(config)
if not name or name == '' then
return
end
name = vim.fn.fnamemodify(name, ':~:.')
vim.notify(
('Reloaded from disk: %s'):format(name),
vim.log.levels.INFO,
{ title = 'Rovo Dev' }
)

-- Check if the file actually exists
if vim.fn.filereadable(name) == 1 then
-- Reset deletion notification flag since file exists again
vim.b[buf].rovo_dev_deletion_notified = nil
name = vim.fn.fnamemodify(name, ':~:.')
vim.notify(
('Reloaded from disk: %s'):format(name),
vim.log.levels.INFO,
{ title = 'Rovo Dev' }
)
else
-- File was deleted - show a different notification only once
-- We use a buffer variable to track if we've already notified about deletion
local already_notified = vim.b[buf].rovo_dev_deletion_notified
if not already_notified then
vim.b[buf].rovo_dev_deletion_notified = true
name = vim.fn.fnamemodify(name, ':~:.')
vim.notify(('File deleted: %s'):format(name), vim.log.levels.WARN, { title = 'Rovo Dev' })
end
end
end,
desc = 'Notify when a buffer is updated externally',
})
Expand Down
95 changes: 88 additions & 7 deletions tests/test_rovo_dev_spec.lua
Original file line number Diff line number Diff line change
Expand Up @@ -13,7 +13,9 @@ local function reset_state()
pcall(vim.cmd, 'silent! only')
local ok, state = pcall(require, 'rovo-dev.state')
if ok then
if state.has_win() then pcall(vim.api.nvim_win_close, state.win, true) end
if state.has_win() then
pcall(vim.api.nvim_win_close, state.win, true)
end
pcall(state.reset_buf)
pcall(state.clear_win)
end
Expand All @@ -24,7 +26,9 @@ local original_termopen
local function stub_termopen(capture)
original_termopen = original_termopen or vim.fn.termopen
vim.fn.termopen = function(cmd, opts)
if capture then capture(cmd, opts) end
if capture then
capture(cmd, opts)
end
return 4242 -- fake job id
end
end
Expand All @@ -40,7 +44,9 @@ local function visible_windows_for_buf(buf)
for _, win in ipairs(vim.api.nvim_list_wins()) do
if vim.api.nvim_win_get_buf(win) == buf then
local cfg = vim.api.nvim_win_get_config(win)
if not cfg or cfg.relative == '' then table.insert(wins, win) end
if not cfg or cfg.relative == '' then
table.insert(wins, win)
end
end
end
return wins
Expand Down Expand Up @@ -82,20 +88,26 @@ describe('rovo-dev.nvim', function()

it('appends flags to cmd for list and string forms', function()
local captured
stub_termopen(function(cmd) captured = cmd end)
stub_termopen(function(cmd)
captured = cmd
end)

local rovo = require('rovo-dev')
rovo.setup({ terminal = { cmd = { 'acli', 'rovodev', 'run' } } })

rovo.toggle({ '--verbose', '--restore' })

assert.is_true(type(captured) == 'table')
eq('--verbose', captured[#captured-1])
eq('--verbose', captured[#captured - 1])
eq('--restore', captured[#captured])

-- string form
unload_rovo(); reset_state(); captured = nil
stub_termopen(function(cmd) captured = cmd end)
unload_rovo()
reset_state()
captured = nil
stub_termopen(function(cmd)
captured = cmd
end)
rovo = require('rovo-dev')
rovo.setup({ terminal = { cmd = 'acli rovodev run' } })
rovo.toggle({ '--shadow' })
Expand Down Expand Up @@ -132,4 +144,73 @@ describe('rovo-dev.nvim', function()

vim.notify = old_notify
end)

it('handles deleted files correctly without repeated notifications', function()
stub_termopen()
local rovo = require('rovo-dev')
rovo.setup({})
rovo.toggle()

-- Create a temporary file
local test_file = 'tmp_rovodev_deleted_test.txt'
vim.fn.writefile({ 'test content' }, test_file)

-- Open the file in a buffer
vim.cmd('edit ' .. test_file)
local file_buf = vim.api.nvim_get_current_buf()

local notifications = {}
local old_notify = vim.notify
vim.notify = function(msg, level, opts)
table.insert(notifications, { msg = msg, level = level, opts = opts })
end

-- Delete the file externally
vim.fn.delete(test_file)

-- Trigger FileChangedShellPost multiple times
vim.api.nvim_exec_autocmds('FileChangedShellPost', { buffer = file_buf })
vim.api.nvim_exec_autocmds('FileChangedShellPost', { buffer = file_buf })
vim.api.nvim_exec_autocmds('FileChangedShellPost', { buffer = file_buf })

-- Should only get one notification about deletion
eq(1, #notifications)
assert.is_truthy(notifications[1].msg:match('File deleted:'))
eq(vim.log.levels.WARN, notifications[1].level)

-- Test recreation resets the flag via external write
notifications = {}
vim.fn.writefile({ 'recreated' }, test_file)
vim.api.nvim_exec_autocmds('FileChangedShellPost', { buffer = file_buf })

eq(1, #notifications)
assert.is_truthy(notifications[1].msg:match('Reloaded from disk:'))
eq(vim.log.levels.INFO, notifications[1].level)

-- Delete again - should notify again
notifications = {}
vim.fn.delete(test_file)
vim.api.nvim_exec_autocmds('FileChangedShellPost', { buffer = file_buf })
vim.api.nvim_exec_autocmds('FileChangedShellPost', { buffer = file_buf })

eq(1, #notifications)
assert.is_truthy(notifications[1].msg:match('File deleted:'))

-- Now recreate by writing the buffer (simulates user :write)
notifications = {}
vim.api.nvim_buf_set_lines(file_buf, 0, -1, false, { 'recreated by buffer write' })
vim.cmd('write')
-- Writing should clear the deletion flag via BufWritePost; no notification expected here
eq(0, #notifications)

-- Delete again - should notify again after buffer write recreation
vim.fn.delete(test_file)
vim.api.nvim_exec_autocmds('FileChangedShellPost', { buffer = file_buf })

eq(1, #notifications)
assert.is_truthy(notifications[1].msg:match('File deleted:'))

vim.notify = old_notify
vim.cmd('bdelete! ' .. file_buf)
end)
end)