<commit_before>/*
 * Copyright (C) 2012 Thiago Bellini <hackedbellini@gmail.com>
 *
 * This library is free software; you can redistribute it and/or
 * modify it under the terms of the GNU Lesser General Public
 * License as published by the Free Software Foundation; either
 * version 2.1 of the License, or (at your option) any later version.
 *
 * This library is distributed in the hope that it will be useful,
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
 * Lesser General Public License for more details.
 *
 * You should have received a copy of the GNU Lesser General Public
 * License along with this library; if not, write to the Free Software
 * Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA  02110-1301  USA
 *
 * Some parts of this code were forked from message-notifier:
 *   https://extensions.gnome.org/extension/150/message-notifier/
 * The idea of setting the menu red were inspired by pidgin-persistent-notification:
 *   https://extensions.gnome.org/extension/170/pidgin-peristent-notification
 *
 */

const Lang = imports.lang;
const Mainloop = imports.mainloop;
const Main = imports.ui.main;
const MessageTray = imports.ui.messageTray;

const ExtensionUtils = imports.misc.extensionUtils;
const Me = ExtensionUtils.getCurrentExtension();
const Lib = Me.imports.lib;

const SETTING_BLINK_RATE = 'blinkrate';
const SETTING_COLOR = 'color';
const SETTING_CHAT_ONLY = 'chatonly';
const SETTING_FORCE = 'force';

let settings, messageStyleHandler;
let originalCountUpdated, originalDestroy;

function _MessageStyleHandler() {

  this._loopTimeoutId = null;
  this._oldStyle = null;
  this._hasStyleAdded = false;

  /*
     Public API
  */

  this.enable = function() {
    this._notificationsSwitch = Main.panel.statusArea.userMenu._notificationsSwitch;
    this._notificationsSwitchToggledSignal = this._notificationsSwitch.connect(
        'toggled', Lang.bind(this, this._onNotificationsSwitchToggled));

    // Connect settings change events, so we can update message style
    // as soon as the user makes the change
    settings.connect("changed::" + SETTING_COLOR,
                     Lang.bind(this, this._onSettingsChanged));
    settings.connect("changed::" + SETTING_CHAT_ONLY,
                     Lang.bind(this, this._onSettingsChanged));
    settings.connect("changed::" + SETTING_FORCE,
                     Lang.bind(this, this._onSettingsChanged));
    settings.connect("changed::" + SETTING_BLINK_RATE,
                     Lang.bind(this, this._onSettingsChanged));

    // Check for existing message counters when extension were
    // loaded on an already running shell.
    this.updateMessageStyle();
  }

  this.disable = function() {
    this._notificationsSwitch.disconnect(this._notificationsSwitchToggledSignal);
    this._removeMessageStyle();
  }

  this.updateMessageStyle = function() {
    let items = Main.messageTray.getSummaryItems();

    if (settings.get_boolean(SETTING_FORCE) ||
        this._notificationsSwitch.state) {
      let chatOnly = settings.get_boolean(SETTING_CHAT_ONLY);

      for (let i = 0; i < items.length; i++) {
        let source = items[i].source;

        if (chatOnly && !source.isChat) {
          // The user choose to only be alerted by real chat notifications
          continue;
        }
        if (source.isMuted) {
          // Do not alert for muted notifications
          continue;
        }
        if (this._hasNotifications(source)) {
          this._addMessageStyle();
          return;
        }
      }
    }
    // If for above ended without adding the style, that means there's
    // no counter and we need to remove the message style.
    this._removeMessageStyle();
  }

  /*
     Private
  */

  this._hasNotifications = function(source) {
    // FIXME: We should be using source.unseenCount > 0 here
    // but something is wrong on notifications system. Rhythmbox's only
    // notifications is always not acknowledged and that was producing
    // false positives. Relying on countVisible for now is the best way to go
    if (source.countVisible) {
      return true;
    }
    for (let n = 0; n < source.notifications.length; n++) {
      if (!source.notifications[n].resident) {
        // Do not alert resident notifications (like Rhythmbox ones)
        return true;
      }
    }
    return false;
  }

  this._loopStyle = function(toggle) {
    let userMenu = Main.panel._statusArea.userMenu;

    if (!this._hasStyleAdded) {
      // notifications may have been cleared since loop timer was added
      return;
    }
    let style = toggle ?
      "color: " + settings.get_string(SETTING_COLOR) :
      this._oldStyle;

    userMenu._iconBox.set_style(style);

    // loop it
    let loopDelay = settings.get_int(SETTING_BLINK_RATE);
    if (loopDelay > 0) {
      // For some reason, trying to use this directly above
      // will result in "this._loopStyle is not a function" error
      let that = this;
      this._loopTimeoutId = Mainloop.timeout_add(
        loopDelay, function() {
          that._loopStyle(!toggle)
        });
    }
  }

  this._addMessageStyle = function() {
    let userMenu = Main.panel.statusArea.userMenu;

    if (!this._hasStyleAdded) {
      // Only cache oldStyle when when adding style the first time.
      // Do this to support change the notification color as soon the
      // setting changes.
      this._oldStyle = userMenu._iconBox.get_style();
    }

    this._loopStyle(true);
    this._hasStyleAdded = true;
  }

  this._removeMessageStyle = function() {
    if (!this._hasStyleAdded) {
      return;
    }

    if (this._loopTimeoutId != null) {
      // Prevent a possible race condition
      Mainloop.source_remove(this._loopTimeoutId);
    }

    let userMenu = Main.panel.statusArea.userMenu;
    userMenu._iconBox.style = this._oldStyle;
    this._oldStyle = null;
    this._hasStyleAdded = false;
  }

  /*
     Callbacks
  */

  this._onSettingsChanged = function() {
    this.updateMessageStyle();
  }

  this._onNotificationsSwitchToggled = function() {
    this.updateMessageStyle();
  }
}

/*
   Monkey-patchs for MessageTray.Source
*/

function _countUpdated() {
  originalCountUpdated.call(this);

  messageStyleHandler.updateMessageStyle();
}

function _destroy() {
  originalDestroy.call(this);

  messageStyleHandler.updateMessageStyle();
}

/*
   Shell-extensions handlers
*/

function init() {
  Lib.initTranslations(Me);
  settings = Lib.getSettings(Me);
}

function enable() {
  messageStyleHandler = new _MessageStyleHandler();

  originalCountUpdated = MessageTray.Source.prototype.countUpdated;
  originalDestroy = MessageTray.Source.prototype.destroy;

  MessageTray.Source.prototype.countUpdated = _countUpdated;
  MessageTray.Source.prototype.destroy = _destroy;

  messageStyleHandler.enable();
}

function disable() {
  MessageTray.Source.prototype.countUpdated = originalCountUpdated;
  MessageTray.Source.prototype.destroy = originalDestroy;

  messageStyleHandler.disable();
}
<commit_msg>Prevent some race conditions<commit_after>/*
 * Copyright (C) 2012 Thiago Bellini <hackedbellini@gmail.com>
 *
 * This library is free software; you can redistribute it and/or
 * modify it under the terms of the GNU Lesser General Public
 * License as published by the Free Software Foundation; either
 * version 2.1 of the License, or (at your option) any later version.
 *
 * This library is distributed in the hope that it will be useful,
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
 * Lesser General Public License for more details.
 *
 * You should have received a copy of the GNU Lesser General Public
 * License along with this library; if not, write to the Free Software
 * Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA  02110-1301  USA
 *
 * Some parts of this code were forked from message-notifier:
 *   https://extensions.gnome.org/extension/150/message-notifier/
 * The idea of setting the menu red were inspired by pidgin-persistent-notification:
 *   https://extensions.gnome.org/extension/170/pidgin-peristent-notification
 *
 */

const Lang = imports.lang;
const Mainloop = imports.mainloop;
const Main = imports.ui.main;
const MessageTray = imports.ui.messageTray;

const ExtensionUtils = imports.misc.extensionUtils;
const Me = ExtensionUtils.getCurrentExtension();
const Lib = Me.imports.lib;

const SETTING_BLINK_RATE = 'blinkrate';
const SETTING_COLOR = 'color';
const SETTING_CHAT_ONLY = 'chatonly';
const SETTING_FORCE = 'force';

let settings, messageStyleHandler;
let originalCountUpdated, originalDestroy;

