Diffdown is a real-time collaborative Markdown editor/previewer built on the AT Protocol
diffdown.com
1import {ScriptText, StyleText, TextareaText,
2 Element, TagName, Attribute, AttributeName, OpenTag, CloseTag,
3 AttributeValue, UnquotedAttributeValue} from "./parser.terms.js"
4import {parseMixed} from "@lezer/common"
5
6function getAttrs(openTag, input) {
7 let attrs = Object.create(null)
8 for (let att of openTag.getChildren(Attribute)) {
9 let name = att.getChild(AttributeName), value = att.getChild(AttributeValue) || att.getChild(UnquotedAttributeValue)
10 if (name) attrs[input.read(name.from, name.to)] =
11 !value ? "" : value.type.id == AttributeValue ? input.read(value.from + 1, value.to - 1) : input.read(value.from, value.to)
12 }
13 return attrs
14}
15
16function findTagName(openTag, input) {
17 let tagNameNode = openTag.getChild(TagName)
18 return tagNameNode ? input.read(tagNameNode.from, tagNameNode.to) : " "
19}
20
21function maybeNest(node, input, tags) {
22 let attrs
23 for (let tag of tags) {
24 if (!tag.attrs || tag.attrs(attrs || (attrs = getAttrs(node.node.parent.firstChild, input))))
25 return {parser: tag.parser, bracketed: true}
26 }
27 return null
28}
29
30// tags?: {
31// tag: string,
32// attrs?: ({[attr: string]: string}) => boolean,
33// parser: Parser
34// }[]
35// attributes?: {
36// name: string,
37// tagName?: string,
38// parser: Parser
39// }[]
40
41export function configureNesting(tags = [], attributes = []) {
42 let script = [], style = [], textarea = [], other = []
43 for (let tag of tags) {
44 let array = tag.tag == "script" ? script : tag.tag == "style" ? style : tag.tag == "textarea" ? textarea : other
45 array.push(tag)
46 }
47 let attrs = attributes.length ? Object.create(null) : null
48 for (let attr of attributes) (attrs[attr.name] || (attrs[attr.name] = [])).push(attr)
49
50 return parseMixed((node, input) => {
51 let id = node.type.id
52 if (id == ScriptText) return maybeNest(node, input, script)
53 if (id == StyleText) return maybeNest(node, input, style)
54 if (id == TextareaText) return maybeNest(node, input, textarea)
55
56 if (id == Element && other.length) {
57 let n = node.node, open = n.firstChild, tagName = open && findTagName(open, input), attrs
58 if (tagName) for (let tag of other) {
59 if (tag.tag == tagName && (!tag.attrs || tag.attrs(attrs || (attrs = getAttrs(open, input))))) {
60 let close = n.lastChild
61 let to = close.type.id == CloseTag ? close.from : n.to
62 if (to > open.to)
63 return {parser: tag.parser, overlay: [{from: open.to, to}]}
64 }
65 }
66 }
67
68 if (attrs && id == Attribute) {
69 let n = node.node, nameNode
70 if (nameNode = n.firstChild) {
71 let matches = attrs[input.read(nameNode.from, nameNode.to)]
72 if (matches) for (let attr of matches) {
73 if (attr.tagName && attr.tagName != findTagName(n.parent, input)) continue
74 let value = n.lastChild
75 if (value.type.id == AttributeValue) {
76 let from = value.from + 1
77 let last = value.lastChild, to = value.to - (last && last.isError ? 0 : 1)
78 if (to > from) return {parser: attr.parser, overlay: [{from, to}], bracketed: true}
79 } else if (value.type.id == UnquotedAttributeValue) {
80 return {parser: attr.parser, overlay: [{from: value.from, to: value.to}]}
81 }
82 }
83 }
84 }
85 return null
86 })
87}