/*
* To change this license header, choose License Headers in Project Properties.
* To change this template file, choose Tools | Templates
* and open the template in the editor.
*/
package ca.weblite.netbeans.mirah.cc;
import ca.weblite.netbeans.mirah.lexer.DocumentQuery;
import ca.weblite.netbeans.mirah.lexer.MirahTokenId;
import java.awt.Color;
import java.awt.Font;
import java.awt.Graphics;
import java.awt.event.KeyEvent;
import java.lang.reflect.Method;
import java.lang.reflect.Modifier;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.HashSet;
import java.util.List;
import java.util.Scanner;
import java.util.Set;
import java.util.regex.Matcher;
import java.util.regex.Pattern;
import javax.swing.ImageIcon;
import javax.swing.text.BadLocationException;
import javax.swing.text.Document;
import javax.swing.text.JTextComponent;
import javax.swing.text.SimpleAttributeSet;
import javax.swing.text.StyledDocument;
import mirah.impl.Tokens;
import org.netbeans.api.editor.completion.Completion;
import org.netbeans.api.lexer.Token;
import org.netbeans.api.lexer.TokenHierarchy;
import org.netbeans.api.lexer.TokenSequence;
import org.netbeans.editor.BaseDocument;
import org.netbeans.lib.editor.codetemplates.api.CodeTemplate;
import org.netbeans.lib.editor.codetemplates.api.CodeTemplateManager;
import org.netbeans.spi.editor.completion.CompletionItem;
import org.netbeans.spi.editor.completion.CompletionTask;
import org.netbeans.spi.editor.completion.support.CompletionUtilities;
import org.netbeans.spi.editor.hints.ChangeInfo;
import org.openide.util.Exceptions;
import org.openide.util.ImageUtilities;
/**
*
* @author shannah
*/
public class MirahDefCompletionItem implements CompletionItem {
private int caretOffset;
private int length;
private Method method;
private static ImageIcon fieldIcon = new ImageIcon(ImageUtilities.loadImage("ca/weblite/netbeans/mirah/1391571312_application-x-ruby.png"));
private static Color fieldColor = Color.decode("0x0000B2");
public MirahDefCompletionItem(Method m, int caretOffset, int len) {
this.method = m;
this.caretOffset = caretOffset;
this.length = len;
}
/**
* Get token sequence positioned over a token.
*
* @param doc
* @param caretOffset
* @param backwardBias
* @return token sequence positioned over a token that "contains" the offset
* or null if the document does not contain any java token sequence or the
* offset is at doc-or-section-start-and-bwd-bias or
* doc-or-section-end-and-fwd-bias.
*/
private static TokenSequence<MirahTokenId> mirahTokenSequence(Document doc, int caretOffset, boolean backwardBias) {
try {
((BaseDocument)doc).readLock();
TokenHierarchy<?> hi = TokenHierarchy.get(doc);
List<TokenSequence<?>> tsList = hi.embeddedTokenSequences(caretOffset, backwardBias);
// Go from inner to outer TSes
for (int i = tsList.size() - 1; i >= 0; i--) {
TokenSequence<?> ts = tsList.get(i);
if (ts.languagePath().innerLanguage() == MirahTokenId.getLanguage()) {
TokenSequence<MirahTokenId> javaInnerTS = (TokenSequence<MirahTokenId>) ts;
return javaInnerTS;
}
}
return null;
} finally {
((BaseDocument)doc).readUnlock();
}
}
public List<String> getImports(Document doc) throws Exception {
DocumentQuery q = new DocumentQuery(doc);
return q.getImports();
}
public void addImport(Document doc, String fullClassName) throws Exception {
//Document doc = source.getDocument(true);
TokenHierarchy<?> hi = TokenHierarchy.get(doc);
int caretOffset = 0;
TokenSequence<MirahTokenId> seq = mirahTokenSequence(doc, caretOffset, false);
// Find the first package or import and place the import after that.
MirahTokenId PKG = MirahTokenId.get(Tokens.tPackage.ordinal());
MirahTokenId IMPORT = MirahTokenId.get(Tokens.tImport.ordinal());
MirahTokenId EOL = MirahTokenId.get(Tokens.tNL.ordinal());
int pos = 0;
Token pkg = null;
Token firstImport = null;
do {
Token curr = seq.token();
MirahTokenId currTok = (MirahTokenId)curr.id();
if ( PKG.equals(currTok) ){
pkg = curr;
} else if ( IMPORT.equals(currTok)){
firstImport = curr;
}
} while ( seq.moveNext());
if ( firstImport != null ){
doc.insertString(firstImport.offset(hi), "import "+fullClassName+"\n", new SimpleAttributeSet());
} else if ( pkg != null ){
seq.move(pkg.offset(hi));
while ( seq.moveNext() ){
if ( EOL.equals(seq.token().id())){
doc.insertString(seq.token().offset(hi), "\nimport "+fullClassName+"\n", new SimpleAttributeSet());
break;
}
}
} else {
doc.insertString(0, "import "+fullClassName+"\n", new SimpleAttributeSet());
}
}
@Override
public void defaultAction(JTextComponent jtc) {
CodeTemplateManager mgr = CodeTemplateManager.get(jtc.getDocument());
CodeTemplate tpl = mgr.createTemporary(formatMethod(method));
try {
StyledDocument doc = (StyledDocument) jtc.getDocument();
if ( length > 0 ){
doc.remove(caretOffset, length);
}
tpl.insert(jtc);
// Now to add the imports.
List primitives = Arrays.asList(new String[]{
"boolean","int","short","long","char","float","double"
});
Set<String> classNames = new HashSet<String>();
for ( Class paramType : method.getParameterTypes()){
if ( paramType.getName().startsWith("java.lang.")){
continue;
}
classNames.add(paramType.getName());
}
Class returnType = method.getReturnType();
if ( !returnType.getName().startsWith("java.lang.")){
classNames.add(returnType.getName());
}
try {
classNames.removeAll(getImports(doc));
} catch (Exception ex) {
Exceptions.printStackTrace(ex);
}
classNames.removeAll(primitives);
if ( !classNames.isEmpty()){
for ( String className : classNames ){
try {
addImport(doc, className);
} catch (Exception ex) {
Exceptions.printStackTrace(ex);
}
}
}
//doc.insertString(caretOffset, tpl., null);
//This statement will close the code completion box:
Completion.get().hideAll();
} catch (BadLocationException ex) {
Exceptions.printStackTrace(ex);
}
}
private String formatMethod(Method m){
StringBuilder sb = new StringBuilder();
if ( Modifier.isPrivate(m.getModifiers()) ){
sb.append("private ");
} else if ( Modifier.isProtected(m.getModifiers()) ){
sb.append("protected ");
} else if ( Modifier.isPublic(m.getModifiers()) ){
// No need to prepend any modifier for public
} else {
sb.append("package_private ");
}
sb.append("def ");
sb.append(m.getName());
Class[] args = m.getParameterTypes();
if ( args.length > 0 ){
sb.append("(");
}
for ( int i=0; i<args.length; i++){
sb
.append("arg")
.append(i)
.append(":")
.append(args[i].getSimpleName());
if ( i < args.length-1 ){
sb.append(",");
} else {
sb.append(")");
}
}
sb.append(" : ");
sb.append(m.getReturnType().getSimpleName());
return sb.toString();
}
private String formatMethodForList(Method m){
StringBuilder sb = new StringBuilder();
if ( Modifier.isPrivate(m.getModifiers()) ){
sb.append("private ");
} else if ( Modifier.isProtected(m.getModifiers()) ){
sb.append("protected ");
} else if ( Modifier.isPublic(m.getModifiers()) ){
// No need to prepend any modifier for public
} else {
sb.append("package_private ");
}
sb.append(m.getName());
Class[] args = m.getParameterTypes();
if ( args.length > 0 ){
sb.append("(");
}
for ( int i=0; i<args.length; i++){
sb
.append("arg")
.append(i)
.append(":")
.append(args[i].getSimpleName());
if ( i < args.length-1 ){
sb.append(",");
} else {
sb.append(")");
}
}
sb.append(" : ");
sb.append(m.getReturnType().getSimpleName());
return sb.toString();
}
@Override
public void processKeyEvent(KeyEvent ke) {
}
@Override
public int getPreferredWidth(Graphics graphics, Font font) {
return CompletionUtilities.getPreferredWidth(formatMethodForList(method), null, graphics, font);
}
@Override
public void render(Graphics g, Font font, Color color, Color color1, int width, int height, boolean selected) {
CompletionUtilities.renderHtml(fieldIcon, formatMethodForList(method), null, g, font,
(selected ? Color.white : fieldColor), width, height, selected);
}
@Override
public CompletionTask createDocumentationTask() {
return null;
}
@Override
public CompletionTask createToolTipTask() {
return null;
}
@Override
public boolean instantSubstitution(JTextComponent jtc) {
return false;
}
@Override
public int getSortPriority() {
return 0;
}
@Override
public CharSequence getSortText() {
return method.getName();
}
@Override
public CharSequence getInsertPrefix() {
return method.getName();
}
}