Codebox Software

UltraEdit Tag Completion Script

Published:

This script adds HTML/XML tag-completion functionality to the UltraEdit text editor. For details of how to install and use UltraEdit scripts see the UltraEdit Scripting Tutorial. You can download the script here.

The script searches backwards through the active document looking for HTML/XML style tags that have been opened but not yet closed - the first such tag that is found will have an appropriate closing tag inserted into the document at the cursor location.

HTML/XML/JSP comments, CDATA sections, DOCTYPE declarations and special JSP directives / scriptlets / declarations are all ignored.

The default configuration will ignore the 9 'self-closing' HTML tags contained in the 'ignoredTags' array near the top of the code - when used in an HTML document these tags should not be closed, however in XHTML/XML documents all tags must be closed, so this line should be modified or commented out according to your needs.

/*
     This script adds HTML/XML tag-completion functionality to the UltraEdit text editor. For details
     of how to install and use UltraEdit scripts see here:

          http://www.ultraedit.com/support/tutorials_power_tips/ultraedit/scripting_engine_tutorial.html
     
     The script searches backwards through the active document looking for HTML/XML style tags that have
     been opened but not yet closed - the first such tag that is found will have an appropriate closing 
     tag inserted into the document at the cursor location.
     
     HTML/XML/JSP comments, CDATA sections, DOCTYPE declarations and special JSP directives / scriptlets /  
     declarations are all ignored.
     
     The default configuration will ignore the 9 'self-closing' HTML tags contained in the 'ignoredTags' 
     array near the top of the code - when used in an HTML document these tags should not be closed, however 
     in XHTML/XML documents all tags must be closed, so this line should be modified or commented out 
     according to your needs.
     
     For the latest version of this script visit:
          http://codebox.org.uk/pages/ultraedit-scripts/html-xml-tag-auto-complete
     
     For licensing information go to: 
          http://codebox.org.uk/pages/about
     
 */
 
if (CODEBOX === undefined){
    var CODEBOX = {};
}

CODEBOX.tagComplete = function(){
    var closedTags = [], tag, haveRestored = false, parts = [];
    var whitespaceRegex = /^\s+|\s+$/g;
    var ignoredTags = ['area', 'base', 'basefont', 'br', 'hr', 'input', 'img', 'link' , 'meta']; // Comment this line out to stop ignoring them
     
    var doc = UltraEdit.activeDocument;
    var initialPos  = doc.currentPos;
    var previousPos = initialPos;     
    var currentPos;
     
 // Remember where we start from
    var restorePosn = (function(col, line){
            return function(){
                doc.gotoLine(line, col+1);
                haveRestored = true;
           };
       })(doc.currentColumnNum, doc.currentLineNum);
     
 // Set up the find options
    doc.findReplace.regExp     = true;
    doc.findReplace.searchDown = false;
    doc.findReplace.mode       = 0;

    function extractTagName(tag){
        if (tag.charAt(0) === '/'){
         // Strip out the leading slash character if its a closing tag
            tag = tag.substring(1);     
        } else {
         // Remove any attributes if its an opening tag
            tag = tag.split(/\s/)[0];
        }
        return tag;
    }
     
    function isIgnoredTag(tag){
     // Is this tag one of the 'ignored' ones listed above
        if (typeof(ignoredTags) !== 'undefined'){
            tag = extractTagName(tag);               
            for(var i=0, l=ignoredTags.length; i<l; i++){
                if (ignoredTags[i] === tag){
                    return true;     
                }
            }
        }
        return false;
    }

    while (true) {
        previousPos = currentPos;
        tag = getNextTag();
        if (!isIgnoredTag(tag)){
            currentPos = doc.currentPos;
     
            if (previousPos <= currentPos){
             // Our search has hit the top of the document and begun again from the bottom, so stop
                break;     
            }
                
            if (tag.length > 0){
                if (tag.charAt(0) === '/'){
                 // This is a closing tag, eg </div> - push it onto the stack and continue searching
                    closedTags.push(extractTagName(tag));
                         
                } else if (tag.charAt(tag.length - 1) === '/'){
                 // This is an empty element, eg <div /> - ignore and continue                    
                } else {
                 /* If the tag has attributes we want to remove them, so split tag contents on 
                    whitespace and take the first part */
                    tag = extractTagName(tag);
                         
                    if ((closedTags.length > 0) && (tag === closedTags[closedTags.length - 1])){
                     /* This is an opening tag but there is already a corresponding closing tag, so just
                        pop the entry off the stack and continue moving back through the document */
                        closedTags.pop();
                              
                    } else {
                     /* This looks like an opening tag that has no corresponding close tag, so move back
                        to where we started from and write one. */
                        restorePosn();
                        doc.write('</' + tag + '>');
                        break;
                    }
                }
            }
        }
    };
     
    if (!haveRestored) {
        restorePosn();
    }
     
    function getNextTag(){
     /* Search backwards through the document looking for text inside angle-brackets, ignoring
        anything that looks like an XML/HTML comment, a CDATA tag, a DOCTYPE declaration, or 
        any of the special JSP directives  */
        function trim(txt){
            return txt.replace(whitespaceRegex, '');
        }
        doc.findReplace.find("<[^!%\?>][^>]*>");
        var match = doc.selection;
     
        if (match.length > 2){
         // remove the brackets and just return what's inside
            return trim(match.substring(1, match.length - 1));
        } else {
            return '';     
        }
    }
     
};

CODEBOX.tagComplete();