Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
53 changes: 53 additions & 0 deletions .github/workflows/tests.yaml
Original file line number Diff line number Diff line change
@@ -0,0 +1,53 @@
name: Neovim Plugin Tests
on:
pull_request:
paths:
- "Makefile"
- ".github/workflows/tests.yaml"
- "examples/**"
- "lua/**"
- "tests/**"
push:
paths:
- "Makefile"
- ".github/workflows/tests.yaml"
- "examples/**"
- "lua/**"
- "tests/**"

env: {}

concurrency:
group: ${{ github.workflow }}-${{ github.ref }}-${{ github.actor }}
cancel-in-progress: true

jobs:
build:
name: Run tests
timeout-minutes: 15
strategy:
fail-fast: false
matrix:
os: [ubuntu-latest]
neovim_version: ["v0.10.4", "v0.11.3", "nightly"]
include:
- os: macos-latest
neovim_version: v0.11.3
# TODO include windows when I have access to test on a windows machine.
# - os: windows-latest
# neovim_version: v0.11.3
runs-on: ${{ matrix.os }}

steps:
- uses: actions/checkout@v4
- name: Setup neovim
uses: rhysd/action-setup-vim@v1
with:
neovim: true
version: ${{ matrix.neovim_version }}

- name: Install uv
uses: astral-sh/setup-uv@v6

- name: Run tests
run: make test
3 changes: 3 additions & 0 deletions .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -214,3 +214,6 @@ cython_debug/

# PyPI configuration file
.pypirc

# test deps
deps/
75 changes: 75 additions & 0 deletions Makefile
Original file line number Diff line number Diff line change
@@ -0,0 +1,75 @@
SHELL := /bin/bash
.ONESHELL:
.SILENT:

# Run all test files
test: deps test_requirements
nvim --headless --noplugin -u ./scripts/minimal_init.lua -c "lua MiniTest.run()"

# Run test from file at `$FILE` environment variable
test_file: deps test_requirements
nvim --headless --noplugin -u ./scripts/minimal_init.lua -c "lua MiniTest.run_file('$(FILE)')"

# Install all test dependencies
deps: deps/mini.nvim \
deps/nvim-dap \
deps/nvim-dap-python \
deps/nvim-lspconfig \
deps/nui.nvim \
deps/LuaSnip \
deps/neotest \
deps/neotest-python \
deps/nvim-treesitter

test_requirements:
for req in "python3" "uv"; do \
command -v "$$req" > /dev/null || { echo "system cmd '$$req' required for tests"; exit 1; } ;\
done
for req in "venv"; do \
python3 -c "import $$req" || { echo "python dependency '$$req' required for tests"; exit 1; } ;\
done

deps/nvim-treesitter:
set -x
@mkdir -p deps
git clone --filter=blob:none https://github.com/nvim-treesitter/nvim-treesitter $@

deps/mini.nvim:
set -x
@mkdir -p deps
git clone --filter=blob:none https://github.com/echasnovski/mini.nvim $@

deps/nvim-dap:
set -x
@mkdir -p deps
git clone --filter=blob:none https://github.com/mfussenegger/nvim-dap $@

deps/nvim-dap-python:
set -x
@mkdir -p deps
git clone --filter=blob:none https://github.com/mfussenegger/nvim-dap-python $@

deps/nvim-lspconfig:
set -x
@mkdir -p deps
git clone --filter=blob:none https://github.com/neovim/nvim-lspconfig $@

deps/nui.nvim:
set -x
@mkdir -p deps
git clone --filter=blob:none https://github.com/MunifTanjim/nui.nvim $@

deps/LuaSnip:
set -x
@mkdir -p deps
git clone --filter=blob:none https://github.com/L3MON4D3/LuaSnip $@

deps/neotest:
set -x
@mkdir -p deps
git clone --filter=blob:none https://github.com/nvim-neotest/neotest $@

