# FILE:     autoload/conque_term/conque.py 
# AUTHOR:   Nico Raffo <nicoraffo@gmail.com>
# WEBSITE:  http://conque.googlecode.com
# MODIFIED: 2011-09-12
# VERSION:  2.3, for Vim 7.0
# LICENSE:
# Conque - Vim terminal/console emulator
# Copyright (C) 2009-2011 Nico Raffo
#
# MIT License
#
# Permission is hereby granted, free of charge, to any person obtaining a copy
# of this software and associated documentation files (the "Software"), to deal
# in the Software without restriction, including without limitation the rights
# to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
# copies of the Software, and to permit persons to whom the Software is
# furnished to do so, subject to the following conditions:
#
# The above copyright notice and this permission notice shall be included in
# all copies or substantial portions of the Software.
#
# THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
# IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
# FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
# AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
# LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
# OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
# THE SOFTWARE.

"""
Vim terminal emulator.

This class is the main interface between Vim and the terminal application. It 
handles both updating the Vim buffer with new output and accepting new keyboard
input from the Vim user.

Although this class was originally designed for a Unix terminal environment, it
has been extended by the ConqueSole class for Windows.

Usage:
    term = Conque()
    term.open('/bin/bash', {'TERM': 'vt100'})
    term.write("ls -lha\r")
    term.read()
    term.close()
"""

import vim
import re
import math
import time


