001: /*
002: * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS HEADER.
003: *
004: * Copyright 1997-2007 Sun Microsystems, Inc. All rights reserved.
005: *
006: * The contents of this file are subject to the terms of either the GNU
007: * General Public License Version 2 only ("GPL") or the Common
008: * Development and Distribution License("CDDL") (collectively, the
009: * "License"). You may not use this file except in compliance with the
010: * License. You can obtain a copy of the License at
011: * http://www.netbeans.org/cddl-gplv2.html
012: * or nbbuild/licenses/CDDL-GPL-2-CP. See the License for the
013: * specific language governing permissions and limitations under the
014: * License. When distributing the software, include this License Header
015: * Notice in each file and include the License file at
016: * nbbuild/licenses/CDDL-GPL-2-CP. Sun designates this
017: * particular file as subject to the "Classpath" exception as provided
018: * by Sun in the GPL Version 2 section of the License file that
019: * accompanied this code. If applicable, add the following below the
020: * License Header, with the fields enclosed by brackets [] replaced by
021: * your own identifying information:
022: * "Portions Copyrighted [year] [name of copyright owner]"
023: *
024: * Contributor(s):
025: *
026: * The Original Software is NetBeans. The Initial Developer of the Original
027: * Software is Sun Microsystems, Inc. Portions Copyright 1997-2006 Sun
028: * Microsystems, Inc. All Rights Reserved.
029: *
030: * If you wish your version of this file to be governed by only the CDDL
031: * or only the GPL Version 2, indicate your decision by adding
032: * "[Contributor] elects to include this software in this distribution
033: * under the [CDDL or GPL Version 2] license." If you do not indicate a
034: * single choice of license, a recipient has the option to distribute
035: * your version of this file under either the CDDL, the GPL Version 2 or
036: * to extend the choice of license to its licensees as provided above.
037: * However, if you add GPL Version 2 code and therefore, elected the GPL
038: * Version 2 license, then the option applies only if the new code is
039: * made subject to such option by the copyright holder.
040: */
041:
042: package org.netbeans.modules.gsfret.editor.completion;
043:
044: import java.awt.Color;
045: import java.awt.Font;
046: import java.awt.Graphics;
047: import java.awt.event.KeyEvent;
048: import java.util.*;
049: import javax.swing.ImageIcon;
050: import javax.swing.text.BadLocationException;
051: import javax.swing.text.JTextComponent;
052: import javax.swing.text.Position;
053: import org.netbeans.api.editor.completion.Completion;
054: import org.netbeans.modules.gsf.api.CompletionProposal;
055: import org.netbeans.modules.gsf.api.ElementHandle;
056: import org.netbeans.modules.gsf.api.Modifier;
057: import org.netbeans.napi.gsfret.source.CompilationInfo;
058: import org.netbeans.editor.BaseDocument;
059: import org.netbeans.lib.editor.codetemplates.api.CodeTemplateManager;
060: import org.netbeans.spi.editor.completion.CompletionItem;
061: import org.netbeans.spi.editor.completion.CompletionTask;
062: import org.netbeans.spi.editor.completion.support.CompletionUtilities;
063:
064: /**
065: * Code completion items originating from the language plugin.
066: *
067: * Based on JavaCompletionItem by Dusan Balek.
068: *
069: * @author Tor Norbye
070: */
071: public abstract class GsfCompletionItem implements CompletionItem {
072: /** Cache for looking up tip proposal - usually null (shortlived) */
073: static org.netbeans.modules.gsf.api.CompletionProposal tipProposal;
074:
075: protected CompilationInfo info;
076:
077: protected static int SMART_TYPE = 1000;
078:
079: private static class DelegatedItem extends GsfCompletionItem {
080: private org.netbeans.modules.gsf.api.CompletionProposal item;
081: private static ImageIcon icon[][] = new ImageIcon[2][4];
082:
083: private DelegatedItem(CompilationInfo info,
084: org.netbeans.modules.gsf.api.CompletionProposal item) {
085: super (item.getAnchorOffset());
086: this .item = item;
087: this .info = info;
088: }
089:
090: public int getSortPriority() {
091: switch (item.getKind()) {
092: case ERROR:
093: return -5000;
094: case DB:
095: return item.isSmart() ? 155 - SMART_TYPE : 155;
096: case PARAMETER:
097: return item.isSmart() ? 105 - SMART_TYPE : 105;
098: case CALL:
099: return item.isSmart() ? 110 - SMART_TYPE : 110;
100: case CONSTRUCTOR:
101: return item.isSmart() ? 400 - SMART_TYPE : 400;
102: case PACKAGE:
103: case MODULE:
104: return item.isSmart() ? 900 - SMART_TYPE : 900;
105: case CLASS:
106: return item.isSmart() ? 800 - SMART_TYPE : 800;
107: case ATTRIBUTE:
108: case TAG:
109: return item.isSmart() ? 480 - SMART_TYPE : 480;
110: case PROPERTY:
111: case METHOD:
112: return item.isSmart() ? 500 - SMART_TYPE : 500;
113: case FIELD:
114: return item.isSmart() ? 300 - SMART_TYPE : 300;
115: case CONSTANT:
116: case GLOBAL:
117: case VARIABLE:
118: return item.isSmart() ? 200 - SMART_TYPE : 200;
119: case KEYWORD:
120: return item.isSmart() ? 600 - SMART_TYPE : 600;
121: case OTHER:
122: default:
123: return item.isSmart() ? 999 - SMART_TYPE : 999;
124: }
125: }
126:
127: public boolean instantSubstitution(JTextComponent component) {
128: // ElementKind kind = item.getKind();
129: // return !(kind == ElementKind.CLASS || kind == ElementKind.MODULE);
130: return false;
131: }
132:
133: public CharSequence getSortText() {
134: return item.getSortText();
135: }
136:
137: public CharSequence getInsertPrefix() {
138: return item.getInsertPrefix();
139: }
140:
141: protected String getLeftHtmlText() {
142: return item.getLhsHtml();
143: }
144:
145: public String toString() {
146: return item.getName();
147: }
148:
149: protected String getRightHtmlText() {
150: String rhs = item.getRhsHtml();
151:
152: // Count text length on LHS
153: String lhs = item.getLhsHtml();
154: boolean inTag = false;
155: int length = 0;
156: for (int i = 0, n = lhs.length(); i < n; i++) {
157: char c = lhs.charAt(i);
158: if (inTag) {
159: if (c == '>') {
160: inTag = false;
161: }
162: } else if (c == '<') {
163: inTag = true;
164: } else {
165: length++;
166: }
167: }
168:
169: return truncateRhs(rhs, length);
170: }
171:
172: @Override
173: public CompletionTask createDocumentationTask() {
174: final ElementHandle element = item.getElement();
175: if (element != null) {
176: return GsfCompletionProvider.createDocTask(element,
177: info);
178: }
179:
180: return null;
181: }
182:
183: protected ImageIcon getIcon() {
184: ImageIcon ic = item.getIcon();
185: if (ic != null) {
186: return ic;
187: }
188:
189: ImageIcon icon = org.netbeans.modules.gsfret.navigation.Icons
190: .getElementIcon(item.getKind(), item.getModifiers());
191: // TODO - cache!
192: return icon;
193: //
194: //
195: // ElementKind kind = item.getKind();
196: // switch (kind) {
197: // case CONSTRUCTOR:
198: // case METHOD:
199: // return getMethodIcon();
200: // case ATTRIBUTE:
201: // case FIELD:
202: // return getFieldIcon();
203: // case CLASS:
204: // return getClassIcon();
205: // case MODULE:
206: // return getModuleIcon();
207: // case CONSTANT:
208: // return getConstantIcon();
209: // case VARIABLE:
210: // return getVariableIcon();
211: // case KEYWORD:
212: // return getKeywordIcon();
213: // case OTHER:
214: // }
215: //
216: // return null;
217: }
218:
219: // protected ImageIcon getMethodIcon() {
220: // Set<Modifier> modifiers = item.getModifiers();
221: //
222: // boolean isStatic = modifiers.contains(Modifier.STATIC);
223: //// int level = getProtectionLevel(elem.getModifiers());
224: //
225: // ImageIcon cachedIcon = icon[isStatic?1:0][level];
226: // if (cachedIcon != null) {
227: // return cachedIcon;
228: // }
229: //
230: // String iconPath = METHOD_PUBLIC;
231: // if (isStatic) {
232: // switch (level) {
233: // case PRIVATE_LEVEL:
234: // iconPath = METHOD_ST_PRIVATE;
235: // break;
236: //
237: // case PACKAGE_LEVEL:
238: // iconPath = METHOD_ST_PACKAGE;
239: // break;
240: //
241: // case PROTECTED_LEVEL:
242: // iconPath = METHOD_ST_PROTECTED;
243: // break;
244: //
245: // case PUBLIC_LEVEL:
246: // iconPath = METHOD_ST_PUBLIC;
247: // break;
248: // }
249: // }else{
250: // switch (level) {
251: // case PRIVATE_LEVEL:
252: // iconPath = METHOD_PRIVATE;
253: // break;
254: //
255: // case PACKAGE_LEVEL:
256: // iconPath = METHOD_PACKAGE;
257: // break;
258: //
259: // case PROTECTED_LEVEL:
260: // iconPath = METHOD_PROTECTED;
261: // break;
262: //
263: // case PUBLIC_LEVEL:
264: // iconPath = METHOD_PUBLIC;
265: // break;
266: // }
267: // }
268: // ImageIcon newIcon = new ImageIcon(org.openide.util.Utilities.loadImage(iconPath));
269: // icon[isStatic?1:0][level] = newIcon;
270: // return newIcon;
271: // }
272:
273: protected void substituteText(final JTextComponent c,
274: int offset, int len, String toAdd) {
275: String template = item.getCustomInsertTemplate();
276: if (template != null) {
277: BaseDocument doc = (BaseDocument) c.getDocument();
278: CodeTemplateManager ctm = CodeTemplateManager.get(doc);
279: if (ctm != null) {
280: doc.atomicLock();
281: try {
282: doc.remove(offset, len);
283: c.getCaret().setDot(offset);
284: } catch (BadLocationException e) {
285: // Can't update
286: } finally {
287: doc.atomicUnlock();
288: }
289:
290: ctm.createTemporary(template).insert(c);
291: // TODO - set the actual method to be used here so I don't have to
292: // work quite as hard...
293: //tipProposal = item;
294: Completion.get().showToolTip();
295: }
296:
297: return;
298: }
299:
300: List<String> params = item.getInsertParams();
301: if (params == null || params.size() == 0) {
302: // TODO - call getParamListDelimiters here as well!
303: super .substituteText(c, offset, len, toAdd);
304: return;
305: }
306:
307: super .substituteText(c, offset, len, toAdd);
308:
309: BaseDocument doc = (BaseDocument) c.getDocument();
310: CodeTemplateManager ctm = CodeTemplateManager.get(doc);
311: if (ctm != null) {
312: StringBuilder sb = new StringBuilder();
313: String[] delimiters = item.getParamListDelimiters();
314: assert delimiters.length == 2;
315: sb.append(delimiters[0]);
316: int id = 1;
317: for (Iterator<String> it = params.iterator(); it
318: .hasNext();) {
319: String paramDesc = it.next();
320: sb.append("${"); //NOI18N
321: // Ensure that we don't use one of the "known" logical parameters
322: // such that a parameter like "path" gets replaced with the source file
323: // path!
324: sb.append("gsf-cc-"); // NOI18N
325: sb.append(Integer.toString(id++));
326: sb.append(" default=\""); // NOI18N
327: sb.append(paramDesc);
328: sb.append("\""); // NOI18N
329: sb.append("}"); //NOI18N
330: if (it.hasNext())
331: sb.append(", "); //NOI18N
332: }
333: sb.append(delimiters[1]);
334: sb.append("${cursor}");
335: ctm.createTemporary(sb.toString()).insert(c);
336: // TODO - set the actual method to be used here so I don't have to
337: // work quite as hard...
338: //tipProposal = item;
339: Completion.get().showToolTip();
340: }
341:
342: }
343: }
344:
345: public static final GsfCompletionItem createItem(
346: CompletionProposal proposal, CompilationInfo info) {
347: return new DelegatedItem(info, proposal);
348: }
349:
350: public static final String COLOR_END = "</font>"; //NOI18N
351: public static final String STRIKE = "<s>"; //NOI18N
352: public static final String STRIKE_END = "</s>"; //NOI18N
353: public static final String BOLD = "<b>"; //NOI18N
354: public static final String BOLD_END = "</b>"; //NOI18N
355:
356: protected int substitutionOffset;
357:
358: private GsfCompletionItem(int substitutionOffset) {
359: this .substitutionOffset = substitutionOffset;
360: }
361:
362: public void defaultAction(JTextComponent component) {
363: if (component != null) {
364: // Items with no insert prefix and no custom code template
365: // are "read-only" (such as the method call items)
366: if (getInsertPrefix().length() == 0) {
367: return;
368: }
369: Completion.get().hideAll();
370: int caretOffset = component.getSelectionEnd();
371: substituteText(component, substitutionOffset, caretOffset
372: - substitutionOffset, null);
373: }
374: }
375:
376: public void processKeyEvent(KeyEvent evt) {
377: if (evt.getID() == KeyEvent.KEY_TYPED) {
378: switch (evt.getKeyChar()) {
379: case ';':
380: case ',':
381: case '(':
382: case '.':
383: case '\n':
384: Completion.get().hideAll();
385: // case '.':
386: // JTextComponent component = (JTextComponent)evt.getSource();
387: // int caretOffset = component.getSelectionEnd();
388: // substituteText(component, substitutionOffset, caretOffset - substitutionOffset, Character.toString(evt.getKeyChar()));
389: // evt.consume();
390: // break;
391: }
392: }
393: }
394:
395: public boolean instantSubstitution(JTextComponent component) {
396: defaultAction(component);
397: return true;
398: }
399:
400: public CompletionTask createDocumentationTask() {
401: return null;
402: }
403:
404: public CompletionTask createToolTipTask() {
405: return null;
406: }
407:
408: public int getPreferredWidth(Graphics g, Font defaultFont) {
409: return CompletionUtilities.getPreferredWidth(getLeftHtmlText(),
410: getRightHtmlText(), g, defaultFont);
411: }
412:
413: public void render(Graphics g, Font defaultFont,
414: Color defaultColor, Color backgroundColor, int width,
415: int height, boolean selected) {
416: CompletionUtilities.renderHtml(getIcon(), getLeftHtmlText(),
417: getRightHtmlText(), g, defaultFont, defaultColor,
418: width, height, selected);
419: }
420:
421: protected abstract ImageIcon getIcon();
422:
423: protected String getLeftHtmlText() {
424: return null;
425: }
426:
427: protected String getRightHtmlText() {
428: return null;
429: }
430:
431: protected void substituteText(JTextComponent c, int offset,
432: int len, String toAdd) {
433: BaseDocument doc = (BaseDocument) c.getDocument();
434: String text = getInsertPrefix().toString();
435: if (text != null) {
436: //int semiPos = toAdd != null && toAdd.endsWith(";") ? findPositionForSemicolon(c) : -2; //NOI18N
437: //if (semiPos > -2)
438: // toAdd = toAdd.length() > 1 ? toAdd.substring(0, toAdd.length() - 1) : null;
439: //if (toAdd != null && !toAdd.equals("\n")) {//NOI18N
440: // TokenSequence<JavaTokenId> sequence = Utilities.getJavaTokenSequence(c, offset + len);
441: // if (sequence == null) {
442: // text += toAdd;
443: // toAdd = null;
444: // }
445: // boolean added = false;
446: // while(toAdd != null && toAdd.length() > 0) {
447: // String tokenText = sequence.token().text().toString();
448: // if (tokenText.startsWith(toAdd)) {
449: // len = sequence.offset() - offset + toAdd.length();
450: // text += toAdd;
451: // toAdd = null;
452: // } else if (toAdd.startsWith(tokenText)) {
453: // sequence.moveNext();
454: // len = sequence.offset() - offset;
455: // text += toAdd.substring(0, tokenText.length());
456: // toAdd = toAdd.substring(tokenText.length());
457: // added = true;
458: // } else if (sequence.token().id() == JavaTokenId.WHITESPACE && sequence.token().text().toString().indexOf('\n') < 0) {//NOI18N
459: // if (!sequence.moveNext()) {
460: // text += toAdd;
461: // toAdd = null;
462: // }
463: // } else {
464: // if (!added)
465: // text += toAdd;
466: // toAdd = null;
467: // }
468: // }
469: //}
470:
471: int semiPos = -2;
472: // Update the text
473: doc.atomicLock();
474: try {
475: String textToReplace = doc.getText(offset, len);
476: if (text.equals(textToReplace)) {
477: if (semiPos > -1)
478: doc.insertString(semiPos, ";", null); //NOI18N
479: return;
480: }
481: Position position = doc.createPosition(offset);
482: Position semiPosition = semiPos > -1 ? doc
483: .createPosition(semiPos) : null;
484: doc.remove(offset, len);
485: doc.insertString(position.getOffset(), text, null);
486: if (semiPosition != null)
487: doc.insertString(semiPosition.getOffset(), ";",
488: null);
489: } catch (BadLocationException e) {
490: // Can't update
491: } finally {
492: doc.atomicUnlock();
493: }
494: }
495: }
496:
497: private static String truncateRhs(String rhs, int left) {
498: if (rhs != null) {
499: final int MAX_SIZE = 40;
500: int size = MAX_SIZE - left;
501: if (size < 10) {
502: size = 10;
503: }
504: if (rhs != null && rhs.length() > size) {
505: rhs = rhs.substring(0, size - 3) + "<b>></b>"; // Add a ">" to indicate truncation
506: }
507: }
508: return rhs;
509: }
510:
511: // TODO: KeywordItem has a postfix:
512: // private static class KeywordItem extends GsfCompletionItem {
513: // private String postfix;
514: // private KeywordItem(ComKeyword keyword, String postfix, int substitutionOffset) {
515: // this.postfix = postfix;
516: // }
517: // protected void substituteText(JTextComponent c, int offset, int len, String toAdd) {
518: // super.substituteText(c, offset, len, toAdd != null ? toAdd : postfix);
519: // }
520:
521: private static final int PUBLIC_LEVEL = 3;
522: private static final int PROTECTED_LEVEL = 2;
523: private static final int PACKAGE_LEVEL = 1;
524: private static final int PRIVATE_LEVEL = 0;
525:
526: private static int getProtectionLevel(Set<Modifier> modifiers) {
527: if (modifiers.contains(Modifier.PUBLIC))
528: return PUBLIC_LEVEL;
529: if (modifiers.contains(Modifier.PROTECTED))
530: return PROTECTED_LEVEL;
531: if (modifiers.contains(Modifier.PRIVATE))
532: return PRIVATE_LEVEL;
533: return PACKAGE_LEVEL;
534: }
535: }
|