/
markup.lua
235 lines (212 loc) · 9.63 KB
/
markup.lua
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
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
-- Copyright 2011-16 Paul Kulchenko, ZeroBrane LLC
-- styles for comment markup
---------------------------------------------------------
local ide = ide
local MD_MARK_ITAL = '_' -- italic
local MD_MARK_BOLD = '**' -- bold
local MD_MARK_LINK = '[' -- link description start
local MD_MARK_LINZ = ']' -- link description end
local MD_MARK_LINA = '(' -- link URL start
local MD_MARK_LINT = ')' -- link URL end
local MD_MARK_HEAD = '#' -- header
local MD_MARK_CODE = '`' -- code
local MD_MARK_BOXD = '|' -- highlight
local MD_MARK_MARK = ' ' -- separator
local MD_LINK_NEWWINDOW = '+' -- indicator to open a new window for links
-- old versions of Scintilla had only 5-bit styles, so assign styles manually in those cases
local markup = {
[MD_MARK_BOXD] = {st=ide:AddStyle("markup.boxd", ide.STYLEMASK == 31 and 25 or nil), fg={127,0,127}, b=true},
[MD_MARK_CODE] = {st=ide:AddStyle("markup.code", ide.STYLEMASK == 31 and 26 or nil), fg={127,127,127}, fs=10},
[MD_MARK_HEAD] = {st=ide:AddStyle("markup.head", ide.STYLEMASK == 31 and 27 or nil), fn="Lucida Console", b=true},
[MD_MARK_LINK] = {st=ide:AddStyle("markup.link", ide.STYLEMASK == 31 and 28 or nil), u=true, hs={32,32,127}},
[MD_MARK_BOLD] = {st=ide:AddStyle("markup.bold", ide.STYLEMASK == 31 and 29 or nil), b=true},
[MD_MARK_ITAL] = {st=ide:AddStyle("markup.ital", ide.STYLEMASK == 31 and 30 or nil), i=true},
[MD_MARK_MARK] = {st=ide:AddStyle("markup.mark", ide.STYLEMASK == 31 and 31 or nil), v=false},
}
-- allow other editor features to recognize this special markup
function MarkupIsSpecial(style) return style == markup[MD_MARK_MARK].st end
function MarkupIsAny(style)
for _, mark in pairs(markup) do
if style == mark.st then return true end
end
return false
end
function MarkupAddStyles(styles)
local comment = styles.comment or {}
for key,value in pairs(markup) do
local style = styles[key] or {}
-- copy all style features by value
for feature in pairs(value) do
style[feature] = style[feature] or value[feature]
end
style.fg = style.fg or comment.fg
style.bg = style.bg or comment.bg
styles[key] = style
end
end
local q = EscapeMagic
local MD_MARK_PTRN = '' -- combination of all markup marks that can start styling
for key in pairs(markup) do
if key ~= MD_MARK_MARK then MD_MARK_PTRN = MD_MARK_PTRN .. q(key) end
end
MarkupAddStyles(ide.config.styles)
function MarkupHotspotClick(pos, editor)
-- check if this is "our" hotspot event
if bit.band(editor:GetStyleAt(pos),ide.STYLEMASK) ~= markup[MD_MARK_LINK].st then
-- not "our" style, so nothing to do for us here
return
end
local line = editor:LineFromPosition(pos)
local tx = editor:GetLineDyn(line)
pos = pos + #MD_MARK_LINK - editor:PositionFromLine(line) -- turn into relative position
-- extract the URL/command on the right side of the separator
local _,_,text = string.find(tx, q(MD_MARK_LINZ).."(%b"..MD_MARK_LINA..MD_MARK_LINT..")", pos)
if text then
text = text:gsub("^"..q(MD_MARK_LINA), ""):gsub(q(MD_MARK_LINT).."$", "")
local doc = ide:GetDocument(editor)
local filepath = doc and doc.filePath or ide:GetProject()
local _,_,http = string.find(text, [[^(https?:%S+)$]])
local _,_,command,code = string.find(text, [[^macro:(%w+)%((.*%S)%)$]])
if not command then _,_,command = string.find(text, [[^macro:(%w+)$]]) end
if command == 'shell' then
ShellExecuteCode(code)
elseif command == 'inline' then
ShellExecuteInline(code)
elseif command == 'run' then -- run the current file
ProjectRun()
elseif command == 'debug' then -- debug the current file
ProjectDebug()
elseif http then -- open the URL in a new browser window
wx.wxLaunchDefaultBrowser(http, 0)
elseif filepath then -- only check for saved files
-- check if requested to open in a new window
local newwindow = not doc or string.find(text, MD_LINK_NEWWINDOW, 1, true)
if newwindow then text = string.gsub(text, "^%" .. MD_LINK_NEWWINDOW, "") end
local filename = GetFullPathIfExists(
wx.wxFileName(filepath):GetPath(wx.wxPATH_GET_VOLUME), text)
if filename and
(newwindow or SaveModifiedDialog(editor, true) ~= wx.wxID_CANCEL) then
if not newwindow and ide.osname == 'Macintosh' then editor:GotoPos(0) end
LoadFile(filename,not newwindow and editor or nil,true)
end
end
end
return true
end
local function ismarkup (tx)
local start = 1
local marksep = "[%s!%?%.,;:%(%)]"
while true do
-- find a separator first
local st,_,sep,more = string.find(tx, "(["..MD_MARK_PTRN.."])(.)", start)
if not st then return end
-- check if this is a first character of a multi-character separator
if not markup[sep] then sep = sep .. (more or '') end
local s,e,cap
local qsep = q(sep)
local nonspace = "[^%s]"
if sep == MD_MARK_HEAD then
-- always search from the start of the line
-- [%w%p] set is needed to avoid continuing this markup to the next line
s,e,cap = string.find(tx,"^("..q(MD_MARK_HEAD)..".+[%w%p])")
elseif sep == MD_MARK_LINK then
-- allow everything based on balanced link separators
s,e,cap = string.find(tx,
"^(%b"..MD_MARK_LINK..MD_MARK_LINZ
.."%b"..MD_MARK_LINA..MD_MARK_LINT..")", st)
-- if either part of the link is empty `[]` or `()`, skip the match
if cap and cap:find("^"..q(MD_MARK_LINK..MD_MARK_LINZ))
or cap and cap:find(q(MD_MARK_LINA..MD_MARK_LINT).."$") then s = nil end
elseif markup[sep] then
-- try a single character first, then 2+ characters between separators;
-- this is to handle "`5` `6`" as two sequences, not one.
s,e,cap = string.find(tx,"^("..qsep..nonspace..qsep..")".."%f"..marksep, st)
if not s then s,e,cap = string.find(tx,"^("..qsep..nonspace..".-"..nonspace..qsep..")".."%f"..marksep, st) end
end
if s and -- selected markup is surrounded by spaces or punctuation marks
(s == 1 or tx:sub(s-1, s-1):match(marksep)) and
(e == #tx or tx:sub(e+1, e+1):match(marksep))
then return s,e,cap,sep end
start = st+1
end
end
function MarkupStyle(editor, lines, linee)
local lines = lines or 0
if (lines < 0) then return end
-- if the current spec doesn't have any comments, nothing to style
if not next(editor.spec.iscomment) then return end
-- always style to the end as there may be comments that need re-styling
-- technically, this should be GetLineCount()-1, but we want to style
-- beyond the last line to make sure it is styled correctly
local linec = editor:GetLineCount()
local linee = linee or linec
local linecomment = editor.spec.linecomment
local iscomment = {}
for i,v in pairs(editor.spec.iscomment) do
iscomment[i] = v
end
local es = editor:GetEndStyled()
local needfix = false
for line=lines,linee do
local tx = editor:GetLineDyn(line)
local ls = editor:PositionFromLine(line)
local from = 1
local off = -1
-- doing WrapCount(line) when line == linec (which may be beyond
-- the last line) occasionally crashes the application on OSX.
local wrapped = line < linec and editor:WrapCount(line) or 0
while from do
tx = string.sub(tx,from)
local f,t,w,mark = ismarkup(tx)
if (f) then
local p = ls+f+off
local s = bit.band(editor:GetStyleAt(p), ide.STYLEMASK)
-- only style comments and only those that are not at the beginning
-- of the file to avoid styling shebang (#!) lines
-- also ignore matches for line comments (as defined in the spec)
if iscomment[s] and p > 0 and mark ~= linecomment then
local smark = #mark
local emark = #mark -- assumes end mark is the same length as start mark
if mark == MD_MARK_HEAD then
-- grab multiple MD_MARK_HEAD if present
local _,_,full = string.find(w,"^("..q(MD_MARK_HEAD).."+)")
smark,emark = #full,0
elseif mark == MD_MARK_LINK then
local lsep = w:find(q(MD_MARK_LINZ)..q(MD_MARK_LINA))
if lsep then emark = #w-lsep+#MD_MARK_LINT end
end
local sp = bit.band(editor:GetStyleAt(p-1), ide.STYLEMASK) -- previous position style
if mark == MD_MARK_HEAD and not iscomment[sp] then
p = p + 1
smark = smark - 1
end
-- StartStyling deprecated the second parameter, but since a version check
-- is not available for Scintilla, we check for INDIC0_MASK, which was
-- removed in the same wxwidgets commit
editor:StartStyling(p, wxstc.wxSTC_INDIC0_MASK and ide.STYLEMASK or 0)
editor:SetStyling(smark, markup[MD_MARK_MARK].st)
editor:SetStyling(t-f+1-smark-emark, markup[mark].st or markup[MD_MARK_MARK].st)
editor:SetStyling(emark, markup[MD_MARK_MARK].st)
end
off = off + t
end
from = t and (t+1)
end
-- has this line changed its wrapping because of invisible styling?
if wrapped > 1 and editor:WrapCount(line) < wrapped then needfix = true end
end
editor:StartStyling(es, wxstc.wxSTC_INDIC0_MASK and ide.STYLEMASK or 0)
-- if any wrapped lines have changed, then reset WrapMode to fix the drawing
if needfix then
-- this fixes an issue with duplicate lines in Scintilla when
-- invisible styles hide some of the content that would be wrapped.
local wrapmode = editor:GetWrapMode()
if wrapmode ~= wxstc.wxSTC_WRAP_NONE then
-- change the wrap mode to force recalculation
editor:SetWrapMode(wxstc.wxSTC_WRAP_NONE)
editor:SetWrapMode(wrapmode)
end
-- if some of the lines have folded, this can make not styled lines visible
MarkupStyle(editor, linee+1) -- style to the end in this case
end
end