diff options
Diffstat (limited to 'lua/intellij_to_vscode/converter.lua')
| -rw-r--r-- | lua/intellij_to_vscode/converter.lua | 222 |
1 files changed, 222 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 |
