-- SciTEStartup.lua -- SciTE extention functions
-- Some functions and code fragments were taken from other people,
-- including Tugarinov Sergey, mozers™ et al. Thanks!
-- Georgiy Pruss (C) 2007-05-24 21:23:31 Thu version 1294 * $@{YmdHMSv4a}
-- http://moon.aka.sun.googlepages.com/SciTE.htm

--[[ INDEX:
--||
--|| back_up
--|| calculate_total
--|| capitalize
--|| collect_marked
--|| current_word
--|| double_backslashes
--|| exec_lua
--|| exec_python_and_replace
--|| fill_rectangle
--|| find_next_function & find_prev_function
--|| mark_text_0/1/2/3
--|| move_word_left & move_word_right
--|| OnDoubleClick (timer)
--|| pop_mark
--|| push_mark
--|| replace_version
--|| sort_text
--|| tabs_to_spaces
--|| toggle_bool
--|| open_favorites
--|| utility
--]]


--print( os.date()..' Re-loading file "SciTEStartup.lua". Buffer "'..props.FileNameExt..'"' )

--[[ UTILITIES ]]

function get_sel_or_line()
  if editor:GetSelText()=='' then
    editor:Home()
    editor:LineEndExtend()
  end
  return editor:GetSelText()
end

menu_table = {} -- a table id-->fn

function choose_from_menu( lst, sep, id, fn )
  -- show menu, after the choice call fn with selected item
  -- lst - string of items e.g. "First;Second;Third"
  -- sep - separator in lst e.g. ";"
  -- id  - unique id of this menu, arbitrary number
  -- fn  - function to call e.g. function(s) print( s.." is chosen" ) end
  menu_table[id] = fn
  editor.AutoCSeparator = string.byte(sep)
  editor:UserListShow(id,lst)
  editor.AutoCSeparator = string.byte(' ')
end

function OnUserListSelection(id,s)
  local fn = menu_table[id]
  if fn then fn(s) end
end

--[[ fill_rectangle ]]

function _replace_block( l1, c1, l2, c2, fill, incr, nlen )
  -- Replace/Insert/Fill in rectangle block
  local l, line_pos, line_len, start_pos, end_pos, nblanks, blanks, txt, inslen
  if l1>l2 then l2,l1=l1,l2 end
  if c1>c2 then c2,c1=c1,c2 end
  if nlen>0 then inslen=nlen else inslen=string.len(fill) end
  editor:BeginUndoAction()
  editor:DeleteBack() -- we don't need old text in the selection
  for l = l1, l2 do
    line_pos = editor:PositionFromLine(l)
    line_len = editor.LineEndPosition[l] - line_pos
    if c1 > line_len then
      nblanks = c1 - line_len
      blanks = string.rep(" ",nblanks)
      editor:InsertText(line_pos+line_len, blanks)
    end
    start_pos = line_pos + c1
    if nlen==0 then
      editor:InsertText(start_pos, fill)
    else
      txt = string.format("%0"..nlen.."d", fill)
      editor:InsertText(start_pos, txt)
      fill = fill + incr
    end
  end
  editor.CurrentPos = editor:PositionFromLine(l1) + c1 + inslen
  editor:EndUndoAction()
end

function fill_rectangle(args)
  -- fill_rectangle text:$(1) incr:$(2)
  -- with one argument - fill with it
  -- with two arguments (numeric) - fill with sequence
  if editor.SelectionMode ~= SC_SEL_RECTANGLE then
    _ALERT("Please provide rectangular selection")
    return
  end
  local ARG1 = "text:"
  local ARG1LEN = string.len( ARG1 )
  local ARG2 = " incr:"
  local ARG2LEN = string.len( ARG2 )
  local fill=''
  local incr=''
  local nlen=0
  local pos = string.find(args,ARG1,1,true)
  if not pos then
    return
  end
  args = string.sub(args,pos+ARG1LEN)
  local pos = string.find(args,ARG2,1,true)
  if pos then
    fill = string.sub(args,1,pos-1)
    incr = string.sub(args,pos+ARG2LEN)
  else
    fill = args
    incr = ''
  end
  if incr ~= '' then
    nlen = string.len( fill )
    fill = tonumber( fill )
    incr = tonumber( incr )
  end
  local p1 = editor.CurrentPos
  local p2 = editor.Anchor
  if p1 > p2 then
    p1, p2 = p2, p1
  end
  _replace_block( editor:LineFromPosition(p1), editor.Column[p1],
                  editor:LineFromPosition(p2), editor.Column[p2],
                  fill, incr, nlen )
