public OffsetRange getReferenceSpan(Document doc, int lexOffset) {
    TokenHierarchy<Document> th = TokenHierarchy.get(doc);

    // BaseDocument doc = (BaseDocument)document;

    TokenSequence<? extends PythonTokenId> ts = PythonLexerUtils.getPythonSequence(th, lexOffset);

    if (ts == null) {
      return OffsetRange.NONE;
    }

    ts.move(lexOffset);

    if (!ts.moveNext() && !ts.movePrevious()) {
      return OffsetRange.NONE;
    }

    // Determine whether the caret position is right between two tokens
    boolean isBetween = (lexOffset == ts.offset());

    OffsetRange range = getReferenceSpan(ts, th, lexOffset);

    if ((range == OffsetRange.NONE) && isBetween) {
      // The caret is between two tokens, and the token on the right
      // wasn't linkable. Try on the left instead.
      if (ts.movePrevious()) {
        range = getReferenceSpan(ts, th, lexOffset);
      }
    }

    return range;
  }
  public IndexedMethod findMethodDeclaration(
      PythonParserResult info,
      org.python.antlr.ast.Call call,
      AstPath path,
      Set<IndexedMethod>[] alternativesHolder) {
    PythonParserResult parseResult = PythonAstUtils.getParseResult(info);
    PythonIndex index = PythonIndex.get(info.getSnapshot().getSource().getFileObject());
    Set<IndexedElement> functions = null;

    // TODO - do more accurate lookup of types here!
    // (a) For functions, look in imported symbols first!
    // (b) For methods, try to resolve the lhs type first and search within specific types!

    String callName = PythonAstUtils.getCallName(call);
    if (callName == null) {
      return null;
    }

    if (call.getInternalFunc() instanceof Attribute) {
      // Method/member access
      functions = index.getAllMembers(callName, QuerySupport.Kind.EXACT, parseResult, false);
    } else {
      functions = index.getAllElements(callName, QuerySupport.Kind.EXACT, parseResult, false);
    }

    if (functions != null && functions.size() > 0) {
      Set<IndexedElement> eligible = new HashSet<IndexedElement>();
      for (IndexedElement element : functions) {
        if (element instanceof IndexedMethod) {
          eligible.add(element);
        }
      }

      int astOffset = call.getCharStartIndex();
      int lexOffset = PythonLexerUtils.getLexerOffset(info, astOffset);
      IndexedElement candidate =
          findBestMatch(
              info,
              callName,
              eligible,
              (BaseDocument) info.getSnapshot().getSource().getDocument(false),
              astOffset,
              lexOffset,
              path,
              call,
              index);
      assert candidate instanceof IndexedMethod; // Filtered into earlier already
      return (IndexedMethod) candidate;
    }

    return null;
  }
  @SuppressWarnings("empty-statement")
  private DeclarationLocation findUrl(PythonParserResult info, Document doc, int lexOffset) {
    TokenSequence<?> ts = PythonLexerUtils.getPythonSequence((BaseDocument) doc, lexOffset);

    if (ts == null) {
      return DeclarationLocation.NONE;
    }

    ts.move(lexOffset);

    if (!ts.moveNext() && !ts.movePrevious()) {
      return DeclarationLocation.NONE;
    }

    Token<?> token = ts.token();

    TokenSequence<?> embedded = ts.embedded();

    if (embedded != null) {
      ts = embedded;

      embedded.move(lexOffset);

      if (!embedded.moveNext() && !embedded.movePrevious()) {
        return DeclarationLocation.NONE;
      }

      token = embedded.token();
    }

    // Is this a comment? If so, possibly do rdoc-method reference jump
    if ((token != null) && (token.id() == PythonStringTokenId.URL)) {
      // TODO - use findLinkedMethod
      String method = token.text().toString();
      if (method.startsWith("www.")) { // NOI18N
        method = "http://" + method; // NOI18N
      }

      // A URL such as http://netbeans.org - try to open it in a browser!
      try {
        URL url = new URL(method);

        return new DeclarationLocation(url);
      } catch (MalformedURLException mue) {
        // URL is from user source... don't complain with exception dialogs etc.
        ;
      }
    }

    return DeclarationLocation.NONE;
  }
  @Override
  public DeclarationLocation findDeclaration(ParserResult info, int lexOffset) {

    final Document document = info.getSnapshot().getSource().getDocument(false);
    if (document == null) {
      return DeclarationLocation.NONE;
    }

    final BaseDocument doc = (BaseDocument) document;

    PythonParserResult parseResult = PythonAstUtils.getParseResult(info);
    doc.readLock(); // Read-lock due to Token hierarchy use
    try {
      PythonTree root = PythonAstUtils.getRoot(parseResult);
      final int astOffset = PythonAstUtils.getAstOffset(info, lexOffset);
      if (astOffset == -1) {
        return DeclarationLocation.NONE;
      }

      AstPath path = null;
      PythonTree node = null;
      if (root != null) {
        path = AstPath.get(root, astOffset);
        node = path.leaf();
      }

      // See if it's an import
      DeclarationLocation imp = findImport(parseResult, lexOffset, doc);
      if (imp != DeclarationLocation.NONE) {
        return imp;
      }

      DeclarationLocation url = findUrl(parseResult, doc, lexOffset);
      if (url != DeclarationLocation.NONE) {
        return url;
      }

      final TokenHierarchy<Document> th = TokenHierarchy.get(document);
      org.netbeans.modules.python.editor.lexer.Call call =
          org.netbeans.modules.python.editor.lexer.Call.getCallType(doc, th, lexOffset);

      FileObject fileObject = info.getSnapshot().getSource().getFileObject();

      // Search for local variables
      if (root != null && call.getLhs() == null) {
        if ((path.leaf() instanceof Name)) {
          PythonTree scope = PythonAstUtils.getLocalScope(path);
          SymbolTable symbolTable = parseResult.getSymbolTable();

          String name = ((Name) path.leaf()).getInternalId();

          SymInfo sym = symbolTable.findDeclaration(scope, name, true);
          if (sym != null) {
            if (sym.isFree()) {
              PythonIndex index = PythonIndex.get(fileObject);

              List<Import> imports = symbolTable.getImports();
              List<ImportFrom> importsFrom = symbolTable.getImportsFrom();
              Set<IndexedElement> elements =
                  index.getImportedElements(
                      name, QuerySupport.Kind.EXACT, parseResult, imports, importsFrom);
              if (elements != null && elements.size() > 0) {
                return getDeclaration(
                    parseResult, null /*name*/, elements, path, node, index, astOffset, lexOffset);
              }
              // Must be defined by one of the imported symbols
            }
            if (sym.node != null) {
              PythonTree declNode = sym.node;
              if (sym
                  .isImported()) { // Rather than showing the import symbol go to the definition in
                                   // the library!
                // Determine if it's an "as" name (import foo as bar) and if so just show the "as",
                // if not,
                // follow through to the library
                if (declNode instanceof Import) {
                  Import impNode = (Import) declNode;
                  List<alias> names = impNode.getInternalNames();
                  if (names != null) {
                    for (alias at : names) {
                      if (at.getInternalAsname() != null && name.equals(at.getInternalAsname())) {
                        break;
                      } else if (at.getInternalName().equals(name)) {
                        // We found our library - just show it
                        return findImport(parseResult, name, null);
                      }
                    }
                  }
                } else {
                  assert declNode instanceof ImportFrom : declNode;
                  ImportFrom impNode = (ImportFrom) declNode;
                  List<alias> names = impNode.getInternalNames();
                  if (names != null) {
                    for (alias at : names) {
                      if (at.getInternalAsname() != null && name.equals(at.getInternalAsname())) {
                        break;
                      } else if (at.getInternalName().equals(name)) {
                        // We found our library - just show it
                        return findImport(parseResult, impNode.getInternalModule(), name);
                      }
                    }
                  }
                }
              }

              if (sym.isUnresolved()) {
                PythonIndex index = PythonIndex.get(fileObject);

                List<Import> imports = symbolTable.getImports();
                List<ImportFrom> importsFrom = symbolTable.getImportsFrom();
                Set<IndexedElement> elements =
                    index.getImportedElements(
                        name, QuerySupport.Kind.EXACT, parseResult, imports, importsFrom);
                if (elements != null && elements.size() > 0) {
                  return getDeclaration(
                      parseResult,
                      null /*name*/,
                      elements,
                      path,
                      node,
                      index,
                      astOffset,
                      lexOffset);
                }
              } else {
                OffsetRange astRange = PythonAstUtils.getNameRange(null, declNode);
                int lexerOffset = PythonLexerUtils.getLexerOffset(parseResult, astRange.getStart());
                if (lexerOffset == -1) {
                  lexerOffset = 0;
                }
                return new DeclarationLocation(fileObject, lexerOffset);
              }
            }
          }
          //
          //                    List<Name> localVars = PythonAstUtils.getLocalVarNodes(info, scope,
          // name);
          //                    if (localVars.size() > 0) {
          //                        return new DeclarationLocation(info.getFileObject(),
          // PythonAstUtils.getRange(localVars.get(0)).getStart());
          //                    }
        }
      }

      // I'm not doing any data flow analysis at this point, so
      // I can't do anything with a LHS like "foo.". Only actual types.
      String type = call.getType();
      if (type != null && "self".equals(type)) { // NOI18N
        type = PythonAstUtils.getFqnName(path);
      }
      if (type != null && type.length() > 0) {
        String name = null;
        PythonTree leaf = path.leaf();
        if (leaf instanceof Name) {
          name = ((Name) path.leaf()).getInternalId();
        } else if (leaf instanceof Attribute) {
          name = ((Attribute) leaf).getInternalAttr();
        }

        if (name != null) {
          PythonIndex index = PythonIndex.get(fileObject);
          // Add methods in the class (without an FQN)
          Set<IndexedElement> elements =
              index.getInheritedElements(type, name, QuerySupport.Kind.EXACT);
          if (elements != null && elements.size() > 0) {
            return getDeclaration(
                parseResult, null /*name*/, elements, path, node, index, astOffset, lexOffset);
          }
        }
      }

      // Fallback: Index search on all names
      String prefix = new PythonCodeCompleter().getPrefix(info, lexOffset, false);
      if (prefix == null) {
        try {
          prefix = Utilities.getIdentifier(doc, lexOffset);
        } catch (BadLocationException ex) {
          Exceptions.printStackTrace(ex);
        }
      }
      if (prefix != null) {
        PythonIndex index = PythonIndex.get(fileObject);

        Set<? extends IndexedElement> elements = null;
        if (prefix.length() > 0 && Character.isUpperCase(prefix.charAt(0))) {
          elements = index.getClasses(prefix, QuerySupport.Kind.EXACT, parseResult, true);
        }

        if (elements == null || elements.size() == 0) {
          elements = index.getAllElements(prefix, QuerySupport.Kind.EXACT, parseResult, true);
        }

        if (elements == null || elements.size() == 0) {
          elements = index.getAllMembers(prefix, QuerySupport.Kind.EXACT, parseResult, true);
        }

        if (elements != null && elements.size() > 0) {
          return getDeclaration(
              parseResult, null /*name*/, elements, path, node, index, astOffset, lexOffset);
        }

        // TODO - classes
        // WORKING HERE
        //                if (elements == null || elements.size() == 0) {
        //                    elements = index.getClasses(prefix, QuerySupport.Kind.EXACT,
        // PythonIndex.ALL_SCOPE, parseResult, true);
        //                }
        //                if (elements != null && elements.size() > 0) {
        //                    String name = null; // unused!
        //
        //                    return getMethodDeclaration(info, name, elements,
        //                         path, null, index, astOffset, lexOffset);
        //                }
      }
    } finally {
      doc.readUnlock();
    }
    return DeclarationLocation.NONE;
  }
  private DeclarationLocation findImport(PythonParserResult info, int lexOffset, BaseDocument doc) {
    TokenSequence<? extends PythonTokenId> ts =
        PythonLexerUtils.getPositionedSequence(doc, lexOffset);
    if (ts == null) {
      return DeclarationLocation.NONE;
    }
    if (ts.offset() == lexOffset) {
      // We're looking at the offset to the RIGHT of the caret
      // and here I care about what's on the left
      if (!ts.movePrevious()) {
        return DeclarationLocation.NONE;
      }
    }

    Token<? extends PythonTokenId> token = ts.token();
    if (token == null) {
      return DeclarationLocation.NONE;
    }

    TokenId id = token.id();

    String moduleName = null;
    while (true) {
      if (id == PythonTokenId.IDENTIFIER || id.primaryCategory().equals(PythonLexer.KEYWORD_CAT)) {
        // Possibly inside the import string
        String tokenText = token.text().toString();
        if (moduleName == null) {
          moduleName = tokenText;
        } else {
          moduleName = tokenText + "." + moduleName;
        }
      } else if (id != PythonTokenId.DOT) {
        break;
      }
      if (!ts.movePrevious()) {
        return DeclarationLocation.NONE;
      }
      token = ts.token();
      id = token.id();
    }

    if (id != PythonTokenId.ERROR
        && id != PythonTokenId.NEWLINE
        && id != PythonTokenId.WHITESPACE) {
      return DeclarationLocation.NONE;
    }

    if (!ts.movePrevious()) {
      return DeclarationLocation.NONE;
    }
    token = ts.token();
    id = token.id();
    if (id != PythonTokenId.IMPORT) {
      return DeclarationLocation.NONE;
    }
    if (moduleName == null) {
      return DeclarationLocation.NONE;
    }

    if (id == PythonTokenId.IMPORT || id == PythonTokenId.FROM) {
      if (id == PythonTokenId.IMPORT
          && ts.movePrevious()
          && ts.token().id() == PythonTokenId.WHITESPACE
          && ts.movePrevious()) {
        // See if this was "from foo import bar" such that we really should
        // be listing symbols inside the foo library
        token = ts.token();
        id = token.id();
        String library = null;
        while (true) {
          if (id == PythonTokenId.IDENTIFIER
              || id.primaryCategory().equals(PythonLexer.KEYWORD_CAT)) {
            // Possibly inside the import string
            String tokenText = token.text().toString();
            if (library == null) {
              library = tokenText;
            } else {
              library = tokenText + "." + library;
            }
          } else if (id != PythonTokenId.DOT) {
            break;
          }
          if (!ts.movePrevious()) {
            return DeclarationLocation.NONE;
          }
          token = ts.token();
          id = token.id();
        }
        if (library != null) {
          if (id == PythonTokenId.WHITESPACE
              && ts.movePrevious()
              && ts.token().id() == PythonTokenId.FROM) {
            return findImport(info, library, moduleName);
          }
        }
      }

      return findImport(info, moduleName, null);
    }

    return DeclarationLocation.NONE;
  }