Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

feat: support SHOW query results and Y to copy row #146

Open
wants to merge 8 commits into
base: main
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
13 changes: 12 additions & 1 deletion app/keymap.go
Original file line number Diff line number Diff line change
Expand Up @@ -45,6 +45,7 @@ const (
EditorGroup = "editor"
ConnectionGroup = "connection"
SidebarGroup = "sidebar"
CopyAsMenuGroup = "copyas"
)

// Define a global KeymapSystem object with default keybinds
Expand Down Expand Up @@ -83,6 +84,7 @@ var Keymaps = KeymapSystem{
Bind{Key: Key{Char: 'c'}, Cmd: cmd.TreeCollapseAll, Description: "Collapse all"},
Bind{Key: Key{Char: 'e'}, Cmd: cmd.ExpandAll, Description: "Expand all"},
Bind{Key: Key{Char: 'R'}, Cmd: cmd.Refresh, Description: "Refresh tree"},
Bind{Key: Key{Char: 'y'}, Cmd: cmd.Copy, Description: "Copy node name"},
},
TreeFilterGroup: {
Bind{Key: Key{Code: tcell.KeyEscape}, Cmd: cmd.UnfocusTreeFilter, Description: "Unfocus tree filter"},
Expand All @@ -96,7 +98,8 @@ var Keymaps = KeymapSystem{
Bind{Key: Key{Char: 'b'}, Cmd: cmd.GotoPrev, Description: "Go to previous cell"},
Bind{Key: Key{Char: '$'}, Cmd: cmd.GotoEnd, Description: "Go to last cell"},
Bind{Key: Key{Char: '0'}, Cmd: cmd.GotoStart, Description: "Go to first cell"},
Bind{Key: Key{Char: 'y'}, Cmd: cmd.Copy, Description: "Copy cell value to clipboard"},
Bind{Key: Key{Char: 'y'}, Cmd: cmd.Copy, Description: "Copy cell value"},
Bind{Key: Key{Char: 'Y'}, Cmd: cmd.CopyRow, Description: "Copy entire row"},
Bind{Key: Key{Char: 'o'}, Cmd: cmd.AppendNewRow, Description: "Append new row"},
Bind{Key: Key{Char: 'J'}, Cmd: cmd.SortDesc, Description: "Sort descending"},
Bind{Key: Key{Char: 'R'}, Cmd: cmd.Refresh, Description: "Refresh the current table"},
Expand All @@ -119,11 +122,13 @@ var Keymaps = KeymapSystem{
// Sidebar
Bind{Key: Key{Char: 'S'}, Cmd: cmd.ToggleSidebar, Description: "Toggle sidebar"},
Bind{Key: Key{Char: 's'}, Cmd: cmd.FocusSidebar, Description: "Focus sidebar"},
Bind{Key: Key{Code: tcell.KeyCtrlY}, Cmd: cmd.CopyAsMenu, Description: "Copy as menu"},
},
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"},
Bind{Key: Key{Code: tcell.KeyCtrlY}, Cmd: cmd.Copy, Description: "Copy editor text"},
},
SidebarGroup: {
Bind{Key: Key{Char: 's'}, Cmd: cmd.UnfocusSidebar, Description: "Focus table"},
Expand All @@ -138,5 +143,11 @@ var Keymaps = KeymapSystem{
Bind{Key: Key{Char: 'C'}, Cmd: cmd.SetValue, Description: "Toggle value menu to put values like NULL, EMPTY or DEFAULT"},
Bind{Key: Key{Char: 'y'}, Cmd: cmd.Copy, Description: "Copy value to clipboard"},
},
CopyAsMenuGroup: {
Bind{Key: Key{Char: 'i'}, Cmd: cmd.CopyRowAsInsert, Description: "Copy as INSERT"},
Bind{Key: Key{Char: 'u'}, Cmd: cmd.CopyRowAsUpdate, Description: "Copy as UPDATE"},
Bind{Key: Key{Char: 's'}, Cmd: cmd.CopyRowAsSelect, Description: "Copy as SELECT"},
Bind{Key: Key{Code: tcell.KeyEscape}, Cmd: cmd.Quit, Description: "Close menu"},
},
},
}
19 changes: 19 additions & 0 deletions commands/commands.go
Original file line number Diff line number Diff line change
Expand Up @@ -74,6 +74,15 @@ const (
TestConnection
EditConnection
DeleteConnection

// CopyRow Command
CopyRow
CopyRowAsInsert
CopyRowAsUpdate
CopyRowAsSelect

// CopyAsMenu Command
CopyAsMenu
)

