diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index f923c421..0819d97a 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -607,7 +607,7 @@ jobs: path: dist/plugins retention-days: "7" build-plugins-willow: - name: "Plugins (willow): asciidoc, diff, jinja2, markdown, svelte, typst, vue" + name: "Plugins (willow): asciidoc, diff, jinja2, latex, markdown, svelte, typst, vue" runs-on: depot-ubuntu-24.04-32 container: "ghcr.io/bearcove/arborium-plugin-builder:latest" needs: @@ -625,10 +625,10 @@ jobs: set -e tar -xf generate-output.tar && rm generate-output.tar shell: bash - - name: Build asciidoc, diff, jinja2, markdown, svelte, typst, vue + - name: Build asciidoc, diff, jinja2, latex, markdown, svelte, typst, vue run: |- set -e - ./xtask/target/release/xtask build asciidoc diff jinja2 markdown svelte typst vue -o dist/plugins + ./xtask/target/release/xtask build asciidoc diff jinja2 latex markdown svelte typst vue -o dist/plugins shell: bash - name: Upload plugins artifact uses: actions/upload-artifact@v5 diff --git a/CHANGELOG.md b/CHANGELOG.md index 0c952042..b624406a 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,3 +1,9 @@ +## Unreleased + +### Added + +- LaTeX (`latex` / `tex`) language support via `latex-lsp/tree-sitter-latex` + ## 0.2.2 (2025-12-04) diff --git a/demo/samples/latex.tex b/demo/samples/latex.tex new file mode 100644 index 00000000..57ca4bca --- /dev/null +++ b/demo/samples/latex.tex @@ -0,0 +1,156 @@ +\documentclass[12pt,a4paper]{article} + +% Packages +\usepackage[utf8]{inputenc} +\usepackage[T1]{fontenc} +\usepackage{amsmath} +\usepackage{amssymb} +\usepackage{hyperref} +\usepackage{graphicx} +\usepackage{booktabs} + +\title{A Sample \LaTeX{} Document} +\author{Jane Doe \and John Smith} +\date{\today} + +\begin{document} + +\maketitle + +% Table of contents +\tableofcontents +\newpage + +%% Introduction +\section{Introduction} +\label{sec:intro} + +This document demonstrates the syntax of \LaTeX{}, a document preparation system +based on Donald Knuth's \TeX{} typesetting engine. It is widely used in academia +for \textbf{scientific}, \textit{mathematical}, and \emph{technical} writing. + +See Section~\ref{sec:math} for examples of mathematical typesetting. + +\subsection{Background} +\label{sec:background} + +\LaTeX{} was developed by Leslie Lamport in 1983 as a higher-level interface to +\TeX{}. It provides macros for document structuring, cross-referencing, and +bibliography management~\cite{lamport1994latex}. + +\subsubsection{Why Use \LaTeX{}?} +\begin{itemize} + \item Superior mathematical typesetting + \item Automatic numbering and cross-references + \item Consistent, professional output + \item Free and open source +\end{itemize} + +\section{Mathematics} +\label{sec:math} + +\subsection{Inline and Display Math} + +Inline math uses dollar signs: $E = mc^2$ and $\sum_{i=1}^{n} i = \frac{n(n+1)}{2}$. + +Display math uses the \texttt{equation} environment: + +\begin{equation} + \label{eq:euler} + e^{i\pi} + 1 = 0 +\end{equation} + +Unnumbered display equations use \verb|\[...\]|: + +\[ + \int_{-\infty}^{\infty} e^{-x^2} \, dx = \sqrt{\pi} +\] + +\subsection{Aligned Equations} + +\begin{align} + f(x) &= x^2 + 2x + 1 \\ + &= (x + 1)^2 +\end{align} + +\subsection{Theorem Environments} + +\newtheorem{theorem}{Theorem} +\newtheorem{lemma}[theorem]{Lemma} + +\begin{theorem}[Pythagorean Theorem] + For a right triangle with legs $a$ and $b$ and hypotenuse $c$: + \[ + a^2 + b^2 = c^2 + \] +\end{theorem} + +\section{Environments} +\label{sec:environments} + +\subsection{Tables} + +\begin{table}[ht] + \centering + \caption{Comparison of typesetting systems} + \label{tab:comparison} + \begin{tabular}{lcc} + \toprule + System & Math support & Free \\ + \midrule + \LaTeX{} & Excellent & Yes \\ + Typst & Good & Yes \\ + Word & Limited & No \\ + \bottomrule + \end{tabular} +\end{table} + +\subsection{Figures} + +\begin{figure}[ht] + \centering + % \includegraphics[width=0.5\textwidth]{example-image} + \caption{An example figure.} + \label{fig:example} +\end{figure} + +\subsection{Verbatim} + +\begin{verbatim} +#include +int main() { + printf("Hello, World!\n"); + return 0; +} +\end{verbatim} + +\section{Cross-References and URLs} +\label{sec:refs} + +Refer to Equation~\ref{eq:euler}, Table~\ref{tab:comparison}, +and Figure~\ref{fig:example}. + +Visit \url{https://www.latex-project.org} or use +\href{https://ctan.org}{CTAN} for packages. + +% Define a custom command +\newcommand{\R}{\mathbb{R}} +\newcommand{\norm}[1]{\left\|#1\right\|} + +The set of real numbers is $\R$ and a norm is $\norm{x}$. + +% Bibliography +\bibliographystyle{plain} +\begin{thebibliography}{9} + \bibitem{lamport1994latex} + Leslie Lamport, + \textit{\LaTeX: A Document Preparation System}, + Addison-Wesley, 1994. + + \bibitem{knuth1984tex} + Donald E. Knuth, + \textit{The \TeX book}, + Addison-Wesley, 1984. +\end{thebibliography} + +\end{document} diff --git a/langs/group-willow/latex/def/arborium.yaml b/langs/group-willow/latex/def/arborium.yaml new file mode 100644 index 00000000..8d599271 --- /dev/null +++ b/langs/group-willow/latex/def/arborium.yaml @@ -0,0 +1,24 @@ +repo: https://github.com/latex-lsp/tree-sitter-latex +commit: "7e0ecdc02926c7b9b2e0c76003d4fe7b0944f957" +license: MIT + +grammars: + - id: latex + name: LaTeX + tag: markup + tier: 3 + has_scanner: true + icon: simple-icons:latex + aliases: + - tex + + inventor: Leslie Lamport + year: 1983 + description: "A document preparation system built on Donald Knuth's TeX typesetting engine, widely used in academia for scientific, mathematical, and technical documents." + link: https://www.latex-project.org + trivia: "LaTeX is used for typesetting over 90% of mathematics and physics research papers. Its macro system is so expressive that it is technically Turing-complete." + + samples: + - path: samples/sample.tex + description: A representative LaTeX document covering document structure, math, sectioning, environments, and formatting commands. + license: MIT diff --git a/langs/group-willow/latex/def/grammar/grammar.js b/langs/group-willow/latex/def/grammar/grammar.js new file mode 100644 index 00000000..c1cecef7 --- /dev/null +++ b/langs/group-willow/latex/def/grammar/grammar.js @@ -0,0 +1,1390 @@ +/// +// @ts-check + +const sepBy1 = (rule, sep) => seq(rule, repeat(seq(sep, rule))); + +const sepBy = (rule, sep) => optional(sepBy1(rule, sep)); + +const specialEnvironment = ({ rule, name, content, options }) => { + const beginRule = `_${rule}_begin`; + const endRule = `_${rule}_end`; + const groupRule = `_${rule}_group`; + const nameRule = `_${rule}_name`; + return { + [rule]: $ => + seq( + field('begin', alias($[beginRule], $.begin)), + content($), + field('end', alias($[endRule], $.end)), + ), + + [beginRule]: $ => + seq( + field('command', '\\begin'), + field('name', alias($[groupRule], $.curly_group_text)), + options ? options($) : seq(), + ), + + [endRule]: $ => + seq( + field('command', '\\end'), + field('name', alias($[groupRule], $.curly_group_text)), + ), + + [groupRule]: $ => seq('{', field('text', alias($[nameRule], $.text)), '}'), + + [nameRule]: $ => seq(field('word', alias(name, $.word))), + }; +}; + +module.exports = grammar({ + name: 'latex', + extras: $ => [$._whitespace, $.line_comment], + externals: $ => [ + $._trivia_raw_fi, + $._trivia_raw_env_comment, + $._trivia_raw_env_verbatim, + $._trivia_raw_env_listing, + $._trivia_raw_env_minted, + $._trivia_raw_env_asy, + $._trivia_raw_env_asydef, + $._trivia_raw_env_pycode, + $._trivia_raw_env_luacode, + $._trivia_raw_env_luacode_star, + $._trivia_raw_env_sagesilent, + $._trivia_raw_env_sageblock, + ], + word: $ => $.command_name, + rules: { + source_file: $ => repeat($._root_content), + + //--- Trivia + + _whitespace: $ => /\s+/, + + line_comment: $ => /%[^\r\n]*/, + + block_comment: $ => + seq( + field('begin', '\\iffalse'), + field('comment', optional(alias($._trivia_raw_fi, $.comment))), + field('end', optional('\\fi')), + ), + + //--- Content + + _root_content: $ => choice($._section, $._paragraph, $._flat_content), + + _flat_content: $ => prec.right(choice($._text_with_env_content, '[', ']')), + + _text_with_env_content: $ => + choice( + ',', + '=', + $.comment_environment, + $.verbatim_environment, + $.listing_environment, + $.minted_environment, + $.asy_environment, + $.asydef_environment, + $.pycode_environment, + $.luacode_environment, + $.sagesilent_environment, + $.sageblock_environment, + $.generic_environment, + $.math_environment, + $._text_content, + ), + + _text_content: $ => + prec.right( + 1, + choice( + $.curly_group, + $.block_comment, + $._command, + $.text, + $._math_content, + '(', + ')', + ), + ), + + //--- Sections + + _section: $ => + prec.right( + choice( + repeat1($.part), + repeat1($.chapter), + repeat1($.section), + repeat1($.subsection), + repeat1($.subsubsection), + ), + ), + + _paragraph: $ => + prec.right( + choice( + repeat1($.paragraph), + repeat1($.subparagraph), + repeat1($.enum_item), + ), + ), + + _section_part: $ => + seq(field('toc', optional($.brack_group)), field('text', $.curly_group)), + + _part_declaration: $ => + prec.right( + seq( + field( + 'command', + choice('\\part', '\\part*', '\\addpart', '\\addpart*'), + ), + optional($._section_part), + ), + ), + + part: $ => + prec.right( + -1, + seq( + $._part_declaration, + repeat($._flat_content), + optional(prec.right(-1, $._paragraph)), + optional( + prec.right( + choice( + repeat1($.chapter), + repeat1($.section), + repeat1($.subsection), + repeat1($.subsubsection), + ), + ), + ), + ), + ), + + _chapter_declaration: $ => + prec.right( + seq( + field( + 'command', + choice('\\chapter', '\\chapter*', '\\addchap', '\\addchap*'), + ), + optional($._section_part), + ), + ), + + chapter: $ => + prec.right( + -1, + seq( + $._chapter_declaration, + repeat($._flat_content), + optional(prec.right(-1, $._paragraph)), + optional( + prec.right( + choice( + repeat1($.section), + repeat1($.subsection), + repeat1($.subsubsection), + ), + ), + ), + ), + ), + + _section_declaration: $ => + prec.right( + seq( + field( + 'command', + choice('\\section', '\\section*', '\\addsec', '\\addsec*'), + ), + optional($._section_part), + ), + ), + + section: $ => + prec.right( + -1, + seq( + $._section_declaration, + repeat($._flat_content), + optional(prec.right(-1, $._paragraph)), + optional( + prec.right(choice(repeat1($.subsection), repeat1($.subsubsection))), + ), + ), + ), + + _subsection_declaration: $ => + prec.right( + seq( + field('command', choice('\\subsection', '\\subsection*')), + optional($._section_part), + ), + ), + + subsection: $ => + prec.right( + -1, + seq( + $._subsection_declaration, + repeat($._flat_content), + optional(prec.right(-1, $._paragraph)), + optional(prec.right(repeat1($.subsubsection))), + ), + ), + + _subsubsection_declaration: $ => + prec.right( + seq( + field('command', choice('\\subsubsection', '\\subsubsection*')), + optional($._section_part), + ), + ), + + subsubsection: $ => + prec.right( + -1, + seq( + $._subsubsection_declaration, + repeat($._flat_content), + optional(prec.right(-1, $._paragraph)), + ), + ), + + _paragraph_declaration: $ => + prec.right( + seq( + field('command', choice('\\paragraph', '\\paragraph*')), + optional($._section_part), + ), + ), + + paragraph: $ => + prec.right( + -1, + seq( + $._paragraph_declaration, + repeat($._flat_content), + optional( + prec.right(choice(repeat1($.subparagraph), repeat1($.enum_item))), + ), + ), + ), + + _subparagraph_declaration: $ => + prec.right( + seq( + field('command', choice('\\subparagraph', '\\subparagraph*')), + optional($._section_part), + ), + ), + + subparagraph: $ => + prec.right( + -1, + seq( + $._subparagraph_declaration, + repeat($._flat_content), + optional(prec.right(choice(repeat1($.enum_item)))), + ), + ), + + _enum_itemdeclaration: $ => + prec.right( + seq( + field('command', choice('\\item', '\\item*')), + field('label', optional($.brack_group_text)), + ), + ), + + enum_item: $ => + prec.right( + -1, + seq( + $._enum_itemdeclaration, + repeat($._flat_content), + ), + ), + + //--- Group + + curly_group: $ => seq('{', repeat($._root_content), '}'), + + curly_group_text: $ => seq('{', field('text', $.text), '}'), + + curly_group_word: $ => seq('{', field('word', $.word), '}'), + + curly_group_value: $ => seq('{', field('value', $.value_literal), '}'), + + curly_group_spec: $ => + seq('{', repeat(choice($._text_content, '=')), '}'), + + curly_group_text_list: $ => + seq('{', sepBy(field('text', $.text), ','), '}'), + + curly_group_label: $ => seq('{', field('label', $.label), '}'), + + curly_group_label_list: $ => + seq('{', sepBy(field('label', $.label), ','), '}'), + + curly_group_path: $ => seq('{', field('path', $.path), '}'), + + curly_group_path_list: $ => + seq('{', sepBy(field('path', $.path), ','), '}'), + + curly_group_uri: $ => seq('{', field('uri', $.uri), '}'), + + curly_group_command_name: $ => + seq('{', field('command', $.command_name), '}'), + + curly_group_key_value: $ => + seq('{', sepBy(field('pair', $.key_value_pair), ','), '}'), + + curly_group_glob_pattern: $ => + seq('{', field('pattern', $.glob_pattern), '}'), + + curly_group_impl: $ => + seq('{', repeat(choice($._text_content, '[', ']', ',', '=')), '}'), + + curly_group_author_list: $ => + seq( + '{', + sepBy( + alias(repeat1($._text_content), $.author), + alias('\\and', $.command_name), + ), + '}', + ), + + brack_group: $ => + seq('[', repeat(choice($._text_with_env_content, $.brack_group)), ']'), + + brack_group_text: $ => seq('[', field('text', $.text), ']'), + + brack_group_word: $ => seq('[', field('word', $.word), ']'), + + brack_group_argc: $ => seq('[', field('value', $.argc), ']'), + + brack_group_key_value: $ => + seq('[', sepBy(field('pair', $.key_value_pair), ','), ']'), + + //--- Text + + text: $ => + prec.right( + repeat1( + field( + 'word', + choice( + $.operator, + $.word, + $.placeholder, + $.delimiter, + $.block_comment, + $._command, + $.superscript, + $.subscript, + ), + ), + ), + ), + + word: $ => /[^\s\\%\{\},\$\[\]\(\)=\#&_\^\-\+\/\*]+/, + + placeholder: $ => /#+\d/, + + value_literal: $ => /(\d+\.)?\d+/, + + delimiter: $ => /&/, + + path: $ => /[^\*\"\[\]:;,\|\{\}<>]+/, + + uri: $ => /[^\[\]\{\}]+/, + + label: $ => /[^\\\[\]\{\}\$\(\)=&%\s_\^\#\~,]+/, + + argc: $ => /\d/, + + glob_pattern: $ => repeat1($._glob_pattern_fragment), + + _glob_pattern_fragment: $ => + choice( + seq('{', repeat($._glob_pattern_fragment), '}'), + /[^\"\[\]:;\|\{\}<>]+/, + ), + + operator: $ => choice('+', '-', '*', '/', '<', '>', '!', '|', ':', "'"), + + letter: $ => /[^\\%\{\}\$\#_\^]/, + + subscript: $ => + seq( + '_', + field('subscript', choice($.curly_group, $.letter, $.command_name)), + ), + + superscript: $ => + seq( + '^', + field('superscript', choice($.curly_group, $.letter, $.command_name)), + ), + + //--- Key / Value + + key_value_pair: $ => + seq(field('key', $.text), optional(seq('=', field('value', $.value)))), + + value: $ => repeat1(choice($._text_content, $.brack_group)), + + //--- Math + + _math_content: $ => + choice( + $.displayed_equation, + $.inline_formula, + $.math_delimiter, + $.text_mode, + ), + + displayed_equation: $ => + prec.left( + seq(choice('$$', '\\['), repeat($._root_content), choice('$$', '\\]')), + ), + + inline_formula: $ => + prec.left( + seq(choice('$', '\\('), repeat($._root_content), choice('$', '\\)')), + ), + + _math_delimiter_part: $ => + choice($.word, $.command_name, '[', ']', '(', ')', '|'), + + math_delimiter: $ => + prec.left( + seq( + field( + 'left_command', + choice('\\left', '\\bigl', '\\Bigl', '\\biggl', '\\Biggl'), + ), + field('left_delimiter', $._math_delimiter_part), + repeat($._root_content), + field( + 'right_command', + choice('\\right', '\\bigr', '\\Bigr', '\\biggr', '\\Biggr'), + ), + field('right_delimiter', $._math_delimiter_part), + ), + ), + + text_mode: $ => + seq( + field('command', choice('\\text', '\\intertext', '\\shortintertext')), + field('content', $.curly_group), + ), + + //--- Environments + + begin: $ => + prec.right( + seq( + field('command', '\\begin'), + field('name', $.curly_group_text), + field('options', optional($.brack_group)), + ), + ), + + end: $ => + prec.right( + seq(field('command', '\\end'), field('name', $.curly_group_text)), + ), + + generic_environment: $ => + seq( + field('begin', $.begin), + repeat($._root_content), + field('end', $.end), + ), + + //--- Trivia environments + + ...specialEnvironment({ + rule: 'comment_environment', + name: 'comment', + content: $ => + field('comment', alias($._trivia_raw_env_comment, $.comment)), + options: undefined, + }), + + ...specialEnvironment({ + rule: 'verbatim_environment', + name: 'verbatim', + content: $ => + field('verbatim', alias($._trivia_raw_env_verbatim, $.comment)), + options: undefined, + }), + + ...specialEnvironment({ + rule: 'listing_environment', + name: 'lstlisting', + content: $ => + field('code', alias($._trivia_raw_env_listing, $.source_code)), + options: undefined, + }), + + ...specialEnvironment({ + rule: 'minted_environment', + name: 'minted', + content: $ => + field('code', alias($._trivia_raw_env_minted, $.source_code)), + options: $ => + seq( + field('options', optional($.brack_group_key_value)), + field('language', $.curly_group_text), + ), + }), + + ...specialEnvironment({ + rule: 'asy_environment', + name: 'asy', + content: $ => field('code', alias($._trivia_raw_env_asy, $.source_code)), + options: undefined, + }), + + ...specialEnvironment({ + rule: 'asydef_environment', + name: 'asydef', + content: $ => + field('code', alias($._trivia_raw_env_asydef, $.source_code)), + options: undefined, + }), + + ...specialEnvironment({ + rule: 'pycode_environment', + name: 'pycode', + content: $ => + field('code', alias($._trivia_raw_env_pycode, $.source_code)), + options: undefined, + }), + + luacode_environment: $ => + choice($._luacode_environment, $._luacode_environment_star), + + ...specialEnvironment({ + rule: '_luacode_environment', + name: 'luacode', + content: $ => + field('code', alias($._trivia_raw_env_luacode, $.source_code)), + options: undefined, + }), + + ...specialEnvironment({ + rule: '_luacode_environment_star', + name: 'luacode*', + content: $ => + field('code', alias($._trivia_raw_env_luacode_star, $.source_code)), + options: undefined, + }), + + ...specialEnvironment({ + rule: 'sagesilent_environment', + name: 'sagesilent', + content: $ => + field('code', alias($._trivia_raw_env_sagesilent, $.source_code)), + options: undefined, + }), + + ...specialEnvironment({ + rule: 'sageblock_environment', + name: 'sageblock', + content: $ => + field('code', alias($._trivia_raw_env_sageblock, $.source_code)), + options: undefined, + }), + + ...specialEnvironment({ + rule: 'math_environment', + name: choice( + 'math', + 'displaymath', + 'displaymath*', + 'equation', + 'equation*', + 'multline', + 'multline*', + 'eqnarray', + 'eqnarray*', + 'align', + 'align*', + 'aligned', + 'aligned*', + 'array', + 'array*', + 'split', + 'split*', + 'alignat', + 'alignat*', + 'alignedat', + 'alignedat*', + 'gather', + 'gather*', + 'gathered', + 'gathered*', + 'flalign', + 'flalign*', + ), + content: $ => repeat($._flat_content), + options: undefined, + }), + + //--- Command + + _command: $ => + choice( + $.title_declaration, + $.author_declaration, + $.package_include, + $.class_include, + $.latex_include, + $.biblatex_include, + $.bibstyle_include, + $.bibtex_include, + $.graphics_include, + $.svg_include, + $.inkscape_include, + $.verbatim_include, + $.import_include, + $.caption, + $.citation, + $.counter_declaration, + $.counter_within_declaration, + $.counter_without_declaration, + $.counter_value, + $.counter_definition, + $.counter_addition, + $.counter_increment, + $.counter_typesetting, + $.label_definition, + $.label_reference, + $.label_reference_range, + $.label_number, + $.new_command_definition, + $.old_command_definition, + $.let_command_definition, + $.paired_delimiter_definition, + $.environment_definition, + $.glossary_entry_definition, + $.glossary_entry_reference, + $.acronym_definition, + $.acronym_reference, + $.theorem_definition, + $.color_definition, + $.color_set_definition, + $.color_reference, + $.tikz_library_import, + $.hyperlink, + $.changes_replaced, + $.todo, + $.generic_command, + ), + + todo: $ => + seq( + field('command', $.todo_command_name), + field('options', optional($.brack_group)), + field('arg', $.curly_group) + ), + + todo_command_name: $ => /\\([a-zA-Z]?[a-zA-Z]?todo)/, + + generic_command: $ => + prec.right( + seq( + field('command', $.command_name), + repeat(field('arg', $.curly_group)), + ), + ), + + command_name: $ => /\\([^\r\n]|[@a-zA-Z]+\*?)?/, + + counter_declaration: $ => + prec.right( + seq( + field('command', '\\newcounter'), + field('counter', $.curly_group_word), + optional(field('supercounter', $.brack_group_word)) + ) + ), + + counter_within_declaration: $ => + seq( + field('command', choice('\\counterwithin', '\\counterwithin*')), + field('counter', $.curly_group_word), + field('supercounter', $.curly_group_word), + ), + + counter_without_declaration: $ => + seq( + field('command', choice('\\counterwithout', '\\counterwithout*')), + field('counter', $.curly_group_word), + field('supercounter', $.curly_group_word), + ), + + counter_value: $ => + seq( + field('command', '\\value'), + field('counter', $.curly_group_word) + ), + + counter_definition: $ => + seq( + field('command', '\\setcounter'), + field('counter', $.curly_group_word), + // We can have a value or a call to \value{x} inside curly braces: + field('value', choice( + $.curly_group_value, + seq('{', field('value', $.counter_value), '}') + )), + ), + + counter_addition: $ => + seq( + field('command', '\\addtocounter'), + field('counter', $.curly_group_word), + // Same as $.counter_definition['value']: + field('value', choice( + $.curly_group_value, + seq('{', field('value', $.counter_value), '}') + )), + ), + + counter_increment: $ => + seq( + field('command', choice('\\stepcounter', '\\refstepcounter')), + field('counter', $.curly_group_word), + ), + + // NOTE: It is not the rule of the grammar tree to check if the counter + // is in [0,9] for \fnsymbol: it's up to the LSP (or the user) :) + counter_typesetting: $ => + seq( + field('command', choice( + '\\arabic', + '\\alph', + '\\Alph', + '\\roman', + '\\Roman', + '\\fnsymbol' + )), + field('counter', $.curly_group_word), + ), + + title_declaration: $ => + seq( + field('command', '\\title'), + field('options', optional($.brack_group)), + field('text', $.curly_group), + ), + + author_declaration: $ => + seq( + field('command', '\\author'), + field('options', optional($.brack_group)), + field('authors', $.curly_group_author_list), + ), + + package_include: $ => + seq( + field('command', choice('\\usepackage', '\\RequirePackage')), + field('options', optional($.brack_group_key_value)), + field('paths', $.curly_group_path_list), + ), + + class_include: $ => + seq( + field('command', '\\documentclass'), + field('options', optional($.brack_group_key_value)), + field('path', $.curly_group_path), + ), + + latex_include: $ => + seq( + field( + 'command', + choice('\\include', '\\subfileinclude', '\\input', '\\subfile'), + ), + field('path', $.curly_group_path), + ), + + biblatex_include: $ => + seq( + '\\addbibresource', + field('options', optional($.brack_group_key_value)), + field('glob', $.curly_group_glob_pattern), + ), + + bibstyle_include: $ => + seq( + field('command', '\\bibliographystyle'), + field('path', $.curly_group_path), + ), + + bibtex_include: $ => + seq( + field('command', '\\bibliography'), + field('paths', $.curly_group_path_list), + ), + + graphics_include: $ => + seq( + field('command', '\\includegraphics'), + field('options', optional($.brack_group_key_value)), + field('path', $.curly_group_path), + ), + + svg_include: $ => + seq( + field('command', '\\includesvg'), + field('options', optional($.brack_group_key_value)), + field('path', $.curly_group_path), + ), + + inkscape_include: $ => + seq( + field('command', '\\includeinkscape'), + field('options', optional($.brack_group_key_value)), + field('path', $.curly_group_path), + ), + + verbatim_include: $ => + seq( + field('command', choice('\\verbatiminput', '\\VerbatimInput')), + field('path', $.curly_group_path), + ), + + import_include: $ => + seq( + field( + 'command', + choice( + '\\import', + '\\subimport', + '\\inputfrom', + '\\subimportfrom', + '\\includefrom', + '\\subincludefrom', + ), + ), + field('directory', $.curly_group_path), + field('file', $.curly_group_path), + ), + + caption: $ => + seq( + field('command', '\\caption'), + field('short', optional($.brack_group)), + field('long', $.curly_group), + ), + + citation: $ => + seq( + field( + 'command', + choice( + '\\cite', + '\\cite*', + '\\Cite', + '\\nocite', + '\\citet', + '\\citep', + '\\citet*', + '\\citep*', + '\\citeA', + '\\citeR', + '\\citeS', + '\\citeyearR', + '\\citeauthor', + '\\citeauthor*', + '\\Citeauthor', + '\\Citeauthor*', + '\\citetitle', + '\\citetitle*', + '\\citeyear', + '\\citeyear*', + '\\citedate', + '\\citedate*', + '\\citeurl', + '\\fullcite', + '\\citeyearpar', + '\\citealt', + '\\citealp', + '\\citetext', + '\\parencite', + '\\parencite*', + '\\Parencite', + '\\footcite', + '\\footfullcite', + '\\footcitetext', + '\\textcite', + '\\Textcite', + '\\smartcite', + '\\Smartcite', + '\\supercite', + '\\autocite', + '\\Autocite', + '\\autocite*', + '\\Autocite*', + '\\volcite', + '\\Volcite', + '\\pvolcite', + '\\Pvolcite', + '\\fvolcite', + '\\ftvolcite', + '\\svolcite', + '\\Svolcite', + '\\tvolcite', + '\\Tvolcite', + '\\avolcite', + '\\Avolcite', + '\\notecite', + '\\Notecite', + '\\pnotecite', + '\\Pnotecite', + '\\fnotecite', + ), + ), + optional( + seq( + field('prenote', $.brack_group), + field('postnote', optional($.brack_group)), + ), + ), + field('keys', $.curly_group_text_list), + ), + + label_definition: $ => + seq(field('command', '\\label'), field('name', $.curly_group_label)), + + label_reference: $ => + seq( + field( + 'command', + choice( + '\\ref', + '\\eqref', + '\\vref', + '\\Vref', + '\\autoref', + '\\autoref*', + '\\pageref', + '\\pageref*', + '\\autopageref', + '\\autopageref*', + '\\cref', + '\\cref*', + '\\Cref', + '\\Cref*', + '\\cpageref', + '\\Cpageref', + '\\namecref', + '\\nameCref', + '\\lcnamecref', + '\\namecrefs', + '\\nameCrefs', + '\\lcnamecrefs', + '\\labelcref', + '\\labelcref*', + '\\labelcpageref', + '\\labelcpageref*', + ), + ), + field('names', $.curly_group_label_list), + ), + + label_reference_range: $ => + seq( + field( + 'command', + choice( + '\\crefrange', + '\\crefrange*', + '\\Crefrange', + '\\Crefrange*', + '\\cpagerefrange', + '\\Cpagerefrange', + ), + ), + field('from', $.curly_group_label), + field('to', $.curly_group_label), + ), + + label_number: $ => + seq( + field('command', '\\newlabel'), + field('name', $.curly_group_label), + field('number', $.curly_group), + ), + + new_command_definition: $ => + choice($._new_command_definition, $._newer_command_definition, $._new_command_copy), + + _new_command_definition: $ => + seq( + field( + 'command', + choice( + '\\newcommand', + '\\newcommand*', + '\\renewcommand', + '\\renewcommand*', + '\\providecommand', + '\\providecommand*', + '\\DeclareRobustCommand', + '\\DeclareRobustCommand*', + '\\DeclareMathOperator', + '\\DeclareMathOperator*', + ), + ), + field('declaration', choice($.curly_group_command_name, $.command_name)), + optional( + seq( + field('argc', $.brack_group_argc), + field('default', optional($.brack_group)), + ), + ), + field('implementation', $.curly_group), + ), + + _newer_command_definition: $ => + seq( + field( + 'command', + choice( + '\\NewDocumentCommand', + '\\RenewDocumentCommand', + '\\ProvideDocumentCommand', + '\\DeclareDocumentCommand', + '\\NewExpandableDocumentCommand', + '\\RenewExpandableDocumentCommand', + '\\ProvideExpandableDocumentCommand', + '\\DeclareExpandableDocumentCommand', + ), + ), + field('declaration', choice($.curly_group_command_name, $.command_name)), + field('spec', $.curly_group_spec), + field('implementation', $.curly_group), + ), + + _new_command_copy: $ => + seq( + field( + 'command', + choice( + '\\NewCommandCopy', + '\\RenewCommandCopy', + '\\DeclareCommandCopy', + ), + ), + field('declaration', choice($.curly_group_command_name, $.command_name)), + field('implementation', $.curly_group_command_name), + ), + + old_command_definition: $ => + seq( + field('command', choice('\\def', '\\gdef', '\\edef', '\\xdef')), + field('declaration', $.command_name) + ), + + let_command_definition: $ => + seq( + field('command', choice('\\let', '\\glet')), + field('declaration', $.command_name), + optional('='), + field('implementation', $.command_name), + ), + + paired_delimiter_definition: $ => + prec.right( + seq( + field( + 'command', + choice('\\DeclarePairedDelimiter', '\\DeclarePairedDelimiterX'), + ), + field('declaration', $.curly_group_command_name), + field('argc', optional($.brack_group_argc)), + field('left', choice($.curly_group_impl, $.command_name)), + field('right', choice($.curly_group_impl, $.command_name)), + field('body', optional($.curly_group)), + ), + ), + + environment_definition: $ => + choice($._environment_definition, $._newer_environment_definition, $._new_environment_copy), + + _environment_definition: $ => + seq( + field( + 'command', + choice( + '\\newenvironment', + '\\renewenvironment', + ), + ), + field('name', $.curly_group_text), + field('argc', optional($.brack_group_argc)), + field('begin', $.curly_group_impl), + field('end', $.curly_group_impl), + ), + + _newer_environment_definition: $ => + seq( + field( + 'command', + choice( + '\\NewDocumentEnvironment', + '\\RenewDocumentEnvironment', + '\\ProvideDocumentEnvironment', + '\\DeclareDocumentEnvironment', + ), + ), + field('name', $.curly_group_text), + field('spec', $.curly_group_spec), + field('begin', $.curly_group_impl), + field('end', $.curly_group_impl), + ), + + _new_environment_copy: $ => + seq( + field( + 'command', + choice( + '\\NewEnvironmentCopy', + '\\RenewEnvironmentCopy', + '\\DeclareEnvironmentCopy', + ), + ), + field('name', $.curly_group_text), + field('name', $.curly_group_text), + ), + + glossary_entry_definition: $ => + seq( + field('command', '\\newglossaryentry'), + field('name', $.curly_group_text), + field('options', $.curly_group_key_value), + ), + + glossary_entry_reference: $ => + seq( + field( + 'command', + choice( + '\\gls', + '\\Gls', + '\\GLS', + '\\glspl', + '\\Glspl', + '\\GLSpl', + '\\glsdisp', + '\\glslink', + '\\glstext', + '\\Glstext', + '\\GLStext', + '\\glsfirst', + '\\Glsfirst', + '\\GLSfirst', + '\\glsplural', + '\\Glsplural', + '\\GLSplural', + '\\glsfirstplural', + '\\Glsfirstplural', + '\\GLSfirstplural', + '\\glsname', + '\\Glsname', + '\\GLSname', + '\\glssymbol', + '\\Glssymbol', + '\\glsdesc', + '\\Glsdesc', + '\\GLSdesc', + '\\glsuseri', + '\\Glsuseri', + '\\GLSuseri', + '\\glsuserii', + '\\Glsuserii', + '\\GLSuserii', + '\\glsuseriii', + '\\Glsuseriii', + '\\GLSuseriii', + '\\glsuseriv', + '\\Glsuseriv', + '\\GLSuseriv', + '\\glsuserv', + '\\Glsuserv', + '\\GLSuserv', + '\\glsuservi', + '\\Glsuservi', + '\\GLSuservi', + ), + ), + field('options', optional($.brack_group_key_value)), + field('name', $.curly_group_text), + ), + + acronym_definition: $ => + seq( + field('command', '\\newacronym'), + field('options', optional($.brack_group_key_value)), + field('name', $.curly_group_text), + field('short', $.curly_group), + field('long', $.curly_group), + ), + + acronym_reference: $ => + seq( + field( + 'command', + choice( + '\\acrshort', + '\\Acrshort', + '\\ACRshort', + '\\acrshortpl', + '\\Acrshortpl', + '\\ACRshortpl', + '\\acrlong', + '\\Acrlong', + '\\ACRlong', + '\\acrlongpl', + '\\Acrlongpl', + '\\ACRlongpl', + '\\acrfull', + '\\Acrfull', + '\\ACRfull', + '\\acrfullpl', + '\\Acrfullpl', + '\\ACRfullpl', + '\\acs', + '\\Acs', + '\\acsp', + '\\Acsp', + '\\acl', + '\\Acl', + '\\aclp', + '\\Aclp', + '\\acf', + '\\Acf', + '\\acfp', + '\\Acfp', + '\\ac', + '\\Ac', + '\\acp', + '\\glsentrylong', + '\\Glsentrylong', + '\\glsentrylongpl', + '\\Glsentrylongpl', + '\\glsentryshort', + '\\Glsentryshort', + '\\glsentryshortpl', + '\\Glsentryshortpl', + '\\glsentryfullpl', + '\\Glsentryfullpl', + ), + ), + field('options', optional($.brack_group_key_value)), + field('name', $.curly_group_text), + ), + + theorem_definition: $ => + prec.right( + seq( + field( + 'command', + choice( + '\\newtheorem', + '\\newtheorem*', + '\\declaretheorem', + '\\declaretheorem*', + ), + ), + optional(field('options', $.brack_group_key_value)), + field('name', $.curly_group_text_list), + optional( + choice( + seq( + field('title', $.curly_group), + field('counter', optional($.brack_group_text)), + ), + seq( + field('counter', $.brack_group_text), + field('title', $.curly_group), + ), + ), + ), + ), + ), + + color_definition: $ => + seq( + field('command', '\\definecolor'), + optional($.brack_group_text), + field('name', $.curly_group_text), + field('model', $.curly_group_text), + field('spec', $.curly_group), + ), + + color_set_definition: $ => + seq( + field('command', '\\definecolorset'), + field('ty', optional($.brack_group_text)), + field('model', $.curly_group_text_list), + field('head', $.curly_group), + field('tail', $.curly_group), + field('spec', $.curly_group), + ), + + color_reference: $ => + prec.right( + seq( + field( + 'command', + choice('\\color', '\\pagecolor', '\\textcolor', '\\mathcolor', '\\colorbox'), + ), + choice( + field('name', $.curly_group_text), + seq( + field('model', $.brack_group_text), + field('spec', $.curly_group), + ), + ), + optional(field('text', $.curly_group)), + ), + ), + + tikz_library_import: $ => + seq( + field('command', choice('\\usepgflibrary', '\\usetikzlibrary')), + field('paths', $.curly_group_path_list), + ), + + hyperlink: $ => + prec.right( + seq( + field('command', choice('\\url', '\\href')), + field('uri', $.curly_group_uri), + field('label', optional($.curly_group)), + ), + ), + + changes_replaced: $ => + seq( + field('command', '\\replaced'), + field('text_added', $.curly_group), + field('text_deleted', $.curly_group) + ), + }, +}); diff --git a/langs/group-willow/latex/def/grammar/scanner.c b/langs/group-willow/latex/def/grammar/scanner.c new file mode 100644 index 00000000..b1f67c56 --- /dev/null +++ b/langs/group-willow/latex/def/grammar/scanner.c @@ -0,0 +1,141 @@ +#include +#include +#include +#include + +enum TokenType { + TRIVIA_RAW_FI, + TRIVIA_RAW_ENV_COMMENT, + TRIVIA_RAW_ENV_VERBATIM, + TRIVIA_RAW_ENV_LISTING, + TRIVIA_RAW_ENV_MINTED, + TRIVIA_RAW_ENV_ASY, + TRIVIA_RAW_ENV_ASYDEF, + TRIVIA_RAW_ENV_PYCODE, + TRIVIA_RAW_ENV_LUACODE, + TRIVIA_RAW_ENV_LUACODE_STAR, + TRIVIA_RAW_ENV_SAGESILENT, + TRIVIA_RAW_ENV_SAGEBLOCK, +}; + +static bool find_verbatim(TSLexer *lexer, const char *keyword, + bool is_command_name) { + bool has_marked = false; + while (true) { + if (lexer->eof(lexer)) { + break; + } + + bool advanced = false; + bool failed = false; + for (size_t i = 0; keyword[i]; i++) { + if (lexer->eof(lexer)) { + return has_marked; + } + + if (lexer->lookahead != keyword[i]) { + failed = true; + break; + } + + lexer->advance(lexer, false); + advanced = true; + } + + if (failed && !advanced) { + lexer->advance(lexer, false); + lexer->mark_end(lexer); + has_marked = true; + continue; + } + + if (!failed) { + if (is_command_name) { + if (lexer->eof(lexer)) { + return has_marked; + } + + char c = lexer->lookahead; + switch (c) { + case ':': + case '_': + case '@': + failed = true; + break; + default: + failed = (c >= 'a' && c <= 'z') || (c >= 'A' && c <= 'Z'); + break; + } + + if (failed) { + lexer->mark_end(lexer); + has_marked = true; + continue; + } + } + + return has_marked; + } + } + + return has_marked; +} + +void *tree_sitter_latex_external_scanner_create() { return NULL; } + +void tree_sitter_latex_external_scanner_destroy(void *payload) {} + +unsigned tree_sitter_latex_external_scanner_serialize(void *payload, + char *buffer) { + return 0; +} + +void tree_sitter_latex_external_scanner_deserialize(void *payload, + const char *buffer, + unsigned length) {} + +bool tree_sitter_latex_external_scanner_scan(void *payload, TSLexer *lexer, + const bool *valid_symbols) { + bool found = false; + TSSymbol type = 0xFFFF; + for (int i = 0; i <= TRIVIA_RAW_ENV_SAGEBLOCK; i++) { + if (valid_symbols[i]) { + if (found) { + return false; + } else { + found = true; + type = i; + } + } + } + + lexer->result_symbol = type; + switch (type) { + case TRIVIA_RAW_FI: + return find_verbatim(lexer, "\\fi", true); + case TRIVIA_RAW_ENV_COMMENT: + return find_verbatim(lexer, "\\end{comment}", false); + case TRIVIA_RAW_ENV_VERBATIM: + return find_verbatim(lexer, "\\end{verbatim}", false); + case TRIVIA_RAW_ENV_LISTING: + return find_verbatim(lexer, "\\end{lstlisting}", false); + case TRIVIA_RAW_ENV_MINTED: + return find_verbatim(lexer, "\\end{minted}", false); + case TRIVIA_RAW_ENV_PYCODE: + return find_verbatim(lexer, "\\end{pycode}", false); + case TRIVIA_RAW_ENV_ASY: + return find_verbatim(lexer, "\\end{asy}", false); + case TRIVIA_RAW_ENV_ASYDEF: + return find_verbatim(lexer, "\\end{asydef}", false); + case TRIVIA_RAW_ENV_LUACODE: + return find_verbatim(lexer, "\\end{luacode}", false); + case TRIVIA_RAW_ENV_LUACODE_STAR: + return find_verbatim(lexer, "\\end{luacode*}", false); + case TRIVIA_RAW_ENV_SAGESILENT: + return find_verbatim(lexer, "\\end{sagesilent}", false); + case TRIVIA_RAW_ENV_SAGEBLOCK: + return find_verbatim(lexer, "\\end{sageblock}", false); + } + + return false; +} diff --git a/langs/group-willow/latex/def/queries/highlights.scm b/langs/group-willow/latex/def/queries/highlights.scm new file mode 100644 index 00000000..9cbdd042 --- /dev/null +++ b/langs/group-willow/latex/def/queries/highlights.scm @@ -0,0 +1,250 @@ +;; Comments +[ + (comment) + (line_comment) + (block_comment) + (comment_environment) +] @comment + +;; Commands — the \commandname token +(command_name) @function + +;; Environments — \begin{name} and \end{name} +(begin + command: _ @function.builtin + name: (curly_group_text (text) @function.macro)) + +(end + command: _ @function.builtin + name: (curly_group_text (text) @function.macro)) + +;; Math +(displayed_equation) @markup.raw +(inline_formula) @markup.raw + +;; Math sub/superscript — $x_i^2$ +(subscript) @markup.raw +(superscript) @markup.raw + +(math_environment + (begin + command: _ @function.builtin + name: (curly_group_text (text) @markup.raw))) + +(math_environment + (text) @markup.raw) + +(math_environment + (end + command: _ @function.builtin + name: (curly_group_text (text) @markup.raw))) + +;; Sectioning commands — command gets @namespace, heading text gets @markup.heading +(title_declaration + command: _ @namespace + options: (brack_group (_) @markup.heading)? + text: (curly_group (_) @markup.heading)) + +(author_declaration + command: _ @namespace + authors: (curly_group_author_list + ((author)+ @markup.heading))) + +(chapter + command: _ @namespace + toc: (brack_group (_) @markup.heading)? + text: (curly_group (_) @markup.heading)) + +(part + command: _ @namespace + toc: (brack_group (_) @markup.heading)? + text: (curly_group (_) @markup.heading)) + +(section + command: _ @namespace + toc: (brack_group (_) @markup.heading)? + text: (curly_group (_) @markup.heading)) + +(subsection + command: _ @namespace + toc: (brack_group (_) @markup.heading)? + text: (curly_group (_) @markup.heading)) + +(subsubsection + command: _ @namespace + toc: (brack_group (_) @markup.heading)? + text: (curly_group (_) @markup.heading)) + +(paragraph + command: _ @namespace + toc: (brack_group (_) @markup.heading)? + text: (curly_group (_) @markup.heading)) + +(subparagraph + command: _ @namespace + toc: (brack_group (_) @markup.heading)? + text: (curly_group (_) @markup.heading)) + +;; Definitions and references +(new_command_definition + command: _ @function.macro + declaration: (curly_group_command_name (command_name) @function)) +(new_command_definition + command: _ @function.macro + declaration: (command_name) @function) +(old_command_definition + command: _ @function.macro + declaration: (_) @function) +(let_command_definition + command: _ @function.macro + declaration: (_) @function) + +(environment_definition + command: _ @function.macro + name: (curly_group_text (text) @constant)) + +(theorem_definition + command: _ @function.macro + name: (curly_group_text_list (text) @constant)) + +(paired_delimiter_definition + command: _ @function.macro + declaration: (curly_group_command_name (command_name) @function)) + +;; Labels and references +(label_definition + command: _ @function.macro + name: (curly_group_label (label) @label)) +(label_reference_range + command: _ @function.macro + from: (curly_group_label (label) @label) + to: (curly_group_label (label) @label)) +(label_reference + command: _ @function.macro + names: (curly_group_label_list (label) @label)) +(label_number + command: _ @function.macro + name: (curly_group_label (label) @label) + number: (curly_group) @markup.link) + +;; Citations +(citation + command: _ @function.macro + keys: (curly_group_text_list) @string) + +;; Glossary and acronyms +(glossary_entry_definition + command: _ @function.macro + name: (curly_group_text (text) @string)) +(glossary_entry_reference + command: _ @function.macro + name: (curly_group_text (text) @string)) +(acronym_definition + command: _ @function.macro + name: (curly_group_text (text) @string)) +(acronym_reference + command: _ @function.macro + name: (curly_group_text (text) @string)) + +;; Colors +(color_definition + command: _ @function.macro + name: (curly_group_text (text) @string)) +(color_reference + command: _ @function.macro + name: (curly_group_text (text) @string)) + +;; File inclusion +(class_include + command: _ @keyword.storage.type + path: (curly_group_path) @string) + +(package_include + command: _ @keyword.storage.type + paths: (curly_group_path_list) @string) + +(latex_include + command: _ @keyword.control.import + path: (curly_group_path) @string) +(import_include + command: _ @keyword.control.import + directory: (curly_group_path) @string + file: (curly_group_path) @string) + +(bibtex_include + command: _ @keyword.control.import + paths: (curly_group_path_list) @string) +(biblatex_include + "\\addbibresource" @keyword.control.import + glob: (curly_group_glob_pattern) @string) + +(graphics_include + command: _ @keyword.control.import + path: (curly_group_path) @string) +(tikz_library_import + command: _ @keyword.control.import + paths: (curly_group_path_list) @string) + +;; Hyperlinks (\url{...} and \href{...}{...}) +(hyperlink + command: _ @function.macro + uri: (curly_group_uri) @markup.link) + +;; Text formatting +((generic_command + command: (command_name) @_name + arg: (curly_group (_) @markup.italic)) + (#eq? @_name "\\emph")) + +((generic_command + command: (command_name) @_name + arg: (curly_group (_) @markup.italic)) + (#match? @_name "^(\\\\textit|\\\\mathit)$")) + +((generic_command + command: (command_name) @_name + arg: (curly_group (_) @markup.bold)) + (#match? @_name "^(\\\\textbf|\\\\mathbf)$")) + +((generic_command + command: (command_name) @_name + . + arg: (curly_group (_) @markup.link)) + (#match? @_name "^(\\\\url|\\\\href)$")) + +;; Key-value parameters +(key_value_pair + key: (_) @variable.parameter) + +[ + (brack_group) + (brack_group_argc) +] @variable.parameter + +;; Verbatim and code environments +(verbatim_environment) @markup.raw + +[ + (listing_environment) + (minted_environment) +] @markup.raw + +;; TODO notes (\todo{...}) — command in comment colour, message as string +(todo + command: (todo_command_name) @comment + arg: (curly_group (_) @string)) + +;; Operators and punctuation +[(operator) "="] @operator + +"\\item" @punctuation.special + +(delimiter) @punctuation.delimiter + +["[" "]" "{" "}"] @punctuation.bracket + +(math_delimiter + left_command: _ @punctuation.delimiter + left_delimiter: _ @punctuation.delimiter + right_command: _ @punctuation.delimiter + right_delimiter: _ @punctuation.delimiter) diff --git a/langs/group-willow/latex/def/queries/indents.scm b/langs/group-willow/latex/def/queries/indents.scm new file mode 100644 index 00000000..37523170 --- /dev/null +++ b/langs/group-willow/latex/def/queries/indents.scm @@ -0,0 +1,3 @@ +;; Indent on \begin{env}, outdent on \end{env} +(begin) @indent.begin +(end) @indent.end diff --git a/langs/group-willow/latex/def/samples/sample.tex b/langs/group-willow/latex/def/samples/sample.tex new file mode 100644 index 00000000..360891d4 --- /dev/null +++ b/langs/group-willow/latex/def/samples/sample.tex @@ -0,0 +1,173 @@ +\documentclass[12pt,a4paper]{article} + +% Packages +\usepackage[utf8]{inputenc} +\usepackage[T1]{fontenc} +\usepackage{amsmath} +\usepackage{amssymb} +\usepackage{hyperref} +\usepackage{graphicx} +\usepackage{booktabs} + +\title{A Sample \LaTeX{} Document} +\author{Jane Doe \and John Smith} +\date{\today} + +\begin{document} + +\maketitle + +% Table of contents +\tableofcontents +\newpage + +%% Introduction +\section{Introduction} +\label{sec:intro} + +This document demonstrates the syntax of \LaTeX{}, a document preparation system +based on Donald Knuth's \TeX{} typesetting engine. It is widely used in academia +for \textbf{scientific}, \textit{mathematical}, and \emph{technical} writing. + +See Section~\ref{sec:math} for examples of mathematical typesetting. + +\subsection{Background} +\label{sec:background} + +\LaTeX{} was developed by Leslie Lamport in 1983 as a higher-level interface to +\TeX{}. It provides macros for document structuring, cross-referencing, and +bibliography management~\cite{lamport1994latex}. + +\subsubsection{Why Use \LaTeX{}?} +\begin{itemize} + \item Superior mathematical typesetting + \item Automatic numbering and cross-references + \item Consistent, professional output + \item Free and open source +\end{itemize} + +\section{Mathematics} +\label{sec:math} + +\subsection{Inline and Display Math} + +Inline math uses dollar signs: $E = mc^2$ and $\sum_{i=1}^{n} i = \frac{n(n+1)}{2}$. + +Display math uses the \texttt{equation} environment: + +\begin{equation} + \label{eq:euler} + e^{i\pi} + 1 = 0 +\end{equation} + +Unnumbered display equations use \verb|\[...\]|: + +\[ + \int_{-\infty}^{\infty} e^{-x^2} \, dx = \sqrt{\pi} +\] + +\subsection{Aligned Equations} + +\begin{align} + f(x) &= x^2 + 2x + 1 \\ + &= (x + 1)^2 +\end{align} + +\subsection{Theorem Environments} + +\newtheorem{theorem}{Theorem} +\newtheorem{lemma}[theorem]{Lemma} + +\begin{theorem}[Pythagorean Theorem] + For a right triangle with legs $a$ and $b$ and hypotenuse $c$: + \[ + a^2 + b^2 = c^2 + \] +\end{theorem} + +\section{Environments} +\label{sec:environments} + +\subsection{Tables} + +\begin{table}[ht] + \centering + \caption{Comparison of typesetting systems} + \label{tab:comparison} + \begin{tabular}{lcc} + \toprule + System & Math support & Free \\ + \midrule + \LaTeX{} & Excellent & Yes \\ + Typst & Good & Yes \\ + Word & Limited & No \\ + \bottomrule + \end{tabular} +\end{table} + +\subsection{Figures} + +\begin{figure}[ht] + \centering + % \includegraphics[width=0.5\textwidth]{example-image} + \caption{An example figure.} + \label{fig:example} +\end{figure} + +\subsection{Verbatim} + +\begin{verbatim} +#include +int main() { + printf("Hello, World!\n"); + return 0; +} +\end{verbatim} + +\subsection{Code Listings} + +\begin{lstlisting}[language=Python] +def greet(name): + print(f"Hello, {name}!") +\end{lstlisting} + +\subsection{Math Sub and Superscripts} + +The quadratic formula: $x_{1,2} = \frac{-b \pm \sqrt{b^2 - 4ac}}{2a}$ + +Einstein's mass-energy: $E = mc^2$ + +\subsection{TODO Notes} + +\todo{Add more examples here.} + +\section{Cross-References and URLs} +\label{sec:refs} + +Refer to Equation~\ref{eq:euler}, Table~\ref{tab:comparison}, +and Figure~\ref{fig:example}. + +Visit \url{https://www.latex-project.org} or use +\href{https://ctan.org}{CTAN} for packages. + +% Define a custom command +\newcommand{\R}{\mathbb{R}} +\newcommand{\norm}[1]{\left\|#1\right\|} + +The set of real numbers is $\R$ and a norm is $\norm{x}$. + +% Bibliography +\bibliographystyle{plain} +\begin{thebibliography}{9} + \bibitem{lamport1994latex} + Leslie Lamport, + \textit{\LaTeX: A Document Preparation System}, + Addison-Wesley, 1994. + + \bibitem{knuth1984tex} + Donald E. Knuth, + \textit{The \TeX book}, + Addison-Wesley, 1984. +\end{thebibliography} + +\end{document}