adding exemptions for sql

This commit is contained in:
2025-07-11 23:05:05 -07:00
parent 28a7100d9a
commit c7c0675f47
2 changed files with 150 additions and 21 deletions

View File

@@ -64,12 +64,13 @@ go run main.go sample.db
### Cell Edit Mode ### Cell Edit Mode
- Type new value for the selected cell - Type new value for the selected cell
- **Text Wrapping**: Long values are automatically wrapped for better visibility
- `Enter`: Save changes to database - `Enter`: Save changes to database
- `Esc`: Cancel editing and return to row detail - `Esc`: Cancel editing and return to row detail
- `Backspace`: Delete characters - `Backspace`: Delete characters
### SQL Query Mode ### SQL Query Mode
- Type your SQL query - Type your SQL query (all keys including r, s, h, j, k, l work as input)
- `Enter`: Execute query - `Enter`: Execute query
- `Backspace`: Delete characters - `Backspace`: Delete characters
- `Esc`: Return to table list - `Esc`: Return to table list
@@ -84,14 +85,15 @@ go run main.go sample.db
5. **Data Search**: Search within table data across all columns 5. **Data Search**: Search within table data across all columns
6. **Row Detail Modal**: 2-column view showing Column | Value for selected row 6. **Row Detail Modal**: 2-column view showing Column | Value for selected row
7. **Cell Editing**: Live editing of individual cell values with database updates 7. **Cell Editing**: Live editing of individual cell values with database updates
8. **Primary Key Detection**: Uses primary keys for reliable row updates 8. **Text Wrapping**: Long values are automatically wrapped in edit and detail views
9. **Screen-Aware Display**: Content automatically fits terminal size 9. **Primary Key Detection**: Uses primary keys for reliable row updates
10. **SQL Query Execution**: Execute custom SQL queries and view results 10. **Screen-Aware Display**: Content automatically fits terminal size
11. **Error Handling**: Displays database errors gracefully 11. **SQL Query Execution**: Execute custom SQL queries and view results (all keys work as input)
12. **Responsive UI**: Clean, styled interface that adapts to terminal size 12. **Error Handling**: Displays database errors gracefully
13. **Column Information**: Shows column names and handles NULL values 13. **Responsive UI**: Clean, styled interface that adapts to terminal size
14. **Navigation**: Intuitive keyboard shortcuts for all operations 14. **Column Information**: Shows column names and handles NULL values
15. **Dynamic Column Width**: Columns adjust to terminal width 15. **Navigation**: Intuitive keyboard shortcuts for all operations
16. **Dynamic Column Width**: Columns adjust to terminal width
## Navigation Flow ## Navigation Flow

137
main.go
View File

