diff options
| -rw-r--r-- | lua/intellij_to_vscode/converter.lua | 222 | ||||
| -rw-r--r-- | lua/intellij_to_vscode/init.lua | 21 | ||||
| -rw-r--r-- | lua/intellij_to_vscode/intellij_to_vscode.lua | 11 | 
3 files changed, 254 insertions, 0 deletions
diff --git a/lua/intellij_to_vscode/converter.lua b/lua/intellij_to_vscode/converter.lua new file mode 100644 index 0000000..5d87cd8 --- /dev/null +++ b/lua/intellij_to_vscode/converter.lua @@ -0,0 +1,222 @@ +local M = {} +local uv = vim.loop +local api = vim.api + +local function read_file(path) +	local fd = io.open(path, "r") +	if not fd then +		return nil +	end +	local content = fd:read("*a") +	fd:close() +	return content +end + +local function write_file(path, content) +	local dir = path:match("(.*/)[^/]+$") +	if dir then +		vim.fn.mkdir(dir, "p") +	end +	local fd = io.open(path, "w") +	if not fd then +		error("Cannot write " .. path) +	end +	fd:write(content) +	fd:close() +end + +-- Minimal XML attribute extractor for a tag like: <option name="MAIN_CLASS_NAME" value="com.example.Main" /> +local function extract_attributes(tag) +	local attrs = {} +	for k, v in tag:gmatch('(%w+)%s*=\\s*"([^"]*)"') do +		attrs[k] = v +	end +	return attrs +end + +-- Given the whole xml content, return table: +-- { type = "Application", name = "MyApp", options = { [name] = value, ... }, env = {k=v}, method = {...} } +local function parse_run_configuration(xml) +	-- find <configuration ...> ... </configuration> +	local cfg_tag, body = xml:match("<configuration%s+([^>]*)>(.-)</configuration>") +	if not cfg_tag then +		-- sometimes configuration is self-closing: <configuration ... /> +		cfg_tag = xml:match("<configuration%s+([^/>]+)/>") +		body = "" +	end +	if not cfg_tag then +		return nil +	end + +	local cfg_attrs = extract_attributes(cfg_tag) +	local result = { _attrs = cfg_attrs, options = {}, env = {}, method = {} } + +	-- options: <option name="..." value="..."/> +	for option in body:gmatch("<option%s+([^>/]-)/>") do +		local a = extract_attributes(option) +		if a.name and a.value then +			result.options[a.name] = a.value +		end +	end + +	-- envs: <envs><env name="K" value="V"/></envs> +	local envs_block = body:match("<envs>(.-)</envs>") +	if envs_block then +		for envtag in envs_block:gmatch("<env%s+([^>/]-)/>") do +			local a = extract_attributes(envtag) +			if a.name and a.value then +				result.env[a.name] = a.value +			end +		end +	end + +	-- method (for JUnit): <method v="2"> <option name="..." value="..."/> </method> +	local method_block = body:match("<method%s+([^>]*)>(.-)</method>") +	if method_block then +		local method_tag, method_body = body:match("<method%s+([^>]*)>(.-)</method>") +		if method_tag then +			result.method._attrs = extract_attributes(method_tag) +			result.method.options = {} +			for opt in method_body:gmatch("<option%s+([^>/]-)/>") do +				local a = extract_attributes(opt) +				if a.name and a.value then +					result.method.options[a.name] = a.value +				end +			end +		end +	end + +	return result +end + +-- Map a single parsed config to a VSCode launch configuration (as Lua table) +local function map_to_vscode(name, parsed) +	local t = parsed._attrs.type or parsed._attrs.factoryName or "" +	local vscode = { +		name = name or parsed._attrs.name or "Converted", +		request = "launch", +	} + +	-- Common Application type +	if t:match("[Aa]pplication") or parsed.options.MAIN_CLASS_NAME then +		vscode.type = "java" +		vscode.mainClass = parsed.options.MAIN_CLASS_NAME or parsed.options.MAIN_CLASS +		if parsed.options.PROGRAM_PARAMETERS and parsed.options.PROGRAM_PARAMETERS ~= "" then +			-- split by space respecting simple quotes (basic) +			local args = {} +			for a in parsed.options.PROGRAM_PARAMETERS:gmatch("%S+") do +				table.insert(args, a) +			end +			vscode.args = args +		end +		if parsed.options.VM_PARAMETERS and parsed.options.VM_PARAMETERS ~= "" then +			vscode.vmArgs = parsed.options.VM_PARAMETERS +		end +		if parsed.options.WORKING_DIRECTORY and parsed.options.WORKING_DIRECTORY ~= "" then +			vscode.cwd = parsed.options.WORKING_DIRECTORY:gsub("%$PROJECT_DIR%$", "${workspaceFolder}") +		else +			vscode.cwd = "${workspaceFolder}" +		end +		if next(parsed.env) then +			vscode.env = parsed.env +		end +		-- If module present, attach projectName (useful for java extension) +		if parsed.options.MODULE_NAME then +			vscode.projectName = parsed.options.MODULE_NAME +		end + +		return vscode +	end + +	-- JUnit test configuration +	if +		t:match("[Jj]Unit") +		or parsed.method.options and (parsed.method.options.CLASS_NAME or parsed.method.options.METHOD_NAME) +	then +		vscode.type = "java" +		-- Use test runner config via mainClass (JUnit runner) or use builtin test adapters +		if parsed.method.options.CLASS_NAME then +			vscode.name = (parsed._attrs.name or "JUnit:") .. " " .. parsed.method.options.CLASS_NAME +			-- map to a launch that runs the single test class +			vscode.mainClass = parsed.method.options.CLASS_NAME +			vscode.args = {} +		end +		if next(parsed.env) then +			vscode.env = parsed.env +		end +		vscode.cwd = parsed.options.WORKING_DIRECTORY +				and parsed.options.WORKING_DIRECTORY:gsub("%$PROJECT_DIR%$", "${workspaceFolder}") +			or "${workspaceFolder}" +		return vscode +	end + +	-- Gradle run config +	if t:match("[Gg]radle") or parsed._attrs.type == "Gradle" or parsed.options.TASK_NAME then +		-- Represent as a 'command' launch using terminal.integrated (fallback) +		vscode.type = "pwa-node" -- generic terminal-ish; user may change +		vscode.name = parsed._attrs.name or "Gradle Task" +		local task = parsed.options.TASK_NAME or parsed.options.GRADLE_TASK +		vscode.request = "launch" +		vscode.runtimeExecutable = "gradle" +		vscode.args = {} +		if task and task ~= "" then +			for tkn in (task .. ""):gmatch("%S+") do +				table.insert(vscode.args, tkn) +			end +		end +		vscode.cwd = parsed.options.WORKING_DIRECTORY +				and parsed.options.WORKING_DIRECTORY:gsub("%$PROJECT_DIR%$", "${workspaceFolder}") +			or "${workspaceFolder}" +		if next(parsed.env) then +			vscode.env = parsed.env +		end +		return vscode +	end + +	-- Fallback: produce a shell launch that echos an unsupported type +	return { +		type = "pwa-node", +		name = parsed._attrs.name or "Converted (unsupported)", +		request = "launch", +		program = "${file}", +		cwd = "${workspaceFolder}", +	} +end + +-- Main: scan .idea/runConfigurations and convert +function M.convert_all(opts) +	opts = opts or {} +	local pattern = ".run/*.xml" +	local files = vim.tbl_filter(function(p) +		return p ~= "" +	end, vim.fn.glob(pattern, false, true)) +	if #files == 0 then +		error(".run not found or no xml files present") +	end + +	local configs = {} +	for _, f in ipairs(files) do +		local content = read_file(f) +		if not content then +			vim.notify("Cannot read " .. f, vim.log.levels.WARN) +		end +		local name = f:match("([^/]+)%.xml$") or f +		local parsed = parse_run_configuration(content) +		if parsed then +			local vs = map_to_vscode(name, parsed) +			table.insert(configs, vs) +		else +			vim.notify("Skipping (unrecognized) " .. f, vim.log.levels.DEBUG) +		end +	end + +	local launch = { version = "0.2.0", configurations = configs } +	local ok, j = pcall(vim.fn.json_encode, launch) +	if not ok then +		error("Failed to encode launch.json") +	end +	write_file(".vscode/launch.json", j) +	return true +end + +return M diff --git a/lua/intellij_to_vscode/init.lua b/lua/intellij_to_vscode/init.lua new file mode 100644 index 0000000..8ff5251 --- /dev/null +++ b/lua/intellij_to_vscode/init.lua @@ -0,0 +1,21 @@ +local M = {} +local converter = require("intellij_to_vscode.converter") + +function M.setup(opts) +	opts = opts or {} +	M.opts = opts +	vim.api.nvim_create_user_command("ITVConvert", function() +		local ok, err = pcall(converter.convert_all, opts) +		if not ok then +			vim.notify("ITVConvert failed: " .. tostring(err), vim.log.levels.ERROR) +		else +			vim.notify("Converted IntelliJ run configurations to .vscode/launch.json", vim.log.levels.INFO) +		end +	end, {}) +end + +function M.convert_all(opts) +	return converter.convert_all(opts or M.opts) +end + +return M diff --git a/lua/intellij_to_vscode/intellij_to_vscode.lua b/lua/intellij_to_vscode/intellij_to_vscode.lua new file mode 100644 index 0000000..a2bfa78 --- /dev/null +++ b/lua/intellij_to_vscode/intellij_to_vscode.lua @@ -0,0 +1,11 @@ +-- This file makes the plugin load on startup and provide command if not already created +if vim.fn.exists(":ITVConvert") == 0 then +	vim.api.nvim_create_user_command("ITVConvert", function() +		local ok, err = pcall(function() +			require("intellij_to_vscode").convert_all() +		end) +		if not ok then +			vim.notify("ITVConvert failed: " .. tostring(err), vim.log.levels.ERROR) +		end +	end, {}) +end  | 