func (c Command) String() string {
Expand Down Expand Up @@ -200,6 +209,16 @@ func (c Command) String() string {
return "CommitEdit"
case DiscardEdit:
return "DiscardEdit"
case CopyRow:
return "CopyRow"
case CopyRowAsInsert:
return "CopyRowAsInsert"
case CopyRowAsUpdate:
return "CopyRowAsUpdate"
case CopyRowAsSelect:
return "CopyRowAsSelect"
case CopyAsMenu:
return "CopyAsMenu"
}

return "Unknown"
Expand Down
4 changes: 4 additions & 0 deletions components/constants.go
Original file line number Diff line number Diff line change
Expand Up @@ -32,6 +32,9 @@ const (

// SetValueList
pageNameSetValue string = "SetValue"

// CopyAs
pageNameCopyAs = "copyAs"
)

// Tabs
Expand All @@ -49,6 +52,7 @@ const (

eventSQLEditorQuery string = "Query"
eventSQLEditorEscape string = "Escape"
eventSQLEditorError string = "Error"

eventResultsTableFiltering string = "FilteringResultsTable"

Expand Down
71 changes: 71 additions & 0 deletions components/copy_as_list.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,71 @@
package components

import (
"github.com/gdamore/tcell/v2"
"github.com/jorgerojas26/lazysql/app"
"github.com/jorgerojas26/lazysql/commands"
"github.com/rivo/tview"
)

type CopyAsList struct {
*tview.List
table *ResultsTable
}

func NewCopyAsList(table *ResultsTable) *CopyAsList {
list := &CopyAsList{
List: tview.NewList(),
table: table,
}

list.SetBorder(true)
list.SetTitle("Copy as...")
list.SetTitleColor(app.Styles.PrimaryTextColor)
list.SetBackgroundColor(app.Styles.PrimitiveBackgroundColor)

// Add options
list.AddItem("Copy as INSERT", "", 'i', nil)
list.AddItem("Copy as UPDATE", "", 'u', nil)
list.AddItem("Copy as SELECT", "", 's', nil)

list.SetSelectedFunc(func(index int, _ string, _ string, _ rune) {
list.Hide()
switch index {
case 0:
if err := table.copyRowAsSQL("INSERT"); err != nil {
table.SetError(err.Error(), nil)
}
case 1:
if err := table.copyRowAsSQL("UPDATE"); err != nil {
table.SetError(err.Error(), nil)
}
case 2:
if err := table.copyRowAsSQL("SELECT"); err != nil {
table.SetError(err.Error(), nil)
}
}
})

// Add shortcut key support
list.SetInputCapture(func(event *tcell.EventKey) *tcell.EventKey {
command := app.Keymaps.Group(app.CopyAsMenuGroup).Resolve(event)
if command == commands.Quit {
list.Hide()
return nil
}
return event
})

return list
}

func (list *CopyAsList) Show(x, y, width int) {
list.SetRect(x, y, width, 8) // Adjust height based on number of options
MainPages.AddPage(pageNameCopyAs, list, false, true)
App.SetFocus(list)
}

func (list *CopyAsList) Hide() {
MainPages.RemovePage(pageNameCopyAs)
App.SetFocus(list.table)
}
94 changes: 91 additions & 3 deletions components/results_table.go
Original file line number Diff line number Diff line change
Expand Up @@ -466,6 +466,29 @@ func (table *ResultsTable) tableInputCapture(event *tcell.EventKey) *tcell.Event
if table.GetShowSidebar() {
App.SetFocus(table.Sidebar)
}
} else if command == commands.CopyRow {
row, _ := table.GetSelection()
var rowData []string
// Get data from all columns
for col := 0; col < table.GetColumnCount(); col++ {
cell := table.GetCell(row, col)
rowData = append(rowData, cell.Text)
}

// Join data with tab character
clipboard := strings.Join(rowData, "\t")
err := lib.NewClipboard().Write(clipboard)
if err != nil {
table.SetError(err.Error(), nil)
}
} else if command == commands.CopyAsMenu {
// Get current selected cell position
row, col := table.GetSelection()
x, y, _, _ := table.GetRect()

// Show copy menu
copyAsList := NewCopyAsList(table)
copyAsList.Show(x+col*10, y+row, 30) // Adjust position and width
}

if len(table.GetRecords()) > 0 {
Expand Down Expand Up @@ -603,13 +626,13 @@ func (table *ResultsTable) subscribeToEditorChanges() {
case eventSQLEditorQuery:
query := stateChange.Value.(string)
if query != "" {
queryLower := strings.ToLower(query)

if strings.Contains(queryLower, "select") {
queryLower := strings.ToLower(strings.TrimSpace(query))
if strings.Contains(queryLower, "select") || strings.Contains(queryLower, "show") {
table.SetLoading(true)
App.Draw()

rows, err := table.DBDriver.ExecuteQuery(query)

table.Pagination.SetTotalRecords(len(rows))
table.Pagination.SetLimit(len(rows))

Expand Down Expand Up @@ -1369,3 +1392,68 @@ func (table *ResultsTable) UpdateSidebar() {

}
}

func (table *ResultsTable) copyRowAsSQL(format string) error {
row, _ := table.GetSelection()
var values []string
var columns []string
var whereConditions []string

// Get column names and values
for col := 0; col < table.GetColumnCount(); col++ {
header := table.GetCell(0, col).Text
cell := table.GetCell(row, col)
columns = append(columns, header)

// Handle value formatting
value := cell.Text
if value == "NULL" {
values = append(values, "NULL")
whereConditions = append(whereConditions, fmt.Sprintf("`%s` IS NULL", header))
} else {
values = append(values, fmt.Sprintf("'%s'", value))
whereConditions = append(whereConditions, fmt.Sprintf("`%s` = '%s'", header, value))
}
}

var sql string
switch format {
case "INSERT":
sql = fmt.Sprintf("INSERT INTO %s (%s) VALUES (%s);",
table.GetTitle(),
strings.Join(columns, ", "),
strings.Join(values, ", "))

case "UPDATE":
var sets []string
for i := range columns {
sets = append(sets, fmt.Sprintf("%s = %s", columns[i], values[i]))
}
// Assume first column is primary key
sql = fmt.Sprintf("UPDATE %s SET %s WHERE %s = %s;",
table.GetTitle(),
strings.Join(sets, ", "),
columns[0],
values[0])

case "SELECT":
sql = fmt.Sprintf("SELECT * FROM %s WHERE %s;",
table.GetTitle(),
strings.Join(whereConditions, " AND "))
}

clipboard := lib.NewClipboard()
return clipboard.Write(sql)
}
func (table *ResultsTable) HandleCommand(command commands.Command) {
switch command {
case commands.CopyAsMenu:
// Get current selected cell position
row, col := table.GetSelection()
x, y, _, _ := table.GetRect()

// Show copy menu
copyAsList := NewCopyAsList(table)
copyAsList.Show(x+col*10, y+row, 30) // Adjust position and width
}
}
9 changes: 9 additions & 0 deletions components/sql_editor.go
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,7 @@ import (

"github.com/jorgerojas26/lazysql/app"
"github.com/jorgerojas26/lazysql/commands"
"github.com/jorgerojas26/lazysql/lib"
"github.com/jorgerojas26/lazysql/models"
)

Expand Down Expand Up @@ -51,6 +52,14 @@ func NewSQLEditor() *SQLEditor {
text := openExternalEditor(sqlEditor)
sqlEditor.SetText(text, true)
}

case commands.Copy:
text := sqlEditor.GetText()
clipboard := lib.NewClipboard()
if err := clipboard.Write(text); err != nil {
sqlEditor.Publish(eventSQLEditorError, err.Error())
}
return nil
}

return event
Expand Down
14 changes: 14 additions & 0 deletions components/tree.go
Original file line number Diff line number Diff line change
Expand Up @@ -14,6 +14,7 @@ import (
"github.com/jorgerojas26/lazysql/commands"
"github.com/jorgerojas26/lazysql/drivers"
"github.com/jorgerojas26/lazysql/helpers/logger"
"github.com/jorgerojas26/lazysql/lib"
"github.com/jorgerojas26/lazysql/models"
)

Expand Down Expand Up @@ -176,6 +177,19 @@ func NewTree(dbName string, dbdriver drivers.Driver) *Tree {
tree.ExpandAll()
case commands.Refresh:
tree.Refresh(dbName)
case commands.Copy:
node := tree.GetCurrentNode()
if node == nil {
return event
}

nodeRef := node.GetReference().(string)
clipboard := lib.NewClipboard()
if err := clipboard.Write(nodeRef); err != nil {
// handle error
return event
}
return nil
}
return nil
})
Expand Down