diff --git a/.luarc.json b/.luarc.json new file mode 100644 index 00000000..54965bab --- /dev/null +++ b/.luarc.json @@ -0,0 +1,9 @@ +{ + "workspace": { + "library": ["$VIMRUNTIME/lua", "${3rd}/luv/library", "./lua"], + "checkThirdParty": false + }, + "diagnostics": { + "globals": ["vim"] + } +} diff --git a/README.md b/README.md index 7971e09e..b69d0a70 100644 --- a/README.md +++ b/README.md @@ -7,18 +7,15 @@ ### Demo - - https://github.com/user-attachments/assets/fdbb6655-4b2d-4a2b-81d1-fd8af6e7d9f1 - -
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 +wget https://raw.githubusercontent.com/batoaqaa/nvim-platformio.lua/refs/heads/main/mini_nvimPlatformio.lua +nvim -u mini_nvimPlatformio.lua # Now run :Pioinit ``` @@ -26,102 +23,122 @@ nvim -u minimal_config.lua ## Installation #### PlatformIO Core -Follow the installation instructions in the [PlatformIO documentation](https://docs.platformio.org/en/latest/core/installation/index.html). +Follow the installation instructions in the [PlatformIO documentation](https://docs.platformio.org/en/latest/core/installation/index.html). #### Plugin + Install the plugin using lazy + ```lua return { - 'anurag3301/nvim-platformio.lua', + 'batoaqaa/nvim-platformio.lua', + -- cmd = { 'Pioinit', 'Piorun', 'Piocmdh', 'Piocmdf', 'Piolib', 'Piomon', 'Piodebug', 'Piodb' }, -- 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 - vim.notify('PlatformIO loaded', vim.log.levels.INFO, { title = 'PlatformIO' }) - require("platformio").setup(vim.g.pioConfig) - vim.cmd('Pioinit') - end - end, - }) + -- 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 and vim.fs.find('.pio', { path = platformioRootDir, type = 'directory' })[1] then + -- if platformio.ini file and .pio folder 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() - 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-telescope/telescope.nvim' }, - { 'nvim-telescope/telescope-ui-select.nvim' }, - { 'nvim-lua/plenary.nvim' }, - { 'folke/which-key.nvim' }, - { 'nvim-treesitter/nvim-treesitter' } - }, + 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 + vim.notify('PlatformIO loaded', vim.log.levels.INFO, { title = 'PlatformIO' }) + 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 = { + { 'akinsho/toggleterm.nvim' }, + { 'nvim-telescope/telescope.nvim' }, + { 'nvim-telescope/telescope-ui-select.nvim' }, + { 'nvim-lua/plenary.nvim' }, + { 'folke/which-key.nvim' }, + { + 'mason-org/mason-lspconfig.nvim', + dependencies = { + { 'mason-org/mason.nvim' }, + { 'folke/trouble.nvim' }, + { 'j-hui/fidget.nvim' }, -- status bottom right + }, + }, + }, } + ``` #### Usage `:h PlatformIO` ### Configuration + ```lua -vim.g.pioConfig ={ - lsp = 'clangd', -- value: clangd | ccls - menu_key = '\\', -- replace this menu key to your convenience - debug = false -- enable debug messages - clangd_source = 'ccls' -- value: ccls | compiledb, For detailed explation check :help platformio-clangd_source -} -local pok, platformio = pcall(require, 'platformio') -if pok then platformio.setup(vim.g.pioConfig) end + vim.g.pioConfig ={ + lspClangd = { + enabled = true, + attach = { + enabled = true, + keymaps = true, + }, + }, + menu_key = '\\', -- replace this menu key to your convenience + menu_name = 'PlatformIO', -- replace this menu name to your convenience + } + local pok, platformio = pcall(require, 'platformio') + if pok then platformio.setup(vim.g.pioConfig) end ``` ### 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 + lspClangd = { + enabled = false, + attach = { + enabled = false, + keymaps = false, + }, + }, + menu_key = '\\', -- replace this menu key to your convenience menu_name = 'PlatformIO', -- replace this menu name to your convenience + debug = false, - -- 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 = '[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 = '[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' }, }, }, @@ -130,10 +147,10 @@ These are the default keybindings, which you can override in your configuration. 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 = '[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 = 'item', desc = '[E]rase Flash', shortcut = 'e', command = 'Piocmdf run -t erase' }, }, }, { @@ -141,9 +158,9 @@ These are the default keybindings, which you can override in your configuration. desc = '[D]ependencies', shortcut = 'd', items = { - { node = 'item', desc = '[L]ist packages', shortcut = 'l', command = 'Piocmdf pkg list' }, + { 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 = 'item', desc = '[U]pdate packages', shortcut = 'u', command = 'Piocmdf pkg update' }, }, }, { @@ -151,31 +168,31 @@ These are the default keybindings, which you can override in your configuration. 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 = '[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 = '[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 [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' }, }, @@ -189,6 +206,7 @@ These are the default keybindings, which you can override in your configuration. }, }, }, + }) end ``` @@ -201,6 +219,6 @@ It's possible to lazy load the plugin using Lazy.nvim, this will load the plugin cmd = { 'Pioinit', 'Piorun', 'Piocmdh', 'Piocmdf', 'Piolib', 'Piomon', 'Piodebug', 'Piodb' }, ``` - ### TODO + - Connect Piodebug with DAP diff --git a/lua/nvimpio/boilerplate.lua b/lua/nvimpio/boilerplate.lua new file mode 100644 index 00000000..bf8b01ed --- /dev/null +++ b/lua/nvimpio/boilerplate.lua @@ -0,0 +1,494 @@ +M = {} + +M.core_dir = '' + +local boilerplate = {} + +-- INFO: main.cpp +--- stylua: ignore +boilerplate['arduino'] = { + rewrite = false, + read = false, + content = [[ +#include + +void setup() { + +} + +void loop() { + +} +]], +} + +-- INFO: platformio.ini +boilerplate['platformio.ini'] = { + rewrite = false, + read = false, + template = [[ +[platformio] +core_dir = %s +platforms_dir = ${platformio.core_dir}/platforms +packages_dir = ${platformio.core_dir}/packages +;libdeps_dir = ./external_libs + +default_envs = +;default_envs = uno, nodemcu + +;-------------------------------------------------------------------------- +[env] +framework = arduino +upload_speed = 115200 +monitor_speed = 9600 + +monitor_rts = 1 ; 1 combination to reset esp32c6 (Table 32.3-2. CDC-ACM Settings with RTS and DTR) +monitor_dtr = 0 ; 0 // pio dev mon --rts=0 --dtr=0 then pio dev mon --rts=1 dtr=0 + +extra_scripts = +;post:generate_compileDB.py +; pre:enable_toolchain.py ; enabled global env 'PLATFORMIO_SETTING_COMPILATIONDB_INCLUDE_TOOLCHAIN' + +lib_ldf_mode = chain ;Library dependencies Finder ldf + +;[env:seeed_xiao_esp32c3] +;platform = espressif32 +;board = seeed_xiao_esp32c3 + +]], + content = function(self) + return string.format(self.template, M.core_dir) + end, +} + +-- "--config-file=%s" +-- ============================================================================= +-- DYNAMIC CLANGD CONFIGURATION TEMPLATE +-- ============================================================================= +-- Note: %q is used for paths to handle escaping and spaces automatically. +-- INFO: .clangd_config +boilerplate['.clangd_config'] = { + rewrite = false, + read = true, + content = [[ +{ + cmd = { + "clangd", + "--all-scopes-completion", + "--background-index", + "--clang-tidy", + "--compile_args_from=filesystem", + "--enable-config", + "--completion-parse=always", + "--completion-style=detailed", + "--header-insertion=iwyu", + "--fallback-style=llvm", + "--log=verbose", + "--pch-storage=memory", + "--pretty", + "--ranking-model=decision_forest", + "--sync", + "--offset-encoding=utf-16", + "--query-driver=%s" + }, + filetypes = { 'c', 'cpp', 'objc', 'objcpp', 'cuda', 'proto' }, + root_markers = { + 'platformio.ini', + 'CMakeLists.txt', + '.clangd', + '.clang-tidy', + '.clang-format', + 'compile_commands.json', + 'compile_flags.txt', + 'configure.ac', + '.git', + }, + workspace_required = true, + single_file_support = true, + init_options = { + usePlaceholders = true, + completeUnimported = true, + fallbackFlags = {%s}, + clangdFileStatus = true, + compilationDatabasePath = %q, + } +} +]], +} +-- CompileFlags: +-- Add: +-- - "-xc++" +-- - "-std=c++17" +-- - "-D__cplusplus=201703L" +-- - "-isystemC:/Users/batoaqaa/.platformio/packages/toolchain-riscv32-esp/riscv32-esp-elf/include/c++/14.2.0" +-- - "-isystemC:/Users/batoaqaa/.platformio/packages/toolchain-riscv32-esp/riscv32-esp-elf/include/c++/14.2.0/riscv32-esp-elf" +-- - "-isystemC:/Users/batoaqaa/.platformio/packages/toolchain-riscv32-esp/lib/gcc/riscv32-esp-elf/14.2.0/include" +-- - "-isystemC:/Users/batoaqaa/.platformio/packages/toolchain-riscv32-esp/lib/gcc/riscv32-esp-elf/14.2.0/include-fixed" +-- - "-isystemC:/Users/batoaqaa/.platformio/packages/toolchain-riscv32-esp/riscv32-esp-elf/include" +-- Remove: +-- - "-target=*" +-- - "-target" +-- - "riscv32-esp-elf" +-- - "-fno-fat-lto-objects" +-- - "-fno%%-fat%%-lto%%-objects" +-- - "-fno%%-canonical%%-system%%-headers" +-- - "-misc-definitions-in-headers" +-- - "-fno-tree-switch-conversion" +-- - "-mtext-section-literals" +-- - "-mlong-calls" +-- - "-mlongcalls" +-- - "-fstrict-volatile-bitfields" +-- - "-free*" +-- - "-fipa-pta*" +-- - "-march=*" +-- - "-mabi=*" +-- - "-mcpu=*" +-- Diagnostics: +-- Suppress: +-- - "misc-definitions-in-headers" +-- - "pp_including_mainfile_in_preamble" +-- - "misc-unused-using-decls" +-- - "unused-includes" +-- ClangTidy: +-- Remove: +-- - "readability-*" +-- - "cert-err58-cpp" +-- - "llvmlibc-*" +-- - "fuchsia-*" +-- - "hicpp-avoid-c-arrays" +-- - "cppcoreguidelines-*" +-- - "llvm-*" +-- - "google-*" +-- - "bugprone-*" +-- - "hicpp-vararg" +-- - "modernize-*" +-- Index: +-- Background: Build +-- External: +-- File: .clangd_index + +-- INFO: .clangd +-- boilerplate['.clangd'] +boilerplate['.clangd'] = { + rewrite = false, + read = false, + -- template = [[ + content = [[ +--- +CompileFlags: + Add: + - "-xc++" + - "-std=gnu++17" + - "-Wno-pragma-system-header-outside-header" + - "-Wno-unknown-warning-option" + - "-Wno-unused-includes" + Remove: + - "-Wunknown-warning-option" + - "-fno-tree-switch-conversion" + - "-fno-fat-lto-objects" + - "-fno-canonical-system-headers" + - "-mtext-section-literals" + - "-mlong-calls" + - "-fstrict-volatile-bitfields" + - "-march=.*" + - "-mabi=.*" + - "-mcpu=.*" + - "-fipa-pta.*" +Diagnostics: + Suppress: + - "pp_file_not_found" + - "pp_file_not_found_angled_not_fatal" + - "pp_included_file_not_found" + - "pp_including_mainfile_in_preamble" + - "unused-includes" + - "misc-definitions-in-headers" + ClangTidy: + Remove: ["readability-*", "modernize-*", "bugprone-*", "cert-err58-cpp"] +]], + -- content = function(self) + -- local sysroot = '--sysroot=' .. _G.metadata.sysroot + -- local triplet = '--target=' .. _G.metadata.triplet + -- return string.format(self.template, triplet, sysroot) + -- end, +} + +-- INFO: .clang-format +boilerplate['.clang-format'] = { + rewrite = false, + read = false, + content = [[ +--- +Language: Cpp +# BasedOnStyle: LLVM +AccessModifierOffset: -2 +AlignAfterOpenBracket: Align +AlignArrayOfStructures: None +AlignConsecutiveAssignments: + Enabled: false + AcrossEmptyLines: false + AcrossComments: false + AlignCompound: false + AlignFunctionPointers: false + PadOperators: true +AlignConsecutiveBitFields: + Enabled: false + AcrossEmptyLines: false + AcrossComments: false + AlignCompound: false + AlignFunctionPointers: false + PadOperators: false +AlignConsecutiveDeclarations: + Enabled: false + AcrossEmptyLines: false + AcrossComments: false + AlignCompound: false + AlignFunctionPointers: false + PadOperators: false +AlignConsecutiveMacros: + Enabled: false + AcrossEmptyLines: false + AcrossComments: false + AlignCompound: false + AlignFunctionPointers: false + PadOperators: false +AlignConsecutiveShortCaseStatements: + Enabled: false + AcrossEmptyLines: false + AcrossComments: false + AlignCaseColons: false +AlignEscapedNewlines: Right +AlignOperands: Align +AlignTrailingComments: + Kind: Always + OverEmptyLines: 0 +AllowAllArgumentsOnNextLine: true +AllowAllParametersOfDeclarationOnNextLine: true +AllowBreakBeforeNoexceptSpecifier: Never +AllowShortBlocksOnASingleLine: Never +AllowShortCaseLabelsOnASingleLine: false +AllowShortCompoundRequirementOnASingleLine: true +AllowShortEnumsOnASingleLine: true +AllowShortFunctionsOnASingleLine: All +AllowShortIfStatementsOnASingleLine: Never +AllowShortLambdasOnASingleLine: All +AllowShortLoopsOnASingleLine: false +AlwaysBreakAfterDefinitionReturnType: None +AlwaysBreakAfterReturnType: None +AlwaysBreakBeforeMultilineStrings: false +AlwaysBreakTemplateDeclarations: MultiLine +AttributeMacros: + - __capability +BinPackArguments: true +BinPackParameters: true +BitFieldColonSpacing: Both +BraceWrapping: + AfterCaseLabel: false + AfterClass: false + AfterControlStatement: Never + AfterEnum: false + AfterExternBlock: false + AfterFunction: false + AfterNamespace: false + AfterObjCDeclaration: false + AfterStruct: false + AfterUnion: false + BeforeCatch: false + BeforeElse: false + BeforeLambdaBody: false + BeforeWhile: false + IndentBraces: false + SplitEmptyFunction: true + SplitEmptyRecord: true + SplitEmptyNamespace: true +BreakAdjacentStringLiterals: true +BreakAfterAttributes: Leave +BreakAfterJavaFieldAnnotations: false +BreakArrays: true +BreakBeforeBinaryOperators: None +BreakBeforeConceptDeclarations: Always +BreakBeforeBraces: Attach +BreakBeforeInlineASMColon: OnlyMultiline +BreakBeforeTernaryOperators: true +BreakConstructorInitializers: BeforeColon +BreakInheritanceList: BeforeColon +BreakStringLiterals: true +ColumnLimit: 80 +CommentPragmas: '^ IWYU pragma:' +CompactNamespaces: false +ConstructorInitializerIndentWidth: 4 +ContinuationIndentWidth: 4 +Cpp11BracedListStyle: true +DerivePointerAlignment: false +DisableFormat: false +EmptyLineAfterAccessModifier: Never +EmptyLineBeforeAccessModifier: LogicalBlock +ExperimentalAutoDetectBinPacking: false +FixNamespaceComments: true +ForEachMacros: + - foreach + - Q_FOREACH + - BOOST_FOREACH +IfMacros: + - KJ_IF_MAYBE +IncludeBlocks: Preserve +IncludeCategories: + - Regex: '^"(llvm|llvm-c|clang|clang-c)/' + Priority: 2 + SortPriority: 0 + CaseSensitive: false + - Regex: '^(<|"(gtest|gmock|isl|json)/)' + Priority: 3 + SortPriority: 0 + CaseSensitive: false + - Regex: '.*' + Priority: 1 + SortPriority: 0 + CaseSensitive: false +IncludeIsMainRegex: '(Test)?$' +IncludeIsMainSourceRegex: '' +IndentAccessModifiers: false +IndentCaseBlocks: false +IndentCaseLabels: false +IndentExternBlock: AfterExternBlock +IndentGotoLabels: true +IndentPPDirectives: None +IndentRequiresClause: true +IndentWidth: 2 +IndentWrappedFunctionNames: false +InsertBraces: false +InsertNewlineAtEOF: false +InsertTrailingCommas: None +IntegerLiteralSeparator: + Binary: 0 + BinaryMinDigits: 0 + Decimal: 0 + DecimalMinDigits: 0 + Hex: 0 + HexMinDigits: 0 +JavaScriptQuotes: Leave +JavaScriptWrapImports: true +KeepEmptyLinesAtTheStartOfBlocks: true +KeepEmptyLinesAtEOF: false +LambdaBodyIndentation: Signature +LineEnding: DeriveLF +MacroBlockBegin: '' +MacroBlockEnd: '' +MaxEmptyLinesToKeep: 1 +NamespaceIndentation: None +ObjCBinPackProtocolList: Auto +ObjCBlockIndentWidth: 2 +ObjCBreakBeforeNestedBlockParam: true +ObjCSpaceAfterProperty: false +ObjCSpaceBeforeProtocolList: true +PackConstructorInitializers: BinPack +PenaltyBreakAssignment: 2 +PenaltyBreakBeforeFirstCallParameter: 19 +PenaltyBreakComment: 300 +PenaltyBreakFirstLessLess: 120 +PenaltyBreakOpenParenthesis: 0 +PenaltyBreakScopeResolution: 500 +PenaltyBreakString: 1000 +PenaltyBreakTemplateDeclaration: 10 +PenaltyExcessCharacter: 1000000 +PenaltyIndentedWhitespace: 0 +PenaltyReturnTypeOnItsOwnLine: 60 +PointerAlignment: Right +PPIndentWidth: -1 +QualifierAlignment: Leave +ReferenceAlignment: Pointer +ReflowComments: true +RemoveBracesLLVM: false +RemoveParentheses: Leave +RemoveSemicolon: false +RequiresClausePosition: OwnLine +RequiresExpressionIndentation: OuterScope +SeparateDefinitionBlocks: Leave +ShortNamespaceLines: 1 +SkipMacroDefinitionBody: false +SortIncludes: CaseSensitive +SortJavaStaticImport: Before +SortUsingDeclarations: LexicographicNumeric +SpaceAfterCStyleCast: false +SpaceAfterLogicalNot: false +SpaceAfterTemplateKeyword: true +SpaceAroundPointerQualifiers: Default +SpaceBeforeAssignmentOperators: true +SpaceBeforeCaseColon: false +SpaceBeforeCpp11BracedList: false +SpaceBeforeCtorInitializerColon: true +SpaceBeforeInheritanceColon: true +SpaceBeforeJsonColon: false +SpaceBeforeParens: ControlStatements +SpaceBeforeParensOptions: + AfterControlStatements: true + AfterForeachMacros: true + AfterFunctionDefinitionName: false + AfterFunctionDeclarationName: false + AfterIfMacros: true + AfterOverloadedOperator: false + AfterPlacementOperator: true + AfterRequiresInClause: false + AfterRequiresInExpression: false + BeforeNonEmptyParentheses: false +SpaceBeforeRangeBasedForLoopColon: true +SpaceBeforeSquareBrackets: false +SpaceInEmptyBlock: false +SpacesBeforeTrailingComments: 1 +SpacesInAngles: Never +SpacesInContainerLiterals: true +SpacesInLineCommentPrefix: + Minimum: 1 + Maximum: -1 +SpacesInParens: Never +SpacesInParensOptions: + InCStyleCasts: false + InConditionalStatements: false + InEmptyParentheses: false + Other: false +SpacesInSquareBrackets: false +Standard: Latest +StatementAttributeLikeMacros: + - Q_EMIT +StatementMacros: + - Q_UNUSED + - QT_REQUIRE_VERSION +TabWidth: 8 +UseTab: Never +VerilogBreakBetweenInstancePorts: true +WhitespaceSensitiveMacros: + - BOOST_PP_STRINGIZE + - CF_SWIFT_NAME + - NS_SWIFT_NAME + - PP_STRINGIZE + - STRINGIZE +... +]], +} +-- stylua: ignore +function M.boilerplate_gen(framework, src_path, filename) + filename = filename or framework + local entry = boilerplate[framework] + if not entry then return '' end + local file_path = vim.fs.normalize(src_path .. '/' .. filename) + + if vim.uv.fs_stat(file_path) then + if not entry.rewrite then + if entry.read then + local ok, content = vim.misc.readFile(file_path) + if ok then return content end + -- local fr = io.open(file_path, 'r') + -- if fr then return (fr:read('*a')) end + end + return '' + end + end + -- + local template = type(entry.content) == 'function' and entry:content() or entry.content + vim.misc.writeFile(file_path, template, {}) + + if entry.read then + return template + else return '' end +end + +return M diff --git a/lua/platformio/init.lua b/lua/nvimpio/init.lua similarity index 67% rename from lua/platformio/init.lua rename to lua/nvimpio/init.lua index e1307d5a..e06c0eb1 100644 --- a/lua/platformio/init.lua +++ b/lua/nvimpio/init.lua @@ -1,10 +1,15 @@ local M = {} M.config = { - lsp = 'ccls', - menu_key = nil, - menu_name = 'PlatformIO', + lspClangd = { + enabled = false, + attach = { + enabled = false, + keymaps = false, + }, + }, + menu_key = '\\', -- replace this menu key to your convenience + menu_name = 'PlatformIO', -- replace this menu name to your convenience debug = false, - clangd_source = 'ccls', menu_bindings = { { node = 'item', desc = '[L]ist terminals', shortcut = 'l', command = 'PioTermList' }, @@ -85,8 +90,6 @@ M.config = { { node = 'item', desc = '[U]pgrade PlatformIO Core', shortcut = 'u', command = 'Piocmdf upgrade' }, }, }, - -- }, -- - -- }, -- }, } @@ -151,56 +154,82 @@ local function validateMenu(menu) return true end -function M.setup(user_config) - if next(user_config) ~= nil then - local valid_keys = { - lsp = true, - menu_key = true, - menu_name = true, - menu_bindings = true, - debug = true, - clangd_source = true, - } - local err = false - for key, value in pairs(user_config or {}) do - if not valid_keys[key] then - local error_message = string.format('Invalid PlatformIO settings key-value: %s = "%s"', key, value) - vim.api.nvim_echo({ { error_message, 'ErrorMsg' } }, true, {}) - err = true +function M.piomenu(config) + local icon = { icon = ' ', color = 'orange' } -- Assign platformio orange icon + local wk_table = { mode = { 'n', 'v' } } + + local function traverseMenu(menu, wkey) + for _, child_node in ipairs(menu) do + if child_node.node == 'menu' then + traverseMenu(child_node.items, wkey .. child_node.shortcut) + table.insert(wk_table, { wkey .. child_node.shortcut, group = child_node.desc, icon = icon }) + elseif child_node.node == 'item' then + table.insert(wk_table, { + wkey .. child_node.shortcut, + ' ' .. child_node.command .. '', + desc = child_node.desc, + icon = icon, + }) end end - if user_config.lsp and not (user_config.lsp == 'ccls' or user_config.lsp == 'clangd') then - vim.api.nvim_echo( - { { 'Invalid PlatformIO lsp "' .. user_config.lsp .. '", {allowed "clangd" or "ccls"} (default "' .. M.config.lsp .. '" will be used)', 'ErrorMsg' } }, - true, - {} - ) - user_config.lsp = M.config.lsp - end - if user_config.lsp == 'clangd' then - if user_config.clangd_source ~= 'ccls' and user_config.clangd_source ~= 'compiledb' then - vim.api.nvim_echo( - { { 'Invalid clangd source {allowed "ccls" or "compiledb"} (default "' .. M.config.clangd_source .. '" will be used)', 'ErrorMsg' } }, - true, - {} - ) - user_config.clangd_source = M.config.clangd_source + end + if config.menu_key == nil then + return + end + + local ok, wk = pcall(require, 'which-key') + if not ok then + vim.api.nvim_echo({ { 'which-key plugin not found!', 'ErrorMsg' } }, true, {}) + return + end + + wk.setup({ + preset = 'helix', --'modern', --'classic' + }) + 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) + + wk.add(wk_table) +end + +function M.setup(user_config) + if vim.g.platformioRootDir and (next(user_config) ~= nil) then + if user_config.lspClangd then + vim.validate('lspClangd', user_config.lspClangd, 'table', true) + vim.validate('lspClangdEnabled', user_config.lspClangd.enabled, 'boolean', true) + if user_config.lspClangd.attach then + vim.validate('lspAttach', user_config.lspClangd.attach, 'table', true) + vim.validate('lspAttachEnabled', user_config.lspClangd.attach.enabled, 'boolean', true) + vim.validate('lspKeymaps', user_config.lspClangd.attach.keyMaps, 'boolean', true) end end - if not err then -- if no error, merge user_config to M.config - if user_config.menu_bindings then - if not validateMenu(user_config.menu_bindings) then - user_config.menu_bindings = nil -- if validation error, cancel merging menu_bindings with M.config - -- else - -- print('good validation') - end + vim.validate('menu_key', user_config.lspClangd_enable, 'string', true) + vim.validate('menu_name', user_config.menu_name, 'string', true) + vim.validate('debug', user_config.debug, 'boolean', true) + vim.validate('menu_bindings', user_config.menu_bindings, 'table', true) + + if user_config.menu_bindings then + if not validateMenu(user_config.menu_bindings) then + user_config.menu_bindings = nil -- if validation error, cancel merging menu_bindings with M.config + -- else + -- print('good validation') end - M.config = vim.tbl_deep_extend('force', M.config, user_config or {}) end + M.config = vim.tbl_deep_extend('force', M.config, user_config or {}) end - require('platformio.piomenu').piomenu(M.config) + M.piomenu(M.config) + + vim.schedule(function() + require('nvimpio.pio.watcher').init() + end) end +vim.notify('nvim-platformio.lua started', vim.log.levels.INFO) + return M diff --git a/lua/nvimpio/lspConfig/attach.lua b/lua/nvimpio/lspConfig/attach.lua new file mode 100644 index 00000000..4cc87a84 --- /dev/null +++ b/lua/nvimpio/lspConfig/attach.lua @@ -0,0 +1,126 @@ +-- local piolsp = require('nvimpio.piolsp') --.piolsp +-- INFO: LspAttach autocommand start +vim.api.nvim_create_autocmd('LspAttach', { + group = vim.api.nvim_create_augroup('platformio-lsp-attach', { clear = true }), + callback = function(args) + local client = assert(vim.lsp.get_client_by_id(args.data.client_id)) + local bufnr = args.buf + + if client then + vim.api.nvim_echo({ { 'Attaching ' .. client.name .. ' to buffer ' .. bufnr, 'Info' } }, true, {}) + + ------------------------------------------------------------------ + if client.name == 'clangd' then + local uri = vim.uri_from_bufnr(bufnr) + if not uri:match('^file://') then + return -- Stop here for non-file buffers (like git:// or nvim://) + end + vim.api.nvim_buf_create_user_command(bufnr, 'LspClangdSwitchSourceHeader', function() + local params = vim.lsp.util.make_text_document_params(bufnr) + client:request('textDocument/switchSourceHeader', params, function(err, result) + if err then + vim.notify('LSP Attach: Clangd Error ' .. tostring(err), vim.log.levels.ERROR) + return + end + if not result or result == '' then + vim.notify('LSP Attach: Corresponding file cannot be determined', vim.log.levels.WARN) + return + end + -- Use vim.schedule to ensure we aren't editing while the LSP is in a callback + vim.schedule(function() + local target = type(result) == 'string' and result or result.uri + local fname = vim.uri_to_fname(target) + vim.cmd.edit(vim.uri_to_fname(fname)) + end) + end, bufnr) + end, { desc = 'Switch between source/header' }) + end + + -- use lsp completion if no blink + local ok, _ = pcall(require, 'blink.cmp') + if not ok then + if client:supports_method('textDocument/completion') then + vim.opt.completeopt = { 'menu', 'menuone', 'noselect', 'noinsert', 'fuzzy', 'popup' } + + -- Enable native completion for this specific client and buffer + vim.lsp.completion.enable(true, client.id, args.buf, { autotrigger = true }) + vim.keymap.set('i', ' End LspAttach autocommand diff --git a/lua/nvimpio/lspConfig/clangd.lua b/lua/nvimpio/lspConfig/clangd.lua new file mode 100644 index 00000000..9c167fad --- /dev/null +++ b/lua/nvimpio/lspConfig/clangd.lua @@ -0,0 +1,277 @@ +local boilerplate_gen = require('nvimpio.boilerplate').boilerplate_gen +local ok, result +ok, result = pcall(require, 'fidget') +if ok then + result.setup({}) +end + +----------------------------------------------------------------------------------------- +ok, result = pcall(require, 'trouble') +if ok then + result.setup({}) +end + +---------------------------------------------------------------------------------------- +-- INFO: setup and install mason packages +----------------------------------------------------------------------------------------- +ok, result = pcall(require, 'mason') +if ok then + result.setup({ + PATH = 'append', + ui = { + border = 'single', + icons = { + package_installed = '✓', + package_pending = '➜', + package_uninstalled = '✗', + }, + }, + }) +end + +-- List of packages you want Mason to ensure are installed +local ensure_installed = { + -- 'clang-format', embeded in clangd + -- 'stylua', +} +-- call mason-registry function to install or ensure formatters/linters are installed +local mr = require('mason-registry') +mr.refresh(function() + for _, tool in ipairs(ensure_installed) do + ok, result = pcall(mr.get_package, tool) + if ok and result then + if not result:is_installed() then + if not result:is_installing() then + result:install({}, function(success, _) + if not success then + vim.defer_fn(function() + vim.notify('LSP: clangd; ' .. tool .. ' failed to install', vim.log.levels.ERROR) + end, 0) + end + end) + else + vim.defer_fn(function() + vim.notify('LSP: clangd; ' .. tool .. ' already installed', vim.log.levels.WARN) + end, 0) + end + end + else + vim.defer_fn(function() + vim.notify('LSP: clangd; Failed to get package: ' .. tool, vim.log.levels.WARN) + end, 0) + end + end +end) + +---------------------------------------------------------------------------------------- +-- INFO: install clangd using mason-lspconfig +----------------------------------------------------------------------------------------- +local mok, mason_lspconfig = pcall(require, 'mason-lspconfig') +if mok then + mason_lspconfig.setup({ + ensure_installed = { 'clangd', 'lua_ls', 'pyrefly', 'yamlls', 'jsonls' }, + automatic_enable = true, -- this will automatically enable LSP servers after lsp.config + }) +end + +local capabilities = vim.lsp.protocol.make_client_capabilities() + +capabilities.textDocument.foldingRange = { + textDocument = { + -- Folding capabilities for nvim-ufo + foldingRange = { + dynamicRegistration = false, + lineFoldingOnly = true, + }, + }, +} +local bok, blink = pcall(require, 'blink.cmp') +if bok then + capabilities = blink.get_lsp_capabilities(capabilities) +end + +-- INFO: 1 +vim.lsp.config('*', { + capabilities = capabilities, + root_markers = { '.git' }, + workspace_required = false, +}) + +---------------------------------------------------------------------------------------- +-- INFO: configure clangd lsp server +----------------------------------------------------------------------------------------- +--stylua: ignore +function _G.get_clangd_config() + local new_root_dir = vim.uv.cwd() or '.' + if not new_root_dir then return end + + -- 1. Safe defaults (Standard clangd behavior) + local f_flags, q_driver = [["-std=c++17", "-xc++"]], '--query-driver=**' + + -- 2. Run your toolchain detection + if _G.metadata and _G.metadata.cc_compiler and _G.metadata.cc_compiler ~= '' then + if _G.metadata.triplet and _G.metadata.triplet ~= '' then + -- local include_flags = table.concat(vim.tbl_map(function(item) + -- return '"' .. item .. '"' + -- end, _G.metadata.fallbackFlags), ", ") + -- + -- local includes_toolchain = table.concat(vim.tbl_map(function(item) + -- return '"' .. item .. '"' + -- end, _G.metadata.includes_toolchain), ", ") + + f_flags = '' + -- f_flags = string.format([["-std=gnu++17", "-xc++", "-D__cplusplus=201703L", "--target=%s", "--sysroot=%s", %s, %s]], _G.metadata.triplet, _G.metadata.sysroot, includes_toolchain, include_flags) + -- f_flags = string.format('"--sysroot=%s"', _G.metadata.sysroot) + -- f_flags = string.format([["--sysroot=%s", %s]], _G.metadata.sysroot, include_flags) + + -- q_driver = '**' --_G.metadata.query_driver .. ',C:/PROGRA~1/LLVM/bin/*' -- use with "--query-driver=%s" + q_driver = _G.metadata.query_driver --.. ',C:/PROGRA~1/LLVM/bin/*' -- use with "--query-driver=%s" + end + end + + -- 3. Format your template string + local table_config = boilerplate_gen([[.clangd_config]], vim.g.platformioRootDir) + local formatted_str = string.format(table_config or '', q_driver, f_flags, vim.misc.normalizePath(new_root_dir)) + -- local formatted_str = string.format(table_config or '', q_driver, '', vim.misc.normalizePath(new_root_dir)) + -- local formatted_str = string.format(table_config or '', q_driver, '', vim.g.platformioRootDir) + + -- 4. Load the config table + local cok, clangd_config = pcall(function() return load('return ' .. formatted_str)() end) + + local formated = vim.misc.jsonFormat(clangd_config) + local file = vim.misc.joinPath(vim.uv.cwd(), 'clangd_config.json') + vim.misc.writeFile(file, formated, {}) + + if cok and clangd_config then + -- print(vim.inspect(clangd_config)) + return clangd_config + end +end + +-- Apply and Enable +vim.lsp.config('clangd', _G.get_clangd_config()) +vim.lsp.enable('clangd') + +---------------------------------------------------------------------------------------- +-- INFO: configure jsonls lsp server +----------------------------------------------------------------------------------------- +local jsonls = { + -- lazy-load schemastore when needed + cmd = { 'vscode-json-language-server', '--stdio' }, + filetypes = { 'json', 'jsonc' }, + init_options = { provideFormatter = true }, + root_makers = { '.git' }, +} +-- Apply and Enable +vim.lsp.config('jsonls', jsonls) + +---------------------------------------------------------------------------------------- +-- INFO: configure clangd lsp server +----------------------------------------------------------------------------------------- +local lua_ls = { + cmd = { 'lua-language-server' }, + filetypes = { 'lua' }, + root_markers = { + '.luarc.json', + '.luarc.jsonc', + '.luacheckrc', + '.stylua.toml', + 'selene.toml', + 'selene.yml', + '.git', + }, + settings = { + Lua = { + hint = { + enable = true, + arrayIndex = 'Enable', + await = true, + paramName = 'All', + paramType = true, + semicolon = 'Disable', + setType = true, + }, + telemetry = { enable = false }, + diagnostics = { globals = { 'vim' } }, + runtime = { + -- Specify LuaJIT for Neovim + version = 'LuaJIT', + -- Include Neovim runtime files + path = vim.split(package.path, ';'), + }, + workspace = { + checkThirdParty = false, + library = { + vim.env.VIMRUNTIME, + '${3rd}/luv/library', + './lua', + vim.api.nvim_get_runtime_file('', true), + -- Depending on the usage, you might want to add additional paths here. + -- "${3rd}/busted/library", + }, + }, + }, + }, +} +vim.lsp.config('lua_ls', lua_ls) + +local yamlls = { + -- on_attach = opts.on_attach, + cmd = { 'yaml-language-server', '--stdio' }, + filetypes = { 'yaml', 'yaml.docker-compose', 'yaml.gitlab' }, + settings = { + yaml = { + hover = true, + validate = false, + completion = true, + keyOrdering = false, + format = { enabled = false }, + redhat = { + telemetry = { enabled = false }, + }, + schemaStore = { + enable = true, + url = 'https://www.schemastore.org/api/json/catalog.json', + }, + schemas = { + kubernetes = '*.yaml', + ['http://json.schemastore.org/github-workflow'] = '.github/workflows/*', + ['http://json.schemastore.org/github-action'] = '.github/action.{yml,yaml}', + ['https://raw.githubusercontent.com/microsoft/azure-pipelines-vscode/master/service-schema.json'] = 'azure-pipelines.yml', + ['http://json.schemastore.org/ansible-stable-2.9'] = 'roles/tasks/*.{yml,yaml}', + ['http://json.schemastore.org/prettierrc'] = '.prettierrc.{yml,yaml}', + ['http://json.schemastore.org/kustomization'] = 'kustomization.{yml,yaml}', + ['http://json.schemastore.org/ansible-playbook'] = '*play*.{yml,yaml}', + ['http://json.schemastore.org/chart'] = 'Chart.{yml,yaml}', + ['https://json.schemastore.org/dependabot-v2'] = '.github/dependabot.{yml,yaml}', + ['https://gitlab.com/gitlab-org/gitlab/-/raw/master/app/assets/javascripts/editor/schema/ci.json'] = '*gitlab-ci*.{yml,yaml}', + ['https://raw.githubusercontent.com/OAI/OpenAPI-Specification/main/schemas/v3.1/schema.json'] = '*api*.{yml,yaml}', + ['https://raw.githubusercontent.com/compose-spec/compose-spec/master/schema/compose-spec.json'] = '*docker-compose*.{yml,yaml}', + ['https://raw.githubusercontent.com/argoproj/argo-workflows/master/api/jsonschema/schema.json'] = '*flow*.{yml,yaml}', + ['https://raw.githubusercontent.com/yannh/kubernetes-json-schema/refs/heads/master/v1.32.1-standalone-strict/all.json'] = '/*.k8s.yaml', + }, + }, + }, +} +vim.lsp.config('yamlls', yamlls) + +local pyrefly = { + name = 'pyrefly', + cmd = { 'pyrefly', 'lsp' }, + filetypes = { 'python' }, + root_markers = { 'pyrefly.toml', 'pyproject.toml', 'setup.py', 'setup.cfg', 'requirements.txt', 'Pipfile', '.git' }, + settings = { + python = { + pyrefly = { + displayTypeErrors = 'force-on', + }, + -- pythonPath = vim.env.VIRTUAL_ENV, + venvPath = vim.env.VIRTUAL_ENV, + }, + }, +} +vim.lsp.config('pyrefly', pyrefly) + +-- restart lsp +-- require('nvimpio.lspConfig.tools').lsp_restart('clangd') +---------------------------------------------------------------------------------- diff --git a/lua/nvimpio/lspConfig/keymaps.lua b/lua/nvimpio/lspConfig/keymaps.lua new file mode 100644 index 00000000..a757b8b9 --- /dev/null +++ b/lua/nvimpio/lspConfig/keymaps.lua @@ -0,0 +1,136 @@ +local K = {} +--Lua functions in combination with the option expr = true handles keycodes automatically +function K.lspKeymaps(client, bufnr) + local bufkeymap = function(mode, lhs, rhs, desc) + vim.keymap.set(mode, lhs, rhs, { buffer = bufnr, silent = true, desc = desc }) -- noremap by default + end + -- Disable defaults + pcall(vim.keymap.del, 'n', 'gra') + pcall(vim.keymap.del, 'n', 'gri') + pcall(vim.keymap.del, 'n', 'grn') + pcall(vim.keymap.del, 'n', 'grr') + pcall(vim.keymap.del, 'n', 'gO') + pcall(vim.keymap.del, 'n', 'K') + -- + -- Quickfix list + bufkeymap('n', '[q', vim.cmd.cprev, 'Previous quickfix item') + bufkeymap('n', ']q', vim.cmd.cnext, 'Next quickfix item') + + -- Diagnostic keymaps + bufkeymap('n', '[d', 'vim.diagnostic.goto_prev()', 'Go to previous [d]iagnostic message') + bufkeymap('n', ']d', 'vim.diagnostic.goto_next()', 'Go to next [d]iagnostic message') + bufkeymap('n', 'gle', vim.diagnostic.open_float, 'Show diagnostic [e]rror messages') + -- bufkeymap('n', 'gle', 'Telescope diagnostics', 'Show diagnostic [e]rror messages') + bufkeymap('n', 'glq', vim.diagnostic.setloclist, 'Open diagnostic [q]uickfix list') + -- + -- stylua: ignore start + -- << local trouble = require("trouble").toggle + -- << bufkeymap('n', "tt", function() trouble() end, "Toggle Trouble") + -- << bufkeymap('n', "tq", function() trouble("quickfix") end, "Quickfix List") + -- << bufkeymap('n', "dr", function() trouble("lsp_references") end, "References") + -- << bufkeymap('n', "dd", function() trouble("document_diagnostics") end, "Document Diagnostics") + -- << bufkeymap('n', "dw", function() trouble("workspace_diagnostics") end, "Workspace Diagnostics") + -- stylua: ignore end + -- + if client.server_capabilities.hoverProvider then + bufkeymap('n', 'glk', vim.lsp.buf.hover, 'Hover Documentation') + end + if client.server_capabilities.signatureHelpProvider then + bufkeymap({ 'i', 'n' }, 'gls', vim.lsp.buf.signature_help, 'Show signature') + end + if client.server_capabilities.declarationProvider then + bufkeymap('n', 'glD', vim.lsp.buf.declaration, 'Goto [D]eclaration') + end + if client.server_capabilities.definitionProvider then + bufkeymap('n', 'gld', vim.lsp.buf.definition, 'Go to [d]efinition') + -- bufkeymap('n', 'gld', 'Telescope lsp_definitions', '[G]oto [D]efinition') + end + if client.server_capabilities.typeDefinitionProvider then + bufkeymap('n', 'glt', vim.lsp.buf.type_definition, 'Goto [t]ype definition') + -- bufkeymap('n', 'glt', 'Telescope lsp_type_definitions', 'Goto [t]ype definition') + end + if client.server_capabilities.implementationProvider then + bufkeymap('n', 'gli', vim.lsp.buf.implementation, 'Goto [i]mplementation') + -- bufkeymap('n', 'gli', 'Telescope lsp_implementations', 'Goto [i]mplementation') + end + + -- bufkeymap('n', 'glr', '(CodeAction, implementation, rename, references)', 'CodeAction, implementation, rename, references') + if client.server_capabilities.referencesProvider then + -- bufkeymap('n', 'gr', vim.lsp.buf.references, 'List references') + bufkeymap('n', 'glr', 'Telescope lsp_references', 'Goto [r]eferences') + -- bufkeymap('n', 'glr', 'Telescope lsp_references', '[G]oto [R]eferences') + end + if client.server_capabilities.renameProvider then + -- bufkeymap('n', '', vim.lsp.buf.rename, 'Rename symbol') + bufkeymap('n', 'glR', vim.lsp.buf.rename, '[R]ename') + end + if client.server_capabilities.codeActionProvider then + bufkeymap('n', 'gla', vim.lsp.buf.code_action, 'Code [a]ction') + end + + if client.server_capabilities.documentSymbolProvider then + bufkeymap('n', 'glwd', vim.lsp.buf.document_symbol, '[D]ocument symbols') + -- bufkeymap('n', 'glwd', Telescope lsp_document_symbols, '[D]ocument [S]ymbols') + end + if client:supports_method('workspace/symbol') then + -- if client.server_capabilities.workspaceSymbolProvider then + bufkeymap('n', 'glww', vim.lsp.buf.workspace_symbol, 'List [w]orkspace symbols') + -- bufkeymap('n', 'glww', require('telescope.builtin').lsp_dynamic_workspace_symbols, '[W]orkspace [S]ymbols') + end + if client.server_capabilities.workspace then + bufkeymap('n', 'glwa', vim.lsp.buf.add_workspace_folder, 'Workspace [a]dd folder') + bufkeymap('n', 'glwr', vim.lsp.buf.remove_workspace_folder, 'Workspace [r]emove folder') + bufkeymap('n', 'glwl', function() + print(vim.inspect(vim.lsp.buf.list_workspace_folders())) + end, '[W]orkspace [L]ist folders') + end + -- + if client:supports_method('textDocument/switchSourceHeader') then + bufkeymap('n', 'glws', 'LspClangdSwitchSourceHeader', '[S]witch Source/Header (C/C++)') + end + + if client:supports_method('textDocument/formatting') then + -- if client.server_capabilities.documentFormattingProvider then + bufkeymap({ 'n', 'x' }, 'glf', function() + vim.lsp.buf.format({ bufnr = bufnr, async = true }) + -- require('conform').format({ bufnr = bufnr, async = true }) + end, '[f]ormat buffer') + + -- LSP format the current buffer on save + local fmt_group = vim.api.nvim_create_augroup('autoformat_cmds', { clear = true }) + vim.api.nvim_create_autocmd('BufWritePre', { + buffer = bufnr, + group = fmt_group, + desc = 'Fromat current buffer', + callback = function(args) + -- if (client.name == 'lua_ls') and (vim.fn.executable('stylua') == 1) then + -- -- if (client.name == 'stylua') and (vim.fn.executable('stylua') == 1) then + -- -- vim.fn.system({ 'stylua', vim.api.nvim_buf_get_name(bufnr) }) + -- vim.fn.system({ 'stylua', vim.api.nvim_buf_get_name(args.buf) }) + -- vim.cmd('checktime') + -- print('stylua formatting') + -- else + vim.lsp.buf.format({ + bufnr = bufnr, + async = false, + timeout_ms = 10000, + id = client.id, + filter = function(c) + return c.id == client.id + end, + }) + print('LSP: clangd formatting') + -- end + end, + }) + end + -- + if client.server_capabilities.inlayHintProvider and vim.lsp.inlay_hint then + bufkeymap('n', 'glh', function() + vim.lsp.inlay_hint.enable(not vim.lsp.inlay_hint.is_enabled({ bufnr = bufnr }), { bufnr = bufnr }) + end, '[h]ints toggle') + ------------------------------------------------------------------------------ + end +end + +return K diff --git a/lua/nvimpio/lspConfig/tools.lua b/lua/nvimpio/lspConfig/tools.lua new file mode 100644 index 00000000..ed133426 --- /dev/null +++ b/lua/nvimpio/lspConfig/tools.lua @@ -0,0 +1,20 @@ +local M = {} + +-- -- INFO: + +--- stylua: ignore +function M.clangdRestart() + local name = 'clangd' + -- vim.schedule_wrap(function() + vim.notify('LSP: Clangd restart.', vim.log.levels.WARN) + + local clangConfig = _G.get_clangd_config() + -- print(vim.inspect(clangConfig)) + vim.lsp.config(name, clangConfig) + vim.lsp.enable(name, false) + vim.lsp.enable(name, true) + vim.cmd('checktime') + -- end) +end + +return M diff --git a/lua/nvimpio/pio/metadata.lua b/lua/nvimpio/pio/metadata.lua new file mode 100644 index 00000000..a2526e5a --- /dev/null +++ b/lua/nvimpio/pio/metadata.lua @@ -0,0 +1,134 @@ +local M = {} + +------------------------------------------------------------------------------------------------------- +local last_saved_hash = '' + +--INFO: +-- 1. Internal State & Defaults +local _pio_metadata = { + isBusy = false, + envs = {}, + active_env = '', + default_envs = {}, + core_dir = '', + packages_dir = '', + platforms_dir = '', + query_driver = '', + cc_compiler = '', + includes_build = {}, + includes_compatlib = {}, + includes_toolchain = {}, + cc_path = '', + cc_flags = {}, + cxx_path = '', + cxx_flags = {}, + gdb_path = '', + defines = {}, + triplet = '', + toolchain_root = '', + sysroot = '', + fallbackFlags = {}, + dbTrigger = false, + last_projectChecksum = '', -- Used to track changes +} +-- 2. The Reactive Proxy Wrapper +-- Any write to _G.metadata.key = val triggers this logic +_G.metadata = setmetatable({}, { + __index = _pio_metadata, + __newindex = function(_, key, value) + if _pio_metadata[key] == value then + -- print('Value is identical, returning...') -- DEBUG LINE + return + end -- Performance check + -- print('Newindex attempt for: ' .. tostring(key)) -- DEBUG LINE + _pio_metadata[key] = value + + -- Trigger background actions + vim.schedule(function() + -- M.save_project_config(true) + if key == 'toolchain_root' then + local binPath = value .. '/bin' + local sep = (vim.fn.has('win32') == 1 and ';' or ':') + vim.env.PATH = binPath .. sep .. vim.env.PATH + vim.notify('PIO env: ' .. binPath .. ' added to path', vim.log.levels.INFO, { title = 'PlatformIO', render = 'compact' }) + -- vim.notify('Env: ' .. value, vim.log.levels.INFO, { title = 'PlatformIO', render = 'compact' }) + -- pcall(function() + -- if _pio_metadata.dbTrigger then + -- vim.notify('Env: dbTrigger', vim.log.levels.INFO, { title = 'PlatformIO', render = 'compact' }) + -- local dbFix = pio.compile_commandsFix + -- local ok, _ = pcall(dbFix) + -- if not ok then + -- print('Env: dbTrigger, fail to call dbFix') + -- end + -- -- dbFix() + -- _pio_metadata.dbTrigger = false + -- else + -- local LspRestart = require('nvimpio.lspConfigConfig.tools').lsp_restart + -- LspRestart('clangd') + -- vim.notify('Env: LspRestart', vim.log.levels.INFO, { title = 'PlatformIO', render = 'compact' }) + -- end + -- end) + elseif key == 'last_projectChecksum' then + elseif key == 'active_env' then + end + end) + end, +}) + +local config_path = vim.fs.joinpath(vim.uv.cwd(), '.project_config.json') +-- -- Add this temporary line in a file where you are coding: +-- ---@type platformio.utils.misc +-- local misc = vim.misc +--INFO: +-- 2. Save Logic (Uses sha256 for stability) +function M.save_project_config(from) + -- 1. Generate the formatted string directly, jsonFormat already returns a string! + local ok, pretty_json = pcall(vim.misc.jsonFormat, _pio_metadata) + + if not ok or not pretty_json then + print('Error formatting metadata') + return + end + + local current_hash = vim.fn.sha256(pretty_json) + + -- 2. Only write if the content actually changed + if current_hash ~= last_saved_hash then + local status, err = vim.misc.writeFile(config_path, pretty_json, {}) + + if status then + last_saved_hash = current_hash + vim.notify(from .. 'config save success', vim.log.levels.INFO, { title = 'PlatformIO' }) + else + vim.notify(from .. 'config save failed==> ' .. (err or 'unknown error'), vim.log.levels.ERROR) + end + end +end + +--INFO: +-- 3. Load Logic (Populates proxy safely) +function M.load_project_config() + if vim.fn.filereadable(config_path) == 1 then + local _, json_data = vim.misc.readFile(config_path) + if json_data then + local ok, table_data = pcall(vim.json.decode, json_data) + if ok and type(table_data) == 'table' then + -- We update _pio_metadata directly to avoid triggering + -- 50+ notifications/restarts during the initial load loop + for k, v in pairs(table_data) do + _G.metadata[k] = v + end + last_saved_hash = vim.fn.sha256(json_data) + return + end + end + end + -- If no file, initialize hash with defaults + last_saved_hash = vim.fn.sha256(vim.misc.jsonFormat(_pio_metadata)) +end + +--INFO: +-- 4. Initialization +M.load_project_config() + +return M diff --git a/lua/nvimpio/pio/upkeep.lua b/lua/nvimpio/pio/upkeep.lua new file mode 100644 index 00000000..12713916 --- /dev/null +++ b/lua/nvimpio/pio/upkeep.lua @@ -0,0 +1,680 @@ +---@class platformio.utils.pio +local M = {} + +-- to fix require loop, this value is set in plugin/platformio +local misc = vim.misc + +-- local sep = package.config:sub(1, 1) -- Dynamic OS separator (\ or /) +M.selected_framework = '' +M.is_processing = false +M.queue = {} + +local term = require('nvimpio.utils.term') +local clangdRestart = require('nvimpio.lspConfig.tools').clangdRestart + +-- INFO: +-- ============================================================================= +-- UNIVERSAL TOOLCHAIN DETECTION +-- ============================================================================= +-- stylua: ignore +function M.get_sysroot_triplet(cc_compiler) + local bin_path = vim.fn.fnamemodify(cc_compiler, ':h') + + -- Early exit if path is nil or not a directory + if not bin_path or vim.fn.isdirectory(bin_path) == 0 then return nil end + + -- Normalize backslashes to forward slashes for cross-platform consistency + bin_path = bin_path:gsub('\\', '/') + local files = vim.fn.readdir(bin_path) + local triplet = nil + + -- Loop through files to find the compiler and extract the triplet + for _, name in ipairs(files) do + -- Pattern: ^(.*) matches triplet, %- matches dash, g[c%+][c%+] matches gcc/g++ + local match = name:match('^(.*)%-g[c%+][c%+]') + if match then triplet = vim.misc.normalizePath(match) break + end + end + + -- Return nil if no compiler was found in the bin directory + if not triplet then return nil end + + -- toolchain_root is the parent of the 'bin' folder + local toolchain_root = vim.misc.normalizePath(vim.fn.fnamemodify(bin_path, ':h')) + -- sysroot folder is expected to have the same name as the triplet + local sysroot = vim.misc.normalizePath(toolchain_root .. '/' .. triplet) + local query_driver = vim.misc.normalizePath(bin_path .. '/' .. triplet .. '-*') + + -- vim.notify('triplet= ' .. triplet, vim.log.levels.INFO) + -- Only return data if the sysroot folder actually exists on disk + if vim.fn.isdirectory(sysroot) == 1 then + _G.metadata.triplet = triplet + _G.metadata.sysroot = sysroot + _G.metadata.toolchain_root = toolchain_root + _G.metadata.query_driver = query_driver + return { + triplet = triplet, + sysroot = sysroot, + toolchain_root = toolchain_root, + query_driver = query_driver, + } + end + return nil +end + +--INFO: +-- Fast environment detection from platformio.ini file(no external calls) +-- stylua: ignore +--============================================================================= +function M.get_active__env() + local path + + for _, dir in ipairs({ vim.api.nvim_buf_get_name(0):match('(.*[/\\])'), (vim.uv.cwd() .. '/') }) do + local tmp = dir .. 'platformio.ini' + local filestat = vim.uv.fs_stat(tmp) + if filestat and filestat.type == 'file' then + path = vim.fs.normalize(tmp) + break + end + end + if not path or path == '' then return vim.notify('PIO: platformio.ini not found or no [env] defined.', vim.log.levels.ERROR) end + + -- Read file content (returns string or nil) + local ok, content = vim.misc.readFile(path) + if not ok or not content then return vim.notify('PIO: platformio.ini not found in ' .. path, vim.log.levels.WARN) end + + local default_envs_raw = '' + local first_env = nil + local valid_envs = {} + local in_platformio_block = false + + -- Iterate lines from the content string + for line in vim.gsplit(content, '\n') do + -- Section Detection: [section_name] + local section = line:match('^%s*%[(.+)%]%s*$') + if section then + in_platformio_block = (section == 'platformio') + local env_name = section:match('^env:(.+)') + if env_name then + if not first_env then first_env = env_name end + valid_envs[env_name] = true + end + end + + -- Collect the default_envs string from [platformio] block + if in_platformio_block then + local def = line:match('^%s*default_envs%s*=%s*(.+)') + if def then default_envs_raw = def end + end + end + + -- Validation: Find the first default_env that actually exists as a block + if default_envs_raw ~= '' then + for env_name in default_envs_raw:gmatch('([^%s,]+)') do + if valid_envs[env_name] then return env_name end + end + end + + -- Fallback to the very first [env:...] block found in the file + return first_env +end + + +--INFO: +-- get pio project metadata info +-- stylua: ignore +--============================================================================= +function M.fetch_metadata(callback, env, from, attempts) + local msg = (type(from)=='string' and from ~= '') and from or 'PIO: ' + local meta = _G.metadata + local active_env = env or meta.active_env + if not active_env or active_env == '' then + return + end + + -- Set up file paths + local build_dir = vim.misc.joinPath(vim.uv.cwd(), '.pio', 'build') + local build_env_dir = vim.misc.joinPath(build_dir, active_env) + local checksum_file = vim.misc.joinPath(build_dir, 'project.checksum') + local idedata_file = vim.misc.joinPath(build_env_dir, 'idedata.json') + + --INFO: + --INTERNAL PROCESSOR: Applies parsed data to _G.metadata + --------------------------------------------------------- + local function apply_metadata(data, checksum) + if not data then return false end + + local norm = function(p) return vim.misc.normalizePath(p) or '' end + + -- Helper for flags/defines to keep order and formatting + local quote_map = function(list, prefix) + local res = {} + for _, v in ipairs(list or {}) do + local val = prefix and (prefix .. norm(v)) or v + table.insert(res, string.format('%s', val)) + end + return res + end + + -- 1. Base Paths & Compilers + meta.cc_path = norm(data.cc_path) + meta.cc_compiler = meta.cc_path + meta.cxx_path = norm(data.cxx_path) + meta.gdb_path = norm(data.gdb_path) + + -- 2. Flags & Defines + meta.cc_flags = quote_map(data.cc_flags) + meta.cxx_flags = quote_map(data.cxx_flags) + meta.defines = quote_map(data.defines) + + -- 3. Includes (Build, Toolchain, Compatlib) + local inc = data.includes or {} + meta.includes_build = quote_map(inc.build, '-I') + meta.includes_toolchain = quote_map(inc.toolchain, '-isystem') + meta.includes_compatlib = quote_map(inc.compatlib, '-isystem') + meta.last_projectChecksum = checksum + pcall(M.get_sysroot_triplet, meta.cc_compiler) + + return true + end + + --INFO: + --Generate idedata.json + --------------------------------------------------------- + local function buildIdedata() + vim.notify(msg .. 'Initializing project metadata...', vim.log.levels.INFO) + vim.system({ 'pio', 'run', '-t', 'idedata', '-e', active_env, '-s' }, { text = true }, function(obj) + vim.schedule(function() + if obj.code == 0 then + vim.notify(msg .. 'Initializing project metadata success.', vim.log.levels.INFO) + M.fetch_metadata(callback, active_env, from, attempts - 1) -- Recursive call after files created + else + vim.notify(msg .. 'Initialization failed. Build project manually.', vim.log.levels.ERROR) + end + end) + end) + return true + end + + --------------------------------------------------------- + -- STEP 1: Fast Checksum Check (project.checksum and idedata.json) + --------------------------------------------------------- + local ok, current_checksum = vim.misc.readFile(checksum_file) + if ok and (type(current_checksum) == 'string' and current_checksum ~= '') then + if current_checksum == meta.last_projectChecksum then + vim.notify(msg .. 'Metadata synced with cache', vim.log.levels.INFO) + -- if callback then callback() end + if callback then vim.schedule(callback) end + return true + end -- Already updated + + -- STEP 2: Cache Path (idedata.json exists and checksum changed) + local idok, content = vim.misc.readFile(idedata_file) + if idok and (type(content) == 'string' and content ~= '') then + local cok, decoded = pcall(vim.json.decode, content) + + -- local formated = vim.misc.jsonFormat(decoded) + -- local file = vim.misc.joinPath(vim.uv.cwd(), 'idedata.json') + -- vim.misc.writeFile(file, formated, {}) + + if cok and apply_metadata(decoded, current_checksum) then + local metadata = require('nvimpio.pio.metadata') + metadata.save_project_config(msg) + vim.notify(msg .. 'Metadata synced from cache', vim.log.levels.INFO) + -- if callback then vim.schedule(callback) end + + if type(callback) == "function" then + vim.schedule(callback) + else + -- If it's not a function, just do nothing or print a debug message + print(msg .." Debug; callback was " .. type(callback)) + end + + return true + end + -- else + end + -- else + end + --------------------------------------------------------- + -- STEP 3: Auto-Initialize (If files project.checksum and idedata.json are missing) + --------------------------------------------------------- + buildIdedata() + + --------------------------------------------------------- + -- STEP 4: Standard CLI Fallback (The Slow Path) + --------------------------------------------------------- + -- vim.notify(msg .. 'Metadata sync ...', vim.log.levels.INFO) + -- vim.system({ 'pio', 'project', 'metadata', '-e', active_env, '--json-output' }, { text = true }, function(obj) + -- vim.schedule(function() + -- if obj.code ~= 0 then + -- if attempts > 0 then + -- vim.defer_fn(function() M.fetch_metadata(attempts - 1, env) end, 500) + -- return + -- end + -- return vim.notify(msg .. 'Metadata Error: ' .. (obj.stderr or 'Unknown'), vim.log.levels.WARN) + -- end + -- + -- local ook, raw_data = pcall(vim.json.decode, obj.stdout or '') + -- local _, data = next(raw_data or {}) + -- + -- if ook and apply_metadata(data, current_checksum) then + -- vim.notify(msg .. 'Metadata synced from CLI', vim.log.levels.INFO) + -- if callback then vim.schedule(callback) end + -- else + -- vim.notify(msg .. 'Failed to parse metadata output', vim.log.levels.WARN) + -- end + -- end) + -- end) +end + +-- INFO: +-- ============================================================================= +-- Get project configuration +-- ============================================================================= +-- stylua: ignore +function M.fetch_config(on_done, from) + local msg = (type(from) == 'string' and from ~= '') and from or 'PIO: ' + local meta = _G.metadata + local home = (os.getenv('HOME') or os.getenv('USERPROFILE') or ''):gsub('[\\/]+$', '') + + local active_env + vim.system({ 'pio', 'project', 'config', '--json-output' }, { text = true }, function(obj) + vim.schedule(function() + -- 1. Check Execution + if obj.code ~= 0 then + local errmsg = obj.code == 127 and "'pio' not found" or (obj.stderr or 'Unknown Error') + return vim.notify(msg .. 'Config Error: ' .. errmsg, vim.log.levels.ERROR) + end + + -- 2. Decode JSON safely + local ok, decoded = pcall(vim.json.decode, obj.stdout or '') + if not ok or type(decoded) ~= 'table' then + return vim.notify(msg .. 'Failed to decode config JSON', vim.log.levels.ERROR) + end + + -- local formated = vim.misc.jsonFormat(decoded) + -- local file = vim.misc.joinPath(vim.uv.cwd(), 'config.json') + -- vim.misc.writeFile(file, formated, {}) + + -- Reset core structure + meta.envs = {} + meta.default_envs = {} + local valid_envs = {} + + -- 3. Parse Sections + for _, section in ipairs(decoded) do + local name, data = section[1], section[2] + if name == 'platformio' then + for _, kv in ipairs(data) do + meta[kv[1]] = kv[2] + end + elseif name:match('^env:') then + local env_name = name:match('^env:(.+)') + if not active_env then active_env = env_name end + valid_envs[env_name] = true + meta.envs[env_name] = {} + for _, kv in ipairs(data) do + meta.envs[env_name][kv[1]] = kv[2] + end + end + end + + -- 4. Assign active_env + -- Validation: Find the first default_env that actually exists as a block + for _, env_name in ipairs(meta.default_envs) do + if valid_envs[env_name] then + active_env = env_name + break + end + end + meta.active_env = active_env + + -- 5. Resolve Paths (INI -> Env -> Default) + local path_map = { + { key = 'core_dir', env = 'PLATFORMIO_CORE_DIR', sub = '/.platformio' }, + { key = 'packages_dir', env = 'PLATFORMIO_PACKAGES_DIR', sub = '/.platformio/packages' }, + { key = 'platforms_dir', env = 'PLATFORMIO_PLATFORMS_DIR', sub = '/.platformio/platforms' }, + } + + for _, item in ipairs(path_map) do + local val = meta[item.key] + -- Fallback chain + if not val or val == '' then + val = os.getenv(item.env) or (home .. item.sub) + end + -- Expand variables and Normalize + if type(val) == 'string' then + val = val:gsub('%%${platformio.core_dir}', meta.core_dir or '') + meta[item.key] = vim.misc.normalizePath(val) + end + end + + -- if active_env then + -- vim.notify(msg .. 'active_env= ' .. active_env, vim.log.levels.INFO) + -- end + -- 6. Trigger next step + if meta.active_env ~= '' then + vim.notify(msg .. 'Config sync successful', vim.log.levels.INFO) + else + vim.notify(msg .. 'No [env:] found. Please add a board.', vim.log.levels.ERROR) + end + + if on_done then + vim.schedule(function() on_done(active_env) end) + end + end) + end) +end + +-- INFO: +-- Fix compile_commands.json file with absoulute paths +-- stylua: ignore +-- ============================================================================= +function M.compile_commandsFix() --M.dbPathsFix() + local filename = vim.fs.joinpath(vim.uv.cwd(), 'compile_commands.json') + local content = vim.fn.readfile(filename) + if #content == 0 then return end + + local start_time = vim.loop.hrtime() + local ok, data = pcall(vim.json.decode, table.concat(content, '\n')) + if not ok or type(data) ~= 'table' then return end + + -- 1. Build Path Map (Scan toolchain) + local path_map = {} + local pio_binaries = _G.metadata.query_driver or '/bin/*' + -- local pio_binaries = (_G.metadata.toolchain_root or "") .. '/bin/*' + for _, full_path in ipairs(vim.fn.glob(pio_binaries, false, true)) do + local name = full_path:match('([^/\\\\]+)$'):gsub('%.exe$', '') + path_map[name] = full_path + end + + -- 2. Update Entries + local modified = false + local prntFlags = true + for _, entry in ipairs(data) do + -- Standard normalization + if entry.directory then entry.directory = misc.normalizePath(entry.directory) end + if entry.file then entry.file = misc.normalizePath(entry.file) end + if entry.arguments then entry.arguments = misc.normalizeFlags(entry.arguments) end + if entry.output then entry.output = misc.normalizePath(entry.output) end + + if entry.command then + -- Extract compiler and everything after it + local compiler, args = entry.command:match("^%s*(%S+)(.*)") + if compiler then + local is_absolute = compiler:sub(1, 1) == '/' or compiler:match('^%a:') + + if not is_absolute then + local short_name = compiler:match('([^/\\\\]+)$'):gsub('%.exe$', '') + + if path_map[short_name] then + -- Use normalizePath on the new path + local full_compiler_path = misc.normalizePath(path_map[short_name]) + + -- Quote the path if it contains spaces + if full_compiler_path:find(" ") then + full_compiler_path = '"' .. full_compiler_path .. '"' + end + if prntFlags then + -- print(string.format('ful_compiler_path = %s flags=%s', full_compiler_path, args)) + prntFlags = false + end + entry.command = full_compiler_path .. args + modified = true + end + end + end + end + end + -- -- 3. Save with Formatting + if modified then + local jok, formatted = pcall(vim.misc.jsonFormat, data) + -- local jok, formatted = pcall(M.pretty_print, data) + if not jok then + print('Formatting failed: ' .. formatted) + return + end + + local wk, err = vim.misc.writeFile(filename, formatted, { overwrite = true, mkdir = true }) + if not wk then print(err) end + + local end_time = vim.loop.hrtime() + local duration = (end_time - start_time) / 1e6 + vim.notify(string.format('compiledb: paths fixed in %.2fms', duration), vim.log.levels.INFO) + clangdRestart() + end + _G.metadata.isBusy = false +end + + +-- INFO: +--configuration for running sequential commands on ToggleTerminal +-- stylua: ignore +-- ============================================================================= +-- ============================================================================= +local callBack = nil +local pio_buffer = '' -- Persistent stream buffer + +-- INFO: ToggleTerminal commands stdout filter +-- stylua: ignore +-- ============================================================================= +function M.stdoutcallback(_, _, data) + if not data then return end + + -- 1. Combine the last partial line with the new first line + local lines_to_process = pio_buffer .. data[1] + + -- 2. If there are newlines, we have complete lines to check + if #data > 1 then + -- Join all complete parts (everything except the very last partial line) + for i = 2, #data - 1 do lines_to_process = lines_to_process .. data[i] end + + -- 3. Search for the status in the complete chunk + local status = lines_to_process:match('_CMMNDS_:(%a+)') + if status and callBack then vim.schedule(function() callBack(status) end) end + -- save the trailing part for the next chunk + pio_buffer = data[#data] + else + -- Only one element in data means no newline yet; just update the partial buffer + pio_buffer = lines_to_process + end + + -- 4. Safety Trim (Prevents memory leaks if no newline ever comes) + if #pio_buffer > 5000 then pio_buffer = pio_buffer:sub(-2500) end +end + +local commandPassed = 0 + + +-- INFO: commands sequencer +-- stylua: ignore +-- ============================================================================= +M.run_sequence = function(tasks) + M.queue = {} + local commands = tasks.cmnds + + local done = ' && echo _CMMNDS_":"DONE' + local pass = ' && echo _CMMNDS_":"PASS' + local fail = ' || echo _CMMNDS_":"FAIL' + -- + for i, cmd in ipairs(commands) do + local full_cmd = '' + if i == #commands then full_cmd = cmd .. done .. fail + else full_cmd = cmd .. pass .. fail end + table.insert(M.queue, full_cmd) + end + + + callBack = tasks.cb -- 1. Save the callback in a local variable + + commandPassed = 1 + _G.metadata.isBusy = true + + term.stdout_callback = M.stdoutcallback + vim.schedule(function() if callBack then callBack('INIT') end end) +end + +local trm +local win_id +------------------------------------------------------ +-- Handle after pioinit execution +-- ============================================================================= +-- stylua: ignore +function M.handlePioinitDb(result) + if result == 'INIT' then + local boilerplate = require('nvimpio.boilerplate') + local boilerplate_gen = boilerplate.boilerplate_gen + + boilerplate.core_dir = _G.metadata.core_dir + boilerplate_gen([[platformio.ini]], vim.g.platformioRootDir) + + boilerplate_gen([[.clang-format]], vim.g.platformioRootDir) + + boilerplate_gen([[.clangd]], vim.g.platformioRootDir) + -- boilerplate_gen([[.clangd]], _G.metadata.core_dir) + -- boilerplate_gen([[.clangd]], vim.fs.joinpath(vim.env.XDG_CONFIG_HOME, 'clangd'), 'config.yaml') + + win_id = vim.misc.showMessage('************ Project Initializing ************') + if #M.queue > 0 then trm = term.ToggleTerminal(table.remove(M.queue, 1), 'float')end + elseif result == 'PASS' then + -- if commandPassed == 1 then + -- elseif commandPassed == 2 then -- if you sned more than 2 commands you need this + -- end + vim.notify('PIO init+db: pass ' .. commandPassed, vim.log.levels.INFO) + commandPassed = commandPassed + 1 + if #M.queue > 0 then term.ToggleTerminal(table.remove(M.queue, 1), 'float') end + elseif result == 'DONE' then -- result of the last command + vim.schedule(function() + vim.notify('PIO init+db: pass ' .. commandPassed, vim.log.levels.INFO) + vim.notify('PIO init+db: Done', vim.log.levels.INFO) + vim.misc.gitignore_lsp_configs('compile_commands.json') + local pio_refresh = require('nvimpio.pio.watcher').pio_refresh + pio_refresh(function() + local boilerplate_gen = require('nvimpio.boilerplate').boilerplate_gen + boilerplate_gen([[.clangd]], _G.metadata.core_dir) + vim.misc.closeMessage(win_id) + clangdRestart() + -- term.ToggleTerminal('echo "************ project Initialization success ************"', 'float') + end, 'PIO init+db: ') + end) + vim.misc.deleteFile(vim.fs.joinpath(vim.g.platformioRootDir, '.ccls')) + M.queue = {} + term.stdout_callback = nil + trm:close() + _G.metadata.isBusy = false + elseif result == 'FAIL' then + _G.metadata.isBusy = false + vim.misc.closeMessage(win_id) + M.queue = {} + term.stdout_callback = nil + trm:close() + end +end + + +---------------------------------------------------- +-- Handle after pioinit execution +-- stylua: ignore +function M.handlePioinit(result) + if result == 'INIT' then + local boilerplate = require('nvimpio.boilerplate') + local boilerplate_gen = boilerplate.boilerplate_gen + + boilerplate.core_dir = _G.metadata.core_dir + boilerplate_gen([[platformio.ini]], vim.g.platformioRootDir) + + boilerplate_gen([[.clang-format]], vim.g.platformioRootDir) + + boilerplate_gen([[.clangd]], vim.g.platformioRootDir) + -- boilerplate_gen([[.clangd]], _G.metadata.core_dir) + -- boilerplate_gen([[.clangd]], vim.fs.joinpath(vim.env.XDG_CONFIG_HOME, 'clangd'), 'config.yaml') + + win_id = vim.misc.showMessage('************ Project Initializing ************') + if #M.queue > 0 then trm = term.ToggleTerminal(table.remove(M.queue, 1), 'float')end + elseif result == 'DONE' then -- result of the last command + vim.schedule(function() + vim.notify('PIO init: pass ' .. commandPassed, vim.log.levels.INFO) + vim.notify('PIO init: Done', vim.log.levels.INFO) + vim.misc.gitignore_lsp_configs('compile_commands.json') + + -- \27[s : Save current cursor position (the prompt) + -- \r : Go to start of line + -- \27[A : Move cursor UP one line (to space above prompt) + -- \27[K : Clear that line + -- \27[33m : Color Yellow (optional) + -- %s : Your message + -- \27[0m : Reset color + -- \27[u : Restore cursor back to the prompt + -- IMPORTANT: No \n at the end, so it doesn't execute + -- local msg = '************ Please wait for project Initialization to finish ************' + -- local clean_msg = string.format('\27[G\27[2K\27[33m%s\27[0m', msg) + -- vim.api.nvim_chan_send(trm.job_id, clean_msg) + + local pio_refresh = require('nvimpio.pio.watcher').pio_refresh + pio_refresh(function() + local boilerplate_gen = require('nvimpio.boilerplate').boilerplate_gen + boilerplate_gen([[.clangd]], _G.metadata.core_dir) + vim.misc.closeMessage(win_id) + clangdRestart() + -- term.ToggleTerminal('echo "************ project Initialization success ************"', 'float') + end, 'PIO init: ') + end) + vim.misc.deleteFile(vim.fs.joinpath(vim.g.platformioRootDir, '.ccls')) + M.queue = {} + term.stdout_callback = nil + trm:close() + _G.metadata.isBusy = false + elseif result == 'FAIL' then + _G.metadata.isBusy = false + vim.misc.closeMessage(win_id) + M.queue = {} + term.stdout_callback = nil + trm:close() + end +end + +------------------------------------------------------ +-- Handle after piolib execution +-- ============================================================================= +-- stylua: ignore +function M.handlePiolib(result) + if result == 'INIT' then + if #M.queue > 0 then term.ToggleTerminal(table.remove(M.queue, 1), 'float')end + elseif result == 'DONE' then -- result of the only and the last command + vim.notify('PIO lib: pass ' .. commandPassed, vim.log.levels.INFO) + vim.notify('PIO lib: Done', vim.log.levels.INFO) + commandPassed = commandPassed + 1 + M.queue = {} + term.stdout_callback = nil + _G.metadata.isBusy = false + elseif result == 'FAIL' then + M.queue = {} + term.stdout_callback = nil + _G.metadata.isBusy = false + end +end + +------------------------------------------------------ +-- ============================================================================= +-- stylua: ignore +function M.handlePiodb(target, result) + if result == 'INIT' then + if #M.queue > 0 then term.ToggleTerminal(table.remove(M.queue, 1), 'float')end + elseif result == 'DONE' then -- result of the only and the last command + vim.notify('PIO db: pass ' .. commandPassed, vim.log.levels.INFO) + vim.notify('PIO db: Done', vim.log.levels.INFO) + commandPassed = commandPassed + 1 + target.isBusy = false + M.queue = {} + term.stdout_callback = nil + _G.metadata.isBusy = false + elseif result == 'FAIL' then + target.isBusy = false + M.queue = {} + term.stdout_callback = nil + _G.metadata.isBusy = false + end +end + +return M diff --git a/lua/nvimpio/pio/watcher.lua b/lua/nvimpio/pio/watcher.lua new file mode 100644 index 00000000..a90dbd50 --- /dev/null +++ b/lua/nvimpio/pio/watcher.lua @@ -0,0 +1,303 @@ +M = {} + +local clangdRestart = require('nvimpio.lspConfig.tools').clangdRestart +local boilerplate = require('nvimpio.boilerplate') +local boilerplate_gen = boilerplate.boilerplate_gen + +-- ============================================================================= +-- INFO: +-- Unified hashing for change detection +local function get_hash(path) + if vim.fn.filereadable(path) == 0 then + return nil + end + -- local ok, data = pcall(vim.fn.readfile, path) -- readfile is safer than io.open + -- return ok and vim.fn.sha256(table.concat(data, '\n')) or nil + local ok, data = vim.misc.readFile(path) -- readfile is safer than io.open + return (ok and type(data) == 'string' and data ~= '') and vim.fn.sha256(data) or '' +end + + +--INFO: +--stylua: ignore +--============================================================================= +function M.pio_refresh(callback, from) + local msg = (type(from)=='string' and from ~= '') and from or 'PIO: ' + vim.notify(msg ..'Config sync ...', vim.log.levels.INFO) + + local function on_done(active_env) + if active_env then vim.notify(msg .. 'active_env= ' .. active_env, vim.log.levels.INFO) end + if active_env then vim.pio.fetch_metadata(callback, active_env, from, 1) end + end + vim.pio.fetch_config(on_done, from) +end + +--INFO: +--============================================================================= +-- watchers setup +--============================================================================= +-- Ensure this is at the TOP of your file, outside any functions +local uv = vim.uv or vim.loop +M.watcher_handles = {} +local debounce_timer = uv.new_timer() +local last_mtime = 0 + +-- --INFO: +-- --stylua: ignore +-- --1.run_compiledb after platformio.ini changed +-- --============================================================================= +-- function M.run_compiledb(target) +-- if target.isBusy then return end +-- if _G.metadata.isBusy == true then return end +-- +-- local env = vim.pio.get_active__env() +-- if not env then return end +-- target.isBusy = true +-- vim.notify('PIO platformio.ini change: compiledb update ...', vim.log.levels.INFO, { title = 'PlatformIO' }) +-- vim.system({ 'pio', 'run', '-t', 'compiledb', '-s', '-e', env }, { text = true }, function(obj) +-- vim.schedule(function() +-- target.isBusy = false +-- +-- if obj.code == 0 then +-- vim.schedule(function () +-- M.pio_refresh(function() +-- vim.notify('PIO platformio.ini change: compiledb update Success', vim.log.levels.INFO, { title = 'PlatformIO' }) +-- clangdRestart() +-- end, 'PIO platformio.ini change: ') +-- end) +-- else +-- local err = (obj.stderr and obj.stderr ~= '') and obj.stderr or 'Check PIO logs' +-- vim.notify('PIO Build Failed: ' .. err, vim.log.levels.ERROR, { title = 'PlatformIO' }) +-- end +-- _G.metadata.isBusy = false +-- end) +-- end) +-- end +-- +--INFO: +--stylua: ignore +--1.stop_watchers +--============================================================================= +function M.stop_watchers() + if not M.watcher_handles or (type(M.watcher_handles) ~= 'table') then M.watcher_handles = {} return end + + for _, handle in ipairs(M.watcher_handles) do + if handle and not handle:is_closing() then + handle:stop() + handle:close() -- CRITICAL: This allows Neovim to quit instantly + end + end + M.watcher_handles = {} +end + +--INFO: +--stylua: ignore +--2.watcher cleanup +--============================================================================= +function M.cleanup() + M.stop_watchers() + if debounce_timer and not debounce_timer:is_closing() then + debounce_timer:stop() + debounce_timer:close() + end +end + +-- Force cleanup when leaving Neovim to prevent :qa lag +vim.api.nvim_create_autocmd('VimLeavePre', { + callback = function() + M.cleanup() + end, +}) + +--INFO: +--stylua: ignore +--3. MAIN WATCHER: Efficient Folder Monitoring +--============================================================================= +local function watch_file(target, callback) + local folder_path = target.path:match('(.*[/\\])') + local target_filename = target.path:match('[^/\\]+$') + + local handle = uv.new_fs_event() + if not handle then return end + + handle:start(folder_path, {}, function(err, filename) + if err then return end + + -- Early Exit Filters + if target.isBusy or (filename and filename ~= target_filename) then return end + + -- local f = io.open(target.path, "r") + -- if f then f:close() + -- else return end -- Not readable (protected, locked, or missing) + + if not uv.fs_access(target.path, 'R') then return end + + -- Protected Execution + local ok, result = pcall(function() + local stat = uv.fs_stat(target.path) + if not stat or stat.mtime.sec <= last_mtime then return end + + vim.schedule(function() + if debounce_timer then + debounce_timer:stop() + local retries = 0 + local max_retries = 15 -- 15 seconds max wait + + local function attempt_callback() + -- Check if busy (checks both local M and global _G) + if target.isBusy then --or (_G.metadata and _G.metadata.isBusy) then + if retries < max_retries then + retries = retries + 1 + debounce_timer:start(1000, 0, vim.schedule_wrap(attempt_callback)) + return + end + vim.notify('PIO: Sync timed out (busy)', vim.log.levels.ERROR) + return + end + + -- Final validation & run + local final_stat = uv.fs_stat(target.path) + if final_stat and final_stat.mtime.sec > last_mtime then + last_mtime = final_stat.mtime.sec + callback(target) + end + end + + debounce_timer:start(1000, 0, vim.schedule_wrap(attempt_callback)) + end + end) + end) + + if not ok then + vim.schedule(function() + vim.notify('PIO Watcher Error: ' .. tostring(result), vim.log.levels.ERROR) + end) + end + end) + + table.insert(M.watcher_handles, handle) + return handle +end + +--INFO: +--stylua: ignore +--4. start_watches +--============================================================================= +function M.start_watchers() + -- Clean up any existing watchers first to prevent duplicates + if next(M.watcher_handles) then M.stop_watchers() end + + local project_root = vim.uv.cwd() -- Use dynamic CWD instead of hardcoded path + + local targets = { + { -- watcher for platformio.ini + name = 'ini', + isBusy = false, + last_hash = '', + path = vim.misc.joinPath(project_root, 'platformio.ini'), + cb = function(self) + if self.isBusy then return end + if _G.metadata.isBusy == true then return end + local new_hash = get_hash(self.path) or '' + if new_hash and new_hash ~= self.last_hash then + self.last_hash = new_hash + local env = vim.pio.get_active__env() + if not env then return end + self.isBusy = true + vim.notify('PIO platformio.ini change: compiledb update ...', vim.log.levels.INFO, { title = 'PlatformIO' }) + vim.system({ 'pio', 'run', '-t', 'compiledb', '-s', '-e', env }, { text = true }, function(obj) + vim.schedule(function() + if obj.code == 0 then + vim.schedule(function () + M.pio_refresh(function() + vim.notify('PIO platformio.ini change: compiledb update Success', vim.log.levels.INFO, { title = 'PlatformIO' }) + clangdRestart() + end, 'PIO platformio.ini change: ') + end) + else + local err = (obj.stderr and obj.stderr ~= '') and obj.stderr or 'Check PIO logs' + vim.notify('PIO Build Failed: ' .. err, vim.log.levels.ERROR, { title = 'PlatformIO' }) + end + self.isBusy = false + end) + end) + -- M.run_compiledb(self) -- Smart: Auto-update DB if config changes + end + end, + }, + { -- watcher for ./.pio/build/projct.checksum + name = 'checksum', + isBusy = false, + path = vim.misc.joinPath(project_root, '.pio', 'build', 'project.checksum'), --checksum_path + cb = function(self) + if self.isBusy then return end + local ok, current_checksum = vim.misc.readFile(self.path) + -- Check if we should exit early + if ok and type(current_checksum) == 'string' and current_checksum ~= '' then + if current_checksum == _G.metadata.last_projectChecksum then + return + end + + self.isBusy = true + vim.defer_fn(function () + M.pio_refresh(function() + self.isBusy = false + vim.notify('PIO checksum: Metadata synced', vim.log.levels.INFO) + clangdRestart() + end, 'PIO checksum: ') + end, 500) + end + end + }, + } + + for _, target in ipairs(targets) do + --[[ wrap the callback in a small anonymous function, + so it passes the target (self) back into it.]] + watch_file(target, target.cb) + end +end + +--INFO: 6. Exported setup function +--stylua: ignore +--============================================================================= +function M.init() + local config = require('nvimpio').config + if config.lspClangd.enabled == true then + vim.notify('PIO start: initialize', vim.log.levels.INFO) + + -- activate meta save and upload and env switch + local metadata = require('nvimpio.pio.metadata') + metadata.load_project_config() + + require('nvimpio.lspConfig.clangd') + if config.lspClangd.attach.enabled then + require('nvimpio.lspConfig.attach') + end + + -- Always start the watcher so it can catch a future 'pio init' + M.start_watchers() + + -- boilerplate_gen([[platformio.ini]], vim.g.platformioRootDir) + -- If the file already exists, do an initial sync + if vim.fn.filereadable(vim.uv.cwd() .. '/platformio.ini') == 1 then + ---------------------------------------------------------------------------------------- + --INFO: create clangd required files + ----------------------------------------------------------------------------------------- + -- boilerplate_gen([[.clangd]], vim.g.platformioRootDir) + -- boilerplate_gen([[.clangd]], vim.fs.joinpath(vim.env.XDG_CONFIG_HOME, 'clangd'), 'config.yaml') + -- boilerplate_gen([[.clangd]], _G.metadata.core_dir) + boilerplate.core_dir = _G.metadata.core_dir + boilerplate_gen([[.clang-format]], vim.g.platformioRootDir) + --------------------------------------------------------------------------------- + -- M.run_compiledb() -- Smart: Auto-update DB if config changes + M.pio_refresh(function() + -- vim.schedule(function() + -- lsp_restart('clangd') + -- end) + end, 'PIO start: ') + end + end +end + +return M diff --git a/lua/nvimpio/pioCommands.lua b/lua/nvimpio/pioCommands.lua new file mode 100644 index 00000000..835b715f --- /dev/null +++ b/lua/nvimpio/pioCommands.lua @@ -0,0 +1,112 @@ +local M = {} + +-- local misc = require('nvimpio.utils.misc') +local ToggleTerminal = require('nvimpio.utils.term').ToggleTerminal +local misc = vim.misc + +-- stylua: ignore +--INFO: PioLSP +------------------------------------------------------ +function M.piolsp() + require('nvimpio.lspConfig.tools').clangdRestart() +end + +-- stylua: ignore +--INFO: Piocmd(h/f) +------------------------------------------------------ +function M.piocmd(cmd_table, direction) + if not misc.pio_install_check() then return end + + misc.cd_pioini() + + if cmd_table[1] == '' then ToggleTerminal('', direction) + else + local cmd = 'pio ' + for _, v in pairs(cmd_table) do cmd = cmd .. ' ' .. v end + ToggleTerminal(cmd, direction) + end +end + +-- stylua: ignore +--INFO: Piodebug +------------------------------------------------------ +function M.piodebug(args_table) + if not misc.pio_install_check() then return end + + misc.cd_pioini() + + local command = 'pio debug --interface=gdb -- -x .pioinit' + -- local command = string.format('pio debug --interface=gdb -- -x .pioinit %s', utils.extra) + ToggleTerminal(command, 'float') +end + +-- stylua: ignore +--INFO: Piomon +------------------------------------------------------ +function M.piomon(args_table) + if not misc.pio_install_check() then return end + + misc.cd_pioini() + + local command = nil + if #args_table == 0 then command = 'pio device monitor' + elseif #args_table == 1 then + local baud_rate = args_table[1] + command = string.format('pio device monitor -b %s', baud_rate) + elseif #args_table == 2 then + local baud_rate = args_table[1] + local port = args_table[2] + command = string.format('pio device monitor -b %s -p %s', baud_rate, port) + end + + if command == nil then vim.notify('Usage: Piomon ', vim.log.levels.ERROR) + else ToggleTerminal(command, 'horizontal') end +end + +-- stylua: ignore +--INFO: Piorun +------------------------------------------------------ +function M.piobuild() + misc.cd_pioini() + local command = 'pio run' -- .. utils.extra + ToggleTerminal(command, 'float') +end + +function M.pioupload() + misc.cd_pioini() + local command = 'pio run --target upload' -- .. utils.extra + ToggleTerminal(command, 'float') +end + +function M.piouploadfs() + misc.cd_pioini() + local command = 'pio run --target uploadfs' -- .. utils.extra + ToggleTerminal(command, 'float') +end + +function M.pioclean() + misc.cd_pioini() + local command = 'pio run --target clean' -- .. utils.extra + ToggleTerminal(command, 'float') +end + +function M.piorun(arg_table) + if not misc.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/nvimpio/pioinit.lua b/lua/nvimpio/pioinit.lua new file mode 100644 index 00000000..76896134 --- /dev/null +++ b/lua/nvimpio/pioinit.lua @@ -0,0 +1,159 @@ +local pickers = require('telescope.pickers') +local finders = require('telescope.finders') +local actions = require('telescope.actions') +local action_state = require('telescope.actions.state') +local previewers = require('telescope.previewers') +local telescope_conf = require('telescope.config').values +local themes = require('telescope.themes') +local pio = require('nvimpio.pio.upkeep') + +local wizard_data = {} + +-- Visual Notifications +local function notify(msg, level) + vim.notify('PIO init+db: ' .. msg, level or vim.log.levels.INFO) +end + +-- Reusable Small Menu for Yes/No and Frameworks +local function small_menu(title, results, callback) + pickers + .new( + themes.get_dropdown({ + prompt_title = title, + layout_config = { width = 0.3, height = 0.25 }, + previewer = false, + }), + { + finder = finders.new_table({ results = results }), + sorter = telescope_conf.generic_sorter({}), + attach_mappings = function(prompt_bufnr) + actions.select_default:replace(function() + local selection = action_state.get_selected_entry() + actions.close(prompt_bufnr) + if selection then + callback(selection[1]) + end + end) + return true + end, + } + ) + :find() +end + +-- FINAL STEP: Construction & Sequence Execution +local function finalize_setup() + -- local pio = require('nvimpio.pio.upkeep') + + local sample_flag = wizard_data.sample == 'Yes' and ' --sample-code' or '' + local init_cmd = string.format('pio project init --ide vim --board %s -O "framework=%s"%s', wizard_data.board_id, wizard_data.framework, sample_flag) + + local db_cmd = string.format('pio run -t compiledb -e %s', wizard_data.board_id) + local commands = { init_cmd, db_cmd } + local final_cb = pio.handlePioinitDb + + -- local commands = { init_cmd } + -- local final_cb = pio.handlePioinit + + notify('Starting project setup for ' .. wizard_data.board_id .. '...') + pio.run_sequence({ cmnds = commands, cb = final_cb }) +end + +--- SEQUENTIAL STEPS --- + +-- Step 4: CompileDB +-- local function pick_compiledb() +-- small_menu('Generate Compilation Database (LSP)?', { 'Yes', 'No' }, function(choice) +-- wizard_data.use_compiledb = choice +-- finalize_setup() +-- end) +-- end + +-- Step 3: Sample Code +local function pick_sample() + small_menu('Include Sample Code?', { 'Yes', 'No' }, function(choice) + wizard_data.sample = choice + -- pick_compiledb() + finalize_setup() + end) +end + +-- Step 2: Framework +local function pick_framework(board_details) + small_menu('Select Framework', board_details.frameworks, function(choice) + wizard_data.framework = choice + pick_sample() + end) +end + +-- Step 1: Board (Entry Point) +local function pick_board(json_data) + pickers + .new({}, { + prompt_title = 'Select Board', + -- Define the layout behavior + layout_strategy = 'horizontal', + layout_config = { + width = 0.9, -- Overall width of the Telescope window (90% of screen) + preview_width = 0.70, -- 65% of the window goes to "Board Details", leaving 25% for results + }, + finder = finders.new_table({ + results = json_data, + entry_maker = function(entry) + return { + value = entry, + display = entry.name or entry.id, + ordinal = (entry.name or '') .. ' ' .. (entry.id or ''), + } + end, + }), + previewer = previewers.new_buffer_previewer({ + title = 'Board Details', + define_preview = function(self, entry) + local content = vim.split(vim.inspect(entry.value), '\n') + vim.api.nvim_buf_set_lines(self.state.bufnr, 0, -1, false, content) + vim.api.nvim_set_option_value('filetype', 'lua', { buf = self.state.bufnr }) + end, + }), + sorter = telescope_conf.generic_sorter({}), + attach_mappings = function(prompt_bufnr) + actions.select_default:replace(function() + local selection = action_state.get_selected_entry() + actions.close(prompt_bufnr) + -- wizard_data.board_id = selection.value.id + -- pick_framework(selection.value) -- Next step + if selection then + wizard_data.board_id = selection.value.id + pick_framework(selection.value) + end + end) + return true + end, + }) + :find() +end + +-- Entry point +local function launch_project_init() + wizard_data = {} -- Reset state + notify('Fetching board database...') + + local handle = io.popen('pio boards --json-output') + if not handle then + return + end + local result = handle:read('*a') + handle:close() + + local ok, json_data = pcall(vim.json.decode, result) + if not ok or type(json_data) ~= 'table' then + notify('Failed to parse board data.', vim.log.levels.ERROR) + return + end + + pick_board(json_data) +end + +return { + pioinit = launch_project_init, +} diff --git a/lua/nvimpio/piolib.lua b/lua/nvimpio/piolib.lua new file mode 100644 index 00000000..e7b4c8ae --- /dev/null +++ b/lua/nvimpio/piolib.lua @@ -0,0 +1,207 @@ +local M = {} + +local curl = require('plenary.curl') +local pickers = require('telescope.pickers') +local finders = require('telescope.finders') +local entry_display = require('telescope.pickers.entry_display') +local make_entry = require('telescope.make_entry') +local conf = require('telescope.config').values +local actions = require('telescope.actions') +local action_state = require('telescope.actions.state') +local misc = require('nvimpio.utils.misc') +local previewers = require('telescope.previewers') + +local libentry_maker = function(opts) + local displayer = entry_display.create({ + separator = '▏', + items = { + { width = 50 }, + { width = 50 }, + { remaining = true }, + }, + }) + + 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.username, + description = entry.description, + data = entry, + }, + ordinal = entry.name .. ' ' .. entry.owner.username .. ' ' .. entry.description, + display = make_display, + }, opts) + end +end + +-- stylua: ignore +local function pick_library(json_data) + local opts = {} + pickers.new(opts, { + prompt_title = 'Libraries', + layout_config = { + width = 0.9, -- Overall width of the Telescope window (90% of screen) + preview_width = 0.60, -- 65% of the window goes to "Board Details", leaving 25% for results + }, + finder = finders.new_table({ + results = json_data['items'], + 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() + local pkg_name = selection['value']['owner'] .. '/' .. selection['value']['name'] + -- local command = 'pio pkg install --library "' .. pkg_name .. '"' + -- command = command .. ' && pio run -t compiledb' + + local pio = require('nvimpio.pio.upkeep') + pio.run_sequence({ + cmnds = {'pio pkg install --library "' .. pkg_name .. '"'}, + cb = pio.handlePiolib + --function () vim.notify('Piolib: Done', vim.log.levels.INFO) end + }) + end) + return true + end, + + previewer = previewers.new_buffer_previewer({ + title = 'Package Info', + define_preview = function(self, entry, _) + local json = misc.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 }) --fix deprecated function + 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 = conf.generic_sorter(opts), + }):find() +end + +-- local function pick_library(json_data) +-- local opts = {} +-- +-- -- 1. Create a displayer for exactly 2 columns +-- local displayer = entry_display.create({ +-- separator = " │ ", +-- items = { +-- { width = 25 }, -- Column 1: Owner (fixed width) +-- { remaining = true }, -- Column 2: Library Name +-- }, +-- }) +-- +-- -- 2. Define the display logic for each row +-- local make_display = function(entry) +-- return displayer({ +-- { entry.value.owner or "unknown", "TelescopeResultsVariable" }, +-- entry.value.name or "unnamed", +-- }) +-- end +-- +-- pickers.new(opts, { +-- prompt_title = 'Libraries', +-- layout_config = { +-- width = 0.9, -- Overall width (90%) +-- preview_width = 0.60, -- Wider preview (60%) +-- }, +-- +-- +-- finder = finders.new_table({ +-- results = json_data['items'], +-- entry_maker = function(entry) +-- return { +-- value = entry, +-- display = make_display, +-- -- Ordinal is used for searching/filtering +-- ordinal = (entry.owner or '') .. ' ' .. (entry.name or ''), +-- } +-- end, +-- }), +-- attach_mappings = function(prompt_bufnr, _) +-- actions.select_default:replace(function() +-- actions.close(prompt_bufnr) +-- local selection = action_state.get_selected_entry() +-- local pkg_name = selection['value']['owner'] .. '/' .. selection['value']['name'] +-- +-- local pio = require('nvimpio.utils.pio') +-- pio.run_sequence({ +-- cmnds = {'pio pkg install --library "' .. pkg_name .. '"'}, +-- cb = function () vim.notify('Piolib: Done', vim.log.levels.INFO) end +-- }) +-- end) +-- return true +-- end, +-- +-- -- +-- previewer = previewers.new_buffer_previewer({ +-- title = 'Package Info', +-- define_preview = function(self, entry, _) +-- local json = misc.strsplit(vim.inspect(entry['value']['data'] or entry['value']), '\n') +-- local bufnr = self.state.bufnr +-- local win = self.state.winid +-- +-- vim.api.nvim_buf_set_lines(bufnr, 0, -1, false, json) +-- vim.api.nvim_set_option_value('filetype', 'lua', { buf = bufnr }) +-- +-- -- Apply wrapping to make the wide preview readable +-- vim.api.nvim_set_option_value('wrap', true, { win = win }) +-- vim.api.nvim_set_option_value('linebreak', true, { win = win }) +-- end, +-- }), +-- sorter = conf.generic_sorter(opts), +-- }):find() +-- end + +function M.piolib(lib_arg_list) + if not misc.pio_install_check() then + return + end + + local lib_str = '' + + for _, v in pairs(lib_arg_list) do + lib_str = lib_str .. v .. '+' + end + + local url = 'https://api.registry.platformio.org/v3/search' + local res = curl.get(url, { + insecure = true, + timeout = 20000, + headers = { content_type = 'application/json' }, + query = { + query = lib_str, + limit = 30, + sort = 'popularity', + -- page = 1, + -- limit = 1, + }, + }) + + if res['status'] == 200 then + local json_data = vim.json.decode(res['body']) + + pick_library(json_data) + else + vim.notify( + 'API Request to platformio return HTTP code: ' .. res['status'] .. '\nplease run `curl -LI ' .. url .. '` for complete information', + vim.log.levels.ERROR + ) + end +end + +return M diff --git a/lua/platformio/piolsserial.lua b/lua/nvimpio/piolsserial.lua similarity index 75% rename from lua/platformio/piolsserial.lua rename to lua/nvimpio/piolsserial.lua index 2ff7dede..29b01b40 100644 --- a/lua/platformio/piolsserial.lua +++ b/lua/nvimpio/piolsserial.lua @@ -1,5 +1,5 @@ local M = {} -local utils = require('platformio.utils') +local misc = require('nvimpio.utils.misc') M.tty_list = {} @@ -8,7 +8,7 @@ function M.parse_tty(lines) M.tty_list[k] = nil end local json_data = vim.json.decode(lines[1]) - for key, value in pairs(json_data) do + for _, value in pairs(json_data) do if value['description'] ~= 'n/a' then table.insert(M.tty_list, { port = value['port'], description = value['description'] }) end @@ -16,14 +16,14 @@ function M.parse_tty(lines) end function M.sync_ttylist() - utils.async_shell_cmd({ 'platformio', 'device', 'list', '--json-output' }, M.parse_tty) + misc.async_shell_cmd({ 'platformio', 'device', 'list', '--json-output' }, M.parse_tty) end function M.sync_ttylist_await() local done = false local result = nil - utils.async_shell_cmd({ 'platformio', 'device', 'list', '--json-output' }, function(lines, code) + misc.async_shell_cmd({ 'platformio', 'device', 'list', '--json-output' }, function(lines, code) result = { lines = lines, code = code } done = true end) diff --git a/lua/nvimpio/utils/misc.lua b/lua/nvimpio/utils/misc.lua new file mode 100644 index 00000000..2aee7f94 --- /dev/null +++ b/lua/nvimpio/utils/misc.lua @@ -0,0 +1,482 @@ +---@class platformio.utils.misc + +local M = {} + +M.is_windows = jit.os == 'Windows' +local uv = vim.uv or vim.loop + +M.devNul = M.is_windows and ' 2>./nul' or ' 2>/dev/null' +-- M.extra = 'printf \'\\\\n\\\\033[0;33mPlease Press ENTER to continue \\\\033[0m\'; read' +-- M.extra = ' && echo . && echo . && echo Please Press ENTER to continue' + +------------------------------------------------------ +--INFO: + +-- stylua: ignore +function M.showMessage(msg) + local bufnr = vim.api.nvim_create_buf(false, true) + local text = ' ' .. msg .. ' ' + vim.api.nvim_buf_set_lines(bufnr, 0, -1, false, { '', text, '' }) + + local width, height = #text + 2, 3 + local row = math.floor((vim.o.lines - height) / 2) + local col = math.floor((vim.o.columns - width) / 2) + + local win_id = vim.api.nvim_open_win(bufnr, false, { + relative = 'editor', + row = row, + col = col, + width = width, + height = height, + style = 'minimal', + border = 'double', + zindex = 250, + }) + + -- Define the "Glow" colors + -- We use 'IncSearch' or 'CurSearch' for a bright, glowing look + local hl_on = 'Normal:IncSearch,FloatBorder:IncSearch' + local hl_off = 'Normal:NormalFloat,FloatBorder:NormalFloat' + + -- Create a timer for the blinking effect + local blink_timer = uv.new_timer() + local is_on = true + + if blink_timer then + blink_timer:start( + 0, + 500, + vim.schedule_wrap(function() + if vim.api.nvim_win_is_valid(win_id) then + vim.api.nvim_set_option_value('winhl', is_on and hl_on or hl_off, { scope = 'local', win = win_id }) + is_on = not is_on + else + blink_timer:stop() + blink_timer:close() + end + end) + ) + end + + -- Return both so you can kill them later + return { win = win_id, timer = blink_timer } +end + +function M.closeMessage(status_obj) + if status_obj then + if status_obj.timer then + status_obj.timer:stop() + status_obj.timer:close() + end + if status_obj.win and vim.api.nvim_win_is_valid(status_obj.win) then + vim.api.nvim_win_close(status_obj.win, true) + end + end +end + +------------------------------------------------------ +--INFO: +-- stylua: ignore +function M.deleteFile(path) + local file = vim.fn.fnamemodify(path, ':t') + if vim.fn.filereadable(path) == 1 then + local success = vim.fn.delete(path) + + if success == 0 then vim.notify('PlatformIO: ' .. file .. ' file removed', vim.log.levels.INFO) + else vim.notify('PlatformIO: Failed to delete ' .. file, vim.log.levels.ERROR) end + else vim.notify('PlatformIO: ' .. file .. ' file not found', vim.log.levels.WARN) end +end + +------------------------------------------------------ +--INFO: +-- Version-Safe Path Joining (Fallback for Neovim < 0.10.0) +-- stylua: ignore +M.joinPath = vim.fs.joinpath or function(...) + return table.concat({ ... }, '/'):gsub('//+', '/') +end +------------------------------------------------------ +--INFO: +-- iterrative loop 48ms +-- stylua: ignore +function M.jsonFormat(root_data) + local buffer = {} + -- Stack stores: { val = item, lvl = depth, stage = "start"|"items", keys = {}, index = 0 } + local stack = { { val = root_data, lvl = 0, stage = 'start' } } + + local function get_indent(lvl) return string.rep(' ', lvl) end + + -- Full JSON Escape Table + local escapes = { + ['\\'] = '\\\\', + ['"'] = '\\"', + ['\b'] = '\\b', + ['\f'] = '\\f', + ['\n'] = '\\n', + ['\r'] = '\\r', + ['\t'] = '\\t', + } + + while #stack > 0 do + local curr = stack[#stack] + local val, lvl = curr.val, curr.lvl + local indent = get_indent(lvl) + + if type(val) == 'table' then + -- 1. Determine if Array or Object + local is_array = false + + -- Check if it's explicitly marked as an array by the Neovim parser + local mt = getmetatable(val) + if mt and mt.__jsontype == 'array' then + is_array = true + -- If not marked, check if it has indexed items or is literally an empty table + elseif #val > 0 or next(val) == nil then + is_array = true + end + + if curr.stage == 'start' then + table.insert(buffer, (is_array and '[' or '{') .. '\n') + curr.stage = 'items' + curr.keys = {} + + -- 2. Collect and Sort Keys (CRITICAL for SHA256 stability) + if is_array then for i = 1, #val do table.insert(curr.keys, i) end + else + for k in pairs(val) do table.insert(curr.keys, k) end + table.sort(curr.keys, function(a, b) return tostring(a) < tostring(b) end) + end + curr.total = #curr.keys + curr.cursor = 1 -- Point to the first key + elseif curr.stage == 'items' then + if curr.cursor <= curr.total then + local key = curr.keys[curr.cursor] + local item = val[key] + + -- Add comma for all but the first item + if curr.cursor > 1 then table.insert(buffer, ',\n') end + + table.insert(buffer, get_indent(lvl + 1)) + if not is_array then table.insert(buffer, '"' .. tostring(key) .. '": ') end + + curr.cursor = curr.cursor + 1 + -- Push next item to process + table.insert(stack, { val = item, lvl = lvl + 1, stage = 'start' }) + else + -- 3. Close the block + table.insert(buffer, '\n' .. indent .. (is_array and ']' or '}')) + table.remove(stack) + end + end + else + -- 4. Primitives (String, Number, Bool, Nil) + local output = '' + if val == nil or val == vim.NIL then output = 'null' + elseif val == vim.empty_dict then output = '{}' + elseif type(val) == 'boolean' then output = tostring(val) + elseif type(val) == 'string' then + -- A. Handle standard escapes (\n, \t, etc.) + local s = val:gsub('[\\"\b\f\n\r\t]', escapes) + + -- B. Handle unprintable control characters (U+0000 to U+001F) + s = s:gsub('[%z\1-\31]', function(c) + return string.format('\\u%04x', string.byte(c)) + end) + + -- C. Normalize Windows paths to Unix for cross-platform SHA256 stability + -- We flip double-backslashes (\\) resulting from the escape to (/) + s = s:gsub('\\\\', '/') + + output = '"' .. s .. '"' + else output = tostring(val) end + table.insert(buffer, output) + table.remove(stack) + end + end + return table.concat(buffer) +end + + +------------------------------------------------------ +--INFO: +-- Example Usage +-- local content = readFile("compile_commands.json") +-- if content then local data = vim.json.decode(content) end +-- stylua: ignore +---@param path string +function M.readFile(path) + -- 1. Check if file exists before opening to avoid "noisy" errors + local stat = uv.fs_stat(path) + if not stat then return false, 'File does not exist' end + + -- 2. Open the file + local fd, err = uv.fs_open(path, 'r', 438) + if not fd then return false, err end + + -- 3. Read the content (using stat.size from our check above) + local content, read_err = uv.fs_read(fd, stat.size, 0) + uv.fs_close(fd) + + if read_err then return false, read_err end + + return true, content +end + +--INFO: +-- Example +-- local ok, err = writeFiile(path, json) +-- if ok then print("Write complete!") end +-- stylua: ignore +---@param path string +---@param data string +---@param opts table +function M.writeFile(path, data, opts) + -- opts.overwrite: boolean (default true) + -- opts.mkdir: boolean (default true) + opts = opts or { overwrite = true, mkdir = true } + + local stat = uv.fs_stat(path) + -- 1. Overwrite protection + if opts.overwrite == false and stat then + return false, 'writeFile: File already exists' + end + + -- 2. Recursive directory creation + if opts.mkdir ~= false then + local parent = vim.fn.fnamemodify(path, ':h') + if not stat or stat.type ~= 'directory' then + vim.fn.mkdir(parent, 'p', '0700') + end + end + + --[[ + Octal Decimal Permission + 0700 448 Owner only (Full) + 0755 493 Owner (Full), Others (Read/Execute) + 0666 438 Everyone (Read/Write) - Not recommended for folders + 'w' truncates existing, 'wx' fails if exists (extra safety) + ]] + -- 3. Open for writing ('w' flag truncates automatically) + local fd, err = uv.fs_open(path, 'w', 438) + if not fd then return false, 'writeFile: Open error: ' .. (err or 'unknown') end + + -- 4. Robust Write Loop + -- Loop ensures all data is written even if it takes multiple chunks + local offset = 0 + while offset < #data do + local bytes_written, w_err = uv.fs_write(fd, data:sub(offset + 1), offset) + if w_err then + uv.fs_close(fd) + return false, 'writeFile: Write error: ' .. w_err + end + offset = offset + bytes_written + end + + -- 5. Force Sync (Crucial for your project.checksum watcher) + uv.fs_fsync(fd) + uv.fs_close(fd) + + return true, 'Success' +end + + +------------------------------------------------------ +--[[ +Targets Windows paths, normalizes slashes, and fixes smashed PlatformIO paths. +Cleans and repairs compiler flags in a command string. +{ "-I", "-L", "-isystem", "-T", "-include" } +1. Library Paths + -L: Specifies directories to search for library files (.a, .lib, .so). + Example: -L"C:\Users\lib" + -L"C:/Users/lib" + -l (lowercase L): While usually just a name (like -lmath), it can sometimes be a direct path to a specific file. +2. Header Inclusion (Advanced) + -isystem: Similar to -I, but treats the directory as a "system" header (suppresses warnings). PlatformIO uses this heavily for framework headers (Arduino/ESP-IDF). + -include: Forces the compiler to include a specific file before anything else. + Example: -include "C:\project\config.h" + -iquote: Directories for headers wrapped in double quotes "". +3. Output and Debugging + -o: The output path for the compiled object file or binary. + -fdebug-prefix-map=: Used to make builds reproducible by mapping absolute paths to relative ones in the debug symbols. +4. Linker and Frameworks + -T: Path to a linker script (very common in embedded/PlatformIO for memory mapping). + Example: -T"C:\project\ld\esp32.ld" + -F: (macOS/iOS) Path to search for frameworks. +]] +-- stylua: ignore +--- @param flags string: The raw command string (e.g., from compile_commands.json) +--- @return string: The cleaned command string +--INFO: +function M.normalizeFlags(flags) + if not flags or flags == '' then + return '' + end + + --1. Identify flags that look like paths. + -- Pattern explanation: + -- %- : Matches a literal hyphen (the start of a flag) + -- %S* : Matches zero or more non-space characters + -- \\ : Matches a literal backslash (identifies it as a Windows path) + -- %S* : Matches the rest of the non-space characters in that flag + local cleaned_cmd = flags:gsub('(%-%S-\\S*)', function(flag) + --2. Normalize Slashes + -- Replaces any number of backslashes (single \ or JSON-escaped \\) with one forward slash. + -- Forward slashes are safer and more portable for compilers like GCC/Clang. + flag = flag:gsub('[\\]+', '/') + + --3. Heal PlatformIO "Smashed" Paths + -- Fixes the bug where PlatformIO expansions repeat the user home directory. + -- Example: /Users/name/.platformiopackages/toolchain -> /.platformio/packages/toolchain + flag = flag:gsub('/Users/[^/]+%.platformio/packages', '/.platformio/packages') + + return flag + end) + + -- Return only the result string (discarding the replacement count) + return cleaned_cmd +end + +------------------------------------------------------ +--INFO: +function M.normalizePath(path) + -- return path:gsub('[\\]+', '/'):gsub('[//]+', '/') + return path:gsub('[\\/]+', '/') +end + +------------------------------------------------------ +--INFO: +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 + end + return t +end + +------------------------------------------------------ +--INFO: +function M.check_prefix(str, prefix) + return str:sub(1, #prefix) == prefix +end + +------------------------------------------------------ +--INFO: +local function pathmul(n) + return '..' .. string.rep('/..', n) +end + +local paths = { '.', '..', pathmul(1), pathmul(2), pathmul(3), pathmul(4), pathmul(5) } + +------------------------------------------------------ +--INFO: +function M.file_exists(name) + local f = io.open(name, 'r') + if f ~= nil then + io.close(f) + return true + else + return false + end +end + +------------------------------------------------------ +--INFO: +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 + end + vim.notify('Could not find platformio.ini, run :Pioinit to create a new project', vim.log.levels.ERROR) +end + +------------------------------------------------------ +--INFO: +function M.cd_pioini() + -- M.set_platformioRootDir() + vim.cmd('cd ' .. vim.g.platformioRootDir) +end + +------------------------------------------------------ +--INFO: +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 +end + +------------------------------------------------------ +--INFO: +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, + + on_exit = function(_, code) + callback(output, code) + end, + }) +end + +------------------------------------------------------ +--INFO: +function M.shell_cmd_blocking(command) + local handle = io.popen(command, 'r') + if not handle then + return nil, 'failed to run command' + end + + local result = handle:read('*a') + handle:close() + + return result +end + +------------------------------------------------------ +--INFO: +function M.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 + file:close() + end + + file = io.open(gitignore_path, 'a') + if file then + file:write(config_file .. '\n') + file:close() + end +end + +return M diff --git a/lua/platformio/utils.lua b/lua/nvimpio/utils/term.lua similarity index 82% rename from lua/platformio/utils.lua rename to lua/nvimpio/utils/term.lua index 3a7f302a..c62c9f59 100644 --- a/lua/platformio/utils.lua +++ b/lua/nvimpio/utils/term.lua @@ -1,398 +1,341 @@ -local M = {} - -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) - end - end - return t -end - -function M.check_prefix(str, prefix) - return str:sub(1, #prefix) == prefix -end - -local function pathmul(n) - return '..' .. string.rep('/..', n) -end - ------------------------------------------------------- -local is_windows = jit.os == 'Windows' - -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 -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 - end - 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) - 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 -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 - 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 - 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) - 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 - terminal:toggle() - vim.defer_fn(function() - if command and command ~= '' then - send(terminal, command) - end - end, 50) -- 50ms delay, adjust as needed sgget -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 -end - -function M.get_pioini_path() - for _, path in pairs(paths) do - if M.file_exists(path .. '/platformio.ini') then - return path - end - end -end - -function M.cd_pioini() - if vim.g.platformioRootDir ~= nil then - vim.cmd('cd ' .. vim.g.platformioRootDir) - else - vim.cmd('cd ' .. M.get_pioini_path()) - end -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() - - 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, - - 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 result = handle:read('*a') - handle:close() - - return result -end - -return M +local M = {} + +local is_windows = jit.os == 'Windows' +M.devNul = is_windows and ' 2>./nul' or ' 2>/dev/null' +-- M.extra = 'printf \'\\\\n\\\\033[0;33mPlease Press ENTER to continue \\\\033[0m\'; read' +-- M.extra = ' && echo . && echo . && echo Please Press ENTER to continue' + +local config = require('nvimpio').config + +-- to fix require loop, toggleterm is using stdout_callback function in 'platformio.utils.pio' +-- M.stdout_callback will be assigned by 'platformio.utils.pio' +M.stdout_callback = nil + +------------------------------------------------------ +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 + end + return t +end + +function M.check_prefix(str, prefix) + return str:sub(1, #prefix) == prefix +end + +------------------------------------------------------ + +-- 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 +end + +-- 1. Tell the LSP what a "Terminal" object looks like (simplified) +---@class Terminal +---@field id number +---@field bufnr number +---@field window number +---@field close function +---@field toggle function +-- INFO: get previous window +local function getPreviousWindow(orig_window) + -- 2. Define your context class + ---@class PioPrevContext + ---@field term Terminal|nil -- Handle for horizontal terminal + ---@field mon Terminal|nil + ---@field cli Terminal|nil + ---@field float boolean -- flag float terminal + ---@field orig_window number|nil + 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 + 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) + 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 +end + +------------------------------------------------------ +-- INFO: ToggleTerminal +function M.ToggleTerminal(command, direction) + 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 + prev.mon.display_name = 'piomon:' .. orig_window + 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 + pioOpts.id = 98 + pioOpts.on_stdout = nil + else -- INFO: if previous cli terminal already opened ==> reopen + if prev.cli then + prev.cli.display_name = 'piocli:' .. orig_window + 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 + pioOpts.id = 99 + + -- INFO: on_stdout + pioOpts.on_stdout = function(t, job, exit_code) + if type(M.stdout_callback) == 'function' then + M.stdout_callback(t, job, exit_code) + end + end + 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, + -- shell = vim.o.shell, + shell = vim.o.shell, + highlights = { + border = 'FloatBorder', + background = 'NormalFloat', + }, + }, + close_on_exit = false, --closeOnexit, + + -- 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 + end, + + -- -- INFO: on_exit() + -- on_exit = function(_) + -- 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) + 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 + terminal:toggle() + vim.defer_fn(function() + if command and command ~= '' then + send(terminal, command) + end + end, 50) -- 50ms delay, adjust as needed sgget + return terminal +end + +return M +---------------------------------------------------------------------------------------- diff --git a/lua/platformio/boilerplate.lua b/lua/platformio/boilerplate.lua deleted file mode 100644 index ee349a71..00000000 --- a/lua/platformio/boilerplate.lua +++ /dev/null @@ -1,57 +0,0 @@ -local M = {} -local uv = vim.loop - -local boilerplate = {} - -boilerplate['arduino'] = { - filename = 'main.cpp', - content = [[ -#include - -void setup() { - -} - -void loop() { - -} -]], -} - -function M.boilerplate_gen(framework) - local entry = boilerplate[framework] - if not entry then - return - end - - local src_path = 'src' - local stat = uv.fs_stat(src_path) - - if not stat or stat.type ~= 'directory' then - return - end - - local handle = uv.fs_scandir(src_path) - if handle then - while true do - local name = uv.fs_scandir_next(handle) - if not name then - break - end - if name ~= '.' and name ~= '..' then - return - end - end - end - - local file_path = src_path .. '/' .. entry.filename - local fd = uv.fs_open(file_path, 'w', 420) - if not fd then - return - end - - uv.fs_write(fd, entry.content) - uv.fs_close(fd) -end - -return M diff --git a/lua/platformio/piocmd.lua b/lua/platformio/piocmd.lua deleted file mode 100644 index 0f5fde5d..00000000 --- a/lua/platformio/piocmd.lua +++ /dev/null @@ -1,22 +0,0 @@ -local utils = require('platformio.utils') -local M = {} - -function M.piocmd(cmd_table, direction) - if not utils.pio_install_check() then - return - end - - 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 - utils.ToggleTerminal(cmd, direction) - end -end - -return M diff --git a/lua/platformio/piodebug.lua b/lua/platformio/piodebug.lua deleted file mode 100644 index d3786e54..00000000 --- a/lua/platformio/piodebug.lua +++ /dev/null @@ -1,16 +0,0 @@ -local utils = require('platformio.utils') -local M = {} - -function M.piodebug(args_table) - if not utils.pio_install_check() then - return - end - - 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') -end - -return M diff --git a/lua/platformio/pioinit.lua b/lua/platformio/pioinit.lua deleted file mode 100644 index a9843aab..00000000 --- a/lua/platformio/pioinit.lua +++ /dev/null @@ -1,143 +0,0 @@ -local M = {} - -local pickers = require('telescope.pickers') -local finders = require('telescope.finders') -local telescope_conf = require('telescope.config').values -local actions = require('telescope.actions') -local action_state = require('telescope.actions.state') -local entry_display = require('telescope.pickers.entry_display') -local make_entry = require('telescope.make_entry') -local utils = require('platformio.utils') -local previewers = require('telescope.previewers') -local config = require('platformio').config -local boilerplate_gen = require('platformio.boilerplate').boilerplate_gen - -local boardentry_maker = function(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, - }) - 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 pick_framework(board_details) - local opts = {} - pickers - .new(opts, { - prompt_title = 'frameworks', - finder = finders.new_table({ - results = board_details['frameworks'], - }), - attach_mappings = function(prompt_bufnr, _) - actions.select_default:replace(function() - actions.close(prompt_bufnr) - local selection = action_state.get_selected_entry() - local selected_framework = selection[1] - local command = 'pio project init --board ' .. board_details['id'] .. ' --project-option "framework=' .. selected_framework .. '"' - -- .. utils.extra - utils.ToggleTerminal(command, 'float', function() - vim.cmd(':PioLSP') - boilerplate_gen(selected_framework) - end) - end) - return true - end, - sorter = telescope_conf.generic_sorter(opts), - }) - :find() -end - -local function pick_board(json_data) - local opts = {} - pickers - .new(opts, { - prompt_title = 'Boards', - finder = finders.new_table({ - results = json_data, - 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() - pick_framework(selection['value']['data']) - 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 }) --fix deprecated function - 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.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 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) -end - -return M diff --git a/lua/platformio/piolib.lua b/lua/platformio/piolib.lua deleted file mode 100644 index 98b7e517..00000000 --- a/lua/platformio/piolib.lua +++ /dev/null @@ -1,127 +0,0 @@ -local M = {} - -local config = require('platformio').config -local curl = require('plenary.curl') - -local pickers = require('telescope.pickers') -local finders = require('telescope.finders') -local entry_display = require('telescope.pickers.entry_display') -local make_entry = require('telescope.make_entry') -local conf = require('telescope.config').values -local actions = require('telescope.actions') -local action_state = require('telescope.actions.state') -local utils = require('platformio.utils') -local previewers = require('telescope.previewers') - -local libentry_maker = function(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, - }) - end - - return function(entry) - return make_entry.set_default_entry_mt({ - value = { - name = entry.name, - owner = entry.owner.username, - description = entry.description, - data = entry, - }, - ordinal = entry.name .. ' ' .. entry.owner.username .. ' ' .. entry.description, - display = make_display, - }, opts) - end -end - -local function pick_library(json_data) - local opts = {} - pickers - .new(opts, { - prompt_title = 'Libraries', - finder = finders.new_table({ - results = json_data['items'], - 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() - local pkg_name = selection['value']['owner'] .. '/' .. selection['value']['name'] - local command = 'pio pkg install --library "' .. pkg_name .. '"' - utils.ToggleTerminal(command, 'float', function() - vim.cmd(':PioLSP') - 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 }) --fix deprecated function - 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 = conf.generic_sorter(opts), - }) - :find() -end - -function M.piolib(lib_arg_list) - if not utils.pio_install_check() then - return - end - - local lib_str = '' - - for _, v in pairs(lib_arg_list) do - lib_str = lib_str .. v .. '+' - end - - local url = 'https://api.registry.platformio.org/v3/search' - local res = curl.get(url, { - insecure = true, - timeout = 20000, - headers = { content_type = 'application/json' }, - query = { - query = lib_str, - limit = 30, - sort = 'popularity', - -- page = 1, - -- limit = 1, - }, - }) - - if res['status'] == 200 then - local json_data = vim.json.decode(res['body']) - - pick_library(json_data) - else - vim.notify( - 'API Request to platformio return HTTP code: ' .. res['status'] .. '\nplease run `curl -LI ' .. url .. '` for complete information', - vim.log.levels.ERROR - ) - end -end - -return M diff --git a/lua/platformio/piolsp.lua b/lua/platformio/piolsp.lua deleted file mode 100644 index b122cf42..00000000 --- a/lua/platformio/piolsp.lua +++ /dev/null @@ -1,123 +0,0 @@ -local M = {} - -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('%)', '\\)') - table.insert(escaped_flags, escaped) - end - - return escaped_flags -end - -local function process_ccls() - 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 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 - - 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 - - ::continue:: - end - - f:close() - - 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() -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 - 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) -end - -function M.piolsp() - 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')) - else - gitignore_lsp_configs('.ccls') - end - end - vim.notify('LSP config generation completed!', vim.log.levels.INFO) - vim.cmd('LspRestart') -end - -return M diff --git a/lua/platformio/piomenu.lua b/lua/platformio/piomenu.lua deleted file mode 100644 index 7657a12a..00000000 --- a/lua/platformio/piomenu.lua +++ /dev/null @@ -1,46 +0,0 @@ -local M = {} - -local icon = { icon = ' ', color = 'orange' } -- Assign platformio orange icon -local wk_table = { mode = { 'n', 'v' } } - -local function traverseMenu(menu, wkey) - for _, child_node in ipairs(menu) do - if child_node.node == 'menu' then - traverseMenu(child_node.items, wkey .. child_node.shortcut) - table.insert(wk_table, { wkey .. child_node.shortcut, group = child_node.desc, icon = icon }) - elseif child_node.node == 'item' then - table.insert(wk_table, { - wkey .. child_node.shortcut, - ' ' .. child_node.command .. '', - desc = child_node.desc, - icon = icon, - }) - end - end -end - -function M.piomenu(config) - if config.menu_key == nil then - return - end - - local ok, wk = pcall(require, 'which-key') - if not ok then - vim.api.nvim_echo({ { 'which-key plugin not found!', 'ErrorMsg' } }, true, {}) - return - end - - wk.setup({ - preset = 'helix', --'modern', --'classic' - }) - 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) - - wk.add(wk_table) -end - -return M diff --git a/lua/platformio/piomon.lua b/lua/platformio/piomon.lua deleted file mode 100644 index 0917abd3..00000000 --- a/lua/platformio/piomon.lua +++ /dev/null @@ -1,30 +0,0 @@ -local utils = require('platformio.utils') -local M = {} - -function M.piomon(args_table) - if not utils.pio_install_check() then - return - end - - utils.cd_pioini() - - local command = nil - if #args_table == 0 then - command = 'pio device monitor' - elseif #args_table == 1 then - local baud_rate = args_table[1] - command = string.format('pio device monitor -b %s', baud_rate) - elseif #args_table == 2 then - local baud_rate = args_table[1] - local port = args_table[2] - command = string.format('pio device monitor -b %s -p %s', baud_rate, port) - end - - if command == nil then - vim.notify('Usage: Piomon ', vim.log.levels.ERROR) - else - utils.ToggleTerminal(command, 'horizontal') - end -end - -return M diff --git a/lua/platformio/piorun.lua b/lua/platformio/piorun.lua deleted file mode 100644 index 89020f20..00000000 --- a/lua/platformio/piorun.lua +++ /dev/null @@ -1,48 +0,0 @@ -local M = {} - -local utils = require('platformio.utils') - -function M.piobuild() - utils.cd_pioini() - local command = 'pio run' -- .. 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') -end - -function M.piouploadfs() - utils.cd_pioini() - local command = 'pio run --target uploadfs' -- .. 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') -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 -end - -return M diff --git a/mini_nvimPIO.lua b/mini_nvimPIO.lua new file mode 100644 index 00000000..243bedee --- /dev/null +++ b/mini_nvimPIO.lua @@ -0,0 +1,629 @@ +local isWindows = vim.fn.has('win32') == 1 --jit.os == 'Windows' +local isMac = vim.fn.has('mac') == 1 + +---------------------------------------------------------------------------------------- +-- INFO: Set options +-- disable netrw at the very start of your init.lua +vim.g.loaded_netrw = 1 +vim.g.loaded_netrwPlugin = 1 + +-- optionally enable 24-bit colour +vim.opt.termguicolors = true + +vim.opt['number'] = true +vim.opt.autowrite = true -- Enable auto write + +-- only set clipboard if not in ssh, to make sure the OSC 52 +-- integration works automatically. Requires Neovim >= 0.10.0 +vim.opt.clipboard = vim.env.SSH_TTY and '' or 'unnamedplus' -- Sync with system clipboard + +vim.opt.tabstop = 2 -- Number of spaces tabs count for +vim.opt.softtabstop = 2 +vim.opt.shiftround = true -- Round indent +vim.opt.shiftwidth = 2 -- Size of an indent +vim.opt.smartindent = true -- Insert indents automatically +vim.opt.expandtab = true -- Use spaces instead of tabs + +vim.opt.smoothscroll = true +vim.opt.foldmethod = 'expr' +vim.opt.foldtext = '' +vim.opt.fillchars = '' +vim.opt.foldcolumn = '0' +vim.opt.foldenable = true +vim.opt.foldexpr = 'v:lua.vim.treesitter.foldexpr()' +vim.opt.foldlevel = 99 +vim.opt.foldlevelstart = 99 +vim.opt.foldnestmax = 3 + +vim.g.have_nerd_font = true +vim.g.mapleader = ' ' +vim.g.maplocalleader = ' ' + +if isWindows then + local pwsh = vim.fn.executable('pwsh') == 1 and 'pwsh' or 'powershell' + vim.opt.shell = pwsh + vim.opt.shellcmdflag = + '-NoLogo -NoProfile -ExecutionPolicy RemoteSigned -Command [Console]::InputEncoding=[Console]::OutputEncoding=[System.Text.UTF8Encoding]::new();' + vim.opt.shellredir = '2>&1 | Out-File -Encoding UTF8 %s; exit $LastExitCode' + vim.opt.shellpipe = '2>&1 | Out-File -Encoding UTF8 %s; exit $LastExitCode' + vim.opt.shellquote = '' + vim.opt.shellxquote = '' + +--elseif vim.fn.has("mac") == 1 then +else + vim.g.shell = '/bin/bash' -- or '/bin/zsh', '/usr/bin/fish', etc. + vim.g.shellcmdflag = '-c' -- Executes the command passed as a string + vim.g.shellpipe = '|' -- Pipes output of external commands + vim.g.shellredir = '> ' -- Redirects output of external commands +end + +vim.hl = vim.highlight +vim.api.nvim_set_hl(0, 'PioStatus', { + fg = '#e0af68', -- Dark text + bg = '#11111b', + bold = true, +}) +---------------------------------------------------------------------------------------- +-- INFO: Set diagnostic config +vim.diagnostic.config({ + virtual_lines = true, + update_in_insert = true, + underline = true, + severity_sort = true, + float = { + focusable = true, + style = 'minimal', + border = 'rounded', + source = true, + header = '', + prefix = '', + }, + signs = { + text = { + [vim.diagnostic.severity.ERROR] = ' ', + [vim.diagnostic.severity.WARN] = ' ', + [vim.diagnostic.severity.HINT] = ' ', + [vim.diagnostic.severity.INFO] = ' ', + }, + }, +}) + +---------------------------------------------------------------------------------------- +-- INFO: Set nvim keymaps +local keymap = function(mode, lhs, rhs, opts) + local options = { silent = true } --noremap = true by default in vim.keymap.set + if opts then + options = vim.tbl_extend('force', options, opts or {}) + end + vim.keymap.set(mode, lhs, rhs, options) +end + +--To toggle line wrapping in Neovim +keymap('n', 'w', ':set wrap!', { desc = 'Toggle wrap' }) + +keymap('n', 'gll', function() + vim.cmd.edit(vim.lsp.log.get_filename()) +end, { desc = 'open LSP [l]og' }) +-- Keybinds to make split navigation easier. +-- Use CTRL+ to switch between windows +-- See `:help wincmd` for a list of all window commands +keymap('n', '', '', { desc = 'Move focus to the left window' }) +keymap('n', '', '', { desc = 'Move focus to the right window' }) +keymap('n', '', '', { desc = 'Move focus to the lower window' }) +keymap('n', '', '', { desc = 'Move focus to the upper window' }) + +-- Resize with arrows +keymap('n', '', ':resize -2') +keymap('n', '', ':resize +2') +keymap('n', '', ':vertical resize -2') +keymap('n', '', ':vertical resize +2') + +keymap('n', 'bb', ':bprevious', { desc = '[B]efore Buffer' }) +keymap('n', 'ba', ':bnext', { desc = '[A]fter Buffer' }) +keymap('n', 'bs', ':ball', { desc = '[S]how AllOpened Buffers' }) + +-- keymap('n', 'bd', 'bdelete', { desc = '[D]elete Buffer' }) +keymap('n', 'bd', function() + local bufnr = vim.api.nvim_get_current_buf() + local bufs = vim.fn.getbufinfo({ buflisted = 1 }) + + if #bufs <= 1 then + -- Create a new empty buffer + vim.cmd('enew') + else + -- Switch to the previous buffer + vim.cmd('bp') + end + + -- Delete the buffer we started with (using pcall to ignore "No buffers deleted" errors) + pcall(vim.api.nvim_buf_delete, bufnr, { force = false }) +end, { desc = '[D]elete Buffer' }) + +keymap('n', 'e', 'Neotree document_symbols', { desc = 'NeoTreeToggle' }) +keymap('n', '\\', 'Neotree toggle', { desc = 'NeoTreeToggle' }) +-- keymap('n', 'e', 'NvimTreeToggle', { desc = 'NvimTreeToggle' }) +-- keymap('n', '\\', 'NvimTreeToggle', { desc = 'NvimTreeToggle' }) + +-- Keybinds to make split navigation easier. +-- Use CTRL+ to switch between windows +keymap('n', '', '', { desc = 'Move focus to the left window' }) +keymap('n', '', '', { desc = 'Move focus to the right window' }) +keymap('n', '', '', { desc = 'Move focus to the lower window' }) +keymap('n', '', '', { desc = 'Move focus to the upper window' }) + +---------------------------------------------------------------------------------------- +---------------------------------------------------------------------------------------- +-- INFO: Set mini lazy config +---------------------------------------------------------------------------------------- +-- stylua: ignore +---[[ +local function setup_xdg_paths() + -- local isWindows = vim.fn.has('win32') == 1 + -- local isMac = vim.fn.has('mac') == 1 + local home = vim.env.HOME or vim.env.USERPROFILE or '' + local app_name = 'nvim-pio' -- pick a temp root + + -- Helper to ensure we never gsub a nil and always use forward slashes + local function normalize(path) + return path:gsub('\\', '/') + end + + -- 1. XDG_CONFIG_HOME (Settings/Configs) + if not vim.env.XDG_CONFIG_HOME then + local path = isWindows and (vim.env.LOCALAPPDATA or (home .. '/AppData/Local')) + or isMac and (home .. '/Library/Preferences') + or (home .. '/.config') + vim.env.XDG_CONFIG_HOME = normalize(vim.fs.joinpath(path, app_name)) + end + + -- 2. XDG_DATA_HOME (Large data/Databases) + if not vim.env.XDG_DATA_HOME then + local path = isWindows and (vim.env.LOCALAPPDATA or (home .. '/AppData/Local')) + or isMac and (home .. '/Library/Application Support') + or (home .. '/.local/share') + vim.env.XDG_DATA_HOME = normalize(vim.fs.joinpath(path, app_name)) + end + + -- 3. XDG_STATE_HOME (Logs/History/Persistent State) + if not vim.env.XDG_STATE_HOME then + local path = isWindows and (vim.env.LOCALAPPDATA or (home .. '/AppData/Local')) + or isMac and (home .. '/Library/Application Support') + or (home .. '/.local/state') + vim.env.XDG_STATE_HOME = normalize(vim.fs.joinpath(path, app_name)) + end + + -- 4. XDG_CACHE_HOME (Temporary/Disposable data) + if not vim.env.XDG_CACHE_HOME then + local path = isWindows and (vim.env.TEMP or (home .. '/AppData/Local/Temp')) + or isMac and (home .. '/Library/Caches') + or (home .. '/.cache') + vim.env.XDG_CACHE_HOME = normalize(vim.fs.joinpath(path, app_name)) + end +end + +setup_xdg_paths() +---]] +--[[ +local app_name = 'nvim-pio' -- pick a temp root +local home = isWindows and vim.env.LOCALAPPDATA:gsub('\\', '/') or vim.env.HOME +-- local home = vim.env.HOME or vim.env.USERPROFILE or "" +home = home .. '/' .. app_name +-- local home = vim.loop.os_tmpdir():gsub('\\', '/') .. '/' .. app_name + +-- vim.env.NVIM_APPNAME = app_name --isolated nvim +vim.env.XDG_CONFIG_HOME = home .. (isWindows and '/config/' or '/.config') +vim.env.XDG_DATA_HOME = home .. (isWindows and '/data/' or '/.local/share/') +vim.env.XDG_STATE_HOME = home .. (isWindows and '/state/' or '/.local/state/') +vim.env.XDG_CACHE_HOME = home .. (isWindows and '/cache/' or '/.cache/') +--]] + +-- BOOTSTRAP (Use stdpath so it ALWAYS matches Neovim's internal logic) +local lazypath = vim.fn.stdpath('data') .. '/lazy/lazy.nvim' + +if not (vim.uv or vim.loop).fs_stat(lazypath) then + print('Installing lazy.nvim to: ' .. lazypath) + vim.fn.system({ + 'git', + 'clone', + '--filter=blob:none', + 'https://github.com/folke/lazy.nvim.git', + '--branch=stable', + lazypath, + }) +end + +-- ADD TO RUNTIME PATH (Crucial: makes 'require("lazy")' work) +vim.opt.rtp:prepend(lazypath) + +---------------------------------------------------------------------------------------- +-- INFO: define plugins table +local plugins = { + { 'windwp/nvim-autopairs', event = 'InsertEnter', config = true }, + + { + 'Saghen/blink.cmp', + dependencies = { 'rafamadriz/friendly-snippets' }, + version = '1.*', -- Download pre-built binaries + opts = { + keymap = { preset = 'default' }, -- 'default', 'super-tab', or 'enter' + sources = { + default = { 'lsp', 'path', 'snippets', 'buffer' }, + }, + }, + }, + + -- Recommended: Minimal statusline/tabline + { + 'nvim-lualine/lualine.nvim', + dependencies = { + 'nvim-tree/nvim-web-devicons', + config = function() + require('lualine').setup({ + options = { + globalstatus = true, -- Single statusline for all windows + extensios = { 'neo-treee' }, + }, + -- This replaces the visual part of bufferline + tabline = { + lualine_a = { + { + 'buffers', + show_filename_only = true, + hide_filename_extension = false, + show_modified_status = true, + mode = 0, -- 0: Shows buffer name + max_length = vim.o.columns, + filetype_names = { + NvimTree = 'Explorer', + TelescopePrompt = 'Telescope', + }, + }, + }, + }, + }) + end, + }, + }, + + { + 'nvim-neo-tree/neo-tree.nvim', + branch = 'v3.x', + dependencies = { + 'nvim-lua/plenary.nvim', + 'nvim-tree/nvim-web-devicons', + 'MunifTanjim/nui.nvim', + }, + opts = { + filesystem = { + -- use_libuv_file_watcher = true, + filtered_items = { + hide_dotfiles = false, + hide_gitignored = true, + hide_by_name = { + '.pio', + '.cache', + }, + never_show = { -- Add any massive folders here + -- '.cache', + -- '.git', + 'node_modules', + -- 'build', + -- 'target', + }, + }, + }, + }, + }, + + { + 'batoaqaa/nvim-platformio.lua', + cond = function() + -- local platformioRootDir = (vim.fn.filereadable('platformio.ini') == 1) and vim.fn.getcwd() or nil + local platformioRootDir = (vim.fn.filereadable('platformio.ini') == 1) and vim.uv.cwd() or nil + if platformioRootDir and vim.fs.find('.pio', { path = platformioRootDir, type = 'directory' })[1] then + -- if platformio.ini file and .pio folder 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.env.XDG_DATA_HOME .. '/lazy/nvim-platformio.lua') == nil then + -- if nvim-platformio not installed, enable plugin to install it first time + -- vim.g.platformioRootDir = vim.fn.getcwd() + vim.g.platformioRootDir = vim.uv.cwd() + 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 + vim.notify('PlatformIO loaded', vim.log.levels.INFO, { title = 'PlatformIO' }) + vim.cmd('Pioinit') + end + end, + }) + -- vim.g.platformioRootDir = vim.fn.getcwd() + vim.g.platformioRootDir = vim.uv.cwd() + require('lazy').restore({ plguins = { 'nvim-platformio.lua' }, show = false }) + end, {}) + end + return vim.g.platformioRootDir ~= nil + end, + dependencies = { + { 'akinsho/toggleterm.nvim' }, + { 'nvim-telescope/telescope.nvim' }, + -- { + -- 'nvim-telescope/telescope.nvim', + -- tag = '0.1.8', + -- dependencies = { 'nvim-lua/plenary.nvim' }, + -- }, + { 'nvim-telescope/telescope-ui-select.nvim' }, + { 'nvim-lua/plenary.nvim' }, + { 'folke/which-key.nvim' }, + { + 'mason-org/mason-lspconfig.nvim', + dependencies = { + { 'mason-org/mason.nvim' }, + { 'folke/trouble.nvim' }, + { 'j-hui/fidget.nvim' }, -- status bottom right + }, + }, + }, + }, +} +---------------------------------------------------------------------------------------- + +---------------------------------------------------------------------------------------- +-- INFO: Install/config plugins +require('lazy').setup(plugins, { + root = vim.fn.stdpath('data') .. '/lazy', + install = { missing = true }, + ui = { border = 'rounded' }, +}) + +---------------------------------------------------------------------------------------- +-- stylua: ignore +if vim.fn.has('nvim-0.11') == 1 then + local json_format_group = vim.api.nvim_create_augroup('JsonFormat', { clear = true }) + vim.api.nvim_create_autocmd('BufWritePre', { + group = json_format_group, + pattern = '*.json', + -- This runs 'python -m json.tool' on the current buffer content + -- It updates the buffer in-place before the file is written to disk + callback = function() vim.cmd('%!python -m json.tool') end, + }) +elseif vim.fn.has('nvim-0.12') == 1 then +end + +---------------------------------------------------------------------------------------- +-- INFO: autocommand to Update lazy.nvim plugins in the background +vim.api.nvim_create_autocmd('User', { + pattern = 'LazyVimStarted', -- Triggers after the UI enters and startup time is calculated + desc = 'Update lazy.nvim plugins in the background', + callback = function() + require('lazy').sync({ + wait = false, -- Makes the operation asynchronous + show = false, -- Prevents the Lazy UI from automatically opening + }) + -- You can add a notification here if you like + -- vim.notify("Lazy plugins sync started in background", vim.log.levels.INFO) + end, +}) + +-- AUTO-CLEANUP ON EXIT +-- SELECTIVE CLEANUP ON EXIT (Keeps plugins, deletes temp files) +-- stylua: ignore +-- SELECTIVE CLEANUP FOR WINDOWS +vim.api.nvim_create_autocmd("VimLeave", { + callback = function() + -- On Windows, we specifically want to wipe the shada and temp files + local state_path = vim.fn.stdpath("state") + local cache_path = vim.fn.stdpath("cache") + + local targets = { + state_path .. "/shada", -- Delete history/marks + cache_path, -- Delete temp bytecode + } + + for _, path in ipairs(targets) do + if vim.fn.isdirectory(path) == 1 then + local cmd = isWindows + and string.format('rmdir /s /q "%s"', path:gsub("/", "\\")) + or string.format('rm -rf "%s"', path) + vim.fn.jobstart(cmd, { detach = true }) + end + end + end, +}) + +---------------------------------------------------------------------------------------- +-- INFO: set up python nvim venv (virtual environment 'nenv'), activaten. +local platformio_core_dir, pynvim_env, pynvim_python, pynvim_lib, pynvim_bin, pynvim_activate +if isWindows then + platformio_core_dir = vim.env.HOME .. '/.platformio' + pynvim_env = platformio_core_dir .. '/nenv' + pynvim_bin = pynvim_env .. '/Scripts' + pynvim_python = pynvim_bin .. '/python.exe' + pynvim_activate = pynvim_bin .. '/Activate.ps1' +else + platformio_core_dir = vim.env.HOME .. '/.platformio' + pynvim_env = platformio_core_dir .. '/nenv' + pynvim_bin = pynvim_env .. '/bin' + pynvim_python = pynvim_bin .. '/python3' + pynvim_activate = pynvim_bin .. '/activate' +end + +--Toolchain inclusion forced in Global Environment +-- vim.uv.os_setenv('PLATFORMIO_SETTING_COMPILATIONDB_INCLUDE_TOOLCHAIN', 'true') +vim.uv.os_setenv('PLATFORMIO_CORE_DIR', platformio_core_dir) +vim.g.python_host_prog = pynvim_python +vim.g.python3_host_prog = pynvim_python + +local sep = (vim.fn.has('win32') == 1 and ';' or ':') +vim.env.PATH = pynvim_bin .. sep .. vim.env.PATH +vim.env.VIRTUAL_ENV = pynvim_env + +if vim.fn.isdirectory(platformio_core_dir) == 0 then + vim.fn.mkdir(platformio_core_dir, 'p') + -- vim.fn.system({ + -- "wget", + -- "https://raw.githubusercontent.com/platformio/platformio-core-installer/master/get-platformio.py", + -- }) + -- vim.fn.system({ "python", "get-platformio.py" }) + -- os.execute((isWindows and "del " or "rm -f ") .. "get-platformio.py*") +end + +local output +-- local expand_dir = vim.fn.expand(pynvim_env) +if not vim.uv.fs_stat(pynvim_env) then + if not isWindows then + output = vim.fn.system({ 'python3', '-m', 'venv', pynvim_env }) + print(output) + vim.fn.system({ 'chmod', '755', '-R', pynvim_bin }) + vim.fn.system('source ' .. pynvim_activate) + else + vim.fn.system({ 'python', '-m', 'venv', pynvim_env }) + vim.fn.system(pynvim_activate) + end + + -------------------------------------------------------------------------------------- + -- INFO: install platformio and nvim required packages. + output = vim.fn.system({ pynvim_python, '-m', 'pip', 'install', '-U', 'pip' }) + print(output) + output = vim.fn.system({ pynvim_python, '-m', 'pip', 'install', 'pynvim' }) + print(output) + vim.fn.system({ pynvim_python, '-m', 'pip', 'install', 'neovim' }) + vim.fn.system({ pynvim_python, '-m', 'pip', 'install', 'debugpy' }) + vim.fn.system({ pynvim_python, '-m', 'pip', 'install', 'isort' }) + vim.fn.system({ pynvim_python, '-m', 'pip', 'install', 'scons' }) + vim.fn.system({ pynvim_python, '-m', 'pip', 'install', 'sconscrip' }) + vim.fn.system({ pynvim_python, '-m', 'pip', 'install', 'yamllint' }) + vim.fn.system({ pynvim_python, '-m', 'pip', 'install', '-U', 'platformio' }) + -- vim.fn.system({ 'pip', 'install', '-U', 'platformio' }) +end + +---------------------------------------------------------------------------------------- +-- INFO: configure nvim-platformio and load +----------------------------------------------------------------------------------------- +local tok, telescope = pcall(require, 'telescope') +if tok then + -- 1. Import the actions module (This is the missing part!) + local actions = require('telescope.actions') + -- local telescope = require('telescope') + -- print("here" .. vim.inspect(pioConfig)) + telescope.setup({ + extensions = { + ['ui-select'] = { + require('telescope.themes').get_dropdown({ + -- Customizing the dialog appearance + width = 0.6, + previewer = false, + }), + }, + }, + defaults = { + mappings = { + i = { + [''] = actions.delete_buffer, -- Delete buffer in insert mode + }, + n = { + ['dd'] = actions.delete_buffer, -- Delete buffer in normal mode + }, + }, + }, + pickers = { + buffers = { + show_all_buffers = true, + sort_lastused = true, + theme = 'dropdown', -- Compact look + previewer = false, -- Disable preview for a faster feel + }, + }, + }) + + -- Enable Telescope extensions if they are installed + pcall(require('telescope').load_extension, 'fzf') + pcall(require('telescope').load_extension, 'ui-select') + + local function run_project_wizard() + local project_config = {} + + -- Step 1: Select IDE + vim.ui.select({ 'Neovim', 'VS Code', 'IntelliJ' }, { prompt = 'Select IDE' }, function(ide) + if not ide then + return + end + project_config.ide = ide + + -- Step 2: Select Board + vim.ui.select({ 'ESP32', 'Arduino Uno', 'Raspberry Pi' }, { prompt = 'Select Board' }, function(board) + if not board then + return + end + project_config.board = board + + -- Step 3: Select Framework + vim.ui.select({ 'ESP-IDF', 'Arduino Core', 'MicroPython' }, { prompt = 'Select Framework' }, function(fw) + if not fw then + return + end + project_config.framework = fw + + -- Step 4: Final Selection + vim.ui.select({ 'true', 'false' }, { prompt = 'Include Sample Code?' }, function(sample) + project_config.sample = sample == 'true' + + -- Final Output/Action + print( + string.format('Setup: %s on %s using %s (Sample: %s)', project_config.ide, project_config.board, project_config.framework, project_config.sample) + ) + end) + end) + end) + end) + end + + vim.keymap.set('n', 'pw', run_project_wizard, { desc = 'Run Project Wizard' }) + + -- See `:help telescope.builtin` + local builtin = require('telescope.builtin') + vim.keymap.set('n', 'sh', builtin.help_tags, { desc = 'Search [H]elp' }) + vim.keymap.set('n', 'sk', builtin.keymaps, { desc = 'Search [K]eymaps' }) + vim.keymap.set('n', 'sf', builtin.find_files, { desc = 'Search [F]iles' }) + vim.keymap.set('n', 'ss', builtin.builtin, { desc = 'Search [S]elect Telescope' }) + vim.keymap.set('n', 'sw', builtin.grep_string, { desc = 'Search current [W]ord' }) + vim.keymap.set('n', 'sg', builtin.live_grep, { desc = 'Search by [G]rep' }) + vim.keymap.set('n', 'sd', builtin.diagnostics, { desc = 'Search [D]iagnostics' }) + vim.keymap.set('n', 'sr', builtin.resume, { desc = 'Search [R]esume' }) + vim.keymap.set('n', 's.', builtin.oldfiles, { desc = 'Search Recent Files ("." for repeat)' }) + vim.keymap.set('n', '', builtin.buffers, { desc = '[ ] Find existing buffers' }) + + -- Slightly advanced example of overriding default behavior and theme + vim.keymap.set('n', '/', function() + -- You can pass additional configuration to Telescope to change the theme, layout, etc. + builtin.current_buffer_fuzzy_find(require('telescope.themes').get_dropdown({ + winblend = 10, + previewer = false, + })) + end, { desc = '[/] Fuzzily search in current buffer' }) + -- Keymap to open the buffer list + vim.keymap.set('n', 'fb', 'Telescope buffers', { desc = 'Find Buffers' }) +end + +local pioConfig = { + lspClangd = { + -- enabled = false, + enabled = true, + attach = { + enabled = true, + keymaps = true, + }, + }, + -- menu_key = "\\", -- replace this menu key to your convenience + -- menu_name = "PlatformIO", -- replace this menu name to your convenience + -- debug = false, +} +local pok, nvimpio = pcall(require, 'nvimpio') +if pok then + -- print("here" .. vim.inspect(pioConfig)) + nvimpio.setup(pioConfig) +end diff --git a/minimal_config.lua b/minimal_config.lua deleted file mode 100644 index f00d500f..00000000 --- a/minimal_config.lua +++ /dev/null @@ -1,79 +0,0 @@ --- insures lazy is installed -local lazypath = vim.loop.os_tmpdir() .. '/lazy/lazy.nvim' -if not (vim.uv or vim.loop).fs_stat(lazypath) then - vim.fn.system({ - 'git', - 'clone', - '--filter=blob:none', - 'https://github.com/folke/lazy.nvim.git', - '--branch=stable', -- latest stable release - lazypath, - }) -end -vim.opt.rtp:prepend(lazypath) - -local plugins = { - { - 'anurag3301/nvim-platformio.lua', - -- cmd = { 'Pioinit', 'Piorun', 'Piocmdh', 'Piocmdf', 'Piolib', 'Piomon', 'Piodebug', 'Piodb' }, - - -- 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 and vim.fs.find('.pio', { path = platformioRootDir, type = 'directory' })[1] then - -- if platformio.ini file and .pio folder 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 - 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-telescope/telescope.nvim' }, - { 'nvim-telescope/telescope-ui-select.nvim' }, - { 'nvim-lua/plenary.nvim' }, - { 'folke/which-key.nvim' }, - { 'nvim-treesitter/nvim-treesitter' } - }, - }, -} - -require('lazy').setup(plugins, { - install = { - missing = true, - }, -}) - -vim.opt['number'] = true - -vim.g.pioConfig ={ - lsp = 'clangd', - menu_key = '\\', -- replace this menu key to your convenience -} -local pok, platformio = pcall(require, 'platformio') -if pok then platformio.setup(vim.g.pioConfig) end diff --git a/plugin/platformio.lua b/plugin/nvimpio.lua similarity index 63% rename from plugin/platformio.lua rename to plugin/nvimpio.lua index 35020c1b..70422084 100644 --- a/plugin/platformio.lua +++ b/plugin/nvimpio.lua @@ -6,25 +6,83 @@ -- +: At least one argument. -- -1: Zero or one argument (like ?, explicitly). -local utils = require('platformio.utils') -local piolsserial = require('platformio.piolsserial') +---------------------------------------------------------------- +-- lazy-loading technique using a Lua metatable. +-- It essentially "teaches" Neovim how to find a custom module only when you actually try to use it +-- setmetatable(vim, { +-- __index = function(t, k) +-- -- Lazy load the misc module if requested +-- if k == 'misc' then +-- t.misc = require('nvimpio.utils.misc') +-- return t.misc +-- end +-- +-- -- Alias vim.pio to the pio module for convenience +-- if k == 'pio' then +-- t.pio = require('nvimpio.utils.pio') +-- return t.pio +-- end +-- end, +-- }) + +--@class vim +--@field pio platformio.utils.pio +--@field misc platformio.utils.misc + +-- setmetatable(vim, { +-- __index = function(t, k) +-- if k == 'misc' then +-- local m = require('nvimpio.utils.misc') +-- rawset(t, k, m) -- Physically add 'misc' to 'vim' +-- return m +-- end +-- if k == 'pio' then +-- local p = require('nvimpio.utils.pio') +-- rawset(t, k, p) -- Physically add 'pio' to 'vim' +-- return p +-- end +-- end, +-- }) +vim.misc = require('nvimpio.utils.misc') +vim.pio = require('nvimpio.pio.upkeep') + +-- INFO: fix paths in compile_commands.json +vim.api.nvim_create_user_command('PioFixPaths', function() + vim.pio.compile_commandsFix() +end, {}) + +-- -- Pioinit2 +-- local pio_wiz = require('nvimpio.pioinit2') +-- +-- -- Create a keybinding to trigger the wizard +-- vim.keymap.set('n', 'pi', function() +-- pio_wiz.launch() +-- end, { desc = 'Run PIO Project Wizard' }) +-- +-- -- Alternatively, create a user command +-- vim.api.nvim_create_user_command('PioWizard', function() +-- pio_wiz.launch() +-- end, {}) +-- +------------------------------------------------------ +local piolsserial = require('nvimpio.piolsserial') -- Pioinit vim.api.nvim_create_user_command('Pioinit', function() - require('platformio.pioinit').pioinit() + require('nvimpio.pioinit').pioinit() end, { force = true }) -- Piolsp vim.api.nvim_create_user_command('PioLSP', function() vim.schedule(function() - require('platformio.piolsp').piolsp() + require('nvimpio.pioCommands').piolsp() end) end, {}) -- Piorun vim.api.nvim_create_user_command('Piorun', function(opts) local args = opts.args - require('platformio.piorun').piorun({ args }) + require('nvimpio.piocommands').piorun({ args }) end, { nargs = '?', complete = function(_, _, _) @@ -33,10 +91,10 @@ end, { }) -- Piomon -piolsserial.sync_ttylist() +-- piolsserial.sync_ttylist() vim.api.nvim_create_user_command('Piomon', function(opts) local args = opts.fargs - require('platformio.piomon').piomon(args) + require('nvimpio.pioCommands').piomon(args) end, { nargs = '*', @@ -59,13 +117,13 @@ end, { -- Piolsserial vim.api.nvim_create_user_command('Piolsserial', function() - require('platformio.piolsserial').print_tty_list() + require('nvimpio.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) + require('nvimpio.piolib').piolib(args) end, { nargs = '+', }) @@ -73,7 +131,7 @@ end, { -- 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') + require('nvimpio.pioCommands').piocmd(cmd_table, 'horizontal') end, { nargs = '*', }) @@ -81,19 +139,20 @@ end, { -- 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') + require('nvimpio.pioCommands').piocmd(cmd_table, 'float') end, { nargs = '*', }) -- Piodebug vim.api.nvim_create_user_command('Piodebug', function() - require('platformio.piodebug').piodebug() + require('nvimpio.pioCommands').piodebug() end, {}) ------------------------------------------------------ -- require('telescope').load_extension('ui-select') +-- stylua: ignore -- INFO: List ToggleTerminals vim.api.nvim_create_user_command('PioTermList', function() local telescope = require('telescope') @@ -123,14 +182,13 @@ vim.api.nvim_create_user_command('PioTermList', function() }, }) telescope.load_extension('ui-select') - local utils = require('platformio.utils') 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] + local termtype = vim.misc.strsplit(terms[i].display_name, ':')[1] table.insert(toggleterm_list, { term = terms[i], termtype = termtype, -- Store the terminal type [piomon or piocli] @@ -157,16 +215,13 @@ vim.api.nvim_create_user_command('PioTermList', function() kind = 'PioTerminals', }, function(chosen, _) if chosen then + chosen.term.display_name = chosen.termtype .. ':' .. vim.api.nvim_get_current_win() 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 + else chosen.term:open() end vim.api.nvim_echo({ { 'Switched to PIO terminal: ' .. chosen.termtype, 'Normal' } }, true, {}) - else - vim.api.nvim_echo({ { 'No PIO terminal window selected.', 'Normal' } }, true, {}) - end + else vim.api.nvim_echo({ { 'No PIO terminal window selected.', 'Normal' } }, true, {}) end end) end, {})