Formatter.js 5.4 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140
  1. function Formatter(codeMirror, builder)
  2. {
  3. console.assert(codeMirror);
  4. console.assert(builder);
  5. this._codeMirror = codeMirror;
  6. this._builder = builder;
  7. this._lastToken = null;
  8. this._lastContent = "";
  9. };
  10. Formatter.prototype = {
  11. constructor: Formatter,
  12. // Public
  13. format: function(from, to)
  14. {
  15. console.assert(this._builder.originalContent === null);
  16. if (this._builder.originalContent !== null)
  17. return;
  18. var outerMode = this._codeMirror.getMode();
  19. var content = this._codeMirror.getRange(from, to);
  20. var state = CodeMirror.copyState(outerMode, this._codeMirror.getTokenAt(from).state);
  21. this._builder.setOriginalContent(content);
  22. var lineOffset = 0;
  23. var lines = content.split("\n");
  24. for (var i = 0; i < lines.length; ++i) {
  25. var line = lines[i];
  26. var startOfNewLine = true;
  27. var firstTokenOnLine = true;
  28. var stream = new CodeMirror.StringStream(line);
  29. while (!stream.eol()) {
  30. var innerMode = CodeMirror.innerMode(outerMode, state);
  31. var token = outerMode.token(stream, state);
  32. var isWhiteSpace = token === null && /^\s*$/.test(stream.current());
  33. this._handleToken(innerMode.mode, token, state, stream, lineOffset + stream.start, isWhiteSpace, startOfNewLine, firstTokenOnLine);
  34. stream.start = stream.pos;
  35. startOfNewLine = false;
  36. if (firstTokenOnLine && !isWhiteSpace)
  37. firstTokenOnLine = false;
  38. }
  39. if (firstTokenOnLine)
  40. this._handleEmptyLine();
  41. lineOffset += line.length + 1; // +1 for the "\n" removed in split.
  42. this._handleLineEnding(lineOffset - 1); // -1 for the index of the "\n".
  43. }
  44. this._builder.finish();
  45. },
  46. // Private
  47. _handleToken: function(mode, token, state, stream, originalPosition, isWhiteSpace, startOfNewLine, firstTokenOnLine)
  48. {
  49. // String content of the token.
  50. var content = stream.current();
  51. // Start of a new line. Insert a newline to be safe if code was not-ASI safe. These are collapsed.
  52. if (startOfNewLine)
  53. this._builder.appendNewline();
  54. // Whitespace. Collapse to a single space.
  55. if (isWhiteSpace) {
  56. this._builder.appendSpace();
  57. return;
  58. }
  59. // Avoid some hooks for content in comments.
  60. var isComment = token && /\bcomment\b/.test(token);
  61. if (mode.modifyStateForTokenPre)
  62. mode.modifyStateForTokenPre(this._lastToken, this._lastContent, token, state, content, isComment);
  63. // Should we remove the last newline?
  64. if (this._builder.lastTokenWasNewline && mode.removeLastNewline(this._lastToken, this._lastContent, token, state, content, isComment, firstTokenOnLine))
  65. this._builder.removeLastNewline();
  66. // Add whitespace after the last token?
  67. if (!this._builder.lastTokenWasWhitespace && mode.shouldHaveSpaceAfterLastToken(this._lastToken, this._lastContent, token, state, content, isComment))
  68. this._builder.appendSpace();
  69. // Add whitespace before this token?
  70. if (!this._builder.lastTokenWasWhitespace && mode.shouldHaveSpaceBeforeToken(this._lastToken, this._lastContent, token, state, content, isComment))
  71. this._builder.appendSpace();
  72. // Should we dedent before this token?
  73. var dedents = mode.dedentsBeforeToken(this._lastToken, this._lastContent, token, state, content, isComment);
  74. while (dedents-- > 0)
  75. this._builder.dedent();
  76. // Should we add a newline before this token?
  77. if (mode.newlineBeforeToken(this._lastToken, this._lastContent, token, state, content, isComment))
  78. this._builder.appendNewline();
  79. // Should we indent before this token?
  80. if (mode.indentBeforeToken(this._lastToken, this._lastContent, token, state, content, isComment))
  81. this._builder.indent();
  82. // Append token.
  83. this._builder.appendToken(content, originalPosition);
  84. // Let the pretty printer update any state it keeps track of.
  85. if (mode.modifyStateForTokenPost)
  86. mode.modifyStateForTokenPost(this._lastToken, this._lastContent, token, state, content, isComment);
  87. // Should we indent or dedent after this token?
  88. if (!isComment && mode.indentAfterToken(this._lastToken, this._lastContent, token, state, content, isComment))
  89. this._builder.indent();
  90. // Should we add newlines after this token?
  91. var newlines = mode.newlinesAfterToken(this._lastToken, this._lastContent, token, state, content, isComment);
  92. if (newlines)
  93. this._builder.appendMultipleNewlines(newlines);
  94. // Record this token as the last token.
  95. this._lastToken = token;
  96. this._lastContent = content;
  97. },
  98. _handleEmptyLine: function()
  99. {
  100. // Preserve original whitespace only lines by adding a newline.
  101. // However, don't do this if the builder just added multiple newlines.
  102. if (!(this._builder.lastTokenWasNewline && this._builder.lastNewlineAppendWasMultiple))
  103. this._builder.appendNewline(true);
  104. },
  105. _handleLineEnding: function(originalNewLinePosition)
  106. {
  107. // Record the original line ending.
  108. this._builder.addOriginalLineEnding(originalNewLinePosition);
  109. }
  110. };