#@+leo-ver=5-thin
#@+node:ekr.20171124080430.1: * @file ../commands/commanderOutlineCommands.py
"""Outline commands that used to be defined in leoCommands.py"""
#@+<< commanderOutlineCommands imports & annotations >>
#@+node:ekr.20220826123551.1: ** << commanderOutlineCommands imports & annotations >>
from __future__ import annotations
from collections import defaultdict
from collections.abc import Callable
import xml.etree.ElementTree as ElementTree
import json
import time
from typing import Any, Generator, Optional, TYPE_CHECKING
from leo.core import leoGlobals as g
from leo.core import leoNodes
from leo.core import leoFileCommands

if TYPE_CHECKING:  # pragma: no cover
    from leo.core.leoCommands import Commands as Cmdr
    from leo.core.leoGui import LeoKeyEvent as Event
    from leo.core.leoNodes import Position, VNode
#@-<< commanderOutlineCommands imports & annotations >>

#@+others
#@+node:ekr.20031218072017.1548: ** c_oc.Cut & Paste Outlines
#@+node:ekr.20031218072017.1550: *3* c_oc.copyOutline
@g.commander_command('copy-node')
def copyOutline(self: Cmdr, event: Event = None) -> str:
    """Copy the selected outline to the clipboard."""
    # Copying an outline has no undo consequences.
    c = self
    c.endEditing()
    s = c.fileCommands.outline_to_clipboard_string()
    g.app.paste_c = c
    if g.app.inBridge:
        return s
    g.app.gui.replaceClipboardWith(s)
    return s
#@+node:ekr.20220314071523.1: *3* c_oc.copyOutlineAsJson & helpers
@g.commander_command('copy-node-as-json')
def copyOutlineAsJSON(self: Cmdr, event: Event = None) -> Optional[str]:
    """Copy the selected outline as JSON to the clipboard"""
    # Copying an outline has no undo consequences.
    c = self
    c.endEditing()
    s = c.fileCommands.outline_to_clipboard_json_string()
    g.app.paste_c = c
    if g.app.inBridge:
        return s
    g.app.gui.replaceClipboardWith(s)
    return None
#@+node:ekr.20031218072017.1549: *3* c_oc.cutOutline
@g.commander_command('cut-node')
def cutOutline(self: Cmdr, event: Event = None) -> None:
    """Delete the selected outline and send it to the clipboard."""
    c = self
    if c.canDeleteHeadline():
        c.copyOutline()
        c.deleteOutline(op_name="Cut Node")
        c.recolor()
#@+node:ekr.20031218072017.1551: *3* c_oc.pasteOutline
@g.commander_command('paste-node')
def pasteOutline(
    self: Cmdr,
    event: Event = None,
    s: str = None,
    undoFlag: bool = True,  # A hack for abbrev.paste_tree.
) -> Optional[Position]:
    """
    Paste an outline into the present outline from the clipboard.
    Nodes do *not* retain their original identify.
    """
    c = self
    if s is None:
        s = g.app.gui.getTextFromClipboard()
    c.endEditing()
    if not s or not c.canPasteOutline(s):
        return None  # This should never happen.
    isLeo = s.lstrip().startswith("{") or g.match(s, 0, g.app.prolog_prefix_string)
    if not isLeo:
        return None
    # Get *position* to be pasted.
    pasted = c.fileCommands.getLeoOutlineFromClipboard(s)
    if not pasted:
        # Leo no longer supports MORE outlines. Use import-MORE-files instead.
        return None
    # Validate.
    errors = c.checkOutline()
    if errors > 0:
        return None
    # Handle the "before" data for undo.
    if undoFlag:
        undoData = c.undoer.beforeInsertNode(c.p,
            pasteAsClone=False,
            copiedBunchList=[],
        )
    # Paste the node into the outline.
    c.selectPosition(pasted)
    pasted.setDirty()
    c.setChanged()
    back = pasted.back()
    if back and back.hasChildren() and back.isExpanded():
        pasted.moveToNthChildOf(back, 0)
    # Finish the command.
    if undoFlag:
        c.undoer.afterInsertNode(pasted, 'Paste Node', undoData)
    c.redraw(pasted)
    c.recolor()
    return pasted
#@+node:EKR.20040610130943: *3* c_oc.pasteOutlineRetainingClones & helpers
@g.commander_command('paste-retaining-clones')
def pasteOutlineRetainingClones(
    self: Cmdr, event: Event = None, s: str = None,
) -> Optional[Position]:
    """
    Paste an outline into the present outline from the clipboard.
    Nodes *retain* their original identify.
    """
    c = self
    if s is None:
        s = g.app.gui.getTextFromClipboard()
    c.endEditing()
    if not s or not c.canPasteOutline(s):
        return None  # This should never happen.
    # Get *position* to be pasted.
    pasted = c.fileCommands.getLeoOutlineFromClipboardRetainingClones(s)
    if not pasted:
        # Leo no longer supports MORE outlines. Use import-MORE-files instead.
        return None
    # Validate.
    errors = c.checkOutline()
    if errors > 0:
        return None
    # Handle the "before" data for undo.
    if True:  ### undoFlag:
        vnodeInfoDict = computeVnodeInfoDict(c)
        undoData = c.undoer.beforeInsertNode(c.p,
            pasteAsClone=True,
            copiedBunchList=computeCopiedBunchList(c, pasted, vnodeInfoDict),
        )
    # Paste the node into the outline.
    c.selectPosition(pasted)
    pasted.setDirty()
    c.setChanged()
    back = pasted.back()
    if back and back.hasChildren() and back.isExpanded():
        pasted.moveToNthChildOf(back, 0)
        pasted.setDirty()
    # Set dirty bits for ancestors of *all* pasted nodes.
    for p in pasted.self_and_subtree():
        p.setAllAncestorAtFileNodesDirty()
    # Finish the command.
    if True:  ### undoFlag:
        c.undoer.afterInsertNode(pasted, 'Paste As Clone', undoData)
    c.redraw(pasted)
    c.recolor()
    return pasted
#@+node:ekr.20050418084539.2: *4* def computeCopiedBunchList
def computeCopiedBunchList(
    c: Cmdr,
    pasted: Position,
    vnodeInfoDict: dict[VNode, Any],
) -> list[Any]:
    """Create a dict containing only copied vnodes."""
    d = {}
    for p in pasted.self_and_subtree(copy=False):
        d[p.v] = p.v
    aList = []
    for v in vnodeInfoDict:
        if d.get(v):
            bunch = vnodeInfoDict.get(v)
            aList.append(bunch)
    return aList
#@+node:ekr.20050418084539: *4* def computeVnodeInfoDict
def computeVnodeInfoDict(c: Cmdr) -> dict[VNode, Any]:
    """
    We don't know yet which nodes will be affected by the paste, so we remember
    everything. This is expensive, but foolproof.

    The alternative is to try to remember the 'before' values of nodes in the
    FileCommands read logic. Several experiments failed, and the code is very ugly.
    In short, it seems wise to do things the foolproof way.
    """
    d = {}
    for v in c.all_unique_nodes():
        if v not in d:
            d[v] = g.Bunch(v=v, head=v.h, body=v.b)
    return d
#@+node:vitalije.20200529105105.1: *3* c_oc.pasteAsTemplate
@g.commander_command('paste-as-template')
def pasteAsTemplate(self: Cmdr, event: Event = None) -> None:
    """Paste as template clones only nodes that were already clones"""
    c = self
    p = c.p
    s = g.app.gui.getTextFromClipboard()
    if not s or not c.canPasteOutline(s):
        return  # This should never happen.
    isJson = s.lstrip().startswith("{")

    # Define helpers.
    #@+others
    #@+node:vitalije.20200529112224.1: *4* skip_root
    def skip_root(v: VNode) -> Generator:
        """
        generates v nodes in the outline order
        but skips a subtree of the node with root_gnx
        """
        if v.gnx != root_gnx:
            yield v
            for ch in v.children:
                yield from skip_root(ch)
    #@+node:vitalije.20200529112459.1: *4* translate_gnx
    def translate_gnx(gnx: str) -> str:
        """
        allocates a new gnx for all nodes that
        are not found outside copied tree
        """
        if gnx in outside:
            return gnx
        return g.app.nodeIndices.computeNewIndex()
    #@+node:vitalije.20200529115141.1: *4* viter
    def viter(parent_gnx: str, xv: Any) -> Generator:
        """
        iterates <v> nodes generating tuples:

            (parent_gnx, child_gnx, headline, body)

        skipping the descendants of already seen nodes.
        """

        if not isJson:
            chgnx = xv.attrib.get('t')
        else:
            chgnx = xv.get('gnx')

        b = bodies[chgnx]
        gnx = translation.get(chgnx)
        if gnx in seen:
            yield parent_gnx, gnx, heads.get(gnx), b
        else:
            seen.add(gnx)
            if not isJson:
                h = xv[0].text
            else:
                h = xv.get('vh', '')
            heads[gnx] = h
            yield parent_gnx, gnx, h, b
            if not isJson:
                for xch in xv[1:]:
                    yield from viter(gnx, xch)
            else:
                if xv.get('children'):
                    for xch in xv['children']:
                        yield from viter(gnx, xch)

    #@+node:vitalije.20200529114857.1: *4* getv
    gnx2v = c.fileCommands.gnxDict
    def getv(gnx: str) -> tuple[VNode, bool]:
        """
        returns a pair (vnode, is_new) for the given gnx.
        if node doesn't exist, creates a new one.
        """
        v = gnx2v.get(gnx)
        if v is None:
            return leoNodes.VNode(c, gnx), True
        return v, False
    #@+node:vitalije.20200529115539.1: *4* do_paste (pasteAsTemplate)
    def do_paste(vpar: Any, index: int) -> VNode:
        """
        pastes a new node as a child of vpar at given index
        """
        vpargnx = vpar.gnx
        # the first node is inserted at the given index
        # and the rest are just appended at parents children
        # to achieve this we first create a generator object
        rows = viter(vpargnx, xvelements[0])

        # then we just take first tuple
        pgnx, gnx, h, b = next(rows)

        # create vnode
        v, _ = getv(gnx)
        v.h = h
        v.b = b

        # and finally insert it at the given index
        vpar.children.insert(index, v)
        v.parents.append(vpar)

        pasted = v  # remember the first node as a return value

        # now we iterate the rest of tuples
        for pgnx, gnx, h, b in rows:

            # get or create a child `v`
            v, isNew = getv(gnx)
            if isNew:
                v.h = h
                v.b = b
                ua = uas.get(gnx)
                if ua:
                    v.unknownAttributes = ua
            # get parent node `vpar`
            vpar = getv(pgnx)[0]

            # and link them
            vpar.children.append(v)
            v.parents.append(vpar)

        return pasted
    #@+node:vitalije.20200529120440.1: *4* undoHelper
    def undoHelper() -> None:
        v = vpar.children.pop(index)
        v.parents.remove(vpar)
        c.redraw(bunch.p)
    #@+node:vitalije.20200529120537.1: *4* redoHelper
    def redoHelper() -> None:
        vpar.children.insert(index, pasted)
        pasted.parents.append(vpar)
        c.redraw(newp)
    #@-others

    xvelements: Any
    xtelements: Any
    uas: Any  # Possible bug?

    x = leoFileCommands.FastRead(c, {})

    if not isJson:
        xroot = ElementTree.fromstring(s)
        xvelements = xroot.find('vnodes')  # <v> elements.
        xtelements = xroot.find('tnodes')  # <t> elements.
        bodies, uas = x.scanTnodes(xtelements)
        root_gnx = xvelements[0].attrib.get('t')  # the gnx of copied node
    else:
        xroot = json.loads(s)
        xvelements = xroot.get('vnodes')  # <v> elements.
        xtelements = xroot.get('tnodes')  # <t> elements.
        bodies = x.scanJsonTnodes(xtelements)
        # g.printObj(bodies, tag='bodies/gnx2body')

        def addBody(node: Any) -> None:
            if not hasattr(bodies, node['gnx']):
                bodies[node['gnx']] = ''
            if node.get('children'):
                for child in node['children']:
                    addBody(child)

        # generate bodies for all possible nodes, not just non-empty bodies.
        addBody(xvelements[0])
        uas = defaultdict(dict)
        uas.update(xroot.get('uas', {}))
        root_gnx = xvelements[0].get('gnx')  # the gnx of copied node

    # outside will contain gnxes of nodes that are outside the copied tree
    outside = {x.gnx for x in skip_root(c.hiddenRootNode)}

    # we generate new gnx for each node in the copied tree
    translation = {x: translate_gnx(x) for x in bodies}

    seen = set(outside)  # required for the treatment of local clones inside the copied tree

    heads: dict[str, str] = {}

    bunch = c.undoer.createCommonBunch(p)
    #@+<< prepare destination data >>
    #@+node:vitalije.20200529111500.1: *4* << prepare destination data >>
    # destination data consists of
    #    1. vpar --- parent v node that should receive pasted child
    #    2. index --- at which pasted child will be
    #    3. parStack --- a stack for creating new position of the pasted node
    #
    # the new position will be:  Position(vpar.children[index], index, parStack)
    # but it can't be calculated yet, before actual paste is done
    if p.isExpanded():
        # paste as a first child of current position
        vpar = p.v
        index = 0
        parStack = p.stack + [(p.v, p._childIndex)]
    else:
        # paste after the current position
        parStack = p.stack
        vpar = p.stack[-1][0] if p.stack else c.hiddenRootNode
        index = p._childIndex + 1

    #@-<< prepare destination data >>

    pasted = do_paste(vpar, index)

    newp = leoNodes.Position(pasted, index, parStack)

    bunch.undoHelper = undoHelper
    bunch.redoHelper = redoHelper
    bunch.undoType = 'paste-retaining-outside-clones'

    newp.setDirty()
    c.undoer.pushBead(bunch)
    c.redraw(newp)
