blob: 0b0a64191a7bfcc64ddaf4a7320c2ab5cbce3d81 [file] [log] [blame]
/*******************************************************************************
* Copyright (c) 2002, 2018 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
* Save build output
* Alex Collins (Broadcom Corp.) - Global build console
*******************************************************************************/
package org.eclipse.cdt.internal.ui.buildconsole;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.Iterator;
import java.util.List;
import java.util.Map;
import java.util.ResourceBundle;
import org.eclipse.cdt.core.ProblemMarkerInfo;
import org.eclipse.cdt.core.model.ICModelMarker;
import org.eclipse.cdt.core.resources.IConsole;
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.resources.IFile;
import org.eclipse.core.resources.IMarker;
import org.eclipse.core.resources.IProject;
import org.eclipse.core.resources.IResource;
import org.eclipse.core.runtime.CoreException;
import org.eclipse.core.runtime.IAdaptable;
import org.eclipse.jface.action.GroupMarker;
import org.eclipse.jface.action.IAction;
import org.eclipse.jface.action.IMenuManager;
import org.eclipse.jface.action.IToolBarManager;
import org.eclipse.jface.action.MenuManager;
import org.eclipse.jface.action.Separator;
import org.eclipse.jface.resource.JFaceResources;
import org.eclipse.jface.text.IDocument;
import org.eclipse.jface.text.IFindReplaceTarget;
import org.eclipse.jface.text.ITextListener;
import org.eclipse.jface.text.ITextOperationTarget;
import org.eclipse.jface.text.ITextSelection;
import org.eclipse.jface.text.TextEvent;
import org.eclipse.jface.util.IPropertyChangeListener;
import org.eclipse.jface.util.OpenStrategy;
import org.eclipse.jface.util.PropertyChangeEvent;
import org.eclipse.jface.viewers.ISelection;
import org.eclipse.jface.viewers.ISelectionChangedListener;
import org.eclipse.jface.viewers.ISelectionProvider;
import org.eclipse.jface.viewers.IStructuredSelection;
import org.eclipse.jface.viewers.StructuredSelection;
import org.eclipse.swt.custom.StyledText;
import org.eclipse.swt.graphics.Font;
import org.eclipse.swt.widgets.Composite;
import org.eclipse.swt.widgets.Control;
import org.eclipse.swt.widgets.Display;
import org.eclipse.swt.widgets.Menu;
import org.eclipse.swt.widgets.Widget;
import org.eclipse.ui.IActionBars;
import org.eclipse.ui.IEditorInput;
import org.eclipse.ui.IEditorPart;
import org.eclipse.ui.IFileEditorInput;
import org.eclipse.ui.ISelectionListener;
import org.eclipse.ui.ISharedImages;
import org.eclipse.ui.IWorkbenchActionConstants;
import org.eclipse.ui.IWorkbenchPage;
import org.eclipse.ui.IWorkbenchPart;
import org.eclipse.ui.IWorkbenchPartSite;
import org.eclipse.ui.IWorkbenchWindow;
import org.eclipse.ui.PartInitException;
import org.eclipse.ui.PlatformUI;
import org.eclipse.ui.actions.ActionFactory;
import org.eclipse.ui.console.IConsoleConstants;
import org.eclipse.ui.console.IConsoleView;
import org.eclipse.ui.console.actions.ClearOutputAction;
import org.eclipse.ui.console.actions.TextViewerAction;
import org.eclipse.ui.console.actions.TextViewerGotoLineAction;
import org.eclipse.ui.ide.IDE;
import org.eclipse.ui.ide.ResourceUtil;
import org.eclipse.ui.part.IPageSite;
import org.eclipse.ui.part.Page;
import org.eclipse.ui.texteditor.FindReplaceAction;
import org.eclipse.ui.texteditor.ITextEditorActionConstants;
import org.eclipse.ui.texteditor.IUpdate;
public class BuildConsolePage extends Page
implements ISelectionListener, IPropertyChangeListener, IBuildConsoleListener, ITextListener, IAdaptable {
static final int POSITION_NEXT = -1;
static final int POSITION_PREV = -2;
static final int POSITION_FIST = -3;
private BuildConsole fConsole;
private IConsoleView fConsoleView;
private String fContextMenuId;
private BuildConsoleViewer fViewer;
private IProject fProject;
// text selection listener
private ISelectionChangedListener fTextListener = event -> updateSelectionDependentActions();
// actions
private ClearOutputAction fClearOutputAction;
private Map<String, IAction> fGlobalActions = new HashMap<>(10);
private List<String> fSelectionActions = new ArrayList<>(3);
private CopyBuildLogAction fSaveLogAction;
// menus
private Menu fMenu;
private ScrollLockAction fScrollLockAction;
private boolean fIsLocked;
private NextErrorAction fNextErrorAction;
private PreviousErrorAction fPreviousErrorAction;
private ShowErrorAction fShowErrorAction;
private WrapLinesAction fWrapAction;
private BringToTopOnBuild fBringToTopOnBuild;
/**
* @param view
* @param console
* @param contextId
*/
public BuildConsolePage(IConsoleView view, BuildConsole console, String contextId) {
fConsole = console;
fConsoleView = view;
fContextMenuId = contextId;
}
protected void setProject(IProject project) {
if (fProject != project && project.isAccessible()) {
fProject = project;
}
}
protected IProject getProject() {
return fProject;
}
protected BuildConsole getConsole() {
return fConsole;
}
protected IDocument setDocument() {
IProject project = getProject();
if (project != null) {
IBuildConsoleManager consoleManager = getConsole().getConsoleManager();
IDocument document = consoleManager.getConsoleDocument(project);
IConsole console = consoleManager.getProjectConsole(project);
getViewer().setDocument(document);
if (console instanceof BuildConsolePartitioner) {
BuildConsolePartitioner par = (BuildConsolePartitioner) console;
// Show the error, but don't show it in the editor if we are viewing the global console.
// Prevents showing errors in the editor for projects other than the current project.
showError(par, fShowErrorAction.isChecked() && !(getConsole() instanceof GlobalBuildConsole));
}
}
return null;
}
@Override
public void consoleChange(final IBuildConsoleEvent event) {
if (event.getType() == IBuildConsoleEvent.CONSOLE_START
|| event.getType() == IBuildConsoleEvent.CONSOLE_CLOSE) {
Control control = getControl();
if (control != null && !control.isDisposed()) {
Display display = control.getDisplay();
display.asyncExec(() -> {
if (isAvailable()) {
if (event.getType() == IBuildConsoleEvent.CONSOLE_CLOSE && getProject() != event.getProject()) {
return;
}
setProject(event.getProject());
if (isAvailable()) {
setDocument();
getConsole().setTitle(getProject());
}
}
});
}
}
}
boolean isAvailable() {
return getControl() != null;
}
@Override
public void createControl(Composite parent) {
fViewer = new BuildConsoleViewer(parent);
MenuManager manager = new MenuManager("#MessageConsole", "#MessageConsole"); //$NON-NLS-1$ //$NON-NLS-2$
manager.setRemoveAllWhenShown(true);
manager.addMenuListener(m -> contextMenuAboutToShow(m));
fMenu = manager.createContextMenu(getControl());
getControl().setMenu(fMenu);
IPageSite site = getSite();
site.registerContextMenu(fContextMenuId, manager, getViewer());
site.setSelectionProvider(getViewer());
createActions();
configureToolBar(site.getActionBars().getToolBarManager());
fViewer.getSelectionProvider().addSelectionChangedListener(fTextListener);
JFaceResources.getFontRegistry().addListener(this);
setFont(JFaceResources.getFont(BuildConsolePreferencePage.PREF_BUILDCONSOLE_FONT));
setTabs(CUIPlugin.getDefault().getPreferenceStore()
.getInt(BuildConsolePreferencePage.PREF_BUILDCONSOLE_TAB_WIDTH));
getConsole().addPropertyChangeListener(this);
CUIPlugin.getDefault().getPreferenceStore().addPropertyChangeListener(this);
fViewer.addTextListener(this);
fViewer.getTextWidget().setBackground(getConsole().getBackground());
setInitialSelection();
}
/**
* Fill the context menu
*
* @param menu
* menu
*/
protected void contextMenuAboutToShow(IMenuManager menu) {
menu.add(fGlobalActions.get(ActionFactory.COPY.getId()));
menu.add(fGlobalActions.get(ActionFactory.SELECT_ALL.getId()));
menu.add(new Separator("FIND")); //$NON-NLS-1$
menu.add(fGlobalActions.get(ActionFactory.FIND.getId()));
menu.add(fGlobalActions.get(ITextEditorActionConstants.GOTO_LINE));
menu.add(fClearOutputAction);
menu.add(new Separator(IWorkbenchActionConstants.MB_ADDITIONS));
}
@Override
public void propertyChange(PropertyChangeEvent event) {
final Object source = event.getSource();
final String property = event.getProperty();
if (BuildConsole.P_STREAM_COLOR.equals(property) && source instanceof IBuildConsoleStreamDecorator) {
IBuildConsoleStreamDecorator stream = (IBuildConsoleStreamDecorator) source;
if (stream.getConsole().equals(getConsole()) && getControl() != null) {
Display display = getControl().getDisplay();
display.asyncExec(() -> getViewer().getTextWidget().redraw());
}
} else if (property.equals(BuildConsolePreferencePage.PREF_BUILDCONSOLE_FONT)) {
setFont(JFaceResources.getFont(BuildConsolePreferencePage.PREF_BUILDCONSOLE_FONT));
} else if (property.equals(BuildConsolePreferencePage.PREF_BUILDCONSOLE_TAB_WIDTH)) {
setTabs(CUIPlugin.getDefault().getPreferenceStore()
.getInt(BuildConsolePreferencePage.PREF_BUILDCONSOLE_TAB_WIDTH));
} else if (IConsoleConstants.P_BACKGROUND_COLOR.equals(property)) {
fViewer.getTextWidget().setBackground(fConsole.getBackground());
} else if (property.equals(BuildConsolePreferencePage.PREF_BUILDCONSOLE_WRAP_LINES)
|| property.equals(BuildConsolePreferencePage.PREF_BUILDCONSOLE_LINES)
|| property.equals(BuildConsolePreferencePage.PREF_BUILDCONSOLE_WRAP_LINES_MAX)) {
fWrapAction.propertyChange();
} else if (property.equals(BuildConsolePreferencePage.PREF_CONSOLE_ON_TOP)) {
fBringToTopOnBuild.propertyChange();
}
}
protected void createActions() {
fClearOutputAction = new ClearOutputAction(getViewer());
fScrollLockAction = new ScrollLockAction(getViewer());
fScrollLockAction.setChecked(fIsLocked);
fWrapAction = new WrapLinesAction(getViewer());
fNextErrorAction = new NextErrorAction(this);
fPreviousErrorAction = new PreviousErrorAction(this);
fShowErrorAction = new ShowErrorAction(this);
fBringToTopOnBuild = new BringToTopOnBuild();
fSaveLogAction = new CopyBuildLogAction(this);
getViewer().setAutoScroll(!fIsLocked);
// In order for the clipboard actions to accessible via their shortcuts
// (e.g., Ctrl-C, Ctrl-V), we *must* set a global action handler for
// each action
IActionBars actionBars = getSite().getActionBars();
TextViewerAction action = new TextViewerAction(getViewer(), ITextOperationTarget.COPY);
action.configureAction(ConsoleMessages.BuildConsolePage__Copy_Ctrl_C_6, ConsoleMessages.BuildConsolePage_Copy_7,
ConsoleMessages.BuildConsolePage_Copy_7);
action.setImageDescriptor(
PlatformUI.getWorkbench().getSharedImages().getImageDescriptor(ISharedImages.IMG_TOOL_COPY));
action.setDisabledImageDescriptor(
PlatformUI.getWorkbench().getSharedImages().getImageDescriptor(ISharedImages.IMG_TOOL_COPY_DISABLED));
action.setHoverImageDescriptor(
PlatformUI.getWorkbench().getSharedImages().getImageDescriptor(ISharedImages.IMG_TOOL_COPY));
setGlobalAction(actionBars, ActionFactory.COPY.getId(), action);
action = new TextViewerAction(getViewer(), ITextOperationTarget.SELECT_ALL);
action.configureAction(ConsoleMessages.BuildConsolePage_Select__All_Ctrl_A_12,
ConsoleMessages.BuildConsolePage_Select_All, ConsoleMessages.BuildConsolePage_Select_All);
setGlobalAction(actionBars, ActionFactory.SELECT_ALL.getId(), action);
//XXX Still using "old" resource access
ResourceBundle bundle = ResourceBundle.getBundle(ConsoleMessages.BUNDLE_NAME);
setGlobalAction(actionBars, ActionFactory.FIND.getId(), new FindReplaceAction(bundle, "find_replace_action_", //$NON-NLS-1$
getConsoleView()));
action = new TextViewerGotoLineAction(getViewer());
setGlobalAction(actionBars, ITextEditorActionConstants.GOTO_LINE, action);
actionBars.updateActionBars();
fSelectionActions.add(ActionFactory.COPY.getId());
fSelectionActions.add(ActionFactory.FIND.getId());
}
protected void updateSelectionDependentActions() {
Iterator<String> iterator = fSelectionActions.iterator();
while (iterator.hasNext()) {
updateAction(iterator.next());
}
}
protected void updateAction(String actionId) {
IAction action = fGlobalActions.get(actionId);
if (action instanceof IUpdate) {
((IUpdate) action).update();
}
}
protected void setGlobalAction(IActionBars actionBars, String actionID, IAction action) {
fGlobalActions.put(actionID, action);
actionBars.setGlobalActionHandler(actionID, action);
}
protected void configureToolBar(IToolBarManager mgr) {
mgr.insertBefore(IConsoleConstants.OUTPUT_GROUP, new GroupMarker(BuildConsole.ERROR_GROUP));
mgr.appendToGroup(BuildConsole.ERROR_GROUP, fNextErrorAction);
mgr.appendToGroup(BuildConsole.ERROR_GROUP, fPreviousErrorAction);
mgr.appendToGroup(BuildConsole.ERROR_GROUP, fShowErrorAction);
mgr.appendToGroup(IConsoleConstants.OUTPUT_GROUP, fSaveLogAction);
mgr.appendToGroup(IConsoleConstants.OUTPUT_GROUP, fScrollLockAction);
mgr.appendToGroup(IConsoleConstants.OUTPUT_GROUP, fWrapAction);
mgr.appendToGroup(IConsoleConstants.OUTPUT_GROUP, fClearOutputAction);
mgr.appendToGroup(IConsoleConstants.OUTPUT_GROUP, fBringToTopOnBuild);
}
protected BuildConsoleViewer getViewer() {
return fViewer;
}
/**
* Returns the view this page is contained in
*
* @return the view this page is contained in
*/
protected IConsoleView getConsoleView() {
return fConsoleView;
}
@Override
public void dispose() {
getSite().getPage().removeSelectionListener(this);
getConsole().getConsoleManager().removeConsoleListener(this);
fViewer.removeTextListener(this);
super.dispose();
}
@Override
public void init(IPageSite pageSite) {
super.init(pageSite);
getSite().getPage().addSelectionListener(this);
getConsole().getConsoleManager().addConsoleListener(this);
}
protected void setInitialSelection() {
// Use the selection, if any
IWorkbenchPage page = getSite().getPage();
ISelection selection = null;
if (page != null)
selection = page.getSelection();
if (convertSelectionToProject(selection) == null) {
if (selection instanceof ITextSelection) {
Object part = PlatformUI.getWorkbench().getActiveWorkbenchWindow().getActivePage().getActivePart();
if (part instanceof IEditorPart) {
if (setSelectionFromEditor((IEditorPart) part) == true)
return;
}
}
IProject project = getConsole().getConsoleManager().getLastBuiltProject();
if (project != null)
selection = new StructuredSelection(project);
}
selectionChanged(null, selection);
}
boolean setSelectionFromEditor(IEditorPart part) {
if (part == null)
return false;
IWorkbenchPartSite site = part.getSite();
if (site == null)
return false;
ISelectionProvider provider = site.getSelectionProvider();
if (provider != null) {
IEditorInput ei = part.getEditorInput();
if (ei instanceof IFileEditorInput) {
IFile file = ((IFileEditorInput) ei).getFile();
selectionChanged(part, new StructuredSelection(file));
return true;
}
}
return false;
}
@Override
public void selectionChanged(IWorkbenchPart part, ISelection selection) {
IProject newProject = convertSelectionToProject(selection);
IProject oldProject = getProject();
if (oldProject == null || (newProject != null && !newProject.equals(oldProject))) {
setProject(newProject);
setDocument();
getConsole().setTitle(getProject());
}
}
IProject convertSelectionToProject(ISelection selection) {
IProject project = null;
if (selection == null || !(selection instanceof IStructuredSelection)) {
return project;
}
IStructuredSelection ssel = (IStructuredSelection) selection;
Object element = ssel.getFirstElement();
if (element instanceof IAdaptable) {
IAdaptable input = (IAdaptable) element;
IResource resource = null;
if (input instanceof IResource) {
resource = (IResource) input;
} else {
resource = input.getAdapter(IResource.class);
}
if (resource != null) {
project = resource.getProject();
}
}
return project;
}
@Override
public Control getControl() {
if (fViewer != null) {
return fViewer.getControl();
}
return null;
}
@Override
public void setFocus() {
Control control = getControl();
if (control != null) {
control.setFocus();
}
updateSelectionDependentActions();
}
/**
* Sets the font for this page.
*
* @param font
* font
*/
protected void setFont(Font font) {
getViewer().getTextWidget().setFont(font);
}
/**
* Sets the tab width for this page.
*
* @param tabs
* tab width
*/
protected void setTabs(int tabs) {
StyledText textWidget = getViewer().getTextWidget();
if (textWidget != null) {
textWidget.setTabs(tabs);
}
}
/**
* Refreshes this page
*/
protected void refresh() {
getViewer().refresh();
}
@Override
@SuppressWarnings("unchecked")
public <T> T getAdapter(Class<T> required) {
if (IFindReplaceTarget.class.equals(required)) {
return (T) getViewer().getFindReplaceTarget();
}
if (Widget.class.equals(required)) {
return (T) getViewer().getTextWidget();
}
// if (IShowInSource.class.equals(required)) {
// return this;
// }
// if (IShowInTargetList.class.equals(required)) {
// return this;
// }
return null;
}
@Override
public void textChanged(TextEvent event) {
// update the find replace action if the document length is > 0
IUpdate findReplace = (IUpdate) fGlobalActions.get(ActionFactory.FIND.getId());
if (findReplace != null) {
findReplace.update();
}
}
/**
* Get the current CDT IConsole being displayed on the page
*/
private IConsole getCurrentConsole() {
IBuildConsoleManager consoleManager = fConsole.getConsoleManager();
return consoleManager.getProjectConsole(getProject());
}
/**
* Highlight next/previous error or error by console offset
* @param position POSITION_NEXT (-1), POSITION_PREV (-2), or offset
*/
void moveToError(int position) {
IConsole console = getCurrentConsole();
if (console == null)
return;
if (console instanceof BuildConsolePartitioner) {
BuildConsolePartitioner par = (BuildConsolePartitioner) console;
BuildConsolePartition oldPartition = par.fDocumentMarkerManager.getCurrentPartition();
// Move to specified line in the model (BuildConsolePartitioner)
if (position == POSITION_NEXT) {
par.fDocumentMarkerManager.moveToNextError();
} else if (position == POSITION_PREV) {
par.fDocumentMarkerManager.moveToPreviousError();
} else if (position == POSITION_FIST) {
par.fDocumentMarkerManager.moveToFirstError();
} else if (position >= 0) {
if (!par.fDocumentMarkerManager.moveToErrorByOffset(position)) {
// we haven't moved, because offset points to non-error partition
return;
}
}
if (oldPartition != null) {
getViewer().deselectPartition(par, oldPartition);
}
showError(par, position > 0 || fShowErrorAction.isChecked());
}
}
/**
* Highlight current error and show it in editor
*/
public void showError(BuildConsolePartitioner par, boolean openInEditor) {
// Highlight current error
BuildConsolePartition p = par.fDocumentMarkerManager.getCurrentPartition();
if (p == null)
return;
getViewer().selectPartition(par, p);
// Show error in editor if necessary
// (always show when absolute positioning, otherwise depends
// on fShowErrorAction state)
if (openInEditor) {
openErrorInEditor(par.fDocumentMarkerManager.getCurrentErrorMarker());
}
}
/**
* Open error specified by marker in editor
*/
public static void openErrorInEditor(ProblemMarkerInfo marker) {
IWorkbenchWindow window = CUIPlugin.getActiveWorkbenchWindow();
if (marker == null || marker.file == null || window == null)
return;
IWorkbenchPage page = window.getActivePage();
if (page == null)
return;
IEditorPart editor = page.getActiveEditor();
if (editor != null) {
IEditorInput input = editor.getEditorInput();
IFile file = ResourceUtil.getFile(input);
if (file != null && file.equals(marker.file) && OpenStrategy.activateOnOpen()) {
page.activate(editor);
}
}
if (marker.file instanceof IFile) {
try {
// Find IMarker corresponding to ProblemMarkerInfo
IMarker mrkrs[] = marker.file.findMarkers(ICModelMarker.C_MODEL_PROBLEM_MARKER, true,
IResource.DEPTH_ONE);
for (IMarker m : mrkrs) {
if (marker.lineNumber == ((Integer) m.getAttribute(IMarker.LINE_NUMBER)).intValue()
&& marker.description.equals(m.getAttribute(IMarker.MESSAGE))
&& marker.severity == ((Integer) m.getAttribute(IMarker.SEVERITY)).intValue()) {
IDE.openEditor(page, m, OpenStrategy.activateOnOpen());
return;
}
}
} catch (PartInitException e) {
CUIPlugin.log(e);
} catch (CoreException e) {
CUIPlugin.log(e);
}
}
}
}