Skip to content

feat(input): ✨ introducing stdin support allowing to read and search any input #235#539

Open
dvorka wants to merge 20 commits intodev-3.2.0from
enh-235/stdin-support
Open

feat(input): ✨ introducing stdin support allowing to read and search any input #235#539
dvorka wants to merge 20 commits intodev-3.2.0from
enh-235/stdin-support

Conversation

@dvorka
Copy link
Copy Markdown
Owner

@dvorka dvorka commented May 18, 2025

This PR adds ability of HSTR to read input from pipe - just for inspiration:

git log --pretty=format:%s | hstr
find | hstr
ps aux | hstr
cat /var/log/nginx/access.log | hstr
grep phrase . | hstr
cat data.csv | hstr
netstat -tulpn | hstr
kubectl logs mypod | hstr

Tasks:

Known issue:

@dvorka dvorka requested a review from Copilot May 18, 2025 19:14
@dvorka dvorka self-assigned this May 18, 2025
@dvorka dvorka linked an issue May 18, 2025 that may be closed by this pull request
Copy link
Copy Markdown

Copilot AI left a 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);

Comment thread src/hstr_curses.c Outdated
Copy link
Copy Markdown

Copilot AI left a 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

  • rawHistory is allocated with malloc and not zero-initialized. In the population loop, entries can be skipped (e.g., empty lines) without writing a value into rawHistory[rawOffset], leaving garbage pointers; later code assumes rawItems[i] is non-NULL (second pass in hstr_make_selection calls strstr(source[i], ...) without a NULL check), which can crash. Initialize rawHistory to NULL (e.g., calloc), and ensure skipped/ignored items are removed from the raw list and rawCount reflects 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.

Comment thread src/hstr.c
Comment thread src/hstr.c
Comment thread src/hstr.c
Comment thread src/hstr_history.c
Comment thread src/hstr.c Outdated
Comment thread src/hstr.c
Copy link
Copy Markdown

Copilot AI left a 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. Since ubuntu-latest can move to a newer release and break package availability, consider pinning runs-on: ubuntu-24.04 (or adjusting the step to detect the distro version dynamically).
    src/hstr_history.c:192
  • rawHistory is allocated with malloc() and entries are only written for non-empty lines and timestamps. If historyList[i]->line is empty (which is plausible with piped stdin input), the loop continues without setting rawHistory[rawOffset], leaving an uninitialized pointer that later gets exposed via prioritizedHistory->rawItems/rawCount and can crash when accessed. Consider initializing rawHistory to NULL (e.g., calloc), setting rawHistory[rawOffset]=NULL before continue, and/or tracking rawCount based 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.

Comment thread src/hstr.c
Comment thread src/hstr.c Outdated
Comment thread src/hstr_curses.c
Comment on lines +28 to +44
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 {
Copy link

Copilot AI Jan 25, 2026

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.

Copilot uses AI. Check for mistakes.
Comment thread doc/USER-DOCUMENTATION.md
Comment on lines +189 to +193
Browse output of any command interactively:

```bash
ls -la | hh
```
Copy link

Copilot AI Jan 25, 2026

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).

Copilot uses AI. Check for mistakes.
Comment thread pad.xml
Comment on lines +55 to +57
<Program_Release_Month>5</Program_Release_Month>
<Program_Release_Day>9</Program_Release_Day>
<Program_Release_Year>2025</Program_Release_Year>
Copy link

Copilot AI Jan 25, 2026

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.

Suggested change
<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>

Copilot uses AI. Check for mistakes.
Comment thread src/hstr_history.c
Comment on lines +164 to 171
if(!historyState) {
using_history();
if(!history_mgmt_load_history_file()) {
return NULL;
}

historyState=history_get_history_state();
}
Copy link

Copilot AI Jan 25, 2026

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.

Copilot uses AI. Check for mistakes.
Comment thread src/hstr_curses.c

screen = newterm(NULL, stdout, tty_in);
if (!screen) {
fprintf(stderr, "Error initializing curses when HSTR is reading from pipe");
Copy link

Copilot AI Jan 25, 2026

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).

Suggested change
fprintf(stderr, "Error initializing curses when HSTR is reading from pipe");
fprintf(stderr, "Error initializing curses when HSTR is reading from pipe\n");

Copilot uses AI. Check for mistakes.
Comment thread Changelog Outdated
Comment on lines 8 to 11
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.

Copy link

Copilot AI Jan 25, 2026

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.

Suggested change
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 uses AI. Check for mistakes.
Comment thread src/hstr.c
Comment on lines +1801 to +1861
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);
}
Copy link

Copilot AI Jan 25, 2026

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.

Copilot uses AI. Check for mistakes.
Copy link
Copy Markdown

Copilot AI left a 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 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-latest is a moving target, but the install step is explicitly tailored for Ubuntu 24.04. Pinning to ubuntu-24.04 (or adjusting the step to be version-agnostic) will make CI more reproducible and avoid future breakages when ubuntu-latest changes.

💡 Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.

Comment thread src/hstr.c
Comment on lines +1810 to +1814
if(!isatty(fileno(stdin))) {
// load history (or alternative content) from stdin
hstr->readingFromPipe=true;
int MAX_STDIN_ENTRIES = 2000;

Copy link

Copilot AI Jan 26, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

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.

Copilot uses AI. Check for mistakes.
Comment thread src/hstr_history.c
RadixItem *radixItem;
HIST_ENTRY **historyList=history_list();
HIST_ENTRY **historyList=historyState ? historyState->entries : history_list();
char **rawHistory=malloc(sizeof(char*) * historyState->length);
Copy link

Copilot AI Jan 26, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

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.

Suggested change
char **rawHistory=malloc(sizeof(char*) * historyState->length);
char **rawHistory=calloc(historyState->length, sizeof(char*));

Copilot uses AI. Check for mistakes.
Comment thread src/include/hstr_curses.h
Comment on lines 33 to 35

void hstr_curses_start(void);
FILE* hstr_curses_start(bool ttyInit);
bool terminal_has_colors(void);
Copy link

Copilot AI Jan 26, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

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).

Copilot uses AI. Check for mistakes.
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Projects

None yet

Development

Successfully merging this pull request may close these issues.

Improve HSTR to read input from pipe

2 participants