Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
55 changes: 55 additions & 0 deletions .github/workflows/main.yml
Original file line number Diff line number Diff line change
Expand Up @@ -54,6 +54,61 @@ jobs:
- name: Run tests
run: bash test/run.sh ${{ matrix.editor }}

# The oldest supported versions: Vim 8.2.3591 adds the WinClosed event,
# Neovim 0.5.0 ignores jobstart() 'stdin' so rg reads the pipe forever
Oldest:
name: Test (${{ matrix.editor }} ${{ matrix.version }})
runs-on: ubuntu-latest
strategy:
fail-fast: false
matrix:
include:
- editor: vim
version: v8.2.3591
- editor: nvim
version: v0.5.1
steps:
- name: Check out repository code
uses: actions/checkout@v4

- name: Cache vim build
if: matrix.editor == 'vim'
id: cache-vim
uses: actions/cache@v4
with:
path: ~/vim
key: vim-${{ matrix.version }}

- name: Build vim
if: matrix.editor == 'vim' && steps.cache-vim.outputs.cache-hit != 'true'
run: |
sudo apt-get update
sudo apt-get -y install libncurses-dev
git clone --depth 1 --branch ${{ matrix.version }} https://github.com/vim/vim /tmp/vim-src
cd /tmp/vim-src
# Old code with newer gcc: keep implicit declarations as warnings
CFLAGS="-O2 -Wno-error=implicit-function-declaration" \
./configure --prefix="$HOME/vim" --with-features=huge --enable-gui=no --without-x
make -j"$(nproc)"
make install

- name: Install vim
if: matrix.editor == 'vim'
run: echo "$HOME/vim/bin" >> "$GITHUB_PATH"

- name: Install neovim
if: matrix.editor == 'nvim'
run: |
curl -fsSL https://github.com/neovim/neovim/releases/download/${{ matrix.version }}/nvim-linux64.tar.gz \
| sudo tar -xz -C /opt
echo /opt/nvim-linux64/bin >> "$GITHUB_PATH"

- name: Show versions
run: ${{ matrix.editor }} --version | head -2

- name: Run tests
run: bash test/run.sh ${{ matrix.editor }}

# vint lint of plugin/ and autoload/. Warnings are reported but do not
# fail the build yet; errors do.
Lint:
Expand Down
6 changes: 3 additions & 3 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -8,7 +8,7 @@ Projects for Vim and Neovim: switch between them, find files and text, replace a

## Why vim-project

- **One plugin, Vim and Neovim.** Plain Vimscript, tested on Vim 9.1 and Neovim 0.11 in CI.
- **One plugin, Vim and Neovim.** Plain Vimscript, tested on Vim 8.2.3591 to 9.1 and Neovim 0.5.1 to 0.11 in CI.
- **Never waits for you.** Searching in files runs in the background, so typing never freezes, even in large repositories. File names are matched with Vim's built-in fuzzy matcher.
- **Safe find and replace.** A line is only replaced if it still reads as it did in the search, files with unsaved changes are skipped, and line endings are kept.
- **Git changes like an IDE.** Group local changes in changelists, commit one changelist at a time, shelve and unshelve, roll back, pull and push in the background.
Expand Down Expand Up @@ -206,7 +206,7 @@ All of them can be changed with `list_mappings`. Typed characters are highlighte

## Requirements

