blob: 033b413a032e6d56c242a46668af951674df76b0 [file] [log] [blame]
/*******************************************************************************
* Copyright (c) 2000, 2015 IBM Corporation and others.
* 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:
* IBM Corporation - initial API and implementation
* Sebastian Davids <sdavids@gmx.de> bug 38745
*******************************************************************************/
package org.eclipse.ui.texteditor;
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.osgi.framework.Bundle;
import org.eclipse.swt.widgets.Shell;
import org.eclipse.core.commands.ExecutionException;
import org.eclipse.core.commands.operations.IOperationHistory;
import org.eclipse.core.commands.operations.IUndoableOperation;
import org.eclipse.core.runtime.Assert;
import org.eclipse.core.runtime.CoreException;
import org.eclipse.core.runtime.IAdaptable;
import org.eclipse.core.runtime.ILog;
import org.eclipse.core.runtime.IStatus;
import org.eclipse.core.runtime.Platform;
import org.eclipse.core.runtime.Status;
import org.eclipse.core.resources.IFile;
import org.eclipse.core.resources.IMarker;
import org.eclipse.core.resources.IResource;
import org.eclipse.jface.dialogs.ErrorDialog;
import org.eclipse.jface.dialogs.IInputValidator;
import org.eclipse.jface.dialogs.InputDialog;
import org.eclipse.jface.window.Window;
import org.eclipse.jface.text.BadLocationException;
import org.eclipse.jface.text.IDocument;
import org.eclipse.jface.text.IRegion;
import org.eclipse.jface.text.Position;
import org.eclipse.jface.text.source.IAnnotationModel;
import org.eclipse.jface.text.source.IVerticalRuler;
import org.eclipse.jface.text.source.IVerticalRulerInfo;
import org.eclipse.ui.IEditorInput;
import org.eclipse.ui.PlatformUI;
import org.eclipse.ui.ide.undo.CreateMarkersOperation;
import org.eclipse.ui.ide.undo.DeleteMarkersOperation;
/**
* A ruler action which can add and remove markers which have a visual
* representation in the ruler.
* <p>
* This class may be instantiated but is not intended for sub-classing.
* </p>
*
* @noextend This class is not intended to be subclassed by clients.
*/
public class MarkerRulerAction extends ResourceAction implements IUpdate {
/** The maximum length of an proposed label. */
private static final int MAX_LABEL_LENGTH= 80;
/** The vertical ruler info of the editor. */
private IVerticalRulerInfo fRuler;
/** The associated editor */
private ITextEditor fTextEditor;
/** The of the marker to be created/removed. */
private String fMarkerType;
/** The cached list of markers covering a particular vertical ruler position. */
private List<IMarker> fMarkers;
/** The flag indicating whether user interaction is required. */
private boolean fAskForLabel;
/** The action's resource bundle. */
private ResourceBundle fBundle;
/** The prefix used for resource bundle look ups. */
private String fPrefix;
/** The cached action label when adding a marker. */
private String fAddLabel;
/** The cached action label when removing a marker. */
private String fRemoveLabel;
/**
* Creates a new action for the given ruler and editor. The action configures
* its visual representation from the given resource bundle.
*
* @param bundle the resource bundle
* @param prefix a prefix to be prepended to the various resource keys
* (described in {@link org.eclipse.ui.texteditor.ResourceAction} constructor), or <code>null</code> if none
* @param editor the editor
* @param ruler the ruler
* @param markerType the type of marker
* @param askForLabel <code>true</code> if the user should be asked for a label when a new marker is created
* @see ResourceAction#ResourceAction(ResourceBundle, String)
* @since 2.0
*/
public MarkerRulerAction(ResourceBundle bundle, String prefix, ITextEditor editor, IVerticalRulerInfo ruler, String markerType, boolean askForLabel) {
super(bundle, prefix);
Assert.isLegal(editor != null);
fRuler= ruler;
fTextEditor= editor;
fMarkerType= markerType;
fAskForLabel= askForLabel;
fBundle= bundle;
fPrefix= prefix;
fAddLabel= getString(bundle, prefix + "add.label", prefix + "add.label"); //$NON-NLS-2$ //$NON-NLS-1$
fRemoveLabel= getString(bundle, prefix + "remove.label", prefix + "remove.label"); //$NON-NLS-2$ //$NON-NLS-1$
}
/**
* Creates a new action for the given ruler and editor. The action configures
* its visual representation from the given resource bundle.
*
* @param bundle the resource bundle
* @param prefix a prefix to be prepended to the various resource keys
* @param ruler the ruler
* @param editor the editor
* @param markerType the type of the marker
* @param askForLabel <code>true</code> if the user should be asked for a label
* @deprecated use <code>MarkerRulerAction(ResourceBundle, String, ITextEditor, IVerticalRulerInfo, String, boolean)</code> instead
*/
@Deprecated
public MarkerRulerAction(ResourceBundle bundle, String prefix, IVerticalRuler ruler, ITextEditor editor, String markerType, boolean askForLabel) {
this(bundle, prefix, editor, ruler, markerType, askForLabel);
}
/**
* Returns this action's text editor.
*
* @return this action's text editor
*/
protected ITextEditor getTextEditor() {
return fTextEditor;
}
/**
* Returns this action's vertical ruler.
*
* @return this action's vertical ruler
* @deprecated use <code>getVerticalRulerInfo</code> instead
*/
@Deprecated
protected IVerticalRuler getVerticalRuler() {
if (fRuler instanceof IVerticalRuler)
return (IVerticalRuler) fRuler;
return null;
}
/**
* Returns this action's vertical ruler info.
*
* @return this action's vertical ruler info
* @since 2.0
*/
protected IVerticalRulerInfo getVerticalRulerInfo() {
return fRuler;
}
/**
* Returns this action's resource bundle.
*
* @return this action's resource bundle
*/
protected ResourceBundle getResourceBundle() {
return fBundle;
}
/**
* Returns this action's resource key prefix.
*
* @return this action's resource key prefix
*/
protected String getResourceKeyPrefix() {
return fPrefix;
}
@Override
public void update() {
//bug 38745
IDocument document= getDocument();
if (document != null) {
int line= getVerticalRuler().getLineOfLastMouseButtonActivity() + 1;
if (line > document.getNumberOfLines()) {
setEnabled(false);
setText(fAddLabel);
} else {
fMarkers= getMarkers();
setEnabled(getResource() != null && (fMarkers.isEmpty() || markersUserEditable(fMarkers)));
setText(fMarkers.isEmpty() ? fAddLabel : fRemoveLabel);
}
}
}
/**
* Returns whether the given markers are all editable by the user.
*
* @param markers the list of markers to test
* @return boolean <code>true</code> if they are all editable
* @since 3.2
*/
private boolean markersUserEditable(List<IMarker> markers) {
Iterator<IMarker> iter= markers.iterator();
while (iter.hasNext()) {
if (!isUserEditable(iter.next()))
return false;
}
return true;
}
/**
* Returns whether the given marker is editable by the user.
*
* @param marker the marker to test
* @return boolean <code>true</code> if it is editable
* @since 3.2
*/
private boolean isUserEditable(IMarker marker) {
return marker != null && marker.exists() && marker.getAttribute(IMarker.USER_EDITABLE, true);
}
@Override
public void run() {
if (fMarkers.isEmpty())
addMarker();
else
removeMarkers(fMarkers);
}
/**
* Returns the resource for which to create the marker,
* or <code>null</code> if there is no applicable resource.
*
* @return the resource for which to create the marker or <code>null</code>
*/
protected IResource getResource() {
IEditorInput input= fTextEditor.getEditorInput();
IResource resource= input.getAdapter(IFile.class);
if (resource == null)
resource= input.getAdapter(IResource.class);
return resource;
}
/**
* Returns the <code>AbstractMarkerAnnotationModel</code> of the editor's input.
*
* @return the marker annotation model
*/
protected AbstractMarkerAnnotationModel getAnnotationModel() {
IDocumentProvider provider= fTextEditor.getDocumentProvider();
IAnnotationModel model= provider.getAnnotationModel(fTextEditor.getEditorInput());
if (model instanceof AbstractMarkerAnnotationModel)
return (AbstractMarkerAnnotationModel) model;
return null;
}
/**
* Returns the <code>IDocument</code> of the editor's input.
*
* @return the document of the editor's input
*/
protected IDocument getDocument() {
IDocumentProvider provider= fTextEditor.getDocumentProvider();
return provider.getDocument(fTextEditor.getEditorInput());
}
/**
* Checks whether a position includes the ruler's line of activity.
*
* @param position the position to be checked
* @param document the document the position refers to
* @return <code>true</code> if the line is included by the given position
*/
protected boolean includesRulerLine(Position position, IDocument document) {
if (position != null) {
try {
int markerLine= document.getLineOfOffset(position.getOffset());
int line= fRuler.getLineOfLastMouseButtonActivity();
if (line == markerLine)
return true;
// commented because of "1GEUOZ9: ITPJUI:ALL - Confusing UI for multi-line Bookmarks and Tasks"
// return (markerLine <= line && line <= document.getLineOfOffset(position.getOffset() + position.getLength()));
} catch (BadLocationException x) {
}
}
return false;
}
/**
* Handles core exceptions. This implementation logs the exceptions
* with the workbench plug-in and shows an error dialog.
*
* @param exception the exception to be handled
* @param message the message to be logged with the given exception
*/
protected void handleCoreException(CoreException exception, String message) {
Bundle bundle= Platform.getBundle(PlatformUI.PLUGIN_ID);
ILog log= Platform.getLog(bundle);
if (message != null)
log.log(new Status(IStatus.ERROR, PlatformUI.PLUGIN_ID, IStatus.OK, message, exception));
else
log.log(exception.getStatus());
Shell shell= getTextEditor().getSite().getShell();
String title= getString(fBundle, fPrefix + "error.dialog.title", fPrefix + "error.dialog.title"); //$NON-NLS-2$ //$NON-NLS-1$
String msg= getString(fBundle, fPrefix + "error.dialog.message", fPrefix + "error.dialog.message"); //$NON-NLS-2$ //$NON-NLS-1$
ErrorDialog.openError(shell, title, msg, exception.getStatus());
}
/**
* Returns all markers which include the ruler's line of activity.
*
* @return all a list of markers which include the ruler's line of activity
*/
protected List<IMarker> getMarkers() {
List<IMarker> markers= new ArrayList<>();
IResource resource= getResource();
IDocument document= getDocument();
AbstractMarkerAnnotationModel model= getAnnotationModel();
if (resource != null && model != null && resource.exists()) {
try {
IMarker[] allMarkers= resource.findMarkers(fMarkerType, true, IResource.DEPTH_ZERO);
if (allMarkers != null) {
for (int i= 0; i < allMarkers.length; i++) {
if (includesRulerLine(model.getMarkerPosition(allMarkers[i]), document)) {
markers.add(allMarkers[i]);
}
}
}
} catch (CoreException x) {
handleCoreException(x, TextEditorMessages.MarkerRulerAction_getMarker);
}
}
return markers;
}
/**
* Creates a new marker according to the specification of this action and
* adds it to the marker resource.
*/
protected void addMarker() {
IResource resource= getResource();
if (resource == null)
return;
Map<String, Object> attributes= getInitialAttributes();
if (fAskForLabel) {
if (!askForLabel(attributes))
return;
}
execute(new CreateMarkersOperation(fMarkerType, attributes, resource, getOperationName()));
}
/**
* Removes the given markers.
*
* @param markers the markers to be deleted
*/
protected void removeMarkers(final List<? extends IMarker> markers) {
IMarker[] markersArray= markers.toArray(new IMarker[markers.size()]);
execute(new DeleteMarkersOperation(markersArray, getOperationName()));
}
/**
* Asks the user for a marker label. Returns <code>true</code> if a label
* is entered, <code>false</code> if the user cancels the input dialog.
* Sets the value of the attribute <code>message</code> in the given
* map of attributes.
*
* @param attributes the map of attributes
* @return <code>true</code> if the map of attributes has successfully been initialized
*/
protected boolean askForLabel(Map<String, Object> attributes) {
Object o= attributes.get("message"); //$NON-NLS-1$
String proposal= (o instanceof String) ? (String) o : ""; //$NON-NLS-1$
String title= getString(fBundle, fPrefix + "add.dialog.title", fPrefix + "add.dialog.title"); //$NON-NLS-2$ //$NON-NLS-1$
String message= getString(fBundle, fPrefix + "add.dialog.message", fPrefix + "add.dialog.message"); //$NON-NLS-2$ //$NON-NLS-1$
IInputValidator inputValidator= new IInputValidator() {
@Override
public String isValid(String newText) {
return (newText == null || newText.trim().length() == 0) ? " " : null; //$NON-NLS-1$
}
};
InputDialog dialog= new InputDialog(fTextEditor.getSite().getShell(), title, message, proposal, inputValidator);
String label= null;
if (dialog.open() != Window.CANCEL)
label= dialog.getValue();
if (label == null)
return false;
label= label.trim();
if (label.length() == 0)
return false;
MarkerUtilities.setMessage(attributes, label);
return true;
}
/**
* Returns the attributes with which a newly created marker will be
* initialized.
*
* @return the initial marker attributes
*/
protected Map<String, Object> getInitialAttributes() {
Map<String, Object> attributes= new HashMap<>(11);
IDocumentProvider provider= fTextEditor.getDocumentProvider();
IDocument document= provider.getDocument(fTextEditor.getEditorInput());
int line= fRuler.getLineOfLastMouseButtonActivity();
int start= -1;
int end= -1;
int length= 0;
try {
IRegion lineInformation= document.getLineInformation(line);
start= lineInformation.getOffset();
length= lineInformation.getLength();
end= start + length;
} catch (BadLocationException x) {
}
// marker line numbers are 1-based
MarkerUtilities.setMessage(attributes, getLabelProposal(document, start, length));
MarkerUtilities.setLineNumber(attributes, line + 1);
MarkerUtilities.setCharStart(attributes, start);
MarkerUtilities.setCharEnd(attributes, end);
return attributes;
}
/**
* Returns the initial label for the marker.
*
* @param document the document from which to extract a label proposal
* @param offset the document offset of the range from which to extract the label proposal
* @param length the length of the range from which to extract the label proposal
* @return the label proposal
* @since 3.0
*/
protected String getLabelProposal(IDocument document, int offset, int length) {
try {
String label= document.get(offset, length).trim();
if (label.length() <= MAX_LABEL_LENGTH)
return label;
return label.substring(0, MAX_LABEL_LENGTH);
} catch (BadLocationException x) {
// don't propose label then
return null;
}
}
/**
* Returns the name to be used for the operation.
*
* @return the operation name
* @since 3.3
*/
private String getOperationName() {
String name= getText();
return name == null ? TextEditorMessages.AddMarkerAction_addMarker : name;
}
/**
* Execute the specified undoable operation.
*
* @param operation the operation to execute
* @since 3.3
*/
private void execute(IUndoableOperation operation) {
final Shell shell= getTextEditor().getSite().getShell();
IAdaptable context= new IAdaptable() {
@SuppressWarnings("unchecked")
@Override
public <T> T getAdapter(Class<T> adapter) {
if (adapter == Shell.class)
return (T) shell;
return null;
}
};
IOperationHistory operationHistory= PlatformUI.getWorkbench().getOperationSupport().getOperationHistory();
try {
operationHistory.execute(operation, null, context);
} catch (ExecutionException e) {
Bundle bundle= Platform.getBundle(PlatformUI.PLUGIN_ID);
ILog log= Platform.getLog(bundle);
String msg= getString(fBundle, fPrefix + "error.dialog.message", fPrefix + "error.dialog.message"); //$NON-NLS-2$ //$NON-NLS-1$
log.log(new Status(IStatus.ERROR, PlatformUI.PLUGIN_ID, IStatus.OK, msg, e));
}
}
}