Make dashboard updateable
This commit is contained in:
205
branch_completion.go
Normal file
205
branch_completion.go
Normal file
@@ -0,0 +1,205 @@
|
||||
package main
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"sort"
|
||||
"strings"
|
||||
"time"
|
||||
|
||||
"github.com/charmbracelet/x/ansi"
|
||||
)
|
||||
|
||||
type branchSuggestion struct {
|
||||
branch RepositoryBranch
|
||||
score int
|
||||
}
|
||||
|
||||
func rankBranchSuggestions(
|
||||
branches []RepositoryBranch,
|
||||
query, current string,
|
||||
now time.Time,
|
||||
) []branchSuggestion {
|
||||
query = strings.TrimSpace(strings.ToLower(query))
|
||||
current = strings.ToLower(current)
|
||||
suggestions := make([]branchSuggestion, 0, len(branches))
|
||||
for _, branch := range branches {
|
||||
name := strings.ToLower(branch.Name)
|
||||
matchScore := 0
|
||||
if query != "" {
|
||||
var matches bool
|
||||
matchScore, matches = fuzzyTermScore([]rune(name), []rune(query))
|
||||
if !matches {
|
||||
continue
|
||||
}
|
||||
switch {
|
||||
case name == query:
|
||||
matchScore += 50000
|
||||
case strings.HasPrefix(name, query):
|
||||
matchScore += 30000
|
||||
case branchSegmentHasPrefix(name, query):
|
||||
matchScore += 20000
|
||||
}
|
||||
}
|
||||
|
||||
score := matchScore * 10
|
||||
if branch.IsDefault {
|
||||
score += 9000
|
||||
}
|
||||
if name == current {
|
||||
score += 7000
|
||||
}
|
||||
switch name {
|
||||
case "main", "master":
|
||||
score += 3500
|
||||
case "develop", "development", "dev":
|
||||
score += 2500
|
||||
}
|
||||
if strings.HasPrefix(name, "release/") || strings.HasPrefix(name, "release-") {
|
||||
score += 1800
|
||||
}
|
||||
score += branchFreshnessScore(branch.UpdatedAt, now)
|
||||
suggestions = append(suggestions, branchSuggestion{branch: branch, score: score})
|
||||
}
|
||||
sort.SliceStable(suggestions, func(i, j int) bool {
|
||||
if suggestions[i].score != suggestions[j].score {
|
||||
return suggestions[i].score > suggestions[j].score
|
||||
}
|
||||
if !suggestions[i].branch.UpdatedAt.Equal(suggestions[j].branch.UpdatedAt) {
|
||||
return suggestions[i].branch.UpdatedAt.After(suggestions[j].branch.UpdatedAt)
|
||||
}
|
||||
return strings.ToLower(suggestions[i].branch.Name) <
|
||||
strings.ToLower(suggestions[j].branch.Name)
|
||||
})
|
||||
return suggestions
|
||||
}
|
||||
|
||||
func branchSegmentHasPrefix(name, query string) bool {
|
||||
for _, segment := range strings.FieldsFunc(name, func(value rune) bool {
|
||||
return strings.ContainsRune("/._-", value)
|
||||
}) {
|
||||
if strings.HasPrefix(segment, query) {
|
||||
return true
|
||||
}
|
||||
}
|
||||
return false
|
||||
}
|
||||
|
||||
func branchFreshnessScore(updatedAt, now time.Time) int {
|
||||
if updatedAt.IsZero() {
|
||||
return 0
|
||||
}
|
||||
age := now.Sub(updatedAt)
|
||||
if age < 0 {
|
||||
age = 0
|
||||
}
|
||||
days := int(age / (24 * time.Hour))
|
||||
return max(0, 3000-min(days, 3000))
|
||||
}
|
||||
|
||||
func branchAgeLabel(updatedAt, now time.Time) string {
|
||||
if updatedAt.IsZero() {
|
||||
return "age unknown"
|
||||
}
|
||||
age := now.Sub(updatedAt)
|
||||
if age < time.Hour {
|
||||
return "updated recently"
|
||||
}
|
||||
if age < 24*time.Hour {
|
||||
return fmt.Sprintf("updated %dh ago", int(age/time.Hour))
|
||||
}
|
||||
days := int(age / (24 * time.Hour))
|
||||
if days < 30 {
|
||||
return fmt.Sprintf("updated %dd ago", days)
|
||||
}
|
||||
months := days / 30
|
||||
if months < 24 {
|
||||
return fmt.Sprintf("updated %dmo ago", months)
|
||||
}
|
||||
return fmt.Sprintf("updated %dy ago", days/365)
|
||||
}
|
||||
|
||||
func (m App) branchSuggestions() []branchSuggestion {
|
||||
suggestions := rankBranchSuggestions(
|
||||
m.prEditBranches,
|
||||
m.prEditEditors[prEditBaseField].Text,
|
||||
m.prEditOriginal.BaseRef,
|
||||
time.Now(),
|
||||
)
|
||||
const maximumVisibleSuggestions = 6
|
||||
if len(suggestions) > maximumVisibleSuggestions {
|
||||
suggestions = suggestions[:maximumVisibleSuggestions]
|
||||
}
|
||||
return suggestions
|
||||
}
|
||||
|
||||
func (m *App) moveBranchSuggestion(delta int) {
|
||||
suggestions := m.branchSuggestions()
|
||||
if len(suggestions) == 0 {
|
||||
m.prEditBranchIndex = 0
|
||||
return
|
||||
}
|
||||
m.prEditBranchIndex = (m.prEditBranchIndex + delta + len(suggestions)) % len(suggestions)
|
||||
}
|
||||
|
||||
func (m *App) completeBranchSuggestion() bool {
|
||||
suggestions := m.branchSuggestions()
|
||||
if len(suggestions) == 0 {
|
||||
return false
|
||||
}
|
||||
index := clamp(m.prEditBranchIndex, 0, len(suggestions)-1)
|
||||
name := suggestions[index].branch.Name
|
||||
editor := &m.prEditEditors[prEditBaseField]
|
||||
if editor.Text == name {
|
||||
return false
|
||||
}
|
||||
editor.Text = name
|
||||
editor.Cursor = len([]rune(name))
|
||||
m.prEditBranchIndex = 0
|
||||
m.err = nil
|
||||
return true
|
||||
}
|
||||
|
||||
func (m App) branchCompletionLines(width int) []string {
|
||||
width = max(1, width)
|
||||
if m.prEditBranchesLoading {
|
||||
return []string{dimStyle.Render(" loading repository branches…")}
|
||||
}
|
||||
if m.prEditBranchesError != "" {
|
||||
message := " branch recommendations unavailable: " + m.prEditBranchesError
|
||||
wrapped := ansi.Hardwrap(ansi.Wordwrap(message, width, ""), width, false)
|
||||
var lines []string
|
||||
for _, line := range strings.Split(wrapped, "\n") {
|
||||
lines = append(lines, warnStyle.Render(line))
|
||||
}
|
||||
return lines
|
||||
}
|
||||
suggestions := m.branchSuggestions()
|
||||
if len(suggestions) == 0 {
|
||||
return []string{dimStyle.Render(" no matching repository branches")}
|
||||
}
|
||||
lines := []string{dimStyle.Render(" ctrl-p/n choose • tab/enter complete • tab again advances")}
|
||||
now := time.Now()
|
||||
for index, suggestion := range suggestions {
|
||||
prefix := " "
|
||||
if index == clamp(m.prEditBranchIndex, 0, len(suggestions)-1) {
|
||||
prefix = " ▶ "
|
||||
}
|
||||
suffix := branchAgeLabel(suggestion.branch.UpdatedAt, now)
|
||||
if suggestion.branch.IsDefault {
|
||||
suffix = "default • " + suffix
|
||||
}
|
||||
if suggestion.branch.Name == m.prEditOriginal.BaseRef {
|
||||
suffix = "current • " + suffix
|
||||
}
|
||||
available := max(1, width-len([]rune(prefix))-len([]rune(suffix))-2)
|
||||
name := ansi.Truncate(suggestion.branch.Name, available, "…")
|
||||
line := prefix + name + strings.Repeat(" ", max(1, available-ansi.StringWidth(name)+1)) + dimStyle.Render(suffix)
|
||||
if strings.HasPrefix(prefix, " ▶") {
|
||||
line = titleStyle.Render(prefix+name) +
|
||||
strings.Repeat(" ", max(1, available-ansi.StringWidth(name)+1)) +
|
||||
dimStyle.Render(suffix)
|
||||
}
|
||||
lines = append(lines, line)
|
||||
}
|
||||
return lines
|
||||
}
|
||||
Reference in New Issue
Block a user