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

Commit 1deb307

Browse files
committed
Merge pull request #5191 from adobe/pflynn/find-tickmarks
Show scroll track tickmarks for Find results
2 parents 1c268c5 + 859119f commit 1deb307

6 files changed

Lines changed: 301 additions & 14 deletions

File tree

src/editor/Editor.js

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -506,6 +506,11 @@ define(function (require, exports, module) {
506506
});
507507
};
508508

509+
/** @return {boolean} True if editor is not showing the entire text of the document (i.e. an inline editor) */
510+
Editor.prototype.isTextSubset = function () {
511+
return Boolean(this._visibleRange);
512+
};
513+
509514
/**
510515
* Ensures that the lines that are actually hidden in the inline editor correspond to
511516
* the desired visible range.

src/search/FindReplace.js

Lines changed: 30 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -26,7 +26,7 @@
2626
/*unittests: FindReplace*/
2727

2828

29-
/*
29+
/**
3030
* Adds Find and Replace commands
3131
*
3232
* Originally based on the code in CodeMirror2/lib/util/search.js.
@@ -43,6 +43,7 @@ define(function (require, exports, module) {
4343
Editor = require("editor/Editor"),
4444
EditorManager = require("editor/EditorManager"),
4545
ModalBar = require("widgets/ModalBar").ModalBar,
46+
ScrollTrackMarkers = require("search/ScrollTrackMarkers"),
4647
PanelManager = require("view/PanelManager"),
4748
Resizer = require("utils/Resizer"),
4849
StatusBar = require("widgets/StatusBar"),
@@ -151,6 +152,7 @@ define(function (require, exports, module) {
151152
});
152153
});
153154
state.marked.length = 0;
155+
ScrollTrackMarkers.clear();
154156
}
155157

156158
function clearSearch(cm) {
@@ -191,7 +193,26 @@ define(function (require, exports, module) {
191193
"<span id='find-counter'></span> " +
192194
"<span style='color: #888'>(" + Strings.SEARCH_REGEXP_INFO + ")</span>" +
193195
"</div>" +
194-
"<div class='error'></div>";
196+
"<div class='error'></div>";
197+
198+
199+
function toggleHighlighting(editor, enabled) {
200+
// Temporarily change selection color to improve highlighting - see LESS code for details
201+
if (enabled) {
202+
$(editor.getRootElement()).addClass("find-highlighting");
203+
} else {
204+
$(editor.getRootElement()).removeClass("find-highlighting");
205+
}
206+
207+
ScrollTrackMarkers.setVisible(editor, enabled);
208+
}
209+
210+
function addHighlight(editor, state, cursor) {
211+
var cm = editor._codeMirror;
212+
state.marked.push(cm.markText(cursor.from(), cursor.to(), { className: "CodeMirror-searching" }));
213+
214+
ScrollTrackMarkers.addTickmark(editor, cursor.from());
215+
}
195216

196217
/**
197218
* If no search pending, opens the search dialog. If search is already open, moves to
@@ -246,14 +267,13 @@ define(function (require, exports, module) {
246267
// Highlight all matches
247268
// (Except on huge documents, where this is too expensive)
248269
if (cm.getValue().length < 500000) {
249-
// Temporarily change selection color to improve highlighting - see LESS code for details
250-
$(cm.getWrapperElement()).addClass("find-highlighting");
270+
toggleHighlighting(editor, true);
251271

252272
// FUTURE: if last query was prefix of this one, could optimize by filtering existing result set
253273
var resultCount = 0;
254274
var cursor = getSearchCursor(cm, state.query);
255275
while (cursor.findNext()) {
256-
state.marked.push(cm.markText(cursor.from(), cursor.to(), { className: "CodeMirror-searching" }));
276+
addHighlight(editor, state, cursor);
257277
resultCount++;
258278

259279
//Remove this section when https://github.com/marijnh/CodeMirror/issues/1155 will be fixed
@@ -268,7 +288,7 @@ define(function (require, exports, module) {
268288
if (resultCount === 0) {
269289
$("#find-counter").text(Strings.FIND_NO_RESULTS);
270290
} else if (resultCount === 1) {
271-
$("#find-counter").text(Strings.FIND_RESULT_COUNT_SINGLE);
291+
$("#find-counter").text(Strings.FIND_RESULT_COUNT_SINGLE);
272292
} else {
273293
$("#find-counter").text(StringUtils.format(Strings.FIND_RESULT_COUNT, resultCount));
274294
enableNavigator = true;
@@ -313,15 +333,15 @@ define(function (require, exports, module) {
313333
// Clear highlights but leave search state in place so Find Next/Previous work after closing
314334
clearHighlights(cm, state);
315335

316-
// As soon as focus goes back to the editor, restore normal selection color
317-
$(cm.getWrapperElement()).removeClass("find-highlighting");
336+
// Dispose highlighting UI (important to restore normal selection color as soon as focus goes back to the editor)
337+
toggleHighlighting(editor, false);
318338
});
319339

320340
modalBar.getRoot().on("click", function (e) {
321341
if (e.target.id === "find-next") {
322-
_findNext();
342+
doSearch(editor);
323343
} else if (e.target.id === "find-prev") {
324-
_findPrevious();
344+
doSearch(editor, true);
325345
}
326346
});
327347

src/search/ScrollTrackMarkers.js

Lines changed: 158 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,158 @@
1+
/*
2+
* Copyright (c) 2013 Adobe Systems Incorporated. All rights reserved.
3+
*
4+
* Permission is hereby granted, free of charge, to any person obtaining a
5+
* copy of this software and associated documentation files (the "Software"),
6+
* to deal in the Software without restriction, including without limitation
7+
* the rights to use, copy, modify, merge, publish, distribute, sublicense,
8+
* and/or sell copies of the Software, and to permit persons to whom the
9+
* Software is furnished to do so, subject to the following conditions:
10+
*
11+
* The above copyright notice and this permission notice shall be included in
12+
* all copies or substantial portions of the Software.
13+
*
14+
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
15+
* IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
16+
* FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
17+
* AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
18+
* LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
19+
* FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER
20+
* DEALINGS IN THE SOFTWARE.
21+
*
22+
*/
23+
24+
/*jslint vars: true, plusplus: true, devel: true, nomen: true, regexp: true, indent: 4, maxerr: 50 */
25+
/*global define, $, brackets, window */
26+
27+
28+
/**
29+
* Manages tickmarks shown along the scrollbar track.
30+
* NOT yet intended for use by anyone other than the FindReplace module.
31+
* It is assumed that markers are always clear()ed when switching editors.
32+
*/
33+
define(function (require, exports, module) {
34+
"use strict";
35+
36+
var Editor = require("editor/Editor"),
37+
EditorManager = require("editor/EditorManager"),
38+
Async = require("utils/Async");
39+
40+
41+
/** @const @type {number} Height (and width) or scrollbar up/down arrow button on Win */
42+
var WIN_ARROW_HT = 17;
43+
44+
/** @type {?Editor} Editor the markers are currently shown for, or null if not shown */
45+
var editor;
46+
47+
/** @type {number} Top of scrollbar track area, relative to top of scrollbar */
48+
var trackOffset;
49+
50+
/** @type {number} Height of scrollbar track area */
51+
var trackHt;
52+
53+
/** @type {!Array.<{line: number, ch: number}>} Text positions of markers */
54+
var marks = [];
55+
56+
57+
/** Measure scrollbar track */
58+
function _calcScaling() {
59+
var rootElem = editor.getRootElement();
60+
var $sb = $(".CodeMirror-vscrollbar", rootElem);
61+
62+
trackHt = $sb[0].offsetHeight;
63+
64+
if (trackHt > 0) {
65+
// Scrollbar visible: determine offset of track from top of scrollbar
66+
if (brackets.platform === "win") {
67+
trackOffset = WIN_ARROW_HT; // Up arrow pushes down track
68+
} else {
69+
trackOffset = 0; // No arrows
70+
}
71+
72+
} else {
73+
// No scrollbar: use the height of the entire code content
74+
trackHt = $(".CodeMirror-sizer", rootElem)[0].offsetHeight;
75+
trackOffset = 0;
76+
}
77+
78+
trackHt -= trackOffset * 2;
79+
}
80+
81+
/** Add one tickmark to the DOM */
82+
function _renderMark(pos) {
83+
var top = Math.round(pos.line / editor.lineCount() * trackHt) + trackOffset;
84+
top--; // subtract ~1/2 the ht of a tickmark to center it on ideal pos
85+
86+
var $mark = $("<div class='tickmark' style='top:" + top + "px'></div>");
87+
88+
$(".tickmark-track", editor.getRootElement()).append($mark);
89+
}
90+
91+
92+
/**
93+
* Clear any markers in the editor's tickmark track, but leave it visible. Safe to call when
94+
* tickmark track is not visible also.
95+
*/
96+
function clear() {
97+
if (editor) {
98+
$(".tickmark-track", editor.getRootElement()).empty();
99+
marks = [];
100+
}
101+
}
102+
103+
/** Add or remove the tickmark track from the editor's UI */
104+
function setVisible(curEditor, visible) {
105+
// short-circuit no-ops
106+
if ((visible && curEditor === editor) || (!visible && !editor)) {
107+
return;
108+
}
109+
110+
if (visible) {
111+
console.assert(!editor);
112+
editor = curEditor;
113+
114+
// Don't support inline editors yet - search inside them is pretty screwy anyway (#2110)
115+
if (editor.isTextSubset()) {
116+
return;
117+
}
118+
119+
var rootElem = editor.getRootElement();
120+
var $sb = $(".CodeMirror-vscrollbar", rootElem);
121+
var $overlay = $("<div class='tickmark-track'></div>");
122+
$sb.parent().append($overlay);
123+
124+
_calcScaling();
125+
126+
// Update tickmarks during window resize (whenever resizing has paused/stopped for > 1/3 sec)
127+
$(window).on("resize.ScrollTrackMarkers", Async.whenIdle(300, function () {
128+
if (marks.length) {
129+
_calcScaling();
130+
$(".tickmark-track", editor.getRootElement()).empty();
131+
marks.forEach(function (pos) {
132+
_renderMark(pos);
133+
});
134+
}
135+
}));
136+
137+
} else {
138+
console.assert(editor === curEditor);
139+
$(".tickmark-track", curEditor.getRootElement()).remove();
140+
editor = null;
141+
marks = [];
142+
$(window).off("resize.ScrollTrackMarkers");
143+
}
144+
}
145+
146+
/** Add a tickmark to the editor's tickmark track, if it's visible */
147+
function addTickmark(curEditor, pos) {
148+
console.assert(editor === curEditor);
149+
150+
marks.push(pos);
151+
_renderMark(pos);
152+
}
153+
154+
155+
exports.clear = clear;
156+
exports.setVisible = setVisible;
157+
exports.addTickmark = addTickmark;
158+
});

