New Commands: - z read - Smart file reader with auto-detection (markdown, syntax highlighting) - z sys - System overview (CPU, RAM, Disk, processes) - z find - File search with pattern/size/time filters - z search - User-friendly grep alternative - z usage - Process resource usage monitor Enhancements: - Unified help system with consistent error messages - Help on error shows usage and examples for each command - "Command not found" suggests similar commands - Directory info now detects .gitignore and shows ignored files - File handler registry for extensible file type detection - Added Italic, Dim, Underline color constants
135 lines
2.7 KiB
Go
135 lines
2.7 KiB
Go
package formatter
|
|
|
|
import (
|
|
"bytes"
|
|
"fmt"
|
|
"math"
|
|
"os"
|
|
"path/filepath"
|
|
"strings"
|
|
|
|
"github.com/alecthomas/chroma/v2/formatters"
|
|
"github.com/alecthomas/chroma/v2/lexers"
|
|
"github.com/alecthomas/chroma/v2/styles"
|
|
)
|
|
|
|
type FileHandler func(args []string) error
|
|
|
|
type FileHandlerRegistry struct {
|
|
handlers map[string]FileHandler
|
|
}
|
|
|
|
var ReadHandlers = &FileHandlerRegistry{
|
|
handlers: make(map[string]FileHandler),
|
|
}
|
|
|
|
func (r *FileHandlerRegistry) Register(ext string, handler FileHandler) {
|
|
r.handlers[ext] = handler
|
|
}
|
|
|
|
func (r *FileHandlerRegistry) GetHandler(filename string) FileHandler {
|
|
ext := filepath.Ext(filename)
|
|
|
|
if ext == "" || ext == "." {
|
|
basename := filepath.Base(filename)
|
|
if strings.HasPrefix(basename, "Dockerfile") {
|
|
return r.handlers[".dockerfile"]
|
|
}
|
|
return nil
|
|
}
|
|
|
|
if handler, ok := r.handlers[ext]; ok {
|
|
return handler
|
|
}
|
|
|
|
return nil
|
|
}
|
|
|
|
func ReadFileWithHighlight(file *os.File, filePath string) error {
|
|
content := ReadFileContent(file)
|
|
|
|
lexer := lexers.Match(filepath.Base(filePath))
|
|
if lexer == nil {
|
|
lexer = lexers.Fallback
|
|
}
|
|
|
|
iterator, err := lexer.Tokenise(nil, content)
|
|
if err != nil {
|
|
return fmt.Errorf("error tokenizing: %w", err)
|
|
}
|
|
|
|
formatter_ := formatters.TTY256
|
|
style := styles.Get("monokai")
|
|
if style == nil {
|
|
style = styles.Fallback
|
|
}
|
|
|
|
var buf bytes.Buffer
|
|
err = formatter_.Format(&buf, style, iterator)
|
|
if err != nil {
|
|
return fmt.Errorf("error formatting: %w", err)
|
|
}
|
|
|
|
lines := strings.Split(buf.String(), "\n")
|
|
for i, line := range lines {
|
|
if line != "" {
|
|
fmt.Printf("%s%4d: %s%s\n", "\033[90m", i+1, "\033[0m", line)
|
|
}
|
|
}
|
|
|
|
return nil
|
|
}
|
|
|
|
// FormatSize converts bytes to human-readable format
|
|
func FormatSize(bytes int64) string {
|
|
if bytes == 0 {
|
|
return "0 B"
|
|
}
|
|
|
|
const unit = 1024
|
|
units := []string{"B", "KB", "MB", "GB", "TB", "PB"}
|
|
|
|
exp := int(math.Log(float64(bytes)) / math.Log(float64(unit)))
|
|
if exp > len(units)-1 {
|
|
exp = len(units) - 1
|
|
}
|
|
|
|
value := float64(bytes) / math.Pow(float64(unit), float64(exp))
|
|
return fmt.Sprintf("%.2f %s", value, units[exp])
|
|
}
|
|
|
|
// CenterText centers text within a given width
|
|
func CenterText(text string, width int) string {
|
|
if len(text) >= width {
|
|
return text[:width]
|
|
}
|
|
|
|
padding := width - len(text)
|
|
left := padding / 2
|
|
right := padding - left
|
|
|
|
return fmt.Sprintf("%s%s%s", repeat(" ", left), text, repeat(" ", right))
|
|
}
|
|
|
|
func repeat(s string, count int) string {
|
|
result := ""
|
|
for i := 0; i < count; i++ {
|
|
result += s
|
|
}
|
|
return result
|
|
}
|
|
|
|
func ReadFileContent(file *os.File) string {
|
|
content := make([]byte, 0)
|
|
buffer := make([]byte, 4096)
|
|
for {
|
|
n, err := file.Read(buffer)
|
|
if n > 0 {
|
|
content = append(content, buffer[:n]...)
|
|
}
|
|
if err != nil {
|
|
break
|
|
}
|
|
}
|
|
return string(content)
|
|
}
|