///|
/// Measure a line excluding terminal control sequences, including OSC links.
pub fn get_line_width(text : String) -> Int {
string_display_width(text)
}
///|
/// Measure the widest line in a string.
pub fn get_text_width(text : String) -> Int {
let lines = split_lines(text)
let mut width = 0
for index, line in lines {
// Rust's str::lines removes one CR only when it precedes a newline.
let line = if line.has_suffix("\r") && index + 1 < lines.length() {
line[:line.length() - 1].to_owned()
} else {
line
}
width = Int::max(width, get_line_width(line))
}
width
}
///|
/// Return the number of logical lines and the maximum visible line width.
pub fn get_text_dimension(text : String) -> (Int, Int) {
let lines = get_lines(text)
(
lines.length(),
lines.fold(init=0, (width, line) => Int::max(width, get_line_width(line))),
)
}
///|
pub fn count_lines(text : String) -> Int {
text
.iter()
.fold(init=1, (count, ch) => if ch == '\n' { count + 1 } else { count })
}
///|
pub fn count_tabs(text : String) -> Int {
text
.iter()
.fold(init=0, (count, ch) => if ch == '\t' { count + 1 } else { count })
}
///|
/// Replace literal tabs with spaces. The upstream four-space fast path replaces
/// every tab; other sizes preserve a tab immediately preceded by a backslash.
pub fn replace_tab(text : String, size : Int) -> String {
let fill = String::make(Int::max(0, size), ' ')
let out = StringBuilder()
let mut escaped = false
for ch in text {
if ch == '\t' && (size == 4 || !escaped) {
out.write_string(fill)
} else {
out.write_char(ch)
}
escaped = ch == '\\'
}
out.to_string()
}