mirror of
https://github.com/tree-sitter/tree-sitter.git
synced 2026-09-10 07:36:22 -04:00
Add a playground to the docs site
This commit is contained in:
parent
1fc0525940
commit
e39d69dfa7
2
.gitignore
vendored
2
.gitignore
vendored
|
|
@ -8,6 +8,8 @@ fuzz-results
|
||||||
test/fixtures/grammars/*
|
test/fixtures/grammars/*
|
||||||
!test/fixtures/grammars/.gitkeep
|
!test/fixtures/grammars/.gitkeep
|
||||||
|
|
||||||
|
docs/assets/js/tree-sitter.js
|
||||||
|
|
||||||
/target
|
/target
|
||||||
*.rs.bk
|
*.rs.bk
|
||||||
*.a
|
*.a
|
||||||
|
|
|
||||||
|
|
@ -130,7 +130,7 @@
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
|
||||||
$('h1, h2, h3').filter('[id]').each(function() {
|
$('h2, h3').filter('[id]').each(function() {
|
||||||
$(this).html('<a href="#'+$(this).attr('id')+'">' + $(this).text() + '</a>');
|
$(this).html('<a href="#'+$(this).attr('id')+'">' + $(this).text() + '</a>');
|
||||||
});
|
});
|
||||||
</script>
|
</script>
|
||||||
|
|
|
||||||
|
|
@ -116,3 +116,43 @@ body {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#playground-container {
|
||||||
|
> .CodeMirror {
|
||||||
|
height: auto;
|
||||||
|
max-height: 350px;
|
||||||
|
border: 1px solid #aaa;
|
||||||
|
}
|
||||||
|
|
||||||
|
.CodeMirror-scroll {
|
||||||
|
height: auto;
|
||||||
|
max-height: 350px;
|
||||||
|
}
|
||||||
|
|
||||||
|
h4, select, .field {
|
||||||
|
display: inline-block;
|
||||||
|
margin-right: 20px;
|
||||||
|
}
|
||||||
|
|
||||||
|
#logging-checkbox {
|
||||||
|
height: 15px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.CodeMirror div.CodeMirror-cursor {
|
||||||
|
border-left: 3px solid red;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
#output-container {
|
||||||
|
position: relative;
|
||||||
|
margin-top: 0;
|
||||||
|
overflow: auto;
|
||||||
|
padding: 20px 10px;
|
||||||
|
max-height: 350px;
|
||||||
|
border: 1px solid #aaa;
|
||||||
|
}
|
||||||
|
|
||||||
|
.tree-link.highlighted {
|
||||||
|
background-color: #ddd;
|
||||||
|
text-decoration: underline;
|
||||||
|
}
|
||||||
|
|
|
||||||
191
docs/assets/js/playground.js
Normal file
191
docs/assets/js/playground.js
Normal file
|
|
@ -0,0 +1,191 @@
|
||||||
|
const codeInput = document.getElementById('code-input');
|
||||||
|
const languageSelect = document.getElementById('language-select');
|
||||||
|
const loggingCheckbox = document.getElementById('logging-checkbox');
|
||||||
|
const outputContainer = document.getElementById('output-container');
|
||||||
|
const updateTimeSpan = document.getElementById('update-time');
|
||||||
|
const demoContainer = document.getElementById('playground-container');
|
||||||
|
|
||||||
|
const languagesByName = {};
|
||||||
|
|
||||||
|
let tree;
|
||||||
|
let parser;
|
||||||
|
let codeEditor;
|
||||||
|
let queryEditor;
|
||||||
|
let languageName;
|
||||||
|
let highlightedNodeLink;
|
||||||
|
|
||||||
|
main();
|
||||||
|
|
||||||
|
async function main() {
|
||||||
|
await TreeSitter.init();
|
||||||
|
parser = new TreeSitter();
|
||||||
|
codeEditor = CodeMirror.fromTextArea(codeInput, {
|
||||||
|
lineNumbers: true,
|
||||||
|
showCursorWhenSelecting: true
|
||||||
|
});
|
||||||
|
|
||||||
|
languageName = languageSelect.value;
|
||||||
|
codeEditor.on('changes', handleCodeChange);
|
||||||
|
codeEditor.on('cursorActivity', handleCursorMovement);
|
||||||
|
loggingCheckbox.addEventListener('change', handleLoggingChange);
|
||||||
|
languageSelect.addEventListener('change', handleLanguageChange);
|
||||||
|
outputContainer.addEventListener('click', handleTreeClick);
|
||||||
|
|
||||||
|
await handleLanguageChange();
|
||||||
|
demoContainer.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();
|
||||||
|
}
|
||||||
|
|
||||||
|
function handleLoggingChange() {
|
||||||
|
if (loggingCheckbox.checked) {
|
||||||
|
parser.setLogger(console.log);
|
||||||
|
} else {
|
||||||
|
parser.setLogger(null);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
function handleCodeChange(editor, changes) {
|
||||||
|
let start;
|
||||||
|
if (tree && changes) {
|
||||||
|
start = performance.now();
|
||||||
|
for (const change of changes) {
|
||||||
|
const edit = treeEditForEditorChange(change);
|
||||||
|
tree.edit(edit);
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
start = performance.now();
|
||||||
|
}
|
||||||
|
const newTree = parser.parse(codeEditor.getValue() + '\n', tree);
|
||||||
|
tree && tree.delete();
|
||||||
|
tree = newTree;
|
||||||
|
updateTimeSpan.innerText = `${(performance.now() - start).toFixed(1)} ms`;
|
||||||
|
renderTree();
|
||||||
|
}
|
||||||
|
|
||||||
|
function handleTreeClick(event) {
|
||||||
|
if (event.target.className === 'tree-link') {
|
||||||
|
event.preventDefault();
|
||||||
|
const row = parseInt(event.target.dataset.row);
|
||||||
|
const column = parseInt(event.target.dataset.column);
|
||||||
|
codeEditor.setCursor({line: row, ch: column});
|
||||||
|
codeEditor.focus();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
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
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
var handleCursorMovement = debounce(() => {
|
||||||
|
if (highlightedNodeLink) {
|
||||||
|
highlightedNodeLink.classList.remove('highlighted');
|
||||||
|
highlightedNodeLink = null;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!codeEditor.somethingSelected()) 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);
|
||||||
|
const link = document.querySelector(`.tree-link[data-id="${node[0]}"]`);
|
||||||
|
link.classList.add('highlighted');
|
||||||
|
highlightedNodeLink = link;
|
||||||
|
|
||||||
|
$(outputContainer).animate({
|
||||||
|
scrollTop: Math.max(0, link.offsetTop - outputContainer.clientHeight / 2)
|
||||||
|
}, 200);
|
||||||
|
}, 300);
|
||||||
|
|
||||||
|
var renderTree = debounce(() => {
|
||||||
|
let result = "";
|
||||||
|
renderNode(tree.rootNode, 0);
|
||||||
|
function renderNode(node, indentLevel) {
|
||||||
|
let space = ' '.repeat(indentLevel);
|
||||||
|
const type = node.type;
|
||||||
|
const start = node.startPosition;
|
||||||
|
const end = node.endPosition;
|
||||||
|
result += space;
|
||||||
|
result += "(<a class='tree-link' href='#' ";
|
||||||
|
result += `data-id="${node[0]}" data-row=${start.row} data-column=${start.column}>${type}</a>`
|
||||||
|
result += `[${start.row + 1}, ${start.column}] - [${end.row + 1}, ${end.column}]`;
|
||||||
|
if (node.namedChildCount > 0) {
|
||||||
|
for (let i = 0, n = node.namedChildCount; i < n; i++) {
|
||||||
|
result += '\n';
|
||||||
|
renderNode(node.namedChild(i), indentLevel + 1);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
result += ')';
|
||||||
|
}
|
||||||
|
|
||||||
|
outputContainer.innerHTML = result;
|
||||||
|
}, 200);
|
||||||
|
|
||||||
|
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);
|
||||||
|
};
|
||||||
|
};
|
||||||
60
docs/section-5-playground.html
Normal file
60
docs/section-5-playground.html
Normal file
|
|
@ -0,0 +1,60 @@
|
||||||
|
---
|
||||||
|
layout: default
|
||||||
|
title: Playground
|
||||||
|
permalink: playground
|
||||||
|
---
|
||||||
|
|
||||||
|
<link rel="stylesheet" href="https://cdnjs.cloudflare.com/ajax/libs/codemirror/5.45.0/codemirror.min.css">
|
||||||
|
|
||||||
|
<h1>Playground</h1>
|
||||||
|
|
||||||
|
<div id="playground-container" style="visibility: hidden;">
|
||||||
|
|
||||||
|
<h4>Code</h4>
|
||||||
|
<select id="language-select">
|
||||||
|
<option value="bash">Bash</option>
|
||||||
|
<option value="c">C</option>
|
||||||
|
<option value="cpp">C++</option>
|
||||||
|
<option value="go">Go</option>
|
||||||
|
<option value="html">HTML</option>
|
||||||
|
<option value="javascript" selected="selected">JavaScript</option>
|
||||||
|
<option value="python">Python</option>
|
||||||
|
<option value="ruby">Ruby</option>
|
||||||
|
<option value="rust">Rust</option>
|
||||||
|
</select>
|
||||||
|
|
||||||
|
<input id="logging-checkbox" type="checkbox"></input>
|
||||||
|
<label for="logging-checkbox">Log</label>
|
||||||
|
|
||||||
|
<textarea id="code-input">
|
||||||
|
function quicksort() {
|
||||||
|
function sort(items) {
|
||||||
|
if (items.length <= 1) return items;
|
||||||
|
var pivot = items.shift(), current, left = [], right = [];
|
||||||
|
while (items.length > 0) {
|
||||||
|
current = items.shift();
|
||||||
|
current < pivot ? left.push(current) : right.push(current);
|
||||||
|
}
|
||||||
|
return sort(left).concat(pivot).concat(sort(right));
|
||||||
|
};
|
||||||
|
return sort(Array.apply(this, arguments));
|
||||||
|
};
|
||||||
|
</textarea>
|
||||||
|
|
||||||
|
<h4>Tree</h4>
|
||||||
|
<span id="update-time"></span>
|
||||||
|
<pre id="output-container" class="highlight"></pre>
|
||||||
|
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<script src="https://cdnjs.cloudflare.com/ajax/libs/codemirror/5.45.0/codemirror.min.js"></script>
|
||||||
|
|
||||||
|
{% if jekyll.environment == "development" %}
|
||||||
|
<script>LANGUAGE_BASE_URL = "/assets/js";</script>
|
||||||
|
<script src="/assets/js/tree-sitter.js"></script>
|
||||||
|
{% else %}
|
||||||
|
<script>LANGUAGE_BASE_URL = "https://tree-sitter.github.io";</script>
|
||||||
|
<script src="https://tree-sitter.github.io/tree-sitter.js"></script>
|
||||||
|
{% endif %}
|
||||||
|
|
||||||
|
<script src="/assets/js/playground.js"></script>
|
||||||
|
|
@ -1,4 +1,25 @@
|
||||||
#!/bin/bash
|
#!/bin/bash
|
||||||
|
|
||||||
|
root=$PWD
|
||||||
cd docs
|
cd docs
|
||||||
bundle exec jekyll serve "$@"
|
|
||||||
|
bundle exec jekyll serve "$@" &
|
||||||
|
|
||||||
|
bundle exec ruby <<RUBY &
|
||||||
|
require "listen"
|
||||||
|
dir = "$root/target/release"
|
||||||
|
puts "Watching #{dir}"
|
||||||
|
listener = Listen.to(dir, only: /^tree-sitter\.(js|wasm)$/, wait_for_delay: 3.0) do
|
||||||
|
puts "WASM files updated. Copying to docs folder..."
|
||||||
|
system(
|
||||||
|
"cp",
|
||||||
|
"$root/target/release/tree-sitter.js",
|
||||||
|
"$root/target/release/tree-sitter.wasm",
|
||||||
|
"$root/docs/assets/js/"
|
||||||
|
)
|
||||||
|
end
|
||||||
|
listener.start
|
||||||
|
sleep
|
||||||
|
RUBY
|
||||||
|
|
||||||
|
wait
|
||||||
|
|
|
||||||
Loading…
Reference in a new issue