"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 // Patterns that increase indent on the NEXT line this.indentIncrease = [': %>', ': ?>', '{ %>', '{ ?>']; // Patterns that decrease indent this.indentDecrease = [ '<% end; %>', '<% endif; %>', '<% endfor; %>', '<% endforeach; %>', '<% endfunction; %>', '<% } %>', '<% }); %>', '<% } else', '<% else' ]; // 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 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 2: 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 3: Split into lines with indent levels const lines = []; const splitLines = working.split('\n'); for (const line of splitLines) { lines.push([0, line.trim()]); } // Step 4: Handle JS/control flow indents (if:, endif;, etc) let jsIndent = 0; for (let i = 0; i < lines.length; i++) { const trimmedLine = lines[i][1]; let plus = 0; let minus = 0; // Check for indent increase patterns for (const pattern of this.indentIncrease) { if (trimmedLine.indexOf(pattern) !== -1) { plus++; } } // Check for indent decrease patterns for (const pattern of this.indentDecrease) { if (trimmedLine.indexOf(pattern) !== -1) { minus++; } } // Apply indent changes if (plus > minus) { lines[i][0] = jsIndent; jsIndent += plus; jsIndent -= minus; } else { jsIndent += plus; jsIndent -= minus; lines[i][0] = jsIndent; } // Special handling for else statements if (trimmedLine.startsWith('<% else') || trimmedLine.startsWith('<% } else')) { lines[i][0]--; } } // Step 5: 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 6: Handle HTML tag indents let htmlIndent = 0; for (let i = 0; i < lines.length; i++) { const line = lines[i][1]; let thisIndent = 0; // 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 7: 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 8: 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 9: 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