From 3a9e04d3ce7267352abbf1373d83458a4aa2de9e Mon Sep 17 00:00:00 2001 From: Marty Oehme Date: Wed, 4 Nov 2020 21:29:23 +0100 Subject: [PATCH] Refactor open actions Open actions now make use of a 'link' data structure containing a text and ref string, and the respective start and end positions on the line. Additionally, parsers are now simple objects containing extraction regex for extracting their text and ref link individually. --- lua/zettelkasten/action.lua | 80 ++++++++++++++++++--------- lua/zettelkasten/action_spec.lua | 92 +++++++++++++++++++++----------- 2 files changed, 116 insertions(+), 56 deletions(-) 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)