Re: SciTE customisation with Lua Scripts

andrew strain <[email protected]> Wed, 31 Jan 2024 18:24:36 -0800 (PST)
Newsgroups gmane.editors.scite.general
Message-ID <[email protected]>
Hi - I'm assuming this bumped topic as the latest centralised resource to 
share lua scripts ;) 
So here are three refined features yanked out of my sprawling collection 
(codeberg.org/lusci/lusci) to work under Gavin's neat set up. 
There is also a GetClipboard() function with no dependencies at the top 
that is not used within but might be handy.

The three main functions are:
  keypr_sort() - toggles standard and natural sort order in selected text
  keypr_twiddle() - reverses selected words or lines or column, or flips 
letters at caret
  keypr_cyclecase() - cycles through lower > Sentence > First Letter > ALL 
CAPS of selected

They are made to work with normal or monospaced rectangular selections.
I put twiddle and sort on ctrl+t and shift+ctrl+t  and cyclecase on ctrl+u

It occurs to me momentarily, the sort would be better if it included a 
shuffle phase...
...a future version. Best wishes - andrew
On Sunday 21 January 2024 at 18:19:11 UTC Gavin Holt wrote:

> Hi,
>
> Answering your questions:
>
> *extensions.lua*  -  is a lua script I use for many lua projects. It adds 
> functions to the standard libraries which are missing. The necessary 
> functions are now in SciTE.lua.
>
> *extman.lua *- was a way of combining different plugin lua scripts, so 
> they play nicely together. I have stopped using this and have all my SciTE 
> scripting in a single file - attached.
>
> My current windows SciTE installation for reference:
>
> ----+/SciTE/
>     ---- Sc541.exe
>     ---- SciTEGlobal.properties     
>             (ext.lua.startup.script=$(SciteDefaultHome)\SciTE.lua)
>             (import $(SciteDefaultHome)\mod)
>     ---- SciTE.lua                  
>             (This is my only script)
>     ----+ /mod/                       
>             (modifications and additions to the Sc1.exe bundled properties 
> files)
>         ---- ahk.properties
>         ---- kix.properties
>         ---- lua.api
>         ---- lua.properties
>         ---- markdown.properties
>         ---- props.api
>         ---- props.properties
>         ---- SciTELua.api
>         ---- SciTELua.properties
>         ---- sql.properties
>
> SciTE is not my only editor and the script is dependent upon several 
> external tools/files most of which are annotated in the script (e.g. 
> Hunspell.exe, paste.exe, shelexec.exe, GrepWin.exe).
>         
> I have given up on using any third party binary (DLL) addins for SciTE - 
> see dead batteries <https://lwn.net/Articles/812122/>.
>
> Kind Regards Gavin Holt 
>
> On Thursday 18 January 2024 at 10:08:20 UTC [email protected] wrote:
>
>> Neil, thanks for your answer !
>> > There is https://github.com/moltenform/scite-files but I just noticed 
>> a link to its old location on the web page. 
>> Oh yes, I know well this ressource. It's an almost complete digest of 
>> what have been done in the SciTEsphere. 
>>
>> BTW by "centralized ressource" I think more of something easyer for new 
>> adopters, like unique startup script allowing easy extensions integration 
>> (such as extman), videos/pictures of what can be done... the lua-users wiki 
>> could be a good base because of its editablility. 
>>
>> My configs are all messy as yours (as early adopter and daily user), and 
>> the molteform repo is more a do-what-you-can-whith-this list :D
>>
>> I know SciTE purpose was at first a proof of concept for Scintilla, 
>> although her maturity, lightliness and flexibility give her the chance to 
>> be among the best editors/IDE.
>>
>> Pascal
>>
>> Le mercredi 17 janvier 2024 à 22:01:56 UTC+1, Neil Hodgson a écrit :
>>
>>> Pascal: 
>>>
>>> > Actually, unless I missed something, SciTE lacks a centralised 
>>> ressource to allow people to easily install and configure her. 
>>> > There are scattered initiatives, mostly olds. Nevertheless, this group 
>>> and lua-users site are a good start. 
>>>
>>> There is https://github.com/moltenform/scite-files but I just noticed a 
>>> link to its old location on the web page. 
>>>
>>> > I always was curious of some people's configs, such as Neil's (which I 
>>> imagine is powerfull but sleek and simple) ;-) 
>>>
>>> My configuration is always very messy as I add items to check reported 
>>> bugs and never clean it up properly. 
>>>
>>> Neil 
>>>
>>>

