This commit is contained in:
Lucas F. 2024-12-10 19:07:29 -03:00
commit 5931cb012f
9 changed files with 900 additions and 0 deletions

23
lua/django/helpers.lua Normal file
View file

@ -0,0 +1,23 @@
local plen_status_ok, _ = pcall(require, "plenary")
if not plen_status_ok then return end
local M = {}
local Path = require "plenary.path"
function M.get_path(str) return str:match "(.*[/\\])" end
function M.add_trailing_slash(value)
if value:sub(-1) ~= Path.path.sep then return value .. Path.path.sep end
return value
end
function M.split(inputstr, sep)
if sep == nil then sep = Path.path.sep end
local t = {}
for str in string.gmatch(inputstr, "([^" .. sep .. "]+)") do
table.insert(t, str)
end
return t
end
return M

10
lua/django/init.lua Normal file
View file

@ -0,0 +1,10 @@
local mapping = require "django.mappings"
local M = {}
function M.setup()
mapping.setup_global_mappings()
end
return M

399
lua/django/input.lua Normal file
View file

@ -0,0 +1,399 @@
local nui,_ = pcall(require, "nui.input")
if not nui then return end
local M = { fn = {} }
local fn = {}
local state = {
query = '',
history = nil,
idx_hist = 0,
hooks = {},
cmdline = {},
user_opts = {},
prompt_length = 0,
prompt_content = ''
}
local defaults = {
cmdline = {
enable_keymaps = true,
smart_history = true,
prompt = ': '
},
popup = {
position = {
row = '10%',
col = '50%',
},
size = {
width = '60%',
},
border = {
style = 'rounded',
},
win_options = {
winhighlight = 'Normal:Normal,FloatBorder:FloatBorder',
},
},
hooks = {
before_mount = function(input)
end,
after_mount = function(input)
end,
set_keymaps = function(imap, feedkeys)
end
}
}
M.inp = nil
fn.check_nvim = function()
if vim.fn.has('nvim-0.7') == 1 then
fn.map = function(lhs, rhs)
vim.keymap.set('i', lhs, rhs, { buffer = M.inp.bufnr, noremap = true })
end
fn.nmap = function(lhs, rhs)
vim.keymap.set('n', lhs, rhs, { buffer = M.inp.bufnr, noremap = true })
end
else
fn.map = function(lhs, rhs)
if type(rhs) == 'string' then
vim.api.nvim_buf_set_keymap(M.inp.bufnr, 'i', lhs, rhs, { noremap = true })
else
M.inp:map('i', lhs, rhs, { noremap = true }, true)
end
end
fn.nmap = function(lhs, rhs)
M.inp:map('n', lhs, rhs, { noremap = true }, true)
end
end
end
M.setup = function(config, input_opts, callback)
config = config or {}
input_opts = input_opts or {}
state.user_opts = config
defaults.cmdline.prompt = input_opts.prompt or ": "
local popup_options = fn.merge(defaults.popup, config.popup)
state.hooks = fn.merge(defaults.hooks, config.hooks)
state.cmdline = fn.merge(defaults.cmdline, config.cmdline)
state.prompt_length = state.cmdline.prompt:len()
state.prompt_content = state.cmdline.prompt
return {
popup = popup_options,
input = {
prompt = state.cmdline.prompt,
default_value = input_opts.default_value,
on_change = fn.on_change(),
on_close = function() fn.reset_history() end,
on_submit = callback
}
}
end
M.open = function(opts, callback)
local ui = M.setup(state.user_opts, opts, callback)
fn.check_nvim()
M.inp = require('nui.input')(ui.popup, ui.input)
state.hooks.before_mount(M.inp)
M.inp:mount()
vim.bo.omnifunc = 'v:lua._fine_cmdline_omnifunc'
if state.cmdline.enable_keymaps then
fn.keymaps()
end
if vim.fn.has('nvim-0.7') == 0 then
fn.map('<BS>', function() fn.prompt_backspace(state.prompt_length) end)
end
state.hooks.set_keymaps(fn.map, fn.feedkeys)
state.hooks.after_mount(M.inp)
end
fn.on_change = function()
local prev_hist_idx = 0
return function(value)
if prev_hist_idx == state.idx_hist then
state.query = value
return
end
if value == '' then
return
end
prev_hist_idx = state.idx_hist
end
end
fn.keymaps = function()
fn.map('<Esc>', M.fn.close)
fn.map('<C-c>', M.fn.close)
fn.nmap('<Esc>', M.fn.close)
fn.nmap('<C-c>', M.fn.close)
fn.map('<Tab>', M.fn.complete_or_next_item)
fn.map('<S-Tab>', M.fn.stop_complete_or_previous_item)
if state.cmdline.smart_history then
fn.map('<Up>', M.fn.up_search_history)
fn.map('<Down>', M.fn.down_search_history)
else
fn.map('<Up>', M.fn.up_history)
fn.map('<Down>', M.fn.down_history)
end
end
M.fn.close = function()
if vim.fn.pumvisible() == 1 then
fn.feedkeys('<C-e>')
else
fn.feedkeys('<Space>')
vim.defer_fn(function()
local ok = pcall(M.inp.input_props.on_close)
if not ok then
pcall(vim.api.nvim_win_close, M.inp.winid, true)
pcall(vim.api.nvim_buf_delete, M.inp.bufnr, { force = true })
end
end, 3)
end
end
M.fn.up_search_history = function()
if vim.fn.pumvisible() == 1 then return end
local prompt = state.prompt_length
local line = vim.fn.getline('.')
local user_input = line:sub(prompt + 1, vim.fn.col('.'))
if line:len() == prompt then
M.fn.up_history()
return
end
fn.cmd_history()
local idx = state.idx_hist == 0 and 1 or (state.idx_hist + 1)
while (state.history[idx]) do
local cmd = state.history[idx]
if vim.startswith(cmd, state.query) then
state.idx_hist = idx
fn.replace_line(cmd)
return
end
idx = idx + 1
end
state.idx_hist = 1
if user_input ~= state.query then
fn.replace_line(state.query)
end
end
M.fn.down_search_history = function()
if vim.fn.pumvisible() == 1 then return end
local prompt = state.prompt_length
local line = vim.fn.getline('.')
local user_input = line:sub(prompt + 1, vim.fn.col('.'))
if line:len() == prompt then
M.fn.down_history()
return
end
fn.cmd_history()
local idx = state.idx_hist == 0 and #state.history or (state.idx_hist - 1)
while (state.history[idx]) do
local cmd = state.history[idx]
if vim.startswith(cmd, state.query) then
state.idx_hist = idx
fn.replace_line(cmd)
return
end
idx = idx - 1
end
state.idx_hist = #state.history
if user_input ~= state.query then
fn.replace_line(state.query)
end
end
M.fn.up_history = function()
if vim.fn.pumvisible() == 1 then return end
fn.cmd_history()
state.idx_hist = state.idx_hist + 1
local cmd = state.history[state.idx_hist]
if not cmd then
state.idx_hist = 0
return
end
fn.replace_line(cmd)
end
M.fn.down_history = function()
if vim.fn.pumvisible() == 1 then return end
fn.cmd_history()
state.idx_hist = state.idx_hist - 1
local cmd = state.history[state.idx_hist]
if not cmd then
state.idx_hist = 0
return
end
fn.replace_line(cmd)
end
M.fn.complete_or_next_item = function()
state.uses_completion = true
if vim.fn.pumvisible() == 1 then
fn.feedkeys('<C-n>')
else
fn.feedkeys('<C-x><C-o>')
end
end
M.fn.stop_complete_or_previous_item = function()
if vim.fn.pumvisible() == 1 then
fn.feedkeys('<C-p>')
else
fn.feedkeys('<C-x><C-z>')
end
end
M.fn.next_item = function()
if vim.fn.pumvisible() == 1 then
fn.feedkeys('<C-n>')
end
end
M.fn.previous_item = function()
if vim.fn.pumvisible() == 1 then
fn.feedkeys('<C-p>')
end
end
M.omnifunc = function(start, base)
local prompt_length = state.prompt_length
local line = vim.fn.getline('.')
local input = line:sub(prompt_length + 1)
if start == 1 then
local split = vim.split(input, ' ')
local last_word = split[#split]
local len = #line - #last_word
for i = #split - 1, 1, -1 do
local word = split[i]
if vim.endswith(word, [[\\]]) then
break
elseif vim.endswith(word, [[\]]) then
len = len - #word - 1
else
break
end
end
return len
end
return vim.api.nvim_buf_call(vim.fn.bufnr('#'), function()
return vim.fn.getcompletion(input .. base, 'file')
end)
end
fn.replace_line = function(cmd)
vim.cmd('normal! V"_c')
vim.api.nvim_buf_set_lines(
M.inp.bufnr,
vim.fn.line('.') - 1,
vim.fn.line('.'),
true,
{ state.prompt_content .. cmd }
)
vim.api.nvim_win_set_cursor(
M.inp.winid,
{ vim.fn.line('$'), vim.fn.getline('.'):len() }
)
end
fn.cmd_history = function()
if state.history then return end
local history_string = vim.fn.execute('history cmd')
local history_list = vim.split(history_string, '\n')
local results = {}
for i = #history_list, 3, -1 do
local item = history_list[i]
local _, finish = string.find(item, '%d+ +')
table.insert(results, string.sub(item, finish + 1))
end
state.history = results
end
fn.reset_history = function()
state.idx_hist = 0
state.history = nil
state.query = ''
end
fn.merge = function(defaults, override)
return vim.tbl_deep_extend(
'force',
{},
defaults,
override or {}
)
end
fn.feedkeys = function(keys)
vim.api.nvim_feedkeys(
vim.api.nvim_replace_termcodes(keys, true, true, true),
'n',
true
)
end
fn.prompt_backspace = function(prompt)
local cursor = vim.api.nvim_win_get_cursor(0)
local line = cursor[1]
local col = cursor[2]
if col ~= prompt then
local completion = vim.fn.pumvisible() == 1 and state.uses_completion
if completion then fn.feedkeys('<C-x><C-z>') end
vim.api.nvim_buf_set_text(0, line - 1, col - 1, line - 1, col, { '' })
vim.api.nvim_win_set_cursor(0, { line, col - 1 })
if completion then fn.feedkeys('<C-x><C-o>') end
end
end
_fine_cmdline_omnifunc = M.omnifunc
return M

17
lua/django/lazy.lua Normal file
View file

@ -0,0 +1,17 @@
local lazy = {}
--- Require on index.
---
--- Will only require the module after the first index of a module.
--- Only works for modules that export a table.
---@param require_path string
---@return table
lazy.require = function(require_path)
return setmetatable({}, {
__index = function(_, key) return require(require_path)[key] end,
__newindex = function(_, key, value) require(require_path)[key] = value end,
})
end
return lazy

94
lua/django/mappings.lua Normal file
View file

@ -0,0 +1,94 @@
local django = require "django.utils"
local mappings = {
n = {
["<leader>j"] = { desc = " Django" },
["<leader>ja"] = {
function()
django.create_app()
end,
desc = "create app",
},
["<leader>jp"] = {
function()
django.create_package()
end,
desc = "create python package",
},
["<leader>js"] = {
function()
django.manage_shell_plus(true)
end,
desc = "shell plus",
},
["<leader>je"] = {
function()
django.create_env_file()
end,
desc = "create env file",
},
["<leader>jr"] = {
function()
django.manage_run_server "make run"
end,
desc = "run server",
},
["<leader>jm"] = {
function()
django.manage_make_migrations()
end,
desc = "make migrations",
},
["<leader>ji"] = {
function()
django.manage_migrate()
end,
desc = "migrate",
},
["<leader>jc"] = {
function()
django.check()
end,
desc = "check",
},
["<leader>jh"] = {
function()
django.show_migrations()
end,
desc = "show migrations",
},
["<leader>jy"] = {
function()
django.run_celery()
end,
desc = "run celery",
},
},
}
local M = {}
local function key_map(mod, lhs, rhs, opts)
if type(lhs) == "string" then
vim.keymap.set(mod, lhs, rhs, opts)
elseif type(lhs) == "table" then
for _, key in pairs(lhs) do
vim.keymap.set(mod, key, rhs, opts)
end
end
end
function M.setup_global_mappings()
if mappings then
for k, v in pairs(mappings.n) do
if unpack(v) == nil then
key_map("n", k, "", { desc = v.desc })
else
key_map("n", k, unpack(v), { desc = v.desc })
end
end
end
end
return M

267
lua/django/utils.lua Normal file
View file

@ -0,0 +1,267 @@
local plen_status_ok, _ = pcall(require, "plenary")
if not plen_status_ok then
return
end
local term_stat_ok, _ = pcall(require, "toggleterm.terminal")
if not term_stat_ok then
return
end
local inp = require "django.input"
local helpers = require "django.helpers"
local toggleterm = require "toggleterm"
local terms = require "toggleterm.terminal"
local CHARS = "abcdefghijklmnopqrstuvwxyz0123456789!@#$%^&*(-_=+)"
local function generate_secret_key()
local chars = {}
for x in CHARS:gmatch "[a-zA-Z0-9!@#$%^&*(-_=+)]" do
table.insert(chars, x)
end
local key = ""
math.randomseed(os.time())
for _ = 1, 50 do
key = key .. chars[math.random(1, #chars)]
end
return key
end
local M = {}
local Path = require "plenary.path"
local env_opts = {
secret_key = generate_secret_key(),
allowed_hosts = { "localhost", "10.0.2.2", "127.0.0.1" },
local_ip = "",
debug = "True",
database = {
user = "postgres",
password = "postgres",
dbname = "db",
port = "5433",
address = "127.0.0.1",
},
admin = {
username = "admin",
email = "adm@email.com",
password = "",
},
email = {
host = "",
port = "587",
host_user = "",
host_password = "",
use_tls = "True",
default_from_email = "",
},
celery = {
result_backend = "django-db",
broker_url = "redis://localhost:6379",
accept_content = "application/json",
result_serializer = "json",
task_serializer = "json",
timezone = "America/Sao_Paulo",
task_time_limit = "30 * 60",
},
}
M.title = "Django.nvim"
local function execute_command(cmd, args)
local ok, err = pcall(cmd, args)
---@diagnostic disable-next-line: param-type-mismatch
if not ok then
pcall(vim.notify, err, vim.log.levels.ERROR)
end
end
local function setup_opts(opts)
vim.validate { opts = { opts, "table", true } }
opts = vim.tbl_deep_extend("force", env_opts, opts or {})
vim.validate {
secret_key = { opts.secret_key, "string" },
allowed_hosts = { opts.allowed_hosts, "table" },
database = { opts.database, "table" },
admin = { opts.admin, "table" },
email = { opts.email, "table" },
celery = { opts.celery, "table" },
}
return opts
end
local function file_exists(name)
local f = io.open(name, "r")
if f ~= nil then
io.close(f)
return true
else
return false
end
end
local function create_template_static_dirs(name, dir)
execute_command(os.execute, "mkdir -p " .. dir .. "/static/" .. name .. "/css")
execute_command(os.execute, "mkdir -p " .. dir .. "/static/" .. name .. "/img")
execute_command(os.execute, "mkdir -p " .. dir .. "/static/" .. name .. "/js")
execute_command(os.execute, "mkdir -p " .. dir .. "/templates/" .. name)
end
local function get_app_name(path)
local name = helpers.split(helpers.add_trailing_slash(path))
return name[#name]
end
local function check_app(path)
return vim.loop.fs_stat(helpers.add_trailing_slash(path) .. "apps.py") ~= nil
end
local function get_module_name(path)
return path:gsub(Path.path.sep, ".")
end
local function perform_create_app(value)
local appname = get_app_name(value)
if appname then
if vim.loop.fs_stat(value) == nil then
execute_command(os.execute, "mkdir " .. value)
end
if check_app(value) == true then
vim.notify("This app already exists", vim.log.levels.ERROR, { title = M.title })
return
end
execute_command(os.execute, "./manage.py startapp " .. appname .. " " .. value)
if check_app(value) == true then
create_template_static_dirs(value, value)
execute_command(
os.execute,
"echo -e \"from django.urls import path\n\napp_name = '"
.. appname
.. "'\n\nurlpatterns = []\" > "
.. value
.. "/urls.py"
)
execute_command(
os.execute,
'echo -e " internal_app = True" >> ' .. helpers.add_trailing_slash(value) .. "apps.py"
)
local cmd = string.format(
'sed -i "s/"%s"/"%s"/" %s',
appname,
get_module_name(value),
helpers.add_trailing_slash(value) .. "apps.py"
)
os.execute(cmd)
end
vim.notify("App " .. value .. " created!", vim.log.levels.INFO, { title = M.title .. " - startapp" })
end
end
local function perform_create_package(value)
if vim.loop.fs_stat(value) == nil then
execute_command(os.execute, "mkdir -p " .. value)
execute_command(os.execute, "touch " .. helpers.add_trailing_slash(value) .. "__init__.py")
vim.notify("Package " .. value .. " created!", vim.log.levels.INFO, { title = M.title .. " - create package" })
end
end
local function env_ip(ip)
if #ip > 0 then
return ", " .. ip
end
return ip
end
function M.create_app()
inp.open({ prompt = "create app: " }, perform_create_app)
end
function M.create_package()
inp.open({ prompt = "create package: " }, perform_create_package)
end
function M.create_env_file(opts)
local opts = setup_opts(opts)
if file_exists ".env" then
vim.notify("File .env already exists", vim.log.levels.WARN, { title = "Create env file" })
return
end
local env_file = io.open(".env", "w")
local env_text = [[
SECRET_KEY=']] .. opts.secret_key .. "'\n" .. "ALLOWED_HOSTS=localhost, 10.0.2.2, 127.0.0.1" .. env_ip(opts.local_ip) .. "\n" .. "DEBUG=" .. opts.debug .. "\n\n" .. "DATABASE_URL=postgres://" .. opts.database.user .. ":" .. opts.database.password .. "@" .. opts.database.address .. ":" .. opts.database.port .. "/" .. opts.database.dbname .. "\n\n" .. "DB_USER=\n" .. "DB_PASSWORD=\n" .. "DB_HOST=\n" .. "DB_PORT=\n" .. "\n" .. "ADMIN_USERNAME=" .. opts.admin.username .. "\n" .. "ADMIN_EMAIL=" .. opts.admin.email .. "\n" .. "ADMIN_PASSWORD=" .. opts.admin.password .. "\n\n" .. "EMAIL_HOST=" .. opts.email.host .. "\n" .. "EMAIL_PORT=" .. opts.email.port .. "\n" .. "EMAIL_HOST_USER=" .. opts.email.host_user .. "\n" .. "EMAIL_HOST_PASSWORD=" .. opts.email.host_password .. "\n" .. "EMAIL_USE_TLS=" .. opts.email.use_tls .. "\n" .. "DEFAULT_FROM_EMAIL=" .. opts.email.default_from_email .. "\n\n" .. "CELERY_RESULT_BACKEND=" .. opts.celery.result_backend .. "\n" .. "CELERY_BROKER_URL=" .. opts.celery.broker_url .. "\n" .. "CELERY_ACCEPT_CONTENT=" .. opts.celery.accept_content .. "\n" .. "CELERY_RESULT_SERIALIZER=" .. opts.celery.result_serializer .. "\n" .. "CELERY_TASK_SERIALIZER=" .. opts.celery.task_serializer .. "\n" .. "CELERY_TIMEZONE=" .. opts.celery.timezone .. "\n" .. "CELERY_TASK_TIME_LIMIT=" .. opts.celery.task_time_limit .. "\n\n" .. "AWS_ACCESS_KEY_ID=\n" .. "AWS_SECRET_ACCESS_KEY=\n" .. "AWS_STORAGE_BUCKET_NAME=\n" .. "AWS_S3_ENDPOINT_URL=\n" .. "AWS_LOCATION=\n"
if env_file ~= nil then
env_file:write(env_text)
env_file:close()
end
end
function M.manage_run_server(custom_command)
local term_num = 2
local term = terms.get(term_num, true)
if not file_exists "Makefile" then
if not file_exists "makefile" then
custom_command = "./manage.py runserver 0.0.0.0:8000"
end
end
if term then
term:toggle()
else
toggleterm.exec(custom_command, term_num, 100, ".", "float", "runserver")
end
end
function M.manage_shell_plus(is_plus)
local term_num = 10
local term = terms.get(term_num, true)
local cmd = "./manage.py shell"
if is_plus then
cmd = "./manage.py shell_plus"
end
if term then
term:toggle()
else
toggleterm.exec(cmd, term_num, 100, ".", "float", "shell_plus", true)
end
end
function M.manage_make_migrations()
local term_num = 3
toggleterm.exec("./manage.py makemigrations", term_num, 100, ".", "float", "make_migrations", false)
end
function M.manage_migrate()
local term_num = 4
toggleterm.exec("./manage.py migrate", term_num, 100, ".", "float", "migrate", false)
end
function M.check()
local term_num = 5
toggleterm.exec("./manage.py check", term_num, 100, ".", "float", "check", false)
end
function M.show_migrations()
local term_num = 6
toggleterm.exec("./manage.py showmigrations", term_num, 100, ".", "float", "showmigrations", false)
end
function M.run_celery()
local term_num = 7
local term = terms.get(term_num, true)
if term then
term:toggle()
else
toggleterm.exec("make run_celery", term_num, 100, ".", "float", "run_celery", false)
end
end
return M