///|
pub(all) struct MarkdownEditorTableCellRange {
hit_start : Int
hit_end : Int
content_start : Int
content_end : Int
row : Int
column : Int
} derive(Eq, Debug)
///|
fn markdown_editor_move_table_cell(
source : String,
caret : Int,
selection : @core.TextRange?,
forward~ : Bool,
) -> RichTextInputTransform? {
if markdown_editor_active_selection(selection) is Some(_) {
return None
}
let chars = source.to_array()
let caret = markdown_editor_clamp_int(caret, 0, chars.length())
for block in parse_markdown_editor_blocks(source) {
if !(block.block.kind is MarkdownBlockKind::Table) ||
caret < block.source_range.start ||
caret > block.source_range.end {
continue
}
let cells = markdown_editor_table_cells_for_block(source, block)
if cells.length() == 0 {
return None
}
let current = markdown_editor_table_cell_index_at(cells, caret)
match current {
Some(index) =>
if forward {
if index + 1 < cells.length() {
return Some(
markdown_editor_transform_result(
text=source,
caret=cells[index + 1].content_start,
),
)
}
return Some(
markdown_editor_append_table_row(source, block, cells[0].row),
)
} else if index > 0 {
return Some(
markdown_editor_transform_result(
text=source,
caret=cells[index - 1].content_start,
),
)
} else {
return Some(
markdown_editor_exit_table_before_table_start(source, block),
)
}
None => return None
}
}
None
}
///|
fn markdown_editor_exit_table_before_table_start(
source : String,
block : MarkdownEditorBlock,
) -> RichTextInputTransform {
let chars = source.to_array()
let start = markdown_editor_clamp_int(
block.source_range.start,
0,
chars.length(),
)
if start == 0 {
return markdown_editor_transform_result(text="\n" + source, caret=0)
}
if chars[start - 1] == '\n' {
if start == 1 || (start >= 2 && chars[start - 2] == '\n') {
return markdown_editor_transform_result(text=source, caret=start - 1)
}
let text = String::from_array(chars[:start]) +
"\n" +
String::from_array(chars[start:])
return markdown_editor_transform_result(text~, caret=start)
}
markdown_editor_transform_result(
text=String::from_array(chars[:start]) +
"\n\n" +
String::from_array(chars[start:]),
caret=start + 1,
)
}
///|
fn markdown_editor_table_cells_for_block(
source : String,
block : MarkdownEditorBlock,
) -> Array[MarkdownEditorTableCellRange] {
let cells : Array[MarkdownEditorTableCellRange] = []
let lines = markdown_editor_lines(source)
let mut table_line_index = 0
let mut row = 0
for line in lines {
if line.start < block.source_range.start ||
line.end > block.source_range.end {
continue
}
if table_line_index != 1 {
cells.append(markdown_editor_table_cells_for_line(line, row))
row = row + 1
}
table_line_index = table_line_index + 1
}
cells
}
///|
fn markdown_editor_table_cells_for_line(
line : MarkdownLine,
row : Int,
) -> Array[MarkdownEditorTableCellRange] {
let cells : Array[MarkdownEditorTableCellRange] = []
let chars = line.text.to_array()
let mut start = 0
let mut end = chars.length()
if start < end && chars[start] == '|' {
start = start + 1
}
if end > start &&
chars[end - 1] == '|' &&
markdown_editor_table_pipe_is_separator(chars, end - 1) {
end = end - 1
}
let mut segment_start = start
let mut index = start
let mut column = 0
while index <= end {
if index == end ||
(
chars[index] == '|' &&
markdown_editor_table_pipe_is_separator(chars, index)
) {
let content = markdown_editor_table_cell_content_range(
chars, segment_start, index,
)
cells.push({
hit_start: line.start + segment_start,
hit_end: line.start + index,
content_start: line.start + content.start,
content_end: line.start + content.end,
row,
column,
})
column = column + 1
segment_start = index + 1
}
index = index + 1
}
cells
}
///|
fn markdown_editor_table_pipe_is_separator(
chars : Array[Char],
index : Int,
) -> Bool {
if index < 0 || index >= chars.length() || chars[index] != '|' {
return false
}
let mut slash_count = 0
let mut cursor = index - 1
while cursor >= 0 && chars[cursor] == '\\' {
slash_count = slash_count + 1
cursor = cursor - 1
}
slash_count % 2 == 0
}
///|
fn markdown_editor_table_cell_content_range(
chars : Array[Char],
start : Int,
end : Int,
) -> MarkdownSourceRange {
let mut content_start = start
let mut content_end = end
while content_start < content_end &&
(chars[content_start] == ' ' || chars[content_start] == '\t') {
content_start = content_start + 1
}
while content_end > content_start &&
(chars[content_end - 1] == ' ' || chars[content_end - 1] == '\t') {
content_end = content_end - 1
}
{ start: content_start, end: content_end }
}
///|
fn markdown_editor_table_cell_index_at(
cells : Array[MarkdownEditorTableCellRange],
caret : Int,
) -> Int? {
for index in 0..= cell.hit_start && caret <= cell.hit_end {
return Some(index)
}
}
None
}
///|
fn markdown_editor_insert_table_row_on_enter(
source : String,
caret : Int,
selection : @core.TextRange?,
) -> RichTextInputTransform? {
if markdown_editor_active_selection(selection) is Some(_) {
return None
}
let chars = source.to_array()
let caret = markdown_editor_clamp_int(caret, 0, chars.length())
for block in parse_markdown_editor_blocks(source) {
if !(block.block.kind is MarkdownBlockKind::Table) ||
caret < block.source_range.start ||
caret > block.source_range.end {
continue
}
let cells = markdown_editor_table_cells_for_block(source, block)
if cells.length() == 0 {
return None
}
match markdown_editor_table_cell_index_at(cells, caret) {
Some(index) => {
match
markdown_editor_exit_table_after_empty_last_row_on_enter(
source,
block,
cells,
cells[index],
) {
Some(transform) => return Some(transform)
None => ()
}
return Some(
markdown_editor_insert_table_row_after_cell(
source,
cells,
cells[index],
),
)
}
None => return None
}
}
None
}
///|
fn markdown_editor_exit_table_after_empty_last_row_on_enter(
source : String,
block : MarkdownEditorBlock,
cells : Array[MarkdownEditorTableCellRange],
current : MarkdownEditorTableCellRange,
) -> RichTextInputTransform? {
if current.row <= 0 ||
current.row != markdown_editor_table_last_data_row(cells) ||
!markdown_editor_table_row_empty(source, cells, current.row) {
return None
}
let data_row_count = markdown_editor_table_data_row_count(cells)
if data_row_count > 1 {
let row_start = markdown_editor_table_row_start(cells, current.row)
let row_end = markdown_editor_table_row_end(source, cells, current.row)
let chars = source.to_array()
let remove_start = if row_start > 0 && chars[row_start - 1] == '\n' {
row_start - 1
} else {
row_start
}
let text = String::from_array(chars[:remove_start]) +
String::from_array(chars[row_end:])
return Some(markdown_editor_exit_table_after_table_end(text, remove_start))
}
Some(
markdown_editor_exit_table_after_table_end(source, block.source_range.end),
)
}
///|
fn markdown_editor_table_last_data_row(
cells : Array[MarkdownEditorTableCellRange],
) -> Int {
let mut row = 0
for cell in cells {
if cell.row > row {
row = cell.row
}
}
row
}
///|
fn markdown_editor_table_row_empty(
source : String,
cells : Array[MarkdownEditorTableCellRange],
row : Int,
) -> Bool {
let mut found = false
for cell in cells {
if cell.row == row {
found = true
let text = markdown_editor_substring(
source,
cell.content_start,
cell.content_end,
)
if text.trim().to_owned() != "" {
return false
}
}
}
found
}
///|
fn markdown_editor_exit_table_after_table_end(
source : String,
table_end : Int,
) -> RichTextInputTransform {
let chars = source.to_array()
let table_end = markdown_editor_clamp_int(table_end, 0, chars.length())
if table_end >= chars.length() {
return markdown_editor_transform_result(
text=source + "\n",
caret=table_end + 1,
)
}
if chars[table_end] == '\n' {
let caret = table_end + 1
if caret < chars.length() && chars[caret] == '\n' {
markdown_editor_transform_result(text=source, caret~)
} else {
markdown_editor_transform_result(
text=String::from_array(chars[:caret]) +
"\n" +
String::from_array(chars[caret:]),
caret~,
)
}
} else {
markdown_editor_transform_result(
text=String::from_array(chars[:table_end]) +
"\n\n" +
String::from_array(chars[table_end:]),
caret=table_end + 1,
)
}
}
///|
fn markdown_editor_backspace_empty_trailing_table_row(
source : String,
caret : Int,
selection : @core.TextRange?,
) -> RichTextInputTransform? {
markdown_editor_remove_empty_trailing_table_row_at_caret(
source, caret, selection,
)
}
///|
fn markdown_editor_delete_empty_trailing_table_row(
source : String,
caret : Int,
selection : @core.TextRange?,
) -> RichTextInputTransform? {
markdown_editor_remove_empty_trailing_table_row_at_caret(
source, caret, selection,
)
}
///|
fn markdown_editor_remove_empty_trailing_table_row_at_caret(
source : String,
caret : Int,
selection : @core.TextRange?,
) -> RichTextInputTransform? {
if markdown_editor_active_selection(selection) is Some(_) {
return None
}
let caret = markdown_editor_clamp_int(
caret,
0,
markdown_editor_text_length(source),
)
for block in parse_markdown_editor_blocks(source) {
if !(block.block.kind is MarkdownBlockKind::Table) ||
caret < block.source_range.start ||
caret > block.source_range.end {
continue
}
let cells = markdown_editor_table_cells_for_block(source, block)
if cells.length() == 0 {
return None
}
match markdown_editor_table_cell_index_at(cells, caret) {
Some(index) =>
return markdown_editor_remove_empty_trailing_table_row_on_backspace(
source,
cells,
cells[index],
)
None => return None
}
}
None
}
///|
fn markdown_editor_remove_empty_trailing_table_row_on_backspace(
source : String,
cells : Array[MarkdownEditorTableCellRange],
current : MarkdownEditorTableCellRange,
) -> RichTextInputTransform? {
if current.row <= 0 ||
current.row != markdown_editor_table_last_data_row(cells) ||
markdown_editor_table_data_row_count(cells) <= 1 ||
!markdown_editor_table_row_empty(source, cells, current.row) {
return None
}
let row_start = markdown_editor_table_row_start(cells, current.row)
let row_end = markdown_editor_table_row_end(source, cells, current.row)
let chars = source.to_array()
let remove_start = if row_start > 0 && chars[row_start - 1] == '\n' {
row_start - 1
} else {
row_start
}
let text = String::from_array(chars[:remove_start]) +
String::from_array(chars[row_end:])
let caret = markdown_editor_previous_table_row_column_caret(
cells,
current.row,
current.column,
)
Some(
markdown_editor_transform_result(
text~,
caret=markdown_editor_clamp_int(
caret,
0,
markdown_editor_text_length(text),
),
),
)
}
///|
fn markdown_editor_previous_table_row_column_caret(
cells : Array[MarkdownEditorTableCellRange],
row : Int,
column : Int,
) -> Int {
let mut fallback = 0
for cell in cells {
if cell.row == row - 1 {
if cell.column == 0 {
fallback = cell.content_end
}
if cell.column == column {
return cell.content_end
}
}
}
fallback
}
///|
fn markdown_editor_table_column_count(
cells : Array[MarkdownEditorTableCellRange],
) -> Int {
let mut count = 0
for cell in cells {
if cell.column + 1 > count {
count = cell.column + 1
}
}
count
}
///|
fn markdown_editor_table_row_end(
source : String,
cells : Array[MarkdownEditorTableCellRange],
row : Int,
) -> Int {
let mut end = 0
for cell in cells {
if cell.row == row && cell.hit_end > end {
end = cell.hit_end
}
}
let chars = source.to_array()
while end < chars.length() && chars[end] != '\n' {
end = end + 1
}
end
}
///|