diff options
Diffstat (limited to 'runtime/lua/man.lua')
| -rw-r--r-- | runtime/lua/man.lua | 168 | 
1 files changed, 168 insertions, 0 deletions
| diff --git a/runtime/lua/man.lua b/runtime/lua/man.lua new file mode 100644 index 0000000000..b0fbe9cc35 --- /dev/null +++ b/runtime/lua/man.lua @@ -0,0 +1,168 @@ +local buf_hls = {} + +local function highlight_line(line, linenr) +  local chars = {} +  local prev_char = '' +  local overstrike, escape = false, false +  local hls = {} -- Store highlight groups as { attr, start, final } +  local NONE, BOLD, UNDERLINE, ITALIC = 0, 1, 2, 3 +  local hl_groups = {[BOLD]="manBold", [UNDERLINE]="manUnderline", [ITALIC]="manItalic"} +  local attr = NONE +  local byte = 0 -- byte offset + +  local function end_attr_hl(attr) +    for i, hl in ipairs(hls) do +      if hl.attr == attr and hl.final == -1 then +        hl.final = byte +        hls[i] = hl +      end +    end +  end + +  local function add_attr_hl(code) +    local continue_hl = true +    if code == 0 then +      attr = NONE +      continue_hl = false +    elseif code == 1 then +      attr = BOLD +    elseif code == 22 then +      attr = BOLD +      continue_hl = false +    elseif code == 3 then +      attr = ITALIC +    elseif code == 23 then +      attr = ITALIC +      continue_hl = false +    elseif code == 4 then +      attr = UNDERLINE +    elseif code == 24 then +      attr = UNDERLINE +      continue_hl = false +    else +      attr = NONE +      return +    end + +    if continue_hl then +      hls[#hls + 1] = {attr=attr, start=byte, final=-1} +    else +      if attr == NONE then +        for a, _ in pairs(hl_groups) do +          end_attr_hl(a) +        end +      else +        end_attr_hl(attr) +      end +    end +  end + +  -- Break input into UTF8 code points. ASCII code points (from 0x00 to 0x7f) +  -- can be represented in one byte. Any code point above that is represented by +  -- a leading byte (0xc0 and above) and continuation bytes (0x80 to 0xbf, or +  -- decimal 128 to 191). +  for char in line:gmatch("[^\128-\191][\128-\191]*") do +    if overstrike then +      local last_hl = hls[#hls] +      if char == prev_char then +        if char == '_' and attr == UNDERLINE and last_hl and last_hl.final == byte then +          -- This underscore is in the middle of an underlined word +          attr = UNDERLINE +        else +          attr = BOLD +        end +      elseif prev_char == '_' then +        -- char is underlined +        attr = UNDERLINE +      elseif prev_char == '+' and char == 'o' then +        -- bullet (overstrike text '+^Ho') +        attr = BOLD +        char = '·' +      elseif prev_char == '·' and char == 'o' then +        -- bullet (additional handling for '+^H+^Ho^Ho') +        attr = BOLD +        char = '·' +      else +        -- use plain char +        attr = NONE +      end + +      -- Grow the previous highlight group if possible +      if last_hl and last_hl.attr == attr and last_hl.final == byte then +        last_hl.final = byte + #char +      else +        hls[#hls + 1] = {attr=attr, start=byte, final=byte + #char} +      end + +      overstrike = false +      prev_char = '' +      byte = byte + #char +      chars[#chars + 1] = char +    elseif escape then +      -- Use prev_char to store the escape sequence +      prev_char = prev_char .. char +      -- We only want to match against SGR sequences, which consist of ESC +      -- followed by '[', then a series of parameter and intermediate bytes in +      -- the range 0x20 - 0x3f, then 'm'. (See ECMA-48, sections 5.4 & 8.3.117) +      local sgr = prev_char:match("^%[([\032-\063]*)m$") +      if sgr then +        local match = '' +        while sgr and #sgr > 0 do +          -- Match against SGR parameters, which may be separated by ';' +          match, sgr = sgr:match("^(%d*);?(.*)") +          add_attr_hl(match + 0) -- coerce to number +        end +        escape = false +      elseif not prev_char:match("^%[[\032-\063]*$") then +        -- Stop looking if this isn't a partial CSI sequence +        escape = false +      end +    elseif char == "\027" then +      escape = true +      prev_char = '' +    elseif char == "\b" then +      overstrike = true +      prev_char = chars[#chars] +      byte = byte - #prev_char +      chars[#chars] = nil +    else +      byte = byte + #char +      chars[#chars + 1] = char +    end +  end + +  for _, hl in ipairs(hls) do +    if hl.attr ~= NONE then +      buf_hls[#buf_hls + 1] = { +        0, +        -1, +        hl_groups[hl.attr], +        linenr - 1, +        hl.start, +        hl.final +      } +    end +  end + +  return table.concat(chars, '') +end + +local function highlight_man_page() +  local mod = vim.api.nvim_buf_get_option(0, "modifiable") +  vim.api.nvim_buf_set_option(0, "modifiable", true) + +  local lines = vim.api.nvim_buf_get_lines(0, 0, -1, false) +  for i, line in ipairs(lines) do +    lines[i] = highlight_line(line, i) +  end +  vim.api.nvim_buf_set_lines(0, 0, -1, false, lines) + +  for _, args in ipairs(buf_hls) do +    vim.api.nvim_buf_add_highlight(unpack(args)) +  end +  buf_hls = {} + +  vim.api.nvim_buf_set_option(0, "modifiable", mod) +end + +return { highlight_man_page = highlight_man_page } | 
