mirror of
1
0
Fork 0
ultimate-vim/sources_non_forked/coc.nvim/src/__tests__/completion/basic.test.ts

1155 lines
38 KiB
TypeScript

import { Neovim } from '@chemzqm/neovim'
import { CancellationToken, Disposable } from 'vscode-languageserver-protocol'
import completion from '../../completion'
import events from '../../events'
import sources from '../../sources'
import { CompleteOption, CompleteResult, ISource, SourceType } from '../../types'
import { disposeAll } from '../../util'
import workspace from '../../workspace'
import helper from '../helper'
let nvim: Neovim
let disposables: Disposable[] = []
beforeAll(async () => {
await helper.setup()
nvim = helper.nvim
})
afterAll(async () => {
await helper.shutdown()
})
afterEach(async () => {
disposeAll(disposables)
await helper.reset()
})
async function triggerCompletion(source: string): Promise<void> {
await nvim.call('coc#start', { source })
}
describe('completion', () => {
describe('preferences', () => {
describe('autoTrigger', () => {
it('should not trigger when autoTrigger is none', async () => {
helper.updateConfiguration('suggest.autoTrigger', 'none')
let doc = await workspace.document
await nvim.setLine('foo football')
await doc.synchronize()
await nvim.input('of')
await helper.wait(20)
expect(completion.isActivated).toBe(false)
})
})
describe('disableKind & disableMenu', () => {
it('should hide kind and menu when configured', async () => {
helper.updateConfiguration('suggest.disableKind', true)
helper.updateConfiguration('suggest.disableMenu', true)
let doc = await workspace.document
await nvim.setLine('fball football')
await doc.synchronize()
await nvim.input('of')
await helper.waitPopup()
let items = await helper.getItems()
expect(items[0].kind).toBeUndefined()
expect(items[0].menu).toBeUndefined()
})
})
describe('keepCompleteopt', () => {
it('should show error when keepCompleteopt unable to work', async () => {
let prev = workspace.env.completeOpt
workspace.env.completeOpt = 'menu,preview'
helper.updateConfiguration('suggest.keepCompleteopt', true)
expect(completion.config.keepCompleteopt).toBe(false)
let line = await helper.getCmdline()
expect(line).toMatch('disabled')
workspace.env.completeOpt = prev
})
})
describe('characters only', () => {
beforeEach(() => {
helper.updateConfiguration('suggest.asciiCharactersOnly', true)
})
it('should trigger with none ascii characters', async () => {
let doc = await workspace.document
await nvim.setLine('world')
await doc.synchronize()
await nvim.input('o')
await nvim.input('你')
await nvim.input('w')
let visible = await helper.visible('world', 'around')
expect(visible).toBe(true)
})
it('should not trigger with none ascii characters', async () => {
let doc = await workspace.document
await nvim.setLine('你好')
await doc.synchronize()
await nvim.input('o')
await nvim.input('你')
await helper.wait(50)
let visible = await helper.pumvisible()
expect(visible).toBe(false)
})
it('should consider none word character as input', async () => {
let doc = await helper.createDocument('t.vim')
let res = completion.getInput(doc, 'a#b#')
expect(res).toBe('a#b#')
})
})
describe('ignore by regex', () => {
it('should trigger with number input', async () => {
let doc = await workspace.document
await nvim.setLine('1357')
await doc.synchronize()
await nvim.input('o')
await nvim.input('1')
let visible = await helper.visible('1357', 'around')
expect(visible).toBe(true)
})
it('should not trigger with number input', async () => {
helper.updateConfiguration('suggest.ignoreRegexps', ['[0-9]+'])
let doc = await workspace.document
await nvim.setLine('1357')
await doc.synchronize()
await nvim.input('o')
await nvim.input('1')
let visible = await helper.pumvisible()
expect(visible).toBe(false)
})
})
describe('selection', () => {
it('should not select when selection is none', async () => {
helper.updateConfiguration('suggest.enablePreselect', true)
let doc = await workspace.document
await nvim.setLine('around')
await doc.synchronize()
await nvim.input('oa')
await helper.visible('around')
await nvim.call('nvim_select_popupmenu_item', [0, false, false, {}])
await nvim.input('<C-y>')
await nvim.input('<esc>')
await nvim.input('oa')
await helper.visible('around')
let context = await nvim.getVar('coc#_context') as any
expect(context.preselect).toBe(-1)
})
it('should select recent used item', async () => {
helper.updateConfiguration('suggest.selection', 'recentlyUsed')
helper.updateConfiguration('suggest.enablePreselect', true)
let doc = await workspace.document
await nvim.setLine('result')
await doc.synchronize()
await nvim.input('or')
await helper.visible('result')
await nvim.call('nvim_select_popupmenu_item', [0, false, false, {}])
await nvim.input('<C-y>')
await nvim.input('<esc>')
await nvim.input('or')
await helper.visible('result')
})
it('should select recent item by prefix', async () => {
helper.updateConfiguration('suggest.selection', 'recentlyUsedByPrefix')
helper.updateConfiguration('suggest.enablePreselect', true)
let doc = await workspace.document
await nvim.setLine('world')
await doc.synchronize()
await nvim.input('owo')
await helper.visible('world')
await nvim.call('nvim_select_popupmenu_item', [0, false, false, {}])
await nvim.input('<C-y>')
await nvim.input('<esc>')
await nvim.input('ow')
await helper.visible('world')
let context = await nvim.getVar('coc#_context') as any
expect(context.preselect).toBe(-1)
})
})
})
describe('doComplete()', () => {
it('should deactivate on doComplete error', async () => {
await helper.createDocument()
await nvim.command(`edit +setl\\ buftype=nofile`)
let option: CompleteOption = await nvim.call('coc#util#get_complete_option')
await completion.startCompletion(option)
expect(completion.isActivated).toBe(false)
})
it('should show slow source', async () => {
let source: ISource = {
priority: 0,
enable: true,
name: 'slow',
sourceType: SourceType.Service,
triggerCharacters: ['.'],
doComplete: (_opt: CompleteOption): Promise<CompleteResult> => new Promise(resolve => {
setTimeout(() => {
resolve({ items: [{ word: 'foo' }, { word: 'bar' }] })
}, 50)
})
}
disposables.push(sources.addSource(source))
await nvim.input('i.')
await helper.waitPopup()
expect(completion.isActivated).toBe(true)
let items = await helper.items()
expect(items.length).toBe(2)
})
it('should show items before slow source finished', async () => {
let source: ISource = {
name: 'fast',
enable: true,
doComplete: (_opt: CompleteOption): Promise<CompleteResult> => new Promise(resolve => {
resolve({ items: [{ word: 'foo' }, { word: 'bar' }] })
})
}
disposables.push(sources.addSource(source))
let finished = false
let slowSource: ISource = {
name: 'slow',
enable: true,
doComplete: (_opt: CompleteOption): Promise<CompleteResult> => new Promise(resolve => {
setTimeout(() => {
finished = true
resolve({ items: [{ word: 'world' }] })
}, 100)
})
}
disposables.push(sources.addSource(slowSource))
await nvim.input('if')
await helper.waitPopup()
expect(finished).toBe(false)
})
})
describe('resumeCompletion()', () => {
it('should stop if no filtered items', async () => {
await nvim.setLine('foo ')
await nvim.input('Af')
await helper.waitPopup()
expect(completion.isActivated).toBe(true)
await nvim.input('d')
await helper.waitValue(() => {
return completion.isActivated
}, false)
})
it('should resume with inserted characters', async () => {
let doc = await workspace.document
await nvim.setLine('foo fat')
await doc.synchronize()
await nvim.input('of')
await nvim.setLine('fo')
await doc.synchronize()
await nvim.call('cursor', [2, 3])
await helper.wait(50)
let items = await helper.getItems()
expect(items.length).toBeGreaterThan(0)
})
it('should stop with bad insert on CursorMovedI', async () => {
await nvim.setLine('foo fat')
await nvim.input('of')
await nvim.setLine('f a')
await nvim.call('cursor', [2, 4])
await helper.wait(30)
let visible = await helper.pumvisible()
expect(visible).toBe(false)
})
it('should deactivate without filtered items', async () => {
let doc = await workspace.document
await nvim.setLine('foo fbi ')
await doc.synchronize()
await nvim.input('Af')
await helper.waitPopup()
await nvim.input('c')
await helper.waitFor('pumvisible', [], 0)
let items = await helper.items()
expect(items.length).toBe(0)
expect(completion.isActivated).toBe(false)
})
it('should deactivate when insert space', async () => {
let source: ISource = {
priority: 0,
enable: true,
name: 'empty',
sourceType: SourceType.Service,
triggerCharacters: ['.'],
doComplete: (_opt: CompleteOption): Promise<CompleteResult> => new Promise(resolve => {
resolve({ items: [{ word: 'foo bar' }] })
})
}
disposables.push(sources.addSource(source))
await nvim.input('i.')
await helper.waitPopup()
expect(completion.isActivated).toBe(true)
let items = await helper.items()
expect(items[0].word).toBe('foo bar')
await nvim.input(' ')
await helper.waitFor('pumvisible', [], 0)
})
it('should use resume input to filter', async () => {
let source: ISource = {
priority: 0,
enable: true,
name: 'source',
sourceType: SourceType.Service,
triggerCharacters: ['.'],
doComplete: (): Promise<CompleteResult> => new Promise(resolve => {
setTimeout(() => {
resolve({ items: [{ word: 'foo' }, { word: 'bar' }] })
}, 60)
})
}
disposables.push(sources.addSource(source))
await nvim.input('i.')
await helper.wait(20)
await nvim.input('f')
await helper.waitPopup()
expect(completion.isActivated).toBe(true)
let items = await helper.items()
expect(items.length).toBe(1)
expect(items[0].word).toBe('foo')
})
it('should filter slow source', async () => {
let source: ISource = {
priority: 0,
enable: true,
name: 'slow',
sourceType: SourceType.Service,
triggerCharacters: ['.'],
doComplete: (): Promise<CompleteResult> => new Promise(resolve => {
setTimeout(() => {
resolve({ items: [{ word: 'foo' }, { word: 'bar' }] })
}, 100)
})
}
disposables.push(sources.addSource(source))
await nvim.input('i.f')
await helper.waitPopup()
await nvim.input('o')
await helper.waitFor('eval', ['len(coc#_context["candidates"])'], 1)
let items = await helper.items()
expect(items.length).toBe(1)
expect(items[0].word).toBe('foo')
})
it('should complete inComplete source', async () => {
let source: ISource = {
priority: 0,
enable: true,
name: 'inComplete',
sourceType: SourceType.Service,
triggerCharacters: ['.'],
doComplete: async (opt: CompleteOption): Promise<CompleteResult> => {
if (opt.input.length <= 1) {
return { isIncomplete: true, items: [{ word: 'foo' }, { word: opt.input }] }
}
await helper.wait(10)
return { isIncomplete: false, items: [{ word: 'foo' }, { word: opt.input }] }
}
}
disposables.push(sources.addSource(source))
await nvim.input('i.')
await helper.waitPopup()
expect(completion.isActivated).toBe(true)
await nvim.input('a')
await helper.wait(20)
await nvim.input('b')
})
it('should not complete inComplete source when isIncomplete is false', async () => {
let lastOption: CompleteOption
let source: ISource = {
priority: 0,
enable: true,
name: 'inComplete',
sourceType: SourceType.Service,
triggerCharacters: ['.'],
doComplete: async (opt: CompleteOption): Promise<CompleteResult> => {
lastOption = opt
await helper.wait(30)
if (opt.input.length <= 1) {
return { isIncomplete: true, items: [{ word: 'foobar' }] }
}
return { isIncomplete: false, items: [{ word: 'foobar' }] }
}
}
disposables.push(sources.addSource(source))
await nvim.input('i.')
await helper.waitPopup()
expect(completion.isActivated).toBe(true)
await nvim.input('fo')
await helper.wait(50)
await nvim.input('b')
await helper.wait(50)
expect(completion.isActivated).toBe(true)
})
it('should filter when item has selected with noselect', async () => {
helper.updateConfiguration('suggest.noselect', false)
let source: ISource = {
priority: 0,
enable: true,
name: 'filter',
sourceType: SourceType.Service,
doComplete: (): Promise<CompleteResult> => {
return Promise.resolve({ items: [{ word: 'foo' }, { word: 'fox' }, { word: 'fat' }] })
}
}
disposables.push(sources.addSource(source))
await nvim.input('if')
await helper.waitPopup()
await nvim.input('o')
await helper.waitFor('eval', ['len(coc#_context["candidates"])'], 2)
await nvim.input('o')
await helper.waitFor('eval', ['len(coc#_context["candidates"])'], 1)
})
it('should filter when type character after item selected without handle complete done', async () => {
let input: string
let fn = jest.fn()
let source: ISource = {
priority: 0,
enable: true,
name: 'filter',
sourceType: SourceType.Service,
doComplete: (opt): Promise<CompleteResult> => {
input = opt.input
if (input == 'f') return Promise.resolve({ items: [{ word: 'fo' }] })
if (input == 'foo') return Promise.resolve({ items: [{ word: 'foobar' }, { word: 'foot' }] })
return Promise.resolve({ items: [] })
},
onCompleteDone: () => {
fn()
}
}
disposables.push(sources.addSource(source))
await nvim.input('if')
await helper.waitPopup()
await nvim.input('<C-n>')
await helper.wait(20)
await nvim.input('o')
await helper.waitPopup()
expect(fn).toBeCalledTimes(0)
})
})
describe('TextChangedI', () => {
it('should respect commitCharacter on TextChangedI', async () => {
helper.updateConfiguration('suggest.acceptSuggestionOnCommitCharacter', true)
helper.updateConfiguration('suggest.noselect', false)
let source: ISource = {
enable: true,
name: 'commit',
sourceType: SourceType.Service,
triggerCharacters: ['.'],
doComplete: (opt: CompleteOption): Promise<CompleteResult> => {
if (opt.triggerCharacter == '.') {
return Promise.resolve({ items: [{ word: 'bar' }] })
}
return Promise.resolve({ items: [{ word: 'foo' }] })
},
shouldCommit: (_item, character) => character == '.'
}
disposables.push(sources.addSource(source))
await nvim.input('if')
await helper.waitPopup()
await nvim.input('.')
await helper.waitFor('getline', ['.'], 'foo.')
})
})
describe('TextChangedP', () => {
it('should stop when input length below option input length', async () => {
let doc = await workspace.document
await nvim.setLine('foo fbi ')
await doc.synchronize()
await nvim.input('Af')
await helper.waitPopup()
await nvim.input('<backspace>')
await helper.waitFor('getline', ['.'], 'foo fbi ')
expect(completion.isActivated).toBe(false)
})
it('should filter on none keyword input', async () => {
let source: ISource = {
priority: 99,
enable: true,
name: 'temp',
sourceType: SourceType.Service,
doComplete: (_opt: CompleteOption): Promise<CompleteResult> => Promise.resolve({ items: [{ word: 'foo#abc' }] }),
}
disposables.push(sources.addSource(source))
await nvim.input('if')
await helper.waitPopup()
await nvim.input('#')
await helper.wait(50)
let items = await helper.getItems()
expect(items[0].word).toBe('foo#abc')
})
it('should cancel on InsertLeave', async () => {
let source: ISource = {
priority: 99,
enable: true,
name: 'temp',
sourceType: SourceType.Service,
doComplete: (_opt: CompleteOption): Promise<CompleteResult> => Promise.resolve({ items: [{ word: 'foo#abc' }] }),
}
disposables.push(sources.addSource(source))
await nvim.input('if')
await helper.waitPopup()
await nvim.input('<esc>')
await helper.wait(50)
expect(completion.isActivated).toBe(false)
})
it('should cancel on CursorMoved', async () => {
let buf = await nvim.buffer
await buf.setLines(['', 'bar'], { start: 0, end: -1, strictIndexing: false })
let source: ISource = {
priority: 99,
enable: true,
name: 'temp',
sourceType: SourceType.Service,
doComplete: (_opt: CompleteOption): Promise<CompleteResult> => Promise.resolve({ items: [{ word: 'foo#abc' }] }),
}
disposables.push(sources.addSource(source))
await nvim.input('if')
await helper.waitPopup()
void events.fire('CompleteDone', [{}])
await helper.wait(10)
await events.fire('CursorMovedI', [buf.id, [2, 1]])
expect(completion.isActivated).toBe(false)
await nvim.input('<esc>')
})
it('should use source-provided score', async () => {
let source: ISource = {
priority: 0,
enable: true,
name: 'source',
sourceType: SourceType.Service,
doComplete: (_opt: CompleteOption): Promise<CompleteResult> => Promise.resolve({
items: [
{ word: 'candidate_a', sourceScore: 0.1 },
{ word: 'candidate_b', sourceScore: 10 },
{ word: 'candidate_c' },
]
}),
}
disposables.push(sources.addSource(source))
await nvim.input('ocand')
await helper.waitPopup()
let items = await helper.getItems()
expect(items[0].word).toBe('candidate_b')
expect(items[1].word).toBe('candidate_c')
expect(items[2].word).toBe('candidate_a')
})
it('should do resolve for complete item', async () => {
let resolved = false
let source: ISource = {
priority: 0,
enable: true,
name: 'resolve',
sourceType: SourceType.Service,
triggerCharacters: ['.'],
doComplete: (_opt: CompleteOption): Promise<CompleteResult> => Promise.resolve({ items: [{ word: 'foo' }] }),
onCompleteResolve: item => {
resolved = true
item.info = 'detail'
}
}
disposables.push(sources.addSource(source))
await nvim.input('i.')
await helper.waitPopup()
await helper.selectCompleteItem(0)
await helper.waitFor('getline', ['.'], '.foo')
expect(resolved).toBe(true)
})
})
describe('CompleteDone', () => {
it('should fix word on CompleteDone', async () => {
let doc = await workspace.document
await nvim.setLine('fball football')
await doc.synchronize()
await nvim.input('i')
await nvim.call('cursor', [1, 2])
let option: CompleteOption = await nvim.call('coc#util#get_complete_option')
await completion.startCompletion(option)
await helper.waitPopup()
let items = await helper.items()
expect(items.length).toBe(1)
await helper.selectCompleteItem(0)
await helper.waitFor('getline', ['.'], 'football football')
})
})
describe('InsertEnter', () => {
beforeEach(() => {
helper.updateConfiguration('suggest.triggerAfterInsertEnter', true)
})
it('should trigger completion if triggerAfterInsertEnter is true', async () => {
let doc = await workspace.document
await nvim.setLine('foo fo')
await doc.synchronize()
await nvim.input('A')
await doc.synchronize()
await helper.waitPopup()
expect(completion.isActivated).toBe(true)
})
it('should not trigger when input length too small', async () => {
await nvim.setLine('foo ')
await nvim.input('A')
await helper.wait(30)
expect(completion.isActivated).toBe(false)
})
})
describe('trigger completion', () => {
it('should trigger complete on trigger patterns match', async () => {
let source: ISource = {
priority: 99,
enable: true,
name: 'temp',
triggerPatterns: [/EM/],
sourceType: SourceType.Service,
doComplete: (opt: CompleteOption): Promise<CompleteResult> => {
if (!opt.input.startsWith('EM')) return null
return Promise.resolve({
items: [
{ word: 'foo', filterText: 'EMfoo' },
{ word: 'bar', filterText: 'EMbar' }
]
})
},
}
disposables.push(sources.addSource(source))
await nvim.input('i')
await nvim.input('EM')
await helper.waitPopup()
let items = await helper.getItems()
expect(items.length).toBe(2)
})
it('should cancel on backspace', async () => {
let doc = await workspace.document
await nvim.setLine('foo bar')
await doc.synchronize()
await nvim.input('of')
let res = await helper.visible('foo', 'around')
expect(res).toBe(true)
await nvim.input('<backspace>')
await helper.waitFor('pumvisible', [], 0)
})
it('should trigger on first letter insert', async () => {
await nvim.setLine('foo bar')
await helper.wait(30)
await nvim.input('of')
let res = await helper.visible('foo', 'around')
expect(res).toBe(true)
})
it('should trigger on force refresh', async () => {
let doc = await workspace.document
await nvim.setLine('foo f')
await doc.synchronize()
await nvim.input('A')
await nvim.call('coc#start')
let res = await helper.visible('foo', 'around')
expect(res).toBe(true)
})
it('should filter and sort on increment search', async () => {
let doc = await workspace.document
await nvim.setLine('forceDocumentSync format fallback')
await doc.synchronize()
await nvim.input('of')
await helper.waitPopup()
let items = await helper.getItems()
await nvim.input('oa')
await helper.waitFor('eval', ['len(coc#_context["candidates"])'], 1)
items = await helper.getItems()
expect(items.findIndex(o => o.word == 'fallback')).toBe(-1)
})
it('should not trigger on insert enter', async () => {
let doc = await workspace.document
await nvim.setLine('foo bar')
await doc.synchronize()
await nvim.input('o')
let visible = await nvim.call('pumvisible')
expect(visible).toBe(0)
})
it('should filter on fast input', async () => {
let doc = await workspace.document
await nvim.setLine('foo bar')
await doc.synchronize()
await nvim.input('oba')
await helper.waitPopup()
let items = await helper.getItems()
let item = items.find(o => o.word == 'foo')
expect(item).toBeFalsy()
expect(items[0].word).toBe('bar')
})
it('should filter completion when type none trigger character', async () => {
let source: ISource = {
name: 'test',
priority: 10,
enable: true,
firstMatch: false,
sourceType: SourceType.Native,
triggerCharacters: [],
doComplete: async (): Promise<CompleteResult> => {
let result: CompleteResult = {
items: [{ word: 'if(' }]
}
return Promise.resolve(result)
}
}
disposables.push(sources.addSource(source))
await nvim.setLine('')
await nvim.input('iif')
await helper.waitPopup()
await nvim.input('(')
await helper.wait(50)
let res = await helper.pumvisible()
expect(res).toBe(true)
})
it('should trigger on triggerCharacters', async () => {
let source: ISource = {
name: 'trigger',
enable: true,
triggerCharacters: ['.'],
doComplete: async (): Promise<CompleteResult> => Promise.resolve({
items: [{ word: 'foo' }]
})
}
disposables.push(sources.addSource(source))
let source1: ISource = {
name: 'trigger1',
enable: true,
triggerCharacters: ['.'],
doComplete: async (): Promise<CompleteResult> => Promise.resolve({
items: [{ word: 'bar' }]
})
}
disposables.push(sources.addSource(source1))
await nvim.input('i.')
await helper.waitPopup()
let items = await helper.getItems()
expect(items.length).toBe(2)
})
it('should fix start column', async () => {
let source: ISource = {
name: 'test',
priority: 10,
enable: true,
firstMatch: false,
sourceType: SourceType.Native,
triggerCharacters: [],
doComplete: async (): Promise<CompleteResult> => {
let result: CompleteResult = {
startcol: 0,
items: [{ word: 'foo.bar' }]
}
return Promise.resolve(result)
}
}
let disposable = sources.addSource(source)
await nvim.setLine('foo.')
await nvim.input('Ab')
await helper.waitPopup()
let val = await nvim.getVar('coc#_context') as any
expect(val.start).toBe(0)
disposable.dispose()
})
it('should should complete items without input', async () => {
await workspace.document
let source: ISource = {
enable: true,
name: 'trigger',
priority: 10,
sourceType: SourceType.Native,
doComplete: async (): Promise<CompleteResult> => Promise.resolve({
items: [{ word: 'foo' }, { word: 'bar' }]
})
}
disposables.push(sources.addSource(source))
await nvim.command('inoremap <silent><nowait><expr> <c-space> coc#refresh()')
await nvim.input('i')
await helper.wait(30)
await nvim.input('<c-space>')
await helper.waitPopup()
let items = await helper.getItems()
expect(items.length).toBeGreaterThan(1)
})
it('should show float window', async () => {
let source: ISource = {
name: 'float',
priority: 10,
enable: true,
sourceType: SourceType.Native,
doComplete: (): Promise<CompleteResult> => Promise.resolve({
items: [{ word: 'foo', info: 'bar' }]
})
}
disposables.push(sources.addSource(source))
await nvim.input('i')
await helper.wait(30)
await nvim.input('f')
await helper.waitPopup()
await nvim.call('nvim_select_popupmenu_item', [0, false, false, {}])
await helper.wait(100)
let hasFloat = await nvim.call('coc#float#has_float')
expect(hasFloat).toBe(1)
let res = await helper.visible('foo', 'float')
expect(res).toBe(true)
})
it('should trigger on triggerPatterns', async () => {
let source: ISource = {
name: 'pattern',
priority: 10,
enable: true,
sourceType: SourceType.Native,
triggerPatterns: [/\w+\.$/],
doComplete: async (): Promise<CompleteResult> => Promise.resolve({
items: [{ word: 'foo' }]
})
}
disposables.push(sources.addSource(source))
await nvim.input('i')
await helper.wait(10)
await nvim.input('.')
await helper.wait(30)
let pumvisible = await nvim.call('pumvisible')
expect(pumvisible).toBe(0)
await nvim.input('a')
await helper.wait(30)
await nvim.input('.')
await helper.waitPopup()
let res = await helper.visible('foo', 'pattern')
expect(res).toBe(true)
})
it('should not trigger triggerOnly source', async () => {
await nvim.setLine('foo bar')
let source: ISource = {
name: 'pattern',
triggerOnly: true,
priority: 10,
enable: true,
sourceType: SourceType.Native,
triggerPatterns: [/^From:\s*/],
doComplete: async (): Promise<CompleteResult> => Promise.resolve({
items: [{ word: 'foo' }]
})
}
disposables.push(sources.addSource(source))
await nvim.input('o')
await helper.wait(10)
await nvim.input('f')
await helper.wait(10)
let res = await helper.visible('foo', 'around')
expect(res).toBe(true)
let items = await helper.items()
expect(items.length).toBe(1)
})
it('should not trigger when cursor moved', async () => {
let source: ISource = {
name: 'trigger',
priority: 10,
enable: true,
sourceType: SourceType.Native,
triggerCharacters: ['.'],
doComplete: async (): Promise<CompleteResult> => Promise.resolve({
items: [{ word: 'foo' }]
})
}
disposables.push(sources.addSource(source))
await nvim.setLine('.a')
await nvim.input('A')
await nvim.eval('feedkeys("\\<bs>")')
await helper.wait(10)
await nvim.eval('feedkeys("\\<left>")')
await helper.wait(20)
let visible = await nvim.call('pumvisible')
expect(visible).toBe(0)
})
it('should trigger when completion is not completed', async () => {
let token: CancellationToken
let promise = new Promise(resolve => {
let source: ISource = {
name: 'completion',
priority: 10,
enable: true,
sourceType: SourceType.Native,
triggerCharacters: ['.'],
doComplete: async (opt, cancellationToken): Promise<CompleteResult> => {
if (opt.triggerCharacter != '.') {
token = cancellationToken
resolve(undefined)
return new Promise<CompleteResult>((resolve, reject) => {
let timer = setTimeout(() => {
resolve({ items: [{ word: 'foo' }] })
}, 200)
if (cancellationToken.isCancellationRequested) {
clearTimeout(timer)
reject(new Error('Cancelled'))
}
})
}
return Promise.resolve({
items: [{ word: 'bar' }]
})
}
}
disposables.push(sources.addSource(source))
})
await nvim.input('if')
await promise
await nvim.input('.')
await helper.waitPopup()
await helper.visible('bar', 'completion')
expect(token).toBeDefined()
expect(token.isCancellationRequested).toBe(true)
})
})
describe('completion results', () => {
it('should limit results for low priority source', async () => {
let doc = await workspace.document
helper.updateConfiguration('suggest.lowPrioritySourceLimit', 2)
await nvim.setLine('filename filepath find filter findIndex')
await doc.synchronize()
await nvim.input('of')
await helper.waitPopup()
let items = await helper.getItems()
items = items.filter(o => o.menu == '[A]')
expect(items.length).toBe(2)
})
it('should limit result for high priority source', async () => {
helper.updateConfiguration('suggest.highPrioritySourceLimit', 2)
let source: ISource = {
name: 'high',
priority: 90,
enable: true,
sourceType: SourceType.Native,
triggerCharacters: ['.'],
doComplete: async (): Promise<CompleteResult> => Promise.resolve({
items: ['filename', 'filepath', 'filter', 'file'].map(key => ({ word: key }))
})
}
disposables.push(sources.addSource(source))
await nvim.input('i.')
await helper.waitPopup()
let items = await helper.getItems()
expect(items.length).toBeGreaterThan(1)
})
it('should truncate label of complete items', async () => {
helper.updateConfiguration('suggest.labelMaxLength', 10)
let source: ISource = {
name: 'high',
priority: 90,
enable: true,
sourceType: SourceType.Native,
triggerCharacters: ['.'],
doComplete: async (): Promise<CompleteResult> => Promise.resolve({
items: ['a', 'b', 'c', 'd'].map(key => ({ word: key.repeat(20) }))
})
}
disposables.push(sources.addSource(source))
await nvim.input('i.')
await helper.waitPopup()
let items = await helper.getItems()
for (let item of items) {
if (!item.abbr) continue
expect(item.abbr.length).toBeLessThanOrEqual(10)
}
})
it('should delete previous items when complete items is null', async () => {
let source1: ISource = {
name: 'source1',
priority: 90,
enable: true,
sourceType: SourceType.Native,
triggerCharacters: ['.'],
doComplete: async (): Promise<CompleteResult> => Promise.resolve({
items: [{ word: 'foo', dup: 1 }]
})
}
let source2: ISource = {
name: 'source2',
priority: 90,
enable: true,
sourceType: SourceType.Native,
triggerCharacters: ['.'],
doComplete: async (opt: CompleteOption): Promise<CompleteResult> => {
let result: CompleteResult = opt.input == 'foo' ? null : {
items: [{ word: 'foo', dup: 1 }], isIncomplete: true
}
return Promise.resolve(result)
}
}
disposables.push(sources.addSource(source1))
disposables.push(sources.addSource(source2))
await nvim.input('i')
await nvim.input('.f')
await helper.waitPopup()
let items = await helper.getItems()
expect(items.length).toEqual(2)
await nvim.input('oo')
await helper.waitFor('eval', ['len(coc#_context["candidates"])'], 1)
items = await helper.getItems()
expect(items.length).toEqual(1)
expect(items[0].word).toBe('foo')
})
})
describe('fix indent', () => {
it('should indent lines on TextChangedP #1', async () => {
let doc = await workspace.document as any
doc._indentkeys = '=~end,0=\\item'
let source: ISource = {
name: 'source1',
priority: 90,
enable: true,
sourceType: SourceType.Native,
doComplete: async (): Promise<CompleteResult> => Promise.resolve({
items: [
{ word: 'item' },
{ word: 'items' },
{ word: 'END' },
{ word: 'ENDIF' }
]
})
}
disposables.push(sources.addSource(source))
await nvim.input('i')
await helper.wait(10)
await nvim.input(' \\ite')
await helper.waitPopup()
await nvim.input('m')
await helper.waitFor('getline', ['.'], '\\item')
await nvim.input('<cr>')
await helper.wait(30)
await nvim.input(' END')
await helper.waitFor('getline', ['.'], 'END')
})
it('should trigger completion after indent change', async () => {
let doc = await workspace.document as any
doc._indentkeys = '=end'
let source: ISource = {
name: 'source1',
priority: 90,
enable: true,
sourceType: SourceType.Native,
doComplete: async (): Promise<CompleteResult> => Promise.resolve({
items: [
{ word: 'endif' },
{ word: 'endfunction' }
]
})
}
disposables.push(sources.addSource(source))
await nvim.input('i')
await helper.wait(10)
await nvim.input(' en')
await helper.waitPopup()
await nvim.input('d')
await helper.waitFor('getline', ['.'], 'end')
await helper.waitPopup()
let items = await helper.getItems()
expect(items.length).toBeGreaterThan(0)
})
})
describe('Character insert', () => {
beforeAll(() => {
let source: ISource = {
name: 'insert',
firstMatch: false,
sourceType: SourceType.Native,
triggerCharacters: ['.'],
doComplete: async (opt): Promise<CompleteResult> => {
if (opt.word === 'f') return { items: [{ word: 'foo' }] }
if (!opt.triggerCharacter) return { items: [] }
let result: CompleteResult = {
items: [{ word: 'one' }, { word: 'two' }]
}
return Promise.resolve(result)
}
}
sources.addSource(source)
})
afterAll(() => {
sources.removeSource('insert')
})
it('should keep selected text after text change', async () => {
let doc = await workspace.document
await nvim.setLine('f')
await nvim.input('A')
await doc.synchronize()
await triggerCompletion('insert')
await helper.waitPopup()
await nvim.call('nvim_select_popupmenu_item', [0, true, false, {}])
let line = await nvim.line
expect(line).toBe('foo')
await nvim.exec(`
noa call setline('.', 'foobar')
noa call cursor(1, 7)
`)
await helper.wait(50)
let res = await helper.pumvisible()
expect(res).toBe(false)
line = await nvim.line
expect(line).toBe('foobar')
})
it('should trigger specific sources by api', async () => {
let text = 'foo bar f'
await nvim.setLine(text)
await nvim.input('A')
await triggerCompletion('insert')
await helper.waitPopup()
})
})
})