1 /******************************************************************************* 2 * Copyright (c) 2000, 2015 IBM Corporation and others. 3 * 4 * This program and the accompanying materials 5 * are made available under the terms of the Eclipse Public License 2.0 6 * which accompanies this distribution, and is available at 7 * https://www.eclipse.org/legal/epl-2.0/ 8 * 9 * SPDX-License-Identifier: EPL-2.0 10 * 11 * Contributors: 12 * IBM Corporation - initial API and implementation 13 *******************************************************************************/ 14 package org.eclipse.ui.texteditor; 15 16 import java.util.HashMap; 17 import java.util.Map; 18 import java.util.ResourceBundle; 19 20 import org.osgi.framework.Bundle; 21 22 import org.eclipse.swt.widgets.Shell; 23 24 import org.eclipse.core.commands.ExecutionException; 25 import org.eclipse.core.commands.operations.IOperationHistory; 26 import org.eclipse.core.commands.operations.IUndoableOperation; 27 28 import org.eclipse.core.runtime.IAdaptable; 29 import org.eclipse.core.runtime.ILog; 30 import org.eclipse.core.runtime.IStatus; 31 import org.eclipse.core.runtime.Platform; 32 import org.eclipse.core.runtime.Status; 33 34 import org.eclipse.core.resources.IResource; 35 36 import org.eclipse.jface.dialogs.IInputValidator; 37 import org.eclipse.jface.dialogs.InputDialog; 38 import org.eclipse.jface.window.Window; 39 40 import org.eclipse.jface.text.BadLocationException; 41 import org.eclipse.jface.text.IDocument; 42 import org.eclipse.jface.text.ITextSelection; 43 44 import org.eclipse.ui.IEditorInput; 45 import org.eclipse.ui.PlatformUI; 46 import org.eclipse.ui.ide.undo.CreateMarkersOperation; 47 48 49 /** 50 * Action for creating a marker of a specified type for the editor's input element based on the 51 * editor's selection. If required, the action asks the user to provide a marker label. The action 52 * is initially associated with a text editor via the constructor, but that can be subsequently 53 * changed using <code>setEditor</code>. 54 * <p> 55 * The following keys, prepended by the given option prefix, are used for retrieving resources from 56 * the given bundle: 57 * </p> 58 * <ul> 59 * <li><code>"dialog.title"</code> - the input dialog's title</li> 60 * <li><code>"dialog.message"</code> - the input dialog's message</li> 61 * <li><code>"error.dialog.title"</code> - the error dialog's title</li> 62 * <li><code>"error.dialog.message"</code> - the error dialog's message</li> 63 * </ul> 64 * <p> 65 * This class may be instantiated but is not intended to be subclassed. 66 * </p> 67 * 68 * @noextend This class is not intended to be subclassed by clients. 69 */ 70 public class AddMarkerAction extends TextEditorAction { 71 72 73 /** The maximum length of an proposed label. */ 74 private static final int MAX_LABEL_LENGTH= 80; 75 /** The type for newly created markers. */ 76 private String fMarkerType; 77 /** Should the user be asked for a label? */ 78 private boolean fAskForLabel; 79 /** The action's resource bundle. */ 80 private ResourceBundle fBundle; 81 /** The prefix used for resource bundle lookup. */ 82 private String fPrefix; 83 84 85 /** 86 * Creates a new action for the given text editor. The action configures its 87 * visual representation from the given resource bundle. 88 * 89 * @param bundle the resource bundle 90 * @param prefix a prefix to be prepended to the various resource keys 91 * (described in <code>ResourceAction</code> constructor), or 92 * <code>null</code> if none 93 * @param textEditor the text editor 94 * @param markerType the type of marker to add 95 * @param askForLabel <code>true</code> if the user should be asked for 96 * a label for the new marker 97 * @see TextEditorAction#TextEditorAction(ResourceBundle, String, ITextEditor) 98 */ AddMarkerAction(ResourceBundle bundle, String prefix, ITextEditor textEditor, String markerType, boolean askForLabel)99 public AddMarkerAction(ResourceBundle bundle, String prefix, ITextEditor textEditor, String markerType, boolean askForLabel) { 100 super(bundle, prefix, textEditor); 101 fBundle= bundle; 102 fPrefix= prefix; 103 fMarkerType= markerType; 104 fAskForLabel= askForLabel; 105 } 106 107 /** 108 * Returns this action's resource bundle. 109 * 110 * @return this action's resource bundle 111 */ getResourceBundle()112 protected ResourceBundle getResourceBundle() { 113 return fBundle; 114 } 115 116 /** 117 * Returns this action's resource key prefix. 118 * 119 * @return this action's resource key prefix 120 */ getResourceKeyPrefix()121 protected String getResourceKeyPrefix() { 122 return fPrefix; 123 } 124 125 @Override run()126 public void run() { 127 IResource resource= getResource(); 128 if (resource == null) 129 return; 130 Map<String, Object> attributes= getInitialAttributes(); 131 if (fAskForLabel) { 132 if (!askForLabel(attributes)) 133 return; 134 } 135 136 String name= getToolTipText(); 137 name= name == null ? TextEditorMessages.AddMarkerAction_addMarker : name; 138 139 final Shell shell= getTextEditor().getSite().getShell(); 140 IAdaptable context= new IAdaptable() { 141 @SuppressWarnings("unchecked") 142 @Override 143 public <T> T getAdapter(Class<T> adapter) { 144 if (adapter == Shell.class) 145 return (T) shell; 146 return null; 147 } 148 }; 149 150 IUndoableOperation operation= new CreateMarkersOperation(fMarkerType, attributes, resource, name); 151 IOperationHistory operationHistory= PlatformUI.getWorkbench().getOperationSupport().getOperationHistory(); 152 try { 153 operationHistory.execute(operation, null, context); 154 } catch (ExecutionException x) { 155 Bundle bundle= Platform.getBundle(PlatformUI.PLUGIN_ID); 156 ILog log= Platform.getLog(bundle); 157 String msg= getString(fBundle, fPrefix + "error.dialog.message", fPrefix + "error.dialog.message"); //$NON-NLS-2$ //$NON-NLS-1$ 158 log.log(new Status(IStatus.ERROR, PlatformUI.PLUGIN_ID, IStatus.OK, msg, x)); 159 } 160 } 161 162 @Override update()163 public void update() { 164 setEnabled(getResource() != null); 165 } 166 167 /** 168 * Asks the user for a marker label. Returns <code>true</code> if a label 169 * is entered, <code>false</code> if the user cancels the input dialog. 170 * The value for the attribute <code>message</code> is modified in the given 171 * attribute map. 172 * 173 * @param attributes the attributes map 174 * @return <code>true</code> if a label has been entered 175 */ askForLabel(Map<String, Object> attributes)176 protected boolean askForLabel(Map<String, Object> attributes) { 177 178 Object o= attributes.get("message"); //$NON-NLS-1$ 179 String proposal= (o instanceof String) ? (String) o : ""; //$NON-NLS-1$ 180 String title= getString(fBundle, fPrefix + "dialog.title", fPrefix + "dialog.title"); //$NON-NLS-2$ //$NON-NLS-1$ 181 String message= getString(fBundle, fPrefix + "dialog.message", fPrefix + "dialog.message"); //$NON-NLS-2$ //$NON-NLS-1$ 182 IInputValidator inputValidator= newText -> (newText == null || newText.trim().isEmpty()) ? " " : null; 183 InputDialog dialog= new InputDialog(getTextEditor().getSite().getShell(), title, message, proposal, inputValidator); 184 185 String label= null; 186 if (dialog.open() != Window.CANCEL) 187 label= dialog.getValue(); 188 189 if (label == null) 190 return false; 191 192 label= label.trim(); 193 if (label.isEmpty()) 194 return false; 195 196 attributes.put("message", label); //$NON-NLS-1$ 197 return true; 198 } 199 200 /** 201 * Returns the attributes the new marker will be initialized with. 202 * <p> 203 * Subclasses may extend or replace this method.</p> 204 * 205 * @return the attributes the new marker will be initialized with 206 */ getInitialAttributes()207 protected Map<String, Object> getInitialAttributes() { 208 209 Map<String, Object> attributes= new HashMap<>(11); 210 211 ITextSelection selection= (ITextSelection) getTextEditor().getSelectionProvider().getSelection(); 212 if (!selection.isEmpty()) { 213 214 int start= selection.getOffset(); 215 int length= selection.getLength(); 216 217 if (length < 0) { 218 length= -length; 219 start -= length; 220 } 221 222 MarkerUtilities.setCharStart(attributes, start); 223 MarkerUtilities.setCharEnd(attributes, start + length); 224 225 // marker line numbers are 1-based 226 int line= selection.getStartLine(); 227 MarkerUtilities.setLineNumber(attributes, line == -1 ? -1 : line + 1); 228 229 IDocument document= getTextEditor().getDocumentProvider().getDocument(getTextEditor().getEditorInput()); 230 MarkerUtilities.setMessage(attributes, getLabelProposal(document, start, length)); 231 232 } 233 234 return attributes; 235 } 236 237 /** 238 * Returns the initial label for the marker. 239 * 240 * @param document the document from which to extract a label proposal 241 * @param offset the document offset of the range from which to extract the label proposal 242 * @param length the length of the range from which to extract the label proposal 243 * @return the label proposal 244 */ getLabelProposal(IDocument document, int offset, int length)245 protected String getLabelProposal(IDocument document, int offset, int length) { 246 247 248 try { 249 250 251 if (length > 0) { 252 253 // find first white char but skip leading white chars 254 int i= 0; 255 boolean skip= true; 256 while (i < length) { 257 boolean isWhitespace= Character.isWhitespace(document.getChar(offset + i)); 258 if (!skip && isWhitespace) 259 break; 260 if (skip && !isWhitespace) 261 skip= false; 262 i++; 263 } 264 265 String label= document.get(offset, i); 266 return label.trim(); 267 } 268 269 270 char ch; 271 272 // Get the first white char before the selection. 273 int left= offset; 274 275 int line= document.getLineOfOffset(offset); 276 int limit= document.getLineOffset(line); 277 278 while (left > limit) { 279 ch= document.getChar(left); 280 if (Character.isWhitespace(ch)) 281 break; 282 --left; 283 } 284 285 limit += document.getLineLength(line); 286 287 // Now get the first letter. 288 while (left <= limit) { 289 ch= document.getChar(left); 290 if (!Character.isWhitespace(ch)) 291 break; 292 ++left; 293 } 294 295 if (left > limit) 296 return null; 297 298 limit= Math.min(limit, left + MAX_LABEL_LENGTH); 299 300 // Get the next white char. 301 int right= (offset + length > limit ? limit : offset + length); 302 while (right < limit) { 303 ch= document.getChar(right); 304 if (Character.isWhitespace(ch)) 305 break; 306 ++right; 307 } 308 309 // Trim the string and return it. 310 if (left != right) { 311 String label= document.get(left, right - left); 312 return label.trim(); 313 } 314 315 } catch (BadLocationException x) { 316 // don't proposal label then 317 } 318 319 return null; 320 } 321 322 /** 323 * Returns the resource on which to create the marker, 324 * or <code>null</code> if there is no applicable resource. This 325 * queries the editor's input using <code>getAdapter(IResource.class)</code>. 326 * Subclasses may override this method. 327 * 328 * @return the resource to which to attach the newly created marker 329 */ getResource()330 protected IResource getResource() { 331 ITextEditor editor= getTextEditor(); 332 if (editor != null) { 333 IEditorInput input= editor.getEditorInput(); 334 return ((IAdaptable) input).getAdapter(IResource.class); 335 } 336 return null; 337 } 338 } 339