2 * This Source Code Form is subject to the terms of the Mozilla Public
3 * License, v. 2.0. If a copy of the MPL was not distributed with this
4 * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
8 const Services = require("Services");
10 const EXPAND_TAB = "devtools.editor.expandtab";
11 const TAB_SIZE = "devtools.editor.tabsize";
12 const DETECT_INDENT = "devtools.editor.detectindentation";
13 const DETECT_INDENT_MAX_LINES = 500;
16 * Get the number of indentation units to use to indent a "block"
17 * and a boolean indicating whether indentation must be done using tabs.
19 * @return {Object} an object of the form {indentUnit, indentWithTabs}.
20 * |indentUnit| is the number of indentation units to use
21 * to indent a "block".
22 * |indentWithTabs| is a boolean which is true if indentation
23 * should be done using tabs.
25 function getTabPrefs() {
26 const indentWithTabs = !Services.prefs.getBoolPref(EXPAND_TAB);
27 const indentUnit = Services.prefs.getIntPref(TAB_SIZE);
28 return { indentUnit, indentWithTabs };
32 * Get the indentation to use in an editor, or return false if the user has
33 * asked for the indentation to be guessed from some text.
35 * @return {false | Object}
36 * Returns false if the "detect indentation" pref is set.
37 * If the pref is not set, returns an object of the same
38 * form as returned by getTabPrefs.
40 function getIndentationFromPrefs() {
41 const shouldDetect = Services.prefs.getBoolPref(DETECT_INDENT);
50 * Given a function that can iterate over some text, compute the indentation to
51 * use. This consults various prefs to arrive at a decision.
53 * @param {Function} iterFunc A function of three arguments:
54 * (start, end, callback); where |start| and |end| describe
55 * the range of text lines to examine, and |callback| is a function
56 * to be called with the text of each line.
58 * @return {Object} an object of the form {indentUnit, indentWithTabs}.
59 * |indentUnit| is the number of indentation units to use
60 * to indent a "block".
61 * |indentWithTabs| is a boolean which is true if indentation
62 * should be done using tabs.
64 function getIndentationFromIteration(iterFunc) {
65 let indentWithTabs = !Services.prefs.getBoolPref(EXPAND_TAB);
66 let indentUnit = Services.prefs.getIntPref(TAB_SIZE);
67 const shouldDetect = Services.prefs.getBoolPref(DETECT_INDENT);
70 const indent = detectIndentation(iterFunc);
72 indentWithTabs = indent.tabs;
73 indentUnit = indent.spaces ? indent.spaces : indentUnit;
77 return { indentUnit, indentWithTabs };
81 * A wrapper for @see getIndentationFromIteration which computes the
82 * indentation of a given string.
84 * @param {String} string the input text
85 * @return {Object} an object of the same form as returned by
86 * getIndentationFromIteration
88 function getIndentationFromString(string) {
89 const iteratorFn = function(start, end, callback) {
90 const split = string.split(/\r\n|\r|\n|\f/);
91 split.slice(start, end).forEach(callback);
93 return getIndentationFromIteration(iteratorFn);
97 * Detect the indentation used in an editor. Returns an object
98 * with 'tabs' - whether this is tab-indented and 'spaces' - the
99 * width of one indent in spaces. Or `null` if it's inconclusive.
101 function detectIndentation(textIteratorFn) {
102 // # spaces indent -> # lines with that indent
104 // indentation width of the last line we saw
106 // # of lines that start with a tab
108 // # of indented lines (non-zero indent)
111 textIteratorFn(0, DETECT_INDENT_MAX_LINES, text => {
112 if (text.startsWith("\t")) {
118 while (text[width] === " ") {
121 // don't count lines that are all spaces
122 if (width == text.length) {
130 // see how much this line is offset from the line above it
131 const indent = Math.abs(width - last);
132 if (indent > 1 && indent <= 8) {
133 spaces[indent] = (spaces[indent] || 0) + 1;
138 // this file is not indented at all
143 // mark as tabs if they start more than half the lines
144 if (tabs >= total / 2) {
145 return { tabs: true };
148 // find most frequent non-zero width difference between adjacent lines
149 let freqIndent = null,
151 for (let width in spaces) {
152 width = parseInt(width, 10);
153 const tally = spaces[width];
163 return { tabs: false, spaces: freqIndent };
166 exports.EXPAND_TAB = EXPAND_TAB;
167 exports.TAB_SIZE = TAB_SIZE;
168 exports.DETECT_INDENT = DETECT_INDENT;
169 exports.getTabPrefs = getTabPrefs;
170 exports.getIndentationFromPrefs = getIndentationFromPrefs;
171 exports.getIndentationFromIteration = getIndentationFromIteration;
172 exports.getIndentationFromString = getIndentationFromString;