Skip to content

Commit 72d1a68

Browse files
authored
Merge pull request #83 from fulll/feat/tui-cosmetics
feat: TUI cosmetics v1.8.0 — purple theme, progress bar, position indicator, line-anchored links
2 parents 5e0d8a9 + b1f6601 commit 72d1a68

9 files changed

Lines changed: 572 additions & 54 deletions

File tree

CHANGELOG.md

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -6,6 +6,7 @@ Each release entry covers the motivation, new features, breaking changes (if any
66

77
| Version | Blog post |
88
| ------------------------------------------------------------------------ | --------------------------------------------------------------------------------------------------------------- |
9+
| [v1.8.0](https://fulll.github.io/github-code-search/blog/release-v1-8-0) | Purple TUI theme, fetch progress bar, position indicator, line-anchored file links, Esc to close help |
910
| [v1.7.0](https://fulll.github.io/github-code-search/blog/release-v1-7-0) | Shell completions (bash/zsh/fish) + extended syntax highlighting (PHP, C/C++, Swift, Terraform/HCL, Dockerfile) |
1011
| [v1.6.1](https://fulll.github.io/github-code-search/blog/release-v1-6-1) | Fix TUI only displaying first text fragment when a file has multiple matches |
1112
| [v1.6.0](https://fulll.github.io/github-code-search/blog/release-v1-6-0) | Power navigation: global fold/unfold, gg/G top/bottom, paged scroll, open-in-browser |

demo/demo.gif

12.4 KB
Loading

docs/blog/index.md

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -11,6 +11,7 @@ Full release notes and changelogs are always available on
1111

1212
| Release | Highlights |
1313
| -------------------------- | ---------------------------------------------------------------------------------------------------------- |
14+
| [v1.8.0](./release-v1-8-0) | Purple TUI theme, fetch progress bar, position indicator, line-anchored file links, Esc to close help |
1415
| [v1.7.0](./release-v1-7-0) | Shell completions (bash/zsh/fish), extended syntax highlighting (PHP, C/C++, Swift, Terraform, Dockerfile) |
1516
| [v1.6.1](./release-v1-6-1) | Fix TUI rendering only the first fragment for multi-match files |
1617
| [v1.6.0](./release-v1-6-0) | Power navigation: global fold/unfold (`Z`), Vim `gg`/`G` jumps, paged scroll, open-in-browser (`o`) |

docs/blog/release-v1-8-0.md

Lines changed: 70 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,70 @@
1+
---
2+
title: "What's new in v1.8.0"
3+
description: "Purple TUI theme, fetch progress bar, position indicator, line-anchored file links, and Esc to close help."
4+
date: 2026-03-04
5+
---
6+
7+
# What's new in github-code-search v1.8.0
8+
9+
> Full release notes: <https://github.com/fulll/github-code-search/releases/tag/v1.8.0>
10+
11+
## Highlights
12+
13+
### Cohesive purple TUI theme
14+
15+
The interactive view has been completely re-skinned around a purple colour palette.
16+
17+
| Element | Before | After |
18+
| ----------------------- | ----------------- | ------------------------------- |
19+
| Active row background | Grey (`48;5;236`) | Dark purple (`48;5;53`) |
20+
| Active row left bar `` | None | Saturated purple (`38;5;129`) |
21+
| Repo names | Bold cyan | Bright purple bold (`38;5;129`) |
22+
| Match count badge | Dim white | Muted purple (`38;5;99`) |
23+
24+
The left bar spans the full terminal width so the selected row is unmistakable even in long result lists.
25+
Path and loc-suffix (`:line:col`) on the active extract row use **bold white** for visual consistency.
26+
27+
### Fetch progress bar
28+
29+
A progress bar is now displayed during the GitHub API search so you can track paged requests in real time:
30+
31+
```
32+
Fetching results ████████████████░░░░░░░░ 8/13 pages
33+
```
34+
35+
The bar fills in saturated purple and shows the current page / estimated total pages.
36+
It disappears automatically when all results are loaded.
37+
38+
### Position indicator
39+
40+
A persistent indicator at the bottom of the TUI shows your exact position within the result list:
41+
42+
```
43+
↕ row 14 of 49
44+
```
45+
46+
The number always reflects the **cursor position**, not the scroll offset, so it updates on every keypress — including when you navigate upward inside an already-scrolled viewport.
47+
48+
### Line-anchored file links (`o`)
49+
50+
When you press `o` on an **extract row**, the browser now opens the file at the **exact matched line** by appending a `#L{line}` anchor to the GitHub URL.
51+
52+
Before: `https://github.com/org/repo/blob/main/src/foo.ts`
53+
After: `https://github.com/org/repo/blob/main/src/foo.ts#L42`
54+
55+
On a **repo row**, `o` still opens the repository home page unchanged.
56+
57+
### Esc to close the help overlay
58+
59+
The help overlay (toggled with `h` or `?`) can now also be dismissed with `Esc`, matching the expected behaviour of most terminal UIs.
60+
61+
---
62+
63+
## Upgrade
64+
65+
```bash
66+
github-code-search upgrade
67+
```
68+
69+
Or grab the latest binary directly from the
70+
[GitHub Releases page](https://github.com/fulll/github-code-search/releases/tag/v1.8.0).

src/api.test.ts

Lines changed: 39 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,11 @@
11
import { afterEach, beforeEach, describe, expect, it } from "bun:test";
2-
import { fetchAllResults, fetchRepoTeams, searchCode, segmentLineCol } from "./api.ts";
2+
import {
3+
buildFetchProgress,
4+
fetchAllResults,
5+
fetchRepoTeams,
6+
searchCode,
7+
segmentLineCol,
8+
} from "./api.ts";
39

410
const originalFetch = globalThis.fetch;
511
const originalSetTimeout = globalThis.setTimeout;
@@ -506,3 +512,35 @@ describe("fetchRepoTeams", () => {
506512
expect(result.has("myorg/repo-a")).toBe(true);
507513
});
508514
});
515+
516+
// ─── buildFetchProgress ───────────────────────────────────────────────────────
517+
518+
describe("buildFetchProgress", () => {
519+
it("starts with \\r to overwrite the current line", () => {
520+
const s = buildFetchProgress(1, 10);
521+
expect(s.startsWith("\r")).toBe(true);
522+
});
523+
524+
it("contains bright purple ANSI escape 38;5;129m", () => {
525+
const s = buildFetchProgress(1, 10);
526+
expect(s).toContain("\x1b[38;5;129m");
527+
});
528+
529+
it("contains page counter text", () => {
530+
const s = buildFetchProgress(3, 10);
531+
const stripped = s.replace(/\x1b\[[0-9;]*m/g, "");
532+
expect(stripped).toContain("3/10");
533+
});
534+
535+
it("full bar when currentPage equals totalPages — no empty block character", () => {
536+
const s = buildFetchProgress(10, 10);
537+
const stripped = s.replace(/\x1b\[[0-9;]*m/g, "");
538+
expect(stripped).not.toContain("░");
539+
});
540+
541+
it("empty bar at page 0 — no filled block character", () => {
542+
const s = buildFetchProgress(0, 10);
543+
const stripped = s.replace(/\x1b\[[0-9;]*m/g, "");
544+
expect(stripped).not.toContain("▓");
545+
});
546+
});

src/api.ts

Lines changed: 33 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -144,23 +144,54 @@ export async function searchCode(
144144
return { items: data.items ?? [], total: data.total_count ?? 0 };
145145
}
146146

147+
// ─── Fetch progress bar ───────────────────────────────────────────────────────
148+
149+
const PROGRESS_BAR_WIDTH = 20;
150+
151+
/**
152+
* Build a single-line purple progress bar string suitable for writing to
153+
* stderr with a leading \r so it overwrites the current line.
154+
*
155+
* Pure function — no side effects; exported for unit testing.
156+
*/
157+
export function buildFetchProgress(currentPage: number, totalPages: number): string {
158+
const filled = totalPages > 0 ? Math.round((currentPage / totalPages) * PROGRESS_BAR_WIDTH) : 0;
159+
const empty = PROGRESS_BAR_WIDTH - filled;
160+
// \x1b[38;5;129m — bright purple (filled blocks)
161+
// \x1b[38;5;240m — dark grey (empty blocks)
162+
const bar = `\x1b[38;5;129m${"▓".repeat(filled)}\x1b[38;5;240m${"░".repeat(empty)}\x1b[0m`;
163+
return `\r Fetching results from GitHub… ${bar} page ${currentPage}/${totalPages}`;
164+
}
165+
147166
export async function fetchAllResults(
148167
query: string,
149168
org: string,
150169
token: string,
151170
): Promise<CodeMatch[]> {
152-
process.stderr.write(pc.dim("Fetching results from GitHub…\n"));
171+
// Write the initial progress line (no newline — will be overwritten by \r).
172+
process.stderr.write(pc.dim(" Fetching results from GitHub…"));
173+
let totalPages = 0;
153174
// GitHub code search is capped at 1000 results; paginatedFetch stops naturally
154175
// when a page returns fewer than 100 items (or the API returns an empty page
155176
// after the cap is reached).
156177
const allItems = await paginatedFetch<RawCodeItem>(
157178
async (page) => {
158-
const { items } = await searchCode(query, org, token, page);
179+
const { items, total } = await searchCode(query, org, token, page);
180+
// On the first page, compute the expected number of pages so the bar
181+
// can show realistic progress. GitHub caps at 1 000 results (10 pages).
182+
if (page === 1) {
183+
totalPages = Math.min(Math.ceil(total / 100), 10);
184+
}
185+
if (totalPages > 0) {
186+
process.stderr.write(buildFetchProgress(page, totalPages));
187+
}
159188
return items;
160189
},
161190
100, // GitHub returns up to 100 items per page
162191
250, // Be kind to rate limits between pages
163192
);
193+
// Terminate the progress line.
194+
process.stderr.write("\n");
164195

165196
// ─── Resolve absolute line numbers ──────────────────────────────────────
166197
// The GitHub Code Search API returns fragment-relative character indices

0 commit comments

Comments
 (0)