Tuesday, April 19, 2005
The SciTE Programmer's Editor
A good programmer's editor is an essential tool. It's amazing what people have done just using Notepad, and sometimes that's all you need (use a good replacement for a superior driving experience). To do serious work, you need a text editor which understands how code works.
Programmers have strong opinions about editors, which is actually not too suprising considering that it's basically where they live when coding. Some swear by EMACS, but I can't remember all the key bindings and customizing it is a lot of work. If I'm not in Visual Studio, then I'm probably using SciTE. It understands most languages, including HTML, does syntax highlighting, folding, automatic indentation, all the standard stuff. It's completely free (with a liberal Python-style licence), is written in a clean and maintainable C++, and runs on both Windows and GTK. It is compact and efficient, which are some of my favourite software virtues. And it's scriptable in Lua.
Languages like C# are pretty verbose, which isn't a problem for reading, but tedious to type. SciTE allows you to specify abbreviations; edit the abbrev.properties file (Options|Open Abbreviations File) and put in these lines:
wl=Console.WriteLine(|)
p=public |
ps=public static |
if=if (|) {\n}\n
cl=public class | {\n\n}\n
sum=///\n | ///
To insert an abbreviation, type it followed by ctrl-B. After the text has been inserted, the cursor position will be left at the spot indicated by '|'. (This does mean that abbreviations can't contain '|'.) '\n' indicates a newline as usual. You will find that typing 'wl'(ctrl+B) is much easier on the fingers than 'Console.WriteLine()'.
Scripting with SciTE
SciTE scripting actually got me into Lua, which is a little language which comes from Brazil. Here I want to give a flavour of how SciTE scripts can make common coding tasks easier. The first example is declarating and initializing variables in C#, where there is a lot of repetition involved:
ArrayList list = new ArrayList();
int[] arr = new int[30];
What I want is a command which will complete a declaration, so after I type
ArrayList list followed by Ctrl+D, the editor will fill out the redundant details. In the case of array declarations it can't of course judge the size, but will leave the cursor between the square brackets. I'm assuming that the C# declaration is on a fresh line, so that the first word will be the type (I'm assuming that you don't leave spaces in array types like 'int[]') which is extracted from the line by a Lua regular expression. If it was not an array, then it needs an extra '()' to make it a constructor call.
scite_Command 'Complete Declaration|complete_declaration()|Ctrl+d'
function complete_declaration()
local line = editor:GetCurLine()
-- fetch the first non-blank token on the line
local s1,s2,classname = string.find(line,'%s*([^%s]+)')
if not s1 then return end
local init = ' = new '..classname
-- if it ends with ']', then we don't have to make it a constructor call
local was_array = string.sub(classname,-1) == ']'
if not was_array then
init = init..'()'
end
init = init..';'
-- this will leave the cursor at the end of the inserted text
editor:AddText(init)
-- generally, people are going to have to specify a size for the array
-- so put the cursor inside the []
if was_array then
editor:CharLeft();
editor:CharLeft();
end
end
To read this code, mentally replace '%' by '\' in regular expressions (Lua uses '%' because strings may contain C-style escapes.) Note that string concatenation is '..', not '+', and that methods (like
editor:GetCurLine()) are called using ':' and not '.'. The standard libraries are inside tables, and do use '.', which is the equivalent of being inside a namespace. Otherwise Lua has a fairly standard BASIC-like syntax. It is considered good style to make your variables explicitly local; otherwise variables are implicitly considered global. This macro uses extman, which is a Lua script that simplifies extending SciTE. Please find the latest version here, which also includes the example C# scripts here as cs.lua. Extract this zip file to your SciTE program directory (please ensure that the directory structure is preserved), and place the following line in your global properties file (Options|Open Global Options File):
ext.lua.startup.script=extman.lua
There will be a subdirectory called scite_lua, which contains the actual scripts. To add new scripts, merely add .lua files to this subdirectory. (They will only become active when SciTE is restarted.) Any new commands will be available from the Tools menu.
Using Ctags
Exuberant Ctags is one of those tools that make life much easier for programmers. The command
ctags *.cs executed in your source directory will create a file tags which contains a sorted list of definitions, or tags, that editors can use to navigate around in code. The SciTE interface to Ctags is written completely in Lua, and supplies a 'find tag' command (Ctrl+., that's a period) and 'go to mark' command (Alt+.). You may go to the definition of a class or a field using ctrl+., and this automatically sets a mark. So getting back to your original place in the code is simply Alt+. You can always directly 'push' a mark explicitly with (Ctrl+,) This is useful before a Find operation. To use ctags, make sure that extman is configured as above and ctags has been executed, and put this in your properties file:
ctags.path.cxx=/your_source_directory/tags
Using Macros
Last year I wrote a simple macro preprocessor which expands macros in the current buffer, rather like abbreviations. (If you downloaded the latest version of extman, it will be already present.) For example, I've always found the C-style for-loop tedious to type. If I put this in my global properties file
macro.subst.1=for(i,n)=for(int i = 0; i < n; i++)
and type
for(k,10), followed by 'Expand Macro' (Alt+Enter), then a single-pass expansion takes place and for(k = 0; k < 10; k++) is inserted into the buffer. (And this all took only 200-odd lines of Lua.) The most interesting aspect of this macro facility is that you can use it to call Lua code. Any function in a macro definition that begins with '$' is assumed to be an available Lua function; I've supplied a few convenient functions like $eval, which evaluates Lua expressions, $cat to do token-pasting, and $quote to do 'stringizing'.
macro.subst.2=date=$eval(os.date())
macro.subst.3=cs(item)=case $quote(item): return item;
Typing
date(Alt+Enter) will insert the current date and time into your document, and typing cs'FINISH'(Alt+Enter) will insert 'case "FINISH": return FINISH'. (In the Lua spirit, parentheses around a single string parameter are not necessary.) And of course you can write your own Lua functions to be called. That makes these macros a way to execute arbitrary code which may have side effects (and why not?) They may not even result in any substitution other than the empty string! I think of it as a way to deal with the interface problem that any extendable system has. SciTE doesn't currently allow users to arbitrarily configure the menu structure. All user commands end up on the Tools Menu, although with the 1.64 preliminary release one can now add items to the context menu. There are a limit to how many hot keys a normal user (such as myself) can remember, and besides Scintilla has already already mapped most of these onto editing functions. So mnemonic short codes such as used in Abbreviations and Macros make sense, especially since programmer's minds are already pretty good at remembering APIs.Generating Public Properties
Sometimes good practice involves extra work. For instance, data fields should be private, and be exposed as public properties. This macro simplifies the job of generating public properties:
function Prop(type,var)
local first_char = string.sub(var,1,1)
first_char = string.upper(first_char)
local prop_name = first_char..string.sub(var,2)
local text = [[
$type $var;
public $type $prop_name {
get { return $var; }
set { $var = value; }
}
]]
local gsub = string.gsub
text = gsub(text,"$type",type)
text = gsub(text,"$var",var)
text = gsub(text,"$prop_name",prop_name)
return text
end
add_macro 'prop(type,var)=$Prop(type,var)'
In Lua, everything between [[ and ]] is considered a string; it's almost exactly what C# achieves with @"...". The template string contains placeholders starting with '$', and string.gsub does the substitutions. With this definition,
prop(int,width) would expand using Alt+Enter to:
int width;
public int Width {
get { return width; }
set { width = value; }
}
Generating Strongly-typed Containers
Strongly-typed containers are not only more convenient but make your code clearer. It becomes a compile-time error to attempt to add objects of the wrong type, and objects can be extracted without typecasts. They unfortunately involve a lot of typing. Creative laziness involves getting the computer to do the typing involved in strong typing. First, we define a function which generates a new source file containing the collection.
local template = [[
using System;
using System.Collections;
public class $List : CollectionBase {
public int Add($ s) {
return List.Add(s);
}
public void Remove($ s) {
List.Remove(s);
}
public $ this[int i] {
get { return ($)(List[i]); }
}
// this is non-standard, but rather useful!
public $[] AsArray() {
$[] arr = new $[List.Count];
List.CopyTo(arr,0);
return arr;
}
}
]]
function list(type)
local classname = type..'List'
local filename = classname..'.cs'
-- does this class already exist?
local f = io.open(filename,'r')
if f then
f:close()
print(filename..' already exists')
return
end
local body = string.gsub(template,'%$',type);
local f = io.open(filename,'w')
f:write(body)
f:close()
return ''
end
scite_require 'macro.lua'
add_macro 'list(name)=$list(name)'
Say I had a type
Customer (which seems the traditional name to use in these examples); I would type list(Customer) in the editor, and then expand the macro with Alt+Enter. The result of the expansion is the empty string, but it has a side-effect of generating a source file CustomerList.cs code>. This you will still have to add manually to your project, unless you are a fan of csc /recurse:*.cs.
Inserting Custom Comments
Continuing the theme of macros which have side-effects, consider the following need: we wish to enter dated notes into source, but also keep track of these notes. Typing note'please remember to change this!' followed by Alt+Enter results in //*note 04/07/05 sdonovan: please remember to change this! and updates a text file with the comment and the file and line number.
local filename
function set_filename(f)
filename = f
end
scite_OnSwitchFile(set_filename)
scite_OnOpen(set_filename)
function current_line_number()
return editor:LineFromPosition(editor.CurrentPos)+1
end
function note(msg)
local comment = '//*note '..os.date('%d/%m/%y')..' '.. os.getenv("USERNAME")..':'..msg
local f = io.open('comments.txt','a')
f:write(comment..'\n')
f:write(filename..':'..current_line_number()..'\n')
f:close()
return comment
end
add_macro 'note(msg)=$note(msg)'
There's no direct way to find the file currently being edited, but one can subscribe to SciTE events (here managed by extman) which keep us up to date. io.open takes a second argument which works just like the C function fopen.
Comments:
<< Home
There were three guys, wow gold a Torontonian, 2moons power leveling an American world of warcraft power leveling and a Newfoundlander.archlord power leveling They were all going to be executed.wotlk gold The executioner Warhammer Power Leveling said that since all three flyff power leveling were to be executed that night, world of warcraft power leveling that they would each get to choose the flyff money method 2moons power leveling by which they would dieAtlantica power leveling Their choices were: world of warcraft power leveling lethal injection, electric chair Knight Gold or by hanging. 2moon dilThe American was afraid of Aion Power Leveling needles and did'nt power leveling want to be hanged.buy archlord gold The American 2moons dil chose the electric chair. He sat in the chair archlord power leveling and they pulled the flyff gold switch and nothing happened.
Post a Comment
<< Home

