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,
62 ICompletionProposalExtension2, ICompletionProposalExtension3 {
64 private final Template fTemplate;
66 private final TemplateContext fContext;
68 private final Image fImage;
70 private IRegion fRegion;
72 private int fRelevance;
74 private IRegion fSelectedRegion; // initialized by apply()
76 private String fDisplayString;
79 * Creates a template proposal with a template and its context.
84 * the context in which the template was requested
86 * the region this proposal applies to
88 * the icon of the proposal
90 public TemplateProposal(Template template, TemplateContext context,
91 IRegion region, Image image) {
92 Assert.isNotNull(template);
93 Assert.isNotNull(context);
94 Assert.isNotNull(region);
101 fDisplayString = null;
103 if (context instanceof JavaContext) {
104 switch (((JavaContext) context).getCharacterBeforeStart()) {
105 // high relevance after whitespace
121 * @see ICompletionProposal#apply(IDocument)
123 public final void apply(IDocument document) {
124 // not called anymore
128 * @see org.eclipse.jface.text.contentassist.ICompletionProposalExtension2#apply(org.eclipse.jface.text.ITextViewer,
131 public void apply(ITextViewer viewer, char trigger, int stateMask,
136 fContext.setReadOnly(false);
137 TemplateBuffer templateBuffer;
139 templateBuffer = fContext.evaluate(fTemplate);
140 } catch (TemplateException e1) {
141 fSelectedRegion = fRegion;
145 int start = getReplaceOffset();
146 int end = getReplaceEndOffset();
147 end = Math.max(end, offset);
149 // insert template string
150 IDocument document = viewer.getDocument();
151 String templateString = templateBuffer.getString();
152 document.replace(start, end - start, templateString);
154 // translate positions
155 LinkedModeModel model = new LinkedModeModel();
156 TemplateVariable[] variables = templateBuffer.getVariables();
158 MultiVariableGuess guess = fContext instanceof CompilationUnitContext ? ((CompilationUnitContext) fContext)
159 .getMultiVariableGuess()
162 boolean hasPositions = false;
163 for (int i = 0; i != variables.length; i++) {
164 TemplateVariable variable = variables[i];
166 if (variable.isUnambiguous())
169 LinkedPositionGroup group = new LinkedPositionGroup();
171 int[] offsets = variable.getOffsets();
172 int length = variable.getLength();
174 LinkedPosition first;
175 if (guess != null && variable instanceof MultiVariable) {
176 first = new VariablePosition(document, offsets[0] + start,
177 length, guess, (MultiVariable) variable);
178 guess.addSlave((VariablePosition) first);
180 String[] values = variable.getValues();
181 ICompletionProposal[] proposals = new ICompletionProposal[values.length];
182 for (int j = 0; j < values.length; j++) {
183 ensurePositionCategoryInstalled(document, model);
184 Position pos = new Position(offsets[0] + start, length);
185 document.addPosition(getCategory(), pos);
186 proposals[j] = new PositionBasedCompletionProposal(
187 values[j], pos, length);
190 if (proposals.length > 1)
191 first = new ProposalPosition(document, offsets[0]
192 + start, length, proposals);
194 first = new LinkedPosition(document,
195 offsets[0] + start, length);
198 for (int j = 0; j != offsets.length; j++)
200 group.addPosition(first);
202 group.addPosition(new LinkedPosition(document,
203 offsets[j] + start, length));
205 model.addGroup(group);
210 model.forceInstall();
211 PHPEditor editor = getJavaEditor();
212 if (editor != null) {
214 .addLinkingListener(new EditorHighlightingSynchronizer(
218 LinkedModeUI ui = new EditorLinkedModeUI(model, viewer);
219 ui.setExitPosition(viewer, getCaretOffset(templateBuffer)
220 + start, 0, Integer.MAX_VALUE);
223 fSelectedRegion = ui.getSelectedRegion();
225 fSelectedRegion = new Region(getCaretOffset(templateBuffer)
228 } catch (BadLocationException e) {
229 PHPeclipsePlugin.log(e);
230 openErrorDialog(viewer.getTextWidget().getShell(), e);
231 fSelectedRegion = fRegion;
232 } catch (BadPositionCategoryException e) {
233 PHPeclipsePlugin.log(e);
234 openErrorDialog(viewer.getTextWidget().getShell(), e);
235 fSelectedRegion = fRegion;
241 * Returns the currently active java editor, or <code>null</code> if it
242 * cannot be determined.
244 * @return the currently active java editor, or <code>null</code>
246 private PHPEditor getJavaEditor() {
247 IEditorPart part = PHPeclipsePlugin.getActivePage().getActiveEditor();
248 if (part instanceof PHPEditor)
249 return (PHPEditor) part;
255 * Returns the offset of the range in the document that will be replaced by
256 * applying this template.
258 * @return the offset of the range in the document that will be replaced by
259 * applying this template
261 private int getReplaceOffset() {
263 if (fContext instanceof DocumentTemplateContext) {
264 DocumentTemplateContext docContext = (DocumentTemplateContext) fContext;
265 start = docContext.getStart();
267 start = fRegion.getOffset();
273 * Returns the end offset of the range in the document that will be replaced
274 * by applying this template.
276 * @return the end offset of the range in the document that will be replaced
277 * by applying this template
279 private int getReplaceEndOffset() {
281 if (fContext instanceof DocumentTemplateContext) {
282 DocumentTemplateContext docContext = (DocumentTemplateContext) fContext;
283 end = docContext.getEnd();
285 end = fRegion.getOffset() + fRegion.getLength();
290 private void ensurePositionCategoryInstalled(final IDocument document,
291 LinkedModeModel model) {
292 if (!document.containsPositionCategory(getCategory())) {
293 document.addPositionCategory(getCategory());
294 final InclusivePositionUpdater updater = new InclusivePositionUpdater(
296 document.addPositionUpdater(updater);
298 model.addLinkingListener(new ILinkedModeListener() {
301 * @see org.eclipse.jface.text.link.ILinkedModeListener#left(org.eclipse.jface.text.link.LinkedModeModel,
304 public void left(LinkedModeModel environment, int flags) {
306 document.removePositionCategory(getCategory());
307 } catch (BadPositionCategoryException e) {
310 document.removePositionUpdater(updater);
313 public void suspend(LinkedModeModel environment) {
316 public void resume(LinkedModeModel environment, int flags) {
322 private String getCategory() {
323 return "TemplateProposalCategory_" + toString(); //$NON-NLS-1$
326 private int getCaretOffset(TemplateBuffer buffer) {
328 TemplateVariable[] variables = buffer.getVariables();
329 for (int i = 0; i != variables.length; i++) {
330 TemplateVariable variable = variables[i];
331 if (variable.getType().equals(GlobalTemplateVariables.Cursor.NAME))
332 return variable.getOffsets()[0];
335 return buffer.getString().length();
339 * @see ICompletionProposal#getSelection(IDocument)
341 public Point getSelection(IDocument document) {
342 return new Point(fSelectedRegion.getOffset(), fSelectedRegion
347 * @see ICompletionProposal#getAdditionalProposalInfo()
349 public String getAdditionalProposalInfo() {
351 fContext.setReadOnly(true);
352 TemplateBuffer templateBuffer;
354 templateBuffer = fContext.evaluate(fTemplate);
355 } catch (TemplateException e1) {
359 return templateBuffer.getString();
361 } catch (BadLocationException e) {
362 handleException(PHPeclipsePlugin.getActiveWorkbenchShell(),
363 new CoreException(new Status(IStatus.ERROR,
364 PHPeclipsePlugin.getPluginId(), IStatus.OK, "", e))); //$NON-NLS-1$
370 * @see ICompletionProposal#getDisplayString()
372 public String getDisplayString() {
373 if (fDisplayString == null) {
374 fDisplayString = fTemplate.getName()
375 + TemplateContentAssistMessages
376 .getString("TemplateProposal.delimiter") + fTemplate.getDescription(); //$NON-NLS-1$
378 return fDisplayString;
381 public void setDisplayString(String displayString) {
382 fDisplayString = displayString;
386 * @see ICompletionProposal#getImage()
388 public Image getImage() {
393 * @see ICompletionProposal#getContextInformation()
395 public IContextInformation getContextInformation() {
399 private void openErrorDialog(Shell shell, Exception e) {
400 MessageDialog.openError(shell, TemplateContentAssistMessages
401 .getString("TemplateEvaluator.error.title"), e.getMessage()); //$NON-NLS-1$
404 private void handleException(Shell shell, CoreException e) {
405 ExceptionHandler.handle(e, shell, TemplateContentAssistMessages
406 .getString("TemplateEvaluator.error.title"), null); //$NON-NLS-1$
410 * @see IJavaCompletionProposal#getRelevance()
412 public int getRelevance() {
416 public void setRelevance(int relevance) {
417 fRelevance = relevance;
420 public Template getTemplate() {
425 * @see org.eclipse.jface.text.contentassist.ICompletionProposalExtension3#getInformationControlCreator()
427 public IInformationControlCreator getInformationControlCreator() {
428 return new TemplateInformationControlCreator();
432 * @see org.eclipse.jface.text.contentassist.ICompletionProposalExtension2#selected(org.eclipse.jface.text.ITextViewer,
435 public void selected(ITextViewer viewer, boolean smartToggle) {
439 * @see org.eclipse.jface.text.contentassist.ICompletionProposalExtension2#unselected(org.eclipse.jface.text.ITextViewer)
441 public void unselected(ITextViewer viewer) {
445 * @see org.eclipse.jface.text.contentassist.ICompletionProposalExtension2#validate(org.eclipse.jface.text.IDocument,
446 * int, org.eclipse.jface.text.DocumentEvent)
448 public boolean validate(IDocument document, int offset, DocumentEvent event) {
450 int replaceOffset = getReplaceOffset();
451 if (offset >= replaceOffset) {
452 String content = document.get(replaceOffset, offset
454 return fTemplate.getName().startsWith(content);
456 } catch (BadLocationException e) {
457 // concurrent modification - ignore
463 * @see org.eclipse.jface.text.contentassist.ICompletionProposalExtension3#getReplacementString()
465 public CharSequence getPrefixCompletionText(IDocument document,
466 int completionOffset) {
467 return fTemplate.getName();
471 * @see org.eclipse.jface.text.contentassist.ICompletionProposalExtension3#getReplacementOffset()
473 public int getPrefixCompletionStart(IDocument document, int completionOffset) {
474 return getReplaceOffset();