def make_attribute(cursor_trail, tree):
    """Attribute(expr value, identifier attr, expr_context ctx)"""
    selected_expr = core_logic.get_node_at_cursor(cursor_trail, tree)

    # In the case the parent is an annotated assignment,
    # It must'nt have the "simple" tag
    # TODO: Other annotatable and assignable expressions might need this too
    parent = core_logic.get_node_at_cursor(cursor_trail[:-1], tree)
    if isinstance(parent, ast.AnnAssign):
        # TODO: Can we use False? (The grammar says int)
        parent.simple = 0

    # Get the context from the node that was already here
    ctx = getattr(selected_expr, "ctx", ast.Load())

    # Make sure it's of the expr type (an not a statement)
    selected_expr = selected_expr if isinstance(selected_expr,
                                                ast.expr) else None

    generated_attribute = ast.Attribute(value=selected_expr if selected_expr
                                        is not None else make_expression(),
                                        attr="attr",
                                        ctx=ctx)

    # Set it's new context if the child was an expression
    if selected_expr is not None:
        recursively_fix_context(generated_attribute, selected_expr)

    return generated_attribute
def append(cursor_trail, tree, _):
    selected_node = core_logic.get_node_at_cursor(cursor_trail, tree)

    if getattr(selected_node, "elts", False):
        selected_node.elts.append(make_none_expression())
        return

    if getattr(selected_node, "body", False):
        # I think there is a case where body is not a list
        # TODO: Check the ASDL and fix this
        selected_node.body.append(make_none_statement())
        return

    parent = core_logic.get_node_at_cursor(cursor_trail[:-1], tree)
    fieldname, index = core_logic.get_field_name_for_child(
        parent, selected_node)

    # TODO: implemente the make_default_of function
    # that returns the simplest of an ast node
    # (most likely a bunch on nodes structurally nested)
    # for now, let's just make a copy of the currently selected node
    if index is not None:
        children = getattr(parent, fieldname)
        children.insert(index + 1, deepcopy(selected_node))
        return

    # If we haven't returned yet, we can't append to it
    print("Can't append to this type of node")
def delete(cursor_trail, tree, _):

    # Don't touch the module
    if cursor_trail == []:
        return

    selected_node = core_logic.get_node_at_cursor(cursor_trail, tree)
    parent = core_logic.get_node_at_cursor(cursor_trail[:-1], tree)
    fieldname, index = core_logic.get_field_name_for_child(parent, selected_node)

    if index is not None:
        children_list = getattr(parent, fieldname)

        # A try block needs to have at least one of those not empty
        if fieldname == "handlers" or fieldname == "finalbody":
            if len(parent.handlers) + len(parent.finalbody)  == 1:
                print("A Try block needs handlers or a finally")
                return

        if isinstance(parent, ast.Dict):
            # If it's a dictionary, keep the key value pairs synced
            # by deleting the correspondig key/value
            if fieldname == "keys":
                parent.values.pop(index)

            if fieldname == "values":
                parent.keys.pop(index)

        if isinstance(parent, ast.BoolOp):
            if len(children_list) < 3:
                print("A boolean operation must have at least 2 operands")
                return

        # These kinds of nodes can't be empty
        elif  (  fieldname == "names"
            or fieldname == "body"
            or fieldname == "items"
            or fieldname == "targets"):

            if len(children_list) < 2:
                print("This can't be empty")
                return
        
        children_list.pop(index)

        # If there are no more children, move up
        if len(children_list) == 0:
            cursor_trail.pop()

    else:
        # setattr(parent, fieldname, None)
        pass