#@+node:ekr.20040412060927: ** c_oc.dumpOutline
@g.commander_command('dump-outline')
def dumpOutline(self: Cmdr, event: Event = None) -> None:
    """ Dump all nodes in the outline."""
    c = self
    seen = {}
    print('')
    print('=' * 40)
    v = c.hiddenRootNode
    v.dump()
    seen[v] = True
    for p in c.all_positions():
        if p.v not in seen:
            seen[p.v] = True
            p.v.dump()
#@+node:ekr.20031218072017.2898: ** c_oc.Expand & contract commands
#@+node:ekr.20031218072017.2900: *3* c_oc.contract-all
@g.commander_command('contract-all')
def contractAllHeadlinesCommand(self: Cmdr, event: Event = None) -> None:
    """Contract all nodes in the outline."""
    # The helper does all the work.
    c = self
    c.contractAllHeadlines()
    c.redraw()
#@+node:ekr.20080819075811.3: *3* c_oc.contractAllOtherNodes & helper
@g.commander_command('contract-all-other-nodes')
def contractAllOtherNodes(self: Cmdr, event: Event = None) -> None:
    """
    Contract all nodes except those needed to make the
    presently selected node visible.
    """
    c = self
    leaveOpen = c.p
    for p in c.rootPosition().self_and_siblings():
        contractIfNotCurrent(c, p, leaveOpen)
    c.redraw()
#@+node:ekr.20080819075811.7: *4* def contractIfNotCurrent
def contractIfNotCurrent(c: Cmdr, p: Position, leaveOpen: Any) -> None:
    if p == leaveOpen or not p.isAncestorOf(leaveOpen):
        p.contract()
    for child in p.children():
        if child != leaveOpen and child.isAncestorOf(leaveOpen):
            contractIfNotCurrent(c, child, leaveOpen)
        else:
            for p2 in child.self_and_subtree():
                p2.contract()
#@+node:ekr.20200824130837.1: *3* c_oc.contractAllSubheads (new)
@g.commander_command('contract-all-subheads')
def contractAllSubheads(self: Cmdr, event: Event = None) -> None:
    """Contract all children of the presently selected node."""
    c, p = self, self.p
    if not p:
        return
    child = p.firstChild()
    c.contractSubtree(p)
    while child:
        c.contractSubtree(child)
        child = child.next()
    c.redraw(p)
#@+node:ekr.20031218072017.2901: *3* c_oc.contractNode
@g.commander_command('contract-node')
def contractNode(self: Cmdr, event: Event = None) -> None:
    """Contract the presently selected node."""
    c = self
    p = c.p
    c.endEditing()
    p.contract()
    c.redraw_after_contract(p)
    c.selectPosition(p)
#@+node:ekr.20040930064232: *3* c_oc.contractNodeOrGoToParent
@g.commander_command('contract-or-go-left')
def contractNodeOrGoToParent(self: Cmdr, event: Event = None) -> None:
    """Simulate the left Arrow Key in folder of Windows Explorer."""
    c, cc, p = self, self.chapterController, self.p
    parent = p.parent()
    redraw = False
    # Bug fix: 2016/04/19: test p.v.isExpanded().
    if p.hasChildren() and (p.v.isExpanded() or p.isExpanded()):
        c.contractNode()
    elif parent and parent.isVisible(c):
        # Contract all children first.
        if c.collapse_on_lt_arrow:
            for child in parent.children():
                if child.isExpanded():
                    child.contract()
                    if child.hasChildren():
                        redraw = True
        if cc and cc.inChapter() and parent.h.startswith('@chapter '):
            pass
        else:
            c.goToParent()
    if redraw:
        # A *child* should be collapsed.  Do a *full* redraw.
        c.redraw()
#@+node:ekr.20031218072017.2902: *3* c_oc.contractParent
@g.commander_command('contract-parent')
def contractParent(self: Cmdr, event: Event = None) -> None:
    """Contract the parent of the presently selected node."""
    c = self
    c.endEditing()
    p = c.p
    parent = p.parent()
    if not parent:
        return
    parent.contract()
    c.redraw_after_contract(p=parent)
#@+node:ekr.20031218072017.2903: *3* c_oc.expandAllHeadlines
@g.commander_command('expand-all')
def expandAllHeadlines(self: Cmdr, event: Event = None) -> None:
    """Expand all headlines.
    Warning: this can take a long time for large outlines."""
    c = self
    c.endEditing()
    p0 = c.p
    p = c.rootPosition()
    while p:
        c.expandSubtree(p)
        p.moveToNext()
    c.redraw_after_expand(p0)  # Keep focus on original position
    c.expansionLevel = 0  # Reset expansion level.
#@+node:ekr.20031218072017.2904: *3* c_oc.expandAllSubheads
@g.commander_command('expand-all-subheads')
def expandAllSubheads(self: Cmdr, event: Event = None) -> None:
    """Expand all children of the presently selected node."""
    c, p = self, self.p
    if not p:
        return
    child = p.firstChild()
    c.expandSubtree(p)
    while child:
        c.expandSubtree(child)
        child = child.next()
    c.redraw(p)
#@+node:ekr.20031218072017.2905: *3* c_oc.expandLevel1..9
@g.commander_command('expand-to-level-1')
def expandLevel1(self: Cmdr, event: Event = None) -> None:
    """Expand the outline to level 1"""
    self.expandToLevel(1)

@g.commander_command('expand-to-level-2')
def expandLevel2(self: Cmdr, event: Event = None) -> None:
    """Expand the outline to level 2"""
    self.expandToLevel(2)

@g.commander_command('expand-to-level-3')
def expandLevel3(self: Cmdr, event: Event = None) -> None:
    """Expand the outline to level 3"""
    self.expandToLevel(3)

@g.commander_command('expand-to-level-4')
def expandLevel4(self: Cmdr, event: Event = None) -> None:
    """Expand the outline to level 4"""
    self.expandToLevel(4)

@g.commander_command('expand-to-level-5')
def expandLevel5(self: Cmdr, event: Event = None) -> None:
    """Expand the outline to level 5"""
    self.expandToLevel(5)

@g.commander_command('expand-to-level-6')
def expandLevel6(self: Cmdr, event: Event = None) -> None:
    """Expand the outline to level 6"""
    self.expandToLevel(6)

@g.commander_command('expand-to-level-7')
def expandLevel7(self: Cmdr, event: Event = None) -> None:
    """Expand the outline to level 7"""
    self.expandToLevel(7)

@g.commander_command('expand-to-level-8')
def expandLevel8(self: Cmdr, event: Event = None) -> None:
    """Expand the outline to level 8"""
    self.expandToLevel(8)

@g.commander_command('expand-to-level-9')
def expandLevel9(self: Cmdr, event: Event = None) -> None:
    """Expand the outline to level 9"""
    self.expandToLevel(9)
#@+node:ekr.20031218072017.2906: *3* c_oc.expandNextLevel
@g.commander_command('expand-next-level')
def expandNextLevel(self: Cmdr, event: Event = None) -> None:
    """
    Increase the expansion level of the outline and
    Expand all nodes at that level or lower.
    """
    c = self
    # Expansion levels are now local to a particular tree.
    if c.expansionNode != c.p:
        c.expansionLevel = 1
        c.expansionNode = c.p.copy()
    self.expandToLevel(c.expansionLevel + 1)
#@+node:ekr.20031218072017.2907: *3* c_oc.expandNode
@g.commander_command('expand-node')
def expandNode(self: Cmdr, event: Event = None) -> None:
    """Expand the presently selected node."""
    c = self
    p = c.p
    c.endEditing()
    p.expand()
    c.redraw_after_expand(p)
    c.selectPosition(p)
#@+node:ekr.20040930064232.1: *3* c_oc.expandNodeAndGoToFirstChild
@g.commander_command('expand-and-go-right')
def expandNodeAndGoToFirstChild(self: Cmdr, event: Event = None) -> None:
    """If a node has children, expand it if needed and go to the first child."""
    c, p = self, self.p
    c.endEditing()
    if p.hasChildren():
        if not p.isExpanded():
            c.expandNode()
        c.selectPosition(p.firstChild())
    c.treeFocusHelper()
#@+node:ekr.20171125082744.1: *3* c_oc.expandNodeOrGoToFirstChild
@g.commander_command('expand-or-go-right')
def expandNodeOrGoToFirstChild(self: Cmdr, event: Event = None) -> None:
    """
    Simulate the Right Arrow Key in folder of Windows Explorer.
    if c.p has no children, do nothing.
    Otherwise, if c.p is expanded, select the first child.
    Otherwise, expand c.p.
    """
    c, p = self, self.p
    c.endEditing()
    if p.hasChildren():
        if p.isExpanded():
            c.redraw_after_expand(p.firstChild())
        else:
            c.expandNode()
#@+node:ekr.20060928062431: *3* c_oc.expandOnlyAncestorsOfNode
@g.commander_command('expand-ancestors-only')
def expandOnlyAncestorsOfNode(self: Cmdr, event: Event = None, p: Position = None) -> None:
    """Contract all nodes except ancestors of the selected node."""
    c = self
    level = 1
    if p:
        c.selectPosition(p)  # 2013/12/25
    root = c.p
    for p in c.all_unique_positions():
        p.v.expandedPositions = []
        p.v.contract()
    for p in root.parents():
        p.expand()
        level += 1
    c.expansionLevel = level  # Reset expansion level.
#@+node:ekr.20031218072017.2908: *3* c_oc.expandPrevLevel
@g.commander_command('expand-prev-level')
def expandPrevLevel(self: Cmdr, event: Event = None) -> None:
    """Decrease the expansion level of the outline and
    Expand all nodes at that level or lower."""
    c = self
    # Expansion levels are now local to a particular tree.
    if c.expansionNode != c.p:
        c.expansionLevel = 1
        c.expansionNode = c.p.copy()
    self.expandToLevel(max(1, c.expansionLevel - 1))
