<commit_before>var _ = require('lazy.js');
var blessed = require('blessed');
var path = require('path');
var Promise = require('bluebird');
var fs = Promise.promisifyAll(require('fs'));

var Editor = require('./editor');
var Field = require('./field');
var Coordinate = require('./coordinate');
var markup = require('./markup');
var util = require('./util');
var textUtil = require('./textUtil');

function Slap (opts) {
  var self = this;

  if (!(self instanceof blessed.Node)) { return new Slap(opts); }

  blessed.Screen.call(self, opts);

  self.header = new blessed.Box(_({
    tags: true,
    top: 0,
    left: 0,
    right: 0,
    height: 1
  }).merge(opts.header || {}).merge({parent: self}).toObject());

  self.editor = new Editor(_({
    parent: self,
    top: 1,
    left: 0,
    right: 0,
    bottom: 0
  }).merge(opts.editor || {}).toObject());
  self.editor.focus();

  self._initHandlers().render();
}
Slap.prototype.__proto__ = blessed.Screen.prototype;

Slap.normalizePath = function (givenPath) {
  if (!givenPath) { givenPath = ''; }
  if (givenPath[0] === '~') { givenPath = path.join(process.env.HOME, givenPath.slice(1)); }
  return path.normalize(givenPath);
};
Slap.prototype.path = util.getterSetter('path', null, Slap.normalizePath);
Slap.prototype.open = function (givenPath) {
  var self = this;
  givenPath = Slap.normalizePath(givenPath);
  return fs.readFileAsync(givenPath).then(function (data) {
    self.editor.text(data, givenPath.split('.').pop());
    return self.path(givenPath);
  });
};
Slap.prototype.save = function (givenPath) {
  var self = this;
  givenPath = givenPath ? Slap.normalizePath(givenPath) : self.path();
  if (!givenPath) { return; }

  var text = self.editor.text();
  return fs.writeFileAsync(givenPath, text, {flags: 'w'})
    .then(function () {
      self.editor.changeStack.save();
      self.emit('save', givenPath, text);
      return self.path(givenPath).message("saved to " + givenPath, 'success');
    })
    .catch(function (err) {
      switch (err.code) {
        case 'EACCES': case 'EISDIR':
          self.message(err.message, 'error');
          break;
        default: throw err;
      }
    });
};
Slap.prototype.saveAs = function () {
  var self = this;
  return self.prompt("save as").then(function (results) {
    if (results) { return self.save(results[0]); }
  });
};

Slap.prototype.prompt = function (prompts) {
  var self = this;

  return new Promise(function (resolve, reject) {
    prompts = typeof prompts !== 'string' ? prompts : [prompts];

    self.closePrompt();
    self.editor.bottom += prompts.length;
    self.form = new blessed.Form({
      parent: self,
      height: prompts.length,
      left: 0,
      right: 0,
      bottom: 0
    });

    var labelWidth = _(prompts).pluck('length').max() + 3;
    var label = new blessed.Box({
      parent: self.form,
      tags: true,
      content: prompts.map(function (prompt) {
        return markup.markupLine(
          (' ' + prompt + ':' + _.repeat(' ', labelWidth).join('')).slice(0, labelWidth),
        self.options.style.prompt || self.options.style.main);
      }).join('\n'),
      top: 0,
      left: 0,
      width: labelWidth,
      bottom: 0
    });

    function done (submit) {
      self.closePrompt(true);

      // FIXME: nasty hack to stop Enter key event propagating to the wrong element
      self.lockKeys = true;
      process.nextTick(function () {
        self.lockKeys = false;
        resolve(submit ? _(fields).invoke('text').toArray() : null);
      });
    }

    var fields = prompts.map(function (prompt, i) {
      var field = new Field(_(self.options.editor || {}).merge({
        parent: self.form,
        top: i,
        height: 1,
        left: labelWidth,
        right: 0
      }).merge(self.options.field || {}).toObject());
      if (!i) { field.focus(); }
      field.on('submit', done.bind(null, true));
      field.on('cancel', done.bind(null, false));
      return field;
    });

    self.render();
  });
};

