├── .gitignore ├── .gitmodules ├── Brewfile ├── ackrc ├── bash_profile ├── env ├── ghostty ├── config └── themes │ └── Patrick ├── gitconfig ├── gitconfig-work ├── gitignore ├── hammerspoon ├── battery.lua ├── caffeinate.lua ├── caffeine-on.pdf ├── cpu.lua ├── focus.lua ├── init.lua ├── position.lua ├── setup.lua ├── spaces.lua ├── vpn.lua └── wifi.lua ├── install.sh ├── macos-defaults.sh ├── nvmrc ├── ssh └── config ├── starship.toml ├── tmux.conf ├── tmux └── tmux-url-select.pl ├── vim └── autoload │ └── plug.vim ├── vimrc ├── zsh └── kubectl │ └── kubectl.plugin.zsh └── zshrc /.gitignore: -------------------------------------------------------------------------------- 1 | vim/.netrwhist 2 | vim/plugged 3 | .DS_Store 4 | ._* 5 | hammerspoon/hs 6 | x86-bin/* -------------------------------------------------------------------------------- /.gitmodules: -------------------------------------------------------------------------------- 1 | [submodule "zsh/fast-syntax-highlighting"] 2 | path = zsh/fast-syntax-highlighting 3 | url = git@github.com:zdharma-continuum/fast-syntax-highlighting.git 4 | -------------------------------------------------------------------------------- /Brewfile: -------------------------------------------------------------------------------- 1 | # Use `brew bundle` to install the packages listed below 2 | 3 | # Packages 4 | brew 'ack' 5 | brew 'watch' 6 | brew 'wget' 7 | brew 'git' 8 | brew 'colordiff' 9 | brew 'tmux' 10 | brew 'starship' 11 | brew 'jq' 12 | brew 'htop' 13 | brew 'vim' 14 | brew 'diffr' 15 | brew 'atuin' 16 | # brew 'aha' # shell escape codes to html (Uebersicht.app) 17 | 18 | # CLIs 19 | brew 'kubectl' 20 | brew 'wireguard-tools' 21 | brew 'fnm' 22 | brew 'flyctl 23 | tap 'hashicorp/tap' 24 | brew 'hashicorp/tap/terraform' 25 | 26 | # Apps 27 | #cask 'ghostty' 28 | 29 | # Fonts 30 | #tap 'homebrew/cask-fonts' 31 | #tap 'homebrew/cask' 32 | #tap 'robertgzr/homebrew-tap' 33 | #brew 'iosevka', args: ['with-ss05', 'with-termlig'] 34 | #brew 'iosevka', args: ['with-ss12', 'with-termlig'] 35 | 36 | # Applications 37 | # tap 'caskroom/cask' 38 | # cask_args appdir: '/Applications' 39 | 40 | # Quick Look plugins 41 | # cask 'qlcolorcode' 42 | # cask 'qlstephen' 43 | # cask 'qlmarkdown' 44 | # cask 'quicklook-json' 45 | # cask 'quicklook-csv' 46 | -------------------------------------------------------------------------------- /ackrc: -------------------------------------------------------------------------------- 1 | --smart-case 2 | 3 | --sort-files 4 | 5 | # CSS compilers 6 | --type-add=css=.sass,.scss,.less 7 | --type-set=sass=.sass,.scss 8 | --type-set=less=.less 9 | 10 | # Translation files 11 | --type-set=po=.po 12 | 13 | # Misc 14 | --type-set=cfg=.cfg 15 | -------------------------------------------------------------------------------- /bash_profile: -------------------------------------------------------------------------------- 1 | export CLICOLOR=1 2 | export LSCOLORS='ExFxCxDxbxegedabagacad' 3 | 4 | # command prompt 5 | if [ `whoami` == "root" ]; then 6 | export PS1='\[\033[0;31m\]\u\[\033[0;37m\]@\[\033[0;37m\]\h \[\033[1;34m\]\w \[\033[0;37m\]\$ ' 7 | else 8 | export PS1='\[\033[0;32m\]\u\[\033[0;37m\]@\[\033[0;37m\]\h \[\033[1;34m\]\w \[\033[0;37m\]\$ ' 9 | fi 10 | 11 | [ -f ~/.env ] && source ~/.env 12 | -------------------------------------------------------------------------------- /env: -------------------------------------------------------------------------------- 1 | # general 2 | export LC_ALL=en_US.UTF-8 3 | export LANG=en_US.UTF-8 4 | export LANGUAGE=en_US.UTF-8 5 | 6 | export EDITOR=vim 7 | 8 | export LESS='-IRXM' # case insensitive search / raw color sequences / don't clear screen on exit / extended prompt 9 | alias top='top -o cpu' 10 | alias grep='grep --color' 11 | 12 | ulimit -n 1024 13 | 14 | # iCloud Drive 15 | export ICLOUD_DRIVE=$HOME/Library/Mobile\ Documents/com~apple~CloudDocs 16 | 17 | # homebrew 18 | export HOMEBREW_PREFIX="/opt/homebrew"; 19 | export HOMEBREW_CELLAR="/opt/homebrew/Cellar"; 20 | export HOMEBREW_REPOSITORY="/opt/homebrew"; 21 | export PATH="/opt/homebrew/bin:/opt/homebrew/sbin${PATH+:$PATH}"; 22 | export MANPATH="/opt/homebrew/share/man${MANPATH+:$MANPATH}:"; 23 | export INFOPATH="/opt/homebrew/share/info:${INFOPATH:-}"; 24 | #export C_INCLUDE_PATH=$HOMEBREW_DIR/include:$C_INCLUDE_PATH 25 | #export OBJC_INCLUDE_PATH=$HOMEBREW_DIR/include:$OBJC_INCLUDE_PATH 26 | #export CPLUS_INCLUDE_PATH=$HOMEBREW_DIR/include:$CPLUS_INCLUDE_PATH 27 | #export OBJCPLUS_INCLUDE_PATH=$HOMEBREW_DIR/include:$OBJCPLUS_INCLUDE_PATH 28 | export HOMEBREW_NO_ANALYTICS=1 29 | 30 | # node.js 31 | export PATH=./node_modules/.bin:$HOME/.fnm:$PATH 32 | eval "`fnm env`" 33 | 34 | # go 35 | export GOPATH=~/Go 36 | export PATH=$PATH:$GOPATH/bin 37 | 38 | # java 39 | export JAVA_HOME=$(/usr/libexec/java_home -v 21) 40 | 41 | # serve the current directory 42 | function serve { 43 | port=${1:-8080} 44 | (ifconfig | grep -E 'inet.[0-9]' | grep -v -E '127.0.0.1|-->' | awk '{ printf $2}';echo ":$port") | pbcopy 45 | echo "URL copied to clipboard." 46 | python3 -m http.server $port 47 | } 48 | 49 | # tmux 50 | function tmx { 51 | [ "$TERM_PROGRAM" = "vscode" ] && return 52 | [[ ! $TERM =~ screen ]] && [ -z $TMUX ] && tmux new -A -s default 53 | } 54 | 55 | if command -v tmux > /dev/null; then 56 | tmx 57 | fi 58 | 59 | # load host-specific .env-local 60 | [ -f ~/.env-local ] && source ~/.env-local 61 | -------------------------------------------------------------------------------- /ghostty/config: -------------------------------------------------------------------------------- 1 | theme = Patrick 2 | 3 | font-family = "Iosevka Term SS12" 4 | font-size = 13 5 | font-style = Medium 6 | font-style-bold = Medium 7 | font-style-italic = Medium 8 | font-style-bold-italic = Medium 9 | font-thicken = true 10 | font-thicken-strength = 25 11 | bold-color = bright 12 | 13 | #window-decoration = none 14 | #window-padding-x = 0 15 | #window-padding-y = 0 16 | #background-opacity = 0.9 17 | #background-blur = 20 18 | title = " " 19 | macos-titlebar-proxy-icon = hidden 20 | window-save-state = always 21 | bell-features = no-attention 22 | confirm-close-surface = false 23 | macos-shortcuts = deny 24 | 25 | cursor-style = block 26 | cursor-style-blink = false 27 | shell-integration-features = no-cursor 28 | 29 | # manipulate tmux panes 30 | keybind = cmd+d=text:u{0004} 31 | keybind = cmd+t=text:u{0014} 32 | keybind = cmd+w=text:u{0018} 33 | # navigate tmux tabs 34 | keybind = opt+left=unbind 35 | keybind = opt+right=unbind 36 | # ctrl+l to clear screen - "clear_screen" doesn't work 37 | keybind = ctrl+l=text:\x0c 38 | keybind = cmd+w=close_window -------------------------------------------------------------------------------- /ghostty/themes/Patrick: -------------------------------------------------------------------------------- 1 | palette = 0=#72777b 2 | palette = 1=#ff8b92 3 | palette = 2=#64d38a 4 | palette = 3=#eed49f 5 | palette = 4=#9992f0 6 | palette = 5=#bf74d2 7 | palette = 6=#6caeff 8 | palette = 7=#f2f2f2 9 | palette = 8=#72777b 10 | palette = 9=#ff8b92 11 | palette = 10=#64d38a 12 | palette = 11=#eed49f 13 | palette = 12=#9992f0 14 | palette = 13=#bf74d2 15 | palette = 14=#6caeff 16 | palette = 15=#f2f2f2 17 | 18 | background = #1d1f20 19 | foreground = #72777b 20 | cursor-color = #ffcc00 21 | cursor-text = #000000 -------------------------------------------------------------------------------- /gitconfig: -------------------------------------------------------------------------------- 1 | [user] 2 | name = Patrick Stadler 3 | email = patrick.stadler@gmail.com 4 | [includeIf "gitdir/i:~/work/"] 5 | path = ~/.gitconfig-work 6 | [core] 7 | #pager = `brew --prefix`/share/git-core/contrib/diff-highlight/diff-highlight | less -IRX 8 | pager = diffr | less -IRX 9 | excludesfile = ~/.gitignore 10 | [color] 11 | ui = true 12 | #[column] 13 | #ui = auto 14 | [branch] 15 | sort = -committerdate 16 | [tag] 17 | sort = version:refname 18 | [init] 19 | defaultBranch = main 20 | [push] 21 | default = current 22 | autoSetupRemote = true 23 | [pull] 24 | rebase = true 25 | [fetch] 26 | prune = true 27 | pruneTags = true 28 | all = true 29 | [rebase] 30 | autoStash = true 31 | [alias] 32 | st = status -sb 33 | co = checkout 34 | ci = commit 35 | br = branch 36 | l = log --graph --full-history --pretty=format:'%C(red)%h%Creset - %C(yellow)%s %C(green)(%an, %cr)%C(white)%d%C(reset)' --abbrev-commit --date=relative 37 | contains = branch --contains 38 | ch = "!git reflog | awk '/: checkout:/{print $NF}' | awk '!a[$0]++' | head" 39 | summary = "!git log --numstat --no-merges --format=\"\" \"$@\" | awk '{files += 1}{ins += $1}{del += $2} END{print \"total: \"files\" files, \"ins\" insertions(+) \"del\" deletions(-)\"}' #" 40 | -------------------------------------------------------------------------------- /gitconfig-work: -------------------------------------------------------------------------------- 1 | [user] 2 | name = Patrick Stadler 3 | email = patrick.stadler@datatrans.ch -------------------------------------------------------------------------------- /gitignore: -------------------------------------------------------------------------------- 1 | .DS_Store 2 | -------------------------------------------------------------------------------- /hammerspoon/battery.lua: -------------------------------------------------------------------------------- 1 | -- Battery 2 | local previousPowerSource = hs.battery.powerSource() 3 | 4 | function minutesToHours(minutes) 5 | if minutes <= 0 then 6 | return "0:00"; 7 | else 8 | hours = string.format("%d", math.floor(minutes / 60)) 9 | mins = string.format("%02.f", math.floor(minutes - (hours * 60))) 10 | return string.format("%s:%s", hours, mins) 11 | end 12 | end 13 | 14 | function showBatteryStatus() 15 | local message 16 | 17 | if hs.battery.isCharging() then 18 | local pct = hs.battery.percentage() 19 | local untilFull = hs.battery.timeToFullCharge() 20 | message = "Charging:" 21 | 22 | if untilFull == -1 then 23 | message = string.format("%s %.0f%% (calculating...)", message, pct); 24 | else 25 | local watts = hs.battery.watts() 26 | message = string.format("%s %.0f%% (%s remaining @ %.1fW)", message, pct, minutesToHours(untilFull), watts) 27 | end 28 | elseif hs.battery.powerSource() == "Battery Power" then 29 | local pct = hs.battery.percentage() 30 | local untilEmpty = hs.battery.timeRemaining() 31 | message = "Battery:" 32 | 33 | if untilEmpty == -1 then 34 | message = string.format("%s %.0f%% (calculating...)", message, pct) 35 | else 36 | local watts = hs.battery.watts() 37 | message = string.format("%s %.0f%% (%s remaining @ %.1fW)", message, pct, minutesToHours(untilEmpty), watts) 38 | end 39 | else 40 | message = "Fully charged" 41 | end 42 | 43 | hs.alert.show(message) 44 | end 45 | 46 | hs.hotkey.bind(mash.utils, "b", showBatteryStatus) 47 | -------------------------------------------------------------------------------- /hammerspoon/caffeinate.lua: -------------------------------------------------------------------------------- 1 | -- Caffeinate 2 | -- Icon shamelessly copied from https://github.com/BrianGilbert/.hammerspoon 3 | local caffeine 4 | 5 | function toggleCaffeine() 6 | setCaffeineMenuItem(hs.caffeinate.toggle("systemIdle")) 7 | end 8 | 9 | function setCaffeineMenuItem(isIdle) 10 | if isIdle then 11 | caffeine = hs.menubar.new() 12 | caffeine:setIcon(hs.image.imageFromPath(os.getenv("HOME") .. "/.hammerspoon/caffeine-on.pdf")) 13 | caffeine:setClickCallback(toggleCaffeine) 14 | 15 | hs.alert.show("Caffeinated!") 16 | else 17 | caffeine:delete() 18 | 19 | hs.alert.show("Decaf") 20 | end 21 | end 22 | 23 | hs.hotkey.bind(mash.utils, "c", toggleCaffeine) 24 | -------------------------------------------------------------------------------- /hammerspoon/caffeine-on.pdf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/pstadler/dotfiles/ae19f4eee622577840671963fa0bb3493c5c9d7f/hammerspoon/caffeine-on.pdf -------------------------------------------------------------------------------- /hammerspoon/cpu.lua: -------------------------------------------------------------------------------- 1 | local settings = { 2 | theme = hs.settings.get("theme"), 3 | updateFrequency = hs.settings.get("updateFrequency") 4 | } 5 | 6 | local themes = { 7 | dark = { 8 | frameAlpha = 1, 9 | barAlpha = 0 10 | }, 11 | light = { 12 | frameAlpha = 0.2, 13 | barAlpha = 1 14 | } 15 | } 16 | local theme = themes[settings.theme] and settings.theme or "dark" 17 | local updateFrequency = settings.updateFrequency or 3 18 | local imageHeight = 17 19 | local barWidth = 3 20 | local gridSpacing = 2 21 | 22 | local maxBarHeight = (imageHeight - (gridSpacing * 3)) / 2 23 | 24 | local menubar = hs.menubar.new():autosaveName("cpu") 25 | local canvas 26 | local timer 27 | 28 | local canvasFrameItems = 0 29 | 30 | function update(data) 31 | local usages 32 | local numberOfCores = data.n 33 | local barsPerRow = numberOfCores / 2 34 | local imageWidth = barsPerRow * (barWidth + gridSpacing) + gridSpacing 35 | 36 | -- init canvas only once 37 | if canvas == nil then 38 | canvas = hs.canvas.new { 39 | x = 0, 40 | y = 0, 41 | w = imageWidth, 42 | h = imageHeight 43 | } 44 | 45 | canvas[1] = { 46 | action = "fill", 47 | type = "rectangle", 48 | fillColor = {alpha = themes[theme].frameAlpha}, 49 | frame = {x = 0, y = 0, w = imageWidth, h = imageHeight}, 50 | roundedRectRadii = {xRadius = 1, yRadius = 1}, 51 | clipToPath = true 52 | } 53 | 54 | -- local barBackgroundAlpha = .1 55 | -- for i = 1, numberOfCores do 56 | -- local y = imageHeight - gridSpacing 57 | -- local x = i - 1 58 | -- if i <= numberOfCores / 2 then 59 | -- y = y / 2 60 | -- elseif i > numberOfCores / 2 then 61 | -- x = x - (numberOfCores / 2) 62 | -- end 63 | 64 | -- canvas[i + 1] = { 65 | -- action = "fill", 66 | -- type = "rectangle", 67 | -- compositeRule = "sourceOut", 68 | -- fillColor = {alpha = barBackgroundAlpha}, 69 | -- frame = { 70 | -- x = x * (barWidth + gridSpacing) + gridSpacing, 71 | -- y = y - maxBarHeight, 72 | -- w = barWidth, 73 | -- h = maxBarHeight 74 | -- } 75 | -- } 76 | -- end 77 | 78 | canvasFrameItems = #canvas 79 | end 80 | 81 | for i = 1, numberOfCores do 82 | local barHeight = math.max((maxBarHeight * (data[i].active / 100)), 1) 83 | --local barHeight = math.max((maxBarHeight * (100 / 100)), 1) 84 | 85 | local y = imageHeight - gridSpacing 86 | local x = i - 1 87 | if i <= numberOfCores / 2 then 88 | y = y / 2 89 | elseif i > numberOfCores / 2 then 90 | x = x - (numberOfCores / 2) 91 | end 92 | 93 | canvas[i + canvasFrameItems] = { 94 | action = "fill", 95 | type = "rectangle", 96 | compositeRule = themes[theme].barAlpha < themes[theme].frameAlpha and 97 | (data[i].active == 0.0 and "destinationOut" or "sourceOut") or "sourceOver", 98 | fillColor = {alpha = data[i].active == 0.0 and .5 or themes[theme].barAlpha}, 99 | frame = { 100 | x = x * (barWidth + gridSpacing) + gridSpacing, 101 | y = y - barHeight, 102 | w = barWidth, 103 | h = barHeight 104 | } 105 | } 106 | end 107 | 108 | menubar:setIcon(canvas:imageFromCanvas()) 109 | end 110 | 111 | function query() 112 | hs.host.cpuUsage(1, update) 113 | end 114 | 115 | function changeUpdateFrequency(freq) 116 | updateFrequency = freq 117 | timer:stop() 118 | timer = hs.timer.doEvery(updateFrequency, query) 119 | hs.settings.set("updateFrequency", freq) 120 | end 121 | 122 | function changeTheme(t) 123 | theme = t 124 | canvas = nil 125 | timer:fire() 126 | hs.settings.set("theme", t) 127 | end 128 | 129 | menubar:setMenu( 130 | function() 131 | return { 132 | { 133 | title = "Update Frequency", 134 | menu = { 135 | { 136 | title = "Very often (1s)", 137 | checked = updateFrequency == 1, 138 | fn = function() 139 | changeUpdateFrequency(1) 140 | end 141 | }, 142 | { 143 | title = "Often (3s)", 144 | checked = updateFrequency == 3, 145 | fn = function() 146 | changeUpdateFrequency(3) 147 | end 148 | }, 149 | { 150 | title = "Normally (5s)", 151 | checked = updateFrequency == 5, 152 | fn = function() 153 | changeUpdateFrequency(5) 154 | end 155 | } 156 | } 157 | }, 158 | { 159 | title = "Theme", 160 | menu = { 161 | { 162 | title = "Dark", 163 | checked = theme == "dark", 164 | fn = function() 165 | changeTheme("dark") 166 | end 167 | }, 168 | { 169 | title = "Light", 170 | checked = theme == "light", 171 | fn = function() 172 | changeTheme("light") 173 | end 174 | } 175 | } 176 | }, 177 | { 178 | title = "Open Activity Monitor", 179 | fn = function() 180 | hs.application.launchOrFocus("Activity Monitor") 181 | end 182 | } 183 | } 184 | end 185 | ) 186 | 187 | query() 188 | timer = hs.timer.doEvery(updateFrequency, query) 189 | -------------------------------------------------------------------------------- /hammerspoon/focus.lua: -------------------------------------------------------------------------------- 1 | -- Focus windows 2 | hs.hotkey.bind(mash.focus, "up", hs.window.frontmostWindow().focusWindowNorth) 3 | hs.hotkey.bind(mash.focus, "right", hs.window.frontmostWindow().focusWindowEast) 4 | hs.hotkey.bind(mash.focus, "down", hs.window.frontmostWindow().focusWindowSouth) 5 | hs.hotkey.bind(mash.focus, "left", hs.window.frontmostWindow().focusWindowWest) -------------------------------------------------------------------------------- /hammerspoon/init.lua: -------------------------------------------------------------------------------- 1 | mash = { 2 | position = {"ctrl", "alt", "cmd"}, 3 | focus = {"ctrl", "alt"}, 4 | utils = {"ctrl", "alt", "cmd"} 5 | } 6 | 7 | require('setup') 8 | 9 | require('position') 10 | require('focus') 11 | require('spaces') 12 | require('caffeinate') 13 | require('wifi') 14 | require('battery') 15 | require('cpu') 16 | require('vpn') 17 | 18 | --hs.alert.show("Hammerspoon!") -------------------------------------------------------------------------------- /hammerspoon/position.lua: -------------------------------------------------------------------------------- 1 | -- Window positioning 2 | -- Based on: https://github.com/miromannino/miro-position-management 3 | 4 | hs.window.animationDuration = 0 5 | 6 | local sizes = {2, 3, 3/2} 7 | local fullScreenSizes = {1, 4/3, 2} 8 | 9 | local GRID = {w = 24, h = 24} 10 | hs.grid.setGrid(GRID.w .. 'x' .. GRID.h) 11 | hs.grid.MARGINX = 0 12 | hs.grid.MARGINY = 0 13 | 14 | local pressed = { 15 | up = false, 16 | down = false, 17 | left = false, 18 | right = false 19 | } 20 | 21 | function nextStep(dim, offs, cb) 22 | if hs.window.focusedWindow() then 23 | local axis = dim == 'w' and 'x' or 'y' 24 | local oppDim = dim == 'w' and 'h' or 'w' 25 | local oppAxis = dim == 'w' and 'y' or 'x' 26 | local win = hs.window.frontmostWindow() 27 | local id = win:id() 28 | local screen = win:screen() 29 | 30 | cell = hs.grid.get(win, screen) 31 | 32 | local nextSize = sizes[1] 33 | for i=1,#sizes do 34 | if cell[dim] == GRID[dim] / sizes[i] and 35 | (cell[axis] + (offs and cell[dim] or 0)) == (offs and GRID[dim] or 0) 36 | then 37 | nextSize = sizes[(i % #sizes) + 1] 38 | break 39 | end 40 | end 41 | 42 | cb(cell, nextSize) 43 | if cell[oppAxis] ~= 0 and cell[oppAxis] + cell[oppDim] ~= GRID[oppDim] then 44 | cell[oppDim] = GRID[oppDim] 45 | cell[oppAxis] = 0 46 | end 47 | 48 | hs.grid.set(win, cell, screen) 49 | end 50 | end 51 | 52 | function nextFullScreenStep() 53 | if hs.window.focusedWindow() then 54 | local win = hs.window.frontmostWindow() 55 | local id = win:id() 56 | local screen = win:screen() 57 | 58 | cell = hs.grid.get(win, screen) 59 | 60 | local nextSize = fullScreenSizes[1] 61 | for i=1,#fullScreenSizes do 62 | if cell.w == GRID.w / fullScreenSizes[i] and 63 | cell.h == GRID.h / fullScreenSizes[i] and 64 | cell.x == (GRID.w - GRID.w / fullScreenSizes[i]) / 2 then 65 | nextSize = fullScreenSizes[(i % #fullScreenSizes) + 1] 66 | break 67 | end 68 | end 69 | 70 | cell.w = GRID.w / nextSize 71 | cell.h = GRID.h / nextSize 72 | cell.x = (GRID.w - GRID.w / nextSize) / 2 73 | cell.y = 0 74 | 75 | hs.grid.set(win, cell, screen) 76 | end 77 | end 78 | 79 | function fullDimension(dim) 80 | if hs.window.focusedWindow() then 81 | local win = hs.window.frontmostWindow() 82 | local id = win:id() 83 | local screen = win:screen() 84 | cell = hs.grid.get(win, screen) 85 | 86 | if (dim == 'x') then 87 | cell = '0,0 ' .. GRID.w .. 'x' .. GRID.h 88 | else 89 | cell[dim] = GRID[dim] 90 | cell[dim == 'w' and 'x' or 'y'] = 0 91 | end 92 | 93 | hs.grid.set(win, cell, screen) 94 | end 95 | end 96 | 97 | hs.hotkey.bind(mash.position, "down", function () 98 | pressed.down = true 99 | if pressed.up then 100 | fullDimension('h') 101 | else 102 | nextStep('h', true, function (cell, nextSize) 103 | cell.y = GRID.h - GRID.h / nextSize 104 | cell.h = GRID.h / nextSize 105 | end) 106 | end 107 | end, function () 108 | pressed.down = false 109 | end) 110 | 111 | hs.hotkey.bind(mash.position, "right", function () 112 | pressed.right = true 113 | if pressed.left then 114 | fullDimension('w') 115 | else 116 | nextStep('w', true, function (cell, nextSize) 117 | cell.x = GRID.w - GRID.w / nextSize 118 | cell.w = GRID.w / nextSize 119 | end) 120 | end 121 | end, function () 122 | pressed.right = false 123 | end) 124 | 125 | hs.hotkey.bind(mash.position, "left", function () 126 | pressed.left = true 127 | if pressed.right then 128 | fullDimension('w') 129 | else 130 | nextStep('w', false, function (cell, nextSize) 131 | cell.x = 0 132 | cell.w = GRID.w / nextSize 133 | end) 134 | end 135 | end, function () 136 | pressed.left = false 137 | end) 138 | 139 | hs.hotkey.bind(mash.position, "up", function () 140 | pressed.up = true 141 | if pressed.down then 142 | fullDimension('h') 143 | else 144 | nextStep('h', false, function (cell, nextSize) 145 | cell.y = 0 146 | cell.h = GRID.h / nextSize 147 | end) 148 | end 149 | end, function () 150 | pressed.up = false 151 | end) 152 | 153 | hs.hotkey.bind(mash.position, ".", function () 154 | nextFullScreenStep() 155 | end) 156 | 157 | -- hs.hotkey.bind(mash.position, "i", function () 158 | -- local win = hs.window.frontmostWindow() 159 | -- local id = win:id() 160 | -- local screen = win:screen() 161 | -- cell = hs.grid.get(win, screen) 162 | -- hs.alert.show(cell) 163 | -- end) 164 | -------------------------------------------------------------------------------- /hammerspoon/setup.lua: -------------------------------------------------------------------------------- 1 | logger = hs.logger.new("config", "verbose") 2 | 3 | hs.alert.defaultStyle.strokeColor = { white = 0, alpha = 0.75 } 4 | hs.alert.defaultStyle.textSize = 25 5 | 6 | -- Reload config 7 | hs.hotkey.bind(mash.utils, "-", function() 8 | hs.reload() 9 | end) 10 | -------------------------------------------------------------------------------- /hammerspoon/spaces.lua: -------------------------------------------------------------------------------- 1 | -- Fast space switching using https://github.com/asmagill/hs._asm.spaces 2 | local spaces = require("hs.spaces") 3 | 4 | local spacesModifier = "ctrl" 5 | 6 | local spacesTable = spaces.spacesForScreen() 7 | local spacesCount = #spacesTable 8 | local spacesModifiers = {"fn", spacesModifier} 9 | 10 | function getFocusedSpaceIndex() 11 | local focusedSpace = spaces.focusedSpace() 12 | for k, v in ipairs(spacesTable) do 13 | if v == focusedSpace then return k end 14 | end 15 | end 16 | 17 | -- infinitely cycle through spaces using ctrl+left/right to trigger ctrl+[1..n] 18 | local spacesEventtap = hs.eventtap.new({hs.eventtap.event.types.keyDown}, function(o) 19 | local keyCode = o:getKeyCode() 20 | local modifiers = o:getFlags() 21 | 22 | --logger.i(keyCode, hs.inspect(modifiers)) 23 | 24 | -- check if correct key code 25 | if keyCode ~= 123 and keyCode ~= 124 then return end 26 | if not modifiers[spacesModifier] then return end 27 | 28 | -- check if no other modifiers where pressed 29 | local passed = hs.fnutils.every(modifiers, function(_, modifier) 30 | return hs.fnutils.contains(spacesModifiers, modifier) 31 | end) 32 | 33 | if not passed then return end 34 | 35 | -- switch spaces 36 | local currentSpace = getFocusedSpaceIndex() 37 | local nextSpace 38 | 39 | -- left arrow 40 | if keyCode == 123 then 41 | nextSpace = currentSpace ~= 1 and currentSpace - 1 or spacesCount 42 | -- right arrow 43 | elseif keyCode == 124 then 44 | nextSpace = currentSpace ~= spacesCount and currentSpace + 1 or 1 45 | end 46 | 47 | local event = require("hs.eventtap").event 48 | hs.eventtap.keyStroke({spacesModifier}, string.format("%d", nextSpace), 0) 49 | 50 | -- stop propagation 51 | return true 52 | end):start() 53 | 54 | hs.hotkey.bind(mash.utils, "e", function() 55 | -- this is to bind the spacesEventtap variable to a long-lived function in 56 | -- order to prevent GC from doing their evil business 57 | hs.alert.show("Fast space switching enabled: " .. tostring(spacesEventtap:isEnabled())) 58 | end) 59 | -------------------------------------------------------------------------------- /hammerspoon/vpn.lua: -------------------------------------------------------------------------------- 1 | -- avoid garbage collection of timers 2 | timers = {} 3 | 4 | local function menuItemForRunningProcess(processName, menuItemLabel, checkIntervalSeconds, clickCallback) 5 | local menubar 6 | 7 | local function checkProcess() 8 | local output = hs.execute("pgrep -x " .. processName) 9 | if output == "" then 10 | if menubar and menubar:isInMenuBar() then 11 | menubar:delete() 12 | end 13 | elseif not (menubar and menubar:isInMenuBar()) then 14 | menubar = hs.menubar.new():setTitle(menuItemLabel) 15 | menubar:setClickCallback(function() 16 | if clickCallback then 17 | clickCallback() 18 | end 19 | checkProcess() 20 | end) 21 | end 22 | end 23 | 24 | table.insert(timers, hs.timer.new(checkIntervalSeconds, checkProcess, true):start()) 25 | 26 | checkProcess() 27 | end 28 | 29 | menuItemForRunningProcess("openconnect", "VPN", 5, function() 30 | hs.execute([[osascript -e 'do shell script "sudo pkill openconnect" with administrator privileges']]) 31 | end) -------------------------------------------------------------------------------- /hammerspoon/wifi.lua: -------------------------------------------------------------------------------- 1 | -- Reconnect to current Wifi 2 | function ssidChangedCallback() 3 | local ssid = hs.wifi.currentNetwork() 4 | if ssid then 5 | hs.alert.show("Network connected: " .. ssid) 6 | end 7 | end 8 | 9 | hs.location.get() -- See: https://github.com/Hammerspoon/hammerspoon/issues/3537#issuecomment-1743870568 10 | hs.wifi.watcher.new(ssidChangedCallback):start() 11 | 12 | hs.hotkey.bind(mash.utils, "r", function() 13 | local ssid = hs.wifi.currentNetwork() 14 | if not ssid then return end 15 | 16 | hs.alert.show("Reconnecting to: " .. ssid) 17 | hs.execute("networksetup -setairportpower en0 off") 18 | hs.execute("networksetup -setairportpower en0 on") 19 | end) 20 | -------------------------------------------------------------------------------- /install.sh: -------------------------------------------------------------------------------- 1 | #!/bin/sh 2 | 3 | echo "Updating submodules..." 4 | git submodule update --init --recursive 5 | 6 | echo "Installing dotfiles..." 7 | for symlink in zshrc bash_profile env gitconfig gitconfig-work gitignore \ 8 | vim vimrc ackrc nvmrc hammerspoon tmux.conf 9 | do 10 | echo " symlink ~/.$symlink" 11 | rm ~/.$symlink 12 | ln -s $PWD/$symlink ~/.$symlink 13 | done 14 | 15 | echo "Installing .config files..." 16 | mkdir -p ~/.config 17 | for symlink in starship.toml ghostty 18 | do 19 | echo " symlink ~/.config/$symlink" 20 | rm ~/.config/$symlink 21 | ln -s $PWD/$symlink ~/.config/$symlink 22 | done 23 | 24 | 25 | echo "Installing ssh config..." 26 | echo " symlink ~/.ssh/config" 27 | rm ~/.ssh/config 28 | ln -s $PWD/ssh/config ~/.ssh/config && chmod 0700 ~/.ssh/config 29 | echo " mkdir ~/.ssh/config.d/" 30 | [ -d ~/.ssh/config.d ] || mkdir ~/.ssh/config.d && chmod -h 0700 ~/.ssh/config.d 31 | echo " mkdir ~/.ssh/sockets/" 32 | [ -d ~/.ssh/sockets ] || mkdir ~/.ssh/sockets && chmod -h 0700 ~/.ssh/sockets 33 | 34 | echo "Installing zsh plugins..." 35 | ZSH_PLUGIN_DIR=~/.oh-my-zsh/custom/plugins 36 | for symlink in $(ls zsh) 37 | do 38 | echo " symlink $ZSH_PLUGIN_DIR/$symlink" 39 | [ -d $ZSH_PLUGIN_DIR/$symlink ] \ 40 | || ln -s $PWD/zsh/$symlink $ZSH_PLUGIN_DIR/$symlink 41 | done -------------------------------------------------------------------------------- /macos-defaults.sh: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env bash 2 | 3 | # ~/.macos — https://mths.be/macos 4 | 5 | # Close any open System Preferences panes, to prevent them from overriding 6 | # settings we’re about to change 7 | osascript -e 'tell application "System Preferences" to quit' 8 | 9 | # Ask for the administrator password upfront 10 | sudo -v 11 | 12 | # Keep-alive: update existing `sudo` time stamp until `.macos` has finished 13 | while true; do sudo -n true; sleep 60; kill -0 "$$" || exit; done 2>/dev/null & 14 | 15 | ############################################################################### 16 | # General UI/UX # 17 | ############################################################################### 18 | 19 | # Set computer name (as done via System Preferences → Sharing) 20 | #sudo scutil --set ComputerName "0x6D746873" 21 | #sudo scutil --set HostName "0x6D746873" 22 | #sudo scutil --set LocalHostName "0x6D746873" 23 | #sudo defaults write /Library/Preferences/SystemConfiguration/com.apple.smb.server NetBIOSName -string "0x6D746873" 24 | 25 | # Disable the sound effects on boot 26 | #sudo nvram SystemAudioVolume=" " 27 | 28 | # Disable transparency in the menu bar and elsewhere on Yosemite 29 | #defaults write com.apple.universalaccess reduceTransparency -bool true 30 | 31 | # Set highlight color to green 32 | #defaults write NSGlobalDomain AppleHighlightColor -string "0.764700 0.976500 0.568600" 33 | 34 | # Set sidebar icon size to medium 35 | #defaults write NSGlobalDomain NSTableViewDefaultSizeMode -int 2 36 | 37 | # Always show scrollbars 38 | #defaults write NSGlobalDomain AppleShowScrollBars -string "Always" 39 | # Possible values: `WhenScrolling`, `Automatic` and `Always` 40 | 41 | # Disable the over-the-top focus ring animation 42 | #defaults write NSGlobalDomain NSUseAnimatedFocusRing -bool false 43 | 44 | # Adjust toolbar title rollover delay 45 | #defaults write NSGlobalDomain NSToolbarTitleViewRolloverDelay -float 0 46 | 47 | # Disable smooth scrolling 48 | # (Uncomment if you’re on an older Mac that messes up the animation) 49 | #defaults write NSGlobalDomain NSScrollAnimationEnabled -bool false 50 | 51 | # Increase window resize speed for Cocoa applications 52 | defaults write NSGlobalDomain NSWindowResizeTime -float 0.001 53 | 54 | # Expand save panel by default 55 | defaults write NSGlobalDomain NSNavPanelExpandedStateForSaveMode -bool true 56 | defaults write NSGlobalDomain NSNavPanelExpandedStateForSaveMode2 -bool true 57 | 58 | # Expand print panel by default 59 | defaults write NSGlobalDomain PMPrintingExpandedStateForPrint -bool true 60 | defaults write NSGlobalDomain PMPrintingExpandedStateForPrint2 -bool true 61 | 62 | # Save to disk (not to iCloud) by default 63 | defaults write NSGlobalDomain NSDocumentSaveNewDocumentsToCloud -bool false 64 | 65 | # Automatically quit printer app once the print jobs complete 66 | defaults write com.apple.print.PrintingPrefs "Quit When Finished" -bool true 67 | 68 | # Disable the “Are you sure you want to open this application?” dialog 69 | defaults write com.apple.LaunchServices LSQuarantine -bool false 70 | 71 | # Remove duplicates in the “Open With” menu (also see `lscleanup` alias) 72 | /System/Library/Frameworks/CoreServices.framework/Frameworks/LaunchServices.framework/Support/lsregister -kill -r -domain local -domain system -domain user 73 | 74 | # Display ASCII control characters using caret notation in standard text views 75 | # Try e.g. `cd /tmp; unidecode "\x{0000}" > cc.txt; open -e cc.txt` 76 | #defaults write NSGlobalDomain NSTextShowsControlCharacters -bool true 77 | 78 | # Disable Resume system-wide 79 | defaults write com.apple.systempreferences NSQuitAlwaysKeepsWindows -bool false 80 | 81 | # Disable automatic termination of inactive apps 82 | defaults write NSGlobalDomain NSDisableAutomaticTermination -bool true 83 | 84 | # Disable the crash reporter 85 | #defaults write com.apple.CrashReporter DialogType -string "none" 86 | 87 | # Set Help Viewer windows to non-floating mode 88 | defaults write com.apple.helpviewer DevMode -bool true 89 | 90 | # Fix for the ancient UTF-8 bug in QuickLook (https://mths.be/bbo) 91 | # Commented out, as this is known to cause problems in various Adobe apps :( 92 | # See https://github.com/mathiasbynens/dotfiles/issues/237 93 | #echo "0x08000100:0" > ~/.CFUserTextEncoding 94 | 95 | # Reveal IP address, hostname, OS version, etc. when clicking the clock 96 | # in the login window 97 | #sudo defaults write /Library/Preferences/com.apple.loginwindow AdminHostInfo HostName 98 | 99 | # Disable Notification Center and remove the menu bar icon 100 | #launchctl unload -w /System/Library/LaunchAgents/com.apple.notificationcenterui.plist 2> /dev/null 101 | 102 | # Disable automatic capitalization as it’s annoying when typing code 103 | defaults write NSGlobalDomain NSAutomaticCapitalizationEnabled -bool false 104 | 105 | # Disable smart dashes as they’re annoying when typing code 106 | #defaults write NSGlobalDomain NSAutomaticDashSubstitutionEnabled -bool false 107 | 108 | # Disable automatic period substitution as it’s annoying when typing code 109 | #defaults write NSGlobalDomain NSAutomaticPeriodSubstitutionEnabled -bool false 110 | 111 | # Disable smart quotes as they’re annoying when typing code 112 | #defaults write NSGlobalDomain NSAutomaticQuoteSubstitutionEnabled -bool false 113 | 114 | # Disable auto-correct 115 | #defaults write NSGlobalDomain NSAutomaticSpellingCorrectionEnabled -bool false 116 | 117 | # Set a custom wallpaper image. `DefaultDesktop.jpg` is already a symlink, and 118 | # all wallpapers are in `/Library/Desktop Pictures/`. The default is `Wave.jpg`. 119 | #rm -rf ~/Library/Application Support/Dock/desktoppicture.db 120 | #sudo rm -rf /System/Library/CoreServices/DefaultDesktop.jpg 121 | #sudo ln -s /path/to/your/image /System/Library/CoreServices/DefaultDesktop.jpg 122 | 123 | ############################################################################### 124 | # Trackpad, mouse, keyboard, Bluetooth accessories, and input # 125 | ############################################################################### 126 | 127 | # Trackpad: enable tap to click for this user and for the login screen 128 | defaults write com.apple.driver.AppleBluetoothMultitouch.trackpad Clicking -bool true 129 | defaults -currentHost write NSGlobalDomain com.apple.mouse.tapBehavior -int 1 130 | defaults write NSGlobalDomain com.apple.mouse.tapBehavior -int 1 131 | 132 | # Trackpad: map bottom right corner to right-click 133 | # defaults write com.apple.driver.AppleBluetoothMultitouch.trackpad TrackpadCornerSecondaryClick -int 2 134 | # defaults write com.apple.driver.AppleBluetoothMultitouch.trackpad TrackpadRightClick -bool true 135 | # defaults -currentHost write NSGlobalDomain com.apple.trackpad.trackpadCornerClickBehavior -int 1 136 | # defaults -currentHost write NSGlobalDomain com.apple.trackpad.enableSecondaryClick -bool true 137 | 138 | # Disable “natural” (Lion-style) scrolling 139 | defaults write NSGlobalDomain com.apple.swipescrolldirection -bool false 140 | 141 | # Increase sound quality for Bluetooth headphones/headsets 142 | #defaults write com.apple.BluetoothAudioAgent "Apple Bitpool Min (editable)" -int 40 143 | 144 | # Enable full keyboard access for all controls 145 | # (e.g. enable Tab in modal dialogs) 146 | defaults write NSGlobalDomain AppleKeyboardUIMode -int 3 147 | 148 | # Use scroll gesture with the Ctrl (^) modifier key to zoom 149 | #defaults write com.apple.universalaccess closeViewScrollWheelToggle -bool true 150 | #defaults write com.apple.universalaccess HIDScrollZoomModifierMask -int 262144 151 | # Follow the keyboard focus while zoomed in 152 | #defaults write com.apple.universalaccess closeViewZoomFollowsFocus -bool true 153 | 154 | # Disable press-and-hold for keys in favor of key repeat 155 | defaults write NSGlobalDomain ApplePressAndHoldEnabled -bool false 156 | 157 | # Set a blazingly fast keyboard repeat rate 158 | defaults write NSGlobalDomain KeyRepeat -int 1 159 | defaults write NSGlobalDomain InitialKeyRepeat -int 12 160 | 161 | # Set language and text formats 162 | # Note: if you’re in the US, replace `EUR` with `USD`, `Centimeters` with 163 | # `Inches`, `en_GB` with `en_US`, and `true` with `false`. 164 | #defaults write NSGlobalDomain AppleLanguages -array "en" "nl" 165 | #defaults write NSGlobalDomain AppleLocale -string "en_GB@currency=EUR" 166 | #defaults write NSGlobalDomain AppleMeasurementUnits -string "Centimeters" 167 | #defaults write NSGlobalDomain AppleMetricUnits -bool true 168 | 169 | # Show language menu in the top right corner of the boot screen 170 | sudo defaults write /Library/Preferences/com.apple.loginwindow showInputMenu -bool true 171 | 172 | # Set the timezone; see `sudo systemsetup -listtimezones` for other values 173 | #sudo systemsetup -settimezone "Europe/Brussels" > /dev/null 174 | 175 | # Stop iTunes from responding to the keyboard media keys 176 | #launchctl unload -w /System/Library/LaunchAgents/com.apple.rcd.plist 2> /dev/null 177 | 178 | ############################################################################### 179 | # Energy saving # 180 | ############################################################################### 181 | 182 | # Enable lid wakeup 183 | #sudo pmset -a lidwake 1 184 | 185 | # Restart automatically on power loss 186 | #sudo pmset -a autorestart 1 187 | 188 | # Restart automatically if the computer freezes 189 | #sudo systemsetup -setrestartfreeze on 190 | 191 | # Sleep the display after 15 minutes 192 | #sudo pmset -a displaysleep 15 193 | 194 | # Disable machine sleep while charging 195 | #sudo pmset -c sleep 0 196 | 197 | # Set machine sleep to 5 minutes on battery 198 | #sudo pmset -b sleep 5 199 | 200 | # Set standby delay to 24 hours (default is 1 hour) 201 | #sudo pmset -a standbydelay 86400 202 | 203 | # Never go into computer sleep mode 204 | #sudo systemsetup -setcomputersleep Off > /dev/null 205 | 206 | # Hibernation mode 207 | # 0: Disable hibernation (speeds up entering sleep mode) 208 | # 3: Copy RAM to disk so the system state can still be restored in case of a 209 | # power failure. 210 | #sudo pmset -a hibernatemode 0 211 | 212 | # Remove the sleep image file to save disk space 213 | #sudo rm /private/var/vm/sleepimage 214 | # Create a zero-byte file instead… 215 | #sudo touch /private/var/vm/sleepimage 216 | # …and make sure it can’t be rewritten 217 | #sudo chflags uchg /private/var/vm/sleepimage 218 | 219 | ############################################################################### 220 | # Screen # 221 | ############################################################################### 222 | 223 | # Require password immediately after sleep or screen saver begins 224 | defaults write com.apple.screensaver askForPassword -int 1 225 | defaults write com.apple.screensaver askForPasswordDelay -int 0 226 | 227 | # Save screenshots to the desktop 228 | defaults write com.apple.screencapture location -string "${HOME}/Desktop" 229 | 230 | # Save screenshots in PNG format (other options: BMP, GIF, JPG, PDF, TIFF) 231 | defaults write com.apple.screencapture type -string "png" 232 | 233 | # Disable shadow in screenshots 234 | #defaults write com.apple.screencapture disable-shadow -bool true 235 | 236 | # Enable subpixel font rendering on non-Apple LCDs 237 | # Reference: https://github.com/kevinSuttle/macOS-Defaults/issues/17#issuecomment-266633501 238 | #defaults write NSGlobalDomain AppleFontSmoothing -int 1 239 | 240 | # Enable HiDPI display modes (requires restart) 241 | #sudo defaults write /Library/Preferences/com.apple.windowserver DisplayResolutionEnabled -bool true 242 | 243 | ############################################################################### 244 | # Finder # 245 | ############################################################################### 246 | 247 | # Finder: allow quitting via ⌘ + Q; doing so will also hide desktop icons 248 | #defaults write com.apple.finder QuitMenuItem -bool true 249 | 250 | # Finder: disable window animations and Get Info animations 251 | defaults write com.apple.finder DisableAllAnimations -bool true 252 | 253 | # Set Desktop as the default location for new Finder windows 254 | # For other paths, use `PfLo` and `file:///full/path/here/` 255 | #defaults write com.apple.finder NewWindowTarget -string "PfDe" 256 | #defaults write com.apple.finder NewWindowTargetPath -string "file://${HOME}/Desktop/" 257 | 258 | # Show icons for hard drives, servers, and removable media on the desktop 259 | defaults write com.apple.finder ShowExternalHardDrivesOnDesktop -bool true 260 | #defaults write com.apple.finder ShowHardDrivesOnDesktop -bool true 261 | defaults write com.apple.finder ShowMountedServersOnDesktop -bool true 262 | defaults write com.apple.finder ShowRemovableMediaOnDesktop -bool true 263 | 264 | # Finder: show hidden files by default 265 | #defaults write com.apple.finder AppleShowAllFiles -bool true 266 | 267 | # Finder: show all filename extensions 268 | defaults write NSGlobalDomain AppleShowAllExtensions -bool true 269 | 270 | # Finder: show status bar 271 | defaults write com.apple.finder ShowStatusBar -bool true 272 | 273 | # Finder: show path bar 274 | #defaults write com.apple.finder ShowPathbar -bool true 275 | 276 | # Display full POSIX path as Finder window title 277 | defaults write com.apple.finder _FXShowPosixPathInTitle -bool true 278 | 279 | # Keep folders on top when sorting by name 280 | defaults write com.apple.finder _FXSortFoldersFirst -bool true 281 | 282 | # When performing a search, search the current folder by default 283 | defaults write com.apple.finder FXDefaultSearchScope -string "SCcf" 284 | 285 | # Disable the warning when changing a file extension 286 | defaults write com.apple.finder FXEnableExtensionChangeWarning -bool false 287 | 288 | # Enable spring loading for directories 289 | defaults write NSGlobalDomain com.apple.springing.enabled -bool true 290 | 291 | # Remove the spring loading delay for directories 292 | defaults write NSGlobalDomain com.apple.springing.delay -float 0 293 | 294 | # Avoid creating .DS_Store files on network or USB volumes 295 | defaults write com.apple.desktopservices DSDontWriteNetworkStores -bool true 296 | defaults write com.apple.desktopservices DSDontWriteUSBStores -bool true 297 | 298 | # Disable disk image verification 299 | defaults write com.apple.frameworks.diskimages skip-verify -bool true 300 | defaults write com.apple.frameworks.diskimages skip-verify-locked -bool true 301 | defaults write com.apple.frameworks.diskimages skip-verify-remote -bool true 302 | 303 | # Automatically open a new Finder window when a volume is mounted 304 | #defaults write com.apple.frameworks.diskimages auto-open-ro-root -bool true 305 | #defaults write com.apple.frameworks.diskimages auto-open-rw-root -bool true 306 | #defaults write com.apple.finder OpenWindowForNewRemovableDisk -bool true 307 | 308 | # Show item info near icons on the desktop and in other icon views 309 | /usr/libexec/PlistBuddy -c "Set :DesktopViewSettings:IconViewSettings:showItemInfo true" ~/Library/Preferences/com.apple.finder.plist 310 | /usr/libexec/PlistBuddy -c "Set :FK_StandardViewSettings:IconViewSettings:showItemInfo true" ~/Library/Preferences/com.apple.finder.plist 311 | /usr/libexec/PlistBuddy -c "Set :StandardViewSettings:IconViewSettings:showItemInfo true" ~/Library/Preferences/com.apple.finder.plist 312 | 313 | # Show item info to the right of the icons on the desktop 314 | /usr/libexec/PlistBuddy -c "Set DesktopViewSettings:IconViewSettings:labelOnBottom false" ~/Library/Preferences/com.apple.finder.plist 315 | 316 | # Enable snap-to-grid for icons on the desktop and in other icon views 317 | /usr/libexec/PlistBuddy -c "Set :DesktopViewSettings:IconViewSettings:arrangeBy grid" ~/Library/Preferences/com.apple.finder.plist 318 | /usr/libexec/PlistBuddy -c "Set :FK_StandardViewSettings:IconViewSettings:arrangeBy grid" ~/Library/Preferences/com.apple.finder.plist 319 | /usr/libexec/PlistBuddy -c "Set :StandardViewSettings:IconViewSettings:arrangeBy grid" ~/Library/Preferences/com.apple.finder.plist 320 | 321 | # Increase grid spacing for icons on the desktop and in other icon views 322 | /usr/libexec/PlistBuddy -c "Set :DesktopViewSettings:IconViewSettings:gridSpacing 50" ~/Library/Preferences/com.apple.finder.plist 323 | /usr/libexec/PlistBuddy -c "Set :FK_StandardViewSettings:IconViewSettings:gridSpacing 50" ~/Library/Preferences/com.apple.finder.plist 324 | /usr/libexec/PlistBuddy -c "Set :StandardViewSettings:IconViewSettings:gridSpacing 50" ~/Library/Preferences/com.apple.finder.plist 325 | 326 | # Increase the size of icons on the desktop and in other icon views 327 | /usr/libexec/PlistBuddy -c "Set :DesktopViewSettings:IconViewSettings:iconSize 44" ~/Library/Preferences/com.apple.finder.plist 328 | /usr/libexec/PlistBuddy -c "Set :FK_StandardViewSettings:IconViewSettings:iconSize 44" ~/Library/Preferences/com.apple.finder.plist 329 | /usr/libexec/PlistBuddy -c "Set :StandardViewSettings:IconViewSettings:iconSize 44" ~/Library/Preferences/com.apple.finder.plist 330 | 331 | # Use list view in all Finder windows by default 332 | # Four-letter codes for the other view modes: `icnv`, `clmv`, `glyv` 333 | defaults write com.apple.finder FXPreferredViewStyle -string "Nlsv" 334 | 335 | # Disable the warning before emptying the Trash 336 | #defaults write com.apple.finder WarnOnEmptyTrash -bool false 337 | 338 | # Enable AirDrop over Ethernet and on unsupported Macs running Lion 339 | defaults write com.apple.NetworkBrowser BrowseAllInterfaces -bool true 340 | 341 | # Show the ~/Library folder 342 | chflags nohidden ~/Library 343 | 344 | # Show the /Volumes folder 345 | #sudo chflags nohidden /Volumes 346 | 347 | # Remove Dropbox’s green checkmark icons in Finder 348 | #file=/Applications/Dropbox.app/Contents/Resources/emblem-dropbox-uptodate.icns 349 | #[ -e "${file}" ] && mv -f "${file}" "${file}.bak" 350 | 351 | # Expand the following File Info panes: 352 | # “General”, “Open with”, and “Sharing & Permissions” 353 | defaults write com.apple.finder FXInfoPanesExpanded -dict \ 354 | General -bool true \ 355 | OpenWith -bool true \ 356 | Privileges -bool true 357 | 358 | ############################################################################### 359 | # Dock, Dashboard, and hot corners # 360 | ############################################################################### 361 | 362 | # Enable highlight hover effect for the grid view of a stack (Dock) 363 | defaults write com.apple.dock mouse-over-hilite-stack -bool true 364 | 365 | # Set the icon size of Dock items to 36 pixels 366 | #defaults write com.apple.dock tilesize -int 36 367 | 368 | # Change minimize/maximize window effect 369 | #defaults write com.apple.dock mineffect -string "scale" 370 | 371 | # Minimize windows into their application’s icon 372 | #defaults write com.apple.dock minimize-to-application -bool true 373 | 374 | # Enable spring loading for all Dock items 375 | defaults write com.apple.dock enable-spring-load-actions-on-all-items -bool true 376 | 377 | # Show indicator lights for open applications in the Dock 378 | defaults write com.apple.dock show-process-indicators -bool true 379 | 380 | # Wipe all (default) app icons from the Dock 381 | # This is only really useful when setting up a new Mac, or if you don’t use 382 | # the Dock to launch apps. 383 | #defaults write com.apple.dock persistent-apps -array 384 | 385 | # Show only open applications in the Dock 386 | #defaults write com.apple.dock static-only -bool true 387 | 388 | # Don’t animate opening applications from the Dock 389 | #defaults write com.apple.dock launchanim -bool false 390 | 391 | # Speed up Mission Control animations 392 | #defaults write com.apple.dock expose-animation-duration -float 0.1 393 | 394 | # Don’t group windows by application in Mission Control 395 | # (i.e. use the old Exposé behavior instead) 396 | #defaults write com.apple.dock expose-group-by-app -bool false 397 | 398 | # Disable Dashboard 399 | #defaults write com.apple.dashboard mcx-disabled -bool true 400 | 401 | # Don’t show Dashboard as a Space 402 | defaults write com.apple.dock dashboard-in-overlay -bool true 403 | 404 | # Don’t automatically rearrange Spaces based on most recent use 405 | defaults write com.apple.dock mru-spaces -bool false 406 | 407 | # Remove the auto-hiding Dock delay 408 | #defaults write com.apple.dock autohide-delay -float 0 409 | # Remove the animation when hiding/showing the Dock 410 | #defaults write com.apple.dock autohide-time-modifier -float 0 411 | 412 | # Automatically hide and show the Dock 413 | #defaults write com.apple.dock autohide -bool true 414 | 415 | # Make Dock icons of hidden applications translucent 416 | defaults write com.apple.dock showhidden -bool true 417 | 418 | # Don’t show recent applications in Dock 419 | #defaults write com.apple.dock show-recents -bool false 420 | 421 | # Disable the Launchpad gesture (pinch with thumb and three fingers) 422 | #defaults write com.apple.dock showLaunchpadGestureEnabled -int 0 423 | 424 | # Reset Launchpad, but keep the desktop wallpaper intact 425 | #find "${HOME}/Library/Application Support/Dock" -name "*-*.db" -maxdepth 1 -delete 426 | 427 | # Add iOS & Watch Simulator to Launchpad 428 | #sudo ln -sf "/Applications/Xcode.app/Contents/Developer/Applications/Simulator.app" "/Applications/Simulator.app" 429 | #sudo ln -sf "/Applications/Xcode.app/Contents/Developer/Applications/Simulator (Watch).app" "/Applications/Simulator (Watch).app" 430 | 431 | # Add a spacer to the left side of the Dock (where the applications are) 432 | #defaults write com.apple.dock persistent-apps -array-add '{tile-data={}; tile-type="spacer-tile";}' 433 | # Add a spacer to the right side of the Dock (where the Trash is) 434 | #defaults write com.apple.dock persistent-others -array-add '{tile-data={}; tile-type="spacer-tile";}' 435 | 436 | # Hot corners 437 | # Possible values: 438 | # 0: no-op 439 | # 2: Mission Control 440 | # 3: Show application windows 441 | # 4: Desktop 442 | # 5: Start screen saver 443 | # 6: Disable screen saver 444 | # 7: Dashboard 445 | # 10: Put display to sleep 446 | # 11: Launchpad 447 | # 12: Notification Center 448 | # 13: Lock Screen 449 | # Top left screen corner → Mission Control 450 | #defaults write com.apple.dock wvous-tl-corner -int 2 451 | #defaults write com.apple.dock wvous-tl-modifier -int 0 452 | # Top right screen corner → Desktop 453 | #defaults write com.apple.dock wvous-tr-corner -int 4 454 | #defaults write com.apple.dock wvous-tr-modifier -int 0 455 | # Bottom left screen corner → Start screen saver 456 | #defaults write com.apple.dock wvous-bl-corner -int 5 457 | #defaults write com.apple.dock wvous-bl-modifier -int 0 458 | 459 | ############################################################################### 460 | # Safari & WebKit # 461 | ############################################################################### 462 | 463 | # Privacy: don’t send search queries to Apple 464 | #defaults write com.apple.Safari UniversalSearchEnabled -bool false 465 | #defaults write com.apple.Safari SuppressSearchSuggestions -bool true 466 | 467 | # Press Tab to highlight each item on a web page 468 | #defaults write com.apple.Safari WebKitTabToLinksPreferenceKey -bool true 469 | #defaults write com.apple.Safari com.apple.Safari.ContentPageGroupIdentifier.WebKit2TabsToLinks -bool true 470 | 471 | # Show the full URL in the address bar (note: this still hides the scheme) 472 | #defaults write com.apple.Safari ShowFullURLInSmartSearchField -bool true 473 | 474 | # Set Safari’s home page to `about:blank` for faster loading 475 | #defaults write com.apple.Safari HomePage -string "about:blank" 476 | 477 | # Prevent Safari from opening ‘safe’ files automatically after downloading 478 | #defaults write com.apple.Safari AutoOpenSafeDownloads -bool false 479 | 480 | # Allow hitting the Backspace key to go to the previous page in history 481 | #defaults write com.apple.Safari com.apple.Safari.ContentPageGroupIdentifier.WebKit2BackspaceKeyNavigationEnabled -bool true 482 | 483 | # Hide Safari’s bookmarks bar by default 484 | #defaults write com.apple.Safari ShowFavoritesBar -bool false 485 | 486 | # Hide Safari’s sidebar in Top Sites 487 | #defaults write com.apple.Safari ShowSidebarInTopSites -bool false 488 | 489 | # Disable Safari’s thumbnail cache for History and Top Sites 490 | #defaults write com.apple.Safari DebugSnapshotsUpdatePolicy -int 2 491 | 492 | # Enable Safari’s debug menu 493 | defaults write com.apple.Safari IncludeInternalDebugMenu -bool true 494 | 495 | # Make Safari’s search banners default to Contains instead of Starts With 496 | defaults write com.apple.Safari FindOnPageMatchesWordStartsOnly -bool false 497 | 498 | # Remove useless icons from Safari’s bookmarks bar 499 | #defaults write com.apple.Safari ProxiesInBookmarksBar "()" 500 | 501 | # Enable the Develop menu and the Web Inspector in Safari 502 | defaults write com.apple.Safari IncludeDevelopMenu -bool true 503 | defaults write com.apple.Safari WebKitDeveloperExtrasEnabledPreferenceKey -bool true 504 | defaults write com.apple.Safari com.apple.Safari.ContentPageGroupIdentifier.WebKit2DeveloperExtrasEnabled -bool true 505 | 506 | # Add a context menu item for showing the Web Inspector in web views 507 | defaults write NSGlobalDomain WebKitDeveloperExtras -bool true 508 | 509 | # Enable continuous spellchecking 510 | #defaults write com.apple.Safari WebContinuousSpellCheckingEnabled -bool true 511 | # Disable auto-correct 512 | #defaults write com.apple.Safari WebAutomaticSpellingCorrectionEnabled -bool false 513 | 514 | # Disable AutoFill 515 | #defaults write com.apple.Safari AutoFillFromAddressBook -bool false 516 | #defaults write com.apple.Safari AutoFillPasswords -bool false 517 | #defaults write com.apple.Safari AutoFillCreditCardData -bool false 518 | #defaults write com.apple.Safari AutoFillMiscellaneousForms -bool false 519 | 520 | # Warn about fraudulent websites 521 | #defaults write com.apple.Safari WarnAboutFraudulentWebsites -bool true 522 | 523 | # Disable plug-ins 524 | #defaults write com.apple.Safari WebKitPluginsEnabled -bool false 525 | #defaults write com.apple.Safari com.apple.Safari.ContentPageGroupIdentifier.WebKit2PluginsEnabled -bool false 526 | 527 | # Disable Java 528 | #defaults write com.apple.Safari WebKitJavaEnabled -bool false 529 | #defaults write com.apple.Safari com.apple.Safari.ContentPageGroupIdentifier.WebKit2JavaEnabled -bool false 530 | #defaults write com.apple.Safari com.apple.Safari.ContentPageGroupIdentifier.WebKit2JavaEnabledForLocalFiles -bool false 531 | 532 | # Block pop-up windows 533 | #defaults write com.apple.Safari WebKitJavaScriptCanOpenWindowsAutomatically -bool false 534 | #defaults write com.apple.Safari com.apple.Safari.ContentPageGroupIdentifier.WebKit2JavaScriptCanOpenWindowsAutomatically -bool false 535 | 536 | # Disable auto-playing video 537 | #defaults write com.apple.Safari WebKitMediaPlaybackAllowsInline -bool false 538 | #defaults write com.apple.SafariTechnologyPreview WebKitMediaPlaybackAllowsInline -bool false 539 | #defaults write com.apple.Safari com.apple.Safari.ContentPageGroupIdentifier.WebKit2AllowsInlineMediaPlayback -bool false 540 | #defaults write com.apple.SafariTechnologyPreview com.apple.Safari.ContentPageGroupIdentifier.WebKit2AllowsInlineMediaPlayback -bool false 541 | 542 | # Enable “Do Not Track” 543 | #defaults write com.apple.Safari SendDoNotTrackHTTPHeader -bool true 544 | 545 | # Update extensions automatically 546 | #defaults write com.apple.Safari InstallExtensionUpdatesAutomatically -bool true 547 | 548 | ############################################################################### 549 | # Mail # 550 | ############################################################################### 551 | 552 | # Disable send and reply animations in Mail.app 553 | #defaults write com.apple.mail DisableReplyAnimations -bool true 554 | #defaults write com.apple.mail DisableSendAnimations -bool true 555 | 556 | # Copy email addresses as `foo@example.com` instead of `Foo Bar ` in Mail.app 557 | #defaults write com.apple.mail AddressesIncludeNameOnPasteboard -bool false 558 | 559 | # Add the keyboard shortcut ⌘ + Enter to send an email in Mail.app 560 | #defaults write com.apple.mail NSUserKeyEquivalents -dict-add "Send" "@\U21a9" 561 | 562 | # Display emails in threaded mode, sorted by date (oldest at the top) 563 | #defaults write com.apple.mail DraftsViewerAttributes -dict-add "DisplayInThreadedMode" -string "yes" 564 | #defaults write com.apple.mail DraftsViewerAttributes -dict-add "SortedDescending" -string "yes" 565 | #defaults write com.apple.mail DraftsViewerAttributes -dict-add "SortOrder" -string "received-date" 566 | 567 | # Disable inline attachments (just show the icons) 568 | #defaults write com.apple.mail DisableInlineAttachmentViewing -bool true 569 | 570 | # Disable automatic spell checking 571 | #defaults write com.apple.mail SpellCheckingBehavior -string "NoSpellCheckingEnabled" 572 | 573 | ############################################################################### 574 | # Spotlight # 575 | ############################################################################### 576 | 577 | # Hide Spotlight tray-icon (and subsequent helper) 578 | #sudo chmod 600 /System/Library/CoreServices/Search.bundle/Contents/MacOS/Search 579 | # Disable Spotlight indexing for any volume that gets mounted and has not yet 580 | # been indexed before. 581 | # Use `sudo mdutil -i off "/Volumes/foo"` to stop indexing any volume. 582 | #sudo defaults write /.Spotlight-V100/VolumeConfiguration Exclusions -array "/Volumes" 583 | # Change indexing order and disable some search results 584 | # Yosemite-specific search results (remove them if you are using macOS 10.9 or older): 585 | # MENU_DEFINITION 586 | # MENU_CONVERSION 587 | # MENU_EXPRESSION 588 | # MENU_SPOTLIGHT_SUGGESTIONS (send search queries to Apple) 589 | # MENU_WEBSEARCH (send search queries to Apple) 590 | # MENU_OTHER 591 | #defaults write com.apple.spotlight orderedItems -array \ 592 | # '{"enabled" = 1;"name" = "APPLICATIONS";}' \ 593 | # '{"enabled" = 1;"name" = "SYSTEM_PREFS";}' \ 594 | # '{"enabled" = 1;"name" = "DIRECTORIES";}' \ 595 | # '{"enabled" = 1;"name" = "PDF";}' \ 596 | # '{"enabled" = 1;"name" = "FONTS";}' \ 597 | # '{"enabled" = 0;"name" = "DOCUMENTS";}' \ 598 | # '{"enabled" = 0;"name" = "MESSAGES";}' \ 599 | # '{"enabled" = 0;"name" = "CONTACT";}' \ 600 | # '{"enabled" = 0;"name" = "EVENT_TODO";}' \ 601 | # '{"enabled" = 0;"name" = "IMAGES";}' \ 602 | # '{"enabled" = 0;"name" = "BOOKMARKS";}' \ 603 | # '{"enabled" = 0;"name" = "MUSIC";}' \ 604 | # '{"enabled" = 0;"name" = "MOVIES";}' \ 605 | # '{"enabled" = 0;"name" = "PRESENTATIONS";}' \ 606 | # '{"enabled" = 0;"name" = "SPREADSHEETS";}' \ 607 | # '{"enabled" = 0;"name" = "SOURCE";}' \ 608 | # '{"enabled" = 0;"name" = "MENU_DEFINITION";}' \ 609 | # '{"enabled" = 0;"name" = "MENU_OTHER";}' \ 610 | # '{"enabled" = 0;"name" = "MENU_CONVERSION";}' \ 611 | # '{"enabled" = 0;"name" = "MENU_EXPRESSION";}' \ 612 | # '{"enabled" = 0;"name" = "MENU_WEBSEARCH";}' \ 613 | # '{"enabled" = 0;"name" = "MENU_SPOTLIGHT_SUGGESTIONS";}' 614 | # Load new settings before rebuilding the index 615 | #killall mds > /dev/null 2>&1 616 | # Make sure indexing is enabled for the main volume 617 | #sudo mdutil -i on / > /dev/null 618 | # Rebuild the index from scratch 619 | #sudo mdutil -E / > /dev/null 620 | 621 | ############################################################################### 622 | # Terminal & iTerm 2 # 623 | ############################################################################### 624 | 625 | # Only use UTF-8 in Terminal.app 626 | defaults write com.apple.terminal StringEncodings -array 4 627 | 628 | # Use a modified version of the Solarized Dark theme by default in Terminal.app 629 | # osascript < /dev/null && sudo tmutil disablelocal 702 | 703 | ############################################################################### 704 | # Activity Monitor # 705 | ############################################################################### 706 | 707 | # Show the main window when launching Activity Monitor 708 | #defaults write com.apple.ActivityMonitor OpenMainWindow -bool true 709 | 710 | # Visualize CPU usage in the Activity Monitor Dock icon 711 | #defaults write com.apple.ActivityMonitor IconType -int 5 712 | 713 | # Show all processes in Activity Monitor 714 | #defaults write com.apple.ActivityMonitor ShowCategory -int 0 715 | 716 | # Sort Activity Monitor results by CPU usage 717 | #defaults write com.apple.ActivityMonitor SortColumn -string "CPUUsage" 718 | #defaults write com.apple.ActivityMonitor SortDirection -int 0 719 | 720 | ############################################################################### 721 | # Address Book, Dashboard, iCal, TextEdit, and Disk Utility # 722 | ############################################################################### 723 | 724 | # Enable the debug menu in Address Book 725 | #defaults write com.apple.addressbook ABShowDebugMenu -bool true 726 | 727 | # Enable Dashboard dev mode (allows keeping widgets on the desktop) 728 | #defaults write com.apple.dashboard devmode -bool true 729 | 730 | # Enable the debug menu in iCal (pre-10.8) 731 | #defaults write com.apple.iCal IncludeDebugMenu -bool true 732 | 733 | # Use plain text mode for new TextEdit documents 734 | #defaults write com.apple.TextEdit RichText -int 0 735 | # Open and save files as UTF-8 in TextEdit 736 | #defaults write com.apple.TextEdit PlainTextEncoding -int 4 737 | #defaults write com.apple.TextEdit PlainTextEncodingForWrite -int 4 738 | 739 | # Enable the debug menu in Disk Utility 740 | #defaults write com.apple.DiskUtility DUDebugMenuEnabled -bool true 741 | #defaults write com.apple.DiskUtility advanced-image-options -bool true 742 | 743 | # Auto-play videos when opened with QuickTime Player 744 | #defaults write com.apple.QuickTimePlayerX MGPlayMovieOnOpen -bool true 745 | 746 | ############################################################################### 747 | # Mac App Store # 748 | ############################################################################### 749 | 750 | # Enable the WebKit Developer Tools in the Mac App Store 751 | #defaults write com.apple.appstore WebKitDeveloperExtras -bool true 752 | 753 | # Enable Debug Menu in the Mac App Store 754 | #defaults write com.apple.appstore ShowDebugMenu -bool true 755 | 756 | # Enable the automatic update check 757 | #defaults write com.apple.SoftwareUpdate AutomaticCheckEnabled -bool true 758 | 759 | # Check for software updates daily, not just once per week 760 | defaults write com.apple.SoftwareUpdate ScheduleFrequency -int 1 761 | 762 | # Download newly available updates in background 763 | #defaults write com.apple.SoftwareUpdate AutomaticDownload -int 1 764 | 765 | # Install System data files & security updates 766 | #defaults write com.apple.SoftwareUpdate CriticalUpdateInstall -int 1 767 | 768 | # Automatically download apps purchased on other Macs 769 | #defaults write com.apple.SoftwareUpdate ConfigDataInstall -int 1 770 | 771 | # Turn on app auto-update 772 | #defaults write com.apple.commerce AutoUpdate -bool true 773 | 774 | # Allow the App Store to reboot machine on macOS updates 775 | #defaults write com.apple.commerce AutoUpdateRestartRequired -bool true 776 | 777 | ############################################################################### 778 | # Photos # 779 | ############################################################################### 780 | 781 | # Prevent Photos from opening automatically when devices are plugged in 782 | #defaults -currentHost write com.apple.ImageCapture disableHotPlug -bool true 783 | 784 | ############################################################################### 785 | # Messages # 786 | ############################################################################### 787 | 788 | # Disable automatic emoji substitution (i.e. use plain text smileys) 789 | #defaults write com.apple.messageshelper.MessageController SOInputLineSettings -dict-add "automaticEmojiSubstitutionEnablediMessage" -bool false 790 | 791 | # Disable smart quotes as it’s annoying for messages that contain code 792 | #defaults write com.apple.messageshelper.MessageController SOInputLineSettings -dict-add "automaticQuoteSubstitutionEnabled" -bool false 793 | 794 | # Disable continuous spell checking 795 | #defaults write com.apple.messageshelper.MessageController SOInputLineSettings -dict-add "continuousSpellCheckingEnabled" -bool false 796 | 797 | ############################################################################### 798 | # Kill affected applications # 799 | ############################################################################### 800 | 801 | for app in "Activity Monitor" \ 802 | "Address Book" \ 803 | "Calendar" \ 804 | "cfprefsd" \ 805 | "Contacts" \ 806 | "Dock" \ 807 | "Finder" \ 808 | "Mail" \ 809 | "Messages" \ 810 | "Photos" \ 811 | "Safari" \ 812 | "SystemUIServer" \ 813 | "Terminal" \ 814 | "Calendar"; do 815 | killall "${app}" &> /dev/null 816 | done 817 | echo "Done. Note that some of these changes require a logout/restart to take effect." 818 | -------------------------------------------------------------------------------- /nvmrc: -------------------------------------------------------------------------------- 1 | v20 2 | -------------------------------------------------------------------------------- /ssh/config: -------------------------------------------------------------------------------- 1 | Include config.d/* 2 | 3 | Host * 4 | AddKeysToAgent yes 5 | UseKeychain yes 6 | ForwardAgent no 7 | IdentityFile ~/.ssh/id_ed25519 8 | HostkeyAlgorithms +ssh-rsa 9 | PubkeyAcceptedAlgorithms +ssh-rsa 10 | ControlMaster auto 11 | ControlPath ~/.ssh/sockets/%r@%h-%p 12 | ControlPersist 900 13 | -------------------------------------------------------------------------------- /starship.toml: -------------------------------------------------------------------------------- 1 | "$schema" = 'https://starship.rs/config-schema.json' 2 | 3 | add_newline = false 4 | right_format = """$cmd_duration$status$sudo""" 5 | 6 | [line_break] 7 | disabled = true 8 | 9 | [directory] 10 | truncation_length = 0 11 | truncate_to_repo = false 12 | read_only = '' 13 | 14 | [character] 15 | success_symbol = '[❯](bold black)' 16 | error_symbol = '[❯](bold black)' 17 | 18 | [status] 19 | disabled = false 20 | symbol = '↵ ' 21 | 22 | [sudo] 23 | disabled = false 24 | format = '[$](red bold)' 25 | 26 | [git_branch] 27 | format = '[$symbol$branch(:$remote_branch)]($style) ' 28 | symbol = '' 29 | style = 'bold yellow' 30 | 31 | [git_status] 32 | format = '([$all_status$ahead_behind]($style)) ' 33 | stashed = '' 34 | modified = '🞱' 35 | diverged = '‼' 36 | deleted = '×' 37 | 38 | [cmd_duration] 39 | disabled = false 40 | 41 | [username] 42 | disabled = true 43 | [hostname] 44 | disabled = true 45 | [localip] 46 | disabled = true 47 | [shlvl] 48 | disabled = true 49 | [singularity] 50 | disabled = true 51 | [kubernetes] 52 | disabled = true 53 | [vcsh] 54 | disabled = true 55 | [fossil_branch] 56 | disabled = true 57 | [hg_branch] 58 | disabled = true 59 | [pijul_channel] 60 | disabled = true 61 | [docker_context] 62 | disabled = true 63 | [package] 64 | disabled = true 65 | [c] 66 | disabled = true 67 | [cmake] 68 | disabled = true 69 | [cobol] 70 | disabled = true 71 | [daml] 72 | disabled = true 73 | [dart] 74 | disabled = true 75 | [deno] 76 | disabled = true 77 | [dotnet] 78 | disabled = true 79 | [elixir] 80 | disabled = true 81 | [elm] 82 | disabled = true 83 | [erlang] 84 | disabled = true 85 | [fennel] 86 | disabled = true 87 | [golang] 88 | disabled = true 89 | [guix_shell] 90 | disabled = true 91 | [haskell] 92 | disabled = true 93 | [haxe] 94 | disabled = true 95 | [helm] 96 | disabled = true 97 | [java] 98 | disabled = true 99 | [julia] 100 | disabled = true 101 | [kotlin] 102 | disabled = true 103 | [gradle] 104 | disabled = true 105 | [lua] 106 | disabled = true 107 | [nim] 108 | disabled = true 109 | [nodejs] 110 | disabled = true 111 | [ocaml] 112 | disabled = true 113 | [opa] 114 | disabled = true 115 | [perl] 116 | disabled = true 117 | [php] 118 | disabled = true 119 | [pulumi] 120 | disabled = true 121 | [purescript] 122 | disabled = true 123 | [python] 124 | disabled = true 125 | [raku] 126 | disabled = true 127 | [rlang] 128 | disabled = true 129 | [red] 130 | disabled = true 131 | [ruby] 132 | disabled = true 133 | [rust] 134 | disabled = true 135 | [scala] 136 | disabled = true 137 | [solidity] 138 | disabled = true 139 | [swift] 140 | disabled = true 141 | [terraform] 142 | disabled = true 143 | [vlang] 144 | disabled = true 145 | [vagrant] 146 | disabled = true 147 | [zig] 148 | disabled = true 149 | [buf] 150 | disabled = true 151 | [nix_shell] 152 | disabled = true 153 | [conda] 154 | disabled = true 155 | [meson] 156 | disabled = true 157 | [spack] 158 | disabled = true 159 | [memory_usage] 160 | disabled = true 161 | [aws] 162 | disabled = true 163 | [gcloud] 164 | disabled = true 165 | [openstack] 166 | disabled = true 167 | [azure] 168 | disabled = true 169 | [crystal] 170 | disabled = true 171 | [battery] 172 | disabled = true 173 | [time] 174 | disabled = true 175 | [os] 176 | disabled = true 177 | [container] 178 | disabled = true 179 | [shell] 180 | disabled = true 181 | -------------------------------------------------------------------------------- /tmux.conf: -------------------------------------------------------------------------------- 1 | # vim: syntax=tmux 2 | 3 | # remap prefix to ctrl+y 4 | set -g prefix C-y 5 | unbind C-b 6 | bind C-a send-prefix 7 | 8 | bind-key -T copy-mode-vi MouseDragEnd1Pane send-keys -X copy-pipe-and-cancel "pbcopy" 9 | 10 | # force a reload of the config file 11 | unbind , 12 | bind , source-file ~/.tmux.conf \; display-message " ~/.tmux.conf reloaded" 13 | 14 | # rename window using r 15 | unbind r 16 | bind r command-prompt 'rename-window %%' 17 | 18 | # quick pane cycling 19 | unbind C-y 20 | bind C-y select-pane -t :.+ 21 | 22 | # switch panes / windows using alt+arrow without prefix 23 | bind -n M-Left previous-window 24 | bind -n M-Right next-window 25 | bind -n M-Up select-pane -U 26 | bind -n M-Down select-pane -D 27 | # swap panes using ctrl+arrow 28 | #bind -n C-Up swap-pane -U 29 | #bind -n C-Down swap-pane -D 30 | 31 | # split panes vertically using ctrl+d without prefix 32 | bind -n C-d split-window -v -c "#{pane_current_path}" \; select-layout even-vertical 33 | unbind '"' 34 | unbind % 35 | 36 | # create windows using ctrl+t without prefix 37 | bind -n C-t new-window -c "#{pane_current_path}" 38 | 39 | # kill panes using ctrl+x, resize equally on close and exit 40 | bind -n C-x kill-pane \; select-layout even-vertical 41 | set-hook -g pane-exited 'select-layout even-vertical' 42 | 43 | setw -g mouse on 44 | set -g history-limit 10000 45 | 46 | # Theme based on https://github.com/ndyakov/pimux 47 | 48 | # Colors 49 | # theme: flare 50 | BACKGROUND="terminal" 51 | FOREGROUND="terminal" 52 | BACKGROUND_DIM="#36393a" 53 | FOREGROUND_DIM="#62676a" 54 | HIGHLIGHT="#ffffff" 55 | ACTIVITY="#6caeff" 56 | BORDER="$BACKGROUND_DIM" 57 | 58 | # Title 59 | set -g set-titles on 60 | set -g set-titles-string '#(whoami)@#H - (#S:#W.#P)' 61 | 62 | # Panes 63 | set -g pane-border-style bg=$BACKGROUND,fg=$BORDER 64 | set -g pane-active-border-style bg=$BACKGROUND,fg=$BORDER 65 | 66 | set -g display-panes-time 1000 67 | set -g display-panes-colour $FOREGROUND 68 | set -g display-panes-active-colour $HIGHLIGHT 69 | 70 | # Mode 71 | set -g mode-style bg=$HIGHLIGHT,fg=$BACKGROUND 72 | 73 | # Windows 74 | set -g window-style bg=$BACKGROUND_DIM,fg=$FOREGROUND_DIM 75 | set -g window-active-style bg=$BACKGROUND,fg=$FOREGROUND 76 | 77 | setw -g window-status-format " #I:#W" 78 | setw -g window-status-style bg=terminal,fg=$FOREGROUND,dim 79 | 80 | setw -g window-status-current-format " #I:#W" 81 | setw -g window-status-current-style bg=terminal,fg=$HIGHLIGHT,bold 82 | 83 | setw -g window-status-activity-style bg=$BACKGROUND,fg=$ACTIVITY,dim 84 | 85 | # Base index ( start counting from 1 ) 86 | set -g base-index 1 87 | setw -g pane-base-index 1 88 | set-option -g renumber-windows on 89 | 90 | # Status Top 91 | set -g status-position top 92 | 93 | # Status Colors 94 | set -g status-style bg=$BACKGROUND,fg=$FOREGROUND 95 | 96 | # Status Interval 97 | # But there is a strange bug that freezes osx 98 | # https://github.com/tmux/tmux/issues/108 99 | # set to 0 100 | set -g status-interval 0 101 | 102 | # Status contents 103 | set -g status-left '' 104 | set -g status-right ' #S:#I.#P ' 105 | #set -g status-right ' #{?client_prefix,#[reverse]^y#[noreverse] ,}#S:#I.#P ' 106 | 107 | # Message 108 | set -g message-style bg=$BACKGROUND,fg=$HIGHLIGHT,bright 109 | 110 | unbind -n C-o 111 | bind -n C-o run $HOME/GitHub/dotfiles/tmux/tmux-url-select.pl 112 | -------------------------------------------------------------------------------- /tmux/tmux-url-select.pl: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env perl 2 | # 3 | # tmux-url-select 4 | # mit licensed 5 | # 6 | 7 | use strict; 8 | use warnings; 9 | 10 | ### config 11 | 12 | use constant COMMAND => 'open %s'; 13 | use constant YANK_COMMAND => 'echo %s | pbcopy'; 14 | 15 | use constant SHOW_STATUS_BAR => 1; 16 | use constant VERBOSE_MESSAGES => 0; 17 | use constant TMUX_WINDOW_TITLE => 'Select URL'; 18 | use constant TMUX_WINDOW_ID => 9999; 19 | use constant HIDE_WINDOW => 1; 20 | 21 | use constant PROMPT_COLOR => "\033[42;30m"; 22 | use constant ACTIVE_LINK_HIGHLIGHT => "\033[44;4m"; 23 | use constant NORMAL_LINK_HIGHLIGHT => "\033[94;1;4m"; 24 | 25 | # other options: 26 | # - blue background, underlined: \033[44;4m 27 | # - 256 color term light blue: \033[38;5;39m 28 | # - bold bright blue: \033[94;1;4m 29 | # - bright blue background: \033[104;4m 30 | # - just underlined: \033[4m 31 | 32 | # regex stolen from urxvtperls url-select.pl 33 | my $url_pattern = qr{( 34 | (?:https?://|ftp://|news://|git://|mailto:|file://|www\.) 35 | [\w\-\@;\/?:&=%\$_.+!*\x27(),~#\x1b\[\]]+[\w\-\@;\/?&=%\$_+!*\x27(~] 36 | )}x; 37 | 38 | ### config end 39 | 40 | my $raw_buffer; 41 | my $buffer; 42 | my $buffer_first_newline_position; 43 | my @matches; 44 | my $match_count; 45 | my $selection = 0; 46 | 47 | # terminal helper functions 48 | 49 | sub clear { 50 | print "\033[H\033[2J"; 51 | } 52 | 53 | sub display_status_bar { 54 | my $is_first_line = shift; 55 | my $position = $is_first_line ? "2;2" : "1;2"; 56 | print sprintf("\033[%sH%s URL select: (%s/%s) [j/k/y/o/q/enter] \033[0m", $position, PROMPT_COLOR, $selection+1, $match_count); 57 | } 58 | 59 | sub display_highlighted_buffer { 60 | my $i = 0; 61 | my $is_first_line = 0; 62 | my $cb = sub { 63 | if ($i++ == $selection) { 64 | $is_first_line = 1 if ($+[0] < $buffer_first_newline_position); 65 | return ACTIVE_LINK_HIGHLIGHT."$1\033[0m"; 66 | return; 67 | } 68 | return NORMAL_LINK_HIGHLIGHT."$1\033[0m" if NORMAL_LINK_HIGHLIGHT; 69 | return $1; 70 | }; 71 | print $buffer =~ s/($url_pattern)/&$cb()/ger; 72 | return $is_first_line; 73 | } 74 | 75 | sub display_stuff { 76 | clear(); 77 | my $is_first_line = display_highlighted_buffer(); 78 | display_status_bar($is_first_line) if SHOW_STATUS_BAR; 79 | } 80 | 81 | # tmux command helpers 82 | 83 | sub tmux_display_message { 84 | system 'tmux', 'display-message', shift; 85 | } 86 | 87 | sub tmux_switch_to_last { 88 | system 'tmux', 'last-window'; 89 | } 90 | 91 | sub tmux_select_my_window { 92 | system "tmux", "select-window", "-t", TMUX_WINDOW_ID; 93 | } 94 | 95 | sub tmux_capture_pane { 96 | system "tmux", "capture-pane", "-eJ"; 97 | } 98 | 99 | sub tmux_get_buffer { 100 | return `tmux show-buffer`; 101 | } 102 | 103 | sub tmux_open_inner_window { 104 | system "tmux", "new-window", "-dn", "", "-t", TMUX_WINDOW_ID, "$0 inner"; 105 | system "tmux", "setw", "-qt", TMUX_WINDOW_ID, "window-status-format", ""; 106 | system "tmux", "setw", "-qt", TMUX_WINDOW_ID, "window-status-current-format", ""; 107 | } 108 | 109 | # other shell helpers 110 | 111 | sub enable_canonical_mode { 112 | # "canonical mode" to read char by char, thanks roger. 113 | system "stty", "-icanon", "cbreak", "min", "1", "-echo"; 114 | } 115 | 116 | sub single_quote_escape { 117 | return "'".(shift =~ s/\'/%27/gr)."'"; 118 | } 119 | 120 | # actions 121 | 122 | sub fix_url { 123 | my $url = shift; 124 | # some silly url openers think ^www. urls are files 125 | $url = "http://".$url if $url =~ /^www\./; 126 | # clear out color codes 127 | $url =~ s/\x1b\[[0-9;]*m//g; 128 | return $url; 129 | } 130 | 131 | sub safe_exec { 132 | my ($command, $message) = @_; 133 | $SIG{CHLD} = 'IGNORE'; 134 | $SIG{HUP} = 'IGNORE'; 135 | 136 | unless (fork) { 137 | tmux_display_message($message) if VERBOSE_MESSAGES; 138 | exec $command; 139 | } 140 | } 141 | 142 | sub launch_url { 143 | my $url = fix_url(shift); 144 | tmux_switch_to_last() if shift; 145 | 146 | my $command = sprintf(COMMAND, single_quote_escape($url)); 147 | safe_exec($command, "Launched ". $url); 148 | } 149 | 150 | sub yank_url { 151 | my $url = fix_url(shift); 152 | tmux_switch_to_last() if shift; 153 | my $command = sprintf(YANK_COMMAND, single_quote_escape($url)); 154 | safe_exec($command, "Yanked ". $url); 155 | } 156 | 157 | # main functions 158 | 159 | sub main_inner { 160 | $raw_buffer = tmux_get_buffer(); 161 | system "tmux delete-buffer"; 162 | 163 | $buffer = $raw_buffer =~ s/\n$//r; 164 | $buffer_first_newline_position = index($raw_buffer, "\n"); 165 | 166 | @matches = ($buffer =~ /$url_pattern/g); 167 | $match_count = @matches; 168 | exit 1 if !$match_count; 169 | 170 | $selection = $#matches; 171 | 172 | display_stuff(); 173 | 174 | enable_canonical_mode(); 175 | 176 | # switch to the tmux-url-select window now to avoid 'flickering' 177 | tmux_select_my_window(); 178 | 179 | # main loop 180 | while(defined($_ = getc)) { 181 | $selection++ if /[jB]/; 182 | $selection-- if /[kA]/; 183 | $selection = ($_-1) if /[0-9]/; 184 | $selection %= $match_count; 185 | my $do_return = /[qyo\n]/; 186 | yank_url($matches[$selection], $do_return) if /[yY]/; 187 | launch_url($matches[$selection], $do_return) if /[\noO]/; 188 | return if $do_return; 189 | display_stuff(); 190 | } 191 | } 192 | 193 | sub main { 194 | tmux_capture_pane(); 195 | 196 | @matches = tmux_get_buffer() =~ /$url_pattern/g; 197 | $match_count = @matches; 198 | 199 | if (!$match_count) { 200 | tmux_display_message("No URLs"); 201 | system "tmux delete-buffer"; 202 | exit 0; 203 | } 204 | 205 | # open window here, backgrounded 206 | tmux_open_inner_window(); 207 | } 208 | 209 | if (!@ARGV) { 210 | main(); 211 | } elsif ($ARGV[0] eq "inner") { 212 | main_inner(); 213 | } 214 | -------------------------------------------------------------------------------- /vim/autoload/plug.vim: -------------------------------------------------------------------------------- 1 | " vim-plug: Vim plugin manager 2 | " ============================ 3 | " 4 | " Download plug.vim and put it in ~/.vim/autoload 5 | " 6 | " curl -fLo ~/.vim/autoload/plug.vim --create-dirs \ 7 | " https://raw.githubusercontent.com/junegunn/vim-plug/master/plug.vim 8 | " 9 | " Edit your .vimrc 10 | " 11 | " call plug#begin('~/.vim/plugged') 12 | " 13 | " " Make sure you use single quotes 14 | " 15 | " " Shorthand notation; fetches https://github.com/junegunn/vim-easy-align 16 | " Plug 'junegunn/vim-easy-align' 17 | " 18 | " " Any valid git URL is allowed 19 | " Plug 'https://github.com/junegunn/vim-github-dashboard.git' 20 | " 21 | " " Multiple Plug commands can be written in a single line using | separators 22 | " Plug 'SirVer/ultisnips' | Plug 'honza/vim-snippets' 23 | " 24 | " " On-demand loading 25 | " Plug 'preservim/nerdtree', { 'on': 'NERDTreeToggle' } 26 | " Plug 'tpope/vim-fireplace', { 'for': 'clojure' } 27 | " 28 | " " Using a non-default branch 29 | " Plug 'rdnetto/YCM-Generator', { 'branch': 'stable' } 30 | " 31 | " " Using a tagged release; wildcard allowed (requires git 1.9.2 or above) 32 | " Plug 'fatih/vim-go', { 'tag': '*' } 33 | " 34 | " " Plugin options 35 | " Plug 'nsf/gocode', { 'tag': 'v.20150303', 'rtp': 'vim' } 36 | " 37 | " " Plugin outside ~/.vim/plugged with post-update hook 38 | " Plug 'junegunn/fzf', { 'dir': '~/.fzf', 'do': './install --all' } 39 | " 40 | " " Unmanaged plugin (manually installed and updated) 41 | " Plug '~/my-prototype-plugin' 42 | " 43 | " " Initialize plugin system 44 | " call plug#end() 45 | " 46 | " Then reload .vimrc and :PlugInstall to install plugins. 47 | " 48 | " Plug options: 49 | " 50 | "| Option | Description | 51 | "| ----------------------- | ------------------------------------------------ | 52 | "| `branch`/`tag`/`commit` | Branch/tag/commit of the repository to use | 53 | "| `rtp` | Subdirectory that contains Vim plugin | 54 | "| `dir` | Custom directory for the plugin | 55 | "| `as` | Use different name for the plugin | 56 | "| `do` | Post-update hook (string or funcref) | 57 | "| `on` | On-demand loading: Commands or ``-mappings | 58 | "| `for` | On-demand loading: File types | 59 | "| `frozen` | Do not update unless explicitly specified | 60 | " 61 | " More information: https://github.com/junegunn/vim-plug 62 | " 63 | " 64 | " Copyright (c) 2017 Junegunn Choi 65 | " 66 | " MIT License 67 | " 68 | " Permission is hereby granted, free of charge, to any person obtaining 69 | " a copy of this software and associated documentation files (the 70 | " "Software"), to deal in the Software without restriction, including 71 | " without limitation the rights to use, copy, modify, merge, publish, 72 | " distribute, sublicense, and/or sell copies of the Software, and to 73 | " permit persons to whom the Software is furnished to do so, subject to 74 | " the following conditions: 75 | " 76 | " The above copyright notice and this permission notice shall be 77 | " included in all copies or substantial portions of the Software. 78 | " 79 | " THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, 80 | " EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF 81 | " MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND 82 | " NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE 83 | " LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION 84 | " OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION 85 | " WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. 86 | 87 | if exists('g:loaded_plug') 88 | finish 89 | endif 90 | let g:loaded_plug = 1 91 | 92 | let s:cpo_save = &cpo 93 | set cpo&vim 94 | 95 | let s:plug_src = 'https://github.com/junegunn/vim-plug.git' 96 | let s:plug_tab = get(s:, 'plug_tab', -1) 97 | let s:plug_buf = get(s:, 'plug_buf', -1) 98 | let s:mac_gui = has('gui_macvim') && has('gui_running') 99 | let s:is_win = has('win32') 100 | let s:nvim = has('nvim-0.2') || (has('nvim') && exists('*jobwait') && !s:is_win) 101 | let s:vim8 = has('patch-8.0.0039') && exists('*job_start') 102 | if s:is_win && &shellslash 103 | set noshellslash 104 | let s:me = resolve(expand(':p')) 105 | set shellslash 106 | else 107 | let s:me = resolve(expand(':p')) 108 | endif 109 | let s:base_spec = { 'branch': '', 'frozen': 0 } 110 | let s:TYPE = { 111 | \ 'string': type(''), 112 | \ 'list': type([]), 113 | \ 'dict': type({}), 114 | \ 'funcref': type(function('call')) 115 | \ } 116 | let s:loaded = get(s:, 'loaded', {}) 117 | let s:triggers = get(s:, 'triggers', {}) 118 | 119 | function! s:is_powershell(shell) 120 | return a:shell =~# 'powershell\(\.exe\)\?$' || a:shell =~# 'pwsh\(\.exe\)\?$' 121 | endfunction 122 | 123 | function! s:isabsolute(dir) abort 124 | return a:dir =~# '^/' || (has('win32') && a:dir =~? '^\%(\\\|[A-Z]:\)') 125 | endfunction 126 | 127 | function! s:git_dir(dir) abort 128 | let gitdir = s:trim(a:dir) . '/.git' 129 | if isdirectory(gitdir) 130 | return gitdir 131 | endif 132 | if !filereadable(gitdir) 133 | return '' 134 | endif 135 | let gitdir = matchstr(get(readfile(gitdir), 0, ''), '^gitdir: \zs.*') 136 | if len(gitdir) && !s:isabsolute(gitdir) 137 | let gitdir = a:dir . '/' . gitdir 138 | endif 139 | return isdirectory(gitdir) ? gitdir : '' 140 | endfunction 141 | 142 | function! s:git_origin_url(dir) abort 143 | let gitdir = s:git_dir(a:dir) 144 | let config = gitdir . '/config' 145 | if empty(gitdir) || !filereadable(config) 146 | return '' 147 | endif 148 | return matchstr(join(readfile(config)), '\[remote "origin"\].\{-}url\s*=\s*\zs\S*\ze') 149 | endfunction 150 | 151 | function! s:git_revision(dir) abort 152 | let gitdir = s:git_dir(a:dir) 153 | let head = gitdir . '/HEAD' 154 | if empty(gitdir) || !filereadable(head) 155 | return '' 156 | endif 157 | 158 | let line = get(readfile(head), 0, '') 159 | let ref = matchstr(line, '^ref: \zs.*') 160 | if empty(ref) 161 | return line 162 | endif 163 | 164 | if filereadable(gitdir . '/' . ref) 165 | return get(readfile(gitdir . '/' . ref), 0, '') 166 | endif 167 | 168 | if filereadable(gitdir . '/packed-refs') 169 | for line in readfile(gitdir . '/packed-refs') 170 | if line =~# ' ' . ref 171 | return matchstr(line, '^[0-9a-f]*') 172 | endif 173 | endfor 174 | endif 175 | 176 | return '' 177 | endfunction 178 | 179 | function! s:git_local_branch(dir) abort 180 | let gitdir = s:git_dir(a:dir) 181 | let head = gitdir . '/HEAD' 182 | if empty(gitdir) || !filereadable(head) 183 | return '' 184 | endif 185 | let branch = matchstr(get(readfile(head), 0, ''), '^ref: refs/heads/\zs.*') 186 | return len(branch) ? branch : 'HEAD' 187 | endfunction 188 | 189 | function! s:git_origin_branch(spec) 190 | if len(a:spec.branch) 191 | return a:spec.branch 192 | endif 193 | 194 | " The file may not be present if this is a local repository 195 | let gitdir = s:git_dir(a:spec.dir) 196 | let origin_head = gitdir.'/refs/remotes/origin/HEAD' 197 | if len(gitdir) && filereadable(origin_head) 198 | return matchstr(get(readfile(origin_head), 0, ''), 199 | \ '^ref: refs/remotes/origin/\zs.*') 200 | endif 201 | 202 | " The command may not return the name of a branch in detached HEAD state 203 | let result = s:lines(s:system('git symbolic-ref --short HEAD', a:spec.dir)) 204 | return v:shell_error ? '' : result[-1] 205 | endfunction 206 | 207 | if s:is_win 208 | function! s:plug_call(fn, ...) 209 | let shellslash = &shellslash 210 | try 211 | set noshellslash 212 | return call(a:fn, a:000) 213 | finally 214 | let &shellslash = shellslash 215 | endtry 216 | endfunction 217 | else 218 | function! s:plug_call(fn, ...) 219 | return call(a:fn, a:000) 220 | endfunction 221 | endif 222 | 223 | function! s:plug_getcwd() 224 | return s:plug_call('getcwd') 225 | endfunction 226 | 227 | function! s:plug_fnamemodify(fname, mods) 228 | return s:plug_call('fnamemodify', a:fname, a:mods) 229 | endfunction 230 | 231 | function! s:plug_expand(fmt) 232 | return s:plug_call('expand', a:fmt, 1) 233 | endfunction 234 | 235 | function! s:plug_tempname() 236 | return s:plug_call('tempname') 237 | endfunction 238 | 239 | function! plug#begin(...) 240 | if a:0 > 0 241 | let s:plug_home_org = a:1 242 | let home = s:path(s:plug_fnamemodify(s:plug_expand(a:1), ':p')) 243 | elseif exists('g:plug_home') 244 | let home = s:path(g:plug_home) 245 | elseif has('nvim') 246 | let home = stdpath('data') . '/plugged' 247 | elseif !empty(&rtp) 248 | let home = s:path(split(&rtp, ',')[0]) . '/plugged' 249 | else 250 | return s:err('Unable to determine plug home. Try calling plug#begin() with a path argument.') 251 | endif 252 | if s:plug_fnamemodify(home, ':t') ==# 'plugin' && s:plug_fnamemodify(home, ':h') ==# s:first_rtp 253 | return s:err('Invalid plug home. '.home.' is a standard Vim runtime path and is not allowed.') 254 | endif 255 | 256 | let g:plug_home = home 257 | let g:plugs = {} 258 | let g:plugs_order = [] 259 | let s:triggers = {} 260 | 261 | call s:define_commands() 262 | return 1 263 | endfunction 264 | 265 | function! s:define_commands() 266 | command! -nargs=+ -bar Plug call plug#() 267 | if !executable('git') 268 | return s:err('`git` executable not found. Most commands will not be available. To suppress this message, prepend `silent!` to `call plug#begin(...)`.') 269 | endif 270 | if has('win32') 271 | \ && &shellslash 272 | \ && (&shell =~# 'cmd\(\.exe\)\?$' || s:is_powershell(&shell)) 273 | return s:err('vim-plug does not support shell, ' . &shell . ', when shellslash is set.') 274 | endif 275 | if !has('nvim') 276 | \ && (has('win32') || has('win32unix')) 277 | \ && !has('multi_byte') 278 | return s:err('Vim needs +multi_byte feature on Windows to run shell commands. Enable +iconv for best results.') 279 | endif 280 | command! -nargs=* -bar -bang -complete=customlist,s:names PlugInstall call s:install(0, []) 281 | command! -nargs=* -bar -bang -complete=customlist,s:names PlugUpdate call s:update(0, []) 282 | command! -nargs=0 -bar -bang PlugClean call s:clean(0) 283 | command! -nargs=0 -bar PlugUpgrade if s:upgrade() | execute 'source' s:esc(s:me) | endif 284 | command! -nargs=0 -bar PlugStatus call s:status() 285 | command! -nargs=0 -bar PlugDiff call s:diff() 286 | command! -nargs=? -bar -bang -complete=file PlugSnapshot call s:snapshot(0, ) 287 | endfunction 288 | 289 | function! s:to_a(v) 290 | return type(a:v) == s:TYPE.list ? a:v : [a:v] 291 | endfunction 292 | 293 | function! s:to_s(v) 294 | return type(a:v) == s:TYPE.string ? a:v : join(a:v, "\n") . "\n" 295 | endfunction 296 | 297 | function! s:glob(from, pattern) 298 | return s:lines(globpath(a:from, a:pattern)) 299 | endfunction 300 | 301 | function! s:source(from, ...) 302 | let found = 0 303 | for pattern in a:000 304 | for vim in s:glob(a:from, pattern) 305 | execute 'source' s:esc(vim) 306 | let found = 1 307 | endfor 308 | endfor 309 | return found 310 | endfunction 311 | 312 | function! s:assoc(dict, key, val) 313 | let a:dict[a:key] = add(get(a:dict, a:key, []), a:val) 314 | endfunction 315 | 316 | function! s:ask(message, ...) 317 | call inputsave() 318 | echohl WarningMsg 319 | let answer = input(a:message.(a:0 ? ' (y/N/a) ' : ' (y/N) ')) 320 | echohl None 321 | call inputrestore() 322 | echo "\r" 323 | return (a:0 && answer =~? '^a') ? 2 : (answer =~? '^y') ? 1 : 0 324 | endfunction 325 | 326 | function! s:ask_no_interrupt(...) 327 | try 328 | return call('s:ask', a:000) 329 | catch 330 | return 0 331 | endtry 332 | endfunction 333 | 334 | function! s:lazy(plug, opt) 335 | return has_key(a:plug, a:opt) && 336 | \ (empty(s:to_a(a:plug[a:opt])) || 337 | \ !isdirectory(a:plug.dir) || 338 | \ len(s:glob(s:rtp(a:plug), 'plugin')) || 339 | \ len(s:glob(s:rtp(a:plug), 'after/plugin'))) 340 | endfunction 341 | 342 | function! plug#end() 343 | if !exists('g:plugs') 344 | return s:err('plug#end() called without calling plug#begin() first') 345 | endif 346 | 347 | if exists('#PlugLOD') 348 | augroup PlugLOD 349 | autocmd! 350 | augroup END 351 | augroup! PlugLOD 352 | endif 353 | let lod = { 'ft': {}, 'map': {}, 'cmd': {} } 354 | 355 | if get(g:, 'did_load_filetypes', 0) 356 | filetype off 357 | endif 358 | for name in g:plugs_order 359 | if !has_key(g:plugs, name) 360 | continue 361 | endif 362 | let plug = g:plugs[name] 363 | if get(s:loaded, name, 0) || !s:lazy(plug, 'on') && !s:lazy(plug, 'for') 364 | let s:loaded[name] = 1 365 | continue 366 | endif 367 | 368 | if has_key(plug, 'on') 369 | let s:triggers[name] = { 'map': [], 'cmd': [] } 370 | for cmd in s:to_a(plug.on) 371 | if cmd =~? '^.\+' 372 | if empty(mapcheck(cmd)) && empty(mapcheck(cmd, 'i')) 373 | call s:assoc(lod.map, cmd, name) 374 | endif 375 | call add(s:triggers[name].map, cmd) 376 | elseif cmd =~# '^[A-Z]' 377 | let cmd = substitute(cmd, '!*$', '', '') 378 | if exists(':'.cmd) != 2 379 | call s:assoc(lod.cmd, cmd, name) 380 | endif 381 | call add(s:triggers[name].cmd, cmd) 382 | else 383 | call s:err('Invalid `on` option: '.cmd. 384 | \ '. Should start with an uppercase letter or ``.') 385 | endif 386 | endfor 387 | endif 388 | 389 | if has_key(plug, 'for') 390 | let types = s:to_a(plug.for) 391 | if !empty(types) 392 | augroup filetypedetect 393 | call s:source(s:rtp(plug), 'ftdetect/**/*.vim', 'after/ftdetect/**/*.vim') 394 | augroup END 395 | endif 396 | for type in types 397 | call s:assoc(lod.ft, type, name) 398 | endfor 399 | endif 400 | endfor 401 | 402 | for [cmd, names] in items(lod.cmd) 403 | execute printf( 404 | \ 'command! -nargs=* -range -bang -complete=file %s call s:lod_cmd(%s, "", , , , %s)', 405 | \ cmd, string(cmd), string(names)) 406 | endfor 407 | 408 | for [map, names] in items(lod.map) 409 | for [mode, map_prefix, key_prefix] in 410 | \ [['i', '', ''], ['n', '', ''], ['v', '', 'gv'], ['o', '', '']] 411 | execute printf( 412 | \ '%snoremap %s %s:call lod_map(%s, %s, %s, "%s")', 413 | \ mode, map, map_prefix, string(map), string(names), mode != 'i', key_prefix) 414 | endfor 415 | endfor 416 | 417 | for [ft, names] in items(lod.ft) 418 | augroup PlugLOD 419 | execute printf('autocmd FileType %s call lod_ft(%s, %s)', 420 | \ ft, string(ft), string(names)) 421 | augroup END 422 | endfor 423 | 424 | call s:reorg_rtp() 425 | filetype plugin indent on 426 | if has('vim_starting') 427 | if has('syntax') && !exists('g:syntax_on') 428 | syntax enable 429 | end 430 | else 431 | call s:reload_plugins() 432 | endif 433 | endfunction 434 | 435 | function! s:loaded_names() 436 | return filter(copy(g:plugs_order), 'get(s:loaded, v:val, 0)') 437 | endfunction 438 | 439 | function! s:load_plugin(spec) 440 | call s:source(s:rtp(a:spec), 'plugin/**/*.vim', 'after/plugin/**/*.vim') 441 | endfunction 442 | 443 | function! s:reload_plugins() 444 | for name in s:loaded_names() 445 | call s:load_plugin(g:plugs[name]) 446 | endfor 447 | endfunction 448 | 449 | function! s:trim(str) 450 | return substitute(a:str, '[\/]\+$', '', '') 451 | endfunction 452 | 453 | function! s:version_requirement(val, min) 454 | for idx in range(0, len(a:min) - 1) 455 | let v = get(a:val, idx, 0) 456 | if v < a:min[idx] | return 0 457 | elseif v > a:min[idx] | return 1 458 | endif 459 | endfor 460 | return 1 461 | endfunction 462 | 463 | function! s:git_version_requirement(...) 464 | if !exists('s:git_version') 465 | let s:git_version = map(split(split(s:system(['git', '--version']))[2], '\.'), 'str2nr(v:val)') 466 | endif 467 | return s:version_requirement(s:git_version, a:000) 468 | endfunction 469 | 470 | function! s:progress_opt(base) 471 | return a:base && !s:is_win && 472 | \ s:git_version_requirement(1, 7, 1) ? '--progress' : '' 473 | endfunction 474 | 475 | function! s:rtp(spec) 476 | return s:path(a:spec.dir . get(a:spec, 'rtp', '')) 477 | endfunction 478 | 479 | if s:is_win 480 | function! s:path(path) 481 | return s:trim(substitute(a:path, '/', '\', 'g')) 482 | endfunction 483 | 484 | function! s:dirpath(path) 485 | return s:path(a:path) . '\' 486 | endfunction 487 | 488 | function! s:is_local_plug(repo) 489 | return a:repo =~? '^[a-z]:\|^[%~]' 490 | endfunction 491 | 492 | " Copied from fzf 493 | function! s:wrap_cmds(cmds) 494 | let cmds = [ 495 | \ '@echo off', 496 | \ 'setlocal enabledelayedexpansion'] 497 | \ + (type(a:cmds) == type([]) ? a:cmds : [a:cmds]) 498 | \ + ['endlocal'] 499 | if has('iconv') 500 | if !exists('s:codepage') 501 | let s:codepage = libcallnr('kernel32.dll', 'GetACP', 0) 502 | endif 503 | return map(cmds, printf('iconv(v:val."\r", "%s", "cp%d")', &encoding, s:codepage)) 504 | endif 505 | return map(cmds, 'v:val."\r"') 506 | endfunction 507 | 508 | function! s:batchfile(cmd) 509 | let batchfile = s:plug_tempname().'.bat' 510 | call writefile(s:wrap_cmds(a:cmd), batchfile) 511 | let cmd = plug#shellescape(batchfile, {'shell': &shell, 'script': 0}) 512 | if s:is_powershell(&shell) 513 | let cmd = '& ' . cmd 514 | endif 515 | return [batchfile, cmd] 516 | endfunction 517 | else 518 | function! s:path(path) 519 | return s:trim(a:path) 520 | endfunction 521 | 522 | function! s:dirpath(path) 523 | return substitute(a:path, '[/\\]*$', '/', '') 524 | endfunction 525 | 526 | function! s:is_local_plug(repo) 527 | return a:repo[0] =~ '[/$~]' 528 | endfunction 529 | endif 530 | 531 | function! s:err(msg) 532 | echohl ErrorMsg 533 | echom '[vim-plug] '.a:msg 534 | echohl None 535 | endfunction 536 | 537 | function! s:warn(cmd, msg) 538 | echohl WarningMsg 539 | execute a:cmd 'a:msg' 540 | echohl None 541 | endfunction 542 | 543 | function! s:esc(path) 544 | return escape(a:path, ' ') 545 | endfunction 546 | 547 | function! s:escrtp(path) 548 | return escape(a:path, ' ,') 549 | endfunction 550 | 551 | function! s:remove_rtp() 552 | for name in s:loaded_names() 553 | let rtp = s:rtp(g:plugs[name]) 554 | execute 'set rtp-='.s:escrtp(rtp) 555 | let after = globpath(rtp, 'after') 556 | if isdirectory(after) 557 | execute 'set rtp-='.s:escrtp(after) 558 | endif 559 | endfor 560 | endfunction 561 | 562 | function! s:reorg_rtp() 563 | if !empty(s:first_rtp) 564 | execute 'set rtp-='.s:first_rtp 565 | execute 'set rtp-='.s:last_rtp 566 | endif 567 | 568 | " &rtp is modified from outside 569 | if exists('s:prtp') && s:prtp !=# &rtp 570 | call s:remove_rtp() 571 | unlet! s:middle 572 | endif 573 | 574 | let s:middle = get(s:, 'middle', &rtp) 575 | let rtps = map(s:loaded_names(), 's:rtp(g:plugs[v:val])') 576 | let afters = filter(map(copy(rtps), 'globpath(v:val, "after")'), '!empty(v:val)') 577 | let rtp = join(map(rtps, 'escape(v:val, ",")'), ',') 578 | \ . ','.s:middle.',' 579 | \ . join(map(afters, 'escape(v:val, ",")'), ',') 580 | let &rtp = substitute(substitute(rtp, ',,*', ',', 'g'), '^,\|,$', '', 'g') 581 | let s:prtp = &rtp 582 | 583 | if !empty(s:first_rtp) 584 | execute 'set rtp^='.s:first_rtp 585 | execute 'set rtp+='.s:last_rtp 586 | endif 587 | endfunction 588 | 589 | function! s:doautocmd(...) 590 | if exists('#'.join(a:000, '#')) 591 | execute 'doautocmd' ((v:version > 703 || has('patch442')) ? '' : '') join(a:000) 592 | endif 593 | endfunction 594 | 595 | function! s:dobufread(names) 596 | for name in a:names 597 | let path = s:rtp(g:plugs[name]) 598 | for dir in ['ftdetect', 'ftplugin', 'after/ftdetect', 'after/ftplugin'] 599 | if len(finddir(dir, path)) 600 | if exists('#BufRead') 601 | doautocmd BufRead 602 | endif 603 | return 604 | endif 605 | endfor 606 | endfor 607 | endfunction 608 | 609 | function! plug#load(...) 610 | if a:0 == 0 611 | return s:err('Argument missing: plugin name(s) required') 612 | endif 613 | if !exists('g:plugs') 614 | return s:err('plug#begin was not called') 615 | endif 616 | let names = a:0 == 1 && type(a:1) == s:TYPE.list ? a:1 : a:000 617 | let unknowns = filter(copy(names), '!has_key(g:plugs, v:val)') 618 | if !empty(unknowns) 619 | let s = len(unknowns) > 1 ? 's' : '' 620 | return s:err(printf('Unknown plugin%s: %s', s, join(unknowns, ', '))) 621 | end 622 | let unloaded = filter(copy(names), '!get(s:loaded, v:val, 0)') 623 | if !empty(unloaded) 624 | for name in unloaded 625 | call s:lod([name], ['ftdetect', 'after/ftdetect', 'plugin', 'after/plugin']) 626 | endfor 627 | call s:dobufread(unloaded) 628 | return 1 629 | end 630 | return 0 631 | endfunction 632 | 633 | function! s:remove_triggers(name) 634 | if !has_key(s:triggers, a:name) 635 | return 636 | endif 637 | for cmd in s:triggers[a:name].cmd 638 | execute 'silent! delc' cmd 639 | endfor 640 | for map in s:triggers[a:name].map 641 | execute 'silent! unmap' map 642 | execute 'silent! iunmap' map 643 | endfor 644 | call remove(s:triggers, a:name) 645 | endfunction 646 | 647 | function! s:lod(names, types, ...) 648 | for name in a:names 649 | call s:remove_triggers(name) 650 | let s:loaded[name] = 1 651 | endfor 652 | call s:reorg_rtp() 653 | 654 | for name in a:names 655 | let rtp = s:rtp(g:plugs[name]) 656 | for dir in a:types 657 | call s:source(rtp, dir.'/**/*.vim') 658 | endfor 659 | if a:0 660 | if !s:source(rtp, a:1) && !empty(s:glob(rtp, a:2)) 661 | execute 'runtime' a:1 662 | endif 663 | call s:source(rtp, a:2) 664 | endif 665 | call s:doautocmd('User', name) 666 | endfor 667 | endfunction 668 | 669 | function! s:lod_ft(pat, names) 670 | let syn = 'syntax/'.a:pat.'.vim' 671 | call s:lod(a:names, ['plugin', 'after/plugin'], syn, 'after/'.syn) 672 | execute 'autocmd! PlugLOD FileType' a:pat 673 | call s:doautocmd('filetypeplugin', 'FileType') 674 | call s:doautocmd('filetypeindent', 'FileType') 675 | endfunction 676 | 677 | function! s:lod_cmd(cmd, bang, l1, l2, args, names) 678 | call s:lod(a:names, ['ftdetect', 'after/ftdetect', 'plugin', 'after/plugin']) 679 | call s:dobufread(a:names) 680 | execute printf('%s%s%s %s', (a:l1 == a:l2 ? '' : (a:l1.','.a:l2)), a:cmd, a:bang, a:args) 681 | endfunction 682 | 683 | function! s:lod_map(map, names, with_prefix, prefix) 684 | call s:lod(a:names, ['ftdetect', 'after/ftdetect', 'plugin', 'after/plugin']) 685 | call s:dobufread(a:names) 686 | let extra = '' 687 | while 1 688 | let c = getchar(0) 689 | if c == 0 690 | break 691 | endif 692 | let extra .= nr2char(c) 693 | endwhile 694 | 695 | if a:with_prefix 696 | let prefix = v:count ? v:count : '' 697 | let prefix .= '"'.v:register.a:prefix 698 | if mode(1) == 'no' 699 | if v:operator == 'c' 700 | let prefix = "\" . prefix 701 | endif 702 | let prefix .= v:operator 703 | endif 704 | call feedkeys(prefix, 'n') 705 | endif 706 | call feedkeys(substitute(a:map, '^', "\", '') . extra) 707 | endfunction 708 | 709 | function! plug#(repo, ...) 710 | if a:0 > 1 711 | return s:err('Invalid number of arguments (1..2)') 712 | endif 713 | 714 | try 715 | let repo = s:trim(a:repo) 716 | let opts = a:0 == 1 ? s:parse_options(a:1) : s:base_spec 717 | let name = get(opts, 'as', s:plug_fnamemodify(repo, ':t:s?\.git$??')) 718 | let spec = extend(s:infer_properties(name, repo), opts) 719 | if !has_key(g:plugs, name) 720 | call add(g:plugs_order, name) 721 | endif 722 | let g:plugs[name] = spec 723 | let s:loaded[name] = get(s:loaded, name, 0) 724 | catch 725 | return s:err(repo . ' ' . v:exception) 726 | endtry 727 | endfunction 728 | 729 | function! s:parse_options(arg) 730 | let opts = copy(s:base_spec) 731 | let type = type(a:arg) 732 | let opt_errfmt = 'Invalid argument for "%s" option of :Plug (expected: %s)' 733 | if type == s:TYPE.string 734 | if empty(a:arg) 735 | throw printf(opt_errfmt, 'tag', 'string') 736 | endif 737 | let opts.tag = a:arg 738 | elseif type == s:TYPE.dict 739 | for opt in ['branch', 'tag', 'commit', 'rtp', 'dir', 'as'] 740 | if has_key(a:arg, opt) 741 | \ && (type(a:arg[opt]) != s:TYPE.string || empty(a:arg[opt])) 742 | throw printf(opt_errfmt, opt, 'string') 743 | endif 744 | endfor 745 | for opt in ['on', 'for'] 746 | if has_key(a:arg, opt) 747 | \ && type(a:arg[opt]) != s:TYPE.list 748 | \ && (type(a:arg[opt]) != s:TYPE.string || empty(a:arg[opt])) 749 | throw printf(opt_errfmt, opt, 'string or list') 750 | endif 751 | endfor 752 | if has_key(a:arg, 'do') 753 | \ && type(a:arg.do) != s:TYPE.funcref 754 | \ && (type(a:arg.do) != s:TYPE.string || empty(a:arg.do)) 755 | throw printf(opt_errfmt, 'do', 'string or funcref') 756 | endif 757 | call extend(opts, a:arg) 758 | if has_key(opts, 'dir') 759 | let opts.dir = s:dirpath(s:plug_expand(opts.dir)) 760 | endif 761 | else 762 | throw 'Invalid argument type (expected: string or dictionary)' 763 | endif 764 | return opts 765 | endfunction 766 | 767 | function! s:infer_properties(name, repo) 768 | let repo = a:repo 769 | if s:is_local_plug(repo) 770 | return { 'dir': s:dirpath(s:plug_expand(repo)) } 771 | else 772 | if repo =~ ':' 773 | let uri = repo 774 | else 775 | if repo !~ '/' 776 | throw printf('Invalid argument: %s (implicit `vim-scripts'' expansion is deprecated)', repo) 777 | endif 778 | let fmt = get(g:, 'plug_url_format', 'https://git::@github.com/%s.git') 779 | let uri = printf(fmt, repo) 780 | endif 781 | return { 'dir': s:dirpath(g:plug_home.'/'.a:name), 'uri': uri } 782 | endif 783 | endfunction 784 | 785 | function! s:install(force, names) 786 | call s:update_impl(0, a:force, a:names) 787 | endfunction 788 | 789 | function! s:update(force, names) 790 | call s:update_impl(1, a:force, a:names) 791 | endfunction 792 | 793 | function! plug#helptags() 794 | if !exists('g:plugs') 795 | return s:err('plug#begin was not called') 796 | endif 797 | for spec in values(g:plugs) 798 | let docd = join([s:rtp(spec), 'doc'], '/') 799 | if isdirectory(docd) 800 | silent! execute 'helptags' s:esc(docd) 801 | endif 802 | endfor 803 | return 1 804 | endfunction 805 | 806 | function! s:syntax() 807 | syntax clear 808 | syntax region plug1 start=/\%1l/ end=/\%2l/ contains=plugNumber 809 | syntax region plug2 start=/\%2l/ end=/\%3l/ contains=plugBracket,plugX 810 | syn match plugNumber /[0-9]\+[0-9.]*/ contained 811 | syn match plugBracket /[[\]]/ contained 812 | syn match plugX /x/ contained 813 | syn match plugDash /^-\{1}\ / 814 | syn match plugPlus /^+/ 815 | syn match plugStar /^*/ 816 | syn match plugMessage /\(^- \)\@<=.*/ 817 | syn match plugName /\(^- \)\@<=[^ ]*:/ 818 | syn match plugSha /\%(: \)\@<=[0-9a-f]\{4,}$/ 819 | syn match plugTag /(tag: [^)]\+)/ 820 | syn match plugInstall /\(^+ \)\@<=[^:]*/ 821 | syn match plugUpdate /\(^* \)\@<=[^:]*/ 822 | syn match plugCommit /^ \X*[0-9a-f]\{7,9} .*/ contains=plugRelDate,plugEdge,plugTag 823 | syn match plugEdge /^ \X\+$/ 824 | syn match plugEdge /^ \X*/ contained nextgroup=plugSha 825 | syn match plugSha /[0-9a-f]\{7,9}/ contained 826 | syn match plugRelDate /([^)]*)$/ contained 827 | syn match plugNotLoaded /(not loaded)$/ 828 | syn match plugError /^x.*/ 829 | syn region plugDeleted start=/^\~ .*/ end=/^\ze\S/ 830 | syn match plugH2 /^.*:\n-\+$/ 831 | syn match plugH2 /^-\{2,}/ 832 | syn keyword Function PlugInstall PlugStatus PlugUpdate PlugClean 833 | hi def link plug1 Title 834 | hi def link plug2 Repeat 835 | hi def link plugH2 Type 836 | hi def link plugX Exception 837 | hi def link plugBracket Structure 838 | hi def link plugNumber Number 839 | 840 | hi def link plugDash Special 841 | hi def link plugPlus Constant 842 | hi def link plugStar Boolean 843 | 844 | hi def link plugMessage Function 845 | hi def link plugName Label 846 | hi def link plugInstall Function 847 | hi def link plugUpdate Type 848 | 849 | hi def link plugError Error 850 | hi def link plugDeleted Ignore 851 | hi def link plugRelDate Comment 852 | hi def link plugEdge PreProc 853 | hi def link plugSha Identifier 854 | hi def link plugTag Constant 855 | 856 | hi def link plugNotLoaded Comment 857 | endfunction 858 | 859 | function! s:lpad(str, len) 860 | return a:str . repeat(' ', a:len - len(a:str)) 861 | endfunction 862 | 863 | function! s:lines(msg) 864 | return split(a:msg, "[\r\n]") 865 | endfunction 866 | 867 | function! s:lastline(msg) 868 | return get(s:lines(a:msg), -1, '') 869 | endfunction 870 | 871 | function! s:new_window() 872 | execute get(g:, 'plug_window', 'vertical topleft new') 873 | endfunction 874 | 875 | function! s:plug_window_exists() 876 | let buflist = tabpagebuflist(s:plug_tab) 877 | return !empty(buflist) && index(buflist, s:plug_buf) >= 0 878 | endfunction 879 | 880 | function! s:switch_in() 881 | if !s:plug_window_exists() 882 | return 0 883 | endif 884 | 885 | if winbufnr(0) != s:plug_buf 886 | let s:pos = [tabpagenr(), winnr(), winsaveview()] 887 | execute 'normal!' s:plug_tab.'gt' 888 | let winnr = bufwinnr(s:plug_buf) 889 | execute winnr.'wincmd w' 890 | call add(s:pos, winsaveview()) 891 | else 892 | let s:pos = [winsaveview()] 893 | endif 894 | 895 | setlocal modifiable 896 | return 1 897 | endfunction 898 | 899 | function! s:switch_out(...) 900 | call winrestview(s:pos[-1]) 901 | setlocal nomodifiable 902 | if a:0 > 0 903 | execute a:1 904 | endif 905 | 906 | if len(s:pos) > 1 907 | execute 'normal!' s:pos[0].'gt' 908 | execute s:pos[1] 'wincmd w' 909 | call winrestview(s:pos[2]) 910 | endif 911 | endfunction 912 | 913 | function! s:finish_bindings() 914 | nnoremap R :call retry() 915 | nnoremap D :PlugDiff 916 | nnoremap S :PlugStatus 917 | nnoremap U :call status_update() 918 | xnoremap U :call status_update() 919 | nnoremap ]] :silent! call section('') 920 | nnoremap [[ :silent! call section('b') 921 | endfunction 922 | 923 | function! s:prepare(...) 924 | if empty(s:plug_getcwd()) 925 | throw 'Invalid current working directory. Cannot proceed.' 926 | endif 927 | 928 | for evar in ['$GIT_DIR', '$GIT_WORK_TREE'] 929 | if exists(evar) 930 | throw evar.' detected. Cannot proceed.' 931 | endif 932 | endfor 933 | 934 | call s:job_abort() 935 | if s:switch_in() 936 | if b:plug_preview == 1 937 | pc 938 | endif 939 | enew 940 | else 941 | call s:new_window() 942 | endif 943 | 944 | nnoremap q :call close_pane() 945 | if a:0 == 0 946 | call s:finish_bindings() 947 | endif 948 | let b:plug_preview = -1 949 | let s:plug_tab = tabpagenr() 950 | let s:plug_buf = winbufnr(0) 951 | call s:assign_name() 952 | 953 | for k in ['', 'L', 'o', 'X', 'd', 'dd'] 954 | execute 'silent! unmap ' k 955 | endfor 956 | setlocal buftype=nofile bufhidden=wipe nobuflisted nolist noswapfile nowrap cursorline modifiable nospell 957 | if exists('+colorcolumn') 958 | setlocal colorcolumn= 959 | endif 960 | setf vim-plug 961 | if exists('g:syntax_on') 962 | call s:syntax() 963 | endif 964 | endfunction 965 | 966 | function! s:close_pane() 967 | if b:plug_preview == 1 968 | pc 969 | let b:plug_preview = -1 970 | else 971 | bd 972 | endif 973 | endfunction 974 | 975 | function! s:assign_name() 976 | " Assign buffer name 977 | let prefix = '[Plugins]' 978 | let name = prefix 979 | let idx = 2 980 | while bufexists(name) 981 | let name = printf('%s (%s)', prefix, idx) 982 | let idx = idx + 1 983 | endwhile 984 | silent! execute 'f' fnameescape(name) 985 | endfunction 986 | 987 | function! s:chsh(swap) 988 | let prev = [&shell, &shellcmdflag, &shellredir] 989 | if !s:is_win 990 | set shell=sh 991 | endif 992 | if a:swap 993 | if s:is_powershell(&shell) 994 | let &shellredir = '2>&1 | Out-File -Encoding UTF8 %s' 995 | elseif &shell =~# 'sh' || &shell =~# 'cmd\(\.exe\)\?$' 996 | set shellredir=>%s\ 2>&1 997 | endif 998 | endif 999 | return prev 1000 | endfunction 1001 | 1002 | function! s:bang(cmd, ...) 1003 | let batchfile = '' 1004 | try 1005 | let [sh, shellcmdflag, shrd] = s:chsh(a:0) 1006 | " FIXME: Escaping is incomplete. We could use shellescape with eval, 1007 | " but it won't work on Windows. 1008 | let cmd = a:0 ? s:with_cd(a:cmd, a:1) : a:cmd 1009 | if s:is_win 1010 | let [batchfile, cmd] = s:batchfile(cmd) 1011 | endif 1012 | let g:_plug_bang = (s:is_win && has('gui_running') ? 'silent ' : '').'!'.escape(cmd, '#!%') 1013 | execute "normal! :execute g:_plug_bang\\" 1014 | finally 1015 | unlet g:_plug_bang 1016 | let [&shell, &shellcmdflag, &shellredir] = [sh, shellcmdflag, shrd] 1017 | if s:is_win && filereadable(batchfile) 1018 | call delete(batchfile) 1019 | endif 1020 | endtry 1021 | return v:shell_error ? 'Exit status: ' . v:shell_error : '' 1022 | endfunction 1023 | 1024 | function! s:regress_bar() 1025 | let bar = substitute(getline(2)[1:-2], '.*\zs=', 'x', '') 1026 | call s:progress_bar(2, bar, len(bar)) 1027 | endfunction 1028 | 1029 | function! s:is_updated(dir) 1030 | return !empty(s:system_chomp(['git', 'log', '--pretty=format:%h', 'HEAD...HEAD@{1}'], a:dir)) 1031 | endfunction 1032 | 1033 | function! s:do(pull, force, todo) 1034 | for [name, spec] in items(a:todo) 1035 | if !isdirectory(spec.dir) 1036 | continue 1037 | endif 1038 | let installed = has_key(s:update.new, name) 1039 | let updated = installed ? 0 : 1040 | \ (a:pull && index(s:update.errors, name) < 0 && s:is_updated(spec.dir)) 1041 | if a:force || installed || updated 1042 | execute 'cd' s:esc(spec.dir) 1043 | call append(3, '- Post-update hook for '. name .' ... ') 1044 | let error = '' 1045 | let type = type(spec.do) 1046 | if type == s:TYPE.string 1047 | if spec.do[0] == ':' 1048 | if !get(s:loaded, name, 0) 1049 | let s:loaded[name] = 1 1050 | call s:reorg_rtp() 1051 | endif 1052 | call s:load_plugin(spec) 1053 | try 1054 | execute spec.do[1:] 1055 | catch 1056 | let error = v:exception 1057 | endtry 1058 | if !s:plug_window_exists() 1059 | cd - 1060 | throw 'Warning: vim-plug was terminated by the post-update hook of '.name 1061 | endif 1062 | else 1063 | let error = s:bang(spec.do) 1064 | endif 1065 | elseif type == s:TYPE.funcref 1066 | try 1067 | call s:load_plugin(spec) 1068 | let status = installed ? 'installed' : (updated ? 'updated' : 'unchanged') 1069 | call spec.do({ 'name': name, 'status': status, 'force': a:force }) 1070 | catch 1071 | let error = v:exception 1072 | endtry 1073 | else 1074 | let error = 'Invalid hook type' 1075 | endif 1076 | call s:switch_in() 1077 | call setline(4, empty(error) ? (getline(4) . 'OK') 1078 | \ : ('x' . getline(4)[1:] . error)) 1079 | if !empty(error) 1080 | call add(s:update.errors, name) 1081 | call s:regress_bar() 1082 | endif 1083 | cd - 1084 | endif 1085 | endfor 1086 | endfunction 1087 | 1088 | function! s:hash_match(a, b) 1089 | return stridx(a:a, a:b) == 0 || stridx(a:b, a:a) == 0 1090 | endfunction 1091 | 1092 | function! s:checkout(spec) 1093 | let sha = a:spec.commit 1094 | let output = s:git_revision(a:spec.dir) 1095 | if !empty(output) && !s:hash_match(sha, s:lines(output)[0]) 1096 | let credential_helper = s:git_version_requirement(2) ? '-c credential.helper= ' : '' 1097 | let output = s:system( 1098 | \ 'git '.credential_helper.'fetch --depth 999999 && git checkout '.plug#shellescape(sha).' --', a:spec.dir) 1099 | endif 1100 | return output 1101 | endfunction 1102 | 1103 | function! s:finish(pull) 1104 | let new_frozen = len(filter(keys(s:update.new), 'g:plugs[v:val].frozen')) 1105 | if new_frozen 1106 | let s = new_frozen > 1 ? 's' : '' 1107 | call append(3, printf('- Installed %d frozen plugin%s', new_frozen, s)) 1108 | endif 1109 | call append(3, '- Finishing ... ') | 4 1110 | redraw 1111 | call plug#helptags() 1112 | call plug#end() 1113 | call setline(4, getline(4) . 'Done!') 1114 | redraw 1115 | let msgs = [] 1116 | if !empty(s:update.errors) 1117 | call add(msgs, "Press 'R' to retry.") 1118 | endif 1119 | if a:pull && len(s:update.new) < len(filter(getline(5, '$'), 1120 | \ "v:val =~ '^- ' && v:val !~# 'Already up.to.date'")) 1121 | call add(msgs, "Press 'D' to see the updated changes.") 1122 | endif 1123 | echo join(msgs, ' ') 1124 | call s:finish_bindings() 1125 | endfunction 1126 | 1127 | function! s:retry() 1128 | if empty(s:update.errors) 1129 | return 1130 | endif 1131 | echo 1132 | call s:update_impl(s:update.pull, s:update.force, 1133 | \ extend(copy(s:update.errors), [s:update.threads])) 1134 | endfunction 1135 | 1136 | function! s:is_managed(name) 1137 | return has_key(g:plugs[a:name], 'uri') 1138 | endfunction 1139 | 1140 | function! s:names(...) 1141 | return sort(filter(keys(g:plugs), 'stridx(v:val, a:1) == 0 && s:is_managed(v:val)')) 1142 | endfunction 1143 | 1144 | function! s:check_ruby() 1145 | silent! ruby require 'thread'; VIM::command("let g:plug_ruby = '#{RUBY_VERSION}'") 1146 | if !exists('g:plug_ruby') 1147 | redraw! 1148 | return s:warn('echom', 'Warning: Ruby interface is broken') 1149 | endif 1150 | let ruby_version = split(g:plug_ruby, '\.') 1151 | unlet g:plug_ruby 1152 | return s:version_requirement(ruby_version, [1, 8, 7]) 1153 | endfunction 1154 | 1155 | function! s:update_impl(pull, force, args) abort 1156 | let sync = index(a:args, '--sync') >= 0 || has('vim_starting') 1157 | let args = filter(copy(a:args), 'v:val != "--sync"') 1158 | let threads = (len(args) > 0 && args[-1] =~ '^[1-9][0-9]*$') ? 1159 | \ remove(args, -1) : get(g:, 'plug_threads', 16) 1160 | 1161 | let managed = filter(copy(g:plugs), 's:is_managed(v:key)') 1162 | let todo = empty(args) ? filter(managed, '!v:val.frozen || !isdirectory(v:val.dir)') : 1163 | \ filter(managed, 'index(args, v:key) >= 0') 1164 | 1165 | if empty(todo) 1166 | return s:warn('echo', 'No plugin to '. (a:pull ? 'update' : 'install')) 1167 | endif 1168 | 1169 | if !s:is_win && s:git_version_requirement(2, 3) 1170 | let s:git_terminal_prompt = exists('$GIT_TERMINAL_PROMPT') ? $GIT_TERMINAL_PROMPT : '' 1171 | let $GIT_TERMINAL_PROMPT = 0 1172 | for plug in values(todo) 1173 | let plug.uri = substitute(plug.uri, 1174 | \ '^https://git::@github\.com', 'https://github.com', '') 1175 | endfor 1176 | endif 1177 | 1178 | if !isdirectory(g:plug_home) 1179 | try 1180 | call mkdir(g:plug_home, 'p') 1181 | catch 1182 | return s:err(printf('Invalid plug directory: %s. '. 1183 | \ 'Try to call plug#begin with a valid directory', g:plug_home)) 1184 | endtry 1185 | endif 1186 | 1187 | if has('nvim') && !exists('*jobwait') && threads > 1 1188 | call s:warn('echom', '[vim-plug] Update Neovim for parallel installer') 1189 | endif 1190 | 1191 | let use_job = s:nvim || s:vim8 1192 | let python = (has('python') || has('python3')) && !use_job 1193 | let ruby = has('ruby') && !use_job && (v:version >= 703 || v:version == 702 && has('patch374')) && !(s:is_win && has('gui_running')) && threads > 1 && s:check_ruby() 1194 | 1195 | let s:update = { 1196 | \ 'start': reltime(), 1197 | \ 'all': todo, 1198 | \ 'todo': copy(todo), 1199 | \ 'errors': [], 1200 | \ 'pull': a:pull, 1201 | \ 'force': a:force, 1202 | \ 'new': {}, 1203 | \ 'threads': (python || ruby || use_job) ? min([len(todo), threads]) : 1, 1204 | \ 'bar': '', 1205 | \ 'fin': 0 1206 | \ } 1207 | 1208 | call s:prepare(1) 1209 | call append(0, ['', '']) 1210 | normal! 2G 1211 | silent! redraw 1212 | 1213 | " Set remote name, overriding a possible user git config's clone.defaultRemoteName 1214 | let s:clone_opt = ['--origin', 'origin'] 1215 | if get(g:, 'plug_shallow', 1) 1216 | call extend(s:clone_opt, ['--depth', '1']) 1217 | if s:git_version_requirement(1, 7, 10) 1218 | call add(s:clone_opt, '--no-single-branch') 1219 | endif 1220 | endif 1221 | 1222 | if has('win32unix') || has('wsl') 1223 | call extend(s:clone_opt, ['-c', 'core.eol=lf', '-c', 'core.autocrlf=input']) 1224 | endif 1225 | 1226 | let s:submodule_opt = s:git_version_requirement(2, 8) ? ' --jobs='.threads : '' 1227 | 1228 | " Python version requirement (>= 2.7) 1229 | if python && !has('python3') && !ruby && !use_job && s:update.threads > 1 1230 | redir => pyv 1231 | silent python import platform; print platform.python_version() 1232 | redir END 1233 | let python = s:version_requirement( 1234 | \ map(split(split(pyv)[0], '\.'), 'str2nr(v:val)'), [2, 6]) 1235 | endif 1236 | 1237 | if (python || ruby) && s:update.threads > 1 1238 | try 1239 | let imd = &imd 1240 | if s:mac_gui 1241 | set noimd 1242 | endif 1243 | if ruby 1244 | call s:update_ruby() 1245 | else 1246 | call s:update_python() 1247 | endif 1248 | catch 1249 | let lines = getline(4, '$') 1250 | let printed = {} 1251 | silent! 4,$d _ 1252 | for line in lines 1253 | let name = s:extract_name(line, '.', '') 1254 | if empty(name) || !has_key(printed, name) 1255 | call append('$', line) 1256 | if !empty(name) 1257 | let printed[name] = 1 1258 | if line[0] == 'x' && index(s:update.errors, name) < 0 1259 | call add(s:update.errors, name) 1260 | end 1261 | endif 1262 | endif 1263 | endfor 1264 | finally 1265 | let &imd = imd 1266 | call s:update_finish() 1267 | endtry 1268 | else 1269 | call s:update_vim() 1270 | while use_job && sync 1271 | sleep 100m 1272 | if s:update.fin 1273 | break 1274 | endif 1275 | endwhile 1276 | endif 1277 | endfunction 1278 | 1279 | function! s:log4(name, msg) 1280 | call setline(4, printf('- %s (%s)', a:msg, a:name)) 1281 | redraw 1282 | endfunction 1283 | 1284 | function! s:update_finish() 1285 | if exists('s:git_terminal_prompt') 1286 | let $GIT_TERMINAL_PROMPT = s:git_terminal_prompt 1287 | endif 1288 | if s:switch_in() 1289 | call append(3, '- Updating ...') | 4 1290 | for [name, spec] in items(filter(copy(s:update.all), 'index(s:update.errors, v:key) < 0 && (s:update.force || s:update.pull || has_key(s:update.new, v:key))')) 1291 | let [pos, _] = s:logpos(name) 1292 | if !pos 1293 | continue 1294 | endif 1295 | if has_key(spec, 'commit') 1296 | call s:log4(name, 'Checking out '.spec.commit) 1297 | let out = s:checkout(spec) 1298 | elseif has_key(spec, 'tag') 1299 | let tag = spec.tag 1300 | if tag =~ '\*' 1301 | let tags = s:lines(s:system('git tag --list '.plug#shellescape(tag).' --sort -version:refname 2>&1', spec.dir)) 1302 | if !v:shell_error && !empty(tags) 1303 | let tag = tags[0] 1304 | call s:log4(name, printf('Latest tag for %s -> %s', spec.tag, tag)) 1305 | call append(3, '') 1306 | endif 1307 | endif 1308 | call s:log4(name, 'Checking out '.tag) 1309 | let out = s:system('git checkout -q '.plug#shellescape(tag).' -- 2>&1', spec.dir) 1310 | else 1311 | let branch = s:git_origin_branch(spec) 1312 | call s:log4(name, 'Merging origin/'.s:esc(branch)) 1313 | let out = s:system('git checkout -q '.plug#shellescape(branch).' -- 2>&1' 1314 | \. (has_key(s:update.new, name) ? '' : ('&& git merge --ff-only '.plug#shellescape('origin/'.branch).' 2>&1')), spec.dir) 1315 | endif 1316 | if !v:shell_error && filereadable(spec.dir.'/.gitmodules') && 1317 | \ (s:update.force || has_key(s:update.new, name) || s:is_updated(spec.dir)) 1318 | call s:log4(name, 'Updating submodules. This may take a while.') 1319 | let out .= s:bang('git submodule update --init --recursive'.s:submodule_opt.' 2>&1', spec.dir) 1320 | endif 1321 | let msg = s:format_message(v:shell_error ? 'x': '-', name, out) 1322 | if v:shell_error 1323 | call add(s:update.errors, name) 1324 | call s:regress_bar() 1325 | silent execute pos 'd _' 1326 | call append(4, msg) | 4 1327 | elseif !empty(out) 1328 | call setline(pos, msg[0]) 1329 | endif 1330 | redraw 1331 | endfor 1332 | silent 4 d _ 1333 | try 1334 | call s:do(s:update.pull, s:update.force, filter(copy(s:update.all), 'index(s:update.errors, v:key) < 0 && has_key(v:val, "do")')) 1335 | catch 1336 | call s:warn('echom', v:exception) 1337 | call s:warn('echo', '') 1338 | return 1339 | endtry 1340 | call s:finish(s:update.pull) 1341 | call setline(1, 'Updated. Elapsed time: ' . split(reltimestr(reltime(s:update.start)))[0] . ' sec.') 1342 | call s:switch_out('normal! gg') 1343 | endif 1344 | endfunction 1345 | 1346 | function! s:job_abort() 1347 | if (!s:nvim && !s:vim8) || !exists('s:jobs') 1348 | return 1349 | endif 1350 | 1351 | for [name, j] in items(s:jobs) 1352 | if s:nvim 1353 | silent! call jobstop(j.jobid) 1354 | elseif s:vim8 1355 | silent! call job_stop(j.jobid) 1356 | endif 1357 | if j.new 1358 | call s:rm_rf(g:plugs[name].dir) 1359 | endif 1360 | endfor 1361 | let s:jobs = {} 1362 | endfunction 1363 | 1364 | function! s:last_non_empty_line(lines) 1365 | let len = len(a:lines) 1366 | for idx in range(len) 1367 | let line = a:lines[len-idx-1] 1368 | if !empty(line) 1369 | return line 1370 | endif 1371 | endfor 1372 | return '' 1373 | endfunction 1374 | 1375 | function! s:job_out_cb(self, data) abort 1376 | let self = a:self 1377 | let data = remove(self.lines, -1) . a:data 1378 | let lines = map(split(data, "\n", 1), 'split(v:val, "\r", 1)[-1]') 1379 | call extend(self.lines, lines) 1380 | " To reduce the number of buffer updates 1381 | let self.tick = get(self, 'tick', -1) + 1 1382 | if !self.running || self.tick % len(s:jobs) == 0 1383 | let bullet = self.running ? (self.new ? '+' : '*') : (self.error ? 'x' : '-') 1384 | let result = self.error ? join(self.lines, "\n") : s:last_non_empty_line(self.lines) 1385 | call s:log(bullet, self.name, result) 1386 | endif 1387 | endfunction 1388 | 1389 | function! s:job_exit_cb(self, data) abort 1390 | let a:self.running = 0 1391 | let a:self.error = a:data != 0 1392 | call s:reap(a:self.name) 1393 | call s:tick() 1394 | endfunction 1395 | 1396 | function! s:job_cb(fn, job, ch, data) 1397 | if !s:plug_window_exists() " plug window closed 1398 | return s:job_abort() 1399 | endif 1400 | call call(a:fn, [a:job, a:data]) 1401 | endfunction 1402 | 1403 | function! s:nvim_cb(job_id, data, event) dict abort 1404 | return (a:event == 'stdout' || a:event == 'stderr') ? 1405 | \ s:job_cb('s:job_out_cb', self, 0, join(a:data, "\n")) : 1406 | \ s:job_cb('s:job_exit_cb', self, 0, a:data) 1407 | endfunction 1408 | 1409 | function! s:spawn(name, cmd, opts) 1410 | let job = { 'name': a:name, 'running': 1, 'error': 0, 'lines': [''], 1411 | \ 'new': get(a:opts, 'new', 0) } 1412 | let s:jobs[a:name] = job 1413 | 1414 | if s:nvim 1415 | if has_key(a:opts, 'dir') 1416 | let job.cwd = a:opts.dir 1417 | endif 1418 | let argv = a:cmd 1419 | call extend(job, { 1420 | \ 'on_stdout': function('s:nvim_cb'), 1421 | \ 'on_stderr': function('s:nvim_cb'), 1422 | \ 'on_exit': function('s:nvim_cb'), 1423 | \ }) 1424 | let jid = s:plug_call('jobstart', argv, job) 1425 | if jid > 0 1426 | let job.jobid = jid 1427 | else 1428 | let job.running = 0 1429 | let job.error = 1 1430 | let job.lines = [jid < 0 ? argv[0].' is not executable' : 1431 | \ 'Invalid arguments (or job table is full)'] 1432 | endif 1433 | elseif s:vim8 1434 | let cmd = join(map(copy(a:cmd), 'plug#shellescape(v:val, {"script": 0})')) 1435 | if has_key(a:opts, 'dir') 1436 | let cmd = s:with_cd(cmd, a:opts.dir, 0) 1437 | endif 1438 | let argv = s:is_win ? ['cmd', '/s', '/c', '"'.cmd.'"'] : ['sh', '-c', cmd] 1439 | let jid = job_start(s:is_win ? join(argv, ' ') : argv, { 1440 | \ 'out_cb': function('s:job_cb', ['s:job_out_cb', job]), 1441 | \ 'err_cb': function('s:job_cb', ['s:job_out_cb', job]), 1442 | \ 'exit_cb': function('s:job_cb', ['s:job_exit_cb', job]), 1443 | \ 'err_mode': 'raw', 1444 | \ 'out_mode': 'raw' 1445 | \}) 1446 | if job_status(jid) == 'run' 1447 | let job.jobid = jid 1448 | else 1449 | let job.running = 0 1450 | let job.error = 1 1451 | let job.lines = ['Failed to start job'] 1452 | endif 1453 | else 1454 | let job.lines = s:lines(call('s:system', has_key(a:opts, 'dir') ? [a:cmd, a:opts.dir] : [a:cmd])) 1455 | let job.error = v:shell_error != 0 1456 | let job.running = 0 1457 | endif 1458 | endfunction 1459 | 1460 | function! s:reap(name) 1461 | let job = s:jobs[a:name] 1462 | if job.error 1463 | call add(s:update.errors, a:name) 1464 | elseif get(job, 'new', 0) 1465 | let s:update.new[a:name] = 1 1466 | endif 1467 | let s:update.bar .= job.error ? 'x' : '=' 1468 | 1469 | let bullet = job.error ? 'x' : '-' 1470 | let result = job.error ? join(job.lines, "\n") : s:last_non_empty_line(job.lines) 1471 | call s:log(bullet, a:name, empty(result) ? 'OK' : result) 1472 | call s:bar() 1473 | 1474 | call remove(s:jobs, a:name) 1475 | endfunction 1476 | 1477 | function! s:bar() 1478 | if s:switch_in() 1479 | let total = len(s:update.all) 1480 | call setline(1, (s:update.pull ? 'Updating' : 'Installing'). 1481 | \ ' plugins ('.len(s:update.bar).'/'.total.')') 1482 | call s:progress_bar(2, s:update.bar, total) 1483 | call s:switch_out() 1484 | endif 1485 | endfunction 1486 | 1487 | function! s:logpos(name) 1488 | let max = line('$') 1489 | for i in range(4, max > 4 ? max : 4) 1490 | if getline(i) =~# '^[-+x*] '.a:name.':' 1491 | for j in range(i + 1, max > 5 ? max : 5) 1492 | if getline(j) !~ '^ ' 1493 | return [i, j - 1] 1494 | endif 1495 | endfor 1496 | return [i, i] 1497 | endif 1498 | endfor 1499 | return [0, 0] 1500 | endfunction 1501 | 1502 | function! s:log(bullet, name, lines) 1503 | if s:switch_in() 1504 | let [b, e] = s:logpos(a:name) 1505 | if b > 0 1506 | silent execute printf('%d,%d d _', b, e) 1507 | if b > winheight('.') 1508 | let b = 4 1509 | endif 1510 | else 1511 | let b = 4 1512 | endif 1513 | " FIXME For some reason, nomodifiable is set after :d in vim8 1514 | setlocal modifiable 1515 | call append(b - 1, s:format_message(a:bullet, a:name, a:lines)) 1516 | call s:switch_out() 1517 | endif 1518 | endfunction 1519 | 1520 | function! s:update_vim() 1521 | let s:jobs = {} 1522 | 1523 | call s:bar() 1524 | call s:tick() 1525 | endfunction 1526 | 1527 | function! s:tick() 1528 | let pull = s:update.pull 1529 | let prog = s:progress_opt(s:nvim || s:vim8) 1530 | while 1 " Without TCO, Vim stack is bound to explode 1531 | if empty(s:update.todo) 1532 | if empty(s:jobs) && !s:update.fin 1533 | call s:update_finish() 1534 | let s:update.fin = 1 1535 | endif 1536 | return 1537 | endif 1538 | 1539 | let name = keys(s:update.todo)[0] 1540 | let spec = remove(s:update.todo, name) 1541 | let new = empty(globpath(spec.dir, '.git', 1)) 1542 | 1543 | call s:log(new ? '+' : '*', name, pull ? 'Updating ...' : 'Installing ...') 1544 | redraw 1545 | 1546 | let has_tag = has_key(spec, 'tag') 1547 | if !new 1548 | let [error, _] = s:git_validate(spec, 0) 1549 | if empty(error) 1550 | if pull 1551 | let cmd = s:git_version_requirement(2) ? ['git', '-c', 'credential.helper=', 'fetch'] : ['git', 'fetch'] 1552 | if has_tag && !empty(globpath(spec.dir, '.git/shallow')) 1553 | call extend(cmd, ['--depth', '99999999']) 1554 | endif 1555 | if !empty(prog) 1556 | call add(cmd, prog) 1557 | endif 1558 | call s:spawn(name, cmd, { 'dir': spec.dir }) 1559 | else 1560 | let s:jobs[name] = { 'running': 0, 'lines': ['Already installed'], 'error': 0 } 1561 | endif 1562 | else 1563 | let s:jobs[name] = { 'running': 0, 'lines': s:lines(error), 'error': 1 } 1564 | endif 1565 | else 1566 | let cmd = ['git', 'clone'] 1567 | if !has_tag 1568 | call extend(cmd, s:clone_opt) 1569 | endif 1570 | if !empty(prog) 1571 | call add(cmd, prog) 1572 | endif 1573 | call s:spawn(name, extend(cmd, [spec.uri, s:trim(spec.dir)]), { 'new': 1 }) 1574 | endif 1575 | 1576 | if !s:jobs[name].running 1577 | call s:reap(name) 1578 | endif 1579 | if len(s:jobs) >= s:update.threads 1580 | break 1581 | endif 1582 | endwhile 1583 | endfunction 1584 | 1585 | function! s:update_python() 1586 | let py_exe = has('python') ? 'python' : 'python3' 1587 | execute py_exe "<< EOF" 1588 | import datetime 1589 | import functools 1590 | import os 1591 | try: 1592 | import queue 1593 | except ImportError: 1594 | import Queue as queue 1595 | import random 1596 | import re 1597 | import shutil 1598 | import signal 1599 | import subprocess 1600 | import tempfile 1601 | import threading as thr 1602 | import time 1603 | import traceback 1604 | import vim 1605 | 1606 | G_NVIM = vim.eval("has('nvim')") == '1' 1607 | G_PULL = vim.eval('s:update.pull') == '1' 1608 | G_RETRIES = int(vim.eval('get(g:, "plug_retries", 2)')) + 1 1609 | G_TIMEOUT = int(vim.eval('get(g:, "plug_timeout", 60)')) 1610 | G_CLONE_OPT = ' '.join(vim.eval('s:clone_opt')) 1611 | G_PROGRESS = vim.eval('s:progress_opt(1)') 1612 | G_LOG_PROB = 1.0 / int(vim.eval('s:update.threads')) 1613 | G_STOP = thr.Event() 1614 | G_IS_WIN = vim.eval('s:is_win') == '1' 1615 | 1616 | class PlugError(Exception): 1617 | def __init__(self, msg): 1618 | self.msg = msg 1619 | class CmdTimedOut(PlugError): 1620 | pass 1621 | class CmdFailed(PlugError): 1622 | pass 1623 | class InvalidURI(PlugError): 1624 | pass 1625 | class Action(object): 1626 | INSTALL, UPDATE, ERROR, DONE = ['+', '*', 'x', '-'] 1627 | 1628 | class Buffer(object): 1629 | def __init__(self, lock, num_plugs, is_pull): 1630 | self.bar = '' 1631 | self.event = 'Updating' if is_pull else 'Installing' 1632 | self.lock = lock 1633 | self.maxy = int(vim.eval('winheight(".")')) 1634 | self.num_plugs = num_plugs 1635 | 1636 | def __where(self, name): 1637 | """ Find first line with name in current buffer. Return line num. """ 1638 | found, lnum = False, 0 1639 | matcher = re.compile('^[-+x*] {0}:'.format(name)) 1640 | for line in vim.current.buffer: 1641 | if matcher.search(line) is not None: 1642 | found = True 1643 | break 1644 | lnum += 1 1645 | 1646 | if not found: 1647 | lnum = -1 1648 | return lnum 1649 | 1650 | def header(self): 1651 | curbuf = vim.current.buffer 1652 | curbuf[0] = self.event + ' plugins ({0}/{1})'.format(len(self.bar), self.num_plugs) 1653 | 1654 | num_spaces = self.num_plugs - len(self.bar) 1655 | curbuf[1] = '[{0}{1}]'.format(self.bar, num_spaces * ' ') 1656 | 1657 | with self.lock: 1658 | vim.command('normal! 2G') 1659 | vim.command('redraw') 1660 | 1661 | def write(self, action, name, lines): 1662 | first, rest = lines[0], lines[1:] 1663 | msg = ['{0} {1}{2}{3}'.format(action, name, ': ' if first else '', first)] 1664 | msg.extend([' ' + line for line in rest]) 1665 | 1666 | try: 1667 | if action == Action.ERROR: 1668 | self.bar += 'x' 1669 | vim.command("call add(s:update.errors, '{0}')".format(name)) 1670 | elif action == Action.DONE: 1671 | self.bar += '=' 1672 | 1673 | curbuf = vim.current.buffer 1674 | lnum = self.__where(name) 1675 | if lnum != -1: # Found matching line num 1676 | del curbuf[lnum] 1677 | if lnum > self.maxy and action in set([Action.INSTALL, Action.UPDATE]): 1678 | lnum = 3 1679 | else: 1680 | lnum = 3 1681 | curbuf.append(msg, lnum) 1682 | 1683 | self.header() 1684 | except vim.error: 1685 | pass 1686 | 1687 | class Command(object): 1688 | CD = 'cd /d' if G_IS_WIN else 'cd' 1689 | 1690 | def __init__(self, cmd, cmd_dir=None, timeout=60, cb=None, clean=None): 1691 | self.cmd = cmd 1692 | if cmd_dir: 1693 | self.cmd = '{0} {1} && {2}'.format(Command.CD, cmd_dir, self.cmd) 1694 | self.timeout = timeout 1695 | self.callback = cb if cb else (lambda msg: None) 1696 | self.clean = clean if clean else (lambda: None) 1697 | self.proc = None 1698 | 1699 | @property 1700 | def alive(self): 1701 | """ Returns true only if command still running. """ 1702 | return self.proc and self.proc.poll() is None 1703 | 1704 | def execute(self, ntries=3): 1705 | """ Execute the command with ntries if CmdTimedOut. 1706 | Returns the output of the command if no Exception. 1707 | """ 1708 | attempt, finished, limit = 0, False, self.timeout 1709 | 1710 | while not finished: 1711 | try: 1712 | attempt += 1 1713 | result = self.try_command() 1714 | finished = True 1715 | return result 1716 | except CmdTimedOut: 1717 | if attempt != ntries: 1718 | self.notify_retry() 1719 | self.timeout += limit 1720 | else: 1721 | raise 1722 | 1723 | def notify_retry(self): 1724 | """ Retry required for command, notify user. """ 1725 | for count in range(3, 0, -1): 1726 | if G_STOP.is_set(): 1727 | raise KeyboardInterrupt 1728 | msg = 'Timeout. Will retry in {0} second{1} ...'.format( 1729 | count, 's' if count != 1 else '') 1730 | self.callback([msg]) 1731 | time.sleep(1) 1732 | self.callback(['Retrying ...']) 1733 | 1734 | def try_command(self): 1735 | """ Execute a cmd & poll for callback. Returns list of output. 1736 | Raises CmdFailed -> return code for Popen isn't 0 1737 | Raises CmdTimedOut -> command exceeded timeout without new output 1738 | """ 1739 | first_line = True 1740 | 1741 | try: 1742 | tfile = tempfile.NamedTemporaryFile(mode='w+b') 1743 | preexec_fn = not G_IS_WIN and os.setsid or None 1744 | self.proc = subprocess.Popen(self.cmd, stdout=tfile, 1745 | stderr=subprocess.STDOUT, 1746 | stdin=subprocess.PIPE, shell=True, 1747 | preexec_fn=preexec_fn) 1748 | thrd = thr.Thread(target=(lambda proc: proc.wait()), args=(self.proc,)) 1749 | thrd.start() 1750 | 1751 | thread_not_started = True 1752 | while thread_not_started: 1753 | try: 1754 | thrd.join(0.1) 1755 | thread_not_started = False 1756 | except RuntimeError: 1757 | pass 1758 | 1759 | while self.alive: 1760 | if G_STOP.is_set(): 1761 | raise KeyboardInterrupt 1762 | 1763 | if first_line or random.random() < G_LOG_PROB: 1764 | first_line = False 1765 | line = '' if G_IS_WIN else nonblock_read(tfile.name) 1766 | if line: 1767 | self.callback([line]) 1768 | 1769 | time_diff = time.time() - os.path.getmtime(tfile.name) 1770 | if time_diff > self.timeout: 1771 | raise CmdTimedOut(['Timeout!']) 1772 | 1773 | thrd.join(0.5) 1774 | 1775 | tfile.seek(0) 1776 | result = [line.decode('utf-8', 'replace').rstrip() for line in tfile] 1777 | 1778 | if self.proc.returncode != 0: 1779 | raise CmdFailed([''] + result) 1780 | 1781 | return result 1782 | except: 1783 | self.terminate() 1784 | raise 1785 | 1786 | def terminate(self): 1787 | """ Terminate process and cleanup. """ 1788 | if self.alive: 1789 | if G_IS_WIN: 1790 | os.kill(self.proc.pid, signal.SIGINT) 1791 | else: 1792 | os.killpg(self.proc.pid, signal.SIGTERM) 1793 | self.clean() 1794 | 1795 | class Plugin(object): 1796 | def __init__(self, name, args, buf_q, lock): 1797 | self.name = name 1798 | self.args = args 1799 | self.buf_q = buf_q 1800 | self.lock = lock 1801 | self.tag = args.get('tag', 0) 1802 | 1803 | def manage(self): 1804 | try: 1805 | if os.path.exists(self.args['dir']): 1806 | self.update() 1807 | else: 1808 | self.install() 1809 | with self.lock: 1810 | thread_vim_command("let s:update.new['{0}'] = 1".format(self.name)) 1811 | except PlugError as exc: 1812 | self.write(Action.ERROR, self.name, exc.msg) 1813 | except KeyboardInterrupt: 1814 | G_STOP.set() 1815 | self.write(Action.ERROR, self.name, ['Interrupted!']) 1816 | except: 1817 | # Any exception except those above print stack trace 1818 | msg = 'Trace:\n{0}'.format(traceback.format_exc().rstrip()) 1819 | self.write(Action.ERROR, self.name, msg.split('\n')) 1820 | raise 1821 | 1822 | def install(self): 1823 | target = self.args['dir'] 1824 | if target[-1] == '\\': 1825 | target = target[0:-1] 1826 | 1827 | def clean(target): 1828 | def _clean(): 1829 | try: 1830 | shutil.rmtree(target) 1831 | except OSError: 1832 | pass 1833 | return _clean 1834 | 1835 | self.write(Action.INSTALL, self.name, ['Installing ...']) 1836 | callback = functools.partial(self.write, Action.INSTALL, self.name) 1837 | cmd = 'git clone {0} {1} {2} {3} 2>&1'.format( 1838 | '' if self.tag else G_CLONE_OPT, G_PROGRESS, self.args['uri'], 1839 | esc(target)) 1840 | com = Command(cmd, None, G_TIMEOUT, callback, clean(target)) 1841 | result = com.execute(G_RETRIES) 1842 | self.write(Action.DONE, self.name, result[-1:]) 1843 | 1844 | def repo_uri(self): 1845 | cmd = 'git rev-parse --abbrev-ref HEAD 2>&1 && git config -f .git/config remote.origin.url' 1846 | command = Command(cmd, self.args['dir'], G_TIMEOUT,) 1847 | result = command.execute(G_RETRIES) 1848 | return result[-1] 1849 | 1850 | def update(self): 1851 | actual_uri = self.repo_uri() 1852 | expect_uri = self.args['uri'] 1853 | regex = re.compile(r'^(?:\w+://)?(?:[^@/]*@)?([^:/]*(?::[0-9]*)?)[:/](.*?)(?:\.git)?/?$') 1854 | ma = regex.match(actual_uri) 1855 | mb = regex.match(expect_uri) 1856 | if ma is None or mb is None or ma.groups() != mb.groups(): 1857 | msg = ['', 1858 | 'Invalid URI: {0}'.format(actual_uri), 1859 | 'Expected {0}'.format(expect_uri), 1860 | 'PlugClean required.'] 1861 | raise InvalidURI(msg) 1862 | 1863 | if G_PULL: 1864 | self.write(Action.UPDATE, self.name, ['Updating ...']) 1865 | callback = functools.partial(self.write, Action.UPDATE, self.name) 1866 | fetch_opt = '--depth 99999999' if self.tag and os.path.isfile(os.path.join(self.args['dir'], '.git/shallow')) else '' 1867 | cmd = 'git fetch {0} {1} 2>&1'.format(fetch_opt, G_PROGRESS) 1868 | com = Command(cmd, self.args['dir'], G_TIMEOUT, callback) 1869 | result = com.execute(G_RETRIES) 1870 | self.write(Action.DONE, self.name, result[-1:]) 1871 | else: 1872 | self.write(Action.DONE, self.name, ['Already installed']) 1873 | 1874 | def write(self, action, name, msg): 1875 | self.buf_q.put((action, name, msg)) 1876 | 1877 | class PlugThread(thr.Thread): 1878 | def __init__(self, tname, args): 1879 | super(PlugThread, self).__init__() 1880 | self.tname = tname 1881 | self.args = args 1882 | 1883 | def run(self): 1884 | thr.current_thread().name = self.tname 1885 | buf_q, work_q, lock = self.args 1886 | 1887 | try: 1888 | while not G_STOP.is_set(): 1889 | name, args = work_q.get_nowait() 1890 | plug = Plugin(name, args, buf_q, lock) 1891 | plug.manage() 1892 | work_q.task_done() 1893 | except queue.Empty: 1894 | pass 1895 | 1896 | class RefreshThread(thr.Thread): 1897 | def __init__(self, lock): 1898 | super(RefreshThread, self).__init__() 1899 | self.lock = lock 1900 | self.running = True 1901 | 1902 | def run(self): 1903 | while self.running: 1904 | with self.lock: 1905 | thread_vim_command('noautocmd normal! a') 1906 | time.sleep(0.33) 1907 | 1908 | def stop(self): 1909 | self.running = False 1910 | 1911 | if G_NVIM: 1912 | def thread_vim_command(cmd): 1913 | vim.session.threadsafe_call(lambda: vim.command(cmd)) 1914 | else: 1915 | def thread_vim_command(cmd): 1916 | vim.command(cmd) 1917 | 1918 | def esc(name): 1919 | return '"' + name.replace('"', '\"') + '"' 1920 | 1921 | def nonblock_read(fname): 1922 | """ Read a file with nonblock flag. Return the last line. """ 1923 | fread = os.open(fname, os.O_RDONLY | os.O_NONBLOCK) 1924 | buf = os.read(fread, 100000).decode('utf-8', 'replace') 1925 | os.close(fread) 1926 | 1927 | line = buf.rstrip('\r\n') 1928 | left = max(line.rfind('\r'), line.rfind('\n')) 1929 | if left != -1: 1930 | left += 1 1931 | line = line[left:] 1932 | 1933 | return line 1934 | 1935 | def main(): 1936 | thr.current_thread().name = 'main' 1937 | nthreads = int(vim.eval('s:update.threads')) 1938 | plugs = vim.eval('s:update.todo') 1939 | mac_gui = vim.eval('s:mac_gui') == '1' 1940 | 1941 | lock = thr.Lock() 1942 | buf = Buffer(lock, len(plugs), G_PULL) 1943 | buf_q, work_q = queue.Queue(), queue.Queue() 1944 | for work in plugs.items(): 1945 | work_q.put(work) 1946 | 1947 | start_cnt = thr.active_count() 1948 | for num in range(nthreads): 1949 | tname = 'PlugT-{0:02}'.format(num) 1950 | thread = PlugThread(tname, (buf_q, work_q, lock)) 1951 | thread.start() 1952 | if mac_gui: 1953 | rthread = RefreshThread(lock) 1954 | rthread.start() 1955 | 1956 | while not buf_q.empty() or thr.active_count() != start_cnt: 1957 | try: 1958 | action, name, msg = buf_q.get(True, 0.25) 1959 | buf.write(action, name, ['OK'] if not msg else msg) 1960 | buf_q.task_done() 1961 | except queue.Empty: 1962 | pass 1963 | except KeyboardInterrupt: 1964 | G_STOP.set() 1965 | 1966 | if mac_gui: 1967 | rthread.stop() 1968 | rthread.join() 1969 | 1970 | main() 1971 | EOF 1972 | endfunction 1973 | 1974 | function! s:update_ruby() 1975 | ruby << EOF 1976 | module PlugStream 1977 | SEP = ["\r", "\n", nil] 1978 | def get_line 1979 | buffer = '' 1980 | loop do 1981 | char = readchar rescue return 1982 | if SEP.include? char.chr 1983 | buffer << $/ 1984 | break 1985 | else 1986 | buffer << char 1987 | end 1988 | end 1989 | buffer 1990 | end 1991 | end unless defined?(PlugStream) 1992 | 1993 | def esc arg 1994 | %["#{arg.gsub('"', '\"')}"] 1995 | end 1996 | 1997 | def killall pid 1998 | pids = [pid] 1999 | if /mswin|mingw|bccwin/ =~ RUBY_PLATFORM 2000 | pids.each { |pid| Process.kill 'INT', pid.to_i rescue nil } 2001 | else 2002 | unless `which pgrep 2> /dev/null`.empty? 2003 | children = pids 2004 | until children.empty? 2005 | children = children.map { |pid| 2006 | `pgrep -P #{pid}`.lines.map { |l| l.chomp } 2007 | }.flatten 2008 | pids += children 2009 | end 2010 | end 2011 | pids.each { |pid| Process.kill 'TERM', pid.to_i rescue nil } 2012 | end 2013 | end 2014 | 2015 | def compare_git_uri a, b 2016 | regex = %r{^(?:\w+://)?(?:[^@/]*@)?([^:/]*(?::[0-9]*)?)[:/](.*?)(?:\.git)?/?$} 2017 | regex.match(a).to_a.drop(1) == regex.match(b).to_a.drop(1) 2018 | end 2019 | 2020 | require 'thread' 2021 | require 'fileutils' 2022 | require 'timeout' 2023 | running = true 2024 | iswin = VIM::evaluate('s:is_win').to_i == 1 2025 | pull = VIM::evaluate('s:update.pull').to_i == 1 2026 | base = VIM::evaluate('g:plug_home') 2027 | all = VIM::evaluate('s:update.todo') 2028 | limit = VIM::evaluate('get(g:, "plug_timeout", 60)') 2029 | tries = VIM::evaluate('get(g:, "plug_retries", 2)') + 1 2030 | nthr = VIM::evaluate('s:update.threads').to_i 2031 | maxy = VIM::evaluate('winheight(".")').to_i 2032 | vim7 = VIM::evaluate('v:version').to_i <= 703 && RUBY_PLATFORM =~ /darwin/ 2033 | cd = iswin ? 'cd /d' : 'cd' 2034 | tot = VIM::evaluate('len(s:update.todo)') || 0 2035 | bar = '' 2036 | skip = 'Already installed' 2037 | mtx = Mutex.new 2038 | take1 = proc { mtx.synchronize { running && all.shift } } 2039 | logh = proc { 2040 | cnt = bar.length 2041 | $curbuf[1] = "#{pull ? 'Updating' : 'Installing'} plugins (#{cnt}/#{tot})" 2042 | $curbuf[2] = '[' + bar.ljust(tot) + ']' 2043 | VIM::command('normal! 2G') 2044 | VIM::command('redraw') 2045 | } 2046 | where = proc { |name| (1..($curbuf.length)).find { |l| $curbuf[l] =~ /^[-+x*] #{name}:/ } } 2047 | log = proc { |name, result, type| 2048 | mtx.synchronize do 2049 | ing = ![true, false].include?(type) 2050 | bar += type ? '=' : 'x' unless ing 2051 | b = case type 2052 | when :install then '+' when :update then '*' 2053 | when true, nil then '-' else 2054 | VIM::command("call add(s:update.errors, '#{name}')") 2055 | 'x' 2056 | end 2057 | result = 2058 | if type || type.nil? 2059 | ["#{b} #{name}: #{result.lines.to_a.last || 'OK'}"] 2060 | elsif result =~ /^Interrupted|^Timeout/ 2061 | ["#{b} #{name}: #{result}"] 2062 | else 2063 | ["#{b} #{name}"] + result.lines.map { |l| " " << l } 2064 | end 2065 | if lnum = where.call(name) 2066 | $curbuf.delete lnum 2067 | lnum = 4 if ing && lnum > maxy 2068 | end 2069 | result.each_with_index do |line, offset| 2070 | $curbuf.append((lnum || 4) - 1 + offset, line.gsub(/\e\[./, '').chomp) 2071 | end 2072 | logh.call 2073 | end 2074 | } 2075 | bt = proc { |cmd, name, type, cleanup| 2076 | tried = timeout = 0 2077 | begin 2078 | tried += 1 2079 | timeout += limit 2080 | fd = nil 2081 | data = '' 2082 | if iswin 2083 | Timeout::timeout(timeout) do 2084 | tmp = VIM::evaluate('tempname()') 2085 | system("(#{cmd}) > #{tmp}") 2086 | data = File.read(tmp).chomp 2087 | File.unlink tmp rescue nil 2088 | end 2089 | else 2090 | fd = IO.popen(cmd).extend(PlugStream) 2091 | first_line = true 2092 | log_prob = 1.0 / nthr 2093 | while line = Timeout::timeout(timeout) { fd.get_line } 2094 | data << line 2095 | log.call name, line.chomp, type if name && (first_line || rand < log_prob) 2096 | first_line = false 2097 | end 2098 | fd.close 2099 | end 2100 | [$? == 0, data.chomp] 2101 | rescue Timeout::Error, Interrupt => e 2102 | if fd && !fd.closed? 2103 | killall fd.pid 2104 | fd.close 2105 | end 2106 | cleanup.call if cleanup 2107 | if e.is_a?(Timeout::Error) && tried < tries 2108 | 3.downto(1) do |countdown| 2109 | s = countdown > 1 ? 's' : '' 2110 | log.call name, "Timeout. Will retry in #{countdown} second#{s} ...", type 2111 | sleep 1 2112 | end 2113 | log.call name, 'Retrying ...', type 2114 | retry 2115 | end 2116 | [false, e.is_a?(Interrupt) ? "Interrupted!" : "Timeout!"] 2117 | end 2118 | } 2119 | main = Thread.current 2120 | threads = [] 2121 | watcher = Thread.new { 2122 | if vim7 2123 | while VIM::evaluate('getchar(1)') 2124 | sleep 0.1 2125 | end 2126 | else 2127 | require 'io/console' # >= Ruby 1.9 2128 | nil until IO.console.getch == 3.chr 2129 | end 2130 | mtx.synchronize do 2131 | running = false 2132 | threads.each { |t| t.raise Interrupt } unless vim7 2133 | end 2134 | threads.each { |t| t.join rescue nil } 2135 | main.kill 2136 | } 2137 | refresh = Thread.new { 2138 | while true 2139 | mtx.synchronize do 2140 | break unless running 2141 | VIM::command('noautocmd normal! a') 2142 | end 2143 | sleep 0.2 2144 | end 2145 | } if VIM::evaluate('s:mac_gui') == 1 2146 | 2147 | clone_opt = VIM::evaluate('s:clone_opt').join(' ') 2148 | progress = VIM::evaluate('s:progress_opt(1)') 2149 | nthr.times do 2150 | mtx.synchronize do 2151 | threads << Thread.new { 2152 | while pair = take1.call 2153 | name = pair.first 2154 | dir, uri, tag = pair.last.values_at *%w[dir uri tag] 2155 | exists = File.directory? dir 2156 | ok, result = 2157 | if exists 2158 | chdir = "#{cd} #{iswin ? dir : esc(dir)}" 2159 | ret, data = bt.call "#{chdir} && git rev-parse --abbrev-ref HEAD 2>&1 && git config -f .git/config remote.origin.url", nil, nil, nil 2160 | current_uri = data.lines.to_a.last 2161 | if !ret 2162 | if data =~ /^Interrupted|^Timeout/ 2163 | [false, data] 2164 | else 2165 | [false, [data.chomp, "PlugClean required."].join($/)] 2166 | end 2167 | elsif !compare_git_uri(current_uri, uri) 2168 | [false, ["Invalid URI: #{current_uri}", 2169 | "Expected: #{uri}", 2170 | "PlugClean required."].join($/)] 2171 | else 2172 | if pull 2173 | log.call name, 'Updating ...', :update 2174 | fetch_opt = (tag && File.exist?(File.join(dir, '.git/shallow'))) ? '--depth 99999999' : '' 2175 | bt.call "#{chdir} && git fetch #{fetch_opt} #{progress} 2>&1", name, :update, nil 2176 | else 2177 | [true, skip] 2178 | end 2179 | end 2180 | else 2181 | d = esc dir.sub(%r{[\\/]+$}, '') 2182 | log.call name, 'Installing ...', :install 2183 | bt.call "git clone #{clone_opt unless tag} #{progress} #{uri} #{d} 2>&1", name, :install, proc { 2184 | FileUtils.rm_rf dir 2185 | } 2186 | end 2187 | mtx.synchronize { VIM::command("let s:update.new['#{name}'] = 1") } if !exists && ok 2188 | log.call name, result, ok 2189 | end 2190 | } if running 2191 | end 2192 | end 2193 | threads.each { |t| t.join rescue nil } 2194 | logh.call 2195 | refresh.kill if refresh 2196 | watcher.kill 2197 | EOF 2198 | endfunction 2199 | 2200 | function! s:shellesc_cmd(arg, script) 2201 | let escaped = substitute('"'.a:arg.'"', '[&|<>()@^!"]', '^&', 'g') 2202 | return substitute(escaped, '%', (a:script ? '%' : '^') . '&', 'g') 2203 | endfunction 2204 | 2205 | function! s:shellesc_ps1(arg) 2206 | return "'".substitute(escape(a:arg, '\"'), "'", "''", 'g')."'" 2207 | endfunction 2208 | 2209 | function! s:shellesc_sh(arg) 2210 | return "'".substitute(a:arg, "'", "'\\\\''", 'g')."'" 2211 | endfunction 2212 | 2213 | " Escape the shell argument based on the shell. 2214 | " Vim and Neovim's shellescape() are insufficient. 2215 | " 1. shellslash determines whether to use single/double quotes. 2216 | " Double-quote escaping is fragile for cmd.exe. 2217 | " 2. It does not work for powershell. 2218 | " 3. It does not work for *sh shells if the command is executed 2219 | " via cmd.exe (ie. cmd.exe /c sh -c command command_args) 2220 | " 4. It does not support batchfile syntax. 2221 | " 2222 | " Accepts an optional dictionary with the following keys: 2223 | " - shell: same as Vim/Neovim 'shell' option. 2224 | " If unset, fallback to 'cmd.exe' on Windows or 'sh'. 2225 | " - script: If truthy and shell is cmd.exe, escape for batchfile syntax. 2226 | function! plug#shellescape(arg, ...) 2227 | if a:arg =~# '^[A-Za-z0-9_/:.-]\+$' 2228 | return a:arg 2229 | endif 2230 | let opts = a:0 > 0 && type(a:1) == s:TYPE.dict ? a:1 : {} 2231 | let shell = get(opts, 'shell', s:is_win ? 'cmd.exe' : 'sh') 2232 | let script = get(opts, 'script', 1) 2233 | if shell =~# 'cmd\(\.exe\)\?$' 2234 | return s:shellesc_cmd(a:arg, script) 2235 | elseif s:is_powershell(shell) 2236 | return s:shellesc_ps1(a:arg) 2237 | endif 2238 | return s:shellesc_sh(a:arg) 2239 | endfunction 2240 | 2241 | function! s:glob_dir(path) 2242 | return map(filter(s:glob(a:path, '**'), 'isdirectory(v:val)'), 's:dirpath(v:val)') 2243 | endfunction 2244 | 2245 | function! s:progress_bar(line, bar, total) 2246 | call setline(a:line, '[' . s:lpad(a:bar, a:total) . ']') 2247 | endfunction 2248 | 2249 | function! s:compare_git_uri(a, b) 2250 | " See `git help clone' 2251 | " https:// [user@] github.com[:port] / junegunn/vim-plug [.git] 2252 | " [git@] github.com[:port] : junegunn/vim-plug [.git] 2253 | " file:// / junegunn/vim-plug [/] 2254 | " / junegunn/vim-plug [/] 2255 | let pat = '^\%(\w\+://\)\='.'\%([^@/]*@\)\='.'\([^:/]*\%(:[0-9]*\)\=\)'.'[:/]'.'\(.\{-}\)'.'\%(\.git\)\=/\?$' 2256 | let ma = matchlist(a:a, pat) 2257 | let mb = matchlist(a:b, pat) 2258 | return ma[1:2] ==# mb[1:2] 2259 | endfunction 2260 | 2261 | function! s:format_message(bullet, name, message) 2262 | if a:bullet != 'x' 2263 | return [printf('%s %s: %s', a:bullet, a:name, s:lastline(a:message))] 2264 | else 2265 | let lines = map(s:lines(a:message), '" ".v:val') 2266 | return extend([printf('x %s:', a:name)], lines) 2267 | endif 2268 | endfunction 2269 | 2270 | function! s:with_cd(cmd, dir, ...) 2271 | let script = a:0 > 0 ? a:1 : 1 2272 | return printf('cd%s %s && %s', s:is_win ? ' /d' : '', plug#shellescape(a:dir, {'script': script}), a:cmd) 2273 | endfunction 2274 | 2275 | function! s:system(cmd, ...) 2276 | let batchfile = '' 2277 | try 2278 | let [sh, shellcmdflag, shrd] = s:chsh(1) 2279 | if type(a:cmd) == s:TYPE.list 2280 | " Neovim's system() supports list argument to bypass the shell 2281 | " but it cannot set the working directory for the command. 2282 | " Assume that the command does not rely on the shell. 2283 | if has('nvim') && a:0 == 0 2284 | return system(a:cmd) 2285 | endif 2286 | let cmd = join(map(copy(a:cmd), 'plug#shellescape(v:val, {"shell": &shell, "script": 0})')) 2287 | if s:is_powershell(&shell) 2288 | let cmd = '& ' . cmd 2289 | endif 2290 | else 2291 | let cmd = a:cmd 2292 | endif 2293 | if a:0 > 0 2294 | let cmd = s:with_cd(cmd, a:1, type(a:cmd) != s:TYPE.list) 2295 | endif 2296 | if s:is_win && type(a:cmd) != s:TYPE.list 2297 | let [batchfile, cmd] = s:batchfile(cmd) 2298 | endif 2299 | return system(cmd) 2300 | finally 2301 | let [&shell, &shellcmdflag, &shellredir] = [sh, shellcmdflag, shrd] 2302 | if s:is_win && filereadable(batchfile) 2303 | call delete(batchfile) 2304 | endif 2305 | endtry 2306 | endfunction 2307 | 2308 | function! s:system_chomp(...) 2309 | let ret = call('s:system', a:000) 2310 | return v:shell_error ? '' : substitute(ret, '\n$', '', '') 2311 | endfunction 2312 | 2313 | function! s:git_validate(spec, check_branch) 2314 | let err = '' 2315 | if isdirectory(a:spec.dir) 2316 | let result = [s:git_local_branch(a:spec.dir), s:git_origin_url(a:spec.dir)] 2317 | let remote = result[-1] 2318 | if empty(remote) 2319 | let err = join([remote, 'PlugClean required.'], "\n") 2320 | elseif !s:compare_git_uri(remote, a:spec.uri) 2321 | let err = join(['Invalid URI: '.remote, 2322 | \ 'Expected: '.a:spec.uri, 2323 | \ 'PlugClean required.'], "\n") 2324 | elseif a:check_branch && has_key(a:spec, 'commit') 2325 | let sha = s:git_revision(a:spec.dir) 2326 | if empty(sha) 2327 | let err = join(add(result, 'PlugClean required.'), "\n") 2328 | elseif !s:hash_match(sha, a:spec.commit) 2329 | let err = join([printf('Invalid HEAD (expected: %s, actual: %s)', 2330 | \ a:spec.commit[:6], sha[:6]), 2331 | \ 'PlugUpdate required.'], "\n") 2332 | endif 2333 | elseif a:check_branch 2334 | let current_branch = result[0] 2335 | " Check tag 2336 | let origin_branch = s:git_origin_branch(a:spec) 2337 | if has_key(a:spec, 'tag') 2338 | let tag = s:system_chomp('git describe --exact-match --tags HEAD 2>&1', a:spec.dir) 2339 | if a:spec.tag !=# tag && a:spec.tag !~ '\*' 2340 | let err = printf('Invalid tag: %s (expected: %s). Try PlugUpdate.', 2341 | \ (empty(tag) ? 'N/A' : tag), a:spec.tag) 2342 | endif 2343 | " Check branch 2344 | elseif origin_branch !=# current_branch 2345 | let err = printf('Invalid branch: %s (expected: %s). Try PlugUpdate.', 2346 | \ current_branch, origin_branch) 2347 | endif 2348 | if empty(err) 2349 | let [ahead, behind] = split(s:lastline(s:system([ 2350 | \ 'git', 'rev-list', '--count', '--left-right', 2351 | \ printf('HEAD...origin/%s', origin_branch) 2352 | \ ], a:spec.dir)), '\t') 2353 | if !v:shell_error && ahead 2354 | if behind 2355 | " Only mention PlugClean if diverged, otherwise it's likely to be 2356 | " pushable (and probably not that messed up). 2357 | let err = printf( 2358 | \ "Diverged from origin/%s (%d commit(s) ahead and %d commit(s) behind!\n" 2359 | \ .'Backup local changes and run PlugClean and PlugUpdate to reinstall it.', origin_branch, ahead, behind) 2360 | else 2361 | let err = printf("Ahead of origin/%s by %d commit(s).\n" 2362 | \ .'Cannot update until local changes are pushed.', 2363 | \ origin_branch, ahead) 2364 | endif 2365 | endif 2366 | endif 2367 | endif 2368 | else 2369 | let err = 'Not found' 2370 | endif 2371 | return [err, err =~# 'PlugClean'] 2372 | endfunction 2373 | 2374 | function! s:rm_rf(dir) 2375 | if isdirectory(a:dir) 2376 | return s:system(s:is_win 2377 | \ ? 'rmdir /S /Q '.plug#shellescape(a:dir) 2378 | \ : ['rm', '-rf', a:dir]) 2379 | endif 2380 | endfunction 2381 | 2382 | function! s:clean(force) 2383 | call s:prepare() 2384 | call append(0, 'Searching for invalid plugins in '.g:plug_home) 2385 | call append(1, '') 2386 | 2387 | " List of valid directories 2388 | let dirs = [] 2389 | let errs = {} 2390 | let [cnt, total] = [0, len(g:plugs)] 2391 | for [name, spec] in items(g:plugs) 2392 | if !s:is_managed(name) 2393 | call add(dirs, spec.dir) 2394 | else 2395 | let [err, clean] = s:git_validate(spec, 1) 2396 | if clean 2397 | let errs[spec.dir] = s:lines(err)[0] 2398 | else 2399 | call add(dirs, spec.dir) 2400 | endif 2401 | endif 2402 | let cnt += 1 2403 | call s:progress_bar(2, repeat('=', cnt), total) 2404 | normal! 2G 2405 | redraw 2406 | endfor 2407 | 2408 | let allowed = {} 2409 | for dir in dirs 2410 | let allowed[s:dirpath(s:plug_fnamemodify(dir, ':h:h'))] = 1 2411 | let allowed[dir] = 1 2412 | for child in s:glob_dir(dir) 2413 | let allowed[child] = 1 2414 | endfor 2415 | endfor 2416 | 2417 | let todo = [] 2418 | let found = sort(s:glob_dir(g:plug_home)) 2419 | while !empty(found) 2420 | let f = remove(found, 0) 2421 | if !has_key(allowed, f) && isdirectory(f) 2422 | call add(todo, f) 2423 | call append(line('$'), '- ' . f) 2424 | if has_key(errs, f) 2425 | call append(line('$'), ' ' . errs[f]) 2426 | endif 2427 | let found = filter(found, 'stridx(v:val, f) != 0') 2428 | end 2429 | endwhile 2430 | 2431 | 4 2432 | redraw 2433 | if empty(todo) 2434 | call append(line('$'), 'Already clean.') 2435 | else 2436 | let s:clean_count = 0 2437 | call append(3, ['Directories to delete:', '']) 2438 | redraw! 2439 | if a:force || s:ask_no_interrupt('Delete all directories?') 2440 | call s:delete([6, line('$')], 1) 2441 | else 2442 | call setline(4, 'Cancelled.') 2443 | nnoremap d :set opfunc=delete_opg@ 2444 | nmap dd d_ 2445 | xnoremap d :call delete_op(visualmode(), 1) 2446 | echo 'Delete the lines (d{motion}) to delete the corresponding directories' 2447 | endif 2448 | endif 2449 | 4 2450 | setlocal nomodifiable 2451 | endfunction 2452 | 2453 | function! s:delete_op(type, ...) 2454 | call s:delete(a:0 ? [line("'<"), line("'>")] : [line("'["), line("']")], 0) 2455 | endfunction 2456 | 2457 | function! s:delete(range, force) 2458 | let [l1, l2] = a:range 2459 | let force = a:force 2460 | let err_count = 0 2461 | while l1 <= l2 2462 | let line = getline(l1) 2463 | if line =~ '^- ' && isdirectory(line[2:]) 2464 | execute l1 2465 | redraw! 2466 | let answer = force ? 1 : s:ask('Delete '.line[2:].'?', 1) 2467 | let force = force || answer > 1 2468 | if answer 2469 | let err = s:rm_rf(line[2:]) 2470 | setlocal modifiable 2471 | if empty(err) 2472 | call setline(l1, '~'.line[1:]) 2473 | let s:clean_count += 1 2474 | else 2475 | delete _ 2476 | call append(l1 - 1, s:format_message('x', line[1:], err)) 2477 | let l2 += len(s:lines(err)) 2478 | let err_count += 1 2479 | endif 2480 | let msg = printf('Removed %d directories.', s:clean_count) 2481 | if err_count > 0 2482 | let msg .= printf(' Failed to remove %d directories.', err_count) 2483 | endif 2484 | call setline(4, msg) 2485 | setlocal nomodifiable 2486 | endif 2487 | endif 2488 | let l1 += 1 2489 | endwhile 2490 | endfunction 2491 | 2492 | function! s:upgrade() 2493 | echo 'Downloading the latest version of vim-plug' 2494 | redraw 2495 | let tmp = s:plug_tempname() 2496 | let new = tmp . '/plug.vim' 2497 | 2498 | try 2499 | let out = s:system(['git', 'clone', '--depth', '1', s:plug_src, tmp]) 2500 | if v:shell_error 2501 | return s:err('Error upgrading vim-plug: '. out) 2502 | endif 2503 | 2504 | if readfile(s:me) ==# readfile(new) 2505 | echo 'vim-plug is already up-to-date' 2506 | return 0 2507 | else 2508 | call rename(s:me, s:me . '.old') 2509 | call rename(new, s:me) 2510 | unlet g:loaded_plug 2511 | echo 'vim-plug has been upgraded' 2512 | return 1 2513 | endif 2514 | finally 2515 | silent! call s:rm_rf(tmp) 2516 | endtry 2517 | endfunction 2518 | 2519 | function! s:upgrade_specs() 2520 | for spec in values(g:plugs) 2521 | let spec.frozen = get(spec, 'frozen', 0) 2522 | endfor 2523 | endfunction 2524 | 2525 | function! s:status() 2526 | call s:prepare() 2527 | call append(0, 'Checking plugins') 2528 | call append(1, '') 2529 | 2530 | let ecnt = 0 2531 | let unloaded = 0 2532 | let [cnt, total] = [0, len(g:plugs)] 2533 | for [name, spec] in items(g:plugs) 2534 | let is_dir = isdirectory(spec.dir) 2535 | if has_key(spec, 'uri') 2536 | if is_dir 2537 | let [err, _] = s:git_validate(spec, 1) 2538 | let [valid, msg] = [empty(err), empty(err) ? 'OK' : err] 2539 | else 2540 | let [valid, msg] = [0, 'Not found. Try PlugInstall.'] 2541 | endif 2542 | else 2543 | if is_dir 2544 | let [valid, msg] = [1, 'OK'] 2545 | else 2546 | let [valid, msg] = [0, 'Not found.'] 2547 | endif 2548 | endif 2549 | let cnt += 1 2550 | let ecnt += !valid 2551 | " `s:loaded` entry can be missing if PlugUpgraded 2552 | if is_dir && get(s:loaded, name, -1) == 0 2553 | let unloaded = 1 2554 | let msg .= ' (not loaded)' 2555 | endif 2556 | call s:progress_bar(2, repeat('=', cnt), total) 2557 | call append(3, s:format_message(valid ? '-' : 'x', name, msg)) 2558 | normal! 2G 2559 | redraw 2560 | endfor 2561 | call setline(1, 'Finished. '.ecnt.' error(s).') 2562 | normal! gg 2563 | setlocal nomodifiable 2564 | if unloaded 2565 | echo "Press 'L' on each line to load plugin, or 'U' to update" 2566 | nnoremap L :call status_load(line('.')) 2567 | xnoremap L :call status_load(line('.')) 2568 | end 2569 | endfunction 2570 | 2571 | function! s:extract_name(str, prefix, suffix) 2572 | return matchstr(a:str, '^'.a:prefix.' \zs[^:]\+\ze:.*'.a:suffix.'$') 2573 | endfunction 2574 | 2575 | function! s:status_load(lnum) 2576 | let line = getline(a:lnum) 2577 | let name = s:extract_name(line, '-', '(not loaded)') 2578 | if !empty(name) 2579 | call plug#load(name) 2580 | setlocal modifiable 2581 | call setline(a:lnum, substitute(line, ' (not loaded)$', '', '')) 2582 | setlocal nomodifiable 2583 | endif 2584 | endfunction 2585 | 2586 | function! s:status_update() range 2587 | let lines = getline(a:firstline, a:lastline) 2588 | let names = filter(map(lines, 's:extract_name(v:val, "[x-]", "")'), '!empty(v:val)') 2589 | if !empty(names) 2590 | echo 2591 | execute 'PlugUpdate' join(names) 2592 | endif 2593 | endfunction 2594 | 2595 | function! s:is_preview_window_open() 2596 | silent! wincmd P 2597 | if &previewwindow 2598 | wincmd p 2599 | return 1 2600 | endif 2601 | endfunction 2602 | 2603 | function! s:find_name(lnum) 2604 | for lnum in reverse(range(1, a:lnum)) 2605 | let line = getline(lnum) 2606 | if empty(line) 2607 | return '' 2608 | endif 2609 | let name = s:extract_name(line, '-', '') 2610 | if !empty(name) 2611 | return name 2612 | endif 2613 | endfor 2614 | return '' 2615 | endfunction 2616 | 2617 | function! s:preview_commit() 2618 | if b:plug_preview < 0 2619 | let b:plug_preview = !s:is_preview_window_open() 2620 | endif 2621 | 2622 | let sha = matchstr(getline('.'), '^ \X*\zs[0-9a-f]\{7,9}') 2623 | if empty(sha) 2624 | let name = matchstr(getline('.'), '^- \zs[^:]*\ze:$') 2625 | if empty(name) 2626 | return 2627 | endif 2628 | let title = 'HEAD@{1}..' 2629 | let command = 'git diff --no-color HEAD@{1}' 2630 | else 2631 | let title = sha 2632 | let command = 'git show --no-color --pretty=medium '.sha 2633 | let name = s:find_name(line('.')) 2634 | endif 2635 | 2636 | if empty(name) || !has_key(g:plugs, name) || !isdirectory(g:plugs[name].dir) 2637 | return 2638 | endif 2639 | 2640 | if exists('g:plug_pwindow') && !s:is_preview_window_open() 2641 | execute g:plug_pwindow 2642 | execute 'e' title 2643 | else 2644 | execute 'pedit' title 2645 | wincmd P 2646 | endif 2647 | setlocal previewwindow filetype=git buftype=nofile bufhidden=wipe nobuflisted modifiable 2648 | let batchfile = '' 2649 | try 2650 | let [sh, shellcmdflag, shrd] = s:chsh(1) 2651 | let cmd = 'cd '.plug#shellescape(g:plugs[name].dir).' && '.command 2652 | if s:is_win 2653 | let [batchfile, cmd] = s:batchfile(cmd) 2654 | endif 2655 | execute 'silent %!' cmd 2656 | finally 2657 | let [&shell, &shellcmdflag, &shellredir] = [sh, shellcmdflag, shrd] 2658 | if s:is_win && filereadable(batchfile) 2659 | call delete(batchfile) 2660 | endif 2661 | endtry 2662 | setlocal nomodifiable 2663 | nnoremap q :q 2664 | wincmd p 2665 | endfunction 2666 | 2667 | function! s:section(flags) 2668 | call search('\(^[x-] \)\@<=[^:]\+:', a:flags) 2669 | endfunction 2670 | 2671 | function! s:format_git_log(line) 2672 | let indent = ' ' 2673 | let tokens = split(a:line, nr2char(1)) 2674 | if len(tokens) != 5 2675 | return indent.substitute(a:line, '\s*$', '', '') 2676 | endif 2677 | let [graph, sha, refs, subject, date] = tokens 2678 | let tag = matchstr(refs, 'tag: [^,)]\+') 2679 | let tag = empty(tag) ? ' ' : ' ('.tag.') ' 2680 | return printf('%s%s%s%s%s (%s)', indent, graph, sha, tag, subject, date) 2681 | endfunction 2682 | 2683 | function! s:append_ul(lnum, text) 2684 | call append(a:lnum, ['', a:text, repeat('-', len(a:text))]) 2685 | endfunction 2686 | 2687 | function! s:diff() 2688 | call s:prepare() 2689 | call append(0, ['Collecting changes ...', '']) 2690 | let cnts = [0, 0] 2691 | let bar = '' 2692 | let total = filter(copy(g:plugs), 's:is_managed(v:key) && isdirectory(v:val.dir)') 2693 | call s:progress_bar(2, bar, len(total)) 2694 | for origin in [1, 0] 2695 | let plugs = reverse(sort(items(filter(copy(total), (origin ? '' : '!').'(has_key(v:val, "commit") || has_key(v:val, "tag"))')))) 2696 | if empty(plugs) 2697 | continue 2698 | endif 2699 | call s:append_ul(2, origin ? 'Pending updates:' : 'Last update:') 2700 | for [k, v] in plugs 2701 | let branch = s:git_origin_branch(v) 2702 | if len(branch) 2703 | let range = origin ? '..origin/'.branch : 'HEAD@{1}..' 2704 | let cmd = ['git', 'log', '--graph', '--color=never'] 2705 | if s:git_version_requirement(2, 10, 0) 2706 | call add(cmd, '--no-show-signature') 2707 | endif 2708 | call extend(cmd, ['--pretty=format:%x01%h%x01%d%x01%s%x01%cr', range]) 2709 | if has_key(v, 'rtp') 2710 | call extend(cmd, ['--', v.rtp]) 2711 | endif 2712 | let diff = s:system_chomp(cmd, v.dir) 2713 | if !empty(diff) 2714 | let ref = has_key(v, 'tag') ? (' (tag: '.v.tag.')') : has_key(v, 'commit') ? (' '.v.commit) : '' 2715 | call append(5, extend(['', '- '.k.':'.ref], map(s:lines(diff), 's:format_git_log(v:val)'))) 2716 | let cnts[origin] += 1 2717 | endif 2718 | endif 2719 | let bar .= '=' 2720 | call s:progress_bar(2, bar, len(total)) 2721 | normal! 2G 2722 | redraw 2723 | endfor 2724 | if !cnts[origin] 2725 | call append(5, ['', 'N/A']) 2726 | endif 2727 | endfor 2728 | call setline(1, printf('%d plugin(s) updated.', cnts[0]) 2729 | \ . (cnts[1] ? printf(' %d plugin(s) have pending updates.', cnts[1]) : '')) 2730 | 2731 | if cnts[0] || cnts[1] 2732 | nnoremap (plug-preview) :silent! call preview_commit() 2733 | if empty(maparg("\", 'n')) 2734 | nmap (plug-preview) 2735 | endif 2736 | if empty(maparg('o', 'n')) 2737 | nmap o (plug-preview) 2738 | endif 2739 | endif 2740 | if cnts[0] 2741 | nnoremap X :call revert() 2742 | echo "Press 'X' on each block to revert the update" 2743 | endif 2744 | normal! gg 2745 | setlocal nomodifiable 2746 | endfunction 2747 | 2748 | function! s:revert() 2749 | if search('^Pending updates', 'bnW') 2750 | return 2751 | endif 2752 | 2753 | let name = s:find_name(line('.')) 2754 | if empty(name) || !has_key(g:plugs, name) || 2755 | \ input(printf('Revert the update of %s? (y/N) ', name)) !~? '^y' 2756 | return 2757 | endif 2758 | 2759 | call s:system('git reset --hard HEAD@{1} && git checkout '.plug#shellescape(g:plugs[name].branch).' --', g:plugs[name].dir) 2760 | setlocal modifiable 2761 | normal! "_dap 2762 | setlocal nomodifiable 2763 | echo 'Reverted' 2764 | endfunction 2765 | 2766 | function! s:snapshot(force, ...) abort 2767 | call s:prepare() 2768 | setf vim 2769 | call append(0, ['" Generated by vim-plug', 2770 | \ '" '.strftime("%c"), 2771 | \ '" :source this file in vim to restore the snapshot', 2772 | \ '" or execute: vim -S snapshot.vim', 2773 | \ '', '', 'PlugUpdate!']) 2774 | 1 2775 | let anchor = line('$') - 3 2776 | let names = sort(keys(filter(copy(g:plugs), 2777 | \'has_key(v:val, "uri") && isdirectory(v:val.dir)'))) 2778 | for name in reverse(names) 2779 | let sha = has_key(g:plugs[name], 'commit') ? g:plugs[name].commit : s:git_revision(g:plugs[name].dir) 2780 | if !empty(sha) 2781 | call append(anchor, printf("silent! let g:plugs['%s'].commit = '%s'", name, sha)) 2782 | redraw 2783 | endif 2784 | endfor 2785 | 2786 | if a:0 > 0 2787 | let fn = s:plug_expand(a:1) 2788 | if filereadable(fn) && !(a:force || s:ask(a:1.' already exists. Overwrite?')) 2789 | return 2790 | endif 2791 | call writefile(getline(1, '$'), fn) 2792 | echo 'Saved as '.a:1 2793 | silent execute 'e' s:esc(fn) 2794 | setf vim 2795 | endif 2796 | endfunction 2797 | 2798 | function! s:split_rtp() 2799 | return split(&rtp, '\\\@ :bnext 68 | nnoremap :bprevious 69 | nnoremap q :bdelete 70 | nnoremap :w 71 | 72 | " tabs / indent 73 | set tabstop=2 74 | set shiftwidth=2 75 | set expandtab " spaces instead of tabs 76 | set smartindent 77 | set autoindent 78 | set smarttab 79 | " shift-tab to unindent 80 | imap << 81 | 82 | " no backups, swapfiles 83 | set nobackup 84 | set nowb 85 | set noswapfile 86 | 87 | " ruler, line numbers, cursorline 88 | set ruler 89 | set number 90 | set textwidth=80 91 | set colorcolumn=+0 92 | set formatoptions-=t 93 | " set cursorline " may slows things down dramatically 94 | " hi clear CursorLine " don't highlight whole line as it's slow 95 | 96 | " display command 97 | "set showcmd 98 | 99 | " allow opening buffers in background even if current is unsaved 100 | set hidden 101 | 102 | " disable folding 103 | set nofoldenable 104 | 105 | " search 106 | set incsearch " find as you type 107 | set smartcase 108 | set nohlsearch 109 | 110 | " keep lines when scrolling 111 | set scrolloff=3 112 | 113 | " if a file has been changed outside of vim and it has not been changed, 114 | " automatically re-read it 115 | set autoread 116 | 117 | " macosx clipboard instead of vim's 118 | set clipboard=unnamed 119 | 120 | " delete to the left in insert mode with backspace 121 | set backspace=indent,eol,start 122 | 123 | " fix delete key in iTerm2 124 | exe "set =\[3;*~" 125 | 126 | " remember last location in file 127 | if has("autocmd") 128 | au BufReadPost * if line("'\"") > 0 && line("'\"") <= line("$") | exe "normal g'\"" | endif 129 | endif 130 | 131 | " toggle insert mode 132 | nnoremap i 133 | imap 134 | 135 | " invisibles 136 | "hi NonText ctermfg=Grey 137 | "hi SpecialKey ctermfg=Grey 138 | " shortcut to rapidly toggle `set list` 139 | nmap l :set list! 140 | " use the same symbols as TextMate for tabstops and EOLs 141 | set listchars=tab:▸\ ,eol:¬ 142 | 143 | " line numbers 144 | nmap n :set number! 145 | 146 | " line breaks 147 | set showbreak=↳ 148 | " toggle showbreak 149 | fun! ToggleShowBreak() 150 | if &showbreak == '' 151 | set showbreak=↳ 152 | else 153 | set showbreak= 154 | endif 155 | endfun 156 | nmap b :call ToggleShowBreak() 157 | 158 | " strip trailing whitespace on save 159 | fun! StripTrailingWhitespaces() 160 | let l = line(".") 161 | let c = col(".") 162 | %s/\s\+$//e 163 | call cursor(l, c) 164 | endfun 165 | autocmd BufWritePre * :call StripTrailingWhitespaces() 166 | -------------------------------------------------------------------------------- /zsh/kubectl/kubectl.plugin.zsh: -------------------------------------------------------------------------------- 1 | if [ $commands[kubectl] ]; then 2 | kubectl() { 3 | unfunction "$0" 4 | 5 | source <(kubectl completion zsh) 6 | 7 | $0 "$@" 8 | } 9 | fi 10 | -------------------------------------------------------------------------------- /zshrc: -------------------------------------------------------------------------------- 1 | # Path to your oh-my-zsh configuration. 2 | ZSH=$HOME/.oh-my-zsh 3 | 4 | # Set name of the theme to load. 5 | # Look in ~/.oh-my-zsh/themes/ 6 | # Optionally, if you set this to "random", it'll load a random theme each 7 | # time that oh-my-zsh is loaded. 8 | ZSH_THEME="" 9 | 10 | # Set to this to use case-sensitive completion 11 | # CASE_SENSITIVE="true" 12 | 13 | # Comment this out to disable weekly auto-update checks 14 | # DISABLE_AUTO_UPDATE="true" 15 | 16 | # Uncomment following line if you want to disable autosetting terminal title. 17 | # DISABLE_AUTO_TITLE="true" 18 | 19 | plugins=(z git docker npm colored-man-pages history-substring-search fast-syntax-highlighting kubectl) 20 | 21 | if type brew &>/dev/null; then 22 | FPATH="/opt/homebrew/share/zsh/site-functions:${FPATH}" 23 | fi 24 | 25 | [ -f ~/.env ] && source ~/.env 26 | 27 | source $ZSH/oh-my-zsh.sh 28 | 29 | HISTSIZE=500000 30 | SAVEHIST=$HISTSIZE 31 | 32 | zstyle ':completion:*' insert-tab false # don't write tabs to prompt 33 | 34 | setopt share_history # share history across sessions 35 | setopt hist_reduce_blanks # remove blanks for commands 36 | setopt hist_ignore_all_dups # prevent duplicate entries 37 | setopt interactive_comments # allow interactive comments 38 | setopt transient_rprompt # only show rprompt on last line 39 | unsetopt cdablevars # vars shouldn't expand to directory names 40 | 41 | # https://geoff.greer.fm/lscolors/ 42 | export LSCOLORS='ExFxCxDxbxegedabagacad' 43 | zstyle ':completion:*' list-colors 'di=1;34:ln=1;35:so=1;32:pi=1;33:ex=31:bd=34;46:cd=34;43:su=30;41:sg=30;46:tw=30;42:ow=30;43' 44 | 45 | # https://github.com/zdharma/fast-syntax-highlighting 46 | FAST_HIGHLIGHT_STYLES[${FAST_THEME_NAME}path]='fg=white,bold' 47 | FAST_HIGHLIGHT_STYLES[${FAST_THEME_NAME}path-to-dir]='fg=white,bold' 48 | FAST_HIGHLIGHT_STYLES[${FAST_THEME_NAME}global-alias]='fg=white,bold,bg=none' 49 | 50 | eval "$(starship init zsh)" 51 | #export ATUIN_DB_PATH=~/.history.db 52 | eval "$(atuin init zsh --disable-up-arrow --disable-ctrl-r)" 53 | --------------------------------------------------------------------------------