From 396488e38c02f01d70d92ef68b4b168c1c10e203 Mon Sep 17 00:00:00 2001 From: Marsway Date: Mon, 9 Jun 2025 17:41:55 +0800 Subject: [PATCH] backup: 2025-06-0 --- .bin/z.lua/.gitignore | 42 + .bin/z.lua/LICENSE | 21 + .bin/z.lua/README.cn.md | 470 +++++ .bin/z.lua/README.md | 610 ++++++ .bin/z.lua/images/complete-1.png | Bin 0 -> 5245 bytes .bin/z.lua/images/complete-2.png | Bin 0 -> 9265 bytes .bin/z.lua/images/fzf.png | Bin 0 -> 8674 bytes .bin/z.lua/images/mru.png | Bin 0 -> 10665 bytes .bin/z.lua/images/step1.png | Bin 0 -> 7710 bytes .bin/z.lua/images/step2.png | Bin 0 -> 5798 bytes .bin/z.lua/images/step3.png | Bin 0 -> 3713 bytes .bin/z.lua/images/step4.png | Bin 0 -> 3523 bytes .bin/z.lua/init.fish | 52 + .bin/z.lua/ranger_zlua.py | 91 + .bin/z.lua/test_path.lua.rename | 193 ++ .bin/z.lua/z.cmd | 130 ++ .bin/z.lua/z.lua | 2990 ++++++++++++++++++++++++++++++ .bin/z.lua/z.lua.plugin.zsh | 39 + .config/Brewfile | 7 +- .config/rclone/rclone.conf | 4 +- .gitmodules | 0 .oh-my-zsh | 1 - .ssh/known_hosts | 3 + 23 files changed, 4649 insertions(+), 4 deletions(-) create mode 100644 .bin/z.lua/.gitignore create mode 100644 .bin/z.lua/LICENSE create mode 100644 .bin/z.lua/README.cn.md create mode 100644 .bin/z.lua/README.md create mode 100644 .bin/z.lua/images/complete-1.png create mode 100644 .bin/z.lua/images/complete-2.png create mode 100644 .bin/z.lua/images/fzf.png create mode 100644 .bin/z.lua/images/mru.png create mode 100644 .bin/z.lua/images/step1.png create mode 100644 .bin/z.lua/images/step2.png create mode 100644 .bin/z.lua/images/step3.png create mode 100644 .bin/z.lua/images/step4.png create mode 100644 .bin/z.lua/init.fish create mode 100644 .bin/z.lua/ranger_zlua.py create mode 100644 .bin/z.lua/test_path.lua.rename create mode 100644 .bin/z.lua/z.cmd create mode 100755 .bin/z.lua/z.lua create mode 100644 .bin/z.lua/z.lua.plugin.zsh delete mode 100644 .gitmodules delete mode 160000 .oh-my-zsh diff --git a/.bin/z.lua/.gitignore b/.bin/z.lua/.gitignore new file mode 100644 index 0000000..4975c6c --- /dev/null +++ b/.bin/z.lua/.gitignore @@ -0,0 +1,42 @@ +# Compiled Lua sources +luac.out + +# luarocks build files +*.src.rock +*.zip +*.tar.gz + +# Object files +*.o +*.os +*.ko +*.obj +*.elf + +# Precompiled Headers +*.gch +*.pch + +# Libraries +*.lib +*.a +*.la +*.lo +*.def +*.exp + +# Shared objects (inc. Windows DLLs) +*.dll +*.so +*.so.* +*.dylib + +# Executables +*.exe +*.out +*.app +*.i*86 +*.x86_64 +*.hex + +/.vscode/* diff --git a/.bin/z.lua/LICENSE b/.bin/z.lua/LICENSE new file mode 100644 index 0000000..b9effe8 --- /dev/null +++ b/.bin/z.lua/LICENSE @@ -0,0 +1,21 @@ +MIT License + +Copyright (c) 2018 Linwei + +Permission is hereby granted, free of charge, to any person obtaining a copy +of this software and associated documentation files (the "Software"), to deal +in the Software without restriction, including without limitation the rights +to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +copies of the Software, and to permit persons to whom the Software is +furnished to do so, subject to the following conditions: + +The above copyright notice and this permission notice shall be included in all +copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE +SOFTWARE. diff --git a/.bin/z.lua/README.cn.md b/.bin/z.lua/README.cn.md new file mode 100644 index 0000000..493e680 --- /dev/null +++ b/.bin/z.lua/README.cn.md @@ -0,0 +1,470 @@ +# z.lua + +快速路径切换工具(类似 z.sh / autojump / fasd),兼容 Windows 和所有 Posix Shell 以及 Fish Shell,同时包含了众多改进。 + + +## Description + +z.lua 是一个快速路径切换工具,它会跟踪你在 shell 下访问过的路径,通过一套称为 Frecent 的机制(源自 FireFox),经过一段简短的学习之后,z.lua 会帮你跳转到所有匹配正则关键字的路径里 Frecent 值最高的那条路径去。 + +正则将按顺序进行匹配,"z foo bar" 可以匹配到 /foo/bar ,但是不能匹配 /bar/foo。 + + +## Features + +- 性能比 **z.sh** 快三倍,比 **fasd** / **autojump** 快十倍以上。 +- 支持 Posix Shell:bash, zsh, dash, sh, ash, busybox 等等。 +- 支持 Fish Shell,Power Shell 和 Windows cmd。 +- 使用增强匹配算法,更准确的带你去到你想去的地方。 +- 低占用,能够仅在当前路径改变时才更新数据库(将 `$_ZL_ADD_ONCE` 设成 1)。 +- 交互选择模式,如果有多个匹配结果的话,跳转前允许你进行选择。 +- 集成 fzf (可选),可以用来做可视化选择或者参数补全。 +- 快速跳转到父目录,或者项目根目录,代替反复 “cd ../../.." 。 +- 兼容 lua 5.1, 5.2 和 5.3 以上版本。 +- 自包含且无额外依赖,单个 `z.lua` 文件完成所有工作。 + + +## Examples + +```bash +z foo # 跳转到包含 foo 并且权重(Frecent)最高的路径 +z foo bar # 跳转到同时包含 foo 和 bar 并且权重最高的路径 +z -r foo # 跳转到包含 foo 并且访问次数最高的路径 +z -t foo # 跳转到包含 foo 并且最近访问过的路径 +z -l foo # 不跳转,只是列出所有匹配 foo 的路径 +z -c foo # 跳转到包含 foo 并且是当前路径的子路径的权重最高的路径 +z -e foo # 不跳转,只是打印出匹配 foo 并且权重最高的路径 +z -i foo # 进入交互式选择模式,让你自己挑选去哪里(多个结果的话) +z -I foo # 进入交互式选择模式,但是使用 fzf 来选择 +z -b foo # 跳转到父目录中名称以 foo 开头的那一级 +``` + + +## Install + +- Posix Shells(Bash、zsh、dash、sh 或 BusyBox 等): + + 在你的 `.bashrc`, `.zshrc` 或者 `.profile` 文件中按 shell 类型添加对应语句: + + eval "$(lua /path/to/z.lua --init bash)" # BASH 初始化 + eval "$(lua /path/to/z.lua --init zsh)" # ZSH 初始化 + eval "$(lua /path/to/z.lua --init posix)" # Posix shell 初始化 + + 用下面参数初始化会进入“增强匹配模式”: + + eval "$(lua /path/to/z.lua --init bash once enhanced)" # BASH 初始化 + eval "$(lua /path/to/z.lua --init zsh once enhanced)" # ZSH 初始化 + eval "$(lua /path/to/z.lua --init posix once enhanced)" # Posix shell 初始化 + + 同时 zsh 支持 antigen/oh-my-zsh 等包管理器,可以用下面路径: + + skywind3000/z.lua + + 进行安装,比如 antigen 的话,在 `.zshrc` 中加入: + + antigen bundle skywind3000/z.lua + + 就可以了(主要要放在 antigen apply 语句之前)。 + + **注意**:使用 WSL-1 的用户,需要安装 `lua-filesystem` 包: + + sudo apt-get install lua-filesystem + + 这是由于 wsl-1 的 [bug](https://github.com/microsoft/WSL/issues/5505) 引起的,使用 lua-filesystem 可以避免该问题。 + +- Fish Shell: + + 新建 `~/.config/fish/conf.d/z.fish` 文件,并包含如下代码: + + source (lua /path/to/z.lua --init fish | psub) + + Fish version `2.4.0` 或者以上版本都支持,还有一种初始化方法: + + lua /path/to/z.lua --init fish > ~/.config/fish/conf.d/z.fish + + 但是第二种方法需要记得在 z.lua 位置改变或者 lua 版本升级后需要重新生成。 + +- Nushell: + + 在 `env.nu` 中加入如下代码: + + lua /path/to/z.lua --init nushell | save -f ~/.cache/zlua.nu + + 然后在 `config.nu` 中加入如下代码: + + source ~/.cache/zlua.nu + alias z = _zlua + +- Power Shell: + + 在你 Power Shell 的配置文件 `profile.ps1` 中放入下面语句: + + Invoke-Expression (& { (lua /path/to/z.lua --init powershell) -join "`n" }) + + +- Windows cmd (with clink): + + - 将 z.lua 和 z.cmd 拷贝到 clink 的安装目录。 + - 将 clink 的安装目录添加到 `%PATH%` (z.cmd 可以被任意位置调用到)。 + - 保证 lua 命令在你的 `%PATH%` 环境变量中。 + + +- Windows cmder: + + - 将 z.lua 和 z.cmd 拷贝到 cmder/vendor 目录中。 + - 将 cmder/vendor 添加到环境变量 `%PATH%` 里面。 + - 保证 lua 命令在你的 `%PATH%` 环境变量中。 + + +## Options + +- 设置 `$_ZL_CMD` 来改变命令名称 (默认为 z)。 +- 设置 `$_ZL_DATA` 来改变数据文件 (default ~/.zlua)。 +- 设置 `$_ZL_NO_PROMPT_COMMAND` 为 1 来跳过钩子函数初始化(方便自己处理)。 +- 设置 `$_ZL_EXCLUDE_DIRS` 逗号分隔的路径列表,列表内的路径不会被收集。 +- 设置 `$_ZL_ADD_ONCE` 为 '1' 时,仅在当前路径 `$PWD` 改变时才更新数据库。 +- 设置 `$_ZL_MAXAGE` 来确定一个数据老化的阈值 (默认为 5000)。 +- 设置 `$_ZL_CD` 用来指定你想用的 cd 命令,比如有人用 cd_func 。 +- 设置 `$_ZL_ECHO` 为 1 可以在跳转后显示目标路径名称。 +- 设置 `$_ZL_MATCH_MODE` 为 1 可以打开 “增强匹配模式”。 +- 设置 `$_ZL_HYPHEN` 为 1 可以允许关键字中包含横线 (横线默认是 lua 正则关键字,要转写成 `%-`)。 + +## Aging + +`z.lua` 在数据库中为每条路径维护着一个称为 rank 的字段,用于记录每条历史路径的访问次数,每次访问某路径,该路径对应 rank 字段的值就会增加 1。随着被添加的路径越来越多,`z.lua` 使用一种称为 “数据老化” 的方式来控制数据的总量。即,每次更新数据库后,会将所有路径的 rank 值加起来,如果这个值大于 5000 (`$_ZL_MAXAGE`),所有路径的 rank 值都会乘以 0.9,然后剔除所有 rank 小于 1 的记录。 + + +## Frecency + +Frecency 是一个由 'recent' 和 'frequency' 组成的合成词,这个术语由 Mozilla 发明,用于同时兼顾访问的频率和上一次访问到现在的时间差(是否最近访问)两种情况。 + +对于 z.lua,一条路径如果访问次数过低,它的 rank 值就会比较低,但是如果它最近被访问过,那么它将迅速获得一个比其他曾经频繁访问但是最近没有访问过的路径更高的权重。Frecent 并不记录在数据库中,是运行的时候即时计算出来的。 + + + +## 默认匹配算法 + +默认情况下 z.lua 使用和 z.sh 类似的匹配算法,成为默认匹配法。给定路径会按顺序匹配各个正则表达式。 + +- cd 到一个包含 foo 的路径: + + z foo + +- cd 到一个以 foo 结尾的路径: + + z foo$ + +- 使用多个参数进行跳转: + + 假设路径历史数据库(~/.zlua)中有两条记录: + + 10 /home/user/work/inbox + 30 /home/user/mail/inbox + + `"z in"`将会跳转到 `/home/user/mail/inbox` 因为它有更高的权重,同时你可以传递更多参数给 z.lua 来更加精确的指明,如 `"z w in"` 则会让你跳到 `/home/user/work/inbox`。 + +## 增强匹配算法 + +你可以通过设置环境变量来启用增强匹配模式: + + export _ZL_MATCH_MODE=1 + +或者使用下面语句: + + eval "$(lua /path/to/z.lua --init bash enhanced)" + +进行初始化,他们是等效的,记得把上面的 bash 可以根据你的 shell 改为 `zsh` 或者 `posix`。 + + +对于一个给定的正则关键字序列(即 z 命令后面的参数),只有同时满足两个条件才算匹配成功: + +1. 正则关键字将按顺序进行匹配(这条和默认匹配法相同)。 +2. 最后一个关键字可以和路径名的最后一段相匹配。 + +如果两条规则同时启用找不到任何结果,那么将会退回到只用规则 1 进行筛选,这两条规则是参考 fasd 引入的。 + +- 匹配路径名的最后一段: + + 假设数据库内容为: + + 10 /home/user/workspace + 20 /home/user/workspace/project1 + 30 /home/user/workspace/project2 + 40 /home/user/workspace/project3 + + 在增强模式下使用 `"z wo"` 的话,只有 `/home/user/workspace` 满足匹配,因为按照第二条规则,这是唯一一条最有一段名称匹配 `wo` 的路径。 + + 因为最后一级目录名称总是最容易记住的,所以给到它比较高的优先级。在默认匹配算法中,你同样可以用 `"z space$"` 来达到相同的目的,但是 `"z wo"` 可以打更少的字。 + + 小技巧: + + - 如果你在增强匹配算法下,想让最后一个关键字不当匹配最后一段路径名,还可以像默认匹配算法中一样匹配路径的其他部分的话,你可以在最后加一个独立的 '$' 参数,比如:`"z wo $"` + - 如果你在增强匹配算法下,想让最后一个关键字匹配最后一段路径名以前的部分,那么可以增加一个斜杆参数,比如:`"z wo /"`。 + + +- 如果没法匹配,同时又存在一条路径名和关键字相同,那么 cd 过去: + + 有时候如果你输入: + + z foo + + 但是数据库里又没有任何匹配 foo 的记录,然后却存在一个可以在当前位置访问的目录,刚好名字是 "foo",那么 "`z foo`" 的效果将会和下面的命令效果相同: + + cd foo + + 因此,在增强匹配算法中,你总可以像 cd 命令一样使用 z 命令,而不必当心目标路径是否被记录过。 + +- 忽略当前路径: + + 如果你使用 `z xxx` 但是当前路径恰好是最佳匹配结果,那么 z.lua 会使用次优结果进行跳转。假设有如下数据: + + 10 /Users/Great_Wall/.rbenv/versions/2.4.1/lib/ruby/gems + 20 /Library/Ruby/Gems/2.0.0/gems + + 默认情况下,当我使用 `z gems` 时,我会被带到 `/Library/Ruby/Gems/2.0.0/gems`,因为它有更高权重,但是可能并不是我想要去的地方,这时我按一下方向键上键,再次执行 `z gems`,那么我就能被带到 `/Users/Great_Wall/.rbenv/versions/2.4.1/lib/ruby/gems` 目录中,而这正是我想去的地方。 + + 我当然可以每次使用`z env gems` 来精确指明,但是每当我输入 `z xxx` 我必然是想进行路径跳转的,而不是呆在原地,所以使用增强匹配模式,即便当前目录是最佳匹配,它也能懂得你想跳转的心思。 + +在我最初实现 z.lua 时,只有一个和 z.sh 类似的默认匹配算法,在网友的建议下,我陆续学习了来自 fasd / autojump 中的优秀理念,并加以完善改进,成为如今集三家之长的 “增强匹配算法” ,给它取个昵称,叫做 “更懂你的匹配算法”。 + + +## Add once + +何时更新数据呢?默认情况下,z.lua 会在每次显示命令提示符时记录当前路径(和 z.sh 一致),但是还提供了一个 $_ZL_ADD_ONCE 的环境变量选项,设置成 1 的话,只有当前路径改变,才会将新路径添加到数据库。 + +除了设置环境变量外,不同的 shell 下还可以在初始化时增加 "once" 参数来达到相同目的,比如: + +````bash +eval "$(lua /path/to/z.lua --init bash once enhanced)" +eval "$(lua /path/to/z.lua --init zsh once enhanced)" +source (lua /path/to/z.lua --init fish once enhanced | psub) +```` + +将会同时启用增强匹配算法和 once 机制,在一些比较慢的硬件下(路由器,cygwin,msys),使用该机制将有效的提升性能。其实 autojump 在 zsh 下会使用类似 once 的机制,而 bash 下则和 z.sh 类似。 + +从效果上来讲,z.sh 的模式(关闭 once)强调的是 “在某路径下工作的时间长短”,而 autojump 的模式(启用 once)则强调 “进入某路径的次数多少”。 + +## 交互式选择模式 + +使用 -i 参数进行跳转时, 如果有多个匹配结果,那么 z.lua 会给你显示一个列表: + +```bash +$ z -i soft +3: 0.25 /home/data/software +2: 3.75 /home/skywind/tmp/comma/software +1: 21 /home/skywind/software +> {光标位置} +``` + +然后你按照最前面的序号输入你想要去的地方,比如输入 3 就会进入 `/home/data/software`。如果你不输入任何东西直接按回车,那么将会直接退出而不进行任何跳转。 + +PS:如果你使用 Fish shell,需要 2.7.0 以上才支持该功能。 + +## FZF supports + +版本 1.1.0 引入了新的 `"-I"` 参数,让你可以使用 fzf 在多项结果进行快速选择: + + +![](images/fzf.png) + +当你使用 `"z -I vim"` 时,12 条路径被筛选出来,并按照 frecent 排序,他们都包含 "vim" 关键字,在实际 cd 改变路径前,z.lua 会调用 fzf 来让你更方便的选择你想去的地方,每条记录包含左边的 frecent 权重和右边的路径名,权重越高的排在越前面。 + +你可以在 fzf 里输入一些**空格分隔**的关键字(不需要先后顺序),或者按 `CTRL+J` / `CTRL+K` (方向键的上下也可以)进行选择,`ESC` 或者 `CTRL`+`D`/`G` 放弃。 + +你仍然可以用老方法,通过在 `z` 命令后面添加更多关键词来精确的匹配你想去的地方,这个特性给了你一个可视化的方式来做这件事情。为了方便起见,通常把 `z -I` alias 成 `zf` (z + fuzzy finder)。如果搜索结果只有一项,那么 z.lua 会直接跳转过去,不需要启动 fzf 再选择一遍,只有多项结果要选择时,才会启动 fzf。 + +`"z -I ."` 或者 `"zf ."` 可以让 fzf 来对整个数据库中的路径进行选择。 + +PS:你可以使用 `$_ZL_FZF` 环境变量来精确指明 fzf 的可执行路径,默认的话就是 fzf。如果你使用 Fish shell,需要 2.7.0 以上才支持该功能。 + + +## 快速回到父目录 + +`"-b"` 选项可以快速回到某一级父目录,避免重复的输入 "cd ../../.."。 + +- **(没有参数)**:`cd` 到项目根目录,即跳转到最近的包含 (.git/.svn/.hg) 的父目录。 +- **(单个参数)**:`cd` 到离当前目录最近的以关键字开头的父目录,如果找不到就尝试跳到包含关键字的父目录。 +- **(两个参数)**:将当前路径中的第一个关键词替换为第二个关键词。 + +先将 `z -b` 别名成 `zb`: + +```bash +# 一直向上退到项目根目录(就是里面有一个 .git 目录的地方) +~/github/lorem/src/public$ zb + => cd ~/github/lorem + +# cd 到第一个以 g 开头的父目录 +~/github/vimium/src/public$ zb g + => cd ~/github + +# 快速回到 site 目录 +~/github/demo/src/org/main/site/utils/file/reader/whatever$ zb si + => cd ~/github/demo/src/org/main/site + +# 将 jekyll 替换为 ghost +~/github/jekyll/test$ zb jekyll ghost + => cd ~/github/ghost/test +``` + +向后跳转同样也支持环境变量 `$_ZL_ECHO`(用来显示跳转结果),这样可以搭配其他工具,在目标目录内执行命令,而并不需要改变当前工作目录(比如:``ls `zb git` ``)。 + +环境变量 `$_ZL_ROOT_MARKERS` 是一个逗号分隔的列表,用来识别项目根目录,可以重定义成: + + +```bash +export _ZL_ROOT_MARKERS=".git,.svn,.hg,.root,package.json" +``` + +这样在用 `zb` 时,可以回到包含 `.root`文件,或者 `package.json` 文件的父目录。 + +**Bonus**:`zb ..` 相当于 `cd ..`,`zb ...` 相当于 `cd ../..`,而 `zb ....` 相当于 `cd ../../..` 等等。 最后 `zb ..20` 等同于调用 `cd ..` 二十次。 + +**Bonus**: 试试 `z -b -i` 以及 `z -b -I`,推荐把他们取个别名成 `zbi` 和 `zbf`。 + + +## 补全功能 + +zsh/fish 的补全系统是比较完善的,使用 `z foo` 就能触发补全,显示一个列表: + +![](images/complete-1.png) + +再次按 `` 键,就可以用可视化的方式对列表进行选择。 + +在 bash 下面补全系统没有那么强大,所以 z.lua 引入了 fzf 补全,初始化时在 `--init` 后追加 `fzf` 关键字: + +```bash +eval "$(lua /path/to/z.lua --init bash enhanced once echo fzf)" +``` + +如果你想在 zsh 中使用 fzf 补全,初始化时在 `--init` 后追加 `fzf` 关键字: + +```zsh +eval "$(lua /path/to/z.lua --init zsh enhanced once echo fzf)" +``` + +然后你在 bash/zsh 中,输入部分关键字后按 tab,就能把匹配的路径列出来: + +![](images/complete-2.png) + +有了 fzf 的帮助,bash 下补全也非常方便了。注意看左边的权重,fzf 搜索过滤的顺序是有讲究的,Frecent 权重越高的越靠前,不是乱排序的,更不是简单的按字符串字母排序。这里完全保证权重越高的路径越靠前。 + +`z.lua` 可以同 [fz](https://github.com/changyuheng/fz) 协作以提供**更好的补全结果**,详细见 [FAQ](https://github.com/skywind3000/z.lua/wiki/FAQ#fzsh-for-better-completion)。 + +注意:该功能在初始化 z.lua 之前,会检测 $PATH 中是否有 fzf 这个程序,有的话才启用。 + + +## MRU + +`z.lua` 提供 `dirstack` 让你更便捷的访问最近刚刚去过的目录,而不需要输入任何关键字。这个方法叫做 `dirstack`,它记录着最近你刚刚去过的 10 条最新路径,然后是用 `z -`,`z --` 和 `z -{num}` 来操作: + +```bash +# 显示当前的 dir stack +$ z -- + 0 /home/skywind/work/match/memory-match + 1 /home/skywind/.local/etc + 2 /home/skywind/software/vifm-0.9.1 + 3 /home/skywind/work + 4 /home/skywind/work/match + +# cd 到栈里 2 号路径 +$ z -2 + => cd /home/skywind/software/vifm-0.9.1 + +# 弹出栈顶 (cd 到上一次的老路径),和 "z -0" 相同 +$ z - + => cd - +``` + +这个 `dirstack` 是根据 z.lua 的路径历史数据库计算的出来的,和具体的 shell 或者操作系统无关。你退出再登陆不会丢失这些记录,不同的 shell 之间也可以共享同一份记录。 + +此外,还能通过前面提到的 `-I` 和 `-t` 参数组和,使用 fzf 选择最近去过的目录: + +```bash +alias zh='z -I -t .' +``` + +方便起见,定义个新的别名 `zh`(回到历史路径的意思),我们用 `-t` 参数来告诉 `z.lua` 按时间戳为权重排序,同时 `-I` 启用 fzf 搜索,最后句号代表任意路径。 + +那么当我们在命令行敲入 zh 时,就可以用 fzf 进行历史路径操作了: + +![](images/mru.png) + +第一列上次访问距今多少秒,第二列是目录名。你可以试着敲几个字母,用 fzf 的字符串模糊匹配进行定位,或者用光标键的上和下(CTRL+J/K 也可以)来上下移动,最后按回车 cd 过去,或者 ESC 放弃。 + + + +## Tips + +推荐一些常用的命令别名: + +```bash +alias zc='z -c' # 严格匹配当前路径的子路径 +alias zz='z -i' # 使用交互式选择模式 +alias zf='z -I' # 使用 fzf 对多个结果进行选择 +alias zb='z -b' # 快速回到父目录 +``` + +导入 z.sh 的数据: + + +```bash +cat ~/.z >> ~/.zlua +``` + +导入 autojump 的数据: + +```bash +FN="$HOME/.local/share/autojump/autojump.txt" +awk -F '\t' '{print $2 "|" $1 "|" 0}' $FN >> ~/.zlua +``` + +要更好的使用 `z.lua`,别忘记阅读:[Frequently Asked Questions](https://github.com/skywind3000/z.lua/wiki/FAQ)。 + + +## Benchmark + +最慢的部分当然是添加当前路径到数据库。该操作会在每次你按回车时执行,所以我在我的 Nas 上做了个对比: + +```bash +$ time autojump --add /tmp +real 0m0.352s +user 0m0.077s +sys 0m0.185s + +$ time fasd -A /tmp +real 0m0.618s +user 0m0.076s +sys 0m0.242s + +$ time _z --add /tmp +real 0m0.194s +user 0m0.046s +sys 0m0.154s + +$ time _zlua --add /tmp +real 0m0.052s +user 0m0.015s +sys 0m0.030s +``` + +可以看出,z.lua 是消耗资源最少,并且最快的,可以更流畅的在性能不好的环境中使用。 + +## Why Lua ? + +更好的兼容性,最开始我想要在我的路由器和 Nas 系统上使用 z.sh,但是它依赖的 awk 版本比较高,这两个系统上的 awk 都是一个 busybox 的经过裁剪的 awk ,z.sh 完全无法正常工作。使用 shell 开发还有一个问题是严重依赖 shell 的版本,很多逻辑既要在 zsh 下可以运行,又要在 dash 下能跑,用 lua 开发的话,核心逻辑全部写成 lua 不用考虑太多琐碎的兼容性,可以为各种 shell 提供完全一致的体验。 + +描述力强,可以更好的实现核心功能,同时速度更快,纯 shell 开发的话,太多语句是通过子进程 shell 的模式运行,所以性能很差,而 Python 开发的话启动速度又太慢,我在 Cygwin/msys 下用 z.sh 都觉得很卡,autojump/fasd 卡到不能用。 + +最关键的一点,Lua 速度很快 200 KB 的可执行程序,启动速度是 python 的 3 倍,perl 的 2 倍,很多命令行工具 go/rust 写成,动不动就 2MB / 3MB,他们都还没有完成加载,lua 脚本可能都运行完了。 + + +## Credit + +我的推特:https://x.com/skywind3000 +个人博客: https://skywind.me/blog + + +## License + +Licensed under MIT license. + diff --git a/.bin/z.lua/README.md b/.bin/z.lua/README.md new file mode 100644 index 0000000..fe1fa8f --- /dev/null +++ b/.bin/z.lua/README.md @@ -0,0 +1,610 @@ +# z.lua + +A command line tool which helps you navigate faster by learning your habits :zap: + +An alternative to [z.sh](https://github.com/rupa/z) with windows and posix shells support and various improvements. + +【[README in Chinese | 中文文档](README.cn.md)】 + + +## Description + +z.lua is a faster way to navigate your filesystem. It tracks your most used directories, based on 'frecency'. After a short learning phase, z will take you to the most 'frecent' directory that matches ALL of the regexes given on the command line, in order. + +For example, `z foo bar` would match `/foo/bar` but not `/bar/foo`. + +## Reputation + +From people using z.lua: + +- I like this in principal. I’m pretty damn predictable at the command line and far too lazy to make shortcuts +- It feels far more intuitive and it's so incredibly convenient to be able to jump between folders I'm working in without having to traverse an entire tree. The shell used to feel so constraining for me, but tools like this are making me enjoy it so much more. +- I can finally have autojump-like functionality on my Raspberry Pi 1 without waiting 30 seconds every time I open a new shell. Thanks z.lua devs. +- Anyway, z.lua is a promising project. If you only need directory jumping, it may be the best choice. + + +## Features + +- **10x** times faster than **fasd** and **autojump**, **3x** times faster than **z.sh**. +- Gain the ultimate speed with an optional [native module](https://github.com/skywind3000/czmod) written in C. +- Available for **posix shells**: bash, zsh, dash, sh, ash, ksh, busybox and etc. +- Available for Fish Shell, Power Shell and Windows cmd. +- [Enhanced matching algorithm](#enhanced-matching) takes you to where ever you want precisely. +- Allow updating database only if `$PWD` changed with "$_ZL_ADD_ONCE" set to 1. +- Interactive selection enables you to choose where to go before cd. +- Integrated with FZF (optional) for interactive selection and completion. +- Quickly go back to a parent directory instead of typing "cd ../../..". +- Corresponding experience in different shells and operating systems. +- Compatible with Lua (5.1, 5.2, 5.3+) and luajit. +- Self contained, distributed as a single `z.lua` script, no other dependence. + + +## Examples + +```bash +z foo # cd to most frecent dir matching foo +z foo bar # cd to most frecent dir matching foo and bar +z -r foo # cd to the highest ranked dir matching foo +z -t foo # cd to most recently accessed dir matching foo +z -l foo # list matches instead of cd +z -c foo # restrict matches to subdirs of $PWD +z -e foo # echo the best match, don't cd +z -i foo # cd with interactive selection +z -I foo # cd with interactive selection using fzf +z -b foo # cd to the parent directory starting with foo +z -b foo bar # replace foo with bar in cwd and cd there +``` + + +## Install + +- Bash: + + put something like this in your `.bashrc`: + + eval "$(lua /path/to/z.lua --init bash)" + + the default matching algorithm is similar to z.sh to keep compatible, you may like the enhanced matching algorithm for productivity: + + eval "$(lua /path/to/z.lua --init bash enhanced once)" + + and perhaps this: + + eval "$(lua /path/to/z.lua --init bash enhanced once echo)" + + if you want `z.lua` print the new directory after cd. + + For `fzf` tab completion use: + + eval "$(lua /path/to/z.lua --init bash enhanced once fzf)" + + **NOTE**: For wsl-1 users, `lua-filesystem` must be installed: + + sudo apt-get install lua-filesystem + + To avoid a wsl-1 [defect](https://github.com/microsoft/WSL/issues/5505). + +- Zsh: + + put something like this in your `.zshrc`: + + eval "$(lua /path/to/z.lua --init zsh)" + + Options like "enhanced", "once" and "fzf" can be used after `--init` too. It can also be initialized from "skywind3000/z.lua" with your zsh plugin managers (antigen / oh-my-zsh). + + **NOTE**: for wsl-1 users, `lua-filesystem` must be installed. + +- Posix Shells: + + put something like this in your `.profile`: + + eval "$(lua /path/to/z.lua --init posix)" + + For old shells like ksh (Korn Shell), some features are missing, you can try: + + eval "$(lua /path/to/z.lua --init posix legacy)" + + To generate old posix compatible script. + +- Fish Shell (version `2.4.0` or above): + + Create `~/.config/fish/conf.d/z.fish` with following code + + lua /path/to/z.lua --init fish | source + + If you'd like `z.lua` to cooperate with fish's own [directory history](https://fishshell.com/docs/3.2/index.html#id34), you can put + + set -gx _ZL_CD cd + + into the same file. + +- Nushell + + Put something like this in your `env.nu`: + + lua /path/to/z.lua --init nushell | save -f ~/.cache/zlua.nu + + Then put something like this in your `config.nu`: + + source ~/.cache/zlua.nu + alias z = _zlua + +- Power Shell: + + > ⚠️ **WARNING**: users of [Starship Prompt](https://starship.rs/) should add the following command *after* `starship init`. + + put something like this in your `profile.ps1`: + + Invoke-Expression (& { (lua /path/to/z.lua --init powershell) -join "`n" }) + +- Windows cmd (with clink): + + - Copy z.lua and z.cmd to clink's home directory + - Add clink's home to `%PATH%` (z.cmd can be called anywhere) + - Ensure that "lua" can be called in `%PATH%` + +- Windows cmder: + + - Copy z.lua and z.cmd to cmder/vendor + - Add cmder/vendor to `%PATH%` + - Ensure that "lua" can be called in `%PATH%` + +- Windows WSL-1: + + Install `lua-filesystem` module before init z.lua: + + sudo apt-get install lua-filesystem + + This module is required due to a wsl-1 [defect](https://github.com/microsoft/WSL/issues/5505). + + + +## Options + +- set `$_ZL_CMD` in .bashrc/.zshrc to change the command (default z). +- set `$_ZL_DATA` in .bashrc/.zshrc to change the datafile (default ~/.zlua). +- set `$_ZL_NO_PROMPT_COMMAND` if you're handling PROMPT_COMMAND yourself. +- set `$_ZL_EXCLUDE_DIRS` to a comma separated list of dirs to exclude. +- set `$_ZL_ADD_ONCE` to '1' to update database only if `$PWD` changed. +- set `$_ZL_MAXAGE` to define a aging threshold (default is 5000). +- set `$_ZL_CD` to specify your own cd command (default is `builtin cd` in Unix shells). +- set `$_ZL_ECHO` to 1 to display new directory name after cd. +- set `$_ZL_MATCH_MODE` to 1 to enable enhanced matching. +- set `$_ZL_NO_CHECK` to 1 to disable path validation, use `z --purge` to clean +- set `$_ZL_HYPHEN` to 0 to treat a hyphen (`-`) as a + [lua regexp special character](https://www.lua.org/pil/20.2.html), + set `$_ZL_HYPHEN` to 1 to treat a hyphen as a normal character. + If `$_ZL_HYPHEN` is not set or if it is set to `auto`, z.lua tries to treat `-` + as a lua regexp special character first. If there are no matches, z.lua tries + again, this time treating `-` as a normal character. +- set `$_ZL_CLINK_PROMPT_PRIORITY` change clink prompt register priority (default 99). + +## Aging + +The rank of directories maintained by z.lua undergoes aging based on a simple formula. The rank of each entry is incremented every time it is accessed. When the sum of ranks is over 5000 (`$_ZL_MAXAGE`), all ranks are multiplied by 0.9. Entries with a rank lower than 1 are forgotten. + + +## Frecency + +Frecency is a portmanteau of 'recent' and 'frequency'. It is a weighted rank that depends on how often and how recently something occurred. As far as I know, Mozilla came up with the term. + +To z.lua, a directory that has low ranking but has been accessed recently will quickly have higher rank than a directory accessed frequently a long time ago. Frecency is determined at runtime. + + +## Default Matching + +By default, `z.lua` uses default matching algorithm similar to the original `z.sh`. Paths must be match all of the regexes in order. + +- cd to a directory contains foo: + + z foo + +- use multiple arguments: + + Assuming the following database: + + 10 /home/user/work/inbox + 30 /home/user/mail/inbox + + `"z in"` would cd into `/home/user/mail/inbox` as the higher weighted entry. However you can pass multiple arguments to z.lua to prefer a different entry. In the above example, `"z w in"` would then change directory to `/home/user/work/inbox`. + +- use regexes: + + ```bash + z foo$ # cd to a directory ends with foo + z %d # cd to a directory that contains a digit + ``` + + Unlike `z.sh`, `z.lua` uses the [Lua regular expression syntax](https://www.lua.org/pil/20.2.html). + +## Enhanced Matching + +Enhanced matching can be enabled by exporting the environment: + +```bash +export _ZL_MATCH_MODE=1 +``` + +Or, append a `enhanced` after `--init xxx`: + +```bash +eval "$(lua /path/to/z.lua --init bash enhanced)" +``` + +For a given set of queries (the set of command-line arguments passed to z.lua), a path is a match if and only if: + +1. Queries match the path in order (same as default method). +2. The last query matches the last segment of the path. + +If no match is found, it will fall back to default matching method. + +- match the last segment of the path: + + Assuming the following database: + + 10 /home/user/workspace + 20 /home/user/workspace/project1 + 30 /home/user/workspace/project2 + 40 /home/user/workspace/project3 + + If you use `"z wo"` in enhanced matching mode, only the `/home/user/workspace` will be matched, because according to rule No.2 it is the only path whose last segment matches `"wo"`. + + Since the last segment of a path is always easier to be recalled, it is sane to give it higher priority. You can also achieve this by typing `"z space$"` in both methods, but `"z wo"` is easier to type. + + Tips for rule No.2: + + - If you want your last query **not only** to match the last segment of the path, append '$' as the last query. eg. `"z wo $"`. + - If you want your last query **not** to match the last segment of the path, append '/' as the last query. eg. `"z wo /"`. + + +- cd to the existent path if there is no match: + + Sometimes if you use: + + z foo + + And there is no matching result in the database, but there is an existent directory which can be accessed with the name "foo" from current directory, "`z foo`" will just work as: + + cd foo + + So, in the enhanced matching method, you can always use `z` like `cd` to change directory even if the new directory is untracked (hasn't been accessed). + +- Skip the current directory: + + When you are calling `z xxx` but the best match is the current directory, z.lua will choose the 2nd best match result for you. Assuming the database: + + 10 /Users/Great_Wall/.rbenv/versions/2.4.1/lib/ruby/gems + 20 /Library/Ruby/Gems/2.0.0/gems + + When I use `z gems` by default, it will take me to `/Library/Ruby/Gems/2.0.0/gems`, but it's not what I want, so I press up arrow and execute `z gems` again, it will take me to `/Users/Great_Wall/.rbenv/versions/2.4.1/lib/ruby/gems` and this what I want. + + Of course, I can always use `z env gems` to indicate what I want precisely. Skip the current directory means when you use `z xxx` you always want to change directory instead of stay in the same directory and do nothing if current directory is the best match. + +The default matching method is designed to be compatible with original z.sh, but the enhanced matching method is much more handy and exclusive to z.lua. + + +## Add Once + +By default, z.lua will add current directory to database each time before display command prompt (correspond with z.sh). But there is an option to allow z.lua add path only if current working directory changed. + +To enable this, you can set `$_ZL_ADD_ONCE` to `1` before init z.lua. Or you can initialize z.lua on linux like this: + +````bash +eval "$(lua /path/to/z.lua --init bash once)" +eval "$(lua /path/to/z.lua --init zsh once)" +lua /path/to/z.lua --init fish once | source +```` + +With `add once` mode off (default), z.lua will consider the time you spent in the directory (like z.sh). When this mode is on, consider the times you accessed the directory (like autojump), and that could be much faster on slow hardware. + + +## Interactive Selection + +When there are multiple matches found, using `z -i` will display a list: + +```bash +$ z -i soft +3: 0.25 /home/data/software +2: 3.75 /home/skywind/tmp/comma/software +1: 21 /home/skywind/software +> {CURSOR} +``` + +And then you can input the number and choose where to go before actual cd. eg. input 3 to cd to `/home/data/software`. And if you just press ENTER and input nothing, it will just quit and stay where you were. + +NOTE: for fish shell, this feature requires fish 2.7.0 or above. + + +## FZF Supports + +From version 1.1.0, a new option `"-I"` will allow you to use fzf to select when there are multiple matches. + +![](images/fzf.png) + +When we use `"z -I vim"`,12 paths contains keyword "vim" has been matched and ordered by their frecent value, the higher frecent comes with the higher rank. Then without cd to the highest ranked path, z.lua passes all the candidates to fzf. + +Now you can input some space separated keywords (no order required) or use `CTRL+J`/`CTRL+K` (same as `UP`/`DOWN`) to select where you want to go, or `ESC` / `CTRL`+`D`/`G` to give up. + +Of course, you can always give more keywords to `z` command to match your destination precisely. `"z -I"` is similar to `"z -i"`, but use fzf. Both `"-i"` and `"-I"` provide you another way for path navigation. + +Usually, `z -I` can be aliased to `zf` (z + fuzzy finder) for convenience. If there are only one path matched, `z -I` will jump to it directly, fzf will only be invoked for multiple matches. `"z -I ."` or `"zf ."` can be used to use fzf select from entire database. + +For more information about this, please visit [wiki - effective with fzf](https://github.com/skywind3000/z.lua/wiki/Effective-with-fzf). + +NOTE: For fish shell, this feature requires fish 2.7.0 or above. You can specify fzf executable in `$_ZL_FZF` environment variable, `"fzf"` will be called by default. + + +## Jump Backwards + +New option `"-b"` can quickly go back to a specific parent directory in bash instead of typing "cd ../../.." redundantly. + +- **(No argument)**: `cd` into the project root, the project root the nearest parent directory with `.git`/`.hg`/`.svn` in it. +- **(One argument)**: `cd` into the closest parent starting with keyword, if not find, go to the parent containing keyword. +- **(Two arguments)**: replace the first value with the second one (in the current path). + If simple substitution does not work, falls back to fuzzily replacing path components. + +Let's start by aliasing `z -b` to `zb`: + +```bash +# go all the way up to the project root (in this case, the one that has .git in it) +~/github/lorem/src/public$ zb + => cd ~/github/lorem + +# cd into to the first parent directory named g* +~/github/vimium/src/public$ zb g + => cd ~/github + +# goto the site directory quickly +~/github/demo/src/org/main/site/utils/file/reader/whatever$ zb si + => cd ~/github/demo/src/org/main/site + +# substitute jekyll with ghost +~/github/jekyll/test$ zb jekyll ghost + => cd ~/github/ghost/test + +# same as above, but fuzzy +~/github/jekyll/test$ zb jek gh + => z ~/github/ gh /test + => cd ~/github/ghost/test # Assuming that's the most frecent match +``` + +Backward jumping can also be used with `$_ZL_ECHO` option (echo $PWD after cd), which makes it possible to combine them with other tools without actually changing the working directory (eg. ``ls `zb git` ``). + +Environment variable `$_ZL_ROOT_MARKERS` is a comma separated list for project root locating, and can be redefined as: + +```bash +export _ZL_ROOT_MARKERS=".git,.svn,.hg,.root,package.json" +``` + +If you want `zb` jump back to a parent directory contains a `.root` or `package.json` in it. + +**Bonus**: `zb ..` equals to `cd ..`, `zb ...` equals to `cd ../..` and `zb ....` equals to `cd ../../..`, and so on. Finally, `zb ..20` equals to `cd (..)x20`. + +**Bonus**: try `z -b -i` and `z -b -I` and you can alias them to `zbi` and `zbf`. + +## Completion + +For zsh/fish, completion can be triggered by `z foo`. and a list of candidates will display in zsh / fish: + +![](images/complete-1.png) + +Press `` again, you can select your destination in a visualized way. + +Bash is not as powerful as zsh/fish, so we introduced fzf-completion for bash, initialize your z.lua and append `fzf` keyword after `--init`: + +```bash +eval "$(lua /path/to/z.lua --init bash enhanced once echo fzf)" +``` + +If you want use fzf completion in zsh, initalize your z.lua and append `fzf` keyword after `--init`: + +```zsh +eval "$(lua /path/to/z.lua --init zsh enhanced once echo fzf)" +``` + +Then press `` after `z xxx`: + +![](images/complete-2.png) + +With the help of fzf, completion in bash is much easier now. + +`z.lua` can cooperate with [fz](https://github.com/changyuheng/fz) for **better completion** result in both bash and zsh, for more information see [FAQ](https://github.com/skywind3000/z.lua/wiki/FAQ#fzsh-for-better-completion). + +NOTE: To enable this, command `fzf` must be found in `$PATH` before initialization. + + +## Most Recently Accessed Path + +`z.lua` provides a fast way to visit MRU directories without typing any keyword. That is `dirstack`, which records recently visited paths and can be manipulated by `z -`, `z --` and `z -{num}`: + +```bash +# display current dir stack +$ z -- + 0 /home/skywind/work/match/memory-match + 1 /home/skywind/.local/etc + 2 /home/skywind/software/vifm-0.9.1 + 3 /home/skywind/work + 4 /home/skywind/work/match + +# cd to the 2nd directory in the stack +$ z -2 + => cd /home/skywind/software/vifm-0.9.1 + +# popup stacktop (cd to previous directory), same as "z -0" +$ z - + => cd - +``` + +The `dirstack` is calculated from z.lua's database, and has no dependency on shells or systems. You will not lost records after re-login, and history can be shared across shells and sessions. + +There is another way to access MRU directories interactively by utilizing parameter `-I` (fzf) and `-t` (sort by time): + +```bash +alias zh='z -I -t .' +``` + +The new alias `zh` (jump to history) is very easy to input: + +![](images/mru.png) + +The first column indicates how many seconds ago you have visited, and the second column is the path name. With `zh`, you can type some character to use string matching in fzf, or use ``/`` (as well as `CTRL+j/k`) to move the selector (red `>`) up and down. + +At last, press `` to accept or `` to give up. + +Remember to enable the [enhanced matching](#enhanced-matching) algorithm, the current working directory can be skipped with it. + + +## Ranger integration +To add a `:z` command to the [`ranger` file manager], copy the `ranger_zlua.py` file to `~/.config/ranger/plugins/`. +You can then use `:z foo`, `:z -b foo`, etc. from ranger. Use `:z -h` for help. + +[`ranger` file manager]: https://github.com/ranger/ranger + +To define additional commands (`:zb` for example) in ranger, you can put `alias zb z -b` into `~/.config/ranger/rc.conf`. + + +## Tips + +Recommended aliases you may find useful: + +```bash +alias zz='z -c' # restrict matches to subdirs of $PWD +alias zi='z -i' # cd with interactive selection +alias zf='z -I' # use fzf to select in multiple matches +alias zb='z -b' # quickly cd to the parent directory +``` + +Import data from z.sh: + + +```bash +cat ~/.z >> ~/.zlua +``` + +Import data from autojump: + +```bash +FN="$HOME/.local/share/autojump/autojump.txt" +awk -F '\t' '{print $2 "|" $1 "|" 0}' $FN >> ~/.zlua +``` + +Don't forget to read the [Frequently Asked Questions](https://github.com/skywind3000/z.lua/wiki/FAQ). + + +## Benchmark + +The slowest part is adding path to history data file. It will run every time when you press enter (installed in $PROMPT_COMMAND). So I profile it on my NAS: + +```bash +$ time autojump --add /tmp +real 0m0.352s +user 0m0.077s +sys 0m0.185s + +$ time fasd -A /tmp +real 0m0.618s +user 0m0.076s +sys 0m0.242s + +$ time _z --add /tmp +real 0m0.194s +user 0m0.046s +sys 0m0.154s + +$ time _zlua --add /tmp +real 0m0.052s +user 0m0.015s +sys 0m0.030s +``` + +As you see, z.lua is the fastest one and requires less resource. + +## Native Module + +z.lua is fast enough for most case, the path tracking action will be triggered each time when you change your current directory. + +So I still recommend the pure lua script for portability and flexibility, but for someone who really care about `10ms` or `1ms` things, this module can help them to gain the ultimate speed. + +- [czmod](https://github.com/skywind3000/czmod): native module to boost `z.lua`. + +Average performance: + +| Name | czmod | z.lua | +|-|-|-| +| **Update Time** | 1.6ms | 13.2ms | +| **Query Time** | 1.5ms | 9.8ms | + + + +## History + +- 1.8.7 (2020-06-29): use lfs or luajit's cffi if possible. +- 1.8.4 (2020-02-10): fish shell: set `$_ZL_ECHO` to global scope. +- 1.8.3 (2020-02-09): new: `z -b -i` and `z -b -I` to jump backwards in interactive mode. +- 1.7.4 (2019-12-29): new: `$_ZL_HYPHEN` to treat hyphen as a normal character, see [here](https://github.com/skywind3000/z.lua/wiki/FAQ#how-to-input-a-hyphen---in-the-keyword-). +- 1.7.3 (2019-09-07): use [lua-filesystem](http://keplerproject.github.io/luafilesystem/) package if possible when `$_ZL_USE_LFS` is `1`. +- 1.7.2 (2019-08-01): Improve bash/zsh shell compatibility by [@barlik](https://github.com/barlik). +- 1.7.1 (2019-06-07): Fixed: `$_ZL_DATA` failure on Linux sometimes. +- 1.7.0 (2019-03-09): Support [ranger](https://github.com/skywind3000/z.lua/wiki/FAQ#how-to-integrate-zlua-to-ranger-), fix ReplaceFile issue in luajit (windows). +- 1.6.0 (2019-03-04): optimize with ffi module (luajit builtin module). +- 1.5.11 (2019-03-02): fixed: os.path.isdir doesn't work for symbol link folders. +- 1.5.10 (2019-03-01): Prevent writing file racing. +- 1.5.9 (2019-02-25): `z -b` should not match current directory (close #56). +- 1.5.8 (2019-02-21): new `$_ZL_FZF_HEIGHT` to control `--height` parameter in fzf. +- 1.5.7 (2019-02-21): rename `$_ZL_FZF_SORT` to `$_ZL_INT_SORT` it will affect both `-i` and `-I`. +- 1.5.6 (2019-02-20): set `$_ZL_FZF_SORT` to 1 to sort directories by alphabet in fzf. +- 1.5.5 (2019-02-20): `$_ZL_FZF_FLAG` can be used to override fzf flags, default to "+s -e". +- 1.5.4 (2019-02-19): fixed: file/path existence detection fails on read-only fs (closed [#49](https://github.com/skywind3000/z.lua/issues/49) by [@contrun](https://github.com/contrun)). +- 1.5.3 (2019-02-17): new `$_ZL_FZF_FLAG` for passing additional flags to fzf, add `-e` argument to fzf. +- 1.5.2 (2019-02-16): be aware of all arguments in fzf completion. +- 1.5.1 (2019-02-15): new: simulated dir stack by `z -`, `z --` and `z -{num}`. +- 1.5.0 (2019-02-14): fixed minor issues in backward jumping. +- 1.4.7 (2019-02-13): Don't use regex in backward jumping (use plain text instead). +- 1.4.6 (2019-02-12): change: `_ZL_EXCLUDE_DIRS` to a comma separated list of dirs to exclude. +- 1.4.5 (2019-02-10): improve bash fzf completion and posix compatibility. +- 1.4.4 (2019-02-10): supports legacy posix shells like ksh, init with `z.lua --init posix legacy`. +- 1.4.3 (2019-02-08): fixed minor issues. +- 1.4.2 (2019-02-06): you can disabled path validation by `$_ZL_NO_CHECK`, and use `z --purge` to clear bad paths manually. +- 1.4.1 (2019-02-06): fzf tab-completion in bash ([@BarbUk](https://github.com/BarbUk)), fixed hang in fish shell (close [#29](https://github.com/skywind3000/z.lua/issues/29)). +- 1.4.0 (2019-02-04): Ported to Power Shell ([@manhong2112](https://github.com/manhong2112)) +- 1.3.0 (2019-02-04): Backward jumping, prevent "cd ../../.." repeatly. +- 1.2.0 (2019-02-03): Upgrade string lib and path lib. +- 1.1.0 (2019-02-02): New option '-I' to use fzf to select from multiple matches. +- 1.0.0 (2019-02-01): Fixed minor issues and make it stable. +- 0.5.0 (2019-01-21): Ported to Fish Shell ([@TeddyDD](https://github.com/TeddyDD)). +- 0.4.1 (2019-01-20): Don't return failed exit code when $_ZL_ECHO is unbind (Mario Rodas). +- 0.4.0 (2019-01-17): new enhanced matching algorithm,can be enabled by appending `enhanced` keyword after `--init`. +- 0.3.0 (2018-12-26): new option `-i` to enable interactive selection. +- 0.2.0 (2018-11-25): new option `$_ZL_ADD_ONCE` to enable updating datafile only if `$PWD` changed. +- 0.1.0 (2018-04-30): supports windows cmd, cmder and conemu. +- 0.0.0 (2018-03-21): initial commit, compatible with original z.sh. + +## Help + +This project needs help for the tasks below: + +- [ ] Support csh/tcsh. +- [ ] Completion: Actually I got little knowledge in completion, and need help to improve it. +- [ ] Completion: Implement completion for Power Shell. +- [ ] Completion: Implement completion for different arguments. +- [ ] Packaging: make it possible to be installed easily in different systems or popular plugin managers. + + +## Thanks + +- Thanks to [@rupa](https://github.com/rupa) for inspiring me to start this project. +- Thanks to [@vigneshwaranr](https://github.com/vigneshwaranr) and [@shyiko](https://github.com/shyiko) for inspiring me the backward jumping. +- Thanks to [@TeddyDD](https://github.com/TeddyDD) for Fish Shell porting. +- Thanks to [@manhong2112](https://github.com/manhong2112) for Power Shell porting. +- Thanks to [@BarbUk](https://github.com/BarbUk) for fzf completion in Bash. +- Thanks to [@barlik](https://github.com/barlik) for many improvements. +- Thanks to [@brglng](https://github.com/brglng) for nushell porting. + +And many others. + + + +## License + +Licensed under MIT license. + diff --git a/.bin/z.lua/images/complete-1.png b/.bin/z.lua/images/complete-1.png new file mode 100644 index 0000000000000000000000000000000000000000..dbffc1053b96469b037442b06d59e9ee72bfccca GIT binary patch literal 5245 zcmc&&XH-*5*FNf{2!enVsZj~Nqk&5RK@1?hB%y>7dJ{oP=tWUVxFAI!w1|L|M4BKi zGz&!uy%1VRyb<9)wB-;eM2ch)&)X7)V$+0X2mwf0(rH8<5~V-a8h0D#Tl z7R(X=PM6U8EzD==|MP4yJoM%-&l@H;0N_O$>u+ZU`uLLTElU#sh!6*Wdk+ENfNr|C z3;-BK0Qlhq0MJ|j;JaH$vd{p4Q&@9jgf1OGrBdta>hO5HgM)*Qk56!L@T*s^Vq;^? z%*=9fasU$(ATtxNv3d6F8Ied#OiXNSYy_S@1-!h{($YvI5`>W%!UzC10N@M&+CWN5 zN`8Jmpsfu!I|Fy`0*vhO+~74K@Y+RwG%LLi8V~{xaC1Wd0L=|r69UE4eF1iY5ST!p zqFXL<1FZaOLi}g|fH1NXE`rgl+=PqtDfR&ldLV8z9iIdM`2c_e054!=W##1L|ogafq3%}!ti07h;I2mb&)E;kqenBy;kfhYiY3IGp)zP>&$FE1b|35be< z{87aXm;e zWsc|OCvbBUIM~q~paHs6M&)xN{O3D0 z>j4U*8<~O)OmvwRPYE-fVS9UaPz3-urww2?5MiIz@YsUS-Tb}CsWF>=7rPoBLob!r zQ@%PnpB{3Y=0@2Mndgjgc^MfRp(Hz&*BV191)9fOL&a*~)TpY^=+3Ss11p*6Ag{%p zfm;#Pn8LCJbuK307B~GhpB}sSz@0)~-#)8_*nIGsprez7&N4I4jnEjX)?~oSx53_? zV$!sfO83GdZg&f2_T#t)7vK_{gwJD-WTq!1}7q!~M3{_9P(pqAIn-q*!DIfM8Q|Aelj;i!#%T~S>- z{g|euNnawxZ&b8u19ao=JXcG=yCsykMcSQilk1{5qu>BT)tjU_OnmQjreKdRN} zNve@A&!?v+)zy#>d8j#52g~&EjPgrU8G%n?db5l3cCI9^)Piy$Zows z6r}9Dbs{S;bCATa`Q~-*#8h0pJQ%e^ev>X$ZX5KY=7PB{GS3AML&;t$dGN%}wG`y6 z!D_?MHW+`(bHKQl; z9fQsBFhRaNKgxAsSqmu3Y@v4Xkf&g19!%jR`4QI6S~dPsEA#^A;x!iRlR_46#`ma~ z^74_n6NQzgOqLOK-;*+AGkD=5jp1TOXa<_{Tm~B_wUP> zA?4DlS28$mHyXxYP!!HtP7=VedB^LZT&7F8s#{E}^a(cW)iX?S1y@~2x|?gt0oLTM;UO%uwxa!h2pE%2mwed?0KVRca=U4|uG zW%_4uEt4d66@}SE!)qR5hr5EOI|V)c^rN@>YL%660z+@j`f`>RBJFPlbME)wrmaM5 zy)tU6JHPJ=5 zykJ_=XIjA3-Tj3Sm_C96Jy6<2*b>VNGu`h_3*9U!vq|}sLvE2i+d$Ks`yHf`EUz(T zuayJy_xJrRHf4P zMn93TuCVk&6(_(3$9y5c{z{g-(2RC}0-A~8;ao4R^2up&R8g&b^i9-_RB@kr$UQzz z+(SLZ*jfHaJz~25!=@-NWtYgi?$M+P-e?SfztuS;;kLPNY4jVSV%=R|0Eb?0nTc%H#f1o&MH7?U?Yw;dgn36Je#5sJ~*G zF88T)xKk>#=o>>7XED;VXlmVwvI<<3B;pEl1nzP2R>YVG@g2+dW>nhzb-iWy8rW~3 z%g1jDl7A~pPIs_vcfI~SwAMUG?%!0V1eRtE9~w!I;-1`g*q4|iwQmY_1af@_jW8^9 z9S^E3pWa?L{JB{<+wZfz8EfN1LL4dZh_W3P1?@Yo_7oJ&xTaaz$@Vm-1�WHKQm` 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 IHQTWN0S=YkI{*Lx literal 0 HcmV?d00001 diff --git a/.bin/z.lua/images/fzf.png b/.bin/z.lua/images/fzf.png new file mode 100644 index 0000000000000000000000000000000000000000..43e68b1f0f6ead3329b2aa802fa1f866dead75d2 GIT binary patch literal 8674 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 JN)+B8{tHCD=)M2| literal 0 HcmV?d00001 diff --git a/.bin/z.lua/images/mru.png b/.bin/z.lua/images/mru.png new file mode 100644 index 0000000000000000000000000000000000000000..2e46d3ce3c099cf4d15339584667c30885674717 GIT binary patch literal 10665 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 literal 0 HcmV?d00001 diff --git a/.bin/z.lua/images/step1.png b/.bin/z.lua/images/step1.png new file mode 100644 index 0000000000000000000000000000000000000000..23229ee654346134d1faf07ec70615fb5581cec5 GIT binary patch literal 7710 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 Uz7_9|=>Wh$&*;W$9ed1w0ck49cK`qY literal 0 HcmV?d00001 diff --git a/.bin/z.lua/images/step3.png b/.bin/z.lua/images/step3.png new file mode 100644 index 0000000000000000000000000000000000000000..656c5d8e7a4ba14eed5d4aa3eb55bc376cdcceae GIT binary patch literal 3713 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 F{{W6ma5w+} literal 0 HcmV?d00001 diff --git a/.bin/z.lua/init.fish b/.bin/z.lua/init.fish new file mode 100644 index 0000000..cb2f5ad --- /dev/null +++ b/.bin/z.lua/init.fish @@ -0,0 +1,52 @@ +#! /usr/bin/env fish + +if test -z "$XDG_DATA_HOME" + set -U _ZL_DATA_DIR "$HOME/.local/share/zlua" +else + set -U _ZL_DATA_DIR "$XDG_DATA_HOME/zlua" +end + +set -x _ZL_DATA "$_ZL_DATA_DIR/zlua.txt" 2> /dev/null +set -U _ZL_DATA "$_ZL_DATA_DIR/zlua.txt" 2> /dev/null + +if test ! -e "$_ZL_DATA" + if test ! -e "$_ZL_DATA_DIR" + mkdir -p -m 700 "$_ZL_DATA_DIR" 2> /dev/null + end +end + +set -x _ZL_DATA "$_ZL_DATA" + +set -q XDG_DATA_HOME; or set XDG_DATA_HOME ~/.local/share +if functions -q fisher + set _zlua_dir $XDG_DATA_HOME/fisher/github.com/skywind3000/z.lua +else + set _zlua_dir (dirname (status --current-filename)) +end + +if test -e $_zlua_dir/z.lua + if type -q lua + lua $_zlua_dir/z.lua --init fish enhanced once echo | source + else if type -q luajit + luajit $_zlua_dir/z.lua --init fish enhanced once echo | source + else if type -q lua5.3 + lua5.3 $_zlua_dir/z.lua --init fish enhanced once echo | source + else if type -q lua5.2 + lua5.2 $_zlua_dir/z.lua --init fish enhanced once echo | source + else if type -q lua5.1 + lua5.1 $_zlua_dir/z.lua --init fish enhanced once echo | source + else + echo "init z.lua failed, not find lua in your system" + end + alias zc='z -c' # restrict matches to subdirs of $PWD + alias zz='z -i' # cd with interactive selection + alias zf='z -I' # use fzf to select in multiple matches + alias zb='z -b' # quickly cd to the parent directory + alias zbi='z -b -i' # interactive jump backward + alias zbf='z -b -I' # interactive jump backward with fzf + set -U ZLUA_SCRIPT "$ZLUA_SCRIPT" 2> /dev/null + set -U ZLUA_LUAEXE "$ZLUA_LUAEXE" 2> /dev/null +end + + + diff --git a/.bin/z.lua/ranger_zlua.py b/.bin/z.lua/ranger_zlua.py new file mode 100644 index 0000000..67389fc --- /dev/null +++ b/.bin/z.lua/ranger_zlua.py @@ -0,0 +1,91 @@ +import time, sys, os +import ranger.api +import subprocess + +# $RANGER_LUA and $RANGER_ZLUA variables are deprecated, do not use them. +ZLUA_LUAEXE = os.environ.get('RANGER_LUA') or os.environ.get('ZLUA_LUAEXE') +ZLUA_SCRIPT = os.environ.get('RANGER_ZLUA') or os.environ.get('ZLUA_SCRIPT') + +if not ZLUA_LUAEXE: + for path in os.environ.get('PATH', '').split(os.path.pathsep): + for name in ('lua', 'luajit', 'lua5.3', 'lua5.2', 'lua5.1'): + test = os.path.join(path, name) + test = test + (sys.platform[:3] == 'win' and ".exe" or "") + if os.path.exists(test): + ZLUA_LUAEXE = test + break + +def _report_error(msg): + sys.stderr.write('ranger_zlua: ' + msg) + raise RuntimeError(msg) + +if not ZLUA_LUAEXE: + _report_error('Please install lua in $PATH or make sure $ZLUA_LUAEXE points to a lua executable.\n') +if (not ZLUA_SCRIPT) or (not os.path.exists(ZLUA_SCRIPT)): + _report_error('Could not find z.lua, please make sure $ZLUA_SCRIPT is set to absolute path of z.lua.\n') + + +# Inform z.lua about directories the user browses to inside ranger +old_hook_init = ranger.api.hook_init + +def hook_init(fm): + def update_zlua(signal): + import os, random + os.environ['_ZL_RANDOM'] = str(random.randint(0, 0x7fffffff)) + p = subprocess.Popen([ZLUA_LUAEXE, ZLUA_SCRIPT, "--add", signal.new.path]) + p.wait() + if ZLUA_SCRIPT and ZLUA_LUAEXE and os.path.exists(ZLUA_SCRIPT): + fm.signal_bind('cd', update_zlua) + return old_hook_init(fm) + +ranger.api.hook_init = hook_init + + +class z(ranger.api.commands.Command): + def execute (self): + import sys, os, time + args = self.args[1:] + if args: + mode = '' + for arg in args: + if arg in ('-l', '-e', '-x', '-h', '--help', '--'): + mode = arg + break + elif arg in ('-I', '-i'): + mode = arg + elif arg[:1] != '-': + break + if mode: + cmd = '"%s" "%s" '%(ZLUA_LUAEXE, ZLUA_SCRIPT) + if mode in ('-I', '-i', '--'): + cmd += ' --cd' + for arg in args: + cmd += ' "%s"'%arg + if mode in ('-e', '-x'): + path = subprocess.check_output([ZLUA_LUAEXE, ZLUA_SCRIPT, '--cd'] + args) + path = path.decode("utf-8", "ignore") + path = path.rstrip('\n') + self.fm.notify(path) + elif mode in ('-h', '-l', '--help'): + p = self.fm.execute_command(cmd + '| less +G', universal_newlines=True) + stdout, stderr = p.communicate() + elif mode == '--': + p = self.fm.execute_command(cmd + ' 2>&1 | less +G', universal_newlines=True) + stdout, stderr = p.communicate() + else: + p = self.fm.execute_command(cmd, universal_newlines=True, stdout=subprocess.PIPE) + stdout, stderr = p.communicate() + path = stdout.rstrip('\n') + self.fm.execute_console('redraw_window') + if path and os.path.exists(path): + self.fm.cd(path) + else: + path = subprocess.check_output([ZLUA_LUAEXE, ZLUA_SCRIPT, '--cd'] + args) + path = path.decode("utf-8", "ignore") + path = path.rstrip('\n') + if path and os.path.exists(path): + self.fm.cd(path) + else: + self.fm.notify('No matching found', bad = True) + return True + diff --git a/.bin/z.lua/test_path.lua.rename b/.bin/z.lua/test_path.lua.rename new file mode 100644 index 0000000..2465ffb --- /dev/null +++ b/.bin/z.lua/test_path.lua.rename @@ -0,0 +1,193 @@ +local zmod = require('z') +local windows = os.path.sep == '\\' + +----------------------------------------------------------------------- +-- logo +----------------------------------------------------------------------- +function print_title(text) + print(string.rep('-', 72)) + print('-- '.. text) + print(string.rep('-', 72)) +end + + +----------------------------------------------------------------------- +-- os.path.normpath +----------------------------------------------------------------------- +print_title('os.path.normpath') + +function assert_posix(path, result) + local x = os.path.normpath(path) + print('[test] normpath: ('..path..') -> (' .. result .. ')') + if x:gsub('\\', '/') ~= result then + print('failed: "' .. x .. '" != "'..result.. '"') + os.exit() + else + print('passed') + print() + end +end + +function assert_windows(path, result) + local x = os.path.normpath(path) + print('[test] normpath: ('..path..') -> (' .. result .. ')') + if x ~= result then + print('failed: "' .. x .. '" != "'..result.. '"') + os.exit() + else + print('passed') + print() + end +end + +assert_posix("", ".") +assert_posix("/", "/") +assert_posix("///", "/") +assert_posix("///foo/.//bar//", "/foo/bar") +assert_posix("///foo/.//bar//.//..//.//baz", "/foo/baz") +assert_posix("///..//./foo/.//bar", "/foo/bar") + +if windows then + assert_windows('A//////././//.//B', 'A\\B') + assert_windows('A/./B', 'A\\B') + assert_windows('A/foo/../B', 'A\\B') + assert_windows('C:A//B', 'C:A\\B') + assert_windows('D:A/./B', 'D:A\\B') + assert_windows('e:A/foo/../B', 'e:A\\B') + assert_windows('C:///A//B', 'C:\\A\\B') + assert_windows('D:///A/./B', 'D:\\A\\B') + assert_windows('e:///A/foo/../B', 'e:\\A\\B') + assert_windows('..', '..') + assert_windows('.', '.') + assert_windows('', '.') + assert_windows('/', '\\') + assert_windows('c:/', 'c:\\') + assert_windows('/../.././..', '\\') + assert_windows('c:/../../..', 'c:\\') + assert_windows('../.././..', '..\\..\\..') + assert_windows('K:../.././..', 'K:..\\..\\..') + assert_windows('C:////a/b', 'C:\\a\\b') +end + +print() + + +----------------------------------------------------------------------- +-- os.path.join +----------------------------------------------------------------------- +print_title('os.path.join') + +function assert_join_posix(segments, result, isnt) + print('[test] join: '..zmod.dump(segments)..' -> (' .. result .. ')') + local path = '' + for _, item in ipairs(segments) do + path = os.path.join(path, item) + end + if windows and (not isnt) then + path = path:gsub('\\', '/') + end + if path ~= result then + print('failed: "' .. path .. '"') + os.exit() + else + print('passed') + end +end + +function assert_join_windows(segments, result) + assert_join_posix(segments, result, 1) +end + +assert_join_posix({"/foo", "bar", "/bar", "baz"}, "/bar/baz") +assert_join_posix({"/foo", "bar", "baz"}, "/foo/bar/baz") +assert_join_posix({"/foo/", "bar/", "baz/"}, "/foo/bar/baz/") + +if windows then + assert_join_windows({""}, '') + assert_join_windows({"", "", ""}, '') + assert_join_windows({"a"}, 'a') + assert_join_windows({"/a"}, '/a') + assert_join_windows({"\\a"}, '\\a') + assert_join_windows({"a:"}, 'a:') + assert_join_windows({"a:", "\\b"}, 'a:\\b') + assert_join_windows({"a", "\\b"}, '\\b') + assert_join_windows({"a", "b", "c"}, 'a\\b\\c') + assert_join_windows({"a\\", "b", "c"}, 'a\\b\\c') + assert_join_windows({"a", "b\\", "c"}, 'a\\b\\c') + assert_join_windows({"a", "b", "\\c"}, '\\c') + assert_join_windows({"d:\\", "\\pleep"}, 'd:\\pleep') + assert_join_windows({"d:\\", "a", "b"}, 'd:\\a\\b') + + assert_join_windows({'', 'a'}, 'a') + assert_join_windows({'', '', '', '', 'a'}, 'a') + assert_join_windows({'a', ''}, 'a\\') + assert_join_windows({'a', '', '', '', ''}, 'a\\') + assert_join_windows({'a\\', ''}, 'a\\') + assert_join_windows({'a\\', '', '', '', ''}, 'a\\') + assert_join_windows({'a/', ''}, 'a/') + + assert_join_windows({'a/b', 'x/y'}, 'a/b\\x/y') + assert_join_windows({'/a/b', 'x/y'}, '/a/b\\x/y') + assert_join_windows({'/a/b/', 'x/y'}, '/a/b/x/y') + assert_join_windows({'c:', 'x/y'}, 'c:x/y') + assert_join_windows({'c:a/b', 'x/y'}, 'c:a/b\\x/y') + assert_join_windows({'c:a/b/', 'x/y'}, 'c:a/b/x/y') + assert_join_windows({'c:/', 'x/y'}, 'c:/x/y') + assert_join_windows({'c:/a/b', 'x/y'}, 'c:/a/b\\x/y') + assert_join_windows({'c:/a/b/', 'x/y'}, 'c:/a/b/x/y') + + assert_join_windows({'a/b', '/x/y'}, '/x/y') + assert_join_windows({'/a/b', '/x/y'}, '/x/y') + assert_join_windows({'c:', '/x/y'}, 'c:/x/y') + assert_join_windows({'c:a/b', '/x/y'}, 'c:/x/y') + assert_join_windows({'c:/', '/x/y'}, 'c:/x/y') + assert_join_windows({'c:/a/b', '/x/y'}, 'c:/x/y') + + assert_join_windows({'c:', 'C:x/y'}, 'C:x/y') + assert_join_windows({'c:a/b', 'C:x/y'}, 'C:a/b\\x/y') + assert_join_windows({'c:/', 'C:x/y'}, 'C:/x/y') + assert_join_windows({'c:/a/b', 'C:x/y'}, 'C:/a/b\\x/y') + + for _, x in ipairs({'', 'a/b', '/a/b', 'c:', 'c:a/b', 'c:/', 'c:/a/b'}) do + for _, y in ipairs({'d:', 'd:x/y', 'd:/', 'd:/x/y'}) do + assert_join_windows({x, y}, y) + end + end +end + +print() + + +----------------------------------------------------------------------- +-- os.path.split +----------------------------------------------------------------------- +print_title('os.path.split') +function assert_split(path, sep1, sep2) + print('[test] split: "' .. path ..'" -> ("' .. sep1 .. '", "' .. sep2 .. '")') + local x, y = os.path.split(path) + if x ~= sep1 or y ~= sep2 then + print('failed: ("'..x..'", "'..y..'")') + os.exit() + else + print('passed') + end +end + +assert_split("", "", "") +assert_split(".", "", ".") +assert_split("/foo/bar", "/foo", "bar") +assert_split("/", "/", "") +assert_split("foo", "", "foo") +assert_split("////foo", "////", "foo") +assert_split("//foo//bar", "//foo", "bar") + +if windows then + assert_split("c:\\foo\\bar", 'c:\\foo', 'bar') + assert_split("\\\\conky\\mountpoint\\foo\\bar", '\\\\conky\\mountpoint\\foo', 'bar') + assert_split("c:\\", "c:\\", '') + assert_split("c:/", "c:/", '') + assert_split("c:test", "c:", 'test') + assert_split("c:", "c:", '') + -- assert_split("\\\\conky\\mountpoint\\", "\\\\conky\\mountpoint\\", '') +end + diff --git a/.bin/z.lua/z.cmd b/.bin/z.lua/z.cmd new file mode 100644 index 0000000..f821238 --- /dev/null +++ b/.bin/z.lua/z.cmd @@ -0,0 +1,130 @@ +@echo off +setlocal EnableDelayedExpansion + +set "LuaExe=lua" +set "LuaScript=%~dp0z.lua" +set "MatchType=-n" +set "StrictSub=-n" +set "RunMode=-n" +set "StripMode=" +set "InterMode=" + +if /i not "%_ZL_LUA_EXE%"=="" ( + set "LuaExe=%_ZL_LUA_EXE%" +) + + +:parse + +if /i "%1"=="-r" ( + set "MatchType=-r" + shift /1 + goto parse +) + +if /i "%1"=="-t" ( + set "MatchType=-t" + shift /1 + goto parse +) + +if /i "%1"=="-c" ( + set "StrictSub=-c" + shift /1 + goto parse +) + +if /i "%1"=="-l" ( + set "RunMode=-l" + shift /1 + goto parse +) + +if /i "%1"=="-e" ( + set "RunMode=-e" + shift /1 + goto parse +) + +if /i "%1"=="-x" ( + set "RunMode=-x" + shift /1 + goto parse +) + +if /i "%1"=="--add" ( + set "RunMode=--add" + shift /1 + goto parse +) + +if "%1"=="-i" ( + set "InterMode=-i" + shift /1 + goto parse +) + +if "%1"=="-I" ( + set "InterMode=-I" + shift /1 + goto parse +) + +if /i "%1"=="-s" ( + set "StripMode=-s" + shift /1 + goto parse +) + +if /i "%1"=="-h" ( + call "%LuaExe%" "%LuaScript%" -h + goto end +) + +if /i "%1"=="--purge" ( + call "%LuaExe%" "%LuaScript%" --purge + goto end +) + +:check + +if /i "%1"=="" ( + set "RunMode=-l" +) + +for /f "delims=" %%i in ('cd') do set "PWD=%%i" + +if /i "%RunMode%"=="-n" ( + for /f "delims=" %%i in ('call "%LuaExe%" "%LuaScript%" --cd %MatchType% %StrictSub% %InterMode% %*') do set "NewPath=%%i" + if not "!NewPath!"=="" ( + if exist !NewPath!\nul ( + if /i not "%_ZL_ECHO%"=="" ( + echo !NewPath! + ) + pushd !NewPath! + pushd !NewPath! + endlocal + goto popdir + ) + ) +) else ( + call "%LuaExe%" "%LuaScript%" "%RunMode%" %MatchType% %StrictSub% %InterMode% %StripMode% %* +) + +goto end + +:popdir +rem -- Exploits variable expansion and the pushd stack to set the current +rem -- directory without leaking a pushd. +popd +setlocal +set "NewPath=%CD%" +set "CDCmd=cd /d" +if /i not "%_ZL_CD%"=="" ( + set "CDCmd=%_ZL_CD%" +) +endlocal & popd & %CDCmd% "%NewPath%" + +:end +echo. + diff --git a/.bin/z.lua/z.lua b/.bin/z.lua/z.lua new file mode 100755 index 0000000..40b76dc --- /dev/null +++ b/.bin/z.lua/z.lua @@ -0,0 +1,2990 @@ +#! /usr/bin/env lua +--===================================================================== +-- +-- z.lua - a cd command that learns, by skywind 2018-2022 +-- Licensed under MIT license. +-- +-- Version 1.8.18, Last Modified: 2024/04/30 17:11 +-- +-- * 10x faster than fasd and autojump, 3x faster than z.sh +-- * available for posix shells: bash, zsh, sh, ash, dash, busybox +-- * available for fish shell, power shell and windows cmd +-- * compatible with lua 5.1, 5.2 and 5.3+ +-- +-- USE: +-- * z foo # cd to most frecent dir matching foo +-- * z foo bar # cd to most frecent dir matching foo and bar +-- * z -r foo # cd to highest ranked dir matching foo +-- * z -t foo # cd to most recently accessed dir matching foo +-- * z -l foo # list matches instead of cd +-- * z -c foo # restrict matches to subdirs of $PWD +-- * z -e foo # echo the best match, don't cd +-- * z -x path # remove path from history +-- * z -i foo # cd with interactive selection +-- * z -I foo # cd with interactive selection using fzf +-- * z -b foo # cd to the parent directory starting with foo +-- * z -b foo bar # replace foo with bar in cwd and cd there +-- +-- Bash Install: +-- * put something like this in your .bashrc: +-- eval "$(lua /path/to/z.lua --init bash)" +-- +-- Bash Enhanced Mode: +-- * put something like this in your .bashrc: +-- eval "$(lua /path/to/z.lua --init bash enhanced)" +-- +-- Bash fzf tab completion Mode: +-- * put something like this in your .bashrc: +-- eval "$(lua /path/to/z.lua --init bash fzf)" +-- +-- Zsh Install: +-- * put something like this in your .zshrc: +-- eval "$(lua /path/to/z.lua --init zsh)" +-- +-- Posix Shell Install: +-- * put something like this in your .profile: +-- eval "$(lua /path/to/z.lua --init posix)" +-- +-- Fish Shell Install: +-- * put something like this in your config file: +-- lua /path/to/z.lua --init fish | source +-- +-- Power Shell Install: +-- * put something like this in your config file: +-- Invoke-Expression (& { +-- (lua /path/to/z.lua --init powershell) -join "`n" }) +-- +-- Windows Install (with Clink): +-- * copy z.lua and z.cmd to clink's home directory +-- * Add clink's home to %PATH% (z.cmd can be called anywhere) +-- * Ensure that "lua" can be called in %PATH% +-- +-- Windows Cmder Install: +-- * copy z.lua and z.cmd to cmder/vendor +-- * Add cmder/vendor to %PATH% +-- * Ensure that "lua" can be called in %PATH% +-- +-- Windows WSL-1: +-- * Install lua-filesystem module before init z.lua: +-- sudo apt-get install lua-filesystem +-- +-- Configure (optional): +-- set $_ZL_CMD in .bashrc/.zshrc to change the command (default z). +-- set $_ZL_DATA in .bashrc/.zshrc to change the datafile (default ~/.zlua). +-- set $_ZL_NO_PROMPT_COMMAND if you're handling PROMPT_COMMAND yourself. +-- set $_ZL_EXCLUDE_DIRS to a comma separated list of dirs to exclude. +-- set $_ZL_ADD_ONCE to 1 to update database only if $PWD changed. +-- set $_ZL_CD to specify your own cd command +-- set $_ZL_ECHO to 1 to display new directory name after cd. +-- set $_ZL_MAXAGE to define a aging threshold (default is 5000). +-- set $_ZL_MATCH_MODE to 1 to enable enhanced matching mode. +-- set $_ZL_NO_CHECK to 1 to disable path validation. z --purge to clear. +-- set $_ZL_USE_LFS to 1 to use lua-filesystem package +-- set $_ZL_HYPHEN to 1 to stop treating hyphen as a regexp keyword +-- +--===================================================================== + + +----------------------------------------------------------------------- +-- Module Header +----------------------------------------------------------------------- +local modname = 'z' +local MM = {} +_G[modname] = MM +package.loaded[modname] = MM --return modname +setmetatable(MM, {__index = _G}) + +if _ENV ~= nil then + _ENV[modname] = MM +else + setfenv(1, MM) +end + + +----------------------------------------------------------------------- +-- Environment +----------------------------------------------------------------------- +local windows = package.config:sub(1, 1) ~= '/' and true or false +local in_module = pcall(debug.getlocal, 4, 1) and true or false +local utils = {} +os.path = {} +os.argv = arg ~= nil and arg or {} +os.path.sep = windows and '\\' or '/' + + +----------------------------------------------------------------------- +-- Global Variable +----------------------------------------------------------------------- +MAX_AGE = 5000 +DATA_FILE = '~/.zlua' +PRINT_MODE = '' +PWD = '' +Z_METHOD = 'frecent' +Z_SUBDIR = false +Z_INTERACTIVE = 0 +Z_EXCLUDE = {} +Z_CMD = 'z' +Z_MATCHMODE = 0 +Z_MATCHNAME = false +Z_SKIPPWD = false +Z_HYPHEN = "auto" +Z_DATA_SEPARATOR = "|" + +os.LOG_NAME = os.getenv('_ZL_LOG_NAME') + + +----------------------------------------------------------------------- +-- string lib +----------------------------------------------------------------------- +function string:split(sSeparator, nMax, bRegexp) + assert(sSeparator ~= '') + assert(nMax == nil or nMax >= 1) + local aRecord = {} + if self:len() > 0 then + local bPlain = not bRegexp + nMax = nMax or -1 + local nField, nStart = 1, 1 + local nFirst, nLast = self:find(sSeparator, nStart, bPlain) + while nFirst and nMax ~= 0 do + aRecord[nField] = self:sub(nStart, nFirst - 1) + nField = nField + 1 + nStart = nLast + 1 + nFirst, nLast = self:find(sSeparator, nStart, bPlain) + nMax = nMax - 1 + end + aRecord[nField] = self:sub(nStart) + else + aRecord[1] = '' + end + return aRecord +end + +function string:startswith(text) + local size = text:len() + if self:sub(1, size) == text then + return true + end + return false +end + +function string:endswith(text) + return text == "" or self:sub(-#text) == text +end + +function string:lstrip() + if self == nil then return nil end + local s = self:gsub('^%s+', '') + return s +end + +function string:rstrip() + if self == nil then return nil end + local s = self:gsub('%s+$', '') + return s +end + +function string:strip() + return self:lstrip():rstrip() +end + +function string:rfind(key) + if key == '' then + return self:len(), 0 + end + local length = self:len() + local start, ends = self:reverse():find(key:reverse(), 1, true) + if start == nil then + return nil + end + return (length - ends + 1), (length - start + 1) +end + +function string:join(parts) + if parts == nil or #parts == 0 then + return '' + end + local size = #parts + local text = '' + local index = 1 + while index <= size do + if index == 1 then + text = text .. parts[index] + else + text = text .. self .. parts[index] + end + index = index + 1 + end + return text +end + + +----------------------------------------------------------------------- +-- table size +----------------------------------------------------------------------- +function table.length(T) + local count = 0 + if T == nil then return 0 end + for _ in pairs(T) do count = count + 1 end + return count +end + + +----------------------------------------------------------------------- +-- print table +----------------------------------------------------------------------- +function dump(o) + if type(o) == 'table' then + local s = '{ ' + for k,v in pairs(o) do + if type(k) ~= 'number' then k = '"'..k..'"' end + s = s .. '['..k..'] = ' .. dump(v) .. ',' + end + return s .. '} ' + else + return tostring(o) + end +end + + +----------------------------------------------------------------------- +-- print table +----------------------------------------------------------------------- +function printT(table, level) + key = "" + local func = function(table, level) end + func = function(table, level) + level = level or 1 + local indent = "" + for i = 1, level do + indent = indent.." " + end + if key ~= "" then + print(indent..key.." ".."=".." ".."{") + else + print(indent .. "{") + end + + key = "" + for k, v in pairs(table) do + if type(v) == "table" then + key = k + func(v, level + 1) + else + local content = string.format("%s%s = %s", indent .. " ",tostring(k), tostring(v)) + print(content) + end + end + print(indent .. "}") + end + func(table, level) +end + + +----------------------------------------------------------------------- +-- invoke command and retrive output +----------------------------------------------------------------------- +function os.call(command) + local fp = io.popen(command) + if fp == nil then + return nil + end + local line = fp:read('*l') + fp:close() + return line +end + + +----------------------------------------------------------------------- +-- write log +----------------------------------------------------------------------- +function os.log(text) + if not os.LOG_NAME then + return + end + local fp = io.open(os.LOG_NAME, 'a') + if not fp then + return + end + local date = "[" .. os.date('%Y-%m-%d %H:%M:%S') .. "] " + fp:write(date .. text .. "\n") + fp:close() +end + + +----------------------------------------------------------------------- +-- ffi optimize (luajit has builtin ffi module) +----------------------------------------------------------------------- +os.native = {} +os.native.status, os.native.ffi = pcall(require, "ffi") +if os.native.status then + local ffi = os.native.ffi + if windows then + ffi.cdef[[ + int GetFullPathNameA(const char *name, uint32_t size, char *out, char **name); + int ReplaceFileA(const char *dstname, const char *srcname, void *, uint32_t, void *, void *); + uint32_t GetTickCount(void); + uint32_t GetFileAttributesA(const char *name); + uint32_t GetCurrentDirectoryA(uint32_t size, char *ptr); + uint32_t GetShortPathNameA(const char *longname, char *shortname, uint32_t size); + uint32_t GetLongPathNameA(const char *shortname, char *longname, uint32_t size); + ]] + local kernel32 = ffi.load('kernel32.dll') + local buffer = ffi.new('char[?]', 4100) + local INVALID_FILE_ATTRIBUTES = 0xffffffff + local FILE_ATTRIBUTE_DIRECTORY = 0x10 + os.native.kernel32 = kernel32 + function os.native.GetFullPathName(name) + local hr = kernel32.GetFullPathNameA(name, 4096, buffer, nil) + return (hr > 0) and ffi.string(buffer, hr) or nil + end + function os.native.ReplaceFile(replaced, replacement) + local hr = kernel32.ReplaceFileA(replaced, replacement, nil, 2, nil, nil) + return (hr ~= 0) and true or false + end + function os.native.GetTickCount() + return kernel32.GetTickCount() + end + function os.native.GetFileAttributes(name) + return kernel32.GetFileAttributesA(name) + end + function os.native.GetLongPathName(name) + local hr = kernel32.GetLongPathNameA(name, buffer, 4096) + return (hr ~= 0) and ffi.string(buffer, hr) or nil + end + function os.native.GetShortPathName(name) + local hr = kernel32.GetShortPathNameA(name, buffer, 4096) + return (hr ~= 0) and ffi.string(buffer, hr) or nil + end + function os.native.GetRealPathName(name) + local short = os.native.GetShortPathName(name) + if short then + return os.native.GetLongPathName(short) + end + return nil + end + function os.native.exists(name) + local attr = os.native.GetFileAttributes(name) + return attr ~= INVALID_FILE_ATTRIBUTES + end + function os.native.isdir(name) + local attr = os.native.GetFileAttributes(name) + local isdir = FILE_ATTRIBUTE_DIRECTORY + if attr == INVALID_FILE_ATTRIBUTES then + return false + end + return (attr % (2 * isdir)) >= isdir + end + function os.native.getcwd() + local hr = kernel32.GetCurrentDirectoryA(4096, buffer) + if hr <= 0 then return nil end + return ffi.string(buffer, hr) + end + else + ffi.cdef[[ + typedef struct { long tv_sec; long tv_usec; } timeval; + int gettimeofday(timeval *tv, void *tz); + int access(const char *name, int mode); + char *realpath(const char *path, char *resolve); + char *getcwd(char *buf, size_t size); + ]] + local timeval = ffi.new('timeval[?]', 1) + local buffer = ffi.new('char[?]', 4100) + function os.native.gettimeofday() + local hr = ffi.C.gettimeofday(timeval, nil) + local sec = tonumber(timeval[0].tv_sec) + local usec = tonumber(timeval[0].tv_usec) + return sec + (usec * 0.000001) + end + function os.native.access(name, mode) + return ffi.C.access(name, mode) + end + function os.native.realpath(name) + local path = ffi.C.realpath(name, buffer) + return (path ~= nil) and ffi.string(buffer) or nil + end + function os.native.getcwd() + local hr = ffi.C.getcwd(buffer, 4099) + return hr ~= nil and ffi.string(buffer) or nil + end + end + function os.native.tickcount() + if windows then + return os.native.GetTickCount() + else + return math.floor(os.native.gettimeofday() * 1000) + end + end + os.native.init = true +end + + +----------------------------------------------------------------------- +-- get current path +----------------------------------------------------------------------- +function os.pwd() + if os.native and os.native.getcwd then + local hr = os.native.getcwd() + if hr then return hr end + end + if os.getcwd then + return os.getcwd() + end + if windows then + local fp = io.popen('cd') + if fp == nil then + return '' + end + local line = fp:read('*l') + fp:close() + return line + else + local fp = io.popen('pwd') + if fp == nil then + return '' + end + local line = fp:read('*l') + fp:close() + return line + end +end + + +----------------------------------------------------------------------- +-- which executable +----------------------------------------------------------------------- +function os.path.which(exename) + local path = os.getenv('PATH') + if windows then + paths = ('.;' .. path):split(';') + else + paths = path:split(':') + end + for _, path in pairs(paths) do + if not windows then + local name = path .. '/' .. exename + if os.path.exists(name) then + return name + end + else + for _, ext in pairs({'.exe', '.cmd', '.bat'}) do + local name = path .. '\\' .. exename .. ext + if path == '.' then + name = exename .. ext + end + if os.path.exists(name) then + return name + end + end + end + end + return nil +end + + +----------------------------------------------------------------------- +-- absolute path (simulated) +----------------------------------------------------------------------- +function os.path.absolute(path) + local pwd = os.pwd() + return os.path.normpath(os.path.join(pwd, path)) +end + + +----------------------------------------------------------------------- +-- absolute path (system call, can fall back to os.path.absolute) +----------------------------------------------------------------------- +function os.path.abspath(path) + if path == '' then path = '.' end + if os.native and os.native.GetFullPathName then + local test = os.native.GetFullPathName(path) + if test then return test end + end + if windows then + local script = 'FOR /f "delims=" %%i IN ("%s") DO @echo %%~fi' + local script = string.format(script, path) + local script = 'cmd.exe /C ' .. script .. ' 2> nul' + local output = os.call(script) + local test = output:gsub('%s$', '') + if test ~= nil and test ~= '' then + return test + end + else + local test = os.path.which('realpath') + if test ~= nil and test ~= '' then + test = os.call('realpath -s \'' .. path .. '\' 2> /dev/null') + if test ~= nil and test ~= '' then + return test + end + test = os.call('realpath \'' .. path .. '\' 2> /dev/null') + if test ~= nil and test ~= '' then + return test + end + end + local test = os.path.which('perl') + if test ~= nil and test ~= '' then + local s = 'perl -MCwd -e "print Cwd::realpath(\\$ARGV[0])" \'%s\'' + local s = string.format(s, path) + test = os.call(s) + if test ~= nil and test ~= '' then + return test + end + end + for _, python in pairs({'python3', 'python2', 'python'}) do + local s = 'sys.stdout.write(os.path.abspath(sys.argv[1]))' + local s = '-c "import os, sys;' .. s .. '" \'' .. path .. '\'' + local s = python .. ' ' .. s + local test = os.path.which(python) + if test ~= nil and test ~= '' then + test = os.call(s) + if test ~= nil and test ~= '' then + return test + end + end + end + end + return os.path.absolute(path) +end + + +----------------------------------------------------------------------- +-- dir exists +----------------------------------------------------------------------- +function os.path.isdir(pathname) + if pathname == '/' then + return true + elseif pathname == '' then + return false + elseif windows then + if pathname == '\\' then + return true + end + end + if os.native and os.native.isdir then + return os.native.isdir(pathname) + end + if clink and os.isdir then + return os.isdir(pathname) + end + local name = pathname + if (not name:endswith('/')) and (not name:endswith('\\')) then + name = name .. os.path.sep + end + return os.path.exists(name) +end + + +----------------------------------------------------------------------- +-- file or path exists +----------------------------------------------------------------------- +function os.path.exists(name) + if name == '/' then + return true + end + if os.native and os.native.exists then + return os.native.exists(name) + end + local ok, err, code = os.rename(name, name) + if not ok then + if code == 13 or code == 17 then + return true + elseif code == 30 then + local f = io.open(name,"r") + if f ~= nil then + io.close(f) + return true + end + elseif name:sub(-1) == '/' and code == 20 and (not windows) then + local test = name .. '.' + ok, err, code = os.rename(test, test) + if code == 16 or code == 13 or code == 22 then + return true + end + end + return false + end + return true +end + + +----------------------------------------------------------------------- +-- is absolute path +----------------------------------------------------------------------- +function os.path.isabs(path) + if path == nil or path == '' then + return false + elseif path:sub(1, 1) == '/' then + return true + end + if windows then + local head = path:sub(1, 1) + if head == '\\' then + return true + elseif path:match('^%a:[/\\]') ~= nil then + return true + end + end + return false +end + + +----------------------------------------------------------------------- +-- normalize path +----------------------------------------------------------------------- +function os.path.norm(pathname) + if windows then + pathname = pathname:gsub('\\', '/') + end + if windows then + pathname = pathname:gsub('/', '\\') + end + return pathname +end + + +----------------------------------------------------------------------- +-- normalize . and .. +----------------------------------------------------------------------- +function os.path.normpath(path) + if os.path.sep ~= '/' then + path = path:gsub('\\', '/') + end + path = path:gsub('/+', '/') + local srcpath = path + local basedir = '' + local isabs = false + if windows and path:sub(2, 2) == ':' then + basedir = path:sub(1, 2) + path = path:sub(3, -1) + end + if path:sub(1, 1) == '/' then + basedir = basedir .. '/' + isabs = true + path = path:sub(2, -1) + end + local parts = path:split('/') + local output = {} + for _, path in ipairs(parts) do + if path == '.' or path == '' then + elseif path == '..' then + local size = #output + if size == 0 then + if not isabs then + table.insert(output, '..') + end + elseif output[size] == '..' then + table.insert(output, '..') + else + table.remove(output, size) + end + else + table.insert(output, path) + end + end + path = basedir .. string.join('/', output) + if windows then path = path:gsub('/', '\\') end + return path == '' and '.' or path +end + + +----------------------------------------------------------------------- +-- join two path +----------------------------------------------------------------------- +function os.path.join(path1, path2) + if path1 == nil or path1 == '' then + if path2 == nil or path2 == '' then + return '' + else + return path2 + end + elseif path2 == nil or path2 == '' then + local head = path1:sub(-1, -1) + if head == '/' or (windows and head == '\\') then + return path1 + end + return path1 .. os.path.sep + elseif os.path.isabs(path2) then + if windows then + local head = path2:sub(1, 1) + if head == '/' or head == '\\' then + if path1:match('^%a:') then + return path1:sub(1, 2) .. path2 + end + end + end + return path2 + elseif windows then + local d1 = path1:match('^%a:') and path1:sub(1, 2) or '' + local d2 = path2:match('^%a:') and path2:sub(1, 2) or '' + if d1 ~= '' then + if d2 ~= '' then + if d1:lower() == d2:lower() then + return d2 .. os.path.join(path1:sub(3), path2:sub(3)) + else + return path2 + end + end + elseif d2 ~= '' then + return path2 + end + end + local postsep = true + local len1 = path1:len() + local len2 = path2:len() + if path1:sub(-1, -1) == '/' then + postsep = false + elseif windows then + if path1:sub(-1, -1) == '\\' then + postsep = false + elseif len1 == 2 and path1:sub(2, 2) == ':' then + postsep = false + end + end + if postsep then + return path1 .. os.path.sep .. path2 + else + return path1 .. path2 + end +end + + +----------------------------------------------------------------------- +-- split +----------------------------------------------------------------------- +function os.path.split(path) + if path == '' then + return '', '' + end + local pos = path:rfind('/') + if os.path.sep == '\\' then + local p2 = path:rfind('\\') + if pos == nil and p2 ~= nil then + pos = p2 + elseif pos ~= nil and p2 ~= nil then + pos = (pos < p2) and pos or p2 + end + if path:match('^%a:[/\\]') and pos == nil then + return path:sub(1, 2), path:sub(3) + end + end + if pos == nil then + if windows then + local drive = path:match('^%a:') and path:sub(1, 2) or '' + if drive ~= '' then + return path:sub(1, 2), path:sub(3) + end + end + return '', path + elseif pos == 1 then + return path:sub(1, 1), path:sub(2) + elseif windows then + local drive = path:match('^%a:') and path:sub(1, 2) or '' + if pos == 3 and drive ~= '' then + return path:sub(1, 3), path:sub(4) + end + end + local head = path:sub(1, pos) + local tail = path:sub(pos + 1) + if not windows then + local test = string.rep('/', head:len()) + if head ~= test then + head = head:gsub('/+$', '') + end + else + local t1 = string.rep('/', head:len()) + local t2 = string.rep('\\', head:len()) + if head ~= t1 and head ~= t2 then + head = head:gsub('[/\\]+$', '') + end + end + return head, tail +end + + +----------------------------------------------------------------------- +-- check subdir +----------------------------------------------------------------------- +function os.path.subdir(basename, subname) + if windows then + basename = basename:gsub('\\', '/') + subname = subname:gsub('\\', '/') + basename = basename:lower() + subname = subname:lower() + end + local last = basename:sub(-1, -1) + if last ~= '/' then + basename = basename .. '/' + end + if subname:find(basename, 0, true) == 1 then + return true + end + return false +end + + +----------------------------------------------------------------------- +-- check single name element +----------------------------------------------------------------------- +function os.path.single(path) + if string.match(path, '/') then + return false + end + if windows then + if string.match(path, '\\') then + return false + end + end + return true +end + + +----------------------------------------------------------------------- +-- expand user home +----------------------------------------------------------------------- +function os.path.expand(pathname) + if not pathname:find('~') then + return pathname + end + local home = '' + if windows then + home = os.getenv('USERPROFILE') + else + home = os.getenv('HOME') + end + if pathname == '~' then + return home + end + local head = pathname:sub(1, 2) + if windows then + if head == '~/' or head == '~\\' then + return home .. '\\' .. pathname:sub(3, -1) + end + elseif head == '~/' then + return home .. '/' .. pathname:sub(3, -1) + end + return pathname +end + + +----------------------------------------------------------------------- +-- search executable +----------------------------------------------------------------------- +function os.path.search(name) +end + + +----------------------------------------------------------------------- +-- get lua executable +----------------------------------------------------------------------- +function os.interpreter() + if os.argv == nil then + io.stderr:write("cannot get arguments (arg), recompiled your lua\n") + return nil + end + local lua = os.argv[-1] + if lua == nil then + io.stderr:write("cannot get executable name, recompiled your lua\n") + return nil + end + if os.path.single(lua) then + local path = os.path.which(lua) + if not os.path.isabs(path) then + return os.path.abspath(path) + end + return path + end + return os.path.abspath(lua) +end + + +----------------------------------------------------------------------- +-- get script name +----------------------------------------------------------------------- +function os.scriptname() + if os.argv == nil then + io.stderr:write("cannot get arguments (arg), recompiled your lua\n") + return nil + end + local script = os.argv[0] + if script == nil then + io.stderr:write("cannot get script name, recompiled your lua\n") + end + return os.path.abspath(script) +end + + +----------------------------------------------------------------------- +-- get environ +----------------------------------------------------------------------- +function os.environ(name, default) + local value = os.getenv(name) + if os.envmap ~= nil and type(os.envmap) == 'table' then + local t = os.envmap[name] + value = (t ~= nil and type(t) == 'string') and t or value + end + if value == nil then + return default + elseif type(default) == 'boolean' then + value = value:lower() + if value == '0' or value == '' or value == 'no' then + return false + elseif value == 'false' or value == 'n' or value == 'f' then + return false + else + return true + end + elseif type(default) == 'number' then + value = tonumber(value) + if value == nil then + return default + else + return value + end + elseif type(default) == 'string' then + return value + elseif type(default) == 'table' then + return value:sep(',') + end +end + + +----------------------------------------------------------------------- +-- parse option +----------------------------------------------------------------------- +function os.getopt(argv) + local args = {} + local options = {} + argv = argv ~= nil and argv or os.argv + if argv == nil then + return nil, nil + elseif (#argv) == 0 then + return options, args + end + local count = #argv + local index = 1 + while index <= count do + local arg = argv[index] + local head = arg:sub(1, 1) + if arg ~= '' then + if head ~= '-' then + break + end + if arg == '-' then + options['-'] = '' + elseif arg == '--' then + options['-'] = '-' + elseif arg:match('^-%d+$') then + options['-'] = arg:sub(2) + else + local part = arg:split('=') + options[part[1]] = part[2] ~= nil and part[2] or '' + end + end + index = index + 1 + end + while index <= count do + table.insert(args, argv[index]) + index = index + 1 + end + return options, args +end + + +----------------------------------------------------------------------- +-- generate random seed +----------------------------------------------------------------------- +function math.random_init() + -- random seed from os.time() + local seed = tostring(os.time() * 1000) + seed = seed .. tostring(math.random(99999999)) + if os.argv ~= nil then + for _, key in ipairs(os.argv) do + seed = seed .. '/' .. key + end + end + local ppid = os.getenv('PPID') + seed = (ppid ~= nil) and (seed .. '/' .. ppid) or seed + -- random seed from socket.gettime() + local status, socket = pcall(require, 'socket') + if status then + seed = seed .. tostring(socket.gettime()) + end + -- random seed from _ZL_RANDOM + local rnd = os.getenv('_ZL_RANDOM') + if rnd ~= nil then + seed = seed .. rnd + end + seed = seed .. tostring(os.clock() * 10000000) + if os.native and os.native.tickcount then + seed = seed .. tostring(os.native.tickcount()) + end + local number = 0 + for i = 1, seed:len() do + local k = string.byte(seed:sub(i, i)) + number = ((number * 127) % 0x7fffffff) + k + end + math.randomseed(number) +end + + +----------------------------------------------------------------------- +-- math random string +----------------------------------------------------------------------- +function math.random_string(N) + local text = '' + for i = 1, N do + local k = math.random(0, 26 * 2 + 10 - 1) + if k < 26 then + text = text .. string.char(0x41 + k) + elseif k < 26 * 2 then + text = text .. string.char(0x61 + k - 26) + elseif k < 26 * 2 + 10 then + text = text .. string.char(0x30 + k - 26 * 2) + else + end + end + return text +end + + +----------------------------------------------------------------------- +-- returns true for path is insensitive +----------------------------------------------------------------------- +function path_case_insensitive() + if windows then + return true + end + local eos = os.getenv('OS') + eos = eos ~= nil and eos or '' + eos = eos:lower() + if eos:sub(1, 7) == 'windows' then + return true + end + return false +end + + +----------------------------------------------------------------------- +-- Read a line of the database and return a list of the 3 fields in it +----------------------------------------------------------------------- +function read_data_line(line) + local part = string.split(line, Z_DATA_SEPARATOR) + if #part <= 3 then + return part + end + -- If the part is made of more than 3 elements, it's probably because the + -- path element contains '|' that have been split. Thus, we want to + -- reconstruct it and keep the 2 last elements of part intact as the end + -- of the returned part. + local path = part[1] + for i=2,#part-2 do + path = path .. Z_DATA_SEPARATOR .. part[i] + end + return {path, part[#part-1], part[#part]} +end + + +----------------------------------------------------------------------- +-- load and split data +----------------------------------------------------------------------- +function data_load(filename) + local M = {} + local N = {} + local insensitive = path_case_insensitive() + local fp = io.open(os.path.expand(filename), 'r') + if fp == nil then + return {} + end + for line in fp:lines() do + local part = read_data_line(line) + local item = {} + if part and part[1] and part[2] and part[3] then + local key = insensitive and part[1]:lower() or part[1] + part[2] = part[2]:gsub(",", ".") + item.name = part[1] + item.rank = tonumber(part[2]) + item.time = tonumber(part[3]) + 0 + item.frecent = item.rank + if string.len(part[3]) < 12 then + if item.rank ~= nil and item.time ~= nil then + if N[key] == nil then + table.insert(M, item) + N[key] = 1 + end + end + end + end + end + fp:close() + return M +end + + +----------------------------------------------------------------------- +-- save data +----------------------------------------------------------------------- +function data_save(filename, M) + local fp = nil + local tmpname = nil + local i + filename = os.path.expand(filename) + math.random_init() + while true do + tmpname = filename .. '.' .. tostring(os.time()) + if os.native and os.native.tickcount then + local key = os.native.tickcount() % 1000 + tmpname = tmpname .. string.format('%03d', key) + tmpname = tmpname .. math.random_string(5) + else + tmpname = tmpname .. math.random_string(8) + end + if not os.path.exists(tmpname) then + -- print('tmpname: '..tmpname) + break + end + end + if windows then + if os.native and os.native.ReplaceFile then + fp = io.open(tmpname, 'w') + else + fp = io.open(filename, 'w') + tmpname = nil + end + else + fp = io.open(tmpname, 'w') + end + if fp == nil then + return false + end + for i = 1, #M do + local item = M[i] + local text = item.name .. Z_DATA_SEPARATOR .. item.rank .. Z_DATA_SEPARATOR .. item.time + fp:write(text .. '\n') + end + fp:close() + if tmpname ~= nil then + if windows then + local ok, err, code = os.rename(tmpname, filename) + if not ok then + os.native.ReplaceFile(filename, tmpname) + end + else + os.rename(tmpname, filename) + end + os.remove(tmpname) + end + return true +end + + +----------------------------------------------------------------------- +-- filter out bad dirname +----------------------------------------------------------------------- +function data_filter(M) + local N = {} + local i + M = M ~= nil and M or {} + for i = 1, #M do + local item = M[i] + if os.path.isdir(item.name) then + table.insert(N, item) + end + end + return N +end + + +----------------------------------------------------------------------- +-- insert item +----------------------------------------------------------------------- +function data_insert(M, filename) + local i = 1 + local sumscore = 0 + for i = 1, #M do + local item = M[i] + sumscore = sumscore + item.rank + end + if sumscore >= MAX_AGE then + local X = {} + for i = 1, #M do + local item = M[i] + item.rank = item.rank * 0.9 + if item.rank >= 1.0 then + table.insert(X, item) + end + end + M = X + end + local nocase = path_case_insensitive() + local name = filename + local key = nocase and string.lower(name) or name + local find = false + local current = os.time() + for i = 1, #M do + local item = M[i] + if not nocase then + if name == item.name then + item.rank = item.rank + 1 + item.time = current + find = true + break + end + else + if key == string.lower(item.name) then + item.rank = item.rank + 1 + item.time = current + find = true + break + end + end + end + if not find then + local item = {} + item.name = name + item.rank = 1 + item.time = current + item.frecent = item.rank + table.insert(M, item) + end + return M +end + + +----------------------------------------------------------------------- +-- change database +----------------------------------------------------------------------- +function data_file_set(name) + DATA_FILE = name +end + + +----------------------------------------------------------------------- +-- change pattern +----------------------------------------------------------------------- +function case_insensitive_pattern(pattern) + -- find an optional '%' (group 1) followed by any character (group 2) + local p = pattern:gsub("(%%?)(.)", function(percent, letter) + + if percent ~= "" or not letter:match("%a") then + -- if the '%' matched, or `letter` is not a letter, return "as is" + return percent .. letter + else + -- else, return a case-insensitive character class of the matched letter + return string.format("[%s%s]", letter:lower(), letter:upper()) + end + end) + return p +end + + +----------------------------------------------------------------------- +-- pathmatch +----------------------------------------------------------------------- +function path_match(pathname, patterns, matchlast) + local pos = 1 + local i = 0 + local matchlast = matchlast ~= nil and matchlast or false + for i = 1, #patterns do + local pat = patterns[i] + local start, endup = pathname:find(pat, pos) + if start == nil or endup == nil then + return false + end + pos = endup + 1 + end + if matchlast and #patterns > 0 then + local last = '' + local index = #patterns + local pat = patterns[index] + if not windows then + last = string.match(pathname, ".*(/.*)") + else + last = string.match(pathname, ".*([/\\].*)") + end + if last then + local start, endup = last:find(pat, 1) + if start == nil or endup == nil then + return false + end + end + end + return true +end + + +----------------------------------------------------------------------- +-- select matched pathnames +----------------------------------------------------------------------- +-- z_hyphen must be `true`, `false``, or `"auto"`. +function data_select(M, patterns, matchlast, z_hyphen) + local N = {} + local i = 1 + local pats = {} + local hyphens = false + for i = 1, #patterns do + local p = patterns[i] + hyphens = hyphens or string.match(p, "%-") + if z_hyphen == true then + p = p:gsub('-', '%%-') + end + table.insert(pats, case_insensitive_pattern(p)) + end + for i = 1, #M do + local item = M[i] + if path_match(item.name, pats, matchlast) then + table.insert(N, item) + end + end + if (hyphens and z_hyphen == "auto" and #N == 0) then + N = data_select(M, patterns, matchlast, true) + end + return N +end + + +----------------------------------------------------------------------- +-- update frecent +----------------------------------------------------------------------- +function data_update_frecent(M) + local current = os.time() + local i + for i = 1, #M do + local item = M[i] + local dx = current - item.time + if dx < 3600 then + item.frecent = item.rank * 4 + elseif dx < 86400 then + item.frecent = item.rank * 2 + elseif dx < 604800 then + item.frecent = item.rank * 0.5 + else + item.frecent = item.rank * 0.25 + end + end + return M +end + + +----------------------------------------------------------------------- +-- add path +----------------------------------------------------------------------- +function z_add(path) + local paths = {} + local count = 0 + if type(path) == 'table' then + paths = path + elseif type(path) == 'string' then + paths[1] = path + end + if table.length(paths) == 0 then + return false + end + local H = os.getenv('HOME') + local M = data_load(DATA_FILE) + local nc = os.getenv('_ZL_NO_CHECK') + if nc == nil or nc == '' or nc == '0' then + M = data_filter(M) + end + -- insert paths + for _, path in pairs(paths) do + if os.path.isdir(path) and os.path.isabs(path) then + local skip = false + local test = path + path = os.path.norm(path) + -- check ignore + if windows then + if path:len() == 3 and path:sub(2, 2) == ':' then + local tail = path:sub(3, 3) + if tail == '/' or tail == '\\' then + skip = true + end + end + test = os.path.norm(path:lower()) + else + if H == path then + skip = true + end + end + -- check exclude + if not skip then + for _, exclude in ipairs(Z_EXCLUDE) do + if test:startswith(exclude) then + skip = true + break + end + end + end + if not skip then + if windows then + if os.native and os.native.GetRealPathName then + local ts = os.native.GetRealPathName(path) + if ts then + path = ts + end + end + end + M = data_insert(M, path) + count = count + 1 + end + end + end + if count > 0 then + data_save(DATA_FILE, M) + end + return true +end + + +----------------------------------------------------------------------- +-- remove path +----------------------------------------------------------------------- +function z_remove(path) + local paths = {} + local count = 0 + local remove = {} + if type(path) == 'table' then + paths = path + elseif type(path) == 'string' then + paths[1] = path + end + if table.length(paths) == 0 then + return false + end + local H = os.getenv('HOME') + local M = data_load(DATA_FILE) + local X = {} + M = data_filter(M) + local insensitive = path_case_insensitive() + for _, path in pairs(paths) do + path = os.path.abspath(path) + if not insensitive then + remove[path] = 1 + else + remove[path:lower()] = 1 + end + end + for i = 1, #M do + local item = M[i] + if not insensitive then + if not remove[item.name] then + table.insert(X, item) + end + else + if not remove[item.name:lower()] then + table.insert(X, item) + end + end + end + data_save(DATA_FILE, X) +end + + +----------------------------------------------------------------------- +-- match method: frecent, rank, time +----------------------------------------------------------------------- +function z_match(patterns, method, subdir) + patterns = patterns ~= nil and patterns or {} + method = method ~= nil and method or 'frecent' + subdir = subdir ~= nil and subdir or false + local M = data_load(DATA_FILE) + M = data_select(M, patterns, false, Z_HYPHEN) + M = data_filter(M) + if Z_MATCHNAME then + local N = data_select(M, patterns, true, Z_HYPHEN) + N = data_filter(N) + if #N > 0 then + M = N + end + end + M = data_update_frecent(M) + if method == 'time' then + current = os.time() + for _, item in pairs(M) do + item.score = item.time - current + end + elseif method == 'rank' then + for _, item in pairs(M) do + item.score = item.rank + end + else + for _, item in pairs(M) do + item.score = item.frecent + end + end + table.sort(M, function (a, b) return a.score > b.score end) + local pwd = (PWD == nil or PWD == '') and os.getenv('PWD') or PWD + if pwd == nil or pwd == '' then + pwd = os.pwd() + end + if pwd ~= '' and pwd ~= nil then + if subdir then + local N = {} + for _, item in pairs(M) do + if os.path.subdir(pwd, item.name) then + table.insert(N, item) + end + end + M = N + end + if Z_SKIPPWD then + local N = {} + local key = windows and string.lower(pwd) or pwd + for _, item in pairs(M) do + local match = false + local name = windows and string.lower(item.name) or item.name + if name ~= key then + table.insert(N, item) + end + end + M = N + end + end + return M +end + + +----------------------------------------------------------------------- +-- pretty print +----------------------------------------------------------------------- +function z_print(M, weight, number) + local N = {} + local maxsize = 9 + local numsize = string.len(tostring(#M)) + for _, item in pairs(M) do + local record = {} + record.score = string.format('%.2f', item.score) + record.name = item.name + table.insert(N, record) + if record.score:len() > maxsize then + maxsize = record.score:len() + end + end + local fp = io.stdout + if PRINT_MODE == '' then + fp = io.stdout + elseif PRINT_MODE == '' then + fp = io.stderr + else + fp = io.open(PRINT_MODE, 'w') + end + for i = #N, 1, -1 do + local record = N[i] + local line = record.score + while true do + local tail = line:sub(-1, -1) + if tail ~= '0' and tail ~= '.' then + break + end + line = line:sub(1, -2) + if tail == '.' then + break + end + end + local dx = maxsize - line:len() + if dx > 0 then + line = line .. string.rep(' ', dx) + end + if weight then + line = line .. ' ' .. record.name + else + line = record.name + end + if number then + local head = tostring(i) + if head:len() < numsize then + head = string.rep(' ', numsize - head:len()) .. head + end + line = head .. ': ' .. line + end + if fp ~= nil then + fp:write(line .. '\n') + end + end + if PRINT_MODE:sub(1, 1) ~= '<' then + if fp ~= nil then fp:close() end + end +end + + +----------------------------------------------------------------------- +-- calculate jump dir +----------------------------------------------------------------------- +function z_cd(patterns) + if patterns == nil then + return nil + end + if #patterns == 0 then + return nil + end + local last = patterns[#patterns] + if last == '~' or last == '~/' then + return os.path.expand('~') + elseif windows and last == '~\\' then + return os.path.expand('~') + end + if os.path.isabs(last) and os.path.isdir(last) then + local size = #patterns + if size <= 1 then + return os.path.norm(last) + elseif last ~= '/' and last ~= '\\' then + return os.path.norm(last) + end + end + local M = z_match(patterns, Z_METHOD, Z_SUBDIR) + if M == nil then + return nil + end + if #M == 0 then + return nil + elseif #M == 1 then + return M[1].name + elseif Z_INTERACTIVE == 0 then + return M[1].name + end + if os.environ('_ZL_INT_SORT', false) then + table.sort(M, function (a, b) return a.name < b.name end) + end + local retval = nil + if Z_INTERACTIVE == 1 then + PRINT_MODE = '' + z_print(M, true, true) + io.stderr:write('> ') + io.stderr:flush() + local input = io.read('*l') + if input == nil or input == '' then + return nil + end + local index = tonumber(input) + if index == nil then + return nil + end + if index < 1 or index > #M then + return nil + end + retval = M[index].name + elseif Z_INTERACTIVE == 2 then + local fzf = os.environ('_ZL_FZF', 'fzf') + local tmpname = '/tmp/zlua.txt' + local cmd = '--nth 2.. --reverse --info=inline --tac ' + local flag = os.environ('_ZL_FZF_FLAG', '') + flag = (flag == '' or flag == nil) and '+s -e' or flag + cmd = ((fzf == '') and 'fzf' or fzf) .. ' ' .. cmd .. ' ' .. flag + if not windows then + tmpname = os.tmpname() + local height = os.environ('_ZL_FZF_HEIGHT', '35%') + if height ~= nil and height ~= '' and height ~= '0' then + cmd = cmd .. ' --height ' .. height + end + cmd = cmd .. ' < "' .. tmpname .. '"' + else + tmpname = os.tmpname():gsub('\\', ''):gsub('%.', '') + tmpname = os.environ('TMP', '') .. '\\zlua_' .. tmpname .. '.txt' + cmd = 'type "' .. tmpname .. '" | ' .. cmd + end + PRINT_MODE = tmpname + z_print(M, true, false) + retval = os.call(cmd) + -- io.stderr:write('<'..cmd..'>\n') + os.remove(tmpname) + if retval == '' or retval == nil then + return nil + end + local pos = retval:find(' ') + if not pos then + return nil + end + retval = retval:sub(pos, -1):gsub('^%s*', '') + end + return (retval ~= '' and retval or nil) +end + + +----------------------------------------------------------------------- +-- purge invalid paths +----------------------------------------------------------------------- +function z_purge() + local M = data_load(DATA_FILE) + local N = data_filter(M) + local x = #M + local y = #N + if x == y then + return x, y + end + data_save(DATA_FILE, N) + return x, y +end + + +----------------------------------------------------------------------- +-- find_vcs_root +----------------------------------------------------------------------- +function find_vcs_root(path) + local markers = os.getenv('_ZL_ROOT_MARKERS') + local markers = markers and markers or '.git,.svn,.hg,.root' + local markers = string.split(markers, ',') + path = os.path.absolute(path) + while true do + for _, marker in ipairs(markers) do + local test = os.path.join(path, marker) + if os.path.exists(test) then + return path + end + end + local parent, _ = os.path.split(path) + if path == parent then break end + path = parent + end + return nil +end + + +----------------------------------------------------------------------- +-- cd to parent directories which contains keyword +-- #args == 0 -> returns to vcs root +-- #args == 1 -> returns to parent dir starts with args[1] +-- #args == 2 -> returns string.replace($PWD, args[1], args[2]) +----------------------------------------------------------------------- +function cd_backward(args, options, pwd) + local nargs = #args + local pwd = (pwd ~= nil) and pwd or os.pwd() + if nargs == 0 then + return find_vcs_root(pwd) + elseif nargs == 1 and args[1]:sub(1, 2) == '..' then + local size = args[1]:len() - 1 + if args[1]:match('^%.%.+$') then + size = args[1]:len() - 1 + elseif args[1]:match('^%.%.%d+$') then + size = tonumber(args[1]:sub(3)) + else + return nil + end + local path = pwd + for index = 1, size do + path = os.path.join(path, '..') + end + return os.path.normpath(path) + elseif nargs == 1 then + pwd = os.path.split(pwd) + local test = windows and pwd:gsub('\\', '/') or pwd + local key = windows and args[1]:lower() or args[1] + if not key:match('%u') then + test = test:lower() + end + local pos, ends = test:rfind('/' .. key) + if pos then + ends = test:find('/', pos + key:len() + 1, true) + ends = ends and ends or test:len() + return os.path.normpath(pwd:sub(1, ends)) + elseif windows and test:startswith(key) then + ends = test:find('/', key:len(), true) + ends = ends and ends or test:len() + return os.path.normpath(pwd:sub(1, ends)) + end + pos = test:rfind(key) + if pos then + ends = test:find('/', pos + key:len(), true) + ends = ends and ends or test:len() + return os.path.normpath(pwd:sub(1, ends)) + end + return nil + elseif nargs == 2 then + local test = windows and pwd:gsub('\\', '/') or pwd + local src = args[1] + local dst = args[2] + if not src:match('%u') then + test = test:lower() + end + local start, ends = test:rfind(src) + if not start then + return pwd + end + + local lhs = pwd:sub(1, start - 1) + local rhs = pwd:sub(ends + 1) + local newpath = lhs .. dst .. rhs + if os.path.isdir(newpath) then + return newpath + end + + -- Get rid of the entire path component that matched `src`. + lhs = lhs:gsub("[^/]*$", "") + rhs = rhs:gsub("^[^/]*", "") + return z_cd({lhs, dst, rhs}) + -- In the future, it would make sense to have `z -b -c from to to2` + -- to z_cd({lhs, dst[1], dst[2]}). Without `-c`, we probably still + -- want to support only 2 argumets. + end + + io.stderr:write("Error: " .. Z_CMD .. " -b takes at most 2 arguments.\n") + return nil +end + + +----------------------------------------------------------------------- +-- cd minus: "z -", "z --", "z -2" +----------------------------------------------------------------------- +function cd_minus(args, options) + Z_SKIPPWD = true + local M = z_match({}, 'time', Z_SUBDIR) + local size = #M + if options['-'] == '-' then + for i, item in ipairs(M) do + if i > 10 then break end + io.stderr:write(' ' .. tostring(i - 1) .. ' ' .. item.name .. '\n') + end + else + local level = 0 + local num = options['-'] + if num and num ~= '' then + level = tonumber(num) + end + if level >= 0 and level < size then + return M[level + 1].name + end + end + return nil +end + + +----------------------------------------------------------------------- +-- cd breadcrumbs: z -b -i, z -b -I +----------------------------------------------------------------------- +function cd_breadcrumbs(pwd, interactive) + local pwd = (pwd == nil or pwd == '') and os.pwd() or pwd + local pwd = os.path.normpath(pwd) + local path, _ = os.path.split(pwd) + local elements = {} + local interactive = interactive and interactive or 1 + local fullname = os.environ('_ZL_FULL_PATH', false) + while true do + local head, name = os.path.split(path) + if head == path then -- reached root + table.insert(elements, {head, head}) + break + elseif name ~= '' then + table.insert(elements, {name, path}) + else + break + end + path = head + end + local tmpname = '/tmp/zlua.txt' + local fp = io.stderr + if interactive == 2 then + if not windows then + tmpname = os.tmpname() + else + tmpname = os.tmpname():gsub('\\', ''):gsub('%.', '') + tmpname = os.environ('TMP', '') .. '\\zlua_' .. tmpname .. '.txt' + end + fp = io.open(tmpname, 'w') + end + -- print table + local maxsize = string.len(tostring(#elements)) + for i = #elements, 1, -1 do + local item = elements[i] + local name = item[1] + local text = string.rep(' ', maxsize - string.len(i)) .. tostring(i) + text = text .. ': ' .. (fullname and item[2] or item[1]) + fp:write(text .. '\n') + end + if fp ~= io.stderr then + fp:close() + end + local retval = '' + -- select from stdin or fzf + if interactive == 1 then + io.stderr:write('> ') + io.stderr:flush() + retval = io.read('*l') + elseif interactive == 2 then + local fzf = os.environ('_ZL_FZF', 'fzf') + local cmd = '--reverse --info=inline --tac ' + local flag = os.environ('_ZL_FZF_FLAG', '') + flag = (flag == '' or flag == nil) and '+s -e' or flag + cmd = ((fzf == '') and 'fzf' or fzf) .. ' ' .. cmd .. ' ' .. flag + if not windows then + local height = os.environ('_ZL_FZF_HEIGHT', '35%') + if height ~= nil and height ~= '' and height ~= '0' then + cmd = cmd .. ' --height ' .. height + end + cmd = cmd .. '< "' .. tmpname .. '"' + else + cmd = 'type "' .. tmpname .. '" | ' .. cmd + end + retval = os.call(cmd) + os.remove(tmpname) + if retval == '' or retval == nil then + return nil + end + local pos = retval:find(':') + if not pos then + return nil + end + retval = retval:sub(1, pos - 1):gsub('^%s*', '') + end + local index = tonumber(retval) + if index == nil or index < 1 or index > #elements then + return nil + end + return elements[index][2] +end + + +----------------------------------------------------------------------- +-- main entry +----------------------------------------------------------------------- +function main(argv) + local options, args = os.getopt(argv) + os.log("main()") + if options == nil then + return false + elseif table.length(args) == 0 and table.length(options) == 0 then + print(os.argv[0] .. ': missing arguments') + help = os.argv[-1] .. ' ' .. os.argv[0] .. ' --help' + print('Try \'' .. help .. '\' for more information') + return false + end + if true then + os.log("options: " .. dump(options)) + os.log("args: " .. dump(args)) + end + if options['-c'] then + Z_SUBDIR = true + end + if options['-r'] then + Z_METHOD = 'rank' + elseif options['-t'] then + Z_METHOD = 'time' + end + if options['-i'] then + Z_INTERACTIVE = 1 + elseif options['-I'] then + Z_INTERACTIVE = 2 + end + if options['--cd'] or options['-e'] then + local path = '' + if options['-b'] then + if #args > 0 or Z_INTERACTIVE == 0 then + path = cd_backward(args, options) + else + path = cd_breadcrumbs('', Z_INTERACTIVE) + end + elseif options['-'] then + path = cd_minus(args, options) + elseif #args == 0 then + path = nil + else + path = z_cd(args) + if path == nil and Z_MATCHMODE ~= 0 then + local last = args[#args] + if os.path.isdir(last) then + path = os.path.abspath(last) + path = os.path.norm(path) + end + end + end + if path ~= nil then + io.write(path .. (options['-e'] and "\n" or "")) + end + elseif options['--add'] then + -- print('data: ' .. DATA_FILE) + z_add(args) + elseif options['-x'] then + z_remove(args) + elseif options['--purge'] then + local src, dst = z_purge() + local fp = io.stderr + fp:write('purge: ' .. tostring(src) .. ' record(s) remaining, ') + fp:write(tostring(src - dst) .. ' invalid record(s) removed.\n') + elseif options['--init'] then + local opts = {} + for _, key in ipairs(args) do + opts[key] = 1 + end + if opts.nushell then + z_nushell_init(opts) + elseif windows then + z_windows_init(opts) + elseif opts.fish then + z_fish_init(opts) + elseif opts.powershell then + z_windows_init(opts) + else + z_shell_init(opts) + end + elseif options['-l'] then + local M = z_match(args and args or {}, Z_METHOD, Z_SUBDIR) + if options['-s'] then + z_print(M, false, false) + else + z_print(M, true, false) + end + elseif options['--complete'] then + local M = {} + if options['-m1'] then + M = z_match(args and args or {}, Z_METHOD, Z_SUBDIR) + else + local line = args[1] and args[1] or '' + local head = line:sub(Z_CMD:len()+1):gsub('^%s+', '') + M = z_match({head}, Z_METHOD, Z_SUBDIR) + end + for _, item in pairs(M) do + print(item.name) + end + elseif options['--help'] or options['-h'] then + z_help() + end + return true +end + + +----------------------------------------------------------------------- +-- initialize from environment variable +----------------------------------------------------------------------- +function z_init() + local _zl_data = os.getenv('_ZL_DATA') + local _zl_maxage = os.getenv('_ZL_MAXAGE') + local _zl_exclude = os.getenv('_ZL_EXCLUDE_DIRS') + local _zl_cmd = os.getenv('_ZL_CMD') + local _zl_matchname = os.getenv('_ZL_MATCH_NAME') + local _zl_skippwd = os.getenv('_ZL_SKIP_PWD') + local _zl_matchmode = os.getenv('_ZL_MATCH_MODE') + local _zl_hyphen = os.getenv('_ZL_HYPHEN') + if _zl_data ~= nil and _zl_data ~= "" then + if windows then + DATA_FILE = _zl_data + else + -- avoid windows environments affect cygwin & msys + if not string.match(_zl_data, '^%a:[/\\]') then + DATA_FILE = _zl_data + end + end + end + if _zl_maxage ~= nil and _zl_maxage ~= "" then + _zl_maxage = tonumber(_zl_maxage) + if _zl_maxage ~= nil and _zl_maxage > 0 then + MAX_AGE = _zl_maxage + end + end + if _zl_exclude ~= nil and _zl_exclude ~= "" then + local part = _zl_exclude:split(',') + local insensitive = path_case_insensitive() + for _, name in ipairs(part) do + if insensitive then + name = name:lower() + end + if windows then + name = os.path.norm(name) + end + table.insert(Z_EXCLUDE, name) + end + end + if _zl_cmd ~= nil and _zl_cmd ~= '' then + Z_CMD = _zl_cmd + end + if _zl_matchname ~= nil then + local m = string.lower(_zl_matchname) + if (m == '1' or m == 'yes' or m == 'true' or m == 't') then + Z_MATCHNAME = true + end + end + if _zl_skippwd ~= nil then + local m = string.lower(_zl_skippwd) + if (m == '1' or m == 'yes' or m == 'true' or m == 't') then + Z_SKIPPWD = true + end + end + if _zl_matchmode ~= nil then + local m = tonumber(_zl_matchmode) + Z_MATCHMODE = m + if (m == 1) then + Z_MATCHNAME = true + Z_SKIPPWD = true + end + end + assert(Z_HYPHEN == "auto", "Z_HYPHEN initialized to an unexpected value") + if _zl_hyphen ~= nil then + local m = string.lower(_zl_hyphen) + if (m == '1' or m == 'yes' or m == 'true' or m == 't') then + Z_HYPHEN = true + elseif (m == '0' or m == 'no' or m == 'false' or m == 'f') then + Z_HYPHEN = false + end + end +end + + +----------------------------------------------------------------------- +-- initialize clink hooks +----------------------------------------------------------------------- +function z_clink_init() + local once = os.environ("_ZL_ADD_ONCE", false) + local _zl_clink_prompt_priority = os.environ('_ZL_CLINK_PROMPT_PRIORITY', 99) + local previous = '' + function z_add_to_database() + pwd = clink.get_cwd() + if once then + if previous == pwd then + return + end + previous = pwd + end + z_add(clink.get_cwd()) + end + clink.prompt.register_filter(z_add_to_database, _zl_clink_prompt_priority) + function z_match_completion(word) + local M = z_match({word}, Z_METHOD, Z_SUBDIR) + for _, item in pairs(M) do + clink.add_match(item.name) + end + return {} + end + local dirmatchfunc = function (word) + clink.matches_are_files(1) + return clink.match_files(word..'*', true, clink.find_dirs) + end + local dirmatchparser = clink.arg.new_parser():set_arguments({ dirmatchfunc }) + local z_parser = clink.arg.new_parser() + z_parser:set_arguments({ z_match_completion }) + z_parser:set_flags("-r", "-i", "-I", "-t", "-l", "-c", "-e", "-b", "-x"..dirmatchparser, "-h") + if z_parser.adddescriptions then + z_parser:adddescriptions({ + ['-r'] = "cd to highest ranked dir matching", + ['-i'] = "cd with interactive selection", + ['-I'] = "cd with interactive selection using fzf", + ['-t'] = "cd to most recently accessed dir matching", + ['-l'] = "list matches instead of cd", + ['-c'] = "restrict matches to subdirs of cwd (or %PWD% if set)", + ['-e'] = "echo the best match, don't cd", + ['-b'] = "jump backwards to given dir or to project root", + ['-x'] = { " dir", "remove path from history" }, + ['-h'] = "show help", + }) + end + clink.arg.register_parser("z", z_parser) +end + + +----------------------------------------------------------------------- +-- shell scripts +----------------------------------------------------------------------- +local script_zlua = [[ +_zlua() { + local arg_mode="" + local arg_type="" + local arg_subdir="" + local arg_inter="" + local arg_strip="" + if [ "$1" = "--add" ]; then + shift + _ZL_RANDOM="$RANDOM" "$ZLUA_LUAEXE" "$ZLUA_SCRIPT" --add "$@" + return + elif [ "$1" = "--complete" ]; then + shift + "$ZLUA_LUAEXE" "$ZLUA_SCRIPT" --complete "$@" + return + fi + while [ "$1" ]; do + case "$1" in + -l) local arg_mode="-l" ;; + -e) local arg_mode="-e" ;; + -x) local arg_mode="-x" ;; + -t) local arg_type="-t" ;; + -r) local arg_type="-r" ;; + -c) local arg_subdir="-c" ;; + -s) local arg_strip="-s" ;; + -i) local arg_inter="-i" ;; + -I) local arg_inter="-I" ;; + -h) local arg_mode="-h" ;; + --help) local arg_mode="-h" ;; + --purge) local arg_mode="--purge" ;; + *) break ;; + esac + shift + done + if [ "$arg_mode" = "-h" ] || [ "$arg_mode" = "--purge" ]; then + "$ZLUA_LUAEXE" "$ZLUA_SCRIPT" $arg_mode + elif [ "$arg_mode" = "-l" ] || [ "$#" -eq 0 ]; then + "$ZLUA_LUAEXE" "$ZLUA_SCRIPT" -l $arg_subdir $arg_type $arg_strip "$@" + elif [ -n "$arg_mode" ]; then + "$ZLUA_LUAEXE" "$ZLUA_SCRIPT" $arg_mode $arg_subdir $arg_type $arg_inter "$@" + else + local zdest=$("$ZLUA_LUAEXE" "$ZLUA_SCRIPT" --cd $arg_type $arg_subdir $arg_inter "$@") + if [ -n "$zdest" ] && [ -d "$zdest" ]; then + if [ -z "$_ZL_CD" ]; then + builtin cd "$zdest" + else + $_ZL_CD "$zdest" + fi + if [ -n "$_ZL_ECHO" ]; then pwd; fi + fi + fi +} +# alias ${_ZL_CMD:-z}='_zlua 2>&1' +alias ${_ZL_CMD:-z}='_zlua' +]] + +local script_init_bash = [[ +case "$PROMPT_COMMAND" in + *_zlua?--add*) ;; + *) PROMPT_COMMAND="(_zlua --add \"\$(command pwd 2>/dev/null)\" &)${PROMPT_COMMAND:+;$PROMPT_COMMAND}" ;; +esac +]] + +local script_init_bash_fast = [[ +case "$PROMPT_COMMAND" in + *_zlua?--add*) ;; + *) PROMPT_COMMAND="(_zlua --add \"\$PWD\" &)${PROMPT_COMMAND:+;$PROMPT_COMMAND}" ;; +esac +]] + +local script_init_bash_once = [[ +_zlua_precmd() { + [ "$_ZL_PREVIOUS_PWD" = "$PWD" ] && return + _ZL_PREVIOUS_PWD="$PWD" + (_zlua --add "$PWD" 2> /dev/null &) +} +case "$PROMPT_COMMAND" in + *_zlua_precmd*) ;; + *) PROMPT_COMMAND="_zlua_precmd${PROMPT_COMMAND:+;$PROMPT_COMMAND}" ;; +esac +]] + +local script_init_posix = [[ +case "$PS1" in + *_zlua?--add*) ;; + *) PS1="\$(_zlua --add \"\$(command pwd 2>/dev/null)\" &)$PS1" +esac +]] + +local script_init_posix_once = [[ +_zlua_precmd() { + [ "$_ZL_PREVIOUS_PWD" = "$PWD" ] && return + _ZL_PREVIOUS_PWD="$PWD" + (_zlua --add "$PWD" 2> /dev/null &) +} +case "$PS1" in + *_zlua_precmd*) ;; + *) PS1="\$(_zlua_precmd)$PS1" +esac +]] + +local script_init_zsh = [[ +_zlua_precmd() { + (_zlua --add "${PWD:a}" &) +} +typeset -ga precmd_functions +[ -n "${precmd_functions[(r)_zlua_precmd]}" ] || { + precmd_functions[$(($#precmd_functions+1))]=_zlua_precmd +} +]] + +local script_init_zsh_once = [[ +_zlua_precmd() { + (_zlua --add "${PWD:a}" &) +} +typeset -ga chpwd_functions +[ -n "${chpwd_functions[(r)_zlua_precmd]}" ] || { + chpwd_functions[$(($#chpwd_functions+1))]=_zlua_precmd +} +]] + +local script_complete_bash = [[ +if [ -n "$BASH_VERSION" ]; then + complete -o filenames -C '_zlua --complete "$COMP_LINE"' ${_ZL_CMD:-z} +fi +]] + +local script_fzf_complete_bash = [[ +if [ "$TERM" != "dumb" ] && command -v fzf >/dev/null 2>&1; then + # To redraw line after fzf closes (printf '\e[5n') + bind '"\e[0n": redraw-current-line' + _zlua_fzf_complete() { + local selected=$(_zlua -l "${COMP_WORDS[@]:1}" | sed "s|$HOME|\~|" | $zlua_fzf | sed 's/^[0-9,.]* *//') + if [ -n "$selected" ]; then + COMPREPLY=( "$selected" ) + fi + printf '\e[5n' + } + complete -o bashdefault -o nospace -F _zlua_fzf_complete ${_ZL_CMD:-z} +fi +]] + +local script_complete_zsh = [[ +_zlua_zsh_tab_completion() { + # tab completion + (( $+compstate )) && compstate[insert]=menu # no expand + local -a tmp=(${(f)"$(_zlua --complete "${words/_zlua/z}")"}) + _describe "directory" tmp -U +} +if [ "${+functions[compdef]}" -ne 0 ]; then + compdef _zlua_zsh_tab_completion _zlua 2> /dev/null + compdef ${_ZL_CMD:-z}=_zlua +fi +]] + +local script_fzf_complete_zsh = [[ +if command -v fzf >/dev/null 2>&1; then + # To redraw line after fzf closes (printf '\e[5n') + bindkey '\e[0n' kill-whole-line + _zlua_zsh_fzf_complete() { + local list=$(_zlua -l ${words[2,-1]}) + + if [ -n "$list" ]; then + local selected=$(print $list | ${=zlua_fzf} | sed 's/^[0-9,.]* *//') + + if [ -n "$selected" ]; then + cd ${selected} + printf '\e[5n' + fi + fi + } + + if [ "${+functions[compdef]}" -ne 0 ]; then + compdef _zlua_zsh_fzf_complete _zlua > /dev/null 2>&1 + compdef ${_ZL_CMD:-z}=_zlua + fi +fi +]] + + +----------------------------------------------------------------------- +-- initialize bash/zsh +---------------------------------------------------------------------- +function z_shell_init(opts) + print('ZLUA_SCRIPT="' .. os.scriptname() .. '"') + print('ZLUA_LUAEXE="' .. os.interpreter() .. '"') + print('') + if not opts.posix then + print(script_zlua) + elseif not opts.legacy then + local script = script_zlua:gsub('builtin ', '') + print(script) + else + local script = script_zlua:gsub('local ', ''):gsub('builtin ', '') + print(script) + end + + local prompt_hook = (not os.environ("_ZL_NO_PROMPT_COMMAND", false)) + local once = os.environ("_ZL_ADD_ONCE", false) or opts.once ~= nil + + if opts.clean ~= nil then + prompt_hook = false + end + + if opts.bash ~= nil then + if prompt_hook then + if once then + print(script_init_bash_once) + elseif opts.fast then + print(script_init_bash_fast) + else + print(script_init_bash) + end + end + print(script_complete_bash) + if opts.fzf ~= nil then + local fzf_cmd = "fzf --nth 2.. --reverse --info=inline --tac " + local height = os.environ('_ZL_FZF_HEIGHT', '35%') + if height ~= nil and height ~= '' and height ~= '0' then + fzf_cmd = fzf_cmd .. ' --height ' .. height .. ' ' + end + local flag = os.environ('_ZL_FZF_FLAG', '') + flag = (flag == '' or flag == nil) and '+s -e' or flag + fzf_cmd = fzf_cmd .. ' ' .. flag .. ' ' + print('zlua_fzf="' .. fzf_cmd .. '"') + print(script_fzf_complete_bash) + end + elseif opts.zsh ~= nil then + if prompt_hook then + print(once and script_init_zsh_once or script_init_zsh) + end + print(script_complete_zsh) + if opts.fzf ~= nil then + local fzf_cmd = "fzf --nth 2.. --reverse --info=inline --tac " + local height = os.environ('_ZL_FZF_HEIGHT', '35%') + if height ~= nil and height ~= '' and height ~= '0' then + fzf_cmd = fzf_cmd .. ' --height ' .. height .. ' ' + end + local flag = os.environ('_ZL_FZF_FLAG', '') + flag = (flag == '' or flag == nil) and '+s -e' or flag + fzf_cmd = fzf_cmd .. ' ' .. flag .. ' ' + print('zlua_fzf="' .. fzf_cmd .. '"') + print(script_fzf_complete_zsh) + end + elseif opts.posix ~= nil then + if prompt_hook then + local script = script_init_posix + if once then script = script_init_posix_once end + if opts.legacy then + script = script:gsub('%&%)', ')') + end + print(script) + end + else + if prompt_hook then + print('if [ -n "$BASH_VERSION" ]; then') + if opts.once then + print(script_init_bash_once) + elseif opts.fast then + print(script_init_bash_fast) + else + print(script_init_bash) + end + print(script_complete_bash) + print('elif [ -n "$ZSH_VERSION" ]; then') + print(once and script_init_zsh_once or script_init_zsh) + -- print(script_complete_zsh) + print('else') + print(once and script_init_posix_once or script_init_posix) + print('builtin() { cd "$2"; }') + print('fi') + end + end + if opts.enhanced ~= nil then + print('export _ZL_MATCH_MODE=1') + end + if opts.nc then + print('export _ZL_NO_CHECK=1') + end + if opts.echo then + print('_ZL_ECHO=1') + end +end + + +----------------------------------------------------------------------- +-- Fish shell init +----------------------------------------------------------------------- +local script_zlua_fish = [[ +function _zlua + set -l arg_mode "" + set -l arg_type "" + set -l arg_subdir "" + set -l arg_inter "" + set -l arg_strip "" + function _zlua_call; eval (string escape -- $argv); end + if test "$argv[1]" = "--add" + set -e argv[1] + set -x _ZL_RANDOM (random) + _zlua_call "$ZLUA_LUAEXE" "$ZLUA_SCRIPT" --add $argv + return + else if test "$argv[1]" = "--complete" + set -e argv[1] + _zlua_call "$ZLUA_LUAEXE" "$ZLUA_SCRIPT" --complete $argv + return + end + while true + switch "$argv[1]" + case "-l"; set arg_mode "-l" + case "-e"; set arg_mode "-e" + case "-x"; set arg_mode "-x" + case "-t"; set arg_type "-t" + case "-r"; set arg_type "-r" + case "-c"; set arg_subdir "-c" + case "-s"; set arg_strip "-s" + case "-i"; set arg_inter "-i" + case "-I"; set arg_inter "-I" + case "-h"; set arg_mode "-h" + case "--help"; set arg_mode "-h" + case "--purge"; set arg_mode "--purge" + case '*'; break + end + set -e argv[1] + end + if test "$arg_mode" = "-h" + _zlua_call "$ZLUA_LUAEXE" "$ZLUA_SCRIPT" -h + else if test "$arg_mode" = "--purge" + _zlua_call "$ZLUA_LUAEXE" "$ZLUA_SCRIPT" --purge + else if test "$arg_mode" = "-l" + _zlua_call "$ZLUA_LUAEXE" "$ZLUA_SCRIPT" -l $arg_subdir $arg_type $arg_strip $argv + else if test (count $argv) -eq 0 + _zlua_call "$ZLUA_LUAEXE" "$ZLUA_SCRIPT" -l $arg_subdir $arg_type $arg_strip $argv + else if test -n "$arg_mode" + _zlua_call "$ZLUA_LUAEXE" "$ZLUA_SCRIPT" $arg_mode $arg_subdir $arg_type $arg_inter $argv + else + set -l dest (_zlua_call "$ZLUA_LUAEXE" "$ZLUA_SCRIPT" --cd $arg_type $arg_subdir $arg_inter $argv) + if test -n "$dest" -a -d "$dest" + if test -z "$_ZL_CD" + builtin cd "$dest" + else + _zlua_call "$_ZL_CD" "$dest" + end + if test -n "$_ZL_ECHO"; pwd; end + end + end +end + +if test -z "$_ZL_CMD"; set -x _ZL_CMD z; end +function $_ZL_CMD -w _zlua -d "alias $_ZL_CMD=_zlua" + _zlua $argv +end +]] + +script_init_fish = [[ +function _zlua_precmd --on-event fish_prompt + _zlua --add "$PWD" 2> /dev/null & +end +]] + +script_init_fish_once = [[ +function _zlua_precmd --on-variable PWD + _zlua --add "$PWD" 2> /dev/null & +end +]] + +script_complete_fish = [[ +function _z_complete + eval "$_ZL_CMD" --complete (commandline -t) +end + +complete -c $_ZL_CMD -f -a '(_z_complete)' +complete -c $_ZL_CMD -s 'r' -d 'cd to highest ranked dir matching' +complete -c $_ZL_CMD -s 'i' -d 'cd with interactive selection' +complete -c $_ZL_CMD -s 'I' -d 'cd with interactive selection using fzf' +complete -c $_ZL_CMD -s 't' -d 'cd to most recently accessed dir matching' +complete -c $_ZL_CMD -s 'l' -d 'list matches instead of cd' +complete -c $_ZL_CMD -s 'c' -d 'restrict matches to subdirs of $PWD' +complete -c $_ZL_CMD -s 'e' -d 'echo the best match, don''t cd' +complete -c $_ZL_CMD -s 'b' -d 'jump backwards to given dir or to project root' +complete -c $_ZL_CMD -s 'x' -x -d 'remove path from history' -a '(_z_complete)' +]] + + +function z_fish_init(opts) + print('set -x ZLUA_SCRIPT "' .. os.scriptname() .. '"') + print('set -x ZLUA_LUAEXE "' .. os.interpreter() .. '"') + local once = (os.getenv("_ZL_ADD_ONCE") ~= nil) or opts.once ~= nil + local prompt_hook = (not os.environ("_ZL_NO_PROMPT_COMMAND", false)) + if opts.clean ~= nil then + prompt_hook = false + end + print(script_zlua_fish) + if prompt_hook then + if once then + print(script_init_fish_once) + else + print(script_init_fish) + end + end + print(script_complete_fish) + if opts.enhanced ~= nil then + print('set -x _ZL_MATCH_MODE 1') + end + if opts.echo then + print('set -g _ZL_ECHO 1') + end + if opts.nc then + print('set -x _ZL_NO_CHECK 1') + end +end + + +----------------------------------------------------------------------- +-- windows .cmd script +----------------------------------------------------------------------- +local script_init_cmd = [[ +set "MatchType=-n" +set "StrictSub=-n" +set "RunMode=-n" +set "StripMode=" +set "InterMode=" +if /i not "%_ZL_LUA_EXE%"=="" ( + set "LuaExe=%_ZL_LUA_EXE%" +) +:parse +if /i "%1"=="-r" ( + set "MatchType=-r" + shift /1 + goto parse +) +if /i "%1"=="-t" ( + set "MatchType=-t" + shift /1 + goto parse +) +if /i "%1"=="-c" ( + set "StrictSub=-c" + shift /1 + goto parse +) +if /i "%1"=="-l" ( + set "RunMode=-l" + shift /1 + goto parse +) +if /i "%1"=="-e" ( + set "RunMode=-e" + shift /1 + goto parse +) +if /i "%1"=="-x" ( + set "RunMode=-x" + shift /1 + goto parse +) +if /i "%1"=="--add" ( + set "RunMode=--add" + shift /1 + goto parse +) +if "%1"=="-i" ( + set "InterMode=-i" + shift /1 + goto parse +) +if "%1"=="-I" ( + set "InterMode=-I" + shift /1 + goto parse +) +if /i "%1"=="-s" ( + set "StripMode=-s" + shift /1 + goto parse +) +if /i "%1"=="-h" ( + call "%LuaExe%" "%LuaScript%" -h + goto end +) +if /i "%1"=="--purge" ( + call "%LuaExe%" "%LuaScript%" --purge + goto end +) +:check +if /i "%1"=="" ( + set "RunMode=-l" +) +for /f "delims=" %%i in ('cd') do set "PWD=%%i" +if /i "%RunMode%"=="-n" ( + for /f "delims=" %%i in ('call "%LuaExe%" "%LuaScript%" --cd %MatchType% %StrictSub% %InterMode% %*') do set "NewPath=%%i" + if not "!NewPath!"=="" ( + if exist !NewPath!\nul ( + if /i not "%_ZL_ECHO%"=="" ( + echo !NewPath! + ) + pushd !NewPath! + pushd !NewPath! + endlocal + goto popdir + ) + ) +) else ( + call "%LuaExe%" "%LuaScript%" "%RunMode%" %MatchType% %StrictSub% %InterMode% %StripMode% %* +) +goto end +:popdir +popd +setlocal +set "NewPath=%CD%" +set "CDCmd=cd /d" +if /i not "%_ZL_CD%"=="" ( + set "CDCmd=%_ZL_CD%" +) +endlocal & popd & %CDCmd% "%NewPath%" +:end +]] + + +----------------------------------------------------------------------- +-- powershell +----------------------------------------------------------------------- +local script_zlua_powershell = [[ +function global:_zlua { + $arg_mode = "" + $arg_type = "" + $arg_subdir = "" + $arg_inter = "" + $arg_strip = "" + if ($args[0] -eq "--add") { + $_, $rest = $args + $env:_ZL_RANDOM = Get-Random + & $script:ZLUA_LUAEXE $script:ZLUA_SCRIPT --add $rest + return + } elseif ($args[0] -eq "--complete") { + $_, $rest = $args + & $script:ZLUA_LUAEXE $script:ZLUA_SCRIPT --complete $rest + return + } elseif ($args[0] -eq "--update") { + $str_pwd = ([string] $PWD) + if ((!$env:_ZL_ADD_ONCE) -or + ($env:_ZL_ADD_ONCE -and ($script:_zlua_previous -ne $str_pwd))) { + $script:_zlua_previous = $str_pwd + _zlua --add $str_pwd + } + return + } + :loop while ($args) { + switch -casesensitive ($args[0]) { + "-l" { $arg_mode = "-l"; break } + "-e" { $arg_mode = "-e"; break } + "-x" { $arg_mode = "-x"; break } + "-t" { $arg_type = "-t"; break } + "-r" { $arg_type = "-r"; break } + "-c" { $arg_subdir="-c"; break } + "-s" { $arg_strip="-s"; break } + "-i" { $arg_inter="-i"; break } + "-I" { $arg_inter="-I"; break } + "-h" { $arg_mode="-h"; break } + "--help" { $arg_mode="-h"; break } + "--purge" { $arg_mode="--purge"; break } + Default { break loop } + } + $_, $args = $args + if (!$args) { break loop } + } + $env:PWD = ([string] $PWD) + if ($arg_mode -eq "-h" -or $arg_mode -eq "--purge") { + & $script:ZLUA_LUAEXE $script:ZLUA_SCRIPT $arg_mode + } elseif ($arg_mode -eq "-l" -or $args.Length -eq 0) { + & $script:ZLUA_LUAEXE $script:ZLUA_SCRIPT -l $arg_subdir $arg_type $arg_strip $args + } elseif ($arg_mode -ne "") { + & $script:ZLUA_LUAEXE $script:ZLUA_SCRIPT $arg_mode $arg_subdir $arg_type $arg_inter $args + } else { + $dest = & $script:ZLUA_LUAEXE $script:ZLUA_SCRIPT --cd $arg_type $arg_subdir $arg_inter $args + if ($dest) { + if ($env:_ZL_CD) { & $env:_ZL_CD "$dest" } + else { & "Push-Location" "$dest" } + if ($env:_ZL_ECHO) { Write-Host $PWD } + } + } +} + +if ($env:_ZL_CMD) { Set-Alias $env:_ZL_CMD _zlua -Scope Global } +else { Set-Alias z _zlua -Scope Global } +]] + +local script_init_powershell = [[ +if (!$env:_ZL_NO_PROMPT_COMMAND -and (!$global:_zlua_inited)) { + $script:_zlua_orig_prompt = ([ref] $function:prompt) + $global:_zlua_inited = $True + function global:prompt { + & $script:_zlua_orig_prompt.value + _zlua --update + } +} +]] + + +----------------------------------------------------------------------- +-- initialize cmd/powershell +----------------------------------------------------------------------- +function z_windows_init(opts) + local prompt_hook = (not os.environ("_ZL_NO_PROMPT_COMMAND", false)) + if opts.clean ~= nil then + prompt_hook = false + end + if opts.powershell ~= nil then + print('$script:ZLUA_LUAEXE = "' .. os.interpreter() .. '"') + print('$script:ZLUA_SCRIPT = "' .. os.scriptname() .. '"') + print(script_zlua_powershell) + if opts.enhanced ~= nil then + print('$env:_ZL_MATCH_MODE = 1') + end + if opts.once ~= nil then + print('$env:_ZL_ADD_ONCE = 1') + end + if opts.echo ~= nil then + print('$env:_ZL_ECHO = 1') + end + if opts.nc ~= nil then + print('$env:_ZL_NO_CHECK = 1') + end + if prompt_hook then + print(script_init_powershell) + end + else + print('@echo off') + print('setlocal EnableDelayedExpansion') + print('set "LuaExe=' .. os.interpreter() .. '"') + print('set "LuaScript=' .. os.scriptname() .. '"') + print(script_init_cmd) + if opts.newline then + print('echo.') + end + end +end + + +----------------------------------------------------------------------- +-- nushell +----------------------------------------------------------------------- +local script_zlua_nushell = [[ +def _zlua --env --wrapped [...args: string] { + if ($args | length) != 0 and $args.0 == "--add" { + with-env { _ZL_RANDOM: (random int) } { ^$env.ZLUA_LUAEXE $env.ZLUA_SCRIPT --add ...($args | skip 1) } + } else if ($args | length) != 0 and $args.0 == "--complete" { + ^$env.ZLUA_LUAEXE $env.ZLUA_SCRIPT --complete ...($args | skip 1) + } else { + mut arg_mode = '' + mut arg_type = '' + mut arg_subdir = '' + mut arg_inter = '' + mut arg_strip = '' + mut count = 0 + for arg in $args { + match $arg { + '-l' => { $arg_mode = '-l' }, + '-e' => { $arg_mode = '-e' }, + '-x' => { $arg_mode = '-x' }, + '-t' => { $arg_type = '-t' }, + '-r' => { $arg_type = '-r' }, + '-c' => { $arg_subdir = '-c' }, + '-s' => { $arg_strip = '-s' }, + '-i' => { $arg_inter = '-i' }, + '-I' => { $arg_inter = '-I' }, + '-h' => { $arg_mode = '-h' }, + '--help' => { $arg_mode = '-h' }, + '--purge' => { $arg_mode = '--purge' }, + _ => break + } + $count += 1 + } + let args = $args | skip $count + if $arg_mode == '-h' or $arg_mode == '--purge' { + ^$env.ZLUA_LUAEXE $env.ZLUA_SCRIPT $arg_mode + } else if $arg_mode == '-l' or ($args | length) == 0 { + ^$env.ZLUA_LUAEXE $env.ZLUA_SCRIPT -l $arg_subdir $arg_type $arg_strip ...$args + } else if $arg_mode != '' { + ^$env.ZLUA_LUAEXE $env.ZLUA_SCRIPT $arg_mode $arg_subdir $arg_type $arg_inter ...$args + } else { + let zdest = (^$env.ZLUA_LUAEXE $env.ZLUA_SCRIPT --cd $arg_type $arg_subdir $arg_inter ...$args) + if $zdest != '' and ($zdest | path exists) { + cd $zdest + if _ZL_ECHO in $env and $env._ZL_ECHO != '' { + pwd + } + } + } + } +} +]] + +local script_init_nushell = [[ +$env.config = ($env | default {} config).config +$env.config = ($env.config | default {} hooks) +$env.config = ($env.config | update hooks ($env.config.hooks | default {} env_change)) +$env.config = ($env.config | update hooks.env_change ($env.config.hooks.env_change | default [] PWD)) +$env.config = ($env.config | update hooks.env_change.PWD ($env.config.hooks.env_change.PWD | append {|_, dir| _zlua --add $dir })) +]] + +local script_complete_nushell = [[ +let zlua_completer = {|spans| $spans | skip 1 | _zlua --complete -m1 ...$in | lines | where {|x| $x != $env.PWD}} + +$env.config = ($env.config | default {} completions) +$env.config = ($env.config | update completions ($env.config.completions | default {} external)) +$env.config = ($env.config | update completions.external ($env.config.completions.external | default true enable)) +if completer in $env.config.completions.external { + let orig_completer = $env.config.completions.external.completer + $env.config = ($env.config | update completions.external.completer { + {|spans| + match $spans.0 { + z => $zlua_completer, + _zlua => $zlua_completer, + _ => $orig_completer + } | do $in $spans + } + }) +} else { + $env.config = ($env.config | update completions.external.completer { + {|spans| + match $spans.0 { + z => $zlua_completer, + _zlua => $zlua_completer, + } | do $in $spans + } + }) +} +]] + +----------------------------------------------------------------------- +-- initialize nushell +----------------------------------------------------------------------- +function z_nushell_init(opts) + print('$env.ZLUA_LUAEXE = \'' .. os.interpreter() .. '\'') + print('$env.ZLUA_SCRIPT = \'' .. os.scriptname() .. '\'') + local prompt_hook = (not os.environ("_ZL_NO_PROMPT_COMMAND", false)) + if opts.clean ~= nil then + prompt_hook = false + end + print(script_zlua_nushell) + if prompt_hook then + print(script_init_nushell) + end + print(script_complete_nushell) + if opts.enhanced ~= nil then + print('$env._ZL_MATCH_MODE = 1') + end + if opts.once ~= nil then + print('$env._ZL_ADD_ONCE = 1') + end + if opts.echo ~= nil then + print('$env._ZL_ECHO = 1') + end + if opts.nc ~= nil then + print('$env._ZL_NO_CHECK = 1') + end +end + +----------------------------------------------------------------------- +-- help +----------------------------------------------------------------------- +function z_help() + local cmd = Z_CMD .. ' ' + print(cmd .. 'foo # cd to most frecent dir matching foo') + print(cmd .. 'foo bar # cd to most frecent dir matching foo and bar') + print(cmd .. '-r foo # cd to highest ranked dir matching foo') + print(cmd .. '-t foo # cd to most recently accessed dir matching foo') + print(cmd .. '-l foo # list matches instead of cd') + print(cmd .. '-c foo # restrict matches to subdirs of $PWD') + print(cmd .. '-e foo # echo the best match, don\'t cd') + print(cmd .. '-x path # remove path from history') + print(cmd .. '-i foo # cd with interactive selection') + print(cmd .. '-I foo # cd with interactive selection using fzf') + print(cmd .. '-b foo # cd to the parent directory starting with foo') + print(cmd .. '-b foo bar # replace foo with bar in cwd and cd there') +end + + +----------------------------------------------------------------------- +-- LFS optimize +----------------------------------------------------------------------- +os.lfs = {} +os.lfs.enable = os.getenv('_ZL_USE_LFS') +os.lfs.enable = '1' +if os.lfs.enable ~= nil then + local m = string.lower(os.lfs.enable) + if (m == '1' or m == 'yes' or m == 'true' or m == 't') then + os.lfs.status, os.lfs.pkg = pcall(require, 'lfs') + if os.lfs.status then + local lfs = os.lfs.pkg + os.path.exists = function (name) + return lfs.attributes(name) and true or false + end + os.path.isdir = function (name) + local mode = lfs.attributes(name) + if not mode then + return false + end + return (mode.mode == 'directory') and true or false + end + end + end +end + + +----------------------------------------------------------------------- +-- program entry +----------------------------------------------------------------------- +if not pcall(debug.getlocal, 4, 1) then + -- main script + z_init() + if windows and type(clink) == 'table' and clink.prompt ~= nil then + z_clink_init() + else + main() + end +end + +-- vim: set ts=4 sw=4 tw=0 noet : + diff --git a/.bin/z.lua/z.lua.plugin.zsh b/.bin/z.lua/z.lua.plugin.zsh new file mode 100644 index 0000000..2ccac99 --- /dev/null +++ b/.bin/z.lua/z.lua.plugin.zsh @@ -0,0 +1,39 @@ +#! /usr/bin/env zsh + +ZLUA_SCRIPT="${0:A:h}/z.lua" + +if [[ -n "$ZLUA_EXEC" ]] && ! which "$ZLUA_EXEC" &>/dev/null; then + echo "$ZLUA_EXEC not found" + ZLUA_EXEC="" +fi + +# search lua executable +if [[ -z "$ZLUA_EXEC" ]]; then + for lua in lua luajit lua5.4 lua5.3 lua5.2 lua5.1; do + ZLUA_EXEC="$(command -v "$lua")" + [[ -n "$ZLUA_EXEC" ]] && break + done + if [[ -z "$ZLUA_EXEC" ]]; then + echo "Not find lua in your $PATH, please install it." + return + fi +fi + +export _ZL_FZF_FLAG=${_ZL_FZF_FLAG:-"-e"} + +if [[ -z "$_ZL_ZSH_NO_FZF" ]]; then + eval "$($ZLUA_EXEC $ZLUA_SCRIPT --init zsh once enhanced fzf)" +else + eval "$($ZLUA_EXEC $ZLUA_SCRIPT --init zsh once enhanced)" +fi + +if [[ -z "$_ZL_NO_ALIASES" ]]; then + alias zz='z -i' + alias zc='z -c' + alias zf='z -I' + alias zb='z -b' + alias zbi='z -b -i' + alias zbf='z -b -I' + alias zh='z -I -t .' + alias zzc='zz -c' +fi diff --git a/.config/Brewfile b/.config/Brewfile index 554502f..682fdaf 100644 --- a/.config/Brewfile +++ b/.config/Brewfile @@ -36,6 +36,7 @@ brew "wget" brew "xdotool" brew "xprop" brew "xwininfo" +cask "affine" cask "agent-tars" cask "alacritty" cask "alcove" @@ -43,7 +44,6 @@ cask "android-studio" cask "appcleaner" cask "bettertouchtool" cask "cap" -cask "capacities" cask "cherry-studio" cask "clash-verge-rev" cask "coteditor" @@ -51,6 +51,8 @@ cask "cursor" cask "daisydisk" cask "dockdoor" cask "flutter" +cask "font-fira-code" +cask "font-fira-code-nerd-font" cask "font-jetbrains-mono" cask "font-jetbrains-mono-nerd-font" cask "font-maple-mono-nf-cn" @@ -72,6 +74,7 @@ cask "maccy" cask "masscode" cask "mihomo-party" cask "miniconda" +cask "mos" cask "musescore" cask "navicat-premium-lite" cask "notion" @@ -92,6 +95,7 @@ cask "snipaste" cask "spotify" cask "steam" cask "tagspaces" +cask "tailscale" cask "thor" cask "trae" cask "trae-cn" @@ -99,6 +103,7 @@ cask "ultimate-vocal-remover" cask "visual-studio-code" cask "wechatwebdevtools" cask "windsurf" +cask "wolai" cask "wpsoffice-cn" cask "zed" mas "Aspect Ratio", id: 1156930257 diff --git a/.config/rclone/rclone.conf b/.config/rclone/rclone.conf index 13e25dd..75c096a 100644 --- a/.config/rclone/rclone.conf +++ b/.config/rclone/rclone.conf @@ -1,12 +1,12 @@ [gd] type = drive scope = drive -token = {"access_token":"ya29.a0AZYkNZgxL9qXDm9KpfJxpFfd3gGXErk22oHQ6D9Bn0uSbS8H871-j7Jq_YuTBkUktOruShQ0XbDH3tPdZjspsiX4ze75x-2Z73npwlCYYFZA76tls5eYtcAaY2eMg5Hzdd1xVbgMS6pqDpSaW52cj_UOQdsZ74UPCkieTmzVAwaCgYKAUMSARQSFQHGX2MiW1oX_j-MtlFk2MLWdt5vhg0177","token_type":"Bearer","refresh_token":"1//06TiWE1qRgHJbCgYIARAAGAYSNwF-L9Ir03ZLbQ5mqZN2Cmx3UVDneqzkfxI2dqn9uHe5zJk9VZo9OCVd9XjYG1i84gMW7aXwSTY","expiry":"2025-04-21T21:27:08.43310467+08:00"} +token = {"access_token":"ya29.a0AW4XtxghIGG7lrwLfmb7QHNpsMz2QsPy8DHhP1VxkTTTO7xFuW6hCj5Iwe3cyrierTd97y4zDorgP4_hJDsBIg_w1D9zhlZ6KubcXkLmeDGuFhM_J4Q0CA5DWd0Qz3-8wst-bTjxqEpXQ0D7cfl7CvAyb02ENY-vTcOHsB3Q2toaCgYKAcYSARQSFQHGX2MiIVF3f4wLB-1kcH9e-ks_tg0178","token_type":"Bearer","refresh_token":"1//06TiWE1qRgHJbCgYIARAAGAYSNwF-L9Ir03ZLbQ5mqZN2Cmx3UVDneqzkfxI2dqn9uHe5zJk9VZo9OCVd9XjYG1i84gMW7aXwSTY","expiry":"2025-05-29T18:15:36.994872+08:00"} team_drive = [od] type = onedrive -token = {"access_token":"EwA4BMl6BAAUBKgm8k1UswUNwklmy2v7U/S+1fEAAZZ0PYIXlfgLSKs7rJjREEMzA/rYLkdjQvqU1yv9O2mN3vcvVE46WjO+3bIPqkH/uJwu8uB2f7ga9rj4OxJComucKid3g1n4bX6Pi4mpCpiW9zsLCxITfjU86c8hCqMXkCQKzemNnwgM27RvgngrH7QMpk2LGswAPbYVNJ5r3eYsEPk9CeHxHqQFqrhOkzE3pbNklytKPI3Kv3k9uPu4LXsVq3jrWLCZWkyXsaJi2pxwtBKxyHD3hQttHkzy4FEkRXPKarfok6bB0q4w9YJmmCk58RqLrXrLPy7X0+Vtv9CpaTz0uvmXXKSDEzkMKn5MnrH/2SyZ31jZ1Uu5YklpkmwQZgAAEFZUbqokNY61TKKECX9mt/UAA7Hl4zZQwBEqWE7VhTyzEPcU8jBv6X9XcFqU6JyEFiRc5TKcPg0Y1PHtMED+iL0RekDLy0Wth6gci1vgmEvE/EdNRbrHersucs4bpzlOasnCW3gJEOFFrnZC2DdVGpLjr40pnJJ0Pm0ohLprxE4uJh1crM6LbzhvslbAabr+i8nMU2yQ19SM2Yy8zQZSGEoXYYA9KM3i6gWF5qBsvFQ7RYQZUCtyMvpfBgKeTuVqSBsHO/gdi2boPvmNc5ZdfGOPjnU8AgtUhgGDDHKTE+iM/r51OCL8nXPMR5jmrdOF3yCGR+8VHm1KuZ1si4Gm3IXyZzEQLZL5zqZIa6OueJZVI4L1QiaVsirIsmN/hm0qHsG1yx+W6prKFQWXihYz0Y4pxdKrD+DUa3/ZI7S/84rsRmbz2z2AfR2ciLRaWdsIRXxiggPEwz6AllsQ2x3nbTTJDNDXq2ytYxh3Jrlp101bnMEI7DLF8vpaY0cBVacMOqFLscibvaERhwiT/yGScmiVS0tykYSCuLvJMlTVqVgJW7pFh5MezUnQ8fEHRjxuqGEXLwp/CYrVFO0oqTFoB6a/h31MKR8eYA3zRnlO61tCkfVfpbbfrmUkQWWeh5aC2exopTazzW67qjX4g7s+hW8Lap9YhiVbsY4M9fWWSXzmrnuc9BIzCdGsedoM2eEbLzEirRNrxElV2dULN6AACuW5+bDKIgJQ1xuW4P2kB09lPpBc/6jz4UDHgeiro5VXWEYdeqXJ409ES/VC286gmvl9JCyJdiCb+LREUBM4LH3VGRrT45c6UEyorW8/7jq7S3mcH2KG+yBuAjmIbnUSygeMpb1Ti27lR6MZeJ/Crj6Tw9WfaNOXUu2w5eYmw9kgrX/59IWhi0TW1Mfz5dHp9fAv/8UncorkZDtfoNsOQZsI2VsmLalGzvyaVZVu2Y7b/cRh7Jd/D5+ukO6y1bqH06LVZue5Wx54S5AZxHqpyCu1lYaz0PKd9lplcqXLeFkcTxDlegLdghnKQ2Qct7v9ZLb+2TkD","token_type":"Bearer","refresh_token":"M.C534_SN1.0.U.-CsNFZmVINToEW8*HVLD7xHaaV6GHyk0wnL!ETnyhqUMAFP!IvjPAV*eFuj45ftz7nGQuLR6EB2urd5JK*ln!9Rw9cK!1ezDZw8zWIQm3l4zGdCes!cPGpqlZvB4WDfNi69K1nlNS!uA!qn91PtmdDhPmKwFwvbgz9HDub!BoKdeK83pcAYqLTv!odEI*UzDgAjZv37Ki2YIbhH38xRmIaqmq8dZ3dISZSHCgZHmEGluaU2IPtYVKLp9rCu77fb7LVZu!PGG5lvu4J!pVlAL0d1RDdwh672WIFX*6sePFRdOWm4IR9tI8YbAl2Z7IproxDBA*3dI!JG0A*9QAWdCCE!YYZnWMjPGaZ!ASavda9ycF","expiry":"2025-05-12T03:28:14.136561914+08:00"} +token = {"access_token":"EwA4BMl6BAAUBKgm8k1UswUNwklmy2v7U/S+1fEAAdUGB8PTLMhDOiwG7uLu9QgSf67kcBVFbUZZT1ZnvB9yQibUe2/ok4+/I6pZrpexuKMZGcl+ffaaMhpZTS6nrIkj2dLbb9jq75KnQAP81lS9xqdVcV7QvKt6FTDZsHMQ2bFm0C6Eq6Z6U2YJEV2vWjjZY1Tb/jdZl5mGFzFbR90RsHq15tDmcudetde2U1tenlgslnjxhHkABZRxkiQitEVCX4aCaXhafwBo23FgOrUibdLXTxfuLrjRJUYFZj/fqqlD7ptWxA7mx2Xj1RJNmF34EuO5fd16q8+1QfsSolcSDguZS6QFKR7O0U/jxi8bB2C8B2d+PsrTNpsducu2wBEQZgAAEAKOWXq/X2g5vPVCvbJVMecAA7Z0nutP3cW2Kp3LfrqHUli+OwdKsucaCyBuuU2xF+SlvrdRrl0RdSIzGQveyLoKhrIgWzkEcLJm+58vj2oZ5fdguLUnJoJF4rF5mDARxHzbEEzHrrYBUBcrj/7HLl3PdkaIrHuQk2ScHNga4oarfLRgPZMlw8mOAL+HUZpI4hhowJYfpfEy0UWVeI45KYmWhhpnNzNxzyR/zLi5XaY2fsvTeG3AIzpdkKWkeuk3NBJk08sbOmGaKtRQfOtWTuZqwUULUAKkURt0mA2y+tIttfy78TNzSkoMnvUm6XoT1VeWr2o0knwXEfASHQ68hBUl34ImmFN+BynZNspe7mLQl86c0aYS9lEp37bfHIhVAV/08uJ3Q2AbFdgwx8SmyvO4dYFT/wOgZGa6rjXL8Z/guE0xJlFhAFROSzccUWmmXBjmjw7UllyaKlbn3pQxb/JyRHSOdeK4olBNxg29y5loPUznFxb7VEP+/uQCjPZBRaQrjwZrJzqChG+9o0dTeprVbaVvzp+wWgO7+Q6AJczhXAEjEF30HMNof1yQOiYhzJa6fi9hi5jwA92OQ5Uxfwaj+S4Ch7RIcQwFn6jpsolwhLPd+K4H8zD7Ut0MV41P+2d10njtgtBjoAtuKG7oGu0ITTJ8/pD/7P9fK+iknJ0wT3zl+LPHswPRIpOBPBK9BTLhEc/NaIGu/uLUmF8PAAs49FgoKd8J7YAdlZuXlJ2Z4E4Z/NnNzO6jfk3cVOFlJESOYw3jY9O0dKemDt65gi5vyTan8uF+doM8Rw321EKX1BlSaYVUdX16HV1VhWkBhqEVTs35vkHifHNPBIpmgzS4p9sxcosqnxJaLc/KdEE8QeLYCio9Zj1Hp+gpI0yLSGWNEOo6IN+lGGJD0MaYYXxiYSQWKaStSxM/TJgm/vyBwtuTBroM5JXIQldZZqd76B9kAm+XmUH13knd0ttufSCN0jnHnr5TObQGOoLm2UlquXF1ptrBNlOrTugFhfgoIwKKYzFuijvn+1w/8n5iknfEITkD","token_type":"Bearer","refresh_token":"M.C534_BAY.0.U.-Ck5DTsGaJoOnE0OwmKqR6nuWZvRX*tOhEBpK04yGFDzL1F**ZV7EsGseoZZfjgLm3LXbEa9kmXeGe1Qkfc3lz3pWTUhWzANJ2d!G4xNc22RNn7P2IIweANb6*t51MLAta0khsw*tla0vY4lraZdGGGIBwHVmrqzTokmmsP7yecQIn8WRMIyyTwygwFfhcBohpq7e1hIRh*Bpt8*Vi9OcChjdSCKoO7mZoOccw6*UaySgwB62NL7ByC2hZicfrh6eBTgiTbr54Ip78LmFVw4hivBpO3NAp9sr!j!G6mscR*fXgcHgBD*c*UwxdhuuXWwo5kbki3FDS9GESjMq7sv*!rMx*YxvId*defxL2OWgVpWg","expiry":"2025-05-30T02:53:16.186871+08:00"} drive_id = DA445CD50DBE653E drive_type = personal diff --git a/.gitmodules b/.gitmodules deleted file mode 100644 index e69de29..0000000 diff --git a/.oh-my-zsh b/.oh-my-zsh deleted file mode 160000 index 1af6bac..0000000 --- a/.oh-my-zsh +++ /dev/null @@ -1 +0,0 @@ -Subproject commit 1af6bace7b05a4d25fceb9006cd37f68d17141c8 diff --git a/.ssh/known_hosts b/.ssh/known_hosts index 96e6cef..b2ebb31 100644 --- a/.ssh/known_hosts +++ b/.ssh/known_hosts @@ -13,3 +13,6 @@ autops.eagle.local ssh-ed25519 AAAAC3NzaC1lZDI1NTE5AAAAICXyXL24LLKF0GAsVOAWqwkjz portal.eagle.local ssh-ed25519 AAAAC3NzaC1lZDI1NTE5AAAAIMYLIIUHcfd2hWVI2VQr1wDzHRgS8F5aRYE87uSn5fAV portal.eagle.local ecdsa-sha2-nistp256 AAAAE2VjZHNhLXNoYTItbmlzdHAyNTYAAAAIbmlzdHAyNTYAAABBBPv25Fr8/XHoRp8cS4p8gvt4xPjwJWT62MhCBvBCP/yfP3h3lbWCAJL8GZqrVxzXnnOqsW9Ggz+OvStmTEv0+v8= suzgitex.eagle.local ssh-ed25519 AAAAC3NzaC1lZDI1NTE5AAAAIMYLIIUHcfd2hWVI2VQr1wDzHRgS8F5aRYE87uSn5fAV +nas.local ssh-ed25519 AAAAC3NzaC1lZDI1NTE5AAAAIGrw1W1hB6yQ1VMbUthNH4b3yfNISDzHHiemAU3S3KlB +nas ssh-ed25519 AAAAC3NzaC1lZDI1NTE5AAAAIGrw1W1hB6yQ1VMbUthNH4b3yfNISDzHHiemAU3S3KlB +xxl.marsway.red ssh-ed25519 AAAAC3NzaC1lZDI1NTE5AAAAIKPK7TJzKpzuwH39jLPIRWjA6FehyG4x3cQAZjyUm2hz