add auto_save and fix bugs

This commit is contained in:
Elflare
2025-07-06 14:58:30 +08:00
parent 71f34789eb
commit 2f7271120e
3 changed files with 329 additions and 249 deletions

View File

@@ -73,7 +73,7 @@ require("memos").setup({
-- Number of memos to fetch per page
pageSize = 50,
auto_save = false,
-- Set to false or nil to disable a keymap
keymaps = {
-- Keymap to open the memos list. Default: <leader>mm
@@ -176,7 +176,7 @@ require("memos").setup({
-- 每页获取的 memo 数量
pageSize = 50,
auto_save = false,
-- 设置为 false 或 nil 可以禁用某个快捷键
keymaps = {
-- 用于打开 Memos 列表的快捷键。默认值: <leader>mm

View File

@@ -3,10 +3,11 @@ local M = {}
M.config = {
host = nil,
token = nil,
auto_save = false,
pageSize = 50,
keymaps = {
start_memos = "<leader>mm",
-- 在列表窗口中的快捷键
-- 在列表窗口中的快捷键
list = {
add_memo = 'a',
delete_memo = 'd',
@@ -18,7 +19,7 @@ M.config = {
next_page = '.',
quit = 'q'
},
-- 在编辑和创建窗口中的快捷键
-- 在编辑和创建窗口中的快捷键
buffer = {
save = '<leader>ms'
}
@@ -30,89 +31,96 @@ local config_file_path = config_dir .. "/memos_config.json"
-- 【修改】全新的、能处理 host 和 token 的交互式配置函数
local function prompt_for_config()
local function prompt_for_token()
vim.ui.input({ prompt = "Memos Access Token:", hide = true }, function(token)
if token and token ~= "" then
M.config.token = token
local choice = vim.fn.confirm("Save host and token for future sessions?", "&Yes\n&No", 2)
if choice == 1 then
vim.fn.mkdir(config_dir, "p")
-- 将 host token 一起存入 JSON 文件
local config_to_save = { host = M.config.host, token = M.config.token }
vim.fn.writefile({ vim.json.encode(config_to_save) }, config_file_path)
vim.notify("Host and token saved permanently.", vim.log.levels.INFO)
end
else
vim.notify("No token entered. Memos plugin will not work.", vim.log.levels.ERROR)
end
end)
end
local function prompt_for_token()
vim.ui.input({
prompt = "Memos Access Token:",
hide = true
}, function(token)
if token and token ~= "" then
M.config.token = token
local choice = vim.fn.confirm("Save host and token for future sessions?", "&Yes\n&No", 2)
if choice == 1 then
vim.fn.mkdir(config_dir, "p")
-- 将 host token 一起存入 JSON 文件
local config_to_save = {
host = M.config.host,
token = M.config.token
}
vim.fn.writefile({vim.json.encode(config_to_save)}, config_file_path)
vim.notify("Host and token saved permanently.", vim.log.levels.INFO)
end
else
vim.notify("No token entered. Memos plugin will not work.", vim.log.levels.ERROR)
end
end)
end
if not M.config.host then
vim.ui.input({ prompt = "Memos Host URL (e.g., http://127.0.0.1:5230):" }, function(host)
if host and host ~= "" then
M.config.host = host
-- 获取到 host 后,接着获取 token
if not M.config.host then
vim.ui.input({
prompt = "Memos Host URL (e.g., http://127.0.0.1:5230):"
}, function(host)
if host and host ~= "" then
M.config.host = host
-- 获取到 host 后,接着获取 token
prompt_for_token()
else
vim.notify("No host entered. Memos plugin will not work.", vim.log.levels.ERROR)
end
end)
else
-- 如果 host 已存在,只获取 token
prompt_for_token()
else
vim.notify("No host entered. Memos plugin will not work.", vim.log.levels.ERROR)
end
end)
else
-- 如果 host 已存在,只获取 token
prompt_for_token()
end
end
end
function M.setup(opts)
-- 1. 先加载默认配置
local final_config = vim.deepcopy(M.config)
-- 1. 先加载默认配置
local final_config = vim.deepcopy(M.config)
-- 2. 加载文件中的配置
if vim.fn.filereadable(config_file_path) == 1 then
local file_content = vim.fn.readfile(config_file_path)
if file_content and #file_content > 0 and file_content[1] ~= "" then
local saved_config = vim.json.decode(file_content[1])
final_config = vim.tbl_deep_extend("force", final_config, saved_config)
-- 2. 加载文件中的配置
if vim.fn.filereadable(config_file_path) == 1 then
local file_content = vim.fn.readfile(config_file_path)
if file_content and #file_content > 0 and file_content[1] ~= "" then
local saved_config = vim.json.decode(file_content[1])
final_config = vim.tbl_deep_extend("force", final_config, saved_config)
end
end
end
-- 3. 加载环境变量 (优先级高于文件)
local token_from_env = os.getenv('MEMOS_TOKEN')
if token_from_env and token_from_env ~= "" then
final_config.token = token_from_env
end
local host_from_env = os.getenv('MEMOS_HOST')
if host_from_env and host_from_env ~= "" then
final_config.host = host_from_env
end
-- 3. 加载环境变量 (优先级高于文件)
local token_from_env = os.getenv('MEMOS_TOKEN')
if token_from_env and token_from_env ~= "" then
final_config.token = token_from_env
end
local host_from_env = os.getenv('MEMOS_HOST')
if host_from_env and host_from_env ~= "" then
final_config.host = host_from_env
end
-- 4. 加载用户在 setup() 中直接提供的配置 (优先级最高)
final_config = vim.tbl_deep_extend("force", final_config, opts or {})
-- 4. 加载用户在 setup() 中直接提供的配置 (优先级最高)
final_config = vim.tbl_deep_extend("force", final_config, opts or {})
M.config = final_config
M.config = final_config
end
-- 【修改】确保在执行任何操作前,配置是完整的
local function ensure_config(callback)
if M.config.host and M.config.token then
callback()
else
prompt_for_config()
end
if M.config.host and M.config.token then
callback()
else
prompt_for_config()
end
end
function M.create_memo()
ensure_config(function()
require('memos.ui').create_memo_in_buffer()
end)
ensure_config(function()
require('memos.ui').create_memo_in_buffer()
end)
end
function M.show_list()
ensure_config(function()
require('memos.ui').show_memos_list()
end)
ensure_config(function()
require('memos.ui').show_memos_list()
end)
end
return M
return M

View File

@@ -16,74 +16,123 @@ local current_filter = nil
-- 渲染 Memos 列表到 buffer
local function render_memos(data, append)
vim.schedule(function()
if not buf_id or not vim.api.nvim_buf_is_valid(buf_id) then return end
local new_memos = data.memos or {}
current_page_token = data.nextPageToken or ""
if append then
memos_cache = vim.list_extend(memos_cache, new_memos)
else
memos_cache = new_memos
end
local lines = {}
local k = config.keymaps.list
if #memos_cache == 0 then
local help = string.format("No memos found. Press '%s' to refresh, '%s' to add, or '%s' to quit.",
k.refresh_list, k.add_memo, k.quit)
table.insert(lines, help)
else
for i, memo in ipairs(memos_cache) do
local first_line = memo.content:match("^[^\n]*")
local display_time = memo.displayTime:sub(1, 10)
table.insert(lines, string.format("%d. [%s] %s", i, display_time, first_line))
end
end
if current_page_token ~= "" then
table.insert(lines, "...")
table.insert(lines, string.format("(Press '%s' to load more)", k.next_page))
end
vim.api.nvim_buf_set_option(buf_id, 'modifiable', true)
vim.api.nvim_buf_set_lines(buf_id, 0, -1, false, lines)
vim.api.nvim_buf_set_option(buf_id, 'modifiable', false)
end)
vim.schedule(function()
if not buf_id or not vim.api.nvim_buf_is_valid(buf_id) then
return
end
local new_memos = data.memos or {}
current_page_token = data.nextPageToken or ""
if append then
memos_cache = vim.list_extend(memos_cache, new_memos)
else
memos_cache = new_memos
end
local lines = {}
local k = config.keymaps.list
if #memos_cache == 0 then
local help = string.format("No memos found. Press '%s' to refresh, '%s' to add, or '%s' to quit.",
k.refresh_list, k.add_memo, k.quit)
table.insert(lines, help)
else
for i, memo in ipairs(memos_cache) do
local first_line = memo.content:match("^[^\n]*")
local display_time = memo.displayTime:sub(1, 10)
table.insert(lines, string.format("%d. [%s] %s", i, display_time, first_line))
end
end
if current_page_token ~= "" then
table.insert(lines, "...")
table.insert(lines, string.format("(Press '%s' to load more)", k.next_page))
end
vim.api.nvim_buf_set_option(buf_id, 'modifiable', true)
vim.api.nvim_buf_set_lines(buf_id, 0, -1, false, lines)
vim.api.nvim_buf_set_option(buf_id, 'modifiable', false)
end)
end
-- 设置编辑 buffer 的通用函数
local function setup_buffer_for_editing()
vim.bo.buftype = 'nofile'
vim.bo.bufhidden = 'wipe'
vim.bo.swapfile = false
vim.bo.buflisted = true
vim.bo.buftype = 'nofile'
vim.bo.bufhidden = 'wipe'
vim.bo.swapfile = false
vim.bo.buflisted = true
vim.bo.filetype = 'markdown'
vim.bo.filetype = 'markdown'
local save_key_string = ""
if config.keymaps.buffer.save and config.keymaps.buffer.save ~= "" then
save_key_string = string.format(" or %s", config.keymaps.buffer.save)
end
local save_key_string = ""
if config.keymaps.buffer.save and config.keymaps.buffer.save ~= "" then
save_key_string = string.format(" or %s", config.keymaps.buffer.save)
end
if vim.b.memos_memo_name then
vim.notify("Editing memo. Use :MemosSave" .. save_key_string .. " to save.")
else
vim.notify("📝 New memo. Use :MemosSave" .. save_key_string .. " to create.")
end
vim.api.nvim_buf_create_user_command(0, 'MemosSave', 'lua require("memos.ui").save_or_create_dispatcher()', {})
if config.keymaps.buffer.save and config.keymaps.buffer.save ~= "" then
vim.api.nvim_buf_set_keymap(0, 'n', config.keymaps.buffer.save, '<Cmd>MemosSave<CR>', { noremap = true, silent = true })
end
if vim.b.memos_memo_name then
vim.notify("Editing memo. Use :MemosSave" .. save_key_string .. " to save.")
else
vim.notify("📝 New memo. Use :MemosSave" .. save_key_string .. " to create.")
end
vim.api.nvim_buf_create_user_command(0, 'MemosSave', 'lua require("memos.ui").save_or_create_dispatcher()', {})
if config.keymaps.buffer.save and config.keymaps.buffer.save ~= "" then
vim.api.nvim_buf_set_keymap(0, 'n', config.keymaps.buffer.save, '<Cmd>MemosSave<CR>', {
noremap = true,
silent = true
})
end
-- 【修改】全新的、更可靠的自动保存逻辑
if config.auto_save then
local group = vim.api.nvim_create_augroup('MemosAutoSave', {
clear = true
})
-- 1. 进入插入模式时,保存当前内容的快照
vim.api.nvim_create_autocmd('InsertEnter', {
group = group,
buffer = 0,
callback = function()
vim.b.memos_original_content = table.concat(vim.api.nvim_buf_get_lines(0, 0, -1, false), '\n')
end
})
-- 2. 离开插入模式时,比较内容是否发生变化
vim.api.nvim_create_autocmd('InsertLeave', {
group = group,
buffer = 0,
callback = function()
local current_content = table.concat(vim.api.nvim_buf_get_lines(0, 0, -1, false), '\n')
if vim.b.memos_original_content ~= current_content then
require('memos.ui').save_or_create_dispatcher()
end
end
})
end
end
-- 打开一个已存在的 Memo
local function open_memo_for_edit(memo, open_cmd)
vim.cmd(open_cmd)
local first_line = memo.content:match("^[^\n]*")
local buffer_name = "memos/" .. memo.name:gsub("memos/", "") .. "/" .. first_line:gsub("[/\\]", "_"):sub(1, 50) .. ".md"
vim.api.nvim_buf_set_name(0, buffer_name)
vim.api.nvim_buf_set_lines(0, 0, -1, false, vim.split(memo.content, '\n'))
vim.b.memos_memo_name = memo.name
setup_buffer_for_editing()
-- 1. 根据 memo 信息,构造一个唯一的 buffer 名称
local first_line = memo.content:match("^[^\n]*")
local buffer_name = "memos/" .. memo.name:gsub("memos/", "") .. "/" .. first_line:gsub("[/\\]", "_"):sub(1, 50) ..
".md"
-- 2. 检查这个名字的 buffer 是否已经存在
local existing_bufnr = vim.fn.bufnr(buffer_name)
if existing_bufnr ~= -1 and vim.api.nvim_buf_is_loaded(existing_bufnr) then
-- 3. 如果已存在,则找到它所在的窗口并跳转过去
local win_id = vim.fn.bufwinid(existing_bufnr)
if win_id ~= -1 then
vim.api.nvim_set_current_win(win_id)
else
-- 如果窗口已关闭但 buffer 仍在,则在当前窗口打开它
vim.api.nvim_set_current_buf(existing_bufnr)
end
else
-- 4. 如果不存在,才执行我们之前的创建新窗口的逻辑
vim.cmd(open_cmd)
vim.api.nvim_buf_set_name(0, buffer_name)
vim.api.nvim_buf_set_lines(0, 0, -1, false, vim.split(memo.content, '\n'))
vim.b.memos_memo_name = memo.name
setup_buffer_for_editing()
end
end
-- ===================================================================
@@ -92,160 +141,183 @@ end
-- 【修正】将此函数移到 render_memos 定义之后,解决报错
function M.refresh_list_silently()
if not current_user or not current_user.name then return end
api.list_memos(current_user.name, current_filter, config.pageSize, nil, function(data)
render_memos(data, false)
end)
if not current_user or not current_user.name then
return
end
api.list_memos(current_user.name, current_filter, config.pageSize, nil, function(data)
render_memos(data, false)
end)
end
function M.save_or_create_dispatcher()
local memo_name = vim.b.memos_memo_name
local content = table.concat(vim.api.nvim_buf_get_lines(0, 0, -1, false), '\n')
local memo_name = vim.b.memos_memo_name
local content = table.concat(vim.api.nvim_buf_get_lines(0, 0, -1, false), '\n')
if content == '' then
vim.notify("Memo is empty, not sending.", vim.log.levels.WARN)
return
end
if memo_name then
api.update_memo(memo_name, content, function(success)
if success then
vim.schedule(function() vim.notify("✅ Memo updated successfully!") end)
M.refresh_list_silently()
end
end)
else
api.create_memo(content, function(new_memo)
if new_memo and new_memo.name then
vim.schedule(function()
vim.notify("✅ Memo created successfully!")
vim.cmd('bdelete!') -- 强制关闭临时的 new_memo buffer
open_memo_for_edit(new_memo, 'enew') -- 用新 memo 的信息打开一个标准的编辑窗口
M.refresh_list_silently()
if content == '' then
vim.notify("Memo is empty, not sending.", vim.log.levels.WARN)
return
end
if memo_name then
api.update_memo(memo_name, content, function(success)
if success then
vim.schedule(function()
vim.notify("✅ Memo updated successfully!")
end)
M.refresh_list_silently()
end
end)
else
vim.schedule(function() vim.notify("❌ Failed to create memo.", vim.log.levels.ERROR) end)
end
end)
end
else
api.create_memo(content, function(new_memo)
if new_memo and new_memo.name then
vim.schedule(function()
vim.notify("✅ Memo created successfully!")
vim.cmd('bdelete!') -- 强制关闭临时的 new_memo buffer
open_memo_for_edit(new_memo, 'enew') -- 用新 memo 的信息打开一个标准的编辑窗口
M.refresh_list_silently()
end)
else
vim.schedule(function()
vim.notify("❌ Failed to create memo.", vim.log.levels.ERROR)
end)
end
end)
end
end
function M.create_memo_in_buffer()
vim.cmd('enew')
vim.b.memos_memo_name = nil
vim.api.nvim_buf_set_name(0, "memos/new_memo.md")
setup_buffer_for_editing()
vim.cmd('enew')
vim.b.memos_memo_name = nil
vim.api.nvim_buf_set_name(0, "memos/new_memo.md")
setup_buffer_for_editing()
end
function M.show_memos_list(filter)
current_filter = filter
local should_create_buf = true
if buf_id and vim.api.nvim_buf_is_valid(buf_id) then
should_create_buf = false
else
buf_id = vim.api.nvim_create_buf(true, true) -- listed = true, scratch = true
vim.api.nvim_buf_set_name(buf_id, " Memos")
vim.bo[buf_id].buftype = 'nofile'
vim.bo[buf_id].swapfile = false
vim.bo[buf_id].filetype = 'memos_list'
vim.bo[buf_id].modifiable = false
end
-- 【修正】确保跳转到列表窗口,并且是在当前窗口打开
local win_id = vim.fn.bufwinid(buf_id)
if win_id ~= -1 then
vim.api.nvim_set_current_win(win_id)
else
vim.api.nvim_set_current_buf(buf_id)
end
vim.schedule(function() vim.notify("Getting user info...") end)
api.get_current_user(function(user)
if user and user.name then
current_user = user
vim.schedule(function() vim.notify("Fetching memos for " .. user.name .. "...") end)
api.list_memos(user.name, current_filter, config.pageSize, nil, function(data)
render_memos(data, false)
end)
current_filter = filter
local should_create_buf = true
if buf_id and vim.api.nvim_buf_is_valid(buf_id) then
should_create_buf = false
else
vim.schedule(function() vim.notify("Could not get user, aborting fetch.", vim.log.levels.ERROR) end)
buf_id = vim.api.nvim_create_buf(true, true) -- listed = true, scratch = true
vim.api.nvim_buf_set_name(buf_id, " Memos")
vim.bo[buf_id].buftype = 'nofile'
vim.bo[buf_id].swapfile = false
vim.bo[buf_id].filetype = 'memos_list'
vim.bo[buf_id].modifiable = false
end
end)
local function set_keymap(key, command)
if key and key ~= "" then
vim.api.nvim_buf_set_keymap(buf_id, 'n', key, command, { noremap = true, silent = true })
-- 【修正】确保跳转到列表窗口,并且是在当前窗口打开
local win_id = vim.fn.bufwinid(buf_id)
if win_id ~= -1 then
vim.api.nvim_set_current_win(win_id)
else
vim.api.nvim_set_current_buf(buf_id)
end
vim.schedule(function()
vim.notify("Getting user info...")
end)
api.get_current_user(function(user)
if user and user.name then
current_user = user
vim.schedule(function()
vim.notify("Fetching memos for " .. user.name .. "...")
end)
api.list_memos(user.name, current_filter, config.pageSize, nil, function(data)
render_memos(data, false)
end)
else
vim.schedule(function()
vim.notify("Could not get user, aborting fetch.", vim.log.levels.ERROR)
end)
end
end)
local function set_keymap(key, command)
if key and key ~= "" then
vim.api.nvim_buf_set_keymap(buf_id, 'n', key, command, {
noremap = true,
silent = true
})
end
end
if config.keymaps and config.keymaps.list then
local list_keymaps = config.keymaps.list
set_keymap(list_keymaps.edit_memo, '<Cmd>lua require("memos.ui").edit_selected_memo()<CR>')
set_keymap(list_keymaps.vsplit_edit_memo, '<Cmd>lua require("memos.ui").edit_selected_memo_in_vsplit()<CR>')
set_keymap(list_keymaps.quit, '<Cmd>bdelete!<CR>')
set_keymap(list_keymaps.search_memos, '<Cmd>lua require("memos.ui").search_memos()<CR>')
set_keymap(list_keymaps.refresh_list, '<Cmd>lua require("memos.ui").show_memos_list()<CR>')
set_keymap(list_keymaps.next_page, '<Cmd>lua require("memos.ui").load_next_page()<CR>')
set_keymap(list_keymaps.add_memo, '<Cmd>lua require("memos.ui").create_memo_in_buffer()<CR>')
set_keymap(list_keymaps.delete_memo, '<Cmd>lua require("memos.ui").confirm_delete_memo()<CR>')
set_keymap(list_keymaps.delete_memo_visual, '<Cmd>lua require("memos.ui").confirm_delete_memo()<CR>')
end
end
if config.keymaps and config.keymaps.list then
local list_keymaps = config.keymaps.list
set_keymap(list_keymaps.edit_memo, '<Cmd>lua require("memos.ui").edit_selected_memo()<CR>')
set_keymap(list_keymaps.vsplit_edit_memo, '<Cmd>lua require("memos.ui").edit_selected_memo_in_vsplit()<CR>')
set_keymap(list_keymaps.quit, '<Cmd>bdelete!<CR>')
set_keymap(list_keymaps.search_memos, '<Cmd>lua require("memos.ui").search_memos()<CR>')
set_keymap(list_keymaps.refresh_list, '<Cmd>lua require("memos.ui").show_memos_list()<CR>')
set_keymap(list_keymaps.next_page, '<Cmd>lua require("memos.ui").load_next_page()<CR>')
set_keymap(list_keymaps.add_memo, '<Cmd>lua require("memos.ui").create_memo_in_buffer()<CR>')
set_keymap(list_keymaps.delete_memo, '<Cmd>lua require("memos.ui").confirm_delete_memo()<CR>')
set_keymap(list_keymaps.delete_memo_visual, '<Cmd>lua require("memos.ui").confirm_delete_memo()<CR>')
end
end
function M.load_next_page()
if not current_page_token or current_page_token == "" then
vim.notify("No more pages to load.", vim.log.levels.INFO)
return
end
if not current_user or not current_user.name then
vim.notify("User info not available.", vim.log.levels.WARN)
return
end
vim.schedule(function() vim.notify("Loading next page...") end)
api.list_memos(current_user.name, current_filter, config.pageSize, current_page_token, function(data)
render_memos(data, true)
end)
if not current_page_token or current_page_token == "" then
vim.notify("No more pages to load.", vim.log.levels.INFO)
return
end
if not current_user or not current_user.name then
vim.notify("User info not available.", vim.log.levels.WARN)
return
end
vim.schedule(function()
vim.notify("Loading next page...")
end)
api.list_memos(current_user.name, current_filter, config.pageSize, current_page_token, function(data)
render_memos(data, true)
end)
end
function M.edit_selected_memo()
local line_num = vim.api.nvim_win_get_cursor(0)[1]
local selected_memo = memos_cache[line_num]
if selected_memo then
open_memo_for_edit(selected_memo, 'enew')
end
local line_num = vim.api.nvim_win_get_cursor(0)[1]
local selected_memo = memos_cache[line_num]
if selected_memo then
open_memo_for_edit(selected_memo, 'enew')
end
end
function M.edit_selected_memo_in_vsplit()
local line_num = vim.api.nvim_win_get_cursor(0)[1]
local selected_memo = memos_cache[line_num]
if selected_memo then
open_memo_for_edit(selected_memo, 'vsplit | enew')
end
local line_num = vim.api.nvim_win_get_cursor(0)[1]
local selected_memo = memos_cache[line_num]
if selected_memo then
open_memo_for_edit(selected_memo, 'vsplit | enew')
end
end
function M.confirm_delete_memo()
local line_num = vim.api.nvim_win_get_cursor(0)[1]
local selected_memo = memos_cache[line_num]
if not selected_memo then return end
local choice = vim.fn.confirm("Delete this memo?\n[" .. selected_memo.content:sub(1, 50) .. "...]", "&Yes\n&No", 2)
if choice == 1 then
api.delete_memo(selected_memo.name, function(success)
if success then
vim.schedule(function()
vim.notify("✅ Memo deleted.")
M.show_memos_list(current_filter)
local line_num = vim.api.nvim_win_get_cursor(0)[1]
local selected_memo = memos_cache[line_num]
if not selected_memo then
return
end
local choice = vim.fn.confirm("Delete this memo?\n[" .. selected_memo.content:sub(1, 50) .. "...]", "&Yes\n&No", 2)
if choice == 1 then
api.delete_memo(selected_memo.name, function(success)
if success then
vim.schedule(function()
vim.notify("✅ Memo deleted.")
M.show_memos_list(current_filter)
end)
else
vim.schedule(function()
vim.notify("❌ Failed to delete memo.", vim.log.levels.ERROR)
end)
end
end)
else
vim.schedule(function() vim.notify("❌ Failed to delete memo.", vim.log.levels.ERROR) end)
end
end)
end
end
end
function M.search_memos()
vim.ui.input({ prompt = "Search Memos: " }, function(input)
M.show_memos_list(input or "")
end)
vim.ui.input({
prompt = "Search Memos: "
}, function(input)
M.show_memos_list(input or "")
end)
end
return M