const _uppercase_word = /[A-Z0-9.()][-A-Z0-9.()_]+/;
const _li_token = /([-•]|([0-9]{1,3}\.))[ ]+/;
const _keycode = choice(
/<[-a-zA-Z0-9_]+>/,
/<[SCMAD]-.>/,
/CTRL-./,
/CTRL-SHIFT-./,
/CTRL-(Break|PageUp|PageDown|Insert|Del)/,
'CTRL-{char}',
/META-./,
/ALT-./,
);
const _tag_text = prec(1, /[^*\n\t ]+/);
const _codespan_text = prec(1, /[^`\n\t]+/); const _argument_text = /[^}\n\t ]+/;
const _optionlink_text = /[a-z][a-z]+/;
module.exports = grammar({
name: 'vimdoc',
conflicts: $ => [
[$._line_noli, $._line_cb, $._column_heading],
[$._line_any, $._line_noli],
[$._column_heading],
],
extras: () => [/[\t ]/],
rules: {
help_file: ($) =>
seq(
repeat($._blank), repeat($.block),
repeat($.modeline),
),
_uppercase_char: () => token(prec(-1, /[A-Z0-9]/)),
_atom: ($) => choice(
$.word,
$._atom_common,
),
word: ($) => choice(
token(prec(-1, /[^.,(\[\n\t ]+/)),
$._word_common,
),
_atom_noli: ($) => choice(
alias($.word_noli, $.word),
alias(token.immediate(_keycode), $.keycode),
$._atom_common,
),
word_noli: ($) => choice(
token(prec(-1, /(([^-•\n\t ])|([^0-9\n\t ][^.\n\t ]))[^.(\[\n\t ]*/)),
token(prec(-1, /[-•][^\n\t ]+/)),
$._word_common,
),
_atom_common: ($) =>
choice(
$.tag,
$.url,
$.optionlink,
$.taglink,
$.codespan,
$.argument,
$.keycode,
$.note,
),
_not_tag: () => seq('*', optional(seq(
alias(token.immediate(_tag_text), 'word'),
optional(token.immediate(/\*[^\n\t ]+/)),
))),
_word_common: ($) => choice(
$._not_tag,
seq('`', optional(alias(token.immediate(_codespan_text), 'word'))),
seq("'", optional(alias(token.immediate(_optionlink_text), 'word'))),
seq("'", token.immediate(/[^'\n\t ]/)),
seq("'", token.immediate(/[a-z]*[^'a-z\n\t ][a-z]*/), optional(token.immediate("'"))),
seq("'", token.immediate(/[^'\n\t ]/), token.immediate("'")),
/\|\|+/,
'|', "'|'",
seq('{', optional(alias(token.immediate(_argument_text), 'word'))),
'{}',
/\{\{+[0-9]*/,
'(',
')',
'[',
']',
'~',
'>',
',',
'.',
),
note: () => choice(
'Note:', 'NOTE:', 'Notes:',
'Warning:', 'WARNING:',
'Deprecated', 'DEPRECATED:'
),
keycode: () => _keycode,
uppercase_name: ($) => seq(
token.immediate(_uppercase_word), repeat(choice(_uppercase_word, $._uppercase_char)),
),
_uppercase_words: ($) => prec.left(-1, seq(
alias(token.immediate(_uppercase_word), $.word),
alias(repeat(choice(_uppercase_word, $._uppercase_char)), $.word),
)),
block: ($) => {
const lines = choice(
repeat1($._line_any),
repeat1($.line_li),
seq(repeat1($._line_any), repeat1($.line_li)),
);
return seq(
choice(seq(alias($._line_h, $.line), optional(lines)), lines),
choice(
token.immediate('<'), $._blank),
repeat($._blank),
);
},
_line_any: ($) => choice(
$.line,
seq(alias($._line_cb, $.line), alias($._line_h, $.line)),
),
_line_h: ($) => choice(
$.h1,
$.h2,
prec.dynamic(2, $.h3),
),
codeblock: ($) => prec.right(seq(
'>',
choice(
alias(token.immediate(/[a-z0-9]+\n/), $.language),
token.immediate(/\n/)),
alias(repeat1(alias($.line_code, $.line)), $.code),
)),
_blank: () => field('blank', /\n/),
line: ($) => choice(
$.column_heading,
$._line_noli,
),
line_li: ($) => prec.right(1, seq(
optional(token.immediate('<')), alias(_li_token, $.prefix),
choice(
alias(seq(repeat1($._atom), /\n/), $.line),
seq(alias(repeat1($._atom), $.line), $.codeblock),
),
repeat(choice(
alias($._line_noli, $.line),
token.immediate(/<([\t ]*\n)?[\t ]+/),
)),
)),
line_code: () => choice(/\n/, /[\t ]+[^\n]+\n/),
_line_noli: ($) => choice(
seq(choice($._atom_noli, $._uppercase_words), repeat($._atom), /\n/),
$._line_cb,
),
_line_cb: ($) => seq(
optional(seq(choice($._atom_noli, $._uppercase_words), repeat($._atom))),
$.codeblock,
),
modeline: ($) => token(prec(2, /vim:[^\n]+\n/)),
column_heading: ($) => seq(
alias($._column_heading, $.heading),
alias('~', $.delimiter),
token.immediate(/\n/),
),
_column_heading: $ => prec.dynamic(1, seq(
choice($._atom_noli, $._uppercase_words),
repeat($._atom)
)),
h1: ($) =>
prec(1, seq(
alias(token.immediate(/============+[\t ]*\n/), $.delimiter),
alias(repeat1($._atom), $.heading),
optional(seq($.tag, repeat($._atom))),
/\n/,
)),
h2: ($) =>
prec(1, seq(
alias(token.immediate(/------------+[\t ]*\n/), $.delimiter),
alias(repeat1($._atom), $.heading),
optional(seq($.tag, repeat($._atom))),
/\n/,
)),
h3: ($) =>
seq(
alias($.uppercase_name, $.heading),
optional(seq(choice($.tag, alias($._not_tag, $.word)), repeat($._atom))),
/\n/,
),
tag: ($) => _word($, _tag_text, '*', '*'),
url_word: () => /https?:\/\/[^\n\t\] ]*[^\n\t )\].,:]/,
url: ($) => choice(
field('text', alias($.url_word, $.word))
),
optionlink: ($) => _word($, _optionlink_text, "'", "'"),
taglink: ($) => _word($, prec(1, /[^|\n\t ]+/), '|', '|'),
codespan: ($) => _word($, _codespan_text, '`', '`'),
argument: ($) => seq(_word($, _argument_text, '{', '}'), optional(token.immediate('?'))),
},
});
function _word($, rule, c1, c2, fname) {
fname = fname ?? 'text';
return seq(c1, field(fname, alias(token.immediate(rule), $.word)), token.immediate(c2));
}