diff --git a/Dockerfile b/Dockerfile index 70b187a..fe25a46 100644 --- a/Dockerfile +++ b/Dockerfile @@ -1,3 +1,23 @@ +# Copyright (c) 2024 Erich L Foster +# +# Permission is hereby granted, free of charge, to any person obtaining a copy of +# this software and associated documentation files (the "Software"), to deal in +# the Software without restriction, including without limitation the rights to +# use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies +# of the Software, and to permit persons to whom the Software is furnished to do +# so, subject to the following conditions: +# +# The above copyright notice and this permission notice shall be included in all +# copies or substantial portions of the Software. +# +# THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +# IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +# FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +# AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +# LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +# OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE +# SOFTWARE. + FROM ubuntu:22.04 as builder ENV USER_NAME=my-app diff --git a/LICENSE b/LICENSE index 83dc276..ddecf74 100644 --- a/LICENSE +++ b/LICENSE @@ -1,13 +1,11 @@ -MIT License +Copyright (c) 2024 Erich L Foster -Copyright (c) 2023 Ellison - -Permission is hereby granted, free of charge, to any person obtaining a copy -of this software and associated documentation files (the "Software"), to deal -in the Software without restriction, including without limitation the rights -to use, copy, modify, merge, publish, distribute, sublicense, and/or sell -copies of the Software, and to permit persons to whom the Software is -furnished to do so, subject to the following conditions: +Permission is hereby granted, free of charge, to any person obtaining a copy of +this software and associated documentation files (the "Software"), to deal in +the Software without restriction, including without limitation the rights to +use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies +of the Software, and to permit persons to whom the Software is furnished to do +so, subject to the following conditions: The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software. diff --git a/Makefile b/Makefile index 05bfb7c..f640a42 100644 --- a/Makefile +++ b/Makefile @@ -8,7 +8,7 @@ test: --headless \ --noplugin \ -u ${TESTS_INIT} \ - -c "PlenaryBustedFile tests/devcontainer_cli/unit_tests.lua" + -c "PlenaryBustedFile tests/devcontainer-cli/unit_tests.lua" test_all: @nvim \ diff --git a/README.md b/README.md index 1e1d571..19de96a 100644 --- a/README.md +++ b/README.md @@ -76,6 +76,7 @@ make assumptions about how you work. - [docker](https://docs.docker.com/get-docker/) - [devcontainer-cli](https://github.com/devcontainers/cli#npm-install) +- [toggleterm](https://github.com/akinsho/toggleterm.nvim) ## 🔧 Installation @@ -84,6 +85,7 @@ make assumptions about how you work. ```lua { "erichlf/devcontainer-cli.nvim", + dependencies = { 'akinsho/toggleterm.nvim' }, opts = { -- whather to verify that the final devcontainer should be run interactive = false, @@ -107,29 +109,34 @@ make assumptions about how you work. -- stylua: ignore { "Du", - ":DevcontainerUp", + ":DevcontainerUp", desc = "Bring up the DevContainer", }, { "Dc", - ":DevcontainerConnect", + ":DevcontainerConnect", desc = "Connect to DevContainer", }, { "De", - ":DevcontainerExec", + ":DevcontainerExec direction='vertical' size='40'", desc = "Execute a command in DevContainer", }, { "Db", - ":DevcontainerExec cd build && make", + ":DevcontainerExec cd build && make", desc = "Execute build command in DevContainer", }, { "Dt", - ":DevcontainerExec cd build && make test", + ":DevcontainerExec cmd='cd build && make test' direction='horizontal'", desc = "Execute test command in DevContainer", }, + { + "DT", + "DevContainerToggle", + desc = "Toggle the current DevContainer Terminal" + }, } }, ``` @@ -160,6 +167,14 @@ There are 3 main commands: `:DevcontainerUp`, `:DevcontainerExec`, and `:Devcont continue working in your current session and run commands in the devcontainer via `DevcontainerExec`. +During execution using `DevcontainerUp` or `DevcontainerExec` it is possible +to toggle the terminal via `t` while in normal mode and then to bring it back +you can run `:DevContainerToggle`. Additionally you could bring it back through +`:TermSelect`. + +During the execution of a Devcontainer process you can also type `q` or `` +to kill the process and exit the terminal window. + ## Tests Tests are executed automatically on each PR using Github Actions. diff --git a/doc/devcontainer-cli.nvim.txt b/doc/devcontainer-cli.nvim.txt index 2074477..810b14b 100644 --- a/doc/devcontainer-cli.nvim.txt +++ b/doc/devcontainer-cli.nvim.txt @@ -19,6 +19,11 @@ DevcontainerUp *DevcontainerUp* DevcontainerExec *DevcontainerExec* Runs a given command in the projects devcontainer. +DevcontainerToggle *DevcontainerToggle* + Toggles the current devcontainer window. It is expected that only one + devcontainer window is open at a time and so this will only toggle the last + devcontainer window. + DevcontainerConnect *DevcontainerConnect* Closes the nvim sessions (all sessions fromt the terminal) and opens a new terminal which is connected in the docker container, ready to execute the diff --git a/lua/devcontainer-cli/config/init.lua b/lua/devcontainer-cli/config/init.lua index dedc76c..f9381fe 100644 --- a/lua/devcontainer-cli/config/init.lua +++ b/lua/devcontainer-cli/config/init.lua @@ -1,3 +1,23 @@ +-- Copyright (c) 2024 Erich L Foster +-- +-- Permission is hereby granted, free of charge, to any person obtaining a copy of +-- this software and associated documentation files (the "Software"), to deal in +-- the Software without restriction, including without limitation the rights to +-- use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies +-- of the Software, and to permit persons to whom the Software is furnished to do +-- so, subject to the following conditions: +-- +-- The above copyright notice and this permission notice shall be included in all +-- copies or substantial portions of the Software. +-- +-- THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +-- IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +-- FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +-- AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +-- LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +-- OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE +-- SOFTWARE. + local ConfigModule = {} local file_path = debug.getinfo(1).source:sub(2) local default_config = { diff --git a/lua/devcontainer-cli/devcontainer_cli.lua b/lua/devcontainer-cli/devcontainer_cli.lua index 2aedc47..bf0ecd9 100644 --- a/lua/devcontainer-cli/devcontainer_cli.lua +++ b/lua/devcontainer-cli/devcontainer_cli.lua @@ -1,5 +1,25 @@ +-- Copyright (c) 2024 Erich L Foster +-- +-- Permission is hereby granted, free of charge, to any person obtaining a copy of +-- this software and associated documentation files (the "Software"), to deal in +-- the Software without restriction, including without limitation the rights to +-- use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies +-- of the Software, and to permit persons to whom the Software is furnished to do +-- so, subject to the following conditions: +-- +-- The above copyright notice and this permission notice shall be included in all +-- copies or substantial portions of the Software. +-- +-- THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +-- IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +-- FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +-- AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +-- LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +-- OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE +-- SOFTWARE. + local config = require("devcontainer-cli.config") -local devcontainer_utils = require("devcontainer-cli.devcontainer_utils") +local utils = require("devcontainer-cli.devcontainer_utils") local M = {} @@ -11,10 +31,12 @@ local function define_autocommands() -- It connects with the Devcontainer just after quiting neovim. -- TODO: checks that the devcontainer is not already connected -- TODO: checks that there is a devcontainer running - vim.schedule(function() - local command = config.nvim_plugin_folder .. "/bin/connect_to_devcontainer.sh" - vim.fn.jobstart(command, { detach = true }) - end) + vim.schedule( + function() + local command = config.nvim_plugin_folder .. "/bin/connect_to_devcontainer.sh" + vim.fn.jobstart(command, { detach = true }) + end + ) end, }) end @@ -22,17 +44,39 @@ end -- executes a given command in the devcontainer of the current project directory ---@param opts (table) options for executing the command function M.exec(opts) - vim.validate({ args = { opts.args, "string" } }) - if opts.args == nil or opts.args == "" then - devcontainer_utils.exec() - else - devcontainer_utils.exec_cmd(opts.args) + local args = opts.args + vim.validate({ args = { args, "string", true } }) + + local parsed = { + cmd = nil, + direction = nil, + size = nil, + } + + if args ~= nil then + parsed = utils.parse(args) + + vim.validate({ + cmd = { parsed.cmd, "string", true }, + direction = { parsed.direction, "string", true }, + size = { parsed.size, "number", true }, + }) + if parsed.cmd == nil and parsed.direction == nil and parsed.size == nil then + parsed.cmd = args + end end + + utils.exec(parsed.cmd, parsed.direction, parsed.size) +end + +-- toggle the current devcontainer window +function M.toggle() + utils.toggle() end -- bring up the devcontainer in the current project directory function M.up() - devcontainer_utils.bringup(vim.loop.cwd()) + utils.bringup() end -- Thanks to the autocommand executed after leaving the UI, after closing the diff --git a/lua/devcontainer-cli/devcontainer_utils.lua b/lua/devcontainer-cli/devcontainer_utils.lua index d7ce5dd..15c74c0 100644 --- a/lua/devcontainer-cli/devcontainer_utils.lua +++ b/lua/devcontainer-cli/devcontainer_utils.lua @@ -1,24 +1,72 @@ -local config = require("devcontainer-cli.config") -local windows_utils = require("devcontainer-cli.windows_utils") -local folder_utils = require("devcontainer-cli.folder_utils") +-- Copyright (c) 2024 Erich L Foster +-- +-- Permission is hereby granted, free of charge, to any person obtaining a copy of +-- this software and associated documentation files (the "Software"), to deal in +-- the Software without restriction, including without limitation the rights to +-- use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies +-- of the Software, and to permit persons to whom the Software is furnished to do +-- so, subject to the following conditions: +-- +-- The above copyright notice and this permission notice shall be included in all +-- copies or substantial portions of the Software. +-- +-- THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +-- IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +-- FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +-- AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +-- LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +-- OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE +-- SOFTWARE. -local M = {} +local config = require("devcontainer-cli.config") +local folder_utils = require("devcontainer-cli.folder_utils") +local Terminal = require('toggleterm.terminal').Terminal +local mode = require('toggleterm.terminal').mode + +local M = {} + +-- valid window directions +local directions = { + "float", + "horizontal", + "tab", + "vertical", +} -- window management variables -local prev_win = -1 -local win = -1 -local buffer = -1 +local _terminal = nil +-- +-- number of columns for displaying text +local terminal_columns = config.terminal_columns + +-- wrap the given text at max_width +---@param text (string) the text to wrap +---@return (string) the text wrapped +local function _wrap_text(text) + local wrapped_lines = {} + for line in text:gmatch("[^\n]+") do + local current_line = "" + for word in line:gmatch("%S+") do + if #current_line + #word <= terminal_columns then + current_line = current_line .. word .. " " + else + table.insert(wrapped_lines, current_line) + current_line = word .. " " + end + end + table.insert(wrapped_lines, current_line) + end + return table.concat(wrapped_lines, "\n") +end -- window the created window detaches set things back to -1 -local on_detach = function() - prev_win = -1 - win = -1 - buffer = -1 +local _on_detach = function() + _terminal = nil end -- on_fail callback ----@param exit_code (integer) the exit code from the failed job -local on_fail = function(exit_code) +---@param exit_code (number) the exit code from the failed job +local _on_fail = function(exit_code) vim.notify( "Devcontainer process has failed! exit_code: " .. exit_code, vim.log.levels.ERROR @@ -27,56 +75,75 @@ local on_fail = function(exit_code) vim.cmd("silent! :checktime") end -local on_success = function() +local _on_success = function() vim.notify("Devcontainer process succeeded!", vim.log.levels.INFO) end -- on_exit callback function to delete the open buffer when devcontainer exits -- in a neovim terminal ----@param code (integer) the exit code -local on_exit = function(_, code, _) +---@param code (number) the exit code +local _on_exit = function(code) if code == 0 then - on_success() + _on_success() return end - on_fail(code) + _on_fail(code) end ---- execute command ----@param cmd (string) the command to execute in the devcontainer terminal -function M.exec_command(cmd) - vim.fn.termopen( - cmd, - { - on_exit = on_exit, - on_stdout = function(_, _, _) - if win ~= -1 then - vim.api.nvim_win_call( - win, - function() - vim.cmd("normal! G") - end - ) - else - vim.notify("Executed " .. cmd) - end - end, - } - ) - if buffer ~= -1 then - vim.api.nvim_set_current_buf(buffer) - else - vim.notify("No buffer created, therefore no output from command will be visible", vim.log.levels.WARN) +-- check if the value is in the given table +local function tableContains(tbl, value) + for _, item in ipairs(tbl) do + if item == value then + return true + end end + + return false end --- create a new window and execute the given command ----@param cmd (string) the command to execute in the devcontainer terminal -local function spawn_and_execute(cmd) - prev_win = vim.api.nvim_get_current_win() - win, buffer = windows_utils.open_floating_window(on_detach) - M.exec_command(cmd) +---@class ParsedArgs +---@field direction string? +---@field cmd string? +---@field size number? + +---Take a users command arguments in the format "cmd='git commit' direction='float'" size='42' +---and parse this into a table of arguments +---{cmd = "git commit", direction = "float", size = "42"} +---@param args string +---@return ParsedArgs +function M.parse(args) + local p = { + single = "'(.-)'", + double = '"(.-)"', + } + local result = {} + if args then + local quotes = args:match(p.single) and p.single or args:match(p.double) and p.double or nil + if quotes then + -- 1. extract the quoted command + local pattern = "(%S+)=" .. quotes + for key, value in args:gmatch(pattern) do + quotes = p.single + value = vim.fn.shellescape(value) + result[vim.trim(key)] = vim.fn.expandcmd(value:match(quotes)) + end + -- 2. then remove it from the rest of the argument string + args = args:gsub(pattern, "") + end + + for _, part in ipairs(vim.split(args, " ")) do + if #part > 1 then + local arg = vim.split(part, "=") + local key, value = arg[1], arg[2] + if key == "size" then + value = tonumber(value) + end + result[key] = value + end + end + end + return result end -- build the initial part of a devcontainer command @@ -84,7 +151,7 @@ end -- (see man devcontainer) ---@return (string|nil) nil if no devcontainer_parent could be found otherwise -- the basic devcontainer command for the given type -local function devcontainer_command(action) +local function _devcontainer_command(action) local devcontainer_root = folder_utils.get_root(config.toplevel) if devcontainer_root == nil then vim.notify("Unable to find devcontainer directory...", vim.log.levels.ERROR) @@ -100,8 +167,8 @@ end -- helper function to generate devcontainer bringup command ---@return (string|nil) nil if no devcontainer_parent could be found otherwise the -- devcontainer bringup command -local function get_devcontainer_up_cmd() - local command = devcontainer_command("up") +local function _get_devcontainer_up_cmd() + local command = _devcontainer_command("up") if command == nil then return command end @@ -133,9 +200,60 @@ local function get_devcontainer_up_cmd() return command end +-- create a new window and execute the given command +---@param cmd (string) the command to execute in the devcontainer terminal +---@param direction (string|nil) the placement of the window to be created (float, horizontal, vertical) +---@param size (number|nil) the size of the window to be created +local function _spawn_and_execute(cmd, direction, size) + direction = vim.F.if_nil(direction, "float") + if tableContains(directions, direction) == false then + vim.notify("Invalid direction: " .. direction, vim.log.levels.ERROR) + return + end + + -- create the terminal + _terminal = Terminal:new { + cmd = cmd, + hidden = false, + display_name = "devcontainer-cli", + direction = vim.F.if_nil(direction, "float"), + dir = folder_utils.get_root(config.toplevel), + size = size, + close_on_exit = false, + on_open = function(term) + -- ensure that we are not in insert mode + vim.cmd("stopinsert") + vim.api.nvim_buf_set_keymap( + term.bufnr, + 'n', + '', + 'lua vim.api.nvim_buf_delete(' .. term.bufnr .. ', { force = true } )close', + { noremap = true, silent = true } + ) + vim.api.nvim_buf_set_keymap( + term.bufnr, + 'n', + 'q', + 'lua vim.api.nvim_buf_delete(' .. term.bufnr .. ', { force = true } )close', + { noremap = true, silent = true } + ) + vim.api.nvim_buf_set_keymap(term.bufnr, 'n', 't', 'close', { noremap = true, silent = true }) + end, + auto_scroll = true, + on_exit = function(_, _, code, _) + _on_exit(code) + _on_detach() + end, -- callback for when process closes + } + -- start in insert mode + _terminal:set_mode(mode.NORMAL) + -- now execute the command + _terminal:open() +end + -- issues command to bringup devcontainer function M.bringup() - local command = get_devcontainer_up_cmd() + local command = _get_devcontainer_up_cmd() if command == nil then return @@ -144,7 +262,7 @@ function M.bringup() if config.interactive then vim.ui.input( { - prompt = windows_utils.wrap_text( + prompt = _wrap_text( "Spawning devcontainer with command: " .. command ) .. "\n\n" .. "Press q to cancel or any other key to continue\n" }, @@ -154,38 +272,65 @@ function M.bringup() "\nUser cancelled bringing up devcontainer" ) else - spawn_and_execute(command) + _spawn_and_execute(command) end end ) return end - spawn_and_execute(command) + _spawn_and_execute(command) end -- execute the given cmd within the given devcontainer_parent ---@param cmd (string) the command to issue in the devcontainer terminal -function M.exec_cmd(cmd) - local command = devcontainer_command("exec") +---@param direction (string|nil) the placement of the window to be created +-- (left, right, bottom, float) +function M._exec_cmd(cmd, direction, size) + local command = _devcontainer_command("exec") if command == nil then return end command = command .. " " .. config.shell .. " -c '" .. cmd .. "'" - spawn_and_execute(command) + vim.notify(command) + _spawn_and_execute(command, direction, size) end -- execute a given cmd within the given devcontainer_parent -function M.exec() - vim.ui.input( - { prompt = "Enter command:" }, - function(input) - if input ~= nil then - M.exec_cmd(input) +---@param cmd (string|nil) the command to issue in the devcontainer terminal +---@param direction (string|nil) the placement of the window to be created +-- (left, right, bottom, float) +---@param size (number|nil) size of the window to create +function M.exec(cmd, direction, size) + if _terminal ~= nil then + vim.notify("There is already a devcontainer process running.", vim.log.levels.WARN) + return + end + + if cmd == nil or cmd == "" then + vim.ui.input( + { prompt = "Enter command:" }, + function(input) + if input ~= nil then + M._exec_cmd(input, direction, size) + else + vim.notify("No command received, ignoring.", vim.log.levels.WARN) + end end - end - ) + ) + else + M._exec_cmd(cmd, direction, size) + end +end + +-- toggle the current terminal +function M.toggle() + if _terminal == nil then + vim.notify("No devcontainer window to toggle.", vim.log.levels.WARN) + return + end + _terminal:toggle() end return M diff --git a/lua/devcontainer-cli/folder_utils.lua b/lua/devcontainer-cli/folder_utils.lua index b86c6a6..b783c81 100644 --- a/lua/devcontainer-cli/folder_utils.lua +++ b/lua/devcontainer-cli/folder_utils.lua @@ -1,17 +1,37 @@ +-- Copyright (c) 2024 Erich L Foster +-- +-- Permission is hereby granted, free of charge, to any person obtaining a copy of +-- this software and associated documentation files (the "Software"), to deal in +-- the Software without restriction, including without limitation the rights to +-- use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies +-- of the Software, and to permit persons to whom the Software is furnished to do +-- so, subject to the following conditions: +-- +-- The above copyright notice and this permission notice shall be included in all +-- copies or substantial portions of the Software. +-- +-- THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +-- IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +-- FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +-- AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +-- LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +-- OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE +-- SOFTWARE. + local M = {} -- return true if directory exists -local function directory_exists(target_folder) +local function _directory_exists(target_folder) return (vim.fn.isdirectory(target_folder) == 1) end -- get the devcontainer path for the given directory ---@param directory (string) the directory containing .devcontainer ---@return (string|nil) directory if a devcontainer exists within it or nil otherwise -local function get_devcontainer_parent(directory) +local function _get_devcontainer_parent(directory) local devcontainer_directory = directory .. '/.devcontainer' - if directory_exists(devcontainer_directory) then + if _directory_exists(devcontainer_directory) then return directory end @@ -24,9 +44,9 @@ end -- returned ---@return (string|nil) the devcontainer directory closest to the root directory -- or the first if toplevel is true, and nil if no directory was found -local function get_root_directory(directory, toplevel) +local function _get_root_directory(directory, toplevel) local parent_directory = vim.fn.fnamemodify(directory, ':h') - local devcontainer_parent = get_devcontainer_parent(directory) + local devcontainer_parent = _get_devcontainer_parent(directory) -- Base case: If we've reached the root directory if parent_directory == directory then @@ -37,7 +57,7 @@ local function get_root_directory(directory, toplevel) return devcontainer_parent end - local upper_devcontainer_directory = get_root_directory(parent_directory, toplevel) + local upper_devcontainer_directory = _get_root_directory(parent_directory, toplevel) -- no devcontainer higher up so return what was found here if upper_devcontainer_directory == nil then return devcontainer_parent @@ -55,7 +75,7 @@ end -- or the first if toplevel is true, and nil if no directory was found function M.get_root(toplevel) local current_directory = vim.fn.getcwd() - return get_root_directory(current_directory, toplevel) + return _get_root_directory(current_directory, toplevel) end return M diff --git a/lua/devcontainer-cli/health.lua b/lua/devcontainer-cli/health.lua index d8eb4ed..8551047 100644 --- a/lua/devcontainer-cli/health.lua +++ b/lua/devcontainer-cli/health.lua @@ -1,3 +1,23 @@ +-- Copyright (c) 2024 Erich L Foster +-- +-- Permission is hereby granted, free of charge, to any person obtaining a copy of +-- this software and associated documentation files (the "Software"), to deal in +-- the Software without restriction, including without limitation the rights to +-- use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies +-- of the Software, and to permit persons to whom the Software is furnished to do +-- so, subject to the following conditions: +-- +-- The above copyright notice and this permission notice shall be included in all +-- copies or substantial portions of the Software. +-- +-- THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +-- IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +-- FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +-- AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +-- LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +-- OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE +-- SOFTWARE. + local M = {} local start = vim.health.start or vim.health.report_start @@ -12,7 +32,16 @@ local function verify_binary(binary_name) ok(("`%s` executable found."):format(binary_name)) end end --- TODO: create a check for DevcontainerUp, this needs to be done after + +local function verify_plugin_dependencies(plugin_name) + if require(plugin_name) then + ok(("`%s` plugin found."):format(plugin_name)) + else + error(("`%s` plugin not found."):format(plugin_name), ("Add %s to dependencies in Lazy.git"):format(plugin_name)) + end +end + +-- TODO: create a check for DevcontainerUp, this needs to be done after -- creating the ability to stop a container -- TODO: create a check for DevcontainerExec @@ -22,9 +51,15 @@ function M.check() "docker", "devcontainer", } + local plugin_dependencies = { + "toggleterm", + } for _, bin_name in ipairs(required_binaries) do verify_binary(bin_name) end + for _, plugin_name in ipairs(plugin_dependencies) do + verify_plugin_dependencies(plugin_name) + end end return M diff --git a/lua/devcontainer-cli/init.lua b/lua/devcontainer-cli/init.lua index a690231..a5c802b 100644 --- a/lua/devcontainer-cli/init.lua +++ b/lua/devcontainer-cli/init.lua @@ -1,3 +1,23 @@ +-- Copyright (c) 2024 Erich L Foster +-- +-- Permission is hereby granted, free of charge, to any person obtaining a copy of +-- this software and associated documentation files (the "Software"), to deal in +-- the Software without restriction, including without limitation the rights to +-- use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies +-- of the Software, and to permit persons to whom the Software is furnished to do +-- so, subject to the following conditions: +-- +-- The above copyright notice and this permission notice shall be included in all +-- copies or substantial portions of the Software. +-- +-- THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +-- IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +-- FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +-- AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +-- LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +-- OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE +-- SOFTWARE. + local M = {} local devcontainer_cli = require("devcontainer-cli.devcontainer_cli") @@ -35,6 +55,15 @@ function M.setup(opts) } ) + vim.api.nvim_create_user_command( + "DevcontainerToggle", + devcontainer_cli.toggle, + { + nargs = 0, + desc = "Toggle the current devcontainer window.", + } + ) + vim.api.nvim_create_user_command( "DevcontainerConnect", devcontainer_cli.connect, diff --git a/lua/devcontainer-cli/windows_utils.lua b/lua/devcontainer-cli/windows_utils.lua deleted file mode 100644 index 485daea..0000000 --- a/lua/devcontainer-cli/windows_utils.lua +++ /dev/null @@ -1,76 +0,0 @@ -local config = require("devcontainer-cli.config") - -local M = {} - --- number of columns for displaying text -local terminal_columns = config.terminal_columns - --- wrap the given text at max_width ----@param text (string) the text to wrap ----@return (string) the text wrapped -function M.wrap_text(text) - local wrapped_lines = {} - for line in text:gmatch("[^\n]+") do - local current_line = "" - for word in line:gmatch("%S+") do - if #current_line + #word <= terminal_columns then - current_line = current_line .. word .. " " - else - table.insert(wrapped_lines, current_line) - current_line = word .. " " - end - end - table.insert(wrapped_lines, current_line) - end - return table.concat(wrapped_lines, "\n") -end - --- create a floating window ----@param on_detach (function) call back for when the window is detached ----@return integer, integer the window and buffer numbers -function M.open_floating_window(on_detach) - local buf = vim.api.nvim_create_buf(false, true) - vim.api.nvim_buf_set_option(buf, 'bufhidden', 'wipe') - vim.api.nvim_buf_set_option(buf, 'filetype', 'devcontainer-cli') - vim.api.nvim_buf_set_keymap(buf, 'n', 'q', 'close', {}) - vim.api.nvim_buf_set_keymap(buf, 'n', '', 'close', {}) - - local width = math.ceil( - math.min(vim.o.columns, math.max(terminal_columns, vim.o.columns - 20)) - ) - local height = math.ceil(math.min(vim.o.lines, math.max(20, vim.o.lines - 10))) - - local row = math.ceil(vim.o.lines - height) * 0.5 - 1 - local col = math.ceil(vim.o.columns - width) * 0.5 - 1 - - local win = vim.api.nvim_open_win(buf, true, { - relative = "editor", - width = width, - height = height, - row = row, - col = col, - style = "minimal", - border = "rounded", - title = "devcontainer-cli", - title_pos = "center", - -- noautocommand = false, - }) - -- Attach autocommand for when the buffer is detached (closed) - vim.api.nvim_buf_attach(buf, false, { - on_detach = on_detach - }) - - return win, buf -end - --- send text to the given buffer ----@param text (string) the text to send ----@param buffer (integer) the buffer to send text to -function M.send_text(text, buffer) - local text_tbl = vim.split(M.wrap_text(text), "\n") - - -- Set the content of the buffer - vim.api.nvim_buf_set_lines(buffer, 0, -1, false, text_tbl) -end - -return M diff --git a/tests/devcontainer_cli/unit_tests.lua b/tests/devcontainer-cli/unit_tests.lua similarity index 66% rename from tests/devcontainer_cli/unit_tests.lua rename to tests/devcontainer-cli/unit_tests.lua index e5f74ea..fe352a9 100644 --- a/tests/devcontainer_cli/unit_tests.lua +++ b/tests/devcontainer-cli/unit_tests.lua @@ -1,5 +1,24 @@ +-- Copyright (c) 2024 Erich L Foster +-- +-- Permission is hereby granted, free of charge, to any person obtaining a copy of +-- this software and associated documentation files (the "Software"), to deal in +-- the Software without restriction, including without limitation the rights to +-- use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies +-- of the Software, and to permit persons to whom the Software is furnished to do +-- so, subject to the following conditions: +-- +-- The above copyright notice and this permission notice shall be included in all +-- copies or substantial portions of the Software. +-- +-- THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +-- IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +-- FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +-- AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +-- LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +-- OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE +-- SOFTWARE. + local folder_utils = require("devcontainer-cli.folder_utils") -local utils = require("devcontainer-cli.devcontainer_utils") describe("folder_utils.get_root:", function() it( @@ -51,12 +70,3 @@ describe("folder_utils.get_root:", function() end ) end) - -describe("devcontainer_utils.exec_command:", function() - it( - "check if a command can be executed", - function() - utils.exec_command("echo 'Hello World!'") - end - ) -end) diff --git a/tests/minimal_init.lua b/tests/minimal_init.lua index b4c8c23..141b127 100644 --- a/tests/minimal_init.lua +++ b/tests/minimal_init.lua @@ -1,3 +1,23 @@ +-- Copyright (c) 2024 Erich L Foster +-- +-- Permission is hereby granted, free of charge, to any person obtaining a copy of +-- this software and associated documentation files (the "Software"), to deal in +-- the Software without restriction, including without limitation the rights to +-- use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies +-- of the Software, and to permit persons to whom the Software is furnished to do +-- so, subject to the following conditions: +-- +-- The above copyright notice and this permission notice shall be included in all +-- copies or substantial portions of the Software. +-- +-- THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +-- IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +-- FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +-- AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +-- LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +-- OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE +-- SOFTWARE. + local plenary_dir = os.getenv("PLENARY_DIR") or "/tmp/plenary.nvim" local is_not_a_directory = vim.fn.isdirectory(plenary_dir) == 0 if is_not_a_directory then