Slap.prototype.closePrompt = function (focus) {
  var self = this;

  if (self.form) {
    self.remove(self.form);
    self.form = null;
    if (focus) { self.editor.focus(); }
    self.editor.bottom = 0;
    self.render();
  }

  return self;
};

Slap.prototype.exit = function () {
  process.exit(0);

  return this; // Just in case
};

Slap.prototype.message = util.getterSetter('message', null, function (message, styleName) {
  var self = this;

  clearTimeout(self.data.messageTimer);
  self.data.messageTimer = setTimeout(function () {
    self.message(null);
  }, self.options.messageDuration);

  return message !== null ? markup.markupLine(' '+message+' ', self.options.style[styleName]) : null;
});

Slap._regExpRegExp = /^\/(.+)\/$/;
Slap.prototype._initHandlers = function () {
  var self = this;

  var style = self.options.style;
  self.on('element keypress', function (el, ch, key) {
    self.message(null);
    switch (util.getBinding(self.options.bindings, key)) {
      case 'quit':
        if (self.editor.changeStack.dirty()) {
          self.prompt("save unsaved changes to (leave empty to quit)")
            .then(function (results) {
              if (results && results[0]) { return self.save(results[0]); }
            })
            .done(function () { self.exit(); });
        } else {
          self.exit();
        }
        break;
      case 'find':
        self.prompt("find (/.*/ for regex)").done(function (results) {
          if (results) {
            pattern = results[0];
            var regExpMatch = pattern.match(Slap._regExpRegExp);
            pattern = regExpMatch
              ? new RegExp(regExpMatch[1])
              : new RegExp(textUtil.escapeRegExp(pattern));

            var startPos = self.editor.cursor();
            startPos.x += 1;
            var pos = textUtil.find(self.editor.lines(), pattern, startPos);

            if (pos) {
              self.editor.select(null, pos);<commit_msg>Doesn't hide message on mouse move<commit_after>var _ = require('lazy.js');
var blessed = require('blessed');
var path = require('path');
var Promise = require('bluebird');
var fs = Promise.promisifyAll(require('fs'));

var Editor = require('./editor');
var Field = require('./field');
var Coordinate = require('./coordinate');
var markup = require('./markup');
var util = require('./util');
var textUtil = require('./textUtil');

function Slap (opts) {
  var self = this;

  if (!(self instanceof blessed.Node)) { return new Slap(opts); }

  blessed.Screen.call(self, opts);

  self.header = new blessed.Box(_({
    tags: true,
    top: 0,
    left: 0,
    right: 0,
    height: 1
  }).merge(opts.header || {}).merge({parent: self}).toObject());

  self.editor = new Editor(_({
    parent: self,
    top: 1,
    left: 0,
    right: 0,
    bottom: 0
  }).merge(opts.editor || {}).toObject());
  self.editor.focus();

  self._initHandlers().render();
}
Slap.prototype.__proto__ = blessed.Screen.prototype;

Slap.normalizePath = function (givenPath) {
  if (!givenPath) { givenPath = ''; }
  if (givenPath[0] === '~') { givenPath = path.join(process.env.HOME, givenPath.slice(1)); }
  return path.normalize(givenPath);
};
Slap.prototype.path = util.getterSetter('path', null, Slap.normalizePath);
Slap.prototype.open = function (givenPath) {
  var self = this;
  givenPath = Slap.normalizePath(givenPath);
  return fs.readFileAsync(givenPath).then(function (data) {
    self.editor.text(data, givenPath.split('.').pop());
    return self.path(givenPath);
  });
};
Slap.prototype.save = function (givenPath) {
  var self = this;
  givenPath = givenPath ? Slap.normalizePath(givenPath) : self.path();
  if (!givenPath) { return; }

  var text = self.editor.text();
  return fs.writeFileAsync(givenPath, text, {flags: 'w'})
    .then(function () {
      self.editor.changeStack.save();
      self.emit('save', givenPath, text);
      return self.path(givenPath).message("saved to " + givenPath, 'success');
    })
    .catch(function (err) {
      switch (err.code) {
        case 'EACCES': case 'EISDIR':
          self.message(err.message, 'error');
          break;
        default: throw err;
      }
    });
};
Slap.prototype.saveAs = function () {
  var self = this;
  return self.prompt("save as").then(function (results) {
    if (results) { return self.save(results[0]); }
  });
};

Slap.prototype.prompt = function (prompts) {
  var self = this;

  return new Promise(function (resolve, reject) {
    prompts = typeof prompts !== 'string' ? prompts : [prompts];

    self.closePrompt();
    self.editor.bottom += prompts.length;
    self.form = new blessed.Form({
      parent: self,
      height: prompts.length,
      left: 0,
      right: 0,
      bottom: 0
    });

    var labelWidth = _(prompts).pluck('length').max() + 3;
    var label = new blessed.Box({
      parent: self.form,
      tags: true,
      content: prompts.map(function (prompt) {
        return markup.markupLine(
          (' ' + prompt + ':' + _.repeat(' ', labelWidth).join('')).slice(0, labelWidth),
        self.options.style.prompt || self.options.style.main);
      }).join('\n'),
      top: 0,
      left: 0,
      width: labelWidth,
      bottom: 0
    });

    function done (submit) {
      self.closePrompt(true);

      // FIXME: nasty hack to stop Enter key event propagating to the wrong element
      self.lockKeys = true;
      process.nextTick(function () {
        self.lockKeys = false;
        resolve(submit ? _(fields).invoke('text').toArray() : null);
      });
    }

    var fields = prompts.map(function (prompt, i) {
      var field = new Field(_(self.options.editor || {}).merge({
        parent: self.form,
        top: i,
        height: 1,
        left: labelWidth,
        right: 0
      }).merge(self.options.field || {}).toObject());
      if (!i) { field.focus(); }
      field.on('submit', done.bind(null, true));
      field.on('cancel', done.bind(null, false));
      return field;
    });

    self.render();
  });
};

Slap.prototype.closePrompt = function (focus) {
  var self = this;

  if (self.form) {
    self.remove(self.form);
    self.form = null;
    if (focus) { self.editor.focus(); }
    self.editor.bottom = 0;
    self.render();
  }

  return self;
};

Slap.prototype.exit = function () {
  process.exit(0);

  return this; // Just in case
};

Slap.prototype.message = util.getterSetter('message', null, function (message, styleName) {
  var self = this;

  clearTimeout(self.data.messageTimer);
  self.data.messageTimer = setTimeout(function () {
    self.message(null);
  }, self.options.messageDuration);

  return message !== null ? markup.markupLine(' '+message+' ', self.options.style[styleName]) : null;
});

Slap._regExpRegExp = /^\/(.+)\/$/;
Slap.prototype._initHandlers = function () {
  var self = this;

  var style = self.options.style;
  self.on('element keypress', function (el, ch, key) {
    if (key.name !== 'mouse') { self.message(null); }
    switch (util.getBinding(self.options.bindings, key)) {
      case 'quit':
        if (self.editor.changeStack.dirty()) {
          self.prompt("save unsaved changes to (leave empty to quit)")
            .then(function (results) {
              if (results && results[0]) { return self.save(results[0]); }
            })
            .done(function () { self.exit(); });
        } else {
          self.exit();
        }
        break;
      case 'find':
        self.prompt("find (/.*/ for regex)").done(function (results) {
          if (results) {
            pattern = results[0];
            var regExpMatch = pattern.match(Slap._regExpRegExp);
            pattern = regExpMatch
              ? new RegExp(regExpMatch[1])
              : new RegExp(textUtil.escapeRegExp(pattern));

            var startPos = self.editor.cursor();
            startPos.x += 1;
            var pos = textUtil.find(self.editor.lines(), pattern, startPos);

            if (pos) {
              self.editor.select(null, pos);
{'fraction_non_alphanumeric': 0.16765615854168409, 'fraction_numerical': 0.004682665774730328, 'mean_word_length': 2.7108284207260316, 'pattern_counts': {'":': 0, '<': 3, '<?xml version=': 0, '>': 3, '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': '1911790', 'n_tokens_mistral': 3868, 'n_tokens_neox': 3730, 'n_words': 847}