end

--[[ calculate_total ]]

function calculate_total()
  local txt = editor:GetSelText()
  local num = 0
  local sum = 0.0
  local function add(x)     num = num + 1; sum = sum + tonumber(x);    return '' end
  local function add_hex(x) num = num + 1; sum = sum + tonumber(x,16); return '' end
  txt = string.gsub( txt, "[+-]?%d+%.%d+[eE][+-]?%d+", add )
  txt = string.gsub( txt, "[+-]?%d+[eE][+-]?%d+", add )
  txt = string.gsub( txt, "[+-]?%d+%.%d+", add )
  txt = string.gsub( txt, "[+-]?0x%x+", add_hex )
  txt = string.gsub( string.gsub( txt, ",", "" ), "[+-]?%d+", add )
  if num>0 then
    print( "Count: "..num.."     Sum: "..sum.."     Avg: "..(sum/num) )
  else
    print( "No numbers found in the selection" )
  end
end

--[[ tabs_to_spaces ]]

function tabs_to_spaces()
  -- get override or editor's current tab size
  local tabsz = tonumber(props["ext.lua.tabtospace.tabsize"])
  if not tabsz then tabsz = editor.TabWidth end
  local sub = string.sub
  editor:BeginUndoAction()
  for ln = 0, editor.LineCount - 1 do
    local lbeg = editor:PositionFromLine(ln)
    local lend = editor.LineEndPosition[ln]
    local text = editor:textrange(lbeg, lend)
    local changed = false
    local x, y = string.find(text, "\t", 1, 1)
    while x do
      y = x - 1 + tabsz
      y = y - math.mod(y, tabsz) + 1 -- tab stop position
      text = sub(text, 1, x - 1) .. string.rep(" ", y - x) .. sub(text, x + 1)
      changed = true
      x, y = string.find(text, "\t", 1, 1)
    end--while
    if changed then
      editor.TargetStart = lbeg
      editor.TargetEnd = lend
      editor:ReplaceTarget(text)
    end
  end--for
  editor:EndUndoAction()
end

--[[ sort_text ]]

function _sort_text( opts )
  local desc    = string.find( opts, 'desc', 1, true ) ~= nil
  local igncase = string.find( opts, 'ignore', 1, true ) ~= nil
  editor:BeginUndoAction()
  -- Tugarinov Sergey & mozers™ & me :)
  local sel_text = editor:GetSelText()
  local sel_start = editor.SelectionStart
  local sel_end = editor.SelectionEnd
  local lines = {}
  if sel_text ~= '' then
    local a_line = ''
    for a_line in string.gfind(sel_text, "[^\n]+") do
      table.insert(lines, a_line)
    end
    if table.getn(lines) > 1 then
      upr = string.upper
      if desc then
        if igncase then
          table.sort(lines, function(a, b) return upr(a) > upr(b) end)
        else
          table.sort(lines, function(a, b) return a > b end)
        end
      else -- asc
        if igncase then
          table.sort(lines, function(a, b) return upr(a) < upr(b) end)
        else
          table.sort(lines)
        end
      end
      local out_text = table.concat(lines, "\n").."\n"
      editor:ReplaceSel(out_text)
    end
  end
  editor:SetSel(sel_start, sel_end)
  editor:EndUndoAction()
  -- ToDo: option for ignoring leading '_'s
  --
  -- To[Re]Do: arguments from the parameters dialog
  -- flags in the first arg:
  -- d - desceding (default - asc)
  -- i - ignore case (default - case-sensitive)
  -- n - numeric (50<100, default - lexical, 50>100) [NIY]
  -- u - keep unique strings (default - keep duplicates too) [NIY]
  -- e - external program (MS sort) [NIY]
  -- g - external program (GNU sort) [NIY]
  -- to do: parameters 2-4 can define positions for sorting
  -- e.g. 10 = start from 10, 20-30 = chars 20-30, -40 = up to 40, etc
  -- parse them for internal sort or rather pass to external programs
  --
  -- args is a string of opts followed by (hint)
  --~ local x, y = string.find( args, '(', 1, true )
  --~ local opts = string.sub( args, 1, x )
  --~ local desc    = string.find( opts, 'd', 1, true ) ~= nil
  --~ local igncase = string.find( opts, 'i', 1, true ) ~= nil