class Conque(object):

    # screen object
    screen = None

    # subprocess object
    proc = None

    # terminal dimensions and scrolling region
    columns = 80 # same as $COLUMNS
    lines = 24 # same as $LINES
    working_columns = 80 # can be changed by CSI ? 3 l/h
    working_lines = 24 # can be changed by CSI r

    # top/bottom of the scroll region
    top = 1 # relative to top of screen
    bottom = 24 # relative to top of screen

    # cursor position
    l = 1 # current cursor line
    c = 1 # current cursor column

    # autowrap mode
    autowrap = True

    # absolute coordinate mode
    absolute_coords = True

    # tabstop positions
    tabstops = []

    # enable colors
    enable_colors = True

    # color changes
    color_changes = {}

    # color history
    color_history = {}

    # color highlight cache
    highlight_groups = {}

    # prune terminal colors
    color_pruning = True

    # don't wrap table output
    unwrap_tables = True

    # wrap CUF/CUB around line breaks
    wrap_cursor = False

    # do we need to move the cursor?
    cursor_set = False

    # current character set, ascii or graphics
    character_set = 'ascii'

    # used for auto_read actions
    read_count = 0

    # input buffer, array of ordinals
    input_buffer = []

    def open(self):
        """ Start program and initialize this instance. 

        Arguments:
        command -- Command string to execute, e.g. '/bin/bash --login'
        options -- Dictionary of environment vars to set and other options.

        """
        # get arguments
        command = vim.eval('command')
        options = vim.eval('options')

        # create terminal screen instance
        self.screen = ConqueScreen()

        # int vars
        self.columns = vim.current.window.width
        self.lines = vim.current.window.height
        self.working_columns = vim.current.window.width
        self.working_lines = vim.current.window.height
        self.bottom = vim.current.window.height

        # offset first line to make room for startup messages
        if int(options['offset']) > 0:
            self.l = int(options['offset'])

        # init color
        self.enable_colors = int(options['color']) and not CONQUE_FAST_MODE

        # init tabstops
        self.init_tabstops()

        # open command
        self.proc = ConqueSubprocess()
        self.proc.open(command, {'TERM': options['TERM'], 'CONQUE': '1', 'LINES': str(self.lines), 'COLUMNS': str(self.columns)})

        # send window size signal, in case LINES/COLUMNS is ignored
        self.update_window_size(True)


    def write(self, input, set_cursor=True, read=True):
        """ Write a unicode string to the subprocess. 

        set_cursor -- Position the cursor in the current buffer when finished
        read -- Check program for new output when finished

        """
        # check if window size has changed
        if not CONQUE_FAST_MODE:
            self.update_window_size()

        # write and read
        self.proc.write(input)

        # read output immediately
        if read:
            self.read(1, set_cursor)



    def write_ord(self, input, set_cursor=True, read=True):
        """ Write a single character to the subprocess, using an unicode ordinal. """

        if CONQUE_PYTHON_VERSION == 2:
            self.write(unichr(input), set_cursor, read)
        else:
            self.write(chr(input), set_cursor, read)
        


    def write_expr(self, expr, set_cursor=True, read=True):
        """ Write the value of a Vim expression to the subprocess. """

        if CONQUE_PYTHON_VERSION == 2:
            try:
                val = vim.eval(expr)
                self.write(unicode(val, CONQUE_VIM_ENCODING, 'ignore'), set_cursor, read)
            except:

                pass
        else:
            try:
                # XXX - Depending on Vim to deal with encoding, sadly
                self.write(vim.eval(expr), set_cursor, read)
            except:

                pass


    def write_latin1(self, input, set_cursor=True, read=True):
        """ Write latin-1 string to conque. Very ugly, shood be removed. """
        # XXX - this whole method is a hack, to be removed soon

        if CONQUE_PYTHON_VERSION == 2:
            try:
                input_unicode = input.decode('latin-1', 'ignore')
                self.write(input_unicode.encode('utf-8', 'ignore'), set_cursor, read)
            except:
                return
        else:
            self.write(input, set_cursor, read)


    def write_buffered_ord(self, chr):
        """ Add character ordinal to input buffer. In case we're not allowed to modify buffer a time of input. """
        self.input_buffer.append(chr)


    def read(self, timeout=1, set_cursor=True, return_output=False, update_buffer=True):
        """ Read new output from the subprocess and update the Vim buffer.

        Arguments:
        timeout -- Milliseconds to wait before reading input
        set_cursor -- Set the cursor position in the current buffer when finished
        return_output -- Return new subprocess STDOUT + STDERR as a string
        update_buffer -- Update the current Vim buffer with the new output

        This method goes through the following rough steps:
            1. Get new output from subprocess
            2. Split output string into control codes, escape sequences, or plain text
            3. Loop over and process each chunk, updating the Vim buffer as we go

        """
        output = ''

        # this may not actually work
        try:

            # read from subprocess and strip null characters
            output = self.proc.read(timeout)

            if output == '':
                return output

            # for bufferless terminals
            if not update_buffer:
                return output



            # strip null characters. I'm still not sure why they appear
            output = output.replace(chr(0), '')

            # split input into individual escape sequences, control codes, and text output
            chunks = CONQUE_SEQ_REGEX.split(output)



            # if there were no escape sequences, skip processing and treat entire string as plain text
            if len(chunks) == 1:
                self.plain_text(chunks[0])

            # loop through and process escape sequences
            else:
                for s in chunks:
                    if s == '':
                        continue




                    # Check for control character match 
                    if CONQUE_SEQ_REGEX_CTL.match(s[0]):

                        nr = ord(s[0])
                        if nr in CONQUE_CTL:
                            getattr(self, 'ctl_' + CONQUE_CTL[nr])()
                        else:

                            pass

                    # check for escape sequence match 
                    elif CONQUE_SEQ_REGEX_CSI.match(s):

                        if s[-1] in CONQUE_ESCAPE:
                            csi = self.parse_csi(s[2:])

                            getattr(self, 'csi_' + CONQUE_ESCAPE[s[-1]])(csi)
                        else:

                            pass

                    # check for title match 
                    elif CONQUE_SEQ_REGEX_TITLE.match(s):

                        self.change_title(s[2], s[4:-1])

                    # check for hash match 
                    elif CONQUE_SEQ_REGEX_HASH.match(s):

                        if s[-1] in CONQUE_ESCAPE_HASH:
                            getattr(self, 'hash_' + CONQUE_ESCAPE_HASH[s[-1]])()
                        else:

                            pass

                    # check for charset match 
                    elif CONQUE_SEQ_REGEX_CHAR.match(s):

                        if s[-1] in CONQUE_ESCAPE_CHARSET:
                            getattr(self, 'charset_' + CONQUE_ESCAPE_CHARSET[s[-1]])()
                        else:

                            pass

                    # check for other escape match 
                    elif CONQUE_SEQ_REGEX_ESC.match(s):

                        if s[-1] in CONQUE_ESCAPE_PLAIN:
                            getattr(self, 'esc_' + CONQUE_ESCAPE_PLAIN[s[-1]])()
                        else:

                            pass

                    # else process plain text 
                    else:
                        self.plain_text(s)

            # set cusor position
            if set_cursor:
                self.screen.set_cursor(self.l, self.c)

            # we need to set the cursor position
            self.cursor_set = False

        except:


            pass

        if return_output:
            if CONQUE_PYTHON_VERSION == 3:
                return output
            else:
                return output.encode(CONQUE_VIM_ENCODING, 'replace')


    def auto_read(self, reset_timer = True):
        """ Poll program for more output. 

        Since Vim doesn't have a reliable event system that can be triggered when new
        output is available, we have to continually poll the subprocess instead. This
        method is called many times a second when the terminal buffer is active, so it
        needs to be very fast and efficient.

        The feedkeys portion is required to reset Vim's timer system. The timer is used
        to execute this command, typically set to go off after 50 ms of inactivity.

        """

        # process buffered input if any
        if len(self.input_buffer):
            for chr in self.input_buffer:
                self.write_ord(chr, set_cursor=False, read=False)
            self.input_buffer = []
            self.read(1)

        if not self.proc.is_alive():
            vim.command('call conque_term#get_instance().close()')
            return

        # check subprocess status, but not every time since it's CPU expensive
        if self.read_count % 32 == 0:
            if self.read_count > 512:
                self.read_count = 0

                # trim color history occasionally if desired
                if self.enable_colors and self.color_pruning:
                    self.prune_colors()

        # ++
        self.read_count += 1

        # read output
        self.read(1)

        if reset_timer:
            try:
                # reset timer
                if self.c == vim.eval('col("$")'):
                    vim.command('call feedkeys("\<Right>", "n")')
                else:
                    vim.command('call feedkeys("\<Right>\<Left>", "n")')
            except:
                pass

        # stop here if cursor doesn't need to be moved
        if self.cursor_set:
            return

        # otherwise set cursor position
        try:
            self.set_cursor(self.l, self.c)
        except:
            pass

        self.cursor_set = True


    def plain_text(self, input):
        """ Write text output to Vim buffer.

  
        This method writes a string of characters without any control characters or escape sequences
        to the Vim buffer. In simple terms, it writes the input string to the buffer starting at the
        current cursor position, wrapping the text to a new line if needed. It also triggers the 
        terminal coloring methods if needed.


        """
        # translate input into graphics character set if needed
        if self.character_set == 'graphics':
            old_input = input
            input = u('')
            for i in range(0, len(old_input)):
                chrd = ord(old_input[i])


                try:
                    if chrd > 255:

                        input = input + old_input[i]
                    else:
                        input = input + uchr(CONQUE_GRAPHICS_SET[chrd])
                except:

                    pass



        # get current line from Vim buffer
        current_line = self.screen[self.l]

        # pad current line with spaces, if it's shorter than cursor position
        if len(current_line) < self.c:
            current_line = current_line + ' ' * (self.c - len(current_line))

        # if line is wider than screen
        if self.c + len(input) - 1 > self.working_columns:

            # Table formatting hack
            if self.unwrap_tables and CONQUE_TABLE_OUTPUT.match(input):
                self.screen[self.l] = current_line[:self.c - 1] + input + current_line[self.c + len(input) - 1:]
                self.apply_color(self.c, self.c + len(input))
                self.c += len(input)
                return


            diff = self.c + len(input) - self.working_columns - 1

            # if autowrap is enabled
            if self.autowrap:
                self.screen[self.l] = current_line[:self.c - 1] + input[:-1 * diff]
                self.apply_color(self.c, self.working_columns)
                self.ctl_nl()
                self.ctl_cr()
                remaining = input[-1 * diff:]

                self.plain_text(remaining)
            else:
                self.screen[self.l] = current_line[:self.c - 1] + input[:-1 * diff - 1] + input[-1]
                self.apply_color(self.c, self.working_columns)
                self.c = self.working_columns

        # no autowrap
        else:
            self.screen[self.l] = current_line[:self.c - 1] + input + current_line[self.c + len(input) - 1:]
            self.apply_color(self.c, self.c + len(input))
            self.c += len(input)



    def apply_color(self, start, end, line=0):
        """ Apply terminal colors to buffer for a range of characters in a single line. 

        When a text attribute escape sequence is encountered during input processing, the
        attributes are recorded in the dictionary self.color_changes. After those attributes
        have been applied, the changes are recorded in a second dictionary self.color_history.

  
        This method inspects both dictionaries to calculate any syntax highlighting 
        that needs to be executed to render the text attributes in the Vim buffer.


        """


        # stop here if coloration is disabled
        if not self.enable_colors:
            return

        # allow custom line nr to be passed
        if line:
            buffer_line = line
        else:
            buffer_line = self.get_buffer_line(self.l)

        # check for previous overlapping coloration

        to_del = []
        if buffer_line in self.color_history:
            for i in range(len(self.color_history[buffer_line])):
                syn = self.color_history[buffer_line][i]

                if syn['start'] >= start and syn['start'] < end:

                    vim.command('syn clear ' + syn['name'])
                    to_del.append(i)
                    # outside
                    if syn['end'] > end:

                        self.exec_highlight(buffer_line, end, syn['end'], syn['highlight'])
                elif syn['end'] > start and syn['end'] <= end:

                    vim.command('syn clear ' + syn['name'])
                    to_del.append(i)
                    # outside
                    if syn['start'] < start:

                        self.exec_highlight(buffer_line, syn['start'], start, syn['highlight'])

        # remove overlapped colors
        if len(to_del) > 0:
            to_del.reverse()
            for di in to_del:
                del self.color_history[buffer_line][di]

        # if there are no new colors
        if len(self.color_changes) == 0:
            return

        # build the color attribute string
        highlight = ''
        for attr in self.color_changes.keys():
            highlight = highlight + ' ' + attr + '=' + self.color_changes[attr]

        # execute the highlight
        self.exec_highlight(buffer_line, start, end, highlight)


    def exec_highlight(self, buffer_line, start, end, highlight):
        """ Execute the Vim commands for a single syntax highlight """

        syntax_name = 'ConqueHighLightAt_%d_%d_%d_%d' % (self.proc.pid, self.l, start, len(self.color_history) + 1)
        syntax_options = 'contains=ALLBUT,ConqueString,MySQLString,MySQLKeyword oneline'
        syntax_region = 'syntax match %s /\%%%dl\%%>%dc.\{%d}\%%<%dc/ %s' % (syntax_name, buffer_line, start - 1, end - start, end + 1, syntax_options)

        # check for cached highlight group
        hgroup = 'ConqueHL_%d' % (abs(hash(highlight)))
        if hgroup not in self.highlight_groups:
            syntax_group = 'highlight %s %s' % (hgroup, highlight)
            self.highlight_groups[hgroup] = hgroup
            vim.command(syntax_group)

        # link this syntax match to existing highlight group
        syntax_highlight = 'highlight link %s %s' % (syntax_name, self.highlight_groups[hgroup])



        vim.command(syntax_region)
        vim.command(syntax_highlight)

        # add syntax name to history
        if not buffer_line in self.color_history:
            self.color_history[buffer_line] = []

        self.color_history[buffer_line].append({'name': syntax_name, 'start': start, 'end': end, 'highlight': highlight})


    def prune_colors(self):
        """ Remove old syntax highlighting from the Vim buffer

        The kind of syntax highlighting required for terminal colors can make
        Conque run slowly. The prune_colors() method will remove old highlight definitions
        to keep the maximum number of highlight rules within a reasonable range.

        """


        buffer_line = self.get_buffer_line(self.l)
        ks = list(self.color_history.keys())

        for line in ks:
            if line < buffer_line - CONQUE_MAX_SYNTAX_LINES:
                for syn in self.color_history[line]:
                    vim.command('syn clear ' + syn['name'])
                del self.color_history[line]




    ###############################################################################################
    # Control functions 

    def ctl_nl(self):
        """ Process the newline control character. """
        # if we're in a scrolling region, scroll instead of moving cursor down
        if self.lines != self.working_lines and self.l == self.bottom:
            del self.screen[self.top]
            self.screen.insert(self.bottom, '')
        elif self.l == self.bottom:
            self.screen.append('')
        else:
            self.l += 1

        self.color_changes = {}

    def ctl_cr(self):
        """ Process the carriage return control character. """
        self.c = 1

        self.color_changes = {}

    def ctl_bs(self):
        """ Process the backspace control character. """
        if self.c > 1:
            self.c += -1

    def ctl_soh(self):
        """ Process the start of heading control character. """
        pass

    def ctl_stx(self):
        pass

    def ctl_bel(self):
        """ Process the bell control character. """
        vim.command('call conque_term#bell()')

    def ctl_tab(self):
        """ Process the tab control character. """
        # default tabstop location
        ts = self.working_columns

        # check set tabstops
        for i in range(self.c, len(self.tabstops)):
            if self.tabstops[i]:
                ts = i + 1
                break



        self.c = ts

    def ctl_so(self):
        """ Process the shift out control character. """
        self.character_set = 'graphics'

    def ctl_si(self):
        """ Process the shift in control character. """
        self.character_set = 'ascii'



    ###############################################################################################
    # CSI functions 

    def csi_font(self, csi):
        """ Process the text attribute escape sequence. """
        if not self.enable_colors:
            return

        # defaults to 0
        if len(csi['vals']) == 0:
            csi['vals'] = [0]

        # 256 xterm color foreground
        if len(csi['vals']) == 3 and csi['vals'][0] == 38 and csi['vals'][1] == 5:
            self.color_changes['ctermfg'] = str(csi['vals'][2])
            self.color_changes['guifg'] = '#' + self.xterm_to_rgb(csi['vals'][2])

        # 256 xterm color background
        elif len(csi['vals']) == 3 and csi['vals'][0] == 48 and csi['vals'][1] == 5:
            self.color_changes['ctermbg'] = str(csi['vals'][2])
            self.color_changes['guibg'] = '#' + self.xterm_to_rgb(csi['vals'][2])

        # 16 colors
        else:
            for val in csi['vals']:
                if val in CONQUE_FONT:

                    # ignore starting normal colors
                    if CONQUE_FONT[val]['normal'] and len(self.color_changes) == 0:

                        continue
                    # clear color changes
                    elif CONQUE_FONT[val]['normal']:

                        self.color_changes = {}
                    # save these color attributes for next plain_text() call
                    else:

                        for attr in CONQUE_FONT[val]['attributes'].keys():
                            if attr in self.color_changes and (attr == 'cterm' or attr == 'gui'):
                                self.color_changes[attr] += ',' + CONQUE_FONT[val]['attributes'][attr]
                            else:
                                self.color_changes[attr] = CONQUE_FONT[val]['attributes'][attr]


    def csi_clear_line(self, csi):
        """ Process the line clear escape sequence. """


        # this escape defaults to 0
        if len(csi['vals']) == 0:
            csi['val'] = 0




        # 0 means cursor right
        if csi['val'] == 0:
            self.screen[self.l] = self.screen[self.l][0:self.c - 1]

        # 1 means cursor left
        elif csi['val'] == 1:
            self.screen[self.l] = ' ' * (self.c) + self.screen[self.l][self.c:]

        # clear entire line
        elif csi['val'] == 2:
            self.screen[self.l] = ''

        # clear colors
        if csi['val'] == 2 or (csi['val'] == 0 and self.c == 1):
            buffer_line = self.get_buffer_line(self.l)
            if buffer_line in self.color_history:
                for syn in self.color_history[buffer_line]:
                    vim.command('syn clear ' + syn['name'])





    def csi_cursor_right(self, csi):
        """ Process the move cursor right escape sequence. """
        # we use 1 even if escape explicitly specifies 0
        if csi['val'] == 0:
            csi['val'] = 1




        if self.wrap_cursor and self.c + csi['val'] > self.working_columns:
            self.l += int(math.floor((self.c + csi['val']) / self.working_columns))
            self.c = (self.c + csi['val']) % self.working_columns
            return

        self.c = self.bound(self.c + csi['val'], 1, self.working_columns)


    def csi_cursor_left(self, csi):
        """ Process the move cursor left escape sequence. """
        # we use 1 even if escape explicitly specifies 0
        if csi['val'] == 0:
            csi['val'] = 1

        if self.wrap_cursor and csi['val'] >= self.c:
            self.l += int(math.floor((self.c - csi['val']) / self.working_columns))
            self.c = self.working_columns - (csi['val'] - self.c) % self.working_columns
            return

        self.c = self.bound(self.c - csi['val'], 1, self.working_columns)


    def csi_cursor_to_column(self, csi):
        """ Process the move cursor to column escape sequence. """
        self.c = self.bound(csi['val'], 1, self.working_columns)


    def csi_cursor_up(self, csi):
        """ Process the move cursor up escape sequence. """
        self.l = self.bound(self.l - csi['val'], self.top, self.bottom)

        self.color_changes = {}


    def csi_cursor_down(self, csi):
        """ Process the move cursor down escape sequence. """
        self.l = self.bound(self.l + csi['val'], self.top, self.bottom)

        self.color_changes = {}


    def csi_clear_screen(self, csi):
        """ Process the clear screen escape sequence. """
        # default to 0
        if len(csi['vals']) == 0:
            csi['val'] = 0

        # 2 == clear entire screen
        if csi['val'] == 2:
            self.l = 1
            self.c = 1
            self.screen.clear()

        # 0 == clear down
        elif csi['val'] == 0:
            for l in range(self.bound(self.l + 1, 1, self.lines), self.lines + 1):
                self.screen[l] = ''

            # clear end of current line
            self.csi_clear_line(self.parse_csi('K'))

        # 1 == clear up
        elif csi['val'] == 1:
            for l in range(1, self.bound(self.l, 1, self.lines + 1)):
                self.screen[l] = ''

            # clear beginning of current line
            self.csi_clear_line(self.parse_csi('1K'))

        # clear coloration
        if csi['val'] == 2 or csi['val'] == 0:
            buffer_line = self.get_buffer_line(self.l)
            for line in self.color_history.keys():
                if line >= buffer_line:
                    for syn in self.color_history[line]:
                        vim.command('syn clear ' + syn['name'])

        self.color_changes = {}


    def csi_delete_chars(self, csi):
        self.screen[self.l] = self.screen[self.l][:self.c] + self.screen[self.l][self.c + csi['val']:]


    def csi_add_spaces(self, csi):
        self.screen[self.l] = self.screen[self.l][: self.c - 1] + ' ' * csi['val'] + self.screen[self.l][self.c:]


    def csi_cursor(self, csi):
        if len(csi['vals']) == 2:
            new_line = csi['vals'][0]
            new_col = csi['vals'][1]
        else:
            new_line = 1
            new_col = 1

        if self.absolute_coords:
            self.l = self.bound(new_line, 1, self.lines)
        else:
            self.l = self.bound(self.top + new_line - 1, self.top, self.bottom)

        self.c = self.bound(new_col, 1, self.working_columns)
        if self.c > len(self.screen[self.l]):
            self.screen[self.l] = self.screen[self.l] + ' ' * (self.c - len(self.screen[self.l]))



    def csi_set_coords(self, csi):
        if len(csi['vals']) == 2:
            new_start = csi['vals'][0]
            new_end = csi['vals'][1]
        else:
            new_start = 1
            new_end = vim.current.window.height

        self.top = new_start
        self.bottom = new_end
        self.working_lines = new_end - new_start + 1

        # if cursor is outside scrolling region, reset it
        if self.l < self.top:
            self.l = self.top
        elif self.l > self.bottom:
            self.l = self.bottom

        self.color_changes = {}


    def csi_tab_clear(self, csi):
        # this escape defaults to 0
        if len(csi['vals']) == 0:
            csi['val'] = 0



        if csi['val'] == 0:
            self.tabstops[self.c - 1] = False
        elif csi['val'] == 3:
            for i in range(0, self.columns + 1):
                self.tabstops[i] = False


    def csi_set(self, csi):
        # 132 cols
        if csi['val'] == 3:
            self.csi_clear_screen(self.parse_csi('2J'))
            self.working_columns = 132

        # relative_origin
        elif csi['val'] == 6:
            self.absolute_coords = False

        # set auto wrap
        elif csi['val'] == 7:
            self.autowrap = True


        self.color_changes = {}


    def csi_reset(self, csi):
        # 80 cols
        if csi['val'] == 3:
            self.csi_clear_screen(self.parse_csi('2J'))
            self.working_columns = 80

        # absolute origin
        elif csi['val'] == 6:
            self.absolute_coords = True

        # reset auto wrap
        elif csi['val'] == 7:
            self.autowrap = False


        self.color_changes = {}




    ###############################################################################################
    # ESC functions 

    def esc_scroll_up(self):
        self.ctl_nl()

        self.color_changes = {}


    def esc_next_line(self):
        self.ctl_nl()
        self.c = 1


    def esc_set_tab(self):

        if self.c <= len(self.tabstops):
            self.tabstops[self.c - 1] = True


    def esc_scroll_down(self):
        if self.l == self.top:
            del self.screen[self.bottom]
            self.screen.insert(self.top, '')
        else:
            self.l += -1

        self.color_changes = {}




    ###############################################################################################
    # HASH functions 

    def hash_screen_alignment_test(self):
        self.csi_clear_screen(self.parse_csi('2J'))
        self.working_lines = self.lines
        for l in range(1, self.lines + 1):
            self.screen[l] = 'E' * self.working_columns



    ###############################################################################################
    # CHARSET functions 

    def charset_us(self):
        self.character_set = 'ascii'

    def charset_uk(self):
        self.character_set = 'ascii'

    def charset_graphics(self):
        self.character_set = 'graphics'



    ###############################################################################################
    # Random stuff 

    def set_cursor(self, line, col):
        """ Set cursor position in the Vim buffer.

        Note: the line and column numbers are relative to the top left corner of the 
        visible screen. Not the line number in the Vim buffer.

        """
        self.screen.set_cursor(line, col)

    def change_title(self, key, val):
        """ Change the Vim window title. """


        if key == '0' or key == '2':

            vim.command('setlocal statusline=' + re.escape(val))
            try:
                vim.command('set titlestring=' + re.escape(val))
            except:
                pass

    def update_window(self, force=False):
        """
        Update Conque buffer size attributes if needed.
        """
        if force or vim.current.window.width != self.columns or vim.current.window.height != self.lines:

            # reset all window size attributes to default
            self.columns = vim.current.window.width
            self.lines = vim.current.window.height
            self.working_columns = vim.current.window.width
            self.working_lines = vim.current.window.height
            self.bottom = vim.current.window.height

            # reset screen object attributes
            self.l = self.screen.reset_size(self.l)

            # reset tabstops
            self.init_tabstops()

            return True

        return False

    def update_window_size(self, force=False):
        """ Check and save the current buffer dimensions.

        If the buffer size has changed, the update_window_size() method both updates
        the Conque buffer size attributes as well as sending the new dimensions to the
        subprocess pty.

        """
        if self.update_window(force):
            # signal process that screen size has changed
            self.proc.window_resize(self.lines, self.columns)

    def insert_enter(self):
        """ Run commands when user enters insert mode. """

        # check window size
        self.update_window_size()

        # we need to set the cursor position
        self.cursor_set = False

    def init_tabstops(self):
        """ Intitialize terminal tabstop positions. """
        for i in range(0, self.columns + 1):
            if i % 8 == 0:
                self.tabstops.append(True)
            else:
                self.tabstops.append(False)

    def idle(self):
        """ Called when this terminal becomes idle. """
        pass

    def resume(self):
        """ Called when this terminal is no longer idle. """
        pass
        pass

    def close(self):
        """ End the process running in the terminal. """
        self.abort()

    def abort(self):
        """ Forcefully end the process running in the terminal. """
        self.proc.signal(1)
        self.poll_wait_for_proc(10);

    def poll_wait_for_proc(self, tries):
        """ Try 'tries' times to see if self.proc has become a zombie
		such that we can reclaim its resources. Wait for 2ms before each try.
		"""
        pid = self.proc.getpid()
        try:
            for i in range(tries):
                time.sleep(0.02)
                if os.waitpid(pid, os.WNOHANG)[0]:
                    break;
        except:
            pass
        



    ###############################################################################################
    # Utility 

    def parse_csi(self, s):
        """ Parse an escape sequence into it's meaningful values. """

        attr = {'key': s[-1], 'flag': '', 'val': 1, 'vals': []}

        if len(s) == 1:
            return attr

        full = s[0:-1]

        if full[0] == '?':
            full = full[1:]
            attr['flag'] = '?'

        if full != '':
            vals = full.split(';')
            for val in vals:

                val = re.sub("\D", "", val)

                if val != '':
                    attr['vals'].append(int(val))

        if len(attr['vals']) == 1:
            attr['val'] = int(attr['vals'][0])

        return attr


    def bound(self, val, min, max):
        """ TODO: This probably exists as a builtin function. """
        if val > max:
            return max

        if val < min:
            return min

        return val


    def xterm_to_rgb(self, color_code):
        """ Translate a terminal color number into a RGB string. """
        if color_code < 16:
            ascii_colors = ['000000', 'CD0000', '00CD00', 'CDCD00', '0000EE', 'CD00CD', '00CDCD', 'E5E5E5',
                   '7F7F7F', 'FF0000', '00FF00', 'FFFF00', '5C5CFF', 'FF00FF', '00FFFF', 'FFFFFF']
            return ascii_colors[color_code]

        elif color_code < 232:
            cc = int(color_code) - 16

            p1 = "%02x" % (math.floor(cc / 36) * (255 / 5))
            p2 = "%02x" % (math.floor((cc % 36) / 6) * (255 / 5))
            p3 = "%02x" % (math.floor(cc % 6) * (255 / 5))

            return p1 + p2 + p3
        else:
            grey_tone = "%02x" % math.floor((255 / 24) * (color_code - 232))
            return grey_tone + grey_tone + grey_tone




    def get_buffer_line(self, line):
        """ Get the buffer line number corresponding to the supplied screen line number. """
        return self.screen.get_buffer_line(line)


