299 lines
5.6 KiB
Go
299 lines
5.6 KiB
Go
package main
|
|
|
|
import (
|
|
"fmt"
|
|
"math"
|
|
"os"
|
|
|
|
engine "git.pablu.de/pablu/sqv-engine"
|
|
|
|
"github.com/charmbracelet/bubbles/list"
|
|
"github.com/charmbracelet/bubbles/textarea"
|
|
tea "github.com/charmbracelet/bubbletea"
|
|
"github.com/charmbracelet/lipgloss"
|
|
"github.com/charmbracelet/lipgloss/table"
|
|
"github.com/charmbracelet/log"
|
|
)
|
|
|
|
type Focused int
|
|
|
|
const (
|
|
EDITOR Focused = iota
|
|
TABLE
|
|
PICKER
|
|
)
|
|
|
|
type mainModel struct {
|
|
width, height int
|
|
|
|
manager *engine.Manager
|
|
|
|
lastFocused, focused Focused
|
|
|
|
table *table.Table
|
|
editor textarea.Model
|
|
picker list.Model
|
|
|
|
debugMsgs []string
|
|
}
|
|
|
|
var (
|
|
defaultStyle = lipgloss.NewStyle().
|
|
Align(lipgloss.Center).
|
|
BorderStyle(lipgloss.NormalBorder())
|
|
|
|
focusedStyle = defaultStyle.BorderForeground(lipgloss.Color("202"))
|
|
)
|
|
|
|
type item struct {
|
|
title string
|
|
}
|
|
|
|
func (i item) Title() string { return i.title }
|
|
func (i item) Description() string { return i.title }
|
|
func (i item) FilterValue() string { return i.title }
|
|
|
|
func newMainModerl(manager *engine.Manager) mainModel {
|
|
ed := textarea.New()
|
|
ed.Placeholder = "Try \"SELECT * FROM ?;\""
|
|
|
|
ed.ShowLineNumbers = false
|
|
|
|
ta := table.New().Border(lipgloss.NormalBorder())
|
|
|
|
li := list.New(nil, list.NewDefaultDelegate(), 0, 0)
|
|
li.Title = "Table Picker"
|
|
|
|
return mainModel{
|
|
editor: ed,
|
|
table: ta,
|
|
picker: li,
|
|
manager: manager,
|
|
}
|
|
}
|
|
|
|
func (m mainModel) Init() tea.Cmd {
|
|
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([]string, len(t.Columns)),
|
|
Rows: make([][]string, len(t.Rows)),
|
|
}
|
|
|
|
for i, r := range t.Rows {
|
|
msg.Rows[i] = r.Values
|
|
}
|
|
|
|
for i, c := range t.Columns {
|
|
msg.Columns[i] = c.Name
|
|
}
|
|
|
|
return msg
|
|
}
|
|
}
|
|
|
|
type tableMsg struct {
|
|
Columns []string
|
|
Rows [][]string
|
|
}
|
|
|
|
func (m mainModel) Update(msg tea.Msg) (tea.Model, tea.Cmd) {
|
|
var (
|
|
edCmd tea.Cmd
|
|
pickerCmd tea.Cmd
|
|
cmds tea.Cmd
|
|
)
|
|
|
|
switch msg := msg.(type) {
|
|
case tea.KeyMsg:
|
|
switch msg.String() {
|
|
case "ctrl+c":
|
|
return m, tea.Quit
|
|
case "tab":
|
|
if m.focused == EDITOR {
|
|
m.focused = TABLE
|
|
m.editor.Blur()
|
|
} else {
|
|
m.focused = EDITOR
|
|
}
|
|
case "j":
|
|
m.table.Offset(50)
|
|
case "ctrl+e":
|
|
if m.focused == PICKER {
|
|
m.focused = m.lastFocused
|
|
} else {
|
|
// Maybe blur is not even needed
|
|
m.editor.Blur()
|
|
m.lastFocused = m.focused
|
|
m.focused = PICKER
|
|
}
|
|
case "enter":
|
|
log.Debug("Enter was pressed")
|
|
if m.focused == PICKER {
|
|
i, ok := m.picker.SelectedItem().(item)
|
|
if ok {
|
|
log.Debug("Selected item okay and batched")
|
|
cmds = tea.Batch(cmds, m.GetTable(i.title))
|
|
}
|
|
|
|
m.focused = m.lastFocused
|
|
if m.focused == EDITOR {
|
|
m.editor.Focus()
|
|
}
|
|
}
|
|
}
|
|
case tea.WindowSizeMsg:
|
|
m = m.updateStyles(msg.Width, msg.Height)
|
|
cmds = tea.Batch(cmds, m.GetFirstTable)
|
|
|
|
case tableMsg:
|
|
m.table.Offset(0)
|
|
m.table.ClearRows()
|
|
m.table.Headers(msg.Columns...)
|
|
m.table.Rows(msg.Rows...)
|
|
|
|
case tableDefinitionsMsg:
|
|
pickerCmd = m.picker.SetItems(msg.Tables)
|
|
}
|
|
|
|
switch m.focused {
|
|
case EDITOR:
|
|
m.editor.Focus()
|
|
m.editor, edCmd = m.editor.Update(msg)
|
|
case PICKER:
|
|
m.picker, pickerCmd = m.picker.Update(msg)
|
|
}
|
|
|
|
return m, tea.Batch(edCmd, cmds, pickerCmd)
|
|
}
|
|
|
|
func (m mainModel) updateStyles(width, height int) mainModel {
|
|
m.width = width
|
|
m.height = height
|
|
|
|
h, v := defaultStyle.GetFrameSize()
|
|
|
|
topHeight := math.Ceil(float64(m.height)*3/4 - float64(h))
|
|
editorHeight := math.Ceil(float64(m.height)*1/4 - float64(h))
|
|
|
|
m.editor.SetWidth(m.width - v)
|
|
m.editor.SetHeight(int(editorHeight))
|
|
|
|
m.table.Width(m.width - v)
|
|
m.table.Height(int(topHeight) - h*2)
|
|
|
|
m.picker.SetSize(m.width*7/10, m.height*7/10)
|
|
|
|
return m
|
|
}
|
|
|
|
func (m mainModel) View() string {
|
|
var (
|
|
view, editor string
|
|
)
|
|
|
|
h, _ := defaultStyle.GetFrameSize()
|
|
topHeight := (m.height * 3 / 4) - h
|
|
editorHeight := (m.height * 1 / 4) - h
|
|
switch m.focused {
|
|
case EDITOR:
|
|
view = defaultStyle.
|
|
Height(topHeight).
|
|
Render(m.table.Render())
|
|
editor = focusedStyle.
|
|
Height(editorHeight).
|
|
Render(m.editor.View())
|
|
case TABLE:
|
|
view = focusedStyle.
|
|
Height(topHeight).
|
|
Render(m.table.Render())
|
|
editor = defaultStyle.
|
|
Height(editorHeight).
|
|
Render(m.editor.View())
|
|
case PICKER:
|
|
view = defaultStyle.
|
|
Height(topHeight).
|
|
Render(m.table.Render())
|
|
editor = defaultStyle.
|
|
Height(editorHeight).
|
|
Render(m.editor.View())
|
|
}
|
|
|
|
main := lipgloss.JoinVertical(lipgloss.Top, view, editor)
|
|
|
|
if m.focused == PICKER {
|
|
x := (m.width / 2) - m.picker.Width()/2
|
|
y := (m.height / 2) - m.picker.Height()/2
|
|
|
|
return PlaceOverlay(x, y, focusedStyle.Render(m.picker.View()), main, false)
|
|
}
|
|
|
|
return main
|
|
}
|
|
|
|
func main() {
|
|
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)
|
|
}
|
|
|
|
table.New()
|
|
|
|
p := tea.NewProgram(newMainModerl(m), tea.WithAltScreen())
|
|
if _, err := p.Run(); err != nil {
|
|
fmt.Println("fatal:", err)
|
|
os.Exit(1)
|
|
}
|
|
}
|