Is there a way to autocomplete tags of Components that I would import in React JS/TS?

Simply put, I’d like to be able to begin typing the name of a component from somewhere else in a project and have it autocomplete from within TSX/JSX (specifically the embedded html part). Does anyone have a working configuration in which they’ve achieved this?

All I can get working are the emmet completions inside of the html:

Sharing the config is kind of weird on this one because I use Nix to bundle together the lua and plugins that I use in my neovim config.

Here’s the repo

And this is the full config as it would be built (and slightly cleaned up by hand for posting here):

require("catppuccin").setup({
  flavour = "mocha",
})

local status, typescripttools = pcall(require, "typescript-tools")
if (not status) then return end

local status, whichKey = pcall(require, 'which-key')
if (not status) then return end
whichKey.setup {}

local options = {
  encoding = "utf-8",
  mouse = "a",
  tabstop = 2,
  shiftwidth = 2,
  softtabstop = 2,
  expandtab = true,
  cmdheight = 1,
  updatetime = 300,
  tm = 500,
  hidden = true,
  splitbelow = true,
  splitright = true,
  signcolumn = "yes",
  autoindent = true,
  swapfile = false,
  backup = false,
  writebackup = false,
  visualbell = true,
  errorbells = true,
  relativenumber = true,
  number = true,
  wrap = false,
  termguicolors = true,
  hlsearch = true,
  incsearch = false,

}
for k, v in pairs(options) do
  vim.opt[k] = v
end

vim.opt.clipboard:append("unnamedplus")


vim.cmd("syntax on")


vim.cmd("au BufNewFile,BufRead *.md set spell")


vim.g.mapleader = " "
vim.g.maplocalleader = " "

local status, formatter = pcall(require, "formatter")
if (not status) then return end

local status, cmp = pcall(require, "cmp")
if (not status) then return end

local status, lspkind = pcall(require, "lspkind")
if (not status) then return end

local tsBuiltin = require('telescope.builtin')

local status, autopairs = pcall(require, "nvim-autopairs")
if (not status) then return end

local status, APRule = pcall(require, "nvim-autopairs.rule")
if (not status) then return end

local status, APcond = pcall(require, "nvim-autopairs.conds")
if (not status) then return end

local status, colorizer = pcall(require, "colorizer")
if (not status) then return end

local status, lspsaga = pcall(require, 'lspsaga')
if (not status) then return end
lspsaga.setup({
  lightbulb = {
    enable = false
  },
  diagnostic = {
    show_code_action = false
  }
})

local status, startup = pcall(require, "startup")
if (not status) then return end

local status, luasnip = pcall(require, "luasnip")
if (not status) then return end

local status, reactSnippets = pcall(require, "vim-react-snippets")
if (not status) then return end

local status, lualine = pcall(require, "lualine")
if (not status) then return end

-- Who's gonna carry the boats?

local status, noice = pcall(require, 'noice')
if (not status) then return end

local status, notify = pcall(require, 'notify')
if (not status) then return end

local status, glow = pcall(require, "glow")
if (not status) then return end

