]> git.ipfire.org Git - thirdparty/vim.git/commitdiff
runtime(vim): add gf support for import and packadd in ftplugin
authorlacygoill <lacygoill@lacygoill.me>
Wed, 6 Aug 2025 11:37:12 +0000 (13:37 +0200)
committerChristian Brabandt <cb@256bit.org>
Wed, 6 Aug 2025 11:38:30 +0000 (13:38 +0200)
closes: #17881

Signed-off-by: lacygoill <lacygoill@lacygoill.me>
Signed-off-by: Christian Brabandt <cb@256bit.org>
runtime/autoload/vim.vim [new file with mode: 0644]
runtime/doc/filetype.txt
runtime/ftplugin/vim.vim

diff --git a/runtime/autoload/vim.vim b/runtime/autoload/vim.vim
new file mode 100644 (file)
index 0000000..8dc14c4
--- /dev/null
@@ -0,0 +1,134 @@
+vim9script
+
+# Interface {{{1
+export def Find(editcmd: string) #{{{2
+    var curline: string = getline('.')
+
+    if curline =~ '^\s*\%(:\s*\)\=packadd!\=\s'
+        HandlePackaddLine(editcmd, curline)
+        return
+    endif
+
+    if curline =~ '^\s*\%(:\s*\)\=import\s'
+        HandleImportLine(editcmd, curline)
+        return
+    endif
+
+    try
+        execute 'normal! ' .. editcmd
+    catch
+        Error(v:exception)
+    endtry
+enddef
+#}}}1
+# Core {{{1
+def HandlePackaddLine(editcmd: string, curline: string) #{{{2
+    var pat: string = '^\s*packadd!\=\s\+\zs\S\+$'
+    var plugin: string = curline
+        ->matchstr(pat)
+        ->substitute('^vim-\|\.vim$', '', 'g')
+
+    if plugin == ''
+        try
+            execute 'normal! ' .. editcmd .. 'zv'
+        catch
+            Error(v:exception)
+            return
+        endtry
+    else
+        var split: string = editcmd[0] == 'g' ? 'edit' : editcmd[1] == 'g' ? 'tabedit' : 'split'
+        # In the  past, we passed  `runtime` to `getcompletion()`,  instead of
+        # `cmdline`.  But the  output was tricky to use,  because it contained
+        # paths relative to inconsistent root directories.
+        var files: list<string> = getcompletion($'edit **/plugin/{plugin}.vim', 'cmdline')
+            ->filter((_, path: string): bool => filereadable(path))
+            ->map((_, fname: string) => fname->fnamemodify(':p'))
+        if empty(files)
+            echo 'Could not find any plugin file for ' .. string(plugin)
+            return
+        endif
+        files->Open(split)
+    endif
+enddef
+
+def HandleImportLine(editcmd: string, curline: string) #{{{2
+    var fname: string
+    var import_cmd: string = '^\s*import\s\+\%(autoload\s\+\)\='
+    var import_alias: string = '\%(\s\+as\s\+\w\+\)\=$'
+    var import_string: string = import_cmd .. '\([''"]\)\zs.*\ze\1' .. import_alias
+    var import_expr: string = import_cmd .. '\zs.*\ze' .. import_alias
+    # the script is referred to by its name in a quoted string
+    if curline =~ import_string
+        fname = curline->matchstr(import_string)
+    # the script is referred to by an expression
+    elseif curline =~ import_expr
+        try
+            sandbox fname = curline
+                ->matchstr(import_expr)
+                ->eval()
+        catch
+            Error(v:exception)
+            return
+        endtry
+    endif
+
+    var filepath: string
+    if fname->isabsolutepath()
+        filepath = fname
+    elseif fname[0] == '.'
+        filepath = (expand('%:h') .. '/' .. fname)->simplify()
+    else
+        var subdir: string = curline =~ '^\s*import\s\+autoload\>' ? 'autoload' : 'import'
+        # Matching patterns in `'wildignore'` can be slow.
+        # Let's set `{nosuf}` to `true` to avoid `globpath()` to be slow.
+        filepath = globpath(&runtimepath, subdir .. '/' .. fname, true, true)
+            ->get(0, '')
+    endif
+
+    if !filepath->filereadable()
+        printf('E447: Can''t find file "%s" in path', fname)
+            ->Error()
+        return
+    endif
+
+    var how_to_split: string = {
+        gF: 'edit',
+        "\<C-W>F": 'split',
+        "\<C-W>gF": 'tab split',
+    }[editcmd]
+    execute how_to_split .. ' ' .. filepath
+enddef
+
+def Open(what: any, how: string) #{{{2
+    var fname: string
+    if what->typename() == 'list<string>'
+        if what->empty()
+            return
+        endif
+        fname = what[0]
+    else
+        if what->typename() != 'string'
+            return
+        endif
+        fname = what
+    endif
+
+    execute $'{how} {fname}'
+    cursor(1, 1)
+
+    # If there are several files to open, put them into an arglist.
+    if what->typename() == 'list<string>'
+            && what->len() > 1
+        var arglist: list<string> = what
+            ->copy()
+            ->map((_, f: string) => f->fnameescape())
+        execute $'arglocal {arglist->join()}'
+    endif
+enddef
+#}}}1
+# Util {{{1
+def Error(msg: string) #{{{2
+    echohl ErrorMsg
+    echomsg msg
+    echohl NONE
+enddef
index 0de7b20f33c16f7a7e9196deafca685b9d4cbda6..7fa135edb14a1e9c1eb0380ba7d4de8b6e93d70c 100644 (file)
@@ -1063,8 +1063,18 @@ To disable: >
 <
 VIM                                                    *ft-vim-plugin*
 
-The Vim filetype plugin defines mappings to move to the start and end of
-functions with [[ and ]].  Move around comments with ]" and [".
+The Vim filetype plugin defines the following mappings:
+
+    [[         move to the start of the previous function
+    ]]         move to the start of the next function
+    ][         move to the end of the previous function
+    []         move to the end of the next function
+    ]"         move to the next (legacy) comment
+    ["         move to the previous (legacy) comment
+    gf         edit the file under the cursor
+    CTRL-W gf  edit the file under the cursor in a new tab
+    CTRL-W f   edit the file under the cursor in a new window
+
 
 The mappings can be disabled with: >
        let g:no_vim_maps = 1
index 99ce0bc586dbff1a26bcec5441bed3540f087888..28ca9b3265abf440009da6c949909b5185aaab68 100644 (file)
@@ -1,11 +1,13 @@
 " Vim filetype plugin
 " Language:          Vim
 " Maintainer:        Doug Kearns <dougkearns@gmail.com>
-" Last Change:       2025 Mar 05
 " Former Maintainer: Bram Moolenaar <Bram@vim.org>
 " Contributors:      Riley Bruins <ribru17@gmail.com> ('commentstring'),
 "                    @Konfekt
 "                    @tpope (s:Help())
+"                    @lacygoill
+" Last Change:       2025 Mar 05
+" 2025 Aug 06 by Vim Project (add gf maps #17881)
 
 " Only do this when not done yet for this buffer
 if exists("b:did_ftplugin")
@@ -35,6 +37,9 @@ if !exists('*VimFtpluginUndo')
       silent! xunmap <buffer> ]"
       silent! nunmap <buffer> ["
       silent! xunmap <buffer> ["
+      silent! nunmap <buffer> gf
+      silent! nunmap <buffer> <C-W>f
+      silent! nunmap <buffer> <C-W>gf
     endif
     unlet! b:match_ignorecase b:match_words b:match_skip b:did_add_maps
   endfunc
@@ -139,6 +144,29 @@ if !exists("no_plugin_maps") && !exists("no_vim_maps")
   xnoremap <silent><buffer> ]" :<C-U>exe "normal! gv"<Bar>call search('\%(^\s*".*\n\)\@<!\%(^\s*"\)', "W")<CR>
   nnoremap <silent><buffer> [" :call search('\%(^\s*".*\n\)\%(^\s*"\)\@!', "bW")<CR>
   xnoremap <silent><buffer> [" :<C-U>exe "normal! gv"<Bar>call search('\%(^\s*".*\n\)\%(^\s*"\)\@!', "bW")<CR>
+
+  " Purpose: Handle `:import` and `:packadd` lines in a smarter way. {{{
+  "
+  " `:import` is followed by a filename or filepath.  Find it.
+  "
+  " `:packadd`  is  followed  by the  name  of  a  package,  which we  might  have
+  " configured in scripts under `~/.vim/plugin`.  Find it.
+  "
+  " ---
+  "
+  " We can't handle the `:import` lines simply by setting `'includeexpr'`, because
+  " the option would be ignored if:
+  "
+  "    - the name of the imported script is the same as the current one
+  "    - `'path'` includes the `.` item
+  "
+  " Indeed,  in that  case, Vim  finds the  current file,  and simply  reloads the
+  " buffer.
+  " }}}
+  " We use the `F` variants, instead of the `f` ones, because they're smarter.
+  nnoremap <silent><buffer> gf :<C-U>call vim#Find('gF')<CR>
+  nnoremap <silent><buffer> <C-W>f :<C-U>call vim#Find("\<lt>C-W>F")<CR>
+  nnoremap <silent><buffer> <C-W>gf :<C-U>call vim#Find("\<lt>C-W>gF")<CR>
 endif
 
 " Let the matchit plugin know what items can be matched.