autozimu / LanguageClient-neovim

Language Server Protocol (LSP) support for vim and neovim.
MIT License
3.55k stars 273 forks source link

LanguageClient#textDocument_codeAction() is only applied after timeout #546

Closed marcuskrahl closed 5 years ago

marcuskrahl commented 6 years ago

Describe the bug

When running LanguageClient#textDocument_codeAction() in a typescript project the available actions are shown almost immediately, but after selecting an action, the action is applied only after the language client timeout.

Environment

My full vimrc is available at https://github.com/marcuskrahl/dotfiles

Relevant parts:

call plug#begin()
Plug 'tpope/vim-surround'
Plug 'tpope/vim-commentary'
Plug 'matze/vim-move'
Plug 'jedverity/feral-vim'
Plug 'editorconfig/editorconfig-vim'
Plug 'autozimu/LanguageClient-neovim', { 'branch': 'next', 'do': 'bash install.sh' }
Plug 'jceb/vim-orgmode' | Plug 'tpope/vim-speeddating'

"FZF
if !empty(glob('/usr/share/vim/vimfiles/plugin/fzf.vim'))
    so /usr/share/vim/vimfiles/plugin/fzf.vim
    Plug 'junegunn/fzf.vim'
endif

" Typescript dependencies
Plug 'leafgarland/typescript-vim'

call plug#end()

let g:LanguageClient_autoStart = 1
let g:LanguageClient_serverCommands = {}
if executable('javascript-typescript-stdio')
    let g:LanguageClient_serverCommands.javascript = ['javascript-typescript-stdio']
    let g:LanguageClient_serverCommands.typescript = ['javascript-typescript-stdio']
    "let g:LanguageClient_serverCommands.typescript = ['tcp://127.0.0.1:2089']
    autocmd FileType javascript setlocal omnifunc=LanguageClient#complete
    autocmd FileType typescript setlocal omnifunc=LanguageClient#complete
    nnoremap <silent> <leader>lh :call LanguageClient#textDocument_hover()<CR>
    nnoremap <silent> <leader>ld :call LanguageClient#textDocument_definition()<CR>
    nnoremap <silent> <leader>lr :call LanguageClient#textDocument_rename()<CR>
    nnoremap <silent> <leader>la :call LanguageClient#textDocument_codeAction()<CR>
    nnoremap <silent> <leader>ls :call LanguageClient#textDocument_documentSymbol()<CR>
    nnoremap <silent> <leader>lS :call LanguageClient#workspace_symbol()<CR>

    let g:LanguageClient_loggingFile = '/tmp/LanguageClient.log'
    let g:LanguageClient_loggingLevel = 'INFO'
    let g:LanguageClient_serverStderr = '/tmp/LanguageServer.log'
    let g:LanguageClient_waitOutputTimeout = 10
else
    echo "javascript-typescript-stdio not installed!\n"
    :cq
endif

To Reproduce

Steps to reproduce the behavior:

  1. Fetch example project https://github.com/marcuskrahl/angular6-vim
  2. Start vim, nvim -u min-vimrc.vim
  3. Edit src/app/app.component.ts
  4. go to line 17 and put the cursor on test()
  5. Trigger code action
  6. select replace with this.test()
  7. wait for timeout
  8. the action is applied

Current behavior

The action is only applied after the language client timeout (g:LanguageClient_waitOutputTimeout). There is an error in the log as well.

If I set the timeout to a lower value (10s -> 2s) the action is successfully applied after this shorter timeout

Expected behavior

The action should be applied as soon as possible

Log

