Bug 1839316: part 5) Guard the "fetchpriority" attribute behind a pref. r=kershaw...
[gecko.git] / devtools / shared / indentation.js
blob13c80f32254d67b5a2af79b013b2fbdf32946767
1 /*
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/. */
6 "use strict";
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;
13 /**
14  * Get the number of indentation units to use to indent a "block"
15  * and a boolean indicating whether indentation must be done using tabs.
16  *
17  * @return {Object} an object of the form {indentUnit, indentWithTabs}.
18  *        |indentUnit| is the number of indentation units to use
19  *        to indent a "block".
20  *        |indentWithTabs| is a boolean which is true if indentation
21  *        should be done using tabs.
22  */
23 function getTabPrefs() {
24   const indentWithTabs = !Services.prefs.getBoolPref(EXPAND_TAB);
25   const indentUnit = Services.prefs.getIntPref(TAB_SIZE, 2);
26   return { indentUnit, indentWithTabs };
29 /**
30  * Get the indentation to use in an editor, or return false if the user has
31  * asked for the indentation to be guessed from some text.
32  *
33  * @return {false | Object}
34  *        Returns false if the "detect indentation" pref is set.
35  *        If the pref is not set, returns an object of the same
36  *        form as returned by getTabPrefs.
37  */
38 function getIndentationFromPrefs() {
39   const shouldDetect = Services.prefs.getBoolPref(DETECT_INDENT);
40   if (shouldDetect) {
41     return false;
42   }
44   return getTabPrefs();
47 /**
48  * Given a function that can iterate over some text, compute the indentation to
49  * use.  This consults various prefs to arrive at a decision.
50  *
51  * @param {Function} iterFunc A function of three arguments:
52  *        (start, end, callback); where |start| and |end| describe
53  *        the range of text lines to examine, and |callback| is a function
54  *        to be called with the text of each line.
55  *
56  * @return {Object} an object of the form {indentUnit, indentWithTabs}.
57  *        |indentUnit| is the number of indentation units to use
58  *        to indent a "block".
59  *        |indentWithTabs| is a boolean which is true if indentation
60  *        should be done using tabs.
61  */
62 function getIndentationFromIteration(iterFunc) {
63   let indentWithTabs = !Services.prefs.getBoolPref(EXPAND_TAB);
64   let indentUnit = Services.prefs.getIntPref(TAB_SIZE);
65   const shouldDetect = Services.prefs.getBoolPref(DETECT_INDENT);
67   if (shouldDetect) {
68     const indent = detectIndentation(iterFunc);
69     if (indent != null) {
70       indentWithTabs = indent.tabs;
71       indentUnit = indent.spaces ? indent.spaces : indentUnit;
72     }
73   }
75   return { indentUnit, indentWithTabs };
78 /**
79  * A wrapper for @see getIndentationFromIteration which computes the
80  * indentation of a given string.
81  *
82  * @param {String} string the input text
83  * @return {Object} an object of the same form as returned by
84  *                  getIndentationFromIteration
85  */
86 function getIndentationFromString(string) {
87   const iteratorFn = function (start, end, callback) {
88     const split = string.split(/\r\n|\r|\n|\f/);
89     split.slice(start, end).forEach(callback);
90   };
91   return getIndentationFromIteration(iteratorFn);
94 /**
95  * Detect the indentation used in an editor. Returns an object
96  * with 'tabs' - whether this is tab-indented and 'spaces' - the
97  * width of one indent in spaces. Or `null` if it's inconclusive.
98  */
99 function detectIndentation(textIteratorFn) {
100   // # spaces indent -> # lines with that indent
101   const spaces = {};
102   // indentation width of the last line we saw
103   let last = 0;
104   // # of lines that start with a tab
105   let tabs = 0;
106   // # of indented lines (non-zero indent)
107   let total = 0;
109   textIteratorFn(0, DETECT_INDENT_MAX_LINES, text => {
110     if (text.startsWith("\t")) {
111       tabs++;
112       total++;
113       return;
114     }
115     let width = 0;
116     while (text[width] === " ") {
117       width++;
118     }
119     // don't count lines that are all spaces
120     if (width == text.length) {
121       last = 0;
122       return;
123     }
124     if (width > 1) {
125       total++;
126     }
128     // see how much this line is offset from the line above it
129     const indent = Math.abs(width - last);
130     if (indent > 1 && indent <= 8) {
131       spaces[indent] = (spaces[indent] || 0) + 1;
132     }
133     last = width;
134   });
136   // this file is not indented at all
137   if (total == 0) {
138     return null;
139   }
141   // mark as tabs if they start more than half the lines
142   if (tabs >= total / 2) {
143     return { tabs: true };
144   }
146   // find most frequent non-zero width difference between adjacent lines
147   let freqIndent = null,
148     max = 1;
149   for (let width in spaces) {
150     width = parseInt(width, 10);
151     const tally = spaces[width];
152     if (tally > max) {
153       max = tally;
154       freqIndent = width;
155     }
156   }
157   if (!freqIndent) {
158     return null;
159   }
161   return { tabs: false, spaces: freqIndent };
164 exports.EXPAND_TAB = EXPAND_TAB;
165 exports.TAB_SIZE = TAB_SIZE;
166 exports.DETECT_INDENT = DETECT_INDENT;
167 exports.getTabPrefs = getTabPrefs;
168 exports.getIndentationFromPrefs = getIndentationFromPrefs;
169 exports.getIndentationFromIteration = getIndentationFromIteration;
170 exports.getIndentationFromString = getIndentationFromString;