summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
-rw-r--r--logcat/gen/com/android/tools/idea/logcat/filters/parser/LogcatFilterLexer.java131
-rw-r--r--logcat/src/com/android/tools/idea/logcat/LogcatToolWindowFactory.kt6
-rw-r--r--logcat/src/com/android/tools/idea/logcat/filters/FilterTextField.kt58
-rw-r--r--logcat/src/com/android/tools/idea/logcat/filters/LogcatFilter.kt12
-rw-r--r--logcat/src/com/android/tools/idea/logcat/filters/LogcatFilterCompletionContributor.kt6
-rw-r--r--logcat/src/com/android/tools/idea/logcat/filters/LogcatFilterParser.kt40
-rw-r--r--logcat/src/com/android/tools/idea/logcat/filters/parser/LogcatFilter.flex1
-rw-r--r--logcat/src/com/android/tools/idea/logcat/filters/parser/LogcatFilterLexerWrapper.kt1
-rw-r--r--logcat/testSrc/com/android/tools/idea/logcat/filters/FilterTextFieldTest.kt63
-rw-r--r--logcat/testSrc/com/android/tools/idea/logcat/filters/LogcatFilterCompletionContributorTest.kt3
-rw-r--r--logcat/testSrc/com/android/tools/idea/logcat/filters/LogcatFilterParserTest.kt66
-rw-r--r--logcat/testSrc/com/android/tools/idea/logcat/filters/LogcatFilterTest.kt42
-rw-r--r--logcat/testSrc/com/android/tools/idea/logcat/filters/parser/LogcatFilterPsiTest.kt2
13 files changed, 341 insertions, 90 deletions
diff --git a/logcat/gen/com/android/tools/idea/logcat/filters/parser/LogcatFilterLexer.java b/logcat/gen/com/android/tools/idea/logcat/filters/parser/LogcatFilterLexer.java
index b76bcc79611..ec65e4b4cf8 100644
--- a/logcat/gen/com/android/tools/idea/logcat/filters/parser/LogcatFilterLexer.java
+++ b/logcat/gen/com/android/tools/idea/logcat/filters/parser/LogcatFilterLexer.java
@@ -120,13 +120,13 @@ class LogcatFilterLexer implements FlexLexer {
private static final String ZZ_ACTION_PACKED_0 =
"\4\0\1\1\1\2\1\1\1\3\1\4\1\5\1\6"+
- "\2\7\6\1\1\10\3\7\1\11\2\7\1\12\2\1"+
- "\1\0\1\1\3\0\7\1\1\10\1\0\1\10\3\0"+
+ "\2\7\7\1\1\10\3\7\1\11\2\7\1\12\2\1"+
+ "\1\0\1\1\3\0\10\1\1\10\1\0\1\10\3\0"+
"\1\11\1\0\1\11\3\0\4\1\1\13\3\1\2\10"+
"\2\11\2\1\1\14\3\1\1\15";
private static int [] zzUnpackAction() {
- int [] result = new int[72];
+ int [] result = new int[74];
int offset = 0;
offset = zzUnpackAction(ZZ_ACTION_PACKED_0, offset, result);
return result;
@@ -153,16 +153,17 @@ class LogcatFilterLexer implements FlexLexer {
private static final String ZZ_ROWMAP_PACKED_0 =
"\0\0\0\33\0\66\0\121\0\154\0\207\0\242\0\154"+
"\0\154\0\275\0\275\0\330\0\363\0\u010e\0\u0129\0\u0144"+
- "\0\u015f\0\u017a\0\u0195\0\u01b0\0\275\0\u01cb\0\u01e6\0\u0201"+
- "\0\u021c\0\u0237\0\u0252\0\u026d\0\u0288\0\330\0\275\0\u02a3"+
- "\0\363\0\u02be\0\u02d9\0\u02f4\0\u030f\0\u032a\0\u0345\0\u0360"+
- "\0\u037b\0\u0396\0\u01cb\0\275\0\u03b1\0\u01e6\0\u03cc\0\u03e7"+
- "\0\u021c\0\275\0\u0402\0\u0237\0\u041d\0\330\0\363\0\u0438"+
- "\0\u0453\0\154\0\u046e\0\u0489\0\u04a4\0\u01cb\0\u01e6\0\u021c"+
- "\0\u0237\0\u04bf\0\u04da\0\154\0\u04f5\0\u0510\0\u052b\0\154";
+ "\0\u015f\0\u017a\0\u0195\0\u01b0\0\u01cb\0\275\0\u01e6\0\u0201"+
+ "\0\u021c\0\u0237\0\u0252\0\u026d\0\u0288\0\u02a3\0\330\0\275"+
+ "\0\u02be\0\363\0\u02d9\0\u02f4\0\u030f\0\u032a\0\u0345\0\u0360"+
+ "\0\u037b\0\u0396\0\u03b1\0\u03cc\0\u01e6\0\275\0\u03e7\0\u0201"+
+ "\0\u0402\0\u041d\0\u0237\0\275\0\u0438\0\u0252\0\u0453\0\330"+
+ "\0\363\0\u046e\0\u0489\0\154\0\u04a4\0\u04bf\0\u04da\0\u01e6"+
+ "\0\u0201\0\u0237\0\u0252\0\u04f5\0\u0510\0\154\0\u052b\0\u0546"+
+ "\0\u0561\0\154";
private static int [] zzUnpackRowMap() {
- int [] result = new int[72];
+ int [] result = new int[74];
int offset = 0;
offset = zzUnpackRowMap(ZZ_ROWMAP_PACKED_0, offset, result);
return result;
@@ -186,58 +187,60 @@ class LogcatFilterLexer implements FlexLexer {
private static final String ZZ_TRANS_PACKED_0 =
"\1\5\1\6\1\5\1\7\2\5\1\10\1\11\1\12"+
- "\1\13\1\14\1\5\1\6\1\15\1\16\1\17\2\5"+
- "\1\20\1\5\1\21\1\5\1\22\2\5\1\23\1\5"+
- "\1\24\1\6\6\24\2\25\1\26\1\24\1\6\1\27"+
- "\15\24\1\30\1\6\6\30\2\25\1\31\1\30\1\6"+
- "\1\32\15\30\1\33\1\6\12\33\1\6\16\33\1\5"+
- "\1\0\6\5\2\0\1\5\1\34\1\0\16\5\1\0"+
- "\1\6\12\0\1\6\16\0\1\5\1\0\6\5\2\0"+
- "\1\5\1\34\1\0\1\5\1\35\3\5\1\20\3\5"+
- "\1\22\2\5\1\23\1\5\33\0\12\36\1\37\1\40"+
- "\17\36\13\41\1\42\1\41\1\37\15\41\1\5\1\0"+
- "\6\5\2\0\1\5\1\34\1\0\2\5\1\43\1\5"+
- "\1\44\12\5\1\0\6\5\2\0\1\5\1\34\1\0"+
- "\6\5\1\45\10\5\1\0\6\5\2\0\1\5\1\34"+
- "\1\0\4\5\1\46\12\5\1\0\6\5\2\0\1\5"+
- "\1\34\1\0\10\5\1\47\6\5\1\0\6\5\2\0"+
- "\1\5\1\34\1\0\7\5\1\50\7\5\1\0\6\5"+
- "\2\0\1\5\1\34\1\0\7\5\1\51\6\5\1\24"+
- "\1\0\6\24\2\0\1\24\1\52\1\0\16\24\12\53"+
- "\1\54\1\55\17\53\13\56\1\57\1\56\1\54\15\56"+
- "\1\30\1\0\6\30\2\0\1\30\1\60\1\0\16\30"+
- "\12\61\1\62\1\63\17\61\13\64\1\65\1\64\1\62"+
- "\15\64\1\33\1\0\12\33\1\0\16\33\1\5\1\0"+
- "\6\5\2\0\1\5\1\34\20\5\1\0\6\5\2\0"+
- "\1\5\1\34\1\0\2\5\1\43\13\5\12\36\1\66"+
- "\1\40\17\36\13\41\1\42\1\41\1\67\15\41\1\5"+
- "\1\0\6\5\2\0\1\5\1\34\1\0\3\5\1\70"+
- "\13\5\1\0\6\5\2\0\1\5\1\34\1\0\15\5"+
- "\1\71\1\5\1\0\1\72\5\5\2\0\1\5\1\34"+
- "\1\0\17\5\1\0\6\5\2\0\1\5\1\34\1\0"+
- "\6\5\1\73\10\5\1\0\6\5\2\0\1\5\1\34"+
- "\1\0\4\5\1\45\12\5\1\0\6\5\2\0\1\5"+
- "\1\34\1\0\12\5\1\74\4\5\1\0\6\5\2\0"+
- "\1\5\1\34\1\0\10\5\1\75\5\5\1\24\1\0"+
- "\6\24\2\0\1\24\1\52\17\24\12\53\1\76\1\55"+
- "\17\53\13\56\1\57\1\56\1\77\15\56\1\30\1\0"+
- "\6\30\2\0\1\30\1\60\17\30\12\61\1\100\1\63"+
- "\17\61\13\64\1\65\1\64\1\101\15\64\1\5\1\0"+
- "\6\5\2\0\1\5\1\34\1\0\4\5\1\75\12\5"+
- "\1\0\6\5\2\0\1\5\1\34\1\0\4\5\1\102"+
- "\12\5\1\0\6\5\2\0\1\5\1\34\1\0\6\5"+
- "\1\103\10\5\1\0\6\5\2\0\1\5\1\34\1\0"+
- "\13\5\1\103\3\5\1\0\1\104\1\5\1\105\1\106"+
- "\2\5\2\0\1\5\1\34\1\0\17\5\1\0\6\5"+
- "\2\0\1\5\1\34\1\0\1\5\1\45\15\5\1\0"+
- "\6\5\2\0\1\5\1\34\1\0\7\5\1\107\7\5"+
- "\1\0\1\110\5\5\2\0\1\5\1\34\1\0\17\5"+
- "\1\0\1\104\5\5\2\0\1\5\1\34\1\0\17\5"+
- "\1\0\6\5\2\0\1\5\1\34\1\0\10\5\1\70"+
- "\5\5";
+ "\1\13\1\14\1\5\1\6\1\15\1\16\1\17\1\20"+
+ "\1\5\1\21\1\5\1\22\1\5\1\23\2\5\1\24"+
+ "\1\5\1\25\1\6\6\25\2\26\1\27\1\25\1\6"+
+ "\1\30\15\25\1\31\1\6\6\31\2\26\1\32\1\31"+
+ "\1\6\1\33\15\31\1\34\1\6\12\34\1\6\16\34"+
+ "\1\5\1\0\6\5\2\0\1\5\1\35\1\0\16\5"+
+ "\1\0\1\6\12\0\1\6\16\0\1\5\1\0\6\5"+
+ "\2\0\1\5\1\35\1\0\1\5\1\36\3\5\1\21"+
+ "\3\5\1\23\2\5\1\24\1\5\33\0\12\37\1\40"+
+ "\1\41\17\37\13\42\1\43\1\42\1\40\15\42\1\5"+
+ "\1\0\6\5\2\0\1\5\1\35\1\0\2\5\1\44"+
+ "\1\5\1\45\12\5\1\0\6\5\2\0\1\5\1\35"+
+ "\1\0\6\5\1\46\10\5\1\0\6\5\2\0\1\5"+
+ "\1\35\1\0\7\5\1\47\7\5\1\0\6\5\2\0"+
+ "\1\5\1\35\1\0\4\5\1\50\12\5\1\0\6\5"+
+ "\2\0\1\5\1\35\1\0\10\5\1\51\6\5\1\0"+
+ "\6\5\2\0\1\5\1\35\1\0\7\5\1\52\7\5"+
+ "\1\0\6\5\2\0\1\5\1\35\1\0\7\5\1\53"+
+ "\6\5\1\25\1\0\6\25\2\0\1\25\1\54\1\0"+
+ "\16\25\12\55\1\56\1\57\17\55\13\60\1\61\1\60"+
+ "\1\56\15\60\1\31\1\0\6\31\2\0\1\31\1\62"+
+ "\1\0\16\31\12\63\1\64\1\65\17\63\13\66\1\67"+
+ "\1\66\1\64\15\66\1\34\1\0\12\34\1\0\16\34"+
+ "\1\5\1\0\6\5\2\0\1\5\1\35\20\5\1\0"+
+ "\6\5\2\0\1\5\1\35\1\0\2\5\1\44\13\5"+
+ "\12\37\1\70\1\41\17\37\13\42\1\43\1\42\1\71"+
+ "\15\42\1\5\1\0\6\5\2\0\1\5\1\35\1\0"+
+ "\3\5\1\72\13\5\1\0\6\5\2\0\1\5\1\35"+
+ "\1\0\15\5\1\73\1\5\1\0\1\74\5\5\2\0"+
+ "\1\5\1\35\1\0\17\5\1\0\6\5\2\0\1\5"+
+ "\1\35\1\0\5\5\1\51\11\5\1\0\6\5\2\0"+
+ "\1\5\1\35\1\0\6\5\1\75\10\5\1\0\6\5"+
+ "\2\0\1\5\1\35\1\0\4\5\1\46\12\5\1\0"+
+ "\6\5\2\0\1\5\1\35\1\0\12\5\1\76\4\5"+
+ "\1\0\6\5\2\0\1\5\1\35\1\0\10\5\1\77"+
+ "\5\5\1\25\1\0\6\25\2\0\1\25\1\54\17\25"+
+ "\12\55\1\100\1\57\17\55\13\60\1\61\1\60\1\101"+
+ "\15\60\1\31\1\0\6\31\2\0\1\31\1\62\17\31"+
+ "\12\63\1\102\1\65\17\63\13\66\1\67\1\66\1\103"+
+ "\15\66\1\5\1\0\6\5\2\0\1\5\1\35\1\0"+
+ "\4\5\1\77\12\5\1\0\6\5\2\0\1\5\1\35"+
+ "\1\0\4\5\1\104\12\5\1\0\6\5\2\0\1\5"+
+ "\1\35\1\0\6\5\1\105\10\5\1\0\6\5\2\0"+
+ "\1\5\1\35\1\0\13\5\1\105\3\5\1\0\1\106"+
+ "\1\5\1\107\1\110\2\5\2\0\1\5\1\35\1\0"+
+ "\17\5\1\0\6\5\2\0\1\5\1\35\1\0\1\5"+
+ "\1\46\15\5\1\0\6\5\2\0\1\5\1\35\1\0"+
+ "\7\5\1\111\7\5\1\0\1\112\5\5\2\0\1\5"+
+ "\1\35\1\0\17\5\1\0\1\106\5\5\2\0\1\5"+
+ "\1\35\1\0\17\5\1\0\6\5\2\0\1\5\1\35"+
+ "\1\0\10\5\1\72\5\5";
private static int [] zzUnpackTrans() {
- int [] result = new int[1350];
+ int [] result = new int[1404];
int offset = 0;
offset = zzUnpackTrans(ZZ_TRANS_PACKED_0, offset, result);
return result;
@@ -275,12 +278,12 @@ class LogcatFilterLexer implements FlexLexer {
private static final int [] ZZ_ATTRIBUTE = zzUnpackAttribute();
private static final String ZZ_ATTRIBUTE_PACKED_0 =
- "\4\0\5\1\2\11\11\1\1\11\10\1\1\0\1\11"+
- "\3\0\10\1\1\0\1\11\3\0\1\1\1\0\1\11"+
+ "\4\0\5\1\2\11\12\1\1\11\10\1\1\0\1\11"+
+ "\3\0\11\1\1\0\1\11\3\0\1\1\1\0\1\11"+
"\3\0\23\1";
private static int [] zzUnpackAttribute() {
- int [] result = new int[72];
+ int [] result = new int[74];
int offset = 0;
offset = zzUnpackAttribute(ZZ_ATTRIBUTE_PACKED_0, offset, result);
return result;
diff --git a/logcat/src/com/android/tools/idea/logcat/LogcatToolWindowFactory.kt b/logcat/src/com/android/tools/idea/logcat/LogcatToolWindowFactory.kt
index 7664ebca2a4..b3b2c2779bc 100644
--- a/logcat/src/com/android/tools/idea/logcat/LogcatToolWindowFactory.kt
+++ b/logcat/src/com/android/tools/idea/logcat/LogcatToolWindowFactory.kt
@@ -57,6 +57,12 @@ internal class LogcatToolWindowFactory : SplittingTabsToolWindowFactory(), DumbA
ProcessNameMonitor.getInstance(project).start()
}
+ override fun createToolWindowContent(project: Project, toolWindow: ToolWindow) {
+ super.createToolWindowContent(project, toolWindow)
+ toolWindow.isAvailable = true
+ toolWindow.setToHideOnEmptyContent(true)
+ }
+
private fun showLogcat(toolWindow: ToolWindowEx, serialNumber: String) {
EventQueue.invokeLater {
toolWindow.activate {
diff --git a/logcat/src/com/android/tools/idea/logcat/filters/FilterTextField.kt b/logcat/src/com/android/tools/idea/logcat/filters/FilterTextField.kt
index 7ec3e8843d8..67defa4d5dc 100644
--- a/logcat/src/com/android/tools/idea/logcat/filters/FilterTextField.kt
+++ b/logcat/src/com/android/tools/idea/logcat/filters/FilterTextField.kt
@@ -22,6 +22,7 @@ import com.android.tools.idea.logcat.LogcatBundle
import com.android.tools.idea.logcat.LogcatPresenter
import com.android.tools.idea.logcat.PACKAGE_NAMES_PROVIDER_KEY
import com.android.tools.idea.logcat.TAGS_PROVIDER_KEY
+import com.android.tools.idea.logcat.filters.FilterTextField.FilterHistoryItem.Hint
import com.android.tools.idea.logcat.filters.FilterTextField.FilterHistoryItem.Item
import com.android.tools.idea.logcat.filters.FilterTextField.FilterHistoryItem.Separator
import com.android.tools.idea.logcat.filters.parser.LogcatFilterFileType
@@ -38,11 +39,17 @@ import com.intellij.openapi.application.ApplicationManager
import com.intellij.openapi.editor.event.DocumentEvent
import com.intellij.openapi.editor.event.DocumentListener
import com.intellij.openapi.editor.ex.EditorEx
+import com.intellij.openapi.editor.markup.TextAttributes
import com.intellij.openapi.project.Project
+import com.intellij.openapi.ui.asSequence
+import com.intellij.openapi.ui.popup.JBPopupFactory
import com.intellij.openapi.ui.popup.PopupChooserBuilder
import com.intellij.openapi.util.Disposer
import com.intellij.ui.CollectionListModel
import com.intellij.ui.EditorTextField
+import com.intellij.ui.JBColor.BLUE
+import com.intellij.ui.SimpleColoredComponent
+import com.intellij.ui.SimpleTextAttributes
import com.intellij.ui.components.JBList
import com.intellij.util.ui.EmptyIcon
import com.intellij.util.ui.JBUI
@@ -99,6 +106,8 @@ private val HISTORY_ICON_BORDER = JBUI.Borders.empty(0, 5, 0, 4)
private val HISTORY_LIST_SEPARATOR_BORDER = JBUI.Borders.empty(3)
+private val NAMED_FILTER_HISTORY_ITEM_COLOR = SimpleTextAttributes.fromTextAttributes(TextAttributes(BLUE, null, null, null, Font.PLAIN))
+
/**
* A text field for the filter.
*/
@@ -245,7 +254,9 @@ internal class FilterTextField(
val popupDisposable = Disposer.newDisposable("popupDisposable")
addToHistory()
- val popup = PopupChooserBuilder(HistoryList(popupDisposable, logcatPresenter, filterHistory))
+ val list: JList<FilterHistoryItem> = HistoryList(popupDisposable, logcatPresenter, filterHistory, filterParser)
+ JBPopupFactory.getInstance().createPopupChooserBuilder(listOf("foo", "bar"))
+ val popup = PopupChooserBuilder(list)
.setMovable(false)
.setRequestFocus(true)
.setItemChosenCallback {
@@ -254,7 +265,7 @@ internal class FilterTextField(
isFavorite = item.isFavorite
}
}
- .setSelectedValue(Item(text, isFavorite, count = null), true)
+ .setSelectedValue(Item(text, isFavorite, count = null, filterParser), true)
.createPopup()
Disposer.register(popup, popupDisposable)
@@ -334,16 +345,19 @@ internal class FilterTextField(
parentDisposable: Disposable,
logcatPresenter: LogcatPresenter,
filterHistory: AndroidLogcatFilterHistory,
+ filterParser: LogcatFilterParser,
coroutineContext: CoroutineContext = EmptyCoroutineContext,
) : JBList<FilterHistoryItem>() {
init {
// The "count" field in FilterHistoryItem.Item takes time to calculate so initially, add all items with no count.
val items = mutableListOf<FilterHistoryItem>().apply {
- addAll(filterHistory.favorites.map { Item(filter = it, isFavorite = true, count = null) })
+ addAll(filterHistory.favorites.map { Item(filter = it, isFavorite = true, count = null, filterParser) })
if (filterHistory.favorites.isNotEmpty() && filterHistory.nonFavorites.isNotEmpty()) {
add(Separator)
}
- addAll(filterHistory.nonFavorites.map { Item(filter = it, isFavorite = false, count = null) })
+ addAll(filterHistory.nonFavorites.map { Item(filter = it, isFavorite = false, count = null, filterParser) })
+ add(Separator)
+ add(Hint)
}
val listModel = CollectionListModel(items)
model = listModel
@@ -370,7 +384,7 @@ internal class FilterTextField(
// Replacing an item in the model will remove the selection. Save the selected index, so we can restore it after.
withContext(uiThread) {
val selected = selectedIndex
- listModel.setElementAt(Item(item.filter, item.isFavorite, count), index)
+ listModel.setElementAt(Item(item.filter, item.isFavorite, count, filterParser), index)
if (selected >= 0) {
selectedIndex = selected
}
@@ -399,12 +413,25 @@ internal class FilterTextField(
*/
@VisibleForTesting
internal sealed class FilterHistoryItem {
- class Item(val filter: String, val isFavorite: Boolean, val count: Int?)
+ class Item(val filter: String, val isFavorite: Boolean, val count: Int?, private val filterParser: LogcatFilterParser)
: FilterHistoryItem() {
+ private val filterName = filterParser.parse(filter)?.getFilterName()
+
override fun getComponent(isSelected: Boolean, list: JList<out FilterHistoryItem>): JComponent {
+ filterLabel.clear()
+ if (filterName != null) {
+ // If there is more than one Item with the same filterName, show the name and the filter.
+ val sameName = list.model.asSequence().filterIsInstance<Item>().count { it.filterName == filterName }
+ filterLabel.append(filterName, NAMED_FILTER_HISTORY_ITEM_COLOR)
+ if (sameName > 1) {
+ filterLabel.append(": ${filterParser.removeFilterNames(filter)}")
+ }
+ }
+ else {
+ filterLabel.append(filter)
+ }
favoriteLabel.icon = if (isFavorite) FAVORITE_ON_ICON else FAVORITE_BLANK_ICON
- filterLabel.text = filter
countLabel.text = when (count) {
null -> " ".repeat(3)
in 0..99 -> "% 2d ".format(count)
@@ -445,7 +472,7 @@ internal class FilterTextField(
// The common pattern is to reuse the same component for all the items rather than allocate a new one for each item.
companion object {
private val favoriteLabel = JLabel()
- private val filterLabel = JLabel().apply {
+ private val filterLabel = SimpleColoredComponent().apply {
border = HISTORY_ITEM_LABEL_BORDER
}
@@ -478,6 +505,21 @@ internal class FilterTextField(
}
}
+ object Hint : FilterHistoryItem() {
+ // A standalone JLabel here will change the background of the separator when it is selected. Wrapping it with a JPanel
+ // suppresses that behavior for some reason.
+ private val component = JPanel().apply {
+ add(JLabel("Press Delete to remove an item").apply {
+ foreground = SimpleTextAttributes.GRAYED_ATTRIBUTES.fgColor
+ })
+ }
+
+ override fun getComponent(isSelected: Boolean, list: JList<out FilterHistoryItem>): JPanel {
+ component.background = list.background
+ return component
+ }
+ }
+
abstract fun getComponent(isSelected: Boolean, list: JList<out FilterHistoryItem>): JComponent
}
}
diff --git a/logcat/src/com/android/tools/idea/logcat/filters/LogcatFilter.kt b/logcat/src/com/android/tools/idea/logcat/filters/LogcatFilter.kt
index 9207547305b..27b7f3d74db 100644
--- a/logcat/src/com/android/tools/idea/logcat/filters/LogcatFilter.kt
+++ b/logcat/src/com/android/tools/idea/logcat/filters/LogcatFilter.kt
@@ -55,6 +55,8 @@ internal interface LogcatFilter {
fun matches(message: LogcatMessageWrapper): Boolean
+ open fun getFilterName(): String? = null
+
companion object {
const val MY_PACKAGE = "package:mine"
}
@@ -68,6 +70,8 @@ internal data class AndLogcatFilter(val filters: List<LogcatFilter>) : LogcatFil
}
override fun matches(message: LogcatMessageWrapper) = filters.all { it.matches(message) }
+
+ override fun getFilterName(): String? = filters.mapNotNull { it.getFilterName() }.lastOrNull()
}
internal data class OrLogcatFilter(val filters: List<LogcatFilter>) : LogcatFilter {
@@ -78,6 +82,8 @@ internal data class OrLogcatFilter(val filters: List<LogcatFilter>) : LogcatFilt
}
override fun matches(message: LogcatMessageWrapper) = filters.any { it.matches(message) }
+
+ override fun getFilterName(): String? = filters.mapNotNull { it.getFilterName() }.lastOrNull()
}
internal enum class LogcatFilterField {
@@ -200,6 +206,12 @@ internal object CrashFilter : LogcatFilter {
}
}
+internal data class NameFilter(val name: String) : LogcatFilter {
+ override fun matches(message: LogcatMessageWrapper): Boolean = true
+
+ override fun getFilterName(): String = name
+}
+
private val EXCEPTION_LINE_PATTERN = Regex("\n\\s*at .+\\(.+\\)\n")
internal object StackTraceFilter : LogcatFilter {
diff --git a/logcat/src/com/android/tools/idea/logcat/filters/LogcatFilterCompletionContributor.kt b/logcat/src/com/android/tools/idea/logcat/filters/LogcatFilterCompletionContributor.kt
index 4f08093875d..0252eab415b 100644
--- a/logcat/src/com/android/tools/idea/logcat/filters/LogcatFilterCompletionContributor.kt
+++ b/logcat/src/com/android/tools/idea/logcat/filters/LogcatFilterCompletionContributor.kt
@@ -64,11 +64,13 @@ private const val AGE_KEY = "age:"
private const val IS_KEY = "is:"
+private const val NAME_KEY = "name:"
+
// The following are getters so they can be tested. If they are consts, the value is fixed before we can override the flag
private val KEYS
- get() = STRING_KEYS.map { "$it:" } + LEVEL_KEY + AGE_KEY + maybeAddIsKey()
+ get() = STRING_KEYS.map { "$it:" } + LEVEL_KEY + AGE_KEY + NAME_KEY + maybeAddIsKey()
private val ALL_KEYS
- get() = STRING_KEYS.map(String::getKeyVariants).flatten() + LEVEL_KEY + AGE_KEY + maybeAddIsKey()
+ get() = STRING_KEYS.map(String::getKeyVariants).flatten() + LEVEL_KEY + AGE_KEY + NAME_KEY + maybeAddIsKey()
private fun maybeAddIsKey() = if (StudioFlags.LOGCAT_IS_FILTER.get()) listOf(IS_KEY) else emptyList()
diff --git a/logcat/src/com/android/tools/idea/logcat/filters/LogcatFilterParser.kt b/logcat/src/com/android/tools/idea/logcat/filters/LogcatFilterParser.kt
index 30390296aeb..f16318519f9 100644
--- a/logcat/src/com/android/tools/idea/logcat/filters/LogcatFilterParser.kt
+++ b/logcat/src/com/android/tools/idea/logcat/filters/LogcatFilterParser.kt
@@ -50,6 +50,8 @@ import com.intellij.psi.PsiWhiteSpace
import com.intellij.psi.impl.source.tree.PsiErrorElementImpl
import com.intellij.psi.util.PsiTreeUtil
import com.intellij.psi.util.elementType
+import com.intellij.refactoring.suggested.endOffset
+import com.intellij.refactoring.suggested.startOffset
import java.text.ParseException
import java.time.Clock
import java.time.Duration
@@ -101,6 +103,41 @@ internal class LogcatFilterParser(
}
}
+ /**
+ * Removes `name:` terms from a filter
+ *
+ * This method does a best-effort to remove `name:` terms from a filter. The resulting filter string may not be a valid filter under
+ * extreme circumstances. Therefore, this method should only be used for display purposes. The resulting filter should never actually be
+ * used for filtering.
+ *
+ * An example of a filter that will be broken by this method: `tag:Foo | name:Name`
+ *
+ * It's possible to improve this method to be able to handle these cases, but it's non-trivial.
+ *
+ * TODO(b/236844042): Improve to Handle Complex Expressions
+ */
+ fun removeFilterNames(filterString: String): String {
+ return try {
+ val psi = psiFileFactory.createFileFromText("temp.lcf", LogcatFilterFileType, filterString)
+ val offsets = PsiTreeUtil.findChildrenOfType(psi, LogcatFilterLiteralExpression::class.java)
+ .filter { it.firstChild.text == "name:" }
+ .map { Pair(it.startOffset, it.endOffset) }
+ .sortedBy { it.first }
+
+ val sb = StringBuilder(filterString)
+ offsets.reversed().forEach {(start, end) ->
+ val endWithSpace = if (end < sb.length && sb[end] in arrayOf(' ', '\t')) end + 1 else end
+ sb.replace(start, endWithSpace, "")
+ }
+ sb.toString().trim()
+
+ }
+ catch (e: LogcatFilterParseException) {
+ filterString
+ }
+ }
+
+
private fun parseInternal(filterString: String): LogcatFilter? {
return when {
filterString.isEmpty() -> null
@@ -262,6 +299,7 @@ private fun LogcatFilterLiteralExpression.toKeyFilter(
"level" -> LevelFilter(lastChild.asLogLevel())
"age" -> AgeFilter(lastChild.asDuration(), clock)
"is" -> createIsFilter(lastChild.text)
+ "name" -> createNameFilter(lastChild.toText())
else -> {
val value = lastChild.toText()
val isNegated = firstChild.text.startsWith('-')
@@ -301,6 +339,8 @@ private fun createIsFilter(text: String): LogcatFilter {
}
}
+private fun createNameFilter(text: String): LogcatFilter = NameFilter(text)
+
private fun PsiElement.asLogLevel(): LogLevel =
LogLevel.getByString(text.lowercase())
?: throw LogcatFilterParseException(PsiErrorElementImpl("Invalid Log Level: $text"))
diff --git a/logcat/src/com/android/tools/idea/logcat/filters/parser/LogcatFilter.flex b/logcat/src/com/android/tools/idea/logcat/filters/parser/LogcatFilter.flex
index 11cbbfc16fa..9680fe25e25 100644
--- a/logcat/src/com/android/tools/idea/logcat/filters/parser/LogcatFilter.flex
+++ b/logcat/src/com/android/tools/idea/logcat/filters/parser/LogcatFilter.flex
@@ -93,6 +93,7 @@ KEY
= "age"
| "level"
| "is"
+ | "name"
%state STRING_KVALUE_STATE
%state REGEX_KVALUE_STATE
diff --git a/logcat/src/com/android/tools/idea/logcat/filters/parser/LogcatFilterLexerWrapper.kt b/logcat/src/com/android/tools/idea/logcat/filters/parser/LogcatFilterLexerWrapper.kt
index 78264ce4e34..19feafd97e6 100644
--- a/logcat/src/com/android/tools/idea/logcat/filters/parser/LogcatFilterLexerWrapper.kt
+++ b/logcat/src/com/android/tools/idea/logcat/filters/parser/LogcatFilterLexerWrapper.kt
@@ -36,6 +36,7 @@ private val STRING_KEYS_REGEX = listOf(
"line",
"message",
"msg",
+ "name",
"package",
"tag",
).joinToString("|")
diff --git a/logcat/testSrc/com/android/tools/idea/logcat/filters/FilterTextFieldTest.kt b/logcat/testSrc/com/android/tools/idea/logcat/filters/FilterTextFieldTest.kt
index d693fc7954b..6c776cab81c 100644
--- a/logcat/testSrc/com/android/tools/idea/logcat/filters/FilterTextFieldTest.kt
+++ b/logcat/testSrc/com/android/tools/idea/logcat/filters/FilterTextFieldTest.kt
@@ -19,7 +19,6 @@ import com.android.testutils.MockitoKt.any
import com.android.testutils.MockitoKt.mock
import com.android.tools.adtui.TreeWalker
import com.android.tools.adtui.swing.FakeUi
-import com.android.tools.adtui.swing.popup.JBPopupRule
import com.android.tools.analytics.UsageTrackerRule
import com.android.tools.idea.FakeAndroidProjectDetector
import com.android.tools.idea.concurrency.waitForCondition
@@ -49,6 +48,7 @@ import com.intellij.testFramework.RunsInEdt
import com.intellij.testFramework.runInEdtAndGet
import com.intellij.testFramework.runInEdtAndWait
import com.intellij.ui.EditorTextField
+import com.intellij.ui.SimpleColoredComponent
import com.intellij.util.ui.components.BorderLayoutPanel
import icons.StudioIcons
import kotlinx.coroutines.runBlocking
@@ -73,11 +73,10 @@ import javax.swing.JSeparator
*/
class FilterTextFieldTest {
private val projectRule = ProjectRule()
- private val popupRule = JBPopupRule()
private val usageTrackerRule = UsageTrackerRule()
@get:Rule
- val rule = RuleChain(projectRule, EdtRule(), usageTrackerRule, popupRule)
+ val rule = RuleChain(projectRule, EdtRule(), usageTrackerRule)
private val project get() = projectRule.project
private val filterHistory by lazy { AndroidLogcatFilterHistory.getInstance() }
@@ -236,14 +235,16 @@ class FilterTextFieldTest {
filterHistory.add("bar", isFavorite = false)
fakeLogcatPresenter.filterMatchesCount["foo"] = 1
fakeLogcatPresenter.filterMatchesCount["bar"] = 2
- val historyList = HistoryList(project, fakeLogcatPresenter, filterHistory, coroutineContext)
+ val historyList = HistoryList(project, fakeLogcatPresenter, filterHistory, logcatFilterParser, coroutineContext)
historyList.waitForCounts(fakeLogcatPresenter)
assertThat(historyList.renderToStrings()).containsExactly(
"*: foo ( 1 )",
"----------------------------------",
" : bar ( 2 )",
- )
+ "----------------------------------",
+ "Press Delete to remove an item",
+ ).inOrder() // Order is reverse of the order added
}
@Suppress("OPT_IN_USAGE") // runBlockingTest is experimental
@@ -254,13 +255,15 @@ class FilterTextFieldTest {
filterHistory.add("bar", isFavorite = true)
fakeLogcatPresenter.filterMatchesCount["foo"] = 1
fakeLogcatPresenter.filterMatchesCount["bar"] = 2
- val historyList = HistoryList(project, fakeLogcatPresenter, filterHistory, coroutineContext)
+ val historyList = HistoryList(project, fakeLogcatPresenter, filterHistory, logcatFilterParser, coroutineContext)
historyList.waitForCounts(fakeLogcatPresenter)
assertThat(historyList.renderToStrings()).containsExactly(
- "*: foo ( 1 )",
"*: bar ( 2 )",
- )
+ "*: foo ( 1 )",
+ "----------------------------------",
+ "Press Delete to remove an item",
+ ).inOrder() // Order is reverse of the order added
}
@Suppress("OPT_IN_USAGE") // runBlockingTest is experimental
@@ -271,15 +274,51 @@ class FilterTextFieldTest {
filterHistory.add("bar", isFavorite = false)
fakeLogcatPresenter.filterMatchesCount["foo"] = 1
fakeLogcatPresenter.filterMatchesCount["bar"] = 2
- val historyList = HistoryList(project, fakeLogcatPresenter, filterHistory, coroutineContext)
+ val historyList = HistoryList(project, fakeLogcatPresenter, filterHistory,logcatFilterParser, coroutineContext)
historyList.waitForCounts(fakeLogcatPresenter)
assertThat(historyList.renderToStrings()).containsExactly(
- " : foo ( 1 )",
" : bar ( 2 )",
- )
+ " : foo ( 1 )",
+ "----------------------------------",
+ "Press Delete to remove an item",
+ ).inOrder() // Order is reverse of the order added
+ }
+
+ @Suppress("OPT_IN_USAGE") // runBlockingTest is experimental
+ @Test
+ @RunsInEdt
+ fun historyList_renderNamedFilter() = runBlockingTest {
+ filterHistory.add("name:Foo tag:Foo", isFavorite = false)
+ fakeLogcatPresenter.filterMatchesCount["name:Foo tag:Foo"] = 1
+ val historyList = HistoryList(project, fakeLogcatPresenter, filterHistory, logcatFilterParser, coroutineContext)
+ historyList.waitForCounts(fakeLogcatPresenter)
+
+ assertThat(historyList.renderToStrings()).containsExactly(
+ " : Foo ( 1 )",
+ "----------------------------------",
+ "Press Delete to remove an item",
+ ).inOrder()
}
+ @Suppress("OPT_IN_USAGE") // runBlockingTest is experimental
+ @Test
+ @RunsInEdt
+ fun historyList_renderNamedFilterWithSameName() = runBlockingTest {
+ filterHistory.add("name:Foo tag:Foo", isFavorite = false)
+ filterHistory.add("name:Foo tag:Foobar", isFavorite = false)
+ fakeLogcatPresenter.filterMatchesCount["name:Foo tag:Foo"] = 1
+ fakeLogcatPresenter.filterMatchesCount["name:Foo tag:Foobar"] = 2
+ val historyList = HistoryList(project, fakeLogcatPresenter, filterHistory, logcatFilterParser, coroutineContext)
+ historyList.waitForCounts(fakeLogcatPresenter)
+
+ assertThat(historyList.renderToStrings()).containsExactly(
+ " : Foo: tag:Foobar ( 2 )",
+ " : Foo: tag:Foo ( 1 )",
+ "----------------------------------",
+ "Press Delete to remove an item",
+ ).inOrder() // Order is reverse of the order added
+ }
@Test
@RunsInEdt
@@ -411,7 +450,7 @@ private fun Component.renderToString(): String {
private fun BorderLayoutPanel.renderToString(): String {
val favorite = if ((components[0] as JLabel).icon == StudioIcons.Logcat.Input.FAVORITE_FILLED) "*" else " "
- val text = (components[1] as JLabel).text
+ val text = (components[1] as SimpleColoredComponent).toString()
val count = (components[2] as JLabel).text
return "$favorite: $text ($count)"
}
diff --git a/logcat/testSrc/com/android/tools/idea/logcat/filters/LogcatFilterCompletionContributorTest.kt b/logcat/testSrc/com/android/tools/idea/logcat/filters/LogcatFilterCompletionContributorTest.kt
index 5e189f7dda0..b0af0646b6b 100644
--- a/logcat/testSrc/com/android/tools/idea/logcat/filters/LogcatFilterCompletionContributorTest.kt
+++ b/logcat/testSrc/com/android/tools/idea/logcat/filters/LogcatFilterCompletionContributorTest.kt
@@ -87,6 +87,7 @@ class LogcatFilterCompletionContributorTest {
"level:",
"line:",
"message:",
+ "name:",
"package:",
"package:mine ",
"tag:")
@@ -107,6 +108,7 @@ class LogcatFilterCompletionContributorTest {
"level:",
"line:",
"message:",
+ "name:",
"package:",
"package:mine ",
"tag:",
@@ -389,6 +391,7 @@ class LogcatFilterCompletionContributorTest {
"level:",
"line:",
"message:",
+ "name:",
"package:",
"package:mine ",
"tag:")
diff --git a/logcat/testSrc/com/android/tools/idea/logcat/filters/LogcatFilterParserTest.kt b/logcat/testSrc/com/android/tools/idea/logcat/filters/LogcatFilterParserTest.kt
index c640dc53cf8..90380ed083f 100644
--- a/logcat/testSrc/com/android/tools/idea/logcat/filters/LogcatFilterParserTest.kt
+++ b/logcat/testSrc/com/android/tools/idea/logcat/filters/LogcatFilterParserTest.kt
@@ -218,7 +218,6 @@ class LogcatFilterParserTest {
assertThat(logcatFilterParser().parse(query)).isEqualTo(StringFilter(query, IMPLICIT_LINE))
}
-
@Test
fun parse_topLevelExpressions_joinConsecutiveTopLevelValue_true() {
@@ -382,11 +381,72 @@ class LogcatFilterParserTest {
assertThat(logcatFilterParser().getUsageTrackingEvent("")?.build()).isEqualTo(LogcatFilterEvent.getDefaultInstance())
}
+ @Test
+ fun parse_name() {
+ val filter = logcatFilterParser().parse("level:INFO tag:bar package:foo name:Name")
+
+ assertThat(filter).isEqualTo(
+ AndLogcatFilter(
+ LevelFilter(INFO),
+ StringFilter("bar", TAG),
+ StringFilter("foo", APP),
+ NameFilter("Name"),
+ )
+ )
+ }
+
+ @Test
+ fun parse_name_quoted() {
+ val filter = logcatFilterParser().parse("""name:'Name1' name:"Name2"""")
+
+ assertThat(filter).isEqualTo(
+ AndLogcatFilter(
+ NameFilter("Name1"),
+ NameFilter("Name2"),
+ )
+ )
+ }
+
+ @Test
+ fun removeFilterNames_beginning() {
+ assertThat(logcatFilterParser().removeFilterNames("name:foo package:app")).isEqualTo("package:app")
+ }
+
+ @Test
+ fun removeFilterNames_end() {
+ assertThat(logcatFilterParser().removeFilterNames("package:app name:foo")).isEqualTo("package:app")
+ }
+
+ @Test
+ fun removeFilterNames_withWhitespace() {
+ assertThat(logcatFilterParser().removeFilterNames("package:app name: foo")).isEqualTo("package:app")
+ }
+
+ @Test
+ fun removeFilterNames_withSingleQuotes() {
+ assertThat(logcatFilterParser().removeFilterNames("name:'foo' package:app")).isEqualTo("package:app")
+ }
+
+ @Test
+ fun removeFilterNames_withDoubleQuotes() {
+ assertThat(logcatFilterParser().removeFilterNames("""name:"foo"'" package:app""")).isEqualTo("package:app")
+ }
+
+ @Test
+ fun removeFilterNames_multiple() {
+ assertThat(logcatFilterParser().removeFilterNames("name:foo package:app name:foo")).isEqualTo("package:app")
+ }
+
private fun logcatFilterParser(
androidProjectDetector: AndroidProjectDetector = FakeAndroidProjectDetector(true),
joinConsecutiveTopLevelValue: Boolean = true,
topLevelSameKeyTreatment: CombineWith = AND,
clock: Clock = Clock.systemUTC(),
- ) = LogcatFilterParser(project, fakePackageNamesProvider, androidProjectDetector, joinConsecutiveTopLevelValue, topLevelSameKeyTreatment,
- clock)
+ ) = LogcatFilterParser(
+ project,
+ fakePackageNamesProvider,
+ androidProjectDetector,
+ joinConsecutiveTopLevelValue,
+ topLevelSameKeyTreatment,
+ clock)
}
diff --git a/logcat/testSrc/com/android/tools/idea/logcat/filters/LogcatFilterTest.kt b/logcat/testSrc/com/android/tools/idea/logcat/filters/LogcatFilterTest.kt
index 84cdd8c4944..beee7584910 100644
--- a/logcat/testSrc/com/android/tools/idea/logcat/filters/LogcatFilterTest.kt
+++ b/logcat/testSrc/com/android/tools/idea/logcat/filters/LogcatFilterTest.kt
@@ -278,6 +278,48 @@ class LogcatFilterTest {
message2,
).inOrder()
}
+
+ @Test
+ fun nameFilter_matches() {
+ assertThat(NameFilter("name").matches(logcatMessage(message = "whatever"))).isTrue()
+ }
+
+ @Test
+ fun getFilterName_nameFilter() {
+ assertThat(NameFilter("name").getFilterName()).isEqualTo("name")
+ }
+
+ @Test
+ fun getFilterName_simpleFilters() {
+ assertThat(StringFilter("string", TAG).getFilterName()).isNull()
+ assertThat(NegatedStringFilter("string", TAG).getFilterName()).isNull()
+ assertThat(ExactStringFilter("string", TAG).getFilterName()).isNull()
+ assertThat(NegatedExactStringFilter("string", TAG).getFilterName()).isNull()
+ assertThat(RegexFilter("string", TAG).getFilterName()).isNull()
+ assertThat(NegatedRegexFilter("string", TAG).getFilterName()).isNull()
+ assertThat(LevelFilter(INFO).getFilterName()).isNull()
+ assertThat(AgeFilter(Duration.ofSeconds(60), Clock.systemDefaultZone()).getFilterName()).isNull()
+ assertThat(CrashFilter.getFilterName()).isNull()
+ assertThat(StackTraceFilter.getFilterName()).isNull()
+ }
+
+ @Test
+ fun getFilterName_compoundFilter() {
+ assertThat(AndLogcatFilter(StringFilter("string", TAG), LevelFilter(INFO)).getFilterName()).isNull()
+ assertThat(OrLogcatFilter(StringFilter("string", TAG), LevelFilter(INFO)).getFilterName()).isNull()
+ assertThat(AndLogcatFilter(
+ NameFilter("name1"),
+ StringFilter("string", TAG),
+ LevelFilter(INFO),
+ NameFilter("name2"),
+ ).getFilterName()).isEqualTo("name2")
+ assertThat(OrLogcatFilter(
+ NameFilter("name1"),
+ StringFilter("string", TAG),
+ LevelFilter(INFO),
+ NameFilter("name2"),
+ ).getFilterName()).isEqualTo("name2")
+ }
}
private class TrueFilter : LogcatFilter {
diff --git a/logcat/testSrc/com/android/tools/idea/logcat/filters/parser/LogcatFilterPsiTest.kt b/logcat/testSrc/com/android/tools/idea/logcat/filters/parser/LogcatFilterPsiTest.kt
index 52b031b2a98..9887c3e9775 100644
--- a/logcat/testSrc/com/android/tools/idea/logcat/filters/parser/LogcatFilterPsiTest.kt
+++ b/logcat/testSrc/com/android/tools/idea/logcat/filters/parser/LogcatFilterPsiTest.kt
@@ -39,7 +39,7 @@ import org.junit.Test
import java.text.ParseException
private val STRING_KEYS = listOf("tag", "package", "message", "line")
-private val NON_STRING_KEYS = listOf("level", "age", "is")
+private val NON_STRING_KEYS = listOf("level", "age", "is", "name")
@RunsInEdt
class LogcatFilterPsiTest {