aboutsummaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
authorTor Norbye <tnorbye@google.com>2011-06-09 11:17:31 -0700
committerAndroid Code Review <code-review@android.com>2011-06-09 11:17:31 -0700
commit422c71a3045270f5de478590fdb6a5584d08d335 (patch)
treec89cfdca6c40cf61c301ad912142ea6f18eda578
parent52a4e9a3afd7aecd06801b42d2ba8786ce824921 (diff)
parentc99b6718c30d75adb36727d3f9feaa4e89c5d181 (diff)
downloadsdk-422c71a3045270f5de478590fdb6a5584d08d335.zip
sdk-422c71a3045270f5de478590fdb6a5584d08d335.tar.gz
sdk-422c71a3045270f5de478590fdb6a5584d08d335.tar.bz2
Merge "Suggest similar class names or missing pkgs in the error console"
-rw-r--r--eclipse/dictionary.txt1
-rw-r--r--eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/eclipse/adt/AdtUtils.java37
-rw-r--r--eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/eclipse/adt/internal/editors/layout/gle2/CustomViewFinder.java13
-rw-r--r--eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/eclipse/adt/internal/editors/layout/gle2/GraphicalEditorPart.java184
-rw-r--r--eclipse/plugins/com.android.ide.eclipse.tests/unittests/com/android/ide/eclipse/adt/AdtUtilsTest.java12
5 files changed, 227 insertions, 20 deletions
diff --git a/eclipse/dictionary.txt b/eclipse/dictionary.txt
index 7264f13..853cb3c 100644
--- a/eclipse/dictionary.txt
+++ b/eclipse/dictionary.txt
@@ -120,6 +120,7 @@ javadoc
keystore
layoutlib
leaky
+levenshtein
lib
lifecycle
linebreaks
diff --git a/eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/eclipse/adt/AdtUtils.java b/eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/eclipse/adt/AdtUtils.java
index d73ed87..4eb8e2e 100644
--- a/eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/eclipse/adt/AdtUtils.java
+++ b/eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/eclipse/adt/AdtUtils.java
@@ -67,4 +67,41 @@ public class AdtUtils {
sb.append(str.substring(1));
return sb.toString();
}
+
+ /**
+ * Computes the edit distance (number of insertions, deletions or substitutions
+ * to edit one string into the other) between two strings. In particular,
+ * this will compute the Levenshtein distance.
+ * <p>
+ * See http://en.wikipedia.org/wiki/Levenshtein_distance for details.
+ *
+ * @param s the first string to compare
+ * @param t the second string to compare
+ * @return the edit distance between the two strings
+ */
+ public static int editDistance(String s, String t) {
+ int m = s.length();
+ int n = t.length();
+ int[][] d = new int[m + 1][n + 1];
+ for (int i = 0; i <= m; i++) {
+ d[i][0] = i;
+ }
+ for (int j = 0; j <= n; j++) {
+ d[0][j] = j;
+ }
+ for (int j = 1; j <= n; j++) {
+ for (int i = 1; i <= m; i++) {
+ if (s.charAt(i - 1) == t.charAt(j - 1)) {
+ d[i][j] = d[i - 1][j - 1];
+ } else {
+ int deletion = d[i - 1][j] + 1;
+ int insertion = d[i][j - 1] + 1;
+ int substitution = d[i - 1][j - 1] + 1;
+ d[i][j] = Math.min(deletion, Math.min(insertion, substitution));
+ }
+ }
+ }
+
+ return d[m][n];
+ }
}
diff --git a/eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/eclipse/adt/internal/editors/layout/gle2/CustomViewFinder.java b/eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/eclipse/adt/internal/editors/layout/gle2/CustomViewFinder.java
index 3226a02..ab4b57f 100644
--- a/eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/eclipse/adt/internal/editors/layout/gle2/CustomViewFinder.java
+++ b/eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/eclipse/adt/internal/editors/layout/gle2/CustomViewFinder.java
@@ -115,10 +115,14 @@ public class CustomViewFinder {
}
public void refresh() {
- refresh(null);
+ refresh(null /*listener*/, true /* sync */);
}
public void refresh(final Listener listener) {
+ refresh(listener, false /* sync */);
+ }
+
+ private void refresh(final Listener listener, boolean sync) {
// Add this listener to the list of listeners which should be notified when the
// search is done. (There could be more than one since multiple requests could
// arrive for a slow search since the search is run in a different thread).
@@ -139,6 +143,13 @@ public class CustomViewFinder {
FindViewsJob job = new FindViewsJob();
job.schedule();
+ if (sync) {
+ try {
+ job.join();
+ } catch (InterruptedException e) {
+ AdtPlugin.log(e, null);
+ }
+ }
}
public Collection<String> getCustomViews() {
diff --git a/eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/eclipse/adt/internal/editors/layout/gle2/GraphicalEditorPart.java b/eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/eclipse/adt/internal/editors/layout/gle2/GraphicalEditorPart.java
index 3229a7f..b4ff869 100644
--- a/eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/eclipse/adt/internal/editors/layout/gle2/GraphicalEditorPart.java
+++ b/eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/eclipse/adt/internal/editors/layout/gle2/GraphicalEditorPart.java
@@ -37,6 +37,7 @@ import com.android.ide.common.resources.configuration.FolderConfiguration;
import com.android.ide.common.sdk.LoadStatus;
import com.android.ide.eclipse.adt.AdtConstants;
import com.android.ide.eclipse.adt.AdtPlugin;
+import com.android.ide.eclipse.adt.AdtUtils;
import com.android.ide.eclipse.adt.internal.editors.AndroidXmlEditor;
import com.android.ide.eclipse.adt.internal.editors.IPageImageProvider;
import com.android.ide.eclipse.adt.internal.editors.IconFactory;
@@ -48,6 +49,8 @@ import com.android.ide.eclipse.adt.internal.editors.layout.LayoutReloadMonitor.I
import com.android.ide.eclipse.adt.internal.editors.layout.configuration.ConfigurationComposite;
import com.android.ide.eclipse.adt.internal.editors.layout.configuration.LayoutCreatorDialog;
import com.android.ide.eclipse.adt.internal.editors.layout.configuration.ConfigurationComposite.IConfigListener;
+import com.android.ide.eclipse.adt.internal.editors.layout.descriptors.LayoutDescriptors;
+import com.android.ide.eclipse.adt.internal.editors.layout.descriptors.ViewElementDescriptor;
import com.android.ide.eclipse.adt.internal.editors.layout.gle2.IncludeFinder.Reference;
import com.android.ide.eclipse.adt.internal.editors.layout.gre.RulesEngine;
import com.android.ide.eclipse.adt.internal.editors.ui.DecorComposite;
@@ -95,6 +98,9 @@ import org.eclipse.jdt.core.JavaModelException;
import org.eclipse.jdt.internal.ui.preferences.BuildPathsPropertyPage;
import org.eclipse.jdt.ui.actions.OpenNewClassWizardAction;
import org.eclipse.jdt.ui.wizards.NewClassWizardPage;
+import org.eclipse.jface.text.BadLocationException;
+import org.eclipse.jface.text.IDocument;
+import org.eclipse.jface.text.source.ISourceViewer;
import org.eclipse.jface.viewers.ISelection;
import org.eclipse.jface.viewers.ISelectionProvider;
import org.eclipse.jface.window.Window;
@@ -109,6 +115,9 @@ import org.eclipse.swt.layout.GridData;
import org.eclipse.swt.layout.GridLayout;
import org.eclipse.swt.widgets.Composite;
import org.eclipse.swt.widgets.Display;
+import org.eclipse.text.edits.MalformedTreeException;
+import org.eclipse.text.edits.MultiTextEdit;
+import org.eclipse.text.edits.ReplaceEdit;
import org.eclipse.ui.IEditorInput;
import org.eclipse.ui.IEditorSite;
import org.eclipse.ui.INullSelectionListener;
@@ -1567,16 +1576,23 @@ public class GraphicalEditorPart extends EditorPart
addText(mErrorLabel, "- ");
addText(mErrorLabel, clazz);
addText(mErrorLabel, " (");
- addActionLink(mErrorLabel, clazz,
- ActionLinkStyleRange.LINK_FIX_BUILD_PATH, "Fix Build Path");
+
+ IProject project = getProject();
+ Collection<String> customViews = getCustomViewClassNames(project);
+ addTypoSuggestions(clazz, customViews, false);
+ addTypoSuggestions(clazz, customViews, true);
+ addTypoSuggestions(clazz, getAndroidViewClassNames(project), false);
+
+ addActionLink(mErrorLabel,
+ ActionLinkStyleRange.LINK_FIX_BUILD_PATH, "Fix Build Path", clazz, null);
addText(mErrorLabel, ", ");
- addActionLink(mErrorLabel, clazz,
- ActionLinkStyleRange.LINK_EDIT_XML, "Edit XML");
+ addActionLink(mErrorLabel,
+ ActionLinkStyleRange.LINK_EDIT_XML, "Edit XML", clazz, null);
if (clazz.indexOf('.') != -1) {
// Add "Create Class" link, but only for custom views
addText(mErrorLabel, ", ");
- addActionLink(mErrorLabel, clazz,
- ActionLinkStyleRange.LINK_CREATE_CLASS, "Create Class");
+ addActionLink(mErrorLabel,
+ ActionLinkStyleRange.LINK_CREATE_CLASS, "Create Class", clazz, null);
}
addText(mErrorLabel, ")\n");
}
@@ -1590,11 +1606,11 @@ public class GraphicalEditorPart extends EditorPart
for (String clazz : brokenClasses) {
addText(mErrorLabel, "- ");
addText(mErrorLabel, " (");
- addActionLink(mErrorLabel, clazz,
- ActionLinkStyleRange.LINK_OPEN_CLASS, "Open Class");
+ addActionLink(mErrorLabel,
+ ActionLinkStyleRange.LINK_OPEN_CLASS, "Open Class", clazz, null);
addText(mErrorLabel, ", ");
- addActionLink(mErrorLabel, clazz,
- ActionLinkStyleRange.LINK_SHOW_LOG, "Show Error Log");
+ addActionLink(mErrorLabel,
+ ActionLinkStyleRange.LINK_SHOW_LOG, "Show Error Log", clazz, null);
addText(mErrorLabel, ")\n");
if (!(clazz.startsWith("android.") || //$NON-NLS-1$
@@ -1614,6 +1630,76 @@ public class GraphicalEditorPart extends EditorPart
mSashError.setMaximizedControl(null);
}
+ private void addTypoSuggestions(String actual, Collection<String> views,
+ boolean compareWithPackage) {
+ if (views.size() == 0) {
+ return;
+ }
+
+ // Look for typos and try to match with custom views and android views
+ String actualBase = actual.substring(actual.lastIndexOf('.') + 1);
+ if (views.size() > 0) {
+ for (String suggested : views) {
+ String suggestedBase = suggested.substring(suggested.lastIndexOf('.') + 1);
+
+ String matchWith = compareWithPackage ? suggested : suggestedBase;
+ int maxDistance = actualBase.length() >= 4 ? 2 : 1;
+ if (Math.abs(actualBase.length() - matchWith.length()) > maxDistance) {
+ // The string lengths differ more than the allowed edit distance;
+ // no point in even attempting to compute the edit distance (requires
+ // O(n*m) storage and O(n*m) speed, where n and m are the string lengths)
+ continue;
+ }
+ if (AdtUtils.editDistance(actualBase, matchWith) <= maxDistance) {
+ // Suggest this class as a typo for the given class
+ String labelClass = (suggestedBase.equals(actual) || actual.indexOf('.') != -1)
+ ? suggested : suggestedBase;
+ addActionLink(mErrorLabel,
+ ActionLinkStyleRange.LINK_CHANGE_CLASS_TO,
+ String.format("Change to %1$s",
+ // Only show full package name if class name
+ // is the same
+ labelClass),
+ actual,
+ suggested.startsWith(ANDROID_PKG) ? suggestedBase : suggested
+ );
+ addText(mErrorLabel, ", ");
+ }
+ }
+ }
+ }
+
+ private static Collection<String> getCustomViewClassNames(IProject project) {
+ CustomViewFinder finder = CustomViewFinder.get(project);
+ Collection<String> views = finder.getAllViews();
+ if (views == null) {
+ finder.refresh();
+ views = finder.getAllViews();
+ }
+
+ return views;
+ }
+
+ private static Collection<String> getAndroidViewClassNames(IProject project) {
+ List<String> classNames = new ArrayList<String>(100);
+
+ Sdk currentSdk = Sdk.getCurrent();
+ IAndroidTarget target = currentSdk.getTarget(project);
+ if (target != null) {
+ AndroidTargetData targetData = currentSdk.getTargetData(target);
+ LayoutDescriptors layoutDescriptors = targetData.getLayoutDescriptors();
+
+ for (ViewElementDescriptor d : layoutDescriptors.getViewDescriptors()) {
+ classNames.add(d.getFullClassName());
+ }
+ for (ViewElementDescriptor d : layoutDescriptors.getLayoutDescriptors()) {
+ classNames.add(d.getFullClassName());
+ }
+ }
+
+ return classNames;
+ }
+
/** Add a normal line of text to the styled text widget. */
private void addText(StyledText styledText, String...string) {
for (String s : string) {
@@ -1708,12 +1794,13 @@ public class GraphicalEditorPart extends EditorPart
* A mouse-click listener is setup and it interprets the link based on the
* action, corresponding to the value fields in {@link ActionLinkStyleRange}.
*/
- private void addActionLink(StyledText styledText, String fqcn, int action, String label) {
+ private void addActionLink(StyledText styledText, int action, String label,
+ String data1, String data2) {
String s = styledText.getText();
int start = (s == null ? 0 : s.length());
styledText.append(label);
- StyleRange sr = new ActionLinkStyleRange(action, fqcn);
+ StyleRange sr = new ActionLinkStyleRange(action, data1, data2);
sr.start = start;
sr.length = label.length();
sr.fontStyle = SWT.NORMAL;
@@ -1857,23 +1944,28 @@ public class GraphicalEditorPart extends EditorPart
private static final int LINK_OPEN_CLASS = 4;
/** Show the error log */
private static final int LINK_SHOW_LOG = 5;
+ /** Change the class reference to the given fully qualified name */
+ private static final int LINK_CHANGE_CLASS_TO = 6;
- /** The current class or null */
- private final String mFqcn;
+ /** Client data 1 - usually the class name */
+ private final String mData1;
+ /** Client data 2 - such as the suggested new name */
+ private final String mData2;
/** The action to be taken when the link is clicked */
private final int mAction;
- private ActionLinkStyleRange(int action, String fqcn) {
+ private ActionLinkStyleRange(int action, String data1, String data2) {
super();
- this.mAction = action;
- this.mFqcn = fqcn;
+ mAction = action;
+ mData1 = data1;
+ mData2 = data2;
}
/** Performs the click action */
public void onClick() {
switch (mAction) {
case LINK_CREATE_CLASS:
- createNewClass(mFqcn);
+ createNewClass(mData1);
break;
case LINK_EDIT_XML:
mLayoutEditor.setActivePage(AndroidXmlEditor.TEXT_EDITOR_ID);
@@ -1886,7 +1978,7 @@ public class GraphicalEditorPart extends EditorPart
getProject(), id, null, null).open();
break;
case LINK_OPEN_CLASS:
- AdtPlugin.openJavaClass(getProject(), mFqcn);
+ AdtPlugin.openJavaClass(getProject(), mData1);
break;
case LINK_SHOW_LOG:
IWorkbench workbench = PlatformUI.getWorkbench();
@@ -1898,6 +1990,60 @@ public class GraphicalEditorPart extends EditorPart
AdtPlugin.log(e, null);
}
break;
+ case LINK_CHANGE_CLASS_TO:
+ // Change class reference of mData1 to mData2
+ // TODO: run under undo lock
+ MultiTextEdit edits = new MultiTextEdit();
+ ISourceViewer textViewer = mLayoutEditor.getStructuredSourceViewer();
+ IDocument document = textViewer.getDocument();
+ String xml = document.get();
+ int index = 0;
+ // Replace <old with <new and </old with </new
+ String prefix = "<"; //$NON-NLS-1$
+ String find = prefix + mData1;
+ String replaceWith = prefix + mData2;
+ while (true) {
+ index = xml.indexOf(find, index);
+ if (index == -1) {
+ break;
+ }
+ edits.addChild(new ReplaceEdit(index, find.length(), replaceWith));
+ index += find.length();
+ }
+ index = 0;
+ prefix = "</"; //$NON-NLS-1$
+ find = prefix + mData1;
+ replaceWith = prefix + mData2;
+ while (true) {
+ index = xml.indexOf(find, index);
+ if (index == -1) {
+ break;
+ }
+ edits.addChild(new ReplaceEdit(index, find.length(), replaceWith));
+ index += find.length();
+ }
+ // Handle <view class="old">
+ index = 0;
+ prefix = "\""; //$NON-NLS-1$
+ String suffix = "\""; //$NON-NLS-1$
+ find = prefix + mData1 + suffix;
+ replaceWith = prefix + mData2 + suffix;
+ while (true) {
+ index = xml.indexOf(find, index);
+ if (index == -1) {
+ break;
+ }
+ edits.addChild(new ReplaceEdit(index, find.length(), replaceWith));
+ index += find.length();
+ }
+ try {
+ edits.apply(document);
+ } catch (MalformedTreeException e) {
+ AdtPlugin.log(e, null);
+ } catch (BadLocationException e) {
+ AdtPlugin.log(e, null);
+ }
+ break;
default:
break;
}
diff --git a/eclipse/plugins/com.android.ide.eclipse.tests/unittests/com/android/ide/eclipse/adt/AdtUtilsTest.java b/eclipse/plugins/com.android.ide.eclipse.tests/unittests/com/android/ide/eclipse/adt/AdtUtilsTest.java
index 2589841..6f56f73 100644
--- a/eclipse/plugins/com.android.ide.eclipse.tests/unittests/com/android/ide/eclipse/adt/AdtUtilsTest.java
+++ b/eclipse/plugins/com.android.ide.eclipse.tests/unittests/com/android/ide/eclipse/adt/AdtUtilsTest.java
@@ -45,4 +45,16 @@ public class AdtUtilsTest extends TestCase {
assertSame("Foo", AdtUtils.capitalize("Foo"));
assertNull(null, AdtUtils.capitalize(null));
}
+
+ public void testEditDistance() {
+ // editing kitten to sitting has edit distance 3:
+ // replace k with s
+ // replace e with i
+ // append g
+ assertEquals(3, AdtUtils.editDistance("kitten", "sitting"));
+
+ assertEquals(3, AdtUtils.editDistance("saturday", "sunday"));
+ assertEquals(1, AdtUtils.editDistance("button", "bitton"));
+ assertEquals(6, AdtUtils.editDistance("radiobutton", "bitton"));
+ }
}