10:35:19 INFO reader-typescript src/vim.rs:389 <= Some("typescript") {"jsonrpc":"2.0","id":23,"result":[{"title":"Add 'this.' to unresolved variable","command":"codeFix","arguments":[{"fileName":"/home/marcus/tmp/angular6/src/app/app.component.ts","textChanges":[{"span":{"start":460,"length":4},"newText":"this.test"}]}]}]}
10:35:19 INFO main src/vim.rs:90 => None {"jsonrpc":"2.0","method":"s:FZF","params":[["codeFix: Add 'this.' to unresolved variable"],"LanguageClient_FZFSinkCommand"],"id":24}
10:35:19 INFO reader-main src/vim.rs:389 <= None {"id": 24, "jsonrpc": "2.0", "result": 0}
10:35:19 INFO main src/languageclient.rs:1173 End textDocument/codeAction
10:35:19 INFO main src/vim.rs:90 => None {"jsonrpc":"2.0","result":[{"arguments":[{"fileName":"/home/marcus/tmp/angular6/src/app/app.component.ts","textChanges":[{"newText":"this.test","span":{"length":4,"start":460}}]}],"command":"codeFix","title":"Add 'this.' to unresolved variable"}],"id":1}
10:35:20 INFO reader-main src/vim.rs:389 <= None {"method": "LanguageClient_FZFSinkCommand", "jsonrpc": "2.0", "params": {"languageId": "typescript", "selection": "codeFix: Add 'this.' to unresolved variable", "buftype": ""}}
10:35:20 INFO main src/languageclient.rs:2309 Begin LanguageClient_FZFSinkCommand
10:35:20 INFO main src/languageclient.rs:46 gather_args: ["selection"] = [String("codeFix: Add \'this.\' to unresolved variable")]
10:35:20 INFO main src/languageclient.rs:1566 Begin workspace/executeCommand
10:35:20 INFO main src/languageclient.rs:29 Some arguments are not available. Requesting from vim. Keys: ["languageId"]. Exps: ["&filetype"]
10:35:20 INFO main src/vim.rs:90 => None {"jsonrpc":"2.0","method":"eval","params":["[&filetype]"],"id":25}
10:35:20 INFO reader-main src/vim.rs:389 <= None {"method": "languageClient/handleCursorMoved", "jsonrpc": "2.0", "params": {"languageId": "typescript", "line": 17, "LSP#visible_line_start()": 0, "LSP#visible_line_end()": 28, "buftype": "", "filename": "/home/marcus/tmp/angular6/src/app/app.component.ts"}}
10:35:20 INFO reader-main src/vim.rs:389 <= None {"id": 25, "jsonrpc": "2.0", "result": ["typescript"]}
10:35:20 INFO main src/languageclient.rs:46 gather_args: [LanguageId] = [String("typescript")]
10:35:20 INFO main src/languageclient.rs:46 gather_args: ["command", "arguments"] = [String("codeFix"), Array([Object({"fileName": String("/home/marcus/tmp/angular6/src/app/app.component.ts"), "textChanges": Array([Object({"newText": String("this.test"), "span": Object({"length": Number(4), "start": Number(460)})})])})])]
10:35:20 INFO main src/vim.rs:90 => Some("typescript") {"jsonrpc":"2.0","method":"workspace/executeCommand","params":{"arguments":[{"fileName":"/home/marcus/tmp/angular6/src/app/app.component.ts","textChanges":[{"newText":"this.test","span":{"length":4,"start":460}}]}],"command":"codeFix"},"id":26}
10:35:20 INFO reader-typescript src/vim.rs:389 <= Some("typescript") {"jsonrpc":"2.0","method":"workspace/applyEdit","id":1,"params":{"edit":{"changes":{"file:///home/marcus/tmp/angular6/src/app/app.component.ts":[{"range":{"start":{"line":17,"character":4},"end":{"line":17,"character":8}},"newText":"this.test"}]}}},"meta":{}}
10:35:30 ERROR main src/vim.rs:71 Error handling message: timed out waiting on channel

Message: {"jsonrpc":"2.0","method":"LanguageClient_FZFSinkCommand","params":{"buftype":"","languageId":"typescript","selection":"codeFix: Add 'this.' to unresolved variable"}}

