From dd67e625695db29c2c151374a96a8651ed4b7061 Mon Sep 17 00:00:00 2001 From: Dietrich Rink Date: Thu, 1 Jun 2023 00:54:51 +0200 Subject: [PATCH] build core, basic and go variants and add template --- README.md | 57 ++++++++++++- basic/Containerfile | 25 ++++++ basic/nvim/init/00-basic.lua | 32 ++++++++ basic/nvim/lua/pack.lua | 9 +++ basic/nvim/luasnippets/all.lua | 25 ++++++ basic/nvim/plugconf/LuaSnip.lua | 13 +++ basic/nvim/plugconf/auto-pairs.lua | 4 + basic/nvim/plugconf/cmp-nvim-lsp.lua | 6 ++ basic/nvim/plugconf/cmp.lua | 94 ++++++++++++++++++++++ basic/nvim/plugconf/ctrlp.lua | 5 ++ basic/nvim/plugconf/juliana.lua | 5 ++ basic/nvim/plugconf/lspconfig.lua | 12 +++ basic/nvim/plugconf/tmux-navigator.lua | 9 +++ basic/nvim/plugconf/tree.lua | 106 +++++++++++++++++++++++++ basic/nvim/plugconf/vimux.lua | 10 +++ build.sh | 15 ++++ code | 72 +++++++++++++++++ core/Containerfile | 19 +++++ core/nvim/init.lua | 69 ++++++++++++++++ core/shell/inputrc | 7 ++ core/shell/profile | 24 ++++++ core/shell/tmux.conf | 83 +++++++++++++++++++ go/Containerfile | 17 ++++ go/nvim/init/go.lua | 1 + go/nvim/luasnippets/go.lua | 10 +++ go/podman.args | 4 + template/Containerfile | 33 ++++++++ template/nvim/init/template.lua | 12 +++ template/nvim/luasnippets/all.lua | 11 +++ template/plugconf/my-repo.lua | 5 ++ template/podman.args | 8 ++ 31 files changed, 800 insertions(+), 2 deletions(-) create mode 100644 basic/Containerfile create mode 100644 basic/nvim/init/00-basic.lua create mode 100644 basic/nvim/lua/pack.lua create mode 100644 basic/nvim/luasnippets/all.lua create mode 100644 basic/nvim/plugconf/LuaSnip.lua create mode 100644 basic/nvim/plugconf/auto-pairs.lua create mode 100644 basic/nvim/plugconf/cmp-nvim-lsp.lua create mode 100644 basic/nvim/plugconf/cmp.lua create mode 100644 basic/nvim/plugconf/ctrlp.lua create mode 100644 basic/nvim/plugconf/juliana.lua create mode 100644 basic/nvim/plugconf/lspconfig.lua create mode 100644 basic/nvim/plugconf/tmux-navigator.lua create mode 100644 basic/nvim/plugconf/tree.lua create mode 100644 basic/nvim/plugconf/vimux.lua create mode 100755 build.sh create mode 100755 code create mode 100644 core/Containerfile create mode 100644 core/nvim/init.lua create mode 100644 core/shell/inputrc create mode 100644 core/shell/profile create mode 100644 core/shell/tmux.conf create mode 100644 go/Containerfile create mode 100644 go/nvim/init/go.lua create mode 100644 go/nvim/luasnippets/go.lua create mode 100644 go/podman.args create mode 100644 template/Containerfile create mode 100644 template/nvim/init/template.lua create mode 100644 template/nvim/luasnippets/all.lua create mode 100644 template/plugconf/my-repo.lua create mode 100644 template/podman.args diff --git a/README.md b/README.md index cae9d43..3546036 100644 --- a/README.md +++ b/README.md @@ -1,3 +1,56 @@ -# code +# Containerized `code` IDE +`code` is a containerized IDE, that runs in the terminal and is based on +[tmux][tmux] and [neovim][neovim]. That means you can run it on a machine +without X server or even within an SSH terminal session. -Containerized IDE, that runs in the terminal. \ No newline at end of file +The [podman][podman] containerization makes the development environment not +only more secure but also reproducible. If something in the editor were to +break, restarting it would create a fresh container. Since only the current +working directory is mounted into the container, the editor plugins and code +dependencies can't directly access any other files on your system. In +combination with code reviews on your code hosting platform of trust, this +might even be a reasonable trade-off between security and productivity. + +[tmux]: https://tmux.github.io/ +[neovim]: https://neovim.io/ +[podman]: https://podman.io/ + +## Variants +The beauty of `code` is, that you can easily specify a separate development +environment for each of your coding workflows by writing a Containerfile and +building it. When starting `code`, you simply specify the variant you'd like to +use on the command line. + +The default and most basic variant is *core*. It contains the shell +configuration and a basic neovim setup, that doesn't use any third party +plugins and therefore is the most secure choice. The *basic* variant contains +[git][git] and a bunch of plugins and configurations for integration with tmux, +file tree navigation, completion, quick editing and visuals. It doesn't contain +any language runtimes, libraries or LSP servers though. Any language or +workflow-specific variants can be built on top of *basic*. + +[git]: https://git-scm.com/ + +## Using `code` +After cloning the repository and making sure, that podman is installed on your +machine, you can build all images by executing `./build.sh` or only selected +variants by passing them as command line arguments. The *core* and *basic* +variants need to be built before any variants depending on them. + +Now you can link the `code` script to some directory in your `$PATH` (e. g. +`ln -s "$(pwd)/code" ~/.local/bin/code`) and edit your first file by executing +`code my-file.txt`. Some flags at the beginning of the command are interpreted +by `code`, everything else gets passed to neovim. To learn more about `code`s +command line flags, execute `code --help`. + +## Building new variants +Simply copy the template directory, add you [alpine packages][alpine] and +further instructions to the Containerfile, add additional neovim configuration +files etc. and build your variant with `./build.sh my-variant`. You can now run +it by executing `code --my-variant`. + +Please consider the files in the template directory for further instructions +on installing packages, configuring neovim, installing and configuring plugins +etc. + +[alpine]: https://pkgs.alpinelinux.org/packages diff --git a/basic/Containerfile b/basic/Containerfile new file mode 100644 index 0000000..d59d6e6 --- /dev/null +++ b/basic/Containerfile @@ -0,0 +1,25 @@ +FROM code-core +USER root +ENV HOME=/root + +# Install alpine packages +RUN apk add --no-cache procps curl curl-doc make make-doc git git-doc + +# Install neovim plugins +WORKDIR /usr/local/share/nvim/site/pack/plugins/opt +RUN printf '%s\n' \ + christoomey/vim-tmux-navigator benmills/vimux ap/vim-buftabline \ + nvim-tree/nvim-tree.lua nvim-tree/nvim-web-devicons ctrlpvim/ctrlp.vim \ + hrsh7th/nvim-cmp hrsh7th/cmp-buffer hrsh7th/cmp-path hrsh7th/cmp-nvim-lua \ + hrsh7th/cmp-cmdline hrsh7th/cmp-nvim-lsp neovim/nvim-lspconfig \ + saadparwaiz1/cmp_luasnip L3MON4D3/LuaSnip jiangmiao/auto-pairs \ + tpope/vim-surround tpope/vim-repeat kaiuri/nvim-juliana \ + | xargs -n1 -P0 -I'{}' git clone --depth 1 'https://github.com/{}' +RUN printf 'helptags %s\n' */doc | nvim --noplugin -es + +# Add neovim config +COPY nvim /etc/xdg/nvim + +# Configure environment +USER 1000:1000 +ENV HOME=/tmp diff --git a/basic/nvim/init/00-basic.lua b/basic/nvim/init/00-basic.lua new file mode 100644 index 0000000..be16760 --- /dev/null +++ b/basic/nvim/init/00-basic.lua @@ -0,0 +1,32 @@ +local pack = require('pack') + +-- tmux integration +pack 'vim-tmux-navigator' +pack 'vimux' + +-- File tree navigation +pack 'nvim-web-devicons' +pack 'nvim-tree.lua' +pack 'ctrlp.vim' + +-- Language servers and snippets +pack 'nvim-lspconfig' +pack 'LuaSnip' + +-- Completion +pack 'cmp-buffer' +pack 'cmp-path' +pack 'cmp-cmdline' +pack 'cmp-nvim-lua' +pack 'cmp-nvim-lsp' +pack 'cmp_luasnip' +pack 'nvim-cmp' + +-- Editing +pack 'auto-pairs' +pack 'vim-surround' +pack 'vim-repeat' + +-- Visuals +pack 'vim-buftabline' +pack 'nvim-juliana' diff --git a/basic/nvim/lua/pack.lua b/basic/nvim/lua/pack.lua new file mode 100644 index 0000000..8990ccf --- /dev/null +++ b/basic/nvim/lua/pack.lua @@ -0,0 +1,9 @@ +local conf_path = 'plugconf/%s.lua' + +function pack(name) + vim.cmd('packadd! ' .. name) + name = name:gsub('^n?vim%-', ''):gsub('%..*$', '') + vim.cmd.runtime(conf_path:format(name)) +end + +return pack diff --git a/basic/nvim/luasnippets/all.lua b/basic/nvim/luasnippets/all.lua new file mode 100644 index 0000000..48ad8e9 --- /dev/null +++ b/basic/nvim/luasnippets/all.lua @@ -0,0 +1,25 @@ +return { + parse( + { trig = 'lorem', name = 'Lorem ipsum sentence' }, + table.concat({ + 'Lorem ipsum dolor sit amet, qui minim labore adipisicing minim sint', + 'cillum sint consectetur cupidatat.', + }, " ") + ), + parse( + { trig = 'loremipsum', name = "Lorem ipsum paragraph" }, + table.concat({ + 'Lorem ipsum dolor sit amet, officia excepteur ex fugiat reprehenderit', + 'enim labore culpa sint ad nisi Lorem pariatur mollit ex esse', + 'exercitation amet. Nisi anim cupidatat excepteur officia.', + 'Reprehenderit nostrud nostrud ipsum Lorem est aliquip amet voluptate', + 'voluptate dolor minim nulla est proident. Nostrud officia pariatur ut', + 'officia. Sit irure elit esse ea nulla sunt ex occaecat reprehenderit', + 'commodo officia dolor Lorem duis laboris cupidatat officia voluptate.', + 'Culpa proident adipisicing id nulla nisi laboris ex in Lorem sunt duis', + 'officia eiusmod. Aliqua reprehenderit commodo ex non excepteur duis', + 'sunt velit enim. Voluptate laboris sint cupidatat ullamco ut ea', + 'consectetur et est culpa et culpa duis.', + }, ' ') + ), +} diff --git a/basic/nvim/plugconf/LuaSnip.lua b/basic/nvim/plugconf/LuaSnip.lua new file mode 100644 index 0000000..3116e6c --- /dev/null +++ b/basic/nvim/plugconf/LuaSnip.lua @@ -0,0 +1,13 @@ +local luasnip = require 'luasnip' +local loader = require 'luasnip.loaders.from_lua' + +luasnip.setup { + -- Enable live updates + update_events = { 'TextChanged', 'TextChangedI' }, + + -- Stop jumping when deleted + delete_check_events = { 'TextChanged' }, +} + +-- Automatically load lua snippets +loader.lazy_load() diff --git a/basic/nvim/plugconf/auto-pairs.lua b/basic/nvim/plugconf/auto-pairs.lua new file mode 100644 index 0000000..d6f6636 --- /dev/null +++ b/basic/nvim/plugconf/auto-pairs.lua @@ -0,0 +1,4 @@ +-- Replace mapping +vim.keymap.set('i', '', '{', { remap = true }) +vim.keymap.del('i', ' ') +vim.keymap.del('i', '') diff --git a/basic/nvim/plugconf/cmp-nvim-lsp.lua b/basic/nvim/plugconf/cmp-nvim-lsp.lua new file mode 100644 index 0000000..2eca3fd --- /dev/null +++ b/basic/nvim/plugconf/cmp-nvim-lsp.lua @@ -0,0 +1,6 @@ +local lspconfig = require 'lspconfig' +local cmp_nvim = require 'cmp_nvim_lsp' + +-- Set default capabilities +local capabilities = cmp_nvim.default_capabilities() +lspconfig.util.default_config.capabilities = capabilities diff --git a/basic/nvim/plugconf/cmp.lua b/basic/nvim/plugconf/cmp.lua new file mode 100644 index 0000000..32ffbe5 --- /dev/null +++ b/basic/nvim/plugconf/cmp.lua @@ -0,0 +1,94 @@ +local cmp = require 'cmp' +local context = require 'cmp.config.context' +local luasnip = require 'luasnip' + +vim.o.completeopt = nil + +-- General setup +cmp.setup { + snippet = { + expand = function(args) luasnip.lsp_expand(args.body) end, + }, + window = { + documentation = cmp.config.window.bordered(), + }, + formatting = { + fields = { 'menu', 'abbr', 'kind' }, + format = function(entry, item) + item.menu = ({ + nvim_lsp = 'λ', + luasnip = '⋗', + buffer = 'Ω', + path = '🖫', + })[entry.source.name] + return item + end, + }, + preselect = cmp.PreselectMode.None, + mapping = { + -- Confirm, jump or select with tab + [''] = cmp.mapping(function(fallback) + if cmp.get_selected_entry() then + cmp.confirm() + elseif luasnip.jumpable(1) then + luasnip.jump(1) + elseif luasnip.expandable() then + luasnip.expand() + elseif cmp.visible() then + cmp.select_next_item() + else + fallback() + end + end, { 'i', 's' }), + -- Jump back with shift tab + [''] = cmp.mapping(function(fallback) + if luasnip.jumpable(-1) then + luasnip.jump(-1) + else + fallback() + end + end, { 'i', 's' }), + -- Navigate options with ctrl n and ctrl p + [''] = cmp.mapping(function(fallback) + if not cmp.visible() then + cmp.complete() + else + cmp.select_next_item() + end + end), + [''] = cmp.mapping(function(fallback) + if cmp.visible() then cmp.select_prev_item() else fallback() end + end), + }, + sources = cmp.config.sources({ + { name = 'nvim_lsp' }, + { name = 'luasnip' }, + { name = 'nvim_lua' }, + }, { + { name = 'buffer', keyword_length = 5 }, + { name = 'path' }, + }), + completion = { + keyword_length = 3 + }, + experimental = { + ghost_text = true, + }, + -- Disable in comments + enabled = function() + return not context.in_syntax_group('Comment') + end, +} + +-- Command line setup +cmp.setup.cmdline(':', { + mapping = cmp.mapping.preset.cmdline(), + sources = cmp.config.sources({ + { name = 'path' }, + },{ + { name = 'cmdline' }, + }), + completion = { + keyword_length = 2 + }, +}) diff --git a/basic/nvim/plugconf/ctrlp.lua b/basic/nvim/plugconf/ctrlp.lua new file mode 100644 index 0000000..f62a2ab --- /dev/null +++ b/basic/nvim/plugconf/ctrlp.lua @@ -0,0 +1,5 @@ +-- Ctrlp +vim.g.ctrlp_open_multiple_files = '1r' + +-- Keyboard shortcuts +vim.g.ctrlp_map = '' diff --git a/basic/nvim/plugconf/juliana.lua b/basic/nvim/plugconf/juliana.lua new file mode 100644 index 0000000..0a4f90d --- /dev/null +++ b/basic/nvim/plugconf/juliana.lua @@ -0,0 +1,5 @@ +local juliana = require 'nvim-juliana' + +-- Set color scheme +juliana.setup { colors = { fg4 = '#596875' } } +vim.cmd.colorscheme('juliana') diff --git a/basic/nvim/plugconf/lspconfig.lua b/basic/nvim/plugconf/lspconfig.lua new file mode 100644 index 0000000..afe7cbf --- /dev/null +++ b/basic/nvim/plugconf/lspconfig.lua @@ -0,0 +1,12 @@ +-- Set keymaps +vim.api.nvim_create_autocmd('LspAttach', { + group = vim.api.nvim_create_augroup('UserLspConfig', {}), + callback = function(ev) + local opts = { buffer = ev.buf } + vim.keymap.set('n', 'gd', vim.lsp.buf.definition, opts) + vim.keymap.set('n', 'gr', vim.lsp.buf.references, opts) + vim.keymap.set('n', 'K', vim.lsp.buf.hover, opts) + vim.keymap.set('n', 'cr', vim.lsp.buf.rename, opts) + vim.keymap.set('n', 'ca', vim.lsp.buf.code_action, opts) + end, +}) diff --git a/basic/nvim/plugconf/tmux-navigator.lua b/basic/nvim/plugconf/tmux-navigator.lua new file mode 100644 index 0000000..80bb75a --- /dev/null +++ b/basic/nvim/plugconf/tmux-navigator.lua @@ -0,0 +1,9 @@ +-- Disable wrapping +vim.g.tmux_navigator_no_wrap = 1 + +-- Keyboard shortcuts +vim.g.tmux_navigator_no_mappings = 1 +vim.keymap.set('n', '', vim.cmd.TmuxNavigateLeft, { silent = true }) +vim.keymap.set('n', '', vim.cmd.TmuxNavigateDown, { silent = true }) +vim.keymap.set('n', '', vim.cmd.TmuxNavigateUp, { silent = true }) +vim.keymap.set('n', '', vim.cmd.TmuxNavigateRight, { silent = true }) diff --git a/basic/nvim/plugconf/tree.lua b/basic/nvim/plugconf/tree.lua new file mode 100644 index 0000000..9ccff69 --- /dev/null +++ b/basic/nvim/plugconf/tree.lua @@ -0,0 +1,106 @@ +local tree = require 'nvim-tree' + +-- Disable NetRw +vim.g.loaded_netrw = 1 +vim.g.loaded_netrwPlugin = 1 + +-- Keyboard shortcuts +vim.keymap.set('n', 'U', vim.cmd.NvimTreeToggle, { silent = true }) + +-- Configure Nvim Tree +tree.setup { + sync_root_with_cwd = true, + update_focused_file = { enable = true }, + git = { ignore = false }, + renderer = { + highlight_git = true, + highlight_opened_files = "icon", + root_folder_label = ":~:s?$?", + }, + on_attach = function(buf) + -- Mapping helpers + local api = require 'nvim-tree.api' + local function opts(desc) return { + desc = 'nvim-tree: ' .. desc, buffer = buf, + noremap = true, silent = true, nowait = true, + } end + + -- Directories + vim.keymap.set('n', 'C', api.tree.change_root_to_node, opts('Change Directory')) + vim.keymap.set('n', '-', api.tree.change_root_to_parent, opts('Up')) + vim.keymap.set('n', 'P', api.node.navigate.parent, opts('Parent Directory')) + + -- Open + vim.keymap.set('n', '', api.node.open.edit, opts('Open: Toggle')) + vim.keymap.set('n', '', api.node.open.edit, opts('Open: Toggle')) + vim.keymap.set('n', 'v', api.node.open.vertical, opts('Open: Vertical Split')) + vim.keymap.set('n', 's', api.node.open.horizontal, opts('Open: Horizontal Split')) + vim.keymap.set('n', '', api.node.open.preview, opts('Open: Preview')) + vim.keymap.set('n', 'i', api.node.show_info_popup, opts('Info')) + + -- File operations + vim.keymap.set('n', 'r', api.fs.rename_sub, opts('Rename')) + vim.keymap.set('n', 'R', api.fs.rename_basename, opts('Rename: Basename')) + vim.keymap.set('n', 'a', api.fs.create, opts('Create')) + vim.keymap.set('n', 'c', api.fs.copy.node, opts('Copy')) + vim.keymap.set('n', 'x', api.fs.cut, opts('Cut')) + vim.keymap.set('n', 'p', api.fs.paste, opts('Paste')) + vim.keymap.set('n', 'd', api.fs.remove, opts('Delete')) + vim.keymap.set('n', '.', api.node.run.cmd, opts('Run Command')) + + -- Path copying + vim.keymap.set('n', 'y', api.fs.copy.relative_path, opts('Copy Relative Path')) + vim.keymap.set('n', 'Y', api.fs.copy.absolute_path, opts('Copy Absolute Path')) + + -- Navigation + vim.keymap.set('n', 'q', api.tree.close, opts('Close')) + vim.keymap.set('n', '/', api.tree.search_node, opts('Search')) + vim.keymap.set('n', '?', api.tree.toggle_help, opts('Help')) + + -- Filtering + vim.keymap.set('n', 'f', api.live_filter.start, opts('Filter')) + vim.keymap.set('n', 'F', api.live_filter.clear, opts('Clean Filter')) + vim.keymap.set('n', 'H', api.tree.toggle_hidden_filter, opts('Toggle Dotfiles')) + vim.keymap.set('n', 'T', api.tree.toggle_git_clean_filter, opts('Toggle Git Clean')) + vim.keymap.set('n', 'Z', api.tree.collapse_all, opts('Collapse')) + vim.keymap.set('n', 'z', api.tree.expand_all, opts('Expand All')) + + -- Bookmarks + vim.keymap.set('n', 'm', api.marks.toggle, opts('Toggle Bookmark')) + vim.keymap.set('n', 'M', api.marks.bulk.move, opts('Move Bookmarked')) + + -- Custom open and close + vim.keymap.set('n', 'l', function() + local node = api.tree.get_node_under_cursor() + if node.type == 'directory' then + if not node.open then api.node.open.edit() end + else + api.node.open.edit() + end + end, opts('Open')) + + vim.keymap.set('n', 'h', function() + local node = api.tree.get_node_under_cursor() + if node.type == 'directory' then + if node.open then api.node.open.edit() end + else + local path = node.absolute_path + if vim.fn.buflisted(path) == 1 then + if vim.fn.getbufinfo(path)[1].changed == 0 then + vim.cmd.bdelete(path) + else + vim.notify('Buffer has unsaved changes', vim.log.levels.ERROR) + end + end + end + end, opts('Close')) + + vim.keymap.set('n', 'o', function() + local node = api.tree.get_node_under_cursor() + if node.type == 'file' then + api.node.open.edit() + api.tree.focus() + end + end, opts('Open: Keep Focus')) + end +} diff --git a/basic/nvim/plugconf/vimux.lua b/basic/nvim/plugconf/vimux.lua new file mode 100644 index 0000000..1a3f05d --- /dev/null +++ b/basic/nvim/plugconf/vimux.lua @@ -0,0 +1,10 @@ +-- Keyboard shortcuts +if vim.env.TMUX ~= nil then + vim.keymap.set('n', '', function() + vim.cmd.write() + local file = vim.api.nvim_buf_get_name(0) + local command = vim.g.vimux_command or 'make' + vim.fn.VimuxRunCommand(command:format(file)) + end) + vim.keymap.set('n', '', vim.fn.VimuxCloseRunner, { silent = true }) +end diff --git a/build.sh b/build.sh new file mode 100755 index 0000000..4e1ca31 --- /dev/null +++ b/build.sh @@ -0,0 +1,15 @@ +#!/bin/bash +set -euo pipefail +shopt -s extglob + +cd "$(dirname "$0")" + +if (( $# == 0 )); then + readonly images=(core basic !(template|core|basic)/) +else + readonly images=("$@") +fi + +for image in "${images[@]}"; do + podman build -t "code-${image%/}" -f "${image%/}/Containerfile" +done diff --git a/code b/code new file mode 100755 index 0000000..6f9fc11 --- /dev/null +++ b/code @@ -0,0 +1,72 @@ +#!/bin/bash +set -euo pipefail + +# Config +readonly DEFAULT_VARIANT='core' +readonly LOOPBACK_ARGS=( + --network slirp4netns:allow_host_loopback=true + --add-host host:10.0.2.2 +) + +# Help text +readonly HELP="code [--publish ] [--loopback] + [--] [--variants] [--help] [vim-params...] + + --publish Publish port(s) from container + --loopback Allow host loopback + -- Use editor variant (default: ${DEFAULT_VARIANT}) + --variants Print list of available editor variants and exit + --help Show this help and exit" + +# Get script directory +readonly DIR="$(dirname "$(readlink -f -- "$0")")" + +# Parse arguments +ports=() +loopback=false +variant="$DEFAULT_VARIANT" + +for arg in "$@"; do + case "$arg" in + --publish ) shift; ports+=("$1");; + --loopback ) loopback=true;; + --variants ) + for f in "$DIR"/*; do + [[ "$f" == */template || ! -d "$f" ]] || echo "${f##*/}"; + done + exit;; + -h | --help ) echo >&2 "$HELP"; exit;; + --* ) + if [[ ! -d "${DIR}/${arg#--}" ]]; then break; fi + variant="${arg#--}";; + * ) break;; + esac + shift +done + +# Collect additional arguments +args=() +readonly NAME="$(basename "$(pwd)")" +if [[ -f "${DIR}/${variant}/podman.args" ]]; then + mapfile -t custom < "${DIR}/${variant}/podman.args" + for arg in "${custom[@]}"; do + arg="${arg//'{NAME}'/"$NAME"}" + arg="${arg//'{DIR}'/"$DIR"}" + arg="${arg//'{HOME}'/"$HOME"}" + args+=("$arg") + done +fi +for port in "${ports[@]}"; do + [[ ! "$port" =~ ^[0-9]+$ ]] || port="${port}:${port}" + args+=(--publish "$port") +done +if $loopback; then args+=("${LOOPBACK_ARGS[@]}"); fi + +# Start editor +exec podman run --rm -it \ + --tz local \ + --detach-keys ctrl-q,ctrl-q \ + --volume ".:/work/$NAME" \ + --workdir "/work/$NAME" \ + --userns keep-id --user "$(id -u):$(id -g)" \ + "${args[@]}" "code-${variant}" "$@" diff --git a/core/Containerfile b/core/Containerfile new file mode 100644 index 0000000..09d79e6 --- /dev/null +++ b/core/Containerfile @@ -0,0 +1,19 @@ +FROM alpine:latest + +# Install alpine packages +RUN apk add --no-cache \ + mandoc mandoc-doc less less-doc bash bash-doc bash-completion \ + tmux tmux-doc neovim neovim-doc + +# Add shell configs +COPY shell /etc + +# Add neovim config +COPY nvim /etc/xdg/nvim +ENV VIMINIT=":luafile /etc/xdg/nvim/init.lua" + +# Configure environment +USER 1000:1000 +WORKDIR /work +ENV HOME=/tmp TERM=xterm-256color LANG=en_US.UTF-8 +ENTRYPOINT ["/usr/bin/tmux", "new-session", "/usr/bin/nvim"] diff --git a/core/nvim/init.lua b/core/nvim/init.lua new file mode 100644 index 0000000..7fe7229 --- /dev/null +++ b/core/nvim/init.lua @@ -0,0 +1,69 @@ +-- Change files in place +vim.o.backupcopy = 'yes' + +-- Disable auto comment +vim.cmd.autocmd('FileType', '*', 'setlocal', 'formatoptions-=cro') + +-- Disable double spaces when joining +vim.o.joinspaces = false + +-- Code folding +vim.o.foldmethod = 'indent' +vim.o.foldlevel = 99 + +-- Indentation +vim.o.tabstop = 2 +vim.o.shiftwidth = 2 +vim.o.softtabstop = -1 +vim.o.expandtab = true + +-- Visible line breaks +vim.o.list = true +vim.o.listchars = 'eol:¬' + +-- Relative line numbers +vim.o.number = true +vim.o.relativenumber = true + +-- 80 character indication +vim.o.colorcolumn = '80' + +-- Status window height +vim.o.previewheight = 30 + +-- Theme +vim.o.termguicolors = true +vim.cmd.colorscheme('slate') + +-- Keyboard shortcuts +vim.keymap.set('n', '', ':bn', { silent = true }) +vim.keymap.set('n', '', ':bp', { silent = true }) +vim.keymap.set('n', '', ':noh') +vim.keymap.set('n', 'gp', "'[V']", { remap = true }) +vim.keymap.set('i', '', '{}', { silent = true } ) +vim.keymap.set('i', ' ', '{ }', { silent = true } ) +vim.keymap.set('i', '', '{}O', { silent = true } ) +vim.keymap.set('i', '', '', { remap = true }) + +-- Add command to show help in current window +vim.api.nvim_create_user_command('Help', function(a) + -- Create empty 'help' buffer + vim.cmd.enew() + vim.o.buftype = 'help' + local buf = vim.fn.bufnr('%') + + -- Show help and handle errors + ok, msg = pcall(vim.cmd.help, a.fargs[1] or "help.txt") + if not ok then vim.notify(msg, vim.log.levels.ERROR); vim.cmd.bp() end + + -- Wipe out empty buffer if not taken over + if vim.fn.bufnr('%') ~= buf then vim.cmd.bwipeout(buf) end +end, { nargs = '?', bar = true, complete = 'help' }) + +-- Add command line abbreviation if at start of line +vim.cmd.cabbrev { 'help', + '=(getcmdtype() == ":" && getcmdpos() == 1) ? "Help" : "help"' +} + +-- Source init directory +vim.cmd 'runtime! init/*.lua' diff --git a/core/shell/inputrc b/core/shell/inputrc new file mode 100644 index 0000000..625e393 --- /dev/null +++ b/core/shell/inputrc @@ -0,0 +1,7 @@ +set editing-mode vi + +$if mode=vi + set keymap vi-insert + Control-l: clear-screen + Control-r: vi-put +$endif diff --git a/core/shell/profile b/core/shell/profile new file mode 100644 index 0000000..ba709f3 --- /dev/null +++ b/core/shell/profile @@ -0,0 +1,24 @@ +# Set editor +EDITOR='nvim' +alias vim='nvim' +alias vi='nvim' + +# Configure history +HISTCONTROL=ignoreboth +unset HISTFILE + +# Handy aliases +alias la='ls -A' +alias ll='la -l' +alias ..='cd ..' +alias e='exit' + +# Git branch helper +git_branch_prompt() { + local branch="$(git branch --show-current 2> /dev/null)" + [[ -z "$branch" ]] || echo "[${branch}]" +} + +# Colorful command prompt +PS1='\[\e[33m\][\[\e[0m\]\W\[\e[33m\]]\[\e[32m\]$(git_branch_prompt)\[\e[33m\]\$\[\e[37m\] ' +trap 'printf \\e[0m' DEBUG diff --git a/core/shell/tmux.conf b/core/shell/tmux.conf new file mode 100644 index 0000000..6f5e1ca --- /dev/null +++ b/core/shell/tmux.conf @@ -0,0 +1,83 @@ +# Set default shell +set -g default-shell /bin/bash + +# Increase history size +set -g history-limit 50000 + +# Disable escape delay +set-option -sg escape-time 0 + +# Enable mouse and focus events +set -g mouse on +set -g focus-events on + +################ +# Key bindings # +################ + +# Change prefix +set-option -g prefix C-a + +# Remap split commands +unbind '"' +unbind % +bind s split-window -v +bind v split-window -h + +# Remap pane selection +unbind Up +unbind Down +unbind Left +unbind Right + +is_vim="ps -o state= -o comm= -t '#{pane_tty}' \ + | grep -iqE '^[^TXZ ]+ +(\\S+\\/)?g?(view|l?n?vim?x?)(diff)?$'" +bind-key -n 'M-h' if-shell "$is_vim" { send-keys M-h } { if-shell -F '#{pane_at_left}' {} { select-pane -L } } +bind-key -n 'M-j' if-shell "$is_vim" { send-keys M-j } { if-shell -F '#{pane_at_bottom}' {} { select-pane -D } } +bind-key -n 'M-k' if-shell "$is_vim" { send-keys M-k } { if-shell -F '#{pane_at_top}' {} { select-pane -U } } +bind-key -n 'M-l' if-shell "$is_vim" { send-keys M-l } { if-shell -F '#{pane_at_right}' {} { select-pane -R } } + +# Remap resize +unbind C-Up +unbind C-Down +unbind C-Left +unbind C-Right +unbind M-Up +unbind M-Down +unbind M-Left +unbind M-Right +bind -nr M-K resize-pane -U +bind -nr M-J resize-pane -D +bind -nr M-H resize-pane -L +bind -nr M-L resize-pane -R + +########## +# Design # +########## + +# Borders +set -g pane-border-style 'fg=colour12' +set -g pane-active-border-style 'fg=colour12' + +# Statusbar +set -g status-position bottom +set -g status-justify left +set -g status-style 'bg=colour235' +set -g status-left '' + +# Command line and messages +set -g message-style 'bg=color235' +set -g display-time 4000 + +# Tabs +setw -g window-status-current-style 'bg=colour238 bold' +setw -g window-status-current-format ' #[fg=color245]#I#[fg=colour250] #W ' +setw -g window-status-style 'bg=colour236' +setw -g window-status-format ' #[fg=colour240]#I#[fg=colour245] #W ' + +# Clock +set -g status-right '#[fg=colour0,bg=colour240] %H:%M ' +set -g status-interval 10 + +# Scroll number +setw -g mode-style 'fg=colour240' diff --git a/go/Containerfile b/go/Containerfile new file mode 100644 index 0000000..3afd6ea --- /dev/null +++ b/go/Containerfile @@ -0,0 +1,17 @@ +FROM code-basic +USER root +ENV HOME=/root + +# Install alpine packages +RUN apk add --no-cache go go-doc + +# Install go packages +ENV GOPATH=/usr/local/lib/go PATH=/usr/local/lib/go/bin:$PATH +RUN go install golang.org/x/tools/gopls@latest && rm -rf $(go env GOCACHE) + +# Add neovim config +COPY nvim /etc/xdg/nvim + +# Configure environment +USER 1000:1000 +ENV HOME=/tmp diff --git a/go/nvim/init/go.lua b/go/nvim/init/go.lua new file mode 100644 index 0000000..5938b1c --- /dev/null +++ b/go/nvim/init/go.lua @@ -0,0 +1 @@ +require('lspconfig').gopls.setup {} diff --git a/go/nvim/luasnippets/go.lua b/go/nvim/luasnippets/go.lua new file mode 100644 index 0000000..a1d42c4 --- /dev/null +++ b/go/nvim/luasnippets/go.lua @@ -0,0 +1,10 @@ +return { + parse( + { trig = 'func', name = 'Function' }, + 'func ${1:name}($2) $3{\n\t$4\n}' + ), + parse( + { trig = 'err', name = 'Handle error' }, + 'if ${2:err} != nil { return ${1}${2} }' + ), +} diff --git a/go/podman.args b/go/podman.args new file mode 100644 index 0000000..824823f --- /dev/null +++ b/go/podman.args @@ -0,0 +1,4 @@ +--env +GOPATH=/work/{NAME}/.go +--env +GOCACHE=/work/{NAME}/.go/cache diff --git a/template/Containerfile b/template/Containerfile new file mode 100644 index 0000000..d15e926 --- /dev/null +++ b/template/Containerfile @@ -0,0 +1,33 @@ +FROM code-basic +USER root +ENV HOME=/root + +# Install alpine packages +### +### Replace ... with the alpine packaes you'd like to install. +### You can browser them on https://pkgs.alpinelinux.org/packages. +### +RUN apk add --no-cache ... + +# Install neovim plugins +### +### Replace the first ... with / strings for every neovim plugin +### you'd like to install from GitHub. Replace the second ... with a comma +### separated list of the s, that contain a doc directory. +### +WORKDIR /usr/local/share/nvim/site/pack/plugins/opt +RUN printf '%s\n' \ + ... \ + | xargs -n1 -P0 -I'{}' git clone --depth 1 'https://github.com/{}' +RUN printf 'helptags %s\n' {...}/doc | nvim --noplugin -es + +# Add neovim config +COPY nvim /etc/xdg/nvim + +### +### Execute further instructions and copy furhter configurations here. +### + +# Configure environment +USER 1000:1000 +ENV HOME=/tmp diff --git a/template/nvim/init/template.lua b/template/nvim/init/template.lua new file mode 100644 index 0000000..6597b32 --- /dev/null +++ b/template/nvim/init/template.lua @@ -0,0 +1,12 @@ +local pack = require 'pack' + +--- +--- Add your custom neovim configuration here. This file should be named after +--- your editor variant. You can use `pack '/'` to load a plugin +--- and source it's configuration file. For a plugin +--- 'my-user/nvim-my-repo.nvim', the configuration file plugconf/my-repo.lua +--- would be source, if it exists ("nvim-" and "vim-" prefixes and everyting +--- after the first "." get removed from ). +--- + +pack 'my-user/nvim-my-repo.nvim' diff --git a/template/nvim/luasnippets/all.lua b/template/nvim/luasnippets/all.lua new file mode 100644 index 0000000..761b45a --- /dev/null +++ b/template/nvim/luasnippets/all.lua @@ -0,0 +1,11 @@ +--- +--- Add your lua snippets here. Rename this file to match the file type your +--- snippets are supposed to be availlable for instead of 'all'. +--- + +return { + parse( + { trig = 'my-snippet', name = 'My Snippet' }, + 'The quick brown ${1:fox} jumps over the lazy ${2:dog,cat,elephant}' + ), +} diff --git a/template/plugconf/my-repo.lua b/template/plugconf/my-repo.lua new file mode 100644 index 0000000..59a7f34 --- /dev/null +++ b/template/plugconf/my-repo.lua @@ -0,0 +1,5 @@ +--- +--- You can import your plugin and set it up or set vim options to configure it +--- here. If any of your plugins don't require configuration, you can simply +--- omit creating a plugconf/*.lua file for it. +--- diff --git a/template/podman.args b/template/podman.args new file mode 100644 index 0000000..c309353 --- /dev/null +++ b/template/podman.args @@ -0,0 +1,8 @@ +### +### Add variant specific podman arguments here, one per line, or remove the +### file entirely. You can use {DIR} as placeholder for the directory of this +### repository, {NAME} for the name of the current working directory, that will +### be mounted to /work/{NAME} in the container and {HOME} for the users home +### directory on the host system. That way you can change the confinement or +### network configuration, mount additional directories into the container etc. +###