mirror of
https://github.com/patriceckhart/zot.git
synced 2026-06-26 21:36:31 +02:00
Use ASCII ellipses throughout
This commit is contained in:
parent
47257c8a54
commit
8cd8410ace
21 changed files with 87 additions and 54 deletions
|
|
@ -595,7 +595,7 @@ zot can run as a telegram bot so you can DM it from your phone. Two ways to run
|
|||
Type `/telegram` in the running TUI to open a picker with **connect**, **disconnect**, and **status**. When connected:
|
||||
|
||||
- DMs from the paired user become prompts in the **same** session you're typing in, so you can continue a conversation from the terminal on your phone and back again.
|
||||
- Messages you type in the TUI are mirrored into the Telegram thread prefixed `you: …` and the assistant's replies come back prefixed `zot: …`, so the Telegram chat stays a complete record of both sides of the conversation.
|
||||
- Messages you type in the TUI are mirrored into the Telegram thread prefixed `you: ...` and the assistant's replies come back prefixed `zot: ...`, so the Telegram chat stays a complete record of both sides of the conversation.
|
||||
- Messages sent from Telegram show up as your own bubble in Telegram (no mirror) and the assistant's reply to them comes back bare (no prefix).
|
||||
- The status bar shows a `- tg -` tag while the bridge is active.
|
||||
- `/telegram connect` / `/telegram disconnect` / `/telegram status` (or `/tg`) also work as direct commands without the picker.
|
||||
|
|
|
|||
|
|
@ -43,9 +43,9 @@ Every line in either direction is one JSON object terminated by `\n`. Object bou
|
|||
|
||||
| `type` | Direction | Description |
|
||||
|---|---|---|
|
||||
| any command (`prompt`, `abort`, …) | client → server | Request |
|
||||
| any command (`prompt`, `abort`, ...) | client → server | Request |
|
||||
| `response` | server → client | Reply to one command, correlated by `id` |
|
||||
| any event (`text_delta`, `tool_call`, …) | server → client | Stream notification (no `id`) |
|
||||
| any event (`text_delta`, `tool_call`, ...) | server → client | Stream notification (no `id`) |
|
||||
|
||||
## Commands
|
||||
|
||||
|
|
|
|||
|
|
@ -37,7 +37,7 @@ The model also has a `read_notes` tool. Ask it:
|
|||
|
||||
> "What did I tell you to remember?"
|
||||
|
||||
…and it will call the tool and tell you.
|
||||
...and it will call the tool and tell you.
|
||||
|
||||
## Storage
|
||||
|
||||
|
|
|
|||
|
|
@ -427,7 +427,7 @@ func openOrCreateSessionForBot(args Args, r Resolved, ag *core.Agent, version st
|
|||
return s, nil, err
|
||||
}
|
||||
|
||||
// maskToken returns "123456:ABC…xyz" so copies of zot telegram-bot status can be
|
||||
// maskToken returns "123456:ABC...xyz" so copies of zot telegram-bot status can be
|
||||
// pasted into bug reports without leaking the full token.
|
||||
func maskToken(tok string) string {
|
||||
if len(tok) <= 10 {
|
||||
|
|
@ -436,13 +436,13 @@ func maskToken(tok string) string {
|
|||
// telegram tokens look like "123456789:ABCD..." — keep the id, mask the body.
|
||||
i := strings.IndexByte(tok, ':')
|
||||
if i < 0 {
|
||||
return tok[:4] + "…" + tok[len(tok)-4:]
|
||||
return tok[:4] + "..." + tok[len(tok)-4:]
|
||||
}
|
||||
body := tok[i+1:]
|
||||
if len(body) < 8 {
|
||||
return tok[:i+1] + "<hidden>"
|
||||
}
|
||||
return tok[:i+1] + body[:3] + "…" + body[len(body)-3:]
|
||||
return tok[:i+1] + body[:3] + "..." + body[len(body)-3:]
|
||||
}
|
||||
|
||||
// _ compile-time hint so the strconv import stays if we later add numeric parsing.
|
||||
|
|
|
|||
|
|
@ -216,7 +216,11 @@ func (d *confirmDialog) Render(th tui.Theme, width int) []string {
|
|||
// Truncate the tail if the line would exceed width; keeps
|
||||
// the option numbers always visible.
|
||||
if visibleLen(plain) > width-2 {
|
||||
plain = plain[:width-3] + "\u2026"
|
||||
if width <= 5 {
|
||||
plain = "..."[:max(0, width-2)]
|
||||
} else {
|
||||
plain = plain[:width-5] + "..."
|
||||
}
|
||||
}
|
||||
if i == cursor {
|
||||
lines = append(lines, th.PadHighlight(plain, width))
|
||||
|
|
|
|||
|
|
@ -42,7 +42,7 @@ func renderHelpBlock(th tui.Theme, width int) []string {
|
|||
}
|
||||
|
||||
// Label column width uses display cells, not byte length, so
|
||||
// single-cell multibyte runes (← → - …) don't over-count and leave
|
||||
// single-cell multibyte runes (← → - ...) don't over-count and leave
|
||||
// a raggedy right edge. `len("alt+← / alt+→")` is 17 bytes but
|
||||
// only 13 cells; padding off byte length would either overshoot
|
||||
// (setting labelWidth too high and wasting space on every row)
|
||||
|
|
|
|||
|
|
@ -799,7 +799,7 @@ func (i *Interactive) buildChatLocked(cols int) []string {
|
|||
// narrow terminal.
|
||||
line := "✓ " + i.statusOK
|
||||
if cols > 4 && len(line) > cols {
|
||||
line = line[:cols-1] + "…"
|
||||
line = line[:cols-3] + "..."
|
||||
}
|
||||
chat = append(chat, i.cfg.Theme.FG256(i.cfg.Theme.Tool, line), "")
|
||||
}
|
||||
|
|
@ -1305,7 +1305,7 @@ func clipBottomClippedImages(lines []string) []string {
|
|||
// fixed status bar area. Suppress that image for this frame.
|
||||
//
|
||||
// When the image lives inside a tool box, the reservation rows
|
||||
// are wrapped in vertical box edges ("│ … │"); those rows
|
||||
// are wrapped in vertical box edges ("│ ... │"); those rows
|
||||
// look non-blank under a naive whitespace check but are still
|
||||
// reservation rows for this scan, so treat them as blank.
|
||||
foundInfo := false
|
||||
|
|
@ -1329,7 +1329,7 @@ func clipBottomClippedImages(lines []string) []string {
|
|||
// stripping ANSI escape sequences, surrounding whitespace, and the
|
||||
// vertical box edges drawn by the tool-box renderer. Used by
|
||||
// clipBottomClippedImages so an image's reservation rows still count
|
||||
// as blank when those rows are wrapped in "│ … │" inside a tool box.
|
||||
// as blank when those rows are wrapped in "│ ... │" inside a tool box.
|
||||
func isBoxBlankLine(line string) bool {
|
||||
stripped := stripANSIBytes(line)
|
||||
stripped = strings.TrimSpace(stripped)
|
||||
|
|
@ -1338,7 +1338,7 @@ func isBoxBlankLine(line string) bool {
|
|||
return stripped == ""
|
||||
}
|
||||
|
||||
// stripANSIBytes removes ANSI CSI escape sequences (ESC '[' … final
|
||||
// stripANSIBytes removes ANSI CSI escape sequences (ESC '[' ... final
|
||||
// byte) from s without pulling in the regexp package. Mirrors the
|
||||
// internal helper in package tui; the duplicated copy avoids exporting
|
||||
// it just for one caller.
|
||||
|
|
@ -1423,10 +1423,10 @@ func truncateLine(s string, n int) string {
|
|||
if len(runes) <= n {
|
||||
return s
|
||||
}
|
||||
if n <= 1 {
|
||||
return "…"
|
||||
if n <= 3 {
|
||||
return strings.Repeat(".", n)
|
||||
}
|
||||
return string(runes[:n-1]) + "…"
|
||||
return string(runes[:n-3]) + "..."
|
||||
}
|
||||
|
||||
// ctrlCExitWindow is how long after a ctrl+c press a *second* press
|
||||
|
|
@ -3626,7 +3626,7 @@ func (i *Interactive) startTurnWithImages(parent context.Context, prompt string,
|
|||
i.queued = append([]string{prompt}, i.queued...)
|
||||
}
|
||||
i.statusErr = ""
|
||||
i.extNotes = append(i.extNotes, autoCompactNoteLine(i.cfg.Theme, "context near limit — condensing history before sending…"))
|
||||
i.extNotes = append(i.extNotes, autoCompactNoteLine(i.cfg.Theme, "context near limit — condensing history before sending..."))
|
||||
i.pendingPostCompactNote = "context auto-compacted; sending your last message"
|
||||
i.mu.Unlock()
|
||||
i.invalidate()
|
||||
|
|
@ -4042,7 +4042,7 @@ func (i *Interactive) runReloadExt(ctx context.Context) {
|
|||
return
|
||||
}
|
||||
i.mu.Lock()
|
||||
i.statusOK = "reloading extensions…"
|
||||
i.statusOK = "reloading extensions..."
|
||||
i.statusErr = ""
|
||||
i.mu.Unlock()
|
||||
i.invalidate()
|
||||
|
|
|
|||
|
|
@ -169,7 +169,11 @@ func formatJumpRowPlain(t jumpTarget, maxWidth int) string {
|
|||
}
|
||||
preview := t.Preview
|
||||
if len(preview) > room {
|
||||
preview = preview[:room-1] + "\u2026"
|
||||
if room <= 3 {
|
||||
preview = "..."[:room]
|
||||
} else {
|
||||
preview = preview[:room-3] + "..."
|
||||
}
|
||||
}
|
||||
return left + preview
|
||||
}
|
||||
|
|
|
|||
|
|
@ -188,10 +188,10 @@ func (d *modelDialog) Render(th tui.Theme, width int) []string {
|
|||
}
|
||||
|
||||
if start > 0 {
|
||||
lines = append(lines, th.FG256(th.Muted, fmt.Sprintf(" … %d more above", start)))
|
||||
lines = append(lines, th.FG256(th.Muted, fmt.Sprintf(" ... %d more above", start)))
|
||||
}
|
||||
if end < len(d.view) {
|
||||
lines = append(lines, th.FG256(th.Muted, fmt.Sprintf(" … %d more below", len(d.view)-end)))
|
||||
lines = append(lines, th.FG256(th.Muted, fmt.Sprintf(" ... %d more below", len(d.view)-end)))
|
||||
}
|
||||
|
||||
lines = append(lines, frameRule(th, width))
|
||||
|
|
|
|||
|
|
@ -165,10 +165,10 @@ func (d *rescueDialog) Render(th tui.Theme, width int) []string {
|
|||
}
|
||||
}
|
||||
if start > 0 {
|
||||
lines = append(lines, th.FG256(th.Muted, fmt.Sprintf(" … %d more above", start)))
|
||||
lines = append(lines, th.FG256(th.Muted, fmt.Sprintf(" ... %d more above", start)))
|
||||
}
|
||||
if end < len(d.view) {
|
||||
lines = append(lines, th.FG256(th.Muted, fmt.Sprintf(" … %d more below", len(d.view)-end)))
|
||||
lines = append(lines, th.FG256(th.Muted, fmt.Sprintf(" ... %d more below", len(d.view)-end)))
|
||||
}
|
||||
|
||||
lines = append(lines, frameRule(th, width))
|
||||
|
|
@ -278,7 +278,7 @@ func shortError(msg string) string {
|
|||
if len(msg) <= max {
|
||||
return msg
|
||||
}
|
||||
return msg[:max] + "…"
|
||||
return msg[:max] + "..."
|
||||
}
|
||||
|
||||
// extractFailedProvider tries to pull the failing provider name out
|
||||
|
|
|
|||
|
|
@ -193,13 +193,17 @@ func formatSessionRowPlain(s core.SessionSummary, maxWidth int) string {
|
|||
}
|
||||
runes := []rune(summary)
|
||||
if len(runes) > room {
|
||||
summary = string(runes[:room-1]) + "…"
|
||||
summary = string(runes[:room-3]) + "..."
|
||||
}
|
||||
row := left + summary
|
||||
// Hard clamp: ensure the full row never exceeds maxWidth.
|
||||
rowRunes := []rune(row)
|
||||
if len(rowRunes) > maxWidth {
|
||||
row = string(rowRunes[:maxWidth-1]) + "…"
|
||||
if maxWidth <= 3 {
|
||||
row = strings.Repeat(".", maxWidth)
|
||||
} else {
|
||||
row = string(rowRunes[:maxWidth-3]) + "..."
|
||||
}
|
||||
}
|
||||
return row
|
||||
}
|
||||
|
|
|
|||
|
|
@ -149,7 +149,7 @@ func formatTreeRow(n *core.TreeNode) string {
|
|||
}
|
||||
}
|
||||
if len(preview) > 50 {
|
||||
preview = preview[:49] + "\u2026"
|
||||
preview = preview[:47] + "..."
|
||||
}
|
||||
return fmt.Sprintf("%-14s %s %d msgs", when, preview, n.Summary.MessageCount)
|
||||
}
|
||||
|
|
|
|||
|
|
@ -173,7 +173,11 @@ func formatSkillRow(s *skills.Skill, maxWidth int) string {
|
|||
}
|
||||
desc := s.Description
|
||||
if len(desc) > room {
|
||||
desc = desc[:room-1] + "\u2026"
|
||||
if room <= 3 {
|
||||
desc = strings.Repeat(".", room)
|
||||
} else {
|
||||
desc = desc[:room-3] + "..."
|
||||
}
|
||||
}
|
||||
return left + desc + src
|
||||
}
|
||||
|
|
@ -185,10 +189,10 @@ func truncateLineSafe(s string, n int) string {
|
|||
if len(r) <= n {
|
||||
return s
|
||||
}
|
||||
if n <= 1 {
|
||||
return "\u2026"
|
||||
if n <= 3 {
|
||||
return strings.Repeat(".", n)
|
||||
}
|
||||
return string(r[:n-1]) + "\u2026"
|
||||
return string(r[:n-3]) + "..."
|
||||
}
|
||||
|
||||
// visibleWindow centers cursor in a window of size n within total
|
||||
|
|
|
|||
|
|
@ -1357,12 +1357,16 @@ func formatSwarmRow(r swarm.AgentSnapshot, maxWidth int) string {
|
|||
act = r.Task
|
||||
}
|
||||
if len([]rune(act)) > room {
|
||||
act = string([]rune(act)[:room-1]) + "…"
|
||||
act = string([]rune(act)[:room-3]) + "..."
|
||||
}
|
||||
row := left + act
|
||||
rowRunes := []rune(row)
|
||||
if len(rowRunes) > maxWidth {
|
||||
row = string(rowRunes[:maxWidth-1]) + "…"
|
||||
if maxWidth <= 3 {
|
||||
row = strings.Repeat(".", maxWidth)
|
||||
} else {
|
||||
row = string(rowRunes[:maxWidth-3]) + "..."
|
||||
}
|
||||
}
|
||||
return row
|
||||
}
|
||||
|
|
|
|||
|
|
@ -254,7 +254,10 @@ func truncateForLog(s string, n int) string {
|
|||
if len(s) <= n {
|
||||
return s
|
||||
}
|
||||
return s[:n-1] + "…"
|
||||
if n <= 3 {
|
||||
return strings.Repeat(".", n)
|
||||
}
|
||||
return s[:n-3] + "..."
|
||||
}
|
||||
|
||||
// _ keeps the provider import used; provider types may surface
|
||||
|
|
|
|||
|
|
@ -108,7 +108,7 @@ func runUpdate(version string) error {
|
|||
ctx, cancel := context.WithTimeout(context.Background(), 120*time.Second)
|
||||
defer cancel()
|
||||
|
||||
fmt.Println("zot update: querying latest release…")
|
||||
fmt.Println("zot update: querying latest release...")
|
||||
tag, releaseURL, err := fetchLatestRelease(ctx)
|
||||
if err != nil {
|
||||
return fmt.Errorf("query latest release: %w", err)
|
||||
|
|
@ -147,7 +147,7 @@ func runUpdate(version string) error {
|
|||
// users can clear /tmp themselves.
|
||||
defer func() { _ = os.RemoveAll(tmp) }()
|
||||
|
||||
fmt.Println("zot update: downloading checksums.txt…")
|
||||
fmt.Println("zot update: downloading checksums.txt...")
|
||||
sumsPath := filepath.Join(tmp, "checksums.txt")
|
||||
if err := downloadFile(ctx, sumsURL, sumsPath); err != nil {
|
||||
return fmt.Errorf("download checksums: %w", err)
|
||||
|
|
@ -157,13 +157,13 @@ func runUpdate(version string) error {
|
|||
return err
|
||||
}
|
||||
|
||||
fmt.Println("zot update: downloading archive…")
|
||||
fmt.Println("zot update: downloading archive...")
|
||||
archivePath := filepath.Join(tmp, assetName)
|
||||
if err := downloadFile(ctx, assetURL, archivePath); err != nil {
|
||||
return fmt.Errorf("download archive: %w", err)
|
||||
}
|
||||
|
||||
fmt.Println("zot update: verifying checksum…")
|
||||
fmt.Println("zot update: verifying checksum...")
|
||||
gotSum, err := sha256File(archivePath)
|
||||
if err != nil {
|
||||
return fmt.Errorf("hash archive: %w", err)
|
||||
|
|
@ -172,7 +172,7 @@ func runUpdate(version string) error {
|
|||
return fmt.Errorf("checksum mismatch for %s: got %s, want %s", assetName, gotSum, wantSum)
|
||||
}
|
||||
|
||||
fmt.Println("zot update: extracting…")
|
||||
fmt.Println("zot update: extracting...")
|
||||
extractDir := filepath.Join(tmp, "extracted")
|
||||
if err := os.MkdirAll(extractDir, 0o755); err != nil {
|
||||
return fmt.Errorf("mkdir extract: %w", err)
|
||||
|
|
|
|||
|
|
@ -177,5 +177,8 @@ func truncatePreview(s string, n int) string {
|
|||
if len(s) <= n {
|
||||
return s
|
||||
}
|
||||
return s[:n-1] + "\u2026"
|
||||
if n <= 3 {
|
||||
return "..."[:n]
|
||||
}
|
||||
return s[:n-3] + "..."
|
||||
}
|
||||
|
|
|
|||
|
|
@ -2,6 +2,7 @@ package core
|
|||
|
||||
import (
|
||||
"encoding/json"
|
||||
"strings"
|
||||
"sync"
|
||||
"testing"
|
||||
)
|
||||
|
|
@ -180,5 +181,5 @@ func TestBuildPreview(t *testing.T) {
|
|||
}
|
||||
|
||||
func hasEllipsis(s string) bool {
|
||||
return len(s) > 0 && s[len(s)-len("\u2026"):] == "\u2026"
|
||||
return strings.HasSuffix(s, "...")
|
||||
}
|
||||
|
|
|
|||
|
|
@ -509,7 +509,10 @@ func truncate(s string, n int) string {
|
|||
if len(s) <= n {
|
||||
return s
|
||||
}
|
||||
return s[:n-1] + "…"
|
||||
if n <= 3 {
|
||||
return strings.Repeat(".", n)
|
||||
}
|
||||
return s[:n-3] + "..."
|
||||
}
|
||||
|
||||
func lastN(lines []string, n int) []string {
|
||||
|
|
|
|||
|
|
@ -1160,7 +1160,7 @@ func max(a, b int) int {
|
|||
}
|
||||
|
||||
// pasteCollapseLineThreshold and pasteCollapseCharThreshold govern
|
||||
// when a bracketed paste gets collapsed to a [pasted text #N …]
|
||||
// when a bracketed paste gets collapsed to a [pasted text #N ...]
|
||||
// placeholder instead of being inserted inline. Either trigger
|
||||
// alone is enough — a 500-line log dump and a 1200-character
|
||||
// one-line log entry both bloat the editor in ways the user
|
||||
|
|
|
|||
|
|
@ -654,7 +654,7 @@ func (v *View) renderMessage(m provider.Message, width int, turnOpen bool) []str
|
|||
// before the metadata caption) are tagged with the
|
||||
// imageFootprintSentinel by renderImageBlock. Strip
|
||||
// the tag, parse the optional width hint, then wrap
|
||||
// the row in the usual │ … │ box edges so the
|
||||
// the row in the usual │ ... │ box edges so the
|
||||
// frame stays continuous around the image.
|
||||
imgCells, stripped := parseImageFootprint(line)
|
||||
if hasImageEscapeLine(stripped) {
|
||||
|
|
@ -775,7 +775,7 @@ func (v *View) renderLiveToolBody(tc ToolCallView, width int) []string {
|
|||
}
|
||||
|
||||
// wrapLiveBody returns the streaming body content as a list of
|
||||
// box-side rows: each line wrapped in │ … │ with right padding so
|
||||
// box-side rows: each line wrapped in │ ... │ with right padding so
|
||||
// the closing edge sits at column width-1. The caller (renderToolCall)
|
||||
// supplies the surrounding top/bottom edges so the live overlay
|
||||
// renders as a closed box matching the finalised transcript form.
|
||||
|
|
@ -848,10 +848,10 @@ func toolBoxTop(th Theme, label string, width int) string {
|
|||
// terminals (the chat column is usually wide enough).
|
||||
over := -fill
|
||||
runes := []rune(label)
|
||||
if over+1 < len(runes) {
|
||||
label = string(runes[:len(runes)-over-1]) + "…"
|
||||
if over+3 < len(runes) {
|
||||
label = string(runes[:len(runes)-over-3]) + "..."
|
||||
} else {
|
||||
label = "…"
|
||||
label = "..."
|
||||
}
|
||||
used = visibleWidth(prefix) + visibleWidth(label) + visibleWidth(suffix)
|
||||
fill = w - used - 1
|
||||
|
|
@ -906,7 +906,7 @@ func hasImageEscapeLine(s string) bool {
|
|||
// imageFootprintSentinel marks rows that belong to an inline-image's
|
||||
// reserved footprint — the escape row plus the blank rows below it
|
||||
// plus the gap row before the metadata caption. Any consumer that
|
||||
// wraps content in box edges (│ … │) detects the sentinel, strips
|
||||
// wraps content in box edges (│ ... │) detects the sentinel, strips
|
||||
// it, and emits the row — the image graphics rectangle paints over
|
||||
// whatever was drawn there. Uses a non-printing C0 control byte so
|
||||
// it can never appear in normal text or in an ANSI escape sequence
|
||||
|
|
@ -1124,7 +1124,7 @@ func (v *View) collapseToolBody(lines []string, hasImage bool) []string {
|
|||
// colors matching git diff conventions.
|
||||
func (v *View) renderToolText(text string, width, defaultColor int, sourcePath string, startLine int) []string {
|
||||
// Legacy path: transcripts saved before we dropped line numbers
|
||||
// from the read tool still carry " 1\t…" prefixes. Detect and
|
||||
// from the read tool still carry " 1\t..." prefixes. Detect and
|
||||
// strip them, then fall through to the highlighter.
|
||||
if looksLikeNumberedFile(text) {
|
||||
return v.renderNumberedFile(text, sourcePath)
|
||||
|
|
@ -1308,7 +1308,10 @@ func (v *View) renderDiffRow(line string, width, color int, lineNo int, mark byt
|
|||
// output is unreliable.
|
||||
maxCode := width - 4 /* indent */ - 7 /* gutter (sign+5 digits+tab) */
|
||||
if maxCode > 0 && len(code) > maxCode {
|
||||
trunc := code[:maxCode-1] + "…"
|
||||
trunc := strings.Repeat(".", maxCode)
|
||||
if maxCode > 3 {
|
||||
trunc = code[:maxCode-3] + "..."
|
||||
}
|
||||
if lang != "" {
|
||||
if h := HighlightCode(trunc, lang); len(h) == 1 {
|
||||
codeRendered = h[0]
|
||||
|
|
@ -1379,7 +1382,7 @@ func (v *View) renderImageBlock(b provider.ImageBlock, width int) []string {
|
|||
// edge plus a small interior gutter so the image rectangle
|
||||
// sits visibly inside the frame instead of kissing the │.
|
||||
// The escape row carries a width hint after the sentinel
|
||||
// ("\x1e<cells>\x1e\u2026") so toolBoxSide knows how many
|
||||
// ("\x1e<cells>\x1e...") so toolBoxSide knows how many
|
||||
// cells the image occupies and can pad to the right edge.
|
||||
widthHint := fmt.Sprintf("%s%d%s", imageFootprintSentinel, actualCells, imageFootprintSentinel)
|
||||
out := make([]string, 0, rows+3)
|
||||
|
|
@ -1680,7 +1683,7 @@ func (v *View) renderUnifiedDiff(text string, width int, sourcePath string) []st
|
|||
continue
|
||||
}
|
||||
if l == "..." {
|
||||
out = append(out, " "+v.Theme.FG256(v.Theme.Muted, "…"))
|
||||
out = append(out, " "+v.Theme.FG256(v.Theme.Muted, "..."))
|
||||
continue
|
||||
}
|
||||
switch l[0] {
|
||||
|
|
@ -1899,7 +1902,7 @@ func truncateLines(s string, n int) string {
|
|||
if len(lines) <= n {
|
||||
return s
|
||||
}
|
||||
return strings.Join(lines[:n], "\n") + "\n … (" + fmt.Sprintf("%d", len(lines)-n) + " more)"
|
||||
return strings.Join(lines[:n], "\n") + "\n ... (" + fmt.Sprintf("%d", len(lines)-n) + " more)"
|
||||
}
|
||||
|
||||
// renderCompactionBlock renders a compaction summary as a distinct
|
||||
|
|
|
|||
Loading…
Add table
Reference in a new issue