tjdevries / nvim-langserver-shim

Shim for the language server protocol developed by Microsoft
MIT License
155 stars 4 forks source link

async completion #11

Closed prabirshrestha closed 5 years ago

prabirshrestha commented 7 years ago

So I got a proof of concept pure async completion working at https://gist.github.com/prabirshrestha/0b3b7b4e2112c662979bfeeec4e1edb7 after studying completor.vim's initial commit. Huge credit goes to completor.vim for its simplicity. It is a month completion using timer to prove async. You will notice the completion after 1 second due to the timer. Only tested in Vim8 on windows but I believe it should work on neovim too.

The goal would be to auto implement omnifunc for the filetype if LSP server supports completionProvider. Then \<C-x>\<C-o> could open the completion asynchronously. This allows to use intellisense/autocomplete with just this plugin. If the users like they can install other plugins like https://github.com/lifepillar/vim-mucomplete and https://github.com/Shougo/deoplete.nvim to have a better experience like tab completion and so on.

Deoplete could then use the omni func provided by this plugin.

let g:deoplete#omni#functions = {}
let g:deoplete#omni#functions.ruby = 'langserver#complete'
let g:deoplete#omni#functions.javascript = [
    \ 'tern#Complete',
    \ 'jspc#omni',
    \ 'langserver#complete'
    \ ]

Also would need to improve the omni pattern like deoplete.

let g:deoplete#omni_patterns = {}
let g:deoplete#omni_patterns.java = '[^. *\t]\.\w*'

Super excited about how this will land 😃

tjdevries commented 7 years ago

Wow! Nice work. That looks great. I'll take a look at this later this week. Very cool idea! :+1:

prabirshrestha commented 7 years ago

Hmm. After thinking more not sure my initial async omnicomplete feature is going to work. Since pum (popup menu) is managed by plugins such as completor.vim and deoplete.nvim.

Here is another idea. Note s:emojicomplete is exactly the same signature as vim's completefunc and omnifunc. This could allow us to easily reuse existing completion funcs.

function s:emojicomplete(findstart, base)
    if a:findstart
        return match(getline('.')[0:col('.') -1], ':[^: \t]*$')
    elseif empty(a:base)
        return []
    else
        return [{'word': ':smile:'}, {'word': ':+1:'}]
    endif
endfunction

function s:jscomplete(findstart, base, done)
    if a:findstart
        call done(1)
    elseif empty(a:base)
        call done([])
    else
        " simulate async completion using timer after 1 sec
        let s:timer = timer_start(1000, timer->s:jscomplete_loaded(timer, done))
    endif
    return { 'cancel': {->s:jscancel(s:timer)}}
endfunction

function s:jscancel(timer)
    call timer_stop(a:timer)
endfunction

function s:jscomplete_loaded(timer, done)
    done([{'word': 'function'}, {'word', 'var'}, {'word': 'let'}])
endfunction

completor#add_completor({
    \ 'name': 'js-emoji-completor',
    \ 'filetype': ['javascript', 'typescript'],
    \ 'completors': [
    \   { 'name': 'javascript', 'asynccompletefunc': function('s:jscomplete') },
    \   { 'name': 'emoji', 'completefunc': function('s:emojicomplete') },
    \   'buffer',
    \   'syntax'
    \ ]
})

completor#remove_completor('js-emoji-completor')

We could also provide a helper func to convert sync completion to async completor#to_async_complete_func(function('s:emojicomplete')).

Notice completors as list of completors. Unlike vim-mucomplete we should have an option to merge instead of just chain the completors.

Ideally I would want to avoid writing another completion engine and just use completor.vim or deoplete.nvim if possible.

Another good article on completion in vim. http://junegunn.kr/2014/06/emoji-completion-in-vim/

prabirshrestha commented 7 years ago

Here is more notes I gathered from looking at vscode.

export interface CompletionProvider {
    provideCompletionItems(document: TextDocument, position: Position, token: CancellationToken): ProviderResult<CompletionItem[] | CompletionList>;
}

// example: let sel:DocumentSelector = 'typescript';
// example: let sel:DocumentSelector = ['typescript', { language: 'json', pattern: '**/tsconfig.json' }]
export type DocumentSelector = string | DocumentFilter | (string | DocumentFilter)[];

// trigger characters like '.' or ':'
export function registerCompletionItemProvider(selector: DocumentSelector, provider: CompletionProvider, ...triggerCharacters: string[]): Disposable;

export function registerCompletionItemProvider(languageId: string, provider: CompletionProvider): IDisposable;

export interface TextDocument {
    uri: Uri,
    fileName: string,
    isUntitled: boolean;
    languageId: string;
    version: number;
    isDirty: boolean;
    save(): Thenable<boolean>;
    lineCount: number;
    lineAt(line: number): TextLine;
    lineAt(position: Position): TextLine;
    offsetAt(position: Position): number;
    positionAt(offset: number): Position;
    getText(range?: Range): string;
    getWordRangeAtPosition(position: Position, regex?: RegExp): Range | undefined;
    validateRange(range: Range): Range;
    validatePosition(position: Position): Position;
}

export class Position {
    line: number;
    character: umber;
    constructor(line: number, character: number);
    // some more helper methods related to positions like isBefore, isAfter, is Equal
}

export enum CompletionItemKind {
    Text,
    Method,
    Function,
    Constructor,
    Field,
    Variable,
    Class,
    Interface,
    Module,
    Property,
    Unit,
    Value,
    Enum,
    Keyword,
    Snippet,
    Color,
    File,
    Reference,
    Folder
}

export interface CompletionItem {
    label: string;
    kind: CompletionItemKind;
    detail?: string; // human-readable string with additional information about this item, like type of symbol information
    documentation?: string;
    // some more at https://github.com/Microsoft/vscode/blob/5162e3b5b56ac66a79920e6142fdfdeb27c0420f/src/vs/editor/browser/standalone/standaloneLanguages.ts#L268
}

Here is somewhat of a rough vim api for completion.

function s:emoji_complete(document, position, cancelToken, done)
    " sync just call done immediately
    call done([':smile:', ':+1:'])
endfunction

function s:ts_complete_provider(document, position, cancelToken, done)
    " simulate async with timer
    call timer_start(1000, timer->s:ts_complete_done(document, position, cancelToken, done))
endfunction

function s:ts_complete_done(document, position, cancelToken, done)
    " completion lib should be smart enough to ignore this done call if the buffer has changed
    " so that we don't show old completion items
    call done([
        \ { 'label': 'function', kind: 'keyword' },
        \ { 'label': 'class', kind: 'keyword' }
        \ ])
endfunction

call complete#providers#register(['typescript', 'javascript'], function('s:ts_complete_provider'), ['.'])
call complete#providers#register('*', function('s:emoji_complete'), [':'])

Need to figure out what to do with parameters (document, position, cancelToken, done)

prabirshrestha commented 7 years ago

I got a working async completion library that work in both vim 8 and neovim with timers as the only requirement. You can find it at https://github.com/prabirshrestha/asyncomplete.vim

Huge props goes to https://github.com/roxma/nvim-complete-manager