end

function sort_text()
  choose_from_menu( 'asc;desc;asc,ignore case;desc,ignore case', ';',
    125, _sort_text )
end

--[[ back_up ]]

  -- Back up 1) on open, 2) before save, 3) when Ctrl+2 pressed
  -- copy file on *disk* (not from buffer!) to C:\TEMP\BackUp\
  -- and append suffix .####.bak where #### is a radix-36 counter
  -- of 5-second intervals with period 97.2 days (0000 to ZZZZ)
  -- if new Untitled file, then saved from *buffer* with name Untitled.####.bak

function _radix_str( n, r, l )
  -- formats string from int number n, radix r (2..36), min length l (>=0)
  if r>36 then r=36 end
  if r<2  then r=10 end
  if l<0  then l=0 end
  local a = '0123456789ABCDEFGHIJKLMNOPQRSTUVWXYZ'
  local d = 0
  local s = ''
  local z = ''
  if n<0 then z = '-'; l = l - 1; n = -n end
  n = math.floor(n)
  while l>0 or n>0 do
    n,d = math.floor(n/r),math.mod(n,r)
    l = l - 1
    s = string.sub(a,d+1,d+1)..s
  end
  return z..s
end

function _back_up_file( fp, fn, verbose ) -- may be used in SciTEExtension.lua
  -- if fp and fn are '', save current buffer to 'Untitled'
  if fp=='' then
    fn = "Untitled"
  else
    local xx = string.find( fp, ' ', 1, true )
    if xx then fp = '"'..fp..'"' end
    fn = string.gsub( fn, ' ', '_' )
  end
  local nn = math.mod( math.floor(os.time()/5), 1679616 ) -- 36^4 by 5 s == 97.2 days
  fn = string.format( "%s.%s.bak", fn, _radix_str( nn, 36, 4 ) )
  if verbose then print( fp..' --> C:\\TEMP\\BackUp\\'..fn ) end
  local fo = "C:\\TEMP\\BackUp\\"..fn
  local txt
  if fp=='' or string.find( fp, "C:\\TEMP\\Temporary Internet Files\\Content.IE5\\", 1, true ) then
    txt = editor:GetText()
  else
    local f = io.open(fp, 'rb')
    if f then txt = f:read('*all') f:close() else  txt = fp.." not found" end
  end
  local f = io.open(fo, 'wb') f:write(txt) f:close()
end

function back_up()
  local fp, fn = props.FilePath, props.FileNameExt
  if fn == '' then
    fp = ''
  end
  _back_up_file( fp, fn, true )
end

function OnOpen( fp )
  if props["ext.lua.backup.on.open"] == '1' then
    _back_up_file( fp, props.FileNameExt )
  end
end

function OnBeforeSave( fp )
  assert( fp == props.FilePath )
  -- back up
  if props["ext.lua.backup.before.save"] == '1' then
    _back_up_file( props.FilePath, props.FileNameExt )
  end
  -- change version
  local vers = props["ext.lua.count.version.before.save"]
  if vers then
    for pat in string.gfind( vers, "[^|]+" ) do         -- ToDo: *.ext patterns
      pat = string.gsub( pat, "%.", "%%." )
      local pos,num = string.find( fp, "^.*" .. pat .. "$" )
      if pos then
        print( os.date()..' Versioning file "'..fp..'"' )
        replace_version() -- it's in active buffer, isn't it? so we don't pass fp
      end
    end
  end
end

--[[ replace_version ]]

  -- look for version commands: $@{...}
  -- a command is a char out of set: YymdHIMSwaAbBv+-
  -- a number parameter may preceed a command
  --   for the first command the default parameter is 1
  --   for each next command w/o parameter it's value is incremented
  --   the default parameter for + and - is always 1
  --   after + or - the default goes back to 1 too
  -- YymdHIMSwjUW -- change parameter'th number in the line to the current value
  --   of corresponding component of date/time. See os.date/strftime for details.
  -- aAbBp -- change parameter'th word (actually, id: %a%w*) in the line to
  --   the current weekday/month name. See os.date/strftime for details.
  -- v -- increment the parameter'th number
  -- numbers/words are counted from the beginnig of the line with the commands
  -- commands + and - change the line affected
  -- See examples at the beginnig of this file and in SciTEGlobal.properties

function _process_ctrl_sequence( cmd, nr, st, en, txt )
  local dirty = false
  local digits = false
  local num, num_specified = 0
  local pat, p,e,x,y,z
  for i = 1, string.len( cmd ) do
    local c = string.sub( cmd, i, i )
    if '0'<=c and c<='9' then
      if digits then
        num = num .. c
      else
        num = c
        digits = true
      end
    else -- commnad
      if digits then
        num = tonumber(num)
        num_specified = true
      else
        num_specified = false
      end
      digits = false
      -- process c,num,num_specified
      if not num_specified then num = num + 1 end -- increment parameter
      local is_date_cmd = string.find( "YymdHIMSwjUW", c, 1, true )
      local is_word_cmd = string.find( "aAbBp", c, 1, true )
      if c == 'v' or is_date_cmd then -- change a number
        pat = "^(%D*" .. string.rep( "%d+%D+", num-1 ) .. ")(%d+)(.*)$"
        p,e,x,y,z = string.find( txt, pat )
      elseif is_word_cmd then -- change a word (id, to be precise)
        pat = "^(%A*" .. string.rep( "%a%w*%A+", num-1 ) .. ")(%a%w*)(.*)$"
        p,e,x,y,z = string.find( txt, pat )
      elseif c == '-' or c == '+' then -- previous/next line(s)
        if dirty then
          editor.TargetStart, editor.TargetEnd = st, en
          editor:ReplaceTarget(txt)
        end
        if not num_specified then num = 1 end
        if c == '+' then nr = nr + num else nr = nr - num end
        st = editor:PositionFromLine(nr)
        en = editor.LineEndPosition[nr]
        txt = editor:textrange(st,en)
        num = 0
        dirty = false
      end
      if p then
        if c == 'v' then
          txt = x..tostring(tonumber(y)+1)..z
        elseif is_date_cmd then
          txt = x..os.date("%"..c)..z
        elseif is_word_cmd then
          txt = x..os.date("%"..c)..z
        end
        dirty = true
      end
    end
  end
  if dirty then
    editor.TargetStart, editor.TargetEnd = st, en
    editor:ReplaceTarget(txt)
  end
end

function _process_ctrl_line( nr )
  local st = editor:PositionFromLine(nr)
  local en = editor.LineEndPosition[nr]
  local txt = editor:textrange(st,en)
  local pos,endpos = string.find( txt, "$@".."{[^}]*}" )
  if pos then
    _process_ctrl_sequence( string.sub( txt, pos+3, endpos-1 ), nr, st, en, txt )
  end
end

function _find_ctrl_line( n )
  -- starting from line n, looks for a line >= 0 containing control sequence
  local st = editor:PositionFromLine( n )
  local pos = editor:findtext( "$@{", SCFIND_MATCHCASE, st )
  if pos then
    return editor:LineFromPosition( pos )
  end
  return nil
end

function replace_version()
  editor:BeginUndoAction()
  local nr = _find_ctrl_line( 0 )
  while nr do
    _process_ctrl_line( nr )
    nr = _find_ctrl_line( nr+1 )
  end
  editor:EndUndoAction()
end

--[[ current_word ]]

function _get_current_word_pos()
  return editor:WordStartPosition(editor.CurrentPos,true),
         editor:WordEndPosition(editor.CurrentPos,true)
end

function current_word()
  -- double click on the word does the same
  local s,e = _get_current_word_pos()
  editor:SetSel(s,e)
end

--[[ toggle_bool ]]

function toggle_bool()
  local p = editor.CurrentPos
  local s = editor:WordStartPosition(p,true)
  local e = editor:WordEndPosition(p,true)
  editor.TargetStart, editor.TargetEnd = s, e
  local word = editor:textrange(s,e)
  if     word == "False" then editor:ReplaceTarget("True")
  elseif word == "True"  then editor:ReplaceTarget("False")
  elseif word == "false" then editor:ReplaceTarget("true")
  elseif word == "true"  then editor:ReplaceTarget("false")
  elseif word == "FALSE" then editor:ReplaceTarget("TRUE")
  elseif word == "TRUE"  then editor:ReplaceTarget("FALSE")
  end
  editor:GotoPos( p )
end

--[[ exec_lua ]]

function exec_lua()
  -- executes current line or selection as Lua code
  -- if text starts with '=' execute as expression and print the result
  local txt = string.gsub( get_sel_or_line(),"^%s*","",1 )
  if string.sub(txt,1,1)=='=' then
    dostring( "print("..string.sub(txt,2)..")" )
  else
    dostring( txt )
  end
end

--[[ find_prev_function & find_next_function ]]

function _what_is_fn_re()
  -- returns an RE that recognizes a function start in some languages
  local fn = nil
  local ext = string.lower(props.FileExt)
  if ext=='lua' or ext=='js' or ext=='htm' or ext=='html' then
    fn = '\\<function [A-Za-z_][A-Za-z0-9_:.]*('
  elseif ext=='py' or ext=='pyw' or ext=='rb' then
    fn = '\\<def [A-Za-z_][A-Za-z0-9_]*('
  end
  return fn
end

function find_prev_function()
  local fn = _what_is_fn_re()
  if fn then
    editor:SearchAnchor()
    local x = editor:SearchPrev(SCFIND_REGEXP,fn)
    if x>=0 then editor:CharLeft() end
  end
end

function find_next_function()
  local fn = _what_is_fn_re()
  if fn then
    editor:SearchAnchor()
    local x = editor:SearchNext(SCFIND_REGEXP,fn)
    if x>=0 then editor:CharRight() end
  end
end

--[[ capitalize ]]

function capitalize()
  editor:BeginUndoAction()
  local text = string.gsub( editor:GetSelText(), '([%a_])([%w_]*)',
    function(a,b) return string.upper(a)..string.lower(b) end )
  editor:ReplaceSel(text)
  editor:EndUndoAction()
end

--[[ push_mark & pop_mark ]]

the_pos_stack = {}

function push_mark()
  local f = props.FilePath
  local m = the_pos_stack[f]
  if not m then
    the_pos_stack[f] = {n=0}
    m = the_pos_stack[f]
  end
  local n = m.n
  m[n] = editor.CurrentPos
  m.n = n+1
  -- todo: should be markers, because 'pos' becomes invalid after editing
  -- and another idea - on some key, print FileNameExt:Ln: [comment]
  -- to the output pane. then it's quite easy to move back with a click there.
end

function pop_mark()
  local m = the_pos_stack[props.FilePath]
  if m then
    local n = m.n-1
    if n>=0 then
      m.n = n
      editor:GotoPos( m[n] )
    end
  end
end

--[[ exec_python_and_replace ]]

FILE_IN  = os.getenv('TEMP')..'\\scite_input.py'
FILE_OUT = os.getenv('TEMP')..'\\scite_output.txt'
REDIRECT = ' > '
PYTHON_CMD = 'C:\\Python\\python.exe -W ignore::FutureWarning '

function exec_python_and_replace()
  -- executes line or selection as Python code
  -- replace the selection with the result or append if "#a" is found inside
  editor:BeginUndoAction()
  local txt = get_sel_or_line()
  local f = io.open(FILE_IN, 'wb') f:write(txt) f:close()    -- write the file
  os.execute(PYTHON_CMD..FILE_IN..REDIRECT..FILE_OUT)        -- execute it
  f = io.open(FILE_OUT) local out = f:read('*all') f:close() -- read output
  if string.find(txt,"#a",1,true) then out = txt .. "\n" .. out end
  editor:ReplaceSel(out)
  editor:EndUndoAction()
end

--[[ double_backslashes ]]

