pax_global_header00006660000000000000000000000064145310627110014512gustar00rootroot0000000000000052 comment=4d917f95f8fd36810124c6f5d9de56ff2ae51bd3 lang-markdown-6.2.3/000077500000000000000000000000001453106271100142635ustar00rootroot00000000000000lang-markdown-6.2.3/.github/000077500000000000000000000000001453106271100156235ustar00rootroot00000000000000lang-markdown-6.2.3/.github/workflows/000077500000000000000000000000001453106271100176605ustar00rootroot00000000000000lang-markdown-6.2.3/.github/workflows/dispatch.yml000066400000000000000000000006371453106271100222100ustar00rootroot00000000000000name: Trigger CI on: push jobs: build: name: Dispatch to main repo runs-on: ubuntu-latest steps: - name: Emit repository_dispatch uses: mvasigh/dispatch-action@main with: # You should create a personal access token and store it in your repository token: ${{ secrets.DISPATCH_AUTH }} repo: dev owner: codemirror event_type: push lang-markdown-6.2.3/.gitignore000066400000000000000000000001271453106271100162530ustar00rootroot00000000000000/node_modules package-lock.json /dist /test/*.js /test/*.d.ts /test/*.d.ts.map .tern-* lang-markdown-6.2.3/.npmignore000066400000000000000000000001001453106271100162510ustar00rootroot00000000000000/src /test /node_modules .tern-* rollup.config.js tsconfig.json lang-markdown-6.2.3/CHANGELOG.md000066400000000000000000000077051453106271100161050ustar00rootroot00000000000000## 6.2.3 (2023-11-27) ### Bug fixes Support code folding for GFM tables. ## 6.2.2 (2023-10-06) ### Bug fixes Fix a bug in `insertNewlineContinueMarkup` that caused it to put the cursor in the wrong place when the editor's line break was more than one character long. ## 6.2.1 (2023-09-14) ### Bug fixes Make `insertNewlineContinueMarkup` and `deleteMarkupBackward` use tabs for indentation when appropriate. ## 6.2.0 (2023-06-23) ### New features The markdown package now installs a completion source that completes HTML tags when in Markdown context. ## 6.1.1 (2023-04-13) ### Bug fixes Fix the declaration of `comentTokens` language data for Markdown. Fix a bug in `deleteMarkupBackward` that would cause it to delete pieces of continued paragraphs below list item markers. ## 6.1.0 (2023-02-17) ### New features Add support for folding entire sections from the header. ## 6.0.5 (2022-11-10) ### Bug fixes Make sure task lists are indented correctly even when deeply nested. ## 6.0.4 (2022-11-02) ### Bug fixes Fix an issue where nested task lists were indented too deeply. ## 6.0.3 (2022-10-24) ### Bug fixes Add a `name` value to the Markdown language object. ## 6.0.2 (2022-10-10) ### Bug fixes Improve `insertNewlineContinueMarkup`'s behavior in a fenced code block. ## 6.0.1 (2022-07-25) ### Bug fixes Ignore text after whitespace in code block metadata, when determining which language the block is. ## 6.0.0 (2022-06-08) ### Breaking changes Update dependencies to 6.0.0 ## 0.20.1 (2022-05-20) ### New features The `codeLanguages` option to `markdown` may now be a function from an info string to a language. ## 0.20.0 (2022-04-20) ### New features `insertNewlineContinueMarkup` can now continue task lists. Move highlighting information into @lezer/markdown ## 0.19.6 (2022-02-04) ### Bug fixes Fix an issue where `deleteMarkupBackward` could get confused when there was only whitespace between the cursor and the start of the line. ## 0.19.5 (2022-01-28) ### Bug fixes Make `insertNewlineContinueMarkup` exit blockquotes after two blank lines. ## 0.19.4 (2022-01-03) ### Bug fixes Fix a bug where list items after a removed item were incorrectly renumbered. ## 0.19.3 (2021-12-10) ### Bug fixes `insertNewlineContinueMarkup` will no longer exit lists when there is content after the cursor. Fix an issue in `deleteMarkupBackward` where it only deleted a single space when after a number marker. ## 0.19.2 (2021-10-20) ### Bug fixes Fix a bug where the monospace highlighting tag wasn't correctly applied to code block content. ## 0.19.1 (2021-08-11) ### Bug fixes Fix incorrect versions for @lezer dependencies. ## 0.19.0 (2021-08-11) ### Breaking changes Update dependencies to 0.19.0 ## 0.18.4 (2021-06-16) ### Bug fixes Fix a case where `deleteMarkupBackward` would return true without actually having an effect. ## 0.18.3 (2021-05-19) ### Bug fixes `insertNewlineContinueMarkup` will not continue moving list markers down when they are after an empty line anymore. ## 0.18.2 (2021-05-07) ### Bug fixes Fix a bug where `insertNewlineContinueMarkup` could duplicate bits of content when in dededented continued list items. ## 0.18.1 (2021-04-01) ### Bug fixes Add `monospace` style tag to all children of inline code nodes. ## 0.18.0 (2021-03-03) ### Breaking changes Update dependencies to 0.18. ## 0.17.3 (2021-02-22) ### New features Include heading depth in style tags. ## 0.17.2 (2021-02-10) ### Bug fixes Fix a bug where `insertNewlineContinueMarkup` would sometimes duplicate bits of content. ### New features The package now exports both a `commonmarkLanguage`, with just plain CommonMark, and a `markdownLanguage`, with GFM and some other extensions enabled. It is now possible to pass lezer-markdown extensions to the `markdown` function to configure the parser. ## 0.17.1 (2021-01-06) ### New features The package now also exports a CommonJS module. ## 0.17.0 (2020-12-29) ### Breaking changes First numbered release. lang-markdown-6.2.3/LICENSE000066400000000000000000000021361453106271100152720ustar00rootroot00000000000000MIT License Copyright (C) 2018-2021 by Marijn Haverbeke and others Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions: The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software. THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. lang-markdown-6.2.3/README.md000066400000000000000000000160621453106271100155470ustar00rootroot00000000000000 # @codemirror/lang-markdown [![NPM version](https://img.shields.io/npm/v/@codemirror/lang-markdown.svg)](https://www.npmjs.org/package/@codemirror/lang-markdown) [ [**WEBSITE**](https://codemirror.net/) | [**ISSUES**](https://github.com/codemirror/dev/issues) | [**FORUM**](https://discuss.codemirror.net/c/next/) | [**CHANGELOG**](https://github.com/codemirror/lang-markdown/blob/main/CHANGELOG.md) ] This package implements Markdown language support for the [CodeMirror](https://codemirror.net/) code editor. The [project page](https://codemirror.net/) has more information, a number of [examples](https://codemirror.net/examples/) and the [documentation](https://codemirror.net/docs/). This code is released under an [MIT license](https://github.com/codemirror/lang-markdown/tree/main/LICENSE). We aim to be an inclusive, welcoming community. To make that explicit, we have a [code of conduct](http://contributor-covenant.org/version/1/1/0/) that applies to communication around the project. ## API Reference
markdown(config⁠?: Object = {}) → LanguageSupport

