Proposed
Building panel-based layouts (e.g., a file browser with a list on the left and details box on the right) exposed three problems in the current layout system:
split-lines is duplicated and drops trailing empty linesThree private split-lines functions exist in layout.clj, border.clj, and overlay.clj. All use clojure.string/split-lines, which drops trailing empty strings. This breaks the rendering pipeline when :height is used with borders:
align-vertical "a\nb" height=5 → "a\nb\n\n\n" (correct, 5 lines)
align-horizontal processes it → "a\nb" (3 trailing lines lost)
apply-border wraps it → only 4 lines instead of 7
The :height style option is effectively broken when combined with :border.
join-horizontal concatenates text blocks side-by-side but doesn't guarantee the total width equals the terminal width. There's no way to say "left column gets the remaining space, right column is 36 chars wide." Building a two-panel layout requires manual line-by-line concatenation with pad-right:
;; What you have to write today
(defn- two-columns [left right left-width height]
(let [left-lines (str/split-lines left)
right-lines (str/split-lines right)]
(str/join "\n" (map (fn [i]
(str (w/pad-right (nth left-lines i "") left-width)
(nth right-lines i "")))
(range height)))))
Text that exceeds a column width can only be truncated (charm.ansi.width/truncate). There's no way to wrap text to fit within a width, which is needed for content panes and description text.
Add three focused primitives rather than a full layout engine.
split-lines in charm.ansi.widthMove split-lines to charm.ansi.width as a public function. Use (str/split text #"\n" -1) to preserve trailing empty lines. Update layout.clj, border.clj, and overlay.clj to use it.
(defn split-lines
"Split text into lines, preserving trailing empty lines.
Unlike clojure.string/split-lines, this is a true inverse of
(clojure.string/join \"\\n\" lines)."
[s]
(if (or (nil? s) (empty? s))
[""]
(str/split s #"\n" -1)))
columns function in charm.style.layoutA function that joins pre-rendered text blocks into a fixed-width, fixed-height grid. Each column has a fixed width. Rows are padded/truncated to the specified height.
(defn columns
"Join text blocks into a fixed-width row layout.
Each column is a map with :content (string) and :width (int).
The last column's width is optional — it takes whatever space it has.
Options:
:height - Total height in lines (default: tallest column)"
[cols & {:keys [height]}]
...)
Usage:
(columns [{:content file-list-view :width 44}
{:content details-view}]
:height 20)
This operates at the line level: split each column's content into lines, pad each line to the column's width with pad-right, concatenate row by row.
word-wrap in charm.ansi.widthWrap text to fit within a display width, breaking at word boundaries.
(defn word-wrap
"Wrap text to fit within a display width, breaking at spaces.
Preserves existing line breaks."
[s width]
...)
split-lines duplication eliminated — single source of truth in charm.ansi.width:height + :border works correctly in the style pipelinecolumnsword-wrap enables content-aware text display in fixed-width panessplit-lines to preserve trailing empty lines changes behavior for all layout functions — needs careful testingcolumns is intentionally simple (fixed widths only) — proportional/flex sizing is left for later if neededcolumns is not a component — it's a pure layout function that works on pre-rendered stringsjoin-horizontal/join-vertical remain useful for simpler cases where exact width control isn't neededCan you improve this documentation?Edit on GitHub
cljdoc builds & hosts documentation for Clojure/Script libraries
| Ctrl+k | Jump to recent docs |
| ← | Move to previous article |
| → | Move to next article |
| Ctrl+/ | Jump to the search field |