aboutsummaryrefslogtreecommitdiff
path: root/runtime/doc/lsp-extension.txt
blob: fe72e9eb18d5a349e78e0b2a5820346806750cd2 (plain) (blame)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
*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: >vim
       :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.

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

  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 ""
    end)
    return result, is_changed
  end

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

  -- 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
    end
    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
      end
      -- If we can't ascend further, then stop looking.
      if not did_change then
        return nil
      end
    end
  end

  -- 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
      return
    end
    -- 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'))
    end)
    -- 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
    end
    -- 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)
  end

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

 vim:tw=78:ts=8:ft=help:norl: