def test_exception(self):
        c = CodeRunner(request_refresh=lambda: self.orig_stdout.flush() or
                       self.orig_stderr.flush())

        def ctrlc():
            raise KeyboardInterrupt()

        stdout = FakeOutput(c, lambda x: ctrlc())
        stderr = FakeOutput(c, lambda *args, **kwargs: None)
        sys.stdout = stdout
        sys.stderr = stderr
        c.load_code('1 + 1')
        c.run_code()
Exemplo n.º 2
0
 def test_simple(self):
     c = CodeRunner(request_refresh=lambda: self.orig_stdout.flush() or self
                    .orig_stderr.flush())
     stdout = FakeOutput(c, lambda *args, **kwargs: None, None)
     stderr = FakeOutput(c, lambda *args, **kwargs: None, None)
     sys.stdout = stdout
     sys.stdout = stderr
     c.load_code("1 + 1")
     c.run_code()
     c.run_code()
     c.run_code()
 def test_simple(self):
     c = CodeRunner(request_refresh=lambda: self.orig_stdout.flush() or
                    self.orig_stderr.flush())
     stdout = FakeOutput(c, lambda *args, **kwargs: None)
     stderr = FakeOutput(c, lambda *args, **kwargs: None)
     sys.stdout = stdout
     sys.stdout = stderr
     c.load_code('1 + 1')
     c.run_code()
     c.run_code()
     c.run_code()
Exemplo n.º 4
0
    def test_exception(self):
        c = CodeRunner(request_refresh=lambda: self.orig_stdout.flush() or self
                       .orig_stderr.flush())

        def ctrlc():
            raise KeyboardInterrupt()

        stdout = FakeOutput(c, lambda x: ctrlc(), None)
        stderr = FakeOutput(c, lambda *args, **kwargs: None, None)
        sys.stdout = stdout
        sys.stderr = stderr
        c.load_code("1 + 1")
        c.run_code()
Exemplo n.º 5
0
    def __init__(self, locals_=None, config=None, request_refresh=lambda: None, banner=None, interp=None):
        """
        locals_ is a mapping of locals to pass into the interpreter
        config is a bpython config.Struct with config attributes
        request_refresh is a function that will be called when the Repl
            wants to refresh the display, but wants control returned to it afterwards
        banner is a string to display briefly in the status bar
        interp is an interpreter to use
        """

        logging.debug("starting init")

        if config is None:
            config = Struct()
            loadini(config, default_config_path())

        self.weak_rewind = bool(locals_ or interp) # If creating a new interpreter on undo
                                                   # would be unsafe because initial
                                                   # state was passed in
        if interp is None:
            interp = code.InteractiveInterpreter(locals=locals_)
        if banner is None:
            banner = _('welcome to bpython')
        config.autocomplete_mode = SIMPLE # only one implemented currently
        if config.cli_suggestion_width <= 0 or config.cli_suggestion_width > 1:
            config.cli_suggestion_width = 1

        self.reevaluating = False
        self.fake_refresh_requested = False
        def smarter_request_refresh():
            if self.reevaluating or self.paste_mode:
                self.fake_refresh_requested = True
            else:
                request_refresh()
        self.request_refresh = smarter_request_refresh

        self.status_bar = StatusBar(banner if config.curtsies_fill_terminal else '', _(
            " <%s> Rewind  <%s> Save  <%s> Pastebin <%s> Editor"
            ) % (config.undo_key, config.save_key, config.pastebin_key, config.external_editor_key),
            refresh_request=self.request_refresh
            )
        self.rl_char_sequences = get_updated_char_sequences(key_dispatch, config)
        logging.debug("starting parent init")
        super(Repl, self).__init__(interp, config)
        self.formatter = BPythonFormatter(config.color_scheme)
        self.interact = self.status_bar # overwriting what bpython.Repl put there
                                        # interact is called to interact with the status bar,
                                        # so we're just using the same object
        self._current_line = '' # line currently being edited, without '>>> '
        self.current_stdouterr_line = '' # current line of output - stdout and stdin go here
        self.display_lines = [] # lines separated whenever logical line
                                # length goes over what the terminal width
                                # was at the time of original output
        self.history = [] # this is every line that's been executed;
                          # it gets smaller on rewind
        self.display_buffer = [] # formatted version of lines in the buffer
                                 # kept around so we can unhighlight parens
                                 # using self.reprint_line as called by
                                 # bpython.Repl
        self.scroll_offset = 0 # how many times display has been scrolled down
                               # because there wasn't room to display everything
        self.cursor_offset_in_line = 0 # from the left, 0 means first char

        self.coderunner = CodeRunner(self.interp, self.request_refresh)
        self.stdout = FakeOutput(self.coderunner, self.send_to_stdout)
        self.stderr = FakeOutput(self.coderunner, self.send_to_stderr)
        self.stdin = FakeStdin(self.coderunner, self)

        self.request_paint_to_clear_screen = False # next paint should clear screen
        self.last_events = [None] * 50
        self.presentation_mode = False
        self.paste_mode = False

        self.width = None  # will both be set by a window resize event
        self.height = None
