stringwrap

package module
v1.0.4 Latest Latest
Warning

This package is not in the latest version of its module.

Go to latest
Published: Jun 21, 2025 License: MIT Imports: 8 Imported by: 0

README ยถ

stringwrap logo

Stringwrap is a Go package for wrapping strings by visual width with optional word splitting and full ANSI + grapheme cluster support. Designed for precision line-wrapping, ideal for terminal output, formatted logs, or editors that require accurate metadata per line segment.

โœจ Features

This library offers robust string wrapping capabilities with a focus on accurate visual representation and detailed metadata:

  • Intelligent Wrapping: Adapts to the viewable width by leveraging the runewidth library for precise character width calculation.
  • Comprehensive Text Handling:
    • Ignores ANSI escape codes for width calculations while preserving them in the output.
    • Correctly processes Unicode grapheme clusters, ensuring accurate wrapping of emojis and accented characters.
    • Supports configurable tab sizes.
    • Respects hard breaks (\n) in the input string.
    • Provides optional word splitting for finer-grained control.
    • Handles non-breaking spaces (\u00A0) to prevent unwanted line breaks.
  • Detailed Line Metadata: For each wrapped line, the library provides valuable information:
    • Byte and rune offsets within the original string.
    • The visual width of the wrapped line.
    • The index of the segment from the original line that this wrapped line belongs to.
    • An indication of whether the line ended due to a hard break or soft wrapping.
    • A flag indicating if the segment ends with a word that was split during wrapping.

๐Ÿ’ก Why Grapheme Clusters Matter

Both StringWrap and StringWrapSplit use Unicode grapheme cluster parsing (via the uniseg library) rather than simple rune iteration. This is crucial for accurate width calculation with complex Unicode sequences:

  • ZWJ Emojis: Sequences like "๐Ÿ‘ฉโ€๐Ÿ’ป" (woman technologist) contain multiple runes but display as a single character
  • Combining Marks: Characters like "รฉ" (e + combining acute accent) must be treated as one unit
  • Full-width Characters: Asian characters and emojis that occupy two columns

While this approach is slower than rune-based processing, it prevents incorrect wrapping that would occur with naive rune counting. For applications requiring precise visual alignment (terminals, editors, formatted output), this accuracy is essential.

๐Ÿš€ Getting Started

go get github.com/galactixx/stringwrap@latest

๐Ÿ“š Usage

Regular String Wrapping

import "github.com/galactixx/stringwrap"

wrapped, meta, err := stringwrap.StringWrap("Hello world! ๐ŸŒŸ", 10, 4)

fmt.Println(wrapped)
Output:
Hello 
world! ๐ŸŒŸ

String Wrapping with Word Splitting

wrapped, meta, err := stringwrap.StringWrapSplit("Supercalifragilisticexpialidocious", 10, 4)

fmt.Println(wrapped)
Output:
Supercali-
fragilist-
icexpiali-
docious

Accessing the Metadata

for _, line := range meta.WrappedLines {
	fmt.Printf(
        "Line %d: width=%d, byteOffset=%v\n",
		line.CurLineNum,
        line.Width,
        line.OrigByteOffset
    )
}

๐Ÿ” API

func StringWrap(str string, limit int, tabSize int) (string, *WrappedStringSeq, error)

Wraps a string at a visual width limit. Words are not split.

func StringWrapSplit(str string, limit int, tabSize int) (string, *WrappedStringSeq, error)

Same as StringWrap, but allows splitting words across lines if needed.

type WrappedString struct

Metadata for one wrapped segment.

type WrappedString struct {
	CurLineNum        int
	OrigLineNum       int
	OrigByteOffset    LineOffset
	OrigRuneOffset    LineOffset
	SegmentInOrig     int
	NotWithinLimit    bool
	IsHardBreak       bool
	Width             int
	EndsWithSplitWord bool
}

type WrappedStringSeq struct

Contains all wrapped lines and wrap configuration.

type WrappedStringSeq struct {
	WrappedLines     []WrappedString
	WordSplitAllowed bool
	TabSize          int
	Limit            int
}

๐Ÿค License

This project is licensed under the MIT License. See the LICENSE file for more details.


