mirror of
https://github.com/mark3labs/kit.git
synced 2026-06-14 03:30:26 +00:00
revert(ui): restore original Read tool renderer without herald
The herald-based CodeBlock implementation didn't match the custom styling we had for line numbers and gutters. Restoring the original renderReadBody and renderCodeBlock functions with: - Custom line number gutter styling - Chroma syntax highlighting - Truncation handling with footer preservation
This commit is contained in:
+105
-104
@@ -4,7 +4,6 @@ import (
|
||||
"bytes"
|
||||
"encoding/json"
|
||||
"fmt"
|
||||
"path/filepath"
|
||||
"regexp"
|
||||
"strconv"
|
||||
"strings"
|
||||
@@ -16,89 +15,8 @@ import (
|
||||
"github.com/alecthomas/chroma/v2/styles"
|
||||
udiff "github.com/aymanbagabas/go-udiff"
|
||||
xansi "github.com/charmbracelet/x/ansi"
|
||||
"github.com/indaco/herald"
|
||||
)
|
||||
|
||||
// detectLanguage extracts the language from a filename for syntax highlighting.
|
||||
func detectLanguage(fileName string) string {
|
||||
ext := strings.ToLower(filepath.Ext(fileName))
|
||||
ext = strings.TrimPrefix(ext, ".")
|
||||
|
||||
// Map common extensions to language names
|
||||
langMap := map[string]string{
|
||||
"go": "go",
|
||||
"py": "python",
|
||||
"js": "javascript",
|
||||
"ts": "typescript",
|
||||
"jsx": "jsx",
|
||||
"tsx": "tsx",
|
||||
"rs": "rust",
|
||||
"java": "java",
|
||||
"cpp": "cpp",
|
||||
"c": "c",
|
||||
"h": "c",
|
||||
"hpp": "cpp",
|
||||
"cs": "csharp",
|
||||
"rb": "ruby",
|
||||
"php": "php",
|
||||
"swift": "swift",
|
||||
"kt": "kotlin",
|
||||
"scala": "scala",
|
||||
"r": "r",
|
||||
"sql": "sql",
|
||||
"sh": "bash",
|
||||
"bash": "bash",
|
||||
"zsh": "zsh",
|
||||
"fish": "fish",
|
||||
"ps1": "powershell",
|
||||
"yaml": "yaml",
|
||||
"yml": "yaml",
|
||||
"json": "json",
|
||||
"toml": "toml",
|
||||
"xml": "xml",
|
||||
"html": "html",
|
||||
"htm": "html",
|
||||
"css": "css",
|
||||
"scss": "scss",
|
||||
"sass": "sass",
|
||||
"less": "less",
|
||||
"md": "markdown",
|
||||
"dockerfile": "dockerfile",
|
||||
"makefile": "makefile",
|
||||
"vim": "vim",
|
||||
"lua": "lua",
|
||||
"perl": "perl",
|
||||
"pl": "perl",
|
||||
"haskell": "haskell",
|
||||
"hs": "haskell",
|
||||
"erlang": "erlang",
|
||||
"erl": "erlang",
|
||||
"elixir": "elixir",
|
||||
"ex": "elixir",
|
||||
"exs": "elixir",
|
||||
"clojure": "clojure",
|
||||
"clj": "clojure",
|
||||
"lisp": "lisp",
|
||||
"scheme": "scheme",
|
||||
"racket": "racket",
|
||||
"ocaml": "ocaml",
|
||||
"ml": "ocaml",
|
||||
"fsharp": "fsharp",
|
||||
"fs": "fsharp",
|
||||
"fsx": "fsharp",
|
||||
"dart": "dart",
|
||||
"flutter": "dart",
|
||||
"julia": "julia",
|
||||
"groovy": "groovy",
|
||||
"gradle": "groovy",
|
||||
}
|
||||
|
||||
if lang, ok := langMap[ext]; ok {
|
||||
return lang
|
||||
}
|
||||
return ext
|
||||
}
|
||||
|
||||
// Maximum visible lines per tool type before truncation.
|
||||
const (
|
||||
maxDiffLines = 20 // side-by-side rows for Edit
|
||||
@@ -456,7 +374,8 @@ func renderLsBody(toolResult string, width int) string {
|
||||
// Read tool — code block with line numbers + syntax highlighting
|
||||
// ---------------------------------------------------------------------------
|
||||
|
||||
// renderReadBody renders Read tool output using herald's CodeBlock with line numbers.
|
||||
// renderReadBody renders Read tool output with styled line numbers and optional
|
||||
// syntax highlighting based on file extension.
|
||||
func renderReadBody(toolArgs, toolResult string, width int) string {
|
||||
if strings.TrimSpace(toolResult) == "" {
|
||||
return ""
|
||||
@@ -471,39 +390,121 @@ func renderReadBody(toolArgs, toolResult string, width int) string {
|
||||
}
|
||||
}
|
||||
|
||||
// Parse lines and extract just the code content (removing "N: " prefix)
|
||||
lines := strings.Split(toolResult, "\n")
|
||||
var codeLines []string
|
||||
for _, line := range lines {
|
||||
return renderCodeBlock(toolResult, fileName, width)
|
||||
}
|
||||
|
||||
// codeLine holds a parsed line with optional line number.
|
||||
type codeLine struct {
|
||||
lineNum string
|
||||
code string
|
||||
}
|
||||
|
||||
// renderCodeBlock renders content with a styled gutter (line numbers) and
|
||||
// optional syntax highlighting.
|
||||
func renderCodeBlock(content, fileName string, width int) string {
|
||||
rawLines := strings.Split(content, "\n")
|
||||
|
||||
// Parse lines: detect "N: content" format from Read tool
|
||||
var parsed []codeLine
|
||||
maxNumWidth := 0
|
||||
var codeOnly []string
|
||||
|
||||
for _, line := range rawLines {
|
||||
if idx := strings.Index(line, ": "); idx > 0 && idx <= 7 {
|
||||
numPart := line[:idx]
|
||||
if _, err := strconv.Atoi(strings.TrimSpace(numPart)); err == nil {
|
||||
codeLines = append(codeLines, line[idx+2:])
|
||||
parsed = append(parsed, codeLine{lineNum: numPart, code: line[idx+2:]})
|
||||
if len(numPart) > maxNumWidth {
|
||||
maxNumWidth = len(numPart)
|
||||
}
|
||||
codeOnly = append(codeOnly, line[idx+2:])
|
||||
continue
|
||||
}
|
||||
}
|
||||
codeLines = append(codeLines, line)
|
||||
// No line number — treat as metadata/footer
|
||||
parsed = append(parsed, codeLine{code: line})
|
||||
codeOnly = append(codeOnly, line)
|
||||
}
|
||||
|
||||
content := strings.Join(codeLines, "\n")
|
||||
|
||||
// Truncate if too long
|
||||
if len(codeLines) > maxCodeLines {
|
||||
content = strings.Join(codeLines[:maxCodeLines], "\n")
|
||||
if len(parsed) == 0 {
|
||||
return ""
|
||||
}
|
||||
|
||||
// Detect language from filename
|
||||
lang := detectLanguage(fileName)
|
||||
// Truncate to maxCodeLines visible lines (preserve footer/metadata lines)
|
||||
var codeHiddenCount int
|
||||
totalParsed := len(parsed)
|
||||
if totalParsed > maxCodeLines {
|
||||
// Check if last line is a footer (no line number) — keep it
|
||||
var footerLines []codeLine
|
||||
for totalParsed > 0 && parsed[totalParsed-1].lineNum == "" {
|
||||
footerLines = append([]codeLine{parsed[totalParsed-1]}, footerLines...)
|
||||
totalParsed--
|
||||
}
|
||||
if totalParsed > maxCodeLines {
|
||||
codeHiddenCount = totalParsed - maxCodeLines
|
||||
parsed = append(parsed[:maxCodeLines], footerLines...)
|
||||
codeOnly = codeOnly[:maxCodeLines]
|
||||
for _, fl := range footerLines {
|
||||
codeOnly = append(codeOnly, fl.code)
|
||||
}
|
||||
} else {
|
||||
// Restore — footer trimming was enough
|
||||
parsed = parsed[:totalParsed]
|
||||
parsed = append(parsed, footerLines...)
|
||||
}
|
||||
}
|
||||
|
||||
// Use herald's CodeBlock with line numbers
|
||||
ty := herald.New(
|
||||
herald.WithCodeLineNumbers(true),
|
||||
herald.WithCodeFormatter(func(code, language string) string {
|
||||
return syntaxHighlight(code, fileName)
|
||||
}),
|
||||
)
|
||||
// Syntax highlight the code portion
|
||||
highlighted := syntaxHighlight(strings.Join(codeOnly, "\n"), fileName)
|
||||
highlightedLines := strings.Split(highlighted, "\n")
|
||||
|
||||
return ty.CodeBlock(content, lang)
|
||||
// Layout
|
||||
const codeIndent = " "
|
||||
gutterWidth := max(maxNumWidth+2, 5)
|
||||
codeWidth := max(width-gutterWidth-len(codeIndent), 20)
|
||||
|
||||
theme := getTheme()
|
||||
gutterStyle := lipgloss.NewStyle().Foreground(theme.Muted).Background(theme.GutterBg).PaddingRight(1)
|
||||
codeStyle := lipgloss.NewStyle().Background(theme.CodeBg).PaddingLeft(1)
|
||||
|
||||
var result []string
|
||||
for i, p := range parsed {
|
||||
// If this line has no line number, it's a metadata/footer line (e.g. truncation notice).
|
||||
if p.lineNum == "" {
|
||||
// Render footer lines with code background but no gutter
|
||||
truncatedFooter := truncateLine(p.code, codeWidth-1) // account for PaddingLeft(1)
|
||||
footer := codeStyle.Width(codeWidth).Render(truncatedFooter)
|
||||
emptyGutter := gutterStyle.Width(gutterWidth).Render("")
|
||||
result = append(result, codeIndent+lipgloss.JoinHorizontal(lipgloss.Top, emptyGutter, footer))
|
||||
continue
|
||||
}
|
||||
|
||||
gutter := gutterStyle.Width(gutterWidth).Render(p.lineNum)
|
||||
|
||||
var codePart string
|
||||
if i < len(highlightedLines) {
|
||||
codePart = highlightedLines[i]
|
||||
} else {
|
||||
codePart = p.code
|
||||
}
|
||||
// Truncate the (possibly ANSI-highlighted) line to fit within
|
||||
// the code column, preventing lipgloss from wrapping it.
|
||||
codePart = truncateLine(codePart, codeWidth-1) // account for PaddingLeft(1)
|
||||
styledCode := codeStyle.Width(codeWidth).Render(codePart)
|
||||
|
||||
result = append(result, codeIndent+lipgloss.JoinHorizontal(lipgloss.Top, gutter, styledCode))
|
||||
}
|
||||
|
||||
// Truncation hint
|
||||
if codeHiddenCount > 0 {
|
||||
hint := fmt.Sprintf("...(%d more lines)", codeHiddenCount)
|
||||
emptyGutter := gutterStyle.Width(gutterWidth).Render("")
|
||||
hintContent := codeStyle.Width(codeWidth).
|
||||
Foreground(theme.Muted).Italic(true).Render(hint)
|
||||
result = append(result, codeIndent+lipgloss.JoinHorizontal(lipgloss.Top, emptyGutter, hintContent))
|
||||
}
|
||||
|
||||
return strings.Join(result, "\n")
|
||||
}
|
||||
|
||||
// ---------------------------------------------------------------------------
|
||||
|
||||
Reference in New Issue
Block a user