local attach_keymaps = function(client, bufnr)
  vim.api.nvim_buf_set_keymap(bufnr, "n", "<leader>lca", "<cmd>lua vim.lsp.buf.code_action()<CR>", {
    noremap = true,
    desc = "Code action",

    silent = true

  })

  vim.api.nvim_buf_set_keymap(bufnr, "n", "<leader>lgD", "<cmd>lua vim.lsp.buf.declaration()<CR>", {
    noremap = true,
    desc = "Goto declaration",

    silent = true

  })

  vim.api.nvim_buf_set_keymap(bufnr, "n", "<leader>lgd", "<cmd>lua vim.lsp.buf.definition()<CR>", {
    noremap = true,
    desc = "Goto definition",

    silent = true

  })

  vim.api.nvim_buf_set_keymap(bufnr, "n", "<leader>lgi", "<cmd>lua vim.lsp.buf.implementation()<CR>", {
    noremap = true,
    desc = "Goto implementation",

    silent = true

  })

  vim.api.nvim_buf_set_keymap(bufnr, "n", "<leader>lgn", "<cmd>lua vim.diagnostic.goto_next()<CR>", {
    noremap = true,
    desc = "Goto next diagnostic",

    silent = true

  })

  vim.api.nvim_buf_set_keymap(bufnr, "n", "<leader>lgp", "<cmd>lua vim.diagnostic.goto_prev()<CR>", {
    noremap = true,
    desc = "Goto previous diagnostic",

    silent = true

  })

  vim.api.nvim_buf_set_keymap(bufnr, "n", "<leader>lgr", "<cmd>lua vim.lsp.buf.references()<CR>", {
    noremap = true,
    desc = "Goto references",

    silent = true

  })

  vim.api.nvim_buf_set_keymap(bufnr, "n", "<leader>lgt", "<cmd>lua vim.lsp.buf.type_definition()<CR>", {
    noremap = true,
    desc = "Goto type definition",

    silent = true

  })

  vim.api.nvim_buf_set_keymap(bufnr, "n", "<leader>lh", "<cmd>lua vim.lsp.buf.hover()<CR>", {
    noremap = true,
    desc = "Hover",

    silent = true

  })

  vim.api.nvim_buf_set_keymap(bufnr, "n", "<leader>ln", "<cmd>lua vim.lsp.buf.rename()<CR>", {
    noremap = true,
    desc = "Rename",

    silent = true

  })

  vim.api.nvim_buf_set_keymap(bufnr, "n", "<leader>lsh", "<cmd>lua vim.lsp.buf.signature_help()<CR>", {
    noremap = true,
    desc = "Signature help",

    silent = true

  })

  vim.api.nvim_buf_set_keymap(bufnr, "n", "<leader>lwa", "<cmd>lua vim.lsp.buf.add_workspace_folder()<CR>", {
    noremap = true,
    desc = "Add workspace folder",

    silent = true

  })

  vim.api.nvim_buf_set_keymap(bufnr, "n", "<leader>lwl", "<cmd>lua print(vim.inspect(vim.lsp.buf.list_workspace_folders()))<CR>", {
    noremap = true,
    desc = "List workspace folders",

    silent = true

  })

  vim.api.nvim_buf_set_keymap(bufnr, "n", "<leader>lwr", "<cmd>lua vim.lsp.buf.remove_workspace_folder()<CR>", {
    noremap = true,
    desc = "Remove workspace folder",

    silent = true

  })

  vim.api.nvim_buf_set_keymap(bufnr, "n", "F", "<cmd>lua vim.lsp.buf.format { async = true }<CR>", {
    noremap = true,
    desc = "Format",

    silent = true

  })

end


-- Enable lspconfig
local status, lspconfig = pcall(require, "lspconfig")
if (not status) then return end

local status, util = pcall(require, "lspconfig.util")
if (not status) then return end

local testForLSPBinaryOnPath = function(name, alternate)
  local path = vim.fn.exepath(name)
  if path == "" then
    return alternate
  end
  return path
end

vim.g.formatsave = true
-- Enable formatting
--[[ format_callback = function(client, bufnr)
  vim.api.nvim_create_autocmd("BufWritePre", {
    group = augroup,
    buffer = bufnr,
    callback = function()
      if vim.g.formatsave then
          local params = require'vim.lsp.util'.make_formatting_params({})
          client.request('textDocument/formatting', params, nil, bufnr)
      end
    end
  })
end ]]--

default_on_attach = function(client, bufnr)
  attach_keymaps(client, bufnr)
  -- format_callback(client, bufnr)
end

local capabilities = vim.lsp.protocol.make_client_capabilities()
capabilities = require('cmp_nvim_lsp').default_capabilities(capabilities);


typescripttools.setup {
  on_attach = default_on_attach,
}

-- Python config
lspconfig.pylsp.setup {
  cmd = { testForLSPBinaryOnPath("pylsp", "/nix/store/cadm42vrp8g7r6l1wsw2d9g4rrxrdvxa-python3.11-python-lsp-server-1.12.0/bin/pylsp") }
}

lspconfig.rust_analyzer.setup {
  cmd = { testForLSPBinaryOnPath("rust-analyzer", "/nix/store/0fdrbfqza9p1vwzccy1hb1pdbx55hags-rust-analyzer-2024-09-02/bin/rust-analyzer") },
  settings = {
    ['rust-analyzer'] = {
      diagnostics = {
        enable = false;
      }
    }
  }
}

