diff --git a/js/ui/search.js b/js/ui/search.js index 86173c67e..3b35e4919 100644 --- a/js/ui/search.js +++ b/js/ui/search.js @@ -102,6 +102,11 @@ const SearchResultDisplay = new Lang.Class({ * Subclass this object to add a new result type * to the search system, then call registerProvider() * in SearchSystem with an instance. + * By default, search is synchronous and uses the + * getInitialResultSet()/getSubsearchResultSet() methods. + * For asynchronous search, set the async property to true + * and implement getInitialResultSetAsync()/getSubsearchResultSetAsync() + * instead. */ const SearchProvider = new Lang.Class({ Name: 'SearchProvider', @@ -109,42 +114,7 @@ const SearchProvider = new Lang.Class({ _init: function(title) { this.title = title; this.searchSystem = null; - this.searchAsync = false; - }, - - _asyncCancelled: function() { - }, - - startAsync: function() { - this.searchAsync = true; - }, - - tryCancelAsync: function() { - if (!this.searchAsync) - return; - this._asyncCancelled(); - this.searchAsync = false; - }, - - /** - * addItems: - * @items: an array of result identifier strings representing - * items which match the last given search terms. - * - * This should be used for something that requires a bit more - * logic; it's designed to be an asyncronous way to add a result - * to the current search. - */ - addItems: function(items) { - if (!this.searchSystem) - throw new Error('Search provider not registered'); - - if (!items.length) - return; - - this.tryCancelAsync(); - - this.searchSystem.addProviderItems(this, items); + this.async = false; }, /** @@ -172,6 +142,18 @@ const SearchProvider = new Lang.Class({ throw new Error('Not implemented'); }, + /** + * getInitialResultSetAsync: + * @terms: Array of search terms, treated as logical AND + * + * Like getInitialResultSet(), but the method should return immediately + * without a return value - use SearchSystem.pushResults() when the + * corresponding results are ready. + */ + getInitialResultSetAsync: function(terms) { + throw new Error('Not implemented'); + }, + /** * getSubsearchResultSet: * @previousResults: Array of item identifiers @@ -189,6 +171,19 @@ const SearchProvider = new Lang.Class({ throw new Error('Not implemented'); }, + /** + * getSubsearchResultSetAsync: + * @previousResults: Array of item identifiers + * @newTerms: Updated search terms + * + * Like getSubsearchResultSet(), but the method should return immediately + * without a return value - use SearchSystem.pushResults() when the + * corresponding results are ready. + */ + getSubsearchResultSetAsync: function(previousResults, newTerms) { + throw new Error('Not implemented'); + }, + /** * getResultMetas: * @ids: Result identifier strings @@ -201,6 +196,19 @@ const SearchProvider = new Lang.Class({ throw new Error('Not implemented'); }, + /** + * getResultMetasAsync: + * @ids: Result identifier strings + * @callback: callback to pass the results to when ready + * + * Like getResultMetas(), but the method should return immediately + * without a return value - pass the results to the provided @callback + * when ready. + */ + getResultMetasAsync: function(ids, callback) { + throw new Error('Not implemented'); + }, + /** * createResultContainer: * @@ -369,8 +377,13 @@ const SearchSystem = new Lang.Class({ this._previousResults = []; }, - addProviderItems: function(provider, items) { - this.emit('search-updated', provider, items); + pushResults: function(provider, results) { + let i = this._providers.indexOf(provider); + if (i == -1) + return; + + this._previousResults[i] = [provider, results]; + this.emit('search-updated', this._previousResults[i]); }, updateSearch: function(searchString) { @@ -400,10 +413,14 @@ const SearchSystem = new Lang.Class({ if (isSubSearch) { for (let i = 0; i < this._providers.length; i++) { let [provider, previousResults] = this._previousResults[i]; - provider.tryCancelAsync(); try { - let providerResults = provider.getSubsearchResultSet(previousResults, terms); - results.push([provider, providerResults]); + if (provider.async) { + provider.getSubsearchResultSetAsync(previousResults, terms); + results.push([provider, []]); + } else { + let providerResults = provider.getSubsearchResultSet(previousResults, terms); + results.push([provider, providerResults]); + } } catch (error) { global.log ('A ' + error.name + ' has occured in ' + provider.title + ': ' + error.message); } @@ -411,10 +428,14 @@ const SearchSystem = new Lang.Class({ } else { for (let i = 0; i < this._providers.length; i++) { let provider = this._providers[i]; - provider.tryCancelAsync(); try { - let providerResults = provider.getInitialResultSet(terms); - results.push([provider, providerResults]); + if (provider.async) { + provider.getInitialResultSetAsync(terms); + results.push([provider, []]); + } else { + let providerResults = provider.getInitialResultSet(terms); + results.push([provider, providerResults]); + } } catch (error) { global.log ('A ' + error.name + ' has occured in ' + provider.title + ': ' + error.message); } diff --git a/js/ui/searchDisplay.js b/js/ui/searchDisplay.js index 8caf43303..984a55a97 100644 --- a/js/ui/searchDisplay.js +++ b/js/ui/searchDisplay.js @@ -119,17 +119,24 @@ const GridSearchResults = new Lang.Class({ if (results.length == 0) return; - let metas = provider.getResultMetas(results); - this.renderResults(metas); + if (provider.async) { + provider.getResultMetasAsync(results, + Lang.bind(this, this.renderResults)); + } else { + let metas = provider.getResultMetas(results); + this.renderResults(metas); + } })); })); this._notDisplayedResult = []; this._terms = []; + this._pendingClear = false; }, getResultsForDisplay: function() { + let alreadyVisible = this._pendingClear ? 0 : this._grid.visibleItemsCount(); let canDisplay = this._grid.childrenInRow(this._width) * MAX_SEARCH_RESULTS_ROWS - - this._grid.visibleItemsCount(); + - alreadyVisible; let numResults = Math.min(this._notDisplayedResult.length, canDisplay); @@ -144,6 +151,7 @@ const GridSearchResults = new Lang.Class({ // copy the lists this._notDisplayedResult = results.slice(0); this._terms = terms.slice(0); + this._pendingClear = true; }, renderResults: function(metas) { @@ -154,10 +162,9 @@ const GridSearchResults = new Lang.Class({ }, clear: function () { - this._terms = []; - this._notDisplayedResult = []; this._grid.removeAll(); this.selectionIndex = -1; + this._pendingClear = false; }, selectIndex: function (index) { @@ -297,7 +304,8 @@ const SearchResults = new Lang.Class({ this._providerMeta.push({ provider: provider, actor: providerBox, - resultDisplay: resultDisplay }); + resultDisplay: resultDisplay, + hasPendingResults: false }); this._content.add(providerBox); }, @@ -322,8 +330,8 @@ const SearchResults = new Lang.Class({ } }, - _clearDisplayForProvider: function(index) { - let meta = this._providerMeta[index]; + _clearDisplayForProvider: function(provider) { + let meta = this._metaForProvider(provider); meta.resultDisplay.clear(); meta.actor.hide(); }, @@ -350,15 +358,58 @@ const SearchResults = new Lang.Class({ return this._providerMeta[this._providers.indexOf(provider)]; }, - _updateCurrentResults: function(searchSystem, provider, results) { + _maybeSetInitialSelection: function() { + if (this._selectedOpenSearchButton > -1 || this._selectedProvider > -1) + return; + + for (let i = 0; i < this._providerMeta.length; i++) { + let meta = this._providerMeta[i]; + if (meta.hasPendingResults) + return; + + if (meta.actor.visible) + break; // select this one! + } + + this.selectDown(false); + this._initialSelectionSet = true; + }, + + _updateCurrentResults: function(searchSystem, results) { let terms = searchSystem.getTerms(); + let [provider, providerResults] = results; let meta = this._metaForProvider(provider); - meta.resultDisplay.clear(); - meta.actor.show(); - meta.resultDisplay.setResults(providerResults, terms); - let displayResults = meta.resultDisplay.getResultsForDisplay(); - meta.resultDisplay.renderResults(provider.getResultMetas(displayResults)); - return true; + meta.hasPendingResults = false; + this._updateProviderResults(provider, providerResults, terms); + }, + + _updateProviderResults: function(provider, providerResults, terms) { + let meta = this._metaForProvider(provider); + if (providerResults.length == 0) { + this._clearDisplayForProvider(provider); + meta.resultDisplay.setResults([], []); + } else { + this._providerMetaResults[provider.title] = providerResults; + meta.resultDisplay.setResults(providerResults, terms); + let results = meta.resultDisplay.getResultsForDisplay(); + + if (provider.async) { + provider.getResultMetasAsync(results, Lang.bind(this, + function(metas) { + this._clearDisplayForProvider(provider); + meta.actor.show(); + this._content.hide(); + meta.resultDisplay.renderResults(metas); + this._maybeSetInitialSelection(); + this._content.show(); + })); + } else { + let metas = provider.getResultMetas(results); + this._clearDisplayForProvider(provider); + meta.actor.show(); + meta.resultDisplay.renderResults(metas); + } + } }, _updateResults: function(searchSystem, results) { @@ -368,6 +419,7 @@ const SearchResults = new Lang.Class({ } else { this._selectedOpenSearchButton = -1; this._updateOpenSearchButtonState(); + this._selectedProvider = -1; this._statusText.hide(); } @@ -383,22 +435,13 @@ const SearchResults = new Lang.Class({ for (let i = 0; i < results.length; i++) { let [provider, providerResults] = results[i]; - if (providerResults.length == 0) { - this._clearDisplayForProvider(i); - } else { - this._providerMetaResults[provider.title] = providerResults; - this._clearDisplayForProvider(i); - let meta = this._metaForProvider(provider); - meta.actor.show(); - meta.resultDisplay.setResults(providerResults, terms); - let displayResults = meta.resultDisplay.getResultsForDisplay(); - meta.resultDisplay.renderResults(provider.getResultMetas(displayResults)); - } + let meta = this._metaForProvider(provider); + meta.hasPendingResults = provider.async; + if (!meta.hasPendingResults) + this._updateProviderResults(provider, providerResults, terms); } - if (this._selectedOpenSearchButton == -1) - this.selectDown(false); - + this._maybeSetInitialSelection(); this._content.show(); return true;