src/styles/brackets.less

Lines changed: 22 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -926,6 +926,28 @@ a, img {
926926
padding: 0 5px;
927927
}
928928

929+
.tickmark-track {
930+
position: absolute;
931+
bottom: 0;
932+
top: 0;
933+
right: 0;
934+
width: 16px;
935+
z-index: @z-index-cm-max;
936+
pointer-events: none;
937+
938+
.tickmark {
939+
position: absolute;
940+
width: 16px;
941+
body.platform-mac & { width: 15px; }
942+
943+
height: 1px;
944+
background-color: #eddd23;
945+
border-top: 1px solid #e0d123;
946+
border-bottom: 1px solid #d4c620;
947+
opacity: 0.85; // allow thumb to show through
948+
}
949+
}
950+
929951

930952
/* Quick Open search bar & dropdown */
931953

src/utils/Async.js

Lines changed: 28 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -419,14 +419,40 @@ define(function (require, exports, module) {
419419
});
420420
}
421421
};
422+
423+
424+
/**
425+
* Implements "debouncing." Returns a function that can be called frequently, triggering 'callback' only when calls
426+
* to this function have paused for >= 'idleDelay' ms. The callback may be called multiple times, if there are
427+
* multiple idleDelay-sized gaps in the event sequence. Invoking the callback can be delayed *indefinitely* if the
428+
* event sequence continues forever with no idleDelay-sized gaps at all.
429+
*
430+
* @param {number} idleDelay Minimum delay (ms) before invoking callback.
431+
* @param {!function()} callback
432+
* @return {!function()}
433+
*/
434+
function whenIdle(idleDelay, callback) {
435+
var timer;
436+
return function () {
437+
if (timer) {
438+
window.clearTimeout(timer);
439+
}
440+
timer = window.setTimeout(function () {
441+
timer = null;
442+
callback();
443+
}, idleDelay);
444+
};
445+
}
446+
422447

423448
// Define public API
424449
exports.doInParallel = doInParallel;
425450
exports.doSequentially = doSequentially;
426-
exports.doSequentiallyInBackground = doSequentiallyInBackground;
451+
exports.doSequentiallyInBackground = doSequentiallyInBackground;
427452
exports.doInParallel_aggregateErrors = doInParallel_aggregateErrors;
428453
exports.withTimeout = withTimeout;
454+
exports.ERROR_TIMEOUT = ERROR_TIMEOUT;
429455
exports.chain = chain;
430456
exports.PromiseQueue = PromiseQueue;
431-
exports.ERROR_TIMEOUT = ERROR_TIMEOUT;
457+
exports.whenIdle = whenIdle;
432458
});

0 commit comments

Comments
 (0)