diff --git a/src/command/Commands.js b/src/command/Commands.js index caa7c4bd14..edf3bd1fd4 100644 --- a/src/command/Commands.js +++ b/src/command/Commands.js @@ -534,6 +534,9 @@ define(function (require, exports, module) { /** Closes unmodified files */ exports.CMD_GIT_CLOSE_UNMODIFIED = "git-close-unmodified-files"; + /** Opens all modified/untracked files */ + exports.CMD_GIT_OPEN_CHANGED_FILES = "git-open-changed-files"; + /** Checks out a branch or commit */ exports.CMD_GIT_CHECKOUT = "git-checkout"; diff --git a/src/extensions/default/Git/src/Branch.js b/src/extensions/default/Git/src/Branch.js index 51f3236c98..492d37ede5 100644 --- a/src/extensions/default/Git/src/Branch.js +++ b/src/extensions/default/Git/src/Branch.js @@ -303,6 +303,7 @@ define(function (require, exports) { function attachCloseEvents() { $("html").on("click", closeDropdown); $("#project-files-container").on("scroll", closeDropdown); + $("#git-panel .table-container").on("scroll", closeDropdown); $("#titlebar .nav").on("click", closeDropdown); currentEditor = EditorManager.getCurrentFullEditor(); @@ -316,6 +317,7 @@ define(function (require, exports) { function detachCloseEvents() { $("html").off("click", closeDropdown); $("#project-files-container").off("scroll", closeDropdown); + $("#git-panel .table-container").off("scroll", closeDropdown); $("#titlebar .nav").off("click", closeDropdown); if (currentEditor) { @@ -327,8 +329,59 @@ define(function (require, exports) { $dropdown = null; } + function _positionDropdownBelow($toggle) { + // two margins to account for the preceding project dropdown as well + const marginLeft = (parseInt($toggle.css("margin-left"), 10) * 2) || 0; + + const toggleOffset = $toggle.offset(); + + $dropdown + .css({ + left: toggleOffset.left - marginLeft + 3, + top: toggleOffset.top + $toggle.outerHeight() - 3 + }) + .appendTo($("body")); + + // fix so it doesn't overflow the screen + const maxHeight = $dropdown.parent().height(), + height = $dropdown.height(), + topOffset = $dropdown.position().top; + if (height + topOffset >= maxHeight - 10) { + $dropdown.css("bottom", "10px"); + } + } + + function _positionDropdownAbove($anchor) { + const anchorOffset = $anchor.offset(); + + $dropdown + .css({ + left: anchorOffset.left, + // the .dropdown-menu class positions with "top: 100%", it has to be + // explicitly overridden or the bottom positioning below is over-constrained + top: "auto", + bottom: $(window).height() - anchorOffset.top + 3, + // grow upwards from the anchor instead of the default top-left origin + "transform-origin": "0 100%" + }) + .appendTo($("body")); + + // fix so it doesn't overflow the screen + if ($dropdown.height() >= anchorOffset.top - 10) { + $dropdown.css("top", "10px"); + } + + const rightOverflow = $dropdown.offset().left + $dropdown.outerWidth() - ($(window).width() - 10); + if (rightOverflow > 0) { + $dropdown.css("left", anchorOffset.left - rightOverflow); + } + } + function toggleDropdown(e) { e.stopPropagation(); + // currentTarget is only valid while the event is being dispatched, + // so it has to be captured before the async branch listing below + const $anchor = $(e.currentTarget); // If the dropdown is already visible, close it if ($dropdown) { @@ -349,25 +402,11 @@ define(function (require, exports) { }, []); $dropdown = $(renderList(branches)); - const $toggle = $("#git-branch-dropdown-toggle"); - // two margins to account for the preceding project dropdown as well - const marginLeft = (parseInt($toggle.css("margin-left"), 10) * 2) || 0; - - const toggleOffset = $toggle.offset(); - - $dropdown - .css({ - left: toggleOffset.left - marginLeft + 3, - top: toggleOffset.top + $toggle.outerHeight() - 3 - }) - .appendTo($("body")); - - // fix so it doesn't overflow the screen - var maxHeight = $dropdown.parent().height(), - height = $dropdown.height(), - topOffset = $dropdown.position().top; - if (height + topOffset >= maxHeight - 10) { - $dropdown.css("bottom", "10px"); + if ($anchor.closest("#git-panel").length) { + // the git panel sits at the bottom of the screen, open upwards from there + _positionDropdownAbove($anchor); + } else { + _positionDropdownBelow($("#git-branch-dropdown-toggle")); } PopUpManager.addPopUp($dropdown, detachCloseEvents, true, {closeCurrentPopups: true}); @@ -426,6 +465,7 @@ define(function (require, exports) { .text("\u2026") .parent() .show(); + Panel.setBranchName("\u2026", ""); return Git.getGitRoot().then(function (gitRoot) { var projectRoot = Utils.getProjectRoot(), @@ -440,6 +480,8 @@ define(function (require, exports) { $gitBranchName .off("click") .text("not a git repo"); + Panel.setBranchName("not a git repo", ""); + $("#git-panel .git-panel-branch").removeClass("clickable").off("click"); Panel.disable("not-repo"); return; @@ -482,6 +524,11 @@ define(function (require, exports) { .attr("title", tooltip) .off("click") .on("click", toggleDropdown); + Panel.setBranchName(branchName, tooltip); + $("#git-panel .git-panel-branch") + .addClass("clickable") + .off("click") + .on("click", toggleDropdown); Panel.enable(); }).catch(function (err) { @@ -493,6 +540,8 @@ define(function (require, exports) { $gitBranchName .off("click") .text("no branch"); + Panel.setBranchName("no branch", ""); + $("#git-panel .git-panel-branch").removeClass("clickable").off("click"); Panel.enable(); } else { throw ex; diff --git a/src/extensions/default/Git/src/Constants.js b/src/extensions/default/Git/src/Constants.js index 2f851d7a52..5f7699d7e0 100644 --- a/src/extensions/default/Git/src/Constants.js +++ b/src/extensions/default/Git/src/Constants.js @@ -36,6 +36,7 @@ define(function (require, exports) { exports.CMD_GIT_CLONE_WITH_URL = Commands.CMD_GIT_CLONE_WITH_URL; exports.CMD_GIT_SETTINGS_COMMAND_ID = Commands.CMD_GIT_SETTINGS_COMMAND_ID; exports.CMD_GIT_CLOSE_UNMODIFIED = Commands.CMD_GIT_CLOSE_UNMODIFIED; + exports.CMD_GIT_OPEN_CHANGED_FILES = Commands.CMD_GIT_OPEN_CHANGED_FILES; exports.CMD_GIT_CHECKOUT = Commands.CMD_GIT_CHECKOUT; exports.CMD_GIT_RESET_HARD = Commands.CMD_GIT_RESET_HARD; exports.CMD_GIT_RESET_SOFT = Commands.CMD_GIT_RESET_SOFT; diff --git a/src/extensions/default/Git/src/Main.js b/src/extensions/default/Git/src/Main.js index 16249c4ca4..15e8aedb14 100644 --- a/src/extensions/default/Git/src/Main.js +++ b/src/extensions/default/Git/src/Main.js @@ -241,6 +241,7 @@ define(function (require, exports) { Constants.CMD_GIT_RESET_SOFT, // "More options" context menu commands + Constants.CMD_GIT_OPEN_CHANGED_FILES, Constants.CMD_GIT_DISCARD_ALL_CHANGES, Constants.CMD_GIT_UNDO_LAST_COMMIT, Constants.CMD_GIT_TOGGLE_UNTRACKED, @@ -322,6 +323,8 @@ define(function (require, exports) { // create context menu for git more options const optionsCmenu = Menus.registerContextMenu(Constants.GIT_PANEL_OPTIONS_CMENU); Menus.ContextMenu.assignContextMenuToSelector(".git-more-options-btn", optionsCmenu); + optionsCmenu.addMenuItem(Constants.CMD_GIT_OPEN_CHANGED_FILES); + optionsCmenu.addMenuDivider(); optionsCmenu.addMenuItem(Constants.CMD_GIT_DISCARD_ALL_CHANGES); optionsCmenu.addMenuItem(Constants.CMD_GIT_UNDO_LAST_COMMIT); optionsCmenu.addMenuDivider(); @@ -398,6 +401,7 @@ define(function (require, exports) { Utils.enableCommand(Constants.CMD_GIT_GOTO_NEXT_CHANGE, enabled); Utils.enableCommand(Constants.CMD_GIT_GOTO_PREVIOUS_CHANGE, enabled); Utils.enableCommand(Constants.CMD_GIT_CLOSE_UNMODIFIED, enabled); + Utils.enableCommand(Constants.CMD_GIT_OPEN_CHANGED_FILES, enabled); Utils.enableCommand(Constants.CMD_GIT_AUTHORS_OF_SELECTION, enabled); Utils.enableCommand(Constants.CMD_GIT_AUTHORS_OF_FILE, enabled); diff --git a/src/extensions/default/Git/src/Panel.js b/src/extensions/default/Git/src/Panel.js index 33a5c3b547..0cd592b758 100644 --- a/src/extensions/default/Git/src/Panel.js +++ b/src/extensions/default/Git/src/Panel.js @@ -15,6 +15,7 @@ define(function (require, exports) { Menus = brackets.getModule("command/Menus"), Mustache = brackets.getModule("thirdparty/mustache/mustache"), FindInFiles = brackets.getModule("search/FindInFiles"), + MainViewManager = brackets.getModule("view/MainViewManager"), WorkspaceManager = brackets.getModule("view/WorkspaceManager"), ProjectManager = brackets.getModule("project/ProjectManager"), StringUtils = brackets.getModule("utils/StringUtils"), @@ -851,6 +852,19 @@ define(function (require, exports) { } } + const BRANCH_MAX_LEN = 18; + + // called by Branch.refresh() so the panel indicator always mirrors the sidebar one, + // including merge/rebase decorations like "main|MERGING" + function setBranchName(branchName, tooltip) { + const displayName = branchName.length > BRANCH_MAX_LEN + ? branchName.substring(0, BRANCH_MAX_LEN) + "\u2026" + : branchName; + const $branch = $gitPanel.find(".git-panel-branch"); + $branch.attr("title", tooltip || ""); + $branch.find(".git-branch-name").text(displayName); + } + function refreshCommitCounts() { // Find Push and Pull buttons var $pullBtn = $gitPanel.find(".git-pull"); @@ -1007,6 +1021,49 @@ define(function (require, exports) { }); } + function openAllChangedFiles() { + return Git.status().then(function (files) { + files = _.filter(files, function (file) { + if (!shouldShow(file)) { + return false; + } + // deleted files no longer exist on disk, so there is nothing to open + if (file.status.indexOf(Git.FILE_STATUS.DELETED) !== -1) { + return false; + } + // respect the "show untracked files" panel toggle + if (!showingUntracked && file.status.indexOf(Git.FILE_STATUS.UNTRACKED) !== -1) { + return false; + } + return true; + }); + + if (files.length === 0) { + return; + } + + const currentGitRoot = Preferences.get("currentGitRoot"); + // addListToWorkingSet ignores files already present in any pane, so this is safe + // to run repeatedly or with some of the files already open + const fileList = files.map(function (file) { + return FileSystem.getFileForPath(currentGitRoot + file.file); + }); + MainViewManager.addListToWorkingSet(MainViewManager.ACTIVE_PANE, fileList); + + // if the user is already viewing one of the changed files, we don't switch the file then... + // if user is viewing a un-changed file, then we switch to the first changed file + const currentPath = MainViewManager.getCurrentlyViewedPath(MainViewManager.ACTIVE_PANE); + const viewingChangedFile = _.any(fileList, function (file) { + return file.fullPath === currentPath; + }); + if (!viewingChangedFile) { + CommandManager.execute(Commands.FILE_OPEN, { fullPath: fileList[0].fullPath }); + } + }).catch(function (err) { + ErrorHandler.showError(err, Strings.ERROR_OPENING_CHANGED_FILES); + }); + } + var lastCheckOneClicked = null; function attachDefaultTableHandlers() { @@ -1214,6 +1271,7 @@ define(function (require, exports) { const mainToolbarWidth = $mainToolbar.width(); let overFlowWidth = 540; const breakpoints = [ + { width: 600, className: "hide-when-medium" }, { width: overFlowWidth, className: "hide-when-small" }, { width: 400, className: "hide-when-x-small" } ]; @@ -1345,6 +1403,8 @@ define(function (require, exports) { CommandManager.register(Strings.VIEW_AUTHORS_SELECTION, Constants.CMD_GIT_AUTHORS_OF_SELECTION, handleAuthorsSelection); CommandManager.register(Strings.VIEW_AUTHORS_FILE, Constants.CMD_GIT_AUTHORS_OF_FILE, handleAuthorsFile); CommandManager.register(Strings.HIDE_UNTRACKED, Constants.CMD_GIT_TOGGLE_UNTRACKED, handleToggleUntracked); + CommandManager.register(Strings.CMD_OPEN_CHANGED_FILES, + Constants.CMD_GIT_OPEN_CHANGED_FILES, openAllChangedFiles); CommandManager.register(Strings.GIT_INIT, Constants.CMD_GIT_INIT, EventEmitter.getEmitter(Events.HANDLE_GIT_INIT)); CommandManager.register(Strings.GIT_CLONE, Constants.CMD_GIT_CLONE, EventEmitter.getEmitter(Events.HANDLE_GIT_CLONE)); CommandManager.register(Strings.GIT_SHOW_HISTORY, Constants.CMD_GIT_HISTORY_GLOBAL, ()=>{ @@ -1555,6 +1615,7 @@ define(function (require, exports) { exports.toggle = toggle; exports.enable = enable; exports.disable = disable; + exports.setBranchName = setBranchName; exports.getSelectedHistoryCommit = getSelectedHistoryCommit; exports.getPanel = function () { return $gitPanel; }; diff --git a/src/extensions/default/Git/styles/git-styles.less b/src/extensions/default/Git/styles/git-styles.less index 0cf885384b..035dce6841 100644 --- a/src/extensions/default/Git/styles/git-styles.less +++ b/src/extensions/default/Git/styles/git-styles.less @@ -1088,6 +1088,28 @@ right: 32px; top: 5px; } + .git-panel-branch { + display: inline-block; + vertical-align: middle; + box-sizing: border-box; + height: 22px; + margin-top: 2px; + margin-right: 8px; + padding: 2px 10px; + font-size: 12px; + line-height: 16px; + white-space: nowrap; + cursor: default; + i { + margin-right: 4px; + } + &:not(.clickable) { + opacity: .5; + } + &.clickable { + cursor: pointer; + } + } .octicon:not(:only-child) { margin-right: 5px; vertical-align: -1px; diff --git a/src/extensions/default/Git/templates/git-panel.html b/src/extensions/default/Git/templates/git-panel.html index b0133c2c87..132d7cce07 100644 --- a/src/extensions/default/Git/templates/git-panel.html +++ b/src/extensions/default/Git/templates/git-panel.html @@ -57,6 +57,9 @@