Error: Timeout
10:35:30 INFO main src/languageclient.rs:2122 Begin languageClient/handleCursorMoved
10:35:30 INFO main src/languageclient.rs:46 gather_args: [Buftype, Filename, Line] = [String(""), String("/home/marcus/tmp/angular6/src/app/app.component.ts"), Number(17)]
10:35:30 INFO main src/languageclient.rs:46 gather_args: ["LSP#visible_line_start()", "LSP#visible_line_end()"] = [Number(0), Number(28)]
10:35:30 INFO main src/languageclient.rs:2211 End languageClient/handleCursorMoved
10:35:30 INFO main src/languageclient.rs:1581 Begin workspace/applyEdit
10:35:30 INFO main src/languageclient.rs:29 Some arguments are not available. Requesting from vim. Keys: ["filename", "line", "character"]. Exps: ["LSP#filename()", "LSP#line()", "LSP#character()"]
10:35:30 INFO main src/vim.rs:90 => None {"jsonrpc":"2.0","method":"eval","params":["[LSP#filename(), LSP#line(), LSP#character()]"],"id":27}
10:35:30 INFO reader-main src/vim.rs:389 <= None {"id": 27, "jsonrpc": "2.0", "result": ["/home/marcus/tmp/angular6/src/app/app.component.ts", 17, 5]}
10:35:30 INFO main src/languageclient.rs:46 gather_args: [Filename, Line, Character] = [String("/home/marcus/tmp/angular6/src/app/app.component.ts"), Number(17), Number(5)]
10:35:30 INFO main src/vim.rs:90 => None {"jsonrpc":"2.0","method":"s:Edit","params":["edit","/home/marcus/tmp/angular6/src/app/app.component.ts"],"id":28}
10:35:30 INFO reader-main src/vim.rs:389 <= None {"id": 28, "jsonrpc": "2.0", "result": 0}
10:35:30 INFO main src/vim.rs:90 => None {"jsonrpc":"2.0","method":"getline","params":[1,"$"],"id":29}
10:35:30 INFO reader-main src/vim.rs:389 <= None {"id": 29, "jsonrpc": "2.0", "result": ["import { Component } from \"@angular/core\"", "import { Router, ActivatedRoute } from \"@angular/router\"", "", "@Component({", "  selector: \"app-root\",", "  templateUrl: \"./app.component.html\",", "  styleUrls: [\"./app.component.css\"],", "})", "export class AppComponent {", "  title: number | string = \"app\"", "", "  constructor(private router: Router, private activatedRoute: ActivatedRoute) {}", "", "  public test(): void {", "    console.log(\"this is a test\")", "    this.test();", "    this.title = 42", "    test();", "    this.title = 42;", "  }", "", "  private function3(test: any[]): void {", "    this.router.navigate([\".\"])", "  }", "", "  private function2(n: number, t: string): boolean {", "    return n == 42 || t == \"fourtytwo\"", "  }", "}"]}
10:35:30 INFO main src/vim.rs:90 => None {"jsonrpc":"2.0","method":"eval","params":["&fixendofline"],"id":30}
10:35:30 INFO reader-main src/vim.rs:389 <= None {"id": 30, "jsonrpc": "2.0", "result": 1}
10:35:30 INFO main src/vim.rs:90 => None {"jsonrpc":"2.0","method":"setline","params":[1,["import { Component } from \"@angular/core\"","import { Router, ActivatedRoute } from \"@angular/router\"","","@Component({","  selector: \"app-root\",","  templateUrl: \"./app.component.html\",","  styleUrls: [\"./app.component.css\"],","})","export class AppComponent {","  title: number | string = \"app\"","","  constructor(private router: Router, private activatedRoute: ActivatedRoute) {}","","  public test(): void {","    console.log(\"this is a test\")","    this.test();","    this.title = 42","    this.test();","    this.title = 42;","  }","","  private function3(test: any[]): void {","    this.router.navigate([\".\"])","  }","","  private function2(n: number, t: string): boolean {","    return n == 42 || t == \"fourtytwo\"","  }","}"]],"id":31}
10:35:30 INFO reader-main src/vim.rs:389 <= None {"id": 31, "jsonrpc": "2.0", "result": 0}
10:35:30 INFO main src/vim.rs:90 => None {"jsonrpc":"2.0","method":"s:Edit","params":["edit","/home/marcus/tmp/angular6/src/app/app.component.ts"],"id":32}
10:35:30 INFO reader-main src/vim.rs:389 <= None {"method": "languageClient/handleTextChanged", "jsonrpc": "2.0", "params": {"languageId": "typescript", "buftype": "", "filename": "/home/marcus/tmp/angular6/src/app/app.component.ts"}}
10:35:30 INFO reader-main src/vim.rs:389 <= None {"id": 32, "jsonrpc": "2.0", "result": 0}
10:35:30 INFO main src/vim.rs:90 => None {"jsonrpc":"2.0","method":"cursor","params":[18,6]}
autozimu commented 6 years ago

This is a known limitation of current implementation. At the moment all requests are handled sequentially and blocking other calls. Had to wait for rust async/await before turning them to async handlers.

balta2ar commented 6 years ago

For my own education, what about futures or tokio/mio crates? Are they a viable option nowadays in Rust for async support?

autozimu commented 6 years ago

It is possible to use futures/tokio at the moment. But I don't think this is prime time to onboard as the language is adding syntax support and apis are in flux.

autozimu commented 5 years ago

Resolved by https://github.com/autozimu/LanguageClient-neovim/commit/c101096f0289cd932e84ee3c12a2624b19ffe499