function _MessageStyleHandler() {

  this._loopTimeoutId = null;
  this._oldStyle = null;
  this._hasStyleAdded = false;

  /*
     Public API
  */

  this.enable = function() {
    this._notificationsSwitch = Main.panel.statusArea.userMenu._notificationsSwitch;
    this._notificationsSwitchToggledSignal = this._notificationsSwitch.connect(
        'toggled', Lang.bind(this, this._onNotificationsSwitchToggled));

    // Connect settings change events, so we can update message style
    // as soon as the user makes the change
    settings.connect("changed::" + SETTING_COLOR,
                     Lang.bind(this, this._onSettingsChanged));
    settings.connect("changed::" + SETTING_CHAT_ONLY,
                     Lang.bind(this, this._onSettingsChanged));
    settings.connect("changed::" + SETTING_FORCE,
                     Lang.bind(this, this._onSettingsChanged));
    settings.connect("changed::" + SETTING_BLINK_RATE,
                     Lang.bind(this, this._onSettingsChanged));

    // Check for existing message counters when extension were
    // loaded on an already running shell.
    this.updateMessageStyle();
  }

  this.disable = function() {
    this._notificationsSwitch.disconnect(this._notificationsSwitchToggledSignal);
    this._removeMessageStyle();
  }

  this.updateMessageStyle = function() {
    let items = Main.messageTray.getSummaryItems();

    if (settings.get_boolean(SETTING_FORCE) ||
        this._notificationsSwitch.state) {
      let chatOnly = settings.get_boolean(SETTING_CHAT_ONLY);

      for (let i = 0; i < items.length; i++) {
        let source = items[i].source;

        if (chatOnly && !source.isChat) {
          // The user choose to only be alerted by real chat notifications
          continue;
        }
        if (source.isMuted) {
          // Do not alert for muted notifications
          continue;
        }
        if (this._hasNotifications(source)) {
          this._addMessageStyle();
          return;
        }
      }
    }
    // If for above ended without adding the style, that means there's
    // no counter and we need to remove the message style.
    this._removeMessageStyle();
  }

  /*
     Private
  */

  this._hasNotifications = function(source) {
    // FIXME: We should be using source.unseenCount > 0 here
    // but something is wrong on notifications system. Rhythmbox's only
    // notifications is always not acknowledged and that was producing
    // false positives. Relying on countVisible for now is the best way to go
    if (source.countVisible) {
      return true;
    }
    for (let n = 0; n < source.notifications.length; n++) {
      if (!source.notifications[n].resident) {
        // Do not alert resident notifications (like Rhythmbox ones)
        return true;
      }
    }
    return false;
  }

  this._loopStyle = function(toggle) {
    let userMenu = Main.panel._statusArea.userMenu;
    let loopDelay = settings.get_int(SETTING_BLINK_RATE);
    let willLoop = loopDelay > 0;

    if (!this._hasStyleAdded) {
      // notifications may have been cleared since loop timer was added
      return;
    }

    let style = willLoop && toggle ?
      this._oldStyle :
      "color: " + settings.get_string(SETTING_COLOR);
    userMenu._iconBox.set_style(style);

    // loop it
    if (loopDelay > 0) {
      if (this._loopTimeoutId != null) {
        // Prevent more than one loop being executed
        Mainloop.source_remove(this._loopTimeoutId);
      }
      // For some reason, trying to use this directly above
      // will result in "this._loopStyle is not a function" error
      let that = this;
      this._loopTimeoutId = Mainloop.timeout_add(
        loopDelay, function() {
          that._loopStyle(!toggle)
        });
    }
  }

  this._addMessageStyle = function() {
    let userMenu = Main.panel.statusArea.userMenu;

    if (!this._hasStyleAdded) {
      // Only cache oldStyle when when adding style the first time.
      // Do this to support change the notification color as soon the
      // setting changes.
      let oldStyle = userMenu._iconBox.get_style();
      if (oldStyle != settings.get_string(SETTING_COLOR)) {
        // Changing SETTING_BLINK_RATE from something to 0 can produce
        // a race condition where the loop stops when the style is the
        // one we we added. Prevent it for overwriting this._oldStyle
        this._oldStyle = oldStyle;
      }
    }

    this._loopStyle(false);
    this._hasStyleAdded = true;
  }

  this._removeMessageStyle = function() {
    if (!this._hasStyleAdded) {
      return;
    }

    if (this._loopTimeoutId != null) {
      // Prevent a possible race condition
      Mainloop.source_remove(this._loopTimeoutId);
    }

    let userMenu = Main.panel.statusArea.userMenu;
    userMenu._iconBox.style = this._oldStyle;
    this._oldStyle = null;
    this._hasStyleAdded = false;
  }

  /*
     Callbacks
  */

  this._onSettingsChanged = function() {
    this.updateMessageStyle();
  }

  this._onNotificationsSwitchToggled = function() {
    this.updateMessageStyle();
  }
}

/*
   Monkey-patchs for MessageTray.Source
*/

function _countUpdated() {
  originalCountUpdated.call(this);

  messageStyleHandler.updateMessageStyle();
}

function _destroy() {
  originalDestroy.call(this);

  messageStyleHandler.updateMessageStyle();
}

/*
   Shell-extensions handlers
*/

function init() {
  Lib.initTranslations(Me);
  settings = Lib.getSettings(Me);
}

function enable() {
  messageStyleHandler = new _MessageStyleHandler();

  originalCountUpdated = MessageTray.Source.prototype.countUpdated;
  originalDestroy = MessageTray.Source.prototype.destroy;

  MessageTray.Source.prototype.countUpdated = _countUpdated;
  MessageTray.Source.prototype.destroy = _destroy;

  messageStyleHandler.enable();
}

function disable() {
  MessageTray.Source.prototype.countUpdated = originalCountUpdated;
  MessageTray.Source.prototype.destroy = originalDestroy;

  messageStyleHandler.disable();
}
<|endoftext|>


<commit_before>let vscode = require('vscode');

class Resolver {
    importClass() {
        let activeEditor = this.activeEditor();
        let resolving = this.resolving(activeEditor);

        if (resolving === null) {
            return this.showMessage(`$(issue-opened)  No class is selected.`, true);
        }

        this.findFiles()
            .then(files => this.findNamespaces(activeEditor, resolving, files))
            .then(namespaces => this.pickClass(namespaces))
            .then(pickedClass => {
                let useStatements, declarationLines;

                try {
                    [useStatements, declarationLines] = this.getDeclarations(activeEditor, pickedClass);
                } catch (error) {
                    return this.showMessage(error.message, true);
                }

                if (! this.hasConflict(useStatements, resolving)) {
                    return this.insert(activeEditor, pickedClass, declarationLines);
                }

                vscode.window.showInputBox({
                    placeHolder: 'Enter an alias'
                }).then(alias => {
                    if (alias !== undefined && alias !== '') {
                        this.insert(activeEditor, pickedClass, declarationLines, alias);
                    }
                });
            });
    }

    expandClass() {
        let activeEditor = this.activeEditor();
        let resolving = this.resolving(activeEditor);

        if (resolving === null) {
            return this.showMessage(`$(issue-opened)  No class is selected.`, true);
        }

        this.findFiles()
            .then(files => this.findNamespaces(activeEditor, resolving, files))
            .then(namespaces => this.pickClass(namespaces))
            .then(pickedClass => {
                activeEditor.edit(textEdit => {
                    let selections = activeEditor.selections;

                    for (let i = 0; i < selections.length; i++) {
                        textEdit.replace(
                            activeEditor.document.getWordRangeAtPosition(selections[i].active),
                            (this.config('leadingSeparator') ? '\\' : '') + pickedClass
                        );
                    }
                });
            });
    }

    sortImports() {
        try {
            this.sort(this.activeEditor());
        } catch (error) {
            return this.showMessage(error.message, true);
        }

        this.showMessage('$(check)  Imports sorted.');
    }

    findFiles() {
        return vscode.workspace.findFiles('**/*.php', this.config('exclude'));
    }

    findNamespaces(activeEditor, resolving, files) {
        return new Promise((resolve, reject) => {
            let textDocuments = this.getTextDocuments(files, resolving);

            Promise.all(textDocuments).then(docs => {
                let parsedNamespaces = this.parseNamespaces(docs, resolving);

                if (parsedNamespaces.length === 0) {
                    return this.showMessage(`$(circle-slash)  Class ' ${resolving} ' not found.`, true);
                }

                resolve(parsedNamespaces);
            });
        });
    }

    pickClass(namespaces) {
        return new Promise((resolve, reject) => {
            if (namespaces.length === 1) {
                // There is only one namespace found so return with the first namespace.
                return resolve(namespaces[0]);
            }

            vscode.window.showQuickPick(namespaces).then(picked => {
                if (picked !== undefined) {
                    resolve(picked);
                }
            });
        })
    }

    insert(activeEditor, pickedClass, declarationLines, alias = null) {
        let [prepend, append, insertLine] = this.getInsertLine(declarationLines);

        activeEditor.edit(textEdit => {
            textEdit.replace(
                new vscode.Position((insertLine), 0),
                (`${prepend}use ${pickedClass}`) + (alias !== null ? ` as ${alias}` : '') + (`;${append}`)
            );
        });

        if (this.config('autoSort')) {
            activeEditor.document.save().then(() => {
                try {
                    this.sort(activeEditor);
                } catch (error) {
                    // I don't care. LOL
                }
            });
        }

        this.showMessage('$(check)  Class imported.');
    }

    getTextDocuments(files, resolving) {
        let textDocuments = [];

        for (let i = 0; i < files.length; i++) {
            let fileName = files[i].fsPath.replace(/^.*[\\\/]/, '').split('.')[0];

            if (fileName !== resolving) {
                continue;
            }

            textDocuments.push(vscode.workspace.openTextDocument(files[i]));
        }

        return textDocuments;
    }

    parseNamespaces(docs, resolving) {
        let parsedNamespaces = [];

        for (let i = 0; i < docs.length; i++) {
            for (let line = 0; line < docs[i].lineCount; line++) {
                let textLine = docs[i].lineAt(line).text;

                let namespace;
                let hasNamespace = false;

                if (textLine.startsWith('namespace ') || textLine.startsWith('<?php namespace ')) {
                    hasNamespace = true;
                    namespace = textLine.split('namespace ')[1].split(';')[0] + '\\' + resolving;
                } else if (textLine.startsWith('class ') || textLine.startsWith('<?php class ')) {
                    namespace = textLine.match(/class\s(\w+)/)[1];
                }

                if (namespace !== undefined && parsedNamespaces.indexOf(namespace) === -1) {
                    parsedNamespaces.push(namespace);
                }

                if (hasNamespace) {
                    break;
                }
            }
        }

        return parsedNamespaces;
    }

    sort(activeEditor) {
        let useStatements = this.getDeclarations(activeEditor);

        if (useStatements.length <= 1) {
            throw new Error('$(issue-opened)  Nothing to sort.');
        }

        let sorted = useStatements.slice().sort((a, b) => {
            if (this.config('sortAlphabetically')) {
                if (a.text.toLowerCase() < b.text.toLowerCase()) return -1;
                if (a.text.toLowerCase() > b.text.toLowerCase()) return 1;
                return 0;
            } else {
                return a.text.length - b.text.length;
            }
        });

        activeEditor.edit(textEdit => {
            for (let i = 0; i < sorted.length; i++) {
                textEdit.replace(
                    new vscode.Range(useStatements[i].line, 0, useStatements[i].line, useStatements[i].text.length),
                    sorted[i].text
                );
            }
        });
    }

