diff --git a/src/string_width.gleam b/src/string_width.gleam --- a/src/string_width.gleam +++ b/src/string_width.gleam @@ -340,6 +340,20 @@ state.col + state.spaces_width + state.non_spaces_width + state.overflow_width } +// find the grapheme cluster boundary to set the new overflow. +fn limit_state_overflow( + state: LimitState, + piece: String, + options: Options, +) -> #(String, String, Int) { + let str = state.non_spaces <> state.overflow <> piece + use #(before, after, width), piece <- fold_with(str, options, #("", "", 0)) + case piece.column + piece.width > state.non_spaces_width { + True -> #(before, after <> piece.piece, width + piece.width) + False -> #(before <> piece.piece, after, width) + } +} + /// Like `limit`, but also customise the options used for measuring. pub fn limit_with( str: String, @@ -462,8 +476,7 @@ } let state = { - // TODO: this may be fold_raw-able - use state, piece <- fold_with(str, options, initial) + use state, piece <- fold_raw_pieces(str, options, initial) case state.row >= max_size.rows { // reached the end, but we still want to collect ansi sequences True -> @@ -475,7 +488,7 @@ False -> case piece.piece { // Line break that we recognise as such - "\n" | "\r\n" -> { + "\n" -> { let state = push_space(state, "", 0) LimitState( ..state, @@ -520,19 +533,18 @@ state.non_spaces_width + state.overflow_width + piece.width > max_size.columns { - True -> + True -> { + let #(non_spaces, overflow, overflow_width) = + limit_state_overflow(state, piece.piece, options) // it _is_! do we have room for another row? case state.row + 1 < max_size.rows { True -> LimitState( - str: state.str - <> state.spaces - <> state.non_spaces - <> "\n", + str: state.str <> state.spaces <> non_spaces <> "\n", col: 0, row: state.row + 1, - non_spaces: state.overflow <> piece.piece, - non_spaces_width: state.overflow_width + piece.width, + non_spaces: overflow, + non_spaces_width: overflow_width, spaces: "", spaces_width: 0, overflow: "", @@ -545,11 +557,12 @@ ..state, str: state.str <> state.spaces - <> state.non_spaces + <> non_spaces <> ellipsis, row: state.row + 1, ) } + } False -> LimitState( @@ -588,7 +601,10 @@ False -> // it doesn't; if we are at the last line, truncate. else, wrap. case state.row + 1 >= max_size.rows { - True -> state.str <> state.spaces <> state.non_spaces <> ellipsis + True -> { + let #(non_spaces, _, _) = limit_state_overflow(state, "", options) + state.str <> state.spaces <> non_spaces <> ellipsis + } False -> state.str <> "\n" <> state.non_spaces <> state.overflow } } diff --git a/test/layout_test.gleam b/test/layout_test.gleam --- a/test/layout_test.gleam +++ b/test/layout_test.gleam @@ -25,6 +25,15 @@ |> should.equal("Lorem ipsum dolor\nsit amet\nIs a common place...") } +pub fn limit_cluster_test() { + limit( + "👩‍👩‍👦‍👦👩‍👩‍👦‍👦👩‍👩‍👦‍👦", + Size(1, 12), + "…", + ) + |> should.equal("👩‍👩‍👦‍👦…") +} + pub fn tabs_to_spaces_test() { tabs_to_spaces("hello\tworld") |> should.equal("hello world")