docs: move assets to correct path

This commit is contained in:
Amaan Qureshi 2024-12-25 15:28:05 -05:00
parent 694d636322
commit dda45cfbb6
11 changed files with 8 additions and 208 deletions

View file

@ -0,0 +1,375 @@
/* Base Variables */
:root {
--light-bg: #f9f9f9;
--light-border: #e0e0e0;
--light-text: #333;
--light-hover-border: #c1c1c1;
--light-scrollbar-track: #f1f1f1;
--light-scrollbar-thumb: #c1c1c1;
--light-scrollbar-thumb-hover: #a8a8a8;
--dark-bg: #1d1f21;
--dark-border: #2d2d2d;
--dark-text: #c5c8c6;
--dark-scrollbar-track: #25282c;
--dark-scrollbar-thumb: #4a4d51;
--dark-scrollbar-thumb-hover: #5a5d61;
--primary-color: #0550ae;
--primary-color-alpha: rgba(5, 80, 174, 0.1);
--primary-color-alpha-dark: rgba(121, 192, 255, 0.1);
--selection-color: rgba(39, 95, 255, 0.3);
}
/* Common Scrollbar Styles */
::-webkit-scrollbar {
width: 8px;
height: 8px;
}
::-webkit-scrollbar-track {
border-radius: 4px;
}
::-webkit-scrollbar-thumb {
border-radius: 4px;
}
/* Base Light Theme Scrollbars */
::-webkit-scrollbar-track {
background: var(--light-scrollbar-track);
}
::-webkit-scrollbar-thumb {
background: var(--light-scrollbar-thumb);
}
::-webkit-scrollbar-thumb:hover {
background: var(--light-scrollbar-thumb-hover);
}
/* Dropdown Styling */
.custom-select {
position: relative;
display: inline-block;
}
#language-select {
background-color: var(--light-bg);
border: 1px solid var(--light-border);
border-radius: 4px;
padding: 4px 24px 4px 8px;
font-size: 14px;
color: var(--light-text);
cursor: pointer;
min-width: 120px;
appearance: none;
background-image: url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' width='12' height='12' viewBox='0 0 24 24' fill='none' stroke='%23666' stroke-width='2' stroke-linecap='round' stroke-linejoin='round'%3E%3Cpolyline points='6 9 12 15 18 9'%3E%3C/polyline%3E%3C/svg%3E");
background-repeat: no-repeat;
background-position: right 8px center;
}
.select-button {
background-color: var(--light-bg);
border: 1px solid var(--light-border);
border-radius: 4px;
padding: 4px 8px;
font-size: 14px;
color: var(--light-text);
cursor: pointer;
min-width: 120px;
display: flex;
align-items: center;
justify-content: space-between;
}
#language-select:hover,
.select-button:hover {
border-color: var(--light-hover-border);
}
#language-select:focus,
.select-button:focus {
outline: none;
border-color: var(--primary-color);
box-shadow: 0 0 0 2px var(--primary-color-alpha);
}
/* Custom Checkbox Styling */
input[type="checkbox"] {
appearance: none;
width: 16px;
height: 16px;
border: 1px solid var(--light-border);
border-radius: 3px;
margin-right: 6px;
position: relative;
cursor: pointer;
vertical-align: middle;
}
input[type="checkbox"]:checked {
background-color: var(--primary-color);
border-color: var(--primary-color);
}
input[type="checkbox"]:checked::after {
content: '';
position: absolute;
left: 5px;
top: 2px;
width: 4px;
height: 8px;
border: solid white;
border-width: 0 2px 2px 0;
transform: rotate(45deg);
}
input[type="checkbox"]:hover {
border-color: var(--light-hover-border);
}
input[type="checkbox"]:focus {
outline: none;
border-color: var(--primary-color);
box-shadow: 0 0 0 2px var(--primary-color-alpha);
}
/* Select Dropdown */
.select-dropdown {
position: absolute;
top: 100%;
left: 0;
right: 0;
background-color: var(--light-bg);
border: 1px solid var(--light-border);
border-radius: 4px;
margin-top: 4px;
box-shadow: 0 2px 4px rgba(0, 0, 0, 0.1);
display: none;
z-index: 1000;
max-height: 300px;
overflow-y: auto;
}
.select-dropdown.show {
display: block;
}
.option {
padding: 8px 12px;
cursor: pointer;
}
.option:hover {
background-color: var(--primary-color-alpha);
}
.option.selected {
background-color: var(--primary-color-alpha);
}
/* CodeMirror Base Styles */
.ts-playground .CodeMirror {
border-radius: 6px;
background-color: var(--light-bg) !important;
color: #080808 !important;
}
.ts-playground .CodeMirror-scroll {
padding: 8px;
border: 1px solid var(--light-border);
border-radius: 6px;
box-shadow: 0 1px 2px rgba(0, 0, 0, 0.1);
}
.ayu .ts-playground .CodeMirror-scroll,
.coal .ts-playground .CodeMirror-scroll,
.navy .ts-playground .CodeMirror-scroll {
border-color: var(--dark-border);
}
.ts-playground .CodeMirror-gutters {
background: #ebebeb !important;
border-right: 1px solid #e8e8e8 !important;
}
.ts-playground .CodeMirror-cursor {
border-left: 2px solid #000 !important;
}
.ts-playground .CodeMirror-selected {
background: var(--selection-color) !important;
}
.ts-playground .CodeMirror-activeline-background {
background: rgba(36, 99, 180, 0.12) !important;
}
/* Output Container Styles */
#output-container {
color: #080808;
background-color: var(--light-bg);
margin: 0;
white-space: pre;
font-family: ui-monospace, SFMono-Regular, "SF Mono", Menlo, Consolas, "Liberation Mono", monospace;
}
#output-container-scroll {
max-height: 400px;
overflow: auto;
padding: 8px;
border: 1px solid var(--light-border);
border-radius: 6px;
box-shadow: 0 1px 2px rgba(0, 0, 0, 0.1);
background-color: var(--light-bg);
}
#output-container a {
color: var(--primary-color);
text-decoration: none;
}
#output-container a.node-link.anonymous {
color: #116329;
}
#output-container a.node-link.anonymous:before {
content: '"';
}
#output-container a.node-link.anonymous:after {
content: '"';
}
#output-container a.node-link.error {
color: #cf222e;
}
#output-container a.highlighted {
background-color: var(--selection-color);
}
/* Dark Theme Overrides */
.ayu,
.coal,
.navy {
& #language-select,
& .select-button {
background-color: var(--dark-bg);
border-color: var(--dark-border);
color: var(--dark-text);
}
& input[type="checkbox"] {
border-color: var(--dark-border);
background-color: var(--dark-bg);
}
& input[type="checkbox"]:checked {
background-color: #79c0ff;
border-color: #79c0ff;
}
& label {
color: var(--dark-text);
}
& .select-dropdown {
background-color: var(--dark-bg);
border-color: var(--dark-border);
box-shadow: 0 2px 4px rgba(0, 0, 0, 0.3);
}
& .option:hover {
background-color: var(--primary-color-alpha-dark);
}
& .option.selected {
background-color: var(--primary-color-alpha-dark);
}
& .ts-playground .CodeMirror {
background-color: var(--dark-bg) !important;
color: var(--dark-text) !important;
}
& .ts-playground .CodeMirror-gutters {
background: var(--dark-scrollbar-track) !important;
border-right-color: var(--dark-border) !important;
}
& .ts-playground .CodeMirror-cursor {
border-left-color: #aeafad !important;
}
& .ts-playground .CodeMirror-selected {
background: #373b41 !important;
}
& .ts-playground .CodeMirror-activeline-background {
background: #282a2e !important;
}
& #output-container {
color: var(--dark-text);
background-color: var(--dark-bg);
}
& #output-container-scroll {
background-color: var(--dark-bg);
border-color: var(--dark-border);
}
& #output-container a {
color: #79c0ff;
}
& #output-container a.node-link.anonymous {
color: #7ee787;
}
& #output-container a.node-link.error {
color: #ff7b72;
}
& #output-container a.highlighted {
background-color: #373b41;
}
/* Dark Theme Scrollbars */
& ::-webkit-scrollbar-track {
background: var(--dark-scrollbar-track) !important;
}
& ::-webkit-scrollbar-thumb {
background: var(--dark-scrollbar-thumb) !important;
}
& ::-webkit-scrollbar-thumb:hover {
background: var(--dark-scrollbar-thumb-hover) !important;
}
& * {
scrollbar-width: thin !important;
scrollbar-color: var(--dark-scrollbar-thumb) var(--dark-scrollbar-track) !important;
}
}
/* Spacing Utilities */
#language-select,
input[type="checkbox"],
label {
margin: 0 4px;
}
#language-select {
margin-right: 16px;
}
label {
font-size: 14px;
margin-right: 16px;
cursor: pointer;
}

