lsp-server.lua 7.3 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319
  1. local uv = require('luv')
  2. local json = require('dkjson')
  3. -- arg[1], project path
  4. -- arg[2], language id
  5. local pathToLspFiles = "/tmp/"
  6. local projectRoot = arg[1]
  7. local langId = arg[2]
  8. local function pickLsp(lid)
  9. switch = {
  10. ["c"] = function()
  11. return "ccls", {'-log-file=/tmp/ccls2.log', '-init={}'}
  12. end,
  13. ["h"] = function()
  14. return "ccls", {'-log-file=/tmp/ccls2.log', '-init={}'}
  15. end,
  16. ["hs"] = function()
  17. return "haskell-language-server-9.6.5", {"--lsp"}
  18. end,
  19. ["kt"] = function()
  20. return "kotlin-language-server", {}
  21. end,
  22. ["default"] = function()
  23. return nil, nil
  24. end
  25. }
  26. local v, a = (switch[lid] or switch["default"])()
  27. return v, a
  28. end
  29. local lspCmd, lspArgs = pickLsp(langId)
  30. if lspCmd == nil then
  31. os.exit(1)
  32. end
  33. local function watch_file(filename, callback)
  34. local last_modified = uv.fs_stat(filename).mtime.sec
  35. local timer = uv.new_timer()
  36. timer:start(1000, 1000, function()
  37. local current_modified = uv.fs_stat(filename).mtime.sec
  38. if current_modified ~= last_modified then
  39. callback()
  40. last_modified = current_modified
  41. end
  42. end)
  43. return timer
  44. end
  45. local function run_lsp_client()
  46. local stdin_pipe = uv.new_pipe()
  47. local stdout_pipe = uv.new_pipe()
  48. local stderr_pipe = uv.new_pipe()
  49. local stopper = uv.new_timer()
  50. local file = io.open(pathToLspFiles .. ".ait_lsp_log", "w")
  51. file:write("")
  52. file:close();
  53. local handle, pid = uv.spawn(lspCmd, {
  54. args = lspArgs,
  55. stdio = {stdin_pipe, stdout_pipe, stderr_pipe}
  56. }, function(code, signal)
  57. print("Process exited with code " .. code .. " and signal " .. signal)
  58. stdin_pipe:close()
  59. stdout_pipe:close()
  60. stderr_pipe:close()
  61. end)
  62. if not handle then
  63. print("Failed to spawn LSP process")
  64. return
  65. end
  66. stderr_pipe:read_start(function(err, data)
  67. if err then
  68. print("stderr error:", err)
  69. return
  70. end
  71. if data then
  72. print("stderr:", data)
  73. end
  74. end)
  75. local function send_request(request)
  76. local payload = json.encode(request)
  77. local request_str = string.format("Content-Length: %d\r\n\r\n%s", #payload, payload)
  78. stdin_pipe:write(request_str)
  79. end
  80. local function lsp_cmd(vars)
  81. local filePath
  82. local line
  83. local col
  84. local command
  85. if #vars > 3 then
  86. command = vars[1]
  87. filePath = vars[2]
  88. line = tonumber(vars[3]) - 1
  89. col = tonumber(vars[4]) - 1
  90. else
  91. command = "c"
  92. filePath = vars[1]
  93. line = tonumber(vars[2]) - 1
  94. col = tonumber(vars[3]) - 1
  95. end
  96. local fp = io.open(filePath)
  97. local contents = fp:read("*all");
  98. fp:close()
  99. local didOpen_request = {
  100. jsonrpc = "2.0",
  101. method = "textDocument/didOpen",
  102. params = {
  103. textDocument = {
  104. uri = "file://" .. filePath,
  105. languageId = langId,
  106. version = 1,
  107. text = contents
  108. }
  109. }
  110. }
  111. send_request(didOpen_request)
  112. local documentSymbol_request = {
  113. jsonrpc = "2.0",
  114. method = "textDocument/documentSymbol",
  115. id = 4,
  116. params = {
  117. textDocument = {
  118. uri = "file://" .. filePath,
  119. }
  120. }
  121. }
  122. send_request(documentSymbol_request)
  123. local request
  124. if command == "c" then
  125. request = {
  126. jsonrpc = "2.0",
  127. id = 5,
  128. method = "textDocument/completion",
  129. params = {
  130. textDocument = {
  131. uri = "file://" .. filePath
  132. },
  133. position = {
  134. line = line,
  135. character = col
  136. }
  137. }
  138. }
  139. elseif command == "h" then
  140. request = {
  141. jsonrpc = "2.0",
  142. id = 5,
  143. method = "textDocument/hover",
  144. params = {
  145. textDocument = {
  146. uri = "file://" .. filePath
  147. },
  148. position = {
  149. line = line,
  150. character = col
  151. }
  152. }
  153. }
  154. elseif command == "a" then
  155. local eline = tonumber(vars[4]) - 1
  156. local ecol = tonumber(vars[5]) - 1
  157. request = {
  158. jsonrpc = "2.0",
  159. id = 5,
  160. method = "textDocument/codeAction",
  161. params = {
  162. textDocument = {
  163. uri = "file://" .. filePath
  164. },
  165. range = {
  166. start = {
  167. line = line,
  168. character = col
  169. },
  170. ["end"] = {
  171. line = eline,
  172. character = ecol
  173. }
  174. },
  175. context = {
  176. diagnostics = {
  177. range = {
  178. start = {
  179. line = line, character = col
  180. },
  181. ["end"] = {
  182. line = eline, character = ecol
  183. }
  184. },
  185. severity = 1,
  186. },
  187. only = { "quickfix", "refactor" },
  188. triggerKind = 1
  189. }
  190. }
  191. }
  192. end
  193. send_request(request)
  194. end
  195. local buffer = ""
  196. stdout_pipe:read_start(function(err, data)
  197. file = io.open(pathToLspFiles .. ".ait_lsp_log", "a")
  198. file:write(data .. "\n")
  199. print(data)
  200. if err then
  201. print("stdout error:", err)
  202. return
  203. end
  204. local count = tonumber(data:match("Content%-Length: (%d+)"))
  205. local js = nil
  206. local start_index = data:find("{")
  207. if start_index and count ~= nil then
  208. js = data:sub(start_index)
  209. if js:find("result") then
  210. buffer = js
  211. -- else
  212. -- buffer = ""
  213. end
  214. end
  215. if buffer:match("result") then
  216. if js == nil then
  217. buffer = buffer .. data
  218. end
  219. local parsed = json.decode(buffer)
  220. if parsed then
  221. if parsed.result and parsed.result.items then
  222. local opts = io.open(pathToLspFiles .. ".ait_lsp_options", "w")
  223. for _, item in ipairs(parsed.result.items) do
  224. local str = item.label .. "\t" .. json.encode(item) .. "\n"
  225. opts:write(str)
  226. end
  227. opts:close()
  228. end
  229. buffer = ""
  230. end
  231. end
  232. file:close()
  233. end)
  234. local timer = uv.new_timer()
  235. timer:start(10000, 0, function()
  236. local initialize_request = {
  237. jsonrpc = "2.0",
  238. id = 1,
  239. method = "initialize",
  240. params = {
  241. processId = pid,
  242. rootPath = projectRoot,
  243. rootUri = "file://" .. projectRoot,
  244. capabilities = {
  245. textDocument = {
  246. completion = {
  247. completionItem = {
  248. snippetSupport = true
  249. }
  250. }
  251. },
  252. hover = {},
  253. codeAction = {
  254. dynamicRegistration = true,
  255. codeActionKinds = {
  256. "quickfix",
  257. "refactor",
  258. "refactor.extract",
  259. "refactor.inline",
  260. "refactor.rewrite",
  261. "source",
  262. "source.organizeImports"
  263. }
  264. }
  265. }
  266. }
  267. }
  268. send_request(initialize_request)
  269. local initialized_notification = {
  270. jsonrpc = "2.0",
  271. method = "initialized",
  272. params = {}
  273. }
  274. send_request(initialized_notification)
  275. timer:stop()
  276. end)
  277. local timer = watch_file(pathToLspFiles .. ".ait_lsp_cmd", function()
  278. local f = io.open(pathToLspFiles .. ".ait_lsp_cmd", "r")
  279. local cmd = f:read("*a")
  280. f:close()
  281. local vars = {}
  282. for word in string.gmatch(cmd, "%S+") do
  283. table.insert(vars, word)
  284. end
  285. lsp_cmd(vars)
  286. end)
  287. uv.run()
  288. end
  289. run_lsp_client()