diff --git a/README.md b/README.md index 86a59a670..35f04a92f 100644 --- a/README.md +++ b/README.md @@ -156,7 +156,9 @@ HTML Beautifier Options: -c, --indent-char Indentation character [" "] -b, --brace-style [collapse|expand|end-expand] ["collapse"] -S, --indent-scripts [keep|separate|normal] ["normal"] - -W, --max-char Maximum characters per line (0 disables) [250] + -w, --wrap-line-length Maximum characters per line (0 disables) [250] + -p, --preserve-newlines Preserve existing line-breaks (--no-preserve-newlines disables) + -m, --max-preserve-newlines Maximum number of line-breaks to be preserved in one chunk [10] -U, --unformatted List of tags (defaults to inline) that should not be reformatted ``` diff --git a/index.html b/index.html index c07cf9314..67c280f82 100644 --- a/index.html +++ b/index.html @@ -1,8 +1,4 @@ - - - - - + - doesn't look like html + + var trimmed = source.replace(/^[ \t\n\r]+/, ''); + var comment_mark = '<' + '!-' + '-'; + return (trimmed && (trimmed.substring(0, 1) === '<' && trimmed.substring(0, 4) !== comment_mark)); } - output = js_beautify(source, opts); - } - if (the.editor) { - the.editor.setValue(output); - } else { - $('#source').val(output); - } - - the.beautify_in_progress = false; -} - -function looks_like_html(source) -{ - // - looks like html - // - doesn't look like html - - var trimmed = source.replace(/^[ \t\n\r]+/, ''); - var comment_mark = '<' + '!-' + '-'; - return (trimmed && (trimmed.substring(0, 1) === '<' && trimmed.substring(0, 4) !== comment_mark)); -} - + +
-

Beautify, unpack or deobfuscate JavaScript and HTML, make JSON/JSONP readable, etc.

-

All of the source code is completely free and open, available on the github under MIT licence,
and we have a command-line version, python library and a node package as well. +

+ Beautify, unpack or deobfuscate JavaScript and HTML, make JSON/JSONP readable, etc. +

+

+ All of the source code is completely free and open, available on the github under MIT licence, +
and we have a command-line version, python library and a node package as well. +

- + + +
-
- -
- -
- - - -

HTML <style>, <script> formatting:

- - -
- -
-
-
-
- -
Use a simple textarea for code input? - - -
+ +
+ + +
+ + +
+ + + +

HTML <style>, <script> formatting:

+ + +
+ + + +
+ + +
+ + +
+ + +
+ + +
Use a simple textarea for code input? + + +
- - - + + +
-
- Flattr -
- - - - -
-
- -

Browser extensions and other uses:

- - -

Written by Einar Lielmanis, einar@jsbeautifier.org, maintained and evolved by Liam Newman.

-

We use the wonderful CodeMirror syntax highlighting editor, written by Marijn Haverbeke.

-

Made with a great help of - Jason Diamond, - Patrick Hof, - Nochum Sossonko, - Andreas Schneider, -
- Dave Vasilevsky, - Vital Batmanov, - Ron Baldwin, - Gabriel Harrison, - Chris J. Shull, - Mathias Bynens, -
- Vittorio Gambaletta, - Stefano Sanfilippo and - Daniel Stockman. -

- -

- Run the tests -

+
+ + Flattr + +
+ + + + +
+
+ +

Browser extensions and other uses:

+ + +

Written by Einar Lielmanis, einar@jsbeautifier.org, maintained and evolved by Liam Newman.

+

We use the wonderful CodeMirror syntax highlighting editor, written by Marijn Haverbeke. +

+

Made with a great help of Jason Diamond, Patrick Hof, Nochum Sossonko, Andreas Schneider, +
Dave Vasilevsky, + Vital Batmanov, Ron Baldwin, Gabriel Harrison, + Chris J. Shull, + Mathias Bynens, +
+ Vittorio Gambaletta, + Stefano Sanfilippo and + Daniel Stockman. +

+ +

+ Run the tests +

+ diff --git a/js/lib/beautify-css.js b/js/lib/beautify-css.js index 282c92bdf..34243e4e5 100644 --- a/js/lib/beautify-css.js +++ b/js/lib/beautify-css.js @@ -69,18 +69,22 @@ var whiteRe = /^\s+$/; var wordRe = /[\w$\-_]/; - var pos = -1, ch; + var pos = -1, + ch; + function next() { ch = source_text.charAt(++pos); return ch; } + function peek() { - return source_text.charAt(pos+1); + return source_text.charAt(pos + 1); } + function eatString(comma) { var start = pos; - while(next()){ - if (ch === "\\"){ + while (next()) { + if (ch === "\\") { next(); next(); } else if (ch === comma) { @@ -102,8 +106,7 @@ function skipWhitespace() { var start = pos; - do{ - }while (whiteRe.test(next())); + do {} while (whiteRe.test(next())); return pos !== start + 1; } @@ -112,7 +115,7 @@ next(); while (next()) { if (ch === "*" && peek() === "/") { - pos ++; + pos++; break; } } @@ -122,17 +125,19 @@ function lookBack(str) { - return source_text.substring(pos-str.length, pos).toLowerCase() === str; + return source_text.substring(pos - str.length, pos).toLowerCase() === str; } // printer var indentString = source_text.match(/^[\r\n]*[\t ]*/)[0]; var singleIndent = Array(indentSize + 1).join(indentCharacter); var indentLevel = 0; + function indent() { indentLevel++; indentString += singleIndent; } + function outdent() { indentLevel--; indentString = indentString.slice(0, -indentSize); @@ -175,7 +180,7 @@ } /*_____________________--------------------_____________________*/ - while(true) { + while (true) { var isAfterSpace = skipWhitespace(); if (!ch) { @@ -198,21 +203,21 @@ output.push(eatComment(), "\n", indentString); } else if (ch === '(') { // may be a url if (lookBack("url")) { - output.push(ch); - eatWhitespace(); - if (next()) { - if (ch !== ')' && ch !== '"' && ch !== '\'') { - output.push(eatString(')')); - } else { - pos--; + output.push(ch); + eatWhitespace(); + if (next()) { + if (ch !== ')' && ch !== '"' && ch !== '\'') { + output.push(eatString(')')); + } else { + pos--; + } } - } } else { - if (isAfterSpace) { - print.singleSpace(); - } - output.push(ch); - eatWhitespace(); + if (isAfterSpace) { + print.singleSpace(); + } + output.push(ch); + eatWhitespace(); } } else if (ch === ')') { output.push(ch); @@ -222,7 +227,7 @@ print.singleSpace(); } else if (ch === ']') { output.push(ch); - } else if (ch === '[' || ch === '=') { // no whitespace before or after + } else if (ch === '[' || ch === '=') { // no whitespace before or after eatWhitespace(); output.push(ch); } else { diff --git a/js/lib/beautify-html.js b/js/lib/beautify-html.js index 9cca5c4db..ed30e2bdd 100644 --- a/js/lib/beautify-html.js +++ b/js/lib/beautify-html.js @@ -42,575 +42,672 @@ The options are: indent_size (default 4) — indentation size, indent_char (default space) — character to indent with, - max_char (default 250) - maximum amount of characters per line (0 = disable) + wrap_line_length (default 250) - maximum amount of characters per line (0 = disable) brace_style (default "collapse") - "collapse" | "expand" | "end-expand" put braces on the same line as control statements (default), or put braces on own line (Allman / ANSI style), or just put end braces on own line. unformatted (defaults to inline tags) - list of tags, that shouldn't be reformatted indent_scripts (default normal) - "keep"|"separate"|"normal" + preserve_newlines (default true) - whether existing line breaks before elements should be preserved + Only works before elements, not inside tags or for text. + max_preserve_newlines (default unlimited) - maximum number of line breaks to be preserved in one chunk e.g. style_html(html_source, { 'indent_size': 2, 'indent_char': ' ', - 'max_char': 78, + 'wrap_line_length': 78, 'brace_style': 'expand', - 'unformatted': ['a', 'sub', 'sup', 'b', 'i', 'u'] + 'unformatted': ['a', 'sub', 'sup', 'b', 'i', 'u'], + 'preserve_newlines': true, + 'max_preserve_newlines': 5 }); */ (function() { - function style_html(html_source, options, js_beautify, css_beautify) { - //Wrapper function to invoke all the necessary constructors and deal with the output. - - var multi_parser, - indent_size, - indent_character, - max_char, - brace_style, - unformatted; - - options = options || {}; - indent_size = options.indent_size || 4; - indent_character = options.indent_char || ' '; - brace_style = options.brace_style || 'collapse'; - max_char = options.max_char === 0 ? Infinity : options.max_char || 250; - unformatted = options.unformatted || ['a', 'span', 'bdo', 'em', 'strong', 'dfn', 'code', 'samp', 'kbd', 'var', 'cite', 'abbr', 'acronym', 'q', 'sub', 'sup', 'tt', 'i', 'b', 'big', 'small', 'u', 's', 'strike', 'font', 'ins', 'del', 'pre', 'address', 'dt', 'h1', 'h2', 'h3', 'h4', 'h5', 'h6']; - - function Parser() { - - this.pos = 0; //Parser position - this.token = ''; - this.current_mode = 'CONTENT'; //reflects the current Parser mode: TAG/CONTENT - this.tags = { //An object to hold tags, their position, and their parent-tags, initiated with default values - parent: 'parent1', - parentcount: 1, - parent1: '' - }; - this.tag_type = ''; - this.token_text = this.last_token = this.last_text = this.token_type = ''; - - this.Utils = { //Uilities made available to the various functions - whitespace: "\n\r\t ".split(''), - single_token: 'br,input,link,meta,!doctype,basefont,base,area,hr,wbr,param,img,isindex,?xml,embed,?php,?,?='.split(','), //all the single tags for HTML - extra_liners: 'head,body,/html'.split(','), //for tags that need a line of whitespace before them - in_array: function (what, arr) { - for (var i=0; i= this.input.length) { - return content.length?content.join(''):['', 'TK_EOF']; - } + function ltrim(s) { + return s.replace(/^\s+/g, ''); + } - input_char = this.input.charAt(this.pos); - this.pos++; - this.line_char_count++; + function style_html(html_source, options, js_beautify, css_beautify) { + //Wrapper function to invoke all the necessary constructors and deal with the output. + + var multi_parser, + indent_size, + indent_character, + wrap_line_length, + brace_style, + unformatted, + preserve_newlines, + max_preserve_newlines; + + options = options || {}; + + // backwards compatibility to 1.3.4 + if (options.wrap_line_length == undefined && options.max_char != undefined) { + options.wrap_line_length = options.max_char; + } - if (this.Utils.in_array(input_char, this.Utils.whitespace)) { - if (content.length) { - space = true; - } - this.line_char_count--; - continue; //don't want to insert unnecessary space - } - else if (space) { - if (this.line_char_count >= this.max_char) { //insert a line when the max_char is reached - content.push('\n'); - for (var i=0; i', 'igm'); - reg_match.lastIndex = this.pos; - var reg_array = reg_match.exec(this.input); - var end_script = reg_array?reg_array.index:this.input.length; //absolute end of script - if(this.pos < end_script) { //get everything in between the script tags - content = this.input.substring(this.pos, end_script); - this.pos = end_script; - } - return content; - }; - - this.record_tag = function (tag){ //function to record a tag and its parent in this.tags Object - if (this.tags[tag + 'count']) { //check for the existence of this tag type - this.tags[tag + 'count']++; - this.tags[tag + this.tags[tag + 'count']] = this.indent_level; //and record the present indent level - } - else { //otherwise initialize this tag type - this.tags[tag + 'count'] = 1; - this.tags[tag + this.tags[tag + 'count']] = this.indent_level; //and record the present indent level - } - this.tags[tag + this.tags[tag + 'count'] + 'parent'] = this.tags.parent; //set the parent (i.e. in the case of a div this.tags.div1parent) - this.tags.parent = tag + this.tags[tag + 'count']; //and make this the current parent (i.e. in the case of a div 'div1') - }; - - this.retrieve_tag = function (tag) { //function to retrieve the opening tag to the corresponding closer - if (this.tags[tag + 'count']) { //if the openener is not in the Object we ignore it - var temp_parent = this.tags.parent; //check to see if it's a closable tag. - while (temp_parent) { //till we reach '' (the initial value); - if (tag + this.tags[tag + 'count'] === temp_parent) { //if this is it use it - break; - } - temp_parent = this.tags[temp_parent + 'parent']; //otherwise keep on climbing up the DOM Tree - } - if (temp_parent) { //if we caught something - this.indent_level = this.tags[tag + this.tags[tag + 'count']]; //set the indent_level accordingly - this.tags.parent = this.tags[temp_parent + 'parent']; //and set the current parent - } - delete this.tags[tag + this.tags[tag + 'count'] + 'parent']; //delete the closed tags parent reference... - delete this.tags[tag + this.tags[tag + 'count']]; //...and the tag itself - if (this.tags[tag + 'count'] === 1) { - delete this.tags[tag + 'count']; - } - else { - this.tags[tag + 'count']--; - } - } - }; + }; - this.get_tag = function (peek) { //function to get a full tag and parse its type - var input_char = '', - content = [], - comment = '', - space = false, - tag_start, tag_end, - orig_pos = this.pos, - orig_line_char_count = this.line_char_count; - - peek = peek !== undefined ? peek : false; - - do { - if (this.pos >= this.input.length) { - if (peek) { - this.pos = orig_pos; - this.line_char_count = orig_line_char_count; - } - return content.length?content.join(''):['', 'TK_EOF']; + this.traverse_whitespace = function() { + var input_char = ''; + + input_char = this.input.charAt(this.pos); + if (this.Utils.in_array(input_char, this.Utils.whitespace)) { + this.newlines = 0; + while (this.Utils.in_array(input_char, this.Utils.whitespace)) { + if (preserve_newlines && input_char === '\n' && this.newlines <= max_preserve_newlines) { + this.newlines += 1; + } + + this.pos++; + input_char = this.input.charAt(this.pos); + } + return true; + } + return false; } - input_char = this.input.charAt(this.pos); - this.pos++; - this.line_char_count++; + this.get_content = function() { //function to capture regular content between tags + + var input_char = '', + content = [], + space = false; //if a space is needed + + while (this.input.charAt(this.pos) !== '<') { + if (this.pos >= this.input.length) { + return content.length ? content.join('') : ['', 'TK_EOF']; + } + + if (this.traverse_whitespace()) { + if (content.length) { + space = true; + } + continue; //don't want to insert unnecessary space + } + + input_char = this.input.charAt(this.pos); + this.pos++; + + if (space) { + if (this.line_char_count >= this.wrap_line_length) { //insert a line when the wrap_line_length is reached + this.print_newline(false, content); + this.print_indentation(content); + } else { + this.line_char_count++; + content.push(' '); + } + space = false; + } + this.line_char_count++; + content.push(input_char); //letter at-a-time (or string) inserted to an array + } + return content.length ? content.join('') : ''; + }; - if (this.Utils.in_array(input_char, this.Utils.whitespace)) { //don't want to insert unnecessary space - space = true; - this.line_char_count--; - continue; - } + this.get_contents_to = function(name) { //get the full content of a script or style to pass to js_beautify + if (this.pos === this.input.length) { + return ['', 'TK_EOF']; + } + var input_char = ''; + var content = ''; + var reg_match = new RegExp('', 'igm'); + reg_match.lastIndex = this.pos; + var reg_array = reg_match.exec(this.input); + var end_script = reg_array ? reg_array.index : this.input.length; //absolute end of script + if (this.pos < end_script) { //get everything in between the script tags + content = this.input.substring(this.pos, end_script); + this.pos = end_script; + } + return content; + }; - if (input_char === "'" || input_char === '"') { - if (!content[1] || content[1] !== '!') { //if we're in a comment strings don't get treated specially - input_char += this.get_unformatted(input_char); - space = true; - } - } + this.record_tag = function(tag) { //function to record a tag and its parent in this.tags Object + if (this.tags[tag + 'count']) { //check for the existence of this tag type + this.tags[tag + 'count']++; + this.tags[tag + this.tags[tag + 'count']] = this.indent_level; //and record the present indent level + } else { //otherwise initialize this tag type + this.tags[tag + 'count'] = 1; + this.tags[tag + this.tags[tag + 'count']] = this.indent_level; //and record the present indent level + } + this.tags[tag + this.tags[tag + 'count'] + 'parent'] = this.tags.parent; //set the parent (i.e. in the case of a div this.tags.div1parent) + this.tags.parent = tag + this.tags[tag + 'count']; //and make this the current parent (i.e. in the case of a div 'div1') + }; - if (input_char === '=') { //no space before = - space = false; - } + this.retrieve_tag = function(tag) { //function to retrieve the opening tag to the corresponding closer + if (this.tags[tag + 'count']) { //if the openener is not in the Object we ignore it + var temp_parent = this.tags.parent; //check to see if it's a closable tag. + while (temp_parent) { //till we reach '' (the initial value); + if (tag + this.tags[tag + 'count'] === temp_parent) { //if this is it use it + break; + } + temp_parent = this.tags[temp_parent + 'parent']; //otherwise keep on climbing up the DOM Tree + } + if (temp_parent) { //if we caught something + this.indent_level = this.tags[tag + this.tags[tag + 'count']]; //set the indent_level accordingly + this.tags.parent = this.tags[temp_parent + 'parent']; //and set the current parent + } + delete this.tags[tag + this.tags[tag + 'count'] + 'parent']; //delete the closed tags parent reference... + delete this.tags[tag + this.tags[tag + 'count']]; //...and the tag itself + if (this.tags[tag + 'count'] === 1) { + delete this.tags[tag + 'count']; + } else { + this.tags[tag + 'count']--; + } + } + }; - if (content.length && content[content.length-1] !== '=' && input_char !== '>' && space) { - //no space after = or before > - if (this.line_char_count >= this.max_char) { - this.print_newline(false, content); - this.line_char_count = 0; - } - else { - content.push(' '); - this.line_char_count++; - } - space = false; - } - if (input_char === '<') { - tag_start = this.pos - 1; - } - content.push(input_char); //inserts character at-a-time (or string) - } while (input_char !== '>'); - - var tag_complete = content.join(''); - var tag_index; - if (tag_complete.indexOf(' ') !== -1) { //if there's whitespace, thats where the tag name ends - tag_index = tag_complete.indexOf(' '); - } - else { //otherwise go with the tag ending - tag_index = tag_complete.indexOf('>'); - } - var tag_check = tag_complete.substring(1, tag_index).toLowerCase(); - if (tag_complete.charAt(tag_complete.length-2) === '/' || - this.Utils.in_array(tag_check, this.Utils.single_token)) { //if this tag name is a single tag type (either in the list or has a closing /) - if ( ! peek) { - this.tag_type = 'SINGLE'; - } - } - else if (tag_check === 'script') { //for later script handling - if ( ! peek) { - this.record_tag(tag_check); - this.tag_type = 'SCRIPT'; - } - } - else if (tag_check === 'style') { //for future style handling (for now it justs uses get_content) - if ( ! peek) { - this.record_tag(tag_check); - this.tag_type = 'STYLE'; - } - } - else if (this.is_unformatted(tag_check, unformatted)) { // do not reformat the "unformatted" tags - comment = this.get_unformatted('', tag_complete); //...delegate to get_unformatted function - content.push(comment); - // Preserve collapsed whitespace either before or after this tag. - if (tag_start > 0 && this.Utils.in_array(this.input.charAt(tag_start - 1), this.Utils.whitespace)){ - content.splice(0, 0, this.input.charAt(tag_start - 1)); - } - tag_end = this.pos - 1; - if (this.Utils.in_array(this.input.charAt(tag_end + 1), this.Utils.whitespace)){ - content.push(this.input.charAt(tag_end + 1)); - } - this.tag_type = 'SINGLE'; - } - else if (tag_check.charAt(0) === '!' ) { //peek for ', tag_complete); - content.push(comment); - this.tag_type = 'SINGLE'; - } - else if (tag_check.indexOf('![cdata[') === 0) { //if it's a <[cdata[ comment... - comment = this.get_unformatted(']]>', tag_complete); - content.push(comment); - if ( ! peek) { - this.tag_type = 'SINGLE'; //', tag_complete); - content.push(comment); - this.tag_type = 'SINGLE'; - } - else { // even if this isn't a ', tag_complete); - content.push(comment); - this.tag_type = 'SINGLE'; - } - } - else if ( ! peek) { - if (tag_check.charAt(0) === '/') { //this tag is a double tag so check for tag-ending - this.retrieve_tag(tag_check.substring(1)); //remove it and all ancestors - this.tag_type = 'END'; - } - else { //otherwise it's a start-tag - this.record_tag(tag_check); //push it on the tag stack - this.tag_type = 'START'; - } - if (this.Utils.in_array(tag_check, this.Utils.extra_liners)) { //check if this double needs an extra line - this.print_newline(true, this.output); - } - } + this.get_tag = function(peek) { //function to get a full tag and parse its type + var input_char = '', + content = [], + comment = '', + space = false, + tag_start, tag_end, + orig_pos = this.pos, + orig_line_char_count = this.line_char_count; + + peek = peek !== undefined ? peek : false; + + do { + if (this.pos >= this.input.length) { + if (peek) { + this.pos = orig_pos; + this.line_char_count = orig_line_char_count; + } + return content.length ? content.join('') : ['', 'TK_EOF']; + } + + input_char = this.input.charAt(this.pos); + this.pos++; + + if (this.Utils.in_array(input_char, this.Utils.whitespace)) { //don't want to insert unnecessary space + space = true; + continue; + } + + if (input_char === "'" || input_char === '"') { + input_char += this.get_unformatted(input_char); + space = true; + + } + + if (input_char === '=') { //no space before = + space = false; + } + + if (content.length && content[content.length - 1] !== '=' && input_char !== '>' && space) { + //no space after = or before > + if (this.line_char_count >= this.wrap_line_length) { + this.print_newline(false, content); + this.print_indentation(content); + } else { + content.push(' '); + this.line_char_count++; + } + space = false; + } + + if (input_char === '<' && !tag_start) { + tag_start = this.pos - 1; + } + + this.line_char_count++; + content.push(input_char); //inserts character at-a-time (or string) + + if (content[1] && content[1] === '!') { //if we're in a comment, do something special + // We treat all comments as literals, even more than preformatted tags + // we just look for the appropriate close tag + content = [this.get_comment(tag_start)]; + break; + } + + } while (input_char !== '>'); + + var tag_complete = content.join(''); + var tag_index; + if (tag_complete.indexOf(' ') !== -1) { //if there's whitespace, thats where the tag name ends + tag_index = tag_complete.indexOf(' '); + } else { //otherwise go with the tag ending + tag_index = tag_complete.indexOf('>'); + } + var tag_check = tag_complete.substring(1, tag_index).toLowerCase(); + if (tag_complete.charAt(tag_complete.length - 2) === '/' || + this.Utils.in_array(tag_check, this.Utils.single_token)) { //if this tag name is a single tag type (either in the list or has a closing /) + if (!peek) { + this.tag_type = 'SINGLE'; + } + } else if (tag_check === 'script') { //for later script handling + if (!peek) { + this.record_tag(tag_check); + this.tag_type = 'SCRIPT'; + } + } else if (tag_check === 'style') { //for future style handling (for now it justs uses get_content) + if (!peek) { + this.record_tag(tag_check); + this.tag_type = 'STYLE'; + } + } else if (this.is_unformatted(tag_check, unformatted)) { // do not reformat the "unformatted" tags + comment = this.get_unformatted('', tag_complete); //...delegate to get_unformatted function + content.push(comment); + // Preserve collapsed whitespace either before or after this tag. + if (tag_start > 0 && this.Utils.in_array(this.input.charAt(tag_start - 1), this.Utils.whitespace)) { + content.splice(0, 0, this.input.charAt(tag_start - 1)); + } + tag_end = this.pos - 1; + if (this.Utils.in_array(this.input.charAt(tag_end + 1), this.Utils.whitespace)) { + content.push(this.input.charAt(tag_end + 1)); + } + this.tag_type = 'SINGLE'; + } else if (tag_check.charAt(0) === '!') { //peek for ', + matched = false; + + this.pos = start_pos; + input_char = this.input.charAt(this.pos); + this.pos++; + + while (this.pos <= this.input.length) { + comment += input_char; + + // only need to check for the delimiter if the last chars match + if (comment[comment.length - 1] === delimiter[delimiter.length - 1] && + comment.indexOf(delimiter) !== -1) { + break; + } + + // only need to search for custom delimiter for the first few characters + if (!matched && comment.length < 10) { + if (comment.indexOf(''; + matched = true; + } else if (comment.indexOf(''; + matched = true; + } else if (comment.indexOf(''; + matched = true; + } else if (comment.indexOf(''; + matched = true; + } + } + + input_char = this.input.charAt(this.pos); + this.pos++; + } - if (orig_tag && orig_tag.toLowerCase().indexOf(delimiter) !== -1) { - return ''; - } - var input_char = ''; - var content = ''; - var space = true; - do { + return comment; + }; - if (this.pos >= this.input.length) { - return content; - } + this.get_unformatted = function(delimiter, orig_tag) { //function to return unformatted content in its entirety - input_char = this.input.charAt(this.pos); - this.pos++; - - if (this.Utils.in_array(input_char, this.Utils.whitespace)) { - if (!space) { - this.line_char_count--; - continue; - } - if (input_char === '\n' || input_char === '\r') { - content += '\n'; - /* Don't change tab indention for unformatted blocks. If using code for html editing, this will greatly affect
 tags if they are specified in the 'unformatted array'
+                if (orig_tag && orig_tag.toLowerCase().indexOf(delimiter) !== -1) {
+                    return '';
+                }
+                var input_char = '';
+                var content = '';
+                var space = true;
+                do {
+
+                    if (this.pos >= this.input.length) {
+                        return content;
+                    }
+
+                    input_char = this.input.charAt(this.pos);
+                    this.pos++;
+
+                    if (this.Utils.in_array(input_char, this.Utils.whitespace)) {
+                        if (!space) {
+                            this.line_char_count--;
+                            continue;
+                        }
+                        if (input_char === '\n' || input_char === '\r') {
+                            content += '\n';
+                            /*  Don't change tab indention for unformatted blocks.  If using code for html editing, this will greatly affect 
 tags if they are specified in the 'unformatted array'
                 for (var i=0; i]*>\s*$/);
+                //at this point we have an  tag; is its first child something we want to remain
+                //unformatted?
+                var next_tag = this.get_tag(true /* peek. */ );
 
-            // if next_tag comes back but is not an isolated tag, then
-            // let's treat the 'a' tag as having content
-            // and respect the unformatted option
-            if (!tag || this.Utils.in_array(tag, unformatted)){
-                return true;
-            } else {
-                return false;
-            }
-        };
+                // tets next_tag to see if it is just html tag (no external content)
+                var tag = (next_tag || "").match(/^\s*<\s*\/?([a-z]*)\s*[^>]*>\s*$/);
 
-        this.printer = function (js_source, indent_character, indent_size, max_char, brace_style) { //handles input/output and some other printing functions
-
-          this.input = js_source || ''; //gets the input for the Parser
-          this.output = [];
-          this.indent_character = indent_character;
-          this.indent_string = '';
-          this.indent_size = indent_size;
-          this.brace_style = brace_style;
-          this.indent_level = 0;
-          this.max_char = max_char;
-          this.line_char_count = 0; //count to see if max_char was exceeded
-
-          for (var i=0; i 0) {
-              this.indent_level--;
-            }
-          };
-        };
-        return this;
-      }
+                for (var i = 0; i < this.indent_size; i++) {
+                    this.indent_string += this.indent_character;
+                }
 
-      /*_____________________--------------------_____________________*/
+                this.print_newline = function(force, arr) {
+                    this.line_char_count = 0;
+                    if (!arr || !arr.length) {
+                        return;
+                    }
+                    if (force || (arr[arr.length - 1] !== '\n')) { //we might want the extra line
+                        arr.push('\n');
+                    }
+                };
+
+                this.print_indentation = function(arr) {
+                    for (var i = 0; i < this.indent_level; i++) {
+                        arr.push(this.indent_string);
+                        this.line_char_count += this.indent_string.length;
+                    }
+                };
+
+                this.print_token = function(text) {
+                    if (text || text !== '') {
+                        if (this.output.length && this.output[this.output.length - 1] === '\n') {
+                            this.print_indentation(this.output);
+                            text = ltrim(text);
+                        }
+                    }
+                    this.print_token_raw(text);
+                };
+
+                this.print_token_raw = function(text) {
+                    if (text && text !== '') {
+                        if (text.length > 1 && text[text.length - 1] === '\n') {
+                            // unformatted tags can grab newlines as their last character
+                            this.output.push(text.slice(0, -1));
+                            this.print_newline(false, this.output);
+                        } else {
+                            this.output.push(text);
+                        }
+                    }
+
+                    for (var n = 0; n < this.newlines; n++) {
+                        this.print_newline(n > 0, this.output);
+                    }
+                    this.newlines = 0;
+                };
+
+                this.indent = function() {
+                    this.indent_level++;
+                };
+
+                this.unindent = function() {
+                    if (this.indent_level > 0) {
+                        this.indent_level--;
+                    }
+                };
+            };
+            return this;
+        }
 
-      multi_parser = new Parser(); //wrapping functions Parser
-      multi_parser.printer(html_source, indent_character, indent_size, max_char, brace_style); //initialize starting values
+        /*_____________________--------------------_____________________*/
 
-      while (true) {
-          var t = multi_parser.get_token();
-          multi_parser.token_text = t[0];
-          multi_parser.token_type = t[1];
+        multi_parser = new Parser(); //wrapping functions Parser
+        multi_parser.printer(html_source, indent_character, indent_size, wrap_line_length, brace_style); //initialize starting values
 
-        if (multi_parser.token_type === 'TK_EOF') {
-          break;
-        }
+        while (true) {
+            var t = multi_parser.get_token();
+            multi_parser.token_text = t[0];
+            multi_parser.token_type = t[1];
 
-        switch (multi_parser.token_type) {
-          case 'TK_TAG_START':
-            multi_parser.print_newline(false, multi_parser.output);
-            multi_parser.print_token(multi_parser.token_text);
-            multi_parser.indent();
-            multi_parser.current_mode = 'CONTENT';
-            break;
-          case 'TK_TAG_STYLE':
-          case 'TK_TAG_SCRIPT':
-            multi_parser.print_newline(false, multi_parser.output);
-            multi_parser.print_token(multi_parser.token_text);
-            multi_parser.current_mode = 'CONTENT';
-            break;
-          case 'TK_TAG_END':
-            //Print new line only if the tag has no content and has child
-            if (multi_parser.last_token === 'TK_CONTENT' && multi_parser.last_text === '') {
-                var tag_name = multi_parser.token_text.match(/\w+/)[0];
-                var tag_extracted_from_last_output = multi_parser.output[multi_parser.output.length -1].match(/<\s*(\w+)/);
-                if (tag_extracted_from_last_output === null || tag_extracted_from_last_output[1] !== tag_name) {
-                    multi_parser.print_newline(true, multi_parser.output);
-                }
-            }
-            multi_parser.print_token(multi_parser.token_text);
-            multi_parser.current_mode = 'CONTENT';
-            break;
-          case 'TK_TAG_SINGLE':
-            // Don't add a newline before elements that should remain unformatted.
-            var tag_check = multi_parser.token_text.match(/^\s*<([a-z]+)/i);
-            if (!tag_check || !multi_parser.Utils.in_array(tag_check[1], unformatted)){
-                multi_parser.print_newline(false, multi_parser.output);
-            }
-            multi_parser.print_token(multi_parser.token_text);
-            multi_parser.current_mode = 'CONTENT';
-            break;
-          case 'TK_CONTENT':
-            if (multi_parser.token_text !== '') {
-              multi_parser.print_token(multi_parser.token_text);
-            }
-            multi_parser.current_mode = 'TAG';
-            break;
-          case 'TK_STYLE':
-          case 'TK_SCRIPT':
-            if (multi_parser.token_text !== '') {
-              multi_parser.output.push('\n');
-              var text = multi_parser.token_text,
-                  _beautifier,
-                  script_indent_level = 1;
-              if (multi_parser.token_type === 'TK_SCRIPT') {
-                _beautifier = typeof js_beautify === 'function' && js_beautify;
-              } else if (multi_parser.token_type === 'TK_STYLE') {
-                _beautifier = typeof css_beautify === 'function' && css_beautify;
-              }
-
-              if (options.indent_scripts === "keep") {
-                script_indent_level = 0;
-              } else if (options.indent_scripts === "separate") {
-                script_indent_level = -multi_parser.indent_level;
-              }
-
-              var indentation = multi_parser.get_full_indent(script_indent_level);
-              if (_beautifier) {
-                // call the Beautifier if avaliable
-                text = _beautifier(text.replace(/^\s*/, indentation), options);
-              } else {
-                // simply indent the string otherwise
-                var white = text.match(/^\s*/)[0];
-                var _level = white.match(/[^\n\r]*$/)[0].split(multi_parser.indent_string).length - 1;
-                var reindent = multi_parser.get_full_indent(script_indent_level -_level);
-                text = text.replace(/^\s*/, indentation)
-                       .replace(/\r\n|\r|\n/g, '\n' + reindent)
-                       .replace(/\s*$/, '');
-              }
-              if (text) {
-                multi_parser.print_token(text);
-                multi_parser.print_newline(true, multi_parser.output);
-              }
+            if (multi_parser.token_type === 'TK_EOF') {
+                break;
             }
-            multi_parser.current_mode = 'TAG';
-            break;
+
+            switch (multi_parser.token_type) {
+                case 'TK_TAG_START':
+                    multi_parser.print_newline(false, multi_parser.output);
+                    multi_parser.print_token(multi_parser.token_text);
+                    if (multi_parser.indent_content) {
+                        multi_parser.indent();
+                        multi_parser.indent_content = false;
+                    }
+                    multi_parser.current_mode = 'CONTENT';
+                    break;
+                case 'TK_TAG_STYLE':
+                case 'TK_TAG_SCRIPT':
+                    multi_parser.print_newline(false, multi_parser.output);
+                    multi_parser.print_token(multi_parser.token_text);
+                    multi_parser.current_mode = 'CONTENT';
+                    break;
+                case 'TK_TAG_END':
+                    //Print new line only if the tag has no content and has child
+                    if (multi_parser.last_token === 'TK_CONTENT' && multi_parser.last_text === '') {
+                        var tag_name = multi_parser.token_text.match(/\w+/)[0];
+                        var tag_extracted_from_last_output = multi_parser.output[multi_parser.output.length - 1].match(/<\s*(\w+)/);
+                        if (tag_extracted_from_last_output === null ||
+                            tag_extracted_from_last_output[1] !== tag_name) {
+                            multi_parser.print_newline(false, multi_parser.output);
+                        }
+                    }
+                    multi_parser.print_token(multi_parser.token_text);
+                    multi_parser.current_mode = 'CONTENT';
+                    break;
+                case 'TK_TAG_SINGLE':
+                    // Don't add a newline before elements that should remain unformatted.
+                    var tag_check = multi_parser.token_text.match(/^\s*<([a-z]+)/i);
+                    if (!tag_check || !multi_parser.Utils.in_array(tag_check[1], unformatted)) {
+                        multi_parser.print_newline(false, multi_parser.output);
+                    }
+                    multi_parser.print_token(multi_parser.token_text);
+                    multi_parser.current_mode = 'CONTENT';
+                    break;
+                case 'TK_CONTENT':
+                    multi_parser.print_token(multi_parser.token_text);
+                    multi_parser.current_mode = 'TAG';
+                    break;
+                case 'TK_STYLE':
+                case 'TK_SCRIPT':
+                    if (multi_parser.token_text !== '') {
+                        multi_parser.print_newline(false, multi_parser.output);
+                        var text = multi_parser.token_text,
+                            _beautifier,
+                            script_indent_level = 1;
+                        if (multi_parser.token_type === 'TK_SCRIPT') {
+                            _beautifier = typeof js_beautify === 'function' && js_beautify;
+                        } else if (multi_parser.token_type === 'TK_STYLE') {
+                            _beautifier = typeof css_beautify === 'function' && css_beautify;
+                        }
+
+                        if (options.indent_scripts === "keep") {
+                            script_indent_level = 0;
+                        } else if (options.indent_scripts === "separate") {
+                            script_indent_level = -multi_parser.indent_level;
+                        }
+
+                        var indentation = multi_parser.get_full_indent(script_indent_level);
+                        if (_beautifier) {
+                            // call the Beautifier if avaliable
+                            text = _beautifier(text.replace(/^\s*/, indentation), options);
+                        } else {
+                            // simply indent the string otherwise
+                            var white = text.match(/^\s*/)[0];
+                            var _level = white.match(/[^\n\r]*$/)[0].split(multi_parser.indent_string).length - 1;
+                            var reindent = multi_parser.get_full_indent(script_indent_level - _level);
+                            text = text.replace(/^\s*/, indentation)
+                                .replace(/\r\n|\r|\n/g, '\n' + reindent)
+                                .replace(/\s+$/, '');
+                        }
+                        if (text) {
+                            multi_parser.print_token_raw(indentation + trim(text));
+                            multi_parser.print_newline(false, multi_parser.output);
+                        }
+                    }
+                    multi_parser.current_mode = 'TAG';
+                    break;
+            }
+            multi_parser.last_token = multi_parser.token_type;
+            multi_parser.last_text = multi_parser.token_text;
         }
-        multi_parser.last_token = multi_parser.token_type;
-        multi_parser.last_text = multi_parser.token_text;
-      }
-      return multi_parser.output.join('');
+        return multi_parser.output.join('');
     }
 
     if (typeof define === "function") {
         // Add support for require.js
         define(function(require, exports, module) {
-            var js_beautify  = require('./beautify.js').js_beautify;
+            var js_beautify = require('./beautify.js').js_beautify;
             var css_beautify = require('./beautify-css.js').css_beautify;
 
             exports.html_beautify = function(html_source, options) {
@@ -620,7 +717,7 @@
     } else if (typeof exports !== "undefined") {
         // Add support for CommonJS. Just put this file somewhere on your require.paths
         // and you will be able to `var html_beautify = require("beautify").html_beautify`.
-        var js_beautify  = require('./beautify.js').js_beautify;
+        var js_beautify = require('./beautify.js').js_beautify;
         var css_beautify = require('./beautify-css.js').css_beautify;
 
         exports.html_beautify = function(html_source, options) {
diff --git a/js/lib/beautify.js b/js/lib/beautify.js
index 3dc9d7846..9cf1f7823 100644
--- a/js/lib/beautify.js
+++ b/js/lib/beautify.js
@@ -83,12 +83,12 @@
 
     function Beautifier(js_source_text, options) {
         "use strict";
-        var input, output, token_text, token_type, last_type, last_last_text, indent_string;
+        var input, output_lines;
+        var token_text, token_type, last_type, last_last_text, indent_string;
         var flags, previous_flags, flag_store;
         var whitespace, wordchar, punct, parser_pos, line_starters, digits;
         var prefix;
         var input_wanted_newline;
-        var line_indent_level;
         var output_wrapped, output_space_before_token;
         var input_length, n_newlines, whitespace_before_token;
         var handlers, MODE, opt;
@@ -134,7 +134,16 @@
         };
 
         function create_flags(flags_base, mode) {
-            var next_indent_level =  (flags_base ? flags_base.indentation_level + ((flags_base.var_line && flags_base.var_line_reindented) ? 1 : 0) : 0);
+            var next_indent_level = 0;
+            if (flags_base) {
+                next_indent_level = flags_base.indentation_level;
+                next_indent_level += (flags_base.var_line && flags_base.var_line_reindented) ? 1 : 0;
+                if (!just_added_newline() &&
+                    flags_base.line_indent_level > next_indent_level) {
+                    next_indent_level = flags_base.line_indent_level;
+                }
+            }
+
             var next_flags = {
                 mode: mode,
                 parent: flags_base,
@@ -144,7 +153,7 @@
                 var_line_tainted: false,
                 var_line_reindented: false,
                 in_html_comment: false,
-                multiline_array: false,
+                multiline_frame: false,
                 if_block: false,
                 do_block: false,
                 do_while: false,
@@ -152,11 +161,21 @@
                 in_case: false, // we're on the exact line with "case 0:"
                 case_body: false, // the indented case-action block
                 indentation_level: next_indent_level,
+                line_indent_level: flags_base ? flags_base.line_indent_level : next_indent_level,
+                start_line_index: output_lines.length,
                 ternary_depth: 0
             }
             return next_flags;
         }
 
+        // Using object instead of string to allow for later expansion of info about each line
+
+        function create_output_line() {
+            return {
+                text: []
+            };
+        }
+
         // Some interpreters have unexpected results with foo = baz || bar;
         options = options ? options : {};
         opt = {};
@@ -184,7 +203,7 @@
         opt.space_in_paren = (options.space_in_paren === undefined) ? false : options.space_in_paren;
         opt.jslint_happy = (options.jslint_happy === undefined) ? false : options.jslint_happy;
         opt.keep_array_indentation = (options.keep_array_indentation === undefined) ? false : options.keep_array_indentation;
-        opt.space_before_conditional= (options.space_before_conditional === undefined) ? true : options.space_before_conditional;
+        opt.space_before_conditional = (options.space_before_conditional === undefined) ? true : options.space_before_conditional;
         opt.unescape_strings = (options.unescape_strings === undefined) ? false : options.unescape_strings;
         opt.wrap_line_length = (options.wrap_line_length === undefined) ? 0 : parseInt(options.wrap_line_length, 10);
         opt.e4x = (options.e4x === undefined) ? false : options.e4x;
@@ -207,8 +226,7 @@
 
         last_type = 'TK_START_BLOCK'; // last token type
         last_last_text = ''; // pre-last token text
-        line_indent_level = 0;
-        output = [];
+        output_lines = [create_output_line()];
         output_wrapped = false;
         output_space_before_token = false;
         whitespace_before_token = [];
@@ -228,7 +246,7 @@
 
         parser_pos = 0;
 
-        this.beautify = function () {
+        this.beautify = function() {
             /*jshint onevar:true */
             var t, i, keep_whitespace, sweet_code;
 
@@ -276,19 +294,40 @@
                 }
             }
 
-            sweet_code = preindent_string + output.join('').replace(/[\r\n ]+$/, '');
+
+            sweet_code = output_lines[0].text.join('');
+            for (var line_index = 1; line_index < output_lines.length; line_index++) {
+                sweet_code += '\n' + output_lines[line_index].text.join('');
+            }
+            sweet_code = sweet_code.replace(/[\r\n ]+$/, '');
             return sweet_code;
         };
 
         function trim_output(eat_newlines) {
             eat_newlines = (eat_newlines === undefined) ? false : eat_newlines;
-            while (output.length && (output[output.length - 1] === ' ' || output[output.length - 1] === indent_string || output[output.length - 1] === preindent_string || (eat_newlines && (output[output.length - 1] === '\n' || output[output.length - 1] === '\r')))) {
-                output.pop();
+
+            if (output_lines.length) {
+                trim_output_line(output_lines[output_lines.length - 1], eat_newlines);
+
+                while (eat_newlines && output_lines.length > 1 &&
+                    output_lines[output_lines.length - 1].text.length === 0) {
+                    output_lines.pop();
+                    trim_output_line(output_lines[output_lines.length - 1], eat_newlines);
+                }
+            }
+        }
+
+        function trim_output_line(line) {
+            while (line.text.length &&
+                (line.text[line.text.length - 1] === ' ' ||
+                    line.text[line.text.length - 1] === indent_string ||
+                    line.text[line.text.length - 1] === preindent_string)) {
+                line.text.pop();
             }
         }
 
         function trim(s) {
-            return s.replace(/^\s\s*|\s\s*$/, '');
+            return s.replace(/^\s+|\s+$/g, '');
         }
 
         // we could use just string.split, but
@@ -312,39 +351,30 @@
         }
 
         function just_added_newline() {
-            return output.length && output[output.length - 1] === "\n";
+            var line = output_lines[output_lines.length - 1];
+            return line.text.length === 0;
         }
 
         function just_added_blankline() {
-            return just_added_newline() && output.length - 1 > 0 && output[output.length - 2] === "\n";
-        }
-
-        function _last_index_of(arr, find) {
-            var i = arr.length - 1;
-            if (i < 0) {
-                i += arr.length;
-            }
-            if (i > arr.length - 1) {
-                i = arr.length - 1;
-            }
-            for (i++; i-- > 0;) {
-                if (i in arr && arr[i] === find) {
-                    return i;
+            if (just_added_newline()) {
+                if (output_lines.length === 1) {
+                    return true; // start of the file and newline = blank
                 }
+
+                var line = output_lines[output_lines.length - 2];
+                return line.text.length === 0;
             }
-            return -1;
+            return false;
         }
 
         function allow_wrap_or_preserved_newline(force_linewrap) {
             force_linewrap = (force_linewrap === undefined) ? false : force_linewrap;
             if (opt.wrap_line_length && !force_linewrap) {
-                var current_line = '';
+                var line = output_lines[output_lines.length - 1];
                 var proposed_line_length = 0;
-                var start_line = _last_index_of(output, '\n') + 1;
                 // never wrap the first token of a line.
-                if (start_line < output.length) {
-                    current_line = output.slice(start_line).join('');
-                    proposed_line_length = current_line.length + token_text.length +
+                if (line.text.length > 0) {
+                    proposed_line_length = line.text.join('').length + token_text.length +
                         (output_space_before_token ? 1 : 0);
                     if (proposed_line_length >= opt.wrap_line_length) {
                         force_linewrap = true;
@@ -355,7 +385,7 @@
                 print_newline(false, true);
 
                 // Expressions and array literals already indent their contents.
-                if(! (is_array(flags.mode) || is_expression(flags.mode))) {
+                if (!(is_array(flags.mode) || is_expression(flags.mode))) {
                     output_wrapped = true;
                 }
             }
@@ -366,36 +396,35 @@
             output_space_before_token = false;
 
             if (!preserve_statement_flags) {
-                if  (flags.last_text !== ';') {
+                if (flags.last_text !== ';') {
                     while (flags.mode === MODE.Statement && !flags.if_block && !flags.do_block) {
                         restore_mode();
                     }
                 }
             }
 
-            if (flags.mode === MODE.ArrayLiteral) {
-                flags.multiline_array = true;
-            }
-
-
-            if (!output.length) {
+            if (output_lines.length === 1 && just_added_newline()) {
                 return; // no newline on start of file
             }
 
             if (force_newline || !just_added_newline()) {
-                output.push("\n");
+                flags.multiline_frame = true;
+                output_lines.push(create_output_line());
             }
         }
 
         function print_token_line_indentation() {
             if (just_added_newline()) {
+                var line = output_lines[output_lines.length - 1];
                 if (opt.keep_array_indentation && is_array(flags.mode) && input_wanted_newline) {
+                    // prevent removing of this whitespace as redundant
+                    line.text.push('');
                     for (var i = 0; i < whitespace_before_token.length; i += 1) {
-                        output.push(whitespace_before_token[i]);
+                        line.text.push(whitespace_before_token[i]);
                     }
                 } else {
                     if (preindent_string) {
-                        output.push(preindent_string);
+                        line.text.push(preindent_string);
                     }
 
                     print_indent_string(flags.indentation_level +
@@ -407,19 +436,22 @@
 
         function print_indent_string(level) {
             // Never indent your first output indent at the start of the file
-            if (flags.last_text !== '') {
-                line_indent_level = level;
+            if (output_lines.length > 1) {
+                var line = output_lines[output_lines.length - 1];
+
+                flags.line_indent_level = level;
                 for (var i = 0; i < level; i += 1) {
-                    output.push(indent_string);
+                    line.text.push(indent_string);
                 }
             }
         }
 
         function print_token_space_before() {
-            if (output_space_before_token && output.length) {
-                var last_output = output[output.length - 1];
-                if (!just_added_newline() && last_output !== ' ' && last_output !== indent_string) { // prevent occassional duplicate space
-                    output.push(' ');
+            var line = output_lines[output_lines.length - 1];
+            if (output_space_before_token && line.text.length) {
+                var last_output = line.text[line.text.length - 1];
+                if (last_output !== ' ' && last_output !== indent_string) { // prevent occassional duplicate space
+                    line.text.push(' ');
                 }
             }
         }
@@ -430,7 +462,7 @@
             output_wrapped = false;
             print_token_space_before();
             output_space_before_token = false;
-            output.push(printable_token);
+            output_lines[output_lines.length - 1].text.push(printable_token);
         }
 
         function indent() {
@@ -443,6 +475,43 @@
                 flags.indentation_level -= 1;
         }
 
+        function remove_redundant_indentation(frame) {
+            // This implementation is effective but has some issues:
+            //     - less than great performance due to array splicing
+            //     - can cause line wrap to happen too soon due to indent removal
+            //           after wrap points are calculated
+            // These issues are minor compared to ugly indentation.
+
+            if (frame.multiline_frame) return;
+
+            // remove one indent from each line inside this section
+            var index = frame.start_line_index;
+            var splice_index = 0;
+            var line;
+
+            while (index < output_lines.length) {
+                line = output_lines[index];
+                index++;
+
+                // skip empty lines
+                if (line.text.length === 0) {
+                    continue;
+                }
+
+                // skip the preindent string if present
+                if (preindent_string && line.text[0] === preindent_string) {
+                    splice_index = 1;
+                } else {
+                    splice_index = 0;
+                }
+
+                // remove one indent, if present
+                if (line.text[splice_index] === indent_string) {
+                    line.text.splice(splice_index, 1);
+                }
+            }
+        }
+
         function set_mode(mode) {
             if (flags) {
                 flag_store.push(flags);
@@ -471,9 +540,9 @@
 
         function start_of_statement() {
             if (
-             (flags.last_text === 'do' ||
-                 (flags.last_text === 'else' && token_text !== 'if') ||
-                (last_type === 'TK_END_EXPR' && (previous_flags.mode === MODE.ForInitializer || previous_flags.mode === MODE.Conditional)))) {
+                (flags.last_text === 'do' ||
+                    (flags.last_text === 'else' && token_text !== 'if') ||
+                    (last_type === 'TK_END_EXPR' && (previous_flags.mode === MODE.ForInitializer || previous_flags.mode === MODE.Conditional)))) {
                 // Issue #276:
                 // If starting a new statement with [if, for, while, do], push to a new line.
                 // if (a) if (b) if(c) d(); else e(); else f();
@@ -484,7 +553,7 @@
                 // Issue #275:
                 // If starting on a newline, all of a statement should be indented.
                 // if not, use line wrapping logic for indent.
-                if(just_added_newline()) {
+                if (just_added_newline()) {
                     indent();
                     output_wrapped = false;
                 }
@@ -722,12 +791,13 @@
             if (c === "'" || c === '"' || // string
                 (
                     (c === '/') || // regexp
-                    (opt.e4x && c ==="<" && input.slice(parser_pos - 1).match(/^<([-a-zA-Z:0-9_.]+|{[^{}]*}|!\[CDATA\[[\s\S]*?\]\])\s*([-a-zA-Z:0-9_.]+=('[^']*'|"[^"]*"|{[^{}]*})\s*)*\/?\s*>/)) // xml
+                    (opt.e4x && c === "<" && input.slice(parser_pos - 1).match(/^<([-a-zA-Z:0-9_.]+|{[^{}]*}|!\[CDATA\[[\s\S]*?\]\])\s*([-a-zA-Z:0-9_.]+=('[^']*'|"[^"]*"|{[^{}]*})\s*)*\/?\s*>/)) // xml
                 ) && ( // regex and xml can only appear in specific locations during parsing
-                    (last_type === 'TK_WORD' && is_special_word (flags.last_text)) ||
+                    (last_type === 'TK_WORD' && is_special_word(flags.last_text)) ||
                     (last_type === 'TK_END_EXPR' && in_array(previous_flags.mode, [MODE.Conditional, MODE.ForInitializer])) ||
                     (in_array(last_type, ['TK_COMMENT', 'TK_START_EXPR', 'TK_START_BLOCK',
-                    'TK_END_BLOCK', 'TK_OPERATOR', 'TK_EQUALS', 'TK_EOF', 'TK_SEMICOLON', 'TK_COMMA']))
+                        'TK_END_BLOCK', 'TK_OPERATOR', 'TK_EQUALS', 'TK_EOF', 'TK_SEMICOLON', 'TK_COMMA'
+                    ]))
                 )) {
 
                 var sep = c,
@@ -774,7 +844,7 @@
                             while (match) {
                                 var isEndTag = !! match[1];
                                 var tagName = match[2];
-                                var isSingletonTag = (!! match[match.length - 1]) || (tagName.slice(0, 8) === "![CDATA[");
+                                var isSingletonTag = ( !! match[match.length - 1]) || (tagName.slice(0, 8) === "![CDATA[");
                                 if (tagName === rootTag && !isSingletonTag) {
                                     if (isEndTag) {
                                         --depth;
@@ -836,7 +906,8 @@
             if (c === '#') {
 
 
-                if (output.length === 0 && input.charAt(parser_pos) === '!') {
+                if (output_lines.length === 1 && output_lines[0].text.length === 0 &&
+                    input.charAt(parser_pos) === '!') {
                     // shebang
                     resulting_string = c;
                     while (parser_pos < input_length && c !== '\n') {
@@ -919,22 +990,25 @@
                 // The conditional starts the statement if appropriate.
             }
 
+            var next_mode = MODE.Expression;
             if (token_text === '[') {
 
                 if (last_type === 'TK_WORD' || flags.last_text === ')') {
                     // this is array index specifier, break immediately
                     // a[x], fn()[x]
-                    if (in_array (flags.last_text, line_starters)) {
+                    if (in_array(flags.last_text, line_starters)) {
                         output_space_before_token = true;
                     }
-                    set_mode(MODE.Expression);
+                    set_mode(next_mode);
                     print_token();
+                    indent();
                     if (opt.space_in_paren) {
                         output_space_before_token = true;
                     }
                     return;
                 }
 
+                next_mode = MODE.ArrayLiteral;
                 if (is_array(flags.mode)) {
                     if (flags.last_text === '[' ||
                         (flags.last_text === ',' && (last_last_text === ']' || last_last_text === '}'))) {
@@ -947,21 +1021,21 @@
                 }
 
             } else {
-                if  (flags.last_text === 'for') {
-                    set_mode(MODE.ForInitializer);
-                } else if (in_array (flags.last_text, ['if', 'while'])) {
-                    set_mode(MODE.Conditional);
+                if (flags.last_text === 'for') {
+                    next_mode = MODE.ForInitializer;
+                } else if (in_array(flags.last_text, ['if', 'while'])) {
+                    next_mode = MODE.Conditional;
                 } else {
-                    set_mode(MODE.Expression);
+                    // next_mode = MODE.Expression;
                 }
             }
 
-            if  (flags.last_text === ';' || last_type === 'TK_START_BLOCK') {
+            if (flags.last_text === ';' || last_type === 'TK_START_BLOCK') {
                 print_newline();
             } else if (last_type === 'TK_END_EXPR' || last_type === 'TK_START_EXPR' || last_type === 'TK_END_BLOCK' || flags.last_text === '.') {
-                if (input_wanted_newline) {
-                    print_newline();
-                }
+                // TODO: Consider whether forcing this is required.  Review failing tests when removed.
+                allow_wrap_or_preserved_newline(input_wanted_newline);
+                output_wrapped = false;
                 // do nothing on (( and )( and ][ and ]( and .(
             } else if (last_type !== 'TK_WORD' && last_type !== 'TK_OPERATOR') {
                 output_space_before_token = true;
@@ -970,7 +1044,7 @@
                 if (opt.jslint_happy) {
                     output_space_before_token = true;
                 }
-            } else if (in_array (flags.last_text, line_starters) || flags.last_text === 'catch') {
+            } else if (in_array(flags.last_text, line_starters) || flags.last_text === 'catch') {
                 if (opt.space_before_conditional) {
                     output_space_before_token = true;
                 }
@@ -986,13 +1060,11 @@
                     }
                 }
             }
-            if (token_text === '[') {
-                set_mode(MODE.ArrayLiteral);
-            }
 
+            set_mode(next_mode);
             print_token();
             if (opt.space_in_paren) {
-                    output_space_before_token = true;
+                output_space_before_token = true;
             }
 
             // In all cases, if we newline while inside an expression it should be indented.
@@ -1006,11 +1078,15 @@
                 restore_mode();
             }
 
-            if (token_text === ']' && is_array(flags.mode) && flags.multiline_array && !opt.keep_array_indentation) {
+            if (token_text === ']' && is_array(flags.mode) && flags.multiline_frame && !opt.keep_array_indentation) {
                 print_newline();
             }
+
+            if (flags.multiline_frame) {
+                allow_wrap_or_preserved_newline();
+            }
             if (opt.space_in_paren) {
-                    output_space_before_token = true;
+                output_space_before_token = true;
             }
             if (token_text === ']' && opt.keep_array_indentation) {
                 print_token();
@@ -1019,6 +1095,7 @@
                 restore_mode();
                 print_token();
             }
+            remove_redundant_indentation(previous_flags);
 
             // do {} while () // no statement required after
             if (flags.do_while && previous_flags.mode === MODE.Conditional) {
@@ -1040,8 +1117,8 @@
                 if (last_type !== 'TK_OPERATOR' &&
                     (empty_anonymous_function ||
                         last_type === 'TK_EQUALS' ||
-                        (is_special_word (flags.last_text) && flags.last_text !== 'else'))) {
-                        output_space_before_token = true;
+                        (is_special_word(flags.last_text) && flags.last_text !== 'else'))) {
+                    output_space_before_token = true;
                 } else {
                     print_newline();
                 }
@@ -1073,11 +1150,10 @@
             while (flags.mode === MODE.Statement) {
                 restore_mode();
             }
-            restore_mode();
             var empty_braces = last_type === 'TK_START_BLOCK';
 
             if (opt.brace_style === "expand") {
-                if  (!empty_braces) {
+                if (!empty_braces) {
                     print_newline();
                 }
             } else {
@@ -1094,6 +1170,7 @@
                     }
                 }
             }
+            restore_mode();
             print_token();
         }
 
@@ -1136,6 +1213,19 @@
                 }
             }
 
+            if (token_text === 'case' || (token_text === 'default' && flags.in_case_statement)) {
+                print_newline();
+                if (flags.case_body || opt.jslint_happy) {
+                    // switch cases following one another
+                    deindent();
+                    flags.case_body = false;
+                }
+                print_token();
+                flags.in_case = true;
+                flags.in_case_statement = true;
+                return;
+            }
+
             if (token_text === 'function') {
                 if (flags.var_line && last_type !== 'TK_EQUALS') {
                     flags.var_line_reindented = true;
@@ -1144,16 +1234,13 @@
                     flags.last_text !== '{' && !is_array(flags.mode)) {
                     // make sure there is a nice clean space of at least one blank line
                     // before a new function definition, except in arrays
-                    if(!just_added_newline()) {
-                        print_newline(true);
-                    }
-
-                    if(!just_added_blankline()) {
+                    if (!just_added_blankline()) {
+                        print_newline();
                         print_newline(true);
                     }
                 }
                 if (last_type === 'TK_WORD') {
-                    if  (flags.last_text === 'get' || flags.last_text === 'set' || flags.last_text === 'new' || flags.last_text === 'return') {
+                    if (flags.last_text === 'get' || flags.last_text === 'set' || flags.last_text === 'new' || flags.last_text === 'return') {
                         output_space_before_token = true;
                     } else {
                         print_newline();
@@ -1166,31 +1253,17 @@
                 } else {
                     print_newline();
                 }
+            }
 
-                if (is_expression(flags.mode)) {
-                    // Issue #274
-                    // (function inside expression that is not nested.
-                    if(!(is_expression(flags.parent.mode) || is_array(flags.parent.mode)) &&
-                        line_indent_level < flags.indentation_level) {
-                        deindent();
-                    }
+            if (last_type === 'TK_COMMA' || last_type === 'TK_START_EXPR' || last_type === 'TK_EQUALS' || last_type === 'TK_OPERATOR') {
+                if (flags.mode !== MODE.ObjectLiteral) {
+                    allow_wrap_or_preserved_newline();
                 }
-
-                print_token();
-                flags.last_word = token_text;
-                return;
             }
 
-            if (token_text === 'case' || (token_text === 'default' && flags.in_case_statement)) {
-                print_newline();
-                if (flags.case_body || opt.jslint_happy) {
-                    // switch cases following one another
-                    deindent();
-                    flags.case_body = false;
-                }
+            if (token_text === 'function') {
                 print_token();
-                flags.in_case = true;
-                flags.in_case_statement = true;
+                flags.last_word = token_text;
                 return;
             }
 
@@ -1224,7 +1297,7 @@
             }
 
             if (in_array(token_text, line_starters) && flags.last_text !== ')') {
-                if  (flags.last_text === 'else') {
+                if (flags.last_text === 'else') {
                     prefix = 'SPACE';
                 } else {
                     prefix = 'NEWLINE';
@@ -1232,26 +1305,21 @@
 
             }
 
-            if (last_type === 'TK_COMMA' || last_type === 'TK_START_EXPR' || last_type === 'TK_EQUALS' || last_type === 'TK_OPERATOR') {
-                if (flags.mode !== MODE.ObjectLiteral) {
-                    allow_wrap_or_preserved_newline();
-                }
-            }
-
             if (in_array(token_text, ['else', 'catch', 'finally'])) {
                 if (last_type !== 'TK_END_BLOCK' || opt.brace_style === "expand" || opt.brace_style === "end-expand") {
                     print_newline();
                 } else {
                     trim_output(true);
+                    var line = output_lines[output_lines.length - 1];
                     // If we trimmed and there's something other than a close block before us
                     // put a newline back in.  Handles '} // comment' scenario.
-                    if (output[output.length - 1] !== '}') {
+                    if (line.text[line.text.length - 1] !== '}') {
                         print_newline();
                     }
                     output_space_before_token = true;
                 }
             } else if (prefix === 'NEWLINE') {
-                if (is_special_word (flags.last_text)) {
+                if (is_special_word(flags.last_text)) {
                     // no newline between 'return nnn'
                     output_space_before_token = true;
                 } else if (last_type !== 'TK_END_EXPR') {
@@ -1384,7 +1452,7 @@
         function handle_operator() {
             var space_before = true;
             var space_after = true;
-            if (is_special_word (flags.last_text)) {
+            if (is_special_word(flags.last_text)) {
                 // "return" had a special handling in TK_WORD. Now we need to return the favor
                 output_space_before_token = true;
                 print_token();
@@ -1418,23 +1486,23 @@
                 print_newline();
             }
 
-            if (in_array(token_text, ['--', '++', '!']) || (in_array(token_text, ['-', '+']) && (in_array(last_type, ['TK_START_BLOCK', 'TK_START_EXPR', 'TK_EQUALS', 'TK_OPERATOR']) || in_array (flags.last_text, line_starters) || flags.last_text === ','))) {
+            if (in_array(token_text, ['--', '++', '!']) || (in_array(token_text, ['-', '+']) && (in_array(last_type, ['TK_START_BLOCK', 'TK_START_EXPR', 'TK_EQUALS', 'TK_OPERATOR']) || in_array(flags.last_text, line_starters) || flags.last_text === ','))) {
                 // unary operators (and binary +/- pretending to be unary) special cases
 
                 space_before = false;
                 space_after = false;
 
-                if  (flags.last_text === ';' && is_expression(flags.mode)) {
+                if (flags.last_text === ';' && is_expression(flags.mode)) {
                     // for (;; ++i)
                     //        ^^^
                     space_before = true;
                 }
 
-                if (last_type === 'TK_WORD' && in_array (flags.last_text, line_starters)) {
+                if (last_type === 'TK_WORD' && in_array(flags.last_text, line_starters)) {
                     space_before = true;
                 }
 
-                if ((flags.mode === MODE.BlockStatement || flags.mode === MODE.Statement) &&  (flags.last_text === '{' || flags.last_text === ';')) {
+                if ((flags.mode === MODE.BlockStatement || flags.mode === MODE.Statement) && (flags.last_text === '{' || flags.last_text === ';')) {
                     // { foo; --i }
                     // foo(); --bar;
                     print_newline();
@@ -1478,7 +1546,7 @@
                     print_token(' ' + trim(lines[j]));
                 } else {
                     // normal comments output raw
-                    output.push(lines[j]);
+                    output_lines[output_lines.length - 1].text.push(lines[j]);
                 }
             }
 
@@ -1505,12 +1573,12 @@
         }
 
         function handle_dot() {
-            if (is_special_word (flags.last_text)) {
+            if (is_special_word(flags.last_text)) {
                 output_space_before_token = true;
             } else {
                 // allow preserved newlines before dots in general
                 // force newlines on dots after close paren when break_chained - for bar().baz()
-                allow_wrap_or_preserved_newline (flags.last_text === ')' && opt.break_chained_methods);
+                allow_wrap_or_preserved_newline(flags.last_text === ')' && opt.break_chained_methods);
             }
 
             print_token();
@@ -1518,6 +1586,7 @@
 
         function handle_unknown() {
             print_token();
+
             if (token_text[token_text.length - 1] === '\n') {
                 print_newline();
             }
@@ -1527,9 +1596,17 @@
 
     if (typeof define === "function") {
         // Add support for require.js
-        define(function(require, exports, module) {
-            exports.js_beautify = js_beautify;
-        });
+        if (typeof define.amd === "undefined") {
+            define(function(require, exports, module) {
+                exports.js_beautify = js_beautify;
+            });
+        } else {
+            // if is AMD ( https://github.com/amdjs/amdjs-api/wiki/AMD#defineamd-property- )
+            define([], function() {
+                return js_beautify;
+            });
+        }
+
     } else if (typeof exports !== "undefined") {
         // Add support for CommonJS. Just put this file somewhere on your require.paths
         // and you will be able to `var js_beautify = require("beautify").js_beautify`.
diff --git a/js/lib/cli.js b/js/lib/cli.js
index 690e278aa..052fd2ec6 100755
--- a/js/lib/cli.js
+++ b/js/lib/cli.js
@@ -32,9 +32,9 @@
 
 */
 
-var debug = process.env.DEBUG_JSBEAUTIFY || process.env.JSBEAUTIFY_DEBUG
-    ? function () { console.error.apply(console, arguments); }
-    : function () {};
+var debug = process.env.DEBUG_JSBEAUTIFY || process.env.JSBEAUTIFY_DEBUG ? function() {
+        console.error.apply(console, arguments);
+    } : function() {};
 
 var fs = require('fs'),
     cc = require('config-chain'),
@@ -60,7 +60,7 @@ var fs = require('fs'),
         "wrap_line_length": Number,
         "e4x": Boolean,
         // HTML-only
-        "max_char": Number,
+        "max_char": Number, // obsolete since 1.3.5
         "unformatted": [String, Array],
         "indent_scripts": ["keep", "separate", "normal"],
         // CLI
@@ -92,7 +92,7 @@ var fs = require('fs'),
         "w": ["--wrap_line_length"],
         "X": ["--e4x"],
         // HTML-only
-        "W": ["--max_char"],
+        "W": ["--max_char"], // obsolete since 1.3.5
         "U": ["--unformatted"],
         "S": ["--indent_scripts"],
         // non-dasherized hybrid shortcuts
@@ -101,8 +101,8 @@ var fs = require('fs'),
             "--keep_function_indentation",
             "--jslint_happy"
         ],
-        "js"  : ["--type", "js"],
-        "css" : ["--type", "css"],
+        "js": ["--type", "js"],
+        "css": ["--type", "css"],
         "html": ["--type", "html"],
         // CLI
         "v": ["--version"],
@@ -115,14 +115,13 @@ var fs = require('fs'),
     });
 
 // var cli = require('js-beautify/cli'); cli.interpret();
-var interpret = exports.interpret = function (argv, slice) {
+var interpret = exports.interpret = function(argv, slice) {
     var parsed = nopt(knownOpts, shortHands, argv, slice);
 
     if (parsed.version) {
         console.log(require('../../package.json').version);
         process.exit(0);
-    }
-    else if (parsed.help) {
+    } else if (parsed.help) {
         usage();
         process.exit(0);
     }
@@ -144,9 +143,10 @@ var interpret = exports.interpret = function (argv, slice) {
         debug(cfg);
 
         // Process files synchronously to avoid EMFILE error
-        cfg.files.forEach(processInputSync, { cfg: cfg });
-    }
-    catch (ex) {
+        cfg.files.forEach(processInputSync, {
+            cfg: cfg
+        });
+    } catch (ex) {
         debug(cfg);
         // usage(ex);
         console.error(ex);
@@ -181,27 +181,29 @@ function usage(err) {
     ];
 
     switch (scriptName.split('-').shift()) {
-    case "js":
-        msg.push('  -l, --indent-level            Initial indentation level [0]');
-        msg.push('  -t, --indent-with-tabs        Indent with tabs, overrides -s and -c');
-        msg.push('  -p, --preserve-newlines       Preserve line-breaks (--no-preserve-newlines disables)');
-        msg.push('  -m, --max-preserve-newlines   Number of line-breaks to be preserved in one chunk [10]');
-        msg.push('  -P, --space-in-paren          Add padding spaces within paren, ie. f( a, b )');
-        msg.push('  -j, --jslint-happy            Enable jslint-stricter mode');
-        msg.push('  -b, --brace-style             [collapse|expand|end-expand] ["collapse"]');
-        msg.push('  -B, --break-chained-methods   Break chained method calls across subsequent lines');
-        msg.push('  -k, --keep-array-indentation  Preserve array indentation');
-        msg.push('  -x, --unescape-strings        Decode printable characters encoded in xNN notation');
-        msg.push('  -w, --wrap-line-length        Wrap lines at next opportunity after N characters [0]');
-        msg.push('  -X, --e4x                     Pass E4X xml literals through untouched');
-        msg.push('  --good-stuff                  Warm the cockles of Crockford\'s heart');
-        break;
-    case "html":
-        msg.push('  -b, --brace-style             [collapse|expand|end-expand] ["collapse"]');
-        msg.push('  -S, --indent-scripts          [keep|separate|normal] ["normal"]');
-        msg.push('  -W, --max-char                Maximum characters per line (0 disables) [250]');
-        msg.push('  -U, --unformatted             List of tags (defaults to inline) that should not be reformatted');
-        break;
+        case "js":
+            msg.push('  -l, --indent-level            Initial indentation level [0]');
+            msg.push('  -t, --indent-with-tabs        Indent with tabs, overrides -s and -c');
+            msg.push('  -p, --preserve-newlines       Preserve line-breaks (--no-preserve-newlines disables)');
+            msg.push('  -m, --max-preserve-newlines   Number of line-breaks to be preserved in one chunk [10]');
+            msg.push('  -P, --space-in-paren          Add padding spaces within paren, ie. f( a, b )');
+            msg.push('  -j, --jslint-happy            Enable jslint-stricter mode');
+            msg.push('  -b, --brace-style             [collapse|expand|end-expand] ["collapse"]');
+            msg.push('  -B, --break-chained-methods   Break chained method calls across subsequent lines');
+            msg.push('  -k, --keep-array-indentation  Preserve array indentation');
+            msg.push('  -x, --unescape-strings        Decode printable characters encoded in xNN notation');
+            msg.push('  -w, --wrap-line-length        Wrap lines at next opportunity after N characters [0]');
+            msg.push('  -X, --e4x                     Pass E4X xml literals through untouched');
+            msg.push('  --good-stuff                  Warm the cockles of Crockford\'s heart');
+            break;
+        case "html":
+            msg.push('  -b, --brace-style             [collapse|expand|end-expand] ["collapse"]');
+            msg.push('  -S, --indent-scripts          [keep|separate|normal] ["normal"]');
+            msg.push('  -w, --wrap-line-length        Wrap lines at next opportunity after N characters [0]');
+            msg.push('  -p, --preserve-newlines       Preserve line-breaks (--no-preserve-newlines disables)');
+            msg.push('  -m, --max-preserve-newlines   Number of line-breaks to be preserved in one chunk [10]');
+            msg.push('  -U, --unformatted             List of tags (defaults to inline) that should not be reformatted');
+            break;
     }
 
     if (err) {
@@ -214,6 +216,7 @@ function usage(err) {
 }
 
 // main iterator, {cfg} passed as thisArg of forEach call
+
 function processInputSync(filepath) {
     var data = '',
         config = this.cfg,
@@ -230,15 +233,14 @@ function processInputSync(filepath) {
         input.resume();
         input.setEncoding('utf8');
 
-        input.on('data', function (chunk) {
+        input.on('data', function(chunk) {
             data += chunk;
         });
 
-        input.on('end', function () {
+        input.on('end', function() {
             makePretty(data, config, outfile, writePretty);
         });
-    }
-    else {
+    } else {
         var dir = path.dirname(outfile);
         mkdirp.sync(dir);
         data = fs.readFileSync(filepath, 'utf8');
@@ -255,8 +257,7 @@ function makePretty(code, config, outfile, callback) {
         pretty += '\n';
 
         callback(null, pretty, outfile, config);
-    }
-    catch (ex) {
+    } catch (ex) {
         callback(ex);
     }
 }
@@ -271,17 +272,16 @@ function writePretty(err, pretty, outfile, config) {
         try {
             fs.writeFileSync(outfile, pretty, 'utf8');
             logToStdout('beautified ' + path.relative(process.cwd(), outfile), config);
-        }
-        catch (ex) {
+        } catch (ex) {
             onOutputError(ex);
         }
-    }
-    else {
+    } else {
         process.stdout.write(pretty);
     }
 }
 
 // workaround the fact that nopt.clean doesn't return the object passed in :P
+
 function cleanOptions(data, types) {
     nopt.clean(data, types);
     return data;
@@ -289,26 +289,28 @@ function cleanOptions(data, types) {
 
 // error handler for output stream that swallows errors silently,
 // allowing the loop to continue over unwritable files.
+
 function onOutputError(err) {
     if (err.code === 'EACCES') {
         console.error(err.path + " is not writable. Skipping!");
-    }
-    else {
+    } else {
         console.error(err);
         process.exit(0);
     }
 }
 
 // turn "--foo_bar" into "foo-bar"
+
 function dasherizeFlag(str) {
     return str.replace(/^\-+/, '').replace(/_/g, '-');
 }
 
 // translate weird python underscored keys into dashed argv,
 // avoiding single character aliases.
+
 function dasherizeShorthands(hash) {
     // operate in-place
-    Object.keys(hash).forEach(function (key) {
+    Object.keys(hash).forEach(function(key) {
         // each key value is an array
         var val = hash[key][0];
         // only dasherize one-character shorthands
@@ -358,8 +360,7 @@ function checkFiles(parsed) {
 
     if (!parsed.files) {
         parsed.files = [];
-    }
-    else {
+    } else {
         if (argv.cooked.indexOf('-') > -1) {
             // strip stdin path eagerly added by nopt in '-f -' case
             parsed.files.some(removeDashedPath);
@@ -368,7 +369,7 @@ function checkFiles(parsed) {
 
     if (argv.remain.length) {
         // assume any remaining args are files
-        argv.remain.forEach(function (f) {
+        argv.remain.forEach(function(f) {
             parsed.files.push(path.resolve(f));
         });
     }
@@ -408,8 +409,7 @@ function testFilePath(filepath) {
         if (filepath !== "-") {
             fs.statSync(filepath);
         }
-    }
-    catch (err) {
+    } catch (err) {
         throw 'Unable to open path "' + filepath + '"';
     }
 }
diff --git a/js/test/beautify-tests.js b/js/test/beautify-tests.js
index 95beddd7e..64b134272 100755
--- a/js/test/beautify-tests.js
+++ b/js/test/beautify-tests.js
@@ -116,8 +116,8 @@ function run_beautifier_tests(test_obj, Urlencoded, js_beautify)
         bt('a=0xff+4', 'a = 0xff + 4');
         bt('a = [1, 2, 3, 4]');
         bt('F*(g/=f)*g+b', 'F * (g /= f) * g + b');
-        bt('a.b({c:d})', "a.b({\n        c: d\n    })");
-        bt('a.b\n(\n{\nc:\nd\n}\n)', "a.b({\n        c: d\n    })");
+        bt('a.b({c:d})', "a.b({\n    c: d\n})");
+        bt('a.b\n(\n{\nc:\nd\n}\n)', "a.b({\n    c: d\n})");
         bt('a=!b', 'a = !b');
         bt('a?b:c', 'a ? b : c');
         bt('a?1:2', 'a ? 1 : 2');
@@ -183,7 +183,7 @@ function run_beautifier_tests(test_obj, Urlencoded, js_beautify)
         bt('a = [ // comment\n    -1, // comment\n    -1, -1\n]');
         bt('var a = [ // comment\n    -1, // comment\n    -1, -1\n]');
 
-        bt('o = [{a:b},{c:d}]', 'o = [{\n        a: b\n    }, {\n        c: d\n    }\n]');
+        bt('o = [{a:b},{c:d}]', 'o = [{\n    a: b\n}, {\n    c: d\n}]');
 
         bt("if (a) {\n    do();\n}"); // was: extra space appended
 
@@ -237,7 +237,7 @@ function run_beautifier_tests(test_obj, Urlencoded, js_beautify)
         test_fragment('/incomplete-regex');
 
         test_fragment('{a:1},{a:2}', '{\n    a: 1\n}, {\n    a: 2\n}');
-        test_fragment('var ary=[{a:1}, {a:2}];', 'var ary = [{\n        a: 1\n    }, {\n        a: 2\n    }\n];');
+        test_fragment('var ary=[{a:1}, {a:2}];', 'var ary = [{\n    a: 1\n}, {\n    a: 2\n}];');
 
         test_fragment('{a:#1', '{\n    a: #1'); // incomplete
         test_fragment('{a:#', '{\n    a: #'); // incomplete
@@ -314,8 +314,11 @@ function run_beautifier_tests(test_obj, Urlencoded, js_beautify)
         bt('return\nfunc', 'return\nfunc');
         bt('catch(e)', 'catch (e)');
 
-        bt('var a=1,b={foo:2,bar:3},{baz:4,wham:5},c=4;', 'var a = 1,\n    b = {\n        foo: 2,\n        bar: 3\n    }, {\n        baz: 4,\n        wham: 5\n    }, c = 4;');
-        bt('var a=1,b={foo:2,bar:3},{baz:4,wham:5},\nc=4;', 'var a = 1,\n    b = {\n        foo: 2,\n        bar: 3\n    }, {\n        baz: 4,\n        wham: 5\n    },\n    c = 4;');
+        bt('var a=1,b={foo:2,bar:3},{baz:4,wham:5},c=4;',
+            'var a = 1,\n    b = {\n        foo: 2,\n        bar: 3\n    }, {\n        baz: 4,\n        wham: 5\n    }, c = 4;');
+        bt('var a=1,b={foo:2,bar:3},{baz:4,wham:5},\nc=4;',
+            'var a = 1,\n    b = {\n        foo: 2,\n        bar: 3\n    }, {\n        baz: 4,\n        wham: 5\n    },\n    c = 4;');
+
 
         // inline comment
         bt('function x(/*int*/ start, /*string*/ foo)', 'function x( /*int*/ start, /*string*/ foo)');
@@ -430,20 +433,20 @@ function run_beautifier_tests(test_obj, Urlencoded, js_beautify)
         bt("a = ['a', 'b', 'c',\n       'd', 'e', 'f',\n            'g', 'h', 'i']",
             "a = ['a', 'b', 'c',\n    'd', 'e', 'f',\n    'g', 'h', 'i'\n]");
         bt('var x = [{}\n]', 'var x = [{}]');
-        bt('var x = [{foo:bar}\n]', 'var x = [{\n        foo: bar\n    }\n]');
+        bt('var x = [{foo:bar}\n]', 'var x = [{\n    foo: bar\n}]');
         bt("a = ['something',\n    'completely',\n    'different'];\nif (x);",
             "a = ['something',\n    'completely',\n    'different'\n];\nif (x);");
         bt("a = ['a','b','c']", "a = ['a', 'b', 'c']");
+
         bt("a = ['a',   'b','c']", "a = ['a', 'b', 'c']");
         bt("x = [{'a':0}]",
-            "x = [{\n        'a': 0\n    }\n]");
-        // this is not great, but is accurate
+            "x = [{\n    'a': 0\n}]");
         bt('{a([[a1]], {b;});}',
-            '{\n    a([\n            [a1]\n        ], {\n            b;\n        });\n}');
+            '{\n    a([\n        [a1]\n    ], {\n        b;\n    });\n}');
         bt("a();\n   [\n   ['sdfsdfsd'],\n        ['sdfsdfsdf']\n   ].toString();",
             "a();\n[\n    ['sdfsdfsd'],\n    ['sdfsdfsdf']\n].toString();");
         bt("function() {\n    Foo([\n        ['sdfsdfsd'],\n        ['sdfsdfsdf']\n    ]);\n}",
-            "function() {\n    Foo([\n            ['sdfsdfsd'],\n            ['sdfsdfsdf']\n        ]);\n}");
+            "function() {\n    Foo([\n        ['sdfsdfsd'],\n        ['sdfsdfsdf']\n    ]);\n}");
 
         opts.keep_array_indentation = true;
         bt("a = ['a', 'b', 'c',\n   'd', 'e', 'f']");
@@ -455,9 +458,9 @@ function run_beautifier_tests(test_obj, Urlencoded, js_beautify)
         bt("a = ['a','b','c']", "a = ['a', 'b', 'c']");
         bt("a = ['a',   'b','c']", "a = ['a', 'b', 'c']");
         bt("x = [{'a':0}]",
-            "x = [{\n        'a': 0\n    }]");
+            "x = [{\n    'a': 0\n}]");
         bt('{a([[a1]], {b;});}',
-            '{\n    a([[a1]], {\n            b;\n        });\n}');
+            '{\n    a([[a1]], {\n        b;\n    });\n}');
         bt("a();\n   [\n   ['sdfsdfsd'],\n        ['sdfsdfsdf']\n   ].toString();",
             "a();\n   [\n   ['sdfsdfsd'],\n        ['sdfsdfsdf']\n   ].toString();");
         bt("function() {\n    Foo([\n        ['sdfsdfsd'],\n        ['sdfsdfsdf']\n    ]);\n}",
@@ -796,6 +799,12 @@ function run_beautifier_tests(test_obj, Urlencoded, js_beautify)
         bt('var a = 42; // foo\n\n\nvar b;');
         bt("var a = 'foo' +\n    'bar';");
         bt("var a = \"foo\" +\n    \"bar\";");
+        bt('this.oa = new OAuth(\n' +
+           '    _requestToken,\n' +
+           '    _accessToken,\n' +
+           '    consumer_key\n' +
+           ');');
+
 
         opts.unescape_strings = false;
         test_fragment('"\\x22\\x27", \'\\x22\\x27\', "\\x5c", \'\\x5c\', "\\xff and \\xzz", "unicode \\u0000 \\u0022 \\u0027 \\u005c \\uffff \\uzzzz"');
@@ -895,7 +904,7 @@ function run_beautifier_tests(test_obj, Urlencoded, js_beautify)
                       'if (wraps_can_occur && inside_an_if_block) that_is_\n.okay();',
                       /* expected */
                       'foo.bar().baz().cucumber((fat &&\n' +
-                      '        "sassy") || (leans && mean));\n' +
+                      '    "sassy") || (leans && mean));\n' +
                       'Test_very_long_variable_name_this_should_never_wrap\n' +
                       '    .but_this_can\n' +
                       'if (wraps_can_occur &&\n' +
@@ -971,7 +980,7 @@ function run_beautifier_tests(test_obj, Urlencoded, js_beautify)
                       'if (wraps_can_occur && inside_an_if_block) that_is_\n.okay();',
                       /* expected */
                       'foo.bar().baz().cucumber((fat &&\n' +
-                      '        "sassy") || (leans && mean));\n' +
+                      '    "sassy") || (leans && mean));\n' +
                       'Test_very_long_variable_name_this_should_never_wrap\n' +
                       '    .but_this_can\n' +
                       'if (wraps_can_occur &&\n' +
@@ -1021,6 +1030,12 @@ function run_beautifier_tests(test_obj, Urlencoded, js_beautify)
         bt('if (foo) // comment\n    (bar());');
         bt('if (foo) // comment\n    (bar());');
         bt('if (foo) // comment\n    /asdf/;');
+        bt('this.oa = new OAuth(\n' +
+           '    _requestToken,\n' +
+           '    _accessToken,\n' +
+           '    consumer_key\n' +
+           ');',
+           'this.oa = new OAuth(_requestToken, _accessToken, consumer_key);');
         bt('foo = {\n    x: y, // #44\n    w: z // #44\n}');
         bt('switch (x) {\n    case "a":\n        // comment on newline\n        break;\n    case "b": // comment on same line\n        break;\n}');
 
@@ -1240,14 +1255,14 @@ function run_beautifier_tests(test_obj, Urlencoded, js_beautify)
 
 
         // START tests for issue 268 and 275
+        bt('obj.last(a, function() {\n' +
+           '    var test;\n' +
+           '});\n' +
+           'var test = 1;');
         bt('obj.last(a,\n' +
            '    function() {\n' +
            '        var test;\n' +
            '    });\n' +
-           'var test = 1;',
-           'obj.last(a, function() {\n' +
-           '    var test;\n' +
-           '});\n' +
            'var test = 1;');
 
         bt('(function() {if (!window.FOO) window.FOO || (window.FOO = function() {var b = {bar: "zort"};});})();',
@@ -1260,6 +1275,47 @@ function run_beautifier_tests(test_obj, Urlencoded, js_beautify)
            '})();');
         // END tests for issue 268 and 275
 
+        // START tests for issue 281
+        bt('define(["dojo/_base/declare", "my/Employee", "dijit/form/Button",\n' +
+           '    "dojo/_base/lang", "dojo/Deferred"\n' +
+           '], function(declare, Employee, Button, lang, Deferred) {\n' +
+           '    return declare(Employee, {\n' +
+           '        constructor: function() {\n' +
+           '            new Button({\n' +
+           '                onClick: lang.hitch(this, function() {\n' +
+           '                    new Deferred().then(lang.hitch(this, function() {\n' +
+           '                        this.salary * 0.25;\n' +
+           '                    }));\n' +
+           '                })\n' +
+           '            });\n' +
+           '        }\n' +
+           '    });\n' +
+           '});');
+
+        bt('define(["dojo/_base/declare", "my/Employee", "dijit/form/Button",\n' +
+           '        "dojo/_base/lang", "dojo/Deferred"\n' +
+           '    ],\n' +
+           '    function(declare, Employee, Button, lang, Deferred) {\n' +
+           '        return declare(Employee, {\n' +
+           '            constructor: function() {\n' +
+           '                new Button({\n' +
+           '                    onClick: lang.hitch(this, function() {\n' +
+           '                        new Deferred().then(lang.hitch(this, function() {\n' +
+           '                            this.salary * 0.25;\n' +
+           '                        }));\n' +
+           '                    })\n' +
+           '                });\n' +
+           '            }\n' +
+           '        });\n' +
+           '    });');
+        // END tests for issue 281
+
+        // This is what I think these should look like related #256
+        // we don't have the ability yet
+//         bt('var a=1,b={bang:2},c=3;',
+//             'var a = 1,\n    b = {\n        bang: 2\n    },\n     c = 3;');
+//         bt('var a={bing:1},b=2,c=3;',
+//             'var a = {\n        bing: 1\n    },\n    b = 2,\n    c = 3;');
         Urlencoded.run_tests(sanitytest);
 
         return sanitytest;
diff --git a/js/test/shell-perf-test.sh b/js/test/shell-perf-test.sh
new file mode 100755
index 000000000..7f40e2f3d
--- /dev/null
+++ b/js/test/shell-perf-test.sh
@@ -0,0 +1,27 @@
+#!/bin/bash
+
+REL_SCRIPT_DIR="`dirname \"$0\"`"
+SCRIPT_DIR="`( cd \"$REL_SCRIPT_DIR\" && pwd )`"
+
+
+test_performance_js_beautify()
+{
+  echo ----------------------------------------
+  echo Testing js-beautify cli performance...
+  CLI_SCRIPT=$SCRIPT_DIR/../bin/js-beautify.js
+
+  mkdir -p /tmp/js-beautify-perf
+  if [ ! -f /tmp/js-beautify-perf/jquery-2.0.2.js ];
+  then
+      curl -o /tmp/js-beautify-perf/jquery-2.0.2.js http://code.jquery.com/jquery-2.0.2.js
+  fi
+
+  time $CLI_SCRIPT -o /tmp/js-beautify-perf/jquery-2.0.2-output.js /tmp/js-beautify-perf/jquery-2.0.2.js
+
+}
+
+test_performance_js_beautify
+
+echo ----------------------------------------
+echo $0 - DONE.
+echo ----------------------------------------
diff --git a/package.json b/package.json
index 07136526e..fa772a471 100644
--- a/package.json
+++ b/package.json
@@ -1,6 +1,6 @@
 {
   "name": "js-beautify",
-  "version": "1.3.4",
+  "version": "1.4.0",
   "description": "jsbeautifier.org for node",
   "main": "js/index.js",
   "preferGlobal": true,
diff --git a/python/jsbeautifier/__init__.py b/python/jsbeautifier/__init__.py
index ca9af0fb9..190f9b045 100644
--- a/python/jsbeautifier/__init__.py
+++ b/python/jsbeautifier/__init__.py
@@ -116,7 +116,7 @@ def __init__(self, mode):
         self.var_line_tainted = False
         self.var_line_reindented = False
         self.in_html_comment = False
-        self.multiline_array = False
+        self.multiline_frame = False
         self.if_block = False
         self.do_block = False
         self.do_while = False
@@ -124,16 +124,29 @@ def __init__(self, mode):
         self.in_case_statement = False
         self.case_body = False
         self.indentation_level = 0
+        self.line_indent_level = 0
+        self.start_line_index = 0
         self.ternary_depth = 0
 
-    def apply_base(self, flags_base):
+    def apply_base(self, flags_base, added_newline):
+        next_indent_level = flags_base.indentation_level;
+        if flags_base.var_line and flags_base.var_line_reindented:
+            next_indent_level += 1
+        if not added_newline and \
+            flags_base.line_indent_level > next_indent_level:
+            next_indent_level = flags_base.line_indent_level;
+
         self.parent = flags_base;
         self.last_text = flags_base.last_text
         self.last_word = flags_base.last_word
-        self.indentation_level = flags_base.indentation_level
+        self.indentation_level = next_indent_level
+
+# Using object instead of string to allow for later expansion of info about each line
+class OutputLine:
+    def __init__(self):
+        self.text = []
+
 
-        if flags_base.var_line and flags_base.var_line_reindented:
-            self.indentation_level += 1
 
 
 def default_options():
@@ -222,18 +235,18 @@ def blank_state(self):
         self.previous_flags = None
         self.flag_store = []
         self.input_wanted_newline = False
-        self.line_indent_level = 0;
         if self.opts.indent_with_tabs:
-            self.indent_string = "\t"
-        else:
-            self.indent_string = self.opts.indent_char * self.opts.indent_size
+            self.opts.indent_char = "\t"
+            self.opts.indent_size = 1
+
+        self.indent_string = self.opts.indent_char * self.opts.indent_size
 
         self.preindent_string = ''
-        self.last_type = 'TK_START_EXPR' # last token type
+        self.last_type = 'TK_START_BLOCK' # last token type
         self.last_last_text = ''         # pre-last token text
 
         self.input = None
-        self.output = []                 # formatted javascript gets built here
+        self.output_lines = [ OutputLine() ]
         self.output_wrapped = False
         self.output_space_before_token = False
         self.whitespace_before_token = []
@@ -307,6 +320,7 @@ def beautify(self, s, opts = None ):
                     self.n_newlines = self.opts.max_preserve_newlines
 
                 if self.opts.preserve_newlines and self.n_newlines > 1:
+
                     for i in range(self.n_newlines):
                         self.append_newline(i != 0)
 
@@ -319,7 +333,12 @@ def beautify(self, s, opts = None ):
                 self.last_type = token_type
                 self.flags.last_text = self.token_text
 
-        sweet_code = self.preindent_string + re.sub('[\n ]+$', '', ''.join(self.output))
+        sweet_code = ''.join(self.output_lines[0].text)
+        if len(self.output_lines) > 1:
+            for line_index in range(1, len(self.output_lines)):
+                sweet_code += '\n' + ''.join(self.output_lines[line_index].text);
+
+        sweet_code = re.sub('[\n ]+$', '', sweet_code)
         return sweet_code
 
     def unpack(self, source, evalcode=False):
@@ -331,13 +350,20 @@ def unpack(self, source, evalcode=False):
             return ''
 
     def trim_output(self, eat_newlines = False):
-        while len(self.output) \
+        self.trim_output_line(self.output_lines[-1])
+
+        while eat_newlines and len(self.output_lines) > 1 and \
+            len(self.output_lines[-1].text) == 0:
+            self.output_lines.pop()
+            self.trim_output_line(self.output_lines[-1])
+
+    def trim_output_line(self, line):
+        while len(line.text) \
               and (
-                  self.output[-1] == ' '\
-                  or self.output[-1] == self.indent_string \
-                  or self.output[-1] == self.preindent_string \
-                  or (eat_newlines and self.output[-1] in ['\n', '\r'])):
-            self.output.pop()
+                  line.text[-1] == ' '\
+                  or line.text[-1] == self.indent_string \
+                  or line.text[-1] == self.preindent_string):
+            line.text.pop()
 
     def is_special_word(self, s):
         return s in ['case', 'return', 'do', 'if', 'throw', 'else']
