@deaquinodev/querky 0.4.5 → 0.4.7

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (2) hide show
  1. package/dist/index.js +147 -100
  2. package/package.json +1 -1
package/dist/index.js CHANGED
@@ -193,11 +193,11 @@ async function connectDsn(dsn) {
193
193
  }
194
194
 
195
195
  // src/ui/components/App.tsx
196
- import { useState as useState3, useEffect as useEffect4, useRef as useRef2 } from "react";
196
+ import { useState as useState3, useEffect as useEffect4, useRef as useRef2, memo } from "react";
197
197
  import { writeFileSync as writeFileSync5 } from "fs";
198
198
  import { homedir as homedir4 } from "os";
199
199
  import { join as join6 } from "path";
200
- import { Box as Box7, Text as Text7, Static, useApp, useInput as useInput2, useStdin as useStdin2 } from "ink";
200
+ import { Box as Box7, Text as Text7, useApp, useInput as useInput2, useStdin as useStdin2 } from "ink";
201
201
 
202
202
  // src/db/query.ts
203
203
  async function runQuery(client, sql) {
@@ -1189,7 +1189,7 @@ function useVimInput(onSubmit, isActive, vimEnabled = true, onTab, history = [],
1189
1189
  }
1190
1190
  if (key.leftArrow) return { ...s, cursor: Math.max(0, s.cursor - 1) };
1191
1191
  if (key.rightArrow) return { ...s, cursor: Math.min(s.value.length, s.cursor + 1) };
1192
- if (key.tab) {
1192
+ if (key.tab && !key.shift) {
1193
1193
  const sugs = getSuggestions?.(s.value, s.cursor) ?? [];
1194
1194
  if (sugs.length > 0) {
1195
1195
  const next = s.suggestionIndex < sugs.length - 1 ? s.suggestionIndex + 1 : 0;
@@ -1199,6 +1199,14 @@ function useVimInput(onSubmit, isActive, vimEnabled = true, onTab, history = [],
1199
1199
  if (completed != null) return { ...s, value: completed, cursor: completed.length };
1200
1200
  return s;
1201
1201
  }
1202
+ if (key.tab && key.shift) {
1203
+ const sugs = getSuggestions?.(s.value, s.cursor) ?? [];
1204
+ if (sugs.length > 0) {
1205
+ const next = s.suggestionIndex <= 0 ? sugs.length - 1 : s.suggestionIndex - 1;
1206
+ return { ...s, suggestionIndex: next };
1207
+ }
1208
+ return s;
1209
+ }
1202
1210
  if (key.ctrl && input === "e") {
1203
1211
  return { ...s, pendingEditor: s.value };
1204
1212
  }
@@ -1496,9 +1504,10 @@ function FuzzyHighlight({ text, token, selected }) {
1496
1504
  (seg, i) => seg.hit ? /* @__PURE__ */ jsx(Text, { color: ACCENT, bold: true, children: seg.chars }, i) : /* @__PURE__ */ jsx(Text, { dimColor: !selected, children: seg.chars }, i)
1497
1505
  ) });
1498
1506
  }
1499
- var BG = "#1e1b4b";
1507
+ var BG = "#1e1e1e";
1500
1508
  var ACCENT = "#818cf8";
1501
- var PLACEHOLDER = "#6366f1";
1509
+ var PROMPT_MUTED = "#6b7280";
1510
+ var PLACEHOLDER = "#4b5563";
1502
1511
  var KW_COLOR = "#a5b4fc";
1503
1512
  var STR_COLOR = "#fb923c";
1504
1513
  var NUM_COLOR = "#86efac";
@@ -1510,9 +1519,9 @@ function sqlTokenColor(type) {
1510
1519
  if (type === "comment") return CMT_COLOR;
1511
1520
  return void 0;
1512
1521
  }
1513
- function SqlLine({ text }) {
1522
+ function SqlLine({ text, bg }) {
1514
1523
  const tokens = tokenizeSql(text);
1515
- return /* @__PURE__ */ jsx(Fragment, { children: tokens.map((tok, i) => /* @__PURE__ */ jsx(Text, { color: sqlTokenColor(tok.type), children: tok.text }, i)) });
1524
+ return /* @__PURE__ */ jsx(Fragment, { children: tokens.map((tok, i) => /* @__PURE__ */ jsx(Text, { backgroundColor: bg, color: sqlTokenColor(tok.type), children: tok.text }, i)) });
1516
1525
  }
1517
1526
  function SqlHighlightedInput({ text, cursorAt, mode, pad: pad2 }) {
1518
1527
  const tokens = tokenizeSql(text);
@@ -1530,7 +1539,7 @@ function SqlHighlightedInput({ text, cursorAt, mode, pad: pad2 }) {
1530
1539
  const after = tok.text.slice(rel + 1);
1531
1540
  if (before) parts.push(/* @__PURE__ */ jsx(Text, { backgroundColor: BG, color, children: before }, `${i}b`));
1532
1541
  if (mode === "INSERT") {
1533
- parts.push(/* @__PURE__ */ jsx(Text, { backgroundColor: BG, color: ACCENT, bold: true, children: "\u258C" }, `${i}c`));
1542
+ parts.push(/* @__PURE__ */ jsx(Text, { backgroundColor: BG, color: PROMPT_MUTED, bold: true, children: "\u258C" }, `${i}c`));
1534
1543
  } else {
1535
1544
  parts.push(/* @__PURE__ */ jsx(Text, { backgroundColor: ACCENT, color: BG, bold: true, children: ch || " " }, `${i}c`));
1536
1545
  }
@@ -1543,7 +1552,7 @@ function SqlHighlightedInput({ text, cursorAt, mode, pad: pad2 }) {
1543
1552
  }
1544
1553
  if (!cursorDone) {
1545
1554
  if (mode === "INSERT") {
1546
- parts.push(/* @__PURE__ */ jsx(Text, { backgroundColor: BG, color: ACCENT, bold: true, children: "\u258C" }, "c"));
1555
+ parts.push(/* @__PURE__ */ jsx(Text, { backgroundColor: BG, color: PROMPT_MUTED, bold: true, children: "\u258C" }, "c"));
1547
1556
  } else {
1548
1557
  parts.push(/* @__PURE__ */ jsx(Text, { backgroundColor: ACCENT, color: BG, bold: true, children: " " }, "c"));
1549
1558
  }
@@ -1635,16 +1644,19 @@ function QueryInput({ onSubmit, isLoading, onModeChange, onShellModeChange, vimE
1635
1644
  const isMultiLine = !isEmpty && !isShellMode && !isCommand && value.includes("\n");
1636
1645
  const dOff = isShellMode ? 2 : 0;
1637
1646
  const placeholder = "Type a SQL query\u2026";
1647
+ const previewResult = !isMultiLine && suggestionIndex >= 0 && suggestions.length > 0 ? handleSuggestionAccept(value, cursorPos, suggestions[suggestionIndex]) : null;
1648
+ const renderValue = previewResult?.value ?? value;
1649
+ const renderCursor = previewResult?.cursor ?? cursorPos;
1638
1650
  const lineWidth = innerWidth - 4;
1639
- const displayValue = value.slice(dOff);
1640
- const displayCursor = Math.max(0, cursorPos - dOff);
1651
+ const displayValue = renderValue.slice(dOff);
1652
+ const displayCursor = Math.max(0, renderCursor - dOff);
1641
1653
  const scrollStart = displayCursor > lineWidth - 1 ? displayCursor - lineWidth + 1 : 0;
1642
1654
  const visibleBefore = displayValue.slice(scrollStart, displayCursor);
1643
- const cursorChar = cursorPos < dOff ? " " : displayValue[displayCursor] ?? " ";
1655
+ const cursorChar = renderCursor < dOff ? " " : displayValue[displayCursor] ?? " ";
1644
1656
  const visibleAfter = displayValue.slice(displayCursor + 1, scrollStart + lineWidth);
1645
1657
  const textPad = " ".repeat(Math.max(0, lineWidth - visibleBefore.length - 1 - visibleAfter.length));
1646
- const visibleSql = value.slice(scrollStart, scrollStart + lineWidth);
1647
- const relativeSqlCursor = cursorPos - scrollStart;
1658
+ const visibleSql = renderValue.slice(scrollStart, scrollStart + lineWidth);
1659
+ const relativeSqlCursor = renderCursor - scrollStart;
1648
1660
  const sqlCursorAtEnd = relativeSqlCursor >= visibleSql.length;
1649
1661
  const sqlPad = " ".repeat(Math.max(0, lineWidth - visibleSql.length - (sqlCursorAtEnd ? 1 : 0)));
1650
1662
  const emptyPad = " ".repeat(Math.max(0, lineWidth - placeholder.length - 1));
@@ -1656,31 +1668,37 @@ function QueryInput({ onSubmit, isLoading, onModeChange, onShellModeChange, vimE
1656
1668
  );
1657
1669
  const hintsInline = totalHintsWidth <= termWidth;
1658
1670
  return /* @__PURE__ */ jsxs(Box, { flexDirection: "column", children: [
1659
- isMultiLine ? /* @__PURE__ */ jsxs(Box, { flexDirection: "column", children: [
1660
- /* @__PURE__ */ jsx(Box, { flexDirection: "column", borderStyle: "round", borderColor: ACCENT, paddingX: 1, children: value.split("\n").map((line, i, arr) => {
1671
+ isMultiLine ? /* @__PURE__ */ jsxs(Fragment, { children: [
1672
+ /* @__PURE__ */ jsx(Text, { backgroundColor: BG, children: emptyLine }),
1673
+ value.split("\n").map((line, i, arr) => {
1661
1674
  const numW = String(arr.length).length;
1675
+ const prefixWidth = 2 + numW + 3;
1676
+ const contentWidth = innerWidth - prefixWidth;
1662
1677
  const isLast = i === arr.length - 1;
1678
+ const linePad = " ".repeat(Math.max(0, contentWidth - line.length - (isLast ? 1 : 0)));
1663
1679
  return /* @__PURE__ */ jsxs(Box, { children: [
1664
- /* @__PURE__ */ jsxs(Text, { dimColor: true, children: [
1680
+ /* @__PURE__ */ jsxs(Text, { backgroundColor: BG, dimColor: true, children: [
1681
+ " ",
1665
1682
  String(i + 1).padStart(numW),
1666
1683
  " \u2502 "
1667
1684
  ] }),
1668
- /* @__PURE__ */ jsx(SqlLine, { text: line }),
1669
- isLast && (mode === "INSERT" ? /* @__PURE__ */ jsx(Text, { color: ACCENT, bold: true, children: "\u258C" }) : /* @__PURE__ */ jsx(Text, { backgroundColor: ACCENT, color: BG, bold: true, children: " " }))
1685
+ /* @__PURE__ */ jsx(SqlLine, { text: line, bg: BG }),
1686
+ isLast && (mode === "INSERT" ? /* @__PURE__ */ jsx(Text, { backgroundColor: BG, color: PROMPT_MUTED, bold: true, children: "\u258C" }) : /* @__PURE__ */ jsx(Text, { backgroundColor: ACCENT, color: BG, bold: true, children: " " })),
1687
+ /* @__PURE__ */ jsx(Text, { backgroundColor: BG, children: linePad })
1670
1688
  ] }, i);
1671
- }) }),
1672
- /* @__PURE__ */ jsx(Box, { marginTop: 1, children: /* @__PURE__ */ jsx(Text, { dimColor: true, children: "Enter: run \xB7 Ctrl+E / e: re-edit" }) })
1689
+ }),
1690
+ /* @__PURE__ */ jsx(Text, { backgroundColor: BG, children: emptyLine })
1673
1691
  ] }) : /* @__PURE__ */ jsxs(Fragment, { children: [
1674
1692
  /* @__PURE__ */ jsx(Text, { backgroundColor: BG, children: emptyLine }),
1675
1693
  /* @__PURE__ */ jsxs(Box, { children: [
1676
- /* @__PURE__ */ jsx(Text, { backgroundColor: BG, color: isShellMode ? theme.shellMode : ACCENT, bold: true, children: isShellMode ? " $ " : " > " }),
1694
+ /* @__PURE__ */ jsx(Text, { backgroundColor: BG, color: isShellMode ? theme.shellMode : PROMPT_MUTED, bold: true, children: isShellMode ? " $ " : " > " }),
1677
1695
  isEmpty ? /* @__PURE__ */ jsxs(Fragment, { children: [
1678
1696
  /* @__PURE__ */ jsx(Text, { backgroundColor: BG, color: PLACEHOLDER, children: placeholder }),
1679
- /* @__PURE__ */ jsx(Text, { backgroundColor: BG, color: ACCENT, bold: true, children: "\u258C" }),
1697
+ /* @__PURE__ */ jsx(Text, { backgroundColor: BG, color: PROMPT_MUTED, bold: true, children: "\u258C" }),
1680
1698
  /* @__PURE__ */ jsx(Text, { backgroundColor: BG, children: emptyPad })
1681
1699
  ] }) : isShellMode || isCommand ? /* @__PURE__ */ jsxs(Fragment, { children: [
1682
1700
  /* @__PURE__ */ jsx(Text, { backgroundColor: BG, children: visibleBefore }),
1683
- mode === "INSERT" ? /* @__PURE__ */ jsx(Text, { backgroundColor: BG, color: ACCENT, bold: true, children: "\u258C" }) : /* @__PURE__ */ jsx(Text, { backgroundColor: ACCENT, color: BG, bold: true, children: cursorChar }),
1701
+ mode === "INSERT" ? /* @__PURE__ */ jsx(Text, { backgroundColor: BG, color: PROMPT_MUTED, bold: true, children: "\u258C" }) : /* @__PURE__ */ jsx(Text, { backgroundColor: ACCENT, color: BG, bold: true, children: cursorChar }),
1684
1702
  /* @__PURE__ */ jsxs(Text, { backgroundColor: BG, children: [
1685
1703
  visibleAfter,
1686
1704
  textPad
@@ -1689,30 +1707,45 @@ function QueryInput({ onSubmit, isLoading, onModeChange, onShellModeChange, vimE
1689
1707
  ] }),
1690
1708
  /* @__PURE__ */ jsx(Text, { backgroundColor: BG, children: emptyLine })
1691
1709
  ] }),
1692
- suggestions.length > 0 && /* @__PURE__ */ jsxs(Box, { flexDirection: "column", marginTop: 1, marginLeft: 2, children: [
1693
- suggestions.map((name, i) => {
1694
- const selected = i === suggestionIndex;
1695
- if (isCommand) {
1696
- return /* @__PURE__ */ jsxs(Box, { children: [
1697
- /* @__PURE__ */ jsxs(Text, { children: [
1698
- /* @__PURE__ */ jsx(Text, { dimColor: !selected, color: selected ? ACCENT : void 0, children: "/" }),
1699
- /* @__PURE__ */ jsx(FuzzyHighlight, { text: name, token: partial, selected })
1700
- ] }),
1701
- /* @__PURE__ */ jsxs(Text, { dimColor: true, children: [
1702
- " \u2014 ",
1703
- descMap[name]
1704
- ] })
1705
- ] }, name);
1706
- }
1707
- return /* @__PURE__ */ jsx(Box, { children: /* @__PURE__ */ jsx(FuzzyHighlight, { text: name, token: sqlToken, selected }) }, name);
1708
- }),
1709
- /* @__PURE__ */ jsx(Text, { dimColor: true, children: "Tab/\u2191\u2193 navigate Enter select" })
1710
- ] }),
1711
- /* @__PURE__ */ jsx(Box, { marginTop: 1, flexDirection: hintsInline ? "row" : "column", children: allHints.map(([key, desc], i) => /* @__PURE__ */ jsxs(Text, { children: [
1712
- hintsInline && i > 0 && /* @__PURE__ */ jsx(Text, { dimColor: true, children: " | " }),
1713
- /* @__PURE__ */ jsx(Text, { color: ACCENT, bold: true, children: desc }),
1714
- /* @__PURE__ */ jsx(Text, { dimColor: true, children: `: ${key}` })
1715
- ] }, desc)) })
1710
+ (() => {
1711
+ const hasSuggestions = !isEmpty && !isShellMode && !isMultiLine && suggestions.length > 0;
1712
+ const VISIBLE = 4;
1713
+ const SEP_W = 3;
1714
+ const ELLIPSIS_W = 3;
1715
+ const prefixW = isCommand ? 1 : 0;
1716
+ const availW = termWidth - 2 - ELLIPSIS_W - (VISIBLE - 1) * SEP_W - VISIBLE * prefixW;
1717
+ const maxPerItem = Math.max(6, Math.floor(availW / VISIBLE));
1718
+ const winStart = hasSuggestions ? Math.min(Math.max(0, suggestionIndex - 1), Math.max(0, suggestions.length - VISIBLE)) : 0;
1719
+ const windowSlice = hasSuggestions ? suggestions.slice(winStart, winStart + VISIBLE) : [];
1720
+ function truncate(s) {
1721
+ return s.length > maxPerItem ? s.slice(0, maxPerItem - 1) + "\u2026" : s;
1722
+ }
1723
+ return /* @__PURE__ */ jsxs(Box, { flexDirection: "column", marginTop: 1, children: [
1724
+ /* @__PURE__ */ jsx(Box, { marginLeft: 2, children: hasSuggestions ? /* @__PURE__ */ jsxs(Fragment, { children: [
1725
+ windowSlice.map((name, i) => {
1726
+ const realIdx = winStart + i;
1727
+ const selected = realIdx === suggestionIndex;
1728
+ const displayName = truncate(name);
1729
+ return /* @__PURE__ */ jsxs(Text, { children: [
1730
+ i > 0 && /* @__PURE__ */ jsx(Text, { dimColor: true, children: " " }),
1731
+ isCommand && /* @__PURE__ */ jsx(Text, { dimColor: !selected, color: selected ? ACCENT : void 0, children: "/" }),
1732
+ /* @__PURE__ */ jsx(FuzzyHighlight, { text: displayName, token: isCommand ? partial : sqlToken, selected })
1733
+ ] }, name);
1734
+ }),
1735
+ suggestions.length > VISIBLE && /* @__PURE__ */ jsx(Text, { dimColor: true, children: " \u2026" })
1736
+ ] }) : /* @__PURE__ */ jsx(Box, { flexDirection: hintsInline ? "row" : "column", children: allHints.map(([key, desc], i) => /* @__PURE__ */ jsxs(Text, { children: [
1737
+ hintsInline && i > 0 && /* @__PURE__ */ jsx(Text, { dimColor: true, children: " | " }),
1738
+ /* @__PURE__ */ jsx(Text, { color: ACCENT, bold: true, children: desc }),
1739
+ /* @__PURE__ */ jsx(Text, { dimColor: true, children: `: ${key}` })
1740
+ ] }, desc)) }) }),
1741
+ /* @__PURE__ */ jsx(Box, { marginLeft: 2, children: hasSuggestions ? /* @__PURE__ */ jsxs(Text, { dimColor: true, children: [
1742
+ "Tab/\u2191\u2193 navigate Enter select ",
1743
+ suggestionIndex + 1,
1744
+ "/",
1745
+ suggestions.length
1746
+ ] }) : /* @__PURE__ */ jsx(Text, { children: " " }) })
1747
+ ] });
1748
+ })()
1716
1749
  ] });
1717
1750
  }
1718
1751
 
@@ -1725,7 +1758,7 @@ import { Box as Box2, Text as Text2 } from "ink";
1725
1758
  import { jsx as jsx2, jsxs as jsxs2 } from "react/jsx-runtime";
1726
1759
  var COL_PAD = 1;
1727
1760
  var INDIGO = "#818cf8";
1728
- var BORDER2 = "white";
1761
+ var BORDER = "white";
1729
1762
  var NULL_COLOR = "#6366f1";
1730
1763
  var NULL_MARKER = "\u2205";
1731
1764
  function isNull(val) {
@@ -1756,10 +1789,10 @@ function hline(widths, left, mid, right) {
1756
1789
  function ExpandedTable({ columns, rows }) {
1757
1790
  const keyWidth = columns.reduce((max, col) => Math.max(max, col.length), 0);
1758
1791
  return /* @__PURE__ */ jsx2(Box2, { flexDirection: "column", children: rows.map((row, i) => /* @__PURE__ */ jsxs2(Box2, { flexDirection: "column", marginBottom: i < rows.length - 1 ? 1 : 0, children: [
1759
- /* @__PURE__ */ jsx2(Text2, { color: BORDER2, children: `\u2500[ Record ${i + 1} ]${"\u2500".repeat(Math.max(0, keyWidth + 14 - String(i + 1).length))}` }),
1792
+ /* @__PURE__ */ jsx2(Text2, { color: BORDER, children: `\u2500[ Record ${i + 1} ]${"\u2500".repeat(Math.max(0, keyWidth + 14 - String(i + 1).length))}` }),
1760
1793
  columns.map((col) => /* @__PURE__ */ jsxs2(Box2, { children: [
1761
1794
  /* @__PURE__ */ jsx2(Text2, { color: INDIGO, bold: true, children: col.padEnd(keyWidth) }),
1762
- /* @__PURE__ */ jsx2(Text2, { color: BORDER2, children: " \u2502 " }),
1795
+ /* @__PURE__ */ jsx2(Text2, { color: BORDER, children: " \u2502 " }),
1763
1796
  isNull(row[col]) ? /* @__PURE__ */ jsx2(Text2, { color: NULL_COLOR, dimColor: true, children: NULL_MARKER }) : /* @__PURE__ */ jsx2(Text2, { children: cellValue(row[col]) })
1764
1797
  ] }, col))
1765
1798
  ] }, i)) });
@@ -1772,51 +1805,43 @@ function Table({ columns, rows, expanded = false }) {
1772
1805
  const botLine = hline(widths, "\u2570", "\u2534", "\u256F");
1773
1806
  function renderHeaderRow(cols) {
1774
1807
  return /* @__PURE__ */ jsxs2(Box2, { children: [
1775
- /* @__PURE__ */ jsx2(Text2, { color: BORDER2, children: "\u2502" }),
1808
+ /* @__PURE__ */ jsx2(Text2, { color: BORDER, children: "\u2502" }),
1776
1809
  cols.map((v, i) => /* @__PURE__ */ jsxs2(Box2, { children: [
1777
1810
  /* @__PURE__ */ jsx2(Text2, { color: INDIGO, bold: true, children: " ".repeat(COL_PAD) + pad(v, widths[i]) + " ".repeat(COL_PAD) }),
1778
- /* @__PURE__ */ jsx2(Text2, { color: BORDER2, children: "\u2502" })
1811
+ /* @__PURE__ */ jsx2(Text2, { color: BORDER, children: "\u2502" })
1779
1812
  ] }, i))
1780
1813
  ] });
1781
1814
  }
1782
1815
  function renderDataRow(row) {
1783
1816
  return /* @__PURE__ */ jsxs2(Box2, { children: [
1784
- /* @__PURE__ */ jsx2(Text2, { color: BORDER2, children: "\u2502" }),
1817
+ /* @__PURE__ */ jsx2(Text2, { color: BORDER, children: "\u2502" }),
1785
1818
  columns.map((col, i) => /* @__PURE__ */ jsxs2(Box2, { children: [
1786
1819
  isNull(row[col]) ? /* @__PURE__ */ jsx2(Text2, { color: NULL_COLOR, dimColor: true, children: " ".repeat(COL_PAD) + pad(NULL_MARKER, widths[i]) + " ".repeat(COL_PAD) }) : /* @__PURE__ */ jsx2(Text2, { children: " ".repeat(COL_PAD) + pad(cellValue(row[col]), widths[i]) + " ".repeat(COL_PAD) }),
1787
- /* @__PURE__ */ jsx2(Text2, { color: BORDER2, children: "\u2502" })
1820
+ /* @__PURE__ */ jsx2(Text2, { color: BORDER, children: "\u2502" })
1788
1821
  ] }, i))
1789
1822
  ] });
1790
1823
  }
1791
1824
  return /* @__PURE__ */ jsxs2(Box2, { flexDirection: "column", children: [
1792
- /* @__PURE__ */ jsx2(Text2, { color: BORDER2, children: topLine }),
1825
+ /* @__PURE__ */ jsx2(Text2, { color: BORDER, children: topLine }),
1793
1826
  renderHeaderRow(columns),
1794
- /* @__PURE__ */ jsx2(Text2, { color: BORDER2, children: midLine }),
1827
+ /* @__PURE__ */ jsx2(Text2, { color: BORDER, children: midLine }),
1795
1828
  rows.map((row, i) => /* @__PURE__ */ jsxs2(Box2, { flexDirection: "column", children: [
1796
1829
  renderDataRow(row),
1797
- i < rows.length - 1 && /* @__PURE__ */ jsx2(Text2, { color: BORDER2, children: midLine })
1830
+ i < rows.length - 1 && /* @__PURE__ */ jsx2(Text2, { color: BORDER, children: midLine })
1798
1831
  ] }, i)),
1799
- /* @__PURE__ */ jsx2(Text2, { color: BORDER2, children: botLine })
1832
+ /* @__PURE__ */ jsx2(Text2, { color: BORDER, children: botLine })
1800
1833
  ] });
1801
1834
  }
1802
1835
 
1803
1836
  // src/ui/components/QueryResult.tsx
1804
1837
  import { jsx as jsx3, jsxs as jsxs3 } from "react/jsx-runtime";
1805
- var ERROR_BG = "#3b0f0f";
1806
1838
  var ERROR_FG = "#ff4444";
1807
1839
  function ErrorBox({ message }) {
1808
- const cols = Math.max(0, (process.stdout.columns ?? 80) - 2);
1809
- const blank = " ".repeat(cols);
1810
1840
  const lines = message.split("\n");
1811
- return /* @__PURE__ */ jsxs3(Box3, { flexDirection: "column", marginTop: 1, children: [
1812
- /* @__PURE__ */ jsx3(Text3, { backgroundColor: ERROR_BG, children: blank }),
1813
- lines.map((line, i) => {
1814
- const content = (i === 0 ? " \u2717 " : " ") + line;
1815
- const padded = content.length < cols ? content + " ".repeat(cols - content.length) : content;
1816
- return /* @__PURE__ */ jsx3(Text3, { backgroundColor: ERROR_BG, color: ERROR_FG, bold: true, children: padded }, i);
1817
- }),
1818
- /* @__PURE__ */ jsx3(Text3, { backgroundColor: ERROR_BG, children: blank })
1819
- ] });
1841
+ return /* @__PURE__ */ jsx3(Box3, { flexDirection: "column", marginTop: 1, children: lines.map((line, i) => /* @__PURE__ */ jsxs3(Text3, { color: ERROR_FG, bold: true, children: [
1842
+ i === 0 ? "\u2717 " : " ",
1843
+ line
1844
+ ] }, i)) });
1820
1845
  }
1821
1846
  function formatDuration(ms) {
1822
1847
  if (ms < 1e3) return `${ms}ms`;
@@ -2009,6 +2034,7 @@ var TABLE_COLORS = [
2009
2034
  "#fbbf24",
2010
2035
  "#2dd4bf"
2011
2036
  ];
2037
+ var BORDER2 = "#4b5563";
2012
2038
  var PAD = 1;
2013
2039
  var GAP = 2;
2014
2040
  var FK_PREFIX = "FK \u2192 ";
@@ -2056,31 +2082,31 @@ function TableBox({ table, m, color, colorMap }) {
2056
2082
  const bot = "\u2570" + "\u2500".repeat(nameW + PAD * 2) + "\u2534" + "\u2500".repeat(typeW + PAD * 2) + "\u2534" + "\u2500".repeat(keyW + PAD * 2) + "\u256F";
2057
2083
  const headerW = totalW - 4;
2058
2084
  return /* @__PURE__ */ jsxs6(Box6, { flexDirection: "column", children: [
2059
- /* @__PURE__ */ jsx6(Text6, { color, children: top }),
2085
+ /* @__PURE__ */ jsx6(Text6, { color: BORDER2, children: top }),
2060
2086
  /* @__PURE__ */ jsxs6(Box6, { children: [
2061
- /* @__PURE__ */ jsx6(Text6, { color, children: "\u2502" }),
2087
+ /* @__PURE__ */ jsx6(Text6, { color: BORDER2, children: "\u2502" }),
2062
2088
  /* @__PURE__ */ jsxs6(Text6, { color, bold: true, children: [
2063
2089
  sp,
2064
2090
  p(table.name, headerW),
2065
2091
  sp
2066
2092
  ] }),
2067
- /* @__PURE__ */ jsx6(Text6, { color, children: "\u2502" })
2093
+ /* @__PURE__ */ jsx6(Text6, { color: BORDER2, children: "\u2502" })
2068
2094
  ] }),
2069
- /* @__PURE__ */ jsx6(Text6, { color, children: sep }),
2095
+ /* @__PURE__ */ jsx6(Text6, { color: BORDER2, children: sep }),
2070
2096
  table.columns.map((col, i) => /* @__PURE__ */ jsxs6(Box6, { children: [
2071
- /* @__PURE__ */ jsx6(Text6, { color, children: "\u2502" }),
2097
+ /* @__PURE__ */ jsx6(Text6, { color: BORDER2, children: "\u2502" }),
2072
2098
  /* @__PURE__ */ jsxs6(Text6, { children: [
2073
2099
  sp,
2074
2100
  p(col.name, nameW),
2075
2101
  sp
2076
2102
  ] }),
2077
- /* @__PURE__ */ jsx6(Text6, { color, children: "\u2502" }),
2103
+ /* @__PURE__ */ jsx6(Text6, { color: BORDER2, children: "\u2502" }),
2078
2104
  /* @__PURE__ */ jsxs6(Text6, { dimColor: true, children: [
2079
2105
  sp,
2080
2106
  p(col.type, typeW),
2081
2107
  sp
2082
2108
  ] }),
2083
- /* @__PURE__ */ jsx6(Text6, { color, children: "\u2502" }),
2109
+ /* @__PURE__ */ jsx6(Text6, { color: BORDER2, children: "\u2502" }),
2084
2110
  col.isPk ? /* @__PURE__ */ jsxs6(Text6, { bold: true, children: [
2085
2111
  sp,
2086
2112
  p("PK", keyW),
@@ -2099,9 +2125,9 @@ function TableBox({ table, m, color, colorMap }) {
2099
2125
  " ".repeat(keyW),
2100
2126
  sp
2101
2127
  ] }),
2102
- /* @__PURE__ */ jsx6(Text6, { color, children: "\u2502" })
2128
+ /* @__PURE__ */ jsx6(Text6, { color: BORDER2, children: "\u2502" })
2103
2129
  ] }, i)),
2104
- /* @__PURE__ */ jsx6(Text6, { color, children: bot })
2130
+ /* @__PURE__ */ jsx6(Text6, { color: BORDER2, children: bot })
2105
2131
  ] });
2106
2132
  }
2107
2133
  function ErdView({ data }) {
@@ -2119,7 +2145,7 @@ function ErdView({ data }) {
2119
2145
  {
2120
2146
  table: data.tables[ti],
2121
2147
  m: metrics[ti],
2122
- color: colorMap.get(data.tables[ti].name) ?? BORDER,
2148
+ color: colorMap.get(data.tables[ti].name) ?? BORDER2,
2123
2149
  colorMap
2124
2150
  }
2125
2151
  ) }, ti)) }, ri)) });
@@ -2138,20 +2164,42 @@ function limitLines(s, n) {
2138
2164
  return lines.slice(0, n).join("\n") + `
2139
2165
  \u2026 +${lines.length - n} more lines (scroll up after next submit)`;
2140
2166
  }
2141
- function EntryView({ entry }) {
2167
+ var QUERY_HEADER_COLOR = "#9ca3af";
2168
+ function QueryHeader({ label, query }) {
2169
+ const isMultiLine = query.includes("\n");
2170
+ if (isMultiLine) {
2171
+ return /* @__PURE__ */ jsxs7(Box7, { flexDirection: "column", children: [
2172
+ /* @__PURE__ */ jsxs7(Text7, { children: [
2173
+ /* @__PURE__ */ jsx7(Text7, { color: theme.accent, bold: true, children: "\u25CF " }),
2174
+ /* @__PURE__ */ jsxs7(Text7, { color: QUERY_HEADER_COLOR, bold: true, children: [
2175
+ label,
2176
+ ":"
2177
+ ] })
2178
+ ] }),
2179
+ query.split("\n").map((line, i) => /* @__PURE__ */ jsxs7(Text7, { color: QUERY_HEADER_COLOR, bold: true, children: [
2180
+ " ",
2181
+ line
2182
+ ] }, i))
2183
+ ] });
2184
+ }
2185
+ return /* @__PURE__ */ jsxs7(Text7, { children: [
2186
+ /* @__PURE__ */ jsx7(Text7, { color: theme.accent, bold: true, children: "\u25CF " }),
2187
+ /* @__PURE__ */ jsxs7(Text7, { color: QUERY_HEADER_COLOR, bold: true, children: [
2188
+ label,
2189
+ "(",
2190
+ query,
2191
+ ")"
2192
+ ] })
2193
+ ] });
2194
+ }
2195
+ var EntryView = memo(function EntryView2({ entry }) {
2142
2196
  const showAi = entry.aiResponse !== "" || entry.aiError !== null;
2143
2197
  const showErd = entry.erdData !== null;
2144
2198
  const isShell = entry.query.startsWith("!");
2145
2199
  const isCommand = !isShell && (entry.query.startsWith("/") || entry.query.startsWith("\\"));
2146
- const label = isShell ? "Shell:" : isCommand ? "Command:" : "Query:";
2200
+ const label = isShell ? "Shell" : isCommand ? "Command" : "Query";
2147
2201
  return /* @__PURE__ */ jsxs7(Box7, { flexDirection: "column", marginBottom: 1, paddingX: 1, children: [
2148
- /* @__PURE__ */ jsxs7(Box7, { borderStyle: "round", borderColor: theme.accent, paddingX: 1, children: [
2149
- /* @__PURE__ */ jsxs7(Text7, { color: theme.accent, bold: true, children: [
2150
- label,
2151
- " "
2152
- ] }),
2153
- /* @__PURE__ */ jsx7(Text7, { dimColor: true, children: entry.query })
2154
- ] }),
2202
+ /* @__PURE__ */ jsx7(QueryHeader, { label, query: entry.query }),
2155
2203
  /* @__PURE__ */ jsx7(Box7, { marginTop: 1, flexDirection: "column", children: isShell ? entry.shellOutput !== null && /* @__PURE__ */ jsx7(Text7, { children: entry.shellOutput || "(no output)" }) : /* @__PURE__ */ jsxs7(Fragment4, { children: [
2156
2204
  entry.commandMessage && (entry.commandMessage.helpData ? /* @__PURE__ */ jsx7(HelpView, { data: entry.commandMessage.helpData }) : entry.commandMessage.ok ? /* @__PURE__ */ jsxs7(Text7, { color: theme.accent, children: [
2157
2205
  "\u2713 ",
@@ -2163,7 +2211,7 @@ function EntryView({ entry }) {
2163
2211
  ] }) : !entry.commandMessage && /* @__PURE__ */ jsx7(QueryResult, { state: entry.queryState, elapsed: entry.elapsed, page: entry.page, pageSize: PAGE_SIZE })
2164
2212
  ] }) })
2165
2213
  ] });
2166
- }
2214
+ });
2167
2215
  function App({ connectionState, aiUrl: aiUrl2, aiModel: aiModel2, aiKey: aiKey2, onChangeDatabase }) {
2168
2216
  const { exit } = useApp();
2169
2217
  const { isRawModeSupported } = useStdin2();
@@ -2187,6 +2235,7 @@ function App({ connectionState, aiUrl: aiUrl2, aiModel: aiModel2, aiKey: aiKey2,
2187
2235
  const [erdData, setErdData] = useState3(null);
2188
2236
  const [isErdLoading, setIsErdLoading] = useState3(false);
2189
2237
  const [completedEntries, setCompletedEntries] = useState3([]);
2238
+ const [clearSeq, setClearSeq] = useState3(0);
2190
2239
  const entryIdRef = useRef2(0);
2191
2240
  const aliasScope = connectionState.status === "connected" ? makeScope(connectionState.driver, connectionState.user, connectionState.host, connectionState.database) : "";
2192
2241
  const [aliases, setAliases] = useState3(
@@ -2217,6 +2266,10 @@ function App({ connectionState, aiUrl: aiUrl2, aiModel: aiModel2, aiKey: aiKey2,
2217
2266
  process.off("SIGCONT", handleCont);
2218
2267
  };
2219
2268
  }, [isRawModeSupported]);
2269
+ useEffect4(() => {
2270
+ if (clearSeq === 0) return;
2271
+ process.stdout.write("\x1B[3J");
2272
+ }, [clearSeq]);
2220
2273
  useEffect4(() => {
2221
2274
  if (!isRawModeSupported) return;
2222
2275
  process.stdout.write("\x1B[?25l");
@@ -2387,7 +2440,6 @@ function App({ connectionState, aiUrl: aiUrl2, aiModel: aiModel2, aiKey: aiKey2,
2387
2440
  void handleErd();
2388
2441
  },
2389
2442
  onClear: () => {
2390
- process.stdout.write("\x1B[2J\x1B[3J\x1B[H");
2391
2443
  setCompletedEntries([]);
2392
2444
  setLastQuery("");
2393
2445
  setCommandMessage(null);
@@ -2397,6 +2449,7 @@ function App({ connectionState, aiUrl: aiUrl2, aiModel: aiModel2, aiKey: aiKey2,
2397
2449
  setAiError(null);
2398
2450
  setElapsed(null);
2399
2451
  setPage(0);
2452
+ setClearSeq((s) => s + 1);
2400
2453
  },
2401
2454
  aliases,
2402
2455
  onSaveAlias: handleSaveAlias,
@@ -2424,17 +2477,11 @@ function App({ connectionState, aiUrl: aiUrl2, aiModel: aiModel2, aiKey: aiKey2,
2424
2477
  const isCommand = !isShellEntry && (lastQuery.startsWith("/") || lastQuery.startsWith("\\"));
2425
2478
  const activeLabel = isShellEntry ? "Shell:" : isCommand ? "Command:" : "Query:";
2426
2479
  return /* @__PURE__ */ jsxs7(Box7, { flexDirection: "column", children: [
2427
- /* @__PURE__ */ jsx7(Static, { items: completedEntries, children: (entry) => /* @__PURE__ */ jsx7(EntryView, { entry }, entry.id) }),
2480
+ completedEntries.map((entry) => /* @__PURE__ */ jsx7(EntryView, { entry }, entry.id)),
2428
2481
  /* @__PURE__ */ jsxs7(Box7, { flexDirection: "column", paddingX: 1, children: [
2429
2482
  lastQuery === "" && /* @__PURE__ */ jsx7(Banner, { connectionState }),
2430
2483
  lastQuery !== "" && /* @__PURE__ */ jsxs7(Box7, { flexDirection: "column", marginBottom: 2, children: [
2431
- /* @__PURE__ */ jsxs7(Box7, { borderStyle: "round", borderColor: theme.accent, paddingX: 1, children: [
2432
- /* @__PURE__ */ jsxs7(Text7, { color: theme.accent, bold: true, children: [
2433
- activeLabel,
2434
- " "
2435
- ] }),
2436
- /* @__PURE__ */ jsx7(Text7, { dimColor: true, children: lastQuery })
2437
- ] }),
2484
+ /* @__PURE__ */ jsx7(QueryHeader, { label: activeLabel.replace(":", ""), query: lastQuery }),
2438
2485
  /* @__PURE__ */ jsx7(Box7, { marginTop: 1, flexDirection: "column", children: isShellEntry ? isShellRunning ? /* @__PURE__ */ jsx7(Text7, { dimColor: true, children: "running\u2026" }) : /* @__PURE__ */ jsx7(Text7, { children: limitLines(shellOutput ?? "", activePageSize()) }) : /* @__PURE__ */ jsxs7(Fragment4, { children: [
2439
2486
  commandMessage && (commandMessage.helpData ? /* @__PURE__ */ jsx7(HelpView, { data: commandMessage.helpData }) : commandMessage.ok ? /* @__PURE__ */ jsxs7(Text7, { color: theme.accent, children: [
2440
2487
  "\u2713 ",
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@deaquinodev/querky",
3
- "version": "0.4.5",
3
+ "version": "0.4.7",
4
4
  "description": "A quirky terminal SQL client with vim mode, AI features, and schema-aware autocomplete",
5
5
  "main": "dist/index.js",
6
6
  "files": [