function double_backslashes()
  editor:ReplaceSel( string.gsub( get_sel_or_line(), '\\', '\\\\' ) )
end

--[[ mark_text_0 and mark_text_1/2/3 ]]

function _reset_mark_indic()
  -- there's no editor pane during loading, so this can't be called at start
  -- so let's assign it to mark_text_0 w/o selection -- quite approprite
  editor.IndicStyle[0] = INDIC_ROUNDBOX
  editor.IndicStyle[1] = INDIC_ROUNDBOX
  editor.IndicStyle[2] = INDIC_ROUNDBOX
  -- could be ext.lua.indic.fore.1/2/3 but this file is as good as SciTEGlb.prps
  editor.IndicFore[0] = 255*257   -- YELLOW
  editor.IndicFore[1] = 255*65536 -- BLUE
  editor.IndicFore[2] = 255       -- RED
end

mark_names = "plain,squiggle,strike,box,roundbox,diagonal,tt,hidden" -- in menu order
mark_style = { plain    = INDIC_PLAIN,    squiggle = INDIC_SQUIGGLE,
               tt       = INDIC_TT,       diagonal = INDIC_DIAGONAL,
               strike   = INDIC_STRIKE,   hidden   = INDIC_HIDDEN,
               roundbox = INDIC_ROUNDBOX, box      = INDIC_BOX }

function _set_mark_1(x) editor.IndicStyle[0] = mark_style[x] end
function _set_mark_2(x) editor.IndicStyle[1] = mark_style[x] end
function _set_mark_3(x) editor.IndicStyle[2] = mark_style[x] end

function _selection_pos_len()
  local s,e = editor.SelectionStart,editor.SelectionEnd
  return s,e-s
end

function mark_text_0()
  -- clear marks (indicators) in the selection, or reset mark styles and colors
  local pos,len = _selection_pos_len()
  if len==0 then _reset_mark_indic()
  else editor:StartStyling(pos,INDICS_MASK) editor:SetStyling(len,0) end
end

function _mark_text_N(fn,mask)
  -- mark the selection (see three fns below) or choose style if no selection
  local pos,len = _selection_pos_len()
  if len==0 then choose_from_menu( mark_names, ",", 113, fn )
  else editor:StartStyling(pos,INDICS_MASK) editor:SetStyling(len,mask) end
end

function mark_text_1() _mark_text_N( _set_mark_1, INDIC0_MASK ) end
function mark_text_2() _mark_text_N( _set_mark_2, INDIC1_MASK ) end
function mark_text_3() _mark_text_N( _set_mark_3, INDIC2_MASK ) end

--[[ move_word_left move_word_right ]]

function _exchange_ranges(ls,le,rs,re)
  -- exchange ls:le and rs:re, ls<le<rs<re
  -- here I do it with selections, probably should be done with regions/ranges
  editor:SetSel(ls,le)
  local lw = editor:GetSelText()
  editor:SetSel(rs,re)
  local rw = editor:GetSelText()
  editor:ReplaceSel(lw)
  editor:SetSel(ls,le)
  editor:ReplaceSel(rw)
end

function _is_alnum( n )
  -- 'A'..'Z' or 'a'..'z' or '0'..'9' or '_'
  return 65<=n and n<=90 or 97<=n and n<=122 or 48<=n and n<=57 or n==95
end

function _move_l_r( init_pos, left )
  -- move left or right to the next word
  local savecp = editor.CurrentPos
  editor.CurrentPos = init_pos
  local ch,ps
  repeat
    ps = editor.CurrentPos
    if left then editor:WordLeft() else editor:WordRight() end
    if editor.CurrentPos == ps then editor.CurrentPos = savecp; return false end
    ch = editor.CharAt[editor.CurrentPos]
  until _is_alnum(ch)
  return true
end

function move_word_left()
  local rs,re = _get_current_word_pos()
  if rs==re or rs<1 then return end
  if _move_l_r( rs, true ) then
    editor:BeginUndoAction()
    local ls,le = _get_current_word_pos()
    _exchange_ranges(ls,le,rs,re)
    editor.CurrentPos = ls; editor:SetSel(ls,ls)
    editor:EndUndoAction()
  end
end

function move_word_right()
  local ls,le = _get_current_word_pos()
  if ls==le then return end
  if _move_l_r( le, false ) then
    editor:BeginUndoAction()
    local rs,re = _get_current_word_pos()
    _exchange_ranges(ls,le,rs,re)
    editor.CurrentPos = re; editor:SetSel(re,re)
    editor:EndUndoAction()
  end
end

--[[ collect_marked ]]

function collect_marked( to_cut )
  -- collect all marked lines -- by Jos van der Zande (JdeB)
  editor:BeginUndoAction()
  local text = ""
  local ml = editor:MarkerNext(0, 2) -- find first bookmarked line
  while ml >= 0 do
    text = text .. editor:GetLine(ml) -- collect text
    ml = editor:MarkerNext(ml+1, 2) -- find next bookmarked line
  end
  editor:AddText(text) -- paste
  if to_cut == "cut" then
    ml = editor:MarkerPrevious(editor.LineCount, 2)
    while ml >= 0 do
      editor:MarkerDelete(ml,-1) -- remove all markers
      editor.TargetStart = editor:PositionFromLine(ml)
      editor.TargetEnd = editor:PositionFromLine(ml+1)
      editor:ReplaceTarget("")
      ml = editor:MarkerPrevious(ml-1, 2) -- find prev bookmarked line
    end
  end
  editor:EndUndoAction()
end

--[[ open_favorites ]]

function _open_favorites(f)
  if string.len(f)>0 then scite.Open(f) end
end

function open_favorites() -- open favorites
  local favs = props["favorites.global"] -- list of files by '|'
  if string.len(favs)>0 then
    choose_from_menu( favs, '|', 120, _open_favorites )
  end
end

--[[ utility ]]

function utility() -- temporary fn for doing some work

  -- replace utf-8 symbol encoding \xHH with value bytes
  -- 1. selection based
  -- 1.1. process text
--~   editor:BeginUndoAction()
--~   local txt = get_sel_or_line()
--~   --
--~   txt = string.gsub( txt, "\\x(%x%x)",
--~     function(x) return string.char(tonumber(x,16)) end )
--~   --
--~   editor:ReplaceSel(txt)
--~   editor:EndUndoAction()
  -- 1.2. process lines
--~   local nr=0
--~   local st = editor:PositionFromLine(nr)
--~   local en = editor.LineEndPosition[nr]
--~   local txt
--~   while st <= en do
--~     txt = editor:textrange(st,en)
--~     --print( nr, txt )
--~     nr = nr + 1
--~     st = editor:PositionFromLine(nr)
--~     en = editor.LineEndPosition[nr]
--~   end
end

--[[ hello ]]

function hello(x) -- test fn; '' if called w/o args
  if not x or x=='' then x = "world" end
  print('Hello, '..x..'!')
end

--[[ OnDoubleClick -- timer ]]

function s2hmst( tm )
  -- convert seconds (duration) to "MM:SS.TTT" or "H:MM:SS.TTT"
  local hr,mn,sc,ms
  sc,ms = math.floor(tm),math.floor(1000*math.mod(tm,1)+0.1)
  mn,sc = math.floor(sc/60),math.mod(sc,60)
  hr,mn = math.floor(mn/60),math.mod(mn,60)
  hr = (hr>0) and string.format("%d:",hr) or ""
  return hr..string.format("%02d:%02d.%03d",mn,sc,ms)
end

-- One can build applications with this. Make some dedicated "buttons" and
-- attach some actions to them.
function OnDoubleClick()
  local cmd = editor:GetSelText()
  if not (cmd=='START' or cmd=='STOP' or cmd=='SHOW') then return false end
  -- this example is the simpliest timer
  --
  -------------------> START STOP SHOW <------------------- dblclick on a word
  --
  -- Global vars live only while this buffer is active, so we use props
  local function _show(msg)
    local elapsed = os.clock() - tonumber(props.timer_start)
    print(os.date(msg.." %H:%M:%S").." elapsed "..s2hmst(elapsed))
  end
  if cmd=='START' then
    if props.timer_is_counting == "true" then
      print("already counting, press SHOW or STOP") return end
    props.timer_is_counting = "true"
    props.timer_start = tostring(os.clock())
    print(string.rep("-",50))
    print(os.date("stared %H:%M:%S"))
  elseif cmd=='STOP' then
    if props.timer_is_counting ~= "true" then
      print("first you should start the timer") return end
    props.timer_is_counting = "false"
    _show("stop'd")
  elseif cmd=='SHOW' then
    if props.timer_is_counting ~= "true" then
      print("first you should start the timer") return end
    _show("now is")
  end
  return false
