]> Sergey Matveev's repositories - vim-lsp.git/commitdiff
Initial version
authorYegappan Lakshmanan <yegappan@yahoo.com>
Mon, 7 Dec 2020 05:00:52 +0000 (21:00 -0800)
committerYegappan Lakshmanan <yegappan@yahoo.com>
Mon, 7 Dec 2020 05:00:52 +0000 (21:00 -0800)
README.md
autoload/lsp.vim [new file with mode: 0644]
doc/lsp.txt [new file with mode: 0644]
plugin/lsp.vim [new file with mode: 0644]

index e5e25461450c1a723d6220a27d64206efb41a489..a19266f7095feab142c7b20b850ee238e9a8b00f 100644 (file)
--- a/README.md
+++ b/README.md
@@ -1,2 +1,4 @@
-# lsp
-Language Server Protocol (LSP) plugin for Vim9
+# LSP vim9 plugin
+
+Language Server Protocol (LSP) plugin for Vim9.
+
diff --git a/autoload/lsp.vim b/autoload/lsp.vim
new file mode 100644 (file)
index 0000000..897650b
--- /dev/null
@@ -0,0 +1,327 @@
+vim9script
+
+# Vim LSP client
+
+var lsp_servers: dict<dict<any>> = {}
+
+def lsp#process_reply(req: dict<any>, reply: dict<any>): void
+  if req.method == 'textDocument/definition' || req.method == 'textDocument/declaration'
+    if reply.result->len() == 0
+      echomsg "Error: definition is not found"
+    else
+      var result: dict<any> = reply.result[0]
+      var file = result.uri[7:]
+      var wid = bufwinid(file)
+      if wid != -1
+        win_gotoid(wid)
+      else
+        exe 'split ' .. file
+      endif
+      cursor(result.range.start.line + 1, result.range.start.character + 1)
+      redraw!
+    endif
+  endif
+enddef
+
+def lsp#process_server_msg(ftype: string): void
+  while lsp_servers[ftype].data->len() > 0
+    var idx = stridx(lsp_servers[ftype].data, 'Content-Length: ')
+    if idx == -1
+      return
+    endif
+
+    var len = str2nr(lsp_servers[ftype].data[idx + 16:])
+    if len == 0
+      echomsg "Error: Content length is zero"
+      return
+    endif
+
+    # Header and contents are separated by '\r\n\r\n'
+    idx = stridx(lsp_servers[ftype].data, "\r\n\r\n")
+    if idx == -1
+      echomsg "Error: Content separator is not found"
+      return
+    endif
+
+    idx = idx + 4
+
+    if lsp_servers[ftype].data->len() - idx < len
+      echomsg "Error: Didn't receive the complete message"
+      return
+    endif
+
+    var content = lsp_servers[ftype].data[idx : idx + len - 1]
+    var reply = content->json_decode()
+
+    if reply->has_key('id')
+      var req = lsp_servers[ftype].requests->get(string(reply.id))
+      # Remove the corresponding stored request message
+      lsp_servers[ftype].requests->remove(string(reply.id))
+
+      if reply->has_key('error')
+        echomsg "Error: request " .. req.method .. " failed (" .. reply.error.message .. ")"
+      else
+        lsp#process_reply(req, reply)
+      endif
+    endif
+
+    lsp_servers[ftype].data = lsp_servers[ftype].data[idx + len :]
+  endwhile
+enddef
+
+def lsp#output_cb(ftype: string, chan: channel, msg: string): void
+  #writefile(split(msg, "\n"), 'lsp_server.out', 'a')
+  lsp_servers[ftype].data = lsp_servers[ftype].data .. msg
+  lsp#process_server_msg(ftype)
+enddef
+
+def lsp#error_cb(ftype: string, chan: channel, emsg: string,): void
+  #writefile(split(emsg, "\n"), 'lsp_server.err', 'a')
+enddef
+
+def lsp#exit_cb(ftype: string, job: job, status: number): void
+  echomsg "LSP server exited with status " .. status
+enddef
+
+# Return the next id for a LSP server request message
+def lsp#next_reqid(ftype: string): number
+  var id = lsp_servers[ftype].nextID
+  lsp_servers[ftype].nextID = id + 1
+  return id
+enddef
+
+# Send a request message to LSP server
+def lsp#sendto_server(ftype: string, content: dict<any>): void
+  var req_js: string = content->json_encode()
+  var msg = "Content-Length: " .. req_js->len() .. "\r\n\r\n"
+  var ch = lsp_servers[ftype].job->job_getchannel()
+  ch->ch_sendraw(msg)
+  ch->ch_sendraw(req_js)
+enddef
+
+def lsp#create_reqmsg(ftype: string, method: string): dict<any>
+  var req = {}
+  req.jsonrpc = '2.0'
+  req.id = lsp#next_reqid(ftype)
+  req.method = method
+  req.params = {}
+
+  # Save the request, so that the corresponding response can be processed
+  lsp_servers[ftype].requests->extend({[string(req.id)]: req})
+
+  return req
+enddef
+
+def lsp#create_notifmsg(ftype: string, notif: string): dict<any>
+  var req = {}
+  req.jsonrpc = '2.0'
+  req.method = notif
+  req.params = {}
+
+  return req
+enddef
+
+# Send a "initialize" LSP request
+def lsp#init_server(ftype: string): number
+  var req = lsp#create_reqmsg(ftype, 'initialize')
+  lsp#sendto_server(ftype, req)
+  return 1
+enddef
+
+# Start a LSP server
+def lsp#start_server(ftype: string): number
+  if lsp_servers[ftype].running
+    echomsg "LSP server for " .. ftype .. " is already running"
+    return 0
+  endif
+
+  var cmd = [lsp_servers[ftype].path]
+  cmd->extend(lsp_servers[ftype].args)
+
+  var opts = {'in_mode': 'raw',
+              'out_mode': 'raw',
+              'err_mode': 'raw',
+              'out_cb': function('lsp#output_cb', [ftype]),
+              'err_cb': function('lsp#error_cb', [ftype]),
+              'exit_cb': function('lsp#exit_cb', [ftype])}
+
+  #writefile([], 'lsp_server.out')
+  #writefile([], 'lsp_server.err')
+  lsp_servers[ftype].data = ''
+  lsp_servers[ftype].nextID = 1
+  lsp_servers[ftype].requests = {}
+
+  var job = job_start(cmd, opts)
+  if job->job_status() == 'fail'
+    echomsg "Error: Failed to start LSP server " .. lsp_servers[ftype].path
+    return 1
+  endif
+
+  lsp_servers[ftype].job = job
+  lsp_servers[ftype].running = v:true
+
+  lsp#init_server(ftype)
+
+  return 0
+enddef
+
+# Send a 'shutdown' request to the LSP server
+def lsp#shutdown_server(ftype: string): void
+  var req = lsp#create_reqmsg(ftype, 'shutdown')
+  lsp#sendto_server(ftype, req)
+enddef
+
+# Send a 'exit' notification to the LSP server
+def lsp#exit_server(ftype: string): void
+  var req = lsp#create_notifmsg(ftype, 'exit')
+  lsp#sendto_server(ftype, req)
+enddef
+
+# Stop a LSP server
+def lsp#stop_server(ftype: string): number
+  if !lsp_servers[ftype].running
+    echomsg "LSP server for " .. ftype .. " is not running"
+    return 0
+  endif
+
+  lsp#shutdown_server(ftype)
+
+  # Wait for the server to process the shutodwn request
+  sleep 1
+
+  lsp#exit_server(ftype)
+
+  lsp_servers[ftype].job->job_stop()
+  lsp_servers[ftype].job = v:none
+  lsp_servers[ftype].running = v:false
+  lsp_servers[ftype].requests = {}
+  return 0
+enddef
+
+# Send a LSP "textDocument/didOpen" notification
+def lsp#textdoc_didopen(fname: string, ftype: string): void
+  var notif = lsp#create_notifmsg(ftype, 'textDocument/didOpen')
+
+  # interface DidOpenTextDocumentParams
+  # interface TextDocumentItem
+  var tdi = {}
+  tdi.uri = 'file://' .. fname
+  tdi.languageId = ftype
+  tdi.version = 1
+  tdi.text = readfile(fname)->join("\n")
+  notif.params->extend({'textDocument': tdi})
+
+  lsp#sendto_server(ftype, notif)
+enddef
+
+# Send a LSP "textDocument/didClose" notification
+def lsp#textdoc_didclose(fname: string, ftype: string): void
+  var notif = lsp#create_notifmsg(ftype, 'textDocument/didClose')
+
+  # interface DidCloseTextDocumentParams
+  #   interface TextDocumentIdentifier
+  var tdid = {}
+  tdid.uri = 'file://' .. fname
+  notif.params->extend({'textDocument': tdid})
+
+  lsp#sendto_server(ftype, notif)
+enddef
+
+# Goto a definition using "textDocument/definition" LSP request
+def lsp#goto_definition(fname: string, ftype: string, lnum: number, col: number)
+  if fname == '' || ftype == ''
+    return
+  endif
+  if !lsp_servers->has_key(ftype)
+    echomsg 'Error: LSP server for "' .. ftype .. '" filetype is not found'
+    return
+  endif
+  if !lsp_servers[ftype].running
+    echomsg 'Error: LSP server for "' .. ftype .. '" filetype is not running'
+    return
+  endif
+
+  var req = lsp#create_reqmsg(ftype, 'textDocument/definition')
+
+  # interface DefinitionParams
+  # interface TextDocumentPositionParams
+  # interface TextDocumentIdentifier
+  req.params->extend({'textDocument': {'uri': 'file://' .. fname}})
+  # interface Position
+  req.params->extend({'position': {'line': lnum, 'character': col}})
+
+  lsp#sendto_server(ftype, req)
+enddef
+
+# Goto a declaration using "textDocument/declaration" LSP request
+def lsp#goto_declaration(fname: string, ftype: string, lnum: number, col: number)
+  if fname == '' || ftype == ''
+    return
+  endif
+  if !lsp_servers->has_key(ftype)
+    echomsg 'Error: LSP server for "' .. ftype .. '" filetype is not found'
+    return
+  endif
+  if !lsp_servers[ftype].running
+    echomsg 'Error: LSP server for "' .. ftype .. '" filetype is not running'
+    return
+  endif
+
+  var req = lsp#create_reqmsg(ftype, 'textDocument/declaration')
+
+  # interface DeclarationParams
+  #   interface TextDocumentPositionParams
+  #     interface TextDocumentIdentifier
+  req.params->extend({'textDocument': {'uri': 'file://' .. fname}})
+  #     interface Position
+  req.params->extend({'position': {'line': lnum, 'character': col}})
+
+  lsp#sendto_server(ftype, req)
+enddef
+
+def lsp#add_file(fname: string, ftype: string): void
+  if fname == '' || ftype == '' || !lsp_servers->has_key(ftype)
+    return
+  endif
+  if !lsp_servers[ftype].running
+    lsp#start_server(ftype)
+  endif
+  lsp#textdoc_didopen(fname, ftype)
+enddef
+
+def lsp#remove_file(fname: string, ftype: string): void
+  if fname == '' || ftype == '' || !lsp_servers->has_key(ftype)
+    return
+  endif
+  if !lsp_servers[ftype].running
+    lsp#start_server(ftype)
+  endif
+  lsp#textdoc_didclose(fname, ftype)
+enddef
+
+def lsp#stop_all_servers()
+  for [ftype, server] in items(lsp_servers)
+    if server.running
+      lsp#stop_server(ftype)
+    endif
+  endfor
+enddef
+
+def lsp#add_server(ftype: string, serverpath: string, args: list<string>)
+  var sinfo = {
+    'path': serverpath,
+    'args': args,
+    'running': v:false,
+    'job': v:none,
+    'data': '',
+    'nextID': 1,
+    'requests': {}    # outstanding LSP requests
+  }
+  lsp_servers->extend({[ftype]: sinfo})
+enddef
+
+def lsp#show_servers()
+  echomsg lsp_servers
+enddef
+
+# vim: shiftwidth=2 sts=2 expandtab
diff --git a/doc/lsp.txt b/doc/lsp.txt
new file mode 100644 (file)
index 0000000..e69de29
diff --git a/plugin/lsp.vim b/plugin/lsp.vim
new file mode 100644 (file)
index 0000000..442a724
--- /dev/null
@@ -0,0 +1,14 @@
+vim9script
+
+# LSP plugin for vim9
+
+autocmd BufRead * call lsp#add_file(expand('<afile>:p'), &filetype)
+autocmd BufWipeOut * call lsp#remove_file(expand('<afile>:p'), &filetype)
+
+# TODO: Is it needed to shutdown all the LSP servers when exiting Vim?
+# This takes some time.
+#autocmd VimLeavePre * call lsp#stop_all_servers()
+
+command! -nargs=0 LspGotoDefinition call lsp#goto_definition(expand('%:p'), &filetype, line('.') - 1, col('.') - 1)
+command! -nargs=0 LspGotoDeclaration call lsp#goto_declaration(expand('%:p'), &filetype, line('.') - 1, col('.') - 1)
+