Comparing version 0.2.0 to 0.3.0
@@ -1,531 +0,717 @@ | ||
var indent = (function() { | ||
var rulesCache = {}; | ||
var indent = (function (root) { | ||
var rulesCache = {}; | ||
function filterRules(language) { | ||
if (rulesCache[language]) | ||
return rulesCache[language]; | ||
var ret = []; | ||
rulesCache[language] = ret; | ||
for (var i=0; i<masterRules.length; i++) { | ||
if (masterRules[i].langs.indexOf(language.toLowerCase()) != -1) | ||
ret.push(masterRules[i]); | ||
function filterRules(language, rules, excludes) { | ||
if (rulesCache[language]) | ||
return rulesCache[language]; | ||
var ret = []; | ||
rulesCache[language] = ret; | ||
excludes = excludes || ''; | ||
for (var i = 0; i < rules.length; i++) { | ||
if (rules[i].$languages.indexOf(language.toLowerCase()) !== -1 && | ||
excludes.indexOf(rules[i].$name) === -1) | ||
ret.push(rules[i]); | ||
} | ||
return ret; | ||
} | ||
// String.prototype.trim polyfill for IE9 | ||
if (!String.prototype.trim) { | ||
String.prototype.trim = function () { | ||
return this.replace(/^[\s\uFEFF\xA0]+|[\s\uFEFF\xA0]+$/g, ''); | ||
}; | ||
} | ||
var NEW_LINE_REGEX = /\r*\n/; | ||
/** | ||
* Soft dedent: this type of dedent has the opposite effect and will actually indent every line | ||
* starting from the opening line. | ||
*/ | ||
/** | ||
* $indent - whether rule will cause indent | ||
* $ignoreRules - ignore further rule matching as long as this is last active rule, e.g. string, comments | ||
* $consumeEndMatch - advance the cursor to the end of the end pattern matches | ||
* $endPatternIndent - keep the indent rule active for the $endPatterns | ||
* $endPatterns - list of regex to terminate the rule | ||
* $startPatterns - list of regex to start the rule | ||
* $matchBeginning - match at beginning of line only | ||
* $languages - used to filter by language later | ||
* $lineOffset - added to the line field when rule is applied | ||
* $lastRule - used to continue a previous rule | ||
* $newScope - used to determine if rule creates a new scope, used for lastRule | ||
* | ||
* Always keep NEW_LINE_REGEX $endPatterns as last element, | ||
* as otherwise it will be matched first, and subsequent ones may be ignored | ||
* and skipped permanently by other rules. | ||
*/ | ||
var MASTER_RULES = [ | ||
{ | ||
$languages: "html", | ||
$name: "comment", | ||
$startPatterns: [/\<\!\-\-/], | ||
$endPatterns: [/\-\-\>/], | ||
$ignoreRules: true, | ||
$consumeEndMatch: true | ||
}, | ||
{ | ||
$languages: "html", | ||
$name: "doctype", | ||
$startPatterns: [/\<\!doctype html>/i], | ||
$endPatterns: [NEW_LINE_REGEX], | ||
$ignoreRules: true, | ||
$consumeEndMatch: true | ||
}, | ||
{ | ||
$languages: "html", | ||
$name: "void-tags", | ||
$startPatterns: [ | ||
/\<(area|base|br|col|command|embed|hr|img|input|keygen|link|menuitem|meta|param|source|track|wbr)/i], | ||
$endPatterns: [/>/], | ||
$indent: true, | ||
$consumeEndMatch: true | ||
}, | ||
{ | ||
$languages: "html", | ||
$name: "mode switch js", | ||
$startPatterns: [function (string) { | ||
var start = /<script[\s>].*/i; | ||
var end = /<\/script>/i; | ||
var startMatch = start.exec(string); | ||
var endMatch = end.exec(string); | ||
if (startMatch && (!endMatch || endMatch.index < startMatch.index)) { | ||
return { | ||
matchIndex: startMatch.index, | ||
length: startMatch[0].length | ||
}; | ||
} | ||
return ret; | ||
return null; | ||
}], | ||
$endPatterns: [/<\/script>/i], | ||
$switchRules: "js", | ||
$consumeEndMatch: true, | ||
$indent: true, | ||
$newScope: true | ||
}, | ||
{ | ||
$languages: "html", | ||
$name: "mode switch css", | ||
$startPatterns: [function (string) { | ||
var start = /<style[\s>].*/i; | ||
var end = /<\/style>/i; | ||
var startMatch = start.exec(string); | ||
var endMatch = end.exec(string); | ||
if (startMatch && (!endMatch || endMatch.index < startMatch.index)) { | ||
return { | ||
matchIndex: startMatch.index, | ||
length: startMatch[0].length | ||
}; | ||
} | ||
return null; | ||
}], | ||
$endPatterns: [/<\/style>/i], | ||
$switchRules: "css", | ||
$consumeEndMatch: true, | ||
$indent: true, | ||
$newScope: true | ||
}, | ||
{ | ||
$languages: "html", | ||
$name: "html-tag", | ||
$startPatterns: [/<html[^A-Za-z0-9]/i], | ||
$endPatterns: [/<\/html>/i], | ||
$consumeEndMatch: true | ||
}, | ||
{ | ||
$languages: "html", | ||
$name: "tag", | ||
$startPatterns: [function (string, rule, state) { | ||
var re = /<([A-Za-z0-9\-]+)/; | ||
var match = string.match(re); | ||
if (match) { | ||
state.openingTag = match[1]; | ||
return { | ||
matchIndex: match.index, | ||
length: match[0].length | ||
} | ||
} else { | ||
return null; | ||
} | ||
}], | ||
$endPatterns: [function (string, rule, state) { | ||
var re = new RegExp("</" + state.openingTag + ">", "i"); | ||
var match = string.match(re); | ||
if (match) { | ||
return { | ||
matchIndex: match.index, | ||
length: match[0].length | ||
} | ||
} else { | ||
return null; | ||
} | ||
}], | ||
$indent: true, | ||
$consumeEndMatch: true | ||
}, | ||
{ | ||
$languages: "js", | ||
$name: "line-comment", | ||
$startPatterns: [/\/\//], | ||
$endPatterns: [NEW_LINE_REGEX], | ||
$ignoreRules: true | ||
}, | ||
{ | ||
$languages: "js css", | ||
$name: "block-comment", | ||
$startPatterns: [/\/\*/], | ||
$endPatterns: [/\*\//], | ||
$ignoreRules: true | ||
}, | ||
{ | ||
$languages: "js", | ||
$name: "regex", | ||
$startPatterns: [function (string, rule) { | ||
var re = /[(,=:[!&|?{};][\s]*\/[^/]|^[\s]*\/[^/]/; | ||
var startIndex = string.search(re); | ||
if (startIndex != -1) { | ||
startIndex = string.indexOf('/', startIndex); | ||
var substr = string.substring(startIndex + 1); | ||
var match = searchAny(substr, rule.$endPatterns, rule); | ||
if (match.matchIndex != -1) { | ||
substr = substr.substring(0, match.matchIndex); | ||
try { | ||
(new RegExp(substr)); | ||
return { | ||
matchIndex: startIndex, | ||
length: 1 | ||
}; | ||
} | ||
catch (e) { | ||
return null; | ||
} | ||
} | ||
} | ||
return null; | ||
}], | ||
$endPatterns: [function (string) { | ||
var fromIndex = 0; | ||
var index = string.indexOf('/'); | ||
while (index != -1) { | ||
try { | ||
(new RegExp(string.substring(0, index))); | ||
break; | ||
} | ||
catch (e) { | ||
index = string.indexOf('/', fromIndex); | ||
fromIndex = index + 1; | ||
} | ||
} | ||
return index === -1 ? null : { | ||
matchIndex: index, | ||
length: 1 | ||
}; | ||
}], | ||
$ignoreRules: true, | ||
$consumeEndMatch: true | ||
}, | ||
{ | ||
$languages: "html", | ||
$name: "quotes", | ||
$startPatterns: [/"/], | ||
$endPatterns: [/"/, NEW_LINE_REGEX], | ||
$ignoreRules: true, | ||
$consumeEndMatch: true | ||
}, | ||
{ | ||
$languages: "html", | ||
$name: "quotes", | ||
$startPatterns: [/'/], | ||
$endPatterns: [/'/, NEW_LINE_REGEX], | ||
$ignoreRules: true, | ||
$consumeEndMatch: true | ||
}, | ||
{ | ||
$languages: "js css", | ||
$name: "string", | ||
$startPatterns: [/(''|""|``)/], | ||
$endPatterns: [/./, NEW_LINE_REGEX] | ||
}, | ||
{ | ||
$languages: "js css", | ||
$name: "string", | ||
$startPatterns: [/\"(?=[^"])/], | ||
$endPatterns: [/[^\\]\"/, NEW_LINE_REGEX], | ||
$ignoreRules: true, | ||
$consumeEndMatch: true | ||
}, | ||
{ | ||
$languages: "js css", | ||
$name: "string", | ||
$startPatterns: [/\'(?=[^'])/], | ||
$endPatterns: [/[^\\]\'/, NEW_LINE_REGEX], | ||
$ignoreRules: true, | ||
$consumeEndMatch: true | ||
}, | ||
{ | ||
$languages: "js css", | ||
$name: "string", | ||
$startPatterns: [/\`(?=[^`])/], | ||
$endPatterns: [/[^\\]\`/], | ||
$ignoreRules: true, | ||
$consumeEndMatch: true | ||
}, | ||
{ | ||
$languages: "js", | ||
$name: "if", | ||
$startPatterns: [/^if\s*(?=\()/, /[\s]+if\s*(?=\()/], | ||
$endPatterns: [/else[\s]+/, nonWhitespaceFollowByNewline, /[{;]/], | ||
$indent: true | ||
}, | ||
{ | ||
$languages: "js", | ||
$name: "for|while", | ||
$startPatterns: [/^(for|while)\s*(?=\()/], | ||
$endPatterns: [nonWhitespaceFollowByNewline, /[{;]/], | ||
$indent: true | ||
}, | ||
{ | ||
$languages: "js", | ||
$name: "else", | ||
$startPatterns: [/else[\s]+/], | ||
$endPatterns: [/if[^\w$]/, nonWhitespaceFollowByNewline, /[{;]/], | ||
$indent: true | ||
}, | ||
{ | ||
$languages: "js css", | ||
$name: "bracket", | ||
$startPatterns: [/\(\s*(var|let|const)?\s*/], | ||
$endPatterns: [/\)/], | ||
$indent: true, | ||
$consumeEndMatch: true, | ||
$newScope: true | ||
}, | ||
{ | ||
$languages: "js", | ||
$name: "dot-chain", | ||
$startPatterns: [/^\.[A-Za-z$_]/], | ||
$endPatterns: [/[\.;]/, NEW_LINE_REGEX], | ||
$indent: true, | ||
$matchBeginning: true, | ||
$lineOffset: -1 | ||
}, | ||
{ | ||
$languages: "js", | ||
$name: "dot-chain", | ||
$startPatterns: [/\.\s*\r*\n/], | ||
$endPatterns: [/[\.;})\]]/, /[^\s]\s*\r*\n/], | ||
$indent: true | ||
}, | ||
{ | ||
$languages: "js css", | ||
$name: "array", | ||
$startPatterns: [/\[/], | ||
$endPatterns: [/]/], | ||
$indent: true, | ||
$consumeEndMatch: true, | ||
$newScope: true | ||
}, | ||
{ | ||
$languages: "js css", | ||
$name: "block", | ||
$startPatterns: [/\{/], | ||
$endPatterns: [/}/], | ||
$indent: true, | ||
$consumeEndMatch: true, | ||
$newScope: true | ||
}, | ||
{ | ||
$languages: "js", | ||
$name: "var/let/const", | ||
$startPatterns: [/(var|let|const)[\s]*\r*\n/], | ||
$endPatterns: [nonWhitespaceFollowByNewline], | ||
$indent: true, | ||
$endPatternIndent: true | ||
}, | ||
{ | ||
$languages: "js", | ||
$name: "var/let/const", | ||
$startPatterns: [/(var|let|const)\s+(?=[\w$])/], | ||
$endPatterns: [/[,;=]/, nonWhitespaceFollowByNewline], | ||
$indent: true | ||
}, | ||
{ | ||
$languages: "js", | ||
$name: "var/let/const", | ||
$lastRule: ["var/let/const", "="], | ||
$startPatterns: [/,[\s]*\r*\n/], | ||
$endPatterns: [/[,;]/, nonWhitespaceFollowByNewline], | ||
$indent: true, | ||
callback: postIndentForCommaAfterEqual | ||
}, | ||
{ | ||
$languages: "js", | ||
$name: "var/let/const", | ||
$lastRule: ["var/let/const", "="], | ||
$startPatterns: [/^,/], | ||
$endPatterns: [/[,;]/, nonWhitespaceFollowByNewline], | ||
$matchBeginning: true, | ||
$indent: true, | ||
$lineOffset: -1, | ||
callback: postIndentForCommaAfterEqual | ||
}, | ||
{ | ||
$languages: "js", | ||
$name: "equality", | ||
$startPatterns: [/[=<>!]=(=)?/], | ||
$endPatterns: [/./] | ||
}, | ||
{ | ||
$languages: "js", | ||
$name: "=", | ||
$startPatterns: [/=/], | ||
$endPatterns: [/[,;\)\]}]/, NEW_LINE_REGEX] | ||
}, | ||
{ | ||
$languages: "js", | ||
$name: "?:", | ||
$startPatterns: [/\?/], | ||
$endPatterns: [/[:;]/], | ||
$endPatternIndent: true, | ||
$indent: true | ||
}, | ||
{ | ||
$languages: "js", | ||
$name: "case", | ||
$startPatterns: [/^(case|default)[\s:]/], | ||
$endPatterns: [/break[\s;\r\n]/, /^return[\s;\r\n]/, /^case[\s]+/, /^default[\s:]/, /}/], | ||
$endPatternIndent: function (matchEnd) { | ||
return matchEnd.endPatternIndex <= 1; | ||
}, | ||
$indent: true, | ||
$newScope: true | ||
}, | ||
{ | ||
$languages: "js", | ||
$name: "semicolon", | ||
$startPatterns: [/;/], | ||
$endPatterns: [/./] | ||
} | ||
]; | ||
var NEW_LINE_REGEX = /\r*\n/; | ||
return { | ||
css: function (code, options) { | ||
return indent(code, filterRules('css', MASTER_RULES), options); | ||
}, | ||
js: function (code, options) { | ||
return indent(code, filterRules('js', MASTER_RULES), options); | ||
}, | ||
ts: function (code, options) { | ||
return indent(code, filterRules('js', MASTER_RULES), options); | ||
}, | ||
html: function (code, options) { | ||
var rules = options.indentHtmlTag ? | ||
filterRules('html', MASTER_RULES, 'html-tag') : filterRules('html', MASTER_RULES); | ||
return indent(code, rules, options); | ||
} | ||
}; | ||
function indent(code, baseRules, options) { | ||
code = code || ''; | ||
/** | ||
* indent - whether rule will cause indent | ||
* ignore - ignore further rule matching as long as this is last active rule, e.g. string, comments | ||
* advance - advance the cursor to the end of the endTokens | ||
* endTokenIndent - keep the indent rule active for the endTokens | ||
* head - match at beginning of line only | ||
* langs - used to filter by language later | ||
* lineOffset - added to the line field when rule is applied | ||
* countdown - terminate rule after this number of lines | ||
* Algorithm assumptions | ||
* | ||
* Always keep NEW_LINE_REGEX endToken as last element, | ||
* as otherwise it will be matched first, and subsequent ones may be ignored | ||
* and skipped permanently by other rules. | ||
* indentDeltas - store the the deltas in tabString | ||
* - can be manipulated directly to alter the tabString | ||
* indentBuffer - used to keep tabs on the number of open indentations on each line | ||
* dedentBuffer - each line in the buffer has an array storing open indent lines to be closed | ||
* - an array of numbers is used to reference the opening line | ||
* - a negative number is used to signify a soft dedent (see note about soft dedent) | ||
* | ||
* Each line can create at most 1 tabString. | ||
* When a line is 'used up' for dedent, it cannot be used again, hence the indentBuffer. | ||
*/ | ||
var masterRules = [ | ||
{ | ||
langs: "html", | ||
name: "comment", | ||
startToken: [/\<\!\-\-/], | ||
endToken: [/\-\-\>/], | ||
ignore: true, | ||
advance: true | ||
}, | ||
{ | ||
langs: "html", | ||
name: "doctype", | ||
startToken: [/\<\!/], | ||
endToken: [NEW_LINE_REGEX], | ||
ignore: true, | ||
advance: true | ||
}, | ||
{ | ||
langs: "html", | ||
name: "link|br|hr|input|img|meta", | ||
startToken: [/\<(link|br|hr|input|img|meta)/i], | ||
endToken: [/>/], | ||
advance: true | ||
}, | ||
{ | ||
langs: "html", | ||
name: "mode switch js", | ||
startToken: [function (string, rule) { | ||
var start = /<script[\s>].*/i; | ||
var end = /<\/script>/i; | ||
var startMatch = start.exec(string); | ||
var endMatch = end.exec(string); | ||
var tabString = options.tabString === undefined ? '\t' : options.tabString; | ||
var lines = code.split(/[\r]?\n/gi); | ||
var lineCount = lines.length; | ||
var ignoreBuffer = intArray(lineCount); | ||
var indentBuffer = intArray(lineCount); | ||
var dedentBuffer = arrayOfArrays(lineCount); | ||
var activeMatches = []; | ||
var lastMatches= [null]; | ||
var l = 0; | ||
var pos = 0; | ||
var matchEnd, matchStart; | ||
var modeRules = null; | ||
var line, lineToMatch, activeMatch; | ||
if (startMatch && (!endMatch || endMatch.index < startMatch.index)) { | ||
return { | ||
matchIndex: startMatch.index, | ||
length: startMatch[0].length | ||
}; | ||
} | ||
return {matchIndex: -1}; | ||
}], | ||
endToken: [/<\/script>/i], | ||
rules: "js", | ||
advance: true, | ||
indent: true | ||
}, | ||
{ | ||
langs: "html", | ||
name: "mode switch css", | ||
startToken: [function (string, rule) { | ||
var start = /<style[\s>].*/i; | ||
var end = /<\/style>/i; | ||
var startMatch = start.exec(string); | ||
var endMatch = end.exec(string); | ||
options.debug = { | ||
buffers: { | ||
ignore: ignoreBuffer, | ||
indent: indentBuffer, | ||
dedent: dedentBuffer, | ||
active: activeMatches | ||
} | ||
}; | ||
if (startMatch && (!endMatch || endMatch.index < startMatch.index)) { | ||
return { | ||
matchIndex: startMatch.index, | ||
length: startMatch[0].length | ||
}; | ||
} | ||
return {matchIndex: -1}; | ||
}], | ||
endToken: [/<\/style>/i], | ||
rules: "css", | ||
advance: true, | ||
indent: true | ||
}, | ||
{ | ||
langs: "html", | ||
name: "close-tag", | ||
startToken: [/<\/[A-Za-z0-9\-]+>/], | ||
endToken: [/./], | ||
indent: true | ||
}, | ||
{ | ||
langs: "html", | ||
name: "tag attr", | ||
startToken: [/<[A-Za-z0-9\-]+/], | ||
endToken: [/>/], | ||
indent: true | ||
}, | ||
{ | ||
langs: "html", | ||
name: "tag", | ||
startToken: [/>/], | ||
endToken: [/<\/[A-Za-z0-9\-]+>/], | ||
indent: true, | ||
advance: true | ||
}, | ||
{ | ||
langs: "js", | ||
name: "line comment", | ||
startToken: [/\/\//], | ||
endToken: [NEW_LINE_REGEX], | ||
ignore: true | ||
}, | ||
{ | ||
langs: "js css", | ||
name: "block comment", | ||
startToken: [/\/\*/], | ||
endToken: [/\*\//], | ||
ignore: true | ||
}, | ||
{ | ||
langs: "js", | ||
name: "regex", | ||
startToken: [function (string, rule) { | ||
var re = /[(,=:[!&|?{};][\s]*\/[^/]|^[\s]*\/[^/]/; | ||
var startIndex = string.search(re); | ||
if (startIndex != -1) { | ||
startIndex = string.indexOf('/', startIndex); | ||
var substr = string.substring(startIndex + 1); | ||
var match = searchAny(substr, rule.endToken, rule); | ||
if (match.matchIndex != -1) { | ||
substr = substr.substring(0, match.matchIndex); | ||
try { | ||
(new RegExp(substr)); | ||
return { | ||
matchIndex: startIndex, | ||
length: 1 | ||
}; | ||
} | ||
catch (e) { | ||
return {matchIndex: -1}; | ||
} | ||
} | ||
} | ||
return {matchIndex: -1}; | ||
}], | ||
endToken: [function (string, rule) { | ||
var fromIndex = 0; | ||
var index = string.indexOf('/'); | ||
while (index != -1) { | ||
try { | ||
(new RegExp(string.substring(0, index))); | ||
break; | ||
} | ||
catch (e) { | ||
index = string.indexOf('/', fromIndex); | ||
fromIndex = index + 1; | ||
} | ||
} | ||
return { | ||
matchIndex: index, | ||
length: index == -1 ? 0 : 1 | ||
}; | ||
}], | ||
ignore: true, | ||
advance: true | ||
}, | ||
{ | ||
langs: "js css html", | ||
name: "string", | ||
startToken: [/\"/], | ||
endToken: [/\"/, NEW_LINE_REGEX], | ||
ignore: true, | ||
advance: true | ||
}, | ||
{ | ||
langs: "js css html", | ||
name: "string", | ||
startToken: [/\'/], | ||
endToken: [/\'/, NEW_LINE_REGEX], | ||
ignore: true, | ||
advance: true | ||
}, | ||
{ | ||
langs: "js css html", | ||
name: "string", | ||
startToken: [/\`/], | ||
endToken: [/\`/], | ||
ignore: true, | ||
advance: true | ||
}, | ||
{ | ||
langs: "js", | ||
name: "if", | ||
startToken: [/^if[\s]*(?=\()/, /[\s]+if[\s]*(?=\()/], | ||
endToken: [/else[\s]+/, /\{/, /;/], | ||
countdown: 2, | ||
indent: true | ||
}, | ||
{ | ||
langs: "js", | ||
name: "for", | ||
startToken: [/^for[\s]*(?=\()/], | ||
endToken: [/\{/, /;/], | ||
countdown: 2, | ||
indent: true | ||
}, | ||
{ | ||
langs: "js", | ||
name: "else", | ||
startToken: [/else[\s]+/], | ||
endToken: [/if/, /\{/, /;/], | ||
countdown: 2, | ||
indent: true | ||
}, | ||
{ | ||
langs: "js css", | ||
name: "bracket", | ||
startToken: [/\([\s]*(var)?/], | ||
endToken: [/\)/], | ||
indent: true, | ||
advance: true | ||
}, | ||
{ | ||
langs: "js", | ||
name: "dot chain", | ||
startToken: [/^\../], | ||
endToken: [/;/, NEW_LINE_REGEX], | ||
indent: true, | ||
head: true, | ||
lineOffset: -1 | ||
}, | ||
{ | ||
langs: "js", | ||
name: "dot chain", | ||
startToken: [/\.\s*$/], | ||
endToken: [function (string, rule) { | ||
return { | ||
matchIndex: string.length ? 1 : -1, | ||
length: string.length ? 0 : 1 | ||
}; | ||
}], | ||
indent: true | ||
}, | ||
{ | ||
langs: "js css", | ||
name: "array", | ||
startToken: [/\[/], | ||
endToken: [/]/], | ||
indent: true, | ||
advance: true | ||
}, | ||
{ | ||
langs: "js css", | ||
name: "block", | ||
startToken: [/\{/], | ||
endToken: [/}/], | ||
indent: true, | ||
advance: true | ||
}, | ||
{ | ||
langs: "js", | ||
name: "var", | ||
startToken: [/var[\s]+/], | ||
endToken: [/;/], | ||
indent: true | ||
}, | ||
{ | ||
langs: "js", | ||
name: "case", | ||
startToken: [/^case[\s]+/], | ||
endToken: [/break[\s;]+/, /^case[\s]+/, /^default[\s]+/, /^return([\s]+|;)/, /}/], | ||
endTokenIndent: true, | ||
indent: true | ||
}, | ||
{ | ||
langs: "js", | ||
name: "default", | ||
startToken: [/^default[\s]*:/], | ||
endToken: [/break[\s;]+/, /^case[\s]+/, /^default[\s]+/, /^return([\s]+|;)/, /}/], | ||
endTokenIndent: true, | ||
indent: true | ||
} | ||
]; | ||
while (l < lineCount) { | ||
line = lines[l].trim(); | ||
lineToMatch = cleanEscapedChars(line) + '\r\n'; | ||
activeMatch = activeMatches[activeMatches.length-1]; | ||
var exports = { | ||
css: function (code, indentString) { | ||
return indent(code || '', filterRules('css'), indentString || '\t'); | ||
}, | ||
js: function (code, indentString) { | ||
return indent(code || '', filterRules('js'), indentString || '\t'); | ||
}, | ||
html: function (code, indentString) { | ||
return indent(code || '', filterRules('html'), indentString || '\t'); | ||
}, | ||
/** | ||
* @deprecated Since version 0.2.0. Will be deleted in version 0.3.0. Use js instead. | ||
*/ | ||
indentJS: function(code, indentString) { | ||
if (console && console.warn) console.warn("Calling deprecated function!"); | ||
return exports.js(code, indentString); | ||
}, | ||
/** | ||
* @deprecated Since version 0.2.0. Will be deleted in version 0.3.0. Use css instead. | ||
*/ | ||
indentCSS: function(code, indentString) { | ||
if (console && console.warn) console.warn("Calling deprecated function!"); | ||
return exports.css(code, indentString); | ||
}, | ||
/** | ||
* @deprecated Since version 0.2.0. Will be deleted in version 0.3.0. Use html instead. | ||
*/ | ||
indentHTML: function(code, indentString) { | ||
if (console && console.warn) console.warn("Calling deprecated function!"); | ||
return exports.html(code, indentString); | ||
matchStart = matchStartRule(lineToMatch, modeRules || baseRules, pos); | ||
if (activeMatches.length) { | ||
matchEnd = matchEndRule(lineToMatch, activeMatch, pos, matchStart); | ||
if (matchEnd.matchIndex === -1) { | ||
if (activeMatch.rule.$ignoreRules) { | ||
// last rule is still active, and it's telling us to ignore. | ||
ignoreBuffer[l] = 1; | ||
l++; pos = 0; | ||
continue; | ||
} | ||
} | ||
}; | ||
else if ( | ||
activeMatch.rule.$ignoreRules || | ||
matchStart.matchIndex === -1 || | ||
matchEnd.matchIndex <= matchStart.matchIndex) { | ||
removeCurrentRule(); | ||
pos = matchEnd.cursor; | ||
continue; // Repeat process for matching line start/end | ||
} | ||
} | ||
return exports; | ||
if (matchStart.matchIndex !== -1) { | ||
implementRule(matchStart); | ||
} | ||
else { | ||
// No new token match end, no new match start | ||
l++; pos = 0; | ||
} | ||
} | ||
var | ||
hardIndentCount, | ||
dedentLines, dedentLine, dedents, | ||
i, j, indents = 0, | ||
hardIndents = copyIntArray(indentBuffer), | ||
indentDeltas = intArray(lineCount), | ||
newLines = []; | ||
function indent(code, baseRules, indentation) { | ||
var lines = code.split(/[\r]?\n/gi); | ||
var lineCount = lines.length; | ||
var newLines = []; | ||
var indentBuffer = []; | ||
var activeRules = []; | ||
var activeCountdowns = []; | ||
var lastRule; | ||
var lastCountdown; | ||
var indents = 0; | ||
var l = 0; | ||
var pos = 0; | ||
var matchEnd, matchStart; | ||
var modeRules = null; | ||
for (i=0; i<lineCount; i++) { | ||
dedentLines = dedentBuffer[i]; | ||
dedents = 0; | ||
for (j=0; j<dedentLines.length; j++) { | ||
dedentLine = dedentLines[j]; | ||
if (dedentLine < 0) { | ||
if (-dedentLine !== i) { | ||
indentDeltas[-dedentLine]++; | ||
dedents += 1; | ||
} | ||
} | ||
else if (hardIndents[dedentLine] > 0) { | ||
hardIndents[dedentLine]--; | ||
dedents += dedentLine !== i; | ||
} | ||
} | ||
hardIndentCount = hardIndents[i]; | ||
indentDeltas[i] = hardIndentCount > dedents ? 1 : | ||
(hardIndentCount < dedents ? hardIndentCount - dedents : 0); | ||
hardIndents[i] = hardIndentCount > 0 ? 1 : 0; | ||
} | ||
while (l < lineCount) { | ||
var line = lines[l].trim(); | ||
var lineToMatch = cleanEscapedChars(line) + '\r\n'; | ||
for (i=0; i<lineCount; i++) { | ||
if (ignoreBuffer[i] === 0) { | ||
indents += indentDeltas[i] || 0; | ||
newLines.push((indents > 0 ? repeatString(tabString, indents) : '') + lines[i].trim()); | ||
} else { | ||
newLines.push(lines[i]); | ||
} | ||
} | ||
matchStart = matchStartRule(lineToMatch, modeRules || baseRules, pos); | ||
return newLines.join('\r\n'); | ||
if (activeRules.length) { | ||
matchEnd = matchEndRule(lineToMatch, lastRule, pos); | ||
if (matchEnd.matchIndex == -1) { | ||
if (lastRule.ignore) { | ||
// last rule is still active, and it's telling us to ignore. | ||
incrementLine(); | ||
continue; | ||
} else if (lastCountdown) { | ||
lastCountdown--; | ||
if (lastCountdown === 0) { | ||
removeLastRule(); | ||
} | ||
} | ||
} | ||
else if ( | ||
lastRule.ignore || | ||
matchStart.matchIndex == -1 || | ||
matchEnd.matchIndex <= matchStart.matchIndex) { | ||
removeLastRule(); | ||
pos = matchEnd.cursor; | ||
continue; // Repeat process for matching line start/end | ||
} | ||
} | ||
if (matchStart.matchIndex != -1) { | ||
implementRule(matchStart); | ||
} | ||
else { | ||
// No new token match end, no new match start | ||
incrementLine(); | ||
} | ||
} | ||
function implementRule(match) { | ||
pos = match.cursor; | ||
return newLines.join('\r\n'); | ||
var rule = match.rule; | ||
var line = (l + 1) + (rule.$lineOffset || 0); | ||
match.line = line; | ||
activeMatches.push(match); | ||
function implementRule(match) { | ||
pos = match.cursor; | ||
lastRule = match.rule; | ||
lastCountdown = match.countdown; | ||
activeRules.push(lastRule); | ||
activeCountdowns.push(lastRule.countdown); | ||
if (lastRule.indent) { | ||
incrementIndentation(lastRule.lineOffset); | ||
} | ||
if (lastRule.rules) { | ||
modeRules = filterRules(lastRule.rules); | ||
} | ||
} | ||
if (rule.$indent) { | ||
indentBuffer[line]++; | ||
} | ||
if (rule.$switchRules) { | ||
modeRules = filterRules(rule.$switchRules, MASTER_RULES); | ||
} | ||
if (rule.$newScope) { | ||
lastMatches.push(null); | ||
} | ||
if (rule.callback) { | ||
rule.callback(match, indentBuffer, dedentBuffer); | ||
} | ||
} | ||
function removeLastRule() { | ||
if (lastRule.indent) { | ||
consumeIndentation(); | ||
if (!lastRule.endTokenIndent && matchEnd.matchIndex == 0) { | ||
calculateIndents(); | ||
} | ||
} | ||
if (lastRule.rules) { | ||
modeRules = null; | ||
} | ||
activeRules.pop(); | ||
activeCountdowns.pop(); | ||
lastRule = activeRules[activeRules.length - 1]; | ||
lastCountdown = activeCountdowns[activeCountdowns.length - 1]; | ||
} | ||
function removeCurrentRule() { | ||
var match = activeMatches.pop(), | ||
line = match.line, | ||
rule = match.rule; | ||
function calculateIndents() { | ||
indents = 0; | ||
for (var b, i = 0; i < indentBuffer.length; i++) { | ||
b = indentBuffer[i]; | ||
if (b.open && b.line != l) | ||
indents++; | ||
} | ||
} | ||
if (rule.$indent) { | ||
var endPatternIndent = typeof rule.$endPatternIndent === 'function' ? | ||
rule.$endPatternIndent(matchEnd) : rule.$endPatternIndent; | ||
var offset = !endPatternIndent && matchEnd.matchIndex === 0 ? 0 : 1; | ||
if (dedentBuffer[l + offset]) dedentBuffer[l + offset].push(line); | ||
} | ||
if (rule.$switchRules) { | ||
modeRules = null; | ||
} | ||
if (rule.$newScope) { | ||
lastMatches.pop(); | ||
} | ||
lastMatches[lastMatches.length - 1] = match; | ||
} | ||
function incrementLine() { | ||
newLines[l] = repeatString(indentation, indents) + line; | ||
l++; | ||
pos = 0; | ||
calculateIndents(); | ||
} | ||
function matchStartRule(string, rules, index) { | ||
string = string.substring(index, string.length); | ||
var result = null; | ||
var minIndex = string.length; | ||
var minMatch; | ||
var match; | ||
function incrementIndentation(lineOffset) { | ||
var matched = indentBuffer[indentBuffer.length - 1]; | ||
if (matched && matched.line == l) { | ||
matched.indent++; | ||
} | ||
else { | ||
indentBuffer.push({ | ||
indent: 1, | ||
open: true, | ||
line: lineOffset ? l + lineOffset : l | ||
}); | ||
if (lineOffset < 0) calculateIndents(); | ||
} | ||
} | ||
var lastMatch = lastMatches[lastMatches.length - 1]; | ||
var lastRuleInScope = lastMatch ? lastMatch.rule.$name : ''; | ||
function consumeIndentation() { | ||
var lastElem = indentBuffer[indentBuffer.length - 1]; | ||
if (lastElem) { | ||
lastElem.open = l == lastElem.line; | ||
if (--lastElem.indent <= 0) { | ||
indentBuffer.pop(); | ||
} | ||
} | ||
for (var rule, r = 0; r < rules.length; r++) { | ||
rule = rules[r]; | ||
if (!rule.$lastRule || | ||
(lastRuleInScope && rule.$lastRule.indexOf(lastRuleInScope) !== -1) | ||
) { | ||
match = searchAny(string, rule.$startPatterns, rule); | ||
if (match.matchIndex != -1 && match.matchIndex < minIndex | ||
&& (!rule.$matchBeginning || index === 0)) { | ||
minIndex = match.matchIndex; | ||
minMatch = match; | ||
result = rule; | ||
} | ||
} | ||
} | ||
return { | ||
rule: result, | ||
relativeIndex: result ? minIndex : -1, | ||
matchIndex: result ? minIndex + index : -1, | ||
cursor: result ? index + minMatch.cursor : -1, | ||
state: minMatch ? minMatch.state : {}, | ||
lastMatch: lastMatch | ||
}; | ||
} | ||
function repeatString(baseString, repeat) { | ||
return (new Array(repeat + 1)).join(baseString); | ||
function matchEndRule(string, active, offset, matchStart) { | ||
string = string.substr(offset, string.length); | ||
var rule = active.rule; | ||
var match = searchAny(string, rule.$endPatterns, rule, active.state, matchStart); | ||
var cursor = rule.$consumeEndMatch ? match.cursor : match.matchIndex; | ||
return { | ||
endPatternIndex: match.endPatternIndex, | ||
matchIndex: match.matchIndex === -1 ? -1 : match.matchIndex + offset, | ||
cursor: cursor === -1 ? -1 : cursor + offset, | ||
state: match.state | ||
}; | ||
} | ||
} | ||
function cleanEscapedChars(string) { | ||
return string.replace(/\\(u[0-9A-Za-z]{4}|u\{[0-9A-Za-z]{1,6}]\}|x[0-9A-Za-z]{2}|.)/g, '0'); | ||
function arrayOfArrays(length) { | ||
var array = new Array(length); | ||
for (var i=0; i<length; i++) array[i] = []; | ||
return array; | ||
} | ||
function intArray(length) { | ||
if (root.Int16Array) { | ||
return new Int16Array(length); | ||
} else { | ||
var array = new Array(length); | ||
for (var i=0; i<length; i++) array[i] = 0; | ||
return array; | ||
} | ||
} | ||
function matchStartRule(string, rules, index) { | ||
string = string.substring(index, string.length); | ||
var result = null; | ||
var minIndex = string.length; | ||
var minMatch; | ||
var match; | ||
for (var rule, r = 0; r < rules.length; r++) { | ||
rule = rules[r]; | ||
match = searchAny(string, rule.startToken, rule); | ||
if (match.matchIndex != -1 && match.matchIndex < minIndex | ||
&& (!rule.head || index == 0)) { | ||
minIndex = match.matchIndex; | ||
minMatch = match; | ||
result = rule; | ||
} | ||
} | ||
return { | ||
rule: result, | ||
matchIndex: result ? minIndex + index : -1, | ||
cursor: result ? minIndex + index + minMatch.matchLength : -1 | ||
}; | ||
function copyIntArray(src) { | ||
var copy = intArray(src.length); | ||
for (var i=0; i<src.length; i++) { | ||
copy[i] = src[i]; | ||
} | ||
return copy; | ||
} | ||
function matchEndRule(string, rule, offset) { | ||
string = string.substr(offset, string.length); | ||
var match = searchAny(string, rule.endToken, rule); | ||
var cursor = rule.advance ? match.matchIndex + match.matchLength : match.matchIndex; | ||
function repeatString(baseString, repeat) { | ||
return (new Array(repeat + 1)).join(baseString); | ||
} | ||
function cleanEscapedChars(string) { | ||
return string.replace(/\\(u[0-9A-Za-z]{4}|u\{[0-9A-Za-z]{1,6}]\}|x[0-9A-Za-z]{2}|.)/g, '0'); | ||
} | ||
function postIndentForCommaAfterEqual(match, indentBuffer, dedentBuffer) { | ||
var lastMatch = match.lastMatch; | ||
if (lastMatch && lastMatch.rule.$name === "=") { | ||
dedentBuffer[match.line].push(-lastMatch.line); | ||
} | ||
} | ||
function nonWhitespaceFollowByNewline(string, rule, state, matchStart) { | ||
var index; | ||
if (!state.newline) { | ||
index = string.search(/[^\s\r\n\{\(\[]/); | ||
state.newline = index !== -1 && (index <= matchStart.relativeIndex || matchStart.relativeIndex === -1); | ||
} else { | ||
index = string.search(/[;,=]?\r*\n/); | ||
if (index !== -1) { | ||
return { | ||
matchIndex: match.matchIndex == -1 ? -1 : match.matchIndex + offset, | ||
cursor: cursor == -1 ? -1 : cursor + offset | ||
}; | ||
matchIndex: index, | ||
length: 1 | ||
} | ||
} | ||
} | ||
return null; | ||
} | ||
function searchAny(string, patterns, rule) { | ||
var index = -1; | ||
var length = 0; | ||
for (var pat, p = 0; p < patterns.length; p++) { | ||
pat = patterns[p]; | ||
if (typeof pat == 'function') { | ||
var match = pat(string, rule); | ||
index = match.matchIndex; | ||
length = match.length; | ||
} | ||
else { | ||
index = string.search(pat); | ||
if (index != -1) { | ||
length = string.match(pat)[0].length; | ||
break; | ||
} | ||
} | ||
function searchAny(string, patterns, rule, state, matchStart) { | ||
state = state || {}; | ||
var index = -1, | ||
length = 0, | ||
match; | ||
for (var pat, p = 0; p < patterns.length; p++) { | ||
pat = patterns[p]; | ||
if (typeof pat === 'function') { | ||
match = pat(string, rule, state, matchStart); | ||
if (match) { | ||
index = match.matchIndex; | ||
length = match.length; | ||
break; | ||
} | ||
return { | ||
matchIndex: index, | ||
matchLength: length, | ||
cursor: index + length, | ||
patternIndex: p | ||
}; | ||
} | ||
else { | ||
match = string.match(pat); | ||
if (match) { | ||
index = string.search(pat); | ||
length = match[0].length; | ||
break; | ||
} | ||
} | ||
} | ||
}()); | ||
return { | ||
endPatternIndex: p, | ||
matchIndex: index, | ||
cursor: index + length, | ||
state: state | ||
}; | ||
} | ||
}(this)); |
@@ -56,6 +56,10 @@ var sectionHeight = function() { | ||
var reformatCode = function() { | ||
var indentChar = $('#indentWithSelect').val(); | ||
var tabString = $('#indentWithSelect').val(); | ||
var indentMode = $('#indentMode').val(); | ||
indentChar = indentChar == '\\t' ? '\t' : indentChar; | ||
$('#codeTextArea').val(indent[indentMode]($('#codeTextArea').val(), indentChar)); | ||
tabString = tabString == '\\t' ? '\t' : tabString; | ||
editor.setValue(indent[indentMode](editor.getValue(), {tabString: tabString})); | ||
} | ||
var editor = ace.edit("codeTextArea"); | ||
editor.setTheme("ace/theme/github"); | ||
editor.getSession().setMode("ace/mode/jsx"); |
@@ -9,14 +9,18 @@ var gulp = require('gulp'); | ||
gulp.task('build-minify', function (cb) { | ||
return gulp.src(['src/**/*.js']) | ||
.pipe(concat('indent.min.js')) | ||
.pipe(umd({ | ||
exports: function(file) { | ||
return 'indent'; | ||
}, | ||
namespace: function(file) { | ||
return 'indent'; | ||
} | ||
})) | ||
.pipe(uglify()) | ||
.pipe(gulp.dest('./lib')) | ||
return gulp.src(['src/**/*.js']) | ||
.pipe(concat('indent.min.js')) | ||
.pipe(umd({ | ||
exports: function (file) { | ||
return 'indent'; | ||
}, | ||
namespace: function (file) { | ||
return 'indent'; | ||
} | ||
})) | ||
.pipe(uglify({ | ||
mangleProperties: { | ||
regex: /^\$/ | ||
} | ||
})) | ||
.pipe(gulp.dest('./lib')) | ||
}); | ||
@@ -30,13 +34,13 @@ | ||
gulp.task('build-debug', function (cb) { | ||
return gulp.src(['src/**/*.js']) | ||
.pipe(concat('indent.js')) | ||
.pipe(umd({ | ||
exports: function(file) { | ||
return 'indent'; | ||
}, | ||
namespace: function(file) { | ||
return 'indent'; | ||
} | ||
})) | ||
.pipe(gulp.dest('./lib')); | ||
return gulp.src(['src/**/*.js']) | ||
.pipe(concat('indent.js')) | ||
.pipe(umd({ | ||
exports: function (file) { | ||
return 'indent'; | ||
}, | ||
namespace: function (file) { | ||
return 'indent'; | ||
} | ||
})) | ||
.pipe(gulp.dest('./lib')); | ||
}); |
1168
lib/indent.js
@@ -10,535 +10,721 @@ ;(function(root, factory) { | ||
}(this, function() { | ||
var indent = (function() { | ||
var rulesCache = {}; | ||
var indent = (function (root) { | ||
var rulesCache = {}; | ||
function filterRules(language) { | ||
if (rulesCache[language]) | ||
return rulesCache[language]; | ||
var ret = []; | ||
rulesCache[language] = ret; | ||
for (var i=0; i<masterRules.length; i++) { | ||
if (masterRules[i].langs.indexOf(language.toLowerCase()) != -1) | ||
ret.push(masterRules[i]); | ||
function filterRules(language, rules, excludes) { | ||
if (rulesCache[language]) | ||
return rulesCache[language]; | ||
var ret = []; | ||
rulesCache[language] = ret; | ||
excludes = excludes || ''; | ||
for (var i = 0; i < rules.length; i++) { | ||
if (rules[i].$languages.indexOf(language.toLowerCase()) !== -1 && | ||
excludes.indexOf(rules[i].$name) === -1) | ||
ret.push(rules[i]); | ||
} | ||
return ret; | ||
} | ||
// String.prototype.trim polyfill for IE9 | ||
if (!String.prototype.trim) { | ||
String.prototype.trim = function () { | ||
return this.replace(/^[\s\uFEFF\xA0]+|[\s\uFEFF\xA0]+$/g, ''); | ||
}; | ||
} | ||
var NEW_LINE_REGEX = /\r*\n/; | ||
/** | ||
* Soft dedent: this type of dedent has the opposite effect and will actually indent every line | ||
* starting from the opening line. | ||
*/ | ||
/** | ||
* $indent - whether rule will cause indent | ||
* $ignoreRules - ignore further rule matching as long as this is last active rule, e.g. string, comments | ||
* $consumeEndMatch - advance the cursor to the end of the end pattern matches | ||
* $endPatternIndent - keep the indent rule active for the $endPatterns | ||
* $endPatterns - list of regex to terminate the rule | ||
* $startPatterns - list of regex to start the rule | ||
* $matchBeginning - match at beginning of line only | ||
* $languages - used to filter by language later | ||
* $lineOffset - added to the line field when rule is applied | ||
* $lastRule - used to continue a previous rule | ||
* $newScope - used to determine if rule creates a new scope, used for lastRule | ||
* | ||
* Always keep NEW_LINE_REGEX $endPatterns as last element, | ||
* as otherwise it will be matched first, and subsequent ones may be ignored | ||
* and skipped permanently by other rules. | ||
*/ | ||
var MASTER_RULES = [ | ||
{ | ||
$languages: "html", | ||
$name: "comment", | ||
$startPatterns: [/\<\!\-\-/], | ||
$endPatterns: [/\-\-\>/], | ||
$ignoreRules: true, | ||
$consumeEndMatch: true | ||
}, | ||
{ | ||
$languages: "html", | ||
$name: "doctype", | ||
$startPatterns: [/\<\!doctype html>/i], | ||
$endPatterns: [NEW_LINE_REGEX], | ||
$ignoreRules: true, | ||
$consumeEndMatch: true | ||
}, | ||
{ | ||
$languages: "html", | ||
$name: "void-tags", | ||
$startPatterns: [ | ||
/\<(area|base|br|col|command|embed|hr|img|input|keygen|link|menuitem|meta|param|source|track|wbr)/i], | ||
$endPatterns: [/>/], | ||
$indent: true, | ||
$consumeEndMatch: true | ||
}, | ||
{ | ||
$languages: "html", | ||
$name: "mode switch js", | ||
$startPatterns: [function (string) { | ||
var start = /<script[\s>].*/i; | ||
var end = /<\/script>/i; | ||
var startMatch = start.exec(string); | ||
var endMatch = end.exec(string); | ||
if (startMatch && (!endMatch || endMatch.index < startMatch.index)) { | ||
return { | ||
matchIndex: startMatch.index, | ||
length: startMatch[0].length | ||
}; | ||
} | ||
return ret; | ||
return null; | ||
}], | ||
$endPatterns: [/<\/script>/i], | ||
$switchRules: "js", | ||
$consumeEndMatch: true, | ||
$indent: true, | ||
$newScope: true | ||
}, | ||
{ | ||
$languages: "html", | ||
$name: "mode switch css", | ||
$startPatterns: [function (string) { | ||
var start = /<style[\s>].*/i; | ||
var end = /<\/style>/i; | ||
var startMatch = start.exec(string); | ||
var endMatch = end.exec(string); | ||
if (startMatch && (!endMatch || endMatch.index < startMatch.index)) { | ||
return { | ||
matchIndex: startMatch.index, | ||
length: startMatch[0].length | ||
}; | ||
} | ||
return null; | ||
}], | ||
$endPatterns: [/<\/style>/i], | ||
$switchRules: "css", | ||
$consumeEndMatch: true, | ||
$indent: true, | ||
$newScope: true | ||
}, | ||
{ | ||
$languages: "html", | ||
$name: "html-tag", | ||
$startPatterns: [/<html[^A-Za-z0-9]/i], | ||
$endPatterns: [/<\/html>/i], | ||
$consumeEndMatch: true | ||
}, | ||
{ | ||
$languages: "html", | ||
$name: "tag", | ||
$startPatterns: [function (string, rule, state) { | ||
var re = /<([A-Za-z0-9\-]+)/; | ||
var match = string.match(re); | ||
if (match) { | ||
state.openingTag = match[1]; | ||
return { | ||
matchIndex: match.index, | ||
length: match[0].length | ||
} | ||
} else { | ||
return null; | ||
} | ||
}], | ||
$endPatterns: [function (string, rule, state) { | ||
var re = new RegExp("</" + state.openingTag + ">", "i"); | ||
var match = string.match(re); | ||
if (match) { | ||
return { | ||
matchIndex: match.index, | ||
length: match[0].length | ||
} | ||
} else { | ||
return null; | ||
} | ||
}], | ||
$indent: true, | ||
$consumeEndMatch: true | ||
}, | ||
{ | ||
$languages: "js", | ||
$name: "line-comment", | ||
$startPatterns: [/\/\//], | ||
$endPatterns: [NEW_LINE_REGEX], | ||
$ignoreRules: true | ||
}, | ||
{ | ||
$languages: "js css", | ||
$name: "block-comment", | ||
$startPatterns: [/\/\*/], | ||
$endPatterns: [/\*\//], | ||
$ignoreRules: true | ||
}, | ||
{ | ||
$languages: "js", | ||
$name: "regex", | ||
$startPatterns: [function (string, rule) { | ||
var re = /[(,=:[!&|?{};][\s]*\/[^/]|^[\s]*\/[^/]/; | ||
var startIndex = string.search(re); | ||
if (startIndex != -1) { | ||
startIndex = string.indexOf('/', startIndex); | ||
var substr = string.substring(startIndex + 1); | ||
var match = searchAny(substr, rule.$endPatterns, rule); | ||
if (match.matchIndex != -1) { | ||
substr = substr.substring(0, match.matchIndex); | ||
try { | ||
(new RegExp(substr)); | ||
return { | ||
matchIndex: startIndex, | ||
length: 1 | ||
}; | ||
} | ||
catch (e) { | ||
return null; | ||
} | ||
} | ||
} | ||
return null; | ||
}], | ||
$endPatterns: [function (string) { | ||
var fromIndex = 0; | ||
var index = string.indexOf('/'); | ||
while (index != -1) { | ||
try { | ||
(new RegExp(string.substring(0, index))); | ||
break; | ||
} | ||
catch (e) { | ||
index = string.indexOf('/', fromIndex); | ||
fromIndex = index + 1; | ||
} | ||
} | ||
return index === -1 ? null : { | ||
matchIndex: index, | ||
length: 1 | ||
}; | ||
}], | ||
$ignoreRules: true, | ||
$consumeEndMatch: true | ||
}, | ||
{ | ||
$languages: "html", | ||
$name: "quotes", | ||
$startPatterns: [/"/], | ||
$endPatterns: [/"/, NEW_LINE_REGEX], | ||
$ignoreRules: true, | ||
$consumeEndMatch: true | ||
}, | ||
{ | ||
$languages: "html", | ||
$name: "quotes", | ||
$startPatterns: [/'/], | ||
$endPatterns: [/'/, NEW_LINE_REGEX], | ||
$ignoreRules: true, | ||
$consumeEndMatch: true | ||
}, | ||
{ | ||
$languages: "js css", | ||
$name: "string", | ||
$startPatterns: [/(''|""|``)/], | ||
$endPatterns: [/./, NEW_LINE_REGEX] | ||
}, | ||
{ | ||
$languages: "js css", | ||
$name: "string", | ||
$startPatterns: [/\"(?=[^"])/], | ||
$endPatterns: [/[^\\]\"/, NEW_LINE_REGEX], | ||
$ignoreRules: true, | ||
$consumeEndMatch: true | ||
}, | ||
{ | ||
$languages: "js css", | ||
$name: "string", | ||
$startPatterns: [/\'(?=[^'])/], | ||
$endPatterns: [/[^\\]\'/, NEW_LINE_REGEX], | ||
$ignoreRules: true, | ||
$consumeEndMatch: true | ||
}, | ||
{ | ||
$languages: "js css", | ||
$name: "string", | ||
$startPatterns: [/\`(?=[^`])/], | ||
$endPatterns: [/[^\\]\`/], | ||
$ignoreRules: true, | ||
$consumeEndMatch: true | ||
}, | ||
{ | ||
$languages: "js", | ||
$name: "if", | ||
$startPatterns: [/^if\s*(?=\()/, /[\s]+if\s*(?=\()/], | ||
$endPatterns: [/else[\s]+/, nonWhitespaceFollowByNewline, /[{;]/], | ||
$indent: true | ||
}, | ||
{ | ||
$languages: "js", | ||
$name: "for|while", | ||
$startPatterns: [/^(for|while)\s*(?=\()/], | ||
$endPatterns: [nonWhitespaceFollowByNewline, /[{;]/], | ||
$indent: true | ||
}, | ||
{ | ||
$languages: "js", | ||
$name: "else", | ||
$startPatterns: [/else[\s]+/], | ||
$endPatterns: [/if[^\w$]/, nonWhitespaceFollowByNewline, /[{;]/], | ||
$indent: true | ||
}, | ||
{ | ||
$languages: "js css", | ||
$name: "bracket", | ||
$startPatterns: [/\(\s*(var|let|const)?\s*/], | ||
$endPatterns: [/\)/], | ||
$indent: true, | ||
$consumeEndMatch: true, | ||
$newScope: true | ||
}, | ||
{ | ||
$languages: "js", | ||
$name: "dot-chain", | ||
$startPatterns: [/^\.[A-Za-z$_]/], | ||
$endPatterns: [/[\.;]/, NEW_LINE_REGEX], | ||
$indent: true, | ||
$matchBeginning: true, | ||
$lineOffset: -1 | ||
}, | ||
{ | ||
$languages: "js", | ||
$name: "dot-chain", | ||
$startPatterns: [/\.\s*\r*\n/], | ||
$endPatterns: [/[\.;})\]]/, /[^\s]\s*\r*\n/], | ||
$indent: true | ||
}, | ||
{ | ||
$languages: "js css", | ||
$name: "array", | ||
$startPatterns: [/\[/], | ||
$endPatterns: [/]/], | ||
$indent: true, | ||
$consumeEndMatch: true, | ||
$newScope: true | ||
}, | ||
{ | ||
$languages: "js css", | ||
$name: "block", | ||
$startPatterns: [/\{/], | ||
$endPatterns: [/}/], | ||
$indent: true, | ||
$consumeEndMatch: true, | ||
$newScope: true | ||
}, | ||
{ | ||
$languages: "js", | ||
$name: "var/let/const", | ||
$startPatterns: [/(var|let|const)[\s]*\r*\n/], | ||
$endPatterns: [nonWhitespaceFollowByNewline], | ||
$indent: true, | ||
$endPatternIndent: true | ||
}, | ||
{ | ||
$languages: "js", | ||
$name: "var/let/const", | ||
$startPatterns: [/(var|let|const)\s+(?=[\w$])/], | ||
$endPatterns: [/[,;=]/, nonWhitespaceFollowByNewline], | ||
$indent: true | ||
}, | ||
{ | ||
$languages: "js", | ||
$name: "var/let/const", | ||
$lastRule: ["var/let/const", "="], | ||
$startPatterns: [/,[\s]*\r*\n/], | ||
$endPatterns: [/[,;]/, nonWhitespaceFollowByNewline], | ||
$indent: true, | ||
callback: postIndentForCommaAfterEqual | ||
}, | ||
{ | ||
$languages: "js", | ||
$name: "var/let/const", | ||
$lastRule: ["var/let/const", "="], | ||
$startPatterns: [/^,/], | ||
$endPatterns: [/[,;]/, nonWhitespaceFollowByNewline], | ||
$matchBeginning: true, | ||
$indent: true, | ||
$lineOffset: -1, | ||
callback: postIndentForCommaAfterEqual | ||
}, | ||
{ | ||
$languages: "js", | ||
$name: "equality", | ||
$startPatterns: [/[=<>!]=(=)?/], | ||
$endPatterns: [/./] | ||
}, | ||
{ | ||
$languages: "js", | ||
$name: "=", | ||
$startPatterns: [/=/], | ||
$endPatterns: [/[,;\)\]}]/, NEW_LINE_REGEX] | ||
}, | ||
{ | ||
$languages: "js", | ||
$name: "?:", | ||
$startPatterns: [/\?/], | ||
$endPatterns: [/[:;]/], | ||
$endPatternIndent: true, | ||
$indent: true | ||
}, | ||
{ | ||
$languages: "js", | ||
$name: "case", | ||
$startPatterns: [/^(case|default)[\s:]/], | ||
$endPatterns: [/break[\s;\r\n]/, /^return[\s;\r\n]/, /^case[\s]+/, /^default[\s:]/, /}/], | ||
$endPatternIndent: function (matchEnd) { | ||
return matchEnd.endPatternIndex <= 1; | ||
}, | ||
$indent: true, | ||
$newScope: true | ||
}, | ||
{ | ||
$languages: "js", | ||
$name: "semicolon", | ||
$startPatterns: [/;/], | ||
$endPatterns: [/./] | ||
} | ||
]; | ||
var NEW_LINE_REGEX = /\r*\n/; | ||
return { | ||
css: function (code, options) { | ||
return indent(code, filterRules('css', MASTER_RULES), options); | ||
}, | ||
js: function (code, options) { | ||
return indent(code, filterRules('js', MASTER_RULES), options); | ||
}, | ||
ts: function (code, options) { | ||
return indent(code, filterRules('js', MASTER_RULES), options); | ||
}, | ||
html: function (code, options) { | ||
var rules = options.indentHtmlTag ? | ||
filterRules('html', MASTER_RULES, 'html-tag') : filterRules('html', MASTER_RULES); | ||
return indent(code, rules, options); | ||
} | ||
}; | ||
function indent(code, baseRules, options) { | ||
code = code || ''; | ||
/** | ||
* indent - whether rule will cause indent | ||
* ignore - ignore further rule matching as long as this is last active rule, e.g. string, comments | ||
* advance - advance the cursor to the end of the endTokens | ||
* endTokenIndent - keep the indent rule active for the endTokens | ||
* head - match at beginning of line only | ||
* langs - used to filter by language later | ||
* lineOffset - added to the line field when rule is applied | ||
* countdown - terminate rule after this number of lines | ||
* Algorithm assumptions | ||
* | ||
* Always keep NEW_LINE_REGEX endToken as last element, | ||
* as otherwise it will be matched first, and subsequent ones may be ignored | ||
* and skipped permanently by other rules. | ||
* indentDeltas - store the the deltas in tabString | ||
* - can be manipulated directly to alter the tabString | ||
* indentBuffer - used to keep tabs on the number of open indentations on each line | ||
* dedentBuffer - each line in the buffer has an array storing open indent lines to be closed | ||
* - an array of numbers is used to reference the opening line | ||
* - a negative number is used to signify a soft dedent (see note about soft dedent) | ||
* | ||
* Each line can create at most 1 tabString. | ||
* When a line is 'used up' for dedent, it cannot be used again, hence the indentBuffer. | ||
*/ | ||
var masterRules = [ | ||
{ | ||
langs: "html", | ||
name: "comment", | ||
startToken: [/\<\!\-\-/], | ||
endToken: [/\-\-\>/], | ||
ignore: true, | ||
advance: true | ||
}, | ||
{ | ||
langs: "html", | ||
name: "doctype", | ||
startToken: [/\<\!/], | ||
endToken: [NEW_LINE_REGEX], | ||
ignore: true, | ||
advance: true | ||
}, | ||
{ | ||
langs: "html", | ||
name: "link|br|hr|input|img|meta", | ||
startToken: [/\<(link|br|hr|input|img|meta)/i], | ||
endToken: [/>/], | ||
advance: true | ||
}, | ||
{ | ||
langs: "html", | ||
name: "mode switch js", | ||
startToken: [function (string, rule) { | ||
var start = /<script[\s>].*/i; | ||
var end = /<\/script>/i; | ||
var startMatch = start.exec(string); | ||
var endMatch = end.exec(string); | ||
var tabString = options.tabString === undefined ? '\t' : options.tabString; | ||
var lines = code.split(/[\r]?\n/gi); | ||
var lineCount = lines.length; | ||
var ignoreBuffer = intArray(lineCount); | ||
var indentBuffer = intArray(lineCount); | ||
var dedentBuffer = arrayOfArrays(lineCount); | ||
var activeMatches = []; | ||
var lastMatches= [null]; | ||
var l = 0; | ||
var pos = 0; | ||
var matchEnd, matchStart; | ||
var modeRules = null; | ||
var line, lineToMatch, activeMatch; | ||
if (startMatch && (!endMatch || endMatch.index < startMatch.index)) { | ||
return { | ||
matchIndex: startMatch.index, | ||
length: startMatch[0].length | ||
}; | ||
} | ||
return {matchIndex: -1}; | ||
}], | ||
endToken: [/<\/script>/i], | ||
rules: "js", | ||
advance: true, | ||
indent: true | ||
}, | ||
{ | ||
langs: "html", | ||
name: "mode switch css", | ||
startToken: [function (string, rule) { | ||
var start = /<style[\s>].*/i; | ||
var end = /<\/style>/i; | ||
var startMatch = start.exec(string); | ||
var endMatch = end.exec(string); | ||
options.debug = { | ||
buffers: { | ||
ignore: ignoreBuffer, | ||
indent: indentBuffer, | ||
dedent: dedentBuffer, | ||
active: activeMatches | ||
} | ||
}; | ||
if (startMatch && (!endMatch || endMatch.index < startMatch.index)) { | ||
return { | ||
matchIndex: startMatch.index, | ||
length: startMatch[0].length | ||
}; | ||
} | ||
return {matchIndex: -1}; | ||
}], | ||
endToken: [/<\/style>/i], | ||
rules: "css", | ||
advance: true, | ||
indent: true | ||
}, | ||
{ | ||
langs: "html", | ||
name: "close-tag", | ||
startToken: [/<\/[A-Za-z0-9\-]+>/], | ||
endToken: [/./], | ||
indent: true | ||
}, | ||
{ | ||
langs: "html", | ||
name: "tag attr", | ||
startToken: [/<[A-Za-z0-9\-]+/], | ||
endToken: [/>/], | ||
indent: true | ||
}, | ||
{ | ||
langs: "html", | ||
name: "tag", | ||
startToken: [/>/], | ||
endToken: [/<\/[A-Za-z0-9\-]+>/], | ||
indent: true, | ||
advance: true | ||
}, | ||
{ | ||
langs: "js", | ||
name: "line comment", | ||
startToken: [/\/\//], | ||
endToken: [NEW_LINE_REGEX], | ||
ignore: true | ||
}, | ||
{ | ||
langs: "js css", | ||
name: "block comment", | ||
startToken: [/\/\*/], | ||
endToken: [/\*\//], | ||
ignore: true | ||
}, | ||
{ | ||
langs: "js", | ||
name: "regex", | ||
startToken: [function (string, rule) { | ||
var re = /[(,=:[!&|?{};][\s]*\/[^/]|^[\s]*\/[^/]/; | ||
var startIndex = string.search(re); | ||
if (startIndex != -1) { | ||
startIndex = string.indexOf('/', startIndex); | ||
var substr = string.substring(startIndex + 1); | ||
var match = searchAny(substr, rule.endToken, rule); | ||
if (match.matchIndex != -1) { | ||
substr = substr.substring(0, match.matchIndex); | ||
try { | ||
(new RegExp(substr)); | ||
return { | ||
matchIndex: startIndex, | ||
length: 1 | ||
}; | ||
} | ||
catch (e) { | ||
return {matchIndex: -1}; | ||
} | ||
} | ||
} | ||
return {matchIndex: -1}; | ||
}], | ||
endToken: [function (string, rule) { | ||
var fromIndex = 0; | ||
var index = string.indexOf('/'); | ||
while (index != -1) { | ||
try { | ||
(new RegExp(string.substring(0, index))); | ||
break; | ||
} | ||
catch (e) { | ||
index = string.indexOf('/', fromIndex); | ||
fromIndex = index + 1; | ||
} | ||
} | ||
return { | ||
matchIndex: index, | ||
length: index == -1 ? 0 : 1 | ||
}; | ||
}], | ||
ignore: true, | ||
advance: true | ||
}, | ||
{ | ||
langs: "js css html", | ||
name: "string", | ||
startToken: [/\"/], | ||
endToken: [/\"/, NEW_LINE_REGEX], | ||
ignore: true, | ||
advance: true | ||
}, | ||
{ | ||
langs: "js css html", | ||
name: "string", | ||
startToken: [/\'/], | ||
endToken: [/\'/, NEW_LINE_REGEX], | ||
ignore: true, | ||
advance: true | ||
}, | ||
{ | ||
langs: "js css html", | ||
name: "string", | ||
startToken: [/\`/], | ||
endToken: [/\`/], | ||
ignore: true, | ||
advance: true | ||
}, | ||
{ | ||
langs: "js", | ||
name: "if", | ||
startToken: [/^if[\s]*(?=\()/, /[\s]+if[\s]*(?=\()/], | ||
endToken: [/else[\s]+/, /\{/, /;/], | ||
countdown: 2, | ||
indent: true | ||
}, | ||
{ | ||
langs: "js", | ||
name: "for", | ||
startToken: [/^for[\s]*(?=\()/], | ||
endToken: [/\{/, /;/], | ||
countdown: 2, | ||
indent: true | ||
}, | ||
{ | ||
langs: "js", | ||
name: "else", | ||
startToken: [/else[\s]+/], | ||
endToken: [/if/, /\{/, /;/], | ||
countdown: 2, | ||
indent: true | ||
}, | ||
{ | ||
langs: "js css", | ||
name: "bracket", | ||
startToken: [/\([\s]*(var)?/], | ||
endToken: [/\)/], | ||
indent: true, | ||
advance: true | ||
}, | ||
{ | ||
langs: "js", | ||
name: "dot chain", | ||
startToken: [/^\../], | ||
endToken: [/;/, NEW_LINE_REGEX], | ||
indent: true, | ||
head: true, | ||
lineOffset: -1 | ||
}, | ||
{ | ||
langs: "js", | ||
name: "dot chain", | ||
startToken: [/\.\s*$/], | ||
endToken: [function (string, rule) { | ||
return { | ||
matchIndex: string.length ? 1 : -1, | ||
length: string.length ? 0 : 1 | ||
}; | ||
}], | ||
indent: true | ||
}, | ||
{ | ||
langs: "js css", | ||
name: "array", | ||
startToken: [/\[/], | ||
endToken: [/]/], | ||
indent: true, | ||
advance: true | ||
}, | ||
{ | ||
langs: "js css", | ||
name: "block", | ||
startToken: [/\{/], | ||
endToken: [/}/], | ||
indent: true, | ||
advance: true | ||
}, | ||
{ | ||
langs: "js", | ||
name: "var", | ||
startToken: [/var[\s]+/], | ||
endToken: [/;/], | ||
indent: true | ||
}, | ||
{ | ||
langs: "js", | ||
name: "case", | ||
startToken: [/^case[\s]+/], | ||
endToken: [/break[\s;]+/, /^case[\s]+/, /^default[\s]+/, /^return([\s]+|;)/, /}/], | ||
endTokenIndent: true, | ||
indent: true | ||
}, | ||
{ | ||
langs: "js", | ||
name: "default", | ||
startToken: [/^default[\s]*:/], | ||
endToken: [/break[\s;]+/, /^case[\s]+/, /^default[\s]+/, /^return([\s]+|;)/, /}/], | ||
endTokenIndent: true, | ||
indent: true | ||
} | ||
]; | ||
while (l < lineCount) { | ||
line = lines[l].trim(); | ||
lineToMatch = cleanEscapedChars(line) + '\r\n'; | ||
activeMatch = activeMatches[activeMatches.length-1]; | ||
var exports = { | ||
css: function (code, indentString) { | ||
return indent(code || '', filterRules('css'), indentString || '\t'); | ||
}, | ||
js: function (code, indentString) { | ||
return indent(code || '', filterRules('js'), indentString || '\t'); | ||
}, | ||
html: function (code, indentString) { | ||
return indent(code || '', filterRules('html'), indentString || '\t'); | ||
}, | ||
/** | ||
* @deprecated Since version 0.2.0. Will be deleted in version 0.3.0. Use js instead. | ||
*/ | ||
indentJS: function(code, indentString) { | ||
if (console && console.warn) console.warn("Calling deprecated function!"); | ||
return exports.js(code, indentString); | ||
}, | ||
/** | ||
* @deprecated Since version 0.2.0. Will be deleted in version 0.3.0. Use css instead. | ||
*/ | ||
indentCSS: function(code, indentString) { | ||
if (console && console.warn) console.warn("Calling deprecated function!"); | ||
return exports.css(code, indentString); | ||
}, | ||
/** | ||
* @deprecated Since version 0.2.0. Will be deleted in version 0.3.0. Use html instead. | ||
*/ | ||
indentHTML: function(code, indentString) { | ||
if (console && console.warn) console.warn("Calling deprecated function!"); | ||
return exports.html(code, indentString); | ||
matchStart = matchStartRule(lineToMatch, modeRules || baseRules, pos); | ||
if (activeMatches.length) { | ||
matchEnd = matchEndRule(lineToMatch, activeMatch, pos, matchStart); | ||
if (matchEnd.matchIndex === -1) { | ||
if (activeMatch.rule.$ignoreRules) { | ||
// last rule is still active, and it's telling us to ignore. | ||
ignoreBuffer[l] = 1; | ||
l++; pos = 0; | ||
continue; | ||
} | ||
} | ||
}; | ||
else if ( | ||
activeMatch.rule.$ignoreRules || | ||
matchStart.matchIndex === -1 || | ||
matchEnd.matchIndex <= matchStart.matchIndex) { | ||
removeCurrentRule(); | ||
pos = matchEnd.cursor; | ||
continue; // Repeat process for matching line start/end | ||
} | ||
} | ||
return exports; | ||
if (matchStart.matchIndex !== -1) { | ||
implementRule(matchStart); | ||
} | ||
else { | ||
// No new token match end, no new match start | ||
l++; pos = 0; | ||
} | ||
} | ||
var | ||
hardIndentCount, | ||
dedentLines, dedentLine, dedents, | ||
i, j, indents = 0, | ||
hardIndents = copyIntArray(indentBuffer), | ||
indentDeltas = intArray(lineCount), | ||
newLines = []; | ||
function indent(code, baseRules, indentation) { | ||
var lines = code.split(/[\r]?\n/gi); | ||
var lineCount = lines.length; | ||
var newLines = []; | ||
var indentBuffer = []; | ||
var activeRules = []; | ||
var activeCountdowns = []; | ||
var lastRule; | ||
var lastCountdown; | ||
var indents = 0; | ||
var l = 0; | ||
var pos = 0; | ||
var matchEnd, matchStart; | ||
var modeRules = null; | ||
for (i=0; i<lineCount; i++) { | ||
dedentLines = dedentBuffer[i]; | ||
dedents = 0; | ||
for (j=0; j<dedentLines.length; j++) { | ||
dedentLine = dedentLines[j]; | ||
if (dedentLine < 0) { | ||
if (-dedentLine !== i) { | ||
indentDeltas[-dedentLine]++; | ||
dedents += 1; | ||
} | ||
} | ||
else if (hardIndents[dedentLine] > 0) { | ||
hardIndents[dedentLine]--; | ||
dedents += dedentLine !== i; | ||
} | ||
} | ||
hardIndentCount = hardIndents[i]; | ||
indentDeltas[i] = hardIndentCount > dedents ? 1 : | ||
(hardIndentCount < dedents ? hardIndentCount - dedents : 0); | ||
hardIndents[i] = hardIndentCount > 0 ? 1 : 0; | ||
} | ||
while (l < lineCount) { | ||
var line = lines[l].trim(); | ||
var lineToMatch = cleanEscapedChars(line) + '\r\n'; | ||
for (i=0; i<lineCount; i++) { | ||
if (ignoreBuffer[i] === 0) { | ||
indents += indentDeltas[i] || 0; | ||
newLines.push((indents > 0 ? repeatString(tabString, indents) : '') + lines[i].trim()); | ||
} else { | ||
newLines.push(lines[i]); | ||
} | ||
} | ||
matchStart = matchStartRule(lineToMatch, modeRules || baseRules, pos); | ||
return newLines.join('\r\n'); | ||
if (activeRules.length) { | ||
matchEnd = matchEndRule(lineToMatch, lastRule, pos); | ||
if (matchEnd.matchIndex == -1) { | ||
if (lastRule.ignore) { | ||
// last rule is still active, and it's telling us to ignore. | ||
incrementLine(); | ||
continue; | ||
} else if (lastCountdown) { | ||
lastCountdown--; | ||
if (lastCountdown === 0) { | ||
removeLastRule(); | ||
} | ||
} | ||
} | ||
else if ( | ||
lastRule.ignore || | ||
matchStart.matchIndex == -1 || | ||
matchEnd.matchIndex <= matchStart.matchIndex) { | ||
removeLastRule(); | ||
pos = matchEnd.cursor; | ||
continue; // Repeat process for matching line start/end | ||
} | ||
} | ||
if (matchStart.matchIndex != -1) { | ||
implementRule(matchStart); | ||
} | ||
else { | ||
// No new token match end, no new match start | ||
incrementLine(); | ||
} | ||
} | ||
function implementRule(match) { | ||
pos = match.cursor; | ||
return newLines.join('\r\n'); | ||
var rule = match.rule; | ||
var line = (l + 1) + (rule.$lineOffset || 0); | ||
match.line = line; | ||
activeMatches.push(match); | ||
function implementRule(match) { | ||
pos = match.cursor; | ||
lastRule = match.rule; | ||
lastCountdown = match.countdown; | ||
activeRules.push(lastRule); | ||
activeCountdowns.push(lastRule.countdown); | ||
if (lastRule.indent) { | ||
incrementIndentation(lastRule.lineOffset); | ||
} | ||
if (lastRule.rules) { | ||
modeRules = filterRules(lastRule.rules); | ||
} | ||
} | ||
if (rule.$indent) { | ||
indentBuffer[line]++; | ||
} | ||
if (rule.$switchRules) { | ||
modeRules = filterRules(rule.$switchRules, MASTER_RULES); | ||
} | ||
if (rule.$newScope) { | ||
lastMatches.push(null); | ||
} | ||
if (rule.callback) { | ||
rule.callback(match, indentBuffer, dedentBuffer); | ||
} | ||
} | ||
function removeLastRule() { | ||
if (lastRule.indent) { | ||
consumeIndentation(); | ||
if (!lastRule.endTokenIndent && matchEnd.matchIndex == 0) { | ||
calculateIndents(); | ||
} | ||
} | ||
if (lastRule.rules) { | ||
modeRules = null; | ||
} | ||
activeRules.pop(); | ||
activeCountdowns.pop(); | ||
lastRule = activeRules[activeRules.length - 1]; | ||
lastCountdown = activeCountdowns[activeCountdowns.length - 1]; | ||
} | ||
function removeCurrentRule() { | ||
var match = activeMatches.pop(), | ||
line = match.line, | ||
rule = match.rule; | ||
function calculateIndents() { | ||
indents = 0; | ||
for (var b, i = 0; i < indentBuffer.length; i++) { | ||
b = indentBuffer[i]; | ||
if (b.open && b.line != l) | ||
indents++; | ||
} | ||
} | ||
if (rule.$indent) { | ||
var endPatternIndent = typeof rule.$endPatternIndent === 'function' ? | ||
rule.$endPatternIndent(matchEnd) : rule.$endPatternIndent; | ||
var offset = !endPatternIndent && matchEnd.matchIndex === 0 ? 0 : 1; | ||
if (dedentBuffer[l + offset]) dedentBuffer[l + offset].push(line); | ||
} | ||
if (rule.$switchRules) { | ||
modeRules = null; | ||
} | ||
if (rule.$newScope) { | ||
lastMatches.pop(); | ||
} | ||
lastMatches[lastMatches.length - 1] = match; | ||
} | ||
function incrementLine() { | ||
newLines[l] = repeatString(indentation, indents) + line; | ||
l++; | ||
pos = 0; | ||
calculateIndents(); | ||
} | ||
function matchStartRule(string, rules, index) { | ||
string = string.substring(index, string.length); | ||
var result = null; | ||
var minIndex = string.length; | ||
var minMatch; | ||
var match; | ||
function incrementIndentation(lineOffset) { | ||
var matched = indentBuffer[indentBuffer.length - 1]; | ||
if (matched && matched.line == l) { | ||
matched.indent++; | ||
} | ||
else { | ||
indentBuffer.push({ | ||
indent: 1, | ||
open: true, | ||
line: lineOffset ? l + lineOffset : l | ||
}); | ||
if (lineOffset < 0) calculateIndents(); | ||
} | ||
} | ||
var lastMatch = lastMatches[lastMatches.length - 1]; | ||
var lastRuleInScope = lastMatch ? lastMatch.rule.$name : ''; | ||
function consumeIndentation() { | ||
var lastElem = indentBuffer[indentBuffer.length - 1]; | ||
if (lastElem) { | ||
lastElem.open = l == lastElem.line; | ||
if (--lastElem.indent <= 0) { | ||
indentBuffer.pop(); | ||
} | ||
} | ||
for (var rule, r = 0; r < rules.length; r++) { | ||
rule = rules[r]; | ||
if (!rule.$lastRule || | ||
(lastRuleInScope && rule.$lastRule.indexOf(lastRuleInScope) !== -1) | ||
) { | ||
match = searchAny(string, rule.$startPatterns, rule); | ||
if (match.matchIndex != -1 && match.matchIndex < minIndex | ||
&& (!rule.$matchBeginning || index === 0)) { | ||
minIndex = match.matchIndex; | ||
minMatch = match; | ||
result = rule; | ||
} | ||
} | ||
} | ||
return { | ||
rule: result, | ||
relativeIndex: result ? minIndex : -1, | ||
matchIndex: result ? minIndex + index : -1, | ||
cursor: result ? index + minMatch.cursor : -1, | ||
state: minMatch ? minMatch.state : {}, | ||
lastMatch: lastMatch | ||
}; | ||
} | ||
function repeatString(baseString, repeat) { | ||
return (new Array(repeat + 1)).join(baseString); | ||
function matchEndRule(string, active, offset, matchStart) { | ||
string = string.substr(offset, string.length); | ||
var rule = active.rule; | ||
var match = searchAny(string, rule.$endPatterns, rule, active.state, matchStart); | ||
var cursor = rule.$consumeEndMatch ? match.cursor : match.matchIndex; | ||
return { | ||
endPatternIndex: match.endPatternIndex, | ||
matchIndex: match.matchIndex === -1 ? -1 : match.matchIndex + offset, | ||
cursor: cursor === -1 ? -1 : cursor + offset, | ||
state: match.state | ||
}; | ||
} | ||
} | ||
function cleanEscapedChars(string) { | ||
return string.replace(/\\(u[0-9A-Za-z]{4}|u\{[0-9A-Za-z]{1,6}]\}|x[0-9A-Za-z]{2}|.)/g, '0'); | ||
function arrayOfArrays(length) { | ||
var array = new Array(length); | ||
for (var i=0; i<length; i++) array[i] = []; | ||
return array; | ||
} | ||
function intArray(length) { | ||
if (root.Int16Array) { | ||
return new Int16Array(length); | ||
} else { | ||
var array = new Array(length); | ||
for (var i=0; i<length; i++) array[i] = 0; | ||
return array; | ||
} | ||
} | ||
function matchStartRule(string, rules, index) { | ||
string = string.substring(index, string.length); | ||
var result = null; | ||
var minIndex = string.length; | ||
var minMatch; | ||
var match; | ||
for (var rule, r = 0; r < rules.length; r++) { | ||
rule = rules[r]; | ||
match = searchAny(string, rule.startToken, rule); | ||
if (match.matchIndex != -1 && match.matchIndex < minIndex | ||
&& (!rule.head || index == 0)) { | ||
minIndex = match.matchIndex; | ||
minMatch = match; | ||
result = rule; | ||
} | ||
} | ||
return { | ||
rule: result, | ||
matchIndex: result ? minIndex + index : -1, | ||
cursor: result ? minIndex + index + minMatch.matchLength : -1 | ||
}; | ||
function copyIntArray(src) { | ||
var copy = intArray(src.length); | ||
for (var i=0; i<src.length; i++) { | ||
copy[i] = src[i]; | ||
} | ||
return copy; | ||
} | ||
function matchEndRule(string, rule, offset) { | ||
string = string.substr(offset, string.length); | ||
var match = searchAny(string, rule.endToken, rule); | ||
var cursor = rule.advance ? match.matchIndex + match.matchLength : match.matchIndex; | ||
function repeatString(baseString, repeat) { | ||
return (new Array(repeat + 1)).join(baseString); | ||
} | ||
function cleanEscapedChars(string) { | ||
return string.replace(/\\(u[0-9A-Za-z]{4}|u\{[0-9A-Za-z]{1,6}]\}|x[0-9A-Za-z]{2}|.)/g, '0'); | ||
} | ||
function postIndentForCommaAfterEqual(match, indentBuffer, dedentBuffer) { | ||
var lastMatch = match.lastMatch; | ||
if (lastMatch && lastMatch.rule.$name === "=") { | ||
dedentBuffer[match.line].push(-lastMatch.line); | ||
} | ||
} | ||
function nonWhitespaceFollowByNewline(string, rule, state, matchStart) { | ||
var index; | ||
if (!state.newline) { | ||
index = string.search(/[^\s\r\n\{\(\[]/); | ||
state.newline = index !== -1 && (index <= matchStart.relativeIndex || matchStart.relativeIndex === -1); | ||
} else { | ||
index = string.search(/[;,=]?\r*\n/); | ||
if (index !== -1) { | ||
return { | ||
matchIndex: match.matchIndex == -1 ? -1 : match.matchIndex + offset, | ||
cursor: cursor == -1 ? -1 : cursor + offset | ||
}; | ||
matchIndex: index, | ||
length: 1 | ||
} | ||
} | ||
} | ||
return null; | ||
} | ||
function searchAny(string, patterns, rule) { | ||
var index = -1; | ||
var length = 0; | ||
for (var pat, p = 0; p < patterns.length; p++) { | ||
pat = patterns[p]; | ||
if (typeof pat == 'function') { | ||
var match = pat(string, rule); | ||
index = match.matchIndex; | ||
length = match.length; | ||
} | ||
else { | ||
index = string.search(pat); | ||
if (index != -1) { | ||
length = string.match(pat)[0].length; | ||
break; | ||
} | ||
} | ||
function searchAny(string, patterns, rule, state, matchStart) { | ||
state = state || {}; | ||
var index = -1, | ||
length = 0, | ||
match; | ||
for (var pat, p = 0; p < patterns.length; p++) { | ||
pat = patterns[p]; | ||
if (typeof pat === 'function') { | ||
match = pat(string, rule, state, matchStart); | ||
if (match) { | ||
index = match.matchIndex; | ||
length = match.length; | ||
break; | ||
} | ||
return { | ||
matchIndex: index, | ||
matchLength: length, | ||
cursor: index + length, | ||
patternIndex: p | ||
}; | ||
} | ||
else { | ||
match = string.match(pat); | ||
if (match) { | ||
index = string.search(pat); | ||
length = match[0].length; | ||
break; | ||
} | ||
} | ||
} | ||
}()); | ||
return { | ||
endPatternIndex: p, | ||
matchIndex: index, | ||
cursor: index + length, | ||
state: state | ||
}; | ||
} | ||
}(this)); | ||
return indent; | ||
})); |
@@ -1,1 +0,1 @@ | ||
!function(n,e){"function"==typeof define&&define.amd?define([],e):"object"==typeof exports?module.exports=e():n.indent=e()}(this,function(){var n=function(){function n(n){if(c[n])return c[n];var e=[];c[n]=e;for(var t=0;t<i.length;t++)i[t].langs.indexOf(n.toLowerCase())!=-1&&e.push(i[t]);return e}function e(e,o,c){function d(e){A=e.cursor,g=e.rule,f=e.countdown,j.push(g),w.push(g.countdown),g.indent&&h(g.lineOffset),g.rules&&(z=n(g.rules))}function i(){g.indent&&(m(),g.endTokenIndent||0!=k.matchIndex||l()),g.rules&&(z=null),j.pop(),w.pop(),g=j[j.length-1],f=w[w.length-1]}function l(){b=0;for(var n,e=0;e<I.length;e++)n=I[e],n.open&&n.line!=y&&b++}function u(){p[y]=t(c,b)+O,y++,A=0,l()}function h(n){var e=I[I.length-1];e&&e.line==y?e.indent++:(I.push({indent:1,open:!0,line:n?y+n:y}),n<0&&l())}function m(){var n=I[I.length-1];n&&(n.open=y==n.line,--n.indent<=0&&I.pop())}for(var g,f,k,T,x=e.split(/[\r]?\n/gi),v=x.length,p=[],I=[],j=[],w=[],b=0,y=0,A=0,z=null;y<v;){var O=x[y].trim(),Z=a(O)+"\r\n";if(T=s(Z,z||o,A),j.length)if(k=r(Z,g,A),k.matchIndex==-1){if(g.ignore){u();continue}f&&(f--,0===f&&i())}else if(g.ignore||T.matchIndex==-1||k.matchIndex<=T.matchIndex){i(),A=k.cursor;continue}T.matchIndex!=-1?d(T):u()}return p.join("\r\n")}function t(n,e){return new Array(e+1).join(n)}function a(n){return n.replace(/\\(u[0-9A-Za-z]{4}|u\{[0-9A-Za-z]{1,6}]\}|x[0-9A-Za-z]{2}|.)/g,"0")}function s(n,e,t){n=n.substring(t,n.length);for(var a,s,r,c=null,d=n.length,i=0;i<e.length;i++)r=e[i],s=o(n,r.startToken,r),s.matchIndex!=-1&&s.matchIndex<d&&(!r.head||0==t)&&(d=s.matchIndex,a=s,c=r);return{rule:c,matchIndex:c?d+t:-1,cursor:c?d+t+a.matchLength:-1}}function r(n,e,t){n=n.substr(t,n.length);var a=o(n,e.endToken,e),s=e.advance?a.matchIndex+a.matchLength:a.matchIndex;return{matchIndex:a.matchIndex==-1?-1:a.matchIndex+t,cursor:s==-1?-1:s+t}}function o(n,e,t){for(var a,s=-1,r=0,o=0;o<e.length;o++)if(a=e[o],"function"==typeof a){var c=a(n,t);s=c.matchIndex,r=c.length}else if(s=n.search(a),s!=-1){r=n.match(a)[0].length;break}return{matchIndex:s,matchLength:r,cursor:s+r,patternIndex:o}}var c={},d=/\r*\n/,i=[{langs:"html",name:"comment",startToken:[/\<\!\-\-/],endToken:[/\-\-\>/],ignore:!0,advance:!0},{langs:"html",name:"doctype",startToken:[/\<\!/],endToken:[d],ignore:!0,advance:!0},{langs:"html",name:"link|br|hr|input|img|meta",startToken:[/\<(link|br|hr|input|img|meta)/i],endToken:[/>/],advance:!0},{langs:"html",name:"mode switch js",startToken:[function(n,e){var t=/<script[\s>].*/i,a=/<\/script>/i,s=t.exec(n),r=a.exec(n);return s&&(!r||r.index<s.index)?{matchIndex:s.index,length:s[0].length}:{matchIndex:-1}}],endToken:[/<\/script>/i],rules:"js",advance:!0,indent:!0},{langs:"html",name:"mode switch css",startToken:[function(n,e){var t=/<style[\s>].*/i,a=/<\/style>/i,s=t.exec(n),r=a.exec(n);return s&&(!r||r.index<s.index)?{matchIndex:s.index,length:s[0].length}:{matchIndex:-1}}],endToken:[/<\/style>/i],rules:"css",advance:!0,indent:!0},{langs:"html",name:"close-tag",startToken:[/<\/[A-Za-z0-9\-]+>/],endToken:[/./],indent:!0},{langs:"html",name:"tag attr",startToken:[/<[A-Za-z0-9\-]+/],endToken:[/>/],indent:!0},{langs:"html",name:"tag",startToken:[/>/],endToken:[/<\/[A-Za-z0-9\-]+>/],indent:!0,advance:!0},{langs:"js",name:"line comment",startToken:[/\/\//],endToken:[d],ignore:!0},{langs:"js css",name:"block comment",startToken:[/\/\*/],endToken:[/\*\//],ignore:!0},{langs:"js",name:"regex",startToken:[function(n,e){var t=/[(,=:[!&|?{};][\s]*\/[^\/]|^[\s]*\/[^\/]/,a=n.search(t);if(a!=-1){a=n.indexOf("/",a);var s=n.substring(a+1),r=o(s,e.endToken,e);if(r.matchIndex!=-1){s=s.substring(0,r.matchIndex);try{return new RegExp(s),{matchIndex:a,length:1}}catch(c){return{matchIndex:-1}}}}return{matchIndex:-1}}],endToken:[function(n,e){for(var t=0,a=n.indexOf("/");a!=-1;)try{new RegExp(n.substring(0,a));break}catch(s){a=n.indexOf("/",t),t=a+1}return{matchIndex:a,length:a==-1?0:1}}],ignore:!0,advance:!0},{langs:"js css html",name:"string",startToken:[/\"/],endToken:[/\"/,d],ignore:!0,advance:!0},{langs:"js css html",name:"string",startToken:[/\'/],endToken:[/\'/,d],ignore:!0,advance:!0},{langs:"js css html",name:"string",startToken:[/\`/],endToken:[/\`/],ignore:!0,advance:!0},{langs:"js",name:"if",startToken:[/^if[\s]*(?=\()/,/[\s]+if[\s]*(?=\()/],endToken:[/else[\s]+/,/\{/,/;/],countdown:2,indent:!0},{langs:"js",name:"for",startToken:[/^for[\s]*(?=\()/],endToken:[/\{/,/;/],countdown:2,indent:!0},{langs:"js",name:"else",startToken:[/else[\s]+/],endToken:[/if/,/\{/,/;/],countdown:2,indent:!0},{langs:"js css",name:"bracket",startToken:[/\([\s]*(var)?/],endToken:[/\)/],indent:!0,advance:!0},{langs:"js",name:"dot chain",startToken:[/^\../],endToken:[/;/,d],indent:!0,head:!0,lineOffset:-1},{langs:"js",name:"dot chain",startToken:[/\.\s*$/],endToken:[function(n,e){return{matchIndex:n.length?1:-1,length:n.length?0:1}}],indent:!0},{langs:"js css",name:"array",startToken:[/\[/],endToken:[/]/],indent:!0,advance:!0},{langs:"js css",name:"block",startToken:[/\{/],endToken:[/}/],indent:!0,advance:!0},{langs:"js",name:"var",startToken:[/var[\s]+/],endToken:[/;/],indent:!0},{langs:"js",name:"case",startToken:[/^case[\s]+/],endToken:[/break[\s;]+/,/^case[\s]+/,/^default[\s]+/,/^return([\s]+|;)/,/}/],endTokenIndent:!0,indent:!0},{langs:"js",name:"default",startToken:[/^default[\s]*:/],endToken:[/break[\s;]+/,/^case[\s]+/,/^default[\s]+/,/^return([\s]+|;)/,/}/],endTokenIndent:!0,indent:!0}],l={css:function(t,a){return e(t||"",n("css"),a||"\t")},js:function(t,a){return e(t||"",n("js"),a||"\t")},html:function(t,a){return e(t||"",n("html"),a||"\t")},indentJS:function(n,e){return console&&console.warn&&console.warn("Calling deprecated function!"),l.js(n,e)},indentCSS:function(n,e){return console&&console.warn&&console.warn("Calling deprecated function!"),l.css(n,e)},indentHTML:function(n,e){return console&&console.warn&&console.warn("Calling deprecated function!"),l.html(n,e)}};return l}();return n}); | ||
!function(n,e){"function"==typeof define&&define.amd?define([],e):"object"==typeof exports?module.exports=e():n.indent=e()}(this,function(){var n=function(n){function e(n,e,t){if(h[n])return h[n];var r=[];h[n]=r,t=t||"";for(var a=0;a<e.length;a++)e[a].a.indexOf(n.toLowerCase())!==-1&&t.indexOf(e[a].b)===-1&&r.push(e[a]);return r}function t(n,t,c){function u(n){Z=n.cursor;var t=n.rule,r=O+1+(t.c||0);n.line=r,z.push(n),t.d&&w[r]++,t.e&&(E=e(t.e,f)),t.f&&F.push(null),t.callback&&t.callback(n,w,A)}function h(){var n=z.pop(),e=n.line,t=n.rule;if(t.d){var r="function"==typeof t.g?t.g(g):t.g,a=r||0!==g.matchIndex?1:0;A[O+a]&&A[O+a].push(e)}t.e&&(E=null),t.f&&F.pop(),F[F.length-1]=n}function d(n,e,t){n=n.substring(t,n.length);for(var r,a,i,s=null,l=n.length,c=F[F.length-1],u=c?c.rule.b:"",h=0;h<e.length;h++)i=e[h],(!i.h||u&&i.h.indexOf(u)!==-1)&&(a=o(n,i.i,i),a.matchIndex!=-1&&a.matchIndex<l&&(!i.j||0===t)&&(l=a.matchIndex,r=a,s=i));return{rule:s,relativeIndex:s?l:-1,matchIndex:s?l+t:-1,cursor:s?t+r.cursor:-1,state:r?r.state:{},lastMatch:c}}function m(n,e,t,r){n=n.substr(t,n.length);var a=e.rule,i=o(n,a.k,a,e.state,r),s=a.l?i.cursor:i.matchIndex;return{endPatternIndex:i.endPatternIndex,matchIndex:i.matchIndex===-1?-1:i.matchIndex+t,cursor:s===-1?-1:s+t,state:i.state}}n=n||"";var g,b,x,k,v,p=void 0===c.tabString?"\t":c.tabString,j=n.split(/[\r]?\n/gi),I=j.length,y=a(I),w=a(I),A=r(I),z=[],F=[null],O=0,Z=0,E=null;for(c.debug={buffers:{ignore:y,indent:w,dedent:A,active:z}};O<I;){if(x=j[O].trim(),k=l(x)+"\r\n",v=z[z.length-1],b=d(k,E||t,Z),z.length)if(g=m(k,v,Z,b),g.matchIndex===-1){if(v.rule.m){y[O]=1,O++,Z=0;continue}}else if(v.rule.m||b.matchIndex===-1||g.matchIndex<=b.matchIndex){h(),Z=g.cursor;continue}b.matchIndex!==-1?u(b):(O++,Z=0)}var P,S,$,q,R,T,M=0,C=i(w),H=a(I),L=[];for(R=0;R<I;R++){for(S=A[R],q=0,T=0;T<S.length;T++)$=S[T],$<0?-$!==R&&(H[-$]++,q+=1):C[$]>0&&(C[$]--,q+=$!==R);P=C[R],H[R]=P>q?1:P<q?P-q:0,C[R]=P>0?1:0}for(R=0;R<I;R++)0===y[R]?(M+=H[R]||0,L.push((M>0?s(p,M):"")+j[R].trim())):L.push(j[R]);return L.join("\r\n")}function r(n){for(var e=new Array(n),t=0;t<n;t++)e[t]=[];return e}function a(e){if(n.Int16Array)return new Int16Array(e);for(var t=new Array(e),r=0;r<e;r++)t[r]=0;return t}function i(n){for(var e=a(n.length),t=0;t<n.length;t++)e[t]=n[t];return e}function s(n,e){return new Array(e+1).join(n)}function l(n){return n.replace(/\\(u[0-9A-Za-z]{4}|u\{[0-9A-Za-z]{1,6}]\}|x[0-9A-Za-z]{2}|.)/g,"0")}function c(n,e,t){var r=n.lastMatch;r&&"="===r.rule.b&&t[n.line].push(-r.line)}function u(n,e,t,r){var a;if(t.newline){if(a=n.search(/[;,=]?\r*\n/),a!==-1)return{matchIndex:a,length:1}}else a=n.search(/[^\s\r\n\{\(\[]/),t.newline=a!==-1&&(a<=r.relativeIndex||r.relativeIndex===-1);return null}function o(n,e,t,r,a){r=r||{};for(var i,s,l=-1,c=0,u=0;u<e.length;u++)if(s=e[u],"function"==typeof s){if(i=s(n,t,r,a)){l=i.matchIndex,c=i.length;break}}else if(i=n.match(s)){l=n.search(s),c=i[0].length;break}return{endPatternIndex:u,matchIndex:l,cursor:l+c,state:r}}var h={};String.prototype.trim||(String.prototype.trim=function(){return this.replace(/^[\s\uFEFF\xA0]+|[\s\uFEFF\xA0]+$/g,"")});var d=/\r*\n/,f=[{a:"html",b:"comment",i:[/\<\!\-\-/],k:[/\-\-\>/],m:!0,l:!0},{a:"html",b:"doctype",i:[/\<\!doctype html>/i],k:[d],m:!0,l:!0},{a:"html",b:"void-tags",i:[/\<(area|base|br|col|command|embed|hr|img|input|keygen|link|menuitem|meta|param|source|track|wbr)/i],k:[/>/],d:!0,l:!0},{a:"html",b:"mode switch js",i:[function(n){var e=/<script[\s>].*/i,t=/<\/script>/i,r=e.exec(n),a=t.exec(n);return r&&(!a||a.index<r.index)?{matchIndex:r.index,length:r[0].length}:null}],k:[/<\/script>/i],e:"js",l:!0,d:!0,f:!0},{a:"html",b:"mode switch css",i:[function(n){var e=/<style[\s>].*/i,t=/<\/style>/i,r=e.exec(n),a=t.exec(n);return r&&(!a||a.index<r.index)?{matchIndex:r.index,length:r[0].length}:null}],k:[/<\/style>/i],e:"css",l:!0,d:!0,f:!0},{a:"html",b:"html-tag",i:[/<html[^A-Za-z0-9]/i],k:[/<\/html>/i],l:!0},{a:"html",b:"tag",i:[function(n,e,t){var r=/<([A-Za-z0-9\-]+)/,a=n.match(r);return a?(t.openingTag=a[1],{matchIndex:a.index,length:a[0].length}):null}],k:[function(n,e,t){var r=new RegExp("</"+t.openingTag+">","i"),a=n.match(r);return a?{matchIndex:a.index,length:a[0].length}:null}],d:!0,l:!0},{a:"js",b:"line-comment",i:[/\/\//],k:[d],m:!0},{a:"js css",b:"block-comment",i:[/\/\*/],k:[/\*\//],m:!0},{a:"js",b:"regex",i:[function(n,e){var t=/[(,=:[!&|?{};][\s]*\/[^\/]|^[\s]*\/[^\/]/,r=n.search(t);if(r!=-1){r=n.indexOf("/",r);var a=n.substring(r+1),i=o(a,e.k,e);if(i.matchIndex!=-1){a=a.substring(0,i.matchIndex);try{return new RegExp(a),{matchIndex:r,length:1}}catch(s){return null}}}return null}],k:[function(n){for(var e=0,t=n.indexOf("/");t!=-1;)try{new RegExp(n.substring(0,t));break}catch(r){t=n.indexOf("/",e),e=t+1}return t===-1?null:{matchIndex:t,length:1}}],m:!0,l:!0},{a:"html",b:"quotes",i:[/"/],k:[/"/,d],m:!0,l:!0},{a:"html",b:"quotes",i:[/'/],k:[/'/,d],m:!0,l:!0},{a:"js css",b:"string",i:[/(''|""|``)/],k:[/./,d]},{a:"js css",b:"string",i:[/\"(?=[^"])/],k:[/[^\\]\"/,d],m:!0,l:!0},{a:"js css",b:"string",i:[/\'(?=[^'])/],k:[/[^\\]\'/,d],m:!0,l:!0},{a:"js css",b:"string",i:[/\`(?=[^`])/],k:[/[^\\]\`/],m:!0,l:!0},{a:"js",b:"if",i:[/^if\s*(?=\()/,/[\s]+if\s*(?=\()/],k:[/else[\s]+/,u,/[{;]/],d:!0},{a:"js",b:"for|while",i:[/^(for|while)\s*(?=\()/],k:[u,/[{;]/],d:!0},{a:"js",b:"else",i:[/else[\s]+/],k:[/if[^\w$]/,u,/[{;]/],d:!0},{a:"js css",b:"bracket",i:[/\(\s*(var|let|const)?\s*/],k:[/\)/],d:!0,l:!0,f:!0},{a:"js",b:"dot-chain",i:[/^\.[A-Za-z$_]/],k:[/[\.;]/,d],d:!0,j:!0,c:-1},{a:"js",b:"dot-chain",i:[/\.\s*\r*\n/],k:[/[\.;})\]]/,/[^\s]\s*\r*\n/],d:!0},{a:"js css",b:"array",i:[/\[/],k:[/]/],d:!0,l:!0,f:!0},{a:"js css",b:"block",i:[/\{/],k:[/}/],d:!0,l:!0,f:!0},{a:"js",b:"var/let/const",i:[/(var|let|const)[\s]*\r*\n/],k:[u],d:!0,g:!0},{a:"js",b:"var/let/const",i:[/(var|let|const)\s+(?=[\w$])/],k:[/[,;=]/,u],d:!0},{a:"js",b:"var/let/const",h:["var/let/const","="],i:[/,[\s]*\r*\n/],k:[/[,;]/,u],d:!0,callback:c},{a:"js",b:"var/let/const",h:["var/let/const","="],i:[/^,/],k:[/[,;]/,u],j:!0,d:!0,c:-1,callback:c},{a:"js",b:"equality",i:[/[=<>!]=(=)?/],k:[/./]},{a:"js",b:"=",i:[/=/],k:[/[,;\)\]}]/,d]},{a:"js",b:"?:",i:[/\?/],k:[/[:;]/],g:!0,d:!0},{a:"js",b:"case",i:[/^(case|default)[\s:]/],k:[/break[\s;\r\n]/,/^return[\s;\r\n]/,/^case[\s]+/,/^default[\s:]/,/}/],g:function(n){return n.endPatternIndex<=1},d:!0,f:!0},{a:"js",b:"semicolon",i:[/;/],k:[/./]}];return{css:function(n,r){return t(n,e("css",f),r)},js:function(n,r){return t(n,e("js",f),r)},ts:function(n,r){return t(n,e("js",f),r)},html:function(n,r){var a=r.indentHtmlTag?e("html",f,"html-tag"):e("html",f);return t(n,a,r)}}}(this);return n}); |
{ | ||
"name": "indent.js", | ||
"version": "0.2.0", | ||
"version": "0.3.0", | ||
"description": "Fast minimalistic pure indentation of JavaScript, CSS, and HTML.", | ||
@@ -13,16 +13,12 @@ "main": "lib/indent.js", | ||
"devDependencies": { | ||
"jasmine-core": "^2.2.0", | ||
"karma": "^1.7.0", | ||
"karma-coverage": "^0.3.1", | ||
"karma-jasmine": "^0.3.5", | ||
"karma-phantomjs-launcher": "^0.1.4", | ||
"chai": "^4.1.2", | ||
"gulp": "^3.9", | ||
"gulp-concat": "^2.6.0", | ||
"gulp-uglify": "^1.4.1", | ||
"gulp-umd": "^0.2.0", | ||
"gulp-concat": "^2.6.0", | ||
"gulp-uglify": "^1.4.1" | ||
"mocha": "^4.0.1" | ||
}, | ||
"scripts": { | ||
"test": "karma start karma.conf.js --single-run", | ||
"start": "http-server -a localhost -p 8123 -c-1", | ||
"build": "gulp build" | ||
"build": "gulp build", | ||
"test": "mocha ./tests/**" | ||
}, | ||
@@ -29,0 +25,0 @@ "repository": { |
@@ -5,3 +5,4 @@ # indent.js | ||
Fast minimalistic pure indentation of JavaScript, CSS, and HTML. | ||
Fast minimalistic pure indentation of JavaScript (ES5, ES6, ES7), TypeScript, CSS, Less, Sass, and HTML. | ||
Indents better than most tools, test it out in the demo. | ||
@@ -19,4 +20,5 @@ [Online indent.js demo](https://zebzhao.github.io/indent.js/) | ||
* [Minified (~4 kB)](https://raw.githubusercontent.com/zebzhao/indent.js/master/lib/indent.js) | ||
* [Not minified (~11 kB)](https://raw.githubusercontent.com/zebzhao/indent.js/master/lib/indent.js) | ||
* [Minified and gzip (~2.7 kB)](https://raw.githubusercontent.com/zebzhao/indent.js/master/lib/indent.min.js) | ||
* [Minified (~7 kB)](https://raw.githubusercontent.com/zebzhao/indent.js/master/lib/indent.min.js) | ||
* [Not minified (~20 kB)](https://raw.githubusercontent.com/zebzhao/indent.js/master/lib/indent.js) | ||
@@ -44,14 +46,11 @@ Usage | ||
// JS code | ||
var indented = indent.js(js, ' '); | ||
indent.js(code, {tabString: ' '}); | ||
// TypeScript code | ||
indent.ts(code, {tabString: '\t'}); | ||
// CSS code | ||
var indented = indent.css(css, ' '); | ||
indent.css(code); | ||
// HTML code | ||
var indented = indent.html(html, ' '); | ||
console.log(indented); | ||
indent.html(code, {tabString: ' '}); | ||
``` | ||
--- | ||
This project is great for code editors and file watchers. I'd love to hear about how your projects use indent.js. | ||
Developers | ||
@@ -63,8 +62,10 @@ --- | ||
###Languages still not fully supported: | ||
### Languages supported | ||
1. TypeScript (partial?) | ||
2. Less/Sass (partial?) | ||
1. JavaScript/TypeScript | ||
2. HTML | ||
3. JSX (Partial support) | ||
4. CSS/Less/Sass | ||
###Getting the project | ||
### Getting the project | ||
@@ -71,0 +72,0 @@ 1. Run `npm install` to install dependencies |
1168
src/indent.js
@@ -1,531 +0,717 @@ | ||
var indent = (function() { | ||
var rulesCache = {}; | ||
var indent = (function (root) { | ||
var rulesCache = {}; | ||
function filterRules(language) { | ||
if (rulesCache[language]) | ||
return rulesCache[language]; | ||
var ret = []; | ||
rulesCache[language] = ret; | ||
for (var i=0; i<masterRules.length; i++) { | ||
if (masterRules[i].langs.indexOf(language.toLowerCase()) != -1) | ||
ret.push(masterRules[i]); | ||
function filterRules(language, rules, excludes) { | ||
if (rulesCache[language]) | ||
return rulesCache[language]; | ||
var ret = []; | ||
rulesCache[language] = ret; | ||
excludes = excludes || ''; | ||
for (var i = 0; i < rules.length; i++) { | ||
if (rules[i].$languages.indexOf(language.toLowerCase()) !== -1 && | ||
excludes.indexOf(rules[i].$name) === -1) | ||
ret.push(rules[i]); | ||
} | ||
return ret; | ||
} | ||
// String.prototype.trim polyfill for IE9 | ||
if (!String.prototype.trim) { | ||
String.prototype.trim = function () { | ||
return this.replace(/^[\s\uFEFF\xA0]+|[\s\uFEFF\xA0]+$/g, ''); | ||
}; | ||
} | ||
var NEW_LINE_REGEX = /\r*\n/; | ||
/** | ||
* Soft dedent: this type of dedent has the opposite effect and will actually indent every line | ||
* starting from the opening line. | ||
*/ | ||
/** | ||
* $indent - whether rule will cause indent | ||
* $ignoreRules - ignore further rule matching as long as this is last active rule, e.g. string, comments | ||
* $consumeEndMatch - advance the cursor to the end of the end pattern matches | ||
* $endPatternIndent - keep the indent rule active for the $endPatterns | ||
* $endPatterns - list of regex to terminate the rule | ||
* $startPatterns - list of regex to start the rule | ||
* $matchBeginning - match at beginning of line only | ||
* $languages - used to filter by language later | ||
* $lineOffset - added to the line field when rule is applied | ||
* $lastRule - used to continue a previous rule | ||
* $newScope - used to determine if rule creates a new scope, used for lastRule | ||
* | ||
* Always keep NEW_LINE_REGEX $endPatterns as last element, | ||
* as otherwise it will be matched first, and subsequent ones may be ignored | ||
* and skipped permanently by other rules. | ||
*/ | ||
var MASTER_RULES = [ | ||
{ | ||
$languages: "html", | ||
$name: "comment", | ||
$startPatterns: [/\<\!\-\-/], | ||
$endPatterns: [/\-\-\>/], | ||
$ignoreRules: true, | ||
$consumeEndMatch: true | ||
}, | ||
{ | ||
$languages: "html", | ||
$name: "doctype", | ||
$startPatterns: [/\<\!doctype html>/i], | ||
$endPatterns: [NEW_LINE_REGEX], | ||
$ignoreRules: true, | ||
$consumeEndMatch: true | ||
}, | ||
{ | ||
$languages: "html", | ||
$name: "void-tags", | ||
$startPatterns: [ | ||
/\<(area|base|br|col|command|embed|hr|img|input|keygen|link|menuitem|meta|param|source|track|wbr)/i], | ||
$endPatterns: [/>/], | ||
$indent: true, | ||
$consumeEndMatch: true | ||
}, | ||
{ | ||
$languages: "html", | ||
$name: "mode switch js", | ||
$startPatterns: [function (string) { | ||
var start = /<script[\s>].*/i; | ||
var end = /<\/script>/i; | ||
var startMatch = start.exec(string); | ||
var endMatch = end.exec(string); | ||
if (startMatch && (!endMatch || endMatch.index < startMatch.index)) { | ||
return { | ||
matchIndex: startMatch.index, | ||
length: startMatch[0].length | ||
}; | ||
} | ||
return ret; | ||
return null; | ||
}], | ||
$endPatterns: [/<\/script>/i], | ||
$switchRules: "js", | ||
$consumeEndMatch: true, | ||
$indent: true, | ||
$newScope: true | ||
}, | ||
{ | ||
$languages: "html", | ||
$name: "mode switch css", | ||
$startPatterns: [function (string) { | ||
var start = /<style[\s>].*/i; | ||
var end = /<\/style>/i; | ||
var startMatch = start.exec(string); | ||
var endMatch = end.exec(string); | ||
if (startMatch && (!endMatch || endMatch.index < startMatch.index)) { | ||
return { | ||
matchIndex: startMatch.index, | ||
length: startMatch[0].length | ||
}; | ||
} | ||
return null; | ||
}], | ||
$endPatterns: [/<\/style>/i], | ||
$switchRules: "css", | ||
$consumeEndMatch: true, | ||
$indent: true, | ||
$newScope: true | ||
}, | ||
{ | ||
$languages: "html", | ||
$name: "html-tag", | ||
$startPatterns: [/<html[^A-Za-z0-9]/i], | ||
$endPatterns: [/<\/html>/i], | ||
$consumeEndMatch: true | ||
}, | ||
{ | ||
$languages: "html", | ||
$name: "tag", | ||
$startPatterns: [function (string, rule, state) { | ||
var re = /<([A-Za-z0-9\-]+)/; | ||
var match = string.match(re); | ||
if (match) { | ||
state.openingTag = match[1]; | ||
return { | ||
matchIndex: match.index, | ||
length: match[0].length | ||
} | ||
} else { | ||
return null; | ||
} | ||
}], | ||
$endPatterns: [function (string, rule, state) { | ||
var re = new RegExp("</" + state.openingTag + ">", "i"); | ||
var match = string.match(re); | ||
if (match) { | ||
return { | ||
matchIndex: match.index, | ||
length: match[0].length | ||
} | ||
} else { | ||
return null; | ||
} | ||
}], | ||
$indent: true, | ||
$consumeEndMatch: true | ||
}, | ||
{ | ||
$languages: "js", | ||
$name: "line-comment", | ||
$startPatterns: [/\/\//], | ||
$endPatterns: [NEW_LINE_REGEX], | ||
$ignoreRules: true | ||
}, | ||
{ | ||
$languages: "js css", | ||
$name: "block-comment", | ||
$startPatterns: [/\/\*/], | ||
$endPatterns: [/\*\//], | ||
$ignoreRules: true | ||
}, | ||
{ | ||
$languages: "js", | ||
$name: "regex", | ||
$startPatterns: [function (string, rule) { | ||
var re = /[(,=:[!&|?{};][\s]*\/[^/]|^[\s]*\/[^/]/; | ||
var startIndex = string.search(re); | ||
if (startIndex != -1) { | ||
startIndex = string.indexOf('/', startIndex); | ||
var substr = string.substring(startIndex + 1); | ||
var match = searchAny(substr, rule.$endPatterns, rule); | ||
if (match.matchIndex != -1) { | ||
substr = substr.substring(0, match.matchIndex); | ||
try { | ||
(new RegExp(substr)); | ||
return { | ||
matchIndex: startIndex, | ||
length: 1 | ||
}; | ||
} | ||
catch (e) { | ||
return null; | ||
} | ||
} | ||
} | ||
return null; | ||
}], | ||
$endPatterns: [function (string) { | ||
var fromIndex = 0; | ||
var index = string.indexOf('/'); | ||
while (index != -1) { | ||
try { | ||
(new RegExp(string.substring(0, index))); | ||
break; | ||
} | ||
catch (e) { | ||
index = string.indexOf('/', fromIndex); | ||
fromIndex = index + 1; | ||
} | ||
} | ||
return index === -1 ? null : { | ||
matchIndex: index, | ||
length: 1 | ||
}; | ||
}], | ||
$ignoreRules: true, | ||
$consumeEndMatch: true | ||
}, | ||
{ | ||
$languages: "html", | ||
$name: "quotes", | ||
$startPatterns: [/"/], | ||
$endPatterns: [/"/, NEW_LINE_REGEX], | ||
$ignoreRules: true, | ||
$consumeEndMatch: true | ||
}, | ||
{ | ||
$languages: "html", | ||
$name: "quotes", | ||
$startPatterns: [/'/], | ||
$endPatterns: [/'/, NEW_LINE_REGEX], | ||
$ignoreRules: true, | ||
$consumeEndMatch: true | ||
}, | ||
{ | ||
$languages: "js css", | ||
$name: "string", | ||
$startPatterns: [/(''|""|``)/], | ||
$endPatterns: [/./, NEW_LINE_REGEX] | ||
}, | ||
{ | ||
$languages: "js css", | ||
$name: "string", | ||
$startPatterns: [/\"(?=[^"])/], | ||
$endPatterns: [/[^\\]\"/, NEW_LINE_REGEX], | ||
$ignoreRules: true, | ||
$consumeEndMatch: true | ||
}, | ||
{ | ||
$languages: "js css", | ||
$name: "string", | ||
$startPatterns: [/\'(?=[^'])/], | ||
$endPatterns: [/[^\\]\'/, NEW_LINE_REGEX], | ||
$ignoreRules: true, | ||
$consumeEndMatch: true | ||
}, | ||
{ | ||
$languages: "js css", | ||
$name: "string", | ||
$startPatterns: [/\`(?=[^`])/], | ||
$endPatterns: [/[^\\]\`/], | ||
$ignoreRules: true, | ||
$consumeEndMatch: true | ||
}, | ||
{ | ||
$languages: "js", | ||
$name: "if", | ||
$startPatterns: [/^if\s*(?=\()/, /[\s]+if\s*(?=\()/], | ||
$endPatterns: [/else[\s]+/, nonWhitespaceFollowByNewline, /[{;]/], | ||
$indent: true | ||
}, | ||
{ | ||
$languages: "js", | ||
$name: "for|while", | ||
$startPatterns: [/^(for|while)\s*(?=\()/], | ||
$endPatterns: [nonWhitespaceFollowByNewline, /[{;]/], | ||
$indent: true | ||
}, | ||
{ | ||
$languages: "js", | ||
$name: "else", | ||
$startPatterns: [/else[\s]+/], | ||
$endPatterns: [/if[^\w$]/, nonWhitespaceFollowByNewline, /[{;]/], | ||
$indent: true | ||
}, | ||
{ | ||
$languages: "js css", | ||
$name: "bracket", | ||
$startPatterns: [/\(\s*(var|let|const)?\s*/], | ||
$endPatterns: [/\)/], | ||
$indent: true, | ||
$consumeEndMatch: true, | ||
$newScope: true | ||
}, | ||
{ | ||
$languages: "js", | ||
$name: "dot-chain", | ||
$startPatterns: [/^\.[A-Za-z$_]/], | ||
$endPatterns: [/[\.;]/, NEW_LINE_REGEX], | ||
$indent: true, | ||
$matchBeginning: true, | ||
$lineOffset: -1 | ||
}, | ||
{ | ||
$languages: "js", | ||
$name: "dot-chain", | ||
$startPatterns: [/\.\s*\r*\n/], | ||
$endPatterns: [/[\.;})\]]/, /[^\s]\s*\r*\n/], | ||
$indent: true | ||
}, | ||
{ | ||
$languages: "js css", | ||
$name: "array", | ||
$startPatterns: [/\[/], | ||
$endPatterns: [/]/], | ||
$indent: true, | ||
$consumeEndMatch: true, | ||
$newScope: true | ||
}, | ||
{ | ||
$languages: "js css", | ||
$name: "block", | ||
$startPatterns: [/\{/], | ||
$endPatterns: [/}/], | ||
$indent: true, | ||
$consumeEndMatch: true, | ||
$newScope: true | ||
}, | ||
{ | ||
$languages: "js", | ||
$name: "var/let/const", | ||
$startPatterns: [/(var|let|const)[\s]*\r*\n/], | ||
$endPatterns: [nonWhitespaceFollowByNewline], | ||
$indent: true, | ||
$endPatternIndent: true | ||
}, | ||
{ | ||
$languages: "js", | ||
$name: "var/let/const", | ||
$startPatterns: [/(var|let|const)\s+(?=[\w$])/], | ||
$endPatterns: [/[,;=]/, nonWhitespaceFollowByNewline], | ||
$indent: true | ||
}, | ||
{ | ||
$languages: "js", | ||
$name: "var/let/const", | ||
$lastRule: ["var/let/const", "="], | ||
$startPatterns: [/,[\s]*\r*\n/], | ||
$endPatterns: [/[,;]/, nonWhitespaceFollowByNewline], | ||
$indent: true, | ||
callback: postIndentForCommaAfterEqual | ||
}, | ||
{ | ||
$languages: "js", | ||
$name: "var/let/const", | ||
$lastRule: ["var/let/const", "="], | ||
$startPatterns: [/^,/], | ||
$endPatterns: [/[,;]/, nonWhitespaceFollowByNewline], | ||
$matchBeginning: true, | ||
$indent: true, | ||
$lineOffset: -1, | ||
callback: postIndentForCommaAfterEqual | ||
}, | ||
{ | ||
$languages: "js", | ||
$name: "equality", | ||
$startPatterns: [/[=<>!]=(=)?/], | ||
$endPatterns: [/./] | ||
}, | ||
{ | ||
$languages: "js", | ||
$name: "=", | ||
$startPatterns: [/=/], | ||
$endPatterns: [/[,;\)\]}]/, NEW_LINE_REGEX] | ||
}, | ||
{ | ||
$languages: "js", | ||
$name: "?:", | ||
$startPatterns: [/\?/], | ||
$endPatterns: [/[:;]/], | ||
$endPatternIndent: true, | ||
$indent: true | ||
}, | ||
{ | ||
$languages: "js", | ||
$name: "case", | ||
$startPatterns: [/^(case|default)[\s:]/], | ||
$endPatterns: [/break[\s;\r\n]/, /^return[\s;\r\n]/, /^case[\s]+/, /^default[\s:]/, /}/], | ||
$endPatternIndent: function (matchEnd) { | ||
return matchEnd.endPatternIndex <= 1; | ||
}, | ||
$indent: true, | ||
$newScope: true | ||
}, | ||
{ | ||
$languages: "js", | ||
$name: "semicolon", | ||
$startPatterns: [/;/], | ||
$endPatterns: [/./] | ||
} | ||
]; | ||
var NEW_LINE_REGEX = /\r*\n/; | ||
return { | ||
css: function (code, options) { | ||
return indent(code, filterRules('css', MASTER_RULES), options); | ||
}, | ||
js: function (code, options) { | ||
return indent(code, filterRules('js', MASTER_RULES), options); | ||
}, | ||
ts: function (code, options) { | ||
return indent(code, filterRules('js', MASTER_RULES), options); | ||
}, | ||
html: function (code, options) { | ||
var rules = options.indentHtmlTag ? | ||
filterRules('html', MASTER_RULES, 'html-tag') : filterRules('html', MASTER_RULES); | ||
return indent(code, rules, options); | ||
} | ||
}; | ||
function indent(code, baseRules, options) { | ||
code = code || ''; | ||
/** | ||
* indent - whether rule will cause indent | ||
* ignore - ignore further rule matching as long as this is last active rule, e.g. string, comments | ||
* advance - advance the cursor to the end of the endTokens | ||
* endTokenIndent - keep the indent rule active for the endTokens | ||
* head - match at beginning of line only | ||
* langs - used to filter by language later | ||
* lineOffset - added to the line field when rule is applied | ||
* countdown - terminate rule after this number of lines | ||
* Algorithm assumptions | ||
* | ||
* Always keep NEW_LINE_REGEX endToken as last element, | ||
* as otherwise it will be matched first, and subsequent ones may be ignored | ||
* and skipped permanently by other rules. | ||
* indentDeltas - store the the deltas in tabString | ||
* - can be manipulated directly to alter the tabString | ||
* indentBuffer - used to keep tabs on the number of open indentations on each line | ||
* dedentBuffer - each line in the buffer has an array storing open indent lines to be closed | ||
* - an array of numbers is used to reference the opening line | ||
* - a negative number is used to signify a soft dedent (see note about soft dedent) | ||
* | ||
* Each line can create at most 1 tabString. | ||
* When a line is 'used up' for dedent, it cannot be used again, hence the indentBuffer. | ||
*/ | ||
var masterRules = [ | ||
{ | ||
langs: "html", | ||
name: "comment", | ||
startToken: [/\<\!\-\-/], | ||
endToken: [/\-\-\>/], | ||
ignore: true, | ||
advance: true | ||
}, | ||
{ | ||
langs: "html", | ||
name: "doctype", | ||
startToken: [/\<\!/], | ||
endToken: [NEW_LINE_REGEX], | ||
ignore: true, | ||
advance: true | ||
}, | ||
{ | ||
langs: "html", | ||
name: "link|br|hr|input|img|meta", | ||
startToken: [/\<(link|br|hr|input|img|meta)/i], | ||
endToken: [/>/], | ||
advance: true | ||
}, | ||
{ | ||
langs: "html", | ||
name: "mode switch js", | ||
startToken: [function (string, rule) { | ||
var start = /<script[\s>].*/i; | ||
var end = /<\/script>/i; | ||
var startMatch = start.exec(string); | ||
var endMatch = end.exec(string); | ||
var tabString = options.tabString === undefined ? '\t' : options.tabString; | ||
var lines = code.split(/[\r]?\n/gi); | ||
var lineCount = lines.length; | ||
var ignoreBuffer = intArray(lineCount); | ||
var indentBuffer = intArray(lineCount); | ||
var dedentBuffer = arrayOfArrays(lineCount); | ||
var activeMatches = []; | ||
var lastMatches= [null]; | ||
var l = 0; | ||
var pos = 0; | ||
var matchEnd, matchStart; | ||
var modeRules = null; | ||
var line, lineToMatch, activeMatch; | ||
if (startMatch && (!endMatch || endMatch.index < startMatch.index)) { | ||
return { | ||
matchIndex: startMatch.index, | ||
length: startMatch[0].length | ||
}; | ||
} | ||
return {matchIndex: -1}; | ||
}], | ||
endToken: [/<\/script>/i], | ||
rules: "js", | ||
advance: true, | ||
indent: true | ||
}, | ||
{ | ||
langs: "html", | ||
name: "mode switch css", | ||
startToken: [function (string, rule) { | ||
var start = /<style[\s>].*/i; | ||
var end = /<\/style>/i; | ||
var startMatch = start.exec(string); | ||
var endMatch = end.exec(string); | ||
options.debug = { | ||
buffers: { | ||
ignore: ignoreBuffer, | ||
indent: indentBuffer, | ||
dedent: dedentBuffer, | ||
active: activeMatches | ||
} | ||
}; | ||
if (startMatch && (!endMatch || endMatch.index < startMatch.index)) { | ||
return { | ||
matchIndex: startMatch.index, | ||
length: startMatch[0].length | ||
}; | ||
} | ||
return {matchIndex: -1}; | ||
}], | ||
endToken: [/<\/style>/i], | ||
rules: "css", | ||
advance: true, | ||
indent: true | ||
}, | ||
{ | ||
langs: "html", | ||
name: "close-tag", | ||
startToken: [/<\/[A-Za-z0-9\-]+>/], | ||
endToken: [/./], | ||
indent: true | ||
}, | ||
{ | ||
langs: "html", | ||
name: "tag attr", | ||
startToken: [/<[A-Za-z0-9\-]+/], | ||
endToken: [/>/], | ||
indent: true | ||
}, | ||
{ | ||
langs: "html", | ||
name: "tag", | ||
startToken: [/>/], | ||
endToken: [/<\/[A-Za-z0-9\-]+>/], | ||
indent: true, | ||
advance: true | ||
}, | ||
{ | ||
langs: "js", | ||
name: "line comment", | ||
startToken: [/\/\//], | ||
endToken: [NEW_LINE_REGEX], | ||
ignore: true | ||
}, | ||
{ | ||
langs: "js css", | ||
name: "block comment", | ||
startToken: [/\/\*/], | ||
endToken: [/\*\//], | ||
ignore: true | ||
}, | ||
{ | ||
langs: "js", | ||
name: "regex", | ||
startToken: [function (string, rule) { | ||
var re = /[(,=:[!&|?{};][\s]*\/[^/]|^[\s]*\/[^/]/; | ||
var startIndex = string.search(re); | ||
if (startIndex != -1) { | ||
startIndex = string.indexOf('/', startIndex); | ||
var substr = string.substring(startIndex + 1); | ||
var match = searchAny(substr, rule.endToken, rule); | ||
if (match.matchIndex != -1) { | ||
substr = substr.substring(0, match.matchIndex); | ||
try { | ||
(new RegExp(substr)); | ||
return { | ||
matchIndex: startIndex, | ||
length: 1 | ||
}; | ||
} | ||
catch (e) { | ||
return {matchIndex: -1}; | ||
} | ||
} | ||
} | ||
return {matchIndex: -1}; | ||
}], | ||
endToken: [function (string, rule) { | ||
var fromIndex = 0; | ||
var index = string.indexOf('/'); | ||
while (index != -1) { | ||
try { | ||
(new RegExp(string.substring(0, index))); | ||
break; | ||
} | ||
catch (e) { | ||
index = string.indexOf('/', fromIndex); | ||
fromIndex = index + 1; | ||
} | ||
} | ||
return { | ||
matchIndex: index, | ||
length: index == -1 ? 0 : 1 | ||
}; | ||
}], | ||
ignore: true, | ||
advance: true | ||
}, | ||
{ | ||
langs: "js css html", | ||
name: "string", | ||
startToken: [/\"/], | ||
endToken: [/\"/, NEW_LINE_REGEX], | ||
ignore: true, | ||
advance: true | ||
}, | ||
{ | ||
langs: "js css html", | ||
name: "string", | ||
startToken: [/\'/], | ||
endToken: [/\'/, NEW_LINE_REGEX], | ||
ignore: true, | ||
advance: true | ||
}, | ||
{ | ||
langs: "js css html", | ||
name: "string", | ||
startToken: [/\`/], | ||
endToken: [/\`/], | ||
ignore: true, | ||
advance: true | ||
}, | ||
{ | ||
langs: "js", | ||
name: "if", | ||
startToken: [/^if[\s]*(?=\()/, /[\s]+if[\s]*(?=\()/], | ||
endToken: [/else[\s]+/, /\{/, /;/], | ||
countdown: 2, | ||
indent: true | ||
}, | ||
{ | ||
langs: "js", | ||
name: "for", | ||
startToken: [/^for[\s]*(?=\()/], | ||
endToken: [/\{/, /;/], | ||
countdown: 2, | ||
indent: true | ||
}, | ||
{ | ||
langs: "js", | ||
name: "else", | ||
startToken: [/else[\s]+/], | ||
endToken: [/if/, /\{/, /;/], | ||
countdown: 2, | ||
indent: true | ||
}, | ||
{ | ||
langs: "js css", | ||
name: "bracket", | ||
startToken: [/\([\s]*(var)?/], | ||
endToken: [/\)/], | ||
indent: true, | ||
advance: true | ||
}, | ||
{ | ||
langs: "js", | ||
name: "dot chain", | ||
startToken: [/^\../], | ||
endToken: [/;/, NEW_LINE_REGEX], | ||
indent: true, | ||
head: true, | ||
lineOffset: -1 | ||
}, | ||
{ | ||
langs: "js", | ||
name: "dot chain", | ||
startToken: [/\.\s*$/], | ||
endToken: [function (string, rule) { | ||
return { | ||
matchIndex: string.length ? 1 : -1, | ||
length: string.length ? 0 : 1 | ||
}; | ||
}], | ||
indent: true | ||
}, | ||
{ | ||
langs: "js css", | ||
name: "array", | ||
startToken: [/\[/], | ||
endToken: [/]/], | ||
indent: true, | ||
advance: true | ||
}, | ||
{ | ||
langs: "js css", | ||
name: "block", | ||
startToken: [/\{/], | ||
endToken: [/}/], | ||
indent: true, | ||
advance: true | ||
}, | ||
{ | ||
langs: "js", | ||
name: "var", | ||
startToken: [/var[\s]+/], | ||
endToken: [/;/], | ||
indent: true | ||
}, | ||
{ | ||
langs: "js", | ||
name: "case", | ||
startToken: [/^case[\s]+/], | ||
endToken: [/break[\s;]+/, /^case[\s]+/, /^default[\s]+/, /^return([\s]+|;)/, /}/], | ||
endTokenIndent: true, | ||
indent: true | ||
}, | ||
{ | ||
langs: "js", | ||
name: "default", | ||
startToken: [/^default[\s]*:/], | ||
endToken: [/break[\s;]+/, /^case[\s]+/, /^default[\s]+/, /^return([\s]+|;)/, /}/], | ||
endTokenIndent: true, | ||
indent: true | ||
} | ||
]; | ||
while (l < lineCount) { | ||
line = lines[l].trim(); | ||
lineToMatch = cleanEscapedChars(line) + '\r\n'; | ||
activeMatch = activeMatches[activeMatches.length-1]; | ||
var exports = { | ||
css: function (code, indentString) { | ||
return indent(code || '', filterRules('css'), indentString || '\t'); | ||
}, | ||
js: function (code, indentString) { | ||
return indent(code || '', filterRules('js'), indentString || '\t'); | ||
}, | ||
html: function (code, indentString) { | ||
return indent(code || '', filterRules('html'), indentString || '\t'); | ||
}, | ||
/** | ||
* @deprecated Since version 0.2.0. Will be deleted in version 0.3.0. Use js instead. | ||
*/ | ||
indentJS: function(code, indentString) { | ||
if (console && console.warn) console.warn("Calling deprecated function!"); | ||
return exports.js(code, indentString); | ||
}, | ||
/** | ||
* @deprecated Since version 0.2.0. Will be deleted in version 0.3.0. Use css instead. | ||
*/ | ||
indentCSS: function(code, indentString) { | ||
if (console && console.warn) console.warn("Calling deprecated function!"); | ||
return exports.css(code, indentString); | ||
}, | ||
/** | ||
* @deprecated Since version 0.2.0. Will be deleted in version 0.3.0. Use html instead. | ||
*/ | ||
indentHTML: function(code, indentString) { | ||
if (console && console.warn) console.warn("Calling deprecated function!"); | ||
return exports.html(code, indentString); | ||
matchStart = matchStartRule(lineToMatch, modeRules || baseRules, pos); | ||
if (activeMatches.length) { | ||
matchEnd = matchEndRule(lineToMatch, activeMatch, pos, matchStart); | ||
if (matchEnd.matchIndex === -1) { | ||
if (activeMatch.rule.$ignoreRules) { | ||
// last rule is still active, and it's telling us to ignore. | ||
ignoreBuffer[l] = 1; | ||
l++; pos = 0; | ||
continue; | ||
} | ||
} | ||
}; | ||
else if ( | ||
activeMatch.rule.$ignoreRules || | ||
matchStart.matchIndex === -1 || | ||
matchEnd.matchIndex <= matchStart.matchIndex) { | ||
removeCurrentRule(); | ||
pos = matchEnd.cursor; | ||
continue; // Repeat process for matching line start/end | ||
} | ||
} | ||
return exports; | ||
if (matchStart.matchIndex !== -1) { | ||
implementRule(matchStart); | ||
} | ||
else { | ||
// No new token match end, no new match start | ||
l++; pos = 0; | ||
} | ||
} | ||
var | ||
hardIndentCount, | ||
dedentLines, dedentLine, dedents, | ||
i, j, indents = 0, | ||
hardIndents = copyIntArray(indentBuffer), | ||
indentDeltas = intArray(lineCount), | ||
newLines = []; | ||
function indent(code, baseRules, indentation) { | ||
var lines = code.split(/[\r]?\n/gi); | ||
var lineCount = lines.length; | ||
var newLines = []; | ||
var indentBuffer = []; | ||
var activeRules = []; | ||
var activeCountdowns = []; | ||
var lastRule; | ||
var lastCountdown; | ||
var indents = 0; | ||
var l = 0; | ||
var pos = 0; | ||
var matchEnd, matchStart; | ||
var modeRules = null; | ||
for (i=0; i<lineCount; i++) { | ||
dedentLines = dedentBuffer[i]; | ||
dedents = 0; | ||
for (j=0; j<dedentLines.length; j++) { | ||
dedentLine = dedentLines[j]; | ||
if (dedentLine < 0) { | ||
if (-dedentLine !== i) { | ||
indentDeltas[-dedentLine]++; | ||
dedents += 1; | ||
} | ||
} | ||
else if (hardIndents[dedentLine] > 0) { | ||
hardIndents[dedentLine]--; | ||
dedents += dedentLine !== i; | ||
} | ||
} | ||
hardIndentCount = hardIndents[i]; | ||
indentDeltas[i] = hardIndentCount > dedents ? 1 : | ||
(hardIndentCount < dedents ? hardIndentCount - dedents : 0); | ||
hardIndents[i] = hardIndentCount > 0 ? 1 : 0; | ||
} | ||
while (l < lineCount) { | ||
var line = lines[l].trim(); | ||
var lineToMatch = cleanEscapedChars(line) + '\r\n'; | ||
for (i=0; i<lineCount; i++) { | ||
if (ignoreBuffer[i] === 0) { | ||
indents += indentDeltas[i] || 0; | ||
newLines.push((indents > 0 ? repeatString(tabString, indents) : '') + lines[i].trim()); | ||
} else { | ||
newLines.push(lines[i]); | ||
} | ||
} | ||
matchStart = matchStartRule(lineToMatch, modeRules || baseRules, pos); | ||
return newLines.join('\r\n'); | ||
if (activeRules.length) { | ||
matchEnd = matchEndRule(lineToMatch, lastRule, pos); | ||
if (matchEnd.matchIndex == -1) { | ||
if (lastRule.ignore) { | ||
// last rule is still active, and it's telling us to ignore. | ||
incrementLine(); | ||
continue; | ||
} else if (lastCountdown) { | ||
lastCountdown--; | ||
if (lastCountdown === 0) { | ||
removeLastRule(); | ||
} | ||
} | ||
} | ||
else if ( | ||
lastRule.ignore || | ||
matchStart.matchIndex == -1 || | ||
matchEnd.matchIndex <= matchStart.matchIndex) { | ||
removeLastRule(); | ||
pos = matchEnd.cursor; | ||
continue; // Repeat process for matching line start/end | ||
} | ||
} | ||
if (matchStart.matchIndex != -1) { | ||
implementRule(matchStart); | ||
} | ||
else { | ||
// No new token match end, no new match start | ||
incrementLine(); | ||
} | ||
} | ||
function implementRule(match) { | ||
pos = match.cursor; | ||
return newLines.join('\r\n'); | ||
var rule = match.rule; | ||
var line = (l + 1) + (rule.$lineOffset || 0); | ||
match.line = line; | ||
activeMatches.push(match); | ||
function implementRule(match) { | ||
pos = match.cursor; | ||
lastRule = match.rule; | ||
lastCountdown = match.countdown; | ||
activeRules.push(lastRule); | ||
activeCountdowns.push(lastRule.countdown); | ||
if (lastRule.indent) { | ||
incrementIndentation(lastRule.lineOffset); | ||
} | ||
if (lastRule.rules) { | ||
modeRules = filterRules(lastRule.rules); | ||
} | ||
} | ||
if (rule.$indent) { | ||
indentBuffer[line]++; | ||
} | ||
if (rule.$switchRules) { | ||
modeRules = filterRules(rule.$switchRules, MASTER_RULES); | ||
} | ||
if (rule.$newScope) { | ||
lastMatches.push(null); | ||
} | ||
if (rule.callback) { | ||
rule.callback(match, indentBuffer, dedentBuffer); | ||
} | ||
} | ||
function removeLastRule() { | ||
if (lastRule.indent) { | ||
consumeIndentation(); | ||
if (!lastRule.endTokenIndent && matchEnd.matchIndex == 0) { | ||
calculateIndents(); | ||
} | ||
} | ||
if (lastRule.rules) { | ||
modeRules = null; | ||
} | ||
activeRules.pop(); | ||
activeCountdowns.pop(); | ||
lastRule = activeRules[activeRules.length - 1]; | ||
lastCountdown = activeCountdowns[activeCountdowns.length - 1]; | ||
} | ||
function removeCurrentRule() { | ||
var match = activeMatches.pop(), | ||
line = match.line, | ||
rule = match.rule; | ||
function calculateIndents() { | ||
indents = 0; | ||
for (var b, i = 0; i < indentBuffer.length; i++) { | ||
b = indentBuffer[i]; | ||
if (b.open && b.line != l) | ||
indents++; | ||
} | ||
} | ||
if (rule.$indent) { | ||
var endPatternIndent = typeof rule.$endPatternIndent === 'function' ? | ||
rule.$endPatternIndent(matchEnd) : rule.$endPatternIndent; | ||
var offset = !endPatternIndent && matchEnd.matchIndex === 0 ? 0 : 1; | ||
if (dedentBuffer[l + offset]) dedentBuffer[l + offset].push(line); | ||
} | ||
if (rule.$switchRules) { | ||
modeRules = null; | ||
} | ||
if (rule.$newScope) { | ||
lastMatches.pop(); | ||
} | ||
lastMatches[lastMatches.length - 1] = match; | ||
} | ||
function incrementLine() { | ||
newLines[l] = repeatString(indentation, indents) + line; | ||
l++; | ||
pos = 0; | ||
calculateIndents(); | ||
} | ||
function matchStartRule(string, rules, index) { | ||
string = string.substring(index, string.length); | ||
var result = null; | ||
var minIndex = string.length; | ||
var minMatch; | ||
var match; | ||
function incrementIndentation(lineOffset) { | ||
var matched = indentBuffer[indentBuffer.length - 1]; | ||
if (matched && matched.line == l) { | ||
matched.indent++; | ||
} | ||
else { | ||
indentBuffer.push({ | ||
indent: 1, | ||
open: true, | ||
line: lineOffset ? l + lineOffset : l | ||
}); | ||
if (lineOffset < 0) calculateIndents(); | ||
} | ||
} | ||
var lastMatch = lastMatches[lastMatches.length - 1]; | ||
var lastRuleInScope = lastMatch ? lastMatch.rule.$name : ''; | ||
function consumeIndentation() { | ||
var lastElem = indentBuffer[indentBuffer.length - 1]; | ||
if (lastElem) { | ||
lastElem.open = l == lastElem.line; | ||
if (--lastElem.indent <= 0) { | ||
indentBuffer.pop(); | ||
} | ||
} | ||
for (var rule, r = 0; r < rules.length; r++) { | ||
rule = rules[r]; | ||
if (!rule.$lastRule || | ||
(lastRuleInScope && rule.$lastRule.indexOf(lastRuleInScope) !== -1) | ||
) { | ||
match = searchAny(string, rule.$startPatterns, rule); | ||
if (match.matchIndex != -1 && match.matchIndex < minIndex | ||
&& (!rule.$matchBeginning || index === 0)) { | ||
minIndex = match.matchIndex; | ||
minMatch = match; | ||
result = rule; | ||
} | ||
} | ||
} | ||
return { | ||
rule: result, | ||
relativeIndex: result ? minIndex : -1, | ||
matchIndex: result ? minIndex + index : -1, | ||
cursor: result ? index + minMatch.cursor : -1, | ||
state: minMatch ? minMatch.state : {}, | ||
lastMatch: lastMatch | ||
}; | ||
} | ||
function repeatString(baseString, repeat) { | ||
return (new Array(repeat + 1)).join(baseString); | ||
function matchEndRule(string, active, offset, matchStart) { | ||
string = string.substr(offset, string.length); | ||
var rule = active.rule; | ||
var match = searchAny(string, rule.$endPatterns, rule, active.state, matchStart); | ||
var cursor = rule.$consumeEndMatch ? match.cursor : match.matchIndex; | ||
return { | ||
endPatternIndex: match.endPatternIndex, | ||
matchIndex: match.matchIndex === -1 ? -1 : match.matchIndex + offset, | ||
cursor: cursor === -1 ? -1 : cursor + offset, | ||
state: match.state | ||
}; | ||
} | ||
} | ||
function cleanEscapedChars(string) { | ||
return string.replace(/\\(u[0-9A-Za-z]{4}|u\{[0-9A-Za-z]{1,6}]\}|x[0-9A-Za-z]{2}|.)/g, '0'); | ||
function arrayOfArrays(length) { | ||
var array = new Array(length); | ||
for (var i=0; i<length; i++) array[i] = []; | ||
return array; | ||
} | ||
function intArray(length) { | ||
if (root.Int16Array) { | ||
return new Int16Array(length); | ||
} else { | ||
var array = new Array(length); | ||
for (var i=0; i<length; i++) array[i] = 0; | ||
return array; | ||
} | ||
} | ||
function matchStartRule(string, rules, index) { | ||
string = string.substring(index, string.length); | ||
var result = null; | ||
var minIndex = string.length; | ||
var minMatch; | ||
var match; | ||
for (var rule, r = 0; r < rules.length; r++) { | ||
rule = rules[r]; | ||
match = searchAny(string, rule.startToken, rule); | ||
if (match.matchIndex != -1 && match.matchIndex < minIndex | ||
&& (!rule.head || index == 0)) { | ||
minIndex = match.matchIndex; | ||
minMatch = match; | ||
result = rule; | ||
} | ||
} | ||
return { | ||
rule: result, | ||
matchIndex: result ? minIndex + index : -1, | ||
cursor: result ? minIndex + index + minMatch.matchLength : -1 | ||
}; | ||
function copyIntArray(src) { | ||
var copy = intArray(src.length); | ||
for (var i=0; i<src.length; i++) { | ||
copy[i] = src[i]; | ||
} | ||
return copy; | ||
} | ||
function matchEndRule(string, rule, offset) { | ||
string = string.substr(offset, string.length); | ||
var match = searchAny(string, rule.endToken, rule); | ||
var cursor = rule.advance ? match.matchIndex + match.matchLength : match.matchIndex; | ||
function repeatString(baseString, repeat) { | ||
return (new Array(repeat + 1)).join(baseString); | ||
} | ||
function cleanEscapedChars(string) { | ||
return string.replace(/\\(u[0-9A-Za-z]{4}|u\{[0-9A-Za-z]{1,6}]\}|x[0-9A-Za-z]{2}|.)/g, '0'); | ||
} | ||
function postIndentForCommaAfterEqual(match, indentBuffer, dedentBuffer) { | ||
var lastMatch = match.lastMatch; | ||
if (lastMatch && lastMatch.rule.$name === "=") { | ||
dedentBuffer[match.line].push(-lastMatch.line); | ||
} | ||
} | ||
function nonWhitespaceFollowByNewline(string, rule, state, matchStart) { | ||
var index; | ||
if (!state.newline) { | ||
index = string.search(/[^\s\r\n\{\(\[]/); | ||
state.newline = index !== -1 && (index <= matchStart.relativeIndex || matchStart.relativeIndex === -1); | ||
} else { | ||
index = string.search(/[;,=]?\r*\n/); | ||
if (index !== -1) { | ||
return { | ||
matchIndex: match.matchIndex == -1 ? -1 : match.matchIndex + offset, | ||
cursor: cursor == -1 ? -1 : cursor + offset | ||
}; | ||
matchIndex: index, | ||
length: 1 | ||
} | ||
} | ||
} | ||
return null; | ||
} | ||
function searchAny(string, patterns, rule) { | ||
var index = -1; | ||
var length = 0; | ||
for (var pat, p = 0; p < patterns.length; p++) { | ||
pat = patterns[p]; | ||
if (typeof pat == 'function') { | ||
var match = pat(string, rule); | ||
index = match.matchIndex; | ||
length = match.length; | ||
} | ||
else { | ||
index = string.search(pat); | ||
if (index != -1) { | ||
length = string.match(pat)[0].length; | ||
break; | ||
} | ||
} | ||
function searchAny(string, patterns, rule, state, matchStart) { | ||
state = state || {}; | ||
var index = -1, | ||
length = 0, | ||
match; | ||
for (var pat, p = 0; p < patterns.length; p++) { | ||
pat = patterns[p]; | ||
if (typeof pat === 'function') { | ||
match = pat(string, rule, state, matchStart); | ||
if (match) { | ||
index = match.matchIndex; | ||
length = match.length; | ||
break; | ||
} | ||
return { | ||
matchIndex: index, | ||
matchLength: length, | ||
cursor: index + length, | ||
patternIndex: p | ||
}; | ||
} | ||
else { | ||
match = string.match(pat); | ||
if (match) { | ||
index = string.search(pat); | ||
length = match[0].length; | ||
break; | ||
} | ||
} | ||
} | ||
}()); | ||
return { | ||
endPatternIndex: p, | ||
matchIndex: index, | ||
cursor: index + length, | ||
state: state | ||
}; | ||
} | ||
}(this)); |
Sorry, the diff of this file is not supported yet
Sorry, the diff of this file is not supported yet
License Policy Violation
LicenseThis package is not allowed per your license policy. Review the package's license to ensure compliance.
Found 1 instance in 1 package
Major refactor
Supply chain riskPackage has recently undergone a major refactor. It may be unstable or indicate significant internal changes. Use caution when updating to versions that include significant changes.
Found 1 instance in 1 package
License Policy Violation
LicenseThis package is not allowed per your license policy. Review the package's license to ensure compliance.
Found 1 instance in 1 package
6
64
3949
78
1390077
16