@Override public void addProps( MarkerAnnotationAndPosition markerAnnotation, IAnalysisPreferences analysisPreferences, String line, PySelection ps, int offset, IPythonNature nature, PyEdit edit, List<ICompletionProposal> props) throws BadLocationException, CoreException { if (nature == null) { return; } ICodeCompletionASTManager astManager = nature.getAstManager(); if (astManager == null) { return; } if (markerAnnotation.position == null) { return; } IMarker marker = markerAnnotation.markerAnnotation.getMarker(); Integer id = (Integer) marker.getAttribute(AnalysisRunner.PYDEV_ANALYSIS_TYPE); int start = markerAnnotation.position.offset; int end = start + markerAnnotation.position.length; ps.setSelection(start, end); String markerContents; try { markerContents = ps.getSelectedText(); } catch (Exception e1) { return; // Selection may be wrong. } IDocument doc = ps.getDoc(); List<String> parametersAfterCall = ps.getParametersAfterCall(end); switch (id) { case IAnalysisPreferences.TYPE_UNDEFINED_VARIABLE: addCreateClassOption(ps, edit, props, markerContents, parametersAfterCall); addCreateMethodOption(ps, edit, props, markerContents, parametersAfterCall); break; case IAnalysisPreferences.TYPE_UNDEFINED_IMPORT_VARIABLE: // Say we had something as: // import sys // sys.Bar // in which case 'Bar' is undefined // in this situation, the activationTokenAndQual would be "sys." and "Bar" // and we want to get the definition for "sys" String[] activationTokenAndQual = ps.getActivationTokenAndQual(true); if (activationTokenAndQual[0].endsWith(".")) { ArrayList<IDefinition> selected = findDefinitions(nature, edit, start - 2, doc); for (IDefinition iDefinition : selected) { IModule module = iDefinition.getModule(); if (module.getFile() != null) { Definition definition = (Definition) iDefinition; File file = module.getFile(); if (definition.ast == null) { // if we have no ast in the definition, it means the module itself was found (global // scope) // Add option to create class at the given module! addCreateClassOption(ps, edit, props, markerContents, parametersAfterCall, file); addCreateMethodOption(ps, edit, props, markerContents, parametersAfterCall, file); } else if (definition.ast instanceof ClassDef) { ClassDef classDef = (ClassDef) definition.ast; // Ok, we should create a field or method in this case (accessing a classmethod or // staticmethod) PyCreateMethodOrField pyCreateMethod = new PyCreateMethodOrField(); String className = NodeUtils.getNameFromNameTok(classDef.name); pyCreateMethod.setCreateInClass(className); pyCreateMethod.setCreateAs(PyCreateMethodOrField.CLASSMETHOD); addCreateClassmethodOption( ps, edit, props, markerContents, parametersAfterCall, pyCreateMethod, file, className); } } } } break; case IAnalysisPreferences.TYPE_UNRESOLVED_IMPORT: // This case is the following: from other_module4 import Foo // with 'Foo' being undefined. // So, we have to suggest creating a Foo class/method in other_module4 PyImportsHandling importsHandling = new PyImportsHandling(ps.getDoc(), false); int offsetLine = ps.getLineOfOffset(start); String selectedText = ps.getSelectedText(); Tuple<IModule, String> found = null; String foundFromImportStr = null; boolean isImportFrom = false; OUT: for (ImportHandle handle : importsHandling) { if (handle.startFoundLine == offsetLine || handle.endFoundLine == offsetLine || (handle.startFoundLine < offsetLine && handle.endFoundLine > offsetLine)) { List<ImportHandleInfo> importInfo = handle.getImportInfo(); for (ImportHandleInfo importHandleInfo : importInfo) { String fromImportStr = importHandleInfo.getFromImportStr(); List<String> importedStr = importHandleInfo.getImportedStr(); for (String imported : importedStr) { if (selectedText.equals(imported)) { if (fromImportStr != null) { foundFromImportStr = fromImportStr + "." + imported; isImportFrom = true; } else { // if fromImportStr == null, it's not a from xxx import yyy (i.e.: simple // import) foundFromImportStr = imported; } try { String currentModule = nature.resolveModule(edit.getEditorFile()); ICompletionState state = CompletionStateFactory.getEmptyCompletionState( nature, new CompletionCache()); found = nature .getAstManager() .findModule( foundFromImportStr, currentModule, state, new SourceModule( currentModule, edit.getEditorFile(), edit.getAST(), null)); } catch (Exception e) { Log.log(e); } break OUT; } } } break OUT; } } boolean addOptionToCreateClassOrMethod = isImportFrom; if (found != null && found.o1 != null) { // Ok, we found a module, now, it may be that we still have to create some intermediary // modules // or just create a class or method at the end. if (found.o1 instanceof SourceModule) { // if all was found, there's nothing left to create. if (found.o2 != null && found.o2.length() > 0) { SourceModule sourceModule = (SourceModule) found.o1; File file = sourceModule.getFile(); if (found.o2.indexOf('.') != -1) { // We have to create some intermediary structure. if (!addOptionToCreateClassOrMethod) { // Cannot create class or method from the info (only the module structure). if (sourceModule.getName().endsWith(".__init__")) { File f = getFileStructure(file.getParentFile(), found.o2); addCreateModuleOption(ps, edit, props, markerContents, f); } } else { // Ok, the leaf may be a class or method. if (sourceModule.getName().endsWith(".__init__")) { String moduleName = FullRepIterable.getWithoutLastPart(sourceModule.getName()); String withoutLastPart = FullRepIterable.getWithoutLastPart(found.o2); moduleName += "." + withoutLastPart; String classOrMethodName = FullRepIterable.getLastPart(found.o2); File f = getFileStructure(file.getParentFile(), withoutLastPart); addCreateClassInNewModuleOption( ps, edit, props, classOrMethodName, moduleName, parametersAfterCall, f); addCreateMethodInNewModuleOption( ps, edit, props, classOrMethodName, moduleName, parametersAfterCall, f); } } } else { // Ok, it's all there, we just have to create the leaf. if (!addOptionToCreateClassOrMethod || sourceModule.getName().endsWith(".__init__")) { // Cannot create class or method from the info (only the module structure). if (sourceModule.getName().endsWith(".__init__")) { File f = new File( file.getParent(), found.o2 + FileTypesPreferencesPage.getDefaultDottedPythonExtension()); addCreateModuleOption(ps, edit, props, markerContents, f); } } else { // Ok, the leaf may be a class or method. addCreateClassOption(ps, edit, props, markerContents, parametersAfterCall, file); addCreateMethodOption(ps, edit, props, markerContents, parametersAfterCall, file); } } } } } else if (foundFromImportStr != null) { // We couldn't find anything there, so, we have to create the modules structure as needed // and // maybe create a class or module at the end (but only if it's an import from). // Ok, it's all there, we just have to create the leaf. // Discover the source folder where we should create the structure. File editorFile = edit.getEditorFile(); String onlyProjectPythonPathStr = nature.getPythonPathNature().getOnlyProjectPythonPathStr(false); List<String> split = StringUtils.splitAndRemoveEmptyTrimmed(onlyProjectPythonPathStr, '|'); for (int i = 0; i < split.size(); i++) { String fullPath = FileUtils.getFileAbsolutePath(split.get(i)); fullPath = PythonPathHelper.getDefaultPathStr(fullPath); split.set(i, fullPath); } HashSet<String> projectSourcePath = new HashSet<String>(split); if (projectSourcePath.size() == 0) { return; // No source folder for editor... this shouldn't happen (code analysis wouldn't // even run on it). } String fullPath = FileUtils.getFileAbsolutePath(editorFile); fullPath = PythonPathHelper.getDefaultPathStr(fullPath); String foundSourceFolderFullPath = null; if (projectSourcePath.size() == 1) { foundSourceFolderFullPath = projectSourcePath.iterator().next(); } else { for (String string : projectSourcePath) { if (fullPath.startsWith(string)) { // Use this as the source folder foundSourceFolderFullPath = string; break; } } } if (foundSourceFolderFullPath != null) { if (!addOptionToCreateClassOrMethod) { // Cannot create class or method from the info (only the module structure). File f = getFileStructure(new File(foundSourceFolderFullPath), foundFromImportStr); addCreateModuleOption(ps, edit, props, foundFromImportStr, f); } else { // Ok, the leaf may be a class or method. String moduleName = FullRepIterable.getWithoutLastPart(foundFromImportStr); File file = getFileStructure(new File(foundSourceFolderFullPath), moduleName); String lastPart = FullRepIterable.getLastPart(foundFromImportStr); addCreateClassInNewModuleOption( ps, edit, props, lastPart, moduleName, parametersAfterCall, file); addCreateMethodInNewModuleOption( ps, edit, props, lastPart, moduleName, parametersAfterCall, file); } } } break; } }
/** * Note: This apply is not directly called (it's called through {@link * CtxInsensitiveImportComplProposal#apply(ITextViewer, char, int, int)}) * * <p>This is the point where the completion is written. It has to be written and if some import * is also available it should be inserted at this point. * * <p>We have to be careful to only add an import if that's really needed (e.g.: there's no other * import that equals the import that should be added). * * <p>Also, we have to check if this import should actually be grouped with another import that * already exists. (and it could be a multi-line import) */ public void apply(IDocument document, char trigger, int stateMask, int offset) { if (this.indentString == null) { throw new RuntimeException("Indent string not set (not called with a PyEdit as viewer?)"); } if (!triggerCharAppliesCurrentCompletion(trigger, document, offset)) { newForcedOffset = offset + 1; // +1 because that's the len of the trigger return; } try { PySelection selection = new PySelection(document); int lineToAddImport = -1; ImportHandleInfo groupInto = null; ImportHandleInfo realImportHandleInfo = null; boolean groupImports = ImportsPreferencesPage.getGroupImports(); LineStartingScope previousLineThatStartsScope = null; PySelection ps = null; if (this.addLocalImport) { ps = new PySelection(document, offset); int startLineIndex = ps.getStartLineIndex(); if (startLineIndex == 0) { this.addLocalImport = false; } else { previousLineThatStartsScope = ps.getPreviousLineThatStartsScope( PySelection.INDENT_TOKENS, startLineIndex - 1, PySelection.getFirstCharPosition(ps.getCursorLineContents())); if (previousLineThatStartsScope == null) { // note that if we have no previous scope, it means we're actually on the global scope, // so, // proceed as usual... this.addLocalImport = false; } } } if (realImportRep.length() > 0 && !this.addLocalImport) { // Workaround for: // https://sourceforge.net/tracker/?func=detail&aid=2697165&group_id=85796&atid=577329 // when importing from __future__ import with_statement, we actually want to add a 'with' // token, not // with_statement token. boolean isWithStatement = realImportRep.equals("from __future__ import with_statement"); if (isWithStatement) { this.fReplacementString = "with"; } if (groupImports) { try { realImportHandleInfo = new ImportHandleInfo(realImportRep); PyImportsHandling importsHandling = new PyImportsHandling(document); for (ImportHandle handle : importsHandling) { if (handle.contains(realImportHandleInfo)) { lineToAddImport = -2; // signal that there's no need to find a line available to add the import break; } else if (groupInto == null && realImportHandleInfo.getFromImportStr() != null) { List<ImportHandleInfo> handleImportInfo = handle.getImportInfo(); for (ImportHandleInfo importHandleInfo : handleImportInfo) { if (realImportHandleInfo .getFromImportStr() .equals(importHandleInfo.getFromImportStr())) { List<String> commentsForImports = importHandleInfo.getCommentsForImports(); if (commentsForImports.size() > 0 && commentsForImports.get(commentsForImports.size() - 1).length() == 0) { groupInto = importHandleInfo; break; } } } } } } catch (ImportNotRecognizedException e1) { Log.log(e1); // that should not happen at this point } } if (lineToAddImport == -1) { boolean isFutureImport = PySelection.isFutureImportLine(this.realImportRep); lineToAddImport = selection.getLineAvailableForImport(isFutureImport); } } else { lineToAddImport = -1; } String delimiter = PyAction.getDelimiter(document); appliedWithTrigger = trigger == '.' || trigger == '('; String appendForTrigger = ""; if (appliedWithTrigger) { if (trigger == '(') { appendForTrigger = "()"; } else if (trigger == '.') { appendForTrigger = "."; } } // if the trigger is ')', just let it apply regularly -- so, ')' will only be added if it's // already in the completion. // first do the completion if (fReplacementString.length() > 0) { int dif = offset - fReplacementOffset; document.replace(offset - dif, dif + this.fLen, fReplacementString + appendForTrigger); } if (this.addLocalImport) { // All the code below is because we don't want to work with a generated AST (as it may not // be there), // so, we go to the previous scope, find out the valid indent inside it and then got // backwards // from the position we're in now to find the closer location to where we're now where we're // actually able to add the import. try { int iLineStartingScope; if (previousLineThatStartsScope != null) { iLineStartingScope = previousLineThatStartsScope.iLineStartingScope; // Go to a non-empty line from the line we have and the line we're currently in. int iLine = iLineStartingScope + 1; String line = ps.getLine(iLine); int startLineIndex = ps.getStartLineIndex(); while (iLine < startLineIndex && (line.startsWith("#") || line.trim().length() == 0)) { iLine++; line = ps.getLine(iLine); } if (iLine >= startLineIndex) { // Sanity check! iLine = startLineIndex; line = ps.getLine(iLine); } int firstCharPos = PySelection.getFirstCharPosition(line); // Ok, all good so far, now, this would add the line to the beginning of // the element (after the if statement, def, etc.), let's try to put // it closer to where we're now (but still in a valid position). int j = startLineIndex; while (j >= 0) { String line2 = ps.getLine(j); if (PySelection.getFirstCharPosition(line2) == firstCharPos) { iLine = j; break; } if (j == iLineStartingScope) { break; } j--; } String indent = line.substring(0, firstCharPos); String strToAdd = indent + realImportRep + delimiter; ps.addLine( strToAdd, iLine - 1); // Will add it just after the line passed as a parameter. importLen = strToAdd.length(); return; } } catch (Exception e) { Log.log(e); // Something went wrong, add it as global (i.e.: BUG) } } if (groupInto != null && realImportHandleInfo != null) { // let's try to group it final int maxCols; if (SharedCorePlugin.inTestMode()) { maxCols = 80; } else { IPreferenceStore chainedPrefStore = PydevPrefs.getChainedPrefStore(); maxCols = chainedPrefStore.getInt( AbstractDecoratedTextEditorPreferenceConstants.EDITOR_PRINT_MARGIN_COLUMN); } int endLine = groupInto.getEndLine(); IRegion lineInformation = document.getLineInformation(endLine); String strToAdd = ", " + realImportHandleInfo.getImportedStr().get(0); String line = PySelection.getLine(document, endLine); if (line.length() + strToAdd.length() > maxCols) { if (line.indexOf('#') == -1) { // no comments: just add it in the next line int len = line.length(); if (line.trim().endsWith(")")) { len = line.indexOf(")"); strToAdd = "," + delimiter + indentString + realImportHandleInfo.getImportedStr().get(0); } else { strToAdd = ",\\" + delimiter + indentString + realImportHandleInfo.getImportedStr().get(0); } int end = lineInformation.getOffset() + len; importLen = strToAdd.length(); document.replace(end, 0, strToAdd); return; } } else { // regular addition (it won't pass the number of columns expected). line = PySelection.getLineWithoutCommentsOrLiterals(line); int len = line.length(); if (line.trim().endsWith(")")) { len = line.indexOf(")"); } int end = lineInformation.getOffset() + len; importLen = strToAdd.length(); document.replace(end, 0, strToAdd); return; } } // if we got here, it hasn't been added in a grouped way, so, let's add it in a new import if (lineToAddImport >= 0 && lineToAddImport <= document.getNumberOfLines()) { IRegion lineInformation = document.getLineInformation(lineToAddImport); String strToAdd = realImportRep + delimiter; importLen = strToAdd.length(); document.replace(lineInformation.getOffset(), 0, strToAdd); return; } } catch (BadLocationException x) { Log.log(x); } }