def put(cursor_trail, tree, _):
    selected_node = core_logic.get_node_at_cursor(cursor_trail, tree)

    try:
        yanked = tree.states_for_actions["yanked"]
    except KeyError:
        print("No yanked node")
        return

    # Or they have the same type
    # or they are both statements
    # or they are both expressions
    # or it's an expression being pasted into a statement
    # (In this case we'll wrap it into an Expr)
    # otherwise, we can't paste here
    if not (
               (type(selected_node) == type(yanked))
            or (isinstance(selected_node, ast.stmt) and isinstance(yanked, ast.stmt))
            or (isinstance(selected_node, ast.stmt) and isinstance(yanked, ast.expr))
            or (isinstance(selected_node, ast.expr) and isinstance(yanked, ast.expr))
           ):

         print("Cannot paste here, the type is different")
         return

    if isinstance(selected_node, ast.stmt) and isinstance(yanked, ast.expr):
        # Fix the type by wrapping the expression into an Expr
        # Making both into ast.stmt
        yanked = ast.Expr(value=yanked)

    # TODO: Add the mirror logic for unwrapping an Expr into it's value

    core_logic.set_node_at_cursor(cursor_trail, tree, yanked)
def make_async(cursor_trail, tree):

    definition = core_logic.get_node_at_cursor(cursor_trail, tree)

    if type(definition) == ast.FunctionDef:
        return ast.AsyncFunctionDef(name=definition.name,
                                    args=definition.args,
                                    body=definition.body,
                                    decorator_list=definition.decorator_list,
                                    returns=definition.returns,
                                    type_comment=definition.type_comment)

    if type(definition) == ast.AsyncFunctionDef:
        return ast.FunctionDef(name=definition.name,
                               args=definition.args,
                               body=definition.body,
                               decorator_list=definition.decorator_list,
                               returns=definition.returns,
                               type_comment=definition.type_comment)

    if type(definition) == ast.With:
        return ast.AsyncWith(items=definition.items,
                             body=definition.body,
                             type_comment=definition.type_comment)

    if type(definition) == ast.AsyncWith:
        return ast.With(items=definition.items,
                        body=definition.body,
                        type_comment=definition.type_comment)

    if isinstance(definition, ast.comprehension):
        # Toggle the async
        definition.is_async = 0 if definition.is_async == 1 else 1
        return definition
def move_cursor_up(cursor_trail, ast, _):
    if cursor_trail == []:
        return
    
    cursor_trail.pop()
    current_node = core_logic.get_node_at_cursor(cursor_trail, ast)
    print(current_node.__class__.__name__)
    def action(cursor_trail, tree, get_user_input):
        validator, creator = nodes[node_name]
        selected_node = core_logic.get_node_at_cursor(cursor_trail, tree)

        # Never touch the module
        if type(selected_node) == ast.Module:
            return

        if not validator(cursor_trail, tree):
            print(f"Can't have {node_name} here")
            return

        arguments = []

        # Dependent actions need to know more to return the right thing
        if node_name in dependent_actions:
            arguments = arguments + [cursor_trail, tree]

        if node_name in user_input_actions:
            arguments = arguments + [get_user_input]

        node_to_insert = creator(*arguments)

        # Special case: An expression may be placed where a statement
        # is expected, as long as it's wrapped in an ast.Expr
        if isinstance(selected_node, ast.stmt) and isinstance(
                node_to_insert, ast.expr):
            node_to_insert_as_statement = ast.Expr(value=node_to_insert)

            core_logic.set_node_at_cursor(cursor_trail, tree,
                                          node_to_insert_as_statement)
        else:
            core_logic.set_node_at_cursor(cursor_trail, tree, node_to_insert)
def make_dict(cursor_trail, tree):
    """Dict(expr* keys, expr* values)
    """
    selected_node = core_logic.get_node_at_cursor(cursor_trail, tree)
    expr = selected_node if isinstance(selected_node,
                                       ast.expr) else make_expression()

    return ast.Dict(keys=[ast.Name(id="key", ctx=ast.Load())], values=[expr])
def move_cursor_left(cursor_trail, ast, _):
    if cursor_trail == []:
        return

    # decrement in one the index of the last child
    # effectively moving back to the previous sibling
    cursor_trail.append(cursor_trail.pop() - 1)
    current_node = core_logic.get_node_at_cursor(cursor_trail, ast)
    print(current_node.__class__.__name__)
