nitind | 958d79a | 2004-11-23 19:23:00 +0000 | [diff] [blame] | 1 | /***************************************************************************** |
| 2 | * Copyright (c) 2004 IBM Corporation and others. All rights reserved. This |
| 3 | * program and the accompanying materials are made available under the terms |
| 4 | * of the Eclipse Public License v1.0 which accompanies this distribution, and |
| 5 | * is available at http://www.eclipse.org/legal/epl-v10.html |
| 6 | * |
| 7 | * Contributors: IBM Corporation - initial API and implementation |
| 8 | ****************************************************************************/ |
david_williams | aaadf2b | 2005-04-11 07:28:31 +0000 | [diff] [blame] | 9 | package org.eclipse.wst.css.ui.internal.contentassist; |
nitind | 958d79a | 2004-11-23 19:23:00 +0000 | [diff] [blame] | 10 | |
| 11 | |
| 12 | |
| 13 | import org.eclipse.jface.text.ITextViewer; |
| 14 | import org.eclipse.jface.text.contentassist.ICompletionProposal; |
| 15 | import org.eclipse.jface.text.contentassist.IContentAssistProcessor; |
david_williams | 63219a2 | 2005-04-10 01:59:51 +0000 | [diff] [blame] | 16 | import org.eclipse.wst.css.core.internal.provisional.adapters.ICSSModelAdapter; |
| 17 | import org.eclipse.wst.css.core.internal.provisional.document.ICSSDocument; |
| 18 | import org.eclipse.wst.css.core.internal.provisional.document.ICSSModel; |
| 19 | import org.eclipse.wst.css.core.internal.provisional.document.ICSSNode; |
nitind | 057e5c8 | 2005-06-16 04:12:26 +0000 | [diff] [blame^] | 20 | import org.eclipse.wst.css.ui.internal.templates.TemplateContextTypeIdsCSS; |
david_williams | aaadf2b | 2005-04-11 07:28:31 +0000 | [diff] [blame] | 21 | import org.eclipse.wst.html.core.internal.htmlcss.StyleAdapterFactory; |
david_williams | 4ad020f | 2005-04-18 08:00:30 +0000 | [diff] [blame] | 22 | import org.eclipse.wst.sse.core.internal.provisional.INodeAdapter; |
| 23 | import org.eclipse.wst.sse.core.internal.provisional.IStructuredModel; |
| 24 | import org.eclipse.wst.sse.core.internal.provisional.IndexedRegion; |
| 25 | import org.eclipse.wst.sse.core.internal.provisional.StructuredModelManager; |
pavery | 2f6b6fb | 2005-03-29 21:09:57 +0000 | [diff] [blame] | 26 | import org.eclipse.wst.sse.ui.internal.StructuredTextViewer; |
nitind | 958d79a | 2004-11-23 19:23:00 +0000 | [diff] [blame] | 27 | import org.eclipse.wst.sse.ui.internal.contentassist.ContentAssistUtils; |
david_williams | 4ad020f | 2005-04-18 08:00:30 +0000 | [diff] [blame] | 28 | import org.eclipse.wst.xml.core.internal.provisional.document.IDOMNode; |
david_williams | 12ff79e | 2005-04-13 13:59:30 +0000 | [diff] [blame] | 29 | import org.eclipse.wst.xml.ui.internal.contentassist.XMLContentAssistUtilities; |
david_williams | f3680f0 | 2005-04-13 22:43:54 +0000 | [diff] [blame] | 30 | import org.eclipse.wst.xml.ui.internal.util.SharedXMLEditorPluginImageHelper; |
nitind | 958d79a | 2004-11-23 19:23:00 +0000 | [diff] [blame] | 31 | |
| 32 | public class CSSContentAssistProcessor implements IContentAssistProcessor { |
| 33 | |
| 34 | private int fDocumentOffset = 0; |
| 35 | private char fQuote = 0; |
nitind | 057e5c8 | 2005-06-16 04:12:26 +0000 | [diff] [blame^] | 36 | private CSSTemplateCompletionProcessor fTemplateProcessor = null; |
nitind | 958d79a | 2004-11-23 19:23:00 +0000 | [diff] [blame] | 37 | |
| 38 | /** |
| 39 | * Return a list of proposed code completions based on the specified |
| 40 | * location within the document that corresponds to the current cursor |
| 41 | * position within the text-editor control. |
| 42 | * |
| 43 | * @param documentPosition |
| 44 | * a location within the document |
| 45 | * @return an array of code-assist items |
| 46 | */ |
| 47 | public ICompletionProposal[] computeCompletionProposals(ITextViewer viewer, int documentPosition) { |
| 48 | |
| 49 | IndexedRegion indexedNode = ContentAssistUtils.getNodeAt((StructuredTextViewer) viewer, documentPosition + fDocumentOffset); |
david_williams | c39caaf | 2005-04-05 06:07:16 +0000 | [diff] [blame] | 50 | IDOMNode xNode = null; |
| 51 | IDOMNode parent = null; |
nitind | 958d79a | 2004-11-23 19:23:00 +0000 | [diff] [blame] | 52 | CSSProposalArranger arranger = null; |
nitind | 057e5c8 | 2005-06-16 04:12:26 +0000 | [diff] [blame^] | 53 | boolean isEmptyDocument = false; |
nitind | 958d79a | 2004-11-23 19:23:00 +0000 | [diff] [blame] | 54 | |
| 55 | // bail if we couldn't get an indexed node |
david_williams | cc02363 | 2005-03-08 02:56:22 +0000 | [diff] [blame] | 56 | // if(indexedNode == null) return new ICompletionProposal[0]; |
david_williams | c39caaf | 2005-04-05 06:07:16 +0000 | [diff] [blame] | 57 | if (indexedNode instanceof IDOMNode) { |
| 58 | xNode = (IDOMNode) indexedNode; |
| 59 | parent = (IDOMNode) xNode.getParentNode(); |
nitind | 958d79a | 2004-11-23 19:23:00 +0000 | [diff] [blame] | 60 | } |
| 61 | // need to get in here if there in the no 0 region <style>|</style> |
| 62 | // case |
| 63 | if (xNode != null && xNode.getNodeName().equalsIgnoreCase(HTML40Namespace.ElementName.STYLE)) { |
| 64 | // now we know the cursor is in a <style> tag w/out region |
david_williams | cc02363 | 2005-03-08 02:56:22 +0000 | [diff] [blame] | 65 | IStructuredModel cssModel = getCSSModel(xNode); |
| 66 | if (cssModel != null) { |
| 67 | // adjust offsets for embedded style |
| 68 | int offset = documentPosition; |
| 69 | int pos = 0; |
| 70 | IndexedRegion keyIndexedNode = cssModel.getIndexedRegion(pos); |
| 71 | if (keyIndexedNode == null) { |
| 72 | keyIndexedNode = (IndexedRegion) ((ICSSModel) cssModel).getDocument(); |
| 73 | } |
| 74 | arranger = new CSSProposalArranger(pos, (ICSSNode) keyIndexedNode, offset, (char) 0); |
nitind | 958d79a | 2004-11-23 19:23:00 +0000 | [diff] [blame] | 75 | } |
nitind | 057e5c8 | 2005-06-16 04:12:26 +0000 | [diff] [blame^] | 76 | } else if (parent != null && parent.getNodeName().equalsIgnoreCase(HTML40Namespace.ElementName.STYLE)) { |
nitind | 958d79a | 2004-11-23 19:23:00 +0000 | [diff] [blame] | 77 | // now we know the cursor is in a <style> tag with a region |
| 78 | // use the parent because that will be the <style> tag |
david_williams | cc02363 | 2005-03-08 02:56:22 +0000 | [diff] [blame] | 79 | IStructuredModel cssModel = getCSSModel(parent); |
| 80 | if (cssModel != null) { |
| 81 | // adjust offsets for embedded style |
| 82 | int offset = indexedNode.getStartOffset(); |
| 83 | int pos = documentPosition - offset; |
| 84 | IndexedRegion keyIndexedNode = cssModel.getIndexedRegion(pos); |
| 85 | if (keyIndexedNode == null) { |
| 86 | keyIndexedNode = (IndexedRegion) ((ICSSModel) cssModel).getDocument(); |
| 87 | } |
| 88 | arranger = new CSSProposalArranger(pos, (ICSSNode) keyIndexedNode, offset, (char) 0); |
nitind | 958d79a | 2004-11-23 19:23:00 +0000 | [diff] [blame] | 89 | } |
nitind | 057e5c8 | 2005-06-16 04:12:26 +0000 | [diff] [blame^] | 90 | } else if (indexedNode instanceof IDOMNode) { |
nitind | 958d79a | 2004-11-23 19:23:00 +0000 | [diff] [blame] | 91 | // get model for node w/ style attribute |
david_williams | c39caaf | 2005-04-05 06:07:16 +0000 | [diff] [blame] | 92 | IStructuredModel cssModel = getCSSModel((IDOMNode) indexedNode); |
david_williams | cc02363 | 2005-03-08 02:56:22 +0000 | [diff] [blame] | 93 | if (cssModel != null) { |
| 94 | IndexedRegion keyIndexedNode = cssModel.getIndexedRegion(documentPosition - fDocumentOffset); |
| 95 | if (keyIndexedNode == null) { |
| 96 | keyIndexedNode = (IndexedRegion) ((ICSSModel) cssModel).getDocument(); |
| 97 | } |
| 98 | if (keyIndexedNode instanceof ICSSNode) { |
| 99 | // inline style for a tag, not embedded |
| 100 | arranger = new CSSProposalArranger(documentPosition, (ICSSNode) keyIndexedNode, fDocumentOffset, fQuote); |
| 101 | } |
| 102 | } |
nitind | 057e5c8 | 2005-06-16 04:12:26 +0000 | [diff] [blame^] | 103 | } else if (indexedNode instanceof ICSSNode) { |
nitind | 958d79a | 2004-11-23 19:23:00 +0000 | [diff] [blame] | 104 | // when editing external CSS using CSS Designer, ICSSNode is |
| 105 | // passed. |
| 106 | ICSSDocument cssdoc = ((ICSSNode) indexedNode).getOwnerDocument(); |
| 107 | if (cssdoc != null) { |
david_williams | cc02363 | 2005-03-08 02:56:22 +0000 | [diff] [blame] | 108 | IStructuredModel cssModel = cssdoc.getModel(); |
| 109 | if (cssModel != null) { |
| 110 | IndexedRegion keyIndexedNode = cssModel.getIndexedRegion(documentPosition - fDocumentOffset); |
| 111 | if (keyIndexedNode == null) { |
| 112 | keyIndexedNode = (IndexedRegion) ((ICSSModel) cssModel).getDocument(); |
| 113 | } |
| 114 | if (keyIndexedNode instanceof ICSSNode) { |
| 115 | // inline style for a tag, not embedded |
| 116 | arranger = new CSSProposalArranger(documentPosition, (ICSSNode) keyIndexedNode, fDocumentOffset, fQuote); |
| 117 | } |
| 118 | } |
nitind | 958d79a | 2004-11-23 19:23:00 +0000 | [diff] [blame] | 119 | } |
nitind | 057e5c8 | 2005-06-16 04:12:26 +0000 | [diff] [blame^] | 120 | } else if (indexedNode == null && isViewerEmpty(viewer)) { |
| 121 | isEmptyDocument = true; |
nitind | 958d79a | 2004-11-23 19:23:00 +0000 | [diff] [blame] | 122 | // the top of empty CSS Document |
david_williams | cc02363 | 2005-03-08 02:56:22 +0000 | [diff] [blame] | 123 | IStructuredModel cssModel = null; |
| 124 | try { |
| 125 | cssModel = StructuredModelManager.getModelManager().getExistingModelForRead(viewer.getDocument()); |
| 126 | if (cssModel instanceof ICSSModel) { |
| 127 | IndexedRegion keyIndexedNode = cssModel.getIndexedRegion(documentPosition - fDocumentOffset); |
| 128 | if (keyIndexedNode == null) { |
| 129 | keyIndexedNode = (IndexedRegion) ((ICSSModel) cssModel).getDocument(); |
| 130 | } |
| 131 | if (keyIndexedNode instanceof ICSSNode) { |
| 132 | // inline style for a tag, not embedded |
| 133 | arranger = new CSSProposalArranger(documentPosition, (ICSSNode) keyIndexedNode, fDocumentOffset, fQuote); |
| 134 | } |
| 135 | } |
nitind | 057e5c8 | 2005-06-16 04:12:26 +0000 | [diff] [blame^] | 136 | } finally { |
david_williams | cc02363 | 2005-03-08 02:56:22 +0000 | [diff] [blame] | 137 | if (cssModel != null) |
| 138 | cssModel.releaseFromRead(); |
nitind | 958d79a | 2004-11-23 19:23:00 +0000 | [diff] [blame] | 139 | } |
| 140 | } |
| 141 | |
david_williams | cc02363 | 2005-03-08 02:56:22 +0000 | [diff] [blame] | 142 | ICompletionProposal[] proposals = new ICompletionProposal[0]; |
| 143 | if (arranger != null) { |
| 144 | fDocumentOffset = 0; |
| 145 | proposals = arranger.getProposals(); |
nitind | 958d79a | 2004-11-23 19:23:00 +0000 | [diff] [blame] | 146 | |
nitind | 057e5c8 | 2005-06-16 04:12:26 +0000 | [diff] [blame^] | 147 | ICompletionProposal[] newfileproposals = new ICompletionProposal[0]; |
| 148 | ICompletionProposal[] anyproposals = new ICompletionProposal[0]; |
| 149 | // add template proposals |
| 150 | if (getTemplateCompletionProcessor() != null) { |
| 151 | if (isEmptyDocument) { |
| 152 | getTemplateCompletionProcessor().setContextType(TemplateContextTypeIdsCSS.NEW); |
| 153 | newfileproposals = getTemplateCompletionProcessor().computeCompletionProposals(viewer, documentPosition); |
| 154 | } |
| 155 | getTemplateCompletionProcessor().setContextType(TemplateContextTypeIdsCSS.ALL); |
| 156 | anyproposals = getTemplateCompletionProcessor().computeCompletionProposals(viewer, documentPosition); |
| 157 | } |
| 158 | |
david_williams | cc02363 | 2005-03-08 02:56:22 +0000 | [diff] [blame] | 159 | // add end tag if parent is not closed |
| 160 | ICompletionProposal endTag = XMLContentAssistUtilities.computeXMLEndTagProposal(viewer, documentPosition, indexedNode, HTML40Namespace.ElementName.STYLE, SharedXMLEditorPluginImageHelper.IMG_OBJ_TAG_GENERIC); //$NON-NLS-1$ |
nitind | 057e5c8 | 2005-06-16 04:12:26 +0000 | [diff] [blame^] | 161 | |
| 162 | // add the additional proposals |
| 163 | int additionalLength = newfileproposals.length + anyproposals.length; |
| 164 | additionalLength = (endTag != null) ? ++additionalLength : additionalLength; |
| 165 | if (additionalLength > 0) { |
| 166 | ICompletionProposal[] plusOnes = new ICompletionProposal[proposals.length + additionalLength]; |
| 167 | int appendPos = proposals.length; |
| 168 | // add end tag proposal |
| 169 | if (endTag != null) { |
| 170 | System.arraycopy(proposals, 0, plusOnes, 1, proposals.length); |
| 171 | plusOnes[0] = endTag; |
| 172 | ++appendPos; |
| 173 | } else { |
| 174 | System.arraycopy(proposals, 0, plusOnes, 0, proposals.length); |
| 175 | } |
| 176 | // add items in newfileproposals |
| 177 | for (int i = 0; i < newfileproposals.length; ++i) { |
| 178 | plusOnes[appendPos + i] = newfileproposals[i]; |
| 179 | } |
| 180 | // add items in anyproposals |
| 181 | appendPos = appendPos + newfileproposals.length; |
| 182 | for (int i = 0; i < anyproposals.length; ++i) { |
| 183 | plusOnes[appendPos + i] = anyproposals[i]; |
| 184 | } |
| 185 | proposals = plusOnes; |
david_williams | cc02363 | 2005-03-08 02:56:22 +0000 | [diff] [blame] | 186 | } |
nitind | 958d79a | 2004-11-23 19:23:00 +0000 | [diff] [blame] | 187 | } |
david_williams | cc02363 | 2005-03-08 02:56:22 +0000 | [diff] [blame] | 188 | return proposals; |
nitind | 958d79a | 2004-11-23 19:23:00 +0000 | [diff] [blame] | 189 | } |
| 190 | |
| 191 | /** |
| 192 | * Returns true if there is no text or it's all white space, otherwise |
| 193 | * returns false |
| 194 | * |
| 195 | * @param treeNode |
| 196 | * @param textViewer |
| 197 | * @return boolean |
| 198 | */ |
| 199 | private boolean isViewerEmpty(ITextViewer textViewer) { |
| 200 | boolean isEmpty = false; |
| 201 | String text = textViewer.getTextWidget().getText(); |
| 202 | if (text == null || (text != null && text.trim().equals(""))) //$NON-NLS-1$ |
| 203 | isEmpty = true; |
| 204 | return isEmpty; |
| 205 | } |
| 206 | |
| 207 | /** |
| 208 | * Get CSSModel for an indexed node |
| 209 | * |
| 210 | * @param indexedNode |
| 211 | * @return IStructuredModel |
| 212 | */ |
david_williams | cc02363 | 2005-03-08 02:56:22 +0000 | [diff] [blame] | 213 | // private IStructuredModel getCSSModel(IndexedRegion indexedNode) { |
| 214 | // if (indexedNode == null) return null; |
| 215 | // Node node = (Node)indexedNode; |
| 216 | // INodeNotifier notifier = (INodeNotifier)node.getParentNode(); |
| 217 | // if (notifier == null) return null; |
| 218 | // INodeAdapter adapter = |
nitind | 958d79a | 2004-11-23 19:23:00 +0000 | [diff] [blame] | 219 | // StyleAdapterFactory.getInstance().adapt(notifier); |
david_williams | cc02363 | 2005-03-08 02:56:22 +0000 | [diff] [blame] | 220 | // if (adapter == null || !(adapter instanceof CSSModelAdapter)) return |
nitind | 958d79a | 2004-11-23 19:23:00 +0000 | [diff] [blame] | 221 | // null; |
david_williams | cc02363 | 2005-03-08 02:56:22 +0000 | [diff] [blame] | 222 | // CSSModelAdapter modelAdapter = (CSSModelAdapter)adapter; |
| 223 | // return modelAdapter.getModel(); |
| 224 | // } |
nitind | 958d79a | 2004-11-23 19:23:00 +0000 | [diff] [blame] | 225 | /** |
| 226 | * Returns the CSSmodel for a given XML node. |
| 227 | * |
| 228 | * @param element |
| 229 | * @return IStructuredModel |
| 230 | */ |
david_williams | c39caaf | 2005-04-05 06:07:16 +0000 | [diff] [blame] | 231 | private IStructuredModel getCSSModel(IDOMNode element) { |
nitind | 958d79a | 2004-11-23 19:23:00 +0000 | [diff] [blame] | 232 | if (element == null) |
| 233 | return null; |
| 234 | INodeAdapter adapter = StyleAdapterFactory.getInstance().adapt(element); |
| 235 | if (adapter == null || !(adapter instanceof ICSSModelAdapter)) |
| 236 | return null; |
| 237 | ICSSModelAdapter modelAdapter = (ICSSModelAdapter) adapter; |
| 238 | return modelAdapter.getModel(); |
| 239 | } |
| 240 | |
| 241 | /** |
| 242 | * Returns information about possible contexts based on the specified |
| 243 | * location within the document that corresponds to the current cursor |
| 244 | * position within the text viewer. |
| 245 | * |
| 246 | * @param viewer |
| 247 | * the viewer whose document is used to compute the possible |
| 248 | * contexts |
| 249 | * @param documentPosition |
| 250 | * an offset within the document for which context information |
| 251 | * should be computed |
| 252 | * @return an array of context information objects or <code>null</code> |
| 253 | * if no context could be found |
| 254 | */ |
| 255 | public org.eclipse.jface.text.contentassist.IContextInformation[] computeContextInformation(org.eclipse.jface.text.ITextViewer viewer, int documentOffset) { |
| 256 | return null; |
| 257 | } |
| 258 | |
| 259 | /** |
| 260 | * Returns the characters which when entered by the user should |
| 261 | * automatically trigger the presentation of possible completions. |
| 262 | * |
| 263 | * @return the auto activation characters for completion proposal or |
| 264 | * <code>null</code> if no auto activation is desired |
| 265 | */ |
| 266 | public char[] getCompletionProposalAutoActivationCharacters() { |
| 267 | return null; |
| 268 | } |
| 269 | |
| 270 | /** |
| 271 | * Returns the characters which when entered by the user should |
| 272 | * automatically trigger the presentation of context information. |
| 273 | * |
| 274 | * @return the auto activation characters for presenting context |
| 275 | * information or <code>null</code> if no auto activation is |
| 276 | * desired |
| 277 | */ |
| 278 | public char[] getContextInformationAutoActivationCharacters() { |
| 279 | return null; |
| 280 | } |
| 281 | |
| 282 | /** |
| 283 | * Returns a validator used to determine when displayed context |
| 284 | * information should be dismissed. May only return <code>null</code> if |
| 285 | * the processor is incapable of computing context information. |
| 286 | * |
| 287 | * @return a context information validator, or <code>null</code> if the |
| 288 | * processor is incapable of computing context information |
| 289 | */ |
| 290 | public org.eclipse.jface.text.contentassist.IContextInformationValidator getContextInformationValidator() { |
| 291 | return null; |
| 292 | } |
| 293 | |
| 294 | /** |
| 295 | * Return the reason why computeProposals was not able to find any |
| 296 | * completions. |
| 297 | * |
| 298 | * @return an error message or null if no error occurred |
| 299 | */ |
| 300 | public String getErrorMessage() { |
| 301 | return null; |
| 302 | } |
| 303 | |
| 304 | /** |
| 305 | * Insert the method's description here. Creation date: (2001/05/22 |
| 306 | * 10:37:05) |
| 307 | * |
| 308 | * @param offset |
| 309 | * int |
| 310 | */ |
| 311 | public void setDocumentOffset(int offset) { |
| 312 | fDocumentOffset = offset; |
| 313 | } |
| 314 | |
| 315 | /** |
| 316 | * |
| 317 | * @param quote |
| 318 | * char |
| 319 | */ |
| 320 | public void setQuoteCharOfStyleAttribute(char quote) { |
| 321 | fQuote = quote; |
| 322 | } |
nitind | 057e5c8 | 2005-06-16 04:12:26 +0000 | [diff] [blame^] | 323 | |
| 324 | private CSSTemplateCompletionProcessor getTemplateCompletionProcessor() { |
| 325 | if (fTemplateProcessor == null) { |
| 326 | fTemplateProcessor = new CSSTemplateCompletionProcessor(); |
| 327 | } |
| 328 | return fTemplateProcessor; |
| 329 | } |
nitind | 958d79a | 2004-11-23 19:23:00 +0000 | [diff] [blame] | 330 | } |