Ejemplo n.º 1
0
 def nextSlide(self, event=None):
     c = self.c
     p = c.p
     if p == self.slide:
         p = self.slide.threadNext()
         oldSlide = self.slide
     else:
         oldSlide = None
     while p:
         h = p.h.strip()
         if self.ignored(p):
             p = p.threadNext()
         elif h.startswith('@slideshow'):
             self.select(p)
             return g.es('At %s of slide show' %
                         'end' if oldSlide else 'start')
         elif g.match_word(h, 0, '@ignore') or g.match_word(
                 h, 0, '@noslide'):
             p = p.nodeAfterTree()
         else:
             return self.select(p)
         # elif h.startswith('@slide'):
         # return self.select(p)
         # else: p = p.threadNext()
     return g.es('At end of slide show' if self.
                 slideShowRoot else 'Not in any slide show')
Ejemplo n.º 2
0
    def scanForOptionDocParts(self, p, s):
        '''Return a dictionary containing all options from @rst-options doc parts in p.
        Multiple @rst-options doc parts are allowed: this code aggregates all options.
        '''

        d = {}
        n = 0
        lines = g.splitLines(s)
        while n < len(lines):
            line = lines[n]
            n += 1
            if line.startswith('@'):
                i = g.skip_ws(line, 1)
                for kind in ('@rst-options', '@rst-option'):
                    if g.match_word(line, i, kind):
                        # Allow options on the same line.
                        line = line[i + len(kind):]
                        d.update(self.scanOption(p, line))
                        # Add options until the end of the doc part.
                        while n < len(lines):
                            line = lines[n]
                            n += 1
                            found = False
                            for stop in ('@c', '@code', '@'):
                                if g.match_word(line, 0, stop):
                                    found = True
                                    break
                            if found:
                                break
                            else:
                                d.update(self.scanOption(p, line))
                        break
        return d
Ejemplo n.º 3
0
 def write_root(self, root):
     '''Process all nodes in an @ad tree.'''
     fn =  self.ad_filename(root)
     if not fn:
         g.es_print('Can not happen: not a @ad node: %r' % root.h)
         return None
     path, self.output_file = self.open_file(fn)
     if not self.output_file:
         return None
     # Write only the body of the root.
     self.write_body(root)
     # Write all nodes of the tree, except ignored nodes.
     self.level_offset = self.compute_level_offset(root)
     self.root_level = root.level()
     p = root.threadNext() # Returns a copy.
     after = root.nodeAfterTree()
     while p and p != after:
         h = p.h.rstrip()
         if g.match_word(h, 0, '@ignore-tree'):
             p.moveToNodeAfterTree()
             continue
         if g.match_word(h, 0, '@ignore-node'):
             p.moveToThreadNext()
             continue
         if not g.match_word(h, 0, '@no-head'):
             self.write_headline(p)
         self.write_body(p)
         p.moveToThreadNext()
     self.output_file.close()
     return path
Ejemplo n.º 4
0
    def scanForOptionDocParts (self,p,s):

        '''Return a dictionary containing all options from @rst-options doc parts in p.
        Multiple @rst-options doc parts are allowed: this code aggregates all options.
        '''

        d = {} ; n = 0 ; lines = g.splitLines(s)
        while n < len(lines):
            line = lines[n] ; n += 1
            if line.startswith('@'):
                i = g.skip_ws(line,1)
                for kind in ('@rst-options','@rst-option'):
                    if g.match_word(line,i,kind):
                        # Allow options on the same line.
                        line = line[i+len(kind):]
                        d.update(self.scanOption(p,line))
                        # Add options until the end of the doc part.
                        while n < len(lines):
                            line = lines[n] ; n += 1 ; found = False
                            for stop in ('@c','@code', '@'):
                                if g.match_word(line,0,stop):
                                    found = True ; break
                            if found:
                                break
                            else:
                                d.update(self.scanOption(p,line))
                        break
        return d
Ejemplo n.º 5
0
    def ignored (self,p):

        for p2 in p.self_and_parents():
            if g.match_word(p2.h,0,'@ignore') or g.match_word(p2.h,0,'@noslide'):
                return True
        else:
            return False
Ejemplo n.º 6
0
    def nextSlide (self,event=None):

        c = self.c ; p = c.p
        if p == self.slide:
            p = self.slide.threadNext()
            oldSlide = self.slide
        else:
            oldSlide = None
        while p:
            h = p.h.strip()
            if self.ignored(p):
                p = p.threadNext()
            elif h.startswith('@slideshow'):
                self.select(p)
                return g.es('At %s of slide show' % g.choose(oldSlide,'end','start'))
            elif g.match_word(h,0,'@ignore') or g.match_word(h,0,'@noslide'):
                p = p.nodeAfterTree()
            else:
                return self.select(p)
            # elif h.startswith('@slide'):
                # return self.select(p)
            # else: p = p.threadNext()
        else:
            return g.es(g.choose(self.slideShowRoot,
                'At end of slide show',
                'Not in any slide show'))
Ejemplo n.º 7
0
def on_icondclick(tag, keywords):    
    c = keywords.get("c")
    p = keywords.get("p")
    h = p.h
    if g.match_word(h,0,"@expfolder"):
        if p.hasChildren():
            result = g.app.gui.runAskYesNoDialog(c, "Reread?", "Reread contents of folder "+h[11:]+"?")
            if result == "no":
                return
            kids = []
            for cp in p.subtree():
                if cp.isDirty() and g.match_word(cp.h, 0, "@text"):
                    kids.append(cp.copy())
            if kids != []:
                result = g.app.gui.runAskYesNoDialog(c, "Reread?", "Save changed @text nodes?")
                if result == "yes":
                    for kid in kids:
                        savetextnode(c, kid)

            # delete children
            while p.firstChild():
                p.firstChild().doDelete()

        #changed = c.isChanged()
        dir = h[11:]
        dirs = []
        files = []
        for file in os.listdir(dir):
            path = os.path.join(dir, file)
            if os.path.isdir(path):
                dirs.append(path)
            else:
                files.append(path)

        #g.es('dirs: '+str(dirs))
        #g.es('files: '+str(files))

        dirs.sort()
        files.sort()

        for f in files:
            pn = p.insertAsNthChild(0)
            if os.path.splitext(f)[1] in textexts:
                c.setHeadString(pn, "@text "+f)
                pn.clearDirty()
            else:
                c.setHeadString(pn, f)
            #pn.clearDirty()

        for d in dirs:
            pn = p.insertAsNthChild(0)
            c.setHeadString(pn, "@expfolder "+d)
            #pn.clearDirty()

        #p.clearDirty()

        #c.setChanged(changed)

        c.expandSubtree(p)
