save console output to file (IDEA-64394)

This commit is contained in:
anna
2011-10-21 13:15:02 +02:00
parent 33ff3c8264
commit 7dc0091138
15 changed files with 278 additions and 11 deletions
@@ -292,7 +292,7 @@ public class DebuggerSessionTab extends DebuggerSessionTabBase implements Dispos
}
console.setActions(consoleActions, ActionPlaces.DEBUGGER_TOOLBAR, myConsole.getPreferredFocusableComponent());
initLogConsoles(myConfiguration, myRunContentDescriptor.getProcessHandler());
initLogConsoles(myConfiguration, myRunContentDescriptor.getProcessHandler(), myConsole);
DefaultActionGroup group = new DefaultActionGroup();
final Executor executor = DefaultDebugExecutor.getDebugExecutorInstance();
@@ -41,9 +41,16 @@ public abstract class RunConfigurationBase extends UserDataHolderBase implements
private ArrayList<LogFileOptions> myLogFiles = new ArrayList<LogFileOptions>();
private ArrayList<PredefinedLogFile> myPredefinedLogFiles = new ArrayList<PredefinedLogFile>();
@NonNls private static final String LOG_FILE = "log_file";
@NonNls private static final String PREDEFINED_LOG_FILE_ELEMENT = "predefined_log_file";
@NonNls private static final String FILE_OUTPUT = "output_file";
@NonNls private static final String SAVE = "is_save";
@NonNls private static final String OUTPUT_FILE = "path";
private final Icon myIcon;
private boolean mySaveOutput = false;
private String myFileOutputPath = null;
protected RunConfigurationBase(final Project project, final ConfigurationFactory factory, final String name) {
myProject = project;
@@ -97,6 +104,8 @@ public abstract class RunConfigurationBase extends UserDataHolderBase implements
final RunConfigurationBase runConfiguration = (RunConfigurationBase)super.clone();
runConfiguration.myLogFiles = new ArrayList<LogFileOptions>(myLogFiles);
runConfiguration.myPredefinedLogFiles = new ArrayList<PredefinedLogFile>(myPredefinedLogFiles);
runConfiguration.myFileOutputPath = myFileOutputPath;
runConfiguration.mySaveOutput = mySaveOutput;
copyCopyableDataTo(runConfiguration);
return runConfiguration;
}
@@ -167,6 +176,12 @@ public abstract class RunConfigurationBase extends UserDataHolderBase implements
logFile.readExternal((Element)fileElement);
myPredefinedLogFiles.add(logFile);
}
final Element fileOutputElement = element.getChild(FILE_OUTPUT);
if (fileOutputElement != null) {
myFileOutputPath = fileOutputElement.getAttributeValue(OUTPUT_FILE);
final String isSave = fileOutputElement.getAttributeValue(SAVE);
mySaveOutput = isSave != null && Boolean.parseBoolean(isSave);
}
}
public void writeExternal(Element element) throws WriteExternalException {
@@ -180,6 +195,34 @@ public abstract class RunConfigurationBase extends UserDataHolderBase implements
predefinedLogFile.writeExternal(fileElement);
element.addContent(fileElement);
}
final Element fileOutputPathElement = new Element(FILE_OUTPUT);
if (myFileOutputPath != null) {
fileOutputPathElement.setAttribute(OUTPUT_FILE, myFileOutputPath);
}
fileOutputPathElement.setAttribute(SAVE, String.valueOf(mySaveOutput));
if (myFileOutputPath != null || mySaveOutput) {
element.addContent(fileOutputPathElement);
}
}
public boolean isSaveOutputToFile() {
return mySaveOutput;
}
public void setSaveOutputToFile(boolean redirectOutput) {
mySaveOutput = redirectOutput;
}
public String getOutputFilePath() {
return myFileOutputPath;
}
public void setFileOutputPath(String fileOutputPath) {
myFileOutputPath = fileOutputPath;
}
public boolean collectOutputFromProcessHandler() {
return true;
}
public boolean excludeCompileBeforeLaunchOption() {
@@ -1,6 +1,6 @@
<?xml version="1.0" encoding="UTF-8"?>
<form xmlns="http://www.intellij.com/uidesigner/form/" version="1" bind-to-class="com.intellij.diagnostic.logging.LogConfigurationPanel">
<grid id="57687" binding="myWholePanel" layout-manager="GridLayoutManager" row-count="1" column-count="2" same-size-horizontally="false" same-size-vertically="false" hgap="-1" vgap="-1">
<grid id="57687" binding="myWholePanel" layout-manager="GridLayoutManager" row-count="3" column-count="3" same-size-horizontally="false" same-size-vertically="false" hgap="-1" vgap="-1">
<margin top="0" left="0" bottom="0" right="0"/>
<constraints>
<xy x="25" y="74" width="392" height="210"/>
@@ -14,7 +14,7 @@
<grid id="93252" layout-manager="GridLayoutManager" row-count="4" column-count="1" same-size-horizontally="false" same-size-vertically="false" hgap="-1" vgap="-1">
<margin top="0" left="2" bottom="0" right="2"/>
<constraints>
<grid row="0" column="1" row-span="1" col-span="1" vsize-policy="3" hsize-policy="0" anchor="0" fill="3" indent="0" use-parent-layout="false"/>
<grid row="0" column="2" row-span="1" col-span="1" vsize-policy="3" hsize-policy="0" anchor="0" fill="3" indent="0" use-parent-layout="false"/>
</constraints>
<properties/>
<border type="none"/>
@@ -52,12 +52,32 @@
</grid>
<grid id="faa06" binding="myScrollPanel" layout-manager="BorderLayout" hgap="0" vgap="0">
<constraints>
<grid row="0" column="0" row-span="1" col-span="1" vsize-policy="3" hsize-policy="3" anchor="0" fill="3" indent="0" use-parent-layout="false"/>
<grid row="0" column="0" row-span="1" col-span="2" vsize-policy="3" hsize-policy="3" anchor="0" fill="3" indent="0" use-parent-layout="false"/>
</constraints>
<properties/>
<border type="none"/>
<children/>
</grid>
<component id="37d5" class="com.intellij.ui.components.JBCheckBox" binding="myRedirectOutputCb" default-binding="true">
<constraints>
<grid row="1" column="0" row-span="1" col-span="1" vsize-policy="0" hsize-policy="0" anchor="0" fill="0" indent="0" use-parent-layout="false"/>
</constraints>
<properties>
<margin top="7" left="3" bottom="2" right="3"/>
<text value="&amp;Save console output to file:"/>
</properties>
</component>
<hspacer id="9d463">
<constraints>
<grid row="1" column="1" row-span="1" col-span="1" vsize-policy="1" hsize-policy="6" anchor="0" fill="1" indent="0" use-parent-layout="false"/>
</constraints>
</hspacer>
<component id="c07c6" class="com.intellij.openapi.ui.TextFieldWithBrowseButton" binding="myOutputFile">
<constraints>
<grid row="2" column="0" row-span="1" col-span="2" vsize-policy="0" hsize-policy="6" anchor="0" fill="1" indent="1" use-parent-layout="false"/>
</constraints>
<properties/>
</component>
</children>
</grid>
</form>
@@ -20,13 +20,18 @@ import com.intellij.diagnostic.DiagnosticBundle;
import com.intellij.execution.configurations.LogFileOptions;
import com.intellij.execution.configurations.PredefinedLogFile;
import com.intellij.execution.configurations.RunConfigurationBase;
import com.intellij.openapi.fileChooser.FileChooserDescriptorFactory;
import com.intellij.openapi.options.ConfigurationException;
import com.intellij.openapi.options.SettingsEditor;
import com.intellij.openapi.ui.TextComponentAccessor;
import com.intellij.openapi.ui.TextFieldWithBrowseButton;
import com.intellij.openapi.util.Comparing;
import com.intellij.openapi.util.io.FileUtil;
import com.intellij.openapi.util.text.StringUtil;
import com.intellij.ui.BooleanTableCellRenderer;
import com.intellij.ui.ScrollPaneFactory;
import com.intellij.ui.TableUtil;
import com.intellij.ui.components.JBCheckBox;
import com.intellij.ui.table.TableView;
import com.intellij.util.ui.AbstractTableCellEditor;
import com.intellij.util.ui.CellEditorComponentWithBrowseButton;
@@ -58,6 +63,8 @@ public class LogConfigurationPanel<T extends RunConfigurationBase> extends Setti
private JButton myRemoveButton;
private JPanel myScrollPanel;
private JButton myEditButton;
private JBCheckBox myRedirectOutputCb;
private TextFieldWithBrowseButton myOutputFile;
private final Map<LogFileOptions, PredefinedLogFile> myLog2Predefined = new HashMap<LogFileOptions, PredefinedLogFile>();
private final List<PredefinedLogFile> myUnresolvedPredefined = new ArrayList<PredefinedLogFile>();
@@ -144,6 +151,15 @@ public class LogConfigurationPanel<T extends RunConfigurationBase> extends Setti
scrollPane.setVerticalScrollBarPolicy(ScrollPaneConstants.VERTICAL_SCROLLBAR_AS_NEEDED);
myScrollPanel.add(scrollPane, BorderLayout.CENTER);
myWholePanel.setPreferredSize(new Dimension(-1, 150));
myOutputFile.addBrowseFolderListener("Choose File to Save Console Output", "Console output would be saved to the specified file", null,
FileChooserDescriptorFactory.createSingleFileOrFolderDescriptor(),
TextComponentAccessor.TEXT_FIELD_WHOLE_TEXT);
myRedirectOutputCb.addActionListener(new ActionListener() {
@Override
public void actionPerformed(ActionEvent e) {
myOutputFile.setEnabled(myRedirectOutputCb.isSelected());
}
});
}
private void setUpColumnWidth(final JTableHeader tableHeader, final int preferredWidth, int columnIdx) {
@@ -220,6 +236,11 @@ public class LogConfigurationPanel<T extends RunConfigurationBase> extends Setti
}
}
myModel.setItems(list);
final boolean redirectOutputToFile = configuration.isSaveOutputToFile();
myRedirectOutputCb.setSelected(redirectOutputToFile);
final String fileOutputPath = configuration.getOutputFilePath();
myOutputFile.setText(fileOutputPath != null ? FileUtil.toSystemDependentName(fileOutputPath) : "");
myOutputFile.setEnabled(redirectOutputToFile);
}
protected void applyEditorTo(final RunConfigurationBase configuration) throws ConfigurationException {
@@ -244,7 +265,10 @@ public class LogConfigurationPanel<T extends RunConfigurationBase> extends Setti
}
for (PredefinedLogFile logFile : myUnresolvedPredefined) {
configuration.addPredefinedLogFile(logFile);
}
}
final String text = myOutputFile.getText();
configuration.setFileOutputPath(StringUtil.isEmpty(text) ? null : FileUtil.toSystemIndependentName(text));
configuration.setSaveOutputToFile(myRedirectOutputCb.isSelected());
}
@NotNull
@@ -20,6 +20,7 @@ import com.intellij.execution.configurations.LogFileOptions;
import com.intellij.execution.configurations.RunConfigurationBase;
import com.intellij.execution.process.ProcessHandler;
import com.intellij.openapi.Disposable;
import com.intellij.openapi.diagnostic.Logger;
import com.intellij.openapi.project.Project;
import com.intellij.openapi.util.Condition;
import com.intellij.openapi.util.Disposer;
@@ -35,6 +36,8 @@ import java.util.*;
* Date: 01-Feb-2006
*/
public class LogFilesManager implements Disposable {
public static final Logger LOG = Logger.getInstance("#" + LogFilesManager.class.getName());
private static final int UPDATE_INTERVAL = 500;
private final Map<LogFileOptions, Set<String>> myLogFileManagerMap = new LinkedHashMap<LogFileOptions, Set<String>>();
@@ -0,0 +1,125 @@
/*
* Copyright 2000-2011 JetBrains s.r.o.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package com.intellij.diagnostic.logging;
import com.intellij.execution.configurations.RunConfigurationBase;
import com.intellij.execution.filters.Filter;
import com.intellij.execution.filters.HyperlinkInfo;
import com.intellij.execution.process.ProcessAdapter;
import com.intellij.execution.process.ProcessEvent;
import com.intellij.execution.process.ProcessHandler;
import com.intellij.execution.process.ProcessOutputTypes;
import com.intellij.execution.ui.ConsoleView;
import com.intellij.execution.ui.ExecutionConsole;
import com.intellij.openapi.application.ApplicationManager;
import com.intellij.openapi.fileEditor.FileEditorManager;
import com.intellij.openapi.fileEditor.OpenFileDescriptor;
import com.intellij.openapi.project.Project;
import com.intellij.openapi.util.Computable;
import com.intellij.openapi.util.Key;
import com.intellij.openapi.util.io.FileUtil;
import com.intellij.openapi.util.text.StringUtil;
import com.intellij.openapi.vfs.LocalFileSystem;
import com.intellij.openapi.vfs.VirtualFile;
import org.jetbrains.annotations.Nullable;
import java.io.File;
import java.io.FileNotFoundException;
import java.io.FileOutputStream;
import java.io.PrintStream;
/**
* User: anna
* Date: 10/20/11
*/
public class OutputFileUtil {
private static final String CONSOLE_OUTPUT_FILE_MESSAGE = "Console output is saving to: ";
private OutputFileUtil() {
}
public static void attachDumpListener(final RunConfigurationBase base, final ProcessHandler startedProcess, ExecutionConsole console) {
if (base.isSaveOutputToFile()) {
final String outputFilePath = base.getOutputFilePath();
if (outputFilePath != null) {
final String filePath = FileUtil.toSystemDependentName(outputFilePath);
startedProcess.addProcessListener(new ProcessAdapter() {
private PrintStream myOutput;
@Override
public void onTextAvailable(ProcessEvent event, Key outputType) {
if (base.collectOutputFromProcessHandler() && myOutput != null && outputType != ProcessOutputTypes.SYSTEM) {
myOutput.print(event.getText());
}
}
@Override
public void startNotified(ProcessEvent event) {
try {
myOutput = new PrintStream(new FileOutputStream(new File(filePath)));
}
catch (FileNotFoundException ignored) {
}
startedProcess.notifyTextAvailable(CONSOLE_OUTPUT_FILE_MESSAGE + filePath + "\n", ProcessOutputTypes.SYSTEM);
}
@Override
public void processTerminated(ProcessEvent event) {
startedProcess.removeProcessListener(this);
if (myOutput != null) {
myOutput.close();
}
}
});
if (console instanceof ConsoleView) {
((ConsoleView)console).addMessageFilter(new ShowOutputFileFilter());
}
}
}
}
private static class ShowOutputFileFilter implements Filter {
@Override
public Result applyFilter(String line, int entireLength) {
if (line.startsWith(CONSOLE_OUTPUT_FILE_MESSAGE)) {
final String filePath = StringUtil.trimEnd(line.substring(CONSOLE_OUTPUT_FILE_MESSAGE.length()), "\n");
return new Result(entireLength - filePath.length() - 1, entireLength, new HyperlinkInfo() {
@Override
public void navigate(final Project project) {
final VirtualFile file =
ApplicationManager.getApplication().runWriteAction(new Computable<VirtualFile>() {
@Nullable
@Override
public VirtualFile compute() {
return LocalFileSystem.getInstance().refreshAndFindFileByPath(FileUtil.toSystemIndependentName(filePath));
}
});
if (file != null) {
file.refresh(false, false);
ApplicationManager.getApplication().runReadAction(new Runnable() {
public void run() {
FileEditorManager.getInstance(project).openTextEditor(new OpenFileDescriptor(project, file), true);
}
});
}
}
});
}
return null;
}
}
}
@@ -15,10 +15,7 @@
*/
package com.intellij.execution.runners;
import com.intellij.diagnostic.logging.AdditionalTabComponent;
import com.intellij.diagnostic.logging.LogConsoleImpl;
import com.intellij.diagnostic.logging.LogConsoleManager;
import com.intellij.diagnostic.logging.LogFilesManager;
import com.intellij.diagnostic.logging.*;
import com.intellij.execution.DefaultExecutionResult;
import com.intellij.execution.ExecutionResult;
import com.intellij.execution.Executor;
@@ -140,6 +137,7 @@ public class RunContentBuilder implements LogConsoleManager, Disposable {
}
if (profile instanceof RunConfigurationBase) {
myManager.initLogConsoles((RunConfigurationBase)profile, myExecutionResult.getProcessHandler());
OutputFileUtil.attachDumpListener((RunConfigurationBase)profile, myExecutionResult.getProcessHandler(), console);
}
}
MyRunContentDescriptor contentDescriptor = new MyRunContentDescriptor(profile, myExecutionResult, myReuseProhibited, myUi.getComponent(), this);
@@ -112,6 +112,7 @@ public class SMTestRunnerResultsForm extends TestResultsPanel implements TestFra
//Create tests common suite root
//noinspection HardCodedStringLiteral
myTestsRootNode = new SMTestProxy.SMRootTestProxy();
//todo myTestsRootNode.setOutputFilePath(runConfiguration.getOutputFilePath());
// Fire selection changed and move focus on SHIFT+ENTER
//TODO[romeo] improve
@@ -40,6 +40,7 @@ public class CompositePrintable implements Printable, Disposable {
private final PrintablesWrapper myWrapper = new PrintablesWrapper();
protected int myExceptionMark;
private int myCurrentSize = 0;
private String myOutputFile = null;
private static final Alarm myAlarm = new Alarm(Alarm.ThreadToUse.SHARED_THREAD);
public void flush() {
@@ -119,6 +120,10 @@ public class CompositePrintable implements Printable, Disposable {
myExceptionMark = exceptionMark;
}
public void setOutputFilePath(String outputFile) {
myOutputFile = outputFile;
}
private static final Logger LOG = Logger.getInstance("#" + PrintablesWrapper.class.getName());
private class PrintablesWrapper {
@@ -167,6 +172,41 @@ public class CompositePrintable implements Printable, Disposable {
printable.printOn(myPrinter);
}
myPrinter.close();
if (myOutputFile != null) {
PrintStream printStream = null;
try {
printStream = new PrintStream(new FileOutputStream(new File(myOutputFile), true));
final PrintStream finalPrintStream = printStream;
for (Printable currentPrintable : currentPrintables) {
currentPrintable.printOn(new Printer() {
@Override
public void print(String text, ConsoleViewContentType contentType) {
if (contentType != ConsoleViewContentType.SYSTEM_OUTPUT) {
finalPrintStream.print(text);
}
}
@Override
public void printHyperlink(String text, HyperlinkInfo info) {
finalPrintStream.print(text);
}
@Override
public void onNewAvailable(@NotNull Printable printable) {}
@Override
public void mark() {}
});
}
}
catch (FileNotFoundException e) {
LOG.error(e);
}
finally {
if (printStream != null) {
printStream.close();
}
}
}
}
};
invokeInAlarm(request, ApplicationManager.getApplication().isUnitTestMode());
@@ -80,9 +80,10 @@ public abstract class DebuggerSessionTabBase implements DebuggerLogConsoleManage
}
}
protected void initLogConsoles(final RunProfile runConfiguration, final ProcessHandler processHandler) {
protected void initLogConsoles(final RunProfile runConfiguration, final ProcessHandler processHandler, ExecutionConsole console) {
if (runConfiguration instanceof RunConfigurationBase) {
myManager.initLogConsoles((RunConfigurationBase)runConfiguration, processHandler);
OutputFileUtil.attachDumpListener((RunConfigurationBase)runConfiguration, processHandler, console);
}
}
@@ -198,7 +198,7 @@ public class XDebugSessionTab extends DebuggerSessionTabBase {
if (env != null) {
final RunProfile runConfiguration = env.getRunProfile();
registerFileMatcher(runConfiguration);
initLogConsoles(runConfiguration, myRunContentDescriptor.getProcessHandler());
initLogConsoles(runConfiguration, myRunContentDescriptor.getProcessHandler(), myConsole);
}
rebuildViews();
@@ -357,6 +357,11 @@ public class JUnitConfiguration extends ModuleBasedConfiguration<JavaRunConfigur
return myData.FORK_MODE;
}
@Override
public boolean collectOutputFromProcessHandler() {
return false;
}
public static class Data implements Cloneable {
public String PACKAGE_NAME;
private String DIR_NAME;
@@ -268,6 +268,7 @@ public abstract class TestObject implements JavaCommandLine {
public void notifyStart(TestProxy root) {
super.notifyStart(root);
unboundOutputRoot.addChild(root);
unboundOutputRoot.setOutputFilePath(myConfiguration.getOutputFilePath());
final JUnitRunningModel model = getModel();
if (model != null) {
handler.getOut().setDispatchListener(model.getNotifier());
@@ -421,4 +421,9 @@ public class TestNGConfiguration extends ModuleBasedConfiguration<JavaRunConfigu
}
return null;
}
@Override
public boolean collectOutputFromProcessHandler() {
return false;
}
}
@@ -170,6 +170,7 @@ public class TestNGRunnableState extends JavaCommandLineState {
@Override
public void startNotified(final ProcessEvent event) {
TestNGRemoteListener listener = new TestNGRemoteListener(console, unboundOutputRoot);
unboundOutputRoot.setOutputFilePath(config.getOutputFilePath());
client.prepareListening(listener, port);
mySearchForTestIndicator = new BackgroundableProcessIndicator(task) {
@Override