Diavolo, a XUL-based source code editor

Author
Daniel Glazman <daniel@glazman.org>
Version
0.5
Date
03-jul-2008

Table of Contents

  1. About Diavolo
  2. Install Diavolo in Firefox3
  3. Use Diavolo
  4. Features
  5. API
    1. init()
    2. clear()
    3. textContent
    4. resetUndoStack()
    5. editorCore
    6. tokens
    7. roles
    8. styles
    9. errorStyle
    10. skipuntilStyle
    11. numberingStyle
  6. The grammar
    1. Root element
    2. Regexps
    3. Token definitions
    4. Context definitions
      1. Ignored tokens
      2. Error handling
      3. Expected tokens
    5. Styles
      1. Styles for a given role/token
      2. Styles for errors
      3. Styles for "skipuntil" tokens

About Diavolo

Diavolo is an extension to the XUL tag set that allows to instantiate a source code editor based on a grammar definition and personal styles. The edited source code is then grammatically checked on the fly, errors and all tokens are highlighted as you type.

Diavolo is available as an XPI extension to Firefox 3. Its tri-licensed MPL/GPL/LGPL. It's then very easy to bundle it with another extension's package to or integrate it into a xulrunner-based standalone application. Warning: Diavolo requires Firefox 3 or more recent. It will not work with older versions of Firefox.

Diavolo example

Example of integration of Diavolo into a CSS editor : DiavoloTest
(feel free to install and run it to understand Diavolo,
it adds a new menu entry under the Tools menu of Firefox)

The sources of Diavolo are available in this SVN repository.

Install Diavolo in Firefox3

Install the last version of Diavolo from this directory. Updates will be automatic.

Use Diavolo

To use Diavolo, you must add the following style rule to the CSS styles applied to your XUL document:

sourceeditor {
  -moz-binding: url('chrome://diavolo/content/sourceeditor.xml#sourceeditor');
}

Then add to your XUL document a sourceeditor element:

<sourceeditor id="myId" grammarURL="myGrammarURL" />

where myGrammarURL is a chrome:// URL to your XML grammar file . And finally include the following scripts in your XUL document:

<script type='application/x-javascript' src='chrome://diavolo/content/tokeniser.js'></script>
<script type='application/x-javascript' src='chrome://diavolo/content/highlighter.js'></script>
<script type='application/x-javascript' src='chrome://diavolo/content/editor.js'></script>
<script type='application/x-javascript' src='chrome://diavolo/content/grammar.js'></script>
<script type='application/x-javascript' src='chrome://diavolo/content/selector.js'></script>

You need to initialize your editor to make it work:

document.getElementById("myId").init();

The XML grammar file's structure is detailed below.

An example of grammar is provided for Cascading StyleSheets (CSS) in chrome://diavolo/content/css.xml .

Features

Diavolo 0.5 offers the basic set of features  of a source code editor :

In the near future, Diavolo will also offer :

API

The XUL element <sourceeditor> provides a few attributes and methods for your convenience:

init()

Initializes Diavolo. Mandatory to make the editor become active.

clear()

Clears all contents in Diavolo. Does not clear the Undo stack.

textContent

Read/write property allowing to get or set the textual contents of Diavolo. Setting does not clear the Undo stack.

resetUndoStack()

Resets the Undo/Redo stacks of Diavolo.

editorCore

Read-only attribute offering access to the core nsIEditor living inside Diavolo.

tokens

Read-only attribute : JS table of all tokens defined in the grammar.

roles

Read-only attribute : JS table of all roles defined in the grammar. The grammar associates a role to a given token in a given context. That role is used as the class attribute of the element inserted in the source code editor to represent the token.

styles

Read/write attribute : JS table of all the styles defined in the grammar. Each entry in the table is an object where role specifies the role for which this style is defined, forToken specifies the token for which this style is defined, and value is the set of CSS declarations associated with this style.

Setting this attribute updates all the document's styles in Diavolo.

errorStyle

Read/write attribute : the CSS declarations attached to errors in Diavolo.

skipuntilStyle

Read/Write attribute : the CSS declarations attached to the token closing a succession of errors. Value often equal to errorStyle for visual reasons.

numberingStyle

Read/write attribute : the CSS declarations attached to the column of line numbers in Diavolo. For instance, if your source editor is <sourceeditor id="myDiavolo" .../>, use

document.getElementById("myDiavolo").numberingStyle = "display: none"

to hide the line numbers ! Reset that property to the empty string to show line numbers again.

The grammar

The grammar for a given language is an XML file. As we said above, an example of grammar definition for CSS is available from chrome://diavolo/content/css.xml .

Root element

The root element of a grammar file is the grammar element. The document must make the id attribute available on context elements as an ID. The grammar element must have a name attribute.

<?xml version="1.0"?>

<!DOCTYPE grammar [
<!ATTLIST context id ID #IMPLIED>
]>

<grammar name="css">
...
</grammar>

Regexps