-- HTML config
lspconfig.emmet_language_server.setup {
  cmd = { testForLSPBinaryOnPath("emmet-language-server", "/nix/store/mfqqqz3fp87aaf1dhjjv9zr7hfqiy0p8-emmet-language-server-2.2.0/bin/emmet-language-server"), "--stdio" }
}

-- Zig config
lspconfig.zls.setup {
    cmd = { testForLSPBinaryOnPath("zls", "/nix/store/93shjdfpz3pzvw9q5wbwsz0wp4v43gir-zls-0.13.0/bin/zls") },
}

-- Nix config
lspconfig.nixd.setup{
  capabilities = capabilities;
  on_attach = function(client, bufnr)
    attach_keymaps(client, bufnr)
  end,
  cmd = { testForLSPBinaryOnPath("nixd", "/nix/store/wwn50pvri8909w78wnzr95spa0zgcj52-nixd-2.3.2/bin/nixd") },
  filetypes = { 'nix' },
  single_file_support = true,
  root_dir = function(fname)
    return util.root_pattern(unpack { '.nixd.json', 'flake.nix' })(fname) or util.find_git_ancestor(fname)
  end,
}

lspconfig.lua_ls.setup {
  capabilities = capabilities,
  on_attach = default_on_attach,
  cmd = { testForLSPBinaryOnPath("lua-language-server", "/nix/store/3fp5s0cydnczsh74ylsqni2c3kiapx4s-lua-language-server-3.10.6/bin/lua-language-server") },
  on_init = function(client)
    local path = client.workspace_folders[1].name
    if not vim.loop.fs_stat(path .. '/.luarc.json') and not vim.loop.fs_stat(path .. '/.luarc.jsonc') then
      client.config.settings = vim.tbl_deep_extend('force', client.config.settings, {
        Lua = {
          runtime = {
            -- Tell the language server which version of Lua you're using
            -- (most likely LuaJIT in the case of Neovim)
            version = 'LuaJIT'
          },
          -- Make the server aware of Neovim runtime files
          workspace = {
            checkThirdParty = false,
            library = {
              vim.env.VIMRUNTIME,
              [vim.fn.expand('$VIMRUNTIME/lua')] = true,
              [vim.fn.expand('$VIMRUNTIME/lua/vim/lsp')] = true
            }
          }
        }
      })
      client.notify("workspace/didChangeConfiguration", { settings = client.config.settings })
    end
    return true
  end
}

-- Go config
lspconfig.gopls.setup {
  cmd = { testForLSPBinaryOnPath("gopls", "/nix/store/6nysag23irns7ldbsdrjwc1ckap1hqm6-gopls-0.16.2/bin/gopls") },
  settings = {
    gopls = {
      analyses = {
        unusedparams = true;
      };
      staticcheck = true;
    };
  };
}





function readAll(file)
  local f = assert(io.open(file, "rb"))
  local content = f:read("*all")
  f:close()
  return content
end
vim.cmd.colorscheme("catppuccin")

-- Treesitter config
require'nvim-treesitter.configs'.setup {
  highlight = {
    enable = true,
  },

  indent = {
    enable = true,
  },

  incremental_selection = {
    enable = true,
  },
}

 -- Utilities for creating configurations
local fmt_util = require "formatter.util"

-- Provides the Format, FormatWrite, FormatLock, and FormatWriteLock commands
formatter.setup {
  -- Enable or disable logging
  logging = true,
  -- Set the log level
  log_level = vim.log.levels.WARN,
  filetype = {
    typescriptreact = {
      require("formatter.filetypes.typescript").prettier,
    },
    typescript = {
      require("formatter.filetypes.typescript").prettier,
    },
    javascriptreact = {
      require("formatter.filetypes.javascript").prettier,
    },
    javascript = {
      require("formatter.filetypes.javascript").prettier,
    },
  }
}

local has_words_before = function()
  unpack = unpack or table.unpack
  local line, col = unpack(vim.api.nvim_win_get_cursor(0))
  return col ~= 0 and vim.api.nvim_buf_get_lines(0, line - 1, line, true)[1]:sub(col, col):match("%s") == nil
