summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorDmitrii Morozov <dmitrii.morozov@sbb.ch>2025-10-29 16:47:41 +0100
committerDmitrii Morozov <dmitrii.morozov@sbb.ch>2025-10-29 16:47:41 +0100
commit1c3d6072b0a75666d3de5ae01ed3710f25877b20 (patch)
treef89632bf979d7738d13ed721c0b24fe299720ac4
Initial
-rw-r--r--lua/intellij_to_vscode/converter.lua222
-rw-r--r--lua/intellij_to_vscode/init.lua21
-rw-r--r--lua/intellij_to_vscode/intellij_to_vscode.lua11
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