    hasConflict(useStatements, resolving) {
        for (let i = 0; i < useStatements.length; i++) {
            if (useStatements[i].text.toLowerCase().split(`\\${resolving.toLowerCase()};`).length === 2) {
                return true;
            }
        }

        return false;
    }

    getDeclarations(activeEditor, pickedClass = null) {
        let useStatements = [];
        let declarationLines = {
            PHPTag: null,
            namespace: null,
            useStatement: null,
            class: null
        };

        for (let line = 0; line < activeEditor.document.lineCount; line++) {
            let text = activeEditor.document.lineAt(line).text;

            if (pickedClass !== null && text === `use ${pickedClass};`) {
                throw new Error('$(issue-opened)  Class already imported.');
            }

            // break if all declarations were found
            if (declarationLines.PHPTag && declarationLines.namespace &&
                declarationLines.useStatement && declarationLines.class) {
                break;
            }

            if (text.startsWith('<?php')) {
                declarationLines.PHPTag = line + 1;
            } else if (text.startsWith('namespace ') || text.startsWith('<?php namespace')) {
                declarationLines.namespace = line + 1;
            } else if (text.startsWith('use ')) {
                useStatements.push({ text, line });
                declarationLines.useStatement = line + 1;
            } else if (text.startsWith('final ')
                || text.startsWith('abstract ')
                || text.startsWith('class ')
                || text.startsWith('trait ')
                || text.startsWith('interface ')
            ) {
                declarationLines.class = line + 1;
            } else {
                continue;
            }
        }

        if (declarationLines.PHPTag === null) {
            throw new Error('$(circle-slash)  Can not import class in this file.');
        }

        if (pickedClass === null) {
            return useStatements;
        }

        return [useStatements, declarationLines];
    }

    getInsertLine(declarationLines) {
        let prepend = '\n';
        let append = '\n';
        let insertLine = declarationLines.PHPTag;

        if (declarationLines.useStatement !== null) {
            prepend = '';
            insertLine = declarationLines.useStatement;
        } else if (declarationLines.namespace !== null) {
            insertLine = declarationLines.namespace;
        }

        if (declarationLines.class !== null &&
            ((declarationLines.class - declarationLines.useStatement) <= 1 ||
            (declarationLines.class - declarationLines.namespace) <= 1 ||
            (declarationLines.class - declarationLines.PHPTag) <= 1)
        ) {
            append = '\n\n';
        }

        return [prepend, append, insertLine];
    }

    activeEditor() {
        return vscode.window.activeTextEditor;
    }

    resolving(activeEditor) {
        let wordRange = activeEditor.document.getWordRangeAtPosition(activeEditor.selection.active);

        if (wordRange === undefined) {
            return null;
        }

        return activeEditor.document.getText(wordRange);
    }

    config(key) {
        return vscode.workspace.getConfiguration('namespaceResolver').get(key);
    }

    showMessage(message, error = false) {
        if (this.config('showMessageOnStatusBar')) {
            return vscode.window.setStatusBarMessage(message, 3000);
        }

        let notify = vscode.window.showInformationMessage;

        if (error) {
            notify = vscode.window.showErrorMessage;
        }

        notify(message.replace(/\$\(.+?\)\s\s/, ''));
    }
}

function activate(context) {
    let resolver = new Resolver();

    let importNamespace = vscode.commands.registerCommand('namespaceResolver.import', () => resolver.importClass());
    let expandNamespace = vscode.commands.registerCommand('namespaceResolver.expand', () => resolver.expandClass());
    let sortNamespaces = vscode.commands.registerCommand('namespaceResolver.sort', () => resolver.sortImports());

    context.subscriptions.push(importNamespace);
    context.subscriptions.push(expandNamespace);
    context.subscriptions.push(sortNamespaces);
    context.subscriptions.push(resolver);
}

exports.activate = activate;
<commit_msg>Optimise<commit_after>let vscode = require('vscode');

class Resolver {
    importClass() {
        let activeEditor = this.activeEditor();
        let resolving = this.resolving(activeEditor);

        if (resolving === null) {
            return this.showMessage(`$(issue-opened)  No class is selected.`, true);
        }

        this.findFiles()
            .then(files => this.findNamespaces(activeEditor, resolving, files))
            .then(namespaces => this.pickClass(namespaces))
            .then(pickedClass => {
                let useStatements, declarationLines;

                try {
                    [useStatements, declarationLines] = this.getDeclarations(activeEditor, pickedClass);
                } catch (error) {
                    return this.showMessage(error.message, true);
                }

                if (! this.hasConflict(useStatements, resolving)) {
                    return this.insert(activeEditor, pickedClass, declarationLines);
                }

                vscode.window.showInputBox({
                    placeHolder: 'Enter an alias'
                }).then(alias => {
                    if (alias !== undefined && alias !== '') {
                        this.insert(activeEditor, pickedClass, declarationLines, alias);
                    }
                });
            });
    }

    expandClass() {
        let activeEditor = this.activeEditor();
        let resolving = this.resolving(activeEditor);

        if (resolving === null) {
            return this.showMessage(`$(issue-opened)  No class is selected.`, true);
        }

        this.findFiles()
            .then(files => this.findNamespaces(activeEditor, resolving, files))
            .then(namespaces => this.pickClass(namespaces))
            .then(pickedClass => {
                activeEditor.edit(textEdit => {
                    let selections = activeEditor.selections;

                    for (let i = 0; i < selections.length; i++) {
                        textEdit.replace(
                            activeEditor.document.getWordRangeAtPosition(selections[i].active),
                            (this.config('leadingSeparator') ? '\\' : '') + pickedClass
                        );
                    }
                });
            });
    }

    sortImports() {
        try {
            this.sort(this.activeEditor());
        } catch (error) {
            return this.showMessage(error.message, true);
        }

        this.showMessage('$(check)  Imports sorted.');
    }

    findFiles() {
        return vscode.workspace.findFiles('**/*.php', this.config('exclude'));
    }

    findNamespaces(activeEditor, resolving, files) {
        return new Promise((resolve, reject) => {
            let textDocuments = this.getTextDocuments(files, resolving);

            Promise.all(textDocuments).then(docs => {
                let parsedNamespaces = this.parseNamespaces(docs, resolving);

                if (parsedNamespaces.length === 0) {
                    return this.showMessage(`$(circle-slash)  Class ' ${resolving} ' not found.`, true);
                }

                resolve(parsedNamespaces);
            });
        });
    }

    pickClass(namespaces) {
        return new Promise((resolve, reject) => {
            if (namespaces.length === 1) {
                // There is only one namespace found so return with the first namespace.
                return resolve(namespaces[0]);
            }

            vscode.window.showQuickPick(namespaces).then(picked => {
                if (picked !== undefined) {
                    resolve(picked);
                }
            });
        })
    }

    insert(activeEditor, pickedClass, declarationLines, alias = null) {
        let [prepend, append, insertLine] = this.getInsertLine(declarationLines);

        activeEditor.edit(textEdit => {
            textEdit.replace(
                new vscode.Position((insertLine), 0),
                (`${prepend}use ${pickedClass}`) + (alias !== null ? ` as ${alias}` : '') + (`;${append}`)
            );
        });

        if (this.config('autoSort')) {
            activeEditor.document.save().then(() => {
                try {
                    this.sort(activeEditor);
                } catch (error) {
                    // I don't care. LOL
                }
            });
        }

        this.showMessage('$(check)  Class imported.');
    }

    getTextDocuments(files, resolving) {
        let textDocuments = [];

        for (let i = 0; i < files.length; i++) {
            let fileName = files[i].fsPath.replace(/^.*[\\\/]/, '').split('.')[0];

            if (fileName !== resolving) {
                continue;
            }

            textDocuments.push(vscode.workspace.openTextDocument(files[i]));
        }

        return textDocuments;
    }

    parseNamespaces(docs, resolving) {
        let parsedNamespaces = [];

        for (let i = 0; i < docs.length; i++) {
            for (let line = 0; line < docs[i].lineCount; line++) {
                let textLine = docs[i].lineAt(line).text;

                let namespace;

                if (textLine.startsWith('namespace ') || textLine.startsWith('<?php namespace ')) {
                    namespace = textLine.split('namespace ')[1].split(';')[0] + '\\' + resolving;
                } else if (textLine.startsWith('class ') || textLine.startsWith('<?php class ')) {
                    namespace = textLine.match(/class\s(\w+)/)[1];
                }

                if (namespace !== undefined && parsedNamespaces.indexOf(namespace) === -1) {
                    parsedNamespaces.push(namespace);
                    break;
                }
            }
        }

        return parsedNamespaces;
    }

    sort(activeEditor) {
        let useStatements = this.getDeclarations(activeEditor);

        if (useStatements.length <= 1) {
            throw new Error('$(issue-opened)  Nothing to sort.');
        }

        let sorted = useStatements.slice().sort((a, b) => {
            if (this.config('sortAlphabetically')) {
                if (a.text.toLowerCase() < b.text.toLowerCase()) return -1;
                if (a.text.toLowerCase() > b.text.toLowerCase()) return 1;
                return 0;
            } else {
                return a.text.length - b.text.length;
            }
        });

        activeEditor.edit(textEdit => {
            for (let i = 0; i < sorted.length; i++) {
                textEdit.replace(
                    new vscode.Range(useStatements[i].line, 0, useStatements[i].line, useStatements[i].text.length),
                    sorted[i].text
                );
            }
        });
    }

    hasConflict(useStatements, resolving) {
        for (let i = 0; i < useStatements.length; i++) {
            if (useStatements[i].text.toLowerCase().split(`\\${resolving.toLowerCase()};`).length === 2) {
                return true;
            }
        }

        return false;
    }

