Initial vibecoded proof of concept
This commit is contained in:
parent
74812459af
commit
461318a656
61 changed files with 13306 additions and 0 deletions
561
lua/notex/ui/buffer.lua
Normal file
561
lua/notex/ui/buffer.lua
Normal file
|
@ -0,0 +1,561 @@
|
|||
-- Virtual buffer management module
|
||||
local M = {}
|
||||
|
||||
local utils = require('notex.utils')
|
||||
|
||||
-- Active buffer management
|
||||
local active_buffers = {}
|
||||
local buffer_configs = {}
|
||||
|
||||
-- Create virtual buffer for query results
|
||||
function M.create_query_buffer(query_results, options)
|
||||
options = options or {}
|
||||
|
||||
local buffer_id = vim.api.nvim_create_buf(false, true) -- unlisted, scratch buffer
|
||||
|
||||
if not buffer_id then
|
||||
return nil, "Failed to create virtual buffer"
|
||||
end
|
||||
|
||||
-- Set buffer options
|
||||
M.setup_buffer_options(buffer_id, options)
|
||||
|
||||
-- Generate buffer content
|
||||
local lines, syntax = M.generate_buffer_content(query_results, options)
|
||||
|
||||
-- Set buffer content
|
||||
vim.api.nvim_buf_set_lines(buffer_id, 0, -1, false, lines)
|
||||
|
||||
-- Set syntax highlighting
|
||||
if syntax then
|
||||
vim.api.nvim_buf_set_option(buffer_id, "filetype", syntax)
|
||||
end
|
||||
|
||||
-- Create window if requested
|
||||
local window_id
|
||||
if options.create_window ~= false then
|
||||
window_id = M.create_query_window(buffer_id, options)
|
||||
end
|
||||
|
||||
-- Store buffer configuration
|
||||
local config = {
|
||||
buffer_id = buffer_id,
|
||||
window_id = window_id,
|
||||
query_results = query_results,
|
||||
options = options,
|
||||
created_at = os.time(),
|
||||
mappings = M.setup_buffer_mappings(buffer_id, options)
|
||||
}
|
||||
|
||||
active_buffers[buffer_id] = config
|
||||
buffer_configs[buffer_id] = config
|
||||
|
||||
return config
|
||||
end
|
||||
|
||||
-- Setup buffer options
|
||||
function M.setup_buffer_options(buffer_id, options)
|
||||
local buf_opts = {
|
||||
buftype = "nofile",
|
||||
swapfile = false,
|
||||
bufhidden = "wipe",
|
||||
modifiable = options.modifiable or false,
|
||||
readonly = not (options.modifiable or false),
|
||||
textwidth = 0,
|
||||
wrapmargin = 0,
|
||||
wrap = options.wrap or false
|
||||
}
|
||||
|
||||
for opt, value in pairs(buf_opts) do
|
||||
vim.api.nvim_buf_set_option(buffer_id, opt, value)
|
||||
end
|
||||
|
||||
-- Set buffer name
|
||||
local buf_name = options.name or "notex://query-results"
|
||||
vim.api.nvim_buf_set_name(buffer_id, buf_name)
|
||||
end
|
||||
|
||||
-- Generate buffer content from query results
|
||||
function M.generate_buffer_content(query_results, options)
|
||||
local lines = {}
|
||||
local syntax = "notex"
|
||||
|
||||
-- Add header
|
||||
table.insert(lines, "Query Results")
|
||||
table.insert(lines, string.rep("=", 50))
|
||||
table.insert(lines, "")
|
||||
|
||||
-- Add query metadata
|
||||
if query_results.query_string then
|
||||
table.insert(lines, "Query: " .. query_results.query_string)
|
||||
table.insert(lines, "")
|
||||
end
|
||||
|
||||
-- Add execution statistics
|
||||
table.insert(lines, string.format("Found %d documents (%.2fms)",
|
||||
query_results.total_count or 0,
|
||||
query_results.execution_time_ms or 0))
|
||||
table.insert(lines, "")
|
||||
|
||||
-- Add document results
|
||||
if query_results.documents and #query_results.documents > 0 then
|
||||
lines = M.add_document_table(lines, query_results.documents, options)
|
||||
else
|
||||
table.insert(lines, "No documents found matching the query criteria.")
|
||||
table.insert(lines, "")
|
||||
end
|
||||
|
||||
-- Add help section
|
||||
lines = M.add_help_section(lines, options)
|
||||
|
||||
return lines, syntax
|
||||
end
|
||||
|
||||
-- Add document table to buffer
|
||||
function M.add_document_table(lines, documents, options)
|
||||
local max_width = options.max_width or 120
|
||||
local include_properties = options.include_properties or {"title", "status", "priority", "created_at"}
|
||||
|
||||
-- Calculate column widths
|
||||
local column_widths = M.calculate_column_widths(documents, include_properties, max_width)
|
||||
|
||||
-- Add table header
|
||||
local header_parts = {"#", "File"}
|
||||
for _, prop in ipairs(include_properties) do
|
||||
table.insert(header_parts, M.format_column_header(prop, column_widths[prop]))
|
||||
end
|
||||
table.insert(lines, table.concat(header_parts, " | "))
|
||||
|
||||
-- Add separator
|
||||
local separator_parts = {"-", string.rep("-", 20)}
|
||||
for _, prop in ipairs(include_properties) do
|
||||
table.insert(separator_parts, string.rep("-", column_widths[prop]))
|
||||
end
|
||||
table.insert(lines, table.concat(separator_parts, " | "))
|
||||
table.insert(lines, "")
|
||||
|
||||
-- Add document rows
|
||||
for i, doc in ipairs(documents) do
|
||||
local row_parts = {tostring(i), M.truncate_path(doc.file_path, 20)}
|
||||
|
||||
for _, prop in ipairs(include_properties) do
|
||||
local value = doc.properties and doc.properties[prop] or ""
|
||||
local formatted_value = M.format_property_value(value, column_widths[prop])
|
||||
table.insert(row_parts, formatted_value)
|
||||
end
|
||||
|
||||
table.insert(lines, table.concat(row_parts, " | "))
|
||||
end
|
||||
|
||||
table.insert(lines, "")
|
||||
|
||||
return lines
|
||||
end
|
||||
|
||||
-- Calculate column widths for table
|
||||
function M.calculate_column_widths(documents, properties, max_width)
|
||||
local widths = {}
|
||||
|
||||
-- Set minimum widths based on property names
|
||||
for _, prop in ipairs(properties) do
|
||||
widths[prop] = math.max(#prop, 10)
|
||||
end
|
||||
|
||||
-- Adjust based on content
|
||||
for _, doc in ipairs(documents) do
|
||||
for _, prop in ipairs(properties) do
|
||||
local value = doc.properties and doc.properties[prop] or ""
|
||||
local formatted = tostring(value)
|
||||
widths[prop] = math.max(widths[prop], #formatted)
|
||||
end
|
||||
end
|
||||
|
||||
-- Limit maximum width
|
||||
local total_min_width = 30 -- # + File columns
|
||||
local available_width = max_width - total_min_width
|
||||
|
||||
if #properties > 0 then
|
||||
local per_column = math.floor(available_width / #properties)
|
||||
for _, prop in ipairs(properties) do
|
||||
widths[prop] = math.min(widths[prop], per_column)
|
||||
end
|
||||
end
|
||||
|
||||
return widths
|
||||
end
|
||||
|
||||
-- Format column header
|
||||
function M.format_column_header(property, width)
|
||||
local formatted = property:gsub("_", " "):gsub("(%a)([%w_]*)", function(first, rest)
|
||||
return first:upper() .. rest:lower()
|
||||
end)
|
||||
return M.pad_right(formatted, width)
|
||||
end
|
||||
|
||||
-- Format property value for table
|
||||
function M.format_property_value(value, width)
|
||||
if not value then
|
||||
return M.pad_right("", width)
|
||||
end
|
||||
|
||||
local formatted = tostring(value)
|
||||
|
||||
-- Truncate if too long
|
||||
if #formatted > width then
|
||||
formatted = formatted:sub(1, width - 3) .. "..."
|
||||
end
|
||||
|
||||
return M.pad_right(formatted, width)
|
||||
end
|
||||
|
||||
-- Truncate file path
|
||||
function M.truncate_path(path, max_length)
|
||||
if #path <= max_length then
|
||||
return M.pad_right(path, max_length)
|
||||
end
|
||||
|
||||
local filename = vim.fn.fnamemodify(path, ":t")
|
||||
local dirname = vim.fn.fnamemodify(path, ":h")
|
||||
|
||||
if #filename + 3 <= max_length then
|
||||
local dir_length = max_length - #filename - 3
|
||||
local truncated_dir = dirname:sub(-dir_length)
|
||||
return M.pad_right("..." .. truncated_dir .. "/" .. filename, max_length)
|
||||
else
|
||||
return M.pad_right("..." .. filename:sub(-(max_length - 3)), max_length)
|
||||
end
|
||||
end
|
||||
|
||||
-- Pad string to specified width
|
||||
function M.pad_right(str, width)
|
||||
return str .. string.rep(" ", width - #str)
|
||||
end
|
||||
|
||||
-- Add help section
|
||||
function M.add_help_section(lines, options)
|
||||
if options.show_help == false then
|
||||
return lines
|
||||
end
|
||||
|
||||
table.insert(lines, "Help:")
|
||||
table.insert(lines, " <Enter> - Open document under cursor")
|
||||
table.insert(lines, " o - Open document in new tab")
|
||||
table.insert(lines, " e - Edit document properties")
|
||||
table.insert(lines, " s - Save query")
|
||||
table.insert(lines, " r - Refresh results")
|
||||
table.insert(lines, " q - Close this view")
|
||||
table.insert(lines, "")
|
||||
table.insert(lines, "Press ? for more help")
|
||||
|
||||
return lines
|
||||
end
|
||||
|
||||
-- Create window for buffer
|
||||
function M.create_query_window(buffer_id, options)
|
||||
local window_config = options.window or {}
|
||||
|
||||
-- Default window configuration
|
||||
local default_config = {
|
||||
relative = "editor",
|
||||
width = math.min(120, vim.api.nvim_get_option_value("columns", {})),
|
||||
height = math.min(30, vim.api.nvim_get_option_value("lines", {}) - 5),
|
||||
row = 1,
|
||||
col = 1,
|
||||
border = "rounded",
|
||||
style = "minimal",
|
||||
title = " Query Results ",
|
||||
title_pos = "center"
|
||||
}
|
||||
|
||||
-- Merge with user config
|
||||
local final_config = vim.tbl_deep_extend("force", default_config, window_config)
|
||||
|
||||
local window_id = vim.api.nvim_open_win(buffer_id, true, final_config)
|
||||
|
||||
if not window_id then
|
||||
return nil, "Failed to create window"
|
||||
end
|
||||
|
||||
-- Set window options
|
||||
vim.api.nvim_win_set_option(window_id, "wrap", false)
|
||||
vim.api.nvim_win_set_option(window_id, "cursorline", true)
|
||||
vim.api.nvim_win_set_option(window_id, "number", false)
|
||||
vim.api.nvim_win_set_option(window_id, "relativenumber", false)
|
||||
vim.api.nvim_win_set_option(window_id, "signcolumn", "no")
|
||||
|
||||
return window_id
|
||||
end
|
||||
|
||||
-- Setup buffer mappings
|
||||
function M.setup_buffer_mappings(buffer_id, options)
|
||||
local mappings = options.mappings or M.get_default_mappings()
|
||||
|
||||
for key, action in pairs(mappings) do
|
||||
local mode = action.mode or "n"
|
||||
local opts = {
|
||||
buffer = buffer_id,
|
||||
noremap = true,
|
||||
silent = true,
|
||||
nowait = true
|
||||
}
|
||||
|
||||
if type(action.callback) == "string" then
|
||||
vim.keymap.set(mode, key, action.callback, opts)
|
||||
elseif type(action.callback) == "function" then
|
||||
vim.keymap.set(mode, key, action.callback, opts)
|
||||
end
|
||||
end
|
||||
|
||||
return mappings
|
||||
end
|
||||
|
||||
-- Get default mappings
|
||||
function M.get_default_mappings()
|
||||
return {
|
||||
["<CR>"] = {
|
||||
callback = function()
|
||||
require('notex.ui.buffer').handle_enter_key()
|
||||
end,
|
||||
description = "Open document"
|
||||
},
|
||||
["o"] = {
|
||||
callback = function()
|
||||
require('notex.ui.buffer').handle_open_tab()
|
||||
end,
|
||||
description = "Open in new tab"
|
||||
},
|
||||
["e"] = {
|
||||
callback = function()
|
||||
require('notex.ui.buffer').handle_edit_mode()
|
||||
end,
|
||||
description = "Edit properties"
|
||||
},
|
||||
["s"] = {
|
||||
callback = function()
|
||||
require('notex.ui.buffer').handle_save_query()
|
||||
end,
|
||||
description = "Save query"
|
||||
},
|
||||
["r"] = {
|
||||
callback = function()
|
||||
require('notex.ui.buffer').handle_refresh()
|
||||
end,
|
||||
description = "Refresh results"
|
||||
},
|
||||
["q"] = {
|
||||
callback = function()
|
||||
require('notex.ui.buffer').handle_close_buffer()
|
||||
end,
|
||||
description = "Close buffer"
|
||||
},
|
||||
["?"] = {
|
||||
callback = function()
|
||||
require('notex.ui.buffer').show_help()
|
||||
end,
|
||||
description = "Show help"
|
||||
},
|
||||
["<Esc>"] = {
|
||||
callback = function()
|
||||
require('notex.ui.buffer').handle_close_buffer()
|
||||
end,
|
||||
description = "Close buffer"
|
||||
}
|
||||
}
|
||||
end
|
||||
|
||||
-- Handle Enter key - open document
|
||||
function M.handle_enter_key()
|
||||
local line = vim.api.nvim_get_current_line()
|
||||
local cursor = vim.api.nvim_win_get_cursor(0)
|
||||
local line_num = cursor[1]
|
||||
|
||||
-- Skip header lines
|
||||
if line_num < 6 then
|
||||
return
|
||||
end
|
||||
|
||||
-- Extract file path from line
|
||||
local file_path = M.extract_file_path_from_line(line)
|
||||
if file_path and utils.file_exists(file_path) then
|
||||
vim.cmd('edit ' .. file_path)
|
||||
else
|
||||
vim.notify("Cannot open file: " .. (file_path or "unknown"), vim.log.levels.WARN)
|
||||
end
|
||||
end
|
||||
|
||||
-- Handle open in new tab
|
||||
function M.handle_open_tab()
|
||||
local line = vim.api.nvim_get_current_line()
|
||||
local file_path = M.extract_file_path_from_line(line)
|
||||
|
||||
if file_path and utils.file_exists(file_path) then
|
||||
vim.cmd('tabedit ' .. file_path)
|
||||
else
|
||||
vim.notify("Cannot open file: " .. (file_path or "unknown"), vim.log.levels.WARN)
|
||||
end
|
||||
end
|
||||
|
||||
-- Handle edit mode
|
||||
function M.handle_edit_mode()
|
||||
local buffer = vim.api.nvim_get_current_buf()
|
||||
vim.api.nvim_buf_set_option(buffer, "modifiable", true)
|
||||
vim.notify("Edit mode enabled - press <Esc> to save and exit", vim.log.levels.INFO)
|
||||
end
|
||||
|
||||
-- Handle save query
|
||||
function M.handle_save_query()
|
||||
local config = buffer_configs[vim.api.nvim_get_current_buf()]
|
||||
if not config or not config.query_results then
|
||||
return
|
||||
end
|
||||
|
||||
vim.ui.input({ prompt = "Query name: " }, function(query_name)
|
||||
if query_name and query_name ~= "" then
|
||||
local query_engine = require('notex.query')
|
||||
local result = query_engine.save_query(query_name, config.query_results.query_string)
|
||||
|
||||
if result.success then
|
||||
vim.notify("Query saved: " .. query_name, vim.log.levels.INFO)
|
||||
else
|
||||
vim.notify("Failed to save query: " .. result.error, vim.log.levels.ERROR)
|
||||
end
|
||||
end
|
||||
end)
|
||||
end
|
||||
|
||||
-- Handle refresh
|
||||
function M.handle_refresh()
|
||||
local config = buffer_configs[vim.api.nvim_get_current_buf()]
|
||||
if not config or not config.query_results or not config.query_results.query_string then
|
||||
return
|
||||
end
|
||||
|
||||
-- Re-execute query
|
||||
local query_engine = require('notex.query')
|
||||
local result = query_engine.execute_query(config.query_results.query_string)
|
||||
|
||||
if result.success then
|
||||
-- Update buffer content
|
||||
local lines, _ = M.generate_buffer_content(result, config.options)
|
||||
vim.api.nvim_buf_set_lines(vim.api.nvim_get_current_buf(), 0, -1, false, lines)
|
||||
|
||||
-- Update config
|
||||
config.query_results = result
|
||||
|
||||
vim.notify("Query refreshed", vim.log.levels.INFO)
|
||||
else
|
||||
vim.notify("Failed to refresh query: " .. table.concat(result.errors, ", "), vim.log.levels.ERROR)
|
||||
end
|
||||
end
|
||||
|
||||
-- Handle close buffer
|
||||
function M.handle_close_buffer()
|
||||
local buffer = vim.api.nvim_get_current_buf()
|
||||
local config = active_buffers[buffer]
|
||||
|
||||
if config and config.window_id then
|
||||
vim.api.nvim_win_close(config.window_id, true)
|
||||
else
|
||||
vim.cmd('bdelete!')
|
||||
end
|
||||
end
|
||||
|
||||
-- Show help
|
||||
function M.show_help()
|
||||
local help_content = [[
|
||||
Notex Query Results Help:
|
||||
|
||||
Navigation:
|
||||
<Enter> - Open document under cursor
|
||||
o - Open document in new tab
|
||||
q - Close this view
|
||||
<Esc> - Close this view
|
||||
|
||||
Actions:
|
||||
e - Enable edit mode for modifying results
|
||||
s - Save current query for reuse
|
||||
r - Refresh query results
|
||||
|
||||
Other:
|
||||
? - Show this help
|
||||
j/k - Move up/down
|
||||
gg/G - Go to top/bottom
|
||||
/pattern - Search in results
|
||||
|
||||
Press any key to close this help
|
||||
]]
|
||||
|
||||
local buf = vim.api.nvim_create_buf(false, true)
|
||||
vim.api.nvim_buf_set_lines(buf, 0, -1, false, vim.split(help_content, "\n"))
|
||||
vim.api.nvim_buf_set_option(buf, "filetype", "help")
|
||||
|
||||
local win_id = vim.api.nvim_open_win(buf, 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 = " Help "
|
||||
})
|
||||
|
||||
vim.api.nvim_win_set_option(win_id, "wrap", true)
|
||||
|
||||
-- Close help on any key
|
||||
vim.api.nvim_create_autocmd("CursorMoved,WinLeave", {
|
||||
buffer = buf,
|
||||
once = true,
|
||||
callback = function()
|
||||
vim.api.nvim_win_close(win_id, true)
|
||||
end
|
||||
})
|
||||
end
|
||||
|
||||
-- Extract file path from line
|
||||
function M.extract_file_path_from_line(line)
|
||||
-- Match file path in table row
|
||||
local match = line:match("^%s*%d+%s+|?%s*([^|]+)")
|
||||
if match then
|
||||
-- Clean up the path
|
||||
local path = match:trim()
|
||||
path = path:gsub("%s+$", "") -- Remove trailing spaces
|
||||
return path
|
||||
end
|
||||
|
||||
return nil
|
||||
end
|
||||
|
||||
-- Get active buffer configurations
|
||||
function M.get_active_buffers()
|
||||
local configs = {}
|
||||
for buffer_id, config in pairs(active_buffers) do
|
||||
if vim.api.nvim_buf_is_valid(buffer_id) then
|
||||
configs[buffer_id] = config
|
||||
else
|
||||
-- Clean up invalid buffers
|
||||
active_buffers[buffer_id] = nil
|
||||
buffer_configs[buffer_id] = nil
|
||||
end
|
||||
end
|
||||
|
||||
return configs
|
||||
end
|
||||
|
||||
-- Clean up inactive buffers
|
||||
function M.cleanup_buffers()
|
||||
local to_remove = {}
|
||||
|
||||
for buffer_id, config in pairs(active_buffers) do
|
||||
if not vim.api.nvim_buf_is_valid(buffer_id) then
|
||||
table.insert(to_remove, buffer_id)
|
||||
end
|
||||
end
|
||||
|
||||
for _, buffer_id in ipairs(to_remove) do
|
||||
active_buffers[buffer_id] = nil
|
||||
buffer_configs[buffer_id] = nil
|
||||
end
|
||||
end
|
||||
|
||||
return M
|
572
lua/notex/ui/editor.lua
Normal file
572
lua/notex/ui/editor.lua
Normal file
|
@ -0,0 +1,572 @@
|
|||
-- Inline editing interface module
|
||||
local M = {}
|
||||
|
||||
local buffer_manager = require('notex.ui.buffer')
|
||||
local database = require('notex.database.schema')
|
||||
local parser = require('notex.parser')
|
||||
local utils = require('notex.utils')
|
||||
|
||||
-- Editor state
|
||||
local active_editors = {}
|
||||
|
||||
-- Start editing document properties
|
||||
function M.start_edit_mode(buffer_id, line_number, column_number)
|
||||
local config = buffer_manager.get_active_buffers()[buffer_id]
|
||||
if not config then
|
||||
return false, "Buffer not found"
|
||||
end
|
||||
|
||||
-- Get document at cursor position
|
||||
local doc_info = M.get_document_at_position(buffer_id, line_number, column_number)
|
||||
if not doc_info then
|
||||
return false, "No document found at cursor position"
|
||||
end
|
||||
|
||||
-- Parse document to get current properties
|
||||
local parse_result, parse_err = parser.parse_document(doc_info.file_path)
|
||||
if not parse_result then
|
||||
return false, "Failed to parse document: " .. parse_err
|
||||
end
|
||||
|
||||
-- Create editor session
|
||||
local editor_id = utils.generate_id()
|
||||
local editor_session = {
|
||||
id = editor_id,
|
||||
buffer_id = buffer_id,
|
||||
document_id = doc_info.document_id,
|
||||
file_path = doc_info.file_path,
|
||||
original_properties = vim.deepcopy(parse_result.properties),
|
||||
current_properties = vim.deepcopy(parse_result.properties),
|
||||
parse_result = parse_result,
|
||||
created_at = os.time(),
|
||||
modified = false
|
||||
}
|
||||
|
||||
active_editors[editor_id] = editor_session
|
||||
|
||||
-- Switch buffer to editable mode
|
||||
vim.api.nvim_buf_set_option(buffer_id, "modifiable", true)
|
||||
vim.api.nvim_buf_set_option(buffer_id, "modified", false)
|
||||
|
||||
-- Update buffer content for editing
|
||||
M.update_buffer_for_editing(buffer_id, editor_session)
|
||||
|
||||
-- Setup editor-specific mappings
|
||||
M.setup_editor_mappings(buffer_id, editor_id)
|
||||
|
||||
utils.log("INFO", "Started edit mode for document", {
|
||||
document_id = doc_info.document_id,
|
||||
file_path = doc_info.file_path
|
||||
})
|
||||
|
||||
return true, editor_id
|
||||
end
|
||||
|
||||
-- Get document at position
|
||||
function M.get_document_at_position(buffer_id, line_number, column_number)
|
||||
local config = buffer_manager.get_active_buffers()[buffer_id]
|
||||
if not config or not config.query_results.documents then
|
||||
return nil
|
||||
end
|
||||
|
||||
-- Simple mapping: line numbers correspond to document indices (accounting for headers)
|
||||
local doc_index = line_number - 6 -- Account for header lines
|
||||
if doc_index > 0 and doc_index <= #config.query_results.documents then
|
||||
local doc = config.query_results.documents[doc_index]
|
||||
return {
|
||||
document_id = doc.id,
|
||||
file_path = doc.file_path,
|
||||
document = doc,
|
||||
line_number = line_number,
|
||||
column_number = column_number
|
||||
}
|
||||
end
|
||||
|
||||
return nil
|
||||
end
|
||||
|
||||
-- Update buffer for editing
|
||||
function M.update_buffer_for_editing(buffer_id, editor_session)
|
||||
local lines = {}
|
||||
|
||||
-- Editor header
|
||||
table.insert(lines, "Edit Mode - Document Properties")
|
||||
table.insert(lines, string.rep("=", 50))
|
||||
table.insert(lines, "")
|
||||
table.insert(lines, string.format("File: %s", editor_session.file_path))
|
||||
table.insert(lines, string.format("Modified: %s", editor_session.modified and "Yes" or "No"))
|
||||
table.insert(lines, "")
|
||||
table.insert(lines, "Properties (edit values, press <Enter> to save):")
|
||||
table.insert(lines, "")
|
||||
|
||||
-- Property list
|
||||
if editor_session.current_properties then
|
||||
local sorted_props = {}
|
||||
for key, value in pairs(editor_session.current_properties) do
|
||||
table.insert(sorted_props, {key = key, value = value})
|
||||
end
|
||||
table.sort(sorted_props, function(a, b) return a.key < b.key end)
|
||||
|
||||
for i, prop in ipairs(sorted_props) do
|
||||
local line = string.format("%-20s = %s", prop.key .. ":", tostring(prop.value))
|
||||
table.insert(lines, line)
|
||||
end
|
||||
end
|
||||
|
||||
table.insert(lines, "")
|
||||
|
||||
-- Editor help
|
||||
table.insert(lines, "Editor Commands:")
|
||||
table.insert(lines, " <Enter> - Save changes")
|
||||
table.insert(lines, " <Esc> - Cancel changes")
|
||||
table.insert(lines, " a - Add new property")
|
||||
table.insert(lines, " d - Delete property at cursor")
|
||||
table.insert(lines, " u - Undo changes")
|
||||
table.insert(lines, "")
|
||||
|
||||
-- Set buffer content
|
||||
vim.api.nvim_buf_set_lines(buffer_id, 0, -1, false, lines)
|
||||
|
||||
-- Move cursor to first property
|
||||
vim.api.nvim_win_set_cursor(0, {7, 0})
|
||||
end
|
||||
|
||||
-- Setup editor mappings
|
||||
function M.setup_editor_mappings(buffer_id, editor_id)
|
||||
local opts = {
|
||||
buffer = buffer_id,
|
||||
noremap = true,
|
||||
silent = true
|
||||
}
|
||||
|
||||
-- Save and exit
|
||||
vim.keymap.set("n", "<Enter>", function()
|
||||
M.save_and_exit_edit_mode(buffer_id, editor_id)
|
||||
end, opts)
|
||||
|
||||
vim.keymap.set("i", "<Enter>", function()
|
||||
M.save_and_exit_edit_mode(buffer_id, editor_id)
|
||||
end, opts)
|
||||
|
||||
-- Cancel editing
|
||||
vim.keymap.set("n", "<Esc>", function()
|
||||
M.cancel_edit_mode(buffer_id, editor_id)
|
||||
end, opts)
|
||||
|
||||
vim.keymap.set("i", "<Esc>", function()
|
||||
M.cancel_edit_mode(buffer_id, editor_id)
|
||||
end, opts)
|
||||
|
||||
-- Add new property
|
||||
vim.keymap.set("n", "a", function()
|
||||
M.add_new_property(buffer_id, editor_id)
|
||||
end, opts)
|
||||
|
||||
-- Delete property
|
||||
vim.keymap.set("n", "d", function()
|
||||
M.delete_property_at_cursor(buffer_id, editor_id)
|
||||
end, opts)
|
||||
|
||||
-- Undo changes
|
||||
vim.keymap.set("n", "u", function()
|
||||
M.undo_changes(buffer_id, editor_id)
|
||||
end, opts)
|
||||
end
|
||||
|
||||
-- Save and exit edit mode
|
||||
function M.save_and_exit_edit_mode(buffer_id, editor_id)
|
||||
local editor_session = active_editors[editor_id]
|
||||
if not editor_session then
|
||||
return
|
||||
end
|
||||
|
||||
-- Parse edited properties from buffer
|
||||
local edited_properties = M.parse_properties_from_buffer(buffer_id)
|
||||
if not edited_properties then
|
||||
vim.notify("Failed to parse edited properties", vim.log.levels.ERROR)
|
||||
return
|
||||
end
|
||||
|
||||
-- Check if anything changed
|
||||
local changes = M.detect_property_changes(editor_session.original_properties, edited_properties)
|
||||
if #changes == 0 then
|
||||
vim.notify("No changes detected", vim.log.levels.INFO)
|
||||
M.exit_edit_mode(buffer_id, editor_id)
|
||||
return
|
||||
end
|
||||
|
||||
-- Update database
|
||||
local ok, result = M.update_document_properties(editor_session.document_id, changes)
|
||||
if not ok then
|
||||
vim.notify("Failed to update properties: " .. result, vim.log.levels.ERROR)
|
||||
return
|
||||
end
|
||||
|
||||
-- Update file on disk
|
||||
local file_ok, file_result = M.update_yaml_file(editor_session.file_path, edited_properties)
|
||||
if not file_ok then
|
||||
vim.notify("Failed to update file: " .. file_result, vim.log.levels.ERROR)
|
||||
return
|
||||
end
|
||||
|
||||
vim.notify("Properties updated successfully", vim.log.levels.INFO)
|
||||
|
||||
-- Exit edit mode and refresh view
|
||||
M.exit_edit_mode(buffer_id, editor_id)
|
||||
|
||||
-- Refresh the query results
|
||||
if result and result.refresh_query then
|
||||
buffer_manager.handle_refresh()
|
||||
end
|
||||
end
|
||||
|
||||
-- Cancel edit mode
|
||||
function M.cancel_edit_mode(buffer_id, editor_id)
|
||||
local editor_session = active_editors[editor_id]
|
||||
if not editor_session then
|
||||
return
|
||||
end
|
||||
|
||||
if editor_session.modified then
|
||||
vim.ui.select({"Discard changes", "Continue editing"}, {
|
||||
prompt = "You have unsaved changes. What would you like to do?"
|
||||
}, function(choice)
|
||||
if choice == "Discard changes" then
|
||||
M.exit_edit_mode(buffer_id, editor_id)
|
||||
end
|
||||
end)
|
||||
else
|
||||
M.exit_edit_mode(buffer_id, editor_id)
|
||||
end
|
||||
end
|
||||
|
||||
-- Exit edit mode
|
||||
function M.exit_edit_mode(buffer_id, editor_id)
|
||||
local editor_session = active_editors[editor_id]
|
||||
if not editor_session then
|
||||
return
|
||||
end
|
||||
|
||||
-- Clean up editor session
|
||||
active_editors[editor_id] = nil
|
||||
|
||||
-- Restore buffer to view mode
|
||||
local config = buffer_manager.get_active_buffers()[buffer_id]
|
||||
if config then
|
||||
-- Regenerate original view content
|
||||
local lines, _ = buffer_manager.generate_buffer_content(config.query_results, config.options)
|
||||
vim.api.nvim_buf_set_lines(buffer_id, 0, -1, false, lines)
|
||||
|
||||
vim.api.nvim_buf_set_option(buffer_id, "modifiable", false)
|
||||
vim.api.nvim_buf_set_option(buffer_id, "modified", false)
|
||||
|
||||
-- Restore original mappings
|
||||
buffer_manager.setup_buffer_mappings(buffer_id, config.options)
|
||||
end
|
||||
|
||||
utils.log("INFO", "Exited edit mode", {
|
||||
document_id = editor_session.document_id,
|
||||
file_path = editor_session.file_path
|
||||
})
|
||||
end
|
||||
|
||||
-- Parse properties from buffer
|
||||
function M.parse_properties_from_buffer(buffer_id)
|
||||
local lines = vim.api.nvim_buf_get_lines(buffer_id, 0, -1, false)
|
||||
local properties = {}
|
||||
|
||||
-- Find property lines (skip header)
|
||||
local in_properties = false
|
||||
for _, line in ipairs(lines) do
|
||||
if line:match("^Properties %(edit values") then
|
||||
in_properties = true
|
||||
continue
|
||||
end
|
||||
|
||||
if in_properties and line:trim() == "" then
|
||||
break
|
||||
end
|
||||
|
||||
if in_properties then
|
||||
local key, value = line:match("^(%s*[%w_%-%.]+%s*):%s*(.+)$")
|
||||
if key and value then
|
||||
local clean_key = key:trim():gsub(":$", "")
|
||||
local clean_value = value:trim()
|
||||
|
||||
-- Parse value (handle quotes, numbers, booleans)
|
||||
local parsed_value = M.parse_property_value(clean_value)
|
||||
properties[clean_key] = parsed_value
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
return properties
|
||||
end
|
||||
|
||||
-- Parse property value
|
||||
function M.parse_property_value(value_string)
|
||||
-- Handle quoted strings
|
||||
local quoted = value_string:match('^"(.*)"$')
|
||||
if quoted then
|
||||
return quoted
|
||||
end
|
||||
|
||||
quoted = value_string:match("^'(.*)'$")
|
||||
if quoted then
|
||||
return quoted
|
||||
end
|
||||
|
||||
-- Handle numbers
|
||||
local number = tonumber(value_string)
|
||||
if number then
|
||||
return number
|
||||
end
|
||||
|
||||
-- Handle booleans
|
||||
local lower = value_string:lower()
|
||||
if lower == "true" then
|
||||
return true
|
||||
elseif lower == "false" then
|
||||
return false
|
||||
end
|
||||
|
||||
-- Handle arrays (simple format)
|
||||
if value_string:match("^%[.+]$") then
|
||||
local array_content = value_string:sub(2, -2)
|
||||
local items = {}
|
||||
for item in array_content:gsub("%s", ""):gmatch("[^,]+") do
|
||||
table.insert(items, item:gsub("^['\"](.*)['\"]$", "%1"))
|
||||
end
|
||||
return items
|
||||
end
|
||||
|
||||
-- Default to string
|
||||
return value_string
|
||||
end
|
||||
|
||||
-- Detect property changes
|
||||
function M.detect_property_changes(original, edited)
|
||||
local changes = {}
|
||||
|
||||
-- Find modified and added properties
|
||||
for key, value in pairs(edited) do
|
||||
if not original[key] then
|
||||
table.insert(changes, {
|
||||
type = "added",
|
||||
key = key,
|
||||
new_value = value
|
||||
})
|
||||
elseif original[key] ~= value then
|
||||
table.insert(changes, {
|
||||
type = "modified",
|
||||
key = key,
|
||||
old_value = original[key],
|
||||
new_value = value
|
||||
})
|
||||
end
|
||||
end
|
||||
|
||||
-- Find deleted properties
|
||||
for key, value in pairs(original) do
|
||||
if not edited[key] then
|
||||
table.insert(changes, {
|
||||
type = "deleted",
|
||||
key = key,
|
||||
old_value = value
|
||||
})
|
||||
end
|
||||
end
|
||||
|
||||
return changes
|
||||
end
|
||||
|
||||
-- Update document properties in database
|
||||
function M.update_document_properties(document_id, changes)
|
||||
local updated_count = 0
|
||||
|
||||
for _, change in ipairs(changes) do
|
||||
if change.type == "deleted" then
|
||||
-- Delete property
|
||||
local ok, err = database.properties.delete_by_key(document_id, change.key)
|
||||
if not ok then
|
||||
return false, "Failed to delete property " .. change.key .. ": " .. err
|
||||
end
|
||||
else
|
||||
-- Add or update property
|
||||
local prop_data = {
|
||||
id = utils.generate_id(),
|
||||
document_id = document_id,
|
||||
key = change.key,
|
||||
value = tostring(change.new_value),
|
||||
value_type = type(change.new_value),
|
||||
created_at = os.time(),
|
||||
updated_at = os.time()
|
||||
}
|
||||
|
||||
-- Check if property already exists
|
||||
local existing_prop, err = database.properties.get_by_key(document_id, change.key)
|
||||
if err then
|
||||
return false, "Failed to check existing property: " .. err
|
||||
end
|
||||
|
||||
local ok
|
||||
if existing_prop then
|
||||
prop_data.id = existing_prop.id
|
||||
ok, err = database.properties.update(prop_data)
|
||||
else
|
||||
ok, err = database.properties.create(prop_data)
|
||||
end
|
||||
|
||||
if not ok then
|
||||
return false, "Failed to update property " .. change.key .. ": " .. err
|
||||
end
|
||||
end
|
||||
|
||||
updated_count = updated_count + 1
|
||||
end
|
||||
|
||||
return true, {
|
||||
updated_count = updated_count,
|
||||
refresh_query = true
|
||||
}
|
||||
end
|
||||
|
||||
-- Update YAML file
|
||||
function M.update_yaml_file(file_path, properties)
|
||||
-- Read current file
|
||||
local content, err = utils.read_file(file_path)
|
||||
if not content then
|
||||
return false, "Failed to read file: " .. err
|
||||
end
|
||||
|
||||
-- Parse YAML and content
|
||||
local yaml_content, yaml_err = parser.yaml_parser.extract_yaml_header(content)
|
||||
if not yaml_content then
|
||||
return false, "Failed to extract YAML: " .. yaml_err
|
||||
end
|
||||
|
||||
local body_content = parser.yaml_parser.remove_yaml_header(content)
|
||||
|
||||
-- Parse current YAML
|
||||
local current_yaml, parse_err = parser.yaml_parser.parse_yaml(yaml_content)
|
||||
if not current_yaml then
|
||||
return false, "Failed to parse YAML: " .. parse_err
|
||||
end
|
||||
|
||||
-- Update YAML data
|
||||
for key, value in pairs(properties) do
|
||||
current_yaml[key] = value
|
||||
end
|
||||
|
||||
-- Generate new YAML header
|
||||
local new_yaml_content = M.generate_yaml_content(current_yaml)
|
||||
|
||||
-- Combine with body content
|
||||
local new_content = new_yaml_content .. "\n---\n" .. body_content
|
||||
|
||||
-- Write file
|
||||
local write_ok, write_err = utils.write_file(file_path, new_content)
|
||||
if not write_ok then
|
||||
return false, "Failed to write file: " .. write_err
|
||||
end
|
||||
|
||||
return true, "File updated successfully"
|
||||
end
|
||||
|
||||
-- Generate YAML content from data
|
||||
function M.generate_yaml_content(data)
|
||||
local lines = {"---"}
|
||||
|
||||
-- Sort keys for consistent output
|
||||
local sorted_keys = {}
|
||||
for key, _ in pairs(data) do
|
||||
table.insert(sorted_keys, key)
|
||||
end
|
||||
table.sort(sorted_keys)
|
||||
|
||||
for _, key in ipairs(sorted_keys) do
|
||||
local value = data[key]
|
||||
local line = M.format_yaml_value(key, value)
|
||||
table.insert(lines, line)
|
||||
end
|
||||
|
||||
return table.concat(lines, "\n")
|
||||
end
|
||||
|
||||
-- Format YAML key-value pair
|
||||
function M.format_yaml_value(key, value)
|
||||
if type(value) == "string" then
|
||||
return string.format('%s: "%s"', key, value)
|
||||
elseif type(value) == "number" then
|
||||
return string.format("%s: %s", key, tostring(value))
|
||||
elseif type(value) == "boolean" then
|
||||
return string.format("%s: %s", key, tostring(value))
|
||||
elseif type(value) == "table" then
|
||||
return string.format("%s: %s", key, vim.json.encode(value))
|
||||
else
|
||||
return string.format('%s: "%s"', key, tostring(value))
|
||||
end
|
||||
end
|
||||
|
||||
-- Add new property
|
||||
function M.add_new_property(buffer_id, editor_id)
|
||||
vim.ui.input({ prompt = "Property name: " }, function(property_name)
|
||||
if property_name and property_name ~= "" then
|
||||
vim.ui.input({ prompt = "Property value: " }, function(property_value)
|
||||
if property_value and property_value ~= "" then
|
||||
local editor_session = active_editors[editor_id]
|
||||
if editor_session then
|
||||
editor_session.current_properties[property_name] = property_value
|
||||
editor_session.modified = true
|
||||
M.update_buffer_for_editing(buffer_id, editor_id)
|
||||
end
|
||||
end
|
||||
end)
|
||||
end
|
||||
end)
|
||||
end
|
||||
|
||||
-- Delete property at cursor
|
||||
function M.delete_property_at_cursor(buffer_id, editor_id)
|
||||
local cursor = vim.api.nvim_win_get_cursor(0)
|
||||
local line = vim.api.nvim_buf_get_lines(buffer_id, cursor[1] - 1, cursor[1], false)[1]
|
||||
|
||||
local key = line:match("^(%s*[%w_%-%.]+%s*):")
|
||||
if key then
|
||||
local clean_key = key:trim():gsub(":$", "")
|
||||
local editor_session = active_editors[editor_id]
|
||||
if editor_session and editor_session.current_properties[clean_key] then
|
||||
editor_session.current_properties[clean_key] = nil
|
||||
editor_session.modified = true
|
||||
M.update_buffer_for_editing(buffer_id, editor_id)
|
||||
vim.notify("Deleted property: " + clean_key, vim.log.levels.INFO)
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
-- Undo changes
|
||||
function M.undo_changes(buffer_id, editor_id)
|
||||
local editor_session = active_editors[editor_id]
|
||||
if editor_session then
|
||||
editor_session.current_properties = vim.deepcopy(editor_session.original_properties)
|
||||
editor_session.modified = false
|
||||
M.update_buffer_for_editing(buffer_id, editor_id)
|
||||
vim.notify("Changes undone", vim.log.levels.INFO)
|
||||
end
|
||||
end
|
||||
|
||||
-- Get active editors
|
||||
function M.get_active_editors()
|
||||
local active = {}
|
||||
for editor_id, session in pairs(active_editors) do
|
||||
if vim.api.nvim_buf_is_valid(session.buffer_id) then
|
||||
active[editor_id] = session
|
||||
else
|
||||
-- Clean up invalid sessions
|
||||
active_editors[editor_id] = nil
|
||||
end
|
||||
end
|
||||
return active
|
||||
end
|
||||
|
||||
return M
|
480
lua/notex/ui/init.lua
Normal file
480
lua/notex/ui/init.lua
Normal file
|
@ -0,0 +1,480 @@
|
|||
-- 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
|
525
lua/notex/ui/view.lua
Normal file
525
lua/notex/ui/view.lua
Normal file
|
@ -0,0 +1,525 @@
|
|||
-- Query result visualization module
|
||||
local M = {}
|
||||
|
||||
local buffer_manager = require('notex.ui.buffer')
|
||||
local utils = require('notex.utils')
|
||||
|
||||
-- View configurations
|
||||
local view_configs = {}
|
||||
|
||||
-- Create query view
|
||||
function M.create_query_view(query_results, options)
|
||||
options = options or {}
|
||||
local view_type = options.view_type or "table"
|
||||
|
||||
local view_config = {
|
||||
view_type = view_type,
|
||||
query_results = query_results,
|
||||
options = options,
|
||||
created_at = os.time()
|
||||
}
|
||||
|
||||
-- Create buffer based on view type
|
||||
if view_type == "table" then
|
||||
return M.create_table_view(query_results, options)
|
||||
elseif view_type == "cards" then
|
||||
return M.create_cards_view(query_results, options)
|
||||
elseif view_type == "list" then
|
||||
return M.create_list_view(query_results, options)
|
||||
elseif view_type == "tree" then
|
||||
return M.create_tree_view(query_results, options)
|
||||
else
|
||||
return M.create_table_view(query_results, options)
|
||||
end
|
||||
end
|
||||
|
||||
-- Create table view
|
||||
function M.create_table_view(query_results, options)
|
||||
local table_options = vim.tbl_deep_extend("force", options, {
|
||||
name = "notex://table-view",
|
||||
view_type = "table",
|
||||
include_properties = M.get_table_properties(query_results),
|
||||
max_width = 120,
|
||||
show_help = true
|
||||
})
|
||||
|
||||
return buffer_manager.create_query_buffer(query_results, table_options)
|
||||
end
|
||||
|
||||
-- Create cards view
|
||||
function M.create_cards_view(query_results, options)
|
||||
local cards_options = vim.tbl_deep_extend("force", options, {
|
||||
name = "notex://cards-view",
|
||||
view_type = "cards",
|
||||
show_help = true,
|
||||
wrap = true
|
||||
})
|
||||
|
||||
local buffer_id = vim.api.nvim_create_buf(false, true)
|
||||
buffer_manager.setup_buffer_options(buffer_id, cards_options)
|
||||
|
||||
local lines = M.generate_cards_content(query_results, cards_options)
|
||||
vim.api.nvim_buf_set_lines(buffer_id, 0, -1, false, lines)
|
||||
|
||||
local window_id = buffer_manager.create_query_window(buffer_id, cards_options)
|
||||
|
||||
local config = {
|
||||
buffer_id = buffer_id,
|
||||
window_id = window_id,
|
||||
query_results = query_results,
|
||||
options = cards_options,
|
||||
created_at = os.time(),
|
||||
mappings = buffer_manager.setup_buffer_mappings(buffer_id, cards_options)
|
||||
}
|
||||
|
||||
return config
|
||||
end
|
||||
|
||||
-- Create list view
|
||||
function M.create_list_view(query_results, options)
|
||||
local list_options = vim.tbl_deep_extend("force", options, {
|
||||
name = "notex://list-view",
|
||||
view_type = "list",
|
||||
show_help = true
|
||||
})
|
||||
|
||||
local buffer_id = vim.api.nvim_create_buf(false, true)
|
||||
buffer_manager.setup_buffer_options(buffer_id, list_options)
|
||||
|
||||
local lines = M.generate_list_content(query_results, list_options)
|
||||
vim.api.nvim_buf_set_lines(buffer_id, 0, -1, false, lines)
|
||||
|
||||
local window_id = buffer_manager.create_query_window(buffer_id, list_options)
|
||||
|
||||
local config = {
|
||||
buffer_id = buffer_id,
|
||||
window_id = window_id,
|
||||
query_results = query_results,
|
||||
options = list_options,
|
||||
created_at = os.time(),
|
||||
mappings = buffer_manager.setup_buffer_mappings(buffer_id, list_options)
|
||||
}
|
||||
|
||||
return config
|
||||
end
|
||||
|
||||
-- Create tree view
|
||||
function M.create_tree_view(query_results, options)
|
||||
local tree_options = vim.tbl_deep_extend("force", options, {
|
||||
name = "notex://tree-view",
|
||||
view_type = "tree",
|
||||
group_by = options.group_by or "status",
|
||||
show_help = true
|
||||
})
|
||||
|
||||
local buffer_id = vim.api.nvim_create_buf(false, true)
|
||||
buffer_manager.setup_buffer_options(buffer_id, tree_options)
|
||||
|
||||
local lines = M.generate_tree_content(query_results, tree_options)
|
||||
vim.api.nvim_buf_set_lines(buffer_id, 0, -1, false, lines)
|
||||
|
||||
local window_id = buffer_manager.create_query_window(buffer_id, tree_options)
|
||||
|
||||
local config = {
|
||||
buffer_id = buffer_id,
|
||||
window_id = window_id,
|
||||
query_results = query_results,
|
||||
options = tree_options,
|
||||
created_at = os.time(),
|
||||
mappings = buffer_manager.setup_buffer_mappings(buffer_id, tree_options)
|
||||
}
|
||||
|
||||
return config
|
||||
end
|
||||
|
||||
-- Get table properties
|
||||
function M.get_table_properties(query_results)
|
||||
if not query_results.documents or #query_results.documents == 0 then
|
||||
return {"title", "status", "priority"}
|
||||
end
|
||||
|
||||
-- Find most common properties
|
||||
local property_counts = {}
|
||||
|
||||
for _, doc in ipairs(query_results.documents) do
|
||||
if doc.properties then
|
||||
for prop, _ in pairs(doc.properties) do
|
||||
property_counts[prop] = (property_counts[prop] or 0) + 1
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
-- Sort by frequency
|
||||
local sorted_props = {}
|
||||
for prop, count in pairs(property_counts) do
|
||||
table.insert(sorted_props, {property = prop, count = count})
|
||||
end
|
||||
|
||||
table.sort(sorted_props, function(a, b) return a.count > b.count end)
|
||||
|
||||
-- Return top properties
|
||||
local result = {}
|
||||
for i, item in ipairs(sorted_props) do
|
||||
if i > 6 then break end -- Limit to 6 columns
|
||||
table.insert(result, item.property)
|
||||
end
|
||||
|
||||
return result
|
||||
end
|
||||
|
||||
-- Generate cards content
|
||||
function M.generate_cards_content(query_results, options)
|
||||
local lines = {}
|
||||
|
||||
-- Header
|
||||
table.insert(lines, "Query Results - Card View")
|
||||
table.insert(lines, string.rep("=", 50))
|
||||
table.insert(lines, "")
|
||||
|
||||
-- Query info
|
||||
if query_results.query_string then
|
||||
table.insert(lines, "Query: " .. query_results.query_string)
|
||||
table.insert(lines, "")
|
||||
end
|
||||
|
||||
table.insert(lines, string.format("Found %d documents (%.2fms)",
|
||||
query_results.total_count or 0,
|
||||
query_results.execution_time_ms or 0))
|
||||
table.insert(lines, "")
|
||||
|
||||
-- Document cards
|
||||
if query_results.documents and #query_results.documents > 0 then
|
||||
for i, doc in ipairs(query_results.documents) do
|
||||
lines = M.add_document_card(lines, doc, i, options)
|
||||
table.insert(lines, "")
|
||||
end
|
||||
else
|
||||
table.insert(lines, "No documents found.")
|
||||
table.insert(lines, "")
|
||||
end
|
||||
|
||||
-- Help
|
||||
lines = buffer_manager.add_help_section(lines, options)
|
||||
|
||||
return lines
|
||||
end
|
||||
|
||||
-- Add document card
|
||||
function M.add_document_card(lines, doc, index, options)
|
||||
local max_width = options.max_width or 80
|
||||
|
||||
table.insert(lines, string.format("Document %d: %s", index, doc.properties and doc.properties.title or "Untitled"))
|
||||
table.insert(lines, string.rep("-", max_width))
|
||||
|
||||
-- File path
|
||||
table.insert(lines, "Path: " .. doc.file_path)
|
||||
|
||||
-- Properties
|
||||
if doc.properties then
|
||||
local sorted_props = {}
|
||||
for key, value in pairs(doc.properties) do
|
||||
table.insert(sorted_props, {key = key, value = value})
|
||||
end
|
||||
|
||||
table.sort(sorted_props, function(a, b) return a.key < b.key end)
|
||||
|
||||
for _, prop in ipairs(sorted_props) do
|
||||
if prop.key ~= "title" then
|
||||
local formatted = string.format(" %s: %s", prop.key, tostring(prop.value))
|
||||
if #formatted > max_width - 2 then
|
||||
formatted = formatted:sub(1, max_width - 5) .. "..."
|
||||
end
|
||||
table.insert(lines, formatted)
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
-- Metadata
|
||||
table.insert(lines, string.format(" Modified: %s", os.date("%Y-%m-%d %H:%M", doc.updated_at)))
|
||||
|
||||
return lines
|
||||
end
|
||||
|
||||
-- Generate list content
|
||||
function M.generate_list_content(query_results, options)
|
||||
local lines = {}
|
||||
|
||||
-- Header
|
||||
table.insert(lines, "Query Results - List View")
|
||||
table.insert(lines, string.rep("=", 50))
|
||||
table.insert(lines, "")
|
||||
|
||||
-- Query info
|
||||
table.insert(lines, string.format("%d documents (%.2fms)",
|
||||
query_results.total_count or 0,
|
||||
query_results.execution_time_ms or 0))
|
||||
table.insert(lines, "")
|
||||
|
||||
-- Document list
|
||||
if query_results.documents and #query_results.documents > 0 then
|
||||
for i, doc in ipairs(query_results.documents) do
|
||||
local title = doc.properties and doc.properties.title or vim.fn.fnamemodify(doc.file_path, ":t")
|
||||
local status = doc.properties and doc.properties.status or ""
|
||||
local priority = doc.properties and doc.properties.priority or ""
|
||||
|
||||
local line = string.format("%3d. %-40s %-12s %-8s", i, M.truncate_string(title, 40), status, priority)
|
||||
table.insert(lines, line)
|
||||
end
|
||||
else
|
||||
table.insert(lines, "No documents found.")
|
||||
end
|
||||
|
||||
table.insert(lines, "")
|
||||
|
||||
-- Help
|
||||
lines = buffer_manager.add_help_section(lines, options)
|
||||
|
||||
return lines
|
||||
end
|
||||
|
||||
-- Generate tree content
|
||||
function M.generate_tree_content(query_results, options)
|
||||
local lines = {}
|
||||
local group_by = options.group_by or "status"
|
||||
|
||||
-- Header
|
||||
table.insert(lines, string.format("Query Results - Tree View (Grouped by %s)", group_by))
|
||||
table.insert(lines, string.rep("=", 50))
|
||||
table.insert(lines, "")
|
||||
|
||||
-- Group documents
|
||||
local groups = M.group_documents(query_results.documents, group_by)
|
||||
|
||||
-- Create tree structure
|
||||
for group_name, group_docs in pairs(groups) do
|
||||
table.insert(lines, string.format("▼ %s (%d)", group_name, #group_docs))
|
||||
|
||||
for i, doc in ipairs(group_docs) do
|
||||
local title = doc.properties and doc.properties.title or vim.fn.fnamemodify(doc.file_path, ":t")
|
||||
local line = string.format(" ├─ %s", title)
|
||||
|
||||
if i == #group_docs then
|
||||
line = string.format(" └─ %s", title)
|
||||
end
|
||||
|
||||
table.insert(lines, line)
|
||||
end
|
||||
|
||||
table.insert(lines, "")
|
||||
end
|
||||
|
||||
-- Help
|
||||
lines = buffer_manager.add_help_section(lines, options)
|
||||
|
||||
return lines
|
||||
end
|
||||
|
||||
-- Group documents by property
|
||||
function M.group_documents(documents, group_by)
|
||||
local groups = {}
|
||||
|
||||
for _, doc in ipairs(documents) do
|
||||
local group_value = "Unknown"
|
||||
|
||||
if doc.properties and doc.properties[group_by] then
|
||||
group_value = tostring(doc.properties[group_by])
|
||||
end
|
||||
|
||||
if not groups[group_value] then
|
||||
groups[group_value] = {}
|
||||
end
|
||||
|
||||
table.insert(groups[group_value], doc)
|
||||
end
|
||||
|
||||
-- Sort groups
|
||||
local sorted_groups = {}
|
||||
for group_name, group_docs in pairs(groups) do
|
||||
table.insert(sorted_groups, {name = group_name, docs = group_docs})
|
||||
end
|
||||
|
||||
table.sort(sorted_groups, function(a, b) return a.name < b.name end)
|
||||
|
||||
local result = {}
|
||||
for _, group in ipairs(sorted_groups) do
|
||||
result[group.name] = group.docs
|
||||
end
|
||||
|
||||
return result
|
||||
end
|
||||
|
||||
-- Truncate string
|
||||
function M.truncate_string(str, max_length)
|
||||
if #str <= max_length then
|
||||
return str
|
||||
end
|
||||
|
||||
return str:sub(1, max_length - 3) .. "..."
|
||||
end
|
||||
|
||||
-- Switch view type
|
||||
function M.switch_view_type(buffer_id, new_view_type)
|
||||
local config = require('notex.ui.buffer').get_active_buffers()[buffer_id]
|
||||
if not config then
|
||||
return false, "Buffer not found"
|
||||
end
|
||||
|
||||
-- Close current view
|
||||
if config.window_id then
|
||||
vim.api.nvim_win_close(config.window_id, true)
|
||||
end
|
||||
|
||||
-- Create new view
|
||||
local new_options = vim.tbl_deep_extend("force", config.options, {
|
||||
view_type = new_view_type
|
||||
})
|
||||
|
||||
local new_config = M.create_query_view(config.query_results, new_options)
|
||||
|
||||
if new_config then
|
||||
return true, "View switched to " .. new_view_type
|
||||
else
|
||||
return false, "Failed to create new view"
|
||||
end
|
||||
end
|
||||
|
||||
-- Get available view types
|
||||
function M.get_available_view_types()
|
||||
return {
|
||||
{
|
||||
name = "table",
|
||||
description = "Tabular view with sortable columns",
|
||||
icon = "⊞"
|
||||
},
|
||||
{
|
||||
name = "cards",
|
||||
description = "Card-based view with detailed information",
|
||||
icon = "📄"
|
||||
},
|
||||
{
|
||||
name = "list",
|
||||
description = "Compact list view",
|
||||
icon = "📋"
|
||||
},
|
||||
{
|
||||
name = "tree",
|
||||
description = "Hierarchical view grouped by properties",
|
||||
icon = "🌳"
|
||||
}
|
||||
}
|
||||
end
|
||||
|
||||
-- Export view to different formats
|
||||
function M.export_view(buffer_id, format)
|
||||
local config = require('notex.ui.buffer').get_active_buffers()[buffer_id]
|
||||
if not config then
|
||||
return false, "Buffer not found"
|
||||
end
|
||||
|
||||
format = format or "markdown"
|
||||
|
||||
if format == "markdown" then
|
||||
return M.export_to_markdown(config.query_results, config.options)
|
||||
elseif format == "csv" then
|
||||
return M.export_to_csv(config.query_results, config.options)
|
||||
elseif format == "json" then
|
||||
return M.export_to_json(config.query_results, config.options)
|
||||
else
|
||||
return false, "Unsupported export format: " .. format
|
||||
end
|
||||
end
|
||||
|
||||
-- Export to markdown
|
||||
function M.export_to_markdown(query_results, options)
|
||||
local lines = {}
|
||||
|
||||
table.insert(lines, "# Query Results")
|
||||
table.insert(lines, "")
|
||||
|
||||
if query_results.query_string then
|
||||
table.insert(lines, "## Query")
|
||||
table.insert(lines, "```")
|
||||
table.insert(lines, query_results.query_string)
|
||||
table.insert(lines, "```")
|
||||
table.insert(lines, "")
|
||||
end
|
||||
|
||||
table.insert(lines, string.format("**Found %d documents** (%.2fms)",
|
||||
query_results.total_count or 0,
|
||||
query_results.execution_time_ms or 0))
|
||||
table.insert(lines, "")
|
||||
|
||||
if query_results.documents and #query_results.documents > 0 then
|
||||
table.insert(lines, "## Documents")
|
||||
table.insert(lines, "")
|
||||
|
||||
for i, doc in ipairs(query_results.documents) do
|
||||
table.insert(lines, string.format("### %d. %s", i, doc.properties and doc.properties.title or "Untitled"))
|
||||
table.insert(lines, "")
|
||||
|
||||
table.insert(lines, "**File:** `" .. doc.file_path .. "`")
|
||||
table.insert(lines, "")
|
||||
|
||||
if doc.properties then
|
||||
table.insert(lines, "**Properties:**")
|
||||
for key, value in pairs(doc.properties) do
|
||||
table.insert(lines, string.format("- **%s:** %s", key, tostring(value)))
|
||||
end
|
||||
table.insert(lines, "")
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
return true, table.concat(lines, "\n")
|
||||
end
|
||||
|
||||
-- Export to CSV
|
||||
function M.export_to_csv(query_results, options)
|
||||
local lines = {}
|
||||
|
||||
-- Header
|
||||
local headers = {"#", "File", "Title", "Status", "Priority", "Created", "Modified"}
|
||||
table.insert(lines, table.concat(headers, ","))
|
||||
|
||||
-- Data rows
|
||||
if query_results.documents then
|
||||
for i, doc in ipairs(query_results.documents) do
|
||||
local row = {
|
||||
i,
|
||||
doc.file_path,
|
||||
doc.properties and doc.properties.title or "",
|
||||
doc.properties and doc.properties.status or "",
|
||||
doc.properties and doc.properties.priority or "",
|
||||
doc.created_at and os.date("%Y-%m-%d", doc.created_at) or "",
|
||||
doc.updated_at and os.date("%Y-%m-%d", doc.updated_at) or ""
|
||||
}
|
||||
|
||||
-- Escape CSV values
|
||||
for j, value in ipairs(row) do
|
||||
if value:find("[,\"]") then
|
||||
row[j] = '"' .. value:gsub('"', '""') .. '"'
|
||||
end
|
||||
end
|
||||
|
||||
table.insert(lines, table.concat(row, ","))
|
||||
end
|
||||
end
|
||||
|
||||
return true, table.concat(lines, "\n")
|
||||
end
|
||||
|
||||
-- Export to JSON
|
||||
function M.export_to_json(query_results, options)
|
||||
local export_data = {
|
||||
query = query_results.query_string,
|
||||
total_count = query_results.total_count,
|
||||
execution_time_ms = query_results.execution_time_ms,
|
||||
documents = query_results.documents,
|
||||
exported_at = os.time(),
|
||||
exported_by = "notex.nvim"
|
||||
}
|
||||
|
||||
return true, vim.json.encode(export_data)
|
||||
end
|
||||
|
||||
return M
|
Loading…
Add table
Add a link
Reference in a new issue