/*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package liveplugin;
import com.intellij.diagnostic.PluginException;
import com.intellij.execution.ExecutionManager;
import com.intellij.execution.Executor;
import com.intellij.execution.executors.DefaultRunExecutor;
import com.intellij.execution.filters.TextConsoleBuilderFactory;
import com.intellij.execution.ui.ConsoleView;
import com.intellij.execution.ui.ConsoleViewContentType;
import com.intellij.execution.ui.ExecutionConsole;
import com.intellij.execution.ui.RunContentDescriptor;
import com.intellij.execution.ui.actions.CloseAction;
import com.intellij.icons.AllIcons;
import com.intellij.openapi.actionSystem.*;
import com.intellij.openapi.application.ApplicationManager;
import com.intellij.openapi.application.ModalityState;
import com.intellij.openapi.application.ex.ApplicationManagerEx;
import com.intellij.openapi.diagnostic.Logger;
import com.intellij.openapi.extensions.PluginId;
import com.intellij.openapi.fileEditor.FileDocumentManager;
import com.intellij.openapi.fileTypes.FileType;
import com.intellij.openapi.fileTypes.FileTypeManager;
import com.intellij.openapi.progress.PerformInBackgroundOption;
import com.intellij.openapi.progress.ProgressIndicator;
import com.intellij.openapi.progress.Task;
import com.intellij.openapi.project.Project;
import com.intellij.openapi.ui.Messages;
import com.intellij.openapi.util.Pair;
import com.intellij.openapi.vfs.VirtualFile;
import com.intellij.util.download.DownloadableFileDescription;
import com.intellij.util.download.DownloadableFileService;
import com.intellij.util.text.CharArrayUtil;
import org.jetbrains.annotations.NonNls;
import org.jetbrains.annotations.NotNull;
import javax.swing.*;
import java.awt.*;
import java.io.PrintWriter;
import java.io.StringWriter;
import java.net.URL;
import java.util.List;
import java.util.concurrent.ExecutionException;
import java.util.concurrent.ExecutorService;
import static com.intellij.execution.ui.ConsoleViewContentType.ERROR_OUTPUT;
import static com.intellij.openapi.ui.Messages.showOkCancelDialog;
import static com.intellij.util.containers.ContainerUtil.map;
import static java.util.Arrays.asList;
import static java.util.concurrent.Executors.newSingleThreadExecutor;
import static liveplugin.LivePluginAppComponent.LIVE_PLUGIN_ID;
public class IDEUtil {
public static final FileType GROOVY_FILE_TYPE = FileTypeManager.getInstance().getFileTypeByExtension(".groovy");
public static final FileType KOTLIN_FILE_TYPE = FileTypeManager.getInstance().getFileTypeByExtension(".kt");
public static final FileType SCALA_FILE_TYPE = FileTypeManager.getInstance().getFileTypeByExtension(".scala");
public static final FileType CLOJURE_FILE_TYPE = FileTypeManager.getInstance().getFileTypeByExtension(".clj");
public static final DataContext DUMMY_DATA_CONTEXT = dataId -> null;
private static final Logger LOG = Logger.getInstance(IDEUtil.class);
public static void displayError(String consoleTitle, String text, Project project) {
if (project == null) {
// "project" can be null when there are no open projects or while IDE is loading.
// It is important to log error specifying plugin id, otherwise IDE will try to guess
// plugin id based on classes in stacktrace and might get it wrong,
// e.g. if activity tracker plugin is installed, it will include LivePlugin classes as library
// (see com.intellij.diagnostic.IdeErrorsDialog.findPluginId)
LOG.error(consoleTitle, new PluginException(text, PluginId.getId(LIVE_PLUGIN_ID)));
} else {
showInConsole(text, consoleTitle, project, ERROR_OUTPUT);
}
}
public static void showErrorDialog(Project project, String message, String title) {
Messages.showMessageDialog(project, message, title, Messages.getErrorIcon());
}
public static void saveAllFiles() {
ApplicationManager.getApplication().runWriteAction(() -> FileDocumentManager.getInstance().saveAllDocuments());
}
public static void runAction(final AnAction action, String place) {
final AnActionEvent event = new AnActionEvent(
null,
DUMMY_DATA_CONTEXT,
place,
action.getTemplatePresentation(),
ActionManager.getInstance(),
0
);
ApplicationManager.getApplication().invokeLater(() -> action.actionPerformed(event));
}
public static boolean isOnClasspath(String className) {
URL resource = IDEUtil.class.getClassLoader().getResource(className.replace(".", "/") + ".class");
return resource != null;
}
public static void askIfUserWantsToRestartIde(String message) {
int answer = showOkCancelDialog(message, "Restart Is Required", "Restart", "Postpone", Messages.getQuestionIcon());
if (answer == Messages.OK) {
ApplicationManagerEx.getApplicationEx().restart(true);
}
}
public static boolean downloadFile(String downloadUrl, String fileName, String targetPath) {
//noinspection unchecked
return downloadFiles(asList(Pair.create(downloadUrl, fileName)), targetPath);
}
// TODO make download non-modal
public static boolean downloadFiles(List<Pair<String, String>> urlAndFileNames, String targetPath) {
final DownloadableFileService service = DownloadableFileService.getInstance();
List<DownloadableFileDescription> descriptions = map(urlAndFileNames, it -> service.createFileDescription(it.first + it.second, it.second));
List<VirtualFile> files = service.createDownloader(descriptions, "").downloadFilesWithProgress(targetPath, null, null);
return files != null && files.size() == urlAndFileNames.size();
}
public static String unscrambleThrowable(Throwable throwable) {
StringWriter writer = new StringWriter();
//noinspection ThrowableResultOfMethodCallIgnored
throwable.printStackTrace(new PrintWriter(writer));
return Unscramble.normalizeText(writer.getBuffer().toString());
}
private static void showInConsole(final String message, final String consoleTitle, @NotNull final Project project,
final ConsoleViewContentType contentType) {
Runnable runnable = () -> {
ConsoleView console = TextConsoleBuilderFactory.getInstance().createBuilder(project).getConsole();
console.print(message, contentType);
DefaultActionGroup toolbarActions = new DefaultActionGroup();
JPanel consoleComponent = new MyConsolePanel(console, toolbarActions);
RunContentDescriptor descriptor = new RunContentDescriptor(console, null, consoleComponent, consoleTitle) {
@Override public boolean isContentReuseProhibited() { return true; }
@Override public Icon getIcon() { return AllIcons.Nodes.Plugin; }
};
Executor executor = DefaultRunExecutor.getRunExecutorInstance();
toolbarActions.add(new CloseAction(executor, descriptor, project));
for (AnAction anAction : console.createConsoleActions()) {
toolbarActions.add(anAction);
}
ExecutionManager.getInstance(project).getContentManager().showRunContent(executor, descriptor);
};
ApplicationManager.getApplication().invokeAndWait(runnable, ModalityState.NON_MODAL);
}
private static class MyConsolePanel extends JPanel {
MyConsolePanel(ExecutionConsole consoleView, ActionGroup toolbarActions) {
super(new BorderLayout());
JPanel toolbarPanel = new JPanel(new BorderLayout());
toolbarPanel.add(ActionManager.getInstance().createActionToolbar(ActionPlaces.UNKNOWN, toolbarActions, false).getComponent());
add(toolbarPanel, BorderLayout.WEST);
add(consoleView.getComponent(), BorderLayout.CENTER);
}
}
public static class SingleThreadBackgroundRunner {
private final ExecutorService singleThreadExecutor;
public SingleThreadBackgroundRunner(final String threadName) {
singleThreadExecutor = newSingleThreadExecutor(runnable -> new Thread(runnable, threadName));
}
public void run(Project project, String taskDescription, final Runnable runnable) {
new Task.Backgroundable(project, taskDescription, false, PerformInBackgroundOption.ALWAYS_BACKGROUND) {
@Override public void run(@NotNull ProgressIndicator indicator) {
try {
singleThreadExecutor.submit(runnable).get();
} catch (InterruptedException | ExecutionException ignored) {
}
}
}.queue();
}
}
/**
* Copy-pasted from {@code UnscrambleDialog#normalizeText(String)}
* because PhpStorm doesn't have this class.
*/
private static class Unscramble {
public static String normalizeText(@NonNls String text) {
StringBuilder builder = new StringBuilder(text.length());
text = text.replaceAll("(\\S[ \\t\\x0B\\f\\r]+)(at\\s+)", "$1\n$2");
String[] lines = text.split("\n");
boolean first = true;
boolean inAuxInfo = false;
for (String line : lines) {
//noinspection HardCodedStringLiteral
if (!inAuxInfo && (line.startsWith("JNI global references") || line.trim().equals("Heap"))) {
builder.append("\n");
inAuxInfo = true;
}
if (inAuxInfo) {
builder.append(trimSuffix(line)).append("\n");
continue;
}
if (!first && mustHaveNewLineBefore(line)) {
builder.append("\n");
if (line.startsWith("\"")) builder.append("\n"); // Additional line break for thread names
}
first = false;
int i = builder.lastIndexOf("\n");
CharSequence lastLine = i == -1 ? builder : builder.subSequence(i + 1, builder.length());
if (lastLine.toString().matches("\\s*at") && !line.matches("\\s+.*")) builder.append(" "); // separate 'at' from file name
builder.append(trimSuffix(line));
}
return builder.toString();
}
@SuppressWarnings("RedundantIfStatement")
private static boolean mustHaveNewLineBefore(String line) {
final int nonWs = CharArrayUtil.shiftForward(line, 0, " \t");
if (nonWs < line.length()) {
line = line.substring(nonWs);
}
if (line.startsWith("at")) return true; // Start of the new stack frame entry
if (line.startsWith("Caused")) return true; // Caused by message
if (line.startsWith("- locked")) return true; // "Locked a monitor" logging
if (line.startsWith("- waiting")) return true; // "Waiting for monitor" logging
if (line.startsWith("- parking to wait")) return true;
if (line.startsWith("java.lang.Thread.State")) return true;
if (line.startsWith("\"")) return true; // Start of the new thread (thread name)
return false;
}
private static String trimSuffix(final String line) {
int len = line.length();
while ((0 < len) && (line.charAt(len-1) <= ' ')) {
len--;
}
return (len < line.length()) ? line.substring(0, len) : line;
}
}
}