├── tests ├── minimal.vim └── example_spec.lua ├── .luarc.json ├── .editorconfig ├── .luacheckrc ├── ftplugin └── ansible.lua ├── .github └── workflows │ ├── linter.yml │ └── tests.yml ├── doc └── ansible.txt ├── README.md ├── ftdetect └── ansible.lua └── lua └── ansible.lua /tests/minimal.vim: -------------------------------------------------------------------------------- 1 | set rtp+=. 2 | set rtp+=../plenary.nvim 3 | runtime! plugin/plenary.vim 4 | -------------------------------------------------------------------------------- /.luarc.json: -------------------------------------------------------------------------------- 1 | { 2 | "$schema": "https://raw.githubusercontent.com/sumneko/vscode-lua/master/setting/schema.json", 3 | "Lua.workspace.checkThirdParty": false 4 | } -------------------------------------------------------------------------------- /.editorconfig: -------------------------------------------------------------------------------- 1 | root = true 2 | 3 | [*.lua] 4 | charset = utf-8 5 | indent_style = space 6 | indent_size = 2 7 | trim_trailing_whitespace = true 8 | insert_final_newline = true 9 | -------------------------------------------------------------------------------- /tests/example_spec.lua: -------------------------------------------------------------------------------- 1 | describe('Test example', function() 2 | it('Test can access vim namespace', function() 3 | assert.are.same(vim.trim(' a '), 'a') 4 | end) 5 | end) 6 | -------------------------------------------------------------------------------- /.luacheckrc: -------------------------------------------------------------------------------- 1 | ignore = { 2 | "631", -- max_line_length 3 | } 4 | globals = { 5 | "vim", 6 | } 7 | read_globals = { 8 | "describe", 9 | "it", 10 | "assert" 11 | } 12 | -------------------------------------------------------------------------------- /ftplugin/ansible.lua: -------------------------------------------------------------------------------- 1 | local api = vim.api 2 | if vim.fn.executable('ansible-doc') then 3 | vim.bo.keywordprg = ':sp term://ansible-doc' 4 | end 5 | local fname = api.nvim_buf_get_name(0) 6 | if fname:find('tasks/') then 7 | local paths = { 8 | vim.bo.path, 9 | vim.fs.dirname(fname:gsub("tasks/", "files/")), 10 | vim.fs.dirname(fname:gsub("tasks/", "templates/")), 11 | vim.fs.dirname(fname) 12 | } 13 | vim.bo.path = table.concat(paths, ",") 14 | end 15 | -------------------------------------------------------------------------------- /.github/workflows/linter.yml: -------------------------------------------------------------------------------- 1 | --- 2 | name: Lint Code Base 3 | on: 4 | pull_request: ~ 5 | push: 6 | branches: 7 | - master 8 | 9 | jobs: 10 | build: 11 | name: Lint Code Base 12 | runs-on: ubuntu-latest 13 | 14 | steps: 15 | - name: Checkout Code 16 | uses: actions/checkout@v3 17 | 18 | - name: Lint Code Base 19 | uses: github/super-linter/slim@v4 20 | env: 21 | GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} 22 | LINTER_RULES_PATH: / 23 | VALIDATE_JSCPD: false 24 | VALIDATE_PYTHON_BLACK: false 25 | -------------------------------------------------------------------------------- /doc/ansible.txt: -------------------------------------------------------------------------------- 1 | ================================================================================ 2 | *ansible* 3 | 4 | M.run() *ansible.run* 5 | Run the current file using `ansible-playbook` or `ansible localhost -m import_role` 6 | Which one is used depends on if the current file is in a /roles/ or in a /playbooks folder 7 | 8 | Can also be used to run only a selected portion (in visual mode) of a role. 9 | It will create a `tmptask` folder in the roles directly for that. 10 | 11 | 12 | vim:tw=78:ts=8:noet:ft=help:norl: 13 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # nvim-ansible 2 | 3 | ## Installation 4 | 5 | ```bash 6 | git clone \ 7 | https://github.com/mfussenegger/nvim-ansible.git \ 8 | ~/.config/nvim/pack/plugins/start/nvim-ansible 9 | ``` 10 | 11 | ## Usage 12 | 13 | Small plugin to make working with Ansible playbooks or roles more convenient: 14 | 15 | - Adds `ftdetect` pattern to recognize playbooks/roles and set `filetype` to `yaml.ansible`. 16 | - Sets `keywordprg` to `ansible-doc` if available 17 | - Sets `path` to be able to jump to files using `gf` which are `files/` next to a `tasks/` role file. 18 | - Provides a `run()` function to execute playbooks or roles using `ansible-playbook` or `ansible localhost -m import_role`. See `:help ansible` 19 | 20 | 21 | You may want to setup keymaps for the `run()` function, for example in `ftplugin/ansible.lua` add: 22 | 23 | ```lua 24 | vim.keymap.set('v', 'te', function() require('ansible').run() end, { buffer = true, silent = true }) 25 | vim.keymap.set('n', 'te', ":w :lua require('ansible').run()", { buffer = true, silent = true }) 26 | ``` 27 | 28 | 29 | Best used together with [ansible-language-server](https://github.com/ansible/ansible-language-server) 30 | -------------------------------------------------------------------------------- /.github/workflows/tests.yml: -------------------------------------------------------------------------------- 1 | --- 2 | name: Run tests 3 | on: 4 | pull_request: ~ 5 | push: 6 | branches: 7 | - master 8 | 9 | jobs: 10 | build: 11 | name: Run tests 12 | runs-on: ubuntu-latest 13 | strategy: 14 | matrix: 15 | neovim_version: ['nightly'] 16 | 17 | steps: 18 | - uses: actions/checkout@v3 19 | - run: date +%F > todays-date 20 | - name: Restore cache for today's nightly. 21 | uses: actions/cache@v2 22 | with: 23 | path: _neovim 24 | key: ${{ runner.os }}-x64-${{ hashFiles('todays-date') }} 25 | 26 | - name: Prepare plenary 27 | run: | 28 | git clone --depth 1 https://github.com/nvim-lua/plenary.nvim ~/.local/share/nvim/site/pack/vendor/start/plenary.nvim 29 | ln -s "$(pwd)" ~/.local/share/nvim/site/pack/vendor/start 30 | 31 | - name: Setup neovim 32 | uses: rhysd/action-setup-vim@v1 33 | with: 34 | neovim: true 35 | version: ${{ matrix.neovim_version }} 36 | 37 | - name: Run tests 38 | run: | 39 | nvim --headless --noplugin -u tests/minimal.vim -c "PlenaryBustedDirectory tests/ {minimal_init = 'tests/minimal.vim'}" 40 | -------------------------------------------------------------------------------- /ftdetect/ansible.lua: -------------------------------------------------------------------------------- 1 | if vim.filetype then 2 | vim.filetype.add({ 3 | pattern = { 4 | [".*/defaults/.*%.ya?ml"] = "yaml.ansible", 5 | [".*/host_vars/.*%.ya?ml"] = "yaml.ansible", 6 | [".*/group_vars/.*%.ya?ml"] = "yaml.ansible", 7 | [".*/group_vars/.*/.*%.ya?ml"] = "yaml.ansible", 8 | [".*/playbook.*%.ya?ml"] = "yaml.ansible", 9 | [".*/playbooks/.*%.ya?ml"] = "yaml.ansible", 10 | [".*/roles/.*/tasks/.*%.ya?ml"] = "yaml.ansible", 11 | [".*/roles/.*/handlers/.*%.ya?ml"] = "yaml.ansible", 12 | [".*/tasks/.*%.ya?ml"] = "yaml.ansible", 13 | [".*/molecule/.*%.ya?ml"] = "yaml.ansible", 14 | }, 15 | }) 16 | else 17 | vim.api.nvim_create_autocmd({ "BufRead", "BufNewFile" }, { 18 | pattern = { 19 | "*/defaults/*.yml", 20 | "*/defaults/*.yaml", 21 | "*/host_vars/*.yml", 22 | "*/host_vars/*.yaml", 23 | "*/group_vars/*.yml", 24 | "*/group_vars/*.yaml", 25 | "*/group_vars/*/*.yml", 26 | "*/group_vars/*/*.yaml", 27 | "*/playbook*.yml", 28 | "*/playbook*.yaml", 29 | "*/playbooks/*.yml", 30 | "*/playbooks/*.yaml", 31 | "*/roles/*/tasks/*.yml", 32 | "*/roles/*/tasks/*.yaml", 33 | "*/roles/*/handlers/*.yml", 34 | "*/roles/*/handlers/*.yaml", 35 | "*/tasks/*.yml", 36 | "*/tasks/*.yaml", 37 | "*/molecule/*.yml", 38 | "*/molecule/*.yaml", 39 | }, 40 | callback = function() 41 | vim.bo.filetype = "yaml.ansible" 42 | end, 43 | }) 44 | end 45 | -------------------------------------------------------------------------------- /lua/ansible.lua: -------------------------------------------------------------------------------- 1 | ---@mod ansible 2 | 3 | local api = vim.api 4 | local M = {} 5 | 6 | 7 | local function has_become(lines) 8 | for _, line in pairs(lines) do 9 | if line:find('become: true') then 10 | return true 11 | end 12 | end 13 | return false 14 | end 15 | 16 | 17 | local function launch_term(cmd) 18 | vim.cmd('belowright new') 19 | vim.fn.termopen(cmd, vim.empty_dict()) 20 | end 21 | 22 | 23 | ---@param mode "v"|"V" 24 | local function get_selected_lines(mode) 25 | -- [bufnr, lnum, col, off]; 1-indexed 26 | local start = vim.fn.getpos('v') 27 | local end_ = vim.fn.getpos('.') 28 | local start_row = start[2] 29 | local start_col = start[3] 30 | local end_row = end_[2] 31 | local end_col = end_[3] 32 | if start_row == end_row and end_col < start_col then 33 | end_col, start_col = start_col, end_col 34 | elseif end_row < start_row then 35 | start_row, end_row = end_row, start_row 36 | start_col, end_col = end_col, start_col 37 | end 38 | if mode == "V" then 39 | start_col = 1 40 | local lines = api.nvim_buf_get_lines(0, end_row - 1, end_row, true) 41 | end_col = #(lines[1]) 42 | end 43 | return api.nvim_buf_get_text(0, start_row - 1, start_col - 1, end_row - 1, end_col, {}) 44 | end 45 | 46 | 47 | --- Run the current file using `ansible-playbook` or `ansible localhost -m import_role` 48 | --- Which one is used depends on if the current file is in a /roles/ or in a /playbooks folder 49 | --- 50 | --- Can also be used to run only a selected portion (in visual mode) of a role. 51 | --- It will create a `tmptask` folder in the roles directly for that. 52 | function M.run() 53 | local bufnr = api.nvim_get_current_buf() 54 | local fname = api.nvim_buf_get_name(bufnr) 55 | local path = vim.fn.fnamemodify(fname, ':p') 56 | local role_pattern = '/roles/([%w-]+)/' 57 | local role_match = path:match(role_pattern) 58 | if role_match then 59 | local mode = api.nvim_get_mode().mode 60 | local become 61 | if mode == 'v' or mode == 'V' then 62 | local lines = get_selected_lines(mode) 63 | local tmptask = path:gsub(role_pattern, '/roles/tmptask/') 64 | become = has_become(lines) 65 | tmptask = vim.fn.fnamemodify(tmptask, ':h') .. '/main.yml' 66 | vim.fn.mkdir(vim.fn.fnamemodify(tmptask, ':h'), 'p') 67 | local f = io.open(tmptask, "w") 68 | assert(f, "Must be able to open tmpfile in write mode") 69 | f:write(table.concat(lines, '\n')) 70 | f:flush() 71 | f:close() 72 | role_match = 'tmptask' 73 | else 74 | local lines = api.nvim_buf_get_lines(bufnr, 0, -1, true) 75 | become = has_become(lines) 76 | end 77 | local _, end_ = path:find('/playbooks/') 78 | local playbook_dir = nil 79 | if end_ then 80 | playbook_dir = path:sub(1, end_) 81 | end 82 | local cmd = { 83 | 'ansible', 84 | 'localhost', 85 | '--playbook-dir', playbook_dir, 86 | '-m', 'import_role', 87 | '-a', 'name=' .. role_match 88 | } 89 | if become then 90 | table.insert(cmd, '-K') 91 | end 92 | launch_term(cmd) 93 | elseif path:match('/playbooks/') then 94 | local lines = api.nvim_buf_get_lines(bufnr, 0, -1, true) 95 | local cmd = {'ansible-playbook', path} 96 | if has_become(lines) then 97 | table.insert(cmd, '-K') 98 | end 99 | launch_term(cmd) 100 | end 101 | end 102 | 103 | return M 104 | --------------------------------------------------------------------------------