mirror of
https://github.com/coderaiser/cloudcmd.git
synced 2026-01-23 02:35:49 +00:00
feature(key) vim: split DOM and vim
This commit is contained in:
parent
7648aee85f
commit
87555ce7ba
6 changed files with 238 additions and 227 deletions
|
|
@ -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;
|
||||
|
|
|
|||
|
|
@ -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);
|
||||
|
||||
|
|
|
|||
|
|
@ -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');
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -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();
|
||||
|
|
|
|||
157
client/key/vim/vim.js
Normal file
157
client/key/vim/vim.js
Normal file
|
|
@ -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);
|
||||
}
|
||||
|
||||
12
client/key/vim/vim.spec.js
Normal file
12
client/key/vim/vim.spec.js
Normal file
|
|
@ -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();
|
||||
});
|
||||
|
||||
Loading…
Add table
Add a link
Reference in a new issue