///|
fn markdown_editor_complete_code_fence_on_enter(
  chars : Array[Char],
  range : MarkdownSourceRange,
  line : String,
  caret : Int,
) -> RichTextInputTransform? {
  if caret != range.end {
    return None
  }
  let fence = match markdown_editor_opening_code_fence_marker(line) {
    Some(fence) => fence.marker
    None => return None
  }
  let indent = markdown_editor_line_indent_prefix(line)
  let insert = "\n" + indent + "\n" + indent + fence
  let text = String::from_array(chars[:caret]) +
    insert +
    String::from_array(chars[caret:])
  Some(
    markdown_editor_transform_result(
      text~,
      caret=caret + 1 + markdown_editor_text_length(indent),
      edit_range=Some({ start: caret, end: caret, }),
    ),
  )
}

///|
fn markdown_editor_complete_quoted_code_fence_on_enter(
  chars : Array[Char],
  range : MarkdownSourceRange,
  line : String,
  caret : Int,
) -> RichTextInputTransform? {
  if caret != range.end {
    return None
  }
  let nested = match markdown_editor_quoted_code_nested_text(line) {
    Some(nested) => nested
    None => return None
  }
  let fence = match markdown_editor_opening_code_fence_marker(nested) {
    Some(fence) => fence.marker
    None => return None
  }
  let content_prefix = match markdown_editor_quoted_code_content_prefix(line) {
    Some(prefix) => prefix
    None => return None
  }
  let insert = "\n" + content_prefix + "\n" + content_prefix + fence
  let text = String::from_array(chars[:caret]) +
    insert +
    String::from_array(chars[caret:])
  Some(
    markdown_editor_transform_result(
      text~,
      caret=caret + 1 + markdown_editor_text_length(content_prefix),
      edit_range=Some({ start: caret, end: caret, }),
    ),
  )
}

///|
fn markdown_editor_exit_empty_code_block_on_enter(
  source : String,
  block : MarkdownEditorBlock,
  caret : Int,
) -> RichTextInputTransform? {
  let chars = source.to_array()
  let caret = markdown_editor_clamp_int(caret, 0, chars.length())
  let current_line = markdown_editor_current_line_range(source, {
    caret,
    selection: None,
  })
  let line = String::from_array(chars[current_line.start:current_line.end])
  if line.trim().to_owned() != "" {
    return None
  }
  if current_line.start < block.content_range.start ||
    current_line.end > block.content_range.end {
    return None
  }
  let opening_line_end = markdown_editor_next_line_end(
    chars,
    block.source_range.start,
  )
  let opening_line = String::from_array(
    chars[block.source_range.start:opening_line_end],
  )
  let fence = match markdown_editor_opening_code_fence_marker(opening_line) {
    Some(fence) => fence.marker
    None => return None
  }
  let closing_start = current_line.end + 1
  if closing_start >= chars.length() || closing_start > block.source_range.end {
    return None
  }
  let closing_end = markdown_editor_next_line_end(chars, closing_start)
  let closing_line = String::from_array(chars[closing_start:closing_end])
  if !markdown_editor_closing_code_fence(closing_line, fence) {
    return None
  }
  let exit_indent = markdown_editor_line_indent_prefix(closing_line)
  if closing_end >= chars.length() {
    let insert = "\n" + exit_indent
    Some(
      markdown_editor_transform_result(
        text=source + insert,
        caret=closing_end + markdown_editor_text_length(insert),
        edit_range=Some({ start: closing_end, end: closing_end, }),
      ),
    )
  } else {
    let after_close = closing_end + 1
    if after_close >= chars.length() {
      if exit_indent == "" {
        Some(
          markdown_editor_transform_result(
            text=source,
            caret=after_close,
            edit_range=Some({ start: after_close, end: after_close, }),
          ),
        )
      } else {
        Some(
          markdown_editor_transform_result(
            text=source + exit_indent,
            caret=after_close + markdown_editor_text_length(exit_indent),
            edit_range=Some({ start: after_close, end: after_close, }),
          ),
        )
      }
    } else {
      let insert = exit_indent + "\n"
      Some(
        markdown_editor_transform_result(
          text=String::from_array(chars[:after_close]) +
            insert +
            String::from_array(chars[after_close:]),
          caret=after_close + markdown_editor_text_length(exit_indent),
          edit_range=Some({ start: after_close, end: after_close, }),
        ),
      )
    }
  }
}

