Bug 419633 - Search: Add ability to select search scope from within the search page
- Initial implementation supporting selecting a single folder or the root as the scope
diff --git a/bundles/org.eclipse.orion.client.ui/web/css/controls.css b/bundles/org.eclipse.orion.client.ui/web/css/controls.css
index 38b6ee0..264e973 100644
--- a/bundles/org.eclipse.orion.client.ui/web/css/controls.css
+++ b/bundles/org.eclipse.orion.client.ui/web/css/controls.css
@@ -210,6 +210,10 @@
 	border-spacing: 8px;

 }

 

+.selectableNavRow:hover {

+	background-color: #cedce7;

+}

+

 .disabledNavRow > .navColumn > .mainNavColumn {

 	outline: 1px dashed lightgray;

 }

@@ -231,7 +235,7 @@
 }

 

 .singleNavColumn {

-	width: 100%;

+	width: calc(100% - 6px); /* accounts for left border width */

 	white-space: nowrap;

 }

 

@@ -990,6 +994,91 @@
 	border-bottom: 0;

 }

 

+/* Saved Searches section */

+#searchSectionContent.sectionTable {

+	box-shadow: none;

+}

+

+#searchSectionContent.sectionTable .mainNavColumn {

+	padding-right: 0;

+}

+

+/* Search Scope section */

+.searchScope .sectionWrapper {

+	color: #222; /* same as globalSearch.css->.search-label */

+	margin: 0 5px;

+}

+

+.searchScope .sectionWrapper.sectionClosed {

+	opacity: 1;

+}

+

+.searchScope .sectionTable {

+	background-color: white;

+	box-shadow: none;

+	margin: 2px 10px;

+}

+

+.searchScope .sectionTable .mainNavColumn span{

+	color: #222;

+}

+

+.searchScope .sectionTitle {

+	margin-top: 2px;

+	max-width: calc(100% - 30px);

+}

+

+.searchScope span.core-sprite-closedarrow, .searchScope span.core-sprite-openarrow {

+	cursor: pointer;

+}

+

+.searchScope span.core-sprite-closedarrow:hover, .searchScope span.core-sprite-openarrow:hover {

+	color: #F58B0F;

+}

+

+.searchScopeElement {

+	background-color: whitesmoke;

+	border: 1px solid lightgray;

+	border-radius: 5px;

+	color: #222;

+	cursor: initial;

+	display: inline-block;

+	font-weight: normal;

+	margin: 0 2px;

+	padding: 0 3px;

+	max-width: 100%; /* prevents div from overflowing parent */

+	white-space: nowrap;

+}

+

+.searchScopePreButtonText {

+	display: inline-block;

+	max-width: calc(100% - 15px);

+	overflow: hidden;

+	text-overflow: ellipsis;

+	white-space: nowrap;

+	vertical-align: bottom;

+}

+

+button.scopeDeleteButton {

+	font-size: 14px;

+	margin-left: 2px;

+}

+

+button.scopeDeleteButton:hover {

+	color: #F58B0F;

+}

+

+.searchOutlineContainer {

+	height:100%;

+	width:100%;

+	position:relative;

+	overflow-y: auto;

+}

+

+.searchOutlineContainer .filesystemSwitcher .dropdownMenu {

+	position: fixed;

+}