Binary file not shown.

After

Width:  |  Height:  |  Size: 887 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 2.3 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 112 KiB

View file

@ -0,0 +1,577 @@
function initializeCustomSelect() {
const button = document.getElementById('language-button');
const select = document.getElementById('language-select');
if (!button || !select) return;
const dropdown = button.nextElementSibling;
const selectedValue = button.querySelector('.selected-value');
selectedValue.textContent = select.options[select.selectedIndex].text;
button.addEventListener('click', (e) => {
e.preventDefault(); // Prevent form submission
dropdown.classList.toggle('show');
});
document.addEventListener('click', (e) => {
if (!button.contains(e.target)) {
dropdown.classList.remove('show');
}
});
dropdown.querySelectorAll('.option').forEach(option => {
option.addEventListener('click', () => {
selectedValue.textContent = option.textContent;
select.value = option.dataset.value;
dropdown.classList.remove('show');
const event = new Event('change');
select.dispatchEvent(event);
});
});
}
window.initializePlayground = async function initializePlayground() {
initializeCustomSelect();
let tree;
const CAPTURE_REGEX = /@\s*([\w\._-]+)/g;
const LIGHT_COLORS = [
"#0550ae", // blue
"#ab5000", // rust brown
"#116329", // forest green
"#844708", // warm brown
"#6639ba", // purple
"#7d4e00", // orange brown
"#0969da", // bright blue
"#1a7f37", // green
"#cf222e", // red
"#8250df", // violet
"#6e7781", // gray
"#953800", // dark orange
"#1b7c83" // teal
];
const DARK_COLORS = [
"#79c0ff", // light blue
"#ffa657", // orange
"#7ee787", // light green
"#ff7b72", // salmon
"#d2a8ff", // light purple
"#ffa198", // pink
"#a5d6ff", // pale blue
"#56d364", // bright green
"#ff9492", // light red
"#e0b8ff", // pale purple
"#9ca3af", // gray
"#ffb757", // yellow orange
"#80cbc4" // light teal
];
const codeInput = document.getElementById("code-input");
const languageSelect = document.getElementById("language-select");
const loggingCheckbox = document.getElementById("logging-checkbox");
const anonymousNodes = document.getElementById('anonymous-nodes-checkbox');
const outputContainer = document.getElementById("output-container");
const outputContainerScroll = document.getElementById(
"output-container-scroll",
);
const playgroundContainer = document.getElementById("playground-container");
const queryCheckbox = document.getElementById("query-checkbox");
const queryContainer = document.getElementById("query-container");
const queryInput = document.getElementById("query-input");
const updateTimeSpan = document.getElementById("update-time");
const languagesByName = {};
loadState();
await TreeSitter.init();
const parser = new TreeSitter();
console.log(parser, codeInput, queryInput);
const codeEditor = CodeMirror.fromTextArea(codeInput, {
lineNumbers: true,
showCursorWhenSelecting: true
});
codeEditor.on('keydown', (_, event) => {
if (event.key === 'ArrowLeft' || event.key === 'ArrowRight') {
event.stopPropagation(); // Prevent mdBook from going back/forward
}
});
const queryEditor = CodeMirror.fromTextArea(queryInput, {
lineNumbers: true,
showCursorWhenSelecting: true,
});
queryEditor.on('keydown', (_, event) => {
if (event.key === 'ArrowLeft' || event.key === 'ArrowRight') {
event.stopPropagation(); // Prevent mdBook from going back/forward
}
});
const cluster = new Clusterize({
rows: [],
noDataText: null,
contentElem: outputContainer,
scrollElem: outputContainerScroll,
});
const renderTreeOnCodeChange = debounce(renderTree, 50);
const saveStateOnChange = debounce(saveState, 2000);
const runTreeQueryOnChange = debounce(runTreeQuery, 50);
let languageName = languageSelect.value;
let treeRows = null;
let treeRowHighlightedIndex = -1;
let parseCount = 0;
let isRendering = 0;
let query;
codeEditor.on("changes", handleCodeChange);
codeEditor.on("viewportChange", runTreeQueryOnChange);
codeEditor.on("cursorActivity", debounce(handleCursorMovement, 150));
queryEditor.on("changes", debounce(handleQueryChange, 150));
loggingCheckbox.addEventListener("change", handleLoggingChange);
anonymousNodes.addEventListener('change', renderTree);
queryCheckbox.addEventListener("change", handleQueryEnableChange);
languageSelect.addEventListener("change", handleLanguageChange);
outputContainer.addEventListener("click", handleTreeClick);
handleQueryEnableChange();
await handleLanguageChange();
playgroundContainer.style.visibility = "visible";
async function handleLanguageChange() {
const newLanguageName = languageSelect.value;
if (!languagesByName[newLanguageName]) {
const url = `${LANGUAGE_BASE_URL}/tree-sitter-${newLanguageName}.wasm`;
languageSelect.disabled = true;
try {
languagesByName[newLanguageName] = await TreeSitter.Language.load(url);
} catch (e) {
console.error(e);
languageSelect.value = languageName;
return;
} finally {
languageSelect.disabled = false;
}
}
tree = null;
languageName = newLanguageName;
parser.setLanguage(languagesByName[newLanguageName]);
handleCodeChange();
handleQueryChange();
}
async function handleCodeChange(editor, changes) {
const newText = codeEditor.getValue() + "\n";
const edits = tree && changes && changes.map(treeEditForEditorChange);
const start = performance.now();
if (edits) {
for (const edit of edits) {
tree.edit(edit);
}
}
const newTree = parser.parse(newText, tree);
const duration = (performance.now() - start).toFixed(1);
updateTimeSpan.innerText = `${duration} ms`;
if (tree) tree.delete();
tree = newTree;
parseCount++;
renderTreeOnCodeChange();
runTreeQueryOnChange();
saveStateOnChange();
}
async function renderTree() {
isRendering++;
const cursor = tree.walk();
let currentRenderCount = parseCount;
let row = "";
let rows = [];
let finishedRow = false;
let visitedChildren = false;
let indentLevel = 0;
for (let i = 0; ; i++) {
if (i > 0 && i % 10000 === 0) {
await new Promise((r) => setTimeout(r, 0));
if (parseCount !== currentRenderCount) {
cursor.delete();
isRendering--;
return;
}
}
let displayName;
let displayClass = 'plain';
if (cursor.nodeIsMissing) {
const nodeTypeText = cursor.nodeIsNamed ? cursor.nodeType : `"${cursor.nodeType}"`;
displayName = `MISSING ${nodeTypeText}`;
} else if (cursor.nodeIsNamed) {
displayName = cursor.nodeType;
} else if (anonymousNodes.checked) {
displayName = cursor.nodeType
}
if (visitedChildren) {
if (displayName) {
finishedRow = true;
}
if (cursor.gotoNextSibling()) {
visitedChildren = false;
} else if (cursor.gotoParent()) {
visitedChildren = true;
indentLevel--;
} else {
break;
}
} else {
if (displayName) {
if (finishedRow) {
row += "</div>";
rows.push(row);
finishedRow = false;
}
const start = cursor.startPosition;
const end = cursor.endPosition;
const id = cursor.nodeId;
let fieldName = cursor.currentFieldName;
if (fieldName) {
fieldName += ": ";
} else {
fieldName = "";
}
const nodeClass =
displayName === 'ERROR' || displayName.startsWith('MISSING')
? 'node-link error'
: cursor.nodeIsNamed
? 'node-link named'
: 'node-link anonymous';
row = `<div class="tree-row">${" ".repeat(indentLevel)}${fieldName}` +
`<a class='${nodeClass}' href="#" data-id=${id} ` +
`data-range="${start.row},${start.column},${end.row},${end.column}">` +
`${displayName}</a> <span class="position-info">` +
`[${start.row}, ${start.column}] - [${end.row}, ${end.column}]</span>`;
finishedRow = true;
}
if (cursor.gotoFirstChild()) {
visitedChildren = false;
indentLevel++;
} else {
visitedChildren = true;
}
}
}
if (finishedRow) {
row += "</div>";
rows.push(row);
}
cursor.delete();
cluster.update(rows);
treeRows = rows;
isRendering--;
handleCursorMovement();
}
function runTreeQuery(_, startRow, endRow) {
if (endRow == null) {
const viewport = codeEditor.getViewport();
startRow = viewport.from;
endRow = viewport.to;
}
codeEditor.operation(() => {
const marks = codeEditor.getAllMarks();
marks.forEach((m) => m.clear());
if (tree && query) {
const captures = query.captures(
tree.rootNode,
{ row: startRow, column: 0 },
{ row: endRow, column: 0 },
);
let lastNodeId;
for (const { name, node } of captures) {
if (node.id === lastNodeId) continue;
lastNodeId = node.id;
const { startPosition, endPosition } = node;
codeEditor.markText(
{ line: startPosition.row, ch: startPosition.column },
{ line: endPosition.row, ch: endPosition.column },
{
inclusiveLeft: true,
inclusiveRight: true,
css: `color: ${colorForCaptureName(name)}`,
},
);
}
}
});
}
// When we change from a dark theme to a light theme (and vice versa), the colors of the
// captures need to be updated.
const observer = new MutationObserver((mutations) => {
mutations.forEach((mutation) => {
if (mutation.attributeName === 'class') {
handleQueryChange();
}
});
});
observer.observe(document.documentElement, {
attributes: true,
attributeFilter: ['class']
});
function handleQueryChange() {
if (query) {
query.delete();
query.deleted = true;
query = null;
}
queryEditor.operation(() => {
queryEditor.getAllMarks().forEach((m) => m.clear());
if (!queryCheckbox.checked) return;
const queryText = queryEditor.getValue();
try {
query = parser.getLanguage().query(queryText);
let match;
let row = 0;
queryEditor.eachLine((line) => {
while ((match = CAPTURE_REGEX.exec(line.text))) {
queryEditor.markText(
{ line: row, ch: match.index },
{ line: row, ch: match.index + match[0].length },
{
inclusiveLeft: true,
inclusiveRight: true,
css: `color: ${colorForCaptureName(match[1])}`,
},
);
}
row++;
});
} catch (error) {
const startPosition = queryEditor.posFromIndex(error.index);
const endPosition = {
line: startPosition.line,
ch: startPosition.ch + (error.length || Infinity),
};
if (error.index === queryText.length) {
if (startPosition.ch > 0) {
startPosition.ch--;
} else if (startPosition.row > 0) {
startPosition.row--;
startPosition.column = Infinity;
}
}
queryEditor.markText(startPosition, endPosition, {
className: "query-error",
inclusiveLeft: true,
inclusiveRight: true,
attributes: { title: error.message },
});
}
});
runTreeQuery();
saveQueryState();
}
function handleCursorMovement() {
if (isRendering) return;
const selection = codeEditor.getDoc().listSelections()[0];
let start = { row: selection.anchor.line, column: selection.anchor.ch };
let end = { row: selection.head.line, column: selection.head.ch };
if (
start.row > end.row ||
(start.row === end.row && start.column > end.column)
) {
let swap = end;
end = start;
start = swap;
}
const node = tree.rootNode.namedDescendantForPosition(start, end);
if (treeRows) {
if (treeRowHighlightedIndex !== -1) {
const row = treeRows[treeRowHighlightedIndex];
if (row)
treeRows[treeRowHighlightedIndex] = row.replace(
"highlighted",
"plain",
);
}
treeRowHighlightedIndex = treeRows.findIndex((row) =>
row.includes(`data-id=${node.id}`),
);
if (treeRowHighlightedIndex !== -1) {
const row = treeRows[treeRowHighlightedIndex];
if (row)
treeRows[treeRowHighlightedIndex] = row.replace(
"plain",
"highlighted",
);
}
cluster.update(treeRows);
const lineHeight = cluster.options.item_height;
const scrollTop = outputContainerScroll.scrollTop;
const containerHeight = outputContainerScroll.clientHeight;
const offset = treeRowHighlightedIndex * lineHeight;
if (scrollTop > offset - 20) {
$(outputContainerScroll).animate({ scrollTop: offset - 20 }, 150);
} else if (scrollTop < offset + lineHeight + 40 - containerHeight) {
$(outputContainerScroll).animate(
{ scrollTop: offset - containerHeight + 40 },
150,
);
}
}
}
function handleTreeClick(event) {
if (event.target.tagName === "A") {
event.preventDefault();
const [startRow, startColumn, endRow, endColumn] =
event.target.dataset.range.split(",").map((n) => parseInt(n));
codeEditor.focus();
codeEditor.setSelection(
{ line: startRow, ch: startColumn },
{ line: endRow, ch: endColumn },
);
}
}
function handleLoggingChange() {
if (loggingCheckbox.checked) {
parser.setLogger((message, lexing) => {
if (lexing) {
console.log(" ", message);
} else {
console.log(message);
}
});
} else {
parser.setLogger(null);
}
}
function handleQueryEnableChange() {
if (queryCheckbox.checked) {
queryContainer.style.visibility = "";
queryContainer.style.position = "";
} else {
queryContainer.style.visibility = "hidden";
queryContainer.style.position = "absolute";
}
handleQueryChange();
}
function treeEditForEditorChange(change) {
const oldLineCount = change.removed.length;
const newLineCount = change.text.length;
const lastLineLength = change.text[newLineCount - 1].length;
const startPosition = { row: change.from.line, column: change.from.ch };
const oldEndPosition = { row: change.to.line, column: change.to.ch };
const newEndPosition = {
row: startPosition.row + newLineCount - 1,
column:
newLineCount === 1
? startPosition.column + lastLineLength
: lastLineLength,
};
const startIndex = codeEditor.indexFromPos(change.from);
let newEndIndex = startIndex + newLineCount - 1;
let oldEndIndex = startIndex + oldLineCount - 1;
for (let i = 0; i < newLineCount; i++) newEndIndex += change.text[i].length;
for (let i = 0; i < oldLineCount; i++)
oldEndIndex += change.removed[i].length;
return {
startIndex,
oldEndIndex,
newEndIndex,
startPosition,
oldEndPosition,
newEndPosition,
};
}
function colorForCaptureName(capture) {
const id = query.captureNames.indexOf(capture);
const isDark = document.querySelector('html').classList.contains('ayu') ||
document.querySelector('html').classList.contains('coal') ||
document.querySelector('html').classList.contains('navy');
const colors = isDark ? DARK_COLORS : LIGHT_COLORS;
return colors[id % colors.length];
}
function loadState() {
const language = localStorage.getItem("language");
const sourceCode = localStorage.getItem("sourceCode");
const anonNodes = localStorage.getItem("anonymousNodes");
const query = localStorage.getItem("query");
const queryEnabled = localStorage.getItem("queryEnabled");
if (language != null && sourceCode != null && query != null) {
queryInput.value = query;
codeInput.value = sourceCode;
languageSelect.value = language;
anonymousNodes.checked = anonNodes === "true";
queryCheckbox.checked = queryEnabled === "true";
}
}
function saveState() {
localStorage.setItem("language", languageSelect.value);
localStorage.setItem("sourceCode", codeEditor.getValue());
localStorage.setItem("anonymousNodes", anonymousNodes.checked);
saveQueryState();
}
function saveQueryState() {
localStorage.setItem("queryEnabled", queryCheckbox.checked);
localStorage.setItem("query", queryEditor.getValue());
}
function debounce(func, wait, immediate) {
var timeout;
return function () {
var context = this,
args = arguments;
var later = function () {
timeout = null;
if (!immediate) func.apply(context, args);
};
var callNow = immediate && !timeout;
clearTimeout(timeout);
timeout = setTimeout(later, wait);
if (callNow) func.apply(context, args);
};
}
};

