libghostty
Loading...
Searching...
No Matches
Search

Detailed Description

Search a terminal for a string, covering the active area and scrollback of both the primary and alternate screens.

A GhosttySearch searches the terminal it was created with for a needle set with GHOSTTY_SEARCH_OPT_NEEDLE. It handles the hard parts of terminal search internally: results stay in sync with the live screens, survive primary/alternate screen switches (entering and leaving a fullscreen app such as vim does not restart a scrollback search), and recover from resize, reflow, resets, and scrollback pruning.

A search starts idle. Setting the needle starts the search, changing it restarts the search from scratch, and clearing it returns the search to idle. Matching is byte-exact except ASCII letters, which compare case-insensitively.

Driving a search

Searching a large scrollback takes time, so the work is split into small steps the caller drives so that the caller can control performance more directly:

  • ghostty_search_tick() makes a bounded amount of progress on data the search has already copied. It never touches the terminal, meaning it can be safely called from a thread.
  • ghostty_search_feed() reads the terminal to copy in more data and pick up terminal changes. Feeding is the only way the search learns that the terminal changed, so keep feeding periodically while the search is in use. This requires exclusive terminal access.
  • ghostty_search_run() is a blocking convenience that feeds and ticks until the search is caught up.

GHOSTTY_SEARCH_STATUS_COMPLETE means the search is caught up with the terminal as of the last feed. It never means finished forever, since later terminal writes require another feed to be seen.

Matches are selections

Every match is returned as a GhosttySelection snapshot with rectangle set to false, so the existing selection APIs all work on matches: ghostty_terminal_selection_format_buf() to copy the matched text, ghostty_terminal_point_from_grid_ref() with GHOSTTY_POINT_TAG_VIEWPORT to position highlight rectangles, ghostty_terminal_selection_contains() for hit testing, and ghostty_terminal_set() with GHOSTTY_TERMINAL_OPT_SELECTION to make a match the terminal's selection.

Returned matches follow the usual snapshot lifetime rules: they are only valid until the next operation that modifies the terminal, including ghostty_terminal_vt_write(), resize, reset, and free. Read matches after a feed, use them before the terminal changes again, and re-read them rather than caching them. The selected match is kept accurate internally across terminal changes, so the safe way to follow a match is to re-read GHOSTTY_SEARCH_DATA_SELECTED_MATCH after each feed.

Lifetime

The search borrows the terminal it was created with and never frees it. Any number of searches, alongside other terminal readers such as formatters and render states, may share one terminal.

The search and its terminal can be freed in either order. Freeing the search first releases tracked state it holds within the terminal. If the terminal is freed first, the search detects this: calls that need the terminal return GHOSTTY_INVALID_VALUE, reads return whatever the search last saw, and ghostty_search_free() releases only search-owned memory. A search cannot be rebound, so searching another terminal means creating a new search.

Threading

The library creates no threads. Calls on one GhosttySearch are not safe to make concurrently with each other, so the caller must serialize them.

Functions that touch the terminal (ghostty_search_new(), ghostty_search_feed(), ghostty_search_run(), ghostty_search_set() with the needle and select options, and ghostty_search_free()) must also be serialized with all other access to the same terminal.

Everything else (ghostty_search_tick(), ghostty_search_get(), and ghostty_search_get_multi()) only touches memory owned by the search and is safe to call while another thread modifies the terminal. This split is how Ghostty runs search on a background thread: tick freely, and take the terminal lock only to feed. Reading returned match values is always safe, but passing them to APIs that take the terminal follows the terminal serialization rule above.

Example

