/******************************************************************************* * Copyright (c) 2010 IBM Corporation and others. * All rights reserved. This program and the accompanying materials * are made available under the terms of the Eclipse Public License v1.0 * which accompanies this distribution, and is available at * http://www.eclipse.org/legal/epl-v10.html * * Contributors: * IBM Corporation - initial API and implementation *******************************************************************************/ package org.eclipse.wst.css.ui.internal.contentassist; import java.util.Arrays; import java.util.List; import org.eclipse.core.runtime.IProgressMonitor; import org.eclipse.jface.text.BadLocationException; import org.eclipse.jface.text.ITextSelection; import org.eclipse.jface.text.ITextViewer; import org.eclipse.jface.text.contentassist.ICompletionProposal; import org.eclipse.wst.css.core.internal.Logger; import org.eclipse.wst.css.core.internal.provisional.adapters.ICSSModelAdapter; import org.eclipse.wst.css.core.internal.provisional.document.ICSSDocument; import org.eclipse.wst.css.core.internal.provisional.document.ICSSModel; import org.eclipse.wst.css.core.internal.provisional.document.ICSSNode; import org.eclipse.wst.html.core.internal.htmlcss.StyleAdapterFactory; import org.eclipse.wst.sse.core.StructuredModelManager; import org.eclipse.wst.sse.core.internal.provisional.INodeAdapter; import org.eclipse.wst.sse.core.internal.provisional.IStructuredModel; import org.eclipse.wst.sse.core.internal.provisional.IndexedRegion; import org.eclipse.wst.sse.core.internal.provisional.text.IStructuredDocumentRegion; import org.eclipse.wst.sse.core.internal.provisional.text.ITextRegion; import org.eclipse.wst.sse.ui.contentassist.CompletionProposalInvocationContext; import org.eclipse.wst.sse.ui.contentassist.ICompletionProposalComputer; import org.eclipse.wst.sse.ui.internal.contentassist.ContentAssistUtils; import org.eclipse.wst.xml.core.internal.provisional.document.IDOMNode; import org.eclipse.wst.xml.ui.internal.contentassist.XMLContentAssistUtilities; import org.eclipse.wst.xml.ui.internal.util.SharedXMLEditorPluginImageHelper; /** * <p>Completion computer for CSS</p> */ public class CSSCompletionProposalComputer implements ICompletionProposalComputer { /** * @see org.eclipse.wst.sse.ui.contentassist.ICompletionProposalComputer#computeCompletionProposals(org.eclipse.wst.sse.ui.contentassist.CompletionProposalInvocationContext, org.eclipse.core.runtime.IProgressMonitor) */ public List computeCompletionProposals(CompletionProposalInvocationContext context, IProgressMonitor monitor) { ITextViewer viewer = context.getViewer(); int documentPosition = context.getInvocationOffset(); IndexedRegion indexedNode = ContentAssistUtils.getNodeAt(viewer, documentPosition); IDOMNode xNode = null; IDOMNode parent = null; CSSProposalArranger arranger = null; // If there is a selected region, we'll need to replace the text ITextSelection selection = (ITextSelection) viewer.getSelectionProvider().getSelection(); // bail if we couldn't get an indexed node // if(indexedNode == null) return new ICompletionProposal[0]; if (indexedNode instanceof IDOMNode) { xNode = (IDOMNode) indexedNode; parent = (IDOMNode) xNode.getParentNode(); } // need to get in here if there in the no 0 region <style>|</style> // case if ((xNode != null) && xNode.getNodeName().equalsIgnoreCase(HTML40Namespace.ElementName.STYLE)) { // now we know the cursor is in a <style> tag w/out region IStructuredModel cssModel = getCSSModel(xNode); if (cssModel != null) { // adjust offsets for embedded style int offset = documentPosition; int pos = 0; IndexedRegion keyIndexedNode = cssModel.getIndexedRegion(pos); if (keyIndexedNode == null) { keyIndexedNode = (IndexedRegion) ((ICSSModel) cssModel).getDocument(); } arranger = new CSSProposalArranger(pos, (ICSSNode) keyIndexedNode, offset, selection.getLength(), (char) 0); } } else if ((parent != null) && parent.getNodeName().equalsIgnoreCase(HTML40Namespace.ElementName.STYLE)) { // now we know the cursor is in a <style> tag with a region // use the parent because that will be the <style> tag IStructuredModel cssModel = getCSSModel(parent); if (cssModel != null) { // adjust offsets for embedded style int offset = indexedNode.getStartOffset(); int pos = documentPosition - offset; IndexedRegion keyIndexedNode = cssModel.getIndexedRegion(pos); if (keyIndexedNode == null) { keyIndexedNode = (IndexedRegion) ((ICSSModel) cssModel).getDocument(); } arranger = new CSSProposalArranger(pos, (ICSSNode) keyIndexedNode, offset, selection.getLength(), (char) 0); } } else if (indexedNode instanceof IDOMNode) { IDOMNode domNode = ((IDOMNode)indexedNode); // get model for node w/ style attribute IStructuredModel cssModel = getCSSModel(domNode); if (cssModel != null) { // adjust offsets for embedded style int textRegionStartOffset = getTextRegionStartOffset(domNode, documentPosition); int pos = documentPosition - textRegionStartOffset; char quote = (char) 0; try { quote = context.getDocument().get(textRegionStartOffset, 1).charAt(0); } catch (BadLocationException e) { Logger.logException("error getting quote character", e); } //get css indexed region IndexedRegion cssIndexedNode = cssModel.getIndexedRegion(pos); if (cssIndexedNode == null) { cssIndexedNode = (IndexedRegion) ((ICSSModel) cssModel).getDocument(); } if (cssIndexedNode instanceof ICSSNode) { // inline style for a tag, not embedded arranger = new CSSProposalArranger(pos, (ICSSNode) cssIndexedNode, textRegionStartOffset, selection.getLength(), quote); } } } else if (indexedNode instanceof ICSSNode) { // when editing external CSS using CSS Designer, ICSSNode is passed. ICSSDocument cssdoc = ((ICSSNode) indexedNode).getOwnerDocument(); if (cssdoc != null) { IStructuredModel cssModel = cssdoc.getModel(); if (cssModel != null) { IndexedRegion keyIndexedNode = cssModel.getIndexedRegion(documentPosition); if (keyIndexedNode == null) { keyIndexedNode = (IndexedRegion) ((ICSSModel) cssModel).getDocument(); } if (keyIndexedNode instanceof ICSSNode) { // inline style for a tag, not embedded arranger = new CSSProposalArranger(documentPosition, (ICSSNode) keyIndexedNode, 0, selection.getLength(), (char)0); } } } } else if ((indexedNode == null) && ContentAssistUtils.isViewerEmpty(viewer)) { // the top of empty CSS Document IStructuredModel cssModel = null; try { cssModel = StructuredModelManager.getModelManager().getExistingModelForRead(viewer.getDocument()); if (cssModel instanceof ICSSModel) { IndexedRegion keyIndexedNode = cssModel.getIndexedRegion(documentPosition); if (keyIndexedNode == null) { keyIndexedNode = (IndexedRegion) ((ICSSModel) cssModel).getDocument(); } if (keyIndexedNode instanceof ICSSNode) { // inline style for a tag, not embedded arranger = new CSSProposalArranger(documentPosition, (ICSSNode) keyIndexedNode, 0, (char)0); } } } finally { if (cssModel != null) cssModel.releaseFromRead(); } } ICompletionProposal[] proposals = new ICompletionProposal[0]; if (arranger != null) { proposals = arranger.getProposals(); ICompletionProposal[] newfileproposals = new ICompletionProposal[0]; ICompletionProposal[] anyproposals = new ICompletionProposal[0]; // add end tag if parent is not closed ICompletionProposal endTag = XMLContentAssistUtilities.computeXMLEndTagProposal(viewer, documentPosition, indexedNode, HTML40Namespace.ElementName.STYLE, SharedXMLEditorPluginImageHelper.IMG_OBJ_TAG_GENERIC); // add the additional proposals int additionalLength = newfileproposals.length + anyproposals.length; additionalLength = (endTag != null) ? ++additionalLength : additionalLength; if (additionalLength > 0) { ICompletionProposal[] plusOnes = new ICompletionProposal[proposals.length + additionalLength]; int appendPos = proposals.length; // add end tag proposal if (endTag != null) { System.arraycopy(proposals, 0, plusOnes, 1, proposals.length); plusOnes[0] = endTag; ++appendPos; } else { System.arraycopy(proposals, 0, plusOnes, 0, proposals.length); } // add items in newfileproposals for (int i = 0; i < newfileproposals.length; ++i) { plusOnes[appendPos + i] = newfileproposals[i]; } // add items in anyproposals appendPos = appendPos + newfileproposals.length; for (int i = 0; i < anyproposals.length; ++i) { plusOnes[appendPos + i] = anyproposals[i]; } proposals = plusOnes; } } return Arrays.asList(proposals); } /** * @see org.eclipse.wst.sse.ui.contentassist.ICompletionProposalComputer#computeContextInformation(org.eclipse.wst.sse.ui.contentassist.CompletionProposalInvocationContext, org.eclipse.core.runtime.IProgressMonitor) */ public List computeContextInformation( CompletionProposalInvocationContext context, IProgressMonitor monitor) { // TODO Auto-generated method stub return null; } /** * @see org.eclipse.wst.sse.ui.contentassist.ICompletionProposalComputer#getErrorMessage() */ public String getErrorMessage() { // TODO Auto-generated method stub return null; } /** * @see org.eclipse.wst.sse.ui.contentassist.ICompletionProposalComputer#sessionStarted() */ public void sessionStarted() { //default is to do nothing } /** * @see org.eclipse.wst.sse.ui.contentassist.ICompletionProposalComputer#sessionEnded() */ public void sessionEnded() { //default is to do nothing } /** * Returns the CSSmodel for a given XML node. * * @param element * @return IStructuredModel */ private static IStructuredModel getCSSModel(IDOMNode element) { if (element == null) { return null; } INodeAdapter adapter = StyleAdapterFactory.getInstance().adapt(element); if ((adapter == null) || !(adapter instanceof ICSSModelAdapter)) { return null; } ICSSModelAdapter modelAdapter = (ICSSModelAdapter) adapter; return modelAdapter.getModel(); } /** * <p>Get the start offset of the text region containing the given document position</p> * * @param domNode {@link IDOMNode} containing the document position * @param documentPosition the document relative position to get the start * offset of the containing {@link ITextRegion} for * @return start offset of the {@link ITextRegion} containing the given document position */ private static int getTextRegionStartOffset(IDOMNode domNode, int documentPosition) { IStructuredDocumentRegion structRegion = domNode.getFirstStructuredDocumentRegion(); return structRegion.getStartOffset(structRegion.getRegionAtCharacterOffset(documentPosition)); } }