- Vim 9.1 or Neovim 0.11, the versions tested in CI. Older versions are not tested.
- Vim 8.2.3591+ or Neovim 0.5.1+. CI tests these oldest versions and Vim 9.1, Neovim 0.11.
- git, for the git features.
- Optional, faster search: [fd](https://github.com/sharkdp/fd) for file names, [rg](https://github.com/BurntSushi/ripgrep) or [ag](https://github.com/ggreer/the_silver_searcher) for text. Without them, find, grep and Vim's `:vimgrep` are used.
- Optional icons: a [Nerd Font](https://github.com/ryanoasis/nerd-fonts) with [nerdfont.vim](https://github.com/lambdalisue/nerdfont.vim), and [glyph-palette.vim](https://github.com/lambdalisue/glyph-palette.vim) to color them.
Expand Down Expand Up @@ -236,7 +236,7 @@ vint --error plugin autoload
docs/demo/render.sh # re-record the animations above with VHS
```

Tests are `Test_*` functions in `test/cases/*.vim`. CI runs them with Vim and Neovim, each with and without rg, ag and fd. The animations come from the scripts in `docs/demo/`, so they can be recorded again when the plugin changes.
Tests are `Test_*` functions in `test/cases/*.vim`. CI runs them with Vim and Neovim, each with and without rg, ag and fd, and with the oldest supported Vim 8.2.3591 and Neovim 0.5.1. Editors that can't read script variables by `getscriptinfo()` load the plugin from a temporary copy, where each script ends with a function returning its `s:` dict. The animations come from the scripts in `docs/demo/`, so they can be recorded again when the plugin changes.

## Credits

Expand Down
32 changes: 29 additions & 3 deletions test/cases/changelist_test.vim
Original file line number Diff line number Diff line change
Expand Up @@ -121,15 +121,16 @@ function! Test_ShelfFile_and_UnshelfFile()
let repo = s:OpenRepo('shelf')
let git = 'git -C '.shellescape(repo)
try
call writefile(0z00010203, repo.'/bin.dat')
" Bytes 00 01 02 03: NUL is written as "\n" (Neovim 0.5.0 has no blob writefile())
call writefile(["\n\x01\x02\x03"], repo.'/bin.dat', 'b')
call system(git.' add bin.dat && '.git.' commit -q -m bin')

call writefile(['shelved change'], repo.'/tracked.txt')
call writefile(['deep change'], repo.'/sub/deep.txt')
call writefile(['staged'], repo.'/other.txt')
call system(git.' add other.txt')
call writefile(['staged', 'unstaged'], repo.'/other.txt')
call writefile(0zFF00FF, repo.'/bin.dat')
call writefile(["\xff\n\xff"], repo.'/bin.dat', 'b')
call writefile(['brand new'], repo.'/new.txt')
call project#git#Status()

Expand Down Expand Up @@ -163,7 +164,7 @@ function! Test_ShelfFile_and_UnshelfFile()
call assert_equal(['shelved change'], readfile(repo.'/tracked.txt'))
call assert_equal(['deep change'], readfile(repo.'/sub/deep.txt'))
call assert_equal(['staged', 'unstaged'], readfile(repo.'/other.txt'))
call assert_equal(0zFF00FF, readfile(repo.'/bin.dat', 'B'))
call assert_equal(["\xff\n\xff"], readfile(repo.'/bin.dat', 'b'))
call assert_equal(['brand new'], readfile(repo.'/new.txt'))

" Unshelve a single file of the shelf
Expand All @@ -179,6 +180,31 @@ function! Test_ShelfFile_and_UnshelfFile()
endtry
endfunction

" ---------------------------------------------------------------- commit

" Commit a file from its line: the message window commits when closed
function! Test_Commit_from_message_window()
let repo = s:OpenRepo('commit')
let git = 'git -C '.shellescape(repo)
try
call writefile(['changed'], repo.'/tracked.txt')
call writefile(['changed'], repo.'/other.txt')
call project#git#Status()

let lnum = s:Line('|tracked\.txt|')
call s:RangeCall('Commit', lnum, lnum)
call assert_equal('gitcommit', &filetype)
call append(0, 'from window')
quit
call assert_equal('from window', trim(system(git.' log -1 --pretty=%s')))
call assert_equal(['tracked.txt'],
\ split(system(git.' show --name-only --pretty=format: HEAD'), "\n"))
call assert_equal(['other.txt'], split(system(git.' diff --name-only'), "\n"))
finally
call s:CloseRepo()
endtry
endfunction

" ---------------------------------------------------------------- move

function! Test_MoveToFolder()
Expand Down
5 changes: 5 additions & 0 deletions test/cases/find_in_files_test.vim
Original file line number Diff line number Diff line change
Expand Up @@ -250,6 +250,11 @@ function! Test_ReplaceInFiles_is_safe()
call s:WriteBinary(dir, 'stale.txt', ['foo changed', ''])
call s:WriteBinary(dir, 'unsaved.txt', ['foo', ''])
call s:WriteBinary(dir, 'loaded.txt', ['foo', ''])
" Before Vim 8.2.3510 and Neovim 0.7 a change within about 2 seconds isn't
" detected, as file times are compared in seconds
if (has('nvim') ? !has('nvim-0.7') : !has('patch-8.2.3510')) && executable('touch')
call system('touch -d "1 hour ago" '.shellescape(dir).'/*.txt')
endif

" unsaved.txt has unsaved edits, loaded.txt is open without changes
execute 'edit' fnameescape(dir.'/unsaved.txt')
Expand Down
3 changes: 1 addition & 2 deletions test/cases/search_files_test.vim
Original file line number Diff line number Diff line change
Expand Up @@ -162,8 +162,7 @@ function! s:ResetDiscardsRefresh(dir)
call project#search_files#Reset()
call s:WaitRefresh()
sleep 200m
call assert_false(exists('*Helper_ScriptVar') && has_key(
\ getscriptinfo({'sid': Helper_Sid(s:file)})[0].variables, 'initial_list'))
call assert_false(has_key(Helper_ScriptVars(s:file), 'initial_list'))
endfunction

function! Test_Reset_discards_a_running_refresh()
Expand Down
33 changes: 29 additions & 4 deletions test/helpers.vim
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,7 @@
" e.g. 'autoload/project.vim' or 'autoload/project/git.vim'.
function! Helper_Sid(suffix)
let suffix = substitute(a:suffix, '\\', '/', 'g')
for info in getscriptinfo()
for info in s:ScriptInfo()
let name = substitute(fnamemodify(info.name, ':p'), '\\', '/', 'g')
if name[len(name) - len(suffix):] ==# suffix
return info.sid
Expand All @@ -14,6 +14,21 @@ function! Helper_Sid(suffix)
throw 'Script not loaded: '.a:suffix
endfunction

" List of {'sid': N, 'name': path} of sourced scripts
function! s:ScriptInfo()
if exists('*getscriptinfo')
return getscriptinfo()
endif
let infos = []
for line in split(execute('scriptnames'), "\n")
let m = matchlist(line, '^\s*\(\d\+\): \(.*\)$')
if !empty(m)
call add(infos, {'sid': str2nr(m[1]), 'name': expand(m[2])})
endif
endfor
return infos
endfunction

" Call script-local function a:name defined in the script a:suffix.
function! Helper_Call(suffix, name, args)
return call(Helper_Funcref(a:suffix, a:name), a:args)
Expand All @@ -24,13 +39,23 @@ function! Helper_Funcref(suffix, name)
return function(printf('<SNR>%d_%s', Helper_Sid(a:suffix), a:name))
endfunction

" Read a script-local variable of the script a:suffix.
function! Helper_ScriptVar(suffix, name)
" Script-local variables (the s: dict) of the script a:suffix.
function! Helper_ScriptVars(suffix)
" Set up by the runner for editors that can't read them by getscriptinfo()
let Vars = 'Helper_ScriptVars_'.substitute(a:suffix, '\W', '_', 'g')
if exists('*'.Vars)
return call(Vars, [])
endif
let info = getscriptinfo({'sid': Helper_Sid(a:suffix)})
if empty(info) || !has_key(info[0], 'variables')
throw 'getscriptinfo() does not expose script variables in this editor'
endif
return info[0].variables[a:name]
return info[0].variables
endfunction

" Read a script-local variable of the script a:suffix.
function! Helper_ScriptVar(suffix, name)
return Helper_ScriptVars(a:suffix)[a:name]
endfunction

" Create (and return) a fresh temporary directory under the test root.
Expand Down
30 changes: 29 additions & 1 deletion test/runner.vim
Original file line number Diff line number Diff line change
Expand Up @@ -38,7 +38,35 @@ let g:vim_project_config = {
\'debug': 0,
\}

let &runtimepath = s:root.','.&runtimepath
" Older editors (Vim before 9.0.0244, Neovim before 0.10) can't read
" script-local variables by getscriptinfo(). Load the plugin from a copy
" instead, where each script ends with a function returning its s: dict for
" Helper_ScriptVar().
function! s:CanReadScriptVars()
try
return has_key(get(getscriptinfo({'sid': 1}), 0, {}), 'variables')
catch
return 0
endtry
endfunction

let s:plugin_root = s:root
if !s:CanReadScriptVars()
let s:plugin_root = g:vim_project_test_tmp.'/plugin-copy'
for s:file in glob(s:root.'/plugin/**/*.vim', 0, 1) + glob(s:root.'/autoload/**/*.vim', 0, 1)
let s:relative = s:file[len(s:root) + 1:]
let s:copy = s:plugin_root.'/'.s:relative
call mkdir(fnamemodify(s:copy, ':h'), 'p')
call writefile(readfile(s:file) + [
\ '',
\ 'function! Helper_ScriptVars_'.substitute(s:relative, '\W', '_', 'g').'()',
\ ' return s:',
\ 'endfunction',
\ ], s:copy)
endfor
endif

let &runtimepath = s:plugin_root.','.&runtimepath
runtime plugin/project.vim

" Load the modules under test so their script-local functions exist.
Expand Down
Loading