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