package render import ( "strings" ) // --------------------------------------------------------------------------- // Filesystem: apply_patch + view_image (filesystem_renderer.py) // --------------------------------------------------------------------------- const ( addFilePfx = "*** Add File: " deleteFilePfx = "*** Delete File: " updateFilePfx = "*** Update File: " beginPatch = "*** Begin Patch" endPatch = "*** End Patch" ) type patchOp struct { kind string path string old []string new []string } func extractPatchText(args map[string]any) string { if raw, ok := args["patch"].(string); ok { return raw } if raw, ok := args["patch"].(map[string]any); ok { if inner, ok := raw["patch"].(string); ok { return inner } } if fb, ok := args["input"].(string); ok { return fb } return "" } func parsePatchOperations(patch string) []patchOp { var ops []patchOp var cur *patchOp flush := func() { if cur != nil && cur.kind != "" { ops = append(ops, *cur) } cur = nil } for _, line := range strings.Split(patch, "\n") { switch { case line == beginPatch || line == endPatch: continue case strings.HasPrefix(line, addFilePfx): flush() cur = &patchOp{kind: "add", path: strings.TrimSpace(line[len(addFilePfx):])} case strings.HasPrefix(line, updateFilePfx): flush() cur = &patchOp{kind: "update", path: strings.TrimSpace(line[len(updateFilePfx):])} case strings.HasPrefix(line, deleteFilePfx): flush() cur = &patchOp{kind: "delete", path: strings.TrimSpace(line[len(deleteFilePfx):])} case cur != nil && cur.kind == "update": if strings.HasPrefix(line, "@@") { continue } if strings.HasPrefix(line, "-") && !strings.HasPrefix(line, "---") { cur.old = append(cur.old, line[1:]) } else if strings.HasPrefix(line, "+") && !strings.HasPrefix(line, "+++") { cur.new = append(cur.new, line[1:]) } case cur != nil && cur.kind == "add": if strings.HasPrefix(line, "+") { cur.new = append(cur.new, line[1:]) } else if strings.TrimSpace(line) != "" { cur.new = append(cur.new, line) } } } flush() return ops } var opLabel = map[string]string{"add": "create", "update": "edit", "delete": "delete"} func renderPatchOperation(b *strings.Builder, op patchOp) { label := opLabel[op.kind] if label == "" { label = "file" } b.WriteString(Col(Emerald).Render("◇ ") + Dim().Render(label)) if op.path != "" { p := op.path if len(p) > 60 { p = p[len(p)-60:] } b.WriteString(" " + Dim().Render(p)) } lang := languageForPath(op.path) if op.kind == "update" { for _, line := range highlightLines(op.old, lang) { b.WriteString("\n" + Col(Red).Render("-") + " " + line) } for _, line := range highlightLines(op.new, lang) { b.WriteString("\n" + Col(Green).Render("+") + " " + line) } } else if op.kind == "add" && len(op.new) > 0 { b.WriteString("\n" + HighlightCode(strings.Join(op.new, "\n"), lang)) } } func highlightLines(lines []string, lang string) []string { if len(lines) == 0 || lang == "" { return lines } return strings.Split(HighlightCode(strings.Join(lines, "\n"), lang), "\n") } func renderApplyPatch(args map[string]any, result any, status string) string { ops := parsePatchOperations(extractPatchText(args)) var b strings.Builder if len(ops) == 0 { b.WriteString(Col(Emerald).Render("◇ ") + Dim().Render("patch")) if s, ok := result.(string); ok && strings.TrimSpace(s) != "" { b.WriteString("\n " + Dim().Render(strings.TrimSpace(s))) } else if result == nil { b.WriteString(" " + Dim().Render("Processing...")) } return b.String() } for i, op := range ops { if i > 0 { b.WriteString("\n") } renderPatchOperation(&b, op) } if status == "failed" { if s, ok := result.(string); ok && strings.TrimSpace(s) != "" { b.WriteString("\n " + Col(Red).Render(strings.TrimSpace(s))) } } return b.String() }