pax_global_header00006660000000000000000000000064145720426160014521gustar00rootroot0000000000000052 comment=6fb53b56f59167def8d13f6838113c7f09c70984 lang-xml-6.1.0/000077500000000000000000000000001457204261600132445ustar00rootroot00000000000000lang-xml-6.1.0/.github/000077500000000000000000000000001457204261600146045ustar00rootroot00000000000000lang-xml-6.1.0/.github/workflows/000077500000000000000000000000001457204261600166415ustar00rootroot00000000000000lang-xml-6.1.0/.github/workflows/dispatch.yml000066400000000000000000000006371457204261600211710ustar00rootroot00000000000000name: 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-xml-6.1.0/.gitignore000066400000000000000000000001271457204261600152340ustar00rootroot00000000000000/node_modules package-lock.json /dist /test/*.js /test/*.d.ts /test/*.d.ts.map .tern-* lang-xml-6.1.0/.npmignore000066400000000000000000000001001457204261600152320ustar00rootroot00000000000000/src /test /node_modules .tern-* rollup.config.js tsconfig.json lang-xml-6.1.0/CHANGELOG.md000066400000000000000000000022141457204261600150540ustar00rootroot00000000000000## 6.1.0 (2024-03-06) ### New features Add an `autoCloseTags` extension that closes tags on typing > or /. Enable it by default in the `xml()` language support. ## 6.0.2 (2023-01-12) ### Bug fixes Use only the tag name for matching of opening and closing tags. ## 6.0.1 (2022-10-24) ### Bug fixes Make sure the language object has a name. ## 6.0.0 (2022-06-08) ### Breaking changes Update dependencies to 6.0.0 ## 0.20.0 (2022-04-20) ### Breaking changes Update dependencies to 0.20.0 ## 0.19.2 (2021-09-23) ### New features Use more specific highlighting tags for attribute names and values. ## 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.0 (2021-03-03) ### Breaking changes Update dependencies to 0.18. ## 0.17.2 (2021-02-12) ### Bug fixes Export configuration types and `completeFromSchema` function (which were previously accidentally unexported). ## 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-xml-6.1.0/LICENSE000066400000000000000000000021361457204261600142530ustar00rootroot00000000000000MIT 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-xml-6.1.0/README.md000066400000000000000000000206301457204261600145240ustar00rootroot00000000000000 # @codemirror/lang-xml [![NPM version](https://img.shields.io/npm/v/@codemirror/lang-xml.svg)](https://www.npmjs.org/package/@codemirror/lang-xml) [ [**WEBSITE**](https://codemirror.net/) | [**ISSUES**](https://github.com/codemirror/dev/issues) | [**FORUM**](https://discuss.codemirror.net/c/next/) | [**CHANGELOG**](https://github.com/codemirror/lang-xml/blob/main/CHANGELOG.md) ] This package implements XML 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-xml/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
xml(conf⁠?: Object = {}) → LanguageSupport

XML language support. Includes schema-based autocompletion when configured.

conf
elements⁠?: readonly ElementSpec[]

Provide a schema to create completions from.

attributes⁠?: readonly AttrSpec[]

Supporting attribute descriptions for the schema specified in elements.

autoCloseTags⁠?: boolean

Determines whether autoCloseTags is included in the support extensions. Defaults to true.

xmlLanguage: LRLanguage

A language provider based on the Lezer XML parser, extended with highlighting and indentation information.

interface ElementSpec

Describes an element in your XML document schema.

name: string

The element name.

children⁠?: readonly string[]

Allowed children in this element. When not given, all elements are allowed inside it.

textContent⁠?: readonly string[]

When given, allows users to complete the given content strings as plain text when at the start of the element.

top⁠?: boolean

Whether this element may appear at the top of the document.

attributes⁠?: readonly (string | AttrSpec)[]

Allowed attributes in this element. Strings refer to attributes specified in XMLConfig.attrs, but you can also provide one-off attribute specs. Attributes marked as global are allowed in every element, and don't have to be mentioned here.

completion⁠?: Partial<Completion>

Can be provided to add extra fields to the completion object created for this element.

interface AttrSpec

Describes an attribute in your XML schema.

name: string

The attribute name.

values⁠?: readonly (string | Completion)[]

Pre-defined values to complete for this attribute.

global⁠?: boolean

When true, this attribute can be added to all elements.

completion⁠?: Partial<Completion>

Provides extra fields to the completion object created for this element

completeFromSchema(eltSpecs: readonly ElementSpec[], attrSpecs: readonly AttrSpec[]) → CompletionSource

Create a completion source for the given schema.

autoCloseTags: Extension

Extension that will automatically insert close tags when a > or / is typed.

lang-xml-6.1.0/package.json000066400000000000000000000020111457204261600155240ustar00rootroot00000000000000{ "name": "@codemirror/lang-xml", "version": "6.1.0", "description": "XML language support for the CodeMirror code editor", "scripts": { "test": "cm-runtests", "prepare": "cm-buildhelper src/xml.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.0.0", "@codemirror/language": "^6.4.0", "@codemirror/state": "^6.0.0", "@codemirror/view": "^6.0.0", "@lezer/common": "^1.0.0", "@lezer/xml": "^1.0.0" }, "devDependencies": { "@codemirror/buildhelper": "^1.0.0" }, "repository": { "type": "git", "url": "https://github.com/codemirror/lang-xml.git" } } lang-xml-6.1.0/src/000077500000000000000000000000001457204261600140335ustar00rootroot00000000000000lang-xml-6.1.0/src/README.md000066400000000000000000000021501457204261600153100ustar00rootroot00000000000000 # @codemirror/lang-xml [![NPM version](https://img.shields.io/npm/v/@codemirror/lang-xml.svg)](https://www.npmjs.org/package/@codemirror/lang-xml) [ [**WEBSITE**](https://codemirror.net/) | [**ISSUES**](https://github.com/codemirror/dev/issues) | [**FORUM**](https://discuss.codemirror.net/c/next/) | [**CHANGELOG**](https://github.com/codemirror/lang-xml/blob/main/CHANGELOG.md) ] This package implements XML 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-xml/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 @xml @xmlLanguage @ElementSpec @AttrSpec @completeFromSchema @autoCloseTags lang-xml-6.1.0/src/complete.ts000066400000000000000000000225341457204261600162210ustar00rootroot00000000000000import {Completion, CompletionSource} from "@codemirror/autocomplete" import {EditorState, Text} from "@codemirror/state" import {syntaxTree} from "@codemirror/language" import {SyntaxNode} from "@lezer/common" /// Describes an element in your XML document schema. export interface ElementSpec { /// The element name. name: string, /// Allowed children in this element. When not given, all elements /// are allowed inside it. children?: readonly string[], /// When given, allows users to complete the given content strings /// as plain text when at the start of the element. textContent?: readonly string[], /// Whether this element may appear at the top of the document. top?: boolean, /// Allowed attributes in this element. Strings refer to attributes /// specified in [`XMLConfig.attrs`](#lang-xml.XMLConfig.attrs), but /// you can also provide one-off [attribute /// specs](#lang-xml.AttrSpec). Attributes marked as /// [`global`](#lang-xml.AttrSpec.global) are allowed in every /// element, and don't have to be mentioned here. attributes?: readonly (string | AttrSpec)[], /// Can be provided to add extra fields to the /// [completion](#autocompletion.Completion) object created for this /// element. completion?: Partial } /// Describes an attribute in your XML schema. export interface AttrSpec { /// The attribute name. name: string, /// Pre-defined values to complete for this attribute. values?: readonly (string | Completion)[], /// When `true`, this attribute can be added to all elements. global?: boolean, /// Provides extra fields to the /// [completion](#autocompletion.Completion) object created for this /// element completion?: Partial } function tagName(doc: Text, tag: SyntaxNode | null) { let name = tag && tag.getChild("TagName") return name ? doc.sliceString(name.from, name.to) : "" } function elementName(doc: Text, tree: SyntaxNode | null) { let tag = tree && tree.firstChild return !tag || tag.name != "OpenTag" ? "" : tagName(doc, tag) } function attrName(doc: Text, tag: SyntaxNode | null, pos: number) { 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: SyntaxNode | null) { for (let cur = tree && tree.parent; cur; cur = cur.parent) if (cur.name == "Element") return cur return null } type Location = { type: "openTag" | "closeTag" | "attrValue" | "attrName" | "tag", from: number, context: SyntaxNode | null } | null function findLocation(state: EditorState, pos: number): Location { 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?.name == "StartTag") return {type: "openTag", from: pos, context: findParentElement(elt)} if (before?.name == "StartCloseTag" && before.to <= pos) return {type: "closeTag", from: pos, context: elt} if (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 && !at.lastChild?.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 { name: string completion: Completion openCompletion: Completion closeCompletion: Completion closeNameCompletion: Completion children: Element[] = [] text: Completion[] constructor(spec: ElementSpec, readonly attrs: readonly Completion[], readonly attrValues: {[name: string]: readonly Completion[]}) { this.name = spec.name this.completion = {type: "type", ...spec.completion || {}, label: this.name} this.openCompletion = {...this.completion, label: "<" + this.name} this.closeCompletion = {...this.completion, label: "", boost: 2} this.closeNameCompletion = {...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: AttrSpec): Completion { return {type: "property", ...spec.completion || {}, label: spec.name} } function valueCompletion(spec: string | Completion): Completion { return typeof spec == "string" ? {label: `"${spec}"`, type: "constant"} : /^"/.test(spec.label) ? spec : {...spec, label: `"${spec.label}"`} } /// Create a completion source for the given schema. export function completeFromSchema(eltSpecs: readonly ElementSpec[], attrSpecs: readonly AttrSpec[]): CompletionSource { let allAttrs: Completion[] = [], globalAttrs: Completion[] = [] let attrValues: {[name: string]: readonly Completion[]} = 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: Element[] = [], topElements: Element[] = [] let byName: {[name: string]: Element} = 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 => { 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?.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: [byName[parentName]?.closeNameCompletion || {label: parentName + ">", type: "type"}], validFor: Identifier } : null } else if (type == "attrName") { let parent = byName[tagName(doc, context)] return { from, options: 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?.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?.children || (context ? allElements : topElements)).map(e => e.openCompletion)) if (context && 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 } } } lang-xml-6.1.0/src/xml.ts000066400000000000000000000103311457204261600152010ustar00rootroot00000000000000import {parser} from "@lezer/xml" import {SyntaxNode} from "@lezer/common" import {indentNodeProp, foldNodeProp, LRLanguage, LanguageSupport, bracketMatchingHandle, syntaxTree} from "@codemirror/language" import {EditorSelection, Text} from "@codemirror/state" import {EditorView} from "@codemirror/view" import {ElementSpec, AttrSpec, completeFromSchema} from "./complete" export {ElementSpec, AttrSpec, completeFromSchema} /// A language provider based on the [Lezer XML /// parser](https://github.com/lezer-parser/xml), extended with /// highlighting and indentation information. export const xmlLanguage = LRLanguage.define({ name: "xml", parser: parser.configure({ props: [ 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 } }), 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} } }), bracketMatchingHandle.add({ "OpenTag CloseTag": node => node.getChild("TagName") }) ] }), languageData: { commentTokens: {block: {open: ""}}, indentOnInput: /^\s*<\/$/ } }) type XMLConfig = { /// Provide a schema to create completions from. elements?: readonly ElementSpec[] /// Supporting attribute descriptions for the schema specified in /// [`elements`](#lang-xml.xml^conf.elements). attributes?: readonly AttrSpec[] /// Determines whether [`autoCloseTags`](#lang-xml.autoCloseTags) /// is included in the support extensions. Defaults to true. autoCloseTags?: boolean } /// XML language support. Includes schema-based autocompletion when /// configured. export function xml(conf: XMLConfig = {}) { let support = [xmlLanguage.data.of({ autocomplete: completeFromSchema(conf.elements || [], conf.attributes || []) })] if (conf.autoCloseTags !== false) support.push(autoCloseTags) return new LanguageSupport(xmlLanguage, support) } function elementName(doc: Text, tree: SyntaxNode | null | undefined, max = doc.length) { if (!tree) return "" let tag = tree.firstChild let name = tag && tag.getChild("TagName") return name ? doc.sliceString(name.from, Math.min(name.to, max)) : "" } /// Extension that will automatically insert close tags when a `>` or /// `/` is typed. export const autoCloseTags = EditorView.inputHandler.of((view, from, to, text, insertTransaction) => { if (view.composing || view.state.readOnly || from != to || (text != ">" && text != "/") || !xmlLanguage.isActiveAt(view.state, from, -1)) return false let base = insertTransaction(), {state} = base let closeTags = state.changeByRange(range => { let {head} = range let didType = state.doc.sliceString(head - 1, head) == text let after = syntaxTree(state).resolveInner(head, -1), name if (didType && text == ">" && after.name == "EndTag") { let tag = after.parent! if (tag.parent?.lastChild?.name != "CloseTag" && (name = elementName(state.doc, tag.parent, head))) { let to = head + (state.doc.sliceString(head, head + 1) === ">" ? 1 : 0) let insert = `` return {range, changes: {from: head, to, insert}} } } else if (didType && text == "/" && after.name == "StartCloseTag") { let base = after.parent! if (after.from == head - 2 && base.lastChild?.name != "CloseTag" && (name = elementName(state.doc, base, head))) { let to = head + (state.doc.sliceString(head, head + 1) === ">" ? 1 : 0) let insert = `${name}>` return { range: EditorSelection.cursor(head + insert.length, -1), changes: {from: head, to, insert} } } } return {range} }) if (closeTags.changes.empty) return false view.dispatch([ base, state.update(closeTags, { userEvent: "input.complete", scrollIntoView: true }) ]) return true }) lang-xml-6.1.0/test/000077500000000000000000000000001457204261600142235ustar00rootroot00000000000000lang-xml-6.1.0/test/test-complete.ts000066400000000000000000000070311457204261600173610ustar00rootroot00000000000000import {EditorState} from "@codemirror/state" import {CompletionContext, CompletionResult, CompletionSource} from "@codemirror/autocomplete" import {xml} from "@codemirror/lang-xml" import ist from "ist" function get(doc: string, config?: Parameters[0], explicit = true) { let cur = doc.indexOf("|") doc = doc.slice(0, cur) + doc.slice(cur + 1) let state = EditorState.create({doc, selection: {anchor: cur}, extensions: [xml(config)]}) let result = state.languageDataAt("autocomplete", cur)[0](new CompletionContext(state, cur, explicit)) return result as CompletionResult | null } function str(result: CompletionResult | null) { return !result ? "" : result.options.slice() .sort((a, b) => (b.boost || 0) - (a.boost || 0) || (a.label < b.label ? -1 : 1)) .map(o => o.label) .join(", ") } let schema1 = { elements: [ {name: "doc", top: true, attributes: ["attr1", "attr2", {name: "attr3", values: ["x", "y"], completion: {type: "keyword"}}], children: ["head", "body"], completion: {type: "keyword"}}, {name: "head", attributes: ["attr2"]}, {name: "body", children: []} ], attributes: [ {name: "attr2", values: ["one", "two"]}, {name: "attrglobal", global: true} ] } describe("XML completion", () => { it("completes closing tags", () => { ist(str(get("|")), "") }) it("completes attributes after a tag name", () => { ist(str(get(" { ist(str(get("", schema1)), "attr1, attr2, attr3, attrglobal") }) it("completes attributes after another attribute", () => { ist(str(get("", schema1)), "attr1, attr2, attr3, attrglobal") }) it("completes attribute values", () => { ist(str(get("", schema1)), '"one", "two"') }) it("completes partial attribute values", () => { ist(str(get("", schema1)), '"one", "two"') }) it("completes locally defined attribute values", () => { ist(str(get(" { ist(str(get(" { ist(str(get("", schema1)), "body, head") }) it("completes tag names after a tag start", () => { ist(str(get("<|", schema1)), "body, head") }) it("completes closing tag names", () => { ist(str(get("") ist(str(get("") }) it("completes tags when in text", () => { ist(str(get("foo|bar", schema1)), ", { ist(str(get("|", schema1)), " { ist(str(get("|", schema1, false)), "") }) it("can attach extra info to completions", () => { ist(get(" c.label == "attr3")[0].type, "keyword") }) it("completes the top element", () => { ist(str(get("|", schema1)), " { let schema = { elements: [{name: "top", textContent: ["true", "false"], children: []}] } ist(str(get("|", schema)), ", false, true") ist(str(get("a|", schema)), "") }) })