-
Notifications
You must be signed in to change notification settings - Fork 239
feat(input): ✨ introducing stdin support allowing to read and search any input #235 #539
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
base: dev-3.2.0
Are you sure you want to change the base?
Conversation
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
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.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
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.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
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(hstr->readingFromPipe) { | ||
| free_history_state(historyState); | ||
| } | ||
|
|
Copilot
AI
Jan 25, 2026
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
When reading from stdin (pipe mode), historyState is freed immediately after prioritized_history_create(), but prioritized_history_create() stores rawItems as pointers into historyState->entries[i]->line (no strdup). This leaves hstr->history->rawItems pointing to freed memory and can crash when rendering/selecting raw view. Keep historyState alive until after prioritized_history_destroy(), or make prioritized_history_create() deep-copy raw lines when an external historyState is provided and free them in prioritized_history_destroy().
| if(hstr->readingFromPipe) { | |
| free_history_state(historyState); | |
| } |
| 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); | ||
| } |
Copilot
AI
Jan 25, 2026
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Avoid calling fclose(stdin) here. Closing the global stdin stream can have surprising side effects (and fp is always stdin), especially if other code/libraries read from stdin later. Prefer just reading until EOF without closing, and remove the dead fp == NULL check.
| 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 { |
Copilot
AI
Jan 25, 2026
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
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 | ||
| ``` |
Copilot
AI
Jan 25, 2026
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
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> |
Copilot
AI
Jan 25, 2026
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
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(); | ||
| } |
Copilot
AI
Jan 25, 2026
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
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"); |
Copilot
AI
Jan 25, 2026
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
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. | ||
|
|
Copilot
AI
Jan 25, 2026
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
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); | ||
| } |
Copilot
AI
Jan 25, 2026
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
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.
This PR adds ability of HSTR to read input from pipe - just for inspiration:
Tasks: