Bug 1196431 - generalize detectIndentation and move to new file. r=pbro
This commit is contained in:
@@ -8,14 +8,17 @@
|
||||
|
||||
const { Cu, Cc, Ci, components } = require("chrome");
|
||||
|
||||
const TAB_SIZE = "devtools.editor.tabsize";
|
||||
const {
|
||||
EXPAND_TAB,
|
||||
TAB_SIZE,
|
||||
DETECT_INDENT,
|
||||
getIndentationFromIteration
|
||||
} = require("devtools/toolkit/shared/indentation");
|
||||
|
||||
const ENABLE_CODE_FOLDING = "devtools.editor.enableCodeFolding";
|
||||
const EXPAND_TAB = "devtools.editor.expandtab";
|
||||
const KEYMAP = "devtools.editor.keymap";
|
||||
const AUTO_CLOSE = "devtools.editor.autoclosebrackets";
|
||||
const AUTOCOMPLETE = "devtools.editor.autocomplete";
|
||||
const DETECT_INDENT = "devtools.editor.detectindentation";
|
||||
const DETECT_INDENT_MAX_LINES = 500;
|
||||
const L10N_BUNDLE = "chrome://browser/locale/devtools/sourceeditor.properties";
|
||||
const XUL_NS = "http://www.mozilla.org/keymaster/gatekeeper/there.is.only.xul";
|
||||
const VALID_KEYMAPS = new Set(["emacs", "vim", "sublime"]);
|
||||
@@ -514,20 +517,15 @@ Editor.prototype = {
|
||||
resetIndentUnit: function() {
|
||||
let cm = editors.get(this);
|
||||
|
||||
let indentWithTabs = !Services.prefs.getBoolPref(EXPAND_TAB);
|
||||
let indentUnit = Services.prefs.getIntPref(TAB_SIZE);
|
||||
let shouldDetect = Services.prefs.getBoolPref(DETECT_INDENT);
|
||||
let iterFn = function(start, end, callback) {
|
||||
cm.eachLine(start, end, (line) => {
|
||||
return callback(line.text);
|
||||
});
|
||||
};
|
||||
|
||||
let {indentUnit, indentWithTabs} = getIndentationFromIteration(iterFn);
|
||||
|
||||
cm.setOption("tabSize", indentUnit);
|
||||
|
||||
if (shouldDetect) {
|
||||
let indent = detectIndentation(this);
|
||||
if (indent != null) {
|
||||
indentWithTabs = indent.tabs;
|
||||
indentUnit = indent.spaces ? indent.spaces : indentUnit;
|
||||
}
|
||||
}
|
||||
|
||||
cm.setOption("indentUnit", indentUnit);
|
||||
cm.setOption("indentWithTabs", indentWithTabs);
|
||||
},
|
||||
@@ -1300,73 +1298,4 @@ function controller(ed) {
|
||||
};
|
||||
}
|
||||
|
||||
/**
|
||||
* 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(ed) {
|
||||
let cm = editors.get(ed);
|
||||
|
||||
let spaces = {}; // # spaces indent -> # lines with that indent
|
||||
let last = 0; // indentation width of the last line we saw
|
||||
let tabs = 0; // # of lines that start with a tab
|
||||
let total = 0; // # of indented lines (non-zero indent)
|
||||
|
||||
cm.eachLine(0, DETECT_INDENT_MAX_LINES, (line) => {
|
||||
let text = line.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
|
||||
let 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);
|
||||
let tally = spaces[width];
|
||||
if (tally > max) {
|
||||
max = tally;
|
||||
freqIndent = width;
|
||||
}
|
||||
}
|
||||
if (!freqIndent) {
|
||||
return null;
|
||||
}
|
||||
|
||||
return { tabs: false, spaces: freqIndent };
|
||||
}
|
||||
|
||||
module.exports = Editor;
|
||||
|
||||
161
toolkit/devtools/shared/indentation.js
Normal file
161
toolkit/devtools/shared/indentation.js
Normal file
@@ -0,0 +1,161 @@
|
||||
/* -*- indent-tabs-mode: nil; js-indent-level: 2; fill-column: 80 -*- */
|
||||
/* vim:set ts=2 sw=2 sts=2 et tw=80:
|
||||
* 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 { Cu } = require("chrome");
|
||||
Cu.import("resource://gre/modules/Services.jsm");
|
||||
|
||||
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 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.
|
||||
* 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 getIndentationFromPrefs() {
|
||||
let shouldDetect = Services.prefs.getBoolPref(DETECT_INDENT);
|
||||
if (shouldDetect) {
|
||||
return false;
|
||||
}
|
||||
|
||||
let indentWithTabs = !Services.prefs.getBoolPref(EXPAND_TAB);
|
||||
let indentUnit = Services.prefs.getIntPref(TAB_SIZE);
|
||||
return {indentUnit, indentWithTabs};
|
||||
}
|
||||
|
||||
/**
|
||||
* 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);
|
||||
let shouldDetect = Services.prefs.getBoolPref(DETECT_INDENT);
|
||||
|
||||
if (shouldDetect) {
|
||||
let 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) {
|
||||
let iteratorFn = function(start, end, callback) {
|
||||
let 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
|
||||
let 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
|
||||
let 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);
|
||||
let 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.getIndentationFromPrefs = getIndentationFromPrefs;
|
||||
exports.getIndentationFromIteration = getIndentationFromIteration;
|
||||
exports.getIndentationFromString = getIndentationFromString;
|
||||
@@ -5,10 +5,12 @@
|
||||
# file, You can obtain one at http://mozilla.org/MPL/2.0/.
|
||||
|
||||
BROWSER_CHROME_MANIFESTS += ['tests/browser/browser.ini']
|
||||
XPCSHELL_TESTS_MANIFESTS += ['tests/unit/xpcshell.ini']
|
||||
|
||||
EXTRA_JS_MODULES.devtools.shared += [
|
||||
'async-storage.js',
|
||||
'framerate.js',
|
||||
'indentation.js',
|
||||
'memory.js',
|
||||
'profiler.js',
|
||||
'system.js',
|
||||
|
||||
4
toolkit/devtools/shared/tests/unit/.eslintrc
Normal file
4
toolkit/devtools/shared/tests/unit/.eslintrc
Normal file
@@ -0,0 +1,4 @@
|
||||
{
|
||||
// Extend from the common devtools xpcshell eslintrc config.
|
||||
"extends": "../../../../../browser/devtools/.eslintrc.xpcshell"
|
||||
}
|
||||
135
toolkit/devtools/shared/tests/unit/test_indentation.js
Normal file
135
toolkit/devtools/shared/tests/unit/test_indentation.js
Normal file
@@ -0,0 +1,135 @@
|
||||
/* Any copyright is dedicated to the Public Domain.
|
||||
http://creativecommons.org/publicdomain/zero/1.0/ */
|
||||
|
||||
"use strict";
|
||||
|
||||
const Cu = Components.utils;
|
||||
const {require} = Cu.import("resource://gre/modules/devtools/Loader.jsm");
|
||||
const Services = require("Services");
|
||||
const {
|
||||
EXPAND_TAB,
|
||||
TAB_SIZE,
|
||||
DETECT_INDENT,
|
||||
getIndentationFromPrefs,
|
||||
getIndentationFromIteration,
|
||||
getIndentationFromString,
|
||||
} = require("devtools/toolkit/shared/indentation");
|
||||
|
||||
function test_indent_from_prefs() {
|
||||
Services.prefs.setBoolPref(DETECT_INDENT, true);
|
||||
equal(getIndentationFromPrefs(), false,
|
||||
"getIndentationFromPrefs returning false");
|
||||
|
||||
Services.prefs.setIntPref(TAB_SIZE, 73);
|
||||
Services.prefs.setBoolPref(EXPAND_TAB, false);
|
||||
Services.prefs.setBoolPref(DETECT_INDENT, false);
|
||||
deepEqual(getIndentationFromPrefs(), {indentUnit: 73, indentWithTabs: true},
|
||||
"getIndentationFromPrefs basic test");
|
||||
}
|
||||
|
||||
const TESTS = [
|
||||
{
|
||||
desc: "two spaces",
|
||||
input: [
|
||||
"/*",
|
||||
" * tricky comment block",
|
||||
" */",
|
||||
"div {",
|
||||
" color: red;",
|
||||
" background: blue;",
|
||||
"}",
|
||||
" ",
|
||||
"span {",
|
||||
" padding-left: 10px;",
|
||||
"}"
|
||||
],
|
||||
expected: {indentUnit: 2, indentWithTabs: false}
|
||||
},
|
||||
{
|
||||
desc: "four spaces",
|
||||
input: [
|
||||
"var obj = {",
|
||||
" addNumbers: function() {",
|
||||
" var x = 5;",
|
||||
" var y = 18;",
|
||||
" return x + y;",
|
||||
" },",
|
||||
" ",
|
||||
" /*",
|
||||
" * Do some stuff to two numbers",
|
||||
" * ",
|
||||
" * @param x",
|
||||
" * @param y",
|
||||
" * ",
|
||||
" * @return the result of doing stuff",
|
||||
" */",
|
||||
" subtractNumbers: function(x, y) {",
|
||||
" var x += 7;",
|
||||
" var y += 18;",
|
||||
" var result = x - y;",
|
||||
" result %= 2;",
|
||||
" }",
|
||||
"}"
|
||||
],
|
||||
expected: {indentUnit: 4, indentWithTabs: false}
|
||||
},
|
||||
{
|
||||
desc: "tabs",
|
||||
input: [
|
||||
"/*",
|
||||
" * tricky comment block",
|
||||
" */",
|
||||
"div {",
|
||||
"\tcolor: red;",
|
||||
"\tbackground: blue;",
|
||||
"}",
|
||||
"",
|
||||
"span {",
|
||||
"\tpadding-left: 10px;",
|
||||
"}"
|
||||
],
|
||||
expected: {indentUnit: 2, indentWithTabs: true}
|
||||
},
|
||||
{
|
||||
desc: "no indent",
|
||||
input: [
|
||||
"var x = 0;",
|
||||
" // stray thing",
|
||||
"var y = 9;",
|
||||
" ",
|
||||
""
|
||||
],
|
||||
expected: {indentUnit: 2, indentWithTabs: false}
|
||||
},
|
||||
];
|
||||
|
||||
function test_indent_detection() {
|
||||
Services.prefs.setIntPref(TAB_SIZE, 2);
|
||||
Services.prefs.setBoolPref(EXPAND_TAB, true);
|
||||
Services.prefs.setBoolPref(DETECT_INDENT, true);
|
||||
|
||||
for (let test of TESTS) {
|
||||
let iterFn = function(start, end, callback) {
|
||||
test.input.slice(start, end).forEach(callback);
|
||||
};
|
||||
|
||||
deepEqual(getIndentationFromIteration(iterFn), test.expected,
|
||||
"test getIndentationFromIteration " + test.desc);
|
||||
}
|
||||
|
||||
for (let test of TESTS) {
|
||||
deepEqual(getIndentationFromString(test.input.join("\n")), test.expected,
|
||||
"test getIndentationFromString " + test.desc);
|
||||
}
|
||||
}
|
||||
|
||||
function run_test() {
|
||||
try {
|
||||
test_indent_from_prefs();
|
||||
test_indent_detection();
|
||||
} finally {
|
||||
Services.prefs.clearUserPref(TAB_SIZE);
|
||||
Services.prefs.clearUserPref(EXPAND_TAB);
|
||||
Services.prefs.clearUserPref(DETECT_INDENT);
|
||||
}
|
||||
}
|
||||
5
toolkit/devtools/shared/tests/unit/xpcshell.ini
Normal file
5
toolkit/devtools/shared/tests/unit/xpcshell.ini
Normal file
@@ -0,0 +1,5 @@
|
||||
[DEFAULT]
|
||||
head =
|
||||
tail =
|
||||
|
||||
[test_indentation.js]
|
||||
Reference in New Issue
Block a user