package ui import ( "fmt" "strings" "github.com/gdamore/tcell/v2" "github.com/rivo/tview" "git.warky.dev/wdevs/relspecgo/pkg/models" ) // showTableList displays all tables across all schemas func (se *SchemaEditor) showTableList() { flex := tview.NewFlex().SetDirection(tview.FlexRow) // Title title := tview.NewTextView(). SetText("[::b]All Tables"). SetDynamicColors(true). SetTextAlign(tview.AlignCenter) // Create tables table tableTable := tview.NewTable().SetBorders(true).SetSelectable(true, false).SetFixed(1, 0) // Add header row with padding for full width headers := []string{"Name", "Schema", "Sequence", "Total Columns", "Total Relations", "Total Indexes", "Description", "Comment"} headerWidths := []int{18, 15, 12, 14, 15, 14, 0, 12} // Description gets remainder for i, header := range headers { padding := "" if i < len(headerWidths) && headerWidths[i] > 0 { padding = strings.Repeat(" ", headerWidths[i]-len(header)) } cell := tview.NewTableCell(header + padding). SetTextColor(tcell.ColorYellow). SetSelectable(false). SetAlign(tview.AlignLeft) tableTable.SetCell(0, i, cell) } var tables []*models.Table var tableLocations []struct{ schemaIdx, tableIdx int } for si, schema := range se.db.Schemas { for ti, table := range schema.Tables { tables = append(tables, table) tableLocations = append(tableLocations, struct{ schemaIdx, tableIdx int }{si, ti}) } } for row, table := range tables { tableIdx := tableLocations[row] schema := se.db.Schemas[tableIdx.schemaIdx] // Name - pad to 18 chars nameStr := fmt.Sprintf("%-18s", table.Name) nameCell := tview.NewTableCell(nameStr).SetSelectable(true) tableTable.SetCell(row+1, 0, nameCell) // Schema - pad to 15 chars schemaStr := fmt.Sprintf("%-15s", schema.Name) schemaCell := tview.NewTableCell(schemaStr).SetSelectable(true) tableTable.SetCell(row+1, 1, schemaCell) // Sequence - pad to 12 chars seqStr := fmt.Sprintf("%-12s", fmt.Sprintf("%d", table.Sequence)) seqCell := tview.NewTableCell(seqStr).SetSelectable(true) tableTable.SetCell(row+1, 2, seqCell) // Total Columns - pad to 14 chars colsStr := fmt.Sprintf("%-14s", fmt.Sprintf("%d", len(table.Columns))) colsCell := tview.NewTableCell(colsStr).SetSelectable(true) tableTable.SetCell(row+1, 3, colsCell) // Total Relations - pad to 15 chars relsStr := fmt.Sprintf("%-15s", fmt.Sprintf("%d", len(table.Relationships))) relsCell := tview.NewTableCell(relsStr).SetSelectable(true) tableTable.SetCell(row+1, 4, relsCell) // Total Indexes - pad to 14 chars idxStr := fmt.Sprintf("%-14s", fmt.Sprintf("%d", len(table.Indexes))) idxCell := tview.NewTableCell(idxStr).SetSelectable(true) tableTable.SetCell(row+1, 5, idxCell) // Description - no padding, takes remaining space descCell := tview.NewTableCell(table.Description).SetSelectable(true) tableTable.SetCell(row+1, 6, descCell) // Comment - pad to 12 chars commentStr := fmt.Sprintf("%-12s", table.Comment) commentCell := tview.NewTableCell(commentStr).SetSelectable(true) tableTable.SetCell(row+1, 7, commentCell) } tableTable.SetTitle(" All Tables ").SetBorder(true).SetTitleAlign(tview.AlignLeft) // Action buttons (define before input capture) btnFlex := tview.NewFlex() btnNewTable := tview.NewButton("New Table [n]").SetSelectedFunc(func() { se.showNewTableDialogFromList() }) btnBack := tview.NewButton("Back [b]").SetSelectedFunc(func() { se.pages.SwitchToPage("main") se.pages.RemovePage("tables") }) // Set up button input captures for Tab/Shift+Tab navigation btnNewTable.SetInputCapture(func(event *tcell.EventKey) *tcell.EventKey { if event.Key() == tcell.KeyBacktab { se.app.SetFocus(tableTable) return nil } if event.Key() == tcell.KeyTab { se.app.SetFocus(btnBack) return nil } return event }) btnBack.SetInputCapture(func(event *tcell.EventKey) *tcell.EventKey { if event.Key() == tcell.KeyBacktab { se.app.SetFocus(btnNewTable) return nil } if event.Key() == tcell.KeyTab { se.app.SetFocus(tableTable) return nil } return event }) btnFlex.AddItem(btnNewTable, 0, 1, true). AddItem(btnBack, 0, 1, false) tableTable.SetInputCapture(func(event *tcell.EventKey) *tcell.EventKey { if event.Key() == tcell.KeyEscape { se.pages.SwitchToPage("main") se.pages.RemovePage("tables") return nil } if event.Key() == tcell.KeyTab { se.app.SetFocus(btnNewTable) return nil } if event.Key() == tcell.KeyEnter { row, _ := tableTable.GetSelection() if row > 0 && row <= len(tables) { // Skip header row tableIdx := tableLocations[row-1] se.showTableEditor(tableIdx.schemaIdx, tableIdx.tableIdx, tables[row-1]) return nil } } if event.Rune() == 'n' { se.showNewTableDialogFromList() return nil } if event.Rune() == 'b' { se.pages.SwitchToPage("main") se.pages.RemovePage("tables") return nil } return event }) flex.AddItem(title, 1, 0, false). AddItem(tableTable, 0, 1, true). AddItem(btnFlex, 1, 0, false) se.pages.AddPage("tables", flex, true, true) } // showTableEditor shows editor for a specific table func (se *SchemaEditor) showTableEditor(schemaIndex, tableIndex int, table *models.Table) { flex := tview.NewFlex().SetDirection(tview.FlexRow) // Title title := tview.NewTextView(). SetText(fmt.Sprintf("[::b]Table: %s", table.Name)). SetDynamicColors(true). SetTextAlign(tview.AlignCenter) // Table info info := tview.NewTextView().SetDynamicColors(true) info.SetText(fmt.Sprintf("Schema: %s | Columns: %d | Description: %s", table.Schema, len(table.Columns), table.Description)) // Create columns table colTable := tview.NewTable().SetBorders(true).SetSelectable(true, false).SetFixed(1, 0) // Add header row with padding for full width headers := []string{"Name", "Type", "Default", "KeyType", "Description"} headerWidths := []int{20, 18, 15, 15} // Last column takes remaining space for i, header := range headers { padding := "" if i < len(headerWidths) { padding = strings.Repeat(" ", headerWidths[i]-len(header)) } cell := tview.NewTableCell(header + padding). SetTextColor(tcell.ColorYellow). SetSelectable(false). SetAlign(tview.AlignLeft) colTable.SetCell(0, i, cell) } // Get sorted column names columnNames := getColumnNames(table) for row, colName := range columnNames { column := table.Columns[colName] // Name - pad to 20 chars nameStr := fmt.Sprintf("%-20s", colName) nameCell := tview.NewTableCell(nameStr).SetSelectable(true) colTable.SetCell(row+1, 0, nameCell) // Type - pad to 18 chars typeStr := fmt.Sprintf("%-18s", column.Type) typeCell := tview.NewTableCell(typeStr).SetSelectable(true) colTable.SetCell(row+1, 1, typeCell) // Default - pad to 15 chars defaultStr := "" if column.Default != nil { defaultStr = fmt.Sprintf("%v", column.Default) } defaultStr = fmt.Sprintf("%-15s", defaultStr) defaultCell := tview.NewTableCell(defaultStr).SetSelectable(true) colTable.SetCell(row+1, 2, defaultCell) // KeyType - pad to 15 chars keyTypeStr := "" if column.IsPrimaryKey { keyTypeStr = "PRIMARY" } else if column.NotNull { keyTypeStr = "NOT NULL" } keyTypeStr = fmt.Sprintf("%-15s", keyTypeStr) keyTypeCell := tview.NewTableCell(keyTypeStr).SetSelectable(true) colTable.SetCell(row+1, 3, keyTypeCell) // Description descCell := tview.NewTableCell(column.Description).SetSelectable(true) colTable.SetCell(row+1, 4, descCell) } colTable.SetTitle(" Columns ").SetBorder(true).SetTitleAlign(tview.AlignLeft) // Action buttons flex (define before input capture) btnFlex := tview.NewFlex() btnNewCol := tview.NewButton("Add Column [n]").SetSelectedFunc(func() { se.showNewColumnDialog(schemaIndex, tableIndex) }) btnEditTable := tview.NewButton("Edit Table [e]").SetSelectedFunc(func() { se.showEditTableDialog(schemaIndex, tableIndex) }) btnEditColumn := tview.NewButton("Edit Column [c]").SetSelectedFunc(func() { row, _ := colTable.GetSelection() if row > 0 && row <= len(columnNames) { // Skip header row colName := columnNames[row-1] column := table.Columns[colName] se.showColumnEditor(schemaIndex, tableIndex, row-1, column) } }) btnDelTable := tview.NewButton("Delete Table [d]").SetSelectedFunc(func() { se.showDeleteTableConfirm(schemaIndex, tableIndex) }) btnBack := tview.NewButton("Back to Schema [b]").SetSelectedFunc(func() { se.pages.RemovePage("table-editor") se.pages.SwitchToPage("schema-editor") }) // Set up button input captures for Tab/Shift+Tab navigation btnNewCol.SetInputCapture(func(event *tcell.EventKey) *tcell.EventKey { if event.Key() == tcell.KeyBacktab { se.app.SetFocus(colTable) return nil } if event.Key() == tcell.KeyTab { se.app.SetFocus(btnEditColumn) return nil } return event }) btnEditColumn.SetInputCapture(func(event *tcell.EventKey) *tcell.EventKey { if event.Key() == tcell.KeyBacktab { se.app.SetFocus(btnNewCol) return nil } if event.Key() == tcell.KeyTab { se.app.SetFocus(btnEditTable) return nil } return event }) btnEditTable.SetInputCapture(func(event *tcell.EventKey) *tcell.EventKey { if event.Key() == tcell.KeyBacktab { se.app.SetFocus(btnEditColumn) return nil } if event.Key() == tcell.KeyTab { se.app.SetFocus(btnDelTable) return nil } return event }) btnDelTable.SetInputCapture(func(event *tcell.EventKey) *tcell.EventKey { if event.Key() == tcell.KeyBacktab { se.app.SetFocus(btnEditTable) return nil } if event.Key() == tcell.KeyTab { se.app.SetFocus(btnBack) return nil } return event }) btnBack.SetInputCapture(func(event *tcell.EventKey) *tcell.EventKey { if event.Key() == tcell.KeyBacktab { se.app.SetFocus(btnDelTable) return nil } if event.Key() == tcell.KeyTab { se.app.SetFocus(colTable) return nil } return event }) btnFlex.AddItem(btnNewCol, 0, 1, true). AddItem(btnEditColumn, 0, 1, false). AddItem(btnEditTable, 0, 1, false). AddItem(btnDelTable, 0, 1, false). AddItem(btnBack, 0, 1, false) colTable.SetInputCapture(func(event *tcell.EventKey) *tcell.EventKey { if event.Key() == tcell.KeyEscape { se.pages.SwitchToPage("schema-editor") se.pages.RemovePage("table-editor") return nil } if event.Key() == tcell.KeyTab { se.app.SetFocus(btnNewCol) return nil } if event.Key() == tcell.KeyEnter { row, _ := colTable.GetSelection() if row > 0 { // Skip header row colName := columnNames[row-1] column := table.Columns[colName] se.showColumnEditor(schemaIndex, tableIndex, row-1, column) return nil } } if event.Rune() == 'c' { row, _ := colTable.GetSelection() if row > 0 && row <= len(columnNames) { // Skip header row colName := columnNames[row-1] column := table.Columns[colName] se.showColumnEditor(schemaIndex, tableIndex, row-1, column) } return nil } if event.Rune() == 'b' { se.pages.RemovePage("table-editor") se.pages.SwitchToPage("schema-editor") return nil } return event }) flex.AddItem(title, 1, 0, false). AddItem(info, 2, 0, false). AddItem(colTable, 0, 1, true). AddItem(btnFlex, 1, 0, false) se.pages.AddPage("table-editor", flex, true, true) } // showNewTableDialog shows dialog to create a new table func (se *SchemaEditor) showNewTableDialog(schemaIndex int) { form := tview.NewForm() tableName := "" description := "" form.AddInputField("Table Name", "", 40, nil, func(value string) { tableName = value }) form.AddInputField("Description", "", 40, nil, func(value string) { description = value }) form.AddButton("Save", func() { if tableName == "" { return } se.CreateTable(schemaIndex, tableName, description) schema := se.db.Schemas[schemaIndex] se.pages.RemovePage("new-table") se.pages.RemovePage("schema-editor") se.showSchemaEditor(schemaIndex, schema) }) form.AddButton("Back", func() { schema := se.db.Schemas[schemaIndex] se.pages.RemovePage("new-table") se.pages.RemovePage("schema-editor") se.showSchemaEditor(schemaIndex, schema) }) form.SetBorder(true).SetTitle(" New Table ").SetTitleAlign(tview.AlignLeft) form.SetInputCapture(func(event *tcell.EventKey) *tcell.EventKey { if event.Key() == tcell.KeyEscape { se.showExitConfirmation("new-table", "schema-editor") return nil } return event }) se.pages.AddPage("new-table", form, true, true) } // showNewTableDialogFromList shows dialog to create a new table with schema selection func (se *SchemaEditor) showNewTableDialogFromList() { form := tview.NewForm() tableName := "" description := "" selectedSchemaIdx := 0 // Create schema dropdown options schemaOptions := make([]string, len(se.db.Schemas)) for i, schema := range se.db.Schemas { schemaOptions[i] = schema.Name } form.AddInputField("Table Name", "", 40, nil, func(value string) { tableName = value }) form.AddDropDown("Schema", schemaOptions, 0, func(option string, optionIndex int) { selectedSchemaIdx = optionIndex }) form.AddInputField("Description", "", 40, nil, func(value string) { description = value }) form.AddButton("Save", func() { if tableName == "" { return } se.CreateTable(selectedSchemaIdx, tableName, description) se.pages.RemovePage("new-table-from-list") se.pages.RemovePage("tables") se.showTableList() }) form.AddButton("Back", func() { se.pages.RemovePage("new-table-from-list") se.pages.RemovePage("tables") se.showTableList() }) form.SetBorder(true).SetTitle(" New Table ").SetTitleAlign(tview.AlignLeft) form.SetInputCapture(func(event *tcell.EventKey) *tcell.EventKey { if event.Key() == tcell.KeyEscape { se.showExitConfirmation("new-table-from-list", "tables") return nil } return event }) se.pages.AddPage("new-table-from-list", form, true, true) } // showEditTableDialog shows dialog to edit table properties func (se *SchemaEditor) showEditTableDialog(schemaIndex, tableIndex int) { table := se.db.Schemas[schemaIndex].Tables[tableIndex] form := tview.NewForm() // Local variables to collect changes newName := table.Name newDescription := table.Description form.AddInputField("Table Name", table.Name, 40, nil, func(value string) { newName = value }) form.AddTextArea("Description", table.Description, 40, 5, 0, func(value string) { newDescription = value }) form.AddButton("Save", func() { // Apply changes using dataops se.UpdateTable(schemaIndex, tableIndex, newName, newDescription) table := se.db.Schemas[schemaIndex].Tables[tableIndex] se.pages.RemovePage("edit-table") se.pages.RemovePage("table-editor") se.showTableEditor(schemaIndex, tableIndex, table) }) form.AddButton("Back", func() { // Discard changes - don't apply them table := se.db.Schemas[schemaIndex].Tables[tableIndex] se.pages.RemovePage("edit-table") se.pages.RemovePage("table-editor") se.showTableEditor(schemaIndex, tableIndex, table) }) form.SetBorder(true).SetTitle(" Edit Table ").SetTitleAlign(tview.AlignLeft) form.SetInputCapture(func(event *tcell.EventKey) *tcell.EventKey { if event.Key() == tcell.KeyEscape { se.showExitConfirmation("edit-table", "table-editor") return nil } return event }) se.pages.AddPage("edit-table", form, true, true) }