Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
16 changes: 16 additions & 0 deletions internal/history/history.go
Original file line number Diff line number Diff line change
Expand Up @@ -98,3 +98,19 @@ func (h *History) Count(project string) (int, error) {
}
return n, nil
}

// Counts returns the number of Visit lines recorded for every Project in
// one read of History, for the Picker's preview. Malformed lines are
// skipped silently.
func (h *History) Counts() (map[string]int, error) {
visits, err := readVisits(h.path)
if err != nil {
return nil, fmt.Errorf("history: counts: %w", err)
}

counts := make(map[string]int, len(visits))
for _, v := range visits {
counts[v.Project]++
}
return counts, nil
}
36 changes: 36 additions & 0 deletions internal/history/history_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -138,6 +138,42 @@ func TestCount_PerProject(t *testing.T) {
}
}

func TestCounts_AllProjectsInOneRead(t *testing.T) {
dir := t.TempDir()
path := filepath.Join(dir, "history")

h, err := history.Open(path, 1000)
if err != nil {
t.Fatalf("Open: unexpected error: %v", err)
}

for range 3 {
if err := h.Record("tools/cdd"); err != nil {
t.Fatalf("Record: unexpected error: %v", err)
}
}
if err := h.Record("tools/other"); err != nil {
t.Fatalf("Record: unexpected error: %v", err)
}

counts, err := h.Counts()
if err != nil {
t.Fatalf("Counts: unexpected error: %v", err)
}
want := map[string]int{"tools/cdd": 3, "tools/other": 1}
if len(counts) != len(want) {
t.Fatalf("Counts: got %d Projects, want %d", len(counts), len(want))
}
for project, n := range want {
if counts[project] != n {
t.Errorf("Counts[%q] = %d, want %d", project, counts[project], n)
}
}
if counts["never/visited"] != 0 {
t.Errorf("Counts of an unvisited Project = %d, want 0", counts["never/visited"])
}
}

func TestReadVisits_SkipsMalformedLines(t *testing.T) {
dir := t.TempDir()
path := filepath.Join(dir, "history")
Expand Down
11 changes: 8 additions & 3 deletions internal/jump/jump.go
Original file line number Diff line number Diff line change
Expand Up @@ -42,7 +42,12 @@ func Resolve(ctx context.Context, cfg config.Config, hist *history.History, quer
return "", fmt.Errorf("jump: %w", err)
}

rel, abs, err := choose(cfg, projects, latest, query, pick)
counts, err := hist.Counts()
if err != nil {
return "", fmt.Errorf("jump: %w", err)
}

rel, abs, err := choose(cfg, projects, latest, counts, query, pick)
if err != nil {
return "", err
}
Expand All @@ -60,12 +65,12 @@ func Resolve(ctx context.Context, cfg config.Config, hist *history.History, quer

// choose picks a Project either via the exact-match shortcut or by running
// the Picker, and returns its Rel and absolute path.
func choose(cfg config.Config, projects []project.Project, latest []history.Visit, query string, pick PickFunc) (rel, abs string, err error) {
func choose(cfg config.Config, projects []project.Project, latest []history.Visit, counts map[string]int, query string, pick PickFunc) (rel, abs string, err error) {
if p, ok := exactMatch(projects, query); ok {
return p.Rel(), p.Abs(cfg.Root), nil
}

rows := order(projects, latest, cfg.Root)
rows := order(projects, latest, counts, cfg.Root)
status := func(c context.Context, dir string) git.Status {
s, _ := git.GetStatus(c, dir)
return s
Expand Down
6 changes: 4 additions & 2 deletions internal/jump/order.go
Original file line number Diff line number Diff line change
Expand Up @@ -16,9 +16,10 @@ import (
// Visits: Projects with a Visit come first, newest Visit first, ties break
// alphabetically by Rel; never-visited Projects follow, alphabetically. A
// Visit for a Project not present in projects (a Stale Visit) contributes
// no row. order is a pure function: it does not touch the filesystem or
// no row. counts supplies each Project's Visit total for the preview and
// may be nil. order is a pure function: it does not touch the filesystem or
// History itself.
func order(projects []project.Project, latest []history.Visit, root string) []picker.Row {
func order(projects []project.Project, latest []history.Visit, counts map[string]int, root string) []picker.Row {
lastVisit := make(map[string]time.Time, len(latest))
for _, v := range latest {
lastVisit[v.Project] = v.At
Expand Down Expand Up @@ -49,6 +50,7 @@ func order(projects []project.Project, latest []history.Visit, root string) []pi
Path: p.Abs(root),
},
LastVisit: lastVisit[p.Rel()],
Visits: counts[p.Rel()],
}
}
return rows
Expand Down
28 changes: 24 additions & 4 deletions internal/jump/order_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -27,7 +27,7 @@ func TestOrder_VisitedFirstNewestThenNeverVisited(t *testing.T) {
{Project: "oss/lib", At: time.Unix(1000, 0)},
}

got := order(projects, latest, "/root")
got := order(projects, latest, nil, "/root")

want := []string{
"work/api", // newest Visit
Expand All @@ -54,7 +54,7 @@ func TestOrder_TiesBreakAlphabetically(t *testing.T) {
{Project: "tools/alpha", At: tie},
}

got := order(projects, latest, "/root")
got := order(projects, latest, nil, "/root")

want := []string{"tools/alpha", "tools/zeta"}
assertRowOrder(t, got, want)
Expand All @@ -69,7 +69,7 @@ func TestOrder_NeverVisitedAlphabetical(t *testing.T) {
{Kind: "tools", Name: "alpha"},
}

got := order(projects, nil, "/root")
got := order(projects, nil, nil, "/root")

want := []string{"archive/old", "tools/alpha", "tools/zeta"}
assertRowOrder(t, got, want)
Expand All @@ -88,7 +88,7 @@ func TestOrder_StaleVisitContributesNoRow(t *testing.T) {
{Project: "gone/vanished", At: time.Unix(9000, 0)},
}

got := order(projects, latest, "/root")
got := order(projects, latest, nil, "/root")

if len(got) != 1 {
t.Fatalf("order: got %d rows, want 1", len(got))
Expand All @@ -113,3 +113,23 @@ func assertRowOrder(t *testing.T, got []picker.Row, want []string) {
}
}
}

// TestOrder_FillsVisitCounts checks that each row carries the Project's
// Visit total from counts, and zero for a Project counts does not know.
func TestOrder_FillsVisitCounts(t *testing.T) {
projects := []project.Project{
{Kind: "tools", Name: "cdd"},
{Kind: "tools", Name: "dotfiles"},
}
latest := []history.Visit{{Project: "tools/cdd", At: time.Unix(3000, 0)}}
counts := map[string]int{"tools/cdd": 4}

got := order(projects, latest, counts, "/root")

if got[0].Visits != 4 {
t.Errorf("tools/cdd Visits = %d, want 4", got[0].Visits)
}
if got[1].Visits != 0 {
t.Errorf("tools/dotfiles Visits = %d, want 0", got[1].Visits)
}
}