ascii-chat 0.11.33
Video chat in your terminal
Loading...
Searching...
No Matches
layout.h File Reference

Two-column layout helpers for help text and option formatting. More...

Go to the source code of this file.

Macros

#define LAYOUT_COLUMN_WIDTH   75
 
#define LAYOUT_DESCRIPTION_START_COL   79
 
#define LAYOUT_NARROW_TERMINAL_THRESHOLD   55
 

Functions

void layout_print_wrapped_description (FILE *stream, const char *text, int indent_width, int term_width, int continuation_indent_extra)
 Print text with wrapping and proper indentation.
 
void layout_print_two_column_row (FILE *stream, const char *first_column, const char *second_column, int first_col_len, int term_width, int continuation_indent_extra)
 Print two-column row with automatic wrapping.
 

Detailed Description

Two-column layout helpers for help text and option formatting.

Provides reusable functions for formatting two-column layouts with proper alignment and text wrapping. Used by both mode-level help and binary-level help.

Definition in file layout.h.

Macro Definition Documentation

◆ LAYOUT_COLUMN_WIDTH

#define LAYOUT_COLUMN_WIDTH   75

Maximum width for first column before description moves to next line

Definition at line 19 of file layout.h.

◆ LAYOUT_DESCRIPTION_START_COL

#define LAYOUT_DESCRIPTION_START_COL   79

Starting column for description text (column 79 = 2 spaces + 75 chars + 2 spacing)

Definition at line 22 of file layout.h.

◆ LAYOUT_NARROW_TERMINAL_THRESHOLD

#define LAYOUT_NARROW_TERMINAL_THRESHOLD   55

Terminal width threshold for switching to vertical layout

Definition at line 25 of file layout.h.

Function Documentation

◆ layout_print_two_column_row()

void layout_print_two_column_row ( FILE *  stream,
const char *  first_column,
const char *  second_column,
int  first_col_len,
int  term_width,
int  continuation_indent_extra 
)

Print two-column row with automatic wrapping.

Prints first column text, then second column description with proper alignment. If first column is too long (>45 chars), description moves to next line. Second column wraps at description start column.

Parameters
streamOutput file stream
first_columnText for first column
second_columnDescription for second column
first_col_lenLength of first column text (excluding ANSI codes)
term_widthTerminal width
continuation_indent_extraExtra spaces for continuation lines (0 for none, 2 for options)

Definition at line 220 of file layout.c.

