indentation.js 4.9 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161
  1. /* -*- indent-tabs-mode: nil; js-indent-level: 2; fill-column: 80 -*- */
  2. /* vim:set ts=2 sw=2 sts=2 et tw=80:
  3. * This Source Code Form is subject to the terms of the Mozilla Public
  4. * License, v. 2.0. If a copy of the MPL was not distributed with this
  5. * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
  6. "use strict";
  7. const Services = require("Services");
  8. const EXPAND_TAB = "devtools.editor.expandtab";
  9. const TAB_SIZE = "devtools.editor.tabsize";
  10. const DETECT_INDENT = "devtools.editor.detectindentation";
  11. const DETECT_INDENT_MAX_LINES = 500;
  12. /**
  13. * Get the indentation to use in an editor, or return false if the user has
  14. * asked for the indentation to be guessed from some text.
  15. *
  16. * @return {false | Object}
  17. * Returns false if the "detect indentation" pref is set.
  18. * an object of the form {indentUnit, indentWithTabs}.
  19. * |indentUnit| is the number of indentation units to use
  20. * to indent a "block".
  21. * |indentWithTabs| is a boolean which is true if indentation
  22. * should be done using tabs.
  23. */
  24. function getIndentationFromPrefs() {
  25. let shouldDetect = Services.prefs.getBoolPref(DETECT_INDENT);
  26. if (shouldDetect) {
  27. return false;
  28. }
  29. let indentWithTabs = !Services.prefs.getBoolPref(EXPAND_TAB);
  30. let indentUnit = Services.prefs.getIntPref(TAB_SIZE);
  31. return {indentUnit, indentWithTabs};
  32. }
  33. /**
  34. * Given a function that can iterate over some text, compute the indentation to
  35. * use. This consults various prefs to arrive at a decision.
  36. *
  37. * @param {Function} iterFunc A function of three arguments:
  38. * (start, end, callback); where |start| and |end| describe
  39. * the range of text lines to examine, and |callback| is a function
  40. * to be called with the text of each line.
  41. *
  42. * @return {Object} an object of the form {indentUnit, indentWithTabs}.
  43. * |indentUnit| is the number of indentation units to use
  44. * to indent a "block".
  45. * |indentWithTabs| is a boolean which is true if indentation
  46. * should be done using tabs.
  47. */
  48. function getIndentationFromIteration(iterFunc) {
  49. let indentWithTabs = !Services.prefs.getBoolPref(EXPAND_TAB);
  50. let indentUnit = Services.prefs.getIntPref(TAB_SIZE);
  51. let shouldDetect = Services.prefs.getBoolPref(DETECT_INDENT);
  52. if (shouldDetect) {
  53. let indent = detectIndentation(iterFunc);
  54. if (indent != null) {
  55. indentWithTabs = indent.tabs;
  56. indentUnit = indent.spaces ? indent.spaces : indentUnit;
  57. }
  58. }
  59. return {indentUnit, indentWithTabs};
  60. }
  61. /**
  62. * A wrapper for @see getIndentationFromIteration which computes the
  63. * indentation of a given string.
  64. *
  65. * @param {String} string the input text
  66. * @return {Object} an object of the same form as returned by
  67. * getIndentationFromIteration
  68. */
  69. function getIndentationFromString(string) {
  70. let iteratorFn = function (start, end, callback) {
  71. let split = string.split(/\r\n|\r|\n|\f/);
  72. split.slice(start, end).forEach(callback);
  73. };
  74. return getIndentationFromIteration(iteratorFn);
  75. }
  76. /**
  77. * Detect the indentation used in an editor. Returns an object
  78. * with 'tabs' - whether this is tab-indented and 'spaces' - the
  79. * width of one indent in spaces. Or `null` if it's inconclusive.
  80. */
  81. function detectIndentation(textIteratorFn) {
  82. // # spaces indent -> # lines with that indent
  83. let spaces = {};
  84. // indentation width of the last line we saw
  85. let last = 0;
  86. // # of lines that start with a tab
  87. let tabs = 0;
  88. // # of indented lines (non-zero indent)
  89. let total = 0;
  90. textIteratorFn(0, DETECT_INDENT_MAX_LINES, (text) => {
  91. if (text.startsWith("\t")) {
  92. tabs++;
  93. total++;
  94. return;
  95. }
  96. let width = 0;
  97. while (text[width] === " ") {
  98. width++;
  99. }
  100. // don't count lines that are all spaces
  101. if (width == text.length) {
  102. last = 0;
  103. return;
  104. }
  105. if (width > 1) {
  106. total++;
  107. }
  108. // see how much this line is offset from the line above it
  109. let indent = Math.abs(width - last);
  110. if (indent > 1 && indent <= 8) {
  111. spaces[indent] = (spaces[indent] || 0) + 1;
  112. }
  113. last = width;
  114. });
  115. // this file is not indented at all
  116. if (total == 0) {
  117. return null;
  118. }
  119. // mark as tabs if they start more than half the lines
  120. if (tabs >= total / 2) {
  121. return { tabs: true };
  122. }
  123. // find most frequent non-zero width difference between adjacent lines
  124. let freqIndent = null, max = 1;
  125. for (let width in spaces) {
  126. width = parseInt(width, 10);
  127. let tally = spaces[width];
  128. if (tally > max) {
  129. max = tally;
  130. freqIndent = width;
  131. }
  132. }
  133. if (!freqIndent) {
  134. return null;
  135. }
  136. return { tabs: false, spaces: freqIndent };
  137. }
  138. exports.EXPAND_TAB = EXPAND_TAB;
  139. exports.TAB_SIZE = TAB_SIZE;
  140. exports.DETECT_INDENT = DETECT_INDENT;
  141. exports.getIndentationFromPrefs = getIndentationFromPrefs;
  142. exports.getIndentationFromIteration = getIndentationFromIteration;
  143. exports.getIndentationFromString = getIndentationFromString;