feat: watch: try pager mode..

This commit is contained in:
2025-04-04 02:09:43 +00:00
parent d800266fe0
commit c5e9d9c8cb
8 changed files with 6363 additions and 202 deletions

View File

@@ -25,7 +25,7 @@ stdenv.mkDerivation {
'';
meta = {
description = "hectic";
description = "hmpl";
license = lib.licenses.mit;
};
}

View File

@@ -66,154 +66,116 @@ void hmpl_render_interpolation_tags(Arena *arena, char **text_ptr, const Json *
}
}
// CREATE OR REPLACE FUNCTION common.render_template_loop_blocks(result TEXT, context JSONB)
// RETURNS TEXT LANGUAGE plpgsql AS $$
// DECLARE
// loop_start INT;
// key_end INT;
// loop_end INT;
// loop_key TEXT;
// block TEXT;
// rendered_block TEXT;
// arr JSONB;
// item JSONB;
// item_text TEXT;
// BEGIN
// LOOP
// loop_start := strpos(result, '{{#');
// EXIT WHEN loop_start = 0; -- Exit if no loop start found.
//
// -- Locate the end of the loop key marker.
// key_end := strpos(result, '}}', loop_start);
// IF key_end = 0 THEN
// RAISE EXCEPTION 'Malformed template: missing closing braces for loop start';
// END IF;
//
// -- Extract the key used for the loop.
// loop_key := substr_cloneing(result from loop_start + 3 for key_end - loop_start - 3);
//
// RAISE DEBUG 'loop key %', loop_key;
//
// -- Find the matching loop end marker for this key.
// loop_end := strpos(result, '{{/#' || loop_key || '}}', key_end);
// IF loop_end = 0 THEN
// RAISE EXCEPTION 'Malformed template: missing loop end for key %', loop_key;
// END IF;
//
// -- Extract the inner block of the loop.
// block := substr_cloneing(result from key_end + 2 for loop_end - key_end - 2);
//
// -- Retrieve the JSON array from the context for the loop key.
// arr := eval_value(context, loop_key);
// rendered_block := '';
//
// -- If an array is found, iterate over each element.
// IF arr IS NOT NULL AND jsonb_typeof(arr) = 'array' THEN
// FOR item IN SELECT * FROM jsonb_array_elements(arr) LOOP
// item_text := block; -- Begin with the raw block.
// IF jsonb_typeof(item) != 'object' THEN
// -- Replace interpolation for primitive values.
// item_text := replace(item_text, '{{.}}', item::text);
// ELSE
// -- For object values, iterate over each key/value.
// item_text := render_template_interpolations(item_text, item, '.'::CHAR(1));
// item_text := render_template_conditions(item_text, item, '.');
// END IF;
// rendered_block := rendered_block || item_text;
// END LOOP;
// END IF;
//
// -- Replace the entire loop block in the result with the rendered content.
// result := substr_cloneing(result from 1 for loop_start - 1)
// || rendered_block
// || substr_cloneing(result from loop_end + char_length('{{/#' || loop_key || '}}'));
// END LOOP;
//
// RETURN result;
// END $$;
// {{#array_key}}
void hmpl_render_section_tags(Arena *arena, char **text_ptr, Json *context, const char * const prefix_start, const char * const prefix_end, const char * const separator_pattern){
// {{item#array}}...{{/array}}
void hmpl_render_section_tags(Arena *arena, char **text_ptr, Json *context, const char * const prefix_start, const char * const prefix_end, const char * const separator_pattern) {
raise_debug("hmpl_render_section_tags(%p, %s, <optimized>, %s, %s, %s)", arena, *text_ptr, prefix_start, prefix_end, separator_pattern);
// Create search patterns
char start_pattern[32];
snprintf(start_pattern, sizeof(start_pattern), "{{%s", prefix_start);
int start_pattern_length = strlen(start_pattern);
Slice start_slice = slice_create(char, start_pattern, strlen(start_pattern), 0, strlen(start_pattern));
// TODO: rename close_tag_start_pattern
char end_pattern[32];
snprintf(end_pattern, sizeof(end_pattern), "{{%s", prefix_end);
int end_pattern_length = strlen(end_pattern);
int separator_pattern_length = strlen(separator_pattern);
if (!separator_pattern || separator_pattern_length == 0) {
// Create a mutable copy of separator_pattern
char separator_copy[32];
strncpy(separator_copy, separator_pattern, sizeof(separator_copy) - 1);
separator_copy[sizeof(separator_copy) - 1] = '\0';
Slice separator_slice = slice_create(char, separator_copy, strlen(separator_copy), 0, strlen(separator_copy));
if (separator_slice.len == 0) {
raise_exception("Unexpected usage: separator pattern cannot be empty");
}
int offset = 0;
// Create slice for the text
Slice text_slice = slice_create(char, *text_ptr, strlen(*text_ptr), 0, strlen(*text_ptr));
size_t offset = 0;
while (1) {
char *current_text = *text_ptr;
char *opening_tag_start = strstr(current_text + offset, start_pattern);
if (!opening_tag_start)
break;
int start_index = opening_tag_start - current_text;
int relative_key_start = start_index + start_pattern_length;
// Find tag start
char *text_data = (char*)text_slice.data;
char *opening_tag_start = strstr(text_data + offset, (char*)start_slice.data);
if (!opening_tag_start) break;
char *opening_tag_separator = strstr(opening_tag_start, separator_pattern);
if (!opening_tag_start) {
raise_exception("Malformed template: missing separator for section tag or not specifiet name for element");
// Create slice for separator search
size_t start_index = opening_tag_start - text_data;
Slice remaining_slice = slice_subslice(text_slice, start_index, text_slice.len - start_index);
// Find separator
char *opening_tag_separator = strstr((char*)remaining_slice.data, (char*)separator_slice.data);
if (!opening_tag_separator) {
raise_exception("Malformed template: missing separator for section tag or not specified name for element");
exit(1);
}
int separator_index = opening_tag_separator - current_text;
int element_name_start = separator_index + separator_pattern_length;
char *opening_tag_end = strstr(opening_tag_separator, "}}");
// Extract element name (now before separator)
size_t separator_index = opening_tag_separator - (char*)remaining_slice.data;
size_t element_name_start = start_slice.len;
size_t element_name_length = separator_index;
char *element_name = arena_alloc(arena, element_name_length + 1);
substr_clone((char*)remaining_slice.data, element_name, element_name_start, element_name_length);
element_name[element_name_length] = '\0';
// Find closing braces
Slice after_separator = slice_subslice(remaining_slice, separator_index + separator_slice.len,
remaining_slice.len - separator_index - separator_slice.len);
char *opening_tag_end = strstr((char*)after_separator.data, "}}");
if (!opening_tag_end) {
raise_exception("Malformed template: missing closing braces for section tag");
exit(1);
}
assert((size_t)opening_tag_end > (size_t)opening_tag_separator);
assert((size_t)opening_tag_separator > (size_t)opening_tag_start);
int key_length = (opening_tag_separator - current_text) - relative_key_start;
assert(key_length > 0);
// Extract key (now after separator)
size_t key_start = 0;
size_t key_length = opening_tag_end - (char*)after_separator.data;
char *key = arena_alloc(arena, key_length + 1);
substr_clone(current_text, key, relative_key_start, key_length);
substr_clone((char*)after_separator.data, key, key_start, key_length);
key[key_length] = '\0';
int element_name_length = (opening_tag_end - current_text) - element_name_start;
assert(element_name_length > 0);
// Create pattern for closing tag
char *close_tag_pattern = arena_alloc(arena, start_slice.len + key_length + 3); // +3 for "{{" and "}}"
snprintf(close_tag_pattern, start_slice.len + key_length + 3,
"{{%s%s}}", prefix_end, key);
char *element_name = arena_alloc(arena, element_name_length + 1);
substr_clone(current_text, element_name, element_name_start, element_name_length);
// Find closing tag
size_t after_opening_end = (opening_tag_end - (char*)after_separator.data) + 2;
Slice after_opening_slice = slice_subslice(after_separator, after_opening_end,
after_separator.len - after_opening_end);
int close_tag_patern_length = start_pattern_length + key_length + end_pattern_length;
char *close_tag_patern = arena_alloc(arena, close_tag_patern_length + 1);
snprintf(close_tag_patern, sizeof(*close_tag_patern), "%s%s%s", start_pattern, key, end_pattern);
// Find the exact closing tag by checking for complete tag pattern
char *close_tag = NULL;
char *search_start = (char*)after_opening_slice.data;
while ((search_start = strstr(search_start, "{{")) != NULL) {
if (strncmp(search_start, close_tag_pattern, strlen(close_tag_pattern)) == 0) {
close_tag = search_start;
break;
}
search_start += 2; // Move past the "{{" we found
}
char *close_tag = strstr(opening_tag_end + offset + 1, close_tag_patern);
if (!close_tag) {
raise_exception("Malformed template: missing loop end for key %s", key);
exit(1);
}
// Get array from context
Json *arr = eval_object(arena, context, key);
if (arr && arr->type == JSON_ARRAY) {
// Count array elements
size_t elem_count = 0;
for (Json *e = arr->child; e; e = e->next) elem_count++;
// Allocate memory for replacement
char *replacement = arena_alloc(arena, MEM_KiB * elem_count);
size_t offset = 0;
size_t replacement_offset = 0;
char *block_buff = arena_alloc(arena, MEM_KiB);
size_t relative_block_start = (size_t)opening_tag_end + 2 - (size_t)current_text;
raise_trace("relative_block_start: %p = %p - 2 - %p", opening_tag_end, current_text);
size_t block_len = (size_t)opening_tag_end - (size_t)close_tag - 2;
raise_trace("block_len %p = %p - %p - 2", block_len, opening_tag_end, close_tag);
assert(block_len > 0);
substr_clone(current_text, block_buff, relative_block_start, block_len);
// Extract template block
size_t block_start = after_opening_end;
size_t block_length = (close_tag - (char*)after_opening_slice.data);
char *block_buff = arena_alloc(arena, block_length + 1);
substr_clone((char*)after_opening_slice.data, block_buff, block_start, block_length);
block_buff[block_length] = '\0';
// Process each array element
for (Json *elem = arr->child; elem; elem = elem->next) {
char *block = arena_strdup(arena, block_buff);
@@ -224,20 +186,26 @@ void hmpl_render_section_tags(Arena *arena, char **text_ptr, Json *context, cons
raise_trace("block after: %s", block);
size_t block_len = strlen(block);
memcpy(replacement + offset, block, block_len);
offset += block_len;
memcpy(replacement + replacement_offset, block, block_len);
replacement_offset += block_len;
}
replacement[offset] = '\0';
replacement[replacement_offset] = '\0';
raise_trace("replacement: %s", replacement);
char *new_text = arena_repstr(arena, current_text,
(size_t)opening_tag_start - 1,
close_tag + close_tag_patern_length - opening_tag_start + 2,
replacement);
// Calculate replacement positions
size_t replace_start = start_index;
size_t replace_length = (close_tag - (char*)after_opening_slice.data) +
start_slice.len + key_length + 2;
// Perform replacement
char *new_text = arena_repstr(arena, (char*)text_slice.data, replace_start, replace_length, replacement);
*text_ptr = new_text;
// Update text slice
text_slice = slice_create(char, new_text, strlen(new_text), 0, strlen(new_text));
}
offset = start_index;
}
}

View File

@@ -2,6 +2,8 @@
# Usage: make.sh [build|check] [--norun] [--debug] [--color]
# Options:
# build Build the library and app (default if no mode is provided).
# watch Build the library and app and watch for changes.
# run Build and run the app.
# check Build tests; runs them unless --norun is specified.
# --norun (check only) Build tests but do not run them.
# --debug Build with -O0 (debug mode).
@@ -9,7 +11,7 @@
# help, --help Show this help message.
check_dependencies() {
for dep in cc ar; do
for dep in cc ar entr pager; do
if ! command -v "$dep" >/dev/null 2>&1; then
echo "Error: Required dependency '$dep' not found." >&2
exit 1
@@ -22,6 +24,8 @@ print_help() {
cat <<EOF
Usage: $0 [build|check] [--norun] [--debug] [--color]
build Build the library and app (default).
watch Build the library and app and watch for changes.
run Build and run the app.
check Build tests; runs them unless --norun is specified.
--norun (check only) Build tests but do not run them.
--debug Build with debug flags (-O0).
@@ -74,8 +78,7 @@ if [ -n "$COLOR_FLAG" ]; then
CFLAGS="$CFLAGS $COLOR_FLAG"
fi
case "$MODE" in
build)
build() {
mkdir -p target
echo "# Build library"
# shellcheck disable=SC2086
@@ -85,10 +88,20 @@ case "$MODE" in
echo "# Build app"
# shellcheck disable=SC2086
cc $CFLAGS $OPTFLAGS main.c -Ltarget -lhmpl $LDFLAGS -o target/hmpl
}
case "$MODE" in
watch)
entr -r sh ./make.sh build | pager
;;
build)
build
;;
run)
build && ./target/hmpl
;;
check)
mkdir -p target/test
export LOG_LEVEL=TRACE
for test_file in test/*.c; do
exe="target/test/$(basename "${test_file%.c}")"
# shellcheck disable=SC2086

6063
package/c/hmpl/test.log Normal file

File diff suppressed because it is too large Load Diff

View File

@@ -101,7 +101,7 @@
"}"
#define TEST_DATA_SIMPLE_SECTION_ITERATION_TEMPLATE \
"{{#array element}}" \
"{{#element array}}" \
" {{element.field.subfield}}" \
"{{/array}}"
@@ -110,78 +110,130 @@
"value2" \
"value3"
#define TEST_DATA_COMPLEX_SECTION_ITERATION_CONTEXT \
"{" \
" \"users\": [" \
" { \"name\": \"John\", \"age\": 30 }," \
" { \"name\": \"Jane\", \"age\": 25 }" \
" ]" \
"}"
#define TEST_DATA_COMPLEX_SECTION_ITERATION_TEMPLATE \
"{{#user users}}" \
" Name: {{user.name}}, Age: {{user.age}}\n" \
"{{/users}}"
#define TEST_DATA_COMPLEX_SECTION_ITERATION_RESULT \
" Name: John, Age: 30\n" \
" Name: Jane, Age: 25\n"
void test_eval_single_level_key(Arena *arena) {
raise_notice("Testing single level key evaluation");
const char *context_text = arena_strdup(arena, "{\"name\": \"world\"}");
Json *context = json_parse(arena, &context_text);
if (!context) { raise_exception("Malformed json"); exit(1); }
char *result = eval_string(arena, context, "name");
raise_debug("eval result: %s", result);
raise_notice("Context: %s", json_to_string(arena, context));
raise_notice("Query: name");
raise_notice("Result: %s", result);
assert(result && strcmp(result, "world") == 0);
}
void test_eval_nested_key(Arena *arena) {
raise_notice("Testing nested key evaluation");
const char *context_text = arena_strdup(arena, "{\"person\": {\"name\": \"Alice\"}}");
Json *context = json_parse(arena, &context_text);
if (!context) { raise_exception("Malformed json"); exit(1); }
char *result = eval_string(arena, context, "person.name");
raise_notice("context: %s, eval result: %s", json_to_string(arena, context), result);
raise_notice("Context: %s", json_to_string(arena, context));
raise_notice("Query: person.name");
raise_notice("Result: %s", result);
assert(result && strcmp(result, "Alice") == 0);
}
void test_render_interpolation_tags(Arena *arena) {
raise_trace("test_render_interpolation_tags(arena)");
raise_notice("Testing interpolation tags without prefix");
const char *context_text = arena_strdup(arena, TEST_DATA_INTERPOLATION_CONTEXT);
Json *context = json_parse(arena, &context_text);
if (!context) { raise_exception("Malformed json"); exit(1); }
char *text = arena_strdup(arena, TEST_DATA_INTERPOLATION_TEMPLATE);
raise_notice("Template:\n%s", text);
raise_notice("Context: %s", json_to_string(arena, context));
hmpl_render_interpolation_tags(arena, &text, context, "");
raise_trace("text: %s", text);
raise_notice("Result:\n%s", text);
assert(strcmp(text, TEST_DATA_INTERPOLATION_RESULT) == 0);
}
void test_render_interpolation_tags_with_prefix(Arena *arena) {
raise_notice("Testing interpolation tags with prefix");
const char *context_text = arena_strdup(arena, TEST_DATA_INTERPOLATION_WITH_PREFIX_CONTEXT);
Json *context = json_parse(arena, &context_text);
if (!context) { raise_exception("Malformed json"); exit(1); }
char *text = arena_strdup(arena, TEST_DATA_INTERPOLATION_WITH_PREFIX_TEMPLATE);
raise_notice("Template:\n%s", text);
raise_notice("Context: %s", json_to_string(arena, context));
hmpl_render_interpolation_tags(arena, &text, context, ".");
raise_notice("Result:\n%s", text);
assert(strcmp(text, TEST_DATA_INTERPOLATION_WITH_PREFIX_RESULT) == 0);
}
void test_render_section_tags(Arena *arena) {
raise_notice("Testing simple section tags");
const char *context_text = arena_strdup(arena, TEST_DATA_SIMPLE_SECTION_ITERATION_CONTEXT);
Json *context = json_parse(arena, &context_text);
if (!context) { raise_exception("Malformed json"); exit(1); }
char *text = arena_strdup(arena, TEST_DATA_SIMPLE_SECTION_ITERATION_TEMPLATE);
raise_notice("Template:\n%s", text);
raise_notice("Context: %s", json_to_string(arena, context));
hmpl_render_section_tags(arena, &text, context, "#", "/", " ");
raise_notice("Result:\n%s", text);
assert(strcmp(text, TEST_DATA_SIMPLE_SECTION_ITERATION_RESULT) == 0);
}
void test_render_complex_section_tags(Arena *arena) {
raise_notice("Testing complex section tags");
const char *context_text = arena_strdup(arena, TEST_DATA_COMPLEX_SECTION_ITERATION_CONTEXT);
Json *context = json_parse(arena, &context_text);
if (!context) { raise_exception("Malformed json"); exit(1); }
char *text = arena_strdup(arena, TEST_DATA_COMPLEX_SECTION_ITERATION_TEMPLATE);
raise_notice("Template:\n%s", text);
raise_notice("Context: %s", json_to_string(arena, context));
hmpl_render_section_tags(arena, &text, context, "#", "/", " ");
raise_notice("Result:\n%s", text);
assert(strcmp(text, TEST_DATA_COMPLEX_SECTION_ITERATION_RESULT) == 0);
}
int main(void) {
init_logger();
raise_notice("Starting HMPL tests");
Arena arena = arena_init(MEM_MiB * 3);
// evaluation
raise_notice("=== Testing key evaluation ===");
test_eval_single_level_key(&arena);
test_eval_nested_key(&arena);
// interpolation tags
raise_notice("=== Testing interpolation tags ===");
test_render_interpolation_tags(&arena);
test_render_interpolation_tags_with_prefix(&arena);
// section tags
raise_notice("=== Testing section tags ===");
test_render_section_tags(&arena);
test_render_complex_section_tags(&arena);
printf("All tests passed.\n");
raise_notice("All tests passed successfully");
arena_free(&arena);
return 0;
}

View File

@@ -1,10 +1,10 @@
{ stdenv, gcc, lib, hectic, bash }:
{ stdenv, gcc, lib, bash, gdb }:
stdenv.mkDerivation {
pname = "watch";
version = "1.0";
src = ./.;
doCheck = true;
doCheck = false;
nativeBuildInputs = [ gcc gdb ];
@@ -17,10 +17,8 @@ stdenv.mkDerivation {
'';
installPhase = ''
mkdir -p $out/bin $out/lib $out/include
cp target/hmpl $out/bin/hmpl
cp target/libhmpl.a $out/lib/
cp hmpl.h $out/include/hmpl.h
mkdir -p $out/bin
cp target/watch $out/bin/watch
'';
meta = {

View File

@@ -9,6 +9,8 @@
#include <fnmatch.h>
#include <getopt.h>
#include <ctype.h>
#include <fcntl.h>
#include <signal.h>
#ifndef PATH_MAX
#define PATH_MAX 4096
@@ -19,15 +21,27 @@
#define MAX_PATTERNS 32
#define POLL_INTERVAL_MS 100
// Global flag to indicate if we're running in pager mode
int pager_mode = 0;
FILE *output_stream = NULL;
int running = 1;
void signal_handler(int sig) {
(void)sig; // Mark parameter as used to avoid warning
running = 0;
}
void print_usage(const char *prog_name) {
fprintf(stderr, "Usage: %s <command> [-p <pattern1>] [-p <pattern2>] ... <dir1> [dir2] ...\n", prog_name);
fprintf(stderr, " or: find . -type d | %s <command> [-p <pattern1>] [-p <pattern2>] ...\n", prog_name);
fprintf(stderr, "Options:\n");
fprintf(stderr, " -p <pattern> File pattern to watch (can be used multiple times)\n");
fprintf(stderr, " -P Enable pager-friendly output (refresh mode)\n");
fprintf(stderr, " -h Show this help message\n");
fprintf(stderr, "Examples:\n");
fprintf(stderr, " %s 'make' -p '*.c' -p '*.h' ./src\n", prog_name);
fprintf(stderr, " find . -type d | %s 'echo changed' -p '*.py'\n", prog_name);
fprintf(stderr, " %s -P 'make' -p '*.c' -p '*.h' ./src | less\n", prog_name);
exit(EXIT_FAILURE);
}
@@ -148,6 +162,10 @@ int match_any_pattern(const char *filename, char **patterns, int num_patterns) {
}
int main(int argc, char *argv[]) {
// Register signal handlers
signal(SIGINT, signal_handler);
signal(SIGTERM, signal_handler);
if (argc < 2) {
print_usage(argv[0]);
}
@@ -159,7 +177,7 @@ int main(int argc, char *argv[]) {
optind = 2;
int opt;
while ((opt = getopt(argc, argv, "p:h")) != -1) {
while ((opt = getopt(argc, argv, "p:Ph")) != -1) {
switch (opt) {
case 'p':
if (num_patterns < MAX_PATTERNS) {
@@ -169,6 +187,9 @@ int main(int argc, char *argv[]) {
exit(EXIT_FAILURE);
}
break;
case 'P':
pager_mode = 1;
break;
case 'h':
print_usage(argv[0]);
break;
@@ -183,6 +204,15 @@ int main(int argc, char *argv[]) {
print_usage(argv[0]);
}
// Set output stream - stderr for normal mode, stdout for pager mode
output_stream = pager_mode ? stdout : stderr;
// Check if stdout is being piped and we're not in pager mode
if (!pager_mode && !isatty(STDOUT_FILENO)) {
pager_mode = 1;
output_stream = stdout;
}
struct dir_info dirs[MAX_DIRS];
int num_dirs = 0;
struct file_hash files;
@@ -242,13 +272,13 @@ int main(int argc, char *argv[]) {
}
// Print the directories and patterns we're watching
fprintf(stderr, "Watching %d directories for files matching: ", num_dirs);
fprintf(output_stream, "Watching %d directories for files matching: ", num_dirs);
for (int i = 0; i < num_patterns; i++) {
fprintf(stderr, "'%s'%s", patterns[i], (i < num_patterns - 1) ? ", " : "\n");
fprintf(output_stream, "'%s'%s", patterns[i], (i < num_patterns - 1) ? ", " : "\n");
}
for (int i = 0; i < num_dirs; i++) {
fprintf(stderr, " %s\n", dirs[i].path);
fprintf(output_stream, " %s\n", dirs[i].path);
}
for (int i = 0; i < num_dirs; i++) {
@@ -281,8 +311,9 @@ int main(int argc, char *argv[]) {
closedir(dir);
}
fprintf(stderr, "Initially found %d matching files\n", files.count);
fprintf(stderr, "Waiting for file modifications...\n");
fprintf(output_stream, "Initially found %d matching files\n", files.count);
fprintf(output_stream, "Waiting for file modifications...\n");
fflush(output_stream);
struct timeval tv;
tv.tv_sec = 0;
@@ -302,7 +333,8 @@ int main(int argc, char *argv[]) {
int first_scan = 1;
while (1) {
// Main watch loop
while (running) {
select(0, NULL, NULL, NULL, &tv);
int any_changes = 0;
@@ -337,14 +369,14 @@ int main(int argc, char *argv[]) {
existing->exists = 1;
if (existing->mtime != st.st_mtime) {
if (!first_scan) {
fprintf(stderr, "File modified: %s\n", filepath);
fprintf(output_stream, "File modified: %s\n", filepath);
any_changes = 1;
}
existing->mtime = st.st_mtime;
}
} else {
if (!first_scan) {
fprintf(stderr, "New file: %s\n", filepath);
fprintf(output_stream, "New file: %s\n", filepath);
any_changes = 1;
}
struct file_info *file = malloc(sizeof(struct file_info));
@@ -361,7 +393,7 @@ int main(int argc, char *argv[]) {
for (int i = 0; i < files.size; i++) {
if (files.items[i] != NULL && files.items[i]->exists == 0) {
if (!first_scan) {
fprintf(stderr, "File deleted: %s\n", files.items[i]->path);
fprintf(output_stream, "File deleted: %s\n", files.items[i]->path);
any_changes = 1;
}
}
@@ -369,8 +401,42 @@ int main(int argc, char *argv[]) {
hash_remove_nonexistent(&files);
// If in pager mode and any changes occur, clear screen and reprint status
if (pager_mode && any_changes) {
// Use ANSI escape codes to clear screen
fprintf(output_stream, "\033[2J\033[H");
// Reprint header
fprintf(output_stream, "Watching %d directories for files matching: ", num_dirs);
for (int i = 0; i < num_patterns; i++) {
fprintf(output_stream, "'%s'%s", patterns[i], (i < num_patterns - 1) ? ", " : "\n");
}
// List current files
fprintf(output_stream, "Currently watching %d files:\n", files.count);
int file_count = 0;
for (int i = 0; i < files.size && file_count < files.count; i++) {
if (files.items[i] != NULL) {
fprintf(output_stream, " %s\n", files.items[i]->path);
file_count++;
}
}
// Show last action
time_t current_time = time(NULL);
fprintf(output_stream, "\nLast event: Command executed at %s", ctime(&current_time));
fprintf(output_stream, "Command: %s\n", command);
// Make sure output is flushed to the pager
fflush(output_stream);
} else if (any_changes) {
// In normal mode, just show status messages
fprintf(output_stream, "Executing command: %s\n", command);
fflush(output_stream);
}
// Execute command if changes detected
if (any_changes) {
fprintf(stderr, "Executing command: %s\n", command);
int res = system(command);
if (res != 0) {
perror("system");
@@ -393,5 +459,6 @@ int main(int argc, char *argv[]) {
free(patterns);
hash_free(&files);
fprintf(output_stream, "Watch terminated.\n");
return 0;
}

View File

@@ -2,7 +2,7 @@
pkgs.writeShellScriptBin "pager" ''
nvim -R --clean -c 'set buftype=nofile' -c 'nnoremap q :q!<CR>' -c 'set nowrap' \
-c 'set runtimepath^=${pkgs.vimPlugins.vim-plugin-AnsiEsc}' \
-c 'runtime! plugin/*.vim' -c 'AnsiEsc' -
-c 'runtime! plugin/*.vim' -c 'AnsiEsc' - $@
# ^^^^^^^^^^^^^^^^^^^^
# Prevents Neovim from treating the buffer as a file
# ^^^^^^^^^^^^^^^^^^^^