Skip to content

Commit 622239b

Browse files
authored
Create utils.go
1 parent 563f2ff commit 622239b

1 file changed

Lines changed: 251 additions & 0 deletions

File tree

source-code/src/utils.go

Lines changed: 251 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,251 @@
1+
package src
2+
3+
import (
4+
"fmt"
5+
"io"
6+
"io/fs"
7+
"net/http"
8+
"os"
9+
"os/exec"
10+
"path/filepath"
11+
"regexp"
12+
"sort"
13+
"strings"
14+
"syscall"
15+
16+
"github.com/alecthomas/chroma/v2/lexers"
17+
"github.com/charmbracelet/bubbles/list"
18+
)
19+
20+
func (m *Model) refreshPanel(idx int) {
21+
p := &m.panels[idx]
22+
items := []list.Item{}
23+
files, err := p.vfs.ReadDir(p.currentDir)
24+
if err != nil {
25+
m.statusMsg = errorStyle.Render(fmt.Sprintf("Error reading directory: %v", err))
26+
return
27+
}
28+
gitStatus := m.getGitStatus(p)
29+
sort.Slice(files, func(i, j int) bool {
30+
return files[i].Name() < files[j].Name()
31+
})
32+
for _, file := range files {
33+
info, _ := file.Info()
34+
desc := "File"
35+
if file.IsDir() {
36+
desc = "Directory"
37+
}
38+
status := gitStatus[file.Name()]
39+
items = append(items, item{
40+
title: file.Name(),
41+
desc: desc,
42+
status: status,
43+
isDir: file.IsDir(),
44+
size: info.Size(),
45+
modTime: info.ModTime(),
46+
})
47+
}
48+
p.fileList.SetItems(items)
49+
m.statusMsg = successStyle.Render("Panel refreshed")
50+
m.updatePreview(idx)
51+
m.updateGitBranch(idx)
52+
}
53+
54+
func (m *Model) getGitStatus(p *panel) map[string]string {
55+
statusMap := make(map[string]string)
56+
if _, ok := p.vfs.(localVFS); !ok {
57+
return statusMap
58+
}
59+
cmd := exec.Command("git", "status", "--porcelain")
60+
cmd.Dir = p.currentDir
61+
output, err := cmd.Output()
62+
if err != nil {
63+
return statusMap
64+
}
65+
lines := strings.Split(string(output), "\n")
66+
for _, line := range lines {
67+
if line == "" {
68+
continue
69+
}
70+
fields := strings.Fields(line)
71+
if len(fields) < 2 {
72+
continue
73+
}
74+
file := fields[1]
75+
st := line[:2]
76+
statusMap[file] = st
77+
}
78+
return statusMap
79+
}
80+
81+
func (m *Model) updateGitBranch(idx int) {
82+
p := &m.panels[idx]
83+
if _, ok := p.vfs.(localVFS); !ok {
84+
p.gitBranch = ""
85+
return
86+
}
87+
cmd := exec.Command("git", "rev-parse", "--abbrev-ref", "HEAD")
88+
cmd.Dir = p.currentDir
89+
output, err := cmd.Output()
90+
if err != nil {
91+
p.gitBranch = ""
92+
return
93+
}
94+
p.gitBranch = strings.TrimSpace(string(output))
95+
}
96+
97+
func (m *Model) updatePreview(idx int) {
98+
p := &m.panels[idx]
99+
selected, ok := p.fileList.SelectedItem().(item)
100+
if !ok {
101+
p.preview.SetContent("")
102+
return
103+
}
104+
if selected.isDir {
105+
p.preview.SetContent("Directory")
106+
return
107+
}
108+
filePath := filepath.Join(p.currentDir, selected.title)
109+
f, err := p.vfs.Open(filePath)
110+
if err != nil {
111+
p.preview.SetContent("Error opening file")
112+
return
113+
}
114+
defer f.Close()
115+
stat, err := f.Stat()
116+
if err != nil {
117+
p.preview.SetContent("Error stat file")
118+
return
119+
}
120+
buf := make([]byte, 512)
121+
_, err = f.Read(buf)
122+
if err != nil && err != io.EOF {
123+
p.preview.SetContent("Error reading for MIME")
124+
return
125+
}
126+
mimeType := http.DetectContentType(buf)
127+
if mimeType == "application/octet-stream" {
128+
if _, ok := p.vfs.(localVFS); ok {
129+
cmd := exec.Command("file", "-b", "--mime-type", filePath)
130+
out, err := cmd.Output()
131+
if err == nil {
132+
mimeType = strings.TrimSpace(string(out))
133+
}
134+
}
135+
}
136+
if s, ok := f.(io.Seeker); ok {
137+
s.Seek(0, io.SeekStart)
138+
} else {
139+
f.Close()
140+
f, err = p.vfs.Open(filePath)
141+
if err != nil {
142+
p.preview.SetContent("Error reopening file")
143+
return
144+
}
145+
defer f.Close()
146+
}
147+
if strings.HasPrefix(mimeType, "image/") {
148+
p.preview.SetContent("Image preview not supported in text mode. MIME: " + mimeType)
149+
return
150+
}
151+
if !strings.HasPrefix(mimeType, "text/") {
152+
p.preview.SetContent("Non-text file: " + mimeType)
153+
return
154+
}
155+
if stat.Size() > maxFileSizeForEdit {
156+
p.preview.SetContent("File too large for preview")
157+
return
158+
}
159+
byteContent, err := io.ReadAll(f)
160+
if err != nil {
161+
p.preview.SetContent("Error reading file")
162+
return
163+
}
164+
content := string(byteContent)
165+
lines := strings.Split(content, "\n")
166+
if len(lines) > previewLines {
167+
lines = lines[:previewLines]
168+
content = strings.Join(lines, "\n") + "\n..."
169+
}
170+
lexer := lexers.Match(selected.title)
171+
if lexer == nil {
172+
lexer = lexers.Fallback
173+
}
174+
iterator, err := lexer.Tokenise(nil, content)
175+
if err != nil {
176+
p.preview.SetContent(content)
177+
return
178+
}
179+
var sb strings.Builder
180+
err = chromaFormatter.Format(&sb, chromaStyle, iterator)
181+
if err != nil {
182+
p.preview.SetContent(content)
183+
return
184+
}
185+
p.preview.SetContent(sb.String())
186+
}
187+
188+
func (m *Model) performFuzzySearch() {
189+
query := m.fuzzyInput.Value()
190+
if query == "" {
191+
return
192+
}
193+
var results []string
194+
err := filepath.Walk(m.panels[m.activePanel].currentDir, func(path string, info fs.FileInfo, err error) error {
195+
if err != nil {
196+
return err
197+
}
198+
if strings.Contains(strings.ToLower(filepath.Base(path)), strings.ToLower(query)) {
199+
rel, _ := filepath.Rel(m.panels[m.activePanel].currentDir, path)
200+
results = append(results, rel)
201+
}
202+
return nil
203+
})
204+
if err != nil {
205+
m.statusMsg = errorStyle.Render(err.Error())
206+
}
207+
m.fuzzyResults = results
208+
items := []list.Item{}
209+
for _, res := range results {
210+
items = append(items, item{title: res})
211+
}
212+
m.panels[m.activePanel].fileList.SetItems(items)
213+
}
214+
215+
func (m *Model) performBulkRename() {
216+
re, err := regexp.Compile(m.bulkRenameFrom)
217+
if err != nil {
218+
m.statusMsg = errorStyle.Render("Invalid regex")
219+
return
220+
}
221+
for file := range m.panels[m.activePanel].selectedFiles {
222+
newName := re.ReplaceAllString(filepath.Base(file), m.bulkRenameTo)
223+
newPath := filepath.Join(filepath.Dir(file), newName)
224+
err := os.Rename(file, newPath)
225+
if err != nil {
226+
m.statusMsg += errorStyle.Render(fmt.Sprintf("Rename failed for %s: %v\n", file, err))
227+
}
228+
}
229+
m.panels[m.activePanel].selectedFiles = make(map[string]bool)
230+
m.refreshPanel(m.activePanel)
231+
m.bulkRenameFrom = ""
232+
m.bulkRenameTo = ""
233+
}
234+
235+
func (m *Model) suspend() {
236+
pid := os.Getpid()
237+
syscall.Kill(pid, syscall.SIGTSTP)
238+
m.refreshPanel(m.activePanel)
239+
}
240+
241+
func (m *Model) openSubShell() {
242+
m.subShell = true
243+
cmd := exec.Command(os.Getenv("SHELL"))
244+
cmd.Stdin = os.Stdin
245+
cmd.Stdout = os.Stdout
246+
cmd.Stderr = os.Stderr
247+
cmd.Dir = m.panels[m.activePanel].currentDir
248+
cmd.Run()
249+
m.subShell = false
250+
m.refreshPanel(m.activePanel)
251+
}

0 commit comments

Comments
 (0)