From eea7326ed96d3dbb9ca7e9db783605522cbd30ae Mon Sep 17 00:00:00 2001 From: Egor Ushakov Date: Mon, 21 Jan 2019 15:48:32 +0300 Subject: [PATCH] annotation support for debugger renderers - IDEA-144797 --- .../debugger/actions/ViewAsGroup.java | 23 ++----- .../debugger/engine/DebugProcessImpl.java | 6 +- .../settings/NodeRendererSettings.java | 62 ++++++++++++++++++- .../util/resources/misc/registry.properties | 1 + 4 files changed, 69 insertions(+), 23 deletions(-) diff --git a/java/debugger/impl/src/com/intellij/debugger/actions/ViewAsGroup.java b/java/debugger/impl/src/com/intellij/debugger/actions/ViewAsGroup.java index 0c028f188f1d..603e60b24e32 100644 --- a/java/debugger/impl/src/com/intellij/debugger/actions/ViewAsGroup.java +++ b/java/debugger/impl/src/com/intellij/debugger/actions/ViewAsGroup.java @@ -1,18 +1,4 @@ -/* - * Copyright 2000-2017 JetBrains s.r.o. - * - * 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. - */ +// Copyright 2000-2019 JetBrains s.r.o. Use of this source code is governed by the Apache 2.0 license that can be found in the LICENSE file. package com.intellij.debugger.actions; import com.intellij.debugger.engine.DebugProcessImpl; @@ -26,6 +12,7 @@ import com.intellij.debugger.ui.tree.render.NodeRenderer; import com.intellij.openapi.actionSystem.*; import com.intellij.openapi.diagnostic.Logger; import com.intellij.openapi.project.DumbAware; +import com.intellij.openapi.project.Project; import com.intellij.xdebugger.frame.XValue; import com.intellij.xdebugger.impl.ui.tree.actions.XDebuggerTreeActionBase; import com.intellij.xdebugger.impl.ui.tree.nodes.XValueNodeImpl; @@ -102,10 +89,10 @@ public class ViewAsGroup extends ActionGroup implements DumbAware { return myChildren; } - private static AnAction [] calcChildren(List values) { + private static AnAction [] calcChildren(List values, Project project) { List renderers = new ArrayList<>(); - List allRenderers = NodeRendererSettings.getInstance().getAllRenderers(); + List allRenderers = NodeRendererSettings.getInstance().getAllRenderers(project); boolean anyValueDescriptor = false; @@ -178,7 +165,7 @@ public class ViewAsGroup extends ActionGroup implements DumbAware { process.getManagerThread().schedule(new DebuggerContextCommandImpl(debuggerContext) { @Override public void threadAction(@NotNull SuspendContextImpl suspendContext) { - myChildren = calcChildren(values); + myChildren = calcChildren(values, process.getProject()); DebuggerAction.enableAction(event, myChildren.length > 0); } }); diff --git a/java/debugger/impl/src/com/intellij/debugger/engine/DebugProcessImpl.java b/java/debugger/impl/src/com/intellij/debugger/engine/DebugProcessImpl.java index a27251cc58b7..3d608c734c36 100644 --- a/java/debugger/impl/src/com/intellij/debugger/engine/DebugProcessImpl.java +++ b/java/debugger/impl/src/com/intellij/debugger/engine/DebugProcessImpl.java @@ -1,4 +1,4 @@ -// Copyright 2000-2018 JetBrains s.r.o. Use of this source code is governed by the Apache 2.0 license that can be found in the LICENSE file. +// Copyright 2000-2019 JetBrains s.r.o. Use of this source code is governed by the Apache 2.0 license that can be found in the LICENSE file. package com.intellij.debugger.engine; import com.intellij.Patches; @@ -161,7 +161,9 @@ public abstract class DebugProcessImpl extends UserDataHolderBase implements Deb myNodeRenderersMap.clear(); myRenderers.clear(); try { - NodeRendererSettings.getInstance().getAllRenderers().stream().filter(NodeRenderer::isEnabled).forEachOrdered(myRenderers::add); + NodeRendererSettings.getInstance().getAllRenderers(myProject).stream() + .filter(NodeRenderer::isEnabled) + .forEachOrdered(myRenderers::add); } finally { DebuggerInvocationUtil.swingInvokeLater(myProject, () -> { diff --git a/java/debugger/impl/src/com/intellij/debugger/settings/NodeRendererSettings.java b/java/debugger/impl/src/com/intellij/debugger/settings/NodeRendererSettings.java index 75d950f2d212..a7305d044fdd 100644 --- a/java/debugger/impl/src/com/intellij/debugger/settings/NodeRendererSettings.java +++ b/java/debugger/impl/src/com/intellij/debugger/settings/NodeRendererSettings.java @@ -14,20 +14,27 @@ import com.intellij.debugger.ui.tree.DebuggerTreeNode; import com.intellij.debugger.ui.tree.ValueDescriptor; import com.intellij.debugger.ui.tree.render.*; import com.intellij.openapi.Disposable; +import com.intellij.openapi.application.ReadAction; import com.intellij.openapi.components.PersistentStateComponent; import com.intellij.openapi.components.ServiceManager; import com.intellij.openapi.components.State; import com.intellij.openapi.components.Storage; +import com.intellij.openapi.diagnostic.Logger; import com.intellij.openapi.fileTypes.StdFileTypes; +import com.intellij.openapi.progress.ProcessCanceledException; +import com.intellij.openapi.project.IndexNotReadyException; import com.intellij.openapi.project.Project; import com.intellij.openapi.util.*; +import com.intellij.openapi.util.registry.Registry; import com.intellij.openapi.util.text.StringUtil; -import com.intellij.psi.CommonClassNames; -import com.intellij.psi.PsiElement; +import com.intellij.psi.*; +import com.intellij.psi.search.GlobalSearchScope; +import com.intellij.psi.search.searches.AnnotatedElementsSearch; import com.intellij.util.EventDispatcher; import com.intellij.util.IncorrectOperationException; import com.sun.jdi.Value; import org.jdom.Element; +import org.jetbrains.annotations.Debugger; import org.jetbrains.annotations.NonNls; import org.jetbrains.annotations.NotNull; import org.jetbrains.annotations.Nullable; @@ -41,6 +48,8 @@ import java.util.List; @Storage(value = "debugger.renderers.xml", deprecated = true), }) public class NodeRendererSettings implements PersistentStateComponent { + private static final Logger LOG = Logger.getInstance(NodeRendererSettings.class); + @NonNls private static final String REFERENCE_RENDERER = "Reference renderer"; @NonNls public static final String RENDERER_TAG = "Renderer"; @NonNls private static final String RENDERER_ID = "ID"; @@ -218,7 +227,7 @@ public class NodeRendererSettings implements PersistentStateComponent { myDispatcher.getMulticaster().renderersChanged(); } - public List getAllRenderers() { + public List getAllRenderers(Project project) { // the order is important as the renderers are applied according to it final List allRenderers = new ArrayList<>(); @@ -228,6 +237,10 @@ public class NodeRendererSettings implements PersistentStateComponent { return true; }); + if (Registry.is("debugger.renderers.annotations")) { + ReadAction.run(() -> addAnnotationRenderers(allRenderers, project)); + } + // plugins registered renderers come after that allRenderers.addAll(NodeRenderer.EP_NAME.getExtensionList()); @@ -241,6 +254,49 @@ public class NodeRendererSettings implements PersistentStateComponent { return allRenderers; } + private void addAnnotationRenderers(List renderers, Project project) { + try { + GlobalSearchScope allScope = GlobalSearchScope.allScope(project); + String annotationFqn = Debugger.Renderer.class.getName().replace("$", "."); + PsiClass annotationClass = + JavaPsiFacade.getInstance(project).findClass(annotationFqn, allScope); + if (annotationClass != null) { + AnnotatedElementsSearch.searchPsiClasses(annotationClass, allScope) + .forEach(e -> { + PsiAnnotation annotation = e.getAnnotation(annotationFqn); + if (annotation != null) { + String text = getAttributeValue(annotation, "text"); + LabelRenderer labelRenderer = StringUtil.isEmpty(text) ? null : createLabelRenderer(null, text, null); + String childrenArray = getAttributeValue(annotation, "childrenArray"); + String isLeaf = getAttributeValue(annotation, "hasChildren"); + ExpressionChildrenRenderer childrenRenderer = + StringUtil.isEmpty(childrenArray) ? null : createExpressionArrayChildrenRenderer(childrenArray, isLeaf, myArrayRenderer); + CompoundReferenceRenderer renderer = createCompoundReferenceRenderer( + e.getQualifiedName(), e.getQualifiedName(), labelRenderer, childrenRenderer); + renderer.setEnabled(true); + renderers.add(renderer); + } + }); + } + } + catch (IndexNotReadyException | ProcessCanceledException ignore) { + } + catch (Exception e) { + LOG.error(e); + } + } + + private static String getAttributeValue(PsiAnnotation annotation, String attribute) { + PsiAnnotationMemberValue value = annotation.findAttributeValue(attribute); + if (value == null) { + return null; + } + if (value instanceof PsiLiteralValue) { + return String.valueOf(((PsiLiteralValue)value).getValue()); + } + throw new IllegalStateException("String literal expected, but was " + value); + } + public Renderer readRenderer(Element root) throws InvalidDataException { if (root == null) { return null; diff --git a/platform/util/resources/misc/registry.properties b/platform/util/resources/misc/registry.properties index 76723e99dec4..ba8b5e499b4c 100644 --- a/platform/util/resources/misc/registry.properties +++ b/platform/util/resources/misc/registry.properties @@ -367,6 +367,7 @@ debugger.evaluate.single.threaded.timeout=1000 debugger.evaluate.single.threaded.timeout.description=Number of milliseconds to evaluate resuming only the current thread, then resume all threads debugger.call.tracing=false debugger.call.tracing.arguments=true +debugger.renderers.annotations=false execution.java.always.debug=false execution.java.always.debug.description=Always run java processes with the debug agent