From 5eda7770be54f222e27e87190a95847e6d0e2c8c Mon Sep 17 00:00:00 2001 From: Michal Hanus Date: Mon, 29 Dec 2025 19:02:55 +0100 Subject: nvim setup --- .config/nvim | 1 - .config/nvim/.github/FUNDING.yml | 12 + .config/nvim/.github/ISSUE_TEMPLATE/bug_report.md | 40 + .../nvim/.github/ISSUE_TEMPLATE/feature_request.md | 19 + .config/nvim/.gitignore | 6 + .config/nvim/.stylua.toml | 6 + .config/nvim/autoload/plug.vim | 2877 ++++++++++++++++++++ .config/nvim/autoload/plug.vim.old | 2812 +++++++++++++++++++ .config/nvim/autoload/utils.vim | 194 ++ .config/nvim/colors/bck.lua | 289 ++ .config/nvim/colors/svratka.lua | 289 ++ .config/nvim/init.lua | 183 ++ .config/nvim/lua/colorschemes.lua | 83 + .config/nvim/lua/config/indent-blankline.lua | 36 + .config/nvim/lua/config/nvim-tree.lua | 108 + .config/nvim/lua/config/statusline.lua | 197 ++ .config/nvim/lua/custom-autocmd.lua | 105 + .config/nvim/lua/mappings.lua | 231 ++ .config/nvim/lua/plugin_specs.lua | 503 ++++ .config/nvim/spell/cs.utf-8.add | 1 + .config/nvim/spell/cs.utf-8.add.spl | Bin 0 -> 41 bytes .config/nvim/spell/cs.utf-8.spl | Bin 0 -> 1204107 bytes .config/nvim/spell/en.utf-8.add | 29 + .config/nvim/viml_conf/autocommands.vim | 123 + .config/nvim/viml_conf/options.vim | 178 ++ .config/nvim/viml_conf/plugins.vim | 409 +++ .gitignore | 1 + 27 files changed, 8731 insertions(+), 1 deletion(-) delete mode 160000 .config/nvim create mode 100644 .config/nvim/.github/FUNDING.yml create mode 100644 .config/nvim/.github/ISSUE_TEMPLATE/bug_report.md create mode 100644 .config/nvim/.github/ISSUE_TEMPLATE/feature_request.md create mode 100644 .config/nvim/.gitignore create mode 100644 .config/nvim/.stylua.toml create mode 100644 .config/nvim/autoload/plug.vim create mode 100644 .config/nvim/autoload/plug.vim.old create mode 100644 .config/nvim/autoload/utils.vim create mode 100644 .config/nvim/colors/bck.lua create mode 100644 .config/nvim/colors/svratka.lua create mode 100644 .config/nvim/init.lua create mode 100644 .config/nvim/lua/colorschemes.lua create mode 100644 .config/nvim/lua/config/indent-blankline.lua create mode 100644 .config/nvim/lua/config/nvim-tree.lua create mode 100644 .config/nvim/lua/config/statusline.lua create mode 100644 .config/nvim/lua/custom-autocmd.lua create mode 100644 .config/nvim/lua/mappings.lua create mode 100644 .config/nvim/lua/plugin_specs.lua create mode 100644 .config/nvim/spell/cs.utf-8.add create mode 100644 .config/nvim/spell/cs.utf-8.add.spl create mode 100644 .config/nvim/spell/cs.utf-8.spl create mode 100644 .config/nvim/spell/en.utf-8.add create mode 100644 .config/nvim/viml_conf/autocommands.vim create mode 100644 .config/nvim/viml_conf/options.vim create mode 100644 .config/nvim/viml_conf/plugins.vim create mode 100644 .gitignore diff --git a/.config/nvim b/.config/nvim deleted file mode 160000 index aab38f3..0000000 --- a/.config/nvim +++ /dev/null @@ -1 +0,0 @@ -Subproject commit aab38f32e0a02e43565a554d0a76ff65636499b8 diff --git a/.config/nvim/.github/FUNDING.yml b/.config/nvim/.github/FUNDING.yml new file mode 100644 index 0000000..131fc11 --- /dev/null +++ b/.config/nvim/.github/FUNDING.yml @@ -0,0 +1,12 @@ +# These are supported funding model platforms + +github: # Replace with up to 4 GitHub Sponsors-enabled usernames e.g., [user1, user2] +patreon: # Replace with a single Patreon username +open_collective: # Replace with a single Open Collective username +ko_fi: # Replace with a single Ko-fi username +tidelift: # Replace with a single Tidelift platform-name/package-name e.g., npm/babel +community_bridge: # Replace with a single Community Bridge project-name e.g., cloud-foundry +liberapay: # Replace with a single Liberapay username +issuehunt: # Replace with a single IssueHunt username +otechie: # Replace with a single Otechie username +custom: ["https://paypal.me/jdhao"] diff --git a/.config/nvim/.github/ISSUE_TEMPLATE/bug_report.md b/.config/nvim/.github/ISSUE_TEMPLATE/bug_report.md new file mode 100644 index 0000000..7a7d7a6 --- /dev/null +++ b/.config/nvim/.github/ISSUE_TEMPLATE/bug_report.md @@ -0,0 +1,40 @@ +--- +name: Bug report +about: Create a report to help us improve +title: '' +labels: '' +assignees: '' +--- + + + +- `nvim --version`: +- terminal emulator and version: +- Operating system/version: +- Have you read and followed all steps in the [doc](https://github.com/jdhao/nvim-config/tree/master/docs): +- Output of command `set runtimepath`: + + + ``` + + ``` + +**Describe the bug** + + + +Steps to reproduce the issue: + +1. +2. +3. + +**Expected behavior** + + +**Screenshots** + + +**Additional context** + + diff --git a/.config/nvim/.github/ISSUE_TEMPLATE/feature_request.md b/.config/nvim/.github/ISSUE_TEMPLATE/feature_request.md new file mode 100644 index 0000000..2f28cea --- /dev/null +++ b/.config/nvim/.github/ISSUE_TEMPLATE/feature_request.md @@ -0,0 +1,19 @@ +--- +name: Feature request +about: Suggest an idea for this project +title: '' +labels: '' +assignees: '' +--- + +**Is your feature request related to a problem? Please describe.** +A clear and concise description of what the problem is. Ex. I'm always frustrated when [...] + +**Describe the solution you'd like** +A clear and concise description of what you want to happen. + +**Describe alternatives you've considered** +A clear and concise description of any alternative solutions or features you've considered. + +**Additional context** +Add any other context or screenshots about the feature request here. diff --git a/.config/nvim/.gitignore b/.config/nvim/.gitignore new file mode 100644 index 0000000..75dbbbb --- /dev/null +++ b/.config/nvim/.gitignore @@ -0,0 +1,6 @@ +tags +en.utf-8.add.spl +*.exe +.netrwhist +*.log +.DS_Store diff --git a/.config/nvim/.stylua.toml b/.config/nvim/.stylua.toml new file mode 100644 index 0000000..4b13416 --- /dev/null +++ b/.config/nvim/.stylua.toml @@ -0,0 +1,6 @@ +# config for stylua, ref: https://github.com/JohnnyMorganz/StyLua +line_endings = "Unix" +indent_type = "Spaces" +indent_width = 2 +quote_style = "AutoPreferDouble" +call_parentheses= "NoSingleTable" diff --git a/.config/nvim/autoload/plug.vim b/.config/nvim/autoload/plug.vim new file mode 100644 index 0000000..f479fba --- /dev/null +++ b/.config/nvim/autoload/plug.vim @@ -0,0 +1,2877 @@ +" vim-plug: Vim plugin manager +" ============================ +" +" 1. Download plug.vim and put it in 'autoload' directory +" +" # Vim +" curl -fLo ~/.vim/autoload/plug.vim --create-dirs \ +" https://raw.githubusercontent.com/junegunn/vim-plug/master/plug.vim +" +" # Neovim +" sh -c 'curl -fLo "${XDG_DATA_HOME:-$HOME/.local/share}"/nvim/site/autoload/plug.vim --create-dirs \ +" https://raw.githubusercontent.com/junegunn/vim-plug/master/plug.vim' +" +" 2. Add a vim-plug section to your ~/.vimrc (or ~/.config/nvim/init.vim for Neovim) +" +" call plug#begin() +" +" " List your plugins here +" Plug 'tpope/vim-sensible' +" +" call plug#end() +" +" 3. Reload the file or restart Vim, then you can, +" +" :PlugInstall to install plugins +" :PlugUpdate to update plugins +" :PlugDiff to review the changes from the last update +" :PlugClean to remove plugins no longer in the list +" +" For more information, see https://github.com/junegunn/vim-plug +" +" +" Copyright (c) 2024 Junegunn Choi +" +" MIT License +" +" 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: +" +" The above copyright notice and this permission notice shall be +" included in all copies or substantial portions of the Software. +" +" 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. + +if exists('g:loaded_plug') + finish +endif +let g:loaded_plug = 1 + +let s:cpo_save = &cpo +set cpo&vim + +let s:plug_src = 'https://github.com/junegunn/vim-plug.git' +let s:plug_tab = get(s:, 'plug_tab', -1) +let s:plug_buf = get(s:, 'plug_buf', -1) +let s:mac_gui = has('gui_macvim') && has('gui_running') +let s:is_win = has('win32') +let s:nvim = has('nvim-0.2') || (has('nvim') && exists('*jobwait') && !s:is_win) +let s:vim8 = has('patch-8.0.0039') && exists('*job_start') +if s:is_win && &shellslash + set noshellslash + let s:me = resolve(expand(':p')) + set shellslash +else + let s:me = resolve(expand(':p')) +endif +let s:base_spec = { 'branch': '', 'frozen': 0 } +let s:TYPE = { +\ 'string': type(''), +\ 'list': type([]), +\ 'dict': type({}), +\ 'funcref': type(function('call')) +\ } +let s:loaded = get(s:, 'loaded', {}) +let s:triggers = get(s:, 'triggers', {}) + +function! s:is_powershell(shell) + return a:shell =~# 'powershell\(\.exe\)\?$' || a:shell =~# 'pwsh\(\.exe\)\?$' +endfunction + +function! s:isabsolute(dir) abort + return a:dir =~# '^/' || (has('win32') && a:dir =~? '^\%(\\\|[A-Z]:\)') +endfunction + +function! s:git_dir(dir) abort + let gitdir = s:trim(a:dir) . '/.git' + if isdirectory(gitdir) + return gitdir + endif + if !filereadable(gitdir) + return '' + endif + let gitdir = matchstr(get(readfile(gitdir), 0, ''), '^gitdir: \zs.*') + if len(gitdir) && !s:isabsolute(gitdir) + let gitdir = a:dir . '/' . gitdir + endif + return isdirectory(gitdir) ? gitdir : '' +endfunction + +function! s:git_origin_url(dir) abort + let gitdir = s:git_dir(a:dir) + let config = gitdir . '/config' + if empty(gitdir) || !filereadable(config) + return '' + endif + return matchstr(join(readfile(config)), '\[remote "origin"\].\{-}url\s*=\s*\zs\S*\ze') +endfunction + +function! s:git_revision(dir) abort + let gitdir = s:git_dir(a:dir) + let head = gitdir . '/HEAD' + if empty(gitdir) || !filereadable(head) + return '' + endif + + let line = get(readfile(head), 0, '') + let ref = matchstr(line, '^ref: \zs.*') + if empty(ref) + return line + endif + + if filereadable(gitdir . '/' . ref) + return get(readfile(gitdir . '/' . ref), 0, '') + endif + + if filereadable(gitdir . '/packed-refs') + for line in readfile(gitdir . '/packed-refs') + if line =~# ' ' . ref + return matchstr(line, '^[0-9a-f]*') + endif + endfor + endif + + return '' +endfunction + +function! s:git_local_branch(dir) abort + let gitdir = s:git_dir(a:dir) + let head = gitdir . '/HEAD' + if empty(gitdir) || !filereadable(head) + return '' + endif + let branch = matchstr(get(readfile(head), 0, ''), '^ref: refs/heads/\zs.*') + return len(branch) ? branch : 'HEAD' +endfunction + +function! s:git_origin_branch(spec) + if len(a:spec.branch) + return a:spec.branch + endif + + " The file may not be present if this is a local repository + let gitdir = s:git_dir(a:spec.dir) + let origin_head = gitdir.'/refs/remotes/origin/HEAD' + if len(gitdir) && filereadable(origin_head) + return matchstr(get(readfile(origin_head), 0, ''), + \ '^ref: refs/remotes/origin/\zs.*') + endif + + " The command may not return the name of a branch in detached HEAD state + let result = s:lines(s:system('git symbolic-ref --short HEAD', a:spec.dir)) + return v:shell_error ? '' : result[-1] +endfunction + +if s:is_win + function! s:plug_call(fn, ...) + let shellslash = &shellslash + try + set noshellslash + return call(a:fn, a:000) + finally + let &shellslash = shellslash + endtry + endfunction +else + function! s:plug_call(fn, ...) + return call(a:fn, a:000) + endfunction +endif + +function! s:plug_getcwd() + return s:plug_call('getcwd') +endfunction + +function! s:plug_fnamemodify(fname, mods) + return s:plug_call('fnamemodify', a:fname, a:mods) +endfunction + +function! s:plug_expand(fmt) + return s:plug_call('expand', a:fmt, 1) +endfunction + +function! s:plug_tempname() + return s:plug_call('tempname') +endfunction + +function! plug#begin(...) + if a:0 > 0 + let home = s:path(s:plug_fnamemodify(s:plug_expand(a:1), ':p')) + elseif exists('g:plug_home') + let home = s:path(g:plug_home) + elseif has('nvim') + let home = stdpath('data') . '/plugged' + elseif !empty(&rtp) + let home = s:path(split(&rtp, ',')[0]) . '/plugged' + else + return s:err('Unable to determine plug home. Try calling plug#begin() with a path argument.') + endif + if s:plug_fnamemodify(home, ':t') ==# 'plugin' && s:plug_fnamemodify(home, ':h') ==# s:first_rtp + return s:err('Invalid plug home. '.home.' is a standard Vim runtime path and is not allowed.') + endif + + let g:plug_home = home + let g:plugs = {} + let g:plugs_order = [] + let s:triggers = {} + + call s:define_commands() + return 1 +endfunction + +function! s:define_commands() + command! -nargs=+ -bar Plug call plug#() + if !executable('git') + return s:err('`git` executable not found. Most commands will not be available. To suppress this message, prepend `silent!` to `call plug#begin(...)`.') + endif + if has('win32') + \ && &shellslash + \ && (&shell =~# 'cmd\(\.exe\)\?$' || s:is_powershell(&shell)) + return s:err('vim-plug does not support shell, ' . &shell . ', when shellslash is set.') + endif + if !has('nvim') + \ && (has('win32') || has('win32unix')) + \ && !has('multi_byte') + return s:err('Vim needs +multi_byte feature on Windows to run shell commands. Enable +iconv for best results.') + endif + command! -nargs=* -bar -bang -complete=customlist,s:names PlugInstall call s:install(0, []) + command! -nargs=* -bar -bang -complete=customlist,s:names PlugUpdate call s:update(0, []) + command! -nargs=0 -bar -bang PlugClean call s:clean(0) + command! -nargs=0 -bar PlugUpgrade if s:upgrade() | execute 'source' s:esc(s:me) | endif + command! -nargs=0 -bar PlugStatus call s:status() + command! -nargs=0 -bar PlugDiff call s:diff() + command! -nargs=? -bar -bang -complete=file PlugSnapshot call s:snapshot(0, ) +endfunction + +function! s:to_a(v) + return type(a:v) == s:TYPE.list ? a:v : [a:v] +endfunction + +function! s:to_s(v) + return type(a:v) == s:TYPE.string ? a:v : join(a:v, "\n") . "\n" +endfunction + +function! s:glob(from, pattern) + return s:lines(globpath(a:from, a:pattern)) +endfunction + +function! s:source(from, ...) + let found = 0 + for pattern in a:000 + for vim in s:glob(a:from, pattern) + execute 'source' s:esc(vim) + let found = 1 + endfor + endfor + return found +endfunction + +function! s:assoc(dict, key, val) + let a:dict[a:key] = add(get(a:dict, a:key, []), a:val) +endfunction + +function! s:ask(message, ...) + call inputsave() + echohl WarningMsg + let answer = input(a:message.(a:0 ? ' (y/N/a) ' : ' (y/N) ')) + echohl None + call inputrestore() + echo "\r" + return (a:0 && answer =~? '^a') ? 2 : (answer =~? '^y') ? 1 : 0 +endfunction + +function! s:ask_no_interrupt(...) + try + return call('s:ask', a:000) + catch + return 0 + endtry +endfunction + +function! s:lazy(plug, opt) + return has_key(a:plug, a:opt) && + \ (empty(s:to_a(a:plug[a:opt])) || + \ !isdirectory(a:plug.dir) || + \ len(s:glob(s:rtp(a:plug), 'plugin')) || + \ len(s:glob(s:rtp(a:plug), 'after/plugin'))) +endfunction + +function! plug#end() + if !exists('g:plugs') + return s:err('plug#end() called without calling plug#begin() first') + endif + + if exists('#PlugLOD') + augroup PlugLOD + autocmd! + augroup END + augroup! PlugLOD + endif + let lod = { 'ft': {}, 'map': {}, 'cmd': {} } + + if get(g:, 'did_load_filetypes', 0) + filetype off + endif + for name in g:plugs_order + if !has_key(g:plugs, name) + continue + endif + let plug = g:plugs[name] + if get(s:loaded, name, 0) || !s:lazy(plug, 'on') && !s:lazy(plug, 'for') + let s:loaded[name] = 1 + continue + endif + + if has_key(plug, 'on') + let s:triggers[name] = { 'map': [], 'cmd': [] } + for cmd in s:to_a(plug.on) + if cmd =~? '^.\+' + if empty(mapcheck(cmd)) && empty(mapcheck(cmd, 'i')) + call s:assoc(lod.map, cmd, name) + endif + call add(s:triggers[name].map, cmd) + elseif cmd =~# '^[A-Z]' + let cmd = substitute(cmd, '!*$', '', '') + if exists(':'.cmd) != 2 + call s:assoc(lod.cmd, cmd, name) + endif + call add(s:triggers[name].cmd, cmd) + else + call s:err('Invalid `on` option: '.cmd. + \ '. Should start with an uppercase letter or ``.') + endif + endfor + endif + + if has_key(plug, 'for') + let types = s:to_a(plug.for) + if !empty(types) + augroup filetypedetect + call s:source(s:rtp(plug), 'ftdetect/**/*.vim', 'after/ftdetect/**/*.vim') + if has('nvim-0.5.0') + call s:source(s:rtp(plug), 'ftdetect/**/*.lua', 'after/ftdetect/**/*.lua') + endif + augroup END + endif + for type in types + call s:assoc(lod.ft, type, name) + endfor + endif + endfor + + for [cmd, names] in items(lod.cmd) + execute printf( + \ has('patch-7.4.1898') + \ ? 'command! -nargs=* -range -bang -complete=file %s call s:lod_cmd(%s, "", , , , ,%s)' + \ : 'command! -nargs=* -range -bang -complete=file %s call s:lod_cmd(%s, "", , , , %s)' + \ , cmd, string(cmd), string(names)) + endfor + + for [map, names] in items(lod.map) + for [mode, map_prefix, key_prefix] in + \ [['i', '', ''], ['n', '', ''], ['v', '', 'gv'], ['o', '', '']] + execute printf( + \ '%snoremap %s %s:call lod_map(%s, %s, %s, "%s")', + \ mode, map, map_prefix, string(map), string(names), mode != 'i', key_prefix) + endfor + endfor + + for [ft, names] in items(lod.ft) + augroup PlugLOD + execute printf('autocmd FileType %s call lod_ft(%s, %s)', + \ ft, string(ft), string(names)) + augroup END + endfor + + call s:reorg_rtp() + filetype plugin indent on + if has('vim_starting') + if has('syntax') && !exists('g:syntax_on') + syntax enable + end + else + call s:reload_plugins() + endif +endfunction + +function! s:loaded_names() + return filter(copy(g:plugs_order), 'get(s:loaded, v:val, 0)') +endfunction + +function! s:load_plugin(spec) + call s:source(s:rtp(a:spec), 'plugin/**/*.vim', 'after/plugin/**/*.vim') + if has('nvim-0.5.0') + call s:source(s:rtp(a:spec), 'plugin/**/*.lua', 'after/plugin/**/*.lua') + endif +endfunction + +function! s:reload_plugins() + for name in s:loaded_names() + call s:load_plugin(g:plugs[name]) + endfor +endfunction + +function! s:trim(str) + return substitute(a:str, '[\/]\+$', '', '') +endfunction + +function! s:version_requirement(val, min) + for idx in range(0, len(a:min) - 1) + let v = get(a:val, idx, 0) + if v < a:min[idx] | return 0 + elseif v > a:min[idx] | return 1 + endif + endfor + return 1 +endfunction + +function! s:git_version_requirement(...) + if !exists('s:git_version') + let s:git_version = map(split(split(s:system(['git', '--version']))[2], '\.'), 'str2nr(v:val)') + endif + return s:version_requirement(s:git_version, a:000) +endfunction + +function! s:progress_opt(base) + return a:base && !s:is_win && + \ s:git_version_requirement(1, 7, 1) ? '--progress' : '' +endfunction + +function! s:rtp(spec) + return s:path(a:spec.dir . get(a:spec, 'rtp', '')) +endfunction + +if s:is_win + function! s:path(path) + return s:trim(substitute(a:path, '/', '\', 'g')) + endfunction + + function! s:dirpath(path) + return s:path(a:path) . '\' + endfunction + + function! s:is_local_plug(repo) + return a:repo =~? '^[a-z]:\|^[%~]' + endfunction + + " Copied from fzf + function! s:wrap_cmds(cmds) + let cmds = [ + \ '@echo off', + \ 'setlocal enabledelayedexpansion'] + \ + (type(a:cmds) == type([]) ? a:cmds : [a:cmds]) + \ + ['endlocal'] + if has('iconv') + if !exists('s:codepage') + let s:codepage = libcallnr('kernel32.dll', 'GetACP', 0) + endif + return map(cmds, printf('iconv(v:val."\r", "%s", "cp%d")', &encoding, s:codepage)) + endif + return map(cmds, 'v:val."\r"') + endfunction + + function! s:batchfile(cmd) + let batchfile = s:plug_tempname().'.bat' + call writefile(s:wrap_cmds(a:cmd), batchfile) + let cmd = plug#shellescape(batchfile, {'shell': &shell, 'script': 0}) + if s:is_powershell(&shell) + let cmd = '& ' . cmd + endif + return [batchfile, cmd] + endfunction +else + function! s:path(path) + return s:trim(a:path) + endfunction + + function! s:dirpath(path) + return substitute(a:path, '[/\\]*$', '/', '') + endfunction + + function! s:is_local_plug(repo) + return a:repo[0] =~ '[/$~]' + endfunction +endif + +function! s:err(msg) + echohl ErrorMsg + echom '[vim-plug] '.a:msg + echohl None +endfunction + +function! s:warn(cmd, msg) + echohl WarningMsg + execute a:cmd 'a:msg' + echohl None +endfunction + +function! s:esc(path) + return escape(a:path, ' ') +endfunction + +function! s:escrtp(path) + return escape(a:path, ' ,') +endfunction + +function! s:remove_rtp() + for name in s:loaded_names() + let rtp = s:rtp(g:plugs[name]) + execute 'set rtp-='.s:escrtp(rtp) + let after = globpath(rtp, 'after') + if isdirectory(after) + execute 'set rtp-='.s:escrtp(after) + endif + endfor +endfunction + +function! s:reorg_rtp() + if !empty(s:first_rtp) + execute 'set rtp-='.s:first_rtp + execute 'set rtp-='.s:last_rtp + endif + + " &rtp is modified from outside + if exists('s:prtp') && s:prtp !=# &rtp + call s:remove_rtp() + unlet! s:middle + endif + + let s:middle = get(s:, 'middle', &rtp) + let rtps = map(s:loaded_names(), 's:rtp(g:plugs[v:val])') + let afters = filter(map(copy(rtps), 'globpath(v:val, "after")'), '!empty(v:val)') + let rtp = join(map(rtps, 'escape(v:val, ",")'), ',') + \ . ','.s:middle.',' + \ . join(map(afters, 'escape(v:val, ",")'), ',') + let &rtp = substitute(substitute(rtp, ',,*', ',', 'g'), '^,\|,$', '', 'g') + let s:prtp = &rtp + + if !empty(s:first_rtp) + execute 'set rtp^='.s:first_rtp + execute 'set rtp+='.s:last_rtp + endif +endfunction + +function! s:doautocmd(...) + if exists('#'.join(a:000, '#')) + execute 'doautocmd' ((v:version > 703 || has('patch442')) ? '' : '') join(a:000) + endif +endfunction + +function! s:dobufread(names) + for name in a:names + let path = s:rtp(g:plugs[name]) + for dir in ['ftdetect', 'ftplugin', 'after/ftdetect', 'after/ftplugin'] + if len(finddir(dir, path)) + if exists('#BufRead') + doautocmd BufRead + endif + return + endif + endfor + endfor +endfunction + +function! plug#load(...) + if a:0 == 0 + return s:err('Argument missing: plugin name(s) required') + endif + if !exists('g:plugs') + return s:err('plug#begin was not called') + endif + let names = a:0 == 1 && type(a:1) == s:TYPE.list ? a:1 : a:000 + let unknowns = filter(copy(names), '!has_key(g:plugs, v:val)') + if !empty(unknowns) + let s = len(unknowns) > 1 ? 's' : '' + return s:err(printf('Unknown plugin%s: %s', s, join(unknowns, ', '))) + end + let unloaded = filter(copy(names), '!get(s:loaded, v:val, 0)') + if !empty(unloaded) + for name in unloaded + call s:lod([name], ['ftdetect', 'after/ftdetect', 'plugin', 'after/plugin']) + endfor + call s:dobufread(unloaded) + return 1 + end + return 0 +endfunction + +function! s:remove_triggers(name) + if !has_key(s:triggers, a:name) + return + endif + for cmd in s:triggers[a:name].cmd + execute 'silent! delc' cmd + endfor + for map in s:triggers[a:name].map + execute 'silent! unmap' map + execute 'silent! iunmap' map + endfor + call remove(s:triggers, a:name) +endfunction + +function! s:lod(names, types, ...) + for name in a:names + call s:remove_triggers(name) + let s:loaded[name] = 1 + endfor + call s:reorg_rtp() + + for name in a:names + let rtp = s:rtp(g:plugs[name]) + for dir in a:types + call s:source(rtp, dir.'/**/*.vim') + if has('nvim-0.5.0') " see neovim#14686 + call s:source(rtp, dir.'/**/*.lua') + endif + endfor + if a:0 + if !s:source(rtp, a:1) && !empty(s:glob(rtp, a:2)) + execute 'runtime' a:1 + endif + call s:source(rtp, a:2) + endif + call s:doautocmd('User', name) + endfor +endfunction + +function! s:lod_ft(pat, names) + let syn = 'syntax/'.a:pat.'.vim' + call s:lod(a:names, ['plugin', 'after/plugin'], syn, 'after/'.syn) + execute 'autocmd! PlugLOD FileType' a:pat + call s:doautocmd('filetypeplugin', 'FileType') + call s:doautocmd('filetypeindent', 'FileType') +endfunction + +if has('patch-7.4.1898') + function! s:lod_cmd(cmd, bang, l1, l2, args, mods, names) + call s:lod(a:names, ['ftdetect', 'after/ftdetect', 'plugin', 'after/plugin']) + call s:dobufread(a:names) + execute printf('%s %s%s%s %s', a:mods, (a:l1 == a:l2 ? '' : (a:l1.','.a:l2)), a:cmd, a:bang, a:args) + endfunction +else + function! s:lod_cmd(cmd, bang, l1, l2, args, names) + call s:lod(a:names, ['ftdetect', 'after/ftdetect', 'plugin', 'after/plugin']) + call s:dobufread(a:names) + execute printf('%s%s%s %s', (a:l1 == a:l2 ? '' : (a:l1.','.a:l2)), a:cmd, a:bang, a:args) + endfunction +endif + +function! s:lod_map(map, names, with_prefix, prefix) + call s:lod(a:names, ['ftdetect', 'after/ftdetect', 'plugin', 'after/plugin']) + call s:dobufread(a:names) + let extra = '' + while 1 + let c = getchar(0) + if c == 0 + break + endif + let extra .= nr2char(c) + endwhile + + if a:with_prefix + let prefix = v:count ? v:count : '' + let prefix .= '"'.v:register.a:prefix + if mode(1) == 'no' + if v:operator == 'c' + let prefix = "\" . prefix + endif + let prefix .= v:operator + endif + call feedkeys(prefix, 'n') + endif + call feedkeys(substitute(a:map, '^', "\", '') . extra) +endfunction + +function! plug#(repo, ...) + if a:0 > 1 + return s:err('Invalid number of arguments (1..2)') + endif + + try + let repo = s:trim(a:repo) + let opts = a:0 == 1 ? s:parse_options(a:1) : s:base_spec + let name = get(opts, 'as', s:plug_fnamemodify(repo, ':t:s?\.git$??')) + let spec = extend(s:infer_properties(name, repo), opts) + if !has_key(g:plugs, name) + call add(g:plugs_order, name) + endif + let g:plugs[name] = spec + let s:loaded[name] = get(s:loaded, name, 0) + catch + return s:err(repo . ' ' . v:exception) + endtry +endfunction + +function! s:parse_options(arg) + let opts = copy(s:base_spec) + let type = type(a:arg) + let opt_errfmt = 'Invalid argument for "%s" option of :Plug (expected: %s)' + if type == s:TYPE.string + if empty(a:arg) + throw printf(opt_errfmt, 'tag', 'string') + endif + let opts.tag = a:arg + elseif type == s:TYPE.dict + for opt in ['branch', 'tag', 'commit', 'rtp', 'dir', 'as'] + if has_key(a:arg, opt) + \ && (type(a:arg[opt]) != s:TYPE.string || empty(a:arg[opt])) + throw printf(opt_errfmt, opt, 'string') + endif + endfor + for opt in ['on', 'for'] + if has_key(a:arg, opt) + \ && type(a:arg[opt]) != s:TYPE.list + \ && (type(a:arg[opt]) != s:TYPE.string || empty(a:arg[opt])) + throw printf(opt_errfmt, opt, 'string or list') + endif + endfor + if has_key(a:arg, 'do') + \ && type(a:arg.do) != s:TYPE.funcref + \ && (type(a:arg.do) != s:TYPE.string || empty(a:arg.do)) + throw printf(opt_errfmt, 'do', 'string or funcref') + endif + call extend(opts, a:arg) + if has_key(opts, 'dir') + let opts.dir = s:dirpath(s:plug_expand(opts.dir)) + endif + else + throw 'Invalid argument type (expected: string or dictionary)' + endif + return opts +endfunction + +function! s:infer_properties(name, repo) + let repo = a:repo + if s:is_local_plug(repo) + return { 'dir': s:dirpath(s:plug_expand(repo)) } + else + if repo =~ ':' + let uri = repo + else + if repo !~ '/' + throw printf('Invalid argument: %s (implicit `vim-scripts'' expansion is deprecated)', repo) + endif + let fmt = get(g:, 'plug_url_format', 'https://git::@github.com/%s.git') + let uri = printf(fmt, repo) + endif + return { 'dir': s:dirpath(g:plug_home.'/'.a:name), 'uri': uri } + endif +endfunction + +function! s:install(force, names) + call s:update_impl(0, a:force, a:names) +endfunction + +function! s:update(force, names) + call s:update_impl(1, a:force, a:names) +endfunction + +function! plug#helptags() + if !exists('g:plugs') + return s:err('plug#begin was not called') + endif + for spec in values(g:plugs) + let docd = join([s:rtp(spec), 'doc'], '/') + if isdirectory(docd) + silent! execute 'helptags' s:esc(docd) + endif + endfor + return 1 +endfunction + +function! s:syntax() + syntax clear + syntax region plug1 start=/\%1l/ end=/\%2l/ contains=plugNumber + syntax region plug2 start=/\%2l/ end=/\%3l/ contains=plugBracket,plugX,plugAbort + syn match plugNumber /[0-9]\+[0-9.]*/ contained + syn match plugBracket /[[\]]/ contained + syn match plugX /x/ contained + syn match plugAbort /\~/ contained + syn match plugDash /^-\{1}\ / + syn match plugPlus /^+/ + syn match plugStar /^*/ + syn match plugMessage /\(^- \)\@<=.*/ + syn match plugName /\(^- \)\@<=[^ ]*:/ + syn match plugSha /\%(: \)\@<=[0-9a-f]\{4,}$/ + syn match plugTag /(tag: [^)]\+)/ + syn match plugInstall /\(^+ \)\@<=[^:]*/ + syn match plugUpdate /\(^* \)\@<=[^:]*/ + syn match plugCommit /^ \X*[0-9a-f]\{7,9} .*/ contains=plugRelDate,plugEdge,plugTag + syn match plugEdge /^ \X\+$/ + syn match plugEdge /^ \X*/ contained nextgroup=plugSha + syn match plugSha /[0-9a-f]\{7,9}/ contained + syn match plugRelDate /([^)]*)$/ contained + syn match plugNotLoaded /(not loaded)$/ + syn match plugError /^x.*/ + syn region plugDeleted start=/^\~ .*/ end=/^\ze\S/ + syn match plugH2 /^.*:\n-\+$/ + syn match plugH2 /^-\{2,}/ + syn keyword Function PlugInstall PlugStatus PlugUpdate PlugClean + hi def link plug1 Title + hi def link plug2 Repeat + hi def link plugH2 Type + hi def link plugX Exception + hi def link plugAbort Ignore + hi def link plugBracket Structure + hi def link plugNumber Number + + hi def link plugDash Special + hi def link plugPlus Constant + hi def link plugStar Boolean + + hi def link plugMessage Function + hi def link plugName Label + hi def link plugInstall Function + hi def link plugUpdate Type + + hi def link plugError Error + hi def link plugDeleted Ignore + hi def link plugRelDate Comment + hi def link plugEdge PreProc + hi def link plugSha Identifier + hi def link plugTag Constant + + hi def link plugNotLoaded Comment +endfunction + +function! s:lpad(str, len) + return a:str . repeat(' ', a:len - len(a:str)) +endfunction + +function! s:lines(msg) + return split(a:msg, "[\r\n]") +endfunction + +function! s:lastline(msg) + return get(s:lines(a:msg), -1, '') +endfunction + +function! s:new_window() + execute get(g:, 'plug_window', '-tabnew') +endfunction + +function! s:plug_window_exists() + let buflist = tabpagebuflist(s:plug_tab) + return !empty(buflist) && index(buflist, s:plug_buf) >= 0 +endfunction + +function! s:switch_in() + if !s:plug_window_exists() + return 0 + endif + + if winbufnr(0) != s:plug_buf + let s:pos = [tabpagenr(), winnr(), winsaveview()] + execute 'normal!' s:plug_tab.'gt' + let winnr = bufwinnr(s:plug_buf) + execute winnr.'wincmd w' + call add(s:pos, winsaveview()) + else + let s:pos = [winsaveview()] + endif + + setlocal modifiable + return 1 +endfunction + +function! s:switch_out(...) + call winrestview(s:pos[-1]) + setlocal nomodifiable + if a:0 > 0 + execute a:1 + endif + + if len(s:pos) > 1 + execute 'normal!' s:pos[0].'gt' + execute s:pos[1] 'wincmd w' + call winrestview(s:pos[2]) + endif +endfunction + +function! s:finish_bindings() + nnoremap R :call retry() + nnoremap D :PlugDiff + nnoremap S :PlugStatus + nnoremap U :call status_update() + xnoremap U :call status_update() + nnoremap ]] :silent! call section('') + nnoremap [[ :silent! call section('b') +endfunction + +function! s:prepare(...) + if empty(s:plug_getcwd()) + throw 'Invalid current working directory. Cannot proceed.' + endif + + for evar in ['$GIT_DIR', '$GIT_WORK_TREE'] + if exists(evar) + throw evar.' detected. Cannot proceed.' + endif + endfor + + call s:job_abort(0) + if s:switch_in() + if b:plug_preview == 1 + pc + endif + enew + else + call s:new_window() + endif + + nnoremap q :call close_pane() + if a:0 == 0 + call s:finish_bindings() + endif + let b:plug_preview = -1 + let s:plug_tab = tabpagenr() + let s:plug_buf = winbufnr(0) + call s:assign_name() + + for k in ['', 'L', 'o', 'X', 'd', 'dd'] + execute 'silent! unmap ' k + endfor + setlocal buftype=nofile bufhidden=wipe nobuflisted nolist noswapfile nowrap cursorline modifiable nospell + if exists('+colorcolumn') + setlocal colorcolumn= + endif + setf vim-plug + if exists('g:syntax_on') + call s:syntax() + endif +endfunction + +function! s:close_pane() + if b:plug_preview == 1 + pc + let b:plug_preview = -1 + elseif exists('s:jobs') && !empty(s:jobs) + call s:job_abort(1) + else + bd + endif +endfunction + +function! s:assign_name() + " Assign buffer name + let prefix = '[Plugins]' + let name = prefix + let idx = 2 + while bufexists(name) + let name = printf('%s (%s)', prefix, idx) + let idx = idx + 1 + endwhile + silent! execute 'f' fnameescape(name) +endfunction + +function! s:chsh(swap) + let prev = [&shell, &shellcmdflag, &shellredir] + if !s:is_win + set shell=sh + endif + if a:swap + if s:is_powershell(&shell) + let &shellredir = '2>&1 | Out-File -Encoding UTF8 %s' + elseif &shell =~# 'sh' || &shell =~# 'cmd\(\.exe\)\?$' + set shellredir=>%s\ 2>&1 + endif + endif + return prev +endfunction + +function! s:bang(cmd, ...) + let batchfile = '' + try + let [sh, shellcmdflag, shrd] = s:chsh(a:0) + " FIXME: Escaping is incomplete. We could use shellescape with eval, + " but it won't work on Windows. + let cmd = a:0 ? s:with_cd(a:cmd, a:1) : a:cmd + if s:is_win + let [batchfile, cmd] = s:batchfile(cmd) + endif + let g:_plug_bang = (s:is_win && has('gui_running') ? 'silent ' : '').'!'.escape(cmd, '#!%') + execute "normal! :execute g:_plug_bang\\" + finally + unlet g:_plug_bang + let [&shell, &shellcmdflag, &shellredir] = [sh, shellcmdflag, shrd] + if s:is_win && filereadable(batchfile) + call delete(batchfile) + endif + endtry + return v:shell_error ? 'Exit status: ' . v:shell_error : '' +endfunction + +function! s:regress_bar() + let bar = substitute(getline(2)[1:-2], '.*\zs=', 'x', '') + call s:progress_bar(2, bar, len(bar)) +endfunction + +function! s:is_updated(dir) + return !empty(s:system_chomp(['git', 'log', '--pretty=format:%h', 'HEAD...HEAD@{1}'], a:dir)) +endfunction + +function! s:do(pull, force, todo) + if has('nvim') + " Reset &rtp to invalidate Neovim cache of loaded Lua modules + " See https://github.com/junegunn/vim-plug/pull/1157#issuecomment-1809226110 + let &rtp = &rtp + endif + for [name, spec] in items(a:todo) + if !isdirectory(spec.dir) + continue + endif + let installed = has_key(s:update.new, name) + let updated = installed ? 0 : + \ (a:pull && index(s:update.errors, name) < 0 && s:is_updated(spec.dir)) + if a:force || installed || updated + execute 'cd' s:esc(spec.dir) + call append(3, '- Post-update hook for '. name .' ... ') + let error = '' + let type = type(spec.do) + if type == s:TYPE.string + if spec.do[0] == ':' + if !get(s:loaded, name, 0) + let s:loaded[name] = 1 + call s:reorg_rtp() + endif + call s:load_plugin(spec) + try + execute spec.do[1:] + catch + let error = v:exception + endtry + if !s:plug_window_exists() + cd - + throw 'Warning: vim-plug was terminated by the post-update hook of '.name + endif + else + let error = s:bang(spec.do) + endif + elseif type == s:TYPE.funcref + try + call s:load_plugin(spec) + let status = installed ? 'installed' : (updated ? 'updated' : 'unchanged') + call spec.do({ 'name': name, 'status': status, 'force': a:force }) + catch + let error = v:exception + endtry + else + let error = 'Invalid hook type' + endif + call s:switch_in() + call setline(4, empty(error) ? (getline(4) . 'OK') + \ : ('x' . getline(4)[1:] . error)) + if !empty(error) + call add(s:update.errors, name) + call s:regress_bar() + endif + cd - + endif + endfor +endfunction + +function! s:hash_match(a, b) + return stridx(a:a, a:b) == 0 || stridx(a:b, a:a) == 0 +endfunction + +function! s:disable_credential_helper() + return s:git_version_requirement(2) && get(g:, 'plug_disable_credential_helper', 1) +endfunction + +function! s:checkout(spec) + let sha = a:spec.commit + let output = s:git_revision(a:spec.dir) + let error = 0 + if !empty(output) && !s:hash_match(sha, s:lines(output)[0]) + let credential_helper = s:disable_credential_helper() ? '-c credential.helper= ' : '' + let output = s:system( + \ 'git '.credential_helper.'fetch --depth 999999 && git checkout '.plug#shellescape(sha).' --', a:spec.dir) + let error = v:shell_error + endif + return [output, error] +endfunction + +function! s:finish(pull) + let new_frozen = len(filter(keys(s:update.new), 'g:plugs[v:val].frozen')) + if new_frozen + let s = new_frozen > 1 ? 's' : '' + call append(3, printf('- Installed %d frozen plugin%s', new_frozen, s)) + endif + call append(3, '- Finishing ... ') | 4 + redraw + call plug#helptags() + call plug#end() + call setline(4, getline(4) . 'Done!') + redraw + let msgs = [] + if !empty(s:update.errors) + call add(msgs, "Press 'R' to retry.") + endif + if a:pull && len(s:update.new) < len(filter(getline(5, '$'), + \ "v:val =~ '^- ' && v:val !~# 'Already up.to.date'")) + call add(msgs, "Press 'D' to see the updated changes.") + endif + echo join(msgs, ' ') + call s:finish_bindings() +endfunction + +function! s:retry() + if empty(s:update.errors) + return + endif + echo + call s:update_impl(s:update.pull, s:update.force, + \ extend(copy(s:update.errors), [s:update.threads])) +endfunction + +function! s:is_managed(name) + return has_key(g:plugs[a:name], 'uri') +endfunction + +function! s:names(...) + return sort(filter(keys(g:plugs), 'stridx(v:val, a:1) == 0 && s:is_managed(v:val)')) +endfunction + +function! s:check_ruby() + silent! ruby require 'thread'; VIM::command("let g:plug_ruby = '#{RUBY_VERSION}'") + if !exists('g:plug_ruby') + redraw! + return s:warn('echom', 'Warning: Ruby interface is broken') + endif + let ruby_version = split(g:plug_ruby, '\.') + unlet g:plug_ruby + return s:version_requirement(ruby_version, [1, 8, 7]) +endfunction + +function! s:update_impl(pull, force, args) abort + let sync = index(a:args, '--sync') >= 0 || has('vim_starting') + let args = filter(copy(a:args), 'v:val != "--sync"') + let threads = (len(args) > 0 && args[-1] =~ '^[1-9][0-9]*$') ? + \ remove(args, -1) : get(g:, 'plug_threads', 16) + + let managed = filter(deepcopy(g:plugs), 's:is_managed(v:key)') + let todo = empty(args) ? filter(managed, '!v:val.frozen || !isdirectory(v:val.dir)') : + \ filter(managed, 'index(args, v:key) >= 0') + + if empty(todo) + return s:warn('echo', 'No plugin to '. (a:pull ? 'update' : 'install')) + endif + + if !s:is_win && s:git_version_requirement(2, 3) + let s:git_terminal_prompt = exists('$GIT_TERMINAL_PROMPT') ? $GIT_TERMINAL_PROMPT : '' + let $GIT_TERMINAL_PROMPT = 0 + for plug in values(todo) + let plug.uri = substitute(plug.uri, + \ '^https://git::@github\.com', 'https://github.com', '') + endfor + endif + + if !isdirectory(g:plug_home) + try + call mkdir(g:plug_home, 'p') + catch + return s:err(printf('Invalid plug directory: %s. '. + \ 'Try to call plug#begin with a valid directory', g:plug_home)) + endtry + endif + + if has('nvim') && !exists('*jobwait') && threads > 1 + call s:warn('echom', '[vim-plug] Update Neovim for parallel installer') + endif + + let use_job = s:nvim || s:vim8 + let python = (has('python') || has('python3')) && !use_job + let ruby = has('ruby') && !use_job && (v:version >= 703 || v:version == 702 && has('patch374')) && !(s:is_win && has('gui_running')) && threads > 1 && s:check_ruby() + + let s:update = { + \ 'start': reltime(), + \ 'all': todo, + \ 'todo': copy(todo), + \ 'errors': [], + \ 'pull': a:pull, + \ 'force': a:force, + \ 'new': {}, + \ 'threads': (python || ruby || use_job) ? min([len(todo), threads]) : 1, + \ 'bar': '', + \ 'fin': 0 + \ } + + call s:prepare(1) + call append(0, ['', '']) + normal! 2G + silent! redraw + + " Set remote name, overriding a possible user git config's clone.defaultRemoteName + let s:clone_opt = ['--origin', 'origin'] + if get(g:, 'plug_shallow', 1) + call extend(s:clone_opt, ['--depth', '1']) + if s:git_version_requirement(1, 7, 10) + call add(s:clone_opt, '--no-single-branch') + endif + endif + + if has('win32unix') || has('wsl') + call extend(s:clone_opt, ['-c', 'core.eol=lf', '-c', 'core.autocrlf=input']) + endif + + let s:submodule_opt = s:git_version_requirement(2, 8) ? ' --jobs='.threads : '' + + " Python version requirement (>= 2.7) + if python && !has('python3') && !ruby && !use_job && s:update.threads > 1 + redir => pyv + silent python import platform; print platform.python_version() + redir END + let python = s:version_requirement( + \ map(split(split(pyv)[0], '\.'), 'str2nr(v:val)'), [2, 6]) + endif + + if (python || ruby) && s:update.threads > 1 + try + let imd = &imd + if s:mac_gui + set noimd + endif + if ruby + call s:update_ruby() + else + call s:update_python() + endif + catch + let lines = getline(4, '$') + let printed = {} + silent! 4,$d _ + for line in lines + let name = s:extract_name(line, '.', '') + if empty(name) || !has_key(printed, name) + call append('$', line) + if !empty(name) + let printed[name] = 1 + if line[0] == 'x' && index(s:update.errors, name) < 0 + call add(s:update.errors, name) + end + endif + endif + endfor + finally + let &imd = imd + call s:update_finish() + endtry + else + call s:update_vim() + while use_job && sync + sleep 100m + if s:update.fin + break + endif + endwhile + endif +endfunction + +function! s:log4(name, msg) + call setline(4, printf('- %s (%s)', a:msg, a:name)) + redraw +endfunction + +function! s:update_finish() + if exists('s:git_terminal_prompt') + let $GIT_TERMINAL_PROMPT = s:git_terminal_prompt + endif + if s:switch_in() + call append(3, '- Updating ...') | 4 + for [name, spec] in items(filter(copy(s:update.all), 'index(s:update.errors, v:key) < 0 && (s:update.force || s:update.pull || has_key(s:update.new, v:key))')) + let [pos, _] = s:logpos(name) + if !pos + continue + endif + let out = '' + let error = 0 + if has_key(spec, 'commit') + call s:log4(name, 'Checking out '.spec.commit) + let [out, error] = s:checkout(spec) + elseif has_key(spec, 'tag') + let tag = spec.tag + if tag =~ '\*' + let tags = s:lines(s:system('git tag --list '.plug#shellescape(tag).' --sort -version:refname 2>&1', spec.dir)) + if !v:shell_error && !empty(tags) + let tag = tags[0] + call s:log4(name, printf('Latest tag for %s -> %s', spec.tag, tag)) + call append(3, '') + endif + endif + call s:log4(name, 'Checking out '.tag) + let out = s:system('git checkout -q '.plug#shellescape(tag).' -- 2>&1', spec.dir) + let error = v:shell_error + endif + if !error && filereadable(spec.dir.'/.gitmodules') && + \ (s:update.force || has_key(s:update.new, name) || s:is_updated(spec.dir)) + call s:log4(name, 'Updating submodules. This may take a while.') + let out .= s:bang('git submodule update --init --recursive'.s:submodule_opt.' 2>&1', spec.dir) + let error = v:shell_error + endif + let msg = s:format_message(v:shell_error ? 'x': '-', name, out) + if error + call add(s:update.errors, name) + call s:regress_bar() + silent execute pos 'd _' + call append(4, msg) | 4 + elseif !empty(out) + call setline(pos, msg[0]) + endif + redraw + endfor + silent 4 d _ + try + call s:do(s:update.pull, s:update.force, filter(copy(s:update.all), 'index(s:update.errors, v:key) < 0 && has_key(v:val, "do")')) + catch + call s:warn('echom', v:exception) + call s:warn('echo', '') + return + endtry + call s:finish(s:update.pull) + call setline(1, 'Updated. Elapsed time: ' . split(reltimestr(reltime(s:update.start)))[0] . ' sec.') + call s:switch_out('normal! gg') + endif +endfunction + +function! s:mark_aborted(name, message) + let attrs = { 'running': 0, 'error': 1, 'abort': 1, 'lines': [a:message] } + let s:jobs[a:name] = extend(get(s:jobs, a:name, {}), attrs) +endfunction + +function! s:job_abort(cancel) + if (!s:nvim && !s:vim8) || !exists('s:jobs') + return + endif + + for [name, j] in items(s:jobs) + if s:nvim + silent! call jobstop(j.jobid) + elseif s:vim8 + silent! call job_stop(j.jobid) + endif + if j.new + call s:rm_rf(g:plugs[name].dir) + endif + if a:cancel + call s:mark_aborted(name, 'Aborted') + endif + endfor + + if a:cancel + for todo in values(s:update.todo) + let todo.abort = 1 + endfor + else + let s:jobs = {} + endif +endfunction + +function! s:last_non_empty_line(lines) + let len = len(a:lines) + for idx in range(len) + let line = a:lines[len-idx-1] + if !empty(line) + return line + endif + endfor + return '' +endfunction + +function! s:bullet_for(job, ...) + if a:job.running + return a:job.new ? '+' : '*' + endif + if get(a:job, 'abort', 0) + return '~' + endif + return a:job.error ? 'x' : get(a:000, 0, '-') +endfunction + +function! s:job_out_cb(self, data) abort + let self = a:self + let data = remove(self.lines, -1) . a:data + let lines = map(split(data, "\n", 1), 'split(v:val, "\r", 1)[-1]') + call extend(self.lines, lines) + " To reduce the number of buffer updates + let self.tick = get(self, 'tick', -1) + 1 + if !self.running || self.tick % len(s:jobs) == 0 + let result = self.error ? join(self.lines, "\n") : s:last_non_empty_line(self.lines) + if len(result) + call s:log(s:bullet_for(self), self.name, result) + endif + endif +endfunction + +function! s:job_exit_cb(self, data) abort + let a:self.running = 0 + let a:self.error = a:data != 0 + call s:reap(a:self.name) + call s:tick() +endfunction + +function! s:job_cb(fn, job, ch, data) + if !s:plug_window_exists() " plug window closed + return s:job_abort(0) + endif + call call(a:fn, [a:job, a:data]) +endfunction + +function! s:nvim_cb(job_id, data, event) dict abort + return (a:event == 'stdout' || a:event == 'stderr') ? + \ s:job_cb('s:job_out_cb', self, 0, join(a:data, "\n")) : + \ s:job_cb('s:job_exit_cb', self, 0, a:data) +endfunction + +function! s:spawn(name, spec, queue, opts) + let job = { 'name': a:name, 'spec': a:spec, 'running': 1, 'error': 0, 'lines': [''], + \ 'new': get(a:opts, 'new', 0), 'queue': copy(a:queue) } + let Item = remove(job.queue, 0) + let argv = type(Item) == s:TYPE.funcref ? call(Item, [a:spec]) : Item + let s:jobs[a:name] = job + + if s:nvim + if has_key(a:opts, 'dir') + let job.cwd = a:opts.dir + endif + call extend(job, { + \ 'on_stdout': function('s:nvim_cb'), + \ 'on_stderr': function('s:nvim_cb'), + \ 'on_exit': function('s:nvim_cb'), + \ }) + let jid = s:plug_call('jobstart', argv, job) + if jid > 0 + let job.jobid = jid + else + let job.running = 0 + let job.error = 1 + let job.lines = [jid < 0 ? argv[0].' is not executable' : + \ 'Invalid arguments (or job table is full)'] + endif + elseif s:vim8 + let cmd = join(map(copy(argv), 'plug#shellescape(v:val, {"script": 0})')) + if has_key(a:opts, 'dir') + let cmd = s:with_cd(cmd, a:opts.dir, 0) + endif + let argv = s:is_win ? ['cmd', '/s', '/c', '"'.cmd.'"'] : ['sh', '-c', cmd] + let jid = job_start(s:is_win ? join(argv, ' ') : argv, { + \ 'out_cb': function('s:job_cb', ['s:job_out_cb', job]), + \ 'err_cb': function('s:job_cb', ['s:job_out_cb', job]), + \ 'exit_cb': function('s:job_cb', ['s:job_exit_cb', job]), + \ 'err_mode': 'raw', + \ 'out_mode': 'raw' + \}) + if job_status(jid) == 'run' + let job.jobid = jid + else + let job.running = 0 + let job.error = 1 + let job.lines = ['Failed to start job'] + endif + else + let job.lines = s:lines(call('s:system', has_key(a:opts, 'dir') ? [argv, a:opts.dir] : [argv])) + let job.error = v:shell_error != 0 + let job.running = 0 + endif +endfunction + +function! s:reap(name) + let job = remove(s:jobs, a:name) + if job.error + call add(s:update.errors, a:name) + elseif get(job, 'new', 0) + let s:update.new[a:name] = 1 + endif + + let more = len(get(job, 'queue', [])) + let result = job.error ? join(job.lines, "\n") : s:last_non_empty_line(job.lines) + if len(result) + call s:log(s:bullet_for(job), a:name, result) + endif + + if !job.error && more + let job.spec.queue = job.queue + let s:update.todo[a:name] = job.spec + else + let s:update.bar .= s:bullet_for(job, '=') + call s:bar() + endif +endfunction + +function! s:bar() + if s:switch_in() + let total = len(s:update.all) + call setline(1, (s:update.pull ? 'Updating' : 'Installing'). + \ ' plugins ('.len(s:update.bar).'/'.total.')') + call s:progress_bar(2, s:update.bar, total) + call s:switch_out() + endif +endfunction + +function! s:logpos(name) + let max = line('$') + for i in range(4, max > 4 ? max : 4) + if getline(i) =~# '^[-+x*] '.a:name.':' + for j in range(i + 1, max > 5 ? max : 5) + if getline(j) !~ '^ ' + return [i, j - 1] + endif + endfor + return [i, i] + endif + endfor + return [0, 0] +endfunction + +function! s:log(bullet, name, lines) + if s:switch_in() + let [b, e] = s:logpos(a:name) + if b > 0 + silent execute printf('%d,%d d _', b, e) + if b > winheight('.') + let b = 4 + endif + else + let b = 4 + endif + " FIXME For some reason, nomodifiable is set after :d in vim8 + setlocal modifiable + call append(b - 1, s:format_message(a:bullet, a:name, a:lines)) + call s:switch_out() + endif +endfunction + +function! s:update_vim() + let s:jobs = {} + + call s:bar() + call s:tick() +endfunction + +function! s:checkout_command(spec) + let a:spec.branch = s:git_origin_branch(a:spec) + return ['git', 'checkout', '-q', a:spec.branch, '--'] +endfunction + +function! s:merge_command(spec) + let a:spec.branch = s:git_origin_branch(a:spec) + return ['git', 'merge', '--ff-only', 'origin/'.a:spec.branch] +endfunction + +function! s:tick() + let pull = s:update.pull + let prog = s:progress_opt(s:nvim || s:vim8) +while 1 " Without TCO, Vim stack is bound to explode + if empty(s:update.todo) + if empty(s:jobs) && !s:update.fin + call s:update_finish() + let s:update.fin = 1 + endif + return + endif + + let name = keys(s:update.todo)[0] + let spec = remove(s:update.todo, name) + if get(spec, 'abort', 0) + call s:mark_aborted(name, 'Skipped') + call s:reap(name) + continue + endif + + let queue = get(spec, 'queue', []) + let new = empty(globpath(spec.dir, '.git', 1)) + + if empty(queue) + call s:log(new ? '+' : '*', name, pull ? 'Updating ...' : 'Installing ...') + redraw + endif + + let has_tag = has_key(spec, 'tag') + if len(queue) + call s:spawn(name, spec, queue, { 'dir': spec.dir }) + elseif !new + let [error, _] = s:git_validate(spec, 0) + if empty(error) + if pull + let cmd = s:disable_credential_helper() ? ['git', '-c', 'credential.helper=', 'fetch'] : ['git', 'fetch'] + if has_tag && !empty(globpath(spec.dir, '.git/shallow')) + call extend(cmd, ['--depth', '99999999']) + endif + if !empty(prog) + call add(cmd, prog) + endif + let queue = [cmd, split('git remote set-head origin -a')] + if !has_tag && !has_key(spec, 'commit') + call extend(queue, [function('s:checkout_command'), function('s:merge_command')]) + endif + call s:spawn(name, spec, queue, { 'dir': spec.dir }) + else + let s:jobs[name] = { 'running': 0, 'lines': ['Already installed'], 'error': 0 } + endif + else + let s:jobs[name] = { 'running': 0, 'lines': s:lines(error), 'error': 1 } + endif + else + let cmd = ['git', 'clone'] + if !has_tag + call extend(cmd, s:clone_opt) + endif + if !empty(prog) + call add(cmd, prog) + endif + call s:spawn(name, spec, [extend(cmd, [spec.uri, s:trim(spec.dir)]), function('s:checkout_command'), function('s:merge_command')], { 'new': 1 }) + endif + + if !s:jobs[name].running + call s:reap(name) + endif + if len(s:jobs) >= s:update.threads + break + endif +endwhile +endfunction + +function! s:update_python() +let py_exe = has('python') ? 'python' : 'python3' +execute py_exe "<< EOF" +import datetime +import functools +import os +try: + import queue +except ImportError: + import Queue as queue +import random +import re +import shutil +import signal +import subprocess +import tempfile +import threading as thr +import time +import traceback +import vim + +G_NVIM = vim.eval("has('nvim')") == '1' +G_PULL = vim.eval('s:update.pull') == '1' +G_RETRIES = int(vim.eval('get(g:, "plug_retries", 2)')) + 1 +G_TIMEOUT = int(vim.eval('get(g:, "plug_timeout", 60)')) +G_CLONE_OPT = ' '.join(vim.eval('s:clone_opt')) +G_PROGRESS = vim.eval('s:progress_opt(1)') +G_LOG_PROB = 1.0 / int(vim.eval('s:update.threads')) +G_STOP = thr.Event() +G_IS_WIN = vim.eval('s:is_win') == '1' + +class PlugError(Exception): + def __init__(self, msg): + self.msg = msg +class CmdTimedOut(PlugError): + pass +class CmdFailed(PlugError): + pass +class InvalidURI(PlugError): + pass +class Action(object): + INSTALL, UPDATE, ERROR, DONE = ['+', '*', 'x', '-'] + +class Buffer(object): + def __init__(self, lock, num_plugs, is_pull): + self.bar = '' + self.event = 'Updating' if is_pull else 'Installing' + self.lock = lock + self.maxy = int(vim.eval('winheight(".")')) + self.num_plugs = num_plugs + + def __where(self, name): + """ Find first line with name in current buffer. Return line num. """ + found, lnum = False, 0 + matcher = re.compile('^[-+x*] {0}:'.format(name)) + for line in vim.current.buffer: + if matcher.search(line) is not None: + found = True + break + lnum += 1 + + if not found: + lnum = -1 + return lnum + + def header(self): + curbuf = vim.current.buffer + curbuf[0] = self.event + ' plugins ({0}/{1})'.format(len(self.bar), self.num_plugs) + + num_spaces = self.num_plugs - len(self.bar) + curbuf[1] = '[{0}{1}]'.format(self.bar, num_spaces * ' ') + + with self.lock: + vim.command('normal! 2G') + vim.command('redraw') + + def write(self, action, name, lines): + first, rest = lines[0], lines[1:] + msg = ['{0} {1}{2}{3}'.format(action, name, ': ' if first else '', first)] + msg.extend([' ' + line for line in rest]) + + try: + if action == Action.ERROR: + self.bar += 'x' + vim.command("call add(s:update.errors, '{0}')".format(name)) + elif action == Action.DONE: + self.bar += '=' + + curbuf = vim.current.buffer + lnum = self.__where(name) + if lnum != -1: # Found matching line num + del curbuf[lnum] + if lnum > self.maxy and action in set([Action.INSTALL, Action.UPDATE]): + lnum = 3 + else: + lnum = 3 + curbuf.append(msg, lnum) + + self.header() + except vim.error: + pass + +class Command(object): + CD = 'cd /d' if G_IS_WIN else 'cd' + + def __init__(self, cmd, cmd_dir=None, timeout=60, cb=None, clean=None): + self.cmd = cmd + if cmd_dir: + self.cmd = '{0} {1} && {2}'.format(Command.CD, cmd_dir, self.cmd) + self.timeout = timeout + self.callback = cb if cb else (lambda msg: None) + self.clean = clean if clean else (lambda: None) + self.proc = None + + @property + def alive(self): + """ Returns true only if command still running. """ + return self.proc and self.proc.poll() is None + + def execute(self, ntries=3): + """ Execute the command with ntries if CmdTimedOut. + Returns the output of the command if no Exception. + """ + attempt, finished, limit = 0, False, self.timeout + + while not finished: + try: + attempt += 1 + result = self.try_command() + finished = True + return result + except CmdTimedOut: + if attempt != ntries: + self.notify_retry() + self.timeout += limit + else: + raise + + def notify_retry(self): + """ Retry required for command, notify user. """ + for count in range(3, 0, -1): + if G_STOP.is_set(): + raise KeyboardInterrupt + msg = 'Timeout. Will retry in {0} second{1} ...'.format( + count, 's' if count != 1 else '') + self.callback([msg]) + time.sleep(1) + self.callback(['Retrying ...']) + + def try_command(self): + """ Execute a cmd & poll for callback. Returns list of output. + Raises CmdFailed -> return code for Popen isn't 0 + Raises CmdTimedOut -> command exceeded timeout without new output + """ + first_line = True + + try: + tfile = tempfile.NamedTemporaryFile(mode='w+b') + preexec_fn = not G_IS_WIN and os.setsid or None + self.proc = subprocess.Popen(self.cmd, stdout=tfile, + stderr=subprocess.STDOUT, + stdin=subprocess.PIPE, shell=True, + preexec_fn=preexec_fn) + thrd = thr.Thread(target=(lambda proc: proc.wait()), args=(self.proc,)) + thrd.start() + + thread_not_started = True + while thread_not_started: + try: + thrd.join(0.1) + thread_not_started = False + except RuntimeError: + pass + + while self.alive: + if G_STOP.is_set(): + raise KeyboardInterrupt + + if first_line or random.random() < G_LOG_PROB: + first_line = False + line = '' if G_IS_WIN else nonblock_read(tfile.name) + if line: + self.callback([line]) + + time_diff = time.time() - os.path.getmtime(tfile.name) + if time_diff > self.timeout: + raise CmdTimedOut(['Timeout!']) + + thrd.join(0.5) + + tfile.seek(0) + result = [line.decode('utf-8', 'replace').rstrip() for line in tfile] + + if self.proc.returncode != 0: + raise CmdFailed([''] + result) + + return result + except: + self.terminate() + raise + + def terminate(self): + """ Terminate process and cleanup. """ + if self.alive: + if G_IS_WIN: + os.kill(self.proc.pid, signal.SIGINT) + else: + os.killpg(self.proc.pid, signal.SIGTERM) + self.clean() + +class Plugin(object): + def __init__(self, name, args, buf_q, lock): + self.name = name + self.args = args + self.buf_q = buf_q + self.lock = lock + self.tag = args.get('tag', 0) + + def manage(self): + try: + if os.path.exists(self.args['dir']): + self.update() + else: + self.install() + with self.lock: + thread_vim_command("let s:update.new['{0}'] = 1".format(self.name)) + except PlugError as exc: + self.write(Action.ERROR, self.name, exc.msg) + except KeyboardInterrupt: + G_STOP.set() + self.write(Action.ERROR, self.name, ['Interrupted!']) + except: + # Any exception except those above print stack trace + msg = 'Trace:\n{0}'.format(traceback.format_exc().rstrip()) + self.write(Action.ERROR, self.name, msg.split('\n')) + raise + + def install(self): + target = self.args['dir'] + if target[-1] == '\\': + target = target[0:-1] + + def clean(target): + def _clean(): + try: + shutil.rmtree(target) + except OSError: + pass + return _clean + + self.write(Action.INSTALL, self.name, ['Installing ...']) + callback = functools.partial(self.write, Action.INSTALL, self.name) + cmd = 'git clone {0} {1} {2} {3} 2>&1'.format( + '' if self.tag else G_CLONE_OPT, G_PROGRESS, self.args['uri'], + esc(target)) + com = Command(cmd, None, G_TIMEOUT, callback, clean(target)) + result = com.execute(G_RETRIES) + self.write(Action.DONE, self.name, result[-1:]) + + def repo_uri(self): + cmd = 'git rev-parse --abbrev-ref HEAD 2>&1 && git config -f .git/config remote.origin.url' + command = Command(cmd, self.args['dir'], G_TIMEOUT,) + result = command.execute(G_RETRIES) + return result[-1] + + def update(self): + actual_uri = self.repo_uri() + expect_uri = self.args['uri'] + regex = re.compile(r'^(?:\w+://)?(?:[^@/]*@)?([^:/]*(?::[0-9]*)?)[:/](.*?)(?:\.git)?/?$') + ma = regex.match(actual_uri) + mb = regex.match(expect_uri) + if ma is None or mb is None or ma.groups() != mb.groups(): + msg = ['', + 'Invalid URI: {0}'.format(actual_uri), + 'Expected {0}'.format(expect_uri), + 'PlugClean required.'] + raise InvalidURI(msg) + + if G_PULL: + self.write(Action.UPDATE, self.name, ['Updating ...']) + callback = functools.partial(self.write, Action.UPDATE, self.name) + fetch_opt = '--depth 99999999' if self.tag and os.path.isfile(os.path.join(self.args['dir'], '.git/shallow')) else '' + cmd = 'git fetch {0} {1} 2>&1'.format(fetch_opt, G_PROGRESS) + com = Command(cmd, self.args['dir'], G_TIMEOUT, callback) + result = com.execute(G_RETRIES) + self.write(Action.DONE, self.name, result[-1:]) + else: + self.write(Action.DONE, self.name, ['Already installed']) + + def write(self, action, name, msg): + self.buf_q.put((action, name, msg)) + +class PlugThread(thr.Thread): + def __init__(self, tname, args): + super(PlugThread, self).__init__() + self.tname = tname + self.args = args + + def run(self): + thr.current_thread().name = self.tname + buf_q, work_q, lock = self.args + + try: + while not G_STOP.is_set(): + name, args = work_q.get_nowait() + plug = Plugin(name, args, buf_q, lock) + plug.manage() + work_q.task_done() + except queue.Empty: + pass + +class RefreshThread(thr.Thread): + def __init__(self, lock): + super(RefreshThread, self).__init__() + self.lock = lock + self.running = True + + def run(self): + while self.running: + with self.lock: + thread_vim_command('noautocmd normal! a') + time.sleep(0.33) + + def stop(self): + self.running = False + +if G_NVIM: + def thread_vim_command(cmd): + vim.session.threadsafe_call(lambda: vim.command(cmd)) +else: + def thread_vim_command(cmd): + vim.command(cmd) + +def esc(name): + return '"' + name.replace('"', '\"') + '"' + +def nonblock_read(fname): + """ Read a file with nonblock flag. Return the last line. """ + fread = os.open(fname, os.O_RDONLY | os.O_NONBLOCK) + buf = os.read(fread, 100000).decode('utf-8', 'replace') + os.close(fread) + + line = buf.rstrip('\r\n') + left = max(line.rfind('\r'), line.rfind('\n')) + if left != -1: + left += 1 + line = line[left:] + + return line + +def main(): + thr.current_thread().name = 'main' + nthreads = int(vim.eval('s:update.threads')) + plugs = vim.eval('s:update.todo') + mac_gui = vim.eval('s:mac_gui') == '1' + + lock = thr.Lock() + buf = Buffer(lock, len(plugs), G_PULL) + buf_q, work_q = queue.Queue(), queue.Queue() + for work in plugs.items(): + work_q.put(work) + + start_cnt = thr.active_count() + for num in range(nthreads): + tname = 'PlugT-{0:02}'.format(num) + thread = PlugThread(tname, (buf_q, work_q, lock)) + thread.start() + if mac_gui: + rthread = RefreshThread(lock) + rthread.start() + + while not buf_q.empty() or thr.active_count() != start_cnt: + try: + action, name, msg = buf_q.get(True, 0.25) + buf.write(action, name, ['OK'] if not msg else msg) + buf_q.task_done() + except queue.Empty: + pass + except KeyboardInterrupt: + G_STOP.set() + + if mac_gui: + rthread.stop() + rthread.join() + +main() +EOF +endfunction + +function! s:update_ruby() + ruby << EOF + module PlugStream + SEP = ["\r", "\n", nil] + def get_line + buffer = '' + loop do + char = readchar rescue return + if SEP.include? char.chr + buffer << $/ + break + else + buffer << char + end + end + buffer + end + end unless defined?(PlugStream) + + def esc arg + %["#{arg.gsub('"', '\"')}"] + end + + def killall pid + pids = [pid] + if /mswin|mingw|bccwin/ =~ RUBY_PLATFORM + pids.each { |pid| Process.kill 'INT', pid.to_i rescue nil } + else + unless `which pgrep 2> /dev/null`.empty? + children = pids + until children.empty? + children = children.map { |pid| + `pgrep -P #{pid}`.lines.map { |l| l.chomp } + }.flatten + pids += children + end + end + pids.each { |pid| Process.kill 'TERM', pid.to_i rescue nil } + end + end + + def compare_git_uri a, b + regex = %r{^(?:\w+://)?(?:[^@/]*@)?([^:/]*(?::[0-9]*)?)[:/](.*?)(?:\.git)?/?$} + regex.match(a).to_a.drop(1) == regex.match(b).to_a.drop(1) + end + + require 'thread' + require 'fileutils' + require 'timeout' + running = true + iswin = VIM::evaluate('s:is_win').to_i == 1 + pull = VIM::evaluate('s:update.pull').to_i == 1 + base = VIM::evaluate('g:plug_home') + all = VIM::evaluate('s:update.todo') + limit = VIM::evaluate('get(g:, "plug_timeout", 60)') + tries = VIM::evaluate('get(g:, "plug_retries", 2)') + 1 + nthr = VIM::evaluate('s:update.threads').to_i + maxy = VIM::evaluate('winheight(".")').to_i + vim7 = VIM::evaluate('v:version').to_i <= 703 && RUBY_PLATFORM =~ /darwin/ + cd = iswin ? 'cd /d' : 'cd' + tot = VIM::evaluate('len(s:update.todo)') || 0 + bar = '' + skip = 'Already installed' + mtx = Mutex.new + take1 = proc { mtx.synchronize { running && all.shift } } + logh = proc { + cnt = bar.length + $curbuf[1] = "#{pull ? 'Updating' : 'Installing'} plugins (#{cnt}/#{tot})" + $curbuf[2] = '[' + bar.ljust(tot) + ']' + VIM::command('normal! 2G') + VIM::command('redraw') + } + where = proc { |name| (1..($curbuf.length)).find { |l| $curbuf[l] =~ /^[-+x*] #{name}:/ } } + log = proc { |name, result, type| + mtx.synchronize do + ing = ![true, false].include?(type) + bar += type ? '=' : 'x' unless ing + b = case type + when :install then '+' when :update then '*' + when true, nil then '-' else + VIM::command("call add(s:update.errors, '#{name}')") + 'x' + end + result = + if type || type.nil? + ["#{b} #{name}: #{result.lines.to_a.last || 'OK'}"] + elsif result =~ /^Interrupted|^Timeout/ + ["#{b} #{name}: #{result}"] + else + ["#{b} #{name}"] + result.lines.map { |l| " " << l } + end + if lnum = where.call(name) + $curbuf.delete lnum + lnum = 4 if ing && lnum > maxy + end + result.each_with_index do |line, offset| + $curbuf.append((lnum || 4) - 1 + offset, line.gsub(/\e\[./, '').chomp) + end + logh.call + end + } + bt = proc { |cmd, name, type, cleanup| + tried = timeout = 0 + begin + tried += 1 + timeout += limit + fd = nil + data = '' + if iswin + Timeout::timeout(timeout) do + tmp = VIM::evaluate('tempname()') + system("(#{cmd}) > #{tmp}") + data = File.read(tmp).chomp + File.unlink tmp rescue nil + end + else + fd = IO.popen(cmd).extend(PlugStream) + first_line = true + log_prob = 1.0 / nthr + while line = Timeout::timeout(timeout) { fd.get_line } + data << line + log.call name, line.chomp, type if name && (first_line || rand < log_prob) + first_line = false + end + fd.close + end + [$? == 0, data.chomp] + rescue Timeout::Error, Interrupt => e + if fd && !fd.closed? + killall fd.pid + fd.close + end + cleanup.call if cleanup + if e.is_a?(Timeout::Error) && tried < tries + 3.downto(1) do |countdown| + s = countdown > 1 ? 's' : '' + log.call name, "Timeout. Will retry in #{countdown} second#{s} ...", type + sleep 1 + end + log.call name, 'Retrying ...', type + retry + end + [false, e.is_a?(Interrupt) ? "Interrupted!" : "Timeout!"] + end + } + main = Thread.current + threads = [] + watcher = Thread.new { + if vim7 + while VIM::evaluate('getchar(1)') + sleep 0.1 + end + else + require 'io/console' # >= Ruby 1.9 + nil until IO.console.getch == 3.chr + end + mtx.synchronize do + running = false + threads.each { |t| t.raise Interrupt } unless vim7 + end + threads.each { |t| t.join rescue nil } + main.kill + } + refresh = Thread.new { + while true + mtx.synchronize do + break unless running + VIM::command('noautocmd normal! a') + end + sleep 0.2 + end + } if VIM::evaluate('s:mac_gui') == 1 + + clone_opt = VIM::evaluate('s:clone_opt').join(' ') + progress = VIM::evaluate('s:progress_opt(1)') + nthr.times do + mtx.synchronize do + threads << Thread.new { + while pair = take1.call + name = pair.first + dir, uri, tag = pair.last.values_at *%w[dir uri tag] + exists = File.directory? dir + ok, result = + if exists + chdir = "#{cd} #{iswin ? dir : esc(dir)}" + ret, data = bt.call "#{chdir} && git rev-parse --abbrev-ref HEAD 2>&1 && git config -f .git/config remote.origin.url", nil, nil, nil + current_uri = data.lines.to_a.last + if !ret + if data =~ /^Interrupted|^Timeout/ + [false, data] + else + [false, [data.chomp, "PlugClean required."].join($/)] + end + elsif !compare_git_uri(current_uri, uri) + [false, ["Invalid URI: #{current_uri}", + "Expected: #{uri}", + "PlugClean required."].join($/)] + else + if pull + log.call name, 'Updating ...', :update + fetch_opt = (tag && File.exist?(File.join(dir, '.git/shallow'))) ? '--depth 99999999' : '' + bt.call "#{chdir} && git fetch #{fetch_opt} #{progress} 2>&1", name, :update, nil + else + [true, skip] + end + end + else + d = esc dir.sub(%r{[\\/]+$}, '') + log.call name, 'Installing ...', :install + bt.call "git clone #{clone_opt unless tag} #{progress} #{uri} #{d} 2>&1", name, :install, proc { + FileUtils.rm_rf dir + } + end + mtx.synchronize { VIM::command("let s:update.new['#{name}'] = 1") } if !exists && ok + log.call name, result, ok + end + } if running + end + end + threads.each { |t| t.join rescue nil } + logh.call + refresh.kill if refresh + watcher.kill +EOF +endfunction + +function! s:shellesc_cmd(arg, script) + let escaped = substitute('"'.a:arg.'"', '[&|<>()@^!"]', '^&', 'g') + return substitute(escaped, '%', (a:script ? '%' : '^') . '&', 'g') +endfunction + +function! s:shellesc_ps1(arg) + return "'".substitute(escape(a:arg, '\"'), "'", "''", 'g')."'" +endfunction + +function! s:shellesc_sh(arg) + return "'".substitute(a:arg, "'", "'\\\\''", 'g')."'" +endfunction + +" Escape the shell argument based on the shell. +" Vim and Neovim's shellescape() are insufficient. +" 1. shellslash determines whether to use single/double quotes. +" Double-quote escaping is fragile for cmd.exe. +" 2. It does not work for powershell. +" 3. It does not work for *sh shells if the command is executed +" via cmd.exe (ie. cmd.exe /c sh -c command command_args) +" 4. It does not support batchfile syntax. +" +" Accepts an optional dictionary with the following keys: +" - shell: same as Vim/Neovim 'shell' option. +" If unset, fallback to 'cmd.exe' on Windows or 'sh'. +" - script: If truthy and shell is cmd.exe, escape for batchfile syntax. +function! plug#shellescape(arg, ...) + if a:arg =~# '^[A-Za-z0-9_/:.-]\+$' + return a:arg + endif + let opts = a:0 > 0 && type(a:1) == s:TYPE.dict ? a:1 : {} + let shell = get(opts, 'shell', s:is_win ? 'cmd.exe' : 'sh') + let script = get(opts, 'script', 1) + if shell =~# 'cmd\(\.exe\)\?$' + return s:shellesc_cmd(a:arg, script) + elseif s:is_powershell(shell) + return s:shellesc_ps1(a:arg) + endif + return s:shellesc_sh(a:arg) +endfunction + +function! s:glob_dir(path) + return map(filter(s:glob(a:path, '**'), 'isdirectory(v:val)'), 's:dirpath(v:val)') +endfunction + +function! s:progress_bar(line, bar, total) + call setline(a:line, '[' . s:lpad(a:bar, a:total) . ']') +endfunction + +function! s:compare_git_uri(a, b) + " See `git help clone' + " https:// [user@] github.com[:port] / junegunn/vim-plug [.git] + " [git@] github.com[:port] : junegunn/vim-plug [.git] + " file:// / junegunn/vim-plug [/] + " / junegunn/vim-plug [/] + let pat = '^\%(\w\+://\)\='.'\%([^@/]*@\)\='.'\([^:/]*\%(:[0-9]*\)\=\)'.'[:/]'.'\(.\{-}\)'.'\%(\.git\)\=/\?$' + let ma = matchlist(a:a, pat) + let mb = matchlist(a:b, pat) + return ma[1:2] ==# mb[1:2] +endfunction + +function! s:format_message(bullet, name, message) + if a:bullet != 'x' + return [printf('%s %s: %s', a:bullet, a:name, s:lastline(a:message))] + else + let lines = map(s:lines(a:message), '" ".v:val') + return extend([printf('x %s:', a:name)], lines) + endif +endfunction + +function! s:with_cd(cmd, dir, ...) + let script = a:0 > 0 ? a:1 : 1 + let pwsh = s:is_powershell(&shell) + let cd = s:is_win && !pwsh ? 'cd /d' : 'cd' + let sep = pwsh ? ';' : '&&' + return printf('%s %s %s %s', cd, plug#shellescape(a:dir, {'script': script, 'shell': &shell}), sep, a:cmd) +endfunction + +function! s:system(cmd, ...) + let batchfile = '' + try + let [sh, shellcmdflag, shrd] = s:chsh(1) + if type(a:cmd) == s:TYPE.list + " Neovim's system() supports list argument to bypass the shell + " but it cannot set the working directory for the command. + " Assume that the command does not rely on the shell. + if has('nvim') && a:0 == 0 + return system(a:cmd) + endif + let cmd = join(map(copy(a:cmd), 'plug#shellescape(v:val, {"shell": &shell, "script": 0})')) + if s:is_powershell(&shell) + let cmd = '& ' . cmd + endif + else + let cmd = a:cmd + endif + if a:0 > 0 + let cmd = s:with_cd(cmd, a:1, type(a:cmd) != s:TYPE.list) + endif + if s:is_win && type(a:cmd) != s:TYPE.list + let [batchfile, cmd] = s:batchfile(cmd) + endif + return system(cmd) + finally + let [&shell, &shellcmdflag, &shellredir] = [sh, shellcmdflag, shrd] + if s:is_win && filereadable(batchfile) + call delete(batchfile) + endif + endtry +endfunction + +function! s:system_chomp(...) + let ret = call('s:system', a:000) + return v:shell_error ? '' : substitute(ret, '\n$', '', '') +endfunction + +function! s:git_validate(spec, check_branch) + let err = '' + if isdirectory(a:spec.dir) + let result = [s:git_local_branch(a:spec.dir), s:git_origin_url(a:spec.dir)] + let remote = result[-1] + if empty(remote) + let err = join([remote, 'PlugClean required.'], "\n") + elseif !s:compare_git_uri(remote, a:spec.uri) + let err = join(['Invalid URI: '.remote, + \ 'Expected: '.a:spec.uri, + \ 'PlugClean required.'], "\n") + elseif !a:check_branch + return ['', 0] + elseif has_key(a:spec, 'commit') + let sha = s:git_revision(a:spec.dir) + if empty(sha) + let err = join(add(result, 'PlugClean required.'), "\n") + elseif !s:hash_match(sha, a:spec.commit) + let err = join([printf('Invalid HEAD (expected: %s, actual: %s)', + \ a:spec.commit[:6], sha[:6]), + \ 'PlugUpdate required.'], "\n") + endif + elseif has_key(a:spec, 'tag') + let tag = s:system_chomp('git describe --exact-match --tags HEAD 2>&1', a:spec.dir) + if a:spec.tag !=# tag && a:spec.tag !~ '\*' + let err = printf('Invalid tag: %s (expected: %s). Try PlugUpdate.', + \ (empty(tag) ? 'N/A' : tag), a:spec.tag) + endif + elseif a:check_branch + let current_branch = result[0] + let origin_branch = s:git_origin_branch(a:spec) + if origin_branch !=# current_branch + let err = printf('Invalid branch: %s (expected: %s). Try PlugUpdate.', + \ current_branch, origin_branch) + endif + if empty(err) + let ahead_behind = split(s:lastline(s:system([ + \ 'git', 'rev-list', '--count', '--left-right', + \ printf('HEAD...origin/%s', origin_branch) + \ ], a:spec.dir)), '\t') + if v:shell_error || len(ahead_behind) != 2 + let err = "Failed to compare with the origin. The default branch might have changed.\nPlugClean required." + else + let [ahead, behind] = ahead_behind + if ahead && behind + " Only mention PlugClean if diverged, otherwise it's likely to be + " pushable (and probably not that messed up). + let err = printf( + \ "Diverged from origin/%s (%d commit(s) ahead and %d commit(s) behind!\n" + \ .'Backup local changes and run PlugClean and PlugUpdate to reinstall it.', origin_branch, ahead, behind) + elseif ahead + let err = printf("Ahead of origin/%s by %d commit(s).\n" + \ .'Cannot update until local changes are pushed.', + \ origin_branch, ahead) + endif + endif + endif + endif + else + let err = 'Not found' + endif + return [err, err =~# 'PlugClean'] +endfunction + +function! s:rm_rf(dir) + if isdirectory(a:dir) + return s:system(s:is_win + \ ? 'rmdir /S /Q '.plug#shellescape(a:dir) + \ : ['rm', '-rf', a:dir]) + endif +endfunction + +function! s:clean(force) + call s:prepare() + call append(0, 'Searching for invalid plugins in '.g:plug_home) + call append(1, '') + + " List of valid directories + let dirs = [] + let errs = {} + let [cnt, total] = [0, len(g:plugs)] + for [name, spec] in items(g:plugs) + if !s:is_managed(name) || get(spec, 'frozen', 0) + call add(dirs, spec.dir) + else + let [err, clean] = s:git_validate(spec, 1) + if clean + let errs[spec.dir] = s:lines(err)[0] + else + call add(dirs, spec.dir) + endif + endif + let cnt += 1 + call s:progress_bar(2, repeat('=', cnt), total) + normal! 2G + redraw + endfor + + let allowed = {} + for dir in dirs + let allowed[s:dirpath(s:plug_fnamemodify(dir, ':h:h'))] = 1 + let allowed[dir] = 1 + for child in s:glob_dir(dir) + let allowed[child] = 1 + endfor + endfor + + let todo = [] + let found = sort(s:glob_dir(g:plug_home)) + while !empty(found) + let f = remove(found, 0) + if !has_key(allowed, f) && isdirectory(f) + call add(todo, f) + call append(line('$'), '- ' . f) + if has_key(errs, f) + call append(line('$'), ' ' . errs[f]) + endif + let found = filter(found, 'stridx(v:val, f) != 0') + end + endwhile + + 4 + redraw + if empty(todo) + call append(line('$'), 'Already clean.') + else + let s:clean_count = 0 + call append(3, ['Directories to delete:', '']) + redraw! + if a:force || s:ask_no_interrupt('Delete all directories?') + call s:delete([6, line('$')], 1) + else + call setline(4, 'Cancelled.') + nnoremap d :set opfunc=delete_opg@ + nmap dd d_ + xnoremap d :call delete_op(visualmode(), 1) + echo 'Delete the lines (d{motion}) to delete the corresponding directories' + endif + endif + 4 + setlocal nomodifiable +endfunction + +function! s:delete_op(type, ...) + call s:delete(a:0 ? [line("'<"), line("'>")] : [line("'["), line("']")], 0) +endfunction + +function! s:delete(range, force) + let [l1, l2] = a:range + let force = a:force + let err_count = 0 + while l1 <= l2 + let line = getline(l1) + if line =~ '^- ' && isdirectory(line[2:]) + execute l1 + redraw! + let answer = force ? 1 : s:ask('Delete '.line[2:].'?', 1) + let force = force || answer > 1 + if answer + let err = s:rm_rf(line[2:]) + setlocal modifiable + if empty(err) + call setline(l1, '~'.line[1:]) + let s:clean_count += 1 + else + delete _ + call append(l1 - 1, s:format_message('x', line[1:], err)) + let l2 += len(s:lines(err)) + let err_count += 1 + endif + let msg = printf('Removed %d directories.', s:clean_count) + if err_count > 0 + let msg .= printf(' Failed to remove %d directories.', err_count) + endif + call setline(4, msg) + setlocal nomodifiable + endif + endif + let l1 += 1 + endwhile +endfunction + +function! s:upgrade() + echo 'Downloading the latest version of vim-plug' + redraw + let tmp = s:plug_tempname() + let new = tmp . '/plug.vim' + + try + let out = s:system(['git', 'clone', '--depth', '1', s:plug_src, tmp]) + if v:shell_error + return s:err('Error upgrading vim-plug: '. out) + endif + + if readfile(s:me) ==# readfile(new) + echo 'vim-plug is already up-to-date' + return 0 + else + call rename(s:me, s:me . '.old') + call rename(new, s:me) + unlet g:loaded_plug + echo 'vim-plug has been upgraded' + return 1 + endif + finally + silent! call s:rm_rf(tmp) + endtry +endfunction + +function! s:upgrade_specs() + for spec in values(g:plugs) + let spec.frozen = get(spec, 'frozen', 0) + endfor +endfunction + +function! s:status() + call s:prepare() + call append(0, 'Checking plugins') + call append(1, '') + + let ecnt = 0 + let unloaded = 0 + let [cnt, total] = [0, len(g:plugs)] + for [name, spec] in items(g:plugs) + let is_dir = isdirectory(spec.dir) + if has_key(spec, 'uri') + if is_dir + let [err, _] = s:git_validate(spec, 1) + let [valid, msg] = [empty(err), empty(err) ? 'OK' : err] + else + let [valid, msg] = [0, 'Not found. Try PlugInstall.'] + endif + else + if is_dir + let [valid, msg] = [1, 'OK'] + else + let [valid, msg] = [0, 'Not found.'] + endif + endif + let cnt += 1 + let ecnt += !valid + " `s:loaded` entry can be missing if PlugUpgraded + if is_dir && get(s:loaded, name, -1) == 0 + let unloaded = 1 + let msg .= ' (not loaded)' + endif + call s:progress_bar(2, repeat('=', cnt), total) + call append(3, s:format_message(valid ? '-' : 'x', name, msg)) + normal! 2G + redraw + endfor + call setline(1, 'Finished. '.ecnt.' error(s).') + normal! gg + setlocal nomodifiable + if unloaded + echo "Press 'L' on each line to load plugin, or 'U' to update" + nnoremap L :call status_load(line('.')) + xnoremap L :call status_load(line('.')) + end +endfunction + +function! s:extract_name(str, prefix, suffix) + return matchstr(a:str, '^'.a:prefix.' \zs[^:]\+\ze:.*'.a:suffix.'$') +endfunction + +function! s:status_load(lnum) + let line = getline(a:lnum) + let name = s:extract_name(line, '-', '(not loaded)') + if !empty(name) + call plug#load(name) + setlocal modifiable + call setline(a:lnum, substitute(line, ' (not loaded)$', '', '')) + setlocal nomodifiable + endif +endfunction + +function! s:status_update() range + let lines = getline(a:firstline, a:lastline) + let names = filter(map(lines, 's:extract_name(v:val, "[x-]", "")'), '!empty(v:val)') + if !empty(names) + echo + execute 'PlugUpdate' join(names) + endif +endfunction + +function! s:is_preview_window_open() + silent! wincmd P + if &previewwindow + wincmd p + return 1 + endif +endfunction + +function! s:find_name(lnum) + for lnum in reverse(range(1, a:lnum)) + let line = getline(lnum) + if empty(line) + return '' + endif + let name = s:extract_name(line, '-', '') + if !empty(name) + return name + endif + endfor + return '' +endfunction + +function! s:preview_commit() + if b:plug_preview < 0 + let b:plug_preview = !s:is_preview_window_open() + endif + + let sha = matchstr(getline('.'), '^ \X*\zs[0-9a-f]\{7,9}') + if empty(sha) + let name = matchstr(getline('.'), '^- \zs[^:]*\ze:$') + if empty(name) + return + endif + let title = 'HEAD@{1}..' + let command = 'git diff --no-color HEAD@{1}' + else + let title = sha + let command = 'git show --no-color --pretty=medium '.sha + let name = s:find_name(line('.')) + endif + + if empty(name) || !has_key(g:plugs, name) || !isdirectory(g:plugs[name].dir) + return + endif + + if !s:is_preview_window_open() + execute get(g:, 'plug_pwindow', 'vertical rightbelow new') + execute 'e' title + else + execute 'pedit' title + wincmd P + endif + setlocal previewwindow filetype=git buftype=nofile bufhidden=wipe nobuflisted modifiable + let batchfile = '' + try + let [sh, shellcmdflag, shrd] = s:chsh(1) + let cmd = 'cd '.plug#shellescape(g:plugs[name].dir).' && '.command + if s:is_win + let [batchfile, cmd] = s:batchfile(cmd) + endif + execute 'silent %!' cmd + finally + let [&shell, &shellcmdflag, &shellredir] = [sh, shellcmdflag, shrd] + if s:is_win && filereadable(batchfile) + call delete(batchfile) + endif + endtry + setlocal nomodifiable + nnoremap q :q + wincmd p +endfunction + +function! s:section(flags) + call search('\(^[x-] \)\@<=[^:]\+:', a:flags) +endfunction + +function! s:format_git_log(line) + let indent = ' ' + let tokens = split(a:line, nr2char(1)) + if len(tokens) != 5 + return indent.substitute(a:line, '\s*$', '', '') + endif + let [graph, sha, refs, subject, date] = tokens + let tag = matchstr(refs, 'tag: [^,)]\+') + let tag = empty(tag) ? ' ' : ' ('.tag.') ' + return printf('%s%s%s%s%s (%s)', indent, graph, sha, tag, subject, date) +endfunction + +function! s:append_ul(lnum, text) + call append(a:lnum, ['', a:text, repeat('-', len(a:text))]) +endfunction + +function! s:diff() + call s:prepare() + call append(0, ['Collecting changes ...', '']) + let cnts = [0, 0] + let bar = '' + let total = filter(copy(g:plugs), 's:is_managed(v:key) && isdirectory(v:val.dir)') + call s:progress_bar(2, bar, len(total)) + for origin in [1, 0] + let plugs = reverse(sort(items(filter(copy(total), (origin ? '' : '!').'(has_key(v:val, "commit") || has_key(v:val, "tag"))')))) + if empty(plugs) + continue + endif + call s:append_ul(2, origin ? 'Pending updates:' : 'Last update:') + for [k, v] in plugs + let branch = s:git_origin_branch(v) + if len(branch) + let range = origin ? '..origin/'.branch : 'HEAD@{1}..' + let cmd = ['git', 'log', '--graph', '--color=never'] + if s:git_version_requirement(2, 10, 0) + call add(cmd, '--no-show-signature') + endif + call extend(cmd, ['--pretty=format:%x01%h%x01%d%x01%s%x01%cr', range]) + if has_key(v, 'rtp') + call extend(cmd, ['--', v.rtp]) + endif + let diff = s:system_chomp(cmd, v.dir) + if !empty(diff) + let ref = has_key(v, 'tag') ? (' (tag: '.v.tag.')') : has_key(v, 'commit') ? (' '.v.commit) : '' + call append(5, extend(['', '- '.k.':'.ref], map(s:lines(diff), 's:format_git_log(v:val)'))) + let cnts[origin] += 1 + endif + endif + let bar .= '=' + call s:progress_bar(2, bar, len(total)) + normal! 2G + redraw + endfor + if !cnts[origin] + call append(5, ['', 'N/A']) + endif + endfor + call setline(1, printf('%d plugin(s) updated.', cnts[0]) + \ . (cnts[1] ? printf(' %d plugin(s) have pending updates.', cnts[1]) : '')) + + if cnts[0] || cnts[1] + nnoremap (plug-preview) :silent! call preview_commit() + if empty(maparg("\", 'n')) + nmap (plug-preview) + endif + if empty(maparg('o', 'n')) + nmap o (plug-preview) + endif + endif + if cnts[0] + nnoremap X :call revert() + echo "Press 'X' on each block to revert the update" + endif + normal! gg + setlocal nomodifiable +endfunction + +function! s:revert() + if search('^Pending updates', 'bnW') + return + endif + + let name = s:find_name(line('.')) + if empty(name) || !has_key(g:plugs, name) || + \ input(printf('Revert the update of %s? (y/N) ', name)) !~? '^y' + return + endif + + call s:system('git reset --hard HEAD@{1} && git checkout '.plug#shellescape(g:plugs[name].branch).' --', g:plugs[name].dir) + setlocal modifiable + normal! "_dap + setlocal nomodifiable + echo 'Reverted' +endfunction + +function! s:snapshot(force, ...) abort + call s:prepare() + setf vim + call append(0, ['" Generated by vim-plug', + \ '" '.strftime("%c"), + \ '" :source this file in vim to restore the snapshot', + \ '" or execute: vim -S snapshot.vim', + \ '', '', 'PlugUpdate!']) + 1 + let anchor = line('$') - 3 + let names = sort(keys(filter(copy(g:plugs), + \'has_key(v:val, "uri") && isdirectory(v:val.dir)'))) + for name in reverse(names) + let sha = has_key(g:plugs[name], 'commit') ? g:plugs[name].commit : s:git_revision(g:plugs[name].dir) + if !empty(sha) + call append(anchor, printf("silent! let g:plugs['%s'].commit = '%s'", name, sha)) + redraw + endif + endfor + + if a:0 > 0 + let fn = s:plug_expand(a:1) + if filereadable(fn) && !(a:force || s:ask(a:1.' already exists. Overwrite?')) + return + endif + call writefile(getline(1, '$'), fn) + echo 'Saved as '.a:1 + silent execute 'e' s:esc(fn) + setf vim + endif +endfunction + +function! s:split_rtp() + return split(&rtp, '\\\@`-mappings | +"| `for` | On-demand loading: File types | +"| `frozen` | Do not update unless explicitly specified | +" +" More information: https://github.com/junegunn/vim-plug +" +" +" Copyright (c) 2017 Junegunn Choi +" +" MIT License +" +" 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: +" +" The above copyright notice and this permission notice shall be +" included in all copies or substantial portions of the Software. +" +" 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. + +if exists('g:loaded_plug') + finish +endif +let g:loaded_plug = 1 + +let s:cpo_save = &cpo +set cpo&vim + +let s:plug_src = 'https://github.com/junegunn/vim-plug.git' +let s:plug_tab = get(s:, 'plug_tab', -1) +let s:plug_buf = get(s:, 'plug_buf', -1) +let s:mac_gui = has('gui_macvim') && has('gui_running') +let s:is_win = has('win32') +let s:nvim = has('nvim-0.2') || (has('nvim') && exists('*jobwait') && !s:is_win) +let s:vim8 = has('patch-8.0.0039') && exists('*job_start') +if s:is_win && &shellslash + set noshellslash + let s:me = resolve(expand(':p')) + set shellslash +else + let s:me = resolve(expand(':p')) +endif +let s:base_spec = { 'branch': '', 'frozen': 0 } +let s:TYPE = { +\ 'string': type(''), +\ 'list': type([]), +\ 'dict': type({}), +\ 'funcref': type(function('call')) +\ } +let s:loaded = get(s:, 'loaded', {}) +let s:triggers = get(s:, 'triggers', {}) + +function! s:is_powershell(shell) + return a:shell =~# 'powershell\(\.exe\)\?$' || a:shell =~# 'pwsh\(\.exe\)\?$' +endfunction + +function! s:isabsolute(dir) abort + return a:dir =~# '^/' || (has('win32') && a:dir =~? '^\%(\\\|[A-Z]:\)') +endfunction + +function! s:git_dir(dir) abort + let gitdir = s:trim(a:dir) . '/.git' + if isdirectory(gitdir) + return gitdir + endif + if !filereadable(gitdir) + return '' + endif + let gitdir = matchstr(get(readfile(gitdir), 0, ''), '^gitdir: \zs.*') + if len(gitdir) && !s:isabsolute(gitdir) + let gitdir = a:dir . '/' . gitdir + endif + return isdirectory(gitdir) ? gitdir : '' +endfunction + +function! s:git_origin_url(dir) abort + let gitdir = s:git_dir(a:dir) + let config = gitdir . '/config' + if empty(gitdir) || !filereadable(config) + return '' + endif + return matchstr(join(readfile(config)), '\[remote "origin"\].\{-}url\s*=\s*\zs\S*\ze') +endfunction + +function! s:git_revision(dir) abort + let gitdir = s:git_dir(a:dir) + let head = gitdir . '/HEAD' + if empty(gitdir) || !filereadable(head) + return '' + endif + + let line = get(readfile(head), 0, '') + let ref = matchstr(line, '^ref: \zs.*') + if empty(ref) + return line + endif + + if filereadable(gitdir . '/' . ref) + return get(readfile(gitdir . '/' . ref), 0, '') + endif + + if filereadable(gitdir . '/packed-refs') + for line in readfile(gitdir . '/packed-refs') + if line =~# ' ' . ref + return matchstr(line, '^[0-9a-f]*') + endif + endfor + endif + + return '' +endfunction + +function! s:git_local_branch(dir) abort + let gitdir = s:git_dir(a:dir) + let head = gitdir . '/HEAD' + if empty(gitdir) || !filereadable(head) + return '' + endif + let branch = matchstr(get(readfile(head), 0, ''), '^ref: refs/heads/\zs.*') + return len(branch) ? branch : 'HEAD' +endfunction + +function! s:git_origin_branch(spec) + if len(a:spec.branch) + return a:spec.branch + endif + + " The file may not be present if this is a local repository + let gitdir = s:git_dir(a:spec.dir) + let origin_head = gitdir.'/refs/remotes/origin/HEAD' + if len(gitdir) && filereadable(origin_head) + return matchstr(get(readfile(origin_head), 0, ''), + \ '^ref: refs/remotes/origin/\zs.*') + endif + + " The command may not return the name of a branch in detached HEAD state + let result = s:lines(s:system('git symbolic-ref --short HEAD', a:spec.dir)) + return v:shell_error ? '' : result[-1] +endfunction + +if s:is_win + function! s:plug_call(fn, ...) + let shellslash = &shellslash + try + set noshellslash + return call(a:fn, a:000) + finally + let &shellslash = shellslash + endtry + endfunction +else + function! s:plug_call(fn, ...) + return call(a:fn, a:000) + endfunction +endif + +function! s:plug_getcwd() + return s:plug_call('getcwd') +endfunction + +function! s:plug_fnamemodify(fname, mods) + return s:plug_call('fnamemodify', a:fname, a:mods) +endfunction + +function! s:plug_expand(fmt) + return s:plug_call('expand', a:fmt, 1) +endfunction + +function! s:plug_tempname() + return s:plug_call('tempname') +endfunction + +function! plug#begin(...) + if a:0 > 0 + let s:plug_home_org = a:1 + let home = s:path(s:plug_fnamemodify(s:plug_expand(a:1), ':p')) + elseif exists('g:plug_home') + let home = s:path(g:plug_home) + elseif has('nvim') + let home = stdpath('data') . '/plugged' + elseif !empty(&rtp) + let home = s:path(split(&rtp, ',')[0]) . '/plugged' + else + return s:err('Unable to determine plug home. Try calling plug#begin() with a path argument.') + endif + if s:plug_fnamemodify(home, ':t') ==# 'plugin' && s:plug_fnamemodify(home, ':h') ==# s:first_rtp + return s:err('Invalid plug home. '.home.' is a standard Vim runtime path and is not allowed.') + endif + + let g:plug_home = home + let g:plugs = {} + let g:plugs_order = [] + let s:triggers = {} + + call s:define_commands() + return 1 +endfunction + +function! s:define_commands() + command! -nargs=+ -bar Plug call plug#() + if !executable('git') + return s:err('`git` executable not found. Most commands will not be available. To suppress this message, prepend `silent!` to `call plug#begin(...)`.') + endif + if has('win32') + \ && &shellslash + \ && (&shell =~# 'cmd\(\.exe\)\?$' || s:is_powershell(&shell)) + return s:err('vim-plug does not support shell, ' . &shell . ', when shellslash is set.') + endif + if !has('nvim') + \ && (has('win32') || has('win32unix')) + \ && !has('multi_byte') + return s:err('Vim needs +multi_byte feature on Windows to run shell commands. Enable +iconv for best results.') + endif + command! -nargs=* -bar -bang -complete=customlist,s:names PlugInstall call s:install(0, []) + command! -nargs=* -bar -bang -complete=customlist,s:names PlugUpdate call s:update(0, []) + command! -nargs=0 -bar -bang PlugClean call s:clean(0) + command! -nargs=0 -bar PlugUpgrade if s:upgrade() | execute 'source' s:esc(s:me) | endif + command! -nargs=0 -bar PlugStatus call s:status() + command! -nargs=0 -bar PlugDiff call s:diff() + command! -nargs=? -bar -bang -complete=file PlugSnapshot call s:snapshot(0, ) +endfunction + +function! s:to_a(v) + return type(a:v) == s:TYPE.list ? a:v : [a:v] +endfunction + +function! s:to_s(v) + return type(a:v) == s:TYPE.string ? a:v : join(a:v, "\n") . "\n" +endfunction + +function! s:glob(from, pattern) + return s:lines(globpath(a:from, a:pattern)) +endfunction + +function! s:source(from, ...) + let found = 0 + for pattern in a:000 + for vim in s:glob(a:from, pattern) + execute 'source' s:esc(vim) + let found = 1 + endfor + endfor + return found +endfunction + +function! s:assoc(dict, key, val) + let a:dict[a:key] = add(get(a:dict, a:key, []), a:val) +endfunction + +function! s:ask(message, ...) + call inputsave() + echohl WarningMsg + let answer = input(a:message.(a:0 ? ' (y/N/a) ' : ' (y/N) ')) + echohl None + call inputrestore() + echo "\r" + return (a:0 && answer =~? '^a') ? 2 : (answer =~? '^y') ? 1 : 0 +endfunction + +function! s:ask_no_interrupt(...) + try + return call('s:ask', a:000) + catch + return 0 + endtry +endfunction + +function! s:lazy(plug, opt) + return has_key(a:plug, a:opt) && + \ (empty(s:to_a(a:plug[a:opt])) || + \ !isdirectory(a:plug.dir) || + \ len(s:glob(s:rtp(a:plug), 'plugin')) || + \ len(s:glob(s:rtp(a:plug), 'after/plugin'))) +endfunction + +function! plug#end() + if !exists('g:plugs') + return s:err('plug#end() called without calling plug#begin() first') + endif + + if exists('#PlugLOD') + augroup PlugLOD + autocmd! + augroup END + augroup! PlugLOD + endif + let lod = { 'ft': {}, 'map': {}, 'cmd': {} } + + if get(g:, 'did_load_filetypes', 0) + filetype off + endif + for name in g:plugs_order + if !has_key(g:plugs, name) + continue + endif + let plug = g:plugs[name] + if get(s:loaded, name, 0) || !s:lazy(plug, 'on') && !s:lazy(plug, 'for') + let s:loaded[name] = 1 + continue + endif + + if has_key(plug, 'on') + let s:triggers[name] = { 'map': [], 'cmd': [] } + for cmd in s:to_a(plug.on) + if cmd =~? '^.\+' + if empty(mapcheck(cmd)) && empty(mapcheck(cmd, 'i')) + call s:assoc(lod.map, cmd, name) + endif + call add(s:triggers[name].map, cmd) + elseif cmd =~# '^[A-Z]' + let cmd = substitute(cmd, '!*$', '', '') + if exists(':'.cmd) != 2 + call s:assoc(lod.cmd, cmd, name) + endif + call add(s:triggers[name].cmd, cmd) + else + call s:err('Invalid `on` option: '.cmd. + \ '. Should start with an uppercase letter or ``.') + endif + endfor + endif + + if has_key(plug, 'for') + let types = s:to_a(plug.for) + if !empty(types) + augroup filetypedetect + call s:source(s:rtp(plug), 'ftdetect/**/*.vim', 'after/ftdetect/**/*.vim') + augroup END + endif + for type in types + call s:assoc(lod.ft, type, name) + endfor + endif + endfor + + for [cmd, names] in items(lod.cmd) + execute printf( + \ 'command! -nargs=* -range -bang -complete=file %s call s:lod_cmd(%s, "", , , , %s)', + \ cmd, string(cmd), string(names)) + endfor + + for [map, names] in items(lod.map) + for [mode, map_prefix, key_prefix] in + \ [['i', '', ''], ['n', '', ''], ['v', '', 'gv'], ['o', '', '']] + execute printf( + \ '%snoremap %s %s:call lod_map(%s, %s, %s, "%s")', + \ mode, map, map_prefix, string(map), string(names), mode != 'i', key_prefix) + endfor + endfor + + for [ft, names] in items(lod.ft) + augroup PlugLOD + execute printf('autocmd FileType %s call lod_ft(%s, %s)', + \ ft, string(ft), string(names)) + augroup END + endfor + + call s:reorg_rtp() + filetype plugin indent on + if has('vim_starting') + if has('syntax') && !exists('g:syntax_on') + syntax enable + end + else + call s:reload_plugins() + endif +endfunction + +function! s:loaded_names() + return filter(copy(g:plugs_order), 'get(s:loaded, v:val, 0)') +endfunction + +function! s:load_plugin(spec) + call s:source(s:rtp(a:spec), 'plugin/**/*.vim', 'after/plugin/**/*.vim') +endfunction + +function! s:reload_plugins() + for name in s:loaded_names() + call s:load_plugin(g:plugs[name]) + endfor +endfunction + +function! s:trim(str) + return substitute(a:str, '[\/]\+$', '', '') +endfunction + +function! s:version_requirement(val, min) + for idx in range(0, len(a:min) - 1) + let v = get(a:val, idx, 0) + if v < a:min[idx] | return 0 + elseif v > a:min[idx] | return 1 + endif + endfor + return 1 +endfunction + +function! s:git_version_requirement(...) + if !exists('s:git_version') + let s:git_version = map(split(split(s:system(['git', '--version']))[2], '\.'), 'str2nr(v:val)') + endif + return s:version_requirement(s:git_version, a:000) +endfunction + +function! s:progress_opt(base) + return a:base && !s:is_win && + \ s:git_version_requirement(1, 7, 1) ? '--progress' : '' +endfunction + +function! s:rtp(spec) + return s:path(a:spec.dir . get(a:spec, 'rtp', '')) +endfunction + +if s:is_win + function! s:path(path) + return s:trim(substitute(a:path, '/', '\', 'g')) + endfunction + + function! s:dirpath(path) + return s:path(a:path) . '\' + endfunction + + function! s:is_local_plug(repo) + return a:repo =~? '^[a-z]:\|^[%~]' + endfunction + + " Copied from fzf + function! s:wrap_cmds(cmds) + let cmds = [ + \ '@echo off', + \ 'setlocal enabledelayedexpansion'] + \ + (type(a:cmds) == type([]) ? a:cmds : [a:cmds]) + \ + ['endlocal'] + if has('iconv') + if !exists('s:codepage') + let s:codepage = libcallnr('kernel32.dll', 'GetACP', 0) + endif + return map(cmds, printf('iconv(v:val."\r", "%s", "cp%d")', &encoding, s:codepage)) + endif + return map(cmds, 'v:val."\r"') + endfunction + + function! s:batchfile(cmd) + let batchfile = s:plug_tempname().'.bat' + call writefile(s:wrap_cmds(a:cmd), batchfile) + let cmd = plug#shellescape(batchfile, {'shell': &shell, 'script': 0}) + if s:is_powershell(&shell) + let cmd = '& ' . cmd + endif + return [batchfile, cmd] + endfunction +else + function! s:path(path) + return s:trim(a:path) + endfunction + + function! s:dirpath(path) + return substitute(a:path, '[/\\]*$', '/', '') + endfunction + + function! s:is_local_plug(repo) + return a:repo[0] =~ '[/$~]' + endfunction +endif + +function! s:err(msg) + echohl ErrorMsg + echom '[vim-plug] '.a:msg + echohl None +endfunction + +function! s:warn(cmd, msg) + echohl WarningMsg + execute a:cmd 'a:msg' + echohl None +endfunction + +function! s:esc(path) + return escape(a:path, ' ') +endfunction + +function! s:escrtp(path) + return escape(a:path, ' ,') +endfunction + +function! s:remove_rtp() + for name in s:loaded_names() + let rtp = s:rtp(g:plugs[name]) + execute 'set rtp-='.s:escrtp(rtp) + let after = globpath(rtp, 'after') + if isdirectory(after) + execute 'set rtp-='.s:escrtp(after) + endif + endfor +endfunction + +function! s:reorg_rtp() + if !empty(s:first_rtp) + execute 'set rtp-='.s:first_rtp + execute 'set rtp-='.s:last_rtp + endif + + " &rtp is modified from outside + if exists('s:prtp') && s:prtp !=# &rtp + call s:remove_rtp() + unlet! s:middle + endif + + let s:middle = get(s:, 'middle', &rtp) + let rtps = map(s:loaded_names(), 's:rtp(g:plugs[v:val])') + let afters = filter(map(copy(rtps), 'globpath(v:val, "after")'), '!empty(v:val)') + let rtp = join(map(rtps, 'escape(v:val, ",")'), ',') + \ . ','.s:middle.',' + \ . join(map(afters, 'escape(v:val, ",")'), ',') + let &rtp = substitute(substitute(rtp, ',,*', ',', 'g'), '^,\|,$', '', 'g') + let s:prtp = &rtp + + if !empty(s:first_rtp) + execute 'set rtp^='.s:first_rtp + execute 'set rtp+='.s:last_rtp + endif +endfunction + +function! s:doautocmd(...) + if exists('#'.join(a:000, '#')) + execute 'doautocmd' ((v:version > 703 || has('patch442')) ? '' : '') join(a:000) + endif +endfunction + +function! s:dobufread(names) + for name in a:names + let path = s:rtp(g:plugs[name]) + for dir in ['ftdetect', 'ftplugin', 'after/ftdetect', 'after/ftplugin'] + if len(finddir(dir, path)) + if exists('#BufRead') + doautocmd BufRead + endif + return + endif + endfor + endfor +endfunction + +function! plug#load(...) + if a:0 == 0 + return s:err('Argument missing: plugin name(s) required') + endif + if !exists('g:plugs') + return s:err('plug#begin was not called') + endif + let names = a:0 == 1 && type(a:1) == s:TYPE.list ? a:1 : a:000 + let unknowns = filter(copy(names), '!has_key(g:plugs, v:val)') + if !empty(unknowns) + let s = len(unknowns) > 1 ? 's' : '' + return s:err(printf('Unknown plugin%s: %s', s, join(unknowns, ', '))) + end + let unloaded = filter(copy(names), '!get(s:loaded, v:val, 0)') + if !empty(unloaded) + for name in unloaded + call s:lod([name], ['ftdetect', 'after/ftdetect', 'plugin', 'after/plugin']) + endfor + call s:dobufread(unloaded) + return 1 + end + return 0 +endfunction + +function! s:remove_triggers(name) + if !has_key(s:triggers, a:name) + return + endif + for cmd in s:triggers[a:name].cmd + execute 'silent! delc' cmd + endfor + for map in s:triggers[a:name].map + execute 'silent! unmap' map + execute 'silent! iunmap' map + endfor + call remove(s:triggers, a:name) +endfunction + +function! s:lod(names, types, ...) + for name in a:names + call s:remove_triggers(name) + let s:loaded[name] = 1 + endfor + call s:reorg_rtp() + + for name in a:names + let rtp = s:rtp(g:plugs[name]) + for dir in a:types + call s:source(rtp, dir.'/**/*.vim') + endfor + if a:0 + if !s:source(rtp, a:1) && !empty(s:glob(rtp, a:2)) + execute 'runtime' a:1 + endif + call s:source(rtp, a:2) + endif + call s:doautocmd('User', name) + endfor +endfunction + +function! s:lod_ft(pat, names) + let syn = 'syntax/'.a:pat.'.vim' + call s:lod(a:names, ['plugin', 'after/plugin'], syn, 'after/'.syn) + execute 'autocmd! PlugLOD FileType' a:pat + call s:doautocmd('filetypeplugin', 'FileType') + call s:doautocmd('filetypeindent', 'FileType') +endfunction + +function! s:lod_cmd(cmd, bang, l1, l2, args, names) + call s:lod(a:names, ['ftdetect', 'after/ftdetect', 'plugin', 'after/plugin']) + call s:dobufread(a:names) + execute printf('%s%s%s %s', (a:l1 == a:l2 ? '' : (a:l1.','.a:l2)), a:cmd, a:bang, a:args) +endfunction + +function! s:lod_map(map, names, with_prefix, prefix) + call s:lod(a:names, ['ftdetect', 'after/ftdetect', 'plugin', 'after/plugin']) + call s:dobufread(a:names) + let extra = '' + while 1 + let c = getchar(0) + if c == 0 + break + endif + let extra .= nr2char(c) + endwhile + + if a:with_prefix + let prefix = v:count ? v:count : '' + let prefix .= '"'.v:register.a:prefix + if mode(1) == 'no' + if v:operator == 'c' + let prefix = "\" . prefix + endif + let prefix .= v:operator + endif + call feedkeys(prefix, 'n') + endif + call feedkeys(substitute(a:map, '^', "\", '') . extra) +endfunction + +function! plug#(repo, ...) + if a:0 > 1 + return s:err('Invalid number of arguments (1..2)') + endif + + try + let repo = s:trim(a:repo) + let opts = a:0 == 1 ? s:parse_options(a:1) : s:base_spec + let name = get(opts, 'as', s:plug_fnamemodify(repo, ':t:s?\.git$??')) + let spec = extend(s:infer_properties(name, repo), opts) + if !has_key(g:plugs, name) + call add(g:plugs_order, name) + endif + let g:plugs[name] = spec + let s:loaded[name] = get(s:loaded, name, 0) + catch + return s:err(repo . ' ' . v:exception) + endtry +endfunction + +function! s:parse_options(arg) + let opts = copy(s:base_spec) + let type = type(a:arg) + let opt_errfmt = 'Invalid argument for "%s" option of :Plug (expected: %s)' + if type == s:TYPE.string + if empty(a:arg) + throw printf(opt_errfmt, 'tag', 'string') + endif + let opts.tag = a:arg + elseif type == s:TYPE.dict + for opt in ['branch', 'tag', 'commit', 'rtp', 'dir', 'as'] + if has_key(a:arg, opt) + \ && (type(a:arg[opt]) != s:TYPE.string || empty(a:arg[opt])) + throw printf(opt_errfmt, opt, 'string') + endif + endfor + for opt in ['on', 'for'] + if has_key(a:arg, opt) + \ && type(a:arg[opt]) != s:TYPE.list + \ && (type(a:arg[opt]) != s:TYPE.string || empty(a:arg[opt])) + throw printf(opt_errfmt, opt, 'string or list') + endif + endfor + if has_key(a:arg, 'do') + \ && type(a:arg.do) != s:TYPE.funcref + \ && (type(a:arg.do) != s:TYPE.string || empty(a:arg.do)) + throw printf(opt_errfmt, 'do', 'string or funcref') + endif + call extend(opts, a:arg) + if has_key(opts, 'dir') + let opts.dir = s:dirpath(s:plug_expand(opts.dir)) + endif + else + throw 'Invalid argument type (expected: string or dictionary)' + endif + return opts +endfunction + +function! s:infer_properties(name, repo) + let repo = a:repo + if s:is_local_plug(repo) + return { 'dir': s:dirpath(s:plug_expand(repo)) } + else + if repo =~ ':' + let uri = repo + else + if repo !~ '/' + throw printf('Invalid argument: %s (implicit `vim-scripts'' expansion is deprecated)', repo) + endif + let fmt = get(g:, 'plug_url_format', 'https://git::@github.com/%s.git') + let uri = printf(fmt, repo) + endif + return { 'dir': s:dirpath(g:plug_home.'/'.a:name), 'uri': uri } + endif +endfunction + +function! s:install(force, names) + call s:update_impl(0, a:force, a:names) +endfunction + +function! s:update(force, names) + call s:update_impl(1, a:force, a:names) +endfunction + +function! plug#helptags() + if !exists('g:plugs') + return s:err('plug#begin was not called') + endif + for spec in values(g:plugs) + let docd = join([s:rtp(spec), 'doc'], '/') + if isdirectory(docd) + silent! execute 'helptags' s:esc(docd) + endif + endfor + return 1 +endfunction + +function! s:syntax() + syntax clear + syntax region plug1 start=/\%1l/ end=/\%2l/ contains=plugNumber + syntax region plug2 start=/\%2l/ end=/\%3l/ contains=plugBracket,plugX + syn match plugNumber /[0-9]\+[0-9.]*/ contained + syn match plugBracket /[[\]]/ contained + syn match plugX /x/ contained + syn match plugDash /^-\{1}\ / + syn match plugPlus /^+/ + syn match plugStar /^*/ + syn match plugMessage /\(^- \)\@<=.*/ + syn match plugName /\(^- \)\@<=[^ ]*:/ + syn match plugSha /\%(: \)\@<=[0-9a-f]\{4,}$/ + syn match plugTag /(tag: [^)]\+)/ + syn match plugInstall /\(^+ \)\@<=[^:]*/ + syn match plugUpdate /\(^* \)\@<=[^:]*/ + syn match plugCommit /^ \X*[0-9a-f]\{7,9} .*/ contains=plugRelDate,plugEdge,plugTag + syn match plugEdge /^ \X\+$/ + syn match plugEdge /^ \X*/ contained nextgroup=plugSha + syn match plugSha /[0-9a-f]\{7,9}/ contained + syn match plugRelDate /([^)]*)$/ contained + syn match plugNotLoaded /(not loaded)$/ + syn match plugError /^x.*/ + syn region plugDeleted start=/^\~ .*/ end=/^\ze\S/ + syn match plugH2 /^.*:\n-\+$/ + syn match plugH2 /^-\{2,}/ + syn keyword Function PlugInstall PlugStatus PlugUpdate PlugClean + hi def link plug1 Title + hi def link plug2 Repeat + hi def link plugH2 Type + hi def link plugX Exception + hi def link plugBracket Structure + hi def link plugNumber Number + + hi def link plugDash Special + hi def link plugPlus Constant + hi def link plugStar Boolean + + hi def link plugMessage Function + hi def link plugName Label + hi def link plugInstall Function + hi def link plugUpdate Type + + hi def link plugError Error + hi def link plugDeleted Ignore + hi def link plugRelDate Comment + hi def link plugEdge PreProc + hi def link plugSha Identifier + hi def link plugTag Constant + + hi def link plugNotLoaded Comment +endfunction + +function! s:lpad(str, len) + return a:str . repeat(' ', a:len - len(a:str)) +endfunction + +function! s:lines(msg) + return split(a:msg, "[\r\n]") +endfunction + +function! s:lastline(msg) + return get(s:lines(a:msg), -1, '') +endfunction + +function! s:new_window() + execute get(g:, 'plug_window', 'vertical topleft new') +endfunction + +function! s:plug_window_exists() + let buflist = tabpagebuflist(s:plug_tab) + return !empty(buflist) && index(buflist, s:plug_buf) >= 0 +endfunction + +function! s:switch_in() + if !s:plug_window_exists() + return 0 + endif + + if winbufnr(0) != s:plug_buf + let s:pos = [tabpagenr(), winnr(), winsaveview()] + execute 'normal!' s:plug_tab.'gt' + let winnr = bufwinnr(s:plug_buf) + execute winnr.'wincmd w' + call add(s:pos, winsaveview()) + else + let s:pos = [winsaveview()] + endif + + setlocal modifiable + return 1 +endfunction + +function! s:switch_out(...) + call winrestview(s:pos[-1]) + setlocal nomodifiable + if a:0 > 0 + execute a:1 + endif + + if len(s:pos) > 1 + execute 'normal!' s:pos[0].'gt' + execute s:pos[1] 'wincmd w' + call winrestview(s:pos[2]) + endif +endfunction + +function! s:finish_bindings() + nnoremap R :call retry() + nnoremap D :PlugDiff + nnoremap S :PlugStatus + nnoremap U :call status_update() + xnoremap U :call status_update() + nnoremap ]] :silent! call section('') + nnoremap [[ :silent! call section('b') +endfunction + +function! s:prepare(...) + if empty(s:plug_getcwd()) + throw 'Invalid current working directory. Cannot proceed.' + endif + + for evar in ['$GIT_DIR', '$GIT_WORK_TREE'] + if exists(evar) + throw evar.' detected. Cannot proceed.' + endif + endfor + + call s:job_abort() + if s:switch_in() + if b:plug_preview == 1 + pc + endif + enew + else + call s:new_window() + endif + + nnoremap q :call close_pane() + if a:0 == 0 + call s:finish_bindings() + endif + let b:plug_preview = -1 + let s:plug_tab = tabpagenr() + let s:plug_buf = winbufnr(0) + call s:assign_name() + + for k in ['', 'L', 'o', 'X', 'd', 'dd'] + execute 'silent! unmap ' k + endfor + setlocal buftype=nofile bufhidden=wipe nobuflisted nolist noswapfile nowrap cursorline modifiable nospell + if exists('+colorcolumn') + setlocal colorcolumn= + endif + setf vim-plug + if exists('g:syntax_on') + call s:syntax() + endif +endfunction + +function! s:close_pane() + if b:plug_preview == 1 + pc + let b:plug_preview = -1 + else + bd + endif +endfunction + +function! s:assign_name() + " Assign buffer name + let prefix = '[Plugins]' + let name = prefix + let idx = 2 + while bufexists(name) + let name = printf('%s (%s)', prefix, idx) + let idx = idx + 1 + endwhile + silent! execute 'f' fnameescape(name) +endfunction + +function! s:chsh(swap) + let prev = [&shell, &shellcmdflag, &shellredir] + if !s:is_win + set shell=sh + endif + if a:swap + if s:is_powershell(&shell) + let &shellredir = '2>&1 | Out-File -Encoding UTF8 %s' + elseif &shell =~# 'sh' || &shell =~# 'cmd\(\.exe\)\?$' + set shellredir=>%s\ 2>&1 + endif + endif + return prev +endfunction + +function! s:bang(cmd, ...) + let batchfile = '' + try + let [sh, shellcmdflag, shrd] = s:chsh(a:0) + " FIXME: Escaping is incomplete. We could use shellescape with eval, + " but it won't work on Windows. + let cmd = a:0 ? s:with_cd(a:cmd, a:1) : a:cmd + if s:is_win + let [batchfile, cmd] = s:batchfile(cmd) + endif + let g:_plug_bang = (s:is_win && has('gui_running') ? 'silent ' : '').'!'.escape(cmd, '#!%') + execute "normal! :execute g:_plug_bang\\" + finally + unlet g:_plug_bang + let [&shell, &shellcmdflag, &shellredir] = [sh, shellcmdflag, shrd] + if s:is_win && filereadable(batchfile) + call delete(batchfile) + endif + endtry + return v:shell_error ? 'Exit status: ' . v:shell_error : '' +endfunction + +function! s:regress_bar() + let bar = substitute(getline(2)[1:-2], '.*\zs=', 'x', '') + call s:progress_bar(2, bar, len(bar)) +endfunction + +function! s:is_updated(dir) + return !empty(s:system_chomp(['git', 'log', '--pretty=format:%h', 'HEAD...HEAD@{1}'], a:dir)) +endfunction + +function! s:do(pull, force, todo) + for [name, spec] in items(a:todo) + if !isdirectory(spec.dir) + continue + endif + let installed = has_key(s:update.new, name) + let updated = installed ? 0 : + \ (a:pull && index(s:update.errors, name) < 0 && s:is_updated(spec.dir)) + if a:force || installed || updated + execute 'cd' s:esc(spec.dir) + call append(3, '- Post-update hook for '. name .' ... ') + let error = '' + let type = type(spec.do) + if type == s:TYPE.string + if spec.do[0] == ':' + if !get(s:loaded, name, 0) + let s:loaded[name] = 1 + call s:reorg_rtp() + endif + call s:load_plugin(spec) + try + execute spec.do[1:] + catch + let error = v:exception + endtry + if !s:plug_window_exists() + cd - + throw 'Warning: vim-plug was terminated by the post-update hook of '.name + endif + else + let error = s:bang(spec.do) + endif + elseif type == s:TYPE.funcref + try + call s:load_plugin(spec) + let status = installed ? 'installed' : (updated ? 'updated' : 'unchanged') + call spec.do({ 'name': name, 'status': status, 'force': a:force }) + catch + let error = v:exception + endtry + else + let error = 'Invalid hook type' + endif + call s:switch_in() + call setline(4, empty(error) ? (getline(4) . 'OK') + \ : ('x' . getline(4)[1:] . error)) + if !empty(error) + call add(s:update.errors, name) + call s:regress_bar() + endif + cd - + endif + endfor +endfunction + +function! s:hash_match(a, b) + return stridx(a:a, a:b) == 0 || stridx(a:b, a:a) == 0 +endfunction + +function! s:checkout(spec) + let sha = a:spec.commit + let output = s:git_revision(a:spec.dir) + if !empty(output) && !s:hash_match(sha, s:lines(output)[0]) + let credential_helper = s:git_version_requirement(2) ? '-c credential.helper= ' : '' + let output = s:system( + \ 'git '.credential_helper.'fetch --depth 999999 && git checkout '.plug#shellescape(sha).' --', a:spec.dir) + endif + return output +endfunction + +function! s:finish(pull) + let new_frozen = len(filter(keys(s:update.new), 'g:plugs[v:val].frozen')) + if new_frozen + let s = new_frozen > 1 ? 's' : '' + call append(3, printf('- Installed %d frozen plugin%s', new_frozen, s)) + endif + call append(3, '- Finishing ... ') | 4 + redraw + call plug#helptags() + call plug#end() + call setline(4, getline(4) . 'Done!') + redraw + let msgs = [] + if !empty(s:update.errors) + call add(msgs, "Press 'R' to retry.") + endif + if a:pull && len(s:update.new) < len(filter(getline(5, '$'), + \ "v:val =~ '^- ' && v:val !~# 'Already up.to.date'")) + call add(msgs, "Press 'D' to see the updated changes.") + endif + echo join(msgs, ' ') + call s:finish_bindings() +endfunction + +function! s:retry() + if empty(s:update.errors) + return + endif + echo + call s:update_impl(s:update.pull, s:update.force, + \ extend(copy(s:update.errors), [s:update.threads])) +endfunction + +function! s:is_managed(name) + return has_key(g:plugs[a:name], 'uri') +endfunction + +function! s:names(...) + return sort(filter(keys(g:plugs), 'stridx(v:val, a:1) == 0 && s:is_managed(v:val)')) +endfunction + +function! s:check_ruby() + silent! ruby require 'thread'; VIM::command("let g:plug_ruby = '#{RUBY_VERSION}'") + if !exists('g:plug_ruby') + redraw! + return s:warn('echom', 'Warning: Ruby interface is broken') + endif + let ruby_version = split(g:plug_ruby, '\.') + unlet g:plug_ruby + return s:version_requirement(ruby_version, [1, 8, 7]) +endfunction + +function! s:update_impl(pull, force, args) abort + let sync = index(a:args, '--sync') >= 0 || has('vim_starting') + let args = filter(copy(a:args), 'v:val != "--sync"') + let threads = (len(args) > 0 && args[-1] =~ '^[1-9][0-9]*$') ? + \ remove(args, -1) : get(g:, 'plug_threads', 16) + + let managed = filter(copy(g:plugs), 's:is_managed(v:key)') + let todo = empty(args) ? filter(managed, '!v:val.frozen || !isdirectory(v:val.dir)') : + \ filter(managed, 'index(args, v:key) >= 0') + + if empty(todo) + return s:warn('echo', 'No plugin to '. (a:pull ? 'update' : 'install')) + endif + + if !s:is_win && s:git_version_requirement(2, 3) + let s:git_terminal_prompt = exists('$GIT_TERMINAL_PROMPT') ? $GIT_TERMINAL_PROMPT : '' + let $GIT_TERMINAL_PROMPT = 0 + for plug in values(todo) + let plug.uri = substitute(plug.uri, + \ '^https://git::@github\.com', 'https://github.com', '') + endfor + endif + + if !isdirectory(g:plug_home) + try + call mkdir(g:plug_home, 'p') + catch + return s:err(printf('Invalid plug directory: %s. '. + \ 'Try to call plug#begin with a valid directory', g:plug_home)) + endtry + endif + + if has('nvim') && !exists('*jobwait') && threads > 1 + call s:warn('echom', '[vim-plug] Update Neovim for parallel installer') + endif + + let use_job = s:nvim || s:vim8 + let python = (has('python') || has('python3')) && !use_job + let ruby = has('ruby') && !use_job && (v:version >= 703 || v:version == 702 && has('patch374')) && !(s:is_win && has('gui_running')) && threads > 1 && s:check_ruby() + + let s:update = { + \ 'start': reltime(), + \ 'all': todo, + \ 'todo': copy(todo), + \ 'errors': [], + \ 'pull': a:pull, + \ 'force': a:force, + \ 'new': {}, + \ 'threads': (python || ruby || use_job) ? min([len(todo), threads]) : 1, + \ 'bar': '', + \ 'fin': 0 + \ } + + call s:prepare(1) + call append(0, ['', '']) + normal! 2G + silent! redraw + + " Set remote name, overriding a possible user git config's clone.defaultRemoteName + let s:clone_opt = ['--origin', 'origin'] + if get(g:, 'plug_shallow', 1) + call extend(s:clone_opt, ['--depth', '1']) + if s:git_version_requirement(1, 7, 10) + call add(s:clone_opt, '--no-single-branch') + endif + endif + + if has('win32unix') || has('wsl') + call extend(s:clone_opt, ['-c', 'core.eol=lf', '-c', 'core.autocrlf=input']) + endif + + let s:submodule_opt = s:git_version_requirement(2, 8) ? ' --jobs='.threads : '' + + " Python version requirement (>= 2.7) + if python && !has('python3') && !ruby && !use_job && s:update.threads > 1 + redir => pyv + silent python import platform; print platform.python_version() + redir END + let python = s:version_requirement( + \ map(split(split(pyv)[0], '\.'), 'str2nr(v:val)'), [2, 6]) + endif + + if (python || ruby) && s:update.threads > 1 + try + let imd = &imd + if s:mac_gui + set noimd + endif + if ruby + call s:update_ruby() + else + call s:update_python() + endif + catch + let lines = getline(4, '$') + let printed = {} + silent! 4,$d _ + for line in lines + let name = s:extract_name(line, '.', '') + if empty(name) || !has_key(printed, name) + call append('$', line) + if !empty(name) + let printed[name] = 1 + if line[0] == 'x' && index(s:update.errors, name) < 0 + call add(s:update.errors, name) + end + endif + endif + endfor + finally + let &imd = imd + call s:update_finish() + endtry + else + call s:update_vim() + while use_job && sync + sleep 100m + if s:update.fin + break + endif + endwhile + endif +endfunction + +function! s:log4(name, msg) + call setline(4, printf('- %s (%s)', a:msg, a:name)) + redraw +endfunction + +function! s:update_finish() + if exists('s:git_terminal_prompt') + let $GIT_TERMINAL_PROMPT = s:git_terminal_prompt + endif + if s:switch_in() + call append(3, '- Updating ...') | 4 + for [name, spec] in items(filter(copy(s:update.all), 'index(s:update.errors, v:key) < 0 && (s:update.force || s:update.pull || has_key(s:update.new, v:key))')) + let [pos, _] = s:logpos(name) + if !pos + continue + endif + if has_key(spec, 'commit') + call s:log4(name, 'Checking out '.spec.commit) + let out = s:checkout(spec) + elseif has_key(spec, 'tag') + let tag = spec.tag + if tag =~ '\*' + let tags = s:lines(s:system('git tag --list '.plug#shellescape(tag).' --sort -version:refname 2>&1', spec.dir)) + if !v:shell_error && !empty(tags) + let tag = tags[0] + call s:log4(name, printf('Latest tag for %s -> %s', spec.tag, tag)) + call append(3, '') + endif + endif + call s:log4(name, 'Checking out '.tag) + let out = s:system('git checkout -q '.plug#shellescape(tag).' -- 2>&1', spec.dir) + else + let branch = s:git_origin_branch(spec) + call s:log4(name, 'Merging origin/'.s:esc(branch)) + let out = s:system('git checkout -q '.plug#shellescape(branch).' -- 2>&1' + \. (has_key(s:update.new, name) ? '' : ('&& git merge --ff-only '.plug#shellescape('origin/'.branch).' 2>&1')), spec.dir) + endif + if !v:shell_error && filereadable(spec.dir.'/.gitmodules') && + \ (s:update.force || has_key(s:update.new, name) || s:is_updated(spec.dir)) + call s:log4(name, 'Updating submodules. This may take a while.') + let out .= s:bang('git submodule update --init --recursive'.s:submodule_opt.' 2>&1', spec.dir) + endif + let msg = s:format_message(v:shell_error ? 'x': '-', name, out) + if v:shell_error + call add(s:update.errors, name) + call s:regress_bar() + silent execute pos 'd _' + call append(4, msg) | 4 + elseif !empty(out) + call setline(pos, msg[0]) + endif + redraw + endfor + silent 4 d _ + try + call s:do(s:update.pull, s:update.force, filter(copy(s:update.all), 'index(s:update.errors, v:key) < 0 && has_key(v:val, "do")')) + catch + call s:warn('echom', v:exception) + call s:warn('echo', '') + return + endtry + call s:finish(s:update.pull) + call setline(1, 'Updated. Elapsed time: ' . split(reltimestr(reltime(s:update.start)))[0] . ' sec.') + call s:switch_out('normal! gg') + endif +endfunction + +function! s:job_abort() + if (!s:nvim && !s:vim8) || !exists('s:jobs') + return + endif + + for [name, j] in items(s:jobs) + if s:nvim + silent! call jobstop(j.jobid) + elseif s:vim8 + silent! call job_stop(j.jobid) + endif + if j.new + call s:rm_rf(g:plugs[name].dir) + endif + endfor + let s:jobs = {} +endfunction + +function! s:last_non_empty_line(lines) + let len = len(a:lines) + for idx in range(len) + let line = a:lines[len-idx-1] + if !empty(line) + return line + endif + endfor + return '' +endfunction + +function! s:job_out_cb(self, data) abort + let self = a:self + let data = remove(self.lines, -1) . a:data + let lines = map(split(data, "\n", 1), 'split(v:val, "\r", 1)[-1]') + call extend(self.lines, lines) + " To reduce the number of buffer updates + let self.tick = get(self, 'tick', -1) + 1 + if !self.running || self.tick % len(s:jobs) == 0 + let bullet = self.running ? (self.new ? '+' : '*') : (self.error ? 'x' : '-') + let result = self.error ? join(self.lines, "\n") : s:last_non_empty_line(self.lines) + call s:log(bullet, self.name, result) + endif +endfunction + +function! s:job_exit_cb(self, data) abort + let a:self.running = 0 + let a:self.error = a:data != 0 + call s:reap(a:self.name) + call s:tick() +endfunction + +function! s:job_cb(fn, job, ch, data) + if !s:plug_window_exists() " plug window closed + return s:job_abort() + endif + call call(a:fn, [a:job, a:data]) +endfunction + +function! s:nvim_cb(job_id, data, event) dict abort + return (a:event == 'stdout' || a:event == 'stderr') ? + \ s:job_cb('s:job_out_cb', self, 0, join(a:data, "\n")) : + \ s:job_cb('s:job_exit_cb', self, 0, a:data) +endfunction + +function! s:spawn(name, cmd, opts) + let job = { 'name': a:name, 'running': 1, 'error': 0, 'lines': [''], + \ 'new': get(a:opts, 'new', 0) } + let s:jobs[a:name] = job + + if s:nvim + if has_key(a:opts, 'dir') + let job.cwd = a:opts.dir + endif + let argv = a:cmd + call extend(job, { + \ 'on_stdout': function('s:nvim_cb'), + \ 'on_stderr': function('s:nvim_cb'), + \ 'on_exit': function('s:nvim_cb'), + \ }) + let jid = s:plug_call('jobstart', argv, job) + if jid > 0 + let job.jobid = jid + else + let job.running = 0 + let job.error = 1 + let job.lines = [jid < 0 ? argv[0].' is not executable' : + \ 'Invalid arguments (or job table is full)'] + endif + elseif s:vim8 + let cmd = join(map(copy(a:cmd), 'plug#shellescape(v:val, {"script": 0})')) + if has_key(a:opts, 'dir') + let cmd = s:with_cd(cmd, a:opts.dir, 0) + endif + let argv = s:is_win ? ['cmd', '/s', '/c', '"'.cmd.'"'] : ['sh', '-c', cmd] + let jid = job_start(s:is_win ? join(argv, ' ') : argv, { + \ 'out_cb': function('s:job_cb', ['s:job_out_cb', job]), + \ 'err_cb': function('s:job_cb', ['s:job_out_cb', job]), + \ 'exit_cb': function('s:job_cb', ['s:job_exit_cb', job]), + \ 'err_mode': 'raw', + \ 'out_mode': 'raw' + \}) + if job_status(jid) == 'run' + let job.jobid = jid + else + let job.running = 0 + let job.error = 1 + let job.lines = ['Failed to start job'] + endif + else + let job.lines = s:lines(call('s:system', has_key(a:opts, 'dir') ? [a:cmd, a:opts.dir] : [a:cmd])) + let job.error = v:shell_error != 0 + let job.running = 0 + endif +endfunction + +function! s:reap(name) + let job = s:jobs[a:name] + if job.error + call add(s:update.errors, a:name) + elseif get(job, 'new', 0) + let s:update.new[a:name] = 1 + endif + let s:update.bar .= job.error ? 'x' : '=' + + let bullet = job.error ? 'x' : '-' + let result = job.error ? join(job.lines, "\n") : s:last_non_empty_line(job.lines) + call s:log(bullet, a:name, empty(result) ? 'OK' : result) + call s:bar() + + call remove(s:jobs, a:name) +endfunction + +function! s:bar() + if s:switch_in() + let total = len(s:update.all) + call setline(1, (s:update.pull ? 'Updating' : 'Installing'). + \ ' plugins ('.len(s:update.bar).'/'.total.')') + call s:progress_bar(2, s:update.bar, total) + call s:switch_out() + endif +endfunction + +function! s:logpos(name) + let max = line('$') + for i in range(4, max > 4 ? max : 4) + if getline(i) =~# '^[-+x*] '.a:name.':' + for j in range(i + 1, max > 5 ? max : 5) + if getline(j) !~ '^ ' + return [i, j - 1] + endif + endfor + return [i, i] + endif + endfor + return [0, 0] +endfunction + +function! s:log(bullet, name, lines) + if s:switch_in() + let [b, e] = s:logpos(a:name) + if b > 0 + silent execute printf('%d,%d d _', b, e) + if b > winheight('.') + let b = 4 + endif + else + let b = 4 + endif + " FIXME For some reason, nomodifiable is set after :d in vim8 + setlocal modifiable + call append(b - 1, s:format_message(a:bullet, a:name, a:lines)) + call s:switch_out() + endif +endfunction + +function! s:update_vim() + let s:jobs = {} + + call s:bar() + call s:tick() +endfunction + +function! s:tick() + let pull = s:update.pull + let prog = s:progress_opt(s:nvim || s:vim8) +while 1 " Without TCO, Vim stack is bound to explode + if empty(s:update.todo) + if empty(s:jobs) && !s:update.fin + call s:update_finish() + let s:update.fin = 1 + endif + return + endif + + let name = keys(s:update.todo)[0] + let spec = remove(s:update.todo, name) + let new = empty(globpath(spec.dir, '.git', 1)) + + call s:log(new ? '+' : '*', name, pull ? 'Updating ...' : 'Installing ...') + redraw + + let has_tag = has_key(spec, 'tag') + if !new + let [error, _] = s:git_validate(spec, 0) + if empty(error) + if pull + let cmd = s:git_version_requirement(2) ? ['git', '-c', 'credential.helper=', 'fetch'] : ['git', 'fetch'] + if has_tag && !empty(globpath(spec.dir, '.git/shallow')) + call extend(cmd, ['--depth', '99999999']) + endif + if !empty(prog) + call add(cmd, prog) + endif + call s:spawn(name, cmd, { 'dir': spec.dir }) + else + let s:jobs[name] = { 'running': 0, 'lines': ['Already installed'], 'error': 0 } + endif + else + let s:jobs[name] = { 'running': 0, 'lines': s:lines(error), 'error': 1 } + endif + else + let cmd = ['git', 'clone'] + if !has_tag + call extend(cmd, s:clone_opt) + endif + if !empty(prog) + call add(cmd, prog) + endif + call s:spawn(name, extend(cmd, [spec.uri, s:trim(spec.dir)]), { 'new': 1 }) + endif + + if !s:jobs[name].running + call s:reap(name) + endif + if len(s:jobs) >= s:update.threads + break + endif +endwhile +endfunction + +function! s:update_python() +let py_exe = has('python') ? 'python' : 'python3' +execute py_exe "<< EOF" +import datetime +import functools +import os +try: + import queue +except ImportError: + import Queue as queue +import random +import re +import shutil +import signal +import subprocess +import tempfile +import threading as thr +import time +import traceback +import vim + +G_NVIM = vim.eval("has('nvim')") == '1' +G_PULL = vim.eval('s:update.pull') == '1' +G_RETRIES = int(vim.eval('get(g:, "plug_retries", 2)')) + 1 +G_TIMEOUT = int(vim.eval('get(g:, "plug_timeout", 60)')) +G_CLONE_OPT = ' '.join(vim.eval('s:clone_opt')) +G_PROGRESS = vim.eval('s:progress_opt(1)') +G_LOG_PROB = 1.0 / int(vim.eval('s:update.threads')) +G_STOP = thr.Event() +G_IS_WIN = vim.eval('s:is_win') == '1' + +class PlugError(Exception): + def __init__(self, msg): + self.msg = msg +class CmdTimedOut(PlugError): + pass +class CmdFailed(PlugError): + pass +class InvalidURI(PlugError): + pass +class Action(object): + INSTALL, UPDATE, ERROR, DONE = ['+', '*', 'x', '-'] + +class Buffer(object): + def __init__(self, lock, num_plugs, is_pull): + self.bar = '' + self.event = 'Updating' if is_pull else 'Installing' + self.lock = lock + self.maxy = int(vim.eval('winheight(".")')) + self.num_plugs = num_plugs + + def __where(self, name): + """ Find first line with name in current buffer. Return line num. """ + found, lnum = False, 0 + matcher = re.compile('^[-+x*] {0}:'.format(name)) + for line in vim.current.buffer: + if matcher.search(line) is not None: + found = True + break + lnum += 1 + + if not found: + lnum = -1 + return lnum + + def header(self): + curbuf = vim.current.buffer + curbuf[0] = self.event + ' plugins ({0}/{1})'.format(len(self.bar), self.num_plugs) + + num_spaces = self.num_plugs - len(self.bar) + curbuf[1] = '[{0}{1}]'.format(self.bar, num_spaces * ' ') + + with self.lock: + vim.command('normal! 2G') + vim.command('redraw') + + def write(self, action, name, lines): + first, rest = lines[0], lines[1:] + msg = ['{0} {1}{2}{3}'.format(action, name, ': ' if first else '', first)] + msg.extend([' ' + line for line in rest]) + + try: + if action == Action.ERROR: + self.bar += 'x' + vim.command("call add(s:update.errors, '{0}')".format(name)) + elif action == Action.DONE: + self.bar += '=' + + curbuf = vim.current.buffer + lnum = self.__where(name) + if lnum != -1: # Found matching line num + del curbuf[lnum] + if lnum > self.maxy and action in set([Action.INSTALL, Action.UPDATE]): + lnum = 3 + else: + lnum = 3 + curbuf.append(msg, lnum) + + self.header() + except vim.error: + pass + +class Command(object): + CD = 'cd /d' if G_IS_WIN else 'cd' + + def __init__(self, cmd, cmd_dir=None, timeout=60, cb=None, clean=None): + self.cmd = cmd + if cmd_dir: + self.cmd = '{0} {1} && {2}'.format(Command.CD, cmd_dir, self.cmd) + self.timeout = timeout + self.callback = cb if cb else (lambda msg: None) + self.clean = clean if clean else (lambda: None) + self.proc = None + + @property + def alive(self): + """ Returns true only if command still running. """ + return self.proc and self.proc.poll() is None + + def execute(self, ntries=3): + """ Execute the command with ntries if CmdTimedOut. + Returns the output of the command if no Exception. + """ + attempt, finished, limit = 0, False, self.timeout + + while not finished: + try: + attempt += 1 + result = self.try_command() + finished = True + return result + except CmdTimedOut: + if attempt != ntries: + self.notify_retry() + self.timeout += limit + else: + raise + + def notify_retry(self): + """ Retry required for command, notify user. """ + for count in range(3, 0, -1): + if G_STOP.is_set(): + raise KeyboardInterrupt + msg = 'Timeout. Will retry in {0} second{1} ...'.format( + count, 's' if count != 1 else '') + self.callback([msg]) + time.sleep(1) + self.callback(['Retrying ...']) + + def try_command(self): + """ Execute a cmd & poll for callback. Returns list of output. + Raises CmdFailed -> return code for Popen isn't 0 + Raises CmdTimedOut -> command exceeded timeout without new output + """ + first_line = True + + try: + tfile = tempfile.NamedTemporaryFile(mode='w+b') + preexec_fn = not G_IS_WIN and os.setsid or None + self.proc = subprocess.Popen(self.cmd, stdout=tfile, + stderr=subprocess.STDOUT, + stdin=subprocess.PIPE, shell=True, + preexec_fn=preexec_fn) + thrd = thr.Thread(target=(lambda proc: proc.wait()), args=(self.proc,)) + thrd.start() + + thread_not_started = True + while thread_not_started: + try: + thrd.join(0.1) + thread_not_started = False + except RuntimeError: + pass + + while self.alive: + if G_STOP.is_set(): + raise KeyboardInterrupt + + if first_line or random.random() < G_LOG_PROB: + first_line = False + line = '' if G_IS_WIN else nonblock_read(tfile.name) + if line: + self.callback([line]) + + time_diff = time.time() - os.path.getmtime(tfile.name) + if time_diff > self.timeout: + raise CmdTimedOut(['Timeout!']) + + thrd.join(0.5) + + tfile.seek(0) + result = [line.decode('utf-8', 'replace').rstrip() for line in tfile] + + if self.proc.returncode != 0: + raise CmdFailed([''] + result) + + return result + except: + self.terminate() + raise + + def terminate(self): + """ Terminate process and cleanup. """ + if self.alive: + if G_IS_WIN: + os.kill(self.proc.pid, signal.SIGINT) + else: + os.killpg(self.proc.pid, signal.SIGTERM) + self.clean() + +class Plugin(object): + def __init__(self, name, args, buf_q, lock): + self.name = name + self.args = args + self.buf_q = buf_q + self.lock = lock + self.tag = args.get('tag', 0) + + def manage(self): + try: + if os.path.exists(self.args['dir']): + self.update() + else: + self.install() + with self.lock: + thread_vim_command("let s:update.new['{0}'] = 1".format(self.name)) + except PlugError as exc: + self.write(Action.ERROR, self.name, exc.msg) + except KeyboardInterrupt: + G_STOP.set() + self.write(Action.ERROR, self.name, ['Interrupted!']) + except: + # Any exception except those above print stack trace + msg = 'Trace:\n{0}'.format(traceback.format_exc().rstrip()) + self.write(Action.ERROR, self.name, msg.split('\n')) + raise + + def install(self): + target = self.args['dir'] + if target[-1] == '\\': + target = target[0:-1] + + def clean(target): + def _clean(): + try: + shutil.rmtree(target) + except OSError: + pass + return _clean + + self.write(Action.INSTALL, self.name, ['Installing ...']) + callback = functools.partial(self.write, Action.INSTALL, self.name) + cmd = 'git clone {0} {1} {2} {3} 2>&1'.format( + '' if self.tag else G_CLONE_OPT, G_PROGRESS, self.args['uri'], + esc(target)) + com = Command(cmd, None, G_TIMEOUT, callback, clean(target)) + result = com.execute(G_RETRIES) + self.write(Action.DONE, self.name, result[-1:]) + + def repo_uri(self): + cmd = 'git rev-parse --abbrev-ref HEAD 2>&1 && git config -f .git/config remote.origin.url' + command = Command(cmd, self.args['dir'], G_TIMEOUT,) + result = command.execute(G_RETRIES) + return result[-1] + + def update(self): + actual_uri = self.repo_uri() + expect_uri = self.args['uri'] + regex = re.compile(r'^(?:\w+://)?(?:[^@/]*@)?([^:/]*(?::[0-9]*)?)[:/](.*?)(?:\.git)?/?$') + ma = regex.match(actual_uri) + mb = regex.match(expect_uri) + if ma is None or mb is None or ma.groups() != mb.groups(): + msg = ['', + 'Invalid URI: {0}'.format(actual_uri), + 'Expected {0}'.format(expect_uri), + 'PlugClean required.'] + raise InvalidURI(msg) + + if G_PULL: + self.write(Action.UPDATE, self.name, ['Updating ...']) + callback = functools.partial(self.write, Action.UPDATE, self.name) + fetch_opt = '--depth 99999999' if self.tag and os.path.isfile(os.path.join(self.args['dir'], '.git/shallow')) else '' + cmd = 'git fetch {0} {1} 2>&1'.format(fetch_opt, G_PROGRESS) + com = Command(cmd, self.args['dir'], G_TIMEOUT, callback) + result = com.execute(G_RETRIES) + self.write(Action.DONE, self.name, result[-1:]) + else: + self.write(Action.DONE, self.name, ['Already installed']) + + def write(self, action, name, msg): + self.buf_q.put((action, name, msg)) + +class PlugThread(thr.Thread): + def __init__(self, tname, args): + super(PlugThread, self).__init__() + self.tname = tname + self.args = args + + def run(self): + thr.current_thread().name = self.tname + buf_q, work_q, lock = self.args + + try: + while not G_STOP.is_set(): + name, args = work_q.get_nowait() + plug = Plugin(name, args, buf_q, lock) + plug.manage() + work_q.task_done() + except queue.Empty: + pass + +class RefreshThread(thr.Thread): + def __init__(self, lock): + super(RefreshThread, self).__init__() + self.lock = lock + self.running = True + + def run(self): + while self.running: + with self.lock: + thread_vim_command('noautocmd normal! a') + time.sleep(0.33) + + def stop(self): + self.running = False + +if G_NVIM: + def thread_vim_command(cmd): + vim.session.threadsafe_call(lambda: vim.command(cmd)) +else: + def thread_vim_command(cmd): + vim.command(cmd) + +def esc(name): + return '"' + name.replace('"', '\"') + '"' + +def nonblock_read(fname): + """ Read a file with nonblock flag. Return the last line. """ + fread = os.open(fname, os.O_RDONLY | os.O_NONBLOCK) + buf = os.read(fread, 100000).decode('utf-8', 'replace') + os.close(fread) + + line = buf.rstrip('\r\n') + left = max(line.rfind('\r'), line.rfind('\n')) + if left != -1: + left += 1 + line = line[left:] + + return line + +def main(): + thr.current_thread().name = 'main' + nthreads = int(vim.eval('s:update.threads')) + plugs = vim.eval('s:update.todo') + mac_gui = vim.eval('s:mac_gui') == '1' + + lock = thr.Lock() + buf = Buffer(lock, len(plugs), G_PULL) + buf_q, work_q = queue.Queue(), queue.Queue() + for work in plugs.items(): + work_q.put(work) + + start_cnt = thr.active_count() + for num in range(nthreads): + tname = 'PlugT-{0:02}'.format(num) + thread = PlugThread(tname, (buf_q, work_q, lock)) + thread.start() + if mac_gui: + rthread = RefreshThread(lock) + rthread.start() + + while not buf_q.empty() or thr.active_count() != start_cnt: + try: + action, name, msg = buf_q.get(True, 0.25) + buf.write(action, name, ['OK'] if not msg else msg) + buf_q.task_done() + except queue.Empty: + pass + except KeyboardInterrupt: + G_STOP.set() + + if mac_gui: + rthread.stop() + rthread.join() + +main() +EOF +endfunction + +function! s:update_ruby() + ruby << EOF + module PlugStream + SEP = ["\r", "\n", nil] + def get_line + buffer = '' + loop do + char = readchar rescue return + if SEP.include? char.chr + buffer << $/ + break + else + buffer << char + end + end + buffer + end + end unless defined?(PlugStream) + + def esc arg + %["#{arg.gsub('"', '\"')}"] + end + + def killall pid + pids = [pid] + if /mswin|mingw|bccwin/ =~ RUBY_PLATFORM + pids.each { |pid| Process.kill 'INT', pid.to_i rescue nil } + else + unless `which pgrep 2> /dev/null`.empty? + children = pids + until children.empty? + children = children.map { |pid| + `pgrep -P #{pid}`.lines.map { |l| l.chomp } + }.flatten + pids += children + end + end + pids.each { |pid| Process.kill 'TERM', pid.to_i rescue nil } + end + end + + def compare_git_uri a, b + regex = %r{^(?:\w+://)?(?:[^@/]*@)?([^:/]*(?::[0-9]*)?)[:/](.*?)(?:\.git)?/?$} + regex.match(a).to_a.drop(1) == regex.match(b).to_a.drop(1) + end + + require 'thread' + require 'fileutils' + require 'timeout' + running = true + iswin = VIM::evaluate('s:is_win').to_i == 1 + pull = VIM::evaluate('s:update.pull').to_i == 1 + base = VIM::evaluate('g:plug_home') + all = VIM::evaluate('s:update.todo') + limit = VIM::evaluate('get(g:, "plug_timeout", 60)') + tries = VIM::evaluate('get(g:, "plug_retries", 2)') + 1 + nthr = VIM::evaluate('s:update.threads').to_i + maxy = VIM::evaluate('winheight(".")').to_i + vim7 = VIM::evaluate('v:version').to_i <= 703 && RUBY_PLATFORM =~ /darwin/ + cd = iswin ? 'cd /d' : 'cd' + tot = VIM::evaluate('len(s:update.todo)') || 0 + bar = '' + skip = 'Already installed' + mtx = Mutex.new + take1 = proc { mtx.synchronize { running && all.shift } } + logh = proc { + cnt = bar.length + $curbuf[1] = "#{pull ? 'Updating' : 'Installing'} plugins (#{cnt}/#{tot})" + $curbuf[2] = '[' + bar.ljust(tot) + ']' + VIM::command('normal! 2G') + VIM::command('redraw') + } + where = proc { |name| (1..($curbuf.length)).find { |l| $curbuf[l] =~ /^[-+x*] #{name}:/ } } + log = proc { |name, result, type| + mtx.synchronize do + ing = ![true, false].include?(type) + bar += type ? '=' : 'x' unless ing + b = case type + when :install then '+' when :update then '*' + when true, nil then '-' else + VIM::command("call add(s:update.errors, '#{name}')") + 'x' + end + result = + if type || type.nil? + ["#{b} #{name}: #{result.lines.to_a.last || 'OK'}"] + elsif result =~ /^Interrupted|^Timeout/ + ["#{b} #{name}: #{result}"] + else + ["#{b} #{name}"] + result.lines.map { |l| " " << l } + end + if lnum = where.call(name) + $curbuf.delete lnum + lnum = 4 if ing && lnum > maxy + end + result.each_with_index do |line, offset| + $curbuf.append((lnum || 4) - 1 + offset, line.gsub(/\e\[./, '').chomp) + end + logh.call + end + } + bt = proc { |cmd, name, type, cleanup| + tried = timeout = 0 + begin + tried += 1 + timeout += limit + fd = nil + data = '' + if iswin + Timeout::timeout(timeout) do + tmp = VIM::evaluate('tempname()') + system("(#{cmd}) > #{tmp}") + data = File.read(tmp).chomp + File.unlink tmp rescue nil + end + else + fd = IO.popen(cmd).extend(PlugStream) + first_line = true + log_prob = 1.0 / nthr + while line = Timeout::timeout(timeout) { fd.get_line } + data << line + log.call name, line.chomp, type if name && (first_line || rand < log_prob) + first_line = false + end + fd.close + end + [$? == 0, data.chomp] + rescue Timeout::Error, Interrupt => e + if fd && !fd.closed? + killall fd.pid + fd.close + end + cleanup.call if cleanup + if e.is_a?(Timeout::Error) && tried < tries + 3.downto(1) do |countdown| + s = countdown > 1 ? 's' : '' + log.call name, "Timeout. Will retry in #{countdown} second#{s} ...", type + sleep 1 + end + log.call name, 'Retrying ...', type + retry + end + [false, e.is_a?(Interrupt) ? "Interrupted!" : "Timeout!"] + end + } + main = Thread.current + threads = [] + watcher = Thread.new { + if vim7 + while VIM::evaluate('getchar(1)') + sleep 0.1 + end + else + require 'io/console' # >= Ruby 1.9 + nil until IO.console.getch == 3.chr + end + mtx.synchronize do + running = false + threads.each { |t| t.raise Interrupt } unless vim7 + end + threads.each { |t| t.join rescue nil } + main.kill + } + refresh = Thread.new { + while true + mtx.synchronize do + break unless running + VIM::command('noautocmd normal! a') + end + sleep 0.2 + end + } if VIM::evaluate('s:mac_gui') == 1 + + clone_opt = VIM::evaluate('s:clone_opt').join(' ') + progress = VIM::evaluate('s:progress_opt(1)') + nthr.times do + mtx.synchronize do + threads << Thread.new { + while pair = take1.call + name = pair.first + dir, uri, tag = pair.last.values_at *%w[dir uri tag] + exists = File.directory? dir + ok, result = + if exists + chdir = "#{cd} #{iswin ? dir : esc(dir)}" + ret, data = bt.call "#{chdir} && git rev-parse --abbrev-ref HEAD 2>&1 && git config -f .git/config remote.origin.url", nil, nil, nil + current_uri = data.lines.to_a.last + if !ret + if data =~ /^Interrupted|^Timeout/ + [false, data] + else + [false, [data.chomp, "PlugClean required."].join($/)] + end + elsif !compare_git_uri(current_uri, uri) + [false, ["Invalid URI: #{current_uri}", + "Expected: #{uri}", + "PlugClean required."].join($/)] + else + if pull + log.call name, 'Updating ...', :update + fetch_opt = (tag && File.exist?(File.join(dir, '.git/shallow'))) ? '--depth 99999999' : '' + bt.call "#{chdir} && git fetch #{fetch_opt} #{progress} 2>&1", name, :update, nil + else + [true, skip] + end + end + else + d = esc dir.sub(%r{[\\/]+$}, '') + log.call name, 'Installing ...', :install + bt.call "git clone #{clone_opt unless tag} #{progress} #{uri} #{d} 2>&1", name, :install, proc { + FileUtils.rm_rf dir + } + end + mtx.synchronize { VIM::command("let s:update.new['#{name}'] = 1") } if !exists && ok + log.call name, result, ok + end + } if running + end + end + threads.each { |t| t.join rescue nil } + logh.call + refresh.kill if refresh + watcher.kill +EOF +endfunction + +function! s:shellesc_cmd(arg, script) + let escaped = substitute('"'.a:arg.'"', '[&|<>()@^!"]', '^&', 'g') + return substitute(escaped, '%', (a:script ? '%' : '^') . '&', 'g') +endfunction + +function! s:shellesc_ps1(arg) + return "'".substitute(escape(a:arg, '\"'), "'", "''", 'g')."'" +endfunction + +function! s:shellesc_sh(arg) + return "'".substitute(a:arg, "'", "'\\\\''", 'g')."'" +endfunction + +" Escape the shell argument based on the shell. +" Vim and Neovim's shellescape() are insufficient. +" 1. shellslash determines whether to use single/double quotes. +" Double-quote escaping is fragile for cmd.exe. +" 2. It does not work for powershell. +" 3. It does not work for *sh shells if the command is executed +" via cmd.exe (ie. cmd.exe /c sh -c command command_args) +" 4. It does not support batchfile syntax. +" +" Accepts an optional dictionary with the following keys: +" - shell: same as Vim/Neovim 'shell' option. +" If unset, fallback to 'cmd.exe' on Windows or 'sh'. +" - script: If truthy and shell is cmd.exe, escape for batchfile syntax. +function! plug#shellescape(arg, ...) + if a:arg =~# '^[A-Za-z0-9_/:.-]\+$' + return a:arg + endif + let opts = a:0 > 0 && type(a:1) == s:TYPE.dict ? a:1 : {} + let shell = get(opts, 'shell', s:is_win ? 'cmd.exe' : 'sh') + let script = get(opts, 'script', 1) + if shell =~# 'cmd\(\.exe\)\?$' + return s:shellesc_cmd(a:arg, script) + elseif s:is_powershell(shell) + return s:shellesc_ps1(a:arg) + endif + return s:shellesc_sh(a:arg) +endfunction + +function! s:glob_dir(path) + return map(filter(s:glob(a:path, '**'), 'isdirectory(v:val)'), 's:dirpath(v:val)') +endfunction + +function! s:progress_bar(line, bar, total) + call setline(a:line, '[' . s:lpad(a:bar, a:total) . ']') +endfunction + +function! s:compare_git_uri(a, b) + " See `git help clone' + " https:// [user@] github.com[:port] / junegunn/vim-plug [.git] + " [git@] github.com[:port] : junegunn/vim-plug [.git] + " file:// / junegunn/vim-plug [/] + " / junegunn/vim-plug [/] + let pat = '^\%(\w\+://\)\='.'\%([^@/]*@\)\='.'\([^:/]*\%(:[0-9]*\)\=\)'.'[:/]'.'\(.\{-}\)'.'\%(\.git\)\=/\?$' + let ma = matchlist(a:a, pat) + let mb = matchlist(a:b, pat) + return ma[1:2] ==# mb[1:2] +endfunction + +function! s:format_message(bullet, name, message) + if a:bullet != 'x' + return [printf('%s %s: %s', a:bullet, a:name, s:lastline(a:message))] + else + let lines = map(s:lines(a:message), '" ".v:val') + return extend([printf('x %s:', a:name)], lines) + endif +endfunction + +function! s:with_cd(cmd, dir, ...) + let script = a:0 > 0 ? a:1 : 1 + return printf('cd%s %s && %s', s:is_win ? ' /d' : '', plug#shellescape(a:dir, {'script': script}), a:cmd) +endfunction + +function! s:system(cmd, ...) + let batchfile = '' + try + let [sh, shellcmdflag, shrd] = s:chsh(1) + if type(a:cmd) == s:TYPE.list + " Neovim's system() supports list argument to bypass the shell + " but it cannot set the working directory for the command. + " Assume that the command does not rely on the shell. + if has('nvim') && a:0 == 0 + return system(a:cmd) + endif + let cmd = join(map(copy(a:cmd), 'plug#shellescape(v:val, {"shell": &shell, "script": 0})')) + if s:is_powershell(&shell) + let cmd = '& ' . cmd + endif + else + let cmd = a:cmd + endif + if a:0 > 0 + let cmd = s:with_cd(cmd, a:1, type(a:cmd) != s:TYPE.list) + endif + if s:is_win && type(a:cmd) != s:TYPE.list + let [batchfile, cmd] = s:batchfile(cmd) + endif + return system(cmd) + finally + let [&shell, &shellcmdflag, &shellredir] = [sh, shellcmdflag, shrd] + if s:is_win && filereadable(batchfile) + call delete(batchfile) + endif + endtry +endfunction + +function! s:system_chomp(...) + let ret = call('s:system', a:000) + return v:shell_error ? '' : substitute(ret, '\n$', '', '') +endfunction + +function! s:git_validate(spec, check_branch) + let err = '' + if isdirectory(a:spec.dir) + let result = [s:git_local_branch(a:spec.dir), s:git_origin_url(a:spec.dir)] + let remote = result[-1] + if empty(remote) + let err = join([remote, 'PlugClean required.'], "\n") + elseif !s:compare_git_uri(remote, a:spec.uri) + let err = join(['Invalid URI: '.remote, + \ 'Expected: '.a:spec.uri, + \ 'PlugClean required.'], "\n") + elseif a:check_branch && has_key(a:spec, 'commit') + let sha = s:git_revision(a:spec.dir) + if empty(sha) + let err = join(add(result, 'PlugClean required.'), "\n") + elseif !s:hash_match(sha, a:spec.commit) + let err = join([printf('Invalid HEAD (expected: %s, actual: %s)', + \ a:spec.commit[:6], sha[:6]), + \ 'PlugUpdate required.'], "\n") + endif + elseif a:check_branch + let current_branch = result[0] + " Check tag + let origin_branch = s:git_origin_branch(a:spec) + if has_key(a:spec, 'tag') + let tag = s:system_chomp('git describe --exact-match --tags HEAD 2>&1', a:spec.dir) + if a:spec.tag !=# tag && a:spec.tag !~ '\*' + let err = printf('Invalid tag: %s (expected: %s). Try PlugUpdate.', + \ (empty(tag) ? 'N/A' : tag), a:spec.tag) + endif + " Check branch + elseif origin_branch !=# current_branch + let err = printf('Invalid branch: %s (expected: %s). Try PlugUpdate.', + \ current_branch, origin_branch) + endif + if empty(err) + let [ahead, behind] = split(s:lastline(s:system([ + \ 'git', 'rev-list', '--count', '--left-right', + \ printf('HEAD...origin/%s', origin_branch) + \ ], a:spec.dir)), '\t') + if !v:shell_error && ahead + if behind + " Only mention PlugClean if diverged, otherwise it's likely to be + " pushable (and probably not that messed up). + let err = printf( + \ "Diverged from origin/%s (%d commit(s) ahead and %d commit(s) behind!\n" + \ .'Backup local changes and run PlugClean and PlugUpdate to reinstall it.', origin_branch, ahead, behind) + else + let err = printf("Ahead of origin/%s by %d commit(s).\n" + \ .'Cannot update until local changes are pushed.', + \ origin_branch, ahead) + endif + endif + endif + endif + else + let err = 'Not found' + endif + return [err, err =~# 'PlugClean'] +endfunction + +function! s:rm_rf(dir) + if isdirectory(a:dir) + return s:system(s:is_win + \ ? 'rmdir /S /Q '.plug#shellescape(a:dir) + \ : ['rm', '-rf', a:dir]) + endif +endfunction + +function! s:clean(force) + call s:prepare() + call append(0, 'Searching for invalid plugins in '.g:plug_home) + call append(1, '') + + " List of valid directories + let dirs = [] + let errs = {} + let [cnt, total] = [0, len(g:plugs)] + for [name, spec] in items(g:plugs) + if !s:is_managed(name) + call add(dirs, spec.dir) + else + let [err, clean] = s:git_validate(spec, 1) + if clean + let errs[spec.dir] = s:lines(err)[0] + else + call add(dirs, spec.dir) + endif + endif + let cnt += 1 + call s:progress_bar(2, repeat('=', cnt), total) + normal! 2G + redraw + endfor + + let allowed = {} + for dir in dirs + let allowed[s:dirpath(s:plug_fnamemodify(dir, ':h:h'))] = 1 + let allowed[dir] = 1 + for child in s:glob_dir(dir) + let allowed[child] = 1 + endfor + endfor + + let todo = [] + let found = sort(s:glob_dir(g:plug_home)) + while !empty(found) + let f = remove(found, 0) + if !has_key(allowed, f) && isdirectory(f) + call add(todo, f) + call append(line('$'), '- ' . f) + if has_key(errs, f) + call append(line('$'), ' ' . errs[f]) + endif + let found = filter(found, 'stridx(v:val, f) != 0') + end + endwhile + + 4 + redraw + if empty(todo) + call append(line('$'), 'Already clean.') + else + let s:clean_count = 0 + call append(3, ['Directories to delete:', '']) + redraw! + if a:force || s:ask_no_interrupt('Delete all directories?') + call s:delete([6, line('$')], 1) + else + call setline(4, 'Cancelled.') + nnoremap d :set opfunc=delete_opg@ + nmap dd d_ + xnoremap d :call delete_op(visualmode(), 1) + echo 'Delete the lines (d{motion}) to delete the corresponding directories' + endif + endif + 4 + setlocal nomodifiable +endfunction + +function! s:delete_op(type, ...) + call s:delete(a:0 ? [line("'<"), line("'>")] : [line("'["), line("']")], 0) +endfunction + +function! s:delete(range, force) + let [l1, l2] = a:range + let force = a:force + let err_count = 0 + while l1 <= l2 + let line = getline(l1) + if line =~ '^- ' && isdirectory(line[2:]) + execute l1 + redraw! + let answer = force ? 1 : s:ask('Delete '.line[2:].'?', 1) + let force = force || answer > 1 + if answer + let err = s:rm_rf(line[2:]) + setlocal modifiable + if empty(err) + call setline(l1, '~'.line[1:]) + let s:clean_count += 1 + else + delete _ + call append(l1 - 1, s:format_message('x', line[1:], err)) + let l2 += len(s:lines(err)) + let err_count += 1 + endif + let msg = printf('Removed %d directories.', s:clean_count) + if err_count > 0 + let msg .= printf(' Failed to remove %d directories.', err_count) + endif + call setline(4, msg) + setlocal nomodifiable + endif + endif + let l1 += 1 + endwhile +endfunction + +function! s:upgrade() + echo 'Downloading the latest version of vim-plug' + redraw + let tmp = s:plug_tempname() + let new = tmp . '/plug.vim' + + try + let out = s:system(['git', 'clone', '--depth', '1', s:plug_src, tmp]) + if v:shell_error + return s:err('Error upgrading vim-plug: '. out) + endif + + if readfile(s:me) ==# readfile(new) + echo 'vim-plug is already up-to-date' + return 0 + else + call rename(s:me, s:me . '.old') + call rename(new, s:me) + unlet g:loaded_plug + echo 'vim-plug has been upgraded' + return 1 + endif + finally + silent! call s:rm_rf(tmp) + endtry +endfunction + +function! s:upgrade_specs() + for spec in values(g:plugs) + let spec.frozen = get(spec, 'frozen', 0) + endfor +endfunction + +function! s:status() + call s:prepare() + call append(0, 'Checking plugins') + call append(1, '') + + let ecnt = 0 + let unloaded = 0 + let [cnt, total] = [0, len(g:plugs)] + for [name, spec] in items(g:plugs) + let is_dir = isdirectory(spec.dir) + if has_key(spec, 'uri') + if is_dir + let [err, _] = s:git_validate(spec, 1) + let [valid, msg] = [empty(err), empty(err) ? 'OK' : err] + else + let [valid, msg] = [0, 'Not found. Try PlugInstall.'] + endif + else + if is_dir + let [valid, msg] = [1, 'OK'] + else + let [valid, msg] = [0, 'Not found.'] + endif + endif + let cnt += 1 + let ecnt += !valid + " `s:loaded` entry can be missing if PlugUpgraded + if is_dir && get(s:loaded, name, -1) == 0 + let unloaded = 1 + let msg .= ' (not loaded)' + endif + call s:progress_bar(2, repeat('=', cnt), total) + call append(3, s:format_message(valid ? '-' : 'x', name, msg)) + normal! 2G + redraw + endfor + call setline(1, 'Finished. '.ecnt.' error(s).') + normal! gg + setlocal nomodifiable + if unloaded + echo "Press 'L' on each line to load plugin, or 'U' to update" + nnoremap L :call status_load(line('.')) + xnoremap L :call status_load(line('.')) + end +endfunction + +function! s:extract_name(str, prefix, suffix) + return matchstr(a:str, '^'.a:prefix.' \zs[^:]\+\ze:.*'.a:suffix.'$') +endfunction + +function! s:status_load(lnum) + let line = getline(a:lnum) + let name = s:extract_name(line, '-', '(not loaded)') + if !empty(name) + call plug#load(name) + setlocal modifiable + call setline(a:lnum, substitute(line, ' (not loaded)$', '', '')) + setlocal nomodifiable + endif +endfunction + +function! s:status_update() range + let lines = getline(a:firstline, a:lastline) + let names = filter(map(lines, 's:extract_name(v:val, "[x-]", "")'), '!empty(v:val)') + if !empty(names) + echo + execute 'PlugUpdate' join(names) + endif +endfunction + +function! s:is_preview_window_open() + silent! wincmd P + if &previewwindow + wincmd p + return 1 + endif +endfunction + +function! s:find_name(lnum) + for lnum in reverse(range(1, a:lnum)) + let line = getline(lnum) + if empty(line) + return '' + endif + let name = s:extract_name(line, '-', '') + if !empty(name) + return name + endif + endfor + return '' +endfunction + +function! s:preview_commit() + if b:plug_preview < 0 + let b:plug_preview = !s:is_preview_window_open() + endif + + let sha = matchstr(getline('.'), '^ \X*\zs[0-9a-f]\{7,9}') + if empty(sha) + let name = matchstr(getline('.'), '^- \zs[^:]*\ze:$') + if empty(name) + return + endif + let title = 'HEAD@{1}..' + let command = 'git diff --no-color HEAD@{1}' + else + let title = sha + let command = 'git show --no-color --pretty=medium '.sha + let name = s:find_name(line('.')) + endif + + if empty(name) || !has_key(g:plugs, name) || !isdirectory(g:plugs[name].dir) + return + endif + + if exists('g:plug_pwindow') && !s:is_preview_window_open() + execute g:plug_pwindow + execute 'e' title + else + execute 'pedit' title + wincmd P + endif + setlocal previewwindow filetype=git buftype=nofile bufhidden=wipe nobuflisted modifiable + let batchfile = '' + try + let [sh, shellcmdflag, shrd] = s:chsh(1) + let cmd = 'cd '.plug#shellescape(g:plugs[name].dir).' && '.command + if s:is_win + let [batchfile, cmd] = s:batchfile(cmd) + endif + execute 'silent %!' cmd + finally + let [&shell, &shellcmdflag, &shellredir] = [sh, shellcmdflag, shrd] + if s:is_win && filereadable(batchfile) + call delete(batchfile) + endif + endtry + setlocal nomodifiable + nnoremap q :q + wincmd p +endfunction + +function! s:section(flags) + call search('\(^[x-] \)\@<=[^:]\+:', a:flags) +endfunction + +function! s:format_git_log(line) + let indent = ' ' + let tokens = split(a:line, nr2char(1)) + if len(tokens) != 5 + return indent.substitute(a:line, '\s*$', '', '') + endif + let [graph, sha, refs, subject, date] = tokens + let tag = matchstr(refs, 'tag: [^,)]\+') + let tag = empty(tag) ? ' ' : ' ('.tag.') ' + return printf('%s%s%s%s%s (%s)', indent, graph, sha, tag, subject, date) +endfunction + +function! s:append_ul(lnum, text) + call append(a:lnum, ['', a:text, repeat('-', len(a:text))]) +endfunction + +function! s:diff() + call s:prepare() + call append(0, ['Collecting changes ...', '']) + let cnts = [0, 0] + let bar = '' + let total = filter(copy(g:plugs), 's:is_managed(v:key) && isdirectory(v:val.dir)') + call s:progress_bar(2, bar, len(total)) + for origin in [1, 0] + let plugs = reverse(sort(items(filter(copy(total), (origin ? '' : '!').'(has_key(v:val, "commit") || has_key(v:val, "tag"))')))) + if empty(plugs) + continue + endif + call s:append_ul(2, origin ? 'Pending updates:' : 'Last update:') + for [k, v] in plugs + let branch = s:git_origin_branch(v) + if len(branch) + let range = origin ? '..origin/'.branch : 'HEAD@{1}..' + let cmd = ['git', 'log', '--graph', '--color=never'] + if s:git_version_requirement(2, 10, 0) + call add(cmd, '--no-show-signature') + endif + call extend(cmd, ['--pretty=format:%x01%h%x01%d%x01%s%x01%cr', range]) + if has_key(v, 'rtp') + call extend(cmd, ['--', v.rtp]) + endif + let diff = s:system_chomp(cmd, v.dir) + if !empty(diff) + let ref = has_key(v, 'tag') ? (' (tag: '.v.tag.')') : has_key(v, 'commit') ? (' '.v.commit) : '' + call append(5, extend(['', '- '.k.':'.ref], map(s:lines(diff), 's:format_git_log(v:val)'))) + let cnts[origin] += 1 + endif + endif + let bar .= '=' + call s:progress_bar(2, bar, len(total)) + normal! 2G + redraw + endfor + if !cnts[origin] + call append(5, ['', 'N/A']) + endif + endfor + call setline(1, printf('%d plugin(s) updated.', cnts[0]) + \ . (cnts[1] ? printf(' %d plugin(s) have pending updates.', cnts[1]) : '')) + + if cnts[0] || cnts[1] + nnoremap (plug-preview) :silent! call preview_commit() + if empty(maparg("\", 'n')) + nmap (plug-preview) + endif + if empty(maparg('o', 'n')) + nmap o (plug-preview) + endif + endif + if cnts[0] + nnoremap X :call revert() + echo "Press 'X' on each block to revert the update" + endif + normal! gg + setlocal nomodifiable +endfunction + +function! s:revert() + if search('^Pending updates', 'bnW') + return + endif + + let name = s:find_name(line('.')) + if empty(name) || !has_key(g:plugs, name) || + \ input(printf('Revert the update of %s? (y/N) ', name)) !~? '^y' + return + endif + + call s:system('git reset --hard HEAD@{1} && git checkout '.plug#shellescape(g:plugs[name].branch).' --', g:plugs[name].dir) + setlocal modifiable + normal! "_dap + setlocal nomodifiable + echo 'Reverted' +endfunction + +function! s:snapshot(force, ...) abort + call s:prepare() + setf vim + call append(0, ['" Generated by vim-plug', + \ '" '.strftime("%c"), + \ '" :source this file in vim to restore the snapshot', + \ '" or execute: vim -S snapshot.vim', + \ '', '', 'PlugUpdate!']) + 1 + let anchor = line('$') - 3 + let names = sort(keys(filter(copy(g:plugs), + \'has_key(v:val, "uri") && isdirectory(v:val.dir)'))) + for name in reverse(names) + let sha = has_key(g:plugs[name], 'commit') ? g:plugs[name].commit : s:git_revision(g:plugs[name].dir) + if !empty(sha) + call append(anchor, printf("silent! let g:plugs['%s'].commit = '%s'", name, sha)) + redraw + endif + endfor + + if a:0 > 0 + let fn = s:plug_expand(a:1) + if filereadable(fn) && !(a:force || s:ask(a:1.' already exists. Overwrite?')) + return + endif + call writefile(getline(1, '$'), fn) + echo 'Saved as '.a:1 + silent execute 'e' s:esc(fn) + setf vim + endif +endfunction + +function! s:split_rtp() + return split(&rtp, '\\\@ %s (getcmdtype() == ":" && getcmdpos() <= %d) ? %s : %s', + \ a:key, 1+len(a:key), Single_quote(a:value), Single_quote(a:key)) +endfunction + +function! s:Single_quote(str) abort + return "'" . substitute(copy(a:str), "'", "''", 'g') . "'" +endfunction + +" Check the syntax group in the current cursor position, see +" https://stackoverflow.com/q/9464844/6064933 and +" https://jordanelver.co.uk/blog/2015/05/27/working-with-vim-colorschemes/ +function! utils#SynGroup() abort + if !exists('*synstack') + return + endif + echo map(synstack(line('.'), col('.')), 'synIDattr(v:val, "name")') +endfunction + +" Check if a colorscheme exists in runtimepath. +" The following two functions are inspired by https://stackoverflow.com/a/5703164/6064933. +function! utils#HasColorscheme(name) abort + let l:pat = printf('colors/%s.vim', a:name) + return !empty(globpath(&runtimepath, l:pat)) +endfunction + +" Custom fold expr, adapted from https://vi.stackexchange.com/a/9094/15292 +function! utils#VimFolds(lnum) abort + " get content of current line and the line below + let l:cur_line = getline(a:lnum) + let l:next_line = getline(a:lnum+1) + + if l:cur_line =~# '^"{' + return '>' . (matchend(l:cur_line, '"{*') - 1) + endif + + if l:cur_line ==# '' && (matchend(l:next_line, '"{*') - 1) == 1 + return 0 + endif + + return '=' +endfunction + +" Custom fold text, adapted from https://vi.stackexchange.com/a/3818/15292 +" and https://vi.stackexchange.com/a/6608/15292 +function! utils#MyFoldText() abort + let l:line = getline(v:foldstart) + let l:fold_line_num = v:foldend - v:foldstart + let l:fold_text = substitute(l:line, '^"{\+', '', 'g') + let l:fill_char_num = &textwidth - len(l:fold_text) - len(l:fold_line_num) - 10 + return printf('+%s%s %s (%s L)', repeat('-', 4), l:fold_text, repeat('-', l:fill_char_num), l:fold_line_num) +endfunction + +" Toggle cursor column +function! utils#ToggleCursorCol() abort + if &cursorcolumn + set nocursorcolumn + echo 'cursorcolumn: OFF' + else + set cursorcolumn + echo 'cursorcolumn: ON' + endif +endfunction + +function! utils#SwitchLine(src_line_idx, direction) abort + if a:direction ==# 'up' + if a:src_line_idx == 1 + return + endif + move-2 + elseif a:direction ==# 'down' + if a:src_line_idx == line('$') + return + endif + move+1 + endif +endfunction + +function! utils#MoveSelection(direction) abort + " only do this if previous mode is visual line mode. Once we press some keys in + " visual line mode, we will leave this mode. So the output of `mode()` will be + " `n` instead of `V`. We can use `visualmode()` instead to check the previous + " mode, see also https://stackoverflow.com/a/61486601/6064933 + if visualmode() !=# 'V' + return + endif + + let l:start_line = line("'<") + let l:end_line = line("'>") + let l:num_line = l:end_line - l:start_line + 1 + + if a:direction ==# 'up' + if l:start_line == 1 + " we can also directly use `normal gv`, see https://stackoverflow.com/q/9724123/6064933 + normal! gv + return + endif + silent execute printf('%s,%smove-2', l:start_line, l:end_line) + normal! gv + elseif a:direction ==# 'down' + if l:end_line == line('$') + normal! gv + return + endif + silent execute printf('%s,%smove+%s', l:start_line, l:end_line, l:num_line) + normal! gv + endif +endfunction + + +function! utils#Get_titlestr() abort + let l:title_str = '' + if g:is_linux + let l:title_str = hostname() . ' ' + endif + + let l:buf_path = expand('%:p:~') + let l:title_str = l:title_str . l:buf_path . ' ' + if &buflisted && l:buf_path != "" + let l:title_str = l:title_str . strftime('%Y-%m-%d %H:%M:%S%z', getftime(expand('%'))) + endif + + return l:title_str +endfunction + +" Output current time or unix timestamp in human-readable format. +function! utils#iso_time(timestamp) abort + " Get current datetime + if !a:timestamp + return strftime('%Y-%m-%d %H:%M:%S%z') + endif + + " this timestamp in expressed in milliseconds + if len(a:timestamp) == 13 + let l:timestamp = a:timestamp[:-4] + " this timestamp in expressed in microseconds + elseif len(a:timestamp) == 16 + let l:timestamp = a:timestamp[:-7] + else + let l:timestamp = a:timestamp + endif + return strftime('%Y-%m-%d %H:%M:%S%z', l:timestamp) + +endfunction + +" Check if we are inside a Git repo. +function! utils#Inside_git_repo() abort + let res = system('git rev-parse --is-inside-work-tree') + if match(res, 'true') == -1 + return v:false + else + " Manually trigger a special user autocmd InGitRepo (used lazyloading. + doautocmd User InGitRepo + return v:true + endif +endfunction + +function! utils#GetGitBranch() + let l:res = systemlist('git rev-parse --abbrev-ref HEAD')[0] + if match(l:res, 'fatal') != -1 + return '' + else + return l:res + endif +endfunction + +" Redirect command output to a register for later processing. +" Ref: https://stackoverflow.com/q/2573021/6064933 and https://unix.stackexchange.com/q/8101/221410 . +function! utils#CaptureCommandOutput(command) abort + let l:tmp = @m + redir @m + silent! execute a:command + redir END + + "create a scratch buffer for dumping the text, ref: https://vi.stackexchange.com/a/11311/15292. + tabnew | setlocal buftype=nofile bufhidden=wipe nobuflisted noswapfile + + let l:lines = split(@m, '\n') + call nvim_buf_set_lines(0, 0, 0, 0, l:lines) + + let @m = l:tmp +endfunction + +" Edit all files matching the given patterns. +function! utils#MultiEdit(patterns) abort + for p in a:patterns + for f in glob(p, 0, 1) + execute 'edit ' . f + endfor + endfor +endfunction diff --git a/.config/nvim/colors/bck.lua b/.config/nvim/colors/bck.lua new file mode 100644 index 0000000..7231418 --- /dev/null +++ b/.config/nvim/colors/bck.lua @@ -0,0 +1,289 @@ +-- Name: vim +-- Description: Vim's default color scheme +-- Author: Bram Moolenaar +-- Maintainer: The Vim Project +-- Website: https://github.com/vim/vim +-- License: Same as Vim +-- Last Updated: 2023 Aug 10 + +-- This is Vim's default color scheme. It doesn't define the Normal +-- highlighting, it uses whatever the colors used to be. + +vim.cmd.highlight('clear') +vim.g.colors_name = 'vim' + +local hi = function(name, val) + -- Force links + val.force = true + + -- Make sure that `cterm` attribute is not populated from `gui` + val.cterm = val.cterm or {} + + -- Define global highlight + vim.api.nvim_set_hl(0, name, val) +end + +--stylua: ignore start +-- General +hi('Normal', {}) + +hi('Conceal', { fg = 'LightGrey', bg = 'DarkGrey', ctermfg = 'LightGrey', ctermbg = 'DarkGrey' }) +hi('Cursor', {}) +hi('lCursor', {}) +hi('DiffText', { bg = 'Red', bold = true, ctermbg = 'Red', cterm = { bold = true } }) +hi('ErrorMsg', { fg = 'White', bg = 'Red', ctermfg = 'White', ctermbg = 'DarkRed' }) +hi('IncSearch', { reverse = true, cterm = { reverse = true } }) +hi('ModeMsg', { bold = true, cterm = { bold = true } }) +hi('NonText', { fg = 'Blue', bold = true, ctermfg = 'Blue' }) +hi('PmenuSbar', { bg = 'Grey', ctermbg = 'Grey' }) +hi('StatusLine', { reverse = true, bold = true, cterm = { reverse = true, bold = true }}) +hi('StatusLineNC', { reverse = true, cterm = { reverse = true } }) +hi('TabLineFill', { reverse = true, cterm = { reverse = true } }) +hi('TabLineSel', { bold = true, cterm = { bold = true } }) +hi('TermCursor', { reverse = true, cterm = { reverse = true } }) +hi('WinBar', { bold = true, cterm = { bold = true } }) +hi('WildMenu', { fg = 'Black', bg = 'Yellow', ctermfg = 'Black', ctermbg = 'Yellow' }) + +hi('VertSplit', { link = 'Normal' }) +hi('WinSeparator', { link = 'VertSplit' }) +hi('WinBarNC', { link = 'WinBar' }) +hi('EndOfBuffer', { link = 'NonText' }) +hi('LineNrAbove', { link = 'LineNr' }) +hi('LineNrBelow', { link = 'LineNr' }) +hi('QuickFixLine', { link = 'Search' }) +hi('CursorLineSign', { link = 'SignColumn' }) +hi('CursorLineFold', { link = 'FoldColumn' }) +hi('CurSearch', { link = 'Search' }) +hi('PmenuKind', { link = 'Pmenu' }) +hi('PmenuKindSel', { link = 'PmenuSel' }) +hi('PmenuExtra', { link = 'Pmenu' }) +hi('PmenuExtraSel', { link = 'PmenuSel' }) +hi('Substitute', { link = 'Search' }) +hi('Whitespace', { link = 'NonText' }) +hi('MsgSeparator', { link = 'StatusLine' }) +hi('NormalFloat', { link = 'Pmenu' }) +hi('FloatBorder', { link = 'WinSeparator' }) +hi('FloatTitle', { link = 'Title' }) +hi('FloatFooter', { link = 'Title' }) + +hi('FloatShadow', { bg = 'Black', blend=80 }) +hi('FloatShadowThrough', { bg = 'Black', blend=100 }) +hi('RedrawDebugNormal', { reverse = true, cterm = { reverse = true } }) +hi('RedrawDebugClear', { bg = 'Yellow', ctermbg = 'Yellow' }) +hi('RedrawDebugComposed', { bg = 'Green', ctermbg = 'Green' }) +hi('RedrawDebugRecompose', { bg = 'Red', ctermbg = 'Red' }) +hi('Error', { fg = 'White', bg = 'Red', ctermfg = 'White', ctermbg = 'Red' }) +hi('Todo', { fg = 'Blue', bg = 'Yellow', ctermfg = 'Black', ctermbg = 'Yellow' }) + +hi('String', { link = 'Constant' }) +hi('Character', { link = 'Constant' }) +hi('Number', { link = 'Constant' }) +hi('Boolean', { link = 'Constant' }) +hi('Float', { link = 'Number' }) +hi('Function', { link = 'Identifier' }) +hi('Conditional', { link = 'Statement' }) +hi('Repeat', { link = 'Statement' }) +hi('Label', { link = 'Statement' }) +hi('Operator', { link = 'Statement' }) +hi('Keyword', { link = 'Statement' }) +hi('Exception', { link = 'Statement' }) +hi('Include', { link = 'PreProc' }) +hi('Define', { link = 'PreProc' }) +hi('Macro', { link = 'PreProc' }) +hi('PreCondit', { link = 'PreProc' }) +hi('StorageClass', { link = 'Type' }) +hi('Structure', { link = 'Type' }) +hi('Typedef', { link = 'Type' }) +hi('Tag', { link = 'Special' }) +hi('SpecialChar', { link = 'Special' }) +hi('Delimiter', { link = 'Special' }) +hi('SpecialComment', { link = 'Special' }) +hi('Debug', { link = 'Special' }) + +hi('DiagnosticError', { fg = 'Red', ctermfg = 1 }) +hi('DiagnosticWarn', { fg = 'Orange', ctermfg = 3 }) +hi('DiagnosticInfo', { fg = 'LightBlue', ctermfg = 4 }) +hi('DiagnosticHint', { fg = 'LightGrey', ctermfg = 7 }) +hi('DiagnosticOk', { fg = 'LightGreen', ctermfg = 10 }) +hi('DiagnosticUnderlineError', { sp = 'Red', underline = true, cterm = { underline = true } }) +hi('DiagnosticUnderlineWarn', { sp = 'Orange', underline = true, cterm = { underline = true } }) +hi('DiagnosticUnderlineInfo', { sp = 'LightBlue', underline = true, cterm = { underline = true } }) +hi('DiagnosticUnderlineHint', { sp = 'LightGrey', underline = true, cterm = { underline = true } }) +hi('DiagnosticUnderlineOk', { sp = 'LightGreen', underline = true, cterm = { underline = true } }) +hi('DiagnosticVirtualTextError', { link = 'DiagnosticError' }) +hi('DiagnosticVirtualTextWarn', { link = 'DiagnosticWarn' }) +hi('DiagnosticVirtualTextInfo', { link = 'DiagnosticInfo' }) +hi('DiagnosticVirtualTextHint', { link = 'DiagnosticHint' }) +hi('DiagnosticVirtualTextOk', { link = 'DiagnosticOk' }) +hi('DiagnosticFloatingError', { link = 'DiagnosticError' }) +hi('DiagnosticFloatingWarn', { link = 'DiagnosticWarn' }) +hi('DiagnosticFloatingInfo', { link = 'DiagnosticInfo' }) +hi('DiagnosticFloatingHint', { link = 'DiagnosticHint' }) +hi('DiagnosticFloatingOk', { link = 'DiagnosticOk' }) +hi('DiagnosticSignError', { link = 'DiagnosticError' }) +hi('DiagnosticSignWarn', { link = 'DiagnosticWarn' }) +hi('DiagnosticSignInfo', { link = 'DiagnosticInfo' }) +hi('DiagnosticSignHint', { link = 'DiagnosticHint' }) +hi('DiagnosticSignOk', { link = 'DiagnosticOk' }) +hi('DiagnosticDeprecated', { sp = 'Red', strikethrough = true, cterm = { strikethrough = true } }) + +hi('DiagnosticUnnecessary', { link = 'Comment' }) +hi('LspInlayHint', { link = 'NonText' }) +hi('SnippetTabstop', { link = 'Visual' }) + +-- Text +hi('@markup.raw', { link = 'Comment' }) +hi('@markup.link', { link = 'Identifier' }) +hi('@markup.heading', { link = 'Title' }) +hi('@markup.link.url', { link = 'Underlined' }) +hi('@markup.underline', { link = 'Underlined' }) +hi('@comment.todo', { link = 'Todo' }) + +-- Miscs +hi('@comment', { link = 'Comment' }) +hi('@punctuation', { link = 'Delimiter' }) + +-- Constants +hi('@constant', { link = 'Constant' }) +hi('@constant.builtin', { link = 'Special' }) +hi('@constant.macro', { link = 'Define' }) +hi('@keyword.directive', { link = 'Define' }) +hi('@string', { link = 'String' }) +hi('@string.escape', { link = 'SpecialChar' }) +hi('@string.special', { link = 'SpecialChar' }) +hi('@character', { link = 'Character' }) +hi('@character.special', { link = 'SpecialChar' }) +hi('@number', { link = 'Number' }) +hi('@boolean', { link = 'Boolean' }) +hi('@number.float', { link = 'Float' }) + +-- Functions +hi('@function', { link = 'Function' }) +hi('@function.builtin', { link = 'Special' }) +hi('@function.macro', { link = 'Macro' }) +hi('@function.method', { link = 'Function' }) +hi('@variable.parameter', { link = 'Identifier' }) +hi('@variable.parameter.builtin', { link = 'Special' }) +hi('@variable.member', { link = 'Identifier' }) +hi('@property', { link = 'Identifier' }) +hi('@attribute', { link = 'Macro' }) +hi('@attribute.builtin', { link = 'Special' }) +hi('@constructor', { link = 'Special' }) + +-- Keywords +hi('@keyword.conditional', { link = 'Conditional' }) +hi('@keyword.repeat', { link = 'Repeat' }) +hi('@keyword.type', { link = 'Structure' }) +hi('@label', { link = 'Label' }) +hi('@operator', { link = 'Operator' }) +hi('@keyword', { link = 'Keyword' }) +hi('@keyword.exception', { link = 'Exception' }) + +hi('@variable', { link = 'Identifier' }) +hi('@type', { link = 'Type' }) +hi('@type.definition', { link = 'Typedef' }) +hi('@module', { link = 'Identifier' }) +hi('@keyword.import', { link = 'Include' }) +hi('@keyword.directive', { link = 'PreProc' }) +hi('@keyword.debug', { link = 'Debug' }) +hi('@tag', { link = 'Tag' }) +hi('@tag.builtin', { link = 'Special' }) + +-- LSP semantic tokens +hi('@lsp.type.class', { link = 'Structure' }) +hi('@lsp.type.comment', { link = 'Comment' }) +hi('@lsp.type.decorator', { link = 'Function' }) +hi('@lsp.type.enum', { link = 'Structure' }) +hi('@lsp.type.enumMember', { link = 'Constant' }) +hi('@lsp.type.function', { link = 'Function' }) +hi('@lsp.type.interface', { link = 'Structure' }) +hi('@lsp.type.macro', { link = 'Macro' }) +hi('@lsp.type.method', { link = 'Function' }) +hi('@lsp.type.namespace', { link = 'Structure' }) +hi('@lsp.type.parameter', { link = 'Identifier' }) +hi('@lsp.type.property', { link = 'Identifier' }) +hi('@lsp.type.struct', { link = 'Structure' }) +hi('@lsp.type.type', { link = 'Type' }) +hi('@lsp.type.typeParameter', { link = 'TypeDef' }) +hi('@lsp.type.variable', { link = 'Identifier' }) + +if vim.o.background == 'light' then + -- Default colors only used with a light background. + hi('ColorColumn', { bg = 'LightRed', ctermbg = 'LightRed' }) + hi('CursorColumn', { bg = 'Grey90', ctermbg = 'LightGrey' }) + hi('CursorLine', { bg = 'Grey90', cterm = { underline = true } }) + hi('CursorLineNr', { fg = 'Brown', bold = true, ctermfg = 'Brown', cterm = { underline = true } }) + hi('DiffAdd', { bg = 'LightBlue', ctermbg = 'LightBlue' }) + hi('DiffChange', { bg = 'LightMagenta', ctermbg = 'LightMagenta' }) + hi('DiffDelete', { fg = 'Blue', bg = 'LightCyan', bold = true, ctermfg = 'Blue', ctermbg = 'LightCyan' }) + hi('Directory', { fg = 'Blue', ctermfg = 'DarkBlue' }) + hi('FoldColumn', { fg = 'DarkBlue', bg = 'Grey', ctermfg = 'DarkBlue', ctermbg = 'Grey' }) + hi('Folded', { fg = 'DarkBlue', bg = 'LightGrey', ctermfg = 'DarkBlue', ctermbg = 'Grey' }) + hi('LineNr', { fg = 'Brown', ctermfg = 'Brown' }) + hi('MatchParen', { bg = 'Cyan', ctermbg = 'Cyan' }) + hi('MoreMsg', { fg = 'SeaGreen', bold = true, ctermfg = 'DarkGreen' }) + hi('Pmenu', { bg = 'LightMagenta', ctermfg = 'Black', ctermbg = 'LightMagenta' }) + hi('PmenuSel', { bg = 'Grey', ctermfg = 'Black', ctermbg = 'LightGrey' }) + hi('PmenuThumb', { bg = 'Black', ctermbg = 'Black' }) + hi('Question', { fg = 'SeaGreen', bold = true, ctermfg = 'DarkGreen' }) + hi('Search', { bg = 'Yellow', ctermbg = 'Yellow' }) + hi('SignColumn', { fg = 'DarkBlue', bg = 'Grey', ctermfg = 'DarkBlue', ctermbg = 'Grey' }) + hi('SpecialKey', { fg = 'Blue', ctermfg = 'DarkBlue' }) + hi('SpellBad', { sp = 'Red', undercurl = true, ctermbg = 'LightRed' }) + hi('SpellCap', { sp = 'Blue', undercurl = true, ctermbg = 'LightBlue' }) + hi('SpellLocal', { sp = 'DarkCyan', undercurl = true, ctermbg = 'Cyan' }) + hi('SpellRare', { sp = 'Magenta', undercurl = true, ctermbg = 'LightMagenta' }) + hi('TabLine', { bg = 'LightGrey', underline = true, ctermfg = 'Black', ctermbg = 'LightGrey', cterm = { underline = true } }) + hi('Title', { fg = 'Magenta', bold = true, ctermfg = 'DarkMagenta' }) + hi('Visual', { fg = 'Black', bg = 'LightGrey', ctermfg = 'Black', ctermbg = 'Grey' }) + hi('WarningMsg', { fg = 'Red', ctermfg = 'DarkRed' }) + hi('Comment', { fg = 'Blue', ctermfg = 'DarkBlue' }) + hi('Constant', { fg = 'Magenta', ctermfg = 'DarkRed' }) + hi('Special', { fg = '#6a5acd', ctermfg = 'DarkMagenta' }) + hi('Identifier', { fg = 'DarkCyan', ctermfg = 'DarkCyan' }) + hi('Statement', { fg = 'Brown', bold = true, ctermfg = 'Brown' }) + hi('PreProc', { fg = '#6a0dad', ctermfg = 'DarkMagenta' }) + hi('Type', { fg = 'SeaGreen', bold = true, ctermfg = 'DarkGreen' }) + hi('Underlined', { fg = 'SlateBlue', underline = true, ctermfg = 'DarkMagenta', cterm = { underline = true } }) + hi('Ignore', { ctermfg = 'White' }) +else + -- Default colors only used with a dark background. + hi('ColorColumn', { bg = 'DarkRed', ctermbg = 'DarkRed' }) + hi('CursorColumn', { bg = 'Grey40', ctermbg = 'DarkGrey' }) + hi('CursorLine', { bg = 'Grey40', cterm = { underline = true } }) + hi('CursorLineNr', { fg = 'Yellow', bold = true, ctermfg = 'Yellow', cterm = { underline = true } }) + hi('DiffAdd', { bg = 'DarkBlue', ctermbg = 'DarkBlue' }) + hi('DiffChange', { bg = 'DarkMagenta', ctermbg = 'DarkMagenta' }) + hi('DiffDelete', { fg = 'Blue', bg = 'DarkCyan', bold = true, ctermfg = 'Blue', ctermbg = 'DarkCyan' }) + hi('Directory', { fg = 'Cyan', ctermfg = 'LightCyan' }) + hi('FoldColumn', { fg = 'Cyan', bg = 'Grey', ctermfg = 'Cyan', ctermbg = 'DarkGrey' }) + hi('Folded', { fg = 'Cyan', bg = 'DarkGrey', ctermfg = 'Cyan', ctermbg = 'DarkGrey' }) + hi('LineNr', { fg = 'Yellow', ctermfg = 'Yellow' }) + hi('MatchParen', { bg = 'DarkCyan', ctermbg = 'DarkCyan' }) + hi('MoreMsg', { fg = 'SeaGreen', bold = true, ctermfg = 'LightGreen' }) + hi('Pmenu', { bg = 'Magenta', ctermfg = 'Black', ctermbg = 'Magenta' }) + hi('PmenuSel', { bg = 'DarkGrey', ctermfg = 'DarkGrey', ctermbg = 'Black' }) + hi('PmenuThumb', { bg = 'White', ctermbg = 'White' }) + hi('Question', { fg = 'Green', bold = true, ctermfg = 'LightGreen' }) + hi('Search', { fg = 'Black', bg = 'Yellow', ctermfg = 'Black', ctermbg = 'Yellow' }) + hi('SignColumn', { fg = 'Cyan', bg = 'Grey', ctermfg = 'Cyan', ctermbg = 'DarkGrey' }) + hi('SpecialKey', { fg = 'Cyan', ctermfg = 'LightBlue' }) + hi('SpellBad', { sp = 'Red', undercurl = true, ctermbg = 'Red' }) + hi('SpellCap', { sp = 'Blue', undercurl = true, ctermbg = 'Blue' }) + hi('SpellLocal', { sp = 'Cyan', undercurl = true, ctermbg = 'Cyan' }) + hi('SpellRare', { sp = 'Magenta', undercurl = true, ctermbg = 'Magenta' }) + hi('TabLine', { bg = 'DarkGrey', underline = true, ctermfg = 'White', ctermbg = 'DarkGrey', cterm = { underline = true } }) + hi('Title', { fg = 'Magenta', bold = true, ctermfg = 'LightMagenta' }) + hi('Visual', { fg = 'LightGrey', bg = '#575757', ctermfg = 'Black', ctermbg = 'Grey' }) + hi('WarningMsg', { fg = 'Red', ctermfg = 'LightRed' }) + hi('Comment', { fg = '#80a0ff', ctermfg = 'Cyan' }) + hi('Constant', { fg = '#ffa0a0', ctermfg = 'Magenta' }) + hi('Special', { fg = 'Orange', ctermfg = 'LightRed' }) + hi('Identifier', { fg = '#40ffff', ctermfg = 'Cyan', cterm = { bold = true } }) + hi('Statement', { fg = '#ffff60', bold = true, ctermfg = 'Yellow' }) + hi('PreProc', { fg = '#ff80ff', ctermfg = 'LightBlue' }) + hi('Type', { fg = '#60ff60', bold = true, ctermfg = 'LightGreen' }) + hi('Underlined', { fg = '#80a0ff', underline = true, ctermfg = 'LightBlue', cterm = { underline = true } }) + hi('Ignore', { ctermfg = 'Black' }) +end +--stylua: ignore end diff --git a/.config/nvim/colors/svratka.lua b/.config/nvim/colors/svratka.lua new file mode 100644 index 0000000..ac48f3b --- /dev/null +++ b/.config/nvim/colors/svratka.lua @@ -0,0 +1,289 @@ +-- Name: vim +-- Description: Vim's default color scheme +-- Author: Bram Moolenaar +-- Maintainer: The Vim Project +-- Website: https://github.com/vim/vim +-- License: Same as Vim +-- Last Updated: 2023 Aug 10 + +-- This is Vim's default color scheme. It doesn't define the Normal +-- highlighting, it uses whatever the colors used to be. + +vim.cmd.highlight('clear') +vim.g.colors_name = 'vim' + +local hi = function(name, val) + -- Force links + val.force = true + + -- Make sure that `cterm` attribute is not populated from `gui` + val.cterm = val.cterm or {} + + -- Define global highlight + vim.api.nvim_set_hl(0, name, val) +end + +--stylua: ignore start +-- General +hi('Normal', {}) + +hi('Conceal', { fg = 'LightGrey', bg = 'DarkGrey', ctermfg = 'LightGrey', ctermbg = 'DarkGrey' }) +hi('Cursor', {}) +hi('lCursor', {}) +hi('DiffText', { bg = 'Red', bold = true, ctermbg = 'Red', cterm = { bold = true } }) +hi('ErrorMsg', { fg = 'White', bg = 'Red', ctermfg = 'White', ctermbg = 'DarkRed' }) +hi('IncSearch', { reverse = true, cterm = { reverse = true } }) +hi('ModeMsg', { bold = true, cterm = { bold = true } }) +hi('NonText', { fg = 'Blue', bold = true, ctermfg = 'Blue' }) +hi('PmenuSbar', { bg = 'Grey', ctermbg = 'Grey' }) +hi('StatusLine', { reverse = true, bold = true, cterm = { reverse = true, bold = true }}) +hi('StatusLineNC', { reverse = true, cterm = { reverse = true } }) +hi('TabLineFill', { reverse = true, cterm = { reverse = true } }) +hi('TabLineSel', { bold = true, cterm = { bold = true } }) +hi('TermCursor', { reverse = true, cterm = { reverse = true } }) +hi('WinBar', { bold = true, cterm = { bold = true } }) +hi('WildMenu', { fg = 'Black', bg = 'Yellow', ctermfg = 'Black', ctermbg = 'Yellow' }) + +hi('VertSplit', { link = 'Normal' }) +hi('WinSeparator', { link = 'VertSplit' }) +hi('WinBarNC', { link = 'WinBar' }) +hi('EndOfBuffer', { link = 'NonText' }) +hi('LineNrAbove', { link = 'LineNr' }) +hi('LineNrBelow', { link = 'LineNr' }) +hi('QuickFixLine', { link = 'Search' }) +hi('CursorLineSign', { link = 'SignColumn' }) +hi('CursorLineFold', { link = 'FoldColumn' }) +hi('CurSearch', { link = 'Search' }) +hi('PmenuKind', { link = 'Pmenu' }) +hi('PmenuKindSel', { link = 'PmenuSel' }) +hi('PmenuExtra', { link = 'Pmenu' }) +hi('PmenuExtraSel', { link = 'PmenuSel' }) +hi('Substitute', { link = 'Search' }) +hi('Whitespace', { link = 'NonText' }) +hi('MsgSeparator', { link = 'StatusLine' }) +hi('NormalFloat', { link = 'Pmenu' }) +hi('FloatBorder', { link = 'WinSeparator' }) +hi('FloatTitle', { link = 'Title' }) +hi('FloatFooter', { link = 'Title' }) + +hi('FloatShadow', { bg = 'Black', blend=80 }) +hi('FloatShadowThrough', { bg = 'Black', blend=100 }) +hi('RedrawDebugNormal', { reverse = true, cterm = { reverse = true } }) +hi('RedrawDebugClear', { bg = 'Yellow', ctermbg = 'Yellow' }) +hi('RedrawDebugComposed', { bg = 'Green', ctermbg = 'Green' }) +hi('RedrawDebugRecompose', { bg = 'Red', ctermbg = 'Red' }) +hi('Error', { fg = 'White', bg = 'Red', ctermfg = 'White', ctermbg = 'Red' }) +hi('Todo', { fg = 'Blue', bg = 'Yellow', ctermfg = 'Black', ctermbg = 'Yellow' }) + +hi('String', { link = 'Constant' }) +hi('Character', { link = 'Constant' }) +hi('Number', { link = 'Constant' }) +hi('Boolean', { link = 'Constant' }) +hi('Float', { link = 'Number' }) +hi('Function', { link = 'Identifier' }) +hi('Conditional', { link = 'Statement' }) +hi('Repeat', { link = 'Statement' }) +hi('Label', { link = 'Statement' }) +hi('Operator', { link = 'Statement' }) +hi('Keyword', { link = 'Statement' }) +hi('Exception', { link = 'Statement' }) +hi('Include', { link = 'PreProc' }) +hi('Define', { link = 'PreProc' }) +hi('Macro', { link = 'PreProc' }) +hi('PreCondit', { link = 'PreProc' }) +hi('StorageClass', { link = 'Type' }) +hi('Structure', { link = 'Type' }) +hi('Typedef', { link = 'Type' }) +hi('Tag', { link = 'Special' }) +hi('SpecialChar', { link = 'Special' }) +hi('Delimiter', { link = 'Special' }) +hi('SpecialComment', { link = 'Special' }) +hi('Debug', { link = 'Special' }) + +hi('DiagnosticError', { fg = 'Red', ctermfg = 1 }) +hi('DiagnosticWarn', { fg = 'Orange', ctermfg = 3 }) +hi('DiagnosticInfo', { fg = 'LightBlue', ctermfg = 4 }) +hi('DiagnosticHint', { fg = 'LightGrey', ctermfg = 7 }) +hi('DiagnosticOk', { fg = 'LightGreen', ctermfg = 10 }) +hi('DiagnosticUnderlineError', { sp = 'Red', underline = true, cterm = { underline = true } }) +hi('DiagnosticUnderlineWarn', { sp = 'Orange', underline = true, cterm = { underline = true } }) +hi('DiagnosticUnderlineInfo', { sp = 'LightBlue', underline = true, cterm = { underline = true } }) +hi('DiagnosticUnderlineHint', { sp = 'LightGrey', underline = true, cterm = { underline = true } }) +hi('DiagnosticUnderlineOk', { sp = 'LightGreen', underline = true, cterm = { underline = true } }) +hi('DiagnosticVirtualTextError', { link = 'DiagnosticError' }) +hi('DiagnosticVirtualTextWarn', { link = 'DiagnosticWarn' }) +hi('DiagnosticVirtualTextInfo', { link = 'DiagnosticInfo' }) +hi('DiagnosticVirtualTextHint', { link = 'DiagnosticHint' }) +hi('DiagnosticVirtualTextOk', { link = 'DiagnosticOk' }) +hi('DiagnosticFloatingError', { link = 'DiagnosticError' }) +hi('DiagnosticFloatingWarn', { link = 'DiagnosticWarn' }) +hi('DiagnosticFloatingInfo', { link = 'DiagnosticInfo' }) +hi('DiagnosticFloatingHint', { link = 'DiagnosticHint' }) +hi('DiagnosticFloatingOk', { link = 'DiagnosticOk' }) +hi('DiagnosticSignError', { link = 'DiagnosticError' }) +hi('DiagnosticSignWarn', { link = 'DiagnosticWarn' }) +hi('DiagnosticSignInfo', { link = 'DiagnosticInfo' }) +hi('DiagnosticSignHint', { link = 'DiagnosticHint' }) +hi('DiagnosticSignOk', { link = 'DiagnosticOk' }) +hi('DiagnosticDeprecated', { sp = 'Red', strikethrough = true, cterm = { strikethrough = true } }) + +hi('DiagnosticUnnecessary', { link = 'Comment' }) +hi('LspInlayHint', { link = 'NonText' }) +hi('SnippetTabstop', { link = 'Visual' }) + +-- Text +hi('@markup.raw', { link = 'Comment' }) +hi('@markup.link', { link = 'Identifier' }) +hi('@markup.heading', { link = 'Title' }) +hi('@markup.link.url', { link = 'Underlined' }) +hi('@markup.underline', { link = 'Underlined' }) +hi('@comment.todo', { link = 'Todo' }) + +-- Miscs +hi('@comment', { link = 'Comment' }) +hi('@punctuation', { link = 'Delimiter' }) + +-- Constants +hi('@constant', { link = 'Constant' }) +hi('@constant.builtin', { link = 'Special' }) +hi('@constant.macro', { link = 'Define' }) +hi('@keyword.directive', { link = 'Define' }) +hi('@string', { link = 'String' }) +hi('@string.escape', { link = 'SpecialChar' }) +hi('@string.special', { link = 'SpecialChar' }) +hi('@character', { link = 'Character' }) +hi('@character.special', { link = 'SpecialChar' }) +hi('@number', { link = 'Number' }) +hi('@boolean', { link = 'Boolean' }) +hi('@number.float', { link = 'Float' }) + +-- Functions +hi('@function', { link = 'Function' }) +hi('@function.builtin', { link = 'Special' }) +hi('@function.macro', { link = 'Macro' }) +hi('@function.method', { link = 'Function' }) +hi('@variable.parameter', { link = 'Identifier' }) +hi('@variable.parameter.builtin', { link = 'Special' }) +hi('@variable.member', { link = 'Identifier' }) +hi('@property', { link = 'Identifier' }) +hi('@attribute', { link = 'Macro' }) +hi('@attribute.builtin', { link = 'Special' }) +hi('@constructor', { link = 'Special' }) + +-- Keywords +hi('@keyword.conditional', { link = 'Conditional' }) +hi('@keyword.repeat', { link = 'Repeat' }) +hi('@keyword.type', { link = 'Structure' }) +hi('@label', { link = 'Label' }) +hi('@operator', { link = 'Operator' }) +hi('@keyword', { link = 'Keyword' }) +hi('@keyword.exception', { link = 'Exception' }) + +hi('@variable', { link = 'Identifier' }) +hi('@type', { link = 'Type' }) +hi('@type.definition', { link = 'Typedef' }) +hi('@module', { link = 'Identifier' }) +hi('@keyword.import', { link = 'Include' }) +hi('@keyword.directive', { link = 'PreProc' }) +hi('@keyword.debug', { link = 'Debug' }) +hi('@tag', { link = 'Tag' }) +hi('@tag.builtin', { link = 'Special' }) + +-- LSP semantic tokens +hi('@lsp.type.class', { link = 'Structure' }) +hi('@lsp.type.comment', { link = 'Comment' }) +hi('@lsp.type.decorator', { link = 'Function' }) +hi('@lsp.type.enum', { link = 'Structure' }) +hi('@lsp.type.enumMember', { link = 'Constant' }) +hi('@lsp.type.function', { link = 'Function' }) +hi('@lsp.type.interface', { link = 'Structure' }) +hi('@lsp.type.macro', { link = 'Macro' }) +hi('@lsp.type.method', { link = 'Function' }) +hi('@lsp.type.namespace', { link = 'Structure' }) +hi('@lsp.type.parameter', { link = 'Identifier' }) +hi('@lsp.type.property', { link = 'Identifier' }) +hi('@lsp.type.struct', { link = 'Structure' }) +hi('@lsp.type.type', { link = 'Type' }) +hi('@lsp.type.typeParameter', { link = 'TypeDef' }) +hi('@lsp.type.variable', { link = 'Identifier' }) + +if vim.o.background == 'light' then + -- Default colors only used with a light background. + hi('ColorColumn', { bg = 'LightRed', ctermbg = 'LightRed' }) + hi('CursorColumn', { bg = 'Grey90', ctermbg = 'LightGrey' }) + hi('CursorLine', { bg = 'Grey90', cterm = { underline = true } }) + hi('CursorLineNr', { fg = 'Brown', bold = true, ctermfg = 'Brown', cterm = { underline = true } }) + hi('DiffAdd', { bg = 'LightBlue', ctermbg = 'LightBlue' }) + hi('DiffChange', { bg = 'LightMagenta', ctermbg = 'LightMagenta' }) + hi('DiffDelete', { fg = 'Blue', bg = 'LightCyan', bold = true, ctermfg = 'Blue', ctermbg = 'LightCyan' }) + hi('Directory', { fg = 'Blue', ctermfg = 'DarkBlue' }) + hi('FoldColumn', { fg = 'DarkBlue', bg = 'Grey', ctermfg = 'DarkBlue', ctermbg = 'Grey' }) + hi('Folded', { fg = 'DarkBlue', bg = 'LightGrey', ctermfg = 'DarkBlue', ctermbg = 'Grey' }) + hi('LineNr', { fg = 'Brown', ctermfg = 'Brown' }) + hi('MatchParen', { bg = 'Cyan', ctermbg = 'Cyan' }) + hi('MoreMsg', { fg = 'SeaGreen', bold = true, ctermfg = 'DarkGreen' }) + hi('Pmenu', { bg = 'LightMagenta', ctermfg = 'Black', ctermbg = 'LightMagenta' }) + hi('PmenuSel', { bg = 'Grey', ctermfg = 'Black', ctermbg = 'LightGrey' }) + hi('PmenuThumb', { bg = 'Black', ctermbg = 'Black' }) + hi('Question', { fg = 'SeaGreen', bold = true, ctermfg = 'DarkGreen' }) + hi('Search', { bg = 'Yellow', ctermbg = 'Yellow' }) + hi('SignColumn', { fg = 'DarkBlue', bg = 'Grey', ctermfg = 'DarkBlue', ctermbg = 'Grey' }) + hi('SpecialKey', { fg = 'Blue', ctermfg = 'DarkBlue' }) + hi('SpellBad', { sp = 'Red', undercurl = true, ctermbg = 'LightRed' }) + hi('SpellCap', { sp = 'Blue', undercurl = true, ctermbg = 'LightBlue' }) + hi('SpellLocal', { sp = 'DarkCyan', undercurl = true, ctermbg = 'Cyan' }) + hi('SpellRare', { sp = 'Magenta', undercurl = true, ctermbg = 'LightMagenta' }) + hi('TabLine', { bg = 'LightGrey', underline = true, ctermfg = 'Black', ctermbg = 'LightGrey', cterm = { underline = true } }) + hi('Title', { fg = 'Magenta', bold = true, ctermfg = 'DarkMagenta' }) + hi('Visual', { fg = 'Black', bg = 'LightGrey', ctermfg = 'Black', ctermbg = 'Grey' }) + hi('WarningMsg', { fg = 'Red', ctermfg = 'DarkRed' }) + hi('Comment', { fg = 'Blue', ctermfg = 'DarkBlue' }) + hi('Constant', { fg = 'Magenta', ctermfg = 'DarkRed' }) + hi('Special', { fg = '#6a5acd', ctermfg = 'DarkMagenta' }) + hi('Identifier', { fg = 'DarkCyan', ctermfg = 'DarkCyan' }) + hi('Statement', { fg = 'Brown', bold = true, ctermfg = 'Brown' }) + hi('PreProc', { fg = '#63aecb', ctermfg = 'SeaGreen' }) + hi('Type', { fg = 'SeaGreen', bold = true, ctermfg = 'DarkGreen' }) + hi('Underlined', { fg = 'SlateBlue', underline = true, ctermfg = 'DarkMagenta', cterm = { underline = true } }) + hi('Ignore', { ctermfg = 'White' }) +else + -- Default colors only used with a dark background. + hi('ColorColumn', { bg = 'DarkRed', ctermbg = 'DarkRed' }) + hi('CursorColumn', { bg = 'Grey40', ctermbg = 'DarkGrey' }) + hi('CursorLine', { bg = 'Grey40', cterm = { underline = true } }) + hi('CursorLineNr', { fg = 'Yellow', bold = true, ctermfg = 'Yellow', cterm = { underline = true } }) + hi('DiffAdd', { bg = 'DarkBlue', ctermbg = 'DarkBlue' }) + hi('DiffChange', { bg = 'DarkMagenta', ctermbg = 'DarkMagenta' }) + hi('DiffDelete', { fg = 'Blue', bg = 'DarkCyan', bold = true, ctermfg = 'Blue', ctermbg = 'DarkCyan' }) + hi('Directory', { fg = 'Cyan', ctermfg = 'LightCyan' }) + hi('FoldColumn', { fg = 'Cyan', bg = 'Grey', ctermfg = 'Cyan', ctermbg = 'DarkGrey' }) + hi('Folded', { fg = 'Cyan', bg = 'DarkGrey', ctermfg = 'Cyan', ctermbg = 'DarkGrey' }) + hi('LineNr', { fg = 'Yellow', ctermfg = 'Yellow' }) + hi('MatchParen', { bg = 'DarkCyan', ctermbg = 'DarkCyan' }) + hi('MoreMsg', { fg = 'SeaGreen', bold = true, ctermfg = 'LightGreen' }) + hi('Pmenu', { bg = 'Magenta', ctermfg = 'Black', ctermbg = 'Magenta' }) + hi('PmenuSel', { bg = 'DarkGrey', ctermfg = 'DarkGrey', ctermbg = 'Black' }) + hi('PmenuThumb', { bg = 'White', ctermbg = 'White' }) + hi('Question', { fg = 'Green', bold = true, ctermfg = 'LightGreen' }) + hi('Search', { fg = 'Black', bg = 'Yellow', ctermfg = 'Black', ctermbg = 'Yellow' }) + hi('SignColumn', { fg = 'Cyan', bg = 'Grey', ctermfg = 'Cyan', ctermbg = 'DarkGrey' }) + hi('SpecialKey', { fg = 'Cyan', ctermfg = 'LightBlue' }) + hi('SpellBad', { sp = 'Red', undercurl = true, ctermbg = 'Red' }) + hi('SpellCap', { sp = 'Blue', undercurl = true, ctermbg = 'Blue' }) + hi('SpellLocal', { sp = 'Cyan', undercurl = true, ctermbg = 'Cyan' }) + hi('SpellRare', { sp = 'Magenta', undercurl = true, ctermbg = 'Magenta' }) + hi('TabLine', { bg = 'DarkGrey', underline = true, ctermfg = 'White', ctermbg = 'DarkGrey', cterm = { underline = true } }) + hi('Title', { fg = 'Magenta', bold = true, ctermfg = 'LightMagenta' }) + hi('Visual', { fg = 'LightGrey', bg = '#575757', ctermfg = 'Black', ctermbg = 'Grey' }) + hi('WarningMsg', { fg = 'Red', ctermfg = 'LightRed' }) + hi('Comment', { fg = '#80a0ff', ctermfg = 'Cyan' }) + hi('Constant', { fg = '#ffa0a0', ctermfg = 'Magenta' }) + hi('Special', { fg = 'Orange', ctermfg = 'LightRed' }) + hi('Identifier', { fg = '#40ffff', ctermfg = 'Cyan', cterm = { bold = true } }) + hi('Statement', { fg = '#ffff60', bold = true, ctermfg = 'Yellow' }) + hi('PreProc', { fg = '#63aecb', ctermfg = 'LightBlue' }) + hi('Type', { fg = '#60ff60', bold = true, ctermfg = 'LightGreen' }) + hi('Underlined', { fg = '#63aecb', underline = true, ctermfg = 'LightBlue', cterm = { underline = true } }) + hi('Ignore', { ctermfg = 'Black' }) +end +--stylua: ignore end diff --git a/.config/nvim/init.lua b/.config/nvim/init.lua new file mode 100644 index 0000000..6603956 --- /dev/null +++ b/.config/nvim/init.lua @@ -0,0 +1,183 @@ +vim.cmd([[ +let mapleader ="," + +if ! filereadable(system('echo -n "$HOME/.config/nvim/autoload/plug.vim"')) + echo "Downloading junegunn/vim-plug to manage plugins..." + silent !mkdir -p $HOME/.config/nvim/autoload/ + silent !curl "https://raw.githubusercontent.com/junegunn/vim-plug/master/plug.vim" > $HOME/.config/nvim/autoload/plug.vim + autocmd VimEnter * PlugInstall +endif + +map ,, :keepp /<++>ca< +imap ,, :keepp /<++>ca< + +call plug#begin(system('echo -n "$HOME/.config/nvim/plugged"')) +Plug 'tpope/vim-surround' +Plug 'preservim/nerdtree' +Plug 'junegunn/goyo.vim' +Plug 'jreybert/vimagit' +Plug 'vimwiki/vimwiki' +Plug 'vim-airline/vim-airline' +Plug 'tpope/vim-commentary' +Plug 'ap/vim-css-color' +Plug 'iamcco/markdown-preview.nvim', { 'do': 'cd app && npx --yes yarn install' } +call plug#end() + + function OpenMarkdownPreview (url) + execute "silent ! firefox --new-window " . a:url + endfunction + let g:mkdp_browserfunc = 'OpenMarkdownPreview' + +set title +set bg=light +set go=a +set mouse=a +set nohlsearch +set clipboard+=unnamedplus +set noshowmode +set noruler +set laststatus=0 +set noshowcmd + +" Some basics: + nnoremap c "_c + set nocompatible + filetype plugin on + syntax on + set encoding=utf-8 + set number relativenumber +" Enable autocompletion: + set wildmode=longest,list,full +" Disables automatic commenting on newline: + autocmd FileType * setlocal formatoptions-=c formatoptions-=r formatoptions-=o +" Perform dot commands over visual blocks: + vnoremap . :normal . +" Goyo plugin makes text more readable when writing prose: + map f :Goyo \| set bg=light \| set linebreak +" Spell-check set to o, 'o' for 'orthography': + map o :setlocal spell! spelllang=en_us +" Splits open at the bottom and right, which is non-retarded, unlike vim defaults. + set splitbelow splitright + +" Nerd tree + map n :NERDTreeToggle + autocmd bufenter * if (winnr("$") == 1 && exists("b:NERDTree") && b:NERDTree.isTabTree()) | q | endif + if has('nvim') + let NERDTreeBookmarksFile = stdpath('data') . '/NERDTreeBookmarks' + else + let NERDTreeBookmarksFile = '~/.vim' . '/NERDTreeBookmarks' + endif + +" vimling: + nm d :call ToggleDeadKeys() + imap d :call ToggleDeadKeys()a + nm i :call ToggleIPA() + imap i :call ToggleIPA()a + nm q :call ToggleProse() + +" Shortcutting split navigation, saving a keypress: + map h + map j + map k + map l + +" Replace ex mode with gq + map Q gq + +" Check file in shellcheck: + map s :!clear && shellcheck -x % + +" Open my bibliography file in split + map b :vsp$BIB + map r :vsp$REFER + +" Replace all is aliased to S. + nnoremap S :%s//g + +" Compile document, be it groff/LaTeX/markdown/etc. + map c :w! \| !compiler "%:p" + +" Open corresponding .pdf/.html or preview + map p :!opout "%:p" + +" Runs a script that cleans out tex build files whenever I close out of a .tex file. + autocmd VimLeave *.tex !texclear % + +" Ensure files are read as what I want: + let g:vimwiki_ext2syntax = {'.Rmd': 'markdown', '.rmd': 'markdown','.md': 'markdown', '.markdown': 'markdown', '.mdown': 'markdown'} + map v :VimwikiIndex + let g:vimwiki_list = [{'path': '~/.local/share/nvim/vimwiki', 'syntax': 'markdown', 'ext': '.md'}] + autocmd BufRead,BufNewFile /tmp/calcurse*,~/.calcurse/notes/* set filetype=markdown + autocmd BufRead,BufNewFile *.ms,*.me,*.mom,*.man set filetype=groff + autocmd BufRead,BufNewFile *.tex set filetype=tex + +" Save file as sudo on files that require root permission + cabbrev w!! execute 'silent! write !sudo tee % >/dev/null' edit! + +" Enable Goyo by default for mutt writing + autocmd BufRead,BufNewFile /tmp/neomutt* let g:goyo_width=80 + autocmd BufRead,BufNewFile /tmp/neomutt* :Goyo | set bg=light + autocmd BufRead,BufNewFile /tmp/neomutt* map ZZ :Goyo\|x! + autocmd BufRead,BufNewFile /tmp/neomutt* map ZQ :Goyo\|q! + +" Automatically deletes all trailing whitespace and newlines at end of file on save. & reset cursor position + autocmd BufWritePre * let currPos = getpos(".") + autocmd BufWritePre * %s/\s\+$//e + autocmd BufWritePre * %s/\n\+\%$//e + autocmd BufWritePre *.[ch] %s/\%$/\r/e " add trailing newline for ANSI C standard + autocmd BufWritePre *neomutt* %s/^--$/-- /e " dash-dash-space signature delimiter in emails + autocmd BufWritePre * cal cursor(currPos[1], currPos[2]) + +" When shortcut files are updated, renew bash and ranger configs with new material: + autocmd BufWritePost bm-files,bm-dirs !shortcuts +" Run xrdb whenever Xdefaults or Xresources are updated. + autocmd BufRead,BufNewFile Xresources,Xdefaults,xresources,xdefaults set filetype=xdefaults + autocmd BufWritePost Xresources,Xdefaults,xresources,xdefaults !xrdb % +" Recompile dwmblocks on config edit. + autocmd BufWritePost ~/.local/src/dwmblocks/config.h !cd ~/.local/src/dwmblocks/; sudo make install && { killall -q dwmblocks;setsid -f dwmblocks } + +" Turns off highlighting on the bits of code that are changed, so the line that is changed is highlighted but the actual text that has changed stands out on the line and is readable. +if &diff + highlight! link DiffText MatchParen +endif + +" Function for toggling the bottom statusbar: +let s:hidden_all = 0 +function! ToggleHiddenAll() + if s:hidden_all == 0 + let s:hidden_all = 1 + set noshowmode + set noruler + set laststatus=0 + set noshowcmd + else + let s:hidden_all = 0 + set showmode + set ruler + set laststatus=2 + set showcmd + endif +endfunction +nnoremap h :call ToggleHiddenAll() +" Load command shortcuts generated from bm-dirs and bm-files via shortcuts script. +" Here leader is ";". +" So ":vs ;cfz" will expand into ":vs /home//.config/zsh/.zshrc" +" if typed fast without the timeout. +silent! source ~/.config/nvim/shortcuts.vim + +set background=dark + +colorscheme svratka +hi Normal guibg=NONE ctermbg=NONE +hi EndOfBuffer guibg=NONE ctermbg=NONE + + +set tabstop=4 +set shiftwidth=4 +set softtabstop=4 +set autoindent +set backspace=indent,eol,start +set hlsearch +set ignorecase +set smartcase +]]) diff --git a/.config/nvim/lua/colorschemes.lua b/.config/nvim/lua/colorschemes.lua new file mode 100644 index 0000000..9618b4d --- /dev/null +++ b/.config/nvim/lua/colorschemes.lua @@ -0,0 +1,83 @@ +--- This module will load a random colorscheme on nvim startup process. + +local utils = require("utils") + +local M = {} + +-- Colorscheme to its directory name mapping, because colorscheme repo name is not necessarily +-- the same as the colorscheme name itself. +M.colorscheme_conf = { + onedark = function() + vim.cmd([[colorscheme onedark]]) + end, + edge = function() + vim.g.edge_enable_italic = 1 + vim.g.edge_better_performance = 1 + + vim.cmd([[colorscheme edge]]) + end, + sonokai = function() + vim.g.sonokai_enable_italic = 1 + vim.g.sonokai_better_performance = 1 + + vim.cmd([[colorscheme sonokai]]) + end, + gruvbox_material = function() + -- foreground option can be material, mix, or original + vim.g.gruvbox_material_foreground = "material" + --background option can be hard, medium, soft + vim.g.gruvbox_material_background = "soft" + vim.g.gruvbox_material_enable_italic = 1 + vim.g.gruvbox_material_better_performance = 1 + + vim.cmd([[colorscheme gruvbox-material]]) + end, + everforest = function() + vim.g.everforest_enable_italic = 1 + vim.g.everforest_better_performance = 1 + + vim.cmd([[colorscheme everforest]]) + end, + nightfox = function() + vim.cmd([[colorscheme nordfox]]) + end, + catppuccin = function() + -- available option: latte, frappe, macchiato, mocha + vim.g.catppuccin_flavour = "frappe" + require("catppuccin").setup() + + vim.cmd([[colorscheme catppuccin]]) + end, + onedarkpro = function() + -- set colorscheme after options + vim.cmd('colorscheme onedark_vivid') + end, + material = function() + vim.g.material_style = "oceanic" + vim.cmd('colorscheme material') + end, +} + +--- Use a random colorscheme from the pre-defined list of colorschemes. +M.rand_colorscheme = function() + local colorscheme = utils.rand_element(vim.tbl_keys(M.colorscheme_conf)) + + if not vim.tbl_contains(vim.tbl_keys(M.colorscheme_conf), colorscheme) then + local msg = "Invalid colorscheme: " .. colorscheme + vim.notify(msg, vim.log.levels.ERROR, { title = "nvim-config" }) + + return + end + + -- Load the colorscheme and its settings + M.colorscheme_conf[colorscheme]() + + if vim.g.logging_level == "debug" then + local msg = "Colorscheme: " .. colorscheme + + vim.notify(msg, vim.log.levels.DEBUG, { title = "nvim-config" }) + end +end + +-- Load a random colorscheme +M.rand_colorscheme() diff --git a/.config/nvim/lua/config/indent-blankline.lua b/.config/nvim/lua/config/indent-blankline.lua new file mode 100644 index 0000000..9b210d7 --- /dev/null +++ b/.config/nvim/lua/config/indent-blankline.lua @@ -0,0 +1,36 @@ +local api = vim.api + +local exclude_ft = { "help", "git", "markdown", "snippets", "text", "gitconfig", "alpha", "dashboard" } + +require("ibl").setup { + indent = { + -- -- U+2502 may also be a good choice, it will be on the middle of cursor. + -- -- U+250A is also a good choice + char = "▏", + }, + scope = { + show_start = false, + show_end = false, + }, + exclude = { + filetypes = exclude_ft, + buftypes = { "terminal" }, + }, +} + +local gid = api.nvim_create_augroup("indent_blankline", { clear = true }) +api.nvim_create_autocmd("InsertEnter", { + pattern = "*", + group = gid, + command = "IBLDisable", +}) + +api.nvim_create_autocmd("InsertLeave", { + pattern = "*", + group = gid, + callback = function() + if not vim.tbl_contains(exclude_ft, vim.bo.filetype) then + vim.cmd([[IBLEnable]]) + end + end, +}) diff --git a/.config/nvim/lua/config/nvim-tree.lua b/.config/nvim/lua/config/nvim-tree.lua new file mode 100644 index 0000000..6bf653d --- /dev/null +++ b/.config/nvim/lua/config/nvim-tree.lua @@ -0,0 +1,108 @@ +local keymap = vim.keymap +local nvim_tree = require("nvim-tree") + +nvim_tree.setup { + auto_reload_on_write = true, + disable_netrw = false, + hijack_netrw = true, + hijack_cursor = false, + hijack_unnamed_buffer_when_opening = false, + open_on_tab = false, + sort_by = "name", + update_cwd = false, + view = { + width = 30, + side = "left", + preserve_window_proportions = false, + number = false, + relativenumber = false, + signcolumn = "yes", + }, + renderer = { + indent_markers = { + enable = false, + icons = { + corner = "└ ", + edge = "│ ", + none = " ", + }, + }, + icons = { + webdev_colors = true, + }, + }, + hijack_directories = { + enable = true, + auto_open = true, + }, + update_focused_file = { + enable = false, + update_cwd = false, + ignore_list = {}, + }, + system_open = { + cmd = "", + args = {}, + }, + diagnostics = { + enable = false, + show_on_dirs = false, + icons = { + hint = "", + info = "", + warning = "", + error = "", + }, + }, + filters = { + dotfiles = false, + custom = {}, + exclude = {}, + }, + git = { + enable = true, + ignore = true, + timeout = 400, + }, + actions = { + use_system_clipboard = true, + change_dir = { + enable = true, + global = false, + restrict_above_cwd = false, + }, + open_file = { + quit_on_open = false, + resize_window = false, + window_picker = { + enable = true, + chars = "ABCDEFGHIJKLMNOPQRSTUVWXYZ1234567890", + exclude = { + filetype = { "notify", "qf", "diff", "fugitive", "fugitiveblame" }, + buftype = { "nofile", "terminal", "help" }, + }, + }, + }, + }, + trash = { + cmd = "trash", + require_confirm = true, + }, + log = { + enable = false, + truncate = false, + types = { + all = false, + config = false, + copy_paste = false, + diagnostics = false, + git = false, + profile = false, + }, + }, +} + +keymap.set("n", "s", require("nvim-tree.api").tree.toggle, { + silent = true, + desc = "toggle nvim-tree", +}) diff --git a/.config/nvim/lua/config/statusline.lua b/.config/nvim/lua/config/statusline.lua new file mode 100644 index 0000000..0563ba6 --- /dev/null +++ b/.config/nvim/lua/config/statusline.lua @@ -0,0 +1,197 @@ +local fn = vim.fn + +local function spell() + if vim.o.spell then + return string.format("[SPELL]") + end + + return "" +end + +--- show indicator for Chinese IME +local function ime_state() + if vim.g.is_mac then + -- ref: https://github.com/vim-airline/vim-airline/blob/master/autoload/airline/extensions/xkblayout.vim#L11 + local layout = fn.libcall(vim.g.XkbSwitchLib, "Xkb_Switch_getXkbLayout", "") + + -- We can use `xkbswitch -g` on the command line to get current mode. + -- mode for macOS builtin pinyin IME: com.apple.inputmethod.SCIM.ITABC + -- mode for Rime: im.rime.inputmethod.Squirrel.Rime + local res = fn.match(layout, [[\v(Squirrel\.Rime|SCIM.ITABC)]]) + if res ~= -1 then + return "[CN]" + end + end + + return "" +end + +local function trailing_space() + if not vim.o.modifiable then + return "" + end + + local line_num = nil + + for i = 1, fn.line("$") do + local linetext = fn.getline(i) + -- To prevent invalid escape error, we wrap the regex string with `[[]]`. + local idx = fn.match(linetext, [[\v\s+$]]) + + if idx ~= -1 then + line_num = i + break + end + end + + local msg = "" + if line_num ~= nil then + msg = string.format("[%d]trailing", line_num) + end + + return msg +end + +local function mixed_indent() + if not vim.o.modifiable then + return "" + end + + local space_pat = [[\v^ +]] + local tab_pat = [[\v^\t+]] + local space_indent = fn.search(space_pat, "nwc") + local tab_indent = fn.search(tab_pat, "nwc") + local mixed = (space_indent > 0 and tab_indent > 0) + local mixed_same_line + if not mixed then + mixed_same_line = fn.search([[\v^(\t+ | +\t)]], "nwc") + mixed = mixed_same_line > 0 + end + if not mixed then + return "" + end + if mixed_same_line ~= nil and mixed_same_line > 0 then + return "MI:" .. mixed_same_line + end + local space_indent_cnt = fn.searchcount({ pattern = space_pat, max_count = 1e3 }).total + local tab_indent_cnt = fn.searchcount({ pattern = tab_pat, max_count = 1e3 }).total + if space_indent_cnt > tab_indent_cnt then + return "MI:" .. tab_indent + else + return "MI:" .. space_indent + end +end + +local diff = function() + local git_status = vim.b.gitsigns_status_dict + if git_status == nil then + return + end + + local modify_num = git_status.changed + local remove_num = git_status.removed + local add_num = git_status.added + + local info = { added = add_num, modified = modify_num, removed = remove_num } + -- vim.print(info) + return info +end + +local virtual_env = function() + -- only show virtual env for Python + if vim.bo.filetype ~= 'python' then + return "" + end + + local conda_env = os.getenv('CONDA_DEFAULT_ENV') + local venv_path = os.getenv('VIRTUAL_ENV') + + if venv_path == nil then + if conda_env == nil then + return "" + else + return string.format(" %s (conda)", conda_env) + end + else + local venv_name = vim.fn.fnamemodify(venv_path, ':t') + return string.format(" %s (venv)", venv_name) + end +end + +require("lualine").setup { + options = { + icons_enabled = true, + theme = "auto", + -- component_separators = { left = "", right = "" }, + -- section_separators = { left = "", right = "" }, + section_separators = "", + component_separators = "", + disabled_filetypes = {}, + always_divide_middle = true, + }, + sections = { + lualine_a = { "mode" }, + lualine_b = { + "branch", + { + "diff", + source = diff, + }, + { + virtual_env, + color = { fg = 'black', bg = "#F1CA81" } + } + }, + lualine_c = { + "filename", + { + ime_state, + color = { fg = "black", bg = "#f46868" }, + }, + { + spell, + color = { fg = "black", bg = "#a7c080" }, + }, + { + "diagnostics", + sources = { "nvim_diagnostic" }, + symbols = {error = '🆇 ', warn = '⚠️ ', info = 'ℹ️ ', hint = ' '}, + }, + }, + lualine_x = { + "encoding", + { + "fileformat", + symbols = { + unix = "unix", + dos = "win", + mac = "mac", + }, + }, + "filetype", + }, + lualine_y = { + "location", + }, + lualine_z = { + { + trailing_space, + color = "WarningMsg", + }, + { + mixed_indent, + color = "WarningMsg", + }, + }, + }, + inactive_sections = { + lualine_a = {}, + lualine_b = {}, + lualine_c = { "filename" }, + lualine_x = { "location" }, + lualine_y = {}, + lualine_z = {}, + }, + tabline = {}, + extensions = { "quickfix", "fugitive", "nvim-tree" }, +} diff --git a/.config/nvim/lua/custom-autocmd.lua b/.config/nvim/lua/custom-autocmd.lua new file mode 100644 index 0000000..21fa15c --- /dev/null +++ b/.config/nvim/lua/custom-autocmd.lua @@ -0,0 +1,105 @@ +local fn = vim.fn +local api = vim.api + +local utils = require("utils") + +-- Display a message when the current file is not in utf-8 format. +-- Note that we need to use `unsilent` command here because of this issue: +-- https://github.com/vim/vim/issues/4379 +api.nvim_create_autocmd({ "BufRead" }, { + pattern = "*", + group = api.nvim_create_augroup("non_utf8_file", { clear = true }), + callback = function() + if vim.bo.fileencoding ~= "utf-8" then + vim.notify("File not in UTF-8 format!", vim.log.levels.WARN, { title = "nvim-config" }) + end + end, +}) + +-- highlight yanked region, see `:h lua-highlight` +local yank_group = api.nvim_create_augroup("highlight_yank", { clear = true }) +api.nvim_create_autocmd({ "TextYankPost" }, { + pattern = "*", + group = yank_group, + callback = function() + vim.highlight.on_yank { higroup = "YankColor", timeout = 300 } + end, +}) + +api.nvim_create_autocmd({ "CursorMoved" }, { + pattern = "*", + group = yank_group, + callback = function() + vim.g.current_cursor_pos = vim.fn.getcurpos() + end, +}) + +api.nvim_create_autocmd("TextYankPost", { + pattern = "*", + group = yank_group, + callback = function(ev) + if vim.v.event.operator == 'y' then + vim.fn.setpos('.', vim.g.current_cursor_pos) + end + end, +}) + +-- Auto-create dir when saving a file, in case some intermediate directory does not exist +api.nvim_create_autocmd({ "BufWritePre" }, { + pattern = "*", + group = api.nvim_create_augroup("auto_create_dir", { clear = true }), + callback = function(ctx) + local dir = fn.fnamemodify(ctx.file, ":p:h") + utils.may_create_dir(dir) + end, +}) + +-- Automatically reload the file if it is changed outside of Nvim, see https://unix.stackexchange.com/a/383044/221410. +-- It seems that `checktime` does not work in command line. We need to check if we are in command +-- line before executing this command, see also https://vi.stackexchange.com/a/20397/15292 . +api.nvim_create_augroup("auto_read", { clear = true }) + +api.nvim_create_autocmd({ "FileChangedShellPost" }, { + pattern = "*", + group = "auto_read", + callback = function() + vim.notify("File changed on disk. Buffer reloaded!", vim.log.levels.WARN, { title = "nvim-config" }) + end, +}) + +api.nvim_create_autocmd({ "FocusGained", "CursorHold" }, { + pattern = "*", + group = "auto_read", + callback = function() + if fn.getcmdwintype() == "" then + vim.cmd("checktime") + end + end, +}) + +-- Resize all windows when we resize the terminal +api.nvim_create_autocmd("VimResized", { + group = api.nvim_create_augroup("win_autoresize", { clear = true }), + desc = "autoresize windows on resizing operation", + command = "wincmd =", +}) + +local function open_nvim_tree(data) + -- check if buffer is a directory + local directory = vim.fn.isdirectory(data.file) == 1 + + if not directory then + return + end + + -- create a new, empty buffer + vim.cmd.enew() + + -- wipe the directory buffer + vim.cmd.bw(data.buf) + + -- open the tree + require("nvim-tree.api").tree.open() +end + +vim.api.nvim_create_autocmd({ "VimEnter" }, { callback = open_nvim_tree }) diff --git a/.config/nvim/lua/mappings.lua b/.config/nvim/lua/mappings.lua new file mode 100644 index 0000000..2837650 --- /dev/null +++ b/.config/nvim/lua/mappings.lua @@ -0,0 +1,231 @@ +local keymap = vim.keymap +local api = vim.api +local uv = vim.loop + +-- Save key strokes (now we do not need to press shift to enter command mode). +keymap.set({ "n", "x" }, ";", ":") + +-- Turn the word under cursor to upper case +keymap.set("i", "", "viwUea") + +-- Turn the current word into title case +keymap.set("i", "", "b~lea") + +-- Paste non-linewise text above or below current line, see https://stackoverflow.com/a/1346777/6064933 +keymap.set("n", "p", "m`op``", { desc = "paste below current line" }) +keymap.set("n", "P", "m`Op``", { desc = "paste above current line" }) + +-- Shortcut for faster save and quit +keymap.set("n", "w", "update", { silent = true, desc = "save buffer" }) + +-- Saves the file if modified and quit +keymap.set("n", "q", "x", { silent = true, desc = "quit current window" }) + +-- Quit all opened buffers +keymap.set("n", "Q", "qa!", { silent = true, desc = "quit nvim" }) + +-- Navigation in the location and quickfix list +keymap.set("n", "[l", "lpreviouszv", { silent = true, desc = "previous location item" }) +keymap.set("n", "]l", "lnextzv", { silent = true, desc = "next location item" }) + +keymap.set("n", "[L", "lfirstzv", { silent = true, desc = "first location item" }) +keymap.set("n", "]L", "llastzv", { silent = true, desc = "last location item" }) + +keymap.set("n", "[q", "cpreviouszv", { silent = true, desc = "previous qf item" }) +keymap.set("n", "]q", "cnextzv", { silent = true, desc = "next qf item" }) + +keymap.set("n", "[Q", "cfirstzv", { silent = true, desc = "first qf item" }) +keymap.set("n", "]Q", "clastzv", { silent = true, desc = "last qf item" }) + +-- Close location list or quickfix list if they are present, see https://superuser.com/q/355325/736190 +keymap.set("n", [[\x]], "windo lclose cclose ", { + silent = true, + desc = "close qf and location list", +}) + +-- Delete a buffer, without closing the window, see https://stackoverflow.com/q/4465095/6064933 +keymap.set("n", [[\d]], "bprevious bdelete #", { + silent = true, + desc = "delete buffer", +}) + +-- Insert a blank line below or above current line (do not move the cursor), +-- see https://stackoverflow.com/a/16136133/6064933 +keymap.set("n", "o", "printf('m`%so``', v:count1)", { + expr = true, + desc = "insert line below", +}) + +keymap.set("n", "O", "printf('m`%sO``', v:count1)", { + expr = true, + desc = "insert line above", +}) + +-- Move the cursor based on physical lines, not the actual lines. +keymap.set("n", "j", "v:count == 0 ? 'gj' : 'j'", { expr = true }) +keymap.set("n", "k", "v:count == 0 ? 'gk' : 'k'", { expr = true }) +keymap.set("n", "^", "g^") +keymap.set("n", "0", "g0") + +-- Do not include white space characters when using $ in visual mode, +-- see https://vi.stackexchange.com/q/12607/15292 +keymap.set("x", "$", "g_") + +-- Go to start or end of line easier +keymap.set({ "n", "x" }, "H", "^") +keymap.set({ "n", "x" }, "L", "g_") + +-- Continuous visual shifting (does not exit Visual mode), `gv` means +-- to reselect previous visual area, see https://superuser.com/q/310417/736190 +keymap.set("x", "<", "", ">gv") + +-- Edit and reload nvim config file quickly +keymap.set("n", "ev", "tabnew $MYVIMRC tcd %:h", { + silent = true, + desc = "open init.lua", +}) + +keymap.set("n", "sv", function() + vim.cmd([[ + update $MYVIMRC + source $MYVIMRC + ]]) + vim.notify("Nvim config successfully reloaded!", vim.log.levels.INFO, { title = "nvim-config" }) +end, { + silent = true, + desc = "reload init.lua", +}) + +-- Reselect the text that has just been pasted, see also https://stackoverflow.com/a/4317090/6064933. +keymap.set("n", "v", "printf('`[%s`]', getregtype()[0])", { + expr = true, + desc = "reselect last pasted area", +}) + +-- Always use very magic mode for searching +keymap.set("n", "/", [[/\v]]) + +-- Search in selected region +-- xnoremap / :call feedkeys('/\%>'.(line("'<")-1).'l\%<'.(line("'>")+1)."l") + +-- Change current working directory locally and print cwd after that, +-- see https://vim.fandom.com/wiki/Set_working_directory_to_the_current_file +keymap.set("n", "cd", "lcd %:p:hpwd", { desc = "change cwd" }) + +-- Use Esc to quit builtin terminal +keymap.set("t", "", [[]]) + +-- Toggle spell checking +keymap.set("n", "", "set spell!", { desc = "toggle spell" }) +keymap.set("i", "", "set spell!", { desc = "toggle spell" }) + +-- Change text without putting it into the vim register, +-- see https://stackoverflow.com/q/54255/6064933 +keymap.set("n", "c", '"_c') +keymap.set("n", "C", '"_C') +keymap.set("n", "cc", '"_cc') +keymap.set("x", "c", '"_c') + +-- Remove trailing whitespace characters +keymap.set("n", "", "StripTrailingWhitespace", { desc = "remove trailing space" }) + +-- check the syntax group of current cursor position +keymap.set("n", "st", "call utils#SynGroup()", { desc = "check syntax group" }) + +-- Copy entire buffer. +keymap.set("n", "y", "%yank", { desc = "yank entire buffer" }) + +-- Toggle cursor column +keymap.set("n", "cl", "call utils#ToggleCursorCol()", { desc = "toggle cursor column" }) + +-- Move current line up and down +keymap.set("n", "", 'call utils#SwitchLine(line("."), "up")', { desc = "move line up" }) +keymap.set("n", "", 'call utils#SwitchLine(line("."), "down")', { desc = "move line down" }) + +-- Move current visual-line selection up and down +keymap.set("x", "", 'call utils#MoveSelection("up")', { desc = "move selection up" }) + +keymap.set("x", "", 'call utils#MoveSelection("down")', { desc = "move selection down" }) + +-- Replace visual selection with text in register, but not contaminate the register, +-- see also https://stackoverflow.com/q/10723700/6064933. +keymap.set("x", "p", '"_cp') + +-- Go to a certain buffer +keymap.set("n", "gb", 'call buf_utils#GoToBuffer(v:count, "forward")', { + desc = "go to buffer (forward)", +}) +keymap.set("n", "gB", 'call buf_utils#GoToBuffer(v:count, "backward")', { + desc = "go to buffer (backward)", +}) + +-- Switch windows +keymap.set("n", "", "h") +keymap.set("n", "", "l") +keymap.set("n", "", "k") +keymap.set("n", "", "j") + +-- Text objects for URL +keymap.set({ "x", "o" }, "iu", "call text_obj#URL()", { desc = "URL text object" }) + +-- Text objects for entire buffer +keymap.set({ "x", "o" }, "iB", ":call text_obj#Buffer()", { desc = "buffer text object" }) + +-- Do not move my cursor when joining lines. +keymap.set("n", "J", function() + vim.cmd([[ + normal! mzJ`z + delmarks z + ]]) +end, { + desc = "join lines without moving cursor", +}) + +keymap.set("n", "gJ", function() + -- we must use `normal!`, otherwise it will trigger recursive mapping + vim.cmd([[ + normal! mzgJ`z + delmarks z + ]]) +end, { + desc = "join lines without moving cursor", +}) + +-- Break inserted text into smaller undo units when we insert some punctuation chars. +local undo_ch = { ",", ".", "!", "?", ";", ":" } +for _, ch in ipairs(undo_ch) do + keymap.set("i", ch, ch .. "u") +end + +-- insert semicolon in the end +keymap.set("i", "", "miA;`ii") + +-- Go to the beginning and end of current line in insert mode quickly +keymap.set("i", "", "") +keymap.set("i", "", "") + +-- Go to beginning of command in command-line mode +keymap.set("c", "", "") + +-- Delete the character to the right of the cursor +keymap.set("i", "", "") + +keymap.set("n", "cb", function() + local cnt = 0 + local blink_times = 7 + local timer = uv.new_timer() + + timer:start(0, 100, vim.schedule_wrap(function() + vim.cmd[[ + set cursorcolumn! + set cursorline! + ]] + + if cnt == blink_times then + timer:close() + end + + cnt = cnt + 1 + end)) +end) diff --git a/.config/nvim/lua/plugin_specs.lua b/.config/nvim/lua/plugin_specs.lua new file mode 100644 index 0000000..a585ee1 --- /dev/null +++ b/.config/nvim/lua/plugin_specs.lua @@ -0,0 +1,503 @@ +local utils = require("utils") + +local lazypath = vim.fn.stdpath("data") .. "/lazy/lazy.nvim" + +if not vim.loop.fs_stat(lazypath) then + vim.fn.system { + "git", + "clone", + "--filter=blob:none", + "https://github.com/folke/lazy.nvim.git", + "--branch=stable", -- latest stable release + lazypath, + } +end +vim.opt.rtp:prepend(lazypath) + +-- check if firenvim is active +local firenvim_not_active = function() + return not vim.g.started_by_firenvim +end + +local plugin_specs = { + -- auto-completion engine + { + "hrsh7th/nvim-cmp", + -- event = 'InsertEnter', + event = "VeryLazy", + dependencies = { + "hrsh7th/cmp-nvim-lsp", + "onsails/lspkind-nvim", + "hrsh7th/cmp-path", + "hrsh7th/cmp-buffer", + "hrsh7th/cmp-omni", + "hrsh7th/cmp-emoji", + "quangnguyen30192/cmp-nvim-ultisnips", + }, + config = function() + require("config.nvim-cmp") + end, + }, + + { + "neovim/nvim-lspconfig", + event = { "BufRead", "BufNewFile" }, + config = function() + require("config.lsp") + end, + }, + + { + "nvim-treesitter/nvim-treesitter", + enabled = function() + if vim.g.is_mac then + return true + end + return false + end, + event = "VeryLazy", + build = ":TSUpdate", + config = function() + require("config.treesitter") + end, + }, + + -- Python indent (follows the PEP8 style) + { "Vimjas/vim-python-pep8-indent", ft = { "python" } }, + + -- Python-related text object + { "jeetsukumaran/vim-pythonsense", ft = { "python" } }, + + { "machakann/vim-swap", event = "VeryLazy" }, + + -- IDE for Lisp + -- 'kovisoft/slimv' + { + "vlime/vlime", + enabled = function() + if utils.executable("sbcl") then + return true + end + return false + end, + config = function(plugin) + vim.opt.rtp:append(plugin.dir .. "/vim") + end, + ft = { "lisp" }, + }, + + -- Super fast buffer jump + { + "smoka7/hop.nvim", + event = "VeryLazy", + config = function() + require("config.nvim_hop") + end, + }, + + -- Show match number and index for searching + { + "kevinhwang91/nvim-hlslens", + branch = "main", + keys = { "*", "#", "n", "N" }, + config = function() + require("config.hlslens") + end, + }, + { + "Yggdroot/LeaderF", + cmd = "Leaderf", + build = function() + if not vim.g.is_win then + vim.cmd(":LeaderfInstallCExtension") + end + end, + }, + "nvim-lua/plenary.nvim", + { + "nvim-telescope/telescope.nvim", + cmd = "Telescope", + dependencies = { + "nvim-telescope/telescope-symbols.nvim", + }, + }, + { + "lukas-reineke/headlines.nvim", + dependencies = "nvim-treesitter/nvim-treesitter", + config = true, -- or `opts = {}` + }, + -- A list of colorscheme plugin you may want to try. Find what suits you. + { "navarasu/onedark.nvim", lazy = true }, + { "sainnhe/edge", lazy = true }, + { "sainnhe/sonokai", lazy = true }, + { "sainnhe/gruvbox-material", lazy = true }, + { "sainnhe/everforest", lazy = true }, + { "EdenEast/nightfox.nvim", lazy = true }, + { "catppuccin/nvim", name = "catppuccin", lazy = true }, + { "olimorris/onedarkpro.nvim", lazy = true }, + { "marko-cerovac/material.nvim", lazy = true }, + + { "nvim-tree/nvim-web-devicons", event = "VeryLazy" }, + + { + "nvim-lualine/lualine.nvim", + event = "VeryLazy", + cond = firenvim_not_active, + config = function() + require("config.statusline") + end, + }, + + { + "akinsho/bufferline.nvim", + event = { "BufEnter" }, + cond = firenvim_not_active, + config = function() + require("config.bufferline") + end, + }, + + -- fancy start screen + { + "nvimdev/dashboard-nvim", + cond = firenvim_not_active, + config = function() + require("config.dashboard-nvim") + end, + }, + + { + "lukas-reineke/indent-blankline.nvim", + event = "VeryLazy", + main = 'ibl', + config = function() + require("config.indent-blankline") + end, + }, + + -- Highlight URLs inside vim + { "itchyny/vim-highlighturl", event = "VeryLazy" }, + + -- notification plugin + { + "rcarriga/nvim-notify", + event = "VeryLazy", + config = function() + require("config.nvim-notify") + end, + }, + + -- For Windows and Mac, we can open an URL in the browser. For Linux, it may + -- not be possible since we maybe in a server which disables GUI. + { + "tyru/open-browser.vim", + enabled = function() + if vim.g.is_win or vim.g.is_mac then + return true + else + return false + end + end, + event = "VeryLazy", + }, + + -- Only install these plugins if ctags are installed on the system + -- show file tags in vim window + { + "liuchengxu/vista.vim", + enabled = function() + if utils.executable("ctags") then + return true + else + return false + end + end, + cmd = "Vista", + }, + + -- Snippet engine and snippet template + { "SirVer/ultisnips", dependencies = { + "honza/vim-snippets", + }, event = "InsertEnter" }, + + -- Automatic insertion and deletion of a pair of characters + { "Raimondi/delimitMate", event = "InsertEnter" }, + + -- Comment plugin + { "tpope/vim-commentary", event = "VeryLazy" }, + + -- Multiple cursor plugin like Sublime Text? + -- 'mg979/vim-visual-multi' + + -- Autosave files on certain events + { "907th/vim-auto-save", event = "InsertEnter" }, + + -- Show undo history visually + { "simnalamburt/vim-mundo", cmd = { "MundoToggle", "MundoShow" } }, + + -- better UI for some nvim actions + { "stevearc/dressing.nvim" }, + + -- Manage your yank history + { + "gbprod/yanky.nvim", + cmd = { "YankyRingHistory" }, + config = function() + require("config.yanky") + end, + }, + + -- Handy unix command inside Vim (Rename, Move etc.) + { "tpope/vim-eunuch", cmd = { "Rename", "Delete" } }, + + -- Repeat vim motions + { "tpope/vim-repeat", event = "VeryLazy" }, + + { "nvim-zh/better-escape.vim", event = { "InsertEnter" } }, + + { + "lyokha/vim-xkbswitch", + enabled = function() + if vim.g.is_mac and utils.executable("xkbswitch") then + return true + end + return false + end, + event = { "InsertEnter" }, + }, + + { + "Neur1n/neuims", + enabled = function() + if vim.g.is_win then + return true + end + return false + end, + event = { "InsertEnter" }, + }, + + -- Auto format tools + { "sbdchd/neoformat", cmd = { "Neoformat" } }, + + -- Git command inside vim + { + "tpope/vim-fugitive", + event = "User InGitRepo", + config = function() + require("config.fugitive") + end, + }, + + -- Better git log display + { "rbong/vim-flog", cmd = { "Flog" } }, + { "akinsho/git-conflict.nvim", version = "*", config = true }, + { + "ruifm/gitlinker.nvim", + event = "User InGitRepo", + config = function() + require("config.git-linker") + end, + }, + + -- Show git change (change, delete, add) signs in vim sign column + { + "lewis6991/gitsigns.nvim", + config = function() + require("config.gitsigns") + end, + }, + + -- Better git commit experience + { "rhysd/committia.vim", lazy = true }, + + { + "sindrets/diffview.nvim" + }, + + { + "kevinhwang91/nvim-bqf", + ft = "qf", + config = function() + require("config.bqf") + end, + }, + + -- Another markdown plugin + { "preservim/vim-markdown", ft = { "markdown" } }, + + -- Faster footnote generation + { "vim-pandoc/vim-markdownfootnotes", ft = { "markdown" } }, + + -- Vim tabular plugin for manipulate tabular, required by markdown plugins + { "godlygeek/tabular", cmd = { "Tabularize" } }, + + -- Markdown previewing (only for Mac and Windows) + { + "iamcco/markdown-preview.nvim", + enabled = function() + if vim.g.is_win or vim.g.is_mac then + return true + end + return false + end, + build = "cd app && npm install", + ft = { "markdown" }, + }, + + { + "folke/zen-mode.nvim", + cmd = "ZenMode", + config = function() + require("config.zen-mode") + end, + }, + + { + "rhysd/vim-grammarous", + enabled = function() + if vim.g.is_mac then + return true + end + return false + end, + ft = { "markdown" }, + }, + + { "chrisbra/unicode.vim", event = "VeryLazy" }, + + -- Additional powerful text object for vim, this plugin should be studied + -- carefully to use its full power + { "wellle/targets.vim", event = "VeryLazy" }, + + -- Plugin to manipulate character pairs quickly + { "machakann/vim-sandwich", event = "VeryLazy" }, + + -- Add indent object for vim (useful for languages like Python) + { "michaeljsmith/vim-indent-object", event = "VeryLazy" }, + + -- Only use these plugin on Windows and Mac and when LaTeX is installed + { + "lervag/vimtex", + enabled = function() + if utils.executable("latex") then + return true + end + return false + end, + ft = { "tex" }, + }, + + -- Since tmux is only available on Linux and Mac, we only enable these plugins + -- for Linux and Mac + -- .tmux.conf syntax highlighting and setting check + { + "tmux-plugins/vim-tmux", + enabled = function() + if utils.executable("tmux") then + return true + end + return false + end, + ft = { "tmux" }, + }, + + -- Modern matchit implementation + { "andymass/vim-matchup", event = "BufRead" }, + { "tpope/vim-scriptease", cmd = { "Scriptnames", "Message", "Verbose" } }, + + -- Asynchronous command execution + { "skywind3000/asyncrun.vim", lazy = true, cmd = { "AsyncRun" } }, + { "cespare/vim-toml", ft = { "toml" }, branch = "main" }, + + -- Edit text area in browser using nvim + { + "glacambre/firenvim", + enabled = function() + if vim.g.is_win or vim.g.is_mac then + return true + end + return false + end, + build = function() + vim.fn["firenvim#install"](0) + end, + lazy = true, + }, + + -- Debugger plugin + { + "sakhnik/nvim-gdb", + enabled = function() + if vim.g.is_win or vim.g.is_linux then + return true + end + return false + end, + build = { "bash install.sh" }, + lazy = true, + }, + + -- Session management plugin + { "tpope/vim-obsession", cmd = "Obsession" }, + + { + "ojroques/vim-oscyank", + enabled = function() + if vim.g.is_linux then + return true + end + return false + end, + cmd = { "OSCYank", "OSCYankReg" }, + }, + + -- The missing auto-completion for cmdline! + { + "gelguy/wilder.nvim", + build = ":UpdateRemotePlugins", + }, + + -- showing keybindings + { + "folke/which-key.nvim", + event = "VeryLazy", + config = function() + require("config.which-key") + end, + }, + + -- show and trim trailing whitespaces + { "jdhao/whitespace.nvim", event = "VeryLazy" }, + + -- file explorer + { + "nvim-tree/nvim-tree.lua", + keys = { "s" }, + dependencies = { "nvim-tree/nvim-web-devicons" }, + config = function() + require("config.nvim-tree") + end, + }, + + { "ii14/emmylua-nvim", ft = "lua" }, + { + "j-hui/fidget.nvim", + event = "VeryLazy", + tag = "legacy", + config = function() + require("config.fidget-nvim") + end, + }, +} + +-- configuration for lazy itself. +local lazy_opts = { + ui = { + border = "rounded", + title = "Plugin Manager", + title_pos = "center", + }, +} + +require("lazy").setup(plugin_specs, lazy_opts) diff --git a/.config/nvim/spell/cs.utf-8.add b/.config/nvim/spell/cs.utf-8.add new file mode 100644 index 0000000..df50541 --- /dev/null +++ b/.config/nvim/spell/cs.utf-8.add @@ -0,0 +1 @@ +podrobna diff --git a/.config/nvim/spell/cs.utf-8.add.spl b/.config/nvim/spell/cs.utf-8.add.spl new file mode 100644 index 0000000..578ecd9 Binary files /dev/null and b/.config/nvim/spell/cs.utf-8.add.spl differ diff --git a/.config/nvim/spell/cs.utf-8.spl b/.config/nvim/spell/cs.utf-8.spl new file mode 100644 index 0000000..e84cb6b Binary files /dev/null and b/.config/nvim/spell/cs.utf-8.spl differ diff --git a/.config/nvim/spell/en.utf-8.add b/.config/nvim/spell/en.utf-8.add new file mode 100644 index 0000000..0022d5b --- /dev/null +++ b/.config/nvim/spell/en.utf-8.add @@ -0,0 +1,29 @@ +DL +E2E +eMBB +eSIM +GNSS +GPIO +HRLLC +I2C +IoT +LPWAN +LTE +mMTC +MQTT +PDM +PIR +PWM +RTC +SiP +SMA +SPI +URLLC +WDT +FSPL +Czechia +Silesian +GSM +3GPP +nRF9160 +LTE diff --git a/.config/nvim/viml_conf/autocommands.vim b/.config/nvim/viml_conf/autocommands.vim new file mode 100644 index 0000000..0fad842 --- /dev/null +++ b/.config/nvim/viml_conf/autocommands.vim @@ -0,0 +1,123 @@ +" Do not use smart case in command line mode, extracted from https://vi.stackexchange.com/a/16511/15292. +augroup dynamic_smartcase + autocmd! + autocmd CmdLineEnter : set nosmartcase + autocmd CmdLineLeave : set smartcase +augroup END + +augroup term_settings + autocmd! + " Do not use number and relative number for terminal inside nvim + autocmd TermOpen * setlocal norelativenumber nonumber + " Go to insert mode by default to start typing command + autocmd TermOpen * startinsert +augroup END + +" More accurate syntax highlighting? (see `:h syn-sync`) +augroup accurate_syn_highlight + autocmd! + autocmd BufEnter * :syntax sync fromstart +augroup END + +" Return to last cursor position when opening a file +augroup resume_cursor_position + autocmd! + autocmd BufReadPost * call s:resume_cursor_position() +augroup END + +" Only resume last cursor position when there is no go-to-line command (something like '+23'). +function s:resume_cursor_position() abort + if line("'\"") > 1 && line("'\"") <= line("$") && &ft !~# 'commit' + let l:args = v:argv " command line arguments + for l:cur_arg in l:args + " Check if a go-to-line command is given. + let idx = match(l:cur_arg, '\v^\+(\d){1,}$') + if idx != -1 + return + endif + endfor + + execute "normal! g`\"zvzz" + endif +endfunction + +augroup numbertoggle + autocmd! + autocmd BufEnter,FocusGained,InsertLeave,WinEnter * if &nu | set rnu | endif + autocmd BufLeave,FocusLost,InsertEnter,WinLeave * if &nu | set nornu | endif +augroup END + +" Define or override some highlight groups +augroup custom_highlight + autocmd! + autocmd ColorScheme * call s:custom_highlight() +augroup END + +function! s:custom_highlight() abort + " For yank highlight + highlight YankColor ctermfg=59 ctermbg=41 guifg=#34495E guibg=#2ECC71 + + " For cursor colors + highlight Cursor cterm=bold gui=bold guibg=#00c918 guifg=black + highlight Cursor2 guifg=red guibg=red + + " For floating windows border highlight + highlight FloatBorder guifg=LightGreen guibg=NONE + + " highlight for matching parentheses + highlight MatchParen cterm=bold,underline gui=bold,underline +endfunction + +augroup auto_close_win + autocmd! + autocmd BufEnter * call s:quit_current_win() +augroup END + +" Quit Nvim if we have only one window, and its filetype match our pattern. +function! s:quit_current_win() abort + let l:quit_filetypes = ['qf', 'vista', 'NvimTree'] + + let l:should_quit = v:true + + let l:tabwins = nvim_tabpage_list_wins(0) + for w in l:tabwins + let l:buf = nvim_win_get_buf(w) + let l:bf = getbufvar(l:buf, '&filetype') + + if index(l:quit_filetypes, l:bf) == -1 + let l:should_quit = v:false + endif + endfor + + if l:should_quit + qall + endif +endfunction + +augroup git_repo_check + autocmd! + autocmd VimEnter,DirChanged * call utils#Inside_git_repo() +augroup END + +" ref: https://vi.stackexchange.com/a/169/15292 +function! s:handle_large_file() abort + let g:large_file = 10485760 " 10MB + let f = expand("") + + if getfsize(f) > g:large_file || getfsize(f) == -2 + set eventignore+=all + " turning off relative number helps a lot + set norelativenumber + setlocal noswapfile bufhidden=unload buftype=nowrite undolevels=-1 + else + set eventignore-=all relativenumber + endif +endfunction + +augroup LargeFile + autocmd! + autocmd BufReadPre * call s:handle_large_file() +augroup END + +" Load auto-command defined in Lua +lua require("custom-autocmd") diff --git a/.config/nvim/viml_conf/options.vim b/.config/nvim/viml_conf/options.vim new file mode 100644 index 0000000..98fb3a6 --- /dev/null +++ b/.config/nvim/viml_conf/options.vim @@ -0,0 +1,178 @@ +scriptencoding utf-8 + +" change fillchars for folding, vertical split, end of buffer, and message separator +set fillchars=fold:\ ,vert:\│,eob:\ ,msgsep:‾,diff:╱ + +" Split window below/right when creating horizontal/vertical windows +set splitbelow splitright + +" Time in milliseconds to wait for a mapped sequence to complete, +" see https://unix.stackexchange.com/q/36882/221410 for more info +set timeoutlen=500 + +set updatetime=500 " For CursorHold events + +" Clipboard settings, always use clipboard for all delete, yank, change, put +" operation, see https://stackoverflow.com/q/30691466/6064933 +if !empty(provider#clipboard#Executable()) + set clipboard+=unnamedplus +endif + +" Disable creating swapfiles, see https://stackoverflow.com/q/821902/6064933 +set noswapfile + +" Ignore certain files and folders when globing +set wildignore+=*.o,*.obj,*.dylib,*.bin,*.dll,*.exe +set wildignore+=*/.git/*,*/.svn/*,*/__pycache__/*,*/build/** +set wildignore+=*.jpg,*.png,*.jpeg,*.bmp,*.gif,*.tiff,*.svg,*.ico +set wildignore+=*.pyc,*.pkl +set wildignore+=*.DS_Store +set wildignore+=*.aux,*.bbl,*.blg,*.brf,*.fls,*.fdb_latexmk,*.synctex.gz,*.xdv +set wildignorecase " ignore file and dir name cases in cmd-completion + +" Set up backup directory +let g:backupdir=expand(stdpath('data') . '/backup//') +let &backupdir=g:backupdir + +" Skip backup for patterns in option wildignore +let &backupskip=&wildignore +set backup " create backup for files +set backupcopy=yes " copy the original file to backupdir and overwrite it + +" General tab settings +set tabstop=2 " number of visual spaces per TAB +set softtabstop=2 " number of spaces in tab when editing +set shiftwidth=2 " number of spaces to use for autoindent +set expandtab " expand tab to spaces so that tabs are spaces + +" Set matching pairs of characters and highlight matching brackets +set matchpairs+=<:>,「:」,『:』,【:】,“:”,‘:’,《:》 + +set number relativenumber " Show line number and relative line number + +" Ignore case in general, but become case-sensitive when uppercase is present +set ignorecase smartcase + +" File and script encoding settings for vim +set fileencoding=utf-8 +set fileencodings=ucs-bom,utf-8,cp936,gb18030,big5,euc-jp,euc-kr,latin1 + +" Break line at predefined characters +set linebreak +" Character to show before the lines that have been soft-wrapped +set showbreak=↪ + +" List all matches and complete till longest common string +set wildmode=list:longest + +" Minimum lines to keep above and below cursor when scrolling +set scrolloff=3 + +" Use mouse to select and resize windows, etc. +set mouse=nic " Enable mouse in several mode +set mousemodel=popup " Set the behaviour of mouse +set mousescroll=ver:1,hor:0 + +" Disable showing current mode on command line since statusline plugins can show it. +set noshowmode + +set fileformats=unix,dos " Fileformats to use for new files + +" Ask for confirmation when handling unsaved or read-only files +set confirm + +set visualbell noerrorbells " Do not use visual and errorbells +set history=500 " The number of command and search history to keep + +" Use list mode and customized listchars +set list listchars=tab:▸\ ,extends:❯,precedes:❮,nbsp:␣ + +" Auto-write the file based on some condition +set autowrite + +" Show hostname, full path of file and last-mod time on the window title. The +" meaning of the format str for strftime can be found in +" http://man7.org/linux/man-pages/man3/strftime.3.html. The function to get +" lastmod time is drawn from https://stackoverflow.com/q/8426736/6064933 +set title +set titlestring= +set titlestring=%{utils#Get_titlestr()} + +" Persistent undo even after you close a file and re-open it +set undofile + +" Do not show "match xx of xx" and other messages during auto-completion +set shortmess+=c + +" Do not show search match count on bottom right (seriously, I would strain my +" neck looking at it). Using plugins like vim-anzu or nvim-hlslens is a better +" choice, IMHO. +set shortmess+=S + +" Disable showing intro message (:intro) +set shortmess+=I + +" Completion behaviour +" set completeopt+=noinsert " Auto select the first completion entry +set completeopt+=menuone " Show menu even if there is only one item +set completeopt-=preview " Disable the preview window + +set pumheight=10 " Maximum number of items to show in popup menu +set pumblend=10 " pseudo transparency for completion menu + +set winblend=0 " pseudo transparency for floating window + +" Insert mode key word completion setting +set complete+=kspell complete-=w complete-=b complete-=u complete-=t + +set spelllang=en,cjk " Spell languages +set spellsuggest+=9 " show 9 spell suggestions at most + +" Align indent to next multiple value of shiftwidth. For its meaning, +" see http://vim.1045645.n5.nabble.com/shiftround-option-td5712100.html +set shiftround + +set virtualedit=block " Virtual edit is useful for visual block edit + +" Correctly break multi-byte characters such as CJK, +" see https://stackoverflow.com/q/32669814/6064933 +set formatoptions+=mM + +" Tilde (~) is an operator, thus must be followed by motions like `e` or `w`. +set tildeop + +set synmaxcol=250 " Text after this column number is not highlighted +set nostartofline + +" External program to use for grep command +if executable('rg') + set grepprg=rg\ --vimgrep\ --no-heading\ --smart-case + set grepformat=%f:%l:%c:%m +endif + +" Enable true color support. Do not set this option if your terminal does not +" support true colors! For a comprehensive list of terminals supporting true +" colors, see https://github.com/termstandard/colors and https://gist.github.com/XVilka/8346728. +set termguicolors + +" Set up cursor color and shape in various mode, ref: +" https://github.com/neovim/neovim/wiki/FAQ#how-to-change-cursor-color-in-the-terminal +set guicursor=n-v-c:block-Cursor/lCursor,i-ci-ve:ver25-Cursor2/lCursor2,r-cr:hor20,o:hor20 + +set signcolumn=yes:1 + +" Remove certain character from file name pattern matching +set isfname-== +set isfname-=, + +" diff options +set diffopt= +set diffopt+=vertical " show diff in vertical position +set diffopt+=filler " show filler for deleted lines +set diffopt+=closeoff " turn off diff when one file window is closed +set diffopt+=context:3 " context for diff +set diffopt+=internal,indent-heuristic,algorithm:histogram +set diffopt+=linematch:60 + +set nowrap " do no wrap +set noruler diff --git a/.config/nvim/viml_conf/plugins.vim b/.config/nvim/viml_conf/plugins.vim new file mode 100644 index 0000000..83b6a52 --- /dev/null +++ b/.config/nvim/viml_conf/plugins.vim @@ -0,0 +1,409 @@ +scriptencoding utf-8 + +" Plugin specification and lua stuff +lua require('plugin_specs') + +" Use short names for common plugin manager commands to simplify typing. +" To use these shortcuts: first activate command line with `:`, then input the +" short alias, e.g., `pi`, then press , the alias will be expanded to +" the full command automatically. +call utils#Cabbrev('pi', 'Lazy install') +call utils#Cabbrev('pud', 'Lazy update') +call utils#Cabbrev('pc', 'Lazy clean') +call utils#Cabbrev('ps', 'Lazy sync') + +"""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""" +" configurations for vim script plugin " +"""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""" + +"""""""""""""""""""""""""UltiSnips settings""""""""""""""""""" +" Trigger configuration. Do not use if you use YouCompleteMe +let g:UltiSnipsExpandTrigger='' + +" Do not look for SnipMate snippets +let g:UltiSnipsEnableSnipMate = 0 + +" Shortcut to jump forward and backward in tabstop positions +let g:UltiSnipsJumpForwardTrigger='' +let g:UltiSnipsJumpBackwardTrigger='' + +" Configuration for custom snippets directory, see +" https://jdhao.github.io/2019/04/17/neovim_snippet_s1/ for details. +let g:UltiSnipsSnippetDirectories=['UltiSnips', 'my_snippets'] + +"""""""""""""""""""""""""" vlime settings """""""""""""""""""""""""""""""" +command! -nargs=0 StartVlime call jobstart(printf("sbcl --load %s/vlime/lisp/start-vlime.lisp", g:package_home)) + +"""""""""""""""""""""""""""""LeaderF settings""""""""""""""""""""" +" Do not use cache file +let g:Lf_UseCache = 0 +" Refresh each time we call leaderf +let g:Lf_UseMemoryCache = 0 + +" Ignore certain files and directories when searching files +let g:Lf_WildIgnore = { + \ 'dir': ['.git', '__pycache__', '.DS_Store', '*_cache'], + \ 'file': ['*.exe', '*.dll', '*.so', '*.o', '*.pyc', '*.jpg', '*.png', + \ '*.gif', '*.svg', '*.ico', '*.db', '*.tgz', '*.tar.gz', '*.gz', + \ '*.zip', '*.bin', '*.pptx', '*.xlsx', '*.docx', '*.pdf', '*.tmp', + \ '*.wmv', '*.mkv', '*.mp4', '*.rmvb', '*.ttf', '*.ttc', '*.otf', + \ '*.mp3', '*.aac'] + \} + +" Do not show fancy icons for Linux server. +if g:is_linux + let g:Lf_ShowDevIcons = 0 +endif + +" Only fuzzy-search files names +let g:Lf_DefaultMode = 'FullPath' + +" Popup window settings +let w = float2nr(&columns * 0.8) +if w > 140 + let g:Lf_PopupWidth = 140 +else + let g:Lf_PopupWidth = w +endif + +let g:Lf_PopupPosition = [0, float2nr((&columns - g:Lf_PopupWidth)/2)] + +" Do not use version control tool to list files under a directory since +" submodules are not searched by default. +let g:Lf_UseVersionControlTool = 0 + +" Use rg as the default search tool +let g:Lf_DefaultExternalTool = "rg" + +" show dot files +let g:Lf_ShowHidden = 1 + +" Disable default mapping +let g:Lf_ShortcutF = '' +let g:Lf_ShortcutB = '' + +" set up working directory for git repository +let g:Lf_WorkingDirectoryMode = 'a' + +" Search files in popup window +nnoremap ff :Leaderf file --popup + +" Grep project files in popup window +nnoremap fg :Leaderf rg --no-messages --popup + +" Search vim help files +nnoremap fh :Leaderf help --popup + +" Search tags in current buffer +nnoremap ft :Leaderf bufTag --popup + +" Switch buffers +nnoremap fb :Leaderf buffer --popup + +" Search recent files +nnoremap fr :Leaderf mru --popup --absolute-path + +let g:Lf_PopupColorscheme = 'gruvbox_material' + +" Change keybinding in LeaderF prompt mode, use ctrl-n and ctrl-p to navigate +" items. +let g:Lf_CommandMap = {'': [''], '': ['']} + +" do not preview results, it will add the file to buffer list +let g:Lf_PreviewResult = { + \ 'File': 0, + \ 'Buffer': 0, + \ 'Mru': 0, + \ 'Tag': 0, + \ 'BufTag': 1, + \ 'Function': 1, + \ 'Line': 0, + \ 'Colorscheme': 0, + \ 'Rg': 0, + \ 'Gtags': 0 + \} + +""""""""""""""""""""""""""""open-browser.vim settings""""""""""""""""""" +if g:is_win || g:is_mac + " Disable netrw's gx mapping. + let g:netrw_nogx = 1 + + " Use another mapping for the open URL method + nmap ob (openbrowser-smart-search) + xmap ob (openbrowser-smart-search) +endif + +""""""""""""""""""""""""""" vista settings """""""""""""""""""""""""""""""""" +let g:vista#renderer#icons = { + \ 'member': '', + \ } + +" Do not echo message on command line +let g:vista_echo_cursor = 0 +" Stay in current window when vista window is opened +let g:vista_stay_on_open = 0 + +nnoremap t :Vista!! + +""""""""""""""""""""""""vim-mundo settings""""""""""""""""""""""" +let g:mundo_verbose_graph = 0 +let g:mundo_width = 80 + +nnoremap u :MundoToggle + +""""""""""""""""""""""""""""better-escape.vim settings""""""""""""""""""""""""" +let g:better_escape_interval = 200 + +""""""""""""""""""""""""""""vim-xkbswitch settings""""""""""""""""""""""""" +let g:XkbSwitchEnabled = 1 + +"""""""""""""""""""""""""""""" neoformat settings """"""""""""""""""""""" +let g:neoformat_enabled_python = ['black', 'yapf'] +let g:neoformat_cpp_clangformat = { + \ 'exe': 'clang-format', + \ 'args': ['--style="{IndentWidth: 4}"'] + \ } +let g:neoformat_c_clangformat = { + \ 'exe': 'clang-format', + \ 'args': ['--style="{IndentWidth: 4}"'] + \ } + +let g:neoformat_enabled_cpp = ['clangformat'] +let g:neoformat_enabled_c = ['clangformat'] + +"""""""""""""""""""""""""vim-markdown settings""""""""""""""""""" +" Disable header folding +let g:vim_markdown_folding_disabled = 1 + +" Whether to use conceal feature in markdown +let g:vim_markdown_conceal = 1 + +" Disable math tex conceal and syntax highlight +let g:tex_conceal = '' +let g:vim_markdown_math = 0 + +" Support front matter of various format +let g:vim_markdown_frontmatter = 1 " for YAML format +let g:vim_markdown_toml_frontmatter = 1 " for TOML format +let g:vim_markdown_json_frontmatter = 1 " for JSON format + +" Let the TOC window autofit so that it doesn't take too much space +let g:vim_markdown_toc_autofit = 1 + +"""""""""""""""""""""""""markdown-preview settings""""""""""""""""""" +" Only setting this for suitable platforms +if g:is_win || g:is_mac + " Do not close the preview tab when switching to other buffers + let g:mkdp_auto_close = 0 + + " Shortcuts to start and stop markdown previewing + nnoremap :MarkdownPreview + nnoremap :MarkdownPreviewStop +endif + +""""""""""""""""""""""""vim-grammarous settings"""""""""""""""""""""""""""""" +if g:is_mac + let g:grammarous#languagetool_cmd = 'languagetool' + let g:grammarous#disabled_rules = { + \ '*' : ['WHITESPACE_RULE', 'EN_QUOTES', 'ARROWS', 'SENTENCE_WHITESPACE', + \ 'WORD_CONTAINS_UNDERSCORE', 'COMMA_PARENTHESIS_WHITESPACE', + \ 'EN_UNPAIRED_BRACKETS', 'UPPERCASE_SENTENCE_START', + \ 'ENGLISH_WORD_REPEAT_BEGINNING_RULE', 'DASH_RULE', 'PLUS_MINUS', + \ 'PUNCTUATION_PARAGRAPH_END', 'MULTIPLICATION_SIGN', 'PRP_CHECKOUT', + \ 'CAN_CHECKOUT', 'SOME_OF_THE', 'DOUBLE_PUNCTUATION', 'HELL', + \ 'CURRENCY', 'POSSESSIVE_APOSTROPHE', 'ENGLISH_WORD_REPEAT_RULE', + \ 'NON_STANDARD_WORD', 'AU', 'DATE_NEW_YEAR'], + \ } + + augroup grammarous_map + autocmd! + autocmd FileType markdown nmap x (grammarous-close-info-window) + autocmd FileType markdown nmap (grammarous-move-to-next-error) + autocmd FileType markdown nmap (grammarous-move-to-previous-error) + augroup END +endif + +""""""""""""""""""""""""unicode.vim settings"""""""""""""""""""""""""""""" +nmap ga (UnicodeGA) + +""""""""""""""""""""""""""""vim-sandwich settings""""""""""""""""""""""""""""" +" Map s to nop since s in used by vim-sandwich. Use cl instead of s. +nmap s +omap s + +""""""""""""""""""""""""""""vimtex settings""""""""""""""""""""""""""""" +if executable('latex') + " Hacks for inverse search to work semi-automatically, + " see https://jdhao.github.io/2021/02/20/inverse_search_setup_neovim_vimtex/. + function! s:write_server_name() abort + let nvim_server_file = (has('win32') ? $TEMP : '/tmp') . '/vimtexserver.txt' + call writefile([v:servername], nvim_server_file) + endfunction + + augroup vimtex_common + autocmd! + autocmd FileType tex call s:write_server_name() + autocmd FileType tex nmap (vimtex-compile) + augroup END + + let g:vimtex_compiler_latexmk = { + \ 'build_dir' : 'build', + \ } + + " TOC settings + let g:vimtex_toc_config = { + \ 'name' : 'TOC', + \ 'layers' : ['content', 'todo', 'include'], + \ 'resize' : 1, + \ 'split_width' : 30, + \ 'todo_sorted' : 0, + \ 'show_help' : 1, + \ 'show_numbers' : 1, + \ 'mode' : 2, + \ } + + " Viewer settings for different platforms + if g:is_win + let g:vimtex_view_general_viewer = 'SumatraPDF' + let g:vimtex_view_general_options = '-reuse-instance -forward-search @tex @line @pdf' + endif + + if g:is_mac + " let g:vimtex_view_method = "skim" + let g:vimtex_view_general_viewer = '/Applications/Skim.app/Contents/SharedSupport/displayline' + let g:vimtex_view_general_options = '-r @line @pdf @tex' + + augroup vimtex_mac + autocmd! + autocmd User VimtexEventCompileSuccess call UpdateSkim() + augroup END + + " The following code is adapted from https://gist.github.com/skulumani/7ea00478c63193a832a6d3f2e661a536. + function! UpdateSkim() abort + let l:out = b:vimtex.out() + let l:src_file_path = expand('%:p') + let l:cmd = [g:vimtex_view_general_viewer, '-r'] + + if !empty(system('pgrep Skim')) + call extend(l:cmd, ['-g']) + endif + + call jobstart(l:cmd + [line('.'), l:out, l:src_file_path]) + endfunction + endif +endif + +""""""""""""""""""""""""""""vim-matchup settings""""""""""""""""""""""""""""" +" Improve performance +let g:matchup_matchparen_deferred = 1 +let g:matchup_matchparen_timeout = 100 +let g:matchup_matchparen_insert_timeout = 30 + +" Enhanced matching with matchup plugin +let g:matchup_override_vimtex = 1 + +" Whether to enable matching inside comment or string +let g:matchup_delim_noskips = 0 + +" Show offscreen match pair in popup window +let g:matchup_matchparen_offscreen = {'method': 'popup'} + +"""""""""""""""""""""""""" asyncrun.vim settings """""""""""""""""""""""""" +" Automatically open quickfix window of 6 line tall after asyncrun starts +let g:asyncrun_open = 6 +if g:is_win + " Command output encoding for Windows + let g:asyncrun_encs = 'gbk' +endif + +""""""""""""""""""""""""""""""firenvim settings"""""""""""""""""""""""""""""" +if exists('g:started_by_firenvim') && g:started_by_firenvim + if g:is_mac + set guifont=Iosevka\ Nerd\ Font:h18 + else + set guifont=Consolas + endif + + " general config for firenvim + let g:firenvim_config = { + \ 'globalSettings': { + \ 'alt': 'all', + \ }, + \ 'localSettings': { + \ '.*': { + \ 'cmdline': 'neovim', + \ 'priority': 0, + \ 'selector': 'textarea', + \ 'takeover': 'never', + \ }, + \ } + \ } + + function s:setup_firenvim() abort + set signcolumn=no + set noruler + set noshowcmd + set laststatus=0 + set showtabline=0 + endfunction + + augroup firenvim + autocmd! + autocmd BufEnter * call s:setup_firenvim() + autocmd BufEnter sqlzoo*.txt set filetype=sql + autocmd BufEnter github.com_*.txt set filetype=markdown + autocmd BufEnter stackoverflow.com_*.txt set filetype=markdown + augroup END +endif + +""""""""""""""""""""""""""""""nvim-gdb settings"""""""""""""""""""""""""""""" +nnoremap dp :GdbStartPDB python -m pdb % + +""""""""""""""""""""""""""""""wilder.nvim settings"""""""""""""""""""""""""""""" +call timer_start(250, { -> s:wilder_init() }) + +function! s:wilder_init() abort + try + call wilder#setup({ + \ 'modes': [':', '/', '?'], + \ 'next_key': '', + \ 'previous_key': '', + \ 'accept_key': '', + \ 'reject_key': '' + \ }) + + call wilder#set_option('pipeline', [ + \ wilder#branch( + \ wilder#cmdline_pipeline({ + \ 'language': 'python', + \ 'fuzzy': 1, + \ 'sorter': wilder#python_difflib_sorter(), + \ 'debounce': 30, + \ }), + \ wilder#python_search_pipeline({ + \ 'pattern': wilder#python_fuzzy_pattern(), + \ 'sorter': wilder#python_difflib_sorter(), + \ 'engine': 're', + \ 'debounce': 30, + \ }), + \ ), + \ ]) + + let l:hl = wilder#make_hl('WilderAccent', 'Pmenu', [{}, {}, {'foreground': '#f4468f'}]) + call wilder#set_option('renderer', wilder#popupmenu_renderer({ + \ 'highlighter': wilder#basic_highlighter(), + \ 'max_height': 15, + \ 'highlights': { + \ 'accent': l:hl, + \ }, + \ 'left': [' ', wilder#popupmenu_devicons(),], + \ 'right': [' ', wilder#popupmenu_scrollbar(),], + \ 'apply_incsearch_fix': 0, + \ })) + catch /^Vim\%((\a\+)\)\=:E117/ + echohl Error |echomsg "Wilder.nvim missing"| echohl None + endtry +endfunction + +""""""""""""""""""""""""""""""vim-auto-save settings"""""""""""""""""""""""""""""" +let g:auto_save = 1 " enable AutoSave on Vim startup diff --git a/.gitignore b/.gitignore new file mode 100644 index 0000000..b7dee09 --- /dev/null +++ b/.gitignore @@ -0,0 +1 @@ +.config/nvim/plugged/ -- cgit v1.2.3