---
name: file_watcher
kind: function
lang: cpp
domain: core
version: "1.0.0"
purity: impure
signature: "fn::FileWatcher* fn::file_watcher_create(); void fn::file_watcher_destroy(fn::FileWatcher*); bool fn::file_watcher_add(fn::FileWatcher*, const char* path); std::vector<fn::FileEvent> fn::file_watcher_poll(fn::FileWatcher*); const char* fn::file_watcher_last_error(const fn::FileWatcher*)"
description: "Watcher de archivos/directorios cross-platform (Linux inotify, Windows ReadDirectoryChangesW). API no bloqueante: registra paths con add() y consulta eventos con poll(). Cada poll() drena todos los eventos pendientes desde la llamada anterior."
tags: [filesystem, watcher, inotify, file_events, io]
uses_functions: ["file_poll_diff_cpp_core"]
uses_types: []
returns: []
returns_optional: false
error_type: "error_go_core"
imports: [unistd, sys/inotify, windows]
tested: false
tests: []
test_file_path: ""
file_path: "cpp/functions/core/file_watcher.cpp"
params: []
output: "FileWatcher opaco con cola interna de eventos. poll() devuelve std::vector<FileEvent> con {path, kind in {Modified, Created, Deleted}}. Errores acumulados en last_error()."
notes: "consumido por cpp/apps/text_editor_smoke/main.cpp; scaffolding/demo en primitives_gallery"
---

# file_watcher

Watcher de archivos no bloqueante con backend nativo por plataforma. Pareja natural de `text_editor_cpp_core` para el ciclo "edita -> guarda -> recompila" sin polling de timestamps.

## API

```cpp
namespace fn {
    struct FileWatcher;
    struct FileEvent {
        enum Kind { Modified, Created, Deleted };
        std::string path;
        Kind kind;
    };

    FileWatcher* file_watcher_create();
    void         file_watcher_destroy(FileWatcher*);
    bool         file_watcher_add(FileWatcher*, const char* path);  // file or dir
    std::vector<FileEvent> file_watcher_poll(FileWatcher*);          // non-blocking, drain
    const char*  file_watcher_last_error(const FileWatcher*);
}
```

## Ejemplo

```cpp
#include "core/file_watcher.h"

auto* fw = fn::file_watcher_create();
fn::file_watcher_add(fw, "/tmp/shader.glsl");

while (running) {
    for (auto& ev : fn::file_watcher_poll(fw)) {
        switch (ev.kind) {
            case fn::FileEvent::Modified: reload(ev.path); break;
            case fn::FileEvent::Created:  std::printf("created: %s\n", ev.path.c_str()); break;
            case fn::FileEvent::Deleted:  std::printf("deleted: %s\n", ev.path.c_str()); break;
        }
    }
    sleep_ms(16);
}

fn::file_watcher_destroy(fw);
```

## Backends

| Plataforma | Mecanismo | Notas |
|-----------|-----------|-------|
| Linux     | `inotify_init1(IN_NONBLOCK)` + `inotify_add_watch` | mascara: MODIFY \| CREATE \| DELETE \| CLOSE_WRITE \| MOVED_*  |
| Windows   | `ReadDirectoryChangesW` overlapped + `GetOverlappedResult` no bloqueante | Para vigilar un archivo, registra el directorio padre y filtra por nombre |
| Otros     | Stub — `poll()` devuelve vector vacio | `last_error()` indica "platform not supported" |

## Limites y avisos

- **inotify watch limit (Linux)**: por defecto `fs.inotify.max_user_watches = 8192`. Si lo superas, `add()` devuelve false y `last_error()` reporta `No space left on device`. Subirlo con:
  ```bash
  sudo sysctl fs.inotify.max_user_watches=524288
  ```
- **Windows directorio-level**: cuando registras un archivo, internamente se vigila el directorio padre y se filtra por nombre exacto en el poll. Eventos de archivos hermanos se descartan.
- **No es recursivo** — `add()` registra el path dado, no su subarbol. Para vigilar un arbol, llama `add()` por cada subdirectorio (TODO si hace falta).
- **Editor "modify" coalescing**: editores como vim escriben usando un swap + rename, lo que produce CREATE + DELETE + MOVED_TO en vez de MODIFY puro. La mascara cubre MOVED_TO para que el evento llegue como `Created` (semantica "ahora hay un archivo nuevo en esa ruta") — el caller deduplica si lo necesita.

## Decisiones de diseño

- **PIMPL**: el header no expone `inotify_event` ni `OVERLAPPED`. `FileWatcher` es opaco.
- **No bloqueante**: el caller hace polling desde su loop principal (tipico ImGui ~60Hz). No threads, no callbacks. Mantenimiento bajo.
- **Errores como string**: no exception throwing. `add()` devuelve `bool` y `last_error()` da contexto. `error_type: stderr_string` en frontmatter.
- **Sin coalescing implicito**: el watcher emite todo lo que recibe del kernel. La app decide si dedup eventos cercanos en el tiempo.