def make_named_expression(cursor_trail, tree):
    """NamedExpr(expr target, expr value)"""

    selected_expr = core_logic.get_node_at_cursor(cursor_trail, tree)
    selected_expr = selected_expr if isinstance(
        selected_expr, ast.expr) else make_expression()

    return ast.NamedExpr(target=ast.Name(id="x", ctx=ast.Store()),
                         value=selected_expr)
def make_name(cursor_trail, tree):
    """Name(identifier id, expr_context ctx).
    This function must be dependent because of the context
    """

    selected_expr = core_logic.get_node_at_cursor(cursor_trail, tree)
    ctx = getattr(selected_expr, "ctx", ast.Load())

    return ast.Name(id="x", ctx=ctx)
def make_set(cursor_trail, tree):
    """Set(expr* elts)
    """

    selected_node = core_logic.get_node_at_cursor(cursor_trail, tree)
    expr = selected_node if isinstance(selected_node,
                                       ast.expr) else make_expression()

    return ast.Set(elts=[expr])
def move_cursor_right(cursor_trail, ast, _):
    if cursor_trail == []:
        return

    # Increment in one the index of the last child
    # Effectively moving to the next sibling
    cursor_trail.append(cursor_trail.pop() + 1)
    current_node = core_logic.get_node_at_cursor(cursor_trail, ast)
    print(current_node.__class__.__name__)
def make_call_on(cursor_trail, tree):
    """Calls a function on the selected expression"""

    expr = core_logic.get_node_at_cursor(cursor_trail, tree)
    expr = expr if isinstance(expr, ast.expr) else make_expression()

    return ast.Call(func=ast.Name(id="f", ctx=ast.Load()),
                    args=[expr],
                    keywords=[])
def make_tuple(cursor_trail, tree):
    """Tuple(expr* elts, expr_context ctx)"""

    selected_expr = core_logic.get_node_at_cursor(cursor_trail, tree)
    parent = core_logic.get_node_at_cursor(cursor_trail[:-1], tree)

    # Steal the ctx from the node that was already here
    # Default to Load
    ctx = getattr(selected_expr, "ctx", ast.Load())

    selected_expr = selected_expr if isinstance(selected_expr,
                                                ast.expr) else None
    elts = [] if selected_expr is None else [selected_expr]

    created_tuple = ast.Tuple(elts=elts, ctx=ctx)

    if selected_expr is not None:
        recursively_fix_context(created_tuple, selected_expr)

    return created_tuple
def make_usub(cursor_trail, tree):
    selected_node = core_logic.get_node_at_cursor(cursor_trail, tree)
    expr = selected_node if isinstance(selected_node,
                                       ast.expr) else make_expression()

    if isinstance(expr, ast.UnaryOp) and isinstance(expr.op, ast.USub):
        # Having - - x doesn't really make sense
        # So let's just remove the minus in this case
        return expr.operand

    return ast.UnaryOp(op=ast.USub(), operand=expr)
def make_call(cursor_trail, tree):
    """Call(expr func, expr* args, keyword* keywords)"""

    expr = core_logic.get_node_at_cursor(cursor_trail, tree)
    expr = expr if isinstance(expr, ast.expr) else ast.Name(id="f",
                                                            ctx=ast.Load())

    return ast.Call(
        func=expr,
        args=[make_expression()],
        keywords=[],
    )
