diff --git a/client/key/vim/find.js b/client/key/vim/find.js index a8573df7..a5f415d0 100644 --- a/client/key/vim/find.js +++ b/client/key/vim/find.js @@ -1,22 +1,18 @@ '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); +module.exports.find = (value, names) => { const result = limier(value, names); searchStore(result); searchIndex(0); - DOM.setCurrentByName(result[0]); + return result; }; module.exports.findNext = () => { @@ -24,7 +20,7 @@ module.exports.findNext = () => { const index = next(searchIndex(), names.length); searchIndex(index); - DOM.setCurrentByName(names[searchIndex()]); + return names[searchIndex()]; }; module.exports.findPrevious = () => { @@ -32,7 +28,7 @@ module.exports.findPrevious = () => { const index = previous(searchIndex(), names.length); searchIndex(index); - DOM.setCurrentByName(names[index]); + return names[index]; }; module.exports._next = next; diff --git a/client/key/vim/find.spec.js b/client/key/vim/find.spec.js index 63b7d7bf..6dbe073d 100644 --- a/client/key/vim/find.spec.js +++ b/client/key/vim/find.spec.js @@ -1,7 +1,6 @@ 'use strict'; const test = require('supertape'); -const stub = require('@cloudcmd/stub'); const dir = './'; const {getDOM} = require('./globals.fixture'); @@ -9,48 +8,10 @@ const {getDOM} = require('./globals.fixture'); global.DOM = getDOM(); const { - find, - findNext, - findPrevious, _next, _previous, } = require(dir + 'find'); -test('cloudcmd: client: vim: find', (t) => { - const {DOM} = global; - const setCurrentByName = stub(); - - DOM.setCurrentByName = setCurrentByName; - DOM.Dialog.prompt = Promise.resolve.bind(Promise); - - find(''); - - t.ok(setCurrentByName.calledWith(undefined), 'should call setCurrentByName'); - t.end(); -}); - -test('cloudcmd: client: vim: findNext', (t) => { - const {DOM} = global; - const setCurrentByName = stub(); - DOM.setCurrentByName = setCurrentByName; - - findNext(); - - t.ok(setCurrentByName.calledWith(undefined), 'should call setCurrentByName'); - t.end(); -}); - -test('cloudcmd: client: vim: findPrevious', (t) => { - const {DOM} = global; - const setCurrentByName = stub(); - - DOM.setCurrentByName = setCurrentByName; - findPrevious(); - - t.ok(setCurrentByName.calledWith(undefined), 'should call setCurrentByName'); - t.end(); -}); - test('cloudcmd: client: vim: _next', (t) => { const result = _next(1, 2); diff --git a/client/key/vim/index.js b/client/key/vim/index.js index 65751d23..7658add0 100644 --- a/client/key/vim/index.js +++ b/client/key/vim/index.js @@ -1,175 +1,66 @@ 'use strict'; -/* global CloudCmd, DOM */ +/* global CloudCmd */ +/* global DOM */ + +const vim = require('./vim'); +const finder = require('./find'); -const KEY = require('../key'); const Info = DOM.CurrentInfo; - const {Dialog} = DOM; -const fullstore = require('fullstore'); -const store = fullstore(''); -const visual = fullstore(false); -const { - find, - findNext, - findPrevious, -} = require('./find'); - -const stopVisual = () => { - visual(false); -}; - -const end = () => { - store(''); -}; - -const rmFirst = (a) => { - return a - .split('') - .slice(1) - .join(''); -}; - module.exports = async (key, event) => { - const current = Info.element; - const {keyCode} = event; - const prevStore = store(); - - const value = store(prevStore.concat(key)); - - if (keyCode === KEY.ENTER) - return end(); - - if (keyCode === KEY.ESC) { - DOM.unselectFiles(); - visual(false); - return end(); - } - - if (key === 'j') { - move('next', { - prevStore, - current, - }); - - return end(); - } - - if (key === 'k') { - move('previous', { - prevStore, - current, - }); - - return end(); - } - - if (/gg/.test(value)) { - move('previous', { - current, - prevStore, - max: Infinity, - }); - - return end(); - } - - if (key === 'd' && (visual() || prevStore === 'd')) { - CloudCmd.Operation.show('delete'); - stopVisual(); - return end(); - } - - if (key === 'G') { - move('next', { - current, - prevStore, - max: Infinity, - }); - - return end(); - } - - if (key === 'y') { - if (!visual()) - return end(); - - DOM.Buffer.copy(); - stopVisual(); - DOM.unselectFiles(); - return end(); - } - - if (/^p$/i.test(key)) { - DOM.Buffer.paste(); - return end(); - } - - if (/^v$/i.test(key)) { - DOM.toggleSelectedFile(current); - visual(!visual()); - - return end(); - } - - if (key === '/') { - event.preventDefault(); - - const [, value] = await Dialog.prompt('Find', ''); - - value && find(value); - - return end(); - } - - if (key === 'n') { - findNext(); - return end(); - } - - if (key === 'N') { - findPrevious(); - return end(); - } + const operations = getOperations(event); + await vim(key, operations); +}; + +const getOperations = (event) => { + return { + escape: DOM.unselectFiles, + remove: () => { + CloudCmd.Operation.show('delete'); + }, + copy: () => { + DOM.Buffer.copy(); + DOM.unselectFiles(); + }, + select: () => { + const current = Info.element; + DOM.toggleSelectedFile(current); + }, + paste: DOM.Buffer.paste, + move: (sibling, {count, visual, isDelete}) => { + setCurrent(sibling, { + count, + visual, + isDelete, + }); + }, + find: async () => { + event.preventDefault(); + const [, value] = await Dialog.prompt('Find', ''); + + if (!value) + return; + + const names = Info.files.map(DOM.getCurrentName); + const [result] = finder.find(value, names); + + DOM.setCurrentByName(result); + }, + findNext: () => { + const name = finder.findNext(); + DOM.setCurrentByName(name); + }, + findPrevious: () => { + const name = finder.findPrevious(); + DOM.setCurrentByName(name); + }, + }; }; module.exports.selectFile = selectFile; -function move(sibling, {max, current, prevStore}) { - const isDelete = prevStore[0] === 'd'; - - if (isDelete) { - visual(true); - prevStore = rmFirst(prevStore); - } - - const n = max || getNumber(prevStore); - - if (isNaN(n)) - return; - - setCurrent({ - n, - current, - sibling, - visual: visual(), - }); - - if (isDelete) - CloudCmd.Operation.show('delete'); -} - -function getNumber(value) { - if (!value) - return 1; - - if (value === 'g') - return 1; - - return parseInt(value); -} - function selectFile(current) { const name = DOM.getCurrentName(current); @@ -179,13 +70,14 @@ function selectFile(current) { DOM.selectFile(current); } -function setCurrent({n, current, visual, sibling}) { - const select = visual ? selectFile : DOM.unselectFile; +function setCurrent(sibling, {count, visual, isDelete}) { + let current = Info.element; + const select = visual() ? selectFile : DOM.unselectFile; select(current); const position = `${sibling}Sibling`; - for (let i = 0; i < n; i++) { + for (let i = 0; i < count; i++) { const next = current[position]; if (!next) @@ -196,5 +88,8 @@ function setCurrent({n, current, visual, sibling}) { } DOM.setCurrentFile(current); + + if (isDelete) + CloudCmd.Operation.show('delete'); } diff --git a/client/key/vim/index.spec.js b/client/key/vim/index.spec.js index abfa3f0f..e7048ca2 100644 --- a/client/key/vim/index.spec.js +++ b/client/key/vim/index.spec.js @@ -5,11 +5,10 @@ const {join} = require('path'); const test = require('supertape'); const stub = require('@cloudcmd/stub'); const mockRequire = require('mock-require'); -const {reRequire} = mockRequire; +const {reRequire, stopAll} = mockRequire; const dir = '../'; -const pathKey = join(dir, 'key'); const pathVim = join(dir, 'vim'); const pathFind = join(dir, 'vim', 'find'); @@ -24,7 +23,6 @@ global.CloudCmd = getCloudCmd(); const {DOM} = global; const {Buffer} = DOM; -const KEY = require(pathKey); const vim = require(pathVim); test('cloudcmd: client: key: set next file: no', (t) => { @@ -147,7 +145,6 @@ test('cloudcmd: client: key: select +2 files from current before delete', (t) => vim('j', event); t.ok(setCurrentFile.calledWith(last), 'should set next file'); - t.end(); }); @@ -194,7 +191,6 @@ test('cloudcmd: client: key: set previous file current', (t) => { vim('k', {}); t.ok(setCurrentFile.calledWith(previousSibling), 'should set previous file'); - t.end(); }); @@ -343,12 +339,9 @@ test('cloudcmd: client: key: ESC', (t) => { global.DOM.CurrentInfo.element = element; global.DOM.unselectFiles = unselectFiles ; - vim('', { - keyCode: KEY.ESC, - }); + vim('Escape'); t.ok(unselectFiles.calledWith(), 'should toggle selection'); - t.end(); }); @@ -363,14 +356,11 @@ test('cloudcmd: client: key: Enter', (t) => { DOM.CurrentInfo.element = element; DOM.setCurrentFile = setCurrentFile; - vim('', { - keyCode: KEY.ENTER, - }); + vim('Enter'); - vim('j', {}); + vim('j'); t.ok(setCurrentFile.calledWith(nextSibling), 'should set next file'); - t.end(); }); @@ -401,7 +391,7 @@ test('cloudcmd: client: key: n', (t) => { vim('n', event); - mockRequire.stop(pathFind); + stopAll(pathFind); t.ok(findNext.calledWith(), 'should call findNext'); t.end(); @@ -419,7 +409,7 @@ test('cloudcmd: client: key: N', (t) => { vim('N', event); - mockRequire.stop(pathFind); + stopAll(pathFind); t.ok(findPrevious.calledWith(), 'should call findPrevious'); t.end(); diff --git a/client/key/vim/vim.js b/client/key/vim/vim.js new file mode 100644 index 00000000..38d3e22d --- /dev/null +++ b/client/key/vim/vim.js @@ -0,0 +1,157 @@ +'use strict'; + +const fullstore = require('fullstore'); +const store = fullstore(''); +const visual = fullstore(false); + +const stopVisual = () => { + visual(false); +}; + +const end = () => { + store(''); +}; + +const rmFirst = (a) => { + return a + .split('') + .slice(1) + .join(''); +}; + +const noop = () => {}; + +module.exports = (key, operations) => { + const prevStore = store(); + const value = store(prevStore.concat(key)); + const { + escape = noop, + move = noop, + remove = noop, + copy = noop, + paste = noop, + select = noop, + find = noop, + findNext = noop, + findPrevious = noop, + } = operations; + + if (key === 'Enter') + return end(); + + if (key === 'Escape') { + visual(false); + escape(); + return end(); + } + + if (key === 'j') { + const [prev, isDelete] = handleDelete(prevStore); + const count = getNumber(prev); + + !isNaN(count) && move('next', { + isDelete, + count, + visual, + }); + + return end(); + } + + if (key === 'k') { + const [prev, isDelete] = handleDelete(prevStore); + const count = getNumber(prev); + + !isNaN(count) && move('previous', { + count, + visual, + isDelete, + }); + + return end(); + } + + if (/^gg$/.test(value)) { + const [, isDelete] = handleDelete(prevStore); + + move('previous', { + count: Infinity, + visual, + isDelete, + }); + + return end(); + } + + if (key === 'd' && (visual() || prevStore === 'd')) { + stopVisual(); + remove(); + return end(); + } + + if (key === 'G') { + move('next', { + count: Infinity, + visual, + }); + + return end(); + } + + if (key === 'y') { + if (!visual()) + return end(); + + stopVisual(); + copy(); + return end(); + } + + if (/^p$/i.test(key)) { + paste(); + return end(); + } + + if (/^v$/i.test(key)) { + visual(!visual()); + select(); + return end(); + } + + if (key === '/') { + find(); + return end(); + } + + if (key === 'n') { + findNext(); + return end(); + } + + if (key === 'N') { + findPrevious(); + return end(); + } +}; + +function handleDelete(prevStore) { + const isDelete = prevStore[0] === 'd'; + + if (isDelete) { + visual(true); + prevStore = rmFirst(prevStore); + } + + return [prevStore, isDelete]; +} + +function getNumber(value) { + if (!value) + return 1; + + if (value === 'g') + return 1; + + return parseInt(value); +} + diff --git a/client/key/vim/vim.spec.js b/client/key/vim/vim.spec.js new file mode 100644 index 00000000..5355807d --- /dev/null +++ b/client/key/vim/vim.spec.js @@ -0,0 +1,12 @@ +'use strict'; + +const test = require('supertape'); +const vim = require('./vim'); + +test('vim: no operations', (t) => { + const result = vim('hello', {}); + + t.notOk(result); + t.end(); +}); +