Search code examples
javascriptjqueryxmlace-editor

Autocompleting XML values and attributes in Ace editor


I'm looking to autocomplete XML tags and attributes. The valid values will come from the server. For example,

If I have a tag such as,

<status></status>

and my cursor is inside the open and closing tags, I'd like to hit control + space and have only valid values appear in the drop-down. Such as: ok, error, warning, ...

Similarly for attributes,

<status audience="">ok</status>

If my cursor has focus inside the quotes I'd like only valid audiences to appear in the drop-down when hitting control + space.

Here's what I have so far. This completer completes words I'm typing. I just can't figure out how to know what kind of tag I'm inside and how to send specific values for that tag or attribute.

Any ideas or examples to point me to? Thanks, /w

function loadEditor() {
    var langTools = ace.require("ace/ext/language_tools");
    editor = ace.edit("editor");
    editor.setOptions({
        enableBasicAutocompletion: true,
        enableLiveAutocompletion: true,
        enableSnippets: true,
    });
    editor.getSession().setMode("ace/mode/xml");

    var myCompleter = {
        getCompletions: function(editor, session, pos, prefix, callback) {
            if (prefix.length === 0) {
                callback(null, []);
                return;
            }                    
            $.getJSON("completions.php?a=completions&prefix=" 
                    + prefix + "&content=" + session, function(json) {
                callback(null, json.map(function(c) {
                    console.log("value: " + c.value);
                    return {value: c.value, caption: c.caption, meta: c.meta, score:c.score};
                }));
            })
        }
    };
    langTools.addCompleter(myCompleter);
}

Solution

  • The editors built-in tokeniser can be used to identify tag and attribute names.

    var attributeCompleter = {
        getCompletions: function(editor, session, position, prefix, callback) {
            var results = [];
    
            var token = editor.session.getTokenAt(position.row, position.column);
    
            if (token.type === "string.attribute-value.xml") {
                var tokens = editor.session.getTokens(position.row);
                var tag, attribute;
    
                for (i = 0; i < tokens.length; i++) {
                    if (token.index <= tokens[i].index) {
                        break; // note tokens[i].index may be undefined
                    }
                    switch (tokens[i].type) {
                    case "meta.tag.tag-name.xml":
                        tag = tokens[i];
                        break;
                    case "entity.other.attribute-name.xml":
                        attribute = tokens[i];
                        break;
                    }
                } // iteration has to be modified for xml tags divided over multiple lines
                
                if (tag.value === "status" && attribute.value === "audience") {
                    results = [ { caption: "caption", value: "value", meta: "meta" } ];
                }
            }
    
            callback(null, results);
        }
    };
    

    Note that the tokeniser considers the ending citation audience="example"<- part of the attribute value.