View file

@ -0,0 +1,275 @@
{
"$schema": "http://json-schema.org/draft-07/schema#",
"type": "object",
"properties": {
"$schema": {
"type": "string"
},
"grammars": {
"type": "array",
"items": {
"type": "object",
"properties": {
"name": {
"type": "string",
"description": "The name of the grammar.",
"pattern": "^[a-z0-9_]+$"
},
"camelcase": {
"type": "string",
"description": "The name converted to CamelCase.",
"pattern": "^\\w+$",
"examples": [
"Rust",
"HTML"
],
"$comment": "This is used in the description and the class names."
},
"scope": {
"type": "string",
"description": "The TextMate scope that represents this language.",
"pattern": "^(source|text)(\\.\\w+)+$",
"examples": [
"source.rust",
"text.html"
]
},
"path": {
"type": "string",
"default": ".",
"description": "The relative path to the directory containing the grammar."
},
"external-files": {
"type": "array",
"description": "The relative paths to files that should be checked for modifications during recompilation.",
"items": {
"type": "string"
},
"minItems": 1
},
"file-types": {
"type": "array",
"description": "An array of filename suffix strings.",
"items": {
"type": "string"
},
"minItems": 1
},
"highlights": {
"anyOf": [
{
"type": "string"
},
{
"type": "array",
"items": {
"type": "string"
},
"minItems": 1
}
],
"default": "queries/highlights.scm",
"description": "The path(s) to the grammar's highlight queries."
},
"injections": {
"anyOf": [
{
"type": "string"
},
{
"type": "array",
"items": {
"type": "string"
},
"minItems": 1
}
],
"default": "queries/injections.scm",
"description": "The path(s) to the grammar's injection queries."
},
"locals": {
"anyOf": [
{
"type": "string"
},
{
"type": "array",
"items": {
"type": "string"
},
"minItems": 1
}
],
"default": "queries/locals.scm",
"description": "The path(s) to the grammar's local variable queries."
},
"tags": {
"anyOf": [
{
"type": "string"
},
{
"type": "array",
"items": {
"type": "string"
},
"minItems": 1
}
],
"default": "queries/tags.scm",
"description": "The path(s) to the grammar's code navigation queries."
},
"injection-regex": {
"type": "string",
"format": "regex",
"description": "A regex pattern that will be tested against a language name in order to determine whether this language should be used for a potential language injection site."
},
"first-line-regex": {
"type": "string",
"format": "regex",
"description": "A regex pattern that will be tested against the first line of a file in order to determine whether this language applies to the file."
},
"content-regex": {
"type": "string",
"format": "regex",
"description": "A regex pattern that will be tested against the contents of the file in order to break ties in cases where multiple grammars matched the file."
}
},
"additionalProperties": false,
"required": [
"name",
"scope"
]
},
"minItems": 1
},
"metadata": {
"type": "object",
"properties": {
"version": {
"type": "string",
"description": "The current version of the project.",
"pattern": "^(0|[1-9]\\d*)\\.(0|[1-9]\\d*)\\.(0|[1-9]\\d*)(?:-((?:0|[1-9]\\d*|\\d*[a-zA-Z-][0-9a-zA-Z-]*)(?:\\.(?:0|[1-9]\\d*|\\d*[a-zA-Z-][0-9a-zA-Z-]*))*))?(?:\\+([0-9a-zA-Z-]+(?:\\.[0-9a-zA-Z-]+)*))?$",
"$comment": "The CLI will use this version to update package.json, Cargo.toml, pyproject.toml, Makefile."
},
"license": {
"type": "string",
"default": "MIT",
"description": "The project's license."
},
"description": {
"type": "string",
"description": "The project's description.",
"examples": [
"Rust grammar for tree-sitter"
]
},
"links": {
"type": "object",
"properties": {
"repository": {
"type": "string",
"format": "uri",
"description": "The project's repository."
},
"homepage": {
"type": "string",
"format": "uri",
"description": "The project's homepage."
}
},
"additionalProperties": false,
"required": [
"repository"
]
},
"authors": {
"type": "array",
"items": {
"type": "object",
"description": "The project's author(s).",
"properties": {
"name": {
"type": "string"
},
"email": {
"type": "string",
"format": "email"
},
"url": {
"type": "string",
"format": "uri"
}
},
"additionalProperties": false,
"required": [
"name"
]
},
"minItems": 1
},
"namespace": {
"type": "string",
"description": "The namespace for the Java & Kotlin packages.",
"default": "io.github.tree-sitter",
"$comment": "Used as is in the Maven/Gradle group name and transformed accordingly for the package names and directories (e.g. io.github.treesitter.jtreesitter.html - src/main/java/io/github/treesitter/jtreesitter/html)."
}
},
"additionalProperties": false,
"required": [
"version",
"links"
]
},
"bindings": {
"type": "object",
"description": "The language bindings that will be generated.",
"properties": {
"c": {
"type": "boolean",
"default": true,
"const": true,
"$comment": "Always generated"
},
"go": {
"type": "boolean",
"default": true
},
"java": {
"type": "boolean",
"default": false
},
"kotlin": {
"type": "boolean",
"default": false
},
"node": {
"type": "boolean",
"default": true,
"const": true,
"$comment": "Always generated (for now)"
},
"python": {
"type": "boolean",
"default": true
},
"rust": {
"type": "boolean",
"default": true,
"const": true,
"$comment": "Always generated"
},
"swift": {
"type": "boolean",
"default": true
}
},
"additionalProperties": false
}
},
"additionalProperties": false,
"required": [
"grammars",
"metadata"
]
}