int main() {
// Create a terminal and fill it with some content to search.
GhosttyTerminal terminal;
GhosttyResult result = ghostty_terminal_new(NULL, &terminal, 80, 24);
assert(result == GHOSTTY_SUCCESS);
const char *lines[] = {
"$ make test\r\n",
"compiling module A... ok\r\n",
"compiling module B... error: missing semicolon\r\n",
"linking... error: undefined symbol\r\n",
"$ grep -n ERROR build.log\r\n",
};
for (size_t i = 0; i < sizeof(lines) / sizeof(lines[0]); i++) {
ghostty_terminal_vt_write(terminal, (const uint8_t *)lines[i],
strlen(lines[i]));
}
// The user opened the find bar, so create a search bound to the
// terminal. It starts idle until it has a needle.
GhosttySearch search;
result = ghostty_search_new(NULL, &search, terminal);
assert(result == GHOSTTY_SUCCESS);
// The user typed a query. Matching is byte-exact except ASCII
// letters, which compare case-insensitively, so "error" also finds
// "ERROR". Retyping just sets the needle again: a changed needle
// restarts the search and an unchanged one keeps its results.
GhosttyString needle = { (const uint8_t *)"error", 5 };
result = ghostty_search_set(search, GHOSTTY_SEARCH_OPT_NEEDLE, &needle);
assert(result == GHOSTTY_SUCCESS);
// Drive the search. Interactive embedders interleave
// ghostty_search_tick() and ghostty_search_feed() with their event
// loop, but for a one-shot search we can just run it to completion.
result = ghostty_search_run(search);
assert(result == GHOSTTY_SUCCESS);
// The total match count, for find bar text like "1 of 3".
size_t total = 0;
&total);
assert(result == GHOSTTY_SUCCESS);
printf("%zu matches for \"error\"\n", total);
// The user pressed Enter, so select the next match. Selection starts
// at the newest match, moves toward older content, and wraps around.
// This scrolls the viewport to the match if it isn't visible, per
// the GHOSTTY_SEARCH_OPT_SELECT_SCROLL policy.
while (true) {
if (result != GHOSTTY_SUCCESS) break;
// Read the selection state in one call. Index 0 is the newest
// match, so a "k of n" find bar renders index + 1.
size_t idx = 0;
const GhosttySearchData keys[] = {
};
void *values[] = { &idx, &match };
search, sizeof(keys) / sizeof(keys[0]), keys, values, NULL);
assert(result == GHOSTTY_SUCCESS);
printf("selected %zu of %zu\n", idx + 1, total);
// Wrapped back around to the first match: stop.
if (idx + 1 == total) break;
}
// Each frame while the find bar is open, feed to catch up with any
// terminal changes and then read the viewport matches to draw
// highlights. The list can include matches just past the viewport
// when they share a page with it, so convert each endpoint to
// viewport coordinates and skip matches outside the visible rows.
result = ghostty_search_feed(search);
assert(result == GHOSTTY_SUCCESS);
GhosttySelection viewport_storage[64];
.ptr = viewport_storage,
.cap = sizeof(viewport_storage) / sizeof(viewport_storage[0]),
};
&viewport);
assert(result == GHOSTTY_SUCCESS);
for (size_t i = 0; i < viewport.len; i++) {
terminal, &viewport_storage[i].start, GHOSTTY_POINT_TAG_VIEWPORT,
&start) != GHOSTTY_SUCCESS) continue;
terminal, &viewport_storage[i].end, GHOSTTY_POINT_TAG_VIEWPORT,
&end) != GHOSTTY_SUCCESS) continue;
if (start.y >= 24 || end.y >= 24) continue;
// A real embedder draws a highlight rect from start to end here.
printf("highlight rows %u-%u, cols %u-%u\n",
(unsigned)start.y, (unsigned)end.y,
(unsigned)start.x, (unsigned)end.x);
}
// Closing the find bar. The search borrows the terminal, but the
// two can be freed in either order.
return 0;
}

Typedefs

typedef struct GhosttySearchImpl * GhosttySearch

Enumerations

