Repository: devongovett/tree-sitter-highlight
Branch: master
Commit: bcde34baa17d
Files: 13
Total size: 35.7 KB
Directory structure:
gitextract_mngzxg9r/
├── .cargo/
│ └── config.toml
├── .github/
│ └── workflows/
│ └── tag-release.yml
├── .gitignore
├── Cargo.toml
├── LICENSE
├── README.md
├── bench.js
├── build.rs
├── index.d.ts
├── index.js
├── package.json
└── src/
├── highlight_names.rs
└── lib.rs
================================================
FILE CONTENTS
================================================
================================================
FILE: .cargo/config.toml
================================================
[target.'cfg(target_env = "gnu")']
rustflags = ["-C", "link-args=-Wl,-z,nodelete"]
[target.arm-unknown-linux-gnueabihf]
linker = "arm-linux-gnueabihf-gcc"
[target.armv7-unknown-linux-gnueabihf]
linker = "arm-linux-gnueabihf-gcc"
[target.aarch64-unknown-linux-gnu]
linker = "aarch64-linux-gnu-gcc"
[target.aarch64-unknown-linux-musl]
linker = "aarch64-linux-musl-gcc"
================================================
FILE: .github/workflows/tag-release.yml
================================================
name: tag-release
on:
release:
types: [published]
workflow_dispatch:
jobs:
build-windows:
name: windows
runs-on: windows-latest
steps:
- uses: actions/checkout@v1
- name: Install Rust
uses: actions-rs/toolchain@v1
with:
toolchain: stable
profile: minimal
override: true
- uses: bahmutov/npm-install@v1.1.0
- name: Build native packages
run: yarn build-release
- name: Upload artifacts
uses: actions/upload-artifact@v4
with:
name: bindings-windows
path: ./*.node
- name: Smoke test
run: node -e "require('./')"
build-linux-gnu-x64:
name: linux-gnu-x64
runs-on: ubuntu-latest
container:
image: node:20
steps:
- uses: actions/checkout@v1
- name: Install Rust
uses: actions-rs/toolchain@v1
with:
toolchain: stable
profile: minimal
override: true
- uses: bahmutov/npm-install@v1.1.0
- name: Build native packages
run: yarn build-release
env:
CFLAGS: -std=c99
- name: Strip debug symbols # https://github.com/rust-lang/rust/issues/46034
run: strip ./*.node
- name: Upload artifacts
uses: actions/upload-artifact@v4
with:
name: bindings-linux-gnu-x64
path: ./*.node
- name: debug
run: ls -l ./*.node
- name: Smoke test
run: node -e 'require("./")'
build-linux-gnu-arm:
strategy:
fail-fast: false
matrix:
include:
- target: arm-unknown-linux-gnueabihf
arch: armhf
strip: arm-linux-gnueabihf-strip
- target: aarch64-unknown-linux-gnu
arch: arm64
strip: aarch64-linux-gnu-strip
name: ${{ matrix.target }}
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v1
- name: Install Rust
uses: actions-rs/toolchain@v1
with:
toolchain: stable
profile: minimal
override: true
target: ${{ matrix.target }}
- name: Install cross compile toolchains
run: |
sudo apt-get update
sudo apt-get install gcc-arm-linux-gnueabihf gcc-aarch64-linux-gnu g++-aarch64-linux-gnu -y
- uses: bahmutov/npm-install@v1.1.0
- name: Build native packages
run: yarn build-release --target ${{ matrix.target }}
- name: Strip debug symbols # https://github.com/rust-lang/rust/issues/46034
run: ${{ matrix.strip }} ./*.node
- name: Upload artifacts
uses: actions/upload-artifact@v4
with:
name: bindings-${{ matrix.target }}
path: ./*.node
- name: debug
run: ls -l ./*.node
- name: Configure binfmt-support
run: docker run --rm --privileged multiarch/qemu-user-static:register --reset
build-linux-musl:
strategy:
fail-fast: false
matrix:
include:
- target: x86_64-unknown-linux-musl
strip: strip
- target: aarch64-unknown-linux-musl
strip: aarch64-linux-musl-strip
name: ${{ matrix.target }}
runs-on: ubuntu-latest
container:
image: node:20-alpine
steps:
- uses: actions/checkout@v1
- name: Install build tools
run: apk add --no-cache python3 make gcc g++ musl-dev curl tar
- name: Install Rust
uses: actions-rs/toolchain@v1
with:
toolchain: stable
profile: minimal
override: true
target: ${{ matrix.target }}
- name: Install cross compile toolchains
if: ${{ matrix.target == 'aarch64-unknown-linux-musl' }}
run: |
curl -L -O https://github.com/devongovett/linux-musl-cross/releases/download/v1/aarch64-linux-musl-cross.tgz
tar -xzf aarch64-linux-musl-cross.tgz
cp -R aarch64-linux-musl-cross/* /usr
- uses: bahmutov/npm-install@v1.1.0
- name: Build native packages
run: yarn build-release --target ${{ matrix.target }}
- name: Strip debug symbols # https://github.com/rust-lang/rust/issues/46034
run: ${{ matrix.strip }} ./*.node
- name: Upload artifacts
uses: actions/upload-artifact@v4
with:
name: bindings-${{ matrix.target }}
path: ./*.node
- name: debug
run: ls -l ./*.node
- name: Smoke test
if: ${{ matrix.target == 'x86_64-unknown-linux-musl' }}
run: node -e 'require("./")'
build-macos:
strategy:
fail-fast: false
matrix:
include:
- name: aarch64-apple-darwin
target: aarch64-apple-darwin
- name: x86_64-apple-darwin
target: x86_64-apple-darwin
name: ${{ matrix.name }}
runs-on: macos-latest
steps:
- uses: actions/checkout@v1
- name: Install Rust
uses: actions-rs/toolchain@v1
with:
toolchain: stable
profile: minimal
override: true
target: ${{ matrix.target }}
- uses: bahmutov/npm-install@v1.1.0
- name: Build native packages
run: |
sudo rm -Rf /Library/Developer/CommandLineTools/SDKs/*;
export CC=$(xcrun -f clang);
export CXX=$(xcrun -f clang++);
SYSROOT=$(xcrun --sdk macosx --show-sdk-path);
export CFLAGS="-isysroot $SYSROOT -isystem $SYSROOT";
export MACOSX_DEPLOYMENT_TARGET="10.9";
yarn build-release --target ${{ matrix.target }}
- name: Strip debug symbols
run: strip -x ./*.node
- name: Upload artifacts
uses: actions/upload-artifact@v4
with:
name: bindings-${{ matrix.target }}
path: ./*.node
- name: debug
run: ls -l ./*.node
build-and-release:
runs-on: ubuntu-latest
name: Build and release the tagged version
needs:
- build-windows
- build-linux-musl
- build-linux-gnu-arm
- build-macos
steps:
- uses: actions/checkout@v1
- uses: bahmutov/npm-install@v1.1.0
- name: Download artifacts
uses: actions/download-artifact@v4
with:
path: artifacts
- name: Move artifacts
run: mv artifacts/*/*.node .
- name: Debug
run: ls -l ./*.node
- run: echo //registry.npmjs.org/:_authToken=${NPM_TOKEN} > .npmrc
env:
NPM_TOKEN: ${{ secrets.NPM_TOKEN }}
- run: npm publish
================================================
FILE: .gitignore
================================================
/target
node_modules
Cargo.lock
*.node
================================================
FILE: Cargo.toml
================================================
[package]
name = "node-tree-sitter-highlight"
version = "0.1.0"
edition = "2021"
[lib]
crate-type = ["cdylib"]
[dependencies]
napi = "2"
napi-derive = "2"
lazy_static = "1.4"
tree-sitter-highlight = "0.26.3"
tree-sitter-javascript = "0.25.0"
tree-sitter-typescript = "0.23.2"
tree-sitter-jsdoc = "0.25.0"
tree-sitter-json = "0.24.8"
tree-sitter-css = "0.25.0"
tree-sitter-regex = "0.25.0"
tree-sitter-yaml = "0.7.2"
tree-sitter-html = "0.23.2"
tree-sitter-c = "0.24.1"
tree-sitter-bash = "0.25.1"
tree-sitter-rust = "0.24.0"
[build-dependencies]
napi-build = { version = "1" }
tree-sitter = "0.26.3"
tree-sitter-highlight = "0.26.3"
tree-sitter-javascript = "0.25.0"
tree-sitter-typescript = "0.23.2"
tree-sitter-jsdoc = "0.25.0"
tree-sitter-json = "0.24.8"
tree-sitter-css = "0.25.0"
tree-sitter-regex = "0.25.0"
tree-sitter-yaml = "0.7.2"
tree-sitter-html = "0.23.2"
tree-sitter-c = "0.24.1"
tree-sitter-bash = "0.25.1"
tree-sitter-rust = "0.24.0"
[profile.release]
opt-level = 3
lto = true
================================================
FILE: LICENSE
================================================
MIT License
Copyright (c) 2021 Devon Govett
Permission is hereby granted, free of charge, to any person obtaining a copy
of this software and associated documentation files (the "Software"), to deal
in the Software without restriction, including without limitation the rights
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
copies of the Software, and to permit persons to whom the Software is
furnished to do so, subject to the following conditions:
The above copyright notice and this permission notice shall be included in all
copies or substantial portions of the Software.
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
SOFTWARE.
================================================
FILE: README.md
================================================
# tree-sitter-highlight
A syntax highlighter for Node.js powered by [Tree Sitter](https://github.com/tree-sitter/tree-sitter). Written in Rust.
## Usage
The following will output HTML:
```js
const treeSitter = require('tree-sitter-highlight');
treeSitter.highlight('const foo = "hi";', treeSitter.Language.JS);
// => '...'
```
You can also output a [HAST](https://github.com/syntax-tree/hast) AST, which is useful for integrating with Markdown or MDX processors (e.g. Remark).
```js
treeSitter.highlightHast('const foo = "hi";', treeSitter.Language.JS);
// => {type: 'element', children: [...]}
```
## Themes
The output HTML will contain CSS class names for various tokens. These will depend on the language, but there are several common names used across languages. Here is a basic example theme:
```css
.keyword {
color: purple;
}
.function {
color: blue;
}
.type {
color: pink;
}
.string {
color: green;
}
.number {
color: brown;
}
.operator {
color: gray;
}
.comment {
color: lightgray;
}
```
Inspect the generated output HTML and design your CSS accordingly.
## License
MIT
================================================
FILE: bench.js
================================================
const {Benchmark} = require("tiny-benchy");
const ts = require('./');
let input = `
function Example() {
let alertDismiss = (close) => {
close();
alert('Dialog dismissed.');
};
return (
Info
{(close) => (
)}
);
}
`;
let suite = new Benchmark({iterations: 50});
suite.add('html', () => {
ts.highlight(input, ts.Language.JSX);
});
suite.add('hast', () => {
ts.highlightHast(input, ts.Language.JSX);
});
suite.run();
================================================
FILE: build.rs
================================================
use tree_sitter::Language;
extern crate napi_build;
fn main() {
let mut queries = String::new();
queries.push_str(tree_sitter_javascript::HIGHLIGHT_QUERY);
queries.push_str(tree_sitter_javascript::JSX_HIGHLIGHT_QUERY);
let mut highlight_names = Vec::new();
add_highlight_names(
tree_sitter_javascript::LANGUAGE.into(),
&queries,
&mut highlight_names,
);
add_highlight_names(
tree_sitter_typescript::LANGUAGE_TSX.into(),
tree_sitter_typescript::HIGHLIGHTS_QUERY,
&mut highlight_names,
);
add_highlight_names(
tree_sitter_css::LANGUAGE.into(),
tree_sitter_css::HIGHLIGHTS_QUERY,
&mut highlight_names,
);
add_highlight_names(
tree_sitter_regex::LANGUAGE.into(),
tree_sitter_regex::HIGHLIGHTS_QUERY,
&mut highlight_names,
);
add_highlight_names(
tree_sitter_jsdoc::LANGUAGE.into(),
tree_sitter_jsdoc::HIGHLIGHTS_QUERY,
&mut highlight_names,
);
add_highlight_names(
tree_sitter_json::LANGUAGE.into(),
tree_sitter_json::HIGHLIGHTS_QUERY,
&mut highlight_names,
);
add_highlight_names(
tree_sitter_yaml::LANGUAGE.into(),
tree_sitter_yaml::HIGHLIGHTS_QUERY,
&mut highlight_names,
);
add_highlight_names(
tree_sitter_html::LANGUAGE.into(),
tree_sitter_html::HIGHLIGHTS_QUERY,
&mut highlight_names,
);
add_highlight_names(
tree_sitter_c::LANGUAGE.into(),
tree_sitter_c::HIGHLIGHT_QUERY,
&mut highlight_names,
);
add_highlight_names(
tree_sitter_bash::LANGUAGE.into(),
tree_sitter_bash::HIGHLIGHT_QUERY,
&mut highlight_names,
);
add_highlight_names(
tree_sitter_rust::LANGUAGE.into(),
tree_sitter_rust::HIGHLIGHTS_QUERY,
&mut highlight_names,
);
highlight_names.sort();
let html_attrs: Vec = highlight_names
.iter()
.map(|s| format!("class=\"{}\"", s.replace('.', " ")))
.collect();
let class_names: Vec = highlight_names
.iter()
.map(|s| s.replace('.', " "))
.collect();
std::fs::write(
"src/highlight_names.rs",
format!(
"pub const HIGHLIGHT_NAMES: &[&str] = &{:#?};\n\npub const HTML_ATTRS: &[&str] = &{:#?};\n\npub const CLASS_NAMES: &[&str] = &{:#?};\n",
highlight_names,
html_attrs,
class_names
),
)
.expect("write error");
napi_build::setup();
}
fn add_highlight_names(lang: Language, source: &str, highlights: &mut Vec) {
let query = tree_sitter::Query::new(&lang, source).unwrap();
for capture in query.capture_names() {
if !highlights.iter().any(|h| h == capture) {
highlights.push(capture.to_string());
}
}
}
================================================
FILE: index.d.ts
================================================
/* tslint:disable */
/* eslint-disable */
/* auto-generated by NAPI-RS */
export const enum Language {
JS = 0,
JSX = 1,
TS = 2,
TSX = 3,
JSON = 4,
YAML = 5,
CSS = 6,
HTML = 7,
Regex = 8,
JsDoc = 9,
C = 10,
Bash = 11,
Rust = 12
}
export declare function highlight(code: string, language: Language): string
export interface HastProperties {
className: string
}
export interface HastNode {
type: string
tagName: string
properties: HastProperties
children: Array
}
export interface HastTextNode {
type: string
value: string
}
export declare function highlightHast(code: string, language: Language): HastNode
================================================
FILE: index.js
================================================
/* tslint:disable */
/* eslint-disable */
/* prettier-ignore */
/* auto-generated by NAPI-RS */
const { existsSync, readFileSync } = require('fs')
const { join } = require('path')
const { platform, arch } = process
let nativeBinding = null
let localFileExisted = false
let loadError = null
function isMusl() {
// For Node 10
if (!process.report || typeof process.report.getReport !== 'function') {
try {
const lddPath = require('child_process').execSync('which ldd').toString().trim()
return readFileSync(lddPath, 'utf8').includes('musl')
} catch (e) {
return true
}
} else {
const { glibcVersionRuntime } = process.report.getReport().header
return !glibcVersionRuntime
}
}
switch (platform) {
case 'android':
switch (arch) {
case 'arm64':
localFileExisted = existsSync(join(__dirname, 'tree-sitter-highlight.android-arm64.node'))
try {
if (localFileExisted) {
nativeBinding = require('./tree-sitter-highlight.android-arm64.node')
} else {
nativeBinding = require('tree-sitter-highlight-android-arm64')
}
} catch (e) {
loadError = e
}
break
case 'arm':
localFileExisted = existsSync(join(__dirname, 'tree-sitter-highlight.android-arm-eabi.node'))
try {
if (localFileExisted) {
nativeBinding = require('./tree-sitter-highlight.android-arm-eabi.node')
} else {
nativeBinding = require('tree-sitter-highlight-android-arm-eabi')
}
} catch (e) {
loadError = e
}
break
default:
throw new Error(`Unsupported architecture on Android ${arch}`)
}
break
case 'win32':
switch (arch) {
case 'x64':
localFileExisted = existsSync(
join(__dirname, 'tree-sitter-highlight.win32-x64-msvc.node')
)
try {
if (localFileExisted) {
nativeBinding = require('./tree-sitter-highlight.win32-x64-msvc.node')
} else {
nativeBinding = require('tree-sitter-highlight-win32-x64-msvc')
}
} catch (e) {
loadError = e
}
break
case 'ia32':
localFileExisted = existsSync(
join(__dirname, 'tree-sitter-highlight.win32-ia32-msvc.node')
)
try {
if (localFileExisted) {
nativeBinding = require('./tree-sitter-highlight.win32-ia32-msvc.node')
} else {
nativeBinding = require('tree-sitter-highlight-win32-ia32-msvc')
}
} catch (e) {
loadError = e
}
break
case 'arm64':
localFileExisted = existsSync(
join(__dirname, 'tree-sitter-highlight.win32-arm64-msvc.node')
)
try {
if (localFileExisted) {
nativeBinding = require('./tree-sitter-highlight.win32-arm64-msvc.node')
} else {
nativeBinding = require('tree-sitter-highlight-win32-arm64-msvc')
}
} catch (e) {
loadError = e
}
break
default:
throw new Error(`Unsupported architecture on Windows: ${arch}`)
}
break
case 'darwin':
localFileExisted = existsSync(join(__dirname, 'tree-sitter-highlight.darwin-universal.node'))
try {
if (localFileExisted) {
nativeBinding = require('./tree-sitter-highlight.darwin-universal.node')
} else {
nativeBinding = require('tree-sitter-highlight-darwin-universal')
}
break
} catch {}
switch (arch) {
case 'x64':
localFileExisted = existsSync(join(__dirname, 'tree-sitter-highlight.darwin-x64.node'))
try {
if (localFileExisted) {
nativeBinding = require('./tree-sitter-highlight.darwin-x64.node')
} else {
nativeBinding = require('tree-sitter-highlight-darwin-x64')
}
} catch (e) {
loadError = e
}
break
case 'arm64':
localFileExisted = existsSync(
join(__dirname, 'tree-sitter-highlight.darwin-arm64.node')
)
try {
if (localFileExisted) {
nativeBinding = require('./tree-sitter-highlight.darwin-arm64.node')
} else {
nativeBinding = require('tree-sitter-highlight-darwin-arm64')
}
} catch (e) {
loadError = e
}
break
default:
throw new Error(`Unsupported architecture on macOS: ${arch}`)
}
break
case 'freebsd':
if (arch !== 'x64') {
throw new Error(`Unsupported architecture on FreeBSD: ${arch}`)
}
localFileExisted = existsSync(join(__dirname, 'tree-sitter-highlight.freebsd-x64.node'))
try {
if (localFileExisted) {
nativeBinding = require('./tree-sitter-highlight.freebsd-x64.node')
} else {
nativeBinding = require('tree-sitter-highlight-freebsd-x64')
}
} catch (e) {
loadError = e
}
break
case 'linux':
switch (arch) {
case 'x64':
if (isMusl()) {
localFileExisted = existsSync(
join(__dirname, 'tree-sitter-highlight.linux-x64-musl.node')
)
try {
if (localFileExisted) {
nativeBinding = require('./tree-sitter-highlight.linux-x64-musl.node')
} else {
nativeBinding = require('tree-sitter-highlight-linux-x64-musl')
}
} catch (e) {
loadError = e
}
} else {
localFileExisted = existsSync(
join(__dirname, 'tree-sitter-highlight.linux-x64-gnu.node')
)
try {
if (localFileExisted) {
nativeBinding = require('./tree-sitter-highlight.linux-x64-gnu.node')
} else {
nativeBinding = require('tree-sitter-highlight-linux-x64-gnu')
}
} catch (e) {
loadError = e
}
}
break
case 'arm64':
if (isMusl()) {
localFileExisted = existsSync(
join(__dirname, 'tree-sitter-highlight.linux-arm64-musl.node')
)
try {
if (localFileExisted) {
nativeBinding = require('./tree-sitter-highlight.linux-arm64-musl.node')
} else {
nativeBinding = require('tree-sitter-highlight-linux-arm64-musl')
}
} catch (e) {
loadError = e
}
} else {
localFileExisted = existsSync(
join(__dirname, 'tree-sitter-highlight.linux-arm64-gnu.node')
)
try {
if (localFileExisted) {
nativeBinding = require('./tree-sitter-highlight.linux-arm64-gnu.node')
} else {
nativeBinding = require('tree-sitter-highlight-linux-arm64-gnu')
}
} catch (e) {
loadError = e
}
}
break
case 'arm':
if (isMusl()) {
localFileExisted = existsSync(
join(__dirname, 'tree-sitter-highlight.linux-arm-musleabihf.node')
)
try {
if (localFileExisted) {
nativeBinding = require('./tree-sitter-highlight.linux-arm-musleabihf.node')
} else {
nativeBinding = require('tree-sitter-highlight-linux-arm-musleabihf')
}
} catch (e) {
loadError = e
}
} else {
localFileExisted = existsSync(
join(__dirname, 'tree-sitter-highlight.linux-arm-gnueabihf.node')
)
try {
if (localFileExisted) {
nativeBinding = require('./tree-sitter-highlight.linux-arm-gnueabihf.node')
} else {
nativeBinding = require('tree-sitter-highlight-linux-arm-gnueabihf')
}
} catch (e) {
loadError = e
}
}
break
case 'riscv64':
if (isMusl()) {
localFileExisted = existsSync(
join(__dirname, 'tree-sitter-highlight.linux-riscv64-musl.node')
)
try {
if (localFileExisted) {
nativeBinding = require('./tree-sitter-highlight.linux-riscv64-musl.node')
} else {
nativeBinding = require('tree-sitter-highlight-linux-riscv64-musl')
}
} catch (e) {
loadError = e
}
} else {
localFileExisted = existsSync(
join(__dirname, 'tree-sitter-highlight.linux-riscv64-gnu.node')
)
try {
if (localFileExisted) {
nativeBinding = require('./tree-sitter-highlight.linux-riscv64-gnu.node')
} else {
nativeBinding = require('tree-sitter-highlight-linux-riscv64-gnu')
}
} catch (e) {
loadError = e
}
}
break
case 's390x':
localFileExisted = existsSync(
join(__dirname, 'tree-sitter-highlight.linux-s390x-gnu.node')
)
try {
if (localFileExisted) {
nativeBinding = require('./tree-sitter-highlight.linux-s390x-gnu.node')
} else {
nativeBinding = require('tree-sitter-highlight-linux-s390x-gnu')
}
} catch (e) {
loadError = e
}
break
default:
throw new Error(`Unsupported architecture on Linux: ${arch}`)
}
break
default:
throw new Error(`Unsupported OS: ${platform}, architecture: ${arch}`)
}
if (!nativeBinding) {
if (loadError) {
throw loadError
}
throw new Error(`Failed to load native binding`)
}
const { Language, highlight, highlightHast } = nativeBinding
module.exports.Language = Language
module.exports.highlight = highlight
module.exports.highlightHast = highlightHast
================================================
FILE: package.json
================================================
{
"name": "tree-sitter-highlight",
"version": "1.1.2",
"description": "A syntax highlighter for Node powered by Tree Sitter. Written in Rust.",
"repository": "https://github.com/devongovett/tree-sitter-highlight",
"author": "Devon Govett ",
"license": "MIT",
"napi": {
"name": "tree-sitter-highlight"
},
"files": [
"*.node",
"index.js",
"index.d.ts"
],
"scripts": {
"build": "napi build --platform",
"build-release": "napi build --platform --release"
},
"devDependencies": {
"@napi-rs/cli": "^2.0.0",
"tiny-benchy": "^2.1.0"
}
}
================================================
FILE: src/highlight_names.rs
================================================
pub const HIGHLIGHT_NAMES: &[&str] = &[
"attribute",
"boolean",
"character.special",
"comment",
"comment.documentation",
"constant",
"constant.builtin",
"constant.character",
"constructor",
"delimiter",
"embedded",
"escape",
"function",
"function.builtin",
"function.macro",
"function.method",
"function.special",
"keyword",
"label",
"number",
"operator",
"property",
"punctuation.bracket",
"punctuation.delimiter",
"punctuation.special",
"string",
"string.special",
"string.special.key",
"tag",
"tag.error",
"type",
"type.builtin",
"variable",
"variable.builtin",
"variable.parameter",
];
pub const HTML_ATTRS: &[&str] = &[
"class=\"attribute\"",
"class=\"boolean\"",
"class=\"character special\"",
"class=\"comment\"",
"class=\"comment documentation\"",
"class=\"constant\"",
"class=\"constant builtin\"",
"class=\"constant character\"",
"class=\"constructor\"",
"class=\"delimiter\"",
"class=\"embedded\"",
"class=\"escape\"",
"class=\"function\"",
"class=\"function builtin\"",
"class=\"function macro\"",
"class=\"function method\"",
"class=\"function special\"",
"class=\"keyword\"",
"class=\"label\"",
"class=\"number\"",
"class=\"operator\"",
"class=\"property\"",
"class=\"punctuation bracket\"",
"class=\"punctuation delimiter\"",
"class=\"punctuation special\"",
"class=\"string\"",
"class=\"string special\"",
"class=\"string special key\"",
"class=\"tag\"",
"class=\"tag error\"",
"class=\"type\"",
"class=\"type builtin\"",
"class=\"variable\"",
"class=\"variable builtin\"",
"class=\"variable parameter\"",
];
pub const CLASS_NAMES: &[&str] = &[
"attribute",
"boolean",
"character special",
"comment",
"comment documentation",
"constant",
"constant builtin",
"constant character",
"constructor",
"delimiter",
"embedded",
"escape",
"function",
"function builtin",
"function macro",
"function method",
"function special",
"keyword",
"label",
"number",
"operator",
"property",
"punctuation bracket",
"punctuation delimiter",
"punctuation special",
"string",
"string special",
"string special key",
"tag",
"tag error",
"type",
"type builtin",
"variable",
"variable builtin",
"variable parameter",
];
================================================
FILE: src/lib.rs
================================================
mod highlight_names;
use highlight_names::{CLASS_NAMES, HIGHLIGHT_NAMES, HTML_ATTRS};
use lazy_static::lazy_static;
use napi::bindgen_prelude::*;
use napi_derive::napi;
use tree_sitter_highlight::{HighlightConfiguration, HighlightEvent, Highlighter, HtmlRenderer};
#[napi]
pub enum Language {
JS,
JSX,
TS,
TSX,
JSON,
YAML,
CSS,
HTML,
Regex,
JsDoc,
C,
Bash,
Rust,
}
macro_rules! language {
($mod: ident, $name: literal, $highlights: ident) => {{
let mut config =
HighlightConfiguration::new($mod::LANGUAGE.into(), $name, $mod::$highlights, "", "")
.unwrap();
config.configure(HIGHLIGHT_NAMES);
config
}};
($mod: ident, $name: literal, $highlights: ident, $injections: ident) => {{
let mut config = HighlightConfiguration::new(
$mod::LANGUAGE.into(),
$name,
$mod::$highlights,
$mod::$injections,
"",
)
.unwrap();
config.configure(HIGHLIGHT_NAMES);
config
}};
}
lazy_static! {
static ref JS_CONFIG: HighlightConfiguration = {
let mut config = HighlightConfiguration::new(
tree_sitter_javascript::LANGUAGE.into(),
"javascript",
tree_sitter_javascript::HIGHLIGHT_QUERY,
tree_sitter_javascript::INJECTIONS_QUERY,
tree_sitter_javascript::LOCALS_QUERY,
)
.unwrap();
config.configure(HIGHLIGHT_NAMES);
config
};
static ref JSX_CONFIG: HighlightConfiguration = {
let mut highlights = tree_sitter_javascript::JSX_HIGHLIGHT_QUERY.to_owned();
highlights.push_str(tree_sitter_javascript::HIGHLIGHT_QUERY);
let mut config = HighlightConfiguration::new(
tree_sitter_javascript::LANGUAGE.into(),
"jsx",
&highlights,
tree_sitter_javascript::INJECTIONS_QUERY,
tree_sitter_javascript::LOCALS_QUERY,
)
.unwrap();
config.configure(HIGHLIGHT_NAMES);
config
};
static ref TS_CONFIG: HighlightConfiguration = {
let mut highlights = tree_sitter_typescript::HIGHLIGHTS_QUERY.to_owned();
highlights.push_str(tree_sitter_javascript::HIGHLIGHT_QUERY);
let mut locals = tree_sitter_typescript::LOCALS_QUERY.to_owned();
locals.push_str(tree_sitter_javascript::LOCALS_QUERY);
let mut config = HighlightConfiguration::new(
tree_sitter_typescript::LANGUAGE_TYPESCRIPT.into(),
"typescript",
&highlights,
tree_sitter_javascript::INJECTIONS_QUERY,
&locals,
)
.unwrap();
config.configure(HIGHLIGHT_NAMES);
config
};
static ref TSX_CONFIG: HighlightConfiguration = {
let mut highlights = tree_sitter_javascript::JSX_HIGHLIGHT_QUERY.to_owned();
highlights.push_str(tree_sitter_typescript::HIGHLIGHTS_QUERY);
highlights.push_str(tree_sitter_javascript::HIGHLIGHT_QUERY);
let mut locals = tree_sitter_typescript::LOCALS_QUERY.to_owned();
locals.push_str(tree_sitter_javascript::LOCALS_QUERY);
let mut config = HighlightConfiguration::new(
tree_sitter_typescript::LANGUAGE_TSX.into(),
"tsx",
&highlights,
tree_sitter_javascript::INJECTIONS_QUERY,
&locals,
)
.unwrap();
config.configure(HIGHLIGHT_NAMES);
config
};
static ref JSDOC_CONFIG: HighlightConfiguration =
language!(tree_sitter_jsdoc, "jsdoc", HIGHLIGHTS_QUERY);
static ref JSON_CONFIG: HighlightConfiguration =
language!(tree_sitter_json, "json", HIGHLIGHTS_QUERY);
static ref YAML_CONFIG: HighlightConfiguration =
language!(tree_sitter_yaml, "yaml", HIGHLIGHTS_QUERY);
static ref CSS_CONFIG: HighlightConfiguration =
language!(tree_sitter_css, "css", HIGHLIGHTS_QUERY);
static ref HTML_CONFIG: HighlightConfiguration =
language!(tree_sitter_html, "html", INJECTIONS_QUERY);
static ref REGEX_CONFIG: HighlightConfiguration =
language!(tree_sitter_regex, "regex", HIGHLIGHTS_QUERY);
static ref C_CONFIG: HighlightConfiguration = language!(tree_sitter_c, "c", HIGHLIGHT_QUERY);
static ref BASH_CONFIG: HighlightConfiguration =
language!(tree_sitter_bash, "bash", HIGHLIGHT_QUERY);
static ref RUST_CONFIG: HighlightConfiguration =
language!(tree_sitter_rust, "rust", HIGHLIGHTS_QUERY);
}
impl Language {
fn highlight_config(&self) -> &'static HighlightConfiguration {
match self {
Language::JS => &*JS_CONFIG,
Language::JSX => &*JSX_CONFIG,
Language::TS => &*TS_CONFIG,
Language::TSX => &*TSX_CONFIG,
Language::JSON => &*JSON_CONFIG,
Language::YAML => &*YAML_CONFIG,
Language::CSS => &*CSS_CONFIG,
Language::HTML => &*HTML_CONFIG,
Language::Regex => &*REGEX_CONFIG,
Language::JsDoc => &*JSDOC_CONFIG,
Language::C => &*C_CONFIG,
Language::Bash => &*BASH_CONFIG,
Language::Rust => &*RUST_CONFIG,
}
}
fn from_name(name: &str) -> Option {
Some(match name {
"js" | "javascript" => Language::JS,
"jsx" => Language::JSX,
"ts" | "typescript" => Language::TS,
"tsx" => Language::TSX,
"json" => Language::JSON,
"yaml" => Language::YAML,
"css" => Language::CSS,
"html" => Language::HTML,
"regex" => Language::Regex,
"jsdoc" => Language::JsDoc,
"c" => Language::C,
"bash" => Language::Bash,
"sh" => Language::Bash,
"rust" => Language::Rust,
"rs" => Language::Rust,
_ => return None,
})
}
}
#[napi]
pub fn highlight(code: String, language: Language) -> String {
let config = language.highlight_config();
let mut highlighter = Highlighter::new();
let highlights = highlighter
.highlight(&config, code.as_bytes(), None, |lang| {
Language::from_name(lang).map(|l| l.highlight_config())
})
.unwrap();
let mut renderer = HtmlRenderer::new();
renderer
.render(highlights, code.as_bytes(), &|highlight, res| {
res.extend_from_slice(HTML_ATTRS[highlight.0].as_bytes())
})
.unwrap();
unsafe { String::from_utf8_unchecked(renderer.html) }
}
#[derive(Debug)]
#[napi(object)]
pub struct HastProperties {
pub class_name: String,
}
#[derive(Debug)]
#[napi(object)]
pub struct HastNode {
#[napi(js_name = "type")]
pub kind: String,
pub tag_name: String,
pub properties: HastProperties,
pub children: Vec>,
}
#[derive(Debug)]
#[napi(object)]
pub struct HastTextNode {
#[napi(js_name = "type")]
pub kind: String,
pub value: String,
}
#[napi]
pub fn highlight_hast(code: String, language: Language) -> HastNode {
let config = language.highlight_config();
let mut highlighter = Highlighter::new();
let highlights = highlighter
.highlight(&config, code.as_bytes(), None, |lang| {
Language::from_name(lang).map(|l| l.highlight_config())
})
.unwrap();
let mut stack = Vec::new();
stack.push(HastNode {
kind: "element".into(),
tag_name: "span".into(),
properties: HastProperties {
class_name: "source".into(),
},
children: Vec::new(),
});
for event in highlights {
match event.unwrap() {
HighlightEvent::HighlightStart(highlight) => {
let node = HastNode {
kind: "element".into(),
tag_name: "span".into(),
properties: HastProperties {
class_name: CLASS_NAMES[highlight.0].to_owned(),
},
children: Vec::new(),
};
stack.push(node);
}
HighlightEvent::Source { start, end } => {
let slice = &code[start..end];
let parent = stack.last_mut().unwrap();
if let Some(Either::B(text_node)) = parent.children.last_mut() {
text_node.value.push_str(slice);
} else {
let text_node = HastTextNode {
kind: "text".into(),
value: slice.into(),
};
parent.children.push(Either::B(text_node));
}
}
HighlightEvent::HighlightEnd => {
let node = stack.pop().unwrap();
let parent = stack.last_mut().unwrap();
parent.children.push(Either::A(node));
}
}
}
stack.pop().unwrap()
}