├── .gitattributes
├── .github
├── vscode-autoformat.gif
├── vscode.png
└── workflows
│ └── ci.yml
├── .gitignore
├── LICENSE
├── README.md
├── build.zig
├── build.zig.zon
├── editors
├── emacs
│ └── README.md
└── vscode
│ ├── .gitignore
│ ├── .vscode
│ ├── extensions.json
│ ├── launch.json
│ ├── settings.json
│ └── tasks.json
│ ├── .vscodeignore
│ ├── CHANGELOG.md
│ ├── LICENSE
│ ├── README.md
│ ├── package-lock.json
│ ├── package.json
│ ├── src
│ ├── extension.ts
│ ├── formatter.ts
│ └── util.ts
│ ├── superhtml.language-configuration.json
│ ├── syntaxes
│ ├── superhtml-derivative.tmLanguage.json
│ └── superhtml.tmLanguage.json
│ ├── tsconfig.json
│ └── vscode.png
├── src
├── Ast.zig
├── cli.zig
├── cli
│ ├── check.zig
│ ├── fmt.zig
│ ├── interface.zig
│ ├── logging.zig
│ ├── lsp.zig
│ └── lsp
│ │ ├── Document.zig
│ │ └── logic.zig
├── css.zig
├── css
│ ├── Ast.zig
│ └── Tokenizer.zig
├── errors.zig
├── fuzz.zig
├── fuzz
│ ├── afl.zig
│ ├── astgen.zig
│ └── cases
│ │ ├── 12.html
│ │ ├── 2.html
│ │ ├── 3-01.html
│ │ ├── 3.html
│ │ ├── 4-01.html
│ │ ├── 5-01.html
│ │ ├── 6-01.html
│ │ ├── 6-02.html
│ │ ├── 77.html
│ │ ├── round2
│ │ ├── 2.html
│ │ └── 3.html
│ │ └── round3
│ │ └── 2.html
├── html.zig
├── html
│ ├── Ast.zig
│ ├── Tokenizer.zig
│ └── named_character_references.zig
├── root.zig
├── sitter.zig
├── template.zig
├── vm.zig
└── wasm.zig
└── tree-sitter-superhtml
├── .editorconfig
├── .gitattributes
├── .gitignore
├── .npmignore
├── Cargo.toml
├── LICENSE
├── Makefile
├── Package.swift
├── README.md
├── binding.gyp
├── bindings
├── c
│ ├── tree-sitter-html.h
│ └── tree-sitter-html.pc.in
├── go
│ ├── binding.go
│ ├── binding_test.go
│ └── go.mod
├── node
│ ├── binding.cc
│ ├── index.d.ts
│ └── index.js
├── python
│ └── tree_sitter_html
│ │ ├── __init__.py
│ │ ├── __init__.pyi
│ │ ├── binding.c
│ │ └── py.typed
├── rust
│ ├── build.rs
│ └── lib.rs
└── swift
│ └── TreeSitterHTML
│ └── html.h
├── examples
├── deeply-nested-custom.html
└── deeply-nested.html
├── grammar.js
├── package-lock.json
├── package.json
├── pyproject.toml
├── queries
├── highlights.scm
└── injections.scm
├── setup.py
├── src
├── grammar.json
├── node-types.json
├── parser.c
├── scanner.c
├── tag.h
└── tree_sitter
│ ├── alloc.h
│ ├── array.h
│ └── parser.h
└── test
├── corpus
└── main.txt
└── highlight
├── attributes.html
├── doctype.html
├── erroneous.html
└── self-closing.html
/.gitattributes:
--------------------------------------------------------------------------------
1 | *.zig text eol=lf
2 | *.zon text eol=lf
--------------------------------------------------------------------------------
/.github/vscode-autoformat.gif:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/kristoff-it/superhtml/13f5a2221cb748bbe50ad702e89362afd5b925a7/.github/vscode-autoformat.gif
--------------------------------------------------------------------------------
/.github/vscode.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/kristoff-it/superhtml/13f5a2221cb748bbe50ad702e89362afd5b925a7/.github/vscode.png
--------------------------------------------------------------------------------
/.github/workflows/ci.yml:
--------------------------------------------------------------------------------
1 | name: CI
2 | on: push
3 | jobs:
4 | deploy:
5 | runs-on: ubuntu-latest
6 | steps:
7 | - uses: actions/checkout@v4
8 | with:
9 | fetch-depth: 0 # Change if you need git info
10 |
11 | - name: Setup Zig
12 | uses: mlugg/setup-zig@v1
13 | with:
14 | version: 0.14.0
15 |
16 | - name: Test
17 | run: zig build test
18 |
19 |
--------------------------------------------------------------------------------
/.gitignore:
--------------------------------------------------------------------------------
1 | .zig-cache/
2 | zig-cache/
3 | zig-out/
4 | release/
5 | scratch
6 |
7 |
--------------------------------------------------------------------------------
/LICENSE:
--------------------------------------------------------------------------------
1 | MIT License
2 |
3 | Copyright (c) 2024 Loris Cro
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 all
13 | 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 THE
21 | SOFTWARE.
22 |
--------------------------------------------------------------------------------
/README.md:
--------------------------------------------------------------------------------
1 | # SuperHTML
2 | HTML Language Server and Templating Language Library
3 |
4 |
5 | ## HTML Language Server
6 | The Super CLI Tool offers **syntax checking** and **autoformatting** features for HTML files.
7 |
8 | The tool can be used either directly (for example by running it on save), or through a LSP client implementation.
9 |
10 | ```
11 | $ superhtml
12 | Usage: superhtml COMMAND [OPTIONS]
13 |
14 | Commands:
15 | check Check documents for syntax errors
16 | interface, i Print a SuperHTML template's interface
17 | fmt Format documents
18 | lsp Start the Super LSP
19 | help Show this menu and exit
20 | version Print Super's version and exit
21 |
22 | General Options:
23 | --help, -h Print command specific usage
24 | ```
25 |
26 | >[!WARNING]
27 | >SuperHTML currently only supports UTF8-encoded HTML and assumes HTML5 compliance (e.g. doesn't support XHTML, regardless of what you define the doctype to be).
28 |
29 | ### Diagnostics
30 |
31 | 
32 |
33 | This language server is stricter than the HTML spec whenever it would prevent potential human errors from being reported.
34 |
35 |
36 | As an example, HTML allows for closing some tags implicitly. For example the following snippet is correct HTML.
37 |
38 | ```html
39 |
43 | ```
44 |
45 | This will still be reported as an error by SuperHTML because otherwise the following snippet would have to be considered correct (while it's most probably a typo):
46 |
47 | ```html
48 | item
49 | ```
50 |
51 | ### Autoformatting
52 | 
53 |
54 | The autoformatter has two main ways of interacting with it in order to request for horizontal / vertical alignment.
55 |
56 | 1. Adding / removing whitespace between the **start tag** of an element and its content.
57 | 2. Adding / removing whitespace between the **last attribute** of a start tag and the closing `>`.
58 |
59 |
60 | #### Example of rule #1
61 | Before:
62 | ```html
63 |
64 | ```
65 |
66 | After:
67 | ```html
68 |
71 | ```
72 |
73 | ##### Reverse
74 |
75 | Before:
76 | ```html
77 |
79 | ```
80 |
81 | After:
82 | ```html
83 |
84 | ```
85 |
86 | #### Example of rule #2
87 | Before:
88 | ```html
89 |
90 | Foo
91 |
92 | ```
93 |
94 | After:
95 | ```html
96 |
100 | Foo
101 |
102 | ```
103 |
104 | ##### Reverse
105 |
106 | Before:
107 | ```html
108 |
111 | Foo
112 |
113 | ```
114 |
115 | After:
116 | ```html
117 |
118 | Foo
119 |
120 | ```
121 |
122 | ### Editor support
123 | #### VSCode
124 | Install the [Super HTML VSCode extension](https://marketplace.visualstudio.com/items?itemName=LorisCro.super).
125 |
126 | #### Neovim
127 | 1. Download a prebuilt version of `superhtml` from the Releases section (or build it yourself).
128 | 2. Put `superhtml` in your `PATH`.
129 | 3. Configure `superhtml` for your chosen lsp
130 |
131 | - ##### [Neovim Built-In](https://neovim.io/doc/user/lsp.html#vim.lsp.start())
132 |
133 | ```lua
134 | vim.api.nvim_create_autocmd("Filetype", {
135 | pattern = { "html", "shtml", "htm" },
136 | callback = function()
137 | vim.lsp.start({
138 | name = "superhtml",
139 | cmd = { "superhtml", "lsp" },
140 | root_dir = vim.fs.dirname(vim.fs.find({".git"}, { upward = true })[1])
141 | })
142 | end
143 | })
144 | ```
145 |
146 | - ##### [LspZero](https://github.com/VonHeikemen/lsp-zero.nvim)
147 |
148 | ```lua
149 | local lsp = require("lsp-zero")
150 |
151 | require('lspconfig.configs').superhtml = {
152 | default_config = {
153 | name = 'superhtml',
154 | cmd = {'superhtml', 'lsp'},
155 | filetypes = {'html', 'shtml', 'htm'},
156 | root_dir = require('lspconfig.util').root_pattern('.git')
157 | }
158 | }
159 |
160 | lsp.configure('superhtml', {force_setup = true})
161 | ```
162 |
163 | #### Helix
164 |
165 | In versions later than `24.07` `superhtml` is supported out of the box, simply add executable to your `PATH`.
166 |
167 | For `24.07` and earlier, add to your `.config/helix/languages.toml`:
168 | ```toml
169 | [language-server.superhtml-lsp]
170 | command = "superhtml"
171 | args = ["lsp"]
172 |
173 | [[language]]
174 | name = "html"
175 | scope = "source.html"
176 | roots = []
177 | file-types = ["html"]
178 | language-servers = [ "superhtml-lsp" ]
179 | ```
180 | See https://helix-editor.com for more information on how to add new language servers.
181 |
182 | #### [Flow Control](https://github.com/neurocyte/flow)
183 | Already defaults to using SuperHTML, just add the executable to your `PATH`.
184 |
185 | #### Vim
186 | Vim should be able to parse the errors that `superhtml check [PATH]`. This
187 | means that you can use `:make` and the quickfix window to check for syntax
188 | errors.
189 |
190 | Set the `makeprg` to the following in your .vimrc:
191 | ```
192 | " for any html file, a :make action will populate the quickfix menu
193 | autocmd filetype html setlocal makeprg=superhtml\ check\ %
194 | " if you want to use gq{motion} to format sections or the whole buffer (with gggqG)
195 | autocmd filetype html setlocal formatprg=superhtml\ fmt\ --stdin
196 | ```
197 |
198 | #### Zed
199 |
200 | See [WeetHet/superhtml-zed](https://github.com/WeetHet/superhtml-zed).
201 |
202 | #### Other editors
203 | Follow your editor specific instructions on how to define a new Language Server for a given language / file format.
204 |
205 | *(Also feel free to contribute more specific instructions to this readme / add files under the `editors/` subdirectory).*
206 |
207 | ## Templating Language Library
208 | SuperHTML is also a HTML templating language. More on that soon.
209 |
210 | ## Contributing
211 | SuperHTML tracks the latest Zig release (0.13.0 at the moment of writing).
212 |
213 | ### Contributing to the HTML parser & LSP
214 | Contributing to the HTML parser and LSP doesn't require you to be familiar with the templating language, basically limiting the scope of what you have to worry about to:
215 |
216 | - `src/cli.zig`
217 | - `src/cli/`
218 | - `src/html/`
219 |
220 | In particular, you will care about `src/html/Tokenizer.zig` and `src/html/Ast.zig`.
221 |
222 | You can run `zig test src/html/Ast.zig` to run parser unit tests without needing to worry the rest of the project.
223 |
224 | Running `zig build` will compile the Super CLI tool, allowing you to also then test the LSP behavior directly from your favorite editor.
225 |
226 | The LSP will log in your cache directory so you can `tail -f ~/.cache/super/super.log` to see what happens with the LSP.
227 |
--------------------------------------------------------------------------------
/build.zig.zon:
--------------------------------------------------------------------------------
1 | .{
2 | .name = .superhtml,
3 | .version = "0.4.0",
4 | .fingerprint = 0xc5e9aede3c1db363,
5 | .minimum_zig_version = "0.14.0-dev.3451+d8d2aa9af",
6 | .dependencies = .{
7 | .lsp_kit = .{
8 | .url = "git+https://github.com/kristoff-it/zig-lsp-kit#87ff3d537a0c852442e180137d9557711963802c",
9 | .hash = "lsp_kit-0.1.0-hAAxO9S9AADv_5D0iplASFtNCFXAPk54M0u-3jj2MRFk",
10 | },
11 | .afl_kit = .{
12 | .url = "git+https://github.com/kristoff-it/zig-afl-kit?ref=zig-0.14.0#1e9fcaa08361307d16a9bde82b4a7fd4560ce502",
13 | .hash = "afl_kit-0.1.0-uhOgGDkdAAALG16McR2B4b8QwRUQ2sa9XdgDTFXRWQTY",
14 | .lazy = true,
15 | },
16 | .known_folders = .{
17 | .url = "git+https://github.com/ziglibs/known-folders#aa24df42183ad415d10bc0a33e6238c437fc0f59",
18 | .hash = "known_folders-0.0.0-Fy-PJtLDAADGDOwYwMkVydMSTp_aN-nfjCZw6qPQ2ECL",
19 | },
20 | .tracy = .{
21 | .url = "git+https://github.com/kristoff-it/tracy#67d2d89e351048c76fc6d161e0ac09d8a831dc60",
22 | .hash = "tracy-0.0.0-4Xw-1pwwAABTfMgoDP1unCbZDZhJEfict7XCBGF6IdIn",
23 | },
24 | .scripty = .{
25 | .url = "git+https://github.com/kristoff-it/scripty#57056571abcc6fe69fcb171c10b0c9e5962f53b0",
26 | .hash = "scripty-0.1.0-LKK5O9jDAADwZbkwkzYcmtTD3xIStr1SNYWL0kcGf8sk",
27 | },
28 | },
29 | .paths = .{
30 | "LICENSE",
31 | "build.zig",
32 | "build.zig.zon",
33 | "src",
34 | },
35 | }
36 |
--------------------------------------------------------------------------------
/editors/emacs/README.md:
--------------------------------------------------------------------------------
1 | # Adding SuperHTML to Emacs via Eglot
2 |
3 | With `eglot` being included with the core Emacs distribution since
4 | version 29, you can add various language servers, including SuperHTML,
5 | to Emacs fairly simply. Just ensure that `superhtml` is somewhere in
6 | your `$PATH` and you should be able to use one of the forms below with
7 | minimal modification.
8 |
9 | ## With `use-package`
10 |
11 | ```elisp
12 | (use-package eglot
13 | :defer t
14 | :hook ((web-mode . eglot-ensure)
15 | ;; Add more modes as needed
16 | )
17 | :config
18 | ;; ...
19 | (add-to-list 'eglot-server-programs '((web-mode :language-id "html") . ("superhtml" "lsp"))))
20 | ```
21 |
22 | ## Without `use-package`
23 |
24 | ```elisp
25 | (require 'eglot)
26 | (with-eval-after-load 'eglot
27 | (add-to-list 'eglot-server-programs
28 | `((web-mode :language-id "html") . ("superhtml" "lsp"))))
29 | ```
30 |
31 | You can modify the `superhtml` path here as well. If you're not using
32 | `web-mode` then you'll also want to substitute your preferred
33 | mode. The `:language-id` property ensures that HTML is the
34 | content-type passed to the language server, as `eglot` will send the
35 | mode name (minus `-mode`) by default.
36 |
--------------------------------------------------------------------------------
/editors/vscode/.gitignore:
--------------------------------------------------------------------------------
1 | node_modules/
2 | out/
3 | wasm/
4 | *.vsix
5 |
--------------------------------------------------------------------------------
/editors/vscode/.vscode/extensions.json:
--------------------------------------------------------------------------------
1 | {
2 | "recommendations": [
3 | "dbaeumer.vscode-eslint"
4 | ]
5 | }
--------------------------------------------------------------------------------
/editors/vscode/.vscode/launch.json:
--------------------------------------------------------------------------------
1 | // A launch configuration that launches the extension inside a new window
2 | // Use IntelliSense to learn about possible attributes.
3 | // Hover to view descriptions of existing attributes.
4 | // For more information, visit: https://go.microsoft.com/fwlink/?linkid=830387
5 | {
6 | "version": "0.2.0",
7 | "configurations": [
8 | {
9 | "name": "Extension",
10 | "type": "extensionHost",
11 | "request": "launch",
12 | "runtimeExecutable": "${execPath}",
13 | "args": [
14 | "--extensionDevelopmentPath=${workspaceFolder}"
15 | ],
16 | "outFiles": [
17 | "${workspaceFolder}/out/**/*.js"
18 | ],
19 | "preLaunchTask": "Build Extension"
20 | }
21 | ]
22 | }
--------------------------------------------------------------------------------
/editors/vscode/.vscode/settings.json:
--------------------------------------------------------------------------------
1 | // Place your settings in this file to overwrite default and user settings.
2 | {
3 | "files.exclude": {
4 | "out": false // set this to true to hide the "out" folder with the compiled JS files
5 | },
6 | "search.exclude": {
7 | "out": true // set this to false to include "out" folder in search results
8 | },
9 | // Turn off tsc task auto detection since we have the necessary tasks as npm scripts
10 | "typescript.tsc.autoDetect": "off",
11 | }
--------------------------------------------------------------------------------
/editors/vscode/.vscode/tasks.json:
--------------------------------------------------------------------------------
1 | // See https://go.microsoft.com/fwlink/?LinkId=733558
2 | // for the documentation about the tasks.json format
3 | {
4 | "version": "2.0.0",
5 | "tasks": [
6 | {
7 | "label": "Build Extension in Background",
8 | "group": "build",
9 | "type": "npm",
10 | "script": "watch",
11 | "problemMatcher": {
12 | "base": "$tsc-watch"
13 | },
14 | "isBackground": true
15 | },
16 | {
17 | "label": "Build Extension",
18 | "group": "build",
19 | "type": "npm",
20 | "script": "build",
21 | "problemMatcher": {
22 | "base": "$tsc"
23 | }
24 | }
25 | ]
26 | }
--------------------------------------------------------------------------------
/editors/vscode/.vscodeignore:
--------------------------------------------------------------------------------
1 | .vscode/**
2 | .vscode-test/**
3 | *.vsix
4 | src/**
5 | node_modules/**
6 | .gitignore
7 |
--------------------------------------------------------------------------------
/editors/vscode/CHANGELOG.md:
--------------------------------------------------------------------------------
1 | # Change Log
2 |
3 | All notable changes to the "super" extension will be documented in this file.
4 |
5 | ## [v0.5.3]
6 | - Fixes remaining bug when formatting void elements vertically.
7 |
8 | ## [v0.5.2]
9 | - Starting from this release, a WASM-WASI build of SuperHTML is available on GitHub (in the Releases section) in case editors other than VSCode might watnt to bundle a wasm build of SuperHTML.
10 |
11 | - Fixed indentation bug when formatting void elements.
12 |
13 | ## [v0.5.1]
14 | - This is now a web extension that can be used with vscode.dev, etc.
15 |
16 | ## [v0.5.0]
17 | - Updated list of obsolete tags, it previously was based on an outdated HTML spec version.
18 | - The minor version of this extension is now aliged with the internal language server implementation version.
19 |
20 | ## [v0.3.0]
21 | Now the LSP server is bundled in the extension, no need for a separate download anymore.
22 |
23 | ## [v0.2.0]
24 | Introduced correct syntax highlighting grammar.
25 |
26 | ## [v0.1.3]
27 | Add 'path' setting for this extension to allow specifying location of the Super CLI executable manually.
28 |
29 | ## [v0.1.2]
30 | Override VSCode default autoformatting.
31 |
32 | ## [v0.1.1]
33 | Readme fixes
34 |
35 | ## [v0.1.0]
36 | - Initial release
37 |
--------------------------------------------------------------------------------
/editors/vscode/LICENSE:
--------------------------------------------------------------------------------
1 | MIT License
2 |
3 | Copyright (c) 2024 Loris Cro
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 all
13 | 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 THE
21 | SOFTWARE.
--------------------------------------------------------------------------------
/editors/vscode/README.md:
--------------------------------------------------------------------------------
1 | # SuperHTML VSCode LSP
2 | Language Server for HTML and SuperHTML Templates.
3 |
4 | 
5 |
6 |
7 | # NOTE: This extension bundles the full language server
8 |
9 | But you can optionally also get the CLI tool so that you can access it outside of VSCode.
10 | For prebuilt binaries and more info: https://github.com/kristoff-it/superhtml
11 |
12 |
13 | ## Diagnostics
14 |
15 | 
16 |
17 | This language server is stricter than the HTML spec whenever it would prevent potential human errors from being reported.
18 |
19 |
20 | As an example, HTML allows for closing some tags implicitly. For example the following snipped is correct HTML.
21 |
22 | ```html
23 |
27 | ```
28 |
29 | This will still be reported as an error by SuperHTML because otherwise the following snippet would have to be considered correct (while it's much probably a typo):
30 |
31 | ```html
32 | Title
33 | ```
34 |
35 | ## Autoformatting
36 |
37 | The autoformatter has two main ways of interacting with it in order to request for horizontal / vertical alignment.
38 |
39 | 1. Adding / removing whitespace between the **start tag** of an element and its content.
40 | 2. Adding / removing whitespace between the **last attribute** of a start tag and the closing `>`.
41 |
42 |
43 | ### Example of rule #1
44 | Before:
45 | ```html
46 |
47 | ```
48 |
49 | After:
50 | ```html
51 |
54 | ```
55 |
56 | #### Reverse
57 |
58 | Before:
59 | ```html
60 |
62 | ```
63 |
64 | After:
65 | ```html
66 |
67 | ```
68 |
69 | ### Example of rule #2
70 | Before:
71 | ```html
72 |
73 | Foo
74 |
75 | ```
76 |
77 | After:
78 | ```html
79 |
83 | Foo
84 |
85 | ```
86 |
87 | #### Reverse
88 |
89 | Before:
90 | ```html
91 |
94 | Foo
95 |
96 | ```
97 |
98 | After:
99 | ```html
100 |
101 | Foo
102 |
103 | ```
104 |
105 |
--------------------------------------------------------------------------------
/editors/vscode/package.json:
--------------------------------------------------------------------------------
1 | {
2 | "name": "super",
3 | "displayName": "SuperHTML",
4 | "description": "Language Server for HTML and SuperHTML Templates.",
5 | "repository": "https://github.com/kristoff-it/superhtml/",
6 | "publisher": "LorisCro",
7 | "version": "0.5.3",
8 | "engines": {
9 | "vscode": "^1.92.0"
10 | },
11 | "categories": [
12 | "Formatters"
13 | ],
14 | "activationEvents": [
15 | "onLanguage:html"
16 | ],
17 | "contributes": {
18 | "configurationDefaults": {
19 | "[html]": {
20 | "editor.formatOnSave": true,
21 | "editor.defaultFormatter": "LorisCro.super",
22 | "files.eol": "\n"
23 | },
24 | "[superhtml]": {
25 | "editor.formatOnSave": true,
26 | "editor.defaultFormatter": "LorisCro.super",
27 | "files.eol": "\n"
28 | }
29 | },
30 | "languages": [
31 | {
32 | "id": "superhtml",
33 | "aliases": [
34 | "SuperHTML",
35 | "Super HTML",
36 | "superhtml",
37 | "shtml",
38 | "super"
39 | ],
40 | "extensions": [
41 | ".shtml"
42 | ],
43 | "configuration": "./superhtml.language-configuration.json"
44 | }
45 | ],
46 | "grammars": [
47 | {
48 | "scopeName": "text.superhtml.basic",
49 | "path": "./syntaxes/superhtml.tmLanguage.json",
50 | "embeddedLanguages": {
51 | "text.superhtml": "superhtml",
52 | "source.css": "css",
53 | "source.js": "javascript"
54 | },
55 | "tokenTypes": {
56 | "meta.tag string.quoted": "other"
57 | }
58 | },
59 | {
60 | "language": "superhtml",
61 | "scopeName": "text.superhtml.derivative",
62 | "path": "./syntaxes/superhtml-derivative.tmLanguage.json",
63 | "embeddedLanguages": {
64 | "text.superhtml": "superhtml",
65 | "source.css": "css",
66 | "source.js": "javascript",
67 | "source.python": "python",
68 | "source.smarty": "smarty"
69 | },
70 | "tokenTypes": {
71 | "meta.tag string.quoted": "other"
72 | }
73 | }
74 | ]
75 | },
76 | "main": "./out/extension",
77 | "browser": "./out/extension",
78 | "extensionDependencies": [
79 | "ms-vscode.wasm-wasi-core"
80 | ],
81 | "devDependencies": {
82 | "@types/mocha": "^2.2.48",
83 | "@types/node": "^18.0.0",
84 | "@types/vscode": "^1.92.0",
85 | "@types/which": "^2.0.1",
86 | "@typescript-eslint/eslint-plugin": "^6.7.0",
87 | "@typescript-eslint/parser": "^6.7.0",
88 | "eslint": "^8.49.0",
89 | "vscode-test": "^1.4.0"
90 | },
91 | "dependencies": {
92 | "@vscode/vsce": "^2.24.0",
93 | "@vscode/wasm-wasi-lsp": "^0.1.0-pre.7",
94 | "camelcase": "^7.0.1",
95 | "esbuild": "^0.12.1",
96 | "lodash-es": "^4.17.21",
97 | "lodash.debounce": "^4.0.8",
98 | "mkdirp": "^2.1.3",
99 | "vscode-languageclient": "^10.0.0-next.12",
100 | "which": "^3.0.0"
101 | },
102 | "scripts": {
103 | "vscode:prepublish": "npm run build-base -- --minify",
104 | "build-base": "esbuild --bundle --external:vscode src/extension.ts --outdir=out --platform=node --format=cjs",
105 | "build": "npm run build-base -- --sourcemap",
106 | "watch": "npm run build-base -- --sourcemap --watch",
107 | "lint": "eslint . --ext .ts"
108 | }
109 | }
--------------------------------------------------------------------------------
/editors/vscode/src/extension.ts:
--------------------------------------------------------------------------------
1 | import {
2 | // createStdioOptions,
3 | startServer
4 | } from '@vscode/wasm-wasi-lsp';
5 | import { ProcessOptions, Stdio, Wasm } from '@vscode/wasm-wasi/v1';
6 | import { ExtensionContext, languages, Uri, window, workspace } from 'vscode';
7 | import {
8 | LanguageClient,
9 | LanguageClientOptions,
10 | ServerOptions
11 | } from 'vscode-languageclient/node';
12 | import { SuperFormatProvider } from './formatter';
13 |
14 |
15 | let client: LanguageClient;
16 | export async function activate(context: ExtensionContext) {
17 | const wasm: Wasm = await Wasm.load();
18 |
19 | const channel = window.createOutputChannel('SuperHTML Language Server');
20 | // The server options to run the WebAssembly language server.
21 | const serverOptions: ServerOptions = async () => {
22 | const options: ProcessOptions = {
23 | stdio: createStdioOptions(),
24 | // mountPoints: [{ kind: 'workspaceFolder' }]
25 | };
26 |
27 | // Load the WebAssembly code
28 | const filename = Uri.joinPath(
29 | context.extensionUri,
30 | 'wasm',
31 | 'superhtml.wasm'
32 | );
33 | const bits = await workspace.fs.readFile(filename);
34 | const module = await WebAssembly.compile(bits);
35 |
36 | // Create the wasm worker that runs the LSP server
37 | const process = await wasm.createProcess(
38 | 'superhtml',
39 | module,
40 | { initial: 160, maximum: 160, shared: false },
41 | options
42 | );
43 |
44 | // Hook stderr to the output channel
45 | const decoder = new TextDecoder('utf-8');
46 | process.stderr!.onData(data => {
47 | channel.append(decoder.decode(data));
48 | });
49 |
50 | return startServer(process);
51 | };
52 |
53 | const clientOptions: LanguageClientOptions = {
54 | documentSelector: [
55 | { scheme: "file", language: 'html' },
56 | { scheme: "file", language: 'superhtml' },
57 | ],
58 | outputChannel: channel,
59 | };
60 |
61 | client = new LanguageClient(
62 | "superhtml",
63 | "SuperHTML Language Server",
64 | serverOptions,
65 | clientOptions
66 | );
67 |
68 | context.subscriptions.push(
69 | languages.registerDocumentFormattingEditProvider(
70 | [{ scheme: "file", language: "html" }],
71 | new SuperFormatProvider(client),
72 | ),
73 | languages.registerDocumentRangeFormattingEditProvider(
74 | [{ scheme: "file", language: "html" }],
75 | new SuperFormatProvider(client),
76 | ),
77 | languages.registerDocumentFormattingEditProvider(
78 | [{ scheme: "file", language: "superhtml" }],
79 | new SuperFormatProvider(client),
80 | ),
81 | languages.registerDocumentRangeFormattingEditProvider(
82 | [{ scheme: "file", language: "superhtml" }],
83 | new SuperFormatProvider(client),
84 | ),
85 | );
86 |
87 | await client.start();
88 |
89 | }
90 |
91 | export function deactivate(): Thenable | undefined {
92 | if (!client) {
93 | return undefined;
94 | }
95 | return client.stop();
96 | }
97 |
98 |
99 | function createStdioOptions(): Stdio {
100 | return {
101 | in: {
102 | kind: 'pipeIn',
103 | },
104 | out: {
105 | kind: 'pipeOut'
106 | },
107 | err: {
108 | kind: 'pipeOut'
109 | }
110 | };
111 | }
112 |
--------------------------------------------------------------------------------
/editors/vscode/src/formatter.ts:
--------------------------------------------------------------------------------
1 | import * as vscode from "vscode";
2 | import { TextEdit } from "vscode";
3 |
4 | import {
5 | DocumentFormattingRequest,
6 | DocumentRangeFormattingRequest,
7 | LanguageClient,
8 | TextDocumentIdentifier
9 | } from 'vscode-languageclient/node';
10 |
11 | export class SuperFormatProvider implements vscode.DocumentFormattingEditProvider, vscode.DocumentRangeFormattingEditProvider {
12 | private _client: LanguageClient;
13 |
14 | constructor(client: LanguageClient) {
15 | this._client = client;
16 | }
17 |
18 | provideDocumentFormattingEdits(document: vscode.TextDocument, options: vscode.FormattingOptions, token: vscode.CancellationToken): vscode.ProviderResult {
19 | return this._client.sendRequest(
20 | DocumentFormattingRequest.type,
21 | { textDocument: TextDocumentIdentifier.create(document.uri.toString()), options: options },
22 | token,
23 | ) as Promise;
24 | }
25 |
26 | provideDocumentRangeFormattingEdits(document: vscode.TextDocument, range: vscode.Range, options: vscode.FormattingOptions, token: vscode.CancellationToken): vscode.ProviderResult {
27 | return this._client.sendRequest(
28 | DocumentRangeFormattingRequest.type,
29 | { textDocument: TextDocumentIdentifier.create(document.uri.toString()), range: range, options: options },
30 | token,
31 | ) as Promise;
32 | }
33 | }
34 |
--------------------------------------------------------------------------------
/editors/vscode/src/util.ts:
--------------------------------------------------------------------------------
1 | import * as fs from "fs";
2 | import * as os from "os";
3 | import * as path from "path";
4 | import { window, workspace } from "vscode";
5 | import which from "which";
6 |
7 | export const isWindows = process.platform === "win32";
8 |
9 | export function getExePath(exePath: string | null, exeName: string, optionName: string): string {
10 | // Allow passing the ${workspaceFolder} predefined variable
11 | // See https://code.visualstudio.com/docs/editor/variables-reference#_predefined-variables
12 | if (exePath && exePath.includes("${workspaceFolder}")) {
13 | // We choose the first workspaceFolder since it is ambiguous which one to use in this context
14 | if (workspace.workspaceFolders && workspace.workspaceFolders.length > 0) {
15 | // older versions of Node (which VSCode uses) may not have String.prototype.replaceAll
16 | exePath = exePath.replace(/\$\{workspaceFolder\}/gm, workspace.workspaceFolders[0].uri.fsPath);
17 | }
18 | }
19 |
20 | if (!exePath) {
21 | exePath = which.sync(exeName, { nothrow: true });
22 | } else if (exePath.startsWith("~")) {
23 | exePath = path.join(os.homedir(), exePath.substring(1));
24 | } else if (!path.isAbsolute(exePath)) {
25 | exePath = which.sync(exePath, { nothrow: true });
26 | }
27 |
28 | let message;
29 | if (!exePath) {
30 | message = `Could not find ${exeName} in PATH`;
31 | } else if (!fs.existsSync(exePath)) {
32 | message = `\`${optionName}\` ${exePath} does not exist`
33 | } else {
34 | try {
35 | fs.accessSync(exePath, fs.constants.R_OK | fs.constants.X_OK);
36 | return exePath;
37 | } catch {
38 | message = `\`${optionName}\` ${exePath} is not an executable`;
39 | }
40 | }
41 | window.showErrorMessage(message);
42 | throw Error(message);
43 | }
44 |
45 | export function getSuperPath(): string {
46 | const configuration = workspace.getConfiguration("super");
47 | const superPath = configuration.get("path");
48 | return getExePath(superPath, "super", "super.path");
49 | }
50 |
51 |
--------------------------------------------------------------------------------
/editors/vscode/superhtml.language-configuration.json:
--------------------------------------------------------------------------------
1 | {
2 | "comments": {
3 | "blockComment": [
4 | ""
6 | ]
7 | },
8 | "brackets": [
9 | [
10 | ""
12 | ],
13 | [
14 | "{",
15 | "}"
16 | ],
17 | [
18 | "(",
19 | ")"
20 | ]
21 | ],
22 | "autoClosingPairs": [
23 | {
24 | "open": "{",
25 | "close": "}"
26 | },
27 | {
28 | "open": "[",
29 | "close": "]"
30 | },
31 | {
32 | "open": "(",
33 | "close": ")"
34 | },
35 | {
36 | "open": "'",
37 | "close": "'"
38 | },
39 | {
40 | "open": "\"",
41 | "close": "\""
42 | },
43 | {
44 | "open": "",
46 | "notIn": [
47 | "comment",
48 | "string"
49 | ]
50 | }
51 | ],
52 | "surroundingPairs": [
53 | {
54 | "open": "'",
55 | "close": "'"
56 | },
57 | {
58 | "open": "\"",
59 | "close": "\""
60 | },
61 | {
62 | "open": "{",
63 | "close": "}"
64 | },
65 | {
66 | "open": "[",
67 | "close": "]"
68 | },
69 | {
70 | "open": "(",
71 | "close": ")"
72 | },
73 | {
74 | "open": "<",
75 | "close": ">"
76 | }
77 | ],
78 | "colorizedBracketPairs": [],
79 | "folding": {
80 | "markers": {
81 | "start": "^\\s*",
82 | "end": "^\\s*"
83 | }
84 | },
85 | "wordPattern": "(-?\\d*\\.\\d\\w*)|([^\\`\\~\\!\\@\\$\\^\\&\\*\\(\\)\\=\\+\\[\\{\\]\\}\\\\\\|\\;\\:\\'\\\"\\,\\.\\<\\>\\/\\s]+)",
86 | "onEnterRules": [
87 | {
88 | "beforeText": {
89 | "pattern": "<(?!(?:area|base|br|col|embed|hr|img|input|keygen|link|menuitem|meta|param|source|track|wbr))([_:\\w][_:\\w-.\\d]*)(?:(?:[^'\"/>]|\"[^\"]*\"|'[^']*')*?(?!\\/)>)[^<]*$",
90 | "flags": "i"
91 | },
92 | "afterText": {
93 | "pattern": "^<\\/([_:\\w][_:\\w-.\\d]*)\\s*>",
94 | "flags": "i"
95 | },
96 | "action": {
97 | "indent": "indentOutdent"
98 | }
99 | },
100 | {
101 | "beforeText": {
102 | "pattern": "<(?!(?:area|base|br|col|embed|hr|img|input|keygen|link|menuitem|meta|param|source|track|wbr))([_:\\w][_:\\w-.\\d]*)(?:(?:[^'\"/>]|\"[^\"]*\"|'[^']*')*?(?!\\/)>)[^<]*$",
103 | "flags": "i"
104 | },
105 | "action": {
106 | "indent": "indent"
107 | }
108 | }
109 | ],
110 | "indentationRules": {
111 | "increaseIndentPattern": "<(?!\\?|(?:area|base|br|col|frame|hr|html|img|input|keygen|link|menuitem|meta|param|source|track|wbr)\\b|[^>]*\\/>)([-_\\.A-Za-z0-9]+)(?=\\s|>)\\b[^>]*>(?!.*<\\/\\1>)|)|\\{[^}\"']*$",
112 | "decreaseIndentPattern": "^\\s*(<\\/(?!html)[-_\\.A-Za-z0-9]+\\b[^>]*>|-->|\\})"
113 | }
114 | }
--------------------------------------------------------------------------------
/editors/vscode/syntaxes/superhtml-derivative.tmLanguage.json:
--------------------------------------------------------------------------------
1 | {
2 | "information_for_contributors": [
3 | "This file has been converted from https://github.com/textmate/html.tmbundle/blob/master/Syntaxes/HTML%20%28Derivative%29.tmLanguage",
4 | "If you want to provide a fix or improvement, please create a pull request against the original repository.",
5 | "Once accepted there, we are happy to receive an update request."
6 | ],
7 | "version": "https://github.com/textmate/html.tmbundle/commit/390c8870273a2ae80244dae6db6ba064a802f407",
8 | "name": "SuperHTML (Derivative)",
9 | "scopeName": "text.superhtml.derivative",
10 | "injections": {
11 | "R:text.superhtml - (comment.block, text.superhtml meta.embedded, meta.tag.*.*.html, meta.tag.*.*.*.html, meta.tag.*.*.*.*.html)": {
12 | "comment": "Uses R: to ensure this matches after any other injections.",
13 | "patterns": [
14 | {
15 | "match": "<",
16 | "name": "invalid.illegal.bad-angle-bracket.html"
17 | }
18 | ]
19 | }
20 | },
21 | "patterns": [
22 | {
23 | "include": "text.superhtml.basic#core-minus-invalid"
24 | },
25 | {
26 | "begin": "(?)(\\w[^\\s>]*)(?)",
36 | "endCaptures": {
37 | "1": {
38 | "name": "punctuation.definition.tag.end.html"
39 | }
40 | },
41 | "name": "meta.tag.other.unrecognized.html.derivative",
42 | "patterns": [
43 | {
44 | "include": "text.html.basic#attribute"
45 | }
46 | ]
47 | }
48 | ]
49 | }
--------------------------------------------------------------------------------
/editors/vscode/tsconfig.json:
--------------------------------------------------------------------------------
1 | {
2 | "compilerOptions": {
3 | "module": "NodeNext",
4 | "target": "ESNext",
5 | "outDir": "out",
6 | "esModuleInterop": true,
7 | "lib": [
8 | "esnext"
9 | ],
10 | "sourceMap": true,
11 | "rootDir": "src",
12 | "moduleResolution": "NodeNext"
13 | },
14 | "exclude": [
15 | "node_modules",
16 | ".vscode-test"
17 | ]
18 | }
--------------------------------------------------------------------------------
/editors/vscode/vscode.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/kristoff-it/superhtml/13f5a2221cb748bbe50ad702e89362afd5b925a7/editors/vscode/vscode.png
--------------------------------------------------------------------------------
/src/cli.zig:
--------------------------------------------------------------------------------
1 | const std = @import("std");
2 | const builtin = @import("builtin");
3 | const build_options = @import("build_options");
4 | const known = @import("known_folders");
5 | const super = @import("super");
6 | const logging = @import("cli/logging.zig");
7 | const interface_exe = @import("cli/interface.zig");
8 | const check_exe = @import("cli/check.zig");
9 | const fmt_exe = @import("cli/fmt.zig");
10 | const lsp_exe = @import("cli/lsp.zig");
11 |
12 | pub const known_folders_config = known.KnownFolderConfig{
13 | .xdg_force_default = true,
14 | .xdg_on_mac = true,
15 | };
16 |
17 | pub const std_options: std.Options = .{
18 | .log_level = if (build_options.verbose_logging)
19 | .debug
20 | else
21 | std.log.default_level,
22 | .logFn = logging.logFn,
23 | };
24 |
25 | var lsp_mode = false;
26 |
27 | pub fn panic(
28 | msg: []const u8,
29 | trace: ?*std.builtin.StackTrace,
30 | ret_addr: ?usize,
31 | ) noreturn {
32 | if (lsp_mode) {
33 | std.log.err("{s}\n\n{?}", .{ msg, trace });
34 | } else {
35 | std.debug.print("{s}\n\n{?}", .{ msg, trace });
36 | }
37 | blk: {
38 | const out = if (!lsp_mode) std.io.getStdErr() else logging.log_file orelse break :blk;
39 | const w = out.writer();
40 | if (builtin.strip_debug_info) {
41 | w.print("Unable to dump stack trace: debug info stripped\n", .{}) catch return;
42 | break :blk;
43 | }
44 | const debug_info = std.debug.getSelfDebugInfo() catch |err| {
45 | w.print("Unable to dump stack trace: Unable to open debug info: {s}\n", .{@errorName(err)}) catch break :blk;
46 | break :blk;
47 | };
48 | std.debug.writeCurrentStackTrace(w, debug_info, .no_color, ret_addr) catch |err| {
49 | w.print("Unable to dump stack trace: {s}\n", .{@errorName(err)}) catch break :blk;
50 | break :blk;
51 | };
52 | }
53 | if (builtin.mode == .Debug) @breakpoint();
54 | std.process.exit(1);
55 | }
56 |
57 | pub const Command = enum {
58 | check,
59 | interface,
60 | i, // alias for interface
61 | fmt,
62 | lsp,
63 | help,
64 | version,
65 | };
66 |
67 | pub fn main() !void {
68 | var gpa_impl: std.heap.GeneralPurposeAllocator(.{}) = .{};
69 | const gpa = gpa_impl.allocator();
70 |
71 | logging.setup(gpa);
72 |
73 | const args = std.process.argsAlloc(gpa) catch oom();
74 | defer std.process.argsFree(gpa, args);
75 |
76 | if (args.len < 2) fatalHelp();
77 |
78 | const cmd = std.meta.stringToEnum(Command, args[1]) orelse {
79 | std.debug.print("unrecognized subcommand: '{s}'\n\n", .{args[1]});
80 | fatalHelp();
81 | };
82 |
83 | if (cmd == .lsp) lsp_mode = true;
84 |
85 | _ = switch (cmd) {
86 | .check => check_exe.run(gpa, args[2..]),
87 | .interface, .i => interface_exe.run(gpa, args[2..]),
88 | .fmt => fmt_exe.run(gpa, args[2..]),
89 | .lsp => lsp_exe.run(gpa, args[2..]),
90 | .help => fatalHelp(),
91 | .version => printVersion(),
92 | } catch |err| fatal("unexpected error: {s}\n", .{@errorName(err)});
93 | }
94 |
95 | fn fatal(comptime fmt: []const u8, args: anytype) noreturn {
96 | std.debug.print(fmt, args);
97 | std.process.exit(1);
98 | }
99 |
100 | fn oom() noreturn {
101 | fatal("oom\n", .{});
102 | }
103 |
104 | fn printVersion() noreturn {
105 | std.debug.print("{s}\n", .{build_options.version});
106 | std.process.exit(0);
107 | }
108 |
109 | fn fatalHelp() noreturn {
110 | fatal(
111 | \\Usage: superhtml COMMAND [OPTIONS]
112 | \\
113 | \\Commands:
114 | \\ check Check documents for syntax errors
115 | \\ interface, i Print a SuperHTML template's interface
116 | \\ fmt Format documents
117 | \\ lsp Start the Super LSP
118 | \\ help Show this menu and exit
119 | \\ version Print Super's version and exit
120 | \\
121 | \\General Options:
122 | \\ --help, -h Print command specific usage
123 | \\
124 | \\
125 | , .{});
126 | }
127 |
--------------------------------------------------------------------------------
/src/cli/check.zig:
--------------------------------------------------------------------------------
1 | const std = @import("std");
2 | const super = @import("superhtml");
3 |
4 | const FileType = enum { html, super };
5 |
6 | pub fn run(gpa: std.mem.Allocator, args: []const []const u8) !void {
7 | const cmd = Command.parse(args);
8 | var any_error = false;
9 | switch (cmd.mode) {
10 | .stdin => {
11 | var buf = std.ArrayList(u8).init(gpa);
12 | try std.io.getStdIn().reader().readAllArrayList(&buf, super.max_size);
13 | const in_bytes = try buf.toOwnedSliceSentinel(0);
14 |
15 | try checkHtml(gpa, null, in_bytes);
16 | },
17 | .stdin_super => {
18 | var buf = std.ArrayList(u8).init(gpa);
19 | try std.io.getStdIn().reader().readAllArrayList(&buf, super.max_size);
20 | const in_bytes = try buf.toOwnedSliceSentinel(0);
21 |
22 | try checkSuper(gpa, null, in_bytes);
23 | },
24 | .paths => |paths| {
25 | // checkFile will reset the arena at the end of each call
26 | var arena_impl = std.heap.ArenaAllocator.init(gpa);
27 | for (paths) |path| {
28 | checkFile(
29 | &arena_impl,
30 | std.fs.cwd(),
31 | path,
32 | path,
33 | &any_error,
34 | ) catch |err| switch (err) {
35 | error.IsDir, error.AccessDenied => {
36 | checkDir(
37 | gpa,
38 | &arena_impl,
39 | path,
40 | &any_error,
41 | ) catch |dir_err| {
42 | std.debug.print("Error walking dir '{s}': {s}\n", .{
43 | path,
44 | @errorName(dir_err),
45 | });
46 | std.process.exit(1);
47 | };
48 | },
49 | else => {
50 | std.debug.print("Error while accessing '{s}': {s}\n", .{
51 | path, @errorName(err),
52 | });
53 | std.process.exit(1);
54 | },
55 | };
56 | }
57 | },
58 | }
59 |
60 | if (any_error) {
61 | std.process.exit(1);
62 | }
63 | }
64 |
65 | fn checkDir(
66 | gpa: std.mem.Allocator,
67 | arena_impl: *std.heap.ArenaAllocator,
68 | path: []const u8,
69 | any_error: *bool,
70 | ) !void {
71 | var dir = try std.fs.cwd().openDir(path, .{ .iterate = true });
72 | defer dir.close();
73 | var walker = dir.walk(gpa) catch oom();
74 | defer walker.deinit();
75 | while (try walker.next()) |item| {
76 | switch (item.kind) {
77 | .file => {
78 | try checkFile(
79 | arena_impl,
80 | item.dir,
81 | item.basename,
82 | item.path,
83 | any_error,
84 | );
85 | },
86 | else => {},
87 | }
88 | }
89 | }
90 |
91 | fn checkFile(
92 | arena_impl: *std.heap.ArenaAllocator,
93 | base_dir: std.fs.Dir,
94 | sub_path: []const u8,
95 | full_path: []const u8,
96 | any_error: *bool,
97 | ) !void {
98 | _ = any_error;
99 | defer _ = arena_impl.reset(.retain_capacity);
100 | const arena = arena_impl.allocator();
101 |
102 | const file = try base_dir.openFile(sub_path, .{});
103 | defer file.close();
104 |
105 | const stat = try file.stat();
106 | if (stat.kind == .directory)
107 | return error.IsDir;
108 |
109 | const file_type: FileType = blk: {
110 | const ext = std.fs.path.extension(sub_path);
111 | if (std.mem.eql(u8, ext, ".html") or
112 | std.mem.eql(u8, ext, ".htm"))
113 | {
114 | break :blk .html;
115 | }
116 |
117 | if (std.mem.eql(u8, ext, ".shtml")) {
118 | break :blk .super;
119 | }
120 | return;
121 | };
122 |
123 | var buf = std.ArrayList(u8).init(arena);
124 | defer buf.deinit();
125 |
126 | try file.reader().readAllArrayList(&buf, super.max_size);
127 |
128 | const in_bytes = try buf.toOwnedSliceSentinel(0);
129 |
130 | switch (file_type) {
131 | .html => try checkHtml(
132 | arena,
133 | full_path,
134 | in_bytes,
135 | ),
136 | .super => try checkSuper(
137 | arena,
138 | full_path,
139 | in_bytes,
140 | ),
141 | }
142 | }
143 |
144 | pub fn checkHtml(
145 | arena: std.mem.Allocator,
146 | path: ?[]const u8,
147 | code: [:0]const u8,
148 | ) !void {
149 | const ast = try super.html.Ast.init(arena, code, .html);
150 | if (ast.errors.len > 0) {
151 | try ast.printErrors(code, path, std.io.getStdErr().writer());
152 | std.process.exit(1);
153 | }
154 | }
155 |
156 | fn checkSuper(
157 | arena: std.mem.Allocator,
158 | path: ?[]const u8,
159 | code: [:0]const u8,
160 | ) !void {
161 | const html = try super.html.Ast.init(arena, code, .superhtml);
162 | if (html.errors.len > 0) {
163 | try html.printErrors(code, path, std.io.getStdErr().writer());
164 | std.process.exit(1);
165 | }
166 |
167 | const s = try super.Ast.init(arena, html, code);
168 | if (s.errors.len > 0) {
169 | try s.printErrors(code, path, std.io.getStdErr().writer());
170 | std.process.exit(1);
171 | }
172 | }
173 |
174 | fn oom() noreturn {
175 | std.debug.print("Out of memory\n", .{});
176 | std.process.exit(1);
177 | }
178 |
179 | const Command = struct {
180 | mode: Mode,
181 |
182 | const Mode = union(enum) {
183 | stdin,
184 | stdin_super,
185 | paths: []const []const u8,
186 | };
187 |
188 | fn parse(args: []const []const u8) Command {
189 | var mode: ?Mode = null;
190 |
191 | var idx: usize = 0;
192 | while (idx < args.len) : (idx += 1) {
193 | const arg = args[idx];
194 | if (std.mem.eql(u8, arg, "--help") or
195 | std.mem.eql(u8, arg, "-h"))
196 | {
197 | fatalHelp();
198 | }
199 |
200 | if (std.mem.startsWith(u8, arg, "-")) {
201 | if (std.mem.eql(u8, arg, "--stdin") or
202 | std.mem.eql(u8, arg, "-"))
203 | {
204 | if (mode != null) {
205 | std.debug.print("unexpected flag: '{s}'\n", .{arg});
206 | std.process.exit(1);
207 | }
208 |
209 | mode = .stdin;
210 | } else if (std.mem.eql(u8, arg, "--stdin-super")) {
211 | if (mode != null) {
212 | std.debug.print("unexpected flag: '{s}'\n", .{arg});
213 | std.process.exit(1);
214 | }
215 |
216 | mode = .stdin_super;
217 | } else {
218 | std.debug.print("unexpected flag: '{s}'\n", .{arg});
219 | std.process.exit(1);
220 | }
221 | } else {
222 | const paths_start = idx;
223 | while (idx < args.len) : (idx += 1) {
224 | if (std.mem.startsWith(u8, args[idx], "-")) {
225 | break;
226 | }
227 | }
228 | idx -= 1;
229 |
230 | if (mode != null) {
231 | std.debug.print(
232 | "unexpected path argument(s): '{s}'...\n",
233 | .{args[paths_start]},
234 | );
235 | std.process.exit(1);
236 | }
237 |
238 | const paths = args[paths_start .. idx + 1];
239 | mode = .{ .paths = paths };
240 | }
241 | }
242 |
243 | const m = mode orelse {
244 | std.debug.print("missing argument(s)\n\n", .{});
245 | fatalHelp();
246 | };
247 |
248 | return .{ .mode = m };
249 | }
250 |
251 | fn fatalHelp() noreturn {
252 | std.debug.print(
253 | \\Usage: super check PATH [PATH...] [OPTIONS]
254 | \\
255 | \\ Checks for syntax errors. If PATH is a directory, it will
256 | \\ be searched recursively for HTML and SuperHTML files.
257 | \\
258 | \\ Detected extensions:
259 | \\ HTML .html, .htm
260 | \\ SuperHTML .shtml
261 | \\
262 | \\Options:
263 | \\
264 | \\ --stdin Format bytes from stdin and output to stdout.
265 | \\ Mutually exclusive with other input arguments.
266 | \\
267 | \\ --stdin-super Same as --stdin but for SuperHTML files.
268 | \\
269 | \\ --help, -h Prints this help and exits.
270 | , .{});
271 |
272 | std.process.exit(1);
273 | }
274 | };
275 |
--------------------------------------------------------------------------------
/src/cli/fmt.zig:
--------------------------------------------------------------------------------
1 | const std = @import("std");
2 | const super = @import("superhtml");
3 |
4 | const FileType = enum { html, super };
5 |
6 | pub fn run(gpa: std.mem.Allocator, args: []const []const u8) !void {
7 | const cmd = Command.parse(args);
8 | var any_error = false;
9 | switch (cmd.mode) {
10 | .stdin => {
11 | var buf = std.ArrayList(u8).init(gpa);
12 | try std.io.getStdIn().reader().readAllArrayList(&buf, super.max_size);
13 | const in_bytes = try buf.toOwnedSliceSentinel(0);
14 |
15 | const out_bytes = try fmtHtml(gpa, null, in_bytes);
16 | try std.io.getStdOut().writeAll(out_bytes);
17 | },
18 | .stdin_super => {
19 | var buf = std.ArrayList(u8).init(gpa);
20 | try std.io.getStdIn().reader().readAllArrayList(&buf, super.max_size);
21 | const in_bytes = try buf.toOwnedSliceSentinel(0);
22 |
23 | const out_bytes = try fmtSuper(gpa, null, in_bytes);
24 | try std.io.getStdOut().writeAll(out_bytes);
25 | },
26 | .paths => |paths| {
27 | // checkFile will reset the arena at the end of each call
28 | var arena_impl = std.heap.ArenaAllocator.init(gpa);
29 | for (paths) |path| {
30 | formatFile(
31 | &arena_impl,
32 | cmd.check,
33 | std.fs.cwd(),
34 | path,
35 | path,
36 | &any_error,
37 | ) catch |err| switch (err) {
38 | error.IsDir, error.AccessDenied => {
39 | formatDir(
40 | gpa,
41 | &arena_impl,
42 | cmd.check,
43 | path,
44 | &any_error,
45 | ) catch |dir_err| {
46 | std.debug.print("Error walking dir '{s}': {s}\n", .{
47 | path,
48 | @errorName(dir_err),
49 | });
50 | std.process.exit(1);
51 | };
52 | },
53 | else => {
54 | std.debug.print("Error while accessing '{s}': {s}\n", .{
55 | path, @errorName(err),
56 | });
57 | std.process.exit(1);
58 | },
59 | };
60 | }
61 | },
62 | }
63 |
64 | if (any_error) {
65 | std.process.exit(1);
66 | }
67 | }
68 |
69 | fn formatDir(
70 | gpa: std.mem.Allocator,
71 | arena_impl: *std.heap.ArenaAllocator,
72 | check: bool,
73 | path: []const u8,
74 | any_error: *bool,
75 | ) !void {
76 | var dir = try std.fs.cwd().openDir(path, .{ .iterate = true });
77 | defer dir.close();
78 | var walker = dir.walk(gpa) catch oom();
79 | defer walker.deinit();
80 | while (try walker.next()) |item| {
81 | switch (item.kind) {
82 | .file => {
83 | try formatFile(
84 | arena_impl,
85 | check,
86 | item.dir,
87 | item.basename,
88 | item.path,
89 | any_error,
90 | );
91 | },
92 | else => {},
93 | }
94 | }
95 | }
96 |
97 | fn formatFile(
98 | arena_impl: *std.heap.ArenaAllocator,
99 | check: bool,
100 | base_dir: std.fs.Dir,
101 | sub_path: []const u8,
102 | full_path: []const u8,
103 | any_error: *bool,
104 | ) !void {
105 | defer _ = arena_impl.reset(.retain_capacity);
106 | const arena = arena_impl.allocator();
107 |
108 | const file = try base_dir.openFile(sub_path, .{});
109 | defer file.close();
110 |
111 | const stat = try file.stat();
112 | if (stat.kind == .directory)
113 | return error.IsDir;
114 |
115 | const file_type: FileType = blk: {
116 | const ext = std.fs.path.extension(sub_path);
117 | if (std.mem.eql(u8, ext, ".html") or
118 | std.mem.eql(u8, ext, ".htm"))
119 | {
120 | break :blk .html;
121 | }
122 |
123 | if (std.mem.eql(u8, ext, ".shtml")) {
124 | break :blk .super;
125 | }
126 | return;
127 | };
128 |
129 | var buf = std.ArrayList(u8).init(arena);
130 | defer buf.deinit();
131 |
132 | try file.reader().readAllArrayList(&buf, super.max_size);
133 |
134 | const in_bytes = try buf.toOwnedSliceSentinel(0);
135 |
136 | const out_bytes = switch (file_type) {
137 | .html => try fmtHtml(
138 | arena,
139 | full_path,
140 | in_bytes,
141 | ),
142 | .super => try fmtSuper(
143 | arena,
144 | full_path,
145 | in_bytes,
146 | ),
147 | };
148 |
149 | if (std.mem.eql(u8, out_bytes, in_bytes)) return;
150 |
151 | const stdout = std.io.getStdOut().writer();
152 | if (check) {
153 | any_error.* = true;
154 | try stdout.print("{s}\n", .{full_path});
155 | return;
156 | }
157 |
158 | var af = try base_dir.atomicFile(sub_path, .{ .mode = stat.mode });
159 | defer af.deinit();
160 |
161 | try af.file.writeAll(out_bytes);
162 | try af.finish();
163 | try stdout.print("{s}\n", .{full_path});
164 | }
165 |
166 | pub fn fmtHtml(
167 | arena: std.mem.Allocator,
168 | path: ?[]const u8,
169 | code: [:0]const u8,
170 | ) ![]const u8 {
171 | const ast = try super.html.Ast.init(arena, code, .html);
172 | if (ast.errors.len > 0) {
173 | try ast.printErrors(code, path, std.io.getStdErr().writer());
174 | std.process.exit(1);
175 | }
176 |
177 | return std.fmt.allocPrint(arena, "{}", .{ast.formatter(code)});
178 | }
179 |
180 | fn fmtSuper(
181 | arena: std.mem.Allocator,
182 | path: ?[]const u8,
183 | code: [:0]const u8,
184 | ) ![]const u8 {
185 | const ast = try super.html.Ast.init(arena, code, .superhtml);
186 | if (ast.errors.len > 0) {
187 | try ast.printErrors(code, path, std.io.getStdErr().writer());
188 | std.process.exit(1);
189 | }
190 |
191 | return std.fmt.allocPrint(arena, "{}", .{ast.formatter(code)});
192 | }
193 |
194 | fn oom() noreturn {
195 | std.debug.print("Out of memory\n", .{});
196 | std.process.exit(1);
197 | }
198 |
199 | const Command = struct {
200 | check: bool,
201 | mode: Mode,
202 |
203 | const Mode = union(enum) {
204 | stdin,
205 | stdin_super,
206 | paths: []const []const u8,
207 | };
208 |
209 | fn parse(args: []const []const u8) Command {
210 | var check: bool = false;
211 | var mode: ?Mode = null;
212 |
213 | var idx: usize = 0;
214 | while (idx < args.len) : (idx += 1) {
215 | const arg = args[idx];
216 | if (std.mem.eql(u8, arg, "--help") or
217 | std.mem.eql(u8, arg, "-h"))
218 | {
219 | fatalHelp();
220 | }
221 |
222 | if (std.mem.eql(u8, arg, "--check")) {
223 | if (check) {
224 | std.debug.print("error: duplicate '--check' flag\n\n", .{});
225 | std.process.exit(1);
226 | }
227 |
228 | check = true;
229 | continue;
230 | }
231 |
232 | if (std.mem.startsWith(u8, arg, "-")) {
233 | if (std.mem.eql(u8, arg, "--stdin") or
234 | std.mem.eql(u8, arg, "-"))
235 | {
236 | if (mode != null) {
237 | std.debug.print("unexpected flag: '{s}'\n", .{arg});
238 | std.process.exit(1);
239 | }
240 |
241 | mode = .stdin;
242 | } else if (std.mem.eql(u8, arg, "--stdin-super")) {
243 | if (mode != null) {
244 | std.debug.print("unexpected flag: '{s}'\n", .{arg});
245 | std.process.exit(1);
246 | }
247 |
248 | mode = .stdin_super;
249 | } else {
250 | std.debug.print("unexpected flag: '{s}'\n", .{arg});
251 | std.process.exit(1);
252 | }
253 | } else {
254 | const paths_start = idx;
255 | while (idx < args.len) : (idx += 1) {
256 | if (std.mem.startsWith(u8, args[idx], "-")) {
257 | break;
258 | }
259 | }
260 | idx -= 1;
261 |
262 | if (mode != null) {
263 | std.debug.print(
264 | "unexpected path argument(s): '{s}'...\n",
265 | .{args[paths_start]},
266 | );
267 | std.process.exit(1);
268 | }
269 |
270 | const paths = args[paths_start .. idx + 1];
271 | mode = .{ .paths = paths };
272 | }
273 | }
274 |
275 | const m = mode orelse {
276 | std.debug.print("missing argument(s)\n\n", .{});
277 | fatalHelp();
278 | };
279 |
280 | return .{ .check = check, .mode = m };
281 | }
282 |
283 | fn fatalHelp() noreturn {
284 | std.debug.print(
285 | \\Usage: super fmt PATH [PATH...] [OPTIONS]
286 | \\
287 | \\ Formats input paths inplace. If PATH is a directory, it will
288 | \\ be searched recursively for HTML and SuperHTML files.
289 | \\
290 | \\ Detected extensions:
291 | \\ HTML .html, .htm
292 | \\ SuperHTML .shtml
293 | \\
294 | \\Options:
295 | \\
296 | \\ --stdin Format bytes from stdin and output to stdout.
297 | \\ Mutually exclusive with other input arguments.
298 | \\
299 | \\ --stdin-super Same as --stdin but for SuperHTML files.
300 | \\
301 | \\ --check List non-conforming files and exit with an
302 | \\ error if the list is not empty.
303 | \\
304 | \\ --help, -h Prints this help and exits.
305 | , .{});
306 |
307 | std.process.exit(1);
308 | }
309 | };
310 |
--------------------------------------------------------------------------------
/src/cli/interface.zig:
--------------------------------------------------------------------------------
1 | const std = @import("std");
2 | const super = @import("superhtml");
3 |
4 | const FileType = enum { html, super };
5 |
6 | pub fn run(gpa: std.mem.Allocator, args: []const []const u8) !void {
7 | const cmd = Command.parse(args);
8 | switch (cmd.mode) {
9 | .stdin => {
10 | var buf = std.ArrayList(u8).init(gpa);
11 | try std.io.getStdIn().reader().readAllArrayList(&buf, super.max_size);
12 | const in_bytes = try buf.toOwnedSliceSentinel(0);
13 |
14 | const out_bytes = try renderInterface(gpa, null, in_bytes);
15 | try std.io.getStdOut().writeAll(out_bytes);
16 | },
17 | .path => |path| {
18 | var arena_impl = std.heap.ArenaAllocator.init(gpa);
19 | const out_bytes = printInterfaceFromFile(
20 | &arena_impl,
21 | std.fs.cwd(),
22 | path,
23 | path,
24 | ) catch |err| switch (err) {
25 | error.IsDir => {
26 | std.debug.print("error: '{s}' is a directory\n\n", .{
27 | path,
28 | });
29 | std.process.exit(1);
30 | },
31 | else => {
32 | std.debug.print("error while accessing '{s}': {}\n\n", .{
33 | path,
34 | err,
35 | });
36 | std.process.exit(1);
37 | },
38 | };
39 |
40 | try std.io.getStdOut().writeAll(out_bytes);
41 | },
42 | }
43 | }
44 |
45 | fn printInterfaceFromFile(
46 | arena_impl: *std.heap.ArenaAllocator,
47 | base_dir: std.fs.Dir,
48 | sub_path: []const u8,
49 | full_path: []const u8,
50 | ) ![]const u8 {
51 | defer _ = arena_impl.reset(.retain_capacity);
52 | const arena = arena_impl.allocator();
53 |
54 | const file = try base_dir.openFile(sub_path, .{});
55 | defer file.close();
56 |
57 | const stat = try file.stat();
58 | if (stat.kind == .directory)
59 | return error.IsDir;
60 |
61 | var buf = std.ArrayList(u8).init(arena);
62 | defer buf.deinit();
63 |
64 | try file.reader().readAllArrayList(&buf, super.max_size);
65 |
66 | const in_bytes = try buf.toOwnedSliceSentinel(0);
67 |
68 | return renderInterface(arena, full_path, in_bytes);
69 | }
70 |
71 | fn renderInterface(
72 | arena: std.mem.Allocator,
73 | path: ?[]const u8,
74 | code: [:0]const u8,
75 | ) ![]const u8 {
76 | const html_ast = try super.html.Ast.init(arena, code, .superhtml);
77 | if (html_ast.errors.len > 0) {
78 | try html_ast.printErrors(code, path, std.io.getStdErr().writer());
79 | std.process.exit(1);
80 | }
81 |
82 | const s = try super.Ast.init(arena, html_ast, code);
83 | if (s.errors.len > 0) {
84 | try s.printErrors(code, path, std.io.getStdErr().writer());
85 | std.process.exit(1);
86 | }
87 |
88 | return std.fmt.allocPrint(arena, "{}", .{
89 | s.interfaceFormatter(html_ast, path),
90 | });
91 | }
92 |
93 | fn oom() noreturn {
94 | std.debug.print("Out of memory\n", .{});
95 | std.process.exit(1);
96 | }
97 |
98 | const Command = struct {
99 | mode: Mode,
100 |
101 | const Mode = union(enum) {
102 | stdin,
103 | path: []const u8,
104 | };
105 |
106 | fn parse(args: []const []const u8) Command {
107 | var mode: ?Mode = null;
108 |
109 | var idx: usize = 0;
110 | while (idx < args.len) : (idx += 1) {
111 | const arg = args[idx];
112 | if (std.mem.eql(u8, arg, "--help") or
113 | std.mem.eql(u8, arg, "-h"))
114 | {
115 | fatalHelp();
116 | }
117 |
118 | if (std.mem.startsWith(u8, arg, "-")) {
119 | if (std.mem.eql(u8, arg, "--stdin") or
120 | std.mem.eql(u8, arg, "-"))
121 | {
122 | if (mode != null) {
123 | std.debug.print("unexpected flag: '{s}'\n", .{arg});
124 | std.process.exit(1);
125 | }
126 |
127 | mode = .stdin;
128 | } else {
129 | std.debug.print("unexpected flag: '{s}'\n", .{arg});
130 | std.process.exit(1);
131 | }
132 | } else {
133 | if (mode != null) {
134 | std.debug.print(
135 | "unexpected path argument: '{s}'...\n",
136 | .{args[idx]},
137 | );
138 | std.process.exit(1);
139 | }
140 |
141 | mode = .{ .path = args[idx] };
142 | }
143 | }
144 |
145 | const m = mode orelse {
146 | std.debug.print("missing argument\n\n", .{});
147 | fatalHelp();
148 | };
149 |
150 | return .{ .mode = m };
151 | }
152 |
153 | fn fatalHelp() noreturn {
154 | std.debug.print(
155 | \\Usage: super i [FILE] [OPTIONS]
156 | \\
157 | \\ Prints a SuperHTML template's interface.
158 | \\
159 | \\Options:
160 | \\
161 | \\ --stdin Read the template from stdin instead of
162 | \\ reading from a file.
163 | \\
164 | \\ --help, -h Prints this help and exits.
165 | , .{});
166 |
167 | std.process.exit(1);
168 | }
169 | };
170 |
--------------------------------------------------------------------------------
/src/cli/logging.zig:
--------------------------------------------------------------------------------
1 | const std = @import("std");
2 | const builtin = @import("builtin");
3 | const build_options = @import("build_options");
4 | const folders = @import("known_folders");
5 |
6 | pub var log_file: ?std.fs.File = switch (builtin.target.os.tag) {
7 | .linux, .macos => std.io.getStdErr(),
8 | else => null,
9 | };
10 |
11 | // const enabled_scopes = blk: {
12 | // const len = build_options.enabled_scopes.len;
13 | // const scopes: [len]@Type(.EnumLiteral) = undefined;
14 | // for (build_options.enabled_scopes, &scopes) |s, *e| {
15 | // e.* = @Type()
16 | // }
17 | // };
18 |
19 | pub fn logFn(
20 | comptime level: std.log.Level,
21 | comptime scope: @Type(.enum_literal),
22 | comptime format: []const u8,
23 | args: anytype,
24 | ) void {
25 | if (build_options.enabled_scopes.len > 0) {
26 | inline for (build_options.enabled_scopes) |es| {
27 | if (comptime std.mem.eql(u8, es, @tagName(scope))) {
28 | break;
29 | }
30 | } else return;
31 | }
32 |
33 | const l = log_file orelse return;
34 | const scope_prefix = "(" ++ @tagName(scope) ++ "): ";
35 | const prefix = "[" ++ @tagName(level) ++ "] " ++ scope_prefix;
36 |
37 | std.debug.lockStdErr();
38 | defer std.debug.unlockStdErr();
39 |
40 | var buf_writer = std.io.bufferedWriter(l.writer());
41 | buf_writer.writer().print(prefix ++ format ++ "\n", args) catch return;
42 | buf_writer.flush() catch return;
43 | }
44 |
45 | pub fn setup(gpa: std.mem.Allocator) void {
46 | std.debug.lockStdErr();
47 | defer std.debug.unlockStdErr();
48 |
49 | setupInternal(gpa) catch {
50 | log_file = null;
51 | };
52 | }
53 |
54 | fn setupInternal(gpa: std.mem.Allocator) !void {
55 | const cache_base = try folders.open(gpa, .cache, .{}) orelse return error.Failure;
56 | try cache_base.makePath("super");
57 |
58 | const log_path = "superhtml.log";
59 | const file = try cache_base.createFile(log_path, .{ .truncate = false });
60 | const end = try file.getEndPos();
61 | try file.seekTo(end);
62 |
63 | log_file = file;
64 | }
65 |
--------------------------------------------------------------------------------
/src/cli/lsp/Document.zig:
--------------------------------------------------------------------------------
1 | const Document = @This();
2 |
3 | const std = @import("std");
4 | const assert = std.debug.assert;
5 | const super = @import("superhtml");
6 |
7 | const log = std.log.scoped(.lsp_document);
8 |
9 | language: super.Language,
10 | src: []const u8,
11 | html: super.html.Ast,
12 | super_ast: ?super.Ast = null,
13 |
14 | pub fn deinit(doc: *Document, gpa: std.mem.Allocator) void {
15 | doc.html.deinit(gpa);
16 | if (doc.super_ast) |s| s.deinit(gpa);
17 | }
18 |
19 | pub fn init(
20 | gpa: std.mem.Allocator,
21 | src: []const u8,
22 | language: super.Language,
23 | ) error{OutOfMemory}!Document {
24 | var doc: Document = .{
25 | .src = src,
26 | .language = language,
27 | .html = try super.html.Ast.init(gpa, src, language),
28 | };
29 | errdefer doc.html.deinit(gpa);
30 |
31 | if (language == .superhtml and doc.html.errors.len == 0) {
32 | const super_ast = try super.Ast.init(gpa, doc.html, src);
33 | errdefer super_ast.deinit(gpa);
34 | doc.super_ast = super_ast;
35 | }
36 |
37 | return doc;
38 | }
39 |
40 | pub fn reparse(doc: *Document, gpa: std.mem.Allocator) !void {
41 | doc.deinit(gpa);
42 | doc.html = try super.html.Ast.init(gpa, doc.src, doc.language);
43 | errdefer doc.html.deinit(gpa);
44 |
45 | if (doc.language == .superhtml and doc.html.errors.len == 0) {
46 | doc.super_ast = try super.Ast.init(gpa, doc.html, doc.src);
47 | } else {
48 | doc.super_ast = null;
49 | }
50 |
51 | return;
52 | }
53 |
--------------------------------------------------------------------------------
/src/cli/lsp/logic.zig:
--------------------------------------------------------------------------------
1 | const std = @import("std");
2 | const lsp = @import("lsp");
3 | const super = @import("superhtml");
4 | const lsp_namespace = @import("../lsp.zig");
5 | const Handler = lsp_namespace.Handler;
6 | const getRange = lsp_namespace.getRange;
7 | const Document = @import("Document.zig");
8 |
9 | const log = std.log.scoped(.ziggy_lsp);
10 |
11 | pub fn loadFile(
12 | self: *Handler,
13 | arena: std.mem.Allocator,
14 | new_text: [:0]const u8,
15 | uri: []const u8,
16 | language: super.Language,
17 | ) !void {
18 | errdefer @panic("error while loading document!");
19 |
20 | var res: lsp.types.PublishDiagnosticsParams = .{
21 | .uri = uri,
22 | .diagnostics = &.{},
23 | };
24 |
25 | const doc = try Document.init(
26 | self.gpa,
27 | new_text,
28 | language,
29 | );
30 |
31 | log.debug("document init", .{});
32 |
33 | const gop = try self.files.getOrPut(self.gpa, uri);
34 | errdefer _ = self.files.remove(uri);
35 |
36 | if (gop.found_existing) {
37 | gop.value_ptr.deinit(self.gpa);
38 | } else {
39 | gop.key_ptr.* = try self.gpa.dupe(u8, uri);
40 | }
41 |
42 | gop.value_ptr.* = doc;
43 |
44 | if (doc.html.errors.len != 0) {
45 | const diags = try arena.alloc(lsp.types.Diagnostic, doc.html.errors.len);
46 | for (doc.html.errors, diags) |err, *d| {
47 | const range = getRange(err.main_location, doc.src);
48 | d.* = .{
49 | .range = range,
50 | .severity = .Error,
51 | .message = switch (err.tag) {
52 | .token => |t| @tagName(t),
53 | .ast => |t| @tagName(t),
54 | },
55 | };
56 | }
57 | res.diagnostics = diags;
58 | } else {
59 | if (doc.super_ast) |super_ast| {
60 | const diags = try arena.alloc(
61 | lsp.types.Diagnostic,
62 | super_ast.errors.len,
63 | );
64 | for (super_ast.errors, diags) |err, *d| {
65 | const range = getRange(err.main_location, doc.src);
66 | d.* = .{
67 | .range = range,
68 | .severity = .Error,
69 | .message = @tagName(err.kind),
70 | };
71 | }
72 | res.diagnostics = diags;
73 | }
74 | }
75 |
76 | const msg = try self.server.sendToClientNotification(
77 | "textDocument/publishDiagnostics",
78 | res,
79 | );
80 |
81 | defer self.gpa.free(msg);
82 | }
83 |
--------------------------------------------------------------------------------
/src/css.zig:
--------------------------------------------------------------------------------
1 | pub const Tokenizer = @import("css/Tokenizer.zig");
2 | pub const Ast = @import("css/Ast.zig");
3 |
4 | test {
5 | _ = Tokenizer;
6 | _ = Ast;
7 | }
8 |
--------------------------------------------------------------------------------
/src/errors.zig:
--------------------------------------------------------------------------------
1 | const std = @import("std");
2 | const builtin = @import("builtin");
3 | const html = @import("html.zig");
4 | const Span = @import("root.zig").Span;
5 |
6 | pub const ErrWriter = std.ArrayListUnmanaged(u8).Writer;
7 |
8 | /// Used to catch programming errors where a function fails to report
9 | /// correctly that an error has occurred.
10 | pub const Fatal = error{
11 | /// The error has been fully reported.
12 | Fatal,
13 |
14 | /// There was an error while outputting to the error writer.
15 | ErrIO,
16 |
17 | /// There war an error while outputting to the out writer.
18 | OutIO,
19 | };
20 |
21 | pub const FatalOOM = error{OutOfMemory} || Fatal;
22 |
23 | pub const FatalShow = Fatal || error{
24 | /// The error has been reported but we should also print the
25 | /// interface of the template we are extending.
26 | FatalShowInterface,
27 | };
28 |
29 | pub const FatalShowOOM = error{OutOfMemory} || FatalShow;
30 |
31 | pub fn report(
32 | writer: ErrWriter,
33 | template_name: []const u8,
34 | template_path: []const u8,
35 | bad_node: Span,
36 | src: []const u8,
37 | error_code: []const u8,
38 | comptime title: []const u8,
39 | comptime msg: []const u8,
40 | ) Fatal {
41 | try header(writer, title, msg);
42 | try diagnostic(
43 | writer,
44 | template_name,
45 | template_path,
46 | true,
47 | error_code,
48 | bad_node,
49 | src,
50 | );
51 | return error.Fatal;
52 | }
53 |
54 | pub fn diagnostic(
55 | writer: ErrWriter,
56 | template_name: []const u8,
57 | template_path: []const u8,
58 | bracket_line: bool,
59 | note_line: []const u8,
60 | span: Span,
61 | src: []const u8,
62 | ) error{ErrIO}!void {
63 | const pos = span.range(src);
64 | const line_off = span.line(src);
65 |
66 | // trim spaces
67 | const line_trim_left = std.mem.trimLeft(u8, line_off.line, &std.ascii.whitespace);
68 | const start_trim_left = line_off.start + line_off.line.len - line_trim_left.len;
69 |
70 | const caret_len = span.end - span.start;
71 | const caret_spaces_len = span.start -| start_trim_left;
72 |
73 | const line_trim = std.mem.trimRight(u8, line_trim_left, &std.ascii.whitespace);
74 |
75 | var buf: [1024]u8 = undefined;
76 |
77 | const highlight = if (caret_len + caret_spaces_len < 1024) blk: {
78 | const h = buf[0 .. caret_len + caret_spaces_len];
79 | @memset(h[0..caret_spaces_len], ' ');
80 | @memset(h[caret_spaces_len..][0..caret_len], '^');
81 | break :blk h;
82 | } else "";
83 |
84 | writer.print(
85 | \\
86 | \\{s}{s}{s}
87 | \\({s}) {s}:{}:{}:
88 | \\ {s}
89 | \\ {s}
90 | \\
91 | , .{
92 | if (bracket_line) "[" else "",
93 | note_line,
94 | if (bracket_line) "]" else "",
95 | template_name,
96 | template_path,
97 | pos.start.row,
98 | pos.start.col,
99 | line_trim,
100 | highlight,
101 | }) catch return error.ErrIO;
102 | }
103 |
104 | pub fn header(
105 | writer: ErrWriter,
106 | comptime title: []const u8,
107 | comptime msg: []const u8,
108 | ) error{ErrIO}!void {
109 | writer.print(
110 | \\
111 | \\---------- {s} ----------
112 | \\
113 | , .{title}) catch return error.ErrIO;
114 | writer.print(msg, .{}) catch return error.ErrIO;
115 | writer.print("\n", .{}) catch return error.ErrIO;
116 | }
117 |
118 | pub fn fatal(
119 | writer: ErrWriter,
120 | comptime fmt: []const u8,
121 | args: anytype,
122 | ) Fatal {
123 | writer.print(fmt, args) catch return error.ErrIO;
124 | return error.ErrIO;
125 | }
126 |
--------------------------------------------------------------------------------
/src/fuzz.zig:
--------------------------------------------------------------------------------
1 | const std = @import("std");
2 | const super = @import("superhtml");
3 |
4 | pub const std_options: std.Options = .{ .log_level = .err };
5 |
6 | /// This main function is meant to be used via black box fuzzers
7 | /// and/or to manually weed out test cases that are not valid anymore
8 | /// after fixing bugs.
9 | ///
10 | /// See fuzz/afl.zig for the AFL++ specific executable.
11 | pub fn main() !void {
12 | var gpa_impl: std.heap.GeneralPurposeAllocator(.{}) = .{};
13 | const gpa = gpa_impl.allocator();
14 |
15 | const stdin = std.io.getStdIn();
16 | const src = try stdin.readToEndAlloc(gpa, std.math.maxInt(usize));
17 | defer gpa.free(src);
18 |
19 | const ast = try super.html.Ast.init(gpa, src, .html);
20 | defer ast.deinit(gpa);
21 |
22 | if (ast.errors.len == 0) {
23 | try ast.render(src, std.io.null_writer);
24 | }
25 | }
26 |
27 | test "afl++ fuzz cases" {
28 | const cases: []const []const u8 = &.{
29 | @embedFile("fuzz/cases/2.html"),
30 | @embedFile("fuzz/cases/3.html"),
31 | @embedFile("fuzz/cases/12.html"),
32 | @embedFile("fuzz/cases/round2/2.html"),
33 | @embedFile("fuzz/cases/round2/3.html"),
34 | @embedFile("fuzz/cases/round3/2.html"),
35 | @embedFile("fuzz/cases/77.html"),
36 | @embedFile("fuzz/cases/3-01.html"),
37 | @embedFile("fuzz/cases/4-01.html"),
38 | @embedFile("fuzz/cases/5-01.html"),
39 | @embedFile("fuzz/cases/6-01.html"),
40 | @embedFile("fuzz/cases/6-02.html"),
41 | };
42 |
43 | for (cases) |c| {
44 | // std.debug.print("test: \n\n{s}\n\n", .{c});
45 | const ast = try super.html.Ast.init(std.testing.allocator, c, .html);
46 | defer ast.deinit(std.testing.allocator);
47 | if (ast.errors.len == 0) {
48 | try ast.render(c, std.io.null_writer);
49 | }
50 | // ast.debug(c);
51 | }
52 | }
53 |
--------------------------------------------------------------------------------
/src/fuzz/afl.zig:
--------------------------------------------------------------------------------
1 | const std = @import("std");
2 | const super = @import("superhtml");
3 | const astgen = @import("astgen.zig");
4 |
5 | pub const std_options: std.Options = .{ .log_level = .err };
6 |
7 | export fn zig_fuzz_init() void {}
8 |
9 | export fn zig_fuzz_test(buf: [*]u8, len: isize) void {
10 | var gpa_impl: std.heap.GeneralPurposeAllocator(.{}) = .{};
11 | defer std.debug.assert(gpa_impl.deinit() == .ok);
12 |
13 | const gpa = gpa_impl.allocator();
14 | const src = buf[0..@intCast(len)];
15 |
16 | const html_ast = super.html.Ast.init(gpa, src, .superhtml) catch unreachable;
17 | defer html_ast.deinit(gpa);
18 |
19 | // if (html_ast.errors.len == 0) {
20 | // const super_ast = super.Ast.init(gpa, html_ast, src) catch unreachable;
21 | // defer super_ast.deinit(gpa);
22 | // }
23 |
24 | if (html_ast.errors.len == 0) {
25 | var out = std.ArrayList(u8).init(gpa);
26 | defer out.deinit();
27 | html_ast.render(src, out.writer()) catch unreachable;
28 |
29 | eqlIgnoreWhitespace(src, out.items);
30 |
31 | var full_circle = std.ArrayList(u8).init(gpa);
32 | defer full_circle.deinit();
33 | html_ast.render(out.items, full_circle.writer()) catch unreachable;
34 |
35 | std.debug.assert(std.mem.eql(u8, out.items, full_circle.items));
36 |
37 | const super_ast = super.Ast.init(gpa, html_ast, src) catch unreachable;
38 | defer super_ast.deinit(gpa);
39 | }
40 | }
41 |
42 | export fn zig_fuzz_test_astgen(buf: [*]u8, len: isize) void {
43 | var gpa_impl: std.heap.GeneralPurposeAllocator(.{}) = .{};
44 | const gpa = gpa_impl.allocator();
45 | const astgen_src = buf[0..@intCast(len)];
46 |
47 | const clamp: u32 = @min(20, astgen_src.len);
48 | const src = astgen.build(gpa, astgen_src[0..clamp]) catch unreachable;
49 | defer gpa.free(src);
50 |
51 | const html_ast = super.html.Ast.init(gpa, src, .superhtml) catch unreachable;
52 | defer html_ast.deinit(gpa);
53 |
54 | std.debug.assert(html_ast.errors.len == 0);
55 |
56 | const super_ast = super.Ast.init(gpa, html_ast, src) catch unreachable;
57 | defer super_ast.deinit(gpa);
58 |
59 | // if (html_ast.errors.len == 0) {
60 | // var out = std.ArrayList(u8).init(gpa);
61 | // defer out.deinit();
62 | // html_ast.render(src, out.writer()) catch unreachable;
63 |
64 | // eqlIgnoreWhitespace(src, out.items);
65 |
66 | // var full_circle = std.ArrayList(u8).init(gpa);
67 | // defer full_circle.deinit();
68 | // html_ast.render(out.items, full_circle.writer()) catch unreachable;
69 |
70 | // std.debug.assert(std.mem.eql(u8, out.items, full_circle.items));
71 |
72 | // const super_ast = super.Ast.init(gpa, html_ast, src) catch unreachable;
73 | // defer super_ast.deinit(gpa);
74 | // }
75 | }
76 |
77 | fn eqlIgnoreWhitespace(a: []const u8, b: []const u8) void {
78 | var i: u32 = 0;
79 | var j: u32 = 0;
80 |
81 | while (i < a.len) : (i += 1) {
82 | const a_byte = a[i];
83 | if (std.ascii.isWhitespace(a_byte)) continue;
84 | while (j < b.len) : (j += 1) {
85 | const b_byte = b[j];
86 | if (std.ascii.isWhitespace(b_byte)) continue;
87 |
88 | if (a_byte != b_byte) {
89 | const a_span: super.Span = .{ .start = i, .end = i + 1 };
90 | const b_span: super.Span = .{ .start = j, .end = j + 1 };
91 | std.debug.panic("mismatch! {c} != {c} \na = {any}\nb={any}\n", .{
92 | a_byte,
93 | b_byte,
94 | a_span.range(a),
95 | b_span.range(b),
96 | });
97 | }
98 | }
99 | }
100 | }
101 |
102 | test "afl++ fuzz cases" {
103 | const cases: []const []const u8 = &.{
104 | @embedFile("fuzz/2.html"),
105 | @embedFile("fuzz/3.html"),
106 | @embedFile("fuzz/12.html"),
107 | @embedFile("fuzz/round2/2.html"),
108 | @embedFile("fuzz/round2/3.html"),
109 | @embedFile("fuzz/round3/2.html"),
110 | @embedFile("fuzz/77.html"),
111 | @embedFile("fuzz/3-01.html"),
112 | @embedFile("fuzz/4-01.html"),
113 | @embedFile("fuzz/5-01.html"),
114 | };
115 |
116 | for (cases) |c| {
117 | // std.debug.print("test: \n\n{s}\n\n", .{c});
118 | const ast = try super.html.Ast.init(std.testing.allocator, c, .html);
119 | defer ast.deinit(std.testing.allocator);
120 | if (ast.errors.len == 0) {
121 | try ast.render(c, std.io.null_writer);
122 | }
123 | // ast.debug(c);
124 | }
125 | }
126 |
--------------------------------------------------------------------------------
/src/fuzz/astgen.zig:
--------------------------------------------------------------------------------
1 | const std = @import("std");
2 | const builtin = @import("builtin");
3 | const super = @import("superhtml");
4 |
5 | const Op = enum(u8) {
6 | // add element
7 | n = 'n',
8 | // add element and give it a template attribute
9 | N = 'N',
10 | // add element
11 | s = 's',
12 | // add text node
13 | t = 't',
14 | // add comment node
15 | c = 'c',
16 | // add new element, enter it
17 | e = 'e',
18 | // add a new non-semantic void element
19 | E = 'E',
20 | // add an id attribute
21 | // (break if any 'u', 'c', or 't' was sent after the last 'e' or 'E')
22 | // (break if another attribute of the same kind was already added)
23 | i = 'i',
24 | // add non-semantic attribute to selected node
25 | // (break if any 'u', 'c', or 't' was sent after the last 'e' or 'E')
26 | a = 'a',
27 | // add loop attribute
28 | // (break if any 'u', 'c', or 't' was sent after the last 'e' or 'E')
29 | // (break if another attribute of the same kind was already added)
30 | l = 'l',
31 | // add an inline-loop attribute
32 | // (break if any 'u', 'c', or 't' was sent after the last 'e' or 'E')
33 | // (break if another attribute of the same kind was already added)
34 | L = 'L',
35 | // add an if attribute
36 | // (break if any 'u', 'c', or 't' was sent after the last 'e' or 'E')
37 | // (break if another attribute of the same kind was already added)
38 | f = 'f',
39 | // add an inline-if attribute
40 | // (break if any 'u', 'c', or 't' was sent after the last 'e' or 'E')
41 | // (break if another attribute of the same kind was already added)
42 | F = 'F',
43 | // add a var attribute
44 | // (break if any 'u', 'c', or 't' was sent after the last 'e' or 'E')
45 | // (break if another attribute of the same kind was already added)
46 | v = 'v',
47 | // add an empty attribute value
48 | // (break when not put in front of an attribute Op)
49 | x = 'x',
50 | // add a static non-scripted attribute value
51 | // (break when not put in front of an attribute Op)
52 | X = 'X',
53 | // add a scripted attribute value
54 | // (break when not put in front of an attribute Op)
55 | y = 'y',
56 | // add a unique non-scripted attribute value
57 | // (break when not put in front of an attribute Op)
58 | Y = 'Y',
59 | // select the parent element of the current element
60 | // (break when a top-level element is already selected)
61 | u = 'u',
62 | // add whitespace
63 | // (consecutive 'w' on the same element will cause a break)
64 | w = 'w',
65 |
66 | // noop
67 | _,
68 | };
69 |
70 | const Element = struct {
71 | // a span of Ops that describes a list of attrs
72 | attrs: super.Span = .{ .start = 0, .end = 0 },
73 | kind: Tag = .none,
74 | whitespace: bool = false,
75 |
76 | pub const Tag = enum { none, div, super, extend, br, comment, text };
77 |
78 | pub fn commit(
79 | e: *Element,
80 | w: anytype,
81 | src: []const u8,
82 | ends: *std.ArrayList(Tag),
83 | ) !void {
84 | switch (e.kind) {
85 | .comment => {
86 | if (e.whitespace) try w.writeAll("\n");
87 | try w.writeAll("");
88 | e.* = .{};
89 | return;
90 | },
91 | .text => {
92 | if (e.whitespace) try w.writeAll("\n");
93 | try w.writeAll("X");
94 | e.* = .{};
95 | return;
96 | },
97 | .none => {
98 | e.* = .{};
99 | return;
100 | },
101 | .div, .super, .extend, .br => {
102 | if (e.whitespace) try w.writeAll("\n");
103 | },
104 | }
105 |
106 | try w.print("<{s}", .{@tagName(e.kind)});
107 | defer {
108 | w.writeAll(">") catch unreachable;
109 | switch (e.kind) {
110 | .div => ends.append(e.kind) catch unreachable,
111 | .super, .br, .extend, .none, .text, .comment => {},
112 | }
113 | e.* = .{};
114 | }
115 |
116 | var has_id = false;
117 | var has_loop = false;
118 | var has_inl_loop = false;
119 | var has_if = false;
120 | var has_inl_if = false;
121 | var has_var = false;
122 | var idx = e.attrs.start;
123 | while (idx < e.attrs.end) : (idx += 1) {
124 | var attribute_was_added = true;
125 | const op: Op = @enumFromInt(src[idx]);
126 | switch (op) {
127 | .N => {
128 | try w.writeAll(" template='x'");
129 | attribute_was_added = false;
130 | },
131 | .a => try w.print(" a{}", .{idx}),
132 | .i => if (!has_id) {
133 | try w.writeAll(" id");
134 | has_id = true;
135 | } else {
136 | return error.Break;
137 | },
138 | .l => if (!has_loop) {
139 | try w.writeAll(" loop");
140 | has_loop = true;
141 | } else {
142 | return error.Break;
143 | },
144 | .L => if (!has_inl_loop) {
145 | try w.writeAll(" inline-loop");
146 | has_inl_loop = true;
147 | } else {
148 | return error.Break;
149 | },
150 | .f => if (!has_if) {
151 | try w.writeAll(" if");
152 | has_if = true;
153 | } else {
154 | return error.Break;
155 | },
156 | .F => if (!has_inl_if) {
157 | try w.writeAll(" inline-if");
158 | has_inl_if = true;
159 | } else {
160 | return error.Break;
161 | },
162 | .v => if (!has_var) {
163 | try w.writeAll(" var");
164 | has_var = true;
165 | } else {
166 | return error.Break;
167 | },
168 | .w => attribute_was_added = false,
169 | else => {
170 | return error.Break;
171 | },
172 | }
173 |
174 | if (attribute_was_added and idx < e.attrs.end - 1) {
175 | idx += 1;
176 | const op_next: Op = @enumFromInt(src[idx]);
177 | switch (op_next) {
178 | .x => try w.writeAll("=''"),
179 | .X => try w.writeAll("='x'"),
180 | .y => try w.writeAll("='$'"),
181 | .Y => try w.print("='{}'", .{idx}),
182 | else => idx -= 1,
183 | }
184 | }
185 | }
186 | }
187 | };
188 |
189 | pub fn build(gpa: std.mem.Allocator, src: []const u8) ![]const u8 {
190 | var out = std.ArrayList(u8).init(gpa);
191 | var ends = std.ArrayList(Element.Tag).init(gpa);
192 | const w = out.writer();
193 | var current: Element = .{};
194 |
195 | buildInternal(w, src, &ends, ¤t) catch |err| switch (err) {
196 | error.Break => {},
197 | else => unreachable,
198 | };
199 |
200 | current.commit(w, src, &ends) catch |err| switch (err) {
201 | error.Break => {},
202 | else => unreachable,
203 | };
204 |
205 | while (ends.popOrNull()) |kind|
206 | try w.print("{s}>", .{@tagName(kind)});
207 |
208 | return out.items;
209 | }
210 | pub fn buildInternal(
211 | w: anytype,
212 | src: []const u8,
213 | ends: *std.ArrayList(Element.Tag),
214 | current: *Element,
215 | ) !void {
216 | for (src, 0..) |c, i| {
217 | const idx: u32 = @intCast(i);
218 | const op: Op = @enumFromInt(c);
219 | switch (op) {
220 | // add attribute
221 | .n => {
222 | try current.commit(w, src, ends);
223 | current.kind = .extend;
224 | },
225 | // add attribute and give it a template attribute
226 | .N => {
227 | try current.commit(w, src, ends);
228 | current.kind = .extend;
229 | current.attrs = .{ .start = idx, .end = idx + 1 };
230 | },
231 | // add new element, enter it
232 | .e => {
233 | try current.commit(w, src, ends);
234 | current.kind = .div;
235 | },
236 | // add a new non-semantic void element
237 | .E => {
238 | try current.commit(w, src, ends);
239 | current.kind = .br;
240 | },
241 | // add element
242 | .s => {
243 | try current.commit(w, src, ends);
244 | current.kind = .super;
245 | },
246 | // add element into the current element and give id
247 | // attribute to the parent if needed
248 | // (noop if any 'u', 'c', or 't' was sent after the last 'e' or 'E')
249 | // .S => switch (current.kind) {
250 | // .none, .comment, .text => continue,
251 | // .div, .super, .extend, .br => {
252 | // if (current.attrs.end == 0) {
253 | // current.attrs = .{ .start = idx, .end = idx + 1 };
254 | // } else {
255 | // current.attrs.end = idx + 1;
256 | // }
257 | // try current.commit(w, src, ends);
258 | // current.kind = .super;
259 | // },
260 | // },
261 | // add text element
262 | .t => {
263 | try current.commit(w, src, ends);
264 | current.kind = .text;
265 | },
266 | // add comment
267 | .c => {
268 | try current.commit(w, src, ends);
269 | current.kind = .comment;
270 | },
271 | // attributes
272 | // (noop if any 'u', 'c', or 't' was sent after the last 'e' or 'E')
273 | .a, .l, .L, .f, .F, .v, .i, .x, .X, .y, .Y => switch (current.kind) {
274 | .none, .comment, .text => break,
275 | .div, .super, .extend, .br => {
276 | if (current.attrs.end == 0) {
277 | current.attrs = .{ .start = idx, .end = idx + 1 };
278 | } else {
279 | current.attrs.end = idx + 1;
280 | }
281 | },
282 | },
283 | // select the parent element of the current element
284 | // (noop when a top-level element is already selected)
285 | .u => {
286 | try current.commit(w, src, ends);
287 | if (ends.popOrNull()) |kind|
288 | try w.print("{s}>", .{@tagName(kind)})
289 | else
290 | break;
291 | },
292 | // add whitespace
293 | // (consecutive 'w' on the same element are noops)
294 | .w => {
295 | if (current.whitespace) break;
296 | current.whitespace = true;
297 | },
298 |
299 | // early return to avoid keeping "dead bytes"
300 | // in the active set of bytes, ideally improving
301 | // fuzzer performance
302 | else => break,
303 | }
304 | }
305 | }
306 |
307 | pub fn main() !void {
308 | var gpa_impl: std.heap.GeneralPurposeAllocator(.{}) = .{};
309 | const gpa = gpa_impl.allocator();
310 |
311 | const args = try std.process.argsAlloc(gpa);
312 | defer std.process.argsFree(gpa, args);
313 |
314 | if (args.len != 2) @panic("wrong number of arguments");
315 | const src = args[1];
316 |
317 | const out = try build(gpa, src);
318 | try std.io.getStdOut().writeAll(out);
319 | }
320 |
--------------------------------------------------------------------------------
/src/fuzz/cases/12.html:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/kristoff-it/superhtml/13f5a2221cb748bbe50ad702e89362afd5b925a7/src/fuzz/cases/12.html
--------------------------------------------------------------------------------
/src/fuzz/cases/2.html:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/kristoff-it/superhtml/13f5a2221cb748bbe50ad702e89362afd5b925a7/src/fuzz/cases/2.html
--------------------------------------------------------------------------------
/src/fuzz/cases/3-01.html:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 | >
7 |
8 |
9 |
10 |
11 | 6
12 |
--------------------------------------------------------------------------------
/src/fuzz/cases/3.html:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/kristoff-it/superhtml/13f5a2221cb748bbe50ad702e89362afd5b925a7/src/fuzz/cases/3.html
--------------------------------------------------------------------------------
/src/fuzz/cases/4-01.html:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/kristoff-it/superhtml/13f5a2221cb748bbe50ad702e89362afd5b925a7/src/fuzz/cases/4-01.html
--------------------------------------------------------------------------------
/src/fuzz/cases/5-01.html:
--------------------------------------------------------------------------------
1 | ]<t<
3 | <dlh/>T>
7 | try out_stream.writeAll("&"),
23 | '>' => try out_stream.writeAll(">"),
24 | '<' => try out_stream.writeAll("<"),
25 | '\'' => try out_stream.writeAll("'"),
26 | '\"' => try out_stream.writeAll("""),
27 | else => try out_stream.writeByte(b),
28 | }
29 | }
30 | }
31 | };
32 |
33 | pub const utils = struct {
34 | pub fn IteratorContext(comptime Value: type, comptime Template: type) type {
35 | return struct {
36 | idx: u32 = undefined,
37 | tpl: *const VM(Template, Value).Template = undefined,
38 |
39 | pub fn up(lc: @This()) Value {
40 | return lc.tpl.loopUp(lc.idx);
41 | }
42 | };
43 | }
44 |
45 | pub fn Ctx(comptime Value: type) type {
46 | return struct {
47 | _map: *const std.StringHashMapUnmanaged(Value) = undefined,
48 |
49 | pub fn dot(
50 | ctx: *const @This(),
51 | _: std.mem.Allocator,
52 | path: []const u8,
53 | ) !Value {
54 | return ctx._map.get(path) orelse .{ .err = "field not found" };
55 | }
56 | pub const docs_description =
57 | \\A special map that contains all the attributes
58 | \\ defined on `` in the current scope.
59 | \\
60 | \\You can access the available fields using dot notation.
61 | \\
62 | \\Example:
63 | \\```superhtml
64 | \\
65 | \\
66 | \\
67 | \\
68 | \\
69 | \\
70 | \\```
71 | ;
72 | pub const Builtins = struct {};
73 | };
74 | }
75 | };
76 |
77 | pub const Language = enum {
78 | html,
79 | superhtml,
80 | xml,
81 |
82 | /// Use to map file extensions to a Language, supports aliases.
83 | pub fn fromSliceResilient(s: []const u8) ?Language {
84 | const Alias = enum { html, superhtml, shtml, xml };
85 |
86 | const alias = std.meta.stringToEnum(Alias, s) orelse {
87 | return null;
88 | };
89 |
90 | return switch (alias) {
91 | .superhtml, .shtml => .superhtml,
92 | .html => .html,
93 | .xml => .xml,
94 | };
95 | }
96 | };
97 | pub const max_size = 4 * 1024 * 1024 * 1024;
98 |
99 | const Range = struct {
100 | start: Pos,
101 | end: Pos,
102 |
103 | const Pos = struct {
104 | row: u32,
105 | col: u32,
106 | };
107 | };
108 |
109 | pub const Line = struct { line: []const u8, start: u32 };
110 |
111 | pub const Span = struct {
112 | start: u32,
113 | end: u32,
114 |
115 | pub fn len(span: Span) u32 {
116 | return span.end - span.start;
117 | }
118 |
119 | pub fn slice(self: Span, src: []const u8) []const u8 {
120 | return src[self.start..self.end];
121 | }
122 |
123 | pub fn range(self: Span, code: []const u8) Range {
124 | var selection: Range = .{
125 | .start = .{ .row = 0, .col = 0 },
126 | .end = undefined,
127 | };
128 |
129 | for (code[0..self.start]) |c| {
130 | if (c == '\n') {
131 | selection.start.row += 1;
132 | selection.start.col = 0;
133 | } else selection.start.col += 1;
134 | }
135 |
136 | selection.end = selection.start;
137 | for (code[self.start..self.end]) |c| {
138 | if (c == '\n') {
139 | selection.end.row += 1;
140 | selection.end.col = 0;
141 | } else selection.end.col += 1;
142 | }
143 | return selection;
144 | }
145 |
146 | /// Finds the line around a Node. Choose simple nodes
147 | // if you don't want unwanted newlines in the middle.
148 | pub fn line(span: Span, src: []const u8) Line {
149 | var idx = span.start;
150 | const s = while (idx > 0) : (idx -= 1) {
151 | if (src[idx] == '\n') break idx + 1;
152 | } else 0;
153 |
154 | idx = span.end;
155 | const e = while (idx < src.len) : (idx += 1) {
156 | if (src[idx] == '\n') break idx;
157 | } else src.len - 1;
158 |
159 | return .{ .line = src[s..e], .start = s };
160 | }
161 |
162 | pub fn getName(span: Span, full_src: []const u8, language: Language) Span {
163 | var temp_tok: html.Tokenizer = .{
164 | .language = language,
165 | .return_attrs = true,
166 | .idx = span.start,
167 | };
168 |
169 | const src = full_src[0..span.end];
170 | return temp_tok.getName(src).?;
171 | }
172 |
173 | pub fn debug(span: Span, src: []const u8) void {
174 | std.debug.print("{s}", .{span.slice(src)});
175 | }
176 | };
177 |
178 | test {
179 | _ = @import("html.zig");
180 | _ = @import("Ast.zig");
181 | // _ = @import("template.zig");
182 | }
183 |
--------------------------------------------------------------------------------
/src/wasm.zig:
--------------------------------------------------------------------------------
1 | const std = @import("std");
2 | const builtin = @import("builtin");
3 | const super = @import("super");
4 | const lsp_exe = @import("cli/lsp.zig");
5 |
6 | pub fn main() !void {
7 | const gpa = std.heap.wasm_allocator;
8 | try lsp_exe.run(gpa, &.{});
9 | }
10 |
--------------------------------------------------------------------------------
/tree-sitter-superhtml/.editorconfig:
--------------------------------------------------------------------------------
1 | root = true
2 |
3 | [*]
4 | charset = utf-8
5 |
6 | [*.{json,toml,yml,gyp}]
7 | indent_style = space
8 | indent_size = 2
9 |
10 | [*.js]
11 | indent_style = space
12 | indent_size = 2
13 |
14 | [*.rs]
15 | indent_style = space
16 | indent_size = 4
17 |
18 | [*.{c,cc,h}]
19 | indent_style = space
20 | indent_size = 4
21 |
22 | [*.{py,pyi}]
23 | indent_style = space
24 | indent_size = 4
25 |
26 | [*.swift]
27 | indent_style = space
28 | indent_size = 4
29 |
30 | [*.go]
31 | indent_style = tab
32 | indent_size = 8
33 |
34 | [Makefile]
35 | indent_style = tab
36 | indent_size = 8
37 |
--------------------------------------------------------------------------------
/tree-sitter-superhtml/.gitattributes:
--------------------------------------------------------------------------------
1 | * text=auto eol=lf
2 |
3 | src/*.json linguist-generated
4 | src/parser.c linguist-generated
5 | src/tree_sitter/* linguist-generated
6 |
7 | bindings/** linguist-generated
8 | binding.gyp linguist-generated
9 | setup.py linguist-generated
10 | Makefile linguist-generated
11 | Package.swift linguist-generated
12 |
--------------------------------------------------------------------------------
/tree-sitter-superhtml/.gitignore:
--------------------------------------------------------------------------------
1 | # Rust artifacts
2 | Cargo.lock
3 | target/
4 |
5 | # Node artifacts
6 | build/
7 | prebuilds/
8 | node_modules/
9 | *.tgz
10 |
11 | # Swift artifacts
12 | .build/
13 | Package.resolved
14 |
15 | # Go artifacts
16 | go.sum
17 | _obj/
18 |
19 | # Python artifacts
20 | .venv/
21 | dist/
22 | *.egg-info
23 | *.whl
24 |
25 | # C artifacts
26 | *.a
27 | *.so
28 | *.so.*
29 | *.dylib
30 | *.dll
31 | *.pc
32 |
33 | # Example dirs
34 | /examples/*/
35 |
36 | # Grammar volatiles
37 | *.wasm
38 | *.obj
39 | *.o
40 |
--------------------------------------------------------------------------------
/tree-sitter-superhtml/.npmignore:
--------------------------------------------------------------------------------
1 | corpus
2 | examples
3 | build
4 | script
5 | target
6 | Cargo.lock
7 |
--------------------------------------------------------------------------------
/tree-sitter-superhtml/Cargo.toml:
--------------------------------------------------------------------------------
1 | [package]
2 | name = "tree-sitter-superhtml"
3 | description = "SuperHTML TreeSitter Grammar"
4 | version = "0.20.3"
5 | authors = ["Loris Cro"]
6 | license = "MIT"
7 | keywords = ["incremental", "parsing", "tree-sitter", "html", "superhtml"]
8 | categories = ["parsing", "text-editors"]
9 | repository = "https://github.com/kristoff-it/superhtml/tree-sitter-superhtml"
10 | edition = "2021"
11 | autoexamples = false
12 |
13 | build = "bindings/rust/build.rs"
14 | include = ["bindings/rust/*", "grammar.js", "queries/*", "src/*"]
15 |
16 | [lib]
17 | path = "bindings/rust/lib.rs"
18 |
19 | [dependencies]
20 | tree-sitter = ">=0.21.0"
21 |
22 | [build-dependencies]
23 | cc = "1.0.87"
24 |
--------------------------------------------------------------------------------
/tree-sitter-superhtml/LICENSE:
--------------------------------------------------------------------------------
1 | The MIT License (MIT)
2 |
3 | Copyright (c) 2024 Loris Cro
4 | Copyright (c) 2014 Max Brunsfeld
5 |
6 | Permission is hereby granted, free of charge, to any person obtaining a copy
7 | of this software and associated documentation files (the "Software"), to deal
8 | in the Software without restriction, including without limitation the rights
9 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
10 | copies of the Software, and to permit persons to whom the Software is
11 | furnished to do so, subject to the following conditions:
12 |
13 | The above copyright notice and this permission notice shall be included in all
14 | copies or substantial portions of the Software.
15 |
16 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
17 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
18 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
19 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
20 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
21 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
22 | SOFTWARE.
23 |
--------------------------------------------------------------------------------
/tree-sitter-superhtml/Makefile:
--------------------------------------------------------------------------------
1 | VERSION := 0.20.3
2 |
3 | LANGUAGE_NAME := tree-sitter-html
4 |
5 | # repository
6 | SRC_DIR := src
7 |
8 | PARSER_REPO_URL := $(shell git -C $(SRC_DIR) remote get-url origin 2>/dev/null)
9 |
10 | ifeq ($(PARSER_URL),)
11 | PARSER_URL := $(subst .git,,$(PARSER_REPO_URL))
12 | ifeq ($(shell echo $(PARSER_URL) | grep '^[a-z][-+.0-9a-z]*://'),)
13 | PARSER_URL := $(subst :,/,$(PARSER_URL))
14 | PARSER_URL := $(subst git@,https://,$(PARSER_URL))
15 | endif
16 | endif
17 |
18 | TS ?= tree-sitter
19 |
20 | # ABI versioning
21 | SONAME_MAJOR := $(word 1,$(subst ., ,$(VERSION)))
22 | SONAME_MINOR := $(word 2,$(subst ., ,$(VERSION)))
23 |
24 | # install directory layout
25 | PREFIX ?= /usr/local
26 | INCLUDEDIR ?= $(PREFIX)/include
27 | LIBDIR ?= $(PREFIX)/lib
28 | PCLIBDIR ?= $(LIBDIR)/pkgconfig
29 |
30 | # object files
31 | OBJS := $(patsubst %.c,%.o,$(wildcard $(SRC_DIR)/*.c))
32 |
33 | # flags
34 | ARFLAGS := rcs
35 | override CFLAGS += -I$(SRC_DIR) -std=c11 -fPIC
36 |
37 | # OS-specific bits
38 | ifeq ($(OS),Windows_NT)
39 | $(error "Windows is not supported")
40 | else ifeq ($(shell uname),Darwin)
41 | SOEXT = dylib
42 | SOEXTVER_MAJOR = $(SONAME_MAJOR).dylib
43 | SOEXTVER = $(SONAME_MAJOR).$(SONAME_MINOR).dylib
44 | LINKSHARED := $(LINKSHARED)-dynamiclib -Wl,
45 | ifneq ($(ADDITIONAL_LIBS),)
46 | LINKSHARED := $(LINKSHARED)$(ADDITIONAL_LIBS),
47 | endif
48 | LINKSHARED := $(LINKSHARED)-install_name,$(LIBDIR)/lib$(LANGUAGE_NAME).$(SONAME_MAJOR).dylib,-rpath,@executable_path/../Frameworks
49 | else
50 | SOEXT = so
51 | SOEXTVER_MAJOR = so.$(SONAME_MAJOR)
52 | SOEXTVER = so.$(SONAME_MAJOR).$(SONAME_MINOR)
53 | LINKSHARED := $(LINKSHARED)-shared -Wl,
54 | ifneq ($(ADDITIONAL_LIBS),)
55 | LINKSHARED := $(LINKSHARED)$(ADDITIONAL_LIBS)
56 | endif
57 | LINKSHARED := $(LINKSHARED)-soname,lib$(LANGUAGE_NAME).so.$(SONAME_MAJOR)
58 | endif
59 | ifneq ($(filter $(shell uname),FreeBSD NetBSD DragonFly),)
60 | PCLIBDIR := $(PREFIX)/libdata/pkgconfig
61 | endif
62 |
63 | all: lib$(LANGUAGE_NAME).a lib$(LANGUAGE_NAME).$(SOEXT) $(LANGUAGE_NAME).pc
64 |
65 | lib$(LANGUAGE_NAME).a: $(OBJS)
66 | $(AR) $(ARFLAGS) $@ $^
67 |
68 | lib$(LANGUAGE_NAME).$(SOEXT): $(OBJS)
69 | $(CC) $(LDFLAGS) $(LINKSHARED) $^ $(LDLIBS) -o $@
70 | ifneq ($(STRIP),)
71 | $(STRIP) $@
72 | endif
73 |
74 | $(LANGUAGE_NAME).pc: bindings/c/$(LANGUAGE_NAME).pc.in
75 | sed -e 's|@URL@|$(PARSER_URL)|' \
76 | -e 's|@VERSION@|$(VERSION)|' \
77 | -e 's|@LIBDIR@|$(LIBDIR)|' \
78 | -e 's|@INCLUDEDIR@|$(INCLUDEDIR)|' \
79 | -e 's|@REQUIRES@|$(REQUIRES)|' \
80 | -e 's|@ADDITIONAL_LIBS@|$(ADDITIONAL_LIBS)|' \
81 | -e 's|=$(PREFIX)|=$${prefix}|' \
82 | -e 's|@PREFIX@|$(PREFIX)|' $< > $@
83 |
84 | $(SRC_DIR)/parser.c: grammar.js
85 | $(TS) generate --no-bindings
86 |
87 | install: all
88 | install -d '$(DESTDIR)$(INCLUDEDIR)'/tree_sitter '$(DESTDIR)$(PCLIBDIR)' '$(DESTDIR)$(LIBDIR)'
89 | install -m644 bindings/c/$(LANGUAGE_NAME).h '$(DESTDIR)$(INCLUDEDIR)'/tree_sitter/$(LANGUAGE_NAME).h
90 | install -m644 $(LANGUAGE_NAME).pc '$(DESTDIR)$(PCLIBDIR)'/$(LANGUAGE_NAME).pc
91 | install -m644 lib$(LANGUAGE_NAME).a '$(DESTDIR)$(LIBDIR)'/lib$(LANGUAGE_NAME).a
92 | install -m755 lib$(LANGUAGE_NAME).$(SOEXT) '$(DESTDIR)$(LIBDIR)'/lib$(LANGUAGE_NAME).$(SOEXTVER)
93 | ln -sf lib$(LANGUAGE_NAME).$(SOEXTVER) '$(DESTDIR)$(LIBDIR)'/lib$(LANGUAGE_NAME).$(SOEXTVER_MAJOR)
94 | ln -sf lib$(LANGUAGE_NAME).$(SOEXTVER_MAJOR) '$(DESTDIR)$(LIBDIR)'/lib$(LANGUAGE_NAME).$(SOEXT)
95 |
96 | uninstall:
97 | $(RM) '$(DESTDIR)$(LIBDIR)'/lib$(LANGUAGE_NAME).a \
98 | '$(DESTDIR)$(LIBDIR)'/lib$(LANGUAGE_NAME).$(SOEXTVER) \
99 | '$(DESTDIR)$(LIBDIR)'/lib$(LANGUAGE_NAME).$(SOEXTVER_MAJOR) \
100 | '$(DESTDIR)$(LIBDIR)'/lib$(LANGUAGE_NAME).$(SOEXT) \
101 | '$(DESTDIR)$(INCLUDEDIR)'/tree_sitter/$(LANGUAGE_NAME).h \
102 | '$(DESTDIR)$(PCLIBDIR)'/$(LANGUAGE_NAME).pc
103 |
104 | clean:
105 | $(RM) $(OBJS) $(LANGUAGE_NAME).pc lib$(LANGUAGE_NAME).a lib$(LANGUAGE_NAME).$(SOEXT)
106 |
107 | test:
108 | $(TS) test
109 | $(TS) parse examples/* --quiet --time
110 |
111 | .PHONY: all install uninstall clean test
112 |
--------------------------------------------------------------------------------
/tree-sitter-superhtml/Package.swift:
--------------------------------------------------------------------------------
1 | // swift-tools-version:5.3
2 | import PackageDescription
3 |
4 | let package = Package(
5 | name: "TreeSitterHTML",
6 | products: [
7 | .library(name: "TreeSitterHTML", targets: ["TreeSitterHTML"]),
8 | ],
9 | dependencies: [],
10 | targets: [
11 | .target(name: "TreeSitterHTML",
12 | path: ".",
13 | exclude: [
14 | "Cargo.toml",
15 | "Makefile",
16 | "binding.gyp",
17 | "bindings/c",
18 | "bindings/go",
19 | "bindings/node",
20 | "bindings/python",
21 | "bindings/rust",
22 | "prebuilds",
23 | "grammar.js",
24 | "package.json",
25 | "package-lock.json",
26 | "pyproject.toml",
27 | "setup.py",
28 | "test",
29 | "examples",
30 | ".editorconfig",
31 | ".github",
32 | ".gitignore",
33 | ".gitattributes",
34 | ".gitmodules",
35 | ],
36 | sources: [
37 | "src/parser.c",
38 | "src/scanner.c",
39 | ],
40 | resources: [
41 | .copy("queries")
42 | ],
43 | publicHeadersPath: "bindings/swift",
44 | cSettings: [.headerSearchPath("src")])
45 | ],
46 | cLanguageStandard: .c11
47 | )
48 |
--------------------------------------------------------------------------------
/tree-sitter-superhtml/README.md:
--------------------------------------------------------------------------------
1 | # tree-sitter-superhtml
2 |
3 | Tree sitter grammar forked from https://github.com/tree-sitter/tree-sitter-html
4 |
--------------------------------------------------------------------------------
/tree-sitter-superhtml/binding.gyp:
--------------------------------------------------------------------------------
1 | {
2 | "targets": [
3 | {
4 | "target_name": "tree_sitter_html_binding",
5 | "dependencies": [
6 | "
2 |
3 | typedef struct TSLanguage TSLanguage;
4 |
5 | extern "C" TSLanguage *tree_sitter_html();
6 |
7 | // "tree-sitter", "language" hashed with BLAKE2
8 | const napi_type_tag LANGUAGE_TYPE_TAG = {
9 | 0x8AF2E5212AD58ABF, 0xD5006CAD83ABBA16
10 | };
11 |
12 | Napi::Object Init(Napi::Env env, Napi::Object exports) {
13 | exports["name"] = Napi::String::New(env, "html");
14 | auto language = Napi::External::New(env, tree_sitter_html());
15 | language.TypeTag(&LANGUAGE_TYPE_TAG);
16 | exports["language"] = language;
17 | return exports;
18 | }
19 |
20 | NODE_API_MODULE(tree_sitter_html_binding, Init)
21 |
--------------------------------------------------------------------------------
/tree-sitter-superhtml/bindings/node/index.d.ts:
--------------------------------------------------------------------------------
1 | type BaseNode = {
2 | type: string;
3 | named: boolean;
4 | };
5 |
6 | type ChildNode = {
7 | multiple: boolean;
8 | required: boolean;
9 | types: BaseNode[];
10 | };
11 |
12 | type NodeInfo =
13 | | (BaseNode & {
14 | subtypes: BaseNode[];
15 | })
16 | | (BaseNode & {
17 | fields: { [name: string]: ChildNode };
18 | children: ChildNode[];
19 | });
20 |
21 | type Language = {
22 | name: string;
23 | language: unknown;
24 | nodeTypeInfo: NodeInfo[];
25 | };
26 |
27 | declare const language: Language;
28 | export = language;
29 |
--------------------------------------------------------------------------------
/tree-sitter-superhtml/bindings/node/index.js:
--------------------------------------------------------------------------------
1 | const root = require("path").join(__dirname, "..", "..");
2 |
3 | module.exports = require("node-gyp-build")(root);
4 |
5 | try {
6 | module.exports.nodeTypeInfo = require("../../src/node-types.json");
7 | } catch (_) {}
8 |
--------------------------------------------------------------------------------
/tree-sitter-superhtml/bindings/python/tree_sitter_html/__init__.py:
--------------------------------------------------------------------------------
1 | "HTML grammar for tree-sitter"
2 |
3 | from ._binding import language
4 |
5 | __all__ = ["language"]
6 |
--------------------------------------------------------------------------------
/tree-sitter-superhtml/bindings/python/tree_sitter_html/__init__.pyi:
--------------------------------------------------------------------------------
1 | def language() -> int: ...
2 |
--------------------------------------------------------------------------------
/tree-sitter-superhtml/bindings/python/tree_sitter_html/binding.c:
--------------------------------------------------------------------------------
1 | #include
2 |
3 | typedef struct TSLanguage TSLanguage;
4 |
5 | TSLanguage *tree_sitter_html(void);
6 |
7 | static PyObject* _binding_language(PyObject *self, PyObject *args) {
8 | return PyLong_FromVoidPtr(tree_sitter_html());
9 | }
10 |
11 | static PyMethodDef methods[] = {
12 | {"language", _binding_language, METH_NOARGS,
13 | "Get the tree-sitter language for this grammar."},
14 | {NULL, NULL, 0, NULL}
15 | };
16 |
17 | static struct PyModuleDef module = {
18 | .m_base = PyModuleDef_HEAD_INIT,
19 | .m_name = "_binding",
20 | .m_doc = NULL,
21 | .m_size = -1,
22 | .m_methods = methods
23 | };
24 |
25 | PyMODINIT_FUNC PyInit__binding(void) {
26 | return PyModule_Create(&module);
27 | }
28 |
--------------------------------------------------------------------------------
/tree-sitter-superhtml/bindings/python/tree_sitter_html/py.typed:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/kristoff-it/superhtml/13f5a2221cb748bbe50ad702e89362afd5b925a7/tree-sitter-superhtml/bindings/python/tree_sitter_html/py.typed
--------------------------------------------------------------------------------
/tree-sitter-superhtml/bindings/rust/build.rs:
--------------------------------------------------------------------------------
1 | fn main() {
2 | let src_dir = std::path::Path::new("src");
3 |
4 | let mut c_config = cc::Build::new();
5 | c_config
6 | .std("c11")
7 | .include(src_dir)
8 | .flag_if_supported("-Wno-sign-compare")
9 | .flag_if_supported("-Wno-implicit-fallthrough");
10 |
11 | #[cfg(target_env = "msvc")]
12 | c_config.flag("-utf-8");
13 |
14 | let parser_path = src_dir.join("parser.c");
15 | c_config.file(&parser_path);
16 | println!("cargo:rerun-if-changed={}", parser_path.to_str().unwrap());
17 |
18 | let scanner_path = src_dir.join("scanner.c");
19 | c_config.file(&scanner_path);
20 | println!("cargo:rerun-if-changed={}", scanner_path.to_str().unwrap());
21 |
22 | c_config.compile("tree-sitter-html");
23 | }
24 |
--------------------------------------------------------------------------------
/tree-sitter-superhtml/bindings/rust/lib.rs:
--------------------------------------------------------------------------------
1 | //! This crate provides HTML language support for the [tree-sitter][] parsing library.
2 | //!
3 | //! Typically, you will use the [language][language func] function to add this language to a
4 | //! tree-sitter [Parser][], and then use the parser to parse some code:
5 | //!
6 | //! ```
7 | //! let code = r#"
8 | //! Hello
9 | //! "#;
10 | //! let mut parser = tree_sitter::Parser::new();
11 | //! parser.set_language(&tree_sitter_html::language()).expect("Error loading HTML grammar");
12 | //! let tree = parser.parse(code, None).unwrap();
13 | //! assert!(!tree.root_node().has_error());
14 | //! ```
15 | //!
16 | //! [Language]: https://docs.rs/tree-sitter/*/tree_sitter/struct.Language.html
17 | //! [language func]: fn.language.html
18 | //! [Parser]: https://docs.rs/tree-sitter/*/tree_sitter/struct.Parser.html
19 | //! [tree-sitter]: https://tree-sitter.github.io/
20 |
21 | use tree_sitter::Language;
22 |
23 | extern "C" {
24 | fn tree_sitter_html() -> Language;
25 | }
26 |
27 | /// Get the tree-sitter [Language][] for this grammar.
28 | ///
29 | /// [Language]: https://docs.rs/tree-sitter/*/tree_sitter/struct.Language.html
30 | pub fn language() -> Language {
31 | unsafe { tree_sitter_html() }
32 | }
33 |
34 | /// The content of the [`node-types.json`][] file for this grammar.
35 | ///
36 | /// [`node-types.json`]: https://tree-sitter.github.io/tree-sitter/using-parsers#static-node-types
37 | pub const NODE_TYPES: &str = include_str!("../../src/node-types.json");
38 |
39 | pub const HIGHLIGHTS_QUERY: &str = include_str!("../../queries/highlights.scm");
40 | pub const INJECTIONS_QUERY: &str = include_str!("../../queries/injections.scm");
41 | // pub const LOCALS_QUERY: &'static str = include_str!("../../queries/locals.scm");
42 | // pub const TAGS_QUERY: &'static str = include_str!("../../queries/tags.scm");
43 |
44 | #[cfg(test)]
45 | mod tests {
46 | #[test]
47 | fn test_can_load_grammar() {
48 | let mut parser = tree_sitter::Parser::new();
49 | parser
50 | .set_language(&super::language())
51 | .expect("Error loading HTML grammar");
52 | }
53 | }
54 |
--------------------------------------------------------------------------------
/tree-sitter-superhtml/bindings/swift/TreeSitterHTML/html.h:
--------------------------------------------------------------------------------
1 | #ifndef TREE_SITTER_HTML_H_
2 | #define TREE_SITTER_HTML_H_
3 |
4 | typedef struct TSLanguage TSLanguage;
5 |
6 | #ifdef __cplusplus
7 | extern "C" {
8 | #endif
9 |
10 | const TSLanguage *tree_sitter_html(void);
11 |
12 | #ifdef __cplusplus
13 | }
14 | #endif
15 |
16 | #endif // TREE_SITTER_HTML_H_
17 |
--------------------------------------------------------------------------------
/tree-sitter-superhtml/examples/deeply-nested-custom.html:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
7 |
8 |
9 |
10 |
11 |
12 |
13 |