Select a Flutter widget and its entire child tree in Neovim.
Inspired by the copy-widget VS Code extension.
Place your cursor on any Flutter widget name (or inside its parentheses) and
run the :CopyWidgetSelect command. The plugin will visually select the widget
constructor call and every nested child within it. You can then copy, delete,
cut, or perform any other Neovim operation on the selection.
MaterialApp(
child: Scaffold( // <- cursor here selects Scaffold and everything inside
appBar: AppBar(
title: Text('Hello'),
),
body: Column(
children: [
Container( // <- cursor here selects Container + its child Text
child: Text('World'),
),
ListView(),
],
),
),
){
"pretodev/copy-widget.nvim",
ft = "dart",
config = true,
}use {
"pretodev/copy-widget.nvim",
ft = "dart",
config = function()
require("copy-widget").setup()
end,
}The plugin does not define any default keymaps. Bind the command to whatever key you prefer:
vim.keymap.set("n", "<A-w>", "<cmd>CopyWidgetSelect<cr>", {
desc = "Select Flutter widget",
})Or inside the lazy.nvim spec:
{
"pretodev/copy-widget.nvim",
ft = "dart",
config = true,
keys = {
{ "<A-w>", "<cmd>CopyWidgetSelect<cr>", desc = "Select Flutter widget", ft = "dart" },
},
}| Command | Description |
|---|---|
:CopyWidgetSelect |
Select the widget at cursor and its entire subtree |
The plugin uses bracket matching on ( / ) pairs -- the same delimiters
that Dart uses for constructor calls. When the command runs:
- If the cursor is on a widget name (an identifier followed by
(), the plugin finds the matching)and selects from the name through the closing paren. - If the cursor is inside a widget's parentheses (on a property name, a
string literal, whitespace, etc.), the plugin searches backward for the
nearest unmatched
(, identifies the widget name before it, and searches forward for the matching).
This approach is simple, predictable, and requires no external dependencies (no Treesitter parser, no LSP).
Supported patterns:
- Simple widgets:
Text('hello') - Nested trees:
Scaffold(body: Column(children: [...])) - Generic types:
ListView<String>(children: [...]) - Named constructors:
Text.rich(TextSpan(...)) const/newkeywords are excluded from the selection (only the widget itself is selected)
Tests use plenary.nvim:
make testIf plenary is not in the default location, point to it:
PLENARY_PATH=/path/to/plenary.nvim make testMIT