aboutsummaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
authorTor Norbye <tnorbye@google.com>2011-03-25 16:39:13 -0700
committerAndroid Code Review <code-review@android.com>2011-03-25 16:39:13 -0700
commit241972d2cf76b3b79fa2c56d4299ea280a8f7ba2 (patch)
tree801567bc085802a4ebd53c61440de7b63e0dfc87
parent163d113f5eea35b96f5bf0d9674e178076a4b943 (diff)
parente2726b151fdb7b60fe603cc92b837b4c2b36258c (diff)
downloadsdk-241972d2cf76b3b79fa2c56d4299ea280a8f7ba2.zip
sdk-241972d2cf76b3b79fa2c56d4299ea280a8f7ba2.tar.gz
sdk-241972d2cf76b3b79fa2c56d4299ea280a8f7ba2.tar.bz2
Merge "Handle AAPT ranges in value resource files"
-rw-r--r--eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/eclipse/adt/internal/build/AaptParser.java224
-rw-r--r--eclipse/plugins/com.android.ide.eclipse.tests/src/com/android/ide/eclipse/adt/internal/build/AaptParserTest.java200
-rw-r--r--eclipse/plugins/com.android.ide.eclipse.tests/src/com/android/ide/eclipse/adt/internal/build/AaptQuickFixTest.java14
-rw-r--r--eclipse/plugins/com.android.ide.eclipse.tests/src/com/android/ide/eclipse/adt/internal/editors/layout/refactoring/AdtProjectTest.java31
-rw-r--r--eclipse/plugins/com.android.ide.eclipse.tests/src/com/android/ide/eclipse/adt/internal/editors/layout/refactoring/testdata/aapterror1.xml12
-rw-r--r--eclipse/plugins/com.android.ide.eclipse.tests/src/com/android/ide/eclipse/adt/internal/editors/layout/refactoring/testdata/aapterror2.xml9
-rw-r--r--eclipse/plugins/com.android.ide.eclipse.tests/src/com/android/ide/eclipse/adt/internal/editors/layout/refactoring/testdata/aapterror3.xml6
-rw-r--r--eclipse/plugins/com.android.ide.eclipse.tests/src/com/android/ide/eclipse/adt/internal/editors/layout/refactoring/testdata/aapterror4.xml7
-rw-r--r--eclipse/plugins/com.android.ide.eclipse.tests/src/com/android/ide/eclipse/adt/internal/editors/layout/refactoring/testdata/aapterror5.xml6
-rw-r--r--eclipse/plugins/com.android.ide.eclipse.tests/src/com/android/ide/eclipse/adt/internal/editors/layout/refactoring/testdata/aapterror6.xml11
-rw-r--r--eclipse/plugins/com.android.ide.eclipse.tests/src/com/android/ide/eclipse/adt/internal/editors/layout/refactoring/testdata/aapterror7.xml10
11 files changed, 484 insertions, 46 deletions
diff --git a/eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/eclipse/adt/internal/build/AaptParser.java b/eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/eclipse/adt/internal/build/AaptParser.java
index bf7287b..2a2f0d0 100644
--- a/eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/eclipse/adt/internal/build/AaptParser.java
+++ b/eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/eclipse/adt/internal/build/AaptParser.java
@@ -28,6 +28,7 @@ import org.eclipse.core.runtime.CoreException;
import org.eclipse.jface.text.FindReplaceDocumentAdapter;
import org.eclipse.jface.text.IDocument;
import org.eclipse.jface.text.IRegion;
+import org.eclipse.jface.text.Region;
import org.eclipse.ui.editors.text.TextFileDocumentProvider;
import org.eclipse.ui.texteditor.IDocumentProvider;
@@ -130,10 +131,49 @@ public final class AaptParser {
* caused the error.
* <p>
* Example:
- * Error: No resource found that matches the given name (at 'text' with value '@string/foo')
+ * error: No resource found that matches the given name (at 'text' with value '@string/foo')
*/
private static final Pattern sValueRangePattern =
- Pattern.compile("\\(at '(.+)' with value '(.+)'\\)"); //$NON-NLS-1$
+ Pattern.compile("\\(at '(.+)' with value '(.*)'\\)"); //$NON-NLS-1$
+
+
+ /**
+ * Portion of error message which points to the second occurrence of a repeated resource
+ * definition.
+ * <p>
+ * Example:
+ * error: Resource entry repeatedStyle1 already has bag item android:gravity.
+ */
+ private static final Pattern sRepeatedRangePattern =
+ Pattern.compile("Resource entry (.+) already has bag item (.+)\\."); //$NON-NLS-1$
+
+ /**
+ * Suffix of error message which points to the first occurrence of a repeated resource
+ * definition.
+ * Example:
+ * Originally defined here.
+ */
+ private static final String ORIGINALLY_DEFINED_MSG = "Originally defined here."; //$NON-NLS-1$
+
+ /**
+ * Portion of error message which points to the second occurrence of a repeated resource
+ * definition.
+ * <p>
+ * Example:
+ * error: Resource entry repeatedStyle1 already has bag item android:gravity.
+ */
+ private static final Pattern sNoResourcePattern =
+ Pattern.compile("No resource found that matches the given name: attr '(.+)'\\."); //$NON-NLS-1$
+
+ /**
+ * Portion of error message which points to a missing required attribute in a
+ * resource definition.
+ * <p>
+ * Example:
+ * error: error: A 'name' attribute is required for <style>
+ */
+ private static final Pattern sRequiredPattern =
+ Pattern.compile("A '(.+)' attribute is required for <(.+)>"); //$NON-NLS-1$
/**
* 2 line aapt error<br>
@@ -416,37 +456,10 @@ public final class AaptParser {
int startOffset = -1;
int endOffset = -1;
if (f2 instanceof IFile) {
- Matcher matcher = sValueRangePattern.matcher(message);
- if (matcher.find()) {
- String value = matcher.group(2);
- IFile iFile = (IFile) f2;
- IDocumentProvider provider = new TextFileDocumentProvider();
- try {
- provider.connect(iFile);
- IDocument document = provider.getDocument(iFile);
- if (document != null) {
- IRegion lineInfo = document.getLineInformation(line - 1);
- int lineStartOffset = lineInfo.getOffset();
- // The aapt errors will be anchored on the line where the
- // element starts - which means that with formatting where
- // attributes end up on subsequent lines we don't find it on
- // the error line indicated by aapt.
- // Therefore, search forwards in the document.
- FindReplaceDocumentAdapter adapter =
- new FindReplaceDocumentAdapter(document);
- IRegion region = adapter.find(lineStartOffset, value,
- true /*forwardSearch*/, true /*caseSensitive*/,
- false /*wholeWord*/, false /*regExSearch*/);
- if (region != null) {
- startOffset = region.getOffset();
- endOffset = startOffset + value.length();
- }
- }
- } catch (Exception e) {
- AdtPlugin.log(e, "Can't find range information for %1$s", location);
- } finally {
- provider.disconnect(iFile);
- }
+ IRegion region = findRange((IFile) f2, line, message);
+ if (region != null) {
+ startOffset = region.getOffset();
+ endOffset = startOffset + region.getLength();
}
}
@@ -502,6 +515,151 @@ public final class AaptParser {
}
/**
+ * Given an aapt error message in a given file and a given (initial) line number,
+ * return the corresponding offset range for the error, or null.
+ */
+ private static IRegion findRange(IFile file, int line, String message) {
+ Matcher matcher = sValueRangePattern.matcher(message);
+ if (matcher.find()) {
+ String property = matcher.group(1);
+ String value = matcher.group(2);
+
+ // First find the property. We can't just immediately look for the
+ // value, because there could be other attributes in this element
+ // earlier than the one in error, and we might accidentally pick
+ // up on a different occurrence of the value in a context where
+ // it is valid.
+ if (value.length() > 0) {
+ return findRange(file, line, property, value);
+ } else {
+ // Find first occurrence of property followed by '' or ""
+ IRegion region1 = findRange(file, line, property, "\"\""); //$NON-NLS-1$
+ IRegion region2 = findRange(file, line, property, "''"); //$NON-NLS-1$
+ if (region1 == null) {
+ if (region2 == null) {
+ // Highlight the property instead
+ return findRange(file, line, property, null);
+ }
+ return region2;
+ } else if (region2 == null) {
+ return region1;
+ } else if (region1.getOffset() < region2.getOffset()) {
+ return region1;
+ } else {
+ return region2;
+ }
+ }
+ }
+
+ matcher = sRepeatedRangePattern.matcher(message);
+ if (matcher.find()) {
+ String property = matcher.group(2);
+ return findRange(file, line, property, null);
+ }
+
+ matcher = sNoResourcePattern.matcher(message);
+ if (matcher.find()) {
+ String property = matcher.group(1);
+ return findRange(file, line, property, null);
+ }
+
+ matcher = sRequiredPattern.matcher(message);
+ if (matcher.find()) {
+ String elementName = matcher.group(2);
+ IRegion region = findRange(file, line, '<' + elementName, null);
+ if (region != null && region.getLength() > 1) {
+ // Skip the opening <
+ region = new Region(region.getOffset() + 1, region.getLength() - 1);
+ }
+ return region;
+ }
+
+ if (message.endsWith(ORIGINALLY_DEFINED_MSG)) {
+ return findLineTextRange(file, line);
+ }
+
+ return null;
+ }
+
+ /**
+ * Given a file and line number, return the range of the first match starting on the
+ * given line. If second is non null, also search for the second string starting at he
+ * location of the first string.
+ */
+ private static IRegion findRange(IFile file, int line, String first,
+ String second) {
+ IRegion region = null;
+ IDocumentProvider provider = new TextFileDocumentProvider();
+ try {
+ provider.connect(file);
+ IDocument document = provider.getDocument(file);
+ if (document != null) {
+ IRegion lineInfo = document.getLineInformation(line - 1);
+ int lineStartOffset = lineInfo.getOffset();
+ // The aapt errors will be anchored on the line where the
+ // element starts - which means that with formatting where
+ // attributes end up on subsequent lines we don't find it on
+ // the error line indicated by aapt.
+ // Therefore, search forwards in the document.
+ FindReplaceDocumentAdapter adapter =
+ new FindReplaceDocumentAdapter(document);
+
+ region = adapter.find(lineStartOffset, first,
+ true /*forwardSearch*/, true /*caseSensitive*/,
+ false /*wholeWord*/, false /*regExSearch*/);
+ if (region != null && second != null) {
+ region = adapter.find(region.getOffset() + first.length(), second,
+ true /*forwardSearch*/, true /*caseSensitive*/,
+ false /*wholeWord*/, false /*regExSearch*/);
+ }
+ }
+ } catch (Exception e) {
+ AdtPlugin.log(e, "Can't find range information for %1$s", file.getName());
+ } finally {
+ provider.disconnect(file);
+ }
+ return region;
+ }
+
+ /** Returns the non-whitespace line range at the given line number. */
+ private static IRegion findLineTextRange(IFile file, int line) {
+ IDocumentProvider provider = new TextFileDocumentProvider();
+ try {
+ provider.connect(file);
+ IDocument document = provider.getDocument(file);
+ if (document != null) {
+ IRegion lineInfo = document.getLineInformation(line - 1);
+ String lineContents = document.get(lineInfo.getOffset(), lineInfo.getLength());
+ int lineBegin = 0;
+ int lineEnd = lineContents.length()-1;
+
+ for (; lineEnd >= 0; lineEnd--) {
+ char c = lineContents.charAt(lineEnd);
+ if (!Character.isWhitespace(c)) {
+ break;
+ }
+ }
+ lineEnd++;
+ for (; lineBegin < lineEnd; lineBegin++) {
+ char c = lineContents.charAt(lineBegin);
+ if (!Character.isWhitespace(c)) {
+ break;
+ }
+ }
+ if (lineBegin < lineEnd) {
+ return new Region(lineInfo.getOffset() + lineBegin, lineEnd - lineBegin);
+ }
+ }
+ } catch (Exception e) {
+ AdtPlugin.log(e, "Can't find range information for %1$s", file.getName());
+ } finally {
+ provider.disconnect(file);
+ }
+
+ return null;
+ }
+
+ /**
* Returns a matching matcher for the next line
* @param lines The array of lines
* @param nextIndex The index of the next line
diff --git a/eclipse/plugins/com.android.ide.eclipse.tests/src/com/android/ide/eclipse/adt/internal/build/AaptParserTest.java b/eclipse/plugins/com.android.ide.eclipse.tests/src/com/android/ide/eclipse/adt/internal/build/AaptParserTest.java
new file mode 100644
index 0000000..af4e2b7
--- /dev/null
+++ b/eclipse/plugins/com.android.ide.eclipse.tests/src/com/android/ide/eclipse/adt/internal/build/AaptParserTest.java
@@ -0,0 +1,200 @@
+/*
+ * Copyright (C) 2011 The Android Open Source Project
+ *
+ * Licensed under the Eclipse Public License, Version 1.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.eclipse.org/org/documents/epl-v10.php
+ *
+ * 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.android.ide.eclipse.adt.internal.build;
+
+import com.android.ide.eclipse.adt.AdtConstants;
+import com.android.ide.eclipse.adt.AdtPlugin;
+import com.android.ide.eclipse.adt.internal.editors.layout.refactoring.AdtProjectTest;
+
+import org.eclipse.core.resources.IFile;
+import org.eclipse.core.resources.IMarker;
+import org.eclipse.core.resources.IProject;
+import org.eclipse.core.resources.IResource;
+
+import java.io.File;
+import java.util.Collections;
+import java.util.List;
+
+public class AaptParserTest extends AdtProjectTest {
+
+ public void testBasic() throws Exception {
+ // Test the "at 'property' with value 'value' range matching included with most aapt errors
+ checkRanges("quickfix1.xml", "res/layout/quickfix1.xml",
+ "quickfix1.xml:7: error: Error: No resource found that matches the given name (at"
+ + " 'text' with value '@string/firststring').",
+ "android:text=\"^@string/firststring\"",
+ "android:text=\"@string/firststring^\"");
+ }
+
+ public void testRange1() throws Exception {
+ // Check that when the actual aapt error occurs on a line later than the original error
+ // line, the forward search which looks for a value match does not stop on an
+ // earlier line that happens to have the same value prefix
+ checkRanges("aapterror1.xml", "res/layout/aapterror1.xml",
+ "aapterror1.xml:5: error: Error: Integer types not allowed (at "
+ + "'layout_marginBottom' with value '50').",
+ "marginBottom=\"^50\"", "marginBottom=\"50^\"");
+ }
+
+ public void testRange2() throws Exception {
+ // Check that when we have a duplicate resource error, we highlight both the original
+ // property and the original definition.
+ // This tests the second, duplicate declaration ration.
+ checkRanges("aapterror2.xml", "res/values/aapterror2.xml",
+ "aapterror2.xml:7: error: Resource entry repeatedStyle1 already has bag item "
+ + "android:gravity.",
+ "<item name=\"^android:gravity\">bottom</item>",
+ "<item name=\"android:gravity^\">bottom</item>");
+ }
+
+ public void testRange3() throws Exception {
+ // Check that when we have a duplicate resource error, we highlight both the original
+ // property and the original definition.
+ // This tests the original definition. Note that we don't have enough position info
+ // so we simply highlight the whitespace portion of the line.
+ checkRanges("aapterror2.xml", "res/values/aapterror2.xml",
+ "aapterror2.xml:4: Originally defined here.",
+ "^<item name=\"android:gravity\">left</item>",
+ "<item name=\"android:gravity\">left</item>^");
+ }
+
+ public void testRange4() throws Exception {
+ // Check for aapt error which occurs when the attribute name in an item style declaration
+ // is nonexistent
+ checkRanges("aapterror3.xml", "res/values/aapterror3.xml",
+ "aapterror3.xml:4: error: Error: No resource found that matches the given name: "
+ + "attr 'nonexistent'.",
+ "<item name=\"^nonexistent\">5</item>",
+ "<item name=\"nonexistent^\">5</item>");
+ }
+
+ public void testRange5() throws Exception {
+ // Test missing resource name
+ checkRanges("aapterror4.xml", "res/values/aapterror4.xml",
+ "aapterror4.xml:3: error: A 'name' attribute is required for <style>",
+ "<^style>",
+ "<style^>");
+ }
+
+ public void testRange6() throws Exception {
+ checkRanges("aapterror4.xml", "res/values/aapterror4.xml",
+ "aapterror4.xml:6: error: A 'type' attribute is required for <item>",
+ "<^item></item>",
+ "<item^></item>");
+ }
+
+ public void testRange7() throws Exception {
+ // Test missing resource name
+ checkRanges("aapterror4.xml", "res/values/aapterror4.xml",
+ "aapterror4.xml:6: error: A 'name' attribute is required for <item>",
+ "<^item></item>",
+ "<item^></item>");
+ }
+
+ // This test is disabled because I can't find a useful scenario for handling this error
+ // message. When this error occurs, we will also get a warning on a missing attribute, and
+ // that warning already underlines the element name.
+ //public void testRange8() throws Exception {
+ // // Test missing resource name
+ // checkRanges("aapterror4.xml", "res/values/aapterror4.xml",
+ // "aapterror4.xml:4: error: Error: Resource id cannot be an empty string: attr ''.",
+ // " ^<item />",
+ // " <item />^");
+ //}
+
+ public void testRange9() throws Exception {
+ // Test missing resource name
+ checkRanges("aapterror5.xml", "res/values/aapterror5.xml",
+ "aapterror5.xml:4: error: Error: String types not allowed (at "
+ + "'android:layout_width' with value '').",
+ " <item name=\"^android:layout_width\"></item>",
+ " <item name=\"android:layout_width^\"></item>");
+ }
+
+ public void testRange10() throws Exception {
+ // Test missing resource name
+ checkRanges("aapterror6.xml", "res/layout/aapterror6.xml",
+ "aapterror6.xml:5: error: Error: String types not allowed (at 'layout_marginTop'"
+ + " with value '').",
+ "android:layout_marginTop=^\"\"",
+ "android:layout_marginTop=\"\"^");
+ }
+
+ public void testRange11() throws Exception {
+ // Test missing resource name
+ checkRanges("aapterror6.xml", "res/layout/aapterror6.xml",
+ "aapterror1.xml:5: error: Error: String types not allowed (at 'layout_marginLeft'"
+ + " with value '').",
+ "android:layout_marginLeft=^''",
+ "android:layout_marginLeft=''^");
+ }
+
+ public void testRange12() throws Exception {
+ // Test missing resource name
+ checkRanges("aapterror7.xml", "res/layout/aapterror7.xml",
+ "aapterror7.xml:5: error: Error: String types not allowed (at 'id'"
+ + " with value '').",
+ "android:id=^\"\"",
+ "android:id=\"\"^");
+ }
+
+ private void checkRanges(String name, String destPath, String aaptError,
+ String expectCaretBegin, String expectCaretEnd)
+ throws Exception {
+ IProject project = getProject();
+ IFile file = getTestDataFile(project, name, destPath);
+
+ // Make file paths absolute
+ String osRoot = project.getLocation().toOSString();
+ String fileRelativePath = file.getProjectRelativePath().toPortableString();
+ String filePath = osRoot + File.separator + fileRelativePath;
+ String originalError = filePath + aaptError.substring(aaptError.indexOf(':'));
+ List<String> errors = Collections.singletonList(originalError);
+
+ // Remove anything already placed there by the project create/build automatic
+ // (this usually only happens while debugging so the background thread has a chance
+ // to get things going)
+ IMarker[] markers = file.findMarkers(AdtConstants.MARKER_AAPT_COMPILE, true,
+ IResource.DEPTH_ZERO);
+ for (IMarker marker : markers) {
+ marker.delete();
+ }
+
+ AaptParser.parseOutput(errors, project);
+ markers = file.findMarkers(AdtConstants.MARKER_AAPT_COMPILE, true,
+ IResource.DEPTH_ZERO);
+ assertNotNull(markers);
+ assertEquals(1, markers.length);
+
+ String fileContents = AdtPlugin.readFile(file);
+ int rangeBegin = getCaretOffset(file, expectCaretBegin);
+ int rangeEnd = getCaretOffset(file, expectCaretEnd);
+
+ // Check text range
+ IMarker marker = markers[0];
+ String message = marker.getAttribute(IMarker.MESSAGE, ""); //$NON-NLS-1$
+ String simplerMessage = aaptError.substring(aaptError.indexOf(' ') + 1);
+ assertEquals(simplerMessage, message);
+ int start = marker.getAttribute(IMarker.CHAR_START, 0);
+ int end = marker.getAttribute(IMarker.CHAR_END, 0);
+
+ assertEquals("Wrong start offset, expected " + expectCaretBegin + " but was "
+ + getCaretContext(fileContents, start), rangeBegin, start);
+ assertEquals("Wrong end offset, expected " + expectCaretEnd + " but was "
+ + getCaretContext(fileContents, end), rangeEnd, end);
+ }
+}
diff --git a/eclipse/plugins/com.android.ide.eclipse.tests/src/com/android/ide/eclipse/adt/internal/build/AaptQuickFixTest.java b/eclipse/plugins/com.android.ide.eclipse.tests/src/com/android/ide/eclipse/adt/internal/build/AaptQuickFixTest.java
index cc4453b..853ab22 100644
--- a/eclipse/plugins/com.android.ide.eclipse.tests/src/com/android/ide/eclipse/adt/internal/build/AaptQuickFixTest.java
+++ b/eclipse/plugins/com.android.ide.eclipse.tests/src/com/android/ide/eclipse/adt/internal/build/AaptQuickFixTest.java
@@ -179,19 +179,7 @@ public class AaptQuickFixTest extends AdtProjectTest {
String newFileContents = AdtPlugin.readFile(newFile);
// Insert selection markers -- [ ] for the selection range, ^ for the caret
- String newFileWithCaret;
- int selectionBegin = selectedRange.x;
- int selectionEnd = selectionBegin + selectedRange.y;
- if (selectionBegin < selectionEnd) {
- newFileWithCaret = newFileContents.substring(0, selectionBegin) + "[^"
- + newFileContents.substring(selectionBegin, selectionEnd) + "]"
- + newFileContents.substring(selectionEnd);
- } else {
- // Selected range
- newFileWithCaret = newFileContents.substring(0, selectionBegin) + "^"
- + newFileContents.substring(selectionBegin);
- }
-
+ String newFileWithCaret = addSelection(newFileContents, selectedRange);
newFileWithCaret = removeSessionData(newFileWithCaret);
assertEqualsGolden(name, newFileWithCaret);
diff --git a/eclipse/plugins/com.android.ide.eclipse.tests/src/com/android/ide/eclipse/adt/internal/editors/layout/refactoring/AdtProjectTest.java b/eclipse/plugins/com.android.ide.eclipse.tests/src/com/android/ide/eclipse/adt/internal/editors/layout/refactoring/AdtProjectTest.java
index fb094ad..0a8ab3d 100644
--- a/eclipse/plugins/com.android.ide.eclipse.tests/src/com/android/ide/eclipse/adt/internal/editors/layout/refactoring/AdtProjectTest.java
+++ b/eclipse/plugins/com.android.ide.eclipse.tests/src/com/android/ide/eclipse/adt/internal/editors/layout/refactoring/AdtProjectTest.java
@@ -43,6 +43,7 @@ import org.eclipse.core.runtime.Path;
import org.eclipse.jface.operation.IRunnableWithProgress;
import org.eclipse.jface.wizard.IWizardContainer;
import org.eclipse.jface.wizard.IWizardPage;
+import org.eclipse.swt.graphics.Point;
import org.eclipse.swt.widgets.Display;
import org.eclipse.swt.widgets.Shell;
import org.eclipse.ui.IWorkingSet;
@@ -206,6 +207,36 @@ public class AdtProjectTest extends SdkTestCase {
return caretContextIndex + caretDelta;
}
+ protected String addSelection(String newFileContents, Point selectedRange) {
+ int selectionBegin = selectedRange.x;
+ int selectionEnd = selectionBegin + selectedRange.y;
+ return addSelection(newFileContents, selectionBegin, selectionEnd);
+ }
+
+ protected String addSelection(String newFileContents, int selectionBegin, int selectionEnd) {
+ // Insert selection markers -- [ ] for the selection range, ^ for the caret
+ String newFileWithCaret;
+ if (selectionBegin < selectionEnd) {
+ newFileWithCaret = newFileContents.substring(0, selectionBegin) + "[^"
+ + newFileContents.substring(selectionBegin, selectionEnd) + "]"
+ + newFileContents.substring(selectionEnd);
+ } else {
+ // Selected range
+ newFileWithCaret = newFileContents.substring(0, selectionBegin) + "^"
+ + newFileContents.substring(selectionBegin);
+ }
+
+ return newFileWithCaret;
+ }
+
+ protected String getCaretContext(String file, int offset) {
+ int windowSize = 20;
+ int begin = Math.max(0, offset - windowSize / 2);
+ int end = Math.min(file.length(), offset + windowSize / 2);
+
+ return "..." + file.substring(begin, offset) + "^" + file.substring(offset, end) + "...";
+ }
+
/**
* Very primitive line differ, intended for files where there are very minor changes
* (such as code completion apply-tests)
diff --git a/eclipse/plugins/com.android.ide.eclipse.tests/src/com/android/ide/eclipse/adt/internal/editors/layout/refactoring/testdata/aapterror1.xml b/eclipse/plugins/com.android.ide.eclipse.tests/src/com/android/ide/eclipse/adt/internal/editors/layout/refactoring/testdata/aapterror1.xml
new file mode 100644
index 0000000..d72f4ba
--- /dev/null
+++ b/eclipse/plugins/com.android.ide.eclipse.tests/src/com/android/ide/eclipse/adt/internal/editors/layout/refactoring/testdata/aapterror1.xml
@@ -0,0 +1,12 @@
+<FrameLayout
+ xmlns:android="http://schemas.android.com/apk/res/android"
+ android:layout_width="wrap_content"
+ android:layout_height="match_parent">
+ <TextView
+ android:layout_width="fill_parent"
+ android:layout_height="wrap_content"
+ android:layout_marginTop="50pt"
+ android:layout_marginLeft="50dp"
+ android:layout_marginBottom="50"
+ />
+</FrameLayout>
diff --git a/eclipse/plugins/com.android.ide.eclipse.tests/src/com/android/ide/eclipse/adt/internal/editors/layout/refactoring/testdata/aapterror2.xml b/eclipse/plugins/com.android.ide.eclipse.tests/src/com/android/ide/eclipse/adt/internal/editors/layout/refactoring/testdata/aapterror2.xml
new file mode 100644
index 0000000..b720daa
--- /dev/null
+++ b/eclipse/plugins/com.android.ide.eclipse.tests/src/com/android/ide/eclipse/adt/internal/editors/layout/refactoring/testdata/aapterror2.xml
@@ -0,0 +1,9 @@
+<?xml version="1.0" encoding="utf-8"?>
+<resources xmlns:android="http://schemas.android.com/apk/res/android">
+ <style name="repeatedStyle1">
+ <item name="android:gravity">left</item>
+ </style>
+ <style name="repeatedStyle1">
+ <item name="android:gravity">bottom</item>
+ </style>
+</resources>
diff --git a/eclipse/plugins/com.android.ide.eclipse.tests/src/com/android/ide/eclipse/adt/internal/editors/layout/refactoring/testdata/aapterror3.xml b/eclipse/plugins/com.android.ide.eclipse.tests/src/com/android/ide/eclipse/adt/internal/editors/layout/refactoring/testdata/aapterror3.xml
new file mode 100644
index 0000000..bc9c134
--- /dev/null
+++ b/eclipse/plugins/com.android.ide.eclipse.tests/src/com/android/ide/eclipse/adt/internal/editors/layout/refactoring/testdata/aapterror3.xml
@@ -0,0 +1,6 @@
+<?xml version="1.0" encoding="utf-8"?>
+<resources xmlns:android="http://schemas.android.com/apk/res/android">
+ <style name="wrongAttribute">
+ <item name="nonexistent">5</item>
+ </style>
+</resources>
diff --git a/eclipse/plugins/com.android.ide.eclipse.tests/src/com/android/ide/eclipse/adt/internal/editors/layout/refactoring/testdata/aapterror4.xml b/eclipse/plugins/com.android.ide.eclipse.tests/src/com/android/ide/eclipse/adt/internal/editors/layout/refactoring/testdata/aapterror4.xml
new file mode 100644
index 0000000..28dd467
--- /dev/null
+++ b/eclipse/plugins/com.android.ide.eclipse.tests/src/com/android/ide/eclipse/adt/internal/editors/layout/refactoring/testdata/aapterror4.xml
@@ -0,0 +1,7 @@
+<?xml version="1.0" encoding="utf-8"?>
+<resources>
+ <style>
+ <item />
+ </style>
+ <item></item>
+</resources>
diff --git a/eclipse/plugins/com.android.ide.eclipse.tests/src/com/android/ide/eclipse/adt/internal/editors/layout/refactoring/testdata/aapterror5.xml b/eclipse/plugins/com.android.ide.eclipse.tests/src/com/android/ide/eclipse/adt/internal/editors/layout/refactoring/testdata/aapterror5.xml
new file mode 100644
index 0000000..ee89ac4
--- /dev/null
+++ b/eclipse/plugins/com.android.ide.eclipse.tests/src/com/android/ide/eclipse/adt/internal/editors/layout/refactoring/testdata/aapterror5.xml
@@ -0,0 +1,6 @@
+<?xml version="1.0" encoding="utf-8"?>
+<resources>
+ <style name="test">
+ <item name="android:layout_width"></item>
+ </style>
+</resources>
diff --git a/eclipse/plugins/com.android.ide.eclipse.tests/src/com/android/ide/eclipse/adt/internal/editors/layout/refactoring/testdata/aapterror6.xml b/eclipse/plugins/com.android.ide.eclipse.tests/src/com/android/ide/eclipse/adt/internal/editors/layout/refactoring/testdata/aapterror6.xml
new file mode 100644
index 0000000..e552ff7
--- /dev/null
+++ b/eclipse/plugins/com.android.ide.eclipse.tests/src/com/android/ide/eclipse/adt/internal/editors/layout/refactoring/testdata/aapterror6.xml
@@ -0,0 +1,11 @@
+<FrameLayout
+ xmlns:android="http://schemas.android.com/apk/res/android"
+ android:layout_width="wrap_content"
+ android:layout_height="match_parent">
+ <TextView
+ android:layout_width="fill_parent"
+ android:layout_height="wrap_content"
+ android:layout_marginTop=""
+ android:layout_marginLeft=''
+ />
+</FrameLayout>
diff --git a/eclipse/plugins/com.android.ide.eclipse.tests/src/com/android/ide/eclipse/adt/internal/editors/layout/refactoring/testdata/aapterror7.xml b/eclipse/plugins/com.android.ide.eclipse.tests/src/com/android/ide/eclipse/adt/internal/editors/layout/refactoring/testdata/aapterror7.xml
new file mode 100644
index 0000000..d47f4ae
--- /dev/null
+++ b/eclipse/plugins/com.android.ide.eclipse.tests/src/com/android/ide/eclipse/adt/internal/editors/layout/refactoring/testdata/aapterror7.xml
@@ -0,0 +1,10 @@
+<FrameLayout
+ xmlns:android="http://schemas.android.com/apk/res/android"
+ android:layout_width="wrap_content"
+ android:layout_height="match_parent">
+ <TextView
+ android:id=""
+ android:layout_width="fill_parent"
+ android:layout_height="wrap_content"
+ />
+</FrameLayout>