| /******************************************************************************* |
| * Copyright (c) 2002, 2017 QNX Software Systems and others. |
| * |
| * This program and the accompanying materials |
| * are made available under the terms of the Eclipse Public License 2.0 |
| * which accompanies this distribution, and is available at |
| * https://www.eclipse.org/legal/epl-2.0/ |
| * |
| * SPDX-License-Identifier: EPL-2.0 |
| * |
| * Contributors: |
| * QNX Software Systems - initial API and implementation |
| * Red Hat Inc. - multiple build console support |
| * Dmitry Kozlov (CodeSourcery) - Build error highlighting and navigation |
| * Alex Collins (Broadcom Corp.) - Global build console |
| *******************************************************************************/ |
| package org.eclipse.cdt.internal.ui.buildconsole; |
| |
| import java.net.URI; |
| import java.net.URL; |
| import java.util.HashMap; |
| import java.util.Map; |
| |
| import org.eclipse.cdt.core.resources.IConsole; |
| import org.eclipse.cdt.internal.core.LocalProjectScope; |
| import org.eclipse.cdt.internal.ui.preferences.BuildConsolePreferencePage; |
| import org.eclipse.cdt.ui.CUIPlugin; |
| import org.eclipse.cdt.ui.IBuildConsoleEvent; |
| import org.eclipse.cdt.ui.IBuildConsoleListener; |
| import org.eclipse.cdt.ui.IBuildConsoleManager; |
| import org.eclipse.core.filesystem.URIUtil; |
| import org.eclipse.core.resources.IProject; |
| import org.eclipse.core.resources.IResource; |
| import org.eclipse.core.resources.IResourceChangeEvent; |
| import org.eclipse.core.resources.IResourceChangeListener; |
| import org.eclipse.core.runtime.Assert; |
| import org.eclipse.core.runtime.CoreException; |
| import org.eclipse.core.runtime.IPath; |
| import org.eclipse.core.runtime.IStatus; |
| import org.eclipse.core.runtime.ListenerList; |
| import org.eclipse.core.runtime.Status; |
| import org.eclipse.jface.preference.IPreferenceStore; |
| import org.eclipse.jface.preference.PreferenceConverter; |
| import org.eclipse.jface.text.IDocument; |
| import org.eclipse.jface.text.IDocumentPartitioner; |
| import org.eclipse.jface.util.IPropertyChangeListener; |
| import org.eclipse.jface.util.PropertyChangeEvent; |
| import org.eclipse.swt.graphics.Color; |
| import org.eclipse.swt.graphics.RGB; |
| import org.eclipse.swt.widgets.Display; |
| import org.eclipse.ui.IViewPart; |
| import org.eclipse.ui.IWorkbenchPage; |
| import org.eclipse.ui.IWorkbenchPart; |
| import org.eclipse.ui.IWorkbenchWindow; |
| import org.eclipse.ui.PartInitException; |
| import org.eclipse.ui.console.ConsolePlugin; |
| import org.eclipse.ui.console.IConsoleConstants; |
| import org.eclipse.ui.console.IConsoleView; |
| import org.osgi.service.prefs.Preferences; |
| |
| public class BuildConsoleManager implements IBuildConsoleManager, IResourceChangeListener, IPropertyChangeListener { |
| /*package*/ static final String PREF_QUALIFIER = CUIPlugin.PLUGIN_ID; |
| public static final String KEY_KEEP_LOG = "keepLog"; //$NON-NLS-1$ |
| public static final String KEY_LOG_LOCATION = "logLocation"; //$NON-NLS-1$ |
| public static final boolean CONSOLE_KEEP_LOG_DEFAULT = true; |
| |
| private static final String BUILD_CONSOLE_NODE = "buildConsole"; //$NON-NLS-1$ |
| private static final String PROJECT_LOG_EXT = ".build.log"; //$NON-NLS-1$ |
| |
| private ListenerList<IBuildConsoleListener> listeners = new ListenerList<>(); |
| /** UI console object in which per-project consoles are shown */ |
| private BuildConsole fConsole; |
| private Map<IProject, BuildConsolePartitioner> fConsoleMap = new HashMap<>(); |
| private Color infoColor; |
| private Color outputColor; |
| private Color errorColor; |
| private Color backgroundColor; |
| private Color problemHighlightedColor; |
| private Color problemErrorBackgroundColor; |
| private Color problemInfoBackgroundColor; |
| private Color problemWarningBackgroundColor; |
| |
| public Color getProblemHighlightedColor() { |
| return problemHighlightedColor; |
| } |
| |
| /** |
| * This function returns background color for errors only now |
| */ |
| public Color getProblemBackgroundColor() { |
| return problemErrorBackgroundColor; |
| } |
| |
| public Color getWarningBackgroundColor() { |
| return problemWarningBackgroundColor; |
| } |
| |
| public Color getInfoBackgroundColor() { |
| return problemInfoBackgroundColor; |
| } |
| |
| private BuildConsoleStreamDecorator infoStream; |
| private BuildConsoleStreamDecorator outputStream; |
| private BuildConsoleStreamDecorator errorStream; |
| |
| private String fName; |
| private String fContextMenuId; |
| |
| static public final int BUILD_STREAM_TYPE_INFO = 0; |
| static public final int BUILD_STREAM_TYPE_OUTPUT = 1; |
| static public final int BUILD_STREAM_TYPE_ERROR = 2; |
| |
| static public final String DEFAULT_CONTEXT_MENU_ID = CUIPlugin.PLUGIN_ID + ".CDTBuildConsole"; //$NON-NLS-1$ |
| |
| private IProject fLastProject; |
| |
| /** |
| * Default constructor. |
| */ |
| public BuildConsoleManager() { |
| } |
| |
| /** |
| * Notifies the console manager that console activity has started on the |
| * project The manager will open the console if the preference is set to |
| * show the console, and notify listeners |
| */ |
| protected void startConsoleActivity(IProject project) { |
| if (!listeners.isEmpty()) { |
| for (IBuildConsoleListener listener : listeners) { |
| ConsoleEvent event = new ConsoleEvent(BuildConsoleManager.this, project, |
| IBuildConsoleEvent.CONSOLE_START); |
| listener.consoleChange(event); |
| } |
| } |
| showConsole(true); |
| } |
| |
| /** |
| * Opens the console view. If the view is already open, it is brought to the |
| * front if requested and the respective user preference is set. The console |
| * that is shown is the console that was last on top. |
| */ |
| protected void showConsole(boolean bringToTop) { |
| IWorkbenchWindow window = CUIPlugin.getActiveWorkbenchWindow(); |
| if (window != null) { |
| IWorkbenchPage page = window.getActivePage(); |
| if (page != null) { |
| IViewPart consoleView = page.findView(IConsoleConstants.ID_CONSOLE_VIEW); |
| if (consoleView == null && BuildConsolePreferencePage.isAutoOpenConsole()) { |
| IWorkbenchPart activePart = page.getActivePart(); |
| try { |
| consoleView = page.showView(IConsoleConstants.ID_CONSOLE_VIEW); |
| } catch (PartInitException pie) { |
| CUIPlugin.log(pie); |
| } |
| //restore focus stolen by the creation of the |
| // console |
| page.activate(activePart); |
| } |
| boolean shouldBringToTop = shouldBringToTop(consoleView); |
| if (bringToTop && shouldBringToTop) { |
| page.bringToTop(consoleView); |
| } |
| if (consoleView instanceof IConsoleView) { |
| if (BuildConsole.getCurrentPage() == null) |
| ((IConsoleView) consoleView).display(fConsole); |
| else if (shouldBringToTop) |
| ((IConsoleView) consoleView).display(BuildConsole.getCurrentPage().getConsole()); |
| } |
| } |
| } |
| } |
| |
| boolean shouldBringToTop(IViewPart consoleView) { |
| boolean bringToTop = false; |
| if (consoleView instanceof IConsoleView) { |
| IConsoleView cView = (IConsoleView) consoleView; |
| return !cView.isPinned() && BuildConsolePreferencePage.isConsoleOnTop(); |
| } |
| return bringToTop; |
| } |
| |
| /** |
| * Traverses the delta looking for added/removed/changed launch |
| * configuration files. |
| * |
| * @see IResourceChangeListener#resourceChanged(IResourceChangeEvent) |
| */ |
| @Override |
| public void resourceChanged(IResourceChangeEvent event) { |
| IResource resource = event.getResource(); |
| if (resource != null && resource.getType() == IResource.PROJECT) { |
| if (event.getType() == IResourceChangeEvent.PRE_DELETE |
| || event.getType() == IResourceChangeEvent.PRE_CLOSE) { |
| IDocumentPartitioner partioner = fConsoleMap.remove(resource); |
| if (partioner != null) { |
| partioner.disconnect(); |
| if (!listeners.isEmpty()) { |
| for (IBuildConsoleListener listener : listeners) { |
| ConsoleEvent consoleEvent = new ConsoleEvent(this, (IProject) resource, |
| IBuildConsoleEvent.CONSOLE_CLOSE); |
| listener.consoleChange(consoleEvent); |
| } |
| } |
| } |
| } |
| } |
| } |
| |
| /** |
| * Release resources allocated on {@link #startup(String, String, URL)}. |
| */ |
| public void shutdown() { |
| if (infoColor != null) { |
| infoColor.dispose(); |
| outputColor.dispose(); |
| errorColor.dispose(); |
| backgroundColor.dispose(); |
| problemErrorBackgroundColor.dispose(); |
| problemWarningBackgroundColor.dispose(); |
| problemInfoBackgroundColor.dispose(); |
| problemHighlightedColor.dispose(); |
| } |
| ConsolePlugin.getDefault().getConsoleManager() |
| .removeConsoles(new org.eclipse.ui.console.IConsole[] { fConsole }); |
| CUIPlugin.getWorkspace().removeResourceChangeListener(this); |
| CUIPlugin.getDefault().getPreferenceStore().removePropertyChangeListener(this); |
| } |
| |
| private void runUI(Runnable run) { |
| Display display; |
| display = Display.getCurrent(); |
| if (display == null) { |
| display = Display.getDefault(); |
| display.asyncExec(run); |
| } else { |
| run.run(); |
| } |
| } |
| |
| /** |
| * Create new build console. Subclasses may override to create a specialized |
| * console. |
| * |
| * @param name - name of console to appear in the list of consoles in context menu |
| * in the Console view. |
| * @param contextId - context menu id in the Console view. |
| * @param iconUrl - a {@link URL} of the icon for the context menu of the Console |
| * view. The url is expected to point to an image in eclipse OSGi bundle. |
| * {@code iconUrl} can be <b>null</b>, in that case the default image is used. |
| * @return newly created build console. |
| */ |
| protected BuildConsole createBuildConsole(String name, String contextId, final URL iconUrl) { |
| return new BuildConsole(this, name, contextId, iconUrl); |
| } |
| |
| /** |
| * Start console activities. This will create a new console in the Console view, |
| * create streams, color resources, register listeners etc. |
| * Most work is done in UI thread. |
| * |
| * Use {@link #shutdown()} after the console activity ends. |
| * |
| * @param name - name of the console to appear in the Console view. |
| * @param contextId - context menu id in the Console view. |
| * @param iconUrl - icon to show in the context menu. |
| */ |
| public void startup(String name, String contextId, final URL iconUrl) { |
| // Ensure global console is initialized before any other build console |
| if (!(this instanceof GlobalBuildConsoleManager)) |
| GlobalBuildConsoleManager.startup(); |
| |
| infoStream = new BuildConsoleStreamDecorator(); |
| outputStream = new BuildConsoleStreamDecorator(); |
| errorStream = new BuildConsoleStreamDecorator(); |
| fName = name; |
| fContextMenuId = contextId; |
| |
| runUI(() -> { |
| // add console to the Console view |
| fConsole = createBuildConsole(fName, fContextMenuId, iconUrl); |
| ConsolePlugin.getDefault().getConsoleManager() |
| .addConsoles(new org.eclipse.ui.console.IConsole[] { fConsole }); |
| |
| infoStream.setConsole(fConsole); |
| infoColor = createColor(CUIPlugin.getStandardDisplay(), |
| BuildConsolePreferencePage.PREF_BUILDCONSOLE_INFO_COLOR); |
| infoStream.setColor(infoColor); |
| outputStream.setConsole(fConsole); |
| outputColor = createColor(CUIPlugin.getStandardDisplay(), |
| BuildConsolePreferencePage.PREF_BUILDCONSOLE_OUTPUT_COLOR); |
| outputStream.setColor(outputColor); |
| errorStream.setConsole(fConsole); |
| errorColor = createColor(CUIPlugin.getStandardDisplay(), |
| BuildConsolePreferencePage.PREF_BUILDCONSOLE_ERROR_COLOR); |
| errorStream.setColor(errorColor); |
| backgroundColor = createBackgroundColor(CUIPlugin.getStandardDisplay(), |
| BuildConsolePreferencePage.PREF_BUILDCONSOLE_BACKGROUND_COLOR); |
| fConsole.setBackground(backgroundColor); |
| problemHighlightedColor = createColor(CUIPlugin.getStandardDisplay(), |
| BuildConsolePreferencePage.PREF_BUILDCONSOLE_PROBLEM_HIGHLIGHTED_COLOR); |
| problemErrorBackgroundColor = createBackgroundColor(CUIPlugin.getStandardDisplay(), |
| BuildConsolePreferencePage.PREF_BUILDCONSOLE_PROBLEM_BACKGROUND_COLOR); |
| problemWarningBackgroundColor = createBackgroundColor(CUIPlugin.getStandardDisplay(), |
| BuildConsolePreferencePage.PREF_BUILDCONSOLE_PROBLEM_WARNING_BACKGROUND_COLOR); |
| problemInfoBackgroundColor = createBackgroundColor(CUIPlugin.getStandardDisplay(), |
| BuildConsolePreferencePage.PREF_BUILDCONSOLE_PROBLEM_INFO_BACKGROUND_COLOR); |
| }); |
| CUIPlugin.getWorkspace().addResourceChangeListener(this); |
| CUIPlugin.getDefault().getPreferenceStore().addPropertyChangeListener(this); |
| } |
| |
| @Override |
| public void propertyChange(PropertyChangeEvent event) { |
| String property = event.getProperty(); |
| // colors |
| if (property.equals(BuildConsolePreferencePage.PREF_BUILDCONSOLE_INFO_COLOR)) { |
| Color newColor = createColor(CUIPlugin.getStandardDisplay(), |
| BuildConsolePreferencePage.PREF_BUILDCONSOLE_INFO_COLOR); |
| infoStream.setColor(newColor); |
| infoColor.dispose(); |
| infoColor = newColor; |
| } else if (property.equals(BuildConsolePreferencePage.PREF_BUILDCONSOLE_OUTPUT_COLOR)) { |
| Color newColor = createColor(CUIPlugin.getStandardDisplay(), |
| BuildConsolePreferencePage.PREF_BUILDCONSOLE_OUTPUT_COLOR); |
| outputStream.setColor(newColor); |
| outputColor.dispose(); |
| outputColor = newColor; |
| } else if (property.equals(BuildConsolePreferencePage.PREF_BUILDCONSOLE_ERROR_COLOR)) { |
| Color newColor = createColor(CUIPlugin.getStandardDisplay(), |
| BuildConsolePreferencePage.PREF_BUILDCONSOLE_ERROR_COLOR); |
| errorStream.setColor(newColor); |
| errorColor.dispose(); |
| errorColor = newColor; |
| } else if (property.equals(BuildConsolePreferencePage.PREF_BUILDCONSOLE_BACKGROUND_COLOR)) { |
| Color newColor = createBackgroundColor(CUIPlugin.getStandardDisplay(), |
| BuildConsolePreferencePage.PREF_BUILDCONSOLE_BACKGROUND_COLOR); |
| fConsole.setBackground(newColor); |
| backgroundColor.dispose(); |
| backgroundColor = newColor; |
| } else if (property.equals(BuildConsolePreferencePage.PREF_BUILDCONSOLE_PROBLEM_HIGHLIGHTED_COLOR)) { |
| Color newColor = createColor(CUIPlugin.getStandardDisplay(), |
| BuildConsolePreferencePage.PREF_BUILDCONSOLE_PROBLEM_HIGHLIGHTED_COLOR); |
| problemHighlightedColor.dispose(); |
| problemHighlightedColor = newColor; |
| redrawTextViewer(); |
| } else if (property.equals(BuildConsolePreferencePage.PREF_BUILDCONSOLE_PROBLEM_BACKGROUND_COLOR)) { |
| Color newColor = createBackgroundColor(CUIPlugin.getStandardDisplay(), |
| BuildConsolePreferencePage.PREF_BUILDCONSOLE_PROBLEM_BACKGROUND_COLOR); |
| problemErrorBackgroundColor.dispose(); |
| problemErrorBackgroundColor = newColor; |
| redrawTextViewer(); |
| } else if (property.equals(BuildConsolePreferencePage.PREF_BUILDCONSOLE_PROBLEM_WARNING_BACKGROUND_COLOR)) { |
| Color newColor = createBackgroundColor(CUIPlugin.getStandardDisplay(), |
| BuildConsolePreferencePage.PREF_BUILDCONSOLE_PROBLEM_WARNING_BACKGROUND_COLOR); |
| problemWarningBackgroundColor.dispose(); |
| problemWarningBackgroundColor = newColor; |
| redrawTextViewer(); |
| } else if (property.equals(BuildConsolePreferencePage.PREF_BUILDCONSOLE_PROBLEM_INFO_BACKGROUND_COLOR)) { |
| Color newColor = createBackgroundColor(CUIPlugin.getStandardDisplay(), |
| BuildConsolePreferencePage.PREF_BUILDCONSOLE_PROBLEM_INFO_BACKGROUND_COLOR); |
| problemInfoBackgroundColor.dispose(); |
| problemInfoBackgroundColor = newColor; |
| redrawTextViewer(); |
| } |
| } |
| |
| private void redrawTextViewer() { |
| final BuildConsolePage p = BuildConsole.getCurrentPage(); |
| if (p == null) |
| return; |
| final BuildConsoleViewer v = p.getViewer(); |
| if (v == null) |
| return; |
| Display display = Display.getDefault(); |
| display.asyncExec(() -> v.getTextWidget().redraw()); |
| } |
| |
| public IBuildConsoleStreamDecorator getStreamDecorator(int type) throws CoreException { |
| switch (type) { |
| case BUILD_STREAM_TYPE_ERROR: |
| return errorStream; |
| case BUILD_STREAM_TYPE_INFO: |
| return infoStream; |
| case BUILD_STREAM_TYPE_OUTPUT: |
| return outputStream; |
| } |
| throw new CoreException(new Status(IStatus.ERROR, CUIPlugin.PLUGIN_ID, -1, "No Such Console", null)); //$NON-NLS-1$ |
| } |
| |
| /** |
| * Returns a color instance based on data from a preference field. |
| */ |
| private Color createColor(Display display, String preference) { |
| RGB rgb = PreferenceConverter.getColor(CUIPlugin.getDefault().getPreferenceStore(), preference); |
| return new Color(display, rgb); |
| } |
| |
| /** |
| * Returns a background color instance based on data from a preference field. |
| * This is a workaround for black console bug 320723. |
| */ |
| private Color createBackgroundColor(Display display, String preference) { |
| IPreferenceStore preferenceStore = CUIPlugin.getDefault().getPreferenceStore(); |
| RGB rgb; |
| if (preferenceStore.contains(preference)) { |
| rgb = PreferenceConverter.getColor(preferenceStore, preference); |
| } else { |
| rgb = new RGB(200, 200, 200); // gray background |
| } |
| return new Color(display, rgb); |
| } |
| |
| /** |
| * {@inheritDoc} |
| */ |
| @Override |
| public IConsole getConsole(IProject project) { |
| return new MultiBuildConsoleAdapter(getProjectConsole(project), GlobalBuildConsoleManager.getGlobalConsole()); |
| } |
| |
| /** |
| * {@inheritDoc} |
| * |
| * @return the console for the specified project. Returns {@code null} |
| * if project is {@code null} or not accessible. |
| */ |
| @Override |
| public IConsole getProjectConsole(IProject project) { |
| if (project == null || !project.isAccessible()) |
| return null; |
| |
| fLastProject = project; |
| return getProjectConsolePartioner(project).getConsole(); |
| } |
| |
| @Override |
| public IProject getLastBuiltProject() { |
| return fLastProject; |
| } |
| |
| /** |
| * @return the partitioner for the specified projects build console |
| */ |
| private BuildConsolePartitioner getProjectConsolePartioner(IProject project) { |
| BuildConsolePartitioner partitioner = fConsoleMap.get(project); |
| if (partitioner == null) { |
| partitioner = new BuildConsolePartitioner(project, this); |
| fConsoleMap.put(project, partitioner); |
| } |
| return partitioner; |
| } |
| |
| /** |
| * @return the document backing the build console for the specified project |
| */ |
| @Override |
| public IDocument getConsoleDocument(IProject project) { |
| Assert.isNotNull(project); |
| return getProjectConsolePartioner(project).getDocument(); |
| } |
| |
| @Override |
| public void addConsoleListener(IBuildConsoleListener listener) { |
| listeners.add(listener); |
| } |
| |
| @Override |
| public void removeConsoleListener(IBuildConsoleListener listener) { |
| listeners.remove(listener); |
| } |
| |
| /** |
| * @return logging preferences for a given project. |
| * @param project to get logging preferences for, can't be {@code null}. |
| */ |
| public Preferences getBuildLogPreferences(IProject project) { |
| return new LocalProjectScope(project).getNode(PREF_QUALIFIER).node(BUILD_CONSOLE_NODE); |
| } |
| |
| /** |
| * @return default location of logs for a project. |
| * @param project to get default log location for, can't be {@code null}. |
| */ |
| public String getDefaultConsoleLogLocation(IProject project) { |
| IPath defaultLogLocation = CUIPlugin.getDefault().getStateLocation() |
| .append(project.getName() + PROJECT_LOG_EXT); |
| return defaultLogLocation.toOSString(); |
| } |
| |
| /** |
| * Where the log for per-project console is kept. |
| * |
| * @param project - the project. Cannot be {@code null}. |
| * @return {@link URI} of build log or {@code null} if not available. |
| */ |
| public URI getLogURI(IProject project) { |
| Assert.isNotNull(project); |
| |
| if (fContextMenuId != DEFAULT_CONTEXT_MENU_ID) |
| return null; |
| |
| URI logURI = null; |
| |
| Preferences prefs = getBuildLogPreferences(project); |
| boolean keepLog = prefs.getBoolean(KEY_KEEP_LOG, CONSOLE_KEEP_LOG_DEFAULT); |
| if (keepLog) { |
| String strLocation; |
| strLocation = prefs.get(KEY_LOG_LOCATION, getDefaultConsoleLogLocation(project)); |
| if (strLocation.trim().length() > 0) { |
| logURI = URIUtil.toURI(strLocation); |
| } |
| if (logURI == null) { |
| IStatus status = new Status(IStatus.ERROR, CUIPlugin.PLUGIN_ID, |
| "Can't determine URI for location=[" + strLocation + "]"); //$NON-NLS-1$ //$NON-NLS-2$ |
| CUIPlugin.log(status); |
| } |
| } |
| return logURI; |
| } |
| |
| } |