end
require("luasnip.loaders.from_snipmate").lazy_load({ paths = { "/nix/store/96hw8jhyrh5mb4iqndhjkv1sjln142br-snippets" } })
cmp.setup({
  snippet = {
    expand = function(args)
      luasnip.lsp_expand(args.body)
    end,
  },
  formatting = {
    format = lspkind.cmp_format({with_text = true, maxwidth = 50})
  },
  completion = {
    completeopt = 'menu,menuone,noinsert',
  },
  mapping = cmp.mapping.preset.insert({
    ["<C-b>"] = cmp.mapping.scroll_docs(-4),
    ["<C-f>"] = cmp.mapping.scroll_docs(4),
    ["<C-Space>"] = cmp.mapping.complete(),
    ["<C-e>"] = cmp.mapping({
      i = cmp.mapping.abort(),
      c = cmp.mapping.close(),
    }),
    ["<CR>"] = cmp.mapping.confirm({ select = true }),
    ["<Tab>"] = cmp.mapping(function(fallback)
      if cmp.visible() then
        cmp.select_next_item()
      -- You could replace the expand_or_jumpable() calls with expand_or_locally_jumpable()
      -- that way you will only jump inside the snippet region
      elseif luasnip.expand_or_jumpable() then
        luasnip.expand_or_jump()
      elseif has_words_before() then
        cmp.complete()
      else
        fallback()
      end
    end, { "i", "s" }),

    ["<S-Tab>"] = cmp.mapping(function(fallback)
      if cmp.visible() then
        cmp.select_prev_item()
      elseif luasnip.jumpable(-1) then
        luasnip.jump(-1)
      else
        fallback()
      end
    end, { "i", "s" }),
  }),
  sources = cmp.config.sources({
    { name = "nvim_lsp" },
    { name = "luasnip" },
    { name = "path" },
  }, {
    { name = "buffer" },
  })
})
cmp.setup.cmdline(':', {
  mapping = cmp.mapping.preset.cmdline(),
  sources = cmp.config.sources({
    { name = 'path' }
  }, {
    { name = 'cmdline' }
  })
})

-- blanko
vim.keymap.set('n', '<leader>ff', tsBuiltin.find_files, { desc = "Find Files" })
vim.keymap.set('n', '<leader>fg', tsBuiltin.live_grep, { desc = "Live Grep" })
vim.keymap.set('n', '<leader>fb', tsBuiltin.buffers, { desc = "Buffers" })
vim.keymap.set('n', '<leader>fh', tsBuiltin.help_tags, { desc = "Help Tags" })

autopairs.setup({
  disable_filetype = { "TelescopePrompt" , "vim" },
})
--[[
autopairs.add_rules({
  APRule("=", "")
    :with_pair(APcond.not_inside_quote())
    :with_pair(function(opts)
        local last_char = opts.line:sub(opts.col - 1, opts.col - 1)
        if last_char:match("[%w%=%s]") then
            return true
        end
        return false
    end)
    :replace_endpair(function(opts)
        local prev_2char = opts.line:sub(opts.col - 2, opts.col - 1)
        local next_char = opts.line:sub(opts.col, opts.col)
        next_char = next_char == " " and "" or " "
        if prev_2char:match("%w$") then
            return "<bs> =" .. next_char
        end
        if prev_2char:match("%=$") then
            return next_char
        end
        if prev_2char:match("=") then
            return "<bs><bs>=" .. next_char
        end
        return ""
    end)
    :set_end_pair_length(0)
    :with_move(APcond.none())
    :with_del(APcond.none())
})
]]

colorizer.setup()

require("true-zen").setup({
  integrations = {
    twilight = true
  }
})

vim.g.loaded_netrw = 1
vim.g.loaded_netrwPlugin = 1
vim.g.loaded_netrwSettings = 1
vim.g.loaded_netrwFileHandlers = 1

require('neo-tree').setup({
  hijack_netrw_behavior = 'open_current',
})

vim.keymap.set({'n', 't'}, '<A-d>', '<cmd>Lspsaga term_toggle<CR>', { desc = "Toggle the FLoating Terminal"})
vim.keymap.set("n", "<C-j>", "<Cmd>Lspsaga diagnostic_jump_next<CR>", {
  noremap = false,
  desc = "Jump to next diagnostic",


})

