def cursor(
    belstr: str, ast: AST, cursor_loc: int, result: Mapping[str, Any] = None
) -> Mapping[str, Any]:
    """Find BEL function or argument at cursor location

    Args:
        belstr: BEL String used to create the completion_text
        ast (Mapping[str, Any]): AST (dict) of BEL String
        cursor_loc (int): given cursor location from input field
            cursor_loc starts at 0, think of it like a block cursor covering each char
        result: used to recursively return the result
    Returns:
        result dict
    """

    log.debug(f"SubAST: {json.dumps(ast, indent=4)}")

    # Recurse down through subject, object, nested to functions
    log.debug(f"Cursor keys {ast.keys()}, BELStr: {belstr}")

    if len(belstr) == 0:
        return {"type": "Function", "replace_span": (0, 0), "completion_text": ""}

    if "relation" in ast and in_span(cursor_loc, ast["relation"]["span"]):
        log.debug("In relation")

        completion_text = belstr[ast["relation"]["span"][0] : cursor_loc + 1]
        return {
            "type": "Relation",
            "replace_span": ast["relation"]["span"],
            "completion_text": completion_text,
        }

    # Handle subject, object and nested keys in tree
    elif "span" not in ast and isinstance(ast, dict):
        for key in ast:
            if key in ["subject", "object", "nested"]:
                log.debug(f"Recursing Keys {key}")
                result = cursor(belstr, ast[key], cursor_loc, result=result)
                if result:
                    return result

    # Matches Functions, NSArgs and StrArgs/StrArgNSArg
    if "span" in ast and in_span(cursor_loc, ast["span"]):
        log.debug("Inside subject/object subAST")
        if "function" in ast:
            name_span = ast["function"]["name_span"]
            if in_span(cursor_loc, name_span):
                return {
                    "type": "Function",
                    "replace_span": name_span,
                    "completion_text": belstr[name_span[0] : cursor_loc + 1],
                }
            for idx, arg in enumerate(ast["args"]):
                if (
                    cursor_loc == ast["function"]["parens_span"][0]
                    and ast["function"]["parens_span"][1] == -1
                ):
                    return {
                        "type": "StrArg",  # default type if unknown
                        "arg_idx": idx,
                        "replace_span": arg["span"],  # replace entire strarg
                        "parent_function": ast["function"]["name"],
                        "completion_text": "",
                    }

                elif in_span(cursor_loc, arg["span"]):
                    log.debug(
                        f'In argument span {arg["span"]}  Cursor_loc: {cursor_loc}'
                    )
                    if arg["type"] == "Function":
                        if in_span(cursor_loc, arg["function"]["name_span"]):
                            log.debug("Found replace_span in args: Function type")
                            return {
                                "type": "Function",
                                "replace_span": arg["function"][
                                    "name_span"
                                ],  # replace function name only
                                "arg_idx": idx,
                                "args": copy.deepcopy(ast["args"]),
                                "parent_function": ast["function"]["name"],
                                "completion_text": belstr[
                                    arg["function"]["name_span"][0] : cursor_loc + 1
                                ],
                            }
                        else:
                            log.debug(f'Recursing Function  {arg["span"]}')
                            result = cursor(belstr, arg, cursor_loc, result=result)
                    elif arg["type"] == "NSArg":

                        result = {
                            "type": "NSArg",
                            "replace_span": arg["span"],  # replace entire nsarg
                            "arg_idx": idx,
                            "args": copy.deepcopy(ast["args"]),
                            "parent_function": ast["function"]["name"],
                        }

                        # Filter on namespace and query on ns_val chars up to cursor_loc
                        if in_span(cursor_loc, arg["nsarg"]["ns_val_span"]):
                            result["namespace"] = arg["nsarg"][
                                "ns"
                            ]  # provide namespace for nsarg filtering
                            result["completion_text"] = belstr[
                                arg["nsarg"]["ns_val_span"][0] : cursor_loc + 1
                            ]
                        # Query on nsarg chars up to cursor_loc
                        else:
                            result["completion_text"] = belstr[
                                arg["nsarg"]["ns_span"][0] : cursor_loc + 1
                            ]

                        log.debug(f"Found replace_span in args: NSArg {result}")
                        return result
                    elif (
                        arg["type"] == "StrArg"
                    ):  # in case this is a default namespace StrArg
                        if (
                            arg["span"][0] == arg["span"][1]
                        ):  # handle case like p() cursor=2
                            completion_text = arg["arg"]
                        else:
                            completion_text = belstr[arg["span"][0] : cursor_loc + 1]

                        return {
                            "type": "StrArg",
                            "arg_idx": idx,
                            "replace_span": arg["span"],  # replace entire strarg
                            "parent_function": ast["function"]["name"],
                            "completion_text": completion_text.lstrip(),
                        }
    return result