diff --git a/bin/connect_to_devcontainer.sh b/bin/connect_to_devcontainer.sh deleted file mode 100755 index ee8cc48..0000000 --- a/bin/connect_to_devcontainer.sh +++ /dev/null @@ -1,42 +0,0 @@ -#!/bin/bash - -set -e -# The following code creates a new terminal (using gnome-terminal) and it -# connects to the container using the devcontainer built in command. -# TODO: Add a check to see if the container is running. -# TODO: Give the possibility of using multiple terminals, not only gnome-terminal -# TODO: Give the possibility of opening a new tmux page instead of opening a new container - -# Get the folder of the current file -function get_script_dir { - # SOURCE: https://stackoverflow.com/a/246128/10491337 - local SOURCE="${BASH_SOURCE[0]}" - local DIR="" - while [ -h "${SOURCE}" ]; do - DIR="$(cd -P "$(dirname "${SOURCE}")" >/dev/null 2>&1 && pwd)" - SOURCE="$(readlink "${SOURCE}")" - [[ "${SOURCE}" != /* ]] && SOURCE="${DIR}/${SOURCE}" - done - cd -P "$(dirname "${SOURCE}")" >/dev/null 2>&1 && pwd -} - -SCRIPT_DIR=$(get_script_dir) -# Execute the command for opening the devcontainer in the following terminal: -if [ -x "$(command -v alacritty)" ]; then - # ALACRITTY TERMINAL EMULATOR - REPOSTORY_NAME=$(basename "$(pwd)") - TERMINAL_TITLE="Devcontainer [${REPOSTORY_NAME}] - You are inside a Docker Container now...!" - alacritty --working-directory . --title "${TERMINAL_TITLE}" -e "${SCRIPT_DIR}"/open_shell_in_devcontainer.sh & -elif [ -x "$(command -v gnome-terminal)" ]; then - # GNOME TERMINAL - gnome-terminal -- bash -c "${SCRIPT_DIR}"/open_shell_in_devcontainer.sh -elif [ "$(uname)" == "Darwin" ] && [ -x "$(command -v iTerm)" ]; then - # MAC ITERM2 TERMINAL EMULATOR - open -a iTerm.app "${SCRIPT_DIR}"/open_shell_in_devcontainer.sh -elif [ "$(uname)" == "Darwin" ] && [ -x "$(command -v Terminal)" ]; then - # MAC TERMINAL - open -a Terminal.app "${SCRIPT_DIR}"/open_shell_in_devcontainer.sh -else - # TERMINAL NO DEFINED - echo "ERROR: No compatible emulators found!" -fi diff --git a/bin/open_shell_in_devcontainer.sh b/bin/open_shell_in_devcontainer.sh deleted file mode 100755 index 99304ea..0000000 --- a/bin/open_shell_in_devcontainer.sh +++ /dev/null @@ -1,34 +0,0 @@ -#!/bin/bash -# This executable file has been isolated from ./bin/connect_to_devcontainer.sh so it -# can be executed in MAC Terminals (Termina.app/iTerm.app) - -tmux-split-cmd() (tmux split-window -h -t "$TMUX_PANE" "bash --rcfile <(echo '. ~/.bashrc;$*')") -tmux-new-cmd() (tmux new-window -n Devcontainer "bash --rcfile <(echo '. ~/.bashrc;$*')") - -set -e - -WORKSPACE="$(pwd)" -SHELL="zsh" - -workspace_folder=$(devcontainer read-configuration --include-merged-configuration --log-format json --workspace-folder . 2>/dev/null | jq .workspace.workspaceFolder | sed 's/"//g') -docker_id=$(docker ps -q -a --filter label=devcontainer.local_folder="${WORKSPACE}" --filter label=devcontainer.config_file="${WORKSPACE}"/.devcontainer/devcontainer.json) - -open_shell_in_devcontainer_command="docker exec -it ${docker_id} bash -c \"cd ${workspace_folder} && ${SHELL}\"" - -# Check if we are inside a tmux session -# If so, create a new pane and execute the open_shell_in_devcontainer_command -# If not, just execute the open_shell_in_devcontainer_command - -if [ -n "$TMUX" ]; then - # Replace by tmux-split-cmd if you want to split the current pane horizontally - tmux-new-cmd "${open_shell_in_devcontainer_command}" -else - eval "${open_shell_in_devcontainer_command}" -fi -# docker exec -it "${docker_id}" ${SHELL} - -# TODO: It would be great to use th devcontainer exec command as long as we know how to fix the visualization issue inside the docker container! -# If you want to give a try, just comment the docker exec -it ... above and uncommend the line below. Then execute the :DevcontainerConnect and open vim. -# There you will see visualzation issues which do not occur when connectint to docker via docker exec - -# devcontainer exec --workspace-folder ${WORKSPACE} ${SHELL} diff --git a/lua/devcontainer-cli/config/init.lua b/lua/devcontainer-cli/config.lua similarity index 92% rename from lua/devcontainer-cli/config/init.lua rename to lua/devcontainer-cli/config.lua index f9381fe..269121b 100644 --- a/lua/devcontainer-cli/config/init.lua +++ b/lua/devcontainer-cli/config.lua @@ -35,7 +35,7 @@ local default_config = { dotfiles_repository = "git@github.com:erichlf/dotfiles", -- branch to checkout for repositories (this is a feature not supported by -- devcontainers in general, but we do) - dotfiles_repository = "devcontainer", + dotfiles_branch = "main", -- directory for the setup environment dotfiles_targetPath = "~/dotfiles", -- command that's executed for installed the dependencies from the @@ -43,9 +43,14 @@ local default_config = { dotfiles_installCommand = "install.sh", -- The number of columns to wrap text at terminal_columns = 80, + -- The particular binary to use for connecting to in the devcontainer + -- Most likely this should remain nvim + nvim_binary = "nvim", -- The shell to use for executing command. Available sh, bash, zsh or any -- other that uses '-c' to signify a command is to follow shell = 'bash', + -- The name of the socket file to use + port = "7777", } local options diff --git a/lua/devcontainer-cli/devcontainer_cli.lua b/lua/devcontainer-cli/devcontainer_cli.lua index bf0ecd9..fddbee4 100644 --- a/lua/devcontainer-cli/devcontainer_cli.lua +++ b/lua/devcontainer-cli/devcontainer_cli.lua @@ -1,15 +1,15 @@ -- 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 @@ -17,30 +17,11 @@ -- 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 utils = require("devcontainer-cli.devcontainer_utils") +local terminal = require("devcontainer-cli.terminal") local M = {} -local function define_autocommands() - local au_id = vim.api.nvim_create_augroup("devcontainer.docker.terminal", {}) - vim.api.nvim_create_autocmd("UILeave", { - group = au_id, - callback = function() - -- 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 - ) - end, - }) -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) @@ -71,7 +52,7 @@ end -- toggle the current devcontainer window function M.toggle() - utils.toggle() + terminal.toggle() end -- bring up the devcontainer in the current project directory @@ -82,7 +63,11 @@ end -- Thanks to the autocommand executed after leaving the UI, after closing the -- neovim window the devcontainer will be automatically open in a new terminal function M.connect() - define_autocommands() + if not utils.create_connect_cmd() then + vim.notify("Failed to create autocommand", vim.log.levels.ERROR) + return + end + vim.cmd("wqa") end diff --git a/lua/devcontainer-cli/devcontainer_utils.lua b/lua/devcontainer-cli/devcontainer_utils.lua index 15c74c0..916f9c1 100644 --- a/lua/devcontainer-cli/devcontainer_utils.lua +++ b/lua/devcontainer-cli/devcontainer_utils.lua @@ -1,15 +1,15 @@ -- 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 @@ -18,26 +18,11 @@ -- OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE -- SOFTWARE. -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 config = require("devcontainer-cli.config") +local folder_utils = require("devcontainer-cli.folder_utils") +local terminal = require("devcontainer-cli.terminal") -local M = {} - --- valid window directions -local directions = { - "float", - "horizontal", - "tab", - "vertical", -} - --- window management variables -local _terminal = nil --- --- number of columns for displaying text -local terminal_columns = config.terminal_columns +local M = {} -- wrap the given text at max_width ---@param text (string) the text to wrap @@ -47,7 +32,7 @@ local function _wrap_text(text) for line in text:gmatch("[^\n]+") do local current_line = "" for word in line:gmatch("%S+") do - if #current_line + #word <= terminal_columns then + if #current_line + #word <= terminal.columns then current_line = current_line .. word .. " " else table.insert(wrapped_lines, current_line) @@ -59,49 +44,6 @@ local function _wrap_text(text) return table.concat(wrapped_lines, "\n") end --- window the created window detaches set things back to -1 -local _on_detach = function() - _terminal = nil -end - --- on_fail callback ----@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 - ) - - vim.cmd("silent! :checktime") -end - -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 (number) the exit code -local _on_exit = function(code) - if code == 0 then - _on_success() - return - end - - _on_fail(code) -end - --- 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 - ---@class ParsedArgs ---@field direction string? ---@field cmd string? @@ -200,57 +142,6 @@ 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() @@ -272,14 +163,14 @@ function M.bringup() "\nUser cancelled bringing up devcontainer" ) else - _spawn_and_execute(command) + terminal.spawn(command) end end ) return end - _spawn_and_execute(command) + terminal.spawn(command) end -- execute the given cmd within the given devcontainer_parent @@ -294,7 +185,7 @@ function M._exec_cmd(cmd, direction, size) command = command .. " " .. config.shell .. " -c '" .. cmd .. "'" vim.notify(command) - _spawn_and_execute(command, direction, size) + terminal.spawn(command, direction, size) end -- execute a given cmd within the given devcontainer_parent @@ -303,7 +194,7 @@ end -- (left, right, bottom, float) ---@param size (number|nil) size of the window to create function M.exec(cmd, direction, size) - if _terminal ~= nil then + if terminal.is_open() then vim.notify("There is already a devcontainer process running.", vim.log.levels.WARN) return end @@ -324,13 +215,48 @@ function M.exec(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 +-- create the necessary functions needed to connect to nvim in a devcontainer +function M.create_connect_cmd() + local au_id = vim.api.nvim_create_augroup("devcontainer-cli.connect", {}) + local dev_command = _devcontainer_command("exec") + if dev_command == nil then + return false end - _terminal:toggle() + dev_command = dev_command .. " " .. config.nvim_binary + + vim.api.nvim_create_autocmd( + "UILeave", + { + group = au_id, + callback = + function() + local connect_command = {} + if vim.env.TMUX ~= "" then + connect_command = { "tmux split-window -h -t \"$TMUX_PANE\"" } + elseif vim.fn.executable("allacrity") == 1 then + connect_command = { "alacritty --working-directory . --title \"Devcontainer\" -e" } + elseif vim.fn.executable("gnome-terminal") == 1 then + connect_command = { "gnome-terminal --" } + elseif vim.fn.executable("iTerm.app") == 1 then + connect_command = { "iTerm.app" } + elseif vim.fn.executable("Terminal.app") == 1 then + connect_command = { "Terminal.app" } + else + vim.notify("No supported terminal emulator found.", vim.log.levels.ERROR) + end + + table.insert(connect_command, dev_command) + local command = table.concat(connect_command, " ") + vim.schedule( + function() + vim.fn.jobstart(command, { detach = true }) + end + ) + end + } + ) + + return true end return M diff --git a/lua/devcontainer-cli/init.lua b/lua/devcontainer-cli/init.lua index a5c802b..99a256f 100644 --- a/lua/devcontainer-cli/init.lua +++ b/lua/devcontainer-cli/init.lua @@ -1,15 +1,15 @@ -- 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 diff --git a/lua/devcontainer-cli/terminal.lua b/lua/devcontainer-cli/terminal.lua new file mode 100644 index 0000000..2f638ce --- /dev/null +++ b/lua/devcontainer-cli/terminal.lua @@ -0,0 +1,135 @@ +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 +M.directions = { + "float", + "horizontal", + "tab", + "vertical", +} + +-- window management variables +local _terminal = nil + +-- when the created window detaches set things back to -1 +local _on_detach = function() + _terminal = nil +end + +-- on_fail callback +---@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 + ) + + vim.cmd("silent! :checktime") +end + +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 (number) the exit code +local _on_exit = function(code) + if code == 0 then + _on_success() + return + end + + _on_fail(code) +end + +local _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 + +-- 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 + +-- number of columns for displaying text +M.columns = config.terminal_columns + +-- 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 +function M.spawn(cmd, direction, size) + direction = vim.F.if_nil(direction, "float") + if tableContains(M.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 = _on_open, + 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 + +-- check if there is already a terminal window open +---@return true if a terminal window is already open +function M.is_open() + return _terminal ~= nil +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