vim.keymap.set("n", "K", "<Cmd>Lspsaga hover_doc<CR>", {
  noremap = false,
  desc = "Show hover documentation",


})

vim.keymap.set("n", "cA", "<cmd>Lspsaga code_action<CR>", {
  noremap = false,
  desc = "Code action",


})

vim.keymap.set("n", "gd", "<Cmd>Lspsaga finder<CR>", {
  noremap = false,
  desc = "Find references",


})

vim.keymap.set("n", "gp", "<Cmd>Lspsaga preview_definition<CR>", {
  noremap = false,
  desc = "Preview definition",


})

vim.keymap.set("n", "gr", "<Cmd>Lspsaga rename<CR>", {
  noremap = false,
  desc = "Rename",


})

vim.keymap.set("i", "<C-k>", "<Cmd>Lspsaga signature_help<CR>", {
  noremap = false,



})


startup.setup({theme = "dashboard"})

reactSnippets.lazy_load()

require("luasnip.loaders.from_snipmate").lazy_load({ paths = { "/nix/store/96hw8jhyrh5mb4iqndhjkv1sjln142br-snippets" } })

vim.keymap.set({"i"}, "<C-K>", function() luasnip.expand() end, {silent = true})
vim.keymap.set({"i", "s"}, "<C-L>", function() luasnip.jump( 1) end, {silent = true})
vim.keymap.set({"i", "s"}, "<C-J>", function() luasnip.jump(-1) end, {silent = true})

vim.keymap.set({"i", "s"}, "<C-E>", function()
  if luasnip.choice_active() then
    luasnip.change_choice(1)
  end
end, {silent = true})

lualine.setup({
  options = {
    globalstatus = true,
    theme = "catppuccin",
  },
  sections = {
    lualine_x = {
      {
        require("noice").api.status.message.get_hl,
        cond = require("noice").api.status.message.has,
      },
      {
        require("noice").api.status.command.get,
        cond = require("noice").api.status.command.has,
        color = { fg = "#ff9e64" },
      },
      {
        require("noice").api.status.mode.get,
        cond = require("noice").api.status.mode.has,
        color = { fg = "#ff9e64" },
      },
      {
        require("noice").api.status.search.get,
        cond = require("noice").api.status.search.has,
        color = { fg = "#ff9e64" },
      },
    },
  },
})

noice.setup({
  lsp = {
    -- override markdown rendering so that **cmp** and other plugins use **Treesitter**
    override = {
      ["vim.lsp.util.convert_input_to_markdown_lines"] = true,
      ["vim.lsp.util.stylize_markdown"] = true,
      ["cmp.entry.get_documentation"] = true,
    },
  },
  -- you can enable a preset for easier configuration
  presets = {
    bottom_search = true, -- use a classic bottom cmdline for search
    command_palette = true, -- position the cmdline and popupmenu together
    long_message_to_split = true, -- long messages will be sent to a split
    inc_rename = false, -- enables an input dialog for inc-rename.nvim
    lsp_doc_border = true, -- add a border to hover docs and signature help
  },
})

notify.setup({
  background_colour = "#000000"
})

glow.setup({})

vim.api.nvim_create_augroup("Markdown", {
  clear = true,
})

vim.api.nvim_create_autocmd({"FileType"}, {
  pattern = "markdown",
  callback = function ()
    vim.api.nvim_buf_set_keymap(0, 'n', '<leader>mdp', ':Glow<CR>', { desc = "Glow preview" })
  end
})


vim.keymap.set("n", "<C-Down>", "<cmd>resize -2<cr>", {
  noremap = false,
  desc = "Decrease window height",


})

vim.keymap.set("n", "<C-Left>", "<cmd>vertical resize -2<cr>", {
  noremap = false,
  desc = "Decrease window width",


})

vim.keymap.set("n", "<C-Right>", "<cmd>vertical resize +2<cr>", {
  noremap = false,
  desc = "Increase window width",


})

vim.keymap.set("n", "<C-Up>", "<cmd>resize +2<cr>", {
  noremap = false,
  desc = "Increase window height",


})

vim.keymap.set("n", "<leader>F", ":Neotree filesystem reveal float<CR>", {
  noremap = false,
  desc = "Open filesystem",


})