    getDeclarations(activeEditor, pickedClass = null) {
        let useStatements = [];
        let declarationLines = {
            PHPTag: null,
            namespace: null,
            useStatement: null,
            class: null
        };

        for (let line = 0; line < activeEditor.document.lineCount; line++) {
            let text = activeEditor.document.lineAt(line).text;

            if (pickedClass !== null && text === `use ${pickedClass};`) {
                throw new Error('$(issue-opened)  Class already imported.');
            }

            // break if all declarations were found
            if (declarationLines.PHPTag && declarationLines.namespace &&
                declarationLines.useStatement && declarationLines.class) {
                break;
            }

            if (text.startsWith('<?php')) {
                declarationLines.PHPTag = line + 1;
            } else if (text.startsWith('namespace ') || text.startsWith('<?php namespace')) {
                declarationLines.namespace = line + 1;
            } else if (text.startsWith('use ')) {
                useStatements.push({ text, line });
                declarationLines.useStatement = line + 1;
            } else if (text.startsWith('final ')
                || text.startsWith('abstract ')
                || text.startsWith('class ')
                || text.startsWith('trait ')
                || text.startsWith('interface ')
            ) {
                declarationLines.class = line + 1;
            } else {
                continue;
            }
        }

        if (declarationLines.PHPTag === null) {
            throw new Error('$(circle-slash)  Can not import class in this file.');
        }

        if (pickedClass === null) {
            return useStatements;
        }

        return [useStatements, declarationLines];
    }

    getInsertLine(declarationLines) {
        let prepend = '\n';
        let append = '\n';
        let insertLine = declarationLines.PHPTag;

        if (declarationLines.useStatement !== null) {
            prepend = '';
            insertLine = declarationLines.useStatement;
        } else if (declarationLines.namespace !== null) {
            insertLine = declarationLines.namespace;
        }

        if (declarationLines.class !== null &&
            ((declarationLines.class - declarationLines.useStatement) <= 1 ||
            (declarationLines.class - declarationLines.namespace) <= 1 ||
            (declarationLines.class - declarationLines.PHPTag) <= 1)
        ) {
            append = '\n\n';
        }

        return [prepend, append, insertLine];
    }

    activeEditor() {
        return vscode.window.activeTextEditor;
    }

    resolving(activeEditor) {
        let wordRange = activeEditor.document.getWordRangeAtPosition(activeEditor.selection.active);

        if (wordRange === undefined) {
            return null;
        }

        return activeEditor.document.getText(wordRange);
    }

    config(key) {
        return vscode.workspace.getConfiguration('namespaceResolver').get(key);
    }

    showMessage(message, error = false) {
        if (this.config('showMessageOnStatusBar')) {
            return vscode.window.setStatusBarMessage(message, 3000);
        }

        let notify = vscode.window.showInformationMessage;

        if (error) {
            notify = vscode.window.showErrorMessage;
        }

        notify(message.replace(/\$\(.+?\)\s\s/, ''));
    }
}

function activate(context) {
    let resolver = new Resolver();

    let importNamespace = vscode.commands.registerCommand('namespaceResolver.import', () => resolver.importClass());
    let expandNamespace = vscode.commands.registerCommand('namespaceResolver.expand', () => resolver.expandClass());
    let sortNamespaces = vscode.commands.registerCommand('namespaceResolver.sort', () => resolver.sortImports());

    context.subscriptions.push(importNamespace);
    context.subscriptions.push(expandNamespace);
    context.subscriptions.push(sortNamespaces);
    context.subscriptions.push(resolver);
}

exports.activate = activate;
<|endoftext|>


<commit_before>export const users = ['Alex', 'Amanda', 'Alistair', 'Aretha', 'Allison', 'Aaron', 'Austin', 'Alyssa', 'Angela']<commit_msg>add some more names<commit_after>export const users = [
	'Alex',
	'Amanda',
	'Alistair',
	'Aretha',
	'Allison',
	'Aaron',
	'Austin',
	'Alyssa',
	'Angela',
	'Noah',
	'Liam',
	'Mason',
	'Jacob',
	'William',
	'Ethan',
	'James',
	'Alexander',
	'Michael',
	'Benjamin',
	'Emma',
	'Olivia',
	'Sophia',
	'Ava',
	'Isabella',
	'Emily',
	'Charlotte',
	'Harper'
]<|endoftext|>


