Skip to main content

Overview

EmmyLua Analyzer integrates seamlessly with Neovim’s native LSP client, providing powerful Lua language support with minimal configuration. Enjoy intelligent completions, diagnostics, and navigation in your favorite terminal editor.
Prerequisites:
  • Neovim 0.8 or higher (0.9+ recommended)
  • EmmyLua Analyzer installed (cargo install emmylua_ls)
  • Optional: nvim-lspconfig plugin

Installation Methods

1

Install nvim-lspconfig

Add nvim-lspconfig to your plugin manager:
{
  'neovim/nvim-lspconfig',
  config = function()
    -- Configuration will go here
  end
}
2

Configure EmmyLua LSP

Add to your init.lua:
init.lua
require('lspconfig').emmylua_ls.setup({
  cmd = { 'emmylua_ls' },
  filetypes = { 'lua' },
  root_dir = require('lspconfig.util').root_pattern(
    '.emmyrc.json',
    '.luarc.json',
    '.git'
  ),
  settings = {
    Lua = {
      runtime = {
        version = 'LuaLatest',
      },
      diagnostics = {
        enable = true,
        globals = { 'vim' },
      },
      completion = {
        enable = true,
        callSnippet = 'Replace',
      },
      hint = {
        enable = true,
        paramHint = true,
      },
    },
  },
})
3

Set Up Key Mappings

Add LSP keybindings:
init.lua
vim.api.nvim_create_autocmd('LspAttach', {
  group = vim.api.nvim_create_augroup('UserLspConfig', {}),
  callback = function(ev)
    local opts = { buffer = ev.buf }
    vim.keymap.set('n', 'gD', vim.lsp.buf.declaration, opts)
    vim.keymap.set('n', 'gd', vim.lsp.buf.definition, opts)
    vim.keymap.set('n', 'K', vim.lsp.buf.hover, opts)
    vim.keymap.set('n', 'gi', vim.lsp.buf.implementation, opts)
    vim.keymap.set('n', '<C-k>', vim.lsp.buf.signature_help, opts)
    vim.keymap.set('n', '<space>rn', vim.lsp.buf.rename, opts)
    vim.keymap.set('n', '<space>ca', vim.lsp.buf.code_action, opts)
    vim.keymap.set('n', 'gr', vim.lsp.buf.references, opts)
    vim.keymap.set('n', '<space>f', function()
      vim.lsp.buf.format { async = true }
    end, opts)
  end,
})

Method 2: Native LSP (Neovim 0.10+)

1

Enable EmmyLua LSP

Neovim 0.10+ includes simplified LSP configuration:
init.lua
vim.lsp.enable('emmylua_ls')
2

Configure Settings (Optional)

Customize LSP behavior:
init.lua
vim.lsp.config.emmylua_ls = {
  cmd = { 'emmylua_ls' },
  filetypes = { 'lua' },
  root_markers = { '.emmyrc.json', '.git' },
  settings = {
    Lua = {
      runtime = { version = 'LuaLatest' },
      diagnostics = { globals = { 'vim' } },
    },
  },
}

Method 3: Manual LSP Setup

1

Configure LSP Manually

For full control without plugins:
init.lua
local lsp_cmd = { 'emmylua_ls' }

vim.api.nvim_create_autocmd('FileType', {
  pattern = 'lua',
  callback = function()
    vim.lsp.start({
      name = 'emmylua_ls',
      cmd = lsp_cmd,
      root_dir = vim.fs.dirname(
        vim.fs.find({ '.emmyrc.json', '.git' }, { upward = true })[1]
      ),
      settings = {
        Lua = {
          runtime = { version = 'LuaLatest' },
          diagnostics = { enable = true },
        },
      },
    })
  end,
})

Configuration

Complete Configuration Example

