# string_width ### A low-ish level library for building terminal UIs. [](https://hex.pm/packages/string_width) [](https://hexdocs.pm/string_width/) ```sh gleam add string_width@3 ``` `string_width` provides functions to measure the required amount of cells, and then layout strings in an ANSI-aware manner. It includes ready-to-use layout functions like [limit](./string_width.html#limit "Word wrapping and truncation") or [stack_horizontal](./string_width.html#stack_horizontal "Build column-based layouts"), as well as low-level primitives, allowing you to build your own layout algorithms. All Gleam targets are supported, and it passes the tests of the NPM [string-width](https://www.npmjs.com/package/string-width) and [unicode-width](https://crates.io/crates/unicode-width) crate where applicable. Compared to others, a heavy focus is put on on reporting the actual width of strings in terminals instead (Check out [Internals](./internals.html) for more information.) It is also one of the fastest options available, even including target-specific ones, while still providing you full flexibility and correctness. ### Tour ```gleam import gleam/int import gleam/io import gleam/list import gleam/result import string_width.{Left, Size, Top} // we will render a simple help menu for a command-line app here. // the help menu will automatically adjust based on the width of the terminal. type Command { Command(name: String, description: String) } const commands = [ Command( name: "build", description: "Build and bundle your application.\nThe generated bundle calls your apps' main function. If your main function accepts a single argument of type List(String), all additional command-line arguments will be passed there.", ), Command( name: "dev", description: "Start a file watcher that automatically recompiles your app on all file changes, and then re-runs your tests. Compilation errors or test failures will be displayed in an overlay, and if everything succeeds, your window will automatically hot-reload.", ), Command( name: "help", description: "Show this help text." ), ] pub fn main() { // get the size of the terminal, or fallback to a default size. let term_size = string_width.get_terminal_size() |> result.unwrap(Size(rows: 24, columns: 80)) // how big does the left-hand side need to be? let left_width = list.fold(commands, 0, fn(max, cmd) { int.max(max, string_width.line(cmd.name)) }) // add some gap between both columns and compute the size of the right side. let gap = 4 // set a maximum width of 60 for the right column to improve readability. let right_width = int.min(60, term_size.columns - left_width - gap) commands |> list.map(fn(cmd) { [ cmd.name |> pink // make sure all left-hand sides are padded to left_width |> string_width.align(left_width, Left, with: " "), // word wrap the right-hand side such that it fits into our column. // if the description would overflow 10 lines, truncate it. cmd.description |> string_width.limit( to: Size(rows: 10, columns: right_width), ellipsis: "…", ) // make sure that if we used styles those would wrap properly |> string_width.inline_styles, ] // build the column layout; // the name should be aligned with the top of the description |> string_width.stack_horizontal(place: Top, gap:, with: " ") }) // combine the help text of all commands, adding a line of space in between. |> string_width.stack_vertical(align: Left, gap: 1, with: " ") |> io.println } // string_width is agnostic to the way you add styles to your strings. // maybe check out gleam_community_ansi! fn pink(str: String) { "\u{1b}[38;5;207m" <> str <> "\u{1b}[m" } ``` Output: