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)