4ca7d170daeda716067e24336461e2be373ff888
[phpeclipse.git] /
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.text.template.contentassist;
12
13 import net.sourceforge.phpdt.internal.corext.template.php.CompilationUnitContext;
14 import net.sourceforge.phpdt.internal.corext.template.php.JavaContext;
15 import net.sourceforge.phpdt.internal.ui.text.java.IPHPCompletionProposal;
16 import net.sourceforge.phpdt.internal.ui.util.ExceptionHandler;
17 import net.sourceforge.phpeclipse.PHPeclipsePlugin;
18 import net.sourceforge.phpeclipse.phpeditor.EditorHighlightingSynchronizer;
19 import net.sourceforge.phpeclipse.phpeditor.PHPEditor;
20
21 import org.eclipse.core.runtime.CoreException;
22 import org.eclipse.core.runtime.IStatus;
23 import org.eclipse.core.runtime.Status;
24 import org.eclipse.jface.dialogs.MessageDialog;
25 import org.eclipse.jface.text.Assert;
26 import org.eclipse.jface.text.BadLocationException;
27 import org.eclipse.jface.text.BadPositionCategoryException;
28 import org.eclipse.jface.text.DocumentEvent;
29 import org.eclipse.jface.text.IDocument;
30 import org.eclipse.jface.text.IInformationControlCreator;
31 import org.eclipse.jface.text.IRegion;
32 import org.eclipse.jface.text.ITextViewer;
33 import org.eclipse.jface.text.Position;
34 import org.eclipse.jface.text.Region;
35 import org.eclipse.jface.text.contentassist.ICompletionProposal;
36 import org.eclipse.jface.text.contentassist.ICompletionProposalExtension2;
37 import org.eclipse.jface.text.contentassist.ICompletionProposalExtension3;
38 import org.eclipse.jface.text.contentassist.IContextInformation;
39 import org.eclipse.jface.text.link.ILinkedModeListener;
40 import org.eclipse.jface.text.link.LinkedModeModel;
41 import org.eclipse.jface.text.link.LinkedModeUI;
42 import org.eclipse.jface.text.link.LinkedPosition;
43 import org.eclipse.jface.text.link.LinkedPositionGroup;
44 import org.eclipse.jface.text.link.ProposalPosition;
45 import org.eclipse.jface.text.templates.DocumentTemplateContext;
46 import org.eclipse.jface.text.templates.GlobalTemplateVariables;
47 import org.eclipse.jface.text.templates.Template;
48 import org.eclipse.jface.text.templates.TemplateBuffer;
49 import org.eclipse.jface.text.templates.TemplateContext;
50 import org.eclipse.jface.text.templates.TemplateException;
51 import org.eclipse.jface.text.templates.TemplateVariable;
52 import org.eclipse.swt.graphics.Image;
53 import org.eclipse.swt.graphics.Point;
54 import org.eclipse.swt.widgets.Shell;
55 import org.eclipse.ui.IEditorPart;
56 import org.eclipse.ui.texteditor.link.EditorLinkedModeUI;
57
58 /**
59  * A template proposal.
60  */
61 public class TemplateProposal implements IPHPCompletionProposal, ICompletionProposalExtension2, ICompletionProposalExtension3 {
62
63         private final Template fTemplate;
64         private final TemplateContext fContext;
65         private final Image fImage;
66         private IRegion fRegion;
67         private int fRelevance;
68
69         private IRegion fSelectedRegion; // initialized by apply()
70         private String fDisplayString;
71                 
72         /**
73          * Creates a template proposal with a template and its context.
74          * 
75          * @param template  the template
76          * @param context   the context in which the template was requested
77          * @param region        the region this proposal applies to
78          * @param image     the icon of the proposal
79          */     
80         public TemplateProposal(Template template, TemplateContext context, IRegion region, Image image) {
81                 Assert.isNotNull(template);
82                 Assert.isNotNull(context);
83                 Assert.isNotNull(region);
84                 
85                 fTemplate= template;
86                 fContext= context;
87                 fImage= image;
88                 fRegion= region;
89                 
90                 fDisplayString= null;
91                 
92                 if (context instanceof JavaContext) {
93                         switch (((JavaContext) context).getCharacterBeforeStart()) {
94                         // high relevance after whitespace
95                         case ' ':
96                         case '\r':
97                         case '\n':
98                         case '\t':
99                                 fRelevance= 90;
100                                 break;
101                         default:
102                                 fRelevance= 0;
103                         }
104                 } else {
105                         fRelevance= 90;                 
106                 }               
107         }
108
109         /*
110          * @see ICompletionProposal#apply(IDocument)
111          */
112         public final void apply(IDocument document) {
113                 // not called anymore
114         }
115         
116         /*
117          * @see org.eclipse.jface.text.contentassist.ICompletionProposalExtension2#apply(org.eclipse.jface.text.ITextViewer, char, int, int)
118          */
119         public void apply(ITextViewer viewer, char trigger, int stateMask, int offset) {
120
121                 try {
122                         
123                         fContext.setReadOnly(false);
124                         TemplateBuffer templateBuffer;
125                         try {
126                                 templateBuffer= fContext.evaluate(fTemplate);
127                         } catch (TemplateException e1) {
128                                 fSelectedRegion= fRegion;
129                                 return;
130                         }
131                         
132                         int start= getReplaceOffset();
133                         int end= getReplaceEndOffset();
134                         end= Math.max(end, offset);
135                         
136                         // insert template string
137                         IDocument document= viewer.getDocument();
138                         String templateString= templateBuffer.getString();      
139                         document.replace(start, end - start, templateString);   
140                         
141                         // translate positions
142                         LinkedModeModel model= new LinkedModeModel();
143                         TemplateVariable[] variables= templateBuffer.getVariables();
144                         
145                         MultiVariableGuess guess= fContext instanceof CompilationUnitContext ? ((CompilationUnitContext) fContext).getMultiVariableGuess() : null;
146                 
147                         boolean hasPositions= false;
148                         for (int i= 0; i != variables.length; i++) {
149                                 TemplateVariable variable= variables[i];
150
151                                 if (variable.isUnambiguous())
152                                         continue;
153                                 
154                                 LinkedPositionGroup group= new LinkedPositionGroup();
155                                 
156                                 int[] offsets= variable.getOffsets();
157                                 int length= variable.getLength();
158                                 
159                                 LinkedPosition first;
160                                 if (guess != null && variable instanceof MultiVariable) {
161                                         first= new VariablePosition(document, offsets[0] + start, length, guess, (MultiVariable) variable);
162                                         guess.addSlave((VariablePosition) first);
163                                 } else {
164                                         String[] values= variable.getValues();
165                                         ICompletionProposal[] proposals= new ICompletionProposal[values.length];
166                                         for (int j= 0; j < values.length; j++) {
167                                                 ensurePositionCategoryInstalled(document, model);
168                                                 Position pos= new Position(offsets[0] + start, length);
169                                                 document.addPosition(getCategory(), pos);
170                                                 proposals[j]= new PositionBasedCompletionProposal(values[j], pos, length);
171                                         }
172                                         
173                                         if (proposals.length > 1)
174                                                 first= new ProposalPosition(document, offsets[0] + start, length, proposals);
175                                         else
176                                                 first= new LinkedPosition(document, offsets[0] + start, length);
177                                 }
178                                 
179                                 for (int j= 0; j != offsets.length; j++)
180                                         if (j == 0)
181                                                 group.addPosition(first);
182                                         else
183                                                 group.addPosition(new LinkedPosition(document, offsets[j] + start, length));
184                                 
185                                 model.addGroup(group);
186                                 hasPositions= true;
187                         }
188                         
189                         if (hasPositions) {
190                                 model.forceInstall();
191                                 PHPEditor editor= getJavaEditor();
192                                 if (editor != null) {
193                                         model.addLinkingListener(new EditorHighlightingSynchronizer(editor));
194                                 }
195                                 
196                                 LinkedModeUI ui= new EditorLinkedModeUI(model, viewer);
197                                 ui.setExitPosition(viewer, getCaretOffset(templateBuffer) + start, 0, Integer.MAX_VALUE);
198                                 ui.enter();
199                                 
200                                 fSelectedRegion= ui.getSelectedRegion();
201                         } else
202                                 fSelectedRegion= new Region(getCaretOffset(templateBuffer) + start, 0);
203                         
204                 } catch (BadLocationException e) {
205                   PHPeclipsePlugin.log(e);
206                         openErrorDialog(viewer.getTextWidget().getShell(), e);              
207                         fSelectedRegion= fRegion;
208                 } catch (BadPositionCategoryException e) {
209                   PHPeclipsePlugin.log(e);
210                         openErrorDialog(viewer.getTextWidget().getShell(), e);              
211                         fSelectedRegion= fRegion;
212                 }
213
214         }       
215         
216         /**
217          * Returns the currently active java editor, or <code>null</code> if it 
218          * cannot be determined.
219          * 
220          * @return  the currently active java editor, or <code>null</code>
221          */
222         private PHPEditor getJavaEditor() {
223                 IEditorPart part= PHPeclipsePlugin.getActivePage().getActiveEditor();
224                 if (part instanceof PHPEditor)
225                         return (PHPEditor) part;
226                 else
227                         return null;
228         }
229
230         /**
231          * Returns the offset of the range in the document that will be replaced by
232          * applying this template.
233          * 
234          * @return the offset of the range in the document that will be replaced by
235          *         applying this template
236          */
237         private int getReplaceOffset() {
238                 int start;
239                 if (fContext instanceof DocumentTemplateContext) {
240                         DocumentTemplateContext docContext = (DocumentTemplateContext)fContext;
241                         start= docContext.getStart();
242                 } else {
243                         start= fRegion.getOffset();
244                 }
245                 return start;
246         }
247
248         /**
249          * Returns the end offset of the range in the document that will be replaced
250          * by applying this template.
251          * 
252          * @return the end offset of the range in the document that will be replaced
253          *         by applying this template
254          */
255         private int getReplaceEndOffset() {
256                 int end;
257                 if (fContext instanceof DocumentTemplateContext) {
258                         DocumentTemplateContext docContext = (DocumentTemplateContext)fContext;
259                         end= docContext.getEnd();
260                 } else {
261                         end= fRegion.getOffset() + fRegion.getLength();
262                 }
263                 return end;
264         }
265
266         private void ensurePositionCategoryInstalled(final IDocument document, LinkedModeModel model) {
267                 if (!document.containsPositionCategory(getCategory())) {
268                         document.addPositionCategory(getCategory());
269                         final InclusivePositionUpdater updater= new InclusivePositionUpdater(getCategory());
270                         document.addPositionUpdater(updater);
271                         
272                         model.addLinkingListener(new ILinkedModeListener() {
273
274                                 /*
275                                  * @see org.eclipse.jface.text.link.ILinkedModeListener#left(org.eclipse.jface.text.link.LinkedModeModel, int)
276                                  */
277                                 public void left(LinkedModeModel environment, int flags) {
278                                         try {
279                                                 document.removePositionCategory(getCategory());
280                                         } catch (BadPositionCategoryException e) {
281                                                 // ignore
282                                         }
283                                         document.removePositionUpdater(updater);
284                                 }
285
286                                 public void suspend(LinkedModeModel environment) {}
287                                 public void resume(LinkedModeModel environment, int flags) {}
288                         });
289                 }
290         }
291
292         private String getCategory() {
293                 return "TemplateProposalCategory_" + toString(); //$NON-NLS-1$
294         }
295
296         private int getCaretOffset(TemplateBuffer buffer) {
297         
298             TemplateVariable[] variables= buffer.getVariables();
299                 for (int i= 0; i != variables.length; i++) {
300                         TemplateVariable variable= variables[i];
301                         if (variable.getType().equals(GlobalTemplateVariables.Cursor.NAME))
302                                 return variable.getOffsets()[0];
303                 }
304
305                 return buffer.getString().length();
306         }
307         
308         /*
309          * @see ICompletionProposal#getSelection(IDocument)
310          */
311         public Point getSelection(IDocument document) {
312                 return new Point(fSelectedRegion.getOffset(), fSelectedRegion.getLength());
313         }
314
315         /*
316          * @see ICompletionProposal#getAdditionalProposalInfo()
317          */
318         public String getAdditionalProposalInfo() {
319             try {
320                     fContext.setReadOnly(true);
321                         TemplateBuffer templateBuffer;
322                         try {
323                                 templateBuffer= fContext.evaluate(fTemplate);
324                         } catch (TemplateException e1) {
325                                 return null;
326                         }
327
328                         return templateBuffer.getString();
329
330             } catch (BadLocationException e) {
331                         handleException(PHPeclipsePlugin.getActiveWorkbenchShell(), new CoreException(new Status(IStatus.ERROR, PHPeclipsePlugin.getPluginId(), IStatus.OK, "", e))); //$NON-NLS-1$
332                         return null;
333                 }
334         }
335
336         /*
337          * @see ICompletionProposal#getDisplayString()
338          */
339         public String getDisplayString() {
340                 if (fDisplayString == null) {
341                         fDisplayString= fTemplate.getName() + TemplateContentAssistMessages.getString("TemplateProposal.delimiter") + fTemplate.getDescription(); //$NON-NLS-1$
342                 }
343                 return fDisplayString;
344         }
345         
346         public void setDisplayString(String displayString) {
347                 fDisplayString= displayString;
348         }       
349
350         /*
351          * @see ICompletionProposal#getImage()
352          */
353         public Image getImage() {
354                 return fImage;
355         }
356
357         /*
358          * @see ICompletionProposal#getContextInformation()
359          */
360         public IContextInformation getContextInformation() {
361                 return null;
362         }
363
364         private void openErrorDialog(Shell shell, Exception e) {
365                 MessageDialog.openError(shell, TemplateContentAssistMessages.getString("TemplateEvaluator.error.title"), e.getMessage()); //$NON-NLS-1$
366         }
367
368         private void handleException(Shell shell, CoreException e) {
369                 ExceptionHandler.handle(e, shell, TemplateContentAssistMessages.getString("TemplateEvaluator.error.title"), null); //$NON-NLS-1$
370         }
371
372         /*
373          * @see IJavaCompletionProposal#getRelevance()
374          */
375         public int getRelevance() {
376                 return fRelevance;
377         }
378
379         public void setRelevance(int relevance) {
380                 fRelevance= relevance;
381         }
382         
383         public Template getTemplate() {
384                 return fTemplate;
385         }
386
387         /*
388          * @see org.eclipse.jface.text.contentassist.ICompletionProposalExtension3#getInformationControlCreator()
389          */
390         public IInformationControlCreator getInformationControlCreator() {
391                 return new TemplateInformationControlCreator();
392         }
393
394         /*
395          * @see org.eclipse.jface.text.contentassist.ICompletionProposalExtension2#selected(org.eclipse.jface.text.ITextViewer, boolean)
396          */
397         public void selected(ITextViewer viewer, boolean smartToggle) {
398         }
399
400         /*
401          * @see org.eclipse.jface.text.contentassist.ICompletionProposalExtension2#unselected(org.eclipse.jface.text.ITextViewer)
402          */
403         public void unselected(ITextViewer viewer) {
404         }
405
406         /*
407          * @see org.eclipse.jface.text.contentassist.ICompletionProposalExtension2#validate(org.eclipse.jface.text.IDocument, int, org.eclipse.jface.text.DocumentEvent)
408          */
409         public boolean validate(IDocument document, int offset, DocumentEvent event) {
410                 try {
411                         int replaceOffset= getReplaceOffset();
412                         if (offset >= replaceOffset) {
413                                 String content= document.get(replaceOffset, offset - replaceOffset);
414                                 return fTemplate.getName().startsWith(content);
415                         }
416                 } catch (BadLocationException e) {
417                         // concurrent modification - ignore
418                 }
419                 return false;
420         }
421
422         /*
423          * @see org.eclipse.jface.text.contentassist.ICompletionProposalExtension3#getReplacementString()
424          */
425         public CharSequence getPrefixCompletionText(IDocument document, int completionOffset) {
426                 return fTemplate.getName();
427         }
428
429         /*
430          * @see org.eclipse.jface.text.contentassist.ICompletionProposalExtension3#getReplacementOffset()
431          */
432         public int getPrefixCompletionStart(IDocument document, int completionOffset) {
433                 return getReplaceOffset();
434         }
435 }