import { parser } from '@lezer/xml'; import { LRLanguage, indentNodeProp, foldNodeProp, bracketMatchingHandle, LanguageSupport, syntaxTree } from '@codemirror/language'; function tagName(doc, tag) { let name = tag && tag.getChild("TagName"); return name ? doc.sliceString(name.from, name.to) : ""; } function elementName(doc, tree) { let tag = tree && tree.firstChild; return !tag || tag.name != "OpenTag" ? "" : tagName(doc, tag); } function attrName(doc, tag, pos) { let attr = tag && tag.getChildren("Attribute").find(a => a.from <= pos && a.to >= pos); let name = attr && attr.getChild("AttributeName"); return name ? doc.sliceString(name.from, name.to) : ""; } function findParentElement(tree) { for (let cur = tree && tree.parent; cur; cur = cur.parent) if (cur.name == "Element") return cur; return null; } function findLocation(state, pos) { var _a; let at = syntaxTree(state).resolveInner(pos, -1), inTag = null; for (let cur = at; !inTag && cur.parent; cur = cur.parent) if (cur.name == "OpenTag" || cur.name == "CloseTag" || cur.name == "SelfClosingTag" || cur.name == "MismatchedCloseTag") inTag = cur; if (inTag && (inTag.to > pos || inTag.lastChild.type.isError)) { let elt = inTag.parent; if (at.name == "TagName") return inTag.name == "CloseTag" || inTag.name == "MismatchedCloseTag" ? { type: "closeTag", from: at.from, context: elt } : { type: "openTag", from: at.from, context: findParentElement(elt) }; if (at.name == "AttributeName") return { type: "attrName", from: at.from, context: inTag }; if (at.name == "AttributeValue") return { type: "attrValue", from: at.from, context: inTag }; let before = at == inTag || at.name == "Attribute" ? at.childBefore(pos) : at; if ((before === null || before === void 0 ? void 0 : before.name) == "StartTag") return { type: "openTag", from: pos, context: findParentElement(elt) }; if ((before === null || before === void 0 ? void 0 : before.name) == "StartCloseTag" && before.to <= pos) return { type: "closeTag", from: pos, context: elt }; if ((before === null || before === void 0 ? void 0 : before.name) == "Is") return { type: "attrValue", from: pos, context: inTag }; if (before) return { type: "attrName", from: pos, context: inTag }; return null; } else if (at.name == "StartCloseTag") { return { type: "closeTag", from: pos, context: at.parent }; } while (at.parent && at.to == pos && !((_a = at.lastChild) === null || _a === void 0 ? void 0 : _a.type.isError)) at = at.parent; if (at.name == "Element" || at.name == "Text" || at.name == "Document") return { type: "tag", from: pos, context: at.name == "Element" ? at : findParentElement(at) }; return null; } class Element { constructor(spec, attrs, attrValues) { this.attrs = attrs; this.attrValues = attrValues; this.children = []; this.name = spec.name; this.completion = Object.assign(Object.assign({ type: "type" }, spec.completion || {}), { label: this.name }); this.openCompletion = Object.assign(Object.assign({}, this.completion), { label: "<" + this.name }); this.closeCompletion = Object.assign(Object.assign({}, this.completion), { label: "", boost: 2 }); this.closeNameCompletion = Object.assign(Object.assign({}, this.completion), { label: this.name + ">" }); this.text = spec.textContent ? spec.textContent.map(s => ({ label: s, type: "text" })) : []; } } const Identifier = /^[:\-\.\w\u00b7-\uffff]*$/; function attrCompletion(spec) { return Object.assign(Object.assign({ type: "property" }, spec.completion || {}), { label: spec.name }); } function valueCompletion(spec) { return typeof spec == "string" ? { label: `"${spec}"`, type: "constant" } : /^"/.test(spec.label) ? spec : Object.assign(Object.assign({}, spec), { label: `"${spec.label}"` }); } /** Create a completion source for the given schema. */ function completeFromSchema(eltSpecs, attrSpecs) { let allAttrs = [], globalAttrs = []; let attrValues = Object.create(null); for (let s of attrSpecs) { let completion = attrCompletion(s); allAttrs.push(completion); if (s.global) globalAttrs.push(completion); if (s.values) attrValues[s.name] = s.values.map(valueCompletion); } let allElements = [], topElements = []; let byName = Object.create(null); for (let s of eltSpecs) { let attrs = globalAttrs, attrVals = attrValues; if (s.attributes) attrs = attrs.concat(s.attributes.map(s => { if (typeof s == "string") return allAttrs.find(a => a.label == s) || { label: s, type: "property" }; if (s.values) { if (attrVals == attrValues) attrVals = Object.create(attrVals); attrVals[s.name] = s.values.map(valueCompletion); } return attrCompletion(s); })); let elt = new Element(s, attrs, attrVals); byName[elt.name] = elt; allElements.push(elt); if (s.top) topElements.push(elt); } if (!topElements.length) topElements = allElements; for (let i = 0; i < allElements.length; i++) { let s = eltSpecs[i], elt = allElements[i]; if (s.children) { for (let ch of s.children) if (byName[ch]) elt.children.push(byName[ch]); } else { elt.children = allElements; } } return cx => { var _a; let { doc } = cx.state, loc = findLocation(cx.state, cx.pos); if (!loc || (loc.type == "tag" && !cx.explicit)) return null; let { type, from, context } = loc; if (type == "openTag") { let children = topElements; let parentName = elementName(doc, context); if (parentName) { let parent = byName[parentName]; children = (parent === null || parent === void 0 ? void 0 : parent.children) || allElements; } return { from, options: children.map(ch => ch.completion), validFor: Identifier }; } else if (type == "closeTag") { let parentName = elementName(doc, context); return parentName ? { from, to: cx.pos + (doc.sliceString(cx.pos, cx.pos + 1) == ">" ? 1 : 0), options: [((_a = byName[parentName]) === null || _a === void 0 ? void 0 : _a.closeNameCompletion) || { label: parentName + ">", type: "type" }], validFor: Identifier } : null; } else if (type == "attrName") { let parent = byName[tagName(doc, context)]; return { from, options: (parent === null || parent === void 0 ? void 0 : parent.attrs) || globalAttrs, validFor: Identifier }; } else if (type == "attrValue") { let attr = attrName(doc, context, from); if (!attr) return null; let parent = byName[tagName(doc, context)]; let values = ((parent === null || parent === void 0 ? void 0 : parent.attrValues) || attrValues)[attr]; if (!values || !values.length) return null; return { from, to: cx.pos + (doc.sliceString(cx.pos, cx.pos + 1) == '"' ? 1 : 0), options: values, validFor: /^"[^"]*"?$/ }; } else if (type == "tag") { let parentName = elementName(doc, context), parent = byName[parentName]; let closing = [], last = context && context.lastChild; if (parentName && (!last || last.name != "CloseTag" || tagName(doc, last) != parentName)) closing.push(parent ? parent.closeCompletion : { label: "", type: "type", boost: 2 }); let options = closing.concat(((parent === null || parent === void 0 ? void 0 : parent.children) || (context ? allElements : topElements)).map(e => e.openCompletion)); if (context && (parent === null || parent === void 0 ? void 0 : parent.text.length)) { let openTag = context.firstChild; if (openTag.to > cx.pos - 20 && !/\S/.test(cx.state.sliceDoc(openTag.to, cx.pos))) options = options.concat(parent.text); } return { from, options, validFor: /^<\/?[:\-\.\w\u00b7-\uffff]*$/ }; } else { return null; } }; } /** A language provider based on the [Lezer XML parser](https://github.com/lezer-parser/xml), extended with highlighting and indentation information. */ const xmlLanguage = /*@__PURE__*/LRLanguage.define({ name: "xml", parser: /*@__PURE__*/parser.configure({ props: [ /*@__PURE__*/indentNodeProp.add({ Element(context) { let closed = /^\s*<\//.test(context.textAfter); return context.lineIndent(context.node.from) + (closed ? 0 : context.unit); }, "OpenTag CloseTag SelfClosingTag"(context) { return context.column(context.node.from) + context.unit; } }), /*@__PURE__*/foldNodeProp.add({ Element(subtree) { let first = subtree.firstChild, last = subtree.lastChild; if (!first || first.name != "OpenTag") return null; return { from: first.to, to: last.name == "CloseTag" ? last.from : subtree.to }; } }), /*@__PURE__*/bracketMatchingHandle.add({ "OpenTag CloseTag": node => node.getChild("TagName") }) ] }), languageData: { commentTokens: { block: { open: "" } }, indentOnInput: /^\s*<\/$/ } }); /** XML language support. Includes schema-based autocompletion when configured. */ function xml(conf = {}) { return new LanguageSupport(xmlLanguage, xmlLanguage.data.of({ autocomplete: completeFromSchema(conf.elements || [], conf.attributes || []) })); } export { completeFromSchema, xml, xmlLanguage };