Make the initial search query for the TUI async so that the UI opens immediately, so that user's can start typing their query without any delay

This commit is contained in:
David Dworken 2023-09-18 19:35:53 -07:00
parent 4f592f4aef
commit c9da7a10e4
No known key found for this signature in database

View File

@ -150,8 +150,8 @@ type model struct {
// Whether the TUI is quitting. // Whether the TUI is quitting.
quitting bool quitting bool
// The table used for displaying search results. // The table used for displaying search results. Nil if the initial search query hasn't returned yet.
table table.Model table *table.Model
// The entries in the table // The entries in the table
tableEntries []*data.HistoryEntry tableEntries []*data.HistoryEntry
// Whether the user has hit enter to select an entry and the TUI is thus about to quit. // Whether the user has hit enter to select an entry and the TUI is thus about to quit.
@ -188,7 +188,7 @@ type asyncQueryFinishedMsg struct {
maintainCursor bool maintainCursor bool
} }
func initialModel(ctx context.Context, t table.Model, tableEntries []*data.HistoryEntry, initialQuery string) model { func initialModel(ctx context.Context, initialQuery string) model {
s := spinner.New() s := spinner.New()
s.Spinner = spinner.Dot s.Spinner = spinner.Dot
s.Style = lipgloss.NewStyle().Foreground(lipgloss.Color("205")) s.Style = lipgloss.NewStyle().Foreground(lipgloss.Color("205"))
@ -206,7 +206,7 @@ func initialModel(ctx context.Context, t table.Model, tableEntries []*data.Histo
if initialQuery != "" { if initialQuery != "" {
queryInput.SetValue(initialQuery) queryInput.SetValue(initialQuery)
} }
return model{ctx: ctx, spinner: s, isLoading: true, table: t, tableEntries: tableEntries, runQuery: &initialQuery, queryInput: queryInput, help: help.New()} return model{ctx: ctx, spinner: s, isLoading: true, table: nil, tableEntries: []*data.HistoryEntry{}, runQuery: &initialQuery, queryInput: queryInput, help: help.New()}
} }
func (m model) Init() tea.Cmd { func (m model) Init() tea.Cmd {
@ -222,14 +222,17 @@ func updateTable(m model, rows []table.Row, entries []*data.HistoryEntry, search
return m return m
} }
m.tableEntries = entries m.tableEntries = entries
initialCursor := m.table.Cursor() initialCursor := 0
if m.table != nil {
initialCursor = m.table.Cursor()
}
if forceUpdateTable { if forceUpdateTable {
t, err := makeTable(m.ctx, rows) t, err := makeTable(m.ctx, rows)
if err != nil { if err != nil {
m.fatalErr = err m.fatalErr = err
return m return m
} }
m.table = t m.table = &t
} }
m.table.SetRows(rows) m.table.SetRows(rows)
if maintainCursor { if maintainCursor {
@ -247,10 +250,12 @@ func updateTable(m model, rows []table.Row, entries []*data.HistoryEntry, search
} }
func preventTableOverscrolling(m model) { func preventTableOverscrolling(m model) {
if m.table != nil {
if m.table.Cursor() >= len(m.tableEntries) { if m.table.Cursor() >= len(m.tableEntries) {
// Ensure that we can't scroll past the end of the table // Ensure that we can't scroll past the end of the table
m.table.SetCursor(len(m.tableEntries) - 1) m.table.SetCursor(len(m.tableEntries) - 1)
} }
}
} }
func runQueryAndUpdateTable(m model, forceUpdateTable, maintainCursor bool) tea.Cmd { func runQueryAndUpdateTable(m model, forceUpdateTable, maintainCursor bool) tea.Cmd {
@ -275,16 +280,19 @@ func (m model) Update(msg tea.Msg) (tea.Model, tea.Cmd) {
m.quitting = true m.quitting = true
return m, tea.Quit return m, tea.Quit
case key.Matches(msg, keys.SelectEntry): case key.Matches(msg, keys.SelectEntry):
if len(m.tableEntries) != 0 { if len(m.tableEntries) != 0 && m.table != nil {
m.selected = Selected m.selected = Selected
} }
return m, tea.Quit return m, tea.Quit
case key.Matches(msg, keys.SelectEntryAndChangeDir): case key.Matches(msg, keys.SelectEntryAndChangeDir):
if len(m.tableEntries) != 0 { if len(m.tableEntries) != 0 && m.table != nil {
m.selected = SelectedWithChangeDir m.selected = SelectedWithChangeDir
} }
return m, tea.Quit return m, tea.Quit
case key.Matches(msg, keys.DeleteEntry): case key.Matches(msg, keys.DeleteEntry):
if m.table == nil {
return m, nil
}
err := deleteHistoryEntry(m.ctx, *m.tableEntries[m.table.Cursor()]) err := deleteHistoryEntry(m.ctx, *m.tableEntries[m.table.Cursor()])
if err != nil { if err != nil {
m.fatalErr = err m.fatalErr = err
@ -297,18 +305,22 @@ func (m model) Update(msg tea.Msg) (tea.Model, tea.Cmd) {
m.help.ShowAll = !m.help.ShowAll m.help.ShowAll = !m.help.ShowAll
return m, nil return m, nil
default: default:
pendingCommands := tea.Batch()
if m.table != nil {
t, cmd1 := m.table.Update(msg) t, cmd1 := m.table.Update(msg)
m.table = t m.table = &t
if strings.HasPrefix(msg.String(), "alt+") { if strings.HasPrefix(msg.String(), "alt+") {
return m, tea.Batch(cmd1) return m, tea.Batch(cmd1)
} }
pendingCommands = tea.Batch(pendingCommands, cmd1)
}
i, cmd2 := m.queryInput.Update(msg) i, cmd2 := m.queryInput.Update(msg)
m.queryInput = i m.queryInput = i
searchQuery := m.queryInput.Value() searchQuery := m.queryInput.Value()
m.runQuery = &searchQuery m.runQuery = &searchQuery
cmd3 := runQueryAndUpdateTable(m, false, false) cmd3 := runQueryAndUpdateTable(m, false, false)
preventTableOverscrolling(m) preventTableOverscrolling(m)
return m, tea.Batch(cmd1, cmd2, cmd3) return m, tea.Batch(pendingCommands, cmd2, cmd3)
} }
case tea.WindowSizeMsg: case tea.WindowSizeMsg:
m.help.Width = msg.Width m.help.Width = msg.Width
@ -333,9 +345,13 @@ func (m model) Update(msg tea.Msg) (tea.Model, tea.Cmd) {
m.spinner, cmd = m.spinner.Update(msg) m.spinner, cmd = m.spinner.Update(msg)
return m, cmd return m, cmd
} else { } else {
m.table, cmd = m.table.Update(msg) if m.table != nil {
t, cmd := m.table.Update(msg)
m.table = &t
return m, cmd return m, cmd
} }
return m, nil
}
} }
} }
@ -367,7 +383,14 @@ func (m model) View() string {
} }
warning += "\n" warning += "\n"
helpView := m.help.View(keys) helpView := m.help.View(keys)
return fmt.Sprintf("\n%s%s%s\nSearch Query: %s\n\n%s\n", loadingMessage, warning, m.banner, m.queryInput.View(), baseStyle.Render(m.table.View())) + helpView return fmt.Sprintf("\n%s%s%s\nSearch Query: %s\n\n%s\n", loadingMessage, warning, m.banner, m.queryInput.View(), renderNullableTable(m)) + helpView
}
func renderNullableTable(m model) string {
if m.table == nil {
return strings.Repeat("\n", TABLE_HEIGHT+3)
}
return baseStyle.Render(m.table.View())
} }
func getRows(ctx context.Context, columnNames []string, query string, numEntries int) ([]table.Row, []*data.HistoryEntry, error) { func getRows(ctx context.Context, columnNames []string, query string, numEntries int) ([]table.Row, []*data.HistoryEntry, error) {
@ -580,20 +603,12 @@ func deleteHistoryEntry(ctx context.Context, entry data.HistoryEntry) error {
func TuiQuery(ctx context.Context, initialQuery string) error { func TuiQuery(ctx context.Context, initialQuery string) error {
lipgloss.SetColorProfile(termenv.ANSI) lipgloss.SetColorProfile(termenv.ANSI)
p := tea.NewProgram(initialModel(ctx, initialQuery), tea.WithOutput(os.Stderr))
// Async: Get the initial set of rows
go func() {
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 { p.Send(asyncQueryFinishedMsg{rows: rows, entries: entries, searchErr: err, forceUpdateTable: true, maintainCursor: false})
if initialQuery != "" { }()
// initialQuery is likely invalid in some way, let's just drop it
return TuiQuery(ctx, "")
}
// Something else has gone wrong, crash
return err
}
t, err := makeTable(ctx, rows)
if err != nil {
return err
}
p := tea.NewProgram(initialModel(ctx, t, entries, initialQuery), tea.WithOutput(os.Stderr))
// Async: Retrieve additional entries from the backend // Async: Retrieve additional entries from the backend
go func() { go func() {
err := lib.RetrieveAdditionalEntriesFromRemote(ctx) err := lib.RetrieveAdditionalEntriesFromRemote(ctx)
@ -622,7 +637,7 @@ func TuiQuery(ctx context.Context, initialQuery string) error {
p.Send(bannerMsg{banner: string(banner)}) p.Send(bannerMsg{banner: string(banner)})
}() }()
// Blocking: Start the TUI // Blocking: Start the TUI
_, err = p.Run() _, err := p.Run()
if err != nil { if err != nil {
return err return err
} }