1" Test for options 2 3source check.vim 4 5func Test_whichwrap() 6 set whichwrap=b,s 7 call assert_equal('b,s', &whichwrap) 8 9 set whichwrap+=h,l 10 call assert_equal('b,s,h,l', &whichwrap) 11 12 set whichwrap+=h,l 13 call assert_equal('b,s,h,l', &whichwrap) 14 15 set whichwrap+=h,l 16 call assert_equal('b,s,h,l', &whichwrap) 17 18 set whichwrap=h,h 19 call assert_equal('h', &whichwrap) 20 21 set whichwrap=h,h,h 22 call assert_equal('h', &whichwrap) 23 24 set whichwrap& 25endfunc 26 27func Test_isfname() 28 " This used to cause Vim to access uninitialized memory. 29 set isfname= 30 call assert_equal("~X", expand("~X")) 31 set isfname& 32endfunc 33 34func Test_wildchar() 35 " Empty 'wildchar' used to access invalid memory. 36 call assert_fails('set wildchar=', 'E521:') 37 call assert_fails('set wildchar=abc', 'E521:') 38 set wildchar=<Esc> 39 let a=execute('set wildchar?') 40 call assert_equal("\n wildchar=<Esc>", a) 41 set wildchar=27 42 let a=execute('set wildchar?') 43 call assert_equal("\n wildchar=<Esc>", a) 44 set wildchar& 45endfunc 46 47func Test_options_command() 48 let caught = 'ok' 49 try 50 options 51 catch 52 let caught = v:throwpoint . "\n" . v:exception 53 endtry 54 call assert_equal('ok', caught) 55 56 " Check if the option-window is opened horizontally. 57 wincmd j 58 call assert_notequal('option-window', bufname('')) 59 wincmd k 60 call assert_equal('option-window', bufname('')) 61 " close option-window 62 close 63 64 " Open the option-window vertically. 65 vert options 66 " Check if the option-window is opened vertically. 67 wincmd l 68 call assert_notequal('option-window', bufname('')) 69 wincmd h 70 call assert_equal('option-window', bufname('')) 71 " close option-window 72 close 73 74 " Open the option-window in a new tab. 75 tab options 76 " Check if the option-window is opened in a tab. 77 normal gT 78 call assert_notequal('option-window', bufname('')) 79 normal gt 80 call assert_equal('option-window', bufname('')) 81 82 " close option-window 83 close 84endfunc 85 86func Test_path_keep_commas() 87 " Test that changing 'path' keeps two commas. 88 set path=foo,,bar 89 set path-=bar 90 set path+=bar 91 call assert_equal('foo,,bar', &path) 92 93 set path& 94endfunc 95 96func Test_signcolumn() 97 if has('signs') 98 call assert_equal("auto", &signcolumn) 99 set signcolumn=yes 100 set signcolumn=no 101 call assert_fails('set signcolumn=nope') 102 endif 103endfunc 104 105func Test_filetype_valid() 106 set ft=valid_name 107 call assert_equal("valid_name", &filetype) 108 set ft=valid-name 109 call assert_equal("valid-name", &filetype) 110 111 call assert_fails(":set ft=wrong;name", "E474:") 112 call assert_fails(":set ft=wrong\\\\name", "E474:") 113 call assert_fails(":set ft=wrong\\|name", "E474:") 114 call assert_fails(":set ft=wrong/name", "E474:") 115 call assert_fails(":set ft=wrong\\\nname", "E474:") 116 call assert_equal("valid-name", &filetype) 117 118 exe "set ft=trunc\x00name" 119 call assert_equal("trunc", &filetype) 120endfunc 121 122func Test_syntax_valid() 123 if !has('syntax') 124 return 125 endif 126 set syn=valid_name 127 call assert_equal("valid_name", &syntax) 128 set syn=valid-name 129 call assert_equal("valid-name", &syntax) 130 131 call assert_fails(":set syn=wrong;name", "E474:") 132 call assert_fails(":set syn=wrong\\\\name", "E474:") 133 call assert_fails(":set syn=wrong\\|name", "E474:") 134 call assert_fails(":set syn=wrong/name", "E474:") 135 call assert_fails(":set syn=wrong\\\nname", "E474:") 136 call assert_equal("valid-name", &syntax) 137 138 exe "set syn=trunc\x00name" 139 call assert_equal("trunc", &syntax) 140endfunc 141 142func Test_keymap_valid() 143 if !has('keymap') 144 return 145 endif 146 call assert_fails(":set kmp=valid_name", "E544:") 147 call assert_fails(":set kmp=valid_name", "valid_name") 148 call assert_fails(":set kmp=valid-name", "E544:") 149 call assert_fails(":set kmp=valid-name", "valid-name") 150 151 call assert_fails(":set kmp=wrong;name", "E474:") 152 call assert_fails(":set kmp=wrong\\\\name", "E474:") 153 call assert_fails(":set kmp=wrong\\|name", "E474:") 154 call assert_fails(":set kmp=wrong/name", "E474:") 155 call assert_fails(":set kmp=wrong\\\nname", "E474:") 156 157 call assert_fails(":set kmp=trunc\x00name", "E544:") 158 call assert_fails(":set kmp=trunc\x00name", "trunc") 159endfunc 160 161func Check_dir_option(name) 162 " Check that it's possible to set the option. 163 exe 'set ' . a:name . '=/usr/share/dict/words' 164 call assert_equal('/usr/share/dict/words', eval('&' . a:name)) 165 exe 'set ' . a:name . '=/usr/share/dict/words,/and/there' 166 call assert_equal('/usr/share/dict/words,/and/there', eval('&' . a:name)) 167 exe 'set ' . a:name . '=/usr/share/dict\ words' 168 call assert_equal('/usr/share/dict words', eval('&' . a:name)) 169 170 " Check rejecting weird characters. 171 call assert_fails("set " . a:name . "=/not&there", "E474:") 172 call assert_fails("set " . a:name . "=/not>there", "E474:") 173 call assert_fails("set " . a:name . "=/not.*there", "E474:") 174endfunc 175 176func Test_cinkeys() 177 " This used to cause invalid memory access 178 set cindent cinkeys=0 179 norm a 180 set cindent& cinkeys& 181endfunc 182 183func Test_dictionary() 184 call Check_dir_option('dictionary') 185endfunc 186 187func Test_thesaurus() 188 call Check_dir_option('thesaurus') 189endfun 190 191func Test_complete() 192 " Trailing single backslash used to cause invalid memory access. 193 set complete=s\ 194 new 195 call feedkeys("i\<C-N>\<Esc>", 'xt') 196 bwipe! 197 set complete& 198endfun 199 200func Test_set_completion() 201 call feedkeys(":set di\<C-A>\<C-B>\"\<CR>", 'tx') 202 call assert_equal('"set dictionary diff diffexpr diffopt digraph directory display', @:) 203 204 call feedkeys(":setlocal di\<C-A>\<C-B>\"\<CR>", 'tx') 205 call assert_equal('"setlocal dictionary diff diffexpr diffopt digraph directory display', @:) 206 207 call feedkeys(":setglobal di\<C-A>\<C-B>\"\<CR>", 'tx') 208 call assert_equal('"setglobal dictionary diff diffexpr diffopt digraph directory display', @:) 209 210 " Expand boolan options. When doing :set no<Tab> 211 " vim displays the options names without "no" but completion uses "no...". 212 call feedkeys(":set nodi\<C-A>\<C-B>\"\<CR>", 'tx') 213 call assert_equal('"set nodiff digraph', @:) 214 215 call feedkeys(":set invdi\<C-A>\<C-B>\"\<CR>", 'tx') 216 call assert_equal('"set invdiff digraph', @:) 217 218 " Expand abbreviation of options. 219 call feedkeys(":set ts\<C-A>\<C-B>\"\<CR>", 'tx') 220 call assert_equal('"set tabstop thesaurus ttyscroll', @:) 221 222 " Expand current value 223 call feedkeys(":set fileencodings=\<C-A>\<C-B>\"\<CR>", 'tx') 224 call assert_equal('"set fileencodings=ucs-bom,utf-8,default,latin1', @:) 225 226 call feedkeys(":set fileencodings:\<C-A>\<C-B>\"\<CR>", 'tx') 227 call assert_equal('"set fileencodings:ucs-bom,utf-8,default,latin1', @:) 228 229 " Expand key codes. 230 call feedkeys(":set <H\<C-A>\<C-B>\"\<CR>", 'tx') 231 call assert_equal('"set <Help> <Home>', @:) 232 233 " Expand terminal options. 234 call feedkeys(":set t_A\<C-A>\<C-B>\"\<CR>", 'tx') 235 call assert_equal('"set t_AB t_AF t_AL', @:) 236 237 " Expand directories. 238 call feedkeys(":set cdpath=./\<C-A>\<C-B>\"\<CR>", 'tx') 239 call assert_match(' ./samples/ ', @:) 240 call assert_notmatch(' ./small.vim ', @:) 241 242 " Expand files and directories. 243 call feedkeys(":set tags=./\<C-A>\<C-B>\"\<CR>", 'tx') 244 call assert_match(' ./samples/.* ./small.vim', @:) 245 246 call feedkeys(":set tags=./\\\\ dif\<C-A>\<C-B>\"\<CR>", 'tx') 247 call assert_equal('"set tags=./\\ diff diffexpr diffopt', @:) 248 249 set tags& 250endfunc 251 252func Test_set_errors() 253 call assert_fails('set scroll=-1', 'E49:') 254 call assert_fails('set backupcopy=', 'E474:') 255 call assert_fails('set regexpengine=3', 'E474:') 256 call assert_fails('set history=10001', 'E474:') 257 call assert_fails('set numberwidth=21', 'E474:') 258 call assert_fails('set colorcolumn=-a', 'E474:') 259 call assert_fails('set colorcolumn=a', 'E474:') 260 call assert_fails('set colorcolumn=1,', 'E474:') 261 call assert_fails('set colorcolumn=1;', 'E474:') 262 call assert_fails('set cmdheight=-1', 'E487:') 263 call assert_fails('set cmdwinheight=-1', 'E487:') 264 if has('conceal') 265 call assert_fails('set conceallevel=-1', 'E487:') 266 call assert_fails('set conceallevel=4', 'E474:') 267 endif 268 call assert_fails('set helpheight=-1', 'E487:') 269 call assert_fails('set history=-1', 'E487:') 270 call assert_fails('set report=-1', 'E487:') 271 call assert_fails('set shiftwidth=-1', 'E487:') 272 call assert_fails('set sidescroll=-1', 'E487:') 273 call assert_fails('set tabstop=-1', 'E487:') 274 call assert_fails('set textwidth=-1', 'E487:') 275 call assert_fails('set timeoutlen=-1', 'E487:') 276 call assert_fails('set updatecount=-1', 'E487:') 277 call assert_fails('set updatetime=-1', 'E487:') 278 call assert_fails('set winheight=-1', 'E487:') 279 call assert_fails('set tabstop!', 'E488:') 280 call assert_fails('set xxx', 'E518:') 281 call assert_fails('set beautify?', 'E519:') 282 call assert_fails('set undolevels=x', 'E521:') 283 call assert_fails('set tabstop=', 'E521:') 284 call assert_fails('set comments=-', 'E524:') 285 call assert_fails('set comments=a', 'E525:') 286 call assert_fails('set foldmarker=x', 'E536:') 287 call assert_fails('set commentstring=x', 'E537:') 288 call assert_fails('set complete=x', 'E539:') 289 call assert_fails('set statusline=%{', 'E540:') 290 call assert_fails('set statusline=' . repeat("%p", 81), 'E541:') 291 call assert_fails('set statusline=%(', 'E542:') 292 if has('cursorshape') 293 " This invalid value for 'guicursor' used to cause Vim to crash. 294 call assert_fails('set guicursor=i-ci,r-cr:h', 'E545:') 295 call assert_fails('set guicursor=i-ci', 'E545:') 296 call assert_fails('set guicursor=x', 'E545:') 297 call assert_fails('set guicursor=x:', 'E546:') 298 call assert_fails('set guicursor=r-cr:horx', 'E548:') 299 call assert_fails('set guicursor=r-cr:hor0', 'E549:') 300 endif 301 if has('mouseshape') 302 call assert_fails('se mouseshape=i-r:x', 'E547:') 303 endif 304 call assert_fails('set backupext=~ patchmode=~', 'E589:') 305 call assert_fails('set winminheight=10 winheight=9', 'E591:') 306 call assert_fails('set winminwidth=10 winwidth=9', 'E592:') 307 call assert_fails("set showbreak=\x01", 'E595:') 308 call assert_fails('set t_foo=', 'E846:') 309endfunc 310 311func CheckWasSet(name) 312 let verb_cm = execute('verbose set ' .. a:name .. '?') 313 call assert_match('Last set from.*test_options.vim', verb_cm) 314endfunc 315func CheckWasNotSet(name) 316 let verb_cm = execute('verbose set ' .. a:name .. '?') 317 call assert_notmatch('Last set from', verb_cm) 318endfunc 319 320" Must be executed before other tests that set 'term'. 321func Test_000_term_option_verbose() 322 CheckNotGui 323 324 call CheckWasNotSet('t_cm') 325 326 let term_save = &term 327 set term=ansi 328 call CheckWasSet('t_cm') 329 let &term = term_save 330endfunc 331 332func Test_copy_context() 333 setlocal list 334 call CheckWasSet('list') 335 split 336 call CheckWasSet('list') 337 quit 338 setlocal nolist 339 340 set ai 341 call CheckWasSet('ai') 342 set filetype=perl 343 call CheckWasSet('filetype') 344 set fo=tcroq 345 call CheckWasSet('fo') 346 347 split Xsomebuf 348 call CheckWasSet('ai') 349 call CheckWasNotSet('filetype') 350 call CheckWasSet('fo') 351endfunc 352 353func Test_set_ttytype() 354 CheckUnix 355 CheckNotGui 356 357 " Setting 'ttytype' used to cause a double-free when exiting vim and 358 " when vim is compiled with -DEXITFREE. 359 set ttytype=ansi 360 call assert_equal('ansi', &ttytype) 361 call assert_equal(&ttytype, &term) 362 set ttytype=xterm 363 call assert_equal('xterm', &ttytype) 364 call assert_equal(&ttytype, &term) 365 " "set ttytype=" gives E522 instead of E529 366 " in travis on some builds. Why? Catch both for now 367 try 368 set ttytype= 369 call assert_report('set ttytype= did not fail') 370 catch /E529\|E522/ 371 endtry 372 373 " Some systems accept any terminal name and return dumb settings, 374 " check for failure of finding the entry and for missing 'cm' entry. 375 try 376 set ttytype=xxx 377 call assert_report('set ttytype=xxx did not fail') 378 catch /E522\|E437/ 379 endtry 380 381 set ttytype& 382 call assert_equal(&ttytype, &term) 383endfunc 384 385func Test_set_all() 386 set tw=75 387 set iskeyword=a-z,A-Z 388 set nosplitbelow 389 let out = execute('set all') 390 call assert_match('textwidth=75', out) 391 call assert_match('iskeyword=a-z,A-Z', out) 392 call assert_match('nosplitbelow', out) 393 set tw& iskeyword& splitbelow& 394endfunc 395 396func Test_set_one_column() 397 let out_mult = execute('set all')->split("\n") 398 let out_one = execute('set! all')->split("\n") 399 call assert_true(len(out_mult) < len(out_one)) 400endfunc 401 402func Test_set_values() 403 if filereadable('opt_test.vim') 404 source opt_test.vim 405 else 406 throw 'Skipped: opt_test.vim does not exist' 407 endif 408endfunc 409 410func Test_renderoptions() 411 " Only do this for Windows Vista and later, fails on Windows XP and earlier. 412 " Doesn't hurt to do this on a non-Windows system. 413 if windowsversion() !~ '^[345]\.' 414 set renderoptions=type:directx 415 set rop=type:directx 416 endif 417endfunc 418 419func ResetIndentexpr() 420 set indentexpr= 421endfunc 422 423func Test_set_indentexpr() 424 " this was causing usage of freed memory 425 set indentexpr=ResetIndentexpr() 426 new 427 call feedkeys("i\<c-f>", 'x') 428 call assert_equal('', &indentexpr) 429 bwipe! 430endfunc 431 432func Test_backupskip() 433 " Option 'backupskip' may contain several comma-separated path 434 " specifications if one or more of the environment variables TMPDIR, TMP, 435 " or TEMP is defined. To simplify testing, convert the string value into a 436 " list. 437 let bsklist = split(&bsk, ',') 438 439 if has("mac") 440 let found = (index(bsklist, '/private/tmp/*') >= 0) 441 call assert_true(found, '/private/tmp not in option bsk: ' . &bsk) 442 elseif has("unix") 443 let found = (index(bsklist, '/tmp/*') >= 0) 444 call assert_true(found, '/tmp not in option bsk: ' . &bsk) 445 endif 446 447 " If our test platform is Windows, the path(s) in option bsk will use 448 " backslash for the path separator and the components could be in short 449 " (8.3) format. As such, we need to replace the backslashes with forward 450 " slashes and convert the path components to long format. The expand() 451 " function will do this but it cannot handle comma-separated paths. This is 452 " why bsk was converted from a string into a list of strings above. 453 " 454 " One final complication is that the wildcard "/*" is at the end of each 455 " path and so expand() might return a list of matching files. To prevent 456 " this, we need to remove the wildcard before calling expand() and then 457 " append it afterwards. 458 if has('win32') 459 let item_nbr = 0 460 while item_nbr < len(bsklist) 461 let path_spec = bsklist[item_nbr] 462 let path_spec = strcharpart(path_spec, 0, strlen(path_spec)-2) 463 let path_spec = substitute(expand(path_spec), '\\', '/', 'g') 464 let bsklist[item_nbr] = path_spec . '/*' 465 let item_nbr += 1 466 endwhile 467 endif 468 469 " Option bsk will also include these environment variables if defined. 470 " If they're defined, verify they appear in the option value. 471 for var in ['$TMPDIR', '$TMP', '$TEMP'] 472 if exists(var) 473 let varvalue = substitute(expand(var), '\\', '/', 'g') 474 let varvalue = substitute(varvalue, '/$', '', '') 475 let varvalue .= '/*' 476 let found = (index(bsklist, varvalue) >= 0) 477 call assert_true(found, var . ' (' . varvalue . ') not in option bsk: ' . &bsk) 478 endif 479 endfor 480 481 " Duplicates should be filtered out (option has P_NODUP) 482 let backupskip = &backupskip 483 set backupskip= 484 set backupskip+=/test/dir 485 set backupskip+=/other/dir 486 set backupskip+=/test/dir 487 call assert_equal('/test/dir,/other/dir', &backupskip) 488 let &backupskip = backupskip 489endfunc 490 491func Test_copy_winopt() 492 set hidden 493 494 " Test copy option from current buffer in window 495 split 496 enew 497 setlocal numberwidth=5 498 wincmd w 499 call assert_equal(4,&numberwidth) 500 bnext 501 call assert_equal(5,&numberwidth) 502 bw! 503 call assert_equal(4,&numberwidth) 504 505 " Test copy value from window that used to be display the buffer 506 split 507 enew 508 setlocal numberwidth=6 509 bnext 510 wincmd w 511 call assert_equal(4,&numberwidth) 512 bnext 513 call assert_equal(6,&numberwidth) 514 bw! 515 516 " Test that if buffer is current, don't use the stale cached value 517 " from the last time the buffer was displayed. 518 split 519 enew 520 setlocal numberwidth=7 521 bnext 522 bnext 523 setlocal numberwidth=8 524 wincmd w 525 call assert_equal(4,&numberwidth) 526 bnext 527 call assert_equal(8,&numberwidth) 528 bw! 529 530 " Test value is not copied if window already has seen the buffer 531 enew 532 split 533 setlocal numberwidth=9 534 bnext 535 setlocal numberwidth=10 536 wincmd w 537 call assert_equal(4,&numberwidth) 538 bnext 539 call assert_equal(4,&numberwidth) 540 bw! 541 542 set hidden& 543endfunc 544 545func Test_shortmess_F() 546 new 547 call assert_match('\[No Name\]', execute('file')) 548 set shortmess+=F 549 call assert_match('\[No Name\]', execute('file')) 550 call assert_match('^\s*$', execute('file foo')) 551 call assert_match('foo', execute('file')) 552 set shortmess-=F 553 call assert_match('bar', execute('file bar')) 554 call assert_match('bar', execute('file')) 555 set shortmess& 556 bwipe 557endfunc 558 559func Test_shortmess_F2() 560 e file1 561 e file2 562 call assert_match('file1', execute('bn', '')) 563 call assert_match('file2', execute('bn', '')) 564 set shortmess+=F 565 call assert_true(empty(execute('bn', ''))) 566 call assert_false(test_getvalue('need_fileinfo')) 567 call assert_true(empty(execute('bn', ''))) 568 call assert_false('need_fileinfo'->test_getvalue()) 569 set hidden 570 call assert_true(empty(execute('bn', ''))) 571 call assert_false(test_getvalue('need_fileinfo')) 572 call assert_true(empty(execute('bn', ''))) 573 call assert_false(test_getvalue('need_fileinfo')) 574 set nohidden 575 call assert_true(empty(execute('bn', ''))) 576 call assert_false(test_getvalue('need_fileinfo')) 577 call assert_true(empty(execute('bn', ''))) 578 call assert_false(test_getvalue('need_fileinfo')) 579 set shortmess& 580 call assert_match('file1', execute('bn', '')) 581 call assert_match('file2', execute('bn', '')) 582 bwipe 583 bwipe 584endfunc 585 586func Test_local_scrolloff() 587 set so=5 588 set siso=7 589 split 590 call assert_equal(5, &so) 591 setlocal so=3 592 call assert_equal(3, &so) 593 wincmd w 594 call assert_equal(5, &so) 595 wincmd w 596 setlocal so< 597 call assert_equal(5, &so) 598 setlocal so=0 599 call assert_equal(0, &so) 600 setlocal so=-1 601 call assert_equal(5, &so) 602 603 call assert_equal(7, &siso) 604 setlocal siso=3 605 call assert_equal(3, &siso) 606 wincmd w 607 call assert_equal(7, &siso) 608 wincmd w 609 setlocal siso< 610 call assert_equal(7, &siso) 611 setlocal siso=0 612 call assert_equal(0, &siso) 613 setlocal siso=-1 614 call assert_equal(7, &siso) 615 616 close 617 set so& 618 set siso& 619endfunc 620 621func Test_writedelay() 622 CheckFunction reltimefloat 623 624 new 625 call setline(1, 'empty') 626 redraw 627 set writedelay=10 628 let start = reltime() 629 call setline(1, repeat('x', 70)) 630 redraw 631 let elapsed = reltimefloat(reltime(start)) 632 set writedelay=0 633 " With 'writedelay' set should take at least 30 * 10 msec 634 call assert_inrange(30 * 0.01, 999.0, elapsed) 635 636 bwipe! 637endfunc 638 639func Test_visualbell() 640 set belloff= 641 set visualbell 642 call assert_beeps('normal 0h') 643 set novisualbell 644 set belloff=all 645endfunc 646 647" Test for the 'write' option 648func Test_write() 649 new 650 call setline(1, ['L1']) 651 set nowrite 652 call assert_fails('write Xfile', 'E142:') 653 set write 654 close! 655endfunc 656 657" Test for 'buftype' option 658func Test_buftype() 659 new 660 call setline(1, ['L1']) 661 set buftype=nowrite 662 call assert_fails('write', 'E382:') 663 664 for val in ['', 'nofile', 'nowrite', 'acwrite', 'quickfix', 'help', 'terminal', 'prompt', 'popup'] 665 exe 'set buftype=' .. val 666 call writefile(['something'], 'XBuftype') 667 call assert_fails('write XBuftype', 'E13:', 'with buftype=' .. val) 668 endfor 669 670 call delete('XBuftype') 671 bwipe! 672endfunc 673 674" Test for the 'shellquote' option 675func Test_shellquote() 676 CheckUnix 677 set shellquote=# 678 set verbose=20 679 redir => v 680 silent! !echo Hello 681 redir END 682 set verbose& 683 set shellquote& 684 call assert_match(': "#echo Hello#"', v) 685endfunc 686 687" Test for the 'rightleftcmd' option 688func Test_rightleftcmd() 689 CheckFeature rightleft 690 set rightleft 691 set rightleftcmd 692 693 let g:l = [] 694 func AddPos() 695 call add(g:l, screencol()) 696 return '' 697 endfunc 698 cmap <expr> <F2> AddPos() 699 700 call feedkeys("/\<F2>abc\<Left>\<F2>\<Right>\<Right>\<F2>" .. 701 \ "\<Left>\<F2>\<Esc>", 'xt') 702 call assert_equal([&co - 1, &co - 4, &co - 2, &co - 3], g:l) 703 704 cunmap <F2> 705 unlet g:l 706 set rightleftcmd& 707 set rightleft& 708endfunc 709 710" vim: shiftwidth=2 sts=2 expandtab 711