2
* Created on Aug 11, 2004
4
* @author Fabio Zadrozny
6
package org.python.pydev.editor.codecompletion;
9
import java.util.ArrayList;
10
import java.util.Arrays;
11
import java.util.Collection;
12
import java.util.HashSet;
13
import java.util.Iterator;
14
import java.util.List;
15
import java.util.StringTokenizer;
17
import org.eclipse.core.runtime.CoreException;
18
import org.eclipse.jface.text.BadLocationException;
19
import org.eclipse.jface.text.IDocument;
20
import org.eclipse.jface.text.IRegion;
21
import org.eclipse.jface.text.ITextViewer;
22
import org.eclipse.jface.text.contentassist.CompletionProposal;
23
import org.eclipse.jface.text.contentassist.ICompletionProposal;
24
import org.eclipse.swt.graphics.Image;
25
import org.python.pydev.core.ExtensionHelper;
26
import org.python.pydev.core.ICodeCompletionASTManager;
27
import org.python.pydev.core.ICompletionState;
28
import org.python.pydev.core.IModule;
29
import org.python.pydev.core.IPythonNature;
30
import org.python.pydev.core.IToken;
31
import org.python.pydev.core.bundle.ImageCache;
32
import org.python.pydev.core.docutils.DocUtils;
33
import org.python.pydev.editor.codecompletion.revisited.ASTManager;
34
import org.python.pydev.editor.codecompletion.revisited.CompletionRecursionException;
35
import org.python.pydev.editor.codecompletion.revisited.CompletionState;
36
import org.python.pydev.editor.codecompletion.revisited.modules.AbstractModule;
37
import org.python.pydev.editor.codecompletion.revisited.modules.CompiledModule;
38
import org.python.pydev.editor.codecompletion.revisited.visitors.FindScopeVisitor;
39
import org.python.pydev.editor.codecompletion.revisited.visitors.Scope;
40
import org.python.pydev.editor.codecompletion.shell.AbstractShell;
41
import org.python.pydev.parser.PyParser;
42
import org.python.pydev.parser.jython.SimpleNode;
43
import org.python.pydev.parser.jython.ast.ClassDef;
44
import org.python.pydev.parser.jython.ast.Name;
45
import org.python.pydev.parser.jython.ast.NameTok;
46
import org.python.pydev.parser.visitors.NodeUtils;
47
import org.python.pydev.plugin.PydevPlugin;
48
import org.python.pydev.ui.UIConstants;
52
* @author Fabio Zadrozny
54
public class PyCodeCompletion {
59
public static final int TYPE_UNKNOWN = -1;
62
* Type for import (used to decide the icon)
64
public static final int TYPE_IMPORT = 0;
67
* Type for class (used to decide the icon)
69
public static final int TYPE_CLASS = 1;
72
* Type for function (used to decide the icon)
74
public static final int TYPE_FUNCTION = 2;
77
* Type for attr (used to decide the icon)
79
public static final int TYPE_ATTR = 3;
82
* Type for attr (used to decide the icon)
84
public static final int TYPE_BUILTIN = 4;
87
* Type for parameter (used to decide the icon)
89
public static final int TYPE_PARAM = 5;
92
* Type for package (used to decide the icon)
94
public static final int TYPE_PACKAGE = 6;
97
* Type for relative import
99
public static final int TYPE_RELATIVE_IMPORT = 7;
103
* Returns an image for the given type
107
public static Image getImageForType(int type){
109
ImageCache imageCache = PydevPlugin.getImageCache();
110
if (imageCache == null)
114
case PyCodeCompletion.TYPE_IMPORT:
115
return imageCache.get(UIConstants.COMPLETION_IMPORT_ICON);
117
case PyCodeCompletion.TYPE_CLASS:
118
return imageCache.get(UIConstants.COMPLETION_CLASS_ICON);
120
case PyCodeCompletion.TYPE_FUNCTION:
121
return imageCache.get(UIConstants.PUBLIC_METHOD_ICON);
123
case PyCodeCompletion.TYPE_ATTR:
124
return imageCache.get(UIConstants.PUBLIC_ATTR_ICON);
126
case PyCodeCompletion.TYPE_BUILTIN:
127
return imageCache.get(UIConstants.BUILTINS_ICON);
129
case PyCodeCompletion.TYPE_PARAM:
130
return imageCache.get(UIConstants.COMPLETION_PARAMETERS_ICON);
132
case PyCodeCompletion.TYPE_PACKAGE:
133
return imageCache.get(UIConstants.COMPLETION_PACKAGE_ICON);
135
case PyCodeCompletion.TYPE_RELATIVE_IMPORT:
136
return imageCache.get(UIConstants.COMPLETION_RELATIVE_IMPORT_ICON);
142
} catch (Exception e) {
143
PydevPlugin.log(e, false);
149
* Returns a list with the tokens to use for autocompletion.
151
* The list is composed from tuples containing the following:
153
* 0 - String - token name
154
* 1 - String - token description
155
* 2 - Integer - token type (see constants)
158
* @return list of IToken.
160
* (This is where we do the "REAL" work).
161
* @throws BadLocationException
163
public List getCodeCompletionProposals(ITextViewer viewer, CompletionRequest request) throws CoreException, BadLocationException {
165
ArrayList ret = new ArrayList();
167
IPythonNature pythonNature = request.nature;
168
if (pythonNature == null) {
169
throw new RuntimeException("Unable to get python nature.");
171
ICodeCompletionASTManager astManager = pythonNature.getAstManager();
172
if (astManager == null) { //we're probably still loading it.
173
return new ArrayList();
176
List theList = new ArrayList();
178
if (CompiledModule.COMPILED_MODULES_ENABLED) {
179
AbstractShell.getServerShell(request.nature, AbstractShell.COMPLETION_SHELL); //just start it
181
} catch (Exception e) {
182
throw new RuntimeException(e);
185
String trimmed = request.activationToken.replace('.', ' ').trim();
187
String importsTipper = getImportsTipperStr(request);
189
int line = request.doc.getLineOfOffset(request.documentOffset);
190
IRegion region = request.doc.getLineInformation(line);
192
CompletionState state = new CompletionState(line, request.documentOffset - region.getOffset(), null, request.nature);
194
boolean importsTip = false;
195
//code completion in imports
196
if (importsTipper.length() != 0) {
198
//get the project and make the code completion!!
199
//so, we want to do a code completion for imports...
200
//let's see what we have...
203
importsTipper = importsTipper.trim();
204
IToken[] imports = astManager.getCompletionsForImport(importsTipper, request);
205
theList.addAll(Arrays.asList(imports));
207
//code completion for a token
208
} else if (trimmed.equals("") == false && request.activationToken.indexOf('.') != -1) {
210
if (request.activationToken.endsWith(".")) {
211
request.activationToken = request.activationToken.substring(0, request.activationToken.length() - 1);
214
List completions = new ArrayList();
215
if (trimmed.equals("self") || trimmed.startsWith("self")) {
216
getSelfCompletions(request, theList, state);
220
state.activationToken = request.activationToken;
222
//Ok, looking for a token in globals.
223
IToken[] comps = astManager.getCompletionsForToken(request.editorFile, request.doc, state);
224
theList.addAll(Arrays.asList(comps));
226
theList.addAll(completions);
228
} else { //go to globals
230
state.activationToken = request.activationToken;
231
IToken[] comps = astManager.getCompletionsForToken(request.editorFile, request.doc, state);
233
theList.addAll(Arrays.asList(comps));
235
theList.addAll(getGlobalsFromParticipants(request, state));
238
changeItokenToCompletionPropostal(viewer, request, ret, theList, importsTip);
239
} catch (CompletionRecursionException e) {
240
ret.add(new CompletionProposal("",request.documentOffset,0,0,null,e.getMessage(), null,null));
246
private Collection getGlobalsFromParticipants(CompletionRequest request, ICompletionState state) {
247
ArrayList ret = new ArrayList();
249
List participants = ExtensionHelper.getParticipants(ExtensionHelper.PYDEV_COMPLETION);
250
for (Iterator iter = participants.iterator(); iter.hasNext();) {
251
IPyDevCompletionParticipant participant = (IPyDevCompletionParticipant) iter.next();
252
ret.addAll(participant.getGlobalCompletions(request, state));
259
* @param pythonNature
261
* @param theList OUT - returned completions are added here. (IToken instances)
264
* @return the same tokens added in theList
266
public static IToken[] getSelfCompletions(CompletionRequest request, List theList, CompletionState state) {
267
return getSelfCompletions(request, theList, state, false);
272
* @param pythonNature
274
* @param theList OUT - returned completions are added here. (IToken instances)
277
* @return the same tokens added in theList
279
public static IToken[] getSelfCompletions(CompletionRequest request, List theList, CompletionState state, boolean getOnlySupers) {
280
IToken[] comps = new IToken[0];
281
SimpleNode s = PyParser.reparseDocument(new PyParser.ParserInfo(request.doc, true, request.nature, state.line)).o1;
283
FindScopeVisitor visitor = new FindScopeVisitor(state.line, 0);
286
comps = getSelfCompletions(visitor.scope, request, theList, state, getOnlySupers);
287
} catch (Exception e1) {
288
e1.printStackTrace();
295
* Get self completions when you already have a scope
297
public static IToken[] getSelfCompletions(Scope scope, CompletionRequest request, List theList, CompletionState state, boolean getOnlySupers) throws BadLocationException {
298
IToken[] comps = new IToken[0];
299
while(scope.scope.size() > 0){
300
SimpleNode node = (SimpleNode) scope.scope.pop();
301
if(node instanceof ClassDef){
302
ClassDef d = (ClassDef) node;
305
List gottenComps = new ArrayList();
306
for (int i = 0; i < d.bases.length; i++) {
307
if(d.bases[i] instanceof Name){
308
Name n = (Name) d.bases[i];
309
state.activationToken = n.id;
310
IToken[] completions = request.nature.getAstManager().getCompletionsForToken(request.editorFile, request.doc, state);
311
gottenComps.addAll(Arrays.asList(completions));
314
comps = (IToken[]) gottenComps.toArray(new IToken[0]);
316
//ok, get the completions for the class, only thing we have to take care now is that we may
317
//not have only 'self' for completion, but somthing lile self.foo.
318
//so, let's analyze our activation token to see what should we do.
320
String trimmed = request.activationToken.replace('.', ' ').trim();
321
String[] actTokStrs = trimmed.split(" ");
322
if(actTokStrs.length == 0 || actTokStrs[0].equals("self") == false){
323
throw new AssertionError("We need to have at least one token (self) for doing completions in the class.");
326
if(actTokStrs.length == 1){
327
//ok, it's just really self, let's get on to get the completions
328
state.activationToken = NodeUtils.getNameFromNameTok((NameTok) d.name);
329
comps = request.nature.getAstManager().getCompletionsForToken(request.editorFile, request.doc, state);
332
//it's not only self, so, first we have to get the definition of the token
333
//the first one is self, so, just discard it, and go on, token by token to know what is the last
334
//one we are completing (e.g.: self.foo.bar)
335
int line = request.doc.getLineOfOffset(request.documentOffset);
336
IRegion region = request.doc.getLineInformationOfOffset(request.documentOffset);
337
int col = request.documentOffset - region.getOffset();
338
IModule module = AbstractModule.createModuleFromDoc("", null, request.doc, request.nature, line);
340
ASTManager astMan = ((ASTManager)request.nature.getAstManager());
341
comps = astMan.getAssignCompletions(module, new CompletionState(line, col, request.activationToken, request.nature));
345
theList.addAll(Arrays.asList(comps));
355
* @param convertedProposals
359
private void changeItokenToCompletionPropostal(ITextViewer viewer, CompletionRequest request, List convertedProposals, List iTokenList, boolean importsTip) {
360
//TODO: check org.eclipse.jface.text.templates.TemplateCompletionProcessor to see how to do custom 'selections' in completions
361
// int offset = request.documentOffset;
362
// ITextSelection selection= (ITextSelection) viewer.getSelectionProvider().getSelection();
364
// // adjust offset to end of normalized selection
365
// if (selection.getOffset() == offset)
366
// offset= selection.getOffset() + selection.getLength();
368
// String prefix= extractPrefix(viewer, offset);
369
// Region region= new Region(offset - prefix.length(), prefix.length());
371
// TemplateContextType contextType= getContextType(viewer, region);
372
// if (contextType != null) {
373
// IDocument document= viewer.getDocument();
374
// new DocumentTemplateContext(contextType, document, region.getOffset(), region.getLength());
378
for (Iterator iter = iTokenList.iterator(); iter.hasNext();) {
380
Object obj = iter.next();
382
if(obj instanceof IToken){
383
IToken element = (IToken) obj;
385
String name = element.getRepresentation();
388
int l = name.length();
392
args = getArgs(element);
394
l++; //cursor position is name + '('
399
String docStr = element.getDocStr();
400
int type = element.getType();
402
int priority = IPyCompletionProposal.PRIORITY_DEFAULT;
403
if(type == PyCodeCompletion.TYPE_PARAM){
404
priority = IPyCompletionProposal.PRIORITY_LOCALS;
407
PyCompletionProposal proposal = new PyCompletionProposal(name+args,
408
request.documentOffset - request.qlen, request.qlen, l, getImageForType(type), null, null, docStr, priority);
410
convertedProposals.add(proposal);
412
}else if(obj instanceof Object[]){
413
Object element[] = (Object[]) obj;
415
String name = (String) element[0];
416
String docStr = (String) element [1];
418
if(element.length > 2){
419
type = ((Integer) element [2]).intValue();
422
int priority = IPyCompletionProposal.PRIORITY_DEFAULT;
423
if(type == PyCodeCompletion.TYPE_PARAM){
424
priority = IPyCompletionProposal.PRIORITY_LOCALS;
427
PyCompletionProposal proposal = new PyCompletionProposal(name,
428
request.documentOffset - request.qlen, request.qlen, name.length(), getImageForType(type), null, null, docStr, priority);
430
convertedProposals.add(proposal);
432
}else if(obj instanceof ICompletionProposal){
434
convertedProposals.add(obj);
447
private String getArgs(IToken element) {
449
if(element.getArgs().trim().length() > 0){
450
StringBuffer buffer = new StringBuffer("(");
451
StringTokenizer strTok = new StringTokenizer(element.getArgs(), "( ,)");
453
while(strTok.hasMoreTokens()){
454
String tok = strTok.nextToken();
455
if(tok.equals("self") == false){
456
if(buffer.length() > 1){
463
args = buffer.toString();
464
} else if (element.getType() == PyCodeCompletion.TYPE_FUNCTION){
474
* Returns non empty string if we are in imports section
476
* @param theActivationToken
479
* @param documentOffset
480
* @return single space string if we are in imports but without any module
481
* string with current module (e.g. foo.bar.
483
public String getImportsTipperStr(CompletionRequest request) {
485
IDocument doc = request.doc;
486
int documentOffset = request.documentOffset;
488
return getImportsTipperStr(doc, documentOffset);
491
public static String getImportsTipperStr(IDocument doc, int documentOffset) {
494
region = doc.getLineInformationOfOffset(documentOffset);
495
String trimmedLine = doc.get(region.getOffset(), documentOffset-region.getOffset());
496
trimmedLine = trimmedLine.trim();
497
return getImportsTipperStr(trimmedLine, true);
498
} catch (BadLocationException e) {
499
throw new RuntimeException(e);
505
* @param documentOffset
508
public static String getImportsTipperStr(String trimmedLine, boolean returnEvenEmpty) {
509
String importMsg = "";
511
if(!trimmedLine.startsWith("from") && !trimmedLine.startsWith("import")){
515
int fromIndex = trimmedLine.indexOf("from");
516
int importIndex = trimmedLine.indexOf("import");
518
//check if we have a from or an import.
519
if(fromIndex != -1 || importIndex != -1){
520
trimmedLine = trimmedLine.replaceAll("#.*", ""); //remove comments
521
String[] strings = trimmedLine.split(" ");
523
if(fromIndex != -1 && importIndex == -1){
524
if(strings.length > 2){
525
//user has spaces as in 'from xxx uuu'
531
for (int i = 0; i < strings.length; i++) {
532
if(strings[i].equals("from")==false && strings[i].equals("import")==false){
533
if(importMsg.length() != 0){
536
importMsg += strings[i];
540
if(fromIndex != -1 && importIndex != -1){
541
if(strings.length == 3){
548
if (importMsg.indexOf(".") == -1){
549
if(returnEvenEmpty || importMsg.trim().length() > 0){
550
return " "; //we have only import fff or from iii (so, we're going for all imports).
552
return ""; //we have only import fff or from iii (so, we're going for all imports).
556
//now, we may still have something like 'unittest.test,' or 'unittest.test.,'
557
//so, we have to remove this comma (s).
559
while ( ( i = importMsg.indexOf(',')) != -1){
560
if(importMsg.charAt(i-1) == '.'){
561
int j = importMsg.lastIndexOf('.');
562
importMsg = importMsg.substring(0, j);
565
int j = importMsg.lastIndexOf('.');
566
importMsg = importMsg.substring(0, j);
569
//if it is something like aaa.sss.bb : removes the bb because it is the qualifier
570
//if it is something like aaa.sss. : removes only the last point
571
if (importMsg.length() > 0 && importMsg.indexOf('.') != -1){
572
importMsg = importMsg.substring(0, importMsg.lastIndexOf('.'));
580
* Return a document to parse, using some heuristics to make it parseable.
583
* @param documentOffset
586
public static String getDocToParse(IDocument doc, int documentOffset) {
587
int lineOfOffset = -1;
589
lineOfOffset = doc.getLineOfOffset(documentOffset);
590
} catch (BadLocationException e) {
594
if(lineOfOffset!=-1){
595
String docToParseFromLine = getDocToParseFromLine(doc, lineOfOffset);
596
if(docToParseFromLine != null)
597
return docToParseFromLine;
598
// return "\n"+docToParseFromLine;
607
* Return a document to parse, using some heuristics to make it parseable.
608
* (Changes the line specified by a pass)
611
* @param documentOffset
612
* @param lineOfOffset
615
public static String getDocToParseFromLine(IDocument doc, int lineOfOffset) {
616
return DocUtils.getDocToParseFromLine(doc, lineOfOffset);
621
* @param useSimpleTipper
622
* @return the script to get the variables.
624
* @throws CoreException
626
public static File getAutoCompleteScript() throws CoreException {
627
return PydevPlugin.getScriptWithinPySrc("simpleTipper.py");
632
* @param pythonAndTemplateProposals
636
public ICompletionProposal[] onlyValidSorted(List pythonAndTemplateProposals, String qualifier) {
637
//FOURTH: Now, we have all the proposals, only thing is deciding wich ones are valid (depending on
638
//qualifier) and sorting them correctly.
639
Collection returnProposals = new HashSet();
640
String lowerCaseQualifier = qualifier.toLowerCase();
642
for (Iterator iter = pythonAndTemplateProposals.iterator(); iter.hasNext();) {
643
Object o = iter.next();
644
if (o instanceof ICompletionProposal) {
645
ICompletionProposal proposal = (ICompletionProposal) o;
647
if (proposal.getDisplayString().toLowerCase().startsWith(lowerCaseQualifier)) {
648
returnProposals.add(proposal);
651
throw new RuntimeException("Error: expected instanceof ICompletionProposal and received: "+o.getClass().getName());
655
ICompletionProposal[] proposals = new ICompletionProposal[returnProposals.size()];
657
// and fill with list elements
658
returnProposals.toArray(proposals);
660
Arrays.sort(proposals, PROPOSAL_COMPARATOR);
665
* Compares proposals so that we can order them.
667
public static final ProposalsComparator PROPOSAL_COMPARATOR = new ProposalsComparator();
b'\\ No newline at end of file'