// Faithful port of vscode/src/vs/editor/common/viewLayout/viewLineRenderer.ts
// (checked-in source): `renderViewLine`,
// `renderViewLine2`, `resolveRenderLineInput`, and `_renderLine`, plus the
// helpers they call. The only intentional deviation is the line wrapper:
// Monaco emits `…`; this viewer emits
// `…` because the browser layer locates
// the content node via `querySelector(".view-line-content")` and styles it by
// that class. The inner HTML and the `CharacterMapping` match Monaco
// byte-for-byte (see `monaco_render_line_reference_test.mbt`).
///|
let long_token : Int32 = 50
// Character codes (mirroring base/common/charCode.ts entries used here).
///|
let cc_tab : Int32 = 9
///|
let cc_space : Int32 = 32
///|
let cc_lt : Int32 = 60
///|
let cc_gt : Int32 = 62
///|
let cc_amp : Int32 = 38
///|
let cc_null : Int32 = 0
///|
let cc_utf8_bom : Int32 = 0xFEFF
///|
let cc_line_separator : Int32 = 0x2028
///|
let cc_paragraph_separator : Int32 = 0x2029
///|
let cc_next_line : Int32 = 0x0085
///|
let ch_nbsp : Int32 = 0xA0
///|
let ch_zwnj : Int32 = 0x200C
///|
let ch_rightwards_arrow : Int32 = 0x2192
///|
let ch_halfwidth_rightwards_arrow : Int32 = 0xFFEB
///|
let ch_replacement : Int32 = 0xFFFD
///|
let ch_control_picture_base : Int32 = 9216
///|
let ch_del_picture : Int32 = 9249
///|
let ch_middot : Int32 = 0xB7
///|
let ch_word_separator_middot : Int32 = 0x2E31
///|
/// Renders one line into `html` and returns mapping/foreign-element metadata.
pub fn render_view_line(
input : RenderLineInput,
html : StringBuilder,
) -> RenderLineOutput {
if input.line_content.length() == 0 {
if input.line_decorations.length() > 0 {
// Empty line carrying inline (before/after) decorations.
html.write_string("")
let mut before_count = 0
let mut contains_foreign : ForeignElementType = None
for decoration in input.line_decorations {
if decoration.decoration_type == Before ||
decoration.decoration_type == After {
html <+
$|
if decoration.decoration_type == Before {
contains_foreign = add_foreign_element(contains_foreign, Before)
before_count += 1
}
if decoration.decoration_type == After {
contains_foreign = add_foreign_element(contains_foreign, After)
}
}
}
html.write_string("")
let character_mapping = CharacterMapping(1)
character_mapping.set_column_info(1, before_count, 0, 0)
return { character_mapping, contains_foreign_elements: contains_foreign, }
}
// Completely empty line.
if input.render_new_line_when_empty {
html.write_string(
"\n",
)
} else {
html.write_string(
"",
)
}
return {
character_mapping: CharacterMapping(0),
contains_foreign_elements: None,
}
}
render_line(resolve_render_line_input(input), html)
}
///|
/// Renders one line and returns the generated HTML with metadata.
pub fn render_view_line2(input : RenderLineInput) -> RenderLineOutput2 {
let html = StringBuilder()
let output = render_view_line(input, html)
{
html: html.to_string(),
character_mapping: output.character_mapping,
contains_foreign_elements: output.contains_foreign_elements,
}
}
///|
/// Fully resolved render input (Monaco's `ResolvedRenderLineInput`).
priv struct ResolvedRenderLineInput {
font_is_monospace : Bool
can_use_halfwidth_rightwards_arrow : Bool
line_content : String
len : Int32
is_overflowing : Bool
overflowing_char_count : Int32
parts : Array[LinePart]
contains_foreign_elements : ForeignElementType
faux_indent_length : Int32
tab_size : Int32
start_visible_column : Int32
space_width : Double
render_space_char_code : Int32
render_whitespace : @editor_api.RenderWhitespace
render_control_characters : Bool
}
///|
fn resolve_render_line_input(
input : RenderLineInput,
) -> ResolvedRenderLineInput {
let line_content = input.line_content
let mut is_overflowing = false
let mut overflowing_char_count = 0
let len = if input.stop_rendering_line_after != -1 &&
input.stop_rendering_line_after < line_content.length() {
is_overflowing = true
overflowing_char_count = line_content.length() -
input.stop_rendering_line_after
input.stop_rendering_line_after
} else {
line_content.length()
}
let mut tokens = transform_and_remove_overflowing(
line_content,
input.contains_rtl,
input.line_tokens,
input.faux_indent_length,
len,
)
if input.render_control_characters && !input.is_basic_ascii {
tokens = extract_control_characters(line_content, tokens)
}
if input.render_whitespace == All ||
input.render_whitespace == Boundary ||
(
input.render_whitespace == Selection &&
input.selections_on_line.length() > 0
) ||
(input.render_whitespace == Trailing && !input.continues_with_wrapped_line) {
tokens = apply_render_whitespace(input, line_content, len, tokens)
}
let contains_foreign_elements = foreign_element_type(input.line_decorations)
if input.line_decorations.length() > 0 {
tokens = apply_inline_decorations(
line_content,
len,
tokens,
input.line_decorations,
)
}
if !input.contains_rtl {
tokens = split_large_tokens(
line_content,
tokens,
!input.is_basic_ascii || input.font_ligatures,
)
} else {
tokens = split_leading_whitespace_from_rtl(line_content, tokens)
}
{
font_is_monospace: input.use_monospace_optimizations,
can_use_halfwidth_rightwards_arrow: input.can_use_halfwidth_rightwards_arrow,
line_content,
len,
is_overflowing,
overflowing_char_count,
parts: tokens,
contains_foreign_elements,
faux_indent_length: input.faux_indent_length,
tab_size: input.tab_size,
start_visible_column: input.start_visible_column,
space_width: input.space_width,
render_space_char_code: input.render_space_char_code,
render_whitespace: input.render_whitespace,
render_control_characters: input.render_control_characters,
}
}
///|
/// Whether the render-space glyph differs in width from a space, so each
/// whitespace needs its own width-bearing part (Monaco's
/// `generateLinePartForEachWhitespace`).
fn generate_line_part_for_each_whitespace(input : RenderLineInput) -> Bool {
input.render_space_width != input.space_width
}
///|
/// Ensures all tokens end before `len`, the last precisely at `len`, and the
/// faux indent forms a leading typeless part.
fn transform_and_remove_overflowing(
line_content : String,
line_contains_rtl : Bool,
tokens : Array[LinePart],
faux_indent_length : Int32,
len : Int32,
) -> Array[LinePart] {
let result : Array[LinePart] = []
if faux_indent_length > 0 {
result.push(LinePart(faux_indent_length, "", contains_rtl=false))
}
let mut start_offset = faux_indent_length
for token in tokens {
let end_index = token.end_index
if end_index <= faux_indent_length {
continue
}
let class_name = token.class_name
if end_index >= len {
let token_contains_rtl = line_contains_rtl &&
contains_rtl_in(line_content, start_offset, len)
result.push(LinePart(len, class_name, contains_rtl=token_contains_rtl))
break
}
let token_contains_rtl = line_contains_rtl &&
contains_rtl_in(line_content, start_offset, end_index)
result.push(
LinePart(end_index, class_name, contains_rtl=token_contains_rtl),
)
start_offset = end_index
}
result
}
///|
fn split_large_tokens(
line_content : String,
tokens : Array[LinePart],
only_at_spaces : Bool,
) -> Array[LinePart] {
let mut last_token_end_index = 0
let result : Array[LinePart] = []
let view = line_content.view()
if only_at_spaces {
for token in tokens {
let token_end_index = token.end_index
if last_token_end_index + long_token < token_end_index {
let token_type = token.class_name
let token_metadata = token.metadata
let token_contains_rtl = token.contains_rtl
let mut last_space_offset = -1
let mut curr_token_start = last_token_end_index
for j in last_token_end_index..= long_token {
result.push({
end_index: last_space_offset + 1,
class_name: token_type,
metadata: token_metadata,
contains_rtl: token_contains_rtl,
})
curr_token_start = last_space_offset + 1
last_space_offset = -1
}
}
if curr_token_start != token_end_index {
result.push({
end_index: token_end_index,
class_name: token_type,
metadata: token_metadata,
contains_rtl: token_contains_rtl,
})
}
} else {
result.push(token)
}
last_token_end_index = token_end_index
}
} else {
for token in tokens {
let token_end_index = token.end_index
let diff = token_end_index - last_token_end_index
if diff > long_token {
let token_type = token.class_name
let token_metadata = token.metadata
let token_contains_rtl = token.contains_rtl
let pieces_count = (diff + long_token - 1) / long_token
for j in 1.. Array[LinePart] {
if tokens.length() == 0 {
return tokens
}
let first_token = tokens[0]
if !first_token.contains_rtl {
return tokens
}
let first_token_end_index = first_token.end_index
let view = line_content.view()
let mut first_non_whitespace_index = 0
for i in 0.. Bool {
if charcode < 32 {
return charcode != cc_tab
}
if charcode == 127 {
return true
}
(charcode >= 0x202A && charcode <= 0x202E) ||
(charcode >= 0x2066 && charcode <= 0x2069) ||
(charcode >= 0x200E && charcode <= 0x200F) ||
charcode == 0x061C
}
///|
fn extract_control_characters(
line_content : String,
tokens : Array[LinePart],
) -> Array[LinePart] {
let result : Array[LinePart] = []
let mut last_end_index = 0
let mut char_offset = 0
let view = line_content.view()
for token in tokens {
let token_end_index = token.end_index
while char_offset < token_end_index {
let charcode = view.unsafe_get(char_offset).to_int()
if is_control_character(charcode) {
if char_offset > last_end_index {
last_end_index = char_offset
result.push({
end_index: char_offset,
class_name: token.class_name,
metadata: token.metadata,
contains_rtl: token.contains_rtl,
})
}
last_end_index = char_offset + 1
result.push({
end_index: char_offset + 1,
class_name: "mtkcontrol",
metadata: token.metadata,
contains_rtl: false,
})
}
char_offset += 1
}
if char_offset > last_end_index {
last_end_index = token_end_index
result.push({
end_index: token_end_index,
class_name: token.class_name,
metadata: token.metadata,
contains_rtl: token.contains_rtl,
})
}
}
result
}
///|
fn apply_render_whitespace(
input : RenderLineInput,
line_content : String,
len : Int32,
tokens : Array[LinePart],
) -> Array[LinePart] {
let continues_with_wrapped_line = input.continues_with_wrapped_line
let faux_indent_length = input.faux_indent_length
let tab_size = input.tab_size
let start_visible_column = input.start_visible_column
let use_monospace_optimizations = input.use_monospace_optimizations
let selections = input.selections_on_line
let has_selections = selections.length() > 0
let only_boundary = input.render_whitespace == Boundary
let only_trailing = input.render_whitespace == Trailing
let generate_for_each = generate_line_part_for_each_whitespace(input)
let view = line_content.view()
let result : Array[LinePart] = []
let mut token_index = 0
let mut token_type = tokens[token_index].class_name
let mut token_contains_rtl = tokens[token_index].contains_rtl
let mut token_end_index = tokens[token_index].end_index
let tokens_length = tokens.length()
let mut line_is_empty_or_whitespace = false
// Monaco computes these over the full line content, not the truncated `len`.
let mut first_non_whitespace_index = first_non_whitespace_index_of(
view,
line_content.length(),
)
let mut last_non_whitespace_index = 0
if first_non_whitespace_index == -1 {
line_is_empty_or_whitespace = true
first_non_whitespace_index = len
last_non_whitespace_index = len
} else {
last_non_whitespace_index = last_non_whitespace_index_of(
view,
line_content.length(),
)
}
let mut was_in_whitespace = false
let mut current_selection_index = 0
let mut tmp_indent = start_visible_column % tab_size
for char_index in faux_indent_length.. last_non_whitespace_index {
true
} else if ch_code == cc_tab {
true
} else if ch_code == cc_space {
if only_boundary {
if was_in_whitespace {
true
} else {
let next_ch_code = if char_index + 1 < len {
view.unsafe_get(char_index + 1).to_int()
} else {
cc_null
}
next_ch_code == cc_space || next_ch_code == cc_tab
}
} else {
true
}
} else {
false
}
if is_in_whitespace && has_selections {
let in_selection = current_selection_index < selections.length() &&
selections[current_selection_index].start <= char_index &&
char_index < selections[current_selection_index].end_exclusive
is_in_whitespace = in_selection
}
if is_in_whitespace && only_trailing {
is_in_whitespace = line_is_empty_or_whitespace ||
char_index > last_non_whitespace_index
}
if is_in_whitespace && token_contains_rtl {
if char_index >= first_non_whitespace_index &&
char_index <= last_non_whitespace_index {
is_in_whitespace = false
}
}
if was_in_whitespace {
if !is_in_whitespace ||
(!use_monospace_optimizations && tmp_indent >= tab_size) {
if generate_for_each {
let last_end_index = if result is [.., last] {
last.end_index
} else {
faux_indent_length
}
for i in (last_end_index + 1)..<=char_index {
result.push(
LinePart(i, "mtkw", metadata=line_part_metadata_is_whitespace),
)
}
} else {
result.push(
LinePart(
char_index,
"mtkw",
metadata=line_part_metadata_is_whitespace,
),
)
}
tmp_indent = tmp_indent % tab_size
}
} else if char_index == token_end_index ||
(is_in_whitespace && char_index > faux_indent_length) {
result.push(
LinePart(char_index, token_type, contains_rtl=token_contains_rtl),
)
tmp_indent = tmp_indent % tab_size
}
if ch_code == cc_tab {
tmp_indent = tab_size
} else if @base_common.is_full_width_character(ch_code) {
tmp_indent += 2
} else {
tmp_indent += 1
}
was_in_whitespace = is_in_whitespace
while char_index == token_end_index {
token_index += 1
if token_index < tokens_length {
token_type = tokens[token_index].class_name
token_contains_rtl = tokens[token_index].contains_rtl
token_end_index = tokens[token_index].end_index
} else {
break
}
}
}
let mut generate_whitespace = false
if was_in_whitespace {
if continues_with_wrapped_line && only_boundary {
let last_char_code = if len > 0 {
view.unsafe_get(len - 1).to_int()
} else {
cc_null
}
let prev_char_code = if len > 1 {
view.unsafe_get(len - 2).to_int()
} else {
cc_null
}
let is_single_trailing_space = last_char_code == cc_space &&
prev_char_code != cc_space &&
prev_char_code != cc_tab
if !is_single_trailing_space {
generate_whitespace = true
}
} else {
generate_whitespace = true
}
}
if generate_whitespace {
if generate_for_each {
let last_end_index = if result is [.., last] {
last.end_index
} else {
faux_indent_length
}
for i in (last_end_index + 1)..<=len {
result.push(
LinePart(i, "mtkw", metadata=line_part_metadata_is_whitespace),
)
}
} else {
result.push(
LinePart(len, "mtkw", metadata=line_part_metadata_is_whitespace),
)
}
} else {
result.push(LinePart(len, token_type, contains_rtl=token_contains_rtl))
}
result
}
///|
fn apply_inline_decorations(
line_content : String,
len : Int32,
tokens : Array[LinePart],
line_decorations : Array[LineDecoration],
) -> Array[LinePart] {
ignore(len)
let sorted = line_decorations.copy()
sorted.sort_by(LineDecoration::compare)
let decorations = LineDecorationsNormalizer::normalize(line_content, sorted)
let decorations_len = decorations.length()
let mut decoration_index = 0
let result : Array[LinePart] = []
let mut last_result_end_index = 0
for token in tokens {
let token_end_index = token.end_index
let token_type = token.class_name
let token_metadata = token.metadata
let token_contains_rtl = token.contains_rtl
while decoration_index < decorations_len &&
decorations[decoration_index].start_offset < token_end_index {
let decoration = decorations[decoration_index]
if decoration.start_offset > last_result_end_index {
last_result_end_index = decoration.start_offset
result.push({
end_index: last_result_end_index,
class_name: token_type,
metadata: token_metadata,
contains_rtl: token_contains_rtl,
})
}
if decoration.end_offset + 1 <= token_end_index {
last_result_end_index = decoration.end_offset + 1
result.push({
end_index: last_result_end_index,
class_name: "\{token_type} \{decoration.class_name}",
metadata: token_metadata | decoration.metadata,
contains_rtl: token_contains_rtl,
})
decoration_index += 1
} else {
last_result_end_index = token_end_index
result.push({
end_index: last_result_end_index,
class_name: "\{token_type} \{decoration.class_name}",
metadata: token_metadata | decoration.metadata,
contains_rtl: token_contains_rtl,
})
break
}
}
if token_end_index > last_result_end_index {
last_result_end_index = token_end_index
result.push({
end_index: last_result_end_index,
class_name: token_type,
metadata: token_metadata,
contains_rtl: token_contains_rtl,
})
}
}
let last_token_end_index = tokens[tokens.length() - 1].end_index
if decoration_index < decorations_len &&
decorations[decoration_index].start_offset == last_token_end_index {
while decoration_index < decorations_len &&
decorations[decoration_index].start_offset == last_token_end_index {
let decoration = decorations[decoration_index]
result.push({
end_index: last_result_end_index,
class_name: decoration.class_name,
metadata: decoration.metadata,
contains_rtl: false,
})
decoration_index += 1
}
}
result
}
///|
/// Monaco's `_renderLine`: emit ``-wrapped parts and fill the mapping.
fn render_line(
input : ResolvedRenderLineInput,
html : StringBuilder,
) -> RenderLineOutput {
let font_is_monospace = input.font_is_monospace
let can_use_halfwidth_rightwards_arrow = input.can_use_halfwidth_rightwards_arrow
let contains_foreign_elements = input.contains_foreign_elements
let line_content = input.line_content
let len = input.len
let parts = input.parts
let faux_indent_length = input.faux_indent_length
let tab_size = input.tab_size
let render_space_char_code = input.render_space_char_code
let render_whitespace = input.render_whitespace
let render_control_characters = input.render_control_characters
let view = line_content.view()
let character_mapping = CharacterMapping(len + 1)
let mut last_character_mapping_defined = false
let mut char_index = 0
let mut visible_column = input.start_visible_column
let mut char_offset_in_part = 0
let mut char_horizontal_offset = 0
let mut part_displacement = 0
html.write_string("")
for part_index in 0..= faux_indent_length {
probe_visible += char_width
}
probe_index += 1
}
if part_renders_whitespace_with_width {
html.write_string(" style=\"width:")
write_css_px(html, input.space_width * part_width.to_double())
html.write_string("px\"")
}
html.write_char('>')
while char_index < part_end_index {
character_mapping.set_column_info(
char_index + 1,
part_index - part_displacement,
char_offset_in_part,
char_horizontal_offset,
)
part_displacement = 0
let charcode = view.unsafe_get(char_index).to_int()
let mut produced_characters = 0
let mut char_width = 0
if charcode == cc_tab {
produced_characters = tab_size - visible_column % tab_size
char_width = produced_characters
if !can_use_halfwidth_rightwards_arrow || char_width > 1 {
append_code(html, ch_rightwards_arrow)
} else {
append_code(html, ch_halfwidth_rightwards_arrow)
}
for _ in 2..<=char_width {
append_code(html, ch_nbsp)
}
} else {
produced_characters = 2
char_width = 1
append_code(html, render_space_char_code)
append_code(html, ch_zwnj)
}
char_offset_in_part += produced_characters
char_horizontal_offset += char_width
if char_index >= faux_indent_length {
visible_column += char_width
}
char_index += 1
}
} else {
html.write_char('>')
let mut run_start = char_index
while char_index < part_end_index {
character_mapping.set_column_info(
char_index + 1,
part_index - part_displacement,
char_offset_in_part,
char_horizontal_offset,
)
part_displacement = 0
let charcode = view.unsafe_get(char_index).to_int()
let mut produced_characters = 1
let mut char_width = 1
if charcode == cc_tab {
flush_run(html, line_content, run_start, char_index)
produced_characters = tab_size - visible_column % tab_size
char_width = produced_characters
for _ in 1..<=produced_characters {
append_code(html, ch_nbsp)
}
run_start = char_index + 1
} else if charcode == cc_space {
flush_run(html, line_content, run_start, char_index)
append_code(html, ch_nbsp)
run_start = char_index + 1
} else if charcode == cc_lt {
flush_run(html, line_content, run_start, char_index)
html.write_string("<")
run_start = char_index + 1
} else if charcode == cc_gt {
flush_run(html, line_content, run_start, char_index)
html.write_string(">")
run_start = char_index + 1
} else if charcode == cc_amp {
flush_run(html, line_content, run_start, char_index)
html.write_string("&")
run_start = char_index + 1
} else if charcode == cc_null {
flush_run(html, line_content, run_start, char_index)
if render_control_characters {
append_code(html, ch_control_picture_base)
} else {
html.write_string("")
}
run_start = char_index + 1
} else if charcode == cc_utf8_bom ||
charcode == cc_line_separator ||
charcode == cc_paragraph_separator ||
charcode == cc_next_line {
flush_run(html, line_content, run_start, char_index)
append_code(html, ch_replacement)
run_start = char_index + 1
} else {
if @base_common.is_full_width_character(charcode) {
char_width += 1
}
if render_control_characters && charcode < 32 {
flush_run(html, line_content, run_start, char_index)
append_code(html, ch_control_picture_base + charcode)
run_start = char_index + 1
} else if render_control_characters && charcode == 127 {
flush_run(html, line_content, run_start, char_index)
append_code(html, ch_del_picture)
run_start = char_index + 1
} else if render_control_characters && is_control_character(charcode) {
flush_run(html, line_content, run_start, char_index)
html <+ "[U+\{to_4_char_hex(charcode)}]"
produced_characters = 8
char_width = produced_characters
run_start = char_index + 1
}
// else: normal char, kept in the pending run
}
char_offset_in_part += produced_characters
char_horizontal_offset += char_width
if char_index >= faux_indent_length {
visible_column += char_width
}
char_index += 1
}
flush_run(html, line_content, run_start, part_end_index)
}
if part_is_empty_and_has_pseudo_after {
part_displacement += 1
} else {
part_displacement = 0
}
if char_index >= len &&
!last_character_mapping_defined &&
part.is_pseudo_after() {
last_character_mapping_defined = true
character_mapping.set_column_info(
char_index + 1,
part_index,
char_offset_in_part,
char_horizontal_offset,
)
}
html.write_string("")
}
if !last_character_mapping_defined {
character_mapping.set_column_info(
len + 1,
parts.length() - 1,
char_offset_in_part,
char_horizontal_offset,
)
}
if input.is_overflowing {
html <+
$|Show more (\{render_overflowing_char_count(input.overflowing_char_count)})
}
html.write_string("")
{ character_mapping, contains_foreign_elements, }
}
///|
fn flush_run(
html : StringBuilder,
line_content : String,
start : Int32,
end : Int32,
) -> Unit {
if end > start {
// Run boundaries fall on BMP special chars or clean token ends, never
// mid-surrogate-pair, so this view never splits a pair.
html.write_view(line_content[start:end])
}
}
///|
fn append_code(html : StringBuilder, code : Int32) -> Unit {
html.write_char(code.unsafe_to_char())
}
///|
fn write_css_px(html : StringBuilder, value : Double) -> Unit {
let truncated = value.to_int()
if truncated.to_double() == value {
html.write_string(truncated.to_string())
} else {
html.write_string(value.to_string())
}
}
///|
fn to_4_char_hex(n : Int32) -> String {
let sb = StringBuilder()
append_code(sb, hex_digit((n >> 12) & 0xF))
append_code(sb, hex_digit((n >> 8) & 0xF))
append_code(sb, hex_digit((n >> 4) & 0xF))
append_code(sb, hex_digit(n & 0xF))
sb.to_string()
}
///|
fn hex_digit(value : Int32) -> Int32 {
if value < 10 {
0x30 + value
} else {
0x41 + value - 10
}
}
///|
fn render_overflowing_char_count(n : Int32) -> String {
if n < 1024 {
"\{n} chars"
} else if n < 1024 * 1024 {
let tenths = n * 10 / 1024
"\{tenths / 10}.\{tenths % 10} KB"
} else {
let tenths = n * 10 / (1024 * 1024)
"\{tenths / 10}.\{tenths % 10} MB"
}
}
///|
fn foreign_element_type(
line_decorations : Array[LineDecoration],
) -> ForeignElementType {
let mut found : ForeignElementType = None
for decoration in line_decorations {
match decoration.decoration_type {
Before | RegularAffectingLetterSpacing =>
found = add_foreign_element(found, Before)
After => found = add_foreign_element(found, After)
Regular => ()
}
}
found
}
///|
/// Monaco's `ForeignElementType` is a two-bit flag. MoonBit models the closed
/// four-state result explicitly so a line with both pseudo-element sides does
/// not lose the first fact when the second is encountered.
fn add_foreign_element(
current : ForeignElementType,
next : ForeignElementType,
) -> ForeignElementType {
match (current, next) {
(None, value) => value
(Before, After) | (After, Before) => BeforeAndAfter
(BeforeAndAfter, _) => BeforeAndAfter
(value, _) => value
}
}
///|
fn first_non_whitespace_index_of(view : StringView, len : Int32) -> Int32 {
for i in 0.. Int32 {
for i in len>..0 {
let charcode = view.unsafe_get(i).to_int()
if charcode != cc_space && charcode != cc_tab {
return i
}
}
-1
}
///|
/// Monaco's `strings.containsRTL`, restricted to the code units in
/// `line_content[start:end)` (surrogate pairs decoded to code points).
fn contains_rtl_in(line_content : String, start : Int32, end : Int32) -> Bool {
let view = line_content.view()
for i = start; i < end; {
let c = view.unsafe_get(i).to_int()
let (i, code) = if c >= 0xD800 && c <= 0xDBFF && i + 1 < end {
let low = view.unsafe_get(i + 1).to_int()
if low >= 0xDC00 && low <= 0xDFFF {
(i + 1, 0x10000 + ((c - 0xD800) << 10) + (low - 0xDC00))
} else {
(i, c)
}
} else {
(i, c)
}
if is_rtl_code(code) {
break true
}
continue i + 1
} nobreak {
false
}
}
///|
fn is_rtl_code(code : Int32) -> Bool {
// Generated from vscode's CONTAINS_RTL regex.
code == 0x05BE ||
code == 0x05C0 ||
code == 0x05C3 ||
code == 0x05C6 ||
(code >= 0x05D0 && code <= 0x05F4) ||
code == 0x0608 ||
code == 0x060B ||
code == 0x060D ||
(code >= 0x061B && code <= 0x064A) ||
(code >= 0x066D && code <= 0x066F) ||
(code >= 0x0671 && code <= 0x06D5) ||
code == 0x06E5 ||
code == 0x06E6 ||
code == 0x06EE ||
code == 0x06EF ||
(code >= 0x06FA && code <= 0x0710) ||
(code >= 0x0712 && code <= 0x072F) ||
(code >= 0x074D && code <= 0x07A5) ||
(code >= 0x07B1 && code <= 0x07EA) ||
code == 0x07F4 ||
code == 0x07F5 ||
code == 0x07FA ||
(code >= 0x07FE && code <= 0x0815) ||
code == 0x081A ||
code == 0x0824 ||
code == 0x0828 ||
(code >= 0x0830 && code <= 0x0858) ||
(code >= 0x085E && code <= 0x088E) ||
(code >= 0x08A0 && code <= 0x08C9) ||
code == 0x200F ||
code == 0xFB1D ||
(code >= 0xFB1F && code <= 0xFB28) ||
(code >= 0xFB2A && code <= 0xFD3D) ||
(code >= 0xFD50 && code <= 0xFDC7) ||
(code >= 0xFDF0 && code <= 0xFDFC) ||
(code >= 0xFE70 && code <= 0xFEFC) ||
(code >= 0x10800 && code <= 0x1091B) ||
(code >= 0x10920 && code <= 0x10A00) ||
(code >= 0x10A10 && code <= 0x10A35) ||
(code >= 0x10A40 && code <= 0x10AE4) ||
(code >= 0x10AEB && code <= 0x10B35) ||
(code >= 0x10B40 && code <= 0x10BFF) ||
(code >= 0x10C00 && code <= 0x10D23) ||
(code >= 0x10E80 && code <= 0x10EA9) ||
(code >= 0x10EAD && code <= 0x10F45) ||
(code >= 0x10F51 && code <= 0x10F81) ||
(code >= 0x10F86 && code <= 0x10FF6) ||
(code >= 0x1E800 && code <= 0x1E8CF) ||
(code >= 0x1E900 && code <= 0x1E943) ||
(code >= 0x1E94B && code <= 0x1EBFF) ||
(code >= 0x1EC00 && code <= 0x1EEBB)
}