Floating Octothorpe

Embedding Ace in Gitit

By default when editing pages in Gitit, text is entered using a standard textarea element. It is however fairly easy to extend Gitit. This post is going to look at using the Ace editor with Gitit.

Downloading Ace

Ace builds are hosted in a separate GitHub repository, start by cloning this repository to a temporary directory:

git clone --depth 1 https://github.com/ajaxorg/ace-builds.git /tmp/ace-builds

Note: using the --depth option will create a shallow clone without a full commit history. This option isn't strictly necessary, however it should reduce the download time.

If you don't have Git installed you can download a tar file instead:

curl -OL https://github.com/ajaxorg/ace-builds/archive/master.tar.gz
tar xf master.tar.gz -C /tmp
rm master.tar.gz

You now need to pick which version of Ace you want to use, there are currently four versions:

Once you've picked a version, make a static/js folder in the Gitit wiki and copy the version across:

mkdir /home/gitit/wiki/static/js
cp -r /tmp/ace-builds/src-min-noconflict/ /home/gitit/wiki/static/js/ace

Updating Gitit templates

We now need to override the Gitit page template to embed Ace. Start by copying the default page template into the templates directory of the wiki:

cp /usr/share/gitit/data/templates/page.st /home/gitit/wiki/templates/

Now add the following between $getuser()$ and </body>:

<script src="$base$/js/ace/ace.js" type="text/javascript" charset="utf-8"></script>
<script type="text/javascript">
    function enable_ace_editor(old_editor) {
        "use strict";

        var ace_div = document.createElement("div");
        ace_div.setAttribute("id", "ace-editor");
        ace_div.style.height = "400px";
        old_editor.parentNode.insertBefore(ace_div, old_editor);
        old_editor.style.display = "none";

        var editor = ace.edit(ace_div);
        //Fix for https://github.com/angular-ui/ui-ace/issues/104
        editor.\$blockScrolling = Infinity;
        editor.getSession().setValue(old_editor.value);
        editor.getSession().on("change", function () {
            old_editor.value = editor.getSession().getValue();
        });
        editor.focus();

    }

    var old_editor = document.getElementById("editedText");
    if (old_editor !== null) {
        enable_ace_editor(old_editor);
    }
</script>

This code will check if the editedText element exists. If the element does exist, it will create a new div for Ace, hide the existing textarea and set up a function to sync the Ace editor with the old textarea.

Note: $ will normally be interpreted when Gitit renders the template, therefore the dollar in front of $blockScrolling is escaped.

Finally restart Gitit (systemctl restart gitit.service) to reload the page template. If everything goes well you should now see Ace in the edit tab:

Screenshot of Gitit with the Ace editor embeded

Customising Ace

From here it's fairly easy to customise Ace, below are the additional settings I ended up using:

editor.getSession().setMode("ace/mode/markdown");
editor.setTheme("ace/theme/chaos");
editor.setFontSize(14);
editor.setKeyboardHandler("ace/keyboard/vim");
editor.setOption("wrap", "free");
editor.setHighlightActiveLine(true);
editor.setHighlightGutterLine(true);
editor.renderer.setShowGutter(true);
editor.renderer.setShowPrintMargin(true);
editor.setOption("scrollPastEnd", true);
editor.getSession().setUseSoftTabs(true);
editor.getSession().setUseWrapMode(true);

The Ace documentation has a full list of available options, the Ace Kitchen Sink is also worth looking at to get a feel for which options you want to configure.

New commands

Ace also makes it easy to add custom key bindings, the code below will add support for :w and :q:

// Monkey patch in write and quit commands for Vim binding
ace.config.loadModule("ace/keyboard/vim", function (m) {
    var VimApi = ace.require("ace/keyboard/vim").CodeMirror.Vim;
    VimApi.defineEx("write", "w", function (cm, input) {
        cm.ace.execCommand("save");
    });
    VimApi.defineEx("quit", "q", function (cm, input) {
        cm.ace.execCommand("quit");
    });
});
editor.commands.addCommand({
    name: "save",
    exec: function (editor) {
        var commit_message = window.prompt("Commit message:");
        if (commit_message) {
            document.getElementById("logMsg").value = commit_message;
            document.getElementById("logMsg").focus();
        }
    }
});
editor.commands.addCommand({
    name: "quit",
    exec: function (editor) {
        window.location.pathname = "$base$$pageUrl$";
    }
});

Note: if you are using mod_proxy_html to rewrite pages, make sure you update the quit function to use the correct URL (e.g. /wiki$pageUrl$).