package main import ( "log" "github.com/charmbracelet/bubbles/list" "github.com/charmbracelet/bubbles/table" "github.com/charmbracelet/bubbles/textarea" tea "github.com/charmbracelet/bubbletea" "github.com/charmbracelet/lipgloss" ) type mainModel struct { width, height int focused int viewStyle lipgloss.Style editorStyle lipgloss.Style pickerStyle lipgloss.Style table table.Model editor textarea.Model picker list.Model } var ( defaultStyle = lipgloss.NewStyle(). Align(lipgloss.Center). BorderStyle(lipgloss.NormalBorder()) ) type item struct { title, desc string } func (i item) Title() string { return i.title } func (i item) Description() string { return i.desc } func (i item) FilterValue() string { return i.title } func newMainModerl() mainModel { ed := textarea.New() ed.Placeholder = "Try \"SELECT * FROM ?;\"" ed.ShowLineNumbers = false ed.Focus() columns := []table.Column{ {Title: "id", Width: 4}, {Title: "name", Width: 4}, {Title: "family_name", Width: 4}, } rows := []table.Row{ {"0", "Conrad", "Adenauer"}, {"1", "Anna", "Aachen"}, {"2", "Karli", "Columbus"}, {"3", "Max", "Mustermann"}, {"4", "Bernd", "Brot"}, } ta := table.New( table.WithColumns(columns), table.WithRows(rows), ) items := []list.Item{ item{ title: "user", desc: "users table", }, item{ title: "job", desc: "jobs table", }, item{ title: "user_has_job", desc: "user has a job", }, } li := list.New(items, list.NewDefaultDelegate(), 0, 0) 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, } } func (m mainModel) Init() tea.Cmd { return nil } func (m mainModel) Update(msg tea.Msg) (tea.Model, tea.Cmd) { var ( edCmd tea.Cmd taCmd tea.Cmd liCmd tea.Cmd ) switch msg := msg.(type) { case tea.KeyMsg: switch msg.String() { case "ctrl+c", "q": return m, tea.Quit case "tab": if m.table.Focused() { m.table.Blur() m.editor.Focus() } else { m.editor.Blur() m.table.Focus() } case "ctrl+e": if m.focused == 1 { m.focused = 0 } else { m.focused = 1 } } case tea.WindowSizeMsg: m = m.updateStyles(msg.Width, msg.Height) } m.editor, edCmd = m.editor.Update(msg) m.table, taCmd = m.table.Update(msg) if m.focused == 1 { m.picker, liCmd = m.picker.Update(msg) } return m, tea.Batch(edCmd, taCmd, liCmd) } func (m mainModel) updateStyles(width, height int) mainModel { h, v := defaultStyle.GetFrameSize() topHeight := (height * 3 / 4) - h editorHeight := (height * 1 / 4) - h m.editorStyle = defaultStyle. Width(width - v). Height(editorHeight) m.viewStyle = defaultStyle. Width(width - v). Height(topHeight) m.editor.SetWidth(m.editorStyle.GetWidth()) m.editor.SetHeight(m.editorStyle.GetHeight()) m.table.SetWidth(m.viewStyle.GetWidth()) m.table.SetHeight(m.viewStyle.GetHeight()) columns := m.table.Columns() colLen := len(columns) colWidth := m.table.Width() / colLen for i := range columns { columns[i].Width = colWidth } m.table.SetColumns(columns) m.pickerStyle = m.pickerStyle. Width((width * 7 / 10) - v). Height((height * 7 / 10) - h) m.picker.SetSize(m.pickerStyle.GetWidth(), m.pickerStyle.GetHeight()) m.width = width m.height = height return m } func (m mainModel) View() string { view := m.viewStyle. Render(m.table.View()) editor := m.editorStyle. Render(m.editor.View()) main := lipgloss.JoinVertical(lipgloss.Top, view, editor) _ = main if m.focused == 1 { x := (m.width / 2) - m.picker.Width()/2 y := (m.height / 2) - m.picker.Height()/2 return PlaceOverlay(x, y, m.pickerStyle.Render(m.picker.View()), main, false) } return main } func main() { p := tea.NewProgram(newMainModerl(), tea.WithAltScreen()) if _, err := p.Run(); err != nil { log.Fatalf("could not start program: %v\n", err) } }