diff --git a/lua/zettelkasten/action.lua b/lua/zettelkasten/action.lua index 27e998f..1c60076 100644 --- a/lua/zettelkasten/action.lua +++ b/lua/zettelkasten/action.lua @@ -2,16 +2,19 @@ local A = {} local o = require 'zettelkasten.options' -local parsers = {markdown = "%[.-%]%((.-)%)", wiki = "%[%[(.+)|?.-%]%]"} +local BIGNUMBER = 10000000 --- Extracts a file name from a link and opens the corresponding file --- in the current buffer. --- Takes an optional input parameter -function A.open(input) - local fname = A.extract_link(input) - if not fname then return end +local parsers = { + markdown = {ref = "%[.-%]%((.-)%)", text = "%[(.-)%]%(.-%)"}, + wiki = {ref = "%[%[(.-)|?.-%]%]", text = "%[%[.-|?(.-)%]%]"} +} + +-- Opens the link passed in in the editor's current buffer. +-- Requires a link object passed in. +function A.open(link) + if not link or not link.ref then return end -- TODO follow: go to anchor, fall back to filename - vim.api.nvim_command(string.format("edit %s", fname)) + vim.api.nvim_command(string.format("edit %s", link.ref)) end -- Gets the input at the current buffer cursor and opens it @@ -27,29 +30,56 @@ function A.open_selected(style) end end --- Return only the link reference portion of a markdown/wiki style link. --- For example, for a markdown link [my text](my-link.md) --- it would only return my-link.md -function A.extract_link(input) +-- Return all links contained in the input given in an array. +-- Returned link tables have the following structure: +-- link = { text=, ref=, startpos=27, endpos=65 } +function A.extract_all_links(input) if not input then return end - for _, parser in pairs(parsers) do return input:match(parser) end - return + local links = {} + local curpos = 1 + for _, parser in pairs(parsers) do + while input:find(parser.ref, curpos) do + local ref = input:match(parser.ref, curpos) + local text = input:match(parser.text, curpos) + local startpos, endpos = input:find(parser.ref, curpos) + table.insert(links, { + ref = ref, + text = text, + startpos = startpos, + endpos = endpos + }) + curpos = endpos + end + end + return links end --- Returns the word currently under cursor, the vim equivalent of yiW. --- Takes an optional boolean flag to set the word being caught --- to the vim equivalent of doing yiw, a more exclusive version. -function A.get_link_under_cursor(small) - local c = "" - if small then c = "" end - local word = vim.fn.expand(c) - return word +-- Returns the link currently under cursor, roughly the vim equivalent of yiW. +-- Works for links containing spaces in their text or reference link. +function A.get_link_under_cursor() + local curpos = vim.api.nvim_win_get_cursor(0)[2] + local links = A.extract_all_links(vim.api.nvim_get_current_line()) + for _, link in pairs(links) do + if link.startpos <= curpos + 1 and link.endpos > curpos then + return link + end + end + return nil end --- Returns the content of the line from the cursor onwards. +-- Returns the next link of the line from the cursor onwards. function A.get_next_link_on_line() - local line = vim.api.nvim_get_current_line() - return line:sub(vim.api.nvim_win_get_cursor(0)[2]) + local curpos = vim.api.nvim_win_get_cursor(0)[2] + local links = A.extract_all_links(vim.api.nvim_get_current_line()) + local nearestpos = BIGNUMBER + local nearestlink + for k, link in pairs(links) do + if link.endpos > curpos and link.endpos < nearestpos then + nearestpos = link.endpos + nearestlink = link + end + end + return nearestlink end return {open = A.open, open_selected = A.open_selected} diff --git a/lua/zettelkasten/action_spec.lua b/lua/zettelkasten/action_spec.lua index 581ce46..1c956f4 100644 --- a/lua/zettelkasten/action_spec.lua +++ b/lua/zettelkasten/action_spec.lua @@ -4,10 +4,10 @@ before_each(function() _G.vim = {g = {}, b = {}} end) after_each(function() _G.vim = nil end) describe("open", function() - it("should open file in editor if it is a valid link", function() + it("should open file in editor if it contains a valid link ref", function() vim.api = {nvim_command = mock(function() end)} - action.open("[some text](1910271456_link-to-my-file.md)") + action.open({ref = "1910271456_link-to-my-file.md"}) assert.spy(vim.api.nvim_command).was_called_with( "edit 1910271456_link-to-my-file.md") end) @@ -28,37 +28,67 @@ describe("open_selected", function() nvim_win_get_cursor = function(winnum) return {0, 0} end } end) - it("should use the style passed to it, above the one set in options", - function() - vim.g['zettel_link_following'] = 'cursor' + describe("when looking under cursor", function() + it("should open link", function() + vim.g['zettel_link_following'] = 'cursor' + vim.api.nvim_win_get_cursor = + function(winnum) return {0, 30} end + action.open_selected() + assert.spy(vim.api.nvim_command).was_called_with( + "edit 1910271456_link-to-my-file.md") + end) + it("should detect correct position for link start", function() + vim.g['zettel_link_following'] = 'cursor' - vim.api.nvim_get_current_line = mock(vim.api.nvim_get_current_line) - action.open_selected("line") + vim.api.nvim_win_get_cursor = + function(winnum) return {0, 25} end + action.open_selected() + assert.spy(vim.api.nvim_command).was_not_called() - assert.spy(vim.api.nvim_get_current_line).was_called() + vim.api.nvim_win_get_cursor = + function(winnum) return {0, 26} end + action.open_selected() + assert.spy(vim.api.nvim_command).was_called_with( + "edit 1910271456_link-to-my-file.md") + end) + it("should detect correct position for link end", function() + vim.g['zettel_link_following'] = 'cursor' + + vim.api.nvim_win_get_cursor = + function(winnum) return {0, 65} end + action.open_selected() + assert.spy(vim.api.nvim_command).was_not_called() + + vim.api.nvim_win_get_cursor = + function(winnum) return {0, 64} end + action.open_selected() + assert.spy(vim.api.nvim_command).was_called_with( + "edit 1910271456_link-to-my-file.md") + end) end) - it("should open link under cursor if option set", function() - vim.g['zettel_link_following'] = 'cursor' - vim.fn = { - expand = function(sure) - return "[" .. sure .. "](1910271456_link-to-my-file.md)" - end - } - action.open_selected() - assert.spy(vim.api.nvim_command).was_called_with( - "edit 1910271456_link-to-my-file.md") - end) - it("should open next link on line if option set", function() - vim.g['zettel_link_following'] = 'line' - action.open_selected() - assert.spy(vim.api.nvim_command).was_called_with( - "edit 1910271456_link-to-my-file.md") - end) - it("should ignore links before cursor position", function() - vim.g['zettel_link_following'] = 'line' - vim.api.nvim_win_get_cursor = function(winnum) return {0, 65} end - action.open_selected() - assert.spy(vim.api.nvim_command).was_called_with( - "edit 2030101158 another-link-now.md") + describe("when looking until end of line", function() + it("should use the style passed to it, above the one set in options", + function() + vim.g['zettel_link_following'] = 'cursor' + + vim.api.nvim_get_current_line = mock(vim.api.nvim_get_current_line) + action.open_selected("line") + + assert.spy(vim.api.nvim_get_current_line).was_called() + end) + it("should open next link on line if option set", function() + vim.g['zettel_link_following'] = 'line' + action.open_selected() + assert.spy(vim.api.nvim_command).was_called_with( + "edit 1910271456_link-to-my-file.md") + end) + it("should ignore links before cursor position", function() + vim.g['zettel_link_following'] = 'line' + vim.api.nvim_win_get_cursor = + function(winnum) return {0, 65} end + action.open_selected() + assert.spy(vim.api.nvim_command).was_called_with( + "edit 2030101158 another-link-now.md") + end) end) end)