221 {
222 if (!stream || !first_column || !second_column)
223 return;
224
225 // Strip ANSI escape codes before calculating display width
226 char *stripped = ansi_strip_escapes(first_column, strlen(first_column));
227
228 // Calculate actual display width of first column (accounts for UTF-8, excluding ANSI codes)
229 int display_width = utf8_display_width(stripped ? stripped : first_column);
230 if (display_width < 0)
231 display_width = first_col_len; // Fallback to provided value if available
232
233 SAFE_FREE(stripped);
234
235 // Use calculated first_col_len if provided (accounts for UTF-8 and ANSI codes),
236 // otherwise default to 35. The provided width respects both colored text and UTF-8 characters.
237 // Allow the first column to expand up to 60% of terminal width, but leave room for description.
238 int fixed_first_col_width = (first_col_len > 0) ? first_col_len : 35;
239 int max_first_col_width = (term_width * 6) / 10;
240 if (max_first_col_width < 40)
241 max_first_col_width = 40;
242 if (fixed_first_col_width > max_first_col_width) {
243 fixed_first_col_width = max_first_col_width;
244 }
245
246 // Second column starts after first column (with 2 spaces padding)
247 // This allows descriptions to be quite wide for wrapping
248 int second_col_start = 2 + fixed_first_col_width + 2;
249
250 // At narrow terminal widths, force single-column layout
251 bool force_single_column = (term_width <= 90);
252
253 // Position where first column ends (including leading spaces)
254 int first_col_end = 2 + display_width;
255
256 // Check if first column fits within the fixed width
257 bool fits_in_first_column_width = (display_width <= fixed_first_col_width);
258
259 // If first column fits within its width and we're not forcing single column, try same-line layout
260 if (fits_in_first_column_width && !force_single_column) {
261 // Print first column
262 fprintf(stream, " %s", first_column);
263
264 // Pad to second column start position
265 int padding = second_col_start - first_col_end;
266 if (padding > 0) {
267 fprintf(stream, "%*s", padding, "");
268 } else {
269 fprintf(stream, " ");
270 }
271
272 // Print description with wrapping at second column position
273 layout_print_wrapped_description(stream, second_column, second_col_start, term_width, continuation_indent_extra);
274 fprintf(stream, "\n");
275 } else if (force_single_column) {
276 // Terminal too narrow, put description on next line with indent
277 fprintf(stream, " %s\n", first_column);
278
279 // Only print description if it exists
280 if (second_column && second_column[0] != '\0') {
281 int description_indent = 4;
282 for (int i = 0; i < description_indent; i++)
283 fprintf(stream, " ");
284
285 layout_print_wrapped_description(stream, second_column, description_indent, term_width,
286 continuation_indent_extra);
287 fprintf(stream, "\n");
288 }
289 } else {
290 // First column overflows its fixed width - bump description to next line
291 // Print first column as-is (it will overflow)
292 fprintf(stream, " %s\n", first_column);
293
294 // Only print description if it exists
295 if (second_column && second_column[0] != '\0') {
296 // Print description on next line at second column position, with wide wrapping
297 for (int i = 0; i < second_col_start; i++)
298 fprintf(stream, " ");
299 layout_print_wrapped_description(stream, second_column, second_col_start, term_width, continuation_indent_extra);
300 fprintf(stream, "\n");
301 }
302 }
303}
char * ansi_strip_escapes(const char *input, size_t input_len)
Strip all ANSI escape sequences from a string.
Definition ansi.c:23
#define SAFE_FREE(ptr)
Definition common.h:376
int display_width(const char *text)
Calculate the visible display width of text with ANSI escape codes.
int utf8_display_width(const char *str)
Calculate terminal display width of a UTF-8 string.
Definition utf8.c:46
void layout_print_wrapped_description(FILE *stream, const char *text, int indent_width, int term_width, int continuation_indent_extra)
Print text with wrapping and proper indentation.
Definition layout.c:98

References ansi_strip_escapes(), display_width(), layout_print_wrapped_description(), SAFE_FREE, and utf8_display_width().

Referenced by options_config_print_options_sections_with_width(), options_config_print_usage(), and options_print_help_for_mode().

◆ layout_print_wrapped_description()

void layout_print_wrapped_description ( FILE *  stream,
const char *  text,
int  indent_width,
int  term_width,
int  continuation_indent_extra 
)

Print text with wrapping and proper indentation.

Wraps text to fit within terminal width, maintaining specified indentation for wrapped lines. Applies color formatting to metadata labels.

Parameters
streamOutput file stream
textPlain text to print (may contain wrapping markers)
indent_widthNumber of spaces to indent wrapped lines
term_widthTerminal width (0 for default 80)
continuation_indent_extraExtra spaces to add to continuation lines (0 for none, 2 for options)

Definition at line 98 of file layout.c.

