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;
59 * A template proposal.
61 public class TemplateProposal implements IPHPCompletionProposal, ICompletionProposalExtension2, ICompletionProposalExtension3 {
63 private final Template fTemplate;
64 private final TemplateContext fContext;
65 private final Image fImage;
66 private IRegion fRegion;
67 private int fRelevance;
69 private IRegion fSelectedRegion; // initialized by apply()
70 private String fDisplayString;
73 * Creates a template proposal with a template and its context.
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
80 public TemplateProposal(Template template, TemplateContext context, IRegion region, Image image) {
81 Assert.isNotNull(template);
82 Assert.isNotNull(context);
83 Assert.isNotNull(region);
92 if (context instanceof JavaContext) {
93 switch (((JavaContext) context).getCharacterBeforeStart()) {
94 // high relevance after whitespace
110 * @see ICompletionProposal#apply(IDocument)
112 public final void apply(IDocument document) {
113 // not called anymore
117 * @see org.eclipse.jface.text.contentassist.ICompletionProposalExtension2#apply(org.eclipse.jface.text.ITextViewer, char, int, int)
119 public void apply(ITextViewer viewer, char trigger, int stateMask, int offset) {
123 fContext.setReadOnly(false);
124 TemplateBuffer templateBuffer;
126 templateBuffer= fContext.evaluate(fTemplate);
127 } catch (TemplateException e1) {
128 fSelectedRegion= fRegion;
132 int start= getReplaceOffset();
133 int end= getReplaceEndOffset();
134 end= Math.max(end, offset);
136 // insert template string
137 IDocument document= viewer.getDocument();
138 String templateString= templateBuffer.getString();
139 document.replace(start, end - start, templateString);
141 // translate positions
142 LinkedModeModel model= new LinkedModeModel();
143 TemplateVariable[] variables= templateBuffer.getVariables();
145 MultiVariableGuess guess= fContext instanceof CompilationUnitContext ? ((CompilationUnitContext) fContext).getMultiVariableGuess() : null;
147 boolean hasPositions= false;
148 for (int i= 0; i != variables.length; i++) {
149 TemplateVariable variable= variables[i];
151 if (variable.isUnambiguous())
154 LinkedPositionGroup group= new LinkedPositionGroup();
156 int[] offsets= variable.getOffsets();
157 int length= variable.getLength();
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);
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);
173 if (proposals.length > 1)
174 first= new ProposalPosition(document, offsets[0] + start, length, proposals);
176 first= new LinkedPosition(document, offsets[0] + start, length);
179 for (int j= 0; j != offsets.length; j++)
181 group.addPosition(first);
183 group.addPosition(new LinkedPosition(document, offsets[j] + start, length));
185 model.addGroup(group);
190 model.forceInstall();
191 PHPEditor editor= getJavaEditor();
192 if (editor != null) {
193 model.addLinkingListener(new EditorHighlightingSynchronizer(editor));
196 LinkedModeUI ui= new EditorLinkedModeUI(model, viewer);
197 ui.setExitPosition(viewer, getCaretOffset(templateBuffer) + start, 0, Integer.MAX_VALUE);
200 fSelectedRegion= ui.getSelectedRegion();
202 fSelectedRegion= new Region(getCaretOffset(templateBuffer) + start, 0);
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;
217 * Returns the currently active java editor, or <code>null</code> if it
218 * cannot be determined.
220 * @return the currently active java editor, or <code>null</code>
222 private PHPEditor getJavaEditor() {
223 IEditorPart part= PHPeclipsePlugin.getActivePage().getActiveEditor();
224 if (part instanceof PHPEditor)
225 return (PHPEditor) part;
231 * Returns the offset of the range in the document that will be replaced by
232 * applying this template.
234 * @return the offset of the range in the document that will be replaced by
235 * applying this template
237 private int getReplaceOffset() {
239 if (fContext instanceof DocumentTemplateContext) {
240 DocumentTemplateContext docContext = (DocumentTemplateContext)fContext;
241 start= docContext.getStart();
243 start= fRegion.getOffset();
249 * Returns the end offset of the range in the document that will be replaced
250 * by applying this template.
252 * @return the end offset of the range in the document that will be replaced
253 * by applying this template
255 private int getReplaceEndOffset() {
257 if (fContext instanceof DocumentTemplateContext) {
258 DocumentTemplateContext docContext = (DocumentTemplateContext)fContext;
259 end= docContext.getEnd();
261 end= fRegion.getOffset() + fRegion.getLength();
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);
272 model.addLinkingListener(new ILinkedModeListener() {
275 * @see org.eclipse.jface.text.link.ILinkedModeListener#left(org.eclipse.jface.text.link.LinkedModeModel, int)
277 public void left(LinkedModeModel environment, int flags) {
279 document.removePositionCategory(getCategory());
280 } catch (BadPositionCategoryException e) {
283 document.removePositionUpdater(updater);
286 public void suspend(LinkedModeModel environment) {}
287 public void resume(LinkedModeModel environment, int flags) {}
292 private String getCategory() {
293 return "TemplateProposalCategory_" + toString(); //$NON-NLS-1$
296 private int getCaretOffset(TemplateBuffer buffer) {
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];
305 return buffer.getString().length();
309 * @see ICompletionProposal#getSelection(IDocument)
311 public Point getSelection(IDocument document) {
312 return new Point(fSelectedRegion.getOffset(), fSelectedRegion.getLength());
316 * @see ICompletionProposal#getAdditionalProposalInfo()
318 public String getAdditionalProposalInfo() {
320 fContext.setReadOnly(true);
321 TemplateBuffer templateBuffer;
323 templateBuffer= fContext.evaluate(fTemplate);
324 } catch (TemplateException e1) {
328 return templateBuffer.getString();
330 } catch (BadLocationException e) {
331 handleException(PHPeclipsePlugin.getActiveWorkbenchShell(), new CoreException(new Status(IStatus.ERROR, PHPeclipsePlugin.getPluginId(), IStatus.OK, "", e))); //$NON-NLS-1$
337 * @see ICompletionProposal#getDisplayString()
339 public String getDisplayString() {
340 if (fDisplayString == null) {
341 fDisplayString= fTemplate.getName() + TemplateContentAssistMessages.getString("TemplateProposal.delimiter") + fTemplate.getDescription(); //$NON-NLS-1$
343 return fDisplayString;
346 public void setDisplayString(String displayString) {
347 fDisplayString= displayString;
351 * @see ICompletionProposal#getImage()
353 public Image getImage() {
358 * @see ICompletionProposal#getContextInformation()
360 public IContextInformation getContextInformation() {
364 private void openErrorDialog(Shell shell, Exception e) {
365 MessageDialog.openError(shell, TemplateContentAssistMessages.getString("TemplateEvaluator.error.title"), e.getMessage()); //$NON-NLS-1$
368 private void handleException(Shell shell, CoreException e) {
369 ExceptionHandler.handle(e, shell, TemplateContentAssistMessages.getString("TemplateEvaluator.error.title"), null); //$NON-NLS-1$
373 * @see IJavaCompletionProposal#getRelevance()
375 public int getRelevance() {
379 public void setRelevance(int relevance) {
380 fRelevance= relevance;
383 public Template getTemplate() {
388 * @see org.eclipse.jface.text.contentassist.ICompletionProposalExtension3#getInformationControlCreator()
390 public IInformationControlCreator getInformationControlCreator() {
391 return new TemplateInformationControlCreator();
395 * @see org.eclipse.jface.text.contentassist.ICompletionProposalExtension2#selected(org.eclipse.jface.text.ITextViewer, boolean)
397 public void selected(ITextViewer viewer, boolean smartToggle) {
401 * @see org.eclipse.jface.text.contentassist.ICompletionProposalExtension2#unselected(org.eclipse.jface.text.ITextViewer)
403 public void unselected(ITextViewer viewer) {
407 * @see org.eclipse.jface.text.contentassist.ICompletionProposalExtension2#validate(org.eclipse.jface.text.IDocument, int, org.eclipse.jface.text.DocumentEvent)
409 public boolean validate(IDocument document, int offset, DocumentEvent event) {
411 int replaceOffset= getReplaceOffset();
412 if (offset >= replaceOffset) {
413 String content= document.get(replaceOffset, offset - replaceOffset);
414 return fTemplate.getName().startsWith(content);
416 } catch (BadLocationException e) {
417 // concurrent modification - ignore
423 * @see org.eclipse.jface.text.contentassist.ICompletionProposalExtension3#getReplacementString()
425 public CharSequence getPrefixCompletionText(IDocument document, int completionOffset) {
426 return fTemplate.getName();
430 * @see org.eclipse.jface.text.contentassist.ICompletionProposalExtension3#getReplacementOffset()
432 public int getPrefixCompletionStart(IDocument document, int completionOffset) {
433 return getReplaceOffset();