mirror of
https://github.com/taigrr/teaqlite.git
synced 2026-04-02 04:59:03 -07:00
fixup cursor, blinking
This commit is contained in:
@@ -25,7 +25,7 @@ var rootCmd = &cobra.Command{
|
|||||||
Args: cobra.ExactArgs(1),
|
Args: cobra.ExactArgs(1),
|
||||||
RunE: func(cmd *cobra.Command, args []string) error {
|
RunE: func(cmd *cobra.Command, args []string) error {
|
||||||
dbPath = args[0]
|
dbPath = args[0]
|
||||||
|
|
||||||
if _, err := os.Stat(dbPath); os.IsNotExist(err) {
|
if _, err := os.Stat(dbPath); os.IsNotExist(err) {
|
||||||
return fmt.Errorf("database file '%s' does not exist", dbPath)
|
return fmt.Errorf("database file '%s' does not exist", dbPath)
|
||||||
}
|
}
|
||||||
@@ -56,4 +56,4 @@ func Execute() error {
|
|||||||
|
|
||||||
func init() {
|
func init() {
|
||||||
rootCmd.Flags().StringVarP(&dbPath, "database", "d", "", "Path to SQLite database file")
|
rootCmd.Flags().StringVarP(&dbPath, "database", "d", "", "Path to SQLite database file")
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -603,4 +603,3 @@ func (m *Model) getSharedData() *SharedData {
|
|||||||
return NewSharedData(m.db)
|
return NewSharedData(m.db)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -82,10 +82,16 @@ func (m *EditCellModel) View() string {
|
|||||||
}
|
}
|
||||||
|
|
||||||
content := TitleStyle.Render(fmt.Sprintf("Edit Cell: %s", columnName)) + "\n\n"
|
content := TitleStyle.Render(fmt.Sprintf("Edit Cell: %s", columnName)) + "\n\n"
|
||||||
content += fmt.Sprintf("Value: %s\n", m.value)
|
|
||||||
content += fmt.Sprintf("Cursor: %d\n\n", m.cursor)
|
// Display value with visible cursor
|
||||||
|
displayValue := m.value
|
||||||
|
if m.cursor <= len(displayValue) {
|
||||||
|
// Insert cursor character at cursor position
|
||||||
|
displayValue = displayValue[:m.cursor] + "_" + displayValue[m.cursor:]
|
||||||
|
}
|
||||||
|
|
||||||
|
content += fmt.Sprintf("Value: %s\n\n", displayValue)
|
||||||
content += HelpStyle.Render("enter: save • esc: cancel")
|
content += HelpStyle.Render("enter: save • esc: cancel")
|
||||||
|
|
||||||
return content
|
return content
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -105,9 +105,121 @@ func (m *QueryModel) handleResultsNavigation(msg tea.KeyMsg) (tea.Model, tea.Cmd
|
|||||||
return m, nil
|
return m, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func (m *QueryModel) ensureIDColumns(query string) string {
|
||||||
|
// Convert to lowercase for easier parsing
|
||||||
|
lowerQuery := strings.ToLower(strings.TrimSpace(query))
|
||||||
|
|
||||||
|
// Only modify SELECT statements
|
||||||
|
if !strings.HasPrefix(lowerQuery, "select") {
|
||||||
|
return query
|
||||||
|
}
|
||||||
|
|
||||||
|
// Extract table name from FROM clause
|
||||||
|
tableName := m.extractTableName(query)
|
||||||
|
if tableName == "" {
|
||||||
|
return query // Can't determine table, return original query
|
||||||
|
}
|
||||||
|
|
||||||
|
// Get primary key columns for this table
|
||||||
|
primaryKeys := m.getTablePrimaryKeys(tableName)
|
||||||
|
if len(primaryKeys) == 0 {
|
||||||
|
return query // No primary keys found
|
||||||
|
}
|
||||||
|
|
||||||
|
// Check if any primary key columns are already in the query
|
||||||
|
for _, pk := range primaryKeys {
|
||||||
|
if strings.Contains(lowerQuery, strings.ToLower(pk)) {
|
||||||
|
return query // Primary key already included
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Check if it's a SELECT * query
|
||||||
|
if strings.Contains(lowerQuery, "select *") {
|
||||||
|
return query // SELECT * already includes all columns
|
||||||
|
}
|
||||||
|
|
||||||
|
// Add primary key columns to the SELECT clause
|
||||||
|
selectIndex := strings.Index(lowerQuery, "select")
|
||||||
|
fromIndex := strings.Index(lowerQuery, "from")
|
||||||
|
|
||||||
|
if selectIndex == -1 || fromIndex == -1 || fromIndex <= selectIndex {
|
||||||
|
return query // Malformed query
|
||||||
|
}
|
||||||
|
|
||||||
|
// Extract the column list
|
||||||
|
selectClause := strings.TrimSpace(query[selectIndex+6 : fromIndex])
|
||||||
|
|
||||||
|
// Add primary keys to the beginning
|
||||||
|
var pkList []string
|
||||||
|
for _, pk := range primaryKeys {
|
||||||
|
pkList = append(pkList, pk)
|
||||||
|
}
|
||||||
|
|
||||||
|
newSelectClause := strings.Join(pkList, ", ") + ", " + selectClause
|
||||||
|
|
||||||
|
// Reconstruct the query
|
||||||
|
return "SELECT " + newSelectClause + " " + query[fromIndex:]
|
||||||
|
}
|
||||||
|
|
||||||
|
func (m *QueryModel) extractTableName(query string) string {
|
||||||
|
lowerQuery := strings.ToLower(query)
|
||||||
|
|
||||||
|
// Find FROM keyword
|
||||||
|
fromIndex := strings.Index(lowerQuery, "from")
|
||||||
|
if fromIndex == -1 {
|
||||||
|
return ""
|
||||||
|
}
|
||||||
|
|
||||||
|
// Extract everything after FROM
|
||||||
|
afterFrom := strings.TrimSpace(query[fromIndex+4:])
|
||||||
|
|
||||||
|
// Split by whitespace and take the first word (table name)
|
||||||
|
parts := strings.Fields(afterFrom)
|
||||||
|
if len(parts) == 0 {
|
||||||
|
return ""
|
||||||
|
}
|
||||||
|
|
||||||
|
// Remove any alias or additional clauses
|
||||||
|
tableName := parts[0]
|
||||||
|
|
||||||
|
// Remove quotes if present
|
||||||
|
tableName = strings.Trim(tableName, "\"'`")
|
||||||
|
|
||||||
|
return tableName
|
||||||
|
}
|
||||||
|
|
||||||
|
func (m *QueryModel) getTablePrimaryKeys(tableName string) []string {
|
||||||
|
rows, err := m.Shared.DB.Query(fmt.Sprintf("PRAGMA table_info(%s)", tableName))
|
||||||
|
if err != nil {
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
defer rows.Close()
|
||||||
|
|
||||||
|
var primaryKeys []string
|
||||||
|
for rows.Next() {
|
||||||
|
var cid int
|
||||||
|
var name, dataType string
|
||||||
|
var notNull, pk int
|
||||||
|
var defaultValue any
|
||||||
|
|
||||||
|
if err := rows.Scan(&cid, &name, &dataType, ¬Null, &defaultValue, &pk); err != nil {
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
|
||||||
|
if pk == 1 {
|
||||||
|
primaryKeys = append(primaryKeys, name)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return primaryKeys
|
||||||
|
}
|
||||||
|
|
||||||
func (m *QueryModel) executeQuery() tea.Cmd {
|
func (m *QueryModel) executeQuery() tea.Cmd {
|
||||||
return func() tea.Msg {
|
return func() tea.Msg {
|
||||||
rows, err := m.Shared.DB.Query(m.query)
|
// Modify query to always include ID columns if it's a SELECT statement
|
||||||
|
modifiedQuery := m.ensureIDColumns(m.query)
|
||||||
|
|
||||||
|
rows, err := m.Shared.DB.Query(modifiedQuery)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
m.err = err
|
m.err = err
|
||||||
return nil
|
return nil
|
||||||
@@ -194,11 +306,21 @@ func (m *QueryModel) View() string {
|
|||||||
content.WriteString(TitleStyle.Render(headerRow))
|
content.WriteString(TitleStyle.Render(headerRow))
|
||||||
content.WriteString("\n")
|
content.WriteString("\n")
|
||||||
|
|
||||||
// Data rows
|
// Data rows with scrolling
|
||||||
visibleCount := Max(1, m.Shared.Height-10)
|
visibleCount := Max(1, m.Shared.Height-10)
|
||||||
endIdx := Min(len(m.results), visibleCount)
|
startIdx := 0
|
||||||
|
|
||||||
for i := 0; i < endIdx; i++ {
|
// Adjust start index if selected row is out of view
|
||||||
|
if m.selectedRow >= visibleCount {
|
||||||
|
startIdx = m.selectedRow - visibleCount + 1
|
||||||
|
}
|
||||||
|
|
||||||
|
endIdx := Min(len(m.results), startIdx+visibleCount)
|
||||||
|
|
||||||
|
for i := range endIdx {
|
||||||
|
if i < startIdx {
|
||||||
|
continue
|
||||||
|
}
|
||||||
row := m.results[i]
|
row := m.results[i]
|
||||||
rowStr := ""
|
rowStr := ""
|
||||||
for j, cell := range row {
|
for j, cell := range row {
|
||||||
@@ -228,4 +350,3 @@ func (m *QueryModel) View() string {
|
|||||||
|
|
||||||
return content.String()
|
return content.String()
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -8,15 +8,17 @@ import (
|
|||||||
)
|
)
|
||||||
|
|
||||||
type RowDetailModel struct {
|
type RowDetailModel struct {
|
||||||
Shared *SharedData
|
Shared *SharedData
|
||||||
rowIndex int
|
rowIndex int
|
||||||
FromQuery bool
|
selectedCol int
|
||||||
|
FromQuery bool
|
||||||
}
|
}
|
||||||
|
|
||||||
func NewRowDetailModel(shared *SharedData, rowIndex int) *RowDetailModel {
|
func NewRowDetailModel(shared *SharedData, rowIndex int) *RowDetailModel {
|
||||||
return &RowDetailModel{
|
return &RowDetailModel{
|
||||||
Shared: shared,
|
Shared: shared,
|
||||||
rowIndex: rowIndex,
|
rowIndex: rowIndex,
|
||||||
|
selectedCol: 0,
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -35,11 +37,21 @@ func (m *RowDetailModel) Update(msg tea.Msg) (tea.Model, tea.Cmd) {
|
|||||||
return m, func() tea.Msg { return SwitchToTableDataMsg{TableIndex: m.Shared.SelectedTable} }
|
return m, func() tea.Msg { return SwitchToTableDataMsg{TableIndex: m.Shared.SelectedTable} }
|
||||||
|
|
||||||
case "e":
|
case "e":
|
||||||
if len(m.Shared.FilteredData) > m.rowIndex && len(m.Shared.Columns) > 0 {
|
if len(m.Shared.FilteredData) > m.rowIndex && len(m.Shared.Columns) > m.selectedCol {
|
||||||
return m, func() tea.Msg {
|
return m, func() tea.Msg {
|
||||||
return SwitchToEditCellMsg{RowIndex: m.rowIndex, ColIndex: 0}
|
return SwitchToEditCellMsg{RowIndex: m.rowIndex, ColIndex: m.selectedCol}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
case "up", "k":
|
||||||
|
if m.selectedCol > 0 {
|
||||||
|
m.selectedCol--
|
||||||
|
}
|
||||||
|
|
||||||
|
case "down", "j":
|
||||||
|
if m.selectedCol < len(m.Shared.Columns)-1 {
|
||||||
|
m.selectedCol++
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
return m, nil
|
return m, nil
|
||||||
@@ -56,16 +68,23 @@ func (m *RowDetailModel) View() string {
|
|||||||
return content.String()
|
return content.String()
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Show current row position
|
||||||
|
content.WriteString(fmt.Sprintf("Row %d of %d\n\n", m.rowIndex+1, len(m.Shared.FilteredData)))
|
||||||
|
|
||||||
row := m.Shared.FilteredData[m.rowIndex]
|
row := m.Shared.FilteredData[m.rowIndex]
|
||||||
for i, col := range m.Shared.Columns {
|
for i, col := range m.Shared.Columns {
|
||||||
if i < len(row) {
|
if i < len(row) {
|
||||||
content.WriteString(fmt.Sprintf("%s: %s\n", col, row[i]))
|
if i == m.selectedCol {
|
||||||
|
content.WriteString(SelectedStyle.Render(fmt.Sprintf("> %s: %s", col, row[i])))
|
||||||
|
} else {
|
||||||
|
content.WriteString(NormalStyle.Render(fmt.Sprintf(" %s: %s", col, row[i])))
|
||||||
|
}
|
||||||
|
content.WriteString("\n")
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
content.WriteString("\n")
|
content.WriteString("\n")
|
||||||
content.WriteString(HelpStyle.Render("e: edit • q: back"))
|
content.WriteString(HelpStyle.Render("↑/↓: navigate columns • e: edit • q: back"))
|
||||||
|
|
||||||
return content.String()
|
return content.String()
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -202,4 +202,3 @@ func (m *TableDataModel) View() string {
|
|||||||
|
|
||||||
return content.String()
|
return content.String()
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -195,4 +195,3 @@ func (m *TableListModel) View() string {
|
|||||||
|
|
||||||
return content.String()
|
return content.String()
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
Reference in New Issue
Block a user