diff --git a/lazy-lock.json b/lazy-lock.json --- a/lazy-lock.json +++ b/lazy-lock.json @@ -1,6 +1,6 @@ { "LazyVim": { "branch": "main", "commit": "dc1ffa5bcb66f46284f91a8593dda5c7c54a1824" }, - "SchemaStore.nvim": { "branch": "main", "commit": "0a900e539511a4461701f241278626ce6ec8d331" }, + "SchemaStore.nvim": { "branch": "main", "commit": "caad5439cc80809be7475b625a5e36d1bb06cc7e" }, "blame.nvim": { "branch": "main", "commit": "d5f4ad5c8ebca3caecc773dda5983709c5a5da8f" }, "blink-cmp-dictionary": { "branch": "master", "commit": "43b701fe9728a704bc63e4667c5d8b398bf129b2" }, "blink-cmp-git": { "branch": "master", "commit": "e3ad0ffaaa3b3b7e2158cc72cd6dad2d19842c46" }, @@ -33,8 +33,8 @@ "mini.icons": { "branch": "main", "commit": "284798619aed9f4c1ac1b9417b9a5e3b4b85ef3a" }, "mini.pairs": { "branch": "main", "commit": "b9aada8c0e59f2b938e98fbf4eae0799eba96ad9" }, "mini.surround": { "branch": "main", "commit": "444e155147e2b5159dd28a65f9736254c16cb817" }, - "neotest": { "branch": "master", "commit": "2cf3544fb55cdd428a9a1b7154aea9c9823426e8" }, - "neotest-golang": { "branch": "main", "commit": "a1811854245b599737a7dfc9978ab74040f40e62" }, + "neotest": { "branch": "master", "commit": "7166dc36af2760a76479e021e0521e23f62165f1" }, + "neotest-golang": { "branch": "main", "commit": "e207f660292cd0e899bd021b74422de9139f2825" }, "neotest-python": { "branch": "master", "commit": "7cab6e2ecc260bf61b97ca147574a470bd880cbd" }, "neotest-zig": { "branch": "main", "commit": "de63f3b9a182d374d2e71cf44385326682ec90e7" }, "noice.nvim": { "branch": "main", "commit": "38c702be0d8fea81527ee6a73e1e834e72481193" }, @@ -46,25 +46,25 @@ "nvim-dap-ui": { "branch": "master", "commit": "cf91d5e2d07c72903d052f5207511bf7ecdb7122" }, "nvim-dap-virtual-text": { "branch": "master", "commit": "fbdb48c2ed45f4a8293d0d483f7730d24467ccb6" }, "nvim-lint": { "branch": "master", "commit": "9da1fb942dd0668d5182f9c8dee801b9c190e2bb" }, - "nvim-lspconfig": { "branch": "master", "commit": "cc2f5f2fa28d240574808e78847978ed6ef20d2a" }, + "nvim-lspconfig": { "branch": "master", "commit": "ac98db2f9f06a56498ec890a96928774eae412c3" }, "nvim-nio": { "branch": "master", "commit": "21f5324bfac14e22ba26553caf69ec76ae8a7662" }, - "nvim-treesitter": { "branch": "main", "commit": "de003000a287c501a6650a1ddd3be80ee34278d4" }, + "nvim-treesitter": { "branch": "main", "commit": "0606c7a9dcaa5c5beee0b0f09043e9fdd1ba0a68" }, "nvim-treesitter-textobjects": { "branch": "main", "commit": "1b2d85d3de6114c4bcea89ffb2cd1ce9e3a19931" }, "nvim-ts-autotag": { "branch": "main", "commit": "c4ca798ab95b316a768d51eaaaee48f64a4a46bc" }, - "obsidian.nvim": { "branch": "main", "commit": "906fa5481812d05f7c428468fe244c5f991e3e83" }, + "obsidian.nvim": { "branch": "main", "commit": "6b2a22a74d1c883e797764c28f75aa6b532a1ae4" }, "octo.nvim": { "branch": "master", "commit": "fded71669b61c6ccae9d8ade30f667c3c4962b48" }, - "one-small-step-for-vimkind": { "branch": "main", "commit": "99c22f1fa83eb9aa442cf13a23aed7197a2d4456" }, + "one-small-step-for-vimkind": { "branch": "main", "commit": "c7c7aa7d764ecdf95181916dbb3f5c56b674ed9e" }, "orphans.nvim": { "branch": "main", "commit": "35f995fbb04b679b7d829072d7209a2117c15adf" }, "persistence.nvim": { "branch": "main", "commit": "51eef57272742b773468949f6bd0503ec3f83874" }, "plenary.nvim": { "branch": "master", "commit": "b9fd5226c2f76c951fc8ed5923d85e4de065e509" }, "project.nvim": { "branch": "main", "commit": "8c6bad7d22eef1b71144b401c9f74ed01526a4fb" }, "render-markdown.nvim": { "branch": "main", "commit": "d53856423be5ef3c267d26ee261b0981b372f718" }, - "rustaceanvim": { "branch": "master", "commit": "25de2236be57cb061854bcaaf3de6707530ab792" }, - "sidekick.nvim": { "branch": "main", "commit": "819470b18c8d3a8a8fec4598ed418ef6d0fdfa9a" }, - "snacks.nvim": { "branch": "main", "commit": "94f10539a64f86a30c212fc3cc3be9cc7145317e" }, + "rustaceanvim": { "branch": "master", "commit": "2f5afad4fc62c609dfd2ef4613999a0e8bddcccc" }, + "sidekick.nvim": { "branch": "main", "commit": "d679927f2f7b0550cb5b36077058ad9a335c7b04" }, + "snacks.nvim": { "branch": "main", "commit": "b8d838d8be274c8eecced4306cca15378cde9830" }, "text-case.nvim": { "branch": "main", "commit": "e898cfd46fa6cde0e83abb624a16e67d2ffc6457" }, "todo-comments.nvim": { "branch": "main", "commit": "19d461ddd543e938eb22505fb03fa878800270b6" }, - "tokyonight.nvim": { "branch": "main", "commit": "00d92e6009671493fceeb0e4baf644f5b983e6e4" }, + "tokyonight.nvim": { "branch": "main", "commit": "d14614cbfc63b6037bfccd48bb982d2ad2003352" }, "trouble.nvim": { "branch": "main", "commit": "c098362fe603d3922095e7db595961e020bdf2d0" }, "ts-comments.nvim": { "branch": "main", "commit": "217ab9cc137fceb6659b53790bd25e608219abe1" }, "venv-selector.nvim": { "branch": "main", "commit": "7fff64b5b1455207b9a9fd2ae8697cf9ac0b2a2d" }, diff --git a/lua/config/keymaps.lua b/lua/config/keymaps.lua --- a/lua/config/keymaps.lua +++ b/lua/config/keymaps.lua @@ -75,3 +75,35 @@ map("t", "", "close", { desc = "Hide Terminal" }) map("n", "cD", "%s/\\s\\+$//e", { desc = "Delete trailing spaces" }) + +-- Toggle harper_ls (spelling checker) +local harper_ls_active = false +map("n", "cs", function() + local clients = vim.lsp.get_clients({ name = "harper_ls" }) + if #clients > 0 then + -- Stop harper_ls + for _, client in ipairs(clients) do + client.stop() + end + harper_ls_active = false + vim.notify("Harper LSP stopped", vim.log.levels.INFO) + else + -- Start harper_ls + vim.cmd("LspStart harper_ls") + harper_ls_active = true + vim.notify("Harper LSP started", vim.log.levels.INFO) + end +end, { desc = "Toggle spelling" }) + +local function undotree() + vim.cmd(":packadd nvim.undotree") + local close = require("undotree").open({ + title = "undotree", + command = "topright 30vnew", + }) + if not close then + vim.bo.filetype = "undotree" + end +end + +vim.api.nvim_create_user_command("Undotree", undotree, {}) diff --git a/lua/config/utils.lua b/lua/config/utils.lua --- a/lua/config/utils.lua +++ b/lua/config/utils.lua @@ -45,9 +45,9 @@ -- Use dd command to copy with elevated privileges local cmd = string.format("dd if=%s of=%s bs=1048576", vim.fn.shellescape(tmpfile), vim.fn.shellescape(filepath)) -- Write current buffer to temporary file - vim.api.nvim_exec(string.format("write! %s", tmpfile), true) + vim.api.nvim_exec2(string.format("write! %s", tmpfile), { output = true }) -- Execute dd command with sudo and handle result - if M.sudo_exec(cmd) then + if M.sudo_exec(cmd, false) then print(string.format('\r\n"%s" written', filepath)) vim.cmd("e!") -- Reload the buffer to reflect changes end diff --git a/lua/plugins/lspconfig.lua b/lua/plugins/lspconfig.lua --- a/lua/plugins/lspconfig.lua +++ b/lua/plugins/lspconfig.lua @@ -19,5 +19,13 @@ }, }) opts.servers.golangci_lint_ls = {} + opts.servers.harper_ls = {} + + -- Setup function to prevent harper_ls from autostarting + opts.setup = opts.setup or {} + opts.setup.harper_ls = function(_, server_opts) + -- Don't setup harper_ls automatically + return true + end end, } diff --git a/lua/plugins/mason.lua b/lua/plugins/mason.lua --- a/lua/plugins/mason.lua +++ b/lua/plugins/mason.lua @@ -4,8 +4,9 @@ "mason-org/mason.nvim", opts = { ensure_installed = { - "ruff", -- Python linting and formatting + "ruff", -- Python linting and formatting "sqlfluff", -- SQL linting and formatting + "harper-ls", -- Spelling and grammar checking }, }, } diff --git a/lua/plugins/url-to-markdown.lua b/lua/plugins/url-to-markdown.lua new file mode 100644 --- /dev/null +++ b/lua/plugins/url-to-markdown.lua @@ -0,0 +1,12 @@ +-- URL to Markdown converter plugin +return { + name = "url-to-markdown", + dir = vim.fn.stdpath("config"), + config = function() + local url_to_markdown = require("utils.url-to-markdown") + url_to_markdown.setup({ + keybind = "", -- Cmd+Shift+A + }) + end, + filetype = { "markdown", "mdx" }, +} diff --git a/lua/utils/url-to-markdown.lua b/lua/utils/url-to-markdown.lua new file mode 100644 --- /dev/null +++ b/lua/utils/url-to-markdown.lua @@ -0,0 +1,135 @@ +-- url-to-markdown.lua +-- Converts URLs to markdown links with fetched page titles (async) + +local M = {} + +-- Track active conversions +local active_conversions = {} + +-- Fetch title from URL asynchronously +local function fetch_title_async(url, callback) + local curl_cmd = { + "curl", + "-sL", + "--max-time", + "10", -- 10 second timeout + url, + } + + vim.system(curl_cmd, { + text = true, + timeout = 10000, -- 10 seconds + }, function(result) + vim.schedule(function() + if result.code == 0 and result.stdout then + -- Extract title from HTML + local title = result.stdout:match("]*>([^<]*)") + if title then + title = title:gsub("^%s+", ""):gsub("%s+$", ""):gsub("\n", " ") + -- Decode common HTML entities + title = title:gsub("&", "&"):gsub("<", "<"):gsub(">", ">"):gsub(""", '"'):gsub("'", "'") + end + callback(title ~= "" and title or nil) + else + callback(nil) + end + end) + end) +end + +-- Convert URL under cursor to markdown link +function M.url_to_markdown() + local line = vim.api.nvim_get_current_line() + local col = vim.api.nvim_win_get_cursor(0)[2] + local line_num = vim.api.nvim_win_get_cursor(0)[1] + local buf = vim.api.nvim_get_current_buf() + + -- Find URL pattern + local url_pattern = "https?://[%w-._~:/?#%[%]@!$&'()*+,;=%%]+" + local url_start, url_end = nil, nil + + for start, finish in line:gmatch("()" .. url_pattern .. "()") do + if start - 1 <= col and col < finish - 1 then + url_start = start + url_end = finish - 1 + break + end + end + + if not url_start then + vim.notify("No URL found under cursor", vim.log.levels.WARN) + return + end + + local url = line:sub(url_start, url_end) + + -- Create unique key for this conversion + local conversion_key = string.format("%d:%d:%d:%s", buf, line_num, url_start, url) + + -- Check if this exact conversion is already in progress + if active_conversions[conversion_key] then + vim.notify("Conversion already in progress for this URL", vim.log.levels.INFO) + return + end + + -- Mark as active + active_conversions[conversion_key] = true + + -- Show progress + vim.notify("Fetching title for: " .. (url:len() > 50 and url:sub(1, 50) .. "..." or url), vim.log.levels.INFO) + + -- Fetch title asynchronously + fetch_title_async(url, function(title) + -- Remove from active conversions + active_conversions[conversion_key] = nil + + -- Check if buffer and line still exist and are unchanged + if not vim.api.nvim_buf_is_valid(buf) then + vim.notify("Buffer no longer valid", vim.log.levels.WARN) + return + end + + local current_line = vim.api.nvim_buf_get_lines(buf, line_num - 1, line_num, false)[1] + if not current_line then + vim.notify("Line no longer exists", vim.log.levels.WARN) + return + end + + -- Check if the URL is still at the expected position + local current_url = current_line:sub(url_start, url_end) + if current_url ~= url then + vim.notify("URL has changed, skipping conversion", vim.log.levels.WARN) + return + end + + -- Use URL as fallback title + if not title then + title = url + vim.notify("Could not fetch title, using URL", vim.log.levels.WARN) + end + + -- Create markdown link + local md_link = string.format("[%s](%s)", title, url) + + -- Replace URL with markdown link + local new_line = current_line:sub(1, url_start - 1) .. md_link .. current_line:sub(url_end + 1) + vim.api.nvim_buf_set_lines(buf, line_num - 1, line_num, false, { new_line }) + + vim.notify("✓ Converted to markdown link", vim.log.levels.INFO) + end) +end + +-- Setup function +function M.setup(opts) + opts = opts or {} + + -- Create command + vim.api.nvim_create_user_command("UrlToMarkdown", M.url_to_markdown, {}) + + -- Optional: Set up default keybinding + if opts.keybind then + vim.keymap.set("n", opts.keybind, M.url_to_markdown, { desc = "Convert URL to markdown link" }) + end +end + +return M