Fix ANSI style preservation in visible-subs and modal rendering
- Track active styles before substring range and prepend them when entering range - Add reset codes around modal lines to prevent style bleeding between layers Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
This commit is contained in:
+31
-14
@@ -159,11 +159,13 @@
|
|||||||
(defn visible-subs
|
(defn visible-subs
|
||||||
"Substring based on visible character positions (ignoring ANSI escape codes).
|
"Substring based on visible character positions (ignoring ANSI escape codes).
|
||||||
Returns substring from visible position start to end (or end of string).
|
Returns substring from visible position start to end (or end of string).
|
||||||
Preserves ANSI escape sequences that affect the visible portion."
|
Preserves ANSI escape sequences that affect the visible portion, including
|
||||||
|
styles that were set before the start position but still active."
|
||||||
([s start] (visible-subs s start nil))
|
([s start] (visible-subs s start nil))
|
||||||
([s start end]
|
([s start end]
|
||||||
(when s
|
(when s
|
||||||
(let [ansi-pattern #"\u001b\[[0-9;]*m"
|
(let [ansi-pattern #"\u001b\[[0-9;]*m"
|
||||||
|
reset-pattern #"\u001b\[0m"
|
||||||
;; Split string into segments: ANSI codes and regular text
|
;; Split string into segments: ANSI codes and regular text
|
||||||
segments (loop [remaining s
|
segments (loop [remaining s
|
||||||
result []]
|
result []]
|
||||||
@@ -180,22 +182,32 @@
|
|||||||
(conj result {:type :text :text (subs remaining 0 idx)}))))
|
(conj result {:type :text :text (subs remaining 0 idx)}))))
|
||||||
;; No more ANSI codes, rest is text
|
;; No more ANSI codes, rest is text
|
||||||
(conj result {:type :text :text remaining}))))
|
(conj result {:type :text :text remaining}))))
|
||||||
;; Build result by tracking visible position
|
;; Build result by tracking visible position and active styles
|
||||||
result (loop [segs segments
|
result (loop [segs segments
|
||||||
visible-pos 0
|
visible-pos 0
|
||||||
output []
|
output []
|
||||||
in-range? false]
|
in-range? false
|
||||||
|
active-styles []] ;; Track styles set before range
|
||||||
(if (empty? segs)
|
(if (empty? segs)
|
||||||
output
|
output
|
||||||
(let [{:keys [type text]} (first segs)]
|
(let [{:keys [type text]} (first segs)]
|
||||||
(if (= type :ansi)
|
(if (= type :ansi)
|
||||||
;; Always include ANSI codes that appear in or after range start
|
(if (or in-range? (>= visible-pos start))
|
||||||
(recur (rest segs)
|
;; In range - include ANSI codes directly
|
||||||
visible-pos
|
(recur (rest segs)
|
||||||
(if (or in-range? (>= visible-pos start))
|
visible-pos
|
||||||
(conj output text)
|
(conj output text)
|
||||||
output)
|
in-range?
|
||||||
in-range?)
|
active-styles)
|
||||||
|
;; Before range - track active styles
|
||||||
|
(let [new-styles (if (re-matches reset-pattern text)
|
||||||
|
[] ;; Reset clears all active styles
|
||||||
|
(conj active-styles text))]
|
||||||
|
(recur (rest segs)
|
||||||
|
visible-pos
|
||||||
|
output
|
||||||
|
in-range?
|
||||||
|
new-styles)))
|
||||||
;; Text segment
|
;; Text segment
|
||||||
(let [seg-len (count text)
|
(let [seg-len (count text)
|
||||||
seg-end (+ visible-pos seg-len)
|
seg-end (+ visible-pos seg-len)
|
||||||
@@ -203,19 +215,24 @@
|
|||||||
(cond
|
(cond
|
||||||
;; Segment entirely before range - skip
|
;; Segment entirely before range - skip
|
||||||
(<= seg-end start)
|
(<= seg-end start)
|
||||||
(recur (rest segs) seg-end output false)
|
(recur (rest segs) seg-end output false active-styles)
|
||||||
|
|
||||||
;; Segment entirely within or after range end - take partial or stop
|
;; Segment entirely within or after range end - take partial or stop
|
||||||
(>= visible-pos effective-end)
|
(>= visible-pos effective-end)
|
||||||
output
|
output
|
||||||
|
|
||||||
;; Segment overlaps range
|
;; Segment overlaps range - entering range, prepend active styles
|
||||||
:else
|
:else
|
||||||
(let [take-start (max 0 (- start visible-pos))
|
(let [take-start (max 0 (- start visible-pos))
|
||||||
take-end (min seg-len (- effective-end visible-pos))
|
take-end (min seg-len (- effective-end visible-pos))
|
||||||
portion (subs text take-start take-end)]
|
portion (subs text take-start take-end)
|
||||||
|
;; Prepend active styles when first entering range
|
||||||
|
output-with-styles (if in-range?
|
||||||
|
output
|
||||||
|
(into output active-styles))]
|
||||||
(recur (rest segs)
|
(recur (rest segs)
|
||||||
seg-end
|
seg-end
|
||||||
(conj output portion)
|
(conj output-with-styles portion)
|
||||||
true))))))))]
|
true
|
||||||
|
active-styles))))))))]
|
||||||
(apply str result)))))
|
(apply str result)))))
|
||||||
|
|||||||
+1
-1
@@ -282,7 +282,7 @@
|
|||||||
bg-after (if (< bg-after-start bg-width)
|
bg-after (if (< bg-after-start bg-width)
|
||||||
(ansi/visible-subs padded-bg-line bg-after-start)
|
(ansi/visible-subs padded-bg-line bg-after-start)
|
||||||
"")]
|
"")]
|
||||||
(str bg-before modal-line bg-after))
|
(str bg-before ansi/reset modal-line ansi/reset bg-after))
|
||||||
;; No modal on this row, just background
|
;; No modal on this row, just background
|
||||||
(ansi/pad-right bg-line bg-width))))))
|
(ansi/pad-right bg-line bg-width))))))
|
||||||
|
|
||||||
|
|||||||
Reference in New Issue
Block a user