A basic suggestion oracle utilty that bolds substrings

Applications can subclass this oracle to have their results
automatically bold the substring(s) which match the current
query string.  This is a rather common idiom for completion
widgets, unfortunately GWT 1.5.3 doesn't provide it out of
the box for us.

Signed-off-by: Shawn O. Pearce <sop@google.com>
diff --git a/src/main/java/com/google/gwtexpui/safehtml/client/HighlightSuggestOracle.java b/src/main/java/com/google/gwtexpui/safehtml/client/HighlightSuggestOracle.java
new file mode 100644
index 0000000..758a215
--- /dev/null
+++ b/src/main/java/com/google/gwtexpui/safehtml/client/HighlightSuggestOracle.java
@@ -0,0 +1,92 @@
+// Copyright 2009 Google Inc.
+//
+// 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.gwtexpui.safehtml.client;
+
+import com.google.gwt.user.client.ui.SuggestOracle;
+
+import java.util.ArrayList;
+
+/**
+ * A suggestion oracle that tries to highlight the matched text.
+ * <p>
+ * Suggestions supplied by the implementation of
+ * {@link #onRequestSuggestions(Request, Callback)} are modified to wrap all
+ * occurrences of the {@link SuggestOracle.Request#getQuery()} substring in HTML
+ * <code>&lt;strong&gt;</code> tags, so they can be emphasized to the user.
+ */
+public abstract class HighlightSuggestOracle extends SuggestOracle {
+  private static String escape(String ds) {
+    return new SafeHtmlBuilder().append(ds).asString();
+  }
+
+  @Override
+  public final boolean isDisplayStringHTML() {
+    return true;
+  }
+
+  @Override
+  public final void requestSuggestions(final Request request, final Callback cb) {
+    onRequestSuggestions(request, new Callback() {
+      public void onSuggestionsReady(final Request request,
+          final Response response) {
+        final String qpat = "(" + escape(request.getQuery()) + ")";
+        final boolean html = isHTML();
+        final ArrayList<Suggestion> r = new ArrayList<Suggestion>();
+        for (final Suggestion s : response.getSuggestions()) {
+          r.add(new BoldSuggestion(qpat, s, html));
+        }
+        cb.onSuggestionsReady(request, new Response(r));
+      }
+    });
+  }
+
+  /**
+   * @return true if {@link SuggestOracle.Suggestion#getDisplayString()} returns
+   *         HTML; false if the text must be escaped before evaluating in an
+   *         HTML like context.
+   */
+  protected boolean isHTML() {
+    return false;
+  }
+
+  /** Compute the suggestions and return them for display. */
+  protected abstract void onRequestSuggestions(Request request, Callback done);
+
+  private static class BoldSuggestion implements Suggestion {
+    private final Suggestion suggestion;
+    private final String displayString;
+
+    BoldSuggestion(final String qstr, final Suggestion s, final boolean html) {
+      suggestion = s;
+
+      String ds = s.getDisplayString();
+      if (!html) {
+        ds = escape(ds);
+      }
+      displayString = sgi(ds, qstr, "<strong>$1</strong>");
+    }
+
+    private static native String sgi(String inString, String pat, String newHtml)
+    /*-{ return inString.replace(RegExp(pat, 'gi'), newHtml); }-*/;
+
+    public String getDisplayString() {
+      return displayString;
+    }
+
+    public String getReplacementString() {
+      return suggestion.getReplacementString();
+    }
+  }
+}