Markdown language support.

config
defaultCodeLanguage⁠?: Language | LanguageSupport

When given, this language will be used by default to parse code blocks.

codeLanguages⁠?: readonly LanguageDescription[] | fn(infostring) → Language | LanguageDescription | null

A source of language support for highlighting fenced code blocks. When it is an array, the parser will use LanguageDescription.matchLanguageName with the fenced code info to find a matching language. When it is a function, will be called with the info string and may return a language or LanguageDescription object.

addKeymap⁠?: boolean

Set this to false to disable installation of the Markdown keymap.

extensions⁠?: MarkdownExtension

Markdown parser extensions to add to the parser.

base⁠?: Language

The base language to use. Defaults to commonmarkLanguage.

markdownLanguage: Language

Language support for GFM plus subscript, superscript, and emoji syntax.

commonmarkLanguage: Language

Language support for strict CommonMark.

insertNewlineContinueMarkup: StateCommand

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).

deleteMarkupBackward: StateCommand

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.

markdownKeymap: readonly KeyBinding[]

A small keymap with Markdown-specific bindings. Binds Enter to insertNewlineContinueMarkup and Backspace to deleteMarkupBackward.

lang-markdown-6.2.3/package.json000066400000000000000000000021061453106271100165500ustar00rootroot00000000000000{ "name": "@codemirror/lang-markdown", "version": "6.2.3", "description": "Markdown language support for the CodeMirror code editor", "scripts": { "test": "cm-runtests", "prepare": "cm-buildhelper src/index.ts" }, "keywords": [ "editor", "code" ], "author": { "name": "Marijn Haverbeke", "email": "marijn@haverbeke.berlin", "url": "http://marijnhaverbeke.nl" }, "type": "module", "main": "dist/index.cjs", "exports": { "import": "./dist/index.js", "require": "./dist/index.cjs" }, "types": "dist/index.d.ts", "module": "dist/index.js", "sideEffects": false, "license": "MIT", "dependencies": { "@codemirror/autocomplete": "^6.7.1", "@codemirror/lang-html": "^6.0.0", "@codemirror/language": "^6.3.0", "@codemirror/state": "^6.0.0", "@codemirror/view": "^6.0.0", "@lezer/markdown": "^1.0.0", "@lezer/common": "^1.0.0" }, "devDependencies": { "@codemirror/buildhelper": "^1.0.0" }, "repository": { "type": "git", "url": "https://github.com/codemirror/lang-markdown.git" } } lang-markdown-6.2.3/src/000077500000000000000000000000001453106271100150525ustar00rootroot00000000000000lang-markdown-6.2.3/src/README.md000066400000000000000000000022551453106271100163350ustar00rootroot00000000000000 # @codemirror/lang-markdown [![NPM version](https://img.shields.io/npm/v/@codemirror/lang-markdown.svg)](https://www.npmjs.org/package/@codemirror/lang-markdown) [ [**WEBSITE**](https://codemirror.net/) | [**ISSUES**](https://github.com/codemirror/dev/issues) | [**FORUM**](https://discuss.codemirror.net/c/next/) | [**CHANGELOG**](https://github.com/codemirror/lang-markdown/blob/main/CHANGELOG.md) ] This package implements Markdown language support for the [CodeMirror](https://codemirror.net/) code editor. The [project page](https://codemirror.net/) has more information, a number of [examples](https://codemirror.net/examples/) and the [documentation](https://codemirror.net/docs/). This code is released under an [MIT license](https://github.com/codemirror/lang-markdown/tree/main/LICENSE). We aim to be an inclusive, welcoming community. To make that explicit, we have a [code of conduct](http://contributor-covenant.org/version/1/1/0/) that applies to communication around the project. ## API Reference @markdown @markdownLanguage @commonmarkLanguage @insertNewlineContinueMarkup @deleteMarkupBackward @markdownKeymap lang-markdown-6.2.3/src/commands.ts000066400000000000000000000256611453106271100172350ustar00rootroot00000000000000import {StateCommand, Text, EditorState, EditorSelection, ChangeSpec, countColumn} from "@codemirror/state" import {syntaxTree, indentUnit} from "@codemirror/language" import {SyntaxNode, Tree} from "@lezer/common" import {markdownLanguage} from "./markdown" class Context { constructor( readonly node: SyntaxNode, readonly from: number, readonly to: number, readonly spaceBefore: string, readonly spaceAfter: string, readonly type: string, readonly item: SyntaxNode | null ) {} blank(maxWidth: number | null, 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: Text, add: number) { let number = this.node.name == "OrderedList" ? String((+itemNumber(this.item!, doc)[2] + add)) : "" return this.spaceBefore + number + this.type + this.spaceAfter } } function getContext(node: SyntaxNode, doc: Text) { let nodes = [] for (let cur: SyntaxNode | null = 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: SyntaxNode, doc: Text) { return /^(\s*)(\d+)(?=[.)])/.exec(doc.sliceString(item.from, item.from + 10))! } function renumberList(after: SyntaxNode, doc: Text, changes: ChangeSpec[], 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: string, state: EditorState) { 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). export const insertNewlineContinueMarkup: StateCommand = ({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) { // First list item or blank line before: delete a level of markup if (inner.node.firstChild!.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: ChangeSpec[] = [{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 this line down 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) } insert = normalizeIndent(insert, state) 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: ChangeSpec[] = [] 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) 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: SyntaxNode) { return node.name == "QuoteMark" || node.name == "ListMark" } function contextNodeForDelete(tree: Tree, pos: number) { 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. export const deleteMarkupBackward: StateCommand = ({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 } lang-markdown-6.2.3/src/index.ts000066400000000000000000000106131453106271100165320ustar00rootroot00000000000000import {Prec, EditorState} from "@codemirror/state" import {KeyBinding, keymap} from "@codemirror/view" import {Language, LanguageSupport, LanguageDescription, syntaxTree} from "@codemirror/language" import {Completion, CompletionContext} from "@codemirror/autocomplete" import {MarkdownExtension, MarkdownParser, parseCode} from "@lezer/markdown" import {html, htmlCompletionSource} from "@codemirror/lang-html" import {commonmarkLanguage, markdownLanguage, mkLang, getCodeParser} from "./markdown" import {insertNewlineContinueMarkup, deleteMarkupBackward} from "./commands" export {commonmarkLanguage, markdownLanguage, insertNewlineContinueMarkup, deleteMarkupBackward} /// A small keymap with Markdown-specific bindings. Binds Enter to /// [`insertNewlineContinueMarkup`](#lang-markdown.insertNewlineContinueMarkup) /// and Backspace to /// [`deleteMarkupBackward`](#lang-markdown.deleteMarkupBackward). export const markdownKeymap: readonly KeyBinding[] = [ {key: "Enter", run: insertNewlineContinueMarkup}, {key: "Backspace", run: deleteMarkupBackward} ] const htmlNoMatch = html({matchClosingTags: false}) /// Markdown language support. export function markdown(config: { /// When given, this language will be used by default to parse code /// blocks. defaultCodeLanguage?: Language | LanguageSupport, /// A source of language support for highlighting fenced code /// blocks. When it is an array, the parser will use /// [`LanguageDescription.matchLanguageName`](#language.LanguageDescription^matchLanguageName) /// with the fenced code info to find a matching language. When it /// is a function, will be called with the info string and may /// return a language or `LanguageDescription` object. codeLanguages?: readonly LanguageDescription[] | ((info: string) => Language | LanguageDescription | null), /// Set this to false to disable installation of the Markdown /// [keymap](#lang-markdown.markdownKeymap). addKeymap?: boolean, /// Markdown parser /// [extensions](https://github.com/lezer-parser/markdown#user-content-markdownextension) /// to add to the parser. extensions?: MarkdownExtension, /// The base language to use. Defaults to /// [`commonmarkLanguage`](#lang-markdown.commonmarkLanguage). base?: Language, /// By default, the extension installs an autocompletion source that /// completes HTML tags when a `<` is typed. Set this to false to /// disable this. completeHTMLTags?: boolean } = {}) { 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: CompletionContext) { 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: readonly Completion[] | null = null function htmlTagCompletions() { if (_tagCompletions) return _tagCompletions let result = htmlCompletionSource(new CompletionContext(EditorState.create({extensions: htmlNoMatch}), 0, true)) return _tagCompletions = result ? result.options : [] } lang-markdown-6.2.3/src/markdown.ts000066400000000000000000000057611453106271100172550ustar00rootroot00000000000000import {Language, defineLanguageFacet, languageDataProp, foldNodeProp, indentNodeProp, foldService, syntaxTree, LanguageDescription, ParseContext} from "@codemirror/language" import {parser as baseParser, MarkdownParser, GFM, Subscript, Superscript, Emoji} from "@lezer/markdown" import {SyntaxNode, NodeType, NodeProp} from "@lezer/common" const data = defineLanguageFacet({commentTokens: {block: {open: ""}}}) const headingProp = new NodeProp() const commonmark = baseParser.configure({ props: [ foldNodeProp.add(type => { return !type.is("Block") || type.is("Document") || isHeading(type) != null ? undefined : (tree, state) => ({from: state.doc.lineAt(tree.from).to, to: tree.to}) }), headingProp.add(isHeading), indentNodeProp.add({ Document: () => null }), languageDataProp.add({ Document: data }) ] }) function isHeading(type: NodeType) { let match = /^(?:ATX|Setext)Heading(\d)$/.exec(type.name) return match ? +match[1] : undefined } function findSectionEnd(headerNode: SyntaxNode, level: number) { 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 = foldService.of((state, start, end) => { for (let node: SyntaxNode | null = 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 }) export function mkLang(parser: MarkdownParser) { return new Language(data, parser, [headerIndent], "markdown") } /// Language support for strict CommonMark. export const commonmarkLanguage = mkLang(commonmark) const extended = commonmark.configure([GFM, Subscript, Superscript, Emoji, { props: [ 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. export const markdownLanguage = mkLang(extended) export function getCodeParser( languages: readonly LanguageDescription[] | ((info: string) => Language | LanguageDescription | null) | undefined, defaultLanguage?: Language ) { return (info: string) => { 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 } } lang-markdown-6.2.3/test/000077500000000000000000000000001453106271100152425ustar00rootroot00000000000000lang-markdown-6.2.3/test/test-commands.ts000066400000000000000000000152201453106271100203700ustar00rootroot00000000000000import {EditorState, EditorSelection, StateCommand, Extension} from "@codemirror/state" import {markdown, deleteMarkupBackward, insertNewlineContinueMarkup} from "@codemirror/lang-markdown" import {indentUnit} from "@codemirror/language" import ist from "ist" function mkState(doc: string, extension?: Extension) { let cursors = [] for (let pos = 0;;) { pos = doc.indexOf("|", pos) if (pos < 0) break cursors.push(EditorSelection.cursor(pos)) doc = doc.slice(0, pos) + doc.slice(pos + 1) } return EditorState.create({ doc, selection: cursors.length ? EditorSelection.create(cursors) : undefined, extensions: [markdown().language, EditorState.allowMultipleSelections.of(true), extension || []] }) } function stateStr(state: EditorState) { let doc = state.doc.toString() for (let i = state.selection.ranges.length - 1; i >= 0; i--) { let range = state.selection.ranges[i] doc = doc.slice(0, range.from) + "|" + doc.slice(range.to) } return doc } const tabs: Extension = [EditorState.tabSize.of(4), indentUnit.of("\t")] function cmd(state: EditorState, command: StateCommand) { command({state, dispatch(tr) { state = tr.state }}) return state } describe("insertNewlineContinueMarkup", () => { function test(from: string, to: string, ext?: Extension) { ist(stateStr(cmd(mkState(from, ext), insertNewlineContinueMarkup)), to) } it("doesn't continue anything at the top level", () => test("one|", "one|")) it("doesn't do anything in non-Markdown content", () => test("-
|", "-
|")) it("can continue blockquotes", () => test("> one|", "> one\n> |")) it("will end blockquotes after two empty lines", () => test("> one\n>\n> |", "> one\n\n|")) it("will end nested blockquotes after two empty lines", () => test("> > one\n> >\n> > |", "> > one\n> \n> |")) it("can continue nested blockquotes", () => test("> > one|", "> > one\n> > |")) it("preserves the absence of a blockquote space", () => test(">>one|", ">>one\n>>|")) it("can continue bullet lists with dashes", () => test(" - one|", " - one\n - |")) it("can continue bullet lists with asterisks", () => test(" * one|", " * one\n * |")) it("can continue bullet lists with plus signs", () => test("+ one|", "+ one\n+ |")) it("can continue ordered lists with dots", () => test(" 1. one|", " 1. one\n 2. |")) it("can continue ordered lists with parens", () => test("2) one|", "2) one\n3) |")) it("can continue lists inside blockquotes", () => test("> - one|", "> - one\n> - |")) it("can continue markup for multiple cursors", () => test("> one|\n\n- two|", "> one\n> |\n\n- two\n- |")) it("can continue nested lists", () => test(" - one\n 1. two|", " - one\n 1. two\n 2. |")) it("will leave space before nested blockquotes", () => test(" - one\n > quoted|", " - one\n > quoted\n > |")) it("can drop trailing space when pressing enter in a blockquote", () => test("> |", ">\n> |")) it("can move list markup when pressing enter directly after it", () => test(" - one\n - |", " - one\n\n - |")) it("can drop list markup after an empty line", () => { test(" - one\n\n - |", " - one\n\n|") }) it("deletes the first list marker", () => test(" - |", "|")) it("will keep the current ordered list number when moving a marker", () => test(" 1. one\n 2. |", " 1. one\n\n 2. |")) it("can move list markup inside a blockquote", () => test("> 1. one\n> 2. |", "> 1. one\n>\n> 2. |")) it("renumbers following ordered list items", () => test("1. one|\n2. two", "1. one\n2. |\n3. two")) it("renumbers after removed markers", () => test("1. one\n\n2. |\n\n3. three", "1. one\n\n|\n\n2. three")) it("stops renumbering on discontinuities", () => test("1. one|\n2. two\n3. three\n1. four", "1. one\n2. |\n3. two\n4. three\n1. four")) it("doesn't fire when the cursor is before the markup depth", () => test("- a\n|bc", "- a\n|bc")) it("continues list items", () => test("- a\n b|", "- a\n b\n |")) it("continues dedented list items", () => test("- hello\nhello|", "- hello\nhello\n|")) it("can lift out of one list level", () => test("1. a\n\n 1. b\n\n 2. |", "1. a\n\n 1. b\n\n2. |")) it("can lift out of one list level and renumber", () => test("1. a\n\n 1. b\n\n 2. |\n\n2. d", "1. a\n\n 1. b\n\n2. |\n\n3. d")) it("doesn't treat lines with content after the cursor as empty", () => { test("1. |x\n2. y", "1.\n2. |x\n3. y") test("1. x\n2. |y", "1. x\n2.\n3. |y") }) it("doesn't continue lists in fenced code", () => { test("- ```foo|", "- ```foo\n |") test("> - ```foo|", "> - ```foo\n> |") }) it("continues nested task lists at the right level", () => { test("- [ ] item 1\n - [ ] item 1.1|", "- [ ] item 1\n - [ ] item 1.1\n - [ ] |") }) it("properly indents task lists nested 2 deep", () => { test("- [ ] item 1\n - [ ] item 1.1\n - [ ] item 1.1.1|", "- [ ] item 1\n - [ ] item 1.1\n - [ ] item 1.1.1\n - [ ] |") }) it("handles tab-indentation", () => { test(" - one\n\t- two|", " - one\n\t- two\n\t- |", tabs) }) }) describe("deleteMarkupBackward", () => { function test(from: string, to: string, ext?: Extension) { ist(stateStr(cmd(mkState(from, ext), deleteMarkupBackward)), to) } it("does nothing in regular text", () => test("one|", "one|")) it("does nothing at the top level", () => test("one\n|", "one\n|")) it("can delete blockquote markers", () => test("> |", "|")) it("only deletes one marker at a time", () => test("> > |", "> |")) it("deletes trailing whitespace", () => test("> |", "> |")) it("clears list markers", () => test(" - one\n - |", " - one\n |")) it("removes extra indentation", () => test("> - one\n> - |", "> - one\n> - |")) it("clears triple-space indentation", () => test(" - one\n |", " - one\n|")) it("clears one level of indentation", () => test("- one\n - two\n |", "- one\n - two\n |")) it("deletes the first list marker immediately", () => test(" - |", "|")) it("clears number markers in one go", () => test("1. one\n2. |", "1. one\n |")) it("deletes nested list markers", () => test(" > - |", " > |")) it("can delete for multiple cursors", () => test("> |\n> |\n> |", "|\n|\n|")) it("does noting in a continued list item", () => test("- Foo\n-\n |Welcome", "- Foo\n-\n |Welcome")) it("doesn't delete normal text in continued list items", () => test("- \na |b", "- \na |b")) it("normalizes whitespace on deleting", () => test(" - one\n - |", " - one\n\t|", tabs)) })