///|
// Ported from tabled and ansi-str. See THIRD_PARTY_NOTICES.md.
priv struct StyledBlock {
text : Array[Char]
prefix : String
suffix : String
}
///|
fn styled_blocks(text : String) -> Array[StyledBlock] {
let blocks = []
let state = SgrState::new()
let mut pending = ""
for token in ansi_tokens(text) {
match token.kind {
Sgr => state.update(token.text)
Text => {
blocks.push({
text: (pending + token.text).to_array(),
prefix: if state.has_any() {
state.prefix()
} else {
""
},
suffix: if state.has_any() {
state.suffix()
} else {
""
},
})
pending = ""
}
_ => pending += token.text
}
}
blocks
}
///|
/// Apply the style of the final ANSI text block to an appended suffix.
pub fn colorize_suffix(text : String, suffix : String) -> String {
match styled_blocks(text).last() {
Some(block) => block.prefix + suffix + block.suffix
None => suffix
}
}
///|
priv struct WordBlocks {
blocks : Array[StyledBlock]
mut index : Int
mut position : Int
}
///|
fn WordBlocks::take(
self : WordBlocks,
out : StringBuilder,
count : Int,
skip : Bool,
) -> Unit {
let mut remaining = count
while remaining > 0 && self.index < self.blocks.length() {
let block = self.blocks[self.index]
let available = block.text.length() - self.position
let amount = Int::min(remaining, available)
let started = self.position > 0
if !started && (!skip || remaining < available) {
out.write_string(block.prefix)
}
if !skip {
out.write_string(
String::from_array(block.text[self.position:self.position + amount]),
)
}
remaining -= amount
self.position += amount
if self.position == block.text.length() {
if started || !skip {
out.write_string(block.suffix)
}
self.index += 1
self.position = 0
}
}
}
///|
fn WordBlocks::break_line(
self : WordBlocks,
out : StringBuilder,
prefix : String,
suffix : String,
) -> Unit {
let block = if self.position > 0 { self.blocks.get(self.index) } else { None }
if block is Some(block) {
out.write_string(block.suffix)
}
out.write_string(suffix + "\n" + prefix)
if block is Some(block) {
out.write_string(block.prefix)
}
}
///|
fn wrap_words(
text : String,
width : Int,
prefix : String,
suffix : String,
placeholder? : String = "�",
) -> String {
guard width > 0 && !text.is_empty() else { return "" }
let plain = strip_ansi(text)
if plain.length() == text.length() && prefix.is_empty() && suffix.is_empty() {
return wrap_words_plain(text, width, placeholder)
}
let cursor = { blocks: styled_blocks(text), index: 0, position: 0, }
let out = StringBuilder()
out.write_string(prefix)
let mut used = 0
for index, word in plain.split(" ").to_array() {
if index > 0 {
if used >= width {
cursor.break_line(out, prefix, suffix)
used = 0
}
cursor.take(out, 1, false)
used += 1
}
let size = get_string_width(word.to_owned())
let chars = word.to_owned().to_array()
if size == 0 {
if !chars.is_empty() {
cursor.take(out, chars.length(), true)
}
continue
}
if used + size <= width {
cursor.take(out, chars.length(), false)
used += size
continue
}
if size <= width {
cursor.break_line(out, prefix, suffix)
cursor.take(out, chars.length(), false)
used = size
continue
}
for ch in chars {
let size = Int::max(1, get_char_width(ch))
if used + size <= width {
cursor.take(out, 1, false)
used += size
} else if size <= width {
cursor.break_line(out, prefix, suffix)
cursor.take(out, 1, false)
used = size
} else {
if used == width {
cursor.break_line(out, prefix, suffix)
used = 0
}
for _ in used.. String {
let out = StringBuilder()
let mut used = 0
for index, word in text.split(" ").to_array() {
if index > 0 {
if used >= width {
out.write_char('\n')
used = 0
}
out.write_char(' ')
used += 1
}
let size = get_string_width(word.to_owned())
if used + size <= width {
out.write_string(word.to_owned())
used += size
continue
}
if size <= width {
out.write_char('\n')
out.write_string(word.to_owned())
used = size
continue
}
for ch in word {
let size = Int::max(1, get_char_width(ch))
if used + size > width {
if size > width {
if used == width {
out.write_char('\n')
used = 0
}
for _ in used..