///|
/// Index of the shell block of `kind` containing the char `offset`. Block
/// ranges are ordered and non-overlapping, so the containing block is
/// unique and a binary search replaces the per-keystroke full scan.
fn markdown_editor_block_at_offset_of_kind(
  blocks : Array[MarkdownEditorBlock],
  offset : Int,
  kind : MarkdownBlockKind,
) -> MarkdownEditorBlock? {
  match markdown_editor_block_index_at_offset(blocks, offset) {
    Some(index) => {
      let block = blocks[index]
      if block.block.kind == kind {
        Some(block)
      } else {
        None
      }
    }
    None => None
  }
}

///|
fn markdown_editor_quoted_code_block_at_content_offset(
  source : String,
  caret : Int,
  selection : @core.TextRange?,
  context? : MarkdownEditorInputContext? = None,
) -> MarkdownEditorQuotedCodeBlock? {
  if markdown_editor_active_selection(selection) is Some(_) {
    return None
  }
  // With the session context the char length is the text store's O(1) tail
  // offset; without one, fall back to the cached whole-source length.
  let source_length = match context {
    Some(context) => context.store.length()
    None => markdown_editor_text_length(source)
  }
  let caret = markdown_editor_clamp_int(caret, 0, source_length)
  let containing = match context {
    // Quoted fences only ever sit on quote-prefixed lines, which the
    // shell kinds track exactly: scan the enclosing quote block's lines
    // (read from the text store, O(block)) instead of line-splitting the
    // whole document.
    Some(context) =>
      match
        markdown_editor_block_at_offset_of_kind(
          context.blocks,
          caret,
          Blockquote,
        ) {
        Some(block) => {
          let range = block.source_range
          let rebased : Array[MarkdownLine] = []
          for
            line in markdown_editor_lines(
              context.store.text_of(range.start, range.end),
            ) {
            rebased.push({
              text: line.text,
              start: line.start + range.start,
              end: line.end + range.start,
            })
          }
          markdown_editor_quoted_code_block_scan(rebased, caret)
        }
        None => None
      }
    None =>
      markdown_editor_containing_quoted_code_block(
        source,
        ({ caret, selection, } : MarkdownEditorSelection),
      )
  }
  match containing {
    Some(block) =>
      if caret >= block.content_range.start && caret <= block.content_range.end {
        Some(block)
      } else {
        None
      }
    None => None
  }
}

///|
fn markdown_editor_any_code_block_at_content_offset(
  source : String,
  caret : Int,
  selection : @core.TextRange?,
  context? : MarkdownEditorInputContext? = None,
) -> Bool {
  markdown_editor_code_block_at_content_offset(
    source,
    caret,
    selection,
    context~,
  )
  is Some(_) ||
  markdown_editor_quoted_code_block_at_content_offset(
    source,
    caret,
    selection,
    context~,
  )
  is Some(_)
}

///|
fn markdown_editor_exit_empty_quoted_code_block_on_enter(
  source : String,
  block : MarkdownEditorQuotedCodeBlock,
  caret : Int,
) -> RichTextInputTransform? {
  let chars = source.to_array()
  let caret = markdown_editor_clamp_int(caret, 0, chars.length())
  let current_line = markdown_editor_current_line_range(source, {
    caret,
    selection: None,
  })
  let line = String::from_array(chars[current_line.start:current_line.end])
  let nested = match markdown_editor_quoted_code_nested_text(line) {
    Some(nested) => nested
    None => return None
  }
  if nested.trim().to_owned() != "" {
    return None
  }
  if current_line.start < block.content_range.start ||
    current_line.end > block.content_range.end {
    return None
  }
  let opening_line_end = markdown_editor_next_line_end(
    chars,
    block.source_range.start,
  )
  let opening_line = String::from_array(
    chars[block.source_range.start:opening_line_end],
  )
  let opening = match markdown_editor_quoted_code_fence_opening(opening_line) {
    Some(opening) => opening
    None => return None
  }
  let closing_start = current_line.end + 1
  if closing_start >= chars.length() || closing_start > block.source_range.end {
    return None
  }
  let closing_end = markdown_editor_next_line_end(chars, closing_start)
  let closing_line = String::from_array(chars[closing_start:closing_end])
  if !markdown_editor_quoted_code_fence_closing(closing_line, opening) {
    return None
  }
  let exit_indent = match
    markdown_editor_quoted_code_content_prefix(closing_line) {
    Some(prefix) => prefix
    None => opening.quote_prefix
  }
  if closing_end >= chars.length() {
    let insert = "\n" + exit_indent
    Some(
      markdown_editor_transform_result(
        text=source + insert,
        caret=closing_end + markdown_editor_text_length(insert),
        edit_range=Some({ start: closing_end, end: closing_end, }),
      ),
    )
  } else {
    let after_close = closing_end + 1
    if after_close >= chars.length() {
      Some(
        markdown_editor_transform_result(
          text=source + exit_indent,
          caret=after_close + markdown_editor_text_length(exit_indent),
          edit_range=Some({ start: after_close, end: after_close, }),
        ),
      )
    } else {
      let insert = exit_indent + "\n"
      Some(
        markdown_editor_transform_result(
          text=String::from_array(chars[:after_close]) +
            insert +
            String::from_array(chars[after_close:]),
          caret=after_close + markdown_editor_text_length(exit_indent),
          edit_range=Some({ start: after_close, end: after_close, }),
        ),
      )
    }
  }
}

