blob: 2d30ad6efb6b8f171a1aa0067d4847e363c345fa [file] [log] [blame]
nitind958d79a2004-11-23 19:23:00 +00001/*****************************************************************************
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_williamsaaadf2b2005-04-11 07:28:31 +00009package org.eclipse.wst.css.ui.internal.contentassist;
nitind958d79a2004-11-23 19:23:00 +000010
11
12
13import org.eclipse.jface.text.ITextViewer;
14import org.eclipse.jface.text.contentassist.ICompletionProposal;
15import org.eclipse.jface.text.contentassist.IContentAssistProcessor;
david_williams63219a22005-04-10 01:59:51 +000016import org.eclipse.wst.css.core.internal.provisional.adapters.ICSSModelAdapter;
17import org.eclipse.wst.css.core.internal.provisional.document.ICSSDocument;
18import org.eclipse.wst.css.core.internal.provisional.document.ICSSModel;
19import org.eclipse.wst.css.core.internal.provisional.document.ICSSNode;
nitind057e5c82005-06-16 04:12:26 +000020import org.eclipse.wst.css.ui.internal.templates.TemplateContextTypeIdsCSS;
david_williamsaaadf2b2005-04-11 07:28:31 +000021import org.eclipse.wst.html.core.internal.htmlcss.StyleAdapterFactory;
david_williams4ad020f2005-04-18 08:00:30 +000022import org.eclipse.wst.sse.core.internal.provisional.INodeAdapter;
23import org.eclipse.wst.sse.core.internal.provisional.IStructuredModel;
24import org.eclipse.wst.sse.core.internal.provisional.IndexedRegion;
25import org.eclipse.wst.sse.core.internal.provisional.StructuredModelManager;
pavery2f6b6fb2005-03-29 21:09:57 +000026import org.eclipse.wst.sse.ui.internal.StructuredTextViewer;
nitind958d79a2004-11-23 19:23:00 +000027import org.eclipse.wst.sse.ui.internal.contentassist.ContentAssistUtils;
david_williams4ad020f2005-04-18 08:00:30 +000028import org.eclipse.wst.xml.core.internal.provisional.document.IDOMNode;
david_williams12ff79e2005-04-13 13:59:30 +000029import org.eclipse.wst.xml.ui.internal.contentassist.XMLContentAssistUtilities;
david_williamsf3680f02005-04-13 22:43:54 +000030import org.eclipse.wst.xml.ui.internal.util.SharedXMLEditorPluginImageHelper;
nitind958d79a2004-11-23 19:23:00 +000031
32public class CSSContentAssistProcessor implements IContentAssistProcessor {
33
34 private int fDocumentOffset = 0;
35 private char fQuote = 0;
nitind057e5c82005-06-16 04:12:26 +000036 private CSSTemplateCompletionProcessor fTemplateProcessor = null;
nitind958d79a2004-11-23 19:23:00 +000037
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_williamsc39caaf2005-04-05 06:07:16 +000050 IDOMNode xNode = null;
51 IDOMNode parent = null;
nitind958d79a2004-11-23 19:23:00 +000052 CSSProposalArranger arranger = null;
nitind057e5c82005-06-16 04:12:26 +000053 boolean isEmptyDocument = false;
nitind958d79a2004-11-23 19:23:00 +000054
55 // bail if we couldn't get an indexed node
david_williamscc023632005-03-08 02:56:22 +000056 // if(indexedNode == null) return new ICompletionProposal[0];
david_williamsc39caaf2005-04-05 06:07:16 +000057 if (indexedNode instanceof IDOMNode) {
58 xNode = (IDOMNode) indexedNode;
59 parent = (IDOMNode) xNode.getParentNode();
nitind958d79a2004-11-23 19:23:00 +000060 }
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_williamscc023632005-03-08 02:56:22 +000065 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);
nitind958d79a2004-11-23 19:23:00 +000075 }
nitind057e5c82005-06-16 04:12:26 +000076 } else if (parent != null && parent.getNodeName().equalsIgnoreCase(HTML40Namespace.ElementName.STYLE)) {
nitind958d79a2004-11-23 19:23:00 +000077 // 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_williamscc023632005-03-08 02:56:22 +000079 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);
nitind958d79a2004-11-23 19:23:00 +000089 }
nitind057e5c82005-06-16 04:12:26 +000090 } else if (indexedNode instanceof IDOMNode) {
nitind958d79a2004-11-23 19:23:00 +000091 // get model for node w/ style attribute
david_williamsc39caaf2005-04-05 06:07:16 +000092 IStructuredModel cssModel = getCSSModel((IDOMNode) indexedNode);
david_williamscc023632005-03-08 02:56:22 +000093 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 }
nitind057e5c82005-06-16 04:12:26 +0000103 } else if (indexedNode instanceof ICSSNode) {
nitind958d79a2004-11-23 19:23:00 +0000104 // when editing external CSS using CSS Designer, ICSSNode is
105 // passed.
106 ICSSDocument cssdoc = ((ICSSNode) indexedNode).getOwnerDocument();
107 if (cssdoc != null) {
david_williamscc023632005-03-08 02:56:22 +0000108 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 }
nitind958d79a2004-11-23 19:23:00 +0000119 }
nitind057e5c82005-06-16 04:12:26 +0000120 } else if (indexedNode == null && isViewerEmpty(viewer)) {
121 isEmptyDocument = true;
nitind958d79a2004-11-23 19:23:00 +0000122 // the top of empty CSS Document
david_williamscc023632005-03-08 02:56:22 +0000123 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 }
nitind057e5c82005-06-16 04:12:26 +0000136 } finally {
david_williamscc023632005-03-08 02:56:22 +0000137 if (cssModel != null)
138 cssModel.releaseFromRead();
nitind958d79a2004-11-23 19:23:00 +0000139 }
140 }
141
david_williamscc023632005-03-08 02:56:22 +0000142 ICompletionProposal[] proposals = new ICompletionProposal[0];
143 if (arranger != null) {
144 fDocumentOffset = 0;
145 proposals = arranger.getProposals();
nitind958d79a2004-11-23 19:23:00 +0000146
nitind057e5c82005-06-16 04:12:26 +0000147 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_williamscc023632005-03-08 02:56:22 +0000159 // 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$
nitind057e5c82005-06-16 04:12:26 +0000161
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_williamscc023632005-03-08 02:56:22 +0000186 }
nitind958d79a2004-11-23 19:23:00 +0000187 }
david_williamscc023632005-03-08 02:56:22 +0000188 return proposals;
nitind958d79a2004-11-23 19:23:00 +0000189 }
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_williamscc023632005-03-08 02:56:22 +0000213 // 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 =
nitind958d79a2004-11-23 19:23:00 +0000219 // StyleAdapterFactory.getInstance().adapt(notifier);
david_williamscc023632005-03-08 02:56:22 +0000220 // if (adapter == null || !(adapter instanceof CSSModelAdapter)) return
nitind958d79a2004-11-23 19:23:00 +0000221 // null;
david_williamscc023632005-03-08 02:56:22 +0000222 // CSSModelAdapter modelAdapter = (CSSModelAdapter)adapter;
223 // return modelAdapter.getModel();
224 // }
nitind958d79a2004-11-23 19:23:00 +0000225 /**
226 * Returns the CSSmodel for a given XML node.
227 *
228 * @param element
229 * @return IStructuredModel
230 */
david_williamsc39caaf2005-04-05 06:07:16 +0000231 private IStructuredModel getCSSModel(IDOMNode element) {
nitind958d79a2004-11-23 19:23:00 +0000232 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 }
nitind057e5c82005-06-16 04:12:26 +0000323
324 private CSSTemplateCompletionProcessor getTemplateCompletionProcessor() {
325 if (fTemplateProcessor == null) {
326 fTemplateProcessor = new CSSTemplateCompletionProcessor();
327 }
328 return fTemplateProcessor;
329 }
nitind958d79a2004-11-23 19:23:00 +0000330}