Home | History | Annotate | Download | only in idlelib
      1 """Class browser.
      2 
      3 XXX TO DO:
      4 
      5 - reparse when source changed (maybe just a button would be OK?)
      6     (or recheck on window popup)
      7 - add popup menu with more options (e.g. doc strings, base classes, imports)
      8 - show function argument list? (have to do pattern matching on source)
      9 - should the classes and methods lists also be in the module's menu bar?
     10 - add base classes to class browser tree
     11 """
     12 
     13 import os
     14 import sys
     15 import pyclbr
     16 
     17 from idlelib import PyShell
     18 from idlelib.WindowList import ListedToplevel
     19 from idlelib.TreeWidget import TreeNode, TreeItem, ScrolledCanvas
     20 from idlelib.configHandler import idleConf
     21 
     22 class ClassBrowser:
     23 
     24     def __init__(self, flist, name, path):
     25         # XXX This API should change, if the file doesn't end in ".py"
     26         # XXX the code here is bogus!
     27         self.name = name
     28         self.file = os.path.join(path[0], self.name + ".py")
     29         self.init(flist)
     30 
     31     def close(self, event=None):
     32         self.top.destroy()
     33         self.node.destroy()
     34 
     35     def init(self, flist):
     36         self.flist = flist
     37         # reset pyclbr
     38         pyclbr._modules.clear()
     39         # create top
     40         self.top = top = ListedToplevel(flist.root)
     41         top.protocol("WM_DELETE_WINDOW", self.close)
     42         top.bind("<Escape>", self.close)
     43         self.settitle()
     44         top.focus_set()
     45         # create scrolled canvas
     46         theme = idleConf.GetOption('main','Theme','name')
     47         background = idleConf.GetHighlight(theme, 'normal')['background']
     48         sc = ScrolledCanvas(top, bg=background, highlightthickness=0, takefocus=1)
     49         sc.frame.pack(expand=1, fill="both")
     50         item = self.rootnode()
     51         self.node = node = TreeNode(sc.canvas, None, item)
     52         node.update()
     53         node.expand()
     54 
     55     def settitle(self):
     56         self.top.wm_title("Class Browser - " + self.name)
     57         self.top.wm_iconname("Class Browser")
     58 
     59     def rootnode(self):
     60         return ModuleBrowserTreeItem(self.file)
     61 
     62 class ModuleBrowserTreeItem(TreeItem):
     63 
     64     def __init__(self, file):
     65         self.file = file
     66 
     67     def GetText(self):
     68         return os.path.basename(self.file)
     69 
     70     def GetIconName(self):
     71         return "python"
     72 
     73     def GetSubList(self):
     74         sublist = []
     75         for name in self.listclasses():
     76             item = ClassBrowserTreeItem(name, self.classes, self.file)
     77             sublist.append(item)
     78         return sublist
     79 
     80     def OnDoubleClick(self):
     81         if os.path.normcase(self.file[-3:]) != ".py":
     82             return
     83         if not os.path.exists(self.file):
     84             return
     85         PyShell.flist.open(self.file)
     86 
     87     def IsExpandable(self):
     88         return os.path.normcase(self.file[-3:]) == ".py"
     89 
     90     def listclasses(self):
     91         dir, file = os.path.split(self.file)
     92         name, ext = os.path.splitext(file)
     93         if os.path.normcase(ext) != ".py":
     94             return []
     95         try:
     96             dict = pyclbr.readmodule_ex(name, [dir] + sys.path)
     97         except ImportError, msg:
     98             return []
     99         items = []
    100         self.classes = {}
    101         for key, cl in dict.items():
    102             if cl.module == name:
    103                 s = key
    104                 if hasattr(cl, 'super') and cl.super:
    105                     supers = []
    106                     for sup in cl.super:
    107                         if type(sup) is type(''):
    108                             sname = sup
    109                         else:
    110                             sname = sup.name
    111                             if sup.module != cl.module:
    112                                 sname = "%s.%s" % (sup.module, sname)
    113                         supers.append(sname)
    114                     s = s + "(%s)" % ", ".join(supers)
    115                 items.append((cl.lineno, s))
    116                 self.classes[s] = cl
    117         items.sort()
    118         list = []
    119         for item, s in items:
    120             list.append(s)
    121         return list
    122 
    123 class ClassBrowserTreeItem(TreeItem):
    124 
    125     def __init__(self, name, classes, file):
    126         self.name = name
    127         self.classes = classes
    128         self.file = file
    129         try:
    130             self.cl = self.classes[self.name]
    131         except (IndexError, KeyError):
    132             self.cl = None
    133         self.isfunction = isinstance(self.cl, pyclbr.Function)
    134 
    135     def GetText(self):
    136         if self.isfunction:
    137             return "def " + self.name + "(...)"
    138         else:
    139             return "class " + self.name
    140 
    141     def GetIconName(self):
    142         if self.isfunction:
    143             return "python"
    144         else:
    145             return "folder"
    146 
    147     def IsExpandable(self):
    148         if self.cl:
    149             try:
    150                 return not not self.cl.methods
    151             except AttributeError:
    152                 return False
    153 
    154     def GetSubList(self):
    155         if not self.cl:
    156             return []
    157         sublist = []
    158         for name in self.listmethods():
    159             item = MethodBrowserTreeItem(name, self.cl, self.file)
    160             sublist.append(item)
    161         return sublist
    162 
    163     def OnDoubleClick(self):
    164         if not os.path.exists(self.file):
    165             return
    166         edit = PyShell.flist.open(self.file)
    167         if hasattr(self.cl, 'lineno'):
    168             lineno = self.cl.lineno
    169             edit.gotoline(lineno)
    170 
    171     def listmethods(self):
    172         if not self.cl:
    173             return []
    174         items = []
    175         for name, lineno in self.cl.methods.items():
    176             items.append((lineno, name))
    177         items.sort()
    178         list = []
    179         for item, name in items:
    180             list.append(name)
    181         return list
    182 
    183 class MethodBrowserTreeItem(TreeItem):
    184 
    185     def __init__(self, name, cl, file):
    186         self.name = name
    187         self.cl = cl
    188         self.file = file
    189 
    190     def GetText(self):
    191         return "def " + self.name + "(...)"
    192 
    193     def GetIconName(self):
    194         return "python" # XXX
    195 
    196     def IsExpandable(self):
    197         return 0
    198 
    199     def OnDoubleClick(self):
    200         if not os.path.exists(self.file):
    201             return
    202         edit = PyShell.flist.open(self.file)
    203         edit.gotoline(self.cl.methods[self.name])
    204 
    205 def main():
    206     try:
    207         file = __file__
    208     except NameError:
    209         file = sys.argv[0]
    210         if sys.argv[1:]:
    211             file = sys.argv[1]
    212         else:
    213             file = sys.argv[0]
    214     dir, file = os.path.split(file)
    215     name = os.path.splitext(file)[0]
    216     ClassBrowser(PyShell.flist, name, [dir])
    217     if sys.stdin is sys.__stdin__:
    218         mainloop()
    219 
    220 if __name__ == "__main__":
    221     main()
    222