]>
Commit | Line | Data |
---|---|---|
0d23b6e5 BB |
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 |