Language Support¶
Supported File Types¶
Language Check extracts prose from these file formats using tree-sitter parsers:
Format |
Language ID |
Extensions |
Parser / Strategy |
|---|---|---|---|
Markdown |
|
|
tree-sitter-markdown |
MDX |
(alias) |
|
Treated as Markdown |
HTML |
|
|
tree-sitter-html |
XHTML |
(alias) |
|
Treated as HTML |
LaTeX |
|
|
tree-sitter-latex |
R Sweave |
|
|
R chunk preprocessing + tree-sitter-latex |
reStructuredText |
|
|
tree-sitter-rst |
Org mode |
|
|
tree-sitter-org (vendored) |
BibTeX |
|
|
tree-sitter-bibtex |
Typst |
|
|
tree-sitter-typst (vendored) |
Forester |
|
|
tree-sitter-forester (vendored) |
Prose extraction details¶
Each language has a custom prose extractor that understands which parts of a document contain human-readable text.
An extractor answers two questions. The first is which AST nodes carry prose, which is a walk over the tree. The second is what sits in the gap between two of those nodes — the markup that decides whether the words on either side belong to one prose block, and which bytes the checker must never see. A language describes its gap syntax once, as a single function that recognizes one token at a byte offset, and both answers are derived from it; see Adding Language Support via the Plugin Path for how to write one. Markdown and HTML take a different path entirely, selecting prose nodes with a tree-sitter query and no gap analysis at all.
Markdown / HTML — Uses tree-sitter query patterns to select prose nodes: paragraphs, headings and table cells in Markdown, text nodes in HTML. Fenced code blocks, front matter and
<script>/<style>bodies are skipped. A Markdown block is handed to the checker whole, so inline code spans and link URLs are skipped by the engine’s own Markdown parser rather than at extraction.LaTeX — Tree-walks the AST collecting
wordnodes from\begin{document}onward. Skips preamble, math environments, verbatim/minted/algorithm blocks, and structural commands (\ref,\label,\includegraphics, etc.). The delimiter-delimited argument of\verb|...|,\verb*|...|,\lstinline!...!and\mintinline{lang}|...|is excluded too, although the grammar leaves it outside the command node. Display math (\[...\]) bridges into surrounding prose as an exclusion zone.R Sweave — Preprocesses R code chunks (
<<...>>=through@) by blanking them with whitespace, then delegates to the LaTeX extractor.reStructuredText — Extracts
paragraphandtitlenodes, with inline literals as exclusion zones. A directive is split by role: its argument is a path or language (except oncsv-table,admonitionand friends, where it is the rendered title), its:caption:and:alt:options are prose, and its body is prose unless the type is code or data (code-block,math,raw,toctree, …). Because tree-sitter-rst leaves a directive body unparsed, the body is recovered from the raw lines: an explicit markup line, a doctest>>>prompt or a trailing::takes its whole indented block out of the prose, so a code block nested in a.. note::stays out.Org mode — Extracts paragraph text and heading titles, plus the parts of a structured node that render: the contents of a
#+begin_quoteor#+begin_verseblock, table cells, footnote definitions ([fn:1] …) and the value of a#+TITLE:,#+SUBTITLE:,#+CAPTION:or#+DESCRIPTION:directive. Skips#+begin_srcblocks, drawers (:PROPERTIES:), LaTeX environments, comments, and the remaining directives.BibTeX — Extracts prose from specific fields:
title,booktitle,abstract,note,annote,annotation,howpublished, andseries. Other fields (author, journal, year, etc.) are ignored. LaTeX commands inside values (e.g.\emph{...}) are handled via exclusion zones.Typst — Collects
textnodes from paragraphs, headings, and list items. Skips code blocks (```), inline code (`), math ($...$and$ ... $),#codeexpressions, set/show rules, let bindings, imports, includes, labels, references, URLs, escapes, and comments. Content blocks passed to a function (#columns(2)[...],#align(center)[...],#figure(caption: [...])[...]) are still checked — only the call itself and its non-content arguments are skipped. Inline markup (*bold*,_italic_) is bridged through.Forester — Collects
textandescapenodes, skipping math (#{...},##{...}), verbatim fences, wiki links, comments, and structural commands (\import,\ref,\def, etc.). Display math bridges as an exclusion zone.
Adding more file types¶
You can add support for extra file types without code in two ways:
map new extensions onto existing built-in language IDs, or
define regex-based Simplified Language Schema (SLS) YAML files in
.langcheck/schemas/.
See the Config-Only Language Guide for both workflows, including a full schema example.
Tip
To add support for an entirely new markup language with its own tree-sitter grammar, see the Plugin Language Guide.
Checking Languages¶
The spell-check and grammar-check language is separate from the file type. Click the language indicator in the VS Code status bar to switch:
EN-US — American English
EN-GB — British English
DE-DE — German
FR — French
ES — Spanish
Language detection can also be automatic via the whatlang crate when no explicit language is set.
More than one language in one file¶
engines.spell_language is the document default, not the only answer. A thesis
in French quoting English, or a German paper with an English abstract, is
checked passage by passage: the prose is grouped by language and each group
goes to the engines that read it, so Harper handles the English while
LanguageTool handles the French.
Three things declare a language, strongest first.
A region directive. Works in every format, and is the only option where the markup has nothing to say:
<!-- lang-check-begin lang:fr -->
Ceci est du texte français.
<!-- lang-check-end -->
A scope marker, which runs until the next one:
<!-- lang: fr -->
The markup’s own declaration, where the format has one. Typst does:
#set text(lang: "fr", region: "CH") // the rest of the enclosing block
#text(lang: "en")[An English aside.] // just this content
That last one needs no extra annotation at all — the lang: an author already
writes for hyphenation and quotation marks is the one the checker reads, and
region: becomes the BCP-47 subtag.
See the directives reference for the full syntax,
and examples/typst/
for a worked document.
When nothing can read the language¶
Not every language has an engine. LanguageTool has no Hebrew and no Latin, and
Harper reads only English. Rather than let the passage pass as clean, the
checker reports a languagecheck.no-provider diagnostic naming the language,
and does not mark the engine unhealthy for declining one.
For most of those, Hunspell fills the gap with spelling — Hebrew, Latin, Welsh, Old English and a long tail besides, using the dictionary format LibreOffice and Firefox already read. It checks spelling and nothing else, so where LanguageTool supports a language, prefer it.
In VS Code the editor offers to install a dictionary when it meets a language
none of the enabled engines read, and remembers a refusal. An engine that
declares the language answers for it, so a custom engines.external checker
naming languages: ["cy"] covers Welsh and the offer never appears — but one
that declares nothing at all still claims every language, and suppresses the
offer for all of them. See
External providers for what a provider should
declare.