572 lines
No EOL
16 KiB
Lua
572 lines
No EOL
16 KiB
Lua
-- 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 |