packages feed

gf-3.6: lib/doc/browse/script.js

/*
  GF RGL Browser
  John J. Camilleri, 2013
*/

var thing = null;
$(document).ready(function(){

    thing = new Thing();

    // ===== URL history =====
    $.history.on('load change push pushed', function(event, url, type) {
        var stripExt = function(s) {
            var i = s.lastIndexOf('.');
            return (i>-1) ? s.substr(0, i) : s;
        };

        var s = url.split("/");
        var lang = s[0];
        var module = stripExt(s[1]);
        var parseLineNo = s[1].match(/:(\d+)(-(\d+))?$/);

        if (thing.state.current.equals(lang, module)) {
            if (parseLineNo) {
                thing.scrollToCodeLine(parseInt(parseLineNo[1]));
            }
            // else there's nothing to do!
        } else {
            if (parseLineNo != undefined)
                thing.loadFile(lang, module, parseInt(parseLineNo[1]));
            else
                thing.loadFile(lang, module);
        }
    }).listen('hash');
});

function Thing() {
    var t = this;

    // ===== State information =====

    this.state = {
        index: undefined,
        lookup: {},
        loadCount: 0,
	recentCount: 5,
        language: undefined, // lang of drop-down
        current: { // current file
            language: undefined,
            module: undefined,
            set: function(lang, module) {
                t.state.current.language = lang;
                t.state.current.module = module;
            },
            equals: function(a, b) {
                if (!b)
                    return (a == t.state.current.module);
                else
                    return (a == t.state.current.language) && (b == t.state.current.module);
            }
        },
        title: "RGL Source Browser",
        urlPrefix: "/",
        defaultLangs: ['abstract','api','common','prelude']
    } ;

    this.lookupModuleLanguage = function(module) {
        var l = t.state.lookup[module];
        if (l==undefined || l.length==0)
            return null;
        else if (l.length==1)
            return l[0];
        else {
            for (i in l) {
                if ($.inArray(l[i], t.state.defaultLangs))
                    return l[i];
            }
            return l[0]; // no preferred default, just return first...
        }
    }
    this.lookupAllModuleLanguages = function(module) {
        return t.state.lookup[module];
    }

    // ===== Utility/UI functions =====

    this.showLoading = function(){
        t.state.loadCount++;
        $("#loading").show();
    }
    this.hideLoading = function(){
        t.state.loadCount = Math.max(t.state.loadCount-1, 0);
        if (t.state.loadCount == 0)
            $("#loading").hide();
    }

    this.scrollToTop = function() {
        $("html, body").animate({ scrollTop: 0 }, "slow");
    }
    this.scrollToCodeLine = function(lineNo) {
        t.showPanel("#code", function() {
            // Find exact line, using the classes generated by google prettify
            try {
                var obj = $("#code pre li.L"+(lineNo%10)+":eq("+Math.floor(lineNo/10)+")").prev();
                var y = Math.max(obj.offset().top - obj.parent().offset().top - 75, 0);
                $("#code").parent().animate({ scrollTop: y }, "slow", function(){
                    t.highlight(obj);
                });
            } catch (e) {}
        });
    }
    this.highlight = function(obj) {
        obj.css('background-color', "yellow");
        setTimeout(function(){
            obj.css('background-color', "");
        }, 1500);
    }

    this.clearScope = function(msg) {
        $('#scope #results').empty();
        t.updateScopeCount();
        if (msg) {
            $('#scope #results').html("<em>"+msg+"</em>");
        }
    }
    this.setScope = function(code) {
        $('#scope #results').html(code);
    }
    this.clearCode = function(msg) {
        $('#code pre').empty();
        if (msg) {
            $('#codes pre').html("<em>"+msg+"</em>");
        }
    }
    this.setCode = function(code) {
        $('#code pre').text(code);
        prettyPrint();
    }
    this.updateScopeCount = function(){
        $('#scope #count').text( $("#scope #results tr:visible").length );
    }
    this.updateAPICount = function(){
        $('#api #count').text( $("#api #results tr:visible").length );
    }

    this.setLanguage = function(lang){
        t.state.language = lang;
        $("#languages select").val(lang);
        t.initModules(lang);
    }

    // hash should be "#code"
    this.showPanel = function(hash, callback){
        t.showLoading();
        setTimeout(function(){
            $(".panel:visible").hide();
            $("a.tab").removeClass('active');
            $("a.tab[href='"+hash+"']").addClass('active');
            $(hash).show(0, callback);
            t.updateScopeCount();
            t.hideLoading();
        }, 200); // this ensures the loading displays
    }
    this.getPanel = function() {
        return $('.panel:visible').first();
    }

    this.setTitle = function(s){
        $('#module_name').html(s);
        $('title').html(t.state.title + ": " + s);
    }


    // ===== Initialization =====

    // Initialize the panels, tabs
    $("a.tab").click(function(){
        var panel = $(this).attr("href");
        t.showPanel(panel);
        return false;
    });
    t.showPanel("#scope");

    // Load the index file and populate language & module lists
    $.ajax({
        url: "index.json",
        dataType: "json",
        type: "GET",
        success: function(data) {
            t.state.index = data;
            if (data['urlprefix']) t.state.urlPrefix = data['urlprefix'];

            // Build language lookup index
            for (var lang in data['languages']) {
                for (var i in data['languages'][lang]) {
                    var module = data['languages'][lang][i];
                    if (!module) continue;
                    if (!t.state.lookup[module]) t.state.lookup[module] = [];
                    t.state.lookup[module].push(lang);
                }
            }

            // Initialize the language list
            var lang_select = $("<select>")
                .attr('id', 'language_select')
                .change(function(){
                    t.setLanguage($(this).val());
                })
                .appendTo("#languages")
            var language_list = data['languages'];
            for (var i in language_list) {
                if (!i) continue;
                var lang = i;
                $('<option>')
                    .html(lang)
                    .appendTo(lang_select);
            }
            t.setLanguage("english");

	    // Module search box
            var module_search = $("<input>")
                .attr('id', 'module_search')
                .keyup(function(){
                    t.searchModule($(this).val());
                })
                .appendTo("#languages");
	    $("<a>")
		.attr('href','#')
		.click(t.clearSearchModule)
		.html("Clear")
                .appendTo("#languages");

	    // Recent modules
	    var recent = $("<div>")
		.attr('id', 'recent')
                .appendTo("#languages");

            // Initialize API results
            t.initAPI();

            // Done
            t.hideLoading();
        },
        error: function(){
            t.hideLoading();
            alert("Error getting index. Try reloading page, or just give up.");
        }
    });


    // ===== Loading functionality =====

    // Initialize the module list
    this.initModules = function(lang){
        t.state.index['languages'][lang] = t.state.index['languages'][lang].sort();
        $("#modules").empty();
        for (var i in t.state.index['languages'][lang]) {
            var module = t.state.index['languages'][lang][i];
            if (!module) continue;
            $('<a>')
                .html(module)
                .attr('href', "#"+lang+"/"+module+".gf")
                .appendTo("#modules");
        }
    }

    // Load both scope & source for a file
    this.loadFile = function(lang, module, lineNo){
        t.setTitle(lang+"/"+module);
        t.state.current.set(lang, module);
        t.loadTagsFile(module);
        t.loadSourceFile(lang, module, lineNo);
        if ($('.tab.api').hasClass('active'))
	    t.showPanel("#scope");
	t.addRecent(lang, module);
    }

    // Add item to recent list
    this.addRecent = function(lang, module) {
	var full_module = lang+'/'+module;
	// If already there, do nothing
	if ($('#recent').text().indexOf(full_module) > -1) return;
	// Delete oldest if at limit
	if ($('#recent a').length >= t.state.recentCount) {
	    $('#recent a').last().remove();
	}
	// Add it
	$('<a>')
            .html(full_module)
            .attr('href', "#"+lang+"/"+module+".gf")
            .prependTo("#recent");
    }

    // Load a tags file
    this.loadTagsFile = function(module) {
        t.clearScope();
        t.showLoading();
        $.ajax({
            url: "tags/"+module+".gf-tags",
            type: "GET",
            dataType: "text",
            success: function(data){
                data = data.replace(/^(\S+)\s(\S+)\s(.+)?$/gm, function(a,b,c,d){
                    var s = d.split("\t");
                    if (c == "indir") {
                        var module = s[2].substring(s[2].lastIndexOf('/')+1, s[2].lastIndexOf('.'));
                        var lang = t.lookupModuleLanguage(module);
                        var name =    lang+"/"+module;
                        var url = "#"+lang+"/"+module;
                        var anchor = '<a href="'+url+'">'+name+'</a>';
                        return '<tr class="indir" name="'+b+'"><th>'+b+'</th><td>'+c+'</td><td>'+s[0]+'</td><td>'+s[1]+'</td><td>'+anchor+'</td><td></td></tr>'
                    } else {
                        var bits = s[0].split("/"); // ["lib", "src", "english", "AdjectiveEng.gf:43-46"]
                        var name =    bits[3]+"/"+bits[4];
                        var url = "#"+bits[3]+"/"+bits[4];
                        var anchor = '<a href="'+url+'">'+name+'</a>';
                        return '<tr class="local" name="'+b+'"><th>'+b+'</th><td>'+c+'</td><td></td><td></td><td>'+anchor+'</td><td>'+s[1]+'</td></tr>'
                    }
                });
                t.setScope(data);
                t.runFilter();
                t.hideLoading();
            },
            error: function(data){
                t.clearScope("No scope available");
                t.hideLoading();
            },
        });
    }

    // Load a source module
    this.loadSourceFile = function(lang, module, lineNo) {
        t.clearCode();
        t.showLoading();
        $.ajax({
            url: t.state.urlPrefix + "lib/src/"+lang+"/"+module+".gf",
            type: "GET",
            dataType: "text",
            success: function(data, status, xhr){
                t.setCode(data);
                t.hideLoading();
                if (lineNo) {
                    t.scrollToCodeLine(lineNo);
                }
            },
            error: function(data){
                t.clearCode("No code available");
                t.hideLoading();
            }
        });
    }

    // Which modules do we include for API?
    this.apiModules = [
        // api
        "Syntax",
        "Constructors", "Cat", "Structural", "Combinators",
        // abstract
        // "Adjective",
        // "Adverb",
        // "Backward",
        // "Cat",
        // "Common",
        // "Compatibility",
        // "Conjunction",
        // "Extra",
        // "Grammar",
        // "Idiom",
        // "Lang",
        // "Lexicon",
        // "Noun",
        // "Numeral",
        // "NumeralTransfer",
        // "Phrase",
        // "Question",
        // "Relative",
        // "Sentence",
        // "Structural",
        // "Symbol",
        // "Tense",
        // "Text",
        // "Transfer",
        // "Verb",
    ];
    this.initAPI = function() {
        t.showLoading();
        $('#api #results').empty();
        for (var i in t.apiModules) {
            var module = t.apiModules[i];
            $.ajax({
                url: "tags/"+module+".gf-tags",
                type: "GET",
                dataType: "text",
                success: function(data){
                    data = data.replace(/^(\S+)\s(\S+)\s(.+)?$/gm, function(a,b,c,d){
                        var out = '';
                        var s = d.split("\t");
                        if (c != "indir") {
                            var type = s[1];
                            if (type) {
                                var bits = s[0].split("/"); // ["lib", "src", "english", "AdjectiveEng.gf:43-46"]
                                var name = bits[3]+"/"+bits[4];
                                var url = "#"+bits[3]+"/"+bits[4];
                                var anchor = '<a href="'+url+'">'+name+'</a>';
                                out += '<tr name="'+b+'"><th>'+b+'</th><td>'+c+'</td><td>'+anchor+'</td><td>'+s[1]+'</td></tr>'
                            }
                        }
                        return out;
                    });
                    $('#api #results').append($(data));
                    $("#api #results tr").removeClass('odd');
                    $("#api #results tr:odd").addClass('odd');
                    $('#api #count').text( $("#api #results tr").length );
                },
                error: function(data){
                    console.log("Error loading tags file: " + module);
                },
            });
        }
        t.hideLoading();
    }

    // ===== Module search =====

    this.searchModule = function(s) {
	if (!s) {
	    return t.clearSearchModule();
	}
	$('#language_select').hide();
        $("#modules").empty();
        for (var lang in t.state.index['languages']) {
	    var modules = t.state.index['languages'][lang];
	    for (var j in modules) {
		var module = modules[j];
		var full_module = lang+'/'+module;
		if (!module) continue;
		if (full_module.toLowerCase().indexOf(s.toLowerCase())==-1) continue;
		$('<a>')
		    .html(full_module)
		    .attr('href', "#"+lang+"/"+module+".gf")
		    .appendTo("#modules");
	    }
	}
    };

    this.clearSearchModule = function() {
	$('#module_search').val('');
	$('#language_select').show();
	t.setLanguage(t.state.language);
	return false;
    };

    // ===== Filtering of scope info =====

    // Custom selector
    $.expr[':'].match = function(a,b,c) {
        var obj = $(a);
        var needle = c[3];
        var haystack = obj.attr('name');
        if (haystack == undefined)
            return false;
        if ($("#scope #case_sensitive").is(":checked"))
            return haystack.indexOf(needle)>=0;
        else
            return haystack.toLowerCase().indexOf(needle.toLowerCase())>=0;
    };

    this.runFilter = function() {
        t.showLoading();
        $("#scope #results tr").removeClass('odd');
        var s = $("#scope #search").val();
        try {
            if (s) {
                $("#scope #results tr").hide();
                $("#scope #results tr:match(\""+s+"\")").show();
            } else {
                $("#scope #results tr").show();
            }
            if ($("#scope #show_local").is(":checked") ) {
                $("#scope #results tr.indir").hide();
            }
        } catch (error) {
            alert(error.message);
        }
        t.updateScopeCount();
        $("#scope #results tr:visible:odd").addClass('odd');
        t.hideLoading();
    }

    // Instant results
    this.prevSearch = $("#scope #search").val();
    $("#scope #search").keyup(function(){
        var s = $("#scope #search").val();
        if (s!=t.prevSearch) {
            t.runFilter();
            t.prevSearch = s;
        }
    });

    $("#scope #search").keypress(function(e){
        var code = (e.keyCode ? e.keyCode : e.which);
        if(code == 13) { // Enter
            t.runFilter();
        }
    });
    $("#scope #clear").click(function(){
        $("#scope #search")
            .val('')
            .focus()
        t.runFilter();
    });
    $("#scope #case_sensitive").change(t.runFilter);
    $("#scope #show_all").change(t.runFilter);
    $("#scope #show_local").change(t.runFilter);

    // ===== API search =====

    // Custom selector
    $.expr[':'].matchAPI = function(a,b,c) {
        var obj = $(a); // tr
        var ident = $(obj.children().get(0)).text();
        var type  = $(obj.children().get(3)).text();
        var needle = c[3];
        var match_ident = ident.toLowerCase().indexOf(needle.toLowerCase())>=0;
        var match_type  =  type.toLowerCase().indexOf(needle.toLowerCase())>=0;
        // if ($("#scope #case_sensitive").is(":checked"))
        //     return haystack.indexOf(needle)>=0;
        // else
            return match_ident || match_type ;
    };

    this.runFilterAPI = function() {
        t.showLoading();
        $("#api #results tr").removeClass('odd');
        var s = $("#api #search").val();
        try {
            if (s) {
                $("#api #results tr").hide();
                $("#api #results tr:matchAPI(\""+s+"\")").show();
            } else {
                $("#api #results tr").show();
            }
        } catch (error) {
            alert(error.message);
        }
        t.updateAPICount();
        $("#api #results tr:visible:odd").addClass('odd');
        t.hideLoading();
    }

    // Instant results
    this.prevAPISearch = $("#api #search").val();
    $("#api #search").keyup(function(){
        var s = $("#api #search").val();
        if (s!=t.prevAPISearch) {
            t.runFilterAPI();
            t.prevAPISearch = s;
        }
    });

    $("#api #search").keypress(function(e){
        var code = (e.keyCode ? e.keyCode : e.which);
        if(code == 13) { // Enter
            t.runFilterAPI();
        }
    });
    $("#api #clear").click(function(){
        $("#api #search")
            .val('')
            .focus();
        t.runFilterAPI();
    });
};