10 January 2016

I’ve been a big fan of the Textadept editor since I first used it in a head-to-head comparison with several alternatives (including jEdit, Sublime Text) to my decades-old default Vim. One of the things that appeals to me is that it’s almost as flexibly-coded as Emacs, but with saner defaults, without Lisp, and without the decades of cruft.

I’d like to introduce a small example of this today with a templating system I’ve come up with for Textadept that supplements the snippets system baked into the editor.

Motivation

The official, baked-in snippets system in Textadept is very nice for short snippets like for loops and the like, but for other use cases it suffers from a few deficiencies.

  1. I’m not fond of manipulating code to enter data in a user program. I far prefer the model of data existing separately from code, even if that means adding a bit of complexity to the code to deal with it.

  2. I have some rather large "snippets". For example I have a "snippet" for Rexx modules that includes all the boilerplate for module-level comments, setting up error handling, etc. that’s well over 200 lines long. Putting this into my ~/.textadept/init.lua file made the file very hard to navigate, especially since that’s just one of many such snippets I have.

  3. It’s hard to share snippets that are baked into individualistic, often idiocyncratic code blocks. (I’ve done …​ things …​ with code-based snippets. Things I’m not proud of.)

Implementation

The implementation rests on three pillars: template files, an event handler, and a glue function that brings it all together.

Template files

In this system snippets are stored in files: one file per snippet. The files must follow a naming convention of <language>.<key> where <key> is the character sequence that starts a snippet (triggered by [TAB] when typing) and <language> is the lexer name for the language being edited. So my aforementioned Rexx module snippet is named rexx.mod and is triggered by typing "mod" (by itself) followed by the [TAB] key.

Inside the file the standard Textadept syntax for snippets is used without alteration. It can be as simple as cutting and pasting existing snippets configurations into the file.

Glue function

A glue function (load_snippets()) is placed into init.lua for building up the Textadept runtime database of snippets. It’s only job is to search the _USERHOME .. '/templates/' directory for files that begin with the passed-in lexer name. For each one that it finds, it extracts the key and inserts that into the language-specific snippets table. (The source for this is at the end of the article.)

Event handler

Having the glue function isn’t much help without calling it. The time to call it is on the LEXER_LOADED event that Textadept fires off each time it loads a new lexer. At this point the language is known, so a call to load_snippets() with the language name imports all of the snippets for that language.

The function has been designed such that it can be passed directly to events.connect() if loading snippets is the only lexer-specific processing that has to be done. Of course if other processing is done, it’s simple to add a single line to your existing event handler to call load_snippets().

Source

init.lua sample
-- ft=lua
local templates_dir = _USERHOME .. '/templates/'
local function load_snippets(language)
  for template in lfs.dir(templates_dir) do
    b, e, key = string.find(template, '^' .. language .. '%.(.+)$')
    if b then
      file = io.open(templates_dir .. template, 'r')
      snippet = file:read('a')
      file:close()
      snippets[language] = snippets[language] or {}
      snippets[language][key] = snippet
    end
  end
end

events.connect(events.LEXER_LOADED, load_snippets)
--[[
-- alternatively
events.connect(events.LEXER_LOADED,
  function(language)
    load_snippets(language)
    -- other lexer-specific settings here
  end)
--]]