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