Bug 1700051: part 11) Change `mozInlineSpellStatus::InitForRange` to static factory...
[gecko.git] / devtools / shared / indentation.js
blobe9fb912842ac92212162b95bd5d13935d0ddd4cc
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 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;
15 /**
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.
18  *
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.
24  */
25 function getTabPrefs() {
26   const indentWithTabs = !Services.prefs.getBoolPref(EXPAND_TAB);
27   const indentUnit = Services.prefs.getIntPref(TAB_SIZE);
28   return { indentUnit, indentWithTabs };
31 /**
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.
34  *
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.
39  */
40 function getIndentationFromPrefs() {
41   const shouldDetect = Services.prefs.getBoolPref(DETECT_INDENT);
42   if (shouldDetect) {
43     return false;
44   }
46   return getTabPrefs();
49 /**
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.
52  *
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.
57  *
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.
63  */
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);
69   if (shouldDetect) {
70     const indent = detectIndentation(iterFunc);
71     if (indent != null) {
72       indentWithTabs = indent.tabs;
73       indentUnit = indent.spaces ? indent.spaces : indentUnit;
74     }
75   }
77   return { indentUnit, indentWithTabs };
80 /**
81  * A wrapper for @see getIndentationFromIteration which computes the
82  * indentation of a given string.
83  *
84  * @param {String} string the input text
85  * @return {Object} an object of the same form as returned by
86  *                  getIndentationFromIteration
87  */
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);
92   };
93   return getIndentationFromIteration(iteratorFn);
96 /**
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.
100  */
101 function detectIndentation(textIteratorFn) {
102   // # spaces indent -> # lines with that indent
103   const spaces = {};
104   // indentation width of the last line we saw
105   let last = 0;
106   // # of lines that start with a tab
107   let tabs = 0;
108   // # of indented lines (non-zero indent)
109   let total = 0;
111   textIteratorFn(0, DETECT_INDENT_MAX_LINES, text => {
112     if (text.startsWith("\t")) {
113       tabs++;
114       total++;
115       return;
116     }
117     let width = 0;
118     while (text[width] === " ") {
119       width++;
120     }
121     // don't count lines that are all spaces
122     if (width == text.length) {
123       last = 0;
124       return;
125     }
126     if (width > 1) {
127       total++;
128     }
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;
134     }
135     last = width;
136   });
138   // this file is not indented at all
139   if (total == 0) {
140     return null;
141   }
143   // mark as tabs if they start more than half the lines
144   if (tabs >= total / 2) {
145     return { tabs: true };
146   }
148   // find most frequent non-zero width difference between adjacent lines
149   let freqIndent = null,
150     max = 1;
151   for (let width in spaces) {
152     width = parseInt(width, 10);
153     const tally = spaces[width];
154     if (tally > max) {
155       max = tally;
156       freqIndent = width;
157     }
158   }
159   if (!freqIndent) {
160     return null;
161   }
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;