-- 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