mirror of
https://gitflic.ru/project/openide/openide.git
synced 2025-12-13 15:52:01 +07:00
IJPL-156272 Add freeze analyzer and stack trace viewer to text file that contains stack trace
(cherry picked from commit 24e9dff91ea035f5c0a50b8e64f4eaa81c2d0729) GitOrigin-RevId: d31101f5fdf1e477fe41887957c9e22231822f68
This commit is contained in:
committed by
intellij-monorepo-bot
parent
6e5be878d4
commit
4cea38b606
1
.idea/modules.xml
generated
1
.idea/modules.xml
generated
@@ -335,6 +335,7 @@
|
||||
<module fileurl="file://$PROJECT_DIR$/plugins/devkit/intellij.devkit.plugin/intellij.devkit.plugin.iml" filepath="$PROJECT_DIR$/plugins/devkit/intellij.devkit.plugin/intellij.devkit.plugin.iml" />
|
||||
<module fileurl="file://$PROJECT_DIR$/plugins/devkit/intellij.devkit.plugin/noKotlin/intellij.devkit.plugin.noKotlin.iml" filepath="$PROJECT_DIR$/plugins/devkit/intellij.devkit.plugin/noKotlin/intellij.devkit.plugin.noKotlin.iml" />
|
||||
<module fileurl="file://$PROJECT_DIR$/plugins/devkit/runtimeModuleRepository/jps/intellij.devkit.runtimeModuleRepository.jps.iml" filepath="$PROJECT_DIR$/plugins/devkit/runtimeModuleRepository/jps/intellij.devkit.runtimeModuleRepository.jps.iml" />
|
||||
<module fileurl="file://$PROJECT_DIR$/plugins/devkit/intellij.devkit.stacktrace/intellij.devkit.stacktrace.iml" filepath="$PROJECT_DIR$/plugins/devkit/intellij.devkit.stacktrace/intellij.devkit.stacktrace.iml" />
|
||||
<module fileurl="file://$PROJECT_DIR$/plugins/devkit/devkit-tests/intellij.devkit.testFramework.iml" filepath="$PROJECT_DIR$/plugins/devkit/devkit-tests/intellij.devkit.testFramework.iml" />
|
||||
<module fileurl="file://$PROJECT_DIR$/plugins/devkit/intellij.devkit.themes/intellij.devkit.themes.iml" filepath="$PROJECT_DIR$/plugins/devkit/intellij.devkit.themes/intellij.devkit.themes.iml" />
|
||||
<module fileurl="file://$PROJECT_DIR$/plugins/devkit/intellij.devkit.uiDesigner/intellij.devkit.uiDesigner.iml" filepath="$PROJECT_DIR$/plugins/devkit/intellij.devkit.uiDesigner/intellij.devkit.uiDesigner.iml" />
|
||||
|
||||
@@ -46,7 +46,7 @@ public class UnscrambleDialog extends DialogWrapper {
|
||||
private static final @NonNls String PROPERTY_LOG_FILE_HISTORY_URLS = "UNSCRAMBLE_LOG_FILE_URL";
|
||||
private static final @NonNls String PROPERTY_LOG_FILE_LAST_URL = "UNSCRAMBLE_LOG_FILE_LAST_URL";
|
||||
private static final @NonNls String PROPERTY_UNSCRAMBLER_NAME_USED = "UNSCRAMBLER_NAME_USED";
|
||||
private static final Condition<ThreadState> DEADLOCK_CONDITION = state -> state.isDeadlocked();
|
||||
|
||||
|
||||
private final Project myProject;
|
||||
private JPanel myEditorPanel;
|
||||
@@ -311,84 +311,11 @@ public class UnscrambleDialog extends DialogWrapper {
|
||||
String unscrambledTrace = unscrambleSupport == null ? textToUnscramble : unscrambleSupport.unscramble(project,textToUnscramble, logName, settings);
|
||||
if (unscrambledTrace == null) return null;
|
||||
List<ThreadState> threadStates = ThreadDumpParser.parse(unscrambledTrace);
|
||||
return addConsole(project, threadStates, unscrambledTrace);
|
||||
}
|
||||
|
||||
private static RunContentDescriptor addConsole(final Project project, final List<ThreadState> threadDump, String unscrambledTrace) {
|
||||
Icon icon = null;
|
||||
String message = JavaBundle.message("unscramble.unscrambled.stacktrace.tab");
|
||||
if (!threadDump.isEmpty()) {
|
||||
message = JavaBundle.message("unscramble.unscrambled.threaddump.tab");
|
||||
icon = AllIcons.Actions.Dump;
|
||||
}
|
||||
else {
|
||||
String name = getExceptionName(unscrambledTrace);
|
||||
if (name != null) {
|
||||
message = name;
|
||||
icon = AllIcons.Actions.Lightning;
|
||||
}
|
||||
}
|
||||
if (ContainerUtil.find(threadDump, DEADLOCK_CONDITION) != null) {
|
||||
message = JavaBundle.message("unscramble.unscrambled.deadlock.tab");
|
||||
icon = AllIcons.Debugger.KillProcess;
|
||||
}
|
||||
return AnalyzeStacktraceUtil.addConsole(project, threadDump.size() > 1 ? new ThreadDumpConsoleFactory(project, threadDump) : null, message, unscrambledTrace, icon);
|
||||
return UnscrambleUtils.addConsole(project, threadStates, unscrambledTrace);
|
||||
}
|
||||
|
||||
@Override
|
||||
protected String getDimensionServiceKey(){
|
||||
return "#com.intellij.unscramble.UnscrambleDialog";
|
||||
}
|
||||
|
||||
private static @Nullable String getExceptionName(String unscrambledTrace) {
|
||||
@SuppressWarnings("IOResourceOpenedButNotSafelyClosed")
|
||||
BufferedReader reader = new BufferedReader(new StringReader(unscrambledTrace));
|
||||
for (int i = 0; i < 3; i++) {
|
||||
try {
|
||||
String line = reader.readLine();
|
||||
if (line == null) return null;
|
||||
String name = getExceptionAbbreviation(line);
|
||||
if (name != null) return name;
|
||||
}
|
||||
catch (IOException e) {
|
||||
return null;
|
||||
}
|
||||
}
|
||||
return null;
|
||||
}
|
||||
|
||||
private static @Nullable String getExceptionAbbreviation(String line) {
|
||||
line = StringUtil.trimStart(line.trim(), "Caused by: ");
|
||||
int classNameStart = 0;
|
||||
int classNameEnd = line.length();
|
||||
for (int j = 0; j < line.length(); j++) {
|
||||
char c = line.charAt(j);
|
||||
if (c == '.' || c == '$') {
|
||||
classNameStart = j + 1;
|
||||
continue;
|
||||
}
|
||||
if (c == ':') {
|
||||
classNameEnd = j;
|
||||
break;
|
||||
}
|
||||
if (!StringUtil.isJavaIdentifierPart(c)) {
|
||||
return null;
|
||||
}
|
||||
}
|
||||
if (classNameStart >= classNameEnd) return null;
|
||||
String clazz = line.substring(classNameStart, classNameEnd);
|
||||
String abbreviate = abbreviate(clazz);
|
||||
return abbreviate.length() > 1 ? abbreviate : clazz;
|
||||
}
|
||||
|
||||
private static String abbreviate(String s) {
|
||||
StringBuilder builder = new StringBuilder();
|
||||
for (int i = 0; i < s.length(); i++) {
|
||||
char c = s.charAt(i);
|
||||
if (Character.isUpperCase(c)) {
|
||||
builder.append(c);
|
||||
}
|
||||
}
|
||||
return builder.toString();
|
||||
}
|
||||
}
|
||||
|
||||
@@ -36,21 +36,7 @@ class UnscrambleListener extends ClipboardAnalyzeListener {
|
||||
|
||||
@Override
|
||||
public boolean canHandle(@NotNull String value) {
|
||||
value = ThreadDumpParser.normalizeText(value);
|
||||
int linesCount = 0;
|
||||
for (String line : value.split("\n")) {
|
||||
line = line.trim();
|
||||
if (line.length() == 0) continue;
|
||||
line = StringUtil.trimEnd(line, "\r");
|
||||
if (STACKTRACE_LINE.matcher(line).matches()) {
|
||||
linesCount++;
|
||||
}
|
||||
else {
|
||||
linesCount = 0;
|
||||
}
|
||||
if (linesCount > 2) return true;
|
||||
}
|
||||
return false;
|
||||
return UnscrambleUtils.isStackTrace(value);
|
||||
}
|
||||
|
||||
@Override
|
||||
|
||||
126
java/unscramble/src/com/intellij/unscramble/UnscrambleUtils.java
Normal file
126
java/unscramble/src/com/intellij/unscramble/UnscrambleUtils.java
Normal file
@@ -0,0 +1,126 @@
|
||||
// Copyright 2000-2024 JetBrains s.r.o. and contributors. Use of this source code is governed by the Apache 2.0 license.
|
||||
package com.intellij.unscramble;
|
||||
|
||||
import com.intellij.execution.ui.RunContentDescriptor;
|
||||
import com.intellij.icons.AllIcons;
|
||||
import com.intellij.java.JavaBundle;
|
||||
import com.intellij.openapi.project.Project;
|
||||
import com.intellij.openapi.util.Condition;
|
||||
import com.intellij.openapi.util.text.StringUtil;
|
||||
import com.intellij.threadDumpParser.ThreadDumpParser;
|
||||
import com.intellij.threadDumpParser.ThreadState;
|
||||
import com.intellij.util.containers.ContainerUtil;
|
||||
import org.jetbrains.annotations.Nullable;
|
||||
|
||||
import javax.swing.*;
|
||||
import java.io.BufferedReader;
|
||||
import java.io.IOException;
|
||||
import java.io.StringReader;
|
||||
import java.util.List;
|
||||
import java.util.regex.Pattern;
|
||||
|
||||
final public class UnscrambleUtils {
|
||||
private static final Condition<ThreadState> DEADLOCK_CONDITION = state -> state.isDeadlocked();
|
||||
private static final Pattern STACKTRACE_LINE =
|
||||
Pattern.compile(
|
||||
"[\t]*at [[_a-zA-Z0-9/]+\\.]+[_a-zA-Z$0-9/]+\\.[a-zA-Z0-9_/]+\\([A-Za-z0-9_/]+\\.(java|kt):[\\d]+\\)+[ [~]*\\[[a-zA-Z0-9\\.\\:/]\\]]*");
|
||||
|
||||
public static @Nullable String getExceptionName(String unscrambledTrace) {
|
||||
@SuppressWarnings("IOResourceOpenedButNotSafelyClosed")
|
||||
BufferedReader reader = new BufferedReader(new StringReader(unscrambledTrace));
|
||||
for (int i = 0; i < 3; i++) {
|
||||
try {
|
||||
String line = reader.readLine();
|
||||
if (line == null) return null;
|
||||
String name = getExceptionAbbreviation(line);
|
||||
if (name != null) return name;
|
||||
}
|
||||
catch (IOException e) {
|
||||
return null;
|
||||
}
|
||||
}
|
||||
return null;
|
||||
}
|
||||
|
||||
private static @Nullable String getExceptionAbbreviation(String line) {
|
||||
line = StringUtil.trimStart(line.trim(), "Caused by: ");
|
||||
int classNameStart = 0;
|
||||
int classNameEnd = line.length();
|
||||
for (int j = 0; j < line.length(); j++) {
|
||||
char c = line.charAt(j);
|
||||
if (c == '.' || c == '$') {
|
||||
classNameStart = j + 1;
|
||||
continue;
|
||||
}
|
||||
if (c == ':') {
|
||||
classNameEnd = j;
|
||||
break;
|
||||
}
|
||||
if (!StringUtil.isJavaIdentifierPart(c)) {
|
||||
return null;
|
||||
}
|
||||
}
|
||||
if (classNameStart >= classNameEnd) return null;
|
||||
String clazz = line.substring(classNameStart, classNameEnd);
|
||||
String abbreviate = abbreviate(clazz);
|
||||
return abbreviate.length() > 1 ? abbreviate : clazz;
|
||||
}
|
||||
|
||||
private static String abbreviate(String s) {
|
||||
StringBuilder builder = new StringBuilder();
|
||||
for (int i = 0; i < s.length(); i++) {
|
||||
char c = s.charAt(i);
|
||||
if (Character.isUpperCase(c)) {
|
||||
builder.append(c);
|
||||
}
|
||||
}
|
||||
return builder.toString();
|
||||
}
|
||||
|
||||
public static RunContentDescriptor addConsole(final Project project,
|
||||
final List<ThreadState> threadDump,
|
||||
String unscrambledTrace,
|
||||
Boolean withExecutor) {
|
||||
Icon icon = null;
|
||||
String message = JavaBundle.message("unscramble.unscrambled.stacktrace.tab");
|
||||
if (!threadDump.isEmpty()) {
|
||||
message = JavaBundle.message("unscramble.unscrambled.threaddump.tab");
|
||||
icon = AllIcons.Actions.Dump;
|
||||
}
|
||||
else {
|
||||
String name = getExceptionName(unscrambledTrace);
|
||||
if (name != null) {
|
||||
message = name;
|
||||
icon = AllIcons.Actions.Lightning;
|
||||
}
|
||||
}
|
||||
if (ContainerUtil.find(threadDump, DEADLOCK_CONDITION) != null) {
|
||||
message = JavaBundle.message("unscramble.unscrambled.deadlock.tab");
|
||||
icon = AllIcons.Debugger.KillProcess;
|
||||
}
|
||||
return AnalyzeStacktraceUtil.addConsole(project, threadDump.size() > 1 ? new ThreadDumpConsoleFactory(project, threadDump) : null,
|
||||
message, unscrambledTrace, icon, withExecutor);
|
||||
}
|
||||
|
||||
public static RunContentDescriptor addConsole(final Project project, final List<ThreadState> threadDump, String unscrambledTrace) {
|
||||
return addConsole(project, threadDump, unscrambledTrace, true);
|
||||
}
|
||||
|
||||
public static boolean isStackTrace(String text) {
|
||||
text = ThreadDumpParser.normalizeText(text);
|
||||
int linesCount = 0;
|
||||
for (String line : text.split("\n")) {
|
||||
line = line.trim();
|
||||
if (line.length() == 0) continue;
|
||||
line = StringUtil.trimEnd(line, "\r");
|
||||
if (STACKTRACE_LINE.matcher(line).matches()) {
|
||||
linesCount++;
|
||||
}
|
||||
else {
|
||||
linesCount = 0;
|
||||
}
|
||||
if (linesCount > 2) return true;
|
||||
}
|
||||
return false;
|
||||
}
|
||||
}
|
||||
@@ -82,7 +82,7 @@ class FreezeAnalyzerTest {
|
||||
fun testGeneralLockFreeze() {
|
||||
val threadDump = File(this::class.java.classLoader.getResource("freezes/generalLock/generalLock.txt")!!.path).toPath().readText()
|
||||
FreezeAnalyzer.analyzeFreeze(threadDump)?.message.shouldBe("EDT is blocked on com.intellij.codeInsight.completion.CompletionProgressIndicator.blockingWaitForFinish")
|
||||
FreezeAnalyzer.analyzeFreeze(threadDump)?.threads?.joinToString { it -> it.stackTrace }.shouldStartWith("Possibly locked by com.intellij.codeInsight.completion.JavaMethodCallElement.<init> in DefaultDispatcher-worker-55")
|
||||
FreezeAnalyzer.analyzeFreeze(threadDump)?.additionalMessage?.shouldBe("Possibly locked by com.intellij.codeInsight.completion.JavaMethodCallElement.<init> in DefaultDispatcher-worker-55")
|
||||
}
|
||||
|
||||
@Test
|
||||
|
||||
@@ -18919,6 +18919,7 @@ f:com.intellij.unscramble.AnalyzeStacktraceUtil
|
||||
- sf:EP_NAME:com.intellij.openapi.extensions.ProjectExtensionPointName
|
||||
- s:addConsole(com.intellij.openapi.project.Project,com.intellij.unscramble.AnalyzeStacktraceUtil$ConsoleFactory,java.lang.String,java.lang.String):V
|
||||
- s:addConsole(com.intellij.openapi.project.Project,com.intellij.unscramble.AnalyzeStacktraceUtil$ConsoleFactory,java.lang.String,java.lang.String,javax.swing.Icon):com.intellij.execution.ui.RunContentDescriptor
|
||||
- s:addConsole(com.intellij.openapi.project.Project,com.intellij.unscramble.AnalyzeStacktraceUtil$ConsoleFactory,java.lang.String,java.lang.String,javax.swing.Icon,java.lang.Boolean):com.intellij.execution.ui.RunContentDescriptor
|
||||
- s:createEditorPanel(com.intellij.openapi.project.Project,com.intellij.openapi.Disposable):com.intellij.unscramble.AnalyzeStacktraceUtil$StacktraceEditorPanel
|
||||
- s:printStacktrace(com.intellij.execution.ui.ConsoleView,java.lang.String):V
|
||||
com.intellij.unscramble.AnalyzeStacktraceUtil$ConsoleFactory
|
||||
|
||||
@@ -59,7 +59,8 @@ public final class AnalyzeStacktraceUtil {
|
||||
@Nullable ConsoleFactory consoleFactory,
|
||||
final @NlsContexts.TabTitle String tabTitle,
|
||||
String text,
|
||||
@Nullable Icon icon) {
|
||||
@Nullable Icon icon,
|
||||
Boolean withExecutor) {
|
||||
final TextConsoleBuilder builder = TextConsoleBuilderFactory.getInstance().createBuilder(project);
|
||||
builder.filters(EP_NAME.getExtensions(project));
|
||||
final ConsoleView consoleView = builder.getConsole();
|
||||
@@ -70,14 +71,13 @@ public final class AnalyzeStacktraceUtil {
|
||||
: new MyConsolePanel(consoleView, toolbarActions);
|
||||
final RunContentDescriptor descriptor =
|
||||
new RunContentDescriptor(consoleView, null, consoleComponent, tabTitle, icon) {
|
||||
@Override
|
||||
public boolean isContentReuseProhibited() {
|
||||
return true;
|
||||
}
|
||||
};
|
||||
@Override
|
||||
public boolean isContentReuseProhibited() {
|
||||
return true;
|
||||
}
|
||||
};
|
||||
|
||||
final Executor executor = DefaultRunExecutor.getRunExecutorInstance();
|
||||
for (AnAction action: consoleView.createConsoleActions()) {
|
||||
for (AnAction action : consoleView.createConsoleActions()) {
|
||||
toolbarActions.add(action);
|
||||
}
|
||||
final ConsoleViewImpl console = (ConsoleViewImpl)consoleView;
|
||||
@@ -85,7 +85,10 @@ public final class AnalyzeStacktraceUtil {
|
||||
console.getEditor().getSettings().setCaretRowShown(true);
|
||||
toolbarActions.add(ActionManager.getInstance().getAction("AnalyzeStacktraceToolbar"));
|
||||
|
||||
RunContentManager.getInstance(project).showRunContent(executor, descriptor);
|
||||
if (withExecutor) {
|
||||
final Executor executor = DefaultRunExecutor.getRunExecutorInstance();
|
||||
RunContentManager.getInstance(project).showRunContent(executor, descriptor);
|
||||
}
|
||||
consoleView.allowHeavyFilters();
|
||||
if (consoleFactory == null) {
|
||||
printStacktrace(consoleView, text);
|
||||
@@ -93,6 +96,14 @@ public final class AnalyzeStacktraceUtil {
|
||||
return descriptor;
|
||||
}
|
||||
|
||||
public static RunContentDescriptor addConsole(Project project,
|
||||
@Nullable ConsoleFactory consoleFactory,
|
||||
final @NlsContexts.TabTitle String tabTitle,
|
||||
String text,
|
||||
@Nullable Icon icon) {
|
||||
return addConsole(project, consoleFactory, tabTitle, text, icon, true);
|
||||
}
|
||||
|
||||
private static final class MyConsolePanel extends JPanel {
|
||||
MyConsolePanel(ExecutionConsole consoleView, ActionGroup toolbarActions) {
|
||||
super(new BorderLayout());
|
||||
|
||||
@@ -18,6 +18,7 @@ public final class DevKitIcons {
|
||||
return IconManager.getInstance().loadRasterizedIcon(path, expUIPath, DevKitIcons.class.getClassLoader(), cacheKey, flags);
|
||||
}
|
||||
/** 16x16 */ public static final @NotNull Icon Add_sdk = load("icons/expui/addSDK.svg", "icons/add_sdk.svg", 641117830, 2);
|
||||
/** 16x16 */ public static final @NotNull Icon Freeze = load("icons/expui/freeze.svg", "icons/freeze.svg", 754396711, 2);
|
||||
|
||||
public static final class Gutter {
|
||||
/** 12x12 */ public static final @NotNull Icon DescriptionFile = load("icons/expui/gutter/descriptionFile@14x14.svg", "icons/gutter/descriptionFile.svg", 1318760137, 2);
|
||||
|
||||
@@ -0,0 +1,3 @@
|
||||
<svg width="16" height="16" viewBox="0 0 16 16" fill="none" xmlns="http://www.w3.org/2000/svg">
|
||||
<path d="M7.49971 1C7.77586 1 7.99971 1.22386 7.99971 1.5V2.56574L8.72236 2.08397C8.95213 1.9308 9.26256 1.99289 9.41574 2.22265C9.56892 2.45241 9.50683 2.76285 9.27706 2.91603L7.99971 3.76759V7.13413L10.9154 5.45076L11.0142 3.91876C11.032 3.64319 11.2698 3.43421 11.5453 3.45198C11.8209 3.46975 12.0299 3.70755 12.0121 3.98312L11.9562 4.84984L12.8792 4.31697C13.1183 4.1789 13.4241 4.26083 13.5622 4.49998C13.7003 4.73913 13.6183 5.04492 13.3792 5.18299L12.4562 5.71586L13.2348 6.10081C13.4823 6.22321 13.5837 6.52309 13.4614 6.77063C13.339 7.01817 13.0391 7.11962 12.7915 6.99722L11.4154 6.31679L8.49965 8.00019L11.4147 9.68321L12.7909 9.00278C13.0384 8.88038 13.3383 8.98183 13.4607 9.22937C13.5831 9.4769 13.4817 9.77679 13.2341 9.89919L12.4556 10.2841L13.3785 10.817C13.6177 10.9551 13.6996 11.2609 13.5615 11.5C13.4235 11.7392 13.1177 11.8211 12.8785 11.683L11.9556 11.1502L12.0115 12.0169C12.0292 12.2924 11.8203 12.5302 11.5447 12.548C11.2691 12.5658 11.0313 12.3568 11.0135 12.0812L10.9147 10.5492L7.99971 8.86625V12.2324L9.27706 13.084C9.50683 13.2372 9.56892 13.5476 9.41574 13.7774C9.26256 14.0071 8.95213 14.0692 8.72236 13.916L7.99971 13.4343V14.5C7.99971 14.7761 7.77586 15 7.49971 15C7.22357 15 6.99971 14.7761 6.99971 14.5V13.4343L6.27706 13.916C6.0473 14.0692 5.73687 14.0071 5.58369 13.7774C5.43051 13.5476 5.4926 13.2372 5.72236 13.084L6.99971 12.2324V8.86618L4.08464 10.5492L3.98583 12.0812C3.96806 12.3568 3.73026 12.5658 3.45469 12.548C3.17912 12.5302 2.97013 12.2924 2.98791 12.0168L3.0438 11.1501L2.12085 11.683C1.8817 11.8211 1.5759 11.7391 1.43783 11.5C1.29976 11.2608 1.3817 10.955 1.62085 10.817L2.5438 10.2841L1.76526 9.89915C1.51772 9.77675 1.41627 9.47687 1.53867 9.22933C1.66106 8.98179 1.96095 8.88034 2.20848 9.00274L3.58464 9.68317L6.49965 8.00019L3.58399 6.31683L2.20784 6.99726C1.9603 7.11966 1.66041 7.01821 1.53802 6.77067C1.41563 6.52313 1.51708 6.22325 1.76461 6.10085L2.54316 5.7159L1.6202 5.18303C1.38105 5.04496 1.29912 4.73917 1.43719 4.50002C1.57526 4.26087 1.88105 4.17894 2.1202 4.31701L3.04316 4.84988L2.98726 3.98316C2.96949 3.70759 3.17848 3.46979 3.45405 3.45202C3.72962 3.43425 3.96742 3.64323 3.98519 3.9188L4.08399 5.4508L6.99971 7.1342V3.76759L5.72236 2.91603C5.4926 2.76285 5.43051 2.45241 5.58369 2.22265C5.73687 1.99289 6.0473 1.9308 6.27706 2.08397L6.99971 2.56574V1.5C6.99971 1.22386 7.22357 1 7.49971 1Z" fill="#3574F0"/>
|
||||
</svg>
|
||||
|
After Width: | Height: | Size: 2.4 KiB |
@@ -0,0 +1,10 @@
|
||||
<svg width="16" height="16" viewBox="0 0 16 16" fill="none" xmlns="http://www.w3.org/2000/svg">
|
||||
<g clip-path="url(#clip0_7133_52115)">
|
||||
<path d="M7.49971 1C7.77586 1 7.99971 1.22386 7.99971 1.5V2.56574L8.72236 2.08397C8.95213 1.9308 9.26256 1.99289 9.41574 2.22265C9.56892 2.45241 9.50683 2.76285 9.27706 2.91603L7.99971 3.76759V7.13413L10.9154 5.45076L11.0142 3.91876C11.032 3.64319 11.2698 3.43421 11.5453 3.45198C11.8209 3.46975 12.0299 3.70755 12.0121 3.98312L11.9562 4.84984L12.8792 4.31697C13.1183 4.1789 13.4241 4.26083 13.5622 4.49998C13.7003 4.73913 13.6183 5.04492 13.3792 5.18299L12.4562 5.71586L13.2348 6.10081C13.4823 6.22321 13.5837 6.52309 13.4614 6.77063C13.339 7.01817 13.0391 7.11962 12.7915 6.99722L11.4154 6.31679L8.49965 8.00019L11.4147 9.68321L12.7909 9.00278C13.0384 8.88038 13.3383 8.98183 13.4607 9.22937C13.5831 9.4769 13.4817 9.77679 13.2341 9.89919L12.4556 10.2841L13.3785 10.817C13.6177 10.9551 13.6996 11.2609 13.5615 11.5C13.4235 11.7392 13.1177 11.8211 12.8785 11.683L11.9556 11.1502L12.0115 12.0169C12.0292 12.2924 11.8203 12.5302 11.5447 12.548C11.2691 12.5658 11.0313 12.3568 11.0135 12.0812L10.9147 10.5492L7.99971 8.86625V12.2324L9.27706 13.084C9.50683 13.2372 9.56892 13.5476 9.41574 13.7774C9.26256 14.0071 8.95213 14.0692 8.72236 13.916L7.99971 13.4343V14.5C7.99971 14.7761 7.77586 15 7.49971 15C7.22357 15 6.99971 14.7761 6.99971 14.5V13.4343L6.27706 13.916C6.0473 14.0692 5.73687 14.0071 5.58369 13.7774C5.43051 13.5476 5.4926 13.2372 5.72236 13.084L6.99971 12.2324V8.86618L4.08464 10.5492L3.98583 12.0812C3.96806 12.3568 3.73026 12.5658 3.45469 12.548C3.17912 12.5302 2.97013 12.2924 2.98791 12.0168L3.0438 11.1501L2.12085 11.683C1.8817 11.8211 1.5759 11.7391 1.43783 11.5C1.29976 11.2608 1.3817 10.955 1.62085 10.817L2.5438 10.2841L1.76526 9.89915C1.51772 9.77675 1.41627 9.47687 1.53867 9.22933C1.66106 8.98179 1.96095 8.88034 2.20848 9.00274L3.58464 9.68317L6.49965 8.00019L3.58399 6.31683L2.20784 6.99726C1.9603 7.11966 1.66041 7.01821 1.53802 6.77067C1.41563 6.52313 1.51708 6.22325 1.76461 6.10085L2.54316 5.7159L1.6202 5.18303C1.38105 5.04496 1.29912 4.73917 1.43719 4.50002C1.57526 4.26087 1.88105 4.17894 2.1202 4.31701L3.04316 4.84988L2.98726 3.98316C2.96949 3.70759 3.17848 3.46979 3.45405 3.45202C3.72962 3.43425 3.96742 3.64323 3.98519 3.9188L4.08399 5.4508L6.99971 7.1342V3.76759L5.72236 2.91603C5.4926 2.76285 5.43051 2.45241 5.58369 2.22265C5.73687 1.99289 6.0473 1.9308 6.27706 2.08397L6.99971 2.56574V1.5C6.99971 1.22386 7.22357 1 7.49971 1Z" fill="#548AF7"/>
|
||||
</g>
|
||||
<defs>
|
||||
<clipPath id="clip0_7133_52115">
|
||||
<rect width="16" height="16" fill="white"/>
|
||||
</clipPath>
|
||||
</defs>
|
||||
</svg>
|
||||
|
After Width: | Height: | Size: 2.6 KiB |
3
plugins/devkit/devkit-core/resources/icons/freeze.svg
Normal file
3
plugins/devkit/devkit-core/resources/icons/freeze.svg
Normal file
@@ -0,0 +1,3 @@
|
||||
<svg width="16" height="16" viewBox="0 0 16 16" fill="none" xmlns="http://www.w3.org/2000/svg">
|
||||
<path d="M7.00003 9.73202V15H9.00003V9.73206L13.5622 12.366L14.5622 10.634L9.99999 7.99999L14.5622 5.36602L13.5622 3.63397L9.00003 6.26792V1H7.00003V6.26796L2.43783 3.63398L1.43783 5.36603L5.99999 7.99999L1.43781 10.634L2.43781 12.366L7.00003 9.73202Z" fill="#389FD6"/>
|
||||
</svg>
|
||||
|
After Width: | Height: | Size: 373 B |
10
plugins/devkit/devkit-core/resources/icons/freeze_dark.svg
Normal file
10
plugins/devkit/devkit-core/resources/icons/freeze_dark.svg
Normal file
@@ -0,0 +1,10 @@
|
||||
<svg width="16" height="16" viewBox="0 0 16 16" fill="none" xmlns="http://www.w3.org/2000/svg">
|
||||
<g clip-path="url(#clip0_8253_2564)">
|
||||
<path d="M7.00003 9.73202V15H9.00003V9.73206L13.5622 12.366L14.5622 10.634L9.99999 7.99999L14.5622 5.36602L13.5622 3.63397L9.00003 6.26792V1H7.00003V6.26796L2.43783 3.63398L1.43783 5.36603L5.99999 7.99999L1.43781 10.634L2.43781 12.366L7.00003 9.73202Z" fill="#3592C4"/>
|
||||
</g>
|
||||
<defs>
|
||||
<clipPath id="clip0_8253_2564">
|
||||
<rect width="16" height="16" fill="white"/>
|
||||
</clipPath>
|
||||
</defs>
|
||||
</svg>
|
||||
|
After Width: | Height: | Size: 519 B |
@@ -17,5 +17,6 @@
|
||||
<orderEntry type="module" module-name="intellij.devkit.jps" scope="RUNTIME" />
|
||||
<orderEntry type="module" module-name="intellij.devkit.runtimeModuleRepository.jps" scope="RUNTIME" />
|
||||
<orderEntry type="module" module-name="intellij.devkit.java.tests" scope="TEST" />
|
||||
<orderEntry type="module" module-name="intellij.devkit.stacktrace" scope="RUNTIME" />
|
||||
</component>
|
||||
</module>
|
||||
@@ -0,0 +1,25 @@
|
||||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<module type="JAVA_MODULE" version="4">
|
||||
<component name="NewModuleRootManager" inherit-compiler-output="true">
|
||||
<exclude-output />
|
||||
<content url="file://$MODULE_DIR$">
|
||||
<sourceFolder url="file://$MODULE_DIR$/resources" type="java-resource" />
|
||||
<sourceFolder url="file://$MODULE_DIR$/src" isTestSource="false" />
|
||||
</content>
|
||||
<orderEntry type="inheritedJdk" />
|
||||
<orderEntry type="sourceFolder" forTests="false" />
|
||||
<orderEntry type="library" name="kotlin-stdlib" level="project" />
|
||||
<orderEntry type="module" module-name="intellij.devkit.core" />
|
||||
<orderEntry type="module" module-name="intellij.platform.core" />
|
||||
<orderEntry type="module" module-name="intellij.platform.editor" />
|
||||
<orderEntry type="module" module-name="intellij.platform.ide.core" />
|
||||
<orderEntry type="module" module-name="intellij.platform.core.ui" />
|
||||
<orderEntry type="module" module-name="intellij.platform.execution" />
|
||||
<orderEntry type="module" module-name="intellij.java" />
|
||||
<orderEntry type="module" module-name="intellij.java.impl" />
|
||||
<orderEntry type="module" module-name="intellij.java.unscramble" />
|
||||
<orderEntry type="module" module-name="intellij.platform.util.coroutines" />
|
||||
<orderEntry type="module" module-name="intellij.platform.threadDumpParser" />
|
||||
<orderEntry type="module" module-name="intellij.platform.diagnostic.freezeAnalyzer" />
|
||||
</component>
|
||||
</module>
|
||||
@@ -0,0 +1,12 @@
|
||||
<idea-plugin package="org.jetbrains.idea.devkit.stacktrace">
|
||||
<dependencies>
|
||||
<module name="intellij.devkit.core"/>
|
||||
<module name="intellij.java.unscramble"/>
|
||||
<module name="intellij.platform.diagnostic.freezeAnalyzer"/>
|
||||
<plugin id="com.intellij.java"/>
|
||||
</dependencies>
|
||||
<extensions defaultExtensionNs="com.intellij">
|
||||
<fileEditorProvider id="java-stacktrace-editor"
|
||||
implementation="org.jetbrains.idea.devkit.stacktrace.editor.StackTraceEditorProvider"/>
|
||||
</extensions>
|
||||
</idea-plugin>
|
||||
@@ -0,0 +1,4 @@
|
||||
progress.title.freeze.analysis=Analyzing freeze\u2026
|
||||
progress.title.parsing.thread.dump=Parsing thread dump\u2026
|
||||
stack.trace=Stack Trace
|
||||
tab.title.freeze.analyzer=Freeze Analyzer
|
||||
@@ -0,0 +1,14 @@
|
||||
// Copyright 2000-2022 JetBrains s.r.o. and contributors. Use of this source code is governed by the Apache 2.0 license.
|
||||
package org.jetbrains.idea.devkit.stacktrace
|
||||
|
||||
import com.intellij.DynamicBundle
|
||||
import org.jetbrains.annotations.Nls
|
||||
import org.jetbrains.annotations.NonNls
|
||||
import org.jetbrains.annotations.PropertyKey
|
||||
|
||||
object DevKitStackTraceBundle {
|
||||
private const val BUNDLE_FQN: @NonNls String = "messages.DevKitStackTraceBundle"
|
||||
private val BUNDLE = DynamicBundle(DevKitStackTraceBundle::class.java, BUNDLE_FQN)
|
||||
|
||||
fun message(key: @PropertyKey(resourceBundle = BUNDLE_FQN) String, vararg params: Any): @Nls String = BUNDLE.getMessage(key, *params)
|
||||
}
|
||||
@@ -0,0 +1,15 @@
|
||||
// Copyright 2000-2024 JetBrains s.r.o. and contributors. Use of this source code is governed by the Apache 2.0 license that can be found in the LICENSE file.
|
||||
package org.jetbrains.idea.devkit.stacktrace.editor
|
||||
|
||||
import com.intellij.openapi.fileEditor.FileEditor
|
||||
import com.intellij.openapi.fileEditor.TextEditor
|
||||
import com.intellij.openapi.fileEditor.TextEditorWithPreviewProvider
|
||||
|
||||
internal class StackTraceEditorProvider : TextEditorWithPreviewProvider(StackTraceFileEditorProvider()) {
|
||||
|
||||
override fun createSplitEditor(firstEditor: TextEditor, secondEditor: FileEditor): FileEditor {
|
||||
require(secondEditor is StackTraceFileEditor) { "Secondary editor should be StackTraceFileEditor" }
|
||||
return StackTraceEditorWithPreview(firstEditor, secondEditor)
|
||||
}
|
||||
|
||||
}
|
||||
@@ -0,0 +1,23 @@
|
||||
// Copyright 2000-2024 JetBrains s.r.o. and contributors. Use of this source code is governed by the Apache 2.0 license that can be found in the LICENSE file.
|
||||
package org.jetbrains.idea.devkit.stacktrace.editor
|
||||
|
||||
import com.intellij.openapi.fileEditor.TextEditor
|
||||
import com.intellij.openapi.fileEditor.TextEditorWithPreview
|
||||
import org.jetbrains.idea.devkit.stacktrace.DevKitStackTraceBundle
|
||||
|
||||
/**
|
||||
* Text and stacktrace preview editor.
|
||||
*/
|
||||
internal class StackTraceEditorWithPreview(editor: TextEditor, preview: StackTraceFileEditor)
|
||||
: TextEditorWithPreview(editor, preview, DevKitStackTraceBundle.message("stack.trace"), Layout.SHOW_EDITOR_AND_PREVIEW, false) {
|
||||
|
||||
init {
|
||||
preview.setMainEditor(editor.getEditor())
|
||||
}
|
||||
|
||||
override fun onLayoutChange(oldValue: Layout?, newValue: Layout?) {
|
||||
if (newValue == Layout.SHOW_PREVIEW) {
|
||||
myPreview.preferredFocusedComponent?.requestFocus() ?: myPreview.component.requestFocus()
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,173 @@
|
||||
package org.jetbrains.idea.devkit.stacktrace.editor
|
||||
|
||||
import com.intellij.execution.ui.RunContentDescriptor
|
||||
import com.intellij.openapi.application.EDT
|
||||
import com.intellij.openapi.editor.Editor
|
||||
import com.intellij.openapi.editor.event.DocumentEvent
|
||||
import com.intellij.openapi.editor.event.DocumentListener
|
||||
import com.intellij.openapi.fileEditor.FileDocumentManager
|
||||
import com.intellij.openapi.fileEditor.FileEditor
|
||||
import com.intellij.openapi.fileEditor.FileEditorState
|
||||
import com.intellij.openapi.options.advanced.AdvancedSettings
|
||||
import com.intellij.openapi.project.Project
|
||||
import com.intellij.openapi.util.Disposer
|
||||
import com.intellij.openapi.util.UserDataHolderBase
|
||||
import com.intellij.openapi.vfs.VirtualFile
|
||||
import com.intellij.openapi.wm.ToolWindow
|
||||
import com.intellij.platform.diagnostic.freezeAnalyzer.FreezeAnalyzer
|
||||
import com.intellij.platform.ide.progress.withBackgroundProgress
|
||||
import com.intellij.threadDumpParser.ThreadDumpParser.parse
|
||||
import com.intellij.ui.content.Content
|
||||
import com.intellij.ui.content.ContentFactory.getInstance
|
||||
import com.intellij.ui.content.ContentManager
|
||||
import com.intellij.ui.content.TabbedPaneContentUI
|
||||
import com.intellij.unscramble.AnalyzeStacktraceUtil
|
||||
import com.intellij.unscramble.UnscrambleUtils.addConsole
|
||||
import com.intellij.util.concurrency.annotations.RequiresEdt
|
||||
import kotlinx.coroutines.*
|
||||
import kotlinx.coroutines.channels.BufferOverflow
|
||||
import kotlinx.coroutines.flow.MutableSharedFlow
|
||||
import kotlinx.coroutines.flow.MutableStateFlow
|
||||
import kotlinx.coroutines.flow.collectLatest
|
||||
import kotlinx.coroutines.flow.debounce
|
||||
import org.jetbrains.idea.devkit.DevKitIcons
|
||||
import org.jetbrains.idea.devkit.stacktrace.DevKitStackTraceBundle
|
||||
import org.jetbrains.idea.devkit.stacktrace.util.StackTracePluginScope
|
||||
import java.awt.BorderLayout
|
||||
import java.beans.PropertyChangeListener
|
||||
import javax.swing.JComponent
|
||||
import javax.swing.JPanel
|
||||
import javax.swing.SwingConstants.TOP
|
||||
import kotlin.time.Duration.Companion.milliseconds
|
||||
|
||||
class StackTraceFileEditor(private val project: Project, private val file: VirtualFile) : FileEditor, UserDataHolderBase() {
|
||||
private val document = FileDocumentManager.getInstance().getDocument(file) ?: error("Document not found for file: $file")
|
||||
private val mainPanelWrapper = JPanel(BorderLayout())
|
||||
private val mainEditor = MutableStateFlow<Editor?>(null)
|
||||
private val coroutineScope = StackTracePluginScope.createChildScope(project)
|
||||
private var myContentManager: ContentManager? = null
|
||||
|
||||
init {
|
||||
document.addDocumentListener(ReparseContentDocumentListener(), this)
|
||||
coroutineScope.launch(Dispatchers.EDT) {
|
||||
myContentManager = getInstance().createContentManager(TabbedPaneContentUI(TOP), false, project)
|
||||
updateStacktracePane()
|
||||
}
|
||||
}
|
||||
|
||||
fun setMainEditor(editor: Editor) {
|
||||
check(mainEditor.value == null) { "Main editor already set" }
|
||||
mainEditor.value = editor
|
||||
}
|
||||
|
||||
override fun getComponent(): JComponent = mainPanelWrapper
|
||||
|
||||
override fun getPreferredFocusedComponent(): JComponent? = myContentManager?.component ?: mainPanelWrapper
|
||||
|
||||
override fun getName(): String = DevKitStackTraceBundle.message("stack.trace")
|
||||
|
||||
override fun setState(state: FileEditorState) {}
|
||||
|
||||
override fun isModified(): Boolean = false
|
||||
|
||||
override fun isValid(): Boolean = true
|
||||
|
||||
override fun addPropertyChangeListener(listener: PropertyChangeListener) {}
|
||||
|
||||
override fun removePropertyChangeListener(listener: PropertyChangeListener) {}
|
||||
|
||||
override fun getFile(): VirtualFile = file
|
||||
|
||||
override fun dispose() {
|
||||
myContentManager?.removeAllContents(true)
|
||||
myContentManager?.let {
|
||||
Disposer.dispose(it)
|
||||
}
|
||||
myContentManager = null
|
||||
coroutineScope.cancel()
|
||||
}
|
||||
|
||||
@RequiresEdt
|
||||
private suspend fun updateStacktracePane() {
|
||||
if (!file.isValid) return
|
||||
val contentManager = myContentManager ?: return
|
||||
myContentManager?.removeAllContents(true)
|
||||
|
||||
addThreadContent(contentManager) ?: return
|
||||
addFreezeAnalysisContent(contentManager)
|
||||
|
||||
mainPanelWrapper.add(contentManager.component, BorderLayout.CENTER)
|
||||
if (mainPanelWrapper.isShowing) mainPanelWrapper.validate()
|
||||
mainPanelWrapper.repaint()
|
||||
}
|
||||
|
||||
private suspend fun addThreadContent(contentManager: ContentManager): Unit? = withContext(Dispatchers.Default) {
|
||||
withBackgroundProgress(project, DevKitStackTraceBundle.message("progress.title.parsing.thread.dump")) {
|
||||
val threadStates = parse(document.text)
|
||||
withContext(Dispatchers.EDT) {
|
||||
addConsole(project, threadStates, document.text, false)
|
||||
}
|
||||
}
|
||||
}?.let { descriptor ->
|
||||
contentManager.addContent(createNewContent(descriptor).apply {
|
||||
executionId = descriptor.executionId
|
||||
component = descriptor.component
|
||||
setPreferredFocusedComponent(descriptor.preferredFocusComputable)
|
||||
putUserData(RunContentDescriptor.DESCRIPTOR_KEY, descriptor)
|
||||
displayName = descriptor.displayName
|
||||
descriptor.setAttachedContent(this)
|
||||
})
|
||||
}
|
||||
|
||||
private suspend fun addFreezeAnalysisContent(contentManager: ContentManager) {
|
||||
withContext(Dispatchers.Default) {
|
||||
withBackgroundProgress(project, DevKitStackTraceBundle.message("progress.title.freeze.analysis")) {
|
||||
FreezeAnalyzer.analyzeFreeze(document.text)
|
||||
}
|
||||
}?.let { result ->
|
||||
val freezeDescriptor = AnalyzeStacktraceUtil.addConsole(
|
||||
project, null,
|
||||
DevKitStackTraceBundle.message("tab.title.freeze.analyzer"),
|
||||
"${result.message}\n${result.additionalMessage ?: ""}\n======= Stack Trace: ========= \n${result.threads.joinToString { it -> it.stackTrace }}",
|
||||
DevKitIcons.Freeze, false
|
||||
)
|
||||
contentManager.addContent(createNewContent(freezeDescriptor).apply {
|
||||
executionId = freezeDescriptor.executionId
|
||||
component = freezeDescriptor.component
|
||||
setPreferredFocusedComponent(freezeDescriptor.preferredFocusComputable)
|
||||
putUserData(RunContentDescriptor.DESCRIPTOR_KEY, freezeDescriptor)
|
||||
displayName = freezeDescriptor.displayName
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
private fun createNewContent(descriptor: RunContentDescriptor): Content {
|
||||
val content = getInstance().createContent(
|
||||
descriptor.component, descriptor.displayName, true
|
||||
).apply {
|
||||
putUserData(ToolWindow.SHOW_CONTENT_ICON, true)
|
||||
isPinned = AdvancedSettings.getBoolean("start.run.configurations.pinned")
|
||||
icon = descriptor.icon
|
||||
}
|
||||
return content
|
||||
}
|
||||
|
||||
private inner class ReparseContentDocumentListener : DocumentListener {
|
||||
@OptIn(FlowPreview::class)
|
||||
private val documentChangedRequests = MutableSharedFlow<Unit>(
|
||||
replay = 1,
|
||||
onBufferOverflow = BufferOverflow.DROP_OLDEST
|
||||
).apply {
|
||||
coroutineScope.launch(Dispatchers.EDT) {
|
||||
debounce(500.milliseconds)
|
||||
.collectLatest {
|
||||
updateStacktracePane()
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
override fun documentChanged(event: DocumentEvent) {
|
||||
documentChangedRequests.tryEmit(Unit)
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,25 @@
|
||||
// Copyright 2000-2024 JetBrains s.r.o. and contributors. Use of this source code is governed by the Apache 2.0 license that can be found in the LICENSE file.
|
||||
package org.jetbrains.idea.devkit.stacktrace.editor
|
||||
|
||||
import com.intellij.openapi.fileEditor.FileEditor
|
||||
import com.intellij.openapi.fileEditor.FileEditorPolicy
|
||||
import com.intellij.openapi.fileEditor.WeighedFileEditorProvider
|
||||
import com.intellij.openapi.project.Project
|
||||
import com.intellij.openapi.vfs.VfsUtilCore.loadText
|
||||
import com.intellij.openapi.vfs.VirtualFile
|
||||
import com.intellij.unscramble.UnscrambleUtils
|
||||
|
||||
/**
|
||||
* Java stacktrace weighed file editor provider.
|
||||
*/
|
||||
class StackTraceFileEditorProvider : WeighedFileEditorProvider() {
|
||||
override fun accept(project: Project, file: VirtualFile): Boolean {
|
||||
return file.extension == "txt" && UnscrambleUtils.isStackTrace(loadText(file))
|
||||
}
|
||||
|
||||
override fun createEditor(project: Project, file: VirtualFile): FileEditor = StackTraceFileEditor(project, file)
|
||||
|
||||
override fun getEditorTypeId(): String = "stacktrace-preview-editor"
|
||||
|
||||
override fun getPolicy(): FileEditorPolicy = FileEditorPolicy.PLACE_AFTER_DEFAULT_EDITOR
|
||||
}
|
||||
@@ -0,0 +1,21 @@
|
||||
// Copyright 2000-2024 JetBrains s.r.o. and contributors. Use of this source code is governed by the Apache 2.0 license that can be found in the LICENSE file.
|
||||
package org.jetbrains.idea.devkit.stacktrace.util
|
||||
|
||||
import com.intellij.openapi.components.Service
|
||||
import com.intellij.openapi.components.service
|
||||
import com.intellij.openapi.project.Project
|
||||
import com.intellij.platform.util.coroutines.childScope
|
||||
import kotlinx.coroutines.CoroutineScope
|
||||
|
||||
@Service(Service.Level.PROJECT)
|
||||
internal class StackTracePluginScope(private val coroutineScope: CoroutineScope) {
|
||||
companion object {
|
||||
fun createChildScope(project: Project): CoroutineScope {
|
||||
return scope(project).childScope("DevKitStackTracePlugin")
|
||||
}
|
||||
|
||||
fun scope(project: Project): CoroutineScope {
|
||||
return project.service<StackTracePluginScope>().coroutineScope
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -23,4 +23,5 @@
|
||||
- name: intellij.devkit.uiDesigner
|
||||
- name: intellij.devkit.workspaceModel
|
||||
- name: intellij.kotlin.devkit
|
||||
- name: intellij.devkit.debugger
|
||||
- name: intellij.devkit.debugger
|
||||
- name: intellij.devkit.stacktrace
|
||||
@@ -28,6 +28,7 @@
|
||||
<module name="intellij.devkit.workspaceModel"/>
|
||||
<module name="intellij.kotlin.devkit"/>
|
||||
<module name="intellij.devkit.debugger"/>
|
||||
<module name="intellij.devkit.stacktrace"/>
|
||||
</content>
|
||||
|
||||
<extensions defaultExtensionNs="com.intellij">
|
||||
|
||||
Reference in New Issue
Block a user