feat(input): ✨ introducing stdin support allowing to read and search any input #235#539
feat(input): ✨ introducing stdin support allowing to read and search any input #235#539
Conversation
There was a problem hiding this comment.
Pull Request Overview
This PR introduces stdin support for HSTR, allowing the utility to read input from a pipe and enable searching of piped data. Key changes include updating function signatures (e.g., fill_terminal_input and prioritized_history_create) to accept additional parameters such as file descriptors and history state, and modifying curses initialization to handle tty reading when input is piped.
Reviewed Changes
Copilot reviewed 16 out of 16 changed files in this pull request and generated 1 comment.
Show a summary per file
| File | Description |
|---|---|
| src/include/hstr_utils.h | Updated fill_terminal_input signature to include file descriptor parameter. |
| src/include/hstr_history.h | Added HISTORY_STATE parameter to prioritized_history_create. |
| src/include/hstr_curses.h | Modified hstr_curses_start to accept a tty initialization flag and return a FILE*. |
| src/hstr_utils.c | Adjusted fill_terminal_input implementation to use the new file descriptor parameter. |
| src/hstr_history.c | Updated prioritized_history_create to support an external history state. |
| src/hstr_curses.c | Updated curses initialization logic to conditionally use /dev/tty when reading from pipe. |
| src/hstr.c | Updated main loop and interactive functions to support pipe input and pass the correct fd. |
| pad.xml, hstr.pro, build scripts | Bumped version numbers and updated build scripts accordingly. |
| README.md | Added package information. |
Comments suppressed due to low confidence (1)
src/hstr.c:1728
- The code closes stdin after processing piped history input, which may be problematic if further standard input operations are expected; double-check that this behavior is intended.
fclose(fp);
There was a problem hiding this comment.
Pull request overview
Copilot reviewed 18 out of 18 changed files in this pull request and generated 6 comments.
Comments suppressed due to low confidence (1)
src/hstr_history.c:188
rawHistoryis allocated withmallocand not zero-initialized. In the population loop, entries can be skipped (e.g., empty lines) without writing a value intorawHistory[rawOffset], leaving garbage pointers; later code assumesrawItems[i]is non-NULL (second pass inhstr_make_selectioncallsstrstr(source[i], ...)without a NULL check), which can crash. InitializerawHistoryto NULL (e.g.,calloc), and ensure skipped/ignored items are removed from the raw list andrawCountreflects only valid strings.
HIST_ENTRY **historyList=historyState ? historyState->entries : history_list();
char **rawHistory=malloc(sizeof(char*) * historyState->length);
int rawOffset=historyState->length-1, rawTimestamps=0;
char *line;
int i;
💡 Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.
…is freeing memory
There was a problem hiding this comment.
Pull request overview
Copilot reviewed 21 out of 22 changed files in this pull request and generated 9 comments.
Comments suppressed due to low confidence (2)
.github/workflows/build_ubuntu.yml:14
- The workflow runs on
ubuntu-latest, but the install step is explicitly tailored to Ubuntu 24.04 packages. Sinceubuntu-latestcan move to a newer release and break package availability, consider pinningruns-on: ubuntu-24.04(or adjusting the step to detect the distro version dynamically).
src/hstr_history.c:192 rawHistoryis allocated withmalloc()and entries are only written for non-empty lines and timestamps. IfhistoryList[i]->lineis empty (which is plausible with piped stdin input), the loopcontinues without settingrawHistory[rawOffset], leaving an uninitialized pointer that later gets exposed viaprioritizedHistory->rawItems/rawCountand can crash when accessed. Consider initializingrawHistoryto NULL (e.g.,calloc), settingrawHistory[rawOffset]=NULLbeforecontinue, and/or trackingrawCountbased on actually populated entries (similar to the timestamp compaction).
HIST_ENTRY **historyList=historyState ? historyState->entries : history_list();
char **rawHistory=malloc(sizeof(char*) * historyState->length);
int rawOffset=historyState->length-1, rawTimestamps=0;
char *line;
int i;
for(i=0; i<historyState->length; i++, rawOffset--) {
if(!historyList[i]->line || !strlen(historyList[i]->line)) {
continue;
}
💡 Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.
| if(ttyInit) { | ||
| SCREEN *screen; | ||
|
|
||
| tty_in = fopen("/dev/tty", "r+"); | ||
| if (!tty_in) { | ||
| perror("Could not open /dev/tty prior to curses initialization"); | ||
| return NULL; | ||
| } | ||
|
|
||
| screen = newterm(NULL, stdout, tty_in); | ||
| if (!screen) { | ||
| fprintf(stderr, "Error initializing curses when HSTR is reading from pipe"); | ||
| fclose(tty_in); | ||
| return NULL; | ||
| } | ||
| set_term(screen); | ||
| } else { |
There was a problem hiding this comment.
In the ttyInit path, newterm() creates a new SCREEN* but it’s stored only in a local variable and never released (e.g., via delscreen() after endwin()), which can leak ncurses resources. Consider storing the SCREEN* somewhere reachable by hstr_curses_stop() (or returning it alongside tty_in) and cleaning it up on shutdown.
| Browse output of any command interactively: | ||
|
|
||
| ```bash | ||
| ls -la | hh | ||
| ``` |
There was a problem hiding this comment.
This new example uses hh, but earlier in the document the pipe example uses ls | hstr and other examples were switched to hstr. To avoid confusion, make the examples consistent (either use hstr everywhere, or consistently use hh and explicitly state it’s an alias).
| <Program_Release_Month>5</Program_Release_Month> | ||
| <Program_Release_Day>9</Program_Release_Day> | ||
| <Program_Release_Year>2025</Program_Release_Year> |
There was a problem hiding this comment.
The PAD release date (2025-05-09) doesn’t match the changelog entry for v3.2.0 (2026-01-25). If v3.2.0 is being released now, update the PAD date to match; otherwise align the changelog/versioning so downstream consumers don’t get conflicting release metadata.
| <Program_Release_Month>5</Program_Release_Month> | |
| <Program_Release_Day>9</Program_Release_Day> | |
| <Program_Release_Year>2025</Program_Release_Year> | |
| <Program_Release_Month>1</Program_Release_Month> | |
| <Program_Release_Day>25</Program_Release_Day> | |
| <Program_Release_Year>2026</Program_Release_Year> |
| if(!historyState) { | ||
| using_history(); | ||
| if(!history_mgmt_load_history_file()) { | ||
| return NULL; | ||
| } | ||
|
|
||
| historyState=history_get_history_state(); | ||
| } |
There was a problem hiding this comment.
prioritized_history_create() allocates a HISTORY_STATE via history_get_history_state() when the caller passes NULL, but it is never freed anymore. Since history_get_history_state() returns heap memory, this introduces a leak on each call. Consider tracking ownership (e.g., bool ownsHistoryState = (historyState == NULL) before assignment) and free(historyState) before returning when it was internally allocated.
|
|
||
| screen = newterm(NULL, stdout, tty_in); | ||
| if (!screen) { | ||
| fprintf(stderr, "Error initializing curses when HSTR is reading from pipe"); |
There was a problem hiding this comment.
This error message is missing a trailing newline, which can make it harder to read when mixed with subsequent output. Consider adding \n (and possibly including strerror(errno)/perror style details for consistency with the /dev/tty open failure path).
| fprintf(stderr, "Error initializing curses when HSTR is reading from pipe"); | |
| fprintf(stderr, "Error initializing curses when HSTR is reading from pipe\n"); |
| Minor release bringing ability to read input | ||
| from the pipe (instead of history) and filter to print it using HSTR; | ||
| and interactive missing configuration detection for TIOCSTI enforcement. | ||
|
|
There was a problem hiding this comment.
The release notes for v3.2.0 are duplicated/redundant (the unbulleted paragraph repeats the bullet point). Consider keeping a single concise entry to avoid confusion in downstream packaging/changelog parsing.
| Minor release bringing ability to read input | |
| from the pipe (instead of history) and filter to print it using HSTR; | |
| and interactive missing configuration detection for TIOCSTI enforcement. |
| if(!isatty(fileno(stdin))) { | ||
| // load history (or alternative content) from stdin | ||
| hstr->readingFromPipe=true; | ||
| int MAX_STDIN_ENTRIES = 2000; | ||
|
|
||
| historyState = malloc(sizeof(HISTORY_STATE)); | ||
| if (!historyState) { | ||
| fprintf(stderr, "Failed to allocate memory for history state\n"); | ||
| hstr_exit(EXIT_FAILURE); | ||
| } | ||
| memset(historyState, 0, sizeof(HISTORY_STATE)); | ||
|
|
||
| historyState->entries = malloc(sizeof(HIST_ENTRY*) * MAX_STDIN_ENTRIES); | ||
| if (!historyState->entries) { | ||
| fprintf(stderr, "Failed to allocate memory for history entries\n"); | ||
| free_history_state(historyState); | ||
| hstr_exit(EXIT_FAILURE); | ||
| } | ||
|
|
||
| FILE *fp = stdin; | ||
| if(fp == NULL) { | ||
| fprintf(stderr, "Error opening stdin\n"); | ||
| free_history_state(historyState); | ||
| hstr_exit(EXIT_FAILURE); | ||
| } | ||
|
|
||
| char *line = NULL; | ||
| size_t len = 0; | ||
| ssize_t read; | ||
| historyState->length=0; | ||
| while ((read = getline(&line, &len, stdin)) != -1 && historyState->length < MAX_STDIN_ENTRIES) { | ||
| if (read > 0 && line[read-1] == '\n') { | ||
| line[read-1] = '\0'; | ||
| } | ||
|
|
||
| historyState->entries[historyState->length] = malloc(sizeof(HIST_ENTRY)); | ||
| if (!historyState->entries[historyState->length]) { | ||
| fprintf(stderr, "Failed to allocate memory for history entry\n"); | ||
| free(line); | ||
| free_history_state(historyState); | ||
| hstr_exit(EXIT_FAILURE); | ||
| } | ||
|
|
||
| historyState->entries[historyState->length]->line = strdup(line); | ||
| if (!historyState->entries[historyState->length]->line) { | ||
| fprintf(stderr, "Failed to allocate memory for history line\n"); | ||
| free(historyState->entries[historyState->length]); | ||
| free(line); | ||
| free_history_state(historyState); | ||
| hstr_exit(EXIT_FAILURE); | ||
| } | ||
|
|
||
| historyState->entries[historyState->length]->timestamp = NULL; | ||
| historyState->entries[historyState->length]->data = NULL; | ||
|
|
||
| historyState->length++; | ||
| } | ||
| free(line); // free(NULL) is safe per C standard | ||
|
|
||
| fclose(fp); | ||
| } |
There was a problem hiding this comment.
The new stdin/pipe parsing path introduces substantial logic (getline loop, max-entry truncation, blank-line handling, and memory management) but doesn’t appear to be covered by the existing Unity test suite. Consider adding unit tests around parsing piped input into a history list (including blank lines and >MAX_STDIN_ENTRIES) to prevent regressions like dangling pointers/uninitialized entries.
… and when they are, ptr is NULLed #235
…ious problems w/ file descriptors #235
There was a problem hiding this comment.
Pull request overview
Copilot reviewed 22 out of 22 changed files in this pull request and generated 3 comments.
Comments suppressed due to low confidence (1)
.github/workflows/build_ubuntu.yml:7
runs-on: ubuntu-latestis a moving target, but the install step is explicitly tailored for Ubuntu 24.04. Pinning toubuntu-24.04(or adjusting the step to be version-agnostic) will make CI more reproducible and avoid future breakages whenubuntu-latestchanges.
💡 Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.
| if(!isatty(fileno(stdin))) { | ||
| // load history (or alternative content) from stdin | ||
| hstr->readingFromPipe=true; | ||
| int MAX_STDIN_ENTRIES = 2000; | ||
|
|
There was a problem hiding this comment.
The new stdin/pipe ingestion path (reading lines with getline, building a synthetic HISTORY_STATE, and toggling readingFromPipe) introduces substantial new behavior but doesn’t appear to be covered by existing Unity tests. Consider adding unit tests that build a small synthetic HISTORY_STATE (including empty lines) and verify prioritized_history_create/selection logic behaves safely in pipe mode.
| RadixItem *radixItem; | ||
| HIST_ENTRY **historyList=history_list(); | ||
| HIST_ENTRY **historyList=historyState ? historyState->entries : history_list(); | ||
| char **rawHistory=malloc(sizeof(char*) * historyState->length); |
There was a problem hiding this comment.
rawHistory is allocated with malloc and not initialized. Later in the loop, some iterations continue (e.g., for empty lines) without writing into the corresponding rawHistory[rawOffset], leaving uninitialized pointers that can later be treated as non-NULL and dereferenced in selection/filtering. Initializing rawHistory to NULL (e.g., calloc/memset) or explicitly setting rawHistory[rawOffset]=NULL before any early-continue avoids crashes, especially now that stdin input can contain empty lines.
| char **rawHistory=malloc(sizeof(char*) * historyState->length); | |
| char **rawHistory=calloc(historyState->length, sizeof(char*)); |
|
|
||
| void hstr_curses_start(void); | ||
| FILE* hstr_curses_start(bool ttyInit); | ||
| bool terminal_has_colors(void); |
There was a problem hiding this comment.
This header now exposes FILE* in the public API but doesn’t include <stdio.h>. Relying on transitive includes from curses headers is non-portable and can break compilation in translation units that include this header before <stdio.h>. Include <stdio.h> (or forward-declare struct _IO_FILE as FILE if you prefer).
This PR adds ability of HSTR to read input from pipe - just for inspiration:
Tasks:
Known issue: