diff --git a/src/project/WorkingSetView.js b/src/project/WorkingSetView.js index b4b26089906..5c54c80d657 100644 --- a/src/project/WorkingSetView.js +++ b/src/project/WorkingSetView.js @@ -39,6 +39,7 @@ define(function (require, exports, module) { Commands = require("command/Commands"), Menus = require("command/Menus"), FileViewController = require("project/FileViewController"), + CollectionUtils = require("utils/CollectionUtils"), ViewUtils = require("utils/ViewUtils"); @@ -88,6 +89,108 @@ define(function (require, exports, module) { } } + /** + * @private + * Adds directory names to elements representing passed files in working tree + * @param {Array.} filesList - list of FileEntries with the same filename + */ + function _addDirectoryNamesToWorkingTreeFiles(filesList) { + // filesList must have at least two files in it for this to make sense + if (filesList.length <= 1) { + return; + } + + // First collect paths from the list of files and fill map with them + var map = {}, filePaths = [], displayPaths = []; + filesList.forEach(function (file, index) { + var fp = file.fullPath.split("/"); + fp.pop(); // Remove the filename itself + displayPaths[index] = fp.pop(); + filePaths[index] = fp; + + if (!map[displayPaths[index]]) { + map[displayPaths[index]] = [index]; + } else { + map[displayPaths[index]].push(index); + } + }); + + // This function is used to loop through map and resolve duplicate names + var processMap = function (map) { + var didSomething = false; + CollectionUtils.forEach(map, function (arr, key) { + // length > 1 means we have duplicates that need to be resolved + if (arr.length > 1) { + arr.forEach(function (index) { + if (filePaths[index].length !== 0) { + displayPaths[index] = filePaths[index].pop() + "/" + displayPaths[index]; + didSomething = true; + + if (!map[displayPaths[index]]) { + map[displayPaths[index]] = [index]; + } else { + map[displayPaths[index]].push(index); + } + } + }); + } + delete map[key]; + }); + return didSomething; + }; + + var repeat; + do { + repeat = processMap(map); + } while (repeat); + + // Go through open files and add directories to appropriate entries + $openFilesContainer.find("ul > li").each(function () { + var $li = $(this); + var io = filesList.indexOf($li.data(_FILE_KEY)); + if (io !== -1) { + var dirSplit = displayPaths[io].split("/"); + if (dirSplit.length > 3) { + displayPaths[io] = dirSplit[0] + "/\u2026/" + dirSplit[dirSplit.length - 1]; + } + + var $dir = $("").html(" — " + displayPaths[io]); + $li.children("a").append($dir); + } + }); + } + + /** + * @private + * Looks for files with the same name in the working set + * and adds a parent directory name to them + */ + function _checkForDuplicatesInWorkingTree() { + var map = {}, + fileList = DocumentManager.getWorkingSet(); + + // We need to always clear current directories as files could be removed from working tree. + $openFilesContainer.find("ul > li > a > span.directory").remove(); + + // Go through files and fill map with arrays of files. + fileList.forEach(function (file) { + // Use the same function that is used to create html for file. + var displayHtml = ViewUtils.getFileEntryDisplay(file); + + if (!map[displayHtml]) { + map[displayHtml] = []; + } + map[displayHtml].push(file); + }); + + // Go through the map and solve the arrays with length over 1. Ignore the rest. + CollectionUtils.forEach(map, function (value) { + if (value.length > 1) { + _addDirectoryNamesToWorkingTreeFiles(value); + } + }); + } + /** * @private * Shows/Hides open files list based on working set content. @@ -99,6 +202,7 @@ define(function (require, exports, module) { } else { $openFilesContainer.show(); $workingSetHeader.show(); + _checkForDuplicatesInWorkingTree(); } _adjustForScrollbars(); _fireSelectionChanged(); diff --git a/src/styles/brackets.less b/src/styles/brackets.less index b08f7cb6211..c77bc0b54a8 100644 --- a/src/styles/brackets.less +++ b/src/styles/brackets.less @@ -403,9 +403,13 @@ a, img { padding: 3px (@sidebar-triangle-size * 2) 3px 0; cursor: default; + + .directory { + font-size: 11px; + } } - .extension { + .extension, .directory { color: @project-panel-text-2; } } diff --git a/test/spec/WorkingSetView-test-files/directory/directory/file_one.js b/test/spec/WorkingSetView-test-files/directory/directory/file_one.js new file mode 100644 index 00000000000..e69de29bb2d diff --git a/test/spec/WorkingSetView-test-files/directory/file_one.js b/test/spec/WorkingSetView-test-files/directory/file_one.js new file mode 100644 index 00000000000..e69de29bb2d diff --git a/test/spec/WorkingSetView-test.js b/test/spec/WorkingSetView-test.js index 94dcd62f0f9..9a583aa7124 100644 --- a/test/spec/WorkingSetView-test.js +++ b/test/spec/WorkingSetView-test.js @@ -23,7 +23,7 @@ /*jslint vars: true, plusplus: true, devel: true, browser: true, nomen: true, indent: 4, maxerr: 50 */ -/*global $, define, describe, it, expect, beforeEach, afterEach, waitsFor, runs, beforeFirst, afterLast */ +/*global $, define, describe, it, expect, beforeEach, afterEach, waitsFor, waitsForDone, runs, beforeFirst, afterLast */ define(function (require, exports, module) { "use strict"; @@ -43,7 +43,6 @@ define(function (require, exports, module) { testWindow, workingSetCount; - function openAndMakeDirty(path) { var doc, didOpen = false, gotError = false; @@ -95,7 +94,6 @@ define(function (require, exports, module) { SpecRunnerUtils.closeTestWindow(); } - beforeFirst(function () { createTestWindow(this, true); }); @@ -116,8 +114,6 @@ define(function (require, exports, module) { testWindow.closeAllFiles(); }); - - it("should add a list item when a file is dirtied", function () { // check if files are added to work set and dirty icons are present runs(function () { @@ -151,7 +147,7 @@ define(function (require, exports, module) { runs(function () { var $ = testWindow.$; var secondItem = $($("#open-files-container > ul").children()[1]); - secondItem.trigger('click'); + secondItem.trigger("click"); var $listItems = $("#open-files-container > ul").children(); expect($($listItems[0]).hasClass("selected")).not.toBeTruthy(); @@ -219,7 +215,7 @@ define(function (require, exports, module) { // hover over and click on close icon of 2nd list item var secondItem = $($("#open-files-container > ul").children()[1]); - secondItem.trigger('mouseover'); + secondItem.trigger("mouseover"); var closeIcon = secondItem.find(".file-status-icon"); expect(closeIcon.length).toBe(1); @@ -228,7 +224,7 @@ define(function (require, exports, module) { didClose = true; }); - closeIcon.trigger('mousedown'); + closeIcon.trigger("mousedown"); }); waitsFor(function () { return didClose; }, "click on working set close icon timeout", 1000); @@ -257,7 +253,7 @@ define(function (require, exports, module) { var $ = testWindow.$; var secondItem = $("#open-files-container > ul").children().eq(1); var fileName = secondItem.text(); - secondItem.trigger('click'); + secondItem.trigger("click"); // Calling FILE_RENAME synchronously works fine here since the item is already visible in project file tree. // However, if the selected item is not already visible in the tree, this command will complete asynchronously. @@ -269,6 +265,67 @@ define(function (require, exports, module) { expect($projectFileItems.find("a.jstree-clicked").eq(0).siblings("input").eq(0).val()).toBe(fileName); }); }); + + it("should show a directory name next to the file name when two files with same names are opened", function () { + runs(function () { + // Count currently opened files + var workingSetCountBeforeTest = workingSetCount; + + // First we need to open another file + openAndMakeDirty(testPath + "/directory/file_one.js"); + + // Wait for file to be added to the working set + waitsFor(function () { return workingSetCount === workingSetCountBeforeTest + 1; }, 1000); + + runs(function () { + // Two files with the same name file_one.js should be now opened + var $list = testWindow.$("#open-files-container > ul"); + expect($list.find(".directory").length).toBe(2); + + // Now close last opened file to hide the directories again + DocumentManager.getCurrentDocument()._markClean(); // so we can close without a save dialog + var didClose = false, gotError = false; + waitsForDone(CommandManager.execute(Commands.FILE_CLOSE), "timeout on FILE_CLOSE", 1000); + + // there should be no more directories shown + runs(function () { + expect($list.find(".directory").length).toBe(0); + }); + }); + }); + }); + + it("should show different directory names, when two files of the same name are opened, located in folders with same name", function () { + runs(function () { + // Count currently opened files + var workingSetCountBeforeTest = workingSetCount; + + // Open both files + openAndMakeDirty(testPath + "/directory/file_one.js"); + openAndMakeDirty(testPath + "/directory/directory/file_one.js"); + + // Wait for them to load + waitsFor(function () { return workingSetCount === workingSetCountBeforeTest + 2; }, 1000); + + runs(function () { + // Collect all directory names displayed + var $list = testWindow.$("#open-files-container > ul"); + var names = $list.find(".directory").map(function () { + return $(this).text(); + }).toArray(); + + // All directory names should be unique + var uniq = 0, map = {}; + names.forEach(function (name) { + if (!map[name]) { + map[name] = true; + uniq++; + } + }); + expect(uniq).toBe(names.length); + }); + }); + }); }); }); \ No newline at end of file