end

--[[ END ]]

--~ some more or less useful fragments...

function input_box(prompt,default,title)
  local txt = 'WScript.Echo InputBox("'..prompt..'","'..title..'","'..default..'")'
  local tmp = os.getenv('TEMP').."\\"
  local tmp1, tmp2 = tmp.."SCRPT.VBS", tmp.."RESLT.TMP"
  print(tmp1,tmp2)
  local f = io.open(tmp1, 'wb') f:write(txt) f:close()    -- write the file
  os.execute("cscript //Nologo "..tmp1.." > "..tmp2) -- execute the file
  f = io.open(tmp2) local out = f:read('*all') f:close() -- read output
  return out
end

--- print(input_box("How's your name?","","Name!"))

--- CREATE_NO_WINDOW = 134217728 -- 0x08000000 not in 5.0.2
--- os.execute('cmd.exe /c dir >c:\\tmp\\dt','d:\\temp',134217728)
--- os.execute('execdll user32.dll MessageBoxA(0,"Hello","***",64)')
---             rundll32 user32.dll,MessageBeep -- only zero or one parameter

--~ print("SciTE is up "..s2hmst(os.clock()))

--~ function OnOpen( fp ) -- props['FilePath']
--~   if props['ext.lua.backup.on.open'] == 1 then
--~     _back_up_file( fp, props['FileNameExt'] )
--~   end
---   return false
--~ end

--- function OnChar( c ) -- works here too!
---   print('char',c)
---   return false
--~   -- OnChar = function(c) if c=='x' then OnChar=nil else print(c) end return false end
--- end

--- get last part from path fp
--~ local fn
--~ if string.find( fp, "[\\/]" ) then
--~   _,_,fn = string.find( fp, '.*[\\/]([^\\/]+)$' )
--~ else
--~   fn = fp
--~ end

-- require 'C:\\Apps\\SciTE\\file_browser.lua' -- full path must be provided

--~ local formula
--~ local Init =loadlib('C:\\Program Files\\Scintilla Text Editor\\luawin.dll', 'Init')
--~ Init()
--~
--~ function setFormula()
--~     local flag, msg = win.InputBox('Formula: <> as the place holder. e.g. <>*10')
--~     if flag and string.find(msg,'<>',1,true) then
--~         formula = msg
--~     end
--~
--~     if formula then msg = formula else msg = "NONE" end
--~     print("FORMULA: "..msg)
--~
--~     executeFormula()
--~ end

--~ function SciteListAllOccurances()
--~   if props.CurrentSelection ~= "" then
--~     for m in editor:match( "^.*" .. props.CurrentSelection .. ".*$", SCFIND_REGEXP, 0) do
--~       print(props.FileNameExt .. ":" .. (editor:LineFromPosition(m.pos)+1) .. ":" .. m.text);
--~     end
--~   else
--~     alert("The InternalGrep script only searchs for selected text");
--~   end
--~ end

--~ Also available:
--~ props.CurrentSelection
--~ props.CurrentWord
--~ props.SelectionStartLine
--~ props.SelectionStartColumn
--~ props.SelectionEndLine
--~ props.SelectionEndColumn

--~ -- goto file being 'require'd
--~ function goto_required()
--~   local line = editor:GetLine(
--~     editor:LineFromPosition(editor.CurrentPos) )
--~   local _, _, _, filename = string.find(line,
--~     '^.*require%s*([\'"])(.-)%1.*$')
--~   for path in string.gfind(LUA_PATH..';', '(.-);') do
--~     path = string.gsub(path, '?', filename)
--~     local f = io.open(path)
--~     if f then f:close() scite.Open(path) break end
--~   end
--~ end

--- editor.MouseDwellTime = 1000

--[[ EOF ]]