repositories / dotfiles
dotfiles
bugabingas dorkfiles
owned by admin
neovim/tests/drive/neovide.lua
Raw---@diagnostic disable: need-check-nil, undefined-field, param-type-mismatch, assign-type-mismatch
-- The checks that a headless neovim can not answer: does this actually behave
-- when a real GUI is painting it.
-- Run with: just drive-neovide
--
-- Everything is driven over the RPC socket of the neovim that neovide is
-- attached to. No mouse, no synthetic keystrokes, no foreground window
-- stealing, so it does not fight the person sitting in front of the machine.
if vim.fn.executable 'neovide' == 0 then
print 'neovide is not installed, skipping'
os.exit( 0 )
end
local ADDRESS = '127.0.0.1:' .. tostring( 6200 + math.random( 1, 180 ) )
local fixture = vim.fn.tempname()
vim.fn.mkdir( fixture, 'p' )
fixture = fixture .. '/watched.txt'
vim.fn.writefile( vim.fn.range( 1, 40 ), fixture )
-- the real configuration on purpose: this checks what a person actually sees,
-- including the notification plugin. `-i NONE` keeps it out of the shada file.
local server = vim.system( {
vim.v.progpath,
'--headless',
'-i', 'NONE',
'--listen', ADDRESS,
fixture,
}, { stdout = false, stderr = false, } )
local channel
local connected = vim.wait( 20000, function ()
local ok, value = pcall( vim.fn.sockconnect, 'tcp', ADDRESS, { rpc = true, } )
if ok and value ~= 0 then
channel = value
return true
end
return false
end, 100 )
if not connected then
io.stderr:write( 'cannot reach the driven neovim at ' .. ADDRESS .. '\n' )
server:kill 'sigterm'
os.exit( 1 )
end
local remote = function ( code, arguments )
return vim.rpcrequest( channel, 'nvim_exec_lua', code, arguments or {} )
end
remote( [[
require 'bugabinga.file_changed'
return package.loaded['bugabinga.file_changed'] ~= nil
]] )
-- `--no-fork` keeps the window in the process we spawned, so it can be
-- cleaned up again
local gui = vim.system( { 'neovide', '--no-fork', '--server', ADDRESS, },
{ stdout = false, stderr = false, } )
local attached = vim.wait( 30000, function ()
return remote [[ return #vim.api.nvim_list_uis() > 0 ]] == true
end, 200 )
local finish = function ( code )
pcall( vim.fn.chanclose, channel )
pcall( function () server:kill 'sigterm' end )
pcall( function () gui:kill 'sigterm' end )
vim.fn.delete( vim.fs.dirname( fixture ), 'rf' )
os.exit( code )
end
if not attached then
io.stderr:write( 'neovide never attached to ' .. ADDRESS .. '\n' )
finish( 1 )
end
local checks, failures = {}, 0
local check = function ( name, body )
local ok, failure = pcall( body )
checks[#checks + 1] = ( ok and 'ok ' or 'FAIL ' ) .. name
if not ok then
failures = failures + 1
checks[#checks] = checks[#checks] .. ': ' .. tostring( failure )
end
end
local expect = function ( condition, message )
if not condition then error( message or 'expectation failed', 2 ) end
end
local target = fixture
--- binary mode, so lua does not turn every \n into \r\n behind our back
local write_file = function ( first, last )
local handle = assert( io.open( target, 'wb' ) )
for number = first, last do handle:write( number .. '\n' ) end
handle:close()
end
--- every floating window the attached UI is currently painting
local floats = function ()
return remote [[
local found = {}
for _, window in ipairs( vim.api.nvim_list_wins() ) do
local config = vim.api.nvim_win_get_config( window )
if config.relative ~= '' then
local buffer = vim.api.nvim_win_get_buf( window )
table.insert( found, {
width = config.width,
height = config.height,
filetype = vim.bo[buffer].filetype,
text = table.concat( vim.api.nvim_buf_get_lines( buffer, 0, -1, false ), '\n' ),
} )
end
end
return found
]]
end
-- start from a clean layout, so a previous run can not poison this one
remote [[
vim.cmd 'silent! diffoff!'
vim.cmd 'silent! only'
local current = vim.api.nvim_get_current_buf()
for _, buffer in ipairs( vim.api.nvim_list_bufs() ) do
if buffer ~= current and vim.api.nvim_buf_get_name( buffer ) == '' then
pcall( vim.api.nvim_buf_delete, buffer, { force = true } )
end
end
vim.cmd 'silent! edit!'
return true
]]
remote [[
_G.T = { reloads = 0, notes = {}, focus = {} }
vim.api.nvim_create_autocmd( 'FileChangedShellPost', {
callback = function() _G.T.reloads = _G.T.reloads + 1 end,
} )
vim.api.nvim_create_autocmd( { 'FocusGained', 'FocusLost' }, {
callback = function( event ) table.insert( _G.T.focus, event.event ) end,
} )
-- the server outlives this script, so only ever wrap the real notify once
_G.T_NOTIFY = _G.T_NOTIFY or vim.notify
vim.notify = function( message, ... )
table.insert( _G.T.notes, message )
return _G.T_NOTIFY( message, ... )
end
return true
]]
local reset = function ()
remote [[
_G.T.reloads = 0
_G.T.notes = {}
pcall( vim.api.nvim_win_set_cursor, 0, { 15, 0 } )
return true
]]
end
local state = function ()
return remote [[
return {
reloads = _G.T.reloads,
notes = _G.T.notes,
focus = _G.T.focus,
cursor = vim.api.nvim_win_get_cursor( 0 )[1],
text = vim.fn.getline '.',
first = vim.fn.getline( 1 ),
lines = vim.api.nvim_buf_line_count( 0 ),
modified = vim.bo.modified,
mode = vim.api.nvim_get_mode().mode,
}
]]
end
local await_reload = function ()
return vim.wait( 8000, function ()
return remote [[ return _G.T.reloads > 0 ]] == true
end, 100 )
end
--[[ CHECKS ]]
check( 'a real neovide UI is attached', function ()
local uis = remote [[ return vim.api.nvim_list_uis() ]]
expect( #uis == 1, 'expected one attached UI, got ' .. tostring( #uis ) )
expect( uis[1].width > 0 and uis[1].height > 0, 'the UI has no grid' )
end )
check( 'a change on disk reloads and the cursor stays put', function ()
reset()
write_file( 101, 140 )
expect( await_reload(), 'the change was never noticed' )
local now = state()
expect( now.text == '115', 'expected 115 on the cursor line, got ' .. vim.inspect( now.text ) )
expect( now.cursor == 15, 'the cursor moved to line ' .. tostring( now.cursor ) )
expect( #now.notes == 1, 'expected one notification, got ' .. tostring( #now.notes ) )
end )
check( 'the reload notification is a real floating window in the UI', function ()
reset()
write_file( 151, 190 )
expect( await_reload(), 'the change was never noticed' )
local painted = floats()
expect( #painted > 0, 'no floating window is being painted' )
local found = false
for _, float in ipairs( painted ) do
if float.text:find( 'reloaded', 1, true ) then found = true end
end
expect( found, 'the reload notification is not on screen: ' .. vim.inspect( painted ) )
end )
check( 'nothing is reloaded under your fingers in insert mode', function ()
reset()
local before = state().text
remote [[ vim.api.nvim_input 'i' ; return true ]]
vim.uv.sleep( 500 )
expect( state().mode == 'i', 'could not get into insert mode' )
write_file( 201, 240 )
vim.uv.sleep( 5000 )
local now = state()
expect( now.mode == 'i', 'insert mode was left on its own' )
expect( now.reloads == 0, 'reloaded while typing' )
expect( now.text == before,
'the buffer changed under the cursor: ' .. vim.inspect( before ) .. ' -> ' .. vim.inspect( now.text ) )
end )
check( 'leaving insert mode reloads immediately', function ()
remote [[ vim.api.nvim_input '<Esc>' ; return true ]]
expect( await_reload(), 'no reload after leaving insert mode' )
local now = state()
expect( now.text == '215', 'expected 215, got ' .. vim.inspect( now.text ) )
expect( now.cursor == 15, 'the cursor moved to line ' .. tostring( now.cursor ) )
end )
check( 'undo brings the version from before the reload back', function ()
remote [[ vim.api.nvim_input 'u' ; return true ]]
vim.uv.sleep( 1500 )
local now = state()
expect( now.first == '151',
'undo did not restore the text from before the reload: ' .. vim.inspect( now.first ) )
expect( now.modified, 'the buffer is not marked modified after undoing a reload' )
remote [[ vim.cmd 'silent! edit!' ; return true ]]
vim.uv.sleep( 1000 )
end )
check( 'unsaved edits are never overwritten', function ()
reset()
remote [[
vim.api.nvim_buf_set_lines( 0, 14, 15, false, { 'MY UNSAVED EDIT' } )
return true
]]
write_file( 301, 340 )
vim.uv.sleep( 4500 )
local now = state()
expect( now.reloads == 0, 'reloaded over unsaved changes' )
expect( now.text == 'MY UNSAVED EDIT', 'lost the local edit: ' .. vim.inspect( now.text ) )
expect( #now.notes == 1, 'expected one warning, got ' .. tostring( #now.notes ) )
expect( now.notes[1]:find( 'DiffDisk', 1, true ) ~= nil, 'the warning offers no way out' )
end )
check( 'the conflict warning is legible in the UI', function ()
local painted = floats()
local warning
for _, float in ipairs( painted ) do
if float.text:find( 'changed on disk', 1, true ) then warning = float end
end
expect( warning, 'the conflict warning is not on screen: ' .. vim.inspect( painted ) )
expect( warning.text:find( 'DiffDisk', 1, true ) ~= nil, 'the way out is cut off' )
expect( warning.height >= 2, 'the warning is squeezed into one line' )
end )
check( 'a second conflict is reported again after :e!', function ()
remote [[ vim.cmd 'edit!' ; return true ]]
reset()
remote [[
vim.api.nvim_buf_set_lines( 0, 14, 15, false, { 'ANOTHER UNSAVED EDIT' } )
return true
]]
write_file( 401, 440 )
vim.uv.sleep( 4500 )
local now = state()
expect( now.reloads == 0, 'reloaded over unsaved changes' )
expect( #now.notes == 1, 'the warning was swallowed, got ' .. tostring( #now.notes ) )
end )
check( 'the buffer survives the file being deleted', function ()
reset()
assert( os.remove( target ) )
vim.uv.sleep( 4500 )
local now = state()
expect( now.text == 'ANOTHER UNSAVED EDIT', 'the text died with the file' )
expect( #now.notes == 1, 'expected one warning, got ' .. tostring( #now.notes ) )
expect( now.notes[1]:find( 'gone from disk', 1, true ) ~= nil,
'unexpected warning: ' .. tostring( now.notes[1] ) )
end )
check( 'DiffDisk opens a real side by side diff', function ()
remote [[ vim.cmd 'write' ; return true ]]
reset()
write_file( 501, 540 )
expect( await_reload(), 'the change was never noticed' )
local diffed = remote [[
vim.api.nvim_buf_set_lines( 0, 0, 1, false, { 'ONLY IN THE BUFFER' } )
vim.cmd 'DiffDisk'
local original = vim.api.nvim_get_current_buf()
local in_diff, disk = 0, nil
for _, window in ipairs( vim.api.nvim_tabpage_list_wins( 0 ) ) do
if vim.wo[window].diff then
in_diff = in_diff + 1
local buffer = vim.api.nvim_win_get_buf( window )
if buffer ~= original then
disk = vim.api.nvim_buf_get_lines( buffer, 0, 1, false )[1]
end
end
end
vim.cmd 'redraw'
return { in_diff = in_diff, disk = disk }
]]
expect( diffed.in_diff == 2, 'expected two diff windows, got ' .. tostring( diffed.in_diff ) )
expect( diffed.disk == '501',
'the scratch side does not hold the disk version: ' .. vim.inspect( diffed.disk ) )
remote [[ vim.cmd 'diffoff!' ; vim.cmd 'only' ; vim.cmd 'silent! edit!' ; return true ]]
end )
--[[ REPORT ]]
print( table.concat( checks, '\n' ) )
-- focus reporting can not be produced from inside neovim: it needs the window
-- manager to actually move focus. report what was observed instead of pretending.
local focus = state().focus
print( ( '\nfocus events seen while driving: %d %s' )
:format( #focus, vim.inspect( focus ) ) )
print( failures == 0 and '\nall neovide checks passed'
or ( '\n' .. tostring( failures ) .. ' check(s) failed' ) )
finish( failures == 0 and 0 or 1 )