From 02476899b44b398e38cc5917e536276aa34a2c51 Mon Sep 17 00:00:00 2001 From: ITQ Date: Sun, 8 Mar 2026 16:23:50 +0300 Subject: [PATCH] init: initial commit --- .config/nvim/init.lua | 6 + .config/nvim/lua/config/autocmds.lua | 12 + .config/nvim/lua/config/diagnostics.lua | 21 + .config/nvim/lua/config/keymaps.lua | 22 + .config/nvim/lua/config/lazy.lua | 22 + .config/nvim/lua/config/options.lua | 31 + .config/nvim/lua/plugins/cmp.lua | 68 ++ .config/nvim/lua/plugins/core.lua | 14 + .config/nvim/lua/plugins/lsp.lua | 79 ++ .config/nvim/lua/plugins/tooling.lua | 46 + .config/nvim/lua/plugins/treesitter.lua | 23 + .config/nvim/lua/plugins/ui.lua | 41 + .gitconfig | 250 ++++++ .gitignore | 29 + .gitmessage.txt | 5 + .skhdrc | 145 ++++ .yabairc | 118 +++ .zshrc | 173 ++++ .zshrc.d/_zshz | 83 ++ .zshrc.d/cs.zsh | 44 + .zshrc.d/zsh-z.zsh | 1023 +++++++++++++++++++++++ commands | 2 + 22 files changed, 2257 insertions(+) create mode 100644 .config/nvim/init.lua create mode 100644 .config/nvim/lua/config/autocmds.lua create mode 100644 .config/nvim/lua/config/diagnostics.lua create mode 100644 .config/nvim/lua/config/keymaps.lua create mode 100644 .config/nvim/lua/config/lazy.lua create mode 100644 .config/nvim/lua/config/options.lua create mode 100644 .config/nvim/lua/plugins/cmp.lua create mode 100644 .config/nvim/lua/plugins/core.lua create mode 100644 .config/nvim/lua/plugins/lsp.lua create mode 100644 .config/nvim/lua/plugins/tooling.lua create mode 100644 .config/nvim/lua/plugins/treesitter.lua create mode 100644 .config/nvim/lua/plugins/ui.lua create mode 100644 .gitconfig create mode 100644 .gitignore create mode 100644 .gitmessage.txt create mode 100755 .skhdrc create mode 100755 .yabairc create mode 100644 .zshrc create mode 100755 .zshrc.d/_zshz create mode 100755 .zshrc.d/cs.zsh create mode 100755 .zshrc.d/zsh-z.zsh create mode 100644 commands diff --git a/.config/nvim/init.lua b/.config/nvim/init.lua new file mode 100644 index 0000000..0042d90 --- /dev/null +++ b/.config/nvim/init.lua @@ -0,0 +1,6 @@ +require("config.options") +require("config.keymaps") +require("config.autocmds") +require("config.diagnostics") +require("config.lazy") + diff --git a/.config/nvim/lua/config/autocmds.lua b/.config/nvim/lua/config/autocmds.lua new file mode 100644 index 0000000..a050543 --- /dev/null +++ b/.config/nvim/lua/config/autocmds.lua @@ -0,0 +1,12 @@ +local augroup = vim.api.nvim_create_augroup + +-- highlight on yank +vim.api.nvim_create_autocmd("TextYankPost", { + group = augroup("YankHighlight", { clear = true }), + callback = function() + vim.highlight.on_yank({ timeout = 200 }) + end, +}) + +-- optional: auto format on save (handled by conform too, but this is a safe place for extra logic later) + diff --git a/.config/nvim/lua/config/diagnostics.lua b/.config/nvim/lua/config/diagnostics.lua new file mode 100644 index 0000000..0311572 --- /dev/null +++ b/.config/nvim/lua/config/diagnostics.lua @@ -0,0 +1,21 @@ +local signs = { + Error = " ", + Warn = " ", + Hint = " ", + Info = " ", +} + +for type, icon in pairs(signs) do + local hl = "DiagnosticSign" .. type + vim.fn.sign_define(hl, { text = icon, texthl = hl, numhl = "" }) +end + +vim.diagnostic.config({ + virtual_text = { spacing = 2, prefix = "●" }, + signs = true, + underline = true, + update_in_insert = false, + severity_sort = true, + float = { border = "rounded", source = "if_many" }, +}) + diff --git a/.config/nvim/lua/config/keymaps.lua b/.config/nvim/lua/config/keymaps.lua new file mode 100644 index 0000000..f1fded7 --- /dev/null +++ b/.config/nvim/lua/config/keymaps.lua @@ -0,0 +1,22 @@ +local map = vim.keymap.set + +-- basics +map("n", "w", "w", { desc = "Save" }) +map("n", "q", "q", { desc = "Quit" }) + +-- better movement +map({ "n", "v" }, "H", "^", { desc = "Line start" }) +map({ "n", "v" }, "L", "$", { desc = "Line end" }) + +-- diagnostics +map("n", "[d", vim.diagnostic.goto_prev, { desc = "Prev diagnostic" }) +map("n", "]d", vim.diagnostic.goto_next, { desc = "Next diagnostic" }) +map("n", "e", vim.diagnostic.open_float, { desc = "Line diagnostic" }) +map("n", "dl", "Telescope diagnostics", { desc = "Diagnostics list" }) + +-- Telescope +map("n", "ff", "Telescope find_files", { desc = "Find files" }) +map("n", "fg", "Telescope live_grep", { desc = "Live grep" }) +map("n", "fb", "Telescope buffers", { desc = "Buffers" }) +map("n", "fh", "Telescope help_tags", { desc = "Help" }) + diff --git a/.config/nvim/lua/config/lazy.lua b/.config/nvim/lua/config/lazy.lua new file mode 100644 index 0000000..37546ea --- /dev/null +++ b/.config/nvim/lua/config/lazy.lua @@ -0,0 +1,22 @@ +-- lazy.nvim bootstrap +local lazypath = vim.fn.stdpath("data") .. "/lazy/lazy.nvim" +if not vim.loop.fs_stat(lazypath) then + vim.fn.system({ + "git", "clone", "--filter=blob:none", + "https://github.com/folke/lazy.nvim.git", + "--branch=stable", + lazypath, + }) +end +vim.opt.rtp:prepend(lazypath) + +require("lazy").setup({ + spec = { + { import = "plugins" }, + }, + defaults = { lazy = true }, + install = { colorscheme = { "gruvbox" } }, + checker = { enabled = true }, + ui = { border = "rounded" }, +}) + diff --git a/.config/nvim/lua/config/options.lua b/.config/nvim/lua/config/options.lua new file mode 100644 index 0000000..e292339 --- /dev/null +++ b/.config/nvim/lua/config/options.lua @@ -0,0 +1,31 @@ +vim.g.mapleader = " " +vim.g.maplocalleader = " " + +local opt = vim.opt +opt.number = true +opt.relativenumber = true +opt.signcolumn = "yes" +opt.cursorline = true +opt.wrap = false +opt.scrolloff = 10 +opt.sidescrolloff = 8 + +opt.tabstop = 4 +opt.shiftwidth = 4 +opt.expandtab = true +opt.smartindent = true + +opt.ignorecase = true +opt.smartcase = true +opt.incsearch = true + +opt.splitright = true +opt.splitbelow = true + +opt.termguicolors = true +opt.updatetime = 250 +opt.timeoutlen = 400 + +opt.clipboard = "unnamedplus" +opt.undofile = true + diff --git a/.config/nvim/lua/plugins/cmp.lua b/.config/nvim/lua/plugins/cmp.lua new file mode 100644 index 0000000..aa948a1 --- /dev/null +++ b/.config/nvim/lua/plugins/cmp.lua @@ -0,0 +1,68 @@ +return { + { "L3MON4D3/LuaSnip", event = "InsertEnter" }, + { "rafamadriz/friendly-snippets", event = "InsertEnter" }, + + { + "hrsh7th/nvim-cmp", + event = "InsertEnter", + dependencies = { + "hrsh7th/cmp-nvim-lsp", + "hrsh7th/cmp-buffer", + "hrsh7th/cmp-path", + "saadparwaiz1/cmp_luasnip", + "L3MON4D3/LuaSnip", + "onsails/lspkind.nvim", + "windwp/nvim-autopairs", + "rafamadriz/friendly-snippets", + }, + config = function() + local cmp = require("cmp") + local luasnip = require("luasnip") + local lspkind = require("lspkind") + + require("luasnip.loaders.from_vscode").lazy_load() + + cmp.setup({ + snippet = { + expand = function(args) luasnip.lsp_expand(args.body) end, + }, + formatting = { + format = lspkind.cmp_format({ mode = "symbol_text", maxwidth = 50 }), + }, + mapping = cmp.mapping.preset.insert({ + [""] = cmp.mapping.complete(), + [""] = cmp.mapping.confirm({ select = true }), + [""] = cmp.mapping(function(fallback) + if cmp.visible() then + cmp.select_next_item() + elseif luasnip.expand_or_jumpable() then + luasnip.expand_or_jump() + else + fallback() + end + end, { "i", "s" }), + [""] = cmp.mapping(function(fallback) + if cmp.visible() then + cmp.select_prev_item() + elseif luasnip.jumpable(-1) then + luasnip.jump(-1) + else + fallback() + end + end, { "i", "s" }), + }), + sources = cmp.config.sources({ + { name = "nvim_lsp" }, + { name = "luasnip" }, + { name = "path" }, + { name = "buffer" }, + }), + }) + + -- autopairs integration + local cmp_autopairs = require("nvim-autopairs.completion.cmp") + cmp.event:on("confirm_done", cmp_autopairs.on_confirm_done()) + end, + }, +} + diff --git a/.config/nvim/lua/plugins/core.lua b/.config/nvim/lua/plugins/core.lua new file mode 100644 index 0000000..3fd180b --- /dev/null +++ b/.config/nvim/lua/plugins/core.lua @@ -0,0 +1,14 @@ +return { + { "nvim-lua/plenary.nvim", lazy = true }, + + { "folke/which-key.nvim", event = "VeryLazy", opts = {} }, + + { "numToStr/Comment.nvim", event = "VeryLazy", opts = {} }, + + { "kylechui/nvim-surround", event = "VeryLazy", opts = {} }, + + { "windwp/nvim-autopairs", event = "InsertEnter", opts = {} }, + + { "lewis6991/gitsigns.nvim", event = "VeryLazy", opts = {} }, +} + diff --git a/.config/nvim/lua/plugins/lsp.lua b/.config/nvim/lua/plugins/lsp.lua new file mode 100644 index 0000000..2577be5 --- /dev/null +++ b/.config/nvim/lua/plugins/lsp.lua @@ -0,0 +1,79 @@ +local function lsp_keymaps(ev) + local buf = ev.buf + local map = function(mode, lhs, rhs, desc) + vim.keymap.set(mode, lhs, rhs, { buffer = buf, desc = desc }) + end + + map("n", "K", vim.lsp.buf.hover, "Hover") + map("n", "gd", vim.lsp.buf.definition, "Go to definition") + map("n", "gD", vim.lsp.buf.declaration, "Go to declaration") + map("n", "gr", vim.lsp.buf.references, "References") + map("n", "gi", vim.lsp.buf.implementation, "Implementation") + map("n", "rn", vim.lsp.buf.rename, "Rename") + map("n", "ca", vim.lsp.buf.code_action, "Code action") + + if vim.lsp.inlay_hint then + pcall(vim.lsp.inlay_hint.enable, true, { bufnr = buf }) + end +end + +return { + { "williamboman/mason.nvim", cmd = "Mason", opts = {} }, + + { + "williamboman/mason-lspconfig.nvim", + event = "VeryLazy", + dependencies = { "mason.nvim" }, + opts = { + ensure_installed = { "clangd", "gopls", "pyright", "lua_ls" }, + automatic_installation = true, + }, + }, + + -- Keep nvim-lspconfig installed (it provides server definitions), + -- but DO NOT call require("lspconfig") anymore. + { + "neovim/nvim-lspconfig", + event = { "BufReadPre", "BufNewFile" }, + dependencies = { "hrsh7th/cmp-nvim-lsp" }, + config = function() + vim.api.nvim_create_autocmd("LspAttach", { callback = lsp_keymaps }) + + local caps = vim.lsp.protocol.make_client_capabilities() + local ok, cmp_lsp = pcall(require, "cmp_nvim_lsp") + if ok then + caps = cmp_lsp.default_capabilities(caps) + end + + vim.lsp.config("clangd", { + capabilities = caps, + cmd = { "clangd", "--background-index", "--clang-tidy", "--header-insertion=iwyu" }, + }) + + vim.lsp.config("gopls", { + capabilities = caps, + settings = { + gopls = { + staticcheck = true, + analyses = { unusedparams = true, nilness = true, shadow = true }, + }, + }, + }) + + vim.lsp.config("pyright", { capabilities = caps }) + + vim.lsp.config("lua_ls", { + capabilities = caps, + settings = { + Lua = { + diagnostics = { globals = { "vim" } }, + workspace = { checkThirdParty = false }, + }, + }, + }) + + vim.lsp.enable({ "clangd", "gopls", "pyright", "lua_ls" }) + end, + }, +} + diff --git a/.config/nvim/lua/plugins/tooling.lua b/.config/nvim/lua/plugins/tooling.lua new file mode 100644 index 0000000..67a7cfd --- /dev/null +++ b/.config/nvim/lua/plugins/tooling.lua @@ -0,0 +1,46 @@ +return { + -- formatting + { + "stevearc/conform.nvim", + event = { "BufReadPre", "BufNewFile" }, + opts = { + format_on_save = { timeout_ms = 1500, lsp_fallback = true }, + formatters_by_ft = { + c = { "clang_format" }, + cpp = { "clang_format" }, + go = { "gofmt", "goimports" }, + python = { "isort", "black" }, + lua = { "stylua" }, + json = { "prettier" }, + yaml = { "prettier" }, + markdown = { "prettier" }, + }, + }, + config = function(_, opts) + require("conform").setup(opts) + vim.keymap.set("n", "f", function() + require("conform").format({ lsp_fallback = true }) + end, { desc = "Format buffer" }) + end, + }, + + -- linting (optional but nice) + { + "mfussenegger/nvim-lint", + event = { "BufReadPost", "BufNewFile" }, + config = function() + local lint = require("lint") + lint.linters_by_ft = { + python = { "flake8" }, + go = { "golangcilint" }, + } + + vim.api.nvim_create_autocmd({ "BufWritePost", "InsertLeave" }, { + callback = function() + lint.try_lint() + end, + }) + end, + }, +} + diff --git a/.config/nvim/lua/plugins/treesitter.lua b/.config/nvim/lua/plugins/treesitter.lua new file mode 100644 index 0000000..efaf883 --- /dev/null +++ b/.config/nvim/lua/plugins/treesitter.lua @@ -0,0 +1,23 @@ +return { + { + "nvim-treesitter/nvim-treesitter", + build = ":TSUpdate", + event = { "BufReadPost", "BufNewFile" }, + opts = { + ensure_installed = { + "c", "cpp", "go", "python", "lua", + "bash", "json", "yaml", "toml", + "markdown", "markdown_inline", + "regex", "vim", "vimdoc", + }, + auto_install = true, + sync_install = false, + highlight = { enable = true }, + indent = { enable = true }, + }, + config = function(_, opts) + require("nvim-treesitter").setup(opts) + end, + }, +} + diff --git a/.config/nvim/lua/plugins/ui.lua b/.config/nvim/lua/plugins/ui.lua new file mode 100644 index 0000000..42eadb9 --- /dev/null +++ b/.config/nvim/lua/plugins/ui.lua @@ -0,0 +1,41 @@ +return { + -- theme + { + "ellisonleao/gruvbox.nvim", + name = "gruvbox", + lazy = false, + priority = 1000, + opts = { + contrast = "hard", -- "hard", "soft", or "" (default medium) + transparent_mode = true, + }, + config = function(_, opts) + require("gruvbox").setup(opts) + vim.cmd.colorscheme("gruvbox") + end, + }, + + -- statusline + { + "nvim-lualine/lualine.nvim", + event = "VeryLazy", + opts = { + options = { theme = "gruvbox", globalstatus = true }, + sections = { + lualine_c = { "filename", "diff" }, + lualine_x = { "diagnostics", "filetype" }, + }, + }, + }, + + -- telescope + { + "nvim-telescope/telescope.nvim", + cmd = "Telescope", + dependencies = { "nvim-lua/plenary.nvim" }, + opts = { + defaults = { borderchars = { "─", "│", "─", "│", "╭", "╮", "╯", "╰" } }, + }, + }, +} + diff --git a/.gitconfig b/.gitconfig new file mode 100644 index 0000000..04e9cef --- /dev/null +++ b/.gitconfig @@ -0,0 +1,250 @@ +[user] + email = itq.dev@ya.ru + name = ITQ + signingkey = B13C2DAF24268258F525697A063A10A651314CE9 +[init] + defaultBranch = main +[core] + editor = nvim + whitespace = fix,-indent-with-non-tab,trailing-space,cr-at-eol + autocrlf = input +[web] + browser = firefox +[push] + default = matching +[commit] + template = ~/.gitmessage.txt + gpgsign = true +[color] + ui = auto +[color "branch"] + current = yellow bold + local = green bold + remote = cyan bold +[color "diff"] + meta = yellow bold + frag = magenta bold + old = red bold + new = green bold + whitespace = red reverse +[color "status"] + added = green bold + changed = yellow bold + untracked = red bold +[alias] + a = add --all + ai = add -i + ############# + ap = apply + as = apply --stat + ac = apply --check + ############# + ama = am --abort + amr = am --resolved + ams = am --skip + ############# + b = branch + ba = branch -a + bd = branch -d + bdd = branch -D + br = branch -r + bc = rev-parse --abbrev-ref HEAD + bu = !git rev-parse --abbrev-ref --symbolic-full-name "@{u}" + bs = !git-branch-status + ############# + c = commit + ca = commit -a + cm = commit -m + cam = commit -am + cem = commit --allow-empty -m + cd = commit --amend + cad = commit -a --amend + ced = commit --allow-empty --amend + ############# + cl = clone + cld = clone --depth 1 + clg = !sh -c 'git clone https://github.com/$1 $(basename $1)' - + clgp = !sh -c 'git clone git@github.com:$1 $(basename $1)' - + clgu = !sh -c 'git clone git@github.com:$(git config --get user.username)/$1 $1' - + ############# + cp = cherry-pick + cpa = cherry-pick --abort + cpc = cherry-pick --continue + ############# + d = diff + dp = diff --patience + dc = diff --cached + dk = diff --check + dck = diff --cached --check + dt = difftool + dct = difftool --cached + ############# + f = fetch + fo = fetch origin + fu = fetch upstream + ############# + fp = format-patch + ############# + fk = fsck + ############# + g = grep -p + ############# + l = log --oneline + lg = log --oneline --graph --decorate + ############# + ls = ls-files + lsf = !git ls-files | grep -i + ############# + m = merge + ma = merge --abort + mc = merge --continue + ms = merge --skip + ############# + o = checkout + om = checkout master + ob = checkout -b + ############# + pr = prune -v + ############# + ps = push + psf = push -f + psu = push -u + pst = push --tags + ############# + pso = push origin + psao = push --all origin + psfo = push -f origin + psuo = push -u origin + ############# + psom = push origin master + psaom = push --all origin master + psfom = push -f origin master + psuom = push -u origin master + psoc = !git push origin $(git bc) + psaoc = !git push --all origin $(git bc) + psfoc = !git push -f origin $(git bc) + psuoc = !git push -u origin $(git bc) + psdc = !git push origin :$(git bc) + ############# + pl = pull + pb = pull --rebase + ############# + plo = pull origin + pbo = pull --rebase origin + plom = pull origin master + ploc = !git pull origin $(git bc) + pbom = pull --rebase origin master + pboc = !git pull --rebase origin $(git bc) + ############# + plu = pull upstream + plum = pull upstream master + pluc = !git pull upstream $(git bc) + pbum = pull --rebase upstream master + pbuc = !git pull --rebase upstream $(git bc) + ############# + rb = rebase + rba = rebase --abort + rbc = rebase --continue + rbi = rebase --interactive + rbs = rebase --skip + ############# + re = reset + rh = reset HEAD + reh = reset --hard + rem = reset --mixed + res = reset --soft + rehh = reset --hard HEAD + remh = reset --mixed HEAD + resh = reset --soft HEAD + rehom = reset --hard origin/master + ############# + r = remote + ra = remote add + rr = remote rm + rv = remote -v + rn = remote rename + rp = remote prune + rs = remote show + rao = remote add origin + rau = remote add upstream + rro = remote remove origin + rru = remote remove upstream + rso = remote show origin + rsu = remote show upstream + rpo = remote prune origin + rpu = remote prune upstream + ############# + rmf = rm -f + rmrf = rm -r -f + ############# + s = status + sb = status -s -b + ############# + sa = stash apply + sc = stash clear + sd = stash drop + sl = stash list + sp = stash pop + ss = stash save + ssk = stash save -k + sw = stash show + st = !git stash list | wc -l 2>/dev/null | grep -oEi '[0-9][0-9]*' + ############# + t = tag + td = tag -d + ############# + w = show + wp = show -p + wr = show -p --no-color + ############# + svnr = svn rebase + svnd = svn dcommit + svnl = svn log --oneline --show-commit + ############# + subadd = !sh -c 'git submodule add https://github.com/$1 $2/$(basename $1)' - + subrm = !sh -c 'git submodule deinit -f -- $1 && rm -rf .git/modules/$1 && git rm -f $1' - + subup = submodule update --init --recursive + subpull = submodule foreach 'git pull --tags -f origin master || git pull --tags -f origin main || git pull --tags -f origin development' + ############# + assume = update-index --assume-unchanged + unassume = update-index --no-assume-unchanged + assumed = !git ls -v | grep ^h | cut -c 3- + unassumeall = !git assumed | xargs git unassume + assumeall = !git status -s | awk {'print $2'} | xargs git assume + ############# + bump = !sh -c 'git commit -am \"Version bump v$1\" && git psuoc && git release $1' - + release = !sh -c 'git tag v$1 && git pst' - + unrelease = !sh -c 'git tag -d v$1 && git pso :v$1' - + merged = !sh -c 'git o master && git plom && git bd $1 && git rpo' - + aliases = !git config -l | grep alias | cut -c 7- + snap = !git stash save 'snapshot: $(date)' && git stash apply 'stash@{0}' + bare = !sh -c 'git symbolic-ref HEAD refs/heads/$1 && git rm --cached -r . && git clean -xfd' - + whois = !sh -c 'git log -i -1 --author=\"$1\" --pretty=\"format:%an <%ae>\"' - + serve = daemon --reuseaddr --verbose --base-path=. --export-all ./.git + ############# + behind = !git rev-list --left-only --count $(git bu)...HEAD + ahead = !git rev-list --right-only --count $(git bu)...HEAD + ############# + ours = "!f() { git checkout --ours $@ && git add $@; }; f" + theirs = "!f() { git checkout --theirs $@ && git add $@; }; f" + subrepo = !sh -c 'git filter-branch --prune-empty --subdirectory-filter $1 master' - + human = name-rev --name-only --refs=refs/heads/* +[filter "lfs"] + clean = git-lfs clean -- %f + smudge = git-lfs smudge -- %f + process = git-lfs filter-process + required = true + +[pull] + rebase = true +[merge] + log = true +[tag] + gpgsign = true +[gpg] + program = gpg +[credential] + helper = store +[advice] + detachedHead = false + diff --git a/.gitignore b/.gitignore new file mode 100644 index 0000000..ef8fb0d --- /dev/null +++ b/.gitignore @@ -0,0 +1,29 @@ +# General +.DS_Store +__MACOSX/ +.AppleDouble +.LSOverride +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 + +# LazyVim lock file +lazy-lock.json diff --git a/.gitmessage.txt b/.gitmessage.txt new file mode 100644 index 0000000..6757761 --- /dev/null +++ b/.gitmessage.txt @@ -0,0 +1,5 @@ +(scope): + +[body] + +[footer(s)] diff --git a/.skhdrc b/.skhdrc new file mode 100755 index 0000000..4f05c72 --- /dev/null +++ b/.skhdrc @@ -0,0 +1,145 @@ +# ################################################################ # +# THE FOLLOWING IS AN EXPLANATION OF THE GRAMMAR THAT SKHD PARSES. # +# FOR SIMPLE EXAMPLE MAPPINGS LOOK FURTHER DOWN THIS FILE.. # +# ################################################################ # + +# A list of all built-in modifier and literal keywords can +# be found at https://github.com/koekeishiya/skhd/issues/1 +# +# A hotkey is written according to the following rules: +# +# hotkey = '<' | +# +# mode = 'name of mode' | ',' +# +# action = '[' ']' | '->' '[' ']' +# ':' | '->' ':' +# ';' | '->' ';' +# +# keysym = '-' | +# +# mod = 'modifier keyword' | '+' +# +# key = | +# +# literal = 'single letter or built-in keyword' +# +# keycode = 'apple keyboard kVK_ values (0x3C)' +# +# proc_map_lst = * +# +# proc_map = ':' | '~' | +# '*' ':' | '*' '~' +# +# string = '"' 'sequence of characters' '"' +# +# command = command is executed through '$SHELL -c' and +# follows valid shell syntax. if the $SHELL environment +# variable is not set, it will default to '/bin/bash'. +# when bash is used, the ';' delimeter can be specified +# to chain commands. +# +# to allow a command to extend into multiple lines, +# prepend '\' at the end of the previous line. +# +# an EOL character signifies the end of the bind. +# +# -> = keypress is not consumed by skhd +# +# * = matches every application not specified in +# +# ~ = application is unbound and keypress is forwarded per usual, when specified in a +# +# A mode is declared according to the following rules: +# +# mode_decl = '::' '@' ':' | '::' ':' | +# '::' '@' | '::' +# +# name = desired name for this mode, +# +# @ = capture keypresses regardless of being bound to an action +# +# command = command is executed through '$SHELL -c' and +# follows valid shell syntax. if the $SHELL environment +# variable is not set, it will default to '/bin/bash'. +# when bash is used, the ';' delimeter can be specified +# to chain commands. +# +# to allow a command to extend into multiple lines, +# prepend '\' at the end of the previous line. +# +# an EOL character signifies the end of the bind. + +# ############################################################### # +# THE FOLLOWING SECTION CONTAIN SIMPLE MAPPINGS DEMONSTRATING HOW # +# TO INTERACT WITH THE YABAI WM. THESE ARE SUPPOSED TO BE USED AS # +# A REFERENCE ONLY, WHEN MAKING YOUR OWN CONFIGURATION.. # +# ############################################################### # + +# focus window +# alt - h : yabai -m window --focus west + +# swap managed window +# shift + alt - h : yabai -m window --swap north + +# move managed window +# shift + cmd - h : yabai -m window --warp east + +# balance size of windows +# shift + alt - 0 : yabai -m space --balance + +# make floating window fill screen +# shift + alt - up : yabai -m window --grid 1:1:0:0:1:1 + +# make floating window fill left-half of screen +# shift + alt - left : yabai -m window --grid 1:2:0:0:1:1 + +# create desktop, move window and follow focus - uses jq for parsing json (brew install jq) +# shift + cmd - n : yabai -m space --create && \ +# index="$(yabai -m query --spaces --display | jq 'map(select(."is-native-fullscreen" == false))[-1].index')" && \ +# yabai -m window --space "${index}" && \ +# yabai -m space --focus "${index}" + +# fast focus desktop +# cmd + alt - x : yabai -m space --focus recent +# cmd + alt - 1 : yabai -m space --focus 1 + +# send window to desktop and follow focus +# shift + cmd - z : yabai -m window --space next; yabai -m space --focus next +# shift + cmd - 2 : yabai -m window --space 2; yabai -m space --focus 2 + +# focus monitor +# ctrl + alt - z : yabai -m display --focus prev +# ctrl + alt - 3 : yabai -m display --focus 3 + +# send window to monitor and follow focus +# ctrl + cmd - c : yabai -m window --display next; yabai -m display --focus next +# ctrl + cmd - 1 : yabai -m window --display 1; yabai -m display --focus 1 + +# move floating window +# shift + ctrl - a : yabai -m window --move rel:-20:0 +# shift + ctrl - s : yabai -m window --move rel:0:20 + +# increase window size +# shift + alt - a : yabai -m window --resize left:-20:0 +# shift + alt - w : yabai -m window --resize top:0:-20 + +# decrease window size +# shift + cmd - s : yabai -m window --resize bottom:0:-20 +# shift + cmd - w : yabai -m window --resize top:0:20 + +# set insertion point in focused container +# ctrl + alt - h : yabai -m window --insert west + +# toggle window zoom +# alt - d : yabai -m window --toggle zoom-parent +# alt - f : yabai -m window --toggle zoom-fullscreen + +# toggle window split type +# alt - e : yabai -m window --toggle split + +# float / unfloat window and center on screen +# alt - t : yabai -m window --toggle float --grid 4:4:1:1:2:2 + +# toggle sticky(+float), picture-in-picture +# alt - p : yabai -m window --toggle sticky --toggle pip diff --git a/.yabairc b/.yabairc new file mode 100755 index 0000000..592e60e --- /dev/null +++ b/.yabairc @@ -0,0 +1,118 @@ +#!/usr/bin/env sh + +# +# for this to work you must configure sudo such that +# it will be able to run the command without password +# +# see this wiki page for information: +# - https://github.com/koekeishiya/yabai/wiki/Installing-yabai-(latest-release)#configure-scripting-addition +# +# yabai -m signal --add event=dock_did_restart action="sudo yabai --load-sa" +# sudo yabai --load-sa +# + +# global settings +yabai -m config \ + external_bar off:40:0 \ + menubar_opacity 1.0 \ + mouse_follows_focus off \ + focus_follows_mouse off \ + display_arrangement_order default \ + window_origin_display default \ + window_placement second_child \ + window_insertion_point focused \ + window_zoom_persist on \ + window_shadow on \ + window_animation_duration 0.0 \ + window_animation_easing ease_out_circ \ + window_opacity_duration 0.0 \ + active_window_opacity 1.0 \ + normal_window_opacity 0.90 \ + window_opacity off \ + insert_feedback_color 0xffd75f5f \ + split_ratio 0.50 \ + split_type auto \ + auto_balance off \ + top_padding 12 \ + bottom_padding 12 \ + left_padding 12 \ + right_padding 12 \ + window_gap 06 \ + layout bsp \ + mouse_modifier fn \ + mouse_action1 move \ + mouse_action2 resize \ + mouse_drop_action swap + +yabai -m config layout bsp +yabai -m config window_placement second_child + +# yabai -m window_shadow on +# yabai -m active_window_opacity 1.0 +# yabai -m normal_window_opacity 0.90 +# Padding +yabai -m config top_padding 10 +yabai -m config bottom_padding 10 +yabai -m config right_padding 10 +yabai -m config left_padding 10 +yabai -m config window_gap 10 +# mouse settings +# yabai -m config mouse_follows_focus on +yabai -m config mouse_modifier alt +yabai -m config mouse_action1 move +yabai -m config mouse_action2 resize +yabai -m config mouse_drop_action swap + +# Disable specific apps +yabai -m rule --add app="^System Settings$" manage=off +yabai -m rule --add app="^Calculator$" manage=off +yabai -m rule --add app="^App Store$" manage=off +yabai -m rule --add app="^Calendar$" manage=off +yabai -m rule --add app="^Finder$" manage=off +yabai -m rule --add app="^Discord$" manage=off +yabai -m rule --add app="^V2BOX$" manage=off +yabai -m rule --add app="^Raycast$" manage=off +yabai -m rule --add app="^Preview$" manage=off +yabai -m rule --add app="^Archive Utility$" manage=off + +# focus window +alt - j : yabai -m window --focus west +alt - k : yabai -m window --focus south +alt - i : yabai -m window --focus north +alt - l : yabai -m window --focus east + +# swap managed window +shift + alt - i : yabai -m window --swap north +shift + alt - k : yabai -m window --swap south +shift + alt - j : yabai -m window --swap west +shift + alt - l : yabai -m window --swap east +# move managed window +shift + cmd - j : yabai -m window --warp west +shift + cmd - i : yabai -m window --warp north +shift + cmd - k : yabai -m window --warp south +shift + cmd - l : yabai -m window --warp east +# balance size of windows +shift + alt - 0 : yabai -m space --balance +# focus monitor +ctrl + alt - z : yabai -m display --focus next +ctrl + alt - x : yabai -m display --focus prev +ctrl + alt - 1 : yabai -m display --focus 1 +ctrl + alt - 2 : yabai -m display --focus 2 +ctrl + alt - 3 : yabai -m display --focus 3 +ctrl + alt - 4 : yabai -m display --focus 4 +# increase window size +shift + alt - a : yabai -m window --resize left:-20:0 +shift + alt - d : yabai -m window --resize right:-20:0 +shift + alt - w : yabai -m window --resize top:0:-20 +shift + alt - s : yabai -m window --resize bottom:0:20 +# decrease window size +shift + cmd - s : yabai -m window --resize bottom:0:-20 +shift + cmd - w : yabai -m window --resize top:0:20 +shift + cmd - a : yabai -m window --resize left:20:0 +shift + cmd - d : yabai -m window --resize right:20:0 +# toggle window split type +alt - e : yabai -m window --toggle split +# float / unfloat window and center on screen +alt - t : yabai -m window --toggle float --grid 4:4:1:1:2:2 + +echo "yabai configuration loaded.." diff --git a/.zshrc b/.zshrc new file mode 100644 index 0000000..910fc4f --- /dev/null +++ b/.zshrc @@ -0,0 +1,173 @@ +# ZSH completion cache +ZSH_COMPDUMP="$HOME/.cache/zsh/zcompdump" +mkdir -p "${ZSH_COMPDUMP:h}" + +# Compinit setup +autoload -Uz compinit +if [ "$(date +'%j')" != "$(stat -f '%Sm' -t '%j' ~/.zcompdump 2>/dev/null)" ]; then + compinit +else + compinit -C +fi + +# If you come from bash you might have to change your $PATH. +export PATH=$HOME/bin:$HOME/.local/bin:/usr/local/bin:$PATH + +# Path to your Oh My Zsh installation. +export ZSH="$HOME/.oh-my-zsh" + +# Theme +ZSH_THEME="robbyrussell" + +# Hyphen-insensitive completion. +HYPHEN_INSENSITIVE="false" + +# Uncomment one of the following lines to change the auto-update behavior +zstyle ':omz:update' mode disabled # disable automatic updates +# zstyle ':omz:update' mode auto # update automatically without asking +# zstyle ':omz:update' mode reminder # just remind me to update when it's time + +zstyle ':omz:update' verbosity minimal + +# Uncomment the following line to change how often to auto-update (in days). +# zstyle ':omz:update' frequency 13 + +# Uncomment the following line if pasting URLs and other text is messed up. +DISABLE_MAGIC_FUNCTIONS="true" + +# Uncomment the following line to disable colors in ls. +# DISABLE_LS_COLORS="true" + +# Uncomment the following line to disable auto-setting terminal title. +# DISABLE_AUTO_TITLE="true" + +# Uncomment the following line to enable command auto-correction. +# ENABLE_CORRECTION="true" + +# Disable compfix +DISABLE_COMPFIX="true" + +# Uncomment the following line to display red dots whilst waiting for completion. +# You can also set it to another string to have that shown instead of the default red dots. +# e.g. COMPLETION_WAITING_DOTS="%F{yellow}waiting...%f" +# Caution: this setting can cause issues with multiline prompts in zsh < 5.7.1 (see #5765) +# COMPLETION_WAITING_DOTS="true" + +# Uncomment the following line if you want to disable marking untracked files +# under VCS as dirty. This makes repository status check for large repositories +# much, much faster. +DISABLE_UNTRACKED_FILES_DIRTY="true" + +# Uncomment the following line if you want to change the command execution time +# stamp shown in the history command output. +# You can set one of the optional three formats: +# "mm/dd/yyyy"|"dd.mm.yyyy"|"yyyy-mm-dd" +# or set a custom format using the strftime function format specifications, +# see 'man strftime' for details. +# HIST_STAMPS="mm/dd/yyyy" + +# Would you like to use another custom folder than $ZSH/custom? +# ZSH_CUSTOM=/path/to/new-custom-folder + +# Which plugins would you like to load? +# Standard plugins can be found in $ZSH/plugins/ +# Custom plugins may be added to $ZSH_CUSTOM/plugins/ +# Example format: plugins=(rails git textmate ruby lighthouse) +plugins=(aliases alias-finder ansible argocd bun colored-man-pages colorize copybuffer copyfile dnf docker docker-compose git git-lfs golang helm kubectl kubectx nmap npm perl pm2 podman poetry poetry-env postgres rsync rust salt ssh ssh-agent sudo systemd tailscale terraform zsh-navigation-tools) + +# Brew + +eval "$(brew shellenv)" + +# Run Oh-my-zsh +source $ZSH/oh-my-zsh.sh + +# User configuration + +export MANPATH="/usr/local/man:$MANPATH" + +# Preferred editor for local and remote sessions +if [[ -n $SSH_CONNECTION ]]; then + export EDITOR='vim' +else + export EDITOR='nvim' +fi + +# Compilation flags +export ARCHFLAGS="-arch arm64" + +# Disable pager +export SYSTEMD_PAGER= + +# Talos config +export TALOSCONFIG=/Users/itq/.talos/config + +# Aliases +alias tf=terraform +alias calc='_(){ awk "BEGIN{print $*}";};_' +alias dive='docker run -ti --rm -v /var/run/docker.sock:/var/run/docker.sock docker.io/wagoodman/dive' +alias ktx=kubectx +alias kns=kubens +alias k=kubectl +alias ktop='watch -n 0.5 -d -c kubectl top pod -A --containers=true --show-swap=true --sort-by=memory --sum=true' +alias ka='kubectl apply -f' +alias kd='kubectl delete -f' +alias cdt='cd $(mktemp -d)' +alias c=clear +alias ipy='ipython --no-autoindent' +alias py='python3' +alias bat='bat --style=plain' +alias netshoot='docker run -it --rm --net host docker.io/nicolaka/netshoot:latest' +alias vim='nvim' +# InfoSec aliases +alias dirsearch=/Users/itq/infosec/tools/dirsearch/.venv/bin/dirsearch +alias pwn=/Users/itq/infosec/tools/pwntools/.venv/bin/pwn +alias pwni=/Users/itq/infosec/tools/pwntools/.venv/bin/python +alias vol2='python2 /Users/itq/infosec/tools/volatility_2/vol.py' +alias vol3=/Users/itq/infosec/tools/volatility_3/.venv/bin/vol +alias stegoveritas='docker run -it --rm -v /:/mnt bannsec/stegoveritas' + +# Brew +HOMEBREW_NO_AUTO_UPDATE=1 + +# fzf +source <($(brew --prefix)/bin/fzf --zsh) + +# Pyenv +export PYENV_ROOT="$HOME/.pyenv" +[[ -d $PYENV_ROOT/bin ]] && export PATH="$PYENV_ROOT/bin:$PATH" +eval "$(pyenv init - zsh)" + +# NVM +export NVM_DIR="$([ -z "${XDG_CONFIG_HOME-}" ] && printf %s "${HOME}/.nvm" || printf %s "${XDG_CONFIG_HOME}/nvm")" +[ -s "$NVM_DIR/nvm.sh" ] && \. "$NVM_DIR/nvm.sh" # This loads nvm + +# Yandex Cloud CLI +if [ -f '/Users/itq/.yandex-cloud/path.bash.inc' ]; then source '/Users/itq/.yandex-cloud/path.bash.inc'; fi +if [ -f '/Users/itq/.yandex-cloud/completion.zsh.inc' ]; then source '/Users/itq/.yandex-cloud/completion.zsh.inc'; fi + +# Show hidden files and folders +setopt globdots + +# No pager for systemd +export SYSTEMD_PAGER= + +# Go binaries +export PATH=$PATH:$(go env GOPATH)/bin + +# load custom zsh snippets +for f in ~/.zshrc.d/*.zsh; do source "$f"; done + +# prioritize binaries in /usr/bin +export PATH=/usr/bin:$PATH + +# Krew +export PATH="${KREW_ROOT:-$HOME/.krew}/bin:$PATH" + +# iTerm2 shell integration +test -e "${HOME}/.iterm2_shell_integration.zsh" && source "${HOME}/.iterm2_shell_integration.zsh" + +# sdkman, must be at the end of file! +export SDKMAN_DIR="$HOME/.sdkman" +[[ -s "$HOME/.sdkman/bin/sdkman-init.sh" ]] && source "$HOME/.sdkman/bin/sdkman-init.sh" + diff --git a/.zshrc.d/_zshz b/.zshrc.d/_zshz new file mode 100755 index 0000000..271e035 --- /dev/null +++ b/.zshrc.d/_zshz @@ -0,0 +1,83 @@ +#compdef zshz ${ZSHZ_CMD:-${_Z_CMD:-z}} +# +# Zsh-z - jump around with Zsh - A native Zsh version of z without awk, sort, +# date, or sed +# +# https://github.com/agkozak/zsh-z +# +# Copyright (c) 2018-2023 Alexandros Kozak +# +# 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 +# AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +# 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. +# +# z (https://github.com/rupa/z) is copyright (c) 2009 rupa deadwyler and +# licensed under the WTFPL license, Version 2.a +# +# shellcheck shell=ksh + +############################################################ +# Zsh-z COMPLETIONS +############################################################ +emulate -L zsh +(( ZSHZ_DEBUG )) && + setopt LOCAL_OPTIONS WARN_CREATE_GLOBAL NO_WARN_NESTED_VAR 2> /dev/null + +# TODO: This routine currently reproduces z's feature of allowing spaces to be +# used as wildcards in completions, so that +# +# z us lo bi +# +# can expand to +# +# z /usr/local/bin +# +# but it also reproduces z's buggy display on the commandline, viz. +# +# z us lo /usr/local/bin +# +# Address. + +local completions expl completion +local -a completion_list + +completions=$(zshz --complete ${(@)words:1}) +[[ -z $completions ]] && return 1 + +for completion in ${(f)completions[@]}; do + if (( ZSHZ_TILDE )) && [[ $completion == ${HOME}* ]]; then + completion="~${(q)${completion#${HOME}}}" + else + completion="${(q)completion}" + fi + completion_list+=( $completion ) +done + +_description -V completion_list expl 'directories' + +if [[ $ZSHZ_COMPLETION == 'legacy' ]]; then + compadd "${expl[@]}" -QU -- "${completion_list[@]}" +else + compadd "${expl[@]}" -QU -V zsh-z -- "${completion_list[@]}" +fi + +compstate[insert]=menu + +return 0 + +# vim: ft=zsh:fdm=indent:ts=2:et:sts=2:sw=2: + diff --git a/.zshrc.d/cs.zsh b/.zshrc.d/cs.zsh new file mode 100755 index 0000000..65bd56f --- /dev/null +++ b/.zshrc.d/cs.zsh @@ -0,0 +1,44 @@ +# Common clipboard detection +_get_clip_cmd() { + if command -v pbcopy >/dev/null 2>&1; then + echo "pbcopy" + elif command -v xclip >/dev/null 2>&1; then + echo "xclip -selection clipboard" + elif command -v clip >/dev/null 2>&1; then + echo "clip" + else + echo "Error: No clipboard command found" >&2 + return 1 + fi +} + +cs() { + local output clip_cmd + clip_cmd=$(_get_clip_cmd) || return 1 + + if [ $# -eq 0 ]; then + output=$(cat && printf X) + output=${output%X} + else + output=$("$@") + fi + + printf '%s' "$output" + printf '%s' "$output" | eval "$clip_cmd" +} + +cse() { + local output clip_cmd + clip_cmd=$(_get_clip_cmd) || return 1 + + if [ $# -eq 0 ]; then + output=$(cat && printf X) + output=${output%X} + else + output=$("$@" 2>&1) + fi + + printf '%s' "$output" + printf '%s' "$output" | eval "$clip_cmd" +} + diff --git a/.zshrc.d/zsh-z.zsh b/.zshrc.d/zsh-z.zsh new file mode 100755 index 0000000..7f1b1be --- /dev/null +++ b/.zshrc.d/zsh-z.zsh @@ -0,0 +1,1023 @@ +################################################################################ +# Zsh-z - jump around with Zsh - A native Zsh version of z without awk, sort, +# date, or sed +# +# https://github.com/agkozak/zsh-z +# +# Copyright (c) 2018-2025 Alexandros Kozak +# +# 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 +# AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +# 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. +# +# z (https://github.com/rupa/z) is copyright (c) 2009 rupa deadwyler and +# licensed under the WTFPL license, Version 2. +# +# Zsh-z maintains a jump-list of the directories you actually use. +# +# INSTALL: +# * put something like this in your .zshrc: +# source /path/to/zsh-z.plugin.zsh +# * cd around for a while to build up the database +# +# USAGE: +# * z foo cd to the most frecent directory matching foo +# * z foo bar cd to the most frecent directory matching both foo and bar +# (e.g. /foo/bat/bar/quux) +# * z -r foo cd to the highest ranked directory matching foo +# * z -t foo cd to most recently accessed directory matching foo +# * z -l foo List matches instead of changing directories +# * z -e foo Echo the best match without changing directories +# * z -c foo Restrict matches to subdirectories of PWD +# * z -x Remove a directory (default: PWD) from the database +# * z -xR Remove a directory (default: PWD) and its subdirectories from +# the database +# +# ENVIRONMENT VARIABLES: +# +# ZSHZ_CASE -> if `ignore', pattern matching is case-insensitive; if `smart', +# pattern matching is case-insensitive only when the pattern is all +# lowercase +# ZSHZ_CD -> the directory-changing command that is used (default: builtin cd) +# ZSHZ_CMD -> name of command (default: z) +# ZSHZ_COMPLETION -> completion method (default: 'frecent'; 'legacy' for +# alphabetic sorting) +# ZSHZ_DATA -> name of datafile (default: ~/.z) +# ZSHZ_EXCLUDE_DIRS -> array of directories to exclude from your database +# (default: empty) +# ZSHZ_KEEP_DIRS -> array of directories that should not be removed from the +# database, even if they are not currently available (default: empty) +# ZSHZ_MAX_SCORE -> maximum combined score the database entries can have +# before beginning to age (default: 9000) +# ZSHZ_NO_RESOLVE_SYMLINKS -> '1' prevents symlink resolution +# ZSHZ_OWNER -> your username (if you want use Zsh-z while using sudo -s) +# ZSHZ_UNCOMMON -> if 1, do not jump to "common directories," but rather drop +# subdirectories based on what the search string was (default: 0) +################################################################################ + +autoload -U is-at-least + +if ! is-at-least 4.3.11; then + print "Zsh-z requires Zsh v4.3.11 or higher." >&2 && exit +fi + +############################################################ +# The help message +# +# Globals: +# ZSHZ_CMD +############################################################ +_zshz_usage() { + print "Usage: ${ZSHZ_CMD:-${_Z_CMD:-z}} [OPTION]... [ARGUMENT] +Jump to a directory that you have visited frequently or recently, or a bit of both, based on the partial string ARGUMENT. + +With no ARGUMENT, list the directory history in ascending rank. + + --add Add a directory to the database + -c Only match subdirectories of the current directory + -e Echo the best match without going to it + -h Display this help and exit + -l List all matches without going to them + -r Match by rank + -t Match by recent access + -x Remove a directory from the database (by default, the current directory) + -xR Remove a directory and its subdirectories from the database (by default, the current directory)" | + fold -s -w $COLUMNS >&2 +} + +# Load zsh/datetime module, if necessary +(( ${+EPOCHSECONDS} )) || zmodload zsh/datetime + +# Global associative array for internal use +typeset -gA ZSHZ + +# Fallback utilities in case Zsh lacks zsh/files (as is the case with MobaXterm) +ZSHZ[CHOWN]='chown' +ZSHZ[MV]='mv' +ZSHZ[RM]='rm' +# Try to load zsh/files utilities +if [[ ${builtins[zf_chown]-} != 'defined' || + ${builtins[zf_mv]-} != 'defined' || + ${builtins[zf_rm]-} != 'defined' ]]; then + zmodload -F zsh/files b:zf_chown b:zf_mv b:zf_rm &> /dev/null +fi +# Use zsh/files, if it is available +[[ ${builtins[zf_chown]-} == 'defined' ]] && ZSHZ[CHOWN]='zf_chown' +[[ ${builtins[zf_mv]-} == 'defined' ]] && ZSHZ[MV]='zf_mv' +[[ ${builtins[zf_rm]-} == 'defined' ]] && ZSHZ[RM]='zf_rm' + +# Load zsh/system, if necessary +[[ ${modules[zsh/system]-} == 'loaded' ]] || zmodload zsh/system &> /dev/null + +# Make sure ZSHZ_EXCLUDE_DIRS has been declared so that other scripts can +# simply append to it +(( ${+ZSHZ_EXCLUDE_DIRS} )) || typeset -gUa ZSHZ_EXCLUDE_DIRS + +# Determine if zsystem flock is available +zsystem supports flock &> /dev/null && ZSHZ[USE_FLOCK]=1 + +# Determine if `print -v' is supported +is-at-least 5.3.0 && ZSHZ[PRINTV]=1 + +############################################################ +# The Zsh-z Command +# +# Globals: +# ZSHZ +# ZSHZ_CASE +# ZSHZ_CD +# ZSHZ_COMPLETION +# ZSHZ_DATA +# ZSHZ_DEBUG +# ZSHZ_EXCLUDE_DIRS +# ZSHZ_KEEP_DIRS +# ZSHZ_MAX_SCORE +# ZSHZ_OWNER +# +# Arguments: +# $* Command options and arguments +############################################################ +zshz() { + + # Don't use `emulate -L zsh' - it breaks PUSHD_IGNORE_DUPS + setopt LOCAL_OPTIONS NO_KSH_ARRAYS NO_SH_WORD_SPLIT EXTENDED_GLOB UNSET + (( ZSHZ_DEBUG )) && setopt LOCAL_OPTIONS WARN_CREATE_GLOBAL + + local REPLY + local -a lines + + # Allow the user to specify a custom datafile in $ZSHZ_DATA (or legacy $_Z_DATA) + local custom_datafile="${ZSHZ_DATA:-$_Z_DATA}" + + # If a datafile was provided as a standalone file without a directory path + # print a warning and exit + if [[ -n ${custom_datafile} && ${custom_datafile} != */* ]]; then + print "ERROR: You configured a custom Zsh-z datafile (${custom_datafile}), but have not specified its directory." >&2 + exit + fi + + # If the user specified a datafile, use that or default to ~/.z + # If the datafile is a symlink, it gets dereferenced + local datafile=${${custom_datafile:-$HOME/.z}:A} + + # If the datafile is a directory, print a warning and exit + if [[ -d $datafile ]]; then + print "ERROR: Zsh-z's datafile (${datafile}) is a directory." >&2 + exit + fi + + # Make sure that the datafile exists before attempting to read it or lock it + # for writing + [[ -f $datafile ]] || { mkdir -p "${datafile:h}" && touch "$datafile" } + + # Bail if we don't own the datafile and $ZSHZ_OWNER is not set + [[ -z ${ZSHZ_OWNER:-${_Z_OWNER}} && -f $datafile && ! -O $datafile ]] && + return + + # Load the datafile into an array and parse it + lines=( ${(f)"$(< $datafile)"} ) + # Discard entries that are incomplete or incorrectly formatted + lines=( ${(M)lines:#/*\|[[:digit:]]##[.,]#[[:digit:]]#\|[[:digit:]]##} ) + + ############################################################ + # Add a path to or remove one from the datafile + # + # Globals: + # ZSHZ + # ZSHZ_EXCLUDE_DIRS + # ZSHZ_OWNER + # + # Arguments: + # $1 Which action to perform (--add/--remove) + # $2 The path to add + ############################################################ + _zshz_add_or_remove_path() { + local action=${1} + shift + + if [[ $action == '--add' ]]; then + + # TODO: The following tasks are now handled by _agkozak_precmd. Dead code? + + # Don't add $HOME + [[ $* == $HOME ]] && return + + # Don't track directory trees excluded in ZSHZ_EXCLUDE_DIRS + local exclude + for exclude in ${(@)ZSHZ_EXCLUDE_DIRS:-${(@)_Z_EXCLUDE_DIRS}}; do + case $* in + ${exclude}|${exclude}/*) return ;; + esac + done + fi + + # A temporary file that gets copied over the datafile if all goes well + local tempfile="${datafile}.${RANDOM}" + + # See https://github.com/rupa/z/pull/199/commits/ed6eeed9b70d27c1582e3dd050e72ebfe246341c + if (( ZSHZ[USE_FLOCK] )); then + + local lockfd + + # Grab exclusive lock (released when function exits) + zsystem flock -f lockfd "$datafile" 2> /dev/null || return + + fi + + integer tmpfd + case $action in + --add) + exec {tmpfd}>|"$tempfile" # Open up tempfile for writing + _zshz_update_datafile $tmpfd "$*" + local ret=$? + ;; + --remove) + local xdir # Directory to be removed + + if (( ${ZSHZ_NO_RESOLVE_SYMLINKS:-${_Z_NO_RESOLVE_SYMLINKS}} )); then + [[ -d ${${*:-${PWD}}:a} ]] && xdir=${${*:-${PWD}}:a} + else + [[ -d ${${*:-${PWD}}:A} ]] && xdir=${${*:-${PWD}}:a} + fi + + local -a lines_to_keep + if (( ${+opts[-R]} )); then + # Prompt user before deleting entire database + if [[ $xdir == '/' ]] && ! read -q "?Delete entire Zsh-z database? "; then + print && return 1 + fi + # All of the lines that don't match the directory to be deleted + lines_to_keep=( ${lines:#${xdir}\|*} ) + # Or its subdirectories + lines_to_keep=( ${lines_to_keep:#${xdir%/}/**} ) + else + # All of the lines that don't match the directory to be deleted + lines_to_keep=( ${lines:#${xdir}\|*} ) + fi + if [[ $lines != "$lines_to_keep" ]]; then + lines=( $lines_to_keep ) + else + return 1 # The $PWD isn't in the datafile + fi + exec {tmpfd}>|"$tempfile" # Open up tempfile for writing + print -u $tmpfd -l -- $lines + local ret=$? + ;; + esac + + if (( tmpfd != 0 )); then + # Close tempfile + exec {tmpfd}>&- + fi + + if (( ret != 0 )); then + # Avoid clobbering the datafile if the write to tempfile failed + ${ZSHZ[RM]} -f "$tempfile" + return $ret + fi + + local owner + owner=${ZSHZ_OWNER:-${_Z_OWNER}} + + if (( ZSHZ[USE_FLOCK] )); then + # An unsual case: if inside Docker container where datafile could be bind + # mounted + if [[ -r '/proc/1/cgroup' && "$(< '/proc/1/cgroup')" == *docker* ]]; then + print "$(< "$tempfile")" > "$datafile" 2> /dev/null + ${ZSHZ[RM]} -f "$tempfile" + # All other cases + else + ${ZSHZ[MV]} "$tempfile" "$datafile" 2> /dev/null || + ${ZSHZ[RM]} -f "$tempfile" + fi + + if [[ -n $owner ]]; then + ${ZSHZ[CHOWN]} ${owner}:"$(id -ng ${owner})" "$datafile" + fi + else + if [[ -n $owner ]]; then + ${ZSHZ[CHOWN]} "${owner}":"$(id -ng "${owner}")" "$tempfile" + fi + ${ZSHZ[MV]} -f "$tempfile" "$datafile" 2> /dev/null || + ${ZSHZ[RM]} -f "$tempfile" + fi + + # In order to make z -x work, we have to disable zsh-z's adding + # to the database until the user changes directory and the + # chpwd_functions are run + if [[ $action == '--remove' ]]; then + ZSHZ[DIRECTORY_REMOVED]=1 + fi + } + + ############################################################ + # Read the current datafile contents, update them, "age" them + # when the total rank gets high enough, and print the new + # contents to STDOUT. + # + # Globals: + # ZSHZ_KEEP_DIRS + # ZSHZ_MAX_SCORE + # + # Arguments: + # $1 File descriptor linked to tempfile + # $2 Path to be added to datafile + ############################################################ + _zshz_update_datafile() { + + integer fd=$1 + local -A rank time + + # Characters special to the shell (such as '[]') are quoted with backslashes + # See https://github.com/rupa/z/issues/246 + local add_path=${(q)2} + + local -a existing_paths + local now=$EPOCHSECONDS line dir + local path_field rank_field time_field count x + + rank[$add_path]=1 + time[$add_path]=$now + + # Remove paths from database if they no longer exist + for line in $lines; do + if [[ ! -d ${line%%\|*} ]]; then + for dir in ${(@)ZSHZ_KEEP_DIRS}; do + if [[ ${line%%\|*} == ${dir}/* || + ${line%%\|*} == $dir || + $dir == '/' ]]; then + existing_paths+=( $line ) + fi + done + else + existing_paths+=( $line ) + fi + done + lines=( $existing_paths ) + + for line in $lines; do + path_field=${(q)line%%\|*} + rank_field=${${line%\|*}#*\|} + time_field=${line##*\|} + + # When a rank drops below 1, drop the path from the database + (( rank_field < 1 )) && continue + + if [[ $path_field == $add_path ]]; then + rank[$path_field]=$rank_field + (( rank[$path_field]++ )) + time[$path_field]=$now + else + rank[$path_field]=$rank_field + time[$path_field]=$time_field + fi + (( count += rank_field )) + done + if (( count > ${ZSHZ_MAX_SCORE:-${_Z_MAX_SCORE:-9000}} )); then + # Aging + for x in ${(k)rank}; do + print -u $fd -- "$x|$(( 0.99 * rank[$x] ))|${time[$x]}" || return 1 + done + else + for x in ${(k)rank}; do + print -u $fd -- "$x|${rank[$x]}|${time[$x]}" || return 1 + done + fi + } + + ############################################################ + # The original tab completion method + # + # String processing is smartcase -- case-insensitive if the + # search string is lowercase, case-sensitive if there are + # any uppercase letters. Spaces in the search string are + # treated as *'s in globbing. Read the contents of the + # datafile and print matches to STDOUT. + # + # Arguments: + # $1 The string to be completed + ############################################################ + _zshz_legacy_complete() { + + local line path_field path_field_normalized + + # Replace spaces in the search string with asterisks for globbing + 1=${1//[[:space:]]/*} + + for line in $lines; do + + path_field=${line%%\|*} + + path_field_normalized=$path_field + if (( ZSHZ_TRAILING_SLASH )); then + path_field_normalized=${path_field%/}/ + fi + + # If the search string is all lowercase, the search will be case-insensitive + if [[ $1 == "${1:l}" && ${path_field_normalized:l} == *${~1}* ]]; then + print -- $path_field + # Otherwise, case-sensitive + elif [[ $path_field_normalized == *${~1}* ]]; then + print -- $path_field + fi + + done + # TODO: Search strings with spaces in them are currently treated case- + # insensitively. + } + + ############################################################ + # `print' or `printf' to REPLY + # + # Variable assignment through command substitution, of the + # form + # + # foo=$( bar ) + # + # requires forking a subshell; on Cygwin/MSYS2/WSL1 that can + # be surprisingly slow. Zsh-z avoids doing that by printing + # values to the variable REPLY. Since Zsh v5.3.0 that has + # been possible with `print -v'; for earlier versions of the + # shell, the values are placed on the editing buffer stack + # and then `read' into REPLY. + # + # Globals: + # ZSHZ + # + # Arguments: + # Options and parameters for `print' + ############################################################ + _zshz_printv() { + # NOTE: For a long time, ZSH's `print -v' had a tendency + # to mangle multibyte strings: + # + # https://www.zsh.org/mla/workers/2020/msg00307.html + # + # The bug was fixed in late 2020: + # + # https://github.com/zsh-users/zsh/commit/b6ba74cd4eaec2b6cb515748cf1b74a19133d4a4#diff-32bbef18e126b837c87b06f11bfc61fafdaa0ed99fcb009ec53f4767e246b129 + # + # In order to support shells with the bug, we must use a form of `printf`, + # which does not exhibit the undesired behavior. See + # + # https://www.zsh.org/mla/workers/2020/msg00308.html + + if (( ZSHZ[PRINTV] )); then + builtin print -v REPLY -f %s $@ + else + builtin print -z $@ + builtin read -rz REPLY + fi + } + + ############################################################ + # If matches share a common root, find it, and put it in + # REPLY for _zshz_output to use. + # + # Arguments: + # $1 Name of associative array of matches and ranks + ############################################################ + _zshz_find_common_root() { + local -a common_matches + local x short + + common_matches=( ${(@Pk)1} ) + + for x in ${(@)common_matches}; do + if [[ -z $short ]] || (( $#x < $#short )) || [[ $x != ${short}/* ]]; then + short=$x + fi + done + + [[ $short == '/' ]] && return + + for x in ${(@)common_matches}; do + [[ $x != $short* ]] && return + done + + _zshz_printv -- $short + } + + ############################################################ + # Calculate a common root, if there is one. Then do one of + # the following: + # + # 1) Print a list of completions in frecent order; + # 2) List them (z -l) to STDOUT; or + # 3) Put a common root or best match into REPLY + # + # Globals: + # ZSHZ_UNCOMMON + # + # Arguments: + # $1 Name of an associative array of matches and ranks + # $2 The best match or best case-insensitive match + # $3 Whether to produce a completion, a list, or a root or + # match + ############################################################ + _zshz_output() { + + local match_array=$1 match=$2 format=$3 + local common k x + local -a descending_list output + local -A output_matches + + output_matches=( ${(Pkv)match_array} ) + + _zshz_find_common_root $match_array + common=$REPLY + + case $format in + + completion) + for k in ${(@k)output_matches}; do + _zshz_printv -f "%.2f|%s" ${output_matches[$k]} $k + descending_list+=( ${(f)REPLY} ) + REPLY='' + done + descending_list=( ${${(@On)descending_list}#*\|} ) + print -l $descending_list + ;; + + list) + local path_to_display + for x in ${(k)output_matches}; do + if (( ${output_matches[$x]} )); then + path_to_display=$x + (( ZSHZ_TILDE )) && + path_to_display=${path_to_display/#${HOME}/\~} + _zshz_printv -f "%-10d %s\n" ${output_matches[$x]} $path_to_display + output+=( ${(f)REPLY} ) + REPLY='' + fi + done + if [[ -n $common ]]; then + (( ZSHZ_TILDE )) && common=${common/#${HOME}/\~} + (( $#output > 1 )) && printf "%-10s %s\n" 'common:' $common + fi + # -lt + if (( $+opts[-t] )); then + for x in ${(@On)output}; do + print -- $x + done + # -lr + elif (( $+opts[-r] )); then + for x in ${(@on)output}; do + print -- $x + done + # -l + else + for x in ${(@on)output}; do + print $x + done + fi + ;; + + *) + if (( ! ZSHZ_UNCOMMON )) && [[ -n $common ]]; then + _zshz_printv -- $common + else + _zshz_printv -- ${(P)match} + fi + ;; + esac + } + + ############################################################ + # Match a pattern by rank, time, or a combination of the + # two, and output the results as completions, a list, or a + # best match. + # + # Globals: + # ZSHZ + # ZSHZ_CASE + # ZSHZ_KEEP_DIRS + # ZSHZ_OWNER + # + # Arguments: + # #1 Pattern to match + # $2 Matching method (rank, time, or [default] frecency) + # $3 Output format (completion, list, or [default] store + # in REPLY + ############################################################ + _zshz_find_matches() { + setopt LOCAL_OPTIONS NO_EXTENDED_GLOB + + local fnd=$1 method=$2 format=$3 + + local -a existing_paths + local line dir path_field rank_field time_field rank dx escaped_path_field + local -A matches imatches + local best_match ibest_match hi_rank=-9999999999 ihi_rank=-9999999999 + + # Remove paths from database if they no longer exist + for line in $lines; do + if [[ ! -d ${line%%\|*} ]]; then + for dir in ${(@)ZSHZ_KEEP_DIRS}; do + if [[ ${line%%\|*} == ${dir}/* || + ${line%%\|*} == $dir || + $dir == '/' ]]; then + existing_paths+=( $line ) + fi + done + else + existing_paths+=( $line ) + fi + done + lines=( $existing_paths ) + + for line in $lines; do + path_field=${line%%\|*} + rank_field=${${line%\|*}#*\|} + time_field=${line##*\|} + + case $method in + rank) rank=$rank_field ;; + time) (( rank = time_field - EPOCHSECONDS )) ;; + *) + # Frecency routine + (( dx = EPOCHSECONDS - time_field )) + rank=$(( 10000 * rank_field * (3.75/( (0.0001 * dx + 1) + 0.25)) )) + ;; + esac + + # Use spaces as wildcards + local q=${fnd//[[:space:]]/\*} + + # If $ZSHZ_TRAILING_SLASH is set, use path_field with a trailing slash for matching. + local path_field_normalized=$path_field + if (( ZSHZ_TRAILING_SLASH )); then + path_field_normalized=${path_field%/}/ + fi + + # If $ZSHZ_CASE is 'ignore', be case-insensitive. + # + # If it's 'smart', be case-insensitive unless the string to be matched + # includes capital letters. + # + # Otherwise, the default behavior of Zsh-z is to match case-sensitively if + # possible, then to fall back on a case-insensitive match if possible. + if [[ $ZSHZ_CASE == 'smart' && ${1:l} == $1 && + ${path_field_normalized:l} == ${~q:l} ]]; then + imatches[$path_field]=$rank + elif [[ $ZSHZ_CASE != 'ignore' && $path_field_normalized == ${~q} ]]; then + matches[$path_field]=$rank + elif [[ $ZSHZ_CASE != 'smart' && ${path_field_normalized:l} == ${~q:l} ]]; then + imatches[$path_field]=$rank + fi + + # Escape characters that would cause "invalid subscript" errors + # when accessing the associative array. + escaped_path_field=${path_field//'\'/'\\'} + escaped_path_field=${escaped_path_field//'`'/'\`'} + escaped_path_field=${escaped_path_field//'('/'\('} + escaped_path_field=${escaped_path_field//')'/'\)'} + escaped_path_field=${escaped_path_field//'['/'\['} + escaped_path_field=${escaped_path_field//']'/'\]'} + + if (( matches[$escaped_path_field] )) && + (( matches[$escaped_path_field] > hi_rank )); then + best_match=$path_field + hi_rank=${matches[$escaped_path_field]} + elif (( imatches[$escaped_path_field] )) && + (( imatches[$escaped_path_field] > ihi_rank )); then + ibest_match=$path_field + ihi_rank=${imatches[$escaped_path_field]} + ZSHZ[CASE_INSENSITIVE]=1 + fi + done + + # Return 1 when there are no matches + [[ -z $best_match && -z $ibest_match ]] && return 1 + + if [[ -n $best_match ]]; then + _zshz_output matches best_match $format + elif [[ -n $ibest_match ]]; then + _zshz_output imatches ibest_match $format + fi + } + + # THE MAIN ROUTINE + + local -A opts + + zparseopts -E -D -A opts -- \ + -add \ + -complete \ + c \ + e \ + h \ + -help \ + l \ + r \ + R \ + t \ + x + + if [[ $1 == '--' ]]; then + shift + elif [[ -n ${(M)@:#-*} && -z $compstate ]]; then + print "Improper option(s) given." + _zshz_usage + return 1 + fi + + local opt output_format method='frecency' fnd prefix req + + for opt in ${(k)opts}; do + case $opt in + --add) + [[ ! -d $* ]] && return 1 + local dir + # Cygwin and MSYS2 have a hard time with relative paths expressed from / + if [[ $OSTYPE == (cygwin|msys) && $PWD == '/' && $* != /* ]]; then + set -- "/$*" + fi + if (( ${ZSHZ_NO_RESOLVE_SYMLINKS:-${_Z_NO_RESOLVE_SYMLINKS}} )); then + dir=${*:a} + else + dir=${*:A} + fi + _zshz_add_or_remove_path --add "$dir" + return + ;; + --complete) + if [[ -s $datafile && ${ZSHZ_COMPLETION:-frecent} == 'legacy' ]]; then + _zshz_legacy_complete "$1" + return + fi + output_format='completion' + ;; + -c) [[ $* == ${PWD}/* || $PWD == '/' ]] || prefix="$PWD " ;; + -h|--help) + _zshz_usage + return + ;; + -l) output_format='list' ;; + -r) method='rank' ;; + -t) method='time' ;; + -x) + # Cygwin and MSYS2 have a hard time with relative paths expressed from / + if [[ $OSTYPE == (cygwin|msys) && $PWD == '/' && $* != /* ]]; then + set -- "/$*" + fi + _zshz_add_or_remove_path --remove $* + return + ;; + esac + done + req="$*" + fnd="$prefix$*" + + [[ -n $fnd && $fnd != "$PWD " ]] || { + [[ $output_format != 'completion' ]] && output_format='list' + } + + ######################################################### + # Allow the user to specify directory-changing command + # using $ZSHZ_CD (default: builtin cd). + # + # Globals: + # ZSHZ_CD + # + # Arguments: + # $* Path + ######################################################### + zshz_cd() { + setopt LOCAL_OPTIONS NO_WARN_CREATE_GLOBAL + + if [[ -z $ZSHZ_CD ]]; then + builtin cd "$*" + else + ${=ZSHZ_CD} "$*" + fi + } + + ######################################################### + # If $ZSHZ_ECHO == 1, display paths as you jump to them. + # If it is also the case that $ZSHZ_TILDE == 1, display + # the home directory as a tilde. + ######################################################### + _zshz_echo() { + if (( ZSHZ_ECHO )); then + if (( ZSHZ_TILDE )); then + print ${PWD/#${HOME}/\~} + else + print $PWD + fi + fi + } + + if [[ ${@: -1} == /* ]] && (( ! $+opts[-e] && ! $+opts[-l] )); then + # cd if possible; echo the new path if $ZSHZ_ECHO == 1 + [[ -d ${@: -1} ]] && zshz_cd ${@: -1} && _zshz_echo && return + fi + + # With option -c, make sure query string matches beginning of matches; + # otherwise look for matches anywhere in paths + + # zpm-zsh/colors has a global $c, so we'll avoid math expressions here + if [[ ! -z ${(tP)opts[-c]} ]]; then + _zshz_find_matches "$fnd*" $method $output_format + else + _zshz_find_matches "*$fnd*" $method $output_format + fi + + local ret2=$? + + local cd + cd=$REPLY + + # New experimental "uncommon" behavior + # + # If the best choice at this point is something like /foo/bar/foo/bar, and the # search pattern is `bar', go to /foo/bar/foo/bar; but if the search pattern + # is `foo', go to /foo/bar/foo + if (( ZSHZ_UNCOMMON )) && [[ -n $cd ]]; then + if [[ -n $cd ]]; then + + # In the search pattern, replace spaces with * + local q=${fnd//[[:space:]]/\*} + q=${q%/} # Trailing slash has to be removed + + # As long as the best match is not case-insensitive + if (( ! ZSHZ[CASE_INSENSITIVE] )); then + # Count the number of characters in $cd that $q matches + local q_chars=$(( ${#cd} - ${#${cd//${~q}/}} )) + # Try dropping directory elements from the right; stop when it affects + # how many times the search pattern appears + until (( ( ${#cd:h} - ${#${${cd:h}//${~q}/}} ) != q_chars )); do + cd=${cd:h} + done + + # If the best match is case-insensitive + else + local q_chars=$(( ${#cd} - ${#${${cd:l}//${~${q:l}}/}} )) + until (( ( ${#cd:h} - ${#${${${cd:h}:l}//${~${q:l}}/}} ) != q_chars )); do + cd=${cd:h} + done + fi + + ZSHZ[CASE_INSENSITIVE]=0 + fi + fi + + if (( ret2 == 0 )) && [[ -n $cd ]]; then + if (( $+opts[-e] )); then # echo + (( ZSHZ_TILDE )) && cd=${cd/#${HOME}/\~} + print -- "$cd" + else + # cd if possible; echo the new path if $ZSHZ_ECHO == 1 + [[ -d $cd ]] && zshz_cd "$cd" && _zshz_echo + fi + else + # if $req is a valid path, cd to it; echo the new path if $ZSHZ_ECHO == 1 + if ! (( $+opts[-e] || $+opts[-l] )) && [[ -d $req ]]; then + zshz_cd "$req" && _zshz_echo + else + return $ret2 + fi + fi +} + +alias ${ZSHZ_CMD:-${_Z_CMD:-z}}='zshz 2>&1' + +############################################################ +# precmd - add path to datafile unless `z -x' has just been +# run +# +# Globals: +# ZSHZ +############################################################ +_zshz_precmd() { + # Protect against `setopt NO_UNSET' + setopt LOCAL_OPTIONS UNSET + + # Do not add PWD to datafile when in HOME directory, or + # if `z -x' has just been run + [[ $PWD == "$HOME" ]] || (( ZSHZ[DIRECTORY_REMOVED] )) && return + + # Don't track directory trees excluded in ZSHZ_EXCLUDE_DIRS + local exclude + for exclude in ${(@)ZSHZ_EXCLUDE_DIRS:-${(@)_Z_EXCLUDE_DIRS}}; do + case $PWD in + ${exclude}|${exclude}/*) return ;; + esac + done + + # It appears that forking a subshell is so slow in Windows that it is better + # just to add the PWD to the datafile in the foreground + if [[ $OSTYPE == (cygwin|msys) ]]; then + zshz --add "$PWD" + else + (zshz --add "$PWD" &) + fi + + # See https://github.com/rupa/z/pull/247/commits/081406117ea42ccb8d159f7630cfc7658db054b6 + : $RANDOM +} + +############################################################ +# chpwd +# +# When the $PWD is removed from the datafile with `z -x', +# Zsh-z refrains from adding it again until the user has +# left the directory. +# +# Globals: +# ZSHZ +############################################################ +_zshz_chpwd() { + ZSHZ[DIRECTORY_REMOVED]=0 +} + +autoload -Uz add-zsh-hook + +add-zsh-hook precmd _zshz_precmd +add-zsh-hook chpwd _zshz_chpwd + +############################################################ +# Completion +############################################################ + +# Standardized $0 handling +# https://zdharma-continuum.github.io/Zsh-100-Commits-Club/Zsh-Plugin-Standard.html +0="${${ZERO:-${0:#$ZSH_ARGZERO}}:-${(%):-%N}}" +0="${${(M)0:#/*}:-$PWD/$0}" + +(( ${fpath[(ie)${0:A:h}]} <= ${#fpath} )) || fpath=( "${0:A:h}" "${fpath[@]}" ) + +############################################################ +# zsh-z functions +############################################################ +ZSHZ[FUNCTIONS]='_zshz_usage + _zshz_add_or_remove_path + _zshz_update_datafile + _zshz_legacy_complete + _zshz_printv + _zshz_find_common_root + _zshz_output + _zshz_find_matches + zshz + _zshz_precmd + _zshz_chpwd + _zshz' + +############################################################ +# Enable WARN_NESTED_VAR for functions listed in +# ZSHZ[FUNCTIONS] +############################################################ +(( ${+ZSHZ_DEBUG} )) && () { + if is-at-least 5.4.0; then + local x + for x in ${=ZSHZ[FUNCTIONS]}; do + functions -W $x + done + fi +} + +############################################################ +# Unload function +# +# See https://github.com/agkozak/Zsh-100-Commits-Club/blob/master/Zsh-Plugin-Standard.adoc#unload-fun +# +# Globals: +# ZSHZ +# ZSHZ_CMD +############################################################ +zsh-z_plugin_unload() { + emulate -L zsh + + add-zsh-hook -D precmd _zshz_precmd + add-zsh-hook -d chpwd _zshz_chpwd + + local x + for x in ${=ZSHZ[FUNCTIONS]}; do + (( ${+functions[$x]} )) && unfunction $x + done + + unset ZSHZ + + fpath=( "${(@)fpath:#${0:A:h}}" ) + + (( ${+aliases[${ZSHZ_CMD:-${_Z_CMD:-z}}]} )) && + unalias ${ZSHZ_CMD:-${_Z_CMD:-z}} + + unfunction $0 +} + +# vim: fdm=indent:ts=2:et:sts=2:sw=2: + diff --git a/commands b/commands new file mode 100644 index 0000000..d51abf2 --- /dev/null +++ b/commands @@ -0,0 +1,2 @@ +ln -s ~/dotfiles/.zshrc ~/.zshrc +ln -s ~/dotfiles/.gitmessage.txt ~/.gitmessage.txt