445 lines
10 KiB
Lua
Executable File
445 lines
10 KiB
Lua
Executable File
-- extlib.lua
|
|
-- a useful set of SciTE Lua functions
|
|
scite_require 'class.lua'
|
|
local sub = string.sub
|
|
local append = table.insert
|
|
local find = string.find
|
|
|
|
local colours = {red = "#FF0000", blue = '#0000FF', green = '#00FF00',pink ="#FFAAAA" ,
|
|
black = '#000000', lightblue = '#AAAAFF',lightgreen = '#AAFFAA'}
|
|
local indicator_masks = {[0] = INDIC0_MASK, [1] = INDIC1_MASK, [2] = INDIC2_MASK}
|
|
WORD_PATTERN = "[a-zA-Z0-9_]"
|
|
NOT_WORD_PATTERN = "[^a-zA-Z0-9_]"
|
|
|
|
local GTK = scite_GetProp('PLAT_GTK')
|
|
local dirsep
|
|
if GTK then
|
|
dirsep = '/'
|
|
else
|
|
dirsep = '\\'
|
|
end
|
|
|
|
function join(path,part1,part2)
|
|
local res = path..dirsep..part1
|
|
if part2 then return res..dirsep..part2 else return res end
|
|
end
|
|
|
|
function fullpath(file)
|
|
return props['FileDir']..dirsep..file
|
|
end
|
|
|
|
function choose(cond,x,y)
|
|
if cond then return x else return y end
|
|
end
|
|
|
|
function split(s,re)
|
|
local i1 = 1
|
|
local sz = #s
|
|
local ls = {}
|
|
while true do
|
|
local i2,i3 = s:find(re,i1)
|
|
if not i2 then
|
|
append(ls,s:sub(i1))
|
|
return ls
|
|
end
|
|
append(ls,s:sub(i1,i2-1))
|
|
i1 = i3+1
|
|
if i1 >= sz then return ls end
|
|
end
|
|
end
|
|
|
|
function split_list(s)
|
|
return split(s,'[%s,]+')
|
|
end
|
|
|
|
local function at (s,i)
|
|
return s:sub(i,i)
|
|
end
|
|
|
|
--- note: for finding the last occurance of a character, it's actualy
|
|
--- easier to do it in an explicit loop rather than use patterns.
|
|
--- (These are not time-critcal functions)
|
|
local function split_last (s,ch)
|
|
local i = #s
|
|
while i > 0 do
|
|
if at(s,i) == ch then
|
|
return s:sub(i+1),i
|
|
end
|
|
i = i - 1
|
|
end
|
|
end
|
|
|
|
function basename(s)
|
|
local res = split_last(s,dirsep)
|
|
if res then return res else return s end
|
|
end
|
|
|
|
function path_of (s)
|
|
local basename,idx = split_last(s,dirsep)
|
|
if idx then
|
|
return s:sub(1,idx-1)
|
|
else
|
|
return ''
|
|
end
|
|
end
|
|
|
|
function extension_of (s)
|
|
return split_last(s,'.')
|
|
end
|
|
|
|
function filename(path)
|
|
local fname = basename(path)
|
|
local _,idx = split_last(fname,'.')
|
|
if idx then return fname:sub(1,idx-1) else return fname end
|
|
end
|
|
|
|
function strip_eol(s)
|
|
if at(s,-1) == '\n' then
|
|
if at(s,-2) == '\r' then
|
|
return s:sub(1,-3)
|
|
else
|
|
return s:sub(1,-2)
|
|
end
|
|
else
|
|
return s
|
|
end
|
|
end
|
|
|
|
|
|
function rtrim(s)
|
|
return string.gsub(s,'%s*$','')
|
|
end
|
|
|
|
--line information functions --
|
|
|
|
function current_line()
|
|
return editor:LineFromPosition(editor.CurrentPos)
|
|
end
|
|
|
|
function current_output_line()
|
|
return output:LineFromPosition(output.CurrentPos)
|
|
end
|
|
|
|
function current_pos()
|
|
return editor.CurrentPos
|
|
end
|
|
|
|
-- start position of the given line; defaults to start of current line
|
|
function start_line_position(line)
|
|
if not line then line = current_line() end
|
|
return editor.LineEndPosition[line]
|
|
end
|
|
|
|
-- what is the word directly behind the cursor?
|
|
-- returns the word and its position.
|
|
function word_at_cursor()
|
|
local pos = editor.CurrentPos
|
|
local line_start = start_line_position()
|
|
-- look backwards to find the first non-word character!
|
|
local p1,p2 = editor:findtext(NOT_WORD_PATTERN,SCFIND_REGEXP,pos,line_start)
|
|
if p1 then
|
|
return editor:textrange(p2,pos),p2
|
|
end
|
|
end
|
|
|
|
-- this centers the cursor position
|
|
-- easy enough to make it optional!
|
|
function center_line(line)
|
|
if not line then line = current_line() end
|
|
local top = editor.FirstVisibleLine
|
|
local middle = top + editor.LinesOnScreen/2
|
|
editor:LineScroll(0,line - middle)
|
|
end
|
|
|
|
--general useful routines--
|
|
|
|
-- returns the character at position p as a string
|
|
function char_at(p)
|
|
return string.char(editor.CharAt[p])
|
|
end
|
|
|
|
-- allows you to use standard HTML '#RRGGBB' colours; there are also a few predefined colours available.
|
|
function colour_parse(str)
|
|
if sub(str,1,1) ~= '#' then
|
|
str = colours[str]
|
|
end
|
|
return tonumber(sub(str,6,7)..sub(str,4,5)..sub(str,2,4),16)
|
|
end
|
|
|
|
function expand_string(subst)
|
|
return string.gsub(subst,'%$%(([%w_]+)%)',function(arg)
|
|
local repl = props[arg]
|
|
return repl
|
|
end)
|
|
end
|
|
|
|
-- indicators --
|
|
-- INDIC_PLAIN Underlined with a single, straight line.
|
|
-- INDIC_SQUIGGLE A squiggly underline.
|
|
-- INDIC_TT A line of small T shapes.
|
|
-- INDIC_DIAGONAL Diagonal hatching.
|
|
-- INDIC_STRIKE Strike out.
|
|
-- INDIC_HIDDEN An indicator with no visual effect.
|
|
-- INDIC_BOX A rectangle around the text.
|
|
|
|
local function indicator_mask(ind)
|
|
return indicator_masks[ind]
|
|
end
|
|
|
|
-- this is the default situation: first 5 bits are for lexical styling
|
|
local style_mask = 31
|
|
|
|
-- get the lexical style at position p, without indicator bits!
|
|
function style_at(p)
|
|
return math.mod(editor.StyleAt[p],32)
|
|
end
|
|
|
|
-- define a given indicator's type and foreground colour
|
|
Indicator = class(function(self,which,typ,colour)
|
|
editor.IndicStyle[which] = typ
|
|
if colour then
|
|
editor.IndicFore[which] = colour_parse(colour)
|
|
end
|
|
self.ind = which
|
|
end)
|
|
|
|
-- set the given indicator ind between pos and endp inclusive
|
|
-- (the val arg is only used by indicator_clear)
|
|
function Indicator:set(pos,endp,val)
|
|
local es = editor.EndStyled
|
|
local mask = indicator_mask(self.ind)
|
|
if not val then
|
|
val = mask
|
|
end
|
|
editor:StartStyling(pos,mask)
|
|
editor:SetStyling(endp-pos,val)
|
|
editor:StartStyling(es,style_mask)
|
|
end
|
|
|
|
-- clear an indicator ind between pos and endp
|
|
function Indicator:clear(ind,pos,endp)
|
|
self:set(pos,endp,0)
|
|
end
|
|
|
|
-- find the next position which has indicator ind
|
|
-- (won't handle overlapping indicators!)
|
|
function Indicator:find(pos)
|
|
if not pos then pos = editor.CurrentPos end
|
|
local endp = editor.Length
|
|
local mask = indicator_mask(self.ind)
|
|
while pos ~= endp do
|
|
local style = editor.StyleAt[pos]
|
|
if style > style_mask then -- there are indicators!
|
|
-- but is the particular bit set?
|
|
local diff = style - mask
|
|
if diff >= 0 and diff < mask then
|
|
return pos
|
|
end
|
|
end
|
|
pos = pos + 1
|
|
end
|
|
end
|
|
|
|
-- markers --
|
|
|
|
Marker = class(function(self,idx,line,file)
|
|
buffer = scite_CurrentFile()
|
|
if not file then file = buffer end
|
|
self.idx = idx
|
|
self.file = file
|
|
self.line = line
|
|
if file == buffer then
|
|
self:create()
|
|
else
|
|
self.state = 'waiting'
|
|
end
|
|
end)
|
|
|
|
function Marker:create()
|
|
self.handle = editor:MarkerAdd(self.line-1,self.idx)
|
|
if self.handle == -1 then
|
|
self.state = 'dud'
|
|
if self.type then self:cannot_create(self.file,self.line) end
|
|
else
|
|
self.state = 'created'
|
|
end
|
|
end
|
|
|
|
function Marker:delete()
|
|
if self.file ~= scite_CurrentFile() then -- not the correct buffer!
|
|
self.state = 'expired'
|
|
else
|
|
editor:MarkerDelete(self.line-1,self.idx)
|
|
if self.type then self.type:remove(self) end
|
|
end
|
|
end
|
|
|
|
function Marker:goto(centre)
|
|
editor:GotoLine(self.line-1)
|
|
if centre then center_line() end
|
|
end
|
|
|
|
function Marker:update_line()
|
|
self.line = editor:MarkerLineFromHandle(self.handle)+1
|
|
end
|
|
|
|
MarkerType = class(function(self,idx,typ,fore,back)
|
|
if typ then editor:MarkerDefine(idx,typ) end
|
|
if fore then editor:MarkerSetFore(idx,colour_parse(fore)) end
|
|
if back then editor:MarkerSetBack(idx,colour_parse(back)) end
|
|
self.idx = idx
|
|
self.markers = create_list()
|
|
-- there may be 'expired' markers which need to finally die!
|
|
scite_OnSwitchFile(function(f)
|
|
local ls = create_list()
|
|
for m in self:for_file() do
|
|
if m.state == 'expired' or m.state == 'dud' then
|
|
ls:append(m)
|
|
end
|
|
if m.state == 'waiting' then
|
|
m:create()
|
|
end
|
|
end
|
|
for m in ls:iter() do
|
|
m:delete()
|
|
end
|
|
end)
|
|
-- when a file is saved, we update any markers associated with it.
|
|
scite_OnSave(function(f)
|
|
local changed = false
|
|
for m in self:for_file() do
|
|
local lline = m.line
|
|
m:update_line()
|
|
changed = changed or lline ~= m.line
|
|
end
|
|
if changed then
|
|
self:has_changed('moved')
|
|
end
|
|
end)
|
|
end)
|
|
|
|
function MarkerType:has_changed(how)
|
|
if self.on_changed then
|
|
self:on_changed(how)
|
|
end
|
|
end
|
|
|
|
function MarkerType:cannot_create(file,line)
|
|
print('error:',file,line)
|
|
end
|
|
|
|
function MarkerType:create(line,file)
|
|
local m = Marker(self.idx,line,file)
|
|
self.markers:append(m)
|
|
m.type = self
|
|
self:has_changed('create')
|
|
return m
|
|
end
|
|
|
|
function MarkerType:remove(marker)
|
|
if self.markers:remove(marker) then
|
|
self:has_changed('remove')
|
|
end
|
|
end
|
|
|
|
-- return an iterator for all markers defined in this file
|
|
-- (see PiL, 7.1)
|
|
function MarkerType:for_file(fname)
|
|
if not fname then fname = scite_CurrentFile() end
|
|
local i = 0
|
|
local n = table.getn(self.markers)
|
|
local t = self.markers
|
|
--~ print(n,t)
|
|
return function ()
|
|
i = i + 1
|
|
while i <= n do
|
|
--~ print (i,t[i].line)
|
|
if t[i].file == fname then
|
|
return t[i]
|
|
else
|
|
i = i + 1
|
|
end
|
|
end
|
|
end
|
|
end
|
|
|
|
function MarkerType:iter()
|
|
return self.markers:iter()
|
|
end
|
|
|
|
function MarkerType:dump()
|
|
for m in self:iter() do
|
|
print(m.line,m.file)
|
|
end
|
|
end
|
|
|
|
Bookmark = MarkerType(1)
|
|
|
|
g = {} -- for globals that don't go away ;)
|
|
|
|
-- get the next line following the marker idx
|
|
-- from the specified line (optional)
|
|
function MarkerType:next(line)
|
|
if not line then line = current_line() end
|
|
local mask = math.pow(2,self.idx)
|
|
return editor:MarkerNext(line,mask)+1
|
|
end
|
|
|
|
------ Marker management -------
|
|
local active_cursor_idx = 5
|
|
local signalled_cursor_idx = 6
|
|
local breakpoint_idx = 7
|
|
local active_cursor = nil
|
|
local signalled_cursor = nil
|
|
local breakpoint = nil
|
|
local last_marker = nil
|
|
local initialized = false
|
|
|
|
local function init_breakpoints()
|
|
if not initialized then
|
|
active_cursor = MarkerType(active_cursor_idx,SC_MARK_BACKGROUND,nil,props['stdcolor.active'])
|
|
signalled_cursor = MarkerType(signalled_cursor_idx,SC_MARK_BACKGROUND,nil,props['stdcolor.error'])
|
|
breakpoint = MarkerType(breakpoint_idx,SC_MARK_ARROW,nil,'red')
|
|
initialized = true
|
|
end
|
|
end
|
|
|
|
function Breakpoints()
|
|
init_breakpoints()
|
|
return breakpoint:iter()
|
|
end
|
|
|
|
function RemoveLastMarker(do_remove)
|
|
if last_marker then
|
|
last_marker:delete()
|
|
end
|
|
if do_remove then
|
|
last_marker = nil
|
|
end
|
|
end
|
|
|
|
function OpenAtPos(fname,lineno,how)
|
|
init_breakpoints()
|
|
RemoveLastMarker(false)
|
|
if not last_marker or (last_marker and fname ~= last_marker.file) then
|
|
scite.Open(fname)
|
|
end
|
|
if how == 'active' then
|
|
last_marker = active_cursor:create(lineno)
|
|
elseif how == 'error' then
|
|
last_marker = signalled_cursor:create(lineno)
|
|
else
|
|
last_marker = nil
|
|
end
|
|
if last_marker then
|
|
last_marker:goto()
|
|
else
|
|
editor:GotoLine(lineno-1)
|
|
end
|
|
end
|
|
|
|
function SetBreakMarker(line)
|
|
init_breakpoints()
|
|
return breakpoint:create(line)
|
|
end
|