Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Move Pants output to console view instead of notification channel #242

Merged
merged 27 commits into from
Jan 13, 2017
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 1 addition & 1 deletion common/com/twitter/intellij/pants/util/PantsConstants.java
Original file line number Diff line number Diff line change
Expand Up @@ -12,7 +12,7 @@

public class PantsConstants {
public static final String PANTS = "pants";
public static final String PLUGIN = "pants_plugin";
public static final String PANTS_CONSOLE_NAME = "PantsConsole";
public static final String PLUGIN_ID = "com.intellij.plugins.pants";

@NotNull
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -28,6 +28,7 @@
import com.twitter.intellij.pants.service.project.PantsResolver;
import com.twitter.intellij.pants.settings.PantsProjectSettings;
import com.twitter.intellij.pants.settings.PantsSettings;
import com.twitter.intellij.pants.ui.PantsConsoleManager;
import com.twitter.intellij.pants.util.PantsConstants;
import com.twitter.intellij.pants.util.PantsUtil;
import icons.PantsIcons;
Expand All @@ -46,12 +47,14 @@ protected PantsProjectComponentImpl(Project project) {
public void projectClosed() {
PantsMetrics.report();
FileChangeTracker.unregisterProject(myProject);
PantsConsoleManager.unregisterConsole(myProject);
super.projectClosed();
}

@Override
public void projectOpened() {
PantsMetrics.initialize();
PantsConsoleManager.registerConsole(myProject);
super.projectOpened();
if (myProject.isDefault()) {
return;
Expand Down
181 changes: 136 additions & 45 deletions src/com/twitter/intellij/pants/execution/PantsMakeBeforeRun.java
Original file line number Diff line number Diff line change
Expand Up @@ -11,12 +11,16 @@
import com.intellij.execution.configurations.GeneralCommandLine;
import com.intellij.execution.configurations.RunConfiguration;
import com.intellij.execution.configurations.RunProfileWithCompileBeforeLaunchOption;
import com.intellij.execution.filters.Filter;
import com.intellij.execution.filters.OpenFileHyperlinkInfo;
import com.intellij.execution.impl.RunManagerImpl;
import com.intellij.execution.process.CapturingAnsiEscapesAwareProcessHandler;
import com.intellij.execution.process.CapturingProcessHandler;
import com.intellij.execution.process.ProcessAdapter;
import com.intellij.execution.process.ProcessEvent;
import com.intellij.execution.runners.ExecutionEnvironment;
import com.intellij.execution.ui.ConsoleView;
import com.intellij.execution.ui.ConsoleViewContentType;
import com.intellij.notification.Notification;
import com.intellij.notification.NotificationType;
import com.intellij.notification.Notifications;
Expand All @@ -25,22 +29,20 @@
import com.intellij.openapi.application.ModalityState;
import com.intellij.openapi.externalSystem.service.execution.ExternalSystemBeforeRunTask;
import com.intellij.openapi.externalSystem.service.execution.ExternalSystemBeforeRunTaskProvider;
import com.intellij.openapi.externalSystem.service.notification.ExternalSystemNotificationManager;
import com.intellij.openapi.externalSystem.service.notification.NotificationCategory;
import com.intellij.openapi.externalSystem.service.notification.NotificationData;
import com.intellij.openapi.externalSystem.service.notification.NotificationSource;
import com.intellij.openapi.fileEditor.FileDocumentManager;
import com.intellij.openapi.module.Module;
import com.intellij.openapi.module.ModuleManager;
import com.intellij.openapi.project.Project;
import com.intellij.openapi.util.Key;
import com.intellij.openapi.util.Pair;
import com.intellij.openapi.util.text.StringUtil;
import com.intellij.openapi.vfs.LocalFileSystem;
import com.intellij.openapi.vfs.VirtualFile;
import com.intellij.openapi.wm.ToolWindowManager;
import com.twitter.intellij.pants.PantsBundle;
import com.twitter.intellij.pants.file.FileChangeTracker;
import com.twitter.intellij.pants.model.PantsOptions;
import com.twitter.intellij.pants.settings.PantsSettings;
import com.twitter.intellij.pants.ui.PantsConsoleManager;
import com.twitter.intellij.pants.util.PantsConstants;
import com.twitter.intellij.pants.util.PantsUtil;
import icons.PantsIcons;
Expand All @@ -53,6 +55,7 @@
import java.util.Collections;
import java.util.HashSet;
import java.util.List;
import java.util.Objects;
import java.util.Optional;
import java.util.Set;

Expand All @@ -67,6 +70,8 @@
public class PantsMakeBeforeRun extends ExternalSystemBeforeRunTaskProvider {

public static final Key<ExternalSystemBeforeRunTask> ID = Key.create("Pants.BeforeRunTask");
public static final String ERROR_TAG = "[error]";


public PantsMakeBeforeRun(@NotNull Project project) {
super(PantsConstants.SYSTEM_ID, project, ID);
Expand Down Expand Up @@ -177,7 +182,7 @@ public Pair<Boolean, Optional<String>> executeTask(Project currentProject, Set<S

prepareIDE(currentProject);
if (targetAddressesToCompile.isEmpty()) {
showPantsMakeTaskMessage("No target found in configuration.", NotificationCategory.INFO, currentProject);
showPantsMakeTaskMessage("No target found in configuration.\n", ConsoleViewContentType.SYSTEM_OUTPUT, currentProject);
return Pair.create(true, Optional.empty());
}

Expand All @@ -192,10 +197,10 @@ public Pair<Boolean, Optional<String>> executeTask(Project currentProject, Set<S
}
final GeneralCommandLine commandLine = PantsUtil.defaultCommandLine(pantsExecutable.get().getPath());

showPantsMakeTaskMessage("Checking Pants options...", NotificationCategory.INFO, currentProject);
showPantsMakeTaskMessage("Checking Pants options...\n", ConsoleViewContentType.SYSTEM_OUTPUT, currentProject);
Optional<PantsOptions> pantsOptional = PantsOptions.getPantsOptions(currentProject);
if (!pantsOptional.isPresent()) {
showPantsMakeTaskMessage("Pants Options not found.", NotificationCategory.ERROR, currentProject);
showPantsMakeTaskMessage("Pants Options not found.\n", ConsoleViewContentType.ERROR_OUTPUT, currentProject);
return Pair.create(false, Optional.empty());
}

Expand All @@ -220,7 +225,7 @@ public Pair<Boolean, Optional<String>> executeTask(Project currentProject, Set<S
commandLine.addParameter(PantsUtil.getJvmDistributionPathParameter(PantsUtil.getJdkPathFromIntelliJCore()));
}
catch (Exception e) {
showPantsMakeTaskMessage(e.getMessage(), NotificationCategory.ERROR, currentProject);
showPantsMakeTaskMessage(e.getMessage(), ConsoleViewContentType.ERROR_OUTPUT, currentProject);
return Pair.create(false, Optional.empty());
}
}
Expand All @@ -236,17 +241,17 @@ public Pair<Boolean, Optional<String>> executeTask(Project currentProject, Set<S
process = commandLine.createProcess();
}
catch (ExecutionException e) {
showPantsMakeTaskMessage(e.getMessage(), NotificationCategory.ERROR, currentProject);
showPantsMakeTaskMessage(e.getMessage(), ConsoleViewContentType.ERROR_OUTPUT, currentProject);
return Pair.create(false, Optional.empty());
}

final CapturingProcessHandler processHandler = new CapturingAnsiEscapesAwareProcessHandler(process, commandLine.getCommandLineString());
addMessageHandler(processHandler, currentProject);
final List<String> output = new ArrayList<>();
processHandler.addProcessListener(new ProcessAdapter() {
@Override
public void onTextAvailable(ProcessEvent event, Key outputType) {
super.onTextAvailable(event, outputType);
showPantsMakeTaskMessage(event.getText(), ConsoleViewContentType.NORMAL_OUTPUT, currentProject);
output.add(event.getText());
}
});
Expand Down Expand Up @@ -286,41 +291,15 @@ private void prepareIDE(Project project) {
@Override
public void run() {
/* Clear message window. */
ExternalSystemNotificationManager.getInstance(project)
.clearNotifications(NotificationSource.TASK_EXECUTION, PantsConstants.SYSTEM_ID);
ConsoleView executionConsole = PantsConsoleManager.getOrMakeNewConsole(project);
executionConsole.getComponent().setVisible(true);
executionConsole.clear();
ToolWindowManager.getInstance(project).getToolWindow(PantsConstants.PANTS_CONSOLE_NAME).activate(null);
/* Force cached changes to disk. */
FileDocumentManager.getInstance().saveAllDocuments();
project.save();
}
}, ModalityState.NON_MODAL);

ExternalSystemNotificationManager.getInstance(project).openMessageView(PantsConstants.SYSTEM_ID, NotificationSource.TASK_EXECUTION);
}

private void addMessageHandler(CapturingProcessHandler processHandler, Project project) {
processHandler.addProcessListener(
new ProcessAdapter() {
@Override
public void onTextAvailable(ProcessEvent event, Key outputType) {
super.onTextAvailable(event, outputType);
String output = event.getText();
if (StringUtil.isEmptyOrSpaces(output)) {
return;
}
NotificationCategory notificationCategory = NotificationCategory.INFO;
if (output.contains("[warn]")) {
notificationCategory = NotificationCategory.WARNING;
}
else if (output.contains("[error]")) {
notificationCategory = NotificationCategory.ERROR;
}

NotificationData notification =
new NotificationData(PantsConstants.PANTS, output, notificationCategory, NotificationSource.TASK_EXECUTION);
ExternalSystemNotificationManager.getInstance(project).showNotification(PantsConstants.SYSTEM_ID, notification);
}
}
);
}

@NotNull
Expand Down Expand Up @@ -348,9 +327,121 @@ private Set<String> getTargetAddressesToCompile(Module[] targetModules) {
return result;
}

private void showPantsMakeTaskMessage(String message, NotificationCategory type, Project project) {
NotificationData notification =
new NotificationData(PantsConstants.PANTS, message, type, NotificationSource.TASK_EXECUTION);
ExternalSystemNotificationManager.getInstance(project).showNotification(PantsConstants.SYSTEM_ID, notification);

private void showPantsMakeTaskMessage(String message, ConsoleViewContentType type, Project project) {
ConsoleView executionConsole = PantsConsoleManager.getOrMakeNewConsole(project);
// Create a filter that monitors console outputs, and turns them into a hyperlink if applicable.
Filter filter = new Filter() {
@Nullable
@Override
public Result applyFilter(String line, int entireLength) {
Optional<ParseResult> result = ParseResult.parseErrorLocation(line, ERROR_TAG);
if (result.isPresent()) {

OpenFileHyperlinkInfo linkInfo = new OpenFileHyperlinkInfo(
project,
result.get().getFile(),
result.get().getLineNumber() - 1, // line number needs to be 0 indexed
result.get().getColumnNumber() - 1 // column number needs to be 0 indexed
);
int startHyperlink = entireLength - line.length() + line.indexOf(ERROR_TAG);

return new Result(
startHyperlink,
entireLength,
linkInfo,
null // TextAttributes, going with default hence null
);
}
return null;
}
};

ApplicationManager.getApplication().invokeLater(new Runnable() {
@Override
public void run() {
executionConsole.addMessageFilter(filter);
executionConsole.print(message, type);
}
}, ModalityState.NON_MODAL);
}

/**
* Encapsulate the result of parsed data.
*/
static class ParseResult {
private VirtualFile file;
private int lineNumber;
private int columnNumber;


/**
* This function parses Pants output against known file and tag,
* and returns (file, line number, column number)
* encapsulated in `ParseResult` object if the output contains valid information.
*
* @param line original Pants output
* @param tag known tag. e.g. [error]
* @return `ParseResult` instance
*/
public static Optional<ParseResult> parseErrorLocation(String line, String tag) {
if (!line.contains(tag)) {
return Optional.empty();
}

String[] splitByColon = line.split(":");
if (splitByColon.length < 3) {
return Optional.empty();
}

try {
// filePath path is between tag and first colon
String filePath = splitByColon[0].substring(splitByColon[0].indexOf(tag) + tag.length()).trim();
VirtualFile virtualFile = LocalFileSystem.getInstance().findFileByPath(filePath);
if (virtualFile == null) {
return Optional.empty();
}
// line number is between first and second colon
int lineNumber = Integer.valueOf(splitByColon[1]);
// column number is between second and third colon
int columnNumber = Integer.valueOf(splitByColon[2]);
return Optional.of(new ParseResult(virtualFile, lineNumber, columnNumber));
}
catch (NumberFormatException e) {
return Optional.empty();
}
}

private ParseResult(VirtualFile file, int lineNumber, int columnNumber) {
this.file = file;
this.lineNumber = lineNumber;
this.columnNumber = columnNumber;
}

@Override
public boolean equals(Object obj) {
if (obj == null) {
return false;
}
if (getClass() != obj.getClass()) {
return false;
}
ParseResult other = (ParseResult) obj;
return Objects.equals(file, other.file)
&& Objects.equals(lineNumber, other.lineNumber)
&& Objects.equals(columnNumber, other.columnNumber);
}

public VirtualFile getFile() {
return file;
}

public int getLineNumber() {
return lineNumber;
}

public int getColumnNumber() {
return columnNumber;
}
}
}
78 changes: 78 additions & 0 deletions src/com/twitter/intellij/pants/ui/PantsConsoleManager.java
Original file line number Diff line number Diff line change
@@ -0,0 +1,78 @@
// Copyright 2017 Pants project contributors (see CONTRIBUTORS.md).
// Licensed under the Apache License, Version 2.0 (see LICENSE).

package com.twitter.intellij.pants.ui;

import com.intellij.execution.filters.TextConsoleBuilderFactory;
import com.intellij.execution.ui.ConsoleView;
import com.intellij.openapi.project.Project;
import com.intellij.openapi.util.Disposer;
import com.intellij.openapi.wm.ToolWindow;
import com.intellij.openapi.wm.ToolWindowAnchor;
import com.intellij.openapi.wm.ToolWindowManager;
import com.intellij.ui.content.impl.TabbedContentImpl;
import com.twitter.intellij.pants.util.PantsConstants;
import org.jetbrains.annotations.TestOnly;

import java.util.Map;
import java.util.concurrent.ConcurrentHashMap;


public class PantsConsoleManager {
private static ConcurrentHashMap<Project, ConsoleView> mapper = new ConcurrentHashMap<>();

public static void registerConsole(Project project) {
ToolWindow window =
ToolWindowManager.getInstance(project).registerToolWindow(
PantsConstants.PANTS_CONSOLE_NAME,
true,
ToolWindowAnchor.BOTTOM,
project,
true
);
ConsoleView console = getOrMakeNewConsole(project);
Disposer.register(project, console);
TabbedContentImpl content = new TabbedContentImpl(console.getComponent(), "", true, "");
window.getContentManager().addContent(content);
}

/**
* Creates a `ConsoleView` for the current project, and register it under `PantsConsole` tool window,
* or just retrieve one if there is already one registered.
*
* @param project current project
* @return Pants ConsoleView for the project
*/
public static ConsoleView getOrMakeNewConsole(Project project) {
ConsoleView console = mapper.get(project);
if (console != null) {
return console;
}
ConsoleView newConsole = TextConsoleBuilderFactory.getInstance().createBuilder(project).getConsole();
mapper.put(project, newConsole);
return newConsole;
}

/**
* Close the console for a project.
*
* @param project current project
*/
public static void unregisterConsole(Project project) {
ConsoleView console = mapper.get(project);
if (console != null) {
console.dispose();
}
mapper.remove(project);
}

/**
* TestOnly because some test library is not tearing down properly.
*/
@TestOnly
public static void disposeAll() {
for (Map.Entry<Project, ConsoleView> entrySet : mapper.entrySet()) {
entrySet.getValue().dispose();
}
}
}
Loading