Add shortcut to delete entries in the TUI to fix #33

This commit is contained in:
David Dworken 2022-12-18 22:02:29 -08:00
parent 554d518caa
commit 9322614a91
No known key found for this signature in database
7 changed files with 172 additions and 9 deletions

View File

@ -1905,6 +1905,40 @@ func TestTui(t *testing.T) {
}) })
out = strings.TrimSpace(strings.Split(out, "hishtory tquery")[1]) out = strings.TrimSpace(strings.Split(out, "hishtory tquery")[1])
compareGoldens(t, out, "TestTui-Resize") compareGoldens(t, out, "TestTui-Resize")
// Check that we can delete an entry
out = captureTerminalOutput(t, tester, []string{
"hishtory SPACE tquery ENTER",
"aaaaaa",
"C-K",
})
out = strings.TrimSpace(strings.Split(out, "hishtory tquery")[1])
compareGoldens(t, out, "TestTui-Delete")
// And that it stays deleted
out = captureTerminalOutput(t, tester, []string{
"hishtory SPACE tquery ENTER",
})
out = strings.TrimSpace(strings.Split(out, "hishtory tquery")[1])
compareGoldens(t, out, "TestTui-DeleteStill")
// And that we can then delete another entry
out = captureTerminalOutput(t, tester, []string{
"hishtory SPACE tquery ENTER",
"C-K",
})
out = strings.TrimSpace(strings.Split(out, "hishtory tquery")[1])
compareGoldens(t, out, "TestTui-DeleteAgain")
// And that it stays deleted
out = captureTerminalOutput(t, tester, []string{
"hishtory SPACE tquery ENTER",
})
out = strings.TrimSpace(strings.Split(out, "hishtory tquery")[1])
compareGoldens(t, out, "TestTui-DeleteAgainStill")
// Assert there are no leaked connections
assertNoLeakedConnections(t)
} }
func captureTerminalOutput(t *testing.T, tester shellTester, commands []string) string { func captureTerminalOutput(t *testing.T, tester shellTester, commands []string) string {

View File

@ -0,0 +1,26 @@
Search Query: > aaaaaa
┌───────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────┐
│ Hostname CWD Timestamp Runtime Exit Code Command │
│───────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────│
│ │
│ │
│ │
│ │
│ │
│ │
│ │
│ │
│ │
│ │
│ │
│ │
│ │
│ │
│ │
│ │
│ │
│ │
│ │
│ │
└───────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────┘

View File

@ -0,0 +1,26 @@
Search Query: > ls
┌──────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────┐
│ Hostname CWD Timestamp Runtime Exit Code Command │
│──────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────│
│ localhost /tmp/ Oct 17 2022 21:43:16 PDT 3s 2 ls ~/ │
│ │
│ │
│ │
│ │
│ │
│ │
│ │
│ │
│ │
│ │
│ │
│ │
│ │
│ │
│ │
│ │
│ │
│ │
│ │
└──────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────┘

View File

@ -0,0 +1,26 @@
Search Query: > ls
┌───────────────────────────────────────────────────────────────────────────────────────────┐
│ Hostname CWD Timestamp Runtime Exit Code Command │
│───────────────────────────────────────────────────────────────────────────────────────────│
│ localhost /tmp/ Oct 17 2022 21:43:16 PDT 3s 2 ls ~/ │
│ │
│ │
│ │
│ │
│ │
│ │
│ │
│ │
│ │
│ │
│ │
│ │
│ │
│ │
│ │
│ │
│ │
│ │
│ │
└───────────────────────────────────────────────────────────────────────────────────────────┘

View File

@ -0,0 +1,26 @@
Search Query: > ls
┌───────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────┐
│ Hostname CWD Timestamp Runtime Exit Code Command │
│───────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────│
│ localhost /tmp/ Oct 17 2022 21:43:26 PDT 3s 2 echo 'cccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccc… │
│ localhost /tmp/ Oct 17 2022 21:43:16 PDT 3s 2 ls ~/ │
│ │
│ │
│ │
│ │
│ │
│ │
│ │
│ │
│ │
│ │
│ │
│ │
│ │
│ │
│ │
│ │
│ │
│ │
└───────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────┘

View File

@ -1,4 +1,3 @@
/Users/david/.zshrc:source:22: no such file or directory: /usr/share/fzf/key-bindings.zsh
david@Davids-MacBook-Air hishtory % hishtory tquery david@Davids-MacBook-Air hishtory % hishtory tquery
david@Davids-MacBook-Air hishtory % david@Davids-MacBook-Air hishtory %

View File

@ -5,6 +5,7 @@ import (
"fmt" "fmt"
"os" "os"
"strings" "strings"
"time"
_ "embed" // for embedding config.sh _ "embed" // for embedding config.sh
@ -16,6 +17,7 @@ import (
"github.com/charmbracelet/lipgloss" "github.com/charmbracelet/lipgloss"
"github.com/ddworken/hishtory/client/data" "github.com/ddworken/hishtory/client/data"
"github.com/ddworken/hishtory/client/hctx" "github.com/ddworken/hishtory/client/hctx"
"github.com/ddworken/hishtory/shared"
"github.com/muesli/termenv" "github.com/muesli/termenv"
"golang.org/x/term" "golang.org/x/term"
) )
@ -96,7 +98,7 @@ func runQueryAndUpdateTable(m model, updateTable bool) model {
if m.runQuery == nil { if m.runQuery == nil {
m.runQuery = &m.lastQuery m.runQuery = &m.lastQuery
} }
rows, entries, _, err := getRows(m.ctx, hctx.GetConf(m.ctx).DisplayedColumns, *m.runQuery, PADDED_NUM_ENTRIES) rows, entries, err := getRows(m.ctx, hctx.GetConf(m.ctx).DisplayedColumns, *m.runQuery, PADDED_NUM_ENTRIES)
m.searchErr = err m.searchErr = err
if err != nil { if err != nil {
return m return m
@ -134,6 +136,14 @@ func (m model) Update(msg tea.Msg) (tea.Model, tea.Cmd) {
m.selected = true m.selected = true
} }
return m, tea.Quit return m, tea.Quit
case "ctrl+k":
err := deleteHistoryEntry(m.ctx, *m.tableEntries[m.table.Cursor()])
if err != nil {
m.fatalErr = err
return m, nil
}
m = runQueryAndUpdateTable(m, true)
return m, nil
default: default:
t, cmd1 := m.table.Update(msg) t, cmd1 := m.table.Update(msg)
m.table = t m.table = t
@ -207,12 +217,12 @@ func (m model) View() string {
return fmt.Sprintf("\n%s\n%s%s\nSearch Query: %s\n\n%s\n", loadingMessage, warning, m.banner, m.queryInput.View(), baseStyle.Render(m.table.View())) return fmt.Sprintf("\n%s\n%s%s\nSearch Query: %s\n\n%s\n", loadingMessage, warning, m.banner, m.queryInput.View(), baseStyle.Render(m.table.View()))
} }
func getRows(ctx *context.Context, columnNames []string, query string, numEntries int) ([]table.Row, []*data.HistoryEntry, int, error) { func getRows(ctx *context.Context, columnNames []string, query string, numEntries int) ([]table.Row, []*data.HistoryEntry, error) {
db := hctx.GetDb(ctx) db := hctx.GetDb(ctx)
config := hctx.GetConf(ctx) config := hctx.GetConf(ctx)
data, err := Search(ctx, db, query, numEntries) data, err := Search(ctx, db, query, numEntries)
if err != nil { if err != nil {
return nil, nil, 0, err return nil, nil, err
} }
var rows []table.Row var rows []table.Row
lastCommand := "" lastCommand := ""
@ -225,7 +235,7 @@ func getRows(ctx *context.Context, columnNames []string, query string, numEntrie
entry.Command = strings.ReplaceAll(entry.Command, "\n", "\\n") entry.Command = strings.ReplaceAll(entry.Command, "\n", "\\n")
row, err := buildTableRow(ctx, columnNames, *entry) row, err := buildTableRow(ctx, columnNames, *entry)
if err != nil { if err != nil {
return nil, nil, 0, fmt.Errorf("failed to build row for entry=%#v: %v", entry, err) return nil, nil, fmt.Errorf("failed to build row for entry=%#v: %v", entry, err)
} }
rows = append(rows, row) rows = append(rows, row)
lastCommand = entry.Command lastCommand = entry.Command
@ -233,7 +243,7 @@ func getRows(ctx *context.Context, columnNames []string, query string, numEntrie
rows = append(rows, table.Row{}) rows = append(rows, table.Row{})
} }
} }
return rows, data, len(data), nil return rows, data, nil
} }
func calculateColumnWidths(rows []table.Row, numColumns int) []int { func calculateColumnWidths(rows []table.Row, numColumns int) []int {
@ -255,7 +265,7 @@ var bigQueryResults []table.Row
func makeTableColumns(ctx *context.Context, columnNames []string, rows []table.Row) ([]table.Column, error) { func makeTableColumns(ctx *context.Context, columnNames []string, rows []table.Row) ([]table.Column, error) {
// Handle an initial query with no results // Handle an initial query with no results
if len(rows) == 0 || len(rows[0]) == 0 { if len(rows) == 0 || len(rows[0]) == 0 {
allRows, _, _, err := getRows(ctx, columnNames, "", 25) allRows, _, err := getRows(ctx, columnNames, "", 25)
if err != nil { if err != nil {
return nil, err return nil, err
} }
@ -281,7 +291,7 @@ func makeTableColumns(ctx *context.Context, columnNames []string, rows []table.R
// Calculate the maximum column width that is useful for each column if we search for the empty string // Calculate the maximum column width that is useful for each column if we search for the empty string
if bigQueryResults == nil { if bigQueryResults == nil {
bigRows, _, _, err := getRows(ctx, columnNames, "", 1000) bigRows, _, err := getRows(ctx, columnNames, "", 1000)
if err != nil { if err != nil {
return nil, err return nil, err
} }
@ -402,9 +412,25 @@ func makeTable(ctx *context.Context, rows []table.Row) (table.Model, error) {
return t, nil return t, nil
} }
func deleteHistoryEntry(ctx *context.Context, entry data.HistoryEntry) error {
db := hctx.GetDb(ctx)
// Delete locally
r := db.Model(&data.HistoryEntry{}).Where("device_id = ? AND end_time = ?", entry.DeviceId, entry.EndTime).Delete(&data.HistoryEntry{})
if r.Error != nil {
return r.Error
}
// Delete remotely
dr := shared.DeletionRequest{
UserId: data.UserId(hctx.GetConf(ctx).UserSecret),
SendTime: time.Now(),
}
dr.Messages.Ids = append(dr.Messages.Ids, shared.MessageIdentifier{Date: entry.EndTime, DeviceId: entry.DeviceId})
return SendDeletionRequest(dr)
}
func TuiQuery(ctx *context.Context, initialQuery string) error { func TuiQuery(ctx *context.Context, initialQuery string) error {
lipgloss.SetColorProfile(termenv.ANSI) lipgloss.SetColorProfile(termenv.ANSI)
rows, entries, _, err := getRows(ctx, hctx.GetConf(ctx).DisplayedColumns, initialQuery, PADDED_NUM_ENTRIES) rows, entries, err := getRows(ctx, hctx.GetConf(ctx).DisplayedColumns, initialQuery, PADDED_NUM_ENTRIES)
if err != nil { if err != nil {
if initialQuery != "" { if initialQuery != "" {
// initialQuery is likely invalid in some way, let's just drop it // initialQuery is likely invalid in some way, let's just drop it