+

 .orionMarkdown {

 	padding: 8px;

 	color: gray;

diff --git a/bundles/org.eclipse.orion.client.ui/web/orion/explorers/explorer.js b/bundles/org.eclipse.orion.client.ui/web/orion/explorers/explorer.js
index 8f8bdf6..4b51d63 100644
--- a/bundles/org.eclipse.orion.client.ui/web/orion/explorers/explorer.js
+++ b/bundles/org.eclipse.orion.client.ui/web/orion/explorers/explorer.js
@@ -799,6 +799,11 @@
 	SelectionRenderer.prototype.renderRow = function(item, tableRow) {
 		tableRow.verticalAlign = "baseline"; //$NON-NLS-0$
 		tableRow.classList.add("treeTableRow"); //$NON-NLS-0$
+		
+		if (this.explorer.selectionPolicy !== "cursorOnly") {
+			tableRow.classList.add("selectableNavRow"); //$NON-NLS-0$
+		}
+		
 		var navDict = this.explorer.getNavDict();
 		if(navDict){
 			navDict.addRow(item, tableRow);
diff --git a/bundles/org.eclipse.orion.client.ui/web/orion/globalCommands.js b/bundles/org.eclipse.orion.client.ui/web/orion/globalCommands.js
index ebac156..d805e6f 100644
--- a/bundles/org.eclipse.orion.client.ui/web/orion/globalCommands.js
+++ b/bundles/org.eclipse.orion.client.ui/web/orion/globalCommands.js
@@ -445,7 +445,7 @@
 			if (options.searchService) {
 				options.searchService.setLocationByMetaData(options.target);
 			}
-			if (options.fileService && !options.breadcrumbTarget) {
+			if (options.fileService && !options.breadcrumbTarget && !options.staticBreadcrumb) {
 				fileSystemRootName = breadcrumbRootName ? breadcrumbRootName + " " : ""; //$NON-NLS-1$ //$NON-NLS-0$
 				fileSystemRootName = fileSystemRootName + options.fileService.fileServiceName(options.target.Location);
 				breadcrumbRootName = null;
@@ -475,18 +475,25 @@
 		var locationNode = options.breadCrumbContainer ? lib.node(options.breadCrumbContainer) : lib.node("location"); //$NON-NLS-0$
 		if (locationNode) {
 			lib.empty(locationNode);
-			var fileClient = serviceRegistry && new mFileClient.FileClient(serviceRegistry);
-			var resource = options.breadcrumbTarget || options.target;
-			var workspaceRootURL = (fileClient && resource && resource.Location) ? fileClient.fileServiceRootURL(resource.Location) : null;
-			new mBreadcrumbs.BreadCrumbs({
-				container: locationNode,
-				resource: resource,
-				rootSegmentName: breadcrumbRootName,
-				workspaceRootSegmentName: fileSystemRootName,
-				workspaceRootURL: workspaceRootURL,
-				makeFinalHref: options.makeBreadcrumFinalLink,
-				makeHref: options.makeBreadcrumbLink
-			});
+			if (options.staticBreadcrumb) {
+				new mBreadcrumbs.BreadCrumbs({
+					container: locationNode,
+					rootSegmentName: breadcrumbRootName
+				});	
+			} else {
+				var fileClient = serviceRegistry && new mFileClient.FileClient(serviceRegistry);
+				var resource = options.breadcrumbTarget || options.target;
+				var workspaceRootURL = (fileClient && resource && resource.Location) ? fileClient.fileServiceRootURL(resource.Location) : null;
+				new mBreadcrumbs.BreadCrumbs({
+					container: locationNode,
+					resource: resource,
+					rootSegmentName: breadcrumbRootName,
+					workspaceRootSegmentName: fileSystemRootName,
+					workspaceRootURL: workspaceRootURL,
+					makeFinalHref: options.makeBreadcrumFinalLink,
+					makeHref: options.makeBreadcrumbLink
+				});	
+			}
 		}
 	}
 
diff --git a/bundles/org.eclipse.orion.client.ui/web/orion/globalSearch/advSearchOptContainer.js b/bundles/org.eclipse.orion.client.ui/web/orion/globalSearch/advSearchOptContainer.js
index 08b0dd6..667c796 100644
--- a/bundles/org.eclipse.orion.client.ui/web/orion/globalSearch/advSearchOptContainer.js
+++ b/bundles/org.eclipse.orion.client.ui/web/orion/globalSearch/advSearchOptContainer.js
@@ -26,8 +26,13 @@
 	'orion/objects',
 	'orion/EventTarget',
 	'orion/widgets/filesystem/filesystemSwitcher',
-	'text!orion/globalSearch/searchBuilder.html'], 
-		function(messages, require, mFileClient, mSearchUtils, mContentTypes, i18nUtil, lib, mInputCompletion, Deferred, mCommands, mSection, objects, EventTarget, mFilesystemSwitcher, optionTemplate){
+	'text!orion/globalSearch/searchBuilder.html',
+	'orion/explorers/explorer-table',
+	'orion/explorers/explorerNavHandler',
+	'orion/explorers/navigatorRenderer',
+	'orion/PageUtil',
+	'orion/selection'
+], function(messages, require, mFileClient, mSearchUtils, mContentTypes, i18nUtil, lib, mInputCompletion, Deferred, mCommands, mSection, objects, EventTarget, mFilesystemSwitcher, optionTemplate, mExplorerTable, mExplorerNavHandler, mNavigatorRenderer, mPageUtil, mSelection){
 
 	/**
 	 * @name orion.search.AdvSearchOptRenderer
@@ -43,14 +48,21 @@
 		this._parentDiv = parentDiv;
 		this._searcher = searcher;
 		this._serviceRegistry = serviceRegistry;
+		this._contentTypeService = this._serviceRegistry.getService("orion.core.contentTypeRegistry"); //$NON-NLS-0$
+		if(!this._contentTypeService){
+			this._contentTypeService = new mContentTypes.ContentTypeRegistry(this._serviceRegistry);
+		}
+		this.contentTypesCache = this._contentTypeService.getContentTypes();
 		this._commandService = commandService;
         this.fileClient = new mFileClient.FileClient(this._serviceRegistry);
         this.fsToolbar = null;
-        this.fileSysChangeListener = function(evt) {
-			var href = mSearchUtils.generateSearchHref({resource : evt.newInput});
+        this.fileSysChangeListener = (function(evt) {
+        	var rootURL = evt.newInput;
+			var href = mSearchUtils.generateSearchHref({resource : rootURL});
 			window.location = href;
-        };
-		this.addEventListener("filesystemChanged", this.fileSysChangeListener);
+			this._rootURL = rootURL;
+        }).bind(this);
+		this.addEventListener("filesystemChanged", this.fileSysChangeListener); //$NON-NLS-0$
 		if (!this.fsToolbar) {
 			var fsToolbar = this.fsToolbar = document.createElement("div"); //$NON-NLS-0$
 			fsToolbar.classList.add("fsToolbarLayout"); //$NON-NLS-0$
@@ -72,13 +84,21 @@
 		destroy: function() {
 			this.removeEventListener("filesystemChanged", this.fileSysChangeListener); //$NON-NLS-0$
 			this.fileSysChangeListener = null;
+			if (this._searchScopeExplorer) {
+				this._searchScopeExplorer.destroy();
+				this._searchScopeExplorer = null;
+			}
 		},
 		
-		getOptions: function(includeLocation){
-			var resource;
-			if(includeLocation) {
-				resource = this._searchParams ? this._searchParams.resource : this._searcher.getSearchRootLocation();
-			}
+		getOptions: function(){
+			var resource = ""; //$NON-NLS-0$
+			this._searchLocations.forEach(function(searchLocation){
+				if (resource) {
+					resource = resource.concat(","); //$NON-NLS-0$
+				}
+				resource = resource.concat(searchLocation);
+			}, this);
+
 			return {keyword: this._searchBox.value,
 					sort: this._sortBy.options[this._sortBy.selectedIndex].value,
 					rows: 40,
@@ -90,12 +110,26 @@
 			        resource: resource
 			};
 		},
-	
+		
 		loadSearchParams: function(searchParams){
 			this._searchParams = searchParams;
 			this._locationName = null;
-			this.dispatchEvent({ type: "rootChanged", root: this.fileClient.fileServiceRootURL(this._searchParams.resource) }); //$NON-NLS-0$
-	        if (this._searchParams.resource.length > 0) {
+			//TODO handle resource parameter containing multiple resources once multiple file/folder search is enabled (see getOptions())
+			this._rootURL = this.fileClient.fileServiceRootURL(this._searchParams.resource);
+			
+			var loadRootOnly = function() {
+				this._searchLocations = [this._rootURL];
+	        	if (this._searchScopeExplorer) {
+	        		this._searchScopeExplorer.loadRoot(this._rootURL);
+	        	}
+	            this._locationName = messages["root"]; //$NON-NLS-0$
+	            if(this._searchNameBox){
+					this._searchNameBox.value = this._getDefaultSaveName();
+				}
+			}.bind(this);
+			
+			this.dispatchEvent({type: "rootChanged", root: this._rootURL}); //$NON-NLS-0$
+	        if ((this._searchParams.resource.length > 0) && (this._rootURL !== this._searchParams.resource)) {
 	            this._serviceRegistry.getService("orion.page.progress").progress(this.fileClient.read(this._searchParams.resource, true), "Getting file metadata " + this._searchParams.resource).then( //$NON-NLS-1$ //$NON-NLS-0$
 	            function(meta) {
 	                var parentName = meta.Parents ? mSearchUtils.fullPathNameByMeta(meta.Parents) : "";
@@ -103,21 +137,22 @@
 		            if(this._searchNameBox){
 						this._searchNameBox.value = this._getDefaultSaveName();
 					}
-	            }.bind(this),
-	
-	            function(error) {
-	                this._locationName = "root"; //$NON-NLS-0$
-		            if(this._searchNameBox){
-						this._searchNameBox.value = this._getDefaultSaveName();
+					
+					if (this._searchScopeSection) {
+						this._searchLocations = [this._searchParams.resource];
+						
+						if (this._searchScopeExplorer) {
+							this._searchScopeExplorer.loadRoot(this._rootURL).then(function(){
+								this._searchScopeExplorer.reveal(meta);
+							}.bind(this));	
+						}
 					}
-	            }.bind(this));
+	            }.bind(this),
+	            loadRootOnly);
 	        } else {
-	            this._locationName = "root"; //$NON-NLS-0$
-	            if(this._searchNameBox){
-					this._searchNameBox.value = this._getDefaultSaveName();
-				}
+	        	loadRootOnly();
 	        }
-	        //this._searchHelper = mSearchUtils.generateSearchHelper(searchParams);
+
 			this._loadSearchParams();
 		},
 	
@@ -145,17 +180,10 @@
 		},
 	
 		render: function(){
-			var contentTypeService = this._serviceRegistry.getService("orion.core.contentTypeRegistry"); //$NON-NLS-0$
-			if(!contentTypeService){
-				contentTypeService = new mContentTypes.ContentTypeRegistry(this._serviceRegistry);
-				this.contentTypesCache = contentTypeService.getContentTypes();
+			this._contentTypeService.getContentTypes().then(function(ct) {
+				this.contentTypesCache = ct;
 				this._render();
-			} else {
-				contentTypeService.getContentTypes().then(function(ct) {
-					this.contentTypesCache = ct;
-					this._render();
-				}.bind(this));
-			}
+			}.bind(this));
 		},
 	
 		_render: function(){
@@ -184,7 +212,7 @@
 		
 	    _saveSearch: function() {
 			if(this._searchBox.value && this._searchNameBox.value){
-				var searchParams = this.getOptions(true);
+				var searchParams = this.getOptions();
 			    var query = mSearchUtils.generateSearchHref(searchParams).split("#")[1]; //$NON-NLS-0$
 				this._serviceRegistry.getService("orion.core.savedSearches").addSearch(this._searchNameBox.value, query); //$NON-NLS-0$
 			}
@@ -193,7 +221,7 @@
 	    _getDefaultSaveName: function() {
 	        if (this._searchBox && this._searchBox.value) {
 	            var qName = "\'" + this._searchBox.value + "\' in "; //$NON-NLS-1$ //$NON-NLS-0$
-	            var locName = "root"; //$NON-NLS-0$
+	            var locName = messages["root"]; //$NON-NLS-0$
 	            if(this._locationName){
 					locName = this._locationName;
 	            }
@@ -402,6 +430,8 @@
 			domWrapperList = [];
 	        this._commandService.renderCommands("advSaveSearchCmd", "advSaveSearchCmd", this, this, "button", null, domWrapperList); //$NON-NLS-2$ //$NON-NLS-1$ //$NON-NLS-0$
 	        domWrapperList[0].domNode.classList.add("search-button"); //$NON-NLS-0$
+	        
+	        this._initSearchScope();
 		},
 		
 		_initHTMLLabels: function(){
@@ -413,6 +443,154 @@
 			document.getElementById("advSearchCaseSensitiveLabel").appendChild(document.createTextNode(messages["Case sensitive"])); //$NON-NLS-0$ //$NON-NLS-0$
 			document.getElementById("advSearchRegExLabel").appendChild(document.createTextNode(messages["Regular expression"])); //$NON-NLS-0$ //$NON-NLS-0$
 			document.getElementById("advSortByLabel").appendChild(document.createTextNode(messages["Sort by"])); //$NON-NLS-0$ //$NON-NLS-0$
+		},
+		
+		_initSearchScope: function() {
+			this._searchLocations = [];
+			var resource = mPageUtil.matchResourceParameters().resource;
+			this._rootURL = this.fileClient.fileServiceRootURL(resource);
+			
+			if (resource) {
+				this._searchLocations.push(resource);
+			} else {
+				this._searchLocations.push(this._rootURL);
+			}
+			
+			this._searchScopeDiv = lib.$("#searchScope", this._parentDiv); //$NON-NLS-0$
+			
+			this._searchScopeSection = new mSection.Section(this._searchScopeDiv, {
+				id: "searchScopeSection", //$NON-NLS-0$
+				title: messages["Scope"] + ": ", //$NON-NLS-0$
+				canHide: true,
+				hidden: true
+			});
+			this._setSearchScopeTitle();
+		
+			var scopeExplorerNode = document.createElement("div"); //$NON-NLS-0$
+			scopeExplorerNode.id = "searchScopeExplorerNode"; //$NON-NLS-0$
+			
+			var selectionModel = new mSelection.Selection(this._serviceRegistry, "searchScopeSelection"); //$NON-NLS-0$
+			this._selectionListener = (function(event) { //$NON-NLS-0$
+				this._searchLocations = [];
+				var selections = event.selections;
+
+				if (selections) {
+					selections.forEach(function(selection){
+						this._searchLocations.push(selection.Location);
+					}, this);
+				} else {
+					this._searchLocations.push(this._rootURL);
+				}
+				
+				if (this._searchLocations) {
+					this._searcher.setLocationbyURL(this._searchLocations.join(","));
+				}
+				
+				this._setSearchScopeTitle();		
+			}).bind(this);
+			selectionModel.addEventListener("selectionChanged", this._selectionListener); //$NON-NLS-0$
+			
+			this._searchScopeExplorer = new ScopeExplorer({
+				parentId: scopeExplorerNode,
+				serviceRegistry: this._serviceRegistry,
+				fileClient: this.fileClient,
+				commandRegistry: this._commandService,
+				contentTypeRegistry: this._contentTypeService,
+				selection: selectionModel,
+				advSearchOptRenderer: this
+			});
+			
+			this._searchScopeSection.embedExplorer(this._searchScopeExplorer, scopeExplorerNode);	
+//			this._searchScopeExplorer.setCommandsVisible(true, "singleSelection"); //$NON-NLS-0$ //TODO remove "singleSelection" once multiple selection is supported
+			this._searchScopeExplorer.setCommandsVisible(true);
+			this._searchScopeExplorer.loadRoot(this._rootURL);
+		},
+		
+		_setSearchScopeTitle: function() {
+			var titleElement = this._searchScopeSection.getTitleElement();
+			lib.empty(titleElement);
+			titleElement.appendChild(document.createTextNode(messages["Scope"] + ": ")); //$NON-NLS-0$
+			
+			this._searchLocations.forEach(function(searchLocation){
+				var decodedLocation = decodeURI(searchLocation);
+				var scopeString = decodedLocation;
+				var rootName = this.fileClient.fileServiceRootURL(scopeString);
+				if (rootName === searchLocation) {
+					//replace location string with "root"
+					scopeString = messages["root"]; //$NON-NLS-0$
+				} else {
+					//set scopeString to resource name
+					var segments = scopeString.split("/");
+					if (segments) {
+						scopeString = segments.pop();
+						if (!scopeString) {
+							// scopeString ended with '/', last element in array returned by 
+							// split() was empty, pop again to get the name
+							scopeString = segments.pop();
+						}
+					}
+				}
+												
+				var locationElement = document.createElement("span"); //$NON-NLS-0$
+				locationElement.classList.add("searchScopeElement"); //$NON-NLS-0$
+				locationElement.dataset.locationString = searchLocation;
+				locationElement.title = decodedLocation;
+				
+				if (scopeString !== messages["root"]) {
+					var locationText = document.createElement("span"); //$NON-NLS-0$
+					locationText.classList.add("searchScopePreButtonText"); //$NON-NLS-0$
+					locationText.appendChild(document.createTextNode(scopeString));
+					locationElement.appendChild(locationText);
+					this._insertDeleteButton(locationElement);
+				} else {
+					locationElement.appendChild(document.createTextNode(scopeString));
+				}
+				
+				titleElement.appendChild(locationElement);	
+			}, this);
+		},
+		
+		_insertDeleteButton: function(locationElement) {
+			var deleteButton = document.createElement("button"); //$NON-NLS-0$
+			deleteButton.classList.add("imageSprite"); //$NON-NLS-0$
+			deleteButton.classList.add("core-sprite-delete"); //$NON-NLS-0$
+			deleteButton.classList.add("scopeDeleteButton"); //$NON-NLS-0$
+			locationElement.appendChild(deleteButton);
+			
+			var deleteButtonHandler = (function(){
+				var locationString = locationElement.dataset.locationString;
+				var index = this._searchLocations.indexOf(locationString);
+				this._searchLocations.splice(index, 1); //remove element from _searchLocations array
+				if (0 === this._searchLocations.length) {
+					this._searchLocations.push(this.fileClient.fileServiceRootURL());
+				}
+				
+				//find model in explorer
+				var explorer = this._searchScopeExplorer;
+				var navHandler = explorer.getNavHandler();
+				if ("singleSelection" === navHandler.getSelectionPolicy()) {
+					explorer.selection.setSelections(null);
+					navHandler.refreshSelection(true, true);
+				} else {
+					var selections = explorer.selection.getSelections();
+					var model = null;
+					selections.some(function(selection){
+						if (selection.Location === locationString){
+							model = selection;
+							return true; //found it, stop iterating
+						}
+						return false;
+					});
+					
+					if (model) {
+						//deselect model
+						navHandler.setSelection(model, true, false);	
+					}
+				}
+				this._setSearchScopeTitle();
+			}).bind(this);
+			
+			deleteButton.addEventListener("click", deleteButtonHandler);
 		}
 	});
 	
@@ -432,6 +610,70 @@
 	
 	AdvSearchOptContainer.prototype.constructor = AdvSearchOptContainer;
 	
+	
+	var FileExplorer = mExplorerTable.FileExplorer;
+	var NavigatorRenderer = mNavigatorRenderer.NavigatorRenderer;
+	var ExplorerNavHandler = mExplorerNavHandler.ExplorerNavHandler;
+	
+	function ScopeExplorer(options) {
+		options.setFocus = false;   // do not steal focus on load
+		options.cachePrefix = null; // do not persist table state
+		options.excludeFiles = true; // do not show files
+		
+		options.rendererFactory = function(explorer) {
+			var renderer = new NavigatorRenderer({
+				checkbox: false,
+				showFolderImage: true,
+				treeTableClass: "sectionTreeTable" //$NON-NLS-0$
+			}, explorer, options.commandRegistry, options.contentTypeRegistry);
+			renderer.oneColumn = true; //don't show modification date and size
+			renderer.showFolderLinks = false; //show folder names but don't render links for them
+			return renderer;
+		};
+		
+		options.navHandlerFactory = {createNavHandler: function(explorer, explorerNavDict, options) {
+			options.setFocus = false;
+			options.gridClickSelectionPolicy = "active"; //clicking on children causes row to be selected
+			return new ScopeNavHandler(explorer, explorerNavDict, options);
+		}};
+		
+		FileExplorer.apply(this, arguments);
+		this.treeRoot = {};
+	}
+	ScopeExplorer.prototype = Object.create(FileExplorer.prototype);
+	objects.mixin(ScopeExplorer.prototype, /** @lends orion.ScopeExplorer.prototype */ {
+		loadRoot: function(root) {
+			var path = root || this.fileClient.fileServiceRootURL();
+			return this.loadResourceList(path);
+		}
+	});
+	ScopeExplorer.prototype.constructor = ScopeExplorer;
+	
+	function ScopeNavHandler(explorer, explorerNavDict, options) {
+		ExplorerNavHandler.apply(this, arguments);
+	}
+	ScopeNavHandler.prototype = Object.create(ExplorerNavHandler.prototype);
+	objects.mixin(ScopeNavHandler.prototype, /** @lends orion.ScopeNavHandler.prototype */ {
+		//overrides ExplorerNavHandler.onClick
+		onClick: function(model, mouseEvt) {
+			ExplorerNavHandler.prototype.onClick.call(this, model, mouseEvt);
+			
+			var twistieSpan = lib.node(this.explorer.renderer.expandCollapseImageId(this.model.getId(model)));
+			if(mouseEvt.target !== twistieSpan){ //don't do anything if twistie was clicked
+				//toggle the model's expand/collapse state
+				if (this.isExpandable(model)){
+					if (!this.isExpanded(model)){
+						this.explorer.myTree.expand(model);
+					} else {
+						this.explorer.myTree.collapse(model);
+					}
+				}
+			}
+		}
+	});
+	ScopeNavHandler.prototype.constructor = ScopeNavHandler;
+	
+	
 	//return module exports
 	return {
 		AdvSearchOptContainer: AdvSearchOptContainer,
diff --git a/bundles/org.eclipse.orion.client.ui/web/orion/globalSearch/searchBuilder.html b/bundles/org.eclipse.orion.client.ui/web/orion/globalSearch/searchBuilder.html
index 5c27592..ab85937 100644
--- a/bundles/org.eclipse.orion.client.ui/web/orion/globalSearch/searchBuilder.html
+++ b/bundles/org.eclipse.orion.client.ui/web/orion/globalSearch/searchBuilder.html
@@ -1,3 +1,4 @@
+	<div id="searchScope" class="searchScope"></div>

 	<div class="searchOptionBlock">

 		<table>

 			<tr>

diff --git a/bundles/org.eclipse.orion.client.ui/web/orion/search/nls/root/messages.js b/bundles/org.eclipse.orion.client.ui/web/orion/search/nls/root/messages.js
index fd0155d..3bab262 100644
--- a/bundles/org.eclipse.orion.client.ui/web/orion/search/nls/root/messages.js
+++ b/bundles/org.eclipse.orion.client.ui/web/orion/search/nls/root/messages.js
@@ -98,5 +98,7 @@
 	"Regular expression is off. You can click here or use options to turn it on for replacement - e.g. when the option is on you can replace <td([\\s\\S]*?)</td> with <span$1</span>." : 
 	"Regular expression is off. You can click here or use options to turn it on for replacement - e.g. when the option is on you can replace <td([\\s\\S]*?)</td> with <span$1</span>.",
 	"Regular expression is on. You can click here or use options to turn it off - e.g. when the option is on you can replace <td([\\s\\S]*?)</td> with <span$1</span>." : 
-	"Regular expression is on. You can click here or use options to turn it off - e.g. when the option is on you can replace <td([\\s\\S]*?)</td> with <span$1</span>."
+	"Regular expression is on. You can click here or use options to turn it off - e.g. when the option is on you can replace <td([\\s\\S]*?)</td> with <span$1</span>.",
+	"root": "root",
+	"Scope": "Scope"
 });
diff --git a/bundles/org.eclipse.orion.client.ui/web/orion/searchOutliner.js b/bundles/org.eclipse.orion.client.ui/web/orion/searchOutliner.js
index 384b71d..850a7e1 100644
--- a/bundles/org.eclipse.orion.client.ui/web/orion/searchOutliner.js
+++ b/bundles/org.eclipse.orion.client.ui/web/orion/searchOutliner.js
@@ -25,8 +25,7 @@
 	'orion/explorers/explorer',
 	'orion/EventTarget',
 	'orion/globalSearch/advSearchOptContainer',
-	'orion/webui/splitter'
-],  function(messages, require, lib, i18nUtil, mSection, mCommands, mCommandRegistry, mKeyBinding, mSelection, mExplorer, EventTarget, mAdvSearchOptContainer, splitter){
+],  function(messages, require, lib, i18nUtil, mSection, mCommands, mCommandRegistry, mKeyBinding, mSelection, mExplorer, EventTarget, mAdvSearchOptContainer){
 
 	/**
 	 * Instantiates the saved search service. This service is used internally by the
@@ -268,7 +267,8 @@
 					content: '<div id="searchContent"></div>', //$NON-NLS-0$
 					useAuxStyle: true,
 					preferenceService: serviceRegistry.getService("orion.core.preference"), //$NON-NLS-0$
-					slideout: true
+					slideout: true,
+					canHide: true
 				});
 				this.searchSelection = new mSelection.Selection(serviceRegistry, "orion.searches.selection"); //$NON-NLS-0$
 				// add commands to the search section heading
@@ -330,16 +330,6 @@
 		this._registry = options.serviceRegistry;
 		this.commandService = options.commandService;
 		this.advSearchOptContainer = new mAdvSearchOptContainer.AdvSearchOptContainer(this._parent, options.searcher, this._registry, this.commandService);
-		var outlinerParent = lib.node("outlineContainer"); //$NON-NLS-0$
-		var top = lib.$("#outlineTop", outlinerParent); //$NON-NLS-0$
-		var bottom = lib.$("#outlineBottom", outlinerParent); //$NON-NLS-0$
-		var splitNode = lib.$(".outlinerSplitLayout", outlinerParent); //$NON-NLS-0$
-		splitNode.id = "searchOutlineSplitter"; //$NON-NLS-0$
-		//The vertical splitter has to adjust the top and bottm pane when the outliner is refreshed by the click on browser's refresh.
-		//Otherwise there the bottom pane is a little offset.
-		window.setTimeout(function() { 
-			this._splitter = new splitter.Splitter({node: splitNode, sidePanel: top, mainPanel: bottom, toggle: true, vertical: true, closeReversely: true});
-		}, 100);
 	}
 	SearchBuilder.prototype = /** @lends orion.navoutliner.SearchOutliner.prototype */ {
 		loadSearchParams: function(searchParams) {
diff --git a/bundles/org.eclipse.orion.client.ui/web/orion/section.js b/bundles/org.eclipse.orion.client.ui/web/orion/section.js
index ab302e9..2abe058 100644
--- a/bundles/org.eclipse.orion.client.ui/web/orion/section.js
+++ b/bundles/org.eclipse.orion.client.ui/web/orion/section.js
@@ -274,9 +274,11 @@
 				getCommandsVisible: function() {
 					return this.section.actionsNode.style.visibility!=="hidden";
 				},
-				setCommandsVisible: function(visible) {
+				setCommandsVisible: function(visible, selectionPolicy) {
 					this.section.actionsNode.style.visibility = visible ? "" : "hidden";
-					var selectionPolicy = visible ? null : "cursorOnly"; //$NON-NLS-0$
+					if (undefined === selectionPolicy){
+						selectionPolicy = visible ? null : "cursorOnly"; //$NON-NLS-0$	
+					} 
 					this.renderer.selectionPolicy = selectionPolicy;
 					var navHandler = this.getNavHandler();
 					if (navHandler) {
diff --git a/bundles/org.eclipse.orion.client.ui/web/search/search.html b/bundles/org.eclipse.orion.client.ui/web/search/search.html
index c7e8cbe..f2b98a1 100644
--- a/bundles/org.eclipse.orion.client.ui/web/search/search.html
+++ b/bundles/org.eclipse.orion.client.ui/web/search/search.html
@@ -28,15 +28,9 @@
 		<div id="pageContent" class="content-fixedHeight">
 			<div id="sideMenuToggle" class="sideMenuToggle"></div>
 			<div class="auxpane sidePanelLayout hasSplit">
-				<div id="outlineContainer" style="height:100%;width:100%;position:relative;">
-					<div id="outlineTop" class="topPanelLayout">
-						<div id="searchBuilder" class="sidePanelMargins">
-						</div>
-					</div>
-					<div class="outlinerSplitLayout splitVerticalLayout"></div>
-					<div id="outlineBottom" class="bottomPanelLayout">
-						<div class="sidePanelMargins" id="searchProgress"></div>
-					</div>
+				<div id="outlineContainer" class="searchOutlineContainer">
+					<div id="searchBuilder" class="sidePanelMargins"></div>
+					<div class="sidePanelMargins" id="searchProgress"></div>
 				</div>
 			</div>
 			<div class="split splitLayout" style="left:33%;"></div>
diff --git a/bundles/org.eclipse.orion.client.ui/web/search/search.js b/bundles/org.eclipse.orion.client.ui/web/search/search.js
index 5ae7fb5..376c6f9 100644
--- a/bundles/org.eclipse.orion.client.ui/web/search/search.js
+++ b/bundles/org.eclipse.orion.client.ui/web/search/search.js
@@ -17,13 +17,7 @@
         'orion/contentTypes', 'orion/searchUtils', 'orion/PageUtil','orion/webui/littlelib'], 
 		function(messages, require, mBrowserCompatibility, mBootstrap, mStatus, mProgress, mDialogs, mCommandRegistry, mSearchOutliner, 
 				mSearchClient, mFileClient, mOperationsClient, mSearchResults, mGlobalCommands, mContentTypes, mSearchUtils, PageUtil, lib) {
-	function makeHref(fileClient, seg, location, searchParams, searcher){
-		var searchLocation = (!location || location === "" || location === "root") ? searcher.getSearchRootLocation() : location; //$NON-NLS-0$
-		var newParams = mSearchUtils.copySearchParams(searchParams);
-		newParams.resource = searchLocation;
-		seg.href = mSearchUtils.generateSearchHref(newParams);
-	}
-
+	
 	function setPageInfo(serviceRegistry, fileClient, commandService, searcher, searchResultsGenerator, searchBuilder, searchParams, progress){
 		var searchLoc = searchParams.resource;
 		var title = searchParams.replace ? messages["Replace All Matches"] : messages["Search Results"];
@@ -32,19 +26,17 @@
 				searcher.setRootLocationbyURL(searchLoc);
 				searcher.setLocationbyURL(searchLoc);
 				mGlobalCommands.setPageTarget({task: "Search", title: title, serviceRegistry: serviceRegistry, //$NON-NLS-0$
-					commandService: commandService, searchService: searcher, fileService: fileClient, breadcrumbRootName: fileClient.fileServiceName(searchLoc),
-					makeBreadcrumbLink: function(seg,location){makeHref(fileClient, seg, location, searchParams, searcher);}});
-					searcher.setChildrenLocationbyURL(searchLoc);
-					searchBuilder.loadSearchParams(searchParams);
-					searchResultsGenerator.loadResults(searchParams);
+					commandService: commandService, searchService: searcher, fileService: fileClient, breadcrumbRootName: "Search", staticBreadcrumb: true}); //$NON-NLS-0$
+				searcher.setChildrenLocationbyURL(searchLoc);
+				searchBuilder.loadSearchParams(searchParams);
+				searchResultsGenerator.loadResults(searchParams);
 			} else {
 				(progress ? progress.progress(fileClient.read(searchLoc, true), "Loading file metadata " + searchLoc) : fileClient.read(searchLoc, true)).then( //$NON-NLS-0$
 					function(metadata) {
 						mGlobalCommands.setPageTarget({task: "Search", title: title, target: metadata, serviceRegistry: serviceRegistry,  //$NON-NLS-0$
-							fileService: fileClient, commandService: commandService, searchService: searcher, breadcrumbRootName: "Search", //$NON-NLS-0$
-							makeBreadcrumbLink: function(seg,location){makeHref(fileClient, seg, location, searchParams, searcher);}});
-							searchBuilder.loadSearchParams(searchParams);
-							searchResultsGenerator.loadResults(searchParams);
+							fileService: fileClient, commandService: commandService, searchService: searcher, staticBreadcrumb: true, breadcrumbRootName: "Search"}); //$NON-NLS-0$
+						searchBuilder.loadSearchParams(searchParams);
+						searchResultsGenerator.loadResults(searchParams);
 					}.bind(this),
 					function(error) {
 						window.console.error("Error loading file metadata: " + error.message); //$NON-NLS-0$
@@ -53,10 +45,9 @@
 			}
 		} else {
 			mGlobalCommands.setPageTarget({task: "Search", title: title, serviceRegistry: serviceRegistry,  //$NON-NLS-0$
-				commandService: commandService, searchService: searcher, fileService: fileClient, breadcrumbRootName: "Search", //$NON-NLS-0$
-				makeBreadcrumbLink: function(seg,location){makeHref(fileClient, seg, location, searchParams, searcher);}});
-				searchBuilder.loadSearchParams(searchParams);
-				searchResultsGenerator.loadResults(searchParams);
+				commandService: commandService, searchService: searcher, fileService: fileClient, staticBreadcrumb: true, breadcrumbRootName: "Search"}); //$NON-NLS-0$
+			searchBuilder.loadSearchParams(searchParams);
+			searchResultsGenerator.loadResults(searchParams);
 		}
 	}
 	mBootstrap.startup().then(function(core) {