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();