From 1af6539deb46167e24c1e899c495f78c04415776 Mon Sep 17 00:00:00 2001 From: Pablu Date: Mon, 19 Jan 2026 22:49:22 +0100 Subject: [PATCH] Update tea and lexer --- cmd/sqv-tea/debug.log | 67 +++++++++++ cmd/sqv-tea/main.go | 233 ++++++++++++++++++++++++++++++------ cmd/sqv-tview/main.go | 4 + go.mod | 3 + go.sum | 6 + sql/lexer.go | 14 ++- sql/parseInsertStatement.go | 4 +- 7 files changed, 293 insertions(+), 38 deletions(-) create mode 100644 cmd/sqv-tea/debug.log diff --git a/cmd/sqv-tea/debug.log b/cmd/sqv-tea/debug.log new file mode 100644 index 0000000..211c854 --- /dev/null +++ b/cmd/sqv-tea/debug.log @@ -0,0 +1,67 @@ +2026/01/19 11:46:36 DEBU Update styles bh=2 bv=2 h=2 v=2 +2026/01/19 11:46:36 DEBU Update styles bh=2 bv=2 h=2 v=2 +2026/01/19 11:46:53 DEBU Enter was pressed +2026/01/19 11:46:53 DEBU Selected item okay and batched +2026/01/19 11:47:01 DEBU Enter was pressed +2026/01/19 11:47:01 DEBU Selected item okay and batched +2026/01/19 11:47:03 DEBU Enter was pressed +2026/01/19 11:47:03 DEBU Selected item okay and batched +2026/01/19 11:47:08 DEBU Enter was pressed +2026/01/19 11:47:08 DEBU Selected item okay and batched +2026/01/19 11:47:21 DEBU Enter was pressed +2026/01/19 11:50:04 DEBU Update styles bh=2 bv=2 h=2 v=2 +2026/01/19 11:50:04 DEBU Update styles bh=2 bv=2 h=2 v=2 +2026/01/19 11:50:04 DEBU Update styles bh=2 bv=2 h=2 v=2 +2026/01/19 11:50:04 DEBU Update styles bh=2 bv=2 h=2 v=2 +2026/01/19 11:50:04 DEBU Update styles bh=2 bv=2 h=2 v=2 +2026/01/19 11:50:04 DEBU Update styles bh=2 bv=2 h=2 v=2 +2026/01/19 11:50:04 DEBU Update styles bh=2 bv=2 h=2 v=2 +2026/01/19 11:50:04 DEBU Update styles bh=2 bv=2 h=2 v=2 +2026/01/19 11:50:04 DEBU Update styles bh=2 bv=2 h=2 v=2 +2026/01/19 11:50:04 DEBU Update styles bh=2 bv=2 h=2 v=2 +2026/01/19 11:50:04 DEBU Update styles bh=2 bv=2 h=2 v=2 +2026/01/19 11:50:04 DEBU Update styles bh=2 bv=2 h=2 v=2 +2026/01/19 11:50:04 DEBU Update styles bh=2 bv=2 h=2 v=2 +2026/01/19 11:50:04 DEBU Update styles bh=2 bv=2 h=2 v=2 +2026/01/19 11:50:04 DEBU Update styles bh=2 bv=2 h=2 v=2 +2026/01/19 11:50:04 DEBU Update styles bh=2 bv=2 h=2 v=2 +2026/01/19 11:50:04 DEBU Update styles bh=2 bv=2 h=2 v=2 +2026/01/19 11:50:04 DEBU Update styles bh=2 bv=2 h=2 v=2 +2026/01/19 11:50:04 DEBU Update styles bh=2 bv=2 h=2 v=2 +2026/01/19 11:50:04 DEBU Update styles bh=2 bv=2 h=2 v=2 +2026/01/19 11:50:04 DEBU Update styles bh=2 bv=2 h=2 v=2 +2026/01/19 11:50:04 DEBU Update styles bh=2 bv=2 h=2 v=2 +2026/01/19 11:50:05 DEBU Update styles bh=2 bv=2 h=2 v=2 +2026/01/19 11:50:05 DEBU Update styles bh=2 bv=2 h=2 v=2 +2026/01/19 11:50:05 DEBU Update styles bh=2 bv=2 h=2 v=2 +2026/01/19 11:50:05 DEBU Update styles bh=2 bv=2 h=2 v=2 +2026/01/19 11:50:05 DEBU Update styles bh=2 bv=2 h=2 v=2 +2026/01/19 11:50:05 DEBU Update styles bh=2 bv=2 h=2 v=2 +2026/01/19 11:50:05 DEBU Update styles bh=2 bv=2 h=2 v=2 +2026/01/19 11:50:05 DEBU Update styles bh=2 bv=2 h=2 v=2 +2026/01/19 11:50:05 DEBU Update styles bh=2 bv=2 h=2 v=2 +2026/01/19 11:50:05 DEBU Update styles bh=2 bv=2 h=2 v=2 +2026/01/19 11:50:05 DEBU Update styles bh=2 bv=2 h=2 v=2 +2026/01/19 11:50:05 DEBU Update styles bh=2 bv=2 h=2 v=2 +2026/01/19 11:50:05 DEBU Update styles bh=2 bv=2 h=2 v=2 +2026/01/19 11:50:05 DEBU Update styles bh=2 bv=2 h=2 v=2 +2026/01/19 11:50:05 DEBU Update styles bh=2 bv=2 h=2 v=2 +2026/01/19 11:50:05 DEBU Update styles bh=2 bv=2 h=2 v=2 +2026/01/19 11:50:09 DEBU Enter was pressed +2026/01/19 11:50:09 DEBU Selected item okay and batched +2026/01/19 11:50:10 DEBU Enter was pressed +2026/01/19 11:50:10 DEBU Selected item okay and batched +2026/01/19 11:50:11 DEBU Enter was pressed +2026/01/19 11:50:11 DEBU Selected item okay and batched +2026/01/19 11:50:12 DEBU Enter was pressed +2026/01/19 11:50:12 DEBU Selected item okay and batched +2026/01/19 11:50:13 DEBU Enter was pressed +2026/01/19 11:50:13 DEBU Selected item okay and batched +2026/01/19 11:50:15 DEBU Enter was pressed +2026/01/19 11:50:15 DEBU Selected item okay and batched +2026/01/19 11:50:17 DEBU Enter was pressed +2026/01/19 11:50:17 DEBU Selected item okay and batched +2026/01/19 11:50:21 DEBU Enter was pressed +2026/01/19 11:50:21 DEBU Selected item okay and batched +2026/01/19 11:50:22 DEBU Enter was pressed +2026/01/19 11:50:22 DEBU Selected item okay and batched diff --git a/cmd/sqv-tea/main.go b/cmd/sqv-tea/main.go index 965ede6..61d7908 100644 --- a/cmd/sqv-tea/main.go +++ b/cmd/sqv-tea/main.go @@ -1,18 +1,26 @@ package main import ( - "log" + "fmt" + "os" + + engine "git.pablu.de/pablu/sqv-engine" "github.com/charmbracelet/bubbles/list" "github.com/charmbracelet/bubbles/table" "github.com/charmbracelet/bubbles/textarea" tea "github.com/charmbracelet/bubbletea" "github.com/charmbracelet/lipgloss" + "github.com/charmbracelet/log" ) type mainModel struct { width, height int - focused int + pickerFocused bool + + manager *engine.Manager + + lastFocused int viewStyle lipgloss.Style editorStyle lipgloss.Style @@ -21,23 +29,30 @@ type mainModel struct { table table.Model editor textarea.Model picker list.Model + + debugMsgs []string } var ( defaultStyle = lipgloss.NewStyle(). - Align(lipgloss.Center). - BorderStyle(lipgloss.NormalBorder()) + Align(lipgloss.Center). + BorderStyle(lipgloss.NormalBorder()) + + focusedStyle = defaultStyle.BorderForeground(lipgloss.Color("202")) + + baseTableStyle = lipgloss.NewStyle(). + BorderStyle(lipgloss.NormalBorder()) ) type item struct { - title, desc string + title string } func (i item) Title() string { return i.title } -func (i item) Description() string { return i.desc } +func (i item) Description() string { return i.title } func (i item) FilterValue() string { return i.title } -func newMainModerl() mainModel { +func newMainModerl(manager *engine.Manager) mainModel { ed := textarea.New() ed.Placeholder = "Try \"SELECT * FROM ?;\"" @@ -66,15 +81,15 @@ func newMainModerl() mainModel { items := []list.Item{ item{ title: "user", - desc: "users table", + // desc: "users table", }, item{ title: "job", - desc: "jobs table", + // desc: "jobs table", }, item{ title: "user_has_job", - desc: "user has a job", + // desc: "user has a job", }, } @@ -82,72 +97,199 @@ func newMainModerl() mainModel { li.Title = "Table Picker" return mainModel{ - focused: 0, - viewStyle: defaultStyle, - editorStyle: defaultStyle, - pickerStyle: defaultStyle.BorderStyle(lipgloss.RoundedBorder()).BorderForeground(lipgloss.Color("67")), - editor: ed, - table: ta, - picker: li, + pickerFocused: false, + viewStyle: baseTableStyle, + editorStyle: defaultStyle, + pickerStyle: defaultStyle.BorderStyle(lipgloss.RoundedBorder()).BorderForeground(lipgloss.Color("67")), + editor: ed, + table: ta, + picker: li, + manager: manager, } } func (m mainModel) Init() tea.Cmd { - return nil + return m.GetTableDefinitions +} + +func (m mainModel) GetTableDefinitions() tea.Msg { + + tables := m.manager.GetTables() + tableNames := make([]list.Item, len(tables)) + + for i, table := range tables { + tableNames[i] = item{table.Name} + } + + return tableDefinitionsMsg{ + tableNames, + } +} + +type tableDefinitionsMsg struct { + Tables []list.Item +} + +func (m mainModel) GetFirstTable() tea.Msg { + t := m.manager.GetTables()[0] + cmd := m.GetTable(t.Name) + return cmd() +} + +func (m mainModel) GetTable(name string) tea.Cmd { + return func() tea.Msg { + t, ok := m.manager.GetTable(name) + if !ok { + return nil + } + + m.manager.LoadTable(&t) + + msg := tableMsg{ + Columns: make([]table.Column, len(t.Columns)), + Rows: make([]table.Row, len(t.Rows)), + } + + lRow := make([]int, len(t.Columns)) + lRowC := 0 + for i, r := range t.Rows { + + msg.Rows[i] = r.Values + + // Pure stupid estemation of how long each row should be + if i == 0 { + for j, v := range r.Values { + vLen := len(v) + vLen = max(vLen, len(t.Columns[j].Name)) + + lRow[j] = vLen + lRowC += vLen + } + } + } + + h, _ := m.viewStyle.GetFrameSize() + width := m.viewStyle.GetWidth() - h + width -= (len(t.Columns) + 1) * 2 + for i, c := range t.Columns { + columnWidth := float64(width) * (float64(lRow[i]) / float64(lRowC)) + + msg.Columns[i] = table.Column{ + Title: c.Name, + Width: int(columnWidth), + } + } + + return msg + } +} + +type tableMsg struct { + Columns []table.Column + Rows []table.Row } func (m mainModel) Update(msg tea.Msg) (tea.Model, tea.Cmd) { var ( - edCmd tea.Cmd - taCmd tea.Cmd - liCmd tea.Cmd + edCmd tea.Cmd + taCmd tea.Cmd + liCmd tea.Cmd + pickerCmd tea.Cmd + cmds tea.Cmd ) switch msg := msg.(type) { case tea.KeyMsg: switch msg.String() { - case "ctrl+c", "q": + case "ctrl+c": return m, tea.Quit case "tab": if m.table.Focused() { m.table.Blur() m.editor.Focus() + m.lastFocused = 0 } else { m.editor.Blur() m.table.Focus() + m.lastFocused = 1 } case "ctrl+e": - if m.focused == 1 { - m.focused = 0 + m.pickerFocused = !m.pickerFocused + + // Picker is now focused + if m.pickerFocused { + m.table.Blur() + m.editor.Blur() } else { - m.focused = 1 + if m.lastFocused == 0 { + m.editor.Focus() + } else { + m.table.Focus() + } + } + case "enter": + log.Debug("Enter was pressed") + if m.pickerFocused { + i, ok := m.picker.SelectedItem().(item) + if ok { + log.Debug("Selected item okay and batched") + cmds = tea.Batch(cmds, m.GetTable(i.title)) + } + + m.pickerFocused = !m.pickerFocused + if m.lastFocused == 0 { + m.editor.Focus() + } else { + m.table.Focus() + } } } case tea.WindowSizeMsg: m = m.updateStyles(msg.Width, msg.Height) + cmds = tea.Batch(cmds, m.GetFirstTable) + + case tableMsg: + // log.Debug("Setting new table view", "columns", msg.Columns, "rows", msg.Rows) + + m.table = table.New( + table.WithColumns(msg.Columns), + table.WithRows(msg.Rows), + ) + + m.table.SetWidth(m.viewStyle.GetWidth() - 2) + m.table.SetHeight(m.viewStyle.GetHeight() - 2) + + // m.table.SetColumns(msg.Columns) + // m.table.SetRows(msg.Rows) + + case tableDefinitionsMsg: + pickerCmd = m.picker.SetItems(msg.Tables) } m.editor, edCmd = m.editor.Update(msg) m.table, taCmd = m.table.Update(msg) - if m.focused == 1 { + if m.pickerFocused { m.picker, liCmd = m.picker.Update(msg) } - return m, tea.Batch(edCmd, taCmd, liCmd) + return m, tea.Batch(edCmd, taCmd, liCmd, cmds, pickerCmd) } func (m mainModel) updateStyles(width, height int) mainModel { h, v := defaultStyle.GetFrameSize() + bh, bv := baseTableStyle.GetFrameSize() - topHeight := (height * 3 / 4) - h + log.Debug("Update styles", "bh", bh, "bv", bv, "h", h, "v", v) + + topHeight := (height * 3 / 4) - bh editorHeight := (height * 1 / 4) - h m.editorStyle = defaultStyle. Width(width - v). Height(editorHeight) - m.viewStyle = defaultStyle. - Width(width - v). + m.viewStyle = baseTableStyle. + Width(width - bv). Height(topHeight) m.editor.SetWidth(m.editorStyle.GetWidth()) @@ -175,7 +317,6 @@ func (m mainModel) updateStyles(width, height int) mainModel { } func (m mainModel) View() string { - view := m.viewStyle. Render(m.table.View()) @@ -183,9 +324,8 @@ func (m mainModel) View() string { Render(m.editor.View()) main := lipgloss.JoinVertical(lipgloss.Top, view, editor) - _ = main - if m.focused == 1 { + if m.pickerFocused { x := (m.width / 2) - m.picker.Width()/2 y := (m.height / 2) - m.picker.Height()/2 @@ -196,8 +336,31 @@ func (m mainModel) View() string { } func main() { - p := tea.NewProgram(newMainModerl(), tea.WithAltScreen()) + f, err := tea.LogToFile("debug.log", "debug") + if err != nil { + fmt.Println("fatal:", err) + os.Exit(1) + } + defer f.Close() + + log.SetLevel(log.DebugLevel) + log.SetOutput(f) + + m, err := engine.NewManager("../vdcmp/db.sqlite") + if err != nil { + fmt.Println("fatal:", err) + os.Exit(1) + } + + err = m.Start() + if err != nil { + fmt.Println("fatal:", err) + os.Exit(1) + } + + p := tea.NewProgram(newMainModerl(m), tea.WithAltScreen()) if _, err := p.Run(); err != nil { - log.Fatalf("could not start program: %v\n", err) + fmt.Println("fatal:", err) + os.Exit(1) } } diff --git a/cmd/sqv-tview/main.go b/cmd/sqv-tview/main.go index 8a801a1..4f8e7b1 100644 --- a/cmd/sqv-tview/main.go +++ b/cmd/sqv-tview/main.go @@ -168,6 +168,10 @@ func main() { return event }) + menuView.SetFocusFunc(func() { + menuView.SetBorderColor(tcell.ColorRed) + }) + if err := app.SetRoot(horizontalFlex, true).EnableMouse(true).Run(); err != nil { panic(err) } diff --git a/go.mod b/go.mod index 7dd6d6a..ae80130 100644 --- a/go.mod +++ b/go.mod @@ -17,6 +17,7 @@ require ( github.com/charmbracelet/x/term v0.2.1 // indirect github.com/erikgeiser/coninput v0.0.0-20211004153227-1c3628e74d0f // indirect github.com/gdamore/encoding v1.0.1 // indirect + github.com/go-logfmt/logfmt v0.6.0 // indirect github.com/lucasb-eyer/go-colorful v1.2.0 // indirect github.com/mattn/go-isatty v0.0.20 // indirect github.com/mattn/go-localereader v0.0.1 // indirect @@ -27,6 +28,7 @@ require ( github.com/rivo/uniseg v0.4.7 // indirect github.com/sahilm/fuzzy v0.1.1 // indirect github.com/xo/terminfo v0.0.0-20220910002029-abceb7e1c41e // indirect + golang.org/x/exp v0.0.0-20231006140011-7918f672742d // indirect golang.org/x/term v0.28.0 // indirect golang.org/x/text v0.25.0 // indirect ) @@ -35,6 +37,7 @@ require ( github.com/charmbracelet/bubbles v0.21.0 github.com/charmbracelet/bubbletea v1.3.10 github.com/charmbracelet/lipgloss v1.1.0 + github.com/charmbracelet/log v0.4.2 github.com/muesli/reflow v0.3.0 golang.org/x/sys v0.36.0 // indirect ) diff --git a/go.sum b/go.sum index 4797924..0c8f3d2 100644 --- a/go.sum +++ b/go.sum @@ -10,6 +10,8 @@ github.com/charmbracelet/colorprofile v0.2.3-0.20250311203215-f60798e515dc h1:4p github.com/charmbracelet/colorprofile v0.2.3-0.20250311203215-f60798e515dc/go.mod h1:X4/0JoqgTIPSFcRA/P6INZzIuyqdFY5rm8tb41s9okk= github.com/charmbracelet/lipgloss v1.1.0 h1:vYXsiLHVkK7fp74RkV7b2kq9+zDLoEU4MZoFqR/noCY= github.com/charmbracelet/lipgloss v1.1.0/go.mod h1:/6Q8FR2o+kj8rz4Dq0zQc3vYf7X+B0binUUBwA0aL30= +github.com/charmbracelet/log v0.4.2 h1:hYt8Qj6a8yLnvR+h7MwsJv/XvmBJXiueUcI3cIxsyig= +github.com/charmbracelet/log v0.4.2/go.mod h1:qifHGX/tc7eluv2R6pWIpyHDDrrb/AG71Pf2ysQu5nw= github.com/charmbracelet/x/ansi v0.8.0 h1:9GTq3xq9caJW8ZrBTe0LIe2fvfLR/bYXKTx2llXn7xE= github.com/charmbracelet/x/ansi v0.8.0/go.mod h1:wdYl/ONOLHLIVmQaxbIYEC/cRKOQyjTkowiI4blgS9Q= github.com/charmbracelet/x/ansi v0.10.1 h1:rL3Koar5XvX0pHGfovN03f5cxLbCF2YvLeyz7D2jVDQ= @@ -24,6 +26,8 @@ github.com/gdamore/encoding v1.0.1 h1:YzKZckdBL6jVt2Gc+5p82qhrGiqMdG/eNs6Wy0u3Uh github.com/gdamore/encoding v1.0.1/go.mod h1:0Z0cMFinngz9kS1QfMjCP8TY7em3bZYeeklsSDPivEo= github.com/gdamore/tcell/v2 v2.8.1 h1:KPNxyqclpWpWQlPLx6Xui1pMk8S+7+R37h3g07997NU= github.com/gdamore/tcell/v2 v2.8.1/go.mod h1:bj8ori1BG3OYMjmb3IklZVWfZUJ1UBQt9JXrOCOhGWw= +github.com/go-logfmt/logfmt v0.6.0 h1:wGYYu3uicYdqXVgoYbvnkrPVXkuLM1p1ifugDMEdRi4= +github.com/go-logfmt/logfmt v0.6.0/go.mod h1:WYhtIu8zTZfxdn5+rREduYbwxfcBr/Vr6KEVveWlfTs= github.com/google/go-cmp v0.6.0/go.mod h1:17dUlkBOakJ0+DkrSSNjCkIjxS6bF9zb3elmeNGIjoY= github.com/lucasb-eyer/go-colorful v1.2.0 h1:1nnpGOrhyZZuNyfu1QjKiUICQ74+3FNCN69Aj6K7nkY= github.com/lucasb-eyer/go-colorful v1.2.0/go.mod h1:R4dSotOR9KMtayYi1e77YzuveK+i7ruzyGqttikkLy0= @@ -61,6 +65,8 @@ golang.org/x/crypto v0.0.0-20210921155107-089bfa567519/go.mod h1:GvvjBRRGRdwPK5y golang.org/x/crypto v0.13.0/go.mod h1:y6Z2r+Rw4iayiXXAIxJIDAJ1zMW4yaTpebo8fPOliYc= golang.org/x/crypto v0.19.0/go.mod h1:Iy9bg/ha4yyC70EfRS8jz+B6ybOBKMaSxLj6P6oBDfU= golang.org/x/crypto v0.23.0/go.mod h1:CKFgDieR+mRhux2Lsu27y0fO304Db0wZe70UKqHu0v8= +golang.org/x/exp v0.0.0-20231006140011-7918f672742d h1:jtJma62tbqLibJ5sFQz8bKtEM8rJBtfilJ2qTU199MI= +golang.org/x/exp v0.0.0-20231006140011-7918f672742d/go.mod h1:ldy0pHrwJyGW56pPQzzkH36rKxoZW1tw7ZJpeKx+hdo= golang.org/x/mod v0.6.0-dev.0.20220419223038-86c51ed26bb4/go.mod h1:jJ57K6gSWd91VN4djpZkiMVwK6gcyfeH4XE8wZrZaV4= golang.org/x/mod v0.8.0/go.mod h1:iBbtSCu2XBx23ZKBPSOrRkjjQPZFPuis4dIYUhu/chs= golang.org/x/mod v0.12.0/go.mod h1:iBbtSCu2XBx23ZKBPSOrRkjjQPZFPuis4dIYUhu/chs= diff --git a/sql/lexer.go b/sql/lexer.go index 6636ac0..a9e92b0 100644 --- a/sql/lexer.go +++ b/sql/lexer.go @@ -25,6 +25,10 @@ const ( QUOTE SINGLE_QUOTE + // TYPES + TYPE_NUMERIC + TYPE_TEXT + // Keywords CREATE TABLE @@ -157,6 +161,12 @@ func (l *Lexer) Lex() (Position, Token, string) { } return startPos, IDENT, lit + } else if unicode.IsNumber(r) { + startPos := l.pos + l.backup() + lit := l.lexIdent() + + return startPos, TYPE_NUMERIC, lit } else { return l.pos, ILLEGAL, string(r) } @@ -176,7 +186,9 @@ func (l *Lexer) lexIdent() string { } l.pos.column++ - if unicode.IsLetter(r) || unicode.IsNumber(r) || r == '_' { + + // Dont allow dot, just for testing with numeric + if unicode.IsLetter(r) || unicode.IsNumber(r) || r == '_' || r == '.' { // Change this to stringstream or something similar lit = lit + string(r) } else { diff --git a/sql/parseInsertStatement.go b/sql/parseInsertStatement.go index 276525d..0582c15 100644 --- a/sql/parseInsertStatement.go +++ b/sql/parseInsertStatement.go @@ -39,7 +39,7 @@ func (p *Parser) parseInsert() (*InsertStatement, error) { for loop := true; loop; { _, tok, val := p.scan() switch tok { - case IDENT: + case IDENT, TYPE_NUMERIC: // TODO, convert to actual datatype? values = append(values, val) case COMMA, QUOTE, SINGLE_QUOTE, BACKQUOTE: @@ -47,7 +47,7 @@ func (p *Parser) parseInsert() (*InsertStatement, error) { case RPAREN: loop = false default: - return nil, p.unexpectedToken(IDENT, RPAREN, COMMA) + return nil, p.unexpectedToken(IDENT, RPAREN, COMMA, TYPE_NUMERIC) } }