codecontainer.py 8.7 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254
  1. """A utility class for a code container.
  2. A code container is a class which holds source code for a debugger. It knows how
  3. to color the text, and also how to translate lines into offsets, and back.
  4. """
  5. import sys
  6. from win32com.axdebug import axdebug
  7. import tokenize
  8. from .util import RaiseNotImpl, _wrap
  9. from win32com.server.exception import Exception
  10. import win32api, winerror
  11. from . import contexts
  12. _keywords = {} # set of Python keywords
  13. for name in """
  14. and assert break class continue def del elif else except exec
  15. finally for from global if import in is lambda not
  16. or pass print raise return try while
  17. """.split():
  18. _keywords[name] = 1
  19. class SourceCodeContainer:
  20. def __init__(self, text, fileName = "<Remove Me!>", sourceContext = 0, startLineNumber = 0, site = None, debugDocument = None):
  21. self.sourceContext = sourceContext # The source context added by a smart host.
  22. self.text = text
  23. if text:
  24. self._buildlines()
  25. self.nextLineNo = 0
  26. self.fileName = fileName
  27. self.codeContexts = {}
  28. self.site = site
  29. self.startLineNumber = startLineNumber
  30. self.debugDocument = None
  31. def _Close(self):
  32. self.text = self.lines = self.lineOffsets = None
  33. self.codeContexts = None
  34. self.debugDocument = None
  35. self.site = None
  36. self.sourceContext = None
  37. def GetText(self):
  38. return self.text
  39. def GetName(self, dnt):
  40. assert 0, "You must subclass this"
  41. def GetFileName(self):
  42. return self.fileName
  43. def GetPositionOfLine(self, cLineNumber):
  44. self.GetText() # Prime us.
  45. try:
  46. return self.lineOffsets[cLineNumber]
  47. except IndexError:
  48. raise Exception(scode=winerror.S_FALSE)
  49. def GetLineOfPosition(self, charPos):
  50. self.GetText() # Prime us.
  51. lastOffset = 0
  52. lineNo = 0
  53. for lineOffset in self.lineOffsets[1:]:
  54. if lineOffset > charPos:
  55. break
  56. lastOffset = lineOffset
  57. lineNo = lineNo + 1
  58. else: # for not broken.
  59. # print "Cant find", charPos, "in", self.lineOffsets
  60. raise Exception(scode=winerror.S_FALSE)
  61. # print "GLOP ret=",lineNo, (charPos-lastOffset)
  62. return lineNo, (charPos-lastOffset)
  63. def GetNextLine(self):
  64. if self.nextLineNo>=len(self.lines):
  65. self.nextLineNo = 0 # auto-reset.
  66. return ""
  67. rc = self.lines[self.nextLineNo]
  68. self.nextLineNo = self.nextLineNo + 1
  69. return rc
  70. def GetLine(self, num):
  71. self.GetText() # Prime us.
  72. return self.lines[num]
  73. def GetNumChars(self):
  74. return len(self.GetText())
  75. def GetNumLines(self):
  76. self.GetText() # Prime us.
  77. return len(self.lines)
  78. def _buildline(self, pos):
  79. i = self.text.find('\n', pos)
  80. if i < 0:
  81. newpos = len(self.text)
  82. else:
  83. newpos = i+1
  84. r = self.text[pos:newpos]
  85. return r, newpos
  86. def _buildlines(self):
  87. self.lines = []
  88. self.lineOffsets = [0]
  89. line, pos = self._buildline(0)
  90. while line:
  91. self.lines.append(line)
  92. self.lineOffsets.append(pos)
  93. line, pos = self._buildline(pos)
  94. def _ProcessToken(self, type, token, spos, epos, line):
  95. srow, scol = spos
  96. erow, ecol = epos
  97. self.GetText() # Prime us.
  98. linenum = srow - 1 # Lines zero based for us too.
  99. realCharPos = self.lineOffsets[linenum] + scol
  100. numskipped = realCharPos - self.lastPos
  101. if numskipped==0:
  102. pass
  103. elif numskipped==1:
  104. self.attrs.append(axdebug.SOURCETEXT_ATTR_COMMENT)
  105. else:
  106. self.attrs.append((axdebug.SOURCETEXT_ATTR_COMMENT, numskipped))
  107. kwSize = len(token)
  108. self.lastPos = realCharPos + kwSize
  109. attr = 0
  110. if type==tokenize.NAME:
  111. if token in _keywords:
  112. attr = axdebug.SOURCETEXT_ATTR_KEYWORD
  113. elif type==tokenize.STRING:
  114. attr = axdebug.SOURCETEXT_ATTR_STRING
  115. elif type==tokenize.NUMBER:
  116. attr = axdebug.SOURCETEXT_ATTR_NUMBER
  117. elif type==tokenize.OP:
  118. attr = axdebug.SOURCETEXT_ATTR_OPERATOR
  119. elif type==tokenize.COMMENT:
  120. attr = axdebug.SOURCETEXT_ATTR_COMMENT
  121. # else attr remains zero...
  122. if kwSize==0:
  123. pass
  124. elif kwSize==1:
  125. self.attrs.append(attr)
  126. else:
  127. self.attrs.append((attr, kwSize))
  128. def GetSyntaxColorAttributes(self):
  129. self.lastPos = 0
  130. self.attrs = []
  131. try:
  132. tokenize.tokenize(self.GetNextLine, self._ProcessToken)
  133. except tokenize.TokenError:
  134. pass # Ignore - will cause all subsequent text to be commented.
  135. numAtEnd = len(self.GetText()) - self.lastPos
  136. if numAtEnd:
  137. self.attrs.append((axdebug.SOURCETEXT_ATTR_COMMENT, numAtEnd))
  138. return self.attrs
  139. # We also provide and manage DebugDocumentContext objects
  140. def _MakeDebugCodeContext(self, lineNo, charPos, len):
  141. return _wrap(contexts.DebugCodeContext(lineNo, charPos, len, self, self.site), axdebug.IID_IDebugCodeContext)
  142. # Make a context at the given position. It should take up the entire context.
  143. def _MakeContextAtPosition(self, charPos):
  144. lineNo, offset = self.GetLineOfPosition(charPos)
  145. try:
  146. endPos = self.GetPositionOfLine(lineNo+1)
  147. except:
  148. endPos = charPos
  149. codecontext = self._MakeDebugCodeContext(lineNo, charPos, endPos-charPos)
  150. return codecontext
  151. # Returns a DebugCodeContext. debugDocument can be None for smart hosts.
  152. def GetCodeContextAtPosition(self, charPos):
  153. # trace("GetContextOfPos", charPos, maxChars)
  154. # Convert to line number.
  155. lineNo, offset = self.GetLineOfPosition(charPos)
  156. charPos = self.GetPositionOfLine(lineNo)
  157. try:
  158. cc = self.codeContexts[charPos]
  159. # trace(" GetContextOfPos using existing")
  160. except KeyError:
  161. cc = self._MakeContextAtPosition(charPos)
  162. self.codeContexts[charPos] = cc
  163. return cc
  164. class SourceModuleContainer(SourceCodeContainer):
  165. def __init__(self, module):
  166. self.module = module
  167. if hasattr(module, '__file__'):
  168. fname = self.module.__file__
  169. # Check for .pyc or .pyo or even .pys!
  170. if fname[-1] in ['O','o','C','c', 'S', 's']: fname = fname[:-1]
  171. try:
  172. fname = win32api.GetFullPathName(fname)
  173. except win32api.error:
  174. pass
  175. else:
  176. if module.__name__=='__main__' and len(sys.argv)>0:
  177. fname = sys.argv[0]
  178. else:
  179. fname = "<Unknown!>"
  180. SourceCodeContainer.__init__(self, None, fname)
  181. def GetText(self):
  182. if self.text is None:
  183. fname = self.GetFileName()
  184. if fname:
  185. try:
  186. self.text = open(fname, "r").read()
  187. except IOError as details:
  188. self.text = "# Exception opening file\n# %s" % (repr(details))
  189. else:
  190. self.text = "# No file available for module '%s'" % (self.module)
  191. self._buildlines()
  192. return self.text
  193. def GetName(self, dnt):
  194. name = self.module.__name__
  195. try:
  196. fname = win32api.GetFullPathName(self.module.__file__)
  197. except win32api.error:
  198. fname = self.module.__file__
  199. except AttributeError:
  200. fname = name
  201. if dnt==axdebug.DOCUMENTNAMETYPE_APPNODE:
  202. return name.split(".")[-1]
  203. elif dnt==axdebug.DOCUMENTNAMETYPE_TITLE:
  204. return fname
  205. elif dnt==axdebug.DOCUMENTNAMETYPE_FILE_TAIL:
  206. return os.path.split(fname)[1]
  207. elif dnt==axdebug.DOCUMENTNAMETYPE_URL:
  208. return "file:%s" % fname
  209. else:
  210. raise Exception(scode=winerror.E_UNEXPECTED)
  211. if __name__=='__main__':
  212. import sys
  213. sys.path.append(".")
  214. import ttest
  215. sc = SourceModuleContainer(ttest)
  216. # sc = SourceCodeContainer(open(sys.argv[1], "rb").read(), sys.argv[1])
  217. attrs = sc.GetSyntaxColorAttributes()
  218. attrlen = 0
  219. for attr in attrs:
  220. if type(attr)==type(()):
  221. attrlen = attrlen + attr[1]
  222. else:
  223. attrlen = attrlen + 1
  224. text = sc.GetText()
  225. if attrlen!=len(text):
  226. print("Lengths dont match!!! (%d/%d)" % (attrlen, len(text)))
  227. # print "Attributes:"
  228. # print attrs
  229. print("GetLineOfPos=", sc.GetLineOfPosition(0))
  230. print("GetLineOfPos=", sc.GetLineOfPosition(4))
  231. print("GetLineOfPos=", sc.GetLineOfPosition(10))