summaryrefslogtreecommitdiffstats
path: root/devtools/shared/indentation.js
diff options
context:
space:
mode:
Diffstat (limited to 'devtools/shared/indentation.js')
-rw-r--r--devtools/shared/indentation.js170
1 files changed, 170 insertions, 0 deletions
diff --git a/devtools/shared/indentation.js b/devtools/shared/indentation.js
new file mode 100644
index 0000000000..13c80f3225
--- /dev/null
+++ b/devtools/shared/indentation.js
@@ -0,0 +1,170 @@
+/*
+ * This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+"use strict";
+
+const EXPAND_TAB = "devtools.editor.expandtab";
+const TAB_SIZE = "devtools.editor.tabsize";
+const DETECT_INDENT = "devtools.editor.detectindentation";
+const DETECT_INDENT_MAX_LINES = 500;
+
+/**
+ * Get the number of indentation units to use to indent a "block"
+ * and a boolean indicating whether indentation must be done using tabs.
+ *
+ * @return {Object} an object of the form {indentUnit, indentWithTabs}.
+ * |indentUnit| is the number of indentation units to use
+ * to indent a "block".
+ * |indentWithTabs| is a boolean which is true if indentation
+ * should be done using tabs.
+ */
+function getTabPrefs() {
+ const indentWithTabs = !Services.prefs.getBoolPref(EXPAND_TAB);
+ const indentUnit = Services.prefs.getIntPref(TAB_SIZE, 2);
+ return { indentUnit, indentWithTabs };
+}
+
+/**
+ * Get the indentation to use in an editor, or return false if the user has
+ * asked for the indentation to be guessed from some text.
+ *
+ * @return {false | Object}
+ * Returns false if the "detect indentation" pref is set.
+ * If the pref is not set, returns an object of the same
+ * form as returned by getTabPrefs.
+ */
+function getIndentationFromPrefs() {
+ const shouldDetect = Services.prefs.getBoolPref(DETECT_INDENT);
+ if (shouldDetect) {
+ return false;
+ }
+
+ return getTabPrefs();
+}
+
+/**
+ * Given a function that can iterate over some text, compute the indentation to
+ * use. This consults various prefs to arrive at a decision.
+ *
+ * @param {Function} iterFunc A function of three arguments:
+ * (start, end, callback); where |start| and |end| describe
+ * the range of text lines to examine, and |callback| is a function
+ * to be called with the text of each line.
+ *
+ * @return {Object} an object of the form {indentUnit, indentWithTabs}.
+ * |indentUnit| is the number of indentation units to use
+ * to indent a "block".
+ * |indentWithTabs| is a boolean which is true if indentation
+ * should be done using tabs.
+ */
+function getIndentationFromIteration(iterFunc) {
+ let indentWithTabs = !Services.prefs.getBoolPref(EXPAND_TAB);
+ let indentUnit = Services.prefs.getIntPref(TAB_SIZE);
+ const shouldDetect = Services.prefs.getBoolPref(DETECT_INDENT);
+
+ if (shouldDetect) {
+ const indent = detectIndentation(iterFunc);
+ if (indent != null) {
+ indentWithTabs = indent.tabs;
+ indentUnit = indent.spaces ? indent.spaces : indentUnit;
+ }
+ }
+
+ return { indentUnit, indentWithTabs };
+}
+
+/**
+ * A wrapper for @see getIndentationFromIteration which computes the
+ * indentation of a given string.
+ *
+ * @param {String} string the input text
+ * @return {Object} an object of the same form as returned by
+ * getIndentationFromIteration
+ */
+function getIndentationFromString(string) {
+ const iteratorFn = function (start, end, callback) {
+ const split = string.split(/\r\n|\r|\n|\f/);
+ split.slice(start, end).forEach(callback);
+ };
+ return getIndentationFromIteration(iteratorFn);
+}
+
+/**
+ * Detect the indentation used in an editor. Returns an object
+ * with 'tabs' - whether this is tab-indented and 'spaces' - the
+ * width of one indent in spaces. Or `null` if it's inconclusive.
+ */
+function detectIndentation(textIteratorFn) {
+ // # spaces indent -> # lines with that indent
+ const spaces = {};
+ // indentation width of the last line we saw
+ let last = 0;
+ // # of lines that start with a tab
+ let tabs = 0;
+ // # of indented lines (non-zero indent)
+ let total = 0;
+
+ textIteratorFn(0, DETECT_INDENT_MAX_LINES, text => {
+ if (text.startsWith("\t")) {
+ tabs++;
+ total++;
+ return;
+ }
+ let width = 0;
+ while (text[width] === " ") {
+ width++;
+ }
+ // don't count lines that are all spaces
+ if (width == text.length) {
+ last = 0;
+ return;
+ }
+ if (width > 1) {
+ total++;
+ }
+
+ // see how much this line is offset from the line above it
+ const indent = Math.abs(width - last);
+ if (indent > 1 && indent <= 8) {
+ spaces[indent] = (spaces[indent] || 0) + 1;
+ }
+ last = width;
+ });
+
+ // this file is not indented at all
+ if (total == 0) {
+ return null;
+ }
+
+ // mark as tabs if they start more than half the lines
+ if (tabs >= total / 2) {
+ return { tabs: true };
+ }
+
+ // find most frequent non-zero width difference between adjacent lines
+ let freqIndent = null,
+ max = 1;
+ for (let width in spaces) {
+ width = parseInt(width, 10);
+ const tally = spaces[width];
+ if (tally > max) {
+ max = tally;
+ freqIndent = width;
+ }
+ }
+ if (!freqIndent) {
+ return null;
+ }
+
+ return { tabs: false, spaces: freqIndent };
+}
+
+exports.EXPAND_TAB = EXPAND_TAB;
+exports.TAB_SIZE = TAB_SIZE;
+exports.DETECT_INDENT = DETECT_INDENT;
+exports.getTabPrefs = getTabPrefs;
+exports.getIndentationFromPrefs = getIndentationFromPrefs;
+exports.getIndentationFromIteration = getIndentationFromIteration;
+exports.getIndentationFromString = getIndentationFromString;