Very fast changes to files on disk after they are saved are not detected on linux
In both textadept 12.9 and nightly, very fast changes to a file on disk after they are saved are not detected by TA and do not pop up a reload prompt or fire a FILE_CHANGED event.
This causes problems when using textadept as an editor for languages such as uiua, where the typical development flow is to leave the compiler watching for changes and modifying the source file when it detects it has been saved (for reformatting, updating special comments within the file with information for debugging, etc). This often happens faster than Textadept detects as a changed file after a save, and the user is not prompted to reload the file.
My exact distro is Fedora Silverblue 42, 64-bit, running textadept from the linux executables in the README (for both 12.9 and nightly). I've reproduced this in a fresh install with no configuration changes.
Reproduction
the smallest reproducing case I could find was to run inotifywait -e close test.txt | (read; echo 'change!' >> test.txt) and change the test.txt file in textadept and save it. TA doesn't prompt to reload the change to the file.
To reproduce with uiua: run uiua in the same directory as a file with the .ua extension open in textadept. Enter some uiua code like self * range 10 and save the file. The compiler will reformat the file, and it will have changed on disk, but textadept does not register the change.
I think this issue is multifaceted. One part is that Textadept is not checking in those scenarios.
It says it's running uiua in the background, and the launched inotifywait will run whilst Textadept is focused as inotifywait waits to detect a change from Textadept first before changing the file again.
But Textadept only tries to notice a file change when the Window comes back into focus or when buffer/view is switched, which simply isn't happening as it stays in focus in those scenarios.
https://github.com/orbitalquark/textadept/blob/fc2c57441df28fcfca6ea39673c1835595064f8c/core/file_io.lua#L277-L290
Unless it forces Textadept to constantly check the file for changes I'm unsure what the solution is. Maybe something like this in its init.lua would work?
local function update_modified_file()
if not buffer.filename then return end
local mod_time = lfs.attributes(buffer.filename, 'modification')
if mod_time and buffer.mod_time and buffer.mod_time < mod_time then
buffer.mod_time = mod_time
events.emit(events.FILE_CHANGED, buffer.filename)
end
end
events.connect('check_timer', update_modified_file)
timeout(0.2, function ()
events.emit('check_timer')
return true
end)
However even this has issues, as lfs.attributes only has so much resolution and inotifywait command happens within milliseconds of the change from Textadept it often doesn't detect the change with that. If I modify to add a sleep the above works: inotifywait -e close test.txt | (read; sleep 1; echo 'change!' >> test.txt) I'm unsure how responsive it needs Textadept to be for uiua?
Edit: Correct pronouns. Sorry about before 😞
Ah, I misunderstood how textadept was checking for file changes and assumed something like inotify was being used rather than it checking for changes based on comparing modification times when some events fire. Presumably changing this in the core would require using inotify or similar, which is both platform-specific and that may be adding more external deps for a very niche use case. If this is just out of scope or not reasonable given TA's other goals I understand. I'll leave this open, but understand that this may just be something that is out of scope for core TA.
Thanks for the ideas for workarounds, I'll try those.
I don't have a Linux desktop setup at the moment, but have you seen this library? Mitchell's debugger and file-diff modules pull in external C libraries so I would imagine pulling in that library would work. The other problem you have of course is that Textadept is single-threaded, so asynchronous file change notifications would require Lanes or something. But if granularity of timestamps is a problem, you could try using a timeout callback like @M0JXD proposed in combination with a coroutine that polls inotify using that library. The timeout callback uses GTK's and Qt's built-in event queue, and every 50ms or so in the curses version.
This probably wouldn't be a core feature because every platform has its own way of doing this. Linux uses inotify, MacOS uses fsevents, and Windows has its own version, and they're all finicky and slightly incompatible, not to mention the threading issue.
@ilo-3a29 I was just reading the docs again and realized Textadept has a platform independent os.spawn() function that works perfectly for this. (Can't believe I missed that one.) All you need to do is install the (also platform independent) fswatch and then add the below snippet to your ~/.textadept/init.lua. (Or you could use inotifywait in its place in the below function if you don't care about platform independence). If you're working on a Uiua program in a buffer, just press the key combo that you assign the function to (ctrl+shift+w in this case) to have Textadept auto-update the buffer when the file is changed externally. I tested this on my Mac by having a shell process append to an open buffer in the background and text shows up instantly in the buffer in Textadept with no prompting.
keys['ctrl+W'] = function ()
local b = buffer
if not b.filename then return end -- file is not saved to disk
local pattern = string.gsub(b.filename, '[^%w]', '%%%0') -- work around Lua patterns in filename.
local function update(stdout)
if string.find(stdout, pattern) then
b:set_save_point() -- mark the file so reload will overwrite
b:reload()
end
end
local fswatch = os.spawn('fswatch '..b.filename, update)
local function deleted(buf) -- cleanup fswatch when the buffer is closed
if buf.filename and string.match(buf.filename, pattern) then
events.disconnect(events.BUFFER_DELETED, deleted)
if fswatch then fswatch:kill() end
end
end
events.connect(events.BUFFER_DELETED, deleted)
end
If that works for you that should be enough for you to close this issue.