| /******************************************************************************* |
| * 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)); |
| } |
| } |
| } |