#@+node:ekr.20171124081846.1: ** c_oc.fullCheckOutline
@g.commander_command('check-outline')
def fullCheckOutline(self: Cmdr, event: Event = None) -> None:
    """Do a full check of the consistency of a .leo file."""
    c = self
    t1 = time.process_time()
    errors = c.checkOutline()
    t2 = time.time()
    g.es_print(f"check-outline: {errors} error{g.plural(errors)} in {t2 - t1:4.2f} sec.")
#@+node:ekr.20031218072017.2913: ** c_oc.Goto commands
#@+node:ekr.20071213123942: *3* c_oc.findNextClone
@g.commander_command('find-next-clone')
def findNextClone(self: Cmdr, event: Event = None) -> None:
    """Select the next cloned node."""
    c, p = self, self.p
    cc = c.chapterController
    if not p:
        return
    if p.isCloned():
        p.moveToThreadNext()
    flag = False
    while p:
        if p.isCloned():
            flag = True
            break
        else:
            p.moveToThreadNext()
    if flag:
        if cc:
            cc.selectChapterByName('main')
        c.selectPosition(p)
        c.redraw_after_select(p)
    else:
        g.blue('no more clones')
#@+node:ekr.20031218072017.1628: *3* c_oc.goNextVisitedNode
@g.commander_command('go-forward')
def goNextVisitedNode(self: Cmdr, event: Event = None) -> None:
    """Select the next visited node."""
    c = self
    p = c.nodeHistory.goNext()
    if p:
        c.nodeHistory.skipBeadUpdate = True
        try:
            c.selectPosition(p)
        finally:
            c.nodeHistory.skipBeadUpdate = False
            c.redraw_after_select(p)
#@+node:ekr.20031218072017.1627: *3* c_oc.goPrevVisitedNode
@g.commander_command('go-back')
def goPrevVisitedNode(self: Cmdr, event: Event = None) -> None:
    """Select the previously visited node."""
    c = self
    p = c.nodeHistory.goPrev()
    if p:
        c.nodeHistory.skipBeadUpdate = True
        try:
            c.selectPosition(p)
        finally:
            c.nodeHistory.skipBeadUpdate = False
            c.redraw_after_select(p)
#@+node:ekr.20031218072017.2914: *3* c_oc.goToFirstNode
@g.commander_command('goto-first-node')
def goToFirstNode(self: Cmdr, event: Event = None) -> None:
    """
    Select the first node of the entire outline.
    Or the first visible node if Leo is hoisted or within a chapter.
    """
    c = self
    p = c.rootPosition()
    c.expandOnlyAncestorsOfNode(p=p)
    c.redraw()
#@+node:ekr.20051012092453: *3* c_oc.goToFirstSibling
@g.commander_command('goto-first-sibling')
def goToFirstSibling(self: Cmdr, event: Event = None) -> None:
    """Select the first sibling of the selected node."""
    c, p = self, self.p
    if p.hasBack():
        while p.hasBack():
            p.moveToBack()
    c.treeSelectHelper(p)
#@+node:ekr.20070615070925: *3* c_oc.goToFirstVisibleNode
@g.commander_command('goto-first-visible-node')
def goToFirstVisibleNode(self: Cmdr, event: Event = None) -> None:
    """Select the first visible node of the selected chapter or hoist."""
    c = self
    p = c.firstVisible()
    if p:
        if c.sparse_goto_visible:
            c.expandOnlyAncestorsOfNode(p=p)
        else:
            c.treeSelectHelper(p)
        c.redraw()
#@+node:ekr.20031218072017.2915: *3* c_oc.goToLastNode
@g.commander_command('goto-last-node')
def goToLastNode(self: Cmdr, event: Event = None) -> None:
    """Select the last node in the entire tree."""
    c = self
    p = c.rootPosition()
    while p and p.hasThreadNext():
        p.moveToThreadNext()
    c.expandOnlyAncestorsOfNode(p=p)
    c.redraw()
#@+node:ekr.20051012092847.1: *3* c_oc.goToLastSibling
@g.commander_command('goto-last-sibling')
def goToLastSibling(self: Cmdr, event: Event = None) -> None:
    """Select the last sibling of the selected node."""
    c, p = self, self.p
    if p.hasNext():
        while p.hasNext():
            p.moveToNext()
    c.treeSelectHelper(p)
#@+node:ekr.20050711153537: *3* c_oc.goToLastVisibleNode
@g.commander_command('goto-last-visible-node')
def goToLastVisibleNode(self: Cmdr, event: Event = None) -> None:
    """Select the last visible node of selected chapter or hoist."""
    c = self
    p = c.lastVisible()
    if p:
        if c.sparse_goto_visible:
            c.expandOnlyAncestorsOfNode(p=p)
        else:
            c.treeSelectHelper(p)
        c.redraw()
#@+node:ekr.20031218072017.2916: *3* c_oc.goToNextClone
@g.commander_command('goto-next-clone')
def goToNextClone(self: Cmdr, event: Event = None) -> None:
    """
    Select the next node that is a clone of the selected node.
    If the selected node is not a clone, do find-next-clone.
    """
    c, p = self, self.p
    cc = c.chapterController
    if not p:
        return
    if not p.isCloned():
        c.findNextClone()
        return
    v = p.v
    p.moveToThreadNext()
    wrapped = False
    while 1:
        if p and p.v == v:
            break
        elif p:
            p.moveToThreadNext()
        elif wrapped:
            break
        else:
            wrapped = True
            p = c.rootPosition()
    if p:
        c.expandAllAncestors(p)
        if cc:
            # #252: goto-next clone activate chapter.
            chapter = cc.getSelectedChapter()
            old_name = chapter and chapter.name
            new_name = cc.findChapterNameForPosition(p)
            if new_name != old_name:
                cc.selectChapterByName(new_name)
        # Always do a full redraw.
        c.redraw(p)
    else:
        g.blue('done')
#@+node:ekr.20031218072017.2917: *3* c_oc.goToNextDirtyHeadline
@g.commander_command('goto-next-changed')
def goToNextDirtyHeadline(self: Cmdr, event: Event = None) -> None:
    """Select the node that is marked as changed."""
    c, p = self, self.p
    if not p:
        return
    p.moveToThreadNext()
    wrapped = False
    while 1:
        if p and p.isDirty():
            break
        elif p:
            p.moveToThreadNext()
        elif wrapped:
            break
        else:
            wrapped = True
            p = c.rootPosition()
    if not p:
        g.blue('done')
    c.treeSelectHelper(p)  # Sets focus.
#@+node:ekr.20031218072017.2918: *3* c_oc.goToNextMarkedHeadline
@g.commander_command('goto-next-marked')
def goToNextMarkedHeadline(self: Cmdr, event: Event = None) -> None:
    """Select the next marked node."""
    c, p = self, self.p
    if not p:
        return
    p.moveToThreadNext()
    wrapped = False
    while 1:
        if p and p.isMarked():
            break
        elif p:
            p.moveToThreadNext()
        elif wrapped:
            break
        else:
            wrapped = True
            p = c.rootPosition()
    if not p:
        g.blue('done')
    c.treeSelectHelper(p)  # Sets focus.
#@+node:ekr.20031218072017.2919: *3* c_oc.goToNextSibling
@g.commander_command('goto-next-sibling')
def goToNextSibling(self: Cmdr, event: Event = None) -> None:
    """Select the next sibling of the selected node."""
    c, p = self, self.p
    c.treeSelectHelper(p and p.next())
#@+node:ekr.20031218072017.2920: *3* c_oc.goToParent
@g.commander_command('goto-parent')
def goToParent(self: Cmdr, event: Event = None) -> None:
    """Select the parent of the selected node."""
    c, p = self, self.p
    c.treeSelectHelper(p and p.parent())
#@+node:ekr.20190211104913.1: *3* c_oc.goToPrevMarkedHeadline
@g.commander_command('goto-prev-marked')
def goToPrevMarkedHeadline(self: Cmdr, event: Event = None) -> None:
    """Select the previous marked node."""
    c, p = self, self.p
    if not p:
        return
    p.moveToThreadBack()
    wrapped = False
    while 1:
        if p and p.isMarked():
            break
        elif p:
            p.moveToThreadBack()
        elif wrapped:
            break
        else:
            wrapped = True
            p = c.rootPosition()
    if not p:
        g.blue('done')
    c.treeSelectHelper(p)  # Sets focus.
#@+node:ekr.20031218072017.2921: *3* c_oc.goToPrevSibling
@g.commander_command('goto-prev-sibling')
def goToPrevSibling(self: Cmdr, event: Event = None) -> None:
    """Select the previous sibling of the selected node."""
    c, p = self, self.p
    c.treeSelectHelper(p and p.back())
#@+node:ekr.20031218072017.2993: *3* c_oc.selectThreadBack
@g.commander_command('goto-prev-node')
def selectThreadBack(self: Cmdr, event: Event = None) -> None:
    """Select the node preceding the selected node in outline order."""
    c, p = self, self.p
    if not p:
        return
    p.moveToThreadBack()
    c.treeSelectHelper(p)
#@+node:ekr.20031218072017.2994: *3* c_oc.selectThreadNext
@g.commander_command('goto-next-node')
def selectThreadNext(self: Cmdr, event: Event = None) -> None:
    """Select the node following the selected node in outline order."""
    c, p = self, self.p
    if not p:
        return
    p.moveToThreadNext()
    c.treeSelectHelper(p)
#@+node:ekr.20031218072017.2995: *3* c_oc.selectVisBack
@g.commander_command('goto-prev-visible')
def selectVisBack(self: Cmdr, event: Event = None) -> None:
    """Select the visible node preceding the presently selected node."""
    # This has an up arrow for a control key.
    c, p = self, self.p
    if not p:
        return
    if c.canSelectVisBack():
        p.moveToVisBack(c)
        c.treeSelectHelper(p)
    else:
        c.endEditing()  # 2011/05/28: A special case.
#@+node:ekr.20031218072017.2996: *3* c_oc.selectVisNext
@g.commander_command('goto-next-visible')
def selectVisNext(self: Cmdr, event: Event = None) -> None:
    """Select the visible node following the presently selected node."""
    c, p = self, self.p
    if not p:
        return
    if c.canSelectVisNext():
        p.moveToVisNext(c)
        c.treeSelectHelper(p)
    else:
        c.endEditing()  # 2011/05/28: A special case.
#@+node:ekr.20031218072017.2028: ** c_oc.hoist/dehoist/clearAllHoists
#@+node:ekr.20120308061112.9865: *3* c_oc.deHoist
@g.commander_command('de-hoist')
@g.commander_command('dehoist')
def dehoist(self: Cmdr, event: Event = None) -> None:
    """Undo a previous hoist of an outline."""
    c, cc, tag = self, self.chapterController, '@chapter '
    if not c.p or not c.hoistStack:
        return
    # #2718: de-hoisting an @chapter node is equivalent to selecting the main chapter.
    if c.p.h.startswith(tag) or c.hoistStack[-1].p.h.startswith(tag):
        c.hoistStack = []
        cc.selectChapterByName('main')
        return
    bunch = c.hoistStack.pop()
    p = bunch.p
    # Checks 'expanded' property, which was preserved by 'hoist' method
    if bunch.expanded:
        p.expand()
    else:
        p.contract()
    c.setCurrentPosition(p)
    c.redraw()
    c.frame.clearStatusLine()
    c.frame.putStatusLine("De-Hoist: " + p.h)
    g.doHook('hoist-changed', c=c)
#@+node:ekr.20120308061112.9866: *3* c_oc.clearAllHoists
@g.commander_command('clear-all-hoists')
def clearAllHoists(self: Cmdr, event: Event = None) -> None:
    """Undo a previous hoist of an outline."""
    c = self
    c.hoistStack = []
    c.frame.putStatusLine("Hoists cleared")
    g.doHook('hoist-changed', c=c)
