Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
20 changes: 17 additions & 3 deletions Makefile
Original file line number Diff line number Diff line change
Expand Up @@ -2,11 +2,15 @@ CC = gcc
CFLAGS = -Wall -Wextra -std=c99
LIBS = -lncurses
TARGET = rtgrep
SOURCES = rtgrep.c line_list.c
SOURCES = rtgrep.c line_list.c arguments.c
OBJECTS = $(SOURCES:.c=.o)

PREFIX = /usr/local
BINDIR = $(PREFIX)/bin
MANDIR = $(PREFIX)/share/man/man1

TEST_TARGET = test_runner
TEST_SOURCES = test/test_root.c test/line_list_tests.c line_list.c
TEST_SOURCES = test/test_root.c test/test_utils.c test/line_list_tests.c test/arguments_tests.c line_list.c arguments.c
TEST_OBJECTS = $(TEST_SOURCES:.c=.o)

$(TARGET): $(OBJECTS)
Expand All @@ -21,7 +25,17 @@ test: $(TEST_TARGET)
$(TEST_TARGET): $(TEST_OBJECTS)
$(CC) $(CFLAGS) -o $(TEST_TARGET) $(TEST_OBJECTS)

install: $(TARGET)
install -d $(BINDIR)
install -m 755 $(TARGET) $(BINDIR)
install -d $(MANDIR)
install -m 644 man/rtgrep.1 $(MANDIR)

uninstall:
rm -f $(BINDIR)/$(TARGET)
rm -f $(MANDIR)/rtgrep.1

clean:
rm -f $(TARGET) $(OBJECTS) $(TEST_TARGET) $(TEST_OBJECTS)

.PHONY: clean test
.PHONY: clean test install uninstall
85 changes: 85 additions & 0 deletions arguments.c
Original file line number Diff line number Diff line change
@@ -0,0 +1,85 @@
#include <stdlib.h>
#include <string.h>
#include <unistd.h>
#include <getopt.h>
#include <stdio.h>
#include "arguments.h"


arguments_t* get_cli_arguments(int argc, char **argv) {
int opt;
arguments_t* parsed_args;

// Reset getopt state for multiple calls
optind = 1;

parsed_args = malloc(sizeof(arguments_t));
parsed_args->pattern = NULL;
parsed_args->grep_command = NULL;

while((opt = getopt(argc, argv, ":g:h")) != -1) {
switch (opt) {
case 'g':
parsed_args->grep_command = malloc(strlen(optarg) + 1);
strcpy(parsed_args->grep_command, optarg);
break;
case 'h':
print_usage(argv[0]);
deallocate_arguments(&parsed_args);
exit(0);
case ':':
fprintf(stderr, "Option -%c requires an argument.\n", optopt);
print_usage(argv[0]);
deallocate_arguments(&parsed_args);
exit(1);
case '?':
fprintf(stderr, "Unknown option: -%c\n", optopt);
print_usage(argv[0]);
deallocate_arguments(&parsed_args);
exit(1);
}
}

// After processing options, get the first non-option argument as pattern
if (optind < argc) {
parsed_args->pattern = malloc(strlen(argv[optind]) + 1);
strcpy(parsed_args->pattern, argv[optind]);

// Check for extra arguments after pattern
if (optind + 1 < argc) {
fprintf(stderr, "Error: Too many arguments.\n");
print_usage(argv[0]);
deallocate_arguments(&parsed_args);
exit(1);
}
}

return parsed_args;
}


void deallocate_arguments(arguments_t ** args) {
if (args && *args) {
if ((*args)->grep_command) {
free((*args)->grep_command);
}
if ((*args)->pattern) {
free((*args)->pattern);
}
free(*args);
*args = NULL;
}
}

void print_usage(const char *program_name) {
printf("Usage: %s [OPTIONS] [PATTERN]\n", program_name);
printf("\n");
printf("Real-time grep utility\n");
printf("\n");
printf("Arguments:\n");
printf(" PATTERN Search pattern to match (optional)\n");
printf("\n");
printf("Options:\n");
printf(" -g COMMAND Custom grep command to use\n");
printf(" -h, --help Show this help message\n");
}
14 changes: 14 additions & 0 deletions arguments.h
Original file line number Diff line number Diff line change
@@ -0,0 +1,14 @@
#ifndef ARGUMENTS_H
#define ARGUMENTS_H

typedef struct {
char *grep_command;
char *pattern;
} arguments_t;

arguments_t* get_cli_arguments(int argc, char **argv);
void print_usage(const char *program_name);
void deallocate_arguments(arguments_t ** args);

#endif

138 changes: 138 additions & 0 deletions man/rtgrep.1
Original file line number Diff line number Diff line change
@@ -0,0 +1,138 @@
.TH RTGREP 1 "2025-09-06" "rtgrep x.x.x" "Real-Time Grep Manual"
.SH NAME
rtgrep \- real-time interactive grep utility
.SH SYNOPSIS
.B rtgrep
[\fIOPTIONS\fR] [\fIPATTERN\fR]
.SH DESCRIPTION
.B rtgrep
is an interactive, real-time grep utility that provides a two-pane terminal interface for searching through files. It displays grep results in real-time as you type search patterns, making it ideal for quickly exploring and searching through codebases and text files.

