mirror of
https://github.com/charmbracelet/glow.git
synced 2026-08-09 09:49:09 +02:00
Put filter results in a tab
This commit is contained in:
parent
84843e4f13
commit
03db9a6869
4 changed files with 100 additions and 64 deletions
|
|
@ -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 {
|
||||
|
|
|
|||
|
|
@ -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
|
||||
|
|
|
|||
109
ui/stash.go
109
ui/stash.go
|
|
@ -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
|
||||
}
|
||||
|
|
|
|||
14
ui/ui.go
14
ui/ui.go
|
|
@ -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 {
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue