----------------------------------------------------------------------- -- Exporter framework for SciTE -- Version 1.1.5, 20070805 -- -- Copyright 2004-2007 by Kein-Hong Man -- All Rights Reserved -- -- Permission to use, copy, modify, and distribute this software and -- its documentation for any purpose and without fee is hereby granted, -- provided that the above copyright notice appear in all copies and -- that both that copyright notice and this permission notice appear -- in supporting documentation. -- -- KEIN-HONG MAN DISCLAIMS ALL WARRANTIES WITH REGARD TO THIS SOFTWARE, -- INCLUDING ALL IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS, IN -- NO EVENT SHALL KEIN-HONG MAN BE LIABLE FOR ANY SPECIAL, INDIRECT OR -- CONSEQUENTIAL DAMAGES OR ANY DAMAGES WHATSOEVER RESULTING FROM LOSS -- OF USE, DATA OR PROFITS, WHETHER IN AN ACTION OF CONTRACT, -- NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF OR IN CONNECTION -- WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. -- ----------------------------------------------------------------------- -- USAGE -- -- * load this script, for example: -- dofile(props["SciteUserHome"].."/SciTE_ExportBase.lua") -- * the exporter code should be in a function executed via the Tools -- menu or via a hotkey -- * initialize style manager using the current lexer number: -- stylemgr:setlexer(editor.Lexer) -- * styles are in the form of a table (see the next section) -- * if you want to build style tables on-the-fly, do: -- t = stylemgr:locate(2) -- t is style table -- * or, -- stylemgr:locate(2) -- t = stylemgr.styles[2] -- same table -- * the global default is always created upon initialization: -- stylemgr.basestyles[32] -- * the lexer default is always created upon initialization: -- stylemgr.styles[32] -- * you can also set all styles using a loop at the start -- * stylemgr:retrieve() is an internal function, but it can be used -- if you want non-standard inheritance of styles -- * there is a global property to control feedback from Lua exporters, -- to disable feedback (usually displayed in the output window), set -- the following property to 0: -- ext.lua.exporters.verbose=1 -- * canonical set of SciTE exporters successfully constructed ----------------------------------------------------------------------- -- STYLE TABLE -- -- * all fields are always filled in -- * style table elements and their types for table t are: -- t.font (string) -- t.size (number) -- t.case (string: m, u or l) -- t.fore (table: elements r, g, b (numbers)) -- t.back (table: elements r, g, b (numbers)) -- t.italics (boolean) -- t.bold (boolean) -- t.eolfilled (boolean) -- t.underlined (boolean) -- t.visible (boolean) -- t.changeable (boolean) -- t.specified (string) -- * the specified field is string of concatenated field names of a -- particular style that overrides the default lexer style -- * I don't know what some fields do, they may be internal, but I -- have included everything for completeness' sake -- * fields and colours can be accessed in many ways: -- t.font or t["font"] -- t.fore.r or t["fore"].r or t.fore["r"] ----------------------------------------------------------------------- -- TESTING -- -- * to display everything, use something like this in a function: -- editor:AddText(stylemgr:showeverything()) -- * to display one lexer's styles, use something like this: -- editor:AddText(stylemgr:showlexer("perl")) -- * to display a single style, initialize first: -- stylemgr:setlexerlanguage("null") -- * then the following displays the default global style: -- editor:AddText(stylemgr:showstyle(stylemgr.basestyles[32]).."\n") -- * and the following displays the default lexer style: -- editor:AddText(stylemgr:showstyle(stylemgr:locate(32)).."\n") ----------------------------------------------------------------------- -- TODO AND DEV NOTES -- -- * style inheritance needs more testing, not sure about styles 33-37 -- * editor.CharAt[pos] returns a signed char value, -128 to +127 -- * everything flagged for self.styles[32].specified, see below -- * for now, lexer 0 (SCLEX_CONTAINER), just copy over base style -- (this is the lexer number used for basic text) -- * exportutil.progress doesn't work (see note below) ----------------------------------------------------------------------- local string = string ----------------------------------------------------------------------- -- a simple check to alert of namespace collision ----------------------------------------------------------------------- if exportutil then error("SciTE_ExportBase: exportutil already defined") end if stylemgr then error("SciTE_ExportBase: stylemgr already defined") end exportutil = {} stylemgr = {} ----------------------------------------------------------------------- -- exportutil variables and constants -- all the STYLE_* constants can be accessed directly too by the Lua -- extension; not really a big deal as they are unlikely to change ----------------------------------------------------------------------- exportutil.STYLE_DEFAULT = 32 exportutil.STYLE_LINENUMBER = 33 exportutil.STYLE_BRACELIGHT = 34 exportutil.STYLE_BRACEBAD = 35 exportutil.STYLE_CONTROLCHAR = 36 exportutil.STYLE_INDENTGUIDE = 37 exportutil.STYLE_CALLTIP = 38 exportutil.STYLE_LASTPREDEFINED = 39 exportutil.STYLE_MAX = 127 exportutil.STYLE_MOD_MASK = 128 exportutil.VERBOSE = tonumber(props["ext.lua.exporters.verbose"]) or 1 ----------------------------------------------------------------------- -- exportutil.exportfile -- filedir part of file's path without the file.ext part -- basename filename without the extension -- newext extension the exporter is using -- returns an acceptable filename for use by exporter -- -- Returns a filename for an exporter to write. If the filename has -- been taken, then it adds a number in parentheses to differentiate -- the latest file. The number is limited to 999 or else the function -- runs the risk of running for too long. The '/' directory separator -- appears to be recognized properly. Usage example: -- -- fn = exportutil.exportfile(props["FileDir"], props["FileName"], "pdf") -- local handle = io.open(fn, "wb") -- blah blah blah... -- ----------------------------------------------------------------------- function exportutil.exportfile(filedir, basename, newext) if not basename or basename == "" then basename = "untitled" end -- tries to open a file to see if it exists local exists = function(filename) local h = io.open(filename, "rb") if h then io.close(h) return true else return false end end fn = filedir.."/"..basename.."."..newext if exists(fn) then -- limit the range so that it doesn't run forever for i = 1,999 do fn = filedir.."/"..basename.."("..i..")."..newext if not exists(fn) then return fn end end -- throw an error if all tested filenames exists error("exportutil.exportfile: filename creation problem") end return fn end ----------------------------------------------------------------------- -- exportutil.progress -- status a string to be display in the output window -- -- Replaces the current line in the output window. Used to indicate -- processing progress, if the document is large or the script is slow. -- [20040828] Actually, this doesn't work because SciTE seem to refresh -- the output pane only *after* the script is complete. -- ----------------------------------------------------------------------- function exportutil.progress(status) if not status then return end local ln = output:LineFromPosition(output.CurrentPos) output.TargetStart = output:PositionFromLine(ln) output.TargetEnd = output.LineEndPosition[ln] output:ReplaceTarget(status) end ----------------------------------------------------------------------- -- exportutil.CharAt -- pos position of character -- returns 1-character string ("\0" if out of range) -- -- exportutil.StyleAt -- pos position of character -- returns style number (0 to exportutil.STYLE_MAX) -- -- CharAt and StyleAt works much like the simpler table indexing form, -- only that CharAt returns a string so that you don't have to compare -- numbers, while StyleAt masks off non-style bits for safety. -- ----------------------------------------------------------------------- function exportutil.CharAt(pos) local c = editor.CharAt[pos] if c < 0 then c = c + 256 end return string.char(c) end function exportutil.StyleAt(pos) return math.mod(editor.StyleAt[pos], exportutil.STYLE_MOD_MASK) end ----------------------------------------------------------------------- -- exportutil.propbool -- property property name to be looked up -- default if non-zero, default is true, otherwise false -- returns boolean version of property value -- -- Convenience function to handle 0/1-style boolean properties. -- ----------------------------------------------------------------------- function exportutil.propbool(property, default) if not default then default = 0 end local n = tonumber(props[property]) or default if n ~= 0 then return true else return false end end ----------------------------------------------------------------------- -- exportutil.GetSelPos -- returns start position of selected text, or 0 -- returns end position of selected text, or doc length -- -- Returns starting and ending positions of the selected text, in -- proper order to be used by exporters. If no text is selected, then -- 0 and the document's length is returned. -- ----------------------------------------------------------------------- function exportutil.GetSelPos() local selBeg = editor.Anchor local selEnd = editor.CurrentPos if selBeg == selEnd then return 0, editor.Length end if selBeg > selEnd then selBeg, selEnd = selEnd, selBeg end return selBeg, selEnd end ----------------------------------------------------------------------- -- stylemgr variables and tables ----------------------------------------------------------------------- -- base style from which style.*.32 inherits. Absolute base. -- (Slightly different from style initialization in the C++ sources.) ----------------------------------------------------------------------- stylemgr.absolutebase = { fore = { r = 0, g = 0, b = 0 }, back = { r = 255, g = 255, b = 255 }, font = "Verdana", size = 10, case = "m", bold = false, italics = false, eolfilled = false, underlined = false, visible = true, changeable = true, } ----------------------------------------------------------------------- -- list of lexers by name (as of Scintilla CVS 20070714, version 1.74) -- * the only lexer with the XYZ in its SCLEX_XYZ different from the -- lexer name string is SCLEX_PROPERTIES -> "props" -- * deprecated lexers are noted below; the entries have been removed -- * appended recent additions, old lexer list has not been re-checked ----------------------------------------------------------------------- stylemgr.lexerlanguage = { -- SCLEX_CONTAINER=0 -- SCLEX_XCODE=13 -- deprecated -- SCLEX_ASP=29 -- deprecated -- SCLEX_PHP=30 -- deprecated "null", "python", "cpp", "hypertext", "xml", -- 01-05 "perl", "sql", "vb", "props", "errorlist", -- 06-10 "makefile", "batch", "", "latex", "lua", -- 11-15 "diff", "conf", "pascal", "ave", "ada", -- 16-20 "lisp", "ruby", "eiffel", "eiffelkw", "tcl", -- 21-25 "nncrontab", "bullant", "vbscript", "", "", -- 26-30 "baan", "matlab", "scriptol", "asm", "cppnocase", -- 31-35 "fortran", "f77", "css", "pov", "lout", -- 36-40 "escript", "ps", "nsis", "mmixal", "clarion", -- 41-45 "clarionnocase", "lot", "yaml", "tex", "metapost", -- 46-50 "powerbasic", "forth", "erlang", "octave", "mssql", -- 51-55 "verilog", "kix", "gui4cli", "specman", "au3", -- 56-60 "apdl", "bash", "asn1", "vhdl", "caml", -- 61-65 "blitzbasic", "purebasic", "haskell", "phpscript", "tads3", -- 66-70 "rebol", "smalltalk", "flagship", "csound", "freebasic", -- 71-75 "inno", "opal", "spice", "d", "cmake", -- 76-80 "gap", "PL/M", "progress", "abaqus", -- 81-84 --SCLEX_AUTOMATIC=1000 } stylemgr.MAXLEXER = #stylemgr.lexerlanguage ----------------------------------------------------------------------- -- variables and constants ----------------------------------------------------------------------- stylemgr.lexer = 1 -- current lexer set, defaults to "null" stylemgr.styles = {} -- current lexer style table stylemgr.basestyles = {} -- table for base styles, "style.*.#" -- fields that are collected, the rest are ignored stylemgr.FIELDS1 = "font|fore|back|size|case|" stylemgr.FIELDS2 = "italics|bold|eolfilled|underlined|visible|changeable" ----------------------------------------------------------------------- -- stylemgr:setlexer -- lexer number of lexer whose style is to be used -- -- stylemgr:setlexerlanguage -- lexerlanguage name of lexer whose style is to be used -- -- stylemgr:getlexer -- returns number of lexer whose style is to be used -- -- stylemgr:getlexerlanguage -- returns name of lexer whose style is to be used -- -- -- Use the set functions to properly initialize the style manager. -- Any time the current lexer is set, the style table is reset and the -- default style (style.LEXER.32) is preloaded. Throws an error if an -- invalid lexer number is requested. Does not understand -- SCLEX_AUTOMATIC (1000), basic text is SCLEX_CONTAINER (0). -- ----------------------------------------------------------------------- function stylemgr:setlexer(lexer) -- basic text is CONTAINER, AUTOMATIC unsupported if lexer == nil then self:initlexer(self.lexer) return end if lexer < 0 or lexer > self.MAXLEXER then error("stylemgr:setlexer: lexer number out of range") end self:initlexer(lexer) end function stylemgr:getlexer() return self.lexer end function stylemgr:setlexerlanguage(lexerlanguage) if lexerlanguage == nil then self:initlexer(self.lexer) return end for i,v in ipairs(self.lexerlanguage) do if lexerlanguage == v then self:initlexer(i) return end end error("stylemgr:setlexerlanguage: unidentified lexer name") end function stylemgr:getlexerlanguage() return self.lexerlanguage[self.lexer] end ----------------------------------------------------------------------- -- stylemgr:getpropname -- returns property name string to lookup using prop[] -- -- Builds a correct style property name. -- ----------------------------------------------------------------------- function stylemgr:getpropname(n) return "style."..self:getlexerlanguage().."."..n end ----------------------------------------------------------------------- -- stylemgr:initlexer -- lexer number of lexer whose style is to be used -- -- Select a lexer for which to lookup styles. For lexer number -- validation, use the above functions instead. Creates the default -- styles, so that other styles can be inherit fields from them. If -- lexer is 0 (container), only styles 32-37 are significant, and are -- duplicates of the base styles. -- ----------------------------------------------------------------------- function stylemgr:initlexer(lexer) local propvalue self.lexer = lexer self.styles = {} if not self.basestyles[32] then -- initialize style.*.32, defaults inherited from absolutebase propvalue = props["style.*.32"] self.basestyles[32] = self:retrieve(propvalue, self.absolutebase) end if lexer > 0 then propvalue = props[self:getpropname(32)] -- initialize style.LEXER.32, defaults inherited from style.*.32 self.styles[32] = self:retrieve(propvalue, self.basestyles[32]) -- for base style, all property fields are specified self.styles[32].specified = self.FIELDS1..self.FIELDS2 else self.styles[32] = self.basestyles[32] end for i = 33, 37 do if not self.basestyles[i] then -- initialize style.*.i, defaults inherited from style.*.32 propvalue = props["style.*."..i] self.basestyles[i] = self:retrieve(propvalue, self.basestyles[32]) end if lexer > 0 then propvalue = props[self:getpropname(i)] -- initialize style.LEXER.i, defaults inherited from style.*.i self.styles[i] = self:retrieve(propvalue, self.basestyles[i]) else self.styles[i] = self.basestyles[i] end end end ----------------------------------------------------------------------- -- stylemgr:locate -- n style number -- returns table containing parsed style information -- -- Returns the style table given style number n. The current lexer -- must have been set first. If the style table has not been created -- then it calls stylemgr:retrieve to try build the style table. -- ----------------------------------------------------------------------- function stylemgr:locate(n) local style = self.styles[n] if not style then if n == 32 then return style elseif self.lexer > 0 then local propvalue = props[self:getpropname(n)] -- initialize style.LEXER.n, defaults inherited from style.LEXER.32 self.styles[n] = self:retrieve(propvalue) else -- inherit scheme for basic unlexed text self.styles[n] = self:retrieve("") end style = self.styles[n] end -- style available return style end ----------------------------------------------------------------------- -- stylemgr:retrieve -- property property value from prop[] lookup to be parsed -- base table from which to inherit unchanged styles -- returns table containing parsed style information -- -- Given property information (not the property name) and an optional -- base style table to inherit from, builds a style table. If base -- style table is not specified, the base style is taken from -- stylemgr.styles[32] ("style.LEXER.32"). Always returns a valid -- and complete table result because there is a chain of inheritance. -- Checks property value formats, ignores unrecognized stuff. -- ----------------------------------------------------------------------- function stylemgr:retrieve(property, base) local t = {} if not property then property = "" end -- inherit from current default style (style.LEXER.32) if required if not base then base = self.styles[32] end local x, y, field, value -- parse style properties for v in string.gfind(property, "([^,]+),?") do if string.find(v, ":") then ----------------------------------------------------------- -- "field:value" format ----------------------------------------------------------- x, y, field, value = string.find(v, "^([a-z]+):([^:]+)$") if x and string.find(self.FIELDS1, field) then if field == "fore" or field == "back" then if string.find(value, "^#%x%x%x%x%x%x$") then local c = {} c["r"] = tonumber(string.sub(value, 2, 3), 16) c["g"] = tonumber(string.sub(value, 4, 5), 16) c["b"] = tonumber(string.sub(value, 6, 7), 16) value = c else value = nil end elseif field == "font" then if not string.find(value, "^!?[%w%s]+$") then value = nil end elseif field == "size" then if string.find(value, "^%d+$") then value = tonumber(value) else value = nil end elseif field == "case" then if not string.find(value, "^[mul]$") then value = nil end end--if field else value = nil -- unrecognized field name end--if x else ----------------------------------------------------------- -- "field" format ----------------------------------------------------------- x, y, field = string.find(v, "^([a-z]+)$") if x then value = true if string.find(field, "^not[a-z]+$") then field = string.sub(field, 4) value = false end if not string.find(self.FIELDS2, field) then value = nil -- unrecognized field name end else value = nil -- unrecognized field name end--if x end--if v if value ~= nil then -- set valid field t[field] = value end end -- fill in "specified" field local specified = "" for i, v in pairs(t) do specified = specified..i.." " end t.specified = specified -- fill in non-specified fields with the base style for v in string.gfind(self.FIELDS1..self.FIELDS2, "([a-z]+)") do if t[v] == nil then t[v] = base[v] end end return t end ----------------------------------------------------------------------- -- stylemgr:hexstr -- colour table with numerical colour components -- returns colour formatted in #RRGGBB string form -- -- Returns a colour specification formatted in #RRGGBB string form. -- Convenience function. -- ----------------------------------------------------------------------- function stylemgr:hexstr(colour) local function hex(c) local x = string.format("%X", c) if c < 16 then x = "0"..x end return x end return "#"..hex(colour.r)..hex(colour.g)..hex(colour.b) end ----------------------------------------------------------------------- -- stylemgr:showstyle -- style style table to display -- returns text to be displayed -- -- Given a style table, returns a formatted string of style fields. -- Useful for testing. -- ----------------------------------------------------------------------- -- [[ function stylemgr:showstyle(style) local op = "" local showcolour = function(col) if col then return string.format("%X %X %X", col.r, col.g, col.b) else error("stylemgr:showstyle: unexpected nil colour value") end end op = op.."fore="..showcolour(style.fore).." " .."back="..showcolour(style.back).."\n" for v in string.gfind("font|size|case", "([a-z]+)") do if style[v] then op = op..v.."="..style[v].." " else error("stylemgr:showstyle: unexpected nil value for style."..v) end end op = op.."\n" for v in string.gfind(self.FIELDS2, "([a-z]+)") do local value = style[v] if value == nil then error("stylemgr:showstyle: unexpected nil value for style."..v) elseif value then value = "T" else value = "F" end op = op..v.."="..value.." " end op = op.."\nspecified='"..style.specified.."'\n\n" return op end --]] ----------------------------------------------------------------------- -- stylemgr:showlexer -- lexer lexer styles to display -- returns text to be displayed -- -- Given a lexer number or name, returns a formatted string of styles. -- Useful for testing. -- ----------------------------------------------------------------------- -- [[ function stylemgr:showlexer(lexer) local op = "" if type(lexer) == "number" then self:setlexer(lexer) else self:setlexerlanguage(lexer) end for i = 0,32 do local propname = self:getpropname(i) local propvalue = props[propname] op = op..propname.."="..propvalue.."\n" ..self:showstyle(self:locate(i)).."\n" end return op end --]] ----------------------------------------------------------------------- -- stylemgr:showeverything -- returns text to be displayed -- -- Returns a formatted string of most styles of all lexers. Useful for -- testing. Building everything takes 1-2 seconds on an Athlon 2500+. -- ----------------------------------------------------------------------- -- [[ function stylemgr:showeverything() local op = "" self:setlexer() op = op.."Style properties for style.&.32 -->\n" ..self:showstyle(self.basestyles[32]).."\n" for i = 1, self.MAXLEXER do self:setlexer(i) op = op.."Style properties for '" ..self:getlexerlanguage().."' -->\n" ..self:showlexer(i) end return op end --]] -- end of script