import { Neovim } from '@chemzqm/neovim' import path from 'path' import { CancellationToken, Disposable } from 'vscode-languageserver-protocol' import BasicList from '../../list/basic' import manager from '../../list/manager' import { IList, ListContext, ListItem, QuickfixItem } from '../../types' import { disposeAll } from '../../util/index' import window from '../../window' import helper from '../helper' class TestList extends BasicList { public name = 'test' public timeout = 3000 public text = 'test' public detail = 'detail' public loadItems(_context: ListContext, token: CancellationToken): Promise { return new Promise(resolve => { let timer = setTimeout(() => { resolve([{ label: this.text }]) }, this.timeout) token.onCancellationRequested(() => { if (timer) { clearTimeout(timer) resolve([]) } }) }) } } let nvim: Neovim let disposables: Disposable[] = [] const locations: ReadonlyArray = [{ filename: __filename, col: 2, lnum: 1, text: 'foo' }, { filename: __filename, col: 1, lnum: 2, text: 'Bar' }, { filename: __filename, col: 1, lnum: 3, text: 'option' }] const lineList: IList = { name: 'lines', actions: [{ name: 'open', execute: async item => { await window.moveTo({ line: (item as ListItem).data.line, character: 0 }) // noop } }], defaultAction: 'open', async loadItems(_context, _token): Promise { let lines = [] for (let i = 0; i < 100; i++) { lines.push(i.toString()) } return lines.map((line, idx) => ({ label: line, data: { line: idx } })) } } beforeAll(async () => { await helper.setup() nvim = helper.nvim await nvim.setVar('coc_jump_locations', locations) }) afterAll(async () => { disposeAll(disposables) await helper.shutdown() }) afterEach(async () => { manager.reset() await helper.reset() }) describe('isValidAction()', () => { it('should check invalid action', async () => { let mappings = manager.mappings expect(mappings.isValidAction('foo')).toBe(false) expect(mappings.isValidAction('do:switch')).toBe(true) expect(mappings.isValidAction('eval:@*')).toBe(true) expect(mappings.isValidAction('undefined:undefined')).toBe(false) }) }) describe('User mappings', () => { it('should show warning for invalid key', async () => { let revert = helper.updateConfiguration('list.insertMappings', { xy: 'action:tabe', }) await helper.wait(30) let msg = await helper.getCmdline() revert() await nvim.command('echo ""') expect(msg).toMatch('Invalid configuration') revert = helper.updateConfiguration('list.insertMappings', { '': 'action:tabe', }) await helper.wait(30) msg = await helper.getCmdline() revert() expect(msg).toMatch('Invalid configuration') revert = helper.updateConfiguration('list.insertMappings', { '': 'foo:bar', }) await helper.wait(30) msg = await helper.getCmdline() revert() expect(msg).toMatch('Invalid configuration') }) it('should execute action keymap', async () => { let revert = helper.updateConfiguration('list.insertMappings', { '': 'action:quickfix', }) await manager.start(['location']) await manager.session.ui.ready await helper.listInput('') let buftype = await nvim.eval('&buftype') expect(buftype).toBe('quickfix') revert() }) it('should execute expr keymap', async () => { await helper.mockFunction('TabOpen', 'quickfix') helper.updateConfiguration('list.insertMappings', { '': 'expr:TabOpen', }) helper.updateConfiguration('list.normalMappings', { t: 'expr:TabOpen', }) await manager.start(['location']) await manager.session.ui.ready await helper.listInput('') let buftype = await nvim.eval('&buftype') expect(buftype).toBe('quickfix') await nvim.command('close') await manager.start(['--normal', 'location']) await manager.session.ui.ready await helper.listInput('t') buftype = await nvim.eval('&buftype') expect(buftype).toBe('quickfix') }) it('should execute do mappings', async () => { helper.updateConfiguration('list.previousKeymap', '') helper.updateConfiguration('list.nextKeymap', '') helper.updateConfiguration('list.insertMappings', { '': 'do:next', '': 'do:previous', '': 'do:exit', }) await manager.start(['location']) await manager.session.ui.ready await helper.listInput('') let item = await manager.session?.ui.item expect(item.label).toMatch(locations[1].text) await helper.listInput('') item = await manager.session?.ui.item expect(item.label).toMatch(locations[0].text) await helper.listInput('') item = await manager.session?.ui.item expect(item.label).toMatch(locations[1].text) await helper.listInput('') item = await manager.session?.ui.item expect(item.label).toMatch(locations[0].text) await helper.listInput('') expect(manager.isActivated).toBe(false) }) it('should execute prompt mappings', async () => { helper.updateConfiguration('list.insertMappings', { '': 'prompt:previous', '': 'prompt:next', '': 'prompt:start', '': 'prompt:end', '': 'prompt:left', '': 'prompt:right', '': 'prompt:deleteforward', '': 'prompt:deletebackward', '': 'prompt:removetail', '': 'prompt:removeahead', }) await manager.start(['location']) await manager.session.ui.ready for (let key of ['', '', '', '', '', '', '', '', '', '']) { await helper.listInput(key) } expect(manager.isActivated).toBe(true) }) it('should execute feedkeys keymap', async () => { helper.updateConfiguration('list.insertMappings', { '': 'feedkeys:\\', }) await manager.start(['location']) await manager.session.ui.ready await helper.listInput('') let line = await nvim.call('line', '.') expect(line).toBe(locations.length) }) it('should execute normal keymap', async () => { helper.updateConfiguration('list.insertMappings', { '': 'normal:G', }) await manager.start(['location']) await manager.session.ui.ready await helper.listInput('') let line = await nvim.call('line', '.') expect(line).toBe(locations.length) }) it('should execute command keymap', async () => { helper.updateConfiguration('list.insertMappings', { '': 'command:wincmd p', }) await manager.start(['location']) await manager.session.ui.ready await helper.listInput('') expect(manager.isActivated).toBe(true) let winnr = await nvim.call('winnr') expect(winnr).toBe(1) }) it('should execute call keymap', async () => { await helper.mockFunction('Test', 1) helper.updateConfiguration('list.insertMappings', { '': 'call:Test', }) await manager.start(['location']) await manager.session.ui.ready await helper.listInput('') expect(manager.isActivated).toBe(true) }) it('should insert clipboard register to prompt', async () => { helper.updateConfiguration('list.insertMappings', { '': 'prompt:paste', }) await nvim.command('let @* = "foobar"') await manager.start(['location']) await manager.session.ui.ready await helper.listInput('') let { input } = manager.prompt expect(input).toMatch('foobar') await nvim.command('let @* = ""') await helper.listInput('') expect(manager.prompt.input).toMatch('foobar') }) it('should insert text from default register to prompt', async () => { helper.updateConfiguration('list.insertMappings', { '': 'eval:@@', }) await nvim.command('let @@ = "bar"') await manager.start(['location']) await manager.session.ui.ready await helper.listInput('') let { input } = manager.prompt expect(input).toMatch('bar') }) }) describe('doAction()', () => { it('should throw when action not found', async () => { let mappings = manager.mappings let fn = async () => { await mappings.doAction('foo:bar') } await expect(fn()).rejects.toThrow(/doesn't exist/) }) it('should not throw when session does not exist', async () => { let mappings = manager.mappings await mappings.doAction('do:selectall') await mappings.doAction('do:help') await mappings.doAction('do:refresh') await mappings.doAction('do:toggle') await mappings.doAction('do:jumpback') await mappings.doAction('prompt:previous') await mappings.doAction('prompt:next') await mappings.doAction('do:refresh') }) it('should not throw when action name does not exist', async () => { await helper.mockFunction('MyExpr', '') let mappings = manager.mappings await mappings.doAction('expr', 'MyExpr') }) }) describe('getAction()', () => { it('should throw for invalid action', async () => { let mappings = manager.mappings let fn = () => { mappings.getAction('foo') } expect(fn).toThrow(Error) fn = () => { mappings.getAction('do:bar') } expect(fn).toThrow(Error) }) }) describe('Default normal mappings', () => { it('should invoke action', async () => { await manager.start(['--normal', '--no-quit', 'location']) await manager.session.ui.ready let winid = manager.session.ui.winid await helper.listInput('t') let nr = await nvim.call('tabpagenr') expect(nr).toBe(2) await nvim.call('win_gotoid', [winid]) await helper.listInput('s') let winnr = await nvim.call('winnr', ['$']) expect(winnr).toBe(3) await nvim.call('win_gotoid', [winid]) await helper.listInput('d') let filename = await nvim.call('expand', ['%']) expect(filename).toMatch(path.basename(__filename)) await nvim.call('win_gotoid', [winid]) await helper.listInput('') filename = await nvim.call('expand', ['%']) expect(filename).toMatch(path.basename(__filename)) }) it('should select all items by ', async () => { await manager.start(['--normal', 'location']) await manager.session.ui.ready await helper.listInput('') let selected = manager.session?.ui.selectedItems expect(selected.length).toBe(locations.length) }) it('should stop by ', async () => { await manager.start(['--normal', 'location']) await manager.session.ui.ready await helper.listInput('') let loading = manager.session?.worker.isLoading expect(loading).toBe(false) }) it('should jump back by ', async () => { let doc = await helper.createDocument() await manager.start(['--normal', 'location']) await manager.session.ui.ready await helper.listInput('') let bufnr = await nvim.call('bufnr', ['%']) expect(bufnr).toBe(doc.bufnr) }) it('should scroll preview window by , ', async () => { await helper.createDocument() await manager.start(['--auto-preview', '--normal', 'location']) await manager.session.ui.ready await helper.waitPreviewWindow() let winnr = await nvim.call('coc#list#has_preview') as number let winid = await nvim.call('win_getid', [winnr]) await helper.listInput('') let res = await nvim.call('getwininfo', [winid]) expect(res[0].topline).toBeGreaterThan(1) await helper.listInput('') res = await nvim.call('getwininfo', [winid]) expect(res[0].topline).toBeLessThan(7) }) it('should insert command by :', async () => { await manager.start(['--normal', 'location']) await manager.session.ui.ready await helper.listInput(':') await nvim.eval('feedkeys("let g:x = 1\\", "in")') let res = await nvim.getVar('x') expect(res).toBe(1) }) it('should select action by ', async () => { await manager.start(['--normal', 'location']) await manager.session.ui.ready let p = helper.listInput('') await helper.wait(50) await nvim.input('t') await p let nr = await nvim.call('tabpagenr') expect(nr).toBe(2) }) it('should preview by p', async () => { await manager.start(['--normal', 'location']) await manager.session.ui.ready await helper.listInput('p') let winnr = await nvim.call('coc#list#has_preview') expect(winnr).toBe(2) }) it('should stop task by ', async () => { disposables.push(manager.registerList(new TestList(nvim))) let p = manager.start(['--normal', 'test']) await helper.wait(50) await nvim.input('') await p let len = manager.session?.ui.length expect(len).toBe(0) }) it('should cancel list by ', async () => { await manager.start(['--normal', 'location']) await manager.session.ui.ready await nvim.eval('feedkeys("\\", "in")') await helper.waitValue(() => { return manager.isActivated }, false) }) it('should reload list by ', async () => { let list = new TestList(nvim) list.timeout = 0 disposables.push(manager.registerList(list)) await manager.start(['--normal', 'test']) await manager.session.ui.ready list.text = 'new' await helper.listInput('') await helper.wait(30) let line = await nvim.line expect(line).toMatch('new') }) it('should toggle selection ', async () => { await manager.start(['--normal', 'location']) await manager.session.ui.ready await helper.listInput(' ') let selected = manager.session?.ui.selectedItems expect(selected.length).toBe(1) await helper.listInput('k') await helper.listInput(' ') selected = manager.session?.ui.selectedItems expect(selected.length).toBe(0) }) it('should change to insert mode by i, o, a', async () => { await manager.start(['--normal', 'location']) await manager.session.ui.ready let keys = ['i', 'I', 'o', 'O', 'a', 'A'] for (let key of keys) { await helper.listInput(key) let mode = manager.prompt.mode expect(mode).toBe('insert') await helper.listInput('') mode = manager.prompt.mode expect(mode).toBe('normal') } }) it('should show help by ?', async () => { await manager.start(['--normal', 'location']) await manager.session.ui.ready await helper.listInput('?') let bufname = await nvim.call('bufname', '%') expect(bufname).toBe('[LIST HELP]') }) }) describe('list insert mappings', () => { it('should open by ', async () => { await manager.start(['location']) await manager.session.ui.ready await helper.listInput('') let bufname = await nvim.call('expand', ['%:p']) expect(bufname).toMatch('mappings.test.ts') }) it('should paste input by ', async () => { await nvim.command('let @* = "foo"') await nvim.command('let @@ = "foo"') await nvim.call('setreg', ['*', 'foo']) await manager.start(['location']) await manager.session.ui.ready await helper.listInput('') let input = manager.prompt.input expect(input).toBe('foo') }) it('should insert register content by ', async () => { await nvim.command('let @* = "foo"') await nvim.command('let @@ = "foo"') await nvim.call('setreg', ['*', 'foo']) await manager.start(['location']) await manager.session.ui.ready await helper.listInput('') await helper.listInput('*') let input = manager.prompt.input expect(input).toBe('foo') await helper.listInput('') await helper.listInput('<') input = manager.prompt.input expect(input).toBe('foo') manager.prompt.reset() }) it('should cancel by ', async () => { await manager.start(['location']) await manager.session.ui.ready await helper.listInput('') expect(manager.isActivated).toBe(false) }) it('should select action by insert ', async () => { await manager.start(['location']) await manager.session.ui.ready let p = helper.listInput('') await helper.wait(50) await nvim.input('d') await p let bufname = await nvim.call('bufname', ['%']) expect(bufname).toMatch(path.basename(__filename)) }) it('should select action for visual selected items', async () => { await manager.start(['--normal', 'location']) await manager.session.ui.ready await helper.wait(50) await nvim.input('V') await helper.wait(30) await nvim.input('2') await helper.wait(30) await nvim.input('j') await helper.wait(30) await manager.doAction('quickfix') let buftype = await nvim.eval('&buftype') expect(buftype).toBe('quickfix') }) it('should stop loading by ', async () => { await manager.start(['location']) await manager.session.ui.ready await helper.listInput('') expect(manager.isActivated).toBe(true) }) it('should reload by ', async () => { await manager.start(['location']) await manager.session.ui.ready await helper.listInput('') expect(manager.isActivated).toBe(true) }) it('should change to normal mode by ', async () => { await manager.start(['location']) await manager.session.ui.ready await helper.listInput('') expect(manager.isActivated).toBe(true) }) it('should select line by and ', async () => { await manager.start(['location']) await manager.session.ui.ready await nvim.eval('feedkeys("\\", "in")') await nvim.eval('feedkeys("\\", "in")') expect(manager.isActivated).toBe(true) let line = await nvim.line expect(line).toMatch('foo') }) it('should move cursor by and ', async () => { await manager.start(['location']) await manager.session.ui.ready await helper.listInput('f') await helper.listInput('') await helper.listInput('') await helper.listInput('a') await helper.listInput('') await helper.listInput('') await helper.listInput('c') let input = manager.prompt.input expect(input).toBe('afc') }) it('should move cursor by and ', async () => { await manager.start(['location']) await manager.session.ui.ready await helper.listInput('') await helper.listInput('') await helper.listInput('a') let input = manager.prompt.input expect(input).toBe('a') }) it('should move cursor by ', async () => { disposables.push(manager.registerList(lineList)) await manager.start(['lines']) await manager.session.ui.ready await helper.listInput('') await helper.listInput('') await helper.listInput('') }) it('should scroll window by and ', async () => { await manager.start(['location']) await manager.session.ui.ready await helper.listInput('') await helper.listInput('') }) it('should change input by ', async () => { await manager.start(['location']) await manager.session.ui.ready await helper.listInput('f') await helper.listInput('') let input = manager.prompt.input expect(input).toBe('') }) it('should change input by ', async () => { let revert = helper.updateConfiguration('list.insertMappings', { '': 'prompt:removetail', }) await manager.start(['location']) await manager.session.ui.ready await helper.listInput('f') await helper.listInput('o') await helper.listInput('o') await helper.listInput('') await helper.listInput('') expect(manager.mappings.hasUserMapping('insert', '')).toBe(true) let input = manager.prompt.input revert() expect(input).toBe('') }) it('should change input by ', async () => { await manager.start(['location']) await manager.session.ui.ready await helper.listInput('f') await helper.listInput('') let input = manager.prompt.input expect(input).toBe('') }) it('should change input by ', async () => { await manager.start(['location']) await manager.session.ui.ready await helper.listInput('f') await helper.listInput('a') await helper.listInput('') let input = manager.prompt.input expect(input).toBe('') }) it('should change input by ', async () => { await manager.start(['--input=a', 'location']) await manager.session.ui.ready await helper.listInput('') let input = manager.prompt.input expect(input).toBe('') }) it('should change input by and ', async () => { async function session(input: string): Promise { await manager.start(['location']) await manager.session.ui.ready for (let ch of input) { await helper.listInput(ch) } await manager.cancel() } await session('foo') await session('bar') await manager.start(['location']) await manager.session.ui.ready await helper.listInput('') let input = manager.prompt.input expect(input.length).toBeGreaterThan(0) await helper.listInput('') input = manager.prompt.input expect(input.length).toBeGreaterThan(0) }) it('should change matcher by ', async () => { await manager.start(['location']) await manager.session.ui.ready await helper.listInput('') let matcher = manager.session?.listOptions.matcher expect(matcher).toBe('strict') await helper.listInput('') matcher = manager.session?.listOptions.matcher expect(matcher).toBe('regex') await helper.listInput('f') let len = manager.session?.ui.length expect(len).toBeGreaterThan(0) }) }) describe('evalExpression', () => { it('should exit list', async () => { helper.updateConfiguration('list.normalMappings', { t: 'do:exit', }) await manager.start(['--normal', 'location']) await manager.session.ui.ready expect(manager.mappings.hasUserMapping('normal', 't')).toBe(true) await helper.listInput('t') expect(manager.isActivated).toBe(false) }) it('should cancel prompt', async () => { helper.updateConfiguration('list.normalMappings', { t: 'do:cancel', }) await manager.start(['--normal', 'location']) await manager.session.ui.ready await helper.listInput('t') let res = await nvim.call('coc#prompt#activated') expect(res).toBe(0) }) it('should invoke normal command', async () => { let revert = helper.updateConfiguration('list.normalMappings', { x: 'normal!:G' }) await manager.start(['--normal', 'location']) await manager.session.ui.ready await helper.listInput('x') revert() let lnum = await nvim.call('line', ['.']) expect(lnum).toBeGreaterThan(1) }) it('should toggle, scroll preview', async () => { let revert = helper.updateConfiguration('list.normalMappings', { '': 'do:toggle', a: 'do:toggle', b: 'do:previewtoggle', c: 'do:previewup', d: 'do:previewdown', e: 'prompt:insertregister', f: 'do:stop', g: 'do:togglemode', }) await manager.start(['--normal', 'location']) await manager.session.ui.ready await helper.listInput(' ') for (let key of ['a', 'b', 'c', 'd', 'e', 'f', 'g']) { await helper.listInput(key) } revert() expect(manager.isActivated).toBe(true) }) })