Funktionierender Prototyp des Serious Games zur Vermittlung von Wissen zu Software-Engineering-Arbeitsmodellen.
You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.

document.py 11KB

1 year ago
123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312
  1. import codecs
  2. import re
  3. import string
  4. import win32con
  5. import win32ui
  6. from pywin import default_scintilla_encoding
  7. from pywin.mfc import docview
  8. from . import scintillacon
  9. crlf_bytes = "\r\n".encode("ascii")
  10. lf_bytes = "\n".encode("ascii")
  11. # re from pep263 - but we use it both on bytes and strings.
  12. re_encoding_bytes = re.compile("coding[:=]\s*([-\w.]+)".encode("ascii"))
  13. re_encoding_text = re.compile("coding[:=]\s*([-\w.]+)")
  14. ParentScintillaDocument = docview.Document
  15. class CScintillaDocument(ParentScintillaDocument):
  16. "A SyntEdit document."
  17. def __init__(self, *args):
  18. self.bom = None # the BOM, if any, read from the file.
  19. # the encoding we detected from the source. Might have
  20. # detected via the BOM or an encoding decl. Note that in
  21. # the latter case (ie, while self.bom is None), it can't be
  22. # trusted - the user may have edited the encoding decl between
  23. # open and save.
  24. self.source_encoding = None
  25. ParentScintillaDocument.__init__(self, *args)
  26. def DeleteContents(self):
  27. pass
  28. def OnOpenDocument(self, filename):
  29. # init data members
  30. # print "Opening", filename
  31. self.SetPathName(filename) # Must set this early!
  32. try:
  33. # load the text as binary we can get smart
  34. # about detecting any existing EOL conventions.
  35. f = open(filename, "rb")
  36. try:
  37. self._LoadTextFromFile(f)
  38. finally:
  39. f.close()
  40. except IOError:
  41. rc = win32ui.MessageBox(
  42. "Could not load the file from %s\n\nDo you want to create a new file?"
  43. % filename,
  44. "Pythonwin",
  45. win32con.MB_YESNO | win32con.MB_ICONWARNING,
  46. )
  47. if rc == win32con.IDNO:
  48. return 0
  49. assert rc == win32con.IDYES, rc
  50. try:
  51. f = open(filename, "wb+")
  52. try:
  53. self._LoadTextFromFile(f)
  54. finally:
  55. f.close()
  56. except IOError as e:
  57. rc = win32ui.MessageBox("Cannot create the file %s" % filename)
  58. return 1
  59. def SaveFile(self, fileName, encoding=None):
  60. view = self.GetFirstView()
  61. ok = view.SaveTextFile(fileName, encoding=encoding)
  62. if ok:
  63. view.SCISetSavePoint()
  64. return ok
  65. def ApplyFormattingStyles(self):
  66. self._ApplyOptionalToViews("ApplyFormattingStyles")
  67. # #####################
  68. # File related functions
  69. # Helper to transfer text from the MFC document to the control.
  70. def _LoadTextFromFile(self, f):
  71. # detect EOL mode - we don't support \r only - so find the
  72. # first '\n' and guess based on the char before.
  73. l = f.readline()
  74. l2 = f.readline()
  75. # If line ends with \r\n or has no line ending, use CRLF.
  76. if l.endswith(crlf_bytes) or not l.endswith(lf_bytes):
  77. eol_mode = scintillacon.SC_EOL_CRLF
  78. else:
  79. eol_mode = scintillacon.SC_EOL_LF
  80. # Detect the encoding - first look for a BOM, and if not found,
  81. # look for a pep263 encoding declaration.
  82. for bom, encoding in (
  83. (codecs.BOM_UTF8, "utf8"),
  84. (codecs.BOM_UTF16_LE, "utf_16_le"),
  85. (codecs.BOM_UTF16_BE, "utf_16_be"),
  86. ):
  87. if l.startswith(bom):
  88. self.bom = bom
  89. self.source_encoding = encoding
  90. l = l[len(bom) :] # remove it.
  91. break
  92. else:
  93. # no bom detected - look for pep263 encoding decl.
  94. for look in (l, l2):
  95. # Note we are looking at raw bytes here: so
  96. # both the re itself uses bytes and the result
  97. # is bytes - but we need the result as a string.
  98. match = re_encoding_bytes.search(look)
  99. if match is not None:
  100. self.source_encoding = match.group(1).decode("ascii")
  101. break
  102. # reading by lines would be too slow? Maybe we can use the
  103. # incremental encoders? For now just stick with loading the
  104. # entire file in memory.
  105. text = l + l2 + f.read()
  106. # Translate from source encoding to UTF-8 bytes for Scintilla
  107. source_encoding = self.source_encoding
  108. # If we don't know an encoding, try utf-8 - if that fails we will
  109. # fallback to latin-1 to treat it as bytes...
  110. if source_encoding is None:
  111. source_encoding = "utf-8"
  112. # we could optimize this by avoiding utf8 to-ing and from-ing,
  113. # but then we would lose the ability to handle invalid utf8
  114. # (and even then, the use of encoding aliases makes this tricky)
  115. # To create an invalid utf8 file:
  116. # >>> open(filename, "wb").write(codecs.BOM_UTF8+"bad \xa9har\r\n")
  117. try:
  118. dec = text.decode(source_encoding)
  119. except UnicodeError:
  120. print(
  121. "WARNING: Failed to decode bytes from '%s' encoding - treating as latin1"
  122. % source_encoding
  123. )
  124. dec = text.decode("latin1")
  125. except LookupError:
  126. print(
  127. "WARNING: Invalid encoding '%s' specified - treating as latin1"
  128. % source_encoding
  129. )
  130. dec = text.decode("latin1")
  131. # and put it back as utf8 - this shouldn't fail.
  132. text = dec.encode(default_scintilla_encoding)
  133. view = self.GetFirstView()
  134. if view.IsWindow():
  135. # Turn off undo collection while loading
  136. view.SendScintilla(scintillacon.SCI_SETUNDOCOLLECTION, 0, 0)
  137. # Make sure the control isnt read-only
  138. view.SetReadOnly(0)
  139. view.SendScintilla(scintillacon.SCI_CLEARALL)
  140. view.SendMessage(scintillacon.SCI_ADDTEXT, text)
  141. view.SendScintilla(scintillacon.SCI_SETUNDOCOLLECTION, 1, 0)
  142. view.SendScintilla(win32con.EM_EMPTYUNDOBUFFER, 0, 0)
  143. # set EOL mode
  144. view.SendScintilla(scintillacon.SCI_SETEOLMODE, eol_mode)
  145. def _SaveTextToFile(self, view, filename, encoding=None):
  146. s = view.GetTextRange() # already decoded from scintilla's encoding
  147. source_encoding = encoding
  148. if source_encoding is None:
  149. if self.bom:
  150. source_encoding = self.source_encoding
  151. else:
  152. # no BOM - look for an encoding.
  153. bits = re.split("[\r\n]+", s, 3)
  154. for look in bits[:-1]:
  155. match = re_encoding_text.search(look)
  156. if match is not None:
  157. source_encoding = match.group(1)
  158. self.source_encoding = source_encoding
  159. break
  160. if source_encoding is None:
  161. source_encoding = "utf-8"
  162. ## encode data before opening file so script is not lost if encoding fails
  163. file_contents = s.encode(source_encoding)
  164. # Open in binary mode as scintilla itself ensures the
  165. # line endings are already appropriate
  166. f = open(filename, "wb")
  167. try:
  168. if self.bom:
  169. f.write(self.bom)
  170. f.write(file_contents)
  171. finally:
  172. f.close()
  173. self.SetModifiedFlag(0)
  174. def FinalizeViewCreation(self, view):
  175. pass
  176. def HookViewNotifications(self, view):
  177. parent = view.GetParentFrame()
  178. parent.HookNotify(
  179. ViewNotifyDelegate(self, "OnBraceMatch"), scintillacon.SCN_CHECKBRACE
  180. )
  181. parent.HookNotify(
  182. ViewNotifyDelegate(self, "OnMarginClick"), scintillacon.SCN_MARGINCLICK
  183. )
  184. parent.HookNotify(
  185. ViewNotifyDelegate(self, "OnNeedShown"), scintillacon.SCN_NEEDSHOWN
  186. )
  187. parent.HookNotify(
  188. DocumentNotifyDelegate(self, "OnSavePointReached"),
  189. scintillacon.SCN_SAVEPOINTREACHED,
  190. )
  191. parent.HookNotify(
  192. DocumentNotifyDelegate(self, "OnSavePointLeft"),
  193. scintillacon.SCN_SAVEPOINTLEFT,
  194. )
  195. parent.HookNotify(
  196. DocumentNotifyDelegate(self, "OnModifyAttemptRO"),
  197. scintillacon.SCN_MODIFYATTEMPTRO,
  198. )
  199. # Tell scintilla what characters should abort auto-complete.
  200. view.SCIAutoCStops(string.whitespace + "()[]:;+-/*=\\?'!#@$%^&,<>\"'|")
  201. if view != self.GetFirstView():
  202. view.SCISetDocPointer(self.GetFirstView().SCIGetDocPointer())
  203. def OnSavePointReached(self, std, extra):
  204. self.SetModifiedFlag(0)
  205. def OnSavePointLeft(self, std, extra):
  206. self.SetModifiedFlag(1)
  207. def OnModifyAttemptRO(self, std, extra):
  208. self.MakeDocumentWritable()
  209. # All Marker functions are 1 based.
  210. def MarkerAdd(self, lineNo, marker):
  211. self.GetEditorView().SCIMarkerAdd(lineNo - 1, marker)
  212. def MarkerCheck(self, lineNo, marker):
  213. v = self.GetEditorView()
  214. lineNo = lineNo - 1 # Make 0 based
  215. markerState = v.SCIMarkerGet(lineNo)
  216. return markerState & (1 << marker) != 0
  217. def MarkerToggle(self, lineNo, marker):
  218. v = self.GetEditorView()
  219. if self.MarkerCheck(lineNo, marker):
  220. v.SCIMarkerDelete(lineNo - 1, marker)
  221. else:
  222. v.SCIMarkerAdd(lineNo - 1, marker)
  223. def MarkerDelete(self, lineNo, marker):
  224. self.GetEditorView().SCIMarkerDelete(lineNo - 1, marker)
  225. def MarkerDeleteAll(self, marker):
  226. self.GetEditorView().SCIMarkerDeleteAll(marker)
  227. def MarkerGetNext(self, lineNo, marker):
  228. return self.GetEditorView().SCIMarkerNext(lineNo - 1, 1 << marker) + 1
  229. def MarkerAtLine(self, lineNo, marker):
  230. markerState = self.GetEditorView().SCIMarkerGet(lineNo - 1)
  231. return markerState & (1 << marker)
  232. # Helper for reflecting functions to views.
  233. def _ApplyToViews(self, funcName, *args):
  234. for view in self.GetAllViews():
  235. func = getattr(view, funcName)
  236. func(*args)
  237. def _ApplyOptionalToViews(self, funcName, *args):
  238. for view in self.GetAllViews():
  239. func = getattr(view, funcName, None)
  240. if func is not None:
  241. func(*args)
  242. def GetEditorView(self):
  243. # Find the first frame with a view,
  244. # then ask it to give the editor view
  245. # as it knows which one is "active"
  246. try:
  247. frame_gev = self.GetFirstView().GetParentFrame().GetEditorView
  248. except AttributeError:
  249. return self.GetFirstView()
  250. return frame_gev()
  251. # Delegate to the correct view, based on the control that sent it.
  252. class ViewNotifyDelegate:
  253. def __init__(self, doc, name):
  254. self.doc = doc
  255. self.name = name
  256. def __call__(self, std, extra):
  257. (hwndFrom, idFrom, code) = std
  258. for v in self.doc.GetAllViews():
  259. if v.GetSafeHwnd() == hwndFrom:
  260. return getattr(v, self.name)(*(std, extra))
  261. # Delegate to the document, but only from a single view (as each view sends it seperately)
  262. class DocumentNotifyDelegate:
  263. def __init__(self, doc, name):
  264. self.doc = doc
  265. self.delegate = getattr(doc, name)
  266. def __call__(self, std, extra):
  267. (hwndFrom, idFrom, code) = std
  268. if hwndFrom == self.doc.GetEditorView().GetSafeHwnd():
  269. self.delegate(*(std, extra))