def append(cursor_trail, tree, _):
    selected_node = core_logic.get_node_at_cursor(cursor_trail, tree)

    # Don't touch the module
    if isinstance(selected_node, ast.Module):
        return

    parent = core_logic.get_node_at_cursor(cursor_trail[:-1], tree)
    fieldname, index = core_logic.get_field_name_for_child(parent, selected_node)

    if index is not None:
        children = getattr(parent, fieldname)
        children.insert(index + 1, deepcopy(selected_node))

        # A dictionary should always have the same amount of keys and values
        # So let's be careful and keep them synced
        if type(parent) == ast.Dict:
            if fieldname == "keys":
                parent.values.insert(index + 1, deepcopy(parent.values[index]))
            if fieldname == "values":
                parent.keys.insert(index + 1, deepcopy(parent.keys[index]))

        # Comparisons are similar:
        # They should have the same number of comparators and comparands
        if type(parent) == ast.Compare:
            parent.ops.insert(index + 1, deepcopy(parent.ops[index]))

        # In the case we are within function arguments
        # two of them with the same name can break stuff.
        # So let's give the new one a different name 
        if type(selected_node) == ast.arg:
            children[index + 1].arg = core_logic.get_unique_name(selected_node.arg, parent)

        return

    # If we haven't returned yet, we can't append to it
    print("Can't append to this type of node")
def make_generator(cursor_trail, tree):
    """ Makes a generator of the selected_expression 
    
    ListComp(expr elt, comprehension* generators)
    SetComp(expr elt, comprehension* generators)
    DictComp(expr key, expr value, comprehension* generators)
    GeneratorExp(expr elt, comprehension* generators)
    """

    selected_node = core_logic.get_node_at_cursor(cursor_trail, tree)
    if isinstance(selected_node, ast.List):
        comprehension = ast.ListComp

        try:
            elt = selected_node.elts[0]
        except IndexError:
            elt = make_expression()

        elts = {"elt": elt}

    elif isinstance(selected_node, ast.Set):
        comprehension = ast.SetComp

        try:
            elt = selected_node.elts[0]
        except IndexError:
            elt = make_expression()

        elts = {"elt": elt}

    elif isinstance(selected_node, ast.Dict):
        comprehension = ast.DictComp

        try:
            key = selected_node.keys[0]
            value = selected_node.values[0]
        except IndexError:
            key = make_expression()
            value = make_expression()

        elts = {"key": key, "value": value}
    else:
        comprehension = ast.GeneratorExp
        expr = selected_node if isinstance(selected_node,
                                           ast.expr) else make_expression()

        elts = {"elt": expr}

    return comprehension(**elts, generators=[make_comprehension()])
def insert_int(cursor_trail, tree, get_input):
    selected_node = core_logic.get_node_at_cursor(cursor_trail, tree)

    if not validators.is_simple_expression(cursor_trail, tree):
        print("Can't have an int here")
        return

    try:
        n = int(get_input("int: "))
    except ValueError:
        print("Invalid int")
        return

    constant = ast.Constant(value=n, kind=None)
    core_logic.set_node_at_cursor(cursor_trail, tree, constant)
def make_subscript(cursor_trail, tree):
    """Subscript(expr value, slice slice, expr_context ctx)"""

    selected_expr = core_logic.get_node_at_cursor(cursor_trail, tree)

    # Steal the ctx from the node that was already here
    # Default to Load
    ctx = getattr(selected_expr, "ctx", ast.Load())
    selected_expr = selected_expr if isinstance(
        selected_expr, ast.expr) else make_expression()

    subscript = ast.Subscript(value=selected_expr, slice=make_slice(), ctx=ctx)
    recursively_fix_context(subscript, selected_expr)

    return subscript
def insert_annotation(cursor_trail, tree, _):
    selected_node = core_logic.get_node_at_cursor(cursor_trail, tree)
    default_annotation = ast.Name(id="annotation", ctx=ast.Load())

    if hasattr(selected_node, "returns"):
        # Toggle the return annotation
        if selected_node.returns is None:
            selected_node.returns = default_annotation 
        else:
            selected_node.returns = None

    # The assignments must come befor the generic annotation case
    # Because otherwise the annotated assignment's annotation will be
    # erroneously set to None
    elif isinstance(selected_node, ast.Assign):
        # Make it into an annotated assign
        annotated_assign = ast.AnnAssign(
                target=selected_node.targets[0],
                annotation=default_annotation,
                value=selected_node.value,
                # TODO: What does simple mean?
                simple=1
                ) 

        core_logic.set_node_at_cursor(cursor_trail, tree, annotated_assign)

    elif isinstance(selected_node, ast.AnnAssign):
        # Make it into a regular assign
        value = selected_node.value

        assign = ast.Assign(
                targets=[selected_node.target],
                value = value if value is not None else make_node.make_expression()
                )

        core_logic.set_node_at_cursor(cursor_trail, tree, assign)

    elif hasattr(selected_node, "annotation"):
        # Toggle the annotation
        if selected_node.annotation is None:
            selected_node.annotation = default_annotation
        else:
            selected_node.annotation = None

    else:
        print("This node can't have type annotations")

    return []
