I figured out how to set up a file watcher from your browser to zig
const std =
@import
("std");
// Add Emscripten extern functions
pub extern fn emscripten_run_script(script: [*:0]const u8) void;
pub extern fn emscripten_async_run_script(script: [*:0]const u8, millis: i32) void;
pub extern fn emscripten_run_script_int(script: [*:0]const u8) i32;
pub extern fn emscripten_run_script_string(script: [*:0]const u8) [*:0]const u8;
// Error codes enum
pub const FileWatcherError = error{
FileNotFound,
PermissionDenied,
ConnectionLost,
ReadError,
UserCancelled,
NotConnected,
InvalidFile,
Unknown,
};
// Callback function pointer types
pub const FileChangeCallback = *const fn (content: []const u8) void;
pub const FileErrorCallback = *const fn (err: FileWatcherError) void;
// Global state struct
const State = struct {
change_fn: ?FileChangeCallback,
error_fn: ?FileErrorCallback,
const Self =
@This
();
fn init() Self {
return .{
.change_fn = null,
.error_fn = null,
};
}
};
// Single global instance
var state = State.init();
// C callback implementations
export fn on_file_changed(content: [*:0]const u8) callconv(.C) void {
if (state.change_fn) |callback| {
const content_slice = std.mem.span(content);
callback(content_slice);
}
}
export fn on_file_error(error_code: c_int) callconv(.C) void {
if (state.error_fn) |callback| {
const err = switch (error_code) {
1 => FileWatcherError.FileNotFound,
2 => FileWatcherError.PermissionDenied,
3 => FileWatcherError.ConnectionLost,
4 => FileWatcherError.ReadError,
5 => FileWatcherError.UserCancelled,
6 => FileWatcherError.NotConnected,
7 => FileWatcherError.InvalidFile,
else => FileWatcherError.Unknown,
};
callback(err);
}
}
// The JavaScript code as a string
const js_setup =
\\if (!Module.fileWatcher) {
\\ const watchFile = function(opts) {
\\
\\ const onChange = opts.onChange || function() {};
\\ const onError = opts.onError || function() {};
\\ const interval = opts.interval || 1000;
\\
\\ const ERROR = {
\\ FILE_NOT_FOUND: 1,
\\ PERMISSION_DENIED: 2,
\\ CONNECTION_LOST: 3,
\\ READ_ERROR: 4,
\\ USER_CANCELLED: 5,
\\ NOT_CONNECTED: 6,
\\ INVALID_FILE: 7,
\\ UNKNOWN: 999
\\ };
\\ let fileHandle = null;
\\ let previousContent = null;
\\ let watchInterval = null;
\\
\\ async function readFile() {
\\ try {
\\ if (!fileHandle) throw { code: ERROR.NOT_CONNECTED };
\\ const file = await fileHandle.getFile();
\\ const content = await file.text();
\\ if (content !== previousContent) {
\\ previousContent = content;
\\ onChange(content);
\\ }
\\ } catch (error) {
\\ console.error('bridge error', error);
\\ onError({ code: error.code || http://ERROR.READ_ERROR });
\\ }
\\ }
\\
\\ function startWatching() {
\\ watchInterval = setInterval(readFile, interval);
\\ }
\\
\\ function stopWatching() {
\\ if (watchInterval) {
\\ clearInterval(watchInterval);
\\ watchInterval = null;
\\ }
\\ }
\\
\\ return {
\\ async connect() {
\\ try {
\\ [fileHandle] = await window.showOpenFilePicker();
\\ const file = await fileHandle.getFile();
\\ previousContent = await file.text();
\\ startWatching();
\\ return file;
\\ } catch (error) {
\\ onError({ code: http://error.name === 'AbortError' ? ERROR.USER_CANCELLED : ERROR.UNKNOWN });
\\ return null;
\\ }
\\ },
\\ disconnect() {
\\ fileHandle = null;
\\ previousContent = null;
\\ stopWatching();
\\ }
\\ };
\\ };
\\
\\ Module.fileWatcher = watchFile({
\\ onChange: function(content) {
\\ if (typeof Module._on_file_changed !== 'function') {
\\ console.error('on_file_changed is not available');
\\ return;
\\ }
\\ var lengthBytes = lengthBytesUTF8(content) + 1;
\\ var ptr = _malloc(lengthBytes);
\\ stringToUTF8(content, ptr, lengthBytes);
\\ Module._on_file_changed(ptr);
\\ _free(ptr);
\\ },
\\ onError: function(error) {
\\ if (typeof Module._on_file_error !== 'function') {
\\ console.error('on_file_error is not available');
\\ return;
\\ }
\\ Module._on_file_error(error.code);
\\ }
\\ });
\\}
;
// Public API
pub const FileWatcher = struct {
pub fn init(
change_cb: FileChangeCallback,
error_cb: FileErrorCallback,
) void {
state.change_fn = change_cb;
state.error_fn = error_cb;
emscripten_run_script(js_setup);
}
pub fn connect() void {
emscripten_run_script("if (Module.fileWatcher) Module.fileWatcher.connect();");
}
pub fn disconnect() void {
emscripten_run_script("if (Module.fileWatcher) Module.fileWatcher.disconnect();");
}
};