初始化仓库
This commit is contained in:
@@ -0,0 +1,314 @@
|
||||
package core
|
||||
|
||||
import (
|
||||
"bufio"
|
||||
"fmt"
|
||||
"os"
|
||||
"path/filepath"
|
||||
"strings"
|
||||
)
|
||||
|
||||
const (
|
||||
defaultShowHeadLines = 80
|
||||
defaultShowContextLines = 8
|
||||
defaultShowMaxRange = 120
|
||||
defaultShowMaxEntries = 50
|
||||
)
|
||||
|
||||
type referenceViewMode string
|
||||
|
||||
const (
|
||||
referenceViewFileHead referenceViewMode = "file_head"
|
||||
referenceViewContext referenceViewMode = "context"
|
||||
referenceViewRange referenceViewMode = "range"
|
||||
referenceViewDir referenceViewMode = "dir"
|
||||
)
|
||||
|
||||
type referenceViewRequest struct {
|
||||
Ref *localReference
|
||||
Mode referenceViewMode
|
||||
Window int
|
||||
MaxLines int
|
||||
MaxEntries int
|
||||
}
|
||||
|
||||
func buildReferenceViewRequest(rawRef, workspaceDir string) (*referenceViewRequest, error) {
|
||||
ref, err := parseUserLocalReference(rawRef, workspaceDir)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
if ref.kind == referenceKindDir && ref.locationFormat != referenceLocationNone {
|
||||
return nil, fmt.Errorf("directory reference cannot carry a location")
|
||||
}
|
||||
req := &referenceViewRequest{
|
||||
Ref: ref,
|
||||
Window: defaultShowContextLines,
|
||||
MaxLines: defaultShowMaxRange,
|
||||
MaxEntries: defaultShowMaxEntries,
|
||||
}
|
||||
switch {
|
||||
case ref.kind == referenceKindDir:
|
||||
req.Mode = referenceViewDir
|
||||
case ref.locationFormat == referenceLocationColonRange:
|
||||
req.Mode = referenceViewRange
|
||||
case ref.locationFormat != referenceLocationNone:
|
||||
req.Mode = referenceViewContext
|
||||
default:
|
||||
req.Mode = referenceViewFileHead
|
||||
req.MaxLines = defaultShowHeadLines
|
||||
}
|
||||
return req, nil
|
||||
}
|
||||
|
||||
func renderReferenceView(req *referenceViewRequest) (string, error) {
|
||||
if req == nil || req.Ref == nil {
|
||||
return "", fmt.Errorf("nil view request")
|
||||
}
|
||||
path := req.Ref.pathAbs
|
||||
if path == "" {
|
||||
path = req.Ref.pathOriginal
|
||||
}
|
||||
if path == "" {
|
||||
return "", fmt.Errorf("empty path")
|
||||
}
|
||||
info, err := os.Stat(path)
|
||||
if err != nil {
|
||||
if os.IsNotExist(err) {
|
||||
return "", fmt.Errorf("path does not exist")
|
||||
}
|
||||
return "", err
|
||||
}
|
||||
if info.IsDir() {
|
||||
if req.Ref.locationFormat != referenceLocationNone {
|
||||
return "", fmt.Errorf("directory reference cannot carry a location")
|
||||
}
|
||||
return renderReferenceDir(path, req)
|
||||
}
|
||||
return renderReferenceFile(path, req)
|
||||
}
|
||||
|
||||
func renderReferenceFile(path string, req *referenceViewRequest) (string, error) {
|
||||
var (
|
||||
lines []string
|
||||
truncated bool
|
||||
err error
|
||||
note string
|
||||
)
|
||||
|
||||
switch req.Mode {
|
||||
case referenceViewContext:
|
||||
lines, _, err = readFileContext(path, req.Ref.lineStart, req.Window, req.Window, req.MaxLines)
|
||||
case referenceViewRange:
|
||||
lines, truncated, err = readFileRange(path, req.Ref.lineStart, req.Ref.lineEnd, req.MaxLines)
|
||||
if truncated {
|
||||
note = fmt.Sprintf("Only showing the first %d lines of the requested range.", req.MaxLines)
|
||||
}
|
||||
default:
|
||||
lines, truncated, err = readFileHead(path, req.MaxLines)
|
||||
if truncated {
|
||||
note = fmt.Sprintf("Only showing the first %d lines.", req.MaxLines)
|
||||
}
|
||||
}
|
||||
if err != nil {
|
||||
return "", err
|
||||
}
|
||||
title := showReferenceTitle(req.Ref)
|
||||
if title == "" {
|
||||
title = cleanDisplayPath(path)
|
||||
}
|
||||
var sb strings.Builder
|
||||
sb.WriteString(title)
|
||||
if note != "" {
|
||||
sb.WriteString("\n")
|
||||
sb.WriteString(note)
|
||||
}
|
||||
sb.WriteString("\n```")
|
||||
if lang := codeFenceLanguage(path); lang != "" {
|
||||
sb.WriteString(lang)
|
||||
}
|
||||
sb.WriteString("\n")
|
||||
if len(lines) > 0 {
|
||||
sb.WriteString(strings.Join(lines, "\n"))
|
||||
sb.WriteString("\n")
|
||||
}
|
||||
sb.WriteString("```")
|
||||
return sb.String(), nil
|
||||
}
|
||||
|
||||
func renderReferenceDir(path string, req *referenceViewRequest) (string, error) {
|
||||
entries, truncated, err := readDirEntries(path, req.MaxEntries)
|
||||
if err != nil {
|
||||
return "", err
|
||||
}
|
||||
title := showReferenceTitle(req.Ref)
|
||||
if title == "" {
|
||||
title = "📁 " + appendDirSuffix(cleanDisplayPath(path), referenceKindDir)
|
||||
}
|
||||
var sb strings.Builder
|
||||
sb.WriteString(title)
|
||||
if len(entries) == 0 {
|
||||
sb.WriteString("\n(empty)")
|
||||
return sb.String(), nil
|
||||
}
|
||||
sb.WriteString("\n")
|
||||
for _, entry := range entries {
|
||||
sb.WriteString("- ")
|
||||
sb.WriteString(entry)
|
||||
sb.WriteString("\n")
|
||||
}
|
||||
if truncated {
|
||||
sb.WriteString(fmt.Sprintf("\nOnly showing the first %d entries.", req.MaxEntries))
|
||||
}
|
||||
return strings.TrimRight(sb.String(), "\n"), nil
|
||||
}
|
||||
|
||||
func showReferenceTitle(ref *localReference) string {
|
||||
if ref == nil {
|
||||
return ""
|
||||
}
|
||||
body := referenceDisplaySource(ref, "relative") + renderReferenceLocation(ref)
|
||||
return applyReferenceMarker("emoji", ref.kind, body)
|
||||
}
|
||||
|
||||
func readFileHead(path string, maxLines int) ([]string, bool, error) {
|
||||
if maxLines <= 0 {
|
||||
maxLines = defaultShowHeadLines
|
||||
}
|
||||
f, err := os.Open(path)
|
||||
if err != nil {
|
||||
return nil, false, err
|
||||
}
|
||||
defer f.Close()
|
||||
|
||||
scanner := bufio.NewScanner(f)
|
||||
buf := make([]byte, 0, 64*1024)
|
||||
scanner.Buffer(buf, 1024*1024)
|
||||
|
||||
lines := make([]string, 0, maxLines)
|
||||
truncated := false
|
||||
for scanner.Scan() {
|
||||
if len(lines) >= maxLines {
|
||||
truncated = true
|
||||
break
|
||||
}
|
||||
lines = append(lines, scanner.Text())
|
||||
}
|
||||
if err := scanner.Err(); err != nil {
|
||||
return nil, false, err
|
||||
}
|
||||
return lines, truncated, nil
|
||||
}
|
||||
|
||||
func readFileRange(path string, start, end, maxLines int) ([]string, bool, error) {
|
||||
if start <= 0 {
|
||||
return nil, false, fmt.Errorf("invalid start line")
|
||||
}
|
||||
if end <= 0 || end < start {
|
||||
return nil, false, fmt.Errorf("invalid end line")
|
||||
}
|
||||
if maxLines <= 0 {
|
||||
maxLines = defaultShowMaxRange
|
||||
}
|
||||
|
||||
f, err := os.Open(path)
|
||||
if err != nil {
|
||||
return nil, false, err
|
||||
}
|
||||
defer f.Close()
|
||||
|
||||
scanner := bufio.NewScanner(f)
|
||||
buf := make([]byte, 0, 64*1024)
|
||||
scanner.Buffer(buf, 1024*1024)
|
||||
|
||||
lines := make([]string, 0, minInt(end-start+1, maxLines))
|
||||
lineNo := 0
|
||||
truncated := false
|
||||
for scanner.Scan() {
|
||||
lineNo++
|
||||
if lineNo < start {
|
||||
continue
|
||||
}
|
||||
if lineNo > end {
|
||||
break
|
||||
}
|
||||
if len(lines) >= maxLines {
|
||||
truncated = true
|
||||
break
|
||||
}
|
||||
lines = append(lines, scanner.Text())
|
||||
}
|
||||
if err := scanner.Err(); err != nil {
|
||||
return nil, false, err
|
||||
}
|
||||
return lines, truncated, nil
|
||||
}
|
||||
|
||||
func readFileContext(path string, line, before, after, maxLines int) ([]string, bool, error) {
|
||||
if line <= 0 {
|
||||
return nil, false, fmt.Errorf("invalid line")
|
||||
}
|
||||
start := line - before
|
||||
if start < 1 {
|
||||
start = 1
|
||||
}
|
||||
end := line + after
|
||||
return readFileRange(path, start, end, maxLines)
|
||||
}
|
||||
|
||||
func readDirEntries(path string, maxEntries int) ([]string, bool, error) {
|
||||
if maxEntries <= 0 {
|
||||
maxEntries = defaultShowMaxEntries
|
||||
}
|
||||
entries, err := os.ReadDir(path)
|
||||
if err != nil {
|
||||
return nil, false, err
|
||||
}
|
||||
out := make([]string, 0, minInt(len(entries), maxEntries))
|
||||
truncated := false
|
||||
for i, entry := range entries {
|
||||
if i >= maxEntries {
|
||||
truncated = true
|
||||
break
|
||||
}
|
||||
name := entry.Name()
|
||||
if entry.IsDir() {
|
||||
name += "/"
|
||||
}
|
||||
out = append(out, name)
|
||||
}
|
||||
return out, truncated, nil
|
||||
}
|
||||
|
||||
func codeFenceLanguage(path string) string {
|
||||
switch strings.ToLower(filepath.Ext(path)) {
|
||||
case ".go":
|
||||
return "go"
|
||||
case ".ts":
|
||||
return "ts"
|
||||
case ".tsx":
|
||||
return "tsx"
|
||||
case ".js":
|
||||
return "js"
|
||||
case ".jsx":
|
||||
return "jsx"
|
||||
case ".py":
|
||||
return "python"
|
||||
case ".md":
|
||||
return "markdown"
|
||||
case ".json":
|
||||
return "json"
|
||||
case ".yaml", ".yml":
|
||||
return "yaml"
|
||||
case ".sh":
|
||||
return "bash"
|
||||
default:
|
||||
return ""
|
||||
}
|
||||
}
|
||||
|
||||
func minInt(a, b int) int {
|
||||
if a < b {
|
||||
return a
|
||||
}
|
||||
return b
|
||||
}
|
||||
Reference in New Issue
Block a user