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;
26 //import org.eclipse.jface.text.Assert;
27 import org.eclipse.core.runtime.Assert;
28 import org.eclipse.jface.text.BadLocationException;
29 import org.eclipse.jface.text.BadPositionCategoryException;
30 import org.eclipse.jface.text.DocumentEvent;
31 import org.eclipse.jface.text.IDocument;
32 import org.eclipse.jface.text.IInformationControlCreator;
33 import org.eclipse.jface.text.IRegion;
34 import org.eclipse.jface.text.ITextViewer;
35 import org.eclipse.jface.text.Position;
36 import org.eclipse.jface.text.Region;
37 import org.eclipse.jface.text.contentassist.ICompletionProposal;
38 import org.eclipse.jface.text.contentassist.ICompletionProposalExtension2;
39 import org.eclipse.jface.text.contentassist.ICompletionProposalExtension3;
40 import org.eclipse.jface.text.contentassist.IContextInformation;
41 import org.eclipse.jface.text.link.ILinkedModeListener;
42 import org.eclipse.jface.text.link.LinkedModeModel;
43 import org.eclipse.jface.text.link.LinkedModeUI;
44 import org.eclipse.jface.text.link.LinkedPosition;
45 import org.eclipse.jface.text.link.LinkedPositionGroup;
46 import org.eclipse.jface.text.link.ProposalPosition;
47 import org.eclipse.jface.text.templates.DocumentTemplateContext;
48 import org.eclipse.jface.text.templates.GlobalTemplateVariables;
49 import org.eclipse.jface.text.templates.Template;
50 import org.eclipse.jface.text.templates.TemplateBuffer;
51 import org.eclipse.jface.text.templates.TemplateContext;
52 import org.eclipse.jface.text.templates.TemplateException;
53 import org.eclipse.jface.text.templates.TemplateVariable;
54 import org.eclipse.swt.graphics.Image;
55 import org.eclipse.swt.graphics.Point;
56 import org.eclipse.swt.widgets.Shell;
57 import org.eclipse.ui.IEditorPart;
58 import org.eclipse.ui.texteditor.link.EditorLinkedModeUI;
61 * A template proposal.
63 public class TemplateProposal implements IPHPCompletionProposal,
64 ICompletionProposalExtension2, ICompletionProposalExtension3 {
66 private final Template fTemplate;
68 private final TemplateContext fContext;
70 private final Image fImage;
72 private IRegion fRegion;
74 private int fRelevance;
76 private IRegion fSelectedRegion; // initialized by apply()
78 private String fDisplayString;
81 * Creates a template proposal with a template and its context.
86 * the context in which the template was requested
88 * the region this proposal applies to
90 * the icon of the proposal
92 public TemplateProposal(Template template, TemplateContext context,
93 IRegion region, Image image) {
94 Assert.isNotNull(template);
95 Assert.isNotNull(context);
96 Assert.isNotNull(region);
103 fDisplayString = null;
105 if (context instanceof JavaContext) {
106 switch (((JavaContext) context).getCharacterBeforeStart()) {
107 // high relevance after whitespace
123 * @see ICompletionProposal#apply(IDocument)
125 public final void apply(IDocument document) {
126 // not called anymore
130 * @see org.eclipse.jface.text.contentassist.ICompletionProposalExtension2#apply(org.eclipse.jface.text.ITextViewer,
133 public void apply(ITextViewer viewer, char trigger, int stateMask,
138 fContext.setReadOnly(false);
139 TemplateBuffer templateBuffer;
141 templateBuffer = fContext.evaluate(fTemplate);
142 } catch (TemplateException e1) {
143 fSelectedRegion = fRegion;
147 int start = getReplaceOffset();
148 int end = getReplaceEndOffset();
149 end = Math.max(end, offset);
151 // insert template string
152 IDocument document = viewer.getDocument();
153 String templateString = templateBuffer.getString();
154 document.replace(start, end - start, templateString);
156 // translate positions
157 LinkedModeModel model = new LinkedModeModel();
158 TemplateVariable[] variables = templateBuffer.getVariables();
160 MultiVariableGuess guess = fContext instanceof CompilationUnitContext ? ((CompilationUnitContext) fContext)
161 .getMultiVariableGuess()
164 boolean hasPositions = false;
165 for (int i = 0; i != variables.length; i++) {
166 TemplateVariable variable = variables[i];
168 if (variable.isUnambiguous())
171 LinkedPositionGroup group = new LinkedPositionGroup();
173 int[] offsets = variable.getOffsets();
174 int length = variable.getLength();
176 LinkedPosition first;
177 if (guess != null && variable instanceof MultiVariable) {
178 first = new VariablePosition(document, offsets[0] + start,
179 length, guess, (MultiVariable) variable);
180 guess.addSlave((VariablePosition) first);
182 String[] values = variable.getValues();
183 ICompletionProposal[] proposals = new ICompletionProposal[values.length];
184 for (int j = 0; j < values.length; j++) {
185 ensurePositionCategoryInstalled(document, model);
186 Position pos = new Position(offsets[0] + start, length);
187 document.addPosition(getCategory(), pos);
188 proposals[j] = new PositionBasedCompletionProposal(
189 values[j], pos, length);
192 if (proposals.length > 1)
193 first = new ProposalPosition(document, offsets[0]
194 + start, length, proposals);
196 first = new LinkedPosition(document,
197 offsets[0] + start, length);
200 for (int j = 0; j != offsets.length; j++)
202 group.addPosition(first);
204 group.addPosition(new LinkedPosition(document,
205 offsets[j] + start, length));
207 model.addGroup(group);
212 model.forceInstall();
213 PHPEditor editor = getJavaEditor();
214 if (editor != null) {
216 .addLinkingListener(new EditorHighlightingSynchronizer(
220 LinkedModeUI ui = new EditorLinkedModeUI(model, viewer);
221 ui.setExitPosition(viewer, getCaretOffset(templateBuffer)
222 + start, 0, Integer.MAX_VALUE);
225 fSelectedRegion = ui.getSelectedRegion();
227 fSelectedRegion = new Region(getCaretOffset(templateBuffer)
230 } catch (BadLocationException e) {
231 PHPeclipsePlugin.log(e);
232 openErrorDialog(viewer.getTextWidget().getShell(), e);
233 fSelectedRegion = fRegion;
234 } catch (BadPositionCategoryException e) {
235 PHPeclipsePlugin.log(e);
236 openErrorDialog(viewer.getTextWidget().getShell(), e);
237 fSelectedRegion = fRegion;
243 * Returns the currently active java editor, or <code>null</code> if it
244 * cannot be determined.
246 * @return the currently active java editor, or <code>null</code>
248 private PHPEditor getJavaEditor() {
249 IEditorPart part = PHPeclipsePlugin.getActivePage().getActiveEditor();
250 if (part instanceof PHPEditor)
251 return (PHPEditor) part;
257 * Returns the offset of the range in the document that will be replaced by
258 * applying this template.
260 * @return the offset of the range in the document that will be replaced by
261 * applying this template
263 private int getReplaceOffset() {
265 if (fContext instanceof DocumentTemplateContext) {
266 DocumentTemplateContext docContext = (DocumentTemplateContext) fContext;
267 start = docContext.getStart();
269 start = fRegion.getOffset();
275 * Returns the end offset of the range in the document that will be replaced
276 * by applying this template.
278 * @return the end offset of the range in the document that will be replaced
279 * by applying this template
281 private int getReplaceEndOffset() {
283 if (fContext instanceof DocumentTemplateContext) {
284 DocumentTemplateContext docContext = (DocumentTemplateContext) fContext;
285 end = docContext.getEnd();
287 end = fRegion.getOffset() + fRegion.getLength();
292 private void ensurePositionCategoryInstalled(final IDocument document,
293 LinkedModeModel model) {
294 if (!document.containsPositionCategory(getCategory())) {
295 document.addPositionCategory(getCategory());
296 final InclusivePositionUpdater updater = new InclusivePositionUpdater(
298 document.addPositionUpdater(updater);
300 model.addLinkingListener(new ILinkedModeListener() {
303 * @see org.eclipse.jface.text.link.ILinkedModeListener#left(org.eclipse.jface.text.link.LinkedModeModel,
306 public void left(LinkedModeModel environment, int flags) {
308 document.removePositionCategory(getCategory());
309 } catch (BadPositionCategoryException e) {
312 document.removePositionUpdater(updater);
315 public void suspend(LinkedModeModel environment) {
318 public void resume(LinkedModeModel environment, int flags) {
324 private String getCategory() {
325 return "TemplateProposalCategory_" + toString(); //$NON-NLS-1$
328 private int getCaretOffset(TemplateBuffer buffer) {
330 TemplateVariable[] variables = buffer.getVariables();
331 for (int i = 0; i != variables.length; i++) {
332 TemplateVariable variable = variables[i];
333 if (variable.getType().equals(GlobalTemplateVariables.Cursor.NAME))
334 return variable.getOffsets()[0];
337 return buffer.getString().length();
341 * @see ICompletionProposal#getSelection(IDocument)
343 public Point getSelection(IDocument document) {
344 return new Point(fSelectedRegion.getOffset(), fSelectedRegion
349 * @see ICompletionProposal#getAdditionalProposalInfo()
351 public String getAdditionalProposalInfo() {
353 fContext.setReadOnly(true);
354 TemplateBuffer templateBuffer;
356 templateBuffer = fContext.evaluate(fTemplate);
357 } catch (TemplateException e1) {
361 return templateBuffer.getString();
363 } catch (BadLocationException e) {
364 handleException(PHPeclipsePlugin.getActiveWorkbenchShell(),
365 new CoreException(new Status(IStatus.ERROR,
366 PHPeclipsePlugin.getPluginId(), IStatus.OK, "", e))); //$NON-NLS-1$
372 * @see ICompletionProposal#getDisplayString()
374 public String getDisplayString() {
375 if (fDisplayString == null) {
376 fDisplayString = fTemplate.getName()
377 + TemplateContentAssistMessages
378 .getString("TemplateProposal.delimiter") + fTemplate.getDescription(); //$NON-NLS-1$
380 return fDisplayString;
383 // public void setDisplayString(String displayString) {
384 // fDisplayString = displayString;
388 * @see ICompletionProposal#getImage()
390 public Image getImage() {
395 * @see ICompletionProposal#getContextInformation()
397 public IContextInformation getContextInformation() {
401 private void openErrorDialog(Shell shell, Exception e) {
402 MessageDialog.openError(shell, TemplateContentAssistMessages
403 .getString("TemplateEvaluator.error.title"), e.getMessage()); //$NON-NLS-1$
406 private void handleException(Shell shell, CoreException e) {
407 ExceptionHandler.handle(e, shell, TemplateContentAssistMessages
408 .getString("TemplateEvaluator.error.title"), null); //$NON-NLS-1$
412 * @see IJavaCompletionProposal#getRelevance()
414 public int getRelevance() {
418 // public void setRelevance(int relevance) {
419 // fRelevance = relevance;
422 // public Template getTemplate() {
427 * @see org.eclipse.jface.text.contentassist.ICompletionProposalExtension3#getInformationControlCreator()
429 public IInformationControlCreator getInformationControlCreator() {
430 return new TemplateInformationControlCreator();
434 * @see org.eclipse.jface.text.contentassist.ICompletionProposalExtension2#selected(org.eclipse.jface.text.ITextViewer,
437 public void selected(ITextViewer viewer, boolean smartToggle) {
441 * @see org.eclipse.jface.text.contentassist.ICompletionProposalExtension2#unselected(org.eclipse.jface.text.ITextViewer)
443 public void unselected(ITextViewer viewer) {
447 * @see org.eclipse.jface.text.contentassist.ICompletionProposalExtension2#validate(org.eclipse.jface.text.IDocument,
448 * int, org.eclipse.jface.text.DocumentEvent)
450 public boolean validate(IDocument document, int offset, DocumentEvent event) {
452 int replaceOffset = getReplaceOffset();
453 if (offset >= replaceOffset) {
454 String content = document.get(replaceOffset, offset
456 return fTemplate.getName().startsWith(content);
458 } catch (BadLocationException e) {
459 // concurrent modification - ignore
465 * @see org.eclipse.jface.text.contentassist.ICompletionProposalExtension3#getReplacementString()
467 public CharSequence getPrefixCompletionText(IDocument document,
468 int completionOffset) {
469 return fTemplate.getName();
473 * @see org.eclipse.jface.text.contentassist.ICompletionProposalExtension3#getReplacementOffset()
475 public int getPrefixCompletionStart(IDocument document, int completionOffset) {
476 return getReplaceOffset();