ironpython_console.py 13 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469
  1. # -*- coding: utf-8 -*-
  2. # *****************************************************************************
  3. # Copyright (C) 2003-2006 Gary Bishop.
  4. # Copyright (C) 2006-2020 Jorgen Stenarson. <jorgen.stenarson@bostream.nu>
  5. # Copyright (C) 2020 Bassem Girgis. <brgirgis@gmail.com>
  6. #
  7. # Distributed under the terms of the BSD License. The full license is in
  8. # the file COPYING, distributed as part of this software.
  9. # *****************************************************************************
  10. import os
  11. import re
  12. import IronPythonConsole
  13. import System
  14. from pyreadline3.console.ansi import AnsiState
  15. from pyreadline3.keysyms import (
  16. make_keyinfo,
  17. make_KeyPress,
  18. make_KeyPress_from_keydescr,
  19. make_keysym,
  20. )
  21. from pyreadline3.logger import log
  22. from .event import Event
  23. """Cursor control and color for the .NET console.
  24. """
  25. #
  26. # Ironpython requires a patch to work do:
  27. #
  28. # In file PythonCommandLine.cs patch line:
  29. # class PythonCommandLine
  30. # {
  31. # to:
  32. # public class PythonCommandLine
  33. # {
  34. #
  35. #
  36. #
  37. # primitive debug printing that won't interfere with the screen
  38. import sys
  39. import clr
  40. clr.AddReferenceToFileAndPath(sys.executable)
  41. color = System.ConsoleColor
  42. ansicolor = {
  43. "0;30": color.Black,
  44. "0;31": color.DarkRed,
  45. "0;32": color.DarkGreen,
  46. "0;33": color.DarkYellow,
  47. "0;34": color.DarkBlue,
  48. "0;35": color.DarkMagenta,
  49. "0;36": color.DarkCyan,
  50. "0;37": color.DarkGray,
  51. "1;30": color.Gray,
  52. "1;31": color.Red,
  53. "1;32": color.Green,
  54. "1;33": color.Yellow,
  55. "1;34": color.Blue,
  56. "1;35": color.Magenta,
  57. "1;36": color.Cyan,
  58. "1;37": color.White,
  59. }
  60. winattr = {
  61. "black": 0,
  62. "darkgray": 0 + 8,
  63. "darkred": 4,
  64. "red": 4 + 8,
  65. "darkgreen": 2,
  66. "green": 2 + 8,
  67. "darkyellow": 6,
  68. "yellow": 6 + 8,
  69. "darkblue": 1,
  70. "blue": 1 + 8,
  71. "darkmagenta": 5,
  72. "magenta": 5 + 8,
  73. "darkcyan": 3,
  74. "cyan": 3 + 8,
  75. "gray": 7,
  76. "white": 7 + 8,
  77. }
  78. class Console(object):
  79. """Console driver for Windows."""
  80. def __init__(self, newbuffer=0):
  81. """Initialize the Console object.
  82. newbuffer=1 will allocate a new buffer so the old content will be restored
  83. on exit.
  84. """
  85. self.serial = 0
  86. self.attr = System.Console.ForegroundColor
  87. self.saveattr = winattr[str(System.Console.ForegroundColor).lower()]
  88. self.savebg = System.Console.BackgroundColor
  89. log("initial attr=%s" % self.attr)
  90. def _get(self):
  91. top = System.Console.WindowTop
  92. log("WindowTop:%s" % top)
  93. return top
  94. def _set(self, value):
  95. top = System.Console.WindowTop
  96. log("Set WindowTop:old:%s,new:%s" % (top, value))
  97. WindowTop = property(_get, _set)
  98. del _get, _set
  99. def __del__(self):
  100. """Cleanup the console when finished."""
  101. # I don't think this ever gets called
  102. pass
  103. def pos(self, x=None, y=None):
  104. """Move or query the window cursor."""
  105. if x is not None:
  106. System.Console.CursorLeft = x
  107. else:
  108. x = System.Console.CursorLeft
  109. if y is not None:
  110. System.Console.CursorTop = y
  111. else:
  112. y = System.Console.CursorTop
  113. return x, y
  114. def home(self):
  115. """Move to home."""
  116. self.pos(0, 0)
  117. # Map ANSI color escape sequences into Windows Console Attributes
  118. terminal_escape = re.compile("(\001?\033\\[[0-9;]*m\002?)")
  119. escape_parts = re.compile("\001?\033\\[([0-9;]*)m\002?")
  120. # This pattern should match all characters that change the cursor position differently
  121. # than a normal character.
  122. motion_char_re = re.compile("([\n\r\t\010\007])")
  123. def write_scrolling(self, text, attr=None):
  124. """write text at current cursor position while watching for scrolling.
  125. If the window scrolls because you are at the bottom of the screen
  126. buffer, all positions that you are storing will be shifted by the
  127. scroll amount. For example, I remember the cursor position of the
  128. prompt so that I can redraw the line but if the window scrolls,
  129. the remembered position is off.
  130. This variant of write tries to keep track of the cursor position
  131. so that it will know when the screen buffer is scrolled. It
  132. returns the number of lines that the buffer scrolled.
  133. """
  134. x, y = self.pos()
  135. w, h = self.size()
  136. scroll = 0 # the result
  137. # split the string into ordinary characters and funny characters
  138. chunks = self.motion_char_re.split(text)
  139. for chunk in chunks:
  140. n = self.write_color(chunk, attr)
  141. if len(chunk) == 1: # the funny characters will be alone
  142. if chunk[0] == "\n": # newline
  143. x = 0
  144. y += 1
  145. elif chunk[0] == "\r": # carriage return
  146. x = 0
  147. elif chunk[0] == "\t": # tab
  148. x = 8 * (int(x / 8) + 1)
  149. if x > w: # newline
  150. x -= w
  151. y += 1
  152. elif chunk[0] == "\007": # bell
  153. pass
  154. elif chunk[0] == "\010":
  155. x -= 1
  156. if x < 0:
  157. y -= 1 # backed up 1 line
  158. else: # ordinary character
  159. x += 1
  160. if x == w: # wrap
  161. x = 0
  162. y += 1
  163. if y == h: # scroll
  164. scroll += 1
  165. y = h - 1
  166. else: # chunk of ordinary characters
  167. x += n
  168. l = int(x / w) # lines we advanced
  169. x = x % w # new x value
  170. y += l
  171. if y >= h: # scroll
  172. scroll += y - h + 1
  173. y = h - 1
  174. return scroll
  175. trtable = {
  176. 0: color.Black,
  177. 4: color.DarkRed,
  178. 2: color.DarkGreen,
  179. 6: color.DarkYellow,
  180. 1: color.DarkBlue,
  181. 5: color.DarkMagenta,
  182. 3: color.DarkCyan,
  183. 7: color.Gray,
  184. 8: color.DarkGray,
  185. 4 + 8: color.Red,
  186. 2 + 8: color.Green,
  187. 6 + 8: color.Yellow,
  188. 1 + 8: color.Blue,
  189. 5 + 8: color.Magenta,
  190. 3 + 8: color.Cyan,
  191. 7 + 8: color.White,
  192. }
  193. def write_color(self, text, attr=None):
  194. """write text at current cursor position and interpret color escapes.
  195. return the number of characters written.
  196. """
  197. log('write_color("%s", %s)' % (text, attr))
  198. chunks = self.terminal_escape.split(text)
  199. log("chunks=%s" % repr(chunks))
  200. bg = self.savebg
  201. n = 0 # count the characters we actually write, omitting the escapes
  202. if attr is None: # use attribute from initial console
  203. attr = self.attr
  204. try:
  205. fg = self.trtable[(0x000F & attr)]
  206. bg = self.trtable[(0x00F0 & attr) >> 4]
  207. except TypeError:
  208. fg = attr
  209. for chunk in chunks:
  210. m = self.escape_parts.match(chunk)
  211. if m:
  212. log(m.group(1))
  213. attr = ansicolor.get(m.group(1), self.attr)
  214. n += len(chunk)
  215. System.Console.ForegroundColor = fg
  216. System.Console.BackgroundColor = bg
  217. System.Console.Write(chunk)
  218. return n
  219. def write_plain(self, text, attr=None):
  220. """write text at current cursor position."""
  221. log('write("%s", %s)' % (text, attr))
  222. if attr is None:
  223. attr = self.attr
  224. n = c_int(0)
  225. self.SetConsoleTextAttribute(self.hout, attr)
  226. self.WriteConsoleA(self.hout, text, len(text), byref(n), None)
  227. return len(text)
  228. if "EMACS" in os.environ:
  229. def write_color(self, text, attr=None):
  230. junk = c_int(0)
  231. self.WriteFile(self.hout, text, len(text), byref(junk), None)
  232. return len(text)
  233. write_plain = write_color
  234. # make this class look like a file object
  235. def write(self, text):
  236. log('write("%s")' % text)
  237. return self.write_color(text)
  238. # write = write_scrolling
  239. def isatty(self):
  240. return True
  241. def flush(self):
  242. pass
  243. def page(self, attr=None, fill=" "):
  244. """Fill the entire screen."""
  245. System.Console.Clear()
  246. def text(self, x, y, text, attr=None):
  247. """Write text at the given position."""
  248. self.pos(x, y)
  249. self.write_color(text, attr)
  250. def clear_to_end_of_window(self):
  251. oldtop = self.WindowTop
  252. lastline = self.WindowTop + System.Console.WindowHeight
  253. pos = self.pos()
  254. w, h = self.size()
  255. length = w - pos[0] + min((lastline - pos[1] - 1), 5) * w - 1
  256. self.write_color(length * " ")
  257. self.pos(*pos)
  258. self.WindowTop = oldtop
  259. def rectangle(self, rect, attr=None, fill=" "):
  260. """Fill Rectangle."""
  261. oldtop = self.WindowTop
  262. oldpos = self.pos()
  263. # raise NotImplementedError
  264. x0, y0, x1, y1 = rect
  265. if attr is None:
  266. attr = self.attr
  267. if fill:
  268. rowfill = fill[:1] * abs(x1 - x0)
  269. else:
  270. rowfill = " " * abs(x1 - x0)
  271. for y in range(y0, y1):
  272. System.Console.SetCursorPosition(x0, y)
  273. self.write_color(rowfill, attr)
  274. self.pos(*oldpos)
  275. def scroll(self, rect, dx, dy, attr=None, fill=" "):
  276. """Scroll a rectangle."""
  277. raise NotImplementedError
  278. def scroll_window(self, lines):
  279. """Scroll the window by the indicated number of lines."""
  280. top = self.WindowTop + lines
  281. if top < 0:
  282. top = 0
  283. if top + System.Console.WindowHeight > System.Console.BufferHeight:
  284. top = System.Console.BufferHeight
  285. self.WindowTop = top
  286. def getkeypress(self):
  287. """Return next key press event from the queue, ignoring others."""
  288. ck = System.ConsoleKey
  289. while True:
  290. e = System.Console.ReadKey(True)
  291. if e.Key == System.ConsoleKey.PageDown: # PageDown
  292. self.scroll_window(12)
  293. elif e.Key == System.ConsoleKey.PageUp: # PageUp
  294. self.scroll_window(-12)
  295. elif str(e.KeyChar) == "\000": # Drop deadkeys
  296. log("Deadkey: %s" % e)
  297. return event(self, e)
  298. else:
  299. return event(self, e)
  300. def title(self, txt=None):
  301. """Set/get title."""
  302. if txt:
  303. System.Console.Title = txt
  304. else:
  305. return System.Console.Title
  306. def size(self, width=None, height=None):
  307. """Set/get window size."""
  308. sc = System.Console
  309. if width is not None and height is not None:
  310. sc.BufferWidth, sc.BufferHeight = width, height
  311. else:
  312. return sc.BufferWidth, sc.BufferHeight
  313. if width is not None and height is not None:
  314. sc.WindowWidth, sc.WindowHeight = width, height
  315. else:
  316. return sc.WindowWidth - 1, sc.WindowHeight - 1
  317. def cursor(self, visible=True, size=None):
  318. """Set cursor on or off."""
  319. System.Console.CursorVisible = visible
  320. def bell(self):
  321. System.Console.Beep()
  322. def next_serial(self):
  323. """Get next event serial number."""
  324. self.serial += 1
  325. return self.serial
  326. class event(Event):
  327. """Represent events from the console."""
  328. def __init__(self, console, input):
  329. """Initialize an event from the Windows input structure."""
  330. self.type = "??"
  331. self.serial = console.next_serial()
  332. self.width = 0
  333. self.height = 0
  334. self.x = 0
  335. self.y = 0
  336. self.char = str(input.KeyChar)
  337. self.keycode = input.Key
  338. self.state = input.Modifiers
  339. log("%s,%s,%s" % (input.Modifiers, input.Key, input.KeyChar))
  340. self.type = "KeyRelease"
  341. self.keysym = make_keysym(self.keycode)
  342. self.keyinfo = make_KeyPress(self.char, self.state, self.keycode)
  343. def make_event_from_keydescr(keydescr):
  344. def input():
  345. return 1
  346. input.KeyChar = "a"
  347. input.Key = System.ConsoleKey.A
  348. input.Modifiers = System.ConsoleModifiers.Shift
  349. input.next_serial = input
  350. e = event(input, input)
  351. del input.next_serial
  352. keyinfo = make_KeyPress_from_keydescr(keydescr)
  353. e.keyinfo = keyinfo
  354. return e
  355. CTRL_C_EVENT = make_event_from_keydescr("Control-c")
  356. def install_readline(hook):
  357. def hook_wrap():
  358. try:
  359. res = hook()
  360. except KeyboardInterrupt as x: # this exception does not seem to be caught
  361. res = ""
  362. except EOFError:
  363. return None
  364. if res[-1:] == "\n":
  365. return res[:-1]
  366. else:
  367. return res
  368. class IronPythonWrapper(IronPythonConsole.IConsole):
  369. def ReadLine(self, autoIndentSize):
  370. return hook_wrap()
  371. def Write(self, text, style):
  372. System.Console.Write(text)
  373. def WriteLine(self, text, style):
  374. System.Console.WriteLine(text)
  375. IronPythonConsole.PythonCommandLine.MyConsole = IronPythonWrapper()
  376. if __name__ == "__main__":
  377. import sys
  378. import time
  379. c = Console(0)
  380. sys.stdout = c
  381. sys.stderr = c
  382. c.page()
  383. c.pos(5, 10)
  384. c.write("hi there")
  385. c.title("Testing console")
  386. # c.bell()
  387. print()
  388. print("size", c.size())
  389. print(" some printed output")
  390. for i in range(10):
  391. e = c.getkeypress()
  392. print(e.Key, chr(e.KeyChar), ord(e.KeyChar), e.Modifiers)
  393. del c
  394. System.Console.Clear()