diff --git a/lib/ui/FindForm.js b/lib/ui/FindForm.js index de8e4c5..ba8c2e2 100644 --- a/lib/ui/FindForm.js +++ b/lib/ui/FindForm.js @@ -6,8 +6,12 @@ var util = require('slap-util'); var BaseWidget = require('base-widget'); var Slap = require('./Slap'); var BaseFindForm = require('./BaseFindForm'); +var Label = require('./Label'); +var Field = require('editor-widget').Field; +var Button = require('./Button'); +var Interpolator = require('regexp-capture-interpolation'); -FindForm._label = " find (/.*/ for regex): "; +FindForm._label = " Find (/.*/ for regex): "; FindForm._regExpRegExp = /^\/(.+)\/(\w*)$/i; FindForm._invalidRegExpMessageRegExp = /^(Invalid regular expression:|Invalid flags supplied to RegExp constructor)/; function FindForm (opts) { @@ -16,18 +20,40 @@ function FindForm (opts) { if (!(self instanceof FindForm)) return new FindForm(opts); BaseFindForm.call(self, _.merge({ + height: 2, findField: {left: FindForm._label.length} }, Slap.global.options.form.find, opts)); - self.findLabel = new BaseWidget(_.merge({ + self.replaceLabel = new Label(_.merge({ + parent: self, + tags: true, + content: ' Replace with: ', + top: 1, + height: 1, + left: 0 + }, self.options.findLabel)); + + self.replaceField = new Field(_.merge({ + parent: self, + top: 1, + left: 15, + right: 9 + }, Slap.global.options.editor, Slap.global.options.field, self.options.replaceField)); + + self.replaceButton = new Button(_.merge({ + parent: self, + content: "Replace", + top: 1, + right: 0 + }, Slap.global.options.button, self.options.replaceButton)); + + self.findLabel = new Label(_.merge({ parent: self, tags: true, content: FindForm._label, top: 0, - height: 1, left: 0, width: FindForm._label.length, - style: self.options.style }, self.options.findLabel)); } FindForm.prototype.__proto__ = BaseFindForm.prototype; @@ -51,7 +77,12 @@ FindForm.prototype._initHandlers = function () { editor.destroyMarkers({type: 'findMatch'}); editor._updateContent(); }); - self.on('find', lodash.throttle(function (pattern, direction) { + + var interpolateReplacement = function (match, replacement) { + return Interpolator.interpolate(match.concat([match.index,match.input]), replacement); + }; + + var findOrReplace = function (pattern, direction, replacement) { direction = direction || 0; editor.destroyMarkers({type: 'findMatch'}); try { @@ -85,6 +116,9 @@ FindForm.prototype._initHandlers = function () { var cmp = matchRange.start.compare(selectionRange.start); if (cmp === direction) { self.selectRange(matchRange); + if (replacement) { + editor.textBuf.setTextInRange(editor.selection.getRange(), interpolateReplacement(match.match, replacement)); + } return true; } else if (!cmp && matches.length === 1) { header.message("this is the only occurrence", 'info'); @@ -93,15 +127,36 @@ FindForm.prototype._initHandlers = function () { })) { header.message("search wrapped", 'info'); self.selectRange(matches[0].range); + if (replacement) { + editor.textBuf.setTextInRange(editor.selection.getRange(), interpolateReplacement(matches[0].match, replacement)); + } } editor._updateContent(); + }; + + self.on('find', lodash.throttle(findOrReplace, self.options.perf.findThrottle)); + + self.replaceButton.on('press', lodash.throttle( function () { + findOrReplace(self.findField.value(), 0, self.replaceField.value()); }, self.options.perf.findThrottle)); + self.replaceField.on('keypress', function (ch, key) { + var text = self.findField.value(); + switch (self.resolveBinding(key)) { + case 'next': self.find(text, 1); return false; + case 'prev': self.find(text, -1); return false; + case 'submit': findOrReplace(text, 1, self.replaceField.value()); return false; + case 'submitAlt': findOrReplace(text, -1, self.replaceField.value()); return false; + }; + }); + self.findField.on('keypress', function (ch, key) { - var text = self.findField.textBuf.getText(); + var text = self.findField.value(); switch (self.resolveBinding(key)) { case 'next': self.find(text, 1); return false; case 'prev': self.find(text, -1); return false; + case 'submit': self.find(text, 1); return false; + case 'submitAlt': self.find(text, -1); return false; }; }); diff --git a/lib/ui/GoLineForm.js b/lib/ui/GoLineForm.js index 0507656..d80b9c0 100644 --- a/lib/ui/GoLineForm.js +++ b/lib/ui/GoLineForm.js @@ -4,6 +4,7 @@ var BaseWidget = require('base-widget'); var Slap = require('./Slap'); var BaseFindForm = require('./BaseFindForm'); +var Label = require('./Label'); GoLineForm._label = " line number: "; function GoLineForm (opts) { @@ -15,15 +16,13 @@ function GoLineForm (opts) { findField: {left: GoLineForm._label.length} }, Slap.global.options.form.goLine, opts)); - self.goLineLabel = new BaseWidget(_.merge({ + self.goLineLabel = new Label(_.merge({ parent: self, tags: true, content: GoLineForm._label, top: 0, - height: 1, left: 0, width: GoLineForm._label.length, - style: self.options.style }, self.options.goLineLabel)); } GoLineForm.prototype.__proto__ = BaseFindForm.prototype; diff --git a/lib/ui/Label.js b/lib/ui/Label.js new file mode 100644 index 0000000..97c06c7 --- /dev/null +++ b/lib/ui/Label.js @@ -0,0 +1,24 @@ +var blessed = require('blessed'); +var _ = require('lodash'); + +var util = require('slap-util'); + +var BaseWidget = require('base-widget'); +var Slap = require('./Slap'); + +function Label (opts) { + var self = this; + + if (!(self instanceof Label)) return new Label(opts); + + opts = _.merge({ + height: 1 + }, Slap.global.options.label, opts); + + BaseWidget.blessed.Text.call(self, opts); + BaseWidget.call(self, opts); +} + +Label.prototype.__proto__ = BaseWidget.blessed.Text.prototype; + +module.exports = Label; diff --git a/lib/ui/SaveAsForm.js b/lib/ui/SaveAsForm.js index 401ac64..73ea364 100644 --- a/lib/ui/SaveAsForm.js +++ b/lib/ui/SaveAsForm.js @@ -4,6 +4,7 @@ var BaseWidget = require('base-widget'); var Field = require('editor-widget').Field; var Slap = require('./Slap'); var BaseForm = require('./BaseForm'); +var Label = require('./Label'); SaveAsForm._label = " save as: "; function SaveAsForm (opts) { @@ -15,15 +16,13 @@ function SaveAsForm (opts) { field: {left: SaveAsForm._label.length} }, Slap.global.options.form.saveAs, opts)); - self.saveAsLabel = new BaseWidget(_.merge({ + self.saveAsLabel = new Label(_.merge({ parent: self, tags: true, content: SaveAsForm._label, top: 0, - height: 1, left: 0, width: SaveAsForm._label.length, - style: self.options.style }, self.options.saveAsLabel)); self.pathField = new Field(_.merge({ diff --git a/package.json b/package.json index 37784b7..af9112e 100644 --- a/package.json +++ b/package.json @@ -29,6 +29,7 @@ "mkdirp": "0.5.1", "node-clap": "0.0.5", "rc": "1.1.6", + "regexp-capture-interpolation": "0.1.1", "slap-util": "1.0.7", "ttys": "0.0.3", "update-notifier": "1.0.2" diff --git a/slap.ini b/slap.ini index c5a5338..75cc8d9 100644 --- a/slap.ini +++ b/slap.ini @@ -48,13 +48,13 @@ goLine = "C-g" cancel = "escape" [form.baseFind.bindings] -next[] = "enter" +submit[] = "enter" next[] = "down" prev[] = "up" ; These don't work in most terminal emulators -prev[] = "S-enter" -prev[] = "M-enter" -prev[] = "C-enter" +submitAlt[] = "S-enter" +submitAlt[] = "M-enter" +submitAlt[] = "C-enter" [dialog.bindings] hide = "escape"