From b32e2e59450dd5e2c8ea179ea48f9c1f9ffbb6f4 Mon Sep 17 00:00:00 2001 From: x3 Date: Mon, 31 Aug 2026 18:42:20 +0200 Subject: [PATCH] add dotfiles --- .ncmpcpp/config | 5 + .ncmpcpp/error.log | 0 .vim/autoload/plug.vim | 2910 +++++++++++++++++ .vim/plugged/catppuccin | 1 + .vim/plugged/dracula | 1 + .vim/plugged/gruvbox | 1 + .vim/plugged/nerdtree | 1 + .vim/plugged/onedark.vim | 1 + .vim/plugged/tokyonight.nvim | 1 + .vim/plugged/vim-airline | 1 + .vim/plugged/vim-airline-themes | 1 + .vim/plugged/vim-sensible | 1 + hypr/hyprland.lua | 522 +++ kitty/kitty-themes | 1 + kitty/kitty.conf | 32 + kitty/theme.conf | 1 + mpd/database | Bin 0 -> 24018 bytes mpd/mpd.conf | 33 + mpd/pid | 1 + noctalia/colors.json | 18 + .../colorschemes/Cyberpunk/Cyberpunk.json | 94 + .../colorschemes/GitHub Dark/GitHub Dark.json | 94 + noctalia/plugins.json | 12 + noctalia/settings.json | 712 ++++ ranger/commands.py | 62 + ranger/commands_full.py | 1993 +++++++++++ ranger/rc.conf | 758 +++++ ranger/rifle.conf | 284 ++ ranger/scope.sh | 350 ++ wofi/style.css | 70 + 30 files changed, 7961 insertions(+) create mode 100644 .ncmpcpp/config create mode 100644 .ncmpcpp/error.log create mode 100644 .vim/autoload/plug.vim create mode 160000 .vim/plugged/catppuccin create mode 160000 .vim/plugged/dracula create mode 160000 .vim/plugged/gruvbox create mode 160000 .vim/plugged/nerdtree create mode 160000 .vim/plugged/onedark.vim create mode 160000 .vim/plugged/tokyonight.nvim create mode 160000 .vim/plugged/vim-airline create mode 160000 .vim/plugged/vim-airline-themes create mode 160000 .vim/plugged/vim-sensible create mode 100644 hypr/hyprland.lua create mode 160000 kitty/kitty-themes create mode 100644 kitty/kitty.conf create mode 120000 kitty/theme.conf create mode 100644 mpd/database create mode 100644 mpd/mpd.conf create mode 100644 mpd/pid create mode 100644 noctalia/colors.json create mode 100644 noctalia/colorschemes/Cyberpunk/Cyberpunk.json create mode 100644 noctalia/colorschemes/GitHub Dark/GitHub Dark.json create mode 100644 noctalia/plugins.json create mode 100644 noctalia/settings.json create mode 100644 ranger/commands.py create mode 100644 ranger/commands_full.py create mode 100644 ranger/rc.conf create mode 100644 ranger/rifle.conf create mode 100755 ranger/scope.sh create mode 100644 wofi/style.css diff --git a/.ncmpcpp/config b/.ncmpcpp/config new file mode 100644 index 0000000..2d0b21c --- /dev/null +++ b/.ncmpcpp/config @@ -0,0 +1,5 @@ +visualizer_data_source = "/tmp/mpd.fifo" +visualizer_output_name = "my_fifo" +visualizer_in_stereo = "yes" +visualizer_type = "spectrum" +visualizer_look = "+|" diff --git a/.ncmpcpp/error.log b/.ncmpcpp/error.log new file mode 100644 index 0000000..e69de29 diff --git a/.vim/autoload/plug.vim b/.vim/autoload/plug.vim new file mode 100644 index 0000000..d98d611 --- /dev/null +++ b/.vim/autoload/plug.vim @@ -0,0 +1,2910 @@ +" 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') +let s:shell_error = 0 +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 s: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, {'shell': s:is_win ? 'cmd.exe' : &shell}) : 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 = s: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 !s: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 = s: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(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, {'shell': s:is_win ? 'cmd.exe' : 'sh', 'script': 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 = s: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 opts = a:0 > 0 && type(a:1) == s:TYPE.dict ? a:1 : {} + let opts.shell = get(opts, 'shell', &shell) + let opts.script = get(opts, 'script', 1) + + let pwsh = s:is_powershell(opts.shell) + let cd = s:is_win && !pwsh ? 'cd /d' : 'cd' + let sep = pwsh ? ';' : '&&' + let pwsh_block_required = pwsh && !has('patch-9.2.6') + let start = pwsh_block_required ? '& { ' : '' + let end = pwsh_block_required ? ' }' : '' + + return printf('%s%s %s %s %s%s', start, cd, plug#shellescape(a:dir, opts), sep, a:cmd, end) +endfunction + +function! s:system_job(cmd) abort + let tmp = tempname() + let job = job_start(['/bin/sh', '-c', a:cmd], { + \ 'out_io': 'file', + \ 'out_name': tmp, + \ 'err_io': 'out', + \}) + while job_status(job) ==# 'run' + sleep 1m + endwhile + let s:shell_error = job_info(job).exitval + let result = filereadable(tmp) ? join(readfile(tmp, 'b'), "\n") : '' + silent! call delete(tmp) + return result +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 + let ret = system(a:cmd) + let s:shell_error = v:shell_error + return ret + 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, {'script': type(a:cmd) != s:TYPE.list}) + endif + if s:is_win && type(a:cmd) != s:TYPE.list && !s:is_powershell(&shell) + let [batchfile, cmd] = s:batchfile(cmd) + endif + if s:vim8 && has('gui_running') && !s:is_win + return s:system_job(cmd) + endif + let ret = system(cmd) + let s:shell_error = v:shell_error + return ret + 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 s: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 s: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 + \ ? ['rm', '-rf', a:dir] + \ : s:is_powershell(&shell) + \ ? ['Remove-Item', '-Recurse', '-Force', a:dir] + \ : 'rmdir /S /Q '.plug#shellescape(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 s: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, '\\\@= 0.55 +-- Einfache, konservative Lua-Konfiguration + +local mainMod = "ALT" + +local terminal = "kitty" +local fileManager = "thunar" +local menu = "wofi --show drun" +local browser = "firefox" + + +-- ============================================================ +-- HAUPTKONFIGURATION +-- ============================================================ + +hl.env("XCURSOR_THEME", "Bibata-Modern-Ice") +hl.env("XCURSOR_SIZE", "24") +hl.env("HYPRCURSOR_THEME", "Bibata-Modern-Ice") +hl.env("HYPRCURSOR_SIZE", "24") + +hl.on("hyprland.start", function() + hl.exec_cmd("hyprctl setcursor Bibata-Modern-Ice 24") +end) + + +hl.config({ + + monitor = { + ",preferred,auto,1", + }, + + env = { + "XDG_CURRENT_DESKTOP,Hyprland", + "XDG_SESSION_TYPE,wayland", + "XDG_SESSION_DESKTOP,Hyprland", + "QT_QPA_PLATFORM,wayland;xcb", + "GDK_BACKEND,wayland,x11", + "MOZ_ENABLE_WAYLAND,1", + "XCURSOR_THEME,Bibata-Modern-Ice", + "XCURSOR_SIZE,24", + "HYPRCURSOR_THEME,Bibata-Modern-Ice", + "HYPRCURSOR_SIZE,24", + }, + + input = { + kb_layout = "de", + follow_mouse = 1, + sensitivity = 0, + + touchpad = { + natural_scroll = true, + disable_while_typing = true, + tap_to_click = true, + }, + }, + + general = { + gaps_in = 5, + gaps_out = 12, + border_size = 2, + + ["col.active_border"] = "rgba(89b4faee)", + ["col.inactive_border"] = "rgba(31324455)", + + resize_on_border = true, + layout = "dwindle", + allow_tearing = false, + }, + + decoration = { + rounding = 8, + + blur = { + enabled = true, + size = 6, + passes = 2, + }, + }, + + animations = { + enabled = true, + }, + + dwindle = { + preserve_split = true, + }, + + master = { + new_status = "master", + }, + + misc = { + force_default_wallpaper = 0, + disable_hyprland_logo = true, + disable_splash_rendering = true, + }, +}) + +-- ============================================================ +-- LAYOUT WECHSELN (dwm-artig) +-- ============================================================ + +local layoutCycle = { "dwindle", "master" } + +hl.bind(mainMod .. " + SPACE", function() + local current = hl.get_config("general.layout") + + local nextLayout = layoutCycle[1] + for i, name in ipairs(layoutCycle) do + if name == current then + nextLayout = layoutCycle[(i % #layoutCycle) + 1] + break + end + end + + hl.config({ general = { layout = nextLayout } }) + + hl.notification.create({ + text = "Layout: " .. nextLayout, + timeout = 800, + icon = "ok", + }) +end) + +-- Master-Layout: Orientierung durchschalten (links/rechts/oben/unten/zentriert) +-- entspricht in etwa dwm's tile/bstack-Varianten – nur wirksam wenn master aktiv +hl.bind(mainMod .. " + SHIFT + SPACE", hl.dsp.layout("orientationcycle")) + + +hl.env("GTK_THEME", "Adwaita:dark") + +hl.config({ + exec_once = { + "/usr/lib/xdg-desktop-portal-gtk" + } +}) + +-- ============================================================ +-- AUTOSTART +-- ============================================================ + +hl.on("hyprland.start", function() + + hl.exec_cmd( + "dbus-update-activation-environment " .. + "WAYLAND_DISPLAY XDG_CURRENT_DESKTOP" + ) + + + hl.exec_cmd("killall -e xdg-desktop-portal-hyprland xdg-desktop-portal") + + -- kurz warten, dann XDPH zuerst starten (Backend muss vor dem Router da sein) + hl.exec_cmd("sh -c 'sleep 1 && /usr/libexec/xdg-desktop-portal-hyprland'") + hl.exec_cmd("sh -c 'sleep 2 && /usr/libexec/xdg-desktop-portal'") + + + hl.exec_cmd("qs -c noctalia-shell") + hl.exec_cmd("gentoo-pipewire-launcher") + hl.exec_cmd("mpd ~/.config/mpd/mpd.conf &") +end) + +-- ============================================================ +-- NOCTALIA LAYER RULE +-- ============================================================ + +hl.layer_rule({ + name = "noctalia", + match = { + namespace = "^noctalia-.*$", + }, + no_anim = true, +}) + + +-- ============================================================ +-- TOUCHPAD-GESTEN +-- ============================================================ + +hl.gesture({ + fingers = 3, + direction = "horizontal", + action = "workspace", +}) + + +-- ============================================================ +-- PROGRAMME +-- ============================================================ + +hl.bind( + mainMod .. " + Return", + hl.dsp.exec_cmd(terminal) +) + +hl.bind( + mainMod .. " + E", + hl.dsp.exec_cmd(fileManager) +) + +hl.bind( + mainMod .. " + R", + hl.dsp.exec_cmd(menu) +) + +hl.bind( + mainMod .. " + B", + hl.dsp.exec_cmd(browser) +) + + +-- ============================================================ +-- FENSTER +-- ============================================================ + +-- Aktives Fenster schließen +hl.bind( + mainMod .. " + Q", + hl.dsp.window.close() +) + +-- Hyprland beenden +hl.bind( + mainMod .. " + SHIFT + Q", + hl.dsp.exit() +) + +-- Floating umschalten +hl.bind( + mainMod .. " + V", + hl.dsp.window.float({ + action = "toggle", + }) +) + +-- Pseudotile umschalten +hl.bind( + mainMod .. " + P", + hl.dsp.window.pseudo({ + action = "toggle", + }) +) + +-- Split-Richtung umschalten +hl.bind( + mainMod .. " + J", + hl.dsp.layout("togglesplit") +) + +-- Fullscreen umschalten +hl.bind( + mainMod .. " + F", + hl.dsp.window.fullscreen({ + mode = "fullscreen", + action = "toggle", + }) +) + +-- Mod + Linksklick-Drag: Fenster verschieben +hl.bind( + mainMod .. " + mouse:272", + hl.dsp.window.drag(), + { mouse = true } +) + +-- Mod + Rechtsklick-Drag: Fenster resizen +hl.bind( + mainMod .. " + mouse:273", + hl.dsp.window.resize(), + { mouse = true } +) + +-- ============================================================ +-- FOKUS WECHSELN +-- ============================================================ + +hl.bind( + mainMod .. " + left", + hl.dsp.focus({ + direction = "left", + }) +) + +hl.bind( + mainMod .. " + right", + hl.dsp.focus({ + direction = "right", + }) +) + +hl.bind( + mainMod .. " + up", + hl.dsp.focus({ + direction = "up", + }) +) + +hl.bind( + mainMod .. " + down", + hl.dsp.focus({ + direction = "down", + }) +) + + +-- ============================================================ +-- FENSTER VERSCHIEBEN +-- ============================================================ + +hl.bind( + mainMod .. " + SHIFT + left", + hl.dsp.window.move({ + direction = "left", + }) +) + +hl.bind( + mainMod .. " + SHIFT + right", + hl.dsp.window.move({ + direction = "right", + }) +) + +hl.bind( + mainMod .. " + SHIFT + up", + hl.dsp.window.move({ + direction = "up", + }) +) + +hl.bind( + mainMod .. " + SHIFT + down", + hl.dsp.window.move({ + direction = "down", + }) +) + + +-- ============================================================ +-- WORKSPACES 1 BIS 9 +-- ============================================================ + +for i = 1, 9 do + + -- Workspace öffnen + hl.bind( + mainMod .. " + " .. tostring(i), + hl.dsp.focus({ + workspace = tostring(i), + }) + ) + + -- Fenster zu Workspace verschieben + hl.bind( + mainMod .. " + SHIFT + " .. tostring(i), + hl.dsp.window.move({ + workspace = tostring(i), + follow = false, + }) + ) +end + + +-- ============================================================ +-- WORKSPACE 10 +-- ============================================================ + +hl.bind( + mainMod .. " + 0", + hl.dsp.focus({ + workspace = "10", + }) +) + +hl.bind( + mainMod .. " + SHIFT + 0", + hl.dsp.window.move({ + workspace = "10", + follow = false, + }) +) + + +-- ============================================================ +-- WORKSPACE MIT MAUSRAD +-- ============================================================ + +hl.bind( + mainMod .. " + mouse_down", + hl.dsp.focus({ + workspace = "e+1", + }) +) + +hl.bind( + mainMod .. " + mouse_up", + hl.dsp.focus({ + workspace = "e-1", + }) +) + + +-- ============================================================ +-- SCREENSHOTS +-- ============================================================ + +-- Bereich auswählen +hl.bind( + "Print", + hl.dsp.exec_cmd( + 'grim -g "$(slurp)" - | wl-copy' + ) +) + +-- Gesamter Bildschirm +hl.bind( + "SHIFT + Print", + hl.dsp.exec_cmd( + "grim - | wl-copy" + ) +) + + +-- ============================================================ +-- AUDIO +-- ============================================================ + +hl.bind( + "XF86AudioRaiseVolume", + hl.dsp.exec_cmd( + "wpctl set-volume -l 1.0 @DEFAULT_AUDIO_SINK@ 5%+" + ), + { + repeating = true, + locked = true, + } +) + +hl.bind( + "XF86AudioLowerVolume", + hl.dsp.exec_cmd( + "wpctl set-volume @DEFAULT_AUDIO_SINK@ 5%-" + ), + { + repeating = true, + locked = true, + } +) + +hl.bind( + "XF86AudioMute", + hl.dsp.exec_cmd( + "wpctl set-mute @DEFAULT_AUDIO_SINK@ toggle" + ), + { + locked = true, + } +) + + +-- ============================================================ +-- MEDIEN +-- ============================================================ + +hl.bind( + "XF86AudioPlay", + hl.dsp.exec_cmd("playerctl play-pause"), + { + locked = true, + } +) + +hl.bind( + "XF86AudioNext", + hl.dsp.exec_cmd("playerctl next"), + { + locked = true, + } +) + +hl.bind( + "XF86AudioPrev", + hl.dsp.exec_cmd("playerctl previous"), + { + locked = true, + } +) + + +-- ============================================================ +-- HELLIGKEIT +-- ============================================================ + +hl.bind( + "XF86MonBrightnessUp", + hl.dsp.exec_cmd("brightnessctl set +5%"), + { + repeating = true, + locked = true, + } +) + +hl.bind( + "XF86MonBrightnessDown", + hl.dsp.exec_cmd("brightnessctl set 5%-"), + { + repeating = true, + locked = true, + } +) + + +-- ============================================================ +-- BILDSCHIRM SPERREN +-- ============================================================ + +hl.bind( + mainMod .. " + L", + hl.dsp.exec_cmd( + "qs -c noctalia-shell ipc call lockScreen lock" + ) +) diff --git a/kitty/kitty-themes b/kitty/kitty-themes new file mode 160000 index 0000000..b1abdd5 --- /dev/null +++ b/kitty/kitty-themes @@ -0,0 +1 @@ +Subproject commit b1abdd54ba655ef34f75a568d78625981bf1722c diff --git a/kitty/kitty.conf b/kitty/kitty.conf new file mode 100644 index 0000000..2999178 --- /dev/null +++ b/kitty/kitty.conf @@ -0,0 +1,32 @@ +# Font +font_family monospace +bold_font monospace +italic_font monospace +bold_italic_font monospace +font_size 16.0 + +# Window +background_opacity 0.9 + +# Colors: normal +foreground #b7b8b9 +background #0c0d0e + +color0 #0c0d0e +color1 #e31a1c +color2 #31a354 +color3 #dca060 +color4 #3182bd +color5 #756bb1 +color6 #80b1d3 +color7 #b7b8b9 + +# Colors: bright +color8 #737475 +color9 #e31a1c +color10 #31a354 +color11 #dca060 +color12 #3182bd +color13 #756bb1 +color14 #80b1d3 +color15 #fcfdfe diff --git a/kitty/theme.conf b/kitty/theme.conf new file mode 120000 index 0000000..f95354b --- /dev/null +++ b/kitty/theme.conf @@ -0,0 +1 @@ +/home/x3/.config/kitty-themes/themes/Glac.conf \ No newline at end of file diff --git a/mpd/database b/mpd/database new file mode 100644 index 0000000000000000000000000000000000000000..bee491f0ca5c4bf8c5710596346f393ed536f9bd GIT binary patch literal 24018 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 J3WU" in ranger! +class my_edit(Command): + # The so-called doc-string of the class will be visible in the built-in + # help that is accessible by typing "?c" inside ranger. + """:my_edit + + A sample command for demonstration purposes that opens a file in an editor. + """ + + # The execute method is called when you run this command in ranger. + def execute(self): + # self.arg(1) is the first (space-separated) argument to the function. + # This way you can write ":my_edit somefilename". + if self.arg(1): + # self.rest(1) contains self.arg(1) and everything that follows + target_filename = self.rest(1) + else: + # self.fm is a ranger.core.filemanager.FileManager object and gives + # you access to internals of ranger. + # self.fm.thisfile is a ranger.container.file.File object and is a + # reference to the currently selected file. + target_filename = self.fm.thisfile.path + + # This is a generic function to print text in ranger. + self.fm.notify("Let's edit the file " + target_filename + "!") + + # Using bad=True in fm.notify allows you to print error messages: + if not os.path.exists(target_filename): + self.fm.notify("The given file does not exist!", bad=True) + return + + # This executes a function from ranger.core.acitons, a module with a + # variety of subroutines that can help you construct commands. + # Check out the source, or run "pydoc ranger.core.actions" for a list. + self.fm.edit_file(target_filename) + + # The tab method is called when you press tab, and should return a list of + # suggestions that the user will tab through. + # tabnum is 1 for and -1 for by default + def tab(self, tabnum): + # This is a generic tab-completion function that iterates through the + # content of the current directory. + return self._tab_directory_content() diff --git a/ranger/commands_full.py b/ranger/commands_full.py new file mode 100644 index 0000000..5defa67 --- /dev/null +++ b/ranger/commands_full.py @@ -0,0 +1,1993 @@ +# -*- coding: utf-8 -*- +# This file is part of ranger, the console file manager. +# This configuration file is licensed under the same terms as ranger. +# =================================================================== +# +# NOTE: If you copied this file to /etc/ranger/commands_full.py or +# ~/.config/ranger/commands_full.py, then it will NOT be loaded by ranger, +# and only serve as a reference. +# +# =================================================================== +# This file contains ranger's commands. +# It's all in python; lines beginning with # are comments. +# +# Note that additional commands are automatically generated from the methods +# of the class ranger.core.actions.Actions. +# +# You can customize commands in the files /etc/ranger/commands.py (system-wide) +# and ~/.config/ranger/commands.py (per user). +# They have the same syntax as this file. In fact, you can just copy this +# file to ~/.config/ranger/commands_full.py with +# `ranger --copy-config=commands_full' and make your modifications, don't +# forget to rename it to commands.py. You can also use +# `ranger --copy-config=commands' to copy a short sample commands.py that +# has everything you need to get started. +# But make sure you update your configs when you update ranger. +# +# =================================================================== +# Every class defined here which is a subclass of `Command' will be used as a +# command in ranger. Several methods are defined to interface with ranger: +# execute(): called when the command is executed. +# cancel(): called when closing the console. +# tab(tabnum): called when is pressed. +# quick(): called after each keypress. +# +# tab() argument tabnum is 1 for and -1 for by default +# +# The return values for tab() can be either: +# None: There is no tab completion +# A string: Change the console to this string +# A list/tuple/generator: cycle through every item in it +# +# The return value for quick() can be: +# False: Nothing happens +# True: Execute the command afterwards +# +# The return value for execute() and cancel() doesn't matter. +# +# =================================================================== +# Commands have certain attributes and methods that facilitate parsing of +# the arguments: +# +# self.line: The whole line that was written in the console. +# self.args: A list of all (space-separated) arguments to the command. +# self.quantifier: If this command was mapped to the key "X" and +# the user pressed 6X, self.quantifier will be 6. +# self.arg(n): The n-th argument, or an empty string if it doesn't exist. +# self.rest(n): The n-th argument plus everything that followed. For example, +# if the command was "search foo bar a b c", rest(2) will be "bar a b c" +# self.start(n): Anything before the n-th argument. For example, if the +# command was "search foo bar a b c", start(2) will be "search foo" +# +# =================================================================== +# And this is a little reference for common ranger functions and objects: +# +# self.fm: A reference to the "fm" object which contains most information +# about ranger. +# self.fm.notify(string): Print the given string on the screen. +# self.fm.notify(string, bad=True): Print the given string in RED. +# self.fm.reload_cwd(): Reload the current working directory. +# self.fm.thisdir: The current working directory. (A File object.) +# self.fm.thisfile: The current file. (A File object too.) +# self.fm.thistab.get_selection(): A list of all selected files. +# self.fm.execute_console(string): Execute the string as a ranger command. +# self.fm.open_console(string): Open the console with the given string +# already typed in for you. +# self.fm.move(direction): Moves the cursor in the given direction, which +# can be something like down=3, up=5, right=1, left=1, to=6, ... +# +# File objects (for example self.fm.thisfile) have these useful attributes and +# methods: +# +# tfile.path: The path to the file. +# tfile.basename: The base name only. +# tfile.load_content(): Force a loading of the directories content (which +# obviously works with directories only) +# tfile.is_directory: True/False depending on whether it's a directory. +# +# For advanced commands it is unavoidable to dive a bit into the source code +# of ranger. +# =================================================================== + +from __future__ import (absolute_import, division, print_function) + +from collections import deque +import os +import re + +from ranger.api.commands import Command + + +class alias(Command): + """:alias + + Copies the oldcommand as newcommand. + """ + + context = 'browser' + resolve_macros = False + + def execute(self): + if not self.arg(1) or not self.arg(2): + self.fm.notify('Syntax: alias ', bad=True) + return + + self.fm.commands.alias(self.arg(1), self.rest(2)) + + +class echo(Command): + """:echo + + Display the text in the statusbar. + """ + + def execute(self): + self.fm.notify(self.rest(1)) + + +class cd(Command): + """:cd [-r] + + The cd command changes the directory. + If the path is a file, selects that file. + The command 'cd -' is equivalent to typing ``. + Using the option "-r" will get you to the real path. + """ + + def execute(self): + if self.arg(1) == '-r': + self.shift() + destination = os.path.realpath(self.rest(1)) + if os.path.isfile(destination): + self.fm.select_file(destination) + return + else: + destination = self.rest(1) + + if not destination: + destination = '~' + + if destination == '-': + self.fm.enter_bookmark('`') + else: + self.fm.cd(destination) + + def _tab_args(self): + # dest must be rest because path could contain spaces + if self.arg(1) == '-r': + start = self.start(2) + dest = self.rest(2) + else: + start = self.start(1) + dest = self.rest(1) + + if dest: + head, tail = os.path.split(os.path.expanduser(dest)) + if head: + dest_exp = os.path.join(os.path.normpath(head), tail) + else: + dest_exp = tail + else: + dest_exp = '' + return (start, dest_exp, os.path.join(self.fm.thisdir.path, dest_exp), + dest.endswith(os.path.sep)) + + @staticmethod + def _tab_paths(dest, dest_abs, ends_with_sep): + if not dest: + try: + return next(os.walk(dest_abs))[1], dest_abs + except (OSError, StopIteration): + return [], '' + + if ends_with_sep: + try: + return [os.path.join(dest, path) for path in next(os.walk(dest_abs))[1]], '' + except (OSError, StopIteration): + return [], '' + + return None, None + + def _tab_match(self, path_user, path_file): + if self.fm.settings.cd_tab_case == 'insensitive': + path_user = path_user.lower() + path_file = path_file.lower() + elif self.fm.settings.cd_tab_case == 'smart' and path_user.islower(): + path_file = path_file.lower() + return path_file.startswith(path_user) + + def _tab_normal(self, dest, dest_abs): + dest_dir = os.path.dirname(dest) + dest_base = os.path.basename(dest) + + try: + dirnames = next(os.walk(os.path.dirname(dest_abs)))[1] + except (OSError, StopIteration): + return [], '' + + return [os.path.join(dest_dir, d) for d in dirnames if self._tab_match(dest_base, d)], '' + + def _tab_fuzzy_match(self, basepath, tokens): + """ Find directories matching tokens recursively """ + if not tokens: + tokens = [''] + paths = [basepath] + while True: + token = tokens.pop() + matches = [] + for path in paths: + try: + directories = next(os.walk(path))[1] + except (OSError, StopIteration): + continue + matches += [os.path.join(path, d) for d in directories + if self._tab_match(token, d)] + if not tokens or not matches: + return matches + paths = matches + + return None + + def _tab_fuzzy(self, dest, dest_abs): + tokens = [] + basepath = dest_abs + while True: + basepath_old = basepath + basepath, token = os.path.split(basepath) + if basepath == basepath_old: + break + if os.path.isdir(basepath_old) and not token.startswith('.'): + basepath = basepath_old + break + tokens.append(token) + + paths = self._tab_fuzzy_match(basepath, tokens) + if not os.path.isabs(dest): + paths_rel = self.fm.thisdir.path + paths = [os.path.relpath(os.path.join(basepath, path), paths_rel) + for path in paths] + else: + paths_rel = '' + return paths, paths_rel + + def tab(self, tabnum): + from os.path import sep + + start, dest, dest_abs, ends_with_sep = self._tab_args() + + paths, paths_rel = self._tab_paths(dest, dest_abs, ends_with_sep) + if paths is None: + if self.fm.settings.cd_tab_fuzzy: + paths, paths_rel = self._tab_fuzzy(dest, dest_abs) + else: + paths, paths_rel = self._tab_normal(dest, dest_abs) + + paths.sort() + + if self.fm.settings.cd_bookmarks: + paths[0:0] = [ + os.path.relpath(v.path, paths_rel) if paths_rel else v.path + for v in self.fm.bookmarks.dct.values() for path in paths + if v.path.startswith(os.path.join(paths_rel, path) + sep) + ] + + if not paths: + return None + if len(paths) == 1: + return start + paths[0] + sep + return [start + dirname + sep for dirname in paths] + + +class chain(Command): + """:chain ; ; ... + + Calls multiple commands at once, separated by semicolons. + """ + resolve_macros = False + + def execute(self): + if not self.rest(1).strip(): + self.fm.notify('Syntax: chain ; ; ...', bad=True) + return + for command in [s.strip() for s in self.rest(1).split(";")]: + self.fm.execute_console(command) + + +class shell(Command): + escape_macros_for_shell = True + + def execute(self): + if self.arg(1) and self.arg(1)[0] == '-': + flags = self.arg(1)[1:] + command = self.rest(2) + else: + flags = '' + command = self.rest(1) + + if command: + self.fm.execute_command(command, flags=flags) + + def tab(self, tabnum): + from ranger.ext.get_executables import get_executables + if self.arg(1) and self.arg(1)[0] == '-': + command = self.rest(2) + else: + command = self.rest(1) + start = self.line[0:len(self.line) - len(command)] + + try: + position_of_last_space = command.rindex(" ") + except ValueError: + return (start + program + ' ' for program + in get_executables() if program.startswith(command)) + if position_of_last_space == len(command) - 1: + selection = self.fm.thistab.get_selection() + if len(selection) == 1: + return self.line + selection[0].shell_escaped_basename + ' ' + return self.line + '%s ' + + before_word, start_of_word = self.line.rsplit(' ', 1) + return (before_word + ' ' + file.shell_escaped_basename + for file in self.fm.thisdir.files or [] + if file.shell_escaped_basename.startswith(start_of_word)) + + +class open_with(Command): + + def execute(self): + app, flags, mode = self._get_app_flags_mode(self.rest(1)) + self.fm.execute_file( + files=[f for f in self.fm.thistab.get_selection()], + app=app, + flags=flags, + mode=mode) + + def tab(self, tabnum): + return self._tab_through_executables() + + def _get_app_flags_mode(self, string): # pylint: disable=too-many-branches,too-many-statements + """Extracts the application, flags and mode from a string. + + examples: + "mplayer f 1" => ("mplayer", "f", 1) + "atool 4" => ("atool", "", 4) + "p" => ("", "p", 0) + "" => None + """ + + app = '' + flags = '' + mode = 0 + split = string.split() + + if len(split) == 1: + part = split[0] + if self._is_app(part): + app = part + elif self._is_flags(part): + flags = part + elif self._is_mode(part): + mode = part + + elif len(split) == 2: + part0 = split[0] + part1 = split[1] + + if self._is_app(part0): + app = part0 + if self._is_flags(part1): + flags = part1 + elif self._is_mode(part1): + mode = part1 + elif self._is_flags(part0): + flags = part0 + if self._is_mode(part1): + mode = part1 + elif self._is_mode(part0): + mode = part0 + if self._is_flags(part1): + flags = part1 + + elif len(split) >= 3: + part0 = split[0] + part1 = split[1] + part2 = split[2] + + if self._is_app(part0): + app = part0 + if self._is_flags(part1): + flags = part1 + if self._is_mode(part2): + mode = part2 + elif self._is_mode(part1): + mode = part1 + if self._is_flags(part2): + flags = part2 + elif self._is_flags(part0): + flags = part0 + if self._is_mode(part1): + mode = part1 + elif self._is_mode(part0): + mode = part0 + if self._is_flags(part1): + flags = part1 + + return app, flags, int(mode) + + def _is_app(self, arg): + return not self._is_flags(arg) and not arg.isdigit() + + @staticmethod + def _is_flags(arg): + from ranger.core.runner import ALLOWED_FLAGS + return all(x in ALLOWED_FLAGS for x in arg) + + @staticmethod + def _is_mode(arg): + return all(x in '0123456789' for x in arg) + + +class set_(Command): + """:set