├── .github └── workflows │ └── ci.yml ├── .gitmodules ├── Makefile ├── doc └── symlink.txt ├── license ├── media └── demo.gif ├── plugin └── symlink.vim ├── readme.md └── test ├── fixture ├── bar ├── bar.link ├── dir.link ├── dir │ └── .gitkeep ├── foo └── foo.link ├── symlink-split-horizontal.vader ├── symlink-split-vertical.vader ├── symlink.vader └── vimrc /.github/workflows/ci.yml: -------------------------------------------------------------------------------- 1 | on: [push, pull_request] 2 | 3 | name: ci 4 | 5 | jobs: 6 | ci: 7 | strategy: 8 | fail-fast: false 9 | matrix: 10 | os: [ubuntu-latest, macOS-latest] 11 | 12 | runs-on: ${{ matrix.os }} 13 | 14 | steps: 15 | - uses: actions/checkout@v2 16 | 17 | - name: install 18 | run: make install 19 | 20 | - name: lint 21 | run: make lint 22 | 23 | - name: test 24 | run: make test 25 | -------------------------------------------------------------------------------- /.gitmodules: -------------------------------------------------------------------------------- 1 | [submodule "test/vader.vim"] 2 | path = test/vader.vim 3 | url = https://github.com/junegunn/vader.vim.git 4 | [submodule "test/vim-bbye"] 5 | path = test/vim-bbye 6 | url = https://github.com/moll/vim-bbye.git 7 | -------------------------------------------------------------------------------- /Makefile: -------------------------------------------------------------------------------- 1 | PIP ?= pip3 2 | VIM ?= vim 3 | VINT ?= vint 4 | 5 | .PHONY: all 6 | all: install lint test 7 | 8 | .PHONY: install 9 | install: 10 | git submodule update --init 11 | $(PIP) install vim-vint==0.3.21 12 | 13 | .PHONY: lint 14 | lint: 15 | $(VINT) plugin 16 | 17 | .PHONY: test 18 | test: 19 | cd test && $(VIM) -NEsu vimrc -c 'Vader! symlink.vader' 20 | cd test && $(VIM) -NEsu vimrc -R -c 'Vader! symlink.vader' 21 | cd test && $(VIM) -NEsu vimrc -o fixture/foo.link fixture/bar.link -c 'Vader! symlink-split-horizontal.vader' 22 | cd test && $(VIM) -NEsu vimrc -O fixture/foo.link fixture/bar.link -c 'Vader! symlink-split-vertical.vader' 23 | cd test && $(VIM) -NEsu vimrc -d fixture/foo.link fixture/bar.link -c 'Vader! symlink-split-vertical.vader' 24 | -------------------------------------------------------------------------------- /doc/symlink.txt: -------------------------------------------------------------------------------- 1 | *symlink.txt* Automagically follow symlinks 2 | 3 | Author: Aymeric Beaumet (https://aymericbeaumet.com) 4 | License: MIT 5 | 6 | ============================================================================== 7 | 1. Introduction *symlink-introduction* 8 | 9 | {vim-symlink}{1} enables to automatically follow the symlinks in Vim in a 10 | cross-platform way. This means that when you edit a pathname that is a 11 | symlink, vim will instead open the file using the resolved target path. 12 | 13 | {1}: https://github.com/aymericbeaumet/vim-symlink 14 | 15 | ============================================================================== 16 | 2. Configuration *symlink-configuration* 17 | 18 | You can configure this plugin through the following global variables. 19 | 20 | ------------------------------------------------------------------------------ 21 | *g:symlink_loaded* 22 | 23 | If set, turn off the script. 24 | > 25 | let g:symlink_loaded = 1 26 | < 27 | 28 | ------------------------------------------------------------------------------ 29 | *g:symlink_redraw* 30 | 31 | By default vim-symlink will make sure to redraw the screen after a symlink 32 | has been followed. This can cause the screen to flicker. To prevent this, set 33 | this option to 0: 34 | > 35 | let g:symlink_redraw = 0 36 | < 37 | 38 | ============================================================================== 39 | vim:tw=78:ft=help 40 | -------------------------------------------------------------------------------- /license: -------------------------------------------------------------------------------- 1 | MIT License 2 | 3 | Copyright (c) Aymeric Beaumet (https://aymericbeaumet.com) 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions: 6 | 7 | The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software. 8 | 9 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. 10 | -------------------------------------------------------------------------------- /media/demo.gif: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/aymericbeaumet/vim-symlink/fec2d1a72c6875557109ce6113f26d3140b64374/media/demo.gif -------------------------------------------------------------------------------- /plugin/symlink.vim: -------------------------------------------------------------------------------- 1 | if exists('g:symlink_loaded') 2 | finish 3 | endif 4 | let g:symlink_loaded = 1 5 | 6 | let g:symlink_redraw = get(g:,'symlink_redraw', 1) 7 | 8 | function! s:on_buf_read(filepath) 9 | if !filereadable(a:filepath) 10 | return 11 | endif 12 | 13 | let l:resolved = resolve(a:filepath) 14 | if l:resolved ==# a:filepath 15 | return 16 | endif 17 | 18 | if exists(':Bwipeout') " vim-bbye 19 | silent! Bwipeout 20 | else 21 | if &diff 22 | echoerr "symlink.vim: 'moll/vim-bbye' is required in order for this plugin to properly work in diff mode" 23 | return 24 | endif 25 | enew 26 | bwipeout # 27 | endif 28 | 29 | execute 'edit ' . fnameescape(l:resolved) 30 | 31 | if g:symlink_redraw 32 | redraw 33 | endif 34 | endfunction 35 | 36 | augroup symlink_plugin 37 | autocmd! 38 | autocmd BufRead * nested call s:on_buf_read(expand('')) 39 | augroup END 40 | -------------------------------------------------------------------------------- /readme.md: -------------------------------------------------------------------------------- 1 | # vim-symlink [![GitHub Actions](https://github.com/aymericbeaumet/vim-symlink/actions/workflows/ci.yml/badge.svg)](https://github.com/aymericbeaumet/vim-symlink/actions/workflows/ci.yml) 2 | 3 | [vim-symlink](https://github.com/aymericbeaumet/vim-symlink) enables to 4 | automatically follow the symlinks in Vim or Neovim. This means that when you 5 | edit a pathname that is a symlink, vim will instead open the file using the 6 | resolved target path. 7 | 8 | [![demo](./media/demo.gif)](./media/demo.gif) 9 | 10 | ## Features 11 | 12 | - Cross-platform 13 | - Recursive symlinks resolution 14 | - [`vimdiff`](http://vimdoc.sourceforge.net/htmldoc/diff.html) support 15 | - Allow to create new files in symlinked directories 16 | - Make [vim-fugitive](https://github.com/tpope/vim-fugitive) behave properly 17 | with linked files 18 | 19 | ## Install 20 | 21 | Install with [packer](https://github.com/wbthomason/packer.nvim): 22 | 23 | ```lua 24 | use { 'aymericbeaumet/vim-symlink', requires = { 'moll/vim-bbye' } } 25 | ``` 26 | 27 | Install with [vim-plug](https://github.com/junegunn/vim-plug): 28 | 29 | ```vim 30 | Plug 'aymericbeaumet/vim-symlink' 31 | Plug 'moll/vim-bbye' " optional dependency 32 | ``` 33 | 34 | _Note: [vim-bbye](https://github.com/moll/vim-bbye) allows to consistenly wipe 35 | buffers without impacting the windows order. Even though a fallback is present 36 | in vim-symlink (hence avoiding a required dependency), the vim-bbye 37 | implementation is more robust and I advise you to leverage it._ 38 | 39 | ## Usage 40 | 41 | Read more about the usage in [the documentation](./doc/symlink.txt). 42 | -------------------------------------------------------------------------------- /test/fixture/bar: -------------------------------------------------------------------------------- 1 | bar 2 | -------------------------------------------------------------------------------- /test/fixture/bar.link: -------------------------------------------------------------------------------- 1 | bar -------------------------------------------------------------------------------- /test/fixture/dir.link: -------------------------------------------------------------------------------- 1 | dir -------------------------------------------------------------------------------- /test/fixture/dir/.gitkeep: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/aymericbeaumet/vim-symlink/fec2d1a72c6875557109ce6113f26d3140b64374/test/fixture/dir/.gitkeep -------------------------------------------------------------------------------- /test/fixture/foo: -------------------------------------------------------------------------------- 1 | foo 2 | -------------------------------------------------------------------------------- /test/fixture/foo.link: -------------------------------------------------------------------------------- 1 | foo -------------------------------------------------------------------------------- /test/symlink-split-horizontal.vader: -------------------------------------------------------------------------------- 1 | " Make all paths in the tests relative to this directory 2 | Before: 3 | execute 'cd '. fnameescape(fnamemodify(g:vader_file, ':p:h')) 4 | 5 | " It should follow the symlinks for every file 6 | Execute (skip the 2 Vader.vim buffers): 7 | execute "normal \q" 8 | execute "normal \q" 9 | Then (assert that the windows are in the right order with the right content): 10 | AssertEqual 1, tabpagenr('$') 11 | execute "normal 1gt" 12 | AssertEqual 2, winnr('$') 13 | execute "normal 1\\" 14 | AssertEqual 'fixture/bar', expand('%:.') 15 | AssertEqual ['bar'], getline(1, '$') 16 | execute "normal \j" 17 | AssertEqual 'fixture/foo', expand('%:.') 18 | AssertEqual ['foo'], getline(1, '$') 19 | -------------------------------------------------------------------------------- /test/symlink-split-vertical.vader: -------------------------------------------------------------------------------- 1 | " Make all paths in the tests relative to this directory 2 | Before: 3 | execute 'cd '. fnameescape(fnamemodify(g:vader_file, ':p:h')) 4 | 5 | " It should follow the symlinks for every file 6 | Execute (skip the 2 Vader.vim buffers): 7 | execute "normal \q" 8 | execute "normal \q" 9 | Then (assert that the windows are in the right order with the right content): 10 | AssertEqual 1, tabpagenr('$') 11 | execute "normal 1gt" 12 | AssertEqual 2, winnr('$') 13 | execute "normal 1\\" 14 | AssertEqual 'fixture/bar', expand('%:.') 15 | AssertEqual ['bar'], getline(1, '$') 16 | execute "normal \l" 17 | AssertEqual 'fixture/foo', expand('%:.') 18 | AssertEqual ['foo'], getline(1, '$') 19 | -------------------------------------------------------------------------------- /test/symlink.vader: -------------------------------------------------------------------------------- 1 | " Make all paths in the tests relative to this directory 2 | Before: 3 | execute 'cd '. fnameescape(fnamemodify(g:vader_file, ':p:h')) 4 | 5 | " It should support editing normal files 6 | Execute (edit an existing normal file): 7 | edit! fixture/foo 8 | Then (assert that the path/content are as expected): 9 | AssertEqual 'fixture/foo', expand('%:.') 10 | AssertEqual ['foo'], getline(1, '$') 11 | 12 | " It should support editing a new file in a normal directory 13 | Execute (edit a newfile in a normal directory): 14 | edit! fixture/dir/newfile 15 | Then (assert that the path/content are as expected): 16 | AssertEqual 'fixture/dir/newfile', expand('%:.') 17 | AssertEqual [''], getline(1, '$') 18 | 19 | " It should support editing symlinked files 20 | Execute (edit an existing symlinked file): 21 | edit! fixture/foo.link 22 | Then (assert that the path/content are as expected): 23 | AssertEqual 'fixture/foo', expand('%:.') 24 | AssertEqual ['foo'], getline(1, '$') 25 | 26 | " It should support editing a new file in a symlinked directory 27 | Execute (edit a newfile in a symlinked directory): 28 | edit! fixture/dir.link/newfile 29 | Then (assert that the path/content are as expected): 30 | AssertEqual 'fixture/dir/newfile', expand('%:.') 31 | AssertEqual [''], getline(1, '$') 32 | -------------------------------------------------------------------------------- /test/vimrc: -------------------------------------------------------------------------------- 1 | filetype off 2 | let &runtimepath .= ',' . expand(':p:h') . '/vader.vim' 3 | let &runtimepath .= ',' . expand(':p:h') . '/vim-bbye' 4 | let &runtimepath .= ',' . expand(':p:h:h') 5 | filetype plugin indent on 6 | syntax enable 7 | set nomore 8 | set noswapfile 9 | set viminfo= 10 | --------------------------------------------------------------------------------