diff --git a/docs/filegen.md b/docs/filegen.md new file mode 100644 index 0000000..20f24cd --- /dev/null +++ b/docs/filegen.md @@ -0,0 +1,28 @@ +# Dynamic File Suggestion Generator (`commands/core/filegen.go`) + +The `FileGenerator` is a dynamic suggestion engine that bridges the gap between static command specs and your live filesystem. + +## How it works + +The generator is assigned to commands or options that expect file paths (e.g., `git add`, `cat`, `go run`). + +### Path Resolution +It handles both local and nested paths: +- `cat m` -> Scans `./` for entries starting with `m`. +- `cat src/` -> Scans `src/` for all entries. +- `cat src/main` -> Scans `src/` for entries starting with `main`. + +### Intelligence Features + +1. **Extension Filtering**: You can restrict the generator to specific files. + - `FileGenerator(".go")` -> Only shows `.go` files (used in `go build`). +2. **Directory Suffixing**: When a directory is suggested, Iris appends a `/` to the command string. This allows you to immediately continue typing to dive into the next level of the folder tree. +3. **Descriptions**: It automatically converts common extensions into human-readable labels (e.g., `.mp4` -> `video`, `.zip` -> `archive`). +4. **Directory-Only Mode**: Commands like `cd` use a special filter to hide all files and only show folders. + +## Code Example +```go +// Registration for 'go run' +Generator: core.FileGenerator(".go") +``` +When typing `go run `, Iris will scan the current directory, filter for `.go` extensions, and ignore images, binaries, or other unrelated files. diff --git a/docs/history.md b/docs/history.md new file mode 100644 index 0000000..84e66e2 --- /dev/null +++ b/docs/history.md @@ -0,0 +1,25 @@ +# Shell History Integration (`integration/history.go`) + +The `history` module provides the Ctrl+R fuzzy search functionality by reading the user's persistent shell command history. + +## Features + +- **Zsh Extended Support**: Specifically parses the `: ;` format common in Zsh. +- **Lazy Loading**: Doesn't read the disk until the user actually requests history. This keeps startup time instantaneous. +- **Fuzzy Search**: Integrated with the `fuzzyvn` search engine. +- **Deduplication**: Automatically hides duplicate entries, showing only the most unique command variants. + +## Data Flow + +1. User presses `Ctrl+R`. +2. `root` sets `mode = "history"`. +3. `SearchHistory("")` is called. +4. If `cache` is empty: + - Reads `~/.zsh_history`. + - Strips metadata using `;` delimiter. + - Populates a slice of commands. +5. Search matches are sorted by the `fuzzyvn` engine. +6. Suggestions are returned to the `overlay` for rendering. + +## Configuration +It currently looks for `.zsh_history` in the user's home directory. diff --git a/docs/overlay.md b/docs/overlay.md new file mode 100644 index 0000000..e70e673 --- /dev/null +++ b/docs/overlay.md @@ -0,0 +1,31 @@ +# Overlay Rendering UI (`integration/overlay.go`) + +The `overlay` package handles the visual representation of suggestions. It is designed to be "non-destructive," meaning it draws over the shell without corrupting the prompt or the scrollback buffer. + +## Design Philosophy + +- **ANSI ESC Everywhere**: It uses CSI (Command Sequence Introducer) codes to manipulate the terminal. +- **Save/Restore**: It uses `\0337` (DECSC) and `\0338` (DECRC) to jump back to the prompt after drawing the menu. +- **Fixed Width**: The box is exactly 72 characters wide (`boxWidth`) to ensure a consistent, premium feel. + +## Technical Details + +### Terminal Scrolling Protection +When rendering near the bottom of the screen, simply printing lines would cause the terminal to scroll and bury the prompt. Iris solves this by: +1. Moving to the prompt. +2. Printing N empty newlines. +3. Moving back up N lines. +4. Saving the cursor *again* at this new, stabilized location. + +### Styling with Lipgloss +While Iris handles the positioning via raw ANSI codes, it uses **Lipgloss** for the "interior design": +- **Colors**: Dracula-inspired palette (`#BD93F9`, `#6272A4`). +- **Icons**: Fixed-width columns for badges. +- **Selection**: Highlights the active item with a background color (`#44475A`). + +## Example Logic +To draw line 1 of the menu: +1. `\0338` (Jump to prompt anchor). +2. `\033[2B` (Move 2 lines down). +3. `\033[K` (Clear the entire line). +4. Print `│ [Icon] Command name... │`. diff --git a/docs/root.md b/docs/root.md new file mode 100644 index 0000000..dc1313f --- /dev/null +++ b/docs/root.md @@ -0,0 +1,31 @@ +# IRIS: Central Integration & Event Loop (`root/`) + +The `root` package is the entry point and the orchestration layer of IRIS. It handles the low-level terminal manipulation and the main interaction loop. + +## How it works + +1. **PTY Wrapper**: When you run `iris`, it starts a Pseudo-Terminal (PTY) and launches `bash` inside it. +2. **IO Interception**: It creates two "pumps": + - **Output Pump**: Forwards everything from Bash to your screen using `TermWrite` (synchronized to prevent UI glitches). + - **Input Pump**: Listens to your keyboard. If it detects you are typing a command, it records it in a `naiveBuffer` and triggers the suggestion engine. +3. **State Management**: It tracks whether you are in `spec` mode (command suggestions) or `history` mode (Ctrl+R). + +## Key Components + +### `root.go` +Contains the `runWrapper()` function which: +- Sets the terminal to **Raw Mode** so Iris can capture keys like `Tab`, `Esc`, or `Ctrl+C` before the shell does. +- Manages the `naiveBuffer`: a string that tracks exactly what you see on your prompt. +- Handles **Selection Logic**: When you press `Tab`, it modifies the Bash line using the `Ctrl+U` (clear line) + `selected command` sequence. + +### `term_sync.go` +Provides `TermWrite`, a thread-safe wrapper around `os.Stdout`. It uses a `sync.Mutex` to ensure that if Bash and the Iris Overlay try to write at the exact same millisecond, the output doesn't get garbled. + +## Example Flow +1. User types `g`. +2. `root` captures `g`, appends to `naiveBuffer`. +3. `root` calls `renderOverlay()`. +4. `renderOverlay` calls `Lookup("g")`. +5. `overlay` renders the result `git`. +6. User presses `Tab`. +7. `root` sends `Ctrl+U` to Bash, then sends `git ` (the completion). diff --git a/docs/spec.md b/docs/spec.md new file mode 100644 index 0000000..dcc37cc --- /dev/null +++ b/docs/spec.md @@ -0,0 +1,30 @@ +# Command Specification & Navigation (`commands/core/spec.go`) + +The `spec` module is the "language parser" of IRIS. it understands the relationship between commands, subcommands, and flags. + +## Data Structures + +- **`Spec`**: The top-level definition (e.g., for `git`). +- **`Subcommand`**: Recursively defined children (e.g., `commit` under `git`). +- **`Generator`**: A function that provides dynamic content (like files or docker IDs). + +## The `Lookup` Algorithm + +This is the most critical function in IRIS. It follows these steps: + +1. **Tokenization**: Splits `"git commit -m"` into `["git", "commit", "-m"]`. + - *Empty tokens* (from a trailing space) indicate the user is ready for the next level of suggestions. +2. **Tree Walking**: It starts at the root (`git`) and tries to match each following token against the current node's subcommands. +3. **Context Identification**: Once it can't walk any further (e.g., a partial word or an option), it defines: + - `prefix`: The path already traveled. + - `partial`: The word currently being typed. +4. **Result Collection**: It gathers all valid subcommands and options that match the `partial` prefix. + +## Example +Input: `git com` +1. Tokens: `["git", "com"]` +2. Walk: Root is `git`. +3. Next token `com` doesn't exactly match `commit`. +4. Stop walking. `partial` = `com`. +5. Suggestions: Look for subcommands of `git` starting with `com`. +6. Return: `git commit`. diff --git a/iris b/iris new file mode 100755 index 0000000..3eadea7 Binary files /dev/null and b/iris differ diff --git a/root/root.go b/root/root.go index d731bd2..083d92c 100644 --- a/root/root.go +++ b/root/root.go @@ -63,6 +63,8 @@ func runWrapper() { } defer ptmx.Close() + core.ShellPID = c.Process.Pid + ch := make(chan os.Signal, 1) signal.Notify(ch, syscall.SIGWINCH) go func() { diff --git a/scripts/iris.zsh b/scripts/iris.zsh new file mode 100644 index 0000000..60b2400 --- /dev/null +++ b/scripts/iris.zsh @@ -0,0 +1,14 @@ +# iris zsh fast IPC integration + +if [[ -n "$IRIS_FD" ]]; then + _iris_send_lbuffer() { + # -u $IRIS_FD writes to the pipe file descriptor set up by Iris wrappers + # -N appends a null byte '\0' instead of newline (perfect for parsing) + # -r prints raw string + print -u $IRIS_FD -N -r -- "$LBUFFER" 2>/dev/null + } + + autoload -Uz add-zle-hook-widget + # Hook into ZLE so this runs absolutely every time the line buffer changes + add-zle-hook-widget line-pre-redraw _iris_send_lbuffer +fi diff --git a/test.zsh b/test.zsh new file mode 100644 index 0000000..2ee46a2 --- /dev/null +++ b/test.zsh @@ -0,0 +1 @@ +echo ${0:a:h}