Skip to content

Commit f70ab9c

Browse files
committed
Updated Hyperlink_URL2 plugin as per author's changes
1 parent b8f6bd4 commit f70ab9c

File tree

1 file changed

+187
-80
lines changed

1 file changed

+187
-80
lines changed

plugins/Hyperlink_URL2.xml

Lines changed: 187 additions & 80 deletions
Original file line numberDiff line numberDiff line change
@@ -1,23 +1,22 @@
11
<?xml version="1.0" encoding="iso-8859-1"?>
22
<!DOCTYPE muclient>
3-
<!-- Saved on Saturday, 1 April 2006, 12:29 PM -->
4-
<!-- MuClient version 3.73 -->
3+
<!-- Saved on Wednesday, 22 December 2010, 4:25 PM -->
4+
<!-- MuClient version 4.61 -->
55
<muclient>
66
<plugin
77
name="Hyperlink_URL2"
88
author="Sketch"
99
id="520bc4f29806f7af0017985f"
1010
language="Lua"
11-
purpose="Makes URLs on a line into hyperlinks."
11+
purpose="Changes URLs on a line into hyperlinks."
1212
date_written="2006-04-01"
13-
requires="3.72"
14-
version="1.0">
13+
date_modified="2010-12-22"
14+
requires="4.42"
15+
version="3.1">
1516

1617
<description trim="y">
17-
1818
<![CDATA[
19-
Detects text starting with HTTP:xxx and makes that part into a hyperlink.
20-
Limits: HTTP:// and the following character must be the same color
19+
Detects URLs and turns them into hyperlinks.
2120
]]>
2221
</description>
2322

