Using Lua With Scite |
|
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
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.
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
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
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
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
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.
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
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 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.
0 resets the style INDIC0_MASK green word processing-like line INDIC1_MASK bizarre blue line INDIC2_MASK blue round background boxAnd you case use it like this :
editor:StartStyling(editor.SelectionStart,INDICS_MASK)
editor:SetStyling(string.len(editor:GetSelText()),flag)