@@ -334,3 +334,149 @@ pub fn (mut b Builder) write_repeated_rune(r rune, count int) {
334334 }
335335 }
336336}
337+
338+ // IndentParam holds configuration parameters for the indent() function
339+ @[params]
340+ pub struct IndentParam {
341+ pub mut :
342+ block_start rune = `{` // Character that starts a new block (+ indent)
343+ block_end rune = `}` // Character that ends a new block (- indent)
344+ indent_char rune = ` ` // Character used for indentation (space or tab)
345+ indent_count int = 4 // Number of indent_char per indentation level
346+ starting_level int // Initial indentation level (0 = no initial indent)
347+ }
348+
349+ // IndentState represents the current parsing state of the indent() function
350+ enum IndentState {
351+ normal // Normal state, processing regular characters
352+ in_string // Inside a string literal, ignoring formatting characters
353+ }
354+
355+ // indent formats a string by applying structured indentation based on block delimiters.
356+ // It processes the input string `s` and writes the formatted output to the `Builder` `b`.
357+ // The function preserves content inside string literals (both single and double quotes) and
358+ // configures indentation behavior through the `param` structure.
359+ //
360+ // Key behaviors:
361+ // 1. Removes existing indentation at the beginning of lines.
362+ // 2. Applies new indentation based on block nesting levels.
363+ // 3. Ignores block delimiters and formatting characters inside string literals.
364+ // 4. Keeps empty blocks (e.g., {}) on the same line.
365+ // 5. Inserts newlines after `block_start` and before `block_end` (except for empty blocks).
366+ // 6. Maintains existing line breaks from the input.
367+ //
368+ // Example:
369+ // ```v
370+ // import strings
371+ // input := 'User{name:"John" settings:{theme:"dark"}}'
372+ // mut b := strings.new_builder(64)
373+ // b.indent(input, indent_count: 2)
374+ // println(b.str()) // Formatted output: 'User{\n name:"John" settings:{\n theme:"dark"\n }\n}'
375+ // ```
376+ @[direct_array_access]
377+ pub fn (mut b Builder) indent (s string , param IndentParam) {
378+ if s.len == 0 {
379+ return
380+ }
381+
382+ mut state := IndentState.normal
383+ mut indent_level := param.starting_level
384+ mut string_char := `\0`
385+ mut at_line_start := true
386+ for i := 0 ; i < s.len; i++ {
387+ c := s[i]
388+ match state {
389+ // Normal state: process characters outside of string literals
390+ .normal {
391+ match c {
392+ `"` , `'` { // Note: quote characters for editor display "
393+ state = .in_string
394+ string_char = c
395+ // Add indentation if at the start of a line
396+ if at_line_start {
397+ b.write_repeated_rune (param.indent_char, indent_level * param.indent_count)
398+ at_line_start = false
399+ }
400+ // Write the opening quote
401+ b.write_rune (c)
402+ }
403+ param.block_start {
404+ // Start of a new block
405+ // Add indentation if at the start of a line
406+ if at_line_start {
407+ b.write_repeated_rune (param.indent_char, indent_level * param.indent_count)
408+ at_line_start = false
409+ }
410+
411+ // Write the block start character
412+ b.write_rune (c)
413+
414+ // Check for empty block (e.g., {})
415+ // Empty blocks stay on the same line
416+ if i + 1 < s.len && s[i + 1 ] == param.block_end {
417+ b.write_rune (param.block_end)
418+ i++
419+ } else {
420+ // Non-empty block: increase indentation and add newline
421+ indent_level++
422+ b.write_rune (`\n ` )
423+ at_line_start = true
424+ }
425+ }
426+ param.block_end {
427+ // End of a block
428+ // Decrease indentation level (but not below 0)
429+ if indent_level > 0 {
430+ indent_level--
431+ }
432+
433+ // If not at the start of a line, add a newline
434+ if ! at_line_start {
435+ b.write_rune (`\n ` )
436+ }
437+
438+ // Add indentation for the block end
439+ b.write_repeated_rune (param.indent_char, indent_level * param.indent_count)
440+ at_line_start = false
441+
442+ b.write_rune (c)
443+ }
444+ ` ` , `\t ` , `\r ` , `\n ` {
445+ // Whitespace characters
446+ // Only write whitespace if not at the start of a line
447+ if ! at_line_start {
448+ b.write_rune (c)
449+ }
450+
451+ // Newline resets the line start flag
452+ if c == `\n ` {
453+ at_line_start = true
454+ }
455+ }
456+ else {
457+ // Any other character
458+ // Add indentation if at the start of a line
459+ if at_line_start {
460+ b.write_repeated_rune (param.indent_char, indent_level * param.indent_count)
461+ at_line_start = false
462+ }
463+ b.write_rune (c)
464+ }
465+ }
466+ }
467+ .in_string {
468+ // Inside a string literal: preserve all characters as-is
469+ b.write_rune (c)
470+
471+ // Check for string termination
472+ // The character must match the opening quote and not be escaped
473+ if c == string_char {
474+ if s[i - 1 ] != `\\ ` {
475+ state = .normal
476+ string_char = `\0`
477+ }
478+ }
479+ }
480+ }
481+ }
482+ }
0 commit comments