001: /*******************************************************************************
002: * Copyright (c) 2000, 2006 IBM Corporation and others.
003: * All rights reserved. This program and the accompanying materials
004: * are made available under the terms of the Eclipse Public License v1.0
005: * which accompanies this distribution, and is available at
006: * http://www.eclipse.org/legal/epl-v10.html
007: *
008: * Contributors:
009: * IBM Corporation - initial API and implementation
010: *******************************************************************************/package org.eclipse.jdt.internal.ui.text.template.contentassist;
011:
012: import org.eclipse.core.runtime.Assert;
013: import org.eclipse.core.runtime.CoreException;
014: import org.eclipse.core.runtime.IStatus;
015: import org.eclipse.core.runtime.Status;
016:
017: import org.eclipse.swt.SWT;
018: import org.eclipse.swt.graphics.Image;
019: import org.eclipse.swt.graphics.Point;
020: import org.eclipse.swt.widgets.Shell;
021:
022: import org.eclipse.jface.dialogs.MessageDialog;
023:
024: import org.eclipse.jface.text.BadLocationException;
025: import org.eclipse.jface.text.BadPositionCategoryException;
026: import org.eclipse.jface.text.Document;
027: import org.eclipse.jface.text.DocumentEvent;
028: import org.eclipse.jface.text.IDocument;
029: import org.eclipse.jface.text.IInformationControlCreator;
030: import org.eclipse.jface.text.IRegion;
031: import org.eclipse.jface.text.IRewriteTarget;
032: import org.eclipse.jface.text.ITextViewer;
033: import org.eclipse.jface.text.ITextViewerExtension;
034: import org.eclipse.jface.text.Position;
035: import org.eclipse.jface.text.Region;
036: import org.eclipse.jface.text.contentassist.ICompletionProposal;
037: import org.eclipse.jface.text.contentassist.ICompletionProposalExtension2;
038: import org.eclipse.jface.text.contentassist.ICompletionProposalExtension3;
039: import org.eclipse.jface.text.contentassist.ICompletionProposalExtension4;
040: import org.eclipse.jface.text.contentassist.IContextInformation;
041: import org.eclipse.jface.text.link.ILinkedModeListener;
042: import org.eclipse.jface.text.link.LinkedModeModel;
043: import org.eclipse.jface.text.link.LinkedModeUI;
044: import org.eclipse.jface.text.link.LinkedPosition;
045: import org.eclipse.jface.text.link.LinkedPositionGroup;
046: import org.eclipse.jface.text.link.ProposalPosition;
047: import org.eclipse.jface.text.source.LineRange;
048: import org.eclipse.jface.text.templates.DocumentTemplateContext;
049: import org.eclipse.jface.text.templates.GlobalTemplateVariables;
050: import org.eclipse.jface.text.templates.Template;
051: import org.eclipse.jface.text.templates.TemplateBuffer;
052: import org.eclipse.jface.text.templates.TemplateContext;
053: import org.eclipse.jface.text.templates.TemplateException;
054: import org.eclipse.jface.text.templates.TemplateVariable;
055:
056: import org.eclipse.ui.IEditorPart;
057: import org.eclipse.ui.part.IWorkbenchPartOrientation;
058: import org.eclipse.ui.texteditor.link.EditorLinkedModeUI;
059:
060: import org.eclipse.jdt.internal.corext.template.java.CompilationUnitContext;
061: import org.eclipse.jdt.internal.corext.template.java.JavaDocContext;
062: import org.eclipse.jdt.internal.corext.util.Messages;
063:
064: import org.eclipse.jdt.ui.text.java.IJavaCompletionProposal;
065:
066: import org.eclipse.jdt.internal.ui.JavaPlugin;
067: import org.eclipse.jdt.internal.ui.javaeditor.EditorHighlightingSynchronizer;
068: import org.eclipse.jdt.internal.ui.javaeditor.IndentUtil;
069: import org.eclipse.jdt.internal.ui.javaeditor.JavaEditor;
070: import org.eclipse.jdt.internal.ui.util.ExceptionHandler;
071:
072: /**
073: * A template proposal.
074: */
075: public class TemplateProposal implements IJavaCompletionProposal,
076: ICompletionProposalExtension2, ICompletionProposalExtension3,
077: ICompletionProposalExtension4 {
078:
079: private final Template fTemplate;
080: private final TemplateContext fContext;
081: private final Image fImage;
082: private final IRegion fRegion;
083: private int fRelevance;
084:
085: private IRegion fSelectedRegion; // initialized by apply()
086: private String fDisplayString;
087: private InclusivePositionUpdater fUpdater;
088:
089: /**
090: * Creates a template proposal with a template and its context.
091: *
092: * @param template the template
093: * @param context the context in which the template was requested.
094: * @param region the region this proposal is applied to
095: * @param image the icon of the proposal.
096: */
097: public TemplateProposal(Template template, TemplateContext context,
098: IRegion region, Image image) {
099: Assert.isNotNull(template);
100: Assert.isNotNull(context);
101: Assert.isNotNull(region);
102:
103: fTemplate = template;
104: fContext = context;
105: fImage = image;
106: fRegion = region;
107:
108: fDisplayString = null;
109:
110: fRelevance = computeRelevance();
111: }
112:
113: /**
114: * Computes the relevance to match the relevance values generated by the
115: * core content assistant.
116: *
117: * @return a sensible relevance value.
118: */
119: private int computeRelevance() {
120: // see org.eclipse.jdt.internal.codeassist.RelevanceConstants
121: final int R_DEFAULT = 0;
122: final int R_INTERESTING = 5;
123: final int R_CASE = 10;
124: final int R_NON_RESTRICTED = 3;
125: final int R_EXACT_NAME = 4;
126: final int R_INLINE_TAG = 31;
127:
128: int base = R_DEFAULT + R_INTERESTING + R_NON_RESTRICTED;
129:
130: try {
131: if (fContext instanceof DocumentTemplateContext) {
132: DocumentTemplateContext templateContext = (DocumentTemplateContext) fContext;
133: IDocument document = templateContext.getDocument();
134:
135: String content = document.get(fRegion.getOffset(),
136: fRegion.getLength());
137: if (fTemplate.getName().startsWith(content))
138: base += R_CASE;
139: if (fTemplate.getName().equalsIgnoreCase(content))
140: base += R_EXACT_NAME;
141: if (fContext instanceof JavaDocContext)
142: base += R_INLINE_TAG;
143: }
144: } catch (BadLocationException e) {
145: // ignore - not a case sensitive match then
146: }
147:
148: // see CompletionProposalCollector.computeRelevance
149: // just under keywords, but better than packages
150: final int TEMPLATE_RELEVANCE = 1;
151: return base * 16 + TEMPLATE_RELEVANCE;
152: }
153:
154: /**
155: * Returns the template of this proposal.
156: *
157: * @return the template of this proposal
158: * @since 3.1
159: */
160: public final Template getTemplate() {
161: return fTemplate;
162: }
163:
164: /**
165: * Returns the context in which the template was requested.
166: *
167: * @return the context in which the template was requested
168: * @since 3.1
169: */
170: protected final TemplateContext getContext() {
171: return fContext;
172: }
173:
174: /*
175: * @see ICompletionProposal#apply(IDocument)
176: */
177: public final void apply(IDocument document) {
178: // not called anymore
179: }
180:
181: /*
182: * @see org.eclipse.jface.text.contentassist.ICompletionProposalExtension2#apply(org.eclipse.jface.text.ITextViewer, char, int, int)
183: */
184: public void apply(ITextViewer viewer, char trigger, int stateMask,
185: int offset) {
186:
187: IDocument document = viewer.getDocument();
188: try {
189: fContext.setReadOnly(false);
190: int start;
191: TemplateBuffer templateBuffer;
192: try {
193: beginCompoundChange(viewer);
194:
195: int oldReplaceOffset = getReplaceOffset();
196: try {
197: // this may already modify the document (e.g. add imports)
198: templateBuffer = fContext.evaluate(fTemplate);
199: } catch (TemplateException e1) {
200: fSelectedRegion = fRegion;
201: return;
202: }
203:
204: start = getReplaceOffset();
205: int shift = start - oldReplaceOffset;
206: int end = Math.max(getReplaceEndOffset(), offset
207: + shift);
208:
209: // insert template string
210: if (end > document.getLength())
211: end = offset;
212: String templateString = templateBuffer.getString();
213: document.replace(start, end - start, templateString);
214: } finally {
215: endCompoundChange(viewer);
216: }
217:
218: // translate positions
219: LinkedModeModel model = new LinkedModeModel();
220: TemplateVariable[] variables = templateBuffer
221: .getVariables();
222:
223: MultiVariableGuess guess = fContext instanceof CompilationUnitContext ? ((CompilationUnitContext) fContext)
224: .getMultiVariableGuess()
225: : null;
226:
227: boolean hasPositions = false;
228: for (int i = 0; i != variables.length; i++) {
229: TemplateVariable variable = variables[i];
230:
231: if (variable.isUnambiguous())
232: continue;
233:
234: LinkedPositionGroup group = new LinkedPositionGroup();
235:
236: int[] offsets = variable.getOffsets();
237: int length = variable.getLength();
238:
239: LinkedPosition first;
240: if (guess != null && variable instanceof MultiVariable) {
241: first = new VariablePosition(document, offsets[0]
242: + start, length, guess,
243: (MultiVariable) variable);
244: guess.addSlave((VariablePosition) first);
245: } else {
246: String[] values = variable.getValues();
247: ICompletionProposal[] proposals = new ICompletionProposal[values.length];
248: for (int j = 0; j < values.length; j++) {
249: ensurePositionCategoryInstalled(document, model);
250: Position pos = new Position(offsets[0] + start,
251: length);
252: document.addPosition(getCategory(), pos);
253: proposals[j] = new PositionBasedCompletionProposal(
254: values[j], pos, length);
255: }
256:
257: if (proposals.length > 1)
258: first = new ProposalPosition(document,
259: offsets[0] + start, length, proposals);
260: else
261: first = new LinkedPosition(document, offsets[0]
262: + start, length);
263: }
264:
265: for (int j = 0; j != offsets.length; j++)
266: if (j == 0)
267: group.addPosition(first);
268: else
269: group.addPosition(new LinkedPosition(document,
270: offsets[j] + start, length));
271:
272: model.addGroup(group);
273: hasPositions = true;
274: }
275:
276: if (hasPositions) {
277: model.forceInstall();
278: JavaEditor editor = getJavaEditor();
279: if (editor != null) {
280: model
281: .addLinkingListener(new EditorHighlightingSynchronizer(
282: editor));
283: }
284:
285: LinkedModeUI ui = new EditorLinkedModeUI(model, viewer);
286: ui.setExitPosition(viewer,
287: getCaretOffset(templateBuffer) + start, 0,
288: Integer.MAX_VALUE);
289: ui.enter();
290:
291: fSelectedRegion = ui.getSelectedRegion();
292: } else {
293: fSelectedRegion = new Region(
294: getCaretOffset(templateBuffer) + start, 0);
295: }
296:
297: } catch (BadLocationException e) {
298: JavaPlugin.log(e);
299: openErrorDialog(viewer.getTextWidget().getShell(), e);
300: fSelectedRegion = fRegion;
301: } catch (BadPositionCategoryException e) {
302: JavaPlugin.log(e);
303: openErrorDialog(viewer.getTextWidget().getShell(), e);
304: fSelectedRegion = fRegion;
305: }
306:
307: }
308:
309: private void endCompoundChange(ITextViewer viewer) {
310: if (viewer instanceof ITextViewerExtension) {
311: ITextViewerExtension extension = (ITextViewerExtension) viewer;
312: IRewriteTarget target = extension.getRewriteTarget();
313: target.endCompoundChange();
314: }
315: }
316:
317: private void beginCompoundChange(ITextViewer viewer) {
318: if (viewer instanceof ITextViewerExtension) {
319: ITextViewerExtension extension = (ITextViewerExtension) viewer;
320: IRewriteTarget target = extension.getRewriteTarget();
321: target.beginCompoundChange();
322: }
323: }
324:
325: /**
326: * Returns the currently active java editor, or <code>null</code> if it
327: * cannot be determined.
328: *
329: * @return the currently active java editor, or <code>null</code>
330: */
331: private JavaEditor getJavaEditor() {
332: IEditorPart part = JavaPlugin.getActivePage().getActiveEditor();
333: if (part instanceof JavaEditor)
334: return (JavaEditor) part;
335: else
336: return null;
337: }
338:
339: private void ensurePositionCategoryInstalled(
340: final IDocument document, LinkedModeModel model) {
341: if (!document.containsPositionCategory(getCategory())) {
342: document.addPositionCategory(getCategory());
343: fUpdater = new InclusivePositionUpdater(getCategory());
344: document.addPositionUpdater(fUpdater);
345:
346: model.addLinkingListener(new ILinkedModeListener() {
347:
348: /*
349: * @see org.eclipse.jface.text.link.ILinkedModeListener#left(org.eclipse.jface.text.link.LinkedModeModel, int)
350: */
351: public void left(LinkedModeModel environment, int flags) {
352: ensurePositionCategoryRemoved(document);
353: }
354:
355: public void suspend(LinkedModeModel environment) {
356: }
357:
358: public void resume(LinkedModeModel environment,
359: int flags) {
360: }
361: });
362: }
363: }
364:
365: private void ensurePositionCategoryRemoved(IDocument document) {
366: if (document.containsPositionCategory(getCategory())) {
367: try {
368: document.removePositionCategory(getCategory());
369: } catch (BadPositionCategoryException e) {
370: // ignore
371: }
372: document.removePositionUpdater(fUpdater);
373: }
374: }
375:
376: private String getCategory() {
377: return "TemplateProposalCategory_" + toString(); //$NON-NLS-1$
378: }
379:
380: private int getCaretOffset(TemplateBuffer buffer) {
381:
382: TemplateVariable[] variables = buffer.getVariables();
383: for (int i = 0; i != variables.length; i++) {
384: TemplateVariable variable = variables[i];
385: if (variable.getType().equals(
386: GlobalTemplateVariables.Cursor.NAME))
387: return variable.getOffsets()[0];
388: }
389:
390: return buffer.getString().length();
391: }
392:
393: /**
394: * Returns the offset of the range in the document that will be replaced by
395: * applying this template.
396: *
397: * @return the offset of the range in the document that will be replaced by
398: * applying this template
399: */
400: protected final int getReplaceOffset() {
401: int start;
402: if (fContext instanceof DocumentTemplateContext) {
403: DocumentTemplateContext docContext = (DocumentTemplateContext) fContext;
404: start = docContext.getStart();
405: } else {
406: start = fRegion.getOffset();
407: }
408: return start;
409: }
410:
411: /**
412: * Returns the end offset of the range in the document that will be replaced
413: * by applying this template.
414: *
415: * @return the end offset of the range in the document that will be replaced
416: * by applying this template
417: */
418: protected final int getReplaceEndOffset() {
419: int end;
420: if (fContext instanceof DocumentTemplateContext) {
421: DocumentTemplateContext docContext = (DocumentTemplateContext) fContext;
422: end = docContext.getEnd();
423: } else {
424: end = fRegion.getOffset() + fRegion.getLength();
425: }
426: return end;
427: }
428:
429: /*
430: * @see ICompletionProposal#getSelection(IDocument)
431: */
432: public Point getSelection(IDocument document) {
433: return new Point(fSelectedRegion.getOffset(), fSelectedRegion
434: .getLength());
435: }
436:
437: /*
438: * @see ICompletionProposal#getAdditionalProposalInfo()
439: */
440: public String getAdditionalProposalInfo() {
441: try {
442: fContext.setReadOnly(true);
443: TemplateBuffer templateBuffer;
444: try {
445: templateBuffer = fContext.evaluate(fTemplate);
446: } catch (TemplateException e) {
447: return null;
448: }
449:
450: IDocument document = new Document(templateBuffer
451: .getString());
452: IndentUtil.indentLines(document, new LineRange(0, document
453: .getNumberOfLines()), null, null);
454: return document.get();
455:
456: } catch (BadLocationException e) {
457: handleException(JavaPlugin.getActiveWorkbenchShell(),
458: new CoreException(
459: new Status(IStatus.ERROR, JavaPlugin
460: .getPluginId(), IStatus.OK, "", e))); //$NON-NLS-1$
461: return null;
462: }
463: }
464:
465: /*
466: * @see ICompletionProposal#getDisplayString()
467: */
468: public String getDisplayString() {
469: if (fDisplayString == null) {
470: String[] arguments = new String[] { fTemplate.getName(),
471: fTemplate.getDescription() };
472: fDisplayString = Messages
473: .format(
474: TemplateContentAssistMessages.TemplateProposal_displayString,
475: arguments);
476: }
477: return fDisplayString;
478: }
479:
480: public void setDisplayString(String displayString) {
481: fDisplayString = displayString;
482: }
483:
484: /*
485: * @see ICompletionProposal#getImage()
486: */
487: public Image getImage() {
488: return fImage;
489: }
490:
491: /*
492: * @see ICompletionProposal#getContextInformation()
493: */
494: public IContextInformation getContextInformation() {
495: return null;
496: }
497:
498: private void openErrorDialog(Shell shell, Exception e) {
499: MessageDialog
500: .openError(
501: shell,
502: TemplateContentAssistMessages.TemplateEvaluator_error_title,
503: e.getMessage());
504: }
505:
506: private void handleException(Shell shell, CoreException e) {
507: ExceptionHandler
508: .handle(
509: e,
510: shell,
511: TemplateContentAssistMessages.TemplateEvaluator_error_title,
512: null);
513: }
514:
515: /*
516: * @see IJavaCompletionProposal#getRelevance()
517: */
518: public int getRelevance() {
519: return fRelevance;
520: }
521:
522: public void setRelevance(int relevance) {
523: fRelevance = relevance;
524: }
525:
526: /*
527: * @see org.eclipse.jface.text.contentassist.ICompletionProposalExtension3#getInformationControlCreator()
528: */
529: public IInformationControlCreator getInformationControlCreator() {
530: int orientation;
531: IEditorPart editor = getJavaEditor();
532: if (editor instanceof IWorkbenchPartOrientation)
533: orientation = ((IWorkbenchPartOrientation) editor)
534: .getOrientation();
535: else
536: orientation = SWT.LEFT_TO_RIGHT;
537: return new TemplateInformationControlCreator(orientation);
538: }
539:
540: /*
541: * @see org.eclipse.jface.text.contentassist.ICompletionProposalExtension2#selected(org.eclipse.jface.text.ITextViewer, boolean)
542: */
543: public void selected(ITextViewer viewer, boolean smartToggle) {
544: }
545:
546: /*
547: * @see org.eclipse.jface.text.contentassist.ICompletionProposalExtension2#unselected(org.eclipse.jface.text.ITextViewer)
548: */
549: public void unselected(ITextViewer viewer) {
550: }
551:
552: /*
553: * @see org.eclipse.jface.text.contentassist.ICompletionProposalExtension2#validate(org.eclipse.jface.text.IDocument, int, org.eclipse.jface.text.DocumentEvent)
554: */
555: public boolean validate(IDocument document, int offset,
556: DocumentEvent event) {
557: try {
558: int replaceOffset = getReplaceOffset();
559: if (offset >= replaceOffset) {
560: String content = document.get(replaceOffset, offset
561: - replaceOffset);
562: String templateName = fTemplate.getName().toLowerCase();
563: boolean valid = templateName.startsWith(content
564: .toLowerCase());
565: if (!valid && fContext instanceof JavaDocContext
566: && templateName.startsWith("<")) { //$NON-NLS-1$
567: valid = templateName.startsWith(content
568: .toLowerCase(), 1);
569: }
570: return valid;
571: }
572: } catch (BadLocationException e) {
573: // concurrent modification - ignore
574: }
575: return false;
576: }
577:
578: /*
579: * @see org.eclipse.jface.text.contentassist.ICompletionProposalExtension3#getReplacementString()
580: */
581: public CharSequence getPrefixCompletionText(IDocument document,
582: int completionOffset) {
583: // bug 114360 - don't make selection templates prefix-completable
584: if (isSelectionTemplate())
585: return ""; //$NON-NLS-1$
586: return fTemplate.getName();
587: }
588:
589: /*
590: * @see org.eclipse.jface.text.contentassist.ICompletionProposalExtension3#getReplacementOffset()
591: */
592: public int getPrefixCompletionStart(IDocument document,
593: int completionOffset) {
594: return getReplaceOffset();
595: }
596:
597: /*
598: * @see org.eclipse.jface.text.contentassist.ICompletionProposalExtension4#isAutoInsertable()
599: */
600: public boolean isAutoInsertable() {
601: if (isSelectionTemplate())
602: return false;
603: return fTemplate.isAutoInsertable();
604: }
605:
606: /**
607: * Returns <code>true</code> if the proposal has a selection, e.g. will wrap some code.
608: *
609: * @return <code>true</code> if the proposals completion length is non zero
610: * @since 3.2
611: */
612: private boolean isSelectionTemplate() {
613: if (fContext instanceof DocumentTemplateContext) {
614: DocumentTemplateContext ctx = (DocumentTemplateContext) fContext;
615: if (ctx.getCompletionLength() > 0)
616: return true;
617: }
618: return false;
619: }
620: }
|