Initial vibecoded proof of concept
This commit is contained in:
parent
74812459af
commit
461318a656
61 changed files with 13306 additions and 0 deletions
551
lua/notex/init.lua
Normal file
551
lua/notex/init.lua
Normal 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
|
Loading…
Add table
Add a link
Reference in a new issue