diff --git a/init.lua b/init.lua index e0a2756..eb1f5b5 100644 --- a/init.lua +++ b/init.lua @@ -7,5 +7,6 @@ load('user.settings') load('user.commands') load('user.keymaps') require('user.plugins') +require('lsp') pcall(vim.cmd.colorscheme, 'catppuccin-mocha') diff --git a/lua/lsp.lua b/lua/lsp.lua new file mode 100644 index 0000000..7df80ba --- /dev/null +++ b/lua/lsp.lua @@ -0,0 +1,208 @@ +-- LSP Configuration using built-in vim.lsp.config API +local function setup_lsp() + -- Configure diagnostics with custom signs + vim.diagnostic.config({ + virtual_text = false, + severity_sort = true, + float = { + border = 'rounded', + source = true + }, + signs = { + Error = { text = '✘' }, + Warn = { text = '▲' }, + Hint = { text = '⚑' }, + Info = { text = '»' }, + }, + }) + + -- Configure LSP handlers + vim.lsp.handlers['textDocument/hover'] = vim.lsp.with( + vim.lsp.handlers.hover, + {border = 'rounded'} + ) + + vim.lsp.handlers['textDocument/signatureHelp'] = vim.lsp.with( + vim.lsp.handlers.signature_help, + {border = 'rounded'} + ) + + -- LSP capabilities + local lsp_capabilities = vim.lsp.protocol.make_client_capabilities() + + -- LSP attach function + local function on_attach(client, bufnr) + local bufmap = function(mode, lhs, rhs, desc) + local opts = { buffer = bufnr, desc = desc } + vim.keymap.set(mode, lhs, rhs, opts) + end + + -- LSP keybindings + bufmap('n', 'K', 'lua vim.lsp.buf.hover()', "Hover (Show documentation)") + bufmap('n', 'gd', 'lua vim.lsp.buf.definition()', "Go to Definition") + bufmap('n', 'gD', 'lua vim.lsp.buf.declaration()', "Go to Declaration") + bufmap('n', 'gi', 'lua vim.lsp.buf.implementation()', "Go to Implementation") + bufmap('n', 'go', 'lua vim.lsp.buf.type_definition()', "Go to Type Definition") + bufmap('n', 'gr', 'lua vim.lsp.buf.references()', "Find References") + bufmap('n', 'gs', 'lua vim.lsp.buf.signature_help()', "Signature Help") + bufmap('n', '', 'lua vim.lsp.buf.rename()', "Rename Symbol") + bufmap({'n', 'x'}, '', 'lua vim.lsp.buf.format({async = true})', "Format Code") + bufmap('n', '', 'lua vim.lsp.buf.code_action()', "Code Action") + bufmap('n', 'ga', 'lua vim.lsp.buf.code_action()', "Code Action") + bufmap('v', 'ga', 'lua vim.lsp.buf.range_code_action()', "Code Action") + bufmap('n', 'gl', 'lua vim.diagnostic.open_float()', "Show Diagnostic Float") + bufmap('n', '[d', 'lua vim.diagnostic.goto_prev()', "Go to Previous Diagnostic") + bufmap('n', ']d', 'lua vim.diagnostic.goto_next()', "Go to Next Diagnostic") + + -- LSP Debugging keybindings + bufmap('n', 'li', 'lua vim.print(vim.lsp.get_clients({bufnr = 0}))', "LSP Info (Current Buffer)") + bufmap('n', 'la', 'lua vim.print(vim.lsp.get_active_clients())', "LSP Info (All Clients)") + bufmap('n', 'll', 'LspLog', "LSP Log") + bufmap('n', 'lc', 'lua vim.print(vim.lsp.get_clients()[1] and vim.lsp.get_clients()[1].server_capabilities or "No LSP clients")', "LSP Capabilities") + end + + -- Setup LSP attach autocmd + local group = vim.api.nvim_create_augroup('lsp_cmds', {clear = true}) + vim.api.nvim_create_autocmd('LspAttach', { + group = group, + desc = 'LSP actions', + callback = on_attach + }) + + -- Helper function to check if command exists + local function has_command(cmd) + return vim.fn.executable(cmd) == 1 + end + + -- Helper function to get mason binary path + local function get_mason_bin(cmd) + local home = vim.fn.expand("~") + return home .. '/.local/share/nvim/mason/bin/' .. cmd + end + + -- Helper function to check if mason binary exists + local function has_mason_bin(cmd) + return vim.fn.filereadable(get_mason_bin(cmd)) == 1 + end + + -- Configure LSP servers only if they're available + -- TypeScript/JavaScript + if has_mason_bin('typescript-language-server') or has_command('typescript-language-server') then + local cmd = has_mason_bin('typescript-language-server') and get_mason_bin('typescript-language-server') or 'typescript-language-server' + vim.lsp.config('ts_ls', { + cmd = { cmd, '--stdio' }, + capabilities = lsp_capabilities, + filetypes = { 'javascript', 'typescript', 'javascriptreact', 'typescriptreact' }, + settings = { + completions = { + completeFunctionCalls = true + } + } + }) + vim.lsp.enable({'ts_ls'}) + end + + -- Tailwind CSS + if has_mason_bin('tailwindcss-language-server') or has_command('tailwindcss-language-server') then + local cmd = has_mason_bin('tailwindcss-language-server') and get_mason_bin('tailwindcss-language-server') or 'tailwindcss-language-server' + vim.lsp.config('tailwindcss', { + cmd = { cmd, '--stdio' }, + capabilities = lsp_capabilities, + filetypes = { 'html', 'css', 'scss', 'sass', 'less', 'stylus', 'javascript', 'typescript', 'javascriptreact', 'typescriptreact', 'vue', 'svelte' }, + }) + vim.lsp.enable({'tailwindcss'}) + end + + -- Lua + if has_mason_bin('lua-language-server') or has_command('lua-language-server') then + local cmd = has_mason_bin('lua-language-server') and get_mason_bin('lua-language-server') or 'lua-language-server' + local runtime_path = vim.split(package.path, ';') + table.insert(runtime_path, 'lua/?.lua') + table.insert(runtime_path, 'lua/?/init.lua') + + vim.lsp.config('lua_ls', { + cmd = { cmd }, + capabilities = lsp_capabilities, + filetypes = { 'lua' }, + settings = { + Lua = { + runtime = { + version = 'LuaJIT', + path = runtime_path + }, + diagnostics = { + globals = {'vim'} + }, + workspace = { + library = { + vim.fn.expand('$VIMRUNTIME/lua'), + vim.fn.stdpath('config') .. '/lua' + }, + checkThirdParty = false + }, + telemetry = { + enable = false + }, + } + } + }) + vim.lsp.enable({'lua_ls'}) + end + + -- ESLint + if has_mason_bin('vscode-eslint-language-server') or has_command('vscode-eslint-language-server') then + local cmd = has_mason_bin('vscode-eslint-language-server') and get_mason_bin('vscode-eslint-language-server') or 'vscode-eslint-language-server' + vim.lsp.config('eslint', { + cmd = { cmd, '--stdio' }, + capabilities = lsp_capabilities, + filetypes = { 'javascript', 'typescript', 'typescriptreact', 'javascriptreact' }, + }) + vim.lsp.enable({'eslint'}) + end + + -- HTML + if has_mason_bin('vscode-html-language-server') or has_command('vscode-html-language-server') then + local cmd = has_mason_bin('vscode-html-language-server') and get_mason_bin('vscode-html-language-server') or 'vscode-html-language-server' + vim.lsp.config('html', { + cmd = { cmd, '--stdio' }, + capabilities = lsp_capabilities, + filetypes = { 'html' }, + }) + vim.lsp.enable({'html'}) + end + + -- CSS + if has_mason_bin('vscode-css-language-server') or has_command('vscode-css-language-server') then + local cmd = has_mason_bin('vscode-css-language-server') and get_mason_bin('vscode-css-language-server') or 'vscode-css-language-server' + vim.lsp.config('cssls', { + cmd = { cmd, '--stdio' }, + capabilities = lsp_capabilities, + filetypes = { 'css', 'scss', 'sass', 'less' }, + }) + vim.lsp.enable({'cssls'}) + end + + -- Ruby + if has_mason_bin('ruby-lsp') or has_command('ruby-lsp') then + local cmd = has_mason_bin('ruby-lsp') and get_mason_bin('ruby-lsp') or 'ruby-lsp' + vim.lsp.config('ruby_lsp', { + cmd = { cmd }, + capabilities = lsp_capabilities, + filetypes = { 'ruby' }, + }) + vim.lsp.enable({'ruby_lsp'}) + end +end + +-- Create custom LSP log command +vim.api.nvim_create_user_command('LspLog', function() + local log_file = vim.fn.stdpath('state') .. '/lsp.log' + if vim.fn.filereadable(log_file) == 1 then + vim.cmd('split ' .. log_file) + else + vim.notify('LSP log file not found: ' .. log_file, vim.log.levels.WARN) + end +end, { desc = 'Open LSP log file' }) + +-- Initialize LSP +setup_lsp() diff --git a/lua/plugins/cmp.lua b/lua/plugins/cmp.lua index 5b4acb8..850686b 100644 --- a/lua/plugins/cmp.lua +++ b/lua/plugins/cmp.lua @@ -5,7 +5,6 @@ Plugin.dependencies = { {'hrsh7th/cmp-buffer'}, {'hrsh7th/cmp-path'}, {'saadparwaiz1/cmp_luasnip'}, - {'hrsh7th/cmp-nvim-lsp'}, -- Snippets {'L3MON4D3/LuaSnip'}, @@ -39,7 +38,7 @@ function Plugin.config() }, sources = { {name = 'path'}, - {name = 'nvim_lsp'}, + {name = 'lsp'}, {name = 'copilot', group_index = 2}, {name = 'buffer', keyword_length = 3}, {name = 'luasnip', keyword_length = 2}, diff --git a/lua/plugins/init.lua b/lua/plugins/init.lua index 91809d3..5d96195 100644 --- a/lua/plugins/init.lua +++ b/lua/plugins/init.lua @@ -8,7 +8,6 @@ local Plugins = { { 'slim-template/vim-slim', ft = 'slim' }, -- LSP & Autocompletion - { 'neovim/nvim-lspconfig' }, { 'hrsh7th/nvim-compe', event = 'InsertEnter' }, { 'onsails/lspkind.nvim', lazy = true }, diff --git a/lua/plugins/tailwind-tools.lua b/lua/plugins/tailwind-tools.lua index e435d61..fd446c8 100644 --- a/lua/plugins/tailwind-tools.lua +++ b/lua/plugins/tailwind-tools.lua @@ -5,7 +5,6 @@ return { dependencies = { "nvim-treesitter/nvim-treesitter", "nvim-telescope/telescope.nvim", -- optional - "neovim/nvim-lspconfig", -- optional }, opts = {} -- your configuration } diff --git a/lua/utils/vscode-config.lua b/lua/utils/vscode-config.lua index fd0486c..154e892 100644 --- a/lua/utils/vscode-config.lua +++ b/lua/utils/vscode-config.lua @@ -1,33 +1,53 @@ local M = {} --- local function get_project_root() --- local util = require("lspconfig.util") --- return util.root_pattern(".git", ".vscode")(vim.fn.expand("%:p")) or vim.fn.getcwd() --- end - --- function M.get_vscode_config() --- local root = get_project_root() --- local config_path = root .. "/.vscode/settings.json" - --- if vim.fn.filereadable(config_path) == 1 then --- local ok, content = pcall(vim.fn.readfile, config_path) --- if ok and content and #content > 0 then --- local json = table.concat(content, "\n") --- if json and json:match("%S") then -- Pastikan string tidak kosong atau hanya whitespace --- local success, decoded = pcall(vim.fn.json_decode, json) --- if success and type(decoded) == "table" then --- return decoded --- end --- end --- end --- end - --- return {} --- end - --- function M.is_enabled(key) --- local config = M.get_vscode_config() --- return config[key] == true --- end +local function get_project_root() + -- Use built-in root detection instead of lspconfig.util + local current_file = vim.fn.expand("%:p") + local current_dir = vim.fn.fnamemodify(current_file, ":h") + + -- Look for .git or .vscode directories going up the directory tree + local function find_root_dir(dir) + if dir == "" or dir == "/" then + return vim.fn.getcwd() + end + + local git_dir = dir .. "/.git" + local vscode_dir = dir .. "/.vscode" + + if vim.fn.isdirectory(git_dir) == 1 or vim.fn.isdirectory(vscode_dir) == 1 then + return dir + end + + local parent_dir = vim.fn.fnamemodify(dir, ":h") + return find_root_dir(parent_dir) + end + + return find_root_dir(current_dir) +end + +function M.get_vscode_config() + local root = get_project_root() + local config_path = root .. "/.vscode/settings.json" + + if vim.fn.filereadable(config_path) == 1 then + local ok, content = pcall(vim.fn.readfile, config_path) + if ok and content and #content > 0 then + local json = table.concat(content, "\n") + if json and json:match("%S") then -- Pastikan string tidak kosong atau hanya whitespace + local success, decoded = pcall(vim.fn.json_decode, json) + if success and type(decoded) == "table" then + return decoded + end + end + end + end + + return {} +end + +function M.is_enabled(key) + local config = M.get_vscode_config() + return config[key] == true +end return M