Put filter results in a tab

This commit is contained in:
Christian Rocha 2020-12-15 13:37:11 -05:00
commit 03db9a6869
4 changed files with 100 additions and 64 deletions

View file

@ -19,10 +19,16 @@ import (
type markdown struct {
markdownType DocType
// Local identifier. This allows us to precisely determine the stashed
// state of a markdown, regardless of whether it exists locally or on the
// network.
localID ksuid.KSUID
// Stash identifier. This exists so we can keep track of documents stashed
// in-session as they relate to their original, non-stashed counterparts.
// All documents have a stashID, however when a document is stashed that
// document inherits the stashID of the original.
stashID ksuid.KSUID
// Unique identifier. Unlike the stash identifier, this value should always
// be unique so we can confidently find it an operate on it (versus stashID,
// which could match both an original or stashed document).
uniqueID ksuid.KSUID
// Full path of a local markdown file. Only relevant to local documents and
// those that have been stashed in this session.
@ -36,10 +42,11 @@ type markdown struct {
charm.Markdown
}
func (m *markdown) generateLocalID() {
if m.localID.IsNil() {
m.localID = ksuid.New()
func (m *markdown) generateIDs() {
if m.stashID.IsNil() {
m.stashID = ksuid.New()
}
m.uniqueID = ksuid.New()
}
// Generate the value we're doing to filter against.
@ -55,9 +62,9 @@ func (m *markdown) buildFilterValue() {
m.filterValue = note
}
// sortAsLocal returns whether or not this markdown should be sorted as though
// shouldSortAsLocal returns whether or not this markdown should be sorted as though
// it's a local markdown document.
func (m markdown) sortAsLocal() bool {
func (m markdown) shouldSortAsLocal() bool {
return m.markdownType == LocalDoc || m.markdownType == ConvertedDoc
}
@ -67,8 +74,8 @@ type markdownsByLocalFirst []*markdown
func (m markdownsByLocalFirst) Len() int { return len(m) }
func (m markdownsByLocalFirst) Swap(i, j int) { m[i], m[j] = m[j], m[i] }
func (m markdownsByLocalFirst) Less(i, j int) bool {
iIsLocal := m[i].sortAsLocal()
jIsLocal := m[j].sortAsLocal()
iIsLocal := m[i].shouldSortAsLocal()
jIsLocal := m[j].shouldSortAsLocal()
// Local files (and files that used to be local) come first
if iIsLocal && !jIsLocal {
@ -89,10 +96,10 @@ func (m markdownsByLocalFirst) Less(i, j int) bool {
return m[i].CreatedAt.After(m[j].CreatedAt)
}
// If the timestamps also match, sort by local ID.
localIDs := []ksuid.KSUID{m[i].localID, m[j].localID}
ksuid.Sort(localIDs)
return localIDs[0] == m[i].localID
// If the times also match, sort by unqiue ID.
ids := []ksuid.KSUID{m[i].uniqueID, m[j].uniqueID}
ksuid.Sort(ids)
return ids[0] == m[i].uniqueID
}
func (m markdown) relativeTime() string {

View file

@ -242,7 +242,7 @@ func (m pagerModel) update(msg tea.Msg) (pagerModel, tea.Cmd) {
md := m.currentDocument
_, alreadyStashed := m.common.filesStashed[md.localID]
_, alreadyStashed := m.common.filesStashed[md.stashID]
if alreadyStashed {
cmds = append(cmds, m.showStatusMessage("Already stashed"))
break
@ -314,7 +314,7 @@ func (m pagerModel) update(msg tea.Msg) (pagerModel, tea.Cmd) {
}
case stashFailMsg:
delete(m.common.filesStashed, msg.markdown.localID)
delete(m.common.filesStashed, msg.markdown.stashID)
case statusMessageTimeoutMsg:
m.state = pagerStateBrowse

View file

@ -71,6 +71,7 @@ const (
localSection = iota
stashedSection
newsSection
filterSection
)
// section contains definitions and state information for displaying a tab and
@ -85,16 +86,24 @@ type section struct {
// map sections to their associated types.
var sections = map[sectionKey]section{
localSection: {
key: localSection,
docTypes: NewDocTypeSet(LocalDoc),
key: localSection,
docTypes: NewDocTypeSet(LocalDoc),
paginator: newStashPaginator(),
},
stashedSection: {
key: stashedSection,
docTypes: NewDocTypeSet(StashedDoc, ConvertedDoc),
key: stashedSection,
docTypes: NewDocTypeSet(StashedDoc, ConvertedDoc),
paginator: newStashPaginator(),
},
newsSection: {
key: newsSection,
docTypes: NewDocTypeSet(NewsDoc),
key: newsSection,
docTypes: NewDocTypeSet(NewsDoc),
paginator: newStashPaginator(),
},
filterSection: {
key: filterSection,
docTypes: DocTypeSet{},
paginator: newStashPaginator(),
},
}
@ -246,10 +255,17 @@ func (m *stashModel) resetFiltering() {
sort.Stable(markdownsByLocalFirst(m.markdowns))
m.filteredMarkdowns = nil
m.updatePagination()
if m.sections[len(m.sections)-1].key == filterSection {
m.sections = m.sections[:len(m.sections)-1]
}
if m.sectionIndex > len(m.sections)-1 {
m.sectionIndex = 0
}
}
// Is a filter currently being applied?
func (m stashModel) isFiltering() bool {
func (m stashModel) filterApplied() bool {
return m.filterState != unfiltered
}
@ -257,7 +273,7 @@ func (m stashModel) isFiltering() bool {
func (m stashModel) shouldUpdateFilter() bool {
// If we're in the middle of setting a note don't update the filter so that
// the focus won't jump around.
return m.isFiltering() && m.selectionState != selectionSettingNote
return m.filterApplied() && m.selectionState != selectionSettingNote
}
// Update pagination according to the amount of markdowns for the current
@ -305,11 +321,11 @@ func (m stashModel) selectedMarkdown() *markdown {
func (m *stashModel) addMarkdowns(mds ...*markdown) {
if len(mds) > 0 {
for _, md := range mds {
md.generateLocalID()
md.generateIDs()
}
m.markdowns = append(m.markdowns, mds...)
if !m.isFiltering() {
if !m.filterApplied() {
sort.Stable(markdownsByLocalFirst(m.markdowns))
}
m.updatePagination()
@ -323,7 +339,7 @@ func (m stashModel) countMarkdowns(t DocType) (found int) {
}
var mds []*markdown
if m.isFiltering() {
if m.filterState == filtering {
mds = m.getVisibleMarkdowns()
} else {
mds = m.markdowns
@ -359,7 +375,7 @@ func (m stashModel) getMarkdownByType(types ...DocType) []*markdown {
// Returns the markdowns that should be currently shown.
func (m stashModel) getVisibleMarkdowns() []*markdown {
if m.isFiltering() {
if m.filterState == filtering || m.currentSection().key == filterSection {
return m.filteredMarkdowns
}
@ -485,15 +501,6 @@ func newStashModel(common *commonModel) stashModel {
}
}
p := paginator.NewModel()
p.Type = paginator.Dots
p.ActiveDot = brightGrayFg("•")
p.InactiveDot = darkGrayFg("•")
for i := range s {
s[i].paginator = p
}
m := stashModel{
common: common,
spinner: sp,
@ -507,6 +514,14 @@ func newStashModel(common *commonModel) stashModel {
return m
}
func newStashPaginator() paginator.Model {
p := paginator.NewModel()
p.Type = paginator.Dots
p.ActiveDot = brightGrayFg("•")
p.InactiveDot = darkGrayFg("•")
return p
}
// UPDATE
func (m stashModel) update(msg tea.Msg) (stashModel, tea.Cmd) {
@ -558,7 +573,7 @@ func (m stashModel) update(msg tea.Msg) (stashModel, tea.Cmd) {
// If we're filtering build filter indexes immediately so any
// matching results will show up in the filter.
if m.isFiltering() {
if m.filterApplied() {
for _, md := range docs {
md.buildFilterValue()
}
@ -692,14 +707,15 @@ func (m *stashModel) handleDocumentBrowsing(msg tea.Msg) tea.Cmd {
m.paginator().Page = m.paginator().TotalPages - 1
m.setCursor(m.paginator().ItemsOnPage(numDocs) - 1)
// Clear filter (if applicable)
case "esc":
if m.isFiltering() {
if m.filterApplied() {
m.resetFiltering()
break
}
// Next section
case "tab":
if len(m.sections) == 0 || m.isFiltering() {
if len(m.sections) == 0 || m.filterState == filtering {
break
}
m.sectionIndex++
@ -708,8 +724,9 @@ func (m *stashModel) handleDocumentBrowsing(msg tea.Msg) tea.Cmd {
}
m.updatePagination()
// Previous section
case "shift+tab":
if len(m.sections) == 0 {
if len(m.sections) == 0 || m.filterState == filtering {
break
}
m.sectionIndex--
@ -781,21 +798,21 @@ func (m *stashModel) handleDocumentBrowsing(msg tea.Msg) tea.Cmd {
break
}
if _, alreadyStashed := m.common.filesStashed[md.localID]; alreadyStashed {
if _, alreadyStashed := m.common.filesStashed[md.stashID]; alreadyStashed {
cmds = append(cmds, m.newStatusMessage(alreadyStashedStatusMessage))
break
}
if !stashableDocTypes.Contains(md.markdownType) || md.localID.IsNil() {
if debug && md.localID.IsNil() {
if !stashableDocTypes.Contains(md.markdownType) || md.stashID.IsNil() {
if debug && md.stashID.IsNil() {
log.Printf("refusing to stash markdown; local ID path is nil: %#v", md)
}
break
}
// Checks passed; perform the stash
m.common.filesStashed[md.localID] = struct{}{}
m.common.filesStashing[md.localID] = struct{}{}
m.common.filesStashed[md.stashID] = struct{}{}
m.common.filesStashing[md.stashID] = struct{}{}
cmds = append(cmds, stashDocument(m.common.cc, *md))
if m.loadingDone() && !m.spinner.Visible() {
@ -881,11 +898,11 @@ func (m *stashModel) handleDeleteConfirmation(msg tea.Msg) tea.Cmd {
}
// Remove from the things-we-stashed-this-session set
delete(m.common.filesStashed, md.localID)
delete(m.common.filesStashed, md.stashID)
// Delete optimistically and remove the stashed item before
// we've received a success response.
if m.isFiltering() {
if m.filterApplied() {
mds, _ := deleteMarkdown(m.filteredMarkdowns, m.markdowns[i])
m.filteredMarkdowns = mds
}
@ -896,6 +913,10 @@ func (m *stashModel) handleDeleteConfirmation(msg tea.Msg) tea.Cmd {
m.selectionState = selectionIdle
m.updatePagination()
if len(m.filteredMarkdowns) == 0 {
m.resetFiltering()
}
return deleteStashedItem(m.common.cc, smd.ID)
// Any other key cancels deletion
@ -942,6 +963,12 @@ func (m *stashModel) handleFiltering(msg tea.Msg) tea.Cmd {
break
}
// Add new section if it's not present
if m.sections[len(m.sections)-1].key != filterSection {
m.sections = append(m.sections, sections[filterSection])
}
m.sectionIndex = len(m.sections) - 1
m.filterInput.Blur()
m.filterState = filterApplied
@ -1034,9 +1061,9 @@ func (m stashModel) view() string {
// Rules for the logo, filter and status message.
logoOrFilter := " "
if m.showStatusMessage && m.isFiltering() {
if m.showStatusMessage && m.filterState == filtering {
logoOrFilter += m.statusMessage.String()
} else if m.isFiltering() {
} else if m.filterState == filtering {
logoOrFilter += m.filterInput.View()
} else {
logoOrFilter += glowLogoView(" Glow ")
@ -1106,7 +1133,7 @@ func (m stashModel) headerView() string {
var sections []string
// Filter results
if m.isFiltering() {
if m.filterState == filtering {
if localCount+stashedCount+newsCount == 0 {
return grayFg("Nothing found.")
} else {
@ -1157,6 +1184,8 @@ func (m stashModel) headerView() string {
continue
}
s = fmt.Sprintf("%d news", newsCount)
case filterSection:
s = fmt.Sprintf("%d “%s”", len(m.filteredMarkdowns), m.filterInput.Value())
}
if m.sectionIndex == i && len(m.sections) > 1 {
@ -1178,7 +1207,7 @@ func (m stashModel) headerView() string {
func (m stashModel) populatedView() string {
mds := m.getVisibleMarkdowns()
if len(mds) == 0 && m.isFiltering() {
if len(mds) == 0 && m.filterApplied() {
return ""
}
@ -1261,7 +1290,7 @@ func loadRemoteMarkdown(cc *charm.Client, md *markdown) tea.Cmd {
note: md.Note,
}
}
newMD.localID = md.localID
newMD.stashID = md.stashID
return fetchedMarkdownMsg(newMD)
}
}
@ -1302,7 +1331,7 @@ func deleteStashedItem(cc *charm.Client, id int) tea.Cmd {
func filterMarkdowns(m stashModel) tea.Cmd {
return func() tea.Msg {
if m.filterInput.Value() == "" || !m.isFiltering() {
if m.filterInput.Value() == "" || !m.filterApplied() {
return filteredMarkdownMsg(m.getFilterableMarkdowns()) // return everything
}
@ -1356,7 +1385,7 @@ func deleteMarkdown(markdowns []*markdown, target *markdown) ([]*markdown, error
index := -1
for i, v := range markdowns {
if v.localID == target.localID {
if v.uniqueID == target.uniqueID {
index = i
break
}

View file

@ -249,7 +249,7 @@ func (m model) Update(msg tea.Msg) (tea.Model, tea.Cmd) {
case stateShowStash:
// Q quits if we're filtering, but we still send esc though.
if m.stash.isFiltering() {
if m.stash.filterApplied() {
if msg.String() == "q" {
return m, tea.Quit
}
@ -395,7 +395,7 @@ func (m model) Update(msg tea.Msg) (tea.Model, tea.Cmd) {
case foundLocalFileMsg:
newMd := localFileToMarkdown(m.common.cwd, gitcha.SearchResult(msg))
m.stash.addMarkdowns(newMd)
if m.stash.isFiltering() {
if m.stash.filterApplied() {
newMd.buildFilterValue()
}
if m.stash.shouldUpdateFilter() {
@ -407,17 +407,17 @@ func (m model) Update(msg tea.Msg) (tea.Model, tea.Cmd) {
// Common handling that should happen regardless of application state
md := markdown(msg)
m.stash.addMarkdowns(&md)
m.common.filesStashed[msg.localID] = struct{}{}
delete(m.common.filesStashing, md.localID)
m.common.filesStashed[msg.stashID] = struct{}{}
delete(m.common.filesStashing, md.stashID)
if m.stash.isFiltering() {
if m.stash.filterApplied() {
cmds = append(cmds, filterMarkdowns(m.stash))
}
case stashFailMsg:
// Common handling that should happen regardless of application state
delete(m.common.filesStashed, msg.markdown.localID)
delete(m.common.filesStashing, msg.markdown.localID)
delete(m.common.filesStashed, msg.markdown.stashID)
delete(m.common.filesStashing, msg.markdown.stashID)
case filteredMarkdownMsg:
if m.state == stateShowDocument {