more efforts

This commit is contained in:
Anthony Scopatz 2016-10-29 16:17:01 -04:00
parent 076110785b
commit fc012ede84
2 changed files with 155 additions and 26 deletions

View file

@ -218,7 +218,17 @@ class BufferedFDParallelReader:
self.thread.start() self.thread.start()
def populate_console(reader, fd, buffer, chunksize, queue): def _expand_console_buffer(cols, max_offset, expandsize, orig_posize, fd):
# if we are getting close to the end of the console buffer,
# expand it so that we can read from it successfully.
rows = ((max_offset + expandsize)//cols)# + 1
winutils.set_console_screen_buffer_size(cols, rows, fd=fd)
orig_posize = orig_posize[:3] + (rows,)
max_offset = (rows - 1) * cols
return rows, max_offset, orig_posize
def populate_console(reader, fd, buffer, chunksize, queue, expandsize=None):
"""Reads bytes from the file descriptor and puts lines into the queue. """Reads bytes from the file descriptor and puts lines into the queue.
The reads happend in parallel, using xonsh.winutils.read_console_output_character(), The reads happend in parallel, using xonsh.winutils.read_console_output_character(),
and is thus only available on windows. If the read fails for any reason, the reader is and is thus only available on windows. If the read fails for any reason, the reader is
@ -242,33 +252,69 @@ def populate_console(reader, fd, buffer, chunksize, queue):
# care about without a noticible performance hit. # care about without a noticible performance hit.
# b. Even with this huge size, it is still possible to write more lines than # b. Even with this huge size, it is still possible to write more lines than
# this, so we should scroll along with the console. # this, so we should scroll along with the console.
winutils.get_console_screen_buffer_info(1) # Unfortnately, because we do not have control over the terminal emulator,
x, y, cols, lins = posize = winutils.get_position_size(fd) # It is not possible to compute how far back we should set the begining
# read position because we don't know how many characters have been popped
# off the top of the buffer. If we did somehow know this number we could do
# something like the following:
#
# new_offset = (y*cols) + x
# if new_offset == max_offset:
# new_offset -= scolled_offset
# x = new_offset%cols
# y = new_offset//cols
# continue
#
# So this method is imperfect and only works as long as the sceen has
# room to expand to. Thus the trick here is to expand the screen size
# when we get close enough to the end of the screen. There remain some
# async issues related to not being able to set the cursor position.
# but they just affect the alignment / capture of the output of the
# first command run after a screen resize.
if expandsize is None:
expandsize = 100 * chunksize
x, y, cols, rows = posize = winutils.get_position_size(fd)
pre_x = pre_y = -1 pre_x = pre_y = -1
orig_posize = posize orig_posize = posize
offset = (cols*y) + x
max_offset = (rows - 1) * cols
# I believe that there is a bug in PTK that if we reset the
# cursor position, the cursor on the next prompt is accidentally on
# the next line. If this is fixed, uncomment the following line.
#if max_offset < offset + expandsize:
# rows, max_offset, orig_posize = _expand_console_buffer(
# cols, max_offset, expandsize,
# orig_posize, fd)
# winutils.set_console_cursor_position(x, y, fd=fd)
with open('buf.txt', 'w'): with open('buf.txt', 'w'):
pass pass
while True: while True:
posize = winutils.get_position_size(fd) posize = winutils.get_position_size(fd)
if ((posize[1], posize[0]) <= (y, x) and posize[2:] == (cols, lins)) or \ offset = (cols*y) + x
if ((posize[1], posize[0]) <= (y, x) and posize[2:] == (cols, rows)) or \
(pre_x == x and pre_y == y): (pre_x == x and pre_y == y):
# already at the current cursor position. # already at or ahead of the current cursor position.
if reader.closed: if reader.closed:
break break
else: else:
time.sleep(reader.timeout * 10**reader.sleepscale) time.sleep(reader.timeout * 10**reader.sleepscale)
continue continue
elif posize[2:] == (cols, lins): elif max_offset <= offset + expandsize:
rows, max_offset, orig_posize = _expand_console_buffer(cols,
max_offset, expandsize,
orig_posize, fd)
continue
elif posize[2:] == (cols, rows):
# cursor updated but screen size is the same. # cursor updated but screen size is the same.
pass pass
else: else:
# screen size changed, which is offset preserving # screen size changed, which is offset preserving
offset = (cols*y) + x
orig_posize = posize orig_posize = posize
cols, lins = posize[2:] cols, rows = posize[2:]
x = offset % cols x = offset % cols
y = offset // cols y = offset // cols
pre_x = pre_y = -1 pre_x = pre_y = -1
max_offset = (rows - 1) * cols
continue continue
try: try:
buf = winutils.read_console_output_character(x=x, y=y, fd=fd, buf = winutils.read_console_output_character(x=x, y=y, fd=fd,
@ -287,9 +333,9 @@ def populate_console(reader, fd, buffer, chunksize, queue):
cur_offset = (cols*cur_y) + cur_x cur_offset = (cols*cur_y) + cur_x
beg_offset = (cols*y) + x beg_offset = (cols*y) + x
end_offset = beg_offset + nread end_offset = beg_offset + nread
if end_offset > cur_offset: if end_offset > cur_offset and cur_offset != max_offset:
buf = buf[:cur_offset-end_offset] buf = buf[:cur_offset-end_offset]
# convert to lines and add to queue # convert to lines
lines = [buf[:(cols-x)]] lines = [buf[:(cols-x)]]
lines += [buf[l*cols+(cols-x):(l+1)*cols+(cols-x)] lines += [buf[l*cols+(cols-x):(l+1)*cols+(cols-x)]
for l in range((nread//cols) + (1 if nread%cols > 0 else 0))] for l in range((nread//cols) + (1 if nread%cols > 0 else 0))]
@ -297,6 +343,7 @@ def populate_console(reader, fd, buffer, chunksize, queue):
if not lines: if not lines:
time.sleep(reader.timeout * 10**reader.sleepscale) time.sleep(reader.timeout * 10**reader.sleepscale)
continue continue
# put lines in the queue
nl = b'\n' nl = b'\n'
for line in lines[:-1]: for line in lines[:-1]:
queue.put(line.rstrip() + nl) queue.put(line.rstrip() + nl)
@ -305,9 +352,10 @@ def populate_console(reader, fd, buffer, chunksize, queue):
else: else:
queue.put(lines[-1]) queue.put(lines[-1])
with open('buf.txt', 'a+') as f: with open('buf.txt', 'a+') as f:
f.write("{} {} {}\n----------\n".format(x, y, cols)) f.write("{} {} {} {}\n----------\n".format(x, y, cols, rows))
for line in lines: for line in lines:
f.write(repr(line) + '\n') f.write(repr(line) + '\n')
# update x and y locations
if (beg_offset + len(buf))%cols == 0: if (beg_offset + len(buf))%cols == 0:
new_offset = beg_offset + len(buf) new_offset = beg_offset + len(buf)
else: else:

View file

@ -249,8 +249,16 @@ def set_console_mode(mode, fd=1):
hcon = STDHANDLES[fd] hcon = STDHANDLES[fd]
SetConsoleMode(hcon, mode) SetConsoleMode(hcon, mode)
@lazyobject
def COORD():
if platform.has_prompt_toolkit():
# turns out that PTK has a separate ctype wrapper
# for this struct and also wraps similar function calls
# we need to use the same struct to prevent clashes.
import prompt_toolkit.win32_types
return prompt_toolkit.win32_types.COORD
class COORD(ctypes.Structure): class _COORD(ctypes.Structure):
"""Struct from the winapi, representing coordinates in the console. """Struct from the winapi, representing coordinates in the console.
Attributes Attributes
@ -263,6 +271,8 @@ class COORD(ctypes.Structure):
_fields_ = [("X", SHORT), _fields_ = [("X", SHORT),
("Y", SHORT)] ("Y", SHORT)]
return _COORD
@lazyobject @lazyobject
def ReadConsoleOutputCharacterA(): def ReadConsoleOutputCharacterA():
rcoc = ctypes.windll.kernel32.ReadConsoleOutputCharacterA rcoc = ctypes.windll.kernel32.ReadConsoleOutputCharacterA
@ -415,6 +425,9 @@ def get_console_screen_buffer_info(fd=1):
GetConsoleScreenBufferInfo(hcon, byref(csbi)) GetConsoleScreenBufferInfo(hcon, byref(csbi))
return csbi return csbi
#
# end colorama forked section
#
def get_cursor_position(fd=1): def get_cursor_position(fd=1):
"""Gets the current cursor position as an (x, y) tuple.""" """Gets the current cursor position as an (x, y) tuple."""
@ -438,3 +451,71 @@ def get_position_size(fd=1):
info = get_console_screen_buffer_info(fd) info = get_console_screen_buffer_info(fd)
return (info.dwCursorPosition.X, info.dwCursorPosition.Y, return (info.dwCursorPosition.X, info.dwCursorPosition.Y,
info.dwSize.X, info.dwSize.Y) info.dwSize.X, info.dwSize.Y)
@lazyobject
def SetConsoleScreenBufferSize():
"""Set screen buffer dimensions."""
scsbs = ctypes.windll.kernel32.SetConsoleScreenBufferSize
scsbs.errcheck = check_zero
scsbs.argtypes = (
HANDLE, # _In_ HANDLE hConsoleOutput
COORD, # _In_ COORD dwSize
)
scsbs.restype = BOOL
return scsbs
def set_console_screen_buffer_size(x, y, fd=1):
"""Sets the console size for a standard buffer.
Parameters
----------
x : int
Number of columns.
y : int
Number of rows.
fd : int, optional
Standard buffer file descriptor, 0 for stdin, 1 for stdout (default),
and 2 for stderr.
"""
coord = COORD()
coord.X = x
coord.Y = y
hcon = STDHANDLES[fd]
rtn = SetConsoleScreenBufferSize(hcon, coord)
return rtn
@lazyobject
def SetConsoleCursorPosition():
"""Set cursor position in console."""
sccp = ctypes.windll.kernel32.SetConsoleCursorPosition
sccp.errcheck = check_zero
sccp.argtypes = (
HANDLE, # _In_ HANDLE hConsoleOutput
COORD, # _In_ COORD dwCursorPosition
)
sccp.restype = BOOL
return sccp
def set_console_cursor_position(x, y, fd=1):
"""Sets the console cursor position for a standard buffer.
Parameters
----------
x : int
Number of columns.
y : int
Number of rows.
fd : int, optional
Standard buffer file descriptor, 0 for stdin, 1 for stdout (default),
and 2 for stderr.
"""
coord = COORD()
coord.X = x
coord.Y = y
hcon = STDHANDLES[fd]
rtn = SetConsoleCursorPosition(hcon, coord)
return rtn