package com.yoursway.ide.editors.text; import static com.google.common.collect.Lists.newArrayListWithCapacity; import java.io.File; import java.util.ArrayList; import java.util.Collection; import java.util.Collections; import java.util.Comparator; import java.util.List; import org.eclipse.dltk.ast.ASTNode; import org.eclipse.dltk.python.parser.ast.PythonArgument; import org.eclipse.swt.widgets.Display; import com.yoursway.completion.CompletionProposalUpdatesListener; import com.yoursway.completion.CompletionProposalsProvider; import com.yoursway.completion.demo.CompletionProposalImpl; import com.yoursway.ide.application.model.Document; import com.yoursway.sadr.blocks.foundation.values.RuntimeObject; import com.yoursway.sadr.python.ASTUtils; import com.yoursway.sadr.python.core.runtime.FileSourceUnit; import com.yoursway.sadr.python.core.runtime.ProjectRuntime; import com.yoursway.sadr.python.core.runtime.ProjectUnit; import com.yoursway.sadr.python_v2.constructs.ClassDeclarationC; import com.yoursway.sadr.python_v2.constructs.MethodDeclarationC; import com.yoursway.sadr.python_v2.constructs.PythonConstruct; import com.yoursway.sadr.python_v2.constructs.PythonFileC; import com.yoursway.sadr.python_v2.constructs.PythonVariableAcceptor; import com.yoursway.sadr.python_v2.croco.Frog; import com.yoursway.sadr.python_v2.goals.CreateInstanceGoal; import com.yoursway.sadr.python_v2.goals.ResolveNameToObjectGoal; import com.yoursway.sadr.python_v2.goals.acceptors.PythonValueSetAcceptor; import com.yoursway.sadr.python_v2.model.Context; import com.yoursway.sadr.python_v2.model.ContextImpl; import com.yoursway.sadr.python_v2.model.PythonArguments; import com.yoursway.sadr.succeeder.DefaultSchedulingStrategy; import com.yoursway.sadr.succeeder.Engine; import com.yoursway.sadr.succeeder.IGoal; import com.yoursway.sadr.succeeder.IGrade; public class PythonCompletion implements CompletionProposalsProvider { private final Document document; private List<CompletionProposalImpl> proposals; StoppableEngineContainer completer; public void asyncUpdate() { Display.getDefault().asyncExec( new Runnable(){ public void run() { updateProposals(); } }); } private static int counter = 0; public static int setId() { return ++counter; } class StoppableEngineContainer { private final Engine engine; private final CompletionProposalUpdatesListener listener; private boolean stopped = false; private int uid; private Thread thread; public StoppableEngineContainer(CompletionProposalUpdatesListener listener) { uid = setId(); this.listener = listener; this.engine = new Engine(new DefaultSchedulingStrategy(){ @Override public boolean prune(IGoal goal) { return stopped; } }); } public void asyncRun(final int caretOffset, final String wordStarting) { thread = new Thread(){ public void run() { System.out.println("Engine #"+uid+" started."); long start = System.currentTimeMillis(); PythonFileC fileC = currentProject(); long dur = System.currentTimeMillis() - start; IGoal goal = createGoal(completer.getEngine(), fileC, wordStarting, new PythonVariableAcceptor(){ @Override public void addResult(String key, RuntimeObject value) { proposals.add(new CompletionProposalImpl(key, key.startsWith(wordStarting)?1:0)); } public <T> void checkpoint(IGrade<T> grade) { asyncUpdate(); } }, caretOffset); try{ engine.run(goal); }catch(RuntimeException e){ e.printStackTrace(); asyncUpdate(); } System.out.println("Engine #"+uid+" finished, took "+dur+" ms."); } }; thread.start(); } public void stop() { thread.interrupt(); stopped = true; } public Engine getEngine() { return engine; } public boolean isStopped() { return stopped; } public CompletionProposalUpdatesListener getListener() { return listener; } } public PythonCompletion(Document document) { this.document = document; } public int findStartOfWord(CharSequence text, int caretOffset) { int beginIndex = caretOffset - 1; while(beginIndex>=0 && isCompletable(text.charAt(beginIndex))){ beginIndex --; } return beginIndex + 1; } public boolean isCompletable(char character) { return Character.isJavaIdentifierPart(character); } public void startCompletionFor(final CompletionProposalUpdatesListener listener, CharSequence wholeDocument, int caretOffset) { int beginIndex = findStartOfWord(wholeDocument, caretOffset); proposals = new ArrayList<CompletionProposalImpl>(); String wordStarting = wholeDocument.subSequence(beginIndex, caretOffset).toString(); getCompletionProposals(listener, caretOffset, wordStarting); // if (enableKeywordCompletion && wordStarting != null) // for (String element : PythonKeyword.findByPrefix(wordStarting)) // reportKeyword(element); } private PythonFileC currentProject() { Collection<File> files = document.project().findAllFiles(); Collection<FileSourceUnit> sourceUnits = newArrayListWithCapacity(files.size()); String root = document.project().getLocation().getAbsolutePath(); if(!root.endsWith("/")) root += "/"; for (File file : files) if (file.getName().toLowerCase().endsWith(".py")) { String path = file.getAbsolutePath(); path = path.substring(0, path.length()-3).replace(root, "").replace("/", "."); sourceUnits.add(new FileSourceUnit(file, path)); } ProjectRuntime projectRuntime = new ProjectRuntime(new ProjectUnit(sourceUnits)); PythonFileC fileC = projectRuntime.getModule(document.file()); return fileC; } private Context createSelfContext(final MethodDeclarationC func, RuntimeObject self) { ArrayList<PythonArgument> list = new ArrayList<PythonArgument>(); Context context = new ContextImpl(list, new PythonArguments()); PythonArgument arg = (PythonArgument) func.node().getArguments().get(0); context.put(arg.getName(), self); return context; } public IGoal createSelfGoal(PythonFileC fileC, final PythonVariableAcceptor pythonVariableAcceptor, final PythonConstruct construct, final Engine engine, final String wordStarting) { if (construct.scope() instanceof MethodDeclarationC && construct.scope().parentScope() instanceof ClassDeclarationC) { final MethodDeclarationC func = (MethodDeclarationC) construct.scope(); ClassDeclarationC classC = (ClassDeclarationC) construct.scope().parentScope(); return new CreateInstanceGoal(classC, func, new PythonArguments(), Context.EMPTY, new PythonValueSetAcceptor(Context.EMPTY) { @Override protected <T> void acceptIndividualResult(RuntimeObject result, IGrade<T> grade) { Context context = createSelfContext(func, result); IGoal goal = findProposals(construct, context, pythonVariableAcceptor, wordStarting); engine.schedule(null, goal); } }); } else { return findProposals(construct, Context.EMPTY, pythonVariableAcceptor, wordStarting); } } private IGoal findProposals(PythonConstruct construct, Context context, PythonVariableAcceptor pythonVariableAcceptor, String wordStarting) { Frog frog = new Frog(wordStarting + "*"); return new ResolveNameToObjectGoal(frog, construct, context, pythonVariableAcceptor); } public IGoal createGoal(Engine engine, PythonFileC fileC, String wordStarting, PythonVariableAcceptor pythonVariableAcceptor, int namePos) { ASTNode node = ASTUtils.findNodeAt(fileC.node(), namePos); PythonConstruct construct = null; try{ construct = fileC.subconstructFor(node); }catch(NullPointerException e){ }catch(RuntimeException e){ e.printStackTrace(); } if(node != null && construct != null){ return createSelfGoal(fileC, pythonVariableAcceptor, construct, engine, wordStarting); }else{ Frog frog = new Frog(wordStarting+"*"); return new ResolveNameToObjectGoal(frog, fileC, Context.EMPTY, pythonVariableAcceptor); } } private void getCompletionProposals(CompletionProposalUpdatesListener listener, int caretOffset, final String wordStarting) { completer = new StoppableEngineContainer(listener); completer.asyncRun(caretOffset, wordStarting); } protected void updateProposals() { if(completer != null && !completer.isStopped()){ Collections.sort(proposals, new Comparator<CompletionProposalImpl>() { public int compare(CompletionProposalImpl o1, CompletionProposalImpl o2) { int f = o1.relevance() - o2.relevance(); if (f>0) return 1; if (f<0) return -1; return o1.completion().compareToIgnoreCase(o2.completion()); } }); completer.getListener().setProposals(proposals); } } public void stopCompletion() { if(completer != null){ completer.stop(); completer = null; } } }