#@+node:ekr.20120308061112.9867: *3* c_oc.hoist
@g.commander_command('hoist')
def hoist(self: Cmdr, event: Event = None) -> None:
    """Make only the selected outline visible."""
    c, p = self, self.p
    if not p:
        return
    # Don't hoist an @chapter node.
    if c.chapterController and p.h.startswith('@chapter '):
        if not g.unitTesting:
            g.es('can not hoist an @chapter node.', color='blue')
        return
    # Remember the expansion state.
    bunch = g.Bunch(p=p.copy(), expanded=p.isExpanded())
    c.hoistStack.append(bunch)
    p.expand()
    c.redraw(p)
    c.frame.clearStatusLine()
    c.frame.putStatusLine("Hoist: " + p.h)
    g.doHook('hoist-changed', c=c)
#@+node:ekr.20031218072017.1759: ** c_oc.Insert, Delete & Clone commands
#@+node:ekr.20031218072017.1762: *3* c_oc.clone
@g.commander_command('clone-node')
def clone(self: Cmdr, event: Event = None) -> Optional[Position]:
    """Create a clone of the selected outline."""
    c, p, u = self, self.p, self.undoer
    if not p:
        return None
    undoData = c.undoer.beforeCloneNode(p)
    c.endEditing()  # Capture any changes to the headline.
    clone = p.clone()
    clone.setDirty()
    c.setChanged()
    if c.checkOutline() == 0:
        u.afterCloneNode(clone, 'Clone Node', undoData)
        c.redraw(clone)
        c.treeWantsFocus()
        return clone  # For mod_labels and chapters plugins.
    clone.doDelete()
    c.setCurrentPosition(p)
    return None
#@+node:ekr.20150630152607.1: *3* c_oc.cloneToAtSpot
@g.commander_command('clone-to-at-spot')
def cloneToAtSpot(self: Cmdr, event: Event = None) -> None:
    """
    Create a clone of the selected node and move it to the last @spot node
    of the outline. Create the @spot node if necessary.
    """
    c, p, u = self, self.p, self.undoer
    if not p:
        return
    # 2015/12/27: fix bug 220: do not allow clone-to-at-spot on @spot node.
    if p.h.startswith('@spot'):
        g.es("can not clone @spot node", color='red')
        return
    last_spot = None
    for p2 in c.all_positions():
        if g.match_word(p2.h, 0, '@spot'):
            last_spot = p2.copy()
    if not last_spot:
        last = c.lastTopLevel()
        last_spot = last.insertAfter()
        last_spot.h = '@spot'
    undoData = c.undoer.beforeCloneNode(p)
    c.endEditing()  # Capture any changes to the headline.
    clone = p.copy()
    clone._linkAsNthChild(last_spot, n=last_spot.numberOfChildren())
    clone.setDirty()
    c.setChanged()
    if c.checkOutline() == 0:
        u.afterCloneNode(clone, 'Clone Node', undoData)
        c.contractAllHeadlines()
        c.redraw(clone)
    else:
        clone.doDelete()
        c.setCurrentPosition(p)
#@+node:ekr.20141023154408.5: *3* c_oc.cloneToLastNode
@g.commander_command('clone-node-to-last-node')
def cloneToLastNode(self: Cmdr, event: Event = None) -> None:
    """
    Clone the selected node and move it to the last node.
    Do *not* change the selected node.
    """
    c, p, u = self, self.p, self.undoer
    if not p:
        return
    prev = p.copy()
    undoData = c.undoer.beforeCloneNode(p)
    c.endEditing()  # Capture any changes to the headline.
    clone = p.clone()
    last = c.rootPosition()
    while last and last.hasNext():
        last.moveToNext()
    clone.moveAfter(last)
    clone.setDirty()
    c.setChanged()
    u.afterCloneNode(clone, 'Clone Node To Last', undoData)
    c.redraw(prev)
    # return clone # For mod_labels and chapters plugins.
#@+node:ekr.20031218072017.1193: *3* c_oc.deleteOutline
@g.commander_command('delete-node')
def deleteOutline(self: Cmdr, event: Event = None, op_name: str = "Delete Node") -> None:
    """Deletes the selected outline."""
    c, u = self, self.undoer
    p = c.p
    if not p:
        return
    c.endEditing()  # Make sure we capture the headline for Undo.
    if False:  # c.config.getBool('select-next-after-delete'):
        # #721: Optionally select next node after delete.
        if p.hasVisNext(c):
            newNode = p.visNext(c)
        elif p.hasParent():
            newNode = p.parent()
        else:
            newNode = p.back()  # _not_ p.visBack(): we are at the top level.
    else:
        # Legacy: select previous node if possible.
        if p.hasVisBack(c):
            newNode = p.visBack(c)
        else:
            newNode = p.next()  # _not_ p.visNext(): we are at the top level.
    if not newNode:
        return
    undoData = u.beforeDeleteNode(p)
    p.setDirty()
    p.doDelete(newNode)
    c.setChanged()
    u.afterDeleteNode(newNode, op_name, undoData)
    c.redraw(newNode)
    c.checkOutline()
#@+node:ekr.20071005173203.1: *3* c_oc.insertChild
@g.commander_command('insert-child')
def insertChild(self: Cmdr, event: Event = None) -> None:
    """Insert a node after the presently selected node."""
    c = self
    return c.insertHeadline(event=event, op_name='Insert Child', as_child=True)
#@+node:ekr.20031218072017.1761: *3* c_oc.insertHeadline (insert-*)
@g.commander_command('insert-node')
def insertHeadline(
    self: Cmdr, event:
    Event = None,
    op_name: str = "Insert Node",
    as_child: bool = False,
) -> Optional[Position]:
    """
    If c.p is expanded, insert a new node as the first or last child of c.p,
    depending on @bool insert-new-nodes-at-end.

    If c.p is not expanded, insert a new node after c.p.
    """
    c = self
    # Fix #600.
    return insertHeadlineHelper(c, event=event, as_child=as_child, op_name=op_name)

@g.commander_command('insert-as-first-child')
def insertNodeAsFirstChild(self: Cmdr, event: Event = None) -> Optional[Position]:
    """Insert a node as the first child of the previous node."""
    c = self
    return insertHeadlineHelper(c, event=event, as_first_child=True)

@g.commander_command('insert-as-last-child')
def insertNodeAsLastChild(self: Cmdr, event: Event = None) -> Optional[Position]:
    """Insert a node as the last child of the previous node."""
    c = self
    return insertHeadlineHelper(c, event=event, as_last_child=True)
#@+node:ekr.20171124091846.1: *4* function: insertHeadlineHelper
def insertHeadlineHelper(
    c: Cmdr,
    event: Event = None,
    op_name: str = "Insert Node",
    as_child: bool = False,
    as_first_child: bool = False,
    as_last_child: bool = False,
) -> Optional[Position]:
    """Insert a node after the presently selected node."""
    u = c.undoer
    current = c.p
    if not current:
        return None
    c.endEditing()
    undoData = c.undoer.beforeInsertNode(current)
    if as_first_child:
        p = current.insertAsNthChild(0)
    elif as_last_child:
        p = current.insertAsLastChild()
    elif (
        as_child or
        (current.hasChildren() and current.isExpanded()) or
        (c.hoistStack and current == c.hoistStack[-1].p)
    ):
        # Make sure the new node is visible when hoisting.
        if c.config.getBool('insert-new-nodes-at-end'):
            p = current.insertAsLastChild()
        else:
            p = current.insertAsNthChild(0)
    else:
        p = current.insertAfter()
    g.doHook('create-node', c=c, p=p)
    p.setDirty()
    c.setChanged()
    u.afterInsertNode(p, op_name, undoData)
    c.redrawAndEdit(p, selectAll=True)
    return p
#@+node:ekr.20130922133218.11540: *3* c_oc.insertHeadlineBefore
@g.commander_command('insert-node-before')
def insertHeadlineBefore(self: Cmdr, event: Event = None) -> Optional[Position]:
    """Insert a node before the presently selected node."""
    c, current, u = self, self.p, self.undoer
    op_name = 'Insert Node Before'
    if not current:
        return None
    # Can not insert before the base of a hoist.
    if c.hoistStack and current == c.hoistStack[-1].p:
        g.warning('can not insert a node before the base of a hoist')
        return None
    c.endEditing()
    undoData = u.beforeInsertNode(current)
    p = current.insertBefore()
    g.doHook('create-node', c=c, p=p)
    p.setDirty()
    c.setChanged()
    u.afterInsertNode(p, op_name, undoData)
    c.redrawAndEdit(p, selectAll=True)
    return p
#@+node:ekr.20031218072017.2922: ** c_oc.Mark commands
#@+node:ekr.20090905110447.6098: *3* c_oc.cloneMarked
@g.commander_command('clone-marked-nodes')
def cloneMarked(self: Cmdr, event: Event = None) -> None:
    """Clone all marked nodes as children of a new node."""
    c, u = self, self.undoer
    p1 = c.p.copy()
    # Create a new node to hold clones.
    parent = p1.insertAfter()
    parent.h = 'Clones of marked nodes'
    cloned, n, p = [], 0, c.rootPosition()
    while p:
        # Careful: don't clone already-cloned nodes.
        if p == parent:
            p.moveToNodeAfterTree()
        elif p.isMarked() and p.v not in cloned:
            cloned.append(p.v)
            if 0:  # old code
                # Calling p.clone would cause problems
                p.clone().moveToLastChildOf(parent)
            else:  # New code.
                # Create the clone directly as a child of parent.
                p2 = p.copy()
                n = parent.numberOfChildren()
                p2._linkAsNthChild(parent, n)
            p.moveToNodeAfterTree()
            n += 1
        else:
            p.moveToThreadNext()
    if n:
        c.setChanged()
        parent.expand()
        c.selectPosition(parent)
        u.afterCloneMarkedNodes(p1)
    else:
        parent.doDelete()
        c.selectPosition(p1)
    if not g.unitTesting:
        g.blue(f"cloned {n} nodes")
    c.redraw()
#@+node:ekr.20160502090456.1: *3* c_oc.copyMarked
@g.commander_command('copy-marked-nodes')
def copyMarked(self: Cmdr, event: Event = None) -> None:
    """Copy all marked nodes as children of a new node."""
    c, u = self, self.undoer
    p1 = c.p.copy()
    # Create a new node to hold clones.
    parent = p1.insertAfter()
    parent.h = 'Copies of marked nodes'
    copied, n, p = [], 0, c.rootPosition()
    while p:
        # Careful: don't clone already-cloned nodes.
        if p == parent:
            p.moveToNodeAfterTree()
        elif p.isMarked() and p.v not in copied:
            copied.append(p.v)
            p2 = p.copyWithNewVnodes(copyMarked=True)
            p2._linkAsNthChild(parent, n)
            p.moveToNodeAfterTree()
            n += 1
        else:
            p.moveToThreadNext()
    if n:
        c.setChanged()
        parent.expand()
        c.selectPosition(parent)
        u.afterCopyMarkedNodes(p1)
    else:
        parent.doDelete()
        c.selectPosition(p1)
    if not g.unitTesting:
        g.blue(f"copied {n} nodes")
    c.redraw()
#@+node:ekr.20111005081134.15540: *3* c_oc.deleteMarked
@g.commander_command('delete-marked-nodes')
def deleteMarked(self: Cmdr, event: Event = None) -> None:
    """Delete all marked nodes."""
    c, u = self, self.undoer
    p1 = c.p.copy()
    undo_data, p = [], c.rootPosition()
    while p:
        if p.isMarked():
            undo_data.append(p.copy())
            next = p.positionAfterDeletedTree()
            p.doDelete()
            p = next
        else:
            p.moveToThreadNext()
    if undo_data:
        u.afterDeleteMarkedNodes(undo_data, p1)
        if not g.unitTesting:
            g.blue(f"deleted {len(undo_data)} nodes")
        c.setChanged()
    # Don't even *think* about restoring the old position.
    c.contractAllHeadlines()
    c.redraw(c.rootPosition())
