001: /*******************************************************************************
002: * Copyright (c) 2006, 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.pde.internal.ui.editor.contentassist;
011:
012: import java.util.HashSet;
013: import java.util.Stack;
014:
015: import org.eclipse.core.runtime.CoreException;
016: import org.eclipse.core.runtime.IProgressMonitor;
017: import org.eclipse.jface.text.AbstractReusableInformationControlCreator;
018: import org.eclipse.jface.text.BadLocationException;
019: import org.eclipse.jface.text.IDocument;
020: import org.eclipse.jface.text.IInformationControl;
021: import org.eclipse.jface.text.IInformationControlCreator;
022: import org.eclipse.jface.text.ITextSelection;
023: import org.eclipse.jface.text.TextUtilities;
024: import org.eclipse.jface.text.contentassist.ICompletionProposal;
025: import org.eclipse.jface.text.contentassist.ICompletionProposalExtension3;
026: import org.eclipse.jface.text.contentassist.ICompletionProposalExtension5;
027: import org.eclipse.jface.text.contentassist.IContextInformation;
028: import org.eclipse.pde.core.IBaseModel;
029: import org.eclipse.pde.core.plugin.IPluginAttribute;
030: import org.eclipse.pde.core.plugin.IPluginBase;
031: import org.eclipse.pde.core.plugin.IPluginElement;
032: import org.eclipse.pde.core.plugin.IPluginExtension;
033: import org.eclipse.pde.core.plugin.IPluginModelBase;
034: import org.eclipse.pde.core.plugin.IPluginObject;
035: import org.eclipse.pde.core.plugin.IPluginParent;
036: import org.eclipse.pde.internal.core.ischema.ISchemaAttribute;
037: import org.eclipse.pde.internal.core.ischema.ISchemaElement;
038: import org.eclipse.pde.internal.core.ischema.ISchemaObject;
039: import org.eclipse.pde.internal.core.text.AbstractEditingModel;
040: import org.eclipse.pde.internal.core.text.IDocumentAttributeNode;
041: import org.eclipse.pde.internal.core.text.IDocumentElementNode;
042: import org.eclipse.pde.internal.core.text.IDocumentRange;
043: import org.eclipse.pde.internal.core.text.IReconcilingParticipant;
044: import org.eclipse.pde.internal.core.text.plugin.PluginAttribute;
045: import org.eclipse.pde.internal.ui.PDEPlugin;
046: import org.eclipse.pde.internal.ui.PDEUIMessages;
047: import org.eclipse.pde.internal.ui.editor.PDESourcePage;
048: import org.eclipse.pde.internal.ui.editor.contentassist.display.BrowserInformationControl;
049: import org.eclipse.pde.internal.ui.editor.text.HTMLPrinter;
050: import org.eclipse.pde.internal.ui.editor.text.XMLUtil;
051: import org.eclipse.pde.internal.ui.util.TextUtil;
052: import org.eclipse.swt.SWT;
053: import org.eclipse.swt.graphics.Image;
054: import org.eclipse.swt.graphics.Point;
055: import org.eclipse.swt.widgets.Shell;
056:
057: public class XMLCompletionProposal implements ICompletionProposal,
058: ICompletionProposalExtension5, ICompletionProposalExtension3 {
059:
060: private static final String F_DEF_ATTR_INDENT = " "; //$NON-NLS-1$
061:
062: private ISchemaObject fSchemaObject;
063: private IDocumentRange fRange;
064: private int fOffset;
065: private int fLen;
066: private int fSelOffset;
067: private int fSelLen;
068: private XMLContentAssistProcessor fProcessor;
069: private String fAddInfo;
070: private IInformationControlCreator fCreator;
071:
072: private IPluginParent fPluginParent;
073: private ISchemaElement fSchemaElement;
074:
075: public XMLCompletionProposal(IDocumentRange node,
076: ISchemaObject object, int offset,
077: XMLContentAssistProcessor processor) {
078: fLen = -1;
079: fSelOffset = -1;
080: fSelLen = 0;
081: fRange = node;
082: fSchemaObject = object;
083: fOffset = offset;
084: fProcessor = processor;
085: }
086:
087: public void apply(IDocument document) {
088: ITextSelection sel = fProcessor.getCurrentSelection();
089: if (sel == null) {
090: return;
091: }
092: fLen = sel.getLength() + sel.getOffset() - fOffset;
093: String delim = TextUtilities.getDefaultLineDelimiter(document);
094: StringBuffer documentInsertBuffer = new StringBuffer();
095: boolean doInternalWork = false;
096: // Generate the text to apply depending on the proposal type
097: if (fSchemaObject instanceof ISchemaAttribute) {
098: applyAttribute(documentInsertBuffer);
099: } else if (fSchemaObject instanceof ISchemaElement) {
100: applyElement(getIndent(document, fOffset), delim,
101: documentInsertBuffer);
102: doInternalWork = true;
103: } else if (fSchemaObject instanceof VirtualSchemaObject) {
104: doInternalWork = applyVirtual(document, sel, delim,
105: documentInsertBuffer, doInternalWork);
106: }
107: // Check if there is anything to apply
108: if (documentInsertBuffer.length() == 0) {
109: return;
110: }
111: // Apply the proposal to the document
112: try {
113: document.replace(fOffset, fLen, documentInsertBuffer
114: .toString());
115: } catch (BadLocationException e) {
116: PDEPlugin.log(e);
117: }
118: // Update the model if necessary
119: if (doInternalWork) {
120: modifyModel(document);
121: }
122: }
123:
124: /**
125: * @param document
126: * @param sel
127: * @param delim
128: * @param documentInsertBuffer
129: * @param doInternalWork
130: * @return
131: */
132: private boolean applyVirtual(IDocument document,
133: ITextSelection sel, String delim,
134: StringBuffer documentInsertBuffer, boolean doInternalWork) {
135: int type = ((VirtualSchemaObject) fSchemaObject).getVType();
136: switch (type) {
137: case XMLContentAssistProcessor.F_ATTRIBUTE:
138: applyAttribute(documentInsertBuffer);
139: break;
140: case XMLContentAssistProcessor.F_CLOSE_TAG:
141: fOffset = sel.getOffset();
142: fLen = 0;
143: documentInsertBuffer.append(" />"); //$NON-NLS-1$
144: break;
145: case XMLContentAssistProcessor.F_EXTENSION:
146: applyExtension(document, delim, documentInsertBuffer);
147: break;
148: case XMLContentAssistProcessor.F_EXTENSION_POINT:
149: applyExtensionPoint(documentInsertBuffer);
150: break;
151: case XMLContentAssistProcessor.F_EXTENSION_POINT_AND_VALUE:
152: doInternalWork = true; // we will want to add required child nodes/attributes
153: applyExtensionFullPoint(document, delim,
154: documentInsertBuffer);
155: break;
156: case XMLContentAssistProcessor.F_EXTENSION_ATTRIBUTE_POINT_VALUE:
157: doInternalWork = true; // we will want to add required child nodes/attributes
158: case XMLContentAssistProcessor.F_ATTRIBUTE_VALUE:
159: applyAttributeValue(document, documentInsertBuffer);
160: break;
161: }
162: return doInternalWork;
163: }
164:
165: /**
166: * @param document
167: * @param documentInsertBuffer
168: */
169: private void applyAttributeValue(IDocument document,
170: StringBuffer documentInsertBuffer) {
171: if (fRange instanceof IDocumentAttributeNode) {
172: fOffset = ((IDocumentAttributeNode) fRange)
173: .getValueOffset();
174: String value = fSchemaObject.getName();
175: try {
176: // add indentation
177: int off = fOffset;
178: int docLen = document.getLength();
179: fLen = 0;
180: while (off < docLen) {
181: char c = document.getChar(off++);
182: if (c == '"')
183: break;
184: fLen += 1;
185: }
186: } catch (BadLocationException e) {
187: }
188: documentInsertBuffer.append(value);
189: fSelOffset = fOffset + value.length();
190: }
191: }
192:
193: /**
194: * @param documentInsertBuffer
195: */
196: private void applyExtensionPoint(StringBuffer documentInsertBuffer) {
197: String id = "id"; //$NON-NLS-1$
198: documentInsertBuffer.append("<extension-point id=\""); //$NON-NLS-1$
199: fSelOffset = fOffset + documentInsertBuffer.length();
200: fSelLen = id.length();
201: documentInsertBuffer.append(id);
202: documentInsertBuffer.append("\" name=\"name\" />"); //$NON-NLS-1$
203: }
204:
205: /**
206: * @param document
207: * @param delim
208: * @param documentInsertBuffer
209: */
210: private void applyExtension(IDocument document, String delim,
211: StringBuffer documentInsertBuffer) {
212: documentInsertBuffer.append("<extension"); //$NON-NLS-1$
213: documentInsertBuffer.append(delim);
214: String indent = getIndent(document, fOffset);
215: documentInsertBuffer.append(indent);
216: documentInsertBuffer.append(F_DEF_ATTR_INDENT);
217: documentInsertBuffer.append("point=\"\">"); //$NON-NLS-1$
218: fSelOffset = fOffset + documentInsertBuffer.length() - 2; // position rigth inside new point="" attribute
219: documentInsertBuffer.append(delim);
220: documentInsertBuffer.append(indent);
221: documentInsertBuffer.append("</extension>"); //$NON-NLS-1$
222: }
223:
224: /**
225: * @param document
226: * @param delim
227: * @param documentInsertBuffer
228: */
229: private void applyExtensionFullPoint(IDocument document,
230: String delim, StringBuffer documentInsertBuffer) {
231:
232: String pointID = fSchemaObject.getName();
233: String indent = getIndent(document, fOffset);
234: // Add extension mark-up to the buffer right up until the point
235: // attribute value
236: documentInsertBuffer.append('<');
237: documentInsertBuffer.append("extension"); //$NON-NLS-1$
238: documentInsertBuffer.append(delim);
239: documentInsertBuffer.append(indent);
240: documentInsertBuffer.append(F_DEF_ATTR_INDENT);
241: documentInsertBuffer.append("point"); //$NON-NLS-1$
242: documentInsertBuffer.append('=');
243: documentInsertBuffer.append('"');
244: // Calculate the offset for the start of the selection
245: // We want to select the point attribute value in between the quotes
246: // fOffset is the point where content assist was first invoked
247: fSelOffset = fOffset + documentInsertBuffer.length();
248: // Calculate the selection length
249: fSelLen = pointID.length();
250: // Add extension mark-up to the buffer including the point attribute
251: // value and beyond
252: documentInsertBuffer.append(pointID);
253: documentInsertBuffer.append('"');
254: documentInsertBuffer.append('>');
255: documentInsertBuffer.append(delim);
256: documentInsertBuffer.append(indent);
257: documentInsertBuffer.append('<');
258: documentInsertBuffer.append('/');
259: documentInsertBuffer.append("extension"); //$NON-NLS-1$
260: documentInsertBuffer.append('>');
261: }
262:
263: /**
264: * @param document
265: * @param delim
266: * @param sb
267: */
268: private void applyElement(String indent, String delim,
269: StringBuffer documentInsertBuffer) {
270: documentInsertBuffer.append('<');
271: documentInsertBuffer.append(((ISchemaElement) fSchemaObject)
272: .getName());
273: documentInsertBuffer.append('>');
274: documentInsertBuffer.append(delim);
275: documentInsertBuffer.append(indent);
276: documentInsertBuffer.append('<');
277: documentInsertBuffer.append('/');
278: documentInsertBuffer.append(((ISchemaElement) fSchemaObject)
279: .getName());
280: documentInsertBuffer.append('>');
281: }
282:
283: /**
284: * @param sb
285: */
286: private void applyAttribute(StringBuffer documentInsertBuffer) {
287: if (fRange == null) {
288: // Model is broken
289: // Manually adjust offsets
290: fLen -= 1;
291: fOffset += 1;
292: }
293: String attName = fSchemaObject.getName();
294: documentInsertBuffer.append(attName);
295: documentInsertBuffer.append("=\""); //$NON-NLS-1$
296: fSelOffset = fOffset + documentInsertBuffer.length();
297: String value = attName; //$NON-NLS-1$
298: if (fSchemaObject instanceof ISchemaAttribute) {
299: value = XMLInsertionComputer.generateAttributeValue(
300: (ISchemaAttribute) fSchemaObject, fProcessor
301: .getModel(), attName);
302: }
303: documentInsertBuffer.append(value);
304: fSelLen = value.length();
305: documentInsertBuffer.append('"');
306: }
307:
308: private void modifyModel(IDocument document) {
309: // TODO requires
310: // - refactoring
311: // - better grouping of cases (if statements)
312: IBaseModel model = fProcessor.getModel();
313: if (model instanceof IReconcilingParticipant)
314: ((IReconcilingParticipant) model).reconciled(document);
315:
316: if (model instanceof IPluginModelBase) {
317: IPluginBase base = ((IPluginModelBase) model)
318: .getPluginBase();
319:
320: fPluginParent = null;
321: fSchemaElement = null;
322:
323: if (fSchemaObject instanceof VirtualSchemaObject) {
324: switch (((VirtualSchemaObject) fSchemaObject)
325: .getVType()) {
326: case XMLContentAssistProcessor.F_EXTENSION_ATTRIBUTE_POINT_VALUE:
327: if (!(fRange instanceof IDocumentAttributeNode))
328: break;
329: int offset = ((IDocumentAttributeNode) fRange)
330: .getEnclosingElement().getOffset();
331: IPluginExtension[] extensions = base
332: .getExtensions();
333: for (int i = 0; i < extensions.length; i++) {
334: if (((IDocumentElementNode) extensions[i])
335: .getOffset() == offset) {
336: if (extensions[i].getChildCount() != 0)
337: break; // don't modify existing extensions
338: fPluginParent = extensions[i];
339: fSchemaElement = XMLUtil
340: .getSchemaElement(
341: (IDocumentElementNode) extensions[i],
342: extensions[i].getPoint());
343: break;
344: }
345: }
346: break;
347: case XMLContentAssistProcessor.F_EXTENSION_POINT_AND_VALUE:
348: findExtensionVirtualPointValue(base);
349: break;
350: }
351: } else if (fRange instanceof IDocumentElementNode
352: && base instanceof IDocumentElementNode) {
353: Stack s = new Stack();
354: IDocumentElementNode node = (IDocumentElementNode) fRange;
355: IDocumentElementNode newSearch = (IDocumentElementNode) base;
356: // traverse up old model, pushing all nodes onto the stack along the way
357: while (node != null && !(node instanceof IPluginBase)) {
358: s.push(node);
359: node = node.getParentNode();
360: }
361:
362: // traverse down new model to find new node, using stack as a guideline
363: while (!s.isEmpty()) {
364: node = (IDocumentElementNode) s.pop();
365: int nodeIndex = 0;
366: while ((node = node.getPreviousSibling()) != null)
367: nodeIndex += 1;
368: newSearch = newSearch.getChildAt(nodeIndex);
369: }
370: if (newSearch != null) {
371: IDocumentElementNode[] children = newSearch
372: .getChildNodes();
373: for (int i = 0; i < children.length; i++) {
374: if (children[i].getOffset() == fOffset
375: && children[i] instanceof IPluginElement) {
376: fPluginParent = (IPluginElement) children[i];
377: fSchemaElement = (ISchemaElement) fSchemaObject;
378: break;
379: }
380: }
381: }
382: }
383:
384: if (fPluginParent != null && fSchemaElement != null) {
385: XMLInsertionComputer.computeInsertion(fSchemaElement,
386: fPluginParent);
387: fProcessor.flushDocument();
388: if (model instanceof AbstractEditingModel) {
389: try {
390: ((AbstractEditingModel) model)
391: .adjustOffsets(document);
392: } catch (CoreException e) {
393: }
394: setSelectionOffsets(document, fSchemaElement,
395: fPluginParent);
396: }
397: }
398: }
399: }
400:
401: /**
402: * Assumption: Model already reconciled by caller
403: * @param base
404: */
405: private void findExtensionVirtualPointValue(IPluginBase base) {
406:
407: IDocumentRange range = null;
408: PDESourcePage page = fProcessor.getSourcePage();
409: // Ensure page is defined
410: if (page == null) {
411: return;
412: }
413: // When we inserted the extension element and extension point attribute
414: // name and value, we selected the point value
415: // Find the corresponding range in order to add child elements to
416: // the proper extension.
417: range = page.getRangeElement(fOffset, true);
418: // Ensure the range is an attribute
419: if ((range == null)
420: || (range instanceof IDocumentElementNode) == false) {
421: return;
422: }
423: // Get the offset of the extension element
424: int targetOffset = ((IDocumentElementNode) range).getOffset();
425: // Search this plug-ins extensions for the proper one
426: IPluginExtension[] extensions = base.getExtensions();
427: for (int i = 0; i < extensions.length; i++) {
428: // Get the offset of the current extension
429: int extensionOffset = ((IDocumentElementNode) extensions[i])
430: .getOffset();
431: // If the offsets match we foudn the extension element
432: // Note: The extension element should have no children
433: if ((extensionOffset == targetOffset)
434: && (extensions[i].getChildCount() == 0)) {
435: fPluginParent = extensions[i];
436: // Get the corresponding schema element
437: fSchemaElement = XMLUtil.getSchemaElement(
438: (IDocumentElementNode) extensions[i],
439: extensions[i].getPoint());
440: break;
441: }
442: }
443: }
444:
445: private void setSelectionOffsets(IDocument document,
446: ISchemaElement schemaElement, IPluginParent pluginParent) {
447: if (pluginParent instanceof IPluginExtension) {
448: String point = ((IPluginExtension) pluginParent).getPoint();
449: IPluginObject[] children = ((IPluginExtension) pluginParent)
450: .getChildren();
451: if (children != null && children.length > 0
452: && children[0] instanceof IPluginParent) {
453: pluginParent = (IPluginParent) children[0];
454: schemaElement = XMLUtil.getSchemaElement(
455: (IDocumentElementNode) pluginParent, point);
456: }
457: }
458:
459: if (pluginParent instanceof IPluginElement) {
460: int offset = ((IDocumentElementNode) pluginParent)
461: .getOffset();
462: int len = ((IDocumentElementNode) pluginParent).getLength();
463: String value = null;
464: try {
465: value = document.get(offset, len);
466: } catch (BadLocationException e) {
467: }
468: if (((IPluginElement) pluginParent).getAttributeCount() > 0) {
469: // Select value of first required attribute
470: IPluginAttribute att = ((IPluginElement) pluginParent)
471: .getAttributes()[0];
472: if (att instanceof PluginAttribute) {
473: fSelOffset = ((PluginAttribute) att)
474: .getValueOffset();
475: fSelLen = ((PluginAttribute) att).getValueLength();
476: }
477: } else if (XMLInsertionComputer.hasOptionalChildren(
478: schemaElement, false, new HashSet())
479: && value != null) {
480: int ind = value.indexOf('>');
481: if (ind > 0) {
482: fSelOffset = offset + ind + 1;
483: fSelLen = 0;
484: }
485: } else if (XMLInsertionComputer
486: .hasOptionalAttributes(schemaElement)
487: && value != null) {
488: int ind = value.indexOf('>');
489: if (ind != -1) {
490: fSelOffset = offset + ind;
491: fSelLen = 0;
492: }
493: } else {
494: // position caret after element
495: fSelOffset = offset + len;
496: fSelLen = 0;
497: }
498: }
499: }
500:
501: private String getIndent(IDocument document, int offset) {
502: StringBuffer indBuff = new StringBuffer();
503: try {
504: // add indentation
505: int line = document.getLineOfOffset(offset);
506: int lineOffset = document.getLineOffset(line);
507: int indent = offset - lineOffset;
508: char[] indentChars = document.get(lineOffset, indent)
509: .toCharArray();
510: // for every tab append a tab, for anything else append a space
511: for (int i = 0; i < indentChars.length; i++)
512: indBuff.append(indentChars[i] == '\t' ? '\t' : ' ');
513: } catch (BadLocationException e) {
514: }
515: return indBuff.toString();
516: }
517:
518: public String getAdditionalProposalInfo() {
519: if (fAddInfo == null) {
520: if (fSchemaObject == null)
521: return null;
522: StringBuffer sb = new StringBuffer();
523: HTMLPrinter.insertPageProlog(sb, 0, TextUtil
524: .getJavaDocStyleSheerURL());
525: String desc = null;
526: if (fSchemaObject == null)
527: desc = PDEUIMessages.BaseWizardSelectionPage_noDesc;
528: else {
529: desc = fSchemaObject.getDescription();
530: if (desc == null || desc.trim().length() == 0)
531: desc = PDEUIMessages.BaseWizardSelectionPage_noDesc;
532: }
533: sb.append(desc);
534: HTMLPrinter.addPageEpilog(sb);
535: fAddInfo = sb.toString();
536: }
537: return fAddInfo;
538: }
539:
540: public IContextInformation getContextInformation() {
541: return null;
542: }
543:
544: public String getDisplayString() {
545: if (fSchemaObject instanceof VirtualSchemaObject) {
546: switch (((VirtualSchemaObject) fSchemaObject).getVType()) {
547: case XMLContentAssistProcessor.F_CLOSE_TAG:
548: return "... />"; //$NON-NLS-1$
549: case XMLContentAssistProcessor.F_EXTENSION_POINT_AND_VALUE:
550: case XMLContentAssistProcessor.F_EXTENSION_ATTRIBUTE_POINT_VALUE:
551: case XMLContentAssistProcessor.F_ATTRIBUTE_VALUE:
552: return fSchemaObject.getName();
553: }
554: }
555: if (fSchemaObject instanceof ISchemaAttribute)
556: return fSchemaObject.getName();
557: if (fSchemaObject != null)
558: return fSchemaObject.getName();
559: if (fRange instanceof IDocumentElementNode)
560: return "...> </" + ((IDocumentElementNode) fRange).getXMLTagName() + ">"; //$NON-NLS-1$ //$NON-NLS-2$
561: return null;
562: }
563:
564: public Image getImage() {
565: if (fSchemaObject instanceof VirtualSchemaObject)
566: return fProcessor
567: .getImage(((VirtualSchemaObject) fSchemaObject)
568: .getVType());
569: if (fSchemaObject instanceof ISchemaAttribute)
570: return fProcessor
571: .getImage(XMLContentAssistProcessor.F_ATTRIBUTE);
572: if (fSchemaObject instanceof ISchemaElement
573: || fSchemaObject == null)
574: return fProcessor
575: .getImage(XMLContentAssistProcessor.F_ELEMENT);
576: return null;
577: }
578:
579: public Point getSelection(IDocument document) {
580: if (fSelOffset == -1)
581: return null;
582: return new Point(fSelOffset, fSelLen);
583: }
584:
585: public Object getAdditionalProposalInfo(IProgressMonitor monitor) {
586: return getAdditionalProposalInfo();
587: }
588:
589: public IInformationControlCreator getInformationControlCreator() {
590: if (!BrowserInformationControl.isAvailable(null))
591: return null;
592:
593: if (fCreator == null) {
594: fCreator = new AbstractReusableInformationControlCreator() {
595: public IInformationControl doCreateInformationControl(
596: Shell parent) {
597: return new BrowserInformationControl(parent,
598: SWT.NO_TRIM | SWT.TOOL, SWT.NONE);
599: }
600: };
601: }
602: return fCreator;
603: }
604:
605: public int getPrefixCompletionStart(IDocument document,
606: int completionOffset) {
607: return 0;
608: }
609:
610: public CharSequence getPrefixCompletionText(IDocument document,
611: int completionOffset) {
612: return null;
613: }
614:
615: }
|