chore: drop macOS/Windows support and offline nvim tooling
- Remove macOS/Windows clipboard branches, homebrew fzf path from .vimrc - Remove is_macos/is_windows logic from wezterm.lua - Drop scripts/nvim-offline-* and NVIM_OFFLINE plumbing from nvim config - Clean macOS section from .gitignore; simplify .gitattributes to LF-only - Trim README of offline-bundle section
This commit is contained in:
parent
21c315d5f2
commit
84fbb30d82
12 changed files with 20 additions and 728 deletions
|
|
@ -166,13 +166,7 @@ set background=dark
|
|||
" hi VertSplit guibg=NONE guifg=NONE ctermbg=NONE ctermfg=NONE
|
||||
|
||||
" Sync clipboard with OS
|
||||
if has('win32') || has('win64')
|
||||
set clipboard=unnamedplus " Windows
|
||||
elseif has('macunix')
|
||||
set clipboard=unnamed " macOS
|
||||
else
|
||||
set clipboard=unnamedplus " Linux/WSL
|
||||
endif
|
||||
set clipboard=unnamedplus
|
||||
|
||||
" True colors
|
||||
if !has('gui_running') && &term =~ '\%(screen\|tmux\)'
|
||||
|
|
@ -198,4 +192,3 @@ augroup netrw_setup | au!
|
|||
au FileType netrw nmap <buffer> l <CR>
|
||||
augroup END
|
||||
|
||||
set rtp+=/opt/homebrew/opt/fzf
|
||||
|
|
|
|||
|
|
@ -42,10 +42,6 @@ vim.opt.iskeyword:append '-' -- hyphenated words recognized by searches
|
|||
vim.opt.formatoptions:remove { 'c', 'r', 'o' } -- don't insert the current comment leader automatically for auto-wrapping comments using 'textwidth', hitting <Enter> in insert mode, or hitting 'o' or 'O' in normal mode.
|
||||
vim.opt.runtimepath:remove '/usr/share/vim/vimfiles' -- separate vim plugins from neovim in case vim still in use
|
||||
|
||||
-- Offline mode: set NVIM_OFFLINE=1 to disable all network operations
|
||||
-- (plugin update checks, Mason auto-install, Treesitter auto-install)
|
||||
vim.g.offline_mode = vim.env.NVIM_OFFLINE == '1'
|
||||
|
||||
-- Neovim 0.12 changed directive captures from TSNode to TSNode[] (arrays).
|
||||
-- nvim-treesitter predicates do match[capture_id] expecting a single TSNode,
|
||||
-- but now receive a table. The nil guard passes, then get_node_text calls
|
||||
|
|
|
|||
|
|
@ -22,19 +22,14 @@ if not (vim.uv or vim.loop).fs_stat(lazypath) then
|
|||
end
|
||||
vim.opt.rtp:prepend(lazypath)
|
||||
|
||||
local offline = vim.g.offline_mode
|
||||
|
||||
require('lazy').setup {
|
||||
spec = {
|
||||
{ import = 'brian.plugins' },
|
||||
},
|
||||
install = { colorscheme = { 'kanagawa' } },
|
||||
checker = { enabled = not offline, notify = false },
|
||||
checker = { enabled = true, notify = false },
|
||||
change_detection = {
|
||||
notify = false,
|
||||
},
|
||||
git = {
|
||||
timeout = offline and 1 or 120,
|
||||
},
|
||||
}
|
||||
vim.keymap.set('n', '<leader>l', '<cmd>:Lazy<cr>', { desc = 'Plugin Manager' })
|
||||
|
|
|
|||
|
|
@ -1,6 +1,3 @@
|
|||
local preparing = vim.env.NVIM_OFFLINE_PREPARE == '1'
|
||||
local dap_auto_install = not vim.g.offline_mode and not preparing
|
||||
|
||||
return {
|
||||
'mfussenegger/nvim-dap',
|
||||
dependencies = {
|
||||
|
|
@ -52,13 +49,13 @@ return {
|
|||
dependencies = 'mason.nvim',
|
||||
cmd = { 'DapInstall', 'DapUninstall' },
|
||||
opts = {
|
||||
automatic_installation = dap_auto_install,
|
||||
automatic_installation = true,
|
||||
handlers = {},
|
||||
ensure_installed = dap_auto_install and {
|
||||
ensure_installed = {
|
||||
'python',
|
||||
'js',
|
||||
'codelldb',
|
||||
} or {},
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
|
|
|
|||
|
|
@ -1,19 +1,3 @@
|
|||
local offline = vim.g.offline_mode
|
||||
local preparing = vim.env.NVIM_OFFLINE_PREPARE == '1'
|
||||
local online = not offline
|
||||
local auto_install = online and not preparing
|
||||
local skip_prepare_packages = {}
|
||||
|
||||
do
|
||||
local raw = vim.env.PREPARE_SKIP_MASON_PACKAGES or ''
|
||||
for name in string.gmatch(raw, '([^,]+)') do
|
||||
local trimmed = vim.trim(name)
|
||||
if trimmed ~= '' then
|
||||
skip_prepare_packages[trimmed] = true
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
local lsp_servers = {
|
||||
'angularls',
|
||||
'bashls',
|
||||
|
|
@ -68,32 +52,13 @@ local function dedupe(list)
|
|||
return out
|
||||
end
|
||||
|
||||
local function filter_skips(list)
|
||||
if not preparing or vim.tbl_count(skip_prepare_packages) == 0 then
|
||||
return list
|
||||
end
|
||||
local out = {}
|
||||
for _, item in ipairs(list) do
|
||||
if not skip_prepare_packages[item] then
|
||||
table.insert(out, item)
|
||||
end
|
||||
end
|
||||
return out
|
||||
end
|
||||
|
||||
local configured_mason_packages = dedupe(
|
||||
vim.list_extend(vim.deepcopy(tools), lsp_servers)
|
||||
)
|
||||
local prepare_tools = filter_skips(vim.deepcopy(configured_mason_packages))
|
||||
local tool_installer_list = preparing and prepare_tools or configured_mason_packages
|
||||
local tool_installer_list = dedupe(vim.list_extend(vim.deepcopy(tools), lsp_servers))
|
||||
|
||||
return {
|
||||
{
|
||||
'mason-org/mason-lspconfig.nvim',
|
||||
opts = {
|
||||
-- skip auto-install when offline; packages are pre-installed
|
||||
-- during offline prepare we install LSP packages via MasonToolsInstallSync
|
||||
ensure_installed = auto_install and lsp_servers or {},
|
||||
ensure_installed = lsp_servers,
|
||||
-- Only auto-enable the LSP servers we explicitly manage in this config.
|
||||
-- This avoids accidental multi-server attaches (e.g. ts_ls + vtsls),
|
||||
-- which can produce duplicate Telescope definition results.
|
||||
|
|
@ -121,14 +86,12 @@ return {
|
|||
'mason-org/mason.nvim',
|
||||
},
|
||||
opts = {
|
||||
-- skip auto-install when offline; packages are pre-installed
|
||||
-- during offline prepare we install tools + lsp packages in one sync pass
|
||||
ensure_installed = tool_installer_list,
|
||||
integrations = {
|
||||
-- keep mapping enabled so lspconfig names (e.g. ts_ls) resolve to Mason packages
|
||||
['mason-lspconfig'] = true,
|
||||
},
|
||||
run_on_start = auto_install,
|
||||
run_on_start = true,
|
||||
start_delay = 0,
|
||||
},
|
||||
},
|
||||
|
|
|
|||
|
|
@ -1,12 +1,8 @@
|
|||
-- Highlight, edit, and navigate code
|
||||
local preparing = vim.env.NVIM_OFFLINE_PREPARE == '1'
|
||||
|
||||
return {
|
||||
'nvim-treesitter/nvim-treesitter',
|
||||
branch = 'master',
|
||||
-- During offline prepare we run TSUpdateSync explicitly from the script.
|
||||
-- Skipping lazy build here prevents duplicate parser builds in later steps.
|
||||
build = preparing and nil or ':TSUpdate',
|
||||
build = ':TSUpdate',
|
||||
main = 'nvim-treesitter.configs',
|
||||
dependencies = {
|
||||
'nvim-treesitter/nvim-treesitter-textobjects',
|
||||
|
|
@ -41,7 +37,7 @@ return {
|
|||
'html',
|
||||
'rust',
|
||||
},
|
||||
auto_install = not vim.g.offline_mode and not preparing,
|
||||
auto_install = true,
|
||||
highlight = { enable = true },
|
||||
indent = { enable = true },
|
||||
incremental_selection = {
|
||||
|
|
|
|||
|
|
@ -4,10 +4,6 @@ if wezterm.config_builder then
|
|||
config = wezterm.config_builder()
|
||||
end
|
||||
|
||||
local target_triple = wezterm.target_triple or ""
|
||||
local is_macos = target_triple:find("apple%-darwin") ~= nil
|
||||
local is_windows = target_triple:find("windows") ~= nil
|
||||
|
||||
local function env_number(name, fallback)
|
||||
local raw = os.getenv(name)
|
||||
local parsed = raw and tonumber(raw) or nil
|
||||
|
|
@ -26,16 +22,10 @@ config.use_fancy_tab_bar = false
|
|||
config.tab_bar_at_bottom = false
|
||||
config.enable_tab_bar = false
|
||||
-- Font configuration
|
||||
local default_font_size = is_windows and 13 or 16
|
||||
config.font_size = env_number("WEZTERM_FONT_SIZE", default_font_size)
|
||||
config.font_size = env_number("WEZTERM_FONT_SIZE", 16)
|
||||
config.font = wezterm.font("JetBrains Mono", { weight = "Regular" })
|
||||
config.bold_brightens_ansi_colors = true
|
||||
config.force_reverse_video_cursor = false
|
||||
if is_windows then
|
||||
-- Windows GPUs/drivers can show transient render artifacts with the default
|
||||
-- backend. OpenGL is generally more stable here.
|
||||
config.front_end = "OpenGL"
|
||||
end
|
||||
local function resolve_theme_name()
|
||||
local value = (os.getenv("WEZTERM_THEME") or "dragon"):lower()
|
||||
local aliases = {
|
||||
|
|
@ -128,20 +118,11 @@ config.window_padding = {
|
|||
top = 8,
|
||||
bottom = 8,
|
||||
}
|
||||
-- Window transparency and blur
|
||||
local default_opacity = is_windows and 1.0 or 0.80
|
||||
config.window_background_opacity = env_number("WEZTERM_WINDOW_OPACITY", default_opacity)
|
||||
if is_macos then
|
||||
config.macos_window_background_blur = 60
|
||||
end
|
||||
-- Window transparency
|
||||
config.window_background_opacity = env_number("WEZTERM_WINDOW_OPACITY", 0.80)
|
||||
-- Keep text cells opaque by default; transparent text backgrounds can show
|
||||
-- grid/dot artifacts on some GPU/compositor combinations.
|
||||
config.text_background_opacity = env_number("WEZTERM_TEXT_OPACITY", 1.0)
|
||||
if is_windows then
|
||||
-- Disable transparency on Windows to avoid compositor repaint glitches.
|
||||
config.window_background_opacity = 1.0
|
||||
config.text_background_opacity = 1.0
|
||||
end
|
||||
-- Performance
|
||||
config.scrollback_lines = 10000
|
||||
config.enable_scroll_bar = false
|
||||
|
|
@ -151,10 +132,10 @@ config.cursor_blink_rate = 500
|
|||
config.cursor_blink_ease_in = "Constant"
|
||||
config.cursor_blink_ease_out = "Constant"
|
||||
-- Key bindings
|
||||
local keys = {
|
||||
config.keys = {
|
||||
{ key = "Enter", mods = "CTRL", action = wezterm.action({ SendString = "\x1b[13;5u" }) },
|
||||
{ key = "Enter", mods = "SHIFT", action = wezterm.action({ SendString = "\x1b[13;2u" }) },
|
||||
-- Navigate splits (via physical Win+Shift)
|
||||
-- Navigate splits
|
||||
{ key = "h", mods = "CTRL|SHIFT", action = wezterm.action.ActivatePaneDirection("Left") },
|
||||
{ key = "l", mods = "CTRL|SHIFT", action = wezterm.action.ActivatePaneDirection("Right") },
|
||||
{ key = "k", mods = "CTRL|SHIFT", action = wezterm.action.ActivatePaneDirection("Up") },
|
||||
|
|
@ -168,70 +149,6 @@ local keys = {
|
|||
{ key = "z", mods = "CTRL|SHIFT", action = wezterm.action.TogglePaneZoomState },
|
||||
{ key = "f", mods = "SHIFT|CTRL", action = wezterm.action.ToggleFullScreen },
|
||||
}
|
||||
|
||||
if is_windows then
|
||||
-- Keep CapsLock->Escape only on Windows; macOS is handled by Karabiner.
|
||||
table.insert(keys, {
|
||||
key = "CapsLock",
|
||||
mods = "NONE",
|
||||
action = wezterm.action.SendKey({ key = "Escape" }),
|
||||
})
|
||||
end
|
||||
|
||||
if is_macos then
|
||||
local mac_keys = {
|
||||
-- =====================================================
|
||||
-- NuPhy V3 fix: Physical Ctrl sends CMD to macOS.
|
||||
-- Map CMD+key -> Ctrl+key so terminal apps work naturally.
|
||||
-- =====================================================
|
||||
|
||||
-- Tmux prefix: physical Ctrl+Space
|
||||
{ key = "Space", mods = "CMD", action = wezterm.action.SendKey({ key = "Space", mods = "CTRL" }) },
|
||||
|
||||
-- Common terminal control characters
|
||||
{ key = "a", mods = "CMD", action = wezterm.action.SendKey({ key = "a", mods = "CTRL" }) },
|
||||
{ key = "b", mods = "CMD", action = wezterm.action.SendKey({ key = "b", mods = "CTRL" }) },
|
||||
{ key = "c", mods = "CMD", action = wezterm.action.SendKey({ key = "c", mods = "CTRL" }) },
|
||||
{ key = "d", mods = "CMD", action = wezterm.action.SendKey({ key = "d", mods = "CTRL" }) },
|
||||
{ key = "e", mods = "CMD", action = wezterm.action.SendKey({ key = "e", mods = "CTRL" }) },
|
||||
{ key = "f", mods = "CMD", action = wezterm.action.SendKey({ key = "f", mods = "CTRL" }) },
|
||||
{ key = "g", mods = "CMD", action = wezterm.action.SendKey({ key = "g", mods = "CTRL" }) },
|
||||
{ key = "h", mods = "CMD", action = wezterm.action.SendKey({ key = "h", mods = "CTRL" }) },
|
||||
{ key = "j", mods = "CMD", action = wezterm.action.SendKey({ key = "j", mods = "CTRL" }) },
|
||||
{ key = "k", mods = "CMD", action = wezterm.action.SendKey({ key = "k", mods = "CTRL" }) },
|
||||
{ key = "l", mods = "CMD", action = wezterm.action.SendKey({ key = "l", mods = "CTRL" }) },
|
||||
{ key = "n", mods = "CMD", action = wezterm.action.SendKey({ key = "n", mods = "CTRL" }) },
|
||||
{ key = "o", mods = "CMD", action = wezterm.action.SendKey({ key = "o", mods = "CTRL" }) },
|
||||
{ key = "p", mods = "CMD", action = wezterm.action.SendKey({ key = "p", mods = "CTRL" }) },
|
||||
{ key = "r", mods = "CMD", action = wezterm.action.SendKey({ key = "r", mods = "CTRL" }) },
|
||||
{ key = "t", mods = "CMD", action = wezterm.action.SendKey({ key = "t", mods = "CTRL" }) },
|
||||
{ key = "u", mods = "CMD", action = wezterm.action.SendKey({ key = "u", mods = "CTRL" }) },
|
||||
{ key = "v", mods = "CMD", action = wezterm.action.SendKey({ key = "v", mods = "CTRL" }) },
|
||||
{ key = "w", mods = "CMD", action = wezterm.action.SendKey({ key = "w", mods = "CTRL" }) },
|
||||
{ key = "x", mods = "CMD", action = wezterm.action.SendKey({ key = "x", mods = "CTRL" }) },
|
||||
{ key = "z", mods = "CMD", action = wezterm.action.SendKey({ key = "z", mods = "CTRL" }) },
|
||||
{ key = "[", mods = "CMD", action = wezterm.action.SendKey({ key = "[", mods = "CTRL" }) },
|
||||
{ key = "]", mods = "CMD", action = wezterm.action.SendKey({ key = "]", mods = "CTRL" }) },
|
||||
{ key = "\\", mods = "CMD", action = wezterm.action.SendKey({ key = "\\", mods = "CTRL" }) },
|
||||
|
||||
-- Physical Ctrl+Shift combos (CMD+SHIFT -> WezTerm actions)
|
||||
{ key = "f", mods = "CMD|SHIFT", action = wezterm.action.ToggleFullScreen },
|
||||
{ key = "h", mods = "CMD|SHIFT", action = wezterm.action.ActivatePaneDirection("Left") },
|
||||
{ key = "l", mods = "CMD|SHIFT", action = wezterm.action.ActivatePaneDirection("Right") },
|
||||
{ key = "k", mods = "CMD|SHIFT", action = wezterm.action.ActivatePaneDirection("Up") },
|
||||
{ key = "j", mods = "CMD|SHIFT", action = wezterm.action.ActivatePaneDirection("Down") },
|
||||
{ key = "|", mods = "CMD|SHIFT", action = wezterm.action.SplitHorizontal({ domain = "CurrentPaneDomain" }) },
|
||||
{ key = "_", mods = "CMD|SHIFT", action = wezterm.action.SplitVertical({ domain = "CurrentPaneDomain" }) },
|
||||
{ key = "w", mods = "CMD|SHIFT", action = wezterm.action.CloseCurrentPane({ confirm = false }) },
|
||||
{ key = "z", mods = "CMD|SHIFT", action = wezterm.action.TogglePaneZoomState },
|
||||
}
|
||||
|
||||
for _, key in ipairs(mac_keys) do
|
||||
table.insert(keys, key)
|
||||
end
|
||||
end
|
||||
|
||||
config.keys = keys
|
||||
-- Hyperlink rules
|
||||
config.hyperlink_rules = {
|
||||
{ regex = "\\((\\w+://\\S+)\\)", format = "$1", highlight = 1 },
|
||||
|
|
|
|||
15
.gitattributes
vendored
15
.gitattributes
vendored
|
|
@ -1,14 +1 @@
|
|||
* text=auto
|
||||
|
||||
# Keep Unix configs LF so copies into WSL don't break parsers.
|
||||
*.sh text eol=lf
|
||||
*.zsh text eol=lf
|
||||
*.conf text eol=lf
|
||||
*.lua text eol=lf
|
||||
*.toml text eol=lf
|
||||
*.vim text eol=lf
|
||||
*.vimrc text eol=lf
|
||||
*.md text eol=lf
|
||||
|
||||
# Keep PowerShell script native on Windows.
|
||||
*.ps1 text eol=crlf
|
||||
* text=auto eol=lf
|
||||
|
|
|
|||
47
.gitignore
vendored
47
.gitignore
vendored
|
|
@ -1,38 +1,5 @@
|
|||
# Created by https://www.toptal.com/developers/gitignore/api/vim,macos
|
||||
# Edit at https://www.toptal.com/developers/gitignore?templates=vim,macos
|
||||
|
||||
### macOS ###
|
||||
# General
|
||||
.DS_Store
|
||||
.AppleDouble
|
||||
.LSOverride
|
||||
|
||||
# Icon must end with two \r
|
||||
Icon
|
||||
|
||||
|
||||
# Thumbnails
|
||||
._*
|
||||
|
||||
# Files that might appear in the root of a volume
|
||||
.DocumentRevisions-V100
|
||||
.fseventsd
|
||||
.Spotlight-V100
|
||||
.TemporaryItems
|
||||
.Trashes
|
||||
.VolumeIcon.icns
|
||||
.com.apple.timemachine.donotpresent
|
||||
|
||||
# Directories potentially created on remote AFP share
|
||||
.AppleDB
|
||||
.AppleDesktop
|
||||
Network Trash Folder
|
||||
Temporary Items
|
||||
.apdisk
|
||||
|
||||
### macOS Patch ###
|
||||
# iCloud generated files
|
||||
*.icloud
|
||||
# Created by https://www.toptal.com/developers/gitignore/api/vim
|
||||
# Edit at https://www.toptal.com/developers/gitignore?templates=vim
|
||||
|
||||
### Vim ###
|
||||
# Swap
|
||||
|
|
@ -64,8 +31,6 @@ tags
|
|||
!.config/zed/
|
||||
!.config/zed/settings.json
|
||||
!.config/zed/keymap.json
|
||||
!.config/karabiner/
|
||||
!.config/karabiner/karabiner.json
|
||||
!.config/tmux/
|
||||
!.config/tmux/nord-theme.conf
|
||||
!.config/tmux/dragon-theme.conf
|
||||
|
|
@ -75,9 +40,7 @@ tags
|
|||
!.config/.vimrc
|
||||
!.config/.ideavimrc
|
||||
|
||||
# Ignore subfolders in karabiner and tmux
|
||||
.config/karabiner/**
|
||||
!.config/karabiner/karabiner.json
|
||||
# Ignore subfolders in tmux
|
||||
.config/tmux/**
|
||||
!.config/tmux/nord-theme.conf
|
||||
!.config/tmux/dragon-theme.conf
|
||||
|
|
@ -87,7 +50,5 @@ tags
|
|||
!.config/.vimrc
|
||||
!.config/.ideavimrc
|
||||
|
||||
scripts/nvim-offline-bundle-output
|
||||
|
||||
.nvimlog
|
||||
# End of https://www.toptal.com/developers/gitignore/api/vim,macos
|
||||
# End of https://www.toptal.com/developers/gitignore/api/vim
|
||||
|
|
|
|||
42
README.md
42
README.md
|
|
@ -49,45 +49,3 @@ Set in shell:
|
|||
```bash
|
||||
export KANAGAWA_THEME="wave" # or "dragon"
|
||||
```
|
||||
|
||||
## Neovim Offline Setup (Corporate Proxy)
|
||||
|
||||
If you're behind a corporate proxy where Mason, Treesitter, or plugin installs fail, you can pre-download everything on a machine with internet access and deploy offline.
|
||||
|
||||
### Prerequisites
|
||||
|
||||
On the machine with internet access, make sure you have:
|
||||
|
||||
- Docker (Linux containers enabled)
|
||||
|
||||
### Step 1: Prepare the bundle (with internet)
|
||||
|
||||
```bash
|
||||
./scripts/nvim-offline-prepare.sh
|
||||
```
|
||||
|
||||
Optionally pin Neovim to a specific tag:
|
||||
|
||||
```bash
|
||||
NVIM_RELEASE_TAG=v0.11.6 ./scripts/nvim-offline-prepare.sh
|
||||
```
|
||||
|
||||
Output: `scripts/nvim-offline-bundle-output/nvim-offline-bundle.tar.gz`
|
||||
|
||||
### Step 2: Install the bundle (on the proxy machine)
|
||||
|
||||
```bash
|
||||
./scripts/nvim-offline-install.sh ./scripts/nvim-offline-bundle-output/nvim-offline-bundle.tar.gz
|
||||
```
|
||||
|
||||
### Step 3: Enable offline mode
|
||||
|
||||
```bash
|
||||
export NVIM_OFFLINE=1
|
||||
```
|
||||
|
||||
### Re-enabling network access
|
||||
|
||||
```bash
|
||||
unset NVIM_OFFLINE
|
||||
```
|
||||
|
|
|
|||
|
|
@ -1,191 +0,0 @@
|
|||
#!/usr/bin/env bash
|
||||
set -euo pipefail
|
||||
|
||||
# nvim-offline-install.sh
|
||||
# =======================
|
||||
# Deploys pre-downloaded Neovim dependencies on an offline / corporate proxy machine.
|
||||
# Compatible with the Docker-generated offline bundle.
|
||||
|
||||
SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)"
|
||||
DOTFILES_DIR="$(dirname "$SCRIPT_DIR")"
|
||||
NVIM_CONFIG="$DOTFILES_DIR/.config/nvim"
|
||||
|
||||
RED='\033[0;31m'
|
||||
GREEN='\033[0;32m'
|
||||
YELLOW='\033[1;33m'
|
||||
NC='\033[0m'
|
||||
|
||||
info() { echo -e "${GREEN}==>${NC} $*"; }
|
||||
warn() { echo -e "${YELLOW}WARNING:${NC} $*"; }
|
||||
error() { echo -e "${RED}ERROR:${NC} $*" >&2; }
|
||||
|
||||
# ── Archive hygiene (defensive) ──────────────────────────────────────────────
|
||||
export COPYFILE_DISABLE=1
|
||||
|
||||
TARBALL="${1:-}"
|
||||
if [ -z "$TARBALL" ]; then
|
||||
error "Usage: $0 <nvim-offline-bundle.tar.gz>"
|
||||
exit 1
|
||||
fi
|
||||
|
||||
if [ ! -f "$TARBALL" ]; then
|
||||
error "File not found: $TARBALL"
|
||||
exit 1
|
||||
fi
|
||||
|
||||
copy_tree() {
|
||||
local src="$1"
|
||||
local dst="$2"
|
||||
rsync -a --delete "$src/" "$dst/"
|
||||
}
|
||||
|
||||
rewrite_mason_paths() {
|
||||
local mason_root="$1"
|
||||
local rewritten=0
|
||||
|
||||
if [ ! -d "$mason_root" ]; then
|
||||
return 0
|
||||
fi
|
||||
|
||||
while IFS= read -r -d '' file; do
|
||||
# Only rewrite text files.
|
||||
if ! LC_ALL=C grep -Iq . "$file"; then
|
||||
continue
|
||||
fi
|
||||
if ! grep -q '/mason/packages/' "$file"; then
|
||||
continue
|
||||
fi
|
||||
|
||||
local tmp
|
||||
tmp="$(mktemp)"
|
||||
local escaped_root="${mason_root//&/\\&}"
|
||||
sed -E "s|/[^\"'[:space:]]*/mason/packages/|$escaped_root/packages/|g" "$file" >"$tmp"
|
||||
|
||||
if ! cmp -s "$file" "$tmp"; then
|
||||
cat "$tmp" >"$file"
|
||||
chmod +x "$file" || true
|
||||
rewritten=$((rewritten + 1))
|
||||
fi
|
||||
rm -f "$tmp"
|
||||
done < <(find "$mason_root" -type f -print0)
|
||||
|
||||
info "Rewrote $rewritten Mason file(s) in $mason_root"
|
||||
}
|
||||
|
||||
if ! command -v rsync >/dev/null 2>&1; then
|
||||
warn "rsync not found; using cp -a fallback."
|
||||
copy_tree() {
|
||||
local src="$1"
|
||||
local dst="$2"
|
||||
rm -rf "$dst"
|
||||
mkdir -p "$dst"
|
||||
cp -a "$src/." "$dst/"
|
||||
}
|
||||
fi
|
||||
|
||||
XDG_DATA_HOME="${XDG_DATA_HOME:-$HOME/.local/share}"
|
||||
NVIM_DATA="$XDG_DATA_HOME/nvim"
|
||||
|
||||
info "Installing offline Neovim bundle..."
|
||||
echo " Source : $TARBALL"
|
||||
echo " Target : $NVIM_DATA"
|
||||
echo ""
|
||||
|
||||
# ── Extract to temp directory ───────────────────────────────────────────────
|
||||
TMPDIR="$(mktemp -d)"
|
||||
trap 'rm -rf "$TMPDIR"' EXIT
|
||||
|
||||
info "Extracting bundle..."
|
||||
# Note: Docker tarball creates a flat structure (share/ at root)
|
||||
tar \
|
||||
--no-xattrs \
|
||||
--no-acls \
|
||||
--no-selinux \
|
||||
-xzf "$TARBALL" \
|
||||
-C "$TMPDIR"
|
||||
|
||||
# [CRITICAL CHANGE]
|
||||
# The Docker script places 'share' directly in the root of the tarball.
|
||||
# We no longer look for a 'nvim-offline-bundle' subdirectory.
|
||||
BUNDLE_DIR="$TMPDIR"
|
||||
|
||||
if [ ! -d "$BUNDLE_DIR/share/nvim" ]; then
|
||||
error "Invalid bundle structure: 'share/nvim' directory not found."
|
||||
echo " Debug: Extracted contents of $TMPDIR:"
|
||||
ls -F "$TMPDIR"
|
||||
exit 1
|
||||
fi
|
||||
|
||||
# ── Sanity check: NO AppleDouble files ──────────────────────────────────────
|
||||
if find "$BUNDLE_DIR" -name '._*' | grep -q .; then
|
||||
error "AppleDouble (._*) files detected in bundle. Refusing to install."
|
||||
exit 1
|
||||
fi
|
||||
|
||||
# ── Install lazy.nvim plugins ──────────────────────────────────────────────
|
||||
# Path in bundle: share/nvim/lazy
|
||||
SOURCE_LAZY="$BUNDLE_DIR/share/nvim/lazy"
|
||||
|
||||
if [ -d "$SOURCE_LAZY" ]; then
|
||||
PLUGIN_COUNT=$(ls -1 "$SOURCE_LAZY" | wc -l)
|
||||
info "Installing $PLUGIN_COUNT plugins..."
|
||||
|
||||
if [ -d "$NVIM_DATA/lazy" ]; then
|
||||
warn "Existing lazy/ directory found — backing up to lazy.bak/"
|
||||
rm -rf "$NVIM_DATA/lazy.bak"
|
||||
mv "$NVIM_DATA/lazy" "$NVIM_DATA/lazy.bak"
|
||||
fi
|
||||
|
||||
mkdir -p "$NVIM_DATA"
|
||||
copy_tree "$SOURCE_LAZY" "$NVIM_DATA/lazy"
|
||||
fi
|
||||
|
||||
# ── Install Mason packages ─────────────────────────────────────────────────
|
||||
# Path in bundle: share/nvim/mason
|
||||
SOURCE_MASON="$BUNDLE_DIR/share/nvim/mason"
|
||||
|
||||
if [ -d "$SOURCE_MASON" ]; then
|
||||
MASON_COUNT=$(ls -1 "$SOURCE_MASON/packages" 2>/dev/null | wc -l)
|
||||
info "Installing $MASON_COUNT Mason packages..."
|
||||
|
||||
if [ -d "$NVIM_DATA/mason" ]; then
|
||||
warn "Existing mason/ directory found — backing up to mason.bak/"
|
||||
rm -rf "$NVIM_DATA/mason.bak"
|
||||
mv "$NVIM_DATA/mason" "$NVIM_DATA/mason.bak"
|
||||
fi
|
||||
|
||||
copy_tree "$SOURCE_MASON" "$NVIM_DATA/mason"
|
||||
rewrite_mason_paths "$NVIM_DATA/mason"
|
||||
|
||||
if rg -n '/tmp/runtime/share/nvim/mason/packages/' "$NVIM_DATA/mason" -S >/dev/null 2>&1; then
|
||||
error "Stale Mason package paths detected after rewrite. Aborting."
|
||||
exit 1
|
||||
fi
|
||||
fi
|
||||
|
||||
# ── Install lazy-lock.json ─────────────────────────────────────────────────
|
||||
if [ -f "$BUNDLE_DIR/lazy-lock.json" ] && [ -d "$NVIM_CONFIG" ]; then
|
||||
info "Installing lazy-lock.json..."
|
||||
cp "$BUNDLE_DIR/lazy-lock.json" "$NVIM_CONFIG/lazy-lock.json"
|
||||
fi
|
||||
|
||||
# ── Final verification ─────────────────────────────────────────────────────
|
||||
if find "$NVIM_DATA" -name '._*' | grep -q .; then
|
||||
error "Post-install AppleDouble files detected. Installation aborted."
|
||||
exit 1
|
||||
fi
|
||||
|
||||
# ── Summary ────────────────────────────────────────────────────────────────
|
||||
echo ""
|
||||
info "Installation complete!"
|
||||
echo ""
|
||||
echo " Plugins : $NVIM_DATA/lazy/"
|
||||
echo " Mason : $NVIM_DATA/mason/"
|
||||
echo ""
|
||||
echo " ┌─────────────────────────────────────────────────────────┐"
|
||||
echo " │ Add the following to your ~/.zshrc or ~/.bashrc: │"
|
||||
echo " │ │"
|
||||
echo " │ export NVIM_OFFLINE=1 │"
|
||||
echo " │ │"
|
||||
echo " │ Then restart your shell or run: source ~/.zshrc │"
|
||||
echo " └─────────────────────────────────────────────────────────┘"
|
||||
|
|
@ -1,280 +0,0 @@
|
|||
#!/usr/bin/env bash
|
||||
set -euo pipefail
|
||||
|
||||
# nvim-offline-prepare.sh
|
||||
# =======================
|
||||
# Build an offline Neovim bundle in a temporary Linux x86_64 container.
|
||||
# The container downloads and compiles all dependencies, then writes only the
|
||||
# final tarball to the host output directory.
|
||||
|
||||
SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)"
|
||||
DOTFILES_DIR="$(dirname "$SCRIPT_DIR")"
|
||||
NVIM_CONFIG="$DOTFILES_DIR/.config/nvim"
|
||||
OUTPUT_DIR="$SCRIPT_DIR/nvim-offline-bundle-output"
|
||||
TARBALL_NAME="nvim-offline-bundle.tar.gz"
|
||||
NVIM_RELEASE_TAG="${NVIM_RELEASE_TAG:-}"
|
||||
NVIM_PREPARE_TIMEOUT="${NVIM_PREPARE_TIMEOUT:-75m}"
|
||||
BUILDER_IMAGE="${BUILDER_IMAGE:-ubuntu:24.04}"
|
||||
ALLOW_EMPTY_MASON="${ALLOW_EMPTY_MASON:-0}"
|
||||
PREPARE_SKIP_MASON_PACKAGES="${PREPARE_SKIP_MASON_PACKAGES:-}"
|
||||
|
||||
# ── Colors & Helpers ────────────────────────────────────────────────────────
|
||||
RED='\033[0;31m'
|
||||
GREEN='\033[0;32m'
|
||||
YELLOW='\033[1;33m'
|
||||
NC='\033[0m'
|
||||
|
||||
info() { echo -e "${GREEN}==>${NC} $*"; }
|
||||
warn() { echo -e "${YELLOW}WARNING:${NC} $*"; }
|
||||
error() { echo -e "${RED}ERROR:${NC} $*" >&2; }
|
||||
format_duration() {
|
||||
local total_seconds="$1"
|
||||
local hours=$((total_seconds / 3600))
|
||||
local minutes=$(((total_seconds % 3600) / 60))
|
||||
local seconds=$((total_seconds % 60))
|
||||
printf "%02dh:%02dm:%02ds" "$hours" "$minutes" "$seconds"
|
||||
}
|
||||
|
||||
# ── Checks ──────────────────────────────────────────────────────────────────
|
||||
command -v docker >/dev/null 2>&1 || {
|
||||
error "Docker is required but not installed."
|
||||
exit 1
|
||||
}
|
||||
if ! docker info >/dev/null 2>&1; then
|
||||
error "Docker is not running."
|
||||
exit 1
|
||||
fi
|
||||
|
||||
if [ ! -d "$NVIM_CONFIG" ]; then
|
||||
error "Neovim config directory not found: $NVIM_CONFIG"
|
||||
exit 1
|
||||
fi
|
||||
|
||||
# Create output directory
|
||||
mkdir -p "$OUTPUT_DIR"
|
||||
# Clear previous run artifacts
|
||||
rm -f "$OUTPUT_DIR/$TARBALL_NAME"
|
||||
|
||||
info "Starting Linux container (x86_64) to build offline bundle..."
|
||||
info " Source Config : $NVIM_CONFIG"
|
||||
info " Output Dir : $OUTPUT_DIR"
|
||||
info " Docker Image : $BUILDER_IMAGE"
|
||||
if [ -n "$NVIM_RELEASE_TAG" ]; then
|
||||
info " Neovim Tag : $NVIM_RELEASE_TAG"
|
||||
else
|
||||
info " Neovim Tag : latest stable"
|
||||
fi
|
||||
info " Nvim Timeout : $NVIM_PREPARE_TIMEOUT"
|
||||
if [ "$ALLOW_EMPTY_MASON" = "1" ]; then
|
||||
warn "ALLOW_EMPTY_MASON=1 (will not fail if Mason installs 0 packages)"
|
||||
fi
|
||||
if [ -n "$PREPARE_SKIP_MASON_PACKAGES" ]; then
|
||||
warn "PREPARE_SKIP_MASON_PACKAGES=$PREPARE_SKIP_MASON_PACKAGES"
|
||||
fi
|
||||
|
||||
# ── Run Builder Container ───────────────────────────────────────────────────
|
||||
# We use --platform linux/amd64 to simulate a standard corporate Linux server.
|
||||
# This ensures binaries (blink.cmp) and parsers (treesitter) are compiled for x86_64.
|
||||
|
||||
PREPARE_STARTED_AT="$(date +%s)"
|
||||
if ! docker run --rm \
|
||||
--platform linux/amd64 \
|
||||
-v "$NVIM_CONFIG:/input/nvim:ro" \
|
||||
-v "$OUTPUT_DIR:/output" \
|
||||
-e HOST_UID="$(id -u)" \
|
||||
-e HOST_GID="$(id -g)" \
|
||||
-e TARBALL_NAME="$TARBALL_NAME" \
|
||||
-e NVIM_RELEASE_TAG="$NVIM_RELEASE_TAG" \
|
||||
-e NVIM_PREPARE_TIMEOUT="$NVIM_PREPARE_TIMEOUT" \
|
||||
-e ALLOW_EMPTY_MASON="$ALLOW_EMPTY_MASON" \
|
||||
-e PREPARE_SKIP_MASON_PACKAGES="$PREPARE_SKIP_MASON_PACKAGES" \
|
||||
"$BUILDER_IMAGE" /bin/bash -c '
|
||||
|
||||
set -euo pipefail
|
||||
NVIM_PREPARE_TIMEOUT="${NVIM_PREPARE_TIMEOUT:-75m}"
|
||||
ALLOW_EMPTY_MASON="${ALLOW_EMPTY_MASON:-0}"
|
||||
|
||||
echo ">> [Container] Installing dependencies..."
|
||||
export DEBIAN_FRONTEND=noninteractive
|
||||
# Build/runtime tools for Lazy, Treesitter, Mason, and common package installers.
|
||||
apt-get update -qq && apt-get install -y -qq \
|
||||
ca-certificates \
|
||||
git \
|
||||
curl \
|
||||
wget \
|
||||
unzip \
|
||||
tar \
|
||||
gzip \
|
||||
jq \
|
||||
build-essential \
|
||||
python3 \
|
||||
python3-venv \
|
||||
python3-pip \
|
||||
lua5.1 \
|
||||
luarocks \
|
||||
nodejs \
|
||||
npm \
|
||||
>/dev/null
|
||||
update-ca-certificates >/dev/null 2>&1 || true
|
||||
|
||||
echo ">> [Container] Preparing writable config copy..."
|
||||
mkdir -p /tmp/config
|
||||
cp -a /input/nvim /tmp/config/nvim
|
||||
chmod -R u+rwX /tmp/config/nvim
|
||||
|
||||
echo ">> [Container] Fetching Neovim release..."
|
||||
cd /tmp
|
||||
|
||||
if [ -n "${NVIM_RELEASE_TAG:-}" ]; then
|
||||
TAG="$NVIM_RELEASE_TAG"
|
||||
else
|
||||
TAG="$(curl -fsSL https://api.github.com/repos/neovim/neovim/releases/latest | jq -r .tag_name)"
|
||||
fi
|
||||
|
||||
if [ -z "$TAG" ] || [ "$TAG" = "null" ]; then
|
||||
echo "Error: Could not determine a Neovim release tag."
|
||||
exit 1
|
||||
fi
|
||||
|
||||
echo ">> [Container] Downloading $TAG for x86_64..."
|
||||
ASSET=""
|
||||
for CANDIDATE in nvim-linux-x86_64.tar.gz nvim-linux64.tar.gz; do
|
||||
URL="https://github.com/neovim/neovim/releases/download/${TAG}/${CANDIDATE}"
|
||||
if curl -fL --retry 3 --retry-delay 2 -o nvim-linux.tar.gz "$URL"; then
|
||||
ASSET="$CANDIDATE"
|
||||
break
|
||||
fi
|
||||
done
|
||||
if [ -z "$ASSET" ]; then
|
||||
echo "Error: Failed to download Neovim archive for tag $TAG."
|
||||
exit 1
|
||||
fi
|
||||
|
||||
# Verify download integrity ( > 1MB )
|
||||
FILESIZE=$(stat -c%s nvim-linux.tar.gz)
|
||||
if [ "$FILESIZE" -lt 1000000 ]; then
|
||||
echo "Error: Download failed (file too small)."
|
||||
cat nvim-linux.tar.gz
|
||||
exit 1
|
||||
fi
|
||||
|
||||
tar -xf nvim-linux.tar.gz
|
||||
NVIM_DIR="${ASSET%.tar.gz}"
|
||||
if [ ! -x "/tmp/$NVIM_DIR/bin/nvim" ]; then
|
||||
NVIM_DIR="$(find /tmp -maxdepth 1 -type d -name "nvim-linux*" | head -n1 | xargs -r basename)"
|
||||
fi
|
||||
if [ -z "$NVIM_DIR" ] || [ ! -x "/tmp/$NVIM_DIR/bin/nvim" ]; then
|
||||
echo "Error: Neovim binary directory not found after extraction."
|
||||
exit 1
|
||||
fi
|
||||
export PATH="/tmp/$NVIM_DIR/bin:$PATH"
|
||||
|
||||
# Define isolated runtime paths so host files are never added to the bundle.
|
||||
export XDG_CONFIG_HOME="/tmp/config"
|
||||
export RUNTIME_ROOT="/tmp/runtime"
|
||||
export XDG_DATA_HOME="$RUNTIME_ROOT/share"
|
||||
export XDG_STATE_HOME="$RUNTIME_ROOT/state"
|
||||
export XDG_CACHE_HOME="$RUNTIME_ROOT/cache"
|
||||
export NVIM_OFFLINE_PREPARE=1
|
||||
|
||||
mkdir -p "$XDG_DATA_HOME" "$XDG_STATE_HOME" "$XDG_CACHE_HOME"
|
||||
|
||||
echo ">> [Container] 1/5 Bootstrapping lazy.nvim..."
|
||||
# We bootstrap lazy.nvim manually first
|
||||
git clone --filter=blob:none https://github.com/folke/lazy.nvim.git \
|
||||
"$XDG_DATA_HOME/nvim/lazy/lazy.nvim" || true
|
||||
|
||||
echo ">> [Container] 2/5 Restoring Lazy plugins..."
|
||||
if ! timeout "$NVIM_PREPARE_TIMEOUT" nvim --headless \
|
||||
-c "set nomore" \
|
||||
-c "Lazy! restore" \
|
||||
-c "qa!"; then
|
||||
echo "Error: Lazy restore failed or timed out after $NVIM_PREPARE_TIMEOUT."
|
||||
exit 1
|
||||
fi
|
||||
|
||||
echo ">> [Container] 3/5 Building Treesitter parsers..."
|
||||
if ! timeout "$NVIM_PREPARE_TIMEOUT" nvim --headless \
|
||||
-c "set nomore" \
|
||||
-c "TSUpdateSync" \
|
||||
-c "qa!"; then
|
||||
echo "Error: TSUpdateSync failed or timed out after $NVIM_PREPARE_TIMEOUT."
|
||||
exit 1
|
||||
fi
|
||||
|
||||
echo ">> [Container] 4/5 Installing Mason tools..."
|
||||
if ! timeout "$NVIM_PREPARE_TIMEOUT" nvim --headless \
|
||||
-c "set nomore" \
|
||||
-c "MasonToolsInstallSync" \
|
||||
-c "qa!"; then
|
||||
echo "Error: MasonToolsInstallSync failed or timed out after $NVIM_PREPARE_TIMEOUT."
|
||||
echo "Tip: retry with NVIM_PREPARE_TIMEOUT=120m if needed."
|
||||
exit 1
|
||||
fi
|
||||
|
||||
# Verification (safe with pipefail even when directories are missing)
|
||||
PLUGIN_COUNT=0
|
||||
if [ -d "$XDG_DATA_HOME/nvim/lazy" ]; then
|
||||
PLUGIN_COUNT=$(find "$XDG_DATA_HOME/nvim/lazy" -mindepth 1 -maxdepth 1 -type d | wc -l)
|
||||
fi
|
||||
|
||||
MASON_COUNT=0
|
||||
if [ -d "$XDG_DATA_HOME/nvim/mason/packages" ]; then
|
||||
MASON_COUNT=$(find "$XDG_DATA_HOME/nvim/mason/packages" -mindepth 1 -maxdepth 1 -type d | wc -l)
|
||||
fi
|
||||
|
||||
echo ">> [Container] Installed $PLUGIN_COUNT plugins and $MASON_COUNT Mason packages."
|
||||
if [ "$MASON_COUNT" -eq 0 ]; then
|
||||
if [ "$ALLOW_EMPTY_MASON" = "1" ]; then
|
||||
echo ">> [Container] WARNING: No Mason packages detected under $XDG_DATA_HOME/nvim/mason/packages."
|
||||
else
|
||||
echo "Error: No Mason packages were installed. Aborting to avoid an incomplete offline bundle."
|
||||
echo "Tip: set ALLOW_EMPTY_MASON=1 only if you intentionally want a plugins-only bundle."
|
||||
exit 1
|
||||
fi
|
||||
fi
|
||||
|
||||
# Cleanup junk
|
||||
echo ">> [Container] 5/5 Packaging & Cleaning..."
|
||||
BUNDLE_ROOT="/tmp/bundle"
|
||||
mkdir -p "$BUNDLE_ROOT"
|
||||
cp -a "$XDG_DATA_HOME" "$BUNDLE_ROOT/share"
|
||||
find "$BUNDLE_ROOT" -name ".git" -type d -exec rm -rf {} +
|
||||
find "$BUNDLE_ROOT" -name "._*" -type f -delete
|
||||
|
||||
# Copy lockfile
|
||||
if [ -f "$XDG_CONFIG_HOME/nvim/lazy-lock.json" ]; then
|
||||
cp "$XDG_CONFIG_HOME/nvim/lazy-lock.json" "$BUNDLE_ROOT/"
|
||||
fi
|
||||
|
||||
# Create the Tarball
|
||||
tar --exclude="._*" -czf "/output/$TARBALL_NAME" -C "$BUNDLE_ROOT" .
|
||||
|
||||
# Fix ownership
|
||||
chown "$HOST_UID:$HOST_GID" "/output/$TARBALL_NAME"
|
||||
|
||||
echo ">> [Container] Done."
|
||||
'; then
|
||||
PREPARE_ENDED_AT="$(date +%s)"
|
||||
PREPARE_ELAPSED="$((PREPARE_ENDED_AT - PREPARE_STARTED_AT))"
|
||||
error "Offline prepare container failed after $(format_duration "$PREPARE_ELAPSED"). Review the container logs above."
|
||||
exit 1
|
||||
fi
|
||||
PREPARE_ENDED_AT="$(date +%s)"
|
||||
PREPARE_ELAPSED="$((PREPARE_ENDED_AT - PREPARE_STARTED_AT))"
|
||||
|
||||
# ── Summary ─────────────────────────────────────────────────────────────────
|
||||
TARBALL="$OUTPUT_DIR/$TARBALL_NAME"
|
||||
|
||||
if [ -f "$TARBALL" ]; then
|
||||
SIZE=$(du -h "$TARBALL" | cut -f1)
|
||||
echo ""
|
||||
info "Success! Bundle created at:"
|
||||
echo " $TARBALL ($SIZE)"
|
||||
echo " Elapsed: $(format_duration "$PREPARE_ELAPSED")"
|
||||
echo ""
|
||||
echo " Target Platform: Linux (x86_64)"
|
||||
else
|
||||
error "Docker container finished but tarball was not found."
|
||||
exit 1
|
||||
fi
|
||||
Loading…
Add table
Reference in a new issue