diff --git a/config/lsp/ember_ls.lua b/config/lsp/ember_ls.lua new file mode 100644 index 0000000..aacd89d --- /dev/null +++ b/config/lsp/ember_ls.lua @@ -0,0 +1,4 @@ +---@type vim.lsp.Config +return { + filetypes = { "markdown", "typst" }, +} diff --git a/config/lua/ivy/config/lsp.lua b/config/lua/ivy/config/lsp.lua index 37135e8..3f6c33d 100644 --- a/config/lua/ivy/config/lsp.lua +++ b/config/lua/ivy/config/lsp.lua @@ -10,6 +10,7 @@ vim.lsp.enable({ "cssls", "denols", "dockerls", + "ember_ls", "emmet_language_server", "gopls", "hls", diff --git a/config/lua/ivy/config/options.lua b/config/lua/ivy/config/options.lua index e970eb0..0e8bdbe 100644 --- a/config/lua/ivy/config/options.lua +++ b/config/lua/ivy/config/options.lua @@ -47,7 +47,7 @@ vim.o.pumheight = 8 vim.o.pummaxwidth = 42 vim.opt.wildoptions = { "pum", "tagfile" } vim.opt.wildmode = { "list", "full" } -vim.o.complete = "." +vim.o.complete = "" vim.opt.completeopt = { "menuone", "noinsert" } vim.o.autocomplete = true vim.opt.shortmess:append("c") -- don't show autocomplete messages diff --git a/config/plugin/ember_ls.lua b/config/plugin/ember_ls.lua new file mode 100644 index 0000000..551347a --- /dev/null +++ b/config/plugin/ember_ls.lua @@ -0,0 +1,161 @@ +if vim.g.loaded_ember then + return +end + +vim.g.loaded_ember = true + +---@type table +local handlers = {} +local ms = vim.lsp.protocol.Methods + +local chars = {} +for i = 32, 126 do + table.insert(chars, string.char(i)) +end + +---@type lsp.InitializeResult +local initializeResult = { + capabilities = { + hoverProvider = true, + definitionProvider = true, + referencesProvider = true, + completionProvider = { + triggerCharacters = chars, + }, + }, + serverInfo = { + name = "ember_ls", + version = "0.0.1", + }, +} + +handlers[ms.initialize] = function(_, callback) + callback(nil, initializeResult) +end + +-- hover ====================================================================== + +---@param _ lsp.HoverParams +---@param callback fun(err?: lsp.ResponseError, result: lsp.Hover) +handlers[ms.textDocument_hover] = function(_, callback) + local word = vim.fn.expand("") + local url_format = "https://api.dictionaryapi.dev/api/v2/entries/en/%s" + + vim.system( + { "curl", url_format:format(word) }, + vim.schedule_wrap(function(out) + local contents + if out.code ~= 0 then + contents = "word fetch failed" + else + local ok, decoded = pcall(vim.json.decode, out.stdout) + if ok and decoded and decoded[1] then + contents = decoded[1].meanings[1].definitions[1].definition + else + contents = decoded.message -- this api gives a nice message if no result + end + end + callback(nil, { contents = contents }) + end) + ) +end + +-- completion ================================================================= + +---@param params lsp.CompletionParams +---@return string word_to_complete +local get_word_to_complete = function(params) + local col = params.position.character + 1 + local line = vim.api.nvim_get_current_line() + local line_to_cursor = line:sub(1, col) + local regex = vim.regex("\\k*$") + + return line:sub(regex:match_str(line_to_cursor) + 1, col) +end + +---@return vim.lsp.inline_completion.Item[] +local function completion_buffer(params) + local word = get_word_to_complete(params) + + local lines = vim.api.nvim_buf_get_lines(0, 0, -1, false) + local matches = vim.fn.matchstrlist(lines, [[\<\k\+\>]]) + vim.list.unique(matches, function(x) + ---@diagnostic disable-next-line: undefined-field + return x.text + end) + matches = vim.fn.matchfuzzy(matches, word, { key = "text" }) + return vim + .iter(matches) + :map(function(v) + -- dont match current word + if v.text == word then + return + end + return { + label = v.text, + kind = vim.lsp.protocol.CompletionItemKind["Text"], + } + end) + :totable() +end + +local get_spell_candidates = vim.func._memoize("concat", function(w) + local entries = vim.fn.spellsuggest(w) + entries = vim.fn.matchfuzzy(entries, w, { limit = 5 }) + + return vim + .iter(entries) + :map(function(v) + if string.find(v, "[ ']") ~= nil then + return + end + return { + label = v, + kind = vim.lsp.protocol.CompletionItemKind["Text"], + } + end) + :totable() +end) + +---@return vim.lsp.inline_completion.Item[] +local function completion_spell(params) + local word = get_word_to_complete(params) + + return get_spell_candidates(word) +end + +---@param params lsp.CompletionParams +---@param callback fun(err?: lsp.ResponseError, result: lsp.CompletionItem[]) +handlers[ms.textDocument_completion] = function(params, callback) + local items = {} + if vim.wo.spell then + vim.list_extend(items, completion_spell(params)) + end + vim.list_extend(items, completion_buffer(params)) + + callback(nil, { + items = items, + isIncomplete = #items > 0, + }) +end + +-- init ======================================================================= + +---@type vim.lsp.ClientConfig +local cfg = { + name = "ember_ls", + cmd = function() + return { + request = function(method, params, callback) + if handlers[method] then + handlers[method](params, callback) + end + end, + notify = function() end, + is_closing = function() end, + terminate = function() end, + } + end, +} + +vim.lsp.config("ember_ls", cfg)