aboutsummaryrefslogtreecommitdiff
diff options
context:
space:
mode:
-rw-r--r--config/config.h.in1
-rw-r--r--runtime/autoload/man.vim148
-rw-r--r--runtime/doc/options.txt10
-rw-r--r--runtime/doc/various.txt19
-rw-r--r--runtime/ftplugin/man.vim196
-rw-r--r--runtime/plugin/man.vim6
-rw-r--r--src/nvim/normal.c59
-rw-r--r--src/nvim/options.lua4
-rw-r--r--test/functional/normal/K_spec.lua39
9 files changed, 259 insertions, 223 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/runtime/autoload/man.vim b/runtime/autoload/man.vim
new file mode 100644
index 0000000000..cfd0d35a71
--- /dev/null
+++ b/runtime/autoload/man.vim
@@ -0,0 +1,148 @@
+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#get_page(...) abort
+ let invoked_from_man = (&filetype ==# 'man')
+
+ if a:0 == 0
+ echoerr 'argument required'
+ return
+ elseif a:0 > 2
+ echoerr 'too many arguments'
+ return
+ elseif a:0 == 2
+ let [page, sect] = [a:2, 0 + a:1]
+ elseif type(1) == type(a:1)
+ let [page, sect] = ['<cword>', a:1]
+ else
+ let [page, sect] = [a:1, '']
+ endif
+
+ if page == '<cword>'
+ let page = expand('<cword>')
+ endif
+
+ let [page, sect] = s:parse_page_and_section(sect, page)
+
+ if 0 + sect > 0 && s:find_page(sect, page) == 0
+ let sect = ''
+ endif
+
+ if s:find_page(sect, page) == 0
+ echo 'No 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 !invoked_from_man
+ let thiswin = winnr()
+ wincmd b
+ if winnr() > 1
+ exe "norm! " . thiswin . "\<C-W>w"
+ while 1
+ if &filetype == 'man'
+ break
+ endif
+ wincmd w
+ if thiswin == winnr()
+ break
+ endif
+ endwhile
+ endif
+ if !invoked_from_man
+ tabnew
+ let invoked_from_man = 1
+ 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:cmd(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 nomodified
+ setlocal filetype=man
+
+ if invoked_from_man
+ call s:set_window_local_options()
+ endif
+endfunction
+
+function s:set_window_local_options() abort
+ setlocal colorcolumn=0 foldcolumn=0 nonumber
+ setlocal nolist norelativenumber nofoldenable
+endfunction
+
+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
+ 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
+
+" Expects a string like 'access' or 'access(2)'.
+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 = a:sect
+ endif
+ catch
+ let &l:iskeyword = save_isk
+ echoerr 'man.vim: failed to parse: "'.a:str.'"'
+ endtry
+
+ return [page, sect]
+endfunction
+
+function s:cmd(sect, page) abort
+ if 0 + a:sect > 0
+ return s:man_sect_arg.' '.a:sect.' '.a:page
+ endif
+ return a:page
+endfunction
+
+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
+ endif
+ endif
+ return 1
+endfunction
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..38b55e6a83 100644
--- a/runtime/ftplugin/man.vim
+++ b/runtime/ftplugin/man.vim
@@ -1,191 +1,33 @@
" Vim filetype plugin file
" Language: man
" Maintainer: SungHyun Nam <goweol@gmail.com>
-" 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('<Plug>ManBS')
- nmap <buffer> <LocalLeader>h <Plug>ManBS
- endif
- nnoremap <buffer> <Plug>ManBS :%s/.\b//g<CR>:setl nomod<CR>''
-
- nnoremap <buffer> <c-]> :call <SID>PreGetPage(v:count)<CR>
- nnoremap <buffer> <c-t> :call <SID>PopPage()<CR>
- 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
-if exists(":Man") != 2
- com -nargs=+ Man call s:GetPage(<f-args>)
- nmap <Leader>K :call <SID>PreGetPage(0)<CR>
+" Ensure Vim is not recursively invoked (man-db does this)
+" when doing ctrl-[ on a man page reference.
+if exists('$MANPAGER')
+ let $MANPAGER = ''
endif
-" Define functions only once.
-if !exists("s:man_tag_depth")
-
-let s:man_tag_depth = 0
+setlocal iskeyword+=\.,-,(,)
-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 <SID>PreGetPage(cnt)
- if a:cnt == 0
- let old_isk = &iskeyword
- if &ft == 'man'
- setl iskeyword+=(,)
- endif
- let str = expand("<cword>")
- 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("<cword>")
- endif
- call s:GetPage(sect, page)
-endfunc
+setlocal buftype=nofile noswapfile
+setlocal nomodifiable readonly bufhidden=hide nobuflisted tabstop=8
-func <SID>GetCmdArg(sect, page)
- if a:sect == ''
- return a:page
+if !exists("g:no_plugin_maps") && !exists("g:no_man_maps")
+ nnoremap <silent> <buffer> <C-]> :call man#get_page(v:count)<CR>
+ nnoremap <silent> <buffer> <C-T> :call man#pop_page()<CR>
+ nnoremap <silent> <nowait><buffer> q <C-W>c
+ if &keywordprg !=# ':Man'
+ nnoremap <silent> <buffer> K :call man#get_page(v:count)<CR>
endif
- return s:man_sect_arg.' '.a:sect.' '.a:page
-endfunc
-
-func <SID>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 <SID>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 <cword>
- if page == '<cword>'
- let page = expand('<cword>')
- endif
-
- if sect != "" && s:FindPage(sect, page) == 0
- let sect = ""
- endif
- if s:FindPage(sect, page) == 0
- echo "\nCannot find a '".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! \<C-W>b"
- if winnr() > 1
- exe "norm! " . thiswin . "\<C-W>w"
- while 1
- if &filetype == "man"
- break
- endif
- exe "norm! \<C-W>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 <SID>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..8e5062a209
--- /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! -count=0 -nargs=+ Man call man#get_page(<count>, <f-args>)
diff --git a/src/nvim/normal.c b/src/nvim/normal.c
index 5b35af9209..467b74f9e6 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,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. */
- 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
+ size_t buf_size = n * 2 + 30 + STRLEN(kp);
+ char *buf = xmalloc(buf_size);
buf[0] = NUL;
switch (cmdchar) {
@@ -4283,9 +4278,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.
+ snprintf(buf, buf_size, "%" 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,19 +4299,22 @@ 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);
- if (cap->count0 != 0 && !(isman || isman_s))
- sprintf((char *)buf, ".,.+%" PRId64, (int64_t)(cap->count0 - 1));
+ bool isman = (STRCMP(kp, "man") == 0);
+ bool isman_s = (STRCMP(kp, "man -s") == 0);
+ 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((char *)buf + STRLEN(buf), "%" PRId64, (int64_t)cap->count0);
+ snprintf(buf + STRLEN(buf), buf_size - STRLEN(buf), "%" PRId64,
+ (int64_t)cap->count0);
STRCAT(buf, " ");
}
}
@@ -4334,19 +4336,19 @@ static void nv_ident(cmdarg_T *cap)
if (g_cmd)
STRCPY(buf, "tj ");
else
- sprintf((char *)buf, "%" PRId64 "ta ", (int64_t)cap->count0);
+ snprintf(buf, buf_size, "%" 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 +4366,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 +4393,11 @@ 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);
- } else
- do_cmdline_cmd((char *)buf);
+ add_to_history(HIST_SEARCH, (char_u *)buf, true, NUL);
+ (void)normal_search(cap, cmdchar == '*' ? '/' : '?', (char_u *)buf, 0);
+ } else {
+ do_cmdline_cmd(buf);
+ }
xfree(buf);
}
diff --git a/src/nvim/options.lua b/src/nvim/options.lua
index 14f912f9e8..e02326be78 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"},
+ if_true={vi=":Man"},
}
},
{
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('ia<ESC>K')
+ 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..'<ESC>K')
+ feed('<CR>') -- Press ENTER
+ eq({'fnord'}, eval("readfile('"..test_file.."')"))
+ end)
+
+end)