Exemplo n.º 6
0
class Repl(BpythonRepl):
    """Python Repl

    Reacts to events like
     -terminal dimensions and change events
     -keystrokes
    Behavior altered by
     -number of scroll downs that were necessary to render array after each display
     -initial cursor position
    outputs:
     -2D array to be rendered

    Repl is mostly view-independent state of Repl - but self.width and self.height
    are important for figuring out how to wrap lines for example.
    Usually self.width and self.height should be set by receiving a window resize event,
    not manually set to anything - as long as the first event received is a window
    resize event, this works fine.
    """

    ## initialization, cleanup
    def __init__(self, locals_=None, config=None, request_refresh=lambda: None, banner=None, interp=None):
        """
        locals_ is a mapping of locals to pass into the interpreter
        config is a bpython config.Struct with config attributes
        request_refresh is a function that will be called when the Repl
            wants to refresh the display, but wants control returned to it afterwards
        banner is a string to display briefly in the status bar
        interp is an interpreter to use
        """

        logging.debug("starting init")

        if config is None:
            config = Struct()
            loadini(config, default_config_path())

        self.weak_rewind = bool(locals_ or interp) # If creating a new interpreter on undo
                                                   # would be unsafe because initial
                                                   # state was passed in
        if interp is None:
            interp = code.InteractiveInterpreter(locals=locals_)
        if banner is None:
            banner = _('welcome to bpython')
        config.autocomplete_mode = SIMPLE # only one implemented currently
        if config.cli_suggestion_width <= 0 or config.cli_suggestion_width > 1:
            config.cli_suggestion_width = 1

        self.reevaluating = False
        self.fake_refresh_requested = False
        def smarter_request_refresh():
            if self.reevaluating or self.paste_mode:
                self.fake_refresh_requested = True
            else:
                request_refresh()
        self.request_refresh = smarter_request_refresh

        self.status_bar = StatusBar(banner if config.curtsies_fill_terminal else '', _(
            " <%s> Rewind  <%s> Save  <%s> Pastebin <%s> Editor"
            ) % (config.undo_key, config.save_key, config.pastebin_key, config.external_editor_key),
            refresh_request=self.request_refresh
            )
        self.rl_char_sequences = get_updated_char_sequences(key_dispatch, config)
        logging.debug("starting parent init")
        super(Repl, self).__init__(interp, config)
        self.formatter = BPythonFormatter(config.color_scheme)
        self.interact = self.status_bar # overwriting what bpython.Repl put there
                                        # interact is called to interact with the status bar,
                                        # so we're just using the same object
        self._current_line = '' # line currently being edited, without '>>> '
        self.current_stdouterr_line = '' # current line of output - stdout and stdin go here
        self.display_lines = [] # lines separated whenever logical line
                                # length goes over what the terminal width
                                # was at the time of original output
        self.history = [] # this is every line that's been executed;
                          # it gets smaller on rewind
        self.display_buffer = [] # formatted version of lines in the buffer
                                 # kept around so we can unhighlight parens
                                 # using self.reprint_line as called by
                                 # bpython.Repl
        self.scroll_offset = 0 # how many times display has been scrolled down
                               # because there wasn't room to display everything
        self.cursor_offset_in_line = 0 # from the left, 0 means first char

        self.coderunner = CodeRunner(self.interp, self.request_refresh)
        self.stdout = FakeOutput(self.coderunner, self.send_to_stdout)
        self.stderr = FakeOutput(self.coderunner, self.send_to_stderr)
        self.stdin = FakeStdin(self.coderunner, self)

        self.request_paint_to_clear_screen = False # next paint should clear screen
        self.last_events = [None] * 50
        self.presentation_mode = False
        self.paste_mode = False

        self.width = None  # will both be set by a window resize event
        self.height = None

    def __enter__(self):
        self.orig_stdout = sys.stdout
        self.orig_stderr = sys.stderr
        self.orig_stdin = sys.stdin
        sys.stdout = self.stdout
        sys.stderr = self.stderr
        sys.stdin = self.stdin
        return self

    def __exit__(self, *args):
        sys.stdin = self.orig_stdin
        sys.stdout = self.orig_stdout
        sys.stderr = self.orig_stderr

    def clean_up_current_line_for_exit(self):
        """Called when trying to exit to prep for final paint"""
        logging.debug('unhighlighting paren for exit')
        self.cursor_offset_in_line = -1
        self.unhighlight_paren()

    ## Event handling
    def process_event(self, e):
        """Returns True if shutting down, otherwise returns None.
        Mostly mutates state of Repl object"""
        # event names uses here are curses compatible, or the full names
        # for a full list of what should have pretty names, see curtsies.events.CURSES_TABLE

        if not isinstance(e, events.Event):
            self.last_events.append(e)
            self.last_events.pop(0)

        logging.debug("processing event %r", e)
        if isinstance(e, events.RefreshRequestEvent):
            if self.status_bar.has_focus:
                self.status_bar.process_event(e)
            else:
                assert self.coderunner.code_is_waiting
                self.run_code_and_maybe_finish()
        elif isinstance(e, events.WindowChangeEvent):
            logging.debug('window change to %d %d', e.width, e.height)
            self.scroll_offset -= e.cursor_dy
            self.width, self.height = e.width, e.height

        elif self.status_bar.has_focus:
            return self.status_bar.process_event(e)

        # handles paste events for both stdin and repl
        elif isinstance(e, events.PasteEvent):
            ctrl_char = compress_paste_event(e)
            if ctrl_char is not None:
                return self.process_event(ctrl_char)
            with self.in_paste_mode():
                for ee in e.events:
                    if self.stdin.has_focus:
                        self.stdin.process_event(ee)
                    else:
                        self.process_simple_event(ee)
            self.update_completion()

        elif self.stdin.has_focus:
            return self.stdin.process_event(e)

        elif isinstance(e, events.SigIntEvent):
            logging.debug('received sigint event')
            self.keyboard_interrupt()
            self.update_completion()
            return

        elif e in self.rl_char_sequences:
            self.cursor_offset_in_line, self._current_line = self.rl_char_sequences[e](self.cursor_offset_in_line, self._current_line)
            self.update_completion()

        # readline history commands
        elif e in ("KEY_UP",) + key_dispatch[self.config.up_one_line_key]:
            self.rl_history.enter(self._current_line)
            self._current_line = self.rl_history.back(False)
            self.cursor_offset_in_line = len(self._current_line)
            self.update_completion()
        elif e in ("KEY_DOWN",) + key_dispatch[self.config.down_one_line_key]:
            self.rl_history.enter(self._current_line)
            self._current_line = self.rl_history.forward(False)
            self.cursor_offset_in_line = len(self._current_line)
            self.update_completion()
        elif e in key_dispatch[self.config.search_key]: #TODO Not Implemented
            pass
        #TODO add rest of history commands

        # Need to figure out what these are, but I think they belong in manual_realine
        # under slightly different names
        elif e in key_dispatch[self.config.cut_to_buffer_key]: #TODO Not Implemented
            pass
        elif e in key_dispatch[self.config.yank_from_buffer_key]: #TODO Not Implemented
            pass

        elif e in key_dispatch[self.config.clear_screen_key]:
            self.request_paint_to_clear_screen = True
        elif e in key_dispatch[self.config.last_output_key]: #TODO Not Implemented
            pass
        elif e in key_dispatch[self.config.show_source_key]: #TODO Not Implemented
            pass
        elif e in key_dispatch[self.config.suspend_key]:
            raise SystemExit()
        elif e in ("",) + key_dispatch[self.config.exit_key]:
            if self._current_line == '':
                raise SystemExit()
        elif e in ("\n", "\r", "PAD_ENTER"):
            self.on_enter()
            self.update_completion()
        elif e == '\t': # tab
            self.on_tab()
        elif e in ("KEY_BTAB",): # shift-tab
            self.on_tab(back=True)
        elif e in key_dispatch[self.config.undo_key]: #ctrl-r for undo
            self.undo()
            self.update_completion()
        elif e in key_dispatch[self.config.save_key]: # ctrl-s for save
            g = greenlet.greenlet(self.write2file)
            g.switch()
        # F8 for pastebin
        elif e in key_dispatch[self.config.pastebin_key]:
            g = greenlet.greenlet(self.pastebin)
            g.switch()
        elif e in key_dispatch[self.config.external_editor_key]:
            self.send_session_to_external_editor()
        #TODO add PAD keys hack as in bpython.cli
        elif e in ["\x18"]:
            self.send_current_block_to_external_editor()
        elif e in ["\x1b"]: #ESC
            pass
        else:
            self.add_normal_character(e)
            self.update_completion()

    def on_enter(self, insert_into_history=True):
        self.cursor_offset_in_line = -1 # so the cursor isn't touching a paren
        self.unhighlight_paren()        # in unhighlight_paren
        self.highlighted_paren = None

        self.rl_history.append(self._current_line)
        self.rl_history.last()
        self.history.append(self._current_line)
        line = self._current_line
        #self._current_line = ''
        self.push(line, insert_into_history=insert_into_history)

    def on_tab(self, back=False):
        """Do something on tab key
        taken from bpython.cli

        Does one of the following:
        1) add space to move up to the next %4==0 column
        2) complete the current word with characters common to all completions and
        3) select the first or last match
        4) select the next or previous match if already have a match
        """

        def only_whitespace_left_of_cursor():
            """returns true if all characters on current line before cursor are whitespace"""
            return self._current_line[:self.cursor_offset_in_line].strip()

        logging.debug('self.matches: %r', self.matches)
        if not only_whitespace_left_of_cursor():
            front_white = (len(self._current_line[:self.cursor_offset_in_line]) -
                len(self._current_line[:self.cursor_offset_in_line].lstrip()))
            to_add = 4 - (front_white % self.config.tab_length)
            for _ in range(to_add):
                self.add_normal_character(' ')
            return

        #TODO I'm not sure what's going on in the next 10 lines, particularly list_win_visible
        # get the (manually typed or common-sequence completed from manually typed) current word
        if self.matches_iter:
            cw = self.matches_iter.current_word
        else:
            self.complete(tab=True) #TODO why do we call this here?
            if not self.config.auto_display_list and not self.list_win_visible:
                return True #TODO why?
            cw = self.current_string() or self.current_word
            logging.debug('current string: %r', self.current_string())
            logging.debug('current word: %r', self.current_word)
            if not cw:
                return

        # check to see if we can expand the current word
        cseq = os.path.commonprefix(self.matches)
        expanded_string = cseq[len(cw):]
        if expanded_string:
            self.current_word = cw + expanded_string #asdf
            self.matches_iter.update(cseq, self.matches)
            return

        if self.matches:
            self.current_word = (self.matches_iter.previous()
                                 if back else self.matches_iter.next())

    def process_simple_event(self, e):
        if e in ("\n", "\r", "PAD_ENTER"):
            self.on_enter()
            while self.fake_refresh_requested:
                self.fake_refresh_requested = False
                self.process_event(events.RefreshRequestEvent())
        elif isinstance(e, events.Event):
            pass # ignore events
        else:
            self.add_normal_character(e)

    def send_current_block_to_external_editor(self, filename=None):
        text = self.send_to_external_editor(self.get_current_block())
        lines = [line for line in text.split('\n')]
        while lines and not lines[-1].split():
            lines.pop()
        events = '\n'.join(lines + ([''] if len(lines) == 1 else ['', '']))
        self.clear_current_block()
        with self.in_paste_mode():
            for e in events:
                self.process_simple_event(e)
        self._current_line = ''
        self.cursor_offset_in_line = len(self._current_line)

    def send_session_to_external_editor(self, filename=None):
        for_editor = '### current bpython session - file will be reevaluated, ### lines will not be run\n'.encode('utf8')
        for_editor += ('\n'.join(line[4:] if line[:4] in ('... ', '>>> ') else '### '+line
                       for line in self.getstdout().split('\n')).encode('utf8'))
        text = self.send_to_external_editor(for_editor)
        lines = text.split('\n')
        self.history = [line for line in lines if line[:4] != '### ']
        self.reevaluate(insert_into_history=True)
        self._current_line = lines[-1][4:]
        self.cursor_offset_in_line = len(self._current_line)

    ## Handler Helpers
    def add_normal_character(self, char):
        if len(char) > 1 or is_nop(char):
            return
        self._current_line = (self._current_line[:self.cursor_offset_in_line] +
                             char +
                             self._current_line[self.cursor_offset_in_line:])
        self.cursor_offset_in_line += 1
        if self.config.cli_trim_prompts and self._current_line.startswith(">>> "):
            self._current_line = self._current_line[4:]
            self.cursor_offset_in_line = max(0, self.cursor_offset_in_line - 4)

    def update_completion(self, tab=False):
        """Update autocomplete info; self.matches and self.argspec"""

        if self.list_win_visible and not self.config.auto_display_list:
            self.list_win_visible = False
            self.matches_iter.update(self.current_word)
            return

        if self.config.auto_display_list or tab:
            self.list_win_visible = BpythonRepl.complete(self, tab)

    def push(self, line, insert_into_history=True):
        """Push a line of code onto the buffer, start running the buffer

        If the interpreter successfully runs the code, clear the buffer
        """
        if self.paste_mode:
            self.saved_indent = 0
        else:
            indent = len(re.match(r'[ ]*', line).group())
            if line.endswith(':'):
                indent = max(0, indent + self.config.tab_length)
            elif line and line.count(' ') == len(line):
                indent = max(0, indent - self.config.tab_length)
            elif line and ':' not in line and line.strip().startswith(('return', 'pass', 'raise', 'yield')):
                indent = max(0, indent - self.config.tab_length)
            self.saved_indent = indent

        #current line not added to display buffer if quitting #TODO I don't understand this comment
        if self.config.syntax:
            display_line = bpythonparse(format(self.tokenize(line), self.formatter))
            # careful: self.tokenize requires that the line not be in self.buffer yet!

            logging.debug('display line being pushed to buffer: %r -> %r', line, display_line)
            self.display_buffer.append(display_line)
        else:
            self.display_buffer.append(fmtstr(line))

        if insert_into_history:
            self.insert_into_history(line)
        self.buffer.append(line)

        code_to_run = '\n'.join(self.buffer)

        logging.debug('running %r in interpreter', self.buffer)
        try:
            c = bool(code.compile_command('\n'.join(self.buffer)))
            self.saved_predicted_parse_error = False
        except (ValueError, SyntaxError, OverflowError):
            c = self.saved_predicted_parse_error = True
        if c:
            logging.debug('finished - buffer cleared')
            self.display_lines.extend(self.display_buffer_lines)
            self.display_buffer = []
            self.buffer = []
            self.cursor_offset_in_line = 0

        self.coderunner.load_code(code_to_run)
        self.run_code_and_maybe_finish()

    def run_code_and_maybe_finish(self, for_code=None):
        r = self.coderunner.run_code(for_code=for_code)
        if r:
            logging.debug("----- Running finish command stuff -----")
            logging.debug("saved_indent: %r", self.saved_indent)
            err = self.saved_predicted_parse_error
            self.saved_predicted_parse_error = False

            indent = self.saved_indent
            if err:
                indent = 0

            #TODO This should be printed ABOVE the error that just happened instead
            # or maybe just thrown away and not shown
            if self.current_stdouterr_line:
                self.display_lines.extend(paint.display_linize(self.current_stdouterr_line, self.width))
                self.current_stdouterr_line = ''

            self._current_line = ' '*indent
            self.cursor_offset_in_line = len(self._current_line)

    def keyboard_interrupt(self):
        #TODO factor out the common cleanup from running a line
        self.cursor_offset_in_line = -1
        self.unhighlight_paren()
        self.display_lines.extend(self.display_buffer_lines)
        self.display_lines.extend(paint.display_linize(self.current_cursor_line, self.width))
        self.display_lines.extend(paint.display_linize("KeyboardInterrupt", self.width))
        self.clear_current_block(remove_from_history=False)

    def unhighlight_paren(self):
        """modify line in self.display_buffer to unhighlight a paren if possible

        self.highlighted_paren should be a line in ?
        """
        if self.highlighted_paren is not None and self.config.syntax:
            lineno, saved_tokens = self.highlighted_paren
            if lineno == len(self.display_buffer):
                # then this is the current line, so don't worry about it
                return
            self.highlighted_paren = None
            logging.debug('trying to unhighlight a paren on line %r', lineno)
            logging.debug('with these tokens: %r', saved_tokens)
            new = bpythonparse(format(saved_tokens, self.formatter))
            self.display_buffer[lineno] = self.display_buffer[lineno].setslice_with_length(0, len(new), new, len(self.display_buffer[lineno]))

    def clear_current_block(self, remove_from_history=True):
        self.display_buffer = []
        if remove_from_history:
            [self.history.pop() for _ in self.buffer]
        self.buffer = []
        self.cursor_offset_in_line = 0
        self.saved_indent = 0
        self._current_line = ''
        self.cursor_offset_in_line = len(self._current_line)

    def get_current_block(self):
        return '\n'.join(self.buffer + [self._current_line])

    def send_to_stdout(self, output):
        lines = output.split('\n')
        logging.debug('display_lines: %r', self.display_lines)
        self.current_stdouterr_line += lines[0]
        if len(lines) > 1:
            self.display_lines.extend(paint.display_linize(self.current_stdouterr_line, self.width, blank_line=True))
            self.display_lines.extend(sum([paint.display_linize(line, self.width, blank_line=True) for line in lines[1:-1]], []))
            self.current_stdouterr_line = lines[-1]
        logging.debug('display_lines: %r', self.display_lines)

    def send_to_stderr(self, error):
        #self.send_to_stdout(error)
        self.display_lines.extend([func_for_letter(self.config.color_scheme['error'])(line)
                                   for line in sum([paint.display_linize(line, self.width)
                                                    for line in error.split('\n')], [])])

    def send_to_stdin(self, line):
        if line.endswith('\n'):
            self.display_lines.extend(paint.display_linize(self.current_output_line, self.width))
            self.current_output_line = ''
        #self.display_lines = self.display_lines[:len(self.display_lines) - self.stdin.old_num_lines]
        #lines = paint.display_linize(line, self.width)
        #self.stdin.old_num_lines = len(lines)
        #self.display_lines.extend(paint.display_linize(line, self.width))
        pass


    ## formatting, output
    @property
    def done(self):
        """Whether the last block is complete - which prompt to use, ps1 or ps2"""
        return not self.buffer

    @property
    def current_line_formatted(self):
        """The colored current line (no prompt, not wrapped)"""
        if self.config.syntax:
            fs = bpythonparse(format(self.tokenize(self._current_line), self.formatter))
            logging.debug('Display line %r -> %r', self._current_line, fs)
        else:
            fs = fmtstr(self._current_line)
        if hasattr(self, 'old_fs') and str(fs) != str(self.old_fs):
            pass
            #logging.debug('calculating current formatted line: %r', repr(fs))
        self.old_fs = fs
        return fs

    @property
    def lines_for_display(self):
        """All display lines (wrapped, colored, with prompts)"""
        return self.display_lines + self.display_buffer_lines

    @property
    def current_word(self):
        """Returns the "current word", based on what's directly left of the cursor.
        examples inclue "socket.socket.metho" or "self.reco" or "yiel"

        cw() is currently an alias, but cw() is used by bpyton.repl.Repl
        so must match its definition of current word - changing how it behaves
        has many repercussions.
        """

        start, end, word = self._get_current_word()
        return word

    def _get_current_word(self):
        pos = self.cursor_offset_in_line

        matches = list(re.finditer(r'''[\w_][\w0-9._\[\]']*[(]?''', self._current_line))
        start = pos
        end = pos
        word = None
        for m in matches:
            if m.start() < pos and m.end() >= pos:
                start = m.start()
                end = m.end()
                word = m.group()
        return (start, end, word)

    @current_word.setter
    def current_word(self, value):
        # current word means word cursor is at the end of
        start, end, word = self._get_current_word()

        self._current_line = self._current_line[:start] + value + self._current_line[end:]
        self.cursor_offset_in_line = start + len(value)

    @property
    def display_buffer_lines(self):
        """The display lines (wrapped, colored, with prompts) for the current buffer"""
        lines = []
        for display_line in self.display_buffer:
            display_line = (func_for_letter(self.config.color_scheme['prompt_more'])(self.ps2)
                           if lines else
                           func_for_letter(self.config.color_scheme['prompt'])(self.ps1)) + display_line
            for line in paint.display_linize(display_line, self.width):
                lines.append(line)
        return lines

    @property
    def display_line_with_prompt(self):
        """colored line with prompt"""
        return (func_for_letter(self.config.color_scheme['prompt'])(self.ps1)
                if self.done else
                func_for_letter(self.config.color_scheme['prompt_more'])(self.ps2)) + self.current_line_formatted

    @property
    def current_cursor_line(self):
        """Current line, either output/input or Python prompt + code"""
        value = (self.current_output_line +
                ('' if self.coderunner.running else self.display_line_with_prompt))
        logging.debug('current cursor line: %r', value)
        return value

    @property
    def current_output_line(self):
        """line of output currently being written, and stdin typed"""
        return self.current_stdouterr_line + self.stdin.current_line

    @current_output_line.setter
    def current_output_line(self, value):
        self.current_stdouterr_line = ''
        self.stdin.current_line = '\n'

    def paint(self, about_to_exit=False, user_quit=False):
        """Returns an array of min_height or more rows and width columns, plus cursor position

        Paints the entire screen - ideally the terminal display layer will take a diff and only
        write to the screen in portions that have changed, but the idea is that we don't need
        to worry about that here, instead every frame is completely redrawn because
        less state is cool!
        """
        # The hairiest function in the curtsies - a cleanup would be great.

        if about_to_exit:
            self.clean_up_current_line_for_exit() # exception to not changing state!

        width, min_height = self.width, self.height
        show_status_bar = bool(self.status_bar._message) or (self.config.curtsies_fill_terminal or self.status_bar.has_focus)
        if show_status_bar:
            min_height -= 1

        current_line_start_row = len(self.lines_for_display) - max(0, self.scroll_offset)
        if self.request_paint_to_clear_screen: # or show_status_bar and about_to_exit ?
            self.request_paint_to_clear_screen = False
            if self.config.curtsies_fill_terminal: #TODO clean up this logic - really necessary check?
                arr = FSArray(self.height - 1 + current_line_start_row, width)
            else:
                arr = FSArray(self.height + current_line_start_row, width)
        else:
            arr = FSArray(0, width)
        #TODO test case of current line filling up the whole screen (there aren't enough rows to show it)

        if current_line_start_row < 0: #if current line trying to be drawn off the top of the screen
            logging.debug('#<---History contiguity broken by rewind--->')
            msg = "#<---History contiguity broken by rewind--->"
            arr[0, 0:min(len(msg), width)] = [msg[:width]]

            # move screen back up a screen minus a line
            while current_line_start_row < 0:
                self.scroll_offset = self.scroll_offset - self.height
                current_line_start_row = len(self.lines_for_display) - max(-1, self.scroll_offset)

            history = paint.paint_history(max(0, current_line_start_row - 1), width, self.lines_for_display)
            arr[1:history.height+1,:history.width] = history

            if arr.height <= min_height:
                arr[min_height, 0] = ' ' # force scroll down to hide broken history message
        else:
            history = paint.paint_history(current_line_start_row, width, self.lines_for_display)
            arr[:history.height,:history.width] = history

        current_line = paint.paint_current_line(min_height, width, self.current_cursor_line)
        if user_quit: # quit() or exit() in interp
            current_line_start_row = current_line_start_row - current_line.height
        logging.debug("---current line row slice %r, %r", current_line_start_row, current_line_start_row + current_line.height)
        logging.debug("---current line col slice %r, %r", 0, current_line.width)
        arr[current_line_start_row:current_line_start_row + current_line.height,
            0:current_line.width] = current_line

        if current_line.height > min_height:
            return arr, (0, 0) # short circuit, no room for infobox

        lines = paint.display_linize(self.current_cursor_line+'X', width)
                                       # extra character for space for the cursor
        current_line_end_row = current_line_start_row + len(lines) - 1

        if self.stdin.has_focus:
            cursor_row, cursor_column = divmod(len(self.current_stdouterr_line) + self.stdin.cursor_offset_in_line, width)
            assert cursor_column >= 0, cursor_column
        elif self.coderunner.running: #TODO does this ever happen?
            cursor_row, cursor_column = divmod(len(self.current_cursor_line) + self.cursor_offset_in_line, width)
            assert cursor_column >= 0, (cursor_column, len(self.current_cursor_line), len(self._current_line), self.cursor_offset_in_line)
        else:
            cursor_row, cursor_column = divmod(len(self.current_cursor_line) - len(self._current_line) + self.cursor_offset_in_line, width)
            assert cursor_column >= 0, (cursor_column, len(self.current_cursor_line), len(self._current_line), self.cursor_offset_in_line)
        cursor_row += current_line_start_row

        if self.list_win_visible:
            logging.debug('infobox display code running')
            visible_space_above = history.height
            visible_space_below = min_height - current_line_end_row - 1

            info_max_rows = max(visible_space_above, visible_space_below)
            infobox = paint.paint_infobox(info_max_rows, int(width * self.config.cli_suggestion_width), self.matches, self.argspec, self.current_word, self.docstring, self.config)

            if visible_space_above >= infobox.height and self.config.curtsies_list_above:
                arr[current_line_start_row - infobox.height:current_line_start_row, 0:infobox.width] = infobox
            else:
                arr[current_line_end_row + 1:current_line_end_row + 1 + infobox.height, 0:infobox.width] = infobox
                logging.debug('slamming infobox of shape %r into arr of shape %r', infobox.shape, arr.shape)

        logging.debug('about to exit: %r', about_to_exit)
        if show_status_bar:
            if self.config.curtsies_fill_terminal:
                if about_to_exit:
                    arr[max(arr.height, min_height), :] = FSArray(1, width)
                else:
                    arr[max(arr.height, min_height), :] = paint.paint_statusbar(1, width, self.status_bar.current_line, self.config)

                    if self.presentation_mode:
                        rows = arr.height
                        columns = arr.width
                        last_key_box = paint.paint_last_events(rows, columns, [events.pp_event(x) for x in self.last_events if x])
                        arr[arr.height-last_key_box.height:arr.height, arr.width-last_key_box.width:arr.width] = last_key_box
            else:
                statusbar_row = min_height + 1 if arr.height == min_height else arr.height
                if about_to_exit:
                    arr[statusbar_row, :] = FSArray(1, width)
                else:
                    arr[statusbar_row, :] = paint.paint_statusbar(1, width, self.status_bar.current_line, self.config)

        if self.config.color_scheme['background'] not in ('d', 'D'):
            for r in range(arr.height):
                arr[r] = fmtstr(arr[r], bg=color_for_letter(self.config.color_scheme['background']))
        logging.debug('returning arr of size %r', arr.shape)
        logging.debug('cursor pos: %r', (cursor_row, cursor_column))
        return arr, (cursor_row, cursor_column)

    @contextlib.contextmanager
    def in_paste_mode(self):
        orig_value = self.paste_mode
        self.paste_mode = True
        yield
        self.paste_mode = orig_value

    ## Debugging shims, good example of embedding a Repl in other code
    def dumb_print_output(self):
        arr, cpos = self.paint()
        arr[cpos[0]:cpos[0]+1, cpos[1]:cpos[1]+1] = ['~']
        def my_print(msg):
            self.orig_stdout.write(str(msg)+'\n')
        my_print('X'*(self.width+8))
        my_print(' use "/" for enter '.center(self.width+8, 'X'))
        my_print(' use "\\" for rewind '.center(self.width+8, 'X'))
        my_print(' use "|" to raise an error '.center(self.width+8, 'X'))
        my_print(' use "$" to pastebin '.center(self.width+8, 'X'))
        my_print(' "~" is the cursor '.center(self.width+8, 'X'))
        my_print('X'*(self.width+8))
        my_print('X``'+('`'*(self.width+2))+'``X')
        for line in arr:
            my_print('X```'+line.ljust(self.width)+'```X')
        logging.debug('line:')
        logging.debug(repr(line))
        my_print('X``'+('`'*(self.width+2))+'``X')
        my_print('X'*(self.width+8))
        return max(len(arr) - self.height, 0)

    def dumb_input(self, requested_refreshes=[]):
        chars = list(self.orig_stdin.readline()[:-1])
        while chars or requested_refreshes:
            if requested_refreshes:
                requested_refreshes.pop()
                self.process_event(events.RefreshRequestEvent())
                continue
            c = chars.pop(0)
            if c in '/':
                c = '\n'
            elif c in '\\':
                c = ''
            elif c in '|':
                def r(): raise Exception('errors in other threads should look like this')
                t = threading.Thread(target=r)
                t.daemon = True
                t.start()
            elif c in '$':
                c = key_dispatch[self.config.pastebin_key][0]
            self.process_event(c)

    def __repr__(self):
        s = ''
        s += '<Repl\n'
        s += " cursor_offset_in_line:" + repr(self.cursor_offset_in_line) + '\n'
        s += " num display lines:" + repr(len(self.display_lines)) + '\n'
        s += " lines scrolled down:" + repr(self.scroll_offset) + '\n'
        s += '>'
        return s

    ## Provided for bpython.repl.Repl
    def current_line(self):
        """Returns the current line"""
        return self._current_line
    def echo(self, msg, redraw=True):
        """
        Notification that redrawing the current line is necessary (we don't
        care, since we always redraw the whole screen)

        Supposed to parse and echo a formatted string with appropriate attributes.
        It's not supposed to update the screen if it's reevaluating the code (as it
        does with undo)."""
        logging.debug("echo called with %r" % msg)
    def cw(self):
        """Returns the "current word", based on what's directly left of the cursor.
        examples inclue "socket.socket.metho" or "self.reco" or "yiel" """
        return self.current_word
    @property
    def cpos(self):
        "many WATs were had - it's the pos from the end of the line back"""
        return len(self._current_line) - self.cursor_offset_in_line
    def reprint_line(self, lineno, tokens):
        logging.debug("calling reprint line with %r %r", lineno, tokens)
        if self.config.syntax:
            self.display_buffer[lineno] = bpythonparse(format(tokens, self.formatter))
    def reevaluate(self, insert_into_history=False):
        """bpython.Repl.undo calls this"""
        old_logical_lines = self.history
        self.history = []
        self.display_lines = []

        if not self.weak_rewind:
            self.interp = code.InteractiveInterpreter()
            self.coderunner.interp = self.interp
            self.completer = Autocomplete(self.interp.locals, self.config)
            self.completer.autocomplete_mode = 'simple'

        self.buffer = []
        self.display_buffer = []
        self.highlighted_paren = None

        self.reevaluating = True
        sys.stdin = ReevaluateFakeStdin(self.stdin, self)
        for line in old_logical_lines:
            self._current_line = line
            self.on_enter(insert_into_history=insert_into_history)
            while self.fake_refresh_requested:
                self.fake_refresh_requested = False
                self.process_event(events.RefreshRequestEvent())
        sys.stdin = self.stdin
        self.reevaluating = False

        self.cursor_offset_in_line = 0
        self._current_line = ''

    def getstdout(self):
        lines = self.lines_for_display + [self.current_line_formatted]
        s = '\n'.join([x.s if isinstance(x, FmtStr) else x for x in lines]
                     ) if lines else ''
        return s