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