* Added browser like links (Ctrl+Mouseclick on identifier; same as F3 shortcut)
[phpeclipse.git] / net.sourceforge.phpeclipse / src / net / sourceforge / phpdt / internal / ui / actions / BlockCommentAction.java
1 /*******************************************************************************
2  * Copyright (c) 2000, 2004 IBM Corporation and others.
3  * All rights reserved. This program and the accompanying materials 
4  * are made available under the terms of the Common Public License v1.0
5  * which accompanies this distribution, and is available at
6  * http://www.eclipse.org/legal/cpl-v10.html
7  * 
8  * Contributors:
9  *     IBM Corporation - initial API and implementation
10  *******************************************************************************/
11 package net.sourceforge.phpdt.internal.ui.actions;
12
13 import java.util.Iterator;
14 import java.util.List;
15 import java.util.ResourceBundle;
16
17 import org.eclipse.jface.text.*;
18 import org.eclipse.jface.viewers.ISelection;
19 import org.eclipse.jface.viewers.ISelectionProvider;
20
21 import org.eclipse.ui.IEditorInput;
22 import org.eclipse.ui.texteditor.IDocumentProvider;
23 import org.eclipse.ui.texteditor.ITextEditor;
24 import org.eclipse.ui.texteditor.ITextEditorExtension2;
25 import org.eclipse.ui.texteditor.TextEditorAction;
26
27
28 /**
29  * Common block comment code.
30  * 
31  * @since 3.0
32  */
33 public abstract class BlockCommentAction extends TextEditorAction {
34
35         /**
36          * Creates a new instance.
37          * @param bundle
38          * @param prefix
39          * @param editor
40          */
41         public BlockCommentAction(ResourceBundle bundle, String prefix, ITextEditor editor) {
42                 super(bundle, prefix, editor);
43         }
44
45         /**
46          * An edit is a kind of <code>DocumentEvent</code>, in this case an edit instruction, that is 
47          * affilitated with a <code>Position</code> on a document. The offset of the document event is 
48          * not stored statically, but taken from the affiliated <code>Position</code>, which gets 
49          * updated when other edits occurr.
50          */
51         static class Edit extends DocumentEvent {
52                 
53                 /**
54                  * Factory for edits which manages the creation, installation and destruction of 
55                  * position categories, position updaters etc. on a certain document. Once a factory has
56                  * been obtained, <code>Edit</code> objects can be obtained from it which will be linked to
57                  * the document by positions of one position category.
58                  * <p>Clients are required to call <code>release</code> once the <code>Edit</code>s are not
59                  * used any more, so the positions can be discarded.</p>
60                  */
61                 public static class EditFactory {
62         
63                         /** The position category basename for this edits. */
64                         private static final String CATEGORY= "__positionalEditPositionCategory"; //$NON-NLS-1$
65                         
66                         /** The count of factories. */
67                         private static int fgCount= 0;
68                 
69                         /** This factory's category. */
70                         private final String fCategory;
71                         private IDocument fDocument;
72                         private IPositionUpdater fUpdater;
73                         
74                         /**
75                          * Creates a new <code>EditFactory</code> with an unambiguous position category name.
76                          * @param document the document that is being edited.
77                          */
78                         public EditFactory(IDocument document) {
79                                 fCategory= CATEGORY + fgCount++;
80                                 fDocument= document;
81                         }
82                         
83                         /**
84                          * Creates a new edition on the document of this factory.
85                          * 
86                          * @param offset the offset of the edition at the point when is created.
87                          * @param length the length of the edition (not updated via the position update mechanism)
88                          * @param text the text to be replaced on the document
89                          * @return an <code>Edit</code> reflecting the edition on the document
90                          */
91                         public Edit createEdit(int offset, int length, String text) throws BadLocationException {
92                                 
93                                 if (!fDocument.containsPositionCategory(fCategory)) {
94                                         fDocument.addPositionCategory(fCategory);
95                                         fUpdater= new DefaultPositionUpdater(fCategory);
96                                         fDocument.addPositionUpdater(fUpdater);
97                                 }
98                                 
99                                 Position position= new Position(offset);
100                                 try {
101                                         fDocument.addPosition(fCategory, position);
102                                 } catch (BadPositionCategoryException e) {
103                                         Assert.isTrue(false);
104                                 }
105                                 return new Edit(fDocument, length, text, position);
106                         }
107                         
108                         /**
109                          * Releases the position category on the document and uninstalls the position updater. 
110                          * <code>Edit</code>s managed by this factory are not updated after this call.
111                          */
112                         public void release() {
113                                 if (fDocument != null && fDocument.containsPositionCategory(fCategory)) {
114                                         fDocument.removePositionUpdater(fUpdater);
115                                         try {
116                                                 fDocument.removePositionCategory(fCategory);
117                                         } catch (BadPositionCategoryException e) {
118                                                 Assert.isTrue(false);
119                                         }
120                                         fDocument= null;
121                                         fUpdater= null;
122                                 }
123                         }
124                 }
125                 
126                 /** The position in the document where this edit be executed. */
127                 private Position fPosition;
128                 
129                 /**
130                  * Creates a new edition on <code>document</code>, taking its offset from <code>position</code>.
131                  * 
132                  * @param document the document being edited
133                  * @param length the length of the edition
134                  * @param text the replacement text of the edition
135                  * @param position the position keeping the edition's offset
136                  */
137                 protected Edit(IDocument document, int length, String text, Position position) {
138                         super(document, 0, length, text);
139                         fPosition= position;
140                 }
141                 
142                 /*
143                  * @see org.eclipse.jface.text.DocumentEvent#getOffset()
144                  */
145                 public int getOffset() {
146                         return fPosition.getOffset();
147                 }
148                 
149                 /**
150                  * Executes the edition on document. The offset is taken from the position.
151                  * 
152                  * @throws BadLocationException if the execution of the document fails.
153                  */
154                 public void perform() throws BadLocationException {
155                         getDocument().replace(getOffset(), getLength(), getText());
156                 }
157                 
158         }
159
160         public void run() {
161                 if (!isEnabled())
162                         return;
163                         
164                 ITextEditor editor= getTextEditor();
165                 if (editor == null || !ensureEditable(editor))
166                         return;
167                         
168                 ITextSelection selection= getCurrentSelection();
169                 if (!isValidSelection(selection))
170                         return;
171                 
172                 if (!validateEditorInputState())
173                         return;
174                 
175                 IDocumentProvider docProvider= editor.getDocumentProvider();
176                 IEditorInput input= editor.getEditorInput();
177                 if (docProvider == null || input == null)
178                         return;
179                         
180                 IDocument document= docProvider.getDocument(input);
181                 if (document == null)
182                         return;
183                         
184                 IDocumentExtension3 docExtension;
185                 if (document instanceof IDocumentExtension3)
186                         docExtension= (IDocumentExtension3) document;
187                 else
188                         return;
189                 
190                 IRewriteTarget target= (IRewriteTarget)editor.getAdapter(IRewriteTarget.class);
191                 if (target != null) {
192                         target.beginCompoundChange();
193                 }
194                 
195                 Edit.EditFactory factory= new Edit.EditFactory(document);
196                 
197                 try {
198                         runInternal(selection, docExtension, factory);
199         
200                 } catch (BadLocationException e) {
201                         // can happen on concurrent modification, deletion etc. of the document 
202                         // -> don't complain, just bail out
203                 } catch (BadPartitioningException e) {
204                         // should not happen
205                         Assert.isTrue(false, "bad partitioning");  //$NON-NLS-1$
206                 } finally {
207                         factory.release();
208                         
209                         if (target != null) {
210                                 target.endCompoundChange();
211                         }
212                 }
213         }
214
215         /**
216          * Calls <code>perform</code> on all <code>Edit</code>s in <code>edits</code>.
217          * 
218          * @param edits a list of <code>Edit</code>s
219          * @throws BadLocationException if an <code>Edit</code> threw such an exception.
220          */
221         protected void executeEdits(List edits) throws BadLocationException {
222                 for (Iterator it= edits.iterator(); it.hasNext();) {
223                         Edit edit= (Edit) it.next();
224                         edit.perform();
225                 }
226         }
227
228         /**
229          * Ensures that the editor is modifyable. If the editor is an instance of
230          * <code>ITextEditorExtension2</code>, its <code>validateEditorInputState</code> method 
231          * is called, otherwise, the result of <code>isEditable</code> is returned.
232          * 
233          * @param editor the editor to be checked
234          * @return <code>true</code> if the editor is editable, <code>false</code> otherwise
235          */
236         protected boolean ensureEditable(ITextEditor editor) {
237                 Assert.isNotNull(editor);
238         
239                 if (editor instanceof ITextEditorExtension2) {
240                         ITextEditorExtension2 ext= (ITextEditorExtension2) editor;
241                         return ext.validateEditorInputState();
242                 }
243                 
244                 return editor.isEditable();
245         }
246
247         /*
248          * @see org.eclipse.ui.texteditor.IUpdate#update()
249          */
250         public void update() {
251                 super.update();
252                 
253                 if (isEnabled()) {
254                         if (!canModifyEditor() || !isValidSelection(getCurrentSelection()))
255                                 setEnabled(false);
256                 }
257         }
258
259         /**
260          * Returns the editor's selection, or <code>null</code> if no selection can be obtained or the 
261          * editor is <code>null</code>.
262          * 
263          * @return the selection of the action's editor, or <code>null</code>
264          */
265         protected ITextSelection getCurrentSelection() {
266                 ITextEditor editor= getTextEditor();
267                 if (editor != null) {
268                         ISelectionProvider provider= editor.getSelectionProvider();
269                         if (provider != null) {
270                                 ISelection selection= provider.getSelection();
271                                 if (selection instanceof ITextSelection) 
272                                         return (ITextSelection) selection;
273                         }
274                 }
275                 return null;
276         }
277
278         /**
279          * Runs the real command once all the editor, document, and selection checks have succeeded.
280          * 
281          * @param selection the current selection we are being called for
282          * @param docExtension the document extension where we get the partitioning from
283          * @param factory the edit factory we can use to create <code>Edit</code>s 
284          * @throws BadLocationException if an edition fails
285          * @throws BadPartitioningException if a partitioning call fails
286          */
287         protected abstract void runInternal(ITextSelection selection, IDocumentExtension3 docExtension, Edit.EditFactory factory) throws BadLocationException, BadPartitioningException;
288
289         /**
290          * Checks whether <code>selection</code> is valid.
291          * 
292          * @param selection the selection to check
293          * @return <code>true</code> if the selection is valid, <code>false</code> otherwise
294          */
295         protected abstract boolean isValidSelection(ITextSelection selection);
296
297         /**
298          * Returns the text to be inserted at the selection start.
299          * 
300          * @return the text to be inserted at the selection start
301          */
302         protected String getCommentStart() {
303                 // for now: no space story
304                 return "/*"; //$NON-NLS-1$
305         }
306
307         /**
308          * Returns the text to be inserted at the selection end.
309          * 
310          * @return the text to be inserted at the selection end
311          */
312         protected String getCommentEnd() {
313                 // for now: no space story
314                 return "*/"; //$NON-NLS-1$
315         }
316
317 }