enum  GhosttySearchStatus { GHOSTTY_SEARCH_STATUS_RUNNING = 0 , GHOSTTY_SEARCH_STATUS_FEED_REQUIRED = 1 , GHOSTTY_SEARCH_STATUS_COMPLETE = 2 , GHOSTTY_SEARCH_STATUS_MAX_VALUE = GHOSTTY_ENUM_MAX_VALUE }
enum  GhosttySearchScroll { GHOSTTY_SEARCH_SCROLL_IF_NEEDED = 0 , GHOSTTY_SEARCH_SCROLL_NONE = 1 , GHOSTTY_SEARCH_SCROLL_MAX_VALUE = GHOSTTY_ENUM_MAX_VALUE }
enum  GhosttySearchData {
  GHOSTTY_SEARCH_DATA_STATUS = 0 , GHOSTTY_SEARCH_DATA_NEEDLE = 1 , GHOSTTY_SEARCH_DATA_TOTAL_MATCHES = 2 , GHOSTTY_SEARCH_DATA_SELECTED_INDEX = 3 ,
  GHOSTTY_SEARCH_DATA_SELECTED_MATCH = 4 , GHOSTTY_SEARCH_DATA_MATCHES = 5 , GHOSTTY_SEARCH_DATA_VIEWPORT_MATCHES = 6 , GHOSTTY_SEARCH_DATA_SELECT_SCROLL = 7 ,
  GHOSTTY_SEARCH_DATA_MAX_VALUE = GHOSTTY_ENUM_MAX_VALUE
}
enum  GhosttySearchOption { GHOSTTY_SEARCH_OPT_NEEDLE = 0 , GHOSTTY_SEARCH_OPT_SELECT_NEXT = 1 , GHOSTTY_SEARCH_OPT_SELECT_PREV = 2 , GHOSTTY_SEARCH_OPT_SELECT_SCROLL = 3 , GHOSTTY_SEARCH_OPT_MAX_VALUE = GHOSTTY_ENUM_MAX_VALUE }

Functions

GHOSTTY_API GhosttyResult ghostty_search_new (const GhosttyAllocator *allocator, GhosttySearch *out_search, GhosttyTerminal terminal)
GHOSTTY_API void ghostty_search_free (GhosttySearch search)
GHOSTTY_API GhosttyResult ghostty_search_tick (GhosttySearch search, GhosttySearchStatus *out_status)
GHOSTTY_API GhosttyResult ghostty_search_feed (GhosttySearch search)
GHOSTTY_API GhosttyResult ghostty_search_run (GhosttySearch search)
GHOSTTY_API GhosttyResult ghostty_search_set (GhosttySearch search, GhosttySearchOption option, const void *value)
GHOSTTY_API GhosttyResult ghostty_search_get (GhosttySearch search, GhosttySearchData data, void *value)
GHOSTTY_API GhosttyResult ghostty_search_get_multi (GhosttySearch search, size_t count, const GhosttySearchData *keys, void **values, size_t *out_written)

Typedef Documentation

◆ GhosttySearch

typedef struct GhosttySearchImpl* GhosttySearch

Opaque handle to a terminal search.

A search is bound to the terminal it was created with. It borrows the terminal, so it never frees it, and the search must be freed with ghostty_search_free(). If the terminal is freed first, the search detects this: calls that need the terminal fail cleanly and the search can still be freed.

Examples
c-vt-search/src/main.c.

Definition at line 202 of file types.h.

Enumeration Type Documentation

◆ GhosttySearchData

Data fields readable with ghostty_search_get(). The output value type is documented per field.

All reads reflect the terminal's active screen as of the last feed. When the running application switches to the alternate screen, the next feed switches counts, matches, and selection to that screen's results. Primary screen results, including completed scrollback searches, are retained and restored on the way back.

Enumerator
GHOSTTY_SEARCH_DATA_STATUS 

Current search status: GhosttySearchStatus*.

GHOSTTY_SEARCH_DATA_NEEDLE 

The needle this search is looking for: GhosttyString*. The bytes are borrowed from the search and remain valid until the needle is changed or the search is freed. Returns GHOSTTY_NO_VALUE when no needle is set.

GHOSTTY_SEARCH_DATA_TOTAL_MATCHES 

Total matches found so far on the active screen: size_t*. Zero until the first feed.

GHOSTTY_SEARCH_DATA_SELECTED_INDEX 

Index of the selected match: size_t*. This indexes the newest to oldest ordering of GHOSTTY_SEARCH_DATA_MATCHES, where 0 is the newest match, so a "k of n" find bar renders index + 1 of GHOSTTY_SEARCH_DATA_TOTAL_MATCHES. Returns GHOSTTY_NO_VALUE when nothing is selected.

