team-10/venv/Lib/site-packages/win32comext/axdebug/codecontainer.py
2025-08-02 02:00:33 +02:00

279 lines
8.8 KiB
Python

"""A utility class for a code container.
A code container is a class which holds source code for a debugger. It knows how
to color the text, and also how to translate lines into offsets, and back.
"""
from __future__ import annotations
import os
import sys
import tokenize
from keyword import kwlist
from typing import Any
import win32api
import winerror
from win32com.axdebug import axdebug, contexts
from win32com.axdebug.util import _wrap
from win32com.server.exception import COMException
_keywords = {
_keyword
for _keyword in kwlist
# Avoids including True/False/None
if _keyword.islower()
}
"""set of Python keywords"""
class SourceCodeContainer:
def __init__(
self,
text: str | None,
fileName="<Remove Me!>",
sourceContext=0,
startLineNumber=0,
site=None,
debugDocument=None,
):
self.sourceContext = sourceContext # The source context added by a smart host.
self.text: str | None = text
if text:
self._buildlines()
self.nextLineNo = 0
self.fileName = fileName
# Any: PyIDispatch type is not statically exposed
self.codeContexts: dict[int, Any] = {}
self.site = site
self.startLineNumber = startLineNumber
self.debugDocument = debugDocument
def _Close(self):
self.text = self.lines = self.lineOffsets = None
self.codeContexts = None
self.debugDocument = None
self.site = None
self.sourceContext = None
def GetText(self):
return self.text
def GetName(self, dnt):
raise NotImplementedError("You must subclass this")
def GetFileName(self):
return self.fileName
def GetPositionOfLine(self, cLineNumber):
self.GetText() # Prime us.
try:
return self.lineOffsets[cLineNumber]
except IndexError:
raise COMException(scode=winerror.S_FALSE)
def GetLineOfPosition(self, charPos):
self.GetText() # Prime us.
lastOffset = 0
lineNo = 0
for lineOffset in self.lineOffsets[1:]:
if lineOffset > charPos:
break
lastOffset = lineOffset
lineNo += 1
else: # for not broken.
# print("Can't find", charPos, "in", self.lineOffsets)
raise COMException(scode=winerror.S_FALSE)
# print("GLOP ret=", lineNo, (charPos - lastOffset))
return lineNo, (charPos - lastOffset)
def GetNextLine(self):
if self.nextLineNo >= len(self.lines):
self.nextLineNo = 0 # auto-reset.
return ""
rc = self.lines[self.nextLineNo]
self.nextLineNo += 1
return rc
def GetLine(self, num):
self.GetText() # Prime us.
return self.lines[num]
def GetNumChars(self):
return len(self.GetText())
def GetNumLines(self):
self.GetText() # Prime us.
return len(self.lines)
def _buildline(self, pos):
i = self.text.find("\n", pos)
if i < 0:
newpos = len(self.text)
else:
newpos = i + 1
r = self.text[pos:newpos]
return r, newpos
def _buildlines(self):
self.lines = []
self.lineOffsets = [0]
line, pos = self._buildline(0)
while line:
self.lines.append(line)
self.lineOffsets.append(pos)
line, pos = self._buildline(pos)
def _ProcessToken(self, type, token, spos, epos, line):
srow, scol = spos
erow, ecol = epos
self.GetText() # Prime us.
linenum = srow - 1 # Lines zero based for us too.
realCharPos = self.lineOffsets[linenum] + scol
numskipped = realCharPos - self.lastPos
if numskipped == 0:
pass
elif numskipped == 1:
self.attrs.append(axdebug.SOURCETEXT_ATTR_COMMENT)
else:
self.attrs.append((axdebug.SOURCETEXT_ATTR_COMMENT, numskipped))
kwSize = len(token)
self.lastPos = realCharPos + kwSize
attr = 0
if type == tokenize.NAME:
if token in _keywords:
attr = axdebug.SOURCETEXT_ATTR_KEYWORD
elif type == tokenize.STRING:
attr = axdebug.SOURCETEXT_ATTR_STRING
elif type == tokenize.NUMBER:
attr = axdebug.SOURCETEXT_ATTR_NUMBER
elif type == tokenize.OP:
attr = axdebug.SOURCETEXT_ATTR_OPERATOR
elif type == tokenize.COMMENT:
attr = axdebug.SOURCETEXT_ATTR_COMMENT
# else attr remains zero...
if kwSize == 0:
pass
elif kwSize == 1:
self.attrs.append(attr)
else:
self.attrs.append((attr, kwSize))
def GetSyntaxColorAttributes(self):
self.lastPos = 0
self.attrs = []
try:
for tokens in tokenize.tokenize(self.GetNextLine):
self._ProcessToken(*tokens)
except tokenize.TokenError:
pass # Ignore - will cause all subsequent text to be commented.
numAtEnd = len(self.GetText()) - self.lastPos
if numAtEnd:
self.attrs.append((axdebug.SOURCETEXT_ATTR_COMMENT, numAtEnd))
return self.attrs
# We also provide and manage DebugDocumentContext objects
def _MakeDebugCodeContext(self, lineNo, charPos, len):
return _wrap(
contexts.DebugCodeContext(lineNo, charPos, len, self, self.site),
axdebug.IID_IDebugCodeContext,
)
# Make a context at the given position. It should take up the entire context.
def _MakeContextAtPosition(self, charPos):
lineNo, offset = self.GetLineOfPosition(charPos)
try:
endPos = self.GetPositionOfLine(lineNo + 1)
except:
endPos = charPos
codecontext = self._MakeDebugCodeContext(lineNo, charPos, endPos - charPos)
return codecontext
# Returns a DebugCodeContext. debugDocument can be None for smart hosts.
def GetCodeContextAtPosition(self, charPos):
# trace("GetContextOfPos", charPos, maxChars)
# Convert to line number.
lineNo, offset = self.GetLineOfPosition(charPos)
charPos = self.GetPositionOfLine(lineNo)
try:
cc = self.codeContexts[charPos]
except KeyError:
cc = self._MakeContextAtPosition(charPos)
self.codeContexts[charPos] = cc
return cc
class SourceModuleContainer(SourceCodeContainer):
def __init__(self, module):
self.module = module
if hasattr(module, "__file__"):
fname = self.module.__file__
# Check for .pyc or .pyo or even .pys!
if fname[-1] in ["O", "o", "C", "c", "S", "s"]:
fname = fname[:-1]
try:
fname = win32api.GetFullPathName(fname)
except win32api.error:
pass
else:
if module.__name__ == "__main__" and len(sys.argv) > 0:
fname = sys.argv[0]
else:
fname = "<Unknown!>"
SourceCodeContainer.__init__(self, None, fname)
def GetText(self):
if self.text is None:
fname = self.GetFileName()
if fname:
try:
self.text = open(fname, "r").read()
except OSError as details:
self.text = f"# COMException opening file\n# {details!r}"
else:
self.text = f"# No file available for module '{self.module}'"
self._buildlines()
return self.text
def GetName(self, dnt):
name = self.module.__name__
try:
fname = win32api.GetFullPathName(self.module.__file__)
except win32api.error:
fname = self.module.__file__
except AttributeError:
fname = name
if dnt == axdebug.DOCUMENTNAMETYPE_APPNODE:
return name.split(".")[-1]
elif dnt == axdebug.DOCUMENTNAMETYPE_TITLE:
return fname
elif dnt == axdebug.DOCUMENTNAMETYPE_FILE_TAIL:
return os.path.split(fname)[1]
elif dnt == axdebug.DOCUMENTNAMETYPE_URL:
return f"file:{fname}"
else:
raise COMException(scode=winerror.E_UNEXPECTED)
if __name__ == "__main__":
from Test import ttest
sc = SourceModuleContainer(ttest)
# sc = SourceCodeContainer(open(sys.argv[1], "rb").read(), sys.argv[1])
attrs = sc.GetSyntaxColorAttributes()
attrlen = 0
for attr in attrs:
if isinstance(attr, tuple):
attrlen += attr[1]
else:
attrlen += 1
text = sc.GetText()
if attrlen != len(text):
print(f"Lengths don't match!!! ({attrlen}/{len(text)})")
# print("Attributes:")
# print(attrs)
print("GetLineOfPos=", sc.GetLineOfPosition(0))
print("GetLineOfPos=", sc.GetLineOfPosition(4))
print("GetLineOfPos=", sc.GetLineOfPosition(10))