#@+node:ekr.20111005081134.15539: *3* c_oc.moveMarked & helper
@g.commander_command('move-marked-nodes')
def moveMarked(self: Cmdr, event: Event = None) -> None:
    """
    Move all marked nodes as children of a new node.
    This command is not undoable.
    Consider using clone-marked-nodes, followed by copy/paste instead.
    """
    c = self
    p1 = c.p.copy()
    # Check for marks.
    for v in c.all_unique_nodes():
        if v.isMarked():
            break
    else:
        g.warning('no marked nodes')
        return
    result = g.app.gui.runAskYesNoDialog(c,
        'Move Marked Nodes?',
        message='move-marked-nodes is not undoable\nProceed?',
    )
    if result == 'no':
        return
    # Create a new *root* node to hold the moved nodes.
    # This node's position remains stable while other nodes move.
    parent = createMoveMarkedNode(c)
    assert not parent.isMarked()
    moved = []
    p = c.rootPosition()
    while p:
        assert parent == c.rootPosition()
        # Careful: don't move already-moved nodes.
        if p.isMarked() and not parent.isAncestorOf(p):
            moved.append(p.copy())
            next = p.positionAfterDeletedTree()
            p.moveToLastChildOf(parent)  # This does not change parent's position.
            p = next
        else:
            p.moveToThreadNext()
    if moved:
        # Find a position p2 outside of parent's tree with p2.v == p1.v.
        # Such a position may not exist.
        p2 = c.rootPosition()
        while p2:
            if p2 == parent:
                p2.moveToNodeAfterTree()
            elif p2.v == p1.v:
                break
            else:
                p2.moveToThreadNext()
        else:
            # Not found.  Move to last top-level.
            p2 = c.lastTopLevel()
        parent.moveAfter(p2)
        # u.afterMoveMarkedNodes(moved, p1)
        if not g.unitTesting:
            g.blue(f"moved {len(moved)} nodes")
        c.setChanged()
    # Calling c.contractAllHeadlines() causes problems when in a chapter.
    c.redraw(parent)
#@+node:ekr.20111005081134.15543: *4* def createMoveMarkedNode
def createMoveMarkedNode(c: Cmdr) -> Position:
    oldRoot = c.rootPosition()
    p = oldRoot.insertAfter()
    p.h = 'Moved marked nodes'
    p.moveToRoot()
    return p
#@+node:ekr.20031218072017.2923: *3* c_oc.markChangedHeadlines
@g.commander_command('mark-changed-items')
def markChangedHeadlines(self: Cmdr, event: Event = None) -> None:
    """Mark all nodes that have been changed."""
    c, current, u = self, self.p, self.undoer
    undoType = 'Mark Changed'
    c.endEditing()
    changed = False
    for p in c.all_unique_positions():
        if p.isDirty() and not p.isMarked():
            if not changed:
                u.beforeChangeGroup(current, undoType)
            changed = True
            bunch = u.beforeMark(p, undoType)
            # c.setMarked calls a hook.
            c.setMarked(p)
            p.setDirty()
            c.setChanged()
            u.afterMark(p, undoType, bunch)
    if changed:
        u.afterChangeGroup(current, undoType)
    if not g.unitTesting:
        g.blue('done')
#@+node:ekr.20031218072017.2924: *3* c_oc.markChangedRoots
def markChangedRoots(self: Cmdr, event: Event = None) -> None:
    """Mark all changed @root nodes."""
    c, current, u = self, self.p, self.undoer
    undoType = 'Mark Changed'
    c.endEditing()
    changed = False
    for p in c.all_unique_positions():
        if p.isDirty() and not p.isMarked():
            s = p.b
            flag, i = g.is_special(s, "@root")
            if flag:
                if not changed:
                    u.beforeChangeGroup(current, undoType)
                changed = True
                bunch = u.beforeMark(p, undoType)
                c.setMarked(p)  # Calls a hook.
                p.setDirty()
                c.setChanged()
                u.afterMark(p, undoType, bunch)
    if changed:
        u.afterChangeGroup(current, undoType)
    if not g.unitTesting:
        g.blue('done')
#@+node:ekr.20031218072017.2928: *3* c_oc.markHeadline
@g.commander_command('mark')  # Compatibility
@g.commander_command('toggle-mark')
def markHeadline(self: Cmdr, event: Event = None) -> None:
    """Toggle the mark of the selected node."""
    c, p, u = self, self.p, self.undoer
    if not p:
        return
    c.endEditing()
    undoType = 'Unmark' if p.isMarked() else 'Mark'
    bunch = u.beforeMark(p, undoType)
    # c.set/clearMarked call a hook.
    if p.isMarked():
        c.clearMarked(p)
    else:
        c.setMarked(p)
    p.setDirty()
    c.setChanged()
    u.afterMark(p, undoType, bunch)
#@+node:ekr.20031218072017.2929: *3* c_oc.markSubheads
@g.commander_command('mark-subheads')
def markSubheads(self: Cmdr, event: Event = None) -> None:
    """Mark all children of the selected node as changed."""
    c, current, u = self, self.p, self.undoer
    undoType = 'Mark Subheads'
    if not current:
        return
    c.endEditing()
    changed = False
    for p in current.children():
        if not p.isMarked():
            if not changed:
                u.beforeChangeGroup(current, undoType)
            changed = True
            bunch = u.beforeMark(p, undoType)
            c.setMarked(p)  # Calls a hook.
            p.setDirty()
            c.setChanged()
            u.afterMark(p, undoType, bunch)
    if changed:
        u.afterChangeGroup(current, undoType)
#@+node:ekr.20031218072017.2930: *3* c_oc.unmarkAll
@g.commander_command('unmark-all')
def unmarkAll(self: Cmdr, event: Event = None) -> None:
    """Unmark all nodes in the entire outline."""
    c, current, u = self, self.p, self.undoer
    undoType = 'Unmark All'
    if not current:
        return
    c.endEditing()
    changed = False
    p = None  # To keep pylint happy.
    for p in c.all_unique_positions():
        if p.isMarked():
            if not changed:
                u.beforeChangeGroup(current, undoType)
            bunch = u.beforeMark(p, undoType)
            # c.clearMarked(p) # Very slow: calls a hook.
            p.v.clearMarked()
            p.setDirty()
            u.afterMark(p, undoType, bunch)
            changed = True
    if changed:
        g.doHook("clear-all-marks", c=c, p=p)
        c.setChanged()
        u.afterChangeGroup(current, undoType)
#@+node:ekr.20031218072017.1766: ** c_oc.Move commands
#@+node:ekr.20031218072017.1767: *3* c_oc.demote
@g.commander_command('demote')
def demote(self: Cmdr, event: Event = None) -> None:
    """Make all following siblings children of the selected node."""
    c, p, u = self, self.p, self.undoer
    if not p or not p.hasNext():
        c.treeFocusHelper()
        return
    # Make sure all the moves will be valid.
    next = p.next()
    while next:
        if not c.checkMoveWithParentWithWarning(next, p, True):
            c.treeFocusHelper()
            return
        next.moveToNext()
    c.endEditing()
    parent_v = p._parentVnode()
    n = p.childIndex()
    followingSibs = parent_v.children[n + 1 :]
    # Remove the moved nodes from the parent's children.
    parent_v.children = parent_v.children[: n + 1]
    # Add the moved nodes to p's children
    p.v.children.extend(followingSibs)
    # Adjust the parent links in the moved nodes.
    # There is no need to adjust descendant links.
    for child in followingSibs:
        child.parents.remove(parent_v)
        child.parents.append(p.v)
    p.expand()
    p.setDirty()
    c.setChanged()
    u.afterDemote(p, followingSibs)
    c.redraw(p)
    c.updateSyntaxColorer(p)  # Moving can change syntax coloring.
#@+node:ekr.20031218072017.1768: *3* c_oc.moveOutlineDown
@g.commander_command('move-outline-down')
def moveOutlineDown(self: Cmdr, event: Event = None) -> None:
    """Move the selected node down."""
    # Moving down is more tricky than moving up because we can't
    # move p to be a child of itself.
    #
    # An important optimization:
    # we don't have to call checkMoveWithParentWithWarning() if the parent of
    # the moved node remains the same.
    c, p, u = self, self.p, self.undoer
    if not p:
        return
    if not c.canMoveOutlineDown():
        if c.hoistStack:
            cantMoveMessage(c)
        c.treeFocusHelper()
        return
    parent = p.parent()
    next = p.visNext(c)
    while next and p.isAncestorOf(next):
        next = next.visNext(c)
    if not next:
        if c.hoistStack:
            cantMoveMessage(c)
        c.treeFocusHelper()
        return
    c.endEditing()
    undoData = u.beforeMoveNode(p)
    #@+<< Move p down & set moved if successful >>
    #@+node:ekr.20031218072017.1769: *4* << Move p down & set moved if successful >>
    if next.hasChildren() and next.isExpanded():
        # Attempt to move p to the first child of next.
        moved = c.checkMoveWithParentWithWarning(p, next, True)
        if moved:
            p.setDirty()
            p.moveToNthChildOf(next, 0)
    else:
        # Attempt to move p after next.
        moved = c.checkMoveWithParentWithWarning(p, next.parent(), True)
        if moved:
            p.setDirty()
            p.moveAfter(next)
    # Patch by nh2: 0004-Add-bool-collapse_nodes_after_move-option.patch
    if (
        c.collapse_nodes_after_move
        and moved and c.sparse_move
        and parent and not parent.isAncestorOf(p)
    ):
        # New in Leo 4.4.2: contract the old parent if it is no longer the parent of p.
        parent.contract()
    #@-<< Move p down & set moved if successful >>
    if moved:
        p.setDirty()
        c.setChanged()
        u.afterMoveNode(p, 'Move Down', undoData)
    c.redraw(p)
    c.updateSyntaxColorer(p)  # Moving can change syntax coloring.
#@+node:ekr.20031218072017.1770: *3* c_oc.moveOutlineLeft
@g.commander_command('move-outline-left')
def moveOutlineLeft(self: Cmdr, event: Event = None) -> None:
    """Move the selected node left if possible."""
    c, p, u = self, self.p, self.undoer
    if not p:
        return
    if not c.canMoveOutlineLeft():
        if c.hoistStack:
            cantMoveMessage(c)
        c.treeFocusHelper()
        return
    if not p.hasParent():
        c.treeFocusHelper()
        return
    parent = p.parent()
    c.endEditing()
    undoData = u.beforeMoveNode(p)
    p.setDirty()
    p.moveAfter(parent)
    p.setDirty()
    c.setChanged()
    u.afterMoveNode(p, 'Move Left', undoData)
    # Patch by nh2: 0004-Add-bool-collapse_nodes_after_move-option.patch
    if c.collapse_nodes_after_move and c.sparse_move:  # New in Leo 4.4.2
        parent.contract()
    c.redraw(p)
    c.recolor()  # Moving can change syntax coloring.
#@+node:ekr.20031218072017.1771: *3* c_oc.moveOutlineRight
@g.commander_command('move-outline-right')
def moveOutlineRight(self: Cmdr, event: Event = None) -> None:
    """Move the selected node right if possible."""
    c, p, u = self, self.p, self.undoer
    if not p:
        return
    if not c.canMoveOutlineRight():  # 11/4/03: Support for hoist.
        if c.hoistStack:
            cantMoveMessage(c)
        c.treeFocusHelper()
        return
    back = p.back()
    if not back:
        c.treeFocusHelper()
        return
    if not c.checkMoveWithParentWithWarning(p, back, True):
        c.treeFocusHelper()
        return
    c.endEditing()
    undoData = u.beforeMoveNode(p)
    p.setDirty()
    n = back.numberOfChildren()
    p.moveToNthChildOf(back, n)
    p.setDirty()
    c.setChanged()  # #2036.
    u.afterMoveNode(p, 'Move Right', undoData)
    c.redraw(p)
    c.recolor()
