001: /*******************************************************************************
002: * Copyright (c) 2000, 2007 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.jface.text.templates;
011:
012: import org.eclipse.core.runtime.Assert;
013: import org.eclipse.jface.dialogs.MessageDialog;
014: import org.eclipse.jface.text.BadLocationException;
015: import org.eclipse.jface.text.BadPositionCategoryException;
016: import org.eclipse.jface.text.DocumentEvent;
017: import org.eclipse.jface.text.IDocument;
018: import org.eclipse.jface.text.IInformationControlCreator;
019: import org.eclipse.jface.text.IRegion;
020: import org.eclipse.jface.text.ITextViewer;
021: import org.eclipse.jface.text.Position;
022: import org.eclipse.jface.text.Region;
023: import org.eclipse.jface.text.contentassist.ICompletionProposal;
024: import org.eclipse.jface.text.contentassist.ICompletionProposalExtension;
025: import org.eclipse.jface.text.contentassist.ICompletionProposalExtension2;
026: import org.eclipse.jface.text.contentassist.ICompletionProposalExtension3;
027: import org.eclipse.jface.text.contentassist.IContextInformation;
028: import org.eclipse.jface.text.link.ILinkedModeListener;
029: import org.eclipse.jface.text.link.LinkedModeModel;
030: import org.eclipse.jface.text.link.LinkedModeUI;
031: import org.eclipse.jface.text.link.LinkedPosition;
032: import org.eclipse.jface.text.link.LinkedPositionGroup;
033: import org.eclipse.jface.text.link.ProposalPosition;
034: import org.eclipse.swt.graphics.Image;
035: import org.eclipse.swt.graphics.Point;
036: import org.eclipse.swt.widgets.Shell;
037:
038: /**
039: * A template completion proposal.
040: * <p>
041: * Clients may subclass.</p>
042: *
043: * @since 3.0
044: */
045: public class TemplateProposal implements ICompletionProposal,
046: ICompletionProposalExtension, ICompletionProposalExtension2,
047: ICompletionProposalExtension3 {
048:
049: private final Template fTemplate;
050: private final TemplateContext fContext;
051: private final Image fImage;
052: private final IRegion fRegion;
053: private int fRelevance;
054:
055: private IRegion fSelectedRegion; // initialized by apply()
056: private String fDisplayString;
057: private InclusivePositionUpdater fUpdater;
058: private IInformationControlCreator fInformationControlCreator;
059:
060: /**
061: * Creates a template proposal with a template and its context.
062: *
063: * @param template the template
064: * @param context the context in which the template was requested.
065: * @param region the region this proposal is applied to
066: * @param image the icon of the proposal.
067: */
068: public TemplateProposal(Template template, TemplateContext context,
069: IRegion region, Image image) {
070: this (template, context, region, image, 0);
071: }
072:
073: /**
074: * Creates a template proposal with a template and its context.
075: *
076: * @param template the template
077: * @param context the context in which the template was requested.
078: * @param image the icon of the proposal.
079: * @param region the region this proposal is applied to
080: * @param relevance the relevance of the proposal
081: */
082: public TemplateProposal(Template template, TemplateContext context,
083: IRegion region, Image image, int relevance) {
084: Assert.isNotNull(template);
085: Assert.isNotNull(context);
086: Assert.isNotNull(region);
087:
088: fTemplate = template;
089: fContext = context;
090: fImage = image;
091: fRegion = region;
092:
093: fDisplayString = null;
094:
095: fRelevance = relevance;
096: }
097:
098: /**
099: * Sets the information control creator for this completion proposal.
100: *
101: * @param informationControlCreator the information control creator
102: * @since 3.1
103: */
104: public final void setInformationControlCreator(
105: IInformationControlCreator informationControlCreator) {
106: fInformationControlCreator = informationControlCreator;
107: }
108:
109: /**
110: * Returns the template of this proposal.
111: *
112: * @return the template of this proposal
113: * @since 3.1
114: */
115: protected final Template getTemplate() {
116: return fTemplate;
117: }
118:
119: /**
120: * Returns the context in which the template was requested.
121: *
122: * @return the context in which the template was requested
123: * @since 3.1
124: */
125: protected final TemplateContext getContext() {
126: return fContext;
127: }
128:
129: /*
130: * @see ICompletionProposal#apply(IDocument)
131: */
132: public final void apply(IDocument document) {
133: // not called anymore
134: }
135:
136: /**
137: * Inserts the template offered by this proposal into the viewer's document
138: * and sets up a <code>LinkedModeUI</code> on the viewer to edit any of
139: * the template's unresolved variables.
140: *
141: * @param viewer {@inheritDoc}
142: * @param trigger {@inheritDoc}
143: * @param stateMask {@inheritDoc}
144: * @param offset {@inheritDoc}
145: */
146: public void apply(ITextViewer viewer, char trigger, int stateMask,
147: int offset) {
148:
149: IDocument document = viewer.getDocument();
150: try {
151: fContext.setReadOnly(false);
152: int start;
153: TemplateBuffer templateBuffer;
154: {
155: int oldReplaceOffset = getReplaceOffset();
156: try {
157: // this may already modify the document (e.g. add imports)
158: templateBuffer = fContext.evaluate(fTemplate);
159: } catch (TemplateException e1) {
160: fSelectedRegion = fRegion;
161: return;
162: }
163:
164: start = getReplaceOffset();
165: int shift = start - oldReplaceOffset;
166: int end = Math.max(getReplaceEndOffset(), offset
167: + shift);
168:
169: // insert template string
170: String templateString = templateBuffer.getString();
171: document.replace(start, end - start, templateString);
172: }
173:
174: // translate positions
175: LinkedModeModel model = new LinkedModeModel();
176: TemplateVariable[] variables = templateBuffer
177: .getVariables();
178: boolean hasPositions = false;
179: for (int i = 0; i != variables.length; i++) {
180: TemplateVariable variable = variables[i];
181:
182: if (variable.isUnambiguous())
183: continue;
184:
185: LinkedPositionGroup group = new LinkedPositionGroup();
186:
187: int[] offsets = variable.getOffsets();
188: int length = variable.getLength();
189:
190: LinkedPosition first;
191: {
192: String[] values = variable.getValues();
193: ICompletionProposal[] proposals = new ICompletionProposal[values.length];
194: for (int j = 0; j < values.length; j++) {
195: ensurePositionCategoryInstalled(document, model);
196: Position pos = new Position(offsets[0] + start,
197: length);
198: document.addPosition(getCategory(), pos);
199: proposals[j] = new PositionBasedCompletionProposal(
200: values[j], pos, length);
201: }
202:
203: if (proposals.length > 1)
204: first = new ProposalPosition(document,
205: offsets[0] + start, length, proposals);
206: else
207: first = new LinkedPosition(document, offsets[0]
208: + start, length);
209: }
210:
211: for (int j = 0; j != offsets.length; j++)
212: if (j == 0)
213: group.addPosition(first);
214: else
215: group.addPosition(new LinkedPosition(document,
216: offsets[j] + start, length));
217:
218: model.addGroup(group);
219: hasPositions = true;
220: }
221:
222: if (hasPositions) {
223: model.forceInstall();
224: LinkedModeUI ui = new LinkedModeUI(model, viewer);
225: ui.setExitPosition(viewer,
226: getCaretOffset(templateBuffer) + start, 0,
227: Integer.MAX_VALUE);
228: ui.enter();
229:
230: fSelectedRegion = ui.getSelectedRegion();
231: } else {
232: ensurePositionCategoryRemoved(document);
233: fSelectedRegion = new Region(
234: getCaretOffset(templateBuffer) + start, 0);
235: }
236:
237: } catch (BadLocationException e) {
238: openErrorDialog(viewer.getTextWidget().getShell(), e);
239: ensurePositionCategoryRemoved(document);
240: fSelectedRegion = fRegion;
241: } catch (BadPositionCategoryException e) {
242: openErrorDialog(viewer.getTextWidget().getShell(), e);
243: fSelectedRegion = fRegion;
244: }
245:
246: }
247:
248: private void ensurePositionCategoryInstalled(
249: final IDocument document, LinkedModeModel model) {
250: if (!document.containsPositionCategory(getCategory())) {
251: document.addPositionCategory(getCategory());
252: fUpdater = new InclusivePositionUpdater(getCategory());
253: document.addPositionUpdater(fUpdater);
254:
255: model.addLinkingListener(new ILinkedModeListener() {
256:
257: /*
258: * @see org.eclipse.jface.text.link.ILinkedModeListener#left(org.eclipse.jface.text.link.LinkedModeModel, int)
259: */
260: public void left(LinkedModeModel environment, int flags) {
261: ensurePositionCategoryRemoved(document);
262: }
263:
264: public void suspend(LinkedModeModel environment) {
265: }
266:
267: public void resume(LinkedModeModel environment,
268: int flags) {
269: }
270: });
271: }
272: }
273:
274: private void ensurePositionCategoryRemoved(IDocument document) {
275: if (document.containsPositionCategory(getCategory())) {
276: try {
277: document.removePositionCategory(getCategory());
278: } catch (BadPositionCategoryException e) {
279: // ignore
280: }
281: document.removePositionUpdater(fUpdater);
282: }
283: }
284:
285: private String getCategory() {
286: return "TemplateProposalCategory_" + toString(); //$NON-NLS-1$
287: }
288:
289: private int getCaretOffset(TemplateBuffer buffer) {
290:
291: TemplateVariable[] variables = buffer.getVariables();
292: for (int i = 0; i != variables.length; i++) {
293: TemplateVariable variable = variables[i];
294: if (variable.getType().equals(
295: GlobalTemplateVariables.Cursor.NAME))
296: return variable.getOffsets()[0];
297: }
298:
299: return buffer.getString().length();
300: }
301:
302: /**
303: * Returns the offset of the range in the document that will be replaced by
304: * applying this template.
305: *
306: * @return the offset of the range in the document that will be replaced by
307: * applying this template
308: * @since 3.1
309: */
310: protected final int getReplaceOffset() {
311: int start;
312: if (fContext instanceof DocumentTemplateContext) {
313: DocumentTemplateContext docContext = (DocumentTemplateContext) fContext;
314: start = docContext.getStart();
315: } else {
316: start = fRegion.getOffset();
317: }
318: return start;
319: }
320:
321: /**
322: * Returns the end offset of the range in the document that will be replaced
323: * by applying this template.
324: *
325: * @return the end offset of the range in the document that will be replaced
326: * by applying this template
327: * @since 3.1
328: */
329: protected final int getReplaceEndOffset() {
330: int end;
331: if (fContext instanceof DocumentTemplateContext) {
332: DocumentTemplateContext docContext = (DocumentTemplateContext) fContext;
333: end = docContext.getEnd();
334: } else {
335: end = fRegion.getOffset() + fRegion.getLength();
336: }
337: return end;
338: }
339:
340: /*
341: * @see ICompletionProposal#getSelection(IDocument)
342: */
343: public Point getSelection(IDocument document) {
344: return new Point(fSelectedRegion.getOffset(), fSelectedRegion
345: .getLength());
346: }
347:
348: /*
349: * @see ICompletionProposal#getAdditionalProposalInfo()
350: */
351: public String getAdditionalProposalInfo() {
352: try {
353: fContext.setReadOnly(true);
354: TemplateBuffer templateBuffer;
355: try {
356: templateBuffer = fContext.evaluate(fTemplate);
357: } catch (TemplateException e) {
358: return null;
359: }
360:
361: return templateBuffer.getString();
362:
363: } catch (BadLocationException e) {
364: return null;
365: }
366: }
367:
368: /*
369: * @see ICompletionProposal#getDisplayString()
370: */
371: public String getDisplayString() {
372: if (fDisplayString == null) {
373: String[] arguments = new String[] { fTemplate.getName(),
374: fTemplate.getDescription() };
375: fDisplayString = JFaceTextTemplateMessages
376: .getFormattedString(
377: "TemplateProposal.displayString", arguments); //$NON-NLS-1$
378: }
379: return fDisplayString;
380: }
381:
382: /*
383: * @see ICompletionProposal#getImage()
384: */
385: public Image getImage() {
386: return fImage;
387: }
388:
389: /*
390: * @see ICompletionProposal#getContextInformation()
391: */
392: public IContextInformation getContextInformation() {
393: return null;
394: }
395:
396: private void openErrorDialog(Shell shell, Exception e) {
397: MessageDialog
398: .openError(
399: shell,
400: JFaceTextTemplateMessages
401: .getString("TemplateProposal.errorDialog.title"), e.getMessage()); //$NON-NLS-1$
402: }
403:
404: /**
405: * Returns the relevance.
406: *
407: * @return the relevance
408: */
409: public int getRelevance() {
410: return fRelevance;
411: }
412:
413: /*
414: * @see org.eclipse.jface.text.contentassist.ICompletionProposalExtension3#getInformationControlCreator()
415: */
416: public IInformationControlCreator getInformationControlCreator() {
417: return fInformationControlCreator;
418: }
419:
420: /*
421: * @see org.eclipse.jface.text.contentassist.ICompletionProposalExtension2#selected(org.eclipse.jface.text.ITextViewer, boolean)
422: */
423: public void selected(ITextViewer viewer, boolean smartToggle) {
424: }
425:
426: /*
427: * @see org.eclipse.jface.text.contentassist.ICompletionProposalExtension2#unselected(org.eclipse.jface.text.ITextViewer)
428: */
429: public void unselected(ITextViewer viewer) {
430: }
431:
432: /*
433: * @see org.eclipse.jface.text.contentassist.ICompletionProposalExtension2#validate(org.eclipse.jface.text.IDocument, int, org.eclipse.jface.text.DocumentEvent)
434: */
435: public boolean validate(IDocument document, int offset,
436: DocumentEvent event) {
437: try {
438: int replaceOffset = getReplaceOffset();
439: if (offset >= replaceOffset) {
440: String content = document.get(replaceOffset, offset
441: - replaceOffset);
442: return fTemplate.getName().toLowerCase().startsWith(
443: content.toLowerCase());
444: }
445: } catch (BadLocationException e) {
446: // concurrent modification - ignore
447: }
448: return false;
449: }
450:
451: /*
452: * @see org.eclipse.jface.text.contentassist.ICompletionProposalExtension3#getPrefixCompletionText(org.eclipse.jface.text.IDocument, int)
453: */
454: public CharSequence getPrefixCompletionText(IDocument document,
455: int completionOffset) {
456: return fTemplate.getName();
457: }
458:
459: /*
460: * @see org.eclipse.jface.text.contentassist.ICompletionProposalExtension3#getPrefixCompletionStart(org.eclipse.jface.text.IDocument, int)
461: */
462: public int getPrefixCompletionStart(IDocument document,
463: int completionOffset) {
464: return getReplaceOffset();
465: }
466:
467: /*
468: * @see org.eclipse.jface.text.contentassist.ICompletionProposalExtension#apply(org.eclipse.jface.text.IDocument, char, int)
469: */
470: public void apply(IDocument document, char trigger, int offset) {
471: // not called any longer
472: }
473:
474: /*
475: * @see org.eclipse.jface.text.contentassist.ICompletionProposalExtension#isValidFor(org.eclipse.jface.text.IDocument, int)
476: */
477: public boolean isValidFor(IDocument document, int offset) {
478: // not called any longer
479: return false;
480: }
481:
482: /*
483: * @see org.eclipse.jface.text.contentassist.ICompletionProposalExtension#getTriggerCharacters()
484: */
485: public char[] getTriggerCharacters() {
486: // no triggers
487: return new char[0];
488: }
489:
490: /*
491: * @see org.eclipse.jface.text.contentassist.ICompletionProposalExtension#getContextInformationPosition()
492: */
493: public int getContextInformationPosition() {
494: return fRegion.getOffset();
495: }
496: }
|