Here’s a full configuration with all features enabled:
init.lua
require('lspconfig').emmylua_ls.setup({
  cmd = { 'emmylua_ls' },
  filetypes = { 'lua' },
  
  -- Auto-detect project root
  root_dir = require('lspconfig.util').root_pattern(
    '.emmyrc.json',
    '.luarc.json',
    '.git'
  ),
  
  -- Server settings
  settings = {
    Lua = {
      runtime = {
        version = 'LuaLatest',
        path = {
          '?.lua',
          '?/init.lua',
        },
      },
      
      diagnostics = {
        enable = true,
        globals = { 'vim', 'love', 'awesome' },
        disable = {},
      },
      
      completion = {
        enable = true,
        callSnippet = 'Replace',
        autoRequire = true,
      },
      
      hint = {
        enable = true,
        paramHint = true,
        localHint = true,
      },
      
      hover = {
        enable = true,
      },
      
      workspace = {
        library = {
          vim.env.VIMRUNTIME,
          '${3rd}/love2d/library',
        },
        ignoreDir = { 'build', 'dist' },
      },
      
      semanticTokens = {
        enable = true,
      },
    },
  },
  
  -- Capabilities for nvim-cmp integration
  capabilities = require('cmp_nvim_lsp').default_capabilities(),
})

Neovim-Specific Configuration

For Neovim Lua development, add this configuration:
init.lua
require('lspconfig').emmylua_ls.setup({
  settings = {
    Lua = {
      runtime = {
        version = 'LuaJIT', -- Neovim uses LuaJIT
      },
      diagnostics = {
        globals = {
          'vim',
          'describe',
          'it',
          'before_each',
          'after_each',
        },
      },
      workspace = {
        library = {
          vim.env.VIMRUNTIME,
          vim.fn.stdpath('config'),
          vim.fn.stdpath('data') .. '/lazy',
        },
        checkThirdParty = false,
      },
    },
  },
})

Project Configuration File

Create .emmyrc.json in your project root:
.emmyrc.json
{
  "$schema": "https://raw.githubusercontent.com/EmmyLuaLs/emmylua-analyzer-rust/refs/heads/main/crates/emmylua_code_analysis/resources/schema.json",
  "runtime": {
    "version": "Lua5.4"
  },
  "diagnostics": {
    "enable": true,
    "globals": ["myFramework"]
  },
  "workspace": {
    "library": ["./libs"],
    "ignoreDir": ["build", "test"]
  }
}

Key Mappings

init.lua
local function on_attach(client, bufnr)
  local opts = { buffer = bufnr, noremap = true, silent = true }
  
  -- Navigation
  vim.keymap.set('n', 'gd', vim.lsp.buf.definition, opts)
  vim.keymap.set('n', 'gD', vim.lsp.buf.declaration, opts)
  vim.keymap.set('n', 'gi', vim.lsp.buf.implementation, opts)
  vim.keymap.set('n', 'gr', vim.lsp.buf.references, opts)
  vim.keymap.set('n', 'gt', vim.lsp.buf.type_definition, opts)
  
  -- Information
  vim.keymap.set('n', 'K', vim.lsp.buf.hover, opts)
  vim.keymap.set('n', '<C-k>', vim.lsp.buf.signature_help, opts)
  
  -- Refactoring
  vim.keymap.set('n', '<leader>rn', vim.lsp.buf.rename, opts)
  vim.keymap.set('n', '<leader>ca', vim.lsp.buf.code_action, opts)
  
  -- Formatting
  vim.keymap.set('n', '<leader>f', function()
    vim.lsp.buf.format({ async = true })
  end, opts)
  
  -- Diagnostics
  vim.keymap.set('n', '[d', vim.diagnostic.goto_prev, opts)
  vim.keymap.set('n', ']d', vim.diagnostic.goto_next, opts)
  vim.keymap.set('n', '<leader>e', vim.diagnostic.open_float, opts)
  vim.keymap.set('n', '<leader>q', vim.diagnostic.setloclist, opts)
  
  -- Workspace
  vim.keymap.set('n', '<leader>wa', vim.lsp.buf.add_workspace_folder, opts)
  vim.keymap.set('n', '<leader>wr', vim.lsp.buf.remove_workspace_folder, opts)
  vim.keymap.set('n', '<leader>wl', function()
    print(vim.inspect(vim.lsp.buf.list_workspace_folders()))
  end, opts)
end

require('lspconfig').emmylua_ls.setup({
  on_attach = on_attach,
  -- other settings...
})

Integration with Completion Plugins

nvim-cmp

init.lua
local cmp = require('cmp')
local lspconfig = require('lspconfig')

cmp.setup({
  sources = {
    { name = 'nvim_lsp' },
    { name = 'buffer' },
    { name = 'path' },
  },
  snippet = {
    expand = function(args)
      require('luasnip').lsp_expand(args.body)
    end,
  },
})

