sqltui is a terminal UI for viewing and querying tabular data. It has two modes:
- File mode (
sqltui [files...]): open data files (csv/tsv/dsv/json/jsonl/parquet/excel/sqlite/fwf/logfmt/markdown/html), browse them as tabs, run SQL against them via an embedded in-memory SQLite engine. - Database mode (
sqltui mysql|postgres|sqlite|redis): connect to a live database, browse tables, run statements. This preserves the project's original functionality.
Both modes share one UI: tabbed table viewer + row-detail sheet + command palette + overlays.
Do not reference any external project by name anywhere in code, comments, docs or identifiers. Use neutral vocabulary: frame, tab, pane, sheet, palette, overlay, prompt.
cmd/ cobra CLI: root (file mode) + mysql/postgres/sqlite/redis
internal/
data/ Frame model (frame.go DONE) + infer.go, search.go, stats.go
query/ embedded SQL engine over frames + SQL autocompletion
reader/ format readers (reader.go, source.go DONE = contracts)
writer/ format writers (writer.go DONE = contract)
theme/ theme.go (DONE = contract) + builtin.go palettes
config/ config.go (existing JSON config, extend with ui section)
db/ backend.go (DONE = contract) + mysqlbe/ postgresbe/ sqlitebe/ redisbe/
ui/ bubbletea app: overlay.go (DONE = contract), app/tabs/pane/tableview/sheet/statusbar/searchbar/keymap
popup/ modal overlays (palette, query editor, exporter, ...)
plot/ histogram + scatter rendering
- Imports:
tea "charm.land/bubbletea/v2","charm.land/lipgloss/v2","charm.land/bubbles/v2/..."(NOT github.com/charmbracelet paths). type Model interface { Init() tea.Cmd; Update(tea.Msg) (tea.Model, tea.Cmd); View() tea.View }— View returnstea.View, not string.- Build views with
v := tea.NewView(content); v.AltScreen = true; v.BackgroundColor = c; v.MouseMode = .... - Key input arrives as
tea.KeyPressMsg;msg.String()yields"a","A","ctrl+r","shift+left","enter","esc","f1","pgup","pgdown","home","end","tab". Match on those strings in a keymap layer. tea.WindowSizeMsg{Width,Height}sent initially and on resize.- lipgloss v2:
lipgloss.Color(hex)is a function returningcolor.Color, not a type.lipgloss.NewStyle()...Render(s)unchanged. Uselipgloss.JoinHorizontal/JoinVertical/Place. - Overlay compositing: render base view, then splice the overlay box into its center manually (helper in ui package); v2.0.8 has no public layer API.
- Check any uncertain API with
go doc charm.land/bubbletea/v2 <Symbol>before use.
data.Frame/data.Column/data.DType: columnar table; cell values are exactlynil | string | int64 | float64 | bool | time.Time.reader.Reader.Read(*Source, Options) ([]NamedFrame, error); register viareader.Register(format, r)ininit().writer.Writer.Write(io.Writer, *data.Frame, Options) error; register viawriter.Register.theme.Palette(pure data) →theme.Newderives all lipgloss styles.db.Backend(SQL engines) anddb.KVBackend(redis) overdata.Frame.ui.Overlay { Update(tea.Msg) (Overlay, tea.Cmd); View(w, h int, th *theme.Theme) string }; close by returningui.CloseOverlaycmd; errors viaui.ErrorMsg; toasts viaui.ToastMsg.
┌ tab bar / status ────────────────────────────────┐
│ table view (or sheet view) │
│ ... │
├──────────────────────────────────────────────────┤
│ status bar: Tab i/n | Row r | k x m | breadcrumb │ + search bar when active
└──────────────────────────────────────────────────┘
- Tabs: each loaded table is a tab. Each tab (pane) holds a stack of frames: the base frame plus one frame per applied operation (query/filter/select/order/search/cast). A breadcrumb in the status bar shows the history, e.g.
table > filter > sort.qpops one level; when the stack has one frame,qcloses the tab; closing the last tab quits. - Table view: striped rows, bold header, optional row-number gutter, optional borders. Two column modes toggled with
e: compact (shrink columns so everything fits) and expanded (natural widths, horizontal scroll,w/bjump between columns,_/$first/last column). Selected row highlighted. - Sheet view (
enter): vertical field-per-line detail of the selected row, scrollable,ccopies row via OSC52,q/escreturns. - Search bar:
/fuzzy,?exact. Live filtering as you type;entercommits the filtered frame onto the stack,esccancels. - Status bar: colored tag segments — tab index, row position, shape (
rows x cols), breadcrumb.
| Key | Action |
|---|---|
up/k, down/j |
move row |
left/h, right/l |
horizontal scroll (expanded mode) |
g/home, G/end |
first / last row |
ctrl+u/ctrl+d |
half page up/down |
pgup/ctrl+b, pgdown/ctrl+f |
page up/down |
w / b / _ / $ |
next/prev/first/last column |
enter |
open sheet view |
e |
toggle compact/expanded columns |
i |
table info overlay |
R |
jump to random row |
1-9 |
go-to-row prompt (digit prefilled) |
/, ? |
fuzzy / exact search |
: |
command palette |
t |
tab switcher |
H/shift+left, L/shift+right |
prev / next tab |
q |
pop frame stack / close tab |
Q |
quit app |
f1 |
help overlay |
Fuzzy-filterable list; typing a registered prefix + space switches to an inline prompt. Commands (name → behavior):
query/q full SQL editor with completion • select/s inline SELECT list •
filter/f inline WHERE clause • order/o/sort inline ORDER BY •
search exact search • fuzzysearch fuzzy search • schema schema browser
(all loaded tables; enter jumps) • info table info • export export wizard •
import import wizard • cast column type cast • register name the current
frame for SQL • histogram histogram builder • scatterplot scatter builder •
edit open frame in $EDITOR as CSV, reimport on save • theme theme selector
with live preview • toggleborders • togglerownumbers • reset back to base
frame • reloadconfig • help • quit
- Engine: in-memory SQLite (modernc driver, already a dependency).
- Every loaded frame is registered as a table under its tab name; the current
frame is also registered as
_(quoted) soselect * from _ where ...works. - Inline prompts expand to:
SELECT <input> FROM _/SELECT * FROM _ WHERE <input>/SELECT * FROM _ ORDER BY <input>. - Full-query results open as a new tab; inline-prompt results push onto the current pane's stack.
- Completion: SQL keywords + column names of the current frame + registered table names.
- Subcommand opens a connection form overlay (prefilled from config, ctrl+s saves) on top of an empty workspace.
- On connect: schema browser lists namespaces/tables; selecting a table loads
SELECT * ... LIMIT 200into a new tab. :queryruns against the live backend (not the embedded engine). Non-query statements show rows-affected as a toast.- Redis: key browser grouped by type as the schema overlay; selecting a key shows its rendered value in a sheet;
:queryprompt executes raw redis commands, result shown as pretty JSON in a sheet. Command completion fromredisbe.CommandHelps.
-f/--format, --separator, --quote-char, --no-header, --ignore-errors,
--infer-schema no|fast|safe, --infer-types, --truncate-ragged-lines,
--widths, --separator-length, --no-flexible-width, --sqlite-key,
--multiparts (already wired in cmd/root.go). - reads stdin. http(s) URLs
download to a temp file.
~40 built-in palettes in internal/theme/builtin.go (dark & light: catppuccin
variants, dracula, nord, gruvbox, solarized, tokyo-night, monokai, one-dark,
github, ayu, rose-pine, everforest, kanagawa, zenburn, material, ...).
theme.Builtin(name) lookup + theme.Names() sorted. Default sorbet
(warm pastel: yellow/orange leading, sky blue/mint supporting).
Selected theme + border/row-number toggles persist in the JSON config
(~/.config/sqltui/config.json, new ui section).
Every package lands with go vet-clean code and unit tests where logic is
non-trivial (inference, search, engine, readers, writers). go build ./...
must stay green after every phase.