// Port of Monaco/VS Code's CursorColumns visible-column math.
//
// Source: vscode/src/vs/editor/common/core/cursorColumns.ts
// with isFullWidthCharacter / isEmojiImprecise / getNextCodePoint from
// vscode/src/vs/base/common/strings.ts
// (checked-in vscode submodule).
//
// A "visible column" is a rough horizontal screen position: tabs advance to the
// next tab stop, full-width/emoji code points count as two, everything else as
// one.
//
// Adaptation: the upstream column<->visible-column helpers iterate graphemes via
// Intl segmentation; this port iterates code points. The two agree for any
// input without multi-code-point graphemes (no ZWJ emoji / combining marks),
// which covers the conformance corpus.
///|
fn compute_code_point(high : Int32, low : Int32) -> Int32 {
(high - 0xD800) * 0x400 + (low - 0xDC00) + 0x10000
}
///|
fn get_next_code_point(str : String, len : Int32, offset : Int32) -> Int32 {
let char_code = str[offset].to_int()
if @base_common.is_high_surrogate(char_code) && offset + 1 < len {
let next_char_code = str[offset + 1].to_int()
if @base_common.is_low_surrogate(next_char_code) {
return compute_code_point(char_code, next_char_code)
}
}
char_code
}
///|
fn code_point_utf16_length(code_point : Int32) -> Int32 {
if code_point >= 0x10000 {
2
} else {
1
}
}
///|
fn is_emoji_imprecise(x : Int32) -> Bool {
(x >= 0x1F1E6 && x <= 0x1F1FF) ||
x == 8986 ||
x == 8987 ||
x == 9200 ||
x == 9203 ||
(x >= 9728 && x <= 10175) ||
x == 11088 ||
x == 11093 ||
(x >= 127744 && x <= 128591) ||
(x >= 128640 && x <= 128764) ||
(x >= 128992 && x <= 129008) ||
(x >= 129280 && x <= 129535) ||
(x >= 129648 && x <= 129782)
}
///|
fn next_visible_column(
code_point : Int32,
visible_column : Int32,
tab_size : Int32,
) -> Int32 {
if code_point == 9 {
return next_render_tab_stop(visible_column, tab_size)
}
if @base_common.is_full_width_character(code_point) ||
is_emoji_imprecise(code_point) {
return visible_column + 2
}
visible_column + 1
}
///|
/// Returns a visible column from a (1-based) column.
pub fn visible_column_from_column(
line_content : String,
column : Int32,
tab_size : Int32,
) -> Int32 {
let text_len = (column - 1).min(line_content.length())
for offset = 0, result = 0; offset < text_len; {
let code_point = get_next_code_point(line_content, text_len, offset)
continue offset + code_point_utf16_length(code_point),
next_visible_column(code_point, result, tab_size)
} nobreak {
result
}
}
///|
/// Returns the value to display as "Col" in the status bar (1-based, code
/// points, tabs snap to tab stops).
pub fn to_statusbar_column(
line_content : String,
column : Int32,
tab_size : Int32,
) -> Int32 {
let text_len = (column - 1).min(line_content.length())
let result = for offset = 0, result = 0; offset < text_len; {
let code_point = get_next_code_point(line_content, text_len, offset)
let result = if code_point == 9 {
next_render_tab_stop(result, tab_size)
} else {
result + 1
}
continue offset + code_point_utf16_length(code_point), result
} nobreak {
result
}
result + 1
}
///|
/// Returns a (1-based) column from a visible column.
pub fn column_from_visible_column(
line_content : String,
visible_column : Int32,
tab_size : Int32,
) -> Int32 {
if visible_column <= 0 {
return 1
}
let line_content_length = line_content.length()
for offset = 0, before_visible_column = 0, before_column = 1; offset <
line_content_length; {
let code_point = get_next_code_point(
line_content, line_content_length, offset,
)
let offset = offset + code_point_utf16_length(code_point)
let after_visible_column = next_visible_column(
code_point, before_visible_column, tab_size,
)
let after_column = offset + 1
if after_visible_column >= visible_column {
let before_delta = visible_column - before_visible_column
let after_delta = after_visible_column - visible_column
if after_delta < before_delta {
break after_column
} else {
break before_column
}
}
continue offset, after_visible_column, after_column
} nobreak {
line_content_length + 1
}
}
///|
/// ATTENTION: this works with 0-based columns (as opposed to the regular
/// 1-based columns).
fn next_render_tab_stop(visible_column : Int32, tab_size : Int32) -> Int32 {
visible_column + tab_size - visible_column % tab_size
}