local capabilities = require('cmp_nvim_lsp').default_capabilities()

lspconfig.emmylua_ls.setup({
  capabilities = capabilities,
  -- other settings...
})

coq.nvim

init.lua
local lspconfig = require('lspconfig')
local coq = require('coq')

lspconfig.emmylua_ls.setup(coq.lsp_ensure_capabilities({
  -- your settings...
}))

Advanced Configuration

Multiple Lua Versions per Project

Use different configurations based on file location:
init.lua
local function get_lua_version(fname)
  if fname:match('neovim') then
    return 'LuaJIT'
  elseif fname:match('love2d') then
    return 'LuaJIT'
  else
    return 'Lua5.4'
  end
end

require('lspconfig').emmylua_ls.setup({
  on_new_config = function(config, root_dir)
    config.settings.Lua.runtime.version = get_lua_version(root_dir)
  end,
})

Custom Commands

Add custom LSP commands:
init.lua
vim.api.nvim_create_user_command('LspRestart', function()
  vim.cmd('LspStop emmylua_ls')
  vim.cmd('LspStart emmylua_ls')
end, {})

vim.api.nvim_create_user_command('LspLog', function()
  vim.cmd('edit ' .. vim.lsp.get_log_path())
end, {})

Troubleshooting

Solution:
  1. Check if emmylua_ls is in PATH:
    which emmylua_ls
    
  2. Verify LSP is running:
    :LspInfo
    
  3. Check LSP logs:
    :lua vim.cmd('edit ' .. vim.lsp.get_log_path())
    
  4. Restart the LSP:
    :LspRestart
    
Solution:
  1. Verify LSP is attached:
    :LspInfo
    
  2. Check completion settings:
    require('lspconfig').emmylua_ls.setup({
      settings = {
        Lua = {
          completion = { enable = true },
        },
      },
    })
    
  3. Ensure nvim-cmp is configured correctly
  4. Trigger completion manually: Ctrl+X Ctrl+O (omni-completion)
Solution:
  1. Enable diagnostics:
    vim.diagnostic.config({
      virtual_text = true,
      signs = true,
      update_in_insert = false,
    })
    
  2. Check diagnostic settings:
    require('lspconfig').emmylua_ls.setup({
      settings = {
        Lua = {
          diagnostics = { enable = true },
        },
      },
    })
    
  3. View all diagnostics:
    :lua vim.diagnostic.setloclist()
    
Solution:
  1. Ignore unnecessary directories:
    settings = {
      Lua = {
        workspace = {
          ignoreDir = { 'node_modules', 'build', '.git' },
        },
      },
    }
    
  2. Reduce workspace library size
  3. Disable features you don’t need:
    settings = {
      Lua = {
        codeLens = { enable = false },
        semanticTokens = { enable = false },
      },
    }
    
  4. Increase timeout:
    vim.lsp.set_log_level('warn')
    
Solution:Add ‘vim’ to diagnostics globals:
settings = {
  Lua = {
    diagnostics = {
      globals = { 'vim' },
    },
  },
}
Or add Neovim runtime to workspace library:
settings = {
  Lua = {
    workspace = {
      library = { vim.env.VIMRUNTIME },
    },
  },
}

Example init.vim Configuration

For Vimscript users:
init.vim
" Install nvim-lspconfig with your plugin manager

lua << EOF
require('lspconfig').emmylua_ls.setup({
  settings = {
    Lua = {
      runtime = { version = 'LuaLatest' },
      diagnostics = { globals = { 'vim' } },
    },
  },
})

-- Keybindings
vim.api.nvim_create_autocmd('LspAttach', {
  callback = function(args)
    local opts = { buffer = args.buf }
    vim.keymap.set('n', 'gd', vim.lsp.buf.definition, opts)
    vim.keymap.set('n', 'K', vim.lsp.buf.hover, opts)
    vim.keymap.set('n', '<leader>rn', vim.lsp.buf.rename, opts)
  end,
})
EOF

Next Steps

Configuration

Explore advanced configuration options

Annotations

Learn about type annotations

Features

Discover all LSP features

Performance

Optimize for large projects

Build docs developers (and LLMs) love