The interface consists of two panes:
.IP \(bu 2
.B Output Field
- The upper pane displays matching lines from grep results
.IP \(bu 2
.B Input Field
- The bottom pane where you enter search patterns

When you type a pattern and pause for 100ms, rtgrep automatically executes a grep search and displays the results. Only one grep process runs at a time, and typing while a search is running will kill the current process and start a new one when you pause.
.SH OPTIONS
.TP
.BR \-g " " \fICOMMAND\fR
Use custom grep command instead of the default "grep -rn --color=always". This allows you to specify different grep options or use alternative tools like ripgrep or ag.
.TP
.BR \-h ", " \-\-help
Display help information and exit.
.SH ARGUMENTS
.TP
.I PATTERN
Optional initial search pattern. If provided, rtgrep will start with this pattern and begin searching immediately.
.SH USAGE
.SS Interactive Mode
1. Run
.B rtgrep
without arguments to start in interactive mode
2. Type your search pattern in the input field at the bottom
3. Results appear in the output field as you type (with a 100ms delay)
4. Press
.B Enter
or
.B Escape
to exit
.SS Command Line Pattern
Run
.B rtgrep
with a pattern argument to start searching immediately:
.PP
.RS
rtgrep "function.*main"
.RE
.SS Custom Grep Command
Use a different grep command or options:
.PP
.RS
rtgrep -g "rg --color=always -n" "TODO"
.RE
.SH KEYBOARD CONTROLS
.TP
.B Backspace
Delete the last character from the search pattern
.TP
.B Enter
Exit the program and print all results to stdout
.TP
.B Escape
Exit the program and print all results to stdout
.TP
.B Printable characters (32-126)
Add character to the search pattern
.SH BEHAVIOR
.IP \(bu 2
Only one grep process runs at a time for resource efficiency
.IP \(bu 2
Grep is not executed if the input field is empty
.IP \(bu 2
New searches automatically kill previous grep processes
.IP \(bu 2
Results that exceed the output pane size show the most recent matches
.IP \(bu 2
All results are preserved and printed to stdout when exiting
.IP \(bu 2
The program searches recursively through the current directory
.SH OUTPUT
While running, rtgrep displays results in the upper pane with the format:
.PP
.RS
filename:line_number: matched_line_content
.RE
.PP
When exiting, all accumulated results are printed to stdout, making it suitable for use in pipelines or for saving results to files.
.SH EXAMPLES
.TP
Start rtgrep interactively:
.RS
rtgrep
.RE
.TP
Search for "main" function definitions:
.RS
rtgrep "int main"
.RE
.TP
Use ripgrep instead of grep:
.RS
rtgrep -g "rg --color=always -n" "TODO"
.RE
.TP
Search for header files and save results:
.RS
rtgrep "\\.h:" > header_files.txt
.RE
.SH FILES
rtgrep searches recursively through files in the current working directory using the specified or default grep command.
.SH EXIT STATUS
.B rtgrep
exits with status 0 on normal termination (Enter or Escape key).
.SH ENVIRONMENT
The program uses the system shell (/bin/sh) to execute grep commands.
.SH NOTES
.IP \(bu 2
The program redirects stdout to /dev/tty during operation to maintain the interface, then restores it when exiting
.IP \(bu 2
Color output from grep is preserved in the display
.IP \(bu 2
Maximum pattern length is 256 characters
.IP \(bu 2
Maximum line length for results is 512 characters
.IP \(bu 2
Up to 1000 output lines are stored
.SH SEE ALSO
.BR grep (1),
.BR rg (1),
.BR ag (1),
.BR find (1)
.SH AUTHOR
Written for real-time interactive file searching and code exploration.
.SH REPORTING BUGS
Report bugs and issues through the project repository.
19 changes: 15 additions & 4 deletions rtgrep.c
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,7 @@
#include <sys/time.h>
#include "ansi.h"
#include "line_list.h"
#include "arguments.h"

#define MAX_PATTERN_LEN 256
#define MAX_OUTPUT_LINES 1000
Expand Down Expand Up @@ -65,19 +66,28 @@ int main(int argc, char *argv[]) {
output_buffer_t output = {0};
char pattern[MAX_PATTERN_LEN] = "";
grep_state_t grep_state = {0};
arguments_t *args;

//init line list
output.line_list = line_list_init();

// Parse command line arguments
if (argc > 1) {
snprintf(grep_command, sizeof(grep_command), "%s", argv[1]);
args = get_cli_arguments(argc, argv);
if (args->grep_command) {
// TODO this is sortof gross. Should probably just consolidate all of the
// defaults into the args so we don't have to do any of this copying
strcpy(grep_command, args->grep_command);
}

if (args->pattern) {
strcpy(pattern, args->pattern);
gettimeofday(&grep_state.last_keypress_time, NULL);
grep_state.timer_active = 1;
}

init_ui(&ui);

while (1) {

if (should_execute_grep(pattern, &grep_state)) {
execute_grep(pattern, &output, &grep_state);
}
Expand All @@ -95,7 +105,8 @@ int main(int argc, char *argv[]) {

kill_current_grep(&grep_state);
cleanup_ui(&output);


deallocate_arguments(&args);
line_list_deallocate(&(output.line_list));
return 0;
}
Expand Down
2 changes: 1 addition & 1 deletion rtgrep.vim
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
let g:rtgrep_grep_command = "rtgrep \"rg --vimgrep\""
let g:rtgrep_grep_command = "rtgrep -g \"rg --vimgrep --color=always\""
let g:rtgrep_temp_file = "/tmp/rtgrep_output.txt"

function RealTimeGrep()
Expand Down
Loading
Loading