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