" FILE: plugin/conque_term.vim {{{ " " AUTHOR: Nico Raffo " MODIFIED: 2010-05-27 " VERSION: 1.1, for Vim 7.0 " LICENSE: " Conque - pty interaction in Vim " Copyright (C) 2009-2010 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 FUNCTIONS *************************************************************************************** " ********************************************************************************************************** " launch conque function! conque_term#open(...) "{{{ let command = get(a:000, 0, '') let hooks = get(a:000, 1, []) " bare minimum validation if has('python') != 1 echohl WarningMsg | echomsg "Conque requires the Python interface to be installed" | echohl None return 0 endif if empty(command) echohl WarningMsg | echomsg "No command found" | echohl None return 0 else let l:cargs = split(command, '\s') if !executable(l:cargs[0]) echohl WarningMsg | echomsg "Not an executable: " . l:cargs[0] | echohl None return 0 endif endif " set buffer window options let g:ConqueTerm_BufName = substitute(command, ' ', '\\ ', 'g') . "\\ -\\ " . g:ConqueTerm_Idx call conque_term#set_buffer_settings(command, hooks) let b:ConqueTerm_Var = 'ConqueTerm_' . g:ConqueTerm_Idx let g:ConqueTerm_Var = 'ConqueTerm_' . g:ConqueTerm_Idx let g:ConqueTerm_Idx += 1 " open command try let l:config = '{"color":' . string(g:ConqueTerm_Color) . ',"TERM":"' . g:ConqueTerm_TERM . '"}' execute 'python ' . b:ConqueTerm_Var . ' = Conque()' execute "python " . b:ConqueTerm_Var . ".open('" . conque_term#python_escape(command) . "', " . l:config . ")" catch echohl WarningMsg | echomsg "Unable to open command: " . command | echohl None return 0 endtry " set buffer mappings and auto commands call conque_term#set_mappings('start') startinsert! return 1 endfunction "}}} " set buffer options function! conque_term#set_buffer_settings(command, pre_hooks) "{{{ " optional hooks to execute, e.g. 'split' for h in a:pre_hooks sil exe h endfor sil exe "edit " . g:ConqueTerm_BufName " buffer settings setlocal nocompatible " conque won't work in compatible mode setlocal nopaste " conque won't work in paste mode setlocal buftype=nofile " this buffer is not a file, you can't save it setlocal nonumber " hide line numbers setlocal foldcolumn=0 " reasonable left margin setlocal nowrap " default to no wrap (esp with MySQL) setlocal noswapfile " don't bother creating a .swp file setlocal updatetime=50 " trigger cursorhold event after 50ms / XXX - global setlocal scrolloff=0 " don't use buffer lines. it makes the 'clear' command not work as expected setlocal sidescrolloff=0 " don't use buffer lines. it makes the 'clear' command not work as expected setlocal sidescroll=1 " don't use buffer lines. it makes the 'clear' command not work as expected setlocal foldmethod=manual " don't fold on {{{}}} and stuff setlocal bufhidden=hide " when buffer is no longer displayed, don't wipe it out setfiletype conque_term " useful sil exe "setlocal syntax=" . g:ConqueTerm_Syntax endfunction " }}} " set key mappings and auto commands function! conque_term#set_mappings(action) "{{{ " set action if a:action == 'toggle' if exists('b:conque_on') && b:conque_on == 1 let l:action = 'stop' echohl WarningMsg | echomsg "Terminal is paused" | echohl None else let l:action = 'start' echohl WarningMsg | echomsg "Terminal is resumed" | echohl None endif else let l:action = a:action endif " if mappings are being removed, add 'un' let map_modifier = 'nore' if l:action == 'stop' let map_modifier = 'un' endif " remove all auto commands if l:action == 'stop' execute 'autocmd! ' . b:ConqueTerm_Var else execute 'augroup ' . b:ConqueTerm_Var " handle unexpected closing of shell, passes HUP to parent and all child processes execute 'autocmd ' . b:ConqueTerm_Var . ' BufUnload python ' . b:ConqueTerm_Var . '.proc.signal(1)' " check for resized/scrolled buffer when entering buffer execute 'autocmd ' . b:ConqueTerm_Var . ' BufEnter python ' . b:ConqueTerm_Var . '.update_window_size()' execute 'autocmd ' . b:ConqueTerm_Var . ' VimResized python ' . b:ConqueTerm_Var . '.update_window_size()' " set/reset updatetime on entering/exiting buffer autocmd BufEnter set updatetime=50 autocmd BufLeave set updatetime=2000 " check for resized/scrolled buffer when entering insert mode " XXX - messed up since we enter insert mode at each updatetime "execute 'autocmd InsertEnter python ' . b:ConqueTerm_Var . '.screen.align()' " read more output when this isn't the current buffer if g:ConqueTerm_ReadUnfocused == 1 execute 'autocmd ' . b:ConqueTerm_Var . ' CursorHold * call conque_term#read_all()' endif " poll for more output sil execute 'autocmd ' . b:ConqueTerm_Var . ' CursorHoldI python ' . b:ConqueTerm_Var . '.auto_read()' endif " use F22 key to get more input if l:action == 'start' sil exe 'i' . map_modifier . 'map "\\"' sil exe 'i' . map_modifier . 'map "\\"' else sil exe 'i' . map_modifier . 'map ' sil exe 'i' . map_modifier . 'map ' endif " map ASCII 1-31 for c in range(1, 31) " if c == 27 continue endif if l:action == 'start' sil exe 'i' . map_modifier . 'map :python ' . b:ConqueTerm_Var . '.write(chr(' . c . '))' else sil exe 'i' . map_modifier . 'map ' endif endfor if l:action == 'start' sil exe 'n' . map_modifier . 'map :python ' . b:ConqueTerm_Var . '.write(chr(3))' else sil exe 'n' . map_modifier . 'map ' endif " leave insert mode if !exists('g:ConqueTerm_EscKey') || g:ConqueTerm_EscKey == '' " use to send to terminal if l:action == 'start' sil exe 'i' . map_modifier . 'map :python ' . b:ConqueTerm_Var . '.write(chr(27))' else sil exe 'i' . map_modifier . 'map ' endif else " use to send to terminal if l:action == 'start' sil exe 'i' . map_modifier . 'map ' . g:ConqueTerm_EscKey . ' ' sil exe 'i' . map_modifier . 'map :python ' . b:ConqueTerm_Var . '.write(chr(27))' else sil exe 'i' . map_modifier . 'map ' . g:ConqueTerm_EscKey sil exe 'i' . map_modifier . 'map ' endif endif " Map in insert mode if exists('g:ConqueTerm_CWInsert') && g:ConqueTerm_CWInsert == 1 inoremap j j inoremap k k inoremap h h inoremap l l inoremap w w endif " map ASCII 33-127 for i in range(33, 127) " if i == 124 if l:action == 'start' sil exe "i" . map_modifier . "map :python " . b:ConqueTerm_Var . ".write(chr(124))" else sil exe "i" . map_modifier . "map " endif continue endif if l:action == 'start' sil exe "i" . map_modifier . "map " . nr2char(i) . " :python " . b:ConqueTerm_Var . ".write(chr(" . i . "))" else sil exe "i" . map_modifier . "map " . nr2char(i) endif endfor " map ASCII 128-255 for i in range(128, 255) if l:action == 'start' sil exe "i" . map_modifier . "map " . nr2char(i) . " :python " . b:ConqueTerm_Var . ".write('" . nr2char(i) . "')" else sil exe "i" . map_modifier . "map " . nr2char(i) endif endfor " Special cases if l:action == 'start' sil exe 'i' . map_modifier . 'map :python ' . b:ConqueTerm_Var . '.write(u"\u0008")' sil exe 'i' . map_modifier . 'map :python ' . b:ConqueTerm_Var . '.write(" ")' sil exe 'i' . map_modifier . 'map :python ' . b:ConqueTerm_Var . '.write(u"\u001b[A")' sil exe 'i' . map_modifier . 'map :python ' . b:ConqueTerm_Var . '.write(u"\u001b[B")' sil exe 'i' . map_modifier . 'map :python ' . b:ConqueTerm_Var . '.write(u"\u001b[C")' sil exe 'i' . map_modifier . 'map :python ' . b:ConqueTerm_Var . '.write(u"\u001b[D")' else sil exe 'i' . map_modifier . 'map ' sil exe 'i' . map_modifier . 'map ' sil exe 'i' . map_modifier . 'map ' sil exe 'i' . map_modifier . 'map ' sil exe 'i' . map_modifier . 'map ' sil exe 'i' . map_modifier . 'map ' endif " send selected text into conque if l:action == 'start' sil exe 'v' . map_modifier . 'map :call conque_term#send_selected(visualmode())' else sil exe 'v' . map_modifier . 'map ' endif " remap paste keys if l:action == 'start' sil exe 'n' . map_modifier . 'map p :python ' . b:ConqueTerm_Var . '.write(vim.eval("@@"))a' sil exe 'n' . map_modifier . 'map P :python ' . b:ConqueTerm_Var . '.write(vim.eval("@@"))a' sil exe 'n' . map_modifier . 'map ]p :python ' . b:ConqueTerm_Var . '.write(vim.eval("@@"))a' sil exe 'n' . map_modifier . 'map [p :python ' . b:ConqueTerm_Var . '.write(vim.eval("@@"))a' else sil exe 'n' . map_modifier . 'map p' sil exe 'n' . map_modifier . 'map P' sil exe 'n' . map_modifier . 'map ]p' sil exe 'n' . map_modifier . 'map [p' endif if has('gui_running') if l:action == 'start' sil exe 'i' . map_modifier . 'map :python ' . b:ConqueTerm_Var . ".write(vim.eval('@+'))a" else sil exe 'i' . map_modifier . 'map ' endif endif " disable other normal mode keys which insert text if l:action == 'start' sil exe 'n' . map_modifier . 'map r :echo "Replace mode disabled in shell."' sil exe 'n' . map_modifier . 'map R :echo "Replace mode disabled in shell."' sil exe 'n' . map_modifier . 'map c :echo "Change mode disabled in shell."' sil exe 'n' . map_modifier . 'map C :echo "Change mode disabled in shell."' sil exe 'n' . map_modifier . 'map s :echo "Change mode disabled in shell."' sil exe 'n' . map_modifier . 'map S :echo "Change mode disabled in shell."' else sil exe 'n' . map_modifier . 'map r' sil exe 'n' . map_modifier . 'map R' sil exe 'n' . map_modifier . 'map c' sil exe 'n' . map_modifier . 'map C' sil exe 'n' . map_modifier . 'map s' sil exe 'n' . map_modifier . 'map S' endif " set conque as on or off if l:action == 'start' let b:conque_on = 1 else let b:conque_on = 0 endif " map command to start stop the shell if a:action == 'start' nnoremap :call conque_term#set_mappings('toggle') endif endfunction " }}} " send selected text from another buffer function! conque_term#send_selected(type) "{{{ let reg_save = @@ " save user's sb settings let sb_save = &switchbuf set switchbuf=usetab " yank current selection sil exe "normal! `<" . a:type . "`>y" " format yanked text let @@ = substitute(@@, '^[\r\n]*', '', '') let @@ = substitute(@@, '[\r\n]*$', '', '') " execute yanked text sil exe ":sb " . g:ConqueTerm_BufName sil exe 'python ' . g:ConqueTerm_Var . '.paste_selection()' " reset original values let @@ = reg_save sil exe 'set switchbuf=' . sb_save " scroll buffer left startinsert! normal 0zH endfunction "}}} " read from all known conque buffers function! conque_term#read_all() "{{{ " don't run this if we're in a conque buffer if exists('b:ConqueTerm_Var') return endif try for i in range(1, g:ConqueTerm_Idx - 1) execute 'python ConqueTerm_' . string(i) . '.read(1)' endfor catch " probably a deleted buffer endtry " restart updatetime call feedkeys("f\e") endfunction "}}} " util function to add enough \s to pass a string to python function! conque_term#python_escape(input) "{{{ let l:cleaned = a:input let l:cleaned = substitute(l:cleaned, '\\', '\\\\', 'g') let l:cleaned = substitute(l:cleaned, '\n', '\\n', 'g') let l:cleaned = substitute(l:cleaned, '\r', '\\r', 'g') let l:cleaned = substitute(l:cleaned, "'", "\\\\'", 'g') return l:cleaned endfunction "}}} " ********************************************************************************************************** " **** PYTHON ********************************************************************************************** " ********************************************************************************************************** if has('python') python << EOF import vim, re, time, math # CONFIG CONSTANTS {{{ CONQUE_CTL = { 7:'bel', # bell 8:'bs', # backspace 9:'tab', # tab 10:'nl', # new line 13:'cr' # carriage return } # 11 : 'vt', # vertical tab # 12 : 'ff', # form feed # 14 : 'so', # shift out # 15 : 'si' # shift in # Escape sequences CONQUE_ESCAPE = { 'm':'font', 'J':'clear_screen', 'K':'clear_line', '@':'add_spaces', 'A':'cursor_up', 'B':'cursor_down', 'C':'cursor_right', 'D':'cursor_left', 'G':'cursor_to_column', 'H':'cursor', 'P':'delete_chars', 'f':'cursor', 'g':'tab_clear', 'r':'set_coords', 'h':'set', 'l':'reset' } # 'L':'insert_lines', # 'M':'delete_lines', # 'd':'cusor_vpos', # Alternate escape sequences, no [ CONQUE_ESCAPE_PLAIN = { 'D':'scroll_up', 'E':'next_line', 'H':'set_tab', 'M':'scroll_down' } # 'N':'single_shift_2', # 'O':'single_shift_3', # '=':'alternate_keypad', # '>':'numeric_keypad', # '7':'save_cursor', # '8':'restore_cursor', # Uber alternate escape sequences, with # or ? CONQUE_ESCAPE_QUESTION = { '1h':'new_line_mode', '3h':'132_cols', '4h':'smooth_scrolling', '5h':'reverse_video', '6h':'relative_origin', '7h':'set_auto_wrap', '8h':'set_auto_repeat', '9h':'set_interlacing_mode', '1l':'set_cursor_key', '2l':'set_vt52', '3l':'80_cols', '4l':'set_jump_scrolling', '5l':'normal_video', '6l':'absolute_origin', '7l':'reset_auto_wrap', '8l':'reset_auto_repeat', '9l':'reset_interlacing_mode' } CONQUE_ESCAPE_HASH = { '8':'screen_alignment_test' } # '3':'double_height_top', # '4':'double_height_bottom', # '5':'single_height_single_width', # '6':'single_height_double_width', # Font codes {{{ CONQUE_FONT = { 0: {'description':'Normal (default)', 'attributes': {'cterm':'NONE','ctermfg':'NONE','ctermbg':'NONE','gui':'NONE','guifg':'NONE','guibg':'NONE'}, 'normal':True}, 1: {'description':'Bold', 'attributes': {'cterm':'BOLD','gui':'BOLD'}, 'normal':False}, 4: {'description':'Underlined', 'attributes': {'cterm':'UNDERLINE','gui':'UNDERLINE'}, 'normal':False}, 5: {'description':'Blink (appears as Bold)', 'attributes': {'cterm':'BOLD','gui':'BOLD'}, 'normal':False}, 7: {'description':'Inverse', 'attributes': {'cterm':'REVERSE','gui':'REVERSE'}, 'normal':False}, 8: {'description':'Invisible (hidden)', 'attributes': {'ctermfg':'0','ctermbg':'0','guifg':'#000000','guibg':'#000000'}, 'normal':False}, 22: {'description':'Normal (neither bold nor faint)', 'attributes': {'cterm':'NONE','gui':'NONE'}, 'normal':True}, 24: {'description':'Not underlined', 'attributes': {'cterm':'NONE','gui':'NONE'}, 'normal':True}, 25: {'description':'Steady (not blinking)', 'attributes': {'cterm':'NONE','gui':'NONE'}, 'normal':True}, 27: {'description':'Positive (not inverse)', 'attributes': {'cterm':'NONE','gui':'NONE'}, 'normal':True}, 28: {'description':'Visible (not hidden)', 'attributes': {'ctermfg':'NONE','ctermbg':'NONE','guifg':'NONE','guibg':'NONE'}, 'normal':True}, 30: {'description':'Set foreground color to Black', 'attributes': {'ctermfg':'16','guifg':'#000000'}, 'normal':False}, 31: {'description':'Set foreground color to Red', 'attributes': {'ctermfg':'1','guifg':'#ff0000'}, 'normal':False}, 32: {'description':'Set foreground color to Green', 'attributes': {'ctermfg':'2','guifg':'#00ff00'}, 'normal':False}, 33: {'description':'Set foreground color to Yellow', 'attributes': {'ctermfg':'3','guifg':'#ffff00'}, 'normal':False}, 34: {'description':'Set foreground color to Blue', 'attributes': {'ctermfg':'4','guifg':'#0000ff'}, 'normal':False}, 35: {'description':'Set foreground color to Magenta', 'attributes': {'ctermfg':'5','guifg':'#990099'}, 'normal':False}, 36: {'description':'Set foreground color to Cyan', 'attributes': {'ctermfg':'6','guifg':'#009999'}, 'normal':False}, 37: {'description':'Set foreground color to White', 'attributes': {'ctermfg':'7','guifg':'#ffffff'}, 'normal':False}, 39: {'description':'Set foreground color to default (original)', 'attributes': {'ctermfg':'NONE','guifg':'NONE'}, 'normal':True}, 40: {'description':'Set background color to Black', 'attributes': {'ctermbg':'16','guibg':'#000000'}, 'normal':False}, 41: {'description':'Set background color to Red', 'attributes': {'ctermbg':'1','guibg':'#ff0000'}, 'normal':False}, 42: {'description':'Set background color to Green', 'attributes': {'ctermbg':'2','guibg':'#00ff00'}, 'normal':False}, 43: {'description':'Set background color to Yellow', 'attributes': {'ctermbg':'3','guibg':'#ffff00'}, 'normal':False}, 44: {'description':'Set background color to Blue', 'attributes': {'ctermbg':'4','guibg':'#0000ff'}, 'normal':False}, 45: {'description':'Set background color to Magenta', 'attributes': {'ctermbg':'5','guibg':'#990099'}, 'normal':False}, 46: {'description':'Set background color to Cyan', 'attributes': {'ctermbg':'6','guibg':'#009999'}, 'normal':False}, 47: {'description':'Set background color to White', 'attributes': {'ctermbg':'7','guibg':'#ffffff'}, 'normal':False}, 49: {'description':'Set background color to default (original).', 'attributes': {'ctermbg':'NONE','guibg':'NONE'}, 'normal':True}, 90: {'description':'Set foreground color to Black', 'attributes': {'ctermfg':'8','guifg':'#000000'}, 'normal':False}, 91: {'description':'Set foreground color to Red', 'attributes': {'ctermfg':'9','guifg':'#ff0000'}, 'normal':False}, 92: {'description':'Set foreground color to Green', 'attributes': {'ctermfg':'10','guifg':'#00ff00'}, 'normal':False}, 93: {'description':'Set foreground color to Yellow', 'attributes': {'ctermfg':'11','guifg':'#ffff00'}, 'normal':False}, 94: {'description':'Set foreground color to Blue', 'attributes': {'ctermfg':'12','guifg':'#0000ff'}, 'normal':False}, 95: {'description':'Set foreground color to Magenta', 'attributes': {'ctermfg':'13','guifg':'#990099'}, 'normal':False}, 96: {'description':'Set foreground color to Cyan', 'attributes': {'ctermfg':'14','guifg':'#009999'}, 'normal':False}, 97: {'description':'Set foreground color to White', 'attributes': {'ctermfg':'15','guifg':'#ffffff'}, 'normal':False}, 100: {'description':'Set background color to Black', 'attributes': {'ctermbg':'8','guibg':'#000000'}, 'normal':False}, 101: {'description':'Set background color to Red', 'attributes': {'ctermbg':'9','guibg':'#ff0000'}, 'normal':False}, 102: {'description':'Set background color to Green', 'attributes': {'ctermbg':'10','guibg':'#00ff00'}, 'normal':False}, 103: {'description':'Set background color to Yellow', 'attributes': {'ctermbg':'11','guibg':'#ffff00'}, 'normal':False}, 104: {'description':'Set background color to Blue', 'attributes': {'ctermbg':'12','guibg':'#0000ff'}, 'normal':False}, 105: {'description':'Set background color to Magenta', 'attributes': {'ctermbg':'13','guibg':'#990099'}, 'normal':False}, 106: {'description':'Set background color to Cyan', 'attributes': {'ctermbg':'14','guibg':'#009999'}, 'normal':False}, 107: {'description':'Set background color to White', 'attributes': {'ctermbg':'15','guibg':'#ffffff'}, 'normal':False} } # }}} # regular expression matching (almost) all control sequences CONQUE_SEQ_REGEX = re.compile(ur"(\u001b\[?\??#?[0-9;]*[a-zA-Z@]|\u001b\][0-9];.*?\u0007|[\u0007-\u000f])", re.UNICODE) CONQUE_SEQ_REGEX_CTL = re.compile(ur"^[\u0007-\u000f]$", re.UNICODE) CONQUE_SEQ_REGEX_CSI = re.compile(ur"^\u001b\[", re.UNICODE) CONQUE_SEQ_REGEX_TITLE = re.compile(ur"^\u001b\]", re.UNICODE) CONQUE_SEQ_REGEX_HASH = re.compile(ur"^\u001b#", re.UNICODE) CONQUE_SEQ_REGEX_ESC = re.compile(ur"^\u001b", re.UNICODE) # match table output CONQUE_TABLE_OUTPUT = re.compile("^\s*\|\s.*\s\|\s*$|^\s*\+[=+-]+\+\s*$") # }}} ################################################################################################### class Conque: # CLASS PROPERTIES {{{ # 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 = {} # don't wrap table output unwrap_tables = True # wrap CUF/CUB around line breaks wrap_cursor = False # }}} # constructor def __init__(self): # {{{ self.screen = ConqueScreen() # }}} # start program and initialize this instance def open(self, command, options): # {{{ # 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 # init color self.enable_colors = options['color'] # 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)}) # }}} # write to pty def write(self, input): # {{{ # check if window size has changed self.update_window_size() # write and read self.proc.write(input) self.read(1) # }}} # read from pty, and update buffer def read(self, timeout = 1): # {{{ # read from subprocess output = self.proc.read(timeout) # and strip null chars output = output.replace(chr(0), '') if output == '': return chunks = CONQUE_SEQ_REGEX.split(output) # don't go through all the csi regex if length is one (no matches) if len(chunks) == 1: self.plain_text(chunks[0]) 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 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 cursor position self.screen.set_cursor(self.l, self.c) vim.command('redraw') # }}} # for polling def auto_read(self): # {{{ self.read(1) if self.c == 1: vim.command('call feedkeys("\", "t")') else: vim.command('call feedkeys("\", "t")') self.screen.set_cursor(self.l, self.c) # }}} ############################################################################################### # Plain text # {{{ def plain_text(self, input): current_line = self.screen[self.l] if len(current_line) < self.working_columns: 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): # stop here if coloration is disabled if not self.enable_colors: return real_line = self.screen.get_real_line(self.l) # check for previous overlapping coloration to_del = [] if self.color_history.has_key(real_line): for i in range(len(self.color_history[real_line])): syn = self.color_history[real_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(real_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(real_line, syn['start'], start, syn['highlight']) if len(to_del) > 0: to_del.reverse() for di in to_del: del self.color_history[real_line][di] # if there are no new colors if len(self.color_changes) == 0: return highlight = '' for attr in self.color_changes.keys(): highlight = highlight + ' ' + attr + '=' + self.color_changes[attr] # execute the highlight self.exec_highlight(real_line, start, end, highlight) def exec_highlight(self, real_line, start, end, highlight): unique_key = str(self.proc.pid) syntax_name = 'EscapeSequenceAt_' + unique_key + '_' + str(self.l) + '_' + str(start) + '_' + str(len(self.color_history) + 1) syntax_options = ' contains=ALLBUT,ConqueString,MySQLString,MySQLKeyword oneline' syntax_region = 'syntax match ' + syntax_name + ' /\%' + str(real_line) + 'l\%>' + str(start - 1) + 'c.*\%<' + str(end + 1) + 'c/' + syntax_options syntax_highlight = 'highlight ' + syntax_name + highlight vim.command(syntax_region) vim.command(syntax_highlight) # add syntax name to history if not self.color_history.has_key(real_line): self.color_history[real_line] = [] self.color_history[real_line].append({'name':syntax_name, 'start':start, 'end':end, 'highlight':highlight}) # }}} ############################################################################################### # Control functions {{{ def ctl_nl(self): # 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): self.c = 1 self.color_changes = {} def ctl_bs(self): if self.c > 1: self.c += -1 def ctl_bel(self): print 'BELL' def ctl_tab(self): # 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 # }}} ############################################################################################### # CSI functions {{{ def csi_font(self, csi): # {{{ 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 CONQUE_FONT.has_key(val): # 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 self.color_changes.has_key(attr) 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): # {{{ # 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): real_line = self.screen.get_real_line(self.l) if self.color_history.has_key(real_line): for syn in self.color_history[real_line]: vim.command('syn clear ' + syn['name']) # }}} def csi_cursor_right(self, csi): # {{{ # 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): # {{{ # 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): # {{{ self.c = self.bound(csi['val'], 1, self.working_columns) # }}} def csi_cursor_up(self, csi): # {{{ self.l = self.bound(self.l - csi['val'], self.top, self.bottom) self.color_changes = {} # }}} def csi_cursor_down(self, csi): # {{{ self.l = self.bound(self.l + csi['val'], self.top, self.bottom) self.color_changes = {} # }}} def csi_clear_screen(self, csi): # {{{ # 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: real_line = self.screen.get_real_line(self.l) for line in self.color_history.keys(): if line >= real_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 # }}} # }}} ############################################################################################### # Random stuff {{{ def change_title(self, key, val): if key == '0' or key == '2': vim.command('setlocal statusline=' + re.escape(val)) def paste(self): self.write(vim.eval('@@')) self.read(50) def paste_selection(self): self.write(vim.eval('@@')) def update_window_size(self): # resize if needed if 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() # signal process that screen size has changed self.proc.window_resize(self.lines, self.columns) def init_tabstops(self): for i in range(0, self.columns + 1): if i % 8 == 0: self.tabstops.append(True) else: self.tabstops.append(False) # }}} ############################################################################################### # Utility {{{ def parse_csi(self, s): # {{{ 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): # {{{ if val > max: return max if val < min: return min return val # }}} def xterm_to_rgb(self, color_code): # {{{ 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 # }}} # }}} import os, signal, pty, tty, select, fcntl, termios, struct ################################################################################################### class ConqueSubprocess: # process id pid = 0 # stdout+stderr file descriptor fd = None # constructor def __init__(self): # {{{ self.pid = 0 # }}} # create the pty or whatever (whatever == windows) def open(self, command, env = {}): # {{{ command_arr = command.split() executable = command_arr[0] args = command_arr try: self.pid, self.fd = pty.fork() except: pass # child proc, replace with command after altering terminal attributes if self.pid == 0: # set requested environment variables for k in env.keys(): os.environ[k] = env[k] # set some attributes try: attrs = tty.tcgetattr(1) attrs[0] = attrs[0] ^ tty.IGNBRK attrs[0] = attrs[0] | tty.BRKINT | tty.IXANY | tty.IMAXBEL attrs[2] = attrs[2] | tty.HUPCL attrs[3] = attrs[3] | tty.ICANON | tty.ECHO | tty.ISIG | tty.ECHOKE attrs[6][tty.VMIN] = 1 attrs[6][tty.VTIME] = 0 tty.tcsetattr(1, tty.TCSANOW, attrs) except: pass os.execvp(executable, args) # else master, do nothing else: pass # }}} # read from pty # XXX - select.poll() doesn't work in OS X!!!!!!! def read(self, timeout = 1): # {{{ output = '' read_timeout = float(timeout) / 1000 try: # what, no do/while? while 1: s_read, s_write, s_error = select.select( [ self.fd ], [], [], read_timeout) lines = '' for s_fd in s_read: try: lines = os.read( self.fd, 32 ) except: pass output = output + lines if lines == '': break except: pass return output # }}} # I guess this one's not bad def write(self, input): # {{{ try: os.write(self.fd, input) except: pass # }}} # signal process def signal(self, signum): # {{{ try: os.kill(self.pid, signum) except: pass # }}} # get process status def get_status(self): #{{{ p_status = True try: if os.waitpid( self.pid, os.WNOHANG )[0]: p_status = False except: p_status = False return p_status # }}} # update window size in kernel, then send SIGWINCH to fg process def window_resize(self, lines, columns): # {{{ try: fcntl.ioctl(self.fd, termios.TIOCSWINSZ, struct.pack("HHHH", lines, columns, 0, 0)) os.kill(self.pid, signal.SIGWINCH) except: pass # }}} ################################################################################################### # ConqueScreen is an extention of the vim.current.buffer object # It restricts the working indices of the buffer object to the scroll region which pty is expecting # It also uses 1-based indexes, to match escape sequence commands # # E.g.: # s = ConqueScreen() # ... # s[5] = 'Set 5th line in terminal to this line' # s.append('Add new line to terminal') # s[5] = 'Since previous append() command scrolled the terminal down, this is a different line than first cb[5] call' # import vim class ConqueScreen(object): # CLASS PROPERTIES {{{ # the buffer buffer = None # screen and scrolling regions screen_top = 1 # screen width screen_width = 80 screen_height = 80 # }}} def __init__(self): # {{{ self.buffer = vim.current.buffer self.screen_top = 1 self.screen_width = vim.current.window.width self.screen_height = vim.current.window.height # }}} ############################################################################################### # List overload {{{ def __len__(self): # {{{ return len(self.buffer) # }}} def __getitem__(self, key): # {{{ real_line = self.get_real_idx(key) # if line is past buffer end, add lines to buffer if real_line >= len(self.buffer): for i in range(len(self.buffer), real_line + 1): self.append(' ' * self.screen_width) return self.buffer[ real_line ] # }}} def __setitem__(self, key, value): # {{{ real_line = self.get_real_idx(key) # if line is past end of screen, append if real_line == len(self.buffer): self.buffer.append(value) else: self.buffer[ real_line ] = value # }}} def __delitem__(self, key): # {{{ del self.buffer[ self.screen_top + key - 2 ] # }}} def append(self, value): # {{{ if len(self.buffer) > self.screen_top + self.screen_height - 1: self.buffer[len(self.buffer) - 1] = value else: self.buffer.append(value) if len(self.buffer) > self.screen_top + self.screen_height - 1: self.screen_top += 1 if vim.current.buffer.number == self.buffer.number: vim.command('normal G') # }}} def insert(self, line, value): # {{{ l = self.screen_top + line - 2 self.buffer[l:l] = [ value ] # }}} # }}} ############################################################################################### # Util {{{ def get_top(self): # {{{ return self.screen_top # }}} def get_real_idx(self, line): # {{{ return (self.screen_top + line - 2) # }}} def get_real_line(self, line): # {{{ return (self.screen_top + line - 1) # }}} def set_screen_width(self, width): # {{{ self.screen_width = width # }}} # }}} ############################################################################################### def clear(self): # {{{ self.buffer.append(' ') vim.command('normal Gzt') self.screen_top = len(self.buffer) # }}} def set_cursor(self, line, column): # {{{ # figure out line real_line = self.screen_top + line - 1 if real_line > len(self.buffer): for l in range(len(self.buffer) - 1, real_line): self.buffer.append('') # figure out column real_column = column if len(self.buffer[real_line - 1]) < real_column: self.buffer[real_line - 1] = self.buffer[real_line - 1] + ' ' * (real_column - len(self.buffer[real_line - 1])) # python version is occasionally grumpy try: vim.current.window.cursor = (real_line, real_column - 1) except: vim.command('call cursor(' + str(real_line) + ', ' + str(real_column) + ')') # }}} def reset_size(self, line): # {{{ # save cursor line number real_line = self.screen_top + line # reset screen size self.screen_width = vim.current.window.width self.screen_height = vim.current.window.height self.screen_top = len(self.buffer) - vim.current.window.height + 1 if self.screen_top < 1: self.screen_top = 1 # align bottom of buffer to bottom of screen vim.command('normal ' + str(self.screen_height) + 'kG') # return new relative line number return (real_line - self.screen_top) # }}} def scroll_to_bottom(self): # {{{ vim.current.window.cursor = (len(self.buffer) - 1, 1) # }}} def align(self): # {{{ # align bottom of buffer to bottom of screen vim.command('normal ' + str(self.screen_height) + 'kG') # }}} EOF endif