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