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;
20 import net.sourceforge.phpeclipse.ui.WebUI;
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;
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;
62 * A template proposal.
64 public class TemplateProposal implements IPHPCompletionProposal,
65 ICompletionProposalExtension2, ICompletionProposalExtension3 {
67 private final Template fTemplate;
69 private final TemplateContext fContext;
71 private final Image fImage;
73 private IRegion fRegion;
75 private int fRelevance;
77 private IRegion fSelectedRegion; // initialized by apply()
79 private String fDisplayString;
82 * Creates a template proposal with a template and its context.
87 * the context in which the template was requested
89 * the region this proposal applies to
91 * the icon of the proposal
93 public TemplateProposal(Template template, TemplateContext context,
94 IRegion region, Image image) {
95 Assert.isNotNull(template);
96 Assert.isNotNull(context);
97 Assert.isNotNull(region);
104 fDisplayString = null;
106 if (context instanceof JavaContext) {
107 switch (((JavaContext) context).getCharacterBeforeStart()) {
108 // high relevance after whitespace
124 * @see ICompletionProposal#apply(IDocument)
126 public final void apply(IDocument document) {
127 // not called anymore
131 * @see org.eclipse.jface.text.contentassist.ICompletionProposalExtension2#apply(org.eclipse.jface.text.ITextViewer,
134 public void apply(ITextViewer viewer, char trigger, int stateMask,
139 fContext.setReadOnly(false);
140 TemplateBuffer templateBuffer;
142 templateBuffer = fContext.evaluate(fTemplate);
143 } catch (TemplateException e1) {
144 fSelectedRegion = fRegion;
148 int start = getReplaceOffset();
149 int end = getReplaceEndOffset();
150 end = Math.max(end, offset);
152 // insert template string
153 IDocument document = viewer.getDocument();
154 String templateString = templateBuffer.getString();
155 document.replace(start, end - start, templateString);
157 // translate positions
158 LinkedModeModel model = new LinkedModeModel();
159 TemplateVariable[] variables = templateBuffer.getVariables();
161 MultiVariableGuess guess = fContext instanceof CompilationUnitContext ? ((CompilationUnitContext) fContext)
162 .getMultiVariableGuess()
165 boolean hasPositions = false;
166 for (int i = 0; i != variables.length; i++) {
167 TemplateVariable variable = variables[i];
169 if (variable.isUnambiguous())
172 LinkedPositionGroup group = new LinkedPositionGroup();
174 int[] offsets = variable.getOffsets();
175 int length = variable.getLength();
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);
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);
193 if (proposals.length > 1)
194 first = new ProposalPosition(document, offsets[0]
195 + start, length, proposals);
197 first = new LinkedPosition(document,
198 offsets[0] + start, length);
201 for (int j = 0; j != offsets.length; j++)
203 group.addPosition(first);
205 group.addPosition(new LinkedPosition(document,
206 offsets[j] + start, length));
208 model.addGroup(group);
213 model.forceInstall();
214 PHPEditor editor = getJavaEditor();
215 if (editor != null) {
217 .addLinkingListener(new EditorHighlightingSynchronizer(
221 LinkedModeUI ui = new EditorLinkedModeUI(model, viewer);
222 ui.setExitPosition(viewer, getCaretOffset(templateBuffer)
223 + start, 0, Integer.MAX_VALUE);
226 fSelectedRegion = ui.getSelectedRegion();
228 fSelectedRegion = new Region(getCaretOffset(templateBuffer)
231 } catch (BadLocationException e) {
233 openErrorDialog(viewer.getTextWidget().getShell(), e);
234 fSelectedRegion = fRegion;
235 } catch (BadPositionCategoryException e) {
237 openErrorDialog(viewer.getTextWidget().getShell(), e);
238 fSelectedRegion = fRegion;
244 * Returns the currently active java editor, or <code>null</code> if it
245 * cannot be determined.
247 * @return the currently active java editor, or <code>null</code>
249 private PHPEditor getJavaEditor() {
250 IEditorPart part = WebUI.getActivePage().getActiveEditor();
251 if (part instanceof PHPEditor)
252 return (PHPEditor) part;
258 * Returns the offset of the range in the document that will be replaced by
259 * applying this template.
261 * @return the offset of the range in the document that will be replaced by
262 * applying this template
264 private int getReplaceOffset() {
266 if (fContext instanceof DocumentTemplateContext) {
267 DocumentTemplateContext docContext = (DocumentTemplateContext) fContext;
268 start = docContext.getStart();
270 start = fRegion.getOffset();
276 * Returns the end offset of the range in the document that will be replaced
277 * by applying this template.
279 * @return the end offset of the range in the document that will be replaced
280 * by applying this template
282 private int getReplaceEndOffset() {
284 if (fContext instanceof DocumentTemplateContext) {
285 DocumentTemplateContext docContext = (DocumentTemplateContext) fContext;
286 end = docContext.getEnd();
288 end = fRegion.getOffset() + fRegion.getLength();
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(
299 document.addPositionUpdater(updater);
301 model.addLinkingListener(new ILinkedModeListener() {
304 * @see org.eclipse.jface.text.link.ILinkedModeListener#left(org.eclipse.jface.text.link.LinkedModeModel,
307 public void left(LinkedModeModel environment, int flags) {
309 document.removePositionCategory(getCategory());
310 } catch (BadPositionCategoryException e) {
313 document.removePositionUpdater(updater);
316 public void suspend(LinkedModeModel environment) {
319 public void resume(LinkedModeModel environment, int flags) {
325 private String getCategory() {
326 return "TemplateProposalCategory_" + toString(); //$NON-NLS-1$
329 private int getCaretOffset(TemplateBuffer buffer) {
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];
338 return buffer.getString().length();
342 * @see ICompletionProposal#getSelection(IDocument)
344 public Point getSelection(IDocument document) {
345 return new Point(fSelectedRegion.getOffset(), fSelectedRegion
350 * @see ICompletionProposal#getAdditionalProposalInfo()
352 public String getAdditionalProposalInfo() {
354 fContext.setReadOnly(true);
355 TemplateBuffer templateBuffer;
357 templateBuffer = fContext.evaluate(fTemplate);
358 } catch (TemplateException e1) {
362 return templateBuffer.getString();
364 } catch (BadLocationException e) {
365 handleException(WebUI.getActiveWorkbenchShell(),
366 new CoreException(new Status(IStatus.ERROR,
367 WebUI.getPluginId(), IStatus.OK, "", e))); //$NON-NLS-1$
373 * @see ICompletionProposal#getDisplayString()
375 public String getDisplayString() {
376 if (fDisplayString == null) {
377 fDisplayString = fTemplate.getName()
378 + TemplateContentAssistMessages
379 .getString("TemplateProposal.delimiter") + fTemplate.getDescription(); //$NON-NLS-1$
381 return fDisplayString;
384 public void setDisplayString(String displayString) {
385 fDisplayString = displayString;
389 * @see ICompletionProposal#getImage()
391 public Image getImage() {
396 * @see ICompletionProposal#getContextInformation()
398 public IContextInformation getContextInformation() {
402 private void openErrorDialog(Shell shell, Exception e) {
403 MessageDialog.openError(shell, TemplateContentAssistMessages
404 .getString("TemplateEvaluator.error.title"), e.getMessage()); //$NON-NLS-1$
407 private void handleException(Shell shell, CoreException e) {
408 ExceptionHandler.handle(e, shell, TemplateContentAssistMessages
409 .getString("TemplateEvaluator.error.title"), null); //$NON-NLS-1$
413 * @see IJavaCompletionProposal#getRelevance()
415 public int getRelevance() {
419 public void setRelevance(int relevance) {
420 fRelevance = relevance;
423 public Template getTemplate() {
428 * @see org.eclipse.jface.text.contentassist.ICompletionProposalExtension3#getInformationControlCreator()
430 public IInformationControlCreator getInformationControlCreator() {
431 return new TemplateInformationControlCreator();
435 * @see org.eclipse.jface.text.contentassist.ICompletionProposalExtension2#selected(org.eclipse.jface.text.ITextViewer,
438 public void selected(ITextViewer viewer, boolean smartToggle) {
442 * @see org.eclipse.jface.text.contentassist.ICompletionProposalExtension2#unselected(org.eclipse.jface.text.ITextViewer)
444 public void unselected(ITextViewer viewer) {
448 * @see org.eclipse.jface.text.contentassist.ICompletionProposalExtension2#validate(org.eclipse.jface.text.IDocument,
449 * int, org.eclipse.jface.text.DocumentEvent)
451 public boolean validate(IDocument document, int offset, DocumentEvent event) {
453 int replaceOffset = getReplaceOffset();
454 if (offset >= replaceOffset) {
455 String content = document.get(replaceOffset, offset
457 return fTemplate.getName().startsWith(content);
459 } catch (BadLocationException e) {
460 // concurrent modification - ignore
466 * @see org.eclipse.jface.text.contentassist.ICompletionProposalExtension3#getReplacementString()
468 public CharSequence getPrefixCompletionText(IDocument document,
469 int completionOffset) {
470 return fTemplate.getName();
474 * @see org.eclipse.jface.text.contentassist.ICompletionProposalExtension3#getReplacementOffset()
476 public int getPrefixCompletionStart(IDocument document, int completionOffset) {
477 return getReplaceOffset();