Ejemplo n.º 8
0
def on_icondclick(tag, keywords):
    c = keywords.get("c")
    p = keywords.get("p")
    h = p.h
    if g.match_word(h,0,"@expfolder"):
        if p.hasChildren():
            result = g.app.gui.runAskYesNoDialog(c, "Reread?", "Reread contents of folder "+h[11:]+"?")
            if result == "no":
                return
            kids = []
            for cp in p.subtree():
                if cp.isDirty() and g.match_word(cp.h, 0, "@text"):
                    kids.append(cp.copy())
            if kids != []:
                result = g.app.gui.runAskYesNoDialog(c, "Reread?", "Save changed @text nodes?")
                if result == "yes":
                    for kid in kids:
                        savetextnode(c, kid)

            # delete children
            while p.firstChild():
                p.firstChild().doDelete()

        #changed = c.isChanged()
        dir = h[11:]
        dirs = []
        files = []
        for file in os.listdir(dir):
            path = os.path.join(dir, file)
            if os.path.isdir(path):
                dirs.append(path)
            else:
                files.append(path)

        #g.es('dirs: '+str(dirs))
        #g.es('files: '+str(files))

        dirs.sort()
        files.sort()

        for f in files:
            pn = p.insertAsNthChild(0)
            if os.path.splitext(f)[1] in textexts:
                c.setHeadString(pn, "@text "+f)
                pn.clearDirty()
            else:
                c.setHeadString(pn, f)
            #pn.clearDirty()

        for d in dirs:
            pn = p.insertAsNthChild(0)
            c.setHeadString(pn, "@expfolder "+d)
            #pn.clearDirty()

        #p.clearDirty()

        #c.setChanged(changed)

        c.expandSubtree(p)
Ejemplo n.º 9
0
    def ignored(self, p):

        for p2 in p.self_and_parents():
            if g.match_word(p2.h, 0, '@ignore') or g.match_word(
                    p2.h, 0, '@noslide'):
                return True
        else:
            return False
Ejemplo n.º 10
0
 def match(self, fn, i, m, s):
     '''Handle the next match.'''
     trace = False
     self.n_matches += 1
     indent = g.skip_ws(s, 0)
     # Update the context and enter data.
     if g.match_word(s, indent, 'def'):
         self.update_context(fn, indent, 'def', s)
         for i, name in enumerate(m.groups()):
             if name:
                 aList = self.defs_d.get(name, [])
                 def_tuple = self.context_stack[: -1], s
                 aList.append(def_tuple)
                 self.defs_d[name] = aList
                 break
     elif g.match_word(s, indent, 'class'):
         self.update_context(fn, indent, 'class', s)
         for i, name in enumerate(m.groups()):
             if name:
                 aList = self.classes_d.get(name, [])
                 class_tuple = self.context_stack[: -1], s
                 aList.append(class_tuple)
                 self.classes_d[name] = aList
     elif s.find('return') > -1:
         context, name = self.context_names()
         j = s.find('#')
         if j > -1: s = s[: j]
         s = s.strip()
         if s:
             aList = self.returns_d.get(name, [])
             return_tuple = context, s
             aList.append(return_tuple)
             self.returns_d[name] = aList
     else:
         # A call.
         for i, name in enumerate(m.groups()):
             if name:
                 context2, context1 = self.context_names()
                 j = s.find('#')
                 if j > -1:
                     s = s[: j]
                 s = s.strip().strip(',').strip()
                 if s:
                     aList = self.calls_d.get(name, [])
                     call_tuple = context2, context1, s
                     aList.append(call_tuple)
                     self.calls_d[name] = aList
                 break
     if trace:
         print('%4s %4s %3s %3s %s' % (
             self.n_matches, i, len(self.context_stack), indent, s.rstrip()))
Ejemplo n.º 11
0
 def next(self):
     '''Find the next screencast node and execute its script.
     Call m.quit if no more nodes remain.'''
     trace = False and not g.unitTesting
     m = self
     c = m.c
     k = c.k
     m.delete_widgets()
     # Restore k.state from m.k_state.
     if m.k_state.kind and m.k_state.kind != m.state_name:
         k.setState(kind=m.k_state.kind,
                    n=m.k_state.n,
                    handler=m.k_state.handler)
     while m.p:
         if trace: g.trace(m.p.h)
         h = m.p.h.replace('_', '').replace('-', '')
         if g.match_word(h, 0, '@ignorenode'):
             m.p.moveToThreadNext()
         elif g.match_word(h, 0, '@ignoretree') or g.match_word(
                 h, 0, '@button'):
             m.p.moveToNodeAfterTree()
         elif m.p.b.strip():
             p_next = m.p.threadNext()
             p_old = m.p.copy()
             if g.match_word(m.p.h, 0, '@text'):
                 c.redraw(
                     m.p
                 )  # Selects the node, thereby showing the body text.
             else:
                 m.exec_node(m.p)
             # Save k.state in m.k_state.
             if k.state:
                 if k.state.kind == m.state_name:
                     m.clear_state()
                 else:
                     m.set_state(k.state)
             # Re-enable m.state_handler.
             if not m.quit_flag:
                 k.setState(m.state_name, 1, m.state_handler)
             # Change m.p only if the script has not already changed it.
             if not m.p or m.p == p_old:
                 m.p = p_next
             break
         else:
             m.p.moveToThreadNext()
     else:
         # No non-empty node found.
         m.quit()
Ejemplo n.º 12
0
def onIconDoubleClick(tag, keywords):
    """
    Read or write a bibtex file when the node is double-clicked.

    Write the @bibtex tree as bibtex file when the root node is double-clicked. 
    If it has no child nodes, read bibtex file.
    """
    p = keywords.get("p") or keywords.get("v")
    c = keywords.get("c")
    if not c or not p:
        return
    h = p.h.strip()
    if g.match_word(h, 0, "@bibtex"):
        fn = g.os_path_finalize_join(g.os_path_dirname(c.fileName() or ""), h[8:])
        if p.hasChildren():
            bibFile = open(fn, "w")
            writeTreeAsBibTex(bibFile, p, c)
            bibFile.close()
            g.es("wrote: %s" % fn)
        else:
            try:
                bibFile = open(fn, "r")
            except IOError:
                g.es("not found: %s" % fn, color="red")
                return
            g.es("reading: " + fn)
            readBibTexFileIntoTree(bibFile, c)
            bibFile.close()
