1 " FILE: plugin/conque_term.vim {{{
2 " AUTHOR: Nico Raffo <nicoraffo@gmail.com>
4 " VERSION: 1.0, for Vim 7.0
6 " Conque - pty interaction in Vim
7 " Copyright (C) 2009-2010 Nico Raffo
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:
18 " The above copyright notice and this permission notice shall be included in
19 " all copies or substantial portions of the Software.
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
30 " **********************************************************************************************************
31 " **** VIM FUNCTIONS ***************************************************************************************
32 " **********************************************************************************************************
35 function! conque_term#open(...) "{{{
36 let command = get(a:000, 0, '')
37 let hooks = get(a:000, 1, [])
39 " bare minimum validation
41 echohl WarningMsg | echomsg "No command found" | echohl None
44 let l:cargs = split(command, '\s')
45 if !executable(l:cargs[0])
46 echohl WarningMsg | echomsg "Not an executable" | echohl None
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
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 . ")"
64 echohl WarningMsg | echomsg "Unable to open command: " . command | echohl None
68 " set buffer mappings and auto commands
69 call conque_term#set_mappings()
76 function! conque_term#set_buffer_settings(command, pre_hooks) "{{{
78 " optional hooks to execute, e.g. 'split'
82 silent execute "edit " . g:Conque_BufName
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
102 " set key mappings and auto commands
103 function! conque_term#set_mappings() "{{{
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)'
108 " check for resized/scrolled buffer when entering buffer
109 execute 'autocmd BufEnter <buffer> python ' . b:ConqueTerm_Var . '.update_window_size()'
111 " set/reset updatetime on entering/exiting buffer
112 autocmd BufEnter <buffer> set updatetime=50
113 autocmd BufLeave <buffer> set updatetime=1000
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()'
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()
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()'
130 for c in range(1, 31)
135 silent execute 'inoremap <silent> <buffer> <C-' . nr2char(64 + c) . '> <C-o>:python ' . b:ConqueTerm_Var . '.write(chr(' . c . '))<CR>'
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>'
141 for i in range(33, 127)
144 silent execute "inoremap <silent> <buffer> <Bar> <C-o>:python " . b:ConqueTerm_Var . ".write(chr(124))<CR>"
147 silent execute "inoremap <silent> <buffer> " . nr2char(i) . " <C-o>:python " . b:ConqueTerm_Var . ".write(chr(" . i . "))<CR>"
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>"
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>'
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'
171 " send selected text into conque
172 vnoremap <silent> <F9> :<C-u>call conque_term#send_selected(visualmode())<CR>
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"
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>
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>
196 " send selected text from another buffer
197 function! conque_term#send_selected(type) "{{{
200 " yank current selection
201 silent execute "normal! `<" . a:type . "`>y"
203 let @@ = substitute(@@, '^[\r\n]*', '', '')
204 let @@ = substitute(@@, '[\r\n]*$', '', '')
206 silent execute ":sb " . g:Conque_BufName
207 silent execute 'python ' . g:ConqueTerm_Var . '.paste_selection()'
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')
221 for i in range(1, g:ConqueTerm_Idx - 1)
222 execute 'python ConqueTerm_' . string(i) . '.read(1)'
225 " probably a deleted buffer
228 " TODO - doesn't work
230 "call feedkeys("\x80\xFD\x35")
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')
243 " **********************************************************************************************************
244 " **** PYTHON **********************************************************************************************
245 " **********************************************************************************************************
249 import vim, re, time, math
251 # CONFIG CONSTANTS {{{
258 13:'cr' # carriage return
260 # 11 : 'vt', # vertical tab
261 # 12 : 'ff', # form feed
262 # 14 : 'so', # shift out
263 # 15 : 'si' # shift in
275 'G':'cursor_to_column',
284 # 'L':'insert_lines',
285 # 'M':'delete_lines',
288 # Alternate escape sequences, no [
289 CONQUE_ESCAPE_PLAIN = {
295 # 'N':'single_shift_2',
296 # 'O':'single_shift_3',
297 # '=':'alternate_keypad',
298 # '>':'numeric_keypad',
300 # '8':'restore_cursor',
302 # Uber alternate escape sequences, with # or ?
303 CONQUE_ESCAPE_QUESTION = {
304 '1h':'new_line_mode',
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',
315 '4l':'set_jump_scrolling',
317 '6l':'absolute_origin',
318 '7l':'reset_auto_wrap',
319 '8l':'reset_auto_repeat',
320 '9l':'reset_interlacing_mode'
323 CONQUE_ESCAPE_HASH = {
324 '8':'screen_alignment_test'
326 # '3':'double_height_top',
327 # '4':'double_height_bottom',
328 # '5':'single_height_single_width',
329 # '6':'single_height_double_width',
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}
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)
390 CONQUE_TABLE_OUTPUT = re.compile("^\s*\|\s.*\s\|\s*$|^\s*\+[=+-]+\+\s*$")
394 ###################################################################################################
397 # CLASS PROPERTIES {{{
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
412 # top/bottom of the scroll region
413 top = 1 # relative to top of screen
414 bottom = 24 # relative to top of screen
417 l = 1 # current cursor line
418 c = 1 # current cursor column
423 # absolute coordinate mode
424 absolute_coords = True
438 # don't wrap table output
441 # wrap CUF/CUB around line breaks
447 def __init__(self): # {{{
448 self.window = vim.current.window
449 self.screen = ConqueScreen()
452 # start program and initialize this instance
453 def open(self, command, options): # {{{
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
463 self.enable_colors = options['color']
469 self.proc = ConqueSubprocess()
470 self.proc.open(command, { 'TERM' : options['TERM'], 'CONQUE' : '1', 'LINES' : str(self.lines), 'COLUMNS' : str(self.columns)})
474 def write(self, input): # {{{
477 self.proc.write(input)
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), '')
491 chunks = CONQUE_SEQ_REGEX.split(output)
493 # don't go through all the csi regex if length is one (no matches)
496 self.plain_text(chunks[0])
503 # Check for control character match {{{
504 if CONQUE_SEQ_REGEX_CTL.match(s[0]):
508 getattr(self, 'ctl_' + CONQUE_CTL[nr])()
514 # check for escape sequence match {{{
515 elif CONQUE_SEQ_REGEX_CSI.match(s):
517 if s[-1] in CONQUE_ESCAPE:
518 csi = self.parse_csi(s[2:])
520 getattr(self, 'csi_' + CONQUE_ESCAPE[s[-1]])(csi)
526 # check for title match {{{
527 elif CONQUE_SEQ_REGEX_TITLE.match(s):
529 self.change_title(s[2], s[4:-1])
532 # check for hash match {{{
533 elif CONQUE_SEQ_REGEX_HASH.match(s):
535 if s[-1] in CONQUE_ESCAPE_HASH:
536 getattr(self, 'hash_' + CONQUE_ESCAPE_HASH[s[-1]])()
542 # check for other escape match {{{
543 elif CONQUE_SEQ_REGEX_ESC.match(s):
545 if s[-1] in CONQUE_ESCAPE_PLAIN:
546 getattr(self, 'esc_' + CONQUE_ESCAPE_PLAIN[s[-1]])()
552 # else process plain text {{{
557 # set cursor position
558 self.screen.set_cursor(self.l, self.c)
560 vim.command('redraw')
565 def auto_read(self): # {{{
568 vim.command('call feedkeys("\<F23>", "t")')
570 vim.command('call feedkeys("\<F22>", "t")')
571 self.screen.set_cursor(self.l, self.c)
574 ###############################################################################################
577 def plain_text(self, input):
579 current_line = self.screen[self.l]
581 if len(current_line) < self.working_columns:
582 current_line = current_line + ' ' * (self.c - len(current_line))
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))
593 diff = self.c + len(input) - self.working_columns - 1
594 # if autowrap is enabled
596 self.screen[self.l] = current_line[ : self.c - 1] + input[ : -1 * diff ]
597 self.apply_color(self.c, self.working_columns)
600 remaining = input[ -1 * diff : ]
602 self.plain_text(remaining)
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
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))
614 def apply_color(self, start, end):
616 # stop here if coloration is disabled
617 if not self.enable_colors:
620 real_line = self.screen.get_real_line(self.l)
622 # check for previous overlapping coloration
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]
629 if syn['start'] >= start and syn['start'] < end:
631 vim.command('syn clear ' + syn['name'])
636 self.exec_highlight(real_line, end, syn['end'], syn['highlight'])
637 elif syn['end'] > start and syn['end'] <= end:
639 vim.command('syn clear ' + syn['name'])
642 if syn['start'] < start:
644 self.exec_highlight(real_line, syn['start'], start, syn['highlight'])
649 del self.color_history[real_line][di]
651 # if there are no new colors
652 if len(self.color_changes) == 0:
656 for attr in self.color_changes.keys():
657 highlight = highlight + ' ' + attr + '=' + self.color_changes[attr]
659 # execute the highlight
660 self.exec_highlight(real_line, start, end, highlight)
662 def exec_highlight(self, real_line, start, end, highlight):
663 unique_key = str(self.proc.pid)
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
670 vim.command(syntax_region)
671 vim.command(syntax_highlight)
673 # add syntax name to history
674 if not self.color_history.has_key(real_line):
675 self.color_history[real_line] = []
677 self.color_history[real_line].append({'name':syntax_name, 'start':start, 'end':end, 'highlight':highlight})
681 ###############################################################################################
682 # Control functions {{{
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('')
694 self.color_changes = {}
699 self.color_changes = {}
709 # default tabstop location
710 ts = self.working_columns
713 for i in range(self.c, len(self.tabstops)):
722 ###############################################################################################
725 def csi_font(self, csi): # {{{
726 if not self.enable_colors:
730 if len(csi['vals']) == 0:
733 for val in csi['vals']:
734 if CONQUE_FONT.has_key(val):
736 # ignore starting normal colors
737 if CONQUE_FONT[val]['normal'] and len(self.color_changes) == 0:
740 # clear color changes
741 elif CONQUE_FONT[val]['normal']:
743 self.color_changes = {}
744 # save these color attributes for next plain_text() call
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]
751 self.color_changes[attr] = CONQUE_FONT[val]['attributes'][attr]
754 def csi_clear_line(self, csi): # {{{
756 # this escape defaults to 0
757 if len(csi['vals']) == 0:
760 # 0 means cursor right
762 self.screen[self.l] = self.screen[self.l][0 : self.c - 1]
764 # 1 means cursor left
765 elif csi['val'] == 1:
766 self.screen[self.l] = ' ' * (self.c) + self.screen[self.l][self.c : ]
769 elif csi['val'] == 2:
770 self.screen[self.l] = ''
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'])
781 def csi_cursor_right(self, csi): # {{{
782 # we use 1 even if escape explicitly specifies 0
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
791 self.c = self.bound(self.c + csi['val'], 1, self.working_columns)
794 def csi_cursor_left(self, csi): # {{{
795 # we use 1 even if escape explicitly specifies 0
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
804 self.c = self.bound(self.c - csi['val'], 1, self.working_columns)
807 def csi_cursor_to_column(self, csi): # {{{
808 self.c = self.bound(csi['val'], 1, self.working_columns)
811 def csi_cursor_up(self, csi): # {{{
812 self.l = self.bound(self.l - csi['val'], self.top, self.bottom)
814 self.color_changes = {}
817 def csi_cursor_down(self, csi): # {{{
818 self.l = self.bound(self.l + csi['val'], self.top, self.bottom)
820 self.color_changes = {}
823 def csi_clear_screen(self, csi): # {{{
825 if len(csi['vals']) == 0:
828 # 2 == clear entire screen
835 elif csi['val'] == 0:
836 for l in range(self.bound(self.l + 1, 1, self.lines), self.lines + 1):
839 # clear end of current line
840 self.csi_clear_line(self.parse_csi('K'))
843 elif csi['val'] == 1:
844 for l in range(1, self.bound(self.l, 1, self.lines + 1)):
847 # clear beginning of current line
848 self.csi_clear_line(self.parse_csi('1K'))
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'])
858 self.color_changes = {}
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'] : ]
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 : ]
869 def csi_cursor(self, csi): # {{{
870 if len(csi['vals']) == 2:
871 new_line = csi['vals'][0]
872 new_col = csi['vals'][1]
877 if self.absolute_coords:
878 self.l = self.bound(new_line, 1, self.lines)
880 self.l = self.bound(self.top + new_line - 1, self.top, self.bottom)
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]))
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]
894 new_end = self.window.height
897 self.bottom = new_end
898 self.working_lines = new_end - new_start + 1
900 # if cursor is outside scrolling region, reset it
901 if self.l < self.top:
903 elif self.l > self.bottom:
906 self.color_changes = {}
909 def csi_tab_clear(self, csi): # {{{
910 # this escape defaults to 0
911 if len(csi['vals']) == 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
921 def csi_set(self, csi): # {{{
924 self.csi_clear_screen(self.parse_csi('2J'))
925 self.working_columns = 132
928 elif csi['val'] == 6:
929 self.absolute_coords = False
932 elif csi['val'] == 7:
935 self.color_changes = {}
938 def csi_reset(self, csi): # {{{
941 self.csi_clear_screen(self.parse_csi('2J'))
942 self.working_columns = 80
945 elif csi['val'] == 6:
946 self.absolute_coords = True
949 elif csi['val'] == 7:
950 self.autowrap = False
952 self.color_changes = {}
957 ###############################################################################################
960 def esc_scroll_up(self): # {{{
963 self.color_changes = {}
966 def esc_next_line(self): # {{{
971 def esc_set_tab(self): # {{{
973 if self.c <= len(self.tabstops):
974 self.tabstops[self.c - 1] = True
977 def esc_scroll_down(self): # {{{
978 if self.l == self.top:
979 del self.screen[self.bottom]
980 self.screen.insert(self.top, '')
984 self.color_changes = {}
989 ###############################################################################################
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
1001 ###############################################################################################
1004 def change_title(self, key, val):
1006 if key == '0' or key == '2':
1008 vim.command('setlocal statusline=' + re.escape(val))
1011 self.write(vim.eval('@@'))
1014 def paste_selection(self):
1015 self.write(vim.eval('@@'))
1017 def update_window_size(self):
1019 if self.window.width != self.columns or self.window.height != self.lines:
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
1028 # reset screen object attributes
1029 self.l = self.screen.reset_size(self.l)
1032 self.init_tabstops()
1034 # signal process that screen size has changed
1035 self.proc.window_resize(self.lines, self.columns)
1037 def init_tabstops(self):
1038 for i in range(0, self.columns + 1):
1040 self.tabstops.append(True)
1042 self.tabstops.append(False)
1046 ###############################################################################################
1049 def parse_csi(self, s): # {{{
1050 attr = { 'key' : s[-1], 'flag' : '', 'val' : 1, 'vals' : [] }
1062 vals = full.split(';')
1065 val = re.sub("\D", "", val)
1068 attr['vals'].append(int(val))
1070 if len(attr['vals']) == 1:
1071 attr['val'] = int(attr['vals'][0])
1076 def bound(self, val, min, max): # {{{
1088 import os, signal, pty, tty, select, fcntl, termios, struct
1090 ###################################################################################################
1091 class ConqueSubprocess:
1096 # stdout+stderr file descriptor
1100 def __init__(self): # {{{
1104 # create the pty or whatever (whatever == windows)
1105 def open(self, command, env = {}): # {{{
1106 command_arr = command.split()
1107 executable = command_arr[0]
1111 self.pid, self.fd = pty.fork()
1116 # child proc, replace with command after altering terminal attributes
1119 # set requested environment variables
1120 for k in env.keys():
1121 os.environ[k] = env[k]
1123 # set some attributes
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)
1136 os.execvp(executable, args)
1138 # else master, do nothing
1145 # XXX - select.poll() doesn't work in OS X!!!!!!!
1146 def read(self, timeout = 1): # {{{
1149 read_timeout = float(timeout) / 1000
1152 # what, no do/while?
1154 s_read, s_write, s_error = select.select( [ self.fd ], [], [], read_timeout)
1159 lines = os.read( self.fd, 32 )
1162 output = output + lines
1172 # I guess this one's not bad
1173 def write(self, input): # {{{
1175 os.write(self.fd, input)
1181 def signal(self, signum): # {{{
1183 os.kill(self.pid, signum)
1188 # get process status
1189 def get_status(self): #{{{
1194 if os.waitpid( self.pid, os.WNOHANG )[0]:
1203 # update window size in kernel, then send SIGWINCH to fg process
1204 def window_resize(self, lines, columns): # {{{
1206 fcntl.ioctl(self.fd, termios.TIOCSWINSZ, struct.pack("HHHH", lines, columns, 0, 0))
1207 os.kill(self.pid, signal.SIGWINCH)
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
1219 # s = ConqueScreen()
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'
1228 class ConqueScreen(object):
1230 # CLASS PROPERTIES {{{
1236 # screen and scrolling regions
1245 def __init__(self): # {{{
1246 self.buffer = vim.current.buffer
1247 self.window = vim.current.window
1250 self.screen_width = self.window.width
1251 self.screen_height = self.window.height
1254 ###############################################################################################
1256 def __len__(self): # {{{
1257 return len(self.buffer)
1260 def __getitem__(self, key): # {{{
1261 real_line = self.get_real_idx(key)
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)
1268 return self.buffer[ real_line ]
1271 def __setitem__(self, key, value): # {{{
1272 real_line = self.get_real_idx(key)
1274 # if line is past end of screen, append
1275 if real_line == len(self.buffer):
1276 self.buffer.append(value)
1278 self.buffer[ real_line ] = value
1281 def __delitem__(self, key): # {{{
1282 del self.buffer[ self.screen_top + key - 2 ]
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
1289 self.buffer.append(value)
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')
1297 def insert(self, line, value): # {{{
1299 l = self.screen_top + line - 2
1300 self.buffer[l:l] = [ value ]
1305 ###############################################################################################
1307 def get_top(self): # {{{
1308 return self.screen_top
1311 def get_real_idx(self, line): # {{{
1312 return (self.screen_top + line - 2)
1315 def get_real_line(self, line): # {{{
1316 return (self.screen_top + line - 1)
1319 def set_screen_width(self, width): # {{{
1320 self.screen_width = width
1325 ###############################################################################################
1326 def clear(self): # {{{
1327 self.buffer.append(' ')
1328 vim.command('normal Gzt')
1329 self.screen_top = len(self.buffer)
1332 def set_cursor(self, line, column): # {{{
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('')
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]))
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) + ')')
1349 def reset_size(self, line): # {{{
1351 # save cursor line number
1352 real_line = self.screen_top + line
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:
1361 # align bottom of buffer to bottom of screen
1362 vim.command('normal ' + str(self.screen_height) + 'kG')
1364 # return new relative line number
1365 return (real_line - self.screen_top)
1368 def scroll_to_bottom(self): # {{{
1369 self.window.cursor = (len(self.buffer) - 1, 1)
1372 def align(self): # {{{
1373 # align bottom of buffer to bottom of screen
1374 vim.command('normal ' + str(self.screen_height) + 'kG')