Skip to content

[IMP] nvim: add Neovim plugin #322

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Draft
wants to merge 1 commit into
base: master
Choose a base branch
from
Draft
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
99 changes: 99 additions & 0 deletions nvim/README.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,99 @@
# Neovim

Neovim client for the Odools language server

![screenshot](https://i.imgur.com/wuqsF9q.png)

## Important ⚠️
This plugin is still in its early development stage. Don't hesitate to submit bugs, issues and/or
feedbacks to improve the user experience.

## Installation
### requirement
We recommend using nvim version `0.9.0` or later. This plugin is using
[lspconfig](https://github.com/neovim/nvim-lspconfig) to connect communicate with the language
server in your beloved editor.

### downloads
1. Install the plugin

>Installing the neovim plugin is done through the entire Odools repositery. You need to specify to your package manager the `nvim` subfolder that must be added in the runtimepath. Check the examples listed below ⚠️

Using [packer.nvim](https://github.com/wbthomason/packer.nvim)

```lua
use {
'odoo/odoo-ls',
requires = { {'neovim/nvim-lspconfig'} },
rtp = 'nvim/',
}
```

Using [lazy.nvim](https://github.com/folke/lazy.nvim)

```lua
-- init.lua:
{
'odoo/odoo-ls',
dependencies = { 'neovim/nvim-lspconfig' },
config = function(plugin)
vim.opt.rtp:append(plugin.dir .. "/nvim")
require("lazy.core.loader").packadd(plugin.dir .. "/nvim")
end,
init = function(plugin)
require("lazy.core.loader").ftdetect(plugin.dir .. "/nvim")
end
}

-- plugins/odoo.lua:
return {
'odoo/odoo-ls',
dependencies = { 'neovim/nvim-lspconfig' },
config = function(plugin)
vim.opt.rtp:append(plugin.dir .. "/nvim")
require("lazy.core.loader").packadd(plugin.dir .. "/nvim")
end,
init = function(plugin)
require("lazy.core.loader").ftdetect(plugin.dir .. "/nvim")
end
}
```

2. Download the server executable from the release assets
```bash
wget -O ~/.local/share/nvim/odoo/odoo_ls_server https://github.com/odoo/odoo-ls/releases/download/0.4.0/odoo_ls_server
```

3. downloads python [typeshed](https://github.com/python/typeshed) to enrich the server with builtin python package stubs

(2. and 3. can be done automatically via the `:Odools` [command](https://github.com/odoo/odoo-ls/tree/master/nvim/README.md#usage))


## Configuration
The plugin needs different local path and executable to be working. Here is the mandatory config
keys.

```lua
local odools = require('odools')
local h = os.getenv('HOME')
odools.setup({
-- mandatory
odoo_path = h .. "/src/odoo/",
Copy link

@theangryangel theangryangel May 28, 2025

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Although I'm an avid user of Neovim I'll confess that the finer details of Lua just don't fit in my head, so please forgive a potentially very silly question.

In this example you're showing a static path to the Odoo source code. I work with Doodba, and I am current experimenting with uv where Odoo isn't necessarily checked out into 1 place, as I typically work on many many different projects weekly. i.e. the path is dynamic based on the project.

Does this proposal allow you to supply a function here instead of a string/path, like you can with root_dir? This would allow me to dynamically set this. I would have the same follow up questions for addons, etc.

Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This PR is there to move the project from the Whenrow's repository to this one, but the details of the implementation is subject to changes.
Especially configurations, that is the main target of the next update of OdooLS. Configurations will not require specific code into LSP client anymore in 0.8.0, but config files in your filesystem, so it will be the same setup for any IDE, and can more easily be used by OdooLS. It will be very dynamic and suitable for any (or most) configuration.
Then, starting from 0.8.0, the odoo path will not be in the config files of the neovim plugin anymore ;)

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

That sounds awesome Florian. I've been eagerly watching this repository for many months, occasionally testing it. Very excited about it's progress <3

Copy link
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

@theangryangel Hi, the config I provide here doesn't work with dynamic path, unfortunately. I don't really know how tool like Doodba works, but I would suggest you to open different instance of Neovim depending on your setup for now. you can of course give the right odoo_path based on the current open file. I have for instance this in my personal config.

https://github.com/Whenrow/dotfiles/blob/406b71819079dda73741d8b774c40708b37297b4/.config/nvim/lua/whenrow/init.lua#L14-L19

It allows me to set my current dev directory one dir above odoo and have access to sibling dir containing all the extra modules

► src
   ► odoo
   ► enteprise
   ► extra_module

Thank you for the feedback anyway :)

python_path = h .. "/.pyenv/shims/python3",

-- optional
server_path = h .. "/.local/share/nvim/odoo/odoo_ls_server",
addons = {h .. "/src/enterprise/"},
additional_stubs = {h .. "/src/additional_stubs/", h .. "/src/typeshed/stubs"},
root_dir = h .. "/src/", -- working directory, odoo_path if empty
settings = {
autoRefresh = true,
autoRefreshDelay = nil,
diagMissingImportLevel = "none",
},
})
```

## Usage
Try the command `:Odools install` to fetch the language server executable from Github as well as the
python typesheds
62 changes: 62 additions & 0 deletions nvim/lua/odools/init.lua
Original file line number Diff line number Diff line change
@@ -0,0 +1,62 @@
local util = require('odools.utils')
local M = {}

local lsp_config = require('lspconfig.configs')

if not lsp_config then
vim.api.nvim_err_writeln("lsp_config not available")
return
end

M.setup = function(opt)
opt = opt or {}
opt.python_path = opt.python_path or '/usr/bin/python3'
opt.server_path = opt.server_path or util.get_server_path()
util.check_config(opt)
local odoo_path = opt.odoo_path
opt.root_dir = opt.root_dir or odoo_path
local odooConfig = {
id = 1,
name = "main config",
validatedAddonsPaths = opt.addons or {},
addons = opt.addons or {},
odooPath = odoo_path,
finalPythonPath = opt.python_path,
additional_stubs = opt.additional_stubs or {},
}
local server_path = opt.server_path
lsp_config.odools = {
default_config = {
name = 'odools',
cmd = {server_path},
root_dir = function() return vim.fn.fnamemodify(opt.server_path, ":h") end,
workspace_folders = {
{
uri = function() return opt.root_dir end,
name = function() return "base_workspace" end,
},
},
filetypes = { 'python' },
settings = {
Odoo = {
autoRefresh = opt.settings and opt.settings.autoRefresh or true,
autoRefreshDelay = opt.settings and opt.settings.autoRefreshDelay or nil,
diagMissingImportLevel = opt.settings and opt.settings.diagMissingImportLevel or "none",
configurations = { mainConfig = odooConfig },
selectedConfiguration = "mainConfig",
},
},
capabilities = {
textDocument = {
workspace = {
symbol = {
dynamicRegistration = true,
},
},
},
},
},
}
lsp_config.odools.setup {}
end
return M
30 changes: 30 additions & 0 deletions nvim/lua/odools/utils.lua
Original file line number Diff line number Diff line change
@@ -0,0 +1,30 @@
local util = {}

util.get_server_path = function()
local bin_dir_path = vim.fn.stdpath('data') .. '/odoo'
local bin_path = bin_dir_path .. '/odoo_ls_server'
if vim.fn.filereadable(bin_path) == 0 then
vim.api.nvim_out_write("[Odools] You should download the server executable\n")
end
return bin_path
end

---Get the user config to assert basic values
---@param conf {[string]: string}
util.check_config = function(conf)
if not conf then
vim.api.nvim_err_writeln("You should give a minimal configuration")
end

if not conf.odoo_path or type(conf.odoo_path) ~= 'string' or vim.fn.isdirectory(conf.odoo_path) == 0 then
vim.api.nvim_err_writeln("You should give a valid odoo path")
end
if not conf.python_path or type(conf.python_path) ~= 'string' or not vim.fn.isdirectory(conf.python_path) == 0 then
vim.api.nvim_err_writeln("You should give a valid python path")
end
if not conf.server_path or type(conf.server_path) ~= 'string' or not vim.fn.filereadable(conf.server_path) == 0 then
vim.api.nvim_err_writeln("You should give a valid server path or download it")
end
end

return util
95 changes: 95 additions & 0 deletions nvim/plugin/command.lua
Original file line number Diff line number Diff line change
@@ -0,0 +1,95 @@
local command = {}
command.target = "0.4.0"
command.bin_path = ""

local download = function(url, output_path, asset_type)
local stdout = vim.uv.new_pipe(false)
local stderr = vim.uv.new_pipe(false)

local handle
local args, cmd
if asset_type == "file" then
if vim.fn.filereadable(output_path) == 1 then
vim.api.nvim_echo({{"Delete previous file"}}, true, {})
vim.uv.fs_unlink(output_path)
end
cmd = "wget"
args = { "-q", "-O", output_path, url }
else
cmd = "git"
args = { "clone", "-q", url, output_path}
end
vim.api.nvim_echo({{"Starting download from: " .. url}}, true, {})
vim.api.nvim_echo({{"Saving to: " .. output_path}}, true, {})

handle = vim.uv.spawn(cmd, {
args = args,
stdio = { nil, stdout, stderr },
}, function(code, signal)
stdout:close()
stderr:close()
handle:close()

local msg = {"\nDownload successful!"}
if code ~= 0 then
msg = {"\nDownload failed with exit code " .. code}
else
print(vim.uv.fs_chmod(output_path, 493))
end
vim.schedule(function()
vim.api.nvim_echo({msg}, true, {})
end)
end)

stdout:read_start(function(err, data)
assert(not err, err)
if data then
io.write(data)
io.flush()
end
end)

stderr:read_start(function(err, data)
assert(not err, err)
if data then
io.write("\nError: " .. data)
io.flush()
end
end)
end

local download_requirements = function()
local bin_dir_path = vim.fn.stdpath('data') .. '/odoo'
local bin_path = bin_dir_path .. '/odoo_ls_server'
if vim.fn.isdirectory(bin_dir_path) == 0 then
os.execute('mkdir -p ' .. bin_dir_path)
end
download("https://github.com/odoo/odoo-ls/releases/download/" .. command.target .. "/odoo_ls_server", bin_path, 'file')
if vim.fn.executable('git') == 1 then
local path = bin_dir_path .. '/typeshed'
if vim.fn.isdirectory(path) == 0 then
download('https://github.com/python/typeshed.git', path, 'repo')
else
vim.api.nvim_echo({{"typeshed already downloaded"}}, true, {})
end
else
vim.api.nvim_err_writeln("git needed to download python typeshed")
end
vim.cmd.LspRestart('odools')
end

local odoo_command = function(opts)
if opts.fargs and opts.fargs[1] == "install" then
download_requirements()
end
end

vim.api.nvim_create_user_command('Odools', odoo_command, {
nargs = 1,
complete = function()
-- return completion candidates as a list-like table
return { "install" }
end,
})

return command