____________________________________________________________________________ | | | ____ _____ | | | _ \ ___ ___ _ _| __/___ ___ _____ ___ ___ | | | |_| / _ \/ __| |/ | |_ / _ \| _ |_ _| |_ _/ _ \ | | | _ | |_|| |__| ' <| _| |_| | / | | _ | | |_| | | | |_| \_\___/\___|_|\_|_| \___/|_|_\ |_| |_| |___\___/ | | | |__________________________________________________________________________|
____ _____ | _ \ ___ ___ _ _| __/___ ___ _____ ___ ___ | |_| / _ \/ __| |/ | |_ / _ \| _ |_ _| |_ _/ _ \ | _ | |_|| |__| ' <| _| |_| | / | | _ | | |_| | |_| \_\___/\___|_|\_|_| \___/|_|_\ |_| |_| |___\___/

Lua scripts for SciTE =====================

First draft: 2025-04-23
Published:   2025-05-05

Table of contents:

Showing a couple of convenience functions I added to my favorite text editor.

This is part of a series of posts, the project intro article can be found here.

Motivation

In the Scintilla Text Editor, a very large portion of the program's behavior is controlled through properties. Check out the online documentation for a complete list, even if just to see how comprehensive the system is. Values are set and can be overwritten with the various layers of `.properties` files. For most of the properties, you don't even have to restart the program after changes. This provides a very nice environment to experiment with the setup, but the configuration is still static in the sense that most of the properties are accessible only through the files, and cannot be set quickly via graphical menus.

I have found that there are a couple of settings I keep adjusting pretty often, so I have leveraged the integrated Lua execution facilities inside SciTE to create a couple of scripts to adjust these on the fly. I invite you to join me, as I record for future-me how to do this next time.

Preparations

Before we begin, I would like to bring to your attention the following written resources that have helped me a great deal getting to know Lua scripting in SciTE:

As it is mentioned in one of the articles above, probably the first thing to set up is one-click script execution for quick prototyping.

Through "Options" > "Open User Options File" open the `.SciTEUser.properties` file (or you can use Local as well if you prefer), and add the following:

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

With these we define a new command item in the Tools menu, numbered zero (thus accessible with the shortcut Ctrl+0 from now on). It will use the Lua subsystem to execute the contents of the main editor panel as it is, as a script, without saving the contents beforehand.

You can try it by opening a new editor tab and typing simply something like:

> print("Hello World!")

When you invoke the new command through Tools or hit Ctrl+0, the message should appear in your output pane.

Screenshot from SciTE showing
    the text 'Hello World!' on the output.
Document content (left part of window) can be run directly as Lua script even without saving (output is displayed in the right-side pane).

Cycling edge mode

The `edge.mode` property sets how the editor indicates if the contents of a line is too long, i.e. it has more characters than the number set in `edge.column`.

For anyone wondering how this is even a thing and why don't we just use automatic line wrapping, I must say that I have never liked to use it for programming, as usually there are subtle semantics involved in determining where and how to split a long line, to keep readability at maximum.

The possible values of the property are the following.

This property might be a good example where the file extension (hence lexer) based distinction could come in handy for most people. I wanted something more simple though, so that I can just cycle through the options on the occasion when I want the definite vertical line, or would like to get rid of any indication.

> function cycle_edge_mode()
>     local mode0 = math.floor(props['edge.mode'])
>     local mode1 = mode0 + 1
>     if mode1 > 2 then
>         mode1 = 0
>     end
>     props['edge.mode'] = mode1
> 
>     names = {"None", "Vertical", "Highlight"}
>     print("Cycling edge.mode: "
>         .. names[mode0+1]
>         .. " ["
>         .. mode0
>         .. "] to "
>         .. names[mode1+1]
>         .. " ["
>         .. mode1
>         .. "]"
>     )
> end
Two screenshots from
    SciTE, one showing a vertical line after column 40, the other displaying
    all characters after the 40th on each line with a highlighted background.
Showcasing the edge modes available in SciTE.

Setting edge column width

The `edge.column` property controls the width threshold for `edge.mode` indication.

Often I work with files that I like to keep under 80 wide to behave well in terminals. For a long time that was kind of a standard in everyday programming as I understand, but more and more people argue for a wider default. I just hope this is something our visually impaired friends will be able cope with. For the time being, I am keeping my default at 100, in accordance with the Rust Style Guide.