RegExps used for tokenization can be defined inside a regexps element being a child of the root element.

An optional child of regexps is the option element that carries the type attribute. Multiple option elements are allowed in any order. For the time being, only one option is defined:

<option type="case-insensitive"/>

makes all RegExps defined in the grammar applied case-insensitively.

A RegExp is defined as a regexp element being a child of the regexps element. For example:

<regexps>
  <regexp name="s" value="[ \t\r\n\f\u00a0]+"/>
</regexps>

Its name attribute is an unique identifier matching the following regular expression : [a-zA-Z][a-zA-Z0-9]*

Its value attribute is a regular expression (Cf. developer.mozilla.org). It can reuse another RegExp putting its unique identifier between unescaped braces:

<regexp name="ident" value="-?{nmstart}{nmchar}*"/>

Order of regexp elements inside the regexps element does not matter.

Token definitions

Tokens are defined in a deftokens element being a child of the root element of the document. The deftokens element contains a deftoken element per token definition in the grammar.

<grammar name="css">
  <deftokens>
    <deftoken regexp="s" name="whitespace"/>
    <deftoken regexp="comment" name="comment"/>

    <deftoken string="=" name="EQUALS"/>
    <deftoken string="~=" name="INCLUDES"/>
...

A deftoken element has a mandatory attribute name being the case-sensitive name of the token. It also holds the string attribute or (exclusive) the regexp attribute.

The presence of the string attribute means the token's contents are exactly equal to the string being the value of the string attribute.

The presence of the regexp attribute means the token's contents match the RegExp definition in a regexp element of name being the value of the regexp attribute.

Context definitions

The contexts of the grammar live inside a contexts element being a child of the root element of the document.

All contexts are defined as a context element being a child of the contexts element. If order does not matter for most context elements, the first context child of the contexts element specified the context expected to be found when the parsing of a source code is initiated.

A context element MUST carry an id attribute.

A context element can contain zero or more error elements, zero or one ignore element, and one or more token elements.

Ignored tokens

Ignored tokens in a given context are defined inside an ignore element being a child of the context element. That element has zero or more type elements.

A type element MUST carry a name attribute containing a token name.

Error handling

Error handlers in a given context are defined as error elements being a child of the context element. An error element MUST carry the skipuntil attribute containing the name of a token and the expecting attribute containing the id of a context element. If an error is found in a given context (ie no matching token is found), the parser will get all next tokens up to a token matching the skipuntil attribute of an error element. After that point, the parser will expect the context of id given by the expecting attribute.

Expected tokens

The tokens that are accepted in a given context are token elements being children of the context element. Order does matter here, and the tokenizer in Diavolo will match on the first token definition matching in traversal order. In the following example for instance, the tokenizer will never match an IDENT with look-ahead because of the order of the tokens :

<token type="IDENT"                       role="type"      expecting="selector-context1"/>
<token type="IDENT"  lookahead="VBAR"     role="namespace" expecting="namespace-separator"/>

A token element MUST have a type attribute containing a token name. It can also have a lookahead attribute also containing a token name. Diavolo then expects a token of the given name, possibly looking ahead one token. A special value is allowed for the type attribute : it can remain the empty string, meaning that all tokens match this token expectation.

A token element MUST also have a role attribute. The role is not useful to the parsing but is used as the value of the class attribute for the span element that will contain the token in Diavolo.

Finally, a token element MUST also have an expecting attribute specifying the id of a context element being the next parser's context after the expectation if it's successful.

Styles

Styles applied to a document edited with Diavolo are also defined in the grammar file. They're not defined in a regular stylesheet to allow direct modification of these styles using a simple API (see above).

Styles are all contained in a styleset element being a child of the root element of the document. The styleset element can contain zero or more style elements, zero or one errorstyle element, and zero or one skipuntilstyle element.

Styles for a given role/token

The styles for a given role (and possibly token) are defined in a style element being a child of the styleset element.

<styleset>
  <style role="argument" forToken="STRING" value="color: green"/>
...

A style element MUST carry a role attribute (see Expected Tokens section above). It can also carry a forToken attribute containing the name of a token. It MUST carry a value attribute containing CSS declarations to be applied to the token.

The CSS rule generated from the style element in the example above is the following one :

*.argument[token="STRING"] { color: green; }

Styles for errors

The styles for errors are defined in an errorstyle element being a child of the styleset element. It MUST carry a value attribute containing CSS declarations to be applied to errors

<errorstyle value="border-bottom: red dotted 1px;"/>

Styles for "skipuntil" tokens

When an error is found in a given context and an error handler is defined in that context, errors are tracked until a given token using the skipuntil attribute of the error handler. That token can have specific styles through an skipuntilstyle element being a child of the styleset element. It MUST carry a value attribute containing CSS declarations to be applied to that token. The styles applied to "skipuntil" tokens are often similar to the ones applied to error tokens.

<skipuntilstyle value="border-bottom: lime dotted 1px;"/>

Document made with Nvu