diff --git a/README.md b/README.md index 08cb3c3..6bc96ae 100644 --- a/README.md +++ b/README.md @@ -1,47 +1,78 @@ # Devcontainer CLI (Nvim Plugin) -Develop your next Repo in a Devcontainer using Nvim thanks to the [Devconatiner CLI](https://github.com/devcontainers/cli) and this plugin +Develop your next Repo in a Devcontainer using Nvim thanks to the +[Devconatiner CLI](https://github.com/devcontainers/cli) and this plugin ![](doc/gifs/nvim_devcontainer_cli-description.gif) -As you can see in the GIF above, [alacritty](https://github.com/alacritty/alacritty) is being used as a Terminal Emulator. Any of the ones recommended [here](https://www.lazyvim.org/) would work. In case you are struggling configuring the nerd fonts, I personally recommend this great [youtube video](https://www.youtube.com/watch?v=mQdB_kHyZn8&t=182s). +As you can see in the GIF above, +[alacritty](https://github.com/alacritty/alacritty) is being used as a Terminal +Emulator. Any of the ones recommended [here](https://www.lazyvim.org/) would +work. For dotfiles setup I would recommend looking at the `devcontainer` branch +of [my dotfiles](https://github.com/erichlf/dotfiles). The `install.sh` script is +quite simple, but should be very informative. --- -First, which problem is this plugin trying to solve? +First, what problem is this plugin trying to solve? **Situation:** -Your favorite editor is **nvim** and you are currently developing a containerized application (using Docker). +Your favorite editor is **nvim** and you are currently developing a +containerized application (using Docker). **Problem:** -You can definitely use nvim for developing your code, but you quickly face problems with the [LSP](https://microsoft.github.io/language-server-protocol/) and the [DAP](https://microsoft.github.io/debug-adapter-protocol/) (among other plugins), because such plugins do not have access inside the Docker container. True, you can install **nvim** together with all your plugins inside the docker container extending the image. However, this can be cumbersome, and ultimately if you are working in a team, chances are you are the only one is using **nvim**. Also, you do not want to modify your own Docker Target inside your Dockerfile, installing **nvim** etc. +Your team is using a devcontainer (or a docker container) and you want to still +use **nvim** with [LSP](https://microsoft.github.io/language-server-protocol/) +and [DAP](https://microsoft.github.io/debug-adapter-protocol/) (among other +plugins), but you don't want to have to run all the cumbersome commands. **Solution:** -There are multiple IDEs out there who give you the possibility to execute themself inside the Docker container you are developing, fixing the problems above, but there is nothing which works out-of-the-box for **nvim**. Recently, Microsoft opened the code used in VSCode for attaching the IDE to such containers ([Devconatiner CLI](https://github.com/devcontainers/cli)). +There are multiple IDEs out there who give you the possibility to execute +themself inside the Docker container you are developing, fixing the problems +above, but there is nothing which works out-of-the-box for **nvim**. Recently, +Microsoft opened the command line tool, +([Devconatiner CLI](https://github.com/devcontainers/cli)), which allows developers +to run devcontainers without VScode. -The current **nvim** plugin aims to take advantage of such CLI for creating your own local development environment on the top of your containerized applications. This plugin allows you use LSP capabilities for external modules (installed inside the Docker container), and also debug your application ([DAP](https://microsoft.github.io/debug-adapter-protocol/)). +The current **nvim** plugin aims to take advantage of `devcontainer-cli` for +creating your own local development environment on top of a containerized +applications. This plugin allows you use LSP capabilities for external modules +(installed inside the Docker container), and also debug your application +([DAP](https://microsoft.github.io/debug-adapter-protocol/)). But, what is happening under the hood? -1. First, devcontainer-cli is used for setting up your devcontainer, building the image based on the instructions defined in your [devcontainer.json](.devcontainer/devcontainer.json) and initializing a container based on such image. -1. Once the container is already running, nvim installed inside the Docker container together with a set of dependencies that can be found [here](https://github.com/arnaupv/nvim-devcontainer-cli/blob/main/bin/devcontainer_setup_scripts/root_setup.sh). This step resembles the installation of the [vscode-server](https://code.visualstudio.com/docs/devcontainers/containers) inside the container when using VSCode. -1. Finally, nvim needs certain configuration to work properly. That's why the following [nvim_dotfiles_repo](https://github.com/arnaupv/dotfiles) is cloned inside the container ([here](https://github.com/arnaupv/nvim-devcontainer-cli/blob/main/bin/devcontainer_setup_scripts/none_root_setup.sh#L6)). -1. The last step is connecting inside the container. This could be done by `ssh` connection, but in this case the connection is done using `devcontainer exec` ([here](https://github.com/arnaupv/nvim-devcontainer-cli/blob/main/bin/connect_to_devcontainer.sh)). - -As you can see what the plugin does is installing and configuring neovim inside the container, instead of communicating with the info inside the container via nvim client/server. One of the negative consequences of such approach is that all plugins need to be installed each time a devcontainer session starts. This is far from being efficient and it is something that needs to be improved in the future. However, I personally consider that the current solution is good enough for starting to work with nvim inside a Docker container. +1. First, `devcontainer-cli` is used for setting up your devcontainer, building + the image based on the instructions defined in your + [devcontainer.json](.devcontainer/devcontainer.json) and initializing a + container based on such image. +2. Once the container is running, your dotfiles are installed in the docker + container together with a set of dependencies. To install any dependencies you need either the + dotfiles setup script will need to do that or you can use devcontainer features to install them. + A very nice devcontainer feature that can do this is + [apt package](https://github.com/rocker-org/devcontainer-features/tree/main/src/apt-packages). +3. The last step is connecting inside the container via `devcontainer exec` + ([here](https://github.com/erichlf/nvim-devcontainer-cli/blob/main/bin/connect_to_devcontainer.sh)). + +The main thing this plugin does is bringup your devcontainer and execute +commands via a convenient interface. It attempts to stay out of your way and +allows you to do things as you wish, but gives you the tools to do that easily. **Inspiration:** -This plugin has been inspired by the work previously done by [esensar](https://github.com/esensar/nvim-dev-container) and by [jamestthompson3](https://github.com/jamestthompson3/nvim-remote-containers). The main different is that this plugin benefits from the [Devcontainer CLI](https://github.com/devcontainers/cli) which was opensourced by Microsoft in April 2022. +This plugin has been inspired by the work previously done by +[arnaupv](https://github.com/arnaupv/nvim-devcontainer-cli), +[esensar](https://github.com/esensar/nvim-dev-container) and by +[jamestthompson3](https://github.com/jamestthompson3/nvim-remote-containers). +The main difference between this version and arnaupv is that it tries to not +make assumptions about how you work. # Dependencies -- Only Ubuntu/Debian Docker Host (tested with Ubuntu 20.04 and 22.04) - [Planning to support other OS in the future](https://github.com/arnaupv/nvim-devcontainer-cli/issues/5). - [docker](https://docs.docker.com/get-docker/) - [devcontainer-cli](https://github.com/devcontainers/cli#npm-install) -- [jq](https://jqlang.github.io/jq/download/) # 🔧 Installation @@ -49,7 +80,7 @@ This plugin has been inspired by the work previously done by [esensar](https://g ```lua { - "arnaupv/nvim-devcontainer-cli", + "erichlf/nvim-devcontainer-cli", opts = { -- whather to verify that the final devcontainer should be run interactive = false, @@ -60,26 +91,39 @@ This plugin has been inspired by the work previously done by [esensar](https://g remove_existing_container = true, -- By default, if no extra config is added, following nvim_dotfiles are -- installed: "https://github.com/LazyVim/starter" - -- This is an example for configuring other nvim_dotfiles inside the docker container - nvim_dotfiles_repo = "https://github.com/arnaupv/dotfiles.git", - nvim_dotfiles_branch = "main", -- branch to clone from nvim_dotfiles_repo - nvim_dotfiles_install_command = "cd ~/nvim_dotfiles/ && ./install.sh", - -- In case you want to change the way the devenvironment is setup, you can also provide your own setup - setup_environment_repo = "https://github.com/arnaupv/setup-environment", - setup_environment_install_command = "./install.sh -p 'nvim stow zsh'", + -- This is an example for configuring other dotfiles inside the docker container + dotfiles_repository = "https://github.com/erichlf/dotfiles.git", + dotfiles_branch = "main", -- branch to clone from dotfiles_repository` + dotfiles_targetPath = "~/dotfiles", -- location to install dotfiles + dotfiles_intallCommand = "install.sh", -- script to run after dotfiles are cloned }, keys = { -- stylua: ignore { - "cdu", + "Du", ":DevcontainerUp", - desc = "Up the DevContainer", + desc = "Bring up the DevContainer", }, { - "cdc", + "Dc", ":DevcontainerConnect", desc = "Connect to DevContainer", }, + { + "De", + ":DevcontainerExec", + desc = "Execute a command in DevContainer", + }, + { + "Db", + ":DevcontainerExec cd build && make", + desc = "Execute build command in DevContainer", + }, + { + "Dt", + ":DevcontainerExec cd build && make test", + desc = "Execute test command in DevContainer", + }, } }, ``` @@ -88,29 +132,41 @@ The default_config can be found [here](./lua/devcontainer_cli/config/init.lua). # How to use? -There are 2 commands: `:DevcontainerUp` and `:DevcontainerConnect`. - -1. First you need to have your folder with the devcontainer instructions. This folder is usually called `.devcontainer` and contains a `devcontainer.json` file. This file is used by the [Devcontainer CLI](https://github.com/devcontainers/cli). As a first approach you can copy-paste the [.devcontainer](.devcontainer/devcontainer.json) folder of the current project and adapt it for your repo. You can also find more information about the `devcontainer.json` file [here](https://code.visualstudio.com/docs/remote/devcontainerjson-reference). - -1. Then open a nvim session and execute the first command: `DevcontainerUp`, which will create the image based on your Dockerfile. Once created it will initialize a container with the previously created image, adding nvim and other tools defined in ./bin/devcontainer_setup_scripts/ . Currently the following [dotfiles](https://github.com/arnaupv/dotfiles) are hardcoded [here](./bin/devcontainer_setup_scripts/none_root_setup.sh). The new devcontainer running can be easily checked with the following command: `docker ps -a`. - -1. If the process above finishes successfully, you are prepared for closing the current nvim session and open a new nvim inside the docker container. All this can be done from nvim itself, using the second command: `:DevcontainerConnect`. - -1. As an example, you can try to create the first devcontainer using cloning the current repository, following the instructions above. +There are 3 main commands: `:DevcontainerUp`, `:DevcontainerExec`, and `:DevcontainerConnect`. + +1. First, you should be in the main direcotry or subdirectory of your project + container you `.devcontainer` directory. This file is used by the + [Devcontainer CLI](https://github.com/devcontainers/cli). As a first + approach you can copy-paste the + [.devcontainer](.devcontainer/devcontainer.json) folder of the current + project and adapt it for your repo. You can also find more information about + the `devcontainer.json` file + [here](https://code.visualstudio.com/docs/remote/devcontainerjson-reference). +2. Then open a **nvim** session and execute the first command: + `DevcontainerUp`, which will create the image based on your + `.devcontainer\devcontainer.json`. Once created it will initialize a + container with the previously created image, and then clone your dotfiles, + and finally run the specified setup script. The new devcontainer running can + be easily checked with the following command: `docker ps -a`. +3. If the process above finishes successfully, you can choose to close the + current **nvim** session and open a new session within the devcontainer via + the command: `:DevcontainerConnect`. Alternatively, you could choose to + continue working in your current session and run commands in the + devcontainer via `DevcontainerExec`. # Tests Tests are executed automatically on each PR using Github Actions. -In case you want to run Github Actions locally, it is recommended to use [act](https://github.com/nektos/act#installation). -And then execute: +In case you want to run Github Actions locally, it is recommended to use +[act](https://github.com/nektos/act#installation). And then execute: ```bash act -W .github/workflows/default.yml ``` -Another option would be to connect to the devcontainer following the **How to use?** section. -Once connected to the devcontainer, execute: +Another option would be to connect to the devcontainer following the **How to +use?** section. Once connected to the devcontainer, execute: ```bash make test @@ -119,12 +175,9 @@ make test # FEATUREs (in order of priority) 1. [x] Capability to create and run a devcontainer using the [Devconatiner CLI](https://github.com/devcontainers/cli). -1. [x] Capability to attach in a running devcontainer -1. [x] The floating window created during the devcontainer Up process (:DevcontainerUp) is closed when the process finishes successfully. -1. [x] [Give the possibility of defining custom dotfiles when setting up the devcontainer](https://github.com/arnaupv/nvim-devcontainer-cli/issues/1) -1. [x] [Detect the cause/s of the UI issues of neovim when running inside the docker container.](https://github.com/arnaupv/nvim-devcontainer-cli/issues/15) -1. [ ] Add unit tests using plenary.busted lua module. -1. [ ] The logs printed in the floating window when preparing the Devcontainer are saved and easy to access. - -1. [ ] Convert bash scripts in lua code. -1. [ ] Create .devcontainer/devcontainer.json template automatically via a nvim command. Add examples for when the devcontainer is created from docker and also from docker-compose. +2. [x] Capability to attach in a running devcontainer. +3. [x] The floating window created during the devcontainer Up process (:DevcontainerUp) is closed when the process finishes successfully. +4. [x] [Give the possibility of defining custom dotfiles when setting up the devcontainer](https://github.com/erichlf/nvim-devcontainer-cli/issues/1) +5. [ ] Add unit tests using plenary.busted lua module. +6. [ ] The logs printed in the floating window when preparing the Devcontainer are saved and easy to access. +7. [ ] Convert bash scripts in lua code. diff --git a/doc/devcontainer_cli.txt b/doc/devcontainer_cli.txt index 0debc7c..a04f201 100644 --- a/doc/devcontainer_cli.txt +++ b/doc/devcontainer_cli.txt @@ -8,13 +8,15 @@ developing docker containers. Development is in progress, but the plugin can already be used. To find out more: -https://github.com/arnaupv/nvim-devcontainer-cli +https://github.com/erichlf/nvim-devcontainer-cli :h DevcontainerUp DevcontainerUp *DevcontainerUp* - Spawns a docker devcontainer, installing neovim and all the required - dependencies for developing your code inside the docker container. + Spawns a devcontainer, installing dotfiles in the docker container. + +DevcontainerExec *DevcontainerExec* + Runs a given command in the projects devcontainer. DevcontainerConnect *DevcontainerConnect* Closes the nvim sessions (all sessions fromt the terminal) and opens a new diff --git a/lua/devcontainer_cli/config/init.lua b/lua/devcontainer_cli/config/init.lua index f0b1bfa..1d12663 100644 --- a/lua/devcontainer_cli/config/init.lua +++ b/lua/devcontainer_cli/config/init.lua @@ -11,20 +11,15 @@ local default_config = { -- If set to True [default_value] it can take extra time as you force to start from scratch remove_existing_container = true, -- dependencies that have to be installed in the devcontainer (remoteUser = root) - setup_environment_repo = "https://github.com/arnaupv/setup-environment", + 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", -- directory for the setup environment - setup_environment_directory = "setup_dotfiles", + dotfiles_targetPath = "~/dotfiles", -- command that's executed for installed the dependencies from the setup_environment_repo - setup_environment_install_command = "install.sh", - - -- nvim_dotfiles that will be installed inside the docker devcontainer through the devcontainer cli. - nvim_dotfiles_repo = "https://github.com/LazyVim/starter", - -- branch to use for the nvim_dotfiles - nvim_dotfiles_branch = "main", - -- directory where to put the nvim_dotfiles - nvim_dotfiles_directory = "nvim_dotfiles", - -- nvim_dotfiles_install is the command that needs to be executed to install the dotfiles (it can be any bash command) - nvim_dotfiles_install_command = "mkdir -p ~/.config && mv ~/nvim_dotfiles ~/.config/nvim", + dotfiles_installCommand = "install.sh", + -- The number of columns to wrap text at + terminal_columns = 80, } local options diff --git a/lua/devcontainer_cli/devcontainer_cli.lua b/lua/devcontainer_cli/devcontainer_cli.lua index 2cadd54..a262781 100644 --- a/lua/devcontainer_cli/devcontainer_cli.lua +++ b/lua/devcontainer_cli/devcontainer_cli.lua @@ -1,5 +1,4 @@ local config = require("devcontainer_cli.config") -local folder_utils = require("devcontainer_cli.folder_utils") local devcontainer_utils = require("devcontainer_cli.devcontainer_utils") local M = {} @@ -20,19 +19,22 @@ local function define_autocommands() }) end -function M.up() - -- bringup the devcontainer - devcontainer_parent = folder_utils.get_root(config.toplevel) - if devcontainer_parent == nil then - prev_win = vim.api.nvim_get_current_win() - vim.notify( - "Devcontainer folder not available. devconatiner_cli_plugin plugin cannot be used", - vim.log.levels.ERROR - ) - return +-- executes a given command in the devcontainer of the current project directory +-- @param opts options for executing the command +function M.exec(opts) + cwd = vim.loop.cwd() + + vim.validate({ args = { opts.args, "string" } }) + if opts.args == nil or opts.args == "" then + devcontainer_utils.exec(cwd) + else + devcontainer_utils.exec_cmd(opts.args, cwd) end +end - devcontainer_utils.bringup(devcontainer_parent) +-- bring up the devcontainer in the current project directory +function M.up() + devcontainer_utils.bringup(vim.loop.cwd()) end function M.connect() diff --git a/lua/devcontainer_cli/devcontainer_utils.lua b/lua/devcontainer_cli/devcontainer_utils.lua index b882d29..3371b15 100644 --- a/lua/devcontainer_cli/devcontainer_utils.lua +++ b/lua/devcontainer_cli/devcontainer_utils.lua @@ -4,6 +4,7 @@ local folder_utils = require("devcontainer_cli.folder_utils") local M = {} +-- window management variables local prev_win = -1 local win = -1 local buffer = -1 @@ -15,6 +16,8 @@ local on_detach = function() buffer = -1 end +-- on_fail callback +-- @param exit_code the exit code from the failed job local on_fail = function(exit_code) vim.notify( "Devcontainer process has failed! exit_code: " .. exit_code, @@ -28,7 +31,11 @@ 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 +-- on_exit callback function to delete the open buffer when devcontainer exits +-- in a neovim terminal +-- @param job_id the id of the running job +-- @param code the exit code +-- @param event thrown by job local on_exit = function(job_id, code, event) if code == 0 then on_success() @@ -39,6 +46,7 @@ local on_exit = function(job_id, code, event) end --- execute command +-- @param cmd the command to execute in the devcontainer terminal local function exec_command(cmd) vim.fn.termopen( cmd, @@ -57,142 +65,131 @@ local function exec_command(cmd) vim.api.nvim_set_current_buf(buffer) end --- helper function for determine devcontainer specifics -local function get_devcontainer_data(devcontainer_root) - local read_devcontainer - = "devcontainer read-configuration --include-merged-configuration --workspace-folder " .. devcontainer_root - remote_user_cmd = read_devcontainer .. " | jq '.configuration.remoteUser' | tr -d '" .. '"' .. "'" - workspace_cmd = read_devcontainer .. " | jq '.workspace.workspaceFolder' | tr -d '" .. '"' .. "'" - - devcontainer_data = { - remote_user = nil, - workspace = nil, - } +-- create a new window and execute the given command +-- @param cmd the command to execute in the devcontainer terminal +function spawn_and_execute(cmd) + prev_win = vim.api.nvim_get_current_win() + win, buffer = windows_utils.open_floating_window() + exec_command(cmd) +end - stderr = "" - - job1_id = vim.fn.jobstart( - remote_user_cmd, - { - stdout_buffered = true, - on_stdout = function(_, data) - devcontainer_data.remote_user = data[1] - if devcontainer_data.remote_user == " " or devcontainer_data.remote_user == nil then - vim.notify( - "remote_user: " .. remote_user_cmd .. " " .. " " .. vim.inspect(data), - vim.log.levels.ERROR - ) - devcontainer_data.remote_user = nil - end - vim.notify( - "remote user: " .. remote_user_cmd .. " " .. " " .. devcontainer_data.remote_user .. " " .. vim.inspect(data), - vim.log.levels.WARN - ) - end - } - ) +-- build the initial part of a devcontainer command +-- @param action the action for the devcontainer to perform +-- (see man devcontainer) +-- @param cwd the current working directory. Used as a starting place to find +-- .devcontainer directory +-- @return nil if no devcontainer_parent could be found otherwise +-- the basic devcontainer command for the given type +local function devcontainer_command(action, cwd) + devcontainer_root = folder_utils.get_root(cwd) + if devcontainer_root == nil then + vim.notify("Unable to find devcontainer directory...", vim.log.levels.ERROR) + return nil + end - job2_id = vim.fn.jobstart( - workspace_cmd, - { - stdout_buffered = true, - on_stdout = function(_, data) - devcontainer_data.workspace = data[1] - if devcontainer_data.workspace == " " or devcontainer_data.workspace == nil then - vim.notify( - "workspace: " .. workspace_cmd .. " " .. " " .. vim.inspect(data), - vim.log.levels.ERROR - ) - devcontainer_data.workspace = nil - end - vim.notify( - "workspace: " .. remote_user_cmd .. " " .. " " .. devcontainer_data.workspace .. " " .. vim.inspect(data), - vim.log.levels.WARN - ) - end - } - ) - vim.fn.jobwait({job1_id, job2_id}) + local command = "devcontainer " .. action + command = command .. " --workspace-folder '" .. devcontainer_root .. "'" - return devcontainer_data + return command end -- helper function to generate devcontainer bringup command -local function get_devcontainer_up_cmd(devcontainer_parent) - local devcontainer_data = get_devcontainer_data(devcontainer_parent) - - if devcontainer_data.remote_user == nil or devcontainer_data.workspace == nil then - vim.notify( - "Failed to obtain remote user or remote workspace from devcontainer.", - vim.log.levels.ERROR - ) - - return nil +-- @param cwd the current working directory. Used as a starting place to find +-- .devcontainer directory +-- @return nil if no devcontainer_parent could be found otherwise the +-- devcontainer bringup command +local function get_devcontainer_up_cmd(cwd) + local command = devcontainer_command("up", cwd) + if command == nil then + return command end - - local remote_home = "/home/" .. devcontainer_data.remote_user .. "/" - local command = "devcontainer up " if config.remove_existing_container then - command = command .. "--remove-existing-container" + command = command .. " --remove-existing-container" end + command = command .. " --update-remote-user-uid-default off" - -- check the install command for "-" and escape them - install_command = config.setup_environment_install_command -- :gsub("-", "\\-") + if config.dotfiles_repository == "" or config.dotfiles_repository == nil then + return command + end - command = command .. " --dotfiles-repository '" .. config.setup_environment_repo .. "'" - command = command .. " --dotfiles-target-path '" .. remote_home .. config.setup_environment_directory .. "'" - command = command .. " --dotfiles-install-command '" .. install_command .. "'" - command = command .. " --workspace-folder '" .. devcontainer_parent .. "'" - command = command .. " --update-remote-user-uid-default off" + command = command .. " --dotfiles-repository '" .. config.dotfiles_repository + -- only include the branch if it exists + if config.dotfiles_branch ~= "" and config.dotfiles_branch ~= nil then + command = command .. " -b " .. config.dotfiles_branch + end + command = command .. "'" - if config.nvim_dotfiles_repo ~= "" then - command = command .. " && devcontainer exec --workspace-folder " .. devcontainer_parent - command = command .. ' ' .. "sh -c " ..'"' .. "git clone -b " .. config.nvim_dotfiles_branch - command = command .. " " .. config.nvim_dotfiles_repo .. " '" .. remote_home .. config.nvim_dotfiles_directory .. "'" - command = command .. " && cd '" .. remote_home .. config.nvim_dotfiles_directory .. "'" - command = command .. " && " .. config.nvim_dotfiles_install_command .. '"' + if config.dotfiles_targetPath ~= "" and config.dotfiles_targetPath ~= nil then + command = command .. " --dotfiles-target-path '" .. config.dotfiles_targetPath .. "'" + end + + if config.dotfiles_install_command ~= "" and config.dotfiles_install_command ~= nil then + command = command .. " --dotfiles-install-command '" .. config.dotfiles_install_command .. "'" end return command end -- issues command to bringup devcontainer -function M.bringup(devcontainer_parent) - local command = get_devcontainer_up_cmd(devcontainer_parent) +-- @param cwd the current working directory. Used as a starting place to find +-- .devcontainer directory +function M.bringup(cwd) + local command = get_devcontainer_up_cmd(cwd) if command == nil then - prev_win = vim.api.nvim_get_current_win() - return end - local message = windows_utils.wrap_text( - "Devcontainer folder detected. Path: " .. devcontainer_parent .. "\n" .. - "Spawning devcontainer with command: " .. command, - 80 - ) - if config.interactive then vim.ui.input( - message .. "\n\n" .. - "Press q to cancel or any other key to continue\n", + {prompt=windows_utils.wrap_text( + "Spawning devcontainer with command: " .. command + ) .. "\n\n" .. "Press q to cancel or any other key to continue\n" + }, function(input) if (input == "q" or input == "Q") then vim.notify( "\nUser cancelled bringing up devcontainer" ) else - win, buffer = windows_utils.open_floating_window() - exec_command(command) + spawn_and_execute(command) end end ) - else - vim.notify(message) - win, buffer = windows_utils.open_floating_window(on_detach) - exec_command(command) + return end + + spawn_and_execute(command) +end + +-- execute the given cmd within the given devcontainer_parent +-- @param cmd the command to issue in the devcontainer terminal +-- @param cwd the current working directory. Used as a starting place to find +-- .devcontainer directory +function M.exec_cmd(cmd, cwd) + command = devcontainer_command("exec", cwd) + if command == nil then + return + end + + command = command .. " " .. cmd + spawn_and_execute(command) +end + +-- execute a given cmd within the given devcontainer_parent +-- @param cwd the current working directory. Used as a starting place to find +-- .devcontainer directory +-- @param devcontainer_parent the location guess for .devcontainer directory +function M.exec(cwd) + vim.ui.input( + {prompt="Enter command:"}, + function(input) + if input ~= nil then + M.exec_cmd(input, devcontainer_parent) + end + end + ) end return M diff --git a/lua/devcontainer_cli/folder_utils.lua b/lua/devcontainer_cli/folder_utils.lua index 1a80b35..fab6eee 100644 --- a/lua/devcontainer_cli/folder_utils.lua +++ b/lua/devcontainer_cli/folder_utils.lua @@ -5,7 +5,9 @@ local function directory_exists(target_folder) return (vim.fn.isdirectory(target_folder) == 1) end --- return directory if a devcontainer exists within it or nil otherwise +-- get the devcontainer path for the given directory +-- @param directory the directory containing .devcontainer +-- @return directory if a devcontainer exists within it or nil otherwise local function get_devcontainer_parent(directory) local devcontainer_directory = directory .. '/.devcontainer' @@ -16,8 +18,12 @@ local function get_devcontainer_parent(directory) return nil end --- return the devcontainer directory closes to the root directory --- or the first if toplevel is true +-- get the root directory the devcontainer given a directory +-- @param directory to begin search in +-- @param toplevel flag indicating if the directory closes to root should be +-- returned +-- @return 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 parent_directory = vim.fn.fnamemodify(directory, ':h') local devcontainer_parent = get_devcontainer_parent(directory) @@ -41,8 +47,12 @@ local function get_root_directory(directory, toplevel) return upper_devcontainer_directory end --- find the .devcontainer directory closes to the root --- upward from the current directory +-- find the .devcontainer directory closes to the root upward from the current +-- directory +-- @param toplevel flag indicating if the directory closes to root should be +-- returned +-- @return the devcontainer directory closest to the root directory +-- 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) diff --git a/lua/devcontainer_cli/init.lua b/lua/devcontainer_cli/init.lua index 90a7d88..d0a2171 100644 --- a/lua/devcontainer_cli/init.lua +++ b/lua/devcontainer_cli/init.lua @@ -4,6 +4,8 @@ local devcontainer_cli = require("devcontainer_cli.devcontainer_cli") local config = require("devcontainer_cli.config") local configured = false +-- setup the devcontainer-cli plugin +-- @param opts the options to set (see config/init.lua) function M.setup(opts) config.setup(opts) @@ -15,20 +17,32 @@ function M.setup(opts) configured = true -- Docker - vim.api.nvim_create_user_command("DevcontainerUp", function(_) - -- Try to use opts.args and if empty use "pro" - devcontainer_cli.up() - end, { - nargs = 0, - desc = "Up devcontainer using .devcontainer/devcontainer.json", - }) - - vim.api.nvim_create_user_command("DevcontainerConnect", function(_) - devcontainer_cli.connect() - end, { - nargs = 0, - desc = "Connect to devcontainer using .devcontainer.json", - }) + vim.api.nvim_create_user_command( + "DevcontainerUp", + devcontainer_cli.up, + { + nargs = 0, + desc = "Bringup devcontainer.", + } + ) + + vim.api.nvim_create_user_command( + "DevcontainerExec", + devcontainer_cli.exec, + { + nargs = "?", + desc = "Execute command in devcontainer.", + } + ) + + vim.api.nvim_create_user_command( + "DevcontainerConnect", + devcontainer_cli.connect, + { + nargs = 0, + desc = "Connect to devcontainer.", + } + ) end return M diff --git a/lua/devcontainer_cli/windows_utils.lua b/lua/devcontainer_cli/windows_utils.lua index bb30b5f..3689dfc 100644 --- a/lua/devcontainer_cli/windows_utils.lua +++ b/lua/devcontainer_cli/windows_utils.lua @@ -1,11 +1,19 @@ +local config = require("devcontainer_cli.config") + local M = {} -function M.wrap_text(text, max_width) +-- number of columns for displaying text +local terminal_columns = config.terminal_columns + +-- wrap the given text at max_width +-- @param text the text to wrap +-- @return 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 <= max_width then + if #current_line + #word <= terminal_columns then current_line = current_line .. word .. " " else table.insert(wrapped_lines, current_line) @@ -17,6 +25,9 @@ function M.wrap_text(text, max_width) return table.concat(wrapped_lines, "\n") end +-- create a floating window +-- @param on_detach call back for when the window is detached +-- @return 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') @@ -24,7 +35,9 @@ function M.open_floating_window(on_detach) 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(80, vim.o.columns - 20))) + 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 @@ -50,8 +63,11 @@ function M.open_floating_window(on_detach) return win, buf end +-- send text to the given buffer +-- @param text the text to send +-- @param buffer the buffer to send text to function M.send_text(text, buffer) - local text = vim.split(wrap_text(text, 80), "\n") + local text = vim.split(wrap_text(text), "\n") -- Set the content of the buffer vim.api.nvim_buf_set_lines(buffer, 0, -1, false, text)