diff --git a/ansible/inventory/group_vars/all.yml b/ansible/inventory/group_vars/all.yml index 4fe2de1..39f4716 100644 --- a/ansible/inventory/group_vars/all.yml +++ b/ansible/inventory/group_vars/all.yml @@ -33,6 +33,18 @@ common_packages: - zip - zoxide +vim_plugins_enabled: true +vim_plugin_distro_packages: + - fzf + - vim-fugitive +vim_plugin_source_plugins: + - name: fzf + repo: https://github.com/junegunn/fzf.git + version: v0.72.0 + - name: fzf.vim + repo: https://github.com/junegunn/fzf.vim.git + version: 34a564c81f36047f50e593c1656f4580ff75ccca + common_dotfiles: - name: .bashrc src: .bashrc diff --git a/ansible/inventory/group_vars/platform_freebsd.yml b/ansible/inventory/group_vars/platform_freebsd.yml index 353f434..8c2ee05 100644 --- a/ansible/inventory/group_vars/platform_freebsd.yml +++ b/ansible/inventory/group_vars/platform_freebsd.yml @@ -5,6 +5,19 @@ platform_package_manager: pkg platform_service_manager: rc ansible_python_interpreter: /usr/local/bin/python3 +vim_plugin_distro_packages: + - fzf +vim_plugin_source_plugins: + - name: fzf + repo: https://github.com/junegunn/fzf.git + version: v0.72.0 + - name: fzf.vim + repo: https://github.com/junegunn/fzf.vim.git + version: 34a564c81f36047f50e593c1656f4580ff75ccca + - name: vim-fugitive + repo: https://github.com/tpope/vim-fugitive.git + version: 3b753cf8c6a4dcde6edee8827d464ba9b8c4a6f0 + effective_user_group: "{{ user_group }}" effective_user_home: "/home/{{ effective_username }}" user_home: "/home/{{ username }}" diff --git a/ansible/inventory/group_vars/platform_void.yml b/ansible/inventory/group_vars/platform_void.yml index 8be723a..3c81ef7 100644 --- a/ansible/inventory/group_vars/platform_void.yml +++ b/ansible/inventory/group_vars/platform_void.yml @@ -3,3 +3,16 @@ platform_profile: void platform_family: void platform_package_manager: xbps platform_service_manager: runit + +vim_plugin_distro_packages: + - fzf +vim_plugin_source_plugins: + - name: fzf + repo: https://github.com/junegunn/fzf.git + version: v0.72.0 + - name: fzf.vim + repo: https://github.com/junegunn/fzf.vim.git + version: 34a564c81f36047f50e593c1656f4580ff75ccca + - name: vim-fugitive + repo: https://github.com/tpope/vim-fugitive.git + version: 3b753cf8c6a4dcde6edee8827d464ba9b8c4a6f0 diff --git a/ansible/inventory/group_vars/server.yml b/ansible/inventory/group_vars/server.yml index 2350796..7a25183 100644 --- a/ansible/inventory/group_vars/server.yml +++ b/ansible/inventory/group_vars/server.yml @@ -7,6 +7,7 @@ effective_user_group: "{{ server_user_group }}" effective_user_home: "{{ server_user_home }}" server_container_stack_dir: /opt/docker/server ai_agents_enabled: false +vim_plugins_enabled: false profile_packages: - avahi-daemon diff --git a/ansible/roles/dotfiles_common/tasks/main.yml b/ansible/roles/dotfiles_common/tasks/main.yml index e63da3d..c985d4d 100644 --- a/ansible/roles/dotfiles_common/tasks/main.yml +++ b/ansible/roles/dotfiles_common/tasks/main.yml @@ -50,6 +50,53 @@ loop_control: label: "{{ item.dest }}" +- name: Install distro packages for Vim plugins + tags: [packages, vim, fzf] + ansible.builtin.package: + name: "{{ vim_plugin_distro_packages | default([]) }}" + state: present + when: + - vim_plugins_enabled | default(false) + - (vim_plugin_distro_packages | default([])) | length > 0 + +- name: Ensure git package is installed for source Vim plugins + tags: [packages, vim, fzf] + ansible.builtin.package: + name: git + state: present + when: + - vim_plugins_enabled | default(false) + - (vim_plugin_source_plugins | default([])) | length > 0 + +- name: Ensure Vim plugin pack directory exists + tags: [dotfiles, dotfiles:common, vim, fzf] + ansible.builtin.file: + path: "{{ effective_user_home }}/.vim/pack/plugins/opt" + state: directory + owner: "{{ effective_username }}" + group: "{{ effective_user_group }}" + mode: "0755" + when: + - vim_plugins_enabled | default(false) + - (vim_plugin_source_plugins | default([])) | length > 0 + +- name: Install source Vim plugins + tags: [dotfiles, dotfiles:common, vim, fzf] + ansible.builtin.git: + repo: "{{ item.repo }}" + dest: "{{ effective_user_home }}/.vim/pack/plugins/opt/{{ item.name }}" + version: "{{ item.version }}" + update: false + become_user: "{{ effective_username }}" + environment: + HOME: "{{ effective_user_home }}" + loop: "{{ vim_plugin_source_plugins | default([]) }}" + loop_control: + label: "{{ item.name }}" + when: + - vim_plugins_enabled | default(false) + - (vim_plugin_source_plugins | default([])) | length > 0 + - name: Ensure AI config directories exist tags: [dotfiles, dotfiles:common] ansible.builtin.file: diff --git a/dotfiles/common/.vimrc b/dotfiles/common/.vimrc index 24f206d..b3d3c48 100644 --- a/dotfiles/common/.vimrc +++ b/dotfiles/common/.vimrc @@ -149,6 +149,125 @@ endif nnoremap g :silent grep! .:copen nnoremap / :silent grep! +" ---------------------------------------------------------- +" Optional plugins +" ---------------------------------------------------------- + +" Plugins are installed out-of-band by Ansible when available. Keep this +" vimrc usable as a standalone onefile: load optional packages only when they +" exist and keep builtin fallbacks for the mappings below. +let s:fzf_min_version = [0, 54, 0] + +function! s:PackAdd(package) abort + silent! execute "packadd " . a:package +endfunction + +function! s:PackAddForCommand(package, command) abort + if exists(":" . a:command) == 2 + return 1 + endif + + call s:PackAdd(a:package) + return exists(":" . a:command) == 2 +endfunction + +function! s:PackAddByRuntimeFile(runtime_file, command) abort + if exists(":" . a:command) == 2 + return 1 + endif + + " Distro packages and source installs do not always use the same Vim package + " directory name. Look for a package containing the expected runtime file and + " load it by its actual directory name. + for l:packpath in split(&packpath, ",") + let l:packages = glob(l:packpath . "/pack/*/opt/*", 0, 1) + \ + glob(l:packpath . "/pack/*/start/*", 0, 1) + for l:package_dir in l:packages + if filereadable(l:package_dir . "/" . a:runtime_file) + call s:PackAdd(fnamemodify(l:package_dir, ":t")) + if exists(":" . a:command) == 2 + return 1 + endif + endif + endfor + endfor + + return 0 +endfunction + +let s:has_fzf = s:PackAddForCommand("fzf", "FZF") + \ || s:PackAddByRuntimeFile("plugin/fzf.vim", "FZF") +let s:has_fzf_vim = s:has_fzf + \ && (s:PackAddForCommand("fzf.vim", "Files") + \ || s:PackAddByRuntimeFile("autoload/fzf/vim.vim", "Files")) +let s:has_vim_fugitive = s:PackAddForCommand("vim-fugitive", "Git") + \ || s:PackAddByRuntimeFile("autoload/fugitive.vim", "Git") + +function! s:FzfExecutable() abort + if executable("fzf") + return "fzf" + endif + + return "" +endfunction + +function! s:FzfVersionOk() abort + let l:fzf = s:FzfExecutable() + if empty(l:fzf) + return 0 + endif + + let l:version = matchstr(get(systemlist(l:fzf . " --version"), 0, ""), "^\d\+\.\d\+\.\d\+") + if empty(l:version) + return 0 + endif + + let l:parts = map(split(l:version, "\."), "str2nr(v:val)") + return l:parts[0] > s:fzf_min_version[0] + \ || (l:parts[0] == s:fzf_min_version[0] && l:parts[1] > s:fzf_min_version[1]) + \ || (l:parts[0] == s:fzf_min_version[0] && l:parts[1] == s:fzf_min_version[1] + \ && l:parts[2] >= s:fzf_min_version[2]) +endfunction + +if s:has_fzf_vim && s:FzfVersionOk() + let g:fzf_layout = {"down": "40%"} + let g:fzf_vim = get(g:, "fzf_vim", {}) + let g:fzf_vim.preview_window = ["right,50%", "ctrl-/"] +endif + +function! s:FzfCommand(command) abort + if s:has_fzf_vim && s:FzfVersionOk() && exists(":" . a:command) == 2 + execute a:command + elseif a:command ==# "Files" || a:command ==# "GFiles" || a:command ==# "GitFiles" + edit . + elseif a:command ==# "Buffers" + ls + elseif a:command ==# "Rg" + silent grep! . + copen + endif +endfunction + +function! s:GitStatus() abort + if s:has_vim_fugitive && exists(":Git") == 2 + Git + elseif executable("git") + botright new + setlocal buftype=nofile bufhidden=wipe noswapfile nobuflisted + silent read !git status --short --branch + 1delete _ + file git-status + else + echohl WarningMsg | echom "git executable not found" | echohl None + endif +endfunction + +nnoremap ff :call FzfCommand("Files") +nnoremap fg :call FzfCommand("GFiles") +nnoremap fb :call FzfCommand("Buffers") +nnoremap fr :call FzfCommand("Rg") +nnoremap gs :call GitStatus() + " ---------------------------------------------------------- " Quickfix / location list " ----------------------------------------------------------