From a27e4ecf61d9958cce87db417bfc84b3ed6b53e0 Mon Sep 17 00:00:00 2001 From: coderaiser Date: Sun, 29 Jan 2023 17:57:15 +0200 Subject: [PATCH] feature: client: vim: add ability to show terminal with 'tt' --- HELP.md | 1 + client/key/vim/index.js | 50 ++++---- client/key/vim/index.spec.js | 241 +++++++++++++++++++---------------- client/key/vim/vim.js | 70 +++++----- 4 files changed, 196 insertions(+), 166 deletions(-) diff --git a/HELP.md b/HELP.md index 0925eaf3..743743d0 100644 --- a/HELP.md +++ b/HELP.md @@ -222,6 +222,7 @@ When the `--vim` option is provided, or the configuration parameter `vim` is set | `N` | navigate to previous found file | `md` | make directory | `mf` | make file +| `tt` | show terminal Commands can be joined, for example: diff --git a/client/key/vim/index.js b/client/key/vim/index.js index 78998f42..71a48800 100644 --- a/client/key/vim/index.js +++ b/client/key/vim/index.js @@ -16,35 +16,39 @@ module.exports = async (key, event) => { const getOperations = (event) => ({ escape: DOM.unselectFiles, - + remove: () => { CloudCmd.Operation.show('delete'); }, - + makeDirectory: () => { event.stopImmediatePropagation(); event.preventDefault(); DOM.promptNewDir(); }, - + makeFile: () => { event.stopImmediatePropagation(); event.preventDefault(); DOM.promptNewFile(); }, - + + terminal: () => { + CloudCmd.Terminal.show(); + }, + copy: () => { DOM.Buffer.copy(); DOM.unselectFiles(); }, - + select: () => { const current = Info.element; DOM.toggleSelectedFile(current); }, - + paste: DOM.Buffer.paste, - + moveNext: ({count, isVisual, isDelete}) => { setCurrent('next', { count, @@ -52,7 +56,7 @@ const getOperations = (event) => ({ isDelete, }); }, - + movePrevious: ({count, isVisual, isDelete}) => { setCurrent('previous', { count, @@ -60,25 +64,25 @@ const getOperations = (event) => ({ 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); @@ -89,33 +93,33 @@ module.exports.selectFile = selectFile; function selectFile(current) { const name = DOM.getCurrentName(current); - + if (name === '..') return; - + DOM.selectFile(current); } function setCurrent(sibling, {count, isVisual, isDelete}) { let current = Info.element; const select = isVisual ? selectFile : DOM.unselectFile; - + select(current); - + const position = `${sibling}Sibling`; - + for (let i = 0; i < count; i++) { const next = current[position]; - + if (!next) break; - + current = next; select(current); } - + 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 cb8e3d7d..da48cb8f 100644 --- a/client/key/vim/index.spec.js +++ b/client/key/vim/index.spec.js @@ -23,7 +23,10 @@ global.CloudCmd = getCloudCmd(); const vim = require(pathVim); const {assign} = Object; -const {DOM} = global; +const { + DOM, + CloudCmd, +} = global; const {Buffer} = DOM; const pathFind = join(dir, 'vim', 'find'); @@ -31,14 +34,14 @@ const {reRequire, stopAll} = mockRequire; test('cloudcmd: client: key: set next file: no', (t) => { const element = {}; - + const setCurrentFile = stub(); - + global.DOM.CurrentInfo.element = element; global.DOM.setCurrentFile = setCurrentFile; - + vim('j', {}); - + t.calledWith(setCurrentFile, [element], 'should set next file'); t.end(); }); @@ -48,14 +51,14 @@ test('cloudcmd: client: key: set next file current: j', (t) => { const element = { nextSibling, }; - + const setCurrentFile = stub(); - + global.DOM.CurrentInfo.element = element; global.DOM.setCurrentFile = setCurrentFile; - + vim('j', {}); - + t.calledWith(setCurrentFile, [nextSibling], 'should set next file'); t.end(); }); @@ -65,16 +68,16 @@ test('cloudcmd: client: key: set next file current: mjj', (t) => { const element = { nextSibling, }; - + const setCurrentFile = stub(); - + global.DOM.CurrentInfo.element = element; global.DOM.setCurrentFile = setCurrentFile; - + vim('m', {}); vim('j', {}); vim('j', {}); - + t.calledWith(setCurrentFile, [nextSibling], 'should set next file'); t.end(); }); @@ -84,15 +87,15 @@ test('cloudcmd: client: key: set next file current: g', (t) => { const element = { nextSibling, }; - + const setCurrentFile = stub(); - + global.DOM.CurrentInfo.element = element; global.DOM.setCurrentFile = setCurrentFile; - + vim('g', {}); vim('j', {}); - + t.calledWith(setCurrentFile, [nextSibling], 'should ignore g'); t.end(); }); @@ -105,17 +108,17 @@ test('cloudcmd: client: key: set +2 file current', (t) => { const element = { nextSibling, }; - + const setCurrentFile = stub(); - + global.DOM.CurrentInfo.element = element; global.DOM.setCurrentFile = setCurrentFile; - + const event = {}; - + vim('2', event); vim('j', event); - + t.calledWith(setCurrentFile, [last], 'should set next file'); t.end(); }); @@ -128,21 +131,21 @@ test('cloudcmd: client: key: select +2 files from current before delete', (t) => const element = { nextSibling, }; - + const setCurrentFile = stub(); - + global.DOM.CurrentInfo.element = element; global.DOM.setCurrentFile = setCurrentFile; global.DOM.selectFile = stub(); global.DOM.getCurrentName = () => false; global.CloudCmd.Operation.show = stub(); - + const event = {}; - + vim('d', event); vim('2', event); vim('j', event); - + t.calledWith(setCurrentFile, [last], 'should set next file'); t.end(); }); @@ -155,22 +158,22 @@ test('cloudcmd: client: key: delete +2 files from current', (t) => { const element = { nextSibling, }; - + const setCurrentFile = stub(); const show = stub(); - + global.DOM.CurrentInfo.element = element; global.DOM.setCurrentFile = setCurrentFile; global.DOM.selectFile = stub(); global.DOM.getCurrentName = () => false; global.CloudCmd.Operation.show = show; - + const event = {}; - + vim('d', event); vim('2', event); vim('j', event); - + t.calledWith(show, ['delete'], 'should call delete'); t.end(); }); @@ -180,87 +183,87 @@ test('cloudcmd: client: key: set previous file current', (t) => { const element = { previousSibling, }; - + const setCurrentFile = stub(); - + global.DOM.CurrentInfo.element = element; global.DOM.setCurrentFile = setCurrentFile; - + vim('k', {}); - + t.calledWith(setCurrentFile, [previousSibling], 'should set previous file'); t.end(); }); test('cloudcmd: client: key: copy: no', (t) => { const copy = stub(); - + Buffer.copy = copy; - + vim('y', {}); - + t.notOk(copy.called, 'should not copy files'); t.end(); }); test('cloudcmd: client: key: copy', (t) => { const copy = stub(); - + Buffer.copy = copy; - + vim('v', {}); vim('y', {}); - + t.ok(copy.calledWith(), 'should copy files'); t.end(); }); test('cloudcmd: client: key: copy: unselectFiles', (t) => { const unselectFiles = stub(); - + DOM.unselectFiles = unselectFiles; - + vim('v', {}); vim('y', {}); - + t.ok(unselectFiles.calledWith(), 'should unselect files'); t.end(); }); test('cloudcmd: client: key: paste', (t) => { const paste = stub(); - + Buffer.paste = paste; - + vim('p', {}); - + t.ok(paste.calledWith(), 'should paste files'); t.end(); }); test('cloudcmd: client: key: selectFile: ..', (t) => { const getCurrentName = stub(); - + DOM.selectFile = stub(); DOM.getCurrentName = () => '..'; - + const current = {}; vim.selectFile(current); - + t.notOk(getCurrentName.called, 'should not call selectFile'); t.end(); }); test('cloudcmd: client: key: selectFile', (t) => { const selectFile = stub(); - + DOM.selectFile = selectFile; DOM.getCurrentName = (a) => a.name; - + const current = {}; - + vim.selectFile(current); - + t.calledWith(selectFile, [current], 'should call selectFile'); t.end(); }); @@ -273,14 +276,14 @@ test('cloudcmd: client: key: set last file current: shift + g', (t) => { const element = { nextSibling, }; - + const setCurrentFile = stub(); - + global.DOM.CurrentInfo.element = element; global.DOM.setCurrentFile = setCurrentFile; - + vim('G', {}); - + t.calledWith(setCurrentFile, [last], 'should set last file'); t.end(); }); @@ -293,14 +296,14 @@ test('cloudcmd: client: key: set last file current: $', (t) => { const element = { nextSibling, }; - + const setCurrentFile = stub(); - + global.DOM.CurrentInfo.element = element; global.DOM.setCurrentFile = setCurrentFile; - + vim('$', {}); - + t.calledWith(setCurrentFile, [last], 'should set last file'); t.end(); }); @@ -310,19 +313,19 @@ test('cloudcmd: client: key: set first file current: gg', (t) => { const previousSibling = { previousSibling: first, }; - + const element = { previousSibling, }; - + const setCurrentFile = stub(); - + global.DOM.CurrentInfo.element = element; global.DOM.setCurrentFile = setCurrentFile; - + vim('g', {}); vim('g', {}); - + t.calledWith(setCurrentFile, [first], 'should set first file'); t.end(); }); @@ -332,46 +335,46 @@ test('cloudcmd: client: key: set first file current: ^', (t) => { const previousSibling = { previousSibling: first, }; - + const element = { previousSibling, }; - + const setCurrentFile = stub(); - + global.DOM.CurrentInfo.element = element; global.DOM.setCurrentFile = setCurrentFile; - + vim('^', {}); - + t.calledWith(setCurrentFile, [first], 'should set first file'); t.end(); }); test('cloudcmd: client: key: visual', (t) => { const element = {}; - + const toggleSelectedFile = stub(); - + global.DOM.CurrentInfo.element = element; global.DOM.toggleSelectedFile = toggleSelectedFile; - + vim('v', {}); - + t.calledWith(toggleSelectedFile, [element], 'should toggle selection'); t.end(); }); test('cloudcmd: client: key: ESC', (t) => { const element = {}; - + const unselectFiles = stub(); - + global.DOM.CurrentInfo.element = element; global.DOM.unselectFiles = unselectFiles ; - + vim('Escape'); - + t.ok(unselectFiles.calledWith(), 'should toggle selection'); t.end(); }); @@ -381,16 +384,16 @@ test('cloudcmd: client: key: Enter', (t) => { const element = { nextSibling, }; - + const setCurrentFile = stub(); - + DOM.CurrentInfo.element = element; DOM.setCurrentFile = setCurrentFile; - + vim('Enter'); - + vim('j'); - + t.calledWith(setCurrentFile, [nextSibling], 'should set next file'); t.end(); }); @@ -398,14 +401,14 @@ test('cloudcmd: client: key: Enter', (t) => { test('cloudcmd: client: key: /', (t) => { const preventDefault = stub(); const element = {}; - + DOM.CurrentInfo.element = element; DOM.getCurrentName = () => ''; - + vim('/', { preventDefault, }); - + t.calledWithNoArgs(preventDefault, 'should call preventDefault'); t.end(); }); @@ -414,95 +417,111 @@ test('cloudcmd: client: find', (t) => { assign(DOM.Dialog, { prompt: stub().returns([]), }); - + const setCurrentByName = stub(); - + assign(DOM, { setCurrentByName, }); - + const vim = reRequire(pathVim); const event = { preventDefault: stub(), }; - + vim('/', event); - + stopAll(); - + t.notCalled(setCurrentByName); t.end(); }); test('cloudcmd: client: key: n', (t) => { const findNext = stub(); - + mockRequire(pathFind, { findNext, }); - + const vim = reRequire(pathVim); const event = {}; - + vim('n', event); - + stopAll(); - + t.ok(findNext.calledWith(), 'should call findNext'); t.end(); }); test('cloudcmd: client: key: N', (t) => { const findPrevious = stub(); - + mockRequire(pathFind, { findPrevious, }); - + const vim = reRequire(dir + 'vim'); const event = {}; - + vim('N', event); - + stopAll(); - + t.ok(findPrevious.calledWith(), 'should call findPrevious'); t.end(); }); test('cloudcmd: client: key: make directory', (t) => { const vim = reRequire(pathVim); - + assign(DOM, { promptNewDir: stub(), }); - + const event = { stopImmediatePropagation: stub(), preventDefault: stub(), }; vim('m', event); vim('d', event); - + t.calledWithNoArgs(DOM.promptNewDir); t.end(); }); test('cloudcmd: client: key: make file', (t) => { const vim = reRequire(pathVim); - + assign(DOM, { promptNewFile: stub(), }); - + const event = { stopImmediatePropagation: stub(), preventDefault: stub(), }; vim('m', event); vim('f', event); - + t.calledWithNoArgs(DOM.promptNewDir); t.end(); }); +test('cloudcmd: client: vim: terminal', (t) => { + assign(CloudCmd, { + Terminal: { + show: stub(), + }, + }); + + const event = { + }; + vim('t', event); + vim('t', event); + + t.calledWithNoArgs(CloudCmd.Terminal.show); + t.end(); +}); + diff --git a/client/key/vim/vim.js b/client/key/vim/vim.js index 9f60b76f..f49fd614 100644 --- a/client/key/vim/vim.js +++ b/client/key/vim/vim.js @@ -38,123 +38,129 @@ module.exports = (key, operations) => { findPrevious = noop, makeFile = noop, makeDirectory = noop, + terminal = noop, } = operations; - + if (key === 'Enter') return end(); - + if (key === 'Escape') { visual(false); escape(); - + return end(); } - + if (key === 'j' || key === 'w') { const { count, isDelete, isVisual, } = handleDelete(prevStore); - + !isNaN(count) && moveNext({ count, isVisual, isDelete, }); - + return end(); } - + if (key === 'k' || key === 'b') { const { count, isDelete, isVisual, } = handleDelete(prevStore); - + !isNaN(count) && movePrevious({ count, isVisual, isDelete, }); - + return end(); } - + if (value === 'gg' || key === '^') { const { isDelete, isVisual, } = handleDelete(prevStore); - + movePrevious({ count: Infinity, isVisual, isDelete, }); - + return end(); } - + if (value === 'md') { makeDirectory(); return end(); } - + + if (value === 'tt') { + terminal(); + return end(); + } + if (value === 'mf') { makeFile(); return end(); } - + if (key === 'd' && (visual() || prevStore === 'd')) { stopVisual(); remove(); - + return end(); } - + if (key === 'G' || key === '$') { moveNext({ count: Infinity, isVisual, }); - + 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(); @@ -163,15 +169,15 @@ module.exports = (key, operations) => { function handleDelete(prevStore) { const isDelete = prevStore[0] === 'd'; - + if (isDelete) { visual(true); prevStore = rmFirst(prevStore); } - + const count = getNumber(prevStore); const isVisual = visual(); - + return { count, isDelete, @@ -182,10 +188,10 @@ function handleDelete(prevStore) { function getNumber(value) { if (!value) return 1; - + if (value === 'g') return 1; - + return parseInt(value); }