#@+node:ekr.20031218072017.1772: *3* c_oc.moveOutlineUp
@g.commander_command('move-outline-up')
def moveOutlineUp(self: Cmdr, event: Event = None) -> None:
    """Move the selected node up if possible."""
    c, p, u = self, self.p, self.undoer
    if not p:
        return
    if not c.canMoveOutlineUp():  # Support for hoist.
        if c.hoistStack:
            cantMoveMessage(c)
        c.treeFocusHelper()
        return
    back = p.visBack(c)
    if not back:
        return
    back2 = back.visBack(c)
    c.endEditing()
    undoData = u.beforeMoveNode(p)
    moved = False
    #@+<< Move p up >>
    #@+node:ekr.20031218072017.1773: *4* << Move p up >>
    parent = p.parent()
    if not back2:
        if c.hoistStack:  # hoist or chapter.
            limit, limitIsVisible = c.visLimit()
            assert limit
            if limitIsVisible:
                # canMoveOutlineUp should have caught this.
                g.trace('can not happen. In hoist')
            else:
                moved = True
                p.setDirty()
                p.moveToFirstChildOf(limit)
        else:
            # p will be the new root node
            p.setDirty()
            p.moveToRoot()
            moved = True
    elif back2.hasChildren() and back2.isExpanded():
        if c.checkMoveWithParentWithWarning(p, back2, True):
            moved = True
            p.setDirty()
            p.moveToNthChildOf(back2, 0)
    else:
        if c.checkMoveWithParentWithWarning(p, back2.parent(), True):
            moved = True
            p.setDirty()
            p.moveAfter(back2)
    # Patch by nh2: 0004-Add-bool-collapse_nodes_after_move-option.patch
    if (
        c.collapse_nodes_after_move
        and moved and c.sparse_move
        and parent and not parent.isAncestorOf(p)
    ):
        # New in Leo 4.4.2: contract the old parent if it is no longer the parent of p.
        parent.contract()
    #@-<< Move p up >>
    if moved:
        p.setDirty()
        c.setChanged()
        u.afterMoveNode(p, 'Move Up', undoData)
    c.redraw(p)
    c.updateSyntaxColorer(p)  # Moving can change syntax coloring.
#@+node:ekr.20230902051130.1: *3* c_oc.moveOutlineToFirstChild
@g.commander_command('move-outline-to-first-child')
def moveOutlineToFirstChild(self: Cmdr, event: Event = None) -> None:
    """
    Move the selected node so that it is the first child of its parent.

    Do nothing if a hoist is in effect.
    """
    c, p, u = self, self.p, self.undoer
    if not p:
        return
    if c.hoistStack:
        return
    if not p.hasBack():
        return
    parent = p.parent()
    if not parent:
        return
    c.endEditing()
    undoData = u.beforeMoveNode(p)
    p.moveToNthChildOf(p.parent(), 0)
    p.setDirty()
    c.setChanged()
    u.afterMoveNode(p, 'Move To First Child', undoData)
    c.redraw(p)
#@+node:ekr.20230902051833.1: *3* c_oc.moveOutlineToLastChild
@g.commander_command('move-outline-to-last-child')
def moveOutlineToLastChild(self: Cmdr, event: Event = None) -> None:
    """
    Move the selected node so that it is the last child of its parent.

    Do nothing if a hoist is in effect.
    """
    c, p, u = self, self.p, self.undoer
    if not p:
        return
    if c.hoistStack:
        return
    if not p.hasNext():
        return
    parent = p.parent()
    if not parent:
        return
    c.endEditing()
    undoData = u.beforeMoveNode(p)
    p.moveToNthChildOf(parent, len(parent.v.children) - 1)
    p.setDirty()
    c.setChanged()
    u.afterMoveNode(p, 'Move To Last Child', undoData)
    c.redraw(p)
#@+node:ekr.20031218072017.1774: *3* c_oc.promote
@g.commander_command('promote')
def promote(self: Cmdr, event: Event = None, undoFlag: bool = True) -> None:
    """Make all children of the selected nodes siblings of the selected node."""
    c, p, u = self, self.p, self.undoer
    if not p or not p.hasChildren():
        c.treeFocusHelper()
        return
    c.endEditing()
    children = p.v.children  # First, for undo.
    p.promote()
    c.setChanged()
    if undoFlag:
        p.setDirty()
        u.afterPromote(p, children)
    c.redraw(p)
    c.updateSyntaxColorer(p)  # Moving can change syntax coloring.
#@+node:ekr.20071213185710: *3* c_oc.toggleSparseMove
@g.commander_command('toggle-sparse-move')
def toggleSparseMove(self: Cmdr, event: Event = None) -> None:
    """Toggle whether moves collapse the outline."""
    c = self
    c.sparse_move = not c.sparse_move
    if not g.unitTesting:
        g.blue(f"sparse-move: {c.sparse_move}")
#@+node:ekr.20080425060424.1: ** c_oc.Sort commands
#@+node:felix.20230318172503.1: *3* c_oc.reverseSortChildren
@g.commander_command('reverse-sort-children')
def reverseSortChildren(
    self: Cmdr,
    event: Event = None,
    key: str = None
) -> None:
    """Sort the children of a node in reverse order."""
    self.sortChildren(key=key, reverse=True)  # as reverse, Fixes #3188
#@+node:felix.20230318172511.1: *3* c_oc.reverseSortSiblings
@g.commander_command('reverse-sort-siblings')
def reverseSortSiblings(
    self: Cmdr,
    event: Event = None,
    key: str = None
) -> None:
    """Sort the siblings of a node in reverse order."""
    self.sortSiblings(key=key, reverse=True)  # as reverse, Fixes #3188
#@+node:ekr.20050415134809: *3* c_oc.sortChildren
@g.commander_command('sort-children')
def sortChildren(
    self: Cmdr,
    event: Event = None,
    key: Callable = None,
    reverse: bool = False
) -> None:
    """Sort the children of a node."""
    # This method no longer supports the 'cmp' keyword arg.
    c, p = self, self.p
    if p and p.hasChildren():
        c.sortSiblings(p=p.firstChild(), sortChildren=True, key=key, reverse=reverse)
#@+node:ekr.20050415134809.1: *3* c_oc.sortSiblings
@g.commander_command('sort-siblings')
def sortSiblings(
    self: Cmdr,
    event: Event = None,  # cmp keyword is no longer supported.
    key: Callable = None,
    p: Position = None,
    sortChildren: bool = False,
    reverse: bool = False,
) -> None:
    """Sort the siblings of a node."""
    c, u = self, self.undoer
    if not p:
        p = c.p
    if not p:
        return

    oldP, newP = p.copy(), p.copy()
    c.endEditing()
    undoType = 'Sort Children' if sortChildren else 'Sort Siblings'
    if reverse:
        undoType = 'Reverse ' + undoType
    parent_v = p._parentVnode()
    oldChildren = parent_v.children[:]
    newChildren = parent_v.children[:]
    if key is None:

        def lowerKey(self: Cmdr) -> str:
            return self.h.lower()

        key = lowerKey

    newChildren.sort(key=key, reverse=reverse)  # type:ignore
    if oldChildren == newChildren:
        return
    # 2010/01/20. Fix bug 510148.
    c.setChanged()
    bunch = u.beforeSort(p, undoType, oldChildren, newChildren, sortChildren)
    # A copy, so its not the undo bead's oldChildren. Fixes #3205
    parent_v.children = newChildren[:]
    u.afterSort(p, bunch)
    # Sorting destroys position p, and possibly the root position.
    # Only the child index of new position changes!
    for i, v in enumerate(newChildren):
        if v.gnx == oldP.v.gnx:
            newP._childIndex = i
            break

    if newP.parent():
        newP.parent().setDirty()

    if sortChildren:
        c.redraw(newP.parent())
    else:
        c.redraw(newP)
#@+node:ekr.20070420092425: ** def cantMoveMessage
def cantMoveMessage(c: Cmdr) -> None:
    h = c.rootPosition().h
    kind = 'chapter' if h.startswith('@chapter') else 'hoist'
    g.warning("can't move node out of", kind)
#@+node:ekr.20180201040936.1: ** count-children
@g.command('count-children')
def count_children(event: Event = None) -> None:
    """Print out the number of children for the currently selected node"""
    c = event and event.get('c')
    if c:
        g.es_print(f"{c.p.numberOfChildren()} children")
#@-others
#@-leo


import requests
from parsel import Selector

headers = {
    "accept": "text/html,application/xhtml+xml,application/xml;q=0.9,image/avif,image/webp,image/apng,*/*;q=0.8,application/signed-exchange;v=b3;q=0.7",
    "accept-language": "en-IN,en-GB;q=0.9,en-US;q=0.8,en;q=0.7",
    "user-agent": "Mozilla/5.0 (X11; Linux x86_64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/113.0.0.0 Safari/537.36",
}
url = "https://www.edeka24.de/"

def url_links(selector, xpath_expression, page=''):
    links = selector.xpath(xpath_expression).extract()
    repeated = set()
    for i in range(len(links)):
        if not links[i].startswith("https://www.edeka24.de/"):
            links[i] = "https://www.edeka24.de" + links[i]

    for link in links:
        urls = link.split('?')[0]
        if urls in repeated:
            break
        repeated.add(urls)
        url1=urls + "?pgNr=" + str(page)
        print(url1)
        response1 = requests.get(url1, headers=headers)
        selector1= Selector(response1.text)
        title =selector1.xpath("//div[@class='product-details']/a[@class='title']/h2/text()").extract()
        product_title = '\n'.join(title)
        print(product_title)
        # price =selector.xpath("//div[@class='left']/text()").extract()
        # product_price = '\n'.join(price)
        # print(product_price)
        # else:
        #     price=selector.xpath("//div[@class='product-details']//div[@class='price salesprice']/text()").extract()
        #     product_price = '\n'.join(price)
        #     print(product_price)
        # response1 = requests.get(url1, headers=headers)
        # selector1= Selector(response1.text)
        # title =selector1.xpath("//div[@class='product-details']/a[@class='title']/h2/text()").extract()
        # product_title = '\n'.join(title)
        # print(product_title)
        images =selector1.xpath("//div[@class='product-image']/a/img/@src").extract()
        image = '\n'.join(images)
        print(image)
        print()
def main_list(page=1):
    response = requests.get(url, headers=headers)
    print(response.status_code)
    selector = Selector(response.text)
    url_links(selector, '//a/@data-link', page)
    url_links(selector, "//li[@class='nav-item-mobile is-level-2']/a/@href", page)
    url_links(selector, '//li[@class="nav-item-mobile is-level-1"]/span/a/@href', page)
    main_list(page + 1)

main_list(1)


import logging
from collections import defaultdict

import gevent

log = logging.getLogger(__name__)

class Triggers(object):

    def __init__(self, connection, checkpoint):
        self._conn = connection
        self._oplog = self._conn.local.oplog.rs
        self._oplog.ensure_index('ts')
        self._callbacks = defaultdict(list)
        self.checkpoint = checkpoint

    def run(self):
        while True:
            yield self.checkpoint
            spec = dict(ts={'$gt': self.checkpoint})
            q = self._oplog.find(
                spec, tailable=True, await_data=True)
            found=False
            # log.debug('Query on %s', self._oplog)
            for op in q.sort('$natural'):
                found = True
                self.checkpoint = op['ts']
                for callback in self._callbacks.get(
                    (op['ns'], op['op']), []):
                    callback(**op)
            if found:
                gevent.sleep(0)
            else:
                gevent.sleep(1)

    def register(self, namespace, operations, func=None):
        def wrapper(func):
            for op in operations:
                self._callbacks[namespace, op].append(func)
            return func
        if func:
            return wrapper(func)
        else:
            return wrapper


import os
import json
from functools import reduce

class Localization:
    def __init__(self, path):
        self.locale_path = "./locales"
        self.locales = {}
        self.current_locale = ""

    def set_locales_directory(self, path):
        self.locale_path = "./{}".format(path)
        self.locales = {}
        self.current_locale = "en"

        for filename in os.listdir(self.locale_path):
            with open(os.path.join(self.locale_path, filename), "r") as jsonFile:
                locale_name = filename.replace(".json", "")
                self.locales[locale_name] = json.load(jsonFile)
                jsonFile.close()

    def set_locale(self, locale_name):
        assert locale_name in self.locales.keys(), "There is no locale [{}] in folder [{}]".format(locale_name, self.locale_path)
        self.current_locale = locale_name

    def get(self, key, locale):
        assert locale != "", "There is no set locale"
        assert (type(locale) == str), "You passed locale in wrong type: {}. It should be str".format(locale)
        assert locale in self.locales.keys(), "There is no locale [{}] in folder [{}]".format(locale, self.locale_path)

        path = key.split(".")
        result = reduce(lambda obj, obj_key: obj.get(obj_key), path, self.locales[locale])

        assert type(result) is str, "The key should lead to the final translation, not to the group"
        return result

