From 421d64fe82534363f84cfc1062606726b4035eb9 Mon Sep 17 00:00:00 2001 From: Shoxx98 Date: Mon, 18 May 2026 00:36:49 +0200 Subject: [PATCH 01/19] spelling --- lua/platformio/utils.lua | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/lua/platformio/utils.lua b/lua/platformio/utils.lua index 83f86089..943bcbf6 100644 --- a/lua/platformio/utils.lua +++ b/lua/platformio/utils.lua @@ -351,9 +351,9 @@ function M.cd_pioini() end function M.pio_install_check() - local handel = (jit.os == 'Windows') and assert(io.popen('where.exe pio 2>./nul')) or assert(io.popen('which pio 2>/dev/null')) - local pio_path = assert(handel:read('*a')) - handel:close() + local handle = (jit.os == 'Windows') and assert(io.popen('where.exe pio 2>./nul')) or assert(io.popen('which pio 2>/dev/null')) + local pio_path = assert(handle:read('*a')) + handle:close() if #pio_path == 0 then vim.notify('Platformio not found in the path', vim.log.levels.ERROR) From d4c414b94e5f8dd338a30e6a691af85f3cba5487 Mon Sep 17 00:00:00 2001 From: Shoxx98 Date: Fri, 22 May 2026 17:38:33 +0200 Subject: [PATCH 02/19] temp change --- .stylua.toml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.stylua.toml b/.stylua.toml index 89e2ef8e..41c32ae4 100644 --- a/.stylua.toml +++ b/.stylua.toml @@ -1,6 +1,6 @@ column_width = 160 line_endings = "Unix" indent_type = "Spaces" -indent_width = 2 +indent_width = 4 quote_style = "AutoPreferSingle" call_parentheses = "Always" From d26cc1b286278db3ca56f1fe2f03bade8d885da5 Mon Sep 17 00:00:00 2001 From: Shoxx98 Date: Fri, 22 May 2026 17:38:52 +0200 Subject: [PATCH 03/19] formatting --- lua/platformio/pioinit.lua | 80 +++++++++++++++++++------------------- 1 file changed, 40 insertions(+), 40 deletions(-) diff --git a/lua/platformio/pioinit.lua b/lua/platformio/pioinit.lua index 6cee0d3b..09bc5c80 100644 --- a/lua/platformio/pioinit.lua +++ b/lua/platformio/pioinit.lua @@ -5,59 +5,59 @@ local picker = require('platformio.pickers') local boilerplate_gen = require('platformio.boilerplate').boilerplate_gen local function init_project(board_details, selected_framework) - local framework = selected_framework - if framework == 'none' then - framework = '' - end - local command = 'pio project init --board ' .. board_details.id .. ' --project-option "framework=' .. framework .. '"' - utils.ToggleTerminal(command, 'float', function() - vim.cmd(':PioLSP') - boilerplate_gen(framework) - end) + local framework = selected_framework + if framework == 'none' then + framework = '' + end + local command = 'pio project init --board ' .. board_details.id .. ' --project-option "framework=' .. framework .. '"' + utils.ToggleTerminal(command, 'float', function() + vim.cmd(':PioLSP') + boilerplate_gen(framework) + end) end local function pick_framework(board_details) - local framework_list = vim.list_extend({ 'none' }, board_details.frameworks or {}) - picker.pick_framework(framework_list, function(selected_framework) - init_project(board_details, selected_framework) - end) + local framework_list = vim.list_extend({ 'none' }, board_details.frameworks or {}) + picker.pick_framework(framework_list, function(selected_framework) + init_project(board_details, selected_framework) + end) end local function pick_board(boards) - picker.pick_board(boards, function(selected_board) - pick_framework(selected_board) - end) + picker.pick_board(boards, function(selected_board) + pick_framework(selected_board) + end) end function M.pioinit() - if not utils.pio_install_check() then - return - end - - -- Read stdout - local command = 'pio boards --json-output' - local handel = io.popen(command .. utils.devNul) - if not handel then - return - end - local json_str = handel:read('*a') - handel:close() - - if #json_str == 0 then - -- read stderr - handel = io.popen(command .. ' 2>&1') + if not utils.pio_install_check() then + return + end + + -- Read stdout + local command = 'pio boards --json-output' + local handel = io.popen(command .. utils.devNul) if not handel then - return + return end - local command_output = handel:read('*a') + local json_str = handel:read('*a') handel:close() - vim.notify('Some error occured while executing `' .. command .. "`', command output: \n", vim.log.levels.WARN) - print(command_output) - return - end - local json_data = vim.json.decode(json_str) - pick_board(json_data or {}) + if #json_str == 0 then + -- read stderr + handel = io.popen(command .. ' 2>&1') + if not handel then + return + end + local command_output = handel:read('*a') + handel:close() + vim.notify('Some error occured while executing `' .. command .. "`', command output: \n", vim.log.levels.WARN) + print(command_output) + return + end + + local json_data = vim.json.decode(json_str) + pick_board(json_data or {}) end return M From ba46d180f8edd72b4375418b8251e8380c761bf0 Mon Sep 17 00:00:00 2001 From: Shoxx98 Date: Fri, 22 May 2026 18:41:17 +0200 Subject: [PATCH 04/19] spellig --- lua/platformio/pioinit.lua | 16 ++++++++-------- 1 file changed, 8 insertions(+), 8 deletions(-) diff --git a/lua/platformio/pioinit.lua b/lua/platformio/pioinit.lua index 09bc5c80..47aa16a7 100644 --- a/lua/platformio/pioinit.lua +++ b/lua/platformio/pioinit.lua @@ -36,21 +36,21 @@ function M.pioinit() -- Read stdout local command = 'pio boards --json-output' - local handel = io.popen(command .. utils.devNul) - if not handel then + local handle = io.popen(command .. utils.devNul) + if not handle then return end - local json_str = handel:read('*a') - handel:close() + local json_str = handle:read('*a') + handle:close() if #json_str == 0 then -- read stderr - handel = io.popen(command .. ' 2>&1') - if not handel then + handle = io.popen(command .. ' 2>&1') + if not handle then return end - local command_output = handel:read('*a') - handel:close() + local command_output = handle:read('*a') + handle:close() vim.notify('Some error occured while executing `' .. command .. "`', command output: \n", vim.log.levels.WARN) print(command_output) return From 8519d6f7d6394b411851ecac6afa7ed8c08d854f Mon Sep 17 00:00:00 2001 From: Shoxx98 Date: Tue, 26 May 2026 14:34:28 +0200 Subject: [PATCH 05/19] formatting --- lua/platformio/piolsp.lua | 195 +++++----- lua/platformio/piotermlist.lua | 62 ++-- lua/platformio/utils.lua | 650 +++++++++++++++++---------------- plugin/platformio.lua | 84 +++-- 4 files changed, 508 insertions(+), 483 deletions(-) diff --git a/lua/platformio/piolsp.lua b/lua/platformio/piolsp.lua index fac211ed..e306bb03 100644 --- a/lua/platformio/piolsp.lua +++ b/lua/platformio/piolsp.lua @@ -4,133 +4,134 @@ local utils = require('platformio.utils') local config = require('platformio').config local function escape_flags(flags) - local escaped_flags = {} - - for _, flag in ipairs(flags) do - local escaped = flag - escaped = escaped:gsub('\\', '\\\\') -- Escape backslashes first - escaped = escaped:gsub('"', '\\"') -- Escape double quotes (for -D macros) - -- Escape parentheses (common in include paths) - escaped = escaped:gsub('%(', '\\(') - escaped = escaped:gsub('%)', '\\)') - escaped = escaped:gsub('%s+', '\\ ') - table.insert(escaped_flags, escaped) - end - - return escaped_flags + local escaped_flags = {} + + for _, flag in ipairs(flags) do + local escaped = flag + escaped = escaped:gsub('\\', '\\\\') -- Escape backslashes first + escaped = escaped:gsub('"', '\\"') -- Escape double quotes (for -D macros) + -- Escape parentheses (common in include paths) + escaped = escaped:gsub('%(', '\\(') + escaped = escaped:gsub('%)', '\\)') + escaped = escaped:gsub('%s+', '\\ ') + table.insert(escaped_flags, escaped) + end + + return escaped_flags end local function process_ccls() - local flags_allowed = { '%', '-W', '-std' } + local flags_allowed = { '%', '-W', '-std' } - local f = io.open(vim.fs.joinpath(vim.g.platformioRootDir, '.ccls'), 'rb') - if not f then - vim.notify('.ccls file not found', vim.log.levels.ERROR) - return {} - end + local f = io.open(vim.fs.joinpath(vim.g.platformioRootDir, '.ccls'), 'rb') + if not f then + vim.notify('.ccls file not found', vim.log.levels.ERROR) + return {} + end - local compiler = f:read() - local build_flags = { compiler } + local compiler = f:read() + local build_flags = { compiler } - for line in f:lines() do - if #line == 0 or string.sub(line, 1, 1) == '#' then - goto continue - end + for line in f:lines() do + if #line == 0 or string.sub(line, 1, 1) == '#' then + goto continue + end - if utils.check_prefix(line, '-I') or utils.check_prefix(line, '-D') then - table.insert(build_flags, line) - end - if utils.check_prefix(line, '%cpp') then - splitted = utils.strsplit(line, ' ') - for _, flag in ipairs(splitted) do - for _, flag_check in ipairs(flags_allowed) do - if utils.check_prefix(flag, flag_check) then - table.insert(build_flags, flag) - end + if utils.check_prefix(line, '-I') or utils.check_prefix(line, '-D') then + table.insert(build_flags, line) + end + if utils.check_prefix(line, '%cpp') then + splitted = utils.strsplit(line, ' ') + for _, flag in ipairs(splitted) do + for _, flag_check in ipairs(flags_allowed) do + if utils.check_prefix(flag, flag_check) then + table.insert(build_flags, flag) + end + end + end end - end - end - ::continue:: - end + ::continue:: + end - f:close() + f:close() - return escape_flags(build_flags) + return escape_flags(build_flags) end local function gen_compile_commands(build_flags) - local project_root = vim.g.platformioRootDir - local build_cmd = '' - for _, flag in ipairs(build_flags) do - build_cmd = build_cmd .. flag .. ' ' - end - - local entry = { { - directory = project_root, - file = vim.fs.joinpath(project_root, 'src', 'main.cpp'), - command = build_cmd, - } } - - local f = io.open(vim.fs.joinpath(project_root, 'compile_commands.json'), 'w') - f:write(vim.json.encode(entry, { indent = ' ', sort_keys = true })) - f:close() + local project_root = vim.g.platformioRootDir + local build_cmd = '' + for _, flag in ipairs(build_flags) do + build_cmd = build_cmd .. flag .. ' ' + end + + local entry = { { + directory = project_root, + file = vim.fs.joinpath(project_root, 'src', 'main.cpp'), + command = build_cmd, + } } + + local f = io.open(vim.fs.joinpath(project_root, 'compile_commands.json'), 'w') + f:write(vim.json.encode(entry, { indent = ' ', sort_keys = true })) + f:close() end local function gitignore_lsp_configs(config_file) - local gitignore_path = vim.fs.joinpath(vim.g.platformioRootDir, '.gitignore') - local file = io.open(gitignore_path, 'r') - local pattern = '^%s*' .. vim.pesc(config_file) .. '%s*$' - - if file then - for line in file:lines() do - if line:match(pattern) then + local gitignore_path = vim.fs.joinpath(vim.g.platformioRootDir, '.gitignore') + local file = io.open(gitignore_path, 'r') + local pattern = '^%s*' .. vim.pesc(config_file) .. '%s*$' + + if file then + for line in file:lines() do + if line:match(pattern) then + file:close() + return + end + end file:close() - return - end end - file:close() - end - file = io.open(gitignore_path, 'a') - file:write(config_file .. '\n') - file:close() + file = io.open(gitignore_path, 'a') + file:write(config_file .. '\n') + file:close() end function M.gen_clangd_config() - local build_flags = process_ccls() - gen_compile_commands(build_flags) + local build_flags = process_ccls() + gen_compile_commands(build_flags) end function M.piolsp() - if not utils.pio_install_check() then - return - end - utils.cd_pioini() - - if config.lsp == 'clangd' and config.clangd_source == 'compiledb' then - utils.shell_cmd_blocking('pio run -t compiledb') - gitignore_lsp_configs('compile_commands.json') - else - utils.shell_cmd_blocking('pio project init --ide=vim') - - if config.lsp == 'clangd' then - M.gen_clangd_config() - gitignore_lsp_configs('compile_commands.json') - os.remove(vim.fs.joinpath(vim.g.platformioRootDir, '.ccls')) + if not utils.pio_install_check() then + return + end + utils.cd_pioini() + + if config.lsp == 'clangd' and config.clangd_source == 'compiledb' then + utils.shell_cmd_blocking('pio run -t compiledb') + gitignore_lsp_configs('compile_commands.json') else - gitignore_lsp_configs('.ccls') + utils.shell_cmd_blocking('pio project init --ide=vim') + + if config.lsp == 'clangd' then + M.gen_clangd_config() + gitignore_lsp_configs('compile_commands.json') + -- TODO: check for file first + os.remove(vim.fs.joinpath(vim.g.platformioRootDir, '.ccls')) + else + gitignore_lsp_configs('.ccls') + end end - end - vim.notify('LSP config generation completed!', vim.log.levels.INFO) + vim.notify('LSP config generation completed!', vim.log.levels.INFO) - if vim.fn.has('nvim-0.12') then - if #vim.lsp.get_clients() > 0 then - vim.cmd('lsp restart') + if vim.fn.has('nvim-0.12') then + if #vim.lsp.get_clients() > 0 then + vim.cmd('lsp restart') + end + else + vim.cmd('LspRestart') end - else - vim.cmd('LspRestart') - end end return M diff --git a/lua/platformio/piotermlist.lua b/lua/platformio/piotermlist.lua index 7e35087e..fa7a9807 100644 --- a/lua/platformio/piotermlist.lua +++ b/lua/platformio/piotermlist.lua @@ -4,43 +4,43 @@ local utils = require('platformio.utils') local picker = require('platformio.pickers') local function switch_to_terminal(chosen) - if not chosen or not chosen.term then - vim.api.nvim_echo({ { 'No PIO terminal window selected.', 'Normal' } }, true, {}) - return - end - - local win_type = vim.fn.win_gettype(chosen.term.window) - local win_open = win_type == '' or win_type == 'popup' - if chosen.term.window and (win_open and vim.api.nvim_win_get_buf(chosen.term.window) == chosen.term.bufnr) then - vim.api.nvim_set_current_win(chosen.term.window) - else - chosen.term:open() - end - vim.api.nvim_echo({ { 'Switched to PIO terminal: ' .. chosen.termtype, 'Normal' } }, true, {}) + if not chosen or not chosen.term then + vim.api.nvim_echo({ { 'No PIO terminal window selected.', 'Normal' } }, true, {}) + return + end + + local win_type = vim.fn.win_gettype(chosen.term.window) + local win_open = win_type == '' or win_type == 'popup' + if chosen.term.window and (win_open and vim.api.nvim_win_get_buf(chosen.term.window) == chosen.term.bufnr) then + vim.api.nvim_set_current_win(chosen.term.window) + else + chosen.term:open() + end + vim.api.nvim_echo({ { 'Switched to PIO terminal: ' .. chosen.termtype, 'Normal' } }, true, {}) end function M.piotermlist() - local toggleterm_list = {} - local terms = require('toggleterm.terminal').get_all(true) - - if #terms ~= 0 then - for i = 1, #terms do - if terms[i].display_name and terms[i].display_name ~= '' and terms[i].display_name:find('pio', 1) then - local termtype = utils.strsplit(terms[i].display_name, ':')[1] - table.insert(toggleterm_list, { - term = terms[i], - termtype = termtype, - }) - end + local toggleterm_list = {} + local terms = require('toggleterm.terminal').get_all(true) + + if #terms ~= 0 then + for i = 1, #terms do + if terms[i].display_name and terms[i].display_name ~= '' and terms[i].display_name:find('pio', 1) then + local termtype = utils.strsplit(terms[i].display_name, ':')[1] + table.insert(toggleterm_list, { + term = terms[i], + termtype = termtype, + }) + end + end end - end - if #toggleterm_list == 0 then - vim.api.nvim_echo({ { 'No PIO terminal windows found.', 'Normal' } }, true, {}) - return - end + if #toggleterm_list == 0 then + vim.api.nvim_echo({ { 'No PIO terminal windows found.', 'Normal' } }, true, {}) + return + end - picker.pick_terminal(toggleterm_list, switch_to_terminal) + picker.pick_terminal(toggleterm_list, switch_to_terminal) end return M diff --git a/lua/platformio/utils.lua b/lua/platformio/utils.lua index 943bcbf6..8cc02cda 100644 --- a/lua/platformio/utils.lua +++ b/lua/platformio/utils.lua @@ -1,26 +1,31 @@ local M = {} +--- @alias userdir +--- | '"data"' +--- | '"state"' +--- | '"cache"' + local config = require('platformio').config -- M.extra = 'printf \'\\\\n\\\\033[0;33mPlease Press ENTER to continue \\\\033[0m\'; read' M.extra = ' && echo . && echo . && echo Please Press ENTER to continue' function M.strsplit(inputstr, del) - local t = {} - if type(inputstr) == 'string' and inputstr and inputstr ~= '' then - for str in string.gmatch(inputstr, '([^' .. del .. ']+)') do - table.insert(t, str) + local t = {} + if type(inputstr) == 'string' and inputstr and inputstr ~= '' then + for str in string.gmatch(inputstr, '([^' .. del .. ']+)') do + table.insert(t, str) + end end - end - return t + return t end function M.check_prefix(str, prefix) - return str:sub(1, #prefix) == prefix + return str:sub(1, #prefix) == prefix end local function pathmul(n) - return '..' .. string.rep('/..', n) + return '..' .. string.rep('/..', n) end ------------------------------------------------------ @@ -30,292 +35,292 @@ M.devNul = is_windows and ' 2>./nul' or ' 2>/dev/null' -- INFO: get current OS enter function M.enter() - local shell = vim.o.shell - if is_windows then - return vim.fn.executable('pwsh') and '\r' or '\r\n' - elseif shell:find('nu') then - return '\r' - else - return '\n' - end + local shell = vim.o.shell + if is_windows then + return vim.fn.executable('pwsh') and '\r' or '\r\n' + elseif shell:find('nu') then + return '\r' + else + return '\n' + end end -- INFO: get previous window local function getPreviousWindow(orig_window) - local prev = { - orig_window = orig_window, - term = nil, --active terminal - cli = nil, --cli terminal - mon = nil, --mon terminal - float = false, --is active terminal direction float - } - local terms = require('toggleterm.terminal').get_all(true) - if #terms ~= 0 then - for i = 1, #terms do - if terms[i].display_name and terms[i].display_name ~= '' and terms[i].display_name:find('pio', 1) then - local name_splt = M.strsplit(terms[i].display_name, ':') - if name_splt[1] == 'piocli' then - prev.cli = terms[i] - if terms[i].window == orig_window then - ---@diagnostic disable-next-line: cast-local-type - prev.orig_window = tonumber(name_splt[2]) -- set orig_window to the previous terminal onrig_window - prev.term = terms[i] - end - if terms[i].direction == 'float' then - prev.float = true - end - elseif name_splt[1] == 'piomon' then - prev.mon = terms[i] - if terms[i].window == orig_window then - ---@diagnostic disable-next-line: cast-local-type - prev.orig_window = tonumber(name_splt[2]) -- set orig_window to the previous terminal onrig_window - prev.term = terms[i] - end - if terms[i].direction == 'float' then - prev.float = true - end + local prev = { + orig_window = orig_window, + term = nil, --active terminal + cli = nil, --cli terminal + mon = nil, --mon terminal + float = false, --is active terminal direction float + } + local terms = require('toggleterm.terminal').get_all(true) + if #terms ~= 0 then + for i = 1, #terms do + if terms[i].display_name and terms[i].display_name ~= '' and terms[i].display_name:find('pio', 1) then + local name_splt = M.strsplit(terms[i].display_name, ':') + if name_splt[1] == 'piocli' then + prev.cli = terms[i] + if terms[i].window == orig_window then + ---@diagnostic disable-next-line: cast-local-type + prev.orig_window = tonumber(name_splt[2]) -- set orig_window to the previous terminal onrig_window + prev.term = terms[i] + end + if terms[i].direction == 'float' then + prev.float = true + end + elseif name_splt[1] == 'piomon' then + prev.mon = terms[i] + if terms[i].window == orig_window then + ---@diagnostic disable-next-line: cast-local-type + prev.orig_window = tonumber(name_splt[2]) -- set orig_window to the previous terminal onrig_window + prev.term = terms[i] + end + if terms[i].direction == 'float' then + prev.float = true + end + end + end end - end end - end - return prev + return prev end ------------------------------------------------------ -- INFO: Send command local function send(term, cmd) - vim.fn.chansend(term.job_id, cmd .. M.enter()) - if vim.api.nvim_buf_is_loaded(term.bufnr) and vim.api.nvim_buf_is_valid(term.bufnr) then - if term.window and vim.api.nvim_win_is_valid(term.window) then --vim.ui.term_has_open_win(term) then - vim.api.nvim_set_current_win(term.window) -- terminal focus - vim.api.nvim_buf_call(term.bufnr, function() - local mode = vim.api.nvim_get_mode().mode - if mode == 'n' or mode == 'nt' then - vim.cmd('normal! G') -- normal command to Goto bottom of buffer (scroll) + vim.fn.chansend(term.job_id, cmd .. M.enter()) + if vim.api.nvim_buf_is_loaded(term.bufnr) and vim.api.nvim_buf_is_valid(term.bufnr) then + if term.window and vim.api.nvim_win_is_valid(term.window) then --vim.ui.term_has_open_win(term) then + vim.api.nvim_set_current_win(term.window) -- terminal focus + vim.api.nvim_buf_call(term.bufnr, function() + local mode = vim.api.nvim_get_mode().mode + if mode == 'n' or mode == 'nt' then + vim.cmd('normal! G') -- normal command to Goto bottom of buffer (scroll) + end + end) end - end) end - end end ------------------------------------------------------ -- INFO: PioTermClose local function PioTermClose(t) - local orig_window = tonumber(M.strsplit(t.display_name, ':')[2]) - -- close terminal window - vim.api.nvim_win_close(t.window, true) - - -- go back to previous window - if orig_window and vim.api.nvim_win_is_valid(orig_window) then - vim.api.nvim_set_current_win(orig_window) - else - vim.api.nvim_set_current_win(0) - end + local orig_window = tonumber(M.strsplit(t.display_name, ':')[2]) + -- close terminal window + vim.api.nvim_win_close(t.window, true) + + -- go back to previous window + if orig_window and vim.api.nvim_win_is_valid(orig_window) then + vim.api.nvim_set_current_win(orig_window) + else + vim.api.nvim_set_current_win(0) + end end ------------------------------------------------------ -- INFO: ToggleTerminal function M.ToggleTerminal(command, direction, exit_callback) - if type(exit_callback) ~= 'function' then - exit_callback = function() end - end - - local status_ok, _ = pcall(require, 'toggleterm') - if not status_ok then - vim.api.nvim_echo({ { 'toggleterm not found!', 'ErrorMsg' } }, true, {}) - return - end - - local title = '' - local pioOpts = {} - - -- INFO: set orig_window to current window, or if available get current toggleterm previous window - local prev = getPreviousWindow(vim.api.nvim_get_current_win()) - local orig_window = prev.orig_window - - if string.find(command, ' monitor') then - if prev.mon then -- INFO: if previous monitor terminal already opened ==> reopen - local win_type = vim.fn.win_gettype(prev.mon.window) - local win_open = win_type == '' or win_type == 'popup' - if prev.mon.window and (win_open and vim.api.nvim_win_get_buf(prev.mon.window) == prev.mon.bufnr) then - vim.api.nvim_set_current_win(prev.mon.window) - else - prev.mon:open() - end - return + if type(exit_callback) ~= 'function' then + exit_callback = function() end end - title = 'Pio Monitor: [In normal mode press: q or :q to hide; :q! to quit; :PioTermList to list terminals]' - pioOpts.display_name = 'piomon:' .. orig_window - else -- INFO: if previous cli terminal already opened ==> reopen - if prev.cli then - prev.cli.on_close = function(t) - local ow = tonumber(M.strsplit(t.display_name, ':')[2]) - if ow and vim.api.nvim_win_is_valid(ow) then - vim.api.nvim_set_current_win(ow) - else - vim.api.nvim_set_current_win(0) - end - exit_callback() - end - - local win_type = vim.fn.win_gettype(prev.cli.window) - local win_open = win_type == '' or win_type == 'popup' - if prev.cli.window and (win_open and vim.api.nvim_win_get_buf(prev.cli.window) == prev.cli.bufnr) then - vim.api.nvim_set_current_win(prev.cli.window) - else - prev.cli:open() - end - vim.defer_fn(function() - if command and command ~= '' then - send(prev.cli, command) - end - end, 50) -- 50ms delay, adjust as needed - return + + local status_ok, _ = pcall(require, 'toggleterm') + if not status_ok then + vim.api.nvim_echo({ { 'toggleterm not found!', 'ErrorMsg' } }, true, {}) + return end - title = 'Pio CLI> [In normal mode press: q or :q to hide; :q! to quit; :PioTermList to list terminals]' - pioOpts.display_name = 'piocli:' .. orig_window - end - pioOpts.direction = direction - ------------------------------------------------------ - - -- INFO: termConfig table start - local termConfig = { - hidden = true, -- Start hidden, we'll open it explicitly - hide_numbers = true, - float_opts = { - winblend = 0, - width = function() - return math.ceil(vim.o.columns * 0.85) - end, - height = function() - return math.ceil(vim.o.lines * 0.85) - end, - highlights = { - border = 'FloatBorder', - background = 'NormalFloat', - }, - }, - close_on_exit = false, - - -- INFO: on_open() - on_open = function(t) - -- Get properties of the 'Normal' highlight group (background of main editor) - -- local hl = vim.api.nvim_get_hl(0, { name = 'PmenuSel' }) - -- local hl = { bg = '#e4cf0e', fg = '#0012d9' } - local hl = { bg = '#80a3d4', fg = '#000000' } - - if hl then - vim.api.nvim_set_hl(0, 'MyWinBar', { bg = hl.bg, fg = hl.fg }) - - local winBartitle = '%#MyWinBar#' .. title .. '%*' - vim.api.nvim_set_option_value('winbar', winBartitle, { scope = 'local', win = t.window }) - - -- Following necessary to solve that some time winbar not showing - vim.schedule(function() - vim.api.nvim_set_option_value('winbar', winBartitle, { scope = 'local', win = t.window }) - end) - end - vim.keymap.set('t', '', [[k]], { buffer = t.bufnr }) - vim.keymap.set('n', '', [[a]], { buffer = t.bufnr }) - - vim.keymap.set('n', 'q', function() - PioTermClose(t) - end, { desc = 'PioTermClose', buffer = t.bufnr }) - - if config.debug then - local name_splt = M.strsplit(t.display_name, ':') - vim.api.nvim_echo({ - { 'ToggleTerm ', 'MoreMsg' }, - { '(Term name: ' .. name_splt[1] .. ')', 'MoreMsg' }, - { '(Prev win ID: ' .. name_splt[2] .. ')', 'MoreMsg' }, - { '(Term Win ID: ' .. t.window .. ')', 'MoreMsg' }, - { '(Term Buffer#: ' .. t.bufnr .. ')', 'MoreMsg' }, - { '(Term id: ' .. t.id .. ')', 'MoreMsg' }, - { '(Job ID: ' .. t.job_id .. ')', 'MoreMsg' }, - }, true, {}) - end - end, - - -- INFO: on_close() - on_close = function(t) - orig_window = tonumber(M.strsplit(t.display_name, ':')[2]) - ---@diagnostic disable-next-line: param-type-mismatch - if orig_window and vim.api.nvim_win_is_valid(orig_window) then - vim.api.nvim_set_current_win(orig_window) - else - vim.api.nvim_set_current_win(0) - end - exit_callback() - end, - - -- INFO: on_create() { - on_create = function(t) - local platformio = vim.api.nvim_create_augroup(M.strsplit(t.display_name, ':')[1], { clear = true }) - - -- INFO: CmdlineLeave - vim.api.nvim_create_autocmd('CmdlineLeave', { - group = platformio, - -- pattern = ':', - buffer = t.bufnr, - callback = function() - if vim.v.event and not vim.v.event.abort and vim.v.event.cmdtype == ':' then - local quit = vim.fn.getcmdline() == 'q' - local quitbang = vim.fn.getcmdline() == 'q!' - if quitbang or quit then - local name_splt = M.strsplit(t.display_name, ':') - if quitbang then - if name_splt[1] == 'piomon' then -- monitor terminal - local exit = vim.api.nvim_replace_termcodes('exit', true, true, true) - send(t, exit) - else -- cli terminal - send(t, 'exit') - end - end - orig_window = tonumber(name_splt[2]) - vim.schedule(function() - -- go back to previous window - if orig_window and vim.api.nvim_win_is_valid(orig_window) then - vim.api.nvim_set_current_win(orig_window) + local title = '' + local pioOpts = {} + + -- INFO: set orig_window to current window, or if available get current toggleterm previous window + local prev = getPreviousWindow(vim.api.nvim_get_current_win()) + local orig_window = prev.orig_window + + if string.find(command, ' monitor') then + if prev.mon then -- INFO: if previous monitor terminal already opened ==> reopen + local win_type = vim.fn.win_gettype(prev.mon.window) + local win_open = win_type == '' or win_type == 'popup' + if prev.mon.window and (win_open and vim.api.nvim_win_get_buf(prev.mon.window) == prev.mon.bufnr) then + vim.api.nvim_set_current_win(prev.mon.window) + else + prev.mon:open() + end + return + end + title = 'Pio Monitor: [In normal mode press: q or :q to hide; :q! to quit; :PioTermList to list terminals]' + pioOpts.display_name = 'piomon:' .. orig_window + else -- INFO: if previous cli terminal already opened ==> reopen + if prev.cli then + prev.cli.on_close = function(t) + local ow = tonumber(M.strsplit(t.display_name, ':')[2]) + if ow and vim.api.nvim_win_is_valid(ow) then + vim.api.nvim_set_current_win(ow) else - vim.api.nvim_set_current_win(0) + vim.api.nvim_set_current_win(0) end - end) + exit_callback() + end + + local win_type = vim.fn.win_gettype(prev.cli.window) + local win_open = win_type == '' or win_type == 'popup' + if prev.cli.window and (win_open and vim.api.nvim_win_get_buf(prev.cli.window) == prev.cli.bufnr) then + vim.api.nvim_set_current_win(prev.cli.window) + else + prev.cli:open() + end + vim.defer_fn(function() + if command and command ~= '' then + send(prev.cli, command) + end + end, 50) -- 50ms delay, adjust as needed + return + end + title = 'Pio CLI> [In normal mode press: q or :q to hide; :q! to quit; :PioTermList to list terminals]' + pioOpts.display_name = 'piocli:' .. orig_window + end + pioOpts.direction = direction + ------------------------------------------------------ + + -- INFO: termConfig table start + local termConfig = { + hidden = true, -- Start hidden, we'll open it explicitly + hide_numbers = true, + float_opts = { + winblend = 0, + width = function() + return math.ceil(vim.o.columns * 0.85) + end, + height = function() + return math.ceil(vim.o.lines * 0.85) + end, + highlights = { + border = 'FloatBorder', + background = 'NormalFloat', + }, + }, + close_on_exit = false, + + -- INFO: on_open() + on_open = function(t) + -- Get properties of the 'Normal' highlight group (background of main editor) + -- local hl = vim.api.nvim_get_hl(0, { name = 'PmenuSel' }) + -- local hl = { bg = '#e4cf0e', fg = '#0012d9' } + local hl = { bg = '#80a3d4', fg = '#000000' } + + if hl then + vim.api.nvim_set_hl(0, 'MyWinBar', { bg = hl.bg, fg = hl.fg }) + + local winBartitle = '%#MyWinBar#' .. title .. '%*' + vim.api.nvim_set_option_value('winbar', winBartitle, { scope = 'local', win = t.window }) + + -- Following necessary to solve that some time winbar not showing + vim.schedule(function() + vim.api.nvim_set_option_value('winbar', winBartitle, { scope = 'local', win = t.window }) + end) + end + vim.keymap.set('t', '', [[k]], { buffer = t.bufnr }) + vim.keymap.set('n', '', [[a]], { buffer = t.bufnr }) + + vim.keymap.set('n', 'q', function() + PioTermClose(t) + end, { desc = 'PioTermClose', buffer = t.bufnr }) + + if config.debug then + local name_splt = M.strsplit(t.display_name, ':') + vim.api.nvim_echo({ + { 'ToggleTerm ', 'MoreMsg' }, + { '(Term name: ' .. name_splt[1] .. ')', 'MoreMsg' }, + { '(Prev win ID: ' .. name_splt[2] .. ')', 'MoreMsg' }, + { '(Term Win ID: ' .. t.window .. ')', 'MoreMsg' }, + { '(Term Buffer#: ' .. t.bufnr .. ')', 'MoreMsg' }, + { '(Term id: ' .. t.id .. ')', 'MoreMsg' }, + { '(Job ID: ' .. t.job_id .. ')', 'MoreMsg' }, + }, true, {}) end - end end, - }) - - -- INFO: BufUnload - vim.api.nvim_create_autocmd('BufUnload', { - group = platformio, - desc = 'toggleterm buffer unloaded', - buffer = t.bufnr, - callback = function(args) - vim.keymap.del('t', '', { buffer = args.buf }) - vim.keymap.del('n', '', { buffer = args.buf }) - - -- clear autommmand when quit - vim.api.nvim_clear_autocmds({ group = M.strsplit(t.display_name, ':')[1] }) + + -- INFO: on_close() + on_close = function(t) + orig_window = tonumber(M.strsplit(t.display_name, ':')[2]) + ---@diagnostic disable-next-line: param-type-mismatch + if orig_window and vim.api.nvim_win_is_valid(orig_window) then + vim.api.nvim_set_current_win(orig_window) + else + vim.api.nvim_set_current_win(0) + end + exit_callback() end, - }) - end, - } - -- INFO: termConfig table end - - termConfig = vim.tbl_deep_extend('force', termConfig, pioOpts or {}) - - -- INFO: create new terminal - local terminal = require('toggleterm.terminal').Terminal:new(termConfig) - if prev.term and prev.float then - prev.term:close() - end - terminal:toggle() - vim.defer_fn(function() - if command and command ~= '' then - send(terminal, command) + + -- INFO: on_create() { + on_create = function(t) + local platformio = vim.api.nvim_create_augroup(M.strsplit(t.display_name, ':')[1], { clear = true }) + + -- INFO: CmdlineLeave + vim.api.nvim_create_autocmd('CmdlineLeave', { + group = platformio, + -- pattern = ':', + buffer = t.bufnr, + callback = function() + if vim.v.event and not vim.v.event.abort and vim.v.event.cmdtype == ':' then + local quit = vim.fn.getcmdline() == 'q' + local quitbang = vim.fn.getcmdline() == 'q!' + if quitbang or quit then + local name_splt = M.strsplit(t.display_name, ':') + if quitbang then + if name_splt[1] == 'piomon' then -- monitor terminal + local exit = vim.api.nvim_replace_termcodes('exit', true, true, true) + send(t, exit) + else -- cli terminal + send(t, 'exit') + end + end + + orig_window = tonumber(name_splt[2]) + vim.schedule(function() + -- go back to previous window + if orig_window and vim.api.nvim_win_is_valid(orig_window) then + vim.api.nvim_set_current_win(orig_window) + else + vim.api.nvim_set_current_win(0) + end + end) + end + end + end, + }) + + -- INFO: BufUnload + vim.api.nvim_create_autocmd('BufUnload', { + group = platformio, + desc = 'toggleterm buffer unloaded', + buffer = t.bufnr, + callback = function(args) + vim.keymap.del('t', '', { buffer = args.buf }) + vim.keymap.del('n', '', { buffer = args.buf }) + + -- clear autommmand when quit + vim.api.nvim_clear_autocmds({ group = M.strsplit(t.display_name, ':')[1] }) + end, + }) + end, + } + -- INFO: termConfig table end + + termConfig = vim.tbl_deep_extend('force', termConfig, pioOpts or {}) + + -- INFO: create new terminal + local terminal = require('toggleterm.terminal').Terminal:new(termConfig) + if prev.term and prev.float then + prev.term:close() end - end, 50) -- 50ms delay, adjust as needed sgget + terminal:toggle() + vim.defer_fn(function() + if command and command ~= '' then + send(terminal, command) + end + end, 50) -- 50ms delay, adjust as needed sgget end ---------------------------------------------------------------------------------------- @@ -323,78 +328,93 @@ end local paths = { '.', '..', pathmul(1), pathmul(2), pathmul(3), pathmul(4), pathmul(5) } function M.file_exists(name) - local f = io.open(name, 'r') - if f ~= nil then - io.close(f) - return true - else - return false - end + local f = io.open(name, 'r') + if f ~= nil then + io.close(f) + return true + else + return false + end end function M.set_platformioRootDir() - if vim.g.platformioRootDir ~= nil then - return - end - for _, path in pairs(paths) do - if M.file_exists(path .. '/platformio.ini') then - vim.g.platformioRootDir = path - return + if vim.g.platformioRootDir ~= nil then + return + end + for _, path in pairs(paths) do + if M.file_exists(path .. '/platformio.ini') then + vim.g.platformioRootDir = path + return + end end - end - vim.notify('Could not find platformio.ini, run :Pioinit to create a new project', vim.log.levels.ERROR) + vim.notify('Could not find platformio.ini, run :Pioinit to create a new project', vim.log.levels.ERROR) end function M.cd_pioini() - M.set_platformioRootDir() - vim.cmd('cd ' .. vim.g.platformioRootDir) + M.set_platformioRootDir() + vim.cmd('cd ' .. vim.g.platformioRootDir) end function M.pio_install_check() - local handle = (jit.os == 'Windows') and assert(io.popen('where.exe pio 2>./nul')) or assert(io.popen('which pio 2>/dev/null')) - local pio_path = assert(handle:read('*a')) - handle:close() - - if #pio_path == 0 then - vim.notify('Platformio not found in the path', vim.log.levels.ERROR) - return false - end - return true + local handle = (jit.os == 'Windows') and assert(io.popen('where.exe pio 2>./nul')) or assert(io.popen('which pio 2>/dev/null')) + local pio_path = assert(handle:read('*a')) + handle:close() + + if #pio_path == 0 then + vim.notify('Platformio not found in the path', vim.log.levels.ERROR) + return false + end + return true end function M.async_shell_cmd(cmd, callback) - local output = {} - - vim.fn.jobstart(cmd, { - stdout_buffered = true, - stderr_buffered = false, - - on_stdout = function(_, data) - if data then - for _, line in ipairs(data) do - if line ~= '' then - table.insert(output, line) - end - end - end - end, + local output = {} + + vim.fn.jobstart(cmd, { + stdout_buffered = true, + stderr_buffered = false, + + on_stdout = function(_, data) + if data then + for _, line in ipairs(data) do + if line ~= '' then + table.insert(output, line) + end + end + end + end, - on_exit = function(_, code) - callback(output, code) - end, - }) + on_exit = function(_, code) + callback(output, code) + end, + }) end function M.shell_cmd_blocking(command) - local handle = io.popen(command, 'r') - if not handle then - return nil, 'failed to run command' - end + local handle = io.popen(command, 'r') + if not handle then + return nil, 'failed to run command' + end - local result = handle:read('*a') - handle:close() + local result = handle:read('*a') + handle:close() - return result + return result +end + +-- TODO: understand how to execute command and fill in the corresponding table +function M.get_os_user_data_dir() + --- @type string + local os_dirname, path_separator + + if is_windows then + path_separator = '\\' + os_dirname = 'LOCALAPPDATA' + else + path_separator = '/' + os_dirname = 'XDG_STATE_DIR' + end + return os.getenv(os_dirname) .. path_separator .. 'nvim' .. path_separator .. 'nvim-platformio' end return M diff --git a/plugin/platformio.lua b/plugin/platformio.lua index 23734033..96784f54 100644 --- a/plugin/platformio.lua +++ b/plugin/platformio.lua @@ -11,87 +11,91 @@ local piolsserial = require('platformio.piolsserial') -- Pioinit vim.api.nvim_create_user_command('Pioinit', function() - require('platformio.pioinit').pioinit() + require('platformio.pioinit').pioinit() end, { force = true }) -- Piolsp vim.api.nvim_create_user_command('PioLSP', function() - vim.schedule(function() - require('platformio.piolsp').piolsp() - end) + vim.schedule(function() + require('platformio.piolsp').piolsp() + end) end, {}) -- Piorun vim.api.nvim_create_user_command('Piorun', function(opts) - local args = opts.args - require('platformio.piorun').piorun({ args }) + local args = opts.args + require('platformio.piorun').piorun({ args }) end, { - nargs = '?', - complete = function(_, _, _) - return { 'upload', 'uploadfs', 'build', 'clean' } -- Autocompletion options - end, + nargs = '?', + complete = function(_, _, _) + return { 'upload', 'uploadfs', 'build', 'clean' } -- Autocompletion options + end, }) -- Piomon piolsserial.sync_ttylist() vim.api.nvim_create_user_command('Piomon', function(opts) - local args = opts.fargs - require('platformio.piomon').piomon(args) + local args = opts.fargs + require('platformio.piomon').piomon(args) end, { - nargs = '*', + nargs = '*', - complete = function(_, cmd_line) - local parts = vim.split(cmd_line, '%s+') - local BAUD = { '4800', '9600', '57600', '115200' } - local ports = {} - for _, item in ipairs(piolsserial.tty_list) do - table.insert(ports, item.port) - end - if #parts == 2 then - return BAUD - end - if #parts == 3 then - return ports - end - return {} - end, + complete = function(_, cmd_line) + local parts = vim.split(cmd_line, '%s+') + local BAUD = { '4800', '9600', '57600', '115200' } + local ports = {} + for _, item in ipairs(piolsserial.tty_list) do + table.insert(ports, item.port) + end + if #parts == 2 then + return BAUD + end + if #parts == 3 then + return ports + end + return {} + end, }) -- Piolsserial vim.api.nvim_create_user_command('Piolsserial', function() - require('platformio.piolsserial').print_tty_list() + require('platformio.piolsserial').print_tty_list() end, {}) -- Piolib vim.api.nvim_create_user_command('Piolib', function(opts) - local args = vim.split(opts.args, ' ') - require('platformio.piolib').piolib(args) + local args = vim.split(opts.args, ' ') + require('platformio.piolib').piolib(args) end, { - nargs = '+', + nargs = '+', }) -- Piocmdh Piocmd horizontal terminal vim.api.nvim_create_user_command('Piocmdh', function(opts) - local cmd_table = vim.split(opts.args, ' ') - require('platformio.piocmd').piocmd(cmd_table, 'horizontal') + local cmd_table = vim.split(opts.args, ' ') + require('platformio.piocmd').piocmd(cmd_table, 'horizontal') end, { - nargs = '*', + nargs = '*', }) -- Piocmdf Piocmd float terminal vim.api.nvim_create_user_command('Piocmdf', function(opts) - local cmd_table = vim.split(opts.args, ' ') - require('platformio.piocmd').piocmd(cmd_table, 'float') + local cmd_table = vim.split(opts.args, ' ') + require('platformio.piocmd').piocmd(cmd_table, 'float') end, { - nargs = '*', + nargs = '*', }) -- Piodebug vim.api.nvim_create_user_command('Piodebug', function() - require('platformio.piodebug').piodebug() + require('platformio.piodebug').piodebug() end, {}) -- INFO: List ToggleTerminals vim.api.nvim_create_user_command('PioTermList', function() - require('platformio.piotermlist').piotermlist() + require('platformio.piotermlist').piotermlist() +end, {}) + +vim.api.nvim_create_user_command('Pioenv', function() + require('platformio.pioenv').pioenv() end, {}) From 0cf209121f2450a45f6f5a7257a84ee59b49d2ee Mon Sep 17 00:00:00 2001 From: Shoxx98 Date: Tue, 26 May 2026 14:35:27 +0200 Subject: [PATCH 06/19] formatting and laying the groundwork for my additions --- lua/platformio/pickers/init.lua | 83 ++++++++++++----------- lua/platformio/pickers/ui_select.lua | 98 +++++++++++++++------------- lua/platformio/pioenv.lua | 30 +++++++++ 3 files changed, 130 insertions(+), 81 deletions(-) create mode 100644 lua/platformio/pioenv.lua diff --git a/lua/platformio/pickers/init.lua b/lua/platformio/pickers/init.lua index b9e6163c..ef13ca91 100644 --- a/lua/platformio/pickers/init.lua +++ b/lua/platformio/pickers/init.lua @@ -3,61 +3,70 @@ local M = {} local config = require('platformio').config local function load_backend(name) - local ok, backend = pcall(require, 'platformio.pickers.' .. name) - if ok then - return backend - end - return nil + local ok, backend = pcall(require, 'platformio.pickers.' .. name) + if ok then + return backend + end + return nil end local function get_backend() - local backend_name = config.picker_backend or 'auto' + local backend_name = config.picker_backend or 'auto' - if backend_name == 'telescope' then - return load_backend('telescope') or load_backend('ui_select') - end + if backend_name == 'telescope' then + return load_backend('telescope') or load_backend('ui_select') + end - if backend_name == 'ui_select' then - return load_backend('ui_select') - end + if backend_name == 'ui_select' then + return load_backend('ui_select') + end - return load_backend('telescope') or load_backend('ui_select') + return load_backend('telescope') or load_backend('ui_select') end function M.pick_board(boards, on_select) - local backend = get_backend() - if not backend then - vim.notify('No picker backend available for PlatformIO.', vim.log.levels.ERROR) - return - end - backend.pick_board(boards, on_select) + local backend = get_backend() + if not backend then + vim.notify('No picker backend available for PlatformIO.', vim.log.levels.ERROR) + return + end + backend.pick_board(boards, on_select) end function M.pick_framework(frameworks, on_select) - local backend = get_backend() - if not backend then - vim.notify('No picker backend available for PlatformIO.', vim.log.levels.ERROR) - return - end - backend.pick_framework(frameworks, on_select) + local backend = get_backend() + if not backend then + vim.notify('No picker backend available for PlatformIO.', vim.log.levels.ERROR) + return + end + backend.pick_framework(frameworks, on_select) end function M.pick_library(libraries, on_select) - local backend = get_backend() - if not backend then - vim.notify('No picker backend available for PlatformIO.', vim.log.levels.ERROR) - return - end - backend.pick_library(libraries, on_select) + local backend = get_backend() + if not backend then + vim.notify('No picker backend available for PlatformIO.', vim.log.levels.ERROR) + return + end + backend.pick_library(libraries, on_select) end function M.pick_terminal(terminals, on_select) - local backend = get_backend() - if not backend then - vim.notify('No picker backend available for PlatformIO.', vim.log.levels.ERROR) - return - end - backend.pick_terminal(terminals, on_select) + local backend = get_backend() + if not backend then + vim.notify('No picker backend available for PlatformIO.', vim.log.levels.ERROR) + return + end + backend.pick_terminal(terminals, on_select) +end + +function M.pick_env(envs, on_select) + local backend = get_backend() + if not backend then + vim.notify('No picker backend available for PlatformIO.', vim.log.levels.ERROR) + return + end + backend.pick_env(envs, on_select) end return M diff --git a/lua/platformio/pickers/ui_select.lua b/lua/platformio/pickers/ui_select.lua index aa2ead8d..0337a6f9 100644 --- a/lua/platformio/pickers/ui_select.lua +++ b/lua/platformio/pickers/ui_select.lua @@ -1,62 +1,72 @@ local M = {} local function board_label(board) - return string.format('%s | %s | %s', board.name or '', board.vendor or '', board.platform or '') + return string.format('%s | %s | %s', board.name or '', board.vendor or '', board.platform or '') end function M.pick_framework(frameworks, on_select) - vim.ui.select(frameworks, { - prompt = 'Select framework:', - format_item = function(item) - return item - end, - }, function(chosen) - if chosen then - on_select(chosen) - end - end) + vim.ui.select(frameworks, { + prompt = 'Select framework:', + format_item = function(item) + return item + end, + }, function(chosen) + if chosen then + on_select(chosen) + end + end) end function M.pick_board(boards, on_select) - vim.ui.select(boards, { - prompt = 'Select board:', - format_item = board_label, - }, function(chosen) - if chosen then - on_select(chosen) - end - end) + vim.ui.select(boards, { + prompt = 'Select board:', + format_item = board_label, + }, function(chosen) + if chosen then + on_select(chosen) + end + end) end function M.pick_library(libraries, on_select) - vim.ui.select(libraries, { - prompt = 'Select library:', - format_item = function(item) - local owner = (item.owner and item.owner.username) or '' - return string.format('%s | %s | %s', item.name or '', owner, item.description or '') - end, - }, function(chosen) - if chosen then - on_select(chosen) - end - end) + vim.ui.select(libraries, { + prompt = 'Select library:', + format_item = function(item) + local owner = (item.owner and item.owner.username) or '' + return string.format('%s | %s | %s', item.name or '', owner, item.description or '') + end, + }, function(chosen) + if chosen then + on_select(chosen) + end + end) end function M.pick_terminal(terminals, on_select) - vim.ui.select(terminals, { - prompt = 'Select a PIO terminal window:', - kind = 'PioTerminals', - format_item = function(item) - local is_hidden = vim.api.nvim_buf_is_loaded(item.term.bufnr) and (vim.fn.bufwinid(item.term.bufnr) == -1) - return string.format('%d:%s (hidden: %s)', item.term.id, item.termtype, tostring(is_hidden)) - end, - }, function(chosen) - if chosen then - on_select(chosen) - else - on_select(nil) - end - end) + vim.ui.select(terminals, { + prompt = 'Select a PIO terminal window:', + kind = 'PioTerminals', + format_item = function(item) + local is_hidden = vim.api.nvim_buf_is_loaded(item.term.bufnr) and (vim.fn.bufwinid(item.term.bufnr) == -1) + return string.format('%d:%s (hidden: %s)', item.term.id, item.termtype, tostring(is_hidden)) + end, + }, function(chosen) + if chosen then + on_select(chosen) + else + on_select(nil) + end + end) +end + +function M.pick_env(envs, on_select) + vim.ui.select(envs, { + prompt = 'Select an environment to use as default:', + }, function(chosen) + if chosen then + on_select(chosen) + end + end) end return M diff --git a/lua/platformio/pioenv.lua b/lua/platformio/pioenv.lua new file mode 100644 index 00000000..b7afd041 --- /dev/null +++ b/lua/platformio/pioenv.lua @@ -0,0 +1,30 @@ +local utils = require('platformio.utils') +local M = {} + +function pioenv() + if not utils.pio_install_check() then + return + end + utils.cd_pioini() + + -- TODO: execute target list command + local cmd = 'pio run --list-targets' + local stdio_stream = io.popen(cmd, 'r') + -- TODO: error checking + if type(stdio_stream) == type(string) then + vim.notify('opening stdio failed', vim.log.levels.ERROR) + return + end + local stream_string = io.read() + if os.execute('echo' .. '"' .. stream_string .. '" > ' .. utils.get_os_user_data_dir()) == true then + vim.notify('writing to file failed', vim.log.levels.ERROR) + stdio_stream:close() + return + end + stdio_stream:close() + -- TODO: parse output + -- TODO: execute picker + return +end + +return M From aba0b6e58bb79f3506a0de30ce02eb099b08035c Mon Sep 17 00:00:00 2001 From: Shoxx98 Date: Tue, 26 May 2026 14:40:07 +0200 Subject: [PATCH 07/19] actually registering the function --- lua/platformio/pioenv.lua | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/lua/platformio/pioenv.lua b/lua/platformio/pioenv.lua index b7afd041..d2064af2 100644 --- a/lua/platformio/pioenv.lua +++ b/lua/platformio/pioenv.lua @@ -1,7 +1,7 @@ local utils = require('platformio.utils') local M = {} -function pioenv() +function M.pioenv() if not utils.pio_install_check() then return end From 26d4324d89c9b99526e11510bedbb5d520bbcfb1 Mon Sep 17 00:00:00 2001 From: Shoxx98 Date: Tue, 26 May 2026 14:44:30 +0200 Subject: [PATCH 08/19] wrong name correction --- lua/platformio/utils.lua | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/lua/platformio/utils.lua b/lua/platformio/utils.lua index 8cc02cda..98cb1ce5 100644 --- a/lua/platformio/utils.lua +++ b/lua/platformio/utils.lua @@ -412,7 +412,7 @@ function M.get_os_user_data_dir() os_dirname = 'LOCALAPPDATA' else path_separator = '/' - os_dirname = 'XDG_STATE_DIR' + os_dirname = 'XDG_STATE_HOME' end return os.getenv(os_dirname) .. path_separator .. 'nvim' .. path_separator .. 'nvim-platformio' end From f658182ba84d5951602994e4814bfaf5d7d68b5e Mon Sep 17 00:00:00 2001 From: Shoxx98 Date: Tue, 2 Jun 2026 11:43:16 +0200 Subject: [PATCH 09/19] did some further iteration on functionality and testing out how the stdio streaming works with commands now --- lua/platformio/pioenv.lua | 64 ++++++++++++++++++++++++++++++++------- lua/platformio/utils.lua | 14 +++++++-- 2 files changed, 65 insertions(+), 13 deletions(-) diff --git a/lua/platformio/pioenv.lua b/lua/platformio/pioenv.lua index d2064af2..72303cf5 100644 --- a/lua/platformio/pioenv.lua +++ b/lua/platformio/pioenv.lua @@ -7,24 +7,66 @@ function M.pioenv() end utils.cd_pioini() + vim.notify('hi', vim.log.levels.INFO) + -- TODO: execute target list command local cmd = 'pio run --list-targets' - local stdio_stream = io.popen(cmd, 'r') + --- @type file*? + local stdio_stream + stdio_stream, _ = io.popen(cmd, 'r') -- TODO: error checking - if type(stdio_stream) == type(string) then - vim.notify('opening stdio failed', vim.log.levels.ERROR) - return - end - local stream_string = io.read() - if os.execute('echo' .. '"' .. stream_string .. '" > ' .. utils.get_os_user_data_dir()) == true then - vim.notify('writing to file failed', vim.log.levels.ERROR) - stdio_stream:close() + if not stdio_stream then + vim.notify('opening stdio or executing the cmd ' .. cmd .. 'failed', vim.log.levels.ERROR) return end - stdio_stream:close() + + io.input(stdio_stream) + vim.notify(io.read('*a'), vim.log.levels.INFO) + + -- table layout: + -- table header + -- ---------------------- + -- env_name_without_spaces some garbage + -- + -- env_name_without_spaces some garbage + -- ... + + -- discard the first few lines, since they are just table headers and decorators + -- _ = stdio_stream.read(stdio_stream, '*l') + -- _ = stdio_stream.read(stdio_stream, '*l') + + -- --- @type string[] + -- local table_lines = {} + -- --- @type integer | nil + -- local env_name_end + -- for line in stdio_stream.lines(stdio_stream, '*a') do + -- if line == '' then -- discard empty lines + -- goto continue + -- end + -- env_name_end, _ = string.find(line, ' ', 2) -- if cmd succeeds, the env name has to be at least 1 char long and contains no spaces. skip the first one and search for space + -- env_name_end = env_name_end - 1 + -- if env_name_end then -- would start at 1, regardless of start index, so we don't need to differentiate between 0 and nil + -- table.insert(table_lines, string.sub(line, env_name_end)) + -- else + -- vim.notify('formatting pio output failed on finding the first space char', vim.log.levels.ERROR) + -- return + -- end + -- ::continue:: + -- end + + -- local output_filepath = utils.make_os_user_data_dir() .. '/formatted_lines.md' + -- vim.notify(output_filepath, vim.log.levels.INFO) + -- for _, line in ipairs(table_lines) do + -- if not os.execute('echo "' .. line .. '" >> ' .. output_filepath) then + -- vim.notify('writing to file failed', vim.log.levels.ERROR) + -- stdio_stream:close() + -- return + -- end + -- end + + io.close(stdio_stream) -- TODO: parse output -- TODO: execute picker - return end return M diff --git a/lua/platformio/utils.lua b/lua/platformio/utils.lua index 98cb1ce5..d4ac39ca 100644 --- a/lua/platformio/utils.lua +++ b/lua/platformio/utils.lua @@ -402,11 +402,11 @@ function M.shell_cmd_blocking(command) return result end --- TODO: understand how to execute command and fill in the corresponding table +--- gets the os user data dir for windows or linux +---@return string data_dir_path function M.get_os_user_data_dir() --- @type string local os_dirname, path_separator - if is_windows then path_separator = '\\' os_dirname = 'LOCALAPPDATA' @@ -417,4 +417,14 @@ function M.get_os_user_data_dir() return os.getenv(os_dirname) .. path_separator .. 'nvim' .. path_separator .. 'nvim-platformio' end +--- creates the user data dir conditionally, based on its existence. +---@return string data_dir_path for performance +function M.make_os_user_data_dir() + local data_dir_path = M.get_os_user_data_dir() + if not os.execute('test -d ' .. data_dir_path) then + os.execute('mkdir -p ' .. data_dir_path) + end + return data_dir_path +end + return M From 6890283699138bfe24e705e641f48373b46920d1 Mon Sep 17 00:00:00 2001 From: Shoxx98 Date: Fri, 5 Jun 2026 14:18:15 +0200 Subject: [PATCH 10/19] formatting is good now --- lua/platformio/pioenv.lua | 53 ++++++++++++++++----------------------- lua/platformio/utils.lua | 3 ++- 2 files changed, 23 insertions(+), 33 deletions(-) diff --git a/lua/platformio/pioenv.lua b/lua/platformio/pioenv.lua index 72303cf5..2d30ffd1 100644 --- a/lua/platformio/pioenv.lua +++ b/lua/platformio/pioenv.lua @@ -2,13 +2,13 @@ local utils = require('platformio.utils') local M = {} function M.pioenv() + --- @type vim.log.levels + local loglevel = vim.log.levels.INFO if not utils.pio_install_check() then return end utils.cd_pioini() - vim.notify('hi', vim.log.levels.INFO) - -- TODO: execute target list command local cmd = 'pio run --list-targets' --- @type file*? @@ -21,7 +21,7 @@ function M.pioenv() end io.input(stdio_stream) - vim.notify(io.read('*a'), vim.log.levels.INFO) + -- vim.notify(io.read('*a'), vim.log.levels.INFO) -- table layout: -- table header @@ -35,37 +35,26 @@ function M.pioenv() -- _ = stdio_stream.read(stdio_stream, '*l') -- _ = stdio_stream.read(stdio_stream, '*l') - -- --- @type string[] - -- local table_lines = {} - -- --- @type integer | nil - -- local env_name_end - -- for line in stdio_stream.lines(stdio_stream, '*a') do - -- if line == '' then -- discard empty lines - -- goto continue - -- end - -- env_name_end, _ = string.find(line, ' ', 2) -- if cmd succeeds, the env name has to be at least 1 char long and contains no spaces. skip the first one and search for space - -- env_name_end = env_name_end - 1 - -- if env_name_end then -- would start at 1, regardless of start index, so we don't need to differentiate between 0 and nil - -- table.insert(table_lines, string.sub(line, env_name_end)) - -- else - -- vim.notify('formatting pio output failed on finding the first space char', vim.log.levels.ERROR) - -- return - -- end - -- ::continue:: - -- end - - -- local output_filepath = utils.make_os_user_data_dir() .. '/formatted_lines.md' - -- vim.notify(output_filepath, vim.log.levels.INFO) - -- for _, line in ipairs(table_lines) do - -- if not os.execute('echo "' .. line .. '" >> ' .. output_filepath) then - -- vim.notify('writing to file failed', vim.log.levels.ERROR) - -- stdio_stream:close() - -- return - -- end - -- end + --- @type string[] + local table_lines = {} + --- @type integer | nil + local env_name_end + _ = stdio_stream.read(stdio_stream, '*line') + _ = stdio_stream.read(stdio_stream, '*line') + for line in stdio_stream.lines(stdio_stream, '*l') do -- readmode is for the output of lines as var `line` + env_name_end, _ = string.find(line, ' ', 2) -- if cmd succeeds, the env name has to be at least 1 char long and contains no spaces. skip the first one and search for space + env_name_end = env_name_end - 1 + if env_name_end then -- would start at 1, regardless of start index, so we don't need to differentiate between 0 and nil + table.insert(table_lines, string.sub(line, 1, env_name_end)) + else + vim.notify('formatting pio output failed on finding the first space char', vim.log.levels.ERROR) + io.close(stdio_stream) + return + end + _ = stdio_stream.read(stdio_stream, '*line') + end io.close(stdio_stream) - -- TODO: parse output -- TODO: execute picker end diff --git a/lua/platformio/utils.lua b/lua/platformio/utils.lua index d4ac39ca..f92020c6 100644 --- a/lua/platformio/utils.lua +++ b/lua/platformio/utils.lua @@ -421,7 +421,8 @@ end ---@return string data_dir_path for performance function M.make_os_user_data_dir() local data_dir_path = M.get_os_user_data_dir() - if not os.execute('test -d ' .. data_dir_path) then + if os.execute('test -d ' .. data_dir_path) then + -- TODO: error handler os.execute('mkdir -p ' .. data_dir_path) end return data_dir_path From 7604a4dbe35945dcf8e34bebb073fd3710c152be Mon Sep 17 00:00:00 2001 From: Shoxx98 Date: Fri, 5 Jun 2026 14:24:56 +0200 Subject: [PATCH 11/19] now testing how to do ui_select --- lua/platformio/pioenv.lua | 8 ++++++-- 1 file changed, 6 insertions(+), 2 deletions(-) diff --git a/lua/platformio/pioenv.lua b/lua/platformio/pioenv.lua index 2d30ffd1..20ce947d 100644 --- a/lua/platformio/pioenv.lua +++ b/lua/platformio/pioenv.lua @@ -1,4 +1,5 @@ local utils = require('platformio.utils') +local picker = require('platformio.pickers') local M = {} function M.pioenv() @@ -36,7 +37,7 @@ function M.pioenv() -- _ = stdio_stream.read(stdio_stream, '*l') --- @type string[] - local table_lines = {} + local envs = {} --- @type integer | nil local env_name_end _ = stdio_stream.read(stdio_stream, '*line') @@ -45,7 +46,7 @@ function M.pioenv() env_name_end, _ = string.find(line, ' ', 2) -- if cmd succeeds, the env name has to be at least 1 char long and contains no spaces. skip the first one and search for space env_name_end = env_name_end - 1 if env_name_end then -- would start at 1, regardless of start index, so we don't need to differentiate between 0 and nil - table.insert(table_lines, string.sub(line, 1, env_name_end)) + table.insert(envs, string.sub(line, 1, env_name_end)) else vim.notify('formatting pio output failed on finding the first space char', vim.log.levels.ERROR) io.close(stdio_stream) @@ -56,6 +57,9 @@ function M.pioenv() io.close(stdio_stream) -- TODO: execute picker + picker.pick_env(envs, function(selected_env) + vim.notify(selected_env, loglevel) + end) end return M From 9bada475a51c8fb1ec3d48aa7e903ae17f532ee9 Mon Sep 17 00:00:00 2001 From: Shoxx98 Date: Fri, 5 Jun 2026 15:32:48 +0200 Subject: [PATCH 12/19] works on my machine (tm) --- lua/platformio/pioenv.lua | 7 +++++-- 1 file changed, 5 insertions(+), 2 deletions(-) diff --git a/lua/platformio/pioenv.lua b/lua/platformio/pioenv.lua index 20ce947d..36da55a6 100644 --- a/lua/platformio/pioenv.lua +++ b/lua/platformio/pioenv.lua @@ -1,6 +1,8 @@ local utils = require('platformio.utils') local picker = require('platformio.pickers') -local M = {} +local M = { + default_env = 'none', +} function M.pioenv() --- @type vim.log.levels @@ -55,10 +57,11 @@ function M.pioenv() _ = stdio_stream.read(stdio_stream, '*line') end + table.insert(envs, 'none') io.close(stdio_stream) -- TODO: execute picker picker.pick_env(envs, function(selected_env) - vim.notify(selected_env, loglevel) + M.default_env = selected_env end) end From 10b9676c23a81d0869ca1e0aad9a5d847b817b2f Mon Sep 17 00:00:00 2001 From: Shoxx98 Date: Fri, 5 Jun 2026 16:57:51 +0200 Subject: [PATCH 13/19] integration into existing commands --- lua/platformio/piocmd.lua | 28 ++++++++++-------- lua/platformio/piodebug.lua | 15 +++++----- lua/platformio/pioenv.lua | 36 +++++++++++++++++++++++ lua/platformio/piorun.lua | 57 +++++++++++++++++++------------------ 4 files changed, 89 insertions(+), 47 deletions(-) diff --git a/lua/platformio/piocmd.lua b/lua/platformio/piocmd.lua index 0f5fde5d..3f1f06cb 100644 --- a/lua/platformio/piocmd.lua +++ b/lua/platformio/piocmd.lua @@ -1,22 +1,26 @@ local utils = require('platformio.utils') +local env = require('platformio.pioenv') local M = {} function M.piocmd(cmd_table, direction) - if not utils.pio_install_check() then - return - end + if not utils.pio_install_check() then + return + end - utils.cd_pioini() + utils.cd_pioini() - if cmd_table[1] == '' then - utils.ToggleTerminal('', direction) - else - local cmd = 'pio ' - for _, v in pairs(cmd_table) do - cmd = cmd .. ' ' .. v + if cmd_table[1] == '' then + utils.ToggleTerminal('', direction) + else + local cmd = 'pio ' + for _, v in pairs(cmd_table) do + cmd = cmd .. ' ' .. v + end + if env.command_accepts_flag(cmd_table[1]) then + cmd = cmd .. env.get_flag() + end + utils.ToggleTerminal(cmd, direction) end - utils.ToggleTerminal(cmd, direction) - end end return M diff --git a/lua/platformio/piodebug.lua b/lua/platformio/piodebug.lua index d3786e54..2f343780 100644 --- a/lua/platformio/piodebug.lua +++ b/lua/platformio/piodebug.lua @@ -1,16 +1,17 @@ local utils = require('platformio.utils') +local env = require('platformio.pioenv') local M = {} function M.piodebug(args_table) - if not utils.pio_install_check() then - return - end + if not utils.pio_install_check() then + return + end - utils.cd_pioini() + utils.cd_pioini() - local command = 'pio debug --interface=gdb -- -x .pioinit' - -- local command = string.format('pio debug --interface=gdb -- -x .pioinit %s', utils.extra) - utils.ToggleTerminal(command, 'float') + local command = 'pio debug --interface=gdb ' .. env.get_flag() .. ' -- -x .pioinit' -- utils.extra + -- local command = string.format('pio debug --interface=gdb -- -x .pioinit %s', utils.extra) + utils.ToggleTerminal(command, 'float') end return M diff --git a/lua/platformio/pioenv.lua b/lua/platformio/pioenv.lua index 36da55a6..a7a3eba9 100644 --- a/lua/platformio/pioenv.lua +++ b/lua/platformio/pioenv.lua @@ -65,4 +65,40 @@ function M.pioenv() end) end +--- function to use for other modules to get the env flag +---@return string env_flag either an empty string, or a string with the flag +function M.get_flag() + if M.default_env ~= 'none' then + return ' -e ' .. M.default_env + end + return '' +end + +--- function to determine if a pio-command accepts the env flag +---@param cmd string name of command that will be run +---@return boolean accepts_flag is true if cmd accepts the env flag +function M.command_accepts_flag(cmd) + local true_table = { + access = false, + account = false, + boards = false, + check = true, + ci = true, + debug = true, + device = false, + home = false, + org = false, + pkg = false, + project = false, + remote = false, + run = true, + setting = false, + system = false, + team = false, + test = true, + upgrade = false, + } + return true_table[cmd] +end + return M diff --git a/lua/platformio/piorun.lua b/lua/platformio/piorun.lua index 89020f20..dc43a48b 100644 --- a/lua/platformio/piorun.lua +++ b/lua/platformio/piorun.lua @@ -1,48 +1,49 @@ local M = {} local utils = require('platformio.utils') +local env = require('platformio.pioenv') function M.piobuild() - utils.cd_pioini() - local command = 'pio run' -- .. utils.extra - utils.ToggleTerminal(command, 'float') + utils.cd_pioini() + local command = 'pio run' .. env.get_flag() -- .. utils.extra + utils.ToggleTerminal(command, 'float') end function M.pioupload() - utils.cd_pioini() - local command = 'pio run --target upload' -- .. utils.extra - utils.ToggleTerminal(command, 'float') + utils.cd_pioini() + local command = 'pio run --target upload' .. env.get_flag() -- .. utils.extra + utils.ToggleTerminal(command, 'float') end function M.piouploadfs() - utils.cd_pioini() - local command = 'pio run --target uploadfs' -- .. utils.extra - utils.ToggleTerminal(command, 'float') + utils.cd_pioini() + local command = 'pio run --target uploadfs' .. env.get_flag() -- .. utils.extra + utils.ToggleTerminal(command, 'float') end function M.pioclean() - utils.cd_pioini() - local command = 'pio run --target clean' -- .. utils.extra - utils.ToggleTerminal(command, 'float') + utils.cd_pioini() + local command = 'pio run --target clean' .. env.get_flag() -- .. utils.extra + utils.ToggleTerminal(command, 'float') end function M.piorun(arg_table) - if not utils.pio_install_check() then - return - end - if arg_table[1] == '' then - M.pioupload() - elseif arg_table[1] == 'upload' then - M.pioupload() - elseif arg_table[1] == 'uploadfs' then - M.piouploadfs() - elseif arg_table[1] == 'build' then - M.piobuild() - elseif arg_table[1] == 'clean' then - M.pioclean() - else - vim.notify('Invalid argument: build, upload, uploadfs or clean', vim.log.levels.WARN) - end + if not utils.pio_install_check() then + return + end + if arg_table[1] == '' then + M.pioupload() + elseif arg_table[1] == 'upload' then + M.pioupload() + elseif arg_table[1] == 'uploadfs' then + M.piouploadfs() + elseif arg_table[1] == 'build' then + M.piobuild() + elseif arg_table[1] == 'clean' then + M.pioclean() + else + vim.notify('Invalid argument: build, upload, uploadfs or clean', vim.log.levels.WARN) + end end return M From 860bf70c84a6e90b6264e1eeaafc29fbe981d125 Mon Sep 17 00:00:00 2001 From: Shoxx98 Date: Fri, 5 Jun 2026 16:59:51 +0200 Subject: [PATCH 14/19] telescope picker and formatting --- lua/platformio/pickers/telescope.lua | 375 ++++++++++++++------------- 1 file changed, 199 insertions(+), 176 deletions(-) diff --git a/lua/platformio/pickers/telescope.lua b/lua/platformio/pickers/telescope.lua index d180099b..8ab29e6b 100644 --- a/lua/platformio/pickers/telescope.lua +++ b/lua/platformio/pickers/telescope.lua @@ -11,201 +11,224 @@ local previewers = require('telescope.previewers') local utils = require('platformio.utils') local function boardentry_maker(opts) - local displayer = entry_display.create({ - separator = '▏', - items = { - { width = 35 }, - { width = 20 }, - { width = 15 }, - }, - }) - - local make_display = function(entry) - return displayer({ - entry.value.name, - entry.value.vendor, - entry.value.platform, + local displayer = entry_display.create({ + separator = '▏', + items = { + { width = 35 }, + { width = 20 }, + { width = 15 }, + }, }) - end - return function(entry) - return make_entry.set_default_entry_mt({ - value = { - id = entry.id, - name = entry.name, - vendor = entry.vendor, - platform = entry.platform, - data = entry, - }, - ordinal = entry.name .. ' ' .. entry.vendor .. ' ' .. entry.platform, - display = make_display, - }, opts) - end + local make_display = function(entry) + return displayer({ + entry.value.name, + entry.value.vendor, + entry.value.platform, + }) + end + + return function(entry) + return make_entry.set_default_entry_mt({ + value = { + id = entry.id, + name = entry.name, + vendor = entry.vendor, + platform = entry.platform, + data = entry, + }, + ordinal = entry.name .. ' ' .. entry.vendor .. ' ' .. entry.platform, + display = make_display, + }, opts) + end end local function libentry_maker(opts) - local displayer = entry_display.create({ - separator = '▏', - items = { - { width = 20 }, - { width = 20 }, - { remaining = true }, - }, - }) - - local make_display = function(entry) - return displayer({ - entry.value.name, - entry.value.owner, - entry.value.description, + local displayer = entry_display.create({ + separator = '▏', + items = { + { width = 20 }, + { width = 20 }, + { remaining = true }, + }, }) - end - return function(entry) - return make_entry.set_default_entry_mt({ - value = { - name = entry.name, - owner = entry.owner and entry.owner.username or '', - description = entry.description, - data = entry, - }, - ordinal = (entry.name or '') .. ' ' .. (entry.owner and entry.owner.username or '') .. ' ' .. (entry.description or ''), - display = make_display, - }, opts) - end + local make_display = function(entry) + return displayer({ + entry.value.name, + entry.value.owner, + entry.value.description, + }) + end + + return function(entry) + return make_entry.set_default_entry_mt({ + value = { + name = entry.name, + owner = entry.owner and entry.owner.username or '', + description = entry.description, + data = entry, + }, + ordinal = (entry.name or '') .. ' ' .. (entry.owner and entry.owner.username or '') .. ' ' .. (entry.description or ''), + display = make_display, + }, opts) + end end function M.pick_framework(frameworks, on_select) - local opts = {} - pickers - .new(opts, { - prompt_title = 'frameworks', - finder = finders.new_table({ - results = frameworks, - }), - attach_mappings = function(prompt_bufnr, _) - actions.select_default:replace(function() - actions.close(prompt_bufnr) - local selection = action_state.get_selected_entry() - if selection then - on_select(selection[1] or selection.value) - end - end) - return true - end, - sorter = telescope_conf.generic_sorter(opts), - }) - :find() + local opts = {} + pickers + .new(opts, { + prompt_title = 'frameworks', + finder = finders.new_table({ + results = frameworks, + }), + attach_mappings = function(prompt_bufnr, _) + actions.select_default:replace(function() + actions.close(prompt_bufnr) + local selection = action_state.get_selected_entry() + if selection then + on_select(selection[1] or selection.value) + end + end) + return true + end, + sorter = telescope_conf.generic_sorter(opts), + }) + :find() end function M.pick_board(boards, on_select) - local opts = {} - pickers - .new(opts, { - prompt_title = 'Boards', - finder = finders.new_table({ - results = boards, - entry_maker = opts.entry_maker or boardentry_maker(opts), - }), - attach_mappings = function(prompt_bufnr, _) - actions.select_default:replace(function() - actions.close(prompt_bufnr) - local selection = action_state.get_selected_entry() - if selection and selection.value then - on_select(selection.value.data) - end - end) - return true - end, - previewer = previewers.new_buffer_previewer({ - title = 'Board Info', - define_preview = function(self, entry, _) - local json = utils.strsplit(vim.inspect(entry.value.data), '\n') - local bufnr = self.state.bufnr - vim.api.nvim_buf_set_lines(bufnr, 0, -1, false, json) - vim.api.nvim_set_option_value('filetype', 'lua', { buf = bufnr }) - vim.defer_fn(function() - local win = self.state.winid - vim.api.nvim_set_option_value('wrap', true, { scope = 'local', win = win }) - vim.api.nvim_set_option_value('linebreak', true, { scope = 'local', win = win }) - vim.api.nvim_set_option_value('wrapmargin', 2, { buf = bufnr }) - end, 0) - end, - }), - sorter = telescope_conf.generic_sorter(opts), - }) - :find() + local opts = {} + pickers + .new(opts, { + prompt_title = 'Boards', + finder = finders.new_table({ + results = boards, + entry_maker = opts.entry_maker or boardentry_maker(opts), + }), + attach_mappings = function(prompt_bufnr, _) + actions.select_default:replace(function() + actions.close(prompt_bufnr) + local selection = action_state.get_selected_entry() + if selection and selection.value then + on_select(selection.value.data) + end + end) + return true + end, + previewer = previewers.new_buffer_previewer({ + title = 'Board Info', + define_preview = function(self, entry, _) + local json = utils.strsplit(vim.inspect(entry.value.data), '\n') + local bufnr = self.state.bufnr + vim.api.nvim_buf_set_lines(bufnr, 0, -1, false, json) + vim.api.nvim_set_option_value('filetype', 'lua', { buf = bufnr }) + vim.defer_fn(function() + local win = self.state.winid + vim.api.nvim_set_option_value('wrap', true, { scope = 'local', win = win }) + vim.api.nvim_set_option_value('linebreak', true, { scope = 'local', win = win }) + vim.api.nvim_set_option_value('wrapmargin', 2, { buf = bufnr }) + end, 0) + end, + }), + sorter = telescope_conf.generic_sorter(opts), + }) + :find() end function M.pick_library(libraries, on_select) - local opts = {} - pickers - .new(opts, { - prompt_title = 'Libraries', - finder = finders.new_table({ - results = libraries, - entry_maker = opts.entry_maker or libentry_maker(opts), - }), - attach_mappings = function(prompt_bufnr, _) - actions.select_default:replace(function() - actions.close(prompt_bufnr) - local selection = action_state.get_selected_entry() - if selection and selection.value then - on_select(selection.value.data) - end - end) - return true - end, - previewer = previewers.new_buffer_previewer({ - title = 'Package Info', - define_preview = function(self, entry, _) - local json = utils.strsplit(vim.inspect(entry.value.data), '\n') - local bufnr = self.state.bufnr - vim.api.nvim_buf_set_lines(bufnr, 0, -1, false, json) - vim.api.nvim_set_option_value('filetype', 'lua', { buf = bufnr }) - vim.defer_fn(function() - local win = self.state.winid - vim.api.nvim_set_option_value('wrap', true, { scope = 'local', win = win }) - vim.api.nvim_set_option_value('linebreak', true, { scope = 'local', win = win }) - vim.api.nvim_set_option_value('wrapmargin', 2, { buf = bufnr }) - end, 0) - end, - }), - sorter = telescope_conf.generic_sorter(opts), - }) - :find() + local opts = {} + pickers + .new(opts, { + prompt_title = 'Libraries', + finder = finders.new_table({ + results = libraries, + entry_maker = opts.entry_maker or libentry_maker(opts), + }), + attach_mappings = function(prompt_bufnr, _) + actions.select_default:replace(function() + actions.close(prompt_bufnr) + local selection = action_state.get_selected_entry() + if selection and selection.value then + on_select(selection.value.data) + end + end) + return true + end, + previewer = previewers.new_buffer_previewer({ + title = 'Package Info', + define_preview = function(self, entry, _) + local json = utils.strsplit(vim.inspect(entry.value.data), '\n') + local bufnr = self.state.bufnr + vim.api.nvim_buf_set_lines(bufnr, 0, -1, false, json) + vim.api.nvim_set_option_value('filetype', 'lua', { buf = bufnr }) + vim.defer_fn(function() + local win = self.state.winid + vim.api.nvim_set_option_value('wrap', true, { scope = 'local', win = win }) + vim.api.nvim_set_option_value('linebreak', true, { scope = 'local', win = win }) + vim.api.nvim_set_option_value('wrapmargin', 2, { buf = bufnr }) + end, 0) + end, + }), + sorter = telescope_conf.generic_sorter(opts), + }) + :find() end function M.pick_terminal(terminals, on_select) - local opts = {} - pickers - .new(opts, { - prompt_title = 'PIO terminals', - finder = finders.new_table({ - results = terminals, - entry_maker = function(entry) - local is_hidden = vim.api.nvim_buf_is_loaded(entry.term.bufnr) and (vim.fn.bufwinid(entry.term.bufnr) == -1) - local label = string.format('%d:%s (hidden: %s)', entry.term.id, entry.termtype, tostring(is_hidden)) - return { - value = entry, - display = label, - ordinal = label, - } - end, - }), - attach_mappings = function(prompt_bufnr, _) - actions.select_default:replace(function() - actions.close(prompt_bufnr) - local selection = action_state.get_selected_entry() - if selection and selection.value then - on_select(selection.value) - end - end) - return true - end, - sorter = telescope_conf.generic_sorter(opts), - }) - :find() + local opts = {} + pickers + .new(opts, { + prompt_title = 'PIO terminals', + finder = finders.new_table({ + results = terminals, + entry_maker = function(entry) + local is_hidden = vim.api.nvim_buf_is_loaded(entry.term.bufnr) and (vim.fn.bufwinid(entry.term.bufnr) == -1) + local label = string.format('%d:%s (hidden: %s)', entry.term.id, entry.termtype, tostring(is_hidden)) + return { + value = entry, + display = label, + ordinal = label, + } + end, + }), + attach_mappings = function(prompt_bufnr, _) + actions.select_default:replace(function() + actions.close(prompt_bufnr) + local selection = action_state.get_selected_entry() + if selection and selection.value then + on_select(selection.value) + end + end) + return true + end, + sorter = telescope_conf.generic_sorter(opts), + }) + :find() +end + +function M.pick_env(envs, on_select) + local opts = {} + pickers + .new(opts, { + prompt_title = 'Environments', + finder = finders.new_table({ + results = envs, + }), + attach_mappings = function(prompt_bufnr, _) + actions.select_default:replace(function() + actions.close(prompt_bufnr) + local selection = action_state.get_selected_entry() + if selection and selection.value then + on_select(selection.value) + end + end) + return true + end, + sorter = telescope_conf.generic_sorter(opts), + }) + :find() end return M From a092b8ae2eea5a56f963112d88b77fdbf21b78b3 Mon Sep 17 00:00:00 2001 From: Shoxx98 Date: Fri, 5 Jun 2026 18:30:19 +0200 Subject: [PATCH 15/19] windows compatibility of utils that are unused rn --- lua/platformio/utils.lua | 7 ++++--- 1 file changed, 4 insertions(+), 3 deletions(-) diff --git a/lua/platformio/utils.lua b/lua/platformio/utils.lua index f92020c6..3942005d 100644 --- a/lua/platformio/utils.lua +++ b/lua/platformio/utils.lua @@ -421,10 +421,11 @@ end ---@return string data_dir_path for performance function M.make_os_user_data_dir() local data_dir_path = M.get_os_user_data_dir() - if os.execute('test -d ' .. data_dir_path) then - -- TODO: error handler - os.execute('mkdir -p ' .. data_dir_path) + local cmd = 'mkdir ' .. data_dir_path + if not is_windows then + cmd = cmd .. ' -p' end + os.execute(cmd) return data_dir_path end From 0a2e1de9a4beb30c53b283bb96776cdaa254a2f0 Mon Sep 17 00:00:00 2001 From: Shoxx98 Date: Fri, 5 Jun 2026 18:42:06 +0200 Subject: [PATCH 16/19] changing back to an indent_width of 2 for pull request --- .stylua.toml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.stylua.toml b/.stylua.toml index 41c32ae4..89e2ef8e 100644 --- a/.stylua.toml +++ b/.stylua.toml @@ -1,6 +1,6 @@ column_width = 160 line_endings = "Unix" indent_type = "Spaces" -indent_width = 4 +indent_width = 2 quote_style = "AutoPreferSingle" call_parentheses = "Always" From 1275e398b5491e428933c7bbadd88438ab1e4bdc Mon Sep 17 00:00:00 2001 From: Shoxx98 Date: Fri, 5 Jun 2026 18:44:01 +0200 Subject: [PATCH 17/19] reverting all the formatting back to OG standards --- lua/platformio/piocmd.lua | 30 +- lua/platformio/piodebug.lua | 14 +- lua/platformio/pioenv.lua | 154 ++++---- lua/platformio/pioinit.lua | 80 ++-- lua/platformio/piolsp.lua | 196 +++++----- lua/platformio/piorun.lua | 56 +-- lua/platformio/piotermlist.lua | 62 +-- lua/platformio/utils.lua | 664 ++++++++++++++++----------------- plugin/platformio.lua | 82 ++-- 9 files changed, 669 insertions(+), 669 deletions(-) diff --git a/lua/platformio/piocmd.lua b/lua/platformio/piocmd.lua index 3f1f06cb..5450d8a4 100644 --- a/lua/platformio/piocmd.lua +++ b/lua/platformio/piocmd.lua @@ -3,24 +3,24 @@ local env = require('platformio.pioenv') local M = {} function M.piocmd(cmd_table, direction) - if not utils.pio_install_check() then - return - end + if not utils.pio_install_check() then + return + end - utils.cd_pioini() + utils.cd_pioini() - if cmd_table[1] == '' then - utils.ToggleTerminal('', direction) - else - local cmd = 'pio ' - for _, v in pairs(cmd_table) do - cmd = cmd .. ' ' .. v - end - if env.command_accepts_flag(cmd_table[1]) then - cmd = cmd .. env.get_flag() - end - utils.ToggleTerminal(cmd, direction) + if cmd_table[1] == '' then + utils.ToggleTerminal('', direction) + else + local cmd = 'pio ' + for _, v in pairs(cmd_table) do + cmd = cmd .. ' ' .. v + end + if env.command_accepts_flag(cmd_table[1]) then + cmd = cmd .. env.get_flag() end + utils.ToggleTerminal(cmd, direction) + end end return M diff --git a/lua/platformio/piodebug.lua b/lua/platformio/piodebug.lua index 2f343780..67bb563f 100644 --- a/lua/platformio/piodebug.lua +++ b/lua/platformio/piodebug.lua @@ -3,15 +3,15 @@ local env = require('platformio.pioenv') local M = {} function M.piodebug(args_table) - if not utils.pio_install_check() then - return - end + if not utils.pio_install_check() then + return + end - utils.cd_pioini() + utils.cd_pioini() - local command = 'pio debug --interface=gdb ' .. env.get_flag() .. ' -- -x .pioinit' -- utils.extra - -- local command = string.format('pio debug --interface=gdb -- -x .pioinit %s', utils.extra) - utils.ToggleTerminal(command, 'float') + local command = 'pio debug --interface=gdb ' .. env.get_flag() .. ' -- -x .pioinit' -- utils.extra + -- local command = string.format('pio debug --interface=gdb -- -x .pioinit %s', utils.extra) + utils.ToggleTerminal(command, 'float') end return M diff --git a/lua/platformio/pioenv.lua b/lua/platformio/pioenv.lua index a7a3eba9..2a4a136e 100644 --- a/lua/platformio/pioenv.lua +++ b/lua/platformio/pioenv.lua @@ -1,104 +1,104 @@ local utils = require('platformio.utils') local picker = require('platformio.pickers') local M = { - default_env = 'none', + default_env = 'none', } function M.pioenv() - --- @type vim.log.levels - local loglevel = vim.log.levels.INFO - if not utils.pio_install_check() then - return - end - utils.cd_pioini() + --- @type vim.log.levels + local loglevel = vim.log.levels.INFO + if not utils.pio_install_check() then + return + end + utils.cd_pioini() - -- TODO: execute target list command - local cmd = 'pio run --list-targets' - --- @type file*? - local stdio_stream - stdio_stream, _ = io.popen(cmd, 'r') - -- TODO: error checking - if not stdio_stream then - vim.notify('opening stdio or executing the cmd ' .. cmd .. 'failed', vim.log.levels.ERROR) - return - end + -- TODO: execute target list command + local cmd = 'pio run --list-targets' + --- @type file*? + local stdio_stream + stdio_stream, _ = io.popen(cmd, 'r') + -- TODO: error checking + if not stdio_stream then + vim.notify('opening stdio or executing the cmd ' .. cmd .. 'failed', vim.log.levels.ERROR) + return + end - io.input(stdio_stream) - -- vim.notify(io.read('*a'), vim.log.levels.INFO) + io.input(stdio_stream) + -- vim.notify(io.read('*a'), vim.log.levels.INFO) - -- table layout: - -- table header - -- ---------------------- - -- env_name_without_spaces some garbage - -- - -- env_name_without_spaces some garbage - -- ... + -- table layout: + -- table header + -- ---------------------- + -- env_name_without_spaces some garbage + -- + -- env_name_without_spaces some garbage + -- ... - -- discard the first few lines, since they are just table headers and decorators - -- _ = stdio_stream.read(stdio_stream, '*l') - -- _ = stdio_stream.read(stdio_stream, '*l') + -- discard the first few lines, since they are just table headers and decorators + -- _ = stdio_stream.read(stdio_stream, '*l') + -- _ = stdio_stream.read(stdio_stream, '*l') - --- @type string[] - local envs = {} - --- @type integer | nil - local env_name_end - _ = stdio_stream.read(stdio_stream, '*line') - _ = stdio_stream.read(stdio_stream, '*line') - for line in stdio_stream.lines(stdio_stream, '*l') do -- readmode is for the output of lines as var `line` - env_name_end, _ = string.find(line, ' ', 2) -- if cmd succeeds, the env name has to be at least 1 char long and contains no spaces. skip the first one and search for space - env_name_end = env_name_end - 1 - if env_name_end then -- would start at 1, regardless of start index, so we don't need to differentiate between 0 and nil - table.insert(envs, string.sub(line, 1, env_name_end)) - else - vim.notify('formatting pio output failed on finding the first space char', vim.log.levels.ERROR) - io.close(stdio_stream) - return - end - _ = stdio_stream.read(stdio_stream, '*line') + --- @type string[] + local envs = {} + --- @type integer | nil + local env_name_end + _ = stdio_stream.read(stdio_stream, '*line') + _ = stdio_stream.read(stdio_stream, '*line') + for line in stdio_stream.lines(stdio_stream, '*l') do -- readmode is for the output of lines as var `line` + env_name_end, _ = string.find(line, ' ', 2) -- if cmd succeeds, the env name has to be at least 1 char long and contains no spaces. skip the first one and search for space + env_name_end = env_name_end - 1 + if env_name_end then -- would start at 1, regardless of start index, so we don't need to differentiate between 0 and nil + table.insert(envs, string.sub(line, 1, env_name_end)) + else + vim.notify('formatting pio output failed on finding the first space char', vim.log.levels.ERROR) + io.close(stdio_stream) + return end + _ = stdio_stream.read(stdio_stream, '*line') + end - table.insert(envs, 'none') - io.close(stdio_stream) - -- TODO: execute picker - picker.pick_env(envs, function(selected_env) - M.default_env = selected_env - end) + table.insert(envs, 'none') + io.close(stdio_stream) + -- TODO: execute picker + picker.pick_env(envs, function(selected_env) + M.default_env = selected_env + end) end --- function to use for other modules to get the env flag ---@return string env_flag either an empty string, or a string with the flag function M.get_flag() - if M.default_env ~= 'none' then - return ' -e ' .. M.default_env - end - return '' + if M.default_env ~= 'none' then + return ' -e ' .. M.default_env + end + return '' end --- function to determine if a pio-command accepts the env flag ---@param cmd string name of command that will be run ---@return boolean accepts_flag is true if cmd accepts the env flag function M.command_accepts_flag(cmd) - local true_table = { - access = false, - account = false, - boards = false, - check = true, - ci = true, - debug = true, - device = false, - home = false, - org = false, - pkg = false, - project = false, - remote = false, - run = true, - setting = false, - system = false, - team = false, - test = true, - upgrade = false, - } - return true_table[cmd] + local true_table = { + access = false, + account = false, + boards = false, + check = true, + ci = true, + debug = true, + device = false, + home = false, + org = false, + pkg = false, + project = false, + remote = false, + run = true, + setting = false, + system = false, + team = false, + test = true, + upgrade = false, + } + return true_table[cmd] end return M diff --git a/lua/platformio/pioinit.lua b/lua/platformio/pioinit.lua index 47aa16a7..2cbb0f9f 100644 --- a/lua/platformio/pioinit.lua +++ b/lua/platformio/pioinit.lua @@ -5,59 +5,59 @@ local picker = require('platformio.pickers') local boilerplate_gen = require('platformio.boilerplate').boilerplate_gen local function init_project(board_details, selected_framework) - local framework = selected_framework - if framework == 'none' then - framework = '' - end - local command = 'pio project init --board ' .. board_details.id .. ' --project-option "framework=' .. framework .. '"' - utils.ToggleTerminal(command, 'float', function() - vim.cmd(':PioLSP') - boilerplate_gen(framework) - end) + local framework = selected_framework + if framework == 'none' then + framework = '' + end + local command = 'pio project init --board ' .. board_details.id .. ' --project-option "framework=' .. framework .. '"' + utils.ToggleTerminal(command, 'float', function() + vim.cmd(':PioLSP') + boilerplate_gen(framework) + end) end local function pick_framework(board_details) - local framework_list = vim.list_extend({ 'none' }, board_details.frameworks or {}) - picker.pick_framework(framework_list, function(selected_framework) - init_project(board_details, selected_framework) - end) + local framework_list = vim.list_extend({ 'none' }, board_details.frameworks or {}) + picker.pick_framework(framework_list, function(selected_framework) + init_project(board_details, selected_framework) + end) end local function pick_board(boards) - picker.pick_board(boards, function(selected_board) - pick_framework(selected_board) - end) + picker.pick_board(boards, function(selected_board) + pick_framework(selected_board) + end) end function M.pioinit() - if not utils.pio_install_check() then - return - end - - -- Read stdout - local command = 'pio boards --json-output' - local handle = io.popen(command .. utils.devNul) + if not utils.pio_install_check() then + return + end + + -- Read stdout + local command = 'pio boards --json-output' + local handle = io.popen(command .. utils.devNul) + if not handle then + return + end + local json_str = handle:read('*a') + handle:close() + + if #json_str == 0 then + -- read stderr + handle = io.popen(command .. ' 2>&1') if not handle then - return + return end - local json_str = handle:read('*a') + local command_output = handle:read('*a') handle:close() + vim.notify('Some error occured while executing `' .. command .. "`', command output: \n", vim.log.levels.WARN) + print(command_output) + return + end - if #json_str == 0 then - -- read stderr - handle = io.popen(command .. ' 2>&1') - if not handle then - return - end - local command_output = handle:read('*a') - handle:close() - vim.notify('Some error occured while executing `' .. command .. "`', command output: \n", vim.log.levels.WARN) - print(command_output) - return - end - - local json_data = vim.json.decode(json_str) - pick_board(json_data or {}) + local json_data = vim.json.decode(json_str) + pick_board(json_data or {}) end return M diff --git a/lua/platformio/piolsp.lua b/lua/platformio/piolsp.lua index e306bb03..f22ceb73 100644 --- a/lua/platformio/piolsp.lua +++ b/lua/platformio/piolsp.lua @@ -4,134 +4,134 @@ local utils = require('platformio.utils') local config = require('platformio').config local function escape_flags(flags) - local escaped_flags = {} - - for _, flag in ipairs(flags) do - local escaped = flag - escaped = escaped:gsub('\\', '\\\\') -- Escape backslashes first - escaped = escaped:gsub('"', '\\"') -- Escape double quotes (for -D macros) - -- Escape parentheses (common in include paths) - escaped = escaped:gsub('%(', '\\(') - escaped = escaped:gsub('%)', '\\)') - escaped = escaped:gsub('%s+', '\\ ') - table.insert(escaped_flags, escaped) - end - - return escaped_flags + local escaped_flags = {} + + for _, flag in ipairs(flags) do + local escaped = flag + escaped = escaped:gsub('\\', '\\\\') -- Escape backslashes first + escaped = escaped:gsub('"', '\\"') -- Escape double quotes (for -D macros) + -- Escape parentheses (common in include paths) + escaped = escaped:gsub('%(', '\\(') + escaped = escaped:gsub('%)', '\\)') + escaped = escaped:gsub('%s+', '\\ ') + table.insert(escaped_flags, escaped) + end + + return escaped_flags end local function process_ccls() - local flags_allowed = { '%', '-W', '-std' } + local flags_allowed = { '%', '-W', '-std' } - local f = io.open(vim.fs.joinpath(vim.g.platformioRootDir, '.ccls'), 'rb') - if not f then - vim.notify('.ccls file not found', vim.log.levels.ERROR) - return {} - end + local f = io.open(vim.fs.joinpath(vim.g.platformioRootDir, '.ccls'), 'rb') + if not f then + vim.notify('.ccls file not found', vim.log.levels.ERROR) + return {} + end - local compiler = f:read() - local build_flags = { compiler } + local compiler = f:read() + local build_flags = { compiler } - for line in f:lines() do - if #line == 0 or string.sub(line, 1, 1) == '#' then - goto continue - end + for line in f:lines() do + if #line == 0 or string.sub(line, 1, 1) == '#' then + goto continue + end - if utils.check_prefix(line, '-I') or utils.check_prefix(line, '-D') then - table.insert(build_flags, line) - end - if utils.check_prefix(line, '%cpp') then - splitted = utils.strsplit(line, ' ') - for _, flag in ipairs(splitted) do - for _, flag_check in ipairs(flags_allowed) do - if utils.check_prefix(flag, flag_check) then - table.insert(build_flags, flag) - end - end - end + if utils.check_prefix(line, '-I') or utils.check_prefix(line, '-D') then + table.insert(build_flags, line) + end + if utils.check_prefix(line, '%cpp') then + splitted = utils.strsplit(line, ' ') + for _, flag in ipairs(splitted) do + for _, flag_check in ipairs(flags_allowed) do + if utils.check_prefix(flag, flag_check) then + table.insert(build_flags, flag) + end end - - ::continue:: + end end - f:close() + ::continue:: + end + + f:close() - return escape_flags(build_flags) + return escape_flags(build_flags) end local function gen_compile_commands(build_flags) - local project_root = vim.g.platformioRootDir - local build_cmd = '' - for _, flag in ipairs(build_flags) do - build_cmd = build_cmd .. flag .. ' ' - end - - local entry = { { - directory = project_root, - file = vim.fs.joinpath(project_root, 'src', 'main.cpp'), - command = build_cmd, - } } - - local f = io.open(vim.fs.joinpath(project_root, 'compile_commands.json'), 'w') - f:write(vim.json.encode(entry, { indent = ' ', sort_keys = true })) - f:close() + local project_root = vim.g.platformioRootDir + local build_cmd = '' + for _, flag in ipairs(build_flags) do + build_cmd = build_cmd .. flag .. ' ' + end + + local entry = { { + directory = project_root, + file = vim.fs.joinpath(project_root, 'src', 'main.cpp'), + command = build_cmd, + } } + + local f = io.open(vim.fs.joinpath(project_root, 'compile_commands.json'), 'w') + f:write(vim.json.encode(entry, { indent = ' ', sort_keys = true })) + f:close() end local function gitignore_lsp_configs(config_file) - local gitignore_path = vim.fs.joinpath(vim.g.platformioRootDir, '.gitignore') - local file = io.open(gitignore_path, 'r') - local pattern = '^%s*' .. vim.pesc(config_file) .. '%s*$' - - if file then - for line in file:lines() do - if line:match(pattern) then - file:close() - return - end - end + local gitignore_path = vim.fs.joinpath(vim.g.platformioRootDir, '.gitignore') + local file = io.open(gitignore_path, 'r') + local pattern = '^%s*' .. vim.pesc(config_file) .. '%s*$' + + if file then + for line in file:lines() do + if line:match(pattern) then file:close() + return + end end - - file = io.open(gitignore_path, 'a') - file:write(config_file .. '\n') file:close() + end + + file = io.open(gitignore_path, 'a') + file:write(config_file .. '\n') + file:close() end function M.gen_clangd_config() - local build_flags = process_ccls() - gen_compile_commands(build_flags) + local build_flags = process_ccls() + gen_compile_commands(build_flags) end function M.piolsp() - if not utils.pio_install_check() then - return - end - utils.cd_pioini() - - if config.lsp == 'clangd' and config.clangd_source == 'compiledb' then - utils.shell_cmd_blocking('pio run -t compiledb') - gitignore_lsp_configs('compile_commands.json') + if not utils.pio_install_check() then + return + end + utils.cd_pioini() + + if config.lsp == 'clangd' and config.clangd_source == 'compiledb' then + utils.shell_cmd_blocking('pio run -t compiledb') + gitignore_lsp_configs('compile_commands.json') + else + utils.shell_cmd_blocking('pio project init --ide=vim') + + if config.lsp == 'clangd' then + M.gen_clangd_config() + gitignore_lsp_configs('compile_commands.json') + -- TODO: check for file first + os.remove(vim.fs.joinpath(vim.g.platformioRootDir, '.ccls')) else - utils.shell_cmd_blocking('pio project init --ide=vim') - - if config.lsp == 'clangd' then - M.gen_clangd_config() - gitignore_lsp_configs('compile_commands.json') - -- TODO: check for file first - os.remove(vim.fs.joinpath(vim.g.platformioRootDir, '.ccls')) - else - gitignore_lsp_configs('.ccls') - end + gitignore_lsp_configs('.ccls') end - vim.notify('LSP config generation completed!', vim.log.levels.INFO) + end + vim.notify('LSP config generation completed!', vim.log.levels.INFO) - if vim.fn.has('nvim-0.12') then - if #vim.lsp.get_clients() > 0 then - vim.cmd('lsp restart') - end - else - vim.cmd('LspRestart') + if vim.fn.has('nvim-0.12') then + if #vim.lsp.get_clients() > 0 then + vim.cmd('lsp restart') end + else + vim.cmd('LspRestart') + end end return M diff --git a/lua/platformio/piorun.lua b/lua/platformio/piorun.lua index dc43a48b..5a395550 100644 --- a/lua/platformio/piorun.lua +++ b/lua/platformio/piorun.lua @@ -4,46 +4,46 @@ local utils = require('platformio.utils') local env = require('platformio.pioenv') function M.piobuild() - utils.cd_pioini() - local command = 'pio run' .. env.get_flag() -- .. utils.extra - utils.ToggleTerminal(command, 'float') + utils.cd_pioini() + local command = 'pio run' .. env.get_flag() -- .. utils.extra + utils.ToggleTerminal(command, 'float') end function M.pioupload() - utils.cd_pioini() - local command = 'pio run --target upload' .. env.get_flag() -- .. utils.extra - utils.ToggleTerminal(command, 'float') + utils.cd_pioini() + local command = 'pio run --target upload' .. env.get_flag() -- .. utils.extra + utils.ToggleTerminal(command, 'float') end function M.piouploadfs() - utils.cd_pioini() - local command = 'pio run --target uploadfs' .. env.get_flag() -- .. utils.extra - utils.ToggleTerminal(command, 'float') + utils.cd_pioini() + local command = 'pio run --target uploadfs' .. env.get_flag() -- .. utils.extra + utils.ToggleTerminal(command, 'float') end function M.pioclean() - utils.cd_pioini() - local command = 'pio run --target clean' .. env.get_flag() -- .. utils.extra - utils.ToggleTerminal(command, 'float') + utils.cd_pioini() + local command = 'pio run --target clean' .. env.get_flag() -- .. utils.extra + utils.ToggleTerminal(command, 'float') end function M.piorun(arg_table) - if not utils.pio_install_check() then - return - end - if arg_table[1] == '' then - M.pioupload() - elseif arg_table[1] == 'upload' then - M.pioupload() - elseif arg_table[1] == 'uploadfs' then - M.piouploadfs() - elseif arg_table[1] == 'build' then - M.piobuild() - elseif arg_table[1] == 'clean' then - M.pioclean() - else - vim.notify('Invalid argument: build, upload, uploadfs or clean', vim.log.levels.WARN) - end + if not utils.pio_install_check() then + return + end + if arg_table[1] == '' then + M.pioupload() + elseif arg_table[1] == 'upload' then + M.pioupload() + elseif arg_table[1] == 'uploadfs' then + M.piouploadfs() + elseif arg_table[1] == 'build' then + M.piobuild() + elseif arg_table[1] == 'clean' then + M.pioclean() + else + vim.notify('Invalid argument: build, upload, uploadfs or clean', vim.log.levels.WARN) + end end return M diff --git a/lua/platformio/piotermlist.lua b/lua/platformio/piotermlist.lua index fa7a9807..7e35087e 100644 --- a/lua/platformio/piotermlist.lua +++ b/lua/platformio/piotermlist.lua @@ -4,43 +4,43 @@ local utils = require('platformio.utils') local picker = require('platformio.pickers') local function switch_to_terminal(chosen) - if not chosen or not chosen.term then - vim.api.nvim_echo({ { 'No PIO terminal window selected.', 'Normal' } }, true, {}) - return - end - - local win_type = vim.fn.win_gettype(chosen.term.window) - local win_open = win_type == '' or win_type == 'popup' - if chosen.term.window and (win_open and vim.api.nvim_win_get_buf(chosen.term.window) == chosen.term.bufnr) then - vim.api.nvim_set_current_win(chosen.term.window) - else - chosen.term:open() - end - vim.api.nvim_echo({ { 'Switched to PIO terminal: ' .. chosen.termtype, 'Normal' } }, true, {}) + if not chosen or not chosen.term then + vim.api.nvim_echo({ { 'No PIO terminal window selected.', 'Normal' } }, true, {}) + return + end + + local win_type = vim.fn.win_gettype(chosen.term.window) + local win_open = win_type == '' or win_type == 'popup' + if chosen.term.window and (win_open and vim.api.nvim_win_get_buf(chosen.term.window) == chosen.term.bufnr) then + vim.api.nvim_set_current_win(chosen.term.window) + else + chosen.term:open() + end + vim.api.nvim_echo({ { 'Switched to PIO terminal: ' .. chosen.termtype, 'Normal' } }, true, {}) end function M.piotermlist() - local toggleterm_list = {} - local terms = require('toggleterm.terminal').get_all(true) - - if #terms ~= 0 then - for i = 1, #terms do - if terms[i].display_name and terms[i].display_name ~= '' and terms[i].display_name:find('pio', 1) then - local termtype = utils.strsplit(terms[i].display_name, ':')[1] - table.insert(toggleterm_list, { - term = terms[i], - termtype = termtype, - }) - end - end + local toggleterm_list = {} + local terms = require('toggleterm.terminal').get_all(true) + + if #terms ~= 0 then + for i = 1, #terms do + if terms[i].display_name and terms[i].display_name ~= '' and terms[i].display_name:find('pio', 1) then + local termtype = utils.strsplit(terms[i].display_name, ':')[1] + table.insert(toggleterm_list, { + term = terms[i], + termtype = termtype, + }) + end end + end - if #toggleterm_list == 0 then - vim.api.nvim_echo({ { 'No PIO terminal windows found.', 'Normal' } }, true, {}) - return - end + if #toggleterm_list == 0 then + vim.api.nvim_echo({ { 'No PIO terminal windows found.', 'Normal' } }, true, {}) + return + end - picker.pick_terminal(toggleterm_list, switch_to_terminal) + picker.pick_terminal(toggleterm_list, switch_to_terminal) end return M diff --git a/lua/platformio/utils.lua b/lua/platformio/utils.lua index 3942005d..da5623fd 100644 --- a/lua/platformio/utils.lua +++ b/lua/platformio/utils.lua @@ -11,21 +11,21 @@ local config = require('platformio').config M.extra = ' && echo . && echo . && echo Please Press ENTER to continue' function M.strsplit(inputstr, del) - local t = {} - if type(inputstr) == 'string' and inputstr and inputstr ~= '' then - for str in string.gmatch(inputstr, '([^' .. del .. ']+)') do - table.insert(t, str) - end + local t = {} + if type(inputstr) == 'string' and inputstr and inputstr ~= '' then + for str in string.gmatch(inputstr, '([^' .. del .. ']+)') do + table.insert(t, str) end - return t + end + return t end function M.check_prefix(str, prefix) - return str:sub(1, #prefix) == prefix + return str:sub(1, #prefix) == prefix end local function pathmul(n) - return '..' .. string.rep('/..', n) + return '..' .. string.rep('/..', n) end ------------------------------------------------------ @@ -35,292 +35,292 @@ M.devNul = is_windows and ' 2>./nul' or ' 2>/dev/null' -- INFO: get current OS enter function M.enter() - local shell = vim.o.shell - if is_windows then - return vim.fn.executable('pwsh') and '\r' or '\r\n' - elseif shell:find('nu') then - return '\r' - else - return '\n' - end + local shell = vim.o.shell + if is_windows then + return vim.fn.executable('pwsh') and '\r' or '\r\n' + elseif shell:find('nu') then + return '\r' + else + return '\n' + end end -- INFO: get previous window local function getPreviousWindow(orig_window) - local prev = { - orig_window = orig_window, - term = nil, --active terminal - cli = nil, --cli terminal - mon = nil, --mon terminal - float = false, --is active terminal direction float - } - local terms = require('toggleterm.terminal').get_all(true) - if #terms ~= 0 then - for i = 1, #terms do - if terms[i].display_name and terms[i].display_name ~= '' and terms[i].display_name:find('pio', 1) then - local name_splt = M.strsplit(terms[i].display_name, ':') - if name_splt[1] == 'piocli' then - prev.cli = terms[i] - if terms[i].window == orig_window then - ---@diagnostic disable-next-line: cast-local-type - prev.orig_window = tonumber(name_splt[2]) -- set orig_window to the previous terminal onrig_window - prev.term = terms[i] - end - if terms[i].direction == 'float' then - prev.float = true - end - elseif name_splt[1] == 'piomon' then - prev.mon = terms[i] - if terms[i].window == orig_window then - ---@diagnostic disable-next-line: cast-local-type - prev.orig_window = tonumber(name_splt[2]) -- set orig_window to the previous terminal onrig_window - prev.term = terms[i] - end - if terms[i].direction == 'float' then - prev.float = true - end - end - end + local prev = { + orig_window = orig_window, + term = nil, --active terminal + cli = nil, --cli terminal + mon = nil, --mon terminal + float = false, --is active terminal direction float + } + local terms = require('toggleterm.terminal').get_all(true) + if #terms ~= 0 then + for i = 1, #terms do + if terms[i].display_name and terms[i].display_name ~= '' and terms[i].display_name:find('pio', 1) then + local name_splt = M.strsplit(terms[i].display_name, ':') + if name_splt[1] == 'piocli' then + prev.cli = terms[i] + if terms[i].window == orig_window then + ---@diagnostic disable-next-line: cast-local-type + prev.orig_window = tonumber(name_splt[2]) -- set orig_window to the previous terminal onrig_window + prev.term = terms[i] + end + if terms[i].direction == 'float' then + prev.float = true + end + elseif name_splt[1] == 'piomon' then + prev.mon = terms[i] + if terms[i].window == orig_window then + ---@diagnostic disable-next-line: cast-local-type + prev.orig_window = tonumber(name_splt[2]) -- set orig_window to the previous terminal onrig_window + prev.term = terms[i] + end + if terms[i].direction == 'float' then + prev.float = true + end end + end end - return prev + end + return prev end ------------------------------------------------------ -- INFO: Send command local function send(term, cmd) - vim.fn.chansend(term.job_id, cmd .. M.enter()) - if vim.api.nvim_buf_is_loaded(term.bufnr) and vim.api.nvim_buf_is_valid(term.bufnr) then - if term.window and vim.api.nvim_win_is_valid(term.window) then --vim.ui.term_has_open_win(term) then - vim.api.nvim_set_current_win(term.window) -- terminal focus - vim.api.nvim_buf_call(term.bufnr, function() - local mode = vim.api.nvim_get_mode().mode - if mode == 'n' or mode == 'nt' then - vim.cmd('normal! G') -- normal command to Goto bottom of buffer (scroll) - end - end) + vim.fn.chansend(term.job_id, cmd .. M.enter()) + if vim.api.nvim_buf_is_loaded(term.bufnr) and vim.api.nvim_buf_is_valid(term.bufnr) then + if term.window and vim.api.nvim_win_is_valid(term.window) then --vim.ui.term_has_open_win(term) then + vim.api.nvim_set_current_win(term.window) -- terminal focus + vim.api.nvim_buf_call(term.bufnr, function() + local mode = vim.api.nvim_get_mode().mode + if mode == 'n' or mode == 'nt' then + vim.cmd('normal! G') -- normal command to Goto bottom of buffer (scroll) end + end) end + end end ------------------------------------------------------ -- INFO: PioTermClose local function PioTermClose(t) - local orig_window = tonumber(M.strsplit(t.display_name, ':')[2]) - -- close terminal window - vim.api.nvim_win_close(t.window, true) - - -- go back to previous window - if orig_window and vim.api.nvim_win_is_valid(orig_window) then - vim.api.nvim_set_current_win(orig_window) - else - vim.api.nvim_set_current_win(0) - end + local orig_window = tonumber(M.strsplit(t.display_name, ':')[2]) + -- close terminal window + vim.api.nvim_win_close(t.window, true) + + -- go back to previous window + if orig_window and vim.api.nvim_win_is_valid(orig_window) then + vim.api.nvim_set_current_win(orig_window) + else + vim.api.nvim_set_current_win(0) + end end ------------------------------------------------------ -- INFO: ToggleTerminal function M.ToggleTerminal(command, direction, exit_callback) - if type(exit_callback) ~= 'function' then - exit_callback = function() end + if type(exit_callback) ~= 'function' then + exit_callback = function() end + end + + local status_ok, _ = pcall(require, 'toggleterm') + if not status_ok then + vim.api.nvim_echo({ { 'toggleterm not found!', 'ErrorMsg' } }, true, {}) + return + end + + local title = '' + local pioOpts = {} + + -- INFO: set orig_window to current window, or if available get current toggleterm previous window + local prev = getPreviousWindow(vim.api.nvim_get_current_win()) + local orig_window = prev.orig_window + + if string.find(command, ' monitor') then + if prev.mon then -- INFO: if previous monitor terminal already opened ==> reopen + local win_type = vim.fn.win_gettype(prev.mon.window) + local win_open = win_type == '' or win_type == 'popup' + if prev.mon.window and (win_open and vim.api.nvim_win_get_buf(prev.mon.window) == prev.mon.bufnr) then + vim.api.nvim_set_current_win(prev.mon.window) + else + prev.mon:open() + end + return end - - local status_ok, _ = pcall(require, 'toggleterm') - if not status_ok then - vim.api.nvim_echo({ { 'toggleterm not found!', 'ErrorMsg' } }, true, {}) - return - end - - local title = '' - local pioOpts = {} - - -- INFO: set orig_window to current window, or if available get current toggleterm previous window - local prev = getPreviousWindow(vim.api.nvim_get_current_win()) - local orig_window = prev.orig_window - - if string.find(command, ' monitor') then - if prev.mon then -- INFO: if previous monitor terminal already opened ==> reopen - local win_type = vim.fn.win_gettype(prev.mon.window) - local win_open = win_type == '' or win_type == 'popup' - if prev.mon.window and (win_open and vim.api.nvim_win_get_buf(prev.mon.window) == prev.mon.bufnr) then - vim.api.nvim_set_current_win(prev.mon.window) - else - prev.mon:open() - end - return + title = 'Pio Monitor: [In normal mode press: q or :q to hide; :q! to quit; :PioTermList to list terminals]' + pioOpts.display_name = 'piomon:' .. orig_window + else -- INFO: if previous cli terminal already opened ==> reopen + if prev.cli then + prev.cli.on_close = function(t) + local ow = tonumber(M.strsplit(t.display_name, ':')[2]) + if ow and vim.api.nvim_win_is_valid(ow) then + vim.api.nvim_set_current_win(ow) + else + vim.api.nvim_set_current_win(0) end - title = 'Pio Monitor: [In normal mode press: q or :q to hide; :q! to quit; :PioTermList to list terminals]' - pioOpts.display_name = 'piomon:' .. orig_window - else -- INFO: if previous cli terminal already opened ==> reopen - if prev.cli then - prev.cli.on_close = function(t) - local ow = tonumber(M.strsplit(t.display_name, ':')[2]) - if ow and vim.api.nvim_win_is_valid(ow) then - vim.api.nvim_set_current_win(ow) - else - vim.api.nvim_set_current_win(0) - end - exit_callback() - end - - local win_type = vim.fn.win_gettype(prev.cli.window) - local win_open = win_type == '' or win_type == 'popup' - if prev.cli.window and (win_open and vim.api.nvim_win_get_buf(prev.cli.window) == prev.cli.bufnr) then - vim.api.nvim_set_current_win(prev.cli.window) - else - prev.cli:open() - end - vim.defer_fn(function() - if command and command ~= '' then - send(prev.cli, command) - end - end, 50) -- 50ms delay, adjust as needed - return + exit_callback() + end + + local win_type = vim.fn.win_gettype(prev.cli.window) + local win_open = win_type == '' or win_type == 'popup' + if prev.cli.window and (win_open and vim.api.nvim_win_get_buf(prev.cli.window) == prev.cli.bufnr) then + vim.api.nvim_set_current_win(prev.cli.window) + else + prev.cli:open() + end + vim.defer_fn(function() + if command and command ~= '' then + send(prev.cli, command) end - title = 'Pio CLI> [In normal mode press: q or :q to hide; :q! to quit; :PioTermList to list terminals]' - pioOpts.display_name = 'piocli:' .. orig_window + end, 50) -- 50ms delay, adjust as needed + return end - pioOpts.direction = direction - ------------------------------------------------------ - - -- INFO: termConfig table start - local termConfig = { - hidden = true, -- Start hidden, we'll open it explicitly - hide_numbers = true, - float_opts = { - winblend = 0, - width = function() - return math.ceil(vim.o.columns * 0.85) - end, - height = function() - return math.ceil(vim.o.lines * 0.85) - end, - highlights = { - border = 'FloatBorder', - background = 'NormalFloat', - }, - }, - close_on_exit = false, - - -- INFO: on_open() - on_open = function(t) - -- Get properties of the 'Normal' highlight group (background of main editor) - -- local hl = vim.api.nvim_get_hl(0, { name = 'PmenuSel' }) - -- local hl = { bg = '#e4cf0e', fg = '#0012d9' } - local hl = { bg = '#80a3d4', fg = '#000000' } - - if hl then - vim.api.nvim_set_hl(0, 'MyWinBar', { bg = hl.bg, fg = hl.fg }) - - local winBartitle = '%#MyWinBar#' .. title .. '%*' - vim.api.nvim_set_option_value('winbar', winBartitle, { scope = 'local', win = t.window }) - - -- Following necessary to solve that some time winbar not showing - vim.schedule(function() - vim.api.nvim_set_option_value('winbar', winBartitle, { scope = 'local', win = t.window }) - end) - end - vim.keymap.set('t', '', [[k]], { buffer = t.bufnr }) - vim.keymap.set('n', '', [[a]], { buffer = t.bufnr }) - - vim.keymap.set('n', 'q', function() - PioTermClose(t) - end, { desc = 'PioTermClose', buffer = t.bufnr }) - - if config.debug then - local name_splt = M.strsplit(t.display_name, ':') - vim.api.nvim_echo({ - { 'ToggleTerm ', 'MoreMsg' }, - { '(Term name: ' .. name_splt[1] .. ')', 'MoreMsg' }, - { '(Prev win ID: ' .. name_splt[2] .. ')', 'MoreMsg' }, - { '(Term Win ID: ' .. t.window .. ')', 'MoreMsg' }, - { '(Term Buffer#: ' .. t.bufnr .. ')', 'MoreMsg' }, - { '(Term id: ' .. t.id .. ')', 'MoreMsg' }, - { '(Job ID: ' .. t.job_id .. ')', 'MoreMsg' }, - }, true, {}) - end - end, + title = 'Pio CLI> [In normal mode press: q or :q to hide; :q! to quit; :PioTermList to list terminals]' + pioOpts.display_name = 'piocli:' .. orig_window + end + pioOpts.direction = direction + ------------------------------------------------------ + + -- INFO: termConfig table start + local termConfig = { + hidden = true, -- Start hidden, we'll open it explicitly + hide_numbers = true, + float_opts = { + winblend = 0, + width = function() + return math.ceil(vim.o.columns * 0.85) + end, + height = function() + return math.ceil(vim.o.lines * 0.85) + end, + highlights = { + border = 'FloatBorder', + background = 'NormalFloat', + }, + }, + close_on_exit = false, + + -- INFO: on_open() + on_open = function(t) + -- Get properties of the 'Normal' highlight group (background of main editor) + -- local hl = vim.api.nvim_get_hl(0, { name = 'PmenuSel' }) + -- local hl = { bg = '#e4cf0e', fg = '#0012d9' } + local hl = { bg = '#80a3d4', fg = '#000000' } + + if hl then + vim.api.nvim_set_hl(0, 'MyWinBar', { bg = hl.bg, fg = hl.fg }) + + local winBartitle = '%#MyWinBar#' .. title .. '%*' + vim.api.nvim_set_option_value('winbar', winBartitle, { scope = 'local', win = t.window }) + + -- Following necessary to solve that some time winbar not showing + vim.schedule(function() + vim.api.nvim_set_option_value('winbar', winBartitle, { scope = 'local', win = t.window }) + end) + end + vim.keymap.set('t', '', [[k]], { buffer = t.bufnr }) + vim.keymap.set('n', '', [[a]], { buffer = t.bufnr }) + + vim.keymap.set('n', 'q', function() + PioTermClose(t) + end, { desc = 'PioTermClose', buffer = t.bufnr }) + + if config.debug then + local name_splt = M.strsplit(t.display_name, ':') + vim.api.nvim_echo({ + { 'ToggleTerm ', 'MoreMsg' }, + { '(Term name: ' .. name_splt[1] .. ')', 'MoreMsg' }, + { '(Prev win ID: ' .. name_splt[2] .. ')', 'MoreMsg' }, + { '(Term Win ID: ' .. t.window .. ')', 'MoreMsg' }, + { '(Term Buffer#: ' .. t.bufnr .. ')', 'MoreMsg' }, + { '(Term id: ' .. t.id .. ')', 'MoreMsg' }, + { '(Job ID: ' .. t.job_id .. ')', 'MoreMsg' }, + }, true, {}) + end + end, + + -- INFO: on_close() + on_close = function(t) + orig_window = tonumber(M.strsplit(t.display_name, ':')[2]) + ---@diagnostic disable-next-line: param-type-mismatch + if orig_window and vim.api.nvim_win_is_valid(orig_window) then + vim.api.nvim_set_current_win(orig_window) + else + vim.api.nvim_set_current_win(0) + end + exit_callback() + end, + + -- INFO: on_create() { + on_create = function(t) + local platformio = vim.api.nvim_create_augroup(M.strsplit(t.display_name, ':')[1], { clear = true }) + + -- INFO: CmdlineLeave + vim.api.nvim_create_autocmd('CmdlineLeave', { + group = platformio, + -- pattern = ':', + buffer = t.bufnr, + callback = function() + if vim.v.event and not vim.v.event.abort and vim.v.event.cmdtype == ':' then + local quit = vim.fn.getcmdline() == 'q' + local quitbang = vim.fn.getcmdline() == 'q!' + if quitbang or quit then + local name_splt = M.strsplit(t.display_name, ':') + if quitbang then + if name_splt[1] == 'piomon' then -- monitor terminal + local exit = vim.api.nvim_replace_termcodes('exit', true, true, true) + send(t, exit) + else -- cli terminal + send(t, 'exit') + end + end - -- INFO: on_close() - on_close = function(t) - orig_window = tonumber(M.strsplit(t.display_name, ':')[2]) - ---@diagnostic disable-next-line: param-type-mismatch - if orig_window and vim.api.nvim_win_is_valid(orig_window) then - vim.api.nvim_set_current_win(orig_window) - else - vim.api.nvim_set_current_win(0) + orig_window = tonumber(name_splt[2]) + vim.schedule(function() + -- go back to previous window + if orig_window and vim.api.nvim_win_is_valid(orig_window) then + vim.api.nvim_set_current_win(orig_window) + else + vim.api.nvim_set_current_win(0) + end + end) end - exit_callback() + end end, - - -- INFO: on_create() { - on_create = function(t) - local platformio = vim.api.nvim_create_augroup(M.strsplit(t.display_name, ':')[1], { clear = true }) - - -- INFO: CmdlineLeave - vim.api.nvim_create_autocmd('CmdlineLeave', { - group = platformio, - -- pattern = ':', - buffer = t.bufnr, - callback = function() - if vim.v.event and not vim.v.event.abort and vim.v.event.cmdtype == ':' then - local quit = vim.fn.getcmdline() == 'q' - local quitbang = vim.fn.getcmdline() == 'q!' - if quitbang or quit then - local name_splt = M.strsplit(t.display_name, ':') - if quitbang then - if name_splt[1] == 'piomon' then -- monitor terminal - local exit = vim.api.nvim_replace_termcodes('exit', true, true, true) - send(t, exit) - else -- cli terminal - send(t, 'exit') - end - end - - orig_window = tonumber(name_splt[2]) - vim.schedule(function() - -- go back to previous window - if orig_window and vim.api.nvim_win_is_valid(orig_window) then - vim.api.nvim_set_current_win(orig_window) - else - vim.api.nvim_set_current_win(0) - end - end) - end - end - end, - }) - - -- INFO: BufUnload - vim.api.nvim_create_autocmd('BufUnload', { - group = platformio, - desc = 'toggleterm buffer unloaded', - buffer = t.bufnr, - callback = function(args) - vim.keymap.del('t', '', { buffer = args.buf }) - vim.keymap.del('n', '', { buffer = args.buf }) - - -- clear autommmand when quit - vim.api.nvim_clear_autocmds({ group = M.strsplit(t.display_name, ':')[1] }) - end, - }) + }) + + -- INFO: BufUnload + vim.api.nvim_create_autocmd('BufUnload', { + group = platformio, + desc = 'toggleterm buffer unloaded', + buffer = t.bufnr, + callback = function(args) + vim.keymap.del('t', '', { buffer = args.buf }) + vim.keymap.del('n', '', { buffer = args.buf }) + + -- clear autommmand when quit + vim.api.nvim_clear_autocmds({ group = M.strsplit(t.display_name, ':')[1] }) end, - } - -- INFO: termConfig table end - - termConfig = vim.tbl_deep_extend('force', termConfig, pioOpts or {}) - - -- INFO: create new terminal - local terminal = require('toggleterm.terminal').Terminal:new(termConfig) - if prev.term and prev.float then - prev.term:close() + }) + end, + } + -- INFO: termConfig table end + + termConfig = vim.tbl_deep_extend('force', termConfig, pioOpts or {}) + + -- INFO: create new terminal + local terminal = require('toggleterm.terminal').Terminal:new(termConfig) + if prev.term and prev.float then + prev.term:close() + end + terminal:toggle() + vim.defer_fn(function() + if command and command ~= '' then + send(terminal, command) end - terminal:toggle() - vim.defer_fn(function() - if command and command ~= '' then - send(terminal, command) - end - end, 50) -- 50ms delay, adjust as needed sgget + end, 50) -- 50ms delay, adjust as needed sgget end ---------------------------------------------------------------------------------------- @@ -328,105 +328,105 @@ end local paths = { '.', '..', pathmul(1), pathmul(2), pathmul(3), pathmul(4), pathmul(5) } function M.file_exists(name) - local f = io.open(name, 'r') - if f ~= nil then - io.close(f) - return true - else - return false - end + local f = io.open(name, 'r') + if f ~= nil then + io.close(f) + return true + else + return false + end end function M.set_platformioRootDir() - if vim.g.platformioRootDir ~= nil then - return - end - for _, path in pairs(paths) do - if M.file_exists(path .. '/platformio.ini') then - vim.g.platformioRootDir = path - return - end + if vim.g.platformioRootDir ~= nil then + return + end + for _, path in pairs(paths) do + if M.file_exists(path .. '/platformio.ini') then + vim.g.platformioRootDir = path + return end - vim.notify('Could not find platformio.ini, run :Pioinit to create a new project', vim.log.levels.ERROR) + end + vim.notify('Could not find platformio.ini, run :Pioinit to create a new project', vim.log.levels.ERROR) end function M.cd_pioini() - M.set_platformioRootDir() - vim.cmd('cd ' .. vim.g.platformioRootDir) + M.set_platformioRootDir() + vim.cmd('cd ' .. vim.g.platformioRootDir) end function M.pio_install_check() - local handle = (jit.os == 'Windows') and assert(io.popen('where.exe pio 2>./nul')) or assert(io.popen('which pio 2>/dev/null')) - local pio_path = assert(handle:read('*a')) - handle:close() - - if #pio_path == 0 then - vim.notify('Platformio not found in the path', vim.log.levels.ERROR) - return false - end - return true + local handle = (jit.os == 'Windows') and assert(io.popen('where.exe pio 2>./nul')) or assert(io.popen('which pio 2>/dev/null')) + local pio_path = assert(handle:read('*a')) + handle:close() + + if #pio_path == 0 then + vim.notify('Platformio not found in the path', vim.log.levels.ERROR) + return false + end + return true end function M.async_shell_cmd(cmd, callback) - local output = {} - - vim.fn.jobstart(cmd, { - stdout_buffered = true, - stderr_buffered = false, - - on_stdout = function(_, data) - if data then - for _, line in ipairs(data) do - if line ~= '' then - table.insert(output, line) - end - end - end - end, + local output = {} + + vim.fn.jobstart(cmd, { + stdout_buffered = true, + stderr_buffered = false, + + on_stdout = function(_, data) + if data then + for _, line in ipairs(data) do + if line ~= '' then + table.insert(output, line) + end + end + end + end, - on_exit = function(_, code) - callback(output, code) - end, - }) + on_exit = function(_, code) + callback(output, code) + end, + }) end function M.shell_cmd_blocking(command) - local handle = io.popen(command, 'r') - if not handle then - return nil, 'failed to run command' - end + local handle = io.popen(command, 'r') + if not handle then + return nil, 'failed to run command' + end - local result = handle:read('*a') - handle:close() + local result = handle:read('*a') + handle:close() - return result + return result end --- gets the os user data dir for windows or linux ---@return string data_dir_path function M.get_os_user_data_dir() - --- @type string - local os_dirname, path_separator - if is_windows then - path_separator = '\\' - os_dirname = 'LOCALAPPDATA' - else - path_separator = '/' - os_dirname = 'XDG_STATE_HOME' - end - return os.getenv(os_dirname) .. path_separator .. 'nvim' .. path_separator .. 'nvim-platformio' + --- @type string + local os_dirname, path_separator + if is_windows then + path_separator = '\\' + os_dirname = 'LOCALAPPDATA' + else + path_separator = '/' + os_dirname = 'XDG_STATE_HOME' + end + return os.getenv(os_dirname) .. path_separator .. 'nvim' .. path_separator .. 'nvim-platformio' end --- creates the user data dir conditionally, based on its existence. ---@return string data_dir_path for performance function M.make_os_user_data_dir() - local data_dir_path = M.get_os_user_data_dir() - local cmd = 'mkdir ' .. data_dir_path - if not is_windows then - cmd = cmd .. ' -p' - end - os.execute(cmd) - return data_dir_path + local data_dir_path = M.get_os_user_data_dir() + local cmd = 'mkdir ' .. data_dir_path + if not is_windows then + cmd = cmd .. ' -p' + end + os.execute(cmd) + return data_dir_path end return M diff --git a/plugin/platformio.lua b/plugin/platformio.lua index 96784f54..9c526bb0 100644 --- a/plugin/platformio.lua +++ b/plugin/platformio.lua @@ -11,91 +11,91 @@ local piolsserial = require('platformio.piolsserial') -- Pioinit vim.api.nvim_create_user_command('Pioinit', function() - require('platformio.pioinit').pioinit() + require('platformio.pioinit').pioinit() end, { force = true }) -- Piolsp vim.api.nvim_create_user_command('PioLSP', function() - vim.schedule(function() - require('platformio.piolsp').piolsp() - end) + vim.schedule(function() + require('platformio.piolsp').piolsp() + end) end, {}) -- Piorun vim.api.nvim_create_user_command('Piorun', function(opts) - local args = opts.args - require('platformio.piorun').piorun({ args }) + local args = opts.args + require('platformio.piorun').piorun({ args }) end, { - nargs = '?', - complete = function(_, _, _) - return { 'upload', 'uploadfs', 'build', 'clean' } -- Autocompletion options - end, + nargs = '?', + complete = function(_, _, _) + return { 'upload', 'uploadfs', 'build', 'clean' } -- Autocompletion options + end, }) -- Piomon piolsserial.sync_ttylist() vim.api.nvim_create_user_command('Piomon', function(opts) - local args = opts.fargs - require('platformio.piomon').piomon(args) + local args = opts.fargs + require('platformio.piomon').piomon(args) end, { - nargs = '*', + nargs = '*', - complete = function(_, cmd_line) - local parts = vim.split(cmd_line, '%s+') - local BAUD = { '4800', '9600', '57600', '115200' } - local ports = {} - for _, item in ipairs(piolsserial.tty_list) do - table.insert(ports, item.port) - end - if #parts == 2 then - return BAUD - end - if #parts == 3 then - return ports - end - return {} - end, + complete = function(_, cmd_line) + local parts = vim.split(cmd_line, '%s+') + local BAUD = { '4800', '9600', '57600', '115200' } + local ports = {} + for _, item in ipairs(piolsserial.tty_list) do + table.insert(ports, item.port) + end + if #parts == 2 then + return BAUD + end + if #parts == 3 then + return ports + end + return {} + end, }) -- Piolsserial vim.api.nvim_create_user_command('Piolsserial', function() - require('platformio.piolsserial').print_tty_list() + require('platformio.piolsserial').print_tty_list() end, {}) -- Piolib vim.api.nvim_create_user_command('Piolib', function(opts) - local args = vim.split(opts.args, ' ') - require('platformio.piolib').piolib(args) + local args = vim.split(opts.args, ' ') + require('platformio.piolib').piolib(args) end, { - nargs = '+', + nargs = '+', }) -- Piocmdh Piocmd horizontal terminal vim.api.nvim_create_user_command('Piocmdh', function(opts) - local cmd_table = vim.split(opts.args, ' ') - require('platformio.piocmd').piocmd(cmd_table, 'horizontal') + local cmd_table = vim.split(opts.args, ' ') + require('platformio.piocmd').piocmd(cmd_table, 'horizontal') end, { - nargs = '*', + nargs = '*', }) -- Piocmdf Piocmd float terminal vim.api.nvim_create_user_command('Piocmdf', function(opts) - local cmd_table = vim.split(opts.args, ' ') - require('platformio.piocmd').piocmd(cmd_table, 'float') + local cmd_table = vim.split(opts.args, ' ') + require('platformio.piocmd').piocmd(cmd_table, 'float') end, { - nargs = '*', + nargs = '*', }) -- Piodebug vim.api.nvim_create_user_command('Piodebug', function() - require('platformio.piodebug').piodebug() + require('platformio.piodebug').piodebug() end, {}) -- INFO: List ToggleTerminals vim.api.nvim_create_user_command('PioTermList', function() - require('platformio.piotermlist').piotermlist() + require('platformio.piotermlist').piotermlist() end, {}) vim.api.nvim_create_user_command('Pioenv', function() - require('platformio.pioenv').pioenv() + require('platformio.pioenv').pioenv() end, {}) From 4ec4aa3a58dc3d2a431065be1b5e0acd226c0b57 Mon Sep 17 00:00:00 2001 From: Shoxx98 Date: Tue, 30 Jun 2026 11:04:10 +0200 Subject: [PATCH 18/19] made is_windows public --- lua/platformio/utils.lua | 10 +++++----- 1 file changed, 5 insertions(+), 5 deletions(-) diff --git a/lua/platformio/utils.lua b/lua/platformio/utils.lua index da5623fd..df157364 100644 --- a/lua/platformio/utils.lua +++ b/lua/platformio/utils.lua @@ -29,14 +29,14 @@ local function pathmul(n) end ------------------------------------------------------ -local is_windows = jit.os == 'Windows' +M.is_windows = jit.os == 'Windows' -M.devNul = is_windows and ' 2>./nul' or ' 2>/dev/null' +M.devNul = M.is_windows and ' 2>./nul' or ' 2>/dev/null' -- INFO: get current OS enter function M.enter() local shell = vim.o.shell - if is_windows then + if M.is_windows then return vim.fn.executable('pwsh') and '\r' or '\r\n' elseif shell:find('nu') then return '\r' @@ -407,7 +407,7 @@ end function M.get_os_user_data_dir() --- @type string local os_dirname, path_separator - if is_windows then + if M.is_windows then path_separator = '\\' os_dirname = 'LOCALAPPDATA' else @@ -422,7 +422,7 @@ end function M.make_os_user_data_dir() local data_dir_path = M.get_os_user_data_dir() local cmd = 'mkdir ' .. data_dir_path - if not is_windows then + if not M.is_windows then cmd = cmd .. ' -p' end os.execute(cmd) From fafb7599732500598492beb4ce728c50f8f9b1c2 Mon Sep 17 00:00:00 2001 From: Shoxx98 Date: Tue, 30 Jun 2026 11:06:01 +0200 Subject: [PATCH 19/19] now automatically replaces a symlink to compile db in project root and is faster on linux --- lua/platformio/pioenv.lua | 70 +++++++++++++++++++++++++++++++-------- 1 file changed, 56 insertions(+), 14 deletions(-) diff --git a/lua/platformio/pioenv.lua b/lua/platformio/pioenv.lua index 2a4a136e..7968193f 100644 --- a/lua/platformio/pioenv.lua +++ b/lua/platformio/pioenv.lua @@ -3,17 +3,52 @@ local picker = require('platformio.pickers') local M = { default_env = 'none', } +local config = require('platformio').config + +--- changes symlink if feasible. +function M.change_compiledb_symlink(env) + local pathsep = '/' + if utils.is_windows then + pathsep = '\\' + end + if M.default_env == 'none' then + vim.notify('compiledb does not exist for env ' .. M.default_env, vim.log.levels.ERROR, opts) + return + end + utils.cd_pioini() + local compile_commands_filename = 'compile_commands.json' + local compile_commands_path = '.pio' .. pathsep .. 'build' .. pathsep .. M.default_env .. pathsep .. compile_commands_filename + if not utils.file_exists(compile_commands_path) then + vim.notify(compile_commands_path .. ' does not exist to link it', vim.log.levels.ERROR, opts) + return + end + if utils.file_exists(compile_commands_filename) then + if not os.remove(compile_commands_filename) then + vim.notify(compile_commands_filename .. ' did not get removed', vim.log.levels.ERROR) + return + end + end + if config.clangd_source == 'compiledb' and utils.file_exists(compile_commands_path) then + if utils.is_windows then + os.execute('mklink ' .. compile_commands_filename .. ' ' .. compile_commands_path) + else + os.execute('ln -s ' .. compile_commands_path) + end + end +end function M.pioenv() --- @type vim.log.levels local loglevel = vim.log.levels.INFO - if not utils.pio_install_check() then - return - end utils.cd_pioini() + local cmd -- TODO: execute target list command - local cmd = 'pio run --list-targets' + if utils.is_windows then + cmd = 'pio run --list-targets' + else + cmd = 'grep -F "[env:" < platformio.ini | sed "s/\\[env://" | sed "s/\\]//"' + end --- @type file*? local stdio_stream stdio_stream, _ = io.popen(cmd, 'r') @@ -42,19 +77,25 @@ function M.pioenv() local envs = {} --- @type integer | nil local env_name_end - _ = stdio_stream.read(stdio_stream, '*line') - _ = stdio_stream.read(stdio_stream, '*line') + if utils.is_windows then + _ = stdio_stream.read(stdio_stream, '*line') + _ = stdio_stream.read(stdio_stream, '*line') + end for line in stdio_stream.lines(stdio_stream, '*l') do -- readmode is for the output of lines as var `line` - env_name_end, _ = string.find(line, ' ', 2) -- if cmd succeeds, the env name has to be at least 1 char long and contains no spaces. skip the first one and search for space - env_name_end = env_name_end - 1 - if env_name_end then -- would start at 1, regardless of start index, so we don't need to differentiate between 0 and nil - table.insert(envs, string.sub(line, 1, env_name_end)) + if utils.is_windows then + env_name_end, _ = string.find(line, ' ', 2) -- if cmd succeeds, the env name has to be at least 1 char long and contains no spaces. skip the first one and search for space + env_name_end = env_name_end - 1 + if env_name_end then -- would start at 1, regardless of start index, so we don't need to differentiate between 0 and nil + table.insert(envs, string.sub(line, 1, env_name_end)) + else + vim.notify('formatting pio output failed on finding the first space char', vim.log.levels.ERROR) + io.close(stdio_stream) + return + end + _ = stdio_stream.read(stdio_stream, '*line') else - vim.notify('formatting pio output failed on finding the first space char', vim.log.levels.ERROR) - io.close(stdio_stream) - return + table.insert(envs, line) end - _ = stdio_stream.read(stdio_stream, '*line') end table.insert(envs, 'none') @@ -62,6 +103,7 @@ function M.pioenv() -- TODO: execute picker picker.pick_env(envs, function(selected_env) M.default_env = selected_env + M.change_compiledb_symlink() end) end