CodeMirrorFormatters.js 15 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377
  1. // In the inspector token types have been modified to include extra mode information
  2. // after the actual token type. So we can't do token === "foo". So instead we do
  3. // /\bfoo\b/.test(token).
  4. CodeMirror.extendMode("javascript", {
  5. shouldHaveSpaceBeforeToken: function(lastToken, lastContent, token, state, content, isComment)
  6. {
  7. if (!token) {
  8. if (content === "(") // Most keywords like "if (" but not "function(" or "typeof(".
  9. return lastToken && /\bkeyword\b/.test(lastToken) && (lastContent !== "function" && lastContent !== "typeof" && lastContent !== "instanceof");
  10. if (content === ":") // Ternary.
  11. return (state.lexical.type === "stat" || state.lexical.type === ")");
  12. if (content === "!") // Unary ! should not be confused with "!=".
  13. return false;
  14. return "+-/*&&||!===+=-=>=<=?".indexOf(content) >= 0; // Operators.
  15. }
  16. if (isComment)
  17. return true;
  18. if (/\bkeyword\b/.test(token)) { // Most keywords require spaces before them, unless a '}' can come before it.
  19. if (content === "else" || content === "catch" || content === "finally")
  20. return lastContent === "}";
  21. return false;
  22. }
  23. return false;
  24. },
  25. shouldHaveSpaceAfterLastToken: function(lastToken, lastContent, token, state, content, isComment)
  26. {
  27. if (lastToken && /\bkeyword\b/.test(lastToken)) { // Most keywords require spaces after them, unless a '{' or ';' can come after it.
  28. if (lastContent === "else")
  29. return true;
  30. if (lastContent === "catch")
  31. return true;
  32. if (lastContent === "return")
  33. return content !== ";";
  34. if (lastContent === "throw")
  35. return true;
  36. if (lastContent === "try")
  37. return true;
  38. if (lastContent === "finally")
  39. return true;
  40. return false;
  41. }
  42. if (lastToken && /\bcomment\b/.test(lastToken)) // Embedded /* comment */.
  43. return true;
  44. if (lastContent === ")") // "){".
  45. return content === "{";
  46. if (lastContent === ";") // In for loop.
  47. return state.lexical.type === ")";
  48. if (lastContent === "!") // Unary ! should not be confused with "!=".
  49. return false;
  50. return ",+-/*&&||:!===+=-=>=<=?".indexOf(lastContent) >= 0; // Operators.
  51. },
  52. newlinesAfterToken: function(lastToken, lastContent, token, state, content, isComment)
  53. {
  54. if (!token) {
  55. if (content === ",") // In object literals, like in {a:1,b:2}, but not in param lists or vardef lists.
  56. return state.lexical.type === "}" ? 1 : 0;
  57. if (content === ";") // Everywhere except in for loop conditions.
  58. return state.lexical.type !== ")" ? 1 : 0;
  59. if (content === ":" && state.lexical.type === "}" && state.lexical.prev && state.lexical.prev.type === "form") // Switch case/default.
  60. return 1;
  61. return content.length === 1 && "{}".indexOf(content) >= 0 ? 1 : 0; // After braces.
  62. }
  63. if (isComment)
  64. return 1;
  65. return 0;
  66. },
  67. removeLastNewline: function(lastToken, lastContent, token, state, content, isComment, firstTokenOnLine)
  68. {
  69. if (!token) {
  70. if (content === "}") // "{}".
  71. return lastContent === "{";
  72. if (content === ";") // "x = {};" or ";;".
  73. return "};".indexOf(lastContent) >= 0;
  74. if (content === ":") // Ternary.
  75. return lastContent === "}" && (state.lexical.type === "stat" || state.lexical.type === ")");
  76. if (",().".indexOf(content) >= 0) // "})", "}.bind", "function() { ... }()", or "}, false)".
  77. return lastContent === "}";
  78. return false;
  79. }
  80. if (isComment) { // Comment after semicolon.
  81. if (!firstTokenOnLine && lastContent === ";")
  82. return true;
  83. return false;
  84. }
  85. if (/\bkeyword\b/.test(token)) {
  86. if (content === "else" || content === "catch" || content === "finally") // "} else", "} catch", "} finally"
  87. return lastContent === "}";
  88. return false;
  89. }
  90. return false;
  91. },
  92. indentAfterToken: function(lastToken, lastContent, token, state, content, isComment)
  93. {
  94. return content === "{" || content === "case" || content === "default";
  95. },
  96. newlineBeforeToken: function(lastToken, lastContent, token, state, content, isComment)
  97. {
  98. if (state._jsPrettyPrint.shouldIndent)
  99. return true;
  100. return content === "}" && lastContent !== "{"; // "{}"
  101. },
  102. indentBeforeToken: function(lastToken, lastContent, token, state, content, isComment)
  103. {
  104. if (state._jsPrettyPrint.shouldIndent)
  105. return true;
  106. return false;
  107. },
  108. dedentsBeforeToken: function(lastToken, lastContent, token, state, content, isComment)
  109. {
  110. var dedent = 0;
  111. if (state._jsPrettyPrint.shouldDedent)
  112. dedent += state._jsPrettyPrint.dedentSize;
  113. if (!token && content === "}")
  114. dedent += 1;
  115. else if (token && /\bkeyword\b/.test(token) && (content === "case" || content === "default"))
  116. dedent += 1;
  117. return dedent;
  118. },
  119. modifyStateForTokenPre: function(lastToken, lastContent, token, state, content, isComment)
  120. {
  121. if (!state._jsPrettyPrint) {
  122. state._jsPrettyPrint = {
  123. indentCount: 0, // How far have we indented because of single statement blocks.
  124. shouldIndent: false, // Signal we should indent on entering a single statement block.
  125. shouldDedent: false, // Signal we should dedent on leaving a single statement block.
  126. dedentSize: 0, // How far we should dedent when leaving a single statement block.
  127. lastIfIndentCount: 0, // Keep track of the indent the last time we saw an if without braces.
  128. openBraceStartMarkers: [], // Keep track of non-single statement blocks.
  129. openBraceTrackingCount: -1, // Keep track of "{" and "}" in non-single statement blocks.
  130. };
  131. }
  132. // - Entering:
  133. // - Preconditions:
  134. // - last lexical was a "form" we haven't encountered before
  135. // - last content was ")", "else", or "do"
  136. // - current lexical is not ")" (in an expression or condition)
  137. // - Cases:
  138. // 1. "{"
  139. // - indent +0
  140. // - save this indent size so when we encounter the "}" we know how far to dedent
  141. // 2. "else if"
  142. // - indent +0 and do not signal to add a newline and indent
  143. // - mark the last if location so when we encounter an "else" we know how far to dedent
  144. // - mark the lexical state so we know we are inside a single statement block
  145. // 3. Token without brace.
  146. // - indent +1 and signal to add a newline and indent
  147. // - mark the last if location so when we encounter an "else" we know how far to dedent
  148. // - mark the lexical state so we know we are inside a single statement block
  149. if (!isComment && state.lexical.prev && state.lexical.prev.type === "form" && !state.lexical.prev._jsPrettyPrintMarker && (lastContent === ")" || lastContent === "else" || lastContent === "do") && (state.lexical.type !== ")")) {
  150. if (content === "{") {
  151. // Save the state at the opening brace so we can return to it when we see "}".
  152. var savedState = {indentCount:state._jsPrettyPrint.indentCount, openBraceTrackingCount:state._jsPrettyPrint.openBraceTrackingCount};
  153. state._jsPrettyPrint.openBraceStartMarkers.push(savedState);
  154. state._jsPrettyPrint.openBraceTrackingCount = 1;
  155. } else if (state.lexical.type !== "}") {
  156. // Increase the indent count. Signal for a newline and indent if needed.
  157. if (!(lastContent === "else" && content === "if")) {
  158. state._jsPrettyPrint.indentCount++;
  159. state._jsPrettyPrint.shouldIndent = true;
  160. }
  161. state.lexical.prev._jsPrettyPrintMarker = true;
  162. if (state._jsPrettyPrint.enteringIf)
  163. state._jsPrettyPrint.lastIfIndentCount = state._jsPrettyPrint.indentCount - 1;
  164. }
  165. }
  166. // - Leaving:
  167. // - Preconditions:
  168. // - we must be indented
  169. // - ignore ";", wait for the next token instead.
  170. // - Cases:
  171. // 1. "else"
  172. // - dedent to the last "if"
  173. // 2. "}" and all braces we saw are balanced
  174. // - dedent to the last "{"
  175. // 3. Token without a marker on the stack
  176. // - dedent all the way
  177. else if (state._jsPrettyPrint.indentCount) {
  178. console.assert(!state._jsPrettyPrint.shouldDedent);
  179. console.assert(!state._jsPrettyPrint.dedentSize);
  180. // Track "{" and "}" to know when the "}" is really closing a block.
  181. if (!isComment) {
  182. if (content === "{")
  183. state._jsPrettyPrint.openBraceTrackingCount++;
  184. else if (content === "}")
  185. state._jsPrettyPrint.openBraceTrackingCount--;
  186. }
  187. if (content === ";") {
  188. // Ignore.
  189. } else if (content === "else") {
  190. // Dedent to the last "if".
  191. if (lastContent !== "}") {
  192. state._jsPrettyPrint.shouldDedent = true;
  193. state._jsPrettyPrint.dedentSize = state._jsPrettyPrint.indentCount - state._jsPrettyPrint.lastIfIndentCount;
  194. state._jsPrettyPrint.lastIfIndentCount = 0;
  195. }
  196. } else if (content === "}" && !state._jsPrettyPrint.openBraceTrackingCount && state._jsPrettyPrint.openBraceStartMarkers.length) {
  197. // Dedent to the last "{".
  198. var savedState = state._jsPrettyPrint.openBraceStartMarkers.pop();
  199. state._jsPrettyPrint.shouldDedent = true;
  200. state._jsPrettyPrint.dedentSize = state._jsPrettyPrint.indentCount - savedState.indentCount;
  201. state._jsPrettyPrint.openBraceTrackingCount = savedState.openBraceTrackingCount;
  202. } else {
  203. // Dedent all the way.
  204. var shouldDedent = true;
  205. var lexical = state.lexical.prev;
  206. while (lexical) {
  207. if (lexical._jsPrettyPrintMarker) {
  208. shouldDedent = false;
  209. break;
  210. }
  211. lexical = lexical.prev;
  212. }
  213. if (shouldDedent) {
  214. state._jsPrettyPrint.shouldDedent = true;
  215. state._jsPrettyPrint.dedentSize = state._jsPrettyPrint.indentCount;
  216. }
  217. }
  218. }
  219. // Signal for when we will be entering an if.
  220. if (token && state.lexical.type === "form" && state.lexical.prev && state.lexical.prev !== "form" && /\bkeyword\b/.test(token))
  221. state._jsPrettyPrint.enteringIf = (content === "if");
  222. },
  223. modifyStateForTokenPost: function(lastToken, lastContent, token, state, content, isComment)
  224. {
  225. if (state._jsPrettyPrint.shouldIndent)
  226. state._jsPrettyPrint.shouldIndent = false;
  227. if (state._jsPrettyPrint.shouldDedent) {
  228. state._jsPrettyPrint.indentCount -= state._jsPrettyPrint.dedentSize;
  229. state._jsPrettyPrint.dedentSize = 0;
  230. state._jsPrettyPrint.shouldDedent = false;
  231. }
  232. }
  233. });
  234. CodeMirror.extendMode("css-base", {
  235. shouldHaveSpaceBeforeToken: function(lastToken, lastContent, token, state, content, isComment)
  236. {
  237. if (!token) {
  238. if (content === "{")
  239. return true;
  240. return false;
  241. }
  242. if (isComment)
  243. return true;
  244. if (/\bkeyword\b/.test(token)) {
  245. if (content.charAt(0) === "!") // "!important".
  246. return true;
  247. return false;
  248. }
  249. return false;
  250. },
  251. shouldHaveSpaceAfterLastToken: function(lastToken, lastContent, token, state, content, isComment)
  252. {
  253. if (!lastToken) {
  254. if (lastContent === ",")
  255. return true;
  256. return false;
  257. }
  258. if (/\boperator\b/.test(lastToken)) {
  259. if (lastContent === ":") // Space in "prop: value" but not in a selectors "a:link" or "div::after" or media queries "(max-device-width:480px)".
  260. return state.stack.lastValue === "propertyValue";
  261. return false;
  262. }
  263. if (/\bcomment\b/.test(lastToken))
  264. return true;
  265. return false;
  266. },
  267. newlinesAfterToken: function(lastToken, lastContent, token, state, content, isComment)
  268. {
  269. if (!token) {
  270. if (content === ";")
  271. return 1;
  272. if (content === ",") { // "a,b,c,...,z{}" rule list at top level or in @media top level and only if the line length will be large.
  273. if ((!state.stack.length || state.stack.lastValue === "@media{") && state._cssPrettyPrint.lineLength > 60) {
  274. state._cssPrettyPrint.lineLength = 0;
  275. return 1;
  276. }
  277. return 0;
  278. }
  279. if (content === "{")
  280. return 1;
  281. if (content === "}") // 2 newlines between rule declarations.
  282. return 2;
  283. return 0;
  284. }
  285. if (isComment)
  286. return 1;
  287. return 0;
  288. },
  289. removeLastNewline: function(lastToken, lastContent, token, state, content, isComment, firstTokenOnLine)
  290. {
  291. if (isComment) { // Comment after semicolon.
  292. if (!firstTokenOnLine && lastContent === ";")
  293. return true;
  294. return false;
  295. }
  296. return content === "}" && (lastContent === "{" || lastContent === "}"); // "{}" and "}\n}" when closing @media.
  297. },
  298. indentAfterToken: function(lastToken, lastContent, token, state, content, isComment)
  299. {
  300. return content === "{";
  301. },
  302. newlineBeforeToken: function(lastToken, lastContent, token, state, content, isComment)
  303. {
  304. return content === "}" && (lastContent !== "{" && lastContent !== "}"); // "{}" and "}\n}" when closing @media.
  305. },
  306. indentBeforeToken: function(lastToken, lastContent, token, state, content, isComment)
  307. {
  308. return false;
  309. },
  310. dedentsBeforeToken: function(lastToken, lastContent, token, state, content, isComment)
  311. {
  312. return content === "}" ? 1 : 0;
  313. },
  314. modifyStateForTokenPost: function(lastToken, lastContent, token, state, content, isComment)
  315. {
  316. if (!state._cssPrettyPrint)
  317. state._cssPrettyPrint = {lineLength: 0};
  318. // In order insert newlines in selector lists we need keep track of the length of the current line.
  319. // This isn't exact line length, only the builder knows that, but it is good enough to get an idea.
  320. // If we are at a top level, keep track of the current line length, otherwise we reset to 0.
  321. if (!state.stack.length || state.stack.lastValue === "@media{")
  322. state._cssPrettyPrint.lineLength += content.length;
  323. else
  324. state._cssPrettyPrint.lineLength = 0;
  325. }
  326. });