From 481049e0c91995dd48b7cc10f8fbb157258a5e26 Mon Sep 17 00:00:00 2001 From: "Justin M. Keyes" Date: Sat, 19 Sep 2015 14:26:04 -0400 Subject: [PATCH 1/8] build: remove USEMAN_S --- config/config.h.in | 1 - src/nvim/options.lua | 2 -- 2 files changed, 3 deletions(-) diff --git a/config/config.h.in b/config/config.h.in index 54a405727d..c234501820 100644 --- a/config/config.h.in +++ b/config/config.h.in @@ -53,7 +53,6 @@ #cmakedefine HAVE_WORKING_LIBINTL #cmakedefine UNIX #cmakedefine USE_FNAME_CASE -#define USEMAN_S 1 #define FEAT_BROWSE #define FEAT_CSCOPE diff --git a/src/nvim/options.lua b/src/nvim/options.lua index 14f912f9e8..723736d376 100644 --- a/src/nvim/options.lua +++ b/src/nvim/options.lua @@ -1329,9 +1329,7 @@ return { expand=true, varname='p_kp', defaults={ - condition='USEMAN_S', if_true={vi="man -s"}, - if_false={vi="man"}, } }, { From 998d0ffc09d5c7358db62dc88c2e2b87622f60b5 Mon Sep 17 00:00:00 2001 From: "Justin M. Keyes" Date: Fri, 23 Jan 2015 20:03:05 -0500 Subject: [PATCH 2/8] 'keywordprg': support ex commands - new feature: if the first character of 'keywordprg' is ":", the command is invoked as a Vim ex-command prefixed with [count]. - change default 'keywordprg' to :Man --- runtime/doc/options.txt | 10 +++++---- runtime/doc/various.txt | 19 ++++++++--------- runtime/ftplugin/man.vim | 2 +- src/nvim/normal.c | 46 +++++++++++++++++++--------------------- src/nvim/options.lua | 2 +- 5 files changed, 39 insertions(+), 40 deletions(-) diff --git a/runtime/doc/options.txt b/runtime/doc/options.txt index 410e116064..fc4d685cb2 100644 --- a/runtime/doc/options.txt +++ b/runtime/doc/options.txt @@ -3877,18 +3877,20 @@ A jump table for the options with a short description can be found at |Q_op|. The 'keymodel' option is set by the |:behave| command. *'keywordprg'* *'kp'* -'keywordprg' 'kp' string (default "man" or "man -s", DOS: ":help") +'keywordprg' 'kp' string (default ":Man", Windows: ":help") global or local to buffer |global-local| Program to use for the |K| command. Environment variables are expanded |:set_env|. ":help" may be used to access the Vim internal help. (Note that previously setting the global option to the empty value did this, which is now deprecated.) - When "man" is used, Vim will automatically translate a count for the - "K" command to a section number. Also for "man -s", in which case the - "-s" is removed when there is no count. + When the first character is ":", the command is invoked as a Vim + command prefixed with [count]. {Nvim} + When "man" or "man -s" is used, Vim will automatically translate + a [count] for the "K" command to a section number. See |option-backslash| about including spaces and backslashes. Example: > :set keywordprg=man\ -s + :set keywordprg=:Man < This option cannot be set from a |modeline| or in the |sandbox|, for security reasons. diff --git a/runtime/doc/various.txt b/runtime/doc/various.txt index 6b53b8e24f..3cc0e7c8ab 100644 --- a/runtime/doc/various.txt +++ b/runtime/doc/various.txt @@ -542,7 +542,7 @@ which it was defined is reported. {not available when compiled without the |+eval| feature} *K* -K Run a program to lookup the keyword under the +[count]K Run a program to lookup the keyword under the cursor. The name of the program is given with the 'keywordprg' (kp) option (default is "man"). The keyword is formed of letters, numbers and the @@ -550,19 +550,18 @@ K Run a program to lookup the keyword under the right of the cursor is used. The same can be done with the command > :!{program} {keyword} -< There is an example of a program to use in the tools - directory of Vim. It is called 'ref' and does a - simple spelling check. - Special cases: +< Special cases: + - If 'keywordprg' begins with ":" it is invoked as + a Vim command with [count]. - If 'keywordprg' is empty, the ":help" command is used. It's a good idea to include more characters in 'iskeyword' then, to be able to find more help. - - When 'keywordprg' is equal to "man", a count before - "K" is inserted after the "man" command and before - the keyword. For example, using "2K" while the - cursor is on "mkdir", results in: > + - When 'keywordprg' is equal to "man", a [count] + before "K" is inserted after the "man" command and + before the keyword. For example, using "2K" while + the cursor is on "mkdir", results in: > !man 2 mkdir -< - When 'keywordprg' is equal to "man -s", a count +< - When 'keywordprg' is equal to "man -s", a [count] before "K" is inserted after the "-s". If there is no count, the "-s" is removed. diff --git a/runtime/ftplugin/man.vim b/runtime/ftplugin/man.vim index 11b2b0a665..e907220d0a 100644 --- a/runtime/ftplugin/man.vim +++ b/runtime/ftplugin/man.vim @@ -120,7 +120,7 @@ func GetPage(...) let sect = "" endif if s:FindPage(sect, page) == 0 - echo "\nCannot find a '".page."'." + echo "\nNo manual entry for '".page."'." return endif exec "let s:man_tag_buf_".s:man_tag_depth." = ".bufnr("%") diff --git a/src/nvim/normal.c b/src/nvim/normal.c index 5b35af9209..8ce2065336 100644 --- a/src/nvim/normal.c +++ b/src/nvim/normal.c @@ -4213,18 +4213,12 @@ void do_nv_ident(int c1, int c2) static void nv_ident(cmdarg_T *cap) { char_u *ptr = NULL; - char_u *buf; - char_u *newbuf; char_u *p; - char_u *kp; /* value of 'keywordprg' */ - bool kp_help; /* 'keywordprg' is ":help" */ size_t n = 0; /* init for GCC */ int cmdchar; bool g_cmd; /* "g" command */ bool tag_cmd = false; char_u *aux_ptr; - bool isman; - bool isman_s; if (cap->cmdchar == 'g') { /* "g*", "g#", "g]" and "gCTRL-]" */ cmdchar = cap->nchar; @@ -4259,10 +4253,10 @@ static void nv_ident(cmdarg_T *cap) /* Allocate buffer to put the command in. Inserting backslashes can * double the length of the word. p_kp / curbuf->b_p_kp could be added * and some numbers. */ - kp = (*curbuf->b_p_kp == NUL ? p_kp : curbuf->b_p_kp); - kp_help = (*kp == NUL || STRCMP(kp, ":he") == 0 - || STRCMP(kp, ":help") == 0); - buf = xmalloc(n * 2 + 30 + STRLEN(kp)); + char_u *kp = (*curbuf->b_p_kp == NUL ? p_kp : curbuf->b_p_kp); //'keywordprg' + assert(*kp != NUL); //option.c::do_set() should default to ":help" if empty. + bool kp_ex = (*kp == ':'); //'keywordprg' is an ex command + char *buf = xmalloc(n * 2 + 30 + STRLEN(kp)); buf[0] = NUL; switch (cmdchar) { @@ -4283,9 +4277,13 @@ static void nv_ident(cmdarg_T *cap) break; case 'K': - if (kp_help) - STRCPY(buf, "he! "); - else { + if (kp_ex) { + if (cap->count0 != 0) { // Send the count to the ex command. + sprintf(buf, "%" PRId64, (int64_t)(cap->count0)); + } + STRCAT(buf, kp); + STRCAT(buf, " "); + } else { /* An external command will probably use an argument starting * with "-" as an option. To avoid trouble we skip the "-". */ while (*ptr == '-' && n > 0) { @@ -4300,10 +4298,10 @@ static void nv_ident(cmdarg_T *cap) /* When a count is given, turn it into a range. Is this * really what we want? */ - isman = (STRCMP(kp, "man") == 0); - isman_s = (STRCMP(kp, "man -s") == 0); + bool isman = (STRCMP(kp, "man") == 0); + bool isman_s = (STRCMP(kp, "man -s") == 0); if (cap->count0 != 0 && !(isman || isman_s)) - sprintf((char *)buf, ".,.+%" PRId64, (int64_t)(cap->count0 - 1)); + sprintf(buf, ".,.+%" PRId64, (int64_t)(cap->count0 - 1)); STRCAT(buf, "! "); if (cap->count0 == 0 && isman_s) @@ -4312,7 +4310,7 @@ static void nv_ident(cmdarg_T *cap) STRCAT(buf, kp); STRCAT(buf, " "); if (cap->count0 != 0 && (isman || isman_s)) { - sprintf((char *)buf + STRLEN(buf), "%" PRId64, (int64_t)cap->count0); + sprintf(buf + STRLEN(buf), "%" PRId64, (int64_t)cap->count0); STRCAT(buf, " "); } } @@ -4334,19 +4332,19 @@ static void nv_ident(cmdarg_T *cap) if (g_cmd) STRCPY(buf, "tj "); else - sprintf((char *)buf, "%" PRId64 "ta ", (int64_t)cap->count0); + sprintf(buf, "%" PRId64 "ta ", (int64_t)cap->count0); } } /* * Now grab the chars in the identifier */ - if (cmdchar == 'K' && !kp_help) { + if (cmdchar == 'K' && !kp_ex) { /* Escape the argument properly for a shell command */ ptr = vim_strnsave(ptr, n); p = vim_strsave_shellescape(ptr, true, true); xfree(ptr); - newbuf = (char_u *)xrealloc(buf, STRLEN(buf) + STRLEN(p) + 1); + char *newbuf = xrealloc(buf, STRLEN(buf) + STRLEN(p) + 1); buf = newbuf; STRCAT(buf, p); xfree(p); @@ -4364,7 +4362,7 @@ static void nv_ident(cmdarg_T *cap) } else aux_ptr = (char_u *)"\\|\"\n*?["; - p = buf + STRLEN(buf); + p = (char_u *)buf + STRLEN(buf); while (n-- > 0) { /* put a backslash before \ and some others */ if (vim_strchr(aux_ptr, *ptr) != NULL) @@ -4391,10 +4389,10 @@ static void nv_ident(cmdarg_T *cap) STRCAT(buf, "\\>"); /* put pattern in search history */ init_history(); - add_to_history(HIST_SEARCH, buf, true, NUL); - (void)normal_search(cap, cmdchar == '*' ? '/' : '?', buf, 0); + add_to_history(HIST_SEARCH, (char_u *)buf, true, NUL); + (void)normal_search(cap, cmdchar == '*' ? '/' : '?', (char_u *)buf, 0); } else - do_cmdline_cmd((char *)buf); + do_cmdline_cmd(buf); xfree(buf); } diff --git a/src/nvim/options.lua b/src/nvim/options.lua index 723736d376..e02326be78 100644 --- a/src/nvim/options.lua +++ b/src/nvim/options.lua @@ -1329,7 +1329,7 @@ return { expand=true, varname='p_kp', defaults={ - if_true={vi="man -s"}, + if_true={vi=":Man"}, } }, { From 4fb75d61c2c52ac25de96763788775f5e2d27dd7 Mon Sep 17 00:00:00 2001 From: "Justin M. Keyes" Date: Fri, 23 Jan 2015 17:52:47 -0500 Subject: [PATCH 3/8] man.vim: convert ftplugin to actual plugin. - do not create leader maps - :norm! instead of :norm - :keepjumps during layout - use blackhole reg to avoid polluting unnamed reg - format buffer name as "man://foo(2)" - simulate behavior of `man` - buffer-local mapping of q to quit - open in new tab instead of new window - set 'nolist' - set tabstop=8 --- runtime/autoload/man.vim | 134 +++++++++++++++++++++++++ runtime/ftplugin/man.vim | 206 +++++---------------------------------- runtime/plugin/man.vim | 6 ++ 3 files changed, 166 insertions(+), 180 deletions(-) create mode 100644 runtime/autoload/man.vim create mode 100644 runtime/plugin/man.vim diff --git a/runtime/autoload/man.vim b/runtime/autoload/man.vim new file mode 100644 index 0000000000..885d77b645 --- /dev/null +++ b/runtime/autoload/man.vim @@ -0,0 +1,134 @@ +let s:man_tag_depth = 0 +let s:man_sect_arg = '' +let s:man_find_arg = '-w' + +try + if !has('win32') && $OSTYPE !~? 'cygwin\|linux' && system('uname -s') =~? 'SunOS' && system('uname -r') =~? '^5' + let s:man_sect_arg = '-s' + let s:man_find_arg = '-l' + endif +catch /E145:/ + " Ignore the error in restricted mode +endtry + +function man#pre_get_page(cnt) + if a:cnt == 0 + let old_isk = &iskeyword + if &ft == 'man' + setlocal iskeyword+=(,) + endif + let str = expand('') + let &l:iskeyword = old_isk + let page = substitute(str, '(*\(\k\+\).*', '\1', '') + let sect = substitute(str, '\(\k\+\)(\([^()]*\)).*', '\2', '') + if match(sect, '^[0-9 ]\+$') == -1 + let sect = '' + endif + if sect == page + let sect = '' + endif + else + let sect = a:cnt + let page = expand('') + endif + call man#get_page(sect, page) +endfunction + +function man#get_page(...) + if a:0 >= 2 + let sect = a:1 + let page = a:2 + elseif a:0 >= 1 + let sect = '' + let page = a:1 + else + return + endif + + " To support: nmap K :Man + if page == '' + let page = expand('') + endif + + if sect != '' && s:FindPage(sect, page) == 0 + let sect = '' + endif + if s:FindPage(sect, page) == 0 + echo "\nNo manual entry for '".page."'" + return + endif + exec 'let s:man_tag_buf_'.s:man_tag_depth.' = '.bufnr('%') + exec 'let s:man_tag_lin_'.s:man_tag_depth.' = '.line('.') + exec 'let s:man_tag_col_'.s:man_tag_depth.' = '.col('.') + let s:man_tag_depth = s:man_tag_depth + 1 + + " Use an existing "man" window if it exists, otherwise open a new one. + if &filetype != 'man' + let thiswin = winnr() + exe "norm! \b" + if winnr() > 1 + exe "norm! " . thiswin . "\w" + while 1 + if &filetype == 'man' + break + endif + exe "norm! \w" + if thiswin == winnr() + break + endif + endwhile + endif + if &filetype != 'man' + tabnew + " window-local options + setlocal foldcolumn=0 nonumber nolist norelativenumber nofoldenable + endif + endif + silent exec 'edit man://'.page.(empty(sect)?'':'('.sect.')') + + setlocal modifiable + silent keepjumps norm! 1G"_dG + let $MANWIDTH = winwidth(0) + silent exec 'r!/usr/bin/man '.s:GetCmdArg(sect, page).' | col -b' + " Remove blank lines from top and bottom. + while getline(1) =~ '^\s*$' + silent keepjumps norm! gg"_dd + endwhile + while getline('$') =~ '^\s*$' + silent keepjumps norm! G"_dd + endwhile + setlocal filetype=man +endfunction + +function man#pop_page() + if s:man_tag_depth > 0 + let s:man_tag_depth = s:man_tag_depth - 1 + exec "let s:man_tag_buf=s:man_tag_buf_".s:man_tag_depth + exec "let s:man_tag_lin=s:man_tag_lin_".s:man_tag_depth + exec "let s:man_tag_col=s:man_tag_col_".s:man_tag_depth + exec s:man_tag_buf."b" + exec s:man_tag_lin + exec "norm! ".s:man_tag_col."|" + exec "unlet s:man_tag_buf_".s:man_tag_depth + exec "unlet s:man_tag_lin_".s:man_tag_depth + exec "unlet s:man_tag_col_".s:man_tag_depth + unlet s:man_tag_buf s:man_tag_lin s:man_tag_col + endif +endfunction + +function s:GetCmdArg(sect, page) + if a:sect == '' + return a:page + endif + return s:man_sect_arg.' '.a:sect.' '.a:page +endfunction + +function s:FindPage(sect, page) + let where = system('/usr/bin/man '.s:man_find_arg.' '.s:GetCmdArg(a:sect, a:page)) + if where !~ "^/" + if matchstr(where, " [^ ]*$") !~ "^ /" + return 0 + endif + endif + return 1 +endfunction diff --git a/runtime/ftplugin/man.vim b/runtime/ftplugin/man.vim index e907220d0a..a35966f6a9 100644 --- a/runtime/ftplugin/man.vim +++ b/runtime/ftplugin/man.vim @@ -1,191 +1,37 @@ " Vim filetype plugin file " Language: man " Maintainer: SungHyun Nam -" Last Change: 2014 Nov 12 -" To make the ":Man" command available before editing a manual page, source -" this script from your startup vimrc file. - -" If 'filetype' isn't "man", we must have been called to only define ":Man". -if &filetype == "man" - - " Only do this when not done yet for this buffer - if exists("b:did_ftplugin") - finish - endif - let b:did_ftplugin = 1 - - " Ensure Vim is not recursively invoked (man-db does this) - " when doing ctrl-[ on a man page reference. - if exists("$MANPAGER") - let $MANPAGER = "" - endif - - " allow dot and dash in manual page name. - setlocal iskeyword+=\.,- - - " Add mappings, unless the user didn't want this. - if !exists("no_plugin_maps") && !exists("no_man_maps") - if !hasmapto('ManBS') - nmap h ManBS - endif - nnoremap ManBS :%s/.\b//g:setl nomod'' - - nnoremap :call PreGetPage(v:count) - nnoremap :call PopPage() - endif - - let b:undo_ftplugin = "setlocal iskeyword<" +" Only do this when not done yet for this buffer +if exists('b:did_ftplugin') + finish +endif +let b:did_ftplugin = 1 +" Ensure Vim is not recursively invoked (man-db does this) +" when doing ctrl-[ on a man page reference. +if exists('$MANPAGER') + let $MANPAGER = '' endif -if exists(":Man") != 2 - com -nargs=+ Man call s:GetPage() - nmap K :call PreGetPage(0) +" allow dot and dash in manual page name. +setlocal iskeyword+=\.,-,(,) + +" Avoid warning for editing the dummy file twice +setlocal buftype=nofile noswapfile + +setlocal nomodifiable readonly bufhidden=hide nobuflisted +setlocal tabstop=8 colorcolumn=0 + +if !exists("g:no_plugin_maps") && !exists("g:no_man_maps") + nnoremap :call man#pre_get_page(v:count) + nnoremap :call man#pop_page() + nnoremap q c + if &keywordprg !=# ':Man' + nnoremap K :call man#pre_get_page(v:count) + endif endif -" Define functions only once. -if !exists("s:man_tag_depth") - -let s:man_tag_depth = 0 - -let s:man_sect_arg = "" -let s:man_find_arg = "-w" -try - if !has("win32") && $OSTYPE !~ 'cygwin\|linux' && system('uname -s') =~ "SunOS" && system('uname -r') =~ "^5" - let s:man_sect_arg = "-s" - let s:man_find_arg = "-l" - endif -catch /E145:/ - " Ignore the error in restricted mode -endtry - -func PreGetPage(cnt) - if a:cnt == 0 - let old_isk = &iskeyword - if &ft == 'man' - setl iskeyword+=(,) - endif - let str = expand("") - let &l:iskeyword = old_isk - let page = substitute(str, '(*\(\k\+\).*', '\1', '') - let sect = substitute(str, '\(\k\+\)(\([^()]*\)).*', '\2', '') - if match(sect, '^[0-9 ]\+$') == -1 - let sect = "" - endif - if sect == page - let sect = "" - endif - else - let sect = a:cnt - let page = expand("") - endif - call s:GetPage(sect, page) -endfunc - -func GetCmdArg(sect, page) - if a:sect == '' - return a:page - endif - return s:man_sect_arg.' '.a:sect.' '.a:page -endfunc - -func FindPage(sect, page) - let where = system("/usr/bin/man ".s:man_find_arg.' '.s:GetCmdArg(a:sect, a:page)) - if where !~ "^/" - if matchstr(where, " [^ ]*$") !~ "^ /" - return 0 - endif - endif - return 1 -endfunc - -func GetPage(...) - if a:0 >= 2 - let sect = a:1 - let page = a:2 - elseif a:0 >= 1 - let sect = "" - let page = a:1 - else - return - endif - - " To support: nmap K :Man - if page == '' - let page = expand('') - endif - - if sect != "" && s:FindPage(sect, page) == 0 - let sect = "" - endif - if s:FindPage(sect, page) == 0 - echo "\nNo manual entry for '".page."'." - return - endif - exec "let s:man_tag_buf_".s:man_tag_depth." = ".bufnr("%") - exec "let s:man_tag_lin_".s:man_tag_depth." = ".line(".") - exec "let s:man_tag_col_".s:man_tag_depth." = ".col(".") - let s:man_tag_depth = s:man_tag_depth + 1 - - " Use an existing "man" window if it exists, otherwise open a new one. - if &filetype != "man" - let thiswin = winnr() - exe "norm! \b" - if winnr() > 1 - exe "norm! " . thiswin . "\w" - while 1 - if &filetype == "man" - break - endif - exe "norm! \w" - if thiswin == winnr() - break - endif - endwhile - endif - if &filetype != "man" - new - setl nonu fdc=0 - endif - endif - silent exec "edit $HOME/".page.".".sect."~" - " Avoid warning for editing the dummy file twice - setl buftype=nofile noswapfile - - setl ma nonu nornu nofen - silent exec "norm 1GdG" - let $MANWIDTH = winwidth(0) - silent exec "r!/usr/bin/man ".s:GetCmdArg(sect, page)." | col -b" - " Remove blank lines from top and bottom. - while getline(1) =~ '^\s*$' - silent norm ggdd - endwhile - while getline('$') =~ '^\s*$' - silent norm Gdd - endwhile - 1 - setl ft=man nomod - setl bufhidden=hide - setl nobuflisted -endfunc - -func PopPage() - if s:man_tag_depth > 0 - let s:man_tag_depth = s:man_tag_depth - 1 - exec "let s:man_tag_buf=s:man_tag_buf_".s:man_tag_depth - exec "let s:man_tag_lin=s:man_tag_lin_".s:man_tag_depth - exec "let s:man_tag_col=s:man_tag_col_".s:man_tag_depth - exec s:man_tag_buf."b" - exec s:man_tag_lin - exec "norm ".s:man_tag_col."|" - exec "unlet s:man_tag_buf_".s:man_tag_depth - exec "unlet s:man_tag_lin_".s:man_tag_depth - exec "unlet s:man_tag_col_".s:man_tag_depth - unlet s:man_tag_buf s:man_tag_lin s:man_tag_col - endif -endfunc - -endif +let b:undo_ftplugin = 'setlocal iskeyword<' " vim: set sw=2: diff --git a/runtime/plugin/man.vim b/runtime/plugin/man.vim new file mode 100644 index 0000000000..cb1316968f --- /dev/null +++ b/runtime/plugin/man.vim @@ -0,0 +1,6 @@ +if get(g:, 'loaded_man', 0) + finish +endif +let g:loaded_man = 1 + +command! -nargs=+ Man call man#get_page() From 45724e2c4148aaddfbb83909d2d637a760e57710 Mon Sep 17 00:00:00 2001 From: "Justin M. Keyes" Date: Sat, 19 Sep 2015 17:44:23 -0400 Subject: [PATCH 4/8] man.vim: man#get_page(): parse page and section. - Eliminate man#pre_get_page(). - Temporarily remove () from 'iskeyword' to avoid spurious \k match. --- runtime/autoload/man.vim | 62 +++++++++++++++++++++------------------- runtime/ftplugin/man.vim | 7 ++--- 2 files changed, 34 insertions(+), 35 deletions(-) diff --git a/runtime/autoload/man.vim b/runtime/autoload/man.vim index 885d77b645..79977641dd 100644 --- a/runtime/autoload/man.vim +++ b/runtime/autoload/man.vim @@ -11,52 +11,54 @@ catch /E145:/ " Ignore the error in restricted mode endtry -function man#pre_get_page(cnt) - if a:cnt == 0 - let old_isk = &iskeyword - if &ft == 'man' - setlocal iskeyword+=(,) - endif - let str = expand('') - let &l:iskeyword = old_isk - let page = substitute(str, '(*\(\k\+\).*', '\1', '') - let sect = substitute(str, '\(\k\+\)(\([^()]*\)).*', '\2', '') - if match(sect, '^[0-9 ]\+$') == -1 +" Expects a string like 'access' or 'access(2)'. +function s:parse_page_and_section(str) + try + let save_isk = &iskeyword + setlocal iskeyword-=(,) + let page = substitute(a:str, '(*\(\k\+\).*', '\1', '') + let sect = substitute(a:str, '\(\k\+\)(\([^()]*\)).*', '\2', '') + if sect == page || -1 == match(sect, '^[0-9 ]\+$') let sect = '' endif - if sect == page - let sect = '' - endif - else - let sect = a:cnt - let page = expand('') - endif - call man#get_page(sect, page) + catch + let &l:iskeyword = save_isk + echoerr 'man.vim: failed to parse: "'.a:str.'"' + endtry + + return [page, sect] endfunction function man#get_page(...) - if a:0 >= 2 - let sect = a:1 - let page = a:2 - elseif a:0 >= 1 - let sect = '' - let page = a:1 - else + if a:0 == 0 + echoerr 'argument required' return + elseif a:0 > 2 + echoerr 'too many arguments' + return + elseif a:0 == 2 + let [sect, page] = [a:1, a:2] + elseif type(1) == type(a:1) + let [page, sect] = ['', a:1] + else + let [page, sect] = [a:1, ''] endif - " To support: nmap K :Man if page == '' let page = expand('') endif - if sect != '' && s:FindPage(sect, page) == 0 + let [page, sect] = s:parse_page_and_section(page) + + if sect !=# '' && s:FindPage(sect, page) == 0 let sect = '' endif + if s:FindPage(sect, page) == 0 echo "\nNo manual entry for '".page."'" return endif + exec 'let s:man_tag_buf_'.s:man_tag_depth.' = '.bufnr('%') exec 'let s:man_tag_lin_'.s:man_tag_depth.' = '.line('.') exec 'let s:man_tag_col_'.s:man_tag_depth.' = '.col('.') @@ -65,14 +67,14 @@ function man#get_page(...) " Use an existing "man" window if it exists, otherwise open a new one. if &filetype != 'man' let thiswin = winnr() - exe "norm! \b" + wincmd b if winnr() > 1 exe "norm! " . thiswin . "\w" while 1 if &filetype == 'man' break endif - exe "norm! \w" + wincmd w if thiswin == winnr() break endif diff --git a/runtime/ftplugin/man.vim b/runtime/ftplugin/man.vim index a35966f6a9..38c6c7d71e 100644 --- a/runtime/ftplugin/man.vim +++ b/runtime/ftplugin/man.vim @@ -14,21 +14,18 @@ if exists('$MANPAGER') let $MANPAGER = '' endif -" allow dot and dash in manual page name. setlocal iskeyword+=\.,-,(,) -" Avoid warning for editing the dummy file twice setlocal buftype=nofile noswapfile - setlocal nomodifiable readonly bufhidden=hide nobuflisted setlocal tabstop=8 colorcolumn=0 if !exists("g:no_plugin_maps") && !exists("g:no_man_maps") - nnoremap :call man#pre_get_page(v:count) + nnoremap :call man#get_page(v:count) nnoremap :call man#pop_page() nnoremap q c if &keywordprg !=# ':Man' - nnoremap K :call man#pre_get_page(v:count) + nnoremap K :call man#get_page(v:count) endif endif From 3c32ae2ff3f8e8c0879d7adc7b0f4d4023424a25 Mon Sep 17 00:00:00 2001 From: "Justin M. Keyes" Date: Sun, 20 Sep 2015 09:46:55 -0400 Subject: [PATCH 5/8] man.vim: window-local options Set window-local options only on a newly-created "man" tab or if we're already in one. --- runtime/autoload/man.vim | 70 +++++++++++++++++++++++----------------- runtime/ftplugin/man.vim | 3 +- 2 files changed, 42 insertions(+), 31 deletions(-) diff --git a/runtime/autoload/man.vim b/runtime/autoload/man.vim index 79977641dd..41a6c2b717 100644 --- a/runtime/autoload/man.vim +++ b/runtime/autoload/man.vim @@ -11,25 +11,9 @@ catch /E145:/ " Ignore the error in restricted mode endtry -" Expects a string like 'access' or 'access(2)'. -function s:parse_page_and_section(str) - try - let save_isk = &iskeyword - setlocal iskeyword-=(,) - let page = substitute(a:str, '(*\(\k\+\).*', '\1', '') - let sect = substitute(a:str, '\(\k\+\)(\([^()]*\)).*', '\2', '') - if sect == page || -1 == match(sect, '^[0-9 ]\+$') - let sect = '' - endif - catch - let &l:iskeyword = save_isk - echoerr 'man.vim: failed to parse: "'.a:str.'"' - endtry - - return [page, sect] -endfunction - function man#get_page(...) + let invoked_from_man = (&filetype ==# 'man') + if a:0 == 0 echoerr 'argument required' return @@ -50,12 +34,12 @@ function man#get_page(...) let [page, sect] = s:parse_page_and_section(page) - if sect !=# '' && s:FindPage(sect, page) == 0 + if sect !=# '' && s:find_page(sect, page) == 0 let sect = '' endif - if s:FindPage(sect, page) == 0 - echo "\nNo manual entry for '".page."'" + if s:find_page(sect, page) == 0 + echo 'No manual entry for '.page return endif @@ -65,7 +49,7 @@ function man#get_page(...) let s:man_tag_depth = s:man_tag_depth + 1 " Use an existing "man" window if it exists, otherwise open a new one. - if &filetype != 'man' + if !invoked_from_man let thiswin = winnr() wincmd b if winnr() > 1 @@ -80,18 +64,18 @@ function man#get_page(...) endif endwhile endif - if &filetype != 'man' + if !invoked_from_man tabnew - " window-local options - setlocal foldcolumn=0 nonumber nolist norelativenumber nofoldenable + call s:set_window_local_options() endif endif + silent exec 'edit man://'.page.(empty(sect)?'':'('.sect.')') setlocal modifiable silent keepjumps norm! 1G"_dG let $MANWIDTH = winwidth(0) - silent exec 'r!/usr/bin/man '.s:GetCmdArg(sect, page).' | col -b' + silent exec 'r!/usr/bin/man '.s:get_cmd_arg(sect, page).' | col -b' " Remove blank lines from top and bottom. while getline(1) =~ '^\s*$' silent keepjumps norm! gg"_dd @@ -99,7 +83,17 @@ function man#get_page(...) while getline('$') =~ '^\s*$' silent keepjumps norm! G"_dd endwhile + setlocal nomodified setlocal filetype=man + + if invoked_from_man + call s:set_window_local_options() + endif +endfunction + +function s:set_window_local_options() + setlocal colorcolumn=0 foldcolumn=0 nonumber + setlocal nolist norelativenumber nofoldenable endfunction function man#pop_page() @@ -118,15 +112,33 @@ function man#pop_page() endif endfunction -function s:GetCmdArg(sect, page) +" Expects a string like 'access' or 'access(2)'. +function s:parse_page_and_section(str) + try + let save_isk = &iskeyword + setlocal iskeyword-=(,) + let page = substitute(a:str, '(*\(\k\+\).*', '\1', '') + let sect = substitute(a:str, '\(\k\+\)(\([^()]*\)).*', '\2', '') + if sect == page || -1 == match(sect, '^[0-9 ]\+$') + let sect = '' + endif + catch + let &l:iskeyword = save_isk + echoerr 'man.vim: failed to parse: "'.a:str.'"' + endtry + + return [page, sect] +endfunction + +function s:get_cmd_arg(sect, page) if a:sect == '' return a:page endif return s:man_sect_arg.' '.a:sect.' '.a:page endfunction -function s:FindPage(sect, page) - let where = system('/usr/bin/man '.s:man_find_arg.' '.s:GetCmdArg(a:sect, a:page)) +function s:find_page(sect, page) + let where = system('/usr/bin/man '.s:man_find_arg.' '.s:get_cmd_arg(a:sect, a:page)) if where !~ "^/" if matchstr(where, " [^ ]*$") !~ "^ /" return 0 diff --git a/runtime/ftplugin/man.vim b/runtime/ftplugin/man.vim index 38c6c7d71e..38b55e6a83 100644 --- a/runtime/ftplugin/man.vim +++ b/runtime/ftplugin/man.vim @@ -17,8 +17,7 @@ endif setlocal iskeyword+=\.,-,(,) setlocal buftype=nofile noswapfile -setlocal nomodifiable readonly bufhidden=hide nobuflisted -setlocal tabstop=8 colorcolumn=0 +setlocal nomodifiable readonly bufhidden=hide nobuflisted tabstop=8 if !exists("g:no_plugin_maps") && !exists("g:no_man_maps") nnoremap :call man#get_page(v:count) From 6eb78dcaaa6af509e7dce221e88047b5869ced20 Mon Sep 17 00:00:00 2001 From: "Justin M. Keyes" Date: Sun, 20 Sep 2015 10:11:24 -0400 Subject: [PATCH 6/8] nv_ident: sprintf => snprintf Also fix some other clint errors. --- src/nvim/normal.c | 29 +++++++++++++++++------------ 1 file changed, 17 insertions(+), 12 deletions(-) diff --git a/src/nvim/normal.c b/src/nvim/normal.c index 8ce2065336..467b74f9e6 100644 --- a/src/nvim/normal.c +++ b/src/nvim/normal.c @@ -4253,10 +4253,11 @@ static void nv_ident(cmdarg_T *cap) /* Allocate buffer to put the command in. Inserting backslashes can * double the length of the word. p_kp / curbuf->b_p_kp could be added * and some numbers. */ - char_u *kp = (*curbuf->b_p_kp == NUL ? p_kp : curbuf->b_p_kp); //'keywordprg' - assert(*kp != NUL); //option.c::do_set() should default to ":help" if empty. - bool kp_ex = (*kp == ':'); //'keywordprg' is an ex command - char *buf = xmalloc(n * 2 + 30 + STRLEN(kp)); + char_u *kp = *curbuf->b_p_kp == NUL ? p_kp : curbuf->b_p_kp; // 'keywordprg' + assert(*kp != NUL); // option.c:do_set() should default to ":help" if empty. + bool kp_ex = (*kp == ':'); // 'keywordprg' is an ex command + size_t buf_size = n * 2 + 30 + STRLEN(kp); + char *buf = xmalloc(buf_size); buf[0] = NUL; switch (cmdchar) { @@ -4279,7 +4280,7 @@ static void nv_ident(cmdarg_T *cap) case 'K': if (kp_ex) { if (cap->count0 != 0) { // Send the count to the ex command. - sprintf(buf, "%" PRId64, (int64_t)(cap->count0)); + snprintf(buf, buf_size, "%" PRId64, (int64_t)(cap->count0)); } STRCAT(buf, kp); STRCAT(buf, " "); @@ -4300,17 +4301,20 @@ static void nv_ident(cmdarg_T *cap) * really what we want? */ bool isman = (STRCMP(kp, "man") == 0); bool isman_s = (STRCMP(kp, "man -s") == 0); - if (cap->count0 != 0 && !(isman || isman_s)) - sprintf(buf, ".,.+%" PRId64, (int64_t)(cap->count0 - 1)); + if (cap->count0 != 0 && !(isman || isman_s)) { + snprintf(buf, buf_size, ".,.+%" PRId64, (int64_t)(cap->count0 - 1)); + } STRCAT(buf, "! "); - if (cap->count0 == 0 && isman_s) + if (cap->count0 == 0 && isman_s) { STRCAT(buf, "man"); - else + } else { STRCAT(buf, kp); + } STRCAT(buf, " "); if (cap->count0 != 0 && (isman || isman_s)) { - sprintf(buf + STRLEN(buf), "%" PRId64, (int64_t)cap->count0); + snprintf(buf + STRLEN(buf), buf_size - STRLEN(buf), "%" PRId64, + (int64_t)cap->count0); STRCAT(buf, " "); } } @@ -4332,7 +4336,7 @@ static void nv_ident(cmdarg_T *cap) if (g_cmd) STRCPY(buf, "tj "); else - sprintf(buf, "%" PRId64 "ta ", (int64_t)cap->count0); + snprintf(buf, buf_size, "%" PRId64 "ta ", (int64_t)cap->count0); } } @@ -4391,8 +4395,9 @@ static void nv_ident(cmdarg_T *cap) init_history(); add_to_history(HIST_SEARCH, (char_u *)buf, true, NUL); (void)normal_search(cap, cmdchar == '*' ? '/' : '?', (char_u *)buf, 0); - } else + } else { do_cmdline_cmd(buf); + } xfree(buf); } From b22b568266c8d722c399ea0b319830aa3a54ee3d Mon Sep 17 00:00:00 2001 From: "Justin M. Keyes" Date: Tue, 22 Sep 2015 01:10:07 -0400 Subject: [PATCH 7/8] test: K, 'keywordprg' --- test/functional/normal/K_spec.lua | 39 +++++++++++++++++++++++++++++++ 1 file changed, 39 insertions(+) create mode 100644 test/functional/normal/K_spec.lua diff --git a/test/functional/normal/K_spec.lua b/test/functional/normal/K_spec.lua new file mode 100644 index 0000000000..dbda6dcb93 --- /dev/null +++ b/test/functional/normal/K_spec.lua @@ -0,0 +1,39 @@ +local helpers = require('test.functional.helpers') +local execute, eq, clear, eval, feed, ok = + helpers.execute, helpers.eq, helpers.clear, helpers.eval, + helpers.feed, helpers.ok + +describe('K', function() + local test_file = 'K_spec_out' + before_each(function() + clear() + os.remove(test_file) + end) + after_each(function() + os.remove(test_file) + end) + + it("invokes colon-prefixed 'keywordprg' as Vim command", function() + helpers.source([[ + let @a='fnord' + set keywordprg=:put]]) + + -- K on the text "a" resolves to `:put a`. + feed('iaK') + helpers.expect([[ + a + fnord]]) + end) + + it("invokes non-prefixed 'keywordprg' as shell command", function() + helpers.source([[ + let @a='fnord' + set keywordprg=echo\ fnord\ >>]]) + + -- K on the text "K_spec_out" resolves to `!echo fnord >> K_spec_out`. + feed('i'..test_file..'K') + feed('') -- Press ENTER + eq({'fnord'}, eval("readfile('"..test_file.."')")) + end) + +end) From 2169721b94f1f8adf490a39380ed3f16d824d359 Mon Sep 17 00:00:00 2001 From: "Justin M. Keyes" Date: Tue, 22 Sep 2015 03:10:25 -0400 Subject: [PATCH 8/8] man.vim: accept [count] for section number. --- runtime/autoload/man.vim | 32 ++++++++++++++++---------------- runtime/plugin/man.vim | 2 +- 2 files changed, 17 insertions(+), 17 deletions(-) diff --git a/runtime/autoload/man.vim b/runtime/autoload/man.vim index 41a6c2b717..cfd0d35a71 100644 --- a/runtime/autoload/man.vim +++ b/runtime/autoload/man.vim @@ -11,7 +11,7 @@ catch /E145:/ " Ignore the error in restricted mode endtry -function man#get_page(...) +function man#get_page(...) abort let invoked_from_man = (&filetype ==# 'man') if a:0 == 0 @@ -21,7 +21,7 @@ function man#get_page(...) echoerr 'too many arguments' return elseif a:0 == 2 - let [sect, page] = [a:1, a:2] + let [page, sect] = [a:2, 0 + a:1] elseif type(1) == type(a:1) let [page, sect] = ['', a:1] else @@ -32,9 +32,9 @@ function man#get_page(...) let page = expand('') endif - let [page, sect] = s:parse_page_and_section(page) + let [page, sect] = s:parse_page_and_section(sect, page) - if sect !=# '' && s:find_page(sect, page) == 0 + if 0 + sect > 0 && s:find_page(sect, page) == 0 let sect = '' endif @@ -66,7 +66,7 @@ function man#get_page(...) endif if !invoked_from_man tabnew - call s:set_window_local_options() + let invoked_from_man = 1 endif endif @@ -75,7 +75,7 @@ function man#get_page(...) setlocal modifiable silent keepjumps norm! 1G"_dG let $MANWIDTH = winwidth(0) - silent exec 'r!/usr/bin/man '.s:get_cmd_arg(sect, page).' | col -b' + silent exec 'r!/usr/bin/man '.s:cmd(sect, page).' | col -b' " Remove blank lines from top and bottom. while getline(1) =~ '^\s*$' silent keepjumps norm! gg"_dd @@ -91,12 +91,12 @@ function man#get_page(...) endif endfunction -function s:set_window_local_options() +function s:set_window_local_options() abort setlocal colorcolumn=0 foldcolumn=0 nonumber setlocal nolist norelativenumber nofoldenable endfunction -function man#pop_page() +function man#pop_page() abort if s:man_tag_depth > 0 let s:man_tag_depth = s:man_tag_depth - 1 exec "let s:man_tag_buf=s:man_tag_buf_".s:man_tag_depth @@ -113,14 +113,14 @@ function man#pop_page() endfunction " Expects a string like 'access' or 'access(2)'. -function s:parse_page_and_section(str) +function s:parse_page_and_section(sect, str) abort try let save_isk = &iskeyword setlocal iskeyword-=(,) let page = substitute(a:str, '(*\(\k\+\).*', '\1', '') let sect = substitute(a:str, '\(\k\+\)(\([^()]*\)).*', '\2', '') if sect == page || -1 == match(sect, '^[0-9 ]\+$') - let sect = '' + let sect = a:sect endif catch let &l:iskeyword = save_isk @@ -130,15 +130,15 @@ function s:parse_page_and_section(str) return [page, sect] endfunction -function s:get_cmd_arg(sect, page) - if a:sect == '' - return a:page +function s:cmd(sect, page) abort + if 0 + a:sect > 0 + return s:man_sect_arg.' '.a:sect.' '.a:page endif - return s:man_sect_arg.' '.a:sect.' '.a:page + return a:page endfunction -function s:find_page(sect, page) - let where = system('/usr/bin/man '.s:man_find_arg.' '.s:get_cmd_arg(a:sect, a:page)) +function s:find_page(sect, page) abort + let where = system('/usr/bin/man '.s:man_find_arg.' '.s:cmd(a:sect, a:page)) if where !~ "^/" if matchstr(where, " [^ ]*$") !~ "^ /" return 0 diff --git a/runtime/plugin/man.vim b/runtime/plugin/man.vim index cb1316968f..8e5062a209 100644 --- a/runtime/plugin/man.vim +++ b/runtime/plugin/man.vim @@ -3,4 +3,4 @@ if get(g:, 'loaded_man', 0) endif let g:loaded_man = 1 -command! -nargs=+ Man call man#get_page() +command! -count=0 -nargs=+ Man call man#get_page(, )