Ejemplo n.º 13
0
def cloneToAtSpot(self, event=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 = self; u = c.undoer; p = c.p
    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.validateOutline():
        u.afterCloneNode(clone, 'Clone Node', undoData)
        c.contractAllHeadlines()
        c.redraw()
        c.selectPosition(clone)
    else:
        clone.doDelete()
        c.setCurrentPosition(p)
Ejemplo n.º 14
0
    def starts_block(self, i, lines, new_state, prev_state):
        '''True if the new state starts a block.'''

        def end(line):
            # Buggy: 'end' could appear in a string or comment.
            # However, this code is much better than before.
            i = line.find('end')
            return i if i > -1 and g.match_word(line, i, 'end') else -1

        if prev_state.context:
            return False
        line = lines[i]
        m = self.function_pattern.match(line)
        if m and end(line) < m.start():
            self.start_stack.append('function')
            return True
        # Don't create separate nodes for assigned functions,
        # but *do* push 'function2' on the start_stack for the later 'end' statement.
        m = self.function_pattern2.search(line)
        if m and end(line) < m.start():
            self.start_stack.append('function2')
            return False
        # Not a function. Handle constructs ending with 'end'.
        line = line.strip()
        if end(line) == -1:
            for z in ('do', 'for', 'if', 'while',):
                if g.match_word(line, 0, z):
                    self.start_stack.append(z)
                    break
        return False
Ejemplo n.º 15
0
 def ends_block(self, line, new_state, prev_state, stack):
     '''True if line ends a function or procedure.'''
     if prev_state.context:
         return False
     ls = line.lstrip()
     val = g.match_word(ls, 0, 'end')
     return val
Ejemplo n.º 16
0
def onSelect (tag,keywords):
    c = keywords.get('c') or keywords.get('new_c')
    if not c: return
    v = keywords.get("new_v")
    h = v.h
    if g.match_word(h,0,"@folder"):
        sync_node_to_folder(c,v,h[8:])
Ejemplo n.º 17
0
def onIconDoubleClick(tag,keywords):
    """Read or write a bibtex file when the node is double-clicked.

    Write the @bibtex tree as bibtex file when the root node is double-clicked. 
    If it has no child nodes, read bibtex file."""

    v = keywords.get("p") or keywords.get("v")
    c = keywords.get("c")
    h = v.h.strip()
    if g.match_word(h,0,"@bibtex"):
        fname = h[8:]
        if v.hasChildren():
            #@+<< write bibtex file >>
            #@+node:timo.20050213160555.6: *3* << write bibtex file >>
            bibFile = file(fname,'w')
            writeTreeAsBibTex(bibFile, v, c)

            bibFile.close()
            g.es('written: '+str(fname))
            #@-<< write bibtex file >>
        else:
            #@+<< read bibtex file >>
            #@+node:timo.20050214174623: *3* << read bibtex file >>
            g.es('reading: ' + str(fname))
            try: 
                bibFile = file(fname,'r')
            except IOError:
                g.es('IOError: file not found')
                return    
            readBibTexFileIntoTree(bibFile, c)

            bibFile.close()
Ejemplo n.º 18
0
def onIconDoubleClick(tag,keywords):
    """
    Read or write a bibtex file when the node is double-clicked.

    Write the @bibtex tree as bibtex file when the root node is double-clicked.
    If it has no child nodes, read bibtex file.
    """
    p = keywords.get("p") or keywords.get("v")
    c = keywords.get("c")
    if not c or not p:
        return
    h = p.h.strip()
    if g.match_word(h,0,"@bibtex"):
        fn = g.os_path_finalize_join(g.os_path_dirname(c.fileName() or ''),h[8:])
        if p.hasChildren():
            bibFile = open(fn,'w')
            writeTreeAsBibTex(bibFile,p,c)
            bibFile.close()
            g.es('wrote: %s' % fn)
        else:
            try:
                bibFile = open(fn,'r')
            except IOError:
                g.es('not found: %s' % fn,color='red')
                return
            g.es('reading: ' + fn)
            readBibTexFileIntoTree(bibFile,c)
            bibFile.close()
Ejemplo n.º 19
0
 def nextSlideShow(self, event=None):
     c = self.c
     self.findFirstSlideShow()
     if not self.firstSlideShow:
         g.es('No slide show found')
         return
     if not self.slideShowRoot:
         self.select(self.firstSlideShow)
         return
     p = c.p
     h = p.h.strip()
     if h.startswith('@slideshow'):
         p = p.threadNext()
     while p:
         h = p.h.strip()
         if self.ignored(p):
             p = p.threadNext()
         elif h.startswith('@slideshow'):
             self.select(p)
             return
         elif g.match_word(h, 0, '@ignore'):
             p = p.nodeAfterTree()
         else:
             p = p.threadNext()
     self.select(self.slideShowRoot)
     g.es('At start of last slide show')
Ejemplo n.º 20
0
    def getButtonText(self,h):

        '''Returns the button text found in the given headline string'''

        tag = "@button"
        if g.match_word(h,0,tag):
            h = h[len(tag):].strip()

        for tag in ('@key','@args','@color',):
            i = h.find(tag)
            if i > -1:
                j = h.find('@',i+1)
                if i < j:
                    h = h[:i] + h[j+1:]
                else:
                    h = h[:i]
                h = h.strip()
        #i = h.find('@key')
        #if i > -1:
        #    buttonText = h[:i].strip()

        else:
            buttonText = h

        fullButtonText = buttonText
        return buttonText
Ejemplo n.º 21
0
    def scanHeadlineForOptions(self, p):
        '''Return a dictionary containing the options implied by p's headline.'''

        h = p.h.strip()

        if p == self.topNode:
            return {}  # Don't mess with the root node.

        if g.match_word(h, 0, self.getOption('@rst-options')):
            return self.scanOptions(p, p.b)
        else:
            # Careful: can't use g.match_word because options may have '-' chars.
            i = g.skip_id(h, 0, chars='@-')
            word = h[0:i]

            for option, ivar, val in (
                ('@rst-no-head', 'ignore_this_headline', True),
                ('@rst-head', 'show_this_headline', True),
                ('@rst-no-headlines', 'show_headlines', False),
                ('@rst-ignore', 'ignore_this_tree', True),
                ('@rst-ignore-node', 'ignore_this_node', True),
                ('@rst-ignore-tree', 'ignore_this_tree', True),
                    # ('@rst-preformat','preformat_this_node',True),
            ):
                name = self.getOption(option)
                if name:
                    d = {name: val}
                    return d

            return {}
Ejemplo n.º 22
0
 def create_uas(self, at_uas, root):
     """Recreate uA's from the @ua nodes in the @uas tree."""
     # Create an *inner* gnx dict.
     # Keys are gnx's, values are positions *within* root's tree.
     d = {}
     for p in root.self_and_subtree(copy=False):
         d[p.v.gnx] = p.copy()
     # Recreate the uA's for the gnx's given by each @ua node.
     for at_ua in at_uas.children():
         h, b = at_ua.h, at_ua.b
         gnx = h[4:].strip()
         if b and gnx and g.match_word(h, 0, '@ua'):
             p = d.get(gnx)
             if p:
                 # Handle all recent variants of the node.
                 lines = g.splitLines(b)
                 if b.startswith('unl:') and len(lines) == 2:
                     # pylint: disable=unbalanced-tuple-unpacking
                     unl, ua = lines
                 else:
                     unl, ua = None, b
                 if ua.startswith('ua:'):
                     ua = ua[3:]
                 if ua:
                     ua = self.unpickle(ua)
                     p.v.u = ua
                 else:
                     g.trace('Can not unpickle uA in', p.h, repr(unl),
                             type(ua), ua[:40])
Ejemplo n.º 23
0
def onIconDoubleClick(tag,keywords):
    """Read or write a bibtex file when the node is double-clicked.

    Write the @bibtex tree as bibtex file when the root node is double-clicked. 
    If it has no child nodes, read bibtex file."""

    v = keywords.get("p") or keywords.get("v")
    c = keywords.get("c")
    h = v.h.strip()
    if g.match_word(h,0,"@bibtex"):
        fname = h[8:]
        if v.hasChildren():
            #@+<< write bibtex file >>
            #@+node:timo.20050213160555.6: *3* << write bibtex file >>
            bibFile = file(fname,'w')
            writeTreeAsBibTex(bibFile, v, c)

            bibFile.close()
            g.es('written: '+str(fname))
            #@-<< write bibtex file >>
        else:
            #@+<< read bibtex file >>
            #@+node:timo.20050214174623: *3* << read bibtex file >>
            g.es('reading: ' + str(fname))
            try: 
                bibFile = file(fname,'r')
            except IOError:
                g.es('IOError: file not found')
                return    
            readBibTexFileIntoTree(bibFile, c)

            bibFile.close()
Ejemplo n.º 24
0
    def finishCreate(self):
        '''Find or make the @chapters and @chapter trash nodes.'''

        trace = (False or g.trace_startup) and not g.unitTesting
        if trace: print('cc.finishCreate', g.callers())

        cc = self
        c = cc.c

        if cc.findChaptersNode():
            if hasattr(c.frame.iconBar, 'createChaptersIcon'):
                if not cc.tt:
                    cc.tt = c.frame.iconBar.createChaptersIcon()

        # Create the main chapter
        cc.chaptersDict['main'] = chapter(c, cc, 'main')

        tag = '@chapter'
        for p in c.all_unique_positions():
            h = p.h
            # if h.startswith(tag) and not h.startswith('@chapters'):
            if g.match_word(h, 0, tag):
                tabName = h[len(tag):].strip()
                if tabName and tabName not in ('main', ):
                    if cc.chaptersDict.get(tabName):
                        self.error('duplicate chapter name: %s' % tabName)
                    else:
                        cc.chaptersDict[tabName] = chapter(c, cc, tabName)

        # Always select the main chapter.
        # It can be alarming to open a small chapter in a large .leo file.
        cc.selectChapterByName('main', collapse=False)
Ejemplo n.º 25
0
 def cloneNodeToChapterHelper(self, toChapterName):
     cc, c, p, u, undoType = self, self.c, self.c.p, self.c.undoer, 'Clone Node To Chapter'
     # Find the @chapter nodes and related chapter objects.
     fromChapter = cc.getSelectedChapter()
     if not fromChapter:
         return cc.note('no @chapter %s' % (fromChapter and fromChapter.name))
     toChapter = cc.getChapter(toChapterName)
     if not toChapter:
         return cc.note('no @chapter %s' % (toChapter.name))
     # Find the root of each chapter.
     toRoot = toChapter.findRootNode()
     assert toRoot
     if g.match_word(p.h, 0, '@chapter'):
         return cc.note('can not clone @chapter node')
     # Open the group undo.
     c.undoer.beforeChangeGroup(toRoot, undoType)
     # Do the clone.  c.clone handles the inner undo.
     clone = c.clone()
     # Do the move.
     undoData2 = u.beforeMoveNode(clone)
     if toChapter.name == 'main':
         clone.moveAfter(toChapter.p)
     else:
         clone.moveToLastChildOf(toRoot)
     u.afterMoveNode(clone, 'Move Node', undoData2, dirtyVnodeList=[])
     c.redraw(clone)
     c.setChanged(True)
     # Close the group undo.
     # Only the ancestors of the moved node get set dirty.
     dirtyVnodeList = clone.setAllAncestorAtFileNodesDirty()
     c.undoer.afterChangeGroup(clone, undoType, reportFlag=False, dirtyVnodeList=dirtyVnodeList)
     toChapter.p = clone.copy()
     toChapter.select()
     fromChapter.p = p.copy()
Ejemplo n.º 26
0
 def cleanAtCleanTree(self, event):
     '''
     Adjust whitespace in the nearest @clean tree,
     searching c.p and its ancestors.
     '''
     c = self.c
     # Look for an @clean node.
     for p in c.p.self_and_parents():
         if g.match_word(p.h, 0, '@clean') and p.h.rstrip().endswith('.py'):
             break
     else:
         g.es_print('no an @clean node found', p.h, color='blue')
         return
     # pylint: disable=undefined-loop-variable
     # p is certainly defined here.
     bunch = c.undoer.beforeChangeTree(p)
     n = 0
     undoType = 'clean-@clean-tree'
     for p2 in p.subtree():
         if self.cleanAtCleanNode(p2, undoType):
             n += 1
     if n > 0:
         c.setChanged(True)
         c.undoer.afterChangeTree(p, undoType, bunch)
     g.es_print('%s node%s cleaned' % (n, g.plural(n)))
Ejemplo n.º 27
0
 def create_uas(self,at_uas,root):
     '''Recreate uA's from the @ua nodes in the @uas tree.'''
     trace = False and not g.unitTesting
     # Create an *inner* gnx dict.
     # Keys are gnx's, values are positions *within* root's tree.
     d = {}
     for p in root.self_and_subtree():
         d[p.v.gnx] = p.copy()
     # Recreate the uA's for the gnx's given by each @ua node.
     for at_ua in at_uas.children():
         h,b = at_ua.h,at_ua.b
         gnx = h[4:].strip()
         if b and gnx and g.match_word(h,0,'@ua'):
             p = d.get(gnx)
             if p:
                 # Handle all recent variants of the node.
                 lines = g.splitLines(b)
                 if b.startswith('unl:') and len(lines) == 2:
                     # pylint: disable=unbalanced-tuple-unpacking
                     unl,ua = lines
                 else:
                     unl,ua = None,b
                 if ua.startswith('ua:'):
                     ua = ua[3:]
                 if ua:
                     ua = self.unpickle(ua)
                     if trace: g.trace('set',p.h,ua)
                     p.v.u = ua
                 else:
                     g.trace('Can not unpickle uA in',p.h,type(ua),ua[:40])
             elif trace:
                 g.trace('no match for gnx:',repr(gnx),'unl:',unl)
         elif trace:
             g.trace('unexpected child of @uas node',at_ua)
Ejemplo n.º 28
0
    def copyNodeToChapterHelper (self,toChapterName):

        cc,c,p,u,undoType = self,self.c,self.c.p,self.c.undoer,'Copy Node To Chapter'
        if g.match_word(p.h,0,'@chapter'):
            return cc.note('can not copy @chapter node')
        # Get the chapter objects.
        fromChapter = cc.getSelectedChapter()
        if not fromChapter:
            return cc.note('no @chapter %s' % (toChapterName))
        toChapter = cc.getChapter(toChapterName)
        if not toChapter:
            return cc.note('no such chapter: %s' % toChapterName)
        toRoot = toChapter.findRootNode()
        assert toRoot
        # For undo, we treat the copy like a pasted (inserted) node.
        undoData = u.beforeInsertNode(toRoot,pasteAsClone=False,copiedBunchList=[])
        s = c.fileCommands.putLeoOutline()
        p2 = c.fileCommands.getLeoOutline(s)
        p2.moveToLastChildOf(toRoot)
        c.redraw(p2)
        u.afterInsertNode(p2,undoType,undoData)
        c.setChanged(True)
        toChapter.p = p2.copy()
        toChapter.select()
        fromChapter.p = p.copy()
Ejemplo n.º 29
0
def getPathOld(p):
    # NOT USED, my version which does its own @path scanning
    p = p.copy()

    path = []

    while p:
        h = p.h

        if g.match_word(h, 0, "@path"):  # top of the tree
            path.insert(0, os.path.expanduser(h[6:].strip()))
            d = os.path.join(*path)
            return d

        elif h.startswith('@'):  # some other directive, run away
            break

        elif isDirNode(p):  # a directory
            path.insert(0, h.strip('/*'))

        elif not p.hasChildren():  # a leaf node, assume a file
            path.insert(0, h.strip('*'))

        p = p.parent()

    return None
Ejemplo n.º 30
0
    def scanHeadlineForOptions (self,p):

        '''Return a dictionary containing the options implied by p's headline.'''

        h = p.h.strip()

        if p == self.topNode:
            return {} # Don't mess with the root node.

        if g.match_word(h,0,self.getOption('@rst-options')): 
            return self.scanOptions(p,p.b)
        else:
            # Careful: can't use g.match_word because options may have '-' chars.
            # i = g.skip_id(h,0,chars='@-')
            # word = h[0:i]
            for option,ivar,val in (
                ('@rst-no-head','ignore_this_headline',True),
                ('@rst-head'  ,'show_this_headline',True),
                ('@rst-no-headlines','show_headlines',False),
                ('@rst-ignore','ignore_this_tree',True),
                ('@rst-ignore-node','ignore_this_node',True),
                ('@rst-ignore-tree','ignore_this_tree',True),
                # ('@rst-preformat','preformat_this_node',True),
            ):
                name = self.getOption(option)
                if name:
                    d = { name: val }
                    return d

            return {}
Ejemplo n.º 31
0
    def getButtonText(self,h):

        '''Returns the button text found in the given headline string'''

        tag = "@button"
        if g.match_word(h,0,tag):
            h = h[len(tag):].strip()

        for tag in ('@key','@args','@color',):
            i = h.find(tag)
            if i > -1:
                j = h.find('@',i+1)
                if i < j:
                    h = h[:i] + h[j+1:]
                else:
                    h = h[:i]
                h = h.strip()
        #i = h.find('@key')
        #if i > -1:
        #    buttonText = h[:i].strip()

        else:
            buttonText = h

        fullButtonText = buttonText
        return buttonText
Ejemplo n.º 32
0
    def starts_block(self, i, lines, new_state, prev_state):
        '''True if the new state starts a block.'''
        def end(line):
            # Buggy: 'end' could appear in a string or comment.
            # However, this code is much better than before.
            i = line.find('end')
            return i if i > -1 and g.match_word(line, i, 'end') else -1

        if prev_state.context:
            return False
        line = lines[i]
        m = self.function_pattern.match(line)
        if m and end(line) < m.start():
            self.start_stack.append('function')
            return True
        # Don't create separate nodes for assigned functions,
        # but *do* push 'function2' on the start_stack for the later 'end' statement.
        m = self.function_pattern2.search(line)
        if m and end(line) < m.start():
            self.start_stack.append('function2')
            return False
        # Not a function. Handle constructs ending with 'end'.
        line = line.strip()
        if end(line) == -1:
            for z in (
                    'do',
                    'for',
                    'if',
                    'while',
            ):
                if g.match_word(line, 0, z):
                    self.start_stack.append(z)
                    break
        return False
Ejemplo n.º 33
0
def onSelect (tag,keywords):
    c = keywords.get('c') or keywords.get('new_c')
    if not c: return
    v = keywords.get("new_v")
    h = v.h
    if g.match_word(h,0,"@folder"):
        sync_node_to_folder(c,v,h[8:])
Ejemplo n.º 34
0
    def copyNodeToChapterHelper (self,toChapterName):

        cc,c,p,u,undoType = self,self.c,self.c.p,self.c.undoer,'Copy Node To Chapter'
        if g.match_word(p.h,0,'@chapter'):
            return cc.note('can not copy @chapter node')
        # Get the chapter objects.
        fromChapter = cc.getSelectedChapter()
        if not fromChapter:
            return cc.note('no @chapter %s' % (toChapterName))
        toChapter = cc.getChapter(toChapterName)
        if not toChapter:
            return cc.note('no such chapter: %s' % toChapterName)
        toRoot = toChapter.findRootNode()
        assert toRoot
        # For undo, we treat the copy like a pasted (inserted) node.
        undoData = u.beforeInsertNode(toRoot,pasteAsClone=False,copiedBunchList=[])
        s = c.fileCommands.putLeoOutline()
        p2 = c.fileCommands.getLeoOutline(s)
        p2.moveToLastChildOf(toRoot)
        c.redraw(p2)
        u.afterInsertNode(p2,undoType,undoData)
        c.setChanged(True)
        toChapter.p = p2.copy()
        toChapter.select()
        fromChapter.p = p.copy()
Ejemplo n.º 35
0
 def cleanAtCleanTree(self, event):
     """
     Adjust whitespace in the nearest @clean tree,
     searching c.p and its ancestors.
     """
     c = self.c
     # Look for an @clean node.
     for p in c.p.self_and_parents(copy=False):
         if g.match_word(p.h, 0, '@clean') and p.h.rstrip().endswith('.py'):
             break
     else:
         g.es_print('no an @clean node found', p.h, color='blue')
         return
     # pylint: disable=undefined-loop-variable
     # p is certainly defined here.
     bunch = c.undoer.beforeChangeTree(p)
     n = 0
     undoType = 'clean-@clean-tree'
     for p2 in p.subtree():
         if self.cleanAtCleanNode(p2, undoType):
             n += 1
     if n > 0:
         c.setChanged()
         c.undoer.afterChangeTree(p, undoType, bunch)
     g.es_print(f"{n} node{g.plural(n)} cleaned")
Ejemplo n.º 36
0
def getPathOld(p):
    # NOT USED, my version which does its own @path scanning
    p = p.copy()

    path = []

    while p:
        h = p.h

        if g.match_word(h,0,"@path"):  # top of the tree
            path.insert(0,os.path.expanduser(h[6:].strip()))
            d = os.path.join(*path)
            return d

        elif h.startswith('@'):  # some other directive, run away
            break

        elif isDirNode(p):  # a directory
            path.insert(0,h.strip('/*'))

        elif not p.hasChildren():  # a leaf node, assume a file
            path.insert(0,h.strip('*'))

        p = p.parent()

    return None
Ejemplo n.º 37
0
 def finishCreate(self):
     '''Find or make the @chapters and @chapter trash nodes.'''
     trace = (False or g.trace_startup) and not g.unitTesting
     if trace: g.es_debug('(cc)')
     cc, c = self, self.c
     if cc.findChaptersNode():
         if hasattr(c.frame.iconBar, 'createChaptersIcon'):
             if not cc.tt:
                 cc.tt = c.frame.iconBar.createChaptersIcon()
     # Create the main chapter
     cc.chaptersDict['main'] = Chapter(c, cc, 'main')
     tag = '@chapter'
     for p in c.all_unique_positions():
         h = p.h
         # if h.startswith(tag) and not h.startswith('@chapters'):
         if g.match_word(h, 0, tag):
             tabName = h[len(tag):].strip()
             if tabName and tabName not in ('main',):
                 if cc.chaptersDict.get(tabName):
                     self.error('duplicate chapter name: %s' % tabName)
                 else:
                     cc.chaptersDict[tabName] = Chapter(c, cc, tabName)
     # Fix bug: https://github.com/leo-editor/leo-editor/issues/31
     cc.initing = False
     # Always select the main chapter.
     # It can be alarming to open a small chapter in a large .leo file.
     cc.selectChapterByName('main', collapse=False)
Ejemplo n.º 38
0
 def is_root(p):
     """
     A predicate returning True if p is an @<file> node that is not under @nopylint.
     """
     for parent in p.self_and_parents():
         if g.match_word(parent.h, 0, '@nopylint'):
             return False
     return p.isAnyAtFileNode() and p.h.strip().endswith('.py')
Ejemplo n.º 39
0
def on_select2 (tag,keywords):

    c = keywords.get("c")

    if g.match_word(c.p.h,0,"@read-only"):
        disable_body(c.frame.body)
    else:
        enable_body(c.frame.body)
Ejemplo n.º 40
0
def on_select2(tag, keywords):

    c = keywords.get("c")

    if g.match_word(c.p.h, 0, "@read-only"):
        disable_body(c.frame.body)
    else:
        enable_body(c.frame.body)
Ejemplo n.º 41
0
def FindRunChildren(p):

    global RunList

    for child in p.children():
        if g.match_word(child.h,0,"@run"):
            RunList.append(child)
        FindRunChildren(child)
Ejemplo n.º 42
0
 def ends_block(self, line, new_state, prev_state, stack):
     '''True if line ends a function or procedure.'''
     if prev_state.context:
         return False
     else:
         ls = line.lstrip()
         val = g.match_word(ls, 0, 'end')
         return val
Ejemplo n.º 43
0
def FindRunChildren(p):

    global RunList

    for child in p.children():
        if g.match_word(child.h,0,"@run"):
            RunList.append(child)	
        FindRunChildren(child)
Ejemplo n.º 44
0
def on_save(tag,keywords):
    c = keywords.get("c")
    if not c: return

    for p in c.all_positions():
        h = p.h
        if g.match_word(h,0,"@text") and p.isDirty():
            savetextnode(c, p)
            c.setBodyString(p, "")
Ejemplo n.º 45
0
def on_open(tag,keywords):
    c = keywords.get("c")
    if not c: return

    for p in c.all_positions():
        h = p.h
        if g.match_word(h,0,"@text"):
            readtextnode(c, p)
    c.redraw()
Ejemplo n.º 46
0
 def convert_legacy_outline(self, event=None):
     """
     Convert @rst-preformat nodes and `@ @rst-options` doc parts.
     """
     c = self.c
     for p in c.all_unique_positions():
         if g.match_word(p.h, 0, '@rst-preformat'):
             self.preformat(p)
         self.convert_rst_options(p)
Ejemplo n.º 47
0
def on_save(tag,keywords):
    c = keywords.get("c")
    if not c: return

    for p in c.all_positions():
        h = p.h
        if g.match_word(h,0,"@text") and p.isDirty():
            savetextnode(c, p)
            c.setBodyString(p, "")
Ejemplo n.º 48
0
def on_open(tag,keywords):
    c = keywords.get("c")
    if not c: return

    for p in c.all_positions():
        h = p.h
        if g.match_word(h,0,"@text"):
            readtextnode(c, p)
    c.redraw()
Ejemplo n.º 49
0
    def moveNodeToChapterHelper (self,toChapterName):

        cc = self ; c = cc.c ; p = c.p ; u = c.undoer
        undoType = 'Move Node To Chapter'

        if g.match_word(p.h,0,'@chapter'):
            return cc.note('can not move @chapter node to another chapter')
        
        # Get the chapter objects.
        fromChapter = cc.getSelectedChapter()
        toChapter = cc.getChapter(toChapterName)
        if not fromChapter:
            return cc.note('no selected chapter')
        if not fromChapter:
            return cc.note('no chapter: %s' % (toChapterName))
            
        # Get the roots
        fromRoot,toRoot = fromChapter.root(),toChapter.root()
        if not fromRoot:
            return cc.note('no @chapter %s' % (fromChapter.name))
        if not toRoot:
            return cc.note('no @chapter %s' % (toChapter.name))
        
        if toChapter.name == 'main':
            sel = (p.threadBack() != fromRoot and p.threadBack()) or p.nodeAfterTree()
        else:
            sel = p.threadBack() or p.nodeAfterTree()

        if sel:
            # Get 'before' undo data.
            inAtIgnoreRange = p.inAtIgnoreRange()
            undoData = u.beforeMoveNode(p)
            dirtyVnodeList = p.setAllAncestorAtFileNodesDirty()
            # Do the move.
            if toChapter.name == 'main':
                p.moveAfter(toChapter.p)
            else:
                p.moveToLastChildOf(toRoot)
            c.redraw(sel)
            c.setChanged(True)
            # Do the 'after' undo operation.
            if inAtIgnoreRange and not p.inAtIgnoreRange():
                # The moved nodes have just become newly unignored.
                dirtyVnodeList2 = p.setDirty() # Mark descendent @thin nodes dirty.
                dirtyVnodeList.extend(dirtyVnodeList2)
            else: # No need to mark descendents dirty.
                dirtyVnodeList2 = p.setAllAncestorAtFileNodesDirty()
                dirtyVnodeList.extend(dirtyVnodeList2)
            u.afterMoveNode(p,undoType,undoData,dirtyVnodeList=dirtyVnodeList)

        if sel:
            toChapter.p = p.copy()
            toChapter.select()
            fromChapter.p = sel.copy()
        else:
            cc.note('can not move the last remaining node of a chapter.')
Ejemplo n.º 50
0
    def moveNodeToChapterHelper (self,toChapterName):

        cc = self ; c = cc.c ; p = c.p ; u = c.undoer
        undoType = 'Move Node To Chapter'

        if g.match_word(p.h,0,'@chapter'):
            return cc.note('can not move @chapter node to another chapter')

        # Get the chapter objects.
        fromChapter = cc.getSelectedChapter()
        toChapter = cc.getChapter(toChapterName)
        if not fromChapter:
            return cc.note('no selected chapter')
        if not fromChapter:
            return cc.note('no chapter: %s' % (toChapterName))

        # Get the roots
        fromRoot,toRoot = fromChapter.root(),toChapter.root()
        if not fromRoot:
            return cc.note('no @chapter %s' % (fromChapter.name))
        if not toRoot:
            return cc.note('no @chapter %s' % (toChapter.name))

        if toChapter.name == 'main':
            sel = (p.threadBack() != fromRoot and p.threadBack()) or p.nodeAfterTree()
        else:
            sel = p.threadBack() or p.nodeAfterTree()

        if sel:
            # Get 'before' undo data.
            inAtIgnoreRange = p.inAtIgnoreRange()
            undoData = u.beforeMoveNode(p)
            dirtyVnodeList = p.setAllAncestorAtFileNodesDirty()
            # Do the move.
            if toChapter.name == 'main':
                p.moveAfter(toChapter.p)
            else:
                p.moveToLastChildOf(toRoot)
            c.redraw(sel)
            c.setChanged(True)
            # Do the 'after' undo operation.
            if inAtIgnoreRange and not p.inAtIgnoreRange():
                # The moved nodes have just become newly unignored.
                dirtyVnodeList2 = p.setDirty() # Mark descendent @thin nodes dirty.
                dirtyVnodeList.extend(dirtyVnodeList2)
            else: # No need to mark descendents dirty.
                dirtyVnodeList2 = p.setAllAncestorAtFileNodesDirty()
                dirtyVnodeList.extend(dirtyVnodeList2)
            u.afterMoveNode(p,undoType,undoData,dirtyVnodeList=dirtyVnodeList)

        if sel:
            toChapter.p = p.copy()
            toChapter.select()
            fromChapter.p = sel.copy()
        else:
            cc.note('can not move the last remaining node of a chapter.')
Ejemplo n.º 51
0
 def next (self):
     
     '''Find the next screencast node and execute its script.
     Call m.quit if no more nodes remain.'''
     
     trace = False and not g.unitTesting
     m = self ; c = m.c ; k = c.k
     m.delete_widgets()
     # Restore k.state from m.k_state.
     if m.k_state.kind and m.k_state.kind != m.state_name:
         k.setState(kind=m.k_state.kind,n=m.k_state.n,handler=m.k_state.handler)
     while m.p:
         if trace: g.trace(m.p.h)
         h = m.p.h.replace('_','').replace('-','')
         if g.match_word(h,0,'@ignorenode'):
             m.p.moveToThreadNext()
         elif g.match_word(h,0,'@ignoretree') or g.match_word(h,0,'@button'):
             m.p.moveToNodeAfterTree()
         elif m.p.b.strip():
             p_next = m.p.threadNext()
             p_old = m.p.copy()
             if g.match_word(m.p.h,0,'@text'):
                 c.redraw(m.p) # Selects the node, thereby showing the body text.
             else:
                 m.exec_node(m.p)
             # Save k.state in m.k_state.
             if k.state:
                 if k.state.kind == m.state_name:
                     m.clear_state()
                 else:
                     m.set_state(k.state)
             # Re-enable m.state_handler.
             if not m.quit_flag:
                 k.setState(m.state_name,1,m.state_handler)
             # Change m.p only if the script has not already changed it.
             if not m.p or m.p == p_old:
                 m.p = p_next
             break
         else:
             m.p.moveToThreadNext()
     else:
         # No non-empty node found.
         m.quit()
Ejemplo n.º 52
0
def on_headkey2(tag, keywords):

    c = keywords.get("c")
    p = keywords.get("p")
    h = p.h
    ch = keywords.get("ch")
    if ch in ('\n', '\r') and g.match_word(h, 0, "@read-only"):
        # on-the-fly update of @read-only directives
        changed = insert_read_only_node(c, p, h[11:])
        c.setChanged(changed)
Ejemplo n.º 53
0
def on_icondclick(tag, keywords):
    c = keywords["c"]
    p = keywords["p"]
    h = p.h
    if g.match_word(h, 0, "@text"):
        if p.b != "":
            result = g.app.gui.runAskYesNoDialog(c, "Query", "Read from file " + h[6:] + "?")
            if result == "no":
                return
        readtextnode(c, p)
Ejemplo n.º 54
0
def on_icondclick(tag, keywords):
    c = keywords['c']
    p = keywords['p']
    h = p.h
    if g.match_word(h,0,"@text"):
        if p.b != "":
            result = g.app.gui.runAskYesNoDialog(c, "Query", "Read from file "+h[6:]+"?")
            if result == "no":
                return
        readtextnode(c, p)
Ejemplo n.º 55
0
 def findFirstSlideShow(self):
     c = self.c
     for p in c.all_positions():
         h = p.h.strip()
         if h.startswith('@slideshow'):
             self.firstSlideShow = p.copy()
             return p
         elif g.match_word(h, 0, '@ignore'):
             p = p.nodeAfterTree()
     self.firstSlideShow = None
     return None
Ejemplo n.º 56
0
 def ends_block(self, line, new_state, prev_state, stack):
     '''True if line ends a function or procedure.'''
     trace = False and g.unitTesting
     if prev_state.context:
         if trace: g.trace('in context', repr(prev_state.context))
         return False
     else:
         ls = line.lstrip()
         val = g.match_word(ls, 0, 'end')
         if trace and val: g.trace('  ', val, repr(line))
         return val
Ejemplo n.º 57
0
 def createAllButtons(self):
     '''Scan for @button, @rclick, @command, @plugin and @script nodes.'''
     c = self.c
     if self.scanned:
         return # Defensive.
     self.scanned = True
     # First, create standard buttons.
     if self.createRunScriptButton:
         self.createRunScriptIconButton()
     if self.createScriptButtonButton:
         self.createScriptButtonIconButton()
     if self.createDebugButton:
         self.createDebugIconButton()
     # Next, create common buttons and commands.
     self.createCommonButtons()
     self.createCommonCommands()
     # Last, scan for user-defined nodes.
     table = (
         ('@button', self.handleAtButtonNode),
         ('@command', self.handleAtCommandNode),
         ('@plugin', self.handleAtPluginNode),
         ('@rclick', self.handleAtRclickNode), # Jake Peck.
         ('@script', self.handleAtScriptNode),
     )
     p = c.rootPosition()
     while p:
         gnx = p.v.gnx
         if p.isAtIgnoreNode():
             p.moveToNodeAfterTree()
         elif gnx in self.seen:
             # tag:#657
             if g.match_word(p.h, 0, '@rclick'):
                 self.handleAtRclickNode(p)
             p.moveToThreadNext()
         else:
             self.seen.add(gnx)
             for kind, func in table:
                 if g.match_word(p.h, 0, kind):
                     func(p)
                     break
             p.moveToThreadNext()
Ejemplo n.º 58
0
def OnIconDoubleClick(tag,keywords):

    global RunNode,RunList,OwnIdleHook,ExitCode

    c=keywords.get('c')
    if not c or not c.exists: return
    p = c.p
    
    # g.trace(c.shortFileName())

    h = p.h
    if g.match_word(h,0,"@run"):
        if RunNode or RunList:
            g.error("@run already running!")
        else:
            #@+<< handle double click in @run icon >>
            #@+node:ekr.20040910102554: *4* << handle double click in @run icon >>
            RunList = []

            for p2 in p.self_and_subtree():
                if g.match_word(p2.h,0,"@run"):
                    # g.trace(p2.h)
                    # 2009/10/30: don't use iter copy arg.
                    RunList.append(p2.copy())	

            ExitCode = None
            OwnIdleHook = True
            g.enableIdleTimeHook(idleTimeDelay=100)
            #@-<< handle double click in @run icon >>
    elif g.match_word(h,0,"@in"):
        if RunNode:
            #@+<< handle double click in @in icon >>
            #@+node:ekr.20040910102554.1: *4* << handle double click in @in icon >>
            b = p.b

            try:
                In.write(b.encode(Encoding)+"\n")
                In.flush()
                g.es(b)
            except IOError as ioerr:
                g.error("@run IOError: "+str(ioerr))
Ejemplo n.º 59
0
 def cleanAtCleanNode(self, p, undoType):
     '''Adjust whitespace in p, part of an @clean tree.'''
     s = p.b.strip()
     if not s or p.h.strip().startswith('<<'):
         return False
     ws = '\n\n' if g.match_word(s, 0, 'class') else '\n'
     s2 = ws + s + ws
     changed = s2 != p.b
     if changed:
         p.b = s2
         p.setDirty()
     return changed