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