GP-3760 - Annotations - Updated annotation display to not render escape

characters for braces
This commit is contained in:
dragonmacher
2023-08-21 15:29:51 -04:00
parent a44c711987
commit 7de0454432
8 changed files with 186 additions and 50 deletions
@@ -56,7 +56,7 @@ public class AddressAnnotatedStringHandler implements AnnotatedStringHandler {
String addressText = address.toString();
if (text.length > 2) { // address and display text
StringBuffer buffer = new StringBuffer();
StringBuilder buffer = new StringBuilder();
for (int i = 2; i < text.length; i++) {
buffer.append(text[i]).append(" ");
}
@@ -150,7 +150,7 @@ public class Annotation {
}
private String[] parseAnnotationText(String theAnnotationText) {
StringBuffer buffer = new StringBuffer(theAnnotationText);
StringBuilder buffer = new StringBuilder(theAnnotationText);
// strip off the brackets
buffer.delete(0, 2); // remove '{' and '@'
@@ -196,8 +196,12 @@ public class Annotation {
return annotationText;
}
@Override
public String toString() {
return annotationText;
}
/*package*/ static Set<String> getAnnotationNames() {
return Collections.unmodifiableSet(getAnnotatedStringHandlerMap().keySet());
}
}
@@ -0,0 +1,44 @@
/* ###
* IP: GHIDRA
*
* 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 ghidra.app.util.viewer.field;
import docking.widgets.fieldpanel.field.AbstractTextFieldElement;
import ghidra.util.bean.field.AnnotatedTextFieldElement;
public class AnnotationCommentPart extends CommentPart {
private Annotation annotation;
AnnotationCommentPart(String displayText, Annotation annotation) {
super(displayText);
this.annotation = annotation;
}
@Override
String getRawText() {
return annotation.getAnnotationText();
}
@Override
AbstractTextFieldElement createElement(int row, int column) {
return new AnnotatedTextFieldElement(annotation, row, column);
}
@Override
public String toString() {
return annotation.toString();
}
}
@@ -0,0 +1,35 @@
/* ###
* IP: GHIDRA
*
* 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 ghidra.app.util.viewer.field;
import docking.widgets.fieldpanel.field.AbstractTextFieldElement;
public abstract class CommentPart {
protected String displayText;
CommentPart(String displayText) {
this.displayText = displayText;
}
abstract AbstractTextFieldElement createElement(int row, int column);
abstract String getRawText();
String getDisplayText() {
return displayText;
}
}
@@ -31,8 +31,6 @@ import generic.theme.Gui;
import ghidra.program.model.listing.Program;
import ghidra.util.StringUtilities;
import ghidra.util.WordLocation;
import ghidra.util.bean.field.AnnotatedTextFieldElement;
import ghidra.util.exception.AssertException;
public class CommentUtils {
@@ -75,21 +73,10 @@ public class CommentUtils {
};
StringBuilder buffy = new StringBuilder();
List<Object> parts =
List<CommentPart> parts =
doParseTextIntoTextAndAnnotations(rawCommentText, symbolFixer, program, prototype);
for (Object part : parts) {
if (part instanceof String) {
String s = (String) part;
buffy.append(s);
}
else if (part instanceof Annotation) {
Annotation a = (Annotation) part;
buffy.append(a.getAnnotationText());
}
else {
throw new AssertException("Unhandled annotation piece: " + part);
}
for (CommentPart part : parts) {
buffy.append(part.getRawText());
}
return buffy.toString();
}
@@ -136,7 +123,7 @@ public class CommentUtils {
Function<Annotation, Annotation> noFixing = Function.identity();
return doParseTextForAnnotations(text, noFixing, program, prototypeString, row);
}
/**
* Sanitizes the given text, removing or replacing illegal characters.
* <p>
@@ -175,25 +162,12 @@ public class CommentUtils {
text = StringUtilities.convertTabsToSpaces(text);
int column = 0;
List<Object> parts =
List<CommentPart> parts =
doParseTextIntoTextAndAnnotations(text, fixerUpper, program, prototype);
List<FieldElement> fields = new ArrayList<>();
for (Object part : parts) {
if (part instanceof String) {
String s = (String) part;
AttributedString as = prototype.deriveAttributedString(s);
fields.add(new TextFieldElement(as, row, column));
column += s.length();
}
else if (part instanceof Annotation) {
Annotation a = (Annotation) part;
fields.add(new AnnotatedTextFieldElement(a, row, column));
column += a.getAnnotationText().length();
}
else {
throw new AssertException("Unhandled annotation piece: " + part);
}
for (CommentPart part : parts) {
fields.add(part.createElement(row, column));
column += part.getDisplayText().length();
}
return new CompositeFieldElement(fields.toArray(new FieldElement[fields.size()]));
@@ -204,21 +178,21 @@ public class CommentUtils {
* an Annotation
*
* @param text the text to parse
* @param fixerUpper a function that is given a chance to convert an Annotation into a new
* one
* @param fixerUpper a function that is given a chance to convert an Annotation into a new one
* @param program the program
* @param prototype the prototype string that contains decoration attributes
* @return a list that contains a mixture String or an Annotation entries
*/
private static List<Object> doParseTextIntoTextAndAnnotations(String text,
private static List<CommentPart> doParseTextIntoTextAndAnnotations(String text,
Function<Annotation, Annotation> fixerUpper, Program program,
AttributedString prototype) {
List<Object> results = new ArrayList<>();
List<CommentPart> results = new ArrayList<>();
List<WordLocation> annotations = getCommentAnnotations(text);
if (annotations.isEmpty()) {
results.add(text);
String updatedText = removeEscapeChars(text);
results.add(new StringCommentPart(text, updatedText, prototype));
return results;
}
@@ -230,19 +204,23 @@ public class CommentUtils {
if (offset != start) {
// text between annotations
String preceeding = text.substring(offset, start);
results.add(preceeding);
String updatedText = removeEscapeChars(preceeding);
results.add(new StringCommentPart(preceeding, updatedText, prototype));
}
String annotationText = word.getWord();
Annotation annotation = new Annotation(annotationText, prototype, program);
String updatedText = removeEscapeChars(annotationText);
Annotation annotation = new Annotation(updatedText, prototype, program);
annotation = fixerUpper.apply(annotation);
results.add(annotation);
results.add(new AnnotationCommentPart(updatedText, annotation));
offset = start + annotationText.length();
}
if (offset != text.length()) { // trailing text
results.add(text.substring(offset));
String trailing = text.substring(offset);
String updatedText = removeEscapeChars(trailing);
results.add(new StringCommentPart(trailing, updatedText, prototype));
}
return results;
@@ -291,6 +269,23 @@ public class CommentUtils {
//@formatter:on
}
// remove any backslashes that escape special annotation characters, like '{' and '}'
private static String removeEscapeChars(String text) {
StringBuilder buffy = new StringBuilder();
for (int i = 0; i < text.length(); i++) {
char c = text.charAt(i);
if (c == '\\') {
char next = i == text.length() ? '\0' : text.charAt(i + 1);
if (next == '{' || next == '}') {
continue;
}
}
buffy.append(c);
}
return buffy.toString();
}
/*
* Starts at the given index and looks for the end an annotation, ignoring quoted text
* and escaped characters along the way. The value returned is the index after the last
@@ -0,0 +1,50 @@
/* ###
* IP: GHIDRA
*
* 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 ghidra.app.util.viewer.field;
import docking.widgets.fieldpanel.field.*;
public class StringCommentPart extends CommentPart {
private AttributedString prototype;
private String rawText;
StringCommentPart(String rawText, AttributedString prototype) {
this(rawText, rawText, prototype);
}
StringCommentPart(String rawText, String displayText, AttributedString prototype) {
super(displayText);
this.rawText = rawText;
this.prototype = prototype;
}
@Override
String getRawText() {
return rawText;
}
@Override
AbstractTextFieldElement createElement(int row, int column) {
AttributedString as = prototype.deriveAttributedString(displayText);
return new TextFieldElement(as, row, column);
}
@Override
public String toString() {
return rawText;
}
}
@@ -201,14 +201,21 @@ public class AnnotationTest extends AbstractGhidraHeadedIntegrationTest {
public void testSymbolAnnotation_WithBracesInName_Escaped() {
String rawComment = "This is a symbol {@sym mySym\\{0\\}} annotation";
String display = CommentUtils.getDisplayString(rawComment, program);
assertEquals("This is a symbol mySym\\{0\\} annotation", display);
assertEquals("This is a symbol mySym{0} annotation", display);
}
@Test
public void testSymbolAnnotation_WithEscapedItemsOutsideOfAnnotation() {
String rawComment = "This is a foo\\} symbol {@sym mySym\\{0\\}} annotation \\{bar";
String display = CommentUtils.getDisplayString(rawComment, program);
assertEquals("This is a foo} symbol mySym{0} annotation {bar", display);
}
@Test
public void testSymbolAnnotation_FullyEscaped() {
String rawComment = "This is a symbol \\{@sym bob\\} annotation";
String display = CommentUtils.getDisplayString(rawComment, program);
assertEquals(rawComment, display);
assertEquals("This is a symbol {@sym bob} annotation", display);
}
@Test
@@ -106,11 +106,12 @@ public class CommentUtilsTest extends AbstractGhidraHeadlessIntegrationTest {
assertEquals(1, annotations.size());
WordLocation word = annotations.get(0);
assertEquals("{@symbol symbol\\{Name\\}}", word.getWord());
}
@Test
public void testSanitize() {
String comment = null;
String sanitized = CommentUtils.sanitize(comment);
assertNull(sanitized);