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

Commit 1ee6733

Browse files
committed
Merge pull request #11538 from thehogfather/fold-selected
Allow code-folding for selected text in editor
2 parents 5509048 + 3603849 commit 1ee6733

6 files changed

Lines changed: 175 additions & 58 deletions

File tree

src/extensions/default/CodeFolding/Prefs.js

Lines changed: 7 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -8,7 +8,7 @@
88
/*global define, brackets*/
99
define(function (require, exports, module) {
1010
"use strict";
11-
11+
1212
var ProjectManager = brackets.getModule("project/ProjectManager"),
1313
PreferencesManager = brackets.getModule("preferences/PreferencesManager"),
1414
Strings = brackets.getModule("strings"),
@@ -20,7 +20,8 @@ define(function (require, exports, module) {
2020
SAVE_FOLD_STATES = "Save fold states",
2121
ALWAYS_USE_INDENT_FOLD = "Always use indent fold",
2222
HIDE_FOLD_BUTTONS = "Hide fold triangles",
23-
MAX_FOLD_LEVEL = "Max fold level";
23+
MAX_FOLD_LEVEL = "Max fold level",
24+
MAKE_SELECTIONS_FOLDABLE = "Makes selections foldable";
2425

2526
//default preference values
2627
prefs.definePreference("enabled", "boolean", true,
@@ -35,7 +36,9 @@ define(function (require, exports, module) {
3536
{name: HIDE_FOLD_BUTTONS, description: Strings.DESCRIPTION_CODE_FOLDING_HIDE_UNTIL_MOUSEOVER});
3637
prefs.definePreference("maxFoldLevel", "number", 2,
3738
{name: MAX_FOLD_LEVEL, description: Strings.DESCRIPTION_CODE_FOLDING_MAX_FOLD_LEVEL});
38-
39+
prefs.definePreference("makeSelectionsFoldable", "boolean", true,
40+
{name: MAKE_SELECTIONS_FOLDABLE, description: Strings.DESCRIPTION_CODE_FOLDING_MAKE_SELECTIONS_FOLDABLE});
41+
3942
PreferencesManager.stateManager.definePreference(FOLDS_PREF_KEY, "object", {});
4043

4144
/**
@@ -75,7 +78,7 @@ define(function (require, exports, module) {
7578

7679
return ranges;
7780
}
78-
81+
7982
/**
8083
* Returns a 'context' object for getting/setting project-specific view state preferences.
8184
* Similar to code in MultiRangeInlineEditor._getPrefsContext()...
Lines changed: 30 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,30 @@
1+
/**
2+
* Selection range helper for code folding.
3+
* @author Patrick Oladimeji
4+
* @date 31/07/2015 00:11:53
5+
*/
6+
/*global define*/
7+
define(function (require, exports, module) {
8+
"use strict";
9+
10+
/**
11+
* This helper returns the start and end range represeting the current selection in the editor.
12+
* @param {Object} cm The Codemirror instance
13+
* @param {Object} start A Codemirror.Pos object {line, ch} representing the current line we are
14+
* checking for fold ranges
15+
* @returns {Object} The fold range, {from, to} representing the current selection.
16+
*/
17+
function SelectionFold(cm, start) {
18+
if (!cm.somethingSelected()) {
19+
return;
20+
}
21+
22+
var from = cm.getCursor("from"),
23+
to = cm.getCursor("to");
24+
if (from.line === start.line) {
25+
return {from: from, to: to};
26+
}
27+
}
28+
29+
module.exports = SelectionFold;
30+
});

src/extensions/default/CodeFolding/foldhelpers/foldcode.js

Lines changed: 6 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -137,7 +137,6 @@ define(function (require, exports, module) {
137137
cachedRange = folds[lineNumber];
138138
if (range && cachedRange && range.from.line === cachedRange.from.line &&
139139
range.to.line === cachedRange.to.line) {
140-
cm.foldCode(lineNumber, {range: folds[lineNumber]}, "fold");
141140
result[lineNumber] = folds[lineNumber];
142141
}
143142
}
@@ -227,7 +226,9 @@ define(function (require, exports, module) {
227226
};
228227

229228
/**
230-
* Helper to combine an array of fold range finders into one
229+
* Helper to combine an array of fold range finders into one. This goes through the
230+
* list of fold helpers in the parameter arguments and returns the first non-null
231+
* range found from calling the fold helpers in order.
231232
*/
232233
CodeMirror.registerHelper("fold", "combine", function () {
233234
var funcs = Array.prototype.slice.call(arguments, 0);
@@ -249,16 +250,16 @@ define(function (require, exports, module) {
249250
* @param {number} start the current position in the document
250251
*/
251252
CodeMirror.registerHelper("fold", "auto", function (cm, start) {
252-
var helpers = cm.getHelpers(start, "fold"), i, cur;
253+
var helpers = cm.getHelpers(start, "fold"), i, range;
253254
//ensure mode helper is loaded if there is one
254255
var mode = cm.getMode().name;
255256
var modeHelper = CodeMirror.fold[mode];
256257
if (modeHelper && helpers.indexOf(modeHelper) < 0) {
257258
helpers.push(modeHelper);
258259
}
259260
for (i = 0; i < helpers.length; i++) {
260-
cur = helpers[i](cm, start);
261-
if (cur) { return cur; }
261+
range = helpers[i](cm, start);
262+
if (range && range.to.line - range.from.line >= prefs.getSetting("minFoldSize")) { return range; }
262263
}
263264
});
264265
}

src/extensions/default/CodeFolding/foldhelpers/foldgutter.js

Lines changed: 59 additions & 30 deletions
Original file line numberDiff line numberDiff line change
@@ -156,6 +156,19 @@ define(function (require, exports, module) {
156156
});
157157
}
158158

159+
/**
160+
* Helper function to return the fold text marker on a line in an editor
161+
* @param {CodeMirror} cm The CodeMirror instance for the active editor
162+
* @param {Number} line The line number representing the position of the fold marker
163+
* @returns {TextMarker} A CodeMirror TextMarker object
164+
*/
165+
function getFoldOnLine(cm, line) {
166+
var pos = CodeMirror.Pos(line);
167+
var folds = cm.findMarksAt(pos) || [];
168+
folds = folds.filter(isFold);
169+
return folds.length ? folds[0] : undefined;
170+
}
171+
159172
/**
160173
* Synchronises the code folding states in the CM doc to cm._lineFolds cache.
161174
* When an undo operation is done, if folded code fragments are restored, then
@@ -166,19 +179,15 @@ define(function (require, exports, module) {
166179
*/
167180
function syncDocToFoldsCache(cm, from, lineAdded) {
168181
var minFoldSize = prefs.getSetting("minFoldSize") || 2;
169-
var opts = cm.state.foldGutter.options || {};
170-
var rf = opts.rangeFinder || CodeMirror.fold.auto;
171-
var i, pos, folds, fold, range;
182+
var i, fold, range;
172183
if (lineAdded <= 0) {
173184
return;
174185
}
175186

176187
for (i = from; i <= from + lineAdded; i = i + 1) {
177-
pos = CodeMirror.Pos(i);
178-
folds = cm.doc.findMarksAt(pos).filter(isFold);
179-
fold = folds.length ? fold = folds[0] : undefined;
188+
fold = getFoldOnLine(cm, i);
180189
if (fold) {
181-
range = rf(cm, CodeMirror.Pos(i));
190+
range = fold.find();
182191
if (range && range.to.line - range.from.line >= minFoldSize) {
183192
cm._lineFolds[i] = range;
184193
i = range.to.line;
@@ -187,18 +196,27 @@ define(function (require, exports, module) {
187196
}
188197
}
189198
}
199+
}
190200

201+
/**
202+
* Helper function to move a fold range object by the specified number of lines
203+
* @param {Object} range An object specifying the fold range to move. It contains {from, to} which are CodeMirror.Pos objects.
204+
* @param {Number} numLines A positive or negative number representing the numbe of lines to move the range by
205+
*/
206+
function moveRange(range, numLines) {
207+
return {from: CodeMirror.Pos(range.from.line + numLines, range.from.ch),
208+
to: CodeMirror.Pos(range.to.line + numLines, range.to.ch)};
191209
}
192210

193211
/**
194212
* Updates the line folds cache usually when the document changes.
195213
* The following cases are accounted for:
196214
* 1. When the change does not add a new line to the document we check if the line being modified
197215
* is folded. If that is the case, changes to this line might affect the range stored in the cache
198-
* so we update the range.
216+
* so we update the range using the range finder function.
199217
* 2. If lines have been added, we need to update the records for all lines in the folds cache
200218
* which are greater than the line position at which we are adding the new line(s). When existing
201-
* folds exist above the addition we keep the original position in the cache.
219+
* folds are above the addition we keep the original position in the cache.
202220
* 3. If lines are being removed, we need to update the records for all lines in the folds cache which are
203221
* greater than the line position at which we are removing the new lines, while making sure to
204222
* not include any folded lines in the cache that are part of the removed chunk.
@@ -208,7 +226,7 @@ define(function (require, exports, module) {
208226
* This value is negative for deletions and positive for additions.
209227
*/
210228
function updateFoldsCache(cm, from, linesDiff) {
211-
var range;
229+
var oldRange, newRange;
212230
var minFoldSize = prefs.getSetting("minFoldSize") || 2;
213231
var foldedLines = Object.keys(cm._lineFolds).map(function (d) {
214232
return +d;
@@ -218,31 +236,31 @@ define(function (require, exports, module) {
218236

219237
if (linesDiff === 0) {
220238
if (foldedLines.indexOf(from) >= 0) {
221-
range = rf(cm, CodeMirror.Pos(from));
222-
if (range && range.to.line - range.from.line >= minFoldSize) {
223-
cm._lineFolds[from] = range;
239+
newRange = rf(cm, CodeMirror.Pos(from));
240+
if (newRange && newRange.to.line - newRange.from.line >= minFoldSize) {
241+
cm._lineFolds[from] = newRange;
224242
} else {
225243
delete cm._lineFolds[from];
226244
}
227245
}
228246
} else if (foldedLines.length) {
229247
var newFolds = {};
230248
foldedLines.forEach(function (line) {
231-
range = cm._lineFolds[line];
249+
oldRange = cm._lineFolds[line];
250+
//update range with lines-diff
251+
newRange = moveRange(oldRange, linesDiff);
232252
// for removed lines we want to check lines that lie outside the deleted range
233253
if (linesDiff < 0) {
234254
if (line < from) {
235-
newFolds[line] = range;
255+
newFolds[line] = oldRange;
236256
} else if (line >= from + Math.abs(linesDiff)) {
237-
range = rf(cm, CodeMirror.Pos(line + linesDiff));
238-
newFolds[line + linesDiff] = range;
257+
newFolds[line + linesDiff] = newRange;
239258
}
240259
} else {
241260
if (line < from) {
242-
newFolds[line] = range;
243-
} else {
244-
range = rf(cm, CodeMirror.Pos(line + linesDiff));
245-
newFolds[line + linesDiff] = range;
261+
newFolds[line] = oldRange;
262+
} else if (line >= from) {
263+
newFolds[line + linesDiff] = newRange;
246264
}
247265
}
248266
});
@@ -317,17 +335,29 @@ define(function (require, exports, module) {
317335
}, 400);
318336
}
319337

338+
/**
339+
* Triggered when the cursor moves in the editor and used to detect text selection changes
340+
* in the editor.
341+
* @param {!CodeMirror} cm the CodeMirror instance for the active editor
342+
*/
343+
function onCursorActivity(cm) {
344+
var state = cm.state.foldGutter;
345+
var vp = cm.getViewport();
346+
window.clearTimeout(state.changeUpdate);
347+
state.changeUpdate = window.setTimeout(function () {
348+
//need to render the entire visible viewport to remove fold marks rendered from previous selections if any
349+
updateInViewport(cm, vp.from, vp.to);
350+
}, 400);
351+
}
352+
320353
/**
321354
* Triggered when a code segment is folded.
322355
* @param {!CodeMirror} cm the CodeMirror instance for the active editor
323356
* @param {!Object} from the ch and line position that designates the start of the region
324357
* @param {!Object} to the ch and line position that designates the end of the region
325358
*/
326359
function onFold(cm, from, to) {
327-
var state = cm.state.foldGutter, line = from.line;
328-
if (line >= state.from && line < state.to) {
329-
updateFoldInfo(cm, line, line + 1);
330-
}
360+
updateFoldInfo(cm, from.line, from.line + 1);
331361
}
332362

333363
/**
@@ -337,11 +367,7 @@ define(function (require, exports, module) {
337367
* @param {!{line:number, ch:number}} to the ch and line position that designates the end of the region
338368
*/
339369
function onUnFold(cm, from, to) {
340-
var state = cm.state.foldGutter, line = from.line;
341-
var vp = cm.getViewport();
342-
if (line >= state.from && line < state.to) {
343-
updateFoldInfo(cm, line, Math.min(vp.to, to.line));
344-
}
370+
updateFoldInfo(cm, from.line, from.line + 1);
345371
}
346372

347373
/**
@@ -356,6 +382,8 @@ define(function (require, exports, module) {
356382
cm.off("gutterClick", old.onGutterClick);
357383
cm.off("change", onChange);
358384
cm.off("viewportChange", onViewportChange);
385+
cm.off("cursorActivity", onCursorActivity);
386+
359387
cm.off("fold", onFold);
360388
cm.off("unfold", onUnFold);
361389
cm.off("swapDoc", updateInViewport);
@@ -366,6 +394,7 @@ define(function (require, exports, module) {
366394
cm.on("gutterClick", val.onGutterClick);
367395
cm.on("change", onChange);
368396
cm.on("viewportChange", onViewportChange);
397+
cm.on("cursorActivity", onCursorActivity);
369398
cm.on("fold", onFold);
370399
cm.on("unfold", onUnFold);
371400
cm.on("swapDoc", updateInViewport);

0 commit comments

Comments
 (0)