feat(chat): live markdown source-mode highlighting in composer (#1401)

* feat(chat): live markdown source-mode highlighting in composer

Highlight markdown syntax, fenced code blocks, and mention-style tokens
directly in the chat input via the existing transparent-textarea overlay
(color/decoration/background only, so caret alignment is preserved).

- Markdown source-mode: inline/fenced code, links, headings, blockquotes,
  list markers, with dimmed syntax punctuation
- Per-language syntax highlighting inside fenced blocks, reusing the editor's
  CodeMirror language resolver + Lezer (bash/js/ts/json/html/css/python/md);
  highlighted blocks use a neutral base, plain fences keep the code color
- Token highlighting on match: @file, @agent, /command, /skill, #snippet
- Auto-pairing: wrap selection with markers, triple-backtick expands to a
  fenced block; paste a URL over a selection to form a markdown link
- Add md/markdown to the shared code-block language resolver

* fix(chat): address composer highlight review

- Tilde (~~~) fenced blocks now get per-language syntax highlighting
- Share fence open/close detection between tokenizeMarkdown and
  highlightFencedCode so they agree on boundaries (fence length + format),
  fixing range bleed with 4-backtick fences and ```lang lines inside blocks
- Replace buildHighlightParts O(segments x ranges) scan with a sweep-line
  over an active set (verified equivalent vs the prior algorithm across 30k
  randomized cases, including overlaps and explicit class/priority)
- Cap per-block Lezer parsing at 20k chars; oversized blocks keep the neutral
  code base without per-token coloring
This commit is contained in:
Bohdan Triapitsyn
2026-05-24 17:21:11 +03:00
committed by GitHub
parent af25edd3f7
commit c4956af565
4 changed files with 695 additions and 59 deletions
@@ -17,7 +17,7 @@ import { shell } from '@codemirror/legacy-modes/mode/shell';
const shellLanguage = StreamLanguage.define(shell);
function codeBlockLanguageResolver(info: string): Language | LanguageDescription | null {
export function codeBlockLanguageResolver(info: string): Language | LanguageDescription | null {
const normalized = info.trim().toLowerCase();
switch (normalized) {
@@ -49,6 +49,11 @@ function codeBlockLanguageResolver(info: string): Language | LanguageDescription
case 'py':
case 'python':
return python().language;
case 'md':
case 'markdown':
case 'mdown':
case 'mkd':
return markdown().language;
case 'heex':
case 'eex':
case 'leex':