"use strict"; var __createBinding = (this && this.__createBinding) || (Object.create ? (function(o, m, k, k2) { if (k2 === undefined) k2 = k; var desc = Object.getOwnPropertyDescriptor(m, k); if (!desc || ("get" in desc ? !m.__esModule : desc.writable || desc.configurable)) { desc = { enumerable: true, get: function() { return m[k]; } }; } Object.defineProperty(o, k2, desc); }) : (function(o, m, k, k2) { if (k2 === undefined) k2 = k; o[k2] = m[k]; })); var __setModuleDefault = (this && this.__setModuleDefault) || (Object.create ? (function(o, v) { Object.defineProperty(o, "default", { enumerable: true, value: v }); }) : function(o, v) { o["default"] = v; }); var __importStar = (this && this.__importStar) || (function () { var ownKeys = function(o) { ownKeys = Object.getOwnPropertyNames || function (o) { var ar = []; for (var k in o) if (Object.prototype.hasOwnProperty.call(o, k)) ar[ar.length] = k; return ar; }; return ownKeys(o); }; return function (mod) { if (mod && mod.__esModule) return mod; var result = {}; if (mod != null) for (var k = ownKeys(mod), i = 0; i < k.length; i++) if (k[i] !== "default") __createBinding(result, mod, k[i]); __setModuleDefault(result, mod); return result; }; })(); Object.defineProperty(exports, "__esModule", { value: true }); exports.JqhtmlFormattingEditProvider = void 0; const vscode = __importStar(require("vscode")); class JqhtmlFormattingEditProvider { constructor() { this.indentSize = 2; // IMPORTANT: DO NOT USE REGEX FOR PARSING IN THIS FORMATTER // This formatter uses string manipulation and indexOf for reliability // Regex should only be used in the syntax highlighter, not here // Based on the RS3 formatter (reformat_html.php) logic // Known self-closing HTML tags this.selfClosingTags = [ 'area', 'base', 'br', 'embed', 'hr', 'iframe', 'img', 'input', 'link', 'meta', 'param', 'source', 'track' ]; } provideDocumentFormattingEdits(document, options) { this.indentSize = options.tabSize || 2; const formatted = this.formatDocument(document); const fullRange = new vscode.Range(document.positionAt(0), document.positionAt(document.getText().length)); return [vscode.TextEdit.replace(fullRange, formatted)]; } formatDocument(document) { const text = document.getText(); const safeCode = []; let working = text; let reading = text; // Step 1: Escape JQHTML comments (<%-- --%>) working = ''; while (reading.indexOf('<%--') !== -1) { const pos = reading.indexOf('<%--'); working += reading.substring(0, pos); reading = reading.substring(pos); const closePos = reading.indexOf('--%>'); if (closePos === -1) { // Parse error, return original return text; } safeCode.push(reading.substring(0, closePos + 4)); reading = reading.substring(closePos + 4); working += '@@__SAFE__(' + (safeCode.length - 1) + ')'; } working += reading; reading = working; // Step 2: Escape HTML comments () working = ''; while (reading.indexOf(''); if (closePos === -1) { // Parse error, return original return text; } safeCode.push(reading.substring(0, closePos + 3)); reading = reading.substring(closePos + 3); working += '@@__SAFE__(' + (safeCode.length - 1) + ')'; } working += reading; reading = working; // Step 3: Escape multiline <% %> blocks working = ''; while (reading.indexOf('<%') !== -1) { const pos = reading.indexOf('<%'); working += reading.substring(0, pos); reading = reading.substring(pos); const closePos = reading.indexOf('%>'); if (closePos === -1) { // Parse error, return original return text; } const nlPos = reading.indexOf('\n'); if (nlPos === -1 || nlPos > closePos) { // Not multiline, keep it working += reading.substring(0, closePos + 2); reading = reading.substring(closePos + 2); continue; } // It's multiline, escape it safeCode.push(reading.substring(0, closePos + 2)); reading = reading.substring(closePos + 2); working += '@@__SAFE__(' + (safeCode.length - 1) + ')'; } working += reading; // Step 4: Split into lines with indent levels const lines = []; const splitLines = working.split('\n'); for (const line of splitLines) { lines.push([0, line.trim()]); } // Step 5: Handle JS/control flow indents by counting braces let jsIndent = 0; for (let i = 0; i < lines.length; i++) { const trimmedLine = lines[i][1]; // Count opening and closing braces within <% %> blocks let openBraces = 0; let closeBraces = 0; // Find all <% %> blocks in the line let searchPos = 0; while (true) { const startPos = trimmedLine.indexOf('<%', searchPos); if (startPos === -1) break; const endPos = trimmedLine.indexOf('%>', startPos); if (endPos === -1) break; // Extract the code block content const codeBlock = trimmedLine.substring(startPos + 2, endPos); // Count braces in this code block for (let j = 0; j < codeBlock.length; j++) { if (codeBlock[j] === '{') openBraces++; if (codeBlock[j] === '}') closeBraces++; } searchPos = endPos + 2; } const netChange = openBraces - closeBraces; // Apply indent changes if (netChange > 0) { // Opening braces - indent applies to next line lines[i][0] = jsIndent; jsIndent += netChange; } else if (netChange < 0) { // Closing braces - dedent this line jsIndent += netChange; lines[i][0] = jsIndent; } else { // No change lines[i][0] = jsIndent; } // Special handling for else statements - dedent by 1 if (trimmedLine.indexOf('<% else') !== -1 || trimmedLine.indexOf('<% } else') !== -1) { lines[i][0]--; } } // Step 6: Escape remaining single-line code blocks for (let i = 0; i < lines.length; i++) { reading = lines[i][1]; working = ''; // Escape <% %> blocks while (reading.indexOf('<%') !== -1) { const pos = reading.indexOf('<%'); working += reading.substring(0, pos); reading = reading.substring(pos); const closePos = reading.indexOf('%>'); if (closePos === -1) { working += reading; reading = ''; continue; } safeCode.push(reading.substring(0, closePos + 2)); reading = reading.substring(closePos + 2); working += '@@__SAFE__(' + (safeCode.length - 1) + ')'; } working += reading; lines[i][1] = working; } // Step 7: Handle HTML tag indents let htmlIndent = 0; let openSelfClosingTag = null; // Track if we're inside a multiline self-closing tag for (let i = 0; i < lines.length; i++) { const line = lines[i][1]; let thisIndent = 0; // Check if this line opens a self-closing tag (multiline) // e.g., " on same line for (const tag of this.selfClosingTags) { const searchStr = '<' + tag; if (line.indexOf(searchStr) !== -1 && line.indexOf('>') === -1) { // Opened a self-closing tag but no > yet - it's multiline openSelfClosingTag = tag; break; } } // Check if this line closes a multiline self-closing tag // e.g., a line with just ">" or "alt='text'>" when we have an open ') !== -1 && line.indexOf('<') === -1) { // This line closes the self-closing tag - don't count it as opening openSelfClosingTag = null; thisIndent--; // Counteract the opening < that was counted earlier } // Count opening tags thisIndent += this.countOccurrences(line, '<'); // Subtract self-closing tags thisIndent -= this.countOccurrences(line, '/>'); // Subtract closing tags (count double) thisIndent -= this.countOccurrences(line, ' if (parts[j].indexOf('/>') === -1 && parts[j].indexOf('>') !== -1) { thisIndent--; } else if (parts[j].indexOf('/>') !== -1 && parts[j].indexOf('>') !== -1 && parts[j].indexOf('/>') > parts[j].indexOf('>')) { // Has both > and />, but > comes first thisIndent--; } } } } // Special case for DOCTYPE if (line.indexOf(' but no <) if (line.indexOf('/>') !== -1 && line.indexOf('<') === -1) { lines[i][0]++; } } // Step 8: Build result with proper indentation let result = ''; for (const [indent, line] of lines) { const finalIndent = Math.max(0, indent); if (line.length > 0) { result += ' '.repeat(finalIndent * this.indentSize) + line + '\n'; } else { result += '\n'; } } // Step 9: Restore safe blocks for (let attempt = 0; attempt < 10; attempt++) { let hasChanges = false; for (let i = 0; i < safeCode.length; i++) { const placeholder = '@@__SAFE__(' + i + ')'; if (result.indexOf(placeholder) !== -1) { result = result.replace(placeholder, safeCode[i]); hasChanges = true; } } if (!hasChanges || result.indexOf('@@__SAFE__') === -1) { break; } } // Step 10: Add blank lines around Define tag contents result = this.addDefineTagSpacing(result); return result.trim(); } addDefineTagSpacing(text) { const lines = text.split('\n'); const resultLines = []; for (let i = 0; i < lines.length; i++) { const currentLine = lines[i]; const trimmedCurrent = currentLine.trim(); // Check if this is an opening Define tag if (trimmedCurrent.startsWith('') && !trimmedCurrent.includes(' 0 && !trimmedNext.startsWith('')) { // Check if previous line exists and is not already blank if (i > 0) { const prevLine = lines[i - 1]; const trimmedPrev = prevLine.trim(); // Only add blank line if: // 1. Previous line is not already blank // 2. Previous line is not the opening Define tag if (trimmedPrev.length > 0 && !trimmedPrev.startsWith(' 0) { resultLines.push(''); } } resultLines.push(currentLine); } else { resultLines.push(currentLine); } } return resultLines.join('\n'); } countOccurrences(str, search) { let count = 0; let pos = 0; while ((pos = str.indexOf(search, pos)) !== -1) { count++; pos += search.length; } return count; } } exports.JqhtmlFormattingEditProvider = JqhtmlFormattingEditProvider; //# sourceMappingURL=formatter.js.map