Using Lua With Scite

lua-users home
wiki

Any global Lua function can be used as a SciTE command. These functions appear on the Tools Menu, and may have an associated shortcut key.

Here is a simple example; put this in your properties file (could be local,user or global):

command.name.1.*=Load Lua
command.subsystem.1.*=3
command.1.*=dofile $(FilePath)

You will now have a menu item called 'Load Lua' on the Tools menu, and by default it will have a shortcut Ctrl+1. command.subsystem must be set to 3, which is the Lua extension. In this case, the global Lua function is dofile which has been kept in SciTE's Lua 5 precisely because it's so useful in this case; please note that there is no argument list after dofile. There can be at most one argument passed to your function, but like anything in a SciTE properties file, it can contain references to other SciTE properties, like FilePath, which is the full path of the currently shown file. (See the section called 'Properties File' in the SciTE manual for a full list of these dynamic properties)

Now, if you edit a file containing this:

  print ("Hello, World!")

and pressed Ctrl+1, "Hello, World" will be shown in the SciTE output window. If you have modified this file, then SciTE will prompt you first to save; this is the default behaviour. This is a powerful way to begin Lua programming in the SciTE environment.

There is a potential problem with this definition; some other script may have defined the command 1; you can have commands from 0 to 9 with this method, bound to Ctrl+0 .. Ctrl+9. But you can use any number less than 50 in these definitions, as long as you give an explicit shortcut key:

command.name.11.*=Load Lua
command.subsystem.11.*=3
command.11.*=dofile $(FilePath)
command.mode.11.*=savebefore:yes
command.shortcut.11.*=F9

You can rebind a key that has already been defined by SciTE or Scintilla. There are a lot of these, so consult the documentation.

Here is an alternative which doesn't require the document to be on disk - it executes all the source in the current buffer:

command.name.1.*=Run Document as Lua Extension
command.subsystem.1.*=3
command.1.*=dostring dostring(editor:GetText())
command.mode.1.*=savebefore:no

A simple SciTE Macro

Put this in a Lua file (say test.lua) in your home directory

function make_uppercase()
  local sel = editor:GetSelText()
  editor:ReplaceSel(string.upper(sel))
end

and in your properties file, put this

ext.lua.startup.script=$(SciteUserHome)/test.lua

command.name.12.*=Make Selection Uppercase
command.subsystem.12.*=3
command.12.*=make_uppercase
command.mode.12.*=savebefore:no
command.shortcut.12.*=Ctrl+M

Now, after selecting text, you can make it uppercase with Ctrl+M. Because of the savebefore:no, it won't prompt you to save first before executing.

OK, it's true that SciTE already has such an operation. But you can do just about anything with Lua in SciTE! I would suggest that a good way to learn is to experiment using the dofile trick.

Looking up the correct form of a Scintilla function

The best reference for the Lua Scintilla bindings is scintilla.iface, which is in the Scintilla include directory. If you look for GetLength you will find:

 # Returns the number of characters in the document.
 get int GetLength=2006(,)

The 'get' means that there is a read-only property called Length. It will be called like this: editor.Length.

whereas looking for GetText we get:

 # Retrieve all the text in the document.
 # Returns number of characters retrieved.
 fun int GetText=2182(int length, stringresult text)

So GetText is a plain function, which is passed a string. It will be editor:GetText() - note the colon!

The Lua bindings are not always consistent, for example GetSelText is a function, not a property.

An annotated version of the Scintilla documentation for the Lua interface can be found here: http://scite-interest.googlegroups.com/web/ScintillaSciteDoc.html

Getting and Modifying Document Text

editor:GetText() will return the full text of the current document, and editor:SetText(s) will replace the current contents of the document with the string s.

editor:GetLine(n) will get all the text of the line n, including any end-of-line characters. Remember that under Windows there will be two of these ('\r\n'); all line numbers are zero-based. The following is a simple function to remove the end-of-line characters:

  -- removes end-of-line characters in a string
  function Chomp(line)
    return string.gsub(line, "[\r\n]+$", "")
  end

editor:GetSelText() will retrieve the currently selected text.

The length of the document in characters is editor.Length and in lines is editor.LineCount; note the different syntax used here, since Length and LineCount are properties. Another example is editor.CharAt[p] which will get the character at position p. This will be the character code, so use string.char(ch) to generate a string:

  -- returns the character at position p as a string
  function char_at(p)
     return string.char(editor.CharAt[p])
  end

editor:textrange(p1,p2) will get the text between p1 and p2; (this is a SciTE pane function). So an alternative way to get the character as a string at a position p is editor:textrange(p,p+1).

