/******************************************************************************* * Copyright (c) 2014 Bruno Medeiros and other Contributors. * All rights reserved. This program and the accompanying materials * are made available under the terms of the Eclipse Public License v1.0 * which accompanies this distribution, and is available at * http://www.eclipse.org/legal/epl-v10.html * * Contributors: * Bruno Medeiros - initial API and implementation *******************************************************************************/ package melnorme.lang.ide.ui.tools.console; import static melnorme.utilbox.core.Assert.AssertNamespace.assertFail; import static melnorme.utilbox.core.Assert.AssertNamespace.assertNotNull; import java.util.Collections; import java.util.HashSet; import java.util.List; import java.util.Set; import org.eclipse.core.resources.IProject; import org.eclipse.debug.core.DebugPlugin; import org.eclipse.swt.widgets.Display; import org.eclipse.swt.widgets.Shell; import melnorme.lang.ide.core.ILangOperationsListener; import melnorme.lang.ide.core.utils.process.AbstractRunProcessTask.ProcessStartHelper; import melnorme.lang.ide.ui.LangImages; import melnorme.lang.ide.ui.LangUIPlugin_Actual; import melnorme.lang.ide.ui.tools.console.ToolsConsole.IOConsoleOutputStreamExt; import melnorme.lang.ide.ui.utils.ConsoleUtils; import melnorme.lang.ide.ui.utils.StatusMessageDialog2; import melnorme.lang.ide.ui.utils.StatusMessageDialogWithIgnore; import melnorme.lang.ide.ui.utils.UIOperationsStatusHandler; import melnorme.lang.ide.ui.utils.WorkbenchUtils; import melnorme.util.swt.SWTUtil; import melnorme.utilbox.core.CommonException; import melnorme.utilbox.misc.ArrayUtil; import melnorme.utilbox.misc.StringUtil; import melnorme.utilbox.process.ExternalProcessNotifyingHelper.IProcessOutputListener; import melnorme.utilbox.status.StatusException; import melnorme.utilbox.status.StatusLevel; public abstract class LangOperationsConsoleUIHandler implements ILangOperationsListener { public static final String MSG_IgnoreSimilar = "Ignore similar errors during this session."; public LangOperationsConsoleUIHandler() { super(); } protected final Set<String> mutedMessages = Collections.synchronizedSet(new HashSet<>()); @Override public void notifyMessage(String msgId, StatusLevel statusLevel, String title, String message) { SWTUtil.runInSWTThread(new Runnable() { @Override public void run() { if(msgId != null && mutedMessages.contains(msgId)) { return; } StatusException statusMessage = new StatusException(statusLevel.toSeverity(), message); Shell shell = WorkbenchUtils.getActiveWorkbenchShell(); StatusMessageDialog2 dialog; if(msgId == null) { dialog = new StatusMessageDialog2(shell, title, statusMessage); } else { dialog = new StatusMessageDialogWithIgnore(shell, title, statusMessage, MSG_IgnoreSimilar) { @Override protected void setIgnoreFutureMessages() { mutedMessages.add(msgId); }; }; } if(UIOperationsStatusHandler.isIgnoringHandling()) { Display.getCurrent().asyncExec( () -> dialog.okPressed() ); } dialog.open(); } }); } /* ----------------- ----------------- */ @SuppressWarnings("unused") protected String getBuildConsoleName(IProject project) { return LangUIPlugin_Actual.BUILD_ConsoleName; } protected ToolsConsole createBuildConsole(String name) { return new ToolsConsole(name, LangImages.BUILD_CONSOLE_ICON.getDescriptor()); } protected ToolsConsole getBuildConsole(IProject project, boolean clearConsole) { String operationConsoleName = getBuildConsoleName(project); return ConsoleUtils.getOrCreateToolsConsole(operationConsoleName, clearConsole, ToolsConsole.class, () -> createBuildConsole(operationConsoleName)); } /* ----------------- ----------------- */ @Override public IToolOperationMonitor beginOperation(ProcessStartKind kind, boolean clearConsole, boolean activateConsole) { IToolOperationMonitor opHandler = doBeginOperation(kind, clearConsole); if(activateConsole){ opHandler.activate(); } return opHandler; } protected IToolOperationMonitor doBeginOperation(ProcessStartKind kind, boolean clearConsole) { switch (kind) { case BUILD: { ToolsConsole console = getBuildConsole(null, clearConsole); return createConsoleHandler(kind, console, console.stdOut, console.stdErr); } case CHECK_BUILD: { ToolsConsole console = getBuildConsole(null, clearConsole); return createConsoleHandler(kind, console, console.stdOut, console.stdErr_silent); } case ENGINE_SERVER: { EngineToolsConsole console = EngineToolsConsole.getConsole(clearConsole); return createConsoleHandler(kind, console, console.serverStdOut, console.serverStdErr); } case ENGINE_TOOLS: { EngineToolsConsole console = EngineToolsConsole.getConsole(clearConsole); return createConsoleHandler(kind, console, console.stdOut, console.stdErr); } } throw assertFail(); } protected OperationConsoleMonitor createConsoleHandler(ProcessStartKind kind, ToolsConsole console, IOConsoleOutputStreamExt stdOut, IOConsoleOutputStreamExt stdErr) { return new OperationConsoleMonitor(kind, console, stdOut, stdErr); } /* ----------------- ----------------- */ public class OperationConsoleMonitor implements IToolOperationMonitor { protected final ProcessStartKind kind; protected final ToolsConsole console; protected final IOConsoleOutputStreamExt infoOut; protected final IOConsoleOutputStreamExt stdOut; protected final IOConsoleOutputStreamExt stdErr; public boolean errorOnNonZeroExitValueForBuild = false; public OperationConsoleMonitor(ProcessStartKind kind, ToolsConsole console, IOConsoleOutputStreamExt stdOut, IOConsoleOutputStreamExt stdErr) { this.kind = assertNotNull(kind); this.console = assertNotNull(console); this.infoOut = console.infoOut; this.stdOut = stdOut; this.stdErr = stdErr; } @Override public void writeInfoMessage(String operationMessage) { console.writeOperationInfo(operationMessage); } @Override public void handleProcessStart(String prefixText, String suffixText, ProcessBuilder pb, ProcessStartHelper processStartHelper) { String infoPrefaceText = getPrefaceText(prefixText, suffixText, pb); if(infoPrefaceText != null) { infoOut.write(infoPrefaceText); } connectProcessOutputListener(processStartHelper); } protected void connectProcessOutputListener(ProcessStartHelper processStartHelper) { try { processStartHelper.addProcessListener(createOutputListener()); } catch(CommonException ce) { String text = " FAILED: " + ce.getMessage(); Throwable cause = ce.getCause(); if(cause != null) { text += " Reason: " + cause.getMessage() + "\n"; } infoOut.write(text); } } protected IProcessOutputListener createOutputListener() { return new ConsoleOutputProcessListener(stdOut, stdErr) { @Override public void notifyProcessTerminatedAndRead(int exitCode) { super.notifyProcessTerminatedAndRead(exitCode); handleProcessTerminated(exitCode); } }; } protected void handleProcessTerminated(int exitCode) { boolean activateOnErrors = kind == ProcessStartKind.BUILD && ToolsConsolePrefs.ACTIVATE_ON_ERROR_MESSAGES.get(); if(errorOnNonZeroExitValueForBuild && exitCode != 0 && activateOnErrors) { console.activate(); } infoOut.write(getProcessTerminatedMessage(exitCode)); infoOut.flush(); } @Override public void activate() { console.activate(); // Try again - because of poort concurrency guarantes, the console view might have been activated // but with the wrong console page, so send another request, in UI thread. Display.getDefault().asyncExec(() -> console.activate()); } } protected String getPrefaceText(String prefixText, String suffixText, ProcessBuilder pb) { List<String> commandLine = pb.command(); prefixText = prefixText == null ? ">> Running: " : prefixText; String argsLabel = DebugPlugin.renderArguments(ArrayUtil.createFrom(commandLine, String.class), null); String infoPrefaceText = prefixText + argsLabel; return infoPrefaceText + StringUtil.nullAsEmpty(suffixText) + "\n"; } protected String getProcessTerminatedMessage(int exitCode) { return " ^^^ Terminated, exit code: " + exitCode + " ^^^\n"; } }