diff --git a/.devcontainer/devcontainer.json b/.devcontainer/devcontainer.json index 8d05b96..87c10f0 100644 --- a/.devcontainer/devcontainer.json +++ b/.devcontainer/devcontainer.json @@ -10,10 +10,16 @@ "workspaceMount": "source=${localWorkspaceFolder},target=/home/my-app/.local/share/nvim/lazy/nvim-devcontainer-cli/,type=bind", "workspaceFolder": "/home/my-app/.local/share/nvim/lazy/nvim-devcontainer-cli/", "remoteUser": "my-app", - "mounts": [], + "containerEnv": { + "DEV_WORKSPACE": "${containerWorkspaceFolder}", + }, + "mounts": [ +// unfortunately the runner for ci will fail with this mount so comment it out +// "type=bind,source=${localEnv:HOME}/.ssh,target=/home/my-app/.ssh,readonly", + ], "features": { "ghcr.io/rocker-org/devcontainer-features/apt-packages:1": { - "packages": "neovim,stow,zsh,fzf" + "packages": "neovim,stow,zsh,fzf,python3-pip" } } } diff --git a/README.md b/README.md index 8be0da7..7b7ce4a 100644 --- a/README.md +++ b/README.md @@ -2,14 +2,17 @@ Develop your next Repo in a Devcontainer using *nvim* thanks to the [Devconatiner CLI](https://github.com/devcontainers/cli) and this plugin -![devcontainer-cli in action](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. 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. +![devcontainer-cli in action](doc/gifs/devcontainer-cli-description.gif) + +As you can see in the GIF above, guake with tmux is being used. Any of the ones +recommended [here](https://www.lazyvim.org/) would work. For dotfiles setup I created +a version of my dotfiles that doesn't have any private submodules. These dotfiles +are probably more than what anyone would want, but if feel free to use them. The +one gotcha with them is that it requires the environment variable DEV_WORKSPACE +to be set. I would recommend looking at the `devcontainer-cli` branch of +[my dotfiles](https://github.com/erichlf/dotfiles). The `install.sh` script ends +up calling `script/devcontainer-cli` which is quite simple, but should get you +some pretty good ideas of how things can be setup. --- @@ -86,28 +89,6 @@ make assumptions about how you work. { "erichlf/devcontainer-cli.nvim", dependencies = { 'akinsho/toggleterm.nvim' }, - opts = { - -- whather to verify that the final devcontainer should be run - interactive = false, - -- search for the devcontainer directory closest to the root in the - -- directory tree - toplevel = true, - -- Remove existing container each time DevcontainerUp is executed - -- If set to True [default_value] it can take extra time as you force to - -- start from scratch - remove_existing_container = true, - -- By default, if no extra config is added, following nvim_dotfiles are - -- installed: "https://github.com/erichlf/dotfiles" - -- 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 - shell = "bash", -- shell to use when executing commands - -- The particular binary to use for connecting to in the devcontainer - -- Most likely this should remain nvim - nvim_binary = "nvim", - }, keys = { -- stylua: ignore { @@ -140,8 +121,33 @@ make assumptions about how you work. "DevContainerToggle", desc = "Toggle the current DevContainer Terminal" }, - } -}, + }, + init = function() + local opts = { + -- whather to verify that the final devcontainer should be run + interactive = false, + -- search for the devcontainer directory closest to the root in the + -- directory tree + toplevel = true, + -- Remove existing container each time DevcontainerUp is executed + -- If set to True [default_value] it can take extra time as you force to + -- start from scratch + remove_existing_container = true, + -- By default, if no extra config is added, following nvim_dotfiles are + -- installed: "https://github.com/erichlf/dotfiles" + -- This is an example for configuring other dotfiles inside the docker container + dotfiles_repository = "https://github.com/erichlf/dotfiles.git", + dotfiles_branch = "devcontainer-cli", -- branch to clone from dotfiles_repository` + dotfiles_targetPath = "~/dotfiles", -- location to install dotfiles + dotfiles_intallCommand = "install.sh", -- script to run after dotfiles are cloned + shell = "bash", -- shell to use when executing commands + -- The particular binary to use for connecting to in the devcontainer + -- Most likely this should remain nvim + nvim_binary = "nvim", + } + require('devcontainer-cli').setup(opts) + end, +} ``` The default_config can be found [here](./lua/devcontainer_cli/config/init.lua). @@ -204,6 +210,5 @@ make test (`: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/devcontainer-cli.nvim/issues/1) 5. [x] 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. +6. [x] Create a logger. 7. [x] Convert bash scripts in lua code. diff --git a/doc/gifs/devcontainer-cli-description.gif b/doc/gifs/devcontainer-cli-description.gif new file mode 100644 index 0000000..76b6d46 Binary files /dev/null and b/doc/gifs/devcontainer-cli-description.gif differ diff --git a/doc/gifs/nvim_devcontainer_cli-description.gif b/doc/gifs/nvim_devcontainer_cli-description.gif deleted file mode 100644 index 602a732..0000000 Binary files a/doc/gifs/nvim_devcontainer_cli-description.gif and /dev/null differ diff --git a/lua/devcontainer-cli/config.lua b/lua/devcontainer-cli/config.lua index e896f85..bd46684 100644 --- a/lua/devcontainer-cli/config.lua +++ b/lua/devcontainer-cli/config.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,6 +17,7 @@ -- 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 log = require("devcontainer-cli.log") local ConfigModule = {} local file_path = debug.getinfo(1).source:sub(2) @@ -28,17 +29,17 @@ local default_config = { -- Folder where the nvim-devcontainer-cli is installed nvim_plugin_folder = file_path:gsub("init.lua", "") .. "../../../", -- Remove existing container each time DevcontainerUp is executed - -- If set to True [default_value] it can take extra time as you force to + -- If set to True [default_value] it can take extra time as you force to -- start from scratch remove_existing_container = true, -- dotfiles to be downloaded dotfiles_repository = "git@github.com:erichlf/dotfiles", - -- branch to checkout for repositories (this is a feature not supported by + -- branch to checkout for repositories (this is a feature not supported by -- devcontainers in general, but we do) dotfiles_branch = "main", -- directory for the setup environment dotfiles_targetPath = "~/dotfiles", - -- command that's executed for installed the dependencies from the + -- command that's executed for installed the dependencies from the -- setup_environment_repo dotfiles_installCommand = "install.sh", -- The number of columns to wrap text at @@ -46,7 +47,7 @@ local default_config = { -- 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 + -- 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', } @@ -54,15 +55,19 @@ local default_config = { local options function ConfigModule.setup(opts) + log.debug("Configuring devcontainer-cli") opts = vim.tbl_deep_extend("force", default_config, opts or {}) options = opts + log.debug("Configuring devcontainer-cli complete") end -return setmetatable(ConfigModule, { - __index = function(_, key) - if options == nil then - return vim.deepcopy(default_config)[key] - end - return options[key] - end, -}) +return setmetatable(ConfigModule, + { + __index = function(_, key) + if options == nil then + return vim.deepcopy(default_config)[key] + end + return options[key] + end, + } +) diff --git a/lua/devcontainer-cli/devcontainer_cli.lua b/lua/devcontainer-cli/devcontainer_cli.lua index fddbee4..e45955b 100644 --- a/lua/devcontainer-cli/devcontainer_cli.lua +++ b/lua/devcontainer-cli/devcontainer_cli.lua @@ -17,10 +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 utils = require("devcontainer-cli.devcontainer_utils") +local utils = require("devcontainer-cli.devcontainer_utils") local terminal = require("devcontainer-cli.terminal") +local log = require("devcontainer-cli.log") -local M = {} +local M = {} -- executes a given command in the devcontainer of the current project directory ---@param opts (table) options for executing the command @@ -64,7 +65,7 @@ end -- neovim window the devcontainer will be automatically open in a new terminal function M.connect() if not utils.create_connect_cmd() then - vim.notify("Failed to create autocommand", vim.log.levels.ERROR) + log.error("Failed to create autocommand") return end diff --git a/lua/devcontainer-cli/health.lua b/lua/devcontainer-cli/health.lua index 8551047..abb8125 100644 --- a/lua/devcontainer-cli/health.lua +++ b/lua/devcontainer-cli/health.lua @@ -20,10 +20,9 @@ local M = {} -local start = vim.health.start or vim.health.report_start -local ok = vim.health.ok or vim.health.report_ok -local warn = vim.health.warn or vim.health.report_warn -local error = vim.health.error or vim.health.report_error +local start = vim.health.start or vim.health.start +local ok = vim.health.ok or vim.health.ok +local error = vim.health.error or vim.health.error local function verify_binary(binary_name) if vim.fn.executable(binary_name) ~= 1 then diff --git a/lua/devcontainer-cli/init.lua b/lua/devcontainer-cli/init.lua index 99a256f..3b52289 100644 --- a/lua/devcontainer-cli/init.lua +++ b/lua/devcontainer-cli/init.lua @@ -22,20 +22,16 @@ local M = {} local devcontainer_cli = require("devcontainer-cli.devcontainer_cli") local config = require("devcontainer-cli.config") -local configured = false +local log = require("devcontainer-cli.log") -- setup the devcontainer-cli plugin ---@param opts (table) the options to set (see config/init.lua) function M.setup(opts) - config.setup(opts) - - if configured then - print("Already configured, skipping!") - return - end - configured = true + log.debug("Setting up devcontainer-cli") + config.setup(opts) + log.debug("Creating devcontainer-cli user commands") -- Docker vim.api.nvim_create_user_command( "DevcontainerUp", @@ -72,6 +68,8 @@ function M.setup(opts) desc = "Connect to devcontainer.", } ) + + log.debug("Finished setting up devcontainer-cli") end return M diff --git a/lua/devcontainer-cli/log.lua b/lua/devcontainer-cli/log.lua new file mode 100644 index 0000000..fdda1e9 --- /dev/null +++ b/lua/devcontainer-cli/log.lua @@ -0,0 +1,156 @@ +-- log.lua +-- +-- Inspired by rxi/log.lua +-- Modified by tjdevries and can be found at github.com/tjdevries/vlog.nvim +-- +-- This library is free software; you can redistribute it and/or modify it +-- under the terms of the MIT license. See LICENSE for details. + +-- User configuration section +local default_config = { + -- Name of the plugin. Prepended to log messages + plugin = 'decontainer-cli', + + -- Should print the output to neovim while running + use_console = true, + + -- Should highlighting be used in console (using echohl) + highlights = true, + + -- Should write to a file + use_file = true, + + -- Any messages above this level will be logged. + level = "debug", + + -- Level configuration + modes = { + { name = "trace", hl = "Comment", }, + { name = "debug", hl = "Comment", }, + { name = "info", hl = "None", }, + { name = "warn", hl = "WarningMsg", }, + { name = "error", hl = "ErrorMsg", }, + { name = "fatal", hl = "ErrorMsg", }, + }, + + -- Can limit the number of decimals displayed for floats + float_precision = 0.01, +} + +-- {{{ NO NEED TO CHANGE +local log = {} + +local unpack = unpack or table.unpack + +log.new = function(config, standalone) + config = vim.tbl_deep_extend("force", default_config, config) + + local outfile = string.format('%s/%s.log', vim.fn.stdpath("cache"), config.plugin) + + local obj + if standalone then + obj = log + else + obj = {} + end + + local levels = {} + for i, v in ipairs(config.modes) do + levels[v.name] = i + end + + local round = function(x, increment) + increment = increment or 1 + x = x / increment + return (x > 0 and math.floor(x + .5) or math.ceil(x - .5)) * increment + end + + local make_string = function(...) + local t = {} + for i = 1, select('#', ...) do + local x = select(i, ...) + + if type(x) == "number" and config.float_precision then + x = tostring(round(x, config.float_precision)) + elseif type(x) == "table" then + x = vim.inspect(x) + else + x = tostring(x) + end + + t[#t + 1] = x + end + return table.concat(t, " ") + end + + + local log_at_level = function(level, level_config, message_maker, ...) + -- Return early if we're below the config.level + if level < levels[config.level] then + return + end + local nameupper = level_config.name:upper() + + local msg = message_maker(...) + local info = debug.getinfo(2, "Sl") + local lineinfo = info.short_src .. ":" .. info.currentline + + -- Output to console + if config.use_console then + local console_string = string.format( + "[%-6s%s] %s: %s", + nameupper, + os.date("%H:%M:%S"), + lineinfo, + msg + ) + + if config.highlights and level_config.hl then + vim.cmd(string.format("echohl %s", level_config.hl)) + end + + local split_console = vim.split(console_string, "\n") + for _, v in ipairs(split_console) do + vim.cmd(string.format([[echom "[%s] %s"]], config.plugin, vim.fn.escape(v, '"'))) + end + + if config.highlights and level_config.hl then + vim.cmd("echohl NONE") + end + end + + -- Output to log file + if config.use_file then + local fp = io.open(outfile, "a") + local str = string.format("[%-6s%s] %s: %s\n", + nameupper, os.date(), lineinfo, msg) + if fp ~= nil then + fp:write(str) + fp:close() + end + end + end + + for i, x in ipairs(config.modes) do + obj[x.name] = function(...) + return log_at_level(i, x, make_string, ...) + end + + obj[("fmt_%s" ):format(x.name)] = function() + return log_at_level(i, x, function(...) + local passed = {...} + local fmt = table.remove(passed, 1) + local inspected = {} + for _, v in ipairs(passed) do + table.insert(inspected, vim.inspect(v)) + end + return string.format(fmt, unpack(inspected)) + end) + end + end +end + +log.new(default_config, true) +-- }}} + +return log diff --git a/lua/devcontainer-cli/terminal.lua b/lua/devcontainer-cli/terminal.lua index 2f638ce..e0253bf 100644 --- a/lua/devcontainer-cli/terminal.lua +++ b/lua/devcontainer-cli/terminal.lua @@ -1,13 +1,14 @@ -local config = require("devcontainer-cli.config") -local folder_utils = require("devcontainer-cli.folder_utils") +local config = require("devcontainer-cli.config") +local folder_utils = require("devcontainer-cli.folder_utils") +local log = require("devcontainer-cli.log") -local Terminal = require('toggleterm.terminal').Terminal -local mode = require('toggleterm.terminal').mode +local Terminal = require('toggleterm.terminal').Terminal +local mode = require('toggleterm.terminal').mode -local M = {} +local M = {} -- valid window directions -M.directions = { +M.directions = { "float", "horizontal", "tab", @@ -15,32 +16,29 @@ M.directions = { } -- window management variables -local _terminal = nil +local _terminal = nil -- when the created window detaches set things back to -1 -local _on_detach = function() +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 - ) +local _on_fail = function(exit_code) + log.error("Devcontainer process has failed! exit_code: " .. exit_code) vim.cmd("silent! :checktime") end -local _on_success = function() - vim.notify("Devcontainer process succeeded!", vim.log.levels.INFO) +local _on_success = function() + log.INFO("Devcontainer process succeeded!") 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) +local _on_exit = function(code) if code == 0 then _on_success() return @@ -49,7 +47,7 @@ local _on_exit = function(code) _on_fail(code) end -local _on_open = function(term) +local _on_open = function(term) -- ensure that we are not in insert mode vim.cmd("stopinsert") vim.api.nvim_buf_set_keymap( @@ -90,7 +88,7 @@ M.columns = config.terminal_columns 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) + log.error("Invalid direction: " .. direction) return end