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.
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 the last version of Diavolo from this directory. Updates will be automatic.
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
.
Diavolo 0.5 offers the basic set of features of a source code editor :
In the near future, Diavolo will also offer :
The XUL element <sourceeditor>
provides a few attributes and methods for your convenience:
Initializes Diavolo. Mandatory to make the editor become active.
Clears all contents in Diavolo. Does not clear the Undo stack.
Read/write property allowing to get or set the textual contents of Diavolo. Setting does not clear the Undo stack.
Resets the Undo/Redo stacks of Diavolo.
Read-only attribute offering access to the core nsIEditor living inside Diavolo.
Read-only attribute : JS table of all tokens defined in the grammar.
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.
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.
Read/write attribute : the CSS declarations attached to errors in Diavolo.
Read/Write attribute : the CSS declarations attached to the token closing a succession of errors. Value often equal to errorStyle for visual reasons.
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 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
.
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 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.
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.
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 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 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.
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 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.
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; }
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;"/>
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;"/>