@@ -546,6 +546,12 @@ func (m model) Update(msg tea.Msg) (tea.Model, tea.Cmd) {
if m.selectedCol > 0 { if m.selectedCol > 0 {
m.selectedCol-- m.selectedCol--
} }
case modeQuery:
// In query mode, these should be treated as input
if len(msg.String()) == 1 {
m.queryInput += msg.String()
m.query = m.queryInput
}
} }
case "down", "j": case "down", "j":
@@ -567,6 +573,12 @@ func (m model) Update(msg tea.Msg) (tea.Model, tea.Cmd) {
if m.selectedCol < len(m.columns)-1 { if m.selectedCol < len(m.columns)-1 {
m.selectedCol++ m.selectedCol++
} }
case modeQuery:
// In query mode, these should be treated as input
if len(msg.String()) == 1 {
m.queryInput += msg.String()
m.query = m.queryInput
}
} }
case "left", "h": case "left", "h":
@@ -584,6 +596,12 @@ func (m model) Update(msg tea.Msg) (tea.Model, tea.Cmd) {
visibleCount := m.getVisibleTableCount() visibleCount := m.getVisibleTableCount()
m.selectedTable = m.tableListPage * visibleCount m.selectedTable = m.tableListPage * visibleCount
} }
case modeQuery:
// In query mode, these should be treated as input
if len(msg.String()) == 1 {
m.queryInput += msg.String()
m.query = m.queryInput
}
} }
case "right", "l": case "right", "l":
@@ -606,6 +624,12 @@ func (m model) Update(msg tea.Msg) (tea.Model, tea.Cmd) {
m.selectedTable = len(m.filteredTables) - 1 m.selectedTable = len(m.filteredTables) - 1
} }
} }
case modeQuery:
// In query mode, these should be treated as input
if len(msg.String()) == 1 {
m.queryInput += msg.String()
m.query = m.queryInput
}
} }
case "s": case "s":
@@ -614,6 +638,10 @@ func (m model) Update(msg tea.Msg) (tea.Model, tea.Cmd) {
m.queryInput = "" m.queryInput = ""
m.query = "" m.query = ""
m.cursor = 0 m.cursor = 0
} else if m.mode == modeQuery {
// In query mode, 's' should be treated as input
m.queryInput += msg.String()
m.query = m.queryInput
} }
case "r": case "r":
@@ -624,6 +652,10 @@ func (m model) Update(msg tea.Msg) (tea.Model, tea.Cmd) {
m.loadTableData() m.loadTableData()
case modeRowDetail: case modeRowDetail:
m.loadTableData() m.loadTableData()
case modeQuery:
// In query mode, 'r' should be treated as input
m.queryInput += msg.String()
m.query = m.queryInput
} }
case "backspace": case "backspace":
@@ -824,10 +856,43 @@ func (m model) View() string {
col := m.columns[i] col := m.columns[i]
val := row[i] val := row[i]
dataRow := fmt.Sprintf("%-*s | %-*s", // For long values, show them wrapped on multiple lines
if len(val) > valueWidth {
// First line with column name
firstLine := fmt.Sprintf("%-*s | %-*s",
colWidth, truncateString(col, colWidth), colWidth, truncateString(col, colWidth),
valueWidth, truncateString(val, valueWidth)) valueWidth, truncateString(val, valueWidth))
if i == m.selectedCol {
content.WriteString(selectedStyle.Render(firstLine))
} else {
content.WriteString(normalStyle.Render(firstLine))
}
content.WriteString("\n")
// Additional lines for wrapped text (if there's space)
if len(val) > valueWidth && visibleRows > displayRows {
wrappedLines := wrapText(val, valueWidth)
for j, wrappedLine := range wrappedLines[1:] { // Skip first line already shown
if j >= 2 { // Limit to 3 total lines per field
break
}
continuationLine := fmt.Sprintf("%-*s | %-*s",
colWidth, "", valueWidth, wrappedLine)
if i == m.selectedCol {
content.WriteString(selectedStyle.Render(continuationLine))
} else {
content.WriteString(normalStyle.Render(continuationLine))
}
content.WriteString("\n")
}
}
} else {
// Normal single line
dataRow := fmt.Sprintf("%-*s | %-*s",
colWidth, truncateString(col, colWidth),
valueWidth, val)
if i == m.selectedCol { if i == m.selectedCol {
content.WriteString(selectedStyle.Render(dataRow)) content.WriteString(selectedStyle.Render(dataRow))
} else { } else {
@@ -836,6 +901,7 @@ func (m model) View() string {
content.WriteString("\n") content.WriteString("\n")
} }
} }
}
content.WriteString("\n") content.WriteString("\n")
content.WriteString(helpStyle.Render("↑/↓: select field • enter: edit • esc: back • q: quit")) content.WriteString(helpStyle.Render("↑/↓: select field • enter: edit • esc: back • q: quit"))
@@ -850,11 +916,30 @@ func (m model) View() string {
content.WriteString(titleStyle.Render(fmt.Sprintf("Edit: %s.%s", tableName, columnName))) content.WriteString(titleStyle.Render(fmt.Sprintf("Edit: %s.%s", tableName, columnName)))
content.WriteString("\n\n") content.WriteString("\n\n")
content.WriteString("Original: " + m.originalValue) // Calculate available width for text (leave some margin)
content.WriteString("\n") textWidth := max(20, m.width-4)
content.WriteString("New: " + m.editingValue + "_")
content.WriteString("\n\n")
// Wrap original value
content.WriteString("Original:")
content.WriteString("\n")
originalLines := wrapText(m.originalValue, textWidth)
for _, line := range originalLines {
content.WriteString(" " + line)
content.WriteString("\n")
}
content.WriteString("\n")
// Wrap new value
content.WriteString("New:")
content.WriteString("\n")
newLines := wrapText(m.editingValue+"_", textWidth) // Add cursor
for _, line := range newLines {
content.WriteString(" " + line)
content.WriteString("\n")
}
content.WriteString("\n")
content.WriteString(helpStyle.Render("Type new value • enter: save • esc: cancel")) content.WriteString(helpStyle.Render("Type new value • enter: save • esc: cancel"))
case modeQuery: case modeQuery:
@@ -938,6 +1023,48 @@ func truncateString(s string, maxLen int) string {
return s[:maxLen-3] + "..." return s[:maxLen-3] + "..."
} }
func wrapText(text string, width int) []string {
if width <= 0 {
return []string{text}
}
var lines []string
words := strings.Fields(text)
if len(words) == 0 {
return []string{text}
}
currentLine := ""
for _, word := range words {
// If adding this word would exceed the width
if len(currentLine)+len(word)+1 > width {
if currentLine != "" {
lines = append(lines, currentLine)
currentLine = word
} else {
// Word is longer than width, break it
for len(word) > width {
lines = append(lines, word[:width])
word = word[width:]
}
currentLine = word
}
} else {
if currentLine != "" {
currentLine += " " + word
} else {
currentLine = word
}
}
}
if currentLine != "" {
lines = append(lines, currentLine)
}
return lines
}
func min(a, b int) int { func min(a, b int) int {
if a < b { if a < b {
return a return a