import { getEditor } from './editor.js';
export function renderOutput(result) {
renderTokensTab(result.tokens);
renderCstTab(result.cst);
renderInfoTab(result);
const parseTime = result.parseTimeMs ?? 0;
updateParseTime(parseTime);
}
function updateParseTime(timeMs) {
const element = document.getElementById('parse-time');
if (element) {
element.textContent = `Parse time: ${timeMs.toFixed(2)}ms`;
element.style.color = timeMs < 100 ? 'var(--status-success)' :
timeMs < 500 ? 'var(--status-warning)' :
'var(--status-error)';
}
}
export function renderTokensTab(tokens) {
const container = document.getElementById('tokens-content');
if (!container) return;
container.innerHTML = '';
const showWhitespace = document.getElementById('show-whitespace')?.checked ?? false;
const filteredTokens = showWhitespace ? tokens : tokens.filter(t => t.kind !== 'whitespace' & t.kind !== 'Newline');
const table = document.createElement('table');
table.className = 'tokens-table';
const thead = document.createElement('thead');
thead.innerHTML = `
<tr>
<th>Type</th>
<th>Value</th>
<th>Position</th>
<th>Length</th>
</tr>
`;
table.appendChild(thead);
const tbody = document.createElement('tbody');
filteredTokens.forEach((token, index) => {
const row = document.createElement('tr');
row.dataset.tokenIndex = index;
row.dataset.line = token.line;
row.dataset.column = token.column;
row.addEventListener('click', () => {
highlightTokenInEditor(token);
});
const typeCell = document.createElement('td');
const typeBadge = document.createElement('span');
typeBadge.className = `token-type ${token.kind}`;
typeBadge.textContent = token.kind;
typeCell.appendChild(typeBadge);
const valueCell = document.createElement('td');
valueCell.textContent = token.content;
valueCell.style = "white-space: pre;";
valueCell.style.fontFamily = "'Courier New', monospace";
const posCell = document.createElement('td');
posCell.textContent = `${token.line}:${token.column}`;
posCell.style.fontFamily = "'Courier New', monospace";
const lengthCell = document.createElement('td');
lengthCell.textContent = token.length;
row.appendChild(typeCell);
row.appendChild(valueCell);
row.appendChild(posCell);
row.appendChild(lengthCell);
tbody.appendChild(row);
});
table.appendChild(tbody);
container.appendChild(table);
console.log(`✅ Rendered ${filteredTokens.length} tokens`);
}
let nodeIdCounter = 0;
export function renderCstTab(cst) {
const container = document.getElementById('cst-content');
if (!container) return;
container.innerHTML = '';
nodeIdCounter = 0;
const showRanges = document.getElementById('show-byte-ranges')?.checked ?? true;
addNodeIds(cst);
const treeElement = renderCstNode(cst, 0, showRanges);
container.appendChild(treeElement);
console.log(`✅ Rendered CST tree`);
}
function addNodeIds(node) {
if (!node) return;
node._nodeId = nodeIdCounter++;
if (node.children) {
node.children.forEach(child => addNodeIds(child));
}
}
function renderCstNode(node, depth, showRanges) {
const nodeDiv = document.createElement('div');
nodeDiv.className = 'cst-node';
nodeDiv.dataset.nodeType = node.kind;
nodeDiv.dataset.depth = depth;
const header = document.createElement('div');
header.className = 'cst-node-header';
if (node.children && node.children.length > 0) {
const toggle = document.createElement('span');
toggle.className = 'cst-node-toggle';
toggle.addEventListener('click', (e) => {
e.stopPropagation();
nodeDiv.classList.toggle('collapsed');
});
header.appendChild(toggle);
} else {
const spacer = document.createElement('span');
spacer.style.marginRight = '16px';
header.appendChild(spacer);
}
const name = document.createElement('span');
name.className = 'cst-node-name';
name.textContent = node.kind;
header.appendChild(name);
if (node.value !== undefined) {
const value = document.createElement('span');
value.textContent = ` = "${escapeHtml(node.value)}"`;
value.style.color = 'var(--placeholder-color)';
header.appendChild(value);
}
if (showRanges && node.range) {
const range = document.createElement('span');
range.className = 'cst-node-range';
range.textContent = ` [${node.range[0]}..${node.range[1]}]`;
header.appendChild(range);
}
if (node.range) {
nodeDiv.dataset.rangeStart = node.range[0];
nodeDiv.dataset.rangeEnd = node.range[1];
}
if (node._nodeId !== undefined) {
nodeDiv.dataset.nodeId = node._nodeId;
}
header.addEventListener('click', (e) => {
e.stopPropagation();
highlightAndPositionCursor(node);
});
header.addEventListener('mouseenter', () => {
highlightNodeInEditor(node, true);
});
header.addEventListener('mouseleave', () => {
clearEditorHighlight();
});
nodeDiv.appendChild(header);
if (node.children && node.children.length > 0) {
const childrenDiv = document.createElement('div');
childrenDiv.className = 'cst-node-children';
node.children.forEach(child => {
const childElement = renderCstNode(child, depth + 1, showRanges);
childrenDiv.appendChild(childElement);
});
nodeDiv.appendChild(childrenDiv);
}
return nodeDiv;
}
export function renderInfoTab(result) {
const container = document.getElementById('info-content');
if (!container) return;
const placeholder = container.querySelector('.placeholder');
if (placeholder) {
placeholder.classList.add('hidden');
}
const statsSection = document.getElementById('info-stats');
if (statsSection) {
statsSection.classList.remove('hidden');
const tokenCount = result.stats?.token_count ?? result.stats?.tokenCount ?? 0;
const parseTime = result.parseTimeMs ?? 0;
const treeDepth = result.stats?.tree_depth ?? result.stats?.treeDepth ?? 0;
const nodeCount = result.stats?.node_count ?? result.stats?.nodeCount ?? 0;
const fileType = result.cst?.kind ?? 'Unknown';
const editor = getEditor();
let lineCount = 0;
let charCount = 0;
if (editor) {
const model = editor.getModel();
lineCount = model.getLineCount();
charCount = model.getValueLength();
}
const statTokensEl = document.getElementById('stat-tokens');
const statParseTimeEl = document.getElementById('stat-parse-time');
const statTreeDepthEl = document.getElementById('stat-tree-depth');
const statNodeCountEl = document.getElementById('stat-node-count');
const statFileTypeEl = document.getElementById('stat-file-type');
const statLinesEl = document.getElementById('stat-lines');
const statCharsEl = document.getElementById('stat-chars');
if (statTokensEl) statTokensEl.textContent = tokenCount.toLocaleString();
if (statParseTimeEl) statParseTimeEl.textContent = `${parseTime.toFixed(2)}ms`;
if (statTreeDepthEl) statTreeDepthEl.textContent = treeDepth.toLocaleString();
if (statNodeCountEl) statNodeCountEl.textContent = nodeCount.toLocaleString();
if (statFileTypeEl) statFileTypeEl.textContent = fileType;
if (statLinesEl) statLinesEl.textContent = lineCount.toLocaleString();
if (statCharsEl) statCharsEl.textContent = charCount.toLocaleString();
}
const errorsSection = document.getElementById('info-errors');
const errorsList = document.getElementById('errors-list');
if (result.errors && result.errors.length > 0) {
errorsSection?.classList.remove('hidden');
if (errorsList) {
errorsList.innerHTML = '';
result.errors.forEach(error => {
const errorElement = createErrorElement(error);
errorsList.appendChild(errorElement);
});
}
} else {
errorsSection?.classList.add('hidden');
}
const warningsSection = document.getElementById('info-warnings');
const warningsList = document.getElementById('warnings-list');
if (result.warnings && result.warnings.length > 0) {
warningsSection?.classList.remove('hidden');
if (warningsList) {
warningsList.innerHTML = '';
result.warnings.forEach(warning => {
const warningElement = createWarningElement(warning);
warningsList.appendChild(warningElement);
});
}
} else {
warningsSection?.classList.add('hidden');
}
console.log(`✅ Rendered info tab`);
}
function createErrorElement(error) {
const div = document.createElement('div');
div.className = 'error-item';
const header = document.createElement('div');
header.className = 'error-header';
const type = document.createElement('span');
type.className = 'error-type';
type.textContent = error.type || 'Parse Error';
const location = document.createElement('span');
location.className = 'error-location';
location.textContent = `Line ${error.line}, Col ${error.column}`;
header.appendChild(type);
header.appendChild(location);
const message = document.createElement('div');
message.className = 'error-message';
message.textContent = error.message;
div.appendChild(header);
div.appendChild(message);
div.addEventListener('click', () => {
if (error.range) {
highlightRange(error.line, error.column, error.line, error.column + 1);
}
});
return div;
}
function createWarningElement(warning) {
const div = document.createElement('div');
div.className = 'warning-item';
const header = document.createElement('div');
header.className = 'warning-header';
const type = document.createElement('span');
type.className = 'warning-type';
type.textContent = warning.type || 'Warning';
const location = document.createElement('span');
location.className = 'warning-location';
location.textContent = `Line ${warning.line}, Col ${warning.column}`;
header.appendChild(type);
header.appendChild(location);
const message = document.createElement('div');
message.className = 'warning-message';
message.textContent = warning.message;
div.appendChild(header);
div.appendChild(message);
return div;
}
function highlightTokenInEditor(token) {
console.log('TODO: Highlight token in editor:', token);
const event = new CustomEvent('highlightInEditor', {
detail: {
line: token.line,
column: token.column,
length: token.length
}
});
document.dispatchEvent(event);
}
function highlightNodeInEditor(node, isHover = false) {
if (!node.range) return;
const event = new CustomEvent('highlightNodeInEditor', {
detail: {
startOffset: node.range[0],
endOffset: node.range[1],
isHover: isHover
}
});
document.dispatchEvent(event);
}
function highlightAndPositionCursor(node) {
if (!node.range) return;
const event = new CustomEvent('highlightAndPositionCursor', {
detail: {
startOffset: node.range[0],
endOffset: node.range[1]
}
});
document.dispatchEvent(event);
}
function clearEditorHighlight() {
const event = new CustomEvent('clearEditorHighlight');
document.dispatchEvent(event);
}
function escapeHtml(text) {
const div = document.createElement('div');
div.textContent = text;
return div.innerHTML;
}
export function clearOutput() {
const tokensContent = document.getElementById('tokens-content');
if (tokensContent) {
tokensContent.innerHTML = '<div class="placeholder"><p>👈 Enter VB6 code and click Parse to see tokens</p></div>';
}
const cstContent = document.getElementById('cst-content');
if (cstContent) {
cstContent.innerHTML = '<div class="placeholder"><p>👈 Parse code to see the Concrete Syntax Tree</p></div>';
}
const infoStats = document.getElementById('info-stats');
if (infoStats) {
infoStats.classList.add('hidden');
}
const infoErrors = document.getElementById('info-errors');
if (infoErrors) {
infoErrors.classList.add('hidden');
}
const infoWarnings = document.getElementById('info-warnings');
if (infoWarnings) {
infoWarnings.classList.add('hidden');
}
const infoContent = document.getElementById('info-content');
const placeholder = infoContent?.querySelector('.placeholder');
if (placeholder) {
placeholder.classList.remove('hidden');
}
updateParseTime(0);
}
export default {
renderOutput,
renderTokensTab,
renderCstTab,
renderInfoTab,
clearOutput
};