added side context panel and repo only search bar
This commit is contained in:
@@ -7,6 +7,8 @@ import (
|
||||
"os"
|
||||
"os/exec"
|
||||
"path/filepath"
|
||||
"sort"
|
||||
"strconv"
|
||||
"strings"
|
||||
)
|
||||
|
||||
@@ -508,3 +510,197 @@ func ArchiveStream(repoPath string, ref string, format string, w io.Writer) erro
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
// ── Language stats ─────────────────────────────────────────────────────────────
|
||||
|
||||
// LangStat holds the aggregate file-count and percentage for one language.
|
||||
type LangStat struct {
|
||||
Name string `json:"name"`
|
||||
Color string `json:"color"`
|
||||
Count int `json:"count"`
|
||||
Pct float64 `json:"pct"`
|
||||
}
|
||||
|
||||
// extLang maps file extensions to (display name, hex color).
|
||||
var extLang = map[string][2]string{
|
||||
".go": {"Go", "#00ADD8"},
|
||||
".ts": {"TypeScript", "#3178C6"},
|
||||
".tsx": {"TypeScript", "#3178C6"},
|
||||
".js": {"JavaScript", "#F7DF1E"},
|
||||
".jsx": {"JavaScript", "#F7DF1E"},
|
||||
".mjs": {"JavaScript", "#F7DF1E"},
|
||||
".py": {"Python", "#3572A5"},
|
||||
".rb": {"Ruby", "#CC342D"},
|
||||
".rs": {"Rust", "#DEA584"},
|
||||
".java": {"Java", "#B07219"},
|
||||
".cs": {"C#", "#178600"},
|
||||
".cpp": {"C++", "#F34B7D"},
|
||||
".cc": {"C++", "#F34B7D"},
|
||||
".c": {"C", "#555555"},
|
||||
".h": {"C", "#555555"},
|
||||
".swift": {"Swift", "#F05138"},
|
||||
".kt": {"Kotlin", "#A97BFF"},
|
||||
".php": {"PHP", "#4F5D95"},
|
||||
".html": {"HTML", "#E34C26"},
|
||||
".css": {"CSS", "#563D7C"},
|
||||
".scss": {"SCSS", "#C6538C"},
|
||||
".sql": {"SQL", "#e38c00"},
|
||||
".sh": {"Shell", "#89E051"},
|
||||
".bash": {"Shell", "#89E051"},
|
||||
".yaml": {"YAML", "#CB171E"},
|
||||
".yml": {"YAML", "#CB171E"},
|
||||
".json": {"JSON", "#292929"},
|
||||
".md": {"Markdown", "#083FA1"},
|
||||
".tf": {"HCL", "#844FBA"},
|
||||
".proto": {"Protobuf", "#5F5CE9"},
|
||||
".dart": {"Dart", "#00B4AB"},
|
||||
".lua": {"Lua", "#000080"},
|
||||
".r": {"R", "#198CE7"},
|
||||
}
|
||||
|
||||
// LanguageStats analyses the file tree at ref and returns language breakdown
|
||||
// sorted by file count descending. Languages under 3% are collapsed into "Other".
|
||||
func LanguageStats(repoPath, ref string) ([]LangStat, error) {
|
||||
if ref == "" {
|
||||
ref = "HEAD"
|
||||
}
|
||||
out, err := run(repoPath, "ls-tree", "-r", "--name-only", ref)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
counts := map[string]int{}
|
||||
total := 0
|
||||
for _, line := range strings.Split(strings.TrimSpace(string(out)), "\n") {
|
||||
line = strings.TrimSpace(line)
|
||||
if line == "" {
|
||||
continue
|
||||
}
|
||||
ext := strings.ToLower(filepath.Ext(line))
|
||||
lang, ok := extLang[ext]
|
||||
if ok {
|
||||
counts[lang[0]]++
|
||||
} else {
|
||||
counts["Other"]++
|
||||
}
|
||||
total++
|
||||
}
|
||||
if total == 0 {
|
||||
return []LangStat{}, nil
|
||||
}
|
||||
|
||||
// Aggregate by name with color lookup.
|
||||
colorOf := map[string]string{"Other": "#8B8B8B"}
|
||||
for _, v := range extLang {
|
||||
colorOf[v[0]] = v[1]
|
||||
}
|
||||
|
||||
var stats []LangStat
|
||||
otherCount := 0
|
||||
for name, count := range counts {
|
||||
pct := float64(count) / float64(total) * 100
|
||||
if name == "Other" || pct < 3.0 {
|
||||
otherCount += count
|
||||
continue
|
||||
}
|
||||
stats = append(stats, LangStat{Name: name, Color: colorOf[name], Count: count, Pct: pct})
|
||||
}
|
||||
sort.Slice(stats, func(i, j int) bool { return stats[i].Count > stats[j].Count })
|
||||
if len(stats) > 10 {
|
||||
for _, s := range stats[10:] {
|
||||
otherCount += s.Count
|
||||
}
|
||||
stats = stats[:10]
|
||||
}
|
||||
if otherCount > 0 {
|
||||
stats = append(stats, LangStat{
|
||||
Name: "Other",
|
||||
Color: "#8B8B8B",
|
||||
Count: otherCount,
|
||||
Pct: float64(otherCount) / float64(total) * 100,
|
||||
})
|
||||
}
|
||||
return stats, nil
|
||||
}
|
||||
|
||||
// ── Contributor stats ─────────────────────────────────────────────────────────
|
||||
|
||||
// Contributor holds a commit author name and their commit count.
|
||||
type Contributor struct {
|
||||
Name string `json:"name"`
|
||||
Commits int `json:"commits"`
|
||||
}
|
||||
|
||||
// Contributors returns the top limit commit authors sorted by commit count.
|
||||
// Uses git shortlog which is fast even on large repos.
|
||||
func Contributors(repoPath string, limit int) ([]Contributor, error) {
|
||||
out, err := run(repoPath, "shortlog", "-sn", "--no-merges", "HEAD")
|
||||
if err != nil {
|
||||
return []Contributor{}, nil // empty repo or detached HEAD — not an error
|
||||
}
|
||||
|
||||
var result []Contributor
|
||||
for _, line := range strings.Split(strings.TrimSpace(string(out)), "\n") {
|
||||
line = strings.TrimSpace(line)
|
||||
if line == "" {
|
||||
continue
|
||||
}
|
||||
// Format: " 42\tAlice Wang"
|
||||
idx := strings.Index(line, "\t")
|
||||
if idx < 0 {
|
||||
continue
|
||||
}
|
||||
countStr := strings.TrimSpace(line[:idx])
|
||||
name := strings.TrimSpace(line[idx+1:])
|
||||
n, err := strconv.Atoi(countStr)
|
||||
if err != nil {
|
||||
continue
|
||||
}
|
||||
result = append(result, Contributor{Name: name, Commits: n})
|
||||
if len(result) >= limit {
|
||||
break
|
||||
}
|
||||
}
|
||||
return result, nil
|
||||
}
|
||||
|
||||
// CommitCount returns the total number of commits reachable from HEAD.
|
||||
func CommitCount(repoPath string) (int, error) {
|
||||
out, err := run(repoPath, "rev-list", "--count", "HEAD")
|
||||
if err != nil {
|
||||
return 0, nil // empty repo
|
||||
}
|
||||
n, err := strconv.Atoi(strings.TrimSpace(string(out)))
|
||||
if err != nil {
|
||||
return 0, nil
|
||||
}
|
||||
return n, nil
|
||||
}
|
||||
|
||||
// SearchFiles returns file paths matching query (case-insensitive substring)
|
||||
// in the repository tree at ref, capped at limit results.
|
||||
func SearchFiles(repoPath, ref, query string, limit int) ([]string, error) {
|
||||
if ref == "" {
|
||||
ref = "HEAD"
|
||||
}
|
||||
out, err := run(repoPath, "ls-tree", "-r", "--name-only", ref)
|
||||
if err != nil {
|
||||
return []string{}, nil // empty repo
|
||||
}
|
||||
|
||||
lower := strings.ToLower(query)
|
||||
var results []string
|
||||
for _, line := range strings.Split(strings.TrimSpace(string(out)), "\n") {
|
||||
line = strings.TrimSpace(line)
|
||||
if line == "" {
|
||||
continue
|
||||
}
|
||||
if strings.Contains(strings.ToLower(line), lower) {
|
||||
results = append(results, line)
|
||||
if len(results) >= limit {
|
||||
break
|
||||
}
|
||||
}
|
||||
}
|
||||
return results, nil
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user