| // Copyright (C) 2012 The Android Open Source Project |
| // |
| // Licensed under the Apache License, Version 2.0 (the "License"); |
| // you may not use this file except in compliance with the License. |
| // You may obtain a copy of the License at |
| // |
| // http://www.apache.org/licenses/LICENSE-2.0 |
| // |
| // Unless required by applicable law or agreed to in writing, software |
| // distributed under the License is distributed on an "AS IS" BASIS, |
| // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. |
| // See the License for the specific language governing permissions and |
| // limitations under the License. |
| |
| package com.google.gerrit.client; |
| |
| import com.google.gerrit.client.ui.AccountGroupSuggestOracle; |
| import com.google.gerrit.client.ui.AccountSuggestOracle; |
| import com.google.gerrit.client.ui.ProjectNameSuggestOracle; |
| import com.google.gwt.user.client.ui.SuggestOracle; |
| import com.google.gwtexpui.safehtml.client.HighlightSuggestOracle; |
| import java.util.ArrayList; |
| import java.util.Arrays; |
| import java.util.List; |
| import java.util.TreeSet; |
| |
| public class SearchSuggestOracle extends HighlightSuggestOracle { |
| private static final List<ParamSuggester> paramSuggester = |
| Arrays.asList( |
| new ParamSuggester( |
| Arrays.asList("project:", "p:", "parentproject:"), new ProjectNameSuggestOracle()), |
| new ParamSuggester( |
| Arrays.asList( |
| "owner:", |
| "o:", |
| "reviewer:", |
| "r:", |
| "commentby:", |
| "reviewedby:", |
| "author:", |
| "committer:", |
| "from:", |
| "assignee:", |
| "cc:"), |
| new AccountSuggestOracle() { |
| @Override |
| public void onRequestSuggestions(Request request, Callback done) { |
| super.onRequestSuggestions( |
| request, |
| new Callback() { |
| @Override |
| public void onSuggestionsReady(final Request request, Response response) { |
| if ("self".startsWith(request.getQuery())) { |
| final ArrayList<SuggestOracle.Suggestion> r = |
| new ArrayList<>(response.getSuggestions().size() + 1); |
| r.add( |
| new SuggestOracle.Suggestion() { |
| @Override |
| public String getDisplayString() { |
| return getReplacementString(); |
| } |
| |
| @Override |
| public String getReplacementString() { |
| return "self"; |
| } |
| }); |
| r.addAll(response.getSuggestions()); |
| response.setSuggestions(r); |
| } |
| done.onSuggestionsReady(request, response); |
| } |
| }); |
| } |
| }), |
| new ParamSuggester( |
| Arrays.asList("ownerin:", "reviewerin:"), new AccountGroupSuggestOracle())); |
| |
| private static final TreeSet<String> suggestions = new TreeSet<>(); |
| |
| static { |
| suggestions.add("age:"); |
| suggestions.add("age:1week"); // Give an example age |
| |
| suggestions.add("change:"); |
| |
| suggestions.add("owner:"); |
| suggestions.add("owner:self"); |
| suggestions.add("ownerin:"); |
| suggestions.add("author:"); |
| suggestions.add("committer:"); |
| suggestions.add("assignee:"); |
| |
| suggestions.add("reviewer:"); |
| suggestions.add("reviewer:self"); |
| suggestions.add("reviewerin:"); |
| suggestions.add("reviewedby:"); |
| |
| suggestions.add("commit:"); |
| suggestions.add("comment:"); |
| suggestions.add("message:"); |
| suggestions.add("commentby:"); |
| suggestions.add("from:"); |
| suggestions.add("file:"); |
| suggestions.add("conflicts:"); |
| suggestions.add("project:"); |
| suggestions.add("projects:"); |
| suggestions.add("parentproject:"); |
| suggestions.add("branch:"); |
| suggestions.add("topic:"); |
| suggestions.add("intopic:"); |
| suggestions.add("ref:"); |
| suggestions.add("tr:"); |
| suggestions.add("bug:"); |
| suggestions.add("label:"); |
| suggestions.add("query:"); |
| suggestions.add("has:"); |
| suggestions.add("has:draft"); |
| suggestions.add("has:edit"); |
| suggestions.add("has:star"); |
| suggestions.add("has:stars"); |
| suggestions.add("has:unresolved"); |
| suggestions.add("star:"); |
| |
| suggestions.add("is:"); |
| suggestions.add("is:starred"); |
| suggestions.add("is:watched"); |
| suggestions.add("is:reviewed"); |
| suggestions.add("is:owner"); |
| suggestions.add("is:reviewer"); |
| suggestions.add("is:open"); |
| suggestions.add("is:pending"); |
| suggestions.add("is:private"); |
| suggestions.add("is:closed"); |
| suggestions.add("is:merged"); |
| suggestions.add("is:abandoned"); |
| suggestions.add("is:mergeable"); |
| suggestions.add("is:ignored"); |
| suggestions.add("is:wip"); |
| suggestions.add("is:assigned"); |
| suggestions.add("is:submittable"); |
| |
| suggestions.add("status:"); |
| suggestions.add("status:open"); |
| suggestions.add("status:pending"); |
| suggestions.add("status:reviewed"); |
| suggestions.add("status:closed"); |
| suggestions.add("status:merged"); |
| suggestions.add("status:abandoned"); |
| |
| suggestions.add("added:"); |
| suggestions.add("deleted:"); |
| suggestions.add("delta:"); |
| suggestions.add("size:"); |
| |
| suggestions.add("unresolved:"); |
| |
| suggestions.add("revertof:"); |
| |
| if (Gerrit.isNoteDbEnabled()) { |
| suggestions.add("cc:"); |
| suggestions.add("hashtag:"); |
| } |
| |
| suggestions.add("is:assigned"); |
| suggestions.add("is:unassigned"); |
| suggestions.add("assignee:"); |
| |
| suggestions.add("AND"); |
| suggestions.add("OR"); |
| suggestions.add("NOT"); |
| } |
| |
| @Override |
| public void requestDefaultSuggestions(Request request, Callback done) { |
| final ArrayList<SearchSuggestion> r = new ArrayList<>(); |
| // No text - show some default suggestions. |
| r.add(new SearchSuggestion("status:open", "status:open")); |
| r.add(new SearchSuggestion("age:1week", "age:1week")); |
| if (Gerrit.isSignedIn()) { |
| r.add(new SearchSuggestion("owner:self", "owner:self")); |
| } |
| done.onSuggestionsReady(request, new Response(r)); |
| } |
| |
| @Override |
| protected void onRequestSuggestions(Request request, Callback done) { |
| final String query = request.getQuery(); |
| |
| final String lastWord = getLastWord(query); |
| if (lastWord == null) { |
| // Starting a new word - don't show suggestions yet. |
| done.onSuggestionsReady(request, null); |
| return; |
| } |
| |
| for (ParamSuggester ps : paramSuggester) { |
| if (ps.applicable(lastWord)) { |
| ps.suggest(lastWord, request, done); |
| return; |
| } |
| } |
| |
| final ArrayList<SearchSuggestion> r = new ArrayList<>(); |
| for (String suggestion : suggestions.tailSet(lastWord)) { |
| if ((lastWord.length() < suggestion.length()) && suggestion.startsWith(lastWord)) { |
| if (suggestion.contains("self") && !Gerrit.isSignedIn()) { |
| continue; |
| } |
| r.add(new SearchSuggestion(suggestion, query + suggestion.substring(lastWord.length()))); |
| } |
| } |
| done.onSuggestionsReady(request, new Response(r)); |
| } |
| |
| private String getLastWord(String query) { |
| final int lastSpace = query.lastIndexOf(' '); |
| if (lastSpace == query.length() - 1) { |
| return null; |
| } |
| if (lastSpace == -1) { |
| return query; |
| } |
| return query.substring(lastSpace + 1); |
| } |
| |
| @Override |
| protected String getQueryPattern(String query) { |
| return super.getQueryPattern(getLastWord(query)); |
| } |
| |
| @Override |
| protected boolean isHTML() { |
| return true; |
| } |
| |
| private static class SearchSuggestion implements SuggestOracle.Suggestion { |
| private final String suggestion; |
| private final String fullQuery; |
| |
| SearchSuggestion(String suggestion, String fullQuery) { |
| this.suggestion = suggestion; |
| // Add a space to the query if it is a complete operation (e.g. |
| // "status:open") so the user can keep on typing. |
| this.fullQuery = fullQuery.endsWith(":") ? fullQuery : fullQuery + " "; |
| } |
| |
| @Override |
| public String getDisplayString() { |
| return suggestion; |
| } |
| |
| @Override |
| public String getReplacementString() { |
| return fullQuery; |
| } |
| } |
| |
| private static class ParamSuggester { |
| private final List<String> operators; |
| private final SuggestOracle parameterSuggestionOracle; |
| |
| ParamSuggester(List<String> operators, SuggestOracle parameterSuggestionOracle) { |
| this.operators = operators; |
| this.parameterSuggestionOracle = parameterSuggestionOracle; |
| } |
| |
| boolean applicable(String query) { |
| final String operator = getApplicableOperator(query, operators); |
| return operator != null && query.length() > operator.length(); |
| } |
| |
| private String getApplicableOperator(String lastWord, List<String> operators) { |
| for (String operator : operators) { |
| if (lastWord.startsWith(operator)) { |
| return operator; |
| } |
| } |
| return null; |
| } |
| |
| void suggest(String lastWord, Request request, Callback done) { |
| final String operator = getApplicableOperator(lastWord, operators); |
| parameterSuggestionOracle.requestSuggestions( |
| new Request(lastWord.substring(operator.length()), request.getLimit()), |
| new Callback() { |
| @Override |
| public void onSuggestionsReady(Request req, Response response) { |
| final String query = request.getQuery(); |
| final List<SearchSuggestOracle.Suggestion> r = |
| new ArrayList<>(response.getSuggestions().size()); |
| for (SearchSuggestOracle.Suggestion s : response.getSuggestions()) { |
| r.add( |
| new SearchSuggestion( |
| s.getDisplayString(), |
| query.substring(0, query.length() - lastWord.length()) |
| + operator |
| + quoteIfNeeded(s.getReplacementString()))); |
| } |
| done.onSuggestionsReady(request, new Response(r)); |
| } |
| |
| private String quoteIfNeeded(String s) { |
| if (!s.matches("^\\S*$")) { |
| return "\"" + s + "\""; |
| } |
| return s; |
| } |
| }); |
| } |
| } |
| } |