tree-sitter-toml/docs/tree-sitter-playground-0.15.8/playground.js

280 lines
8.4 KiB
JavaScript

let tree;
(async () => {
const scriptURL = document.currentScript.getAttribute('src');
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 outputContainerScroll = document.getElementById('output-container-scroll');
const updateTimeSpan = document.getElementById('update-time');
const demoContainer = document.getElementById('playground-container');
const languagesByName = {};
await TreeSitter.init();
const parser = new TreeSitter();
const codeEditor = CodeMirror.fromTextArea(codeInput, {
lineNumbers: true,
showCursorWhenSelecting: true
});
const cluster = new Clusterize({
rows: [],
noDataText: null,
contentElem: outputContainer,
scrollElem: outputContainerScroll
});
const renderTreeOnCodeChange = debounce(renderTree, 50);
let languageName = languageSelect.value;
let treeRows = null;
let treeRowHighlightedIndex = -1;
let parseCount = 0;
let isRendering = 0;
codeEditor.on('changes', handleCodeChange);
codeEditor.on('cursorActivity', debounce(handleCursorMovement, 150));
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();
}
async function handleCodeChange(editor, changes) {
const newText = codeEditor.getValue() + '\n';
const start = performance.now();
if (tree && changes) {
for (const change of changes) {
tree.edit(treeEditForEditorChange(change));
}
}
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();
}
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;
if (cursor.nodeIsMissing) {
displayName = `MISSING ${cursor.nodeType}`
} else if (cursor.nodeIsNamed) {
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 = '';
}
row = `<div>${' '.repeat(indentLevel)}${fieldName}<a class='plain' href="#" data-id=${id} data-range="${start.row},${start.column},${end.row},${end.column}">${displayName}</a> [${start.row}, ${start.column}] - [${end.row}, ${end.column}])`;
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 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 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 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);
};
}
})();