Skip to content

feat: project_only flag and updated README.md - #80

Open
sail3r wants to merge 3 commits into
anurag3301:mainfrom
sail3r:feat/project-only-menu
Open

sail3r wants to merge 3 commits into
anurag3301:mainfrom
sail3r:feat/project-only-menu

Conversation

@sail3r

@sail3r sail3r commented Oct 19, 2026

Copy link
Copy Markdown

Hi @anurag3301

First to thank you for still maintaining the repo, and second for creating it. Used it last year to create a wonderful autopilot in ESP32 and am just now starting another v2 version.

Made one reasonable implementation and also refactor README.md which was a little sparse and obscure. Made the configs Lazy centered, hope that's ok.

This PR

A new setup option, project_only (default false).

When it is true, the menu_key which-key menu is attached only to buffers
whose file is inside a PlatformIO project. "Inside" means platformio.ini
exists in the file's directory or in any parent directory, so a file deep in
src/ counts. A buffer with no file name is checked against the current
working directory.

When it is false nothing changes: the menu is mapped globally, as before.

require('platformio').setup({
  menu_key = '<leader>\\',
  project_only = true,
})

How it works

  • The menu is added with which-key's buffer option, so the mappings belong
    to that one buffer and nowhere else.
  • Every time you enter a buffer, the plugin checks it. It also checks the
    buffer you are in when setup() runs, because with lazy loading that
    buffer was already entered before the plugin existed.
  • Help, terminal and other special buffers are skipped.
  • A buffer that already got the menu is remembered (b:platformio_menu),
    so the menu is not added twice. Buffers outside a project are re-checked
    each time you enter them, so a project created later with :Pioinit is
    picked up.
  • The search for platformio.ini is the same one the commands already use.
    It now lives in its own function, find_platformioRootDir(), which does
    not show the "Could not find platformio.ini" error and does not change
    vim.g.platformioRootDir. The commands behave exactly as before.

Things worth knowing

  • With project_only = true, load the plugin when a file opens, e.g.
    lazy.nvim event = { 'BufReadPost', 'BufNewFile' }. Lazy-loading on the
    menu key with keys would put a global stub mapping in every buffer,
    which defeats the option.
  • The :Pio* commands stay available everywhere; only the menu is limited.
  • Small internal fix needed for this: which-key empties the table you pass
    to wk.add(), so the menu table is now built fresh for every call instead
    of being one shared module-level table.
  • The menu icon is now written as a byte escape. It is the same character
    at runtime; the source just stays plain ASCII.

README

The README is rewritten around one lazy.nvim spec. Nothing in it is new
behaviour; it describes what the code already does.

  • Introduction after the demo video: what PlatformIO is and what the
    plugin gives you inside Neovim.
  • Requirements, worked out from the code rather than copied from the
    old list:
    • toggleterm.nvim is the only hard requirement. Every command that runs
      pio opens a toggleterm terminal.
    • plenary.nvim is only needed for :Piolib, and which-key.nvim only when
      menu_key is set.
    • No picker plugin is needed. Without one, Neovim's own vim.ui.select
      is used. Telescope, snacks.picker and mini.pick are optional, and are
      shown commented out with what each one needs to work.
    • nvim-treesitter and telescope-ui-select.nvim were listed before, but
      the plugin never uses them, so they are gone.
    • Neovim 0.12, because :PioLSP uses :lsp restart and JSON
      formatting options that only exist from 0.12.
  • Installation is now a single spec with every option in opts, shown
    with its default and a one-line explanation. This replaces the separate
    vim.g.pioConfig snippet.
  • Keybindings holds the default menu tree and the keys inside PIO
    terminals. Lazy loading shows a cmd + keys setup, and a cmd +
    event setup for project_only.
  • The old "Lazy::cond" section is removed; project_only covers what it
    was for. Its snippet also had a typo (plguins) that made lazy.nvim
    restore every installed plugin instead of just this one.
  • A script checks the README against the code: every Lua block runs, the
    options pass the real setup(), the menu tree equals the built-in
    default, and the command lists match the commands the plugin defines.
    markdownlint passes apart from the long logo line at the top.

Testing

It was tested by hand: menu shows up only in project buffers with
project_only = true, and everywhere with false.

Also checked with stylua (--check .) and a script that runs the real setup() and which-key's real mapping parser against fake files and buffers (default mode, project and non-project buffers, nested directories, special buffers, invalid option value, and that the old root-directory lookup still behaves the same).

Cheers, and thank you again :))

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.
- 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.
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
  '<leader>\\'; 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 <Esc>/<Esc>/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
  <img> 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.
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant