Quick links: help overview · quick reference · user manual toc · reference manual toc
lsp-extension.txt   LSP Extension

                            NVIM REFERENCE MANUAL

The vim.lsp Lua module is a framework for building LSP plugins.

  1. Start with vim.lsp.start_client() and vim.lsp.buf_attach_client().
  2. Peek at the API: 
       :lua print(vim.inspect(vim.lsp))
  3. See lsp-extension-example for a full example.

LSP EXAMPLE                                            lsp-extension-example

This example is for plugin authors or users who want a lot of control. If you
are just getting started see lsp-quickstart.

For more advanced configurations where just filtering by filetype isn't
sufficient, you can use the vim.lsp.start_client() and
vim.lsp.buf_attach_client() commands to easily customize the configuration
however you please. For example, if you want to do your own filtering, or
start a new LSP client based on the root directory for working with multiple
projects in a single session. To illustrate, the following is a fully working
Lua example.

The example will:
1. Check for each new buffer whether or not we want to start an LSP client.
2. Try to find a root directory by ascending from the buffer's path.
3. Create a new LSP for that root directory if one doesn't exist.
4. Attach the buffer to the client for that root directory.

  -- Some path manipulation utilities
  local function is_dir(filename)
    local stat = vim.loop.fs_stat(filename)
    return stat and stat.type == 'directory' or false

  local path_sep = vim.loop.os_uname().sysname == "Windows" and "\\" or "/"
  -- Assumes filepath is a file.
  local function dirname(filepath)
    local is_changed = false
    local result = filepath:gsub(path_sep.."([^"..path_sep.."]+)$", function()
      is_changed = true
      return ""
    return result, is_changed

  local function path_join(...)
    return table.concat(vim.tbl_flatten {...}, path_sep)

  -- Ascend the buffer's path until we find the rootdir.
  -- is_root_path is a function which returns bool
  local function buffer_find_root_dir(bufnr, is_root_path)
    local bufname = vim.api.nvim_buf_get_name(bufnr)
    if vim.fn.filereadable(bufname) == 0 then
      return nil
    local dir = bufname
    -- Just in case our algorithm is buggy, don't infinite loop.
    for _ = 1, 100 do
      local did_change
      dir, did_change = dirname(dir)
      if is_root_path(dir, bufname) then
        return dir, bufname
      -- If we can't ascend further, then stop looking.
      if not did_change then
        return nil

  -- A table to store our root_dir to client_id lookup. We want one LSP per
  -- root directory, and this is how we assert that.
  local javascript_lsps = {}
  -- Which filetypes we want to consider.
  local javascript_filetypes = {
    ["javascript.jsx"] = true;
    ["javascript"]     = true;
    ["typescript"]     = true;
    ["typescript.jsx"] = true;

  -- Create a template configuration for a server to start, minus the root_dir
  -- which we will specify later.
  local javascript_lsp_config = {
    name = "javascript";
    cmd = { path_join(os.getenv("JAVASCRIPT_LANGUAGE_SERVER_DIRECTORY"), "lib", "language-server-stdio.js") };

  -- This needs to be global so that we can call it from the autocmd.
  function check_start_javascript_lsp()
    local bufnr = vim.api.nvim_get_current_buf()
    -- Filter which files we are considering.
    if not javascript_filetypes[vim.api.nvim_buf_get_option(bufnr, 'filetype')] then
    -- Try to find our root directory. We will define this as a directory which contains
    -- node_modules. Another choice would be to check for `package.json`, or for `.git`.
    local root_dir = buffer_find_root_dir(bufnr, function(dir)
      return is_dir(path_join(dir, 'node_modules'))
      -- return vim.fn.filereadable(path_join(dir, 'package.json')) == 1
      -- return is_dir(path_join(dir, '.git'))
    -- We couldn't find a root directory, so ignore this file.
    if not root_dir then return end

    -- Check if we have a client already or start and store it.
    local client_id = javascript_lsps[root_dir]
    if not client_id then
      local new_config = vim.tbl_extend("error", javascript_lsp_config, {
        root_dir = root_dir;
      client_id = vim.lsp.start_client(new_config)
      javascript_lsps[root_dir] = client_id
    -- Finally, attach to the buffer to track changes. This will do nothing if we
    -- are already attached.
    vim.lsp.buf_attach_client(bufnr, client_id)

  vim.api.nvim_command [[autocmd BufReadPost * lua check_start_javascript_lsp()]]


Quick links: help overview · quick reference · user manual toc · reference manual toc