├── .gitignore ├── .whitesource ├── README.md ├── SUMMARY.md ├── assets ├── lt-tutorial.png ├── lt-user-plugin-beh.png ├── lt-user-plugin-eval.png ├── lt-user-plugin-hello.png ├── plugin-files.png └── statusbar-item.png ├── behaviors-file-reference.md ├── build-from-source.md ├── building-blocks.md ├── chapter1.md ├── creating-a-language-plugin.md ├── creating-a-lt-client-using-node.md ├── creating-a-skin.md ├── creating-plugins.md ├── dummy.text ├── frequently-asked-questions.md ├── helpful-resources.md ├── keybindings.md ├── light-table-clojurescript-tutorial.md ├── the-light-table-bot.md ├── user-plugin.md └── working-on-light-table-core.md /.gitignore: -------------------------------------------------------------------------------- 1 | # Logs 2 | logs 3 | *.log 4 | 5 | # Runtime data 6 | pids 7 | *.pid 8 | *.seed 9 | 10 | # Directory for instrumented libs generated by jscoverage/JSCover 11 | lib-cov 12 | 13 | # Coverage directory used by tools like istanbul 14 | coverage 15 | 16 | # Grunt intermediate storage (http://gruntjs.com/creating-plugins#storing-task-files) 17 | .grunt 18 | 19 | # Compiled binary addons (http://nodejs.org/api/addons.html) 20 | build/Release 21 | 22 | # Dependency directory 23 | # Deployed apps should consider commenting this line out: 24 | # see https://npmjs.org/doc/faq.html#Should-I-check-my-node_modules-folder-into-git 25 | node_modules 26 | 27 | _book/ 28 | book.pdf 29 | book.epub 30 | book.mobi 31 | -------------------------------------------------------------------------------- /.whitesource: -------------------------------------------------------------------------------- 1 | ########################################################## 2 | #### WhiteSource "Bolt for Github" configuration file #### 3 | ########################################################## 4 | 5 | # Configuration # 6 | #---------------# 7 | ws.repo.scan=true 8 | vulnerable.check.run.conclusion.level=failure 9 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # Light Table Developers Guide 2 | 3 | **Work in progress ...** 4 | 5 | Welcome to the Light Table Developers Guide. This guide has two key purposes; 6 | 7 | * Guide you on the what and hows for developing a Light Table plugin 8 | * Provide the instructions necessary for you if you wish to help out with contributing to the development of the core of Light Table 9 | 10 | > A plugin is the key mechanism in Light Table for customizing and extending its behaviours and features. The plugin system in Light Table is one of it's biggest strengths and it allows you to virtually infinitely customize or extend Light Table. Plugins allow you to do everything from just overriding some setting to almost completely changing the entire behaviour of Light Table. Through plugins you can implement things like; Skins, editor features, simple or advanced language support etc. Actually all your personal user settings is even implemented as a User plugin that is automatically set up for you when you install Light Table. 11 | 12 | ## A quick technical overview 13 | 14 | Almost the entire core of Light Table is implemented using [ClojureScript](http://clojurescript.org/). ClojureScript is a functional language that compiles down to JavaScript. It may be unfamiliar to you, but don't let that scare you off. It really is a super powerful language which you'll come to love once you put in the initial investment to get you up and running. You can check out the [Light Table ClojureScript Tutorial](/light-table-clojurescript-tutorial.md) to get you quickly started. 15 | 16 | Light Table is based on two key components: 17 | 18 | * [Electron](http://electron.atom.io/) - A framework for creating native Desktop web applications. It comes with two vital parts a node server and a chromium browser. 19 | * [CodeMirror](https://codemirror.net/) - An editor implemented in JavaScript for use in Web Browsers. The majority of the editor features in Light Table is based on CodeMirror and CodeMirror add-ons. 20 | 21 | This a very powerful platform which allows you to do some really awesome things. Anything you can do in a browser you can wield Light Table to do. Couple that with the power of having a Node.js server and the npm package eco-system at your disposal. 22 | 23 | If you are new to Light Table, let's move on to the [Getting Started chapter](/chapter1.md) ! 24 | 25 | -------------------------------------------------------------------------------- /SUMMARY.md: -------------------------------------------------------------------------------- 1 | # Summary 2 | 3 | * [Introduction](README.md) 4 | * [Getting Started](chapter1.md) 5 | * [Light Table ClojureScript Tutorial](light-table-clojurescript-tutorial.md) 6 | * [The Light Table BOT](the-light-table-bot.md) 7 | * [User plugin](user-plugin.md) 8 | * [Creating plugins](creating-plugins.md) 9 | * [Creating a Skin](creating-a-skin.md) 10 | * [Creating a Language Plugin](creating-a-language-plugin.md) 11 | * [Creating a LT Client Using Node](creating-a-lt-client-using-node.md) 12 | * [Building blocks](building-blocks.md) 13 | * [Behaviors File Reference](behaviors-file-reference.md) 14 | * [Working on Light Table Core](working-on-light-table-core.md) 15 | * [Build from Source](build-from-source.md) 16 | * [Frequently Asked Questions](frequently-asked-questions.md) 17 | * [Keybindings](keybindings.md) 18 | * [Helpful Resources](helpful-resources.md) 19 | 20 | -------------------------------------------------------------------------------- /assets/lt-tutorial.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/LightTable/developer-guide/180bdce77886d85f0ee50d1c24290331c9b6e651/assets/lt-tutorial.png -------------------------------------------------------------------------------- /assets/lt-user-plugin-beh.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/LightTable/developer-guide/180bdce77886d85f0ee50d1c24290331c9b6e651/assets/lt-user-plugin-beh.png -------------------------------------------------------------------------------- /assets/lt-user-plugin-eval.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/LightTable/developer-guide/180bdce77886d85f0ee50d1c24290331c9b6e651/assets/lt-user-plugin-eval.png -------------------------------------------------------------------------------- /assets/lt-user-plugin-hello.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/LightTable/developer-guide/180bdce77886d85f0ee50d1c24290331c9b6e651/assets/lt-user-plugin-hello.png -------------------------------------------------------------------------------- /assets/plugin-files.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/LightTable/developer-guide/180bdce77886d85f0ee50d1c24290331c9b6e651/assets/plugin-files.png -------------------------------------------------------------------------------- /assets/statusbar-item.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/LightTable/developer-guide/180bdce77886d85f0ee50d1c24290331c9b6e651/assets/statusbar-item.png -------------------------------------------------------------------------------- /behaviors-file-reference.md: -------------------------------------------------------------------------------- 1 | # Behaviors File Reference 2 | 3 | ## Loading JavaScript 4 | 5 | `[:app :lt.objs.plugins/load-js "myplugin_compiled.js"] ; This is the compiled js for your plugin` 6 | 7 | or multiple \(in order\) 8 | 9 | `[:app :lt.objs.plugins/load-js ["myplugin_compiled.js" "js/external.js"]]` 10 | 11 | ## Loading CSS 12 | 13 | `[:app :lt.objs.plugins/load-css "css/myplugin.css"]` 14 | 15 | ## Loading a keymap 16 | 17 | `[:app :lt.objs.plugins/load-keymap "myplugin.keymap"]` 18 | 19 | ## Associate new file types 20 | 21 | `[:files :lt.objs.files/file-types [{:exts [:elm], :mime "text/x-elm", :tags [:editor.elm], :name "elm"}]]` 22 | 23 | Light Table ships with a wide range of mapping to file types to provide syntax highlighting. However you might be developing a plugin for a new language not covered. This config allows you to associate a new file extentions and provide a tag for editors of this file type. In the example above we've added support for files from the Elm programming language. Every Elm file editor object will now get a tag `:editor.elm`.This is obviously useful when you want to configure behaviors that are specific for Elm editors \(say like code eval, docs etc\). 24 | 25 | ## Adding tags to objects of a given tag 26 | 27 | `[:editor.elm :lt.object/add-tag :docable] ;; this tag says that Elm editors supports behaviors for showing language docs` 28 | 29 | You can add additional tags to an object of a given tag by using `:lt.object/add-tab`. This is sometimes useful to allow more flexible configuration of objects and behaviors. 30 | 31 | ## Providing skins and themes 32 | 33 | ` [:app :lt.objs.style/provide-skin "superduper" "css/skins/superduper.css"]````[:app :lt.objs.style/provide-theme "super-light" "css/themes/super-light.css"]` 34 | 35 | * Skins override the std look and feel of all common Light Table elements \(sidebar, doc popups, workspace tree etc\) 36 | * Themes provides overrides of the look and feel of editors in Light Table 37 | 38 | 39 | 40 | -------------------------------------------------------------------------------- /build-from-source.md: -------------------------------------------------------------------------------- 1 | # Building from Source 2 | 3 | 4 | 5 | ## Prerequisites 6 | 7 | * [Leiningen 2.x](http://leiningen.org/) 8 | * [node.js and npm](https://nodejs.org/) 9 | 10 | ## OS Specific Prerequisites 11 | 12 | ### OS X 13 | 14 | None. Skip to [Build](#build). 15 | 16 | ### Windows 17 | 18 | Download [Cygwin](https://cygwin.com/install.html). 19 | 20 | You may need to change _\/etc\/fstab_ \(path in Cygwin\) to fix a directory permission error when building. Change this line: 21 | 22 | `none /cygdrive cygdrive binary,posix=0,user 0 0` 23 | 24 | Add `,noacl` to the line: 25 | 26 | `none /cygdrive cygdrive binary,posix=0,user,noacl 0 0` 27 | 28 | Close and re-open Cygwin Terminal. 29 | 30 | The issue: 31 | 32 | * ["mkdir: cannot create directory" error running branch build on Windows 7 · Issue \#1918 · LightTable\/LightTable](https://github.com/LightTable/LightTable/issues/1918) 33 | 34 | ### Linux 35 | 36 | To run electron on Linux you need to have libgconf-2.so.4 installed. 37 | 38 | Ref: [Linux \(Arch\) build depends on libgconf-2.so.4](https://github.com/LightTable/LightTable/issues/1926) 39 | 40 | > Note that, on Debian-based distros, you may need to install an additional package as there is a pre-existing _node_ package and the standard Node.js package on these distros installs a Node.js executable named `nodejs` instead of `node` as our build script expects. See issue [\#1931](https://github.com/LightTable/LightTable/issues/1931) for some background. 41 | 42 | # Build 43 | 44 | To build LightTable from scratch on OSX, Windows Cygwin or Linux: 45 | 46 | ```bash 47 | $ git clone https://github.com/LightTable/LightTable.git 48 | $ cd LightTable 49 | # Creates a directory in builds/ 50 | $ script/build.sh 51 | ``` 52 | 53 | This will take a few minutes the first time as electron and plugins are downloaded. Subsequent invocations are faster.To override the output directory, specify `$VERSION` e.g. `VERSION=0.8.1-pre script/build.sh`. 54 | 55 | On subsequent builds, use `script/build-app.sh` for quicker builds that don't require updating plugins or electron.If any ClojureScript files change, you must run `lein cljsbuild once app`. On Windows, you may need to comment out the :source-mapline before compiling ClojureScript to get around [issue 1025](https://github.com/LightTable/LightTable/issues/1025). 56 | 57 | # Usage 58 | 59 | Once you've built LightTable, run it in one of the following ways: 60 | 61 | * OS X 62 | * As a commandline executable: `builds/lighttable-0.8.0-mac/light` 63 | * As an application: `open -a $PWD/builds/lighttable-0.8.0-mac/LightTable.app` 64 | 65 | * Linux 66 | * As a commandline executable: `builds/lighttable-0.8.0-linux/light` 67 | * As an application: `builds/lighttable-0.8.0-linux/LightTable` 68 | 69 | * Windows 70 | * As an application: `builds/lighttable-0.8.0-windows/LightTable.exe` 71 | 72 | 73 | You can also run LightTable with `script/light.sh`. This script allows you to skip running `script/build-app.sh`. While it's useful as a dev convenience, final changes should be QAed with a fresh build from `script/build-app.sh`. 74 | 75 | -------------------------------------------------------------------------------- /building-blocks.md: -------------------------------------------------------------------------------- 1 | # Building blocks 2 | 3 | In this chapter we'll cover some typical basic building blocks you might need for your plugin\(s\). 4 | 5 | ## Adding items to the sidebar 6 | 7 | The right sidebar in Light Table allows for adding items dynamically. By default you have a command-bar, docs search, clients bar and file navigation bar. To illustrate how you might add an sidebar item of your own, let's create a super simple module browser\(\/listing\). 8 | 9 | ```clojure 10 | (ns lt.plugins.myplugin.modules 11 | (:require [lt.object :as object] 12 | [lt.objs.sidebar :as sidebar] 13 | [lt.objs.command :as cmd] 14 | [lt.util.dom :as dom]) 15 | (:require-macros [lt.macros :refer [defui behavior]])) 16 | 17 | (defui wrapper [this] // 1. 18 | [:div.modulebrowser.filter-list 19 | [:ul.results "Replace me..."]]) 20 | 21 | (defui module-item-ui [module] 22 | [:li 23 | [:p module]]) 24 | 25 | (defui modules-ui [modules] // 2. 26 | [:ul.results (map module-item-ui modules)]) 27 | 28 | (defn render-modules [this modules] // 3. 29 | (let [container (object/->content this) 30 | results-ul (dom/$ :ul.results container)] // 4. 31 | (dom/replace-with results-ul (modules-ui modules)))) // 5. 32 | 33 | (behavior ::show-modules // 6. 34 | :triggers #{:show-modules} 35 | :reaction (fn [this] 36 | (object/raise sidebar/rightbar :toggle this) // 7. 37 | (render-modules this dummy-modules))) // 8. 38 | 39 | 40 | (object/object* ::modulebrowser 41 | :tags #{:myplugin.modulebrowser} 42 | :label "My Plugin module browser" 43 | :order 2 44 | :behaviors [::show-modules] // 9. 45 | :init (fn [this] 46 | (wrapper this))) // 10. 47 | 48 | 49 | (def modulebrowser-bar (object/create ::modulebrowser)) // 11. 50 | (sidebar/add-item sidebar/rightbar modulebrowser-bar) // 12. 51 | 52 | (cmd/command {:command :show-nsbrowser 53 | :desc "MyPlugin: Show module browser" 54 | :exec (fn [] // 13. 55 | (object/raise modulebrowser-bar :show-modules))}) 56 | 57 | ``` 58 | 59 | 1. `defui` is a macro which helps produce html dom elements. Here we just create a wrapper div for our sidebar item 60 | 2. Much like in [hiccup](https://github.com/weavejester/hiccup) in `defui` we can work with markup as data structures. We can map, filter and the like. Here we map over the modules to produce `li` elements 61 | 3. We've created a function responsible for rendering our modules list 62 | 4. `dom/$` is a utility function for selecting the first item satisifying the given query selector. We retrieve the empty ul element 63 | 5. Now we replace the empty ul element from above with the ul element created from `render-modules` 64 | 6. We've created a behavior to allow the display of the modulebrowser to be configurable 65 | 7. We raise a `:toggle` trigger on the `rightbar` object in the `lt.objs.sidebar` namespace. The responding behavior 66 | will toggle the display of the sidebar and making sure that our modulebrowser is shown as the active\/visible item \(alternatively hiding the sidebar all together\) 67 | 8. We render our current list of modules. Currently hardcoded, so imagine the list of modules is created somewhat more dynamically ! 68 | 9. We've configured our modulebrowser object programtically to be tied to our show behavior. Normally you would do this in a `.behaviors` file for your plugin declaratively 69 | 10. The init function is called upon creation of our object. If the init function returns markup, that will be available 70 | through the `object/->content` function we saw in our `render-modules` function. 71 | 11. We create an instance of our modulebrowser object 72 | 12. This is how we add an additional item to the Light Table sidebar. It will be added, but not visible \(until we trigger the `:toggle` behavior mentioned previously 73 | 13. We've created a command so that you are able to invoke display\/toggling of the module browser. When the command is invoked, it will trigger our `show-modules` behavior 74 | 75 | > For a more advanced example you may want to look at this [blogpost](http://rundis.github.io/blog/2015/lt_react.html) for how you might use [React](https://facebook.github.io/react/) and [Quiescent](https://github.com/levand/quiescent) to render stuff in a sidebar. 76 | 77 | ## Adding an item to the statusbar 78 | 79 | The statusbar is a convenient area for showing small snippets of useful information in Light Table. It is located at the bottom of UI in Light Table. In this example we are going to create a simple status item that shows the number of lines for the currently focused\/active editor. 80 | 81 | ```clojure 82 | (ns lt.plugins.linecounter 83 | "Example statusbar item" 84 | (:require [lt.object :as object] 85 | [lt.objs.editor :as editor] 86 | [lt.objs.editor.pool :as pool] 87 | [lt.objs.statusbar :as statusbar] 88 | [crate.binding :refer [bound]]) 89 | (:require-macros [lt.macros :refer [defui behavior]])) 90 | 91 | (defui linecount-ui [{:keys [linecount]}] // <1> 92 | [:span (str "Lines: " linecount)]) 93 | 94 | (behavior ::update-linecount // <2> 95 | :triggers #{:update!} 96 | :reaction (fn [this linecount] 97 | (object/assoc-in! this [:linecount] linecount))) 98 | 99 | (object/object* ::linecounter // <3> 100 | :triggers #{} 101 | :behaviors #{::update-linecount} 102 | :init (fn [this] 103 | (statusbar/statusbar-item (bound this linecount-ui) ""))) // <4> 104 | 105 | (def linecounter (object/create ::linecounter)) // <5> 106 | (statusbar/add-statusbar-item linecounter) // <6> 107 | 108 | (behavior ::on-editor-linecount // <7> 109 | :triggers #{:focus! :change} // <8> 110 | :reaction (fn [ed] 111 | (when (= ed (pool/last-active)) // <9> 112 | (object/raise linecounter :update! (editor/line-count ed))))) 113 | 114 | (object/tag-behaviors :editor [::on-editor-linecount]) // <10> 115 | 116 | 117 | ``` 118 | 119 | 1. The Ui for our simple line counter, just the displays a label and the given line count 120 | 2. This behavior updates the :linecount entry in the linecounter object described in 3 121 | 3. We create an object to represent our line counter 122 | 4. When the object is initialized we create a statusbar-item element. `bound` is a macro that adds a watcher to the state of our object, whenever the state changes it will replace the content of our statusbar-item with whatever our linecount-ui function returns\). This way the view becomes "derived" from our object state at all times. 123 | 5. We create an instance of our linecounter object 124 | 6. Add the linecounter to the statusbar 125 | 7. We define an behavior to trigger for an editor object 126 | 8. The behavior should trigger whenever an editor receives focus or whenever the number of lines change. For simplicity we listen for change events, so that's any change which is a bit more frequent than we need. 127 | 9. Since we are listening to change events for any editor, we want to constrain it so that we don't update the line counter other than when the event is related to the currently active editor. We get trigger the ::update-linecount behavior and pass it the current line count for the editor in question 128 | 10. Rather than configuring the editor related behavior in a .behaviors file, we do it programaticcaly here by adding our behavior programatically to all objects with the `:editor` tag \(ie any editor\). 129 | 130 | ![](/assets/statusbar-item.png) 131 | -------------------------------------------------------------------------------- /chapter1.md: -------------------------------------------------------------------------------- 1 | # Getting Started 2 | 3 | Developing on and extending Light Table can be done pretty much entirely using Light Table itself. Most changes, even when developing new features, can be performed at runtime without the need to do any restarts. When you work with Light Table you'll find that you'll be using the REPL and the inline evaluation features available quite a lot. The exploratory nature of using a REPL will allow you to familiarize yourself gradually and incrementally with how things work. It's a truly interactive way of doing development and we trust you will find it very productive. 4 | 5 | ![](/assets/lt-user-plugin-eval.png) 6 | 7 | 8 | 9 | **Where to next ?** 10 | 11 | * If you just want to try out the lastest version of Light Table on the master branch, move on to the [Build from Source](/build-from-source.md) chapter. 12 | * Maybe you are curious about how you can go about customzing your Light Table beyond simple configuration settings ? A good place to start is the [User plugin](/user-plugin.md) chapter. 13 | * You've looked at an issue in the issue tracker, maybe browsed through the source of Light Table and feel inspired to try to take it on. A good place to start would be the [Working on Light Table Core](/working-on-light-table-core.md) chapter. 14 | * You have an idea for a new plugin, and you have already played with the [User plugin](/user-plugin.md), it's time to move on to the [Creating plugins](/creating-plugins.md) chapter. 15 | -------------------------------------------------------------------------------- /creating-a-language-plugin.md: -------------------------------------------------------------------------------- 1 | # Creating a Language Plugin 2 | 3 | WORK IN PROGRESS 4 | 5 | 6 | Until the official docs are in place you might want to look at the following Blog Series: 7 | - Developing a Groovy Light Table client ([1](http://rundis.github.io/blog/2014/gr_lt_part1.html), [2](http://rundis.github.io/blog/2014/gr_lt_part2.html), [3](http://rundis.github.io/blog/2014/gr_lt_part3.html), [4](http://rundis.github.io/blog/2014/gr_lt_part4.html) and [5](http://rundis.github.io/blog/2014/gr_lt_part5.html)) by Magnus Rundberget 8 | 9 | > Please note that the behaviors file syntax is of an older format, but hopefully you'll be able to translate that to the current format used by Light Table. 10 | 11 | -------------------------------------------------------------------------------- /creating-a-lt-client-using-node.md: -------------------------------------------------------------------------------- 1 | # Creating a LT Client using Node 2 | Typically when you create a Language plugin for Light Table you will find examples on how to do that for a TCP/IP client. This allows you to code the client in whatever language you prefer (perhaps in the language that your plugin is to support). However sometimes it might be more convenient to code the client using Node as the container for the external process. As it happens Light Table ships with Node (through Electron) so why not make use of it ? The [elm-light plugin] (https://github.com/rundis/elm-light) uses this approach to provide language support for [Elm](http://elm-lang.org/) 3 | 4 | ## Convention 5 | First of we'll establish some conventions on how you might structure your plugin 6 | 7 | ### Directory layout 8 | * $plugindir - The root directory of your plugin * node - In this directory you place the node js code for your plugin * node_modules - If your plugin makes use of node modules currently provided as part of the LT distribution, you can place them here * src - This is where you place the ClojureScript code for your plugin 9 | 10 | You might decide there are other directories (css, codemirror addon etc) that you wish to add as well, but for the purposes of this tutorial we'll just assume the above directory structure. 11 | 12 | ### Other conventions used 13 | We'll refer to our target language as *mylang* from here on. So when you see mylang, just replace with whatever language you would be targeting. 14 | 15 | ## The Node client 16 | We'll start of with creating a file `$plugindir/node/mylang-client.js` 17 | 18 | 19 | ```javascript 20 | // Add any required node modules you might need 21 | var fs = require("fs"); 22 | var cp = require("child_process"); 23 | var custom = require("some_custom_node_plugin"); // some node module you might need that you bundle with your plugin 24 | 25 | var myLangGlobals = {}; // maybe you need some global state for your plugin 26 | 27 | /** 28 | * Bootstrap the client**/ 29 | startRepl( 30 | function(err) { 31 | console.error("Failed to start repl for mylang: " + err); 32 | handleClose(); 33 | }, 34 | function(out) { 35 | console.out("Repl started ok"); 36 | startMessageListener(); 37 | }, 38 | {execPath: process.execPath, cwd: process.cwd()} 39 | ); 40 | 41 | /** 42 | * Send acc to LT and start listening for messages 43 | **/ 44 | function startMessageListener() { 45 | send([1, "mylang.client.connected", []]); // notify lt we`re ready to receive messages 46 | 47 | process.on("message", function (msg) { 48 | var cb = msg.cb; // id for object that should receive response message 49 | var cmd = msg.command; // command to be executed 50 | var data = msg.data; // The payload for the command 51 | 52 | try { 53 | switch (cmd) { 54 | // You must handle this ! This message is automatically sent by LT when disconnecting manually (or terminating LT) 55 | case "client.close": 56 | handleClose(); 57 | break; 58 | case "editor.eval.elm": 59 | handleEval(cb, data); 60 | break; 61 | } 62 | } catch (ex) { 63 | console.error("Error in mylang client message listener for command: " + cmd); 64 | console.error(ex); 65 | handleClose(); 66 | } 67 | }); 68 | } 69 | 70 | /** 71 | * Example function to start a long running process that your client holds on to 72 | **/ 73 | function startRepl(err, success, params) { 74 | myLangGlobals.repl = cp.spawn("mylang-repl", [params.execPath], {cwd: params.cwd}); 75 | myLangGlobals.repl.stdout.on("data", function (out) { 76 | // whatever, some logic to check if the process started ok 77 | success(someOkMsg); 78 | }); 79 | myLangGlobals.repl.stdout.on("data", function (err) { 80 | // whatever, some logic to check if the process failed somehow 81 | err(someErr); 82 | }); 83 | } 84 | 85 | /** 86 | * Close/shutdown the client process (and cleanup any forked processes etc 87 | **/ 88 | function handleClose() { 89 | console.log("Exiting..."); 90 | process.exit(0); 91 | } 92 | 93 | /** 94 | * Handle eval function 95 | **/ 96 | function handleEval(cbId, data) { 97 | // ... do whatever needed to eval send([cbId, "editor.mylang.eval.res", someEvalRes]); 98 | } 99 | 100 | // Emit message back to LT 101 | function send(msg) { process.send(msg); } 102 | ``` 103 | 104 | This might not be idiomatic JavaScript code, but hopefully it's clear enough to get you started on creating a client. 105 | 106 | # Communicating with the client from LT 107 | Let's create a separate ClojureScript namespace for handling communication with the Node client:`$plugindir/src/lt/plugins/mylang/clients.cljs` 108 | 109 | ## Basic setup 110 | 111 | ```Clojure 112 | (ns lt.plugins.mylang.clients 113 | (:require [lt.objs.files :as files] 114 | [lt.object :as object] 115 | [lt.objs.console :as console] 116 | [lt.objs.notifos :as notifos] 117 | [lt.objs.clients :as cs] 118 | [lt.objs.proc :as proc] 119 | [lt.objs.plugins :as plugins]) 120 | (:require-macros [lt.macros :refer [behavior]])) 121 | 122 | (def cp (js/require "child_process")) 123 | (def os (js/require "os")) 124 | (def mylang-client-path (files/join (plugins/find-plugin "mylang") "node/mylang-client.js")) 125 | (def mylang-node-path (files/join u/mylang-plugin-dir "node_modules")) 126 | ``` 127 | 128 | We require a couple of std node modules which we will use soon. Also note the usage of plugins/find-plugin which locates the path to our plugin. 129 | 130 | ## Forking the node client process 131 | 132 | ```Clojure 133 | 134 | (defn on-elm-message [client data] 135 | (let [msg (js->clj data :keywordize-keys true)] ; 1 - convert message into a clojure sequence (cbId, behavior, payload) 136 | (if (= (second msg) "elm.client.connected") 137 | (do 138 | (object/raise cs/clients :connect client) ; 2 - Notifies LT that the client is connected. Updates connect bar. 139 | (object/raise client :connect)) ; 3 - Finalizes connection process and sends any messages queued 140 | (object/raise cs/clients :message msg)))) ; 4 - Triggers routing of the message to intended behaviour 141 | 142 | 143 | (defn start-mylang-worker [path client] 144 | (let [worker (.fork cp ; 1 - Fork a new node process 145 | mylang-client-path #js [] ; 2 - Add args if needed 146 | (clj->js {:execPath (.-executable js/process) ; 3 - Use electron node exe 147 | :cwd path ; 4 - Working dir 148 | :silent true 149 | :env (if (= (.platform os) "win32") ; 5 - Add env needed 150 | {:NODE_PATH mylang-node-path} 151 | (proc/merge-env {:NODE_PATH mylang-node-path}))}))] 152 | (.on (.-stdout worker) "data" #(console/log (str "out: " msg))) 153 | (.on (.-stderr worker) "data" (console/error (str "Error from runner: " err))) 154 | (.on worker "message" #(on-elm-message client %)) 155 | (.on worker "exit" #(cs/rem! client)) ; 6 - 156 | 157 | worker)) 158 | ``` 159 | * `(.-executable js/process)` - Uses the node executable shipped with LT (through Electron) 160 | * We set the NODE_PATH environment path to be able to use our custom node modules easily. 161 | * Here we have made a distinction with default env params between windows and other platforms. `proc/merge-env` allows us to use LT's default behaviour for picking up environment variables like PATH etc. On windows this might not be necessary for your case. 162 | 163 | ## Making a connection 164 | 165 | ```Clojure 166 | (behavior ::send! 167 | :triggers #{:send!} 168 | :reaction (fn [client msg] 169 | (.send (:worker @client) (clj->js msg)))) ; Sends a message to client using node ipc 170 | 171 | (defn start-elm-client [{:keys [proj-path client]}] 172 | (notifos/working "Connecting..") 173 | (let [worker (start-mylang-worker proj-path client)] 174 | (object/merge! client 175 | {:name (files/basename proj-path) 176 | :dir proj-path ; 1 - Directory is used by LT to locate an appropriate client 177 | :worker worker ; 2 - We let the client object(atom) hold on to our client proc 178 | ;; 3 - Set of commands our client supports. Also used to locate client candidates 179 | :commands #{:editor.mylang.eval}}) 180 | (object/add-behavior! client ::send!))) ; 4 - Let LT know what behaviour handles sending messages 181 | 182 | ; This function serves as the entry point for connecting to a new client 183 | (defn try-connect [{:keys [info]}] 184 | (let [path (:path info) ; 1 - Info could typically be the info key from and editor instance of mylang 185 | proj-path (u/project-path path) ; 2 - Some util method to derive root-path for your mylang project 186 | client (cs/client! :elm-client)] ; 3 - Creates a client object instance (atom) 187 | 188 | (if proj-path 189 | (start-mylang-client {:proj-path proj-path ; 4 - Start the client process 190 | :client client}) 191 | (do ; 5 - Notify user of missing precondition. Clean up/remove client instance 192 | (notifos/done-working) 193 | (notifos/set-msg! (str "Couldn't find a mylang.project in any parent of path: " path) {:class "error"}) 194 | (cs/rem! client))) 195 | client)) 196 | ``` 197 | 198 | # Using the connection for the eval behaviour 199 | 200 | ```Clojure 201 | (ns lt.plugins.mylang.core 202 | (:require [lt.plugins.mylang.clients :refer [try-connect]] 203 | [lt.object :as object] 204 | [lt.objs.editor :as editor] 205 | [lt.objs.notifos :as notifos] 206 | [lt.objs.console :as console] 207 | [lt.objs.clients :as clients] 208 | [lt.objs.eval :as eval]) 209 | (:require-macros [lt.macros :refer [behavior]])) 210 | 211 | ; 1 - Behavior wired to an mylang editor for doing eval of a selection of code 212 | (behavior ::on-eval.one 213 | :desc "Mylang repl: Eval current selection" 214 | :triggers #{:eval.one} 215 | :reaction (fn [ed] 216 | (let [pos (editor/->cursor ed) 217 | ;; Pick out code and meta data for eval 218 | info (conj (:info @ed) 219 | {:code (editor/selection ed) 220 | :meta {:start (-> (editor/->cursor ed "start") :line) 221 | :end (-> (editor/->cursor ed "end") :line)}})] 222 | 223 | ;; Raise generic eval behavior. 224 | ;; `:origin` lets LT know where route result message (rf cb id mentioned earlier) 225 | (object/raise mylang :eval! {:origin ed :info info})))) 226 | 227 | ;; Generic eval behavior 228 | (behavior ::eval! 229 | :triggers #{:eval!} 230 | :reaction (fn [this event] 231 | (let [{:keys [info origin]} event] 232 | (notifos/working "Evaluating mylang...") 233 | ;; 1. `eval/get-client` will try to locate a suitable client, 234 | ;; if one doesn't exist it will connect to one using or try-connect function 235 | ;; 2. `clients/send` will create a message and either queue the message (if not yet connected) 236 | ;; or trigger the send! behavior we previously defined 237 | ;; 3. `:only origin` will tell LT to only route the response message back to this particular editor 238 | ;; object. The id of the editor object instance is what's eventually sent to our node client 239 | (clients/send 240 | (eval/get-client! {:command :editor.eval.mylang 241 | :origin origin 242 | :info info 243 | :create try-connect}) 244 | :editor.eval.mylang info 245 | :only origin)))) 246 | 247 | ;; When our node client sends a response message for the eval command, this is the behavior that 248 | ;; is eventually triggered. Here we delegate the displaying of results to a default behaviour in LT for showing 249 | ;; inline editor results 250 | (behavior ::eval-result 251 | :desc "Mylang repl: Eval result" 252 | :triggers #{:editor.mylang.eval.res} 253 | :reaction (fn [ed res] 254 | (notifos/done-working "Mylang evaluated") 255 | (object/raise ed 256 | :editor.result 257 | (:result res) 258 | {:line (-> res :meta :start)}))) 259 | 260 | (object/object* ::mylang-lang 261 | :tags #{:mylang.lang}) 262 | 263 | ;; Object(/atom) we may use for holding onto generic stuff for our language plugin. 264 | ;; We may also wire behaviours to this object 265 | (def mylang (object/create ::mylang-lang)) 266 | ``` 267 | 268 | ## Summary 269 | We've covered quite a bit of ground here. The most important bits we tried to communicate is that it isn't all that difficult to create a language client using node. This might hopefully serve as a template for future language plugins where using a node client might be a good fit. For the elm plugin it certainly was a good match, for other languages youmight prefer to code the client in the language you target. Then you should use the tcp/ip approach 270 | -------------------------------------------------------------------------------- /creating-a-skin.md: -------------------------------------------------------------------------------- 1 | # Creating a Skin 2 | 3 | 4 | 5 | -------------------------------------------------------------------------------- /creating-plugins.md: -------------------------------------------------------------------------------- 1 | # Creating plugins 2 | 3 | ## Getting started 4 | 5 | If you are not already familiar with the [BOT Architecture](/the-light-table-bot.md) in Light Table, it's highly recommended that you read through that before starting to develop a plugin for Light Table. 6 | 7 | ### Bootstrap using the Leiningen lt-plugin 8 | 9 | The easiest route to get started with creating a new plugin is to use the `lt-plugin` for [Leiningen. ](http://leiningen.org/) If haven't installed Leiningen and don't feel like doing so, you can obviously create the files listed below manually. 10 | 11 | To get started. Just type the following in a terminal 12 | - `#> lein new lt-plugin myplugin` 13 | - Then add the folder to your Light Table workspace 14 | 15 | |Structure | Description | 16 | | ------------- |-------------| 17 | |![](/assets/plugin-files.png) | | 18 | 19 | > For a more in-depth explanation of what you can configure in your `.behaviors` file check out the [Behaviors File Reference](/behaviors-file-reference.md) 20 | 21 | 22 | ## Developing 23 | 24 | You might find inspiration from the following plugin creation guides 25 | 26 | * [Creating a Skin](/creating-a-skin.md) 27 | * <Need an example of something in between...> 28 | * [Creating a Language plugin](/creating-a-language-plugin.md) 29 | 30 | ## Submit a plugin 31 | 32 | If it's your first time submitting a plugin, make sure you have a valid `plugin.edn`. Fill out the keys as follows: 33 | 34 | * `:name`\*: Camel case is recommended 35 | * `:version`\*: Must be in format X.X.X e.g. 0.1.4 36 | * `:source`\*: Must be a github url e.g. https:\/\/github.com\/USER\/PLUGIN 37 | * `:behaviors`\*: Refers to the behaviors file which is needed to load the plugin and define any keybinding or behavior. 38 | * `:desc`: Primary means users can find your plugin 39 | * `:author`: Your name and another useful way to find your plugin 40 | 41 | `*` Means the field is required. 42 | 43 | When releasing a new version, you must update the `:version` key and push the new git tag e.g. `git tag X.X.X && git push --tags`. With your updated `plugin.edn`, add or update your plugin to the [plugin metadata repository](https://github.com/LightTable/plugin-metadata) using [these instructions](https://github.com/LightTable/plugin-metadata#submit-a-plugin). 44 | 45 | -------------------------------------------------------------------------------- /dummy.text: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/LightTable/developer-guide/180bdce77886d85f0ee50d1c24290331c9b6e651/dummy.text -------------------------------------------------------------------------------- /frequently-asked-questions.md: -------------------------------------------------------------------------------- 1 | # Frequently Asked Questions 2 | 3 | ## General 4 | 5 | ### How do I get the current editor object? 6 | 7 | `(pool/last-active)` 8 | 9 | ### How do I get every object of a given type? 10 | 11 | `(object/instances-by-type ::type-of-your-object)` 12 | 13 | ### How do I delete every object of a given type? 14 | 15 | `(map object/destroy! (object/instances-by-type ::type-of-your-object))` 16 | 17 | ### Within a command, how do I eval clojure code and call a function on its result? 18 | 19 | 1. Create a behavior with a trigger named `:editor.eval.clj.result.RESULT_TYPE`.Give RESULT\_TYPE a unique name \([here are existing result-types](https://github.com/LightTable/Clojure/blob/d9ea2e5dc7a1b5254544cbaaf5ba4b4802aa43e4/src/lt/plugins/clojure.cljs#L320-L367)\). The behavior's `:reaction` will get the eval result as the second argument. For an example behavior, see [statusbar :result-type](https://github.com/LightTable/Clojure/blob/d9ea2e5dc7a1b5254544cbaaf5ba4b4802aa43e4/src/lt/plugins/clojure.cljs#L331-L340). 20 | 21 | 2. Add the behavior's unique name to the :editor.clj tag in a behaviors file. For the statusbarexample, see [its behavior](https://github.com/LightTable/Clojure/blob/d9ea2e5dc7a1b5254544cbaaf5ba4b4802aa43e4/clojure.behaviors#L67). 22 | 23 | 3. Eval clojure code with the custom :result-type. An eval call should look [like this](https://github.com/LightTable/Clojure/blob/d9ea2e5dc7a1b5254544cbaaf5ba4b4802aa43e4/src/lt/plugins/clojure.cljs#L106-L111). For example, a behavior triggered by `:editor.eval.clj.result.mine` would have the following eval call: 24 | 25 | 26 | `clojurescript(lt.object/raise lt.plugins.clojure/clj-lang :eval! {:origin editor :info (assoc (@editor :info) :meta {:result-type :mine} :code "(inc 41)")})` 27 | 28 | ### How do I read and write files? 29 | 30 | Use `lt.objs.files/open-sync` and `lt.objs.files/save` respectively. For example: 31 | 32 | `clojurescript(lt.objs.files/save "example.edn" "{:amazong true}")(lt.objs.files/open-sync "example.edn")` 33 | 34 | ### How do I copy to and paste from the system clipboard? 35 | 36 | Use `lt.objs.platform/copy` and `lt.objs.platform/paste` respectively. 37 | 38 | ### How do I make an HTTP request? 39 | 40 | LightTable ships with the [NodeJS http lib](http://nodejs.org/api/http.html). Here's an example fn that makes a GET request and calls a fn on the resulting body: 41 | 42 | \`\`\`clojurescript\(defn GET \[url cb\] \(let \[body \(goog.string.StringBuffer. ""\) req \(.get \(js\/require "http"\) url \(fn \[resp\] \(.on resp "data" \(fn \[data\] \(.append body data\)\)\) \(.on resp "end" \(fn \[\] \(cb \(.toString body\)\)\)\)\)\)\] \(.on req "error" \(fn \[err\] \(println "Request" url "failed with:" \(.-message err\)\)\)\)\) \) 43 | 44 | \(GET "[http:\/\/www.lighttable.com](http://www.lighttable.com)" \#\(println "BODY" %\)\)\`\`\` 45 | 46 | ### How do I suppress git noise created by compiled js in a plugin repo? 47 | 48 | Treat a plugin's compiled js as a binary file with: 49 | 50 | `bashecho "*_compiled.js -diff\n*_compiled.js.map -diff" >> .git/info/attributes` 51 | 52 | Now anytime compiled js shows up in a diff or log you only see: 53 | 54 | diff --git a\/clojure\_compiled.js b\/clojure\_compiled.js index e8eb4ef..9f73576 100644 Binary files a\/clojure\_compiled.js and b\/clojure\_compiled.js differ 55 | 56 | To do this globally for git, see [this example](https://github.com/cldwalker/dotfiles/commit/49fd8145193db1074e5639fcd8baf25b5aee19ed). 57 | 58 | ### How do I add post :init behavior to any object? 59 | 60 | When an object is created with `object/create`, object's are initialized with their `:init` and then behaviors that trigger off of `:object.instant` are called. For an example behavior, see the one for [:lt.objs.style\/styles](https://github.com/LightTable/LightTable/blob/407e8a9f2395474c2494cb332a37df35d7ed5196/src/lt/objs/style.cljs#L55-L75). This means you can customize any object so tread carefully ;\) 61 | 62 | ### How do I call a shell command? 63 | 64 | Since you have node's libraries available, use [child\_process.exec](http://nodejs.org/api/child_process.html#child_process_child_process_exec_command_options_callback). Here's an example `ls`: 65 | 66 | `clojurescript(.exec (js/require "child_process") "ls" (fn [err stdout stderr] (when (seq stdout) (println "STDOUT: " stdout)) (when (seq stderr) (println "STDERR: " stderr))))` 67 | 68 | There's also `lt.objs.proc/exec` which wraps `child_process.exec` and is geared towards objects and behaviors. 69 | 70 | ### How do I reload a plugin's behaviors? 71 | 72 | To try changes you make in a plugin, use interactive eval or save your changes and restart Light Table. The `App: Reload behaviors` command will not reload the plugin source as mentioned in[\#1042](https://github.com/LightTable/LightTable/issues/1042). 73 | 74 | ### How do I add a node package to a plugin? 75 | 76 | In your plugin's root, install the dependency e.g. `npm install X --save`. Then torequire it in your plugin: `(def node-lib (js/require (lt.objs.plugins/local-module "PLUGIN_NAME" "NODE_LIB_NAME"))))`. 77 | 78 | See the [JavaScript plugin for an example](https://github.com/LightTable/Javascript/blob/a00e352e6ae5b384333801b47802eea3a4f73d55/src/lt/plugins/js.cljs#L21). 79 | 80 | ### Can I use node packages within LightTable itself for my plugin? 81 | 82 | It is not advised to use the node modules shipping with LightTable and should not be considered part of the official plugin API. You can of course still pull in the package you need for your plugin independently of LightTable. 83 | 84 | ### How do I add a clojurescript library to a plugin? 85 | 86 | In your plugin's project.clj, add an entry to `:dependencies`. Note that ClojureScript libraries [are global](https://github.com/LightTable/LightTable/issues/1091). If you're running into issues loading your dependency, check other plugins to see if they're pulling in a conflicting version. 87 | 88 | ### I've added a clojurescript library to my plugin, but I get a `TypeError: Cannot read property 'X' of undefined` error. Now what? 89 | LightTable loads your plugin js file during bootstrap, so you have two options: 90 | - _ Restart LightTable - this should load your cljs dependency when your plugin is loaded_ 91 | - Manually eval your dependency in the LightTable UI client. No guarantees that this option will work, though. 92 | 93 | -------------------------------------------------------------------------------- /helpful-resources.md: -------------------------------------------------------------------------------- 1 | The following resources may be of use to anyone working on Light Table or its plugins: 2 | 3 | ## Internals 4 | 5 | - Introduction to BOT: [The IDE as a value](http://www.chris-granger.com/2013/01/24/the-ide-as-data/) 6 | - Official [README](https://github.com/LightTable/LightTable/blob/master/README.md) 7 | - Helpful LT [discussion topic](https://groups.google.com/forum/#!topic/light-table-discussion/T3DhzWhabok). 8 | - [Getting Started Programming Light Table](http://devblog.reverb.com/post/85325435561/getting-started-programming-light-table) by Joshua Emmons 9 | 10 | ## Creating a plugin 11 | 12 | - Lein template: https://github.com/mdhaney/lt-plugin-template 13 | - Quick Start: http://youtu.be/j91Li9kL0t8 14 | - From version 0.6.0 the `~/.lighttable/plugins` path shown is no longer valid. See [the official docs](http://docs.lighttable.com/#plugins-directory) for the correct path. 15 | - Jakub Arnold [plugin creating tutorial](http://blog.jakubarnold.cz/2014/01/13/light-table-plugin-tutorial.html). 16 | - Developing a Groovy Light Table client ([1](http://rundis.github.io/blog/2014/gr_lt_part1.html), [2](http://rundis.github.io/blog/2014/gr_lt_part2.html), [3](http://rundis.github.io/blog/2014/gr_lt_part3.html), [4](http://rundis.github.io/blog/2014/gr_lt_part4.html) and [5](http://rundis.github.io/blog/2014/gr_lt_part5.html)) by Magnus Rundberget 17 | 18 | ## Misc 19 | 20 | - [How to Debug CLJS](https://github.com/shaunlebron/How-To-Debug-CLJS) 21 | - [Communicate with a background process](https://github.com/mads379/lt-ocaml/tree/ehco-server-example) 22 | - [Build eval for a language](https://groups.google.com/d/msg/light-table-discussion/Dg0ldzLx4F4/AYNiYCLwhlwJ) 23 | 24 | -------------------------------------------------------------------------------- /keybindings.md: -------------------------------------------------------------------------------- 1 | > ##### Work in Progress 2 | > Editor's Note: Feel free to expand, relocate, or integrate this into the dev guide as you see fit. 3 | 4 | # Keybindings 5 | 6 | Check out the [keybinding section](https://github.com/LightTable/LightTable/blob/master/doc/behavior-and-keymap-configuration.md#keybindings) too for some basic information about keybindings. The following sections assumes you are familiar with the basic premises of keybindings... which are explained in the above link. 7 | 8 | ### Light Table Core 9 | 10 | The default keybindings of Light Table can be found at `deploy/core/settings/default/default.keymap` and, barring any bugs or keybinding conflicts, should rarely be modified. This is because plugins can also add keybindings as they desire. If they map over a default keybinding, or the default keymap suddenly maps over their keybinding, it will cause confusion and frustration. 11 | 12 | The user and plugin keymaps should overrule the default keymap in nearly all instances. There are currently no known reserved keybindings. 13 | 14 | ### Plugin 15 | 16 | At the highest directory in your plugin repo, you can create a file ending in `.keymap`... preferably named after your plugin. For instance, the file would be named `foo.keymap` if your plugin is named `foo`. Doing so will let you define keybindings for your plugin. 17 | 18 | In this `.keymap` file, which is simply EDN, you can add and remove keybindings. Here is a small example for the foo plugin: 19 | 20 | ```Clojure 21 | {:+ 22 | {:foo.focused { "o" [:lt.plugins.foo/open-selection] 23 | "x" [:lt.plugins.foo/close-parent]} 24 | :editor { "pmeta-s" [:lt.plugins.foo/open-selection]}} 25 | :- 26 | {:editor { "pmeta-s" [:save]}}} 27 | ``` 28 | 29 | In this example there are several things to note: 30 | 31 | - Key-value pairs with the key `:+` will be added to the overall keymap. 32 | - The `:-` will remove a keybinding from the keymap. Avoid doing this unless you have good reason (and the user is aware). 33 | - The command associated with the keybinding being added or removed does not even have to be found in the plugin. -------------------------------------------------------------------------------- /light-table-clojurescript-tutorial.md: -------------------------------------------------------------------------------- 1 | # Light Table ClojureScript tutorial 2 | 3 | This tutorial is based on David Nolans [Light Table ClojureScript tutorial](https://github.com/swannodette/lt-cljs-tutorial) 4 | 5 | The minimum fuzz way to get it up and running is by using the User plugin as a starting point. So follow these steps to get going: 6 | 7 | * Add the Light Table User plugin to your workspace with the command `Settings: Add User plugin to workspace` 8 | * Open the `user.cljs` file by using the command `Settings: User script` 9 | * Place the cursor anywhere in the file and save \(cmd-s\/ctrl-s\) - This will connect the user project so that we have an environment to evaluate ClojureScript in 10 | * Okay now open a new editor using the command: `File: New File` 11 | * Set the syntax of the editor to be ClojureScript by using the command: `Editor: Set current Editor Syntax` and then search and select `ClojureScript` 12 | * Now copy the contents from the [tutorial file \(lt-cljs-tutorial.cljs\)](https://raw.githubusercontent.com/swannodette/lt-cljs-tutorial/master/lt-cljs-tutorial.cljs) into your empty ClojureScript editor 13 | * Scroll to the top of the file and place the cursor inside or next to the ns declaration and invoke the command `Eval: Eval a form in the editor` \(cmd-enter\/ctrl-enter\) 14 | * You will be asked to provide a project connection. Select `Light Table UI` 15 | * Now you should be able to follow along with the instructions in the tutorial and evaluate the examples as you go. 16 | 17 | ![](/assets/lt-tutorial.png) 18 | 19 | 20 | > You should obviosly check out the official ClojureScript resources at [clojurescript.org](https://clojurescript.org/). B**ut please be aware that Light Table is currently using a very old version of ClojureScript.** That does not mean you can't use a newer version of ClojureScript when working with projects, but it means that when it comes to developing plugins and working on Light Table Core you have to take this into consideration ! 21 | 22 | -------------------------------------------------------------------------------- /the-light-table-bot.md: -------------------------------------------------------------------------------- 1 | # Light Table BOT 2 | 3 | WORK IN PROGRESS 4 | 5 | This slightly dated Blog Post from Chris Granger \(the creator of Light Table\) gives a nice explanation of the BOT model in Light Table: [The IDE as a value](http://www.chris-granger.com/2013/01/24/the-ide-as-data/) 6 | 7 | 8 | 9 | -------------------------------------------------------------------------------- /user-plugin.md: -------------------------------------------------------------------------------- 1 | # User plugin 2 | 3 | The simplest way to get started developing something for Light Table is to play with the User plugin. In this chapter 4 | we're going to walk through a couple of simple examples. 5 | 6 | ## Location 7 | 8 | The location of the user plugin depends on your OS 9 | 10 | * Mac: ~\/Library\/Application\ Support\/LightTable\/User 11 | * Linux: ~\/.config\/LightTable\/User 12 | * Windows: %APPDATALOCAL%\/LightTable\/User 13 | 14 | ## Getting started 15 | 16 | Your complete configuration, including plugins you've installed, is stored in a User plugin. Since the User plugin is just a directory, you can share it by putting it under revision control e.g. git and uploading it to a service like Github. To explore it 17 | 18 | * add it to your workspace with the command `Settings: Add User plugin to workspace` 19 | * Any custom keybindings and behaviors are added to `user.keymap` and `user.behaviors`. 20 | * To write commands, behaviors and more, see `src/lt/plugins/user.cljs`. 21 | * To open your `user.cljs` at anytime use the command `Settings: User script`. 22 | * Inside the default `user.cljs` is an example command and behavior. 23 | 24 | ![](/assets/lt-user-plugin-hello.png) 25 | 26 | Run the command `User: Say Hello` to see your own command in action ! 27 | 28 | ## Adding your own command 29 | 30 | Commands are the user facing functionality in Light Table. They are typically listed in the Command Pane and you can assign keyboard shortcuts to them. The are the easiest place to start, so let's go ahead and make our own little command. Open `user.cljs` and add this snippet. 31 | 32 | ```clojure 33 | (cmd/command {:command :user.print-filename ;; 1. 34 | :desc "User: Print current file name" ;; 2. 35 | :exec (fn [] ;; 3. 36 | (let [ed (lt.objs.editor.pool/last-active)] ;; 4. 37 | (println (-> @ed :info :path))))}) ;; 5. 38 | 39 | ``` 40 | 41 | 1. We give our command an id. That way we can refer to that command from other places. Typically you want to be able to assign keyboard shortcuts to a command. The id is how you can map a keyboard shortcut to your command. 42 | 2. Give the command a sound description. Commands are shown in the Command Pane in Light Table, so you want to provide a meaningful description so people know what the command does. 43 | 3. This is where we define what's going to happen when the command is to be executed 44 | 4. We are using a function `last-active` from the `lt.objs.editor.pool` namespace to get hold of the current Editor. Or currently focused open file if that makes more sense. Actually we are not getting the file, but we are getting an `object` representing that file. 45 | 5. The editor object we have access to is a special kind of object. It's basically a wrapped ClojureScript [atom. ](http://clojure.org/reference/atoms). Now that's not to important, the main point is that this object contains meta information that we can access. So in JavaScript terms you could read this as `ed.info.path` 46 | 47 | **To make this command available we need to connect to the user plugin project and then evaluate it.** 48 | 49 | 1. Save the file - When you hit save it will first connect to the project and then compile the ClojureScript code in your user plugin to JavaScript. So first time it take a little time, after that it will be much faster. 50 | 2. With the cursor inside the command definition\/\(form in Clojure terms\) 51 | 1. Open the command pane \(ctrl-space\) 52 | 2. Search for the command `Eval: Eval a form in editor`. Select that 53 | 3. Light Table will tell you to connect to plugin. Click the button `Connect a Client` 54 | 4. From the list of available clients select `Light Table UI` 55 | 5. You should see `nil` shown after the form. If not try to evaluate one more time. 56 | 57 | 58 | > When you evaluate code like we just did, LIght Table will during runtime compile the code \(using the ClojureScript compiler\) to JavaScript and add the resulting JavaScript to the running Light Table. You can now make changes to the command as you wish, and re-evaluate it. That will replace the runtime definition of the command with a new definition. This way of developing provides really fast turnaround and promotes a stepwise interactive approach to buidling things. 59 | 60 | You should now be able to call the command. Just open the command pane and search for the description you gave the command. Select \`View -> Console\` from the menu to open the console if you don't already have it open. Voila you should see the filename of the editor you had focus on when invoking your new command ! 61 | 62 | ## Adding your own behavior 63 | 64 | ```clojure 65 | (behavior ::user.display-filename ;; 1. 66 | :desc "Show filename in statusbar for a while when focused" ;; 2. 67 | :triggers #{:focus} ;; 3. 68 | :reaction (fn [ed] ;; 4. 69 | (lt.objs.notifos/set-msg! (-> @ed :info :path)))) ;; 5. 70 | ``` 71 | 72 | 1. We give our behaviour an id. We are going to need this id to be able to configure when this behavior should be triggered 73 | 2. This is optional, but it's nice for documentation and you'll see that it's also helpful when we wire it up in the next section 74 | 3. Triggers are the definition of event name\(s\) that should trigger this behavior to execute 75 | 4. The reaction, defines the function that should be executed when the behavior is triggered. You'll also notice we've 76 | assumed a parameter ed, ie we are expecting this behavior function to be called with an editor object. 77 | 5. Finally we use the `set-msg!` function from the `lt.objs.notifos` namespace to set a message with the editors file name in the bottom status bar in Light Table. \(The message will by default be displayed for 10 seconds\) 78 | 79 | **Assuming you have already gone through the command example in the previous example you need to:** 80 | 81 | 1. First scroll to the top of the `User.cljs` file and put the cursor inside the ns form and then evaluate that. 82 | 2. Then scroll back to the behaviour and evaluate that 83 | 84 | **We are not quite done yet, we also need to tell Light Table how and what should make this behavior actually trigger** 85 | 86 | 1. Open the command pane and search for `Settings: User behaviors` and select that 87 | 2. Add the below definition to the file and then save 88 | 89 | ```clojure 90 | [:editor :lt.plugins.user/user.display-filename] 91 | ``` 92 | 93 | * If you put your cursor over inside or right next to that line, you will see the description we provided to our behavior is shown highlighted next to the right bracket 94 | * `:editor` is a tag that tells Light Table that this the behavior should be considered for any object in Light Table 95 | that has this tag should be considered a candidate for it to be triggered. All open files are represented as editor objects and they all have by default this tag. 96 | * The final piece of the puzzle is the trigger we defined for our behavior. So whenever someone\/somewhere raises a `:trigger` event on an editor instance Light Table now knows enough to invoke our behavior reaction function. Triggering :focus is something that is done inside another behavior in Light Table, let's not worry to much about that for now. 97 | 98 | ![](/assets/lt-user-plugin-beh.png) 99 | ### Flexibility through the BOT architecture 100 | 101 | The combination of **B**ehavior, **O**bject and **T**ags is what is described as the [BOT Architecture](/the-light-table-bot.md) in Light Table. It gives you flexibility in several dimensions; 102 | 103 | 1. **Tags**: You can target behaviors to object with certain tags. So if you change the above behavior wiring to 104 | 1. `[:editor.javascript :lt.plugins.user/user.display-filename]` 105 | 2. Now the behaviour will only trigger when JavaScript files\(\/editors\) receive the `:focus` trigger. BTW, you can have multiple behavior wirings for the same behavior, so if you would like this behavior to happen for Clojure and Elm files just add a line for each of them. 106 | 3. If you'd like to turn off the behavior you can just use the following syntax `[:editor :-lt.plugins.user/user-display-filename]`. Note the minus sigh. 107 | 4. When you save the behaviors file, the changes will be applied at once. Runtime. 108 | 109 | 2. **Triggers**: You can have multiple triggers for your behavior. Say you wanted to also display the filename whenever the editor changes. Just add `:change` to the set of triggers in your behavior and reevaluate it. 110 | 111 | 3. **Objects**: You can add or remove tags to objects both at declaration time, programatically or in your User.behaviors file. For example if you add the following to your `User.behaviors` file `[:editor :lt.obj/add-tag :mytag]` all editor objects in Light Table will also receive the `:mytag` tag. 112 | 113 | 4. **Behaviors**: What's more you can also add or remove behaviors to objects during runtime both in your `User.behaviors`file or programatically. 114 | 115 | 116 | You don't need to understand or use all this from the off, but hopefully you get and idea of the flexibility of the BOT architecture. 117 | 118 | > You can get far by just using commands and normal ClojureScript functions, but if you want to create a runtime configurable plugin you'll want to start looking at using behaviors. 119 | 120 | -------------------------------------------------------------------------------- /working-on-light-table-core.md: -------------------------------------------------------------------------------- 1 | # Working on Light Table Core 2 | 3 | 4 | 5 | --------------------------------------------------------------------------------