///|
fn markdown_editor_table_cell_at_text_offset(
source : String,
caret : Int,
selection : @core.TextRange?,
) -> Bool {
let anchor = match markdown_editor_active_selection(selection) {
Some(range) => range.normalized().start
None => caret
}
let anchor = markdown_editor_clamp_int(
anchor,
0,
markdown_editor_text_length(source),
)
for block in parse_markdown_editor_blocks(source) {
if !(block.block.kind is MarkdownBlockKind::Table) ||
anchor < block.source_range.start ||
anchor > block.source_range.end {
continue
}
let cells = markdown_editor_table_cells_for_block(source, block)
if markdown_editor_table_cell_index_at(cells, anchor) is Some(_) {
return true
}
}
false
}
///|
fn markdown_editor_hard_break_continuation_prefix(line : String) -> String {
let chars = line.to_array()
let indent = markdown_editor_leading_indent_length(chars)
let indent_text = String::from_array(chars[:indent])
if chars.length() >= indent + 2 &&
chars[indent] == '>' &&
chars[indent + 1] == ' ' {
let nested = String::from_array(chars[indent + 2:])
indent_text + "> " + markdown_editor_hard_break_continuation_prefix(nested)
} else if chars.length() >= indent + 6 &&
chars[indent] == '-' &&
chars[indent + 1] == ' ' &&
chars[indent + 2] == '[' &&
(
chars[indent + 3] == ' ' ||
chars[indent + 3] == 'x' ||
chars[indent + 3] == 'X'
) &&
chars[indent + 4] == ']' &&
chars[indent + 5] == ' ' {
indent_text + markdown_editor_spaces(6)
} else if chars.length() >= indent + 2 &&
(chars[indent] == '-' || chars[indent] == '*' || chars[indent] == '+') &&
chars[indent + 1] == ' ' {
indent_text + markdown_editor_spaces(2)
} else {
let ordered_end = markdown_editor_ordered_marker_length(chars)
if ordered_end > indent {
indent_text + markdown_editor_spaces(ordered_end - indent)
} else {
indent_text
}
}
}
///|
fn markdown_editor_spaces(count : Int) -> String {
let chars : Array[Char] = []
let mut index = 0
while index < count {
chars.push(' ')
index = index + 1
}
String::from_array(chars)
}
///|
fn markdown_editor_complete_table_header_on_enter(
chars : Array[Char],
range : MarkdownSourceRange,
line : String,
caret : Int,
) -> RichTextInputTransform? {
if caret != range.end || !markdown_editor_table_header_candidate(line) {
return None
}
// 如果 header 行之后已经紧跟 separator 行(即表格已成型),则不再
// 重复插入 separator,交给 markdown_editor_insert_table_row_on_enter 处理。
if range.end < chars.length() && chars[range.end] == '\n' {
let mut next_end = range.end + 1
while next_end < chars.length() && chars[next_end] != '\n' {
next_end = next_end + 1
}
let next_line = String::from_array(chars[range.end + 1:next_end])
if markdown_editor_table_separator_line(next_line) {
return None
}
}
let column_count = markdown_editor_split_table_cells(line).length()
if column_count <= 1 {
return None
}
let separator = markdown_editor_table_separator_for_columns(column_count)
let row = markdown_editor_empty_table_row(column_count)
let insert = "\n" + separator + "\n" + row
let text = String::from_array(chars[:caret]) +
insert +
String::from_array(chars[caret:])
Some(
markdown_editor_transform_result(
text~,
caret=caret +
markdown_editor_text_length(insert) -
markdown_editor_text_length(row) +
2,
),
)
}
///|
fn markdown_editor_table_header_candidate(line : String) -> Bool {
let trimmed = line.trim().to_owned()
if !trimmed.contains("|") || markdown_editor_table_separator_line(trimmed) {
return false
}
let cells = markdown_editor_split_table_cells(trimmed)
if cells.length() <= 1 {
return false
}
let chars = trimmed.to_array()
let explicit_pipe_table = chars.length() > 1 &&
chars[0] == '|' &&
chars[chars.length() - 1] == '|'
for cell in cells {
if !explicit_pipe_table && cell.trim().to_owned() == "" {
return false
}
}
true
}
///|
fn markdown_editor_table_separator_for_columns(column_count : Int) -> String {
let cells : Array[String] = []
for _ in 0.. RichTextInputTransform? {
match
markdown_editor_table_cell_matrix_paste(source, caret, selection, input) {
Some(transform) => return Some(transform)
None => ()
}
if input == "\t" ||
input == "\n" ||
markdown_editor_active_selection(selection) is Some(_) ||
(!input.contains("\n") && !input.contains("|")) {
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)
match markdown_editor_table_cell_index_at(cells, caret) {
Some(_) => {
let insert = markdown_editor_table_cell_safe_input(input)
if insert == input {
return None
}
return Some(
markdown_editor_transform_result(
text=String::from_array(chars[:caret]) +
insert +
String::from_array(chars[caret:]),
caret=caret + markdown_editor_text_length(insert),
),
)
}
None => return None
}
}
None
}
///|
fn markdown_editor_table_cell_matrix_paste(
source : String,
caret : Int,
selection : @core.TextRange?,
input : String,
) -> RichTextInputTransform? {
if input == "\t" || markdown_editor_active_selection(selection) is Some(_) {
return None
}
let paste = match markdown_editor_table_matrix_paste(input) {
Some(paste) => paste
None => 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)
match markdown_editor_table_cell_index_at(cells, caret) {
Some(index) =>
return Some(
markdown_editor_apply_table_paste_grid(
source,
block,
cells[index],
paste.grid,
paste.alignments,
caret,
),
)
None => return None
}
}
None
}
///|
pub(all) struct MarkdownEditorTableMatrixPaste {
grid : Array[Array[String]]
alignments : Array[@core.TextAlign?]
}
///|
fn markdown_editor_table_matrix_paste(
input : String,
) -> MarkdownEditorTableMatrixPaste? {
if input.contains("\t") {
let grid = markdown_editor_table_paste_grid(input)
if grid.length() == 0 ||
markdown_editor_table_paste_grid_column_count(grid) <= 1 {
None
} else {
Some({ grid, alignments: [] })
}
} else {
match markdown_editor_markdown_table_paste(input) {
Some(table) => Some({ grid: table.grid, alignments: table.alignments })
None => None
}
}
}
///|
fn markdown_editor_table_paste_grid(input : String) -> Array[Array[String]] {
let rows : Array[Array[String]] = []
for line in markdown_editor_paste_lines(input) {
let row : Array[String] = []
for cell in markdown_editor_split_tsv_cells(line) {
row.push(markdown_editor_table_cell_safe_input(cell))
}
rows.push(row)
}
rows
}
///|
fn markdown_editor_split_tsv_cells(line : String) -> Array[String] {
let cells : Array[String] = []
let chars = line.to_array()
let current : Array[Char] = []
let mut index = 0
while index <= chars.length() {
if index == chars.length() || chars[index] == '\t' {
cells.push(String::from_array(current))
current.clear()
} else {
current.push(chars[index])
}
index = index + 1
}
cells
}
///|
fn markdown_editor_table_paste_grid_column_count(
grid : Array[Array[String]],
) -> Int {
let mut count = 0
for row in grid {
if row.length() > count {
count = row.length()
}
}
count
}
///|
fn markdown_editor_paste_tsv_as_table(
source : String,
caret : Int,
selection : @core.TextRange?,
input : String,
) -> RichTextInputTransform? {
if input == "\t" ||
!input.contains("\t") ||
markdown_editor_any_code_block_at_content_offset(source, caret, selection) {
return None
}
let grid = markdown_editor_table_paste_grid(input)
if grid.length() == 0 ||
markdown_editor_table_paste_grid_column_count(grid) <= 1 {
return None
}
let table = markdown_editor_table_text_from_paste_grid(grid)
markdown_editor_paste_table_text_as_block(source, caret, selection, table)
}
///|
fn markdown_editor_paste_markdown_table_as_table(
source : String,
caret : Int,
selection : @core.TextRange?,
input : String,
) -> RichTextInputTransform? {
if markdown_editor_any_code_block_at_content_offset(source, caret, selection) {
return None
}
match markdown_editor_markdown_table_paste(input) {
Some(table) =>
markdown_editor_paste_table_text_as_block(
source,
caret,
selection,
table.table_text,
)
None => None
}
}
///|
fn markdown_editor_paste_table_text_as_block(
source : String,
caret : Int,
selection : @core.TextRange?,
table : String,
) -> RichTextInputTransform? {
markdown_editor_paste_block_text_as_block(source, caret, selection, table)
}
///|
fn markdown_editor_paste_block_text_as_block(
source : String,
caret : Int,
selection : @core.TextRange?,
block_text : String,
) -> RichTextInputTransform? {
let chars = source.to_array()
let range = match markdown_editor_active_selection(selection) {
Some(active) => active.normalized()
None => {
let caret = markdown_editor_clamp_int(caret, 0, chars.length())
@core.TextRange::new(start=caret, end=caret)
}
}
let start = markdown_editor_clamp_int(range.start, 0, chars.length())
let end = markdown_editor_clamp_int(range.end, start, chars.length())
let prefix = markdown_editor_block_insert_prefix(chars, start)
let suffix = markdown_editor_block_insert_suffix(chars, end)
let text = String::from_array(chars[:start]) +
prefix +
block_text +
suffix +
String::from_array(chars[end:])
Some(
markdown_editor_transform_result(
text~,
caret=start +
markdown_editor_text_length(prefix) +
markdown_editor_text_length(block_text),
),
)
}
///|
pub(all) struct MarkdownEditorPastedMarkdownTable {
grid : Array[Array[String]]
alignments : Array[@core.TextAlign?]
table_text : String
} derive(Eq, Debug)
///|
fn markdown_editor_markdown_table_paste(
input : String,
) -> MarkdownEditorPastedMarkdownTable? {
if !input.contains("|") || input.contains("\t") {
return None
}
let source_lines = markdown_editor_paste_lines(input)
let table_lines = markdown_editor_trim_blank_table_paste_lines(source_lines)
if table_lines.length() < 2 ||
!markdown_editor_table_header_candidate(table_lines[0]) ||
!markdown_editor_table_separator_line(table_lines[1]) {
return None
}
let header = markdown_editor_markdown_table_paste_cells(table_lines[0])
if header.length() <= 1 {
return None
}
let separator_cells = markdown_editor_split_table_cells(table_lines[1])
if separator_cells.length() != header.length() {
return None
}
let grid : Array[Array[String]] = [header]
for index in 2.. Array[String] {
let mut start = 0
let mut end = lines.length()
while start < end && markdown_editor_blank_source_line(lines[start]) {
start = start + 1
}
while end > start && markdown_editor_blank_source_line(lines[end - 1]) {
end = end - 1
}
let trimmed : Array[String] = []
for index in start.. Array[String] {
let cells : Array[String] = []
for cell in markdown_editor_split_table_cells(line) {
cells.push(cell.trim().to_owned())
}
cells
}
///|
fn markdown_editor_table_text_from_paste_grid(
grid : Array[Array[String]],
) -> String {
let column_count = markdown_editor_table_paste_grid_column_count(grid)
let lines : Array[String] = []
lines.push(
markdown_editor_table_paste_join_cells(
markdown_editor_table_paste_padded_row(grid[0], column_count),
),
)
let separators : Array[String] = []
for _ in 0.. Array[String] {
let cells : Array[String] = []
for cell in row {
cells.push(cell)
}
while cells.length() < column_count {
cells.push("")
}
cells
}
///|
fn markdown_editor_block_insert_prefix(
chars : Array[Char],
start : Int,
) -> String {
if start <= 0 {
""
} else if chars[start - 1] == '\n' {
if start >= 2 && chars[start - 2] == '\n' {
""
} else {
"\n"
}
} else {
"\n\n"
}
}
///|
fn markdown_editor_block_insert_suffix(
chars : Array[Char],
end : Int,
) -> String {
if end >= chars.length() {
""
} else if chars[end] == '\n' {
if end + 1 < chars.length() && chars[end + 1] == '\n' {
""
} else {
"\n"
}
} else {
"\n\n"
}
}
///|
fn markdown_editor_apply_table_paste_grid(
source : String,
block : MarkdownEditorBlock,
current : MarkdownEditorTableCellRange,
grid : Array[Array[String]],
alignments : Array[@core.TextAlign?],
caret : Int,
) -> RichTextInputTransform {
let table_lines = markdown_editor_source_lines_for_range(
source,
block.source_range,
)
let existing_rows = markdown_editor_max_int(0, table_lines.length() - 1)
let grid_columns = markdown_editor_table_paste_grid_column_count(grid)
let column_count = markdown_editor_max_int(
markdown_editor_table_column_count(
markdown_editor_table_cells_for_block(source, block),
),
current.column + grid_columns,
)
let row_count = markdown_editor_max_int(
existing_rows,
current.row + grid.length(),
)
let lines : Array[String] = []
let first = markdown_editor_table_paste_row_line(
table_lines, 0, column_count, current, grid, caret, source,
)
lines.push(first)
lines.push(
markdown_editor_table_paste_separator_line(
table_lines, column_count, current, alignments,
),
)
for row in 1.. Array[String] {
let lines : Array[String] = []
for line in markdown_editor_lines(source) {
if line.start >= range.start && line.end <= range.end {
lines.push(line.text)
}
}
lines
}
///|
fn markdown_editor_table_paste_row_line(
table_lines : Array[String],
row : Int,
column_count : Int,
current : MarkdownEditorTableCellRange,
grid : Array[Array[String]],
caret : Int,
source : String,
) -> String {
let line_index = if row == 0 { 0 } else { row + 1 }
let base = if line_index < table_lines.length() {
table_lines[line_index]
} else {
""
}
let cells = markdown_editor_table_paste_cells(base, column_count)
let paste_row = row - current.row
if paste_row >= 0 && paste_row < grid.length() {
let pasted = grid[paste_row]
for paste_column in 0.. Array[String] {
let cells : Array[String] = []
if line != "" {
for cell in markdown_editor_split_table_cells(line) {
cells.push(cell.trim().to_owned())
}
}
while cells.length() < column_count {
cells.push("")
}
cells
}
///|
fn markdown_editor_table_paste_cell_at(
cells : Array[String],
column : Int,
) -> String {
if column < cells.length() {
cells[column]
} else {
""
}
}
///|
fn markdown_editor_insert_table_paste_cell(
original : String,
insert : String,
source : String,
current : MarkdownEditorTableCellRange,
caret : Int,
) -> String {
let content = markdown_editor_substring(
source,
current.content_start,
current.content_end,
)
let local_offset = markdown_editor_clamp_int(
caret - current.content_start,
0,
markdown_editor_text_length(content),
)
if content == original {
markdown_editor_substring(content, 0, local_offset) +
insert +
markdown_editor_substring(
content,
local_offset,
markdown_editor_text_length(content),
)
} else {
markdown_editor_substring(original, 0, local_offset) +
insert +
markdown_editor_substring(
original,
local_offset,
markdown_editor_text_length(original),
)
}
}
///|
fn markdown_editor_table_paste_separator_line(
table_lines : Array[String],
column_count : Int,
current : MarkdownEditorTableCellRange,
alignments : Array[@core.TextAlign?],
) -> String {
let base = if table_lines.length() > 1 { table_lines[1] } else { "" }
let cells = markdown_editor_table_paste_cells(base, column_count)
for index in 0..= 0 && column < cells.length() {
cells[column] = markdown_editor_table_alignment_marker(
alignments[paste_column],
)
}
}
markdown_editor_table_paste_join_cells(cells)
}
///|
fn markdown_editor_table_paste_join_cells(cells : Array[String]) -> String {
"| " + cells.join(" | ") + " |"
}
///|
pub(all) struct MarkdownEditorTablePasteTarget {
row : Int
column : Int
}
///|
fn markdown_editor_table_paste_last_target(
current : MarkdownEditorTableCellRange,
grid : Array[Array[String]],
) -> MarkdownEditorTablePasteTarget {
if grid.length() == 0 {
return { row: current.row, column: current.column }
}
let row = current.row + markdown_editor_max_int(0, grid.length() - 1)
let last = grid[grid.length() - 1]
let column = current.column + markdown_editor_max_int(0, last.length() - 1)
{ row, column }
}
///|
fn markdown_editor_table_paste_caret(
table_text : String,
table_start : Int,
row : Int,
column : Int,
) -> Int {
let lines = markdown_editor_lines(table_text)
let line_index = if row == 0 { 0 } else { row + 1 }
if line_index >= lines.length() {
return table_start + markdown_editor_text_length(table_text)
}
let line = lines[line_index]
let cells = markdown_editor_table_cells_for_line(
{
text: line.text,
start: table_start + line.start,
end: table_start + line.end,
},
row,
)
for cell in cells {
if cell.column == column {
return cell.content_end
}
}
table_start + line.end
}
///|
fn markdown_editor_table_cell_safe_input(input : String) -> String {
let chars = input.to_array()
let out : Array[Char] = []
let mut index = 0
let mut previous_was_space = false
while index < chars.length() {
let ch = chars[index]
if ch == '\n' || ch == '\r' {
if !previous_was_space {
out.push(' ')
previous_was_space = true
}
} else if ch == '|' && markdown_editor_table_pipe_is_separator(chars, index) {
out.push('\\')
out.push('|')
previous_was_space = false
} else {
out.push(ch)
previous_was_space = ch == ' ' || ch == '\t'
}
index = index + 1
}
String::from_array(out)
}
///|