function FileManager(rest_interface) {
    var files = {},
        is_breakable = {},
        programListingTemplate = Handlebars.compile( $('#program-listing-template').html() ),
        array_or_hash_element_or_slice = /^(([$@])(\w+(::\w+)*)([[{]))/,
        scalar_array_hash_or_glob = /^(([$@%*])(\w+(::\w+)*))/,
        basic_dereference = /^(([@%*])(\$\w+(::\w+)*))/,
        perl_special_var = /^(([$@%])(?!&(#x[0-9A-Fa-f]{1,4}|quot|amp|apos|lt|gt);)(\^.|.))/,
        other_perl_special_var = /^(\$&(quot|amp|apos|lt|gt|#x27|#x60);)/;

    var markupPerlVars = function(line) {
        var i = 0,  // index into the string
            output = '';   // the string we're returning

        while (i < line.length) {
            var remaining = line.substr(i);
            if (array_or_hash_element_or_slice.test(remaining)) {
                var varPrefix = RegExp.$1,
                    sigil = RegExp.$2,
                    isSlice = sigil === '@',
                    varName = RegExp.$3,
                    openParen = RegExp.$5,
                    isArray = openParen === '[',
                    containerSigil = isArray ? '@' : '%',
                    endpos = remaining.indexOf(isArray ? ']' : '}');
                if (endpos === null) {
                    // Didn't find it - bail out
                    output = varPrefix;
                    i += varPrefix.length;
                    continue;
                }
                // The indexing portion of the variable expression
                // includes the parens around it
                var arrayIndex = remaining.substring(varPrefix.length - 1, endpos + 1);
                output += '<span class="popup-perl-var" data-eval="'
                            + (isArray ? '@' : '%') + varName + '">' + sigil + varName
                            + '</span><span class="popup-perl-var" data-eval="'
                            + remaining.substring(0, endpos + 1) + '" data-part-of="'
                            + containerSigil + varName + '">'
                            + arrayIndex + '</span>';
                i += (endpos + 1);

            } else if (basic_dereference.test(remaining)) {
                output += '<span class="popup-perl-var" data-eval="'
                            + RegExp.$3 + '">' + RegExp.$1 + '</span>';
                i += RegExp.$1.length;

            } else if (scalar_array_hash_or_glob.test(remaining)) {
                output += '<span class="popup-perl-var" data-eval="'
                            + RegExp.$1 + '">' + RegExp.$1 + '</span>';
                i += RegExp.$1.length;

            } else if (perl_special_var.test(remaining)) {
                output += '<span class="popup-perl-var" data-eval="'
                            + RegExp.$1 + '">' + RegExp.$1 + '</span>';
                i += RegExp.$1.length;

            } else if (other_perl_special_var.test(remaining)) {
                output += '<span class="popup-perl-var" data-eval="'
                            + RegExp.$1 + '">' + RegExp.$1 + '</span>';
                i += RegExp.$1.length;

            } else {
                // Found no perl vars
                output += remaining.charAt(0);
                i++;
                continue;
            }
        }
        return output;
    };

    var sourceFileLoaded = function(filename, source_lines) {
        var templateData, $elt, i;

        if (source_lines) {
            // File is loaded
            templateData = { filename: filename, rows: [] };
            is_breakable[filename] = {};
            for (i = 0; i < source_lines.length; i++) {
                templateData.rows.push({
                        line: i+1,
                        code: Handlebars.Utils.escapeExpression(source_lines[i][0]),  // This gets re-htmlified below in codeElt.html()
                        unbreakable: !source_lines[i][1]
                });
                is_breakable[filename][i+1] = (source_lines[i][1] ? true : false);
            }
            var $elt = $( programListingTemplate(templateData) );
            $elt.find('.code').each(function(idx,codeElt) {
                codeElt = $(codeElt);
                codeElt.html( markupPerlVars(codeElt.text()));
            });
            files[filename].resolve($elt);

        } else {
            // Not loaded
            files[filename].reject();

            // remove the deferred object so we can ask for it again later
            delete files[filename];
        }
    };

    this.loadFile = function(filename) {
        if (! (filename in files)) {
            var d = files[filename] = $.Deferred();
            rest_interface.fileSourceAndBreakable(filename)
                .done(function(data) { sourceFileLoaded(filename, data); });
        }
        return files[filename].promise()
    };

    this.isLoaded = function(filename) {
         return ((filename in files) && files[filename]);
    };
    this.isBreakable = function(filename, line) {
        return is_breakable[filename][line];
    };
}
