// 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:", "parentproject:"), new ProjectNameSuggestOracle()), new ParamSuggester(Arrays.asList("owner:", "reviewer:"), new AccountSuggestOracle() { @Override public void onRequestSuggestions(final Request request, final Callback done) { super.onRequestSuggestions(request, new Callback() { @Override public void onSuggestionsReady(final Request request, final Response response) { if ("self".startsWith(request.getQuery())) { final ArrayList<SuggestOracle.Suggestion> r = new ArrayList<>(response.getSuggestions().size() + 1); r.addAll(response.getSuggestions()); r.add(new SuggestOracle.Suggestion() { @Override public String getDisplayString() { return getReplacementString(); } @Override public String getReplacementString() { return "self"; } }); 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("reviewer:"); suggestions.add("reviewer:self"); suggestions.add("reviewerin:"); suggestions.add("commit:"); suggestions.add("comment:"); suggestions.add("conflicts:"); suggestions.add("project:"); suggestions.add("projects:"); suggestions.add("parentproject:"); suggestions.add("branch:"); suggestions.add("topic:"); suggestions.add("ref:"); suggestions.add("tr:"); suggestions.add("bug:"); suggestions.add("label:"); suggestions.add("message:"); suggestions.add("file:"); suggestions.add("has:"); suggestions.add("has:draft"); suggestions.add("has: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:draft"); suggestions.add("is:closed"); suggestions.add("is:submitted"); suggestions.add("is:merged"); suggestions.add("is:abandoned"); suggestions.add("is:mergeable"); suggestions.add("status:"); suggestions.add("status:open"); suggestions.add("status:pending"); suggestions.add("status:reviewed"); suggestions.add("status:submitted"); 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("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 (final 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(final 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(final 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; public 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(final List<String> operators, final SuggestOracle parameterSuggestionOracle) { this.operators = operators; this.parameterSuggestionOracle = parameterSuggestionOracle; } boolean applicable(final String query) { final String operator = getApplicableOperator(query, operators); return operator != null && query.length() > operator.length(); } private String getApplicableOperator(final String lastWord, final List<String> operators) { for (final String operator : operators) { if (lastWord.startsWith(operator)) { return operator; } } return null; } void suggest(final String lastWord, final Request request, final Callback done) { final String operator = getApplicableOperator(lastWord, operators); parameterSuggestionOracle.requestSuggestions( new Request(lastWord.substring(operator.length()), request.getLimit()), new Callback() { @Override public void onSuggestionsReady(final Request req, final Response response) { final String query = request.getQuery(); final List<SearchSuggestOracle.Suggestion> r = new ArrayList<>(response.getSuggestions().size()); for (final 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(final String s) { if (!s.matches("^\\S*$")) { return "\"" + s + "\""; } return s; } }); } } }