Initial vibecoded proof of concept

This commit is contained in:
Alex Selimov 2025-10-05 20:16:33 -04:00
parent 74812459af
commit 461318a656
Signed by: aselimov
GPG key ID: 3DDB9C3E023F1F31
61 changed files with 13306 additions and 0 deletions

551
lua/notex/init.lua Normal file
View file

@ -0,0 +1,551 @@
-- Notex: Relational Document System for Neovim
local M = {}
-- Plugin configuration
local config = {
database_path = nil,
auto_index = true,
index_on_startup = false,
max_file_size = 10 * 1024 * 1024, -- 10MB
default_view_type = "table",
performance = {
max_query_time = 5000, -- 5 seconds
cache_size = 100,
enable_caching = true
},
ui = {
border = "rounded",
max_width = 120,
max_height = 30,
show_help = true
}
}
-- Plugin state
local state = {
initialized = false,
database = nil,
ui = nil,
settings = {}
}
-- Initialize plugin
function M.setup(user_config)
-- Merge user configuration
M.settings = vim.tbl_deep_extend("force", config, user_config or {})
-- Validate configuration
local valid, errors = M.validate_config(M.settings)
if not valid then
vim.notify("Notex configuration error: " .. table.concat(errors, ", "), vim.log.levels.ERROR)
return false
end
-- Initialize modules
local ok, err = M.initialize_modules()
if not ok then
vim.notify("Failed to initialize Notex: " .. err, vim.log.levels.ERROR)
return false
end
-- Initialize caching system if enabled
if M.settings.performance.enable_caching then
local cache = require('notex.utils.cache')
cache.init({
memory = {
max_size = M.settings.performance.cache_size,
enabled = true
},
lru = {
max_size = math.floor(M.settings.performance.cache_size / 2),
enabled = true
},
timed = {
default_ttl = 300, -- 5 minutes
enabled = true
}
})
end
-- Setup autocommands
M.setup_autocommands()
-- Setup keymaps
M.setup_keymaps()
-- Auto-index on startup if enabled
if M.settings.index_on_startup then
vim.defer_fn(function()
M.auto_index_current_workspace()
end, 1000)
end
state.initialized = true
vim.notify("Notex initialized successfully", vim.log.levels.INFO)
return true
end
-- Validate configuration
function M.validate_config(settings)
local errors = {}
-- Validate performance settings
if settings.performance then
if settings.performance.max_query_time and settings.performance.max_query_time < 100 then
table.insert(errors, "max_query_time must be at least 100ms")
end
if settings.performance.cache_size and settings.performance.cache_size < 10 then
table.insert(errors, "cache_size must be at least 10")
end
end
-- Validate UI settings
if settings.ui then
if settings.ui.max_width and settings.ui.max_width < 40 then
table.insert(errors, "max_width must be at least 40 characters")
end
if settings.ui.max_height and settings.ui.max_height < 10 then
table.insert(errors, "max_height must be at least 10 characters")
end
end
return #errors == 0, errors
end
-- Initialize modules
function M.initialize_modules()
-- Initialize database
local database = require('notex.database.init')
local db_path = M.settings.database_path or vim.fn.stdpath('data') .. '/notex/notex.db'
local ok, err = database.init(db_path)
if not ok then
return false, err
end
state.database = database
-- Initialize migrations
local migrations = require('notex.database.migrations')
ok, err = migrations.init()
if not ok then
return false, err
end
-- Initialize UI
local ui = require('notex.ui')
ok, err = ui.init()
if not ok then
return false, err
end
state.ui = ui
-- Initialize query engine
local query_engine = require('notex.query')
ok, err = query_engine.init(db_path)
if not ok then
return false, err
end
return true, "Modules initialized successfully"
end
-- Setup autocommands
function M.setup_autocommands()
local group = vim.api.nvim_create_augroup("Notex", {clear = true})
-- Auto-index markdown files when saved (if enabled)
if M.settings.auto_index then
vim.api.nvim_create_autocmd("BufWritePost", {
pattern = "*.md",
callback = function()
M.auto_index_file(vim.fn.expand('%:p'))
end
})
end
-- Clean up on exit
vim.api.nvim_create_autocmd("VimLeavePre", {
callback = function()
M.cleanup()
end
})
-- Detect query blocks and show hover
vim.api.nvim_create_autocmd({"CursorHold", "CursorHoldI"}, {
pattern = "*.md",
callback = function()
M.check_query_block_under_cursor()
end
})
end
-- Setup keymaps
function M.setup_keymaps()
-- Global keymaps
local keymaps = {
["<leader>nq"] = {callback = M.show_query_prompt, desc = "New query"},
["<leader>nr"] = {callback = M.show_recent_queries, desc = "Recent queries"},
["<leader>ns"] = {callback = M.show_saved_queries, desc = "Saved queries"},
["<leader>ni"] = {callback = M.index_workspace, desc = "Index workspace"},
["<leader>ns"] = {callback = M.show_index_status, desc = "Index status"},
["<leader>nc"] = {callback = M.cleanup_database, desc = "Cleanup database"},
["<leader>nv"] = {callback = M.switch_view_type, desc = "Switch view type"},
["<leader>ne"] = {callback = M.show_export_menu, desc = "Export view"}
}
for key, mapping in pairs(keymaps) do
if not vim.fn.hasmapto(key, "n") then
vim.keymap.set("n", key, mapping.callback, {
noremap = true,
silent = true,
desc = mapping.desc
})
end
end
-- Buffer-local keymaps for query blocks
vim.api.nvim_create_autocmd("FileType", {
pattern = "markdown",
callback = function()
vim.keymap.set("n", "K", M.show_query_under_cursor, {
buffer = 0,
noremap = true,
silent = true,
desc = "Show query"
})
end
})
end
-- Show query prompt
function M.show_query_prompt(initial_query)
local ui = state.ui or require('notex.ui')
ui.show_query_prompt(initial_query)
end
-- Show recent queries
function M.show_recent_queries()
local query_engine = require('notex.query')
local result = query_engine.get_query_statistics()
if result.success and result.statistics.recent_queries then
local ui = state.ui or require('notex.ui')
-- Create a simple UI to show recent queries
local recent_queries = result.statistics.recent_queries
if #recent_queries > 0 then
local choices = {}
for _, query in ipairs(recent_queries) do
table.insert(choices, string.format("%s (%d uses)", query.name, query.use_count))
end
vim.ui.select(choices, {
prompt = "Select recent query:"
}, function(choice)
if choice then
local query_name = choice:match("^(%s+) %(")
if query_name then
ui.execute_query_and_show_results(query_name:trim())
end
end
end)
else
vim.notify("No recent queries found", vim.log.levels.INFO)
end
else
vim.notify("Failed to get query statistics", vim.log.levels.ERROR)
end
end
-- Show saved queries
function M.show_saved_queries()
local query_engine = require('notex.query')
local result = query_engine.list_saved_queries()
if result.success then
local choices = {}
for _, query in ipairs(result.queries) do
table.insert(choices, query.name)
end
vim.ui.select(choices, {
prompt = "Select saved query:"
}, function(choice)
if choice then
local ui = state.ui or require('notex.ui')
ui.execute_query_and_show_results(choice)
end
end)
else
vim.notify("Failed to get saved queries: " .. result.error, vim.log.levels.ERROR)
end
end
-- Execute query and show results
function M.execute_query_and_show_results(query_string, options)
local utils = require('notex.utils')
-- Use caching if enabled
local result
if M.settings.performance.enable_caching then
local cache_key = "query:" .. vim.fn.sha256(query_string)
result = utils.cache_get_or_set(cache_key, function()
local query_engine = require('notex.query')
return query_engine.execute_query(query_string, options)
end, "lru", 300) -- Cache for 5 minutes
else
local query_engine = require('notex.query')
result = query_engine.execute_query(query_string, options)
end
if result.success then
local ui = state.ui or require('notex.ui')
ui.show_query_results(result, M.settings.ui)
else
local ui = state.ui or require('notex.ui')
ui.show_error("Query Error", result.errors, M.settings.ui)
end
end
-- Index workspace
function M.index_workspace()
local workspace_dir = vim.fn.getcwd()
M.index_directory(workspace_dir, {
force_reindex = vim.fn.confirm("Force reindex? " , "&Yes\n&No", 2) == 1
})
end
-- Index directory
function M.index_directory(directory, options)
options = options or {}
vim.notify("Indexing documents in " .. directory, vim.log.levels.INFO)
local indexer = require('notex.index')
local result = indexer.index_documents(directory, options)
if result.success then
vim.notify(string.format("Indexing complete: %d documents processed", result.stats.indexed), vim.log.levels.INFO)
M.log_indexing_result(result)
else
vim.notify("Indexing failed: " .. table.concat(result.errors, ", "), vim.log.levels.ERROR)
end
end
-- Auto-index current workspace
function M.auto_index_current_workspace()
local workspace_dir = vim.fn.getcwd()
M.index_directory(workspace_dir)
end
-- Auto-index single file
function M.auto_index_file(file_path)
local indexer = require('notex.index')
-- Validate file
local validation = require('notex.index.scanner').validate_markdown_file(file_path)
if not validation.valid then
return
end
local result = indexer.update_document(file_path)
if result.success then
vim.notify("Indexed: " .. file_path, vim.log.levels.INFO)
end
end
-- Show index status
function M.show_index_status()
local indexer = require('notex.index')
local stats = indexer.get_statistics()
local lines = {
"Notex Index Status",
string.rep("=", 50),
"",
string.format("Documents indexed: %d", stats.document_count or 0),
string.format("Properties: %d", stats.property_count or 0),
string.format("Unique properties: %d", stats.unique_properties or 0),
"",
"Database:",
string.format(" Size: %s bytes", stats.database.size_bytes or 0),
string.format(" WAL mode: %s", stats.database.wal_mode and "Yes" or "No"),
"",
"Press any key to close"
}
local buffer = vim.api.nvim_create_buf(false, true)
vim.api.nvim_buf_set_lines(buffer, 0, -1, false, lines)
vim.api.nvim_buf_set_option(buffer, "filetype", "text")
local window = vim.api.nvim_open_win(buffer, true, {
relative = "editor",
width = 60,
height = 20,
row = math.floor((vim.api.nvim_get_option_value("lines", {}) - 20) / 2),
col = math.floor((vim.api.nvim_get_option_value("columns", {}) - 60) / 2),
border = "rounded",
style = "minimal",
title = " Index Status "
})
vim.api.nvim_create_autocmd("CursorMoved,WinLeave", {
buffer = buffer,
once = true,
callback = function()
vim.api.nvim_win_close(window, true)
end
})
end
-- Switch view type
function M.switch_view_type()
local ui = state.ui or require('notex.ui')
ui.show_view_type_menu()
end
-- Show export menu
function M.show_export_menu()
local ui = state.ui or require('notex.ui')
ui.show_export_menu()
end
-- Check query block under cursor
function M.check_query_block_under_cursor()
local line = vim.api.nvim_get_current_line()
local cursor = vim.api.nvim_win_get_cursor(0)
-- Check if cursor is on a query block
if line:match("```notex%-query") or
line:match("FROM") or
line:match("WHERE") or
line:match("ORDER BY") then
-- Could be inside a query block, check surrounding context
M.show_query_under_cursor()
end
end
-- Show query under cursor
function M.show_query_under_cursor()
local line_start = math.max(1, vim.api.nvim_win_get_cursor(0)[1] - 5)
local line_end = math.min(vim.api.nvim_buf_line_count(0), vim.api.nvim_win_get_cursor(0)[1] + 5)
local lines = vim.api.nvim_buf_get_lines(0, line_start - 1, line_end - line_start + 1, false)
local query_block = M.extract_query_block(lines)
if query_block then
M.execute_query_and_show_results(query_block)
end
end
-- Extract query block from lines
function M.extract_query_block(lines)
local in_query_block = false
local query_lines = {}
for _, line in ipairs(lines) do
if line:match("```notex%-query") then
in_query_block = true
elseif line:match("```") and in_query_block then
break
elseif in_query_block then
table.insert(query_lines, line)
end
end
if #query_lines > 0 then
return table.concat(query_lines, "\n")
end
return nil
end
-- Cleanup database
function M.cleanup_database()
local indexer = require('notex.index')
local cleanup_result = indexer.cleanup_index()
vim.notify(string.format("Cleanup completed: %d orphans removed, %d missing files removed",
cleanup_result.removed_orphans,
cleanup_result.removed_missing), vim.log.levels.INFO)
end
-- Log indexing result
function M.log_indexing_result(result)
if result.stats.scanned > 0 then
local success_rate = (result.stats.indexed / result.stats.scanned) * 100
vim.notify(string.format("Indexing success rate: %.1f%%", success_rate), vim.log.levels.INFO)
end
if #result.errors > 0 then
vim.notify("Indexing had " .. #result.errors .. " errors", vim.log.levels.WARN)
end
end
-- Cleanup
function M.cleanup()
if state.ui then
state.ui.cleanup_all()
end
if state.database then
state.database.close()
end
state.initialized = false
end
-- Get plugin status
function M.status()
return {
initialized = state.initialized,
database = state.database and state.database.status() or nil,
settings = M.settings
}
end
-- Health check
function M.health_check()
local health = {
ok = true,
checks = {}
}
-- Check database
if state.database then
local db_status = state.database.status()
table.insert(health.checks, {
name = "Database",
status = db_status.initialized and "ok" or "error",
message = db_status.initialized and "Database initialized" or "Database not initialized"
})
else
table.insert(health.checks, {
name = "Database",
status = "error",
message = "Database not initialized"
})
health.ok = false
end
-- Check modules
local modules = {"database", "parser", "query", "ui", "index"}
for _, module_name in ipairs(modules) do
local ok, module = pcall(require, "notex." .. module_name)
table.insert(health.checks, {
name = "Module: " .. module_name,
status = ok and "ok" or "error",
message = ok and "Module loaded" or "Module failed to load"
})
if not ok then
health.ok = false
end
end
return health
end
-- Export main API
return M