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
 
   9  *     IBM Corporation - initial API and implementation
 
  10  *******************************************************************************/
 
  11 package net.sourceforge.phpdt.internal.ui.text.template.contentassist;
 
  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;
 
  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;
 
  58  * A template proposal.
 
  60 public class TemplateProposal implements IPHPCompletionProposal, ICompletionProposalExtension2, ICompletionProposalExtension3 {
 
  62         private final Template fTemplate;
 
  63         private final TemplateContext fContext;
 
  64         private final Image fImage;
 
  65         private IRegion fRegion;
 
  66         private int fRelevance;
 
  68         private IRegion fSelectedRegion; // initialized by apply()
 
  69         private String fDisplayString;
 
  72          * Creates a template proposal with a template and its context.
 
  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
 
  79         public TemplateProposal(Template template, TemplateContext context, IRegion region, Image image) {
 
  80                 Assert.isNotNull(template);
 
  81                 Assert.isNotNull(context);
 
  82                 Assert.isNotNull(region);
 
  91                 if (context instanceof JavaContext) {
 
  92                         switch (((JavaContext) context).getCharacterBeforeStart()) {
 
  93                         // high relevance after whitespace
 
 109          * @see ICompletionProposal#apply(IDocument)
 
 111         public final void apply(IDocument document) {
 
 112                 // not called anymore
 
 116          * @see org.eclipse.jface.text.contentassist.ICompletionProposalExtension2#apply(org.eclipse.jface.text.ITextViewer, char, int, int)
 
 118         public void apply(ITextViewer viewer, char trigger, int stateMask, int offset) {
 
 122                         fContext.setReadOnly(false);
 
 123                         TemplateBuffer templateBuffer;
 
 125                                 templateBuffer= fContext.evaluate(fTemplate);
 
 126                         } catch (TemplateException e1) {
 
 127                                 fSelectedRegion= fRegion;
 
 131                         int start= getReplaceOffset();
 
 132                         int end= getReplaceEndOffset();
 
 133                         end= Math.max(end, offset);
 
 135                         // insert template string
 
 136                         IDocument document= viewer.getDocument();
 
 137                         String templateString= templateBuffer.getString();      
 
 138                         document.replace(start, end - start, templateString);   
 
 140                         // translate positions
 
 141                         LinkedModeModel model= new LinkedModeModel();
 
 142                         TemplateVariable[] variables= templateBuffer.getVariables();
 
 144                         MultiVariableGuess guess= fContext instanceof CompilationUnitContext ? ((CompilationUnitContext) fContext).getMultiVariableGuess() : null;
 
 146                         boolean hasPositions= false;
 
 147                         for (int i= 0; i != variables.length; i++) {
 
 148                                 TemplateVariable variable= variables[i];
 
 150                                 if (variable.isUnambiguous())
 
 153                                 LinkedPositionGroup group= new LinkedPositionGroup();
 
 155                                 int[] offsets= variable.getOffsets();
 
 156                                 int length= variable.getLength();
 
 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);
 
 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);
 
 172                                         if (proposals.length > 1)
 
 173                                                 first= new ProposalPosition(document, offsets[0] + start, length, proposals);
 
 175                                                 first= new LinkedPosition(document, offsets[0] + start, length);
 
 178                                 for (int j= 0; j != offsets.length; j++)
 
 180                                                 group.addPosition(first);
 
 182                                                 group.addPosition(new LinkedPosition(document, offsets[j] + start, length));
 
 184                                 model.addGroup(group);
 
 189                                 model.forceInstall();
 
 190                                 PHPEditor editor= getJavaEditor();
 
 191                                 if (editor != null) {
 
 192                                         model.addLinkingListener(new EditorHighlightingSynchronizer(editor));
 
 195                                 LinkedModeUI ui= new EditorLinkedModeUI(model, viewer);
 
 196                                 ui.setExitPosition(viewer, getCaretOffset(templateBuffer) + start, 0, Integer.MAX_VALUE);
 
 199                                 fSelectedRegion= ui.getSelectedRegion();
 
 201                                 fSelectedRegion= new Region(getCaretOffset(templateBuffer) + start, 0);
 
 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;
 
 216          * Returns the currently active java editor, or <code>null</code> if it 
 
 217          * cannot be determined.
 
 219          * @return  the currently active java editor, or <code>null</code>
 
 221         private PHPEditor getJavaEditor() {
 
 222                 IEditorPart part= PHPeclipsePlugin.getActivePage().getActiveEditor();
 
 223                 if (part instanceof PHPEditor)
 
 224                         return (PHPEditor) part;
 
 230          * Returns the offset of the range in the document that will be replaced by
 
 231          * applying this template.
 
 233          * @return the offset of the range in the document that will be replaced by
 
 234          *         applying this template
 
 236         private int getReplaceOffset() {
 
 238                 if (fContext instanceof DocumentTemplateContext) {
 
 239                         DocumentTemplateContext docContext = (DocumentTemplateContext)fContext;
 
 240                         start= docContext.getStart();
 
 242                         start= fRegion.getOffset();
 
 248          * Returns the end offset of the range in the document that will be replaced
 
 249          * by applying this template.
 
 251          * @return the end offset of the range in the document that will be replaced
 
 252          *         by applying this template
 
 254         private int getReplaceEndOffset() {
 
 256                 if (fContext instanceof DocumentTemplateContext) {
 
 257                         DocumentTemplateContext docContext = (DocumentTemplateContext)fContext;
 
 258                         end= docContext.getEnd();
 
 260                         end= fRegion.getOffset() + fRegion.getLength();
 
 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);
 
 271                         model.addLinkingListener(new ILinkedModeListener() {
 
 274                                  * @see org.eclipse.jface.text.link.ILinkedModeListener#left(org.eclipse.jface.text.link.LinkedModeModel, int)
 
 276                                 public void left(LinkedModeModel environment, int flags) {
 
 278                                                 document.removePositionCategory(getCategory());
 
 279                                         } catch (BadPositionCategoryException e) {
 
 282                                         document.removePositionUpdater(updater);
 
 285                                 public void suspend(LinkedModeModel environment) {}
 
 286                                 public void resume(LinkedModeModel environment, int flags) {}
 
 291         private String getCategory() {
 
 292                 return "TemplateProposalCategory_" + toString(); //$NON-NLS-1$
 
 295         private int getCaretOffset(TemplateBuffer buffer) {
 
 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];
 
 304                 return buffer.getString().length();
 
 308          * @see ICompletionProposal#getSelection(IDocument)
 
 310         public Point getSelection(IDocument document) {
 
 311                 return new Point(fSelectedRegion.getOffset(), fSelectedRegion.getLength());
 
 315          * @see ICompletionProposal#getAdditionalProposalInfo()
 
 317         public String getAdditionalProposalInfo() {
 
 319                     fContext.setReadOnly(true);
 
 320                         TemplateBuffer templateBuffer;
 
 322                                 templateBuffer= fContext.evaluate(fTemplate);
 
 323                         } catch (TemplateException e1) {
 
 327                         return templateBuffer.getString();
 
 329             } catch (BadLocationException e) {
 
 330                         handleException(PHPeclipsePlugin.getActiveWorkbenchShell(), new CoreException(new Status(IStatus.ERROR, PHPeclipsePlugin.getPluginId(), IStatus.OK, "", e))); //$NON-NLS-1$
 
 336          * @see ICompletionProposal#getDisplayString()
 
 338         public String getDisplayString() {
 
 339                 if (fDisplayString == null) {
 
 340                         fDisplayString= fTemplate.getName() + TemplateContentAssistMessages.getString("TemplateProposal.delimiter") + fTemplate.getDescription(); //$NON-NLS-1$
 
 342                 return fDisplayString;
 
 345         public void setDisplayString(String displayString) {
 
 346                 fDisplayString= displayString;
 
 350          * @see ICompletionProposal#getImage()
 
 352         public Image getImage() {
 
 357          * @see ICompletionProposal#getContextInformation()
 
 359         public IContextInformation getContextInformation() {
 
 363         private void openErrorDialog(Shell shell, Exception e) {
 
 364                 MessageDialog.openError(shell, TemplateContentAssistMessages.getString("TemplateEvaluator.error.title"), e.getMessage()); //$NON-NLS-1$
 
 367         private void handleException(Shell shell, CoreException e) {
 
 368                 ExceptionHandler.handle(e, shell, TemplateContentAssistMessages.getString("TemplateEvaluator.error.title"), null); //$NON-NLS-1$
 
 372          * @see IJavaCompletionProposal#getRelevance()
 
 374         public int getRelevance() {
 
 378         public void setRelevance(int relevance) {
 
 379                 fRelevance= relevance;
 
 382         public Template getTemplate() {
 
 387          * @see org.eclipse.jface.text.contentassist.ICompletionProposalExtension3#getInformationControlCreator()
 
 389         public IInformationControlCreator getInformationControlCreator() {
 
 390                 return new TemplateInformationControlCreator();
 
 394          * @see org.eclipse.jface.text.contentassist.ICompletionProposalExtension2#selected(org.eclipse.jface.text.ITextViewer, boolean)
 
 396         public void selected(ITextViewer viewer, boolean smartToggle) {
 
 400          * @see org.eclipse.jface.text.contentassist.ICompletionProposalExtension2#unselected(org.eclipse.jface.text.ITextViewer)
 
 402         public void unselected(ITextViewer viewer) {
 
 406          * @see org.eclipse.jface.text.contentassist.ICompletionProposalExtension2#validate(org.eclipse.jface.text.IDocument, int, org.eclipse.jface.text.DocumentEvent)
 
 408         public boolean validate(IDocument document, int offset, DocumentEvent event) {
 
 410                         int replaceOffset= getReplaceOffset();
 
 411                         if (offset >= replaceOffset) {
 
 412                                 String content= document.get(replaceOffset, offset - replaceOffset);
 
 413                                 return fTemplate.getName().startsWith(content);
 
 415                 } catch (BadLocationException e) {
 
 416                         // concurrent modification - ignore
 
 422          * @see org.eclipse.jface.text.contentassist.ICompletionProposalExtension3#getReplacementString()
 
 424         public CharSequence getPrefixCompletionText(IDocument document, int completionOffset) {
 
 425                 return fTemplate.getName();
 
 429          * @see org.eclipse.jface.text.contentassist.ICompletionProposalExtension3#getReplacementOffset()
 
 431         public int getPrefixCompletionStart(IDocument document, int completionOffset) {
 
 432                 return getReplaceOffset();