Skip to content
Closed

ignore #1764

Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 0 additions & 1 deletion .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -4,4 +4,3 @@ test.sh
nvim

spell/
lazy-lock.json
361 changes: 359 additions & 2 deletions init.lua
Original file line number Diff line number Diff line change
Expand Up @@ -189,6 +189,355 @@ vim.keymap.set('n', '<C-l>', '<C-w><C-l>', { desc = 'Move focus to the right win
vim.keymap.set('n', '<C-j>', '<C-w><C-j>', { desc = 'Move focus to the lower window' })
vim.keymap.set('n', '<C-k>', '<C-w><C-k>', { desc = 'Move focus to the upper window' })

vim.api.nvim_set_keymap('c', '%%', "<C-R>=expand('%:h').'/'<CR>", { noremap = true, silent = true })

-- Obsidian Integration (Optimized)
local M = {}

--[[ Module Design:
1. Encapsulated configuration
2. Separated concerns with dedicated modules
3. Strict local scoping
4. Error handling consistency
5. Documentation-ready structure
6. Reduced code duplication
]]

-- Configuration Module --
local Config = {
path = vim.fn.expand '~/.config/nvim/obsidian_vaults.json',
vaults = {},
default_vault_name = 'default',
temp_dir_suffix = '_temp_preview',
}

-- Utility Module --
local Utils = {}

function Utils.is_executable(cmd)
return vim.fn.executable(cmd) == 1
end

function Utils.ensure_dir(path)
return vim.fn.mkdir(path, 'p') == 1
end

function Utils.safe_path(path)
return (path:gsub([[\]], [[/]]):gsub('/$', '') .. '/')
end

function Utils.notify(msg, level)
vim.notify(msg, level or vim.log.levels.INFO)
end

function Utils.url_encode(str)
if Utils.is_executable 'python3' then
local handle = io.popen(string.format('python3 -c "import sys, urllib.parse; print(urllib.parse.quote(sys.argv[1]))" %q 2>/dev/null', str))
if handle then
local result = handle:read '*a'
handle:close()
return result:gsub('\n', '')
end
end

return str:gsub('[^%w%-%.%_%~]', function(c)
return string.format('%%%02X', string.byte(c))
end)
end

-- If vim.pesc is not defined, define it to escape Lua patterns.
if not vim.pesc then
vim.pesc = function(str)
return str:gsub('([^%w])', '%%%1')
end
end

-- Vault Manager Module --
local VaultManager = {}

function VaultManager.load()
local file = io.open(Config.path, 'r')
if not file then
return VaultManager.create_default()
end

local success, parsed = pcall(vim.json.decode, file:read '*a')
file:close()

if success and type(parsed) == 'table' then
Config.vaults = parsed
return true
end

return VaultManager.create_default()
end

function VaultManager.create_default()
local default_path = Utils.safe_path(vim.fn.expand '~/obsidian-vault/')
if Utils.ensure_dir(default_path) then
Config.vaults = { {
name = Config.default_vault_name,
path = default_path,
} }
return VaultManager.save()
end
return false
end

function VaultManager.save()
local file = io.open(Config.path, 'w')
if not file then
Utils.notify('Failed to save vault config', vim.log.levels.ERROR)
return false
end

local success, encoded = pcall(vim.json.encode, Config.vaults)
if not success then
file:close()
Utils.notify('Config serialization failed', vim.log.levels.ERROR)
return false
end

file:write(encoded)
file:close()
return true
end

function VaultManager.find_containing(filepath)
local safe_path = Utils.safe_path(filepath)
for _, vault in ipairs(Config.vaults) do
local vault_path = Utils.safe_path(vault.path)
if safe_path:find('^' .. vim.pesc(vault_path)) then
return vault, safe_path:gsub('^' .. vim.pesc(vault_path), '')
end
end
return nil, nil
end

-- Obsidian Core --
local Obsidian = {}

function Obsidian.open()
local filepath = vim.fn.expand '%:p'
if filepath == '' then
return Utils.notify('No file to open', vim.log.levels.WARN)
end
if not filepath:match '%.md$' then
return Utils.notify('Not a markdown file', vim.log.levels.WARN)
end

local vault, rel_path = VaultManager.find_containing(filepath)
if vault then
Obsidian.open_vault_file(vault, rel_path)
else
Obsidian.open_external_file(filepath)
end
end

function Obsidian.open_vault_file(vault, rel_path)
local encoded = Utils.url_encode(rel_path)
local uri = ('obsidian://open?vault=%s&file=%s'):format(vault.name, encoded)
Utils.notify(("Opening in '%s': %s"):format(vault.name, rel_path))
Obsidian.execute({ 'xdg-open', uri }, true)
end

function Obsidian.open_external_file(filepath)
local default_vault = Config.vaults[1]
if not default_vault then
return Utils.notify('No default vault', vim.log.levels.ERROR)
end

local temp_dir = Utils.safe_path(default_vault.path .. Config.temp_dir_suffix)
if not Utils.ensure_dir(temp_dir) then
return
end

local basename = vim.fn.fnamemodify(filepath, ':t')
local symlink = temp_dir .. basename

os.remove(symlink)
if not vim.loop.fs_symlink(filepath, symlink) then
return Utils.notify('Symlink failed', vim.log.levels.ERROR)
end

-- Add these lines to refresh Obsidian on save
vim.api.nvim_create_autocmd({ 'BufWritePost' }, {
buffer = vim.api.nvim_get_current_buf(),
callback = function()
-- Update symlink metadata to force Obsidian refresh
os.execute(string.format('touch -h %q', symlink)) -- -h flag affects symlink itself
end,
})

vim.api.nvim_create_autocmd({ 'BufDelete', 'BufWipeout' }, {
buffer = vim.api.nvim_get_current_buf(),
callback = function()
os.remove(symlink)
end,
})

local encoded = Utils.url_encode(Config.temp_dir_suffix .. '/' .. basename)
local uri = ('obsidian://open?vault=%s&file=%s'):format(default_vault.name, encoded)
Obsidian.execute({ 'xdg-open', uri }, true)
end

function Obsidian.close(detach)
if not Utils.is_executable 'pkill' then
return
end

local opts = {
detach = detach or false,
on_exit = not detach and function(_, code)
if code == 0 then
Utils.notify 'Obsidian closed'
end
end,
}

Obsidian.execute({ 'pkill', '-9', '-f', 'obsidian' }, opts.detach, opts.on_exit)
end

function Obsidian.execute(command, detach, callback)
vim.fn.jobstart(command, {
detach = detach,
on_exit = callback or function(_, code)
if code ~= 0 then
Utils.notify('Command failed', vim.log.levels.ERROR)
end
end,
})
end

-- Public API --
function M.add_vault(name, path)
path = Utils.safe_path(vim.fn.expand(path))

for _, vault in ipairs(Config.vaults) do
if vault.name == name then
return Utils.notify('Vault name exists', vim.log.levels.WARN)
end
if Utils.safe_path(vault.path) == path then
return Utils.notify('Vault path exists', vim.log.levels.WARN)
end
end

if not Utils.ensure_dir(path) then
return Utils.notify('Directory creation failed', vim.log.levels.ERROR)
end

table.insert(Config.vaults, { name = name, path = path })
return VaultManager.save() and Utils.notify(('Added vault: %s'):format(name))
end

function M.remove_vault(name)
for i, vault in ipairs(Config.vaults) do
if vault.name == name then
table.remove(Config.vaults, i)
return VaultManager.save() and Utils.notify(('Removed vault: %s'):format(name))
end
end
Utils.notify('Vault not found', vim.log.levels.WARN)
end

function M.list_vaults()
local lines = { 'Configured Vaults:' }
for _, vault in ipairs(Config.vaults) do
table.insert(lines, ('- %s: %s'):format(vault.name, vault.path))
end
Utils.notify(table.concat(lines, '\n'))
end

-- Initialization --
VaultManager.load()

-- Command Setup --
vim.api.nvim_create_user_command('OpenInObsidian', Obsidian.open, {})
vim.api.nvim_create_user_command('CloseObsidian', function()
Obsidian.close()
end, {})
vim.api.nvim_create_user_command('ListObsidianVaults', M.list_vaults, {})
vim.api.nvim_create_user_command('AddObsidianVault', function(opts)
local args = vim.split(opts.args, '%s+', { trimempty = true })
if #args == 2 then
M.add_vault(args[1], args[2])
end
end, { nargs = '+' })

vim.api.nvim_create_user_command('RemoveObsidianVault', function(opts)
M.remove_vault(opts.args)
end, { nargs = 1 })

-- Keymaps --
local wk_ok, wk = pcall(require, 'which-key')
local keymaps = {
['<leader>o'] = {
name = 'Obsidian',
o = { Obsidian.open, 'Open' },
c = {
function()
Obsidian.close()
end,
'Close',
},
l = { M.list_vaults, 'List' },
a = {
function()
vim.ui.input({ prompt = 'Vault name: ' }, function(name)
if name then
vim.ui.input({ prompt = 'Path: ' }, function(path)
if path then
M.add_vault(name, path)
end
end)
end
end)
end,
'Add Vault',
},
r = {
function()
vim.ui.input({ prompt = 'Vault to remove: ' }, function(name)
if name then
M.remove_vault(name)
end
end)
end,
'Remove Vault',
},
},
}

if wk_ok then
wk.register(keymaps)
else
for lhs, rhs in pairs(keymaps['<leader>o']) do
if type(rhs) == 'table' then
vim.keymap.set('n', '<leader>o' .. lhs, rhs[1], { desc = rhs[2] })
end
end
end

-- Autocommands --
vim.api.nvim_create_augroup('ObsidianIntegration', { clear = true })

vim.api.nvim_create_autocmd({ 'FocusGained', 'BufEnter' }, {
group = 'ObsidianIntegration',
callback = vim.schedule_wrap(function()
vim.cmd 'checktime'
end),
})

vim.api.nvim_create_autocmd('VimLeavePre', {
group = 'ObsidianIntegration',
callback = function()
for _, vault in ipairs(Config.vaults) do
os.execute(('rm -rf %q'):format(vault.path .. Config.temp_dir_suffix))
end
-- Obsidian.close(true)
end,
})

-- [[ Basic Autocommands ]]
-- See `:help lua-guide-autocommands`

Expand All @@ -203,6 +552,12 @@ vim.api.nvim_create_autocmd('TextYankPost', {
end,
})

vim.api.nvim_exec(
[[
autocmd BufNewFile ~/cavelazquez8-wiki/diary/*.md :silent 0r !~/.vim/autoload/vimwiki/generate-vimwiki-diary-template '%'
]],
false
)
-- [[ Install `lazy.nvim` plugin manager ]]
-- See `:help lazy.nvim.txt` or https://github.com/folke/lazy.nvim for more info
local lazypath = vim.fn.stdpath 'data' .. '/lazy/lazy.nvim'
Expand Down Expand Up @@ -718,6 +1073,9 @@ require('lazy').setup({
end,
},
}
require('lspconfig').clangd.setup {
capabilities = capabilities,
}
end,
},

Expand Down Expand Up @@ -991,8 +1349,7 @@ require('lazy').setup({
-- This is the easiest way to modularize your config.
--
-- Uncomment the following line and add your plugins to `lua/custom/plugins/*.lua` to get going.
-- { import = 'custom.plugins' },
--
{ import = 'custom.plugins' },
-- For additional information with loading, sourcing and examples see `:help lazy.nvim-🔌-plugin-spec`
-- Or use telescope!
-- In normal mode type `<space>sh` then write `lazy.nvim-plugin`
Expand Down
Loading