├── .npmignore ├── .gitignore ├── .github └── workflows │ └── dispatch.yml ├── LICENSE ├── package.json ├── src ├── README.md ├── html.ts └── complete.ts ├── test └── test-complete.ts ├── CHANGELOG.md └── README.md /.npmignore: -------------------------------------------------------------------------------- 1 | /src 2 | /test 3 | /node_modules 4 | .tern-* 5 | rollup.config.js 6 | tsconfig.json 7 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | /node_modules 2 | package-lock.json 3 | /dist 4 | /test/*.js 5 | /test/*.d.ts 6 | /test/*.d.ts.map 7 | .tern-* 8 | -------------------------------------------------------------------------------- /.github/workflows/dispatch.yml: -------------------------------------------------------------------------------- 1 | name: Trigger CI 2 | on: push 3 | 4 | jobs: 5 | build: 6 | name: Dispatch to main repo 7 | runs-on: ubuntu-latest 8 | steps: 9 | - name: Emit repository_dispatch 10 | uses: mvasigh/dispatch-action@main 11 | with: 12 | # You should create a personal access token and store it in your repository 13 | token: ${{ secrets.DISPATCH_AUTH }} 14 | repo: dev 15 | owner: codemirror 16 | event_type: push 17 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | MIT License 2 | 3 | Copyright (C) 2018-2021 by Marijn Haverbeke and others 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in 13 | all copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN 21 | THE SOFTWARE. 22 | -------------------------------------------------------------------------------- /package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "@codemirror/lang-html", 3 | "version": "6.4.11", 4 | "description": "HTML language support for the CodeMirror code editor", 5 | "scripts": { 6 | "test": "cm-runtests", 7 | "prepare": "cm-buildhelper src/html.ts" 8 | }, 9 | "keywords": [ 10 | "editor", 11 | "code" 12 | ], 13 | "author": { 14 | "name": "Marijn Haverbeke", 15 | "email": "marijn@haverbeke.berlin", 16 | "url": "http://marijnhaverbeke.nl" 17 | }, 18 | "type": "module", 19 | "main": "dist/index.cjs", 20 | "exports": { 21 | "import": "./dist/index.js", 22 | "require": "./dist/index.cjs" 23 | }, 24 | "types": "dist/index.d.ts", 25 | "module": "dist/index.js", 26 | "sideEffects": false, 27 | "license": "MIT", 28 | "dependencies": { 29 | "@codemirror/autocomplete": "^6.0.0", 30 | "@codemirror/lang-css": "^6.0.0", 31 | "@codemirror/lang-javascript": "^6.0.0", 32 | "@codemirror/language": "^6.4.0", 33 | "@codemirror/state": "^6.0.0", 34 | "@codemirror/view": "^6.17.0", 35 | "@lezer/html": "^1.3.12", 36 | "@lezer/common": "^1.0.0", 37 | "@lezer/css": "^1.1.0" 38 | }, 39 | "devDependencies": { 40 | "@codemirror/buildhelper": "^1.0.0" 41 | }, 42 | "repository": { 43 | "type": "git", 44 | "url": "https://github.com/codemirror/lang-html.git" 45 | } 46 | } 47 | -------------------------------------------------------------------------------- /src/README.md: -------------------------------------------------------------------------------- 1 | 2 | 3 | # @codemirror/lang-html [![NPM version](https://img.shields.io/npm/v/@codemirror/lang-html.svg)](https://www.npmjs.org/package/@codemirror/lang-html) 4 | 5 | [ [**WEBSITE**](https://codemirror.net/) | [**ISSUES**](https://github.com/codemirror/dev/issues) | [**FORUM**](https://discuss.codemirror.net/c/next/) | [**CHANGELOG**](https://github.com/codemirror/lang-html/blob/main/CHANGELOG.md) ] 6 | 7 | This package implements HTML language support for the 8 | [CodeMirror](https://codemirror.net/) code editor. 9 | 10 | The [project page](https://codemirror.net/) has more information, a 11 | number of [examples](https://codemirror.net/examples/) and the 12 | [documentation](https://codemirror.net/docs/). 13 | 14 | This code is released under an 15 | [MIT license](https://github.com/codemirror/lang-html/tree/main/LICENSE). 16 | 17 | We aim to be an inclusive, welcoming community. To make that explicit, 18 | we have a [code of 19 | conduct](http://contributor-covenant.org/version/1/1/0/) that applies 20 | to communication around the project. 21 | 22 | ## Usage 23 | 24 | ```javascript 25 | import {EditorView, basicSetup} from "codemirror" 26 | import {html} from "@codemirror/lang-html" 27 | 28 | const view = new EditorView({ 29 | parent: document.body, 30 | doc: `\nHTML`, 31 | extensions: [basicSetup, html()] 32 | }) 33 | ``` 34 | 35 | ## API Reference 36 | 37 | @html 38 | 39 | @htmlLanguage 40 | 41 | @htmlCompletionSource 42 | 43 | @TagSpec 44 | 45 | @htmlCompletionSourceWith 46 | 47 | @autoCloseTags 48 | -------------------------------------------------------------------------------- /test/test-complete.ts: -------------------------------------------------------------------------------- 1 | import {EditorState} from "@codemirror/state" 2 | import {CompletionContext, CompletionResult, CompletionSource} from "@codemirror/autocomplete" 3 | import {html} from "@codemirror/lang-html" 4 | import ist from "ist" 5 | 6 | function get(doc: string, conf: {explicit?: boolean} = {}) { 7 | let cur = doc.indexOf("|") 8 | doc = doc.slice(0, cur) + doc.slice(cur + 1) 9 | let state = EditorState.create({ 10 | doc, 11 | selection: {anchor: cur}, 12 | extensions: [html()] 13 | }) 14 | let result = state.languageDataAt("autocomplete", cur)[0](new CompletionContext(state, cur, !!conf.explicit)) 15 | return result as CompletionResult | null 16 | } 17 | 18 | describe("HTML completion", () => { 19 | it("completes tag names", () => { 20 | let c = get("<|")!.options 21 | ist(c.length, 100, ">") 22 | ist(!c.some(o => /\W/.test(o.label))) 23 | }) 24 | 25 | it("doesn't complete from nothing unless explicit", () => { 26 | ist(!get("|")) 27 | }) 28 | 29 | it("completes at top level", () => { 30 | let c = get("|", {explicit: true})!.options 31 | ist(c.length, 100, ">") 32 | ist(c.every(o => /^<\w+$/.test(o.label) && o.type == "type")) 33 | }) 34 | 35 | it("completes inside an element", () => { 36 | let c = get("|", {explicit: true})!.options 37 | ist(c.length, 100, ">") 38 | ist(c.some(o => o.label == "")) 39 | ist(c.every(o => /^<(\/body>|\w+)$/.test(o.label))) 40 | }) 41 | 42 | it("completes attribute names", () => { 43 | let c = get(" o.type == "property")) 46 | }) 47 | 48 | it("completes attribute names explicitly", () => { 49 | let c = get(" o.type == "property")) 52 | }) 53 | 54 | it("completes attribute values", () => { 55 | let c = get("
o.label).sort().join(","), "delete,get,post,put") 57 | }) 58 | 59 | it("completes the 2nd attribute's values", () => { 60 | let c = get(" o.label).sort().join(","), "delete,get,post,put") 62 | }) 63 | 64 | it("keeps quotes for attribute values", () => { 65 | let c = get(' { 70 | let c = get(' o.apply).sort().join(","), "delete,get,post,put") 73 | }) 74 | 75 | it("can handle single quotes", () => { 76 | let c = get(" o.apply).sort().join(","), "delete,get,post,put") 79 | }) 80 | 81 | it("completes close tags", () => { 82 | let c = get("") 85 | }) 86 | 87 | it("completes partial close tags", () => { 88 | let c = get("") 91 | }) 92 | 93 | it("only completes close tags that haven't already been closed", () => { 94 | let c = get("

")!.options 95 | ist(c.length, 2) 96 | ist(c.map(o => o.apply).join(","), "p>,div>") 97 | }) 98 | 99 | it("includes close tag in completion after less-than", () => { 100 | let c = get("<|")!.options 101 | ist(c.some(o => o.apply == "/html>")) 102 | }) 103 | 104 | it("completes allowed children", () => { 105 | let c = get("|", {explicit: true})!.options 106 | ist(!c.some(o => /\

{ 110 | let c = get("<|")!.options 111 | ist(!c.some(o => /^div/.test(o.label))) 112 | }) 113 | }) 114 | -------------------------------------------------------------------------------- /CHANGELOG.md: -------------------------------------------------------------------------------- 1 | ## 6.4.11 (2025-10-02) 2 | 3 | ### Bug fixes 4 | 5 | Adjust autocompletion to work with @lezer/html's improved handling of `<` characters without tag name after them. 6 | 7 | ## 6.4.10 (2025-09-11) 8 | 9 | ### Bug fixes 10 | 11 | Don't include period characters in the language's word characters. 12 | 13 | ## 6.4.9 (2024-04-12) 14 | 15 | ### Bug fixes 16 | 17 | Fix a bug in `autoCloseTags` that made tags not close when typing > after an attribute. 18 | 19 | ## 6.4.8 (2024-01-23) 20 | 21 | ### Bug fixes 22 | 23 | Complete attribute names after whitespace in a tag even when completion isn't explicitly triggered. 24 | 25 | ## 6.4.7 (2023-11-27) 26 | 27 | ### Bug fixes 28 | 29 | Parse `script` tags with `application/json` type as JSON syntax. 30 | 31 | ## 6.4.6 (2023-08-28) 32 | 33 | ### Bug fixes 34 | 35 | `autoCloseTags` now generates two separate transactions, so that the completion can be undone separately. 36 | 37 | Add highlighting for the content of `