From 2edf7f83219a3e3632cc237e7b5ff1ea4f0caa25 Mon Sep 17 00:00:00 2001 From: coderaiser Date: Mon, 11 Sep 2017 17:12:24 +0300 Subject: [PATCH] feature(vim) add find support with: "/", "n" and "N" --- HELP.md | 3 + client/key/vim/find.js | 53 ++++++++++++++ client/key/{vim.js => vim/index.js} | 29 +++++++- package.json | 3 +- test/client/key/vim/find.js | 79 +++++++++++++++++++++ test/client/key/vim/globals.js | 43 ++++++++++++ test/client/key/{vim.js => vim/index.js} | 88 ++++++++++++++++-------- 7 files changed, 269 insertions(+), 29 deletions(-) create mode 100644 client/key/vim/find.js rename client/key/{vim.js => vim/index.js} (86%) create mode 100644 test/client/key/vim/find.js create mode 100644 test/client/key/vim/globals.js rename test/client/key/{vim.js => vim/index.js} (84%) diff --git a/HELP.md b/HELP.md index 8c01dd3c..330a38cc 100644 --- a/HELP.md +++ b/HELP.md @@ -191,6 +191,9 @@ When `--vim` option provided, or configuration parameter `vim` set, next hot key | `y` | copy (selected in visual mode files) | `p` | paste files | `Esc` | unselect all +| `/` | find file in current directory +| `n` | navigate to next found file +| `N` | navigate to previous found file Commands can be joined, for example: - `5j` will navigate `5` files below current; diff --git a/client/key/vim/find.js b/client/key/vim/find.js new file mode 100644 index 00000000..97d0bbda --- /dev/null +++ b/client/key/vim/find.js @@ -0,0 +1,53 @@ +'use strict'; +/* global DOM */ + +const fullstore = require('fullstore'); +const limier = require('limier'); +const Info = DOM.CurrentInfo; + +const searchStore = fullstore([]); +const searchIndex = fullstore(0); + +module.exports.find = (value) => { + const names = Info.files.map(DOM.getCurrentName); + const result = limier(value, names); + + searchStore(result); + searchIndex(0); + + DOM.setCurrentByName(result[0]); +}; + +module.exports.findNext = () => { + const names = searchStore(); + const index = next(searchIndex(), names.length); + + searchIndex(index); + DOM.setCurrentByName(names[searchIndex()]); +}; + +module.exports.findPrevious = () => { + const names = searchStore(); + const index = previous(searchIndex(), names.length); + + searchIndex(index); + DOM.setCurrentByName(names[index]); +}; + +module.exports._next = next; +module.exports._previous = previous; + +function next(index, length) { + if (index === length - 1) + return 0; + + return ++index; +} + +function previous(index, length) { + if (!index) + return length - 1; + + return --index; +} + diff --git a/client/key/vim.js b/client/key/vim/index.js similarity index 86% rename from client/key/vim.js rename to client/key/vim/index.js index f280c9c7..1c14d764 100644 --- a/client/key/vim.js +++ b/client/key/vim/index.js @@ -1,12 +1,20 @@ 'use strict'; /* global CloudCmd, DOM */ +const KEY = require('../key'); const Info = DOM.CurrentInfo; -const KEY = require('./key'); +const Dialog = DOM.Dialog; const fullstore = require('fullstore/legacy'); const store = fullstore(''); const visual = fullstore(false); +const { + find, + findNext, + findPrevious, +} = require('./find'); + +const TITLE = 'Cloud Commander'; const stopVisual = () => { visual(false); @@ -104,6 +112,25 @@ module.exports = (key, event) => { return end(); } + + if (key === '/') { + event.preventDefault(); + + Dialog.prompt(TITLE, 'Find', '', {cancel: false}) + .then(find); + + return end(); + } + + if (key === 'n') { + findNext(); + return end(); + } + + if (key === 'N') { + findPrevious(); + return end(); + } }; module.exports.selectFile = selectFile; diff --git a/package.json b/package.json index 00caace6..d8c52a89 100644 --- a/package.json +++ b/package.json @@ -202,7 +202,8 @@ "version-io": "^2.0.1", "webpack": "^3.0.0", "wraptile": "^1.0.0", - "yaspeller": "^4.0.0" + "yaspeller": "^4.0.0", + "limier": "^1.0.1" }, "engines": { "node": ">=4.0.0" diff --git a/test/client/key/vim/find.js b/test/client/key/vim/find.js new file mode 100644 index 00000000..5a86d980 --- /dev/null +++ b/test/client/key/vim/find.js @@ -0,0 +1,79 @@ +'use strict'; + +const test = require('tape'); +const diff = require('sinon-called-with-diff'); +const sinon = diff(require('sinon')); +const dir = '../../../../client/key/vim/'; + +const { + getDOM, + getCloudCmd, +} = require('./globals'); + +global.DOM = global.DOM || getDOM(); +global.CloudCmd = global.CloudCmd || getCloudCmd(); + +const DOM = global.DOM +const CloudCmd = global.CloudCmd; + +const { + find, + findNext, + findPrevious, + _next, + _previous, +} = require(dir + 'find'); + +test('cloudcmd: client: vim: find', (t) => { + const setCurrentByName = sinon.stub(); + DOM.setCurrentByName = setCurrentByName; + DOM.Dialog.prompt = Promise.resolve.bind(Promise); + + find(''); + + t.ok(setCurrentByName.calledWith(), 'should call setCurrentByName'); + t.end(); +}); + +test('cloudcmd: client: vim: findNext', (t) => { + const setCurrentByName = sinon.stub(); + DOM.setCurrentByName = setCurrentByName; + + findNext(); + + t.ok(setCurrentByName.calledWith(), 'should call setCurrentByName'); + t.end(); +}); + +test('cloudcmd: client: vim: findPrevious', (t) => { + const setCurrentByName = sinon.stub(); + DOM.setCurrentByName = setCurrentByName; + + findPrevious(); + + t.ok(setCurrentByName.calledWith(), 'should call setCurrentByName'); + t.end(); +}); + +test('cloudcmd: client: vim: _next', (t) => { + const result = _next(1, 2) + + t.notOk(result, 'should return 0'); + t.end(); +}); + +test('cloudcmd: client: vim: _previous', (t) => { + const result = _previous(0, 2) + + t.equal(result, 1, 'should return 1'); + t.end(); +}); + +function clean(path) { + delete require.cache[require.resolve(path)]; +} + +function stub(name, fn) { + require.cache[require.resolve(name)].exports = fn; +} + diff --git a/test/client/key/vim/globals.js b/test/client/key/vim/globals.js new file mode 100644 index 00000000..81994ecf --- /dev/null +++ b/test/client/key/vim/globals.js @@ -0,0 +1,43 @@ +'use strict'; + +module.exports.getDOM = () => { + const resolve = Promise.resolve.bind(Promise); + const CurrentInfo = { + element: {}, + files: [], + }; + + const noop = () => {}; + const Buffer = { + copy: noop, + paste: noop, + }; + + const Dialog = { + prompt: resolve + }; + + return { + Buffer, + CurrentInfo, + Dialog, + selectFile: noop, + unselectFile: noop, + unselectFiles: noop, + setCurrentFile: noop, + getCurrentName: noop, + setCurrentByName: noop, + toggleSelectedFile: noop, + }; +}; + +module.exports.getCloudCmd = () => { + const show = () => {}; + + return { + Operation: { + show + } + }; +}; + diff --git a/test/client/key/vim.js b/test/client/key/vim/index.js similarity index 84% rename from test/client/key/vim.js rename to test/client/key/vim/index.js index ceb8ff9b..2898b3dc 100644 --- a/test/client/key/vim.js +++ b/test/client/key/vim/index.js @@ -3,10 +3,16 @@ const test = require('tape'); const diff = require('sinon-called-with-diff'); const sinon = diff(require('sinon')); -const dir = '../../../client/key/'; +const dir = '../../../../client/key/'; const KEY = require(dir + 'key'); -initGlobals(); +const { + getDOM, + getCloudCmd, +} = require('./globals'); + +global.DOM = global.DOM || getDOM(); +global.CloudCmd = global.CloudCmd || getCloudCmd(); const DOM = global.DOM; const Buffer = DOM.Buffer; @@ -349,8 +355,8 @@ test('cloudcmd: client: key: Enter', (t) => { const setCurrentFile = sinon.stub(); - global.DOM.CurrentInfo.element = element; - global.DOM.setCurrentFile = setCurrentFile; + DOM.CurrentInfo.element = element; + DOM.setCurrentFile = setCurrentFile; vim('', { keyCode: KEY.ENTER @@ -363,32 +369,60 @@ test('cloudcmd: client: key: Enter', (t) => { t.end(); }); -function initGlobals() { - const CurrentInfo = { - element: {}, - }; +test('cloudcmd: client: key: /', (t) => { + const preventDefault = sinon.stub(); + const element = {}; - const noop = () => {}; - const Buffer = { - copy: noop, - }; + DOM.CurrentInfo.element = element; + DOM.getCurrentName = () => ''; - global.DOM = { - Buffer, - CurrentInfo, - selectFile: noop, - unselectFile: noop, - unselectFiles: noop, - setCurrentFile: noop, - toggleSelectedFile: noop, - }; + vim('/', { + preventDefault + }); - const show = () => {}; + t.ok(preventDefault.calledWith(), 'should call preventDefault'); + t.end(); +}); + +test('cloudcmd: client: key: n', (t) => { + const findNext = sinon.stub(); - global.CloudCmd = { - Operation: { - show - } - }; + clean(dir + 'vim'); + stub(dir + 'vim/find', { + findNext + }); + + const vim = require(dir + 'vim'); + const event = {}; + + vim('n', event); + + t.ok(findNext.calledWith(), 'should call findNext'); + t.end(); +}); + +test('cloudcmd: client: key: N', (t) => { + const findPrevious = sinon.stub(); + + clean(dir + 'vim'); + stub(dir + 'vim/find', { + findPrevious, + }); + + const vim = require(dir + 'vim'); + const event = {}; + + vim('N', event); + + t.ok(findPrevious.calledWith(), 'should call findPrevious'); + t.end(); +}); + +function clean(path) { + delete require.cache[require.resolve(path)]; +} + +function stub(name, fn) { + require.cache[require.resolve(name)].exports = fn; }