From d1ceeca7664400fa6a74f09d0a1273705bd69f19 Mon Sep 17 00:00:00 2001 From: sail3r <82087684+sail3r@users.noreply.github.com> Date: Mon, 19 Oct 2026 11:19:33 +1300 Subject: [PATCH 1/3] feat(menu): add project_only option for a per-project which-key menu With project_only = true, the menu_key which-key menu is registered buffer-locally instead of globally, and only for buffers inside a PlatformIO project. which-key's `buffer` field is set on the spec root; which-key marks it `inherit`, so every group and item below gets it. - utils: extract find_platformioRootDir(buf) from get_platformioRootDir(). Same upward vim.fs.find() for platformio.ini, starting at the buffer name (cwd for unnamed buffers), but it neither caches the result in vim.g.platformioRootDir nor notifies "Could not find platformio.ini", so it is safe to call on every BufEnter. get_platformioRootDir() behaviour is unchanged. - piomenu: when project_only, create a BufEnter autocmd in augroup platformio_menu (cleared if setup() runs again) and also check the current buffer immediately: setup() can run after that buffer's BufEnter already fired (lazy.nvim BufReadPost/keys/cmd loading). Buffers with a non-empty buftype (help, terminal, explorers) are skipped. - piomenu: mark mapped buffers with b:platformio_menu so re-entering does not add the menu again. Neovim clears b: variables and buffer-local mappings together when a buffer is reused for another file (free_buffer_stuff), so the flag cannot outlive the menu. Non-project buffers are not marked and are re-checked on each BufEnter (one stat per parent directory), which also picks up a project created later by :Pioinit. - piomenu: build the which-key spec per wk.add() call (build_spec). which-key's parser removes child entries from the table it is given (mappings.lua _parse: spec[k] = nil), so the former module-level wk_table could not be added once per buffer. - piomenu: write the Nerd Font icon (U+E682) as the byte escape '\238\154\130'. The runtime string is byte-identical; the escape keeps the source ASCII and survives editors that drop private-use glyphs. - init: accept project_only (default false). A non-boolean value reports an error and falls back to the default, like lsp and picker_backend. project_only = false (the default) keeps the previous global mapping. --- lua/platformio/init.lua | 11 ++++++++ lua/platformio/piomenu.lua | 55 ++++++++++++++++++++++++++++++++------ lua/platformio/utils.lua | 24 +++++++++++------ 3 files changed, 74 insertions(+), 16 deletions(-) diff --git a/lua/platformio/init.lua b/lua/platformio/init.lua index 6ed6840..9855b81 100644 --- a/lua/platformio/init.lua +++ b/lua/platformio/init.lua @@ -3,6 +3,7 @@ M.config = { lsp = 'ccls', menu_key = nil, menu_name = 'PlatformIO', + project_only = false, debug = false, clangd_source = 'ccls', picker_backend = 'auto', @@ -159,6 +160,7 @@ function M.setup(user_config) menu_key = true, menu_name = true, menu_bindings = true, + project_only = true, debug = true, clangd_source = true, picker_backend = true, @@ -207,6 +209,15 @@ function M.setup(user_config) }, true, {}) user_config.picker_backend = M.config.picker_backend end + if user_config.project_only ~= nil and type(user_config.project_only) ~= 'boolean' then + vim.api.nvim_echo({ + { + 'Invalid PlatformIO project_only {allowed true or false} (default "' .. tostring(M.config.project_only) .. '" will be used)', + 'ErrorMsg', + }, + }, true, {}) + user_config.project_only = M.config.project_only + end if not err then -- if no error, merge user_config to M.config if user_config.menu_bindings then diff --git a/lua/platformio/piomenu.lua b/lua/platformio/piomenu.lua index 5072153..85b4821 100644 --- a/lua/platformio/piomenu.lua +++ b/lua/platformio/piomenu.lua @@ -1,12 +1,13 @@ +local utils = require('platformio.utils') + local M = {} -local icon = { icon = ' ', color = 'orange' } -- Assign platformio orange icon -local wk_table = { mode = { 'n', 'v' } } +local icon = { icon = '\238\154\130 ', color = 'orange' } -- Assign platformio orange icon (Nerd Font U+E682) -local function traverseMenu(menu, wkey) +local function traverseMenu(menu, wkey, wk_table) for _, child_node in ipairs(menu) do if child_node.node == 'menu' then - traverseMenu(child_node.items, wkey .. child_node.shortcut) + traverseMenu(child_node.items, wkey .. child_node.shortcut, wk_table) table.insert(wk_table, { wkey .. child_node.shortcut, group = child_node.desc, icon = icon }) elseif child_node.node == 'item' then table.insert(wk_table, { @@ -19,6 +20,34 @@ local function traverseMenu(menu, wkey) end end +-- which-key's parser removes the child entries from the spec it is given, so +-- every wk.add() call needs a freshly built table. +local function build_spec(config) + local wk_table = { mode = { 'n', 'v' } } + table.insert(wk_table, { config.menu_key, group = config.menu_name, icon = icon }) + traverseMenu(config.menu_bindings, config.menu_key, wk_table) + return wk_table +end + +local function add_project_menu(wk, config, buf) + -- The b: flag is cleared together with buffer-local mappings when Neovim + -- reuses a buffer for another file, so it never claims a menu that is gone. + if vim.b[buf].platformio_menu then + return + end + -- Special buffers (help, terminal, file explorers) have no real file path. + if vim.bo[buf].buftype ~= '' then + return + end + if utils.find_platformioRootDir(buf) == nil then + return + end + local spec = build_spec(config) + spec.buffer = buf + wk.add(spec) + vim.b[buf].platformio_menu = true +end + function M.piomenu(config) if config.menu_key == nil then return @@ -38,11 +67,21 @@ function M.piomenu(config) local Config = require('which-key.config') Config.sort = { 'order', 'group', 'manual', 'mod' } - table.insert(wk_table, { config.menu_key, group = config.menu_name, icon = icon }) - - traverseMenu(config.menu_bindings, config.menu_key) + if not config.project_only then + wk.add(build_spec(config)) + return + end - wk.add(wk_table) + vim.api.nvim_create_autocmd('BufEnter', { + group = vim.api.nvim_create_augroup('platformio_menu', { clear = true }), + desc = 'Add the PlatformIO menu to buffers inside a PlatformIO project', + callback = function(args) + add_project_menu(wk, config, args.buf) + end, + }) + -- setup() can run after BufEnter already fired for the current buffer, e.g. + -- when lazy.nvim loads the plugin from BufReadPost or a :Pio* command. + add_project_menu(wk, config, vim.api.nvim_get_current_buf()) end return M diff --git a/lua/platformio/utils.lua b/lua/platformio/utils.lua index a359717..6d31d56 100644 --- a/lua/platformio/utils.lua +++ b/lua/platformio/utils.lua @@ -38,16 +38,24 @@ function M.file_exists(name) end end +-- Directory of the nearest platformio.ini at or above the buffer's file (the +-- cwd for unnamed buffers), or nil. Unlike get_platformioRootDir() it neither +-- caches the result nor reports a missing project. +function M.find_platformioRootDir(buf) + local path = vim.api.nvim_buf_get_name(buf) + if path == '' then + path = vim.fn.getcwd() + end + local match = vim.fs.find({ 'platformio.ini' }, { upward = true, path = path }) + if #match > 0 then + return vim.fs.dirname(match[1]) + end + return nil +end + function M.get_platformioRootDir() if vim.g.platformioRootDir == nil then - local path = vim.api.nvim_buf_get_name(0) - if path == '' then - path = vim.fn.getcwd() - end - local match = vim.fs.find({ 'platformio.ini' }, { upward = true, path = path }) - if #match > 0 then - vim.g.platformioRootDir = vim.fs.dirname(match[1]) - end + vim.g.platformioRootDir = M.find_platformioRootDir(0) if vim.g.platformioRootDir == nil then vim.notify('Could not find platformio.ini, run :Pioinit to create a new project', vim.log.levels.ERROR) From 52f6f168ba4aab059525b3d57544a02cb90f3a5d Mon Sep 17 00:00:00 2001 From: sail3r <82087684+sail3r@users.noreply.github.com> Date: Mon, 19 Oct 2026 11:19:33 +1300 Subject: [PATCH 2/3] docs: document project_only option - doc/platformio.txt: add the *platformio-project_only* section after menu_bindings: what counts as inside a project (platformio.ini in the file's directory or any parent, cwd for unnamed buffers), when buffers are checked (current buffer at setup, every buffer on BufEnter), and that :Pio* commands are unaffected. - doc/tags: add the platformio-project_only tag (sorted position). - README: add project_only to the configuration example, and note in "Lazy loading" that with project_only the plugin should be loaded on file open (event = { 'BufReadPost', 'BufNewFile' }) rather than with lazy.nvim `keys`, whose stub mapping is global. --- README.md | 7 +++++++ doc/platformio.txt | 11 +++++++++++ doc/tags | 1 + 3 files changed, 19 insertions(+) diff --git a/README.md b/README.md index 8547a98..d64ce6c 100644 --- a/README.md +++ b/README.md @@ -69,6 +69,7 @@ vim.g.pioConfig ={ picker_backend = 'auto', -- value: auto | telescope | snacks | mini_pick | ui_select -- default: auto, check :help platformio-picker menu_key = '\\', -- replace this menu key to your convenience + project_only = false, -- true: menu only in buffers inside a PlatformIO project, check :help platformio-project_only debug = false -- enable debug messages } local pok, platformio = pcall(require, 'platformio') @@ -251,6 +252,12 @@ It's possible to lazy load the plugin using Lazy.nvim, this will load the plugin cmd = { 'Pioinit', 'PioLSP', 'Piorun', 'Piocmdh', 'Piocmdf', 'Piolib', 'Piomon', 'Piolsserial', 'Piodebug', 'PioTermList' }, ``` +With `project_only = true`, don't lazy-load on the menu key with `keys` (lazy.nvim's stub mapping is global, in every buffer). Load the plugin when a file opens instead: + +```lua +event = { 'BufReadPost', 'BufNewFile' }, +``` + ### TODO - Connect Piodebug with DAP diff --git a/doc/platformio.txt b/doc/platformio.txt index 8253d3e..6c7df09 100644 --- a/doc/platformio.txt +++ b/doc/platformio.txt @@ -183,6 +183,17 @@ menu_bindings *platformio-menu_bindings copy and edit. Note that a partial table does not selectively override individual default entries — provide the whole tree if you customize it. +project_only *platformio-project_only* + When `true`, the |platformio-menu_key| menu is added only to buffers + whose file is inside a PlatformIO project: `platformio.ini` exists in the + file's directory or in any parent directory. Unnamed buffers are checked + against the current working directory. The current buffer is checked + when the plugin is set up, and every buffer on |BufEnter|. Do not + lazy-load the plugin on the menu key (lazy.nvim `keys`): that stub + mapping is global. Load it when a file opens instead, e.g. + `event = { 'BufReadPost', 'BufNewFile' }`. + The `:Pio*` commands are not affected. Default: `false`. + debug *platformio-debug* When `true`, echoes extra diagnostic info (terminal name, window/buffer ids, job id) each time a PIO terminal opens. Default: `false`. diff --git a/doc/tags b/doc/tags index e689bfa..c5a8743 100644 --- a/doc/tags +++ b/doc/tags @@ -16,5 +16,6 @@ platformio-menu_bindings platformio.txt /*platformio-menu_bindings* platformio-menu_key platformio.txt /*platformio-menu_key* platformio-menu_name platformio.txt /*platformio-menu_name* platformio-picker platformio.txt /*platformio-picker* +platformio-project_only platformio.txt /*platformio-project_only* platformio-usage platformio.txt /*platformio-usage* platformio.lua platformio.txt /*platformio.lua* From dcddae4a006791a0510951be336c8ccf3720d7d5 Mon Sep 17 00:00:00 2001 From: sail3r <82087684+sail3r@users.noreply.github.com> Date: Mon, 19 Oct 2026 12:49:18 +1300 Subject: [PATCH 3/3] docs(readme): restructure README around a single lazy.nvim spec Rewrite README.md; no code changes. Every Lua block was checked against the plugin source (see "Verification" below). Structure: Introduction (after the demo video), Requirements, Installation, Usage, Keybindings, Lazy loading, Roadmap, License. - Introduction: what PlatformIO is and what the plugin wraps: :Pioinit board/framework picker, build/upload/monitor in toggleterm terminals run from the nearest directory containing platformio.ini, :Piolib registry search + install, :PioLSP config generation (.ccls or compile_commands.json; it does not enable an LSP client), picker backends, the which-key menu (29 default items) and project_only. - Requirements: Neovim 0.12. :PioLSP calls `:lsp restart` (has('nvim-0.12')) and vim.json.encode() with { indent, sort_keys }, which are 0.12 options; on 0.10 json_encode() rejects a second argument ("expected 1 argument"). `pio`/`platformio` on PATH (pio_install_check runs `which pio`; sync_ttylist runs `platformio device list`). curl for :Piolib (plenary.curl shells out to curl). - Dependency table, derived from every non-platformio require(): - toggleterm.nvim: required. terminal.lua and piotermlist.lua require toggleterm.terminal; every pio-running command goes through terminal.ToggleTerminal(). - plenary.nvim: only :Piolib (piolib.lua requires plenary.curl). - which-key.nvim: only when menu_key is set (piomenu.lua returns early on menu_key == nil, pcall-requires which-key otherwise). - pickers: none required. pickers/ui_select.lua uses only vim.ui.select, and 'auto' tries telescope, snacks, mini_pick, ui_select in that order, so the built-in backend always loads. - Dropped from the docs: nvim-treesitter and telescope-ui-select.nvim are never required by the plugin. - Installation: one lazy.nvim spec. Active dependencies are toggleterm, plenary and which-key; telescope, snacks.picker and mini.pick are commented out with what each needs: snacks.picker needs picker.enabled (meta.needs_setup) and its default ui_select = true replaces vim.ui.select; mini.pick needs setup() (`opts = {}`). All setup() options live in `opts` with their defaults (menu_key shown as '\\'; default nil), replacing the vim.g.pioConfig + pcall(require) snippet. Notes that 'auto' picks the first installed picker, that an explicit backend does not fall back, and that one unknown key makes setup() ignore the whole table. - Usage: table of the 10 user commands from plugin/platformio.lua, plus :help PlatformIO and :checkhealth platformio. - Keybindings: the default menu_bindings tree (unchanged, verified equal to M.config.menu_bindings), node/item semantics, project_only link, and the buffer-local //q maps of PIO terminals (terminal.lua on_open). - Lazy loading: why to defer (plugin/ runs `which pio` and an async `platformio device list` at load), a cmd + keys example (keys lhs must match opts.menu_key), and a cmd + event example for project_only, since a lazy `keys` stub is a global mapping. - Removed the "Lazy::cond" section. project_only replaces its purpose. The snippet also called require('lazy').restore({ plguins = ... }); the misspelled key meant restore() ran for every plugin. - Removed the duplicate setup() example inside the old "Keybinds" section and the empty "Usage :h PlatformIO" heading. - ASCII only; markdownlint-cli2 clean except MD013 on line 2 (the logo tag URL), with MD013 ignored for tables/code, MD033 allowing h1/img/p, and MD041 off. Added alt text to the logo. Verification: a scratch script evaluates all four Lua blocks, runs the real setup() on the Installation opts (no errors, menu registered), checks every option is shown with its default, compares the Keybindings tree with M.config.menu_bindings, both cmd lists with the commands defined in plugin/platformio.lua, the keys lhs with opts.menu_key, and that every :Command named in the README exists. --- README.md | 481 ++++++++++++++++++++++++++++++------------------------ 1 file changed, 265 insertions(+), 216 deletions(-) diff --git a/README.md b/README.md index d64ce6c..e362eef 100644 --- a/README.md +++ b/README.md @@ -1,263 +1,312 @@

- + PlatformIO logo nvim-platformio.lua

-

PlatformIO wrapper for Neovim written in Lua.

+

PlatformIO wrapper for Neovim written in Lua.

+ + + +## Introduction + +[PlatformIO](https://platformio.org) is a cross-platform build system and +package manager for embedded development: hundreds of boards, dozens of +platforms and frameworks such as Arduino, ESP-IDF and Zephyr, all behind one +command-line tool. nvim-platformio.lua brings that workflow into Neovim. +Create a project, build and flash firmware, watch the serial port, install +libraries and set up C/C++ language-server support, without leaving the +editor or memorising `pio` flags. Anything else PlatformIO Core can do is one +`:Piocmdf` away. + +- **Project setup in seconds.** `:Pioinit` lets you search every board + PlatformIO knows, pick a framework, and initialises the project in the + current directory. +- **Build, upload and monitor.** Commands run in reusable floating or split + terminals, from the project root: the nearest directory above the current + file that contains `platformio.ini`. +- **Libraries from the registry.** `:Piolib` searches the PlatformIO registry, + installs your pick and refreshes the language-server configuration. +- **Language-server configuration.** `:PioLSP` generates `.ccls` for ccls, or + `compile_commands.json` for clangd, and restarts running LSP clients. The + server itself is enabled in your own LSP setup. +- **Your picker.** Telescope, snacks.picker, mini.pick, or Neovim's built-in + `vim.ui.select`. +- **A key-driven menu.** The default which-key menu puts 29 PlatformIO + actions under one prefix. It is fully customisable and can be limited to + buffers inside a PlatformIO project. + +## Requirements + +- Neovim 0.12 or newer. On older releases most commands work, but `:PioLSP` + can fail: it uses `:lsp restart` and `vim.json.encode()` options that were + added in 0.12. +- [PlatformIO Core](https://docs.platformio.org/en/latest/core/installation/index.html), + with `pio` and `platformio` on your `PATH`. +- `curl`, used by `:Piolib`. + +Plugin dependencies: + +| Plugin | Required | Used for | +| ---------------------------------------------------------------- | ----------------- | ------------------------------------------------------------ | +| [toggleterm.nvim](https://github.com/akinsho/toggleterm.nvim) | Yes | Terminals that run build, upload, monitor and `pio` commands | +| [plenary.nvim](https://github.com/nvim-lua/plenary.nvim) | For `:Piolib` | HTTP requests to the PlatformIO registry | +| [which-key.nvim](https://github.com/folke/which-key.nvim) | For `menu_key` | The PlatformIO key menu | +| A picker | No | Board, framework, library and terminal selection | + +No picker plugin is required. Without one, the plugin uses Neovim's built-in +`vim.ui.select`, so a plugin that replaces `vim.ui.select` (snacks.nvim does +by default) is used too. -### Demo +## Installation +With [lazy.nvim](https://github.com/folke/lazy.nvim). Every option is shown +with its default value, except `menu_key`, which is unset by default: +```lua +return { + 'anurag3301/nvim-platformio.lua', -https://github.com/user-attachments/assets/fdbb6655-4b2d-4a2b-81d1-fd8af6e7d9f1 + dependencies = { + "akinsho/toggleterm.nvim", -- required + "nvim-lua/plenary.nvim", -- :Piolib + "folke/which-key.nvim", -- menu_key + -- Pickers are optional. Uncomment your preference below, if desired. + -- Without any installed, vim.ui.select is used. + -- Telescope Picker + -- 'nvim-telescope/telescope.nvim', -
+ -- snacks.picker + -- Its default 'ui_select = true' also replaces vim.ui.select + -- { 'folke/snacks.nvim', opts = { picker = { enabled = true } } }, -Try the plugin with this minimal standalone config without modifying your existing nvim setup. **This is especially useful if you're encountering errors during installation or usage**. -```sh -wget https://raw.githubusercontent.com/anurag3301/nvim-platformio.lua/main/minimal_config.lua -nvim -u minimal_config.lua + -- mini.pick + -- opts = {} makes lazy.nvim call its setup() + -- { 'nvim-mini/mini.pick', opts = {} }, + }, -# Now run :Pioinit -``` + opts = { + -- Language server :PioLSP configures: 'ccls' | 'clangd'. + lsp = "ccls", -## Installation + -- With lsp = 'clangd', set how compile_commands.json is produced: + -- 'ccls' (converted from .ccls) | 'compiledb' (pio run -t compiledb). + clangd_source = "ccls", -#### PlatformIO Core -Follow the installation instructions in the [PlatformIO documentation](https://docs.platformio.org/en/latest/core/installation/index.html). + -- 'auto' | 'telescope' | 'snacks' | 'mini_pick' | 'ui_select'. + picker_backend = "auto", + -- true: add the menu only to buffers inside a PlatformIO project. + project_only = false, -#### Plugin -Install the plugin using lazy -```lua -return { - 'anurag3301/nvim-platformio.lua', + -- Echo terminal, window and job ids when a PIO terminal opens. + debug = false, + + -- Label of the menu group. + menu_name = "PlatformIO", - -- Dependencies are lazy-loaded by default unless specified otherwise. - dependencies = { - { 'akinsho/toggleterm.nvim' }, - { 'nvim-lua/plenary.nvim' }, - { 'folke/which-key.nvim' }, - { 'nvim-treesitter/nvim-treesitter' }, + -- Prefix of the which-key menu; leave unset to disable the menu. + menu_key = "\\", + -- Menu Keybindings: see Keybindings section below and paste the + -- contents of opts = {} here + }, +} +``` - -- install picker based on your likeing, remove or ignore rest +`picker_backend = 'auto'` uses the first picker that is installed, in the +order telescope, snacks, mini.pick, `vim.ui.select`. If more than one is +installed, for example because telescope is part of your config anyway, set +`picker_backend` explicitly. An explicitly chosen picker that is not installed +reports an error instead of falling back. - -- telescope - { 'nvim-telescope/telescope.nvim' }, - { 'nvim-telescope/telescope-ui-select.nvim' }, +With another plugin manager, install the same dependencies and call +`require('platformio').setup({ ... })` with the same table. - -- Snacks picker - {'folke/snacks.nvim', opts = { - picker = {enabled = true, ui_select = true } - }}, +Details for every option: `:help platformio-configuration`. - -- Mini.Pick - {'nvim-mini/mini.pick'}, - }, -} -``` +### Try this plugin without touching your neovim config +A standalone config that installs everything into a temporary directory. +This is also the quickest way to tell a plugin problem from a config problem: -#### Configuration -```lua -vim.g.pioConfig ={ - lsp = 'clangd', -- value: clangd | ccls - clangd_source = 'ccls', -- value: ccls | compiledb, For detailed explation check :help platformio-clangd_source - picker_backend = 'auto', -- value: auto | telescope | snacks | mini_pick | ui_select - -- default: auto, check :help platformio-picker - menu_key = '\\', -- replace this menu key to your convenience - project_only = false, -- true: menu only in buffers inside a PlatformIO project, check :help platformio-project_only - debug = false -- enable debug messages -} -local pok, platformio = pcall(require, 'platformio') -if pok then platformio.setup(vim.g.pioConfig) end +```sh +wget https://raw.githubusercontent.com/anurag3301/nvim-platformio.lua/main/minimal_config.lua +nvim -u minimal_config.lua ``` -#### Usage `:h PlatformIO` +Then inside neovim run: `:Pioinit` -#### Lazy::cond -The `cond` argument can be used to enable the plugin only if `platformio.ini` is present in cwd or you explicidly run `:Pioinit` +## Usage -```lua -return { - 'anurag3301/nvim-platformio.lua', - - -- optional: cond used to enable/disable platformio - -- based on existance of platformio.ini file and .pio folder in cwd. - -- You can enable platformio plugin, using :Pioinit command - cond = function() - -- local platformioRootDir = vim.fs.root(vim.fn.getcwd(), { 'platformio.ini' }) -- cwd and parents - local platformioRootDir = (vim.fn.filereadable('platformio.ini') == 1) and vim.fn.getcwd() or nil - if platformioRootDir then - -- if platformio.ini file exist in cwd, enable plugin to install plugin (if not istalled) and load it. - vim.g.platformioRootDir = platformioRootDir - elseif (vim.uv or vim.loop).fs_stat(vim.fn.stdpath('data') .. '/lazy/nvim-platformio.lua') == nil then - -- if nvim-platformio not installed, enable plugin to install it first time - vim.g.platformioRootDir = vim.fn.getcwd() - else -- if nvim-platformio.lua installed but disabled, create Pioinit command - vim.api.nvim_create_user_command('Pioinit', function() --available only if no platformio.ini and .pio in cwd - vim.api.nvim_create_autocmd('User', { - pattern = { 'LazyRestore', 'LazyLoad' }, - once = true, - callback = function(args) - if args.match == 'LazyRestore' then - require('lazy').load({ plugins = { 'nvim-platformio.lua' } }) - elseif args.match == 'LazyLoad' then - local pio_install_status = require('platformio.utils').pio_install_check() - if not pio_install_status then return end - vim.notify('PlatformIO loaded', vim.log.levels.INFO, { title = 'PlatformIO' }) - require("platformio").setup(vim.g.pioConfig) - vim.cmd('Pioinit') - end - end, - }) - vim.g.platformioRootDir = vim.fn.getcwd() - require('lazy').restore({ plguins = { 'nvim-platformio.lua' }, show = false }) - end, {}) - end - return vim.g.platformioRootDir ~= nil - end, - - -- Dependencies are lazy-loaded by default unless specified otherwise. - dependencies = { - { 'akinsho/toggleterm.nvim' }, - { 'nvim-lua/plenary.nvim' }, - { 'folke/which-key.nvim' }, - { 'nvim-treesitter/nvim-treesitter' }, - - -- install picker based on your likeing, remove or ignore rest - - -- telescope - { 'nvim-telescope/telescope.nvim' }, - { 'nvim-telescope/telescope-ui-select.nvim' }, - - -- Snacks picker - {'folke/snacks.nvim', opts = { - picker = {enabled = true, ui_select = true } - }}, - - -- Mini.Pick - {'nvim-mini/mini.pick'}, - }, -} -``` +| Command | Description | +| ------------------------------- | --------------------------------------------------------------------------- | +| `:Pioinit` | Pick a board and framework, initialise a project in the current directory | +| `:Piorun [target]` | Target `build`, `upload`, `uploadfs` or `clean`; default: build and upload | +| `:Piocmdf [args]` | Run `pio ` in a floating terminal; without args, just open it | +| `:Piocmdh [args]` | Same as `:Piocmdf`, in a horizontal split | +| `:Piomon [baud] [port]` | Serial monitor; completes baud rates and detected ports | +| `:Piolsserial` | List serial ports | +| `:Piolib ` | Search the PlatformIO registry and install the chosen library | +| `:PioLSP` | Generate the ccls or clangd configuration for the project | +| `:Piodebug` | Start the PlatformIO debugger with gdb | +| `:PioTermList` | Pick one of the open PIO terminals and switch to it | + +Full reference: `:help PlatformIO`. To check the installation, run +`:checkhealth platformio`. + +## Keybindings + +### Menu +Setting `menu_key` registers a which-key menu under that prefix, in normal and +visual mode. With `project_only = true` it is added only to buffers whose file +is in a directory, or below a directory, that contains `platformio.ini` +(`:help platformio-project_only`). + +The tree is defined by `menu_bindings`. A `menu` node opens a sub-menu and +holds `items`; an `item` node runs `command` as an Ex command. `shortcut` is +the key inside its menu. Setting `menu_bindings` replaces the whole default +tree, so copy it and edit it inside your Lazyvim config: -### Keybinds -These are the default keybindings, which you can override in your configuration. ```lua - local pok, platformio = pcall(require, 'platformio') - if pok then - platformio.setup({ - lsp = 'ccls', --default: ccls, other option: clangd - -- If you pick clangd, it also creates compile_commands.json - - -- Uncomment out following line to enable platformio menu. - -- menu_key = '\\', -- replace this menu key to your convenience - menu_name = 'PlatformIO', -- replace this menu name to your convenience - - -- Following are the default keybindings, you can overwrite them in the config - menu_bindings = { - { node = 'item', desc = '[L]ist terminals', shortcut = 'l', command = 'PioTermList' }, - { node = 'item', desc = '[T]erminal Core CLI', shortcut = 't', command = 'Piocmdf' }, - { - node = 'menu', - desc = '[G]eneral', - shortcut = 'g', - items = { - { node = 'item', desc = '[B]uild', shortcut = 'b', command = 'Piocmdf run' }, - { node = 'item', desc = '[U]pload', shortcut = 'u', command = 'Piocmdf run -t upload' }, - { node = 'item', desc = '[M]onitor', shortcut = 'm', command = 'Piocmdh run -t monitor' }, - { node = 'item', desc = '[C]lean', shortcut = 'c', command = 'Piocmdf run -t clean' }, - { node = 'item', desc = '[F]ull clean', shortcut = 'f', command = 'Piocmdf run -t fullclean' }, - { node = 'item', desc = '[D]evice list', shortcut = 'd', command = 'Piocmdf device list' }, - }, - }, - { - node = 'menu', - desc = '[P]latform', - shortcut = 'p', - items = { - { node = 'item', desc = '[B]uild file system', shortcut = 'b', command = 'Piocmdf run -t buildfs' }, - { node = 'item', desc = 'Program [S]ize', shortcut = 's', command = 'Piocmdf run -t size' }, - { node = 'item', desc = '[U]pload file system', shortcut = 'u', command = 'Piocmdf run -t uploadfs' }, - { node = 'item', desc = '[E]rase Flash', shortcut = 'e', command = 'Piocmdf run -t erase' }, - }, - }, - { - node = 'menu', - desc = '[D]ependencies', - shortcut = 'd', - items = { - { node = 'item', desc = '[L]ist packages', shortcut = 'l', command = 'Piocmdf pkg list' }, - { node = 'item', desc = '[O]utdated packages', shortcut = 'o', command = 'Piocmdf pkg outdated' }, - { node = 'item', desc = '[U]pdate packages', shortcut = 'u', command = 'Piocmdf pkg update' }, - }, - }, - { - node = 'menu', - desc = '[A]dvanced', - shortcut = 'a', - items = { - { node = 'item', desc = '[T]est', shortcut = 't', command = 'Piocmdf test' }, - { node = 'item', desc = '[C]heck', shortcut = 'c', command = 'Piocmdf check' }, - { node = 'item', desc = '[D]ebug', shortcut = 'd', command = 'Piocmdf debug' }, - { node = 'item', desc = 'Compilation Data[b]ase', shortcut = 'b', command = 'Piocmdf run -t compiledb' }, - { - node = 'menu', - desc = '[V]erbose', - shortcut = 'v', - items = { - { node = 'item', desc = 'Verbose [B]uild', shortcut = 'b', command = 'Piocmdf run -v' }, - { node = 'item', desc = 'Verbose [U]pload', shortcut = 'u', command = 'Piocmdf run -v -t upload' }, - { node = 'item', desc = 'Verbose [T]est', shortcut = 't', command = 'Piocmdf test -v' }, - { node = 'item', desc = 'Verbose [C]heck', shortcut = 'c', command = 'Piocmdf check -v' }, - { node = 'item', desc = 'Verbose [D]ebug', shortcut = 'd', command = 'Piocmdf debug -v' }, - }, - }, - }, - }, - { - node = 'menu', - desc = '[R]emote', - shortcut = 'r', - items = { - { node = 'item', desc = 'Remote [U]pload', shortcut = 'u', command = 'Piocmdf remote run -t upload' }, - { node = 'item', desc = 'Remote [T]est', shortcut = 't', command = 'Piocmdf remote test' }, - { node = 'item', desc = 'Remote [M]onitor', shortcut = 'm', command = 'Piocmdh remote run -t monitor' }, - { node = 'item', desc = 'Remote [D]evices', shortcut = 'd', command = 'Piocmdf remote device list' }, - }, - }, - { - node = 'menu', - desc = '[M]iscellaneous', - shortcut = 'm', - items = { - { node = 'item', desc = '[U]pgrade PlatformIO Core', shortcut = 'u', command = 'Piocmdf upgrade' }, - }, +opts = { + menu_bindings = { + { node = 'item', desc = '[L]ist terminals', shortcut = 'l', command = 'PioTermList' }, + { node = 'item', desc = '[T]erminal Core CLI', shortcut = 't', command = 'Piocmdf' }, + { + node = 'menu', + desc = '[G]eneral', + shortcut = 'g', + items = { + { node = 'item', desc = '[B]uild', shortcut = 'b', command = 'Piocmdf run' }, + { node = 'item', desc = '[U]pload', shortcut = 'u', command = 'Piocmdf run -t upload' }, + { node = 'item', desc = '[M]onitor', shortcut = 'm', command = 'Piocmdh run -t monitor' }, + { node = 'item', desc = '[C]lean', shortcut = 'c', command = 'Piocmdf run -t clean' }, + { node = 'item', desc = '[F]ull clean', shortcut = 'f', command = 'Piocmdf run -t fullclean' }, + { node = 'item', desc = '[D]evice list', shortcut = 'd', command = 'Piocmdf device list' }, + }, + }, + { + node = 'menu', + desc = '[P]latform', + shortcut = 'p', + items = { + { node = 'item', desc = '[B]uild file system', shortcut = 'b', command = 'Piocmdf run -t buildfs' }, + { node = 'item', desc = 'Program [S]ize', shortcut = 's', command = 'Piocmdf run -t size' }, + { node = 'item', desc = '[U]pload file system', shortcut = 'u', command = 'Piocmdf run -t uploadfs' }, + { node = 'item', desc = '[E]rase Flash', shortcut = 'e', command = 'Piocmdf run -t erase' }, + }, + }, + { + node = 'menu', + desc = '[D]ependencies', + shortcut = 'd', + items = { + { node = 'item', desc = '[L]ist packages', shortcut = 'l', command = 'Piocmdf pkg list' }, + { node = 'item', desc = '[O]utdated packages', shortcut = 'o', command = 'Piocmdf pkg outdated' }, + { node = 'item', desc = '[U]pdate packages', shortcut = 'u', command = 'Piocmdf pkg update' }, + }, + }, + { + node = 'menu', + desc = '[A]dvanced', + shortcut = 'a', + items = { + { node = 'item', desc = '[T]est', shortcut = 't', command = 'Piocmdf test' }, + { node = 'item', desc = '[C]heck', shortcut = 'c', command = 'Piocmdf check' }, + { node = 'item', desc = '[D]ebug', shortcut = 'd', command = 'Piocmdf debug' }, + { node = 'item', desc = 'Compilation Data[b]ase', shortcut = 'b', command = 'Piocmdf run -t compiledb' }, + { + node = 'menu', + desc = '[V]erbose', + shortcut = 'v', + items = { + { node = 'item', desc = 'Verbose [B]uild', shortcut = 'b', command = 'Piocmdf run -v' }, + { node = 'item', desc = 'Verbose [U]pload', shortcut = 'u', command = 'Piocmdf run -v -t upload' }, + { node = 'item', desc = 'Verbose [T]est', shortcut = 't', command = 'Piocmdf test -v' }, + { node = 'item', desc = 'Verbose [C]heck', shortcut = 'c', command = 'Piocmdf check -v' }, + { node = 'item', desc = 'Verbose [D]ebug', shortcut = 'd', command = 'Piocmdf debug -v' }, }, }, - }) - end + }, + }, + { + node = 'menu', + desc = '[R]emote', + shortcut = 'r', + items = { + { node = 'item', desc = 'Remote [U]pload', shortcut = 'u', command = 'Piocmdf remote run -t upload' }, + { node = 'item', desc = 'Remote [T]est', shortcut = 't', command = 'Piocmdf remote test' }, + { node = 'item', desc = 'Remote [M]onitor', shortcut = 'm', command = 'Piocmdh remote run -t monitor' }, + { node = 'item', desc = 'Remote [D]evices', shortcut = 'd', command = 'Piocmdf remote device list' }, + }, + }, + { + node = 'menu', + desc = '[M]iscellaneous', + shortcut = 'm', + items = { + { node = 'item', desc = '[U]pgrade PlatformIO Core', shortcut = 'u', command = 'Piocmdf upgrade' }, + }, + }, + }, +}, ``` -### Lazy loading +### Inside PIO terminals + +These are buffer-local to the terminals the plugin opens: + +| Mode | Key | Action | +| -------- | ------- | ------------------------------- | +| Terminal | `` | Leave terminal mode | +| Normal | `` | Return to terminal mode | +| Normal | `q` | Close the terminal window | + +## Lazy loading + +Loading the plugin runs `which pio` and starts `platformio device list` in the +background, to complete ports for `:Piomon`. To defer that until the plugin is +needed, add triggers to the spec from [Installation](#installation). -It's possible to lazy load the plugin using Lazy.nvim, this will load the plugins only when it is needed, to enable lazy loading, add this plugin spec to your config. +### with `project_only = false` (default) + +Load on the first PlatformIO command, or on the first press of the menu key: ```lua -cmd = { 'Pioinit', 'PioLSP', 'Piorun', 'Piocmdh', 'Piocmdf', 'Piolib', 'Piomon', 'Piolsserial', 'Piodebug', 'PioTermList' }, +cmd = { + 'Pioinit', 'PioLSP', 'Piorun', 'Piocmdh', 'Piocmdf', + 'Piolib', 'Piomon', 'Piolsserial', 'Piodebug', 'PioTermList', +}, + +-- `keys` must match `menu_key` inside `opts`, or opts.menu_key. +-- lazy.nvim maps this key globally until the plugin loads, then replays the keypress. +keys = { { '\\', desc = 'PlatformIO' } }, ``` -With `project_only = true`, don't lazy-load on the menu key with `keys` (lazy.nvim's stub mapping is global, in every buffer). Load the plugin when a file opens instead: +### with `project_only = true` + +Load `event` instead of `keys`. A `keys` trigger +is a global mapping, so the menu key would exist in every buffer until the +plugin loads: ```lua +cmd = { + 'Pioinit', 'PioLSP', 'Piorun', 'Piocmdh', 'Piocmdf', + 'Piolib', 'Piomon', 'Piolsserial', 'Piodebug', 'PioTermList', +}, event = { 'BufReadPost', 'BufNewFile' }, ``` +Neovim started without a file loads the plugin when the first file is opened +or the first PlatformIO command runs. + +## Roadmap + +- Connect `:Piodebug` with DAP. + +## License -### TODO -- Connect Piodebug with DAP +[MIT](LICENSE)