@@ -350,30 +376,27 @@ def is_expression(self, mode):
         return mode in [MODE.Expression, MODE.ForInitializer, MODE.Conditional]
 
     def just_added_newline(self):
-        return len(self.output) and self.output[-1] == '\n'
+        line = self.output_lines[-1]
+        return len(line.text) == 0
 
-    def just_added_blankline(self):
-        return self.just_added_newline() and len(self.output) - 1 > 0 and self.output[-2] == '\n'
 
-    def last_index(self, arr, find):
-        last_index = len(arr) - 1
-        while last_index >= 0:
-            if arr[last_index] == find:
-                break
-            else:
-                last_index -= 1
+    def just_added_blankline(self):
+        if self.just_added_newline():
+            if len(self.output_lines) == 1:
+                return True
 
-        return last_index
+            line = self.output_lines[-2]
+            return len(line.text) == 0
 
+        return False
 
     def allow_wrap_or_preserved_newline(self, token_text, force_linewrap = False):
         if self.opts.wrap_line_length > 0 and not force_linewrap:
-            start_line = self.last_index(self.output, '\n') + 1
+            line = self.output_lines[-1]
 
             # never wrap the first token of a line.
-            if start_line < len(self.output):
-                current_line = ''.join(self.output[start_line:])
-                proposed_line_length = len(current_line) + len(token_text)
+            if len(line.text) > 0:
+                proposed_line_length = len(''.join(line.text)) + len(token_text)
                 if self.output_space_before_token:
                     proposed_line_length += 1
 