GHOSTTY_SEARCH_DATA_SELECTED_MATCH 

The selected match: GhosttySelection*. This is an untracked snapshot with standard GhosttySelection lifetime rules. Returns GHOSTTY_NO_VALUE when nothing is selected.

GHOSTTY_SEARCH_DATA_MATCHES 

All matches on the active screen, ordered newest to oldest, from the bottom of the active area up through scrollback: GhosttySelectionBuffer*. Set ptr to NULL with cap 0 to query the required capacity. An undersized buffer returns GHOSTTY_OUT_OF_SPACE with the required capacity in len.

GHOSTTY_SEARCH_DATA_VIEWPORT_MATCHES 

Matches on the pages covering the viewport, for drawing highlight rectangles: GhosttySelectionBuffer*. The list is computed during feeds and cached, so it reflects the viewport as of the last feed.

Matches are found a page at a time, so the list can include matches slightly outside the visible viewport when they share a page with it. Ghostty's own renderer behaves the same way. Converting each match to viewport coordinates with ghostty_terminal_point_from_grid_ref() clips this naturally: skip matches that fail the conversion or whose row is beyond the visible row count.

GHOSTTY_SEARCH_DATA_SELECT_SCROLL 

Current scroll policy: GhosttySearchScroll*.

Examples
c-vt-search/src/main.c.

Definition at line 175 of file search.h.

◆ GhosttySearchOption

Options writable with ghostty_search_set(). The value type, and what a NULL value means, is documented per option.

Enumerator
GHOSTTY_SEARCH_OPT_NEEDLE 

Set the needle to search for: const GhosttyString*. The bytes are copied, so the caller's memory does not need to outlive the call. Matching is byte-exact except ASCII letters, which compare case-insensitively.

Changing the needle restarts the search from scratch and drops all results. As an exception, setting a needle equal to the current one (compared the same way as matching) keeps existing results, so find bars can resubmit freely. A NULL or empty value clears the needle and returns the search to idle.

Replacing or clearing a needle releases tracked state held within the terminal, so the caller must serialize this with all other access to the same terminal. Returns GHOSTTY_INVALID_VALUE after the terminal was freed.

GHOSTTY_SEARCH_OPT_SELECT_NEXT 

Select the next match, moving toward older content: from the bottom of the screen upward into history, the direction a search from the prompt usually wants. Wraps around past the oldest match.

The value must be NULL. It is reserved for future use.

This catches up with the terminal first, so it is safe to call at any time relative to feeds. The viewport scrolls to the newly selected match according to GHOSTTY_SEARCH_OPT_SELECT_SCROLL. This reads the terminal, so the caller must serialize it with all other access to the same terminal. Returns GHOSTTY_NO_VALUE when there are no matches.

GHOSTTY_SEARCH_OPT_SELECT_PREV 

Select the previous match, moving toward newer content, wrapping around past the newest match. Otherwise identical to GHOSTTY_SEARCH_OPT_SELECT_NEXT.

GHOSTTY_SEARCH_OPT_SELECT_SCROLL 

Set the scroll policy applied by the select options: const GhosttySearchScroll*. The policy persists until changed. A NULL value resets it to GHOSTTY_SEARCH_SCROLL_IF_NEEDED. This only modifies search-owned state and never reads the terminal.

Definition at line 246 of file search.h.

◆ GhosttySearchScroll

Scroll policy applied when a match becomes selected via GHOSTTY_SEARCH_OPT_SELECT_NEXT or GHOSTTY_SEARCH_OPT_SELECT_PREV.

Enumerator
GHOSTTY_SEARCH_SCROLL_IF_NEEDED 

Scroll the viewport so the match is visible, only if it is not already visible. This is the default.

GHOSTTY_SEARCH_SCROLL_NONE 

Never scroll the viewport.

Definition at line 152 of file search.h.

◆ GhosttySearchStatus

Progress state of a search.

Enumerator
GHOSTTY_SEARCH_STATUS_RUNNING 

ghostty_search_tick() can make progress without terminal access.