99 {
100 if (!text || !stream)
101 return;
102
103 // Default terminal width if not specified
104 if (term_width <= 0)
105 term_width = 80;
106
107 // Available width for text after indentation, capped at 130 for readability
108 // Account for continuation lines having extra indent, so reduce available width accordingly
109 // This allows the description column itself to be up to 130 chars wide,
110 // starting from indent_width, for a max total line of indent_width + 130 (first line)
111 // or indent_width + continuation_indent_extra + 130 (continuation lines)
112 int available_width = term_width - indent_width - continuation_indent_extra;
113 if (available_width > 130)
114 available_width = 130;
115 if (available_width < 20)
116 available_width = 20;
117
118 const char *line_start = text;
119 const char *last_space = NULL;
120 const char *p = text;
121 bool inside_metadata = false;
122
123 while (*p) {
124 // Check for explicit newline
125 if (*p == '\n') {
126 // Print everything up to the newline
127 int text_len = p - line_start;
128 if (text_len > 0) {
129 char seg[BUFFER_SIZE_MEDIUM];
130 SAFE_STRNCPY(seg, line_start, text_len + 1);
131 layout_print_colored_segment(stream, seg);
132 }
133
134 fprintf(stream, "\n");
135 if (*(p + 1)) {
136 // Continuation lines get extra indent if specified
137 int continuation_indent = indent_width + continuation_indent_extra;
138 for (int i = 0; i < continuation_indent; i++)
139 fprintf(stream, " ");
140 }
141 p++;
142 line_start = p;
143 last_space = NULL;
144 inside_metadata = false;
145 continue;
146 }
147
148 // Check for start of metadata blocks (must check before space tracking)
149 // Use helper that accounts for ANSI codes in the metadata
150 if (!inside_metadata && is_metadata_start(p)) {
151 inside_metadata = true;
152 }
153
154 // Check for end of metadata blocks - always exit on )
155 // We'll re-enter when we see the next metadata marker
156 if (inside_metadata && *p == ')') {
157 inside_metadata = false;
158 }
159
160 // Track spaces for word wrapping, including inside metadata blocks
161 // Prefer breaks before metadata blocks by updating last_space even if we already have one
162 if (*p == ' ') {
163 const char *next = p + 1;
164 while (*next == ' ')
165 next++;
166 // Track space as break point if it's before metadata or a normal space (not before parenthetical)
167 if (is_metadata_start(next) || !last_space || *next != '(') {
168 last_space = p;
169 }
170 }
171
172 // Decode UTF-8 character length
173 int char_bytes = utf8_next_char_bytes(p, strlen(p));
174
175 if (char_bytes <= 0) {
176 // Invalid UTF-8 or end of string, stop
177 break;
178 }
179
180 // Advance to next character first
181 p += char_bytes;
182
183 // Calculate actual display width from line_start to current position (excluding ANSI codes)
184 int actual_width = calculate_segment_display_width(line_start, p - line_start);
185
186 // Check if we need to wrap
187 if (actual_width >= available_width && last_space && last_space > line_start) {
188 // Print text up to break point with colors applied
189 int text_len = last_space - line_start;
190 char seg[BUFFER_SIZE_MEDIUM];
191 SAFE_STRNCPY(seg, line_start, text_len + 1);
192 layout_print_colored_segment(stream, seg);
193
194 fprintf(stream, "\n");
195 // Continuation lines get extra indent if specified
196 int continuation_indent = indent_width + continuation_indent_extra;
197 for (int i = 0; i < continuation_indent; i++)
198 fprintf(stream, " ");
199
200 p = last_space + 1;
201 line_start = p;
202 last_space = NULL;
203 continue;
204 }
205 }
206
207 // Print remaining text
208 if (p > line_start) {
209 char seg[BUFFER_SIZE_MEDIUM];
210 int text_len = p - line_start;
211 SAFE_STRNCPY(seg, line_start, text_len + 1);
212 layout_print_colored_segment(stream, seg);
213 }
214}
#define BUFFER_SIZE_MEDIUM
Medium buffer size (512 bytes)
#define SAFE_STRNCPY(dst, src, size)
Definition common.h:414
int utf8_next_char_bytes(const char *str, size_t max_bytes)
Get byte length of next UTF-8 character.
Definition utf8.c:205

References BUFFER_SIZE_MEDIUM, SAFE_STRNCPY, and utf8_next_char_bytes().

Referenced by layout_print_two_column_row(), and options_print_help_for_mode().