diff --git a/.idea/modules.xml b/.idea/modules.xml
index 8f87f521d954..30338d9466f6 100644
--- a/.idea/modules.xml
+++ b/.idea/modules.xml
@@ -332,6 +332,7 @@
+
diff --git a/java/unscramble/src/com/intellij/unscramble/UnscrambleDialog.java b/java/unscramble/src/com/intellij/unscramble/UnscrambleDialog.java
index ef2bb37e7e12..39b60740342e 100644
--- a/java/unscramble/src/com/intellij/unscramble/UnscrambleDialog.java
+++ b/java/unscramble/src/com/intellij/unscramble/UnscrambleDialog.java
@@ -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 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 threadStates = ThreadDumpParser.parse(unscrambledTrace);
- return addConsole(project, threadStates, unscrambledTrace);
- }
-
- private static RunContentDescriptor addConsole(final Project project, final List 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();
- }
}
diff --git a/java/unscramble/src/com/intellij/unscramble/UnscrambleListener.java b/java/unscramble/src/com/intellij/unscramble/UnscrambleListener.java
index d84253a4ecaa..22da88bbad96 100644
--- a/java/unscramble/src/com/intellij/unscramble/UnscrambleListener.java
+++ b/java/unscramble/src/com/intellij/unscramble/UnscrambleListener.java
@@ -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
diff --git a/java/unscramble/src/com/intellij/unscramble/UnscrambleUtils.java b/java/unscramble/src/com/intellij/unscramble/UnscrambleUtils.java
new file mode 100644
index 000000000000..b0a51b9f74c1
--- /dev/null
+++ b/java/unscramble/src/com/intellij/unscramble/UnscrambleUtils.java
@@ -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 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 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 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;
+ }
+}
diff --git a/platform/diagnostic/freezeAnalyzer/testSrc/com/intellij/platform/diagnostic/freezeAnalyzer/FreezeAnalyzerTest.kt b/platform/diagnostic/freezeAnalyzer/testSrc/com/intellij/platform/diagnostic/freezeAnalyzer/FreezeAnalyzerTest.kt
index c15cc270e5fa..104cc21be284 100644
--- a/platform/diagnostic/freezeAnalyzer/testSrc/com/intellij/platform/diagnostic/freezeAnalyzer/FreezeAnalyzerTest.kt
+++ b/platform/diagnostic/freezeAnalyzer/testSrc/com/intellij/platform/diagnostic/freezeAnalyzer/FreezeAnalyzerTest.kt
@@ -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. in DefaultDispatcher-worker-55")
+ FreezeAnalyzer.analyzeFreeze(threadDump)?.additionalMessage?.shouldBe("Possibly locked by com.intellij.codeInsight.completion.JavaMethodCallElement. in DefaultDispatcher-worker-55")
}
@Test
diff --git a/platform/lang-impl/api-dump-unreviewed.txt b/platform/lang-impl/api-dump-unreviewed.txt
index 066075a7bada..cb2ee631ccab 100644
--- a/platform/lang-impl/api-dump-unreviewed.txt
+++ b/platform/lang-impl/api-dump-unreviewed.txt
@@ -18925,6 +18925,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
diff --git a/platform/lang-impl/src/com/intellij/unscramble/AnalyzeStacktraceUtil.java b/platform/lang-impl/src/com/intellij/unscramble/AnalyzeStacktraceUtil.java
index dcefa1ddafc1..ae8790507e7f 100644
--- a/platform/lang-impl/src/com/intellij/unscramble/AnalyzeStacktraceUtil.java
+++ b/platform/lang-impl/src/com/intellij/unscramble/AnalyzeStacktraceUtil.java
@@ -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());
diff --git a/plugins/devkit/devkit-core/gen/org/jetbrains/idea/devkit/DevKitIcons.java b/plugins/devkit/devkit-core/gen/org/jetbrains/idea/devkit/DevKitIcons.java
index c4c06d0040c8..988b8947f3b1 100644
--- a/plugins/devkit/devkit-core/gen/org/jetbrains/idea/devkit/DevKitIcons.java
+++ b/plugins/devkit/devkit-core/gen/org/jetbrains/idea/devkit/DevKitIcons.java
@@ -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);
diff --git a/plugins/devkit/devkit-core/resources/icons/expui/freeze.svg b/plugins/devkit/devkit-core/resources/icons/expui/freeze.svg
new file mode 100644
index 000000000000..ecb650ee10e3
--- /dev/null
+++ b/plugins/devkit/devkit-core/resources/icons/expui/freeze.svg
@@ -0,0 +1,3 @@
+
diff --git a/plugins/devkit/devkit-core/resources/icons/expui/freeze_dark.svg b/plugins/devkit/devkit-core/resources/icons/expui/freeze_dark.svg
new file mode 100644
index 000000000000..5368b88af88d
--- /dev/null
+++ b/plugins/devkit/devkit-core/resources/icons/expui/freeze_dark.svg
@@ -0,0 +1,10 @@
+
diff --git a/plugins/devkit/devkit-core/resources/icons/freeze.svg b/plugins/devkit/devkit-core/resources/icons/freeze.svg
new file mode 100644
index 000000000000..bcac1ece6b48
--- /dev/null
+++ b/plugins/devkit/devkit-core/resources/icons/freeze.svg
@@ -0,0 +1,3 @@
+
diff --git a/plugins/devkit/devkit-core/resources/icons/freeze_dark.svg b/plugins/devkit/devkit-core/resources/icons/freeze_dark.svg
new file mode 100644
index 000000000000..b808fbf16a2e
--- /dev/null
+++ b/plugins/devkit/devkit-core/resources/icons/freeze_dark.svg
@@ -0,0 +1,10 @@
+
diff --git a/plugins/devkit/intellij.devkit.plugin/noKotlin/intellij.devkit.plugin.noKotlin.iml b/plugins/devkit/intellij.devkit.plugin/noKotlin/intellij.devkit.plugin.noKotlin.iml
index 90d24673ed5d..9ca0c3113f70 100644
--- a/plugins/devkit/intellij.devkit.plugin/noKotlin/intellij.devkit.plugin.noKotlin.iml
+++ b/plugins/devkit/intellij.devkit.plugin/noKotlin/intellij.devkit.plugin.noKotlin.iml
@@ -17,5 +17,6 @@
+
\ No newline at end of file
diff --git a/plugins/devkit/intellij.devkit.stacktrace/intellij.devkit.stacktrace.iml b/plugins/devkit/intellij.devkit.stacktrace/intellij.devkit.stacktrace.iml
new file mode 100644
index 000000000000..0189e92bce2f
--- /dev/null
+++ b/plugins/devkit/intellij.devkit.stacktrace/intellij.devkit.stacktrace.iml
@@ -0,0 +1,25 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
\ No newline at end of file
diff --git a/plugins/devkit/intellij.devkit.stacktrace/resources/intellij.devkit.stacktrace.xml b/plugins/devkit/intellij.devkit.stacktrace/resources/intellij.devkit.stacktrace.xml
new file mode 100644
index 000000000000..729bd6223fa7
--- /dev/null
+++ b/plugins/devkit/intellij.devkit.stacktrace/resources/intellij.devkit.stacktrace.xml
@@ -0,0 +1,12 @@
+
+
+
+
+
+
+
+
+
+
+
diff --git a/plugins/devkit/intellij.devkit.stacktrace/resources/messages/DevKitStackTraceBundle.properties b/plugins/devkit/intellij.devkit.stacktrace/resources/messages/DevKitStackTraceBundle.properties
new file mode 100644
index 000000000000..bbe599fc556a
--- /dev/null
+++ b/plugins/devkit/intellij.devkit.stacktrace/resources/messages/DevKitStackTraceBundle.properties
@@ -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
diff --git a/plugins/devkit/intellij.devkit.stacktrace/src/org/jetbrains/idea/devkit/stacktrace/DevKitStackTraceBundle.kt b/plugins/devkit/intellij.devkit.stacktrace/src/org/jetbrains/idea/devkit/stacktrace/DevKitStackTraceBundle.kt
new file mode 100644
index 000000000000..cf8d07ed3963
--- /dev/null
+++ b/plugins/devkit/intellij.devkit.stacktrace/src/org/jetbrains/idea/devkit/stacktrace/DevKitStackTraceBundle.kt
@@ -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)
+}
diff --git a/plugins/devkit/intellij.devkit.stacktrace/src/org/jetbrains/idea/devkit/stacktrace/editor/StackTraceEditorProvider.kt b/plugins/devkit/intellij.devkit.stacktrace/src/org/jetbrains/idea/devkit/stacktrace/editor/StackTraceEditorProvider.kt
new file mode 100644
index 000000000000..63cc568abaea
--- /dev/null
+++ b/plugins/devkit/intellij.devkit.stacktrace/src/org/jetbrains/idea/devkit/stacktrace/editor/StackTraceEditorProvider.kt
@@ -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)
+ }
+
+}
\ No newline at end of file
diff --git a/plugins/devkit/intellij.devkit.stacktrace/src/org/jetbrains/idea/devkit/stacktrace/editor/StackTraceEditorWithPreview.kt b/plugins/devkit/intellij.devkit.stacktrace/src/org/jetbrains/idea/devkit/stacktrace/editor/StackTraceEditorWithPreview.kt
new file mode 100644
index 000000000000..0783a1049523
--- /dev/null
+++ b/plugins/devkit/intellij.devkit.stacktrace/src/org/jetbrains/idea/devkit/stacktrace/editor/StackTraceEditorWithPreview.kt
@@ -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()
+ }
+ }
+}
\ No newline at end of file
diff --git a/plugins/devkit/intellij.devkit.stacktrace/src/org/jetbrains/idea/devkit/stacktrace/editor/StackTraceFileEditor.kt b/plugins/devkit/intellij.devkit.stacktrace/src/org/jetbrains/idea/devkit/stacktrace/editor/StackTraceFileEditor.kt
new file mode 100644
index 000000000000..670e67d4d1e2
--- /dev/null
+++ b/plugins/devkit/intellij.devkit.stacktrace/src/org/jetbrains/idea/devkit/stacktrace/editor/StackTraceFileEditor.kt
@@ -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(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(
+ replay = 1,
+ onBufferOverflow = BufferOverflow.DROP_OLDEST
+ ).apply {
+ coroutineScope.launch(Dispatchers.EDT) {
+ debounce(500.milliseconds)
+ .collectLatest {
+ updateStacktracePane()
+ }
+ }
+ }
+
+ override fun documentChanged(event: DocumentEvent) {
+ documentChangedRequests.tryEmit(Unit)
+ }
+ }
+}
\ No newline at end of file
diff --git a/plugins/devkit/intellij.devkit.stacktrace/src/org/jetbrains/idea/devkit/stacktrace/editor/StackTraceFileEditorProvider.kt b/plugins/devkit/intellij.devkit.stacktrace/src/org/jetbrains/idea/devkit/stacktrace/editor/StackTraceFileEditorProvider.kt
new file mode 100644
index 000000000000..3f835e5781d3
--- /dev/null
+++ b/plugins/devkit/intellij.devkit.stacktrace/src/org/jetbrains/idea/devkit/stacktrace/editor/StackTraceFileEditorProvider.kt
@@ -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
+}
diff --git a/plugins/devkit/intellij.devkit.stacktrace/src/org/jetbrains/idea/devkit/stacktrace/util/StackTracePluginScope.kt b/plugins/devkit/intellij.devkit.stacktrace/src/org/jetbrains/idea/devkit/stacktrace/util/StackTracePluginScope.kt
new file mode 100644
index 000000000000..dd9dfda20df8
--- /dev/null
+++ b/plugins/devkit/intellij.devkit.stacktrace/src/org/jetbrains/idea/devkit/stacktrace/util/StackTracePluginScope.kt
@@ -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().coroutineScope
+ }
+ }
+}
diff --git a/plugins/devkit/intellij.devkit/plugin-content.yaml b/plugins/devkit/intellij.devkit/plugin-content.yaml
index 97cdefbc801e..586a0380cac0 100644
--- a/plugins/devkit/intellij.devkit/plugin-content.yaml
+++ b/plugins/devkit/intellij.devkit/plugin-content.yaml
@@ -23,4 +23,5 @@
- name: intellij.devkit.uiDesigner
- name: intellij.devkit.workspaceModel
- name: intellij.kotlin.devkit
- - name: intellij.devkit.debugger
\ No newline at end of file
+ - name: intellij.devkit.debugger
+ - name: intellij.devkit.stacktrace
\ No newline at end of file
diff --git a/plugins/devkit/intellij.devkit/resources/META-INF/plugin.xml b/plugins/devkit/intellij.devkit/resources/META-INF/plugin.xml
index 6fb246cd5df6..825897ff10e6 100644
--- a/plugins/devkit/intellij.devkit/resources/META-INF/plugin.xml
+++ b/plugins/devkit/intellij.devkit/resources/META-INF/plugin.xml
@@ -28,6 +28,7 @@
+