localization = Localization("locales")

def _(text, custom_locale=None):
    locale = custom_locale if custom_locale else localization.current_locale
    return localization.get(text, locale)


###  out_im: 4_dim trnsor   ####   cacu_loss
###  raw_im: 4_dim trnsor
###  bin_mask: 2_dim array
import tensorflow as tf
import functools

def get_gram(x):
    ba,hi,wi,ch = [i.value for i in x.get_shape()]
    feature = tf.reshape(x,[ba,int(hi*wi),ch])
    feature_T = tf.transpose(feature,[0,2,1])
    gram = tf.matmul(feature_T,feature)
    size = 1/(hi*wi*ch)
    return gram*size

def hole_loss(I_out,I_gt,bin_mask):
    L_hole = tf.reduce_mean(tf.abs((1-bin_mask)*(I_out-I_gt)))
    return L_hole

def valid_loss(I_out,I_gt,bin_mask):
    L_valid = tf.reduce_mean(tf.abs(bin_mask*(I_out-I_gt)))
    return L_valid

def Percept_loss(fai_out,fai_gt,fai_comp,layers):
    out_gt = []
    compt_gt = []
    for layer in layers:
        out_gt.append(tf.reduce_mean(tf.abs(fai_out[layer]-fai_gt[layer])))
        compt_gt.append(tf.reduce_mean(tf.abs(fai_comp[layer]-fai_gt[layer])))
    out_gt_loss = functools.reduce(tf.add,out_gt)
    compt_gt_loss = functools.reduce(tf.add,compt_gt)
    return out_gt_loss+compt_gt_loss

def Style_loss_out(fai_out,fai_gt,layers):
    styleloss = []
    for layer in layers:
        gram_out = get_gram(fai_out[layer])
        gram_gt = get_gram(fai_gt[layer])
        styleloss.append(tf.reduce_mean(tf.abs(gram_out-gram_gt)))
    style_out_loss = functools.reduce(tf.add,styleloss)
    return style_out_loss

def Style_loss_comp(fai_comp,fai_gt,layers):
    styleloss = []
    for layer in layers:
        gram_comp = get_gram(fai_comp[layer])
        gram_gt = get_gram(fai_gt[layer])
        styleloss.append(tf.reduce_mean(tf.abs(tf.subtract(gram_comp,gram_gt))))
    style_comp_loss = functools.reduce(tf.add,styleloss)
    return style_comp_loss

def Tv_loss(I_comp):
    I_comp_size = [i.value for i in I_comp.get_shape()]
    x_size = int(I_comp_size[2]*(I_comp_size[1]-1)*I_comp_size[3])
    y_size = int(I_comp_size[1]*(I_comp_size[2]-1)*I_comp_size[3])
    I_comp_x1 = I_comp[:,0:(I_comp_size[1]-1),:,:]
    I_comp_x2 = I_comp[:,1:I_comp_size[1],:,:]
    I_comp_y1 = I_comp[:,:,0:(I_comp_size[2]-1),:]
    I_comp_y2 = I_comp[:,:,1:I_comp_size[2],:]
    tv_x = tf.reduce_sum(tf.squared_difference(I_comp_x1,I_comp_x2))/x_size
    tv_y = tf.reduce_sum(tf.squared_difference(I_comp_y1,I_comp_y2))/y_size
    tv_loss = tv_y+tv_x
    return tv_loss

def get_total_loss(I_out,I_gt,bin_mask,fai_out,fai_gt,fai_comp,layers,I_comp):
    l_hole = hole_loss(I_out,I_gt,bin_mask)
    l_valid = valid_loss(I_out,I_gt,bin_mask)
    percept_loss = Percept_loss(fai_out,fai_gt,fai_comp,layers)
    style_loss_out = Style_loss_out(fai_out,fai_gt,layers)
    style_loss_comp = Style_loss_comp(fai_comp,fai_gt,layers)
    tv_loss = Tv_loss(I_comp)
    all_loss = 6*l_hole + l_valid + 0.05*percept_loss + 120*(style_loss_out + style_loss_comp) + 0.1*tv_loss
    return all_loss,l_hole,l_valid,percept_loss,style_loss_out,style_loss_comp,tv_loss


import os
import json
from io import StringIO
from pylint.lint import Run
from pylint.reporters.text import TextReporter

from service import settings

def pylint_code(file):
    output = StringIO()
    reporter = TextReporter(output)
    Run([file], reporter=reporter, do_exit=False)

    logs = output.getvalue()
    lines = logs.splitlines()
    return lines

def has_data_json(data):
    with open("logs.json", encoding='utf8') as f:
        if not os.path.getsize("logs.json") > 0:
            save_data_json([data])

        else:
            datas = json.load(f)
            datas.append(data)
            save_data_json(datas)

def save_data_json(data):
    with open("logs.json", "w", encoding='utf8') as json_file:
        json.dump(data, json_file, indent=4, ensure_ascii=False)


# -*- coding: utf-8 -*-

import os
import json

DIR = "resources"
PATH = os.path.join(os.path.dirname(__file__), DIR)

class Colors(dict):
    """docstring for Colors."""
    def __init__(self):
        super(Colors, self).__init__()
        for filename in os.listdir(PATH):
            if filename.endswith(".json"):
                fname, ext = os.path.splitext(filename)
                with open(os.fsencode(os.path.join(PATH, filename))) as source:
                    self[fname] = json.load(source)

colors = Colors()


from scipy import io as sio
import numpy as np

def load_data():
    mat = sio.loadmat(r'C:\Users\Luke Hollingsworth\Documents\Other\Python Scripts\SamAssisstant\data\matlab\emnist-letters.mat')
    data = mat['dataset']

    training_images = data['train'][0,0]['images'][0,0]
    training_labels = data['train'][0,0]['labels'][0,0]
    test_images = data['test'][0,0]['images'][0,0]
    test_labels = data['test'][0,0]['labels'][0,0]

    validation_start = training_images.shape[0] - test_images.shape[0]
    validation_images = training_images[validation_start:training_images.shape[0],:]
    validation_labels = training_labels[validation_start:training_images.shape[0]]

    training_images = training_images[0:validation_start, :]
    training_labels = training_labels[0:validation_start]

    return (training_images, training_labels, validation_images, validation_labels, test_images, test_labels)

def load_data_wrapper():
    tr_i, tr_l, va_i, va_l, te_i, te_l = load_data()
    training_inputs = [np.reshape(np.array(tr_i), (784,1))]
    training_results = [vectorized_result(y) for y in tr_l[1]]
    training_data = zip(training_inputs, training_results)
    validation_inputs = [np.reshape(np.array(va_i), (784,1))]
    validation_data = zip(validation_inputs, va_l[1])
    test_inputs = [np.reshape(np.array(te_i), (784,1))]
    test_data = zip(test_inputs, te_l[1])
    return (training_data, validation_data, test_data)

def vectorized_result(j):
    e = np.zeros((26, 1))
    e[j] = 1.0
    return e


import torch
from deepsnap.graph import Graph
from deepsnap.hetero_graph import HeteroGraph
from typing import (
    Callable,
    Dict,
    List
)