vim.keymap.set("n", "<leader>K", "<Cmd>WhichKey<CR>", {
  noremap = false,
  desc = "Which Keys!",


})

vim.keymap.set("n", "<leader>bF", "<Cmd>Format<CR>", {
  noremap = false,
  desc = "Format current buffer!",


})

vim.keymap.set("n", "<leader>bf", "<cmd>bfirst<CR>", {
  noremap = false,
  desc = "First Buffer",


})

vim.keymap.set("n", "<leader>bk", "<cmd>bd<CR>", {
  noremap = false,
  desc = "Close buffer",


})

vim.keymap.set("n", "<leader>bl", "<cmd>blast<CR>", {
  noremap = false,
  desc = "Last Buffer",


})

vim.keymap.set("n", "<leader>bn", "<cmd>bnext<CR>", {
  noremap = false,
  desc = "Next Buffer",


})

vim.keymap.set("n", "<leader>bp", "<cmd>bprevious<CR>", {
  noremap = false,
  desc = "Previous Buffer",


})

vim.keymap.set("n", "<leader>bw", "<cmd>w<CR>", {
  noremap = false,
  desc = "Write buffer",


})

vim.keymap.set("n", "<leader>fmL", "<cmd>CellularAutomaton game_of_life<CR>", {
  noremap = false,
  desc = "Game of life",


})

vim.keymap.set("n", "<leader>fml", "<cmd>CellularAutomaton make_it_rain<CR>", {
  noremap = false,
  desc = "Make it rain",


})

vim.keymap.set("n", "<leader>gs", ":Neotree git_status reveal float<CR>", {
  noremap = false,
  desc = "Git Status",


})

vim.keymap.set("n", "<leader>hc", "<cmd>noh<CR>", {
  noremap = false,
  desc = "Clear hlsearch",
  silent = true,


})

vim.keymap.set("n", "<leader>lb", ":Neotree buffers reveal float<CR>", {
  noremap = false,
  desc = "Open buffers",


})

vim.keymap.set("n", "<leader>qq", "<cmd>q<CR>", {
  noremap = false,
  desc = "Close nvim",


})

vim.keymap.set("n", "<leader>w=", "<C-W>=", {
  noremap = false,
  desc = "Balance windows",


})

vim.keymap.set("n", "<leader>wd", "<C-W>c", {
  noremap = false,
  desc = "Delete window",


})

vim.keymap.set("n", "<leader>wh", "<C-W>h", {
  noremap = false,
  desc = "Move to the left window",


})

vim.keymap.set("n", "<leader>wj", "<C-W>j", {
  noremap = false,
  desc = "Move to the bottom window",


})

vim.keymap.set("n", "<leader>wk", "<C-W>k", {
  noremap = false,
  desc = "Move to the top window",


})

vim.keymap.set("n", "<leader>wl", "<C-W>l", {
  noremap = false,
  desc = "Move to the right window",


})

vim.keymap.set("n", "<leader>wq", "<C-W>q", {
  noremap = false,
  desc = "Quit window",


})

vim.keymap.set("n", "<leader>wr", "<C-W>r", {
  noremap = false,
  desc = "Rotate windows",


})

vim.keymap.set("n", "<leader>ws", "<C-W>s", {
  noremap = false,
  desc = "Split window horizontally",


})

vim.keymap.set("n", "<leader>wv", "<C-W>v", {
  noremap = false,
  desc = "Split window vertically",


})

vim.keymap.set("n", "<leader>ww", "<C-W>w", {
  noremap = false,
  desc = "Toggle between open windows",


})

vim.keymap.set("n", "<leader>za", ":TZAtaraxis<CR>", {
  noremap = false,
  desc = "Ataraxis zen",


})

vim.keymap.set("n", "<leader>zf", ":TZFocus<CR>", {
  noremap = false,
  desc = "Focus zen",


})

vim.keymap.set("n", "<leader>zm", ":TZMinimalist<CR>", {
  noremap = false,
  desc = "Minimalist zen",


})

vim.keymap.set("n", "<leader>zn", ":TZNarrow<CR>", {
  noremap = false,
  desc = "Narrow zen",


})


vim.keymap.set("v", "<leader>zn", ":'<,'>TZNarrow<CR>", {
  noremap = false,



})