ClassBrowser.py 6.8 KB

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