format.js 7.8 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205
  1. /*---------------------------------------------------------------------------------------------
  2. * Copyright (c) Microsoft Corporation. All rights reserved.
  3. * Licensed under the MIT License. See License.txt in the project root for license information.
  4. *--------------------------------------------------------------------------------------------*/
  5. 'use strict';
  6. import { createScanner } from './scanner';
  7. export function format(documentText, range, options) {
  8. var initialIndentLevel;
  9. var formatText;
  10. var formatTextStart;
  11. var rangeStart;
  12. var rangeEnd;
  13. if (range) {
  14. rangeStart = range.offset;
  15. rangeEnd = rangeStart + range.length;
  16. formatTextStart = rangeStart;
  17. while (formatTextStart > 0 && !isEOL(documentText, formatTextStart - 1)) {
  18. formatTextStart--;
  19. }
  20. var endOffset = rangeEnd;
  21. while (endOffset < documentText.length && !isEOL(documentText, endOffset)) {
  22. endOffset++;
  23. }
  24. formatText = documentText.substring(formatTextStart, endOffset);
  25. initialIndentLevel = computeIndentLevel(formatText, options);
  26. }
  27. else {
  28. formatText = documentText;
  29. initialIndentLevel = 0;
  30. formatTextStart = 0;
  31. rangeStart = 0;
  32. rangeEnd = documentText.length;
  33. }
  34. var eol = getEOL(options, documentText);
  35. var lineBreak = false;
  36. var indentLevel = 0;
  37. var indentValue;
  38. if (options.insertSpaces) {
  39. indentValue = repeat(' ', options.tabSize || 4);
  40. }
  41. else {
  42. indentValue = '\t';
  43. }
  44. var scanner = createScanner(formatText, false);
  45. var hasError = false;
  46. function newLineAndIndent() {
  47. return eol + repeat(indentValue, initialIndentLevel + indentLevel);
  48. }
  49. function scanNext() {
  50. var token = scanner.scan();
  51. lineBreak = false;
  52. while (token === 15 /* Trivia */ || token === 14 /* LineBreakTrivia */) {
  53. lineBreak = lineBreak || (token === 14 /* LineBreakTrivia */);
  54. token = scanner.scan();
  55. }
  56. hasError = token === 16 /* Unknown */ || scanner.getTokenError() !== 0 /* None */;
  57. return token;
  58. }
  59. var editOperations = [];
  60. function addEdit(text, startOffset, endOffset) {
  61. if (!hasError && (!range || (startOffset < rangeEnd && endOffset > rangeStart)) && documentText.substring(startOffset, endOffset) !== text) {
  62. editOperations.push({ offset: startOffset, length: endOffset - startOffset, content: text });
  63. }
  64. }
  65. var firstToken = scanNext();
  66. if (firstToken !== 17 /* EOF */) {
  67. var firstTokenStart = scanner.getTokenOffset() + formatTextStart;
  68. var initialIndent = repeat(indentValue, initialIndentLevel);
  69. addEdit(initialIndent, formatTextStart, firstTokenStart);
  70. }
  71. while (firstToken !== 17 /* EOF */) {
  72. var firstTokenEnd = scanner.getTokenOffset() + scanner.getTokenLength() + formatTextStart;
  73. var secondToken = scanNext();
  74. var replaceContent = '';
  75. var needsLineBreak = false;
  76. while (!lineBreak && (secondToken === 12 /* LineCommentTrivia */ || secondToken === 13 /* BlockCommentTrivia */)) {
  77. // comments on the same line: keep them on the same line, but ignore them otherwise
  78. var commentTokenStart = scanner.getTokenOffset() + formatTextStart;
  79. addEdit(' ', firstTokenEnd, commentTokenStart);
  80. firstTokenEnd = scanner.getTokenOffset() + scanner.getTokenLength() + formatTextStart;
  81. needsLineBreak = secondToken === 12 /* LineCommentTrivia */;
  82. replaceContent = needsLineBreak ? newLineAndIndent() : '';
  83. secondToken = scanNext();
  84. }
  85. if (secondToken === 2 /* CloseBraceToken */) {
  86. if (firstToken !== 1 /* OpenBraceToken */) {
  87. indentLevel--;
  88. replaceContent = newLineAndIndent();
  89. }
  90. }
  91. else if (secondToken === 4 /* CloseBracketToken */) {
  92. if (firstToken !== 3 /* OpenBracketToken */) {
  93. indentLevel--;
  94. replaceContent = newLineAndIndent();
  95. }
  96. }
  97. else {
  98. switch (firstToken) {
  99. case 3 /* OpenBracketToken */:
  100. case 1 /* OpenBraceToken */:
  101. indentLevel++;
  102. replaceContent = newLineAndIndent();
  103. break;
  104. case 5 /* CommaToken */:
  105. case 12 /* LineCommentTrivia */:
  106. replaceContent = newLineAndIndent();
  107. break;
  108. case 13 /* BlockCommentTrivia */:
  109. if (lineBreak) {
  110. replaceContent = newLineAndIndent();
  111. }
  112. else if (!needsLineBreak) {
  113. // symbol following comment on the same line: keep on same line, separate with ' '
  114. replaceContent = ' ';
  115. }
  116. break;
  117. case 6 /* ColonToken */:
  118. if (!needsLineBreak) {
  119. replaceContent = ' ';
  120. }
  121. break;
  122. case 10 /* StringLiteral */:
  123. if (secondToken === 6 /* ColonToken */) {
  124. if (!needsLineBreak) {
  125. replaceContent = '';
  126. }
  127. break;
  128. }
  129. // fall through
  130. case 7 /* NullKeyword */:
  131. case 8 /* TrueKeyword */:
  132. case 9 /* FalseKeyword */:
  133. case 11 /* NumericLiteral */:
  134. case 2 /* CloseBraceToken */:
  135. case 4 /* CloseBracketToken */:
  136. if (secondToken === 12 /* LineCommentTrivia */ || secondToken === 13 /* BlockCommentTrivia */) {
  137. if (!needsLineBreak) {
  138. replaceContent = ' ';
  139. }
  140. }
  141. else if (secondToken !== 5 /* CommaToken */ && secondToken !== 17 /* EOF */) {
  142. hasError = true;
  143. }
  144. break;
  145. case 16 /* Unknown */:
  146. hasError = true;
  147. break;
  148. }
  149. if (lineBreak && (secondToken === 12 /* LineCommentTrivia */ || secondToken === 13 /* BlockCommentTrivia */)) {
  150. replaceContent = newLineAndIndent();
  151. }
  152. }
  153. if (secondToken === 17 /* EOF */) {
  154. replaceContent = options.insertFinalNewline ? eol : '';
  155. }
  156. var secondTokenStart = scanner.getTokenOffset() + formatTextStart;
  157. addEdit(replaceContent, firstTokenEnd, secondTokenStart);
  158. firstToken = secondToken;
  159. }
  160. return editOperations;
  161. }
  162. function repeat(s, count) {
  163. var result = '';
  164. for (var i = 0; i < count; i++) {
  165. result += s;
  166. }
  167. return result;
  168. }
  169. function computeIndentLevel(content, options) {
  170. var i = 0;
  171. var nChars = 0;
  172. var tabSize = options.tabSize || 4;
  173. while (i < content.length) {
  174. var ch = content.charAt(i);
  175. if (ch === ' ') {
  176. nChars++;
  177. }
  178. else if (ch === '\t') {
  179. nChars += tabSize;
  180. }
  181. else {
  182. break;
  183. }
  184. i++;
  185. }
  186. return Math.floor(nChars / tabSize);
  187. }
  188. function getEOL(options, text) {
  189. for (var i = 0; i < text.length; i++) {
  190. var ch = text.charAt(i);
  191. if (ch === '\r') {
  192. if (i + 1 < text.length && text.charAt(i + 1) === '\n') {
  193. return '\r\n';
  194. }
  195. return '\r';
  196. }
  197. else if (ch === '\n') {
  198. return '\n';
  199. }
  200. }
  201. return (options && options.eol) || '\n';
  202. }
  203. export function isEOL(text, offset) {
  204. return '\r\n'.indexOf(text.charAt(offset)) !== -1;
  205. }