@@ -397,27 +420,27 @@ def append_newline(self, force_newline = False, preserve_statement_flags = False
                 while self.flags.mode == MODE.Statement and not self.flags.if_block and not self.flags.do_block:
                     self.restore_mode();
 
-        if self.flags.mode == MODE.ArrayLiteral:
-            self.flags.multiline_array = True
-
-
-        if len(self.output) == 0:
+        if len(self.output_lines) == 1 and self.just_added_newline():
             # no newline on start of file
             return
 
         if force_newline or not self.just_added_newline():
-            self.output.append('\n')
+            self.flags.multiline_frame = True
+            self.output_lines.append(OutputLine())
 
 
     def append_token_line_indentation(self):
         if self.just_added_newline():
+            line = self.output_lines[-1]
             if self.opts.keep_array_indentation and self.is_array(self.flags.mode) and self.input_wanted_newline:
+                # prevent removing of this whitespace as redundant
+                line.text.append('');
                 for item in self.whitespace_before_token:
-                    self.output.append(item)
+                    line.text.append(item)
 
             else:
-                if self.preindent_string:
-                    self.output.append(self.preindent_string)
+                if self.preindent_string != '':
+                    line.text.append(self.preindent_string)
 
                 level = self.flags.indentation_level;
                 if self.flags.var_line and self.flags.var_line_reindented:
@@ -430,16 +453,18 @@ def append_token_line_indentation(self):
 
     def append_indent_string(self, level):
         # Never indent your first output indent at the start of the file
-        if self.flags.last_text != '':
-            self.line_indent_level = level
+        if len(self.output_lines) > 1:
+            line = self.output_lines[-1]
+            self.flags.line_indent_level = level
             for i in range(level):
-                self.output.append(self.indent_string)
+                line.text.append(self.indent_string)
 
 
     def append_token_space_before(self):
         # make sure only single space gets drawn
-        if self.output_space_before_token and len(self.output) and self.output[-1] not in [' ', '\n', self.indent_string]:
-            self.output.append(' ')
+        line = self.output_lines[-1]
+        if self.output_space_before_token and len(line.text) and line.text[-1] not in [' ', self.indent_string]:
+            line.text.append(' ')
 
 
     def append_token(self, s):
@@ -447,7 +472,7 @@ def append_token(self, s):
         self.output_wrapped = False
         self.append_token_space_before()
         self.output_space_before_token = False
-        self.output.append(s)
+        self.output_lines[-1].text.append(s)
 
 
     def indent(self):
@@ -459,6 +484,37 @@ def deindent(self):
         if allow_deindent:
             self.flags.indentation_level -= 1
 
+    def remove_redundant_indentation(self, frame):
+        # This implementation is effective but has some issues:
+        #     - less than great performance due to array splicing
+        #     - can cause line wrap to happen too soon due to indent removal
+        #           after wrap points are calculated
+        # These issues are minor compared to ugly indentation.
+
+        if frame.multiline_frame:
+            return
+
+        # remove one indent from each line inside this section
+        index = frame.start_line_index
+        splice_index = 0
+        while index < len(self.output_lines):
+            line = self.output_lines[index]
+            index += 1
+
+            # skip empty lines
+            if len(line.text) == 0:
+                continue
+
+            # skip the preindent string if present
+            if self.preindent_string != '' and \
+                    line.text[0] == self.preindent_string:
+                splice_index = 1
+            else:
+                splice_index = 0
+
+            # remove one indent, if present
+            if line.text[splice_index] == self.indent_string:
+                del line.text[splice_index]
 
     def set_mode(self, mode):
         if self.flags:
@@ -468,7 +524,8 @@ def set_mode(self, mode):
             self.previous_flags = BeautifierFlags(mode)
 
         self.flags = BeautifierFlags(mode)
-        self.flags.apply_base(self.previous_flags);
+        self.flags.apply_base(self.previous_flags, self.just_added_newline());
+        self.flags.start_line_index = len(self.output_lines)
 
     def restore_mode(self):
         if len(self.flag_store) > 0:
@@ -725,7 +782,8 @@ def get_next_token(self):
         if c == '#':
 
             # she-bang
-            if len(self.output) == 0 and len(self.input) > 1 and self.input[self.parser_pos] == '!':
+            if len(self.output_lines) == 1 and len(self.output_lines[0].text) == 0 and \
+                    len(self.input) > self.parser_pos and self.input[self.parser_pos] == '!':
                 resulting_string = c
                 while self.parser_pos < len(self.input) and c != '\n':
                     c = self.input[self.parser_pos]
@@ -794,16 +852,21 @@ def handle_start_expr(self, token_text):
             # The conditional starts the statement if appropriate.
             pass
 
+        next_mode = MODE.Expression
+
         if token_text == '[':
             if self.last_type == 'TK_WORD' or self.flags.last_text == ')':
                 if self.flags.last_text in self.line_starters:
                     self.output_space_before_token = True
-                self.set_mode(MODE.Expression)
+                self.set_mode(next_mode)
                 self.append_token(token_text)
+                self.indent()
                 if self.opts.space_in_paren:
                     self.output_space_before_token = True
                 return
 
+            next_mode = MODE.ArrayLiteral
+
             if self.is_array(self.flags.mode):
                 if self.flags.last_text == '[' or (
                     self.flags.last_text == ',' and (self.last_last_text == ']' or self.last_last_text == '}')):
@@ -814,19 +877,21 @@ def handle_start_expr(self, token_text):
 
         else:
             if self.flags.last_text == 'for':
-                self.set_mode(MODE.ForInitializer)
+                next_mode = MODE.ForInitializer
             elif self.flags.last_text in ['if', 'while']:
-                self.set_mode(MODE.Conditional)
+                next_mode = MODE.Conditional
             else:
-                self.set_mode(MODE.Expression)
+                next_mode = MODE.Expression
 
 
         if self.flags.last_text == ';' or self.last_type == 'TK_START_BLOCK':
             self.append_newline()
         elif self.last_type in ['TK_END_EXPR', 'TK_START_EXPR', 'TK_END_BLOCK'] or self.flags.last_text == '.':
             # do nothing on (( and )( and ][ and ]( and .(
-            if self.input_wanted_newline:
-                self.append_newline()
+            # TODO: Consider whether forcing this is required.  Review failing tests when removed.
+            self.allow_wrap_or_preserved_newline(token_text, self.input_wanted_newline);
+            self.output_wrapped = False;
+
         elif self.last_type not in ['TK_WORD', 'TK_OPERATOR']:
             self.output_space_before_token = True
         elif self.flags.last_word == 'function' or self.flags.last_word == 'typeof':
@@ -841,18 +906,17 @@ def handle_start_expr(self, token_text):
         # a = (b &&
         #     (c || d));
         if self.last_type in ['TK_EQUALS', 'TK_OPERATOR']:
-            if self.flags.mode != 'OBJECT':
+            if self.flags.mode != MODE.ObjectLiteral:
                 self.allow_wrap_or_preserved_newline(token_text)
 
-        if self.token_text == '[':
-            self.set_mode(MODE.ArrayLiteral)
-
+        self.set_mode(next_mode)
         self.append_token(token_text)
+
         if self.opts.space_in_paren:
             self.output_space_before_token = True
 
         # In all cases, if we newline while inside an expression it should be indented.
-        self.indent();
+        self.indent()
 
 
 
@@ -862,9 +926,12 @@ def handle_end_expr(self, token_text):
         while self.flags.mode == MODE.Statement:
             self.restore_mode()
 
-        if self.token_text == ']' and self.is_array(self.flags.mode) and self.flags.multiline_array and not self.opts.keep_array_indentation:
+        if self.token_text == ']' and self.is_array(self.flags.mode) and self.flags.multiline_frame and not self.opts.keep_array_indentation:
             self.append_newline()
 
+        if self.flags.multiline_frame:
+            self.allow_wrap_or_preserved_newline(token_text)
+
         if self.opts.space_in_paren:
             self.output_space_before_token = True
 
@@ -875,6 +942,8 @@ def handle_end_expr(self, token_text):
             self.restore_mode()
             self.append_token(token_text)
 
+        self.remove_redundant_indentation(self.previous_flags);
+
         # do {} while () // no statement required after
         if self.flags.do_while and self.previous_flags.mode == MODE.Conditional:
             self.previous_flags.mode = MODE.Expression
@@ -919,8 +988,6 @@ def handle_end_block(self, token_text):
         while self.flags.mode == MODE.Statement:
             self.restore_mode()
 
-        self.restore_mode()
-
         empty_braces = self.last_type == 'TK_START_BLOCK';
         if self.opts.brace_style == 'expand':
             if not empty_braces:
@@ -935,6 +1002,7 @@ def handle_end_block(self, token_text):
                 else:
                     self.append_newline()
 
+        self.restore_mode()
         self.append_token(token_text)
 
 
@@ -973,6 +1041,15 @@ def handle_word(self, token_text):
 
                 self.flags.if_block = False;
 
+        if token_text == 'case' or (token_text == 'default' and self.flags.in_case_statement):
+            self.append_newline()
+            if self.flags.case_body or self.opts.jslint_happy:
+                self.flags.case_body = False
+                self.deindent()
+            self.append_token(token_text)
+            self.flags.in_case = True
+            self.flags.in_case_statement = True
+            return
 
         if token_text == 'function':
             if self.flags.var_line and self.flags.last_text != '=':
@@ -981,9 +1058,8 @@ def handle_word(self, token_text):
                     self.flags.last_text != '{' and not self.is_array(self.flags.mode):
                 # make sure there is a nice clean space of at least one blank line
                 # before a new function definition, except in arrays
-                if not self.just_added_newline():
-                    self.append_newline(True)
                 if not self.just_added_blankline():
+                    self.append_newline()
                     self.append_newline(True)
 
             if self.last_type == 'TK_WORD':
@@ -1000,28 +1076,15 @@ def handle_word(self, token_text):
             else:
                 self.append_newline()
 
-            if self.is_expression(self.flags.mode):
-                # Issue #274
-                # (function inside expression that is not nested.
-                if not (self.is_expression(self.flags.parent.mode) or \
-                        self.is_array(self.flags.parent.mode)) and \
-                    self.line_indent_level < self.flags.indentation_level:
-                    self.deindent();
+        if self.last_type in ['TK_COMMA', 'TK_START_EXPR', 'TK_EQUALS', 'TK_OPERATOR']:
+            if self.flags.mode != MODE.ObjectLiteral:
+                self.allow_wrap_or_preserved_newline(token_text)
 
+        if token_text == 'function':
             self.append_token(token_text)
             self.flags.last_word = token_text
             return
 
-        if token_text == 'case' or (token_text == 'default' and self.flags.in_case_statement):
-            self.append_newline()
-            if self.flags.case_body or self.opts.jslint_happy:
-                self.flags.case_body = False
-                self.deindent()
-            self.append_token(token_text)
-            self.flags.in_case = True
-            self.flags.in_case_statement = True
-            return
-
         prefix = 'NONE'
 
         if self.last_type == 'TK_END_BLOCK':
@@ -1054,10 +1117,6 @@ def handle_word(self, token_text):
             else:
                 prefix = 'NEWLINE'
 
-        if self.last_type in ['TK_COMMA', 'TK_START_EXPR', 'TK_EQUALS', 'TK_OPERATOR']:
-            if self.flags.mode != 'OBJECT':
-                self.allow_wrap_or_preserved_newline(token_text)
-
         if token_text in ['else', 'catch', 'finally']:
             if self.last_type != 'TK_END_BLOCK' \
                or self.opts.brace_style == 'expand' \
@@ -1065,9 +1124,10 @@ def handle_word(self, token_text):
                 self.append_newline()
             else:
                 self.trim_output(True)
+                line = self.output_lines[-1]
                 # If we trimmed and there's something other than a close block before us
                 # put a newline back in.  Handles '} // comment' scenario.
-                if self.output[-1] != '}':
+                if line.text[-1] != '}':
                     self.append_newline()
 
                 self.output_space_before_token = True
@@ -1123,7 +1183,7 @@ def handle_semicolon(self, token_text):
         self.append_token(token_text)
         self.flags.var_line = False
         self.flags.var_line_reindented = False
-        if self.flags.mode == 'OBJECT':
+        if self.flags.mode == MODE.ObjectLiteral:
             # OBJECT mode is weird and doesn't get reset too well.
             self.flags.mode = MODE.BlockStatement
 
@@ -1135,10 +1195,8 @@ def handle_string(self, token_text):
             self.output_space_before_token = True
         elif self.last_type == 'TK_WORD':
             self.output_space_before_token = True
-        elif self.last_type == 'TK_END_EXPR' and self.previous_flags.mode in [MODE.Conditional, MODE.ForInitializer]:
-            self.output_space_before_token = True
         elif self.last_type in ['TK_COMMA', 'TK_START_EXPR', 'TK_EQUALS', 'TK_OPERATOR']:
-            if self.flags.mode != 'OBJECT':
+            if self.flags.mode != MODE.ObjectLiteral:
                 self.allow_wrap_or_preserved_newline(token_text)
         else:
             self.append_newline()
@@ -1177,12 +1235,12 @@ def handle_comma(self, token_text):
 
         if self.last_type == 'TK_END_BLOCK' and self.flags.mode != MODE.Expression:
             self.append_token(token_text)
-            if self.flags.mode == 'OBJECT' and self.flags.last_text == '}':
+            if self.flags.mode == MODE.ObjectLiteral and self.flags.last_text == '}':
                 self.append_newline()
             else:
                 self.output_space_before_token = True
         else:
-            if self.flags.mode == 'OBJECT':
+            if self.flags.mode == MODE.ObjectLiteral:
                 self.append_token(token_text)
                 self.append_newline()
             else:
@@ -1250,7 +1308,7 @@ def handle_operator(self, token_text):
         elif token_text == ':':
             if self.flags.ternary_depth == 0:
                 if self.flags.mode == MODE.BlockStatement:
-                    self.flags.mode = 'OBJECT'
+                    self.flags.mode = MODE.ObjectLiteral
                 space_before = False
             else:
                 self.flags.ternary_depth -= 1
@@ -1286,7 +1344,7 @@ def handle_block_comment(self, token_text):
                 self.append_token(' ' + line.strip())
             else:
                 # normal comments output raw
-                self.output.append(line)
+                self.output_lines[-1].text.append(line)
 
         self.append_newline(preserve_statement_flags = True)
 
diff --git a/python/jsbeautifier/__version__.py b/python/jsbeautifier/__version__.py
index ac422f13a..96e3ce8d9 100644
--- a/python/jsbeautifier/__version__.py
+++ b/python/jsbeautifier/__version__.py
@@ -1 +1 @@
-__version__ = '1.3.4'
+__version__ = '1.4.0'
diff --git a/python/jsbeautifier/tests/testjsbeautifier.py b/python/jsbeautifier/tests/testjsbeautifier.py
index 62f37bc55..14b5dcd3e 100644
--- a/python/jsbeautifier/tests/testjsbeautifier.py
+++ b/python/jsbeautifier/tests/testjsbeautifier.py
@@ -67,8 +67,8 @@ def test_beautifier(self):
         bt('a=0xff+4', 'a = 0xff + 4');
         bt('a = [1, 2, 3, 4]');
         bt('F*(g/=f)*g+b', 'F * (g /= f) * g + b');
-        bt('a.b({c:d})', "a.b({\n        c: d\n    })");
-        bt('a.b\n(\n{\nc:\nd\n}\n)', "a.b({\n        c: d\n    })");
+        bt('a.b({c:d})', "a.b({\n    c: d\n})");
+        bt('a.b\n(\n{\nc:\nd\n}\n)', "a.b({\n    c: d\n})");
         bt('a=!b', 'a = !b');
         bt('a?b:c', 'a ? b : c');
         bt('a?1:2', 'a ? 1 : 2');
@@ -135,7 +135,7 @@ def test_beautifier(self):
         bt('a = [ // comment\n    -1, // comment\n    -1, -1\n]');
         bt('var a = [ // comment\n    -1, // comment\n    -1, -1\n]');
 
-        bt('o = [{a:b},{c:d}]', 'o = [{\n        a: b\n    }, {\n        c: d\n    }\n]');
+        bt('o = [{a:b},{c:d}]', 'o = [{\n    a: b\n}, {\n    c: d\n}]');
 
         bt("if (a) {\n    do();\n}"); # was: extra space appended
 
@@ -189,7 +189,7 @@ def test_beautifier(self):
         test_fragment('/incomplete-regex');
 
         test_fragment('{a:1},{a:2}', '{\n    a: 1\n}, {\n    a: 2\n}');
-        test_fragment('var ary=[{a:1}, {a:2}];', 'var ary = [{\n        a: 1\n    }, {\n        a: 2\n    }\n];');
+        test_fragment('var ary=[{a:1}, {a:2}];', 'var ary = [{\n    a: 1\n}, {\n    a: 2\n}];');
 
         test_fragment('{a:#1', '{\n    a: #1'); # incomplete
         test_fragment('{a:#', '{\n    a: #'); # incomplete
@@ -266,8 +266,10 @@ def test_beautifier(self):
         bt('return\nfunc', 'return\nfunc');
         bt('catch(e)', 'catch (e)');
 
-        bt('var a=1,b={foo:2,bar:3},{baz:4,wham:5},c=4;', 'var a = 1,\n    b = {\n        foo: 2,\n        bar: 3\n    }, {\n        baz: 4,\n        wham: 5\n    }, c = 4;');
-        bt('var a=1,b={foo:2,bar:3},{baz:4,wham:5},\nc=4;', 'var a = 1,\n    b = {\n        foo: 2,\n        bar: 3\n    }, {\n        baz: 4,\n        wham: 5\n    },\n    c = 4;');
+        bt('var a=1,b={foo:2,bar:3},{baz:4,wham:5},c=4;',
+           'var a = 1,\n    b = {\n        foo: 2,\n        bar: 3\n    }, {\n        baz: 4,\n        wham: 5\n    }, c = 4;');
+        bt('var a=1,b={foo:2,bar:3},{baz:4,wham:5},\nc=4;',
+           'var a = 1,\n    b = {\n        foo: 2,\n        bar: 3\n    }, {\n        baz: 4,\n        wham: 5\n    },\n    c = 4;');
 
         # inline comment
         bt('function x(/*int*/ start, /*string*/ foo)', 'function x( /*int*/ start, /*string*/ foo)');
@@ -384,20 +386,19 @@ def test_beautifier(self):
         bt("a = ['a', 'b', 'c',\n        'd', 'e', 'f',\n            'g', 'h', 'i']",
             "a = ['a', 'b', 'c',\n    'd', 'e', 'f',\n    'g', 'h', 'i'\n]");
         bt('var x = [{}\n]', 'var x = [{}]');
-        bt('var x = [{foo:bar}\n]', 'var x = [{\n        foo: bar\n    }\n]');
+        bt('var x = [{foo:bar}\n]', 'var x = [{\n    foo: bar\n}]');
         bt("a = ['something',\n    'completely',\n    'different'];\nif (x);",
             "a = ['something',\n    'completely',\n    'different'\n];\nif (x);");
         bt("a = ['a','b','c']", "a = ['a', 'b', 'c']");
         bt("a = ['a',   'b','c']", "a = ['a', 'b', 'c']");
         bt("x = [{'a':0}]",
-            "x = [{\n        'a': 0\n    }\n]");
-        # this is not great, but is accurate
+            "x = [{\n    'a': 0\n}]");
         bt('{a([[a1]], {b;});}',
-            '{\n    a([\n            [a1]\n        ], {\n            b;\n        });\n}');
+            '{\n    a([\n        [a1]\n    ], {\n        b;\n    });\n}');
         bt("a();\n   [\n   ['sdfsdfsd'],\n        ['sdfsdfsdf']\n   ].toString();",
             "a();\n[\n    ['sdfsdfsd'],\n    ['sdfsdfsdf']\n].toString();");
         bt("function() {\n    Foo([\n        ['sdfsdfsd'],\n        ['sdfsdfsdf']\n    ]);\n}",
-            "function() {\n    Foo([\n            ['sdfsdfsd'],\n            ['sdfsdfsdf']\n        ]);\n}");
+            "function() {\n    Foo([\n        ['sdfsdfsd'],\n        ['sdfsdfsdf']\n    ]);\n}");
 
         self.options.keep_array_indentation = True;
         bt("a = ['a', 'b', 'c',\n    'd', 'e', 'f']");