View file

@ -0,0 +1,322 @@
{
"$schema": "http://json-schema.org/draft-07/schema#",
"title": "Tree-sitter grammar specification",
"type": "object",
"required": ["name", "rules"],
"additionalProperties": false,
"properties": {
"$schema": {
"type": "string"
},
"name": {
"description": "The name of the grammar",
"type": "string",
"pattern": "^[a-zA-Z_]\\w*"
},
"inherits": {
"description": "The name of the parent grammar",
"type": "string",
"pattern": "^[a-zA-Z_]\\w*"
},
"rules": {
"type": "object",
"patternProperties": {
"^[a-zA-Z_]\\w*$": {
"$ref": "#/definitions/rule"
}
},
"additionalProperties": false
},
"extras": {
"type": "array",
"uniqueItems": true,
"items": {
"$ref": "#/definitions/rule"
}
},
"precedences": {
"type": "array",
"uniqueItems": true,
"items": {
"type": "array",
"uniqueItems": true,
"items": {
"oneOf": [
{ "type": "string" },
{ "$ref": "#/definitions/symbol-rule" }
]
}
}
},
"reserved": {
"type": "object",
"patternProperties": {
"^[a-zA-Z_]\\w*$": {
"type": "array",
"uniqueItems": true,
"items": {
"$ref": "#/definitions/rule"
}
}
},
"additionalProperties": false
},
"externals": {
"type": "array",
"uniqueItems": true,
"items": {
"$ref": "#/definitions/rule"
}
},
"inline": {
"type": "array",
"uniqueItems": true,
"items": {
"type": "string",
"pattern": "^[a-zA-Z_]\\w*$"
}
},
"conflicts": {
"type": "array",
"uniqueItems": true,
"items": {
"type": "array",
"uniqueItems": true,
"items": {
"type": "string",
"pattern": "^[a-zA-Z_]\\w*$"
}
}
},
"word": {
"type": "string",
"pattern": "^[a-zA-Z_]\\w*"
},
"supertypes": {
"description": "A list of hidden rule names that should be considered supertypes in the generated node types file. See https://tree-sitter.github.io/tree-sitter/using-parsers#static-node-types.",
"type": "array",
"uniqueItems": true,
"items": {
"description": "The name of a rule in `rules` or `extras`",
"type": "string"
}
}
},
"definitions": {
"blank-rule": {
"type": "object",
"properties": {
"type": {
"type": "string",
"const": "BLANK"
}
},
"required": ["type"]
},
"string-rule": {
"type": "object",
"properties": {
"type": {
"type": "string",
"const": "STRING"
},
"value": {
"type": "string"
}
},
"required": ["type", "value"]
},
"pattern-rule": {
"type": "object",
"properties": {
"type": {
"type": "string",
"const": "PATTERN"
},
"value": { "type": "string" },
"flags": { "type": "string" }
},
"required": ["type", "value"]
},
"symbol-rule": {
"type": "object",
"properties": {
"type": {
"type": "string",
"const": "SYMBOL"
},
"name": { "type": "string" }
},
"required": ["type", "name"]
},
"seq-rule": {
"type": "object",
"properties": {
"type": {
"type": "string",
"const": "SEQ"
},
"members": {
"type": "array",
"items": {
"$ref": "#/definitions/rule"
}
}
},
"required": ["type", "members"]
},
"choice-rule": {
"type": "object",
"properties": {
"type": {
"type": "string",
"const": "CHOICE"
},
"members": {
"type": "array",
"items": {
"$ref": "#/definitions/rule"
}
}
},
"required": ["type", "members"]
},
"alias-rule": {
"type": "object",
"properties": {
"type": {
"type": "string",
"const": "ALIAS"
},
"value": { "type": "string" },
"named": { "type": "boolean" },
"content": {
"$ref": "#/definitions/rule"
}
},
"required": ["type", "named", "content", "value"]
},
"repeat-rule": {
"type": "object",
"properties": {
"type": {
"type": "string",
"const": "REPEAT"
},
"content": {
"$ref": "#/definitions/rule"
}
},
"required": ["type", "content"]
},
"repeat1-rule": {
"type": "object",
"properties": {
"type": {
"type": "string",
"const": "REPEAT1"
},
"content": {
"$ref": "#/definitions/rule"
}
},
"required": ["type", "content"]
},
"token-rule": {
"type": "object",
"properties": {
"type": {
"type": "string",
"enum": [
"TOKEN",
"IMMEDIATE_TOKEN"
]
},
"content": {
"$ref": "#/definitions/rule"
}
},
"required": ["type", "content"]
},
"field-rule": {
"properties": {
"name": { "type": "string" },
"type": {
"type": "string",
"const": "FIELD"
},
"content": {
"$ref": "#/definitions/rule"
}
},
"required": ["name", "type", "content"]
},
"prec-rule": {
"type": "object",
"properties": {
"type": {
"type": "string",
"enum": [
"PREC",
"PREC_LEFT",
"PREC_RIGHT",
"PREC_DYNAMIC"
]
},
"value": {
"oneof": [
{ "type": "integer" },
{ "type": "string" }
]
},
"content": {
"$ref": "#/definitions/rule"
}
},
"required": ["type", "content", "value"]
},
"rule": {
"oneOf": [
{ "$ref": "#/definitions/alias-rule" },
{ "$ref": "#/definitions/blank-rule" },
{ "$ref": "#/definitions/string-rule" },
{ "$ref": "#/definitions/pattern-rule" },
{ "$ref": "#/definitions/symbol-rule" },
{ "$ref": "#/definitions/seq-rule" },
{ "$ref": "#/definitions/choice-rule" },
{ "$ref": "#/definitions/repeat1-rule" },
{ "$ref": "#/definitions/repeat-rule" },
{ "$ref": "#/definitions/token-rule" },
{ "$ref": "#/definitions/field-rule" },
{ "$ref": "#/definitions/prec-rule" }
]
}
}
}