mirror of
https://github.com/taigrr/teaqlite.git
synced 2026-04-02 04:59:03 -07:00
add ability to use editing modes
This commit is contained in:
32
README.md
32
README.md
@@ -63,16 +63,21 @@ go run main.go sample.db
|
||||
- `q` or `Ctrl+C`: Quit
|
||||
|
||||
### Cell Edit Mode
|
||||
- Type new value for the selected cell
|
||||
- **Readline-style Editing**: Full cursor control and advanced editing
|
||||
- **Cursor Movement**: `←/→` arrows, `Ctrl+←/→` for word navigation
|
||||
- **Line Navigation**: `Home`/`Ctrl+A` (start), `End`/`Ctrl+E` (end)
|
||||
- **Deletion**: `Backspace`, `Delete`/`Ctrl+D`, `Ctrl+W` (word), `Ctrl+K` (to end), `Ctrl+U` (to start)
|
||||
- **Text Wrapping**: Long values are automatically wrapped for better visibility
|
||||
- `Enter`: Save changes to database
|
||||
- `Esc`: Cancel editing and return to row detail
|
||||
- `Backspace`: Delete characters
|
||||
|
||||
### SQL Query Mode
|
||||
- Type your SQL query (all keys including r, s, h, j, k, l work as input)
|
||||
- **Advanced Text Editing**: Full readline-style editing controls
|
||||
- **Cursor Movement**: `←/→` arrows, `Ctrl+←/→` for word navigation
|
||||
- **Line Navigation**: `Home`/`Ctrl+A` (start), `End`/`Ctrl+E` (end)
|
||||
- **Deletion**: `Backspace`, `Delete`/`Ctrl+D`, `Ctrl+W` (word), `Ctrl+K` (to end), `Ctrl+U` (to start)
|
||||
- Type your SQL query (all keys work as input, no conflicts with navigation)
|
||||
- `Enter`: Execute query
|
||||
- `Backspace`: Delete characters
|
||||
- `Esc`: Return to table list
|
||||
- `q` or `Ctrl+C`: Quit
|
||||
|
||||
@@ -85,15 +90,16 @@ go run main.go sample.db
|
||||
5. **Data Search**: Search within table data across all columns
|
||||
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
|
||||
8. **Text Wrapping**: Long values are automatically wrapped in edit and detail views
|
||||
9. **Primary Key Detection**: Uses primary keys for reliable row updates
|
||||
10. **Screen-Aware Display**: Content automatically fits terminal size
|
||||
11. **SQL Query Execution**: Execute custom SQL queries and view results (all keys work as input)
|
||||
12. **Error Handling**: Displays database errors gracefully
|
||||
13. **Responsive UI**: Clean, styled interface that adapts to terminal size
|
||||
14. **Column Information**: Shows column names and handles NULL values
|
||||
15. **Navigation**: Intuitive keyboard shortcuts for all operations
|
||||
16. **Dynamic Column Width**: Columns adjust to terminal width
|
||||
8. **Readline-style Editing**: Full cursor control with word navigation, line navigation, and advanced deletion
|
||||
9. **Text Wrapping**: Long values are automatically wrapped in edit and detail views
|
||||
10. **Primary Key Detection**: Uses primary keys for reliable row updates
|
||||
11. **Screen-Aware Display**: Content automatically fits terminal size
|
||||
12. **SQL Query Execution**: Execute custom SQL queries with advanced text editing
|
||||
13. **Error Handling**: Displays database errors gracefully
|
||||
14. **Responsive UI**: Clean, styled interface that adapts to terminal size
|
||||
15. **Column Information**: Shows column names and handles NULL values
|
||||
16. **Navigation**: Intuitive keyboard shortcuts for all operations
|
||||
17. **Dynamic Column Width**: Columns adjust to terminal width
|
||||
|
||||
## Navigation Flow
|
||||
|
||||
|
||||
112
edit_cell.go
112
edit_cell.go
@@ -14,6 +14,7 @@ type editCellModel struct {
|
||||
colIndex int
|
||||
editingValue string
|
||||
originalValue string
|
||||
cursorPos int
|
||||
}
|
||||
|
||||
func newEditCellModel(shared *sharedData, rowIndex, colIndex int) *editCellModel {
|
||||
@@ -28,6 +29,7 @@ func newEditCellModel(shared *sharedData, rowIndex, colIndex int) *editCellModel
|
||||
colIndex: colIndex,
|
||||
editingValue: originalValue,
|
||||
originalValue: originalValue,
|
||||
cursorPos: len(originalValue), // Start cursor at end
|
||||
}
|
||||
}
|
||||
|
||||
@@ -59,19 +61,104 @@ func (m *editCellModel) handleInput(msg tea.KeyMsg) (subModel, tea.Cmd) {
|
||||
}
|
||||
}
|
||||
|
||||
case "backspace":
|
||||
if len(m.editingValue) > 0 {
|
||||
m.editingValue = m.editingValue[:len(m.editingValue)-1]
|
||||
// Cursor movement
|
||||
case "left":
|
||||
if m.cursorPos > 0 {
|
||||
m.cursorPos--
|
||||
}
|
||||
|
||||
case "right":
|
||||
if m.cursorPos < len(m.editingValue) {
|
||||
m.cursorPos++
|
||||
}
|
||||
|
||||
case "ctrl+left":
|
||||
m.cursorPos = m.wordLeft(m.cursorPos)
|
||||
|
||||
case "ctrl+right":
|
||||
m.cursorPos = m.wordRight(m.cursorPos)
|
||||
|
||||
case "home", "ctrl+a":
|
||||
m.cursorPos = 0
|
||||
|
||||
case "end", "ctrl+e":
|
||||
m.cursorPos = len(m.editingValue)
|
||||
|
||||
// Deletion
|
||||
case "backspace":
|
||||
if m.cursorPos > 0 {
|
||||
m.editingValue = m.editingValue[:m.cursorPos-1] + m.editingValue[m.cursorPos:]
|
||||
m.cursorPos--
|
||||
}
|
||||
|
||||
case "delete", "ctrl+d":
|
||||
if m.cursorPos < len(m.editingValue) {
|
||||
m.editingValue = m.editingValue[:m.cursorPos] + m.editingValue[m.cursorPos+1:]
|
||||
}
|
||||
|
||||
case "ctrl+w":
|
||||
// Delete word backward
|
||||
newPos := m.wordLeft(m.cursorPos)
|
||||
m.editingValue = m.editingValue[:newPos] + m.editingValue[m.cursorPos:]
|
||||
m.cursorPos = newPos
|
||||
|
||||
case "ctrl+k":
|
||||
// Delete from cursor to end of line
|
||||
m.editingValue = m.editingValue[:m.cursorPos]
|
||||
|
||||
case "ctrl+u":
|
||||
// Delete from beginning of line to cursor
|
||||
m.editingValue = m.editingValue[m.cursorPos:]
|
||||
m.cursorPos = 0
|
||||
|
||||
default:
|
||||
// Insert character at cursor position
|
||||
if len(msg.String()) == 1 {
|
||||
m.editingValue += msg.String()
|
||||
m.editingValue = m.editingValue[:m.cursorPos] + msg.String() + m.editingValue[m.cursorPos:]
|
||||
m.cursorPos++
|
||||
}
|
||||
}
|
||||
return m, nil
|
||||
}
|
||||
|
||||
// Helper functions for word navigation (same as query model)
|
||||
func (m *editCellModel) wordLeft(pos int) int {
|
||||
if pos == 0 {
|
||||
return 0
|
||||
}
|
||||
|
||||
// Skip whitespace
|
||||
for pos > 0 && isWhitespace(m.editingValue[pos-1]) {
|
||||
pos--
|
||||
}
|
||||
|
||||
// Skip non-whitespace
|
||||
for pos > 0 && !isWhitespace(m.editingValue[pos-1]) {
|
||||
pos--
|
||||
}
|
||||
|
||||
return pos
|
||||
}
|
||||
|
||||
func (m *editCellModel) wordRight(pos int) int {
|
||||
length := len(m.editingValue)
|
||||
if pos >= length {
|
||||
return length
|
||||
}
|
||||
|
||||
// Skip non-whitespace
|
||||
for pos < length && !isWhitespace(m.editingValue[pos]) {
|
||||
pos++
|
||||
}
|
||||
|
||||
// Skip whitespace
|
||||
for pos < length && isWhitespace(m.editingValue[pos]) {
|
||||
pos++
|
||||
}
|
||||
|
||||
return pos
|
||||
}
|
||||
|
||||
func (m *editCellModel) View() string {
|
||||
var content strings.Builder
|
||||
|
||||
@@ -98,17 +185,28 @@ func (m *editCellModel) View() string {
|
||||
|
||||
content.WriteString("\n")
|
||||
|
||||
// Wrap new value
|
||||
// Wrap new value with cursor
|
||||
content.WriteString("New:")
|
||||
content.WriteString("\n")
|
||||
newLines := wrapText(m.editingValue+"_", textWidth) // Add cursor
|
||||
|
||||
// Display editing value with cursor
|
||||
valueWithCursor := ""
|
||||
if m.cursorPos <= len(m.editingValue) {
|
||||
before := m.editingValue[:m.cursorPos]
|
||||
after := m.editingValue[m.cursorPos:]
|
||||
valueWithCursor = before + "█" + after
|
||||
} else {
|
||||
valueWithCursor = m.editingValue + "█"
|
||||
}
|
||||
|
||||
newLines := wrapText(valueWithCursor, textWidth)
|
||||
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("←/→: move cursor • ctrl+←/→: word nav • home/end: line nav • ctrl+w/k/u: delete • enter: save • esc: cancel"))
|
||||
|
||||
return content.String()
|
||||
}
|
||||
112
query.go
112
query.go
@@ -11,6 +11,7 @@ import (
|
||||
type queryModel struct {
|
||||
shared *sharedData
|
||||
queryInput string
|
||||
cursorPos int
|
||||
results [][]string
|
||||
columns []string
|
||||
}
|
||||
@@ -43,20 +44,108 @@ func (m *queryModel) handleInput(msg tea.KeyMsg) (subModel, tea.Cmd) {
|
||||
// Handle error - could set an error field
|
||||
}
|
||||
|
||||
case "backspace":
|
||||
if len(m.queryInput) > 0 {
|
||||
m.queryInput = m.queryInput[:len(m.queryInput)-1]
|
||||
// Cursor movement
|
||||
case "left":
|
||||
if m.cursorPos > 0 {
|
||||
m.cursorPos--
|
||||
}
|
||||
|
||||
case "right":
|
||||
if m.cursorPos < len(m.queryInput) {
|
||||
m.cursorPos++
|
||||
}
|
||||
|
||||
case "ctrl+left":
|
||||
m.cursorPos = m.wordLeft(m.cursorPos)
|
||||
|
||||
case "ctrl+right":
|
||||
m.cursorPos = m.wordRight(m.cursorPos)
|
||||
|
||||
case "home", "ctrl+a":
|
||||
m.cursorPos = 0
|
||||
|
||||
case "end", "ctrl+e":
|
||||
m.cursorPos = len(m.queryInput)
|
||||
|
||||
// Deletion
|
||||
case "backspace":
|
||||
if m.cursorPos > 0 {
|
||||
m.queryInput = m.queryInput[:m.cursorPos-1] + m.queryInput[m.cursorPos:]
|
||||
m.cursorPos--
|
||||
}
|
||||
|
||||
case "delete", "ctrl+d":
|
||||
if m.cursorPos < len(m.queryInput) {
|
||||
m.queryInput = m.queryInput[:m.cursorPos] + m.queryInput[m.cursorPos+1:]
|
||||
}
|
||||
|
||||
case "ctrl+w":
|
||||
// Delete word backward
|
||||
newPos := m.wordLeft(m.cursorPos)
|
||||
m.queryInput = m.queryInput[:newPos] + m.queryInput[m.cursorPos:]
|
||||
m.cursorPos = newPos
|
||||
|
||||
case "ctrl+k":
|
||||
// Delete from cursor to end of line
|
||||
m.queryInput = m.queryInput[:m.cursorPos]
|
||||
|
||||
case "ctrl+u":
|
||||
// Delete from beginning of line to cursor
|
||||
m.queryInput = m.queryInput[m.cursorPos:]
|
||||
m.cursorPos = 0
|
||||
|
||||
default:
|
||||
// In query mode, all single characters should be treated as input
|
||||
// Insert character at cursor position
|
||||
if len(msg.String()) == 1 {
|
||||
m.queryInput += msg.String()
|
||||
m.queryInput = m.queryInput[:m.cursorPos] + msg.String() + m.queryInput[m.cursorPos:]
|
||||
m.cursorPos++
|
||||
}
|
||||
}
|
||||
return m, nil
|
||||
}
|
||||
|
||||
// Helper functions for word navigation
|
||||
func (m *queryModel) wordLeft(pos int) int {
|
||||
if pos == 0 {
|
||||
return 0
|
||||
}
|
||||
|
||||
// Skip whitespace
|
||||
for pos > 0 && isWhitespace(m.queryInput[pos-1]) {
|
||||
pos--
|
||||
}
|
||||
|
||||
// Skip non-whitespace
|
||||
for pos > 0 && !isWhitespace(m.queryInput[pos-1]) {
|
||||
pos--
|
||||
}
|
||||
|
||||
return pos
|
||||
}
|
||||
|
||||
func (m *queryModel) wordRight(pos int) int {
|
||||
length := len(m.queryInput)
|
||||
if pos >= length {
|
||||
return length
|
||||
}
|
||||
|
||||
// Skip non-whitespace
|
||||
for pos < length && !isWhitespace(m.queryInput[pos]) {
|
||||
pos++
|
||||
}
|
||||
|
||||
// Skip whitespace
|
||||
for pos < length && isWhitespace(m.queryInput[pos]) {
|
||||
pos++
|
||||
}
|
||||
|
||||
return pos
|
||||
}
|
||||
|
||||
func isWhitespace(r byte) bool {
|
||||
return r == ' ' || r == '\t' || r == '\n' || r == '\r'
|
||||
}
|
||||
|
||||
func (m *queryModel) executeQuery() error {
|
||||
if strings.TrimSpace(m.queryInput) == "" {
|
||||
return nil
|
||||
@@ -113,9 +202,18 @@ func (m *queryModel) View() string {
|
||||
content.WriteString(titleStyle.Render("SQL Query"))
|
||||
content.WriteString("\n\n")
|
||||
|
||||
// Display query with cursor
|
||||
content.WriteString("Query: ")
|
||||
if m.cursorPos <= len(m.queryInput) {
|
||||
before := m.queryInput[:m.cursorPos]
|
||||
after := m.queryInput[m.cursorPos:]
|
||||
content.WriteString(before)
|
||||
content.WriteString("█") // Block cursor
|
||||
content.WriteString(after)
|
||||
} else {
|
||||
content.WriteString(m.queryInput)
|
||||
content.WriteString("_") // cursor
|
||||
content.WriteString("█")
|
||||
}
|
||||
content.WriteString("\n\n")
|
||||
|
||||
if len(m.results) > 0 {
|
||||
@@ -169,7 +267,7 @@ func (m *queryModel) View() string {
|
||||
}
|
||||
|
||||
content.WriteString("\n")
|
||||
content.WriteString(helpStyle.Render("enter: execute query • esc: back • q: quit"))
|
||||
content.WriteString(helpStyle.Render("enter: execute • ←/→: move cursor • ctrl+←/→: word nav • home/end: line nav • ctrl+w/k/u: delete • esc: back"))
|
||||
|
||||
return content.String()
|
||||
}
|
||||
Reference in New Issue
Block a user