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
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
|
local buf_hls = {}
local unpack = table.unpack or unpack -- luacheck: ignore
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 }
|