blob: 9fbd8b7ab0ffd731accc1d020181b479da74b8af [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
*******************************************************************************/
package org.eclipse.ui.texteditor;
import java.util.HashMap;
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.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.IResource;
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.ITextSelection;
import org.eclipse.ui.IEditorInput;
import org.eclipse.ui.PlatformUI;
import org.eclipse.ui.ide.undo.CreateMarkersOperation;
/**
* Action for creating a marker of a specified type for the editor's
* input element based on the editor's selection. If required, the
* action asks the user to provide a marker label. The action is initially
* associated with a text editor via the constructor, but that can be
* subsequently changed using <code>setEditor</code>.
* <p>
* The following keys, prepended by the given option prefix,
* are used for retrieving resources from the given bundle:
* <ul>
* <li><code>"dialog.title"</code> - the input dialog's title</li>
* <li><code>"dialog.message"</code> - the input dialog's message</li>
* <li><code>"error.dialog.title"</code> - the error dialog's title</li>
* <li><code>"error.dialog.message"</code> - the error dialog's message</li>
* </ul>
* This class may be instantiated but is not intended to be subclassed.
* </p>
*
* @noextend This class is not intended to be subclassed by clients.
*/
public class AddMarkerAction extends TextEditorAction {
/** The maximum length of an proposed label. */
private static final int MAX_LABEL_LENGTH= 80;
/** The type for newly created markers. */
private String fMarkerType;
/** Should the user be asked for a label? */
private boolean fAskForLabel;
/** The action's resource bundle. */
private ResourceBundle fBundle;
/** The prefix used for resource bundle lookup. */
private String fPrefix;
/**
* Creates a new action for the given text 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 <code>ResourceAction</code> constructor), or
* <code>null</code> if none
* @param textEditor the text editor
* @param markerType the type of marker to add
* @param askForLabel <code>true</code> if the user should be asked for
* a label for the new marker
* @see TextEditorAction#TextEditorAction(ResourceBundle, String, ITextEditor)
*/
public AddMarkerAction(ResourceBundle bundle, String prefix, ITextEditor textEditor, String markerType, boolean askForLabel) {
super(bundle, prefix, textEditor);
fBundle= bundle;
fPrefix= prefix;
fMarkerType= markerType;
fAskForLabel= askForLabel;
}
/**
* 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 run() {
IResource resource= getResource();
if (resource == null)
return;
Map<String, Object> attributes= getInitialAttributes();
if (fAskForLabel) {
if (!askForLabel(attributes))
return;
}
String name= getToolTipText();
name= name == null ? TextEditorMessages.AddMarkerAction_addMarker : name;
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;
}
};
IUndoableOperation operation= new CreateMarkersOperation(fMarkerType, attributes, resource, name);
IOperationHistory operationHistory= PlatformUI.getWorkbench().getOperationSupport().getOperationHistory();
try {
operationHistory.execute(operation, null, context);
} catch (ExecutionException x) {
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, x));
}
}
@Override
public void update() {
setEnabled(getResource() != null);
}
/**
* 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.
* The value for the attribute <code>message</code> is modified in the given
* attribute map.
*
* @param attributes the attributes map
* @return <code>true</code> if a label has been entered
*/
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 + "dialog.title", fPrefix + "dialog.title"); //$NON-NLS-2$ //$NON-NLS-1$
String message= getString(fBundle, fPrefix + "dialog.message", fPrefix + "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(getTextEditor().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;
attributes.put("message", label); //$NON-NLS-1$
return true;
}
/**
* Returns the attributes the new marker will be initialized with.
* <p>
* Subclasses may extend or replace this method.</p>
*
* @return the attributes the new marker will be initialized with
*/
protected Map<String, Object> getInitialAttributes() {
Map<String, Object> attributes= new HashMap<>(11);
ITextSelection selection= (ITextSelection) getTextEditor().getSelectionProvider().getSelection();
if (!selection.isEmpty()) {
int start= selection.getOffset();
int length= selection.getLength();
if (length < 0) {
length= -length;
start -= length;
}
MarkerUtilities.setCharStart(attributes, start);
MarkerUtilities.setCharEnd(attributes, start + length);
// marker line numbers are 1-based
int line= selection.getStartLine();
MarkerUtilities.setLineNumber(attributes, line == -1 ? -1 : line + 1);
IDocument document= getTextEditor().getDocumentProvider().getDocument(getTextEditor().getEditorInput());
MarkerUtilities.setMessage(attributes, getLabelProposal(document, start, length));
}
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
*/
protected String getLabelProposal(IDocument document, int offset, int length) {
try {
if (length > 0) {
// find first white char but skip leading white chars
int i= 0;
boolean skip= true;
while (i < length) {
boolean isWhitespace= Character.isWhitespace(document.getChar(offset + i));
if (!skip && isWhitespace)
break;
if (skip && !isWhitespace)
skip= false;
i++;
}
String label= document.get(offset, i);
return label.trim();
}
char ch;
// Get the first white char before the selection.
int left= offset;
int line= document.getLineOfOffset(offset);
int limit= document.getLineOffset(line);
while (left > limit) {
ch= document.getChar(left);
if (Character.isWhitespace(ch))
break;
--left;
}
limit += document.getLineLength(line);
// Now get the first letter.
while (left <= limit) {
ch= document.getChar(left);
if (!Character.isWhitespace(ch))
break;
++left;
}
if (left > limit)
return null;
limit= Math.min(limit, left + MAX_LABEL_LENGTH);
// Get the next white char.
int right= (offset + length > limit ? limit : offset + length);
while (right < limit) {
ch= document.getChar(right);
if (Character.isWhitespace(ch))
break;
++right;
}
// Trim the string and return it.
if (left != right) {
String label= document.get(left, right - left);
return label.trim();
}
} catch (BadLocationException x) {
// don't proposal label then
}
return null;
}
/**
* Returns the resource on which to create the marker,
* or <code>null</code> if there is no applicable resource. This
* queries the editor's input using <code>getAdapter(IResource.class)</code>.
* Subclasses may override this method.
*
* @return the resource to which to attach the newly created marker
*/
protected IResource getResource() {
ITextEditor editor= getTextEditor();
if (editor != null) {
IEditorInput input= editor.getEditorInput();
return ((IAdaptable) input).getAdapter(IResource.class);
}
return null;
}
}