diff --git a/README.md b/README.md
index 8547a98..e362eef 100644
--- a/README.md
+++ b/README.md
@@ -1,256 +1,312 @@
-
+
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,
- -- 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' },
+ -- Label of the menu group.
+ menu_name = "PlatformIO",
+ -- Prefix of the which-key menu; leave unset to disable the menu.
+ menu_key = "\\",
- -- install picker based on your likeing, remove or ignore rest
+ -- Menu Keybindings: see Keybindings section below and paste the
+ -- contents of opts = {} here
+ },
+}
+```
- -- telescope
- { 'nvim-telescope/telescope.nvim' },
- { 'nvim-telescope/telescope-ui-select.nvim' },
+`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.
- -- Snacks picker
- {'folke/snacks.nvim', opts = {
- picker = {enabled = true, ui_select = true }
- }},
+With another plugin manager, install the same dependencies and call
+`require('platformio').setup({ ... })` with the same table.
- -- Mini.Pick
- {'nvim-mini/mini.pick'},
- },
-}
-```
+Details for every option: `:help platformio-configuration`.
+### Try this plugin without touching your neovim config
-#### 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
- debug = false -- enable debug messages
-}
-local pok, platformio = pcall(require, 'platformio')
-if pok then platformio.setup(vim.g.pioConfig) end
+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:
+
+```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`
+
+## Usage
+
+| 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`.
-#### 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`
+## 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:
```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'},
+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' },
+ },
+ },
+ },
+ },
+ {
+ 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' },
+ },
+ },
},
-}
+},
```
+### 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).
+
+### with `project_only = false` (default)
+
+Load on the first PlatformIO command, or on the first press of the menu key:
-### 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' },
- },
- },
- },
- })
- end
+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' } },
```
-### Lazy loading
+### with `project_only = true`
-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.
+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' },
+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)
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*
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)