Skip to content

Commit 84bf98b

Browse files
committed
fix(workingset): reposition selection marker when working set becomes visible
The working set selection highlight is an absolutely positioned marker div that only moves when a selectionChanged event fires. On startup the working set container gets the working-set-hidden class (the showWorkingSet pref is read before it is defined), so the session-restore file open positions the marker while the container is display:none - every offset computes to 0 and the marker parks on the first row. The same stale marker happens when the current file changes while the working set is hidden via the showWorkingSet pref or while another sidebar tab (e.g. AI) is active. Add WorkingSetView.refreshSelection() which recomputes the selection marker and scrolls the selected item into view, and call it when the showWorkingSet pref un-hides the container and when switching back to the files sidebar tab. Note refresh() cannot be used for this as it restores the pre-redraw scrollTop, which reads 0 on hidden elements and would undo the scroll-into-view. Adds integration tests for both paths in mainview:WorkingSetView. Claude-Session: https://claude.ai/code/session_018BpQ3Z7w4H2U5qrPGWYRWB
1 parent a3761f4 commit 84bf98b

4 files changed

Lines changed: 110 additions & 1 deletion

File tree

src/project/SidebarView.js

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -301,6 +301,9 @@ define(function (require, exports, module) {
301301
if(getPref) {
302302
// refer to brackets.less file for styles
303303
$workingSet.removeClass("working-set-hidden");
304+
// marker positions computed while the container was display:none are
305+
// all 0, so recompute the selection now that it is visible again
306+
WorkingSetView.refreshSelection();
304307
} else {
305308
$workingSet.addClass("working-set-hidden");
306309
}

src/project/WorkingSetView.js

Lines changed: 12 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -121,6 +121,17 @@ define(function (require, exports, module) {
121121
*/
122122
var _DRAG_MOVE_DETECTION_START = 3;
123123

124+
/**
125+
* Recomputes the selection marker of all Pane View List Views and scrolls
126+
* the selected item into view. Needed after the working set becomes visible
127+
* again, as marker positions computed while it was display:none are all 0.
128+
*/
129+
function refreshSelection() {
130+
_.forEach(_views, function (view) {
131+
view._updateListSelection();
132+
});
133+
}
134+
124135
/**
125136
* Refreshes all Pane View List Views
126137
*/
@@ -1628,6 +1639,7 @@ define(function (require, exports, module) {
16281639
// Public API
16291640
exports.createWorkingSetViewForPane = createWorkingSetViewForPane;
16301641
exports.refresh = refresh;
1642+
exports.refreshSelection = refreshSelection;
16311643
exports.addIconProvider = addIconProvider;
16321644
exports.addClassProvider = addClassProvider;
16331645
exports.syncSelectionIndicator = syncSelectionIndicator;

src/view/SidebarTabs.js

Lines changed: 7 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -40,7 +40,8 @@ define(function (require, exports, module) {
4040
const AppInit = require("utils/AppInit"),
4141
EventDispatcher = require("utils/EventDispatcher"),
4242
Metrics = require("utils/Metrics"),
43-
PreferencesManager = require("preferences/PreferencesManager");
43+
PreferencesManager = require("preferences/PreferencesManager"),
44+
WorkingSetView = require("project/WorkingSetView");
4445

4546
// --- Constants -----------------------------------------------------------
4647

@@ -447,6 +448,11 @@ define(function (require, exports, module) {
447448
}
448449

449450
if (previousTabId !== id) {
451+
if (id === SIDEBAR_TAB_FILES) {
452+
// working set marker positions computed while the files tab content was
453+
// display:none are all 0, so recompute the selection now that it is visible
454+
WorkingSetView.refreshSelection();
455+
}
450456
exports.trigger(EVENT_TAB_CHANGED, id, previousTabId);
451457
}
452458
}

test/spec/WorkingSetView-integ-test.js

Lines changed: 88 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -107,6 +107,11 @@ define(function (require, exports, module) {
107107
});
108108

109109
afterEach(async function () {
110+
// restore state in case a selection marker test failed midway
111+
testWindow.brackets.test.PreferencesManager.set("showWorkingSet", true);
112+
const SidebarTabs = testWindow.brackets.test.SidebarTabs;
113+
SidebarTabs.setActiveTab(SidebarTabs.SIDEBAR_TAB_FILES);
114+
testWindow.$(".open-files-container").css("height", "");
110115
await testWindow.closeAllFiles();
111116
});
112117

@@ -341,6 +346,89 @@ define(function (require, exports, module) {
341346
});
342347
});
343348

349+
function _isMarkerAlignedWithSelection() {
350+
const $ = testWindow.$;
351+
const $selected = $(".open-files-container li.selected");
352+
const $marker = $(".open-files-container .sidebar-selection");
353+
return $selected.length === 1 && $marker.length === 1 &&
354+
Math.abs($marker.offset().top - $selected.offset().top) < 2;
355+
}
356+
357+
it("should reposition selection marker and reveal selection when working set is shown again", async function () {
358+
const $ = testWindow.$;
359+
const PreferencesManager = testWindow.brackets.test.PreferencesManager;
360+
const fileList = MainViewManager.getWorkingSet(MainViewManager.ACTIVE_PANE);
361+
const $container = $(".open-files-container");
362+
363+
// view the first file
364+
await awaitsForDone(CommandManager.execute(Commands.FILE_OPEN, { fullPath: fileList[0].fullPath }),
365+
"FILE_OPEN first file");
366+
367+
// shrink the list so the second item overflows and must be scrolled to
368+
$container.css("height", "30px");
369+
$container.scrollTop(0);
370+
371+
// hide the working set
372+
PreferencesManager.set("showWorkingSet", false);
373+
await awaitsFor(function () {
374+
return $("#working-set-list-container").hasClass("working-set-hidden");
375+
}, "working set to hide");
376+
377+
// switch to the second file while hidden - marker offsets all compute
378+
// to 0 on display:none elements, so it cannot be positioned now
379+
await awaitsForDone(CommandManager.execute(Commands.FILE_OPEN, { fullPath: fileList[1].fullPath }),
380+
"FILE_OPEN second file");
381+
382+
// show the working set again
383+
PreferencesManager.set("showWorkingSet", true);
384+
await awaitsFor(function () {
385+
return !$("#working-set-list-container").hasClass("working-set-hidden");
386+
}, "working set to show");
387+
388+
// the marker must align with the newly selected item and the item
389+
// must be scrolled into view
390+
await awaitsFor(_isMarkerAlignedWithSelection, "selection marker to align with selected item");
391+
await awaitsFor(function () {
392+
const $selected = $(".open-files-container li.selected");
393+
const containerTop = $container.offset().top;
394+
const containerBottom = containerTop + $container.height();
395+
return $selected.offset().top >= containerTop &&
396+
($selected.offset().top + $selected.height()) <= containerBottom;
397+
}, "selected item to be scrolled into view");
398+
});
399+
400+
it("should reposition selection marker when switching back to the files sidebar tab", async function () {
401+
const $ = testWindow.$;
402+
const SidebarTabs = testWindow.brackets.test.SidebarTabs;
403+
const fileList = MainViewManager.getWorkingSet(MainViewManager.ACTIVE_PANE);
404+
const TEST_TAB = "sidebar-tab-wsv-selection-test";
405+
406+
// view the first file
407+
await awaitsForDone(CommandManager.execute(Commands.FILE_OPEN, { fullPath: fileList[0].fullPath }),
408+
"FILE_OPEN first file");
409+
410+
// switch to another sidebar tab, which hides the files tab content
411+
SidebarTabs.addTab(TEST_TAB, "wsv test", "fa-solid fa-flask");
412+
SidebarTabs.setActiveTab(TEST_TAB);
413+
await awaitsFor(function () {
414+
return !$("#working-set-list-container").is(":visible");
415+
}, "working set to be hidden by tab switch");
416+
417+
// switch to the second file while the files tab content is hidden
418+
await awaitsForDone(CommandManager.execute(Commands.FILE_OPEN, { fullPath: fileList[1].fullPath }),
419+
"FILE_OPEN second file");
420+
421+
// switch back to the files tab
422+
SidebarTabs.setActiveTab(SidebarTabs.SIDEBAR_TAB_FILES);
423+
await awaitsFor(function () {
424+
return $("#working-set-list-container").is(":visible");
425+
}, "working set to be visible again");
426+
427+
await awaitsFor(_isMarkerAlignedWithSelection, "selection marker to align with selected item");
428+
429+
SidebarTabs.removeTab(TEST_TAB);
430+
});
431+
344432
it("should allow refresh to be used to update the class list", async function () {
345433
function classProvider(file) {
346434
return "one";

0 commit comments

Comments
 (0)