def make_list(cursor_trail, tree):
    """List(expr* elts, expr_context ctx)"""
    selected_expr = core_logic.get_node_at_cursor(cursor_trail, tree)

    # Steal the ctx from the node that was already here
    # Default to Load
    ctx = getattr(selected_expr, "ctx", ast.Load())

    selected_expr = selected_expr if isinstance(selected_expr,
                                                ast.expr) else None
    elts = [] if selected_expr is None else [selected_expr]
    created_list = ast.List(elts=elts, ctx=ctx)

    # If we have children, fix their contexts
    if selected_expr is not None:
        recursively_fix_context(created_list, selected_expr)

    return created_list
def insert(cursor_trail, tree, _):
    """Adds an element inside the node.
    It's useful to unempty an empty container (like a list)
    before populating it using "append" """

    selected_node = core_logic.get_node_at_cursor(cursor_trail, tree)
    if hasattr(selected_node, "bases"):
        # Add a new base class to the class 
        selected_node.bases.append(ast.Name(id="BaseClass", ctx=ast.Load()))

    # It doesn't make sense to insert into a body because they can't ever be empty
    # Except for the Module node
    elif isinstance(selected_node, ast.Module):
        selected_node.body.append(make_nodes.make_pass())

    elif isinstance(selected_node, ast.arguments):

        arg = make_nodes.make_arg()

        # Make the name unique
        arg.arg = core_logic.get_unique_name(arg.arg, selected_node)

        selected_node.args.append(arg)
        return [0]

    # Call has the args list as a list of expressions
    # That's different from function definitions, where it's a list of arguments
    # The grammar is really confusing when it comes to those "args"
    elif isinstance(selected_node, ast.Call):
        arg = make_nodes.make_expression()
        selected_node.args.append(arg)
        return [0]

    elif hasattr(selected_node, "elts"):
        ctx = core_logic.get_immediate_context(cursor_trail, tree)
        selected_node.elts.append(make_nodes.make_expression(ctx=ctx))

        return [-1]

    else:
        print("Can't insert inside this node")
        return []
    def validate(cursor_trail, tree):
        selected_node = core_logic.get_node_at_cursor(cursor_trail, tree)

        # make sure the node is allowed to be placed here
        return isinstance(selected_node, ast_node_type)
def make_invert(cursor_trail, tree):
    selected_node = core_logic.get_node_at_cursor(cursor_trail, tree)
    expr = selected_node if isinstance(selected_node,
                                       ast.expr) else make_expression()

    return ast.UnaryOp(op=ast.Invert(), operand=expr)