editor:ClearAll() will clear the document.

editor:AppendText(s) will append s to the end of the document, and editor:InsertText(p,s) will insert s at the position p; a position of -1 means the current position. This is where the caret is currently displayed; in all cases, please note that InsertText won't scroll the text into view. editor:ScrollCaret() will do that for you.

editor:ReplaceSel(s) will replace the selection with s. Here is a function that will enclose the selected text in bold tags:

  function make_bold()
    local txt = editor:GetSelText();
    editor:ReplaceSel('<b>'..txt..'</b>')
  end

Selection and Position Information

To move to a new position, use editor:GotoPos(p) or editor:GotoLine(l). They will always make the caret visible.

Given a position p, editor:LineFromPosition(p) will give you the line, and editor:PositionFromLine(l) will give you the position at the start of the line. If you need the position at line end, use editor.LineEndPosition[l] (note that properties are accessed as if they were arrays).

editor.CurrentPos will return the current caret position; this is a writeable property, so editor.CurrentPos = p also works, but it doesn't have the same meaning as editor:GotoPos(p). The selection in Scintilla is between the anchor and the position, so if there was an existing selection, then setting the position directly would change the selection. editor:SetSel(p1,p2) is the best way to explicitly set the selection.

To find out the currently visible part of the document, use editor.FirstVisibleLine to find out the starting line number, and editor.LinesOnScreen to find out the number of lines visible on the page.

center_pos() is a useful function that uses this information to center the display around a position.

 -- this centers the cursor position
 function center_pos(line)
  if not line then 
     -- this is the current line
     line = editor:LineFromPosition(editor.CurrentPos)
  end
  local top = editor.FirstVisibleLine
  local middle = top + editor.LinesOnScreen/2
  editor:LineScroll(0,line - middle)
 end

SciTE Properties

There is a pseudo-array called props which can access any defined SciTE property. For example, props['FilePath'] will give you the full path to the file currently being edited. Here is a very simple function which will swap a C++ file with its header, assuming that the extensions are only .cpp and .h:

  function swap_header()
    local cpp_ext = 'cpp'
    local h_ext = 'h'
    local f = props['FileName']    -- e.g 'test'
    local ext = props['FileExt']   -- e.g 'cpp'
    local path = props['FileDir']  -- e.g. '/home/steve/progs'
    if ext == cpp_ext then
       ext = h_ext
    elseif ext == h_ext then
       ext = cpp_ext
    end
    scite.Open(path..'/'..f..'.'..ext)
  end

Please see the section called 'Properties File' in the SciTE documentation for a full list of properties set by the environment.

Remember that parameters as defined by View|Parameters are accessable as prop[1],prop[2],prop[3] and prop[4].

You can of course access any defined properties, for example props['position.height'] will give the initial height of the SciTE window. Special properties may be defined which are meant to be read only by scripts. To make swap_header() more general, define a property called 'cpp.swap.ext' to be your C++ source extension of choice and set cpp_ext to this.

  local cpp_ext = props['cpp.swap.ext']
  ...
Then define 'cpp.swap.ext=cxx' (or whatever) in your Local properties file.

It is possible for a script to change properties, although of course this will be only temporary. Here is something that makes life easier for script developers; normally, each Lua function to be called needs to be specified in a property file, but there's no reason why those properties can't be autogenerated. Here is the important part of scite_Command from SciteExtMan.

     -- we are fed something like 'Process File|ProcessFile|Ctrl+M'
     local name,cmd,shortcut = split3(v,'|')
     local which = '.'..idx..'.*'
     props['command.name'..which] = name
     props['command'..which] = cmd     
     props['command.subsystem'..which] = '3'
     props['command.mode'..which] = 'savebefore:no'
     props['command.shortcut'..which] = shortcut

Searching and Replacing

To find text in the current document, use editor:findtext(). It returns two positions representing the returned range, nil if no match is possible. This function prints out all lines having some given text:

  function all_lines_with_text(txt,flags)
    if not flags then flags = 0 end
    local s,e = editor:findtext(txt,flags,0)
    while s do 
      local l = editor:LineFromPosition(s)
      trace(l..' '..editor:GetLine(l))
      s,e = editor:findtext(txt,flags,e+1)
    end    
  end

