Stackedit/src/services/markdownGrammarSvc.js
2018-04-06 16:20:02 +01:00

432 lines
11 KiB
JavaScript
Raw Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

const charInsideUrl = '(&|[-A-Z0-9+@#/%?=~_|[\\]()!:,.;])';
const charEndingUrl = '(&|[-A-Z0-9+@#/%=~_|[\\])])';
const urlPattern = new RegExp(`(https?|ftp)(://${charInsideUrl}*${charEndingUrl})(?=$|\\W)`, 'gi');
const emailPattern = /(?:mailto:)?([-.\w]+@[-a-z0-9]+(\.[-a-z0-9]+)*\.[a-z]+)/gi;
const markup = {
comment: /<!--[\w\W]*?-->/g,
tag: {
pattern: /<\/?[\w:-]+\s*(?:\s+[\w:-]+(?:=(?:("|')(\\?[\w\W])*?\1|[^\s'">=]+))?\s*)*\/?>/gi,
inside: {
tag: {
pattern: /^<\/?[\w:-]+/i,
inside: {
punctuation: /^<\/?/,
namespace: /^[\w-]+?:/,
},
},
'attr-value': {
pattern: /=(?:('|")[\w\W]*?(\1)|[^\s>]+)/gi,
inside: {
punctuation: /=|>|"/g,
},
},
punctuation: /\/?>/g,
'attr-name': {
pattern: /[\w:-]+/g,
inside: {
namespace: /^[\w-]+?:/,
},
},
},
},
entity: /&#?[\da-z]{1,8};/gi,
};
const latex = {
// A tex command e.g. \foo
keyword: /\\(?:[^a-zA-Z]|[a-zA-Z]+)/g,
// Curly and square braces
lparen: /[[({]/g,
// Curly and square braces
rparen: /[\])}]/g,
// A comment. Tex comments start with % and go to
// the end of the line
comment: /%.*/g,
};
export default {
makeGrammars(options) {
const grammars = {
main: {},
list: {},
blockquote: {},
table: {},
deflist: {},
};
grammars.deflist.deflist = {
pattern: new RegExp(
[
'^ {0,3}\\S.*\\n', // Description line
'(?:[ \\t]*\\n)?', // Optional empty line
'(?:',
'[ \\t]*:[ \\t].*\\n', // Colon line
'(?:',
'(?:',
'.*\\S.*\\n', // Non-empty line
'|',
'[ \\t]*\\n(?! ?\\S)', // Or empty line not followed by unindented line
')',
')*',
'(?:[ \\t]*\\n)*', // Empty lines
')+',
].join(''),
'm',
),
inside: {
term: /^.+/,
cl: /^[ \t]*:[ \t]/gm,
},
};
const insideFences = options.insideFences || {};
insideFences['cl cl-pre'] = /```|~~~/;
if (options.fence) {
grammars.main['pre gfm'] = {
pattern: /^(```|~~~)[\s\S]*?\n\1 *$/gm,
inside: insideFences,
};
grammars.list['pre gfm'] = {
pattern: /^(?: {4}|\t)(```|~~~)[\s\S]*?\n(?: {4}|\t)\1\s*$/gm,
inside: insideFences,
};
grammars.deflist.deflist.inside['pre gfm'] = grammars.list['pre gfm'];
}
grammars.main['h1 alt'] = {
pattern: /^.+\n=+[ \t]*$/gm,
inside: {
'cl cl-hash': /=+[ \t]*$/,
},
};
grammars.main['h2 alt'] = {
pattern: /^.+\n-+[ \t]*$/gm,
inside: {
'cl cl-hash': /-+[ \t]*$/,
},
};
for (let i = 6; i >= 1; i -= 1) {
grammars.main[`h${i}`] = {
pattern: new RegExp(`^#{${i}}[ \t].+$`, 'gm'),
inside: {
'cl cl-hash': new RegExp(`^#{${i}}`),
},
};
}
const list = /^[ \t]*([*+-]|\d+\.)[ \t]/gm;
const blockquote = {
pattern: /^\s*>.*(?:\n[ \t]*\S.*)*/gm,
inside: {
'cl cl-gt': /^\s*>/gm,
'cl cl-li': list,
},
};
grammars.list.blockquote = blockquote;
grammars.blockquote.blockquote = blockquote;
grammars.deflist.deflist.inside.blockquote = blockquote;
grammars.list['cl cl-li'] = list;
grammars.blockquote['cl cl-li'] = list;
grammars.deflist.deflist.inside['cl cl-li'] = list;
grammars.table.table = {
pattern: new RegExp(
[
'^\\s*\\S.*[|].*\\n', // Header Row
'[-| :]+\\n', // Separator
'(?:.*[|].*\\n?)*', // Table rows
'$',
].join(''),
'gm',
),
inside: {
'cl cl-title-separator': /^[-| :]+$/gm,
'cl cl-pipe': /[|]/gm,
},
};
grammars.main.hr = {
pattern: /^ {0,3}([*\-_] *){3,}$/gm,
};
if (options.tasklist) {
grammars.list.task = {
pattern: /^\[[ xX]\] /,
inside: {
cl: /[[\]]/,
strong: /[xX]/,
},
};
}
const defs = {};
if (options.footnote) {
defs.fndef = {
pattern: /^ {0,3}\[\^.*?\]:.*$/gm,
inside: {
'ref-id': {
pattern: /^ {0,3}\[\^.*?\]/,
inside: {
cl: /(\[\^|\])/,
},
},
},
};
}
if (options.abbr) {
defs.abbrdef = {
pattern: /^ {0,3}\*\[.*?\]:.*$/gm,
inside: {
'abbr-id': {
pattern: /^ {0,3}\*\[.*?\]/,
inside: {
cl: /(\*\[|\])/,
},
},
},
};
}
defs.linkdef = {
pattern: /^ {0,3}\[.*?\]:.*$/gm,
inside: {
'link-id': {
pattern: /^ {0,3}\[.*?\]/,
inside: {
cl: /[[\]]/,
},
},
url: urlPattern,
},
};
Object.entries(defs).forEach(([name, def]) => {
grammars.main[name] = def;
grammars.list[name] = def;
grammars.blockquote[name] = def;
grammars.table[name] = def;
grammars.deflist[name] = def;
});
grammars.main.pre = {
pattern: /^\s*\n(?: {4}|\t).*\S.*\n((?: {4}|\t).*\n)*/gm,
};
const rest = {};
rest.code = {
pattern: /(`+)[\s\S]*?\1/g,
inside: {
'cl cl-code': /`/,
},
};
if (options.math) {
rest['math block'] = {
pattern: /\\\\\[[\s\S]*?\\\\\]/g,
inside: {
'cl cl-bracket-start': /^\\\\\[/,
'cl cl-bracket-end': /\\\\\]$/,
rest: latex,
},
};
rest['math inline'] = {
pattern: /\\\\\([\s\S]*?\\\\\)/g,
inside: {
'cl cl-bracket-start': /^\\\\\(/,
'cl cl-bracket-end': /\\\\\)$/,
rest: latex,
},
};
rest['math expr block'] = {
pattern: /(\$\$)[\s\S]*?\1/g,
inside: {
'cl cl-bracket-start': /^\$\$/,
'cl cl-bracket-end': /\$\$$/,
rest: latex,
},
};
rest['math expr inline'] = {
pattern: /\$(?!\s)[\s\S]*?\S\$(?!\d)/g,
inside: {
'cl cl-bracket-start': /^\$/,
'cl cl-bracket-end': /\$$/,
rest: latex,
},
};
}
if (options.footnote) {
rest.inlinefn = {
pattern: /\^\[.+?\]/g,
inside: {
cl: /(\^\[|\])/,
},
};
rest.fn = {
pattern: /\[\^.+?\]/g,
inside: {
cl: /(\[\^|\])/,
},
};
}
rest.img = {
pattern: /!\[.*?\]\(.+?\)/g,
inside: {
'cl cl-title': /['][^']*[']|["“][^"”]*["”](?=\)$)/,
'cl cl-src': {
pattern: /(\]\()[^('" \t]+(?=[)'" \t])/,
lookbehind: true,
},
},
};
if (options.imgsize) {
rest.img.inside['cl cl-size'] = /=\d*x\d*/;
}
rest.link = {
pattern: /\[.*?\]\(.+?\)/gm,
inside: {
'cl cl-underlined-text': {
pattern: /(\[)[^\]]*/,
lookbehind: true,
},
'cl cl-title': /['][^']*[']|["“][^"”]*["”](?=\)$)/,
},
};
rest.imgref = {
pattern: /!\[.*?\][ \t]*\[.*?\]/g,
};
rest.linkref = {
pattern: /\[.*?\][ \t]*\[.*?\]/g,
inside: {
'cl cl-underlined-text': {
pattern: /^(\[)[^\]]*(?=\][ \t]*\[)/,
lookbehind: true,
},
},
};
rest.comment = markup.comment;
rest.tag = markup.tag;
rest.url = urlPattern;
rest.email = emailPattern;
rest.strong = {
pattern: /(^|[^\w*])(__|\*\*)(?![_*])[\s\S]*?\2(?=([^\w*]|$))/gm,
lookbehind: true,
inside: {
'cl cl-strong cl-start': /^(__|\*\*)/,
'cl cl-strong cl-close': /(__|\*\*)$/,
},
};
rest.em = {
pattern: /(^|[^\w*])(_|\*)(?![_*])[\s\S]*?\2(?=([^\w*]|$))/gm,
lookbehind: true,
inside: {
'cl cl-em cl-start': /^(_|\*)/,
'cl cl-em cl-close': /(_|\*)$/,
},
};
rest['strong em'] = {
pattern: /(^|[^\w*])(__|\*\*)(_|\*)(?![_*])[\s\S]*?\3\2(?=([^\w*]|$))/gm,
lookbehind: true,
inside: {
'cl cl-strong cl-start': /^(__|\*\*)(_|\*)/,
'cl cl-strong cl-close': /(_|\*)(__|\*\*)$/,
},
};
rest['strong em inv'] = {
pattern: /(^|[^\w*])(_|\*)(__|\*\*)(?![_*])[\s\S]*?\3\2(?=([^\w*]|$))/gm,
lookbehind: true,
inside: {
'cl cl-strong cl-start': /^(_|\*)(__|\*\*)/,
'cl cl-strong cl-close': /(__|\*\*)(_|\*)$/,
},
};
if (options.del) {
rest.del = {
pattern: /(^|[^\w*])(~~)[\s\S]*?\2(?=([^\w*]|$))/gm,
lookbehind: true,
inside: {
cl: /~~/,
'cl-del-text': /[^~]+/,
},
};
}
if (options.mark) {
rest.mark = {
pattern: /(^|[^\w*])(==)[\s\S]*?\2(?=([^\w*]|$))/gm,
lookbehind: true,
inside: {
cl: /==/,
'cl-mark-text': /[^=]+/,
},
};
}
if (options.sub) {
rest.sub = {
pattern: /(~)(?=\S)(.*?\S)\1/gm,
inside: {
cl: /~/,
},
};
}
if (options.sup) {
rest.sup = {
pattern: /(\^)(?=\S)(.*?\S)\1/gm,
inside: {
cl: /\^/,
},
};
}
rest.entity = markup.entity;
for (let c = 6; c >= 1; c -= 1) {
grammars.main[`h${c}`].inside.rest = rest;
}
grammars.main['h1 alt'].inside.rest = rest;
grammars.main['h2 alt'].inside.rest = rest;
grammars.table.table.inside.rest = rest;
grammars.main.rest = rest;
grammars.list.rest = rest;
grammars.blockquote.blockquote.inside.rest = rest;
grammars.deflist.deflist.inside.rest = rest;
if (options.footnote) {
grammars.main.fndef.inside.rest = rest;
}
const restLight = {
code: rest.code,
inlinefn: rest.inlinefn,
fn: rest.fn,
link: rest.link,
linkref: rest.linkref,
};
rest.strong.inside.rest = restLight;
rest.em.inside.rest = restLight;
if (options.del) {
rest.del.inside.rest = restLight;
}
if (options.mark) {
rest.mark.inside.rest = restLight;
}
const inside = {
code: rest.code,
comment: rest.comment,
tag: rest.tag,
strong: rest.strong,
em: rest.em,
del: rest.del,
sub: rest.sub,
sup: rest.sup,
entity: markup.entity,
};
rest.link.inside['cl cl-underlined-text'].inside = inside;
rest.linkref.inside['cl cl-underlined-text'].inside = inside;
// Wrap any other characters to allow paragraph folding
Object.entries(grammars).forEach(([, grammar]) => {
grammar.rest = grammar.rest || {};
grammar.rest.p = /.+/;
});
return grammars;
},
};