Skip to content
This repository has been archived by the owner on Sep 6, 2021. It is now read-only.

integration of code-folding extension into brackets #10792

Merged
merged 13 commits into from
Apr 7, 2015
Merged
Show file tree
Hide file tree
Changes from 2 commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
19 changes: 19 additions & 0 deletions src/extensions/default/CodeFolding/DefaultSettings.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,19 @@
/**
* JSON values for default settings
* @author Patrick Oladimeji
* @date 8/23/14 15:45:35 PM
*/
/*jslint vars: true, plusplus: true, devel: true, nomen: true, indent: 4, maxerr: 50 */
/*global define*/
define(function (require, exports, module) {
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Files are generally mixed tabs/spaces. For consistency, we should stick to 4 spaces.

"use strict";

module.exports = {
minFoldSize: 2,
saveFoldStates: true,
alwaysUseIndentFold: true,
enableRegionFolding: true,
fadeFoldButtons: false,
maxFoldLevel: 2 // this value is only used when fold all is called
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

NIT: indentation

};
});
72 changes: 72 additions & 0 deletions src/extensions/default/CodeFolding/Prefs.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,72 @@
/**
* Wrapper around brackets pref system to ensure preferences are stored in in one single object instead of using multiple keys.
* This is to make it easy for the user who edits their preferences file to easily manage the potentially numerous lines of preferences generated by the persisting code-folding state.
* @author Patrick Oladimeji
* @date 3/22/14 20:39:53 PM
*/
/*jslint vars: true, plusplus: true, devel: true, nomen: true, indent: 4, maxerr: 50 */
/*global define, brackets*/
define(function (require, exports, module) {
"use strict";
var PreferencesManager = brackets.getModule("preferences/PreferencesManager"),
stateManager = PreferencesManager.stateManager.getPrefixedSystem("code-folding"),
DefaultSettings = require("DefaultSettings"),
store = {},
settings = {},
folds = "folds";

function simplify(folds) {
if (!folds) { return folds; }
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Tweak this to

if (!folds) {
    return folds;
}

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

But how can you return folds if it not passed?

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

That's equivalent to returning undefined. I've updated for clarity :).

var res = {}, range;
Object.keys(folds).map(function (line) {
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Why call map when you are not really transforming data and creating a new array form it? A forEach would suffice.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Indeed that is a typo. Should be a forEach.

range = folds[line];
res[line] = Array.isArray(range) ? range : [[range.from.line, range.from.ch], [range.to.line, range.to.ch]];
});
return res;
}

function inflate(folds) {
if (!folds) { return folds; }
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Expand this out to multiple lines

if (!folds) {
    return folds;
}

//transform the folds into objects with from and to properties
var ranges = {}, obj;
Object.keys(folds).forEach(function (line) {
obj = folds[line];
ranges[line] = {from: {line: obj[0][0], ch: obj[0][1]}, to: {line: obj[1][0], ch: obj[1][1]}};
});

return ranges;
}

module.exports = {
get: function (id) {
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Common style is to make these named functions that you assign to the module.exports. E.g.

function get(id) {
}

module.exports.get = get;

We probably also benefit from some jsdocs :)

store = (stateManager.get(folds) || {});
return inflate(store[id]);
},
set: function (id, value) {
store[id] = simplify(value);
stateManager.set(folds, store);
stateManager.save();
},
getSetting: function (key) {
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Indentation here wont be a problem once they are moved from here to named functions.

settings = (stateManager.get("settings") || DefaultSettings);
return settings[key];
},
setSetting: function (key, value) {
settings[key] = value;
stateManager.set("settings", settings);
stateManager.save();
},
getAllSettings: function () {
var res = {}, self = this;
Object.keys(DefaultSettings).forEach(function (key) {
res[key] = self.getSetting(key);
});
return res;
},
clearAllFolds: function () {
stateManager.set(folds, {});
stateManager.save();
}
};

});
66 changes: 66 additions & 0 deletions src/extensions/default/CodeFolding/SettingsDialog.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,66 @@
/**
* Configure and change settings for the code folding extension
* @author Patrick Oladimeji
* @date 8/23/14 12:32:46 PM
*/
/*jslint vars: true, plusplus: true, devel: true, nomen: true, indent: 4, maxerr: 50 */
/*global define, brackets, Mustache, $ */
define(function (require, exports, module) {
"use strict";
var Dialogs = brackets.getModule("widgets/Dialogs"),
DefaultSettings = require("DefaultSettings"),
Strings = require("i18n!nls/strings"),
settingsTemplate = require("text!htmlTemplates/settings-dialog.html"),
preferences = require("Prefs");

function setFormValues(prefs) {
$("#min-fold-size").val(prefs.minFoldSize || 2);
$("#max-fold-level").val(prefs.maxFoldLevel || 2);
$("#save-fold-states").prop("checked", prefs.saveFoldStates);
$("#always-use-indent-fold").prop("checked", prefs.alwaysUseIndentFold);
$("#enable-region-folding").prop("checked", prefs.enableRegionFolding);
$("#fade-fold-buttons").prop("checked", prefs.fadeFoldButtons);
}

function restoreDefaults() {
setFormValues(DefaultSettings);
}

function showDialog(cb) {
var template = Mustache.render(settingsTemplate, Strings);
var dialog = Dialogs.showModalDialogUsingTemplate(template);
setFormValues(preferences.getAllSettings());

dialog.done(function (buttonId) {
if (buttonId === "ok") {
var $dialog = dialog.getElement();
var minFoldSize = $("#min-fold-size", $dialog).val();
var maxFoldLevel = $("#max-fold-level", $dialog).val();
preferences.setSetting("minFoldSize", isNaN(minFoldSize) || +minFoldSize === 0 ?
+preferences.getSetting("minFoldSize") : +minFoldSize);
preferences.setSetting("saveFoldStates", $("#save-fold-states", $dialog).prop("checked"));
preferences.setSetting("maxFoldLevel", isNaN(maxFoldLevel) || +maxFoldLevel === 0 ?
+preferences.getSetting("maxFoldLevel") : +maxFoldLevel);
preferences.setSetting("alwaysUseIndentFold", $("#always-use-indent-fold", $dialog).prop("checked"));
preferences.setSetting("enableRegionFolding", $("#enable-region-folding", $dialog).prop("checked"));
preferences.setSetting("fadeFoldButtons", $("#fade-fold-buttons", $dialog).prop("checked"));
if (cb && typeof cb === "function") {
cb();
}
}
});
}

function bindListeners() {
$("button[data-button-id='defaults']").on("click", function (e) {
e.stopPropagation();
restoreDefaults();
});
}

bindListeners();

module.exports = {
show: showDialog
};
});
217 changes: 217 additions & 0 deletions src/extensions/default/CodeFolding/foldhelpers/foldcode.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,217 @@
/**
* Based on http://codemirror.net/addon/fold/foldcode.js
* @author Patrick Oladimeji
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I think we still need to keep the original copyright from CodeMirror here

* @date 10/28/13 8:41:46 AM
* @last modified 20 April 2014
*/
/*jslint vars: true, plusplus: true, devel: true, nomen: true, indent: 4, maxerr: 50 */
/*global define, brackets, document*/
define(function (require, exports, module) {
"use strict";
var CodeMirror = brackets.getModule("thirdparty/CodeMirror2/lib/codemirror"),
prefs = require("Prefs");

module.exports = function () {
function doFold(cm, pos, options, force) {
force = force || "fold";
if (typeof pos === "number") {
pos = CodeMirror.Pos(pos, 0);
}
var finder = (options && options.rangeFinder) || CodeMirror.fold.auto;
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

instead of checking is options exists in each line that uses options, you can have give it a default value.

options = options || {};

var minSize = (options && options.minFoldSize) || prefs.getSetting("minFoldSize");

function getRange(allowFolded) {
var range = options && options.range ? options.range : finder(cm, pos);
if (!range || range.to.line - range.from.line < minSize) {
return null;
}
var marks = cm.findMarksAt(range.from),
i;
for (i = 0; i < marks.length; ++i) {
if (marks[i].__isFold && force !== "fold") {
if (!allowFolded) {
return null;
}
range.cleared = true;
marks[i].clear();
}
}
//check for overlapping folds
var lastMark, foldMarks;
if (marks && marks.length) {
foldMarks = marks.filter(function (d) { return d.__isFold; });
if (foldMarks && foldMarks.length) {
lastMark = foldMarks[foldMarks.length - 1].find();
if (lastMark && range.from.line <= lastMark.to.line && lastMark.to.line < range.to.line) {
return null;
}
}
}
return range;
}

function makeWidget() {
var widget = document.createElement("span");
widget.className = "CodeMirror-foldmarker";
return widget;
}

var range = getRange(true);
if (options && options.scanUp) {
while (!range && pos.line > cm.firstLine()) {
pos = CodeMirror.Pos(pos.line - 1, 0);
range = getRange(false);
}
}
if (!range || range.cleared || force === "unfold" || range.to.line - range.from.line < minSize) {
if (range) { range.cleared = false; }
return;
}

var myWidget = makeWidget();
var myRange = cm.markText(range.from, range.to, {
replacedWith: myWidget,
clearOnEnter: true,
__isFold: true
});
CodeMirror.on(myWidget, "mousedown", function () {
myRange.clear();
});
myRange.on("clear", function (from, to) {
delete cm._lineFolds[from.line];
CodeMirror.signal(cm, "unfold", cm, from, to);
});

if (force === "fold") {
delete range.cleared;
cm._lineFolds[pos.line] = range;
} else {
delete cm._lineFolds[pos.line];
}
CodeMirror.signal(cm, force, cm, range.from, range.to);
return range;
}

CodeMirror.defineExtension("foldCode", function (pos, options, force) {
return doFold(this, pos, options, force);
});

//define an unfoldCode extension to quickly unfold folded code
CodeMirror.defineExtension("unfoldCode", function (pos, options) {
return doFold(this, pos, options, "unfold");
});

CodeMirror.registerHelper("fold", "combine", function () {
var funcs = Array.prototype.slice.call(arguments, 0);
return function (cm, start) {
var i;
for (i = 0; i < funcs.length; ++i) {
var found = funcs[i] && funcs[i](cm, start);
if (found) {
return found;
}
}
};
});

CodeMirror.defineExtension("isFolded", function (line) {
return this._lineFolds[line];
});
/**
Checks the validity of the ranges passed in the parameter and returns the foldranges
that are still valid in the current document
@param {object} folds the dictionary of lines in the current document that should be folded
@returns {object} valid folds found in those passed in parameter
*/
CodeMirror.defineExtension("getValidFolds", function (folds) {
var keys, rf = CodeMirror.fold.auto, cm = this, result = {};
if (folds && (keys = Object.keys(folds)).length) {
var range, cachedRange;
keys.forEach(function (lineNumber) {
lineNumber = +lineNumber;
if (lineNumber >= cm.firstLine() && lineNumber <= cm.lastLine()) {
range = rf(cm, CodeMirror.Pos(lineNumber));
cachedRange = folds[lineNumber];
if (range && cachedRange && range.from.line === cachedRange.from.line &&
range.to.line === cachedRange.to.line) {
cm.foldCode(lineNumber, {range: folds[lineNumber]}, "fold");
result[lineNumber] = folds[lineNumber];
}
}
});
}
return result;
});

CodeMirror.commands.toggleFold = function (cm) {
cm.foldCode(cm.getCursor());
};
CodeMirror.commands.fold = function (cm, options, force) {
cm.foldCode(cm.getCursor(), options, "fold");
};
CodeMirror.commands.unfold = function (cm, options, force) {
cm.foldCode(cm.getCursor(), options, "unfold");
};
CodeMirror.commands.foldAll = function (cm) {
cm.operation(function () {
var i, e;
for (i = cm.firstLine(), e = cm.lastLine(); i <= e; i++) {
cm.foldCode(CodeMirror.Pos(i, 0), null, "fold");
}
});
};
/**
Folds the specified range. The descendants of any fold regions within the range are also folded up to
a level set globally in the codeFolding preferences
*/
CodeMirror.commands.foldToLevel = function (cm, start, end) {
var rf = CodeMirror.fold.auto, level = prefs.getSetting("maxFoldLevel");
function foldLevel(n, from, to) {
if (n > 0) {
var i = from, range;
while (i < to) {
range = rf(cm, CodeMirror.Pos(i, 0));
if (range) {
//call fold level for the range just folded
foldLevel(n - 1, range.from.line + 1, range.to.line - 1);
cm.foldCode(CodeMirror.Pos(i, 0), null, "fold");
i = range.to.line + 1;
} else {
i++;
}
}
}
}
cm.operation(function () {
start = start === undefined ? cm.firstLine() : start;
end = end || cm.lastLine();
foldLevel(level, start, end);
});
};

CodeMirror.commands.unfoldAll = function (cm, from, to) {
from = from || cm.firstLine();
to = to || cm.lastLine();
cm.operation(function () {
var i, e;
for (i = from, e = to; i <= e; i++) {
if (cm.isFolded(i)) { cm.unfoldCode(i, {range: cm._lineFolds[i]}); }
}
});
};

CodeMirror.registerHelper("fold", "auto", function (cm, start) {
var helpers = cm.getHelpers(start, "fold"), i, cur;
//ensure mode helper is loaded if there is one
var mode = cm.getMode().name;
var modeHelper = CodeMirror.fold[mode];
if (modeHelper && helpers.indexOf(modeHelper) < 0) {
helpers.push(modeHelper);
}
for (i = 0; i < helpers.length; i++) {
cur = helpers[i](cm, start);
if (cur) { return cur; }
}
});
};
});
Loading