-- 
You received this message because you are subscribed to the Google Groups "scite-interest" group.
To unsubscribe from this group and stop receiving emails from it, send an email to [email protected].
To view this discussion on the web visit https://groups.google.com/d/msgid/scite-interest/df54dae8-9833-4a0a-a7e0-f97a6116b282n%40googlegroups.com.
lusci3.lua (text/x-lua, 13.2 KB)
--[[  From - `LuSci` https://codeberg.org/lusci/lusci
      MIT License (c) 2024 A Strain, [email protected]

This Lua script for SciTE contains the neccessary helpers and
these three functions and for modifying the current selection:
  keypr_sort()
    toggles standard and natural sort order in selected text
  keypr_twiddle()
    reverses selected words or lines or column, or flips letters at caret
  keypr_cyclecase() -
    cycles through lower > Sentence > First Letter > ALL CAPS of selected

They are made to work with normal or monospaced rectangular selections.
--]]

-- section: imported helper functions

function GetClipboard()
  local p,nl,cb = editor, "\n", nil
  local a,c,l,v = p.Anchor,p.CurrentPos, p.Length, p.FirstVisibleLine

  scite.SendEditor(SCI_SETUNDOCOLLECTION,false)
  p:AppendText(nl)  --add newline in case rectangular paste might add indents
  p:SetSel(l+#nl,l+#nl)
  p:Paste()
  cb = p:textrange(l+#nl,p.Length)
--p:DeleteRange(l,p.Length)  --DeleteRange doesnt work on output
  p:SetSel(l,p.Length)  p:Clear()
  p:SetSel(a,c)
  p.FirstVisibleLine=v
  scite.SendEditor(SCI_SETUNDOCOLLECTION,true)
  return cb
end

local function tain(v,bot,top) --con(tain) v=tain(v,[low or 0,[high or no high]])
  bot=bot or 0
  v = v==v and v or bot --sheds damn nans
  if v<bot then v=bot end
  if top and v>top then v=top end
  return v
end

local function get_pane(p) --rt: pan,lfps,rtps,rv,anc,cur,bln,dlnadj,dltrue,plen
  if not p then  p=editor if output.Focus then  p=output end end
  local b,d,r=p.Anchor,p.CurrentPos,false
  if p.CurrentPos<p.Anchor then b,d,r=d,b,true end
  local bl,dl=p:LineFromPosition(b),p:LineFromPosition(d)
  local dbac = bl~=dl and d==p:PositionFromLine(dl) and 1 or 0
  return p,b,d,r,p.Anchor, p.CurrentPos, bl, dl-dbac, dl, p.Length
end

local function poslinestfn(cp,pane,offset,ln) --rtrns: lineSt, linefn, line
  offset= offset or 0
  pane=pane or editor
  cp=cp or pane.CurrentPos
  ln=ln or pane:LineFromPosition(tain(cp,0,pane.Length))
  ln=ln+offset
  if ln<0 then
    return 0,0,0
  elseif ln>pane.LineCount-1 then
    return pane.Length,pane.Length,pane.LineCount-1
  else
    return  pane:PositionFromLine(ln), pane.LineEndPosition[ln], ln
  end
end

local function nlchar(p)
  p = p or (editor.Focus and editor or output)
  if p.EOLMode==0 then return "\r\n" end
  if p.EOLMode==1 then return "\r" end
  return "\n"
end

local function unconcat_(hay, septt, eschar) -- unconcat (hay,sep pattern[, escape char])
  local insert, tbl, emtc = table.insert, {}  -- not sure if needed to import here
  septt=septt or "[,.| ]+"
  if eschar and #eschar>0 then
    eschar = eschar:gsub("%W","%%%0")
    septt = eschar.."*"..septt
    emtc = "("..eschar.."+)"..septt
  end

  local ank,fndst,fndy,fnda,fnde = 1,1,#hay
  while fndst do
    fnda,fnde = hay:find(septt,fndst)
    if fnde then
      fndst=fnde+1
      fndy= fnda-1
      if eschar and fnda~=fnde then -- room for escape
        local esca,esce,escm=string.find(hay:sub(fnda,fnde),emtc)
        if esce and (#escm)&1==1 then
          fndy = nil --septt was escaped
        end
      end
    else --no thing left
      if ank<=#hay then fndy=#hay end
    end

    if fndy then
      insert(tbl,hay:sub(ank,fndy))
      ank=fnde and fnde+1 --fnde may be nil
      fndst=ank           --nil ends while loop
    end
  end
  return tbl
end

local function unconcat(hay, sep, eschar) -- hay, sep = 1 plain char or pattern [,escape char]
  local sep, tbl, insert, patt   =   sep or "", {}, table.insert

  if #sep>1 or eschar then
    return unconcat_(hay,sep,eschar) --complex version,
  end

  if #sep==0 then patt ="(.)" else
    patt="([^"..sep:gsub("%W","%%%0").."]*)"
  end

  for c in hay:gmatch(patt) do  insert(tbl,c)  end
  return tbl
end

local function fliptab(t)
  for i=0, (#t-1)/2, 1 do  t[i+1],t[#t-i] = t[#t-i],t[i+1]  end
  return t
end

function array_reorder(tbl,ndx) --inplace reorder tbl by ndx
  local cv,bi,di
  for ci=1,#tbl-1,1 do
    cv,bi = tbl[ci],ci
    while ndx[bi]~=bi do
      di,ndx[bi] = ndx[bi],bi
      if di ~= ci then
        tbl[bi],bi = tbl[di],di
      else
        tbl[bi] = cv
      end
    end
  end
  return tbl
end

function utftotable(txt)  --unicode iterator from lua manual
  local utab={}
  for c in string.gmatch(txt, "([%z\1-\127\194-\244][\128-\191]*)") do
    table.insert(utab,c)
  end
  return utab
end

function hasutf(txt)  return txt:find("[%z\194-\244][\128-\191]*")  end
-- lookups from https://gist.github.com/natanael-b/3de34eff407477cc519eb6a309ebaf14
local utf_lower = {
 [192]=224, [193]=225, [194]=226, [195]=227, [196]=228, [197]=229, [198]=230, [199]=231,
 [200]=232, [201]=233, [202]=234, [203]=235, [204]=236, [205]=237, [206]=238, [207]=239,
 [208]=240, [209]=241, [210]=242, [211]=243, [212]=244, [213]=245, [214]=246, [216]=248,
 [217]=249, [218]=250, [219]=251, [220]=252, [221]=253, [222]=254, [376]=255 }

local utf_upper = {
 [224]=192, [225]=193, [226]=194, [227]=195, [228]=196, [229]=197, [230]=198, [231]=199,
 [232]=200, [233]=201, [234]=202, [235]=203, [236]=204, [237]=205, [238]=206, [239]=207,
 [240]=208, [241]=209, [242]=210, [243]=211, [244]=212, [245]=213, [246]=214, [248]=216,
 [249]=217, [250]=218, [251]=219, [252]=220, [253]=221, [254]=222, [255]=376 }

local function utfcaseup(str,cas)
  if cas then str=string.lower(str) cas=utf_lower
  else str=string.upper(str) cas=utf_upper end
  if not hasutf(str) then return str end

  local r= {}
  for k,v in utf8.codes(str) do  r[#r+1] = utf8.char( cas[v] or v ) end
  return table.concat(r)
end

function upperutf8(str)  return utfcaseup(str)  end
function lowerutf8(str)  return utfcaseup(str,"low") end


-- section: entrails of the three feature functions

local function matchout(hy,nd,plain) 
  --returns the matched portion and hy with matched portion removed.
  --if the pattern contains captures it returns the captures concatenated instead
  --of the matched portion. All matched text is discarded irregardless of captures.
  if not hy or not nd then return hy, "" end
  local s,e,cap,cap2,cap3 = string.find(hy,nd,1,plain) --  nd can be ^.. or ..$

  if cap then cap = cap..(cap2 or "")..(cap3 or "") end
  if e then
    return string.sub(hy,1,s-1)..string.sub(hy,e+1) , cap or string.sub(hy,s,e)
  else
    return hy , ""
  end
end

local function getmulsel(pan)
  local s,sels = pan.Selections,{}
  sels.rvs=false

  for i=0,s-1 do
    local a,c = pan.SelectionNAnchor[i],pan.SelectionNCaret[i]
    if(c<a) then a,c=c,a sels.rvs=true end
    table.insert(sels,{a,c})
  end

  sels.rc = pan.RectangularSelectionCaret
  sels.rcv = pan.RectangularSelectionCaretVirtualSpace
  sels.ra = pan.RectangularSelectionAnchor
  sels.rav = pan.RectangularSelectionAnchorVirtualSpace
  sels.m = pan.MainSelection
  sels.p = pan  sels.n = s
  sels.x = false

  return sels
end

local function setmulsel(sels)
  local pan=sels.p
  for i=1,sels.n do
    if i==1 then
      if sels.rvs then sels[i][1],sels[i][2]=sels[i][2],sels[i][1] end
      pan:SetSelection(sels[i][2],sels[i][1]) -- setsel(caret,anchor)
    else
      if sels.rvs then sels[i][1],sels[i][2]=sels[i][2],sels[i][1] end
      pan:AddSelection(sels[i][2],sels[i][1])
    end
  end

  pan.MainSelection=sels.m
  if sels.ra then
    pan.RectangularSelectionAnchor=sels.ra
    pan.RectangularSelectionAnchorVirtualSpace=sels.rav
    pan.RectangularSelectionCaret=sels.rc
    pan.RectangularSelectionCaretVirtualSpace=sels.rcv
  end
end

function filtertext(fn,opts,aa,cc) --filters opts="sw" or (s)el or (l)ine or (a)ll

  opts=opts or "esa"
  local os,ow,ol,oa=opts:match("s"),opts:match("w"),opts:match("l"),opts:match("a")
  local oe,oo=opts:match("e"),opts:match("o") --(o)utput (e)ditor

  local pan,b,d,rv,a,c ,x,y = get_pane(oe and editor or oo and output)

  if os and pan.Selections>1 then
    filtermultisel(fn,pan)
    return true
  end
--
  if aa then x,y=aa,cc
  elseif os and b~=d then x,y=b,d
  elseif ol then x,y=poslinestfn(b,pan)
--elseif ow then x,y=wordsomeway(pan,b,lft)  --imports needed from lusci
--elseif oa then x,y=0,pan.Length
  else x,y=0,pan.Length
  end

  local fta=pan:textrange(x,y)
  local ftd=fn(fta)
  if fta~=ftd then
    pan:SetSel(x,y)
    pan:ReplaceSel(ftd)
    d=d-#fta+#ftd
    if rv then pan:SetSel(d,b)
    else pan:SetSel(b,d) end
  end
  return true
end  --work out how to filter multiple sels and virtual sels...

function filtermultisel(fn,pan)

  local sels=getmulsel(pan)
  local s,m = sels.n,sels.m

  pan:ClearSelections()

  pan:BeginUndoAction()
  filtermuls(fn,sels)
  pan:EndUndoAction()

  setmulsel(sels)
end

function filtermuls(fn,sels) --multiselection set
  local pan=sels.p
  local x=0

  for i=1,sels.n do

    local a,c = sels[i][1],sels[i][2]
    a,c = a+x,c+x

    local ins = fn( pan:textrange(a,c) )

    pan:remove(a,c) pan:InsertText(a,ins)

    sels[i][1],sels[i][2] = a, a+#ins

    if c-a~=#ins then
      sels.ra=false  --invalidate rectangle selection mode
      x=x-c+a+#ins
    end
  end
end

local function evocase(c)

  local cz=0
  local d,f,n,m
  --0 all lower
  --1 cap start and periods
  --2 Capitalize first letters
  --3 capitalize all

  f = upperutf8(c)
  if c==f then  return lowerutf8(c)  --c was all upper
  else
    d = lowerutf8(c)
    if c==d then --c was all lower
      --beginning of passage
      d = c:gsub("^[^%l]*%l", upperutf8)
      d = d:gsub("^[^%l]*[\192-\247][\128-\191]?[\128-\191]?[\128-\191]?", upperutf8)

      --after periods,  d is (1)
      d = d:gsub("[.]%s-%a", upperutf8)
      d = d:gsub("[.]%s-[\192-\247][\128-\191]?[\128-\191]?[\128-\191]?", upperutf8)

      return d --return sentence cap
    end

    e = d:gsub("%f[%w_%-\192-\247].[\128-\191]?[\128-\191]?[\128-\191]?", upperutf8)      --e is (2)
    if c~=e then  return e  end
    return f --returns all upper
  end
end

local function togglesort(t)
  local ix,t2 ={},{}

  local function compa(a,b) return t[a] < t[b] end

  local function compb(a,b)
    local bisnum = type(t2[b])=='number'
    if type(t2[a])=='number' then
      if not bisnum then
        return false
      else return t2[a]<t2[b] end
    elseif bisnum then return true
    else return t2[a]<t2[b] end
  end

  local changed=false
  for i=1,#t,1 do ix[i]=i end

  for i=2,#ix,1 do
    if compa(ix[i],ix[i-1]) then
      changed=true
      table.sort(ix,compa)
      break
    end
  end
  if not changed then
    for i=1,#t,1 do
      local u=t[i]:match("^%s*([^%s]+)")
      u = u and tonumber(u)
      t2[i]=u or t[i]:gsub("%d+",function(d) return string.format("%020d",d) end )
    end
    table.sort(ix,compb)
  end

  return array_reorder(t,ix)
end

-- section: main of three feature functions

function keypr_cyclecase()
  return filtertext(evocase,"sl") --filters (s)el or (l)ine if no sel
end

function keypr_sort() return keypr_twiddle("sort") end

function keypr_twiddle(sortit) --twiddles or sorts selection
  local pan,ca,ce,rv=get_pane()
  local t,sels,xa,xe
  local multi=pan.Selections>1

  if not multi then
    if ca==ce then
      local d=tain(ca+1,0,pan.Length)
      filtertext(function(c) return c:sub(2,2)..c:sub(1,1) end ,"-", tain(ca-1),d)
      pan:SetSel(d,d)
      return true
    end

    local tx=pan:textrange(ca,ce)
    local cnt,p={},nil
    xa,xe="",""

    local yca,ycc,ylnn=poslinestfn(ca,pan)
    local zca,zcc,zlnn=poslinestfn(ce,pan)

    if ce==zcc then zca,zlnn=zcc,zlnn+1 end
    if zlnn-ylnn > 1 and ca==yca and ce==zca then -- divby lines when sel at line ends
      punky=nlchar()
      tx,xa=matchout(tx,"^"..punky)
      tx,xe=matchout(tx,punky.."$")
    else

      local punks={"else","==","<=","~=",">=","%)%(","%.%.","><","%]%["," = ","=",",","|",":",";","~",">","<","%+","%-","/","%.","%*","%^","&"," "," or "," and "}
      local lft={"{","%(","%[","<"} --,'"',"'","`"," "} -- i think these dont keypr_twiddle
      local rgt={"}","%)","%]",">"} --,'"',"'","`"," "}
      local mid={"%}%{","%)%(","%]%[","><"} --,'^[^"]+$',"^[^']+$","^[^`]+$","^[^ ]+$"}

      if #tx>3 then
        local ya,ye,yc = tx:sub(1,1),tx:sub(-1),tx:sub(2,#tx-1)
        for i=1,#lft do
          if ya:find(lft[i]) and ye:find(rgt[i])
          and ( yc:find(mid[i]) or not ( yc:find(lft[i],1,true) or yc:find(rgt[i],1,true) )) then
            xa,xe,tx=ya,ye,yc
            break
          end
        end
      end

      for i,v in pairs(punks) do
        local b,c,n=0,0,0
        while c and n<1000 do
          b,c=tx:find(v,c+1)
          if c then  n=n+1 cnt[i]=(cnt[i] or 0)+1  p=i  end
        end
        if cnt[i]==1 then break end
      end

      local m=321
      if not p then
        for i,v in pairs(cnt) do
        if v>1 and v<m then m=v p=i end end
      end

      if not p then punky="" else punky =tx:match(punks[p]) end
      if p then
        punky =tx:match(punks[p])
      else
        punky=""
      end
    end
    t=unconcat(tx,punky)

  else
    t,sels={},getmulsel(pan)
    for i=1,sels.n do
      if sels[i][1]~=sels[i][2] then
        t[i] = pan:textrange(sels[i][1],sels[i][2])
      end
    end
  end

  if sortit then togglesort(t) else fliptab(t) end

  if not multi then
    pan:ReplaceSel(xa..table.concat(t,punky)..xe)
    if rv then ca,ce=ce,ca end
    pan:SetSel(ca,ce)
  else
    pan:BeginUndoAction()
    for i=1,sels.n do
      if sels[i][1]~=sels[i][2] then
        pan:remove(sels[i][1],sels[i][2])
        pan:InsertText(sels[i][1],t[i])
      end
    end
    pan:EndUndoAction()
    setmulsel(sels)
  end

  return true
end