GHOSTTY_SEARCH_STATUS_FEED_REQUIRED 

Blocked until ghostty_search_feed(). This is also the state right after a needle is set, since the search has not yet seen the terminal.

GHOSTTY_SEARCH_STATUS_COMPLETE 

Caught up with the terminal state as of the last feed. This never means finished forever, since later terminal writes require another feed to be seen. A search with no needle set also reports complete, since there is nothing to look for.

Definition at line 122 of file search.h.

Function Documentation

◆ ghostty_search_feed()

GHOSTTY_API GhosttyResult ghostty_search_feed ( GhosttySearch search)

Read the terminal to update the search.

Each feed catches the search up with the terminal: it reconciles the tracked screens against the live ones, re-scans the active area, refreshes the viewport match list, gives the scrollback searcher its next chunk of data, and prunes results that scrollback eviction invalidated. Feeding is also the only way the search learns about terminal changes, so keep feeding periodically while the search is in use, even after it reports complete.

This reads the terminal, so the caller must serialize it with all other access to the same terminal. Each call does a bounded amount of work so that any caller-held terminal lock is held only briefly.

Parameters
searchSearch handle (NULL returns GHOSTTY_INVALID_VALUE)
Returns
GHOSTTY_SUCCESS on success, or GHOSTTY_INVALID_VALUE if search is NULL or the terminal was freed
Examples
c-vt-search/src/main.c.

References ghostty_search_feed().

Referenced by ghostty_search_feed().

◆ ghostty_search_free()

GHOSTTY_API void ghostty_search_free ( GhosttySearch search)

Free a search.

If the bound terminal is still alive, this releases tracked state the search holds within it, so the caller must serialize this call with all other access to the same terminal. If the terminal was already freed, the search has been detached and this releases only search-owned memory. Passing NULL is allowed and is a no-op.

Parameters
searchSearch handle to free
Examples
c-vt-search/src/main.c.

References ghostty_search_free().

Referenced by ghostty_search_free().

◆ ghostty_search_get()

GHOSTTY_API GhosttyResult ghostty_search_get ( GhosttySearch search,
GhosttySearchData data,
void * value )

Read a data field from a search.

The output value type depends on data and is documented by GhosttySearchData. This never reads the terminal, so it is safe to call while another thread modifies the terminal. Returned selections are untracked snapshots with standard GhosttySelection lifetime rules.

Parameters
searchSearch handle (NULL returns GHOSTTY_INVALID_VALUE)
dataData field to read
valueOutput pointer whose type depends on data
Returns
GHOSTTY_SUCCESS on success, GHOSTTY_NO_VALUE if the requested data has no value, GHOSTTY_OUT_OF_SPACE if a provided GhosttySelectionBuffer is too small (required capacity in its len), GHOSTTY_OUT_OF_MEMORY if collecting viewport matches fails, or GHOSTTY_INVALID_VALUE if search, data, or value is invalid
Examples
c-vt-search/src/main.c.

References ghostty_search_get().

Referenced by ghostty_search_get().

◆ ghostty_search_get_multi()

GHOSTTY_API GhosttyResult ghostty_search_get_multi ( GhosttySearch search,
size_t count,
const GhosttySearchData * keys,
void ** values,
size_t * out_written )

Read multiple data fields from a search in a single call.

This is an optimization over calling ghostty_search_get() multiple times. Each entry in values must point to storage of the type documented by the corresponding GhosttySearchData key.

If any individual read fails, the function returns that error and writes the index of the failing key to out_written when out_written is non-NULL. Earlier keys have already been written. On success, out_written receives count when non-NULL. A too-small GhosttySelectionBuffer stops the batch with GHOSTTY_OUT_OF_SPACE at that key's index with the required capacity in its len, so order buffer-valued keys after scalar keys.

Parameters
searchSearch handle (NULL returns GHOSTTY_INVALID_VALUE)
countNumber of data fields to read
keysData fields to read (must not be NULL)
valuesOutput pointers corresponding to keys (must not be NULL)
out_writtenOptional number of fields read, or failing index on error
Returns
GHOSTTY_SUCCESS on success, or the first failing read's result
Examples
c-vt-search/src/main.c.