@@ -28,11 +27,11 @@ Limits: HTTP:// and the following character must be the same color
2827
<triggers>
2928
<trigger
3029
enabled="y"
31-
match="(.*)((https?|mailto)://(?:[\w\d\.\?\/\%#@!&quot;&amp;_]+[/\w\d#]))(.*)$"
30+
match="(?:https?://|mailto:)\S*[\w/=@#\-\?]"
3231
omit_from_output="y"
3332
ignore_case="y"
3433
regexp="y"
35-
script="OnHyperlink"
34+
script="onURL"
3635
sequence="100"
3736
>
3837
</trigger>
@@ -41,75 +40,183 @@ Limits: HTTP:// and the following character must be the same color
4140
<!-- Script -->
4241
<script>
4342
<![CDATA[
44-
function OnHyperlink (name, line, wildcards, styles)
45-
46-
hyperlinks = {}
47-
newstyle = {}
48-
i = 1
49-
hyperlinkcount = 0
50-
doingURL = 0
51-
while i <= table.getn(styles) do -- Doesn't use pairs() because of problems with field-injection.
52-
if doingURL == 0 then
53-
-- **** Not a URL **** --
54-
cut = string.find(styles[i].text, "([hHtTpPsSmMaAiIlLoO]+://[%S]*[%w#/])")
55-
if cut == nil then -- If there's nothing to cut, copy the whole line
56-
table.insert(newstyle, {textcolour = styles[i].textcolour
57-
,backcolour = styles[i].backcolour
58-
,style = styles[i].style
59-
,text = styles[i].text})
60-
else
61-
table.insert(newstyle, {textcolour = styles[i].textcolour
62-
,backcolour = styles[i].backcolour
63-
,style = styles[i].style
64-
,text = string.sub(styles[i].text, 1, cut - 1)})
65-
table.insert(styles, i + 1, {textcolour = styles[i].textcolour
66-
,backcolour = styles[i].backcolour
67-
,style = styles[i].style
68-
,text = string.sub(styles[i].text, cut)})
69-
doingURL = 1
70-
hyperlinkcount = hyperlinkcount + 1
71-
end -- if
72-
else -- **** IS a URL **** --
73-
-- Search for a URL. If the string is completely a URL...
74-
-- Jump to the next table field. And keep doing such.
75-
cut, length, temp = string.find(styles[i].text, "^([%S]*[%w#/])")
76-
if cut ~= nil then
77-
if hyperlinks[hyperlinkcount] ~= nil then
78-
hyperlinks[hyperlinkcount] = hyperlinks[hyperlinkcount] .. temp
79-
else
80-
hyperlinks[hyperlinkcount] = temp
81-
end -- if
82-
table.insert(newstyle, {textcolour = styles[i].textcolour
83-
,backcolour = styles[i].backcolour
84-
,style = styles[i].style
85-
,text = string.sub(styles[i].text, 1, length)
86-
,hypernumber = hyperlinkcount})
87-
styles[i].text = string.sub(styles[i].text, length + 1)
88-
if styles[i].text ~= "" then
89-
i = i - 1 -- The first hyperlink was cut, so scan the same field for more.
90-
doingURL = 0
91-
else
92-
doingURL = 1
93-
end
94-
else
95-
doingURL = 0
96-
i = i - 1
97-
end -- if (cut)
98-
end -- if (doingURL)
99-
i = i + 1
100-
end -- while
101-
102-
for x, y in pairs (newstyle) do -- x is the style number, y is the style-data table.
103-
NoteStyle (y.style)
104-
if y.hypernumber ~= nil then
105-
Hyperlink(hyperlinks[y.hypernumber], y.text, "Go to " .. hyperlinks[y.hypernumber]
106-
, RGBColourToName(y.textcolour), RGBColourToName(y.backcolour), 1)
107-
else
108-
ColourTell (RGBColourToName(y.textcolour), RGBColourToName(y.backcolour), y.text)
109-
end
110-
end -- while
111-
Note ("") -- Insert a true newline at the end of the string.
112-
end -- of hyperlink
43+
--[[
44+
-- What it is --
45+
A URL hyperlinker for MUSHclient.
46+
47+
-- Why it's needed --
48+
We want to hyperlink all the URLs in a line, while preserving the original style and color of the line. However, due to how wildcard matching and triggers work, we can't make a trigger to match each URL on a line and hyperlink it.
49+
What do we do?
50+
51+
-- How it works --
52+
MUSHclient provides the plaintext of the whole line to scripts called from triggers, as the argument "line". MUSHclient also provides an array called "styles" to Lua scripts called from triggers. The styles array contains a structure (or "dictionary") for each styled segment of the whole line the trigger matched. The structure is: {textcolour, backcolour, style, text, length} where textcolour and backcolour are the foreground/background color, style is a set of OR-ed flags bold=1, underline=2, blink=4, text is the text in that segment, and length is the length of the segment. It's worth noting that the 'styles' array is contiguous, and the segments it contains span the whole line. Even segments with no special styling are included, and each style begins immediately after the previous one ends.
53+
Using these two arguments, we can compose a line with all URLs hyperlinked, while preserving colors and other formatting.
54+
55+
Short breakdown:
56+
The script takes a matched trigger line and breaks it up in two different ways: An array of segments based on styling is made, and an array of segments based on URLs is made. Using those two arrays, the segments of the line are pieced back together, placing the styling on the hyperlinks.
57+
58+
Long breakdown: (function names in parentheses)
59+
1) Trigger on a line that contains at least one URL-like item. The 'styles' array is passed into the script, as well as the plaintext line.
60+
61+
2) Find all the URLs in the plaintext line, number them, and record their start/end points and text. We now have an array of (to-be) hyperlinks numbered 1-N, with start/end positions for each.
62+
(findURLs)
63+
64+
3) Merge the start and end points of all the styles and hyperlinks into a sorted set. We now have a list of all points where either or both occurs:
65+
* A style changes (even to/from 'no styling').
66+
* A hyperlink begins or ends.
67+
(getpoints)
68+
69+
4) Convert the start and end points into 'slices' of the text line. Disregarding the text itself, each of these slices is homogenous: the style is the same through the whole slice, and the slice is either not part of any hyperlink, or is part of only one hyperlink.
70+
(getslices)
71+
72+
5) Iterate over the slices, recording style/hyperlink number/text for each slice
73+
into an array named 'reformatted'.
74+
(reformat)
75+
76+
6) Iterate over the 'reformatted' array. If the slice contains part of a hyperlink,
77+
print it as such. Although the slice may contain only part of a hyperlink, the
78+
whole URL was stored in the 'hyperlinks' array (in step 2), and can be looked
79+
up by the slice's stored hyperlink number.
80+
81+
You now have a line with detected URLs changed into hyperlinks, and the original styling preserved.
82+
--]]
83+
84+
-- CODE SECTION --
85+
86+
-- Returns an array {start, end, text}
87+
function findURLs(text)
88+
local URLs = {}
89+
local start, position = 0, 0
90+
-- "rex" is a table supplied by MUSHclient for PCRE functionality.
91+
local re = rex.new("(?:https?://|mailto:)\\S*[\\w/=@#\\-\\?]")
92+
re:gmatch(text,
93+
function (link, _)
94+
start, position = string.find(text, link, position, true)
95+
table.insert(URLs, {start=start, stop=position, text=link})
96+
end
97+
)
98+
return URLs
99+
end -- function findURL
100+
101+
-- Returns a table of points where formatting should change in the new string.
102+
function getpoints(styles, hyperlinks)
103+
local points = {}
104+
local unique_points = {}
105+
local pos = 1
106+
for _,v in pairs(styles) do
107+
table.insert(points, pos)
108+
table.insert(points, pos + v.length)
109+
pos = pos + v.length
110+
end
111+
-- The last value of points is now 1 past the end of the string.
112+
113+
for _,v in pairs(hyperlinks) do
114+
table.insert(points, v.start)
115+
table.insert(points, v.stop + 1)
116+
-- The hyperlink itself is at v.stop. v.stop+1 is where the change is.
117+
end
118+
119+
table.sort(points)
120+
return unique_array(points)
121+
end -- function getpoints
122+
123+
-- Returns an array with consecutive duplicate items removed.
124+
function unique_array(a)
125+
local uniq, current = {}, nil
126+
for _,v in pairs(a) do
127+
if v ~= current then
128+
table.insert(uniq, v)
129+
current = v
130+
end
131+
end
132+
return uniq
133+
end -- function unique_array
134+
135+
-- Given an array of numbers, return an array of pairs, to be used in string.sub().
136+
-- Each pair starts at the original array's key, and ends before the next key.
137+
-- Example: [1, 5, 9, 13] --> [{1,4},{5,8},{9,12}]
138+
function getslices(points)
139+
local newpoints = {}
140+
for i = 1, #points - 1, 1 do
141+
table.insert(newpoints, {start=points[i], stop=points[i+1] - 1})
142+
end
143+
return newpoints
144+
end -- function getslices
145+
146+
-- Returns an array of
147+
-- {startpos, endpos, textcolour, backcolour, style, hyperlink_number}
148+
function reformat(slices, styles, hyperlinks)
149+
local styles_i, hyperlinks_i = 1, 1
150+
local hyperlink_number = 0
151+
local reformatted = {}
152+
-- nextstyle is set at the character where the next style begins.
153+
local nextstyle = styles[1].length + 1
154+
155+
-- Add a fake hyperlink past the end of the string at the end of the array.
156+
-- This way, we don't have to keep checking (hyperlinks_i > #hyperlinks).
157+
table.insert(hyperlinks,{start=slices[#slices].stop + 1,stop=slices[#slices].stop + 1,text=""})
158+
159+
for _,v in pairs(slices) do
160+
161+
-- v.start is our 'current position'.
162+
-- Make sure we're using the correct style
163+
if v.start >= nextstyle then
164+
nextstyle = v.start + styles[styles_i + 1].length
165+
styles_i = styles_i + 1
166+
end
167+
168+
-- If we've passed the hyperlink marked by hyperlinks_i, increment it.
169+
if v.start > hyperlinks[hyperlinks_i].stop then
170+
hyperlinks_i = hyperlinks_i + 1
171+
end
172+
173+
-- The hyperlink_number is set to the hyperlink we're checking for if
174+
-- we're at or past its starting position. (In other words, inside it)
175+
if v.start >= hyperlinks[hyperlinks_i].start then
176+
hyperlink_number = hyperlinks_i
177+
else
178+
hyperlink_number = 0
179+
end
180+
181+
table.insert(reformatted,
182+
{startpoint = v.start
183+
,endpoint = v.stop
184+
,textcolour = styles[styles_i].textcolour
185+
,backcolour = styles[styles_i].backcolour
186+
,style = styles[styles_i].style
187+
,hyperlink_number = hyperlink_number}
188+
)
189+
end
190+
return reformatted
191+
end -- function reformat
192+
193+
-- Line: Whole line that contains the trigger, in plaintext.
194+
-- Styles: [{textcolour, backcolour, text, length, style},...]
195+
function onURL (name, line, wildcards, styles)
196+
local hyperlinks = findURLs(line)
197+
local reformatted = reformat(getslices(getpoints(styles,hyperlinks)), styles, hyperlinks)
198+
199+
for _,v in pairs(reformatted) do
200+
NoteStyle(v.style) -- Set style for the segment, regardless of type.
201+
if v.hyperlink_number ~= 0 then
202+
Hyperlink(
203+
hyperlinks[v.hyperlink_number].text -- Hyperlink
204+
,string.sub(line, v.startpoint, v.endpoint) -- Displayed text
205+
,"Go to " .. hyperlinks[v.hyperlink_number].text -- Hover text
206+
,RGBColourToName(v.textcolour) -- Foreground color
207+
,RGBColourToName(v.backcolour) -- Background color
208+
,1 -- Boolean: Open as a URL?
209+
)
210+
else
211+
ColourTell(
212+
RGBColourToName(v.textcolour) -- Foreground color
213+
,RGBColourToName(v.backcolour) -- Background color
214+
,string.sub(line, v.startpoint, v. endpoint) -- Displayed text
215+
)
216+
end
217+
end
218+
Note ("") -- Insert a newline at the end of the string.
219+
end -- function onURL
113220
]]>
114221
</script>
115-
</muclient>
222+
</muclient>

0 commit comments

Comments
 (0)