(Here I'm using trace() instead of print() because the line already has a line feed)

The search flags are a combination of SCFIND_MATCHCASE, SCFIND_WHOLEWORD, SCFIND_WORDSTART, and SCFIND_REGEXP. By default, the search is a plain case-sensitive search. all_lines_with_text('for',SCFIND_WHOLEWORD) would show all for-statements in a C file, all_lines_with_text('^#',SCFIND_REGEXP) would show all preprocessor statements (i.e. all occurances of '#' which occur at the start of the line). Please note that SciTE regular expressions are different from Lua's - see 'Searching' in http://scintilla.sourceforge.net/ScintillaDoc.html for details.

The easiest way to do a search-and-replace is using editor:match(), which gives us an iterator:

  function replace_all(target,repl)
    editor:BeginUndoAction()
    for m in editor:match(target) do
      m:replace(repl)
    end
    editor:EndUndoAction()
  end

Using BeginUndoAction() is the general way to make sure that a number of changes can be undone at once.

Markers

SciTE uses markers to implement things like bookmarks and for marking lines with errors. There are 32 possible markers, and Scintilla makes markers 0 to 24 available for general use; SciTE uses 0 for error lines and 1 for bookmarks. For example, editor:MarkerAdd(line,1) will put a bookmark at line, and SciTE will treat it just like any other bookmark, since it finds bookmarks using the internal Scintilla list. Please remember, as always, that Scintilla counts lines from zero.

Here is a useful function for defining a custom marker:

  local colours = {red = "#FF0000", blue = '#0000FF', green = '#00FF00',pink ="#FFAAAA" ,
					black = '#000000', lightblue = '#AAAAFF',lightgreen = '#AAFFAA'}

  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 marker_define(idx,typ,fore,back)
    editor:MarkerDefine(idx,typ)
    if fore then editor:MarkerSetFore(idx,colour_parse(fore)) end
    if back then editor:MarkerSetBack(idx,colour_parse(back)) end
  end

User Lists

These are drop-down lists which allow the user to choose from a number of items, which SciTE uses for 'Complete Symbol', etc. They are not difficult to use; you supply a string with a specified separator character, and the OnUserListSelection event is fired when the user selects an item.

 function UserListShow(list)
   local s = ''
   local sep = ';'
   local n = table.getn(list)
   for i = 1,n-1 do
      s = s..list[i]..sep
   end
   s = s..list[n]
   editor.AutoCSeparator = string.byte(sep)
   editor:UserListShow(12,s)
   editor.AutoCSeparator = string.byte(' ')
 end

The tricky thing here is that the property AutoCSeparator is passed a character code, not a string. The '12' is just a number that SciTE doesn't use internally.

Here is an event handler which assumes that the strings represent Lua scripts in some directory. The idea here is to present the user with a list of little-used 'once-off' scripts, which would otherwise clutter the Tools menu.

 function OnUserListSelection(tp,script)
   if tp == 12 then 
      dofile(path..'/'..script..'.lua')
   end
 end

Building up the list of files requires a little work. Here is a non-Windows solution to this problem:

 function GetFiles(mask)
   local files = {}
   local tmpfile = '/tmp/stmp.txt'
   os.execute('ls -1 '..mask..' > '..tmpfile)
   local f = io.open(tmpfile)
   if not f then return files end  
   local k = 1
   for line in f:lines() do
      files[k] = line
      k = k + 1
   end
   f:close()
   return files
 end

Code that works on both Unix and Windows is a little tricky. See scite_Files() in SciteExtMan for a more complete solution.

Indicators

Indicators are not currently used in SciTE, but scripts can easily add them. They could be used by a spell check utility to underline misspelt words with a red line, or dodgy syntax with a squiggly green line. There are three available indicators with the usual SciTE settings. Here is a function which will use the given indicator (0,1 or 2) to underline len characters starting at pos.

 function underline_text(pos,len,ind)
   local es = editor.EndStyled
   editor:StartStyling(pos,INDICS_MASK)
   editor:SetStyling(len,INDIC0_MASK + ind)
   editor:SetStyling(2,31)
 end

To remove underlining, use underline_text(pos,len,-1). The last SetStyling() call is necessary to restore the lexer state; 31 is the mask for the lower 5 bits, which are used for styling. The defaults can be changed, if necessary.

The default indicators are

   0 green squiggly line
   1 light blue line of small T shapes
   2 light red line

The available indicator styles are

  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.

Indicators with version 1.70 and maybe previous

You have these flags available :
   0 resets the style
   INDIC0_MASK green word processing-like line
   INDIC1_MASK bizarre blue line
   INDIC2_MASK blue round background box
And you case use it like this :
editor:StartStyling(editor.SelectionStart,INDICS_MASK)
editor:SetStyling(string.len(editor:GetSelText()),flag)

RecentChanges · preferences
edit · history
Last edited May 21, 2017 3:20 pm GMT (diff)