MingaAgent.Markdown (Minga v0.1.0)

Copy Markdown View Source

Simple markdown parser for agent chat rendering.

Parses a subset of markdown into styled line segments suitable for terminal rendering. This is intentionally not a full CommonMark parser; it handles the patterns that LLM output commonly uses.

Supported syntax

  • **bold** and __bold__
  • *italic* and _italic_
  • `inline code`
  • [link text](https://example.com)
  • Fenced code blocks (`` with optional language tag) - Indented code blocks (4 spaces, outside list continuations) -# Headers(levels 1-3) -- list itemsand list items-> blockquotes- Horizontal rules (---,**,_) ## Output format Returns a list of{line_segments, line_type}tuples where eachline_segmentsis a list of{text, style}pairs andline_type` indicates the block context.

Summary

Types

Semantic markdown block used by agent chat renderers before styling.

Extracted code block with language and content.

Line type indicating block context.

A parsed line with its segments and type.

A styled text segment.

Style attributes for a text segment.

Functions

Extracts fenced code blocks from markdown text.

Infers the target file path for a code block from surrounding markdown text.

Parses markdown text into styled line segments.

Parses markdown into semantic blocks without decorative rendering glyphs.

Parses inline markdown formatting within a single line.

Types

block()

@type block() ::
  %{kind: :paragraph, lines: [String.t()]}
  | %{kind: :heading, level: 1..3, text: String.t()}
  | %{
      kind: :list_item,
      indent: non_neg_integer(),
      ordered: boolean(),
      ordinal: non_neg_integer(),
      text: String.t()
    }
  | %{kind: :blockquote, lines: [String.t()]}
  | %{kind: :rule}
  | %{kind: :spacer, height: pos_integer()}
  | %{
      kind: :code_block,
      language: String.t(),
      lines: [String.t()],
      complete?: boolean(),
      target_path: String.t() | nil
    }

Semantic markdown block used by agent chat renderers before styling.

code_block()

@type code_block() :: %{language: String.t(), content: String.t()}

Extracted code block with language and content.

line_type()

@type line_type() ::
  :text
  | :code
  | {:code_header, String.t()}
  | :header
  | :blockquote
  | :list_item
  | :rule
  | :empty

Line type indicating block context.

parsed_line()

@type parsed_line() :: {[segment()], line_type()}

A parsed line with its segments and type.

segment()

@type segment() :: {String.t(), style()}

A styled text segment.

style()

@type style() ::
  :plain
  | :bold
  | :italic
  | :bold_italic
  | :code
  | {:link, String.t()}
  | :code_block
  | {:code_content, String.t()}
  | {:syntax, Minga.Core.Face.t()}
  | :header1
  | :header2
  | :header3
  | :blockquote
  | :list_bullet
  | :rule

Style attributes for a text segment.

Functions

extract_code_blocks(text)

@spec extract_code_blocks(String.t()) :: [code_block()]

Extracts fenced code blocks from markdown text.

Returns a list of %{language: String.t(), content: String.t()} maps, one per fenced code block. The content excludes the fence markers.

infer_target_path(text, code_block_index)

@spec infer_target_path(String.t(), non_neg_integer()) :: String.t() | nil

Infers the target file path for a code block from surrounding markdown text.

Scans the text preceding the nth code block (0-indexed) for backtick-wrapped file paths. Returns the last match (closest to the code block), or nil.

Examples

iex> MingaAgent.Markdown.infer_target_path("Update `lib/foo.ex`:\n```elixir\ncode\n```", 0)
"lib/foo.ex"

iex> MingaAgent.Markdown.infer_target_path("No file mentioned\n```elixir\ncode\n```", 0)
nil

parse(text)

@spec parse(String.t()) :: [parsed_line()]

Parses markdown text into styled line segments.

Returns a list of {segments, line_type} tuples, one per output line.

parse_blocks(text)

@spec parse_blocks(String.t()) :: [block()]

Parses markdown into semantic blocks without decorative rendering glyphs.

This parser intentionally keeps the same small markdown surface as parse/1, but returns structure that frontends can render directly. Fenced code blocks preserve blank lines and indentation, and an unclosed fence is returned as an incomplete code block for streaming output.

parse_inline(text)

@spec parse_inline(String.t()) :: [segment()]

Parses inline markdown formatting within a single line.

Returns a list of {text, style} segments.