rundis / lt-snippets

Snippets/templates support for Light Table
MIT License
12 stars 4 forks source link

LightTable Snippets

Snippets support for Light Table

This plugin is loosely inspired by the template support in TextMate and YASnippet. It is not intended to be a straight port and will utilize the power of clojurescript when more advanced features are added. For simple yasnippets it might be possible to write a simple conversion util without to much hassle.

Installing

The plugin can be installed using the LightTable plugin manager (or clone this repo to your plugins folder, make sure you call the folder Snippets !). You probably will need to reload behaviors for the plugin to work.

(If you installed by cloning, you'll need to touch one of the cljs files from within lighttable to make sure you get compiled version of the latest source)

NOTE

If you have a version prior to 0.1.0, you will need to uninstall that before using this plugin.

Getting started

Contributing

Pull requests are welcome. However pls don't include the compiled files (snippets_compiled.js and snippets_compiled.js.map) in the pr.

Features

View available shortcuts (and select if you wish)

You just open the command pane (ctrl-space) and select the Snippets: Select snippet command. A list of defined snippets will be presented. Each item presented with name, key and keyboard shortcut (if defined, see below).

Creating shortcuts

Its quite easy setting up shortcuts, just edit your user.keymap file.

[:editor.javascript "ctrl-t ctrl-c" (:snippet.by-key "tc")]
[:editor.javascript "ctrl-a ctrl-e" (:snippet.by-key "ae")]

Invoke snippet by expanding key

Type the key in your editor and then select the command Snippets: Expand by editor token.

Snippet hints/autocomplete (Incubating feature)

Snippets that are applicable for the current editor will be shown in the autocomplete/hints resultlist. When you select an snippet item, it will invoke the snippet.

Caveats:

Should you find in annoying, just disable the behavior:

;; in your user.behaviors file
;; note the colon-minus `:-` prefix which removes behaviors

[:editor :-lt.plugins.snippets/use-local-hints]

Key conflicts

When more than one snippet matches a given key (for a given editor type), a select list will popup inline prompting you to select one of the alternatives.

Snippet templates location

Default location for snippets are set to $lthome/User/snippets. Currently reads any .edn file residing in that directory. It will also walk any subdirectories. There is no limits to how many files you can have. You can put all snippets in one file, or split them into several.

Overriding location

Just modify your user.behaviors to include:

[:snippets.loader :lt.plugins.snippets.loader/set-snippet-dir
"/Users/mrundberget/.lighttable/snippets"]

;; absolute path to where you'd like your snippet root directory to be

If the directory does not exist it will be created.

Snippet definition files

NOTE: Will most likely change a bit in upcoming versions

{
  :modes {:+ #{:editor.javascript}}
  :helper "clojure-helper.js"             ; optional
  :snippets [
    {:name "Buster TestCase"
    :key "tc"
    :snippet-file "testcase.snip"
    :modes {:+ #{:editor.typescript}}     ; optional (probably rarely used)

    {:name "Buster Assert Equals"
    :key "ae"
    :no-indent true                       ; optional
    :snippet "assert.equals($1, $2);$0"}]}

Data format description

More on snippet modes

Snippet modes uses a syntax similar to Light Table behaviors. The modes specified will be matched against the defined tags for the editor in which you are about to expand a snippet.

{ :modes {:+ #{:editor.foo} :- #{:editor.bar}} }
;; Means the snippet will resolve if:
;; * the editor has a tag :editor.foo
;; * but not if the editor also has a tag :editor.bar

Modes can be specified at "group" level, meaning the :modes property in the top level map of your snippet definition file. This will the apply to all snippets in the definition file, unless you specify :modes at the snippet level.

{:+ #{:editor.foo :editor.bar}
 :- #{:editor.transient :editor.plugin }} ;; modes at "group" level

{:+ #{:editor.baz :editor.transient}
 :- #{:editor.foo :editor.foobar}} ;; modes at "snippet" level

;; will resolve to the following modes for the snippet:
{:+ #{:editor.bar :editor.baz :editor.transient}
 :- #{:editor.plugin :editor.foo : editor.foobar}}

Snippet format

Linebreaks/indentations is currently important in your snippet templates for nice display in snippet completion mode. Another reason to have templates beyond one liners in a separate file.

Placeholders

You can have placeholder values for your tabstops. The syntax is ${1:placeholder} When prompted to complete a snippet the placeholder value will be highlighted (if you wish to keep it just tab to next tabstop).

Snippet navigation

Inline scripts

Warning: Scripts are being executed using JavaScript eval. Use at your own risk !

Arbitrary scripts
   /** Created: ${__new Date()__} **/

When the snippet is expanded any blocks with ${something} will be replaced with what the expression resolves to (using javascript eval). In the example above the current date will be shown. You should note that eval only return the result of the last statement. To keep your snippets understandable, you're better off creating helper functions that you bundle in a helper script with your snippet definitions

Script in tabstops
package ${1:__snip$.groovy.suggestPackage()__}

/** Name: $2 **/
class ${2:__snip$.currFileNameSansExt()__} {
    ${__snip$.wrapSelection()__}$0
}

Tabstops with code take precedence of tabstops with just numbering. The code within tabstops are resolved and shown as a placeholde value when the snippet is displayed for completion. Mirrored tabstops work as normal. In the example the snippet for completion with show the filename (if editor has been saved previously!) as classname, and its value mirrored in the comment section.

Mirrored tabstops with transformations

Hello ${1:Dill} results in: ${1:__snip$.groovy.toUpper__}

When this snippet is displayed for user input, it will display Hello dill results in: DILL Changing the text of the tabstop ${1:Dill} will invoke the transformation funtion of the mirror. Transformation functions should accept one parameter, the value of the "master" tabstop at any given time. The function should most likely also return av value.

For the simple example above you could actually do this Hello ${1:Dill} results in: ${1:__(function (txt) { return txt.toUpperCase(); })__}

Sample helper functions file
(function(window) {
  function suggestPackage() {
    var p = snip$.currPath();
    if(p) {
      var parts = snip$.path.dirname(p).split(/src\/main\/groovy\/|src\/test\/groovy\//);
      if(parts.length === 2) {
        return parts[1].replace(/\//g, ".");
      }
    }
  }

  function toUpper(txt) {
    return txt ? txt.toUpperCase() : txt;
  }

  snip$.groovy = {
    suggestPackage: suggestPackage,
    toUpper: toUpper
  };

})(window);

There are few limits to what you can put in your helper files. You may use require, call javascript compiled from ClojureScript from LightTable. You may wish to call CodeMirror directly for some things etc. Calling the javascript outputted from the clojure compiler might not always be for the faint hearted though.

It the sample above, the custom object has been added to the already globally available snip$ object.

Default helper functions

The following functions are included by default exposed through snip$ (on the window object)

currPath: currPath, currFileName: currFileName, currFileNameSansExt: currFileNameSansExt, path: path, wrapSelection: wrapSelection, wrapSelectionEager: wrapSelectionEager

Function/Property Description
currFileName Name of file for current active editor. Returns null editor is transient
currFileNameSansExt Same as above but without file extension
path Node path object. Handy for working with paths
wrapSelection Useful in templates when you wish your snippet to wrap-around an expression/selection you have made prior to expanding a snippet. If you esc from a snippet completion, the selection is not restored, but its in your clipboard so it's not lost !
wrapSelectionEager Same as above, but will select current line if no selection has been made prior to expanding the snippet

Roadmap

Version

License

MIT license, same as Light Table. See LICENSE.md for the full text.