๐Ÿ“ž Contact

If you have any questions or need support, feel free to reach out by opening an issue on the GitHub repository.

Documentation ยถ

Index ยถ

Constants ยถ

This section is empty.

Variables ยถ

This section is empty.

Functions ยถ

This section is empty.

Types ยถ

type LineOffset ยถ

type LineOffset struct {
	Start int
	End   int
}

LineOffset represents a half-open interval [Start, End) that describes either the byte offset or rune offset range of a wrapped segment in the original unwrapped string.

type WrappedString ยถ

type WrappedString struct {
	// The current wrapped line number (after wrapping).
	CurLineNum int
	// The original unwrapped line number this segment came
	// from.
	OrigLineNum int
	// The byte start and end offsets of this segment in the
	// original unwrapped string.
	OrigByteOffset LineOffset
	// The rune start and end offsets of this segment in the
	// original unwrapped string.
	OrigRuneOffset LineOffset
	// Which segment number this is within the original line
	// (first, second, etc.).
	SegmentInOrig int
	// Whether this segment is the last from the original
	// ilne within the unwrapped string.
	LastSegmentInOrig bool
	// Whether the segment fits entirely within the wrapping
	// limit.
	NotWithinLimit bool
	// Whether the wrap was due to a hard break (newline)
	// instead of word wrapping.
	IsHardBreak bool
	// The viewable width of the wrapped string.
	Width int
	// Whether this wrapped segment ends with a split word due
	// to reaching the wrapping limit
	// (e.g., a hyphen may be added).
	EndsWithSplitWord bool
}

WrappedString represents a single wrapped segment of the original unwrapped string, along with metadata about the wrapping process.

The WrappedString struct is used to store the metadata for each wrapped segment of the original unwrapped string.

type WrappedStringSeq ยถ

type WrappedStringSeq struct {
	// WrappedLines is the list of individual wrapped segments with
	// metadata.
	WrappedLines []WrappedString
	// WordSplitAllowed indicates whether splitting words across
	// lines is permitted.
	WordSplitAllowed bool
	// TabSize defines how many spaces a tab character expands to.
	TabSize int
	// Limit is the maximum viewable width allowed per line.
	Limit int
}

WrappedStringSeq holds the sequence of wrapped lines produced by the string wrapping process, along with the configuration used.

func StringWrap ยถ

func StringWrap(str string, limit int, tabSize int, trimWhitespace bool) (
	string, *WrappedStringSeq, error,
)

StringWrap wraps the input string to the specified viewable-width limit, expanding tabs using the given tab size. It preserves *word boundaries* and never splits words across lines.

If trimWhitespace is true, leading and trailing whitespace on each wrapped line is stripped before the newline is appended.

ANSI escape sequences are preserved without contributing to visual width.

NOTE: Even though this variant does **not** split words, it still walks the text by Unicode *grapheme clusters* (using uniseg) and measures each cluster with go-runewidth. That is required for perfect width accounting with sequences such as ZWJ emojis (e.g. "๐Ÿ‘ฉโ€๐Ÿ’ป"), base-plus-combining marks, and full-width spaces. A plain rune scan would over-count their columns and wrap too early.

Returns the wrapped string and a metadata slice (WrappedStringSeq) that maps every wrapped segment back to its byte/rune span in the original input.

func StringWrapSplit ยถ

func StringWrapSplit(str string, limit int, tabSize int, trimWhitespace bool) (
	string, *WrappedStringSeq, error,
)

StringWrapSplit wraps the input string to the specified viewable-width limit, expanding tabs using the given tab size. Unlike StringWrap, this variant *may* split a word across lines if it exceeds the limit.

If trimWhitespace is true, leading and trailing whitespace on each wrapped line is stripped before the newline is appended.

ANSI escape sequences are preserved without contributing to visual width.

Because word splitting must occur only at safe grapheme boundaries, this function uses exactly the same grapheme-aware width logic described above; the only behavioural difference is that it inserts split points (and an optional hyphen) when necessary.

Returns the wrapped string and a metadata sequence describing each wrapped line.

Jump to

Keyboard shortcuts

? : This menu
/ : Search site
f or F : Jump to
y or Y : Canonical URL