-
Notifications
You must be signed in to change notification settings - Fork 0
Expand file tree
/
Copy pathpreview.go
More file actions
155 lines (130 loc) · 3.34 KB
/
Copy pathpreview.go
File metadata and controls
155 lines (130 loc) · 3.34 KB
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
package main
import (
"bufio"
"fmt"
"os"
"path/filepath"
"strings"
"github.com/alecthomas/chroma/v2"
"github.com/alecthomas/chroma/v2/formatters"
"github.com/alecthomas/chroma/v2/lexers"
"github.com/alecthomas/chroma/v2/styles"
tea "github.com/charmbracelet/bubbletea"
)
// PreviewService generates file previews.
type PreviewService struct {
maxLines int
style *chroma.Style
formatter chroma.Formatter
}
// NewPreviewService creates a new PreviewService.
func NewPreviewService(maxLines int) *PreviewService {
if maxLines <= 0 {
maxLines = 500
}
style := styles.Get("monokai")
if style == nil {
style = styles.Fallback
}
formatter := formatters.Get("terminal256")
if formatter == nil {
formatter = formatters.Fallback
}
return &PreviewService{
maxLines: maxLines,
style: style,
formatter: formatter,
}
}
// Supported languages for MVP.
var supportedLangs = map[string]bool{
".go": true,
".py": true,
".js": true,
".ts": true,
".rs": true,
".c": true,
".cpp": true,
".h": true,
".hpp": true,
".java": true,
".md": true,
}
// Generate generates a preview for the given file.
func (p *PreviewService) Generate(path string, width int) (string, error) {
ext := filepath.Ext(path)
// Check if it's a text file
if !IsTextFile(ext) {
return "[Binary file - no preview]", nil
}
// Read file with line limit to avoid OOM on large files
lines, truncated, err := readLinesLimited(path, p.maxLines)
if err != nil {
return fmt.Sprintf("[Error reading file: %v]", err), nil
}
text := strings.Join(lines, "\n")
var result string
if supportedLangs[ext] || ext == ".json" || ext == ".yaml" || ext == ".yml" {
result, err = p.highlight(text, path, width)
if err != nil {
result = text // fallback to plain text
}
} else {
result = text
}
if truncated {
result += fmt.Sprintf("\n\n... (truncated, showing first %d lines)", p.maxLines)
}
return result, nil
}
// GenerateAsync returns a tea.Cmd that generates a preview asynchronously.
func (p *PreviewService) GenerateAsync(path string, width int) tea.Cmd {
return func() tea.Msg {
content, err := p.Generate(path, width)
return PreviewResultMsg{
path: path,
content: content,
err: err,
}
}
}
// readLinesLimited reads up to maxLines from a file using a buffered scanner.
// Returns the lines, whether it was truncated, and any error.
func readLinesLimited(path string, maxLines int) ([]string, bool, error) {
f, err := os.Open(path)
if err != nil {
return nil, false, err
}
defer f.Close()
var lines []string
scanner := bufio.NewScanner(f)
scanner.Buffer(make([]byte, 64*1024), 64*1024) // 64KB buffer
for scanner.Scan() {
lines = append(lines, scanner.Text())
if len(lines) >= maxLines {
return lines, true, nil
}
}
if err := scanner.Err(); err != nil {
// Return what we have so far with the error
return lines, false, err
}
return lines, false, nil
}
// highlight applies syntax highlighting to the content.
func (p *PreviewService) highlight(content, path string, width int) (string, error) {
lexer := lexers.Match(path)
if lexer == nil {
lexer = lexers.Fallback
}
iterator, err := lexer.Tokenise(nil, content)
if err != nil {
return content, err
}
var buf strings.Builder
err = p.formatter.Format(&buf, p.style, iterator)
if err != nil {
return content, err
}
return buf.String(), nil
}