def extend(cursor_trail, tree, _):
    """ Bulks up the selected node.
    Specifically how depends on the node"""

    # If -> add else
    # For (Async too) -> add else
    # While -> add else
    # Try -> add else. (TODO: Do something about the finnaly block too)

    # Function (Async too) -> add decorator
    # Class -> add decorator 

    # Assign -> Augmented Assign
    # Raise -> Add the cause (as in "raise foo from bar")
    # Assert -> Add the message
    # Import -> ImportFrom
    # alias -> add an asname (Which is kind of the whole point of the alias node)
    # comprehension -> add an if clause
    # yield -> add the thing to yield
    # Name -> starred
    # Index -> Slice

    selected_node = core_logic.get_node_at_cursor(cursor_trail, tree)

    # The if expression has an orelse field that is an expression, not a list of statements
    if hasattr(selected_node, "orelse") and not isinstance(selected_node, ast.IfExp): 
        
        # Toggle the else branch
        if selected_node.orelse == []:
            selected_node.orelse = [ast.Pass()]
        else:
            selected_node.orelse = []

    elif hasattr(selected_node, "decorator_list"):

        # Toggle the decorator_list
        if selected_node.decorator_list == []:
            selected_node.decorator_list = [ast.Name(id="decorator", ctx=ast.Load())]
        else:
            selected_node.decorator_list = []

    elif isinstance(selected_node, ast.Raise):

        # toggle the cause
        if selected_node.cause is None:
            selected_node.cause = ast.Name(id="cause", ctx=ast.Load())
        else:
            selected_node.cause = None

    elif isinstance(selected_node, ast.Assert):

        # toggle the message
        if selected_node.msg is None:
            selected_node.msg = ast.Constant(value="message", kind=None)
        else:
            selected_node.msg = None

    elif isinstance(selected_node, ast.Import):

        # TODO: Break up an import as a bunch of ImportFrom instead of losing info
        # by just creating a single one
        new_names = selected_node.names
        new_names = [make_nodes.make_alias()] if new_names == [] else selected_node.names

        new_import = ast.ImportFrom(
                module = selected_node.names[0].name,
                names = selected_node.names,
                # TODO: What does level mean? (Is it like going up in the folder hierarchy?)
                # Even though the grammar allows level to be None, that makes astor break
                # So, let's initialize it to 0 (Still don't quite know what that means)
                level = 0
                )

        core_logic.set_node_at_cursor(cursor_trail, tree, new_import)

    elif isinstance(selected_node, ast.ImportFrom):

        new_import = ast.Import(names=selected_node.names)
        core_logic.set_node_at_cursor(cursor_trail, tree, new_import)

    elif isinstance(selected_node, ast.alias):
        # Toggle the asname
        if selected_node.asname is None:
            selected_node.asname = "alias"
        else: 
            selected_node.asname = None
    
    elif isinstance(selected_node, ast.comprehension):
        # Toggle the if clause
        if selected_node.ifs == []:
            selected_node.ifs = [make_nodes.make_expression()]
        else: 
            selected_node.ifs = []

    elif isinstance(selected_node, ast.Yield):
        # Toggle the thing to yield
        if selected_node.value is None:
            selected_node.value = make_nodes.make_expression()
        else: 
            selected_node.value = None

    elif isinstance(selected_node, ast.Name):
        # Make it into an ast.Starred

        if not core_logic.core_is_within_field(
                cursor_trail,
                tree,
                ast.Assign,
                "targets"
                ):

            print("Can't have a starred variable outside of an assignment")
            return

        # Does this check make the above one useless?
        parent = core_logic.get_node_at_cursor(cursor_trail[:-1], tree)
        if not (isinstance(parent, ast.Tuple) or isinstance(parent, ast.List)):
            print("A starred expression must be within a list or a tuple")
            return

        parent = core_logic.get_node_at_cursor(cursor_trail[:-1], tree)

        if isinstance(parent, ast.Assign) and len(parent.targets) <= 1:
            print("A starred expression can't be alone in an assignment")
            return

        # TODO: Make starred's work for function params
        starred = ast.Starred(
                value = selected_node,
                ctx = selected_node.ctx
                )

        core_logic.set_node_at_cursor(cursor_trail, tree, starred)

    elif (isinstance(selected_node, ast.Starred)
            and isinstance((name := selected_node.value), ast.Name)) :

        # Change the node to be the name
        core_logic.set_node_at_cursor(cursor_trail, tree, name)
def yank(cursor_trail, tree, _):
    selected_node = core_logic.get_node_at_cursor(cursor_trail, tree)
    # TODO: Multi level undo
    tree.states_for_actions["yanked"] = deepcopy(selected_node)
    def validate(cursor_trail, tree):
        selected_node = core_logic.get_node_at_cursor(cursor_trail, tree)

        return not isinstance(selected_node, ast_node_type)