class Batch(Graph):
    r"""
    A plain old python object modeling a batch of
    :class:`deepsnap.graph.Graph` objects as one big (disconnected) graph,
    with :class:`torch_geometric.data.Data` being the
    base class that all its methods can also be used here.
    In addition, graphs can be reconstructed via the assignment vector
    :obj:`batch`, which maps each node to its respective graph identifier.

    .. note::

        For more detailed use of :class:`deepsnap.batch.Batch`, see the `examples
        <https://github.com/snap-stanford/deepsnap/tree/master/examples>`_ folder.

    """
    def __init__(self, batch=None, **kwargs):
        super(Batch, self).__init__(**kwargs)

        self.batch = batch
        self.__data_class__ = Graph
        self.__slices__ = None

    @staticmethod
    def collate(follow_batch=[], transform=None, **kwargs):
        return lambda batch: Batch.from_data_list(
            batch, follow_batch, transform, **kwargs
        )

    @staticmethod
    def from_data_list(
        data_list: List[Graph],
        follow_batch: List = None,
        transform: Callable = None,
        **kwargs
    ):
        r"""
        Constructs A :class:`deepsnap.batch.Batch` object from a python list
        holding :class:`torch_geometric.data.Data` objects.
        The assignment vector :obj:`batch` is created on the fly.
        Additionally, creates assignment batch vectors for each key in
        :obj:`follow_batch`.

        Args:
            data_list (list): A list of :class:`deepsnap.graph.Graph` objects.
            follow_batch (list): Creates assignment batch vectors
                for each key.
            transform (callable): If it is not `None`, apply transform
                when batching.
            **kwargs: Other parameters.
        """
        if follow_batch is None:
            follow_batch = []
        if transform is not None:
            data_list = [
                data.apply_transform(
                    transform,
                    deep_copy=True,
                    **kwargs,
                )
                for data in data_list
            ]
        keys = [set(data.keys) for data in data_list]
        keys = list(set.union(*keys))
        assert "batch" not in keys

        batch, cumsum = Batch._init_batch_fields(keys, follow_batch)
        batch.__data_class__ = data_list[0].__class__
        batch.batch = []
        for i, data in enumerate(data_list):
            # Note: in heterogeneous graph, __inc__ logic is different
            Batch._collate_dict(
                data, cumsum,
                batch.__slices__, batch,
                data, follow_batch, i=i
            )
            if isinstance(data, Graph):
                if isinstance(data, HeteroGraph):
                    num_nodes = sum(data.num_nodes().values())
                else:
                    num_nodes = data.num_nodes
            else:
                raise TypeError(
                    "element in self.graphs of unexpected type"
                )
            if num_nodes is not None:
                item = torch.full((num_nodes, ), i, dtype=torch.long)
                batch.batch.append(item)

        if num_nodes is None:
            batch.batch = None

        Batch._dict_list_to_tensor(batch, data_list[0])

        return batch.contiguous()

    @staticmethod
    def _init_batch_fields(keys, follow_batch):
        batch = Batch()
        batch.__slices__ = {key: [0] for key in keys}

        for key in keys:
            batch[key] = []

        for key in follow_batch:
            batch[f"{key}_batch"] = []

        cumsum = {key: 0 for key in keys}
        return batch, cumsum

    @staticmethod
    def _collate_dict(
        curr_dict,
        cumsum: Dict[str, int],
        slices,
        batched_dict,
        graph,
        follow_batch,
        i=None
    ):
        r""" Called in from_data_list to collate a dictionary.
        This can also be applied to Graph object, since it has support for
        keys and __getitem__().

        Args:
            curr_dict: current dictionary to be added to the
                collated dictionary.
            cumsum: cumulative sum to be used for indexing.
            slices: a dictionary of the same structure as batched_dict,
                slices[key] indicates the indices to slice batch[key] into
                tensors for all graphs in the batch.
            batched_dict: the batched dictionary of the same structure
                as curr_dict. But all graph data are batched together.
        """
        if isinstance(curr_dict, dict):
            keys = curr_dict.keys()
        else:
            keys = curr_dict.keys
        for key in keys:
            item = curr_dict[key]
            if isinstance(item, dict):
                # recursively collate every key in the dictionary
                if isinstance(batched_dict[key], list):
                    # nested dictionary not initialized yet
                    assert len(batched_dict[key]) == 0
                    # initialize the nested dictionary for batch
                    cumsum[key] = {inner_key: 0 for inner_key in item.keys()}
                    slices[key] = {inner_key: [0] for inner_key in item.keys()}
                    batched_dict[key] = {}
                    for inner_key in item.keys():
                        batched_dict[key][inner_key] = []
                    for inner_key in follow_batch:
                        batched_dict[key][f"{key}_batch"] = []
                Batch._collate_dict(
                    item, cumsum[key],
                    slices[key], batched_dict[key],
                    graph, follow_batch, i=i
                )
                continue
            if torch.is_tensor(item) and item.dtype != torch.bool:
                item = item + cumsum[key]
            if torch.is_tensor(item):
                size = item.size(graph.__cat_dim__(key, curr_dict[key]))
            else:
                size = 1
            slices[key].append(size + slices[key][-1])
            cumsum[key] = cumsum[key] + graph.__inc__(key, item)
            batched_dict[key].append(item)

            if key in follow_batch:
                item = torch.full((size, ), i, dtype=torch.long)
                batched_dict[f"{key}_batch"].append(item)

    @staticmethod
    def _dict_list_to_tensor(dict_of_list, graph):
        r"""Convert a dict/Graph with list as values to a dict/Graph with
        concatenated/stacked tensor as values.
        """
        if isinstance(dict_of_list, dict):
            keys = dict_of_list.keys()
        else:
            keys = dict_of_list.keys
        for key in keys:
            if isinstance(dict_of_list[key], dict):
                # recursively convert the dictionary of list to dict of tensor
                Batch._dict_list_to_tensor(dict_of_list[key], graph)
                continue
            item = dict_of_list[key][0]
            if torch.is_tensor(item):
                if (
                    Graph._is_graph_attribute(key)
                    and item.ndim == 1
                    and (not item.dtype == torch.long)
                    and "feature" in key
                ):
                    # special consideration: 1D tensor for graph
                    # attribute (classification)
                    # named as: "graph_xx_feature"
                    # batch by stacking the first dim
                    dict_of_list[key] = torch.stack(
                        dict_of_list[key],
                        dim=0
                    )
                else:
                    # concat at the __cat_dim__
                    dict_of_list[key] = torch.cat(
                        dict_of_list[key],
                        dim=graph.__cat_dim__(key, item)
                    )
            elif isinstance(item, (float, int)):
                dict_of_list[key] = torch.tensor(dict_of_list[key])

    def to_data_list(self):
        r"""
        Reconstructs the list of :class:`torch_geometric.data.Data` objects
        from the batch object.
        The batch object must have been created via :meth:`from_data_list` in
        order to be able to reconstruct the initial objects.
        """
        if self.__slices__ is None:
            raise RuntimeError(
                "Cannot reconstruct data list from batch because the "
                "batch object was not created using Batch.from_data_list()"
            )

        keys = [key for key in self.keys if key[-5:] != "batch"]
        cumsum = {key: 0 for key in keys}
        data_list = []
        for i in range(len(self.__slices__[keys[0]]) - 1):
            # i: from 0 up to num graphs in the batch
            data = self.__data_class__()
            self._reconstruct_dict(
                i, keys, data, cumsum, self.__slices__, self, data
            )
            data_list.append(data)

        return data_list

    def _reconstruct_dict(
            self, graph_idx: int, keys, data_dict,
            cumsum: Dict[str, int], slices, batched_dict, graph):

        for key in keys:
            if isinstance(batched_dict[key], dict):
                # recursively unbatch the dict
                data_dict[key] = {}
                inner_keys = [
                    inner_key
                    for inner_key in batched_dict[key].keys()
                    if inner_key[-5:] != "batch"
                ]
                inner_cumsum = {inner_key: 0 for inner_key in inner_keys}
                inner_slices = slices[key]
                self._reconstruct_dict(
                    graph_idx, inner_keys,
                    data_dict[key], inner_cumsum,
                    inner_slices, batched_dict[key], graph
                )
                continue

            if torch.is_tensor(batched_dict[key]):
                data_dict[key] = batched_dict[key].narrow(
                    graph.__cat_dim__(key, batched_dict[key]),
                    slices[key][graph_idx],
                    slices[key][graph_idx + 1] - slices[key][graph_idx]
                )
                if batched_dict[key].dtype != torch.bool:
                    data_dict[key] = data_dict[key] - cumsum[key]
            else:
                data_dict[key] = (
                    batched_dict[key][
                        slices[key][graph_idx]:slices[key][graph_idx + 1]
                    ]
                )
            cumsum[key] = cumsum[key] + graph.__inc__(key, data_dict[key])

    @property
    def num_graphs(self) -> int:
        r"""
        Returns the number of graphs in the batch.

        Returns:
            int: The number of graphs in the batch.
        """
        return self.batch[-1].item() + 1

    def apply_transform(
        self,
        transform,
        update_tensor: bool = True,
        update_graph: bool = False,
        deep_copy: bool = False,
        **kwargs
    ):
        r"""
        Applies a transformation to each graph object in parallel by first
        calling `to_data_list`, applying the transform, and then perform
        re-batching again to a `Batch`.
        A transform should edit the graph object,
        including changing the graph structure, or adding
        node / edge / graph level attributes.
        The rest are automatically handled by the
        :class:`deepsnap.graph.Graph` object, including everything
        ended with `index`.

        Args:
            transform (callable): Transformation function applied to each graph object.
            update_tensor (bool): Whether use nx graph to update tensor attributes.
            update_graph (bool): Whether use tensor attributes to update nx graphs.
            deep_copy (bool): :obj:`True` if a new deep copy of batch is returned.
                This option allows modifying the batch of graphs without
                changing the graphs in the original dataset.
            kwargs: Parameters used in the transform function for each
                :class:`deepsnap.graph.Graph`.

        Returns:
            A batch object containing all transformed graph objects.

        """
        # TODO: transductive setting, assert update_tensor == True
        return self.from_data_list(
            [
                Graph(graph).apply_transform(
                    transform, update_tensor, update_graph, deep_copy, **kwargs
                )
                for graph in self.G
            ]
        )

    def apply_transform_multi(
        self,
        transform,
        update_tensors: bool = True,
        update_graphs: bool = False,
        deep_copy: bool = False,
        **kwargs
    ):
        r"""
        Compared to :meth:`apply_transform`, this allows multiple graph objects
        to be returned by the given transform function.

        Args:
            transform (callable): (Multiple return value) tranformation function
                applied to each graph object. It needs to return a tuple of
                Graph objects.
            update_tensors (bool): Whether use nx graph to update tensor attributes.
            update_graphs (bool): Whether use tensor attributes to update nx graphs.
            deep_copy (bool): :obj:`True` if a new deep copy of batch is returned.
                This option allows modifying the batch of graphs without
                changing the graphs in the original dataset.
            kwargs: Parameters used in the transform function for each
                :class:`deepsnap.graph.Graph`.

        Returns:
            A tuple of batch objects. The i-th batch object contains the i-th
            return value of the transform function applied to all graphs
            in the batch.
        """
        g_lists = (
            zip(
                *[
                    Graph(graph).apply_transform_multi(
                        transform, update_tensors, update_graphs,
                        deep_copy, **kwargs,
                    )
                    for graph in self.G
                ]
            )
        )
        return (self.from_data_list(g_list) for g_list in g_lists)


# count é um iterador sem fim (itertools)
# (não fala quando acaba igual no range)

from itertools import count

c1 = count()
print(next(c1))
print(next(c1))

for i in c1:
    if i > 100:
        break
    print(i)


import pytest

from elyra.metadata.manager import MetadataManager
from elyra.metadata.schema import METADATA_TEST_SCHEMASPACE
from elyra.metadata.schema import SchemaManager
from elyra.tests.metadata.test_utils import another_metadata_json
from elyra.tests.metadata.test_utils import byo_metadata_json
from elyra.tests.metadata.test_utils import create_instance
from elyra.tests.metadata.test_utils import create_json_file
from elyra.tests.metadata.test_utils import invalid_json
from elyra.tests.metadata.test_utils import invalid_metadata_json
from elyra.tests.metadata.test_utils import invalid_schema_name_json
from elyra.tests.metadata.test_utils import valid_metadata_json

def mkdir(tmp_path, *parts):
    path = tmp_path.joinpath(*parts)
    if not path.exists():
        path.mkdir(parents=True)
    return path

# These location fixtures will need to be revisited once we support multiple metadata storage types.
schemaspace_location = pytest.fixture(lambda jp_data_dir: mkdir(jp_data_dir, "metadata", METADATA_TEST_SCHEMASPACE))
bogus_location = pytest.fixture(lambda jp_data_dir: mkdir(jp_data_dir, "metadata", "bogus"))
shared_location = pytest.fixture(
    lambda jp_system_jupyter_path: mkdir(jp_system_jupyter_path, "metadata", METADATA_TEST_SCHEMASPACE)
)
factory_location = pytest.fixture(
    lambda jp_env_jupyter_path: mkdir(jp_env_jupyter_path, "metadata", METADATA_TEST_SCHEMASPACE)
)

@pytest.fixture
def setup_data(schemaspace_location):
    create_json_file(schemaspace_location, "valid.json", valid_metadata_json)
    create_json_file(schemaspace_location, "another.json", another_metadata_json)
    create_json_file(schemaspace_location, "invalid.json", invalid_metadata_json)

@pytest.fixture
def setup_hierarchy(jp_environ, factory_location):
    # Only populate factory info
    byo_instance = byo_metadata_json
    byo_instance["display_name"] = "factory"
    create_json_file(factory_location, "byo_1.json", byo_instance)
    create_json_file(factory_location, "byo_2.json", byo_instance)
    create_json_file(factory_location, "byo_3.json", byo_instance)

@pytest.fixture
def store_manager(tests_manager):
    return tests_manager.metadata_store

@pytest.fixture(
    params=["elyra.metadata.storage.FileMetadataStore", "elyra.tests.metadata.test_utils.MockMetadataStore"]
)  # Add types as needed
def tests_manager(jp_environ, schemaspace_location, request):
    metadata_mgr = MetadataManager(schemaspace=METADATA_TEST_SCHEMASPACE, metadata_store_class=request.param)
    store_mgr = metadata_mgr.metadata_store
    create_instance(store_mgr, schemaspace_location, "valid", valid_metadata_json)
    create_instance(store_mgr, schemaspace_location, "another", another_metadata_json)
    create_instance(store_mgr, schemaspace_location, "invalid", invalid_metadata_json)
    create_instance(store_mgr, schemaspace_location, "bad", invalid_json)
    create_instance(store_mgr, schemaspace_location, "invalid_schema_name", invalid_schema_name_json)
    return metadata_mgr

@pytest.fixture
def tests_hierarchy_manager(setup_hierarchy):  # Only uses FileMetadataStore for storage right now.
    return MetadataManager(schemaspace=METADATA_TEST_SCHEMASPACE)

@pytest.fixture
def schema_manager():
    schema_manager = SchemaManager.instance()
    yield schema_manager
    SchemaManager.clear_instance()

# Set Elyra server extension as enabled (overriding server_config fixture from jupyter_server)
@pytest.fixture
def jp_server_config():
    return {"ServerApp": {"jpserver_extensions": {"elyra": True}}}


import math
import time

from joblib import Parallel, delayed
from psutil import cpu_count

RANGE = 14000

def main_01():
    res = [math.factorial(x) for x in range(RANGE)]
    return None

def main_02():
    res = Parallel(n_jobs=2)(delayed(math.factorial)(x) for x in range(RANGE))
    return None

def main_03():
    res = Parallel(n_jobs=4)(delayed(math.factorial)(x) for x in range(RANGE))
    return None

def main_04():
    res = Parallel(n_jobs=-1)(delayed(math.factorial)(x) for x in range(RANGE))
    return None

if __name__ == '__main__':

    for func, cores in {main_01: 1, main_02: 2, main_03: 4, main_04: cpu_count()}.items():
        t1 = time.time()
        func()
        main_04()
        t2 = time.time()
        print(f'Execusion time: {t2 - t1:.4f}s with usage of: {cores} core(s).')