Skip to content
Merged
Show file tree
Hide file tree
Changes from all 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
8 changes: 8 additions & 0 deletions docs/API-Reference/project/WorkingSetView.md
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,14 @@
const WorkingSetView = brackets.getModule("project/WorkingSetView")
```

<a name="refreshSelection"></a>

## refreshSelection()
Recomputes the selection marker of all Pane View List Views and scrolls
the selected item into view. Needed after the working set becomes visible
again, as marker positions computed while it was display:none are all 0.

**Kind**: global function
<a name="refresh"></a>

## refresh()
Expand Down
14 changes: 13 additions & 1 deletion docs/API-Reference/utils/NodeUtils.md
Original file line number Diff line number Diff line change
Expand Up @@ -123,7 +123,7 @@ This is only available in the native app.

<a name="execFileWithInput"></a>

## execFileWithInput(command, [args], [options]) ⇒ <code>Promise.&lt;{code: number, stdout: string, stderr: string}&gt;</code>
## execFileWithInput(command, [args], [options]) ⇒ [<code>Promise.&lt;ExecFileResult&gt;</code>](#ExecFileResult)
Runs an executable with the given args, feeding it text on stdin and capturing its output -
a one-shot filter-style invocation (e.g. `ruff format -` for the Python beautifier). No
shell is involved. Resolves with the exit code rather than rejecting on non-zero, so
Expand Down Expand Up @@ -271,3 +271,15 @@ operation - the promise then rejects with a "cancelled" error.
| --- | --- | --- |
| cancel | <code>function</code> | aborts the in-flight operation |

<a name="ExecFileResult"></a>

## ExecFileResult : <code>Object</code>
**Kind**: global typedef
**Properties**

| Name | Type | Description |
| --- | --- | --- |
| code | <code>number</code> | the process's exit code |
| stdout | <code>string</code> | |
| stderr | <code>string</code> | |

3 changes: 3 additions & 0 deletions src/project/SidebarView.js
Original file line number Diff line number Diff line change
Expand Up @@ -301,6 +301,9 @@ define(function (require, exports, module) {
if(getPref) {
// refer to brackets.less file for styles
$workingSet.removeClass("working-set-hidden");
// marker positions computed while the container was display:none are
// all 0, so recompute the selection now that it is visible again
WorkingSetView.refreshSelection();
} else {
$workingSet.addClass("working-set-hidden");
}
Expand Down
30 changes: 28 additions & 2 deletions src/project/WorkingSetView.js
Original file line number Diff line number Diff line change
Expand Up @@ -121,6 +121,17 @@ define(function (require, exports, module) {
*/
var _DRAG_MOVE_DETECTION_START = 3;

/**
* Recomputes the selection marker of all Pane View List Views and scrolls
* the selected item into view. Needed after the working set becomes visible
* again, as marker positions computed while it was display:none are all 0.
*/
function refreshSelection() {
_.forEach(_views, function (view) {
view._updateListSelection();
});
}

/**
* Refreshes all Pane View List Views
*/
Expand Down Expand Up @@ -298,7 +309,8 @@ define(function (require, exports, module) {
offset,
$copy,
$ghost,
draggingCurrentFile;
draggingCurrentFile,
openedOnMouseDown = false;

function initDragging() {
itemHeight = $el.height();
Expand Down Expand Up @@ -778,6 +790,10 @@ define(function (require, exports, module) {
.always(function () {
postDropCleanup();
});
} else if (openedOnMouseDown) {
// file was already opened on mouse down for a fast switch feel,
// nothing left to do here
postDropCleanup();
} else {
// Normal right and left click - select the item
FileViewController.setFileViewFocus(FileViewController.WORKING_SET_VIEW);
Expand Down Expand Up @@ -853,7 +869,16 @@ define(function (require, exports, module) {
return;
}


// open the file right away on mouse down like the file tree and the
// editor tab bar do, so switching files feels fast instead of waiting
// for mouse up. A drag can still follow - the item just becomes the
// current file as soon as it is pressed.
if (!tryClosing) {
openedOnMouseDown = true;
FileViewController.setFileViewFocus(FileViewController.WORKING_SET_VIEW);
CommandManager.execute(Commands.FILE_OPEN, {fullPath: sourceFile.fullPath,
paneId: sourceView.paneId});
}

e.stopPropagation();
});
Expand Down Expand Up @@ -1628,6 +1653,7 @@ define(function (require, exports, module) {
// Public API
exports.createWorkingSetViewForPane = createWorkingSetViewForPane;
exports.refresh = refresh;
exports.refreshSelection = refreshSelection;
exports.addIconProvider = addIconProvider;
exports.addClassProvider = addClassProvider;
exports.syncSelectionIndicator = syncSelectionIndicator;
Expand Down
9 changes: 8 additions & 1 deletion src/utils/NodeUtils.js
Original file line number Diff line number Diff line change
Expand Up @@ -233,6 +233,13 @@ define(function (require, exports, module) {
return utilsConnector.execPeer("setExecutableBits", {filePath});
}

/**
* @typedef {Object} ExecFileResult
* @property {number} code - the process's exit code
* @property {string} stdout
* @property {string} stderr
*/

/**
* Runs an executable with the given args, feeding it text on stdin and capturing its output -
* a one-shot filter-style invocation (e.g. `ruff format -` for the Python beautifier). No
Expand All @@ -246,7 +253,7 @@ define(function (require, exports, module) {
* @param {string} [options.stdinText] - written to the process's stdin, then closed
* @param {string} [options.cwd] - working directory
* @param {number} [options.timeoutMs] - kill the process and reject after this long
* @return {Promise<{code: number, stdout: string, stderr: string}>}
* @return {Promise<ExecFileResult>}
*/
async function execFileWithInput(command, args, options) {
if(!Phoenix.isNativeApp) {
Expand Down
8 changes: 7 additions & 1 deletion src/view/SidebarTabs.js
Original file line number Diff line number Diff line change
Expand Up @@ -40,7 +40,8 @@ define(function (require, exports, module) {
const AppInit = require("utils/AppInit"),
EventDispatcher = require("utils/EventDispatcher"),
Metrics = require("utils/Metrics"),
PreferencesManager = require("preferences/PreferencesManager");
PreferencesManager = require("preferences/PreferencesManager"),
WorkingSetView = require("project/WorkingSetView");

// --- Constants -----------------------------------------------------------

Expand Down Expand Up @@ -447,6 +448,11 @@ define(function (require, exports, module) {
}

if (previousTabId !== id) {
if (id === SIDEBAR_TAB_FILES) {
// working set marker positions computed while the files tab content was
// display:none are all 0, so recompute the selection now that it is visible
WorkingSetView.refreshSelection();
}
exports.trigger(EVENT_TAB_CHANGED, id, previousTabId);
}
}
Expand Down
88 changes: 88 additions & 0 deletions test/spec/WorkingSetView-integ-test.js
Original file line number Diff line number Diff line change
Expand Up @@ -107,6 +107,11 @@ define(function (require, exports, module) {
});

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

Expand Down Expand Up @@ -341,6 +346,89 @@ define(function (require, exports, module) {
});
});

function _isMarkerAlignedWithSelection() {
const $ = testWindow.$;
const $selected = $(".open-files-container li.selected");
const $marker = $(".open-files-container .sidebar-selection");
return $selected.length === 1 && $marker.length === 1 &&
Math.abs($marker.offset().top - $selected.offset().top) < 2;
}

it("should reposition selection marker and reveal selection when working set is shown again", async function () {
const $ = testWindow.$;
const PreferencesManager = testWindow.brackets.test.PreferencesManager;
const fileList = MainViewManager.getWorkingSet(MainViewManager.ACTIVE_PANE);
const $container = $(".open-files-container");

// view the first file
await awaitsForDone(CommandManager.execute(Commands.FILE_OPEN, { fullPath: fileList[0].fullPath }),
"FILE_OPEN first file");

// shrink the list so the second item overflows and must be scrolled to
$container.css("height", "30px");
$container.scrollTop(0);

// hide the working set
PreferencesManager.set("showWorkingSet", false);
await awaitsFor(function () {
return $("#working-set-list-container").hasClass("working-set-hidden");
}, "working set to hide");

// switch to the second file while hidden - marker offsets all compute
// to 0 on display:none elements, so it cannot be positioned now
await awaitsForDone(CommandManager.execute(Commands.FILE_OPEN, { fullPath: fileList[1].fullPath }),
"FILE_OPEN second file");

// show the working set again
PreferencesManager.set("showWorkingSet", true);
await awaitsFor(function () {
return !$("#working-set-list-container").hasClass("working-set-hidden");
}, "working set to show");

// the marker must align with the newly selected item and the item
// must be scrolled into view
await awaitsFor(_isMarkerAlignedWithSelection, "selection marker to align with selected item");
await awaitsFor(function () {
const $selected = $(".open-files-container li.selected");
const containerTop = $container.offset().top;
const containerBottom = containerTop + $container.height();
return $selected.offset().top >= containerTop &&
($selected.offset().top + $selected.height()) <= containerBottom;
}, "selected item to be scrolled into view");
});

it("should reposition selection marker when switching back to the files sidebar tab", async function () {
const $ = testWindow.$;
const SidebarTabs = testWindow.brackets.test.SidebarTabs;
const fileList = MainViewManager.getWorkingSet(MainViewManager.ACTIVE_PANE);
const TEST_TAB = "sidebar-tab-wsv-selection-test";

// view the first file
await awaitsForDone(CommandManager.execute(Commands.FILE_OPEN, { fullPath: fileList[0].fullPath }),
"FILE_OPEN first file");

// switch to another sidebar tab, which hides the files tab content
SidebarTabs.addTab(TEST_TAB, "wsv test", "fa-solid fa-flask");
SidebarTabs.setActiveTab(TEST_TAB);
await awaitsFor(function () {
return !$("#working-set-list-container").is(":visible");
}, "working set to be hidden by tab switch");

// switch to the second file while the files tab content is hidden
await awaitsForDone(CommandManager.execute(Commands.FILE_OPEN, { fullPath: fileList[1].fullPath }),
"FILE_OPEN second file");

// switch back to the files tab
SidebarTabs.setActiveTab(SidebarTabs.SIDEBAR_TAB_FILES);
await awaitsFor(function () {
return $("#working-set-list-container").is(":visible");
}, "working set to be visible again");

await awaitsFor(_isMarkerAlignedWithSelection, "selection marker to align with selected item");

SidebarTabs.removeTab(TEST_TAB);
});

it("should allow refresh to be used to update the class list", async function () {
function classProvider(file) {
return "one";
Expand Down
Loading