@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);
    }
  }