480 lines
14 KiB
Lua
480 lines
14 KiB
Lua
|
-- UI coordination module
|
||
|
local M = {}
|
||
|
|
||
|
local buffer_manager = require('notex.ui.buffer')
|
||
|
local view = require('notex.ui.view')
|
||
|
local editor = require('notex.ui.editor')
|
||
|
local query_engine = require('notex.query')
|
||
|
local utils = require('notex.utils')
|
||
|
|
||
|
-- UI state
|
||
|
local ui_state = {
|
||
|
active_views = {},
|
||
|
default_options = {
|
||
|
max_width = 120,
|
||
|
max_height = 30,
|
||
|
show_help = true,
|
||
|
border = "rounded"
|
||
|
}
|
||
|
}
|
||
|
|
||
|
-- Show query results
|
||
|
function M.show_query_results(query_results, options)
|
||
|
options = options or {}
|
||
|
options = vim.tbl_deep_extend("force", ui_state.default_options, options)
|
||
|
|
||
|
-- Validate query results
|
||
|
if not query_results.success then
|
||
|
M.show_error("Query Error", query_results.errors, options)
|
||
|
return nil, query_results.errors
|
||
|
end
|
||
|
|
||
|
-- Create view
|
||
|
local view_config = view.create_query_view(query_results, options)
|
||
|
if not view_config then
|
||
|
return nil, "Failed to create query view"
|
||
|
end
|
||
|
|
||
|
-- Store active view
|
||
|
ui_state.active_views[view_config.buffer_id] = view_config
|
||
|
|
||
|
-- Setup auto-cleanup
|
||
|
M.setup_view_cleanup(view_config)
|
||
|
|
||
|
utils.log("INFO", "Created query view", {
|
||
|
buffer_id = view_config.buffer_id,
|
||
|
document_count = #query_results.documents,
|
||
|
view_type = options.view_type or "table"
|
||
|
})
|
||
|
|
||
|
return view_config
|
||
|
end
|
||
|
|
||
|
-- Show error message
|
||
|
function M.show_error(title, errors, options)
|
||
|
options = options or {}
|
||
|
|
||
|
local error_lines = {title, string.rep("=", #title), ""}
|
||
|
|
||
|
if type(errors) == "string" then
|
||
|
table.insert(error_lines, errors)
|
||
|
elseif type(errors) == "table" then
|
||
|
for _, error in ipairs(errors) do
|
||
|
table.insert(error_lines, "• " .. error)
|
||
|
end
|
||
|
end
|
||
|
|
||
|
table.insert(error_lines, "")
|
||
|
table.insert(error_lines, "Press any key to close")
|
||
|
|
||
|
local buffer = vim.api.nvim_create_buf(false, true)
|
||
|
vim.api.nvim_buf_set_lines(buffer, 0, -1, false, error_lines)
|
||
|
vim.api.nvim_buf_set_option(buffer, "filetype", "text")
|
||
|
vim.api.nvim_buf_set_name(buffer, "notex://error")
|
||
|
|
||
|
local window = vim.api.nvim_open_win(buffer, true, {
|
||
|
relative = "editor",
|
||
|
width = math.min(80, vim.api.nvim_get_option_value("columns", {})),
|
||
|
height = math.min(20, vim.api.nvim_get_option_value("lines", {})),
|
||
|
row = math.floor((vim.api.nvim_get_option_value("lines", {}) - 20) / 2),
|
||
|
col = math.floor((vim.api.nvim_get_option_value("columns", {}) - 80) / 2),
|
||
|
border = "rounded",
|
||
|
style = "minimal",
|
||
|
title = " Error "
|
||
|
})
|
||
|
|
||
|
-- Close on any key
|
||
|
vim.api.nvim_create_autocmd("CursorMoved,WinLeave", {
|
||
|
buffer = buffer,
|
||
|
once = true,
|
||
|
callback = function()
|
||
|
vim.api.nvim_win_close(window, true)
|
||
|
end
|
||
|
})
|
||
|
|
||
|
return {
|
||
|
buffer_id = buffer,
|
||
|
window_id = window,
|
||
|
type = "error"
|
||
|
}
|
||
|
end
|
||
|
|
||
|
-- Show document details
|
||
|
function M.show_document_details(document_id, options)
|
||
|
options = options or {}
|
||
|
|
||
|
-- Get document details
|
||
|
local indexer = require('notex.index')
|
||
|
local doc_details, err = indexer.get_document_details(document_id)
|
||
|
if not doc_details then
|
||
|
M.show_error("Document Error", {err or "Failed to get document details"})
|
||
|
return nil
|
||
|
end
|
||
|
|
||
|
-- Create detail view
|
||
|
local buffer = vim.api.nvim_create_buf(false, true)
|
||
|
vim.api.nvim_buf_set_option(buffer, "filetype", "yaml")
|
||
|
vim.api.nvim_buf_set_name(buffer, "notex://document-details")
|
||
|
|
||
|
-- Generate detail content
|
||
|
local lines = M.generate_document_details(doc_details)
|
||
|
vim.api.nvim_buf_set_lines(buffer, 0, -1, false, lines)
|
||
|
|
||
|
-- Create window
|
||
|
local window = vim.api.nvim_open_win(buffer, true, {
|
||
|
relative = "editor",
|
||
|
width = math.min(100, vim.api.nvim_get_option_value("columns", {})),
|
||
|
height = math.min(40, vim.api.nvim_get_option_value("lines", {})),
|
||
|
row = 1,
|
||
|
col = 1,
|
||
|
border = "rounded",
|
||
|
style = "minimal",
|
||
|
title = " Document Details "
|
||
|
})
|
||
|
|
||
|
-- Setup mappings
|
||
|
local mappings = {
|
||
|
["<CR>"] = {
|
||
|
callback = function()
|
||
|
vim.cmd('edit ' .. doc_details.document.file_path)
|
||
|
vim.api.nvim_win_close(window, true)
|
||
|
end,
|
||
|
description = "Open document"
|
||
|
},
|
||
|
["e"] = {
|
||
|
callback = function()
|
||
|
editor.start_edit_mode(buffer, 1, 1)
|
||
|
end,
|
||
|
description = "Edit properties"
|
||
|
},
|
||
|
["q"] = {
|
||
|
callback = function()
|
||
|
vim.api.nvim_win_close(window, true)
|
||
|
end,
|
||
|
description = "Close"
|
||
|
}
|
||
|
}
|
||
|
|
||
|
for key, action in pairs(mappings) do
|
||
|
vim.keymap.set("n", key, action.callback, {
|
||
|
buffer = buffer,
|
||
|
noremap = true,
|
||
|
silent = true
|
||
|
})
|
||
|
end
|
||
|
|
||
|
return {
|
||
|
buffer_id = buffer,
|
||
|
window_id = window,
|
||
|
type = "document_details",
|
||
|
document_id = document_id
|
||
|
}
|
||
|
end
|
||
|
|
||
|
-- Generate document details content
|
||
|
function M.generate_document_details(doc_details)
|
||
|
local lines = {}
|
||
|
|
||
|
-- Header
|
||
|
table.insert(lines, "Document Details")
|
||
|
table.insert(lines, string.rep("=", 50))
|
||
|
table.insert(lines, "")
|
||
|
|
||
|
-- File information
|
||
|
table.insert(lines, "## File Information")
|
||
|
table.insert(lines, string.format("Path: %s", doc_details.document.file_path))
|
||
|
table.insert(lines, string.format("Created: %s", os.date("%Y-%m-%d %H:%M:%S", doc_details.document.created_at)))
|
||
|
table.insert(lines, string.format("Modified: %s", os.date("%Y-%m-%d %H:%M:%S", doc_details.document.updated_at)))
|
||
|
table.insert(lines, string.format("Content Hash: %s", doc_details.document.content_hash))
|
||
|
table.insert(lines, string.format("File Exists: %s", doc_details.file_exists and "Yes" or "No"))
|
||
|
table.insert(lines, "")
|
||
|
|
||
|
-- Properties
|
||
|
table.insert(lines, "## Properties")
|
||
|
if doc_details.properties and #doc_details.properties > 0 then
|
||
|
for _, prop in ipairs(doc_details.properties) do
|
||
|
local value_str = tostring(prop.value)
|
||
|
if #value_str > 100 then
|
||
|
value_str = value_str:sub(1, 97) .. "..."
|
||
|
end
|
||
|
table.insert(lines, string.format("%s: %s (%s)", prop.key, value_str, prop.value_type))
|
||
|
end
|
||
|
else
|
||
|
table.insert(lines, "No properties found")
|
||
|
end
|
||
|
table.insert(lines, "")
|
||
|
|
||
|
-- Parse information
|
||
|
if doc_details.parse_result and doc_details.parse_result.success then
|
||
|
table.insert(lines, "## Analysis")
|
||
|
local analysis = doc_details.parse_result.markdown_analysis
|
||
|
table.insert(lines, string.format("Word Count: %d", analysis.word_count))
|
||
|
table.insert(lines, string.format("Character Count: %d", analysis.character_count))
|
||
|
table.insert(lines, string.format("Line Count: %d", analysis.line_count))
|
||
|
table.insert(lines, string.format("Reading Time: %d minutes", analysis.reading_time_minutes))
|
||
|
|
||
|
if #analysis.headings > 0 then
|
||
|
table.insert(lines, "")
|
||
|
table.insert(lines, "### Headings")
|
||
|
for _, heading in ipairs(analysis.headings) do
|
||
|
local indent = string.rep(" ", heading.level)
|
||
|
table.insert(lines, indent .. heading.title)
|
||
|
end
|
||
|
end
|
||
|
|
||
|
if #analysis.links > 0 then
|
||
|
table.insert(lines, "")
|
||
|
table.insert(lines, "### Links")
|
||
|
for i, link in ipairs(analysis.links) do
|
||
|
if i <= 5 then -- Limit to 5 links
|
||
|
table.insert(lines, string.format("• %s → %s", link.text, link.url))
|
||
|
end
|
||
|
end
|
||
|
if #analysis.links > 5 then
|
||
|
table.insert(lines, string.format("... and %d more", #analysis.links - 5))
|
||
|
end
|
||
|
end
|
||
|
end
|
||
|
|
||
|
table.insert(lines, "")
|
||
|
table.insert(lines, "Press <Enter> to open document, e to edit, q to close")
|
||
|
|
||
|
return lines
|
||
|
end
|
||
|
|
||
|
-- Switch view type
|
||
|
function M.switch_view_type(new_view_type)
|
||
|
local current_buffer = vim.api.nvim_get_current_buf()
|
||
|
local success, result = view.switch_view_type(current_buffer, new_view_type)
|
||
|
|
||
|
if success then
|
||
|
vim.notify("Switched to " .. new_view_type .. " view", vim.log.levels.INFO)
|
||
|
else
|
||
|
vim.notify("Failed to switch view: " .. result, vim.log.levels.ERROR)
|
||
|
end
|
||
|
end
|
||
|
|
||
|
-- Show view type menu
|
||
|
function M.show_view_type_menu()
|
||
|
local view_types = view.get_available_view_types()
|
||
|
local choices = {}
|
||
|
|
||
|
for _, view_type in ipairs(view_types) do
|
||
|
table.insert(choices, string.format("%s %s - %s", view_type.icon, view_type.name, view_type.description))
|
||
|
end
|
||
|
|
||
|
vim.ui.select(choices, {
|
||
|
prompt = "Select view type:",
|
||
|
format_item = function(item)
|
||
|
return item
|
||
|
end
|
||
|
}, function(choice)
|
||
|
if choice then
|
||
|
-- Extract view type name from choice
|
||
|
local view_type = choice:match("%s(%w+)%s-") or choice:match("(%w+)%s-")
|
||
|
if view_type then
|
||
|
M.switch_view_type(view_type)
|
||
|
end
|
||
|
end
|
||
|
end)
|
||
|
end
|
||
|
|
||
|
-- Export view
|
||
|
function M.export_view(format)
|
||
|
local current_buffer = vim.api.nvim_get_current_buf()
|
||
|
local success, result = view.export_view(current_buffer, format)
|
||
|
|
||
|
if success then
|
||
|
-- Ask user for file location
|
||
|
local default_filename = "notex_export." .. format
|
||
|
vim.ui.input({ prompt = "Export to file: ", default = default_filename }, function(filename)
|
||
|
if filename and filename ~= "" then
|
||
|
local write_ok, write_err = utils.write_file(filename, result)
|
||
|
if write_ok then
|
||
|
vim.notify("Exported to " .. filename, vim.log.levels.INFO)
|
||
|
else
|
||
|
vim.notify("Failed to export: " .. write_err, vim.log.levels.ERROR)
|
||
|
end
|
||
|
end
|
||
|
end)
|
||
|
else
|
||
|
vim.notify("Failed to export: " .. result, vim.log.levels.ERROR)
|
||
|
end
|
||
|
end
|
||
|
|
||
|
-- Show export menu
|
||
|
function M.show_export_menu()
|
||
|
local formats = {
|
||
|
{name = "Markdown", extension = "md", description = "Markdown format"},
|
||
|
{name = "CSV", extension = "csv", description = "Comma-separated values"},
|
||
|
{name = "JSON", extension = "json", description = "JSON format"}
|
||
|
}
|
||
|
|
||
|
local choices = {}
|
||
|
for _, format in ipairs(formats) do
|
||
|
table.insert(choices, string.format("%s (%s) - %s", format.name, format.extension, format.description))
|
||
|
end
|
||
|
|
||
|
vim.ui.select(choices, {
|
||
|
prompt = "Select export format:"
|
||
|
}, function(choice)
|
||
|
if choice then
|
||
|
local format_name = choice:match("(%w+)%s+%(")
|
||
|
if format_name then
|
||
|
M.export_view(format_name:lower())
|
||
|
end
|
||
|
end
|
||
|
end)
|
||
|
end
|
||
|
|
||
|
-- Show query prompt
|
||
|
function M.show_query_prompt(initial_query)
|
||
|
initial_query = initial_query or ""
|
||
|
|
||
|
vim.ui.input({
|
||
|
prompt = "Query: ",
|
||
|
default = initial_query,
|
||
|
completion = "customlist,require('notex.ui').get_query_completions"
|
||
|
}, function(query_string)
|
||
|
if query_string and query_string ~= "" then
|
||
|
M.execute_query_and_show_results(query_string)
|
||
|
end
|
||
|
end)
|
||
|
end
|
||
|
|
||
|
-- Execute query and show results
|
||
|
function M.execute_query_and_show_results(query_string, options)
|
||
|
options = options or {}
|
||
|
|
||
|
vim.notify("Executing query...", vim.log.levels.INFO)
|
||
|
|
||
|
-- Execute query
|
||
|
local result = query_engine.execute_query(query_string, options)
|
||
|
|
||
|
if result.success then
|
||
|
-- Show results
|
||
|
local view_config = M.show_query_results(result, options)
|
||
|
if view_config then
|
||
|
utils.log("INFO", "Query executed successfully", {
|
||
|
document_count = #result.documents,
|
||
|
execution_time_ms = result.execution_time_ms
|
||
|
})
|
||
|
end
|
||
|
else
|
||
|
M.show_error("Query Failed", result.errors, options)
|
||
|
end
|
||
|
end
|
||
|
|
||
|
-- Get query completions
|
||
|
function M.get_query_completions()
|
||
|
local suggestions = query_engine.get_suggestions("", 0)
|
||
|
|
||
|
local completions = {}
|
||
|
|
||
|
-- Property suggestions
|
||
|
for _, prop in ipairs(suggestions.properties or {}) do
|
||
|
table.insert(completions, prop .. ":")
|
||
|
end
|
||
|
|
||
|
-- Value suggestions for common properties
|
||
|
for prop, values in pairs(suggestions.values or {}) do
|
||
|
for _, value in ipairs(values) do
|
||
|
table.insert(completions, prop .. ' = "' .. value .. '"')
|
||
|
end
|
||
|
end
|
||
|
|
||
|
-- Operator suggestions
|
||
|
for _, op in ipairs(suggestions.operators or {}) do
|
||
|
table.insert(completions, "WHERE " .. op)
|
||
|
end
|
||
|
|
||
|
table.sort(completions)
|
||
|
|
||
|
return completions
|
||
|
end
|
||
|
|
||
|
-- Setup view cleanup
|
||
|
function M.setup_view_cleanup(view_config)
|
||
|
local group = vim.api.nvim_create_augroup("NotexViewCleanup_" .. view_config.buffer_id, {clear = true})
|
||
|
|
||
|
vim.api.nvim_create_autocmd({"BufLeave", "WinLeave"}, {
|
||
|
buffer = view_config.buffer_id,
|
||
|
once = true,
|
||
|
callback = function()
|
||
|
-- Give user time to interact, then cleanup
|
||
|
vim.defer_fn(function()
|
||
|
if vim.api.nvim_buf_is_valid(view_config.buffer_id) then
|
||
|
ui_state.active_views[view_config.buffer_id] = nil
|
||
|
end
|
||
|
end, 1000)
|
||
|
end
|
||
|
})
|
||
|
end
|
||
|
|
||
|
-- Get UI status
|
||
|
function M.get_ui_status()
|
||
|
local active_views = buffer_manager.get_active_buffers()
|
||
|
local active_editors = editor.get_active_editors()
|
||
|
|
||
|
return {
|
||
|
active_views_count = vim.tbl_count(active_views),
|
||
|
active_editors_count = vim.tbl_count(active_editors),
|
||
|
total_windows = vim.tbl_count(vim.api.nvim_list_wins()),
|
||
|
current_buffer = vim.api.nvim_get_current_buf(),
|
||
|
current_window = vim.api.nvim_get_current_win()
|
||
|
}
|
||
|
end
|
||
|
|
||
|
-- Cleanup all UI components
|
||
|
function M.cleanup_all()
|
||
|
-- Close all notex buffers
|
||
|
local buffers = vim.api.nvim_list_bufs()
|
||
|
for _, buf in ipairs(buffers) do
|
||
|
local buf_name = vim.api.nvim_buf_get_name(buf)
|
||
|
if buf_name:match("^notex://") then
|
||
|
if vim.api.nvim_buf_is_valid(buf) then
|
||
|
vim.api.nvim_buf_delete(buf, {force = true})
|
||
|
end
|
||
|
end
|
||
|
end
|
||
|
|
||
|
-- Clear state
|
||
|
ui_state.active_views = {}
|
||
|
buffer_manager.cleanup_buffers()
|
||
|
|
||
|
utils.log("INFO", "Cleaned up all UI components")
|
||
|
end
|
||
|
|
||
|
-- Initialize UI system
|
||
|
function M.init()
|
||
|
-- Set up global keymaps if not already set
|
||
|
local global_keymaps = {
|
||
|
["<leader>nq"] = {
|
||
|
callback = function() M.show_query_prompt() end,
|
||
|
description = "New query"
|
||
|
},
|
||
|
["<leader>nv"] = {
|
||
|
callback = function() M.show_view_type_menu() end,
|
||
|
description = "Switch view type"
|
||
|
},
|
||
|
["<leader>ne"] = {
|
||
|
callback = function() M.show_export_menu() end,
|
||
|
description = "Export view"
|
||
|
}
|
||
|
}
|
||
|
|
||
|
for key, action in pairs(global_keymaps) do
|
||
|
if not vim.fn.hasmapto(key, "n") then
|
||
|
vim.keymap.set("n", key, action.callback, {
|
||
|
noremap = true,
|
||
|
silent = true,
|
||
|
desc = action.description
|
||
|
})
|
||
|
end
|
||
|
end
|
||
|
|
||
|
utils.log("INFO", "UI system initialized")
|
||
|
return true, "UI system initialized successfully"
|
||
|
end
|
||
|
|
||
|
return M
|