tor-browser

The Tor Browser
git clone https://git.dasho.dev/tor-browser.git
Log | Files | Refs | README | LICENSE

indentation.js (5224B)


      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/. */
      5 
      6 "use strict";
      7 
      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 /**
     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 };
     27 }
     28 
     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  }
     43 
     44  return getTabPrefs();
     45 }
     46 
     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);
     66 
     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  }
     74 
     75  return { indentUnit, indentWithTabs };
     76 }
     77 
     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);
     92 }
     93 
     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;
    108 
    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    }
    127 
    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  });
    135 
    136  // this file is not indented at all
    137  if (total == 0) {
    138    return null;
    139  }
    140 
    141  // mark as tabs if they start more than half the lines
    142  if (tabs >= total / 2) {
    143    return { tabs: true };
    144  }
    145 
    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  }
    160 
    161  return { tabs: false, spaces: freqIndent };
    162 }
    163 
    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;