In order to be able to set the property to a value given by the user, we can use the "View" > "Parameters" window, which is for exactly this type of situation, to set custom parameters to internal functions. The four parameter values can be accessed using `props[1]` to `props[4]` in our scripts.

> function set_edge_column()
>     local p1 = tonumber(props[1])
>     if p1 == nil then
>         print("Error: invalid props[1], set a numeric value at View > Parameters.")
>     else
>         local col0 = props["edge.column"]
>         props["edge.column"] = props[1]
>         print("Setting edge.column: from "..col0.." to "..props[1])
>     end
> end
Screenshot from SciTE
    showing the Parameters dialog, with four numbered input text boxes. The
    first value is set to 40.
Setting user-defined parameters for scripts.

In order to automatically open the dialog every time we run our script as a command, we will have to put an asterisk before specifying the function name in the command property (like `*set_edge_column`). You can find an example of this later in the article, around `command.9` in the last code block.

Screenshot from SciTE showing
    highlighted background now starting at column 45.
The script updates the visual indication right after we click "Execute" on the dialog.

Clear before execute

Our next set of functions control the behavior of the output pane. The first one is pretty straight-forward: should the program empty the contents of the output before executing a command? We can control this via the `clear.before.execute` property. I usually keep it at 1 (true = clear) for convenience, but there can be situations when this is not desirable, namely when we want to compare two or more consecutive runs of some kind.

> function toggle_clear_before_execute()
>     local mode0 = math.floor(props['clear.before.execute'])
>     local mode1
>     if mode0 == 0 then
>         mode1 = 1
>     else
>         mode1 = 0
>     end
>     props['clear.before.execute'] = mode1
>     print("Setting clear.before.execute: "..mode0.." to "..mode1)
> end
Screenshot from SciTE, with
    the output pane showing text from multiple command executions, one after
    the other.
After setting `clear.before.execute` to 0, the output pane keeps showing all the messages from our custom commands.

Please note that in the current form the script behavior might be somewhat surprising. With the property set to 1 (clear), you have to anticipate one step ahead if you would possibly not want to clear your output, because by merely executing this very script, your output will inherently be emptied one last time.

Cycling output scroll mode

Our last script for today deals with the scroll behavior at the output pane. The `output.scroll` property has the following possible values:

My default is 2 naturally, but with Rust sometimes I find myself tweaking this, as the compiler tends to put more interesting errors up top, yet also often a bunch of warnings come first...

> function cycle_output_scroll()
>     local mode0 = math.floor(props['output.scroll'])
>     local mode1 = mode0 + 1
>     if mode1 > 2 then
>         mode1 = 0
>     end
>     props['output.scroll'] = mode1
> 
>     names = {"None", "Return", "Bottom"}
>     print("Cycling output.scroll: "
>         .. names[mode0+1]
>         .. " ["
>         .. mode0
>         .. "] to "
>         .. names[mode1+1]
>         .. " ["
>         .. mode1
>         .. "]"
>     )
> end

Putting it all together

With our script(s) ready and tested, we can make them persist in the following way. For my four new scripts above, I added the following lines to `.SciTEUser.properties`:

> ext.lua.startup.script=$(SciteUserHome)/.config/SciTE/SciTE_functions.lua
> 
> command.name.6.*=Toggle clear before execute
> command.subsystem.5.*=3
> command.6.*=toggle_clear_before_execute
> command.mode.6.*=savebefore:no
> 
> command.name.7.*=Cycle output scroll mode
> command.subsystem.7.*=3
> command.7.*=cycle_output_scroll
> command.mode.7.*=savebefore:no
> 
> command.name.8.*=Cycle edge mode
> command.subsystem.8.*=3
> command.8.*=cycle_edge_mode
> command.mode.8.*=savebefore:no
> 
> command.name.9.*=Set edge column width
> command.subsystem.9.*=3
> command.9.*=*set_edge_column
> command.mode.9.*=savebefore:no

Then, as you can see on the first line, I created a dedicated `.lua` file for keeping the functions, and load them at SciTE start-up.

Screenshot from SciTE, 
    'Tool' menu only, showing custom entries along with the usual internal 
    commands
Our command definitions show up in the menu along the usual internal entries.

Finally, I would like to mention that it is advisable not to forget to provide sensible defaults, for all the properties that you intend to set through the UI using one of these methods.