@@ -409,9 +410,9 @@ def test_beautifier(self):
         bt("a = ['a','b','c']", "a = ['a', 'b', 'c']");
         bt("a = ['a',   'b','c']", "a = ['a', 'b', 'c']");
         bt("x = [{'a':0}]",
-            "x = [{\n        'a': 0\n    }]");
+            "x = [{\n    'a': 0\n}]");
         bt('{a([[a1]], {b;});}',
-            '{\n    a([[a1]], {\n            b;\n        });\n}');
+            '{\n    a([[a1]], {\n        b;\n    });\n}');
         bt("a();\n   [\n   ['sdfsdfsd'],\n        ['sdfsdfsdf']\n   ].toString();",
             "a();\n   [\n   ['sdfsdfsd'],\n        ['sdfsdfsdf']\n   ].toString();");
         bt("function() {\n    Foo([\n        ['sdfsdfsd'],\n        ['sdfsdfsdf']\n    ]);\n}",
@@ -826,7 +827,7 @@ def test_beautifier(self):
                       'if (wraps_can_occur && inside_an_if_block) that_is_\n.okay();',
                       # expected #
                       'foo.bar().baz().cucumber((fat &&\n' +
-                      '        "sassy") || (leans && mean));\n' +
+                      '    "sassy") || (leans && mean));\n' +
                       'Test_very_long_variable_name_this_should_never_wrap\n' +
                       '    .but_this_can\n' +
                       'if (wraps_can_occur &&\n' +
@@ -902,7 +903,7 @@ def test_beautifier(self):
                       'if (wraps_can_occur && inside_an_if_block) that_is_\n.okay();',
                       # expected #
                       'foo.bar().baz().cucumber((fat &&\n' +
-                      '        "sassy") || (leans && mean));\n' +
+                      '    "sassy") || (leans && mean));\n' +
                       'Test_very_long_variable_name_this_should_never_wrap\n' +
                       '    .but_this_can\n' +
                       'if (wraps_can_occur &&\n' +
@@ -952,6 +953,12 @@ def test_beautifier(self):
         bt('if (foo) // comment\n    (bar());');
         bt('if (foo) // comment\n    (bar());');
         bt('if (foo) // comment\n    /asdf/;');
+        bt('this.oa = new OAuth(\n' +
+           '    _requestToken,\n' +
+           '    _accessToken,\n' +
+           '    consumer_key\n' +
+           ');',
+           'this.oa = new OAuth(_requestToken, _accessToken, consumer_key);');
         bt('foo = {\n    x: y, // #44\n    w: z // #44\n}');
         bt('switch (x) {\n    case "a":\n        // comment on newline\n        break;\n    case "b": // comment on same line\n        break;\n}');
 
@@ -1020,6 +1027,11 @@ def test_beautifier(self):
         bt('if (foo) // comment\n    (bar());');
         bt('if (foo) // comment\n    (bar());');
         bt('if (foo) // comment\n    /asdf/;');
+        bt('this.oa = new OAuth(\n' +
+           '    _requestToken,\n' +
+           '    _accessToken,\n' +
+           '    consumer_key\n' +
+           ');');
         bt('foo = {\n    x: y, // #44\n    w: z // #44\n}');
         bt('switch (x) {\n    case "a":\n        // comment on newline\n        break;\n    case "b": // comment on same line\n        break;\n}');
 
@@ -1166,14 +1178,15 @@ def test_beautifier(self):
 
 
         # START tests for issue 268 and 275
+        bt('obj.last(a, function() {\n' +
+           '    var test;\n' +
+           '});\n' +
+           'var test = 1;');
+
         bt('obj.last(a,\n' +
            '    function() {\n' +
            '        var test;\n' +
            '    });\n' +
-           'var test = 1;',
-           'obj.last(a, function() {\n' +
-           '    var test;\n' +
-           '});\n' +
            'var test = 1;');
 
         bt('(function() {if (!window.FOO) window.FOO || (window.FOO = function() {var b = {bar: "zort"};});})();',
@@ -1186,6 +1199,46 @@ def test_beautifier(self):
            '})();');
         # END tests for issue 268 and 275
 
+        # START tests for issue 281
+        bt('define(["dojo/_base/declare", "my/Employee", "dijit/form/Button",\n' +
+           '    "dojo/_base/lang", "dojo/Deferred"\n' +
+           '], function(declare, Employee, Button, lang, Deferred) {\n' +
+           '    return declare(Employee, {\n' +
+           '        constructor: function() {\n' +
+           '            new Button({\n' +
+           '                onClick: lang.hitch(this, function() {\n' +
+           '                    new Deferred().then(lang.hitch(this, function() {\n' +
+           '                        this.salary * 0.25;\n' +
+           '                    }));\n' +
+           '                })\n' +
+           '            });\n' +
+           '        }\n' +
+           '    });\n' +
+           '});');
+        bt('define(["dojo/_base/declare", "my/Employee", "dijit/form/Button",\n' +
+           '        "dojo/_base/lang", "dojo/Deferred"\n' +
+           '    ],\n' +
+           '    function(declare, Employee, Button, lang, Deferred) {\n' +
+           '        return declare(Employee, {\n' +
+           '            constructor: function() {\n' +
+           '                new Button({\n' +
+           '                    onClick: lang.hitch(this, function() {\n' +
+           '                        new Deferred().then(lang.hitch(this, function() {\n' +
+           '                            this.salary * 0.25;\n' +
+           '                        }));\n' +
+           '                    })\n' +
+           '                });\n' +
+           '            }\n' +
+           '        });\n' +
+           '    });');
+        # END tests for issue 281
+
+        # This is what I think these should look like related #256
+        # we don't have the ability yet
+        #bt('var a=1,b={bang:2},c=3;',
+        #   'var a = 1,\n    b = {\n        bang: 2\n    },\n     c = 3;');
+        #bt('var a={bing:1},b=2,c=3;',
+        #   'var a = {\n        bing: 1\n    },\n    b = 2,\n    c = 3;');