diff --git a/app/App.go b/app/App.go index 213f09a..a0d2c4c 100644 --- a/app/App.go +++ b/app/App.go @@ -7,6 +7,15 @@ import ( var App = tview.NewApplication() +type Theme struct { + SidebarTitleBorderColor string + tview.Theme +} + +var Styles = Theme{ + SidebarTitleBorderColor: "#666A7E", +} + func init() { theme := tview.Theme{ PrimitiveBackgroundColor: tcell.ColorDefault, @@ -14,12 +23,14 @@ func init() { MoreContrastBackgroundColor: tcell.ColorGreen, BorderColor: tcell.ColorWhite, TitleColor: tcell.ColorWhite, - GraphicsColor: tcell.ColorWhite, + GraphicsColor: tcell.ColorGray, PrimaryTextColor: tcell.ColorDefault.TrueColor(), SecondaryTextColor: tcell.ColorYellow, TertiaryTextColor: tcell.ColorGreen, InverseTextColor: tcell.ColorWhite, ContrastSecondaryTextColor: tcell.ColorBlack, } + + Styles.Theme = theme tview.Styles = theme } diff --git a/app/Keymap.go b/app/Keymap.go index f80a57d..9d69bf8 100644 --- a/app/Keymap.go +++ b/app/Keymap.go @@ -44,6 +44,7 @@ const ( TableGroup = "table" EditorGroup = "editor" ConnectionGroup = "connection" + SidebarGroup = "sidebar" ) // Define a global KeymapSystem object with default keybinds @@ -110,11 +111,25 @@ var Keymaps = KeymapSystem{ Bind{Key: Key{Char: '3'}, Cmd: cmd.ConstraintsMenu, Description: "Switch to constraints menu"}, Bind{Key: Key{Char: '4'}, Cmd: cmd.ForeignKeysMenu, Description: "Switch to foreign keys menu"}, Bind{Key: Key{Char: '5'}, Cmd: cmd.IndexesMenu, Description: "Switch to indexes menu"}, + // Sidebar + Bind{Key: Key{Char: 'S'}, Cmd: cmd.ToggleSidebar, Description: "Toggle sidebar"}, + Bind{Key: Key{Char: 's'}, Cmd: cmd.FocusSidebar, Description: "Focus sidebar"}, }, EditorGroup: { Bind{Key: Key{Code: tcell.KeyCtrlR}, Cmd: cmd.Execute, Description: "Execute query"}, Bind{Key: Key{Code: tcell.KeyEscape}, Cmd: cmd.UnfocusEditor, Description: "Unfocus editor"}, Bind{Key: Key{Code: tcell.KeyCtrlSpace}, Cmd: cmd.OpenInExternalEditor, Description: "Open in external editor"}, }, + SidebarGroup: { + Bind{Key: Key{Char: 's'}, Cmd: cmd.UnfocusSidebar, Description: "Focus table"}, + Bind{Key: Key{Char: 'S'}, Cmd: cmd.ToggleSidebar, Description: "Toggle sidebar"}, + Bind{Key: Key{Char: 'j'}, Cmd: cmd.MoveDown, Description: "Focus next field"}, + Bind{Key: Key{Char: 'k'}, Cmd: cmd.MoveUp, Description: "Focus previous field"}, + Bind{Key: Key{Char: 'g'}, Cmd: cmd.GotoStart, Description: "Focus first field"}, + Bind{Key: Key{Char: 'G'}, Cmd: cmd.GotoEnd, Description: "Focus last field"}, + Bind{Key: Key{Char: 'c'}, Cmd: cmd.Edit, Description: "Edit field"}, + Bind{Key: Key{Code: tcell.KeyEnter}, Cmd: cmd.CommitEdit, Description: "Add edit to pending changes"}, + Bind{Key: Key{Code: tcell.KeyEscape}, Cmd: cmd.DiscardEdit, Description: "Discard edit"}, + }, }, } diff --git a/commands/commands.go b/commands/commands.go index 7929380..6773810 100644 --- a/commands/commands.go +++ b/commands/commands.go @@ -45,6 +45,8 @@ const ( UnfocusEditor Copy Edit + CommitEdit + DiscardEdit Save Delete Search @@ -60,6 +62,9 @@ const ( PreviousFoundNode TreeCollapseAll ExpandAll + FocusSidebar + UnfocusSidebar + ToggleSidebar // Connection NewConnection @@ -179,6 +184,16 @@ func (c Command) String() string { return "TreeCollapseAll" case ExpandAll: return "ExpandAll" + case FocusSidebar: + return "FocusSidebar" + case ToggleSidebar: + return "ToggleSidebar" + case UnfocusSidebar: + return "UnfocusSidebar" + case CommitEdit: + return "CommitEdit" + case DiscardEdit: + return "DiscardEdit" } return "Unknown" } diff --git a/components/ConfirmationModal.go b/components/ConfirmationModal.go index c9723b9..d0f6e79 100644 --- a/components/ConfirmationModal.go +++ b/components/ConfirmationModal.go @@ -2,6 +2,8 @@ package components import ( "github.com/rivo/tview" + + "github.com/jorgerojas26/lazysql/app" ) type ConfirmationModal struct { @@ -16,8 +18,8 @@ func NewConfirmationModal(confirmationText string) *ConfirmationModal { modal.SetText("Are you sure?") } modal.AddButtons([]string{"Yes", "No"}) - modal.SetBackgroundColor(tview.Styles.PrimitiveBackgroundColor) - modal.SetTextColor(tview.Styles.PrimaryTextColor) + modal.SetBackgroundColor(app.Styles.PrimitiveBackgroundColor) + modal.SetTextColor(app.Styles.PrimaryTextColor) return &ConfirmationModal{ Modal: modal, diff --git a/components/ConnectionForm.go b/components/ConnectionForm.go index 36d6c26..30145cb 100644 --- a/components/ConnectionForm.go +++ b/components/ConnectionForm.go @@ -6,6 +6,7 @@ import ( "github.com/gdamore/tcell/v2" "github.com/rivo/tview" + "github.com/jorgerojas26/lazysql/app" "github.com/jorgerojas26/lazysql/drivers" "github.com/jorgerojas26/lazysql/helpers" "github.com/jorgerojas26/lazysql/models" @@ -23,35 +24,35 @@ func NewConnectionForm(connectionPages *models.ConnectionPages) *ConnectionForm wrapper.SetDirection(tview.FlexColumnCSS) - addForm := tview.NewForm().SetFieldBackgroundColor(tview.Styles.InverseTextColor).SetButtonBackgroundColor(tview.Styles.InverseTextColor).SetLabelColor(tview.Styles.PrimaryTextColor).SetFieldTextColor(tview.Styles.ContrastSecondaryTextColor) + addForm := tview.NewForm().SetFieldBackgroundColor(app.Styles.InverseTextColor).SetButtonBackgroundColor(tview.Styles.InverseTextColor).SetLabelColor(tview.Styles.PrimaryTextColor).SetFieldTextColor(tview.Styles.ContrastSecondaryTextColor) addForm.AddInputField("Name", "", 0, nil, nil) addForm.AddInputField("URL", "", 0, nil, nil) buttonsWrapper := tview.NewFlex().SetDirection(tview.FlexColumn) saveButton := tview.NewButton("[yellow]F1 [dark]Save") - saveButton.SetStyle(tcell.StyleDefault.Background(tview.Styles.PrimaryTextColor)) + saveButton.SetStyle(tcell.StyleDefault.Background(app.Styles.PrimaryTextColor)) saveButton.SetBorder(true) buttonsWrapper.AddItem(saveButton, 0, 1, false) buttonsWrapper.AddItem(nil, 1, 0, false) testButton := tview.NewButton("[yellow]F2 [dark]Test") - testButton.SetStyle(tcell.StyleDefault.Background(tview.Styles.PrimaryTextColor)) + testButton.SetStyle(tcell.StyleDefault.Background(app.Styles.PrimaryTextColor)) testButton.SetBorder(true) buttonsWrapper.AddItem(testButton, 0, 1, false) buttonsWrapper.AddItem(nil, 1, 0, false) connectButton := tview.NewButton("[yellow]F3 [dark]Connect") - connectButton.SetStyle(tcell.StyleDefault.Background(tview.Styles.PrimaryTextColor)) + connectButton.SetStyle(tcell.StyleDefault.Background(app.Styles.PrimaryTextColor)) connectButton.SetBorder(true) buttonsWrapper.AddItem(connectButton, 0, 1, false) buttonsWrapper.AddItem(nil, 1, 0, false) cancelButton := tview.NewButton("[yellow]Esc [dark]Cancel") - cancelButton.SetStyle(tcell.StyleDefault.Background(tcell.Color(tview.Styles.PrimaryTextColor))) + cancelButton.SetStyle(tcell.StyleDefault.Background(tcell.Color(app.Styles.PrimaryTextColor))) cancelButton.SetBorder(true) buttonsWrapper.AddItem(cancelButton, 0, 1, false) @@ -77,7 +78,7 @@ func NewConnectionForm(connectionPages *models.ConnectionPages) *ConnectionForm func (form *ConnectionForm) inputCapture(connectionPages *models.ConnectionPages) func(event *tcell.EventKey) *tcell.EventKey { return func(event *tcell.EventKey) *tcell.EventKey { if event.Key() == tcell.KeyEsc { - connectionPages.SwitchToPage("Connections") + connectionPages.SwitchToPage(pageNameConnections) } else if event.Key() == tcell.KeyF1 || event.Key() == tcell.KeyEnter { connectionName := form.GetFormItem(0).(*tview.InputField).GetText() @@ -111,7 +112,7 @@ func (form *ConnectionForm) inputCapture(connectionPages *models.ConnectionPages } switch form.Action { - case "create": + case actionNewConnection: newDatabases = append(databases, parsedDatabaseData) err := helpers.SaveConnectionConfig(newDatabases) @@ -120,7 +121,7 @@ func (form *ConnectionForm) inputCapture(connectionPages *models.ConnectionPages return event } - case "edit": + case actionEditConnection: newDatabases = make([]models.Connection, len(databases)) row, _ := ConnectionListTable.GetSelection() @@ -151,7 +152,7 @@ func (form *ConnectionForm) inputCapture(connectionPages *models.ConnectionPages } ConnectionListTable.SetConnections(newDatabases) - connectionPages.SwitchToPage("Connections") + connectionPages.SwitchToPage(pageNameConnections) } else if event.Key() == tcell.KeyF2 { connectionString := form.GetFormItem(1).(*tview.InputField).GetText() @@ -168,16 +169,16 @@ func (form *ConnectionForm) testConnection(connectionString string) { return } - form.StatusText.SetText("Connecting...").SetTextColor(tview.Styles.TertiaryTextColor) + form.StatusText.SetText("Connecting...").SetTextColor(app.Styles.TertiaryTextColor) var db drivers.Driver switch parsed.Driver { - case "mysql": + case drivers.DriverMySQL: db = &drivers.MySQL{} - case "postgres": + case drivers.DriverPostgres: db = &drivers.Postgres{} - case "sqlite3": + case drivers.DriverSqlite: db = &drivers.SQLite{} } @@ -186,7 +187,7 @@ func (form *ConnectionForm) testConnection(connectionString string) { if err != nil { form.StatusText.SetText(err.Error()).SetTextStyle(tcell.StyleDefault.Foreground(tcell.ColorRed)) } else { - form.StatusText.SetText("Connection success").SetTextColor(tview.Styles.TertiaryTextColor) + form.StatusText.SetText("Connection success").SetTextColor(app.Styles.TertiaryTextColor) } App.ForceDraw() } diff --git a/components/ConnectionPage.go b/components/ConnectionPage.go index 1e13431..8fc7ad9 100644 --- a/components/ConnectionPage.go +++ b/components/ConnectionPage.go @@ -32,8 +32,8 @@ func NewConnectionPages() *models.ConnectionPages { connectionForm := NewConnectionForm(cp) connectionSelection := NewConnectionSelection(connectionForm, cp) - cp.AddPage("Connections", connectionSelection.Flex, true, true) - cp.AddPage("ConnectionForm", connectionForm.Flex, true, false) + cp.AddPage(pageNameConnectionSelection, connectionSelection.Flex, true, true) + cp.AddPage(pageNameConnectionForm, connectionForm.Flex, true, false) return cp } diff --git a/components/ConnectionSelection.go b/components/ConnectionSelection.go index 53f34d0..c5f50e4 100644 --- a/components/ConnectionSelection.go +++ b/components/ConnectionSelection.go @@ -29,35 +29,35 @@ func NewConnectionSelection(connectionForm *ConnectionForm, connectionPages *mod buttonsWrapper := tview.NewFlex().SetDirection(tview.FlexRowCSS) newButton := tview.NewButton("[yellow]N[dark]ew") - newButton.SetStyle(tcell.StyleDefault.Background(tview.Styles.PrimitiveBackgroundColor)) + newButton.SetStyle(tcell.StyleDefault.Background(app.Styles.PrimitiveBackgroundColor)) newButton.SetBorder(true) buttonsWrapper.AddItem(newButton, 0, 1, false) buttonsWrapper.AddItem(nil, 1, 0, false) connectButton := tview.NewButton("[yellow]C[dark]onnect") - connectButton.SetStyle(tcell.StyleDefault.Background(tview.Styles.PrimitiveBackgroundColor)) + connectButton.SetStyle(tcell.StyleDefault.Background(app.Styles.PrimitiveBackgroundColor)) connectButton.SetBorder(true) buttonsWrapper.AddItem(connectButton, 0, 1, false) buttonsWrapper.AddItem(nil, 1, 0, false) editButton := tview.NewButton("[yellow]E[dark]dit") - editButton.SetStyle(tcell.StyleDefault.Background(tview.Styles.PrimitiveBackgroundColor)) + editButton.SetStyle(tcell.StyleDefault.Background(app.Styles.PrimitiveBackgroundColor)) editButton.SetBorder(true) buttonsWrapper.AddItem(editButton, 0, 1, false) buttonsWrapper.AddItem(nil, 1, 0, false) deleteButton := tview.NewButton("[yellow]D[dark]elete") - deleteButton.SetStyle(tcell.StyleDefault.Background(tview.Styles.PrimitiveBackgroundColor)) + deleteButton.SetStyle(tcell.StyleDefault.Background(app.Styles.PrimitiveBackgroundColor)) deleteButton.SetBorder(true) buttonsWrapper.AddItem(deleteButton, 0, 1, false) buttonsWrapper.AddItem(nil, 1, 0, false) quitButton := tview.NewButton("[yellow]Q[dark]uit") - quitButton.SetStyle(tcell.StyleDefault.Background(tview.Styles.PrimitiveBackgroundColor)) + quitButton.SetStyle(tcell.StyleDefault.Background(app.Styles.PrimitiveBackgroundColor)) quitButton.SetBorder(true) buttonsWrapper.AddItem(quitButton, 0, 1, false) @@ -88,18 +88,18 @@ func NewConnectionSelection(connectionForm *ConnectionForm, connectionPages *mod case commands.Connect: go cs.Connect(selectedConnection) case commands.EditConnection: - connectionPages.SwitchToPage("ConnectionForm") + connectionPages.SwitchToPage(pageNameConnectionForm) connectionForm.GetFormItemByLabel("Name").(*tview.InputField).SetText(selectedConnection.Name) connectionForm.GetFormItemByLabel("URL").(*tview.InputField).SetText(selectedConnection.URL) connectionForm.StatusText.SetText("") - connectionForm.SetAction("edit") + connectionForm.SetAction(actionEditConnection) return nil case commands.DeleteConnection: confirmationModal := NewConfirmationModal("") confirmationModal.SetDoneFunc(func(_ int, buttonLabel string) { - MainPages.RemovePage("Confirmation") + MainPages.RemovePage(pageNameConfirmation) confirmationModal = nil if buttonLabel == "Yes" { @@ -115,7 +115,7 @@ func NewConnectionSelection(connectionForm *ConnectionForm, connectionPages *mod } }) - MainPages.AddPage("Confirmation", confirmationModal, true, true) + MainPages.AddPage(pageNameConfirmation, confirmationModal, true, true) return nil } @@ -123,11 +123,11 @@ func NewConnectionSelection(connectionForm *ConnectionForm, connectionPages *mod switch command { case commands.NewConnection: - connectionForm.SetAction("create") + connectionForm.SetAction(actionNewConnection) connectionForm.GetFormItemByLabel("Name").(*tview.InputField).SetText("") connectionForm.GetFormItemByLabel("URL").(*tview.InputField).SetText("") connectionForm.StatusText.SetText("") - connectionPages.SwitchToPage("ConnectionForm") + connectionPages.SwitchToPage(pageNameConnectionForm) case commands.Quit: if wrapper.HasFocus() { app.App.Stop() @@ -145,17 +145,17 @@ func (cs *ConnectionSelection) Connect(connection models.Connection) { MainPages.SwitchToPage(connection.URL) App.Draw() } else { - cs.StatusText.SetText("Connecting...").SetTextColor(tview.Styles.TertiaryTextColor) + cs.StatusText.SetText("Connecting...").SetTextColor(app.Styles.TertiaryTextColor) App.Draw() var newDbDriver drivers.Driver switch connection.Provider { - case "mysql": + case drivers.DriverMySQL: newDbDriver = &drivers.MySQL{} - case "postgres": + case drivers.DriverPostgres: newDbDriver = &drivers.Postgres{} - case "sqlite3": + case drivers.DriverSqlite: newDbDriver = &drivers.SQLite{} } diff --git a/components/ConnectionsTable.go b/components/ConnectionsTable.go index e4ca4ab..411609d 100644 --- a/components/ConnectionsTable.go +++ b/components/ConnectionsTable.go @@ -4,6 +4,7 @@ import ( "github.com/gdamore/tcell/v2" "github.com/rivo/tview" + "github.com/jorgerojas26/lazysql/app" "github.com/jorgerojas26/lazysql/helpers" "github.com/jorgerojas26/lazysql/models" ) @@ -29,7 +30,7 @@ func NewConnectionsTable() *ConnectionsTable { } table.SetOffset(5, 0) - table.SetSelectedStyle(tcell.StyleDefault.Foreground(tview.Styles.SecondaryTextColor).Background(tview.Styles.PrimitiveBackgroundColor)) + table.SetSelectedStyle(tcell.StyleDefault.Foreground(app.Styles.SecondaryTextColor).Background(tview.Styles.PrimitiveBackgroundColor)) wrapper.AddItem(table, 0, 1, true) diff --git a/components/HelpModal.go b/components/HelpModal.go index f6e905f..27c965b 100644 --- a/components/HelpModal.go +++ b/components/HelpModal.go @@ -31,10 +31,10 @@ func NewHelpModal() *HelpModal { // table.SetBorders(true) table.SetBorder(true) - table.SetBorderColor(tview.Styles.PrimaryTextColor) + table.SetBorderColor(app.Styles.PrimaryTextColor) table.SetTitle(" Keybindings ") table.SetSelectable(true, false) - table.SetSelectedStyle(tcell.StyleDefault.Background(tview.Styles.SecondaryTextColor).Foreground(tview.Styles.ContrastSecondaryTextColor)) + table.SetSelectedStyle(tcell.StyleDefault.Background(app.Styles.SecondaryTextColor).Foreground(tview.Styles.ContrastSecondaryTextColor)) keymapGroups := app.Keymaps.Groups @@ -51,7 +51,7 @@ func NewHelpModal() *HelpModal { for groupName, keys := range keymapGroups { rowCount := table.GetRowCount() groupNameCell := tview.NewTableCell(strings.ToUpper(groupName)) - groupNameCell.SetTextColor(tview.Styles.TertiaryTextColor) + groupNameCell.SetTextColor(app.Styles.TertiaryTextColor) groupNameCell.SetSelectable(rowCount == 0) table.SetCell(rowCount, 0, tview.NewTableCell("").SetSelectable(false)) @@ -64,7 +64,7 @@ func NewHelpModal() *HelpModal { if len(keyText) < len(mostLengthyKey) { keyText = strings.Repeat(" ", len(mostLengthyKey)-len(keyText)) + keyText } - table.SetCell(rowCount+3+i, 0, tview.NewTableCell(keyText).SetAlign(tview.AlignRight).SetTextColor(tview.Styles.SecondaryTextColor)) + table.SetCell(rowCount+3+i, 0, tview.NewTableCell(keyText).SetAlign(tview.AlignRight).SetTextColor(app.Styles.SecondaryTextColor)) table.SetCell(rowCount+3+i, 1, tview.NewTableCell(key.Description).SetAlign(tview.AlignLeft).SetExpansion(1)) } @@ -75,7 +75,7 @@ func NewHelpModal() *HelpModal { table.SetInputCapture(func(event *tcell.EventKey) *tcell.EventKey { command := app.Keymaps.Group(app.HomeGroup).Resolve(event) if command == commands.Quit || command == commands.HelpPopup { - MainPages.RemovePage(HelpPageName) + MainPages.RemovePage(pageNameHelp) } return event }) diff --git a/components/HelpStatus.go b/components/HelpStatus.go index dfa03cb..32dec87 100644 --- a/components/HelpStatus.go +++ b/components/HelpStatus.go @@ -12,7 +12,7 @@ type HelpStatus struct { } func NewHelpStatus() HelpStatus { - status := HelpStatus{tview.NewTextView().SetTextColor(tview.Styles.TertiaryTextColor)} + status := HelpStatus{tview.NewTextView().SetTextColor(app.Styles.TertiaryTextColor)} status.SetStatusOnTree() diff --git a/components/Home.go b/components/Home.go index cc8f07c..664ca9c 100644 --- a/components/Home.go +++ b/components/Home.go @@ -47,10 +47,10 @@ func NewHomePage(connection models.Connection, dbdriver drivers.Driver) *Home { go home.subscribeToTreeChanges() - leftWrapper.SetBorderColor(tview.Styles.InverseTextColor) + leftWrapper.SetBorderColor(app.Styles.InverseTextColor) leftWrapper.AddItem(tree.Wrapper, 0, 1, true) - rightWrapper.SetBorderColor(tview.Styles.InverseTextColor) + rightWrapper.SetBorderColor(app.Styles.InverseTextColor) rightWrapper.SetBorder(true) rightWrapper.SetDirection(tview.FlexColumnCSS) rightWrapper.SetInputCapture(home.rightWrapperInputCapture) @@ -66,7 +66,7 @@ func NewHomePage(connection models.Connection, dbdriver drivers.Driver) *Home { home.SetInputCapture(home.homeInputCapture) home.SetFocusFunc(func() { - if home.FocusedWrapper == "left" || home.FocusedWrapper == "" { + if home.FocusedWrapper == focusedWrapperLeft || home.FocusedWrapper == "" { home.focusLeftWrapper() } else { home.focusRightWrapper() @@ -82,7 +82,7 @@ func (home *Home) subscribeToTreeChanges() { for stateChange := range ch { switch stateChange.Key { - case "SelectedTable": + case eventTreeSelectedTable: databaseName := home.Tree.GetSelectedDatabase() tableName := stateChange.Value.(string) @@ -104,16 +104,20 @@ func (home *Home) subscribeToTreeChanges() { } - table.FetchRecords(func() { + results := table.FetchRecords(func() { home.focusLeftWrapper() }) + if len(results) > 1 && !table.GetShowSidebar() { // 1 because the row 0 is the column names + table.ShowSidebar(true) + } + if table.state.error == "" { home.focusRightWrapper() } app.App.ForceDraw() - case "IsFiltering": + case eventTreeIsFiltering: isFiltering := stateChange.Value.(bool) if isFiltering { home.SetInputCapture(nil) @@ -127,8 +131,8 @@ func (home *Home) subscribeToTreeChanges() { func (home *Home) focusRightWrapper() { home.Tree.RemoveHighlight() - home.RightWrapper.SetBorderColor(tview.Styles.PrimaryTextColor) - home.LeftWrapper.SetBorderColor(tview.Styles.InverseTextColor) + home.RightWrapper.SetBorderColor(app.Styles.PrimaryTextColor) + home.LeftWrapper.SetBorderColor(app.Styles.InverseTextColor) home.TabbedPane.Highlight() tab := home.TabbedPane.GetCurrentTab() @@ -136,7 +140,7 @@ func (home *Home) focusRightWrapper() { home.focusTab(tab) } - home.FocusedWrapper = "right" + home.FocusedWrapper = focusedWrapperRight } func (home *Home) focusTab(tab *Tab) { @@ -162,7 +166,7 @@ func (home *Home) focusTab(tab *Tab) { App.SetFocus(table) } - if tab.Name == EditorTabName { + if tab.Name == tabNameEditor { home.HelpStatus.SetStatusOnEditorView() } else { home.HelpStatus.SetStatusOnTableView() @@ -173,8 +177,8 @@ func (home *Home) focusTab(tab *Tab) { func (home *Home) focusLeftWrapper() { home.Tree.Highlight() - home.RightWrapper.SetBorderColor(tview.Styles.InverseTextColor) - home.LeftWrapper.SetBorderColor(tview.Styles.PrimaryTextColor) + home.RightWrapper.SetBorderColor(app.Styles.InverseTextColor) + home.LeftWrapper.SetBorderColor(app.Styles.PrimaryTextColor) tab := home.TabbedPane.GetCurrentTab() @@ -189,7 +193,7 @@ func (home *Home) focusLeftWrapper() { App.SetFocus(home.Tree) - home.FocusedWrapper = "left" + home.FocusedWrapper = focusedWrapperLeft } func (home *Home) rightWrapperInputCapture(event *tcell.EventKey) *tcell.EventKey { @@ -268,22 +272,22 @@ func (home *Home) homeInputCapture(event *tcell.EventKey) *tcell.EventKey { switch command { case commands.MoveLeft: - if table != nil && !table.GetIsEditing() && !table.GetIsFiltering() && home.FocusedWrapper == "right" { + if table != nil && !table.GetIsEditing() && !table.GetIsFiltering() && home.FocusedWrapper == focusedWrapperRight { home.focusLeftWrapper() } case commands.MoveRight: - if table != nil && !table.GetIsEditing() && !table.GetIsFiltering() && home.FocusedWrapper == "left" { + if table != nil && !table.GetIsEditing() && !table.GetIsFiltering() && home.FocusedWrapper == focusedWrapperLeft { home.focusRightWrapper() } case commands.SwitchToEditorView: - tab := home.TabbedPane.GetTabByName(EditorTabName) + tab := home.TabbedPane.GetTabByName(tabNameEditor) if tab != nil { - home.TabbedPane.SwitchToTabByName(EditorTabName) + home.TabbedPane.SwitchToTabByName(tabNameEditor) tab.Content.SetIsFiltering(true) } else { tableWithEditor := NewResultsTable(&home.ListOfDbChanges, home.Tree, home.DBDriver).WithEditor() - home.TabbedPane.AppendTab(EditorTabName, tableWithEditor, EditorTabName) + home.TabbedPane.AppendTab(tabNameEditor, tableWithEditor, tabNameEditor) tableWithEditor.SetIsFiltering(true) } home.HelpStatus.SetStatusOnEditorView() @@ -291,7 +295,7 @@ func (home *Home) homeInputCapture(event *tcell.EventKey) *tcell.EventKey { App.ForceDraw() case commands.SwitchToConnectionsView: if (table != nil && !table.GetIsEditing() && !table.GetIsFiltering() && !table.GetIsLoading()) || table == nil { - MainPages.SwitchToPage("Connections") + MainPages.SwitchToPage(pageNameConnections) } case commands.Quit: if tab != nil { @@ -308,7 +312,7 @@ func (home *Home) homeInputCapture(event *tcell.EventKey) *tcell.EventKey { confirmationModal := NewConfirmationModal("") confirmationModal.SetDoneFunc(func(_ int, buttonLabel string) { - MainPages.RemovePage("Confirmation") + MainPages.RemovePage(pageNameConfirmation) confirmationModal = nil if buttonLabel == "Yes" { @@ -328,7 +332,7 @@ func (home *Home) homeInputCapture(event *tcell.EventKey) *tcell.EventKey { } }) - MainPages.AddPage("Confirmation", confirmationModal, true, true) + MainPages.AddPage(pageNameConfirmation, confirmationModal, true, true) } case commands.HelpPopup: if table == nil || !table.GetIsEditing() { @@ -341,7 +345,7 @@ func (home *Home) homeInputCapture(event *tcell.EventKey) *tcell.EventKey { // } // return event // }) - MainPages.AddPage(HelpPageName, home.HelpModal, true, true) + MainPages.AddPage(pageNameHelp, home.HelpModal, true, true) } } diff --git a/components/Pages.go b/components/Pages.go index 257cf76..7bea031 100644 --- a/components/Pages.go +++ b/components/Pages.go @@ -2,11 +2,13 @@ package components import ( "github.com/rivo/tview" + + "github.com/jorgerojas26/lazysql/app" ) var MainPages = tview.NewPages() func init() { - MainPages.SetBackgroundColor(tview.Styles.PrimitiveBackgroundColor) - MainPages.AddPage("Connections", NewConnectionPages().Flex, true, true) + MainPages.SetBackgroundColor(app.Styles.PrimitiveBackgroundColor) + MainPages.AddPage(pageNameConnections, NewConnectionPages().Flex, true, true) } diff --git a/components/ResultTableFilter.go b/components/ResultTableFilter.go index bf526d5..002582d 100644 --- a/components/ResultTableFilter.go +++ b/components/ResultTableFilter.go @@ -4,6 +4,7 @@ import ( "github.com/gdamore/tcell/v2" "github.com/rivo/tview" + "github.com/jorgerojas26/lazysql/app" "github.com/jorgerojas26/lazysql/models" ) @@ -27,14 +28,14 @@ func NewResultsFilter() *ResultsTableFilter { recordsFilter.SetTitleAlign(tview.AlignCenter) recordsFilter.SetBorderPadding(0, 0, 1, 1) - recordsFilter.Label.SetTextColor(tview.Styles.TertiaryTextColor) + recordsFilter.Label.SetTextColor(app.Styles.TertiaryTextColor) recordsFilter.Label.SetText("WHERE") recordsFilter.Label.SetBorderPadding(0, 0, 0, 1) recordsFilter.Input.SetPlaceholder("Enter a WHERE clause to filter the results") - recordsFilter.Input.SetPlaceholderStyle(tcell.StyleDefault.Foreground(tview.Styles.PrimaryTextColor).Background(tview.Styles.PrimitiveBackgroundColor)) - recordsFilter.Input.SetFieldBackgroundColor(tview.Styles.PrimitiveBackgroundColor) - recordsFilter.Input.SetFieldTextColor(tview.Styles.PrimaryTextColor) + recordsFilter.Input.SetPlaceholderStyle(tcell.StyleDefault.Foreground(app.Styles.PrimaryTextColor).Background(tview.Styles.PrimitiveBackgroundColor)) + recordsFilter.Input.SetFieldBackgroundColor(app.Styles.PrimitiveBackgroundColor) + recordsFilter.Input.SetFieldTextColor(app.Styles.PrimaryTextColor) recordsFilter.Input.SetDoneFunc(func(key tcell.Key) { switch key { case tcell.KeyEnter: @@ -50,7 +51,7 @@ func NewResultsFilter() *ResultsTableFilter { } }) - recordsFilter.Input.SetAutocompleteStyles(tview.Styles.PrimitiveBackgroundColor, tcell.StyleDefault.Foreground(tview.Styles.PrimaryTextColor).Background(tview.Styles.PrimitiveBackgroundColor), tcell.StyleDefault.Foreground(tview.Styles.SecondaryTextColor).Background(tview.Styles.PrimitiveBackgroundColor)) + recordsFilter.Input.SetAutocompleteStyles(app.Styles.PrimitiveBackgroundColor, tcell.StyleDefault.Foreground(tview.Styles.PrimaryTextColor).Background(tview.Styles.PrimitiveBackgroundColor), tcell.StyleDefault.Foreground(tview.Styles.SecondaryTextColor).Background(tview.Styles.PrimitiveBackgroundColor)) recordsFilter.AddItem(recordsFilter.Label, 6, 0, false) recordsFilter.AddItem(recordsFilter.Input, 0, 1, false) @@ -67,7 +68,7 @@ func (filter *ResultsTableFilter) Subscribe() chan models.StateChange { func (filter *ResultsTableFilter) Publish(message string) { for _, sub := range filter.subscribers { sub <- models.StateChange{ - Key: "Filter", + Key: eventResultsTableFiltering, Value: message, } } @@ -87,29 +88,29 @@ func (filter *ResultsTableFilter) SetIsFiltering(filtering bool) { // Function to blur func (filter *ResultsTableFilter) RemoveHighlight() { - filter.SetBorderColor(tview.Styles.InverseTextColor) - filter.Label.SetTextColor(tview.Styles.InverseTextColor) - filter.Input.SetPlaceholderTextColor(tview.Styles.InverseTextColor) - filter.Input.SetFieldTextColor(tview.Styles.InverseTextColor) + filter.SetBorderColor(app.Styles.InverseTextColor) + filter.Label.SetTextColor(app.Styles.InverseTextColor) + filter.Input.SetPlaceholderTextColor(app.Styles.InverseTextColor) + filter.Input.SetFieldTextColor(app.Styles.InverseTextColor) } func (filter *ResultsTableFilter) RemoveLocalHighlight() { filter.SetBorderColor(tcell.ColorWhite) - filter.Label.SetTextColor(tview.Styles.TertiaryTextColor) - filter.Input.SetPlaceholderTextColor(tview.Styles.InverseTextColor) - filter.Input.SetFieldTextColor(tview.Styles.InverseTextColor) + filter.Label.SetTextColor(app.Styles.TertiaryTextColor) + filter.Input.SetPlaceholderTextColor(app.Styles.InverseTextColor) + filter.Input.SetFieldTextColor(app.Styles.InverseTextColor) } func (filter *ResultsTableFilter) Highlight() { filter.SetBorderColor(tcell.ColorWhite) - filter.Label.SetTextColor(tview.Styles.TertiaryTextColor) + filter.Label.SetTextColor(app.Styles.TertiaryTextColor) filter.Input.SetPlaceholderTextColor(tcell.ColorWhite) - filter.Input.SetFieldTextColor(tview.Styles.PrimaryTextColor) + filter.Input.SetFieldTextColor(app.Styles.PrimaryTextColor) } func (filter *ResultsTableFilter) HighlightLocal() { - filter.SetBorderColor(tview.Styles.PrimaryTextColor) - filter.Label.SetTextColor(tview.Styles.TertiaryTextColor) + filter.SetBorderColor(app.Styles.PrimaryTextColor) + filter.Label.SetTextColor(app.Styles.TertiaryTextColor) filter.Input.SetPlaceholderTextColor(tcell.ColorWhite) - filter.Input.SetFieldTextColor(tview.Styles.PrimaryTextColor) + filter.Input.SetFieldTextColor(app.Styles.PrimaryTextColor) } diff --git a/components/ResultsTable.go b/components/ResultsTable.go index 6b0b652..05a08a5 100644 --- a/components/ResultsTable.go +++ b/components/ResultsTable.go @@ -31,6 +31,7 @@ type ResultsTableState struct { isEditing bool isFiltering bool isLoading bool + showSidebar bool } type ResultsTable struct { @@ -47,16 +48,10 @@ type ResultsTable struct { EditorPages *tview.Pages ResultsInfo *tview.TextView Tree *Tree + Sidebar *Sidebar DBDriver drivers.Driver } -var ( - ErrorModal = tview.NewModal() - ChangeColor = tcell.ColorDarkOrange - InsertColor = tcell.ColorDarkGreen - DeleteColor = tcell.ColorRed -) - func NewResultsTable(listOfDbChanges *[]models.DbDmlChange, tree *Tree, dbdriver drivers.Driver) *ResultsTable { state := &ResultsTableState{ records: [][]string{}, @@ -67,6 +62,7 @@ func NewResultsTable(listOfDbChanges *[]models.DbDmlChange, tree *Tree, dbdriver isEditing: false, isLoading: false, listOfDbChanges: listOfDbChanges, + showSidebar: false, } wrapper := tview.NewFlex() @@ -76,22 +72,24 @@ func NewResultsTable(listOfDbChanges *[]models.DbDmlChange, tree *Tree, dbdriver errorModal.AddButtons([]string{"Ok"}) errorModal.SetText("An error occurred") errorModal.SetBackgroundColor(tcell.ColorRed) - errorModal.SetTextColor(tview.Styles.PrimaryTextColor) - errorModal.SetButtonStyle(tcell.StyleDefault.Foreground(tview.Styles.PrimaryTextColor)) + errorModal.SetTextColor(app.Styles.PrimaryTextColor) + errorModal.SetButtonStyle(tcell.StyleDefault.Foreground(app.Styles.PrimaryTextColor)) errorModal.SetFocus(0) loadingModal := tview.NewModal() loadingModal.SetText("Loading...") - loadingModal.SetBackgroundColor(tview.Styles.PrimitiveBackgroundColor) - loadingModal.SetTextColor(tview.Styles.SecondaryTextColor) + loadingModal.SetBackgroundColor(app.Styles.PrimitiveBackgroundColor) + loadingModal.SetTextColor(app.Styles.SecondaryTextColor) pages := tview.NewPages() - pages.AddPage("table", wrapper, true, true) - pages.AddPage("error", errorModal, true, false) - pages.AddPage("loading", loadingModal, false, false) + pages.AddPage(pageNameTable, wrapper, true, true) + pages.AddPage(pageNameTableError, errorModal, true, false) + pages.AddPage(pageNameTableLoading, loadingModal, false, false) pagination := NewPagination() + sidebar := NewSidebar() + table := &ResultsTable{ Table: tview.NewTable(), state: state, @@ -103,15 +101,25 @@ func NewResultsTable(listOfDbChanges *[]models.DbDmlChange, tree *Tree, dbdriver Editor: nil, Tree: tree, DBDriver: dbdriver, + Sidebar: sidebar, } table.SetSelectable(true, true) table.SetBorders(true) table.SetFixed(1, 0) table.SetInputCapture(table.tableInputCapture) - table.SetSelectedStyle(tcell.StyleDefault.Background(tview.Styles.SecondaryTextColor).Foreground(tview.Styles.ContrastSecondaryTextColor)) + table.SetSelectedStyle(tcell.StyleDefault.Background(app.Styles.SecondaryTextColor).Foreground(tview.Styles.ContrastSecondaryTextColor)) + table.Page.AddPage(pageNameSidebar, table.Sidebar, false, false) + + table.SetSelectionChangedFunc(func(row, col int) { + if table.GetShowSidebar() { + logger.Info("table.SetSelectionChangedFunc", map[string]any{"row": row, "col": col}) + go table.UpdateSidebar() + } + }) go table.subscribeToTreeChanges() + go table.subscribeToSidebarChanges() return table } @@ -159,12 +167,12 @@ func (table *ResultsTable) WithEditor() *ResultsTable { resultsInfoWrapper := tview.NewFlex().SetDirection(tview.FlexColumnCSS) resultsInfoText := tview.NewTextView() resultsInfoText.SetBorder(true) - resultsInfoText.SetBorderColor(tview.Styles.PrimaryTextColor) - resultsInfoText.SetTextColor(tview.Styles.PrimaryTextColor) + resultsInfoText.SetBorderColor(app.Styles.PrimaryTextColor) + resultsInfoText.SetTextColor(app.Styles.PrimaryTextColor) resultsInfoWrapper.AddItem(resultsInfoText, 3, 0, false) - editorPages.AddPage("Table", tableWrapper, true, false) - editorPages.AddPage("ResultsInfo", resultsInfoWrapper, true, true) + editorPages.AddPage(pageNameTableEditorTable, tableWrapper, true, false) + editorPages.AddPage(pageNameTableEditorResultsInfo, resultsInfoWrapper, true, true) table.EditorPages = editorPages table.ResultsInfo = resultsInfoText @@ -180,12 +188,54 @@ func (table *ResultsTable) subscribeToTreeChanges() { ch := table.Tree.Subscribe() for stateChange := range ch { - if stateChange.Key == "SelectedDatabase" { + if stateChange.Key == eventTreeSelectedDatabase { table.SetDatabaseName(stateChange.Value.(string)) } } } +func (table *ResultsTable) subscribeToSidebarChanges() { + ch := table.Sidebar.Subscribe() + + for stateChange := range ch { + switch stateChange.Key { + case eventSidebarEditing: + editing := stateChange.Value.(bool) + table.SetIsEditing(editing) + case eventSidebarUnfocusing: + App.SetFocus(table) + App.ForceDraw() + case eventSidebarToggling: + table.ShowSidebar(false) + App.ForceDraw() + case eventSidebarCommitEditing: + params := stateChange.Value.(models.SidebarEditingCommitParams) + + table.SetInputCapture(table.tableInputCapture) + table.SetIsEditing(false) + + row, _ := table.GetSelection() + changedColumnIndex := table.GetColumnIndexByName(params.ColumnName) + tableCell := table.GetCell(row, changedColumnIndex) + + tableCell.SetText(params.NewValue) + + cellValue := models.CellValue{ + Type: models.String, + Column: params.ColumnName, + Value: params.NewValue, + TableColumnIndex: changedColumnIndex, + TableRowIndex: row, + } + + logger.Info("eventSidebarCommitEditing", map[string]any{"cellValue": cellValue, "params": params, "rowIndex": row, "changedColumnIndex": changedColumnIndex}) + table.AppendNewChange(models.DmlUpdateType, row, changedColumnIndex, cellValue) + + App.ForceDraw() + } + } +} + func (table *ResultsTable) AddRows(rows [][]string) { for i, row := range rows { for j, cell := range row { @@ -193,7 +243,7 @@ func (table *ResultsTable) AddRows(rows [][]string) { tableCell.SetSelectable(i > 0) tableCell.SetExpansion(1) - tableCell.SetTextColor(tview.Styles.PrimaryTextColor) + tableCell.SetTextColor(app.Styles.PrimaryTextColor) table.SetCell(i, j, tableCell) } @@ -229,7 +279,7 @@ func (table *ResultsTable) AddInsertedRows() { tableCell.SetReference(inserts[i].PrimaryKeyValue) tableCell.SetTextColor(tcell.ColorWhite.TrueColor()) - tableCell.SetBackgroundColor(InsertColor) + tableCell.SetBackgroundColor(colorTableInsert) table.SetCell(rowIndex, j, tableCell) } @@ -241,7 +291,7 @@ func (table *ResultsTable) AppendNewRow(cells []models.CellValue, index int, UUI tableCell := tview.NewTableCell(cell.Value.(string)) tableCell.SetExpansion(1) tableCell.SetReference(UUID) - tableCell.SetTextColor(tview.Styles.PrimaryTextColor) + tableCell.SetTextColor(app.Styles.PrimaryTextColor) tableCell.SetBackgroundColor(tcell.ColorDarkGreen) switch cell.Type { @@ -249,7 +299,7 @@ func (table *ResultsTable) AppendNewRow(cells []models.CellValue, index int, UUI case models.Default: case models.String: tableCell.SetText("") - tableCell.SetTextColor(tview.Styles.InverseTextColor) + tableCell.SetTextColor(app.Styles.InverseTextColor) } table.SetCell(index, i, tableCell) @@ -309,7 +359,11 @@ func (table *ResultsTable) tableInputCapture(event *tcell.EventKey) *tcell.Event } if command == commands.Edit { - table.StartEditingCell(selectedRowIndex, selectedColumnIndex, nil) + table.StartEditingCell(selectedRowIndex, selectedColumnIndex, func(_ string, _, _ int) { + if table.GetShowSidebar() { + table.UpdateSidebar() + } + }) } else if command == commands.GotoNext { if selectedColumnIndex+1 < colCount { table.Select(selectedRowIndex, selectedColumnIndex+1) @@ -363,10 +417,16 @@ func (table *ResultsTable) tableInputCapture(event *tcell.EventKey) *tcell.Event } } } else { - table.AppendNewChange(models.DmlDeleteType, table.GetDatabaseName(), table.GetTableName(), selectedRowIndex, -1, models.CellValue{}) + table.AppendNewChange(models.DmlDeleteType, selectedRowIndex, -1, models.CellValue{}) } } + } else if command == commands.ToggleSidebar { + table.ShowSidebar(!table.GetShowSidebar()) + } else if command == commands.FocusSidebar { + if table.GetShowSidebar() { + App.SetFocus(table.Sidebar) + } } if len(table.GetRecords()) > 0 { @@ -418,10 +478,10 @@ func (table *ResultsTable) UpdateRowsColor(headerColor tcell.Color, rowColor tce } func (table *ResultsTable) RemoveHighlightTable() { - table.SetBorderColor(tview.Styles.InverseTextColor) - table.SetBordersColor(tview.Styles.InverseTextColor) - table.SetTitleColor(tview.Styles.InverseTextColor) - table.UpdateRowsColor(tview.Styles.InverseTextColor, tview.Styles.InverseTextColor) + table.SetBorderColor(app.Styles.InverseTextColor) + table.SetBordersColor(app.Styles.InverseTextColor) + table.SetTitleColor(app.Styles.InverseTextColor) + table.UpdateRowsColor(app.Styles.InverseTextColor, tview.Styles.InverseTextColor) } func (table *ResultsTable) RemoveHighlightAll() { @@ -435,10 +495,10 @@ func (table *ResultsTable) RemoveHighlightAll() { } func (table *ResultsTable) HighlightTable() { - table.SetBorderColor(tview.Styles.PrimaryTextColor) - table.SetBordersColor(tview.Styles.PrimaryTextColor) - table.SetTitleColor(tview.Styles.PrimaryTextColor) - table.UpdateRowsColor(tview.Styles.PrimaryTextColor, tview.Styles.PrimaryTextColor) + table.SetBorderColor(app.Styles.PrimaryTextColor) + table.SetBordersColor(app.Styles.PrimaryTextColor) + table.SetTitleColor(app.Styles.PrimaryTextColor) + table.UpdateRowsColor(app.Styles.PrimaryTextColor, tview.Styles.PrimaryTextColor) } func (table *ResultsTable) HighlightAll() { @@ -456,7 +516,7 @@ func (table *ResultsTable) subscribeToFilterChanges() { for stateChange := range ch { switch stateChange.Key { - case "Filter": + case eventResultsTableFiltering: if stateChange.Value != "" { rows := table.FetchRecords(nil) @@ -495,7 +555,7 @@ func (table *ResultsTable) subscribeToEditorChanges() { for stateChange := range ch { switch stateChange.Key { - case "Query": + case eventSQLEditorQuery: query := stateChange.Value.(string) if query != "" { queryLower := strings.ToLower(query) @@ -531,7 +591,7 @@ func (table *ResultsTable) subscribeToEditorChanges() { } table.SetLoading(false) } - table.EditorPages.SwitchToPage("Table") + table.EditorPages.SwitchToPage(pageNameTable) App.Draw() } else { table.SetRecords([][]string{}) @@ -547,13 +607,13 @@ func (table *ResultsTable) subscribeToEditorChanges() { } else { table.SetResultsInfo(result) table.SetLoading(false) - table.EditorPages.SwitchToPage("ResultsInfo") + table.EditorPages.SwitchToPage(pageNameTableEditorResultsInfo) App.SetFocus(table.Editor) App.Draw() } } } - case "Escape": + case eventSQLEditorEscape: table.SetIsFiltering(false) App.SetFocus(table) table.HighlightTable() @@ -618,6 +678,17 @@ func (table *ResultsTable) GetColumnNameByIndex(index int) string { return "" } +func (table *ResultsTable) GetColumnIndexByName(columnName string) int { + for i := 0; i < table.GetColumnCount(); i++ { + cell := table.GetCell(0, i) + if cell.Text == columnName { + return i + } + } + + return -1 +} + func (table *ResultsTable) GetIsLoading() bool { return table.state.isLoading } @@ -626,6 +697,10 @@ func (table *ResultsTable) GetIsFiltering() bool { return table.state.isFiltering } +func (table *ResultsTable) GetShowSidebar() bool { + return table.state.showSidebar +} + // Setters func (table *ResultsTable) SetRecords(rows [][]string) { @@ -663,7 +738,7 @@ func (table *ResultsTable) SetError(err string, done func()) { table.Error.SetText(err) table.Error.SetDoneFunc(func(_ int, _ string) { table.state.error = "" - table.Page.HidePage("error") + table.Page.HidePage(pageNameTableError) if table.GetIsFiltering() { if table.Editor != nil { App.SetFocus(table.Editor) @@ -677,7 +752,7 @@ func (table *ResultsTable) SetError(err string, done func()) { done() } }) - table.Page.ShowPage("error") + table.Page.ShowPage(pageNameTableError) App.SetFocus(table.Error) App.ForceDraw() } @@ -690,7 +765,7 @@ func (table *ResultsTable) SetLoading(show bool) { defer func() { if r := recover(); r != nil { logger.Error("ResultsTable.go:800 => Recovered from panic", map[string]any{"error": r}) - _ = table.Page.HidePage("loading") + _ = table.Page.HidePage(pageNameTableLoading) if table.state.error != "" { App.SetFocus(table.Error) } else { @@ -701,11 +776,11 @@ func (table *ResultsTable) SetLoading(show bool) { table.state.isLoading = show if show { - table.Page.ShowPage("loading") + table.Page.ShowPage(pageNameTableLoading) App.SetFocus(table.Loading) App.ForceDraw() } else { - table.Page.HidePage("loading") + table.Page.HidePage(pageNameTableLoading) if table.state.error != "" { App.SetFocus(table.Error) } else { @@ -760,7 +835,7 @@ func (table *ResultsTable) SetSortedBy(column string, direction string) { tableCell := tview.NewTableCell(col[0]) tableCell.SetSelectable(false) tableCell.SetExpansion(1) - tableCell.SetTextColor(tview.Styles.PrimaryTextColor) + tableCell.SetTextColor(app.Styles.PrimaryTextColor) if col[0] == column { tableCell.SetText(fmt.Sprintf("%s %s", col[0], iconDirection)) @@ -827,8 +902,8 @@ func (table *ResultsTable) StartEditingCell(row int, col int, callback func(newV cell := table.GetCell(row, col) inputField := tview.NewInputField() inputField.SetText(cell.Text) - inputField.SetFieldBackgroundColor(tview.Styles.PrimaryTextColor) - inputField.SetFieldTextColor(tview.Styles.PrimitiveBackgroundColor) + inputField.SetFieldBackgroundColor(app.Styles.PrimaryTextColor) + inputField.SetFieldTextColor(app.Styles.PrimitiveBackgroundColor) inputField.SetDoneFunc(func(key tcell.Key) { table.SetIsEditing(false) @@ -840,7 +915,7 @@ func (table *ResultsTable) StartEditingCell(row int, col int, callback func(newV cell.SetText(newValue) if currentValue != newValue { - table.AppendNewChange(models.DmlUpdateType, table.GetDatabaseName(), table.GetTableName(), row, col, models.CellValue{Type: models.String, Value: newValue, Column: columnName}) + table.AppendNewChange(models.DmlUpdateType, row, col, models.CellValue{Type: models.String, Value: newValue, Column: columnName, TableColumnIndex: col, TableRowIndex: row}) } switch key { @@ -868,7 +943,7 @@ func (table *ResultsTable) StartEditingCell(row int, col int, callback func(newV if key == tcell.KeyEnter || key == tcell.KeyEscape { table.SetInputCapture(table.tableInputCapture) - table.Page.RemovePage("edit") + table.Page.RemovePage(pageNameTableEditCell) App.SetFocus(table) } @@ -879,7 +954,7 @@ func (table *ResultsTable) StartEditingCell(row int, col int, callback func(newV x, y, width := cell.GetLastPosition() inputField.SetRect(x, y, width+1, 1) - table.Page.AddPage("edit", inputField, false, true) + table.Page.AddPage(pageNameTableEditCell, inputField, false, true) App.SetFocus(inputField) } @@ -906,7 +981,10 @@ func (table *ResultsTable) MutateInsertedRowCell(rowID string, newValue models.C } } -func (table *ResultsTable) AppendNewChange(changeType models.DmlType, databaseName, tableName string, rowIndex int, colIndex int, value models.CellValue) { +func (table *ResultsTable) AppendNewChange(changeType models.DmlType, rowIndex int, colIndex int, value models.CellValue) { + databaseName := table.GetDatabaseName() + tableName := table.GetTableName() + dmlChangeAlreadyExists := false // If the column has a reference, it means it's an inserted rowIndex @@ -949,18 +1027,18 @@ func (table *ResultsTable) AppendNewChange(changeType models.DmlType, databaseNa } else { (*table.state.listOfDbChanges)[i].Values = append((*table.state.listOfDbChanges)[i].Values[:valueIndex], (*table.state.listOfDbChanges)[i].Values[valueIndex+1:]...) } - table.SetCellColor(rowIndex, colIndex, tview.Styles.PrimitiveBackgroundColor) + table.SetCellColor(rowIndex, colIndex, app.Styles.PrimitiveBackgroundColor) } else { (*table.state.listOfDbChanges)[i].Values[valueIndex] = value } } else { (*table.state.listOfDbChanges)[i].Values = append((*table.state.listOfDbChanges)[i].Values, value) - table.SetCellColor(rowIndex, colIndex, ChangeColor) + table.SetCellColor(rowIndex, colIndex, colorTableChange) } case models.DmlDeleteType: *table.state.listOfDbChanges = append((*table.state.listOfDbChanges)[:i], (*table.state.listOfDbChanges)[i+1:]...) - table.SetRowColor(rowIndex, tview.Styles.PrimitiveBackgroundColor) + table.SetRowColor(rowIndex, app.Styles.PrimitiveBackgroundColor) } } } @@ -968,10 +1046,10 @@ func (table *ResultsTable) AppendNewChange(changeType models.DmlType, databaseNa if !dmlChangeAlreadyExists { switch changeType { - case models.DmlDeleteType: - table.SetRowColor(rowIndex, DeleteColor) case models.DmlUpdateType: - table.SetCellColor(rowIndex, colIndex, ChangeColor) + table.SetCellColor(rowIndex, colIndex, colorTableChange) + case models.DmlDeleteType: + table.SetRowColor(rowIndex, colorTableDelete) } newDmlChange := models.DbDmlChange{ @@ -986,6 +1064,8 @@ func (table *ResultsTable) AppendNewChange(changeType models.DmlType, databaseNa *table.state.listOfDbChanges = append(*table.state.listOfDbChanges, newDmlChange) } + + logger.Info("AppendNewChange", map[string]any{"listOfDbChanges": *table.state.listOfDbChanges}) } func (table *ResultsTable) GetPrimaryKeyValue(rowIndex int) (string, string) { @@ -997,7 +1077,7 @@ func (table *ResultsTable) GetPrimaryKeyValue(rowIndex int) (string, string) { primaryKeyValue := "" switch provider { - case "mysql": + case drivers.DriverMySQL: keyColumnIndex := -1 primaryKeyColumnIndex := -1 @@ -1018,7 +1098,7 @@ func (table *ResultsTable) GetPrimaryKeyValue(rowIndex int) (string, string) { primaryKeyValue = table.GetRecords()[rowIndex][primaryKeyColumnIndex] } - case "postgres": + case drivers.DriverPostgres: keyColumnIndex := -1 constraintTypeColumnIndex := -1 constraintNameColumnIndex := -1 @@ -1060,7 +1140,7 @@ func (table *ResultsTable) GetPrimaryKeyValue(rowIndex int) (string, string) { primaryKeyValue = table.GetRecords()[rowIndex][primaryKeyColumnIndex] } - case "sqlite3": + case drivers.DriverSqlite: keyColumnIndex := -1 primaryKeyColumnIndex := -1 @@ -1103,7 +1183,7 @@ func (table *ResultsTable) appendNewRow() { for i, column := range dbColumns { if i != 0 { // Skip the first row because they are the column names (e.x "Field", "Type", "Null", "Key", "Default", "Extra") - newRow[i-1] = models.CellValue{Type: models.Default, Column: column[0], Value: "DEFAULT"} + newRow[i-1] = models.CellValue{Type: models.Default, Column: column[0], Value: "DEFAULT", TableRowIndex: newRowTableIndex, TableColumnIndex: i} } } @@ -1224,3 +1304,68 @@ func (table *ResultsTable) search() { table.SetInputCapture(nil) } + +func (table *ResultsTable) ShowSidebar(show bool) { + table.state.showSidebar = show + + if show { + table.UpdateSidebar() + table.Page.SendToFront(pageNameSidebar) + table.Page.ShowPage(pageNameSidebar) + } else { + table.Page.HidePage(pageNameSidebar) + App.SetFocus(table) + } +} + +func (table *ResultsTable) UpdateSidebar() { + columns := table.GetColumns() + selectedRow, _ := table.GetSelection() + + if selectedRow > 0 { + tableX, _, _, tableHeight := table.GetRect() + _, _, tableInnerWidth, _ := table.GetInnerRect() + _, tableMenuY, _, tableMenuHeight := table.Menu.GetRect() + _, _, _, tableFilterHeight := table.Filter.GetRect() + _, _, _, tablePaginationHeight := table.Pagination.GetRect() + + sidebarWidth := (tableInnerWidth / 4) + sidebarHeight := tableHeight + tableMenuHeight + tableFilterHeight + tablePaginationHeight + 1 + + table.Sidebar.SetRect(tableX+tableInnerWidth-sidebarWidth, tableMenuY, sidebarWidth, sidebarHeight) + table.Sidebar.Clear() + + for i := 1; i < len(columns); i++ { + name := columns[i][0] + colType := columns[i][1] + + text := table.GetCell(selectedRow, i-1).Text + title := name + + repeatCount := sidebarWidth - len(name) - len(colType) - 4 // idk why 4 is needed, but it works. + + if repeatCount <= 0 { + repeatCount = 1 + } + + title += fmt.Sprintf("[%s]", app.Styles.SidebarTitleBorderColor) + strings.Repeat("-", repeatCount) + title += colType + + pendingEditExist := false + + for _, dmlChange := range *table.state.listOfDbChanges { + if dmlChange.Type == models.DmlUpdateType { + for _, v := range dmlChange.Values { + if v.Column == name && v.TableRowIndex == selectedRow && v.TableColumnIndex == i-1 { + pendingEditExist = true + break + } + } + } + } + + table.Sidebar.AddField(title, text, sidebarWidth, pendingEditExist) + } + + } +} diff --git a/components/ResultsTableMenu.go b/components/ResultsTableMenu.go index c907fca..e37ba9b 100644 --- a/components/ResultsTableMenu.go +++ b/components/ResultsTableMenu.go @@ -4,6 +4,8 @@ import ( "fmt" "github.com/rivo/tview" + + "github.com/jorgerojas26/lazysql/app" ) type ResultsTableMenuState struct { @@ -17,11 +19,11 @@ type ResultsTableMenu struct { } var menuItems = []string{ - "Records", - "Columns", - "Constraints", - "Foreign Keys", - "Indexes", + menuRecords, + menuColumns, + menuConstraints, + menuForeignKeys, + menuIndexes, } func NewResultsTableMenu() *ResultsTableMenu { @@ -46,17 +48,17 @@ func NewResultsTableMenu() *ResultsTableMenu { textview := tview.NewTextView().SetText(text) if i == 0 { - textview.SetTextColor(tview.Styles.PrimaryTextColor) + textview.SetTextColor(app.Styles.PrimaryTextColor) } size := 15 switch item { - case "Constraints": + case menuConstraints: size = 19 - case "Foreign Keys": + case menuForeignKeys: size = 20 - case "Indexes": + case menuIndexes: size = 16 } @@ -80,29 +82,29 @@ func (menu *ResultsTableMenu) SetSelectedOption(option int) { itemCount := menu.GetItemCount() for i := 0; i < itemCount; i++ { - menu.GetItem(i).(*tview.TextView).SetTextColor(tview.Styles.PrimaryTextColor) + menu.GetItem(i).(*tview.TextView).SetTextColor(app.Styles.PrimaryTextColor) } - menu.GetItem(option - 1).(*tview.TextView).SetTextColor(tview.Styles.SecondaryTextColor) + menu.GetItem(option - 1).(*tview.TextView).SetTextColor(app.Styles.SecondaryTextColor) } } func (menu *ResultsTableMenu) SetBlur() { - menu.SetBorderColor(tview.Styles.InverseTextColor) + menu.SetBorderColor(app.Styles.InverseTextColor) for _, item := range menu.MenuItems { - item.SetTextColor(tview.Styles.InverseTextColor) + item.SetTextColor(app.Styles.InverseTextColor) } } func (menu *ResultsTableMenu) SetFocus() { - menu.SetBorderColor(tview.Styles.PrimaryTextColor) + menu.SetBorderColor(app.Styles.PrimaryTextColor) for i, item := range menu.MenuItems { if i+1 == menu.GetSelectedOption() { - item.SetTextColor(tview.Styles.SecondaryTextColor) + item.SetTextColor(app.Styles.SecondaryTextColor) } else { - item.SetTextColor(tview.Styles.PrimaryTextColor) + item.SetTextColor(app.Styles.PrimaryTextColor) } } } diff --git a/components/SQLEditor.go b/components/SQLEditor.go index b3e4389..3302259 100644 --- a/components/SQLEditor.go +++ b/components/SQLEditor.go @@ -38,10 +38,10 @@ func NewSQLEditor() *SQLEditor { command := app.Keymaps.Group(app.EditorGroup).Resolve(event) if command == commands.Execute { - sqlEditor.Publish("Query", sqlEditor.GetText()) + sqlEditor.Publish(eventSQLEditorQuery, sqlEditor.GetText()) return nil } else if command == commands.UnfocusEditor { - sqlEditor.Publish("Escape", "") + sqlEditor.Publish(eventSQLEditorEscape, "") } else if command == commands.OpenInExternalEditor && runtime.GOOS == "linux" { // ----- THIS IS A LINUX-ONLY FEATURE, for now @@ -79,13 +79,13 @@ func (s *SQLEditor) SetIsFocused(isFocused bool) { } func (s *SQLEditor) Highlight() { - s.SetBorderColor(tview.Styles.PrimaryTextColor) - s.SetTextStyle(tcell.StyleDefault.Foreground(tview.Styles.PrimaryTextColor)) + s.SetBorderColor(app.Styles.PrimaryTextColor) + s.SetTextStyle(tcell.StyleDefault.Foreground(app.Styles.PrimaryTextColor)) } func (s *SQLEditor) SetBlur() { - s.SetBorderColor(tview.Styles.InverseTextColor) - s.SetTextStyle(tcell.StyleDefault.Foreground(tview.Styles.InverseTextColor)) + s.SetBorderColor(app.Styles.InverseTextColor) + s.SetTextStyle(tcell.StyleDefault.Foreground(app.Styles.InverseTextColor)) } /* diff --git a/components/Sidebar.go b/components/Sidebar.go new file mode 100644 index 0000000..40211df --- /dev/null +++ b/components/Sidebar.go @@ -0,0 +1,319 @@ +package components + +import ( + "strings" + + "github.com/gdamore/tcell/v2" + "github.com/rivo/tview" + + "github.com/jorgerojas26/lazysql/app" + "github.com/jorgerojas26/lazysql/commands" + "github.com/jorgerojas26/lazysql/models" +) + +type SidebarState struct { + currentFieldIndex int +} + +type SidebarFieldParameters struct { + OriginalValue string + Height int +} + +type Sidebar struct { + *tview.Frame + Flex *tview.Flex + state *SidebarState + FieldParameters []*SidebarFieldParameters + subscribers []chan models.StateChange +} + +func NewSidebar() *Sidebar { + flex := tview.NewFlex().SetDirection(tview.FlexColumnCSS) + frame := tview.NewFrame(flex) + frame.SetBackgroundColor(app.Styles.PrimitiveBackgroundColor) + frame.SetBorder(true) + frame.SetBorders(0, 0, 0, 0, 0, 0) + + sidebarState := &SidebarState{ + currentFieldIndex: 0, + } + + newSidebar := &Sidebar{ + Frame: frame, + Flex: flex, + state: sidebarState, + subscribers: []chan models.StateChange{}, + } + + newSidebar.SetInputCapture(newSidebar.inputCapture) + + newSidebar.SetBlurFunc(func() { + newSidebar.SetCurrentFieldIndex(0) + }) + + return newSidebar +} + +func (sidebar *Sidebar) AddField(title, text string, fieldWidth int, pendingEdit bool) { + field := tview.NewTextArea() + field.SetWrap(true) + field.SetDisabled(true) + + field.SetBorder(true) + field.SetTitle(title) + field.SetTitleAlign(tview.AlignLeft) + field.SetTitleColor(app.Styles.PrimaryTextColor) + field.SetText(text, true) + field.SetTextStyle(tcell.StyleDefault.Background(app.Styles.PrimitiveBackgroundColor).Foreground(tview.Styles.SecondaryTextColor)) + + if pendingEdit { + sidebar.SetEditedStyles(field) + } + + textLength := len(field.GetText()) + + itemFixedSize := 3 + + if textLength >= fieldWidth*3 { + itemFixedSize = 5 + } else if textLength >= fieldWidth { + itemFixedSize = 4 + } else { + field.SetWrap(false) + } + + field.SetFocusFunc(func() { + _, y, _, h := field.GetRect() + _, _, _, mph := sidebar.GetRect() + + if y >= mph { + hidingFieldIndex := 0 + fieldCount := sidebar.Flex.GetItemCount() + + for i := 0; i < fieldCount; i++ { + f := sidebar.Flex.GetItem(i) + _, _, _, h := f.GetRect() + if h != 0 { + hidingFieldIndex = i + break + } + } + + sidebar.Flex.ResizeItem(sidebar.Flex.GetItem(hidingFieldIndex), 0, 0) + } else if h == 0 { + sidebar.Flex.ResizeItem(field, itemFixedSize, 0) + } + }) + + fieldParameters := &SidebarFieldParameters{ + Height: itemFixedSize, + OriginalValue: text, + } + + sidebar.FieldParameters = append(sidebar.FieldParameters, fieldParameters) + sidebar.Flex.AddItem(field, itemFixedSize, 0, true) +} + +func (sidebar *Sidebar) FocusNextField() { + newIndex := sidebar.GetCurrentFieldIndex() + 1 + + if newIndex >= sidebar.Flex.GetItemCount() { + return + } + + item := sidebar.Flex.GetItem(newIndex) + + if item == nil { + return + } + + sidebar.SetCurrentFieldIndex(newIndex) + App.SetFocus(item) + App.ForceDraw() +} + +func (sidebar *Sidebar) FocusPreviousField() { + newIndex := sidebar.GetCurrentFieldIndex() - 1 + + if newIndex < 0 { + return + } + + item := sidebar.Flex.GetItem(newIndex) + + if item == nil { + return + } + + sidebar.SetCurrentFieldIndex(newIndex) + App.SetFocus(item) + App.ForceDraw() +} + +func (sidebar *Sidebar) FocusFirstField() { + sidebar.SetCurrentFieldIndex(0) + App.SetFocus(sidebar.Flex.GetItem(0)) + + fieldCount := sidebar.Flex.GetItemCount() + + for i := 0; i < fieldCount; i++ { + field := sidebar.Flex.GetItem(i) + height := sidebar.FieldParameters[i].Height + sidebar.Flex.ResizeItem(field, height, 0) + } +} + +func (sidebar *Sidebar) FocusLastField() { + newIndex := sidebar.Flex.GetItemCount() - 1 + sidebar.SetCurrentFieldIndex(newIndex) + App.SetFocus(sidebar.Flex.GetItem(newIndex)) + + _, _, _, ph := sidebar.GetRect() + + hSum := 0 + + for i := sidebar.Flex.GetItemCount() - 1; i >= 0; i-- { + field := sidebar.Flex.GetItem(i).(*tview.TextArea) + _, _, _, h := field.GetRect() + + hSum += h + + if hSum >= ph { + sidebar.Flex.ResizeItem(field, 0, 0) + } + } +} + +func (sidebar *Sidebar) FocusField(index int) { + sidebar.SetCurrentFieldIndex(index) + App.SetFocus(sidebar.Flex.GetItem(index)) +} + +func (sidebar *Sidebar) Clear() { + sidebar.FieldParameters = make([]*SidebarFieldParameters, 0) + sidebar.Flex.Clear() +} + +func (sidebar *Sidebar) EditTextCurrentField() { + index := sidebar.GetCurrentFieldIndex() + item := sidebar.Flex.GetItem(index).(*tview.TextArea) + + sidebar.SetEditingStyles(item) +} + +func (sidebar *Sidebar) inputCapture(event *tcell.EventKey) *tcell.EventKey { + command := app.Keymaps.Group(app.SidebarGroup).Resolve(event) + + switch command { + case commands.UnfocusSidebar: + sidebar.Publish(models.StateChange{Key: eventSidebarUnfocusing, Value: nil}) + case commands.ToggleSidebar: + sidebar.Publish(models.StateChange{Key: eventSidebarToggling, Value: nil}) + case commands.MoveDown: + sidebar.FocusNextField() + case commands.MoveUp: + sidebar.FocusPreviousField() + case commands.GotoStart: + sidebar.FocusFirstField() + case commands.GotoEnd: + sidebar.FocusLastField() + case commands.Edit: + sidebar.Publish(models.StateChange{Key: eventSidebarEditing, Value: true}) + + currentItemIndex := sidebar.GetCurrentFieldIndex() + item := sidebar.Flex.GetItem(currentItemIndex).(*tview.TextArea) + text := item.GetText() + + columnName := item.GetTitle() + columnNameSplit := strings.Split(columnName, "[") + columnName = columnNameSplit[0] + + sidebar.SetInputCapture(func(event *tcell.EventKey) *tcell.EventKey { + command := app.Keymaps.Group(app.SidebarGroup).Resolve(event) + + switch command { + case commands.CommitEdit: + sidebar.SetInputCapture(sidebar.inputCapture) + originalValue := sidebar.FieldParameters[currentItemIndex].OriginalValue + newText := item.GetText() + + if originalValue == newText { + sidebar.SetDisabledStyles(item) + } else { + sidebar.SetEditedStyles(item) + sidebar.Publish(models.StateChange{Key: eventSidebarCommitEditing, Value: models.SidebarEditingCommitParams{ColumnName: columnName, NewValue: newText}}) + } + + return nil + case commands.DiscardEdit: + sidebar.SetInputCapture(sidebar.inputCapture) + sidebar.SetDisabledStyles(item) + item.SetText(text, true) + sidebar.Publish(models.StateChange{Key: eventSidebarEditing, Value: false}) + return nil + } + + return event + }) + + sidebar.EditTextCurrentField() + + return nil + } + return event +} + +func (sidebar *Sidebar) SetEditingStyles(item *tview.TextArea) { + item.SetBackgroundColor(app.Styles.SecondaryTextColor) + item.SetTextStyle(tcell.StyleDefault.Background(app.Styles.SecondaryTextColor).Foreground(tview.Styles.ContrastSecondaryTextColor)) + item.SetTitleColor(app.Styles.ContrastSecondaryTextColor) + item.SetBorderColor(app.Styles.SecondaryTextColor) + + item.SetWrap(true) + item.SetDisabled(false) +} + +func (sidebar *Sidebar) SetDisabledStyles(item *tview.TextArea) { + item.SetBackgroundColor(app.Styles.PrimitiveBackgroundColor) + item.SetTextStyle(tcell.StyleDefault.Background(app.Styles.PrimitiveBackgroundColor).Foreground(tview.Styles.SecondaryTextColor)) + item.SetTitleColor(app.Styles.PrimaryTextColor) + item.SetBorderColor(app.Styles.BorderColor) + + item.SetWrap(true) + item.SetDisabled(true) +} + +func (sidebar *Sidebar) SetEditedStyles(item *tview.TextArea) { + item.SetBackgroundColor(colorTableChange) + item.SetTextStyle(tcell.StyleDefault.Background(colorTableChange).Foreground(tview.Styles.ContrastSecondaryTextColor)) + item.SetTitleColor(app.Styles.ContrastSecondaryTextColor) + item.SetBorderColor(app.Styles.ContrastSecondaryTextColor) + + item.SetWrap(true) + item.SetDisabled(true) +} + +// Getters +func (sidebar *Sidebar) GetCurrentFieldIndex() int { + return sidebar.state.currentFieldIndex +} + +// Setters +func (sidebar *Sidebar) SetCurrentFieldIndex(index int) { + sidebar.state.currentFieldIndex = index +} + +// Subscribe to changes in the sidebar state +func (sidebar *Sidebar) Subscribe() chan models.StateChange { + subscriber := make(chan models.StateChange) + sidebar.subscribers = append(sidebar.subscribers, subscriber) + return subscriber +} + +// Publish subscribers of changes in the sidebar state +func (sidebar *Sidebar) Publish(change models.StateChange) { + for _, subscriber := range sidebar.subscribers { + subscriber <- change + } +} diff --git a/components/TabbedMenu.go b/components/TabbedMenu.go index 75dfd0f..2ee7bf7 100644 --- a/components/TabbedMenu.go +++ b/components/TabbedMenu.go @@ -241,9 +241,9 @@ func (t *TabbedPane) HighlightTabHeader(tab *Tab) { for i := 0; tabToHighlight != nil && i < t.state.Length; i++ { if tabToHighlight.Header == tab.Header { - tabToHighlight.Header.SetTextColor(tview.Styles.SecondaryTextColor) + tabToHighlight.Header.SetTextColor(app.Styles.SecondaryTextColor) } else { - tabToHighlight.Header.SetTextColor(tview.Styles.PrimaryTextColor) + tabToHighlight.Header.SetTextColor(app.Styles.PrimaryTextColor) } tabToHighlight = tabToHighlight.NextTab } @@ -254,9 +254,9 @@ func (t *TabbedPane) Highlight() { for i := 0; tab != nil && i < t.state.Length; i++ { if tab == t.state.CurrentTab { - tab.Header.SetTextColor(tview.Styles.SecondaryTextColor) + tab.Header.SetTextColor(app.Styles.SecondaryTextColor) } else { - tab.Header.SetTextColor(tview.Styles.PrimaryTextColor) + tab.Header.SetTextColor(app.Styles.PrimaryTextColor) } tab = tab.NextTab } @@ -266,7 +266,7 @@ func (t *TabbedPane) SetBlur() { tab := t.state.FirstTab for i := 0; tab != nil && i < t.state.Length; i++ { - tab.Header.SetTextColor(tview.Styles.InverseTextColor) + tab.Header.SetTextColor(app.Styles.InverseTextColor) tab = tab.NextTab } } diff --git a/components/Tree.go b/components/Tree.go index 8d1e743..17ac075 100644 --- a/components/Tree.go +++ b/components/Tree.go @@ -49,7 +49,7 @@ func NewTree(dbName string, dbdriver drivers.Driver) *Tree { } tree.SetTopLevel(1) - tree.SetGraphicsColor(tview.Styles.PrimaryTextColor) + tree.SetGraphicsColor(app.Styles.PrimaryTextColor) // tree.SetBorder(true) tree.SetTitle("Databases") tree.SetTitleAlign(tview.AlignLeft) @@ -77,7 +77,7 @@ func NewTree(dbName string, dbdriver drivers.Driver) *Tree { childNode := tview.NewTreeNode(database) childNode.SetExpanded(false) childNode.SetReference(database) - childNode.SetColor(tview.Styles.PrimaryTextColor) + childNode.SetColor(app.Styles.PrimaryTextColor) rootNode.AddChild(childNode) go func(database string, node *tview.TreeNode) { @@ -95,7 +95,7 @@ func NewTree(dbName string, dbdriver drivers.Driver) *Tree { tree.SetFocusFunc(nil) }) - selectedNodeTextColor := fmt.Sprintf("[black:%s]", tview.Styles.SecondaryTextColor.Name()) + selectedNodeTextColor := fmt.Sprintf("[black:%s]", app.Styles.SecondaryTextColor.Name()) previouslyFocusedNode := tree.GetCurrentNode() previouslyFocusedNode.SetText(selectedNodeTextColor + previouslyFocusedNode.GetText()) @@ -253,33 +253,33 @@ func NewTree(dbName string, dbdriver drivers.Driver) *Tree { return event }) - tree.Filter.SetFieldStyle(tcell.StyleDefault.Background(tview.Styles.PrimitiveBackgroundColor).Foreground(tview.Styles.PrimaryTextColor)) - tree.Filter.SetPlaceholderStyle(tcell.StyleDefault.Background(tview.Styles.PrimitiveBackgroundColor).Foreground(tview.Styles.InverseTextColor)) + tree.Filter.SetFieldStyle(tcell.StyleDefault.Background(app.Styles.PrimitiveBackgroundColor).Foreground(tview.Styles.PrimaryTextColor)) + tree.Filter.SetPlaceholderStyle(tcell.StyleDefault.Background(app.Styles.PrimitiveBackgroundColor).Foreground(tview.Styles.InverseTextColor)) tree.Filter.SetBorderPadding(0, 0, 0, 0) - tree.Filter.SetBorderColor(tview.Styles.PrimaryTextColor) + tree.Filter.SetBorderColor(app.Styles.PrimaryTextColor) tree.Filter.SetLabel("Search: ") - tree.Filter.SetLabelColor(tview.Styles.InverseTextColor) + tree.Filter.SetLabelColor(app.Styles.InverseTextColor) tree.Filter.SetFocusFunc(func() { - tree.Filter.SetLabelColor(tview.Styles.TertiaryTextColor) - tree.Filter.SetFieldTextColor(tview.Styles.PrimaryTextColor) + tree.Filter.SetLabelColor(app.Styles.TertiaryTextColor) + tree.Filter.SetFieldTextColor(app.Styles.PrimaryTextColor) }) tree.Filter.SetBlurFunc(func() { if tree.Filter.GetText() == "" { - tree.Filter.SetLabelColor(tview.Styles.InverseTextColor) + tree.Filter.SetLabelColor(app.Styles.InverseTextColor) } else { - tree.Filter.SetLabelColor(tview.Styles.TertiaryTextColor) + tree.Filter.SetLabelColor(app.Styles.TertiaryTextColor) } - tree.Filter.SetFieldTextColor(tview.Styles.InverseTextColor) + tree.Filter.SetFieldTextColor(app.Styles.InverseTextColor) }) - tree.FoundNodeCountInput.SetFieldStyle(tcell.StyleDefault.Background(tview.Styles.PrimitiveBackgroundColor).Foreground(tview.Styles.PrimaryTextColor)) + tree.FoundNodeCountInput.SetFieldStyle(tcell.StyleDefault.Background(app.Styles.PrimitiveBackgroundColor).Foreground(tview.Styles.PrimaryTextColor)) tree.Wrapper.SetDirection(tview.FlexRow) tree.Wrapper.SetBorder(true) tree.Wrapper.SetBorderPadding(0, 0, 1, 1) - tree.Wrapper.SetTitleColor(tview.Styles.PrimaryTextColor) + tree.Wrapper.SetTitleColor(app.Styles.PrimaryTextColor) tree.Wrapper.AddItem(tree.Filter, 1, 0, false) tree.Wrapper.AddItem(tree.FoundNodeCountInput, 1, 0, false) @@ -300,14 +300,14 @@ func (tree *Tree) databasesToNodes(children map[string][]string, node *tview.Tre rootNode = tview.NewTreeNode(key) rootNode.SetExpanded(false) rootNode.SetReference(key) - rootNode.SetColor(tview.Styles.PrimaryTextColor) + rootNode.SetColor(app.Styles.PrimaryTextColor) node.AddChild(rootNode) } for _, child := range values { childNode := tview.NewTreeNode(child) childNode.SetExpanded(defaultExpanded) - childNode.SetColor(tview.Styles.PrimaryTextColor) + childNode.SetColor(app.Styles.PrimaryTextColor) if tree.DBDriver.GetProvider() == "sqlite3" { childNode.SetReference(child) } else if tree.DBDriver.GetProvider() == "postgres" { @@ -388,7 +388,7 @@ func (tree *Tree) GetIsFiltering() bool { func (tree *Tree) SetSelectedDatabase(database string) { tree.state.selectedDatabase = database tree.Publish(models.StateChange{ - Key: "SelectedDatabase", + Key: eventTreeSelectedDatabase, Value: database, }) } @@ -396,7 +396,7 @@ func (tree *Tree) SetSelectedDatabase(database string) { func (tree *Tree) SetSelectedTable(table string) { tree.state.selectedTable = table tree.Publish(models.StateChange{ - Key: "SelectedTable", + Key: eventTreeSelectedTable, Value: table, }) } @@ -404,17 +404,17 @@ func (tree *Tree) SetSelectedTable(table string) { func (tree *Tree) SetIsFiltering(isFiltering bool) { tree.state.isFiltering = isFiltering tree.Publish(models.StateChange{ - Key: "IsFiltering", + Key: eventTreeIsFiltering, Value: isFiltering, }) } // Blur func func (tree *Tree) RemoveHighlight() { - tree.SetBorderColor(tview.Styles.InverseTextColor) - tree.SetGraphicsColor(tview.Styles.InverseTextColor) - tree.SetTitleColor(tview.Styles.InverseTextColor) - // tree.GetRoot().SetColor(tview.Styles.InverseTextColor) + tree.SetBorderColor(app.Styles.InverseTextColor) + tree.SetGraphicsColor(app.Styles.InverseTextColor) + tree.SetTitleColor(app.Styles.InverseTextColor) + // tree.GetRoot().SetColor(app.Styles.InverseTextColor) childrens := tree.GetRoot().GetChildren() @@ -423,8 +423,8 @@ func (tree *Tree) RemoveHighlight() { childrenIsCurrentNode := children.GetReference() == tree.GetCurrentNode().GetReference() - if !childrenIsCurrentNode && currentColor == tview.Styles.PrimaryTextColor { - children.SetColor(tview.Styles.InverseTextColor) + if !childrenIsCurrentNode && currentColor == app.Styles.PrimaryTextColor { + children.SetColor(app.Styles.InverseTextColor) } childrenOfChildren := children.GetChildren() @@ -434,8 +434,8 @@ func (tree *Tree) RemoveHighlight() { childrenIsCurrentNode := children.GetReference() == tree.GetCurrentNode().GetReference() - if !childrenIsCurrentNode && currentColor == tview.Styles.PrimaryTextColor { - children.SetColor(tview.Styles.InverseTextColor) + if !childrenIsCurrentNode && currentColor == app.Styles.PrimaryTextColor { + children.SetColor(app.Styles.InverseTextColor) } } @@ -444,21 +444,21 @@ func (tree *Tree) RemoveHighlight() { } func (tree *Tree) ForceRemoveHighlight() { - tree.SetBorderColor(tview.Styles.InverseTextColor) - tree.SetGraphicsColor(tview.Styles.InverseTextColor) - tree.SetTitleColor(tview.Styles.InverseTextColor) - tree.GetRoot().SetColor(tview.Styles.InverseTextColor) + tree.SetBorderColor(app.Styles.InverseTextColor) + tree.SetGraphicsColor(app.Styles.InverseTextColor) + tree.SetTitleColor(app.Styles.InverseTextColor) + tree.GetRoot().SetColor(app.Styles.InverseTextColor) childrens := tree.GetRoot().GetChildren() for _, children := range childrens { - children.SetColor(tview.Styles.InverseTextColor) + children.SetColor(app.Styles.InverseTextColor) childrenOfChildren := children.GetChildren() for _, children := range childrenOfChildren { - children.SetColor(tview.Styles.InverseTextColor) + children.SetColor(app.Styles.InverseTextColor) } } @@ -466,26 +466,26 @@ func (tree *Tree) ForceRemoveHighlight() { // Focus func func (tree *Tree) Highlight() { - tree.SetBorderColor(tview.Styles.PrimaryTextColor) - tree.SetGraphicsColor(tview.Styles.PrimaryTextColor) - tree.SetTitleColor(tview.Styles.PrimaryTextColor) - tree.GetRoot().SetColor(tview.Styles.PrimaryTextColor) + tree.SetBorderColor(app.Styles.PrimaryTextColor) + tree.SetGraphicsColor(app.Styles.PrimaryTextColor) + tree.SetTitleColor(app.Styles.PrimaryTextColor) + tree.GetRoot().SetColor(app.Styles.PrimaryTextColor) childrens := tree.GetRoot().GetChildren() for _, children := range childrens { currentColor := children.GetColor() - if currentColor == tview.Styles.InverseTextColor { - children.SetColor(tview.Styles.PrimaryTextColor) + if currentColor == app.Styles.InverseTextColor { + children.SetColor(app.Styles.PrimaryTextColor) childrenOfChildren := children.GetChildren() for _, children := range childrenOfChildren { currentColor := children.GetColor() - if currentColor == tview.Styles.InverseTextColor { - children.SetColor(tview.Styles.PrimaryTextColor) + if currentColor == app.Styles.InverseTextColor { + children.SetColor(app.Styles.PrimaryTextColor) } } diff --git a/components/constants.go b/components/constants.go index 6422737..2bd13b5 100644 --- a/components/constants.go +++ b/components/constants.go @@ -1,10 +1,79 @@ package components -import "github.com/jorgerojas26/lazysql/app" +import ( + "github.com/gdamore/tcell/v2" + + "github.com/jorgerojas26/lazysql/app" +) var App = app.App +// Pages +const ( + // General + pageNameHelp string = "Help" + pageNameConfirmation string = "Confirmation" + pageNameConnections string = "Connections" + + // Results table + pageNameTable string = "Table" + pageNameTableError string = "TableError" + pageNameTableLoading string = "TableLoading" + pageNameTableEditorTable string = "TableEditorTable" + pageNameTableEditorResultsInfo string = "TableEditorResultsInfo" + pageNameTableEditCell string = "TableEditCell" + + // Sidebar + pageNameSidebar string = "Sidebar" + + // Connections + pageNameConnectionSelection string = "ConnectionSelection" + pageNameConnectionForm string = "ConnectionForm" +) + +// Tabs +const ( + tabNameEditor string = "Editor" +) + +// Events +const ( + eventSidebarEditing string = "EditingSidebar" + eventSidebarUnfocusing string = "UnfocusingSidebar" + eventSidebarToggling string = "TogglingSidebar" + eventSidebarCommitEditing string = "CommitEditingSidebar" + + eventSQLEditorQuery string = "Query" + eventSQLEditorEscape string = "Escape" + + eventResultsTableFiltering string = "FilteringResultsTable" + + eventTreeSelectedDatabase string = "SelectedDatabase" + eventTreeSelectedTable string = "SelectedTable" + eventTreeIsFiltering string = "IsFiltering" +) + +// Results table menu items +const ( + menuRecords string = "Records" + menuColumns string = "Columns" + menuConstraints string = "Constraints" + menuForeignKeys string = "Foreign Keys" + menuIndexes string = "Indexes" +) + +// Actions +const ( + actionNewConnection string = "NewConnection" + actionEditConnection string = "EditConnection" +) + +// Misc (until i find a better name) const ( - EditorTabName string = "Editor" - HelpPageName string = "Help" + focusedWrapperLeft string = "left" + focusedWrapperRight string = "right" + + colorTableChange = tcell.ColorOrange + colorTableInsert = tcell.ColorDarkGreen + colorTableDelete = tcell.ColorRed ) diff --git a/drivers/constants.go b/drivers/constants.go index 5f46aaa..31d8d6f 100644 --- a/drivers/constants.go +++ b/drivers/constants.go @@ -3,3 +3,10 @@ package drivers const ( DefaultRowLimit = 300 ) + +// Drivers +const ( + DriverMySQL string = "mysql" + DriverPostgres string = "postgres" + DriverSqlite string = "sqlite3" +) diff --git a/drivers/mysql.go b/drivers/mysql.go index 4283057..1f0dd8e 100644 --- a/drivers/mysql.go +++ b/drivers/mysql.go @@ -21,7 +21,7 @@ func (db *MySQL) TestConnection(urlstr string) (err error) { } func (db *MySQL) Connect(urlstr string) (err error) { - db.SetProvider("mysql") + db.SetProvider(DriverMySQL) db.Connection, err = dburl.Open(urlstr) if err != nil { diff --git a/drivers/postgres.go b/drivers/postgres.go index d404b39..4cdb131 100644 --- a/drivers/postgres.go +++ b/drivers/postgres.go @@ -31,7 +31,7 @@ func (db *Postgres) TestConnection(urlstr string) error { } func (db *Postgres) Connect(urlstr string) (err error) { - db.SetProvider("postgres") + db.SetProvider(DriverPostgres) db.Connection, err = dburl.Open(urlstr) if err != nil { @@ -196,6 +196,10 @@ func (db *Postgres) GetTableColumns(database, table string) (results [][]string, results = append(results, row) } + if err := rows.Err(); err != nil { + return nil, err + } + return } diff --git a/drivers/sqlite.go b/drivers/sqlite.go index 2c67d51..b3264f0 100644 --- a/drivers/sqlite.go +++ b/drivers/sqlite.go @@ -23,7 +23,7 @@ func (db *SQLite) TestConnection(urlstr string) (err error) { } func (db *SQLite) Connect(urlstr string) (err error) { - db.SetProvider("sqlite3") + db.SetProvider(DriverSqlite) db.Connection, err = sql.Open("sqlite", urlstr) if err != nil { diff --git a/drivers/utils_test.go b/drivers/utils_test.go index 8a7550f..847f486 100644 --- a/drivers/utils_test.go +++ b/drivers/utils_test.go @@ -6,15 +6,16 @@ import ( "testing" gomock "github.com/DATA-DOG/go-sqlmock" + "github.com/jorgerojas26/lazysql/models" ) func Test_queriesInTransaction(t *testing.T) { tests := []struct { - name string - queries []models.Query setMockExpectations func(mock gomock.Sqlmock) assertErr func(t *testing.T, err error) + name string + queries []models.Query }{ { name: "successful transaction", @@ -38,6 +39,7 @@ func Test_queriesInTransaction(t *testing.T) { mock.ExpectCommit().WillReturnError(errors.New("commit error")) }, assertErr: func(t *testing.T, err error) { + t.Helper() if !strings.Contains(err.Error(), "commit error") { t.Errorf("expected error to contain 'commit error', got %v", err) } @@ -54,6 +56,7 @@ func Test_queriesInTransaction(t *testing.T) { mock.ExpectRollback() }, assertErr: func(t *testing.T, err error) { + t.Helper() if !strings.Contains(err.Error(), "query error") { t.Errorf("expected error to contain 'commit error', got %v", err) } @@ -73,6 +76,7 @@ func Test_queriesInTransaction(t *testing.T) { mock.ExpectRollback() }, assertErr: func(t *testing.T, err error) { + t.Helper() if !strings.Contains(err.Error(), "query error") { t.Errorf("expected error to contain 'commit error', got %v", err) } @@ -89,6 +93,7 @@ func Test_queriesInTransaction(t *testing.T) { mock.ExpectRollback().WillReturnError(errors.New("rollback error")) }, assertErr: func(t *testing.T, err error) { + t.Helper() errMsg := err.Error() if !strings.Contains(errMsg, "query error") { t.Errorf("expected error to contain 'commit error', got %v", err) diff --git a/models/models.go b/models/models.go index e8eb5ea..e0af427 100644 --- a/models/models.go +++ b/models/models.go @@ -37,9 +37,11 @@ const ( ) type CellValue struct { - Type CellValueType - Column string - Value interface{} + Value interface{} + Column string + TableColumnIndex int + TableRowIndex int + Type CellValueType } const ( @@ -49,12 +51,12 @@ const ( ) type DbDmlChange struct { - Type DmlType Database string Table string - Values []CellValue PrimaryKeyColumnName string PrimaryKeyValue string + Values []CellValue + Type DmlType } type DatabaseTableColumn struct { @@ -70,3 +72,8 @@ type Query struct { Query string Args []interface{} } + +type SidebarEditingCommitParams struct { + ColumnName string + NewValue string +}