Skip to content

Commit

Permalink
feat: allow TS users to highlight marker delimiters
Browse files Browse the repository at this point in the history
This permits those of us who use treesitter to change the highlights
surrounding markers. E.g. for the "verbatim" marker we can now highlight
the "=" characters individually instead of the entire chunk being the
same highlight.

This is particularly useful in tables with lots of code elements, this
makes it easier to tell the delimiter characters apart from the primary
marker.
  • Loading branch information
PriceHiller committed Oct 31, 2023
1 parent 47b2978 commit 772c7d6
Show file tree
Hide file tree
Showing 3 changed files with 156 additions and 13 deletions.
45 changes: 33 additions & 12 deletions lua/orgmode/colors/markup_highlighter.lua
Original file line number Diff line number Diff line change
Expand Up @@ -9,58 +9,58 @@ local valid_post_marker_chars =
local markers = {
['*'] = {
hl_name = 'org_bold',
hl_cmd = 'hi def org_bold term=bold cterm=bold gui=bold',
hl_cmd = 'hi def %s term=bold cterm=bold gui=bold',
nestable = true,
type = 'text',
},
['/'] = {
hl_name = 'org_italic',
hl_cmd = 'hi def org_italic term=italic cterm=italic gui=italic',
hl_cmd = 'hi def %s term=italic cterm=italic gui=italic',
nestable = true,
type = 'text',
},
['_'] = {
hl_name = 'org_underline',
hl_cmd = 'hi def org_underline term=underline cterm=underline gui=underline',
hl_cmd = 'hi def %s term=underline cterm=underline gui=underline',
nestable = true,
type = 'text',
},
['+'] = {
hl_name = 'org_strikethrough',
hl_cmd = 'hi def org_strikethrough term=strikethrough cterm=strikethrough gui=strikethrough',
hl_cmd = 'hi def %s term=strikethrough cterm=strikethrough gui=strikethrough',
nestable = true,
type = 'text',
},
['~'] = {
hl_name = 'org_code',
hl_cmd = 'hi def link org_code String',
hl_cmd = 'hi def link %s String',
nestable = false,
spell = false,
type = 'text',
},
['='] = {
hl_name = 'org_verbatim',
hl_cmd = 'hi def link org_verbatim String',
hl_cmd = 'hi def link %s String',
nestable = false,
spell = false,
type = 'text',
},
['\\('] = {
hl_name = 'org_latex',
hl_cmd = 'hi def link org_latex OrgTSLatex',
hl_cmd = 'hi def link %s OrgTSLatex',
nestable = false,
spell = false,
type = 'latex',
},
['\\{'] = {
hl_name = 'org_latex',
hl_cmd = 'hi def link org_latex OrgTSLatex',
hl_cmd = 'hi def link %s OrgTSLatex',
nestable = false,
type = 'latex',
},
['\\s'] = {
hl_name = 'org_latex',
hl_cmd = 'hi def link org_latex OrgTSLatex',
hl_cmd = 'hi def link %s OrgTSLatex',
nestable = false,
type = 'latex',
},
Expand Down Expand Up @@ -357,10 +357,29 @@ local function apply(namespace, bufnr, line_index)
local hide_markers = config.org_hide_emphasis_markers

for _, range in ipairs(result.ranges) do
-- Main body highlight
vim.api.nvim_buf_set_extmark(bufnr, namespace, range.from.start.line, range.from.start.character + 1, {
ephemeral = true,
end_col = range.to['end'].character - 1,
hl_group = markers[range.type].hl_name,
spell = markers[range.type].spell,
priority = 110 + range.from.start.character,
})

-- Leading delimiter
vim.api.nvim_buf_set_extmark(bufnr, namespace, range.from.start.line, range.from.start.character, {
ephemeral = true,
end_col = range.from.start.character + 1,
hl_group = markers[range.type].hl_name .. '_delimiter',
spell = markers[range.type].spell,
priority = 110 + range.from.start.character,
})

-- Closing delimiter
vim.api.nvim_buf_set_extmark(bufnr, namespace, range.from.start.line, range.to['end'].character - 1, {
ephemeral = true,
end_col = range.to['end'].character,
hl_group = markers[range.type].hl_name,
hl_group = markers[range.type].hl_name .. '_delimiter',
spell = markers[range.type].spell,
priority = 110 + range.from.start.character,
})
Expand Down Expand Up @@ -423,8 +442,10 @@ local function apply(namespace, bufnr, line_index)
end

local function setup()
for _, marker in pairs(markers) do
vim.cmd(marker.hl_cmd)
for delimiter, marker in pairs(markers) do
vim.cmd(string.format(marker.hl_cmd, marker.hl_name))
vim.cmd(string.format('syn keyword %s_delimiter %s', marker.hl_name, delimiter))
vim.cmd(string.format(marker.hl_cmd, marker.hl_name .. '_delimiter'))
end
vim.cmd('hi def link org_hyperlink Underlined')
load_deps()
Expand Down
92 changes: 92 additions & 0 deletions lua/orgmode/state/state.lua
Original file line number Diff line number Diff line change
@@ -0,0 +1,92 @@
local utils = require('orgmode.utils')
local Promise = require('orgmode.utils.promise')

local State = { data = {}, _ctx = { loaded = false, curr_loader = nil } }

local cache_path = vim.fs.normalize(
vim.fn.stdpath('cache') .. '/org-cache.json',
{ expand_env = false })
--- Returns the current State singleton
function State:new()
-- This is done so we can later iterate the 'data'
-- subtable cleanly and shove it into a cache
setmetatable(State, {
__index = function(tbl, key) return tbl.data[key] end,
__newindex = function(tbl, key, value) tbl.data[key] = value end
})
-- Start trying to load the state from cache as part of initializing the state
self:load()
return self
end

---Save the current state to cache
---@return Promise
function State:save()
--- We want to ensure the state was loaded before saving.
return self:load():next(function(_)
utils.writefile(cache_path, vim.json.encode(State.data)):next(
function(_) end, function(err_msg)
vim.schedule_wrap(function()
utils.echo_warning('Failed to save current state! Error: ' .. err_msg)
end)
end)
end, function(_) end)
end

---Load the state cache into the current state
---@return Promise
function State:load()
--- If we currently have a loading operation already running, return that
--- promise. This avoids a race condition of sorts as without this there's
--- potential to have two State:load operations occuring and whichever
--- finishes last sets the state. Not desirable.
if self._ctx.curr_loader ~= nil then return self._ctx.curr_loader end

--- If we've already loaded the state from cache we don't need to do so again
if self._ctx.loaded then
return Promise.new(function(resolve, _) return resolve() end)
end

self._ctx.curr_loader = utils.readfile(cache_path, { raw = true }):next(
function(data)
local success, decoded = pcall(vim.json.decode, data, {
luanil = { object = true, array = true }
})
self._ctx.curr_loader = nil
if not success then
vim.schedule(function()
utils.echo_warning('Failed to save current state! Error: ' .. decoded)
-- Try to 'repair' the cache by saving the current state
self:save()
end)
end
-- Because the state cache repair happens potentially after the data has
-- been added to the cache, we need to ensure the decoded table is set to
-- empty if we got an error back on the json decode operation.
if type(decoded) ~= "table" then
decoded = {}
end

self._ctx.loaded = true
-- It is possible that while the state was loading from cache values
-- were saved into the state. We want to preference the newer values in
-- the state and still get whatever values may not have been set in the
-- interim of the load operation.
self.data = vim.tbl_deep_extend('force', decoded, self.data)
return self
end, ---@param err string
function(err)
-- If the file didn't exist then go ahead and save
-- our current cache and as a side effect create the file
if type(err) == 'string' and err:match([[^ENOENT.*]]) then
self:save()
else
-- If the file did exist, something is wrong. Kick this to the top
error(err)
end
end)

return self._ctx.curr_loader
end

return State:new()
32 changes: 31 additions & 1 deletion lua/orgmode/utils/init.lua
Original file line number Diff line number Diff line change
Expand Up @@ -6,7 +6,8 @@ local debounce_timers = {}
local query_cache = {}
local tmp_window_augroup = vim.api.nvim_create_augroup('OrgTmpWindow', { clear = true })

function utils.readfile(file)
function utils.readfile(file, opts)
opts = vim.tbl_deep_extend("keep", opts or {}, { raw = false })
return Promise.new(function(resolve, reject)
uv.fs_open(file, 'r', 438, function(err1, fd)
if err1 then
Expand All @@ -24,6 +25,9 @@ function utils.readfile(file)
if err4 then
return reject(err4)
end
if opts.raw then
return resolve(data)
end
local lines = vim.split(data, '\n')
table.remove(lines, #lines)
return resolve(lines)
Expand All @@ -34,6 +38,32 @@ function utils.readfile(file)
end)
end

function utils.writefile(file, data)
return Promise.new(function(resolve, reject)
uv.fs_open(file, 'w', 438, function(err1, fd)
if err1 then
return reject(err1)
end
uv.fs_fstat(fd, function(err2, stat)
if err2 then
return reject(err2)
end
uv.fs_write(fd, data, nil, function(err3, bytes)
if err3 then
return reject(err3)
end
uv.fs_close(fd, function(err4)
if err4 then
return reject(err4)
end
return resolve(bytes)
end)
end)
end)
end)
end)
end

function utils.open(target)
if vim.fn.executable('xdg-open') == 1 then
return vim.fn.system(string.format('xdg-open %s', target))
Expand Down

0 comments on commit 772c7d6

Please sign in to comment.