<commit_before>(function() { // wrap in anonymous function to not show some helper variables
var ParsonsWidget = function(options) {
    this.modified_lines = [];
    this.extra_lines = [];
    this.model_solution = [];

    //To collect statistics, feedback should not be based on this
    this.user_actions = [];

    var defaults = {
            'incorrectSound': false,
            'x_indent': 50,
            'feedback_cb': false,
            'first_error_only': true,
            'max_wrong_lines': 10,
            'trash_label': 'Drag from here',
            'solution_label': 'Construct your solution here'
    };

    this.options = jQuery.extend({}, defaults, options);
    this.feedback_exists = false;
    this.FEEDBACK_STYLES = { 'correctPosition' : 'correctPosition',
            'incorrectPosition' : 'incorrectPosition',
            'correctIndent' : 'correctIndent',
            'incorrectIndent' : 'incorrectIndent'};
};

// regexp used for trimming
var trimRegexp = /^\s*(.*?)\s*$/;

//Public methods

ParsonsWidget.prototype.parseLine = function(spacePrefixedLine) {
    return {
	code: spacePrefixedLine.replace(trimRegexp, "$1").replace(/\\n/,"\n"),
	indent: spacePrefixedLine.length - spacePrefixedLine.replace(/^\s+/,"").length
    };
}

ParsonsWidget.prototype.parseCode = function(lines, max_distractors) {
    var distractors = [],
    indented = [],
    widgetData = [],
    lineObject,
    errors = [],
    that = this;
    $.each(lines, function(index, item) {
	    if (item.search(/#distractor\s*$/) >= 0) {
		lineObject = {
		    code: item.replace(/#distractor\s*$/,"").replace(trimRegexp, "$1").replace(/\\n/,"\n"),
		    indent: -1,
		    distractor: true,
		    orig: index
		};
		if (lineObject.code.length > 0) {
		    distractors.push(lineObject);
		}
	    } else {
		lineObject = that.parseLine(item);
		if (lineObject.code.length > 0) {
		    lineObject.distractor = false;
		    lineObject.orig = index;
		    indented.push(lineObject);
		}
	    }
	});

    // Normalize indents and make sure indentation is valid
    var normalized = this.normalizeIndents(indented);

    $.each(normalized, function(index, item) {
	    if (item.indent < 0) {
		errors.push("Line " + normalized.orig + " is not correctly indented. No matching indentation.");
	    }
	    widgetData.push(item);
        });

    // Remove extra distractors
    permutation = this.getRandomPermutation(distractors.length)
    var selected_distractors = []
    for (var i = 0; i < max_distractors; i++) {
	selected_distractors.push(distractors[permutation[i]]);
	widgetData.push(distractors[permutation[i]]);
    }

    return {
	solution:  $.extend(true, [], normalized),
        distractors: $.extend(true, [], selected_distractors),
        widgetInitial: $.extend(true, [], widgetData),
        errors: errors};
};

ParsonsWidget.prototype.init = function(text) {
    var initial_structures = this.parseCode(text.split("\n"), this.options.max_wrong_lines);
    this.model_solution = initial_structures.solution;
    this.extra_lines = initial_structures.distractors;
    this.modified_lines = initial_structures.widgetInitial;

    $.each(this.modified_lines, function(index, item) {
	    item.id = 'codeline' + index;
	    item.indent = 0;
        });

};

ParsonsWidget.prototype.addLogEntry = function(entry, extend) {
    var logData = {};
    if (entry && !extend) {
        this.user_actions.push(entry);
    } else {
        if (this.options.trashId) {
            logData = {
                time: new Date(),
                output: jQuery.extend(true, [], this.getModifiedCode("#ul-" + this.options.sortableId)),
                input: jQuery.extend(true, [], this.getModifiedCode("#ul-" + this.options.trashId)),
                type: "action"
            };
        } else {
            logData = {
                time: new Date(),
                output: jQuery.extend(true, [], this.getModifiedCode("#ul-" + this.options.sortableId)),
                type: "action"
            };
        }
        if (entry && extend) {
          jQuery.extend(logData, entry);
        }
        this.user_actions.push(logData);
    }
};

/**
 * Update indentation of a line based on new coordinates
 * leftDiff horizontal difference from (before and after drag) in px
 ***/
ParsonsWidget.prototype.updateIndent = function(leftDiff, id) {
    var code_line = this.getLineById(id);
    var new_indent = code_line.indent + Math.floor(leftDiff / this.options.x_indent);
    new_indent = Math.max(0, new_indent);
    code_line.indent = new_indent;
    return new_indent;
};

/**
 *
 * @param id
 * @return
 */
ParsonsWidget.prototype.getLineById = function(id) {
    var index = -1;
    for (var i = 0; i < this.modified_lines.length; i++) {
        if (this.modified_lines[i].id == id) {
            index = i;
            break;
        }
    }
    return this.modified_lines[index];
};

/** Does not use the current object - only the argument */
ParsonsWidget.prototype.normalizeIndents = function(lines) {

    var normalized = [];
    var new_line;
    var match_indent = function(index) {
        //return line index from the previous lines with matching indentation
        for (var i = index-1; i >= 0; i--) {
            if (lines[i].indent == lines[index].indent) {
                return normalized[i].indent;
            }
        }
        return -1;
    };
    for ( var i = 0; i < lines.length; i++ ) {
        //create shallow copy from the line object
        new_line = jQuery.extend({}, lines[i]);
        if (i === 0) {
            new_line.indent = 0;
            if (lines[i].indent !== 0) {
              new_line.indent = -1;
            }
        } else if (lines[i].indent == lines[i-1].indent) {
            new_line.indent = normalized[i-1].indent;
        } else if (lines[i].indent > lines[i-1].indent) {
            new_line.indent = normalized[i-1].indent + 1;
        } else {
            // indentation can be -1 if no matching indentation exists, i.e. IndentationError in Python
            new_line.indent = match_indent(i);
        }
        normalized[i] = new_line;
    }
    return normalized;
};

/**
 * Retrieve the code lines based on what is in the DOM
 *
 * TODO(petri) refactor to UI
 * */
ParsonsWidget.prototype.getModifiedCode = function(search_string) {
    //ids of the the modified code
    var users_code_ids = $(search_string).sortable('toArray');
    var lines_to_return = [];
    for ( var i = 0; i < users_code_ids.length; i++ ) {
        lines_to_return[i] = this.getLineById(users_code_ids[i]);
    }
    return lines_to_return;
};

/**
 * TODO(petri) refoctor to UI
 */
ParsonsWidget.prototype.displayError = function(message) {
    if (this.options.incorrectSound && $.sound) {
        $.sound.play(this.options.incorrectSound);
    }
    alert(message);
};

/**
 * @return
 * TODO(petri): Separate UI from here
 */
ParsonsWidget.prototype.getFeedback = function() {
    this.feedback_exists = true;
    var student_code = this.normalizeIndents(this.getModifiedCode("#ul-" + this.options.sortableId));
    var lines_to_check = Math.min(student_code.length, this.model_solution.length);
    var errors = [], log_errors = [];

    var lines = _.map(student_code, function(line) {
      return parseInt(line.id.substring(8), 10);
    });
    var inv = LIS.best_lise_inverse(lines);
    var incorrectLines = [];
    _.each(inv, function(itemId) {
      $("#codeline" + itemId).addClass("incorrectPosition");
      incorrectLines.push(itemId);
    });
    if (inv.length > 0) {
      errors.push("Some lines in incorrect position relative to others");
      log_errors.push({type: "incorrectPosition", lines: incorrectLines});
    }

    // Always show this feedback
    if (this.model_solution.length < student_code.length) {
        $("#ul-" + this.options.sortableId).addClass("incorrect");
        errors.push("Too many lines in your solution");
        log_errors.push({type: "tooManyLines", lines: student_code.length});
    } else if (this.model_solution.length > student_code.length){
        $("#ul-" + this.options.sortableId).addClass("incorrect");
        errors.push("Too few lines in your solution");
        log_errors.push({type: "tooFewLines", lines: student_code.length});
    }

    if (errors.length == 0) { // check indent if no other errors
      for (var i = 0; i < lines_to_check; i++) {
        var code_line = student_code[i];
        var model_line = this.model_solution[i];
        // need to uncomment following if distractors are added back!!
        /*if (code_line.code !== model_line.code &&
                ((!this.options.first_error_only) || errors.length == 0)) {
            $("#" + code_line.id).addClass("incorrectPosition");
            errors.push("line " + (i+1) + " is not correct!");
            log_errors.push({type: "incorrectPosition", line: (i+1)});
        }*/
        if (code_line.indent !== model_line.indent &&
                ((!this.options.first_error_only) || errors.length == 0)) {
            $("#" + code_line.id).addClass("incorrectIndent");
            errors.push("line " + (i+1) + " is not indented correctly");
            log_errors.push({type: "incorrectIndent", line: (i+1)});
        }
        if (code_line.code == model_line.code &&
            code_line.indent == model_line.indent &&
            errors.length == 0) {
            $("#" + code_line.id).addClass("correctPosition");
        }
      }
    }

    if (errors.length == 0) {
        if (this.options.correctSound && $.sound) {
            $.sound.play(this.options.correctSound);
        }
        $("#ul-" + this.options.sortableId).addClass("correct");
    }

    if (this.options.feedback_cb) {
        feedback_cb(); //TODO(petri): what is needed?
    }
    this.addLogEntry({type: "feedback", errors: log_errors}, true);
    //alert("ok");
    return errors;
};

ParsonsWidget.prototype.clearFeedback = function() {
    if (this.feedback_exists) {
        $("#ul-" + this.options.sortableId).removeClass("incorrect correct");
        var li_elements = $("#ul-" + this.options.sortableId + " li");
        $.each(this.FEEDBACK_STYLES, function(index, value) {
            li_elements.removeClass(value);
        });
    }
    this.feedback_exists = false;
};

ParsonsWidget.prototype.getRandomPermutation = function(n) {
    var permutation = [];
    var i;
    for (i = 0; i < n; i++) {
        permutation.push(i);
    }
    var swap1, swap2, tmp;
    for (i = 0; i < n; i++) {
        swap1 = Math.floor(Math.random() * n);
        swap2 = Math.floor(Math.random() * n);
        tmp = permutation[swap1];
        permutation[swap1] = permutation[swap2];
        permutation[swap2] = tmp;
    }
    return permutation;
};

ParsonsWidget.prototype.getFixedPermutation = function(n) {
    var permutation = [];
    var i;
    for (i = 0; i < n; i++) {
        permutation.push(i);
    }

    return permutation;
};

ParsonsWidget.prototype.shuffleLines = function() {
    this.createHtml(this.getRandomPermutation);
};

/** modifies the DOM by inserting exercise elements into it */
ParsonsWidget.prototype.createHtml = function(randomizeCallback) {
// TODO(petri): needs more refactoring
        var codelines = [];
        var initial_state = []; //used only for logging

        var that = this;
        for (var i=0; i<this.modified_lines.length; i++) {
            codelines.push('
' + this.modified_lines[i].code + '<\/li>');
        }

        //randomize is a permutation array, i.e. array with index values where [1, 2, ..., n] implies nothing is permutated
        if (randomizeCallback) {
            var permutation = randomizeCallback(codelines.length);
            var randomized_lines = [];
            for (i = 0; i < codelines.length; i++) {
                randomized_lines[i] = codelines[permutation[i]];
                initial_state[i] = this.modified_lines[permutation[i]];
            }
            codelines = randomized_lines;
        } else {
          initial_state = this.modified_lines;
        }

        this.addLogEntry({type: 'init', time: new Date(), initial: initial_state});

        if (this.options.trashId) {
            $("#" + this.options.trashId).html('
'+this.options.trash_label+'

'+codelines.join('')+'
');
            $("#" + this.options.sortableId).html('
'+this.options.solution_label+'

');
        } else {
            var d = $("#" + this.options.sortableId);
            var h = '
'+codelines.join('')+'
';
            $("#" + this.options.sortableId).html('
'+codelines.join('')+'
');
        }
        if (window.prettyPrint && (typeof(this.options.prettyPrint) === "undefined" || this.options.prettyPrint)) {
            prettyPrint(); //NOT IMPLEMENTET YET?
        }
        var sortable = $("#ul-" + this.options.sortableId).sortable({
            start : function() { that.clearFeedback(); },
            stop : function(event, ui) {
                if ($(event.target)[0] != ui.item.parent()[0]) {
                    return;
                }
                var ind = that.updateIndent(ui.position.left - ui.item.parent().offset().left,
                                        ui.item[0].id);
                ui.item.css("margin-left", that.options.x_indent * ind + "px");
                that.addLogEntry({type: "moveOutput", target: ui.item[0].id}, true);
            },
            receive : function(event, ui) {
                var ind = that.updateIndent(ui.position.left - ui.item.parent().offset().left,
                                        ui.item[0].id);
                ui.item.css("margin-left", that.options.x_indent * ind + "px");
                that.addLogEntry({type: "addOutput", target: ui.item[0].id}, true);
            },
            grid : [that.options.x_indent, 1 ]
        });
        if (this.options.trashId) {
            var trash = $("#ul-" + this.options.trashId).sortable({
                connectWith: sortable,
                start: function() { that.clearFeedback(); },
                receive: function(event, ui) {
                    that.getLineById(ui.item[0].id).indent = 0;
                    ui.item.css("margin-left", "0");
                    that.addLogEntry({type: "removeOutput", target: ui.item[0].id}, true);
                },
                stop: function(event, ui) {
                    if ($(event.target)[0] != ui.item.parent()[0]) {
                        // line moved to output and logged there
                        return;
                    }
                    that.addLogEntry({type: "moveInput", target: ui.item[0].id}, true);
                }
            });
            sortable.sortable('option', 'connectWith', trash);
        }
    };
    window['ParsonsWidget'] = ParsonsWidget;
})();<commit_msg>trash_label and solution_label are not added if options are falsy values<commit_after>(function() { // wrap in anonymous function to not show some helper variables
var ParsonsWidget = function(options) {
    this.modified_lines = [];
    this.extra_lines = [];
    this.model_solution = [];

    //To collect statistics, feedback should not be based on this
    this.user_actions = [];

    var defaults = {
            'incorrectSound': false,
            'x_indent': 50,
            'feedback_cb': false,
            'first_error_only': true,
            'max_wrong_lines': 10,
            'trash_label': 'Drag from here',
            'solution_label': 'Construct your solution here'
    };

    this.options = jQuery.extend({}, defaults, options);
    this.feedback_exists = false;
    this.FEEDBACK_STYLES = { 'correctPosition' : 'correctPosition',
            'incorrectPosition' : 'incorrectPosition',
            'correctIndent' : 'correctIndent',
            'incorrectIndent' : 'incorrectIndent'};
};

// regexp used for trimming
var trimRegexp = /^\s*(.*?)\s*$/;

//Public methods

ParsonsWidget.prototype.parseLine = function(spacePrefixedLine) {
    return {
	code: spacePrefixedLine.replace(trimRegexp, "$1").replace(/\\n/,"\n"),
	indent: spacePrefixedLine.length - spacePrefixedLine.replace(/^\s+/,"").length
    };
}

ParsonsWidget.prototype.parseCode = function(lines, max_distractors) {
    var distractors = [],
    indented = [],
    widgetData = [],
    lineObject,
    errors = [],
    that = this;
    $.each(lines, function(index, item) {
	    if (item.search(/#distractor\s*$/) >= 0) {
		lineObject = {
		    code: item.replace(/#distractor\s*$/,"").replace(trimRegexp, "$1").replace(/\\n/,"\n"),
		    indent: -1,
		    distractor: true,
		    orig: index
		};
		if (lineObject.code.length > 0) {
		    distractors.push(lineObject);
		}
	    } else {
		lineObject = that.parseLine(item);
		if (lineObject.code.length > 0) {
		    lineObject.distractor = false;
		    lineObject.orig = index;
		    indented.push(lineObject);
		}
	    }
	});

    // Normalize indents and make sure indentation is valid
    var normalized = this.normalizeIndents(indented);

    $.each(normalized, function(index, item) {
	    if (item.indent < 0) {
		errors.push("Line " + normalized.orig + " is not correctly indented. No matching indentation.");
	    }
	    widgetData.push(item);
        });

    // Remove extra distractors
    permutation = this.getRandomPermutation(distractors.length)
    var selected_distractors = []
    for (var i = 0; i < max_distractors; i++) {
	selected_distractors.push(distractors[permutation[i]]);
	widgetData.push(distractors[permutation[i]]);
    }

    return {
	solution:  $.extend(true, [], normalized),
        distractors: $.extend(true, [], selected_distractors),
        widgetInitial: $.extend(true, [], widgetData),
        errors: errors};
};

ParsonsWidget.prototype.init = function(text) {
    var initial_structures = this.parseCode(text.split("\n"), this.options.max_wrong_lines);
    this.model_solution = initial_structures.solution;
    this.extra_lines = initial_structures.distractors;
    this.modified_lines = initial_structures.widgetInitial;

    $.each(this.modified_lines, function(index, item) {
	    item.id = 'codeline' + index;
	    item.indent = 0;
        });

};

ParsonsWidget.prototype.addLogEntry = function(entry, extend) {
    var logData = {};
    if (entry && !extend) {
        this.user_actions.push(entry);
    } else {
        if (this.options.trashId) {
            logData = {
                time: new Date(),
                output: jQuery.extend(true, [], this.getModifiedCode("#ul-" + this.options.sortableId)),
                input: jQuery.extend(true, [], this.getModifiedCode("#ul-" + this.options.trashId)),
                type: "action"
            };
        } else {
            logData = {
                time: new Date(),
                output: jQuery.extend(true, [], this.getModifiedCode("#ul-" + this.options.sortableId)),
                type: "action"
            };
        }
        if (entry && extend) {
          jQuery.extend(logData, entry);
        }
        this.user_actions.push(logData);
    }
};

/**
 * Update indentation of a line based on new coordinates
 * leftDiff horizontal difference from (before and after drag) in px
 ***/
ParsonsWidget.prototype.updateIndent = function(leftDiff, id) {
    var code_line = this.getLineById(id);
    var new_indent = code_line.indent + Math.floor(leftDiff / this.options.x_indent);
    new_indent = Math.max(0, new_indent);
    code_line.indent = new_indent;
    return new_indent;
};

/**
 *
 * @param id
 * @return
 */
ParsonsWidget.prototype.getLineById = function(id) {
    var index = -1;
    for (var i = 0; i < this.modified_lines.length; i++) {
        if (this.modified_lines[i].id == id) {
            index = i;
            break;
        }
    }
    return this.modified_lines[index];
};

/** Does not use the current object - only the argument */
ParsonsWidget.prototype.normalizeIndents = function(lines) {

    var normalized = [];
    var new_line;
    var match_indent = function(index) {
        //return line index from the previous lines with matching indentation
        for (var i = index-1; i >= 0; i--) {
            if (lines[i].indent == lines[index].indent) {
                return normalized[i].indent;
            }
        }
        return -1;
    };
    for ( var i = 0; i < lines.length; i++ ) {
        //create shallow copy from the line object
        new_line = jQuery.extend({}, lines[i]);
        if (i === 0) {
            new_line.indent = 0;
            if (lines[i].indent !== 0) {
              new_line.indent = -1;
            }
        } else if (lines[i].indent == lines[i-1].indent) {
            new_line.indent = normalized[i-1].indent;
        } else if (lines[i].indent > lines[i-1].indent) {
            new_line.indent = normalized[i-1].indent + 1;
        } else {
            // indentation can be -1 if no matching indentation exists, i.e. IndentationError in Python
            new_line.indent = match_indent(i);
        }
        normalized[i] = new_line;
    }
    return normalized;
};

/**
 * Retrieve the code lines based on what is in the DOM
 *
 * TODO(petri) refactor to UI
 * */
ParsonsWidget.prototype.getModifiedCode = function(search_string) {
    //ids of the the modified code
    var users_code_ids = $(search_string).sortable('toArray');
    var lines_to_return = [];
    for ( var i = 0; i < users_code_ids.length; i++ ) {
        lines_to_return[i] = this.getLineById(users_code_ids[i]);
    }
    return lines_to_return;
};

/**
 * TODO(petri) refoctor to UI
 */
ParsonsWidget.prototype.displayError = function(message) {
    if (this.options.incorrectSound && $.sound) {
        $.sound.play(this.options.incorrectSound);
    }
    alert(message);
};

/**
 * @return
 * TODO(petri): Separate UI from here
 */
ParsonsWidget.prototype.getFeedback = function() {
    this.feedback_exists = true;
    var student_code = this.normalizeIndents(this.getModifiedCode("#ul-" + this.options.sortableId));
    var lines_to_check = Math.min(student_code.length, this.model_solution.length);
    var errors = [], log_errors = [];

    var lines = _.map(student_code, function(line) {
      return parseInt(line.id.substring(8), 10);
    });
    var inv = LIS.best_lise_inverse(lines);
    var incorrectLines = [];
    _.each(inv, function(itemId) {
      $("#codeline" + itemId).addClass("incorrectPosition");
      incorrectLines.push(itemId);
    });
    if (inv.length > 0) {
      errors.push("Some lines in incorrect position relative to others");
      log_errors.push({type: "incorrectPosition", lines: incorrectLines});
    }

    // Always show this feedback
    if (this.model_solution.length < student_code.length) {
        $("#ul-" + this.options.sortableId).addClass("incorrect");
        errors.push("Too many lines in your solution");
        log_errors.push({type: "tooManyLines", lines: student_code.length});
    } else if (this.model_solution.length > student_code.length){
        $("#ul-" + this.options.sortableId).addClass("incorrect");
        errors.push("Too few lines in your solution");
        log_errors.push({type: "tooFewLines", lines: student_code.length});
    }

    if (errors.length == 0) { // check indent if no other errors
      for (var i = 0; i < lines_to_check; i++) {
        var code_line = student_code[i];
        var model_line = this.model_solution[i];
        // need to uncomment following if distractors are added back!!
        /*if (code_line.code !== model_line.code &&
                ((!this.options.first_error_only) || errors.length == 0)) {
            $("#" + code_line.id).addClass("incorrectPosition");
            errors.push("line " + (i+1) + " is not correct!");
            log_errors.push({type: "incorrectPosition", line: (i+1)});
        }*/
        if (code_line.indent !== model_line.indent &&
                ((!this.options.first_error_only) || errors.length == 0)) {
            $("#" + code_line.id).addClass("incorrectIndent");
            errors.push("line " + (i+1) + " is not indented correctly");
            log_errors.push({type: "incorrectIndent", line: (i+1)});
        }
        if (code_line.code == model_line.code &&
            code_line.indent == model_line.indent &&
            errors.length == 0) {
            $("#" + code_line.id).addClass("correctPosition");
        }
      }
    }

    if (errors.length == 0) {
        if (this.options.correctSound && $.sound) {
            $.sound.play(this.options.correctSound);
        }
        $("#ul-" + this.options.sortableId).addClass("correct");
    }

    if (this.options.feedback_cb) {
        feedback_cb(); //TODO(petri): what is needed?
    }
    this.addLogEntry({type: "feedback", errors: log_errors}, true);
    //alert("ok");
    return errors;
};

ParsonsWidget.prototype.clearFeedback = function() {
    if (this.feedback_exists) {
        $("#ul-" + this.options.sortableId).removeClass("incorrect correct");
        var li_elements = $("#ul-" + this.options.sortableId + " li");
        $.each(this.FEEDBACK_STYLES, function(index, value) {
            li_elements.removeClass(value);
        });
    }
    this.feedback_exists = false;
};

ParsonsWidget.prototype.getRandomPermutation = function(n) {
    var permutation = [];
    var i;
    for (i = 0; i < n; i++) {
        permutation.push(i);
    }
    var swap1, swap2, tmp;
    for (i = 0; i < n; i++) {
        swap1 = Math.floor(Math.random() * n);
        swap2 = Math.floor(Math.random() * n);
        tmp = permutation[swap1];
        permutation[swap1] = permutation[swap2];
        permutation[swap2] = tmp;
    }
    return permutation;
};

ParsonsWidget.prototype.getFixedPermutation = function(n) {
    var permutation = [];
    var i;
    for (i = 0; i < n; i++) {
        permutation.push(i);
    }

    return permutation;
};

ParsonsWidget.prototype.shuffleLines = function() {
    this.createHtml(this.getRandomPermutation);
};

/** modifies the DOM by inserting exercise elements into it */
ParsonsWidget.prototype.createHtml = function(randomizeCallback) {
// TODO(petri): needs more refactoring
        var codelines = [];
        var initial_state = []; //used only for logging

        var that = this;
        for (var i=0; i<this.modified_lines.length; i++) {
            codelines.push('
' + this.modified_lines[i].code + '<\/li>');
        }

        //randomize is a permutation array, i.e. array with index values where [1, 2, ..., n] implies nothing is permutated
        if (randomizeCallback) {
            var permutation = randomizeCallback(codelines.length);
            var randomized_lines = [];
            for (i = 0; i < codelines.length; i++) {
                randomized_lines[i] = codelines[permutation[i]];
                initial_state[i] = this.modified_lines[permutation[i]];
            }
            codelines = randomized_lines;
        } else {
          initial_state = this.modified_lines;
        }

        this.addLogEntry({type: 'init', time: new Date(), initial: initial_state});

        if (this.options.trashId) {
            var html = (this.options.trash_label?'
'+this.options.trash_label+'
':'') +
                      '
'+codelines.join('')+'
';
            $("#" + this.options.trashId).html(html);
            html = (this.options.solution_label?'
'+this.options.solution_label+'
':'') +
                  '

'
            $("#" + this.options.sortableId).html(html);
        } else {
            var d = $("#" + this.options.sortableId);
            var h = '
'+codelines.join('')+'
';
            $("#" + this.options.sortableId).html('
'+codelines.join('')+'
');
        }
        if (window.prettyPrint && (typeof(this.options.prettyPrint) === "undefined" || this.options.prettyPrint)) {
            prettyPrint(); //NOT IMPLEMENTET YET?
        }
        var sortable = $("#ul-" + this.options.sortableId).sortable({
            start : function() { that.clearFeedback(); },
            stop : function(event, ui) {
                if ($(event.target)[0] != ui.item.parent()[0]) {
                    return;
                }
                var ind = that.updateIndent(ui.position.left - ui.item.parent().offset().left,
                                        ui.item[0].id);
                ui.item.css("margin-left", that.options.x_indent * ind + "px");
                that.addLogEntry({type: "moveOutput", target: ui.item[0].id}, true);
            },
            receive : function(event, ui) {
                var ind = that.updateIndent(ui.position.left - ui.item.parent().offset().left,
                                        ui.item[0].id);
                ui.item.css("margin-left", that.options.x_indent * ind + "px");
                that.addLogEntry({type: "addOutput", target: ui.item[0].id}, true);
            },
            grid : [that.options.x_indent, 1 ]
        });
        if (this.options.trashId) {
            var trash = $("#ul-" + this.options.trashId).sortable({
                connectWith: sortable,
                start: function() { that.clearFeedback(); },
                receive: function(event, ui) {
                    that.getLineById(ui.item[0].id).indent = 0;
                    ui.item.css("margin-left", "0");
                    that.addLogEntry({type: "removeOutput", target: ui.item[0].id}, true);
                },
                stop: function(event, ui) {
                    if ($(event.target)[0] != ui.item.parent()[0]) {
                        // line moved to output and logged there
                        return;
                    }
                    that.addLogEntry({type: "moveInput", target: ui.item[0].id}, true);
                }
            });
            sortable.sortable('option', 'connectWith', trash);
        }
    };
    window['ParsonsWidget'] = ParsonsWidget;
})();<|endoftext|>


<commit_before>'use strict';

var sepItems    = require('es5-ext/lib/Array/prototype/sep-items')
  , copy        = require('es5-ext/lib/Object/copy')
  , d           = require('es5-ext/lib/Object/descriptor')
  , makeElement = require('dom-ext/lib/Document/prototype/make-element')
  , Input       = require('../_component')

  , defineProperty = Object.defineProperty
  , toDOMInput;

toDOMInput = function (document/*, options*/) {
	var options = Object(arguments[1]), rels = {}, inputs = {}, controlOpts
	  , el = makeElement.bind(document), dom;

	controlOpts = copy(options);
	dom = el('div', sepItems.call(this.triggers.values.map(function (name) {
		rels[name] = this.get(name);
	}, this.obj).sort(function (a, b) {
		return a.__order.__value - b.__order.__value;
	}).map(function (rel) {
		return (inputs[rel.name] = rel.toDOMInput(document, controlOpts));
	}), (options.sep != null) ? options.sep : ' '));

	return new Input(this, inputs, rels, dom);
};

module.exports = function (rel) {
	defineProperty(rel, 'toDOMInput', d(toDOMInput));
};
<commit_msg>Fix line component.<commit_after>'use strict';

var sepItems    = require('es5-ext/lib/Array/prototype/sep-items')
  , copy        = require('es5-ext/lib/Object/copy')
  , d           = require('es5-ext/lib/Object/descriptor')
  , makeElement = require('dom-ext/lib/Document/prototype/make-element')
  , Input       = require('../_component')

  , defineProperty = Object.defineProperty
  , toDOMInput;

toDOMInput = function (document/*, options*/) {
	var options = Object(arguments[1]), rels = {}, inputs = {}, controlOpts
	  , el = makeElement.bind(document), dom;

	controlOpts = copy(options);
	dom = el('div', sepItems.call(this.triggers.values.map(function (name) {
		return (rels[name] = this.get(name));
	}, this.obj).sort(function (a, b) {
		return a.__order.__value - b.__order.__value;
	}).map(function (rel) {
		return (inputs[rel.name] = rel.toDOMInput(document, controlOpts));
	}), (options.sep != null) ? options.sep : ' '));

	return new Input(this, inputs, rels, dom);
};

module.exports = function (rel) {
	defineProperty(rel, 'toDOMInput', d(toDOMInput));
};
<|endoftext|>


<commit_before>var moment = require('moment');
var redis = require("redis");
var ExchangeDescription = require("./exchange_description")
var Quotation = require("./quotation")

function  ExchangeRedisStore(  ) {
  this.symbol_expire_at = {};

    this.client = redis.createClient();
    this.client.on("error", function(err) {
    	logger.error("redis error %s",  err);
    });
    this.client.on("ready", function(err) {
    	logger.info("redis ready");
    });

    this.set_expire = function( obj ){
      var symbol = obj.symbol;
      var now = moment(obj.time);
      var expire_at = now.startOf('week').startOf('day').add(2, 'weeks').format('x');
      expire_at = parseInt(expire_at );
      var key = this.build_zkey( symbol, obj.time);
      if( this.symbol_expire_at[symbol] != expire_at)
      {
        this.client.expireat([key, expire_at], function(err, response){
            if (err) throw err;
            //console.log("key=%s,expire_at=%s,response=%s",key,expire_at,response);
        })
        this.symbol_expire_at[symbol] = expire_at;
        console.log("redis key %s expire at %s", symbol, expire_at);
      }
    }

    this.store = function( obj )
    {
      //#汇率在redis 中的存储结构
      //# name is symbol
      //# orderlist   [time.to_f(score: time.to_i), time.to_f(time.to_i)]
      //# hash        [time.to_f: price, time.to_f: price]

      //  #ZADD key score1 member1 [score2 member2]
      //  #向有序集合添加一个或多个成员，或者更新已存在成员的分数
      var now = moment(obj.time);
      var key = obj.symbol +"_"+  now.startOf('week').startOf('day').format('x');
      var hmkey = "HM_" + key;
      var zkey = "Z_" + key;
      var val = obj.time.format('x') +"_"+ obj.new_value; // we could order it in client.
      if( obj instanceof ExchangeDescription)
      {
          //http://momentjs.com/docs/#/displaying/
          //Unix Timestamp	X	1360013296
          //Unix Millisecond Timestamp	x	1360013296123
          // 一周过期，键值 = 数据格式类型 + 业务类型 + 一周开始时间
          //this.client.hmset( hmkey, parseInt(obj.time.format('x')), obj.new_value )
          this.client.zadd( [zkey, obj.time.format('x'), val]  )
          //#   redis.zadd("zset", 32.0, "member")
      }
      else if ( obj instanceof Quotation) {
        //console.log("store Quotation %s,%s, %s", zkey,  obj.new_value, obj.time.format('x') );
        //this.client.hmset( hmkey, parseInt(obj.time.format('x')), obj.new_value )
        // it has to be uniq, add Timestamp as suffix
        if( obj.delta >0 )
        {
          this.client.zadd( [zkey, obj.time.format('x'), val] )
        }
      }
      this.set_expire( obj);
      this.client.publish( obj.symbol, val );
    }

    this.build_zkey = function( symbol, now){
      var key = "Z_"+ symbol +"_"+  now.startOf('week').startOf('day').format('x');
      return key;
    }
}

module.exports = ExchangeRedisStore;

ExchangeRedisStore.prototype.constructor = ExchangeRedisStore; // make stacktraces readable
<commit_msg>fix expire at<commit_after>var moment = require('moment');
var redis = require("redis");
var ExchangeDescription = require("./exchange_description")
var Quotation = require("./quotation")

function  ExchangeRedisStore(  ) {
  this.symbol_expire_at = {};

    this.client = redis.createClient();
    this.client.on("error", function(err) {
    	logger.error("redis error %s",  err);
    });
    this.client.on("ready", function(err) {
    	logger.info("redis ready");
    });

    this.set_expire = function( obj ){
      var symbol = obj.symbol;
      var now = moment(obj.time);
      var key = this.build_zkey( symbol, now);
      var expire_at = now.startOf('week').startOf('day').add(2, 'weeks').format('x');
      expire_at = parseInt(expire_at );
      if( this.symbol_expire_at[symbol] != expire_at)
      {
        this.client.pexpireat([key, expire_at], function(err, response){
            if (err) throw err;
            //console.log("key=%s,expire_at=%s,response=%s",key,expire_at,response);
        })
        this.symbol_expire_at[symbol] = expire_at;
        console.log("redis key %s expire at %s", symbol, expire_at);
      }
    }

    this.store = function( obj )
    {
      //#汇率在redis 中的存储结构
      //# name is symbol
      //# orderlist   [time.to_f(score: time.to_i), time.to_f(time.to_i)]
      //# hash        [time.to_f: price, time.to_f: price]

      //  #ZADD key score1 member1 [score2 member2]
      //  #向有序集合添加一个或多个成员，或者更新已存在成员的分数
      var now = moment(obj.time);
      var key = obj.symbol +"_"+  now.startOf('week').startOf('day').format('x');
      var hmkey = "HM_" + key;
      var zkey = "Z_" + key;
      var val = obj.time.format('x') +"_"+ obj.new_value; // we could order it in client.
      if( obj instanceof ExchangeDescription)
      {
          //http://momentjs.com/docs/#/displaying/
          //Unix Timestamp	X	1360013296
          //Unix Millisecond Timestamp	x	1360013296123
          // 一周过期，键值 = 数据格式类型 + 业务类型 + 一周开始时间
          //this.client.hmset( hmkey, parseInt(obj.time.format('x')), obj.new_value )
          this.client.zadd( [zkey, obj.time.format('x'), val]  )
          //#   redis.zadd("zset", 32.0, "member")
      }
      else if ( obj instanceof Quotation) {
        //console.log("store Quotation %s,%s, %s", zkey,  obj.new_value, obj.time.format('x') );
        //this.client.hmset( hmkey, parseInt(obj.time.format('x')), obj.new_value )
        // it has to be uniq, add Timestamp as suffix
        if( obj.delta >0 )
        {
          this.client.zadd( [zkey, obj.time.format('x'), val] )
        }
      }
      this.set_expire( obj);
      this.client.publish( obj.symbol, val );
    }

    this.build_zkey = function( symbol, now){
      var key = "Z_"+ symbol +"_"+  now.startOf('week').startOf('day').format('x');
      return key;
    }
}

module.exports = ExchangeRedisStore;

ExchangeRedisStore.prototype.constructor = ExchangeRedisStore; // make stacktraces readable
<|endoftext|>


<commit_before>;(function(exports) {
  var Isla, _, codeAnalyzer, nodeDescriber, expressionDescriber, mapper;
  if(typeof module !== 'undefined' && module.exports) { // node
    Isla = require('../node_modules/isla/src/isla.js').Isla;
    _ = require("Underscore");
    codeAnalyzer = require('../src/code-analyzer').codeAnalyzer;
    nodeDescriber = require('../src/node-describer').nodeDescriber;
    expressionDescriber = require('../src/expression-describer')
                          .expressionDescriber;
    mapper = require('../src/mapper').mapper;
  } else { // browser
    Isla = window.Isla;
    _ = window._;
    codeAnalyzer = window.codeAnalyzer;
    nodeDescriber = window.nodeDescriber;
    expressionDescriber = window.expressionDescriber;
    mapper = window.mapper;
  }

  var id = "textHelper";

  var TextHelper = function(terminal, consoleIndicator, envStore) {
    this.write = function(e) {
      if (e.event === "mousemove") {
        handleHelp(terminal, e.point, envStore);
      } else if (e.event === "mouseout") {
        clearHelp();
      }
    };

    var indicate = function(event, data) {
      consoleIndicator.write({ event:event, data:data, id:id });
    };

    var clearHelp = function() {
      $('#help').text("");
      indicate("clear", {});
    };

    var handleHelp = function(terminal, point, envStore) {
      var text = terminal.getText();
      if (isThereHelpForToken(terminal, point, envStore)) {
        var index = mapper.getIndex(terminal, text, point);
        clearHelp();
        indicate("indicate", { thing:"token", index: index });
        displayHelp(getTokenHelp(text, index, envStore));
        return;
      } else if (isOverLine(terminal, point)) {
        var lineNumber = mapper.getLineNumber(terminal, text, point);
        var line = text.split("\n")[lineNumber];
        if (line.length > 0) {
          clearHelp();
          indicate("indicate", { thing:"line", lineNumber: lineNumber });
          displayHelp(getLineHelp(line, envStore));
          return;
        }
      }

      clearHelp();
    };
  };

  var isThereHelpForToken = function(terminal, point, envStore) {
    var text = terminal.getText();
    var index = mapper.getIndex(terminal, text, point);
    return index !== undefined &&
      codeAnalyzer.getSyntaxTokenIndex(text, index) !== undefined &&
      getTokenHelp(text, index, envStore) !== undefined;
  };

  var isOverLine = function(terminal, point) {
    var text = terminal.getText();
    return mapper.getLineNumber(terminal, text, point) !== undefined &&
      (mapper.getIndex(terminal, text, point) === undefined ||
       codeAnalyzer.expressionTokens(text) === undefined);
  };

  var getTokenHelp = function(text, index, envStore) {
    return nodeDescriber.describe(text, index, envStore.latest());
  };

  var getLineHelp = function(line, envStore) {
    var node = codeAnalyzer.expression(line);
    return expressionDescriber.describe(node, envStore.latest());
  };

  var displayHelp = function(help) {
    $('#help').text(help.body);
  };

  exports.TextHelper = TextHelper;
})(typeof exports === 'undefined' ? this : exports)
<commit_msg>Do not say there is help if line empty.<commit_after>;(function(exports) {
  var Isla, _, codeAnalyzer, nodeDescriber, expressionDescriber, mapper;
  if(typeof module !== 'undefined' && module.exports) { // node
    Isla = require('../node_modules/isla/src/isla.js').Isla;
    _ = require("Underscore");
    codeAnalyzer = require('../src/code-analyzer').codeAnalyzer;
    nodeDescriber = require('../src/node-describer').nodeDescriber;
    expressionDescriber = require('../src/expression-describer')
                          .expressionDescriber;
    mapper = require('../src/mapper').mapper;
  } else { // browser
    Isla = window.Isla;
    _ = window._;
    codeAnalyzer = window.codeAnalyzer;
    nodeDescriber = window.nodeDescriber;
    expressionDescriber = window.expressionDescriber;
    mapper = window.mapper;
  }

  var id = "textHelper";

  var TextHelper = function(terminal, consoleIndicator, envStore) {
    this.write = function(e) {
      if (e.event === "mousemove") {
        handleHelp(terminal, e.point, envStore);
      } else if (e.event === "mouseout") {
        clearHelp();
      }
    };

    var indicate = function(event, data) {
      consoleIndicator.write({ event:event, data:data, id:id });
    };

    var clearHelp = function() {
      $('#help').text("");
      indicate("clear", {});
    };

    var handleHelp = function(terminal, point, envStore) {
      var text = terminal.getText();
      if (isThereHelpForToken(terminal, point, envStore)) {
        var index = mapper.getIndex(terminal, text, point);
        clearHelp();
        indicate("indicate", { thing:"token", index: index });
        displayHelp(getTokenHelp(text, index, envStore));
        return;
      } else if (isOverLine(terminal, point)) {
        var lineNumber = mapper.getLineNumber(terminal, text, point);
        var line = text.split("\n")[lineNumber];
        if (line.length > 0) {
          clearHelp();
          indicate("indicate", { thing:"line", lineNumber: lineNumber });
          displayHelp(getLineHelp(line, envStore));
          return;
        }
      }

      clearHelp();
    };
  };

  var isThereHelpForToken = function(terminal, point, envStore) {
    var text = terminal.getText();
    var index = mapper.getIndex(terminal, text, point);
    return index !== undefined &&
      codeAnalyzer.getSyntaxTokenIndex(text, index) !== undefined &&
      getTokenHelp(text, index, envStore) !== undefined;
  };

  var isOverLine = function(terminal, point) {
    var text = terminal.getText();
    var line = mapper.getLine(terminal, text, point);
    return mapper.getLineNumber(terminal, text, point) !== undefined &&
      codeAnalyzer.expression(line) !== undefined &&
      (mapper.getIndex(terminal, text, point) === undefined);
  };

  var getTokenHelp = function(text, index, envStore) {
    return nodeDescriber.describe(text, index, envStore.latest());
  };

  var getLineHelp = function(line, envStore) {
    var node = codeAnalyzer.expression(line);
    return expressionDescriber.describe(node, envStore.latest());
  };

  var displayHelp = function(help) {
    $('#help').text(help.body);
  };

  exports.TextHelper = TextHelper;
})(typeof exports === 'undefined' ? this : exports)
<|endoftext|>


<commit_before>//Circle
(function(){
    let canvas = document.querySelector('#canvas'),
        context = canvas.getContext('2d'),
        width = canvas.width = window.innerWidth,
        height = canvas.height = window.innerHeight;

    let centerX = width / 2,
        centerY = height / 2,
        radius = 100,
        angle = 0.1;

    const SPEED = 0.05;

    function render() {
        let x = centerX + radius * Math.cos(angle);
        let y = centerY + radius * Math.sin(angle);

        context.clearRect(0, 0, width, height);
        context.beginPath();
        context.arc(centerX, centerY, 2, 0, Math.PI * 2, false);
        context.arc(x, y, 10, 0, Math.PI * 2, false);
        context.fill();

        angle += SPEED;

        requestAnimationFrame(render);

    }

    render();

})();<commit_msg>lesson 4 circle path<commit_after>//Circle
(function(){
    let canvas = document.querySelector('#canvas'),
        context = canvas.getContext('2d'),
        width = canvas.width = window.innerWidth,
        height = canvas.height = window.innerHeight;

    let centerX = width / 2,
        centerY = height / 2,
        radius = 200,
        angle = 0,
        numberOfSteps = 30,
        sliceSize = (Math.PI * 2) / numberOfSteps,
        x, y;

    const SPEED = 0.05;

    function render() {

        context.clearRect(0, 0, width, height);

        for (var slice = 0 ; slice < (Math.PI * 2); slice += sliceSize) {
            x = centerX + radius * Math.cos(slice);
            y = centerY + radius * Math.sin(slice);
            context.beginPath();
            context.arc(x, y, 10, 0, Math.PI * 2, false);
            context.fill();
        }

        x = centerX + radius * Math.cos(angle);
        y = centerY + radius * Math.sin(angle);
        context.beginPath();
        context.arc(centerX, centerY, 2, 0, Math.PI * 2, false);
        context.arc(x, y, 10, 0, Math.PI * 2, false);
        context.fill();

        angle += SPEED;

        requestAnimationFrame(render);

    }

    render();

})();<|endoftext|>