-- Copyright 2006-2019 Mitchell mitchell.att.foicica.com. See License.txt. -- Markdown LPeg lexer. local lexer = require('lexer') local token, word_match = lexer.token, lexer.word_match local P, R, S = lpeg.P, lpeg.R, lpeg.S local lex = lexer.new('markdown') -- Block elements. lex:add_rule('header', token('h6', lexer.starts_line('######') * lexer.nonnewline^0) + token('h5', lexer.starts_line('#####') * lexer.nonnewline^0) + token('h4', lexer.starts_line('####') * lexer.nonnewline^0) + token('h3', lexer.starts_line('###') * lexer.nonnewline^0) + token('h2', lexer.starts_line('##') * lexer.nonnewline^0) + token('h1', lexer.starts_line('#') * lexer.nonnewline^0)) local font_size = lexer.property_int['fontsize'] > 0 and lexer.property_int['fontsize'] or 10 local hstyle = 'fore:$(color.red)' lex:add_style('h6', hstyle) lex:add_style('h5', hstyle..',size:'..(font_size + 1)) lex:add_style('h4', hstyle..',size:'..(font_size + 2)) lex:add_style('h3', hstyle..',size:'..(font_size + 3)) lex:add_style('h2', hstyle..',size:'..(font_size + 4)) lex:add_style('h1', hstyle..',size:'..(font_size + 5)) lex:add_rule('blockquote', token(lexer.STRING, lpeg.Cmt(lexer.starts_line(S(' \t')^0 * '>'), function(input, index) local _, e = input:find('\n[ \t]*\r?\n', index) return (e or #input) + 1 end))) lex:add_rule('list', token('list', lexer.starts_line(S(' \t')^0 * (S('*+-') + R('09')^1 * '.')) * S(' \t'))) lex:add_style('list', lexer.STYLE_CONSTANT) lex:add_rule('block_code', token('code', lexer.starts_line(P(' ')^4 + P('\t')) * -P('<') * lexer.nonnewline^0 * lexer.newline^-1) + token('code', lexer.starts_line(P('```')) * (lexer.any - '```')^0 * P('```')^-1)) lex:add_rule('inline_code', token('code', P('``') * (lexer.any - '``')^0 * P('``')^-1 + lexer.delimited_range('`', false, true))) lex:add_style('code', lexer.STYLE_EMBEDDED..',eolfilled') lex:add_rule('hr', token('hr', lpeg.Cmt(lexer.starts_line(S(' \t')^0 * lpeg.C(S('*-_'))), function(input, index, c) local line = input:match('[^\r\n]*', index) line = line:gsub('[ \t]', '') if line:find('[^'..c..']') or #line < 2 then return nil end return (select(2, input:find('\r?\n', index)) or #input) + 1 end))) lex:add_style('hr', 'back:$(color.black),eolfilled') -- Whitespace. local ws = token(lexer.WHITESPACE, S(' \t')^1 + S('\v\r\n')^1) lex:add_rule('whitespace', ws) -- Span elements. lex:add_rule('escape', token(lexer.DEFAULT, P('\\') * 1)) lex:add_rule('link_label', token('link_label', lexer.delimited_range('[]') * ':') * ws * token('link_url', (lexer.any - lexer.space)^1) * (ws * token(lexer.STRING, lexer.delimited_range('"', false, true) + lexer.delimited_range("'", false, true) + lexer.delimited_range('()')))^-1) lex:add_style('link_label', lexer.STYLE_LABEL) lex:add_style('link_url', 'underlined') lex:add_rule('link', token('link', P('!')^-1 * lexer.delimited_range('[]') * (P('(') * (lexer.any - S(') \t'))^0 * (S(' \t')^1 * lexer.delimited_range('"', false, true))^-1 * ')' + S(' \t')^0 * lexer.delimited_range('[]')) + 'http' * P('s')^-1 * '://' * (lexer.any - lexer.space)^1)) lex:add_style('link', 'underlined') local punct_space = lexer.punct + lexer.space -- Handles flanking delimiters as described in -- https://github.github.com/gfm/#emphasis-and-strong-emphasis in the cases -- where simple delimited ranges are not sufficient. local function flanked_range(s, not_inword) local fl_char = lexer.any - s - lexer.space local left_fl = lpeg.B(punct_space - s) * s * #fl_char + s * #(fl_char - lexer.punct) local right_fl = lpeg.B(lexer.punct) * s * #(punct_space - s) + lpeg.B(fl_char) * s return left_fl * (lexer.any - (not_inword and s * #punct_space or s))^0 * right_fl end lex:add_rule('strong', token('strong', flanked_range('**') + (lpeg.B(punct_space) + #lexer.starts_line('_')) * flanked_range('__', true) * #(punct_space + -1))) lex:add_style('strong', 'bold') lex:add_rule('em', token('em', flanked_range('*') + (lpeg.B(punct_space) + #lexer.starts_line('_')) * flanked_range('_', true) * #(punct_space + -1))) lex:add_style('em', 'italics') -- Embedded HTML. local html = lexer.load('html') local start_rule = lexer.starts_line(S(' \t')^0) * #P('<') * html:get_rule('element') local end_rule = token(lexer.DEFAULT, P('\n')) -- TODO: lexer.WHITESPACE errors lex:embed(html, start_rule, end_rule) return lex