primo commit
This commit is contained in:
		
							
								
								
									
										444
									
								
								media/vendor/codemirror/js/codemirror-lang-markdown.js
									
									
									
									
										vendored
									
									
										Normal file
									
								
							
							
						
						
									
										444
									
								
								media/vendor/codemirror/js/codemirror-lang-markdown.js
									
									
									
									
										vendored
									
									
										Normal file
									
								
							| @ -0,0 +1,444 @@ | ||||
| import { EditorSelection, countColumn, Prec, EditorState } from '@codemirror/state'; | ||||
| import { keymap } from '@codemirror/view'; | ||||
| import { syntaxTree, LanguageSupport, Language, indentUnit, defineLanguageFacet, foldNodeProp, indentNodeProp, languageDataProp, foldService, LanguageDescription, ParseContext } from '@codemirror/language'; | ||||
| import { CompletionContext } from '@codemirror/autocomplete'; | ||||
| import { MarkdownParser, parseCode, parser, GFM, Subscript, Superscript, Emoji } from '@lezer/markdown'; | ||||
| import { htmlCompletionSource, html } from '@codemirror/lang-html'; | ||||
| import { NodeProp } from '@lezer/common'; | ||||
|  | ||||
| const data = /*@__PURE__*/defineLanguageFacet({ commentTokens: { block: { open: "<!--", close: "-->" } } }); | ||||
| const headingProp = /*@__PURE__*/new NodeProp(); | ||||
| const commonmark = /*@__PURE__*/parser.configure({ | ||||
|     props: [ | ||||
|         /*@__PURE__*/foldNodeProp.add(type => { | ||||
|             return !type.is("Block") || type.is("Document") || isHeading(type) != null || isList(type) ? undefined | ||||
|                 : (tree, state) => ({ from: state.doc.lineAt(tree.from).to, to: tree.to }); | ||||
|         }), | ||||
|         /*@__PURE__*/headingProp.add(isHeading), | ||||
|         /*@__PURE__*/indentNodeProp.add({ | ||||
|             Document: () => null | ||||
|         }), | ||||
|         /*@__PURE__*/languageDataProp.add({ | ||||
|             Document: data | ||||
|         }) | ||||
|     ] | ||||
| }); | ||||
| function isHeading(type) { | ||||
|     let match = /^(?:ATX|Setext)Heading(\d)$/.exec(type.name); | ||||
|     return match ? +match[1] : undefined; | ||||
| } | ||||
| function isList(type) { | ||||
|     return type.name == "OrderedList" || type.name == "BulletList"; | ||||
| } | ||||
| function findSectionEnd(headerNode, level) { | ||||
|     let last = headerNode; | ||||
|     for (;;) { | ||||
|         let next = last.nextSibling, heading; | ||||
|         if (!next || (heading = isHeading(next.type)) != null && heading <= level) | ||||
|             break; | ||||
|         last = next; | ||||
|     } | ||||
|     return last.to; | ||||
| } | ||||
| const headerIndent = /*@__PURE__*/foldService.of((state, start, end) => { | ||||
|     for (let node = syntaxTree(state).resolveInner(end, -1); node; node = node.parent) { | ||||
|         if (node.from < start) | ||||
|             break; | ||||
|         let heading = node.type.prop(headingProp); | ||||
|         if (heading == null) | ||||
|             continue; | ||||
|         let upto = findSectionEnd(node, heading); | ||||
|         if (upto > end) | ||||
|             return { from: end, to: upto }; | ||||
|     } | ||||
|     return null; | ||||
| }); | ||||
| function mkLang(parser) { | ||||
|     return new Language(data, parser, [headerIndent], "markdown"); | ||||
| } | ||||
| /** | ||||
| Language support for strict CommonMark. | ||||
| */ | ||||
| const commonmarkLanguage = /*@__PURE__*/mkLang(commonmark); | ||||
| const extended = /*@__PURE__*/commonmark.configure([GFM, Subscript, Superscript, Emoji, { | ||||
|         props: [ | ||||
|             /*@__PURE__*/foldNodeProp.add({ | ||||
|                 Table: (tree, state) => ({ from: state.doc.lineAt(tree.from).to, to: tree.to }) | ||||
|             }) | ||||
|         ] | ||||
|     }]); | ||||
| /** | ||||
| Language support for [GFM](https://github.github.com/gfm/) plus | ||||
| subscript, superscript, and emoji syntax. | ||||
| */ | ||||
| const markdownLanguage = /*@__PURE__*/mkLang(extended); | ||||
| function getCodeParser(languages, defaultLanguage) { | ||||
|     return (info) => { | ||||
|         if (info && languages) { | ||||
|             let found = null; | ||||
|             // Strip anything after whitespace | ||||
|             info = /\S*/.exec(info)[0]; | ||||
|             if (typeof languages == "function") | ||||
|                 found = languages(info); | ||||
|             else | ||||
|                 found = LanguageDescription.matchLanguageName(languages, info, true); | ||||
|             if (found instanceof LanguageDescription) | ||||
|                 return found.support ? found.support.language.parser : ParseContext.getSkippingParser(found.load()); | ||||
|             else if (found) | ||||
|                 return found.parser; | ||||
|         } | ||||
|         return defaultLanguage ? defaultLanguage.parser : null; | ||||
|     }; | ||||
| } | ||||
|  | ||||
| class Context { | ||||
|     constructor(node, from, to, spaceBefore, spaceAfter, type, item) { | ||||
|         this.node = node; | ||||
|         this.from = from; | ||||
|         this.to = to; | ||||
|         this.spaceBefore = spaceBefore; | ||||
|         this.spaceAfter = spaceAfter; | ||||
|         this.type = type; | ||||
|         this.item = item; | ||||
|     } | ||||
|     blank(maxWidth, trailing = true) { | ||||
|         let result = this.spaceBefore + (this.node.name == "Blockquote" ? ">" : ""); | ||||
|         if (maxWidth != null) { | ||||
|             while (result.length < maxWidth) | ||||
|                 result += " "; | ||||
|             return result; | ||||
|         } | ||||
|         else { | ||||
|             for (let i = this.to - this.from - result.length - this.spaceAfter.length; i > 0; i--) | ||||
|                 result += " "; | ||||
|             return result + (trailing ? this.spaceAfter : ""); | ||||
|         } | ||||
|     } | ||||
|     marker(doc, add) { | ||||
|         let number = this.node.name == "OrderedList" ? String((+itemNumber(this.item, doc)[2] + add)) : ""; | ||||
|         return this.spaceBefore + number + this.type + this.spaceAfter; | ||||
|     } | ||||
| } | ||||
| function getContext(node, doc) { | ||||
|     let nodes = []; | ||||
|     for (let cur = node; cur && cur.name != "Document"; cur = cur.parent) { | ||||
|         if (cur.name == "ListItem" || cur.name == "Blockquote" || cur.name == "FencedCode") | ||||
|             nodes.push(cur); | ||||
|     } | ||||
|     let context = []; | ||||
|     for (let i = nodes.length - 1; i >= 0; i--) { | ||||
|         let node = nodes[i], match; | ||||
|         let line = doc.lineAt(node.from), startPos = node.from - line.from; | ||||
|         if (node.name == "FencedCode") { | ||||
|             context.push(new Context(node, startPos, startPos, "", "", "", null)); | ||||
|         } | ||||
|         else if (node.name == "Blockquote" && (match = /^ *>( ?)/.exec(line.text.slice(startPos)))) { | ||||
|             context.push(new Context(node, startPos, startPos + match[0].length, "", match[1], ">", null)); | ||||
|         } | ||||
|         else if (node.name == "ListItem" && node.parent.name == "OrderedList" && | ||||
|             (match = /^( *)\d+([.)])( *)/.exec(line.text.slice(startPos)))) { | ||||
|             let after = match[3], len = match[0].length; | ||||
|             if (after.length >= 4) { | ||||
|                 after = after.slice(0, after.length - 4); | ||||
|                 len -= 4; | ||||
|             } | ||||
|             context.push(new Context(node.parent, startPos, startPos + len, match[1], after, match[2], node)); | ||||
|         } | ||||
|         else if (node.name == "ListItem" && node.parent.name == "BulletList" && | ||||
|             (match = /^( *)([-+*])( {1,4}\[[ xX]\])?( +)/.exec(line.text.slice(startPos)))) { | ||||
|             let after = match[4], len = match[0].length; | ||||
|             if (after.length > 4) { | ||||
|                 after = after.slice(0, after.length - 4); | ||||
|                 len -= 4; | ||||
|             } | ||||
|             let type = match[2]; | ||||
|             if (match[3]) | ||||
|                 type += match[3].replace(/[xX]/, ' '); | ||||
|             context.push(new Context(node.parent, startPos, startPos + len, match[1], after, type, node)); | ||||
|         } | ||||
|     } | ||||
|     return context; | ||||
| } | ||||
| function itemNumber(item, doc) { | ||||
|     return /^(\s*)(\d+)(?=[.)])/.exec(doc.sliceString(item.from, item.from + 10)); | ||||
| } | ||||
| function renumberList(after, doc, changes, offset = 0) { | ||||
|     for (let prev = -1, node = after;;) { | ||||
|         if (node.name == "ListItem") { | ||||
|             let m = itemNumber(node, doc); | ||||
|             let number = +m[2]; | ||||
|             if (prev >= 0) { | ||||
|                 if (number != prev + 1) | ||||
|                     return; | ||||
|                 changes.push({ from: node.from + m[1].length, to: node.from + m[0].length, insert: String(prev + 2 + offset) }); | ||||
|             } | ||||
|             prev = number; | ||||
|         } | ||||
|         let next = node.nextSibling; | ||||
|         if (!next) | ||||
|             break; | ||||
|         node = next; | ||||
|     } | ||||
| } | ||||
| function normalizeIndent(content, state) { | ||||
|     let blank = /^[ \t]*/.exec(content)[0].length; | ||||
|     if (!blank || state.facet(indentUnit) != "\t") | ||||
|         return content; | ||||
|     let col = countColumn(content, 4, blank); | ||||
|     let space = ""; | ||||
|     for (let i = col; i > 0;) { | ||||
|         if (i >= 4) { | ||||
|             space += "\t"; | ||||
|             i -= 4; | ||||
|         } | ||||
|         else { | ||||
|             space += " "; | ||||
|             i--; | ||||
|         } | ||||
|     } | ||||
|     return space + content.slice(blank); | ||||
| } | ||||
| /** | ||||
| This command, when invoked in Markdown context with cursor | ||||
| selection(s), will create a new line with the markup for | ||||
| blockquotes and lists that were active on the old line. If the | ||||
| cursor was directly after the end of the markup for the old line, | ||||
| trailing whitespace and list markers are removed from that line. | ||||
|  | ||||
| The command does nothing in non-Markdown context, so it should | ||||
| not be used as the only binding for Enter (even in a Markdown | ||||
| document, HTML and code regions might use a different language). | ||||
| */ | ||||
| const insertNewlineContinueMarkup = ({ state, dispatch }) => { | ||||
|     let tree = syntaxTree(state), { doc } = state; | ||||
|     let dont = null, changes = state.changeByRange(range => { | ||||
|         if (!range.empty || !markdownLanguage.isActiveAt(state, range.from)) | ||||
|             return dont = { range }; | ||||
|         let pos = range.from, line = doc.lineAt(pos); | ||||
|         let context = getContext(tree.resolveInner(pos, -1), doc); | ||||
|         while (context.length && context[context.length - 1].from > pos - line.from) | ||||
|             context.pop(); | ||||
|         if (!context.length) | ||||
|             return dont = { range }; | ||||
|         let inner = context[context.length - 1]; | ||||
|         if (inner.to - inner.spaceAfter.length > pos - line.from) | ||||
|             return dont = { range }; | ||||
|         let emptyLine = pos >= (inner.to - inner.spaceAfter.length) && !/\S/.test(line.text.slice(inner.to)); | ||||
|         // Empty line in list | ||||
|         if (inner.item && emptyLine) { | ||||
|             let first = inner.node.firstChild, second = inner.node.getChild("ListItem", "ListItem"); | ||||
|             // Not second item or blank line before: delete a level of markup | ||||
|             if (first.to >= pos || second && second.to < pos || | ||||
|                 line.from > 0 && !/[^\s>]/.test(doc.lineAt(line.from - 1).text)) { | ||||
|                 let next = context.length > 1 ? context[context.length - 2] : null; | ||||
|                 let delTo, insert = ""; | ||||
|                 if (next && next.item) { // Re-add marker for the list at the next level | ||||
|                     delTo = line.from + next.from; | ||||
|                     insert = next.marker(doc, 1); | ||||
|                 } | ||||
|                 else { | ||||
|                     delTo = line.from + (next ? next.to : 0); | ||||
|                 } | ||||
|                 let changes = [{ from: delTo, to: pos, insert }]; | ||||
|                 if (inner.node.name == "OrderedList") | ||||
|                     renumberList(inner.item, doc, changes, -2); | ||||
|                 if (next && next.node.name == "OrderedList") | ||||
|                     renumberList(next.item, doc, changes); | ||||
|                 return { range: EditorSelection.cursor(delTo + insert.length), changes }; | ||||
|             } | ||||
|             else { // Move second item down, making tight two-item list non-tight | ||||
|                 let insert = blankLine(context, state, line); | ||||
|                 return { range: EditorSelection.cursor(pos + insert.length + 1), | ||||
|                     changes: { from: line.from, insert: insert + state.lineBreak } }; | ||||
|             } | ||||
|         } | ||||
|         if (inner.node.name == "Blockquote" && emptyLine && line.from) { | ||||
|             let prevLine = doc.lineAt(line.from - 1), quoted = />\s*$/.exec(prevLine.text); | ||||
|             // Two aligned empty quoted lines in a row | ||||
|             if (quoted && quoted.index == inner.from) { | ||||
|                 let changes = state.changes([{ from: prevLine.from + quoted.index, to: prevLine.to }, | ||||
|                     { from: line.from + inner.from, to: line.to }]); | ||||
|                 return { range: range.map(changes), changes }; | ||||
|             } | ||||
|         } | ||||
|         let changes = []; | ||||
|         if (inner.node.name == "OrderedList") | ||||
|             renumberList(inner.item, doc, changes); | ||||
|         let continued = inner.item && inner.item.from < line.from; | ||||
|         let insert = ""; | ||||
|         // If not dedented | ||||
|         if (!continued || /^[\s\d.)\-+*>]*/.exec(line.text)[0].length >= inner.to) { | ||||
|             for (let i = 0, e = context.length - 1; i <= e; i++) { | ||||
|                 insert += i == e && !continued ? context[i].marker(doc, 1) | ||||
|                     : context[i].blank(i < e ? countColumn(line.text, 4, context[i + 1].from) - insert.length : null); | ||||
|             } | ||||
|         } | ||||
|         let from = pos; | ||||
|         while (from > line.from && /\s/.test(line.text.charAt(from - line.from - 1))) | ||||
|             from--; | ||||
|         insert = normalizeIndent(insert, state); | ||||
|         if (nonTightList(inner.node, state.doc)) | ||||
|             insert = blankLine(context, state, line) + state.lineBreak + insert; | ||||
|         changes.push({ from, to: pos, insert: state.lineBreak + insert }); | ||||
|         return { range: EditorSelection.cursor(from + insert.length + 1), changes }; | ||||
|     }); | ||||
|     if (dont) | ||||
|         return false; | ||||
|     dispatch(state.update(changes, { scrollIntoView: true, userEvent: "input" })); | ||||
|     return true; | ||||
| }; | ||||
| function isMark(node) { | ||||
|     return node.name == "QuoteMark" || node.name == "ListMark"; | ||||
| } | ||||
| function nonTightList(node, doc) { | ||||
|     if (node.name != "OrderedList" && node.name != "BulletList") | ||||
|         return false; | ||||
|     let first = node.firstChild, second = node.getChild("ListItem", "ListItem"); | ||||
|     if (!second) | ||||
|         return false; | ||||
|     let line1 = doc.lineAt(first.to), line2 = doc.lineAt(second.from); | ||||
|     let empty = /^[\s>]*$/.test(line1.text); | ||||
|     return line1.number + (empty ? 0 : 1) < line2.number; | ||||
| } | ||||
| function blankLine(context, state, line) { | ||||
|     let insert = ""; | ||||
|     for (let i = 0, e = context.length - 2; i <= e; i++) { | ||||
|         insert += context[i].blank(i < e ? countColumn(line.text, 4, context[i + 1].from) - insert.length : null, i < e); | ||||
|     } | ||||
|     return normalizeIndent(insert, state); | ||||
| } | ||||
| function contextNodeForDelete(tree, pos) { | ||||
|     let node = tree.resolveInner(pos, -1), scan = pos; | ||||
|     if (isMark(node)) { | ||||
|         scan = node.from; | ||||
|         node = node.parent; | ||||
|     } | ||||
|     for (let prev; prev = node.childBefore(scan);) { | ||||
|         if (isMark(prev)) { | ||||
|             scan = prev.from; | ||||
|         } | ||||
|         else if (prev.name == "OrderedList" || prev.name == "BulletList") { | ||||
|             node = prev.lastChild; | ||||
|             scan = node.to; | ||||
|         } | ||||
|         else { | ||||
|             break; | ||||
|         } | ||||
|     } | ||||
|     return node; | ||||
| } | ||||
| /** | ||||
| This command will, when invoked in a Markdown context with the | ||||
| cursor directly after list or blockquote markup, delete one level | ||||
| of markup. When the markup is for a list, it will be replaced by | ||||
| spaces on the first invocation (a further invocation will delete | ||||
| the spaces), to make it easy to continue a list. | ||||
|  | ||||
| When not after Markdown block markup, this command will return | ||||
| false, so it is intended to be bound alongside other deletion | ||||
| commands, with a higher precedence than the more generic commands. | ||||
| */ | ||||
| const deleteMarkupBackward = ({ state, dispatch }) => { | ||||
|     let tree = syntaxTree(state); | ||||
|     let dont = null, changes = state.changeByRange(range => { | ||||
|         let pos = range.from, { doc } = state; | ||||
|         if (range.empty && markdownLanguage.isActiveAt(state, range.from)) { | ||||
|             let line = doc.lineAt(pos); | ||||
|             let context = getContext(contextNodeForDelete(tree, pos), doc); | ||||
|             if (context.length) { | ||||
|                 let inner = context[context.length - 1]; | ||||
|                 let spaceEnd = inner.to - inner.spaceAfter.length + (inner.spaceAfter ? 1 : 0); | ||||
|                 // Delete extra trailing space after markup | ||||
|                 if (pos - line.from > spaceEnd && !/\S/.test(line.text.slice(spaceEnd, pos - line.from))) | ||||
|                     return { range: EditorSelection.cursor(line.from + spaceEnd), | ||||
|                         changes: { from: line.from + spaceEnd, to: pos } }; | ||||
|                 if (pos - line.from == spaceEnd && | ||||
|                     // Only apply this if we're on the line that has the | ||||
|                     // construct's syntax, or there's only indentation in the | ||||
|                     // target range | ||||
|                     (!inner.item || line.from <= inner.item.from || !/\S/.test(line.text.slice(0, inner.to)))) { | ||||
|                     let start = line.from + inner.from; | ||||
|                     // Replace a list item marker with blank space | ||||
|                     if (inner.item && inner.node.from < inner.item.from && /\S/.test(line.text.slice(inner.from, inner.to))) { | ||||
|                         let insert = inner.blank(countColumn(line.text, 4, inner.to) - countColumn(line.text, 4, inner.from)); | ||||
|                         if (start == line.from) | ||||
|                             insert = normalizeIndent(insert, state); | ||||
|                         return { range: EditorSelection.cursor(start + insert.length), | ||||
|                             changes: { from: start, to: line.from + inner.to, insert } }; | ||||
|                     } | ||||
|                     // Delete one level of indentation | ||||
|                     if (start < pos) | ||||
|                         return { range: EditorSelection.cursor(start), changes: { from: start, to: pos } }; | ||||
|                 } | ||||
|             } | ||||
|         } | ||||
|         return dont = { range }; | ||||
|     }); | ||||
|     if (dont) | ||||
|         return false; | ||||
|     dispatch(state.update(changes, { scrollIntoView: true, userEvent: "delete" })); | ||||
|     return true; | ||||
| }; | ||||
|  | ||||
| /** | ||||
| A small keymap with Markdown-specific bindings. Binds Enter to | ||||
| [`insertNewlineContinueMarkup`](https://codemirror.net/6/docs/ref/#lang-markdown.insertNewlineContinueMarkup) | ||||
| and Backspace to | ||||
| [`deleteMarkupBackward`](https://codemirror.net/6/docs/ref/#lang-markdown.deleteMarkupBackward). | ||||
| */ | ||||
| const markdownKeymap = [ | ||||
|     { key: "Enter", run: insertNewlineContinueMarkup }, | ||||
|     { key: "Backspace", run: deleteMarkupBackward } | ||||
| ]; | ||||
| const htmlNoMatch = /*@__PURE__*/html({ matchClosingTags: false }); | ||||
| /** | ||||
| Markdown language support. | ||||
| */ | ||||
| function markdown(config = {}) { | ||||
|     let { codeLanguages, defaultCodeLanguage, addKeymap = true, base: { parser } = commonmarkLanguage, completeHTMLTags = true } = config; | ||||
|     if (!(parser instanceof MarkdownParser)) | ||||
|         throw new RangeError("Base parser provided to `markdown` should be a Markdown parser"); | ||||
|     let extensions = config.extensions ? [config.extensions] : []; | ||||
|     let support = [htmlNoMatch.support], defaultCode; | ||||
|     if (defaultCodeLanguage instanceof LanguageSupport) { | ||||
|         support.push(defaultCodeLanguage.support); | ||||
|         defaultCode = defaultCodeLanguage.language; | ||||
|     } | ||||
|     else if (defaultCodeLanguage) { | ||||
|         defaultCode = defaultCodeLanguage; | ||||
|     } | ||||
|     let codeParser = codeLanguages || defaultCode ? getCodeParser(codeLanguages, defaultCode) : undefined; | ||||
|     extensions.push(parseCode({ codeParser, htmlParser: htmlNoMatch.language.parser })); | ||||
|     if (addKeymap) | ||||
|         support.push(Prec.high(keymap.of(markdownKeymap))); | ||||
|     let lang = mkLang(parser.configure(extensions)); | ||||
|     if (completeHTMLTags) | ||||
|         support.push(lang.data.of({ autocomplete: htmlTagCompletion })); | ||||
|     return new LanguageSupport(lang, support); | ||||
| } | ||||
| function htmlTagCompletion(context) { | ||||
|     let { state, pos } = context, m = /<[:\-\.\w\u00b7-\uffff]*$/.exec(state.sliceDoc(pos - 25, pos)); | ||||
|     if (!m) | ||||
|         return null; | ||||
|     let tree = syntaxTree(state).resolveInner(pos, -1); | ||||
|     while (tree && !tree.type.isTop) { | ||||
|         if (tree.name == "CodeBlock" || tree.name == "FencedCode" || tree.name == "ProcessingInstructionBlock" || | ||||
|             tree.name == "CommentBlock" || tree.name == "Link" || tree.name == "Image") | ||||
|             return null; | ||||
|         tree = tree.parent; | ||||
|     } | ||||
|     return { | ||||
|         from: pos - m[0].length, to: pos, | ||||
|         options: htmlTagCompletions(), | ||||
|         validFor: /^<[:\-\.\w\u00b7-\uffff]*$/ | ||||
|     }; | ||||
| } | ||||
| let _tagCompletions = null; | ||||
| function htmlTagCompletions() { | ||||
|     if (_tagCompletions) | ||||
|         return _tagCompletions; | ||||
|     let result = htmlCompletionSource(new CompletionContext(EditorState.create({ extensions: htmlNoMatch }), 0, true)); | ||||
|     return _tagCompletions = result ? result.options : []; | ||||
| } | ||||
|  | ||||
| export { commonmarkLanguage, deleteMarkupBackward, insertNewlineContinueMarkup, markdown, markdownKeymap, markdownLanguage }; | ||||
		Reference in New Issue
	
	Block a user