1"
2" cream-columns.vim
3"
4" Cream -- An easy-to-use configuration of the famous Vim text editor
5" [ http://cream.sourceforge.net ] Copyright (C) 2001-2011 Steve Hall
6"
7" License:
8" This program is free software; you can redistribute it and/or modify
9" it under the terms of the GNU General Public License as published by
10" the Free Software Foundation; either version 3 of the License, or
11" (at your option) any later version.
12" [ http://www.gnu.org/licenses/gpl.html ]
13"
14" This program is distributed in the hope that it will be useful, but
15" WITHOUT ANY WARRANTY; without even the implied warranty of
16" MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
17" General Public License for more details.
18"
19" You should have received a copy of the GNU General Public License
20" along with this program; if not, write to the Free Software
21" Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA
22" 02111-1307, USA.
23"
24" Updated: 2006-05-13 14:32:50EDT
25" Source: (excerpted from the Cream project)
26" Author: Steve Hall  [ digitect@mindspring.com ]
27"
28" Description:
29" Intuitively type, delete and backspace in a vertical column.
30"
31" One of the many custom utilities and functions for gVim from the
32" Cream project ( http://cream.sourceforge.net ), a configuration of
33" Vim for those of us familiar with Apple or Windows software.
34"
35" Installation:
36"
37" Known Issues:
38" * Undo doesn't always track position correctly
39"
40" Notes:
41" * redraw! is required for any key that changes the background
42"   (color). Unfortunately this includes shifted-motion keys, the very
43"   keys we'd like to avoid having to clear and redraw the screen
44"   after due to speed concerns.
45
46function! Cream_columns()
47" setup stuff before calling main motion/insert stuff
48
49	" Hack: We can select the first column only when list is on. So we
50	" provide ourselves two sets of &listchars:
51	"   On:     Cream_listchars_init()
52	"   Off:    Cream_listchars_col_init()
53
54	" make invisible (use execute to maintain trailing char!)
55	if &list == 0
56		call Cream_listchars_col_init()
57		" MUST proceed initial Visual-block mode start
58		set list
59		" save "column &list" setting (we won't touch global)
60		let g:cream_list_col = 0
61	else
62		let g:cream_list_col = 1
63	endif
64
65	" position (stupid work around for <C-b> stuff at first column over tab)
66	let mycol = virtcol('.')
67
68	" start visual block mode
69	execute "normal \<C-v>"
70	" show initial position
71	redraw!
72
73	" set virtual column behavior
74	" save existing state
75	let myvirtualedit = &virtualedit
76	" all motions/bs/del are always one character
77	set virtualedit=all
78
79	" motion/bs/del space according to characters selected
80	"*** TODO: Broken:
81	" * flakey on Backspace
82	" * flakey when selecting multiple lines preceded with mixed tabs
83	"   and spaces
84	"set virtualedit=
85	"***
86
87	"*** TODO: broken--makes window pos jump
88	"" save screen pos
89	"" HACK: set guifont=* moves screen on WinXP
90	"call Cream_screen_get()
91	"
92	"" change to column-specific font
93	"call Cream_fontset_columns()
94	"
95	"" restore screen
96	"" HACK: set guifont=* moves screen on WinXP
97	"call Cream_screen_init()
98	"***
99
100	" avoid the flash
101	" save existing state
102	let myerrorbells = &errorbells
103	set noerrorbells
104
105	" clear popup menu
106	silent! unmenu PopUp
107	silent! unmenu! PopUp
108
109	" main call
110	call Cream_column_mode()
111
112	" reset mouse menu
113	call Cream_menu_popup()
114
115	"*** TODO: broken
116	"" restore font
117	"call Cream_font_init()
118	"" restore screen
119	"" HACK: set guifont=* moves screen on WinXP
120	"call Cream_screen_init()
121	"***
122
123	" restore listchars
124	call Cream_listchars_init()
125	" restore list state
126	" Note: The user may have toggled on/off during col mode. We'll
127	" restore global settings based on last col state.
128	if     g:cream_list_col == 1
129		set list
130		let g:LIST = 1
131	elseif g:cream_list_col == 0
132		set nolist
133		let g:LIST = 0
134	endif
135	unlet g:cream_list_col
136	" restore virtualedit
137	let &virtualedit = myvirtualedit
138	" restore errorbells
139	let &errorbells = myerrorbells
140
141endfunction
142
143function! Cream_listchars_col_init()
144	execute 'set listchars=eol:\ ,tab:\ \ ,trail:\ ,extends:\ ,precedes:\ '
145endfunction
146
147function! Cream_column_mode()
148" Motion, insert and delete stuff (visual block mode has already been
149" entered)
150
151	" total loops
152	let i = 0
153	let j = 0
154	let s:lastevent = "nothing"
155	while i >= 0
156
157		let i = i + 1
158
159		" get character
160		let mychar = getchar()
161
162""*** DEBUG:
163"let n = confirm(
164"    \ "DEBUG:\n" .
165"    \ "         mychar   = \"" . mychar . "\"\n" .
166"    \ " char2nr(mychar)  = \"" . char2nr(mychar) . "\"\n" .
167"    \ " nr2char(mychar)  = \"" . nr2char(mychar) . "\"\n" .
168"    \ "  strlen(mychar)  = \"" . strlen(mychar) . "\"\n" .
169"    \ "\n", "&Ok\n&Cancel", 1, "Info")
170"if n != 1
171"    return
172"endif
173""***
174
175		if exists("eattest")
176			" <C-u>
177			if mychar == 21
178				" eat the rest
179				call s:Cream_column_eatchar()
180				normal gv
181
182				" we can't yet recover from this error
183				call s:Cream_column_backspace()
184				" unselect (from select mode)
185				normal vv
186				redraw!
187				break
188			endif
189		endif
190
191		" ":" (loop if precursor to <C-u>)
192		if mychar == 58
193			let eattest = ":"
194			call s:Cream_column_insert(mychar)
195		endif
196
197		" test character for motion/selection
198		" <Esc> quit
199		if      mychar == 27
200			execute "normal " . mychar
201			redraw!
202			break
203
204		" <Undo> (Ctrl+Z)
205		elseif mychar == 26
206		\||    mychar == "\<Undo>"
207			execute "normal \<Esc>"
208			normal u
209			normal gv
210			" backup a column to maintain insertion positions
211			if s:lastevent == "insert"
212				normal h
213				normal o
214				normal h
215			elseif s:lastevent == "backspace"
216				normal l
217				normal o
218				normal l
219			endif
220
221			redraw!
222
223		" <C-c> (Copy)
224		elseif  mychar == 3
225		\ ||    mychar == "\<C-c>"
226		\ || nr2char(mychar) == ""
227			execute "normal \"+y<CR>"
228			normal gv
229			redraw!
230			call confirm(
231				\ "To paste this selection in columns, re-enter Column Mode first (Edit > Column Select).\n" .
232				\ "\n", "&Ok", 1, "Info")
233
234		" (alpha-numeric) or <Tab>, but not colon ":"
235		elseif  mychar > 31
236			\&& mychar < 127
237			\&& mychar != 58
238			\|| mychar == 9
239
240			call s:Cream_column_insert(mychar)
241
242		" <CR> (same as delete)
243		elseif mychar == 13
244			normal x
245			break
246
247		" <C-b> and <C-u> (menu calls)
248		elseif  mychar == 15
249		\ || nr2char(mychar) == ""
250
251			" eat chars until <CR>
252			call s:Cream_column_eatchar()
253
254		" <Del>
255		elseif char2nr(mychar[0]) == 128
256		   \&& char2nr(mychar[1]) == 107
257		   \&& char2nr(mychar[2]) == 68
258		" Note:
259		"
260		"    elseif  mychar == "\<Del>"
261		"    \ ||    mychar == 127
262		"
263		" The above fails, apparently due to case-sensitivity with
264		" <Down> ("€kd") conflicting with <Del> ("€kD").
265
266			" delete
267			normal x
268			normal gv
269
270			" retract selection width to a single column
271			let mycol = virtcol(".")
272			normal o
273			if virtcol(".") < mycol
274				" negate left-to-right v. right-to-left selection
275				" discrepancies
276				let mycol = virtcol(".")
277				normal o
278			endif
279			while virtcol(".") > mycol
280				normal h
281			endwhile
282
283			let s:lastevent = "delete"
284
285			redraw!
286
287		" toggle &list
288		elseif mychar == "\<F4>"
289			" Note: List must stay on during column mode to
290			" work around some Vim bugs. We'll just toggle
291			" whether the characters show or not based on the
292			" two sets of listchars we set at the top.
293
294			if g:cream_list_col == 0
295				call Cream_listchars_init()
296				let g:cream_list_col = 1
297				let g:LIST = 1
298			else
299				call Cream_listchars_col_init()
300				let g:cream_list_col = 0
301				let g:LIST = 0
302			endif
303			redraw!
304
305		elseif  mychar == "\<Down>"
306			\|| mychar == "\<Up>"
307			\|| mychar == "\<PageUp>"
308			\|| mychar == "\<PageDown>"
309			" bail
310			execute "normal " . mychar
311			break
312
313		elseif  mychar == "\<Left>"
314			\|| mychar == "\<Right>"
315			\|| mychar == "\<Home>"
316			\|| mychar == "\<End>"
317			" bail
318			execute "normal " . mychar
319			break
320
321		elseif  mychar == "\<BS>"
322
323			call s:Cream_column_backspace()
324			let s:lastevent = "backspace"
325
326		elseif  mychar == "\<S-Left>"
327			\|| mychar == "\<S-Right>"
328			\|| mychar == "\<S-Home>"
329			\|| mychar == "\<S-End>"
330
331			execute "normal " . mychar
332			let s:lastevent = "motion"
333			redraw!
334
335		elseif  mychar == "\<LeftMouse>"
336			\||     char2nr(mychar[0]) == 128
337				\&& char2nr(mychar[1]) == 253
338				\&& char2nr(mychar[2]) == 44
339				\&& char2nr(mychar[3]) == 0
340			execute "normal " . mychar
341
342			redraw!
343			break
344
345		"""elseif  mychar == "\<RightMouse>"
346		"""    \||     char2nr(mychar[0]) == 128
347		"""        \&& char2nr(mychar[1]) == 253
348		"""        \&& char2nr(mychar[2]) == 50
349		"""        \&& char2nr(mychar[3]) == 0
350		"""    \|| mychar == "\<MiddleMouse>"
351		"""    \||     char2nr(mychar[0]) == 128
352		"""        \&& char2nr(mychar[1]) == 253
353		"""        \&& char2nr(mychar[2]) == 47
354		"""        \&& char2nr(mychar[3]) == 0
355		"""
356		"""    call confirm(
357		"""        \ "Right and Middle mouse buttons are not functional in column mode.\n" .
358		"""        \ "\n", "&Ok", 1, "Info")
359		"""    redraw!
360		"""
361		"""elseif  mychar == "\<S-RightMouse>"
362		"""    \|| mychar == "\<M-S-RightMouse>"
363		"""
364		"""    call confirm(
365		"""        \ "Sorry, S-RightMouse not handled in column mode yet!\n" .
366				\ "\n", "&Ok", 1, "Info")
367
368		" <C-x> (Cut)
369		elseif  char2nr(mychar[0]) == 50
370			\&& char2nr(mychar[1]) == 52
371			execute "normal \"+x<CR>"
372
373			redraw!
374
375			call confirm(
376				\ "To paste this selection in columns, re-enter Column Mode first (Edit > Column Select).\n" .
377				\ "\n", "&Ok", 1, "Info")
378
379			break
380
381		" <C-p> (Paste)
382		elseif  char2nr(mychar[0]) == 50
383			\&& char2nr(mychar[1]) == 50
384			execute "normal \"+p<CR>"
385
386			redraw!
387			break
388
389		" other key (untrapped)
390		else
391
392			" just execute that key
393			execute "normal " . mychar
394			" forget backspace condition
395			if exists("s:stop")
396				unlet s:stop
397			endif
398
399			redraw!
400
401		endif
402
403	endwhile
404
405	redraw!
406
407endfunction
408
409function! s:Cream_column_eatchar()
410" eat input until a <CR> is reached
411
412	" loop if menu called (on <C-b> discovered below)
413	while !exists("finished")
414		let mychar = getchar()
415		" until <CR>
416		if mychar == 13
417			let finished = 1
418			call confirm(
419				\ "Sorry, menu items are not yet available during column mode.\n" .
420				\ "\n", "&Ok", 1, "Info")
421		endif
422	endwhile
423
424endfunction
425
426function! s:Cream_column_insert(char)
427
428	" retract selection width to a single column
429	let mycol = virtcol(".")
430	normal o
431	if virtcol(".") < mycol
432		" negate left-to-right v. right-to-left selection
433		" discrepancies
434		let mycol = virtcol(".")
435		normal o
436	endif
437	while virtcol(".") > mycol
438		normal h
439	endwhile
440
441	" forget backspace condition
442	if exists("s:stop")
443		unlet s:stop
444	endif
445
446	" insert character
447	execute "normal I" . nr2char(a:char)
448	" reselect and recover position
449	normal gv
450	normal l
451	normal o
452	normal l
453	"normal o
454
455	let s:lastevent = "insert"
456
457	redraw!
458
459endfunction
460
461function! s:Cream_column_backspace()
462
463	" at non-first columns, move left a column
464	if virtcol(".") != 1
465		normal h
466		normal o
467		normal h
468		normal o
469	endif
470
471	" delete selection
472	normal x
473	" reselect
474	normal gv
475
476	" retract selection width to a single column
477	let mycol = virtcol(".")
478	normal o
479	if virtcol(".") < mycol
480		" negate left-to-right v. right-to-left selection
481		" discrepancies
482		let mycol = virtcol(".")
483		normal o
484	endif
485	while virtcol(".") > mycol
486		normal h
487	endwhile
488	redraw!
489
490endfunction
491
492" column mode font set/change
493function! Cream_fontset_columns()
494	let myos = Cream_getoscode()
495	if exists("g:CREAM_FONT_COLUMNS_{myos}")
496		" had a problem with guifont being set to * in an error.
497		if g:CREAM_FONT_COLUMNS_{myos} != "*"
498			let &guifont = g:CREAM_FONT_COLUMNS_{myos}
499		endif
500	else
501		" Don't prompt to initialize font... use default.
502		"call Cream_font_init()
503	endif
504endfunction
505
506function! Cream_fontinit_columns()
507	let mymsg = "" .
508		\ "This font setting is specifically for column mode. We highly\n" .
509		\ "recommend that you choose the same font as your regular font\n" .
510		\ "with only a style variation such as \"italic.\""
511	if has("gui_running") && has("dialog_gui")
512		call confirm(mymsg, "&Ok", 1, "Info")
513	else
514		echo mymsg
515	endif
516
517	let myos = Cream_getoscode()
518
519	" call dialog...
520	set guifont=*
521	" if still empty or a "*", user may have cancelled; do nothing
522	if    &guifont == ""  && g:CREAM_FONT_COLUMNS_{myos} != "" ||
523		\ &guifont == "*" && g:CREAM_FONT_COLUMNS_{myos} != ""
524		" do nothing
525	else
526		let g:CREAM_FONT_COLUMNS_{myos} = &guifont
527	endif
528	" revert to main font if we're not in column mode
529	let mymode = mode()
530	if mymode != ""
531		if exists("g:CREAM_FONT_{myos}")
532			let &guifont = g:CREAM_FONT_{myos}
533		endif
534	endif
535endfunction
536
537