vimrc: Add conque_term
[cmccabe-etc] / .vim / autoload / conque_term.vim
1 " FILE:     plugin/conque_term.vim {{{
2 " AUTHOR:   Nico Raffo <nicoraffo@gmail.com>
3 " MODIFIED: 2010-02-02
4 " VERSION:  1.0, for Vim 7.0
5 " LICENSE:
6 " Conque - pty interaction in Vim
7 " Copyright (C) 2009-2010 Nico Raffo 
8 "
9 " MIT License
10
11 " Permission is hereby granted, free of charge, to any person obtaining a copy
12 " of this software and associated documentation files (the "Software"), to deal
13 " in the Software without restriction, including without limitation the rights
14 " to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
15 " copies of the Software, and to permit persons to whom the Software is
16 " furnished to do so, subject to the following conditions:
17
18 " The above copyright notice and this permission notice shall be included in
19 " all copies or substantial portions of the Software.
20
21 " THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
22 " IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
23 " FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
24 " AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
25 " LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
26 " OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
27 " THE SOFTWARE.
28 " }}}
29
30 " **********************************************************************************************************
31 " **** VIM FUNCTIONS ***************************************************************************************
32 " **********************************************************************************************************
33
34 " launch conque
35 function! conque_term#open(...) "{{{
36     let command = get(a:000, 0, '')
37     let hooks   = get(a:000, 1, [])
38
39     " bare minimum validation
40     if empty(command)
41         echohl WarningMsg | echomsg "No command found" | echohl None
42         return 0
43     else
44         let l:cargs = split(command, '\s')
45         if !executable(l:cargs[0])
46             echohl WarningMsg | echomsg "Not an executable" | echohl None
47             return 0
48         endif
49     endif
50
51     " set buffer window options
52     let g:Conque_BufName = substitute(command, ' ', '\\ ', 'g') . "\\ -\\ " . g:ConqueTerm_Idx
53     call conque_term#set_buffer_settings(command, hooks)
54     let b:ConqueTerm_Var = 'ConqueTerm_' . g:ConqueTerm_Idx
55     let g:ConqueTerm_Var = 'ConqueTerm_' . g:ConqueTerm_Idx
56     let g:ConqueTerm_Idx += 1
57
58     " open command
59     try
60         let l:config = '{"color":' . string(g:ConqueTerm_Color) . ',"TERM":"' . g:ConqueTerm_TERM . '"}'
61         execute 'python ' . b:ConqueTerm_Var . ' = Conque()'
62         execute "python " . b:ConqueTerm_Var . ".open('" . conque_term#python_escape(command) . "', " . l:config . ")"
63     catch 
64         echohl WarningMsg | echomsg "Unable to open command: " . command | echohl None
65         return 0
66     endtry
67
68     " set buffer mappings and auto commands 
69     call conque_term#set_mappings()
70
71     startinsert!
72     return 1
73 endfunction "}}}
74
75 " set buffer options
76 function! conque_term#set_buffer_settings(command, pre_hooks) "{{{
77
78     " optional hooks to execute, e.g. 'split'
79     for h in a:pre_hooks
80         silent execute h
81     endfor
82     silent execute "edit " . g:Conque_BufName
83
84     " buffer settings 
85     setlocal nocompatible      " conque won't work in compatible mode
86     setlocal buftype=nofile    " this buffer is not a file, you can't save it
87     setlocal nonumber          " hide line numbers
88     setlocal foldcolumn=0      " reasonable left margin
89     setlocal nowrap            " default to no wrap (esp with MySQL)
90     setlocal noswapfile        " don't bother creating a .swp file
91     setlocal updatetime=50     " trigger cursorhold event after 50ms / XXX - global
92     setlocal scrolloff=0       " don't use buffer lines. it makes the 'clear' command not work as expected
93     setlocal sidescrolloff=0   " don't use buffer lines. it makes the 'clear' command not work as expected
94     setlocal sidescroll=1      " don't use buffer lines. it makes the 'clear' command not work as expected
95     setlocal foldmethod=manual " don't fold on {{{}}} and stuff
96     setlocal switchbuf=usetab  " switch tabs with the <f9> command
97     setfiletype conque_term    " useful
98     silent execute "setlocal syntax=" . g:ConqueTerm_Syntax
99
100 endfunction " }}}
101
102 " set key mappings and auto commands
103 function! conque_term#set_mappings() "{{{
104
105     " handle unexpected closing of shell, passes HUP to parent and all child processes
106     execute 'autocmd BufUnload <buffer> python ' . b:ConqueTerm_Var . '.proc.signal(1)'
107
108     " check for resized/scrolled buffer when entering buffer
109     execute 'autocmd BufEnter <buffer> python ' . b:ConqueTerm_Var . '.update_window_size()'
110
111     " set/reset updatetime on entering/exiting buffer
112     autocmd BufEnter <buffer> set updatetime=50
113     autocmd BufLeave <buffer> set updatetime=1000
114
115     " check for resized/scrolled buffer when entering insert mode
116     " XXX - messed up since we enter insert mode at each updatetime
117     "execute 'autocmd InsertEnter <buffer> python ' . b:ConqueTerm_Var . '.screen.align()'
118
119     " read more output when this isn't the current buffer
120     if g:ConqueTerm_ReadUnfocused == 1
121         autocmd CursorHold * call conque_term#read_all()
122     endif
123
124     " use F22 key to get more input
125     inoremap <silent> <buffer> <expr> <F22> "\<left>\<right>"
126     inoremap <silent> <buffer> <expr> <F23> "\<right>\<left>"
127     silent execute 'autocmd CursorHoldI <buffer> python ' .  b:ConqueTerm_Var . '.auto_read()'
128
129     " map ASCII 1-31
130     for c in range(1, 31)
131         " <Esc>
132         if c == 27
133             continue
134         endif
135         silent execute 'inoremap <silent> <buffer> <C-' . nr2char(64 + c) . '> <C-o>:python ' . b:ConqueTerm_Var . '.write(chr(' . c . '))<CR>'
136     endfor
137     silent execute 'inoremap <silent> <buffer> <Esc><Esc> <C-o>:python ' . b:ConqueTerm_Var . '.write(chr(27))<CR>'
138     silent execute 'nnoremap <silent> <buffer> <C-c> <C-o>:python ' . b:ConqueTerm_Var . '.write(chr(3))<CR>'
139
140     " map ASCII 33-127
141     for i in range(33, 127)
142         " <Bar>
143         if i == 124
144             silent execute "inoremap <silent> <buffer> <Bar> <C-o>:python " . b:ConqueTerm_Var . ".write(chr(124))<CR>"
145             continue
146         endif
147         silent execute "inoremap <silent> <buffer> " . nr2char(i) . " <C-o>:python " . b:ConqueTerm_Var . ".write(chr(" . i . "))<CR>"
148     endfor
149
150     " map ASCII 128-255
151     for i in range(128, 255)
152         silent execute "inoremap <silent> <buffer> " . nr2char(i) . " <C-o>:python " . b:ConqueTerm_Var . ".write('" . nr2char(i) . "')<CR>"
153     endfor
154
155     " Special cases
156     silent execute 'inoremap <silent> <buffer> <BS> <C-o>:python ' . b:ConqueTerm_Var . '.write(u"\u0008")<CR>'
157     "silent execute 'inoremap <silent> <buffer> <Tab> <C-o>:python ' . b:ConqueTerm_Var . '.write(u"\u0009")<CR>'
158     silent execute 'inoremap <silent> <buffer> <LF> <C-o>:python ' . b:ConqueTerm_Var . '.write(u"\u000a")<CR>'
159     silent execute 'inoremap <silent> <buffer> <CR> <C-o>:python ' . b:ConqueTerm_Var . '.write(u"\u000d")<CR>'
160     silent execute 'inoremap <silent> <buffer> <Space> <C-o>:python ' . b:ConqueTerm_Var . '.write(" ")<CR>'
161     silent execute 'inoremap <silent> <buffer> <Up> <C-o>:python ' . b:ConqueTerm_Var . '.write(u"\u001b[A")<CR>'
162     silent execute 'inoremap <silent> <buffer> <Down> <C-o>:python ' . b:ConqueTerm_Var . '.write(u"\u001b[B")<CR>'
163     silent execute 'inoremap <silent> <buffer> <Right> <C-o>:python ' . b:ConqueTerm_Var . '.write(u"\u001b[C")<CR>'
164     silent execute 'inoremap <silent> <buffer> <Left> <C-o>:python ' . b:ConqueTerm_Var . '.write(u"\u001b[D")<CR>'
165
166     " meta characters 
167     "for c in split(s:chars_meta, '\zs')
168     "    silent execute 'inoremap <silent> <buffer> <M-' . c . '> <Esc>:call conque_term#press_key("<C-v><Esc>' . c . '")<CR>a'
169     "endfor
170
171     " send selected text into conque
172         vnoremap <silent> <F9> :<C-u>call conque_term#send_selected(visualmode())<CR>
173
174     " remap paste keys
175     silent execute 'nnoremap <silent> <buffer> p :python ' . b:ConqueTerm_Var . '.write(vim.eval("@@"))<CR>a'
176     silent execute 'nnoremap <silent> <buffer> P :python ' . b:ConqueTerm_Var . '.write(vim.eval("@@"))<CR>a'
177     silent execute 'nnoremap <silent> <buffer> ]p :python ' . b:ConqueTerm_Var . '.write(vim.eval("@@"))<CR>a'
178     silent execute 'nnoremap <silent> <buffer> [p :python ' . b:ConqueTerm_Var . '.write(vim.eval("@@"))<CR>a'
179     if has('gui_running')
180         silent execute 'inoremap <buffer> <S-Insert> <Esc>:<C-u>python ' . b:ConqueTerm_Var . ".write(vim.eval('@+'))<CR>a"
181     endif
182
183     " disable other normal mode keys which insert text
184     nnoremap <silent> <buffer> r :echo 'Replace mode disabled in shell.'<CR>
185     nnoremap <silent> <buffer> R :echo 'Replace mode disabled in shell.'<CR>
186     nnoremap <silent> <buffer> c :echo 'Change mode disabled in shell.'<CR>
187     nnoremap <silent> <buffer> C :echo 'Change mode disabled in shell.'<CR>
188     nnoremap <silent> <buffer> s :echo 'Change mode disabled in shell.'<CR>
189     nnoremap <silent> <buffer> S :echo 'Change mode disabled in shell.'<CR>
190
191     " help message about <Esc>
192     "nnoremap <silent> <buffer> <Esc> :echo 'To send an <E'.'sc> to the terminal, press <E'.'sc><E'.'sc> quickly in insert mode. Some programs, such as Vim, will also accept <Ctrl-c> as a substitute for <E'.'sc>'<CR><Esc>
193
194 endfunction "}}}
195
196 " send selected text from another buffer
197 function! conque_term#send_selected(type) "{{{
198     let reg_save = @@
199
200     " yank current selection
201     silent execute "normal! `<" . a:type . "`>y"
202
203     let @@ = substitute(@@, '^[\r\n]*', '', '')
204     let @@ = substitute(@@, '[\r\n]*$', '', '')
205
206     silent execute ":sb " . g:Conque_BufName
207     silent execute 'python ' . g:ConqueTerm_Var . '.paste_selection()'
208
209     let @@ = reg_save
210     startinsert!
211 endfunction "}}}
212
213 " read from all known conque buffers
214 function! conque_term#read_all() "{{{
215     " don't run this if we're in a conque buffer
216     if exists('b:ConqueTerm_Var')
217         return
218     endif
219
220     try
221         for i in range(1, g:ConqueTerm_Idx - 1)
222             execute 'python ConqueTerm_' . string(i) . '.read(1)'
223         endfor
224     catch
225         " probably a deleted buffer
226     endtry
227
228     " TODO - doesn't work
229     " restart updatetime
230     "call feedkeys("\x80\xFD\x35")
231 endfunction "}}}
232
233 " util function to add enough \s to pass a string to python
234 function! conque_term#python_escape(input) "{{{
235     let l:cleaned = a:input
236     let l:cleaned = substitute(l:cleaned, '\\', '\\\\', 'g')
237     let l:cleaned = substitute(l:cleaned, '\n', '\\n', 'g')
238     let l:cleaned = substitute(l:cleaned, '\r', '\\r', 'g')
239     let l:cleaned = substitute(l:cleaned, "'", "\\\\'", 'g')
240     return l:cleaned
241 endfunction "}}}
242
243 " **********************************************************************************************************
244 " **** PYTHON **********************************************************************************************
245 " **********************************************************************************************************
246
247 python << EOF
248
249 import vim, re, time, math
250
251 # CONFIG CONSTANTS  {{{
252
253 CONQUE_CTL = {
254      7:'bel', # bell
255      8:'bs',  # backspace
256      9:'tab', # tab
257     10:'nl',  # new line
258     13:'cr'   # carriage return
259 }
260 #    11 : 'vt',  # vertical tab
261 #    12 : 'ff',  # form feed
262 #    14 : 'so',  # shift out
263 #    15 : 'si'   # shift in
264
265 # Escape sequences 
266 CONQUE_ESCAPE = { 
267     'm':'font',
268     'J':'clear_screen',
269     'K':'clear_line',
270     '@':'add_spaces',
271     'A':'cursor_up',
272     'B':'cursor_down',
273     'C':'cursor_right',
274     'D':'cursor_left',
275     'G':'cursor_to_column',
276     'H':'cursor',
277     'P':'delete_chars',
278     'f':'cursor',
279     'g':'tab_clear',
280     'r':'set_coords',
281     'h':'set',
282     'l':'reset'
283 }
284 #    'L':'insert_lines',
285 #    'M':'delete_lines',
286 #    'd':'cusor_vpos',
287
288 # Alternate escape sequences, no [
289 CONQUE_ESCAPE_PLAIN = {
290     'D':'scroll_up',
291     'E':'next_line',
292     'H':'set_tab',
293     'M':'scroll_down'
294 }
295 #    'N':'single_shift_2',
296 #    'O':'single_shift_3',
297 #    '=':'alternate_keypad',
298 #    '>':'numeric_keypad',
299 #    '7':'save_cursor',
300 #    '8':'restore_cursor',
301
302 # Uber alternate escape sequences, with # or ?
303 CONQUE_ESCAPE_QUESTION = {
304     '1h':'new_line_mode',
305     '3h':'132_cols',
306     '4h':'smooth_scrolling',
307     '5h':'reverse_video',
308     '6h':'relative_origin',
309     '7h':'set_auto_wrap',
310     '8h':'set_auto_repeat',
311     '9h':'set_interlacing_mode',
312     '1l':'set_cursor_key',
313     '2l':'set_vt52',
314     '3l':'80_cols',
315     '4l':'set_jump_scrolling',
316     '5l':'normal_video',
317     '6l':'absolute_origin',
318     '7l':'reset_auto_wrap',
319     '8l':'reset_auto_repeat',
320     '9l':'reset_interlacing_mode'
321 }
322
323 CONQUE_ESCAPE_HASH = {
324     '8':'screen_alignment_test'
325
326 #    '3':'double_height_top',
327 #    '4':'double_height_bottom',
328 #    '5':'single_height_single_width',
329 #    '6':'single_height_double_width',
330
331 # Font codes {{{
332 CONQUE_FONT = {
333     0: {'description':'Normal (default)', 'attributes': {'cterm':'NONE','ctermfg':'NONE','ctermbg':'NONE','gui':'NONE','guifg':'NONE','guibg':'NONE'}, 'normal':True},
334     1: {'description':'Bold', 'attributes': {'cterm':'BOLD','gui':'BOLD'}, 'normal':False},
335     4: {'description':'Underlined', 'attributes': {'cterm':'UNDERLINE','gui':'UNDERLINE'}, 'normal':False},
336     5: {'description':'Blink (appears as Bold)', 'attributes': {'cterm':'BOLD','gui':'BOLD'}, 'normal':False},
337     7: {'description':'Inverse', 'attributes': {'cterm':'REVERSE','gui':'REVERSE'}, 'normal':False},
338     8: {'description':'Invisible (hidden)', 'attributes': {'ctermfg':'0','ctermbg':'0','guifg':'#000000','guibg':'#000000'}, 'normal':False},
339     22: {'description':'Normal (neither bold nor faint)', 'attributes': {'cterm':'NONE','gui':'NONE'}, 'normal':True},
340     24: {'description':'Not underlined', 'attributes': {'cterm':'NONE','gui':'NONE'}, 'normal':True},
341     25: {'description':'Steady (not blinking)', 'attributes': {'cterm':'NONE','gui':'NONE'}, 'normal':True},
342     27: {'description':'Positive (not inverse)', 'attributes': {'cterm':'NONE','gui':'NONE'}, 'normal':True},
343     28: {'description':'Visible (not hidden)', 'attributes': {'ctermfg':'NONE','ctermbg':'NONE','guifg':'NONE','guibg':'NONE'}, 'normal':True},
344     30: {'description':'Set foreground color to Black', 'attributes': {'ctermfg':'16','guifg':'#000000'}, 'normal':False},
345     31: {'description':'Set foreground color to Red', 'attributes': {'ctermfg':'1','guifg':'#ff0000'}, 'normal':False},
346     32: {'description':'Set foreground color to Green', 'attributes': {'ctermfg':'2','guifg':'#00ff00'}, 'normal':False},
347     33: {'description':'Set foreground color to Yellow', 'attributes': {'ctermfg':'3','guifg':'#ffff00'}, 'normal':False},
348     34: {'description':'Set foreground color to Blue', 'attributes': {'ctermfg':'4','guifg':'#0000ff'}, 'normal':False},
349     35: {'description':'Set foreground color to Magenta', 'attributes': {'ctermfg':'5','guifg':'#990099'}, 'normal':False},
350     36: {'description':'Set foreground color to Cyan', 'attributes': {'ctermfg':'6','guifg':'#009999'}, 'normal':False},
351     37: {'description':'Set foreground color to White', 'attributes': {'ctermfg':'7','guifg':'#ffffff'}, 'normal':False},
352     39: {'description':'Set foreground color to default (original)', 'attributes': {'ctermfg':'NONE','guifg':'NONE'}, 'normal':True},
353     40: {'description':'Set background color to Black', 'attributes': {'ctermbg':'16','guibg':'#000000'}, 'normal':False},
354     41: {'description':'Set background color to Red', 'attributes': {'ctermbg':'1','guibg':'#ff0000'}, 'normal':False},
355     42: {'description':'Set background color to Green', 'attributes': {'ctermbg':'2','guibg':'#00ff00'}, 'normal':False},
356     43: {'description':'Set background color to Yellow', 'attributes': {'ctermbg':'3','guibg':'#ffff00'}, 'normal':False},
357     44: {'description':'Set background color to Blue', 'attributes': {'ctermbg':'4','guibg':'#0000ff'}, 'normal':False},
358     45: {'description':'Set background color to Magenta', 'attributes': {'ctermbg':'5','guibg':'#990099'}, 'normal':False},
359     46: {'description':'Set background color to Cyan', 'attributes': {'ctermbg':'6','guibg':'#009999'}, 'normal':False},
360     47: {'description':'Set background color to White', 'attributes': {'ctermbg':'7','guibg':'#ffffff'}, 'normal':False},
361     49: {'description':'Set background color to default (original).', 'attributes': {'ctermbg':'NONE','guibg':'NONE'}, 'normal':True},
362     90: {'description':'Set foreground color to Black', 'attributes': {'ctermfg':'8','guifg':'#000000'}, 'normal':False},
363     91: {'description':'Set foreground color to Red', 'attributes': {'ctermfg':'9','guifg':'#ff0000'}, 'normal':False},
364     92: {'description':'Set foreground color to Green', 'attributes': {'ctermfg':'10','guifg':'#00ff00'}, 'normal':False},
365     93: {'description':'Set foreground color to Yellow', 'attributes': {'ctermfg':'11','guifg':'#ffff00'}, 'normal':False},
366     94: {'description':'Set foreground color to Blue', 'attributes': {'ctermfg':'12','guifg':'#0000ff'}, 'normal':False},
367     95: {'description':'Set foreground color to Magenta', 'attributes': {'ctermfg':'13','guifg':'#990099'}, 'normal':False},
368     96: {'description':'Set foreground color to Cyan', 'attributes': {'ctermfg':'14','guifg':'#009999'}, 'normal':False},
369     97: {'description':'Set foreground color to White', 'attributes': {'ctermfg':'15','guifg':'#ffffff'}, 'normal':False},
370     100: {'description':'Set background color to Black', 'attributes': {'ctermbg':'8','guibg':'#000000'}, 'normal':False},
371     101: {'description':'Set background color to Red', 'attributes': {'ctermbg':'9','guibg':'#ff0000'}, 'normal':False},
372     102: {'description':'Set background color to Green', 'attributes': {'ctermbg':'10','guibg':'#00ff00'}, 'normal':False},
373     103: {'description':'Set background color to Yellow', 'attributes': {'ctermbg':'11','guibg':'#ffff00'}, 'normal':False},
374     104: {'description':'Set background color to Blue', 'attributes': {'ctermbg':'12','guibg':'#0000ff'}, 'normal':False},
375     105: {'description':'Set background color to Magenta', 'attributes': {'ctermbg':'13','guibg':'#990099'}, 'normal':False},
376     106: {'description':'Set background color to Cyan', 'attributes': {'ctermbg':'14','guibg':'#009999'}, 'normal':False},
377     107: {'description':'Set background color to White', 'attributes': {'ctermbg':'15','guibg':'#ffffff'}, 'normal':False}
378
379 # }}}
380
381 # regular expression matching (almost) all control sequences
382 CONQUE_SEQ_REGEX       = re.compile(ur"(\u001b\[?\??#?[0-9;]*[a-zA-Z@]|\u001b\][0-9];.*?\u0007|[\u0007-\u000f])", re.UNICODE)
383 CONQUE_SEQ_REGEX_CTL   = re.compile(ur"^[\u0007-\u000f]$", re.UNICODE)
384 CONQUE_SEQ_REGEX_CSI   = re.compile(ur"^\u001b\[", re.UNICODE)
385 CONQUE_SEQ_REGEX_TITLE = re.compile(ur"^\u001b\]", re.UNICODE)
386 CONQUE_SEQ_REGEX_HASH  = re.compile(ur"^\u001b#", re.UNICODE)
387 CONQUE_SEQ_REGEX_ESC   = re.compile(ur"^\u001b", re.UNICODE)
388
389 # match table output
390 CONQUE_TABLE_OUTPUT   = re.compile("^\s*\|\s.*\s\|\s*$|^\s*\+[=+-]+\+\s*$")
391
392 # }}}
393
394 ###################################################################################################
395 class Conque:
396
397     # CLASS PROPERTIES {{{ 
398
399     # screen object
400     window          = None
401     screen          = None
402
403     # subprocess object
404     proc            = None
405
406     # terminal dimensions and scrolling region
407     columns         = 80 # same as $COLUMNS
408     lines           = 24 # same as $LINES
409     working_columns = 80 # can be changed by CSI ? 3 l/h
410     working_lines   = 24 # can be changed by CSI r
411
412     # top/bottom of the scroll region
413     top             = 1  # relative to top of screen
414     bottom          = 24 # relative to top of screen
415
416     # cursor position
417     l               = 1  # current cursor line
418     c               = 1  # current cursor column
419
420     # autowrap mode
421     autowrap        = True
422
423     # absolute coordinate mode
424     absolute_coords = True
425
426     # tabstop positions
427     tabstops        = []
428
429     # enable colors
430     enable_colors = True
431
432     # color changes
433     color_changes = {}
434
435     # color history
436     color_history = {}
437
438     # don't wrap table output
439     unwrap_tables = True
440
441     # wrap CUF/CUB around line breaks
442     wrap_cursor = False
443
444     # }}}
445
446     # constructor
447     def __init__(self): # {{{
448         self.window = vim.current.window
449         self.screen = ConqueScreen()
450         # }}}
451
452     # start program and initialize this instance
453     def open(self, command, options): # {{{
454
455         # int vars
456         self.columns = self.window.width
457         self.lines = self.window.height
458         self.working_columns = self.window.width
459         self.working_lines = self.window.height
460         self.bottom = self.window.height
461
462         # init color
463         self.enable_colors = options['color']
464
465         # init tabstops
466         self.init_tabstops()
467
468         # open command
469         self.proc = ConqueSubprocess()
470         self.proc.open(command, { 'TERM' : options['TERM'], 'CONQUE' : '1', 'LINES' : str(self.lines), 'COLUMNS' : str(self.columns)})
471         # }}}
472
473     # write to pty
474     def write(self, input): # {{{
475
476         # write and read
477         self.proc.write(input)
478         self.read(1)
479         # }}}
480
481     # read from pty, and update buffer
482     def read(self, timeout = 1): # {{{
483         # read from subprocess
484         output = self.proc.read(timeout)
485         # and strip null chars
486         output = output.replace(chr(0), '')
487
488         if output == '':
489             return
490
491         chunks = CONQUE_SEQ_REGEX.split(output)
492
493         # don't go through all the csi regex if length is one (no matches)
494         if len(chunks) == 1:
495
496             self.plain_text(chunks[0])
497
498         else:
499             for s in chunks:
500                 if s == '':
501                     continue
502
503                 # Check for control character match {{{
504                 if CONQUE_SEQ_REGEX_CTL.match(s[0]):
505
506                     nr = ord(s[0])
507                     if nr in CONQUE_CTL:
508                         getattr(self, 'ctl_' + CONQUE_CTL[nr])()
509                     else:
510
511                         pass
512                     # }}}
513
514                 # check for escape sequence match {{{
515                 elif CONQUE_SEQ_REGEX_CSI.match(s):
516
517                     if s[-1] in CONQUE_ESCAPE:
518                         csi = self.parse_csi(s[2:])
519
520                         getattr(self, 'csi_' + CONQUE_ESCAPE[s[-1]])(csi)
521                     else:
522
523                         pass
524                     # }}}
525         
526                 # check for title match {{{
527                 elif CONQUE_SEQ_REGEX_TITLE.match(s):
528
529                     self.change_title(s[2], s[4:-1])
530                     # }}}
531         
532                 # check for hash match {{{
533                 elif CONQUE_SEQ_REGEX_HASH.match(s):
534
535                     if s[-1] in CONQUE_ESCAPE_HASH:
536                         getattr(self, 'hash_' + CONQUE_ESCAPE_HASH[s[-1]])()
537                     else:
538
539                         pass
540                     # }}}
541                 
542                 # check for other escape match {{{
543                 elif CONQUE_SEQ_REGEX_ESC.match(s):
544
545                     if s[-1] in CONQUE_ESCAPE_PLAIN:
546                         getattr(self, 'esc_' + CONQUE_ESCAPE_PLAIN[s[-1]])()
547                     else:
548
549                         pass
550                     # }}}
551                 
552                 # else process plain text {{{
553                 else:
554                     self.plain_text(s)
555                     # }}}
556
557         # set cursor position
558         self.screen.set_cursor(self.l, self.c)
559
560         vim.command('redraw')
561
562     # }}}
563
564     # for polling
565     def auto_read(self): # {{{
566         self.read(1)
567         if self.c == 1:
568             vim.command('call feedkeys("\<F23>", "t")')
569         else:
570             vim.command('call feedkeys("\<F22>", "t")')
571         self.screen.set_cursor(self.l, self.c)
572     # }}}
573
574     ###############################################################################################
575     # Plain text # {{{
576
577     def plain_text(self, input):
578
579         current_line = self.screen[self.l]
580
581         if len(current_line) < self.working_columns:
582             current_line = current_line + ' ' * (self.c - len(current_line))
583
584         # if line is wider than screen
585         if self.c + len(input) - 1 > self.working_columns:
586             # Table formatting hack
587             if self.unwrap_tables and CONQUE_TABLE_OUTPUT.match(input):
588                 self.screen[self.l] = current_line[ : self.c - 1] + input + current_line[ self.c + len(input) - 1 : ]
589                 self.apply_color(self.c, self.c + len(input))
590                 self.c += len(input)
591                 return
592
593             diff = self.c + len(input) - self.working_columns - 1
594             # if autowrap is enabled
595             if self.autowrap:
596                 self.screen[self.l] = current_line[ : self.c - 1] + input[ : -1 * diff ]
597                 self.apply_color(self.c, self.working_columns)
598                 self.ctl_nl()
599                 self.ctl_cr()
600                 remaining = input[ -1 * diff : ]
601
602                 self.plain_text(remaining)
603             else:
604                 self.screen[self.l] = current_line[ : self.c - 1] + input[ : -1 * diff - 1 ] + input[-1]
605                 self.apply_color(self.c, self.working_columns)
606                 self.c = self.working_columns
607
608         # no autowrap
609         else:
610             self.screen[self.l] = current_line[ : self.c - 1] + input + current_line[ self.c + len(input) - 1 : ]
611             self.apply_color(self.c, self.c + len(input))
612             self.c += len(input)
613
614     def apply_color(self, start, end):
615
616         # stop here if coloration is disabled
617         if not self.enable_colors:
618             return
619
620         real_line = self.screen.get_real_line(self.l)
621
622         # check for previous overlapping coloration
623
624         to_del = []
625         if self.color_history.has_key(real_line):
626             for i in range(len(self.color_history[real_line])):
627                 syn = self.color_history[real_line][i]
628
629                 if syn['start'] >= start and syn['start'] < end:
630
631                     vim.command('syn clear ' + syn['name'])
632                     to_del.append(i)
633                     # outside
634                     if syn['end'] > end:
635
636                         self.exec_highlight(real_line, end, syn['end'], syn['highlight'])
637                 elif syn['end'] > start and syn['end'] <= end:
638
639                     vim.command('syn clear ' + syn['name'])
640                     to_del.append(i)
641                     # outside
642                     if syn['start'] < start:
643
644                         self.exec_highlight(real_line, syn['start'], start, syn['highlight'])
645
646         if len(to_del) > 0:
647             to_del.reverse()
648             for di in to_del:
649                 del self.color_history[real_line][di]
650
651         # if there are no new colors
652         if len(self.color_changes) == 0:
653             return
654
655         highlight = ''
656         for attr in self.color_changes.keys():
657             highlight = highlight + ' ' + attr + '=' + self.color_changes[attr]
658
659         # execute the highlight
660         self.exec_highlight(real_line, start, end, highlight)
661
662     def exec_highlight(self, real_line, start, end, highlight):
663         unique_key = str(self.proc.pid)
664
665         syntax_name = 'EscapeSequenceAt_' + unique_key + '_' + str(self.l) + '_' + str(start) + '_' + str(len(self.color_history) + 1)
666         syntax_options = ' contains=ALLBUT,ConqueString,MySQLString,MySQLKeyword oneline'
667         syntax_region = 'syntax match ' + syntax_name + ' /\%' + str(real_line) + 'l\%>' + str(start - 1) + 'c.*\%<' + str(end + 1) + 'c/' + syntax_options
668         syntax_highlight = 'highlight ' + syntax_name + highlight
669
670         vim.command(syntax_region)
671         vim.command(syntax_highlight)
672
673         # add syntax name to history
674         if not self.color_history.has_key(real_line):
675             self.color_history[real_line] = []
676
677         self.color_history[real_line].append({'name':syntax_name, 'start':start, 'end':end, 'highlight':highlight})
678
679     # }}}
680
681     ###############################################################################################
682     # Control functions {{{
683
684     def ctl_nl(self):
685         # if we're in a scrolling region, scroll instead of moving cursor down
686         if self.lines != self.working_lines and self.l == self.bottom:
687             del self.screen[self.top]
688             self.screen.insert(self.bottom, '')
689         elif self.l == self.bottom:
690             self.screen.append('')
691         else:
692             self.l += 1
693
694         self.color_changes = {}
695
696     def ctl_cr(self):
697         self.c = 1
698
699         self.color_changes = {}
700
701     def ctl_bs(self):
702         if self.c > 1:
703             self.c += -1
704
705     def ctl_bel(self):
706         print 'BELL'
707
708     def ctl_tab(self):
709         # default tabstop location
710         ts = self.working_columns
711
712         # check set tabstops
713         for i in range(self.c, len(self.tabstops)):
714             if self.tabstops[i]:
715                 ts = i + 1
716                 break
717
718         self.c = ts
719
720     # }}}
721
722     ###############################################################################################
723     # CSI functions {{{
724
725     def csi_font(self, csi): # {{{
726         if not self.enable_colors:
727             return
728         
729         # defaults to 0
730         if len(csi['vals']) == 0:
731             csi['vals'] = [0]
732
733         for val in csi['vals']:
734             if CONQUE_FONT.has_key(val):
735
736                 # ignore starting normal colors
737                 if CONQUE_FONT[val]['normal'] and len(self.color_changes) == 0:
738
739                     continue
740                 # clear color changes
741                 elif CONQUE_FONT[val]['normal']:
742
743                     self.color_changes = {}
744                 # save these color attributes for next plain_text() call
745                 else:
746
747                     for attr in CONQUE_FONT[val]['attributes'].keys():
748                         if self.color_changes.has_key(attr) and (attr == 'cterm' or attr == 'gui'):
749                             self.color_changes[attr] += ',' + CONQUE_FONT[val]['attributes'][attr]
750                         else:
751                             self.color_changes[attr] = CONQUE_FONT[val]['attributes'][attr]
752         # }}}
753
754     def csi_clear_line(self, csi): # {{{
755
756         # this escape defaults to 0
757         if len(csi['vals']) == 0:
758             csi['val'] = 0
759
760         # 0 means cursor right
761         if csi['val'] == 0:
762             self.screen[self.l] = self.screen[self.l][0 : self.c - 1]
763
764         # 1 means cursor left
765         elif csi['val'] == 1:
766             self.screen[self.l] = ' ' * (self.c) + self.screen[self.l][self.c : ]
767
768         # clear entire line
769         elif csi['val'] == 2:
770             self.screen[self.l] = ''
771
772         # clear colors
773         if csi['val'] == 2 or (csi['val'] == 0 and self.c == 1):
774             real_line = self.screen.get_real_line(self.l)
775             if self.color_history.has_key(real_line):
776                 for syn in self.color_history[real_line]:
777                     vim.command('syn clear ' + syn['name'])
778
779         # }}}
780
781     def csi_cursor_right(self, csi): # {{{
782         # we use 1 even if escape explicitly specifies 0
783         if csi['val'] == 0:
784             csi['val'] = 1
785
786         if self.wrap_cursor and self.c + csi['val'] > self.working_columns:
787             self.l += int(math.floor( (self.c + csi['val']) / self.working_columns ))
788             self.c = (self.c + csi['val']) % self.working_columns
789             return
790
791         self.c = self.bound(self.c + csi['val'], 1, self.working_columns)
792         # }}}
793
794     def csi_cursor_left(self, csi): # {{{
795         # we use 1 even if escape explicitly specifies 0
796         if csi['val'] == 0:
797             csi['val'] = 1
798
799         if self.wrap_cursor and csi['val'] >= self.c:
800             self.l += int(math.floor( (self.c - csi['val']) / self.working_columns ))
801             self.c = self.working_columns - (csi['val'] - self.c) % self.working_columns
802             return
803
804         self.c = self.bound(self.c - csi['val'], 1, self.working_columns)
805         # }}}
806
807     def csi_cursor_to_column(self, csi): # {{{
808         self.c = self.bound(csi['val'], 1, self.working_columns)
809         # }}}
810
811     def csi_cursor_up(self, csi): # {{{
812         self.l = self.bound(self.l - csi['val'], self.top, self.bottom)
813
814         self.color_changes = {}
815         # }}}
816
817     def csi_cursor_down(self, csi): # {{{
818         self.l = self.bound(self.l + csi['val'], self.top, self.bottom)
819
820         self.color_changes = {}
821         # }}}
822
823     def csi_clear_screen(self, csi): # {{{
824         # default to 0
825         if len(csi['vals']) == 0:
826             csi['val'] = 0
827
828         # 2 == clear entire screen
829         if csi['val'] == 2:
830             self.l = 1
831             self.c = 1
832             self.screen.clear()
833
834         # 0 == clear down
835         elif csi['val'] == 0:
836             for l in range(self.bound(self.l + 1, 1, self.lines), self.lines + 1):
837                 self.screen[l] = ''
838             
839             # clear end of current line
840             self.csi_clear_line(self.parse_csi('K'))
841
842         # 1 == clear up
843         elif csi['val'] == 1:
844             for l in range(1, self.bound(self.l, 1, self.lines + 1)):
845                 self.screen[l] = ''
846
847             # clear beginning of current line
848             self.csi_clear_line(self.parse_csi('1K'))
849
850         # clear coloration
851         if csi['val'] == 2 or csi['val'] == 0:
852             real_line = self.screen.get_real_line(self.l)
853             for line in self.color_history.keys():
854                 if line >= real_line:
855                     for syn in self.color_history[line]:
856                         vim.command('syn clear ' + syn['name'])
857
858         self.color_changes = {}
859         # }}}
860
861     def csi_delete_chars(self, csi): # {{{
862         self.screen[self.l] = self.screen[self.l][ : self.c ] + self.screen[self.l][ self.c + csi['val'] : ]
863         # }}}
864
865     def csi_add_spaces(self, csi): # {{{
866         self.screen[self.l] = self.screen[self.l][ : self.c - 1] + ' ' * csi['val'] + self.screen[self.l][self.c : ]
867         # }}}
868
869     def csi_cursor(self, csi): # {{{
870         if len(csi['vals']) == 2:
871             new_line = csi['vals'][0]
872             new_col = csi['vals'][1]
873         else:
874             new_line = 1
875             new_col = 1
876
877         if self.absolute_coords:
878             self.l = self.bound(new_line, 1, self.lines)
879         else:
880             self.l = self.bound(self.top + new_line - 1, self.top, self.bottom)
881
882         self.c = self.bound(new_col, 1, self.working_columns)
883         if self.c > len(self.screen[self.l]):
884             self.screen[self.l] = self.screen[self.l] + ' ' * (self.c - len(self.screen[self.l]))
885
886         # }}}
887
888     def csi_set_coords(self, csi): # {{{
889         if len(csi['vals']) == 2:
890             new_start = csi['vals'][0]
891             new_end = csi['vals'][1]
892         else:
893             new_start = 1
894             new_end = self.window.height
895
896         self.top = new_start
897         self.bottom = new_end
898         self.working_lines = new_end - new_start + 1
899
900         # if cursor is outside scrolling region, reset it
901         if self.l < self.top:
902             self.l = self.top
903         elif self.l > self.bottom:
904             self.l = self.bottom
905
906         self.color_changes = {}
907         # }}}
908
909     def csi_tab_clear(self, csi): # {{{
910         # this escape defaults to 0
911         if len(csi['vals']) == 0:
912             csi['val'] = 0
913
914         if csi['val'] == 0:
915             self.tabstops[self.c - 1] = False
916         elif csi['val'] == 3:
917             for i in range(0, self.columns + 1):
918                 self.tabstops[i] = False
919         # }}}
920
921     def csi_set(self, csi): # {{{
922         # 132 cols
923         if csi['val'] == 3: 
924             self.csi_clear_screen(self.parse_csi('2J'))
925             self.working_columns = 132
926
927         # relative_origin
928         elif csi['val'] == 6: 
929             self.absolute_coords = False
930
931         # set auto wrap
932         elif csi['val'] == 7: 
933             self.autowrap = True
934
935         self.color_changes = {}
936         # }}}
937
938     def csi_reset(self, csi): # {{{
939         # 80 cols
940         if csi['val'] == 3: 
941             self.csi_clear_screen(self.parse_csi('2J'))
942             self.working_columns = 80
943
944         # absolute origin
945         elif csi['val'] == 6: 
946             self.absolute_coords = True
947
948         # reset auto wrap
949         elif csi['val'] == 7: 
950             self.autowrap = False
951
952         self.color_changes = {}
953         # }}}
954
955     # }}}
956
957     ###############################################################################################
958     # ESC functions {{{
959
960     def esc_scroll_up(self): # {{{
961         self.ctl_nl()
962
963         self.color_changes = {}
964         # }}}
965
966     def esc_next_line(self): # {{{
967         self.ctl_nl()
968         self.c = 1
969         # }}}
970
971     def esc_set_tab(self): # {{{
972
973         if self.c <= len(self.tabstops):
974             self.tabstops[self.c - 1] = True
975         # }}}
976
977     def esc_scroll_down(self): # {{{
978         if self.l == self.top:
979             del self.screen[self.bottom]
980             self.screen.insert(self.top, '')
981         else:
982             self.l += -1
983
984         self.color_changes = {}
985         # }}}
986
987     # }}}
988
989     ###############################################################################################
990     # HASH functions {{{
991
992     def hash_screen_alignment_test(self): # {{{
993         self.csi_clear_screen(self.parse_csi('2J'))
994         self.working_lines = self.lines
995         for l in range(1, self.lines + 1):
996             self.screen[l] = 'E' * self.working_columns
997         # }}}
998
999     # }}}
1000
1001     ###############################################################################################
1002     # Random stuff {{{
1003
1004     def change_title(self, key, val):
1005
1006         if key == '0' or key == '2':
1007
1008             vim.command('setlocal statusline=' + re.escape(val))
1009
1010     def paste(self):
1011         self.write(vim.eval('@@'))
1012         self.read(50)
1013
1014     def paste_selection(self):
1015         self.write(vim.eval('@@'))
1016
1017     def update_window_size(self):
1018         # resize if needed
1019         if self.window.width != self.columns or self.window.height != self.lines:
1020
1021             # reset all window size attributes to default
1022             self.columns = self.window.width
1023             self.lines = self.window.height
1024             self.working_columns = self.window.width
1025             self.working_lines = self.window.height
1026             self.bottom = self.window.height
1027
1028             # reset screen object attributes
1029             self.l = self.screen.reset_size(self.l)
1030
1031             # reset tabstops
1032             self.init_tabstops()
1033
1034             # signal process that screen size has changed
1035             self.proc.window_resize(self.lines, self.columns)
1036
1037     def init_tabstops(self):
1038         for i in range(0, self.columns + 1):
1039             if i % 8 == 0:
1040                 self.tabstops.append(True)
1041             else:
1042                 self.tabstops.append(False)
1043
1044     # }}}
1045
1046     ###############################################################################################
1047     # Utility {{{
1048     
1049     def parse_csi(self, s): # {{{
1050         attr = { 'key' : s[-1], 'flag' : '', 'val' : 1, 'vals' : [] }
1051
1052         if len(s) == 1:
1053             return attr
1054
1055         full = s[0:-1]
1056
1057         if full[0] == '?':
1058             full = full[1:]
1059             attr['flag'] = '?'
1060
1061         if full != '':
1062             vals = full.split(';')
1063             for val in vals:
1064
1065                 val = re.sub("\D", "", val)
1066
1067                 if val != '':
1068                     attr['vals'].append(int(val))
1069
1070         if len(attr['vals']) == 1:
1071             attr['val'] = int(attr['vals'][0])
1072             
1073         return attr
1074         # }}}
1075
1076     def bound(self, val, min, max): # {{{
1077         if val > max:
1078             return max
1079
1080         if val < min:
1081             return min
1082
1083         return val
1084         # }}}
1085
1086     # }}}
1087
1088 import os, signal, pty, tty, select, fcntl, termios, struct
1089
1090 ###################################################################################################
1091 class ConqueSubprocess:
1092
1093     # process id
1094     pid = 0
1095     
1096     # stdout+stderr file descriptor
1097     fd = None
1098
1099     # constructor
1100     def __init__(self): # {{{
1101         self.pid = 0
1102         # }}}
1103
1104     # create the pty or whatever (whatever == windows)
1105     def open(self, command, env = {}): # {{{
1106         command_arr  = command.split()
1107         executable   = command_arr[0]
1108         args         = command_arr
1109
1110         try:
1111             self.pid, self.fd = pty.fork()
1112
1113         except:
1114             pass
1115
1116         # child proc, replace with command after altering terminal attributes
1117         if self.pid == 0:
1118
1119             # set requested environment variables
1120             for k in env.keys():
1121                 os.environ[k] = env[k]
1122
1123             # set some attributes
1124             try:
1125                 attrs = tty.tcgetattr(1)
1126                 attrs[0] = attrs[0] ^ tty.IGNBRK
1127                 attrs[0] = attrs[0] | tty.BRKINT | tty.IXANY | tty.IMAXBEL
1128                 attrs[2] = attrs[2] | tty.HUPCL
1129                 attrs[3] = attrs[3] | tty.ICANON | tty.ECHO | tty.ISIG | tty.ECHOKE
1130                 attrs[6][tty.VMIN]  = 1
1131                 attrs[6][tty.VTIME] = 0
1132                 tty.tcsetattr(1, tty.TCSANOW, attrs)
1133             except:
1134                 pass
1135
1136             os.execvp(executable, args)
1137
1138         # else master, do nothing
1139         else:
1140             pass
1141
1142         # }}}
1143
1144     # read from pty
1145     # XXX - select.poll() doesn't work in OS X!!!!!!!
1146     def read(self, timeout = 1): # {{{
1147
1148         output = ''
1149         read_timeout = float(timeout) / 1000
1150
1151         try:
1152             # what, no do/while?
1153             while 1:
1154                 s_read, s_write, s_error = select.select( [ self.fd ], [], [], read_timeout)
1155
1156                 lines = ''
1157                 for s_fd in s_read:
1158                     try:
1159                         lines = os.read( self.fd, 32 )
1160                     except:
1161                         pass
1162                     output = output + lines
1163
1164                 if lines == '':
1165                     break
1166         except:
1167             pass
1168
1169         return output
1170         # }}}
1171
1172     # I guess this one's not bad
1173     def write(self, input): # {{{
1174         try:
1175             os.write(self.fd, input)
1176         except:
1177             pass
1178         # }}}
1179
1180     # signal process
1181     def signal(self, signum): # {{{
1182         try:
1183             os.kill(self.pid, signum)
1184         except:
1185             pass
1186         # }}}
1187
1188     # get process status
1189     def get_status(self): #{{{
1190
1191         p_status = True
1192
1193         try:
1194             if os.waitpid( self.pid, os.WNOHANG )[0]:
1195                 p_status = False
1196         except:
1197             p_status = False
1198
1199         return p_status
1200
1201         # }}}
1202
1203     # update window size in kernel, then send SIGWINCH to fg process
1204     def window_resize(self, lines, columns): # {{{
1205         try:
1206             fcntl.ioctl(self.fd, termios.TIOCSWINSZ, struct.pack("HHHH", lines, columns, 0, 0))
1207             os.kill(self.pid, signal.SIGWINCH)
1208         except:
1209             pass
1210
1211         # }}}
1212
1213 ###################################################################################################
1214 # ConqueScreen is an extention of the vim.current.buffer object
1215 # It restricts the working indices of the buffer object to the scroll region which pty is expecting
1216 # It also uses 1-based indexes, to match escape sequence commands
1217 #
1218 # E.g.:
1219 #   s = ConqueScreen()
1220 #   ...
1221 #   s[5] = 'Set 5th line in terminal to this line'
1222 #   s.append('Add new line to terminal')
1223 #   s[5] = 'Since previous append() command scrolled the terminal down, this is a different line than first cb[5] call'
1224 #
1225
1226 import vim
1227
1228 class ConqueScreen(object):
1229
1230     # CLASS PROPERTIES  {{{
1231
1232     # the buffer
1233     buffer          = None
1234     window          = None
1235
1236     # screen and scrolling regions
1237     screen_top      = 1
1238
1239     # screen width
1240     screen_width    = 80
1241     screen_height    = 80
1242
1243     # }}}
1244
1245     def __init__(self): # {{{
1246         self.buffer = vim.current.buffer
1247         self.window = vim.current.window
1248
1249         self.screen_top = 1
1250         self.screen_width = self.window.width
1251         self.screen_height = self.window.height
1252     # }}}
1253
1254     ###############################################################################################
1255     # List overload {{{
1256     def __len__(self): # {{{
1257         return len(self.buffer)
1258     # }}}
1259
1260     def __getitem__(self, key): # {{{
1261         real_line = self.get_real_idx(key)
1262
1263         # if line is past buffer end, add lines to buffer
1264         if real_line >= len(self.buffer):
1265             for i in range(len(self.buffer), real_line + 1):
1266                 self.append(' ' * self.screen_width)
1267
1268         return self.buffer[ real_line ]
1269     # }}}
1270
1271     def __setitem__(self, key, value): # {{{
1272         real_line = self.get_real_idx(key)
1273
1274         # if line is past end of screen, append
1275         if real_line == len(self.buffer):
1276             self.buffer.append(value)
1277         else:
1278             self.buffer[ real_line ] = value
1279     # }}}
1280
1281     def __delitem__(self, key): # {{{
1282         del self.buffer[ self.screen_top + key - 2 ]
1283     # }}}
1284
1285     def append(self, value): # {{{
1286         if len(self.buffer) > self.screen_top + self.screen_height - 1:
1287             self.buffer[len(self.buffer) - 1] = value
1288         else:
1289             self.buffer.append(value)
1290
1291         if len(self.buffer) > self.screen_top + self.screen_height - 1:
1292             self.screen_top += 1
1293         if vim.current.buffer.number == self.buffer.number:
1294             vim.command('normal G')
1295     # }}}
1296
1297     def insert(self, line, value): # {{{
1298
1299         l = self.screen_top + line - 2
1300         self.buffer[l:l] = [ value ]
1301     
1302     # }}}
1303     # }}}
1304
1305     ###############################################################################################
1306     # Util {{{
1307     def get_top(self): # {{{
1308         return self.screen_top
1309     # }}}
1310
1311     def get_real_idx(self, line): # {{{
1312         return (self.screen_top + line - 2)
1313     # }}}
1314
1315     def get_real_line(self, line): # {{{
1316         return (self.screen_top + line - 1)
1317     # }}}
1318
1319     def set_screen_width(self, width): # {{{
1320         self.screen_width = width
1321     # }}}
1322
1323     # }}}
1324
1325     ###############################################################################################
1326     def clear(self): # {{{
1327         self.buffer.append(' ')
1328         vim.command('normal Gzt')
1329         self.screen_top = len(self.buffer)
1330     # }}}
1331
1332     def set_cursor(self, line, column): # {{{
1333         # figure out line
1334         real_line =  self.screen_top + line - 1
1335         if real_line > len(self.buffer):
1336             for l in range(len(self.buffer) - 1, real_line):
1337                 self.buffer.append('')
1338
1339         # figure out column
1340         real_column = column
1341         if len(self.buffer[real_line - 1]) < real_column:
1342             self.buffer[real_line - 1] = self.buffer[real_line - 1] + ' ' * (real_column - len(self.buffer[real_line - 1]))
1343
1344         # XXX - Using python's version makes lots of super-fun segfaults
1345         self.window.cursor = (real_line, real_column - 1)
1346         #vim.command('call cursor(' + str(real_line) + ', ' + str(real_column) + ')')
1347     # }}}
1348
1349     def reset_size(self, line): # {{{
1350
1351         # save cursor line number
1352         real_line = self.screen_top + line
1353
1354         # reset screen size
1355         self.screen_width = self.window.width
1356         self.screen_height = self.window.height
1357         self.screen_top = len(self.buffer) - self.window.height + 1
1358         if self.screen_top < 1:
1359             self.screen_top = 1
1360
1361         # align bottom of buffer to bottom of screen
1362         vim.command('normal ' + str(self.screen_height) + 'kG')
1363
1364         # return new relative line number
1365         return (real_line - self.screen_top)
1366     # }}}
1367
1368     def scroll_to_bottom(self): # {{{
1369         self.window.cursor = (len(self.buffer) - 1, 1)
1370     # }}}
1371         
1372     def align(self): # {{{
1373         # align bottom of buffer to bottom of screen
1374         vim.command('normal ' + str(self.screen_height) + 'kG')
1375     # }}}
1376
1377 EOF
1378