From 1d4a0173429ca26c992e3ff5c296ec79f851289a Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Zo=C3=A9=20Cassiop=C3=A9e=20Gauthier?= Date: Thu, 24 Mar 2022 20:39:56 -0400 Subject: [PATCH] Initial commit --- .gitignore | 2 + README.md | 19 + autoload/plug.vim | 2801 +++++++++++++++++++++++++++++++++++++++++++++ coc-settings.json | 11 + screenshot.png | Bin 0 -> 67659 bytes vimrc | 268 +++++ 6 files changed, 3101 insertions(+) create mode 100644 .gitignore create mode 100644 README.md create mode 100644 autoload/plug.vim create mode 100644 coc-settings.json create mode 100644 screenshot.png create mode 100644 vimrc diff --git a/.gitignore b/.gitignore new file mode 100644 index 0000000..2747a15 --- /dev/null +++ b/.gitignore @@ -0,0 +1,2 @@ +.netrwhist +plugged/ diff --git a/README.md b/README.md new file mode 100644 index 0000000..a1ec24e --- /dev/null +++ b/README.md @@ -0,0 +1,19 @@ +# .vim configuration + + + +Clone this repository in a directory named `.vim` in your home directory (hence +the name "dotvim"). Create a symbolic link from the `vimrc` file to a file +named `.vimrc` in your home directory. + +For example: + + zo@laptop:~$ cd + zo@laptop:~$ git clone https://github.com/zoeisnowooze/dotvim.git .vim + Cloning into '.vim'... + ... + zo@laptop:~$ ln -s .vim/vimrc .vimrc + zo@laptop:~$ vim + + +When you're ready, launch Vim and use the `:PlugInstall` command to install all the plug-ins. diff --git a/autoload/plug.vim b/autoload/plug.vim new file mode 100644 index 0000000..6a958cb --- /dev/null +++ b/autoload/plug.vim @@ -0,0 +1,2801 @@ +" vim-plug: Vim plugin manager +" ============================ +" +" Download plug.vim and put it in ~/.vim/autoload +" +" curl -fLo ~/.vim/autoload/plug.vim --create-dirs \ +" https://raw.githubusercontent.com/junegunn/vim-plug/master/plug.vim +" +" Edit your .vimrc +" +" call plug#begin('~/.vim/plugged') +" +" " Make sure you use single quotes +" +" " Shorthand notation; fetches https://github.com/junegunn/vim-easy-align +" Plug 'junegunn/vim-easy-align' +" +" " Any valid git URL is allowed +" Plug 'https://github.com/junegunn/vim-github-dashboard.git' +" +" " Multiple Plug commands can be written in a single line using | separators +" Plug 'SirVer/ultisnips' | Plug 'honza/vim-snippets' +" +" " On-demand loading +" Plug 'scrooloose/nerdtree', { 'on': 'NERDTreeToggle' } +" Plug 'tpope/vim-fireplace', { 'for': 'clojure' } +" +" " Using a non-default branch +" Plug 'rdnetto/YCM-Generator', { 'branch': 'stable' } +" +" " Using a tagged release; wildcard allowed (requires git 1.9.2 or above) +" Plug 'fatih/vim-go', { 'tag': '*' } +" +" " Plugin options +" Plug 'nsf/gocode', { 'tag': 'v.20150303', 'rtp': 'vim' } +" +" " Plugin outside ~/.vim/plugged with post-update hook +" Plug 'junegunn/fzf', { 'dir': '~/.fzf', 'do': './install --all' } +" +" " Unmanaged plugin (manually installed and updated) +" Plug '~/my-prototype-plugin' +" +" " Initialize plugin system +" call plug#end() +" +" Then reload .vimrc and :PlugInstall to install plugins. +" +" Plug options: +" +"| Option | Description | +"| ----------------------- | ------------------------------------------------ | +"| `branch`/`tag`/`commit` | Branch/tag/commit of the repository to use | +"| `rtp` | Subdirectory that contains Vim plugin | +"| `dir` | Custom directory for the plugin | +"| `as` | Use different name for the plugin | +"| `do` | Post-update hook (string or funcref) | +"| `on` | On-demand loading: Commands or ``-mappings | +"| `for` | On-demand loading: File types | +"| `frozen` | Do not update unless explicitly specified | +" +" More information: https://github.com/junegunn/vim-plug +" +" +" Copyright (c) 2017 Junegunn Choi +" +" MIT License +" +" Permission is hereby granted, free of charge, to any person obtaining +" a copy of this software and associated documentation files (the +" "Software"), to deal in the Software without restriction, including +" without limitation the rights to use, copy, modify, merge, publish, +" distribute, sublicense, and/or sell copies of the Software, and to +" permit persons to whom the Software is furnished to do so, subject to +" the following conditions: +" +" The above copyright notice and this permission notice shall be +" included in all copies or substantial portions of the Software. +" +" THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, +" EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF +" MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND +" NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE +" LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION +" OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION +" WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. + +if exists('g:loaded_plug') + finish +endif +let g:loaded_plug = 1 + +let s:cpo_save = &cpo +set cpo&vim + +let s:plug_src = 'https://github.com/junegunn/vim-plug.git' +let s:plug_tab = get(s:, 'plug_tab', -1) +let s:plug_buf = get(s:, 'plug_buf', -1) +let s:mac_gui = has('gui_macvim') && has('gui_running') +let s:is_win = has('win32') +let s:nvim = has('nvim-0.2') || (has('nvim') && exists('*jobwait') && !s:is_win) +let s:vim8 = has('patch-8.0.0039') && exists('*job_start') +if s:is_win && &shellslash + set noshellslash + let s:me = resolve(expand(':p')) + set shellslash +else + let s:me = resolve(expand(':p')) +endif +let s:base_spec = { 'branch': '', 'frozen': 0 } +let s:TYPE = { +\ 'string': type(''), +\ 'list': type([]), +\ 'dict': type({}), +\ 'funcref': type(function('call')) +\ } +let s:loaded = get(s:, 'loaded', {}) +let s:triggers = get(s:, 'triggers', {}) + +function! s:is_powershell(shell) + return a:shell =~# 'powershell\(\.exe\)\?$' || a:shell =~# 'pwsh\(\.exe\)\?$' +endfunction + +function! s:isabsolute(dir) abort + return a:dir =~# '^/' || (has('win32') && a:dir =~? '^\%(\\\|[A-Z]:\)') +endfunction + +function! s:git_dir(dir) abort + let gitdir = s:trim(a:dir) . '/.git' + if isdirectory(gitdir) + return gitdir + endif + if !filereadable(gitdir) + return '' + endif + let gitdir = matchstr(get(readfile(gitdir), 0, ''), '^gitdir: \zs.*') + if len(gitdir) && !s:isabsolute(gitdir) + let gitdir = a:dir . '/' . gitdir + endif + return isdirectory(gitdir) ? gitdir : '' +endfunction + +function! s:git_origin_url(dir) abort + let gitdir = s:git_dir(a:dir) + let config = gitdir . '/config' + if empty(gitdir) || !filereadable(config) + return '' + endif + return matchstr(join(readfile(config)), '\[remote "origin"\].\{-}url\s*=\s*\zs\S*\ze') +endfunction + +function! s:git_revision(dir) abort + let gitdir = s:git_dir(a:dir) + let head = gitdir . '/HEAD' + if empty(gitdir) || !filereadable(head) + return '' + endif + + let line = get(readfile(head), 0, '') + let ref = matchstr(line, '^ref: \zs.*') + if empty(ref) + return line + endif + + if filereadable(gitdir . '/' . ref) + return get(readfile(gitdir . '/' . ref), 0, '') + endif + + if filereadable(gitdir . '/packed-refs') + for line in readfile(gitdir . '/packed-refs') + if line =~# ' ' . ref + return matchstr(line, '^[0-9a-f]*') + endif + endfor + endif + + return '' +endfunction + +function! s:git_local_branch(dir) abort + let gitdir = s:git_dir(a:dir) + let head = gitdir . '/HEAD' + if empty(gitdir) || !filereadable(head) + return '' + endif + let branch = matchstr(get(readfile(head), 0, ''), '^ref: refs/heads/\zs.*') + return len(branch) ? branch : 'HEAD' +endfunction + +function! s:git_origin_branch(spec) + if len(a:spec.branch) + return a:spec.branch + endif + + " The file may not be present if this is a local repository + let gitdir = s:git_dir(a:spec.dir) + let origin_head = gitdir.'/refs/remotes/origin/HEAD' + if len(gitdir) && filereadable(origin_head) + return matchstr(get(readfile(origin_head), 0, ''), + \ '^ref: refs/remotes/origin/\zs.*') + endif + + " The command may not return the name of a branch in detached HEAD state + let result = s:lines(s:system('git symbolic-ref --short HEAD', a:spec.dir)) + return v:shell_error ? '' : result[-1] +endfunction + +if s:is_win + function! s:plug_call(fn, ...) + let shellslash = &shellslash + try + set noshellslash + return call(a:fn, a:000) + finally + let &shellslash = shellslash + endtry + endfunction +else + function! s:plug_call(fn, ...) + return call(a:fn, a:000) + endfunction +endif + +function! s:plug_getcwd() + return s:plug_call('getcwd') +endfunction + +function! s:plug_fnamemodify(fname, mods) + return s:plug_call('fnamemodify', a:fname, a:mods) +endfunction + +function! s:plug_expand(fmt) + return s:plug_call('expand', a:fmt, 1) +endfunction + +function! s:plug_tempname() + return s:plug_call('tempname') +endfunction + +function! plug#begin(...) + if a:0 > 0 + let s:plug_home_org = a:1 + let home = s:path(s:plug_fnamemodify(s:plug_expand(a:1), ':p')) + elseif exists('g:plug_home') + let home = s:path(g:plug_home) + elseif !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 exists('g:did_load_filetypes') + filetype off + endif + for name in g:plugs_order + if !has_key(g:plugs, name) + continue + endif + let plug = g:plugs[name] + if get(s:loaded, name, 0) || !s:lazy(plug, 'on') && !s:lazy(plug, 'for') + let s:loaded[name] = 1 + continue + endif + + if has_key(plug, 'on') + let s:triggers[name] = { 'map': [], 'cmd': [] } + for cmd in s:to_a(plug.on) + if cmd =~? '^.\+' + if empty(mapcheck(cmd)) && empty(mapcheck(cmd, 'i')) + call s:assoc(lod.map, cmd, name) + endif + call add(s:triggers[name].map, cmd) + elseif cmd =~# '^[A-Z]' + let cmd = substitute(cmd, '!*$', '', '') + if exists(':'.cmd) != 2 + call s:assoc(lod.cmd, cmd, name) + endif + call add(s:triggers[name].cmd, cmd) + else + call s:err('Invalid `on` option: '.cmd. + \ '. Should start with an uppercase letter or ``.') + endif + endfor + endif + + if has_key(plug, 'for') + let types = s:to_a(plug.for) + if !empty(types) + augroup filetypedetect + call s:source(s:rtp(plug), 'ftdetect/**/*.vim', 'after/ftdetect/**/*.vim') + augroup END + endif + for type in types + call s:assoc(lod.ft, type, name) + endfor + endif + endfor + + for [cmd, names] in items(lod.cmd) + execute printf( + \ 'command! -nargs=* -range -bang -complete=file %s call s:lod_cmd(%s, "", , , , %s)', + \ cmd, string(cmd), string(names)) + endfor + + for [map, names] in items(lod.map) + for [mode, map_prefix, key_prefix] in + \ [['i', '', ''], ['n', '', ''], ['v', '', 'gv'], ['o', '', '']] + execute printf( + \ '%snoremap %s %s:call lod_map(%s, %s, %s, "%s")', + \ mode, map, map_prefix, string(map), string(names), mode != 'i', key_prefix) + endfor + endfor + + for [ft, names] in items(lod.ft) + augroup PlugLOD + execute printf('autocmd FileType %s call lod_ft(%s, %s)', + \ ft, string(ft), string(names)) + augroup END + endfor + + call s:reorg_rtp() + filetype plugin indent on + if has('vim_starting') + if has('syntax') && !exists('g:syntax_on') + syntax enable + end + else + call s:reload_plugins() + endif +endfunction + +function! s:loaded_names() + return filter(copy(g:plugs_order), 'get(s:loaded, v:val, 0)') +endfunction + +function! s:load_plugin(spec) + call s:source(s:rtp(a:spec), 'plugin/**/*.vim', 'after/plugin/**/*.vim') +endfunction + +function! s:reload_plugins() + for name in s:loaded_names() + call s:load_plugin(g:plugs[name]) + endfor +endfunction + +function! s:trim(str) + return substitute(a:str, '[\/]\+$', '', '') +endfunction + +function! s:version_requirement(val, min) + for idx in range(0, len(a:min) - 1) + let v = get(a:val, idx, 0) + if v < a:min[idx] | return 0 + elseif v > a:min[idx] | return 1 + endif + endfor + return 1 +endfunction + +function! s:git_version_requirement(...) + if !exists('s:git_version') + let s:git_version = map(split(split(s:system(['git', '--version']))[2], '\.'), 'str2nr(v:val)') + endif + return s:version_requirement(s:git_version, a:000) +endfunction + +function! s:progress_opt(base) + return a:base && !s:is_win && + \ s:git_version_requirement(1, 7, 1) ? '--progress' : '' +endfunction + +function! s:rtp(spec) + return s:path(a:spec.dir . get(a:spec, 'rtp', '')) +endfunction + +if s:is_win + function! s:path(path) + return s:trim(substitute(a:path, '/', '\', 'g')) + endfunction + + function! s:dirpath(path) + return s:path(a:path) . '\' + endfunction + + function! s:is_local_plug(repo) + return a:repo =~? '^[a-z]:\|^[%~]' + endfunction + + " Copied from fzf + function! s:wrap_cmds(cmds) + let cmds = [ + \ '@echo off', + \ 'setlocal enabledelayedexpansion'] + \ + (type(a:cmds) == type([]) ? a:cmds : [a:cmds]) + \ + ['endlocal'] + if has('iconv') + if !exists('s:codepage') + let s:codepage = libcallnr('kernel32.dll', 'GetACP', 0) + endif + return map(cmds, printf('iconv(v:val."\r", "%s", "cp%d")', &encoding, s:codepage)) + endif + return map(cmds, 'v:val."\r"') + endfunction + + function! s:batchfile(cmd) + let batchfile = s:plug_tempname().'.bat' + call writefile(s:wrap_cmds(a:cmd), batchfile) + let cmd = plug#shellescape(batchfile, {'shell': &shell, 'script': 0}) + if s:is_powershell(&shell) + let cmd = '& ' . cmd + endif + return [batchfile, cmd] + endfunction +else + function! s:path(path) + return s:trim(a:path) + endfunction + + function! s:dirpath(path) + return substitute(a:path, '[/\\]*$', '/', '') + endfunction + + function! s:is_local_plug(repo) + return a:repo[0] =~ '[/$~]' + endfunction +endif + +function! s:err(msg) + echohl ErrorMsg + echom '[vim-plug] '.a:msg + echohl None +endfunction + +function! s:warn(cmd, msg) + echohl WarningMsg + execute a:cmd 'a:msg' + echohl None +endfunction + +function! s:esc(path) + return escape(a:path, ' ') +endfunction + +function! s:escrtp(path) + return escape(a:path, ' ,') +endfunction + +function! s:remove_rtp() + for name in s:loaded_names() + let rtp = s:rtp(g:plugs[name]) + execute 'set rtp-='.s:escrtp(rtp) + let after = globpath(rtp, 'after') + if isdirectory(after) + execute 'set rtp-='.s:escrtp(after) + endif + endfor +endfunction + +function! s:reorg_rtp() + if !empty(s:first_rtp) + execute 'set rtp-='.s:first_rtp + execute 'set rtp-='.s:last_rtp + endif + + " &rtp is modified from outside + if exists('s:prtp') && s:prtp !=# &rtp + call s:remove_rtp() + unlet! s:middle + endif + + let s:middle = get(s:, 'middle', &rtp) + let rtps = map(s:loaded_names(), 's:rtp(g:plugs[v:val])') + let afters = filter(map(copy(rtps), 'globpath(v:val, "after")'), '!empty(v:val)') + let rtp = join(map(rtps, 'escape(v:val, ",")'), ',') + \ . ','.s:middle.',' + \ . join(map(afters, 'escape(v:val, ",")'), ',') + let &rtp = substitute(substitute(rtp, ',,*', ',', 'g'), '^,\|,$', '', 'g') + let s:prtp = &rtp + + if !empty(s:first_rtp) + execute 'set rtp^='.s:first_rtp + execute 'set rtp+='.s:last_rtp + endif +endfunction + +function! s:doautocmd(...) + if exists('#'.join(a:000, '#')) + execute 'doautocmd' ((v:version > 703 || has('patch442')) ? '' : '') join(a:000) + endif +endfunction + +function! s:dobufread(names) + for name in a:names + let path = s:rtp(g:plugs[name]) + for dir in ['ftdetect', 'ftplugin', 'after/ftdetect', 'after/ftplugin'] + if len(finddir(dir, path)) + if exists('#BufRead') + doautocmd BufRead + endif + return + endif + endfor + endfor +endfunction + +function! plug#load(...) + if a:0 == 0 + return s:err('Argument missing: plugin name(s) required') + endif + if !exists('g:plugs') + return s:err('plug#begin was not called') + endif + let names = a:0 == 1 && type(a:1) == s:TYPE.list ? a:1 : a:000 + let unknowns = filter(copy(names), '!has_key(g:plugs, v:val)') + if !empty(unknowns) + let s = len(unknowns) > 1 ? 's' : '' + return s:err(printf('Unknown plugin%s: %s', s, join(unknowns, ', '))) + end + let unloaded = filter(copy(names), '!get(s:loaded, v:val, 0)') + if !empty(unloaded) + for name in unloaded + call s:lod([name], ['ftdetect', 'after/ftdetect', 'plugin', 'after/plugin']) + endfor + call s:dobufread(unloaded) + return 1 + end + return 0 +endfunction + +function! s:remove_triggers(name) + if !has_key(s:triggers, a:name) + return + endif + for cmd in s:triggers[a:name].cmd + execute 'silent! delc' cmd + endfor + for map in s:triggers[a:name].map + execute 'silent! unmap' map + execute 'silent! iunmap' map + endfor + call remove(s:triggers, a:name) +endfunction + +function! s:lod(names, types, ...) + for name in a:names + call s:remove_triggers(name) + let s:loaded[name] = 1 + endfor + call s:reorg_rtp() + + for name in a:names + let rtp = s:rtp(g:plugs[name]) + for dir in a:types + call s:source(rtp, dir.'/**/*.vim') + endfor + if a:0 + if !s:source(rtp, a:1) && !empty(s:glob(rtp, a:2)) + execute 'runtime' a:1 + endif + call s:source(rtp, a:2) + endif + call s:doautocmd('User', name) + endfor +endfunction + +function! s:lod_ft(pat, names) + let syn = 'syntax/'.a:pat.'.vim' + call s:lod(a:names, ['plugin', 'after/plugin'], syn, 'after/'.syn) + execute 'autocmd! PlugLOD FileType' a:pat + call s:doautocmd('filetypeplugin', 'FileType') + call s:doautocmd('filetypeindent', 'FileType') +endfunction + +function! s:lod_cmd(cmd, bang, l1, l2, args, names) + call s:lod(a:names, ['ftdetect', 'after/ftdetect', 'plugin', 'after/plugin']) + call s:dobufread(a:names) + execute printf('%s%s%s %s', (a:l1 == a:l2 ? '' : (a:l1.','.a:l2)), a:cmd, a:bang, a:args) +endfunction + +function! s:lod_map(map, names, with_prefix, prefix) + call s:lod(a:names, ['ftdetect', 'after/ftdetect', 'plugin', 'after/plugin']) + call s:dobufread(a:names) + let extra = '' + while 1 + let c = getchar(0) + if c == 0 + break + endif + let extra .= nr2char(c) + endwhile + + if a:with_prefix + let prefix = v:count ? v:count : '' + let prefix .= '"'.v:register.a:prefix + if mode(1) == 'no' + if v:operator == 'c' + let prefix = "\" . prefix + endif + let prefix .= v:operator + endif + call feedkeys(prefix, 'n') + endif + call feedkeys(substitute(a:map, '^', "\", '') . extra) +endfunction + +function! plug#(repo, ...) + if a:0 > 1 + return s:err('Invalid number of arguments (1..2)') + endif + + try + let repo = s:trim(a:repo) + let opts = a:0 == 1 ? s:parse_options(a:1) : s:base_spec + let name = get(opts, 'as', s:plug_fnamemodify(repo, ':t:s?\.git$??')) + let spec = extend(s:infer_properties(name, repo), opts) + if !has_key(g:plugs, name) + call add(g:plugs_order, name) + endif + let g:plugs[name] = spec + let s:loaded[name] = get(s:loaded, name, 0) + catch + return s:err(repo . ' ' . v:exception) + endtry +endfunction + +function! s:parse_options(arg) + let opts = copy(s:base_spec) + let type = type(a:arg) + let opt_errfmt = 'Invalid argument for "%s" option of :Plug (expected: %s)' + if type == s:TYPE.string + if empty(a:arg) + throw printf(opt_errfmt, 'tag', 'string') + endif + let opts.tag = a:arg + elseif type == s:TYPE.dict + for opt in ['branch', 'tag', 'commit', 'rtp', 'dir', 'as'] + if has_key(a:arg, opt) + \ && (type(a:arg[opt]) != s:TYPE.string || empty(a:arg[opt])) + throw printf(opt_errfmt, opt, 'string') + endif + endfor + for opt in ['on', 'for'] + if has_key(a:arg, opt) + \ && type(a:arg[opt]) != s:TYPE.list + \ && (type(a:arg[opt]) != s:TYPE.string || empty(a:arg[opt])) + throw printf(opt_errfmt, opt, 'string or list') + endif + endfor + if has_key(a:arg, 'do') + \ && type(a:arg.do) != s:TYPE.funcref + \ && (type(a:arg.do) != s:TYPE.string || empty(a:arg.do)) + throw printf(opt_errfmt, 'do', 'string or funcref') + endif + call extend(opts, a:arg) + if has_key(opts, 'dir') + let opts.dir = s:dirpath(s:plug_expand(opts.dir)) + endif + else + throw 'Invalid argument type (expected: string or dictionary)' + endif + return opts +endfunction + +function! s:infer_properties(name, repo) + let repo = a:repo + if s:is_local_plug(repo) + return { 'dir': s:dirpath(s:plug_expand(repo)) } + else + if repo =~ ':' + let uri = repo + else + if repo !~ '/' + throw printf('Invalid argument: %s (implicit `vim-scripts'' expansion is deprecated)', repo) + endif + let fmt = get(g:, 'plug_url_format', 'https://git::@github.com/%s.git') + let uri = printf(fmt, repo) + endif + return { 'dir': s:dirpath(g:plug_home.'/'.a:name), 'uri': uri } + endif +endfunction + +function! s:install(force, names) + call s:update_impl(0, a:force, a:names) +endfunction + +function! s:update(force, names) + call s:update_impl(1, a:force, a:names) +endfunction + +function! plug#helptags() + if !exists('g:plugs') + return s:err('plug#begin was not called') + endif + for spec in values(g:plugs) + let docd = join([s:rtp(spec), 'doc'], '/') + if isdirectory(docd) + silent! execute 'helptags' s:esc(docd) + endif + endfor + return 1 +endfunction + +function! s:syntax() + syntax clear + syntax region plug1 start=/\%1l/ end=/\%2l/ contains=plugNumber + syntax region plug2 start=/\%2l/ end=/\%3l/ contains=plugBracket,plugX + syn match plugNumber /[0-9]\+[0-9.]*/ contained + syn match plugBracket /[[\]]/ contained + syn match plugX /x/ contained + syn match plugDash /^-\{1}\ / + syn match plugPlus /^+/ + syn match plugStar /^*/ + syn match plugMessage /\(^- \)\@<=.*/ + syn match plugName /\(^- \)\@<=[^ ]*:/ + syn match plugSha /\%(: \)\@<=[0-9a-f]\{4,}$/ + syn match plugTag /(tag: [^)]\+)/ + syn match plugInstall /\(^+ \)\@<=[^:]*/ + syn match plugUpdate /\(^* \)\@<=[^:]*/ + syn match plugCommit /^ \X*[0-9a-f]\{7,9} .*/ contains=plugRelDate,plugEdge,plugTag + syn match plugEdge /^ \X\+$/ + syn match plugEdge /^ \X*/ contained nextgroup=plugSha + syn match plugSha /[0-9a-f]\{7,9}/ contained + syn match plugRelDate /([^)]*)$/ contained + syn match plugNotLoaded /(not loaded)$/ + syn match plugError /^x.*/ + syn region plugDeleted start=/^\~ .*/ end=/^\ze\S/ + syn match plugH2 /^.*:\n-\+$/ + syn match plugH2 /^-\{2,}/ + syn keyword Function PlugInstall PlugStatus PlugUpdate PlugClean + hi def link plug1 Title + hi def link plug2 Repeat + hi def link plugH2 Type + hi def link plugX Exception + hi def link plugBracket Structure + hi def link plugNumber Number + + hi def link plugDash Special + hi def link plugPlus Constant + hi def link plugStar Boolean + + hi def link plugMessage Function + hi def link plugName Label + hi def link plugInstall Function + hi def link plugUpdate Type + + hi def link plugError Error + hi def link plugDeleted Ignore + hi def link plugRelDate Comment + hi def link plugEdge PreProc + hi def link plugSha Identifier + hi def link plugTag Constant + + hi def link plugNotLoaded Comment +endfunction + +function! s:lpad(str, len) + return a:str . repeat(' ', a:len - len(a:str)) +endfunction + +function! s:lines(msg) + return split(a:msg, "[\r\n]") +endfunction + +function! s:lastline(msg) + return get(s:lines(a:msg), -1, '') +endfunction + +function! s:new_window() + execute get(g:, 'plug_window', 'vertical topleft new') +endfunction + +function! s:plug_window_exists() + let buflist = tabpagebuflist(s:plug_tab) + return !empty(buflist) && index(buflist, s:plug_buf) >= 0 +endfunction + +function! s:switch_in() + if !s:plug_window_exists() + return 0 + endif + + if winbufnr(0) != s:plug_buf + let s:pos = [tabpagenr(), winnr(), winsaveview()] + execute 'normal!' s:plug_tab.'gt' + let winnr = bufwinnr(s:plug_buf) + execute winnr.'wincmd w' + call add(s:pos, winsaveview()) + else + let s:pos = [winsaveview()] + endif + + setlocal modifiable + return 1 +endfunction + +function! s:switch_out(...) + call winrestview(s:pos[-1]) + setlocal nomodifiable + if a:0 > 0 + execute a:1 + endif + + if len(s:pos) > 1 + execute 'normal!' s:pos[0].'gt' + execute s:pos[1] 'wincmd w' + call winrestview(s:pos[2]) + endif +endfunction + +function! s:finish_bindings() + nnoremap R :call retry() + nnoremap D :PlugDiff + nnoremap S :PlugStatus + nnoremap U :call status_update() + xnoremap U :call status_update() + nnoremap ]] :silent! call section('') + nnoremap [[ :silent! call section('b') +endfunction + +function! s:prepare(...) + if empty(s:plug_getcwd()) + throw 'Invalid current working directory. Cannot proceed.' + endif + + for evar in ['$GIT_DIR', '$GIT_WORK_TREE'] + if exists(evar) + throw evar.' detected. Cannot proceed.' + endif + endfor + + call s:job_abort() + if s:switch_in() + if b:plug_preview == 1 + pc + endif + enew + else + call s:new_window() + endif + + nnoremap q :call close_pane() + if a:0 == 0 + call s:finish_bindings() + endif + let b:plug_preview = -1 + let s:plug_tab = tabpagenr() + let s:plug_buf = winbufnr(0) + call s:assign_name() + + for k in ['', 'L', 'o', 'X', 'd', 'dd'] + execute 'silent! unmap ' k + endfor + setlocal buftype=nofile bufhidden=wipe nobuflisted nolist noswapfile nowrap cursorline modifiable nospell + if exists('+colorcolumn') + setlocal colorcolumn= + endif + setf vim-plug + if exists('g:syntax_on') + call s:syntax() + endif +endfunction + +function! s:close_pane() + if b:plug_preview == 1 + pc + let b:plug_preview = -1 + else + bd + endif +endfunction + +function! s:assign_name() + " Assign buffer name + let prefix = '[Plugins]' + let name = prefix + let idx = 2 + while bufexists(name) + let name = printf('%s (%s)', prefix, idx) + let idx = idx + 1 + endwhile + silent! execute 'f' fnameescape(name) +endfunction + +function! s:chsh(swap) + let prev = [&shell, &shellcmdflag, &shellredir] + if !s:is_win + set shell=sh + endif + if a:swap + if s:is_powershell(&shell) + let &shellredir = '2>&1 | Out-File -Encoding UTF8 %s' + elseif &shell =~# 'sh' || &shell =~# 'cmd\(\.exe\)\?$' + set shellredir=>%s\ 2>&1 + endif + endif + return prev +endfunction + +function! s:bang(cmd, ...) + let batchfile = '' + try + let [sh, shellcmdflag, shrd] = s:chsh(a:0) + " FIXME: Escaping is incomplete. We could use shellescape with eval, + " but it won't work on Windows. + let cmd = a:0 ? s:with_cd(a:cmd, a:1) : a:cmd + if s:is_win + let [batchfile, cmd] = s:batchfile(cmd) + endif + let g:_plug_bang = (s:is_win && has('gui_running') ? 'silent ' : '').'!'.escape(cmd, '#!%') + execute "normal! :execute g:_plug_bang\\" + finally + unlet g:_plug_bang + let [&shell, &shellcmdflag, &shellredir] = [sh, shellcmdflag, shrd] + if s:is_win && filereadable(batchfile) + call delete(batchfile) + endif + endtry + return v:shell_error ? 'Exit status: ' . v:shell_error : '' +endfunction + +function! s:regress_bar() + let bar = substitute(getline(2)[1:-2], '.*\zs=', 'x', '') + call s:progress_bar(2, bar, len(bar)) +endfunction + +function! s:is_updated(dir) + return !empty(s:system_chomp(['git', 'log', '--pretty=format:%h', 'HEAD...HEAD@{1}'], a:dir)) +endfunction + +function! s:do(pull, force, todo) + for [name, spec] in items(a:todo) + if !isdirectory(spec.dir) + continue + endif + let installed = has_key(s:update.new, name) + let updated = installed ? 0 : + \ (a:pull && index(s:update.errors, name) < 0 && s:is_updated(spec.dir)) + if a:force || installed || updated + execute 'cd' s:esc(spec.dir) + call append(3, '- Post-update hook for '. name .' ... ') + let error = '' + let type = type(spec.do) + if type == s:TYPE.string + if spec.do[0] == ':' + if !get(s:loaded, name, 0) + let s:loaded[name] = 1 + call s:reorg_rtp() + endif + call s:load_plugin(spec) + try + execute spec.do[1:] + catch + let error = v:exception + endtry + if !s:plug_window_exists() + cd - + throw 'Warning: vim-plug was terminated by the post-update hook of '.name + endif + else + let error = s:bang(spec.do) + endif + elseif type == s:TYPE.funcref + try + call s:load_plugin(spec) + let status = installed ? 'installed' : (updated ? 'updated' : 'unchanged') + call spec.do({ 'name': name, 'status': status, 'force': a:force }) + catch + let error = v:exception + endtry + else + let error = 'Invalid hook type' + endif + call s:switch_in() + call setline(4, empty(error) ? (getline(4) . 'OK') + \ : ('x' . getline(4)[1:] . error)) + if !empty(error) + call add(s:update.errors, name) + call s:regress_bar() + endif + cd - + endif + endfor +endfunction + +function! s:hash_match(a, b) + return stridx(a:a, a:b) == 0 || stridx(a:b, a:a) == 0 +endfunction + +function! s:checkout(spec) + let sha = a:spec.commit + let output = s:git_revision(a:spec.dir) + if !empty(output) && !s:hash_match(sha, s:lines(output)[0]) + let credential_helper = s:git_version_requirement(2) ? '-c credential.helper= ' : '' + let output = s:system( + \ 'git '.credential_helper.'fetch --depth 999999 && git checkout '.plug#shellescape(sha).' --', a:spec.dir) + endif + return output +endfunction + +function! s:finish(pull) + let new_frozen = len(filter(keys(s:update.new), 'g:plugs[v:val].frozen')) + if new_frozen + let s = new_frozen > 1 ? 's' : '' + call append(3, printf('- Installed %d frozen plugin%s', new_frozen, s)) + endif + call append(3, '- Finishing ... ') | 4 + redraw + call plug#helptags() + call plug#end() + call setline(4, getline(4) . 'Done!') + redraw + let msgs = [] + if !empty(s:update.errors) + call add(msgs, "Press 'R' to retry.") + endif + if a:pull && len(s:update.new) < len(filter(getline(5, '$'), + \ "v:val =~ '^- ' && v:val !~# 'Already up.to.date'")) + call add(msgs, "Press 'D' to see the updated changes.") + endif + echo join(msgs, ' ') + call s:finish_bindings() +endfunction + +function! s:retry() + if empty(s:update.errors) + return + endif + echo + call s:update_impl(s:update.pull, s:update.force, + \ extend(copy(s:update.errors), [s:update.threads])) +endfunction + +function! s:is_managed(name) + return has_key(g:plugs[a:name], 'uri') +endfunction + +function! s:names(...) + return sort(filter(keys(g:plugs), 'stridx(v:val, a:1) == 0 && s:is_managed(v:val)')) +endfunction + +function! s:check_ruby() + silent! ruby require 'thread'; VIM::command("let g:plug_ruby = '#{RUBY_VERSION}'") + if !exists('g:plug_ruby') + redraw! + return s:warn('echom', 'Warning: Ruby interface is broken') + endif + let ruby_version = split(g:plug_ruby, '\.') + unlet g:plug_ruby + return s:version_requirement(ruby_version, [1, 8, 7]) +endfunction + +function! s:update_impl(pull, force, args) abort + let sync = index(a:args, '--sync') >= 0 || has('vim_starting') + let args = filter(copy(a:args), 'v:val != "--sync"') + let threads = (len(args) > 0 && args[-1] =~ '^[1-9][0-9]*$') ? + \ remove(args, -1) : get(g:, 'plug_threads', 16) + + let managed = filter(copy(g:plugs), 's:is_managed(v:key)') + let todo = empty(args) ? filter(managed, '!v:val.frozen || !isdirectory(v:val.dir)') : + \ filter(managed, 'index(args, v:key) >= 0') + + if empty(todo) + return s:warn('echo', 'No plugin to '. (a:pull ? 'update' : 'install')) + endif + + if !s:is_win && s:git_version_requirement(2, 3) + let s:git_terminal_prompt = exists('$GIT_TERMINAL_PROMPT') ? $GIT_TERMINAL_PROMPT : '' + let $GIT_TERMINAL_PROMPT = 0 + for plug in values(todo) + let plug.uri = substitute(plug.uri, + \ '^https://git::@github\.com', 'https://github.com', '') + endfor + endif + + if !isdirectory(g:plug_home) + try + call mkdir(g:plug_home, 'p') + catch + return s:err(printf('Invalid plug directory: %s. '. + \ 'Try to call plug#begin with a valid directory', g:plug_home)) + endtry + endif + + if has('nvim') && !exists('*jobwait') && threads > 1 + call s:warn('echom', '[vim-plug] Update Neovim for parallel installer') + endif + + let use_job = s:nvim || s:vim8 + let python = (has('python') || has('python3')) && !use_job + let ruby = has('ruby') && !use_job && (v:version >= 703 || v:version == 702 && has('patch374')) && !(s:is_win && has('gui_running')) && threads > 1 && s:check_ruby() + + let s:update = { + \ 'start': reltime(), + \ 'all': todo, + \ 'todo': copy(todo), + \ 'errors': [], + \ 'pull': a:pull, + \ 'force': a:force, + \ 'new': {}, + \ 'threads': (python || ruby || use_job) ? min([len(todo), threads]) : 1, + \ 'bar': '', + \ 'fin': 0 + \ } + + call s:prepare(1) + call append(0, ['', '']) + normal! 2G + silent! redraw + + let s:clone_opt = [] + if get(g:, 'plug_shallow', 1) + call extend(s:clone_opt, ['--depth', '1']) + if s:git_version_requirement(1, 7, 10) + call add(s:clone_opt, '--no-single-branch') + endif + endif + + if has('win32unix') || has('wsl') + call extend(s:clone_opt, ['-c', 'core.eol=lf', '-c', 'core.autocrlf=input']) + endif + + let s:submodule_opt = s:git_version_requirement(2, 8) ? ' --jobs='.threads : '' + + " Python version requirement (>= 2.7) + if python && !has('python3') && !ruby && !use_job && s:update.threads > 1 + redir => pyv + silent python import platform; print platform.python_version() + redir END + let python = s:version_requirement( + \ map(split(split(pyv)[0], '\.'), 'str2nr(v:val)'), [2, 6]) + endif + + if (python || ruby) && s:update.threads > 1 + try + let imd = &imd + if s:mac_gui + set noimd + endif + if ruby + call s:update_ruby() + else + call s:update_python() + endif + catch + let lines = getline(4, '$') + let printed = {} + silent! 4,$d _ + for line in lines + let name = s:extract_name(line, '.', '') + if empty(name) || !has_key(printed, name) + call append('$', line) + if !empty(name) + let printed[name] = 1 + if line[0] == 'x' && index(s:update.errors, name) < 0 + call add(s:update.errors, name) + end + endif + endif + endfor + finally + let &imd = imd + call s:update_finish() + endtry + else + call s:update_vim() + while use_job && sync + sleep 100m + if s:update.fin + break + endif + endwhile + endif +endfunction + +function! s:log4(name, msg) + call setline(4, printf('- %s (%s)', a:msg, a:name)) + redraw +endfunction + +function! s:update_finish() + if exists('s:git_terminal_prompt') + let $GIT_TERMINAL_PROMPT = s:git_terminal_prompt + endif + if s:switch_in() + call append(3, '- Updating ...') | 4 + for [name, spec] in items(filter(copy(s:update.all), 'index(s:update.errors, v:key) < 0 && (s:update.force || s:update.pull || has_key(s:update.new, v:key))')) + let [pos, _] = s:logpos(name) + if !pos + continue + endif + if has_key(spec, 'commit') + call s:log4(name, 'Checking out '.spec.commit) + let out = s:checkout(spec) + elseif has_key(spec, 'tag') + let tag = spec.tag + if tag =~ '\*' + let tags = s:lines(s:system('git tag --list '.plug#shellescape(tag).' --sort -version:refname 2>&1', spec.dir)) + if !v:shell_error && !empty(tags) + let tag = tags[0] + call s:log4(name, printf('Latest tag for %s -> %s', spec.tag, tag)) + call append(3, '') + endif + endif + call s:log4(name, 'Checking out '.tag) + let out = s:system('git checkout -q '.plug#shellescape(tag).' -- 2>&1', spec.dir) + else + let branch = s:git_origin_branch(spec) + call s:log4(name, 'Merging origin/'.s:esc(branch)) + let out = s:system('git checkout -q '.plug#shellescape(branch).' -- 2>&1' + \. (has_key(s:update.new, name) ? '' : ('&& git merge --ff-only '.plug#shellescape('origin/'.branch).' 2>&1')), spec.dir) + endif + if !v:shell_error && filereadable(spec.dir.'/.gitmodules') && + \ (s:update.force || has_key(s:update.new, name) || s:is_updated(spec.dir)) + call s:log4(name, 'Updating submodules. This may take a while.') + let out .= s:bang('git submodule update --init --recursive'.s:submodule_opt.' 2>&1', spec.dir) + endif + let msg = s:format_message(v:shell_error ? 'x': '-', name, out) + if v:shell_error + call add(s:update.errors, name) + call s:regress_bar() + silent execute pos 'd _' + call append(4, msg) | 4 + elseif !empty(out) + call setline(pos, msg[0]) + endif + redraw + endfor + silent 4 d _ + try + call s:do(s:update.pull, s:update.force, filter(copy(s:update.all), 'index(s:update.errors, v:key) < 0 && has_key(v:val, "do")')) + catch + call s:warn('echom', v:exception) + call s:warn('echo', '') + return + endtry + call s:finish(s:update.pull) + call setline(1, 'Updated. Elapsed time: ' . split(reltimestr(reltime(s:update.start)))[0] . ' sec.') + call s:switch_out('normal! gg') + endif +endfunction + +function! s:job_abort() + if (!s:nvim && !s:vim8) || !exists('s:jobs') + return + endif + + for [name, j] in items(s:jobs) + if s:nvim + silent! call jobstop(j.jobid) + elseif s:vim8 + silent! call job_stop(j.jobid) + endif + if j.new + call s:rm_rf(g:plugs[name].dir) + endif + endfor + let s:jobs = {} +endfunction + +function! s:last_non_empty_line(lines) + let len = len(a:lines) + for idx in range(len) + let line = a:lines[len-idx-1] + if !empty(line) + return line + endif + endfor + return '' +endfunction + +function! s:job_out_cb(self, data) abort + let self = a:self + let data = remove(self.lines, -1) . a:data + let lines = map(split(data, "\n", 1), 'split(v:val, "\r", 1)[-1]') + call extend(self.lines, lines) + " To reduce the number of buffer updates + let self.tick = get(self, 'tick', -1) + 1 + if !self.running || self.tick % len(s:jobs) == 0 + let bullet = self.running ? (self.new ? '+' : '*') : (self.error ? 'x' : '-') + let result = self.error ? join(self.lines, "\n") : s:last_non_empty_line(self.lines) + call s:log(bullet, self.name, result) + endif +endfunction + +function! s:job_exit_cb(self, data) abort + let a:self.running = 0 + let a:self.error = a:data != 0 + call s:reap(a:self.name) + call s:tick() +endfunction + +function! s:job_cb(fn, job, ch, data) + if !s:plug_window_exists() " plug window closed + return s:job_abort() + endif + call call(a:fn, [a:job, a:data]) +endfunction + +function! s:nvim_cb(job_id, data, event) dict abort + return (a:event == 'stdout' || a:event == 'stderr') ? + \ s:job_cb('s:job_out_cb', self, 0, join(a:data, "\n")) : + \ s:job_cb('s:job_exit_cb', self, 0, a:data) +endfunction + +function! s:spawn(name, cmd, opts) + let job = { 'name': a:name, 'running': 1, 'error': 0, 'lines': [''], + \ 'new': get(a:opts, 'new', 0) } + let s:jobs[a:name] = job + + if s:nvim + if has_key(a:opts, 'dir') + let job.cwd = a:opts.dir + endif + let argv = a:cmd + call extend(job, { + \ 'on_stdout': function('s:nvim_cb'), + \ 'on_stderr': function('s:nvim_cb'), + \ 'on_exit': function('s:nvim_cb'), + \ }) + let jid = s:plug_call('jobstart', argv, job) + if jid > 0 + let job.jobid = jid + else + let job.running = 0 + let job.error = 1 + let job.lines = [jid < 0 ? argv[0].' is not executable' : + \ 'Invalid arguments (or job table is full)'] + endif + elseif s:vim8 + let cmd = join(map(copy(a:cmd), 'plug#shellescape(v:val, {"script": 0})')) + if has_key(a:opts, 'dir') + let cmd = s:with_cd(cmd, a:opts.dir, 0) + endif + let argv = s:is_win ? ['cmd', '/s', '/c', '"'.cmd.'"'] : ['sh', '-c', cmd] + let jid = job_start(s:is_win ? join(argv, ' ') : argv, { + \ 'out_cb': function('s:job_cb', ['s:job_out_cb', job]), + \ 'err_cb': function('s:job_cb', ['s:job_out_cb', job]), + \ 'exit_cb': function('s:job_cb', ['s:job_exit_cb', job]), + \ 'err_mode': 'raw', + \ 'out_mode': 'raw' + \}) + if job_status(jid) == 'run' + let job.jobid = jid + else + let job.running = 0 + let job.error = 1 + let job.lines = ['Failed to start job'] + endif + else + let job.lines = s:lines(call('s:system', has_key(a:opts, 'dir') ? [a:cmd, a:opts.dir] : [a:cmd])) + let job.error = v:shell_error != 0 + let job.running = 0 + endif +endfunction + +function! s:reap(name) + let job = s:jobs[a:name] + if job.error + call add(s:update.errors, a:name) + elseif get(job, 'new', 0) + let s:update.new[a:name] = 1 + endif + let s:update.bar .= job.error ? 'x' : '=' + + let bullet = job.error ? 'x' : '-' + let result = job.error ? join(job.lines, "\n") : s:last_non_empty_line(job.lines) + call s:log(bullet, a:name, empty(result) ? 'OK' : result) + call s:bar() + + call remove(s:jobs, a:name) +endfunction + +function! s:bar() + if s:switch_in() + let total = len(s:update.all) + call setline(1, (s:update.pull ? 'Updating' : 'Installing'). + \ ' plugins ('.len(s:update.bar).'/'.total.')') + call s:progress_bar(2, s:update.bar, total) + call s:switch_out() + endif +endfunction + +function! s:logpos(name) + let max = line('$') + for i in range(4, max > 4 ? max : 4) + if getline(i) =~# '^[-+x*] '.a:name.':' + for j in range(i + 1, max > 5 ? max : 5) + if getline(j) !~ '^ ' + return [i, j - 1] + endif + endfor + return [i, i] + endif + endfor + return [0, 0] +endfunction + +function! s:log(bullet, name, lines) + if s:switch_in() + let [b, e] = s:logpos(a:name) + if b > 0 + silent execute printf('%d,%d d _', b, e) + if b > winheight('.') + let b = 4 + endif + else + let b = 4 + endif + " FIXME For some reason, nomodifiable is set after :d in vim8 + setlocal modifiable + call append(b - 1, s:format_message(a:bullet, a:name, a:lines)) + call s:switch_out() + endif +endfunction + +function! s:update_vim() + let s:jobs = {} + + call s:bar() + call s:tick() +endfunction + +function! s:tick() + let pull = s:update.pull + let prog = s:progress_opt(s:nvim || s:vim8) +while 1 " Without TCO, Vim stack is bound to explode + if empty(s:update.todo) + if empty(s:jobs) && !s:update.fin + call s:update_finish() + let s:update.fin = 1 + endif + return + endif + + let name = keys(s:update.todo)[0] + let spec = remove(s:update.todo, name) + let new = empty(globpath(spec.dir, '.git', 1)) + + call s:log(new ? '+' : '*', name, pull ? 'Updating ...' : 'Installing ...') + redraw + + let has_tag = has_key(spec, 'tag') + if !new + let [error, _] = s:git_validate(spec, 0) + if empty(error) + if pull + let cmd = s:git_version_requirement(2) ? ['git', '-c', 'credential.helper=', 'fetch'] : ['git', 'fetch'] + if has_tag && !empty(globpath(spec.dir, '.git/shallow')) + call extend(cmd, ['--depth', '99999999']) + endif + if !empty(prog) + call add(cmd, prog) + endif + call s:spawn(name, cmd, { 'dir': spec.dir }) + else + let s:jobs[name] = { 'running': 0, 'lines': ['Already installed'], 'error': 0 } + endif + else + let s:jobs[name] = { 'running': 0, 'lines': s:lines(error), 'error': 1 } + endif + else + let cmd = ['git', 'clone'] + if !has_tag + call extend(cmd, s:clone_opt) + endif + if !empty(prog) + call add(cmd, prog) + endif + call s:spawn(name, extend(cmd, [spec.uri, s:trim(spec.dir)]), { 'new': 1 }) + endif + + if !s:jobs[name].running + call s:reap(name) + endif + if len(s:jobs) >= s:update.threads + break + endif +endwhile +endfunction + +function! s:update_python() +let py_exe = has('python') ? 'python' : 'python3' +execute py_exe "<< EOF" +import datetime +import functools +import os +try: + import queue +except ImportError: + import Queue as queue +import random +import re +import shutil +import signal +import subprocess +import tempfile +import threading as thr +import time +import traceback +import vim + +G_NVIM = vim.eval("has('nvim')") == '1' +G_PULL = vim.eval('s:update.pull') == '1' +G_RETRIES = int(vim.eval('get(g:, "plug_retries", 2)')) + 1 +G_TIMEOUT = int(vim.eval('get(g:, "plug_timeout", 60)')) +G_CLONE_OPT = ' '.join(vim.eval('s:clone_opt')) +G_PROGRESS = vim.eval('s:progress_opt(1)') +G_LOG_PROB = 1.0 / int(vim.eval('s:update.threads')) +G_STOP = thr.Event() +G_IS_WIN = vim.eval('s:is_win') == '1' + +class PlugError(Exception): + def __init__(self, msg): + self.msg = msg +class CmdTimedOut(PlugError): + pass +class CmdFailed(PlugError): + pass +class InvalidURI(PlugError): + pass +class Action(object): + INSTALL, UPDATE, ERROR, DONE = ['+', '*', 'x', '-'] + +class Buffer(object): + def __init__(self, lock, num_plugs, is_pull): + self.bar = '' + self.event = 'Updating' if is_pull else 'Installing' + self.lock = lock + self.maxy = int(vim.eval('winheight(".")')) + self.num_plugs = num_plugs + + def __where(self, name): + """ Find first line with name in current buffer. Return line num. """ + found, lnum = False, 0 + matcher = re.compile('^[-+x*] {0}:'.format(name)) + for line in vim.current.buffer: + if matcher.search(line) is not None: + found = True + break + lnum += 1 + + if not found: + lnum = -1 + return lnum + + def header(self): + curbuf = vim.current.buffer + curbuf[0] = self.event + ' plugins ({0}/{1})'.format(len(self.bar), self.num_plugs) + + num_spaces = self.num_plugs - len(self.bar) + curbuf[1] = '[{0}{1}]'.format(self.bar, num_spaces * ' ') + + with self.lock: + vim.command('normal! 2G') + vim.command('redraw') + + def write(self, action, name, lines): + first, rest = lines[0], lines[1:] + msg = ['{0} {1}{2}{3}'.format(action, name, ': ' if first else '', first)] + msg.extend([' ' + line for line in rest]) + + try: + if action == Action.ERROR: + self.bar += 'x' + vim.command("call add(s:update.errors, '{0}')".format(name)) + elif action == Action.DONE: + self.bar += '=' + + curbuf = vim.current.buffer + lnum = self.__where(name) + if lnum != -1: # Found matching line num + del curbuf[lnum] + if lnum > self.maxy and action in set([Action.INSTALL, Action.UPDATE]): + lnum = 3 + else: + lnum = 3 + curbuf.append(msg, lnum) + + self.header() + except vim.error: + pass + +class Command(object): + CD = 'cd /d' if G_IS_WIN else 'cd' + + def __init__(self, cmd, cmd_dir=None, timeout=60, cb=None, clean=None): + self.cmd = cmd + if cmd_dir: + self.cmd = '{0} {1} && {2}'.format(Command.CD, cmd_dir, self.cmd) + self.timeout = timeout + self.callback = cb if cb else (lambda msg: None) + self.clean = clean if clean else (lambda: None) + self.proc = None + + @property + def alive(self): + """ Returns true only if command still running. """ + return self.proc and self.proc.poll() is None + + def execute(self, ntries=3): + """ Execute the command with ntries if CmdTimedOut. + Returns the output of the command if no Exception. + """ + attempt, finished, limit = 0, False, self.timeout + + while not finished: + try: + attempt += 1 + result = self.try_command() + finished = True + return result + except CmdTimedOut: + if attempt != ntries: + self.notify_retry() + self.timeout += limit + else: + raise + + def notify_retry(self): + """ Retry required for command, notify user. """ + for count in range(3, 0, -1): + if G_STOP.is_set(): + raise KeyboardInterrupt + msg = 'Timeout. Will retry in {0} second{1} ...'.format( + count, 's' if count != 1 else '') + self.callback([msg]) + time.sleep(1) + self.callback(['Retrying ...']) + + def try_command(self): + """ Execute a cmd & poll for callback. Returns list of output. + Raises CmdFailed -> return code for Popen isn't 0 + Raises CmdTimedOut -> command exceeded timeout without new output + """ + first_line = True + + try: + tfile = tempfile.NamedTemporaryFile(mode='w+b') + preexec_fn = not G_IS_WIN and os.setsid or None + self.proc = subprocess.Popen(self.cmd, stdout=tfile, + stderr=subprocess.STDOUT, + stdin=subprocess.PIPE, shell=True, + preexec_fn=preexec_fn) + thrd = thr.Thread(target=(lambda proc: proc.wait()), args=(self.proc,)) + thrd.start() + + thread_not_started = True + while thread_not_started: + try: + thrd.join(0.1) + thread_not_started = False + except RuntimeError: + pass + + while self.alive: + if G_STOP.is_set(): + raise KeyboardInterrupt + + if first_line or random.random() < G_LOG_PROB: + first_line = False + line = '' if G_IS_WIN else nonblock_read(tfile.name) + if line: + self.callback([line]) + + time_diff = time.time() - os.path.getmtime(tfile.name) + if time_diff > self.timeout: + raise CmdTimedOut(['Timeout!']) + + thrd.join(0.5) + + tfile.seek(0) + result = [line.decode('utf-8', 'replace').rstrip() for line in tfile] + + if self.proc.returncode != 0: + raise CmdFailed([''] + result) + + return result + except: + self.terminate() + raise + + def terminate(self): + """ Terminate process and cleanup. """ + if self.alive: + if G_IS_WIN: + os.kill(self.proc.pid, signal.SIGINT) + else: + os.killpg(self.proc.pid, signal.SIGTERM) + self.clean() + +class Plugin(object): + def __init__(self, name, args, buf_q, lock): + self.name = name + self.args = args + self.buf_q = buf_q + self.lock = lock + self.tag = args.get('tag', 0) + + def manage(self): + try: + if os.path.exists(self.args['dir']): + self.update() + else: + self.install() + with self.lock: + thread_vim_command("let s:update.new['{0}'] = 1".format(self.name)) + except PlugError as exc: + self.write(Action.ERROR, self.name, exc.msg) + except KeyboardInterrupt: + G_STOP.set() + self.write(Action.ERROR, self.name, ['Interrupted!']) + except: + # Any exception except those above print stack trace + msg = 'Trace:\n{0}'.format(traceback.format_exc().rstrip()) + self.write(Action.ERROR, self.name, msg.split('\n')) + raise + + def install(self): + target = self.args['dir'] + if target[-1] == '\\': + target = target[0:-1] + + def clean(target): + def _clean(): + try: + shutil.rmtree(target) + except OSError: + pass + return _clean + + self.write(Action.INSTALL, self.name, ['Installing ...']) + callback = functools.partial(self.write, Action.INSTALL, self.name) + cmd = 'git clone {0} {1} {2} {3} 2>&1'.format( + '' if self.tag else G_CLONE_OPT, G_PROGRESS, self.args['uri'], + esc(target)) + com = Command(cmd, None, G_TIMEOUT, callback, clean(target)) + result = com.execute(G_RETRIES) + self.write(Action.DONE, self.name, result[-1:]) + + def repo_uri(self): + cmd = 'git rev-parse --abbrev-ref HEAD 2>&1 && git config -f .git/config remote.origin.url' + command = Command(cmd, self.args['dir'], G_TIMEOUT,) + result = command.execute(G_RETRIES) + return result[-1] + + def update(self): + actual_uri = self.repo_uri() + expect_uri = self.args['uri'] + regex = re.compile(r'^(?:\w+://)?(?:[^@/]*@)?([^:/]*(?::[0-9]*)?)[:/](.*?)(?:\.git)?/?$') + ma = regex.match(actual_uri) + mb = regex.match(expect_uri) + if ma is None or mb is None or ma.groups() != mb.groups(): + msg = ['', + 'Invalid URI: {0}'.format(actual_uri), + 'Expected {0}'.format(expect_uri), + 'PlugClean required.'] + raise InvalidURI(msg) + + if G_PULL: + self.write(Action.UPDATE, self.name, ['Updating ...']) + callback = functools.partial(self.write, Action.UPDATE, self.name) + fetch_opt = '--depth 99999999' if self.tag and os.path.isfile(os.path.join(self.args['dir'], '.git/shallow')) else '' + cmd = 'git fetch {0} {1} 2>&1'.format(fetch_opt, G_PROGRESS) + com = Command(cmd, self.args['dir'], G_TIMEOUT, callback) + result = com.execute(G_RETRIES) + self.write(Action.DONE, self.name, result[-1:]) + else: + self.write(Action.DONE, self.name, ['Already installed']) + + def write(self, action, name, msg): + self.buf_q.put((action, name, msg)) + +class PlugThread(thr.Thread): + def __init__(self, tname, args): + super(PlugThread, self).__init__() + self.tname = tname + self.args = args + + def run(self): + thr.current_thread().name = self.tname + buf_q, work_q, lock = self.args + + try: + while not G_STOP.is_set(): + name, args = work_q.get_nowait() + plug = Plugin(name, args, buf_q, lock) + plug.manage() + work_q.task_done() + except queue.Empty: + pass + +class RefreshThread(thr.Thread): + def __init__(self, lock): + super(RefreshThread, self).__init__() + self.lock = lock + self.running = True + + def run(self): + while self.running: + with self.lock: + thread_vim_command('noautocmd normal! a') + time.sleep(0.33) + + def stop(self): + self.running = False + +if G_NVIM: + def thread_vim_command(cmd): + vim.session.threadsafe_call(lambda: vim.command(cmd)) +else: + def thread_vim_command(cmd): + vim.command(cmd) + +def esc(name): + return '"' + name.replace('"', '\"') + '"' + +def nonblock_read(fname): + """ Read a file with nonblock flag. Return the last line. """ + fread = os.open(fname, os.O_RDONLY | os.O_NONBLOCK) + buf = os.read(fread, 100000).decode('utf-8', 'replace') + os.close(fread) + + line = buf.rstrip('\r\n') + left = max(line.rfind('\r'), line.rfind('\n')) + if left != -1: + left += 1 + line = line[left:] + + return line + +def main(): + thr.current_thread().name = 'main' + nthreads = int(vim.eval('s:update.threads')) + plugs = vim.eval('s:update.todo') + mac_gui = vim.eval('s:mac_gui') == '1' + + lock = thr.Lock() + buf = Buffer(lock, len(plugs), G_PULL) + buf_q, work_q = queue.Queue(), queue.Queue() + for work in plugs.items(): + work_q.put(work) + + start_cnt = thr.active_count() + for num in range(nthreads): + tname = 'PlugT-{0:02}'.format(num) + thread = PlugThread(tname, (buf_q, work_q, lock)) + thread.start() + if mac_gui: + rthread = RefreshThread(lock) + rthread.start() + + while not buf_q.empty() or thr.active_count() != start_cnt: + try: + action, name, msg = buf_q.get(True, 0.25) + buf.write(action, name, ['OK'] if not msg else msg) + buf_q.task_done() + except queue.Empty: + pass + except KeyboardInterrupt: + G_STOP.set() + + if mac_gui: + rthread.stop() + rthread.join() + +main() +EOF +endfunction + +function! s:update_ruby() + ruby << EOF + module PlugStream + SEP = ["\r", "\n", nil] + def get_line + buffer = '' + loop do + char = readchar rescue return + if SEP.include? char.chr + buffer << $/ + break + else + buffer << char + end + end + buffer + end + end unless defined?(PlugStream) + + def esc arg + %["#{arg.gsub('"', '\"')}"] + end + + def killall pid + pids = [pid] + if /mswin|mingw|bccwin/ =~ RUBY_PLATFORM + pids.each { |pid| Process.kill 'INT', pid.to_i rescue nil } + else + unless `which pgrep 2> /dev/null`.empty? + children = pids + until children.empty? + children = children.map { |pid| + `pgrep -P #{pid}`.lines.map { |l| l.chomp } + }.flatten + pids += children + end + end + pids.each { |pid| Process.kill 'TERM', pid.to_i rescue nil } + end + end + + def compare_git_uri a, b + regex = %r{^(?:\w+://)?(?:[^@/]*@)?([^:/]*(?::[0-9]*)?)[:/](.*?)(?:\.git)?/?$} + regex.match(a).to_a.drop(1) == regex.match(b).to_a.drop(1) + end + + require 'thread' + require 'fileutils' + require 'timeout' + running = true + iswin = VIM::evaluate('s:is_win').to_i == 1 + pull = VIM::evaluate('s:update.pull').to_i == 1 + base = VIM::evaluate('g:plug_home') + all = VIM::evaluate('s:update.todo') + limit = VIM::evaluate('get(g:, "plug_timeout", 60)') + tries = VIM::evaluate('get(g:, "plug_retries", 2)') + 1 + nthr = VIM::evaluate('s:update.threads').to_i + maxy = VIM::evaluate('winheight(".")').to_i + vim7 = VIM::evaluate('v:version').to_i <= 703 && RUBY_PLATFORM =~ /darwin/ + cd = iswin ? 'cd /d' : 'cd' + tot = VIM::evaluate('len(s:update.todo)') || 0 + bar = '' + skip = 'Already installed' + mtx = Mutex.new + take1 = proc { mtx.synchronize { running && all.shift } } + logh = proc { + cnt = bar.length + $curbuf[1] = "#{pull ? 'Updating' : 'Installing'} plugins (#{cnt}/#{tot})" + $curbuf[2] = '[' + bar.ljust(tot) + ']' + VIM::command('normal! 2G') + VIM::command('redraw') + } + where = proc { |name| (1..($curbuf.length)).find { |l| $curbuf[l] =~ /^[-+x*] #{name}:/ } } + log = proc { |name, result, type| + mtx.synchronize do + ing = ![true, false].include?(type) + bar += type ? '=' : 'x' unless ing + b = case type + when :install then '+' when :update then '*' + when true, nil then '-' else + VIM::command("call add(s:update.errors, '#{name}')") + 'x' + end + result = + if type || type.nil? + ["#{b} #{name}: #{result.lines.to_a.last || 'OK'}"] + elsif result =~ /^Interrupted|^Timeout/ + ["#{b} #{name}: #{result}"] + else + ["#{b} #{name}"] + result.lines.map { |l| " " << l } + end + if lnum = where.call(name) + $curbuf.delete lnum + lnum = 4 if ing && lnum > maxy + end + result.each_with_index do |line, offset| + $curbuf.append((lnum || 4) - 1 + offset, line.gsub(/\e\[./, '').chomp) + end + logh.call + end + } + bt = proc { |cmd, name, type, cleanup| + tried = timeout = 0 + begin + tried += 1 + timeout += limit + fd = nil + data = '' + if iswin + Timeout::timeout(timeout) do + tmp = VIM::evaluate('tempname()') + system("(#{cmd}) > #{tmp}") + data = File.read(tmp).chomp + File.unlink tmp rescue nil + end + else + fd = IO.popen(cmd).extend(PlugStream) + first_line = true + log_prob = 1.0 / nthr + while line = Timeout::timeout(timeout) { fd.get_line } + data << line + log.call name, line.chomp, type if name && (first_line || rand < log_prob) + first_line = false + end + fd.close + end + [$? == 0, data.chomp] + rescue Timeout::Error, Interrupt => e + if fd && !fd.closed? + killall fd.pid + fd.close + end + cleanup.call if cleanup + if e.is_a?(Timeout::Error) && tried < tries + 3.downto(1) do |countdown| + s = countdown > 1 ? 's' : '' + log.call name, "Timeout. Will retry in #{countdown} second#{s} ...", type + sleep 1 + end + log.call name, 'Retrying ...', type + retry + end + [false, e.is_a?(Interrupt) ? "Interrupted!" : "Timeout!"] + end + } + main = Thread.current + threads = [] + watcher = Thread.new { + if vim7 + while VIM::evaluate('getchar(1)') + sleep 0.1 + end + else + require 'io/console' # >= Ruby 1.9 + nil until IO.console.getch == 3.chr + end + mtx.synchronize do + running = false + threads.each { |t| t.raise Interrupt } unless vim7 + end + threads.each { |t| t.join rescue nil } + main.kill + } + refresh = Thread.new { + while true + mtx.synchronize do + break unless running + VIM::command('noautocmd normal! a') + end + sleep 0.2 + end + } if VIM::evaluate('s:mac_gui') == 1 + + clone_opt = VIM::evaluate('s:clone_opt').join(' ') + progress = VIM::evaluate('s:progress_opt(1)') + nthr.times do + mtx.synchronize do + threads << Thread.new { + while pair = take1.call + name = pair.first + dir, uri, tag = pair.last.values_at *%w[dir uri tag] + exists = File.directory? dir + ok, result = + if exists + chdir = "#{cd} #{iswin ? dir : esc(dir)}" + ret, data = bt.call "#{chdir} && git rev-parse --abbrev-ref HEAD 2>&1 && git config -f .git/config remote.origin.url", nil, nil, nil + current_uri = data.lines.to_a.last + if !ret + if data =~ /^Interrupted|^Timeout/ + [false, data] + else + [false, [data.chomp, "PlugClean required."].join($/)] + end + elsif !compare_git_uri(current_uri, uri) + [false, ["Invalid URI: #{current_uri}", + "Expected: #{uri}", + "PlugClean required."].join($/)] + else + if pull + log.call name, 'Updating ...', :update + fetch_opt = (tag && File.exist?(File.join(dir, '.git/shallow'))) ? '--depth 99999999' : '' + bt.call "#{chdir} && git fetch #{fetch_opt} #{progress} 2>&1", name, :update, nil + else + [true, skip] + end + end + else + d = esc dir.sub(%r{[\\/]+$}, '') + log.call name, 'Installing ...', :install + bt.call "git clone #{clone_opt unless tag} #{progress} #{uri} #{d} 2>&1", name, :install, proc { + FileUtils.rm_rf dir + } + end + mtx.synchronize { VIM::command("let s:update.new['#{name}'] = 1") } if !exists && ok + log.call name, result, ok + end + } if running + end + end + threads.each { |t| t.join rescue nil } + logh.call + refresh.kill if refresh + watcher.kill +EOF +endfunction + +function! s:shellesc_cmd(arg, script) + let escaped = substitute('"'.a:arg.'"', '[&|<>()@^!"]', '^&', 'g') + return substitute(escaped, '%', (a:script ? '%' : '^') . '&', 'g') +endfunction + +function! s:shellesc_ps1(arg) + return "'".substitute(escape(a:arg, '\"'), "'", "''", 'g')."'" +endfunction + +function! s:shellesc_sh(arg) + return "'".substitute(a:arg, "'", "'\\\\''", 'g')."'" +endfunction + +" Escape the shell argument based on the shell. +" Vim and Neovim's shellescape() are insufficient. +" 1. shellslash determines whether to use single/double quotes. +" Double-quote escaping is fragile for cmd.exe. +" 2. It does not work for powershell. +" 3. It does not work for *sh shells if the command is executed +" via cmd.exe (ie. cmd.exe /c sh -c command command_args) +" 4. It does not support batchfile syntax. +" +" Accepts an optional dictionary with the following keys: +" - shell: same as Vim/Neovim 'shell' option. +" If unset, fallback to 'cmd.exe' on Windows or 'sh'. +" - script: If truthy and shell is cmd.exe, escape for batchfile syntax. +function! plug#shellescape(arg, ...) + if a:arg =~# '^[A-Za-z0-9_/:.-]\+$' + return a:arg + endif + let opts = a:0 > 0 && type(a:1) == s:TYPE.dict ? a:1 : {} + let shell = get(opts, 'shell', s:is_win ? 'cmd.exe' : 'sh') + let script = get(opts, 'script', 1) + if shell =~# 'cmd\(\.exe\)\?$' + return s:shellesc_cmd(a:arg, script) + elseif s:is_powershell(shell) + return s:shellesc_ps1(a:arg) + endif + return s:shellesc_sh(a:arg) +endfunction + +function! s:glob_dir(path) + return map(filter(s:glob(a:path, '**'), 'isdirectory(v:val)'), 's:dirpath(v:val)') +endfunction + +function! s:progress_bar(line, bar, total) + call setline(a:line, '[' . s:lpad(a:bar, a:total) . ']') +endfunction + +function! s:compare_git_uri(a, b) + " See `git help clone' + " https:// [user@] github.com[:port] / junegunn/vim-plug [.git] + " [git@] github.com[:port] : junegunn/vim-plug [.git] + " file:// / junegunn/vim-plug [/] + " / junegunn/vim-plug [/] + let pat = '^\%(\w\+://\)\='.'\%([^@/]*@\)\='.'\([^:/]*\%(:[0-9]*\)\=\)'.'[:/]'.'\(.\{-}\)'.'\%(\.git\)\=/\?$' + let ma = matchlist(a:a, pat) + let mb = matchlist(a:b, pat) + return ma[1:2] ==# mb[1:2] +endfunction + +function! s:format_message(bullet, name, message) + if a:bullet != 'x' + return [printf('%s %s: %s', a:bullet, a:name, s:lastline(a:message))] + else + let lines = map(s:lines(a:message), '" ".v:val') + return extend([printf('x %s:', a:name)], lines) + endif +endfunction + +function! s:with_cd(cmd, dir, ...) + let script = a:0 > 0 ? a:1 : 1 + return printf('cd%s %s && %s', s:is_win ? ' /d' : '', plug#shellescape(a:dir, {'script': script}), a:cmd) +endfunction + +function! s:system(cmd, ...) + let batchfile = '' + try + let [sh, shellcmdflag, shrd] = s:chsh(1) + if type(a:cmd) == s:TYPE.list + " Neovim's system() supports list argument to bypass the shell + " but it cannot set the working directory for the command. + " Assume that the command does not rely on the shell. + if has('nvim') && a:0 == 0 + return system(a:cmd) + endif + let cmd = join(map(copy(a:cmd), 'plug#shellescape(v:val, {"shell": &shell, "script": 0})')) + if s:is_powershell(&shell) + let cmd = '& ' . cmd + endif + else + let cmd = a:cmd + endif + if a:0 > 0 + let cmd = s:with_cd(cmd, a:1, type(a:cmd) != s:TYPE.list) + endif + if s:is_win && type(a:cmd) != s:TYPE.list + let [batchfile, cmd] = s:batchfile(cmd) + endif + return system(cmd) + finally + let [&shell, &shellcmdflag, &shellredir] = [sh, shellcmdflag, shrd] + if s:is_win && filereadable(batchfile) + call delete(batchfile) + endif + endtry +endfunction + +function! s:system_chomp(...) + let ret = call('s:system', a:000) + return v:shell_error ? '' : substitute(ret, '\n$', '', '') +endfunction + +function! s:git_validate(spec, check_branch) + let err = '' + if isdirectory(a:spec.dir) + let result = [s:git_local_branch(a:spec.dir), s:git_origin_url(a:spec.dir)] + let remote = result[-1] + if empty(remote) + let err = join([remote, 'PlugClean required.'], "\n") + elseif !s:compare_git_uri(remote, a:spec.uri) + let err = join(['Invalid URI: '.remote, + \ 'Expected: '.a:spec.uri, + \ 'PlugClean required.'], "\n") + elseif a:check_branch && has_key(a:spec, 'commit') + let sha = s:git_revision(a:spec.dir) + if empty(sha) + let err = join(add(result, 'PlugClean required.'), "\n") + elseif !s:hash_match(sha, a:spec.commit) + let err = join([printf('Invalid HEAD (expected: %s, actual: %s)', + \ a:spec.commit[:6], sha[:6]), + \ 'PlugUpdate required.'], "\n") + endif + elseif a:check_branch + let current_branch = result[0] + " Check tag + let origin_branch = s:git_origin_branch(a:spec) + if has_key(a:spec, 'tag') + let tag = s:system_chomp('git describe --exact-match --tags HEAD 2>&1', a:spec.dir) + if a:spec.tag !=# tag && a:spec.tag !~ '\*' + let err = printf('Invalid tag: %s (expected: %s). Try PlugUpdate.', + \ (empty(tag) ? 'N/A' : tag), a:spec.tag) + endif + " Check branch + elseif origin_branch !=# current_branch + let err = printf('Invalid branch: %s (expected: %s). Try PlugUpdate.', + \ current_branch, origin_branch) + endif + if empty(err) + let [ahead, behind] = split(s:lastline(s:system([ + \ 'git', 'rev-list', '--count', '--left-right', + \ printf('HEAD...origin/%s', origin_branch) + \ ], a:spec.dir)), '\t') + if !v:shell_error && ahead + if behind + " Only mention PlugClean if diverged, otherwise it's likely to be + " pushable (and probably not that messed up). + let err = printf( + \ "Diverged from origin/%s (%d commit(s) ahead and %d commit(s) behind!\n" + \ .'Backup local changes and run PlugClean and PlugUpdate to reinstall it.', origin_branch, ahead, behind) + else + let err = printf("Ahead of origin/%s by %d commit(s).\n" + \ .'Cannot update until local changes are pushed.', + \ origin_branch, ahead) + endif + endif + endif + endif + else + let err = 'Not found' + endif + return [err, err =~# 'PlugClean'] +endfunction + +function! s:rm_rf(dir) + if isdirectory(a:dir) + return s:system(s:is_win + \ ? 'rmdir /S /Q '.plug#shellescape(a:dir) + \ : ['rm', '-rf', a:dir]) + endif +endfunction + +function! s:clean(force) + call s:prepare() + call append(0, 'Searching for invalid plugins in '.g:plug_home) + call append(1, '') + + " List of valid directories + let dirs = [] + let errs = {} + let [cnt, total] = [0, len(g:plugs)] + for [name, spec] in items(g:plugs) + if !s:is_managed(name) + call add(dirs, spec.dir) + else + let [err, clean] = s:git_validate(spec, 1) + if clean + let errs[spec.dir] = s:lines(err)[0] + else + call add(dirs, spec.dir) + endif + endif + let cnt += 1 + call s:progress_bar(2, repeat('=', cnt), total) + normal! 2G + redraw + endfor + + let allowed = {} + for dir in dirs + let allowed[s:dirpath(s:plug_fnamemodify(dir, ':h:h'))] = 1 + let allowed[dir] = 1 + for child in s:glob_dir(dir) + let allowed[child] = 1 + endfor + endfor + + let todo = [] + let found = sort(s:glob_dir(g:plug_home)) + while !empty(found) + let f = remove(found, 0) + if !has_key(allowed, f) && isdirectory(f) + call add(todo, f) + call append(line('$'), '- ' . f) + if has_key(errs, f) + call append(line('$'), ' ' . errs[f]) + endif + let found = filter(found, 'stridx(v:val, f) != 0') + end + endwhile + + 4 + redraw + if empty(todo) + call append(line('$'), 'Already clean.') + else + let s:clean_count = 0 + call append(3, ['Directories to delete:', '']) + redraw! + if a:force || s:ask_no_interrupt('Delete all directories?') + call s:delete([6, line('$')], 1) + else + call setline(4, 'Cancelled.') + nnoremap d :set opfunc=delete_opg@ + nmap dd d_ + xnoremap d :call delete_op(visualmode(), 1) + echo 'Delete the lines (d{motion}) to delete the corresponding directories' + endif + endif + 4 + setlocal nomodifiable +endfunction + +function! s:delete_op(type, ...) + call s:delete(a:0 ? [line("'<"), line("'>")] : [line("'["), line("']")], 0) +endfunction + +function! s:delete(range, force) + let [l1, l2] = a:range + let force = a:force + let err_count = 0 + while l1 <= l2 + let line = getline(l1) + if line =~ '^- ' && isdirectory(line[2:]) + execute l1 + redraw! + let answer = force ? 1 : s:ask('Delete '.line[2:].'?', 1) + let force = force || answer > 1 + if answer + let err = s:rm_rf(line[2:]) + setlocal modifiable + if empty(err) + call setline(l1, '~'.line[1:]) + let s:clean_count += 1 + else + delete _ + call append(l1 - 1, s:format_message('x', line[1:], err)) + let l2 += len(s:lines(err)) + let err_count += 1 + endif + let msg = printf('Removed %d directories.', s:clean_count) + if err_count > 0 + let msg .= printf(' Failed to remove %d directories.', err_count) + endif + call setline(4, msg) + setlocal nomodifiable + endif + endif + let l1 += 1 + endwhile +endfunction + +function! s:upgrade() + echo 'Downloading the latest version of vim-plug' + redraw + let tmp = s:plug_tempname() + let new = tmp . '/plug.vim' + + try + let out = s:system(['git', 'clone', '--depth', '1', s:plug_src, tmp]) + if v:shell_error + return s:err('Error upgrading vim-plug: '. out) + endif + + if readfile(s:me) ==# readfile(new) + echo 'vim-plug is already up-to-date' + return 0 + else + call rename(s:me, s:me . '.old') + call rename(new, s:me) + unlet g:loaded_plug + echo 'vim-plug has been upgraded' + return 1 + endif + finally + silent! call s:rm_rf(tmp) + endtry +endfunction + +function! s:upgrade_specs() + for spec in values(g:plugs) + let spec.frozen = get(spec, 'frozen', 0) + endfor +endfunction + +function! s:status() + call s:prepare() + call append(0, 'Checking plugins') + call append(1, '') + + let ecnt = 0 + let unloaded = 0 + let [cnt, total] = [0, len(g:plugs)] + for [name, spec] in items(g:plugs) + let is_dir = isdirectory(spec.dir) + if has_key(spec, 'uri') + if is_dir + let [err, _] = s:git_validate(spec, 1) + let [valid, msg] = [empty(err), empty(err) ? 'OK' : err] + else + let [valid, msg] = [0, 'Not found. Try PlugInstall.'] + endif + else + if is_dir + let [valid, msg] = [1, 'OK'] + else + let [valid, msg] = [0, 'Not found.'] + endif + endif + let cnt += 1 + let ecnt += !valid + " `s:loaded` entry can be missing if PlugUpgraded + if is_dir && get(s:loaded, name, -1) == 0 + let unloaded = 1 + let msg .= ' (not loaded)' + endif + call s:progress_bar(2, repeat('=', cnt), total) + call append(3, s:format_message(valid ? '-' : 'x', name, msg)) + normal! 2G + redraw + endfor + call setline(1, 'Finished. '.ecnt.' error(s).') + normal! gg + setlocal nomodifiable + if unloaded + echo "Press 'L' on each line to load plugin, or 'U' to update" + nnoremap L :call status_load(line('.')) + xnoremap L :call status_load(line('.')) + end +endfunction + +function! s:extract_name(str, prefix, suffix) + return matchstr(a:str, '^'.a:prefix.' \zs[^:]\+\ze:.*'.a:suffix.'$') +endfunction + +function! s:status_load(lnum) + let line = getline(a:lnum) + let name = s:extract_name(line, '-', '(not loaded)') + if !empty(name) + call plug#load(name) + setlocal modifiable + call setline(a:lnum, substitute(line, ' (not loaded)$', '', '')) + setlocal nomodifiable + endif +endfunction + +function! s:status_update() range + let lines = getline(a:firstline, a:lastline) + let names = filter(map(lines, 's:extract_name(v:val, "[x-]", "")'), '!empty(v:val)') + if !empty(names) + echo + execute 'PlugUpdate' join(names) + endif +endfunction + +function! s:is_preview_window_open() + silent! wincmd P + if &previewwindow + wincmd p + return 1 + endif +endfunction + +function! s:find_name(lnum) + for lnum in reverse(range(1, a:lnum)) + let line = getline(lnum) + if empty(line) + return '' + endif + let name = s:extract_name(line, '-', '') + if !empty(name) + return name + endif + endfor + return '' +endfunction + +function! s:preview_commit() + if b:plug_preview < 0 + let b:plug_preview = !s:is_preview_window_open() + endif + + let sha = matchstr(getline('.'), '^ \X*\zs[0-9a-f]\{7,9}') + if empty(sha) + return + endif + + let name = s:find_name(line('.')) + if empty(name) || !has_key(g:plugs, name) || !isdirectory(g:plugs[name].dir) + return + endif + + if exists('g:plug_pwindow') && !s:is_preview_window_open() + execute g:plug_pwindow + execute 'e' sha + else + execute 'pedit' sha + wincmd P + endif + setlocal previewwindow filetype=git buftype=nofile nobuflisted modifiable + let batchfile = '' + try + let [sh, shellcmdflag, shrd] = s:chsh(1) + let cmd = 'cd '.plug#shellescape(g:plugs[name].dir).' && git show --no-color --pretty=medium '.sha + 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") && !has_key(v:val, "commit") && isdirectory(v:val.dir)'))) + for name in reverse(names) + let sha = 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, '\\\@Ha|jEl2z_|%{k^6Bwy7l=EKJdwzKi)^+-)+}diduJoKfgN`p};n^o4lTzrsD@U&-X5tAS(w)drNLt za~DfX2iK2|Zb!In(jd@dkfPj6Ew8lwMNhYflk@EeN8y%`uOvaY-`t_k5!rdHlFRXz z-u>GzvHBo&b#MDSPxbEKmzR5pgNK(({^H#WIUJnaPn37=?np7s4qrh(P?^1QVYhos zn+X$3ll3@SYS?J-+#DGhnv?rf$MND0)2I8dfR}=Gx4I_KpLhS-yr)d7? z27D8j&D177T;|<~9QLPw75pNlo{GL&$GMjF4m{h^Nk}T=s2-d{njlgAxLVV`iOIDb z6S{v-+WQ=D9NS>xm(ol*A6UL6r@#K8_7o2f-{nM;bY}weQ*AH|HnExEm?g3-@vm9lepL>&~Nd50VS* z;+IAmf_U4Uz!A^i42Q|Bf2mru-#Xw?`b^k|Bv@&d739SqdMZ>p<>4*r%>rY4%Q7k) z=HQOMV5jbwlD~v|<7cvQdXp>q_ig)GqzP{SkUyib?0TO(yVIQ zku!UOSUP)0#I4!^hmOS2%=@-fx)wN-T={pGjr2NYqVpeVUEzRUvoFJ{FSf${q=$t| z`|4szbBN3@U#!IAbgdQ}!m()Ps zJybeUghiI+%;q7>U?Fy%^OmZpt-?i73en`7R^}#ZNTwvH-8w{oh^RrW!T9ZEKI;A3 zvEWbOQk67+$!L6n1m9AYMd7Zr571=ByM3s9lfHK&;%Nk?BO4;ynM)yw*yR-o7-RTE zm&L0ntGf*b5g~C3YlWWq@_JSvyHP%AXQG2DgYn08(u%du?A#;Pvv(u<$1HJvi?mQ3 z83z=<(MQSI3*SHzj;{Q`_6?ht29+}ZWEq7rnrsatNh`T;tHJq08pMaNx_eTJ8yFEM z!Ei3rkYbOvdWBu}OS;`8ck2XO=qogVxlB){2hFUtE@I}ap;ep8AL09J%EPF`>VTZN zys@YGILf>{k2MoNr*_0c;}}MbdUt&sr!sEBTX&=sYgL%$uAdxK8dM#RU0rNmC5@jA zZ1-AG$@~m(5S3UAU>f0xW-9coy6ognyGIwXab>Xv|Fk~&L^n}+dvb250rNW6k#XAB z&a4y~gW0WQ43wMCA>(Dzu}_o`kl^1sp{=J~(0_*&P!&&Od%VmT92JDOlxgjd_@>Ud zAJ@+cKr-Eykd=tNWSmRfCU?CbXRM8hP{J?vrzrobHq*K6{mD=6fy>{$hv@(h{(z!Nn!87^u z$`KDC;*2;e=`+*|=~FSW&$`ehbBXdN#zz&gj*@L%l=QSC0ScwfDb@Wp{XQ`&pyst3 zHc&eM{5Vtb0r9_P4rj*`=Q(W4Jiui+l7zl##BODEJ=Rap|3QPWu29Wj$RricvG!@c z7VJFF^R?&Y7n0S#usu(9D1CTWzso-4Y;MK-VXbPe1HMR;-jVW}y>SOci_*j2wDW;3 zE!?2F?Vm-kn@LpAjl%NWE7`xXNxec4Vrj6*dH?Apv7MPsiKX%xZ|XuV&Tp|QDbVC8 zSa2Zh@xW?{_se4KMxHpic?%G~TkoJb`2ksl!jlLON?nSHU9N{eV{-bvp+;sOR}W;rDqgf(pG^b_ zed(yhos&%r>r=AFSY!zouePk?Pi(Jezaq5=xE_04UP9OJ4?YM zAv>|7@ec}R#g3eU*uGsm*E=V z_C~GoxU_Gf5$U{qcdaU_YM|u?DrK7+PE6;wCoyS(f%l%}FyI<4FjL3!f1lF#{LNQI z4@zCEO+@q?GLIbIlSWS>Ca=Dy;I~h%Xso-BFIRZ5@*XNZ;w?nMOUZt3i4I?nal>KciQ@V`&2#M;u(d`D|1<#?aN zcEHoo`Ul%zCljhjw!}6bX`9U%9()})0iCKGSqGFl`lu~6VQSG#s$sQ?M%6JLi!0!D z5k(0Ko?cRk8+SrwBmNsdnPy}$!f$0zl0xuvh^^`mt8J-$k3~ZkR$HcyKU+rzKG2xUaWas7e0y`7)W6}mC^3nq|>5E)`sQ(6b9o*?c%dOxK3i%Y3P&H zq)!4O#-RinUq%5mCU)5yXKP}WF!rQUc5UBy49pghVhe#tux*%HkA3Fkmw=rX5S-jm z#d|qt{#^&x`h3>x|I2>VAiQpzwzF5u2N>>3z8JGYvR-{-PQy zPqRK{WrGn|rvwieW?ULH{-@lG-6MVd5kAs?{QtEG#d_e zRdU6`MAJlgx3%Us!tEu`@y_j}n|jlDj1yJAcZqGH{e}V(mvJs8t_U$m|DE_zdbMBz z!j{C}%-MuzWmC^;{L=3+c2d}S_B03Ld5M4I;5A8p1udxs%Gz-&>=xaZl}J zW|Vm^2xpJX^B&J&ZRO=?1L#RIwM6iEwAIu0Kw|N~PHq9y#w?qtI4zD1mpXm(Px?Dh zs4TaK7Y$al2gxUHI=64AvEfVUuD3A4N`D?sz60v`Aht^WA0q2q1yF9#FWz5&klI7P z)QjgmnHAm0>bsQ}!D*KhEi?o2SC2--(%Ke#9{#-xCdAsHz1C(j5<&iY`B>_G3%^@( zB&p9AzJ5~vr|yrz_-d8*E^n%p%|1`pah+fP?9Lqfhc$;9%8yI^;#g<+FaA#P()+&% z|EmSn9!Rt1KZE#G@ag|LE%5)j*!XXqfTA_9R3L{U7}cUQU-w1(G_NR4CoZ4gY#GNd zwXVI*lJoVx#EU_zOwMn48&xnq;Pc&OnODzXFKI;t{I(o^RaCrb99W6Y&wStAihH}( z)-l#?s$?WEh-j%gzLr;oSIAf&TP##IFy?Ss9693|D2^r2V`;l7nit|JwU&OTO}&LX ziQwc?5F+XPLShaaqdG&pbYZ^oRJ5qit05kqT{O`(r|;9|s6lbg$N`0~O%)yE**P7t ziR!ra2o)H}I~ENf?%c(Be6XM|`AQb&jg;@XiS<2`m~ajO83i@9v?A{~k5$Qs4;S1T z2ikjJ2^VTAH1R?=-)siNBsC)8&xdo5FkC^9qfnS}b$6 zRzU_84Tv)qM4G%8Ggxacc93eXm*I&Cf22S`!CU9!rxov`C!u_V%G%wR(Taxllv-Ma zy=^W=YnVR&nwFxs-xBPIY5yFh5Q;tf&MJJCxu%~_BG#|Co|m(tE^#|>Lcl6K?`|pX z3#EyjF@dUzES>VDUP&cdx1t@6Ft(T|Su01_YRnc;Q{+cyXW#e1%8w7|{2WQBY8{!+ z>z0*Nf6TNEIOlfu;>XO@JbV5K7=GUV>CgH<(`zUF&b?_XnD&_~Mu)%723-8~&j7%W z-iB zvta$>TIV>P4g8llp08y0`}&`)xM?w%1jMPXm)W_d)NtMkz2o`(Mrsv;_%u-qUyWt( z^4b14$~T7rn+DRy-E7H60Tu7MGNxnPX zFK!a-47`wq zRN7-iRd$L*Y~CKhq}g{e+h%Wi_p1F7mO$Kv%D9EL2!NSISZC8hY+pQ9>5cAc)3&9P zTu_XOX4_1ZqBE|%Eejs4tr*?ezL5QLpxW`vhlEqlW?4$%ZBA%S4{*mcWqx0mHFtJ~V(n$1ZJ57W zO#)PW+yrGvXo+4VdTy<0^F}frQom^Fko_pNfhR2K#F*uOrCDVw;wsMVmm-*pNP-qD z{d#a0LciKB;V5w2bTm3UUsa^ce%jFB=7+SmIor0rejC)$zFU?av5`C1R^+@fUm_+& z#m$D8QLl~k?#9e!&3k;!Oz+usFa|lDk+zS_ey&DtIg^&TcX_D~lUDa=182Xpvq(_> z%jr~1%xI=HUPcD_3(CJiZ(Nsm0Fkb(y$>CT#P-I`X48e@tCyEbX%#MSMM zo(fpv?~75WXw9Y}SqwuiFma<>>-W*?v5l}y-uuc&h~pqQ6wy!!a5CpMc6FyfS25d+E=-qS7>{qjd6dI6nBT6(#-z`H5hI9?i8GkV3C!|2N_lg^r2?l#wD2t%! z26OTo7xyNjp#*32@-1KQB$Rk%F2_7vCcQJrInCN#TU&d&$M?O*b;{*N%KVLz;$uE^ zI2t>l=`2@z9i87N(Mp>Keilb)HGSsDK+e151p(bPtN#&{Shy~VMl@)yF^ zoQSOaGRO_rbI7Uu+T;xEkKQjW<^rU{2|KzImU< z4A$g`epl67BjgXH=TsStg-?<6g_o2sn~X6rJ3Fn$cv-q-;i3KD90ktl!Nx0>@Qm#U zbhRzKyA$P)IIF;G?|lueihUXzu61OWS=`1Y4SHF|n`O{?`UlSF&LKr%Z zq9wx5B1{GoZhIbl0Z>CaD)swpj0$6BV3aB}_A}u?eA$cgKWAWJkhu5a^$=@g>&=6g z^nfm?AHi9n9}VO=8t!Rxx7AQh?-Y6-I;9zjdYNk?>No;pX8CkVNELZgPX8*XB zhxAd`Hi%&1$!qN>P5|+vc1F^h&J9XCr$sN>3x^k*>E$Rc`TyXU^#rIj6=CL)7OdH&d%*Z z(Eba6t7rJcdDo!?zqi&`7Jo4XaEHVAoZ#rk)s8ZqAG zw%nDjmFd?0EAR)lSp*_wH;X@dKP@fH043G%>{;)l`j2)d{!7|)l9C0Z6>(xRlU^04 z7w?AE-2Wupq?b-h?+wN;Ak%yMKRt zV+!`Y_Fqc7!7F&mKT9-eNit5+F0A585v#=hxYY}g6Qmob?@?1OyVu7yFb z@N0j(Mt#Y_kZW==TFH>^FQ&bbX6U-MEX4n zXtO){h~(Ez&m>6Dx0SKA^p&Da>W_x8#X~pzl`7sbw4XN($e~P}MsCIxF;?T_SO5D_ znh7@|zp;+H#kc;q=Q|=Q9!lfL7~hGDsQt^R)esh*AlDc%zon)>F6WI!)MpWwBs8L8BAGr^r%PHTO(uJs|wcQ0e^Y_!leV7 zHv4^O-xU(oE+1h;Zc-*|)#SO7mf^jqIli=`zj~^#TlTKlidVc9LD@Q-nmX>SAh*Im z_jXJMf<&&{iZtmGmaCogllPpcM1?;&TsZ>3pANe}PBy<|uta?4a4Fa|RV3EsvNth5 zF@XoTC>D(Sr%m$A9G~po3t`)lQPxbd7UplwLtI4YUUKFr2JeRwR#efDiiw5So+ANN z?S7{DRi5oYwhdmL|xO8KRkiR+xyR!P?HDe zN$Fd-0(3>aN!SW<8S7sT1O4a-&bF3DD0YSrnJ+uTZ4W<7%-OtOrKD~vI;%PL(AKZS zruq9-c)GKq*YBRUN#n*C)F7e;gF&9uT?h_R`$P29{6!$6w-LXU>mBJTJF_6hxa$*6SXa#QS_qp4OU|7fJ^{tF5x36ZBbEr(0MQ zt2m*(PO?qgGL%FM;@HnoqI>0Q7&zchv1Wp8EoW>&_VcMX_QkmBwBM)ys>4BO+!O>F}m&yyD1G6bRBUM$^DdKV8DUqeGnJ$%HTi%~8 zZ{2U%;02{&rQTd}rj#T-1m1!A>XRZ%v*|nhBbR&A^=hFQbcu5BAl?|v)}$RACK8Z+ zx&6@-gS7*eIJAIu{mWelfop`L|M`%ZEK-yJ}9NtxOU<~yRmL?5mz^oMxm{zJS}^5+;0=BXX%2s}o3CicfD0^>M!lT2Vdk_a!X zlw;8usK(i1Z#y)iO$I%yze%1>!q97;H8isZeV5x4kqZq0#(JeZ=@VFsE-LW}ycE?) zFCW_AGXE!J!_aymBwE3zkODeFf;$qW5`C zh!{kj!PMG1h4NPh!xCtJuI_VsGdqNw8(>wSHPVR8ranDTLoYHl1{LCo$FHm1(ixEqy zVN?nLtvLz`sqFA?1v#$emXhR@O^{5qkd=u!#Dd*uFJ?WCUF^C?qcen*-R}5ZCqC(h zjR8#vRe;fch6p$2@S`GdPtpvK+|-FYOK#$*&bvoOt5UD0T?y}XzRqW6X3Kk%nlf^d zOlg>xdO4oWYr41o2@pF}p8KG7*2f@!Rn=JE;K8-Et(cLejI{NfCj_<(OrPglfSXTV zF8*2Vl3NnKzjD|yd0PGROnnKE8=i}x z4TzG=`ZWG%gM_7j@smQ5!mSZgAVPqPbtWLCsnaYL#`BS)P7G&w9xue0)N9pe>~AfC z)#f)dn?axEJc%IS23(8l0UAK|l#cybV;u~(>MQSR;;6-QJvq!pe3&k2zhg#N&Cv+y(58MwKTlIu8X>e+e zk$1Z%XAkmD$XUm$0~cGj6gu|=f2vLUTKhuN{gBvvySa+mzKyPFBoQg-@tFlz#{28j z3m3_dEjyOo`Q_s~4YeS5QUu@iO&Z@Fsojg%03HB|aM&5)w**9X8bgR@X711Qh*sps zo8zQrIGAF_5w|bq$6BMviJ#Ct68s(Cbk$S|-?9_7=k$Mn?dYs~dZAS(w0vYhmX1zd zzxa1AfGV^x_sa3&dNX|)x%y2+$bK60^hjWOWdFNcJ82I=p6{@!fiz0e*Tgrs&mF@7 z09?3nvq!=}xur7P_D0;*-K)bd8iK|HDvx@$rq!#<^Xv@)nDYm0ql?he ztjZ4J=~1d7zQZXG9}%%$cSmTto7;h9UW(WIzH13WfJw*A2K|0`t~Psz*x3*Q;1(_= z)yS2n-0KooF41LYE^B%f-FuMW^w{*WFlC}(+-s^(HYSG3`2rpjYT$V=<`(~lV(UWS zs)KGn@~f%XXoT3GD4sFI)+7pDJU(6V!!NaR zT^%}*=+z^cMAt>txzTa*WlHY)dRKOk>SQE4x)NUg5MAOa7u+da@8Arc;Q{JC&93d} zfsJYZIr3?98W$GhjSPw^jAUTS%26=+v0`HNM0+zhl;HK~T4BDTctBb@?3z@8m-i6d z6n&r;$qpZrKf9iM1ouq@K&N6q&FDC_n2%@@^7%Sr^Lx_6E%aNqhNq&Iof z@3vozYF=C%XuH{rSLqo@SP>Wn7sV#V21k_=FD6EJjr0dijQ3XMGr0Tp!y1)zjA$6L zdedLeC41Sq3D-}avIz}{zBn0+N|XUMdZ$L~m&$HT6&7r02)=tjc#kj#Fl`sR-q|o+E@z%HF4} z92grF7<%dVHxSDtBTsBcZ=q5;)-5fZ-d_m^Ks;-`E32Qa5|pM&3P(33aI^c(_=}Wy zMvZu`jpy72JRFY7I4(!=x*s$%GjrdlX${xLIaJh2TnPm;I=XtlY{q5CSvi{I9Hjdb zdM<>8O>8eT9N*l%U2XrzT*_}XEG=x^Jiy0$zh~-4=7C*Bt?GEO@nKC8&1S_U;J`=# zVI(YW*a*4c^H5dop&YS1E4MM46&E+#TerZpwN&`6b{&bBoP01S5x=@j@$4H}>g6-k zXfD_!2zSHG70-I$Ij*+2W>S0}lyVH-ZJ;yQcO*`bv zq-Rlp`lDNjvaIuT&DMc|0sWqa?bkCQASDHiRW6YLialGz0XV+|VGD%!ox4mj5)maA z7kdn+?j*G7By~R?YXFP9x|@Ec=CTp_8F@XLYH{?8uqEp9uc_bOdc2YZe7f82+dm5Z z4B%>(n4<0$o13C3S$DAJuv!E5;gzhdL?Q(NdiJw!C>{YVqY;jrfFE7X6Bn-uP&v}2UDHaX+?QH({- z1~|xiC4X(y6~zM-Tke63R*Z!9y!;*_{+_3Z8iVf&HDwEu+gM_uCsP51JUvx!uG`k; z=N}&!dGRq1SUB`)$%u+wu7FQR5gkf|claxubmr70pZG{Z)r?Vbw`;}ko%XVyc^Hb5 zKyAwQGpZv$$$mx=eYv2a%EZHy=OOSQF8Weh)q);dsjfn-vwT?5T8s*y&0vT7J+W4I zHZ>1sQd9fzwjcFkA+(gcS-h0y!72$6?SAiSn+u}QK^f-2V_`{=L{$kQhkBE(I4U2Q znhQiV!_pay)58|xhodvgu?r)eu(A54Oa$%1@B6-|m$y=cJy35C-+lj^$`Fs(hQJ1u-igJbc{5fF*ekjVCyz^FONUwibGc zdoqN#)g?al#zf&gUN38Y5|b_hkl&Bu0k?qSFLu2_C1OWA>GqN zjh$WWE^sBMS099^sj{O`t}`>HeS^$Mmr><>J2!D8^_c_XFm!Yp}*0w2l({H%vNS{oo&EcB_6a~$)D4#sn>6ip)J8q z24zqaNpJ=P&SAO-@tyXm{=&Z8a1P{4r^GTKA;QzT<^If9+lgW2+S(gQkB9yaBqP$T zDFXnto=&W&MikSa00C@7`me$<=?2)Jx#`+#k2g-Aom5ptA`f+!zoy?eVG$O9n+ZX2 z3Zj@bQvk_&YJ%o=c=&)UL6ea-#aHvA_e<`sDPZ%U#^I!LBdmU7bhcr+Ge z{hvL5_%%Qp2h|xOk((}rpTeXDTiVk7>LJXaz)R&4Cm?n+&IA+}3!^uJyx+8U&Ys&0 zQAnF)d>O(2LhO<)Yseljn>fr&z82bg_^ey1Nlf4rEK=l4+a`Z7u@shtY8-vune`^) zZkP|g)cwItd;JUa92poEBn#Q#zb^Dr&1h}dyLRkueqMhF%^F4}KRoudM%OB zT*4KQDHXAQ;RVN;wR~7OJ%XHzc@C8k_T` zad|m?IE=!?+}u{#sgA*-r{`o)d0e#eW0qJH+K`Aa+9du`r`zMDJ;tC2`&O-2$e3gk zTwIIDH|7CzA*5~M(e0;?lL0rzbTCOlw9XkvJH&x|7=7|y=u$WMJbao>ElLeI0gO+hGnByE8nKBQPv!s(hTIksD_bmig zS9YjIs&3AxF_!~_tk>0Vtr2EBh%8o_No=l?)C)t&;?r`#hurnEw2Q0!Q*dnhy^U&G z4bHC;qJ5wG-u!Jw8pM=qv9KAxUs|JA9^W@m z^A6IqZQVfWA1xfN-BI7s-ax-7h!cIPuWg-Jq(Q1)Y3RK045~Yozp&iCq~sn_`_jq^ zL#buus62!1T~>-%^gr>S+>_~dV=Jl7zts8Eh$87MeXSDBt*@~=q9~LuRN#w}+TM>$ zEZ^HdxJzr{*Br3l>YwZX_Ktc1$1uT93=4^pj`mz`(S+>xk|)s;8y5qY)~y8RI)t>N z89L>}Pu41USm?ugo2;KRWVISCxqge7k6Aoi!)_(Fotlfrw z)4#0Oj4m(wq6E2Hk)235oKMnTuKjeRmBGSTabs;Nn=ZXA3RChelahnu3$O-oZ0!5{ z&qA546*AcU(}tLj&lslgW_7w*Y-G~EFFa;zZG@iA_=FlgI~TC2_^17#b55(aq;KkW&D@e8>BZccvne?@Qdf2&>K|JUy7eFsk*ptCeIe0^tAwk?`8t7;T6nKxV4 zB)Xxaz5AziZh>fcxgG!Z1)uFXr`VAt z+UvTCCL_e$SibE}7RO%(ASt8bk$zTFZcjYRvOF`(C^q?X|1;hF0xoWhL_Z2o1_M8=;*gn@7fmBpon`na*6!)ZDd(pS$YGP(w@Y3~X!7 zS9HV4_Cy0W0g1!=!{(c=(a}+ik9M>Slh#Lrc6@wv6=pAHXEC4C8|`h>K&Ccd>-p=V z3U!CD*j$yW!Jl+5V5U1ms!hMSU0XgRx)gDK{?mH=OI_1bot?I+01XqSfg`O`rq+nG z7*(sm#dOA|jrlq)NnI@|i{`uBEMSMIFT`ER5=ZU<=nat!-*asjS64^R*iQw5g2t~T z&AK1^`8A`Ru{z*@9eWTs>HHxorarL({+iG7; zax1(*!IIwbr%O=@LL+|DDqR)ISuUmU%J0dzNlIA9JEx`y7P;J?);It}_=EJWH|jR0 zrA;dT>h5OoIG8gmyH#zw{LMKghK-Bsdv&!N3C1I|!IdgD<=Y3epnaME^PFeBA+0Ew&Zw^W0|Q z-K$rB^^f#3aqQA@wi9+7E36 zSXo&qw6(Rp-8W$!x4qOtUQwZYv4)Ii%^No`iA#;LdVW4$1&hwNuKH5n)ZlzNBhxHl zg~P5Uhoj9|rUjR0B{^)m0q3<`JUkl0e9h{@|CZ_-Z*_yZ$J=(orII3OI#PlzF=><6 z1UOuT4&K9GE`tlH1KE~?d4A_;Ni*|uC*7l~;c^lCxv!RkGHdH$Prs1T5*GASO`TV1 zNofrdB&3^E_f`#v&Q)*F-nxaUM;(TUdh*Z&)-8J{xB52;z-z=xjH|NVNWE1`6{&t| z+g7ger^#kuBN5%8F$`l%7q4S|@+7iuDM{C+t*ARwNy3(T%knHF;ozP+cp&k9Q)A=G z#>U+jr#;G>)9|%5eN6v}Sy1s$1=t9ekznfOs{1S!DZREvR`2=Fq!nE8qp#+gjXc9E zX|5)?f4HBOg(WHZs5vroxu(i~I7P&4r;_yJ!qdNoQ$3w_C-m`2cVJ_djHb4>#f5f> zJm3$Dq^WwSF1Ax2DNw4&!Sgms_sg`+6Zv#8t7}X==UtHnkLfacFW*h&+xTeY_!s+q z>=`I4t0qazR$R#DHV`@O(e!P<4ntH}SaU)5DJcg8VA0Ug(O4kVUByceL@ak%{t6Bb zjcR$d*vQm;wM4Y5Fd@)Z4zyvMX2CC!fZyGk?)0Zf?IgU3+s1@7#0}W+1c01 zSxzoKg?yZ9R$*vZ-X!;}PLY*Lb67wo%stG_R4rQ^{{ko2Y3 zCb8$Wr-wah`xvZ^FH^|P-IWQ5Q{_Y+&gl+|4ybYz+8=?lv;;og_GU*B`F-8qPI|Ia zUAru@Tq2a|FO*9wZ1>o)*#YD6q6Px=eT)SdT&mRgaU3Kj$Mzg9UTsen6B;+UTWp9d z;K}oKO?GyL z!`6APjb0?Ir8i^o3P-Q<6l-R!y=S}Q#US9pl96%vXDWDAeF*~z6iB<4mlMiYe$5dBlOuVg)-81ZM^I9W+q~EK9lV z&wLS3-@@6RDC7psmt6@f4PF3|s@z16j7adB^6`5|Iq5B1TsbGNEX5@ht<{e)^4{9o z9Hx0d?;nJ#e+5Y`8Q zqol-J=zg{5PNt4@`}9NJOC?9sc28>so~d1>OM3TWZtM*smnA0(X>WbpTMjRCR2(3O zu(8%K8qMpw?@c`jw3%%FxYW!C0P7IBmwcrjriZ=KW>S1%aq-UGyKL<2RKZywT@k8< z1tXyi3N2<3F#G%hl|uwFAjplshHHbv`UiM}w*Xv|4nWEG0SAMY#mNF!=KTEpLh(H* zYeoN>aq6997FO2O8`7^yNuOF;5Ojq$kcDus27SYOTaD55QOk`!-ri$B-iunn(zf=K zksAY5CFjLNTtaOH-N@lcW^EA~M}zn&9)rF2h0Zxjn^xHa!ZLeq;uu^^XjmAhKxuAF z#30QUu#7<+4V9nFZ}yB-dn0(WJUbgUG*ps0_iM)`0Mc@dyfG;Xw51kI!JjV5( zrH|l0XOK%`LznAvJw2ao2eqYgl!uQEauWBj87v?Ox%FduZ?>N4o32T+ zWvg6Xvl)CXqD80doU>lXWSmtV$b-Pz>(cGZgd9nPm4?O@&i-sm=IJSsL7jDUaVnvw zhjoamRaahA?8Apz7~Dr(6p$!dh2w!@nEv`aqoX5Sjhcmxm7?K$QTtz{IrXz%=cLbj zU%m1(t8g>W*EEW!;7y3coxHB6K)6(0i+X*^$$6Mk_c1a%oB3eAPAuIVGi zfCe%`Npg8eRKqu)_@#`@sFS^ZWpX^{ovJ4UzqKQeEgSZ05?oc(uB);xIBEJwY9P*VPW^ zjOYzAgDjMJjdq$o|7AZ9BVr5?DK!wv_7Bkk+09=f?Rk>j--T_R0Bvnn`QaQc4cg~$ zy5!6#j(JR4T%59vjW{@iVPgcBW3yf_SEgtTQD4vuP|fDn;}0jX%mk?>z8A4q-n%;$ zlF|XydBp5&Y_2SvR+t-4W36|z`a^bd`QumUP?(%FatrV#}Kfx#joS&MJH=Xif!yOY{+G;hHdW(og9 zSixn&lw(4)Oq^{Zx%*FGENK!{!!kE%9J=7T;M7!3pa_49iyO>SiM6oWx^Gncx`DCr zJ~_EE7%Y8w=umAtJ%;h|((75ACzxA9_q{haK^-nmVhoO?jgy70;>kS6Za&vny3rM! zLl43e_uO~JG8}cd&6WrpS2Vm#uN>zIsfrIIs(%{$nnTj0{m&EPN9W=xUqz>+jl_Lf-urqVBE>Ln5o7?1mPBE0yWE4kxxDl#umJ+r|8V!-ixwXyP;W?(n~kiC1_xun z@iU;7He*f%K24Voer3XnxvYyyOE0+{Erpeo*bUF$ek8NXgu(b1Xg9He{z#6pMe$#@ z&~o{J>o>Mn#BE0mUq~2%+baNYO_1s2fcqv&`%+igUwiJz$MP)I22)te*dBxn`S$f| zcVJLU;7=K`-1wF=gzVDro_0IH-ETJX^QKK+{4wgJ^p}BS%CV-o>(sG z>(2u0HMt}BO5kcyced^5F%wgVMf~w&(aB2#@eCQ`=Co^EAAVC)-dD1=S<`#g3pqHm z`mQDISu z#Q?&yg!V?}(Z9_QkEP^J0hrSUWGV>2YVE7+pV83I&qq&*?cToSB8^Vzn<{N`a&dVg zDjJI0M!$w|hEbuZ)4?rA7RM{sfGg+MSOUOPFwngMY=8PZ$(KLhYA>P|nF(~5ySnWy zg{27L>bE%vWHTh`ZN^P2xg4)NU|_(qv>n_8v#HR&D+fPq+eb%1p(?F~9sI}xQZe66 zNzbzbbu%fg$zocwYrhw(QXu>JrH+*j?9Sx;1#up@d@D92^TRAv@$VfsB5M9Xj|0OB z&!d+Nj8~FWyf%<~&ZxumN0Rkz=<5j*dIJG3CkTxo5$`uu+pFP>vj@6sYj;UV))pE* zvdM^K_vtGDefSzC@#jroeQX{Lr09U4H~+iC+#~C2-!2u&&+lygxi`>OCh(b2^6I1C z;nDQ-zb@i<(JR;Y1U6SWhn2MYiXM?Em$I5?X;|%1x ze8!sIq}#zm&yB4o(+=ydDp2BhlA#-bl!~H}e}2%Duc)HJiBKYCx-S0n zSj^|7!{i-6Gd_D(h+!f4(0PlcOiud=f~)9Tc%&6T@pSWG>xoiV&dwgafJ9j^I!H_> zRHx_%9;he$v)ftcuG9XEjGyFgP4W? zI6Us-^R4PP2~dfuJn{xt;+8A7mO?+RxmwSzjw~2oXNJL~hbuL2(2Pa@C1$&QH zq||i!kWsJNiv+-{`drJW%JQd2kdE3u$3F(s%F5%2^}0i-C2%t9>pGjWHGUcMLT9by zLDYsW^3N}TEJ&1yp{15^co-`&owRqlILAu4{93+v=*r}OHJHrMO9S=OKY#8P)8yyY zD0}yovufvJ-)e7@G8NG@e9~JWqZhC3ou7}$Q=!$ls3l<(?@dz3V5(N-Ub;bTJ>FyNq z`?VYeoIgT$3i~Up@4(l=wXi5bb2Q|F$3nB-3!L`5NK5`Rey9f{a8Vu|Tjk4(C2& zw0+c;isFHeFNSDRk?3>52IzDX%E}W&xl4@)1Om8wXx6l}|22zGHt_5+n;H=#B|Gxq z`Bu0D@@CoBlWDM3PkY^8fq(#;W^f0IXH0{G_rDXkEp4s z_m@!wuFHmUK0aSI2TBZDl8!Iky@Ao5?k;Y8nV;Noi}c03oU3jL4h{8TVbA~NKkq=v zbMY5Y7(xFn*CqGWZq@*hd4=O_qbd5b5hD`2KxXPHwL2N}cRb zdQ<4ccfl7e5W`x#0jo?Hv+(&n&$HHLOu%>^v3@OvSv4cn?BZ7iDDoelt6u;Z*TK9; z>dn;LhymsluERht@E4ys!%CAQBWvA#l+;~ z)6IKyZD$&gx*+_e#gOUcq=AZT_DAUU+sLh3x3DwSXj;Hg*cr9IBm0HqHys3M0c{pG z=Ld9wnv2UlM%YdBo42yFa23LMXE%RrhQEg-Kn=SWhoiEg*Q<+SgreS0l#m|W#?q5$ z^fJK2^)R!(f_9Ha*3=RNZkX*8v4>ao%&Mx}YWW8X@AT49Hij zfeFQ)6Po8gL;!2>flN0>MT2t`DwdL!xeSI?dfy2i0T2g++&T3Tq^w>~&I!%qOj zddAMSXOllie9)9>=4pZ~DHs{uLXqqaPIFSOY=2Dqp?!sd$=o~vs$w8WW_P00e(5!| zQy|>1?vNiawl>qyNZm4)?*>%y9y~GHoYM~;t*b@cYh@gooWuhjP6GYp1p|bgiV6ly zGL_izYz3U^4_pA+3)I>9%6oGbAu2BZEg``{HJ?V815sj$0Vo$heJG5ZJXQ{pd4c1> z)M#rfP}8`dzD${Imh4OvJh=11V%@>T#l?D}z7bORG>8PrIZ4i$ zCg*Uf_50RZd!N1b8TXDGc8}o?q3M3#Icruu_0&`KqOl}kzb~pBuN}r>)=!s5oYW-t zPpj7z*+Ronw!J+~g=Lo2e2^)bYv4M9ak z1SH zOT5#^i%q|jMaqw4M{7H$jWs8v6XmGDG8$^w`-{XgY|Nr#t17CDmVv>p)N1Mmg0?aJwGa7s;JqLt z%d`5CizwW-I9DC8;!@a`&U%7)<;_vxqPXg1uTj1=f3T(P8$Cq*TA2~+P)Dkscfmg19O zJsNZ80|;~h17n3&Gp9m7YpyXSx!9=tjHfR__S%}|9cpU!u?QmLNz`zeSakpA!1@~c zqFB#FA`4L`)|Seq+jn2)>JLWu#_SpITni47^}VlIba9A(Zh!no$-+QqU}U5cf7IT& z!9gJxoN56+GqsGBvBY0Fw9;yaV>O|n#|_;Fgre=7T|Se(-iZUh*O=7TM(v{Xuj>mM ztUe7`q_A3#RuON8;}k~Q^p5ewHr@KZj}|}QIA>{NqutcQlu>ogBRVll_;FUY4Y>q4 zA$hJH?aMX++>H%}1aT3OUX}Qb`S_1?xH}n}CsR+)uCHS|RxG~F&u1DP^=D#b)tYM? zlgrKgqGw=$`QrQ2V=58j;WCmmC-u*-M36fS&s$b__QTWPX9knCAKWLO9@Sl5TolJ4 zXLR0m!uQO|x^Hiar@I)c94*$5`rOP?C z&=zevnKC-YNiiT@5VWd$EdAk*8_K{}&?-QTLNq4vlz5Nm+O^e?sQr&{F%^{>j2l?X zyhgLj!!9$_9?aey9mi-~GTA=DJ)-$SZTop?#D(d2(8w`&krt91O+9*uLJYOE$eENy z8J$Du=;+AL#l^)*7|y#p@3>-0wZC=b*gbpOcGtakiO!gSepd3nj!wLLULqr$+q=}% z+D1l?*&66#J#53<`ug6sVVo=HJeTn^V16ObiS~ z3tcls!}g;N&uyGg-dIt~*q75xN&;9-`jl+yXG3)=id@lr;+U?OH9&3C`Kruw*FM-R9%-{`m1>S67!+ zmWKSCznWF=)NJcbW)^ZCZEeW{eSfy*xFti6!<&Q-fg~K}+S=MV+e0r~Rar4^Q%;H_ z?dTWA_yTd+D`H%_T82Wtd@*ED|CS-E?h9vQHLzK;()l>b$knZK@JhvpeVjDi14FC~ zzid_eGi$5mUeS7}%F`P^gtrXFKOr;vQdo0r-_N}|P#jy&Lam?g`1F|`!@_oaXNAXf zkhCT%%8%1_j}xJwz-W@<=1P>0H`;15wKZJk$31s>TxRz@7CYa!H106$79}j&_D9z<8q88sAMa$mH)>#CKJkw#o+YlIxj~ z5+vW7`t*sjcbAjS_gLKO-l`StQ=`OgqJjA;i}6VrA0}O_koX`Mqpnr;Lr&Lu2l*xC zhaOyy$J%sv6uUVc$L~F3BgC!2_{J?G@q)O@=o$upQdOfGG50c2cW;5yT#oIE>*xyBwEtft-9 z;P$T6#>y)wsRQrMVLsf&J3K2Qda%2<9&-2Y-LCARrO2q5YlW_6$Zbb96LFt@70egq z#rTg_fAF|QTQl+pSh@zL3OZnZ#kFKLu|6NO7}Lv$J-HD58*J* z(%faqwcDEVXULrZFJHKINliUYM5qo6zWjnwlf!=LwFeXWl1V1+%R9=el8}Fl-R4%8}W3p-1XbR#^yQJ^{r}% zwz#=P+6M2wvCKP7wVrg&IP{vxxft_>28b06$-2?E9**(W<)6OzRdqlnY}~A<6ia7D zm@-xJW%5%VXYU+m*RK2dCJm1Z(tY!+UUBO){o--lZ~M86dxvH5?buYdakpyUUl?8D z`9MKl73kva%5O!y!#!^!f8k7POj3(gbm0Ar!yG zSIfsAGqwp7`Se?rUVrwY$R1)%GnU6v$WoH3bhi6~Qc$EHiSMv{dx^N+)630pYVODJ zp@`|o*N`%saHCa!=ip#8rlB@%$2e2&&^5g-Rb;|-_2z(xNmPX59Yl`iY57l@yxt71 z4rC>xYRNJs)&Aga^6Ee_mcg9La6s9Q^pGI#IfqUCOK#2{E%Qv?mYJwm5;LNt0`|c| ze#qVfqFVw2e-mIDOM?W;}eFh2>YrI0t|{~=%2&tqpCPwd3hg9 zPE0(5y>4_|^Zel>m1nv@cXDzVu5hrtO!~zBwb5$W$Vp%t-qK`2`)o&=6rQUJI~5&W zbe*P0nw^5;g5##em@lYhj82LR3p+kU&>RXbaDH>)h}Ymd4_e7b#OzstWo1NH(qaHJr%V(XIA058J%U*){j~Hs37ebZHXWNo=82T%}UTIfyn6ke}~M(aay? zq4AYk>T8XvvgA&?)4sXNzuHJR@!GCXeV{`MfeVihv+X1QA4G+c;MYJ&K9rL5Me+;F( zM__w!>mO5UCWS(<{~ka2PUbOn-e0qGC`8XWy_2Q5-)iPqDI{>|@+Dy`ATN8!_sjY|=tVRSbfq~UrmP&9pD7VvV@ zAWV|lkR8yublKlAX-1fl_jL9_w3eBfkoWnVe}0~Cu*jqk`l2Ssgcv9kwB{y?lvD;R z*AE&A<9Dl1bRk!Y$;-opKd8AbR318g;2WHlT<@2Xa<15(y-xkopA{1BJ1gP1vcH&W7e&M5F>38T;1lQUt4bWsY^%mk{NVrm%-?ux zVaPi|v-p#8cR=*+@-0&$KIbc!iNEP+n@dhF*tF~$(-O96G6dU8tBlbsNh=-EUR`^J zb;9Gu`?6NR(7YmdkUe>pW4E*BfA6iSm{&ADFlzgVC^65Z4W@J=USBa+7 ziN#Te>(b(I86Pl5rQf&{_o+%7gFJ znOS@T)S6M3KM_Y{b+u$K^Mk zEdUhJdJ(x#^Yv^lsIvQ9A#o|G-lj9Essz0T{0Q(jq9;3jC4cSw!WVDq}(JS$!sDF-StsHMN!K2kdf3~F?Qz%q(VfrtrEcGJeJ77r# zW)0jqYGrMw{)ZoWO>P*)S^c+Ezy)gR`ik{uScv+7sCuupB!p{;wHJD=Azak#yzVYg zBvdIhYzk}cN35-{pSivDzCc(QH(?x7b;NxY=A`PzsNfydZ!f73^okE^fBo`>rRk*l zgxA^0QPj7sed>^3Sg3Va{DN9??~EtEn~Ro#V$@dQ)||y_gBP-z_jpf2pNmLR@g7|O zF1OIU`;|Y5`?*iq9`p!%)3GEGo_|Xv$c#j}ryPG)&|XPQB|9LF5KSkl5}gt<;-Sy> z30?=TB;e;y8${veZe35|nF4(VN`_Sp{Py4OHzi8aK#NiR!RE-&NSq1i;!VDMe0APd|5dK0i3f1)vTLH?=5dI{EtK6v{{gv$n2IHb-rB_*X{< z!eQ6>r9S^%zA22HoHj6Zn}tbv#A10F;zddZA1rS2-85rdbBwn5%1>eheziI3Wd_Qs zg+KY0jP>+H_!JFtG|K8BiMCZS&|cYPtvabVP7k39d|M`!e;Io)Q@L1wn;IEi`5GW> zT$AGUi+%zr8@P)@g+>^U>U~?F~`TfhOxtA%Kh55 ze{N=-yvY1yFRhB_<}a3_QM>1+!jqZq!{5-bS=(DMDJ1fj)!B2$d9_jqZ*ueTVgBU1 z(lRwWTN9eDTiDh&-_+D3W7AOq*1bRKkXLf9`WN?Z8r|gNR1rg1Gsq)%qb4^R-d~uU z!e4Xjy{xONTTw}(YiL*va>>dL>)K)DX3vcMPFVyQ)Qxs2{gPC)*VB5y)F4gkPEN*Pagd`+Ol$B#ecb(R5hd@}eUFi$bZzVQq`{1+e==$~nicE}!MK-%V zI!Xa?8_i=l)5|@i&7fQok8r;JbfHwP0;xk>WxV*gef=})1flo$H7^`HXtlPseo^W5 z0$I$HCpFK-#2WIQ<>RPxKk_W_xXhjbJK8JS?!2F?T)<6^cl>B`w>5?d%&xYkCKi1K z;XM6LCk54_`v=Yi6plojgpv(gonG?-D^9G++vckxEEZCUiHT*l3yV1Px{oO(<1jwC z9ri`*fnGtsZ<$ulUgmJ`s~CAj%-l``NN(9s4Rs?^_5P}G4or^w3^#8`ARu?x5}l0mKXLs}XZM8x_s^9ScIuQpVaHOpn&d_S5w>&6o}s3|cu z?v-@2^5s(qI%Iq^oUuSl1xH8M_hs{7Us|qR=91-do<0k-esb!Kksm%M>HS0MR**#b ziQVFjaRdqQgsV{ixq!%%CzK=KDg%H4eB9hWD~4tSZfTfYezfvS5>bZdv0>Cyx%U*Q zQTjx_46ec_JMJ=MCYa;dCFNb;By4t8_-c_fO{J8E4?vBYWaBBoW}-ktPmjZ~>kM3^ zbf>Xdkh)41>LA1FXfI*7l0*u!Aw*x!K9gp2nGI6AZCFaV!uGt&4_qw+3qME*%!f-; zoRn}MIqqFuEbrBu{^G@IG5T7Roq&n8<@8oi*GUIQ%JvJ<#!@-a);)P*M$sW<0#`+Q?kK@`nhI}S> zo=%b-mI*v~8Sk<%F5ppq?UVfj8c3fZ`N2LE z-W_*xVxxSRhkvr7n$8fBkbvjq6_;0BeCCtWCiU6#_KESC1q$CFnlJR~BFY_5Nng#N ztduTNo8Eot*zpWHvxv);ImpQ+1{eH#`9k+mnX}b> z4|o=i+Zv)>BruSt=9mJf75?A`HHr(hggUqx)P9bciyjJ&h4u;3rcBAeKSiU8du9f( z$UOY|uKR3?xoT5I0cLD7*|Kv4t%vYliHdvg=Xgbn6@NzwDt|A(f;8R{!K2I~^O0EK zP;6jfQAn23QolR3Hc*;}w>?!>xND&G=AHzY9^%WvEoBN(y7vaZ!?~W&#&sPEQSS;D zoZ}Y>T$5f#sOK&bovca8+`cP~A< zdiK4qFZHZKR^Qj=h}?ygM`vnwNUMMSy0|3Tx3|ao0D7s=yA77`{!{na9iQzgbZOy$ z!O!vKNyQ8T7+~sutc7Fp&s8ro0QM*ahTBKuH;Px38VaQvw2ijEDnyS^|`uz}^^YR|{S6G;v*XML-Nj`gxP9|D6XuInz znJGU6C7m}6Cn6?hHnbE#s+M^Q3K!=CE_@b9^g7+>oJ6#Sh26}{r#CICy8RQ}_XD;` zkl#l~!-d53^B7%OfSPAB68zyl4ezj9tf+x&ySg*fezeu>P-xaKllF}+9TmcDw_~7a z_|T;9!R$=L1KYO0r>^=628nA86`M42H0#Uum))2+475DlQU7MY^z(o`T*uTjOQZy3 zLdB-I6#V=cp4@iE%*I={KluBXc?gKuAusbS7FcgT8D1>UvzgOi(5MjA+Lg>wtGsgO z&K(3$ntMl8n~0k%_E!`X!dZ={CAG9@&;Tm9Zp@@__ur=?P7bTX@sL&lji!v8q?eJE zVdCJJ`Ei1p6+85p@|gta1X-VPueA<1Y{+EGpD!LJOU4apo0xc)yYTU9*vZBvptl$H z_&N;`yYUaLI3W}5SLs8lOX=vM&OH3LNY(ldLM$Tdtkz7%8$fBD#p?9m&Tdg~AEZi# z5&d^Xx(kTIom=_UUv zLT6PLyG-J)EoE4$up5JT|kG01@X&$EC7WBwcq`xFv<-`$cW$BuSwH z3C7&e*cb%x=WBrtlr!pvhsEj5tYt2)ukW(vVoXj>(ke2XI-DhBQ0uI~9Vx=|R5a!y zFml7#d3)=V@NVEuc3Vo2GG%Pw|GD&clc~EtoP&e%=YMiM78Q}e{N(F>%+gO{a~Qx- zhfxiPmPJ|Z^hcK~if3TR_&6@oG66{OPE=();>coY)iuHDPq9ae&+kCZ+M1PdvrPpw zwdpEvejWVv+>xh7Y29awZ#&^Lqe0({eju6ab$t976wATKkCq8gnR}_HH?gwK%_Sv% zEukK2X?)J-KgPv*1JK3iC;ASQNl{lzEMHWH{H@5N#!F8GcSFL%+ujQ}2yCCo$X~j| z%4lY0hG5;rpie1A{=Ff;7LF+-t*my@rOX>QW?2o#Gn7XwgzyoRlx_1oVhDh*pxyi5 z@FIOSeCui43Awlg{X_F*Rse(R8^%xJY(=xq&g#o$FFt}?hk`;IYBy6#m5ZBDw~qK0 zNIuZhS4Du3R?N**@rgc|UEki;7hGeWWsJ?)zN`1g;k~|hdCUF$4XxE!i%6DT*&B>; z-$(N>2L^{GXL{d3!NN%0nHRi}>5AzWjU-}(Mde6XpILN)1&RXd3R&D^=Qbnow^n%d zJ=pvKf#_@nU-c(ubKJTB*!_>mU_cxmnRynGEkG&6>ELNH0ex(`YGW-jzecN&BM(g2^ zw;oT0OvLEqzn-sd8x04?iGbvuAD8?&g?(+7Sn?wtJt_21UD0g;lfdJqN9M@8&1nbq|sm4@~Lj|~! zn*M$ggqRr4A8huX+fGATevD>ipx6iC7uPMPcv+e|c68-``!_;NLIbHXr;T&imk+O! zkeHm|-tOBsYW`~h)-B}GQ0K2VUvT@k#O8m%DgXS7|6j4mKVtBe|1oe0Ur9Rg12Nw_ z&>|B$F#QENs6L8o3B8}}{z@A!!RuRo^Dn1%?KySc8<3XhD|aX^8K3-t~HJC9&+LQPN6BDVsFV<5q`RB1K(5UuvzND5m02pE{$|`8g-)i_?B#Y zVRQu(%9CfV6we`mNmEk%S`Q;Xm?FMy5djg1iM35dPwy8xEr%E$Swfeudn~*J zFns=FG>LaVVw+-|zqBYOX^%@@A>do?Fp1dP46IVxXcgziDx8Z*D8YOPgfIf)uFC|z z5|}Qy4-LckgTAF~2E>-T!3A=>!;gEVjtXb94YmdzAFcghtW)Fu3xSD9r&8x_&U!o} z5lBcwa=f@aolWxHQrA(NHSZDGGD`A#ngKF@+lNru}3Ns;8Gqk zGbgIBVFi%x$H7f3mT{X<=ZT`Rl$I8Gv@=^xT?+f|Dpej0Ypg|h4ka6#LOf zh0fzUqbnnE3dqsn@`!4{Bn33Aq@L))*KAg!%FX0HrXGXD!|yN65Qul4Apm&G5@Z8*70ORK!;0IX4=Y40V^7N01fG>3{_0^$)7 zk<=SQ3c{y%7O4tHvu@wcTf%}y+VOEMzxVH%=GrxDe*Sz1Y@+m7HpA7lwksZt7(j_; zG@~Kc{9q=I$FW^_9v{}4(O@Wa&Tecp-w==C=FvPjB@wtTD2NHgS1Yh4_@;$k8w6BQ zChNya2EKBg{TcV1n20E!nRyX(M>m*w6>MJqN4A8edhxeM+T};d5i3g zEw0aj=Z1t|{B%-aKq2R;t6Ih4Hg6+vj{e^tE2*eht_}-byLJr%TWw;U=0@9)H=99s z!v_*^0Exikg8HPP`ulO=r0^f8F8wt$9eWvXiCoiM1?kEaj@62x)e!J7fSf1Y$pZ3n z1gP4Ob_k=X!hwj?QwM0So_qI!7wZv^hbB5Sn*c_d zT%A!33_hT1-4%&>RU%VnO{HUAXFa(qyT4(Vr4dHNLOt=KE+E1Eh}@6R4eQY$2I zn2Kb^hMbC~`o;)}y(ig-fX+Y%2nv&DT~0UKXj47s8PxM?`vpvwC! z;;^%Pu6G~%&vDWCz-)J(y?vYlweFl|54y6KwQC~0j%#}x6BS|}r(6@FPPTZ)1XwPf zt`WV9&{u>bn;P&PI4nK#pN*XrAN(tErX`>EzEIJ0n_Gg7I)7Px-+AJqyV5BR$*TzT z`2w$&1O;ZKa^msJ2^_+lhc2@^#yysI@9=p8Uh0JM$pPMcYw_udLRd+hn|a(O1A#lS z$f4S4huriiBNZN=vzc7Mc>k|16HaIB5$^;Gy78aIX z6?1(R3{OaJZtr$!BSJ1xtNHn!|CAC)2(SeXc$(~>ep@_R3W4TJM>TS%I;#V-va$?3 zs;eJucJ;CEiu`~YO1tQ>oaj;}%2k41?zZdc)tVoHXg01IV+y9{Ree$F^xa-^*B>fw z2$2yKTzLpWC~dS#57@qpCPK3Q!Bp!MgVaW7vLwg`2itX@;~}eKpYUI1R+l9%p#}F| z9crWt?5xi6ypdX^^#jf5(o&boVB_)1B?j*~!&K9DR#Z*Gi%=cW$^~hL)I}T?^&dfG1(zPpW|Ac`rzSixX!Xa$ zYHa z{AcB{%GZakYoiy?<_@)btL;TQI=P1wP{t?}#yd;=PrU{bISQhwe1mp(ggxq4!-QZJ z=9RA|m;jeS12tFkrrFWi877;0!R}|NZ{FFQWw=96FScpJ-VhjfmxqSrFCoZyc0495 zbE3is`&q^xbt$+*C2xXH=DX%-Hsw_`i}o4z^{HE9<|JSH;2l0qOS{sar5uM+d;n=y z@M~dBVgxjaHG@R;q4trj{qhCbB1u4%?t>NAU~wP`{3o5du2D6iRuSnEhyp{naB2Ce zu`#i5U~DyHhCMyDXufnk5p_WYsbJ^!m*ogG{7jVzGpbX+;O11~;BfzeW_?%JE9dRl zqLVlto1m%_Zk%eUuDs{8d#k9C{y3@r@g%WWxrKyK2PP}CNVWcm2k92uz6S6@vT4@j@wOZ7&lmf${ed@)jEP!r z?Fu_OITX=w>y3kuuy0?-4RaY5hH`0AQweUe>0B!^;h7lyM8?GAfF>`v?3V*ww~@BA zzHcw9ubXbr<>c)DSwv#un_Zx|0zPuC+@e1yX#J600*om5>eBCi|9*?*7age-(Y{bd z3-M`9gD9pc6L?Z)nsXeK#g{J)`T=d7+mqo{>HWY!GQj}!fzW}d5wbcTB#@V%{#tjN z?j{lO`tdG_{}D4*Dk{)H?PWmDU#7h|AW4*n%@VU0%&jmPd zV`$V{=)mQ(lfe^A?rm!B1O1>GS(y#DF2*YN_LU(AB34Va{AtEN8*;C#XT&e$?qFhxyG@E_nV4tlT!VFKg5Rh=V~=c>PJ zp%*CPQ^fh+DE-Z?4N~p?i@;*~(!Y^ZL{w8`=&(MorPY9P- zH=UFouKO#234O%u_26}8Cd=RCnz?GTM_OP**P>oFtLjNfbjVfhXb+H5Xc!WCbDXmR zat#C&$PU--y6S3}WU)}6nxOC^GaO$-!(XYuoT+kj@t~#&T~e=cKaASBr6R!6>SsPT z89?vWXu<(#MlOXhD^5&yxIN~Sh~`es7v+L)8eIg>$k(%q$1dU%@7#eO0>_dVu^o2z zV3EU3_8Qg`kI?$>16G2d=GakFBq6BV$xUt$6B(9TN=z;(xRT-}u7xwcxY**0nw=Qe zYW~F*12WLJ37TSkS?W*AtOGrsaDX}qD7T4fFWow&UtK00+^VRuRg){TKbmT>+}QIZfvQIe<`T3P`0KjmneZh0uHN*VPmj3dXi1Cdcb& z8?8Rci3COjYw)KuiSoU^C&@?7F0eSWB|aBNvb; z@E|tl9$;02xZBk=8ri|DqN1V&6PBKA_LKlUiScQdM{v(^z^AsMu7J{zFR1}{(udr= zQ;f{*U`K23-3ioSH&qTxHYx=`NV52?hx-055vcmAqPLv5k@MAqd1$K;k^` z%`Q!&>+!;exv!!URh5Qs>byD<2#K(S*xdbiG)^826-}BOP&B<_X7h^UIAsA*Ebn$D zg?GKpe1}!*SZf6diw+nECf#g%tYV7MwFIfD-Y(EuB~Hyl~( znWia=>|D*$7$EM_H8kS`ZXM6>x--VcX5f>!_-xEHZ~iz)5D^vaOLp2FS^YqAn%_4E z`jW1U%wP5jO{3q#JCm?F!5Q6{oqZb5-w>M{Zas0N*SmB^@0g^5_QKq^qMHLA_fJ;b z)=h?jA!~^gu;8j%;rmj=59RPnw@xTP(fC|aau)ngB!@$k!eP3e8j=?rs9Vp-};l0OiXDjks$veT8>GsdTl1_Fbl8PG|PgVd46w_UKXjO)j0U`ta~9ArWr25sw41O{;mASE^UsR2rEM5!ySsJvU)>_0g2?Ci^Ax!>|V#c zjFXFwNz7@CD%(m{=%0q0yu!k9&=`YI2Tdpn`BhEFm0+2)?Eow4fraOUZh?52Y%o4O z{qsXqh+~osIhu{{=t$xpa}!9|D!f!|hF1ql@#@t$Ll7OsTkHVb5VMQJX;w7iS?VHx zQ)dHW!hP#{n%V}MJTsSj1R&Ek?u#TUpzf|5!UFUuX5wZ%i3EKYNMff-#&BW0%@^5k zlL2!zlS0|(1UD(-!^IKg^=Av5GA#Vu4(3uiI@kP(%g~yqSRNN_8yi_@99fmcBgOe` zY>)O!<*s3U;$Ow_!=&1V2_1E{wKqWvZY-C3{t;V41^^{3-4K^{9bn_-R}xv{peC$$ zsQ7~P!yj2xa+<+kvM4?~%B*BE(FRn4$4T8t`8t5@4bg03bWe@vjli7g!Ah3|YR#1O zGm8zvk9}RrQ}I$h$Gy3i+0=usPz5q_JOCAJv-d0_PgnRRAikaoj{l~L5oVf&0c!r| zI0>me4w=mnn)jzWzU!I}U#~4P@yKM*{8wdCNd?eL_2?QK{s0slI$e26{@=`dErjP< zj<0)oXd;Y}GMF(n^xRBlVVvDUV9jKiyom)u!M*D7 z=7Y*VJxLq4Y6T~(PLta(R-o4{WxV+!j;P}Bx|&kO7Xs3XsF8AOyyc6({|Z~j z`ffUuA3(2|v*|I#ue%*B1nTbX9_H&k76-%RUK>{FcX1!N4HMqL5?!?7nZ-r0v-kB_ zT2Hq`;eb48YFrC&WZB`cDpQGkkzbcB6ucu=#tJ6?0}F6i0JX`VCAc7#;&v}6u(>dV zF9{=&3_BCk^>t>Zrp`T@$2T|*t;hL7PJWJdbtRfV^sW7k#4Nf3JgexP1x`qD?@aa( zrAo%9E1R2{y@7QK4i0AH!S?jVvUa#{zRK`uRnHu;5QsE8|9_P$AiuR%Dbp+>9}nrM z7!nesj-*3j=yL;wXOAn&1NzNC_e%-MG%%_xE8SMG=^c=DXK$~JIYc^)k=1^)6M&&k z0E`$rfuFze;3QZw?pVH;@Y9nw!^rZ(d2w?XrvjM((1Ney;<6XF7h;5e45xj2`k)V! znD6(e$vxh`ky10cFKN0O^$ln;X8YkQXU_bRMXK!^NJ`@P9rB((f8KU=8SOuyY&svC zQ#z$Qc5J`;o+bO6dHRU2TH*Yqrp?utL5=KV`DW{oNY1IY5we|o zV98xe`UO%b&p^xtP3wme z2VH3Wf(C02XqviMSE=3|5j@)VBvf9{{0zyJj(BJa2o4QxS<_CiL}|S0J9NSlhYVMx zxmBDVVQBved)FGJ%apEFQzVQ>ZL}~|_gS#P$}(@NFX_;>Cz8vB8AL!2_d|$Yzg?w) z#&LX))%u@5=NHHyI=X(0+?8;Erq=Fmes5q?cr?2&5?mPbc)hlk1RW-5IT8P<4rP%s zSHAm5yXnx5ql~ek;d3}Hv#8L}>){N_BG3+)-#<}pI`vi)+I}%W1sdHPjDPLg{>z#O zmHq1x0|Vt?xw^7t!5V$%=nhK|*x0*hDj515fU61#0ZD+5=(i`6T+j&zzE{UUktkQ+ zAB12MkaTu(RVTO|;ub-g6h9Rr;4La+bHrl>i7sVSQe8C>D#m66?ob0h( zEI{|lD!si_Q6*3{@T4S1E(khotXEW6`*TM=Id2=w6{pvN>Mo-%t=I!+>CfT$$rAw7 z*F{JKS|iS!Kixa?<=!MK&@j*ix3#=~K0f~Py7?;hBghak`sJ^SHrr=Eq^kTAf~ji# zNjS8c3iF{%%w)e+n5Kce`Y$bBBx--PcwIv5lJUL!-dB7L3y*9LkdSyRYj9LS>ipK) zwf~}nY!VFE6v z{zJUzY2dTX0~3_x+W6*iyz%3etY@|9!|WDg$Rd5^L)`2rHW%MTk!*%z)sM@|rfu#|pf&dGB$@8}5g>et0k-#~aY#H&5&FFycql-e0zC?pTYJbDdxlsQ*Zh?Wk zq6ZQQFihuWgz*&ew&+rj!}((PRwZR+>*HUSp))52c38SGHE|Y0QnWuU)gx@n=a)Uh zeO&X=4-_n$6zBpw^avIVmj&#|D}Ad?4_+^y>%7}kUPjYe!3Tn%*{+n#H|~<;7fGt9 z+{(j2gNNiCQhw)=2^6w@*R*sf{q8lm*oBV#%P_H{v!aA$nj?U$u@Z|4_4%=1M4Ohy z0LJpN8MwaO3s8nf>X|EV@Keqy0l5F|jCR zmg4$ftyrETl{XAl+|?DHmV*njK&HZB4%& z0ksBbAI9?>_gCHMQXsEKp4RdzxrPA-bYf3 zB1bxjn2c=OXyh9gj3?~5k0SkeIPQm1$Wp9qTeYxHjn8BWN^8e=J08T>fq@ubfzEJQXom#C%+e-TSjCu_AiK|);l127 z#ZO2NoC2*A>sQFQhUMH;E6|#J&|prKPCAcXRBe|UE|;CeMZ1j8g>hl2^!MWZKYWN} zHM$Pz8hTs?s3Ej;b^Sqf2>h7TiYk>AYOenbl+*jN2IaKW!;2kvH$h-@xDkWmYugfYKwT{t*wxByN_!+`#y%?-CZ&GbLg7$ZB?<|Po zML{x=pYQNK04dUMo-3Q5n9{dN65o=5aokkbr?haZrNS*@;aj<+DBm0|I4a zHwCh!%wp+?su}_l?ov<$nU82Rv<%BFEg+qG%sE2opi^gSCA!P`(Ry*gl~V;h<1;2( zGOI+q!VPqs+zOq}sExXNt+>XZhY&pP55p_@AuXLaIlc=6z#50Snpt;uV2WF@&*vZf z)oCe&Q2(*sx4!=Il%L>qC;Q9EJPpR}-G)L;OdgCY&^lKzLCWJyj87d<4Q3riuc*`6 zKPCh9F#af3-%+>GmlLmUXJaofEBm1INBvR zzD3cV9vs$RaD1`jgO2H553*4Id7%& zC9R%FFIH0}a&UnlXV(9IbeloL^{AQxI^)1S+e|(^i`u?b51NXG%Ex?Uyg#SMy>!uK zVj+H~b2BShtm==~V_Z4*XU^WKKWW3{R6ZrR4zxh_3ww(n7QNd@kA%zN4qwl_-?9fdMD(#LByA2XrKT9Q$?G>6Hs0~5y;`6a7 z%+T!^krXi`rJ?O^EM&c7tM!1D{bm3-qa(j=pr;(!_v8om9xM#8 zrEGgnni)`>WM5K?iZdwjw7cxZ;#(W zM<_;|GybjPNizKxYWw^P5t>y%EtOv)=9HYXW8M1FzST}+2`0ofIC%3Wu}s}}Tp)m; zZ6l@9qy0<*8GX~+NnZn?^zGl6Pfj;J=XbQIwMh$cWKa&>89FU+n6r}ZJtS|?eD&^( z#`Pc}A?dS0Per8V?kNsSw5l~_w-&L=mTQP*ufo$y%BE$?3QOsW)1ydq%#EV;^mo7c zQLf&;aL;fYh4(V_Vbpgo?X_c9UPs=yEC$H2_WgJimjVAKpWk(}MJznwL$<3qp?g3j;IranN7T3l0H-Y&}ioWdI-WA>M5>Ls( zuMMby+g(f8wdS`~+i3VvY-d$I$;la-_}II_$;{D1GS2t&Q*552<4oBi6#^aIc9K=P z2f^e}bZjfl4|(U?E+abI<2@**!5X-tS4kA=mBCMK6L=+Hv$d-uvp9^pw9} z3NT(7OX2D7moEcV-k+c95QJ9yzWe=>e{tp?Z~aM?2NO`l^GbA%F?^aJyeP*$|&xRqoyZ@?FFWUBm9>N$-PCLq~rGxkY2Zwi%J>yh(6#CLR( zAFuv?Pv3(z|IyO9&Ef>A2$-An^l9=#S{dvtgL4ky_YdE}Bt7e;c2%`+woxBHerq|k zB}O89FKr5$4K_yik_?pDWqzIiYM?#)V?-(>qM#0+K~Ge5A!gPvO*p| zy2SC7Nq=x8lN*_U>4;uru3~OZ1=B&8>5OK1cy0L4TZ5@Ys$`Fbidz?UpWszg(xb;^ ziiY0%zM-cX5*li{@im$S3930Jqm5G2grTq7^gou2WCgT4YcBjatuHK~>d91$krGJw z{st%F>+$Uu<@N}_@D^ZS@oiCkowt@-A~}4Rj3)2LFZ990>6i~U`Q!7ags(3@$<+#oaT>S)m9(BZE;takkv%^KU zBiDg^H$2ekf3}GzzOQ=1w6n9rHA~Jr?UW(6PWgcCvV{dBwIs1$%+N?@13pcXa)C3Z ziGsy=R|4i~DW&=3v|XBOSfs{r>EsmW+2r@CfZZ=M~C|3c4$50`E}$LTB;}cHMWDNAaqc z1F7}Jo}p+;HeeupkaAy(lyVjSMUogDQ(rY9u(7?pB*y1r#LLt9ogO4TsQQ%{+#nylq_dV%o!N;);2O8 zfmoL!9iDu)NrNyxcQCitvaZ{SKu^g(of|_~OPnFUTe1NqE$r&B(mkHdk=e!Gv*0VO z92}Z$DrbG$Z#o&xy(%`6oxNvbXgK@D1gxUx(%Mu{V|OgK6StHeHBHJDa*y6M;@b=i zWIW}Rn=!$^{mLLg)Y+YlFwP%KS+%R$rR5P=x2a~Fix+77Ef6ohci(9+Ft-_6<7Q*K zjDW>*3ZN+2a6jI@f`GAWH6xY04^;TQaxK`vk8`^0sKqa_DZ1AG6yqT*)Z8Jz%g^ui zsli0s!a^$P*>+w&ZGFAJfD$(=dVhn2MT11NWP@`3@duw-NK&RMMf&(c#`2uco%63dsrk4#`VWk~fIm}P&}Q=>ATUEJm5 zP5dR7YyrSKZ0T90rJ1$$@oBb3X{PNPrr^+k1Uof(`;I&ShSD3$@KY|y``8-7P%7XP=2-xFGVRjwyil`1hb%iEiN*~^QqEGYUJ%wz{Nd|xTA=iyj~ zHlu7`U6l2V8p3kdQum1G4#q%*5lOkn5z$ZAp5`7S6!YBFdsF{k@5_BiC@AdS`}4Io z5sXEXYCiwr43Ou#=VEWAS4+2t;16O;HvS+;8YAU2`VF{Wuv7;MdXQ4gikq&I-y7fi z3LXTSEh3;JAtXB7DZ|RWxFFq`cZdksTF|Rw=aAR#9P(NljUh47Gc;1fcu3<&S6w3J zj*JC#i3&*fnG(?E^!zadj%8>~o9{j(c=mzxX?DYr)dS4{7|Q<8WhZTrED3*fQ3N#X zFZad<%UGoZM#wzpWtraA8jDTi!sv!p^yr2@-Vfk~mCE(+bf>nlscjnhF7l7Qb^iML zbWLboWNq<{rVb`R$k0ed!)GE>t4^S&$Xe;|JycS$+V0y z?xEUIp--|UJ9N?BhDP$K4bL5K9d>rPHCuGZpl$3oA{gasXbt*(mY-(H2N8R@g$@iw zXzj%A4pyDs{^8@_x--f#<}TT5EN^MUm@wB+@%zZVyUs4El`Z#wz#ul<-1S}Bf1-b+ zza&Mmjz39%8o-RT?d-P&HbycQ$yW)<+u?a6YlCUcrIw=`fuGPOurlmXQJ3Ss*i21;?0^s%L<25B(xte&xN0!I8|K*1R zYG>iuHV!9PK=MZyErqXn;6IyEn9Ax|$L$F)z8 zP$XN9K4sGKurAm2VdUxkJyULovH368>CiyLrkzhB?o-43uNfipkpq0sFAsREtZ2Q( zxCqh6iqm)rlmi0iaTmp$ow>n+F)m!n64BCMszPB5v@%*n8`!s@raDbP-C2APu4-(%n)jDoO~_-QC@-l$1z!NlAlrEV{cprMq*% zx!2?K?svau?ERfR&bP-o-yfU5l(Aqf?%%v;UDv$k`tTRQp>M+26icJI9KOsA=`VNT zNAorcZxV%=HHW2=!{T@uqsVsEh8zODJ>sWyXAi`tP(AFMSU_Ucy%K=W_-e^o!P0e7zb`MYxjiO25Tn2GiDE^pF^{FFd zutt&tJinNN8zxmKdtEa3DS$14SzVk%3eGil`M(@bAKa?dImId7zY78$PQnBS!e6@_ zNw?Blpy~;dgt?(A4#R$SHvj?jaJ&Vn`o-9Rw$|OJFO?>*j|ru$c2)2=vIbWC3u=$b z_rL)EVC^}FldG#W6;~8KO}cW!iK)lMQioe^kw*Qr+tno|7~Xx8pdg4fVmg7XHS~Q7 zbT)kU=B|(i^Ar{5iD6eR ziR6~)@`ODyasSYzAK5^tA2$B_fu@3RC%f5(s$8y2ppbj*VVGxRmiFfgzjFbBIbt>) z-12Q?>FKUO5Mm6rP$+FrIs?_lU(V;K!*6OSgUm!D*q^}w(oEKy-N}ZIgZTnsOA?Jr zqf6wJ-lW%9!jwQ(prtKHabq@( zdTWBXx3?-8y05A02cM+d+4+iJu1^EwvAQrq#yNkt#1Z%q#o2NlBgdLr`fc*a{85_>RD{K5`1d%Y}4`Jzp%ySfJQ&5A30 zu=u`!Lf-xTJHzL~d(%DL^2dDcSNUCseJPb+0C)evgOdB5b^W%7!+c--d6tqYvwz2W zwtcn4%%Gv{cL{qgNjg-ZD*F6p!48oBd(%Yx5sXOYkx~3*B$7E3Kt{&b&1Ue|^~rlW z4sUk>5`F7!if7NKBE{9O5{bNo1L%8hr>l;=aWE1HC?~V*jA^nYVaT22Ovx{H)tg%q zAkv_wR|9gNQ^Q45)hjf*@n>oIs+Do_4@^r+O0-X>-vYaMT;+kgP!!Y~bCqm1jq1fB zq^V6z?PzBMSHzl}Jie4M{7-Xz+PUPp>!~tVVMv*9{6(Xl$5M0Djr9iV_BhMx?=Fs; z;}Fj>qr)9DD#!D!!4d+9>4cuJ`{egS_nRRih+B!(bCCZV4IO=WVZS3H(OYe%Uq1>Z z0u}D1hx#+i&`TVM`;|ir$h32LWbU~A{L>uZ0j{g?eMsZ{c9P@5WgA%D373HHgD*uS6uMC6J^`^*%sk_Jg|-%oZ9FVq{QBHPtij_>N( zzJyY!erhgkb@xPO&6ac=*9XPThWh_QagzuQAz)Al#-?cgAR*E1;P6H}kfo~#s*<|3QV5uP{AWh(QKP4_n8Cs?Z}s)5AuA> zc;_g-Rs$I4Xd!-MejJ8U!w#9ko8C-NgCh%S1brnCy!@T9<47pva`H^}c^_M1HyN30 zas#Yp1mqs$?v+zNY9#FwYI_pmkLLnBV({U!aGkJ(VQM917=*4rf8nZyBT7lUU?a^0b?-(INmasF& zN=@Bnb(QY#pYY*SWxDf$r7Ta5eg$-{*0Y~m3J|VEG~FXA3S8MeX7!?(VDY15#&#RT zACC#op=fb@plZe?sZRP%y^DrjD3a0M1bJgIpKNJApSO}U;(xb?f+pS(laOzon_9Fj z2hSt|X5~R90l-6!+oVq*#&MXm@}CRVGc)hemz1ny4ZQCi=H)!*8~_C&;3@O-#99Ms z^Fp|=;RUf}C`JE|t`*z*SKTn+p=CqOu)E;V4sD+!qbW!A z#Piy?7T`H{Xx}9tDJ6zqs>%-$@A3m;dxGQet*Rn$4f(8}#)AuHT!pcDX-njMcsXHo z^oHiSRJ>$+Qmna|SuFR;0;Z??%ZiE|)k^0An&&%>NE>PjoKZ-oWiK-)5J2eQtF(Cp@ zDg2sFCL1@5;{1h!Lt9`VpXhc)pV3@J+)_PI3tG$;jN{?$sxcl+45KOKGNIXfWC02~ zYVI=u21PfFmpaW{ACm+mjg(MRn3dk8XcS3sJl8lStAAQCVL|7t$`GA zFPU()YZcpVQaHqH{#VGH@T~;UECD0O%`GhQ7P73-Xdfu%d;Oa?1c!aLdkNn%!?UfTHGc;hT?(~Cb`{0L|n-b;0{w|rbC z^~H~C(Zua3kV0bRt61w3v9`0(0=io`i350tSfsN!JdTR=in+)y2MqZ~ekto#n8U)& z&wnlXT#5t9YiiIGlPx#VA08RWB~XeWo3NCw=>0B`@>*4CA|Z1IUI_m7_$r5Jsw!ZM zJkXPesG&hnTJN~5^y`WD9A5S3C!gN|4lQLo7tRVuoW~2NI)CNbVokh61Od}m(ZAvS zB>t0~*+YC#4I}Xt(O_AgJ_Aq``_2B2T6}hj10ur_V>7>Oz~{+Dfq-M z$viT7s*MJ`WZl7yDR@~-YN`r+Wyh}s9zeJgwiVXsoGNpMVwA9L175&qP$@arm>Rgw z?q4>Hxz}OsMo~Ze!TjygZNBbV@$P=2(9PU}0NMEWLoUll(YrM#`3V;4HBQv3=?<%6 z0)jPIi>Kj8xw7kQfKSPHF+=Ibt1HwIn$-KLumpYErOfV+m-(w;g$m!DJXi-OR(&ko zI8)2M;7Qq)wtxfpjVU+9_C#Pb=u*M*?>eKzegAs?v@B=LwFnM*1Q8Xn8ZD9$=4$p1 z0Nr`X@H}3tMO+r4yZFo&cv|p)LE*yObSZz`V9cut!`nDhcZq8DALuF*Sz zeDli8gVm|@c#PI%a?kjqDJ@=iAy0{w9ftQ5$-HkR`ACZ`Br>}P|tpt=ng_r0>Rmm{?NH%ACi7;4ROzpGe^7${c8l+C>_Wzl&YR%RxARhw z70{z5L^0EWGGa9LUl79^^e5ECGC82I^jf@n_3yt~4c{2FcY`xoq041D!L)SuJh;8O zSGcsVY_0Y!059|}f|R(RFLi^JxKfZ%GH4YG&Ai7=ncKb+tW2%f6mR9-0~tO(o=i-e z`1)Xm;W}cF_V91(H?RXP;I){FU;Nb$Y2|bY?neS8e92S|Yvgfbow9W27qZ0$`0;M;d4Mc~2b5^F zu1>0-Jy%0&1h(Oa1@??M^D8oU04WXeOtEB87;%6GK`zOn;d`wReO=uacxMh*hRE}7 zORYY>5r83XM~ANqHKE$EEbLq2V4uUr_^H0j>0Wn)Bq_)o5GngE6G7<=F`COF0gUn< zh1=)?O%e^Mge$Zl!gs{=f0tR!Z~zX#ti24M@7l^uKRho-@k&y+?vAux8!r&%b3O{z zzKY{8BoQ*p?^+%ECw^iy@-ti8Wp^3@sM{rEh<+TbSM`LroNjfKWoUMtU4rtul*u$K zIf>+WjrF-?VjIUC*mK|z@t~!H!{G)M5y|wZ={HzwTHE!T_m4r?z11vjRlA#af79S;l@%{$(L+LVB$2pvcr zqft^?W_@&xA-x?#2oQ9gFHQso245&S#=6^jXcQ4WG3l~D)*vDdb{L|8rp@3=xr7Cd zmulrLaG&=l9*hWGiX4m6{Qd}T;q3wqNCYqIt&`g}KX8w=FJPZh$B0d_HX`^nr$-%9s z!O==#eZCr3%1HJ=Joy3$db}Px4*@*t)w>OLP$h{tIpNPcyNZQgD&}9bvyxsc<)0f3 z|9CK0W3ShIC%HbD)Y#!9+30 zACve!*t3kWKmi0g2PgpBc*^+;tl&+&|Co^x*{jp_o#Oe<`~WEQ5Mp6NZ)Rt2Z{>CX zxACYWjVfOi8W}+|a4^7g+n3%}27#pyEw0wEd@@{q4Is$V< zU9Ed@Ro)w@Ux~1z6q_0vq{}f3QhI%-7DxOR|5qacGE=$#{P`L=nCR}6m55>K0A;k_ zaAwlM*%_E*2OOFi-WPm;OCPIyn;|Misv&C8P0p9Uwqh=m)Xr?Fu{B3p9$}%bX|R!J z`Te_%V8``-JX98$6eOpQi8( z>W%|CBfHa;&%j?%ghkc^mi0TMf2?!CCTJV_L6i(&YZkREXnfs5+qMF)b_$LjJXRx6vrr@v8 z0o;;5YV@kWxqaJ(<$viN8hSEgC+aT)f#1$f0ej+9z1;7Kp`i$ifyKD=@<**BBZ5*T zlJV@=P*R~_SJS5eQ>FS!6(^=Z5jP=ndQxxSkB*Mc4c;06T$eaGmY46|-473+p;I1C zUR->^X+nA^BAyj8GQMQ0@QnZEU)jv@m+Xwa(~W){n>N7p+86#kH9g9_{!iF zgk0c46H@qG?|HF>#p-0aDWyLSkOB|=OTOrR)HP|yOO0RJ83|JNxS$dT<6+uxq z0mx-xGXobBRjw;P2$!S~aox8hLN7u1>StpLD(OjxaY^7bZv7fi4Fp{f{Q1$Wqd^Oj zvNu%^nxeuUD!qms1IAhmWPXu~J z$L&&}7zy??d!a)WE+Ojkt|Y3KVTSxb0yYSg6iug}CmYNw zOY*u?;eEV&g6~GMLN;eJ3$$I}J2x-Pn%Eg9h{8pmcitq>shZr zbD9+-0w2l4KvuvCj8|_rKIFt>k3dFtU*o#QVvZ0j6nnN4hzmf22=E+1!39v@7d#d} zNY^kxk`HjYWYDTZSlm&YmM#K#Z;M;TySf%i|4^{Vmo+U73k;UAaR?_4fPxjCfbCd`-%zY#V^}vQx_cj&$BSfhFsz z&UMTTYA9#Z`+pf--OQc=Kx3D{*dNZ!Wp*r7^I##x^D4mBfw^z?l5%lZJ#HTw_Iq&O zAD-uVOx6Vb`0?80^RQlJKFq9iw7v!??`R|wW-qXy|YgLt_`3Kz)?gx*@b@YIS}Ao&kEN8 z?aO5Rr0=jAFcTk~woLponLA0-rNu;-74BmJ%RSYqOL5#5@UC4eOQVb)&4y&1)-10r zpGxbyvpb4a?7H0*C0uSL3>Ymu5twm{o~8tyY=a7yeVFN7mU6>HimEVcbjhxv7=J<; zWq|XU>Dm^Hvv%N$g!a5pZ`bf4!P-hVTmI&?p7vdXfgNIk9TOS^r9*~q-qy~pibJY2 zt{IO`0Hn)$AQ|78t)3W8dq@gO7RcgZ2U3~j`T2RK-UnAOss!?Gr_cc|?1C`R`8Jur zrv1j7X~a)x>s6G#2AIV?vQl+s$?RcCdh7l5iTsWaK$bm!JwV(NR;E=AYl>##S1P$t zt$4Es0X)M%f*z=T+mR)o?Uk8#`w?rfxv=aspYWBp5{s!&|L=}7?T@~K4bA=W)0xGV zqVG~(yD`ym@Y&Nwada;K4?fC7(T-$3TaUaxTu9Du+sL@e zq*C=2NIN0IA?A_8+;O}8+lO!l2yHCv)xWUgXdppE1&FB{Run3~+Yc)Y|1~pHv#^&({lnKGK0^IUEoa)y(ZK`5ux@fj`-uA8-U`^k03qs{3_{^E7f`hBu2SErg1?Ge8pwZ!-5Ol%_(?K=h@5dO3k!g!VXzOn#xoUs`whnk3vRIb4{QcZmeJZG3+W)L z0*(I=&zlTREV5r*xiCigqL2_Qr2A)SuimQzmZvQ!PeDjL=<2|z_JH|m3%RQ?m3s#TMV+Sy@(73%OS`mJ%iFyc~tZoVE$y0$x($6y)kK|Qk$w#l#uKSTJhfUOc= zhd}enjGt^oN>aPqBg4Fw@MI4L|NXP?C0APmjesIgJf+74n`+$JG$r8q1cO_wpHQK{ zt>`x~lI78y?)zYBo@e5AUaqpbuUm4&HmL}pE*fO>DM3Um$K8vb*1P@6Z*fWu2Uy?_ zC8~}-mOSRecBQ%PRsA56<$gDu?TJS%R_r!4fm2S&V*m{Yh;}9=ac%u7k?yrOTku@U z+8L~I5*AN)4Q;N|clZj}JnKPkIY8>#_dvvAm4u4k z1O@cs;rJu~h_OagIXw}*`Vu@?pS=RSwI&+dZ0VK(e^BaM9WE2Ra5U~IU8U5uw6v6| z&jMNmL;#+C48%_%nhQVcgmiELZ*r=^E^gZ6p87tt9aA4nYXCHu#-!&XIRyonYvaEo zhnZBkh&Hgrf?lXp*LP~5ehgoW@QG%l#Oye>be^$oa+OQo0yYmiN(@wMaE=EM!V2Ug z6Mz3+-C)3(a^h%Jf_Xqm`Gd)z;5Pks2-&@T!BhlU_{5KlJsnCA=^D1oq=SY{IZi1 zI|#SMqzQN%C%e#rSXWO=3!pdN+SeBaD8=tIDOWeXU9Rl({ZhRoR0RFsI$>i*K>q@p zKy<*q9$$ZdGN1|8n^O5~V0EoRD0ovb>*Vvz^c?}G?s;~Wn=q>b)S%bR*BNRc=E-(` z&LA7gk-EE5gl_9LZYOZHzM*({^s^D3YabpOZ2fY~UD6aBSY2(O?Z6Q?6Lr?VK{b;x zu-V$!@B;XH_Q&KblX80ScaVS{KA2nsGn8kaZ))rllnF`*$;ikQiqyZvDBkngMmdDl zyoYFz34yJhs(uYG{4_l-DZFqD>qP+zX=9KracseYHCI3vwD1f&LInXdon~Antx$MD z=mjr!if^x({G9#&Q~3Msyhm;@05|}~<#~PELFta5H~$L=?qCA5`j8#!9uUYEV=g2+ z!@{#x=y+R44b&(=@09E1T9G;!Cwc;!5BqgV&e@%ybL700I}QmZy~O5syg_a>)O3q2 z_$WJ`kV#xt=M3(gXRaqpVv_hV_;cpTk0~D?Kg4r9Kl(BMo~v3wcX#HRVXjvAt+ccO zyL6flN?{>0Am;&vBDxkgSBHJWfjtPIP>pZkzOor~EhU z`yy&;s0#tWB5*GJ8przW;(yqLxvETIJHMTuRd}192^#%i#cv=?_%3?;27m(TU;!?K zoG<>^#lqHjqVOK1%+RCz>h#m(3a{AM-GktVh}_rui9^qhojvYCYOoacK^d< zj!^SIi+li`FYSp!cVHx+0R9iiinhTGh~&bASfHZzTfz`g&;_4_3UqHPEnpu^rX>Lr z8T7(dE=qb?8p6hocya|~^{Dy#;(ZaNE6hm1aDp|!K1D`8ga9 z$`NhbV>BCJSJ&hp^fac6Qn%~f_(H+s29)V9z-oa9($kx52L}r3YzV#FNh-))0O5#? zo7C$Lwr>>b6s&9+t=D_B_SOO>l=IO5v0A%Dq2*Uwkiub8WSGP}vt&3|x5`{4DK>T{ zCUklqwy9{b(C-Y(WBW5@Y~lo;G7d{h+LXFsw|5uFQJ2`vUURsVA&0nM+Z&FOiEK=k z5K!-k>4`U^>v(=6v)$kCGyhj8ADXle`s`~UD;#&L3;?WMYuFW5!^O_hMy!xw8muBJ9&;qt#PWDb2|f>rl*4^;$70Lbs#}pU&B2+WdI@6hJ~1tdoGtY@fW-s` z24Fjy`D zyZ;Jw#t!r%{NQ%haz)!hw4XmBcX#!cX0^W@LLeY0QOHxotX7w(tMb>QDv<9Xf22XRr z1Q4v@7WB;-DZ9CG^z<;*fh{v7e&&0dCLZphD1b8SuLY2RV(*J>XxQ@ZVZ;Y1DVA@r zR~EhX(8@^Nj*noW4grAb_$VkuTD!**1+T534~096pzzJWtTpEwt#KnIqv$6c6P2W( zj7$`CWj;r|eP=OK%?9g!cv*hOVAQ@N#dHZvrQMl8Z#ic$r<@`<0|#ERG9{DF=1na7_gELa`!?qGN=^y)p(5azkD%;vz$QV z8Q}Mdsc4;j36+xo73^1^9xm1vd@aJUk6fV*o6}S)d!aE-+H9l1h4b7`C1>Y) z;J7JZ=xvVXf}u@6FzFEnz~H|0YgyfgXBT_+`|1IGPbyDqZQ3eF3ltL$4xR%ij{pZ> zE8FX&fDf2jzF^S3ilXbShCk-(kvb5oh)k!71OupN29(04&pu7AaAdm)J|d{C-PH?u z)1DAZIoFfe5f)B0f4|gn=F**!`y3o8elk)*T%&;?&IZT;4L^3s6sYKfW;3UNL5C8E zj5qsGQF!-mjj0)PBu}BBm!YV^NJiP&`rK}m6(FSWo>z;17|DECP_3BRjx+et?la6E z?P+l>yC^uf=X-UE>{6Gx3yao|k0<bg zKqJPpRYI$3JwP++#1e@QY_9wR+IN}FBo8=b8JD%2&0JB)FcgZrkZLpQR9T zdqE{5xCn3~18vvRmHs}uBb|-n7`p9ciE0~W2>6`;o-1K?a+H;wolp;DjD>yyG{@mI zU0(S-h3A5TNh)9f!5Vg7zlV|SYIM17^6uCYL@w@|m&NDVn*YjFfB^UO#z+aNu$_k8 zbPchrL%;iq)h`z|O5A>e1ahwSPg`YCHTW*N=w;?CwFJrfQoo{ zeh&7kH2uiZPktIXwlAsO1rvPvuWeF~0inK!?K?P)7b~y-WoTh%Vf-4fsKHSGh}Bn9 zFwI7C+KvlpP_4bR^z5vql)aLVMma^2t>Dp}8JN86?VE(N z8%inJHT!z=;*tHob#o(^N`D4KnPTp%Ze3_Iid%|ivA*z@{7~)65ew+Kh*no8bK%i| z6>xn{`TMd#gyg6j19`l#3+^q;ZCQF^JR|ls4M2fkrtKcNl(@6MZ%Z@-kWgZFBTje? z3Vb`fdI`*Hy1_7l>D2rKNIXprISqXU5@C$s7&C&>2aWVJ^pq) zhFPO4`h7h3;OhIJ&Cj~lH(@l&8w)hT*j&f6BWbleRCP789a9WrFPGdvP>B<;=H_lQ zGn&q40~J-NRuemD_pFwvaM`pP^}6&dZ>wT?u|UUM6mRPB(6c<6baXz zzPflMNX86AgOcyt3;RbcK;ZE+6xQU=v1Yq7*)@ydgbMsIxsY2hSWf@= zdd0R>?);GZmtr)S6MVVW@Jtv_iPgiMxF^!X1XK6w6$peZ0(YcvK_PiHL5T&T5JKQ@vw%s4oDWxa8zVzf7i* z@)VCfuzUjEd*tGu5aZ92wFKf1&GvVu#)e=K2+YCxLSjeuD!z`Mi|P??-?WxK&1h%^ z{0;y&uRFPjdbmV}QDzAzq@#i{9AvGdW0tm35I-};qJ9AP1jd;@b>#&-6gxYkjul*Y zKie1XJkZuHt3U6nOo7j{&d&DOZv8gKcuNZaqoE9s8z(Suk#8ypY>NtnLwhib;zPH+ z+a4n4W5UDIxbfzVgzRh~FGx0x1{ba2JpdQ3_$=WHx(pQ8UZmhz{t&3D$xN{kFe})SC2Muu<$RP8`OO9>QcEp1>doSLPOJ0 z=6Q1d#P+n)Ly%awO z^nXtI|BJ)>U!C&ryP4YK;;jA~^F_zz@3?2h`w56oQI({>R+Wz{sG6IP6*`pEnrA1a zn0IXMB=t+In;V+&&t>gS>Sq;{t2h>riM_h#h;UDjXsWW9`U}MQ4i)W91u`P1i)Ufb z_S3Get_Z76zVl0=0ozVS)<(R)yj{3*F{gnxV0jAMULbv|;iY2Y0tAJ9B(TM^4uTg* ztjfJl8u9MWYafuvd%K^MTraNfqRIQC2NAL~{`6OT{TTPLwSquhok07~kUvNG#!pcz zler({m5pcSuGYMlv>MmD;_nj)!oGd*wz7}XIq!$*PJR62TWb1A)Vv7-^TU&(KJVnK z(ME~Y)!yPi{7ux--%sctG4d_k+JJH9!=K+g!S~9^DA6UI?A4$DbCUPJmiY5e18;=r zKi;bs5$O-2{~+$(eWgF|!j6Gi{O5Iggv45bf4%VEpMsXxABw}%vPOvbfA25UGVX1* z*ei6pIYfDEZ~aci2|)Uo$DrjkaEf*x+;d5<*EykB~?Djyyk9TDeo|HITZyraNv z;N=C}sNJ@u-x*H3l`Kzyoxgs5uX#-V>m2`im_GlXr?K(E(CVJTv(k4C3fQG(HfeJ7 zhkI92(%&>O`T14JKYkQ>*B)15M)N1TP+v!0O0(Vyv_Ssz{~;@047NZ2{6E54|9Pzc zyiY!vY#6oGwabSclwPB4B*wX$K;wZDqBXR9@7w#<$x`QuSk6D={Q;dh=sOuufLT_edo2WMOmD+@ z93D`je_Y6B{OpgBgnuHK6s4@i(U?z`w$Fjvc)p7hH8LUso?%-{PmgZ?^!va4oYOr8 za2pmEA!Z8|ME((B%jF9VUx1yX#yvz;RHSp*pB0YY{qr=^w-cKPl)^hnrT#3To+3;s-tPdl7{*YEzSwFCXJd*l`2|v`48F_`TMi zcE?n&CI7+gbX9NA3RG)EcDI3fNXnZD^v_3jf z#aMh?9=BnjcKi_i-lz$1dY=P~W?AdLvk`0bja-|aYt4D~Qq@&-KdiFL(n(Kvh2I!^ za5b_P4#h^=;Ux{jHPzSW%G%~Z!7O=WSFhznt;<>$pUt8n)oyFagxLGz^Hh}2eY4An z!$6DsA!Uc?MP3io1G+eqJxrTo?P%^p!ry;!-LEH-mzSS}dP2J z72I@SlFSWt#mv)4K=J<8!7-6Rl~5L5`qndcF=?RpW$$b%n#oXOJ4YR&F0NLs+?>{OY0j*XYv9vk)@=2RE5ndsP@^P=i>2Qu;;*2EZ zZNQkHB7i!<_%KrA0DbGc^Li30Dr(~`>^eLlfuFg^_$O}dNww02ueg0>ZSB}M`5x=F zmuN>H6*l*vyN9%2TB~rXLPhRs17?H8>aP{*$Z^gg{;w!K4;*=#yVMb1+NMq`QHv^D z2!`&{zEL#ffiyBqqB-6j-G?+vEv!G;&Srpky_NbXa=^*MDOa&H1I-G{ao45W-0KQR z-VX~_f6H55(cVHeh`=u_;NQb{`cCj%W%9P2zYYl8lkV;9T}(i4eF63Q zNsOd~#2Ym=@@IH`=Zfm;6hhZK92pu?v=5j4$SfLv8ILS(Y@kMo`TqP#54Pu}m6qN) zjsaQt#l^)`$CM}LT=!&m767aRW+#6*E`(Ewf(;%PU!RB4*Kv=PYr`;V@4E}|cH@JG zkMXihJGBsI=R}XTF3s!4hdjNtGMLl*>p+I>pHnqXlT$#)rbbM zy3NKjyQW17gDpM3@UIQIaF2a0sf;glJ%^AJsSbhg)3A+u*sTvr$slcemS9NQo5SWm zzuJ~X%8tOWUbA|z#h~)o8n;Qfm!MLB7)uqUP?Poq7gr2%h_u(Uvr`rFp5UUU8tuLmk;hPKn*$`Qe|FmC75k^fn83}2W*n$pqyoWY^#|8@=!CMWVM#7 zf5eihk$+(4UG~qJXWPfe$1Q-jy|lZF<@*|;S`AxKQE_x^EKU!WLGbV)a)}ZqC1r{= zMzPoPRui1`cXwU_ z+_#|NIK!g$!pJM1Hu6>Gj*mYSDSiFArEdX2bwLnIZA{!{*bYYzW_NE~M;uwgp5(-x zcv$G^mGgDAf(Bsfuo?D~cdpt1y-x&)ipu?Y931`rL_RQR{nF8q2P|0!b`&by^8|Zkm!AHTWz_ zQ5A))&L_#fGGXg?PW|wY+5&~VlFPZ3s@PBWyIWJqq}zNn(=h5u;-ipWIe z_3;R=eJTW^rE3_HRl}WFWbszpfMC3?9F^N)Jj2uWzRjrvu}9jg;FDvHFU4gesRz?@ z`ZWjW!6HP~U7wmS!QVwt(&(Y^Sw+Wm&*PAP-drl{LSN^kQZ3#z)#iGqC7$|dw(6Uv2I$| zlBdMzS6>f1z(A~7B?a2$uIGCXpaybs4 z$Oo8Kulu5&3tZJ+7TZ6}kk4v#W!Zfv{u%Mokiouw&B%zvl|{R~AmT|W$z0v@UGw+| z-mn{sN8~?{C>NsF#+zAs35IMDhlUE9lLrP~X70QJPk5_4;c&5rFT>3k_wZ-kOeS++(L%~<)6=w#+! zW&2;fGRyC&xs&5YJ@h&ebml#mwT41w9P3}n3QEh4f4LHaanv?_N~Z1p`Feb)hn6K@ zRQdb+56?sW?uk**F>h6CTIPuBY z?i}AF%R4D1ZRFDjO3YZ*AmnMFT&JTqxvF>h7)oL9I=AZdAG&CqxT2Od}J3hIe zI-y=wT8am57ZoX?PUrPK%mqr4QcwKAH%`leG+-4;*j%mSHvMoAA=7I+J5GR&SpV*P zkg*Vy_G<~)i$kO-UZaXxt|FS5<@U?HE(UPR0yb@DIbD^J@d*8!e`ePhuInN3Xo|`` zwd>j_DOTfXT|ksaDF_E%w&9O$hTfI z@zq`~aCq-oO+Q?u?)JV=`Fc_xW7je^>qfzLT+xME+hjPnj=6dxMI8{kY53iwyfhop zRM*rk^B}u`Xo+Ma_`{G!hDC=}=zf1oc;8g9ZD~^rG!8>_V&42r%tP?wPLIEDCa$A! zjMuE}>ivSUyfQK(j&2C#QyF5g#zNH1yl(#H@n;01nz@gmr_GlsV$z&1D`IJN4==Gq z$u!=UruiQjwdBoSgz}0&8;ljRS=fgt&HHv;$h;MF?eq2=4IOKRpr%46St@*^pXI(? zZS4)?c!V-@A7F2m@pSJbc%Int7WZruSlEKEA&9 znAK~LK^9NKVQ?}NH!(K$gn5D7`EDuJr7ku~ zsm%6Kem5Wag0v)0e)nsV?qjZ*bna2?%v^=zM}^6KD)Y16-A_rL;<+!9F~7atG*Jq+ zN??DdR7W$tN?&z3wbDU^`%}DnZDXb{bsqvrf<7;i{N`LU z&p6cWbYL}frZr&1_Ve|{eY)Ly)CZ1voGgM`deG}UU1n09VLBy z%W=h=*MXvoHeKs7O?Fe?z$2p2=#twDNO9Wfssg*v>V zYYz)^x#r<%#BuIuE4vXn4)R&fl^tsAc%;_xyqfg^C30jMN3<96b5=GnT1!;2+S{C^ zNF#^I+Un0EHXB-MYq_qc!4UJDySsl5fTa<@@zt2*R$w5mg&_Zf3t+9FFdjf3F)g;W zGgYQ-XovzT35f)5tliz+6`Q%^r87~#orOp->a*P$6iC%(Rs!H+BMA!&zY!J9XlQtp zo13dU?lI!4*f=Ebqv`2^6I~FOTg$>x@Q_}g$R|VK>QrsSZ-QB#=jdZhMtU&<$kQZ3 zxtLM;YTl>HIJ}}$Oat3TNq7`0Ul%B3$d4DAlBJToI9q^brPj##b&b?7J&s!33D-Y|IXuL@;IDbShktYi+b8cQXZHD-fq&M|&09)MrXO>K z)71^?GRk==QP~3WPahCa?Q@&4N(&KVigxZ z#CBTC;VdFFDJRtlB$#z02e~}1#@yQ4DmI_t&Q~c%-`lH?xC4|aOhEquvj>cj#+ey19N`<*=H}*N z!+}S0Hq_MAHJ{@EYY?049gA8OSZ^@^y4Z`G*(rVz5%@PSn509o&QG!Yhy>&`>qii; z=^oe}!+JhyOCo``%encLCylgfma@X~W1q5YCQ|wtc+KMML6yFC(aa<9wKuZ#iDqp^ zeV!r3m7y#Xqi?6MdI*TGgTUD?=c~+~3`QbMu&G@KTXSEsn}{WpY=s3H<$u>*Sf8p| zzgxB=;1oB-_%bkhgL7N;L|lOo{kRml6Vx)1pB?E-5fM zoI|O_l9d12L4^hH5Y5@99dkt~S9_%Q>%>=4LH(bFj7@2b-5| zI868!^(Dt6PF7Z{QS|^>S=k}5b_wK4Pnkd3rd>xz6PisHBlPw4QE3#V`OQ$0)Oa$` z(gKDq5v%={+^pMi-@(br7nOqcLiO5r_NC6WS(rti`D~SrJxTd37vrRPZVqv~k2t+q ztbX$Exw*8bsw<&nV6_*NI*cm*YGmjn0oTCjXB5a>FNFpb;<4Q_a#Mw?EU<50bG;2L zpfq^QQI1S=wn|l!?eoIY`A4BU2rQsB@*|p8#xx9@-=_zK>bmE!#9qa6rgAKK#wGUU zjZ_zG+G+FGno4$AwBJhm4A-@M=4;=dcyU81p4JXrKOQ-_n3EKM?CU8RgV`M~649`0 z7z>Dk9V3>^Up~wEv{EnSaC!sFa<*KK^k#}rf^>N^}#QpF-6_+37(xyv#$2K)$klT)7Q7k zl0~=)$>`MupNV;9$=Nqt5@#ijJ)P`C_b;vnH|Jf_+!%$RO*iDtpbAIerfsGGpYeAx zb4RZ#4H@hv&lN3cGA0cF`lL4UIpeUfYth!mYrS+~Y&Z8u5xR9+#$%obuEtW*rx>S9h6Rv7 zy7UN?n95bKGG)-hJ1@{X`*7YauKBwRysN1Ed4DIAcktN<_uBVYzQ#X`rn^nwdS~%R z4Cv9IenI@~@<ur0;MY?lV}^4 zy9NJ|cYGJeuRETK(5JjLAFNhosL1Ec7B>u#0zbolx7}Pw`4$7$LiUyg7d<-Z(uN5V zKNUlwQb-MJ{(6!Jz|8uuv`7s`G|CXp8GCe5+4;~Y$Kvgv91f>kd3wXod*G^p zOdejuSFAa3f*{FQGVH4ei6vk0?H$F7B;v+A!;2LXuF}+s9IM!TUW*oZG&r|U@H@Dt z`fBd3JZf0C)@Eon)yvQe|9OYgX8S#|77ddTOPlr~n{)d&wx7v8(h8O8uI~ThqAM|m zeHxA8q0&FT$LLJ|2$*iZ6Ws$QEObtu&ZV^#FI}+P^-x!cG5<@Iv3}%G%eK$c*+bBp zNUv4J(jF)YNXkInvN-jMK~g!A69CEfD;*Y}q>&B7bNO!hPT_36YFxe^&o|qsfk$8V z^@!BEiaMsK+IU@m5QBGmFSrXjr#T67T_B0zPYxinPe55&+YShb^KenrJxt0M{*;{y z!XmQg-DU9-0i>UtycYB-ti8&M|F7o0GAgRDZFguzX#tT26(uDHr9nECmJaEXu3-cT zky4RCLXqwnKpF+44LSveA*5kw7;?Uc_xsNIb=ErT{5!10uf5sqxS!{~uj{(whV9dM zMnRUA_{LHPqA??*8qkx{(8N&Jm?~U;z>~qYvV$Bk#?bh6_UHmrM!@sBDxg+DK9ZRW zCA&VPWacc;V3JVxd9onDS}!!)K@nL#V!+dlIABtd6XN6~*aJ;_08#Yx=~J&W+=P|n z-LTEbH@f2kN3CT zjpvFUHos0EZrZD3Fl$3&2uAO`V*h9wC(c7VnZ9pj%t0+bD9Y`9v6|ieJbwAMj>KWQ zM&-ABCC@QWb0x9``TYqjs6`RZtv<5&g6RM zq{v%A*Euu8R)-0bid5v3J>SCt@b00uZvEsT90#3lj$91IP4;8Rd= z4e2i%TUqrSMrza|++x)a)qgmp4@>wq2p`Dit&^d>2GOhbpDk@+QE+g%`t4Pw@Lowy zyB>?VSa_weZr!Nx6LZH6oUz*a z)!jMdx-IZ2!kzv3tVVd)mA};&_W)Go58|TynNjrs@K#PKHFn)RJk&SmJVp+0zc@k?b?$=94bo@jQ?($Zt$?p$~A2NPzMV8ZpeS;Hu`|HeR?H z3l{Y4E16_cy{RhMZYNtc!C8xKWV&fx98=^wb=qCQZ)8;<#3>u5_eoj@-CyST^>nJZ z{c+@^OxpVqwUGKQ?O9F$9P_@htX4Y!R?+oYm?+WXeVC(jK`&*;8U4G!<>OB>ug3RrkFhD{=vi$_RE=nqI8V`XKyrX`OOYU{k5fp9iZ_KE)qGOoF_e(S7cT#x1lta9OYW?eINQZA|HQ*M z&VwrDpyr)a=k2tz;#S0>#7ftIhK#$75PiFl)+76uqdK=nMS8>|g;)esSCto#u@4b9 z%4O)(CQOy?ZHS8Q-b3|jE;-KGz4P*_{P6>C`FOD5R?(xz?Tsd=f@JI8Ks$43qi`S= z)B~mBj|ENi?wN{(_{6{_s+=d#rMuSEG**l!zM4nd?1a<=devUsTWqvv?w??I`KubN zd@w(7lkw`WYiikAe#<+7-kLQS7z~q|k--eC-CiVZX3?9MSB##XiYh6rA3E~*n-vca zPe)hRnjjM>A5?S$!zlk)qVz`-C|YEs?{e0D&An!-DlU^`8*;aHwNz>Fs`yCbUmy34 zG8)>&@RIi`=J$(YgkyFR1=ANg#tQtwHs01b%qIBR->Jw&IZlh9@birK8eXX2N-CWD3 zoDw&G4KrtAj7Q2K=pU+9;3*n~vW?{|qUAzEgf6x>zkigFqK4>szxel6DX~MXoe!e* zxoi~yo9~EyaSg@w?h2?Z)GIdV0Ye8g@&PZfC(dqJ12C~<$chNWdWp3Q9`vw5-CBq~ zSG%Aik*d#jTy?o(J=aGvMC~j3H2NXciTS&PI|Y41S{jG|@7Wz6faVYGGT%&SRWmL- z8@0P_bYb9JFkY8g2 zI{b}5JgsC@`Z70zn3inarnEB`^lK8$VG1P6Vdb6QLakczP+ysy7r`CmYBObyy7;wZ zHCLlw0kA&zZ)9rJY|1E2i`0(kG)!9=fUZO->?N~O>plfWi;)tOl5*Yc^0gZx&7r8& z_VZ3g9?i${rC*&2Wu2qXYiV&Qa_KB=k$NeB*ND+5zM7;Fr2#c79qG|4p!n>!*!#Bb z9$4$IMt)n4N0~4exv_S7dDkg7ndd{DoqhyqL)|gzUFV-ZO}p9H+<)9;9+k5(19LxE zyqH3#oCRm%i-{?pUcJ;yYgKJ&M6~tOlBouiTqWmT$kt~XLs!&Y?0|9D?S*u2|J|a@ zK`QRC7GRCmb>wlxM`1h#n?2#Vr2<@@`{T@AVv{K5%5w2%x9E%RGdKRm_x0<{pVx$J zCy7rTH2z9_3>fr$Mptkb9Us!7v*d5~?Vy7sQ*)S>yr$YK8%-N5U@Y4sR*aMDK!^Ajr7C9q6eZUC<}iinO;&?qNFjQc9!Av>Se%Jgy-;|6M1AuQSTY6 z*evt#Tncl2g@_f(Z=a^!X##%F66Me_M9p_JhgOT%E&K~7M7IVheOPiLEQzc8J~Ofg zh=v|50R?>v=3B1M4oH!|T_(53RS!_j%(vDPE?tk93Y}&6jOfpV1iO|>m-{IiVo+%N zj~@hp%2w9arU20SiM~D&5Enpuo4M_|_7yBv#oC&qKx# z<>kd|W@yt})>P@EtWP2!K=Jq?*G7_xDhX8SJydQrI9J$TP8Q1LM=3Ds@x9p(h)jt} zpZfeGv7(xG&7BS^_=yGwfvdcHX|m8|}Kw)~-PylcPA=0yK8`7M@v z9aOf*D=S>m&uFwuhY)$hr{tSDGGEVBdUurdJ}|%e>Jl?e3ZMLNE$ipiE3q%S|E;q1 z$mzR}90vHh5-`ZnMBQ<<)yp)!CdJb$o|3Un-?Nq$SOTceHwa3c?h6G+RmNu-ADocM z-191)uQ$sEh8|Sy;JY3e-l?sqptiKM{3r*P1#KlkgET37NyJb4?d|PWZ74djPe)tw zXK(Llf|)kp7(u6M78bFEHo!762s|6{e5f=sKF$q%BH*V3*NO|hj(49+N=nM}j+5l#zt_LT#cZH39AV&cOZ{0%IXOAPpWeFg^Hw@0U6&0? z20SAmW&jf_aMhRc*0l%+ zcgQ)cGN#WFtivI4XsKxMw6#bu-_C~fk|PCp9%sF28AvRtP^_n5G)(#~AqD$1#V}vw zMrLWpe0PD86Q?{-)Qd)4^4$9`FB!=|h`Sj?E3XN(jLRDd`U0_m?z8U}8ip!}R->9U z6W+&(pX(&DOr^!;N{J`mEPf=0kpofVw}TN0Nb4@6=i>o`-sMkz)i=P4%N~YtAg+h9 zTG)+|iF?^=N<6J~y6dHB!V+DSH}lIw{}W5`lpy8FOy;!PJKyEjWS3jNMF*IQr@Xy} zzds2+%Gli#9eHeQ+nw*mzy{q`k%EOiz*=NPeo><`F*TE4b53&pO0?6F#kkLmpG-|| z65w)h>7rztN|AD+_vh$j6mujbYTGB9I6Z2x@Xp4&bxdNrCfW538o)y485%1$dtz!D z`{fGFLz$>?o;wUVeVcK*7k#&GjP_4-XFqZ4gufakJwFqT>3| z#`2;%r)MsQX5gfg->Wz7xq;1X5MITRYoJpBBT+l?;X+*v>S zu6$f0R4yf7PWoUo)zTNgHtSb(c;L5cApWnHwdlS(cHGh~&!ZtSbD!L|xN&aQ_SNvI zrk<&NC`r_xsU-YQMQvx%vo?!axlWS8Fn*PjM8ayZYVP3X7NnG!SG5!%!TlDD&kO50 zxCWU$!jyEs4D;%*31*2pDu2Wt1J?$eX2o8+k5iGG@_-mrvVM{SnoFC3kyk6Rl8GL)Vxu+AZ^#LOZNwO*6}Wn0kU#Vt zaP!$#{ZScN+}^sD3U=CjIm^WJKjknv5|D(@*Xr9Ns{OqG?lxR%ciD9`%i2Wnm zWs)C5zX%g*+&{vMBWGT4ePLQ}JfVUD!mIrY)@VV?o92y3DGkeFYz$n~(?As0w{d^u z9)0djxWIrG)@KhkJiE5=GWxS7jq=@-g|rq-$!Q1+`d#k$oiU*idf=d(+s;?L0ptnm z;E``^YI@P$%USPrw)=(dfIxn_m$L#OQNSzdJM3rO)EXTm35uiLWyqhosn;bFT8!PKadY4KY04`nw&#rRNUkw}u|=Rn9?O!tKA*;| z@87`9b<*(Qpr*VR4K&;Vz+C~r;gXXf;sSDlOuh$;C$c;g$#L0}|DPPRb|E!6z7UDb zuC^FkU$kMby2G>kVUxiG{dha!%cL%@SbA?=1eIiLa`DIq@wr@Mc%+GKRH|IF>wtRK zpWb{?dfZ{Tf%I{znU|9tgNBdG!06wE<%>!+DD&>)Dh~wbe0vL3lX(d9c5+yZ(KotB zRZN+vgqE^2VdcR5IpJ@#gEhZKl(>;3Gcz+lx%`Cf9`ipFiiJK^S66TJzZ}@`Z{5>F zZx(}M5jzfcb`!9K5dlTPrm8{UuEw-LvK}nRO$bKfq$mn`{Aa((q0Q9_K+7fmINV=% zDppFJ`uZi~c0rz6k|lmV(zb5k+h{0mBz-l37vu+3q2!qDHgBEy0$eQkJI_{ z+_kHastgK-Z?Z|_4{ckuC_N{q9t}*6AlK(7r#VzZGF$+P(V%Oez;6iqu zvR_9%_LZ@H4HEb|(hH%+_?Ldn*v~5t)D%BIQaADY#Q(2+^1;WAhbof)8K*=uC?5NN zN;uCY+9=qP>umUAKQZxoXPcYvg`gebL$C)7HQ360F{_P1Ha;^&Uw=GRnJL%u&ppiCmby#+d+uXB6!J55Y#Y|nMWyTM~@ zOrOgU#}MzEIV}BI#ceONaNqHhs@=rVww$AdY+QMm22YSP*d(^8CY9^-W7$X7Ij*$n_7ZaL#r3cHUjR&>IIFAL1iAnXh zVp28(M>cAwND>4B5qQcDk8En<)6n=ueRBmm>#Zw$K>ax+O#GiWiB8rdDqW#~Y1q-` z$A>L2|CEv?6be&R zSiz2E{IL^c5ZoSfOB?vcwu`^=&j~l`Fm9az%ykqE$a>t(D^Y|jl5Zo-JksKrL<^}J zH>B{mo2I4@y?m>?Q}Hm+P+ZlEbjg8$2oc+;M zIVXonbZsKr-7W3szs}>35(n@g0L3Um7CL4cY1N)vIE^T1tk%)(#_3&^%1QL4w zO?Ce)O(y)H`}@y({QTjirM|6#*)VbOn%PrrDF{Rp!9NnR0S;1=lbFs&vKRD5&J|IA>?&pEX!Uk zC69Edh!N8K4FX~G?FJy~bg0-p%7 zV(9aqG5{z0E`iob4L6AO0PnJwLti`zB=g~?sAQhLgK20Wy-{gd86jkLbD=l@4uf&` zed_H(|MpaEOL&2M;VDv9UamSKVK@DvTTx8B=JJ1Lg&s=$bN8P3tGs0pjRPhd9tW)a`86GU8Rt{@K98JVjHYI6gWqUaB4{zmfN6$6KvSdq zuq6CRjn(Wp>RfTFnG74EQdd@*aV;W(NYBJ1qDzGdEWxBxY|AxKI*(Sc09bcxYDPtj z8H;Dmjf@ZzO;DzmuuVoMVBm{A!rv~Ittjdf=O!KFW$(VW>u+Koe6+#;U!F zkgf51yp8e3Rza**=SQ%}=qL!J!R#?3`BafZQxFT1}}GK?Er&T~|3fDd4z4{s4Z#?;B7`LreQIHSG8Uwo;Yt zJ7XzZFl7q;sF#lq!IxDH?8Zi9e@iIiebvN;b-X(%1icWAM4{kkd%A!|y=8B5z7nTm z=>Fd}VQu#;{QPp^Uv8um2lQZdawdz<#Zh1m%#e9`*H^oB*U$H5X2*@5)ufDrexXE= z`2|~!8@CP_h!w+U+%+{dStj7b0D+!;@MiIDAT%-}T0XvfCu0Z+F)^{VFtV2?C!}cC zkkddQrDc~JB8_W){}h(xPl^KXTQ<;2RDVy%GL*J^C}3eKfjzgV=mtdZ*)xfEy{`%j z$-oPXR3x}{EB)<<}pj`~mD|9FP*r;hu3XUg{`r-Xf$d<_goXD>ps6zp=#Fy8Ij@AEcg zLow$8bIqqALNGg59tC@QNfA*Y$`f-lv)(&TTu|u*M)z*yWGh7P@9P6I2+ipYH4Zr1LP|Yp#)WcMef4?wL5v7&(O@9PM-K_iKP6|nNeSrD%&)bO zt*|$sFHv=E?cCS?<}RsqtM2NR`;eE7C!~<{Hvx9J*8>D##1Lxg*=t_{LWF%`FxE#O zN=hA2zs$mGgyiBzlQ2o+Vdvw*q09gMXw6^`$G}_YPLEiB%)l^h!^5ng#_Gk|zlXWw zbnK|B72p%*es#R=uI2^#_D$x2sLjb+g0X&MQRk6Sv~-OG_*2zk5z9V}$e|7(r2ZY9_n8^8L#7{r0F0=}c^KqVA2&#Y7MgbFSz=b$55)Y_Rg- zl9w7A9Us@+I+m49pQ5KXR4Mm-u(+5mTuVSeWc5F#H`k#{$&!GQ2|bP~nA+YWGDkd@ z1oI@ecMs*YwOKz0ozP3SIIw=U`budPR&2T)&-6<7pS`DN%pxBa@};5rr~G@W{Onw% z=e|v(A_3ThiTYKH34Vt8?Y`Et=Vfo+@D>?-s;H||Qf#AWJXPX}`IwhbAR#(OD&4g7 z1RM(7^V~{svDjPcS3HURt|nK>=(9W{nd3JyGLl`DbBi{X!lC!gI9e{ig9|b{A0-Wu ze`0gr+h=cXVIe9nj|n2SU|~EHzyq;9LcY3ux{HhSHy(zDy>Ea2r|xBNyQ`Y8v}Uzw zGtlh-G`vdaC^HrH0+TnscW*Uq>HPVe{3QTJ8;smg%h&0VhSPvTCTA^ZL7#6w%q>Tsy9vix|%}bii(hDLR&-e0y6RoKRF&q~fyH zQ!)>@0{q!>ci{S_-0YU;IU6Xs;YfLO>6F2Z$Ohtl&{gcPZIX+I3%_rBSZ2up31$zh zAg~*yurQj#^T3If17~#Y3g|ndds$WGa1gnGZX%Nn|LwkItVNZi;u|L-t@-NEH8MWL;rr7YaT63k5lqA_XeZ z34x!Y!HsSiAA|01F@=RvkXiRiwE%p3Qdx&)OXw{ZKj_7NcU|Q{jLa*fF|Xf{;lrA0 zi~Q4c>4!HV6+wGCz5xMAI2g-BR)k|#5WTK!4LOL26tbPV!-?((ZlU{qErJO^2Vu!E z=_bk~8GrVI$B{Y3#aHFdDwy%1r*_}zxT2A|>j<+Y!U12Q=FnsE<`eJX$Rvupmvv!{ z3|9aS43qszH0bBvjYQ2ZF2+V(GoqCIdpTu>hQ^7dj)>trOoGOPJ0)Et$bsi%Xtt0*m&$m=m literal 0 HcmV?d00001 diff --git a/vimrc b/vimrc new file mode 100644 index 0000000..9b00a87 --- /dev/null +++ b/vimrc @@ -0,0 +1,268 @@ +set encoding=utf-8 + +call plug#begin('~/.vim/plugged') + +Plug 'airblade/vim-gitgutter' +Plug 'dense-analysis/ale' +Plug 'jparise/vim-graphql' +Plug 'leafgarland/typescript-vim' +Plug 'maxmellon/vim-jsx-pretty' +Plug 'neoclide/coc.nvim', {'branch': 'release'} +Plug 'pangloss/vim-javascript' +Plug 'peitalin/vim-jsx-typescript' +Plug 'tpope/vim-endwise' +Plug 'tpope/vim-fugitive' +Plug 'tpope/vim-rails' +Plug 'tpope/vim-rhubarb' +Plug 'vim-airline/vim-airline' +Plug 'vim-airline/vim-airline-themes' +Plug 'vim-ruby/vim-ruby' + +Plug 'deuxpi/witchhazel', { 'branch': 'vim-hypercolor' } + +call plug#end() + + +if has('autocmd') + filetype plugin indent on +endif +if has('syntax') && !exists('g:syntax_on') + syntax enable +endif + +set autoindent +set backspace=indent,eol,start +set smarttab + +set nrformats-=octal + +if !has('nvim') && &ttimeoutlen == -1 + set ttimeout + set ttimeoutlen=100 +endif + +set incsearch +" Use to clear the highlighting of :set hlsearch. +if maparg('', 'n') ==# '' + nnoremap :nohlsearch=has('diff')?'diffupdate':'' +endif + +set laststatus=2 +set ruler +set wildmenu + +if !&scrolloff + set scrolloff=1 +endif +if !&sidescrolloff + set sidescrolloff=5 +endif +set display+=lastline + +set autoread + +if &history < 1000 + set history=1000 +endif +if &tabpagemax < 50 + set tabpagemax=50 +endif +if !empty(&viminfo) + set viminfo^=! +endif +set sessionoptions-=options +set viewoptions-=options + +set showfulltag +set showcmd +set showmode +set magic + +" Blink matching brackets for some tenth of a second +set showmatch +set mat=2 + +silent! colorscheme witchhazel-hypercolor +set termguicolors +if $TERM ==# 'xterm-kitty' + let &t_ut='' +endif + +highlight clear SignColumn + +set nofoldenable +set cmdheight=2 +set formatoptions-=cro +set updatetime=300 +set shortmess+=c +set signcolumn=number +set hidden + +" Code formatting, tabs to 4 spaces +set expandtab +set tabstop=4 +set shiftwidth=4 +set nowrap + +set undolevels=1000 + +" Typically useful for Python code +set nofoldenable foldmethod=indent + +" Turn all kinds of temporary files off +set nobackup +set nowritebackup +set noswapfile + +set viminfo='100,<1000,s100,h + +" Override some formatting defaults based on the filetype +autocmd BufNewFile,BufRead *.mako setlocal ft=mako +autocmd BufNewFile,BufRead *.mrb set ft=ruby +autocmd BufNewFile,BufRead *.rbi set ft=ruby +autocmd FileType coffee,css,javascript,json,html,scss,typescript,xhtml,xml :set ts=2 sw=2 sts=2 et ch=2 +autocmd FileType lua :set ts=2 sw=2 sts=2 +autocmd FileType make :set ts=4 noet nolist +autocmd FileType liquid :set noeol + +" vim-ruby + +" x = if condition +" something +" end +let g:ruby_indent_assignment_style = 'variable' + +let g:ruby_space_errors = 1 + + +" Toggle code formatting when pasting chunks of text. Ridiculously useful. +map :set invpaste +set pastetoggle= + +" Automatically jump to end of text just pasted +" https://sheerun.net/2014/03/21/how-to-boost-your-vim-productivity/ +vnoremap y y`] +vnoremap p p`] +nnoremap p p`] + +" Enable extra key combinations like q to reformat a paragraph. +let mapleader="," + +" Configure Flake8 +let g:pyflakes_use_quickfix = 0 +let g:flake8_show_quickfix=1 +let g:flake8_show_in_gutter=1 +let g:flake8_show_in_file=1 + +" Coc +let g:coc_disable_startup_warning = 1 + +" Use tab for trigger completion with characters ahead and navigate. +" NOTE: Use command ':verbose imap ' to make sure tab is not mapped by +" other plugin before putting this into your config. +inoremap + \ pumvisible() ? "\" : + \ check_back_space() ? "\" : + \ coc#refresh() +inoremap pumvisible() ? "\" : "\" + +function! s:check_back_space() abort + let col = col('.') - 1 + return !col || getline('.')[col - 1] =~# '\s' +endfunction + +inoremap coc#refresh() + +autocmd CursorHold * silent call CocActionAsync('highlight') + + +vmap q gq +nmap q gqap + +noremap q: +noremap q? +command -bang Q q +command -bang W w + +" Highlight suspicious characters based on +" https://wincent.com/blog/making-vim-highlight-suspicious-characters +set listchars=space:␣,nbsp:¬,tab:>-,extends:»,precedes:«,trail:• +highlight NonText term=none cterm=none ctermfg=0 ctermbg=8 gui=none +highlight SpecialKey term=none cterm=none ctermfg=0 ctermbg=8 gui=none +map :set invlist + +" Transparent editing of GnuPG-encrypted files +" Based on a solution by Wouter Hanegraaff +augroup encrypted + au! + + " First make sure nothing is written to ~/.viminfo while editing + " an encrypted file. + autocmd BufReadPre,FileReadPre *.gpg,*.asc set viminfo= + " We don't want a swap file, as it writes unencrypted data to disk. + autocmd BufReadPre,FileReadPre *.gpg,*.asc set noswapfile + " Switch to binary mode to read the encrypted file. + autocmd BufReadPre,FileReadPre *.gpg set bin + autocmd BufReadPre,FileReadPre *.gpg,*.asc let ch_save = &ch|set ch=2 + autocmd BufReadPost,FileReadPost *.gpg,*.asc + \ '[,']!sh -c 'gpg --decrypt 2> /dev/null' + " Switch to normal mode for editing + autocmd BufReadPost,FileReadPost *.gpg set nobin + autocmd BufReadPost,FileReadPost *.gpg,*.asc let &ch = ch_save|unlet ch_save + autocmd BufReadPost,FileReadPost *.gpg,*.asc + \ execute ":doautocmd BufReadPost " . expand("%:r") + + " Convert all text to encrypted text before writing + autocmd BufWritePre,FileWritePre *.gpg set bin + autocmd BufWritePre,FileWritePre *.gpg + \ '[,']!sh -c 'gpg --default-recipient-self -e 2>/dev/null' + autocmd BufWritePre,FileWritePre *.asc + \ '[,']!sh -c 'gpg --default-recipient-self -e -a 2>/dev/null' + " Undo the encryption so we are back in the normal text, directly + " after the file has been written. + autocmd BufWritePost,FileWritePost *.gpg,*.asc u +augroup END + + +" vim-airline +let g:airline_powerline_fonts = 1 +let g:airline#extensions#ale#enabled = 1 +let g:airline_theme='bubblegum' + +" ale +let g:ale_disable_lsp = 1 +let g:ale_linters = {'ruby': ['rubocop'], 'eruby': ['erblint'], 'javascript': ['eslint']} +let g:ale_linters_ignore = {'typescript': ['eslint']} +let g:ale_fixers = {'javascript': ['eslint'], 'ruby': ['rubocop']} +let g:ale_ruby_rubocop_executable = 'bundle' +let g:ale_eruby_erblint_executable = 'bundle' +nmap f (ale_fix) + +" vim-gitgutter +let g:gitgutter_sign_added = '●' +let g:gitgutter_sign_modified = '●' +let g:gitgutter_sign_removed = '●' +let g:gitgutter_sign_removed_first_line = '●↑' +let g:gitgutter_sign_modified_removed = '●' + +" vim-json +let g:vim_json_syntax_conceal = 0 + +" fzf +set rtp+=/usr/local/opt/fzf + +" vim-rubocop +let g:vimrubocop_rubocop_cmd = 'bundle exec rubocop' + +" vim-test +let test#strategy = "dispatch" +nmap tt :TestNearest +nmap tf :TestFile +let test#ruby#minitest#options = '-p' +let test#ruby#rails#options = '-p' + +autocmd FileType ruby syn match sorbetSignature "\" nextgroup=sorbetSignatureDeclaration skipwhite skipnl +autocmd FileType ruby syn region sorbetSignatureBlock start="sig {" end="}" +autocmd FileType ruby syn region sorbetSignatureBlock start="\ \" matchgroup=sorbetSignature end="\" +autocmd FileType ruby hi def link sorbetSignature Comment +autocmd FileType ruby hi def link sorbetSignatureBlock Comment