References ghostty_search_get_multi().

Referenced by ghostty_search_get_multi().

◆ ghostty_search_new()

GHOSTTY_API GhosttyResult ghostty_search_new ( const GhosttyAllocator * allocator,
GhosttySearch * out_search,
GhosttyTerminal terminal )

Create a search bound to a terminal.

The search borrows the terminal and never frees it. The search and the terminal can be freed in either order; see ghostty_search_free().

The search starts idle with no needle: it reports GHOSTTY_SEARCH_STATUS_COMPLETE and finds nothing. Set GHOSTTY_SEARCH_OPT_NEEDLE to start searching.

Creation is cheap and does not read terminal contents, but it registers the search with the terminal so the two can be freed in any order. The caller must serialize this call with all other access to the same terminal.

Parameters
allocatorAllocator, or NULL for the default allocator
out_searchReceives the created search handle
terminalTerminal to bind the search to
Returns
GHOSTTY_SUCCESS on success, GHOSTTY_INVALID_VALUE if out_search or terminal is invalid, or GHOSTTY_OUT_OF_MEMORY if allocation fails
Examples
c-vt-search/src/main.c.

References ghostty_search_new().

Referenced by ghostty_search_new().

◆ ghostty_search_run()

GHOSTTY_API GhosttyResult ghostty_search_run ( GhosttySearch search)

Feed and tick until the search is caught up with the terminal.

This is a blocking convenience for one-shot and single-threaded embedders. It always performs at least one feed, so it also picks up any terminal changes since the last feed, then loops until the status is GHOSTTY_SEARCH_STATUS_COMPLETE. Searching a large scrollback can take a while, so interactive embedders should drive ghostty_search_tick() and ghostty_search_feed() themselves.

This reads the terminal for the entire call, so the caller must serialize it with all other access to the same terminal.

Parameters
searchSearch handle (NULL returns GHOSTTY_INVALID_VALUE)
Returns
GHOSTTY_SUCCESS on success, or GHOSTTY_INVALID_VALUE if search is NULL or the terminal was freed
Examples
c-vt-search/src/main.c.

References ghostty_search_run().

Referenced by ghostty_search_run().

◆ ghostty_search_set()

GHOSTTY_API GhosttyResult ghostty_search_set ( GhosttySearch search,
GhosttySearchOption option,
const void * value )

Write an option to a search.

The value type, and what a NULL value means, depends on the option and is documented by GhosttySearchOption. The needle and select options touch the terminal, so the caller must serialize those calls with all other access to the same terminal. GHOSTTY_SEARCH_OPT_SELECT_SCROLL only modifies search-owned state.

Parameters
searchSearch handle (NULL returns GHOSTTY_INVALID_VALUE)
optionOption to write
valuePointer to the input value for the option. The meaning of NULL is documented per option.
Returns
GHOSTTY_SUCCESS on success, GHOSTTY_NO_VALUE if a select option found no matches, GHOSTTY_OUT_OF_MEMORY if allocation fails, or GHOSTTY_INVALID_VALUE if search, option, or value is invalid or the option needs a terminal that was already freed
Examples
c-vt-search/src/main.c.

References ghostty_search_set().

Referenced by ghostty_search_set().

◆ ghostty_search_tick()

GHOSTTY_API GhosttyResult ghostty_search_tick ( GhosttySearch search,
GhosttySearchStatus * out_status )

Make a bounded amount of search progress.

This only works on data the search has already copied and never reads the terminal, so it is safe to call while another thread modifies the terminal. Call it in a loop while the status is GHOSTTY_SEARCH_STATUS_RUNNING. When the status becomes GHOSTTY_SEARCH_STATUS_FEED_REQUIRED, call ghostty_search_feed() to unblock it.

Parameters
searchSearch handle (NULL returns GHOSTTY_INVALID_VALUE)
[out]out_statusReceives the status after the tick (may be NULL)
Returns
GHOSTTY_SUCCESS on success, or GHOSTTY_INVALID_VALUE if search is NULL

References ghostty_search_tick().

Referenced by ghostty_search_tick().