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