///|
fn markdown_editor_quoted_code_block_enter(
  source : String,
  block : MarkdownEditorQuotedCodeBlock,
  caret : Int,
) -> RichTextInputTransform {
  let chars = source.to_array()
  let caret = markdown_editor_clamp_int(caret, 0, chars.length())
  let current_line = markdown_editor_current_line_range(source, {
    caret,
    selection: None,
  })
  let line = String::from_array(chars[current_line.start:current_line.end])
  let prefix = match markdown_editor_quoted_code_content_prefix(line) {
    Some(prefix) => prefix
    None =>
      markdown_editor_quoted_code_block_opening_content_prefix(source, block)
  }
  let insert = "\n" + prefix
  markdown_editor_transform_result(
    text=String::from_array(chars[:caret]) +
      insert +
      String::from_array(chars[caret:]),
    caret=caret + markdown_editor_text_length(insert),
    edit_range=Some({ start: caret, end: caret, }),
  )
}

///|
fn markdown_editor_quoted_code_block_opening_content_prefix(
  source : String,
  block : MarkdownEditorQuotedCodeBlock,
) -> String {
  let chars = source.to_array()
  let opening_line_end = markdown_editor_next_line_end(
    chars,
    block.source_range.start,
  )
  let opening_line = String::from_array(
    chars[block.source_range.start:opening_line_end],
  )
  match markdown_editor_quoted_code_content_prefix(opening_line) {
    Some(prefix) => prefix
    None => ""
  }
}

///|
fn markdown_editor_quoted_code_nested_text(line : String) -> String? {
  let quote_prefix = markdown_editor_quote_context_prefix(line)
  if quote_prefix == "" {
    return None
  }
  Some(
    markdown_editor_substring(
      line,
      markdown_editor_text_length(quote_prefix),
      markdown_editor_text_length(line),
    ),
  )
}

///|
fn markdown_editor_quoted_code_content_prefix(line : String) -> String? {
  let quote_prefix = markdown_editor_quote_context_prefix(line)
  if quote_prefix == "" {
    return None
  }
  let nested = markdown_editor_substring(
    line,
    markdown_editor_text_length(quote_prefix),
    markdown_editor_text_length(line),
  )
  Some(quote_prefix + markdown_editor_line_indent_prefix(nested))
}

///|
fn markdown_editor_opening_code_fence_marker(
  line : StringView,
) -> MarkdownCodeFenceInfo? {
  let chars = line.to_array()
  let indent = markdown_editor_leading_indent_length(chars)
  if !markdown_editor_code_fence_indent_allowed(chars, indent) ||
    chars.length() < indent + 3 {
    return None
  }
  let fence_char = chars[indent]
  if (fence_char != '`' && fence_char != '~') ||
    chars[indent + 1] != fence_char ||
    chars[indent + 2] != fence_char {
    return None
  }
  let mut fence_end = indent + 3
  while fence_end < chars.length() && chars[fence_end] == fence_char {
    fence_end = fence_end + 1
  }
  for index in fence_end.. Bool {
  if indent > 3 {
    return false
  }
  for index in 0.. MarkdownBlock {
  { kind: CodeBlock, text, inlines: [], table_alignments: [], code_language, }
}

///|
fn markdown_editor_line_indent_prefix(line : String) -> String {
  let chars = line.to_array()
  let indent = markdown_editor_leading_indent_length(chars)
  String::from_array(chars[:indent])
}