deps/neotest-python:
set -x
@mkdir -p deps
git clone --filter=blob:none https://github.com/nvim-neotest/neotest-python $@
6 changes: 3 additions & 3 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -289,12 +289,12 @@ return {

- [x] Linux

- [x] MacOS (Mostly should work)
- [x] MacOS

- [x] I am detecting python interpreters in homebrew and hatch. Does the community use other methods of installing python?
- [x] I am detecting python interpreters in homebrew and hatch and uv. Testing in ci.

- [ ] Windows (Un tested)
- [ ] Most likely spots where we are mixing up `/` vs `\` in paths
- [ ] Need to test this plugin on a windows machine to verify. I have seen online that neovim users are deciding on WezTerm + WSL to handle support for neovim plugins.

## Special Thanks

Expand Down
8 changes: 8 additions & 0 deletions examples/python_projects/pip_pyproject/pyproject.toml
Original file line number Diff line number Diff line change
@@ -0,0 +1,8 @@
[project]
name = "test"
dependencies = [
"boto3"
]
version = "0.1.0"
requires-python = ">=3.12"
description = "A short description of my awesome Python package."
8 changes: 8 additions & 0 deletions examples/python_projects/pip_pyproject/script.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,8 @@
import boto3

def main():
print()


if __name__ == "__main__":
main()
1 change: 1 addition & 0 deletions examples/python_projects/pip_requirements/requirements.txt
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
boto3
8 changes: 8 additions & 0 deletions examples/python_projects/pip_requirements/script.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,8 @@
import boto3

def main():
print()


if __name__ == "__main__":
main()
1 change: 0 additions & 1 deletion examples/python_projects/uv/.python-version

This file was deleted.

2 changes: 0 additions & 2 deletions examples/python_projects/uv/main.py
Original file line number Diff line number Diff line change
@@ -1,6 +1,4 @@
import requests
import boto3
import redis

def main():
print("Hello from uv!")
Expand Down
2 changes: 0 additions & 2 deletions examples/python_projects/uv/pyproject.toml
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,5 @@ description = "Add your description here"
readme = "README.md"
requires-python = ">=3.12"
dependencies = [
"boto3>=1.37.23",
"redis>=5.2.1",
"requests>=2.32.3",
]
161 changes: 38 additions & 123 deletions examples/python_projects/uv/uv.lock

Large diffs are not rendered by default.

27 changes: 26 additions & 1 deletion lua/python/config.lua
Original file line number Diff line number Diff line change
Expand Up @@ -109,10 +109,35 @@ local defaults = {
}
}

-- Only for existing keys in `target`.
local function tbl_deep_extend_existing(target, source, prev_key)
-- Return if source or target is not a table or is nil
if type(target) ~= "table" or type(source) ~= "table" then
return target
end

for key, value in pairs(source) do
-- Only update keys that already exist in the target table
if target[key] ~= nil then
if type(target[key]) == "table" and type(value) == "table" then
prev_key = prev_key .. "." .. key
-- Recurse for nested tables
tbl_deep_extend_existing(target[key], value, prev_key)
else
-- Overwrite existing key
target[key] = value
end
else
error(("python.nvim: user inputted config key: %s is not found in config table: %s"):format(key, prev_key))
end
end
return target
end

---@param opts? python.Config
function M.setup(opts)
opts = opts or {}
M.config = vim.tbl_deep_extend('force', defaults, opts)
M.config = tbl_deep_extend_existing(defaults, opts, "config")
end

return setmetatable(M, {
Expand Down
2 changes: 0 additions & 2 deletions lua/python/treesitter/commands.lua
Original file line number Diff line number Diff line change
@@ -1,7 +1,5 @@
-- Utility functions and commands utilizing treesitter
local M = {}
local nodes = require('python.treesitter.nodes')
local ts = require('python.treesitter')
local config = require('python.config')

---get node at cursor and validate that the user has at least nvim 0.9
Expand Down
4 changes: 2 additions & 2 deletions lua/python/venv/create.lua
Original file line number Diff line number Diff line change
Expand Up @@ -283,7 +283,7 @@ end
---Create the python venv with selected interpreter"
---@param venv_path string path of venv to create
---@param python_interpreter string path to python executable to use
local function create_venv_with_python(venv_path, python_interpreter)
function M.create_venv_with_python(venv_path, python_interpreter)
vim.notify_once('python.nvim: creating venv at: ' .. venv_path, vim.log.levels.INFO)
local cmd = { python_interpreter, '-m', 'venv', venv_path }
vim.system(
Expand Down Expand Up @@ -389,7 +389,7 @@ local function venv_install_file(detect)
return
end

create_venv_with_python(venv_path_user_input, python_interpreter_user_input)
M.create_venv_with_python(venv_path_user_input, python_interpreter_user_input)

install_function(detect.venv.install_file, venv_path_user_input, function()
local val = {
Expand Down
14 changes: 10 additions & 4 deletions lua/python/venv/detect.lua
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,7 @@ local state = require("python.state")
local create = require("python.venv.create")

local M = {}
local IS_WINDOWS = vim.uv.os_uname().sysname == 'Windows_NT'

---@class DetectVEnv
---@field dir string Current working directory found containing venv
Expand Down Expand Up @@ -119,12 +120,12 @@ function M.search_up(dir_or_file)
local dir_to_check = nil
-- get parent directory of current file in buffer via vim expand
local dir_template = '%:p:h'
while not found and dir_to_check ~= '/' do
while not found and (dir_to_check ~= '/' or dir_to_check ~= "C:\\" or dir_to_check ~= "D:\\") do
dir_to_check = vim.fn.expand(dir_template)
local check_path = dir_to_check .. '/' .. dir_or_file
local check_git = dir_to_check .. '/' .. '.git'
local check_path = vim.fs.joinpath(dir_to_check, dir_or_file)
local check_git = vim.fs.joinpath(dir_to_check, '.git')
if vim.fn.isdirectory(check_path) == 1 or vim.fn.filereadable(check_path) == 1 then
found = dir_to_check .. '/' .. dir_or_file
found = vim.fs.joinpath(dir_to_check, dir_or_file)
else
dir_template = dir_template .. ':h'
end
Expand Down Expand Up @@ -155,6 +156,11 @@ function M.search_for_detected_type()
if check_type == "pattern" then
if vim.fn.search(search) ~= 0 then
local found = vim.api.nvim_buf_get_name(0)
-- Go through join path to get / slashes to be consistent in windows like
-- we get with M.search_up
if IS_WINDOWS then
found = vim.fs.joinpath(vim.fs.dirname(found), vim.fs.basename(found))
end
return found, search
end
end
Expand Down
29 changes: 29 additions & 0 deletions scripts/minimal_init.lua
Original file line number Diff line number Diff line change
@@ -0,0 +1,29 @@
-- Add current directory to 'runtimepath' to be able to use 'lua' files
vim.cmd([[let &rtp.=','.getcwd()]])

-- Set up 'mini.test' only when calling headless Neovim (like with `make test`)
if #vim.api.nvim_list_uis() == 0 then
-- Add 'mini.nvim' to 'runtimepath' to be able to use 'mini.test'
-- Assumed that 'mini.nvim' is stored in 'deps/mini.nvim'
--
local runtime_dependencies = {
"deps/mini.nvim",
"deps/nui.nvim",
"deps/nvim-treesitter",
"deps/neotest",
"deps/neotest-python",
"deps/nvim-dap",
"deps/nvim-dap-python",
"deps/nvim-lspconfig",
"deps/LuaSnip",
}
local runtime_path = vim.fn.join(runtime_dependencies, ",")
vim.cmd('set rtp+=' .. runtime_path)

-- Set up 'mini.test'
require('nui.popup')
require('luasnip.extras.fmt')
require('luasnip.nodes.absolute_indexer')
require('nvim-treesitter.locals')
require('mini.test').setup()
end
32 changes: 32 additions & 0 deletions tests/_test_template.lua
Original file line number Diff line number Diff line change
@@ -0,0 +1,32 @@
-- Define helper aliases
local new_set = MiniTest.new_set
local expect, eq = MiniTest.expect, MiniTest.expect.equality

-- Create (but not start) child Neovim object
local child = MiniTest.new_child_neovim()

-- Define main test set of this file
local T = new_set({
-- Register hooks
hooks = {
-- This will be executed before every (even nested) case
pre_case = function()
-- Restart child process with custom 'init.lua' script
child.restart({ '-u', 'scripts/minimal_init.lua' })
-- Load tested plugin
child.lua([[require('python').setup()]])
end,
-- This will be executed one after all tests from this set are finished
post_once = child.stop,
},
})

-- T['works'] = function()
-- local x = 1 + 1
-- if x ~= 2 then
-- error('`x` is not equal to 2')
-- end
-- end

-- Return test set which will be collected and execute inside `MiniTest.run()`
return T
Loading
Loading