Luigit
repositories / dotfiles

dotfiles

bugabingas dorkfiles

owned by admin

neovim/lsp/rust_analyzer.lua

Raw
---@brief
---
--- https://github.com/rust-lang/rust-analyzer
---
--- rust-analyzer (aka rls 2.0), a language server for Rust
---
---
--- See [docs](https://rust-analyzer.github.io/book/configuration.html) for extra settings. The settings can be used like this:
--- ```lua
--- vim.lsp.config('rust_analyzer', {
---   settings = {
---     ['rust-analyzer'] = {
---       diagnostics = {
---         enable = false;
---       }
---     }
---   }
--- })
--- ```
---
--- Note: do not set `init_options` for this LS config, it will be automatically populated by the contents of settings["rust-analyzer"] per
--- https://github.com/rust-lang/rust-analyzer/blob/eb5da56d839ae0a9e9f50774fa3eb78eb0964550/docs/dev/lsp-extensions.md?plain=1#L26.

local function reload_workspace( bufnr )
	local clients = vim.lsp.get_clients { bufnr = bufnr, name = 'rust_analyzer' }
	for _, client in ipairs( clients ) do
		vim.notify 'Reloading Cargo Workspace'
		---@diagnostic disable-next-line:param-type-mismatch
		client:request( 'rust-analyzer/reloadWorkspace', nil, function ( err )
											if err then
												error( tostring( err ) )
											end
											vim.notify 'Cargo workspace reloaded'
										end, 0 )
	end
end

local function is_library( fname )
	local user_home = vim.fs.normalize( vim.env.HOME )
	local cargo_home = os.getenv 'CARGO_HOME' or user_home .. '/.cargo'
	local registry = cargo_home .. '/registry/src'
	local git_registry = cargo_home .. '/git/checkouts'

	local rustup_home = os.getenv 'RUSTUP_HOME' or user_home .. '/.rustup'
	local toolchains = rustup_home .. '/toolchains'

	for _, item in ipairs { toolchains, registry, git_registry } do
		if vim.fs.relpath( item, fname ) then
			local clients = vim.lsp.get_clients { name = 'rust_analyzer' }
			return #clients > 0 and clients[#clients].config.root_dir or nil
		end
	end
end

---@type vim.lsp.Config
return {
	cmd = { 'rust-analyzer' },
	filetypes = { 'rust' },
	root_dir = function ( bufnr, on_dir )
		local fname = vim.api.nvim_buf_get_name( bufnr )
		local reused_dir = is_library( fname )
		if reused_dir then
			on_dir( reused_dir )
			return
		end

		local cargo_crate_dir = vim.fs.root( fname, { 'Cargo.toml' } )
		local cargo_workspace_root

		if cargo_crate_dir == nil then
			on_dir(
				vim.fs.root( fname, { 'rust-project.json' } )
				or vim.fs.dirname( vim.fs.find( '.git', { path = fname, upward = true } )[1] )
			)
			return
		end

		local cmd = {
			'cargo',
			'metadata',
			'--no-deps',
			'--format-version',
			'1',
			'--manifest-path',
			cargo_crate_dir .. '/Cargo.toml',
		}

		vim.system( cmd, { text = true }, function ( output )
			if output.code == 0 then
				if output.stdout then
					local result = vim.json.decode( output.stdout )
					if result['workspace_root'] then
						cargo_workspace_root = vim.fs.normalize( result['workspace_root'] )
					end
				end

				on_dir( cargo_workspace_root or cargo_crate_dir )
			else
				vim.schedule( function ()
					vim.notify( ('[rust_analyzer] cmd failed with code %d: %s\n%s'):format( output.code, cmd, output.stderr ) )
				end )
			end
		end )
	end,
	capabilities = {
		experimental = {
			serverStatusNotification = true,
			commands = {
				commands = {
					'rust-analyzer.showReferences',
					'rust-analyzer.runSingle',
					'rust-analyzer.debugSingle',
				},
			},
		},
	},
	settings = {
		['rust-analyzer'] = {
			lens = {
				debug = { enable = true },
				enable = true,
				implementations = { enable = true },
				references = {
					adt = { enable = true },
					enumVariant = { enable = true },
					method = { enable = true },
					trait = { enable = true },
				},
				run = { enable = true },
				updateTest = { enable = true },
			},
		},
	},
	before_init = function ( init_params, config )
		-- See https://github.com/rust-lang/rust-analyzer/blob/eb5da56d839ae0a9e9f50774fa3eb78eb0964550/docs/dev/lsp-extensions.md?plain=1#L26
		if config.settings and config.settings['rust-analyzer'] then
			init_params.initializationOptions = config.settings['rust-analyzer']
		end
		---@param command table{ title: string, command: string, arguments: any[] }
		vim.lsp.commands['rust-analyzer.runSingle'] = function ( command )
			local r = command.arguments[1]
			local cmd = { 'cargo', unpack( r.args.cargoArgs ) }
			if r.args.executableArgs and #r.args.executableArgs > 0 then
				vim.list_extend( cmd, { '--', unpack( r.args.executableArgs ) } )
			end

			local proc = vim.system( cmd, { cwd = r.args.cwd } )

			local result = proc:wait()

			if result.code == 0 then
				vim.notify( result.stdout, vim.log.levels.INFO )
			else
				vim.notify( result.stderr, vim.log.levels.ERROR )
			end
		end
	end,
	on_attach = function ( _, bufnr )
		local user_command = require 'std.user_command'
		user_command.buffer(bufnr).LspCargoReload 'Reload current cargo workspace' (function()
			reload_workspace( bufnr )
		end)
	end,
}