Skip to content

kiyoon/python-import.nvim

Repository files navigation

🐍 python-import.nvim

Ruff StyLua Actions status
Ruff Actions status
pytest doctest Actions status codecov
uv Actions status

A simple tool that auto-inserts import statements.

Sin.titulo3.mov

It matches with:

  1. Pre-defined lookup tables
  2. Existing imports in the project (implemented in Python: python-import cli)
    • It finds the imports in the project and shows a list of them to choose from.
  3. pyright/basedpyright LSP completion
  4. Just import <word> :P

This plugin doesn't detect duplicated imports. Use ruff to sort imports.

Lookup table examples

  • Path -> from pathlib import Path
  • np -> import numpy as np
  • torch -> import torch
  • logging -> import logging
  • logger ->
import logging

logger = logging.getLogger(__name__)

Most will use the current word to find the import statement, but the treesitter node can be used to find the import statement more accurately. (e.g. class SomeDataset(torch.utils.data.DataLoader) -> import torch.utils.data)

Warning

This work-in-progress plugin is not yet ready for the public. There aren't many customisation options (e.g. how to locate the import place) and the behaviour and build/configuration instructions change rapidly. If you don't want breaking changes, please fix the version of this plugin.

🛠️ Installation

Requirements

  • 💻 Neovim >= 0.10
  • pipx or uv (or any other way to install python-import cli in PATH)
  • ripgrep (brew install ripgrep or cargo install ripgrep)

Install with lazy.nvim:

  {
    "kiyoon/python-import.nvim",
    -- build = "pipx install . --force",
    build = "uv tool install . --force --reinstall",
    keys = {
      {
        "<M-CR>",
        function()
          require("python_import.api").add_import_current_word_and_notify()
        end,
        mode = { "i", "n" },
        silent = true,
        desc = "Add python import",
        ft = "python",
      },
      {
        "<M-CR>",
        function()
          require("python_import.api").add_import_current_selection_and_notify()
        end,
        mode = "x",
        silent = true,
        desc = "Add python import",
        ft = "python",
      },
      {
        "<space>i",
        function()
          require("python_import.api").add_import_current_word_and_move_cursor()
        end,
        mode = "n",
        silent = true,
        desc = "Add python import and move cursor",
        ft = "python",
      },
      {
        "<space>i",
        function()
          require("python_import.api").add_import_current_selection_and_move_cursor()
        end,
        mode = "x",
        silent = true,
        desc = "Add python import and move cursor",
        ft = "python",
      },
      {
        "<space>tr",
        function()
          require("python_import.api").add_rich_traceback()
        end,
        silent = true,
        desc = "Add rich traceback",
        ft = "python",
      },
    },
    opts = {
      -- Example 1:
        -- Default behaviour for `tqdm` is `from tqdm.auto import tqdm`.
        -- If you want to change it to `import tqdm`, you can set `import = {"tqdm"}` and `import_from = {tqdm = nil}` here.
        -- If you want to change it to `from tqdm import tqdm`, you can set `import_from = {tqdm = "tqdm"}` here.

      -- Example 2:
        -- Default behaviour for `logger` is `import logging`, ``, `logger = logging.getLogger(__name__)`.
        -- If you want to change it to `import my_custom_logger`, ``, `logger = my_custom_logger.get_logger()`,
        -- you can set `statement_after_imports = {logger = {"import my_custom_logger", "", "logger = my_custom_logger.get_logger()"}}` here.
      extend_lookup_table = {
        ---@type string[]
        import = {
          -- "tqdm",
        },

        ---@type table<string, string>
        import_as = {
          -- These are the default values. Here for demonstration.
          -- np = "numpy",
          -- pd = "pandas",
        },

        ---@type table<string, string>
        import_from = {
          -- tqdm = nil,
          -- tqdm = "tqdm",
        },

        ---@type table<string, string[]>
        statement_after_imports = {
          -- logger = { "import my_custom_logger", "", "logger = my_custom_logger.get_logger()" },
        },
      },

      ---Return nil to indicate no match is found and continue with the default lookup
      ---Return a table to stop the lookup and use the returned table as the result
      ---Return an empty table to stop the lookup. This is useful when you want to add to wherever you need to.
      ---@type fun(winnr: integer, word: string, ts_node: TSNode?): string[]?
      custom_function = function(winnr, word, ts_node)
        -- if vim.endswith(word, "_DIR") then
        --   return { "from my_module import " .. word }
        -- end
      end,
    },
  },
  "rcarriga/nvim-notify",   -- optional

🏋️ Health check

Run :checkhealth python_import and see if python-import.nvim is installed correctly.
You need to disable lazy loading or run any commands in a python file to activate the plugin first.

==============================================================================
python_import: require("python_import.health").check()

python-import ~
- OK Using Neovim >= 0.10.0
- OK `rg` is installed
- OK `python-import` is installed
- ERROR python-import cli (0.1.0+41.g9513418.dirty) and nvim plugin version (0.1.0+42.g9f74e1d.dirty) mismatch.

💻 python-import CLI

The python-import CLI is a simple Python script that counts the number of import statements used in the project.

Note

You don't need to understand this section, but it helps you understand how it works.

$ python-import count /path/to/project np
00004:import numpy as np

$ python-import count /path/to/project pl
00062:import polars as pl
00006:import pytorch_lightning as pl

If there are relative imports, it will convert them to absolute imports and show the absolute form. For example,

  • src/my_project/a.py
from . import my_method
  • src/my_project/b.py
from my_project import my_method

Then,

$ python-import count . my_method
00002:from my_project import my_method

TODO

  • Search class/function/variable definitions from project
  • Add more tests
  • Command to add imports in TYPE_CHECKING
  • Command to add lazy imports in methods
  • Command to add imports in jupytext cell
  • Search in ipynb files
  • VSCode-neovim integration (use vim.ui.select for vscode UI support. However, this complicates stuff because it runs async)
    • Currently, if you open the vscode-neovim output terminal, it is quite usable. The notification works as well. LSP doesn't work yet.