aboutsummaryrefslogtreecommitdiff
path: root/runtime/lua/man.lua
blob: c3d7df2a2ac4912c47768a71d94b649c4a1da0fa (plain) (blame)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
local function highlight_formatted(line, linenr)
  local chars = {}
  local prev_char = ''
  local overstrike, escape = false, false
  local hls = {} -- Store highlight groups as { attr, start, end }
  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[1] == attr and hl[3] == -1 then
        hl[3] = byte
        hls[i] = hl
      end
    end
  end

  local function add_attr_hl(code)
    local on = true
    if code == 0 then
      attr = NONE
      on = false
    elseif code == 1 then
      attr = BOLD
    elseif code == 21 or code == 22 then
      attr = BOLD
      on = false
    elseif code == 3 then
      attr = ITALIC
    elseif code == 23 then
      attr = ITALIC
      on = false
    elseif code == 4 then
      attr = UNDERLINE
    elseif code == 24 then
      attr = UNDERLINE
      on = false
    else
      attr = NONE
      return
    end

    if on then
      hls[#hls + 1] = {attr, byte, -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 characters
  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[3] == 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[1] == attr and last_hl[3] == byte then
        last_hl[3] = byte + #char
      else
        hls[#hls + 1] = {attr, byte, 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
      local sgr = prev_char:match("^%[([\020-\063]*)m$")
      if sgr then
        local match = ''
        while sgr and #sgr > 0 do
          match, sgr = sgr:match("^(%d*);?(.*)")
          add_attr_hl(match + 0) -- coerce to number
        end
        escape = false
      elseif not prev_char:match("^%[[\020-\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 i, hl in ipairs(hls) do
    if hl[1] ~= NONE then
      vim.api.nvim_buf_add_highlight(
        0,
        -1,
        hl_groups[hl[1]],
        linenr - 1,
        hl[2],
        hl[3]
      )
    end
  end

  return table.concat(chars, '')
end

return { highlight_formatted = highlight_formatted }