<commit_before>/*global define, _, chrome, zed */
define(function(require, exports, module) {
    "use strict";
    plugin.consumes = ["eventbus", "command", "sandbox", "configfs"];
    plugin.provides = ["config"];
    return plugin;

    function plugin(options, imports, register) {
        var async = require("./lib/async");
        var path = require("./lib/path");

        var eventbus = imports.eventbus;
        var command = imports.command;
        var sandbox = imports.sandbox;
        var configfs = imports.configfs;

        eventbus.declare("configchanged");
        eventbus.declare("configneedsreloading");

        var minimumConfiguration = {
            imports: [
                "/default.json"],
            preferences: {},
            modes: {},
            keys: {},
            commands: {},
            handlers: {},
            themes: {},
            packages: []
        };

        var config = _.extend({}, minimumConfiguration);
        var userConfig = _.extend({}, minimumConfiguration); // Cache of user.json file

        var expandedConfiguration = _.extend({}, config);

        var api = {
            hook: function() {
                eventbus.on("loadedfilelist", loadConfiguration);
                eventbus.on("configneedsreloading", loadConfiguration);

                eventbus.on("sessionsaved", function(session) {
                    if (session.filename === "/zedconfig.json") {
                        loadConfiguration();
                    }
                });
            },
            writeUserPrefs: writeUserPrefs,
            loadConfiguration: loadConfiguration,
            getPreference: function(key, session) {
                if (session && session.mode) {
                    var mode = session.mode;
                    if (mode.preferences[key] !== undefined) {
                        return mode.preferences[key];
                    }
                }
                return expandedConfiguration.preferences[key];
            },
            setPreference: function(key, value) {
                config.preferences[key] = value;
                userConfig.preferences[key] = value;
                writeUserPrefs();
            },
            togglePreference: function(key, session) {
                var newvalue = !api.getPreference(key, session);
                api.setPreference(key, newvalue);
                return newvalue;
            },
            incrementInteger: function(key, amount, session) {
                var newvalue = api.getPreference(key, session) + amount;
                api.setPreference(key, newvalue);
                return newvalue;
            },
            getModes: function() {
                return expandedConfiguration.modes;
            },
            getKeys: function() {
                return expandedConfiguration.keys;
            },
            getCommands: function() {
                return expandedConfiguration.commands;
            },
            getPreferences: function() {
                return expandedConfiguration.preferences;
            },
            getHandlers: function() {
                return expandedConfiguration.handlers;
            },
            getTheme: function(name) {
                return expandedConfiguration.themes[name];
            },
            getThemes: function() {
                return expandedConfiguration.themes;
            },
            getConfiguration: function() {
                return expandedConfiguration;
            }
        };

        /**
         * This is a super-charged version of _.extend, it recursively merges
         * objects and concatenates arrays (but does not add duplicates).
         * This function does not modify either dest or source, it creates a new
         * object
         */
        function superExtend(dest, source) {
            if (_.isArray(dest)) {
                if (!source) {
                    return dest;
                }
                var newArray = dest.slice();
                for (var i = 0; i < source.length; i++) {
                    var el = source[i];
                    if (newArray.indexOf(el) === -1) {
                        newArray.push(el);
                    }
                }
                return newArray;
            } else if (_.isObject(dest)) {
                dest = _.extend({}, dest); // shallow clone
                if (source) {
                    for (var p in source) {
                        if (source.hasOwnProperty(p)) {
                            if (dest[p] === undefined) {
                                dest[p] = source[p];
                            } else {
                                dest[p] = superExtend(dest[p], source[p]);
                            }
                        }
                    }
                }
                return dest;
            } else {
                return dest !== undefined ? dest : source;
            }
        }

        // ======= WATCHERS =============
        // Setting file watchers (reload config when any of them change)
        var watchers = [];

        function clearWatchers() {
            watchers.forEach(function(watcher) {
                watcher.fs.unwatchFile(watcher.path, watcher.callback);
            });
            watchers = [];
        }

        function watchFile(fs, path, callback) {
            fs.watchFile(path, callback);
            watchers.push({
                fs: fs,
                path: path,
                callback: callback
            });
        }

        /**
         * Recursively import "imports" into the `expandedConfiguration` variable
         */
        function expandConfiguration(setts, importedPackages, callback) {
            setts = _.extend({}, setts);
            var imports = setts.imports;
            delete setts.imports;
            expandedConfiguration = superExtend(expandedConfiguration, setts);

            if (setts.packages && setts.packages.length > 0) {
                _.each(setts.packages, function(uri) {
                    if (!importedPackages[uri]) {
                        imports.push("/packages/" + uri.replace(/:/g, '/') + "/config.json");
                        importedPackages[uri] = true;
                    }
                });
            }

            if (imports) {
                async.forEach(imports, function(imp, next) {
                    function handleFile(err, text) {
                        if (err) {
                            console.warn("Error loading", imp, err);
                            return next();
                        }
                        var json;
                        try {
                            json = JSON.parse(text);
                        } catch (e) {
                            console.error("In file", imp, e);
                            next();
                            return;
                        }
                        resolveRelativePaths(json, imp);
                        expandConfiguration(json, importedPackages, next);
                    }

                    configfs.readFile(imp, handleFile);
                }, function() {
                    callback();
                });
            } else {
                callback();
            }
        }

        function writeUserPrefs() {
            configfs.writeFile("/user.json", JSON.stringify(userConfig, null, 4), function(err) {
                if (err) {
                    console.error("Error during writing config:", err);
                }
            });
        }

        /**
         * Loads config, deciding which config file to use as root:
         * - if a /zedconfig.json file exists in the project, use it
         * - otherwise use the /user.json file in the config project
         */
        function loadConfiguration(callback) {
            if (zed.getService("goto").getFileCache().indexOf("/zedconfig.json") !== -1) {
                zed.getService("fs").readFile("/zedconfig.json", function(err, text) {
                    var base = {};
                    try {
                        base = JSON.parse(text);
                    } catch (e) {
                        console.error(e);
                    }
                    loadUserConfiguration(base, callback);
                });
            } else {
                loadUserConfiguration({}, callback);
            }
        }


        /**
         * Extend the project config (or the empty object, if not present)
         * with config from /user.json from the config project
         */
        function loadUserConfiguration(base, callback) {
            var rootFile = "/user.json";
            config = superExtend(base, minimumConfiguration);
            expandedConfiguration = _.extend({}, config);
            clearWatchers();
            watchFile(configfs, rootFile, loadConfiguration);
            configfs.readFile(rootFile, function(err, config_) {
                try {
                    var json = JSON.parse(config_);
                    resolveRelativePaths(json, rootFile);
                    userConfig = json;
                    config = superExtend(config, json);
                    expandConfiguration(config, {}, function() {
                        eventbus.emit("configchanged", api);
                        _.isFunction(callback) && callback(null, api);
                    });
                } catch (e) {
                    console.error(e);
                }
            });
        }

        /**
         * This function finds all relative script and import paths
         * (scriptUrl keys and for imports) and replaces them with
         * absolute ones
         */
        function resolveRelativePaths(configJson, jsonPath) {
            var dir = path.dirname(jsonPath);
            if (configJson.imports) {
                configJson.imports = _.map(configJson.imports, function(imp) {
                    if (imp[0] === '.' && imp[1] === '/') {<commit_msg>Fixes #227 by continuing when user.json contains parse errors<commit_after>/*global define, _, chrome, zed */
define(function(require, exports, module) {
    "use strict";
    plugin.consumes = ["eventbus", "command", "sandbox", "configfs"];
    plugin.provides = ["config"];
    return plugin;

    function plugin(options, imports, register) {
        var async = require("./lib/async");
        var path = require("./lib/path");

        var eventbus = imports.eventbus;
        var command = imports.command;
        var sandbox = imports.sandbox;
        var configfs = imports.configfs;

        eventbus.declare("configchanged");
        eventbus.declare("configneedsreloading");

        var minimumConfiguration = {
            imports: [
                "/default.json"],
            preferences: {},
            modes: {},
            keys: {},
            commands: {},
            handlers: {},
            themes: {},
            packages: []
        };

        var config = _.extend({}, minimumConfiguration);
        var userConfig = _.extend({}, minimumConfiguration); // Cache of user.json file

        var expandedConfiguration = _.extend({}, config);

        var api = {
            hook: function() {
                eventbus.on("loadedfilelist", loadConfiguration);
                eventbus.on("configneedsreloading", loadConfiguration);

                eventbus.on("sessionsaved", function(session) {
                    if (session.filename === "/zedconfig.json") {
                        loadConfiguration();
                    }
                });
            },
            writeUserPrefs: writeUserPrefs,
            loadConfiguration: loadConfiguration,
            getPreference: function(key, session) {
                if (session && session.mode) {
                    var mode = session.mode;
                    if (mode.preferences[key] !== undefined) {
                        return mode.preferences[key];
                    }
                }
                return expandedConfiguration.preferences[key];
            },
            setPreference: function(key, value) {
                config.preferences[key] = value;
                userConfig.preferences[key] = value;
                writeUserPrefs();
            },
            togglePreference: function(key, session) {
                var newvalue = !api.getPreference(key, session);
                api.setPreference(key, newvalue);
                return newvalue;
            },
            incrementInteger: function(key, amount, session) {
                var newvalue = api.getPreference(key, session) + amount;
                api.setPreference(key, newvalue);
                return newvalue;
            },
            getModes: function() {
                return expandedConfiguration.modes;
            },
            getKeys: function() {
                return expandedConfiguration.keys;
            },
            getCommands: function() {
                return expandedConfiguration.commands;
            },
            getPreferences: function() {
                return expandedConfiguration.preferences;
            },
            getHandlers: function() {
                return expandedConfiguration.handlers;
            },
            getTheme: function(name) {
                return expandedConfiguration.themes[name];
            },
            getThemes: function() {
                return expandedConfiguration.themes;
            },
            getConfiguration: function() {
                return expandedConfiguration;
            }
        };

        /**
         * This is a super-charged version of _.extend, it recursively merges
         * objects and concatenates arrays (but does not add duplicates).
         * This function does not modify either dest or source, it creates a new
         * object
         */
        function superExtend(dest, source) {
            if (_.isArray(dest)) {
                if (!source) {
                    return dest;
                }
                var newArray = dest.slice();
                for (var i = 0; i < source.length; i++) {
                    var el = source[i];
                    if (newArray.indexOf(el) === -1) {
                        newArray.push(el);
                    }
                }
                return newArray;
            } else if (_.isObject(dest)) {
                dest = _.extend({}, dest); // shallow clone
                if (source) {
                    for (var p in source) {
                        if (source.hasOwnProperty(p)) {
                            if (dest[p] === undefined) {
                                dest[p] = source[p];
                            } else {
                                dest[p] = superExtend(dest[p], source[p]);
                            }
                        }
                    }
                }
                return dest;
            } else {
                return dest !== undefined ? dest : source;
            }
        }

        // ======= WATCHERS =============
        // Setting file watchers (reload config when any of them change)
        var watchers = [];

        function clearWatchers() {
            watchers.forEach(function(watcher) {
                watcher.fs.unwatchFile(watcher.path, watcher.callback);
            });
            watchers = [];
        }

        function watchFile(fs, path, callback) {
            fs.watchFile(path, callback);
            watchers.push({
                fs: fs,
                path: path,
                callback: callback
            });
        }

        /**
         * Recursively import "imports" into the `expandedConfiguration` variable
         */
        function expandConfiguration(setts, importedPackages, callback) {
            setts = _.extend({}, setts);
            var imports = setts.imports;
            delete setts.imports;
            expandedConfiguration = superExtend(expandedConfiguration, setts);

            if (setts.packages && setts.packages.length > 0) {
                _.each(setts.packages, function(uri) {
                    if (!importedPackages[uri]) {
                        imports.push("/packages/" + uri.replace(/:/g, '/') + "/config.json");
                        importedPackages[uri] = true;
                    }
                });
            }

            if (imports) {
                async.forEach(imports, function(imp, next) {
                    function handleFile(err, text) {
                        if (err) {
                            console.warn("Error loading", imp, err);
                            return next();
                        }
                        var json;
                        try {
                            json = JSON.parse(text);
                        } catch (e) {
                            console.error("In file", imp, e);
                            next();
                            return;
                        }
                        resolveRelativePaths(json, imp);
                        expandConfiguration(json, importedPackages, next);
                    }

                    configfs.readFile(imp, handleFile);
                }, function() {
                    callback();
                });
            } else {
                callback();
            }
        }

        function writeUserPrefs() {
            configfs.writeFile("/user.json", JSON.stringify(userConfig, null, 4), function(err) {
                if (err) {
                    console.error("Error during writing config:", err);
                }
            });
        }

        /**
         * Loads config, deciding which config file to use as root:
         * - if a /zedconfig.json file exists in the project, use it
         * - otherwise use the /user.json file in the config project
         */
        function loadConfiguration(callback) {
            if (zed.getService("goto").getFileCache().indexOf("/zedconfig.json") !== -1) {
                zed.getService("fs").readFile("/zedconfig.json", function(err, text) {
                    var base = {};
                    try {
                        base = JSON.parse(text);
                    } catch (e) {
                        console.error("Error parsing zedconfig.json", e);
                    }
                    loadUserConfiguration(base, callback);
                });
            } else {
                loadUserConfiguration({}, callback);
            }
        }


        /**
         * Extend the project config (or the empty object, if not present)
         * with config from /user.json from the config project
         */
        function loadUserConfiguration(base, callback) {
            var rootFile = "/user.json";
            config = superExtend(base, minimumConfiguration);
            expandedConfiguration = _.extend({}, config);
            clearWatchers();
            watchFile(configfs, rootFile, loadConfiguration);
            configfs.readFile(rootFile, function(err, config_) {
                var json = {};
                try {
                    json = JSON.parse(config_);
                    resolveRelativePaths(json, rootFile);
                } catch(e) {
                    console.error("Error parsing /user.json", e);
                }
                userConfig = json;
                config = superExtend(config, json);
                expandConfiguration(config, {}, function() {
                    eventbus.emit("configchanged", api);
                    _.isFunction(callback) && callback(null, api);
                });
            });
        }

        /**
         * This function finds all relative script and import paths
         * (scriptUrl keys and for imports) and replaces them with
         * absolute ones
         */
        function resolveRelativePaths(configJson, jsonPath) {
            var dir = path.dirname(jsonPath);
            if (configJson.imports) {
                configJson.imports = _.map(configJson.imports, function(imp) {
                    if (imp[0] === '.' && imp[1] === '/') {
{'fraction_non_alphanumeric': 0.10505623569224644, 'fraction_numerical': 0.0008460236886632825, 'mean_word_length': 1.2236361624432888, 'pattern_counts': {'":': 0, '<': 5, '<?xml version=': 0, '>': 5, 'https://': 0, 'lorem ipsum': 0, 'www.': 0, 'xml': 0}, 'pii_count': 0, 'substrings_counts': 0, 'word_list_counts': {'cursed_substrings.json': 0, 'profanity_word_list.json': 0, 'sexual_word_list.json': 0, 'zh_pornsignals.json': 0}}
{'dir': 'git-commits-cleaned', 'id': '2079117', 'n_tokens_mistral': 4752, 'n_tokens_neox': 4472, 'n_words': 1066}