230 Commits

Author SHA1 Message Date
laily f9e8a50bd9 Configure Vim for systemctl editing 2026-10-30 09:27:18 +08:00
laily 39cf5c6d19 fix: confirm SSH alias updates and keep key handling consistent 2026-10-19 16:57:44 +08:00
laily 59e2356f5c fix: pull ~/.dotfile automatically from ordinary ff commands
Daily ff subcommands never updated the repo; only `ff update` did.
Add a pouch-style interval auto-update at the ff entry (default 24h),
skip dirty/fail/timeout without aborting the current command, and
cover the skip/pull cases with a fish regression.
2026-10-13 16:25:05 +08:00
laily 2ede67d1c9 feat: add ff pouch to clone and install pouch
Clone https://git.yumee.top/laily/.pouch to ~/.pouch and run install.sh.
2026-10-13 16:01:34 +08:00
laily 4fc664d243 feat: split mise shared config from per-machine tools
Link mise/config.toml into ~/.config/mise/conf.d/dotfile.toml so
mise use -g writes the local config.toml instead of dirtying git.
2026-10-12 16:00:33 +08:00
laily 89ad569581 fix: select scan-ip network interface 2026-09-21 19:59:15 +08:00
laily cffe3f01f4 fix: 把 SSH config.d Include 写到 ~/.ssh/config 第一行
Include 落在 Host/Match 之后会被当成该块的条件引入。ssh2 与 ff ssh
改为检测顶层 Include,缺失时插入文件首行并去掉块内重复项。
2026-09-17 14:32:14 +08:00
laily 8c80e5b630 fix: 修正 server-security-check 的 SSH 误报并标注高风险
- PermitRootLogin: 接受 without-password 别名(OpenSSH -T 会输出该旧别名),
  避免把仅允许密钥的 root 登录误报为“无法确定安全状态”
- 检查未通过时列出配置文件中的相关设置行:sshd 只采用先读到的一条,
  主配置 Include 之前的 drop-in 以及 Match 块内的设置都会造成“改过但不生效”
- sshd -T 读取失败时同样给出配置文件设置,便于定位
- 参数匹配改为大小写不敏感
- 高风险整行红色、警告黄色,汇总同步着色;非 TTY 或 NO_COLOR 时禁用
2026-09-17 12:08:24 +08:00
laily 2060bbd23a feat: udpate fish 2026-09-16 21:44:25 +08:00
laily 0b2d2b6c6d feat(ff-ssh): add host from ssh args with ssh-copy-id
- ff ssh <ssh-args> parses target like ssh, runs ssh-copy-id, then
  writes ~/.ssh/config.d/<alias>.conf (HostName/User/Port/IdentityFile/
  ProxyJump/ProxyCommand); supports --no-copy, add and run subcommands
- record IdentityFile whenever -i / -o IdentityFile= is given and pass
  the same key to ssh-copy-id; omit it otherwise so ssh falls back to
  default identities
- ff ssh re-sources ff-ssh.fish each call, ff reload clears cached
  dotfile functions so edits take effect
- fix broken copy subcommand and ssh-arg parsing caused by
  --argument-names argv shadowing fish $argv
- fix ssh subcommand completion guards; wrap ssh completions for add/run
2026-09-16 15:51:48 +08:00
laily 0b04a4de50 feat: split git user identity by repo directory
Leave global name/email empty with useConfigOnly so unmatched repos cannot commit. Personal identity applies under ~/.dotfile, ~/code, and ~/.pouch; work identity stays in local ~/.gitconfig.work for ~/workrepo.
2026-09-14 14:55:44 +08:00
laily 097fce7827 fix: make git merge2 work across worktrees
Checkout of the target branch fails when it is already used by another
worktree. Detect that worktree and merge there instead.
2026-09-10 23:04:13 +08:00
laily a834c46b89 feat: update nvim dep 2026-09-08 14:35:58 +08:00
laily 2dedb9573c fix: invoke git credential helpers via absolute path
Apple Git osxkeychain exclusive-locks argv[0]. A relative name looks in
cwd and fails with "failed to lock git-credential-osxkeychain".
2026-09-08 11:39:25 +08:00
laily dc302bff1c fix: fail mise-install when curl pipeline errors
Do not print success after a failed curl | sh install; enable pipefail and verify mise is on PATH before continuing.
2026-09-08 11:34:46 +08:00
laily c95a1d70b1 fix: docker-install skip installed packages and suspend needrestart
Avoid upgrading already-installed packages and restarting unrelated
services on Ubuntu 24.04, where apt-pinvoke -m u overrides NEEDRESTART_MODE.
2026-08-29 14:37:36 +08:00
laily d6eee6465b feat: update mise 2026-08-28 14:49:34 +08:00
laily 2aba5d7162 feat: modify skiff to pouch 2026-08-25 15:32:09 +08:00
laily a0ea27099b feat: add upstream gitea 2026-08-10 12:22:52 +08:00
laily 8072e344ac feat: add ssh2 2026-08-04 14:59:54 +08:00
laily 4382de9ddc feat: improve scan-ip device identification 2026-07-28 15:47:24 +08:00
laily 8f2ebfc3de feat: add skiff to path 2026-07-27 17:48:01 +08:00
laily 6a92e31a90 feat: sudo by default 2026-07-25 22:16:38 +08:00
laily 045242e2c2 feat: sudo by default 2026-07-25 22:12:11 +08:00
laily 6e57abcec6 feat: sudo by default 2026-07-25 22:04:59 +08:00
laily 7654790973 feat: add ssh-harden 2026-07-25 21:57:16 +08:00
laily c974d858be feat: add ssh-harden 2026-07-25 21:49:20 +08:00
laily 5598ad5b3f feat: add ssh-harden 2026-07-25 21:33:52 +08:00
laily 9617bbbd27 feat: add ssh-harden 2026-07-25 21:27:49 +08:00
laily 1e12ae5c6e feat: add ssh-harden 2026-07-25 21:21:09 +08:00
laily b804e42ef1 feat: add ssh-harden 2026-07-25 21:13:15 +08:00
laily c5d99958d4 feat: add init.sh 2026-07-25 21:02:07 +08:00
laily db8a531b92 feat: add ssh-key-only 2026-07-25 20:58:38 +08:00
laily dc58f0c253 feat: modify scan-ip 2026-07-21 23:35:28 +08:00
laily a376995947 feat: update ghostty ssh term 2026-07-14 21:45:02 +08:00
laily ca88abbfc9 feat: add modify docker config 2026-07-02 12:33:40 +08:00
laily b08868cf16 feat: add sudo write 2026-06-27 14:28:18 +08:00
laily 8dcd6e3ac9 fix: add alias for script 2026-06-27 12:00:04 +08:00
laily a43e8bc885 feat: add git-setup 2026-06-27 11:55:41 +08:00
laily c12b212091 fix: init docker 2026-06-27 11:29:07 +08:00
laily 0548056ca4 fix: toggle-resion 2026-06-27 11:20:47 +08:00
laily acac6c3650 feat: update git credential 2026-06-26 11:18:08 +08:00
laily 783965d826 fix: make help.autocorrect compatible with old Git versions
Use numeric 0 in main config so legacy Git does not fail on parse; keep prompt in platform includes for Git 2.36+.

Co-authored-by: Cursor <cursoragent@cursor.com>
2026-06-26 11:15:24 +08:00
laily 9551225ee9 feat: update git credential 2026-06-26 11:10:23 +08:00
laily 84c7f37c53 docs: add AGENTS.md as shared dotfile maintenance conventions
Point Cursor and Claude rules at AGENTS.md for cross-tool agent guidance.

Co-authored-by: Cursor <cursoragent@cursor.com>
2026-06-25 15:19:25 +08:00
laily 67291a9a50 feat: add script catalog and -h help for all dotfile scripts
Introduce ff script -h with auto descriptions, shared help.sh, and usage flags across script/*.sh.

Co-authored-by: Cursor <cursoragent@cursor.com>
2026-06-25 15:09:35 +08:00
laily 1f6b042b26 feat: split git credential config by OS on Linux and macOS
Use includeIf so Linux prefers libsecret with cache fallback, while macOS keeps osxkeychain.

Co-authored-by: Cursor <cursoragent@cursor.com>
2026-06-25 11:04:15 +08:00
laily e8aae9738b feat: add nosudo 2026-06-25 10:46:07 +08:00
laily 0e7fe55474 fix: support ubuntu 2026-06-10 10:26:05 +08:00
laily ff420521ac feat: add script to change debian source 2026-06-04 15:32:13 +08:00
laily 77c9259ade fix: update docker mirror to tsinghua source 2026-06-04 15:26:12 +08:00
laily 81bebadff8 fix: init fish 2026-06-01 00:41:46 +08:00
laily 058c2e58af fix: <C-p> for sidekick 2026-06-01 00:26:19 +08:00
laily e63107f6ff fix: ff update 2026-06-01 00:23:39 +08:00
laily ec19670ebc feat: modify dirsize 2026-05-30 14:30:46 +08:00
laily d2f1ca33a0 feat: add dirsize 2026-05-30 14:21:17 +08:00
laily 21b12675e9 fix: dirsize 2026-05-30 13:42:51 +08:00
laily 609536a15c feat: add move window of tmux 2026-05-27 10:15:53 +08:00
laily cccf5fd750 feat: move windows 2026-05-27 09:50:02 +08:00
laily 64a059b8ec fix: modify sidekick width 2026-05-27 09:43:32 +08:00
laily 082781b4d2 feat: add tags 2026-05-26 17:51:15 +08:00
laily 4e8e3b7073 feat: add notify command, modify sidekick with tmux 2026-05-26 16:54:53 +08:00
laily c32e8b4015 feat: add mise config 2026-05-26 13:07:40 +08:00
laily 137e67503a feat: scan-ip support port 2026-05-25 21:36:14 +08:00
laily d82c9af650 feat: add ghostty 2026-05-24 14:14:38 +08:00
laily a53a3e0030 fix: unzip nvim package 2026-05-21 17:45:49 +08:00
laily 01998497e3 fix: scan-ip 2026-05-17 14:03:17 +08:00
laily 6abd1b7185 fix: neovim install support aarch64 2026-05-14 17:22:24 +08:00
laily b938a04538 fix: neovim install support aarch64 2026-05-14 17:17:37 +08:00
laily 27e5c11ea9 feat: add dirsize 2026-05-13 22:44:10 +08:00
laily 68e6780238 feat: add ghostty 2026-05-12 11:12:14 +08:00
laily e6bff850c3 feat: add lazygit install 2026-05-11 18:01:05 +08:00
laily c2439bfe72 feat: disable some features of snacks 2026-05-10 14:50:09 +08:00
laily fd0d59a7df fix: set zsh as default shell when init it 2026-05-10 14:13:37 +08:00
laily 9edcf1ffb4 fix: load telescopt fail 2026-05-10 14:04:01 +08:00
laily f8635b0969 feat: add zsh 2026-05-10 13:55:06 +08:00
laily 8e32f5c533 feat: update sidekick toggle 2026-05-10 13:44:39 +08:00
laily 34273ee6d5 feat: update toggle sidekick 2026-05-10 12:43:14 +08:00
laily 34f619c172 feat: update toggle region 2026-05-09 19:17:31 +08:00
laily 1101796d39 feat: update toggle region 2026-05-09 19:11:29 +08:00
laily 6be4389e2e feat: update toggle region 2026-05-09 19:08:35 +08:00
laily 89d808a864 feat: update toggle region 2026-05-09 19:02:21 +08:00
laily 67c5128840 feat: update toggle region 2026-05-09 18:32:47 +08:00
laily 2950f6b442 feat: update nvim config 2026-04-01 02:50:48 +08:00
laily 524dd75eb3 feat: update nvim config 2026-04-01 02:28:02 +08:00
laily 9fd296a777 feat: modify sidekick 2026-04-01 00:47:22 +08:00
laily 4ec54aeff7 feat: update nvim config 2026-03-31 16:26:32 +08:00
laily 3bc05c5abb feat: add glow 2026-03-30 12:13:18 +08:00
laily 61c1f088c7 feat: add journalctl --user 2026-03-24 11:35:15 +08:00
laily e4296ed34d feat: update fish 2026-03-24 11:02:45 +08:00
laily 478449d3a8 feat: update 2026-03-18 17:35:07 +08:00
laily df359c6e07 feat: add vim function 2026-03-18 17:34:47 +08:00
laily 96c923d6cc feat: update bigfiles search 2026-03-02 15:38:33 +08:00
laily 9587780c13 feat: add mise env 2026-02-12 10:53:38 +08:00
laily d58bfd63f4 fix: ctrl+p in neovim 2026-01-17 22:24:19 +08:00
laily ff4b73d8ac feat: add mise 2026-01-17 22:04:21 +08:00
laily fba3be8fd8 fix: nvim 2026-01-17 21:52:18 +08:00
laily 72fb4fc412 feat: update nvom 2026-01-17 14:45:11 +08:00
laily 108a8420f9 feat: update nvim and add gemini cli 2026-01-17 14:00:12 +08:00
laily 5e3ed80e39 feat: update nvim script 2026-01-17 13:38:17 +08:00
laily ddc76e7695 feat: update gitconfig 2025-12-26 11:50:12 +08:00
laily 9d15bcc3c4 feat: add scan-port script 2025-12-16 20:59:34 +08:00
laily f3064c2370 feat: support scanip 2025-12-16 15:33:54 +08:00
laily e4e6281742 fix: dc alias 2025-12-13 19:09:30 +08:00
laily 17b983ffb4 feat: support set debian apt source 2025-12-13 15:29:20 +08:00
laily 7f087b9e5f fix: cursor config 2025-12-02 16:23:42 +08:00
laily 5a78b07c51 feat: update 2025-12-01 19:55:54 +08:00
laily 8de370bdf5 feat: update readme 2025-11-29 19:22:45 +08:00
laily ab0812037d feat: add install mihomo 2025-11-29 19:18:33 +08:00
laily 1fe69f6c95 feat: support nvm(fnm) 2025-11-28 16:16:36 +08:00
laily 9bd43f9f9c feat: update git alias 2025-11-18 14:25:09 +08:00
laily d9bce3b9ee feat: modify golangci-lint config 2025-11-11 20:18:34 +08:00
laily 8de75b7faf feat: update golangci-lint config 2025-11-11 20:10:47 +08:00
laily 78b6f9e6c5 feat: add build_script 2025-10-22 11:40:12 +08:00
laily d80f88dbb4 feat: add ff update 2025-10-19 18:13:44 +08:00
laily 01029777d5 feat: update ff command 2025-10-19 18:06:50 +08:00
laily 03b384b819 feat: add command 2025-10-19 18:02:14 +08:00
laily 2bd26fc9ef feat: 增强用户密码设置功能,支持交互式和非交互式环境 2025-07-17 12:23:23 +08:00
laily 246f230b7f feat: 更新代理设置并添加用户初始化脚本 2025-07-17 12:18:01 +08:00
laily 4bf69165e0 feat: modify filename 2025-07-10 11:08:16 +08:00
laily 239640a996 feat: update git and vscode config 2025-07-09 11:21:21 +08:00
laily 2b04c95e48 feat: add vscode keybinding 2025-06-20 21:14:53 +08:00
laily aab4f145ce feat: add pnpm 2025-06-19 16:25:40 +08:00
laily 1189ac8fd1 fix: install golang 2025-02-26 17:24:45 +08:00
laily bbb57da4b5 feat: modify github git config 2025-02-26 17:20:21 +08:00
laily 7df9df1801 feat: modify github git config 2025-02-26 17:10:44 +08:00
laily a55e3ca1e2 feat: add gvm 2025-02-19 17:21:00 +08:00
laily 6c0d9c3d1d feat: add docker compose alias 2025-01-15 17:37:43 +08:00
laily 21058acac5 feat: add docker compose alias 2025-01-15 17:32:26 +08:00
laily 490e9eff3a feat: add docker compose alias 2025-01-15 17:29:00 +08:00
laily ffc4fcd91f fix: add shortcut for tmux mouse on 2025-01-08 11:24:52 +08:00
laily 0bcae45cd9 fix: script function 2024-11-27 11:41:10 +08:00
laily d72b02118c Merge branch 'main' of github.com:hilaily/.dotfile 2024-11-27 11:30:06 +08:00
laily bb752943a5 fix: update fish script 2024-11-27 11:29:56 +08:00
laily d3f12850bd fix: merge 2024-11-22 11:20:48 +00:00
laily 878d67c8e4 Merge branch 'main' of github.com:hilaily/.dotfile 2024-11-18 16:47:35 +08:00
laily 3f7fabc88a chore: update plugin 2024-11-18 16:47:14 +08:00
laily 5dca7835bf fix: iterm mouse scroll 2024-11-18 16:40:30 +08:00
laily cad5e667f8 fix: docker install 2024-11-18 16:32:16 +08:00
laily 11aeacdc88 fix: nvim install 2024-11-18 16:30:29 +08:00
laily d6135e1ac5 feat: add docker-install.sh 2024-11-17 20:11:55 +08:00
laily 8f4ebb0071 fix: fish 2024-11-11 15:58:55 +08:00
laily 5517cd4186 fix: fish 2024-11-11 15:56:56 +08:00
laily f2da334604 fix: mason 2024-11-11 00:27:45 +08:00
laily 0ceb4dc061 fix: modify font 2024-11-10 23:50:11 +08:00
laily eddda52c71 Merge branch 'main' of github.com:hilaily/.dotfile 2024-11-10 23:49:57 +08:00
laily 3c370d80cc fix: 2024-11-10 23:49:52 +08:00
laily 4f10716dcd fix: merge 2024-11-07 11:14:04 +08:00
laily 25871a335b fix: nvim config 2024-11-06 23:23:01 +08:00
laily 80d0449402 fix: install python 2024-11-06 23:09:01 +08:00
laily 041f993017 fix: add command reset-proxy 2024-11-06 23:01:01 +08:00
laily aec3961983 feat: add font 2024-11-06 22:57:50 +08:00
laily efdbffa489 fix: modify font 2024-11-06 18:01:38 +08:00
laily 6ac8de0999 feat: add install firacode 2024-11-06 12:50:59 +08:00
laily dfa04656dd feat: add nvim init 2024-11-05 12:07:29 +08:00
laily 89b7a7c969 Merge branch 'main' of github.com:hilaily/.dotfile 2024-11-04 14:11:51 +08:00
laily 12af360c37 fix: node path 2024-11-01 17:44:30 +08:00
laily 66a781c5d0 feat: modify git remote 2024-10-25 18:14:23 +08:00
laily eaa9c6d924 feat: add iterm2 2024-10-25 18:13:08 +08:00
laily ff4629cafe feat: modify install nvim 2024-10-25 18:02:02 +08:00
laily 8b609eb959 feat: add croncheck.sh 2024-10-15 11:08:30 +08:00
laily b6dc6b839e feat: add sudoo 2024-09-26 11:11:03 +08:00
laily ddb09fb2cf fix: modify gitignore 2024-08-12 14:29:32 +08:00
laily eeb97adf94 feat: add neotest 2024-08-07 15:14:42 +08:00
laily 67c9d89f41 fix: add alias 2024-08-05 17:25:38 +08:00
laily 30dfd1a02d Merge branch 'main' of github.com:hilaily/.dotfile 2024-08-03 17:56:33 +08:00
laily 9d0b63a2e9 Merge branch 'main' of https://github.com/hilaily/.dotfile into main 2024-08-03 08:05:55 +00:00
laily f139016c25 fix 2024-08-03 16:03:23 +08:00
laily a159a4179e fix 2024-08-03 16:02:45 +08:00
laily 8991b192ea temp 2024-08-03 15:42:13 +08:00
laily 8241518dd2 fix: add resurrect for tmux 2024-07-29 11:43:39 +08:00
laily 4812514474 fix: outline width 2024-07-22 17:06:30 +08:00
laily 3ba2f0b62c fix: git editor 2024-07-22 08:54:47 +00:00
laily c22f3b28b0 fix: merge 2024-07-22 08:54:04 +00:00
laily 8433ab381e fix: modify per 2024-07-22 08:53:49 +00:00
laily 5a9193eb38 fix: modify useless plugin 2024-07-19 17:04:44 +08:00
laily dc376f31e3 fix: remove useless code 2024-06-28 15:33:38 +08:00
laily 0247a4c2a0 merge 2024-06-22 00:09:57 +08:00
laily a64b47a65f feat: add shortcut of swith pane 2024-06-20 15:33:12 +08:00
laily 785d8bde8c feat: add tpm for tmux 2024-06-19 12:22:18 +08:00
laily d97fb14d1f fix: modify mason server 2024-06-12 17:49:36 +08:00
laily 8c5df554ed chrore: update fish script 2024-06-11 23:06:19 +08:00
laily a9f48d2d59 fix: add git alias 2024-06-11 17:53:43 +08:00
laily 7bb20c64e2 fix: modify add to path 2024-06-08 17:16:53 +08:00
laily fd96710321 fix: modify fish/git init script 2024-06-08 16:24:22 +08:00
laily f0186eaefb feat: add script 2024-06-05 19:37:18 +08:00
laily 6283ab3cfb feat: support dap 2024-05-21 14:22:14 +08:00
laily 22b880e642 fix: modify gopls for completion 2024-05-08 20:20:43 +08:00
laily 2a68d143a8 fix: modify lsp 2024-05-04 15:47:07 +08:00
laily 05053a87b7 fix: disable auto format 2024-04-30 17:43:53 +08:00
laily 27dff38b7c fix: new lsp 2024-04-29 19:06:42 +08:00
laily cabec5bd63 fix: rm unused files 2024-03-22 15:48:03 +08:00
laily e430d2a721 fix: add env 2024-03-22 11:06:57 +08:00
laily a48619031d fix: modify nvim config 2024-03-22 11:05:02 +08:00
laily 7ef3a2f073 fix: jump 2024-02-29 14:52:44 +08:00
laily ddda0a2a02 fix: save lazy-lock 2024-02-29 10:54:55 +08:00
laily 685b477e1b fix: plugin 2024-02-28 22:01:26 +08:00
laily dc01c402c8 fix: modify plugin directory 2024-02-28 16:38:02 +08:00
laily b1698fb7cf fix: update telegraf 2024-02-22 20:26:43 +08:00
laily fad2c5fed9 fix: update telegraf 2024-02-22 20:17:41 +08:00
laily 23c83906b5 feat: add prettier 2024-02-21 20:54:58 +08:00
laily 820d501911 fix: c-p to search file 2024-02-21 17:41:48 +08:00
laily 0b7374d42a feat: add new plugin 2024-02-20 21:52:06 +08:00
laily b49026c12e feat: new config 2024-02-20 21:19:43 +08:00
laily 3fad1b11f4 feat: update gitcofig 2024-02-20 19:24:22 +08:00
laily b63fb4494c feat: add fish 2024-02-05 15:28:56 +08:00
laily a103031734 fix: modify nvim-tree 2023-11-06 14:58:22 +08:00
laily f12a4caccb fix: update autocommands 2023-09-15 14:10:38 +08:00
laily fa8b291b1d feat: modify some script 2023-09-13 14:21:52 +08:00
laily 47f16510b2 feat: modify for v0.9.2 2023-09-13 12:29:12 +08:00
laily d2aa7bad86 feat: modify git config 2023-09-07 18:03:21 +08:00
laily 9b9a929281 fix(ci): modify err check 2023-07-31 19:50:17 +08:00
laily e10a850527 feat: add plugin nvim lint 2023-04-27 11:43:55 +08:00
laily 7d83cc0d6e feat: add comment ci 2023-04-26 17:35:00 +08:00
laily d42ae1bb29 fix: linter 2023-04-21 12:24:49 +08:00
laily 96f4181e5f fix: linter 2023-04-21 12:05:30 +08:00
laily 53ab00c196 fix: workrepo 2023-04-19 11:32:29 +08:00
laily 1def7fc888 fix: add alias 2023-04-19 10:57:07 +08:00
laily c20d5f6024 fix: add a command 2023-03-21 15:13:21 +08:00
laily 6c36082c4f merge: master 2023-03-21 14:58:18 +08:00
laily 2072b48df7 fix: modify sub gitconfig 2023-03-21 14:53:18 +08:00
laily 83dbf07703 fix: modify sub gitconfig 2023-03-21 14:48:19 +08:00
laily 1fd0cdaf63 fix: update linux brew 2023-03-14 15:37:04 +08:00
laily 06cf9bae2c fix: add lock file 2023-01-03 11:53:59 +08:00
laily 77d16ce217 fix: modify nvim colorscheme 2022-12-30 14:10:56 +08:00
laily 8bc810fd39 fix: modify nvim plugin 2022-12-30 12:02:36 +08:00
laily 3180d287e7 Merge branch 'master' of github.com:hilaily/.dotfile 2022-12-30 11:35:23 +08:00
laily 46a920d574 fix: modify alias 2022-12-30 11:35:17 +08:00
laily 9a61a98643 fix: modify client.resolved_capabilities to client.server_capabilities 2022-12-01 21:12:05 +08:00
laily c92d815b3f 1. update nvim config of version 0.8.1
2. modify brew environment
2022-12-01 21:06:07 +08:00
204 changed files with 13612 additions and 4899 deletions
+46
View File
@@ -0,0 +1,46 @@
.PHONY: image tag version
# 默认架构
GOOS ?= linux
GOARCH ?= $(shell go env GOARCH)
# 常用架构组合
PLATFORMS ?= linux/amd64,linux/arm64
# Docker 构建
docker-local:
$(MAKE) check-image-name
GOOS=$(GOOS) GOARCH=$(GOARCH) .build_scripts/build_go.sh
GOOS=$(GOOS) GOARCH=$(GOARCH) .build_scripts/build_image.sh $(IMAGE_NAME) Dockerfile.local
docker-local-amd64:
$(MAKE) check-image-name
GOOS=linux GOARCH=amd64 .build_scripts/build_go.sh
GOOS=linux GOARCH=amd64 .build_scripts/build_image.sh $(IMAGE_NAME) Dockerfile.local
docker-local-arm64:
$(MAKE) check-image-name
GOOS=linux GOARCH=arm64 .build_scripts/build_go.sh
GOOS=linux GOARCH=arm64 .build_scripts/build_image.sh $(IMAGE_NAME) Dockerfile.local
docker-local-multiarch:
$(MAKE) check-image-name
make build-all-multiarch
PLATFORM=$(PLATFORMS) .build_scripts/build_image.sh $(IMAGE_NAME) Dockerfile.local
check-image-name:
@if [ -z "$(IMAGE_NAME)" ]; then \
echo "IMAGE_NAME is not set"; \
exit 1; \
fi; \
tag:
@VERSION=$$(cat VERSION); \
echo "Creating tag: $$VERSION"; \
git tag $$VERSION; \
git push origin $$VERSION; \
echo "Tag $$VERSION created and pushed successfully"
version: .build_scripts/version.sh
@.build_scripts/version.sh "$(filter-out version,$(MAKECMDGOALS))" "$(VERSION)"
+250
View File
@@ -0,0 +1,250 @@
# Scripts 使用说明
这个目录包含项目的构建和版本管理脚本。
## 文件说明
- `Makefile`: 构建、推送 Docker 镜像和版本管理的自动化脚本
- `version.sh`: 版本号管理脚本
## Makefile 使用
### 可配置的环境变量
- `IMAGE_NAME`: Docker 镜像名称
### 独立使用
#### 1. 使用默认镜像名
```bash
cd scripts
make image
```
#### 2. 使用自定义镜像名
```bash
cd scripts
IMAGE_NAME=myregistry/myimage make image
```
或者:
```bash
export IMAGE_NAME=myregistry/myimage
cd scripts
make image
```
### 在其他 Makefile 中引用
有多种方式可以在其他 Makefile 中引用这个 Makefile:
#### 方式一:使用 `$(MAKE) -C` 调用子目录(推荐)
在项目根目录创建主 Makefile:
```makefile
.PHONY: build-image
# 定义镜像名称
IMAGE_NAME = myregistry/myproject
# 调用 scripts 目录下的 Makefile
build-image:
IMAGE_NAME=$(IMAGE_NAME) $(MAKE) -C scripts image
# 或者使用 export 导出环境变量
build-image-export:
export IMAGE_NAME=$(IMAGE_NAME) && $(MAKE) -C scripts image
```
使用:
```bash
make build-image
```
#### 方式二:使用 `include` 包含
在项目根目录的 Makefile 中:
```makefile
# 设置变量(会覆盖被包含文件中的默认值)
IMAGE_NAME = myregistry/myproject
# 包含 scripts 目录的 Makefile
include scripts/Makefile
# 现在可以直接调用 image 目标
.PHONY: build
build: image
```
使用:
```bash
make build
```
**注意**: 使用 `include` 时,相对路径会基于主 Makefile 的位置,可能需要调整路径。
#### 方式三:环境变量传递(跨多层 Makefile)
根目录 `Makefile`:
```makefile
.PHONY: all
# 设置并导出环境变量,使所有子 make 都能访问
export IMAGE_NAME = myregistry/myproject
export VERSION = v1.0.0
all:
@echo "Building with IMAGE_NAME=$(IMAGE_NAME)"
$(MAKE) -C scripts image
```
#### 方式四:通过命令行传递多个变量
```bash
IMAGE_NAME=myregistry/myimage VERSION=v2.0.0 make -C scripts image
```
### 完整示例
假设项目结构:
```
/project-root
├── Makefile # 主 Makefile
├── scripts/
│ ├── Makefile # 本文件
│ └── version.sh
└── ...
```
主 `Makefile` 内容示例:
```makefile
.PHONY: help build-backend build-frontend build-all
# 项目配置
PROJECT_NAME = myproject
REGISTRY = docker.io/myorg
BACKEND_IMAGE = $(REGISTRY)/$(PROJECT_NAME)-backend
FRONTEND_IMAGE = $(REGISTRY)/$(PROJECT_NAME)-frontend
help:
@echo "Available targets:"
@echo " build-backend - Build and push backend image"
@echo " build-frontend - Build and push frontend image"
@echo " build-all - Build and push all images"
build-backend:
@echo "Building backend image..."
IMAGE_NAME=$(BACKEND_IMAGE) $(MAKE) -C scripts image
build-frontend:
@echo "Building frontend image..."
IMAGE_NAME=$(FRONTEND_IMAGE) $(MAKE) -C scripts image
build-all: build-backend build-frontend
@echo "All images built successfully!"
# 也可以创建版本管理的快捷方式
version:
$(MAKE) -C scripts version $(filter-out $@,$(MAKECMDGOALS))
# 允许传递额外参数
%:
@:
```
使用示例:
```bash
# 构建后端镜像
make build-backend
# 构建前端镜像
make build-frontend
# 构建所有镜像
make build-all
# 版本管理
make version patch
make version v1.2.3
```
## 版本管理
### 基本用法
```bash
# 查看当前版本和帮助
make version
# 设置特定版本
make version v1.0.0
# 或者
make version VERSION=v1.0.0
# 递增版本
make version patch # v1.0.0 -> v1.0.1
make version minor # v1.0.0 -> v1.1.0
make version major # v1.0.0 -> v2.0.0
# 预发布版本
make version pre # v1.0.0 -> v1.0.0-rc.1
make version pre alpha # v1.0.0 -> v1.0.0-alpha.1
make version pre beta # v1.0.0 -> v1.0.0-beta.1
make version pre rc 2 # v1.0.0 -> v1.0.0-rc.2
```
## 创建标签
```bash
# 基于 VERSION 文件创建并推送 Git 标签
make tag
```
## 注意事项
1. **环境变量优先级**:
- 命令行传递 > 父 Makefile export > 本 Makefile 默认值
2. **工作目录**:
- 使用 `$(MAKE) -C scripts` 会切换到 scripts 目录执行
- 注意脚本中的相对路径问题
3. **变量传递**:
- `IMAGE_NAME=xxx $(MAKE)`: 只对当前 make 有效
- `export IMAGE_NAME=xxx; $(MAKE)`: 对所有子 make 有效
- `export IMAGE_NAME=xxx` 后再调用: 对整个 shell 会话有效
4. **调试技巧**:
```bash
# 查看实际使用的变量值
make -C scripts image -n # 干运行,只显示命令不执行
```
## 扩展建议
如果需要更复杂的配置,可以创建配置文件:
```makefile
# config.mk
PROJECT_NAME = myproject
REGISTRY = docker.io/myorg
VERSION = v1.0.0
# 在主 Makefile 中
include config.mk
export IMAGE_NAME = $(REGISTRY)/$(PROJECT_NAME)
```
这样可以将配置集中管理,便于维护。
+35
View File
@@ -0,0 +1,35 @@
#!/bin/bash
# Build Go binary
# Args:
# 1. OUTPUT_DIR: output directory, default is ./output, the full output directory is $OUTPUT_DIR/$GOOS-$GOARCH
set -e
if [ -z $GOOS ]; then
echo "GOOS is not set"
exit 1
fi
if [ -z $GOARCH ]; then
echo "GOARCH is not set"
exit 1
fi
OUTPUT_DIR=${1:-./output/$GOOS-$GOARCH}
mkdir -p $OUTPUT_DIR/
go build -o $OUTPUT_DIR/server ./cmd/
if [ -d ./conf ]; then
mkdir -p $OUTPUT_DIR/conf/
cp -r ./conf/* $OUTPUT_DIR/conf/
fi
if [ -d ./scripts ]; then
mkdir -p $OUTPUT_DIR/scripts/
cp -r ./scripts/* $OUTPUT_DIR/scripts/
fi
if [ -f ./scripts/Makefile ]; then
cp ./scripts/Makefile $OUTPUT_DIR/
fi
+30
View File
@@ -0,0 +1,30 @@
#!/bin/bash
# Build Docker image
# Args:
# 1. IMAGE_NAME: image name, required
# 2. DOCKER_FILE: docker file, default is Dockerfile
set -e
IMAGE_NAME=$1
DOCKER_FILE=${2:-Dockerfile}
if [ -z "$IMAGE_NAME" ]; then
echo "IMAGE_NAME is not set";
exit 1;
fi;
VERSION=$(cat VERSION);
echo "Building image: $IMAGE_NAME:$VERSION with Dockerfile: $DOCKER_FILE";
docker build -t $IMAGE_NAME:$VERSION -f $DOCKER_FILE .;
docker push $IMAGE_NAME:$VERSION;
if [[ "$VERSION" != *"-"* ]]; then
echo "This is a stable release, also tagging as latest";
docker tag $IMAGE_NAME:$VERSION $IMAGE_NAME:latest;
docker push $IMAGE_NAME:latest;
echo "Latest tag pushed successfully";
else
echo "This is a pre-release version, skipping latest tag";
fi;
echo "Image built and pushed successfully: $IMAGE_NAME:$VERSION"
+26
View File
@@ -0,0 +1,26 @@
#!/bin/bash
# Build Web
set -e
if [ -z $GOOS ]; then
echo "GOOS is not set"
exit 1
fi
if [ -z $GOARCH ]; then
echo "GOARCH is not set"
exit 1
fi
OUTPUT_DIR=${1:-./output/$GOOS-$GOARCH}
cd web
pnpm install
pnpm build
cd ..
mkdir -p $OUTPUT_DIR/
rm -rf $OUTPUT_DIR/web/*
mkdir -p $OUTPUT_DIR/web/
cp -r web/out/* $OUTPUT_DIR/web/
+152
View File
@@ -0,0 +1,152 @@
#!/bin/bash
# Version management script
# Args:
# 1. ARGS: arguments, optional
# 2. VERSION_VAR: version variable, optional
set -e
ARGS="$1"
VERSION_VAR="$2"
CURRENT_VERSION=$(cat VERSION)
show_usage() {
echo "Current version: $CURRENT_VERSION"
echo "Usage:"
echo " make version VERSION=v1.0.0 # Set specific version"
echo " make version v1.0.0 # Set specific version"
echo " make version patch # Increment patch version"
echo " make version minor # Increment minor version"
echo " make version major # Increment major version"
echo " make version pre # Add/increment pre-release (default: rc)"
echo " make version pre alpha # Add alpha pre-release"
echo " make version pre beta # Add beta pre-release"
echo " make version pre rc # Add rc pre-release"
echo " make version pre alpha 2 # Add specific alpha version"
}
parse_version() {
local version="$1"
# Remove 'v' prefix and split by '-'
local clean_version="${version#v}"
local base_version="${clean_version%-*}"
local pre_release=""
if [[ "$clean_version" == *"-"* ]]; then
pre_release="${clean_version#*-}"
fi
# Split base version
IFS='.' read -r major minor patch <<< "$base_version"
echo "$major $minor $patch $pre_release"
}
increment_version() {
local type="$1"
local version_parts
read -r major minor patch pre_release <<< "$(parse_version "$CURRENT_VERSION")"
case "$type" in
"patch")
patch=$((patch + 1))
echo "v$major.$minor.$patch"
;;
"minor")
minor=$((minor + 1))
patch=0
echo "v$major.$minor.$patch"
;;
"major")
major=$((major + 1))
minor=0
patch=0
echo "v$major.$minor.$patch"
;;
esac
}
handle_prerelease() {
local args=($1)
local version_parts
read -r major minor patch pre_release <<< "$(parse_version "$CURRENT_VERSION")"
local base_version="$major.$minor.$patch"
local pre_type="rc"
local pre_num="1"
# Parse arguments
if [[ ${#args[@]} -ge 3 ]]; then
# make version pre alpha 2 (明确指定版本号)
pre_type="${args[1]}"
pre_num="${args[2]}"
elif [[ ${#args[@]} -ge 2 ]]; then
# make version pre alpha (指定类型,智能处理版本号)
pre_type="${args[1]}"
if [[ -n "$pre_release" ]]; then
# 当前已经是 pre-release
IFS='.' read -r current_type current_num <<< "$pre_release"
if [[ "$current_type" == "$pre_type" ]]; then
# 相同类型,递增版本号
pre_num=$((current_num + 1))
else
# 不同类型,重置为 1
pre_num="1"
fi
else
# 当前是正式版本,从 1 开始
pre_num="1"
fi
else
# make version pre (智能处理)
if [[ -n "$pre_release" ]]; then
# 当前已经是 pre-release,递增版本号
IFS='.' read -r current_type current_num <<< "$pre_release"
pre_type="$current_type"
pre_num=$((current_num + 1))
else
# 当前是正式版本,添加默认 pre-release
pre_type="rc"
pre_num="1"
fi
fi
echo "v$base_version-$pre_type.$pre_num"
}
update_version() {
local new_version="$1"
echo "Updating version from $CURRENT_VERSION to $new_version"
echo "$new_version" > VERSION
git add .
git commit -m "update version to $new_version"
git push origin HEAD
echo "[$new_version] Version file updated and pushed successfully"
}
main() {
if [[ -n "$ARGS" ]]; then
local first_arg="${ARGS%% *}"
case "$first_arg" in
"patch"|"minor"|"major")
new_version=$(increment_version "$first_arg")
;;
"pre")
new_version=$(handle_prerelease "$ARGS")
;;
*)
new_version="$first_arg"
;;
esac
update_version "$new_version"
elif [[ -n "$VERSION_VAR" ]]; then
update_version "$VERSION_VAR"
else
show_usage
fi
}
main "$@"
+10
View File
@@ -0,0 +1,10 @@
---
description: dotfile 维护约定(详见 AGENTS.md)
alwaysApply: true
---
# dotfile 项目
修改本仓库前请先阅读并遵循根目录 **[AGENTS.md](../AGENTS.md)** 中的维护约定(目录结构、`*-install` / `*-init`、脚本 `-h`、`ff` 入口、软链 init 等)。
AGENTS.md 为跨工具单一事实来源(Cursor、Claude Code、Codex 等均可使用)。
+8
View File
@@ -2,3 +2,11 @@
.SpaceVim.d/init.vim
.DS_Store
nvim/plugin
_set_env_*.sh
_set_env.sh
log
cn.sh
__pycache__
Makefile
.claude/
script/clash.sh
-356
View File
@@ -1,356 +0,0 @@
run:
tests: false
timeout: 1m
linters:
enable:
- govet
- staticcheck
- unused
- ineffassign
- typecheck
- exportloopref
- bodyclose
- gofmt
- stylecheck
- revive
- errorlint # finds code that will cause problems with the error wrapping scheme introduced in Go 1.13
disable-all: true
linters-settings:
staticcheck:
# SAxxxx checks in https://staticcheck.io/docs/configuration/options/#checks
checks: ["all"]
revive:
# Maximum number of open files at the same time.
# See https://github.com/mgechev/revive#command-line-flags
# Defaults to unlimited.
max-open-files: 2048
# When set to false, ignores files with "GENERATED" header, similar to golint.
# See https://github.com/mgechev/revive#available-rules for details.
# Default: false
ignore-generated-header: true
# Sets the default severity.
# See https://github.com/mgechev/revive#configuration
# Default: warning
severity: error
# Enable all available rules.
# Default: false
enable-all-rules: true
# Sets the default failure confidence.
# This means that linting errors with less than 0.8 confidence will be ignored.
# Default: 0.8
confidence: 0.1
rules:
# https://github.com/mgechev/revive/blob/master/RULES_DESCRIPTIONS.md#add-constant
- name: add-constant
severity: warning
disabled: false
arguments:
- maxLitCount: "3"
allowStrs: '""'
allowInts: "0,1,2"
allowFloats: "0.0,0.,1.0,1.,2.0,2."
# https://github.com/mgechev/revive/blob/master/RULES_DESCRIPTIONS.md#argument-limit
- name: argument-limit
severity: warning
disabled: false
arguments: [4]
# https://github.com/mgechev/revive/blob/master/RULES_DESCRIPTIONS.md#atomic
- name: atomic
severity: warning
disabled: false
# https://github.com/mgechev/revive/blob/master/RULES_DESCRIPTIONS.md#banned-characters
- name: banned-characters
severity: warning
disabled: false
arguments: ["Ω", "Σ", "σ", "7"]
# https://github.com/mgechev/revive/blob/master/RULES_DESCRIPTIONS.md#bare-return
- name: bare-return
severity: warning
disabled: false
# https://github.com/mgechev/revive/blob/master/RULES_DESCRIPTIONS.md#blank-imports
- name: blank-imports
severity: warning
disabled: false
# https://github.com/mgechev/revive/blob/master/RULES_DESCRIPTIONS.md#bool-literal-in-expr
- name: bool-literal-in-expr
severity: warning
disabled: false
# https://github.com/mgechev/revive/blob/master/RULES_DESCRIPTIONS.md#call-to-gc
- name: call-to-gc
severity: warning
disabled: false
# https://github.com/mgechev/revive/blob/master/RULES_DESCRIPTIONS.md#cognitive-complexity
- name: cognitive-complexity
severity: warning
disabled: false
arguments: [7]
# https://github.com/mgechev/revive/blob/master/RULES_DESCRIPTIONS.md#confusing-naming
- name: confusing-naming
severity: warning
disabled: false
# https://github.com/mgechev/revive/blob/master/RULES_DESCRIPTIONS.md#confusing-results
- name: confusing-results
severity: warning
disabled: false
# https://github.com/mgechev/revive/blob/master/RULES_DESCRIPTIONS.md#constant-logical-expr
- name: constant-logical-expr
severity: warning
disabled: false
# https://github.com/mgechev/revive/blob/master/RULES_DESCRIPTIONS.md#context-as-argument
- name: context-as-argument
severity: warning
disabled: false
# https://github.com/mgechev/revive/blob/master/RULES_DESCRIPTIONS.md#context-keys-type
- name: context-keys-type
severity: warning
disabled: false
# https://github.com/mgechev/revive/blob/master/RULES_DESCRIPTIONS.md#cyclomatic
- name: cyclomatic
severity: warning
disabled: false
arguments: [3]
# https://github.com/mgechev/revive/blob/master/RULES_DESCRIPTIONS.md#datarace
- name: datarace
severity: warning
disabled: false
# https://github.com/mgechev/revive/blob/master/RULES_DESCRIPTIONS.md#deep-exit
- name: deep-exit
severity: warning
disabled: false
# https://github.com/mgechev/revive/blob/master/RULES_DESCRIPTIONS.md#defer
- name: defer
severity: warning
disabled: false
arguments:
- ["call-chain", "loop"]
# https://github.com/mgechev/revive/blob/master/RULES_DESCRIPTIONS.md#dot-imports
- name: dot-imports
severity: warning
disabled: false
# https://github.com/mgechev/revive/blob/master/RULES_DESCRIPTIONS.md#duplicated-imports
- name: duplicated-imports
severity: warning
disabled: false
# https://github.com/mgechev/revive/blob/master/RULES_DESCRIPTIONS.md#early-return
- name: early-return
severity: warning
disabled: false
# https://github.com/mgechev/revive/blob/master/RULES_DESCRIPTIONS.md#empty-block
- name: empty-block
severity: warning
disabled: false
# https://github.com/mgechev/revive/blob/master/RULES_DESCRIPTIONS.md#empty-lines
- name: empty-lines
severity: warning
disabled: false
# https://github.com/mgechev/revive/blob/master/RULES_DESCRIPTIONS.md#error-naming
- name: error-naming
severity: warning
disabled: false
# https://github.com/mgechev/revive/blob/master/RULES_DESCRIPTIONS.md#error-return
- name: error-return
severity: warning
disabled: false
# https://github.com/mgechev/revive/blob/master/RULES_DESCRIPTIONS.md#error-strings
- name: error-strings
severity: warning
disabled: false
# https://github.com/mgechev/revive/blob/master/RULES_DESCRIPTIONS.md#errorf
- name: errorf
severity: warning
disabled: false
# https://github.com/mgechev/revive/blob/master/RULES_DESCRIPTIONS.md#exported
- name: exported
severity: warning
disabled: false
arguments:
- "checkPrivateReceivers"
- "sayRepetitiveInsteadOfStutters"
# https://github.com/mgechev/revive/blob/master/RULES_DESCRIPTIONS.md#file-header
- name: file-header
severity: warning
disabled: false
arguments:
- This is the text that must appear at the top of source files.
# https://github.com/mgechev/revive/blob/master/RULES_DESCRIPTIONS.md#flag-parameter
- name: flag-parameter
severity: warning
disabled: false
# https://github.com/mgechev/revive/blob/master/RULES_DESCRIPTIONS.md#function-result-limit
- name: function-result-limit
severity: warning
disabled: false
arguments: [2]
# https://github.com/mgechev/revive/blob/master/RULES_DESCRIPTIONS.md#function-length
- name: function-length
severity: warning
disabled: false
arguments: [10, 0]
# https://github.com/mgechev/revive/blob/master/RULES_DESCRIPTIONS.md#get-return
- name: get-return
severity: warning
disabled: false
# https://github.com/mgechev/revive/blob/master/RULES_DESCRIPTIONS.md#identical-branches
- name: identical-branches
severity: warning
disabled: false
# https://github.com/mgechev/revive/blob/master/RULES_DESCRIPTIONS.md#if-return
- name: if-return
severity: warning
disabled: false
# https://github.com/mgechev/revive/blob/master/RULES_DESCRIPTIONS.md#increment-decrement
- name: increment-decrement
severity: warning
disabled: false
# https://github.com/mgechev/revive/blob/master/RULES_DESCRIPTIONS.md#indent-error-flow
- name: indent-error-flow
severity: warning
disabled: false
# https://github.com/mgechev/revive/blob/master/RULES_DESCRIPTIONS.md#imports-blacklist
- name: imports-blacklist
severity: warning
disabled: false
arguments:
- "crypto/md5"
- "crypto/sha1"
# https://github.com/mgechev/revive/blob/master/RULES_DESCRIPTIONS.md#import-shadowing
- name: import-shadowing
severity: warning
disabled: false
# https://github.com/mgechev/revive/blob/master/RULES_DESCRIPTIONS.md#line-length-limit
- name: line-length-limit
severity: warning
disabled: false
arguments: [80]
# https://github.com/mgechev/revive/blob/master/RULES_DESCRIPTIONS.md#max-public-structs
- name: max-public-structs
severity: warning
disabled: false
arguments: [3]
# https://github.com/mgechev/revive/blob/master/RULES_DESCRIPTIONS.md#modifies-parameter
- name: modifies-parameter
severity: warning
disabled: false
# https://github.com/mgechev/revive/blob/master/RULES_DESCRIPTIONS.md#modifies-value-receiver
- name: modifies-value-receiver
severity: warning
disabled: false
# https://github.com/mgechev/revive/blob/master/RULES_DESCRIPTIONS.md#nested-structs
- name: nested-structs
severity: warning
disabled: false
# https://github.com/mgechev/revive/blob/master/RULES_DESCRIPTIONS.md#optimize-operands-order
- name: optimize-operands-order
severity: warning
disabled: false
# https://github.com/mgechev/revive/blob/master/RULES_DESCRIPTIONS.md#package-comments
- name: package-comments
severity: warning
disabled: false
# https://github.com/mgechev/revive/blob/master/RULES_DESCRIPTIONS.md#range
- name: range
severity: warning
disabled: false
# https://github.com/mgechev/revive/blob/master/RULES_DESCRIPTIONS.md#range-val-in-closure
- name: range-val-in-closure
severity: warning
disabled: false
# https://github.com/mgechev/revive/blob/master/RULES_DESCRIPTIONS.md#range-val-address
- name: range-val-address
severity: warning
disabled: false
# https://github.com/mgechev/revive/blob/master/RULES_DESCRIPTIONS.md#receiver-naming
- name: receiver-naming
severity: warning
disabled: false
# https://github.com/mgechev/revive/blob/master/RULES_DESCRIPTIONS.md#redefines-builtin-id
- name: redefines-builtin-id
severity: warning
disabled: false
# https://github.com/mgechev/revive/blob/master/RULES_DESCRIPTIONS.md#string-of-int
- name: string-of-int
severity: warning
disabled: false
# https://github.com/mgechev/revive/blob/master/RULES_DESCRIPTIONS.md#string-format
- name: string-format
severity: warning
disabled: false
arguments:
- - 'core.WriteError[1].Message'
- '/^([^A-Z]|$)/'
- must not start with a capital letter
- - 'fmt.Errorf[0]'
- '/(^|[^\.!?])$/'
- must not end in punctuation
- - panic
- '/^[^\n]*$/'
- must not contain line breaks
# https://github.com/mgechev/revive/blob/master/RULES_DESCRIPTIONS.md#struct-tag
- name: struct-tag
severity: warning
disabled: false
# https://github.com/mgechev/revive/blob/master/RULES_DESCRIPTIONS.md#superfluous-else
- name: superfluous-else
severity: warning
disabled: false
# https://github.com/mgechev/revive/blob/master/RULES_DESCRIPTIONS.md#time-equal
- name: time-equal
severity: warning
disabled: false
# https://github.com/mgechev/revive/blob/master/RULES_DESCRIPTIONS.md#time-naming
- name: time-naming
severity: warning
disabled: false
# https://github.com/mgechev/revive/blob/master/RULES_DESCRIPTIONS.md#var-naming
- name: var-naming
severity: warning
disabled: false
arguments:
- ["ID"] # AllowList
- ["VM"] # DenyList
# https://github.com/mgechev/revive/blob/master/RULES_DESCRIPTIONS.md#var-declaration
- name: var-declaration
severity: warning
disabled: false
# https://github.com/mgechev/revive/blob/master/RULES_DESCRIPTIONS.md#unconditional-recursion
- name: unconditional-recursion
severity: warning
disabled: false
# https://github.com/mgechev/revive/blob/master/RULES_DESCRIPTIONS.md#unexported-naming
- name: unexported-naming
severity: warning
disabled: false
# https://github.com/mgechev/revive/blob/master/RULES_DESCRIPTIONS.md#unexported-return
- name: unexported-return
disabled: true
# https://github.com/mgechev/revive/blob/master/RULES_DESCRIPTIONS.md#unhandled-error
- name: unhandled-error
severity: warning
disabled: false
arguments:
- "fmt.Printf"
- "myFunction"
# https://github.com/mgechev/revive/blob/master/RULES_DESCRIPTIONS.md#unnecessary-stmt
- name: unnecessary-stmt
severity: warning
disabled: false
# https://github.com/mgechev/revive/blob/master/RULES_DESCRIPTIONS.md#unreachable-code
- name: unreachable-code
severity: warning
disabled: false
# https://github.com/mgechev/revive/blob/master/RULES_DESCRIPTIONS.md#unused-parameter
- name: unused-parameter
severity: warning
disabled: false
# https://github.com/mgechev/revive/blob/master/RULES_DESCRIPTIONS.md#unused-receiver
- name: unused-receiver
severity: warning
disabled: false
# https://github.com/mgechev/revive/blob/master/RULES_DESCRIPTIONS.md#useless-break
- name: useless-break
severity: warning
disabled: false
# https://github.com/mgechev/revive/blob/master/RULES_DESCRIPTIONS.md#waitgroup-by-value
- name: waitgroup-by-value
severity: warning
disabled: false
-45
View File
@@ -1,45 +0,0 @@
setw -g mode-keys vi
#unbind ^b
#set -g prefix 'C-a'
#set -g prefix `
#bind-key ` send-prefix
bind r source-file ~/.tmux.conf \; display-message "Config reloaded"
# 切分面板
# unbind '"'
bind - splitw -v -c '#{pane_current_path}'
# unbind %
bind | splitw -h -c '#{pane_current_path}'
# 开启 24bit 真彩色,tmux > 2.2 开始支持
set -g default-terminal "screen-256color"
set-option -ga terminal-overrides ",*256col*:Tc"
set-option -g mouse on
# 绑定hjkl键为面板切换的上下左右键
bind k select-pane -U # 绑定k为↑
bind j select-pane -D # 绑定j为↓
bind h select-pane -L # 绑定h为←
bind l select-pane -R # 绑定l为→
# 绑定Ctrl+hjkl键为面板上下左右调整边缘的快捷指令
bind -r K resizep -U 10 # 绑定Ctrl+k为往↑调整面板边缘10个单元格
bind -r J resizep -D 10 # 绑定Ctrl+j为往↓调整面板边缘10个单元格
bind -r H resizep -L 10 # 绑定Ctrl+h为往←调整面板边缘10个单元格
bind -r L resizep -R 10 # 绑定Ctrl+l为往→调整面板边缘10个单元格
# 设置tmux的延迟,文档说当有干扰的时候可以设置这个参数,比如影响vim编辑的时候
set -s escape-time 1
# 设置窗口名为当前目录名
set-option -g status-interval 10
set-option -g automatic-rename on
set-option -g automatic-rename-format '#{b:pane_current_path}'
set-option -g set-titles on
#set-option -g set-titles-string "#{session_name} - #{host}"
set-option -g set-titles-string "#{session_name}"
# tmux resurrect
run-shell ~/.tmux/plugins/tmux-resurrect/resurrect.tmux
-145
View File
@@ -1,145 +0,0 @@
# If you come from bash you might have to change your $PATH.
# export PATH=$HOME/bin:/usr/local/bin:$PATH
# Path to your oh-my-zsh installation.
export ZSH=$HOME/.oh-my-zsh
# Set name of the theme to load. Optionally, if you set this to "random"
# it'll load a random theme each time that oh-my-zsh is loaded.
# See https://github.com/robbyrussell/oh-my-zsh/wiki/Themes
ZSH_THEME="laily"
# Set list of themes to load
# Setting this variable when ZSH_THEME=random
# cause zsh load theme from this variable instead of
# looking in ~/.oh-my-zsh/themes/
# An empty array have no effect
# ZSH_THEME_RANDOM_CANDIDATES=( "robbyrussell" "agnoster" )
# Uncomment the following line to use case-sensitive completion.
# CASE_SENSITIVE="true"
# Uncomment the following line to use hyphen-insensitive completion. Case
# sensitive completion must be off. _ and - will be interchangeable.
# HYPHEN_INSENSITIVE="true"
# Uncomment the following line to disable bi-weekly auto-update checks.
# DISABLE_AUTO_UPDATE="true"
# Uncomment the following line to change how often to auto-update (in days).
# export UPDATE_ZSH_DAYS=13
# Uncomment the following line to disable colors in ls.
# DISABLE_LS_COLORS="true"
# Uncomment the following line to disable auto-setting terminal title.
# DISABLE_AUTO_TITLE="true"
# Uncomment the following line to enable command auto-correction.
# ENABLE_CORRECTION="true"
# Uncomment the following line to display red dots whilst waiting for completion.
# COMPLETION_WAITING_DOTS="true"
# Uncomment the following line if you want to disable marking untracked files
# under VCS as dirty. This makes repository status check for large repositories
# much, much faster.
# DISABLE_UNTRACKED_FILES_DIRTY="true"
# Uncomment the following line if you want to change the command execution time
# stamp shown in the history command output.
# The optional three formats: "mm/dd/yyyy"|"dd.mm.yyyy"|"yyyy-mm-dd"
# HIST_STAMPS="mm/dd/yyyy"
# Would you like to use another custom folder than $ZSH/custom?
# ZSH_CUSTOM=/path/to/new-custom-folder
# Which plugins would you like to load? (plugins can be found in ~/.oh-my-zsh/plugins/*)
# Custom plugins may be added to ~/.oh-my-zsh/custom/plugins/
# Example format: plugins=(rails git textmate ruby lighthouse)
# Add wisely, as too many plugins slow down shell startup.
plugins=(
git
extract
docker
docker-compose
)
source $ZSH/oh-my-zsh.sh
# User configuration
# export MANPATH="/usr/local/man:$MANPATH"
# You may need to manually set your language environment
# export LANG=en_US.UTF-8
# Preferred editor for local and remote sessions
# if [[ -n $SSH_CONNECTION ]]; then
# export EDITOR='vim'
# else
# export EDITOR='mvim'
# fi
# Compilation flags
# export ARCHFLAGS="-arch x86_64"
# ssh
# export SSH_KEY_PATH="~/.ssh/rsa_id"
# Set personal aliases, overriding those provided by oh-my-zsh libs,
# plugins, and themes. Aliases can be placed here, though oh-my-zsh
# users are encouraged to define aliases within the ZSH_CUSTOM folder.
# For a full list of active aliases, run `alias`.
#
# Example aliases
# alias zshconfig="mate ~/.zshrc"
# alias ohmyzsh="mate ~/.oh-my-zsh"
# Ctrl+b/f 向后/前移动一个词
bindkey "^b" backward-word
bindkey "^f" forward-word
# Turn off all beeps
unsetopt BEEP
# Turn off autocomplete beeps
# unsetopt LIST_BEEP
# brew
export HOMEBREW_BREW_GIT_REMOTE="https://mirrors.tuna.tsinghua.edu.cn/git/homebrew/brew.git"
export HOMEBREW_BOTTLE_DOMAIN="https://mirrors.tuna.tsinghua.edu.cn/homebrew-bottles"
export HOMEBREW_NO_AUTO_UPDATE=true # 禁止 brew 每次安装软件都 upate
# git
export REVIEW_BASE="master"
# go
export GOPATH=$HOME/go
export PATH=$PATH:$GOPATH/bin
export GOPROXY=goproxy.cn
export GOSUMDB="sum.golang.google.cn"
[ -f ~/.zprofile ] && source ~/.zprofile
[ -f ~/.fzf.zsh ] && source ~/.fzf.zsh
# add z
eval "$(lua /usr/local/z.lua/z.lua --init zsh)"
export PATH="$HOME/.yarn/bin:$HOME/.config/yarn/global/node_modules/.bin:$PATH"
export PATH="/usr/local/sbin:$PATH"
# add nvim
export PATH=$PATH:/usr/local/nvim/bin:/usr/local/osx-64/nvim/bin
# for terminal notify
# https://github.com/julienXX/terminal-notifier
alias alert='terminal-notifier -title "Terminal" -message "Done with task! Exit status: $?"' -activate com.apple.Terminal
# git
alias gitb="git branch --show-current | tr -d '\n' | pbcopy"
alias ct="go test -gcflags=all=-l -cover ./..."
alias ci="golangci-lint -c=~/.dotfile/.golangci.yml run ./..."
alias gmt="go mod tidy"
alias gt="go test -gcflags=all=-l "
alias swg="swag init -o docs -g "
alias sws="swagger serve -F=swagger"
+108
View File
@@ -0,0 +1,108 @@
# dotfile 维护约定
个人 dotfiles 仓库(`~/.dotfile`)。本文件供 AI coding agent 与维护者共用:新增或改造配置/脚本时请遵循以下规则。
## 常用命令
```bash
ff script -h # 列出 script/ 下所有脚本及说明
ff script <name> # 运行脚本
make -f Makefile.scripts <name> # 同上(make 入口)
ff region # 切换 CN_REGION(国内/国外镜像)
ff reload # 重载 fish 配置
ff update # 立即 git pull origin main 并 reload
fish fish/tests/ff-auto-update.fish # ff 自动更新回归
```
## 目录职责
| 路径 | 用途 |
|------|------|
| `script/` | 可执行脚本(安装、初始化、工具);已加入 fish `PATH` |
| `script/common/` | 脚本共享片段(`help.sh`、`os-arch.sh`) |
| `fish/default.fish` | 共享 fish 配置(进 git) |
| `~/.config/fish/config.fish` | 本机 wrapper,仅 `source` default.fish + 可选 `custom.fish` |
| `fish/functions/`、`fish/completions/` | fish 函数与补全;`fish-init` 会软链到 `~/.config/fish/` |
| `git/`、`nvim/`、`tmux/`、`zsh/`、`cursor/`、`ghostty/` 等 | 各工具**真实配置**,由对应 `*-init` 软链到系统路径 |
| `mise/config.toml` | 跨机器共享;`mise-install` 链到 `~/.config/mise/conf.d/dotfile.toml`。本机工具在 `~/.config/mise/config.toml` |
| `Makefile.scripts` | `make <script-name>` 运行 `script/<name>.sh` |
## 脚本命名
- **`*-install.sh`**:安装软件/运行时(如 `docker-install`、`nvim-install`、`mise-install`)
- **`*-init.sh`**:把 dotfile 配置**软链接**到系统默认路径(如 `git-init`、`fish-init`、`nvim-init`)
- **工具脚本**:动词或名词(`dns-clean`、`scan-ip`、`nosudo`、`docker-group`、`dirsize`)
- 文件名与 `make` / `ff script` 子命令一致(无扩展名调用)
## 新增脚本 checklist
1. 放在 `script/`,shebang + **一行 `#` 功能描述**(供 `ff script -h` 自动展示)
2. `source "$SCRIPT_DIR/common/help.sh"`,实现 `usage()`,支持 **`-h` / `--help`**
3. 需要平台判断时用 `script/common/os-arch.sh`
4. 依赖国内镜像的安装脚本读取 **`CN_REGION=1`**(`toggle-region` / `ff region`)
5. 若需 fish 补全,更新 `fish/completions/ff.fish`(描述从脚本头注释提取)
## init 脚本模式
- 目标路径已有文件:**先备份**(`*.bak` 或 `*.backup.<timestamp>`),再 `ln -sf` 指向 `~/.dotfile/...`
- 已存在且指向正确的软链:**幂等跳过**
- 示例:`git-init` → `~/.gitconfig`;`nvim-init` → `~/.config/nvim`;`ghostty-init` → 各平台 Ghostty 配置目录
- **fish-init** 特殊:不链整个 `config.fish`,只在顶部插入 `source ~/.dotfile/fish/default.fish`,并链 `functions/`、`completions/`
## install 脚本模式
- 优先用系统包管理器(brew/apt),其次官方安装脚本或 Release 二进制到 `~/.local/bin`
- Debian/Ubuntu 安装类脚本应支持 `CN_REGION` 切换镜像(参考 `docker-install.sh`)
- 需要 root 的脚本在 usage 里写 `sudo`,不要静默假设
- **mise**:共享 `mise/config.toml` 链到 `~/.config/mise/conf.d/dotfile.toml`;`mise use -g` 写本机 `~/.config/mise/config.toml`,不进 git。旧的指向仓库的全局软链由 `mise-install` 解开
## Fish / `ff` 入口
- 跑脚本:`ff script <name>` 或 `ff s <name>`(走 `execute-script.fish`)
- 列脚本:`ff script -h`
- 新增 `ff` 子命令时:改 `fish/functions/ff.fish` + `fish/completions/ff.fish`
- **自动更新**:日常 `ff` 命令超过间隔(默认开、24h)会顺带 `git pull --ff-only origin main` 更新 `~/.dotfile`,成功且有新提交后再 reload。配置在本机 `~/.config/ff/config`(`enabled`、`interval`),时间戳 `~/.local/share/ff/state`,不要进 git。跳过:`FF_NO_AUTO_UPDATE=1` 或 `ff --no-auto-update …`。显式 `ff update` 立即拉取并刷新时间戳,这次不再走自动更新。脏工作区、超时、网络失败时 stderr 一行并继续当前命令。回归:`fish fish/tests/ff-auto-update.fish`
## Git 配置
- 公共配置在 `git/.gitconfig`
- **身份**:不设全局 `user.name` / `user.email`,`user.useConfigOnly = true`。未匹配目录时拒绝 commit,避免错用身份
- `~/.dotfile/`、`~/code/`、`~/.pouch/` → `git/.gitconfig.personal`(个人,进仓库,已写 name/email)
- `~/workrepo/` → `~/.gitconfig.work`(公司,仅本机;`git-init` 会生成空模板)
- include 目标文件必须存在,否则该目录下 git 命令会 fatal
- **凭据**(所有 Git 版本):`git/git-credential.sh` 写在主配置里,使用 `!$HOME/.dotfile/...`(Git 不展开 `~`)
- macOS:`osxkeychain` → 回退 `store`(`~/.git-credentials`)
- Linux:`libsecret` → 回退 `store`
- 平台扩展(可选):`git/.gitconfig.darwin` / `.gitconfig.linux` 仅在高版本 Git 下通过 includeIf 加载
- `help.autocorrect`:主配置为 `0`(兼容旧 Git);Git ≥ 2.36 的平台文件里为 `prompt`
## 边界(不要做的事)
- 不要把密钥、webhook、本机专用路径写进 git(`script/clash.sh` 等已在 `.gitignore`)
- 不要改 `~/.config/fish/config.fish` 进仓库;本机差异放 `custom.fish`
- 不要把本机 `mise use -g` 的工具写进 `mise/config.toml`;写 `~/.config/mise/config.toml`
- 不要把 `~/.config/ff/config` 或 `~/.local/share/ff/state` 写进 git(本机自动更新配置)
- 不要为 init 复制整份配置到 `$HOME`;**优先软链接**到 dotfile
- 脚本改动保持最小 diff,风格与相邻脚本一致(bash、`set -euo pipefail` 视情况)
- 未经明确要求不要 `git commit` / `git push`
## 参考模板(新 bash 脚本)
```bash
#!/bin/bash
# 一行功能描述
SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)"
source "$SCRIPT_DIR/common/help.sh"
usage() {
cat <<'EOF'
用法: ...
选项:
-h, --help 显示此帮助
EOF
}
dotfile_help_requested "${1:-}" && dotfile_show_help
# ... 正文
```
+3
View File
@@ -0,0 +1,3 @@
# Claude Code
本仓库的 AI / 维护约定见 **[AGENTS.md](./AGENTS.md)**。修改 dotfile、脚本或 init 配置时请遵循该文件。
+47
View File
@@ -0,0 +1,47 @@
.PHONY: help all list
# 脚本目录
SCRIPT_DIR := script
# 默认目标:显示帮助信息
help:
@echo "Dotfile Management"
@echo ""
@echo "Usage: make <script-name>"
@echo " Example: make brew-install"
@echo " make fish-init"
@echo " make zsh-init"
@echo " make docker-clean"
@echo ""
@echo "Available scripts:"
@ls -1 $(SCRIPT_DIR)/*.sh $(SCRIPT_DIR)/*.py 2>/dev/null | xargs -n1 basename | sed 's/\.[^.]*$$//' | sort | sed 's/^/ make /'
@echo ""
@echo "Quick commands:"
@echo " make list List all available scripts"
@echo " make help Show this help message"
# 列出所有可用脚本
list:
@echo "Available scripts in $(SCRIPT_DIR)/"
@ls -1 $(SCRIPT_DIR)/*.sh $(SCRIPT_DIR)/*.py 2>/dev/null | xargs -n1 basename
#################################################################
# 通用规则:自动映射命令到脚本文件
#################################################################
# 匹配所有命令,自动执行对应的脚本文件
%:
@if [ -f "$(SCRIPT_DIR)/$@.sh" ]; then \
echo "Running $(SCRIPT_DIR)/$@.sh..."; \
bash $(SCRIPT_DIR)/$@.sh; \
elif [ -f "$(SCRIPT_DIR)/$@.py" ]; then \
echo "Running $(SCRIPT_DIR)/$@.py..."; \
python3 $(SCRIPT_DIR)/$@.py; \
elif [ "$@" = "init-default-user" ]; then \
echo "Running $(SCRIPT_DIR)/init-default-user.sh (requires sudo)..."; \
sudo bash $(SCRIPT_DIR)/init-default-user.sh; \
else \
echo "Error: No script found for '$@'"; \
echo "Run 'make list' to see available scripts"; \
exit 1; \
fi
+10 -30
View File
@@ -1,35 +1,15 @@
### 使用
```shell
git clone https://github.com/Laily123/.dotfile.git ~/.dotfile
```
### zsh 安装
安装 zsh
```shell
https://github.com/robbyrussell/oh-my-zsh/wiki/Installing-ZSH
# macOS
brew install zsh zsh-completions
# CentOS
sudo yum -y install zsh
# Ubuntu
apt install zsh
git clone https://github.com/hilaily/.dotfile.git ~/.dotfile
```
安装 oh-my-zsh
```shell
sh -c "$(wget https://raw.githubusercontent.com/robbyrussell/oh-my-zsh/master/tools/install.sh -O -)"
首先安装 fish
`brew install fish` 或者 `apt install fish`
然后初始化 fish
```bash
bash ~/.dotfile/script/fish-install.sh
```
初始化配置
```shell
ln -sf ~/.dotfile/.zshrc ~/
ln -sf ~/.dotfile/laily.zsh-theme ~/.oh-my-zsh/themes/
ln -sf ~/.dotfile/.tmux.conf ~/
ln -sf ~/.dotfile/.gitconfig ~/.gitconfig
```
### spacevim 安装
安装
`curl -sLf https://spacevim.org/install.sh | bash`
初始化配置
`ln -sf ~/.dotfile/.SpaceVim.d ~/`
+291
View File
@@ -0,0 +1,291 @@
// 将键绑定放入此文件中以覆盖默认值
[
{
"key": "alt+cmd+left",
"command": "workbench.action.navigateBack",
},
{
"key": "alt+cmd+right",
"command": "workbench.action.navigateForward",
},
{
"key": "alt+cmd+l",
"command": "editor.action.formatSelection",
"when": "editorHasDocumentFormattingProvider && editorHasSelection && editorTextFocus && !editorReadonly",
},
{
"key": "cmd+k m",
"command": "-workbench.action.editor.changeLanguageMode",
"when": "!notebookEditorFocused",
},
{
"key": "ctrl+j",
"command": "selectNextSuggestion",
"when": "suggestWidgetMultipleSuggestions && suggestWidgetVisible && textInputFocus || suggestWidgetVisible && textInputFocus && !suggestWidgetHasFocusedSuggestion",
},
{
"key": "ctrl+n",
"command": "-selectNextSuggestion",
"when": "suggestWidgetMultipleSuggestions && suggestWidgetVisible && textInputFocus || suggestWidgetVisible && textInputFocus && !suggestWidgetHasFocusedSuggestion",
},
{
"key": "ctrl+k",
"command": "-deleteAllRight",
"when": "textInputFocus && !editorReadonly",
},
{
"key": "ctrl+k",
"command": "selectPrevSuggestion",
"when": "suggestWidgetMultipleSuggestions && suggestWidgetVisible && textInputFocus || suggestWidgetVisible && textInputFocus && !suggestWidgetHasFocusedSuggestion",
},
{
"key": "ctrl+p",
"command": "-selectPrevSuggestion",
"when": "suggestWidgetMultipleSuggestions && suggestWidgetVisible && textInputFocus || suggestWidgetVisible && textInputFocus && !suggestWidgetHasFocusedSuggestion",
},
{
"key": "cmd+i",
"command": "composerMode.agent",
},
{
"key": "space",
"command": "vspacecode.space",
"when": "activeEditorGroupEmpty && focusedView == '' && !whichkeyActive && !inputFocus",
},
{
"key": "space",
"command": "vspacecode.space",
"when": "sideBarFocus && !inputFocus && !whichkeyActive",
},
// {
// "key": "tab",
// "command": "extension.vim_tab",
// "when": "editorTextFocus && vim.active && !inDebugRepl && vim.mode != 'Insert' && editorLangId != 'magit'"
// },
// {
// "key": "tab",
// "command": "-extension.vim_tab",
// "when": "editorTextFocus && vim.active && !inDebugRepl && vim.mode != 'Insert'"
// },
{
"key": "x",
"command": "magit.discard-at-point",
"when": "editorTextFocus && editorLangId == 'magit' && vim.mode =~ /^(?!SearchInProgressMode|CommandlineInProgress).*$/",
},
{
"key": "k",
"command": "-magit.discard-at-point",
},
{
"key": "-",
"command": "magit.reverse-at-point",
"when": "editorTextFocus && editorLangId == 'magit' && vim.mode =~ /^(?!SearchInProgressMode|CommandlineInProgress).*$/",
},
{
"key": "v",
"command": "-magit.reverse-at-point",
},
{
"key": "shift+-",
"command": "magit.reverting",
"when": "editorTextFocus && editorLangId == 'magit' && vim.mode =~ /^(?!SearchInProgressMode|CommandlineInProgress).*$/",
},
{
"key": "shift+v",
"command": "-magit.reverting",
},
{
"key": "shift+o",
"command": "magit.resetting",
"when": "editorTextFocus && editorLangId == 'magit' && vim.mode =~ /^(?!SearchInProgressMode|CommandlineInProgress).*$/",
},
{
"key": "shift+x",
"command": "-magit.resetting",
},
{
"key": "x",
"command": "-magit.reset-mixed",
},
{
"key": "ctrl+u x",
"command": "-magit.reset-hard",
},
{
"key": "y",
"command": "-magit.show-refs",
},
{
"key": "y",
"command": "vspacecode.showMagitRefMenu",
"when": "editorTextFocus && editorLangId == 'magit' && vim.mode == 'Normal'",
},
{
"key": "g",
"command": "-magit.refresh",
"when": "editorTextFocus && editorLangId == 'magit' && vim.mode =~ /^(?!SearchInProgressMode|CommandlineInProgress).*$/",
},
{
"key": "g",
"command": "vspacecode.showMagitRefreshMenu",
"when": "editorTextFocus && editorLangId == 'magit' && vim.mode =~ /^(?!SearchInProgressMode|CommandlineInProgress).*$/",
},
{
"key": "ctrl+j",
"command": "workbench.action.quickOpenSelectNext",
"when": "inQuickOpen",
},
{
"key": "ctrl+k",
"command": "workbench.action.quickOpenSelectPrevious",
"when": "inQuickOpen",
},
{
"key": "ctrl+j",
"command": "selectNextSuggestion",
"when": "suggestWidgetMultipleSuggestions && suggestWidgetVisible && textInputFocus",
},
{
"key": "ctrl+k",
"command": "selectPrevSuggestion",
"when": "suggestWidgetMultipleSuggestions && suggestWidgetVisible && textInputFocus",
},
{
"key": "ctrl+l",
"command": "acceptSelectedSuggestion",
"when": "suggestWidgetMultipleSuggestions && suggestWidgetVisible && textInputFocus",
},
{
"key": "ctrl+j",
"command": "showNextParameterHint",
"when": "editorFocus && parameterHintsMultipleSignatures && parameterHintsVisible",
},
{
"key": "ctrl+k",
"command": "showPrevParameterHint",
"when": "editorFocus && parameterHintsMultipleSignatures && parameterHintsVisible",
},
{
"key": "ctrl+j",
"command": "selectNextCodeAction",
"when": "codeActionMenuVisible",
},
{
"key": "ctrl+k",
"command": "selectPrevCodeAction",
"when": "codeActionMenuVisible",
},
{
"key": "ctrl+l",
"command": "acceptSelectedCodeAction",
"when": "codeActionMenuVisible",
},
// Quick Navigation for file browser
{
"key": "ctrl+h",
"command": "file-browser.stepOut",
"when": "inFileBrowser",
},
{
"key": "ctrl+l",
"command": "file-browser.stepIn",
"when": "inFileBrowser",
},
{
"key": "ctrl+h",
"command": "workbench.action.navigateLeft",
"when": "!inQuickOpen && !suggestWidgetVisible && !parameterHintsVisible && !isInDiffEditor",
},
{
"key": "ctrl+j",
"command": "workbench.action.navigateDown",
"when": "!codeActionMenuVisible && !inQuickOpen && !suggestWidgetVisible && !parameterHintsVisible",
},
{
"key": "ctrl+k",
"command": "workbench.action.navigateUp",
"when": "!codeActionMenuVisible && !inQuickOpen && !suggestWidgetVisible && !parameterHintsVisible",
},
{
"key": "ctrl+l",
"command": "workbench.action.navigateRight",
"when": "!codeActionMenuVisible && !inQuickOpen && !suggestWidgetVisible && !parameterHintsVisible && !isInDiffEditor",
},
// Quick Navigation for diff view
{
"key": "ctrl+h",
"command": "workbench.action.compareEditor.focusSecondarySide",
"when": "isInDiffEditor && !isInDiffLeftEditor",
},
{
"key": "ctrl+h",
"command": "workbench.action.navigateLeft",
"when": "isInDiffEditor && isInDiffLeftEditor",
},
{
"key": "ctrl+l",
"command": "workbench.action.compareEditor.focusPrimarySide",
"when": "isInDiffEditor && isInDiffLeftEditor",
},
{
"key": "ctrl+l",
"command": "workbench.action.navigateRight",
"when": "isInDiffEditor && !isInDiffLeftEditor",
},
{
"key": "ctrl+l",
"command": "workbench.action.terminal.clear",
"when": "terminalFocus && terminalHasBeenCreated && !accessibilityModeEnabled || terminalFocus && terminalProcessSupported && !accessibilityModeEnabled || accessibilityModeEnabled && accessibleViewIsShown && terminalHasBeenCreated && accessibleViewCurrentProviderId == 'terminal' || accessibilityModeEnabled && accessibleViewIsShown && terminalProcessSupported && accessibleViewCurrentProviderId == 'terminal'",
},
{
"key": "cmd+k",
"command": "-workbench.action.terminal.clear",
"when": "terminalFocus && terminalHasBeenCreated && !accessibilityModeEnabled || terminalFocus && terminalProcessSupported && !accessibilityModeEnabled || accessibilityModeEnabled && accessibleViewIsShown && terminalHasBeenCreated && accessibleViewCurrentProviderId == 'terminal' || accessibilityModeEnabled && accessibleViewIsShown && terminalProcessSupported && accessibleViewCurrentProviderId == 'terminal'",
},
{
"key": "space",
"command": "vspacecode.space",
"when": "terminalFocus && terminalHasBeenCreated && !accessibilityModeEnabled || terminalFocus && terminalProcessSupported && !accessibilityModeEnabled || accessibilityModeEnabled && accessibleViewIsShown && terminalHasBeenCreated && accessibleViewCurrentProviderId == 'terminal' || accessibilityModeEnabled && accessibleViewIsShown && terminalProcessSupported && accessibleViewCurrentProviderId == 'terminal'",
},
{
"key": "ctrl+o",
"command": "workbench.action.navigateBack",
"when": "canNavigateBack",
},
{
"key": "ctrl+-",
"command": "-workbench.action.navigateBack",
"when": "canNavigateBack",
},
{
"key": "ctrl+o",
"command": "-lineBreakInsert",
"when": "textInputFocus && !editorReadonly",
},
{
"key": "ctrl+\\",
"command": "-glass.openActionMenu",
"when": "isGlassAgentWorkspace && glassActiveTabKind == 'diff'",
},
{
"key": "ctrl+\\",
"command": "-glass.openPlanBuildMenu",
"when": "isGlassAgentWorkspace && glassActiveTabKind == 'plan'",
},
{
"key": "ctrl+cmd+i",
"command": "-workbench.panel.chat",
"when": "workbench.panel.chat.view.copilot.active",
},
{
"key": "ctrl+\\",
"command": "workbench.action.toggleAgentsFromKeyboard",
"when": "!isAuxiliaryWindowFocusedContext && !isGlass",
},
{
"key": "alt+cmd+j",
"command": "-workbench.action.toggleAgentsFromKeyboard",
"when": "!isAuxiliaryWindowFocusedContext && !isGlass",
},
]
+291
View File
@@ -0,0 +1,291 @@
{
"editor.fontLigatures": true,
"editor.fontSize": 16,
"editor.renderControlCharacters": true,
"workbench.iconTheme": "Monokai Classic Icons",
"workbench.welcome.enabled": false,
"workbench.colorTheme": "Dracula Theme Soft",
"vim.overrideCopy": false,
"vim.enableNeovim": true,
"vim.neovimPath": "/usr/local/bin/nvim",
"vim.disableAnnoyingNeovimMessage": true,
// Vim easymotion is required for Jump menu - <SPC> j
"vim.easymotion": true,
// Use system clipboard for vim
"vim.useSystemClipboard": true,
// Trigger the main which key menu with in the active editor with vim
// This cannot be put into keybindings.json because it will
// create conflict with Vim.
// https://github.com/stevenguh/spacecode/issues/3
"vim.normalModeKeyBindingsNonRecursive": [
{
"before": ["<space>"],
"commands": ["vspacecode.space"]
},
{
"before": [","],
"commands": [
"vspacecode.space",
{
"command": "whichkey.triggerKey",
"args": "m"
}
]
}
],
"vim.visualModeKeyBindingsNonRecursive": [
{
"before": ["<space>"],
"commands": ["vspacecode.space"]
},
{
"before": [","],
"commands": [
"vspacecode.space",
{
"command": "whichkey.triggerKey",
"args": "m"
}
]
},
{
"before": [">"],
"commands": ["editor.action.indentLines"]
},
{
"before": ["<"],
"commands": ["editor.action.outdentLines"]
}
],
"workbench.panel.location": "right",
"gitHistory.hideCommitViewExplorer": true,
"python.linting.pylintEnabled": false,
"python.pythonPath": "/usr/local/bin/python3",
"editor.wordWrap": "on",
"editor.suggestSelection": "first",
"vsintellicode.modify.editor.suggestSelection": "automaticallyOverrodeDefaultValue",
"sync.gist": "3c4e02a02a0d110622006b8c8b058fa5",
"go.languageServerFlags": ["-rpc.trace"],
"[json]": {
"editor.defaultFormatter": "vscode.json-language-features"
},
"files.associations": {
"*.html": "html",
"*.json": "jsonc",
"NOTES.txt": "helm",
"Dockerfile": "dockerfile"
},
"[helm]": {
"editor.defaultFormatter": "redhat.vscode-yaml"
// "editor.formatOnSave": false // alternatively, force it to leave you alone
},
"gitlens.codeLens.recentChange.enabled": false,
"gitlens.codeLens.authors.enabled": false,
"sync.forceUpload": true,
"gitlens.currentLine.enabled": false,
"emmet.triggerExpansionOnTab": true,
// "terminal.integrated.shell.osx": "/bin/zsh",
"editor.fontFamily": "Fira Code, Sarasa Fixed SC Nerd Font, Menlo, Monaco, 'Courier New', monospace",
"[javascript]": {
"editor.defaultFormatter": "esbenp.prettier-vscode"
},
"search.followSymlinks": false,
"go.autocompleteUnimportedPackages": true,
"[jsonc]": {
"editor.defaultFormatter": "esbenp.prettier-vscode"
},
"vscode-neovim.mouseSelectionStartVisualMode": true,
"go.lintTool": "golangci-lint",
"go.lintFlags": ["--config=~/.dotfile/golang/golangci.yml"],
"go.liveErrors": {
"enabled": false,
"delay": 500
},
"python.languageServer": "Pylance",
"go.overwriteGoplsMiddleware": {},
"go.useLanguageServer": true,
"[go]": {
"editor.formatOnSave": true,
"editor.codeActionsOnSave": {
"source.organizeImports": "explicit"
},
"editor.snippetSuggestions": "none",
"editor.defaultFormatter": "golang.go"
},
"[go.mod]": {
"editor.formatOnSave": true,
"editor.codeActionsOnSave": {
"source.organizeImports": "explicit"
}
},
"gopls": {
"usePlaceholders": true,
"staticcheck": false
},
"go.testFlags": ["-v"],
"vscode-neovim.neovimExecutablePaths.darwin": "/usr/local/bin/nvim",
"go.buildOnSave": "off",
"vim.autoSwitchInputMethod.defaultIM": "com.apple.keylayout.ABC",
"vim.autoSwitchInputMethod.obtainIMCmd": "/usr/local/bin/im-select",
"vim.autoSwitchInputMethod.switchIMCmd": "/usr/local/bin/im-select {im}",
"vim.autoSwitchInputMethod.enable": true,
"editor.cursorStyle": "line",
"editor.insertSpaces": false,
"editor.lineNumbers": "on",
"editor.wordSeparators": "/\\()\"':,.;<>~!@#$%^&*|+=[]{}`?-",
"security.workspace.trust.untrustedFiles": "open",
"workbench.tree.indent": 4,
"[html]": {
"editor.defaultFormatter": "vscode.html-language-features"
},
"python.defaultInterpreterPath": "/usr/local/bin/python3",
"[python]": {
"editor.formatOnType": true,
"editor.defaultFormatter": "ms-python.python"
},
"[go][go.mod]": {
"editor.codeActionsOnSave": {
"source.organizeImports": "explicit"
}
},
"update.showReleaseNotes": false,
"terminal.integrated.defaultProfile.osx": "fish",
"extensions.experimental.affinity": {
"asvetliakov.vscode-neovim": 1
},
"makefile.configureOnOpen": false,
"python.terminal.activateEnvInCurrentTerminal": true,
"remote.SSH.enableAgentForwarding": false,
"remote.SSH.defaultForwardedPorts": [],
"remote.SSH.useLocalServer": false,
"remote.SSH.connectTimeout": 30,
"remote.SSH.localServerDownload": "always",
"remote.SSH.remotePlatform": {
"box1": "linux",
"hx80": "linux",
"hx20": "linux",
"pc30": "linux"
},
"editor.formatOnSave": true,
"pylint.enabled": false,
"python.analysis.typeCheckingMode": "standard",
"python.analysis.packageIndexDepths": [
{
"name": "sklearn",
"depth": 2
},
{
"name": "matplotlib",
"depth": 2
},
{
"name": "scipy",
"depth": 2
},
{
"name": "django",
"depth": 2
},
{
"name": "flask",
"depth": 2
},
{
"name": "fastapi",
"depth": 2
}
],
"[shellscript]": {
"editor.defaultFormatter": "foxundermoon.shell-format"
},
// "[dockerfile]": {
// "editor.defaultFormatter": "foxundermoon.shell-format"
// },
"[ignore]": {
"editor.defaultFormatter": "foxundermoon.shell-format"
},
"[properties]": {
"editor.defaultFormatter": "foxundermoon.shell-format"
},
"editor.codeActionsOnSave": {
// "source.organizeImports": "explicit",
"source.fixAll.eslint": "explicit"
},
"[proto3]": {
"editor.defaultFormatter": "zxh404.vscode-proto3"
},
"[dotenv]": {
"editor.defaultFormatter": "foxundermoon.shell-format"
},
"typescript.preferences.quoteStyle": "single",
"javascript.preferences.quoteStyle": "single",
"go.toolsManagement.autoUpdate": true,
"pretty-formatter.quoteConvert": "single",
"html.completion.attributeDefaultValue": "singlequotes",
"[typescriptreact]": {
"editor.defaultFormatter": "esbenp.prettier-vscode"
},
"prettier.jsxSingleQuote": true,
"go.buildFlags": ["-tags=wireinject"],
"[yaml]": {
"editor.defaultFormatter": "esbenp.prettier-vscode",
"editor.insertSpaces": true,
"editor.tabSize": 2,
"editor.autoIndent": "advanced",
"diffEditor.ignoreTrimWhitespace": false,
"editor.defaultColorDecorators": "never"
},
"html.format.unformatted": "",
"editor.defaultFormatter": "esbenp.prettier-vscode",
"notebook.defaultFormatter": "esbenp.prettier-vscode",
"workbench.activityBar.orientation": "vertical", // cursor test
"vspacecode.bindingOverrides": [
{
"keys": ["space"],
"name": "Space (Global Leader)",
"type": "bind",
"command": "vspacecode.space",
"when": "editorTextFocus || explorerViewletVisible || terminalFocus"
},
{
"keys": "w.t",
"name": "Toggle Terminal",
"type": "command",
"command": "workbench.action.terminal.toggleTerminal"
},
{
"keys": "w.w",
"name": "Toggle AI Sidebar",
"type": "command",
"command": "workbench.action.toggleAuxiliaryBar"
},
{
"keys": "w.e",
"name": "Focus First Editor Group",
"type": "command",
"command": "workbench.action.focusFirstEditorGroup"
}
],
"[java]": {
"editor.suggest.snippetsPreventQuickSuggestions": false
},
"git.confirmSync": false,
"editor.dropIntoEditor.preferences": [],
"whichkey.bindingOverrides": [],
"workbench.colorCustomizations": {},
"javascript.format.enable": false,
"typescript.format.enable": false,
"terminal.integrated.env.linux": {
"ANTHROPIC_AUTH_TOKEN": "sk-cp-QvKTjyX6D0bf5u0f9mB-mpaYcyCwdXq3nubxEzTFFtiGuYmLmC_nyxGOsK3jzmJSCMix2rXJ90iSFb6Xva0R30ZY1ilwMLf44npGnAzTWgtwUIqoGwHmQsc",
"ANTHROPIC_BASE_URL": "https://api.minimaxi.com/anthropic",
"ANTHROPIC_DEFAULT_HAIKU_MODEL": "MiniMax-M2.1",
"ANTHROPIC_DEFAULT_OPUS_MODEL": "MiniMax-M2.1",
"ANTHROPIC_DEFAULT_SONNET_MODEL": "MiniMax-M2.1",
"ANTHROPIC_MODEL": "MiniMax-M2.1",
"API_TIMEOUT_MS": "3000000",
"CLAUDE_CODE_DISABLE_NONESSENTIAL_TRAFFIC": 1
},
"claudeCode.preferredLocation": "panel",
"window.autoDetectColorScheme": false,
"vim.disableExtension": true
}
+26
View File
@@ -0,0 +1,26 @@
#!/bin/bash
set -e
set -x
# 设置环境变量
export CURSOR_VERSION=
export CURSOR_COMMIT=
export REMOTE_ARCH="x64" # or arm64
export REMOTE_OS="linux"
# 创建目录结构
mkdir -p ${HOME}/.cursor-server/cli/servers/Stable-${CURSOR_COMMIT}/server/
# 下载并解压第一个压缩包 (cli-alpine)
CLI_URL="https://cursor.blob.core.windows.net/remote-releases/${CURSOR_COMMIT}/cli-alpine-${REMOTE_ARCH}.tar.gz"
curl -L "${CLI_URL}" -o ${HOME}/.cursor-server/cursor-cli.tar.gz
tar -xzf ${HOME}/.cursor-server/cursor-cli.tar.gz -C ${HOME}/.cursor-server/
mv ${HOME}/.cursor-server/cursor ${HOME}/.cursor-server/cursor-$CURSOR_COMMIT
# 下载并解压第二个压缩包 (vscode-reh)
VSCODE_URL="https://cursor.blob.core.windows.net/remote-releases/${CURSOR_VERSION}-${CURSOR_COMMIT}/vscode-reh-${REMOTE_OS}-${REMOTE_ARCH}.tar.gz"
curl -L "${VSCODE_URL}" -o ${HOME}/.cursor-server/cursor-vscode-server.tar.gz
tar -xzf ${HOME}/.cursor-server/cursor-vscode-server.tar.gz -C ${HOME}/.cursor-server/cli/servers/Stable-${CURSOR_COMMIT}/server/ --strip-components=1
echo "Cursor server installation completed!"
+7
View File
@@ -0,0 +1,7 @@
{
"log-driver": "json-file",
"log-opts": {
"max-size": "50m",
"max-file": "3"
}
}
+5
View File
@@ -0,0 +1,5 @@
{
"registry-mirrors": [
"https://docker.1ms.run"
]
}
+102
View File
@@ -0,0 +1,102 @@
# Completions for ff command
complete -c ff -f
set -l ff_subcommands reload edit fish dotfile pon poff pst make script s update help region ssh init copy dirsize pouch help region
complete -c ff -n "not __fish_seen_subcommand_from $ff_subcommands" -a "reload" -d "Reload fish configuration"
complete -c ff -n "not __fish_seen_subcommand_from $ff_subcommands" -a "edit" -d "Edit custom fish configuration"
complete -c ff -n "not __fish_seen_subcommand_from $ff_subcommands" -a "fish" -d "Change to fish config directory"
complete -c ff -n "not __fish_seen_subcommand_from $ff_subcommands" -a "dotfile" -d "Change to dotfile directory"
complete -c ff -n "not __fish_seen_subcommand_from $ff_subcommands" -a "pon" -d "Enable proxy (proxy on)"
complete -c ff -n "not __fish_seen_subcommand_from $ff_subcommands" -a "poff" -d "Disable proxy (proxy off)"
complete -c ff -n "not __fish_seen_subcommand_from $ff_subcommands" -a "pst" -d "Show proxy status"
# complete -c ff -n "not __fish_seen_subcommand_from $ff_subcommands" -a "make" -d "Run dotfile Makefile commands"
complete -c ff -n "not __fish_seen_subcommand_from $ff_subcommands" -a "script" -d "Run script from script directory"
complete -c ff -n "not __fish_seen_subcommand_from $ff_subcommands" -a "s" -d "Run script (alias for script)"
complete -c ff -n "not __fish_seen_subcommand_from $ff_subcommands" -a "update" -d "Update dotfile now (also auto every 24h)"
complete -c ff -n "not __fish_seen_subcommand_from $ff_subcommands" -a "help" -d "Show this help message"
complete -c ff -l no-auto-update -d "Skip auto-update this run"
complete -c ff -n "not __fish_seen_subcommand_from $ff_subcommands" -a "region" -d "Toggle or set CN_REGION (mirror/proxy region)"
complete -c ff -n "not __fish_seen_subcommand_from $ff_subcommands" -a "ssh" -d "SSH init / copy key + add host / copy config to remote"
complete -c ff -n "not __fish_seen_subcommand_from $ff_subcommands" -a "dirsize" -d "Show directory sizes"
complete -c ff -n "not __fish_seen_subcommand_from $ff_subcommands" -a "pouch" -d "Clone and install pouch to ~/.pouch"
complete -c ff -n "__fish_seen_subcommand_from pouch" -s h -l help -d "Show help"
complete -c ff -n "__fish_seen_subcommand_from dirsize" -s a -l all -d "Recursively show all subdirectories"
complete -c ff -n "__fish_seen_subcommand_from dirsize" -s d -l depth -d "Recursion depth (default 1)" -r
complete -c ff -n "__fish_seen_subcommand_from dirsize" -s n -l top -d "Show top N only" -r
complete -c ff -n "__fish_seen_subcommand_from dirsize" -s r -l reverse -d "Sort smallest first"
complete -c ff -n "__fish_seen_subcommand_from dirsize" -s h -l help -d "Show help"
complete -c ff -n "__fish_seen_subcommand_from dirsize" -a "(__fish_complete_directories)" -d "Directory"
complete -c ff -n "__fish_seen_subcommand_from ssh; and not __fish_seen_subcommand_from init copy add run help; and test (count (commandline -opc)) -eq 2" -a init -d "Ensure SSH key and config.d Include"
complete -c ff -n "__fish_seen_subcommand_from ssh; and not __fish_seen_subcommand_from init copy add run help; and test (count (commandline -opc)) -eq 2" -a add -d "Copy key + add host from ssh-style args"
complete -c ff -n "__fish_seen_subcommand_from ssh; and not __fish_seen_subcommand_from init copy add run help; and test (count (commandline -opc)) -eq 2" -a run -d "Copy key + add host, then connect"
complete -c ff -n "__fish_seen_subcommand_from ssh; and not __fish_seen_subcommand_from init copy add run help; and test (count (commandline -opc)) -eq 2" -a copy -d "Copy specified SSH config file(s) to remote"
complete -c ff -n "__fish_seen_subcommand_from ssh; and not __fish_seen_subcommand_from init copy add run help; and test (count (commandline -opc)) -eq 2" -a help -d "Show ssh subcommand help"
# 裸参数与 ssh 原生参数一致:复用 ssh 自带的补全(选项 + 主机名)
complete -c ff -n "__fish_seen_subcommand_from ssh; and not __fish_seen_subcommand_from init copy add run help" -w ssh
function __ff_ssh_config_d_files
for d in $HOME/.ssh/config.d $HOME/.dotfile/ssh/config.d
if test -d $d
for f in $d/*
if test -f $f
basename $f
end
end
end
end
end
complete -c ff -n "__fish_seen_subcommand_from ssh; and __fish_seen_subcommand_from copy; and test (count (commandline -opc)) -eq 3" -a "(__fish_print_hostnames)" -d "SSH host"
complete -c ff -n "__fish_seen_subcommand_from ssh; and __fish_seen_subcommand_from copy; and test (count (commandline -opc)) -ge 4" -a "(__ff_ssh_config_d_files)" -d "Config file"
# add/run 使用与 ssh 相同的参数:直接复用 ssh 的选项与主机补全
complete -c ff -n "__fish_seen_subcommand_from ssh; and __fish_seen_subcommand_from add run" -w ssh
complete -c ff -n "__fish_seen_subcommand_from ssh; and not __fish_seen_subcommand_from init copy help" -l no-copy -d "Skip ssh-copy-id"
# # make 子命令的补全 - 直接使用脚本文件名
# complete -c ff -n "__fish_seen_subcommand_from make" -a "help" -d "Show all make targets"
# complete -c ff -n "__fish_seen_subcommand_from make" -a "list" -d "List all available scripts"
# Install scripts
# complete -c ff -n "__fish_seen_subcommand_from make" -a "brew-install" -d "Install Homebrew"
# complete -c ff -n "__fish_seen_subcommand_from make" -a "docker-install" -d "Install Docker"
# complete -c ff -n "__fish_seen_subcommand_from make" -a "nvim-install" -d "Install Neovim"
# complete -c ff -n "__fish_seen_subcommand_from make" -a "python-install" -d "Install Python"
# complete -c ff -n "__fish_seen_subcommand_from make" -a "gvm-install" -d "Install Go Version Manager"
# complete -c ff -n "__fish_seen_subcommand_from make" -a "firacode-install" -d "Install FiraCode font"
# complete -c ff -n "__fish_seen_subcommand_from make" -a "font-install" -d "Install fonts"
# # Init scripts
# complete -c ff -n "__fish_seen_subcommand_from make" -a "fish-init" -d "Initialize Fish shell"
# complete -c ff -n "__fish_seen_subcommand_from make" -a "git-init" -d "Initialize Git config"
# complete -c ff -n "__fish_seen_subcommand_from make" -a "nvim-init" -d "Initialize Neovim config"
# complete -c ff -n "__fish_seen_subcommand_from make" -a "tmux-init" -d "Initialize Tmux config"
# complete -c ff -n "__fish_seen_subcommand_from make" -a "cursor-init" -d "Initialize Cursor config"
# complete -c ff -n "__fish_seen_subcommand_from make" -a "init-default-user" -d "Initialize default user (sudo)"
# script 子命令的补全 - 动态列出 script 目录下的脚本
function __ff_list_scripts
if not functions -q script-list-names
source ~/.dotfile/fish/functions/script-catalog.fish
end
script-list-names
end
function __ff_complete_scripts
if not functions -q script-extract-description
source ~/.dotfile/fish/functions/script-catalog.fish
end
for name in (script-list-names)
set -l path (script-resolve-path $name)
set -l desc (script-extract-description $path)
printf "%s\t%s\n" $name $desc
end
end
complete -c ff -n "__fish_seen_subcommand_from script s; and test (count (commandline -opc)) -eq 2" -s h -l help -d "List all scripts and descriptions"
complete -c ff -n "__fish_seen_subcommand_from script s; and test (count (commandline -opc)) -eq 2" -a help -d "List all scripts and descriptions"
complete -c ff -n "__fish_seen_subcommand_from script s; and not __fish_seen_subcommand_from help -h --help (__ff_list_scripts)" -a '(__ff_complete_scripts)' -d "Script name"
+51
View File
@@ -0,0 +1,51 @@
# ----- Git worktree native completions -----
# 列出分支
function __git_branches
git branch --format="%(refname:short)" 2>/dev/null
end
# 列出 worktree 路径
function __git_worktree_paths
git worktree list --porcelain 2>/dev/null \
| awk '/worktree/ {print $2}'
end
# -----------------------
# Worktree 别名补全
# -----------------------
# git wtl → worktree list
complete -f -c git -n '__fish_seen_subcommand_from wtl' -a ''
# git wtp → worktree prune
complete -f -c git -n '__fish_seen_subcommand_from wtp' -a ''
# git wta → worktree add <path> <branch>
complete -c git -n '__fish_seen_subcommand_from wta' \
-a '(__git_branches)' \
-d "Branch to add worktree for"
# git wtb → worktree add -b <newbranch> <path> <base>
complete -c git -n '__fish_seen_subcommand_from wtb' \
-a '(__git_branches)' \
-d "Base branch to create new branch from"
# git wtr → worktree remove <path>
complete -c git -n '__fish_seen_subcommand_from wtr' \
-a '(__git_worktree_paths)' \
-d "Existing worktree paths"
# ------------------------------
# 原生 git worktree 补全增强
# ------------------------------
# git worktree add <path> <branch>
complete -c git -n '__fish_seen_subcommand_from worktree; and __fish_seen_subcommand_from add' \
-a '(__git_branches)' \
-d "Branch to checkout"
# git worktree remove <path>
complete -c git -n '__fish_seen_subcommand_from worktree; and __fish_seen_subcommand_from remove' \
-a '(__git_worktree_paths)' \
-d "Worktree path"
+2
View File
@@ -0,0 +1,2 @@
# Inherit ssh-copy-id options and host completions
complete -c ssh2 -w ssh-copy-id
+253
View File
@@ -0,0 +1,253 @@
#################################################################
# option
fish_vi_key_bindings
# Vi 模式下:Ctrl+A / Ctrl+E 行首、行尾(readline 习惯)
bind --mode insert \ca beginning-of-line
bind --mode insert \ce end-of-line
bind --mode default \ca beginning-of-line
bind --mode default \ce end-of-line
#################################################################
# basic
# 定义一个函数来检查并添加路径到 PATH
function add_to_path
# 遍历传递给函数的所有参数
for path in $argv
# 检查路径是否存在
if test -d $path
# 如果路径存在且不在 PATH 中,则添加到 PATH
if not contains $path $PATH
#echo "Adding $path to PATH"
set -gx PATH $PATH $path
else
#echo "$path is already in PATH"
end
else
#echo "$path does not exist"
end
end
end
function is_command_exists
if command -v $argv[1] >/dev/null 2>&1
return 0
else
return 1
end
end
# set environment
set paths_to_check /usr/sbin /usr/local/bin /usr/bin /bin /usr/sbin /sbin /opt/bin /opt/sbin /usr/syno/sbin /usr/syno/bin /usr/local/sbin $HOME/.local/bin $HOME/.pouch/bin
for path in $paths_to_check do
add_to_path $path
end
function sudoo
eval sudo $history[1]
end
# add my script
add_to_path $HOME/.dotfile/script $HOME/.app00/script
if test -f ~/.config/fish/custom.fish
source ~/.config/fish/custom.fish
end
#################################################################
# brew
set -gx HOMEBREW_BREW_GIT_REMOTE "https://mirrors.ustc.edu.cn/brew.git"
set -gx HOMEBREW_CORE_GIT_REMOTE "https://mirrors.ustc.edu.cn/homebrew-core.git"
set -gx HOMEBREW_BOTTLE_DOMAIN "https://mirrors.ustc.edu.cn/homebrew-bottles"
#################################################################
# java 17 + android sdk (local debug builds; no Android Studio)
if test -d /usr/local/opt/openjdk@17/libexec/openjdk.jdk/Contents/Home
set -gx JAVA_HOME /usr/local/opt/openjdk@17/libexec/openjdk.jdk/Contents/Home
else if test -d /opt/homebrew/opt/openjdk@17/libexec/openjdk.jdk/Contents/Home
set -gx JAVA_HOME /opt/homebrew/opt/openjdk@17/libexec/openjdk.jdk/Contents/Home
else if test -d /home/linuxbrew/.linuxbrew/opt/openjdk@17
set -gx JAVA_HOME /home/linuxbrew/.linuxbrew/opt/openjdk@17
end
if set -q JAVA_HOME
if not contains $JAVA_HOME/bin $PATH
set -gx PATH $JAVA_HOME/bin $PATH
end
end
if test -d $HOME/Library/Android/sdk
set -gx ANDROID_HOME $HOME/Library/Android/sdk
else if test -d $HOME/Android/Sdk
set -gx ANDROID_HOME $HOME/Android/Sdk
end
if set -q ANDROID_HOME
set -gx ANDROID_SDK_ROOT $ANDROID_HOME
add_to_path $ANDROID_HOME/cmdline-tools/latest/bin $ANDROID_HOME/platform-tools
end
#################################################################
# go
set -gx GOPATH $HOME/go:$HOME/workrepo/go
set -gx GOPROXY "goproxy.cn,direct"
set -gx GOSUMDB "sum.golang.google.cn"
# editor
set -gx SYSTEMD_EDITOR vim
set -gx EDITOR vim
set -gx VISUAL vim
set -gx SUDO_EDITOR vim
add_to_path $HOME/go/bin
add_to_path $HOME/.yarn/bin $HOME/.config/yarn/global/node_modules/.bin
function gtr
go test -gcflags=all=-l -v -run=$argv
end
#################################################################
# python
set -gx PYENV_ROOT $HOME/.pyenv
# Pyenv initialization
if command -v pyenv 1>/dev/null 2>&1
pyenv init - | source
end
add_to_path $PYENV_ROOT/bin
# pnpm
set -gx PNPM_HOME $HOME/.pnpm
if not string match -q -- $PNPM_HOME $PATH
set -gx PATH "$PNPM_HOME" $PATH
end
#################################################################
# git
# alias
alias gitb="git branch --show-current | tr -d '\n' | clip-copy.sh"
alias gci="golangci-lint run -c=~/.dotfile/golang/golangci.yml ./..."
alias gmt="go mod tidy"
alias gtc="go test -gcflags=all=-l -cover ./..."
alias swg="swag init -o docs -g "
alias sws="swagger serve -F=swagger"
alias sourceit="source ~/.config/fish/config.fish"
#################################################################
# docker
function dc
# 检查第一个参数是否是 is_sudo
set use_sudo false
set args $argv
if test (count $argv) -gt 0; and test "$argv[1]" = is_sudo
set use_sudo true
set args $argv[2..-1]
end
# 检查 docker-compose 是否可用
if command -v docker-compose >/dev/null
set compose_cmd docker-compose
else
set compose_cmd docker compose
end
# 构建执行命令(如果需要 sudo,添加 sudo 前缀)
if test $use_sudo = true
set exec_cmd command sudo $compose_cmd
else
set exec_cmd $compose_cmd
end
switch $args[1]
case lf
$exec_cmd logs -f $args[2..-1]
case upd
$exec_cmd up -d $args[2..-1] && $exec_cmd logs -f $args[2..-1]
case reupd
$exec_cmd up -d --force-recreate $args[2..-1] && $exec_cmd logs -f $args[2..-1]
case '*'
$exec_cmd $args
end
end
#################################################################
# systemctl
function sc
systemctl $argv
end
function scu
systemctl --user $argv
end
function jc
journalctl $argv
end
function jcu
journalctl --user $argv
end
# 智能 sudo 包装,支持展开函数别名
function sudo --wraps sudo
if test (count $argv) -gt 0
set cmd $argv[1]
switch $cmd
case systemctl
command sudo env SYSTEMD_EDITOR=$SYSTEMD_EDITOR systemctl $argv[2..-1]
case sc
command sudo env SYSTEMD_EDITOR=$SYSTEMD_EDITOR systemctl $argv[2..-1]
case jc
command sudo journalctl $argv[2..-1]
case dc
# 调用 dc 函数并传递 is_sudo 参数
dc is_sudo $argv[2..-1]
case '*'
command sudo $argv
end
else
command sudo $argv
end
end
#################################################################
# other
function ctl
systemctl $argv
end
if status is-interactive
# Commands to run in interactive sessions can go here
end
# source env
function source_env
for line in (cat $argv | grep -v '^#' | grep -v '^\s*$')
set item (string split -m 1 '=' $line)
set -gx $item[1] (eval echo $item[2])
end
end
function make2
make -f Makefile.local $argv
end
# Added by OrbStack: command-line tools and integration
# This won't be added again if you remove it.
source ~/.orbstack/shell/init2.fish 2>/dev/null || :
# mise (formerly rtx)
if command -v mise >/dev/null
add_to_path $HOME/.local/bin
add_to_path $HOME/.local/share/mise/shims
mise activate fish | source
end
function vim
if command -q nvim
command nvim $argv
else if command -q vim
command vim $argv
else
command vi $argv
end
end
# bun
set --export BUN_INSTALL "$HOME/.bun"
set --export PATH $BUN_INSTALL/bin $PATH
+7
View File
@@ -0,0 +1,7 @@
function _notify_emit --argument-names title body
if set -q TMUX
printf '\ePtmux;\e\e]777;notify;%s;%s\a\e\\' "$title" "$body"
else
printf '\e]777;notify;%s;%s\a' "$title" "$body"
end
end
@@ -0,0 +1,13 @@
function _notify_human_duration --argument-names ms
test -z "$ms"; and set ms 0
set -l s (math --scale=0 $ms / 1000)
if test $s -lt 1
echo {$ms}ms
else if test $s -lt 60
echo {$s}s
else if test $s -lt 3600
echo (math --scale=0 $s / 60)m(math $s % 60)s
else
echo (math --scale=0 $s / 3600)h(math --scale=0 ($s % 3600) / 60)m
end
end
+52
View File
@@ -0,0 +1,52 @@
# Execute script from script directory
function execute-script
if not functions -q script-show-catalog
source ~/.dotfile/fish/functions/script-catalog.fish
end
set -l dotfile_dir ~/.dotfile
set -l script_dir $dotfile_dir/script
set -l script_name $argv[1]
# ff script|s -h / --help / help
if test -z "$script_name"; or contains -- $script_name -h --help help
if test -z "$script_name"; or test (count $argv) -eq 1
script-show-catalog
return 0
end
end
if test -z "$script_name"
script-show-catalog
return 1
end
set -l script_path (script-resolve-path $script_name 2>/dev/null; or echo "")
if test -n "$script_path" -a -f "$script_path"
echo "Running: $script_path"
set -l run_status 0
if test -x $script_path
$script_path $argv[2..-1]
set run_status $status
else if string match -q "*.py" $script_path
python3 $script_path $argv[2..-1]
set run_status $status
else
bash $script_path $argv[2..-1]
set run_status $status
end
if test "$script_name" = toggle-region
if not functions -q region_apply_from_custom
source ~/.dotfile/fish/functions/region.fish
end
region_apply_from_custom
end
return $run_status
end
echo "Error: Script not found: $script_name"
echo ""
script-show-catalog
return 1
end
+707
View File
@@ -0,0 +1,707 @@
# ff ssh: local init (keys + config.d), copy key + add host, copy config to remote
function __ff_ssh_has_key
for f in $HOME/.ssh/id_*
if test -f $f; and not string match -q '*.pub' -- $f
return 0
end
end
return 1
end
function __ff_ssh_ensure_keys
if __ff_ssh_has_key
echo "SSH key exists, skip generation"
return 0
end
set -l key_path "$HOME/.ssh/id_ed25519"
set -l comment "$USER@"(hostname -s 2>/dev/null; or echo localhost)
mkdir -p "$HOME/.ssh"
chmod 700 "$HOME/.ssh"
echo "No SSH key found, generating ed25519: $key_path"
if test -t 0
ssh-keygen -t ed25519 -f $key_path -C $comment; or return 1
else
ssh-keygen -t ed25519 -f $key_path -C $comment -N ""; or return 1
echo "Non-interactive mode: created with empty passphrase"
end
end
function __ff_ssh_has_toplevel_include --argument ssh_config
awk '
tolower($1) == "host" || tolower($1) == "match" { saw_block = 1 }
tolower($1) == "include" {
for (i = 2; i <= NF; i++) {
if (tolower($i) ~ /(^|\/)config\.d\/\*/) {
if (!saw_block) found_ok = 1
}
}
}
END { exit(found_ok ? 0 : 1) }
' "$ssh_config"
end
function __ff_ssh_prepend_include --argument ssh_config include_line
set -l tmp (mktemp)
awk -v include_line="$include_line" '
function is_config_d_include() {
if (tolower($1) != "include") {
return 0
}
for (i = 2; i <= NF; i++) {
if (tolower($i) ~ /(^|\/)config\.d\/\*/) {
return 1
}
}
return 0
}
BEGIN { print include_line }
is_config_d_include() { next }
{ print }
' "$ssh_config" > $tmp
cp $tmp "$ssh_config"
rm -f $tmp
chmod 600 "$ssh_config"
end
function __ff_ssh_ensure_config_d
set -l ssh_config "$HOME/.ssh/config"
set -l include_line "Include ~/.ssh/config.d/*"
mkdir -p "$HOME/.ssh/config.d"
chmod 700 "$HOME/.ssh"
if not test -f $ssh_config
printf '%s\n' "$include_line" > $ssh_config
chmod 600 $ssh_config
echo "Created $ssh_config with $include_line"
return
end
chmod 600 $ssh_config
if __ff_ssh_has_toplevel_include $ssh_config
echo "config.d Include already in $ssh_config"
return
end
set -l backup "$ssh_config.backup."(date +%Y%m%d%H%M%S)
cp -p $ssh_config $backup
__ff_ssh_prepend_include $ssh_config "$include_line"
echo "Prepended to $ssh_config: $include_line (backup $backup)"
end
function __ff_ssh_resolve_path --argument path
if test -L $path
set path (readlink $path)
if not string match -q '/*' -- $path
set path "$HOME/.ssh/$path"
end
end
echo $path
end
function __ff_ssh_remote_has_include --argument host
set -l script 'test -f ~/.ssh/config && awk "
tolower(\$1) == \"host\" || tolower(\$1) == \"match\" { saw_block = 1 }
tolower(\$1) == \"include\" {
for (i = 2; i <= NF; i++) {
if (tolower(\$i) ~ /(^|\/)config\.d\/\*/) {
if (!saw_block) found_ok = 1
}
}
}
END { exit(found_ok ? 0 : 1) }
" ~/.ssh/config'
printf '%s\n' "$script" | ssh -qT $host sh -s
end
function __ff_ssh_remote_ensure_include --argument host
set -l script 'mkdir -p ~/.ssh/config.d && chmod 700 ~/.ssh
include_line="Include ~/.ssh/config.d/*"
has_toplevel() {
awk "
tolower(\$1) == \"host\" || tolower(\$1) == \"match\" { saw_block = 1 }
tolower(\$1) == \"include\" {
for (i = 2; i <= NF; i++) {
if (tolower(\$i) ~ /(^|\/)config\.d\/\*/) {
if (!saw_block) found_ok = 1
}
}
}
END { exit(found_ok ? 0 : 1) }
" ~/.ssh/config
}
if [ ! -f ~/.ssh/config ]; then
printf "%s\n" "$include_line" > ~/.ssh/config
chmod 600 ~/.ssh/config
echo "Created remote config with config.d Include"
elif has_toplevel; then
echo "Remote config.d Include already exists"
else
tmp=$(mktemp)
awk -v include_line="$include_line" "
function is_config_d_include() {
if (tolower(\$1) != \"include\") return 0
for (i = 2; i <= NF; i++) {
if (tolower(\$i) ~ /(^|\/)config\.d\/\*/) return 1
}
return 0
}
BEGIN { print include_line }
is_config_d_include() { next }
{ print }
" ~/.ssh/config > "$tmp"
cp "$tmp" ~/.ssh/config
rm -f "$tmp"
chmod 600 ~/.ssh/config
echo "Prepended config.d Include on remote"
fi'
printf '%s\n' "$script" | ssh -T $host sh -s
end
function __ff_ssh_resolve_copy_src --argument file
set -l src $file
if test -f $src
echo $src
return 0
end
set -l in_config_d "$HOME/.ssh/config.d/$file"
if test -f $in_config_d
echo $in_config_d
return 0
end
set -l dotfile_src "$HOME/.dotfile/ssh/config.d/$file"
if test -f $dotfile_src
echo $dotfile_src
return 0
end
return 1
end
function __ff_ssh_copy_remote
set -l host $argv[1]
set -l files $argv[2..-1]
set -l ssh_dir "$HOME/.ssh"
set -l main_config "$ssh_dir/config"
if test -z "$host"
echo "Usage: ff ssh copy <host> <config-file>..."
echo " host: SSH alias or user@hostname"
echo " file: path, or name under ~/.ssh/config.d/"
return 1
end
if test (count $files) -eq 0
echo "Usage: ff ssh copy <host> <config-file>..."
echo ""
echo "Examples:"
echo " ff ssh copy mynas 30-hanxi.conf"
echo " ff ssh copy mynas ~/.dotfile/ssh/config.d/20-personal.conf"
if test -d "$ssh_dir/config.d"
echo ""
echo "Available in ~/.ssh/config.d/:"
ls -1 "$ssh_dir/config.d/"
end
return 1
end
echo "Checking remote Include on $host ..."
ssh -q $host "mkdir -p ~/.ssh/config.d && chmod 700 ~/.ssh"
if __ff_ssh_remote_has_include $host
echo "Remote config.d Include: OK"
else
echo "Remote missing config.d Include, adding ..."
__ff_ssh_remote_ensure_include $host
end
for f in $files
set -l src (__ff_ssh_resolve_copy_src $f 2>/dev/null)
if test $status -ne 0 -o -z "$src"
echo "Error: file not found: $f"
return 1
end
set -l resolved_main (__ff_ssh_resolve_path $main_config)
set -l name (basename $src)
if test "$src" = "$resolved_main"; or test "$name" = config
set -l ts (date +%Y%m%d%H%M%S)
ssh -q $host "test -f ~/.ssh/config && cp ~/.ssh/config ~/.ssh/config.bak.$ts || true"
scp -q $src "$host:.ssh/config"
ssh -q $host "chmod 600 ~/.ssh/config"
echo "Copied $src -> $host:~/.ssh/config"
else
scp -q $src "$host:.ssh/config.d/$name"
ssh -q $host "chmod 600 ~/.ssh/config.d/$name"
echo "Copied $src -> $host:~/.ssh/config.d/$name"
end
end
echo "Done."
end
# ---- ssh-style argument parsing -------------------------------------------
# ssh short options that consume a following value
function __ff_ssh_opt_takes_value --argument-names flag
contains -- $flag -b -c -D -E -e -F -I -i -J -L -l -m -O -o -p -Q -R -S -W -w
end
# First non-option argument of `ssh`-style args (the [user@]host)
function __ff_ssh_find_target
set -l n (count $argv)
set -l i 1
while test $i -le $n
set -l a $argv[$i]
if test "$a" = '--'
set i (math $i + 1)
if test $i -le $n
echo $argv[$i]
end
return 0
end
if string match -q -- '-*' $a
set -l flag (string sub -s 1 -l 2 -- $a)
if test (string length -- $a) -eq 2; and __ff_ssh_opt_takes_value $flag
set i (math $i + 2)
continue
end
set i (math $i + 1)
continue
end
echo $a
return 0
end
return 1
end
# Value of a short option, supporting both `-i file` and `-ifile`
function __ff_ssh_argv_value --argument-names flag
set -l args $argv[2..-1]
set -l n (count $args)
set -l flen (string length -- $flag)
set -l i 1
while test $i -le $n
set -l a $args[$i]
if test "$a" = "$flag"
set -l j (math $i + 1)
if test $j -le $n
echo $args[$j]
end
return 0
end
if test (string length -- $a) -gt $flen; and string match -q -- "$flag*" $a
string sub -s (math $flen + 1) -- $a
return 0
end
set i (math $i + 1)
end
return 1
end
# One value from `ssh -G` output (stdin)
function __ff_ssh_gval --argument-names key
while read -l line
set -l trimmed (string trim -- $line)
set -l k (string split -m1 ' ' -- $trimmed)[1]
if test (string lower -- $k) = (string lower -- $key)
string replace -r '^\S+\s+' '' -- $trimmed
return 0
end
end
end
# Files declaring an exact alias; repeated declarations need manual disambiguation.
function __ff_ssh_find_alias_file --argument-names alias_name
set -l files $HOME/.ssh/config
if test -d $HOME/.ssh/config.d
set files $files $HOME/.ssh/config.d/*
end
for f in $files
test -f $f; or continue
awk -v want=(string lower -- $alias_name) '
tolower($1) == "host" {
for (i = 2; i <= NF && $i !~ /^#/; i++) {
if (tolower($i) == want) { print FILENAME; break }
}
}
' $f
end
end
# Replace managed values in place; split shared Host declarations for other aliases.
function __ff_ssh_replace_alias --argument-names alias_name original replacement
awk -v want=(string lower -- $alias_name) -v replacement="$replacement" '
BEGIN {
while ((getline line < replacement) > 0) block = block line "\n"
close(replacement)
}
tolower($1) == "host" || tolower($1) == "match" {
updating = 0
if (tolower($1) == "host") {
hit = 0; others = ""; comment = ""
for (i = 2; i <= NF; i++) {
if ($i ~ /^#/) {
for (; i <= NF; i++) comment = comment " " $i
break
}
if (tolower($i) == want) hit = 1
else others = others " " $i
}
if (hit) {
printf "%s", block
if (others != "") print "Host" others " !" want comment
else {
updating = 1
if (comment != "") print " " comment
}
next
}
}
}
updating && tolower($1) ~ /^(hostname|user|port|identityfile|proxyjump|proxycommand)$/ { next }
{ print }
' "$original"
end
# Normalize ssh -G paths (including explicit .pub) to a private-key path.
function __ff_ssh_private_path --argument-names identity
set identity (string replace -r '^~/' "$HOME/" -- $identity)
set identity (string replace -a '%d' "$HOME" -- $identity)
set identity (string replace -r '\.pub$' '' -- $identity)
if not string match -q '/*' -- $identity
set identity "$PWD/$identity"
end
printf '%s\n' "$identity"
end
# Copy only the selected key; upstream can return 0 after an interrupted filter.
function __ff_ssh_copy_id
set -l target $argv[1]
set -l port $argv[2]
set -l identity $argv[3]
set -l proxyjump $argv[4]
set -l proxycommand $argv[5]
if not type -q ssh-copy-id
echo "ff ssh: 未找到 ssh-copy-id;未写入 Host(可用 --no-copy 只写配置)" >&2
return 1
end
set -l options -o ConnectTimeout=10 -o ConnectionAttempts=1
if test -n "$proxyjump"
set options $options -o "ProxyJump=$proxyjump"
end
if test -n "$proxycommand"
set options $options -o "ProxyCommand=$proxycommand"
end
set -l cmd ssh-copy-id -i "$identity.pub" -p "$port" $options "$target"
echo "复制公钥: $cmd"
# The wrapper records SIGINT even if ssh-copy-id's cleanup trap exits 0.
command sh -c 'trap "exit 130" INT; trap "exit 143" TERM; "$@"' ff-ssh $cmd
if test $status -ne 0
echo "ff ssh: 公钥复制失败或已取消,未写入 Host" >&2
return 1
end
# A skipped-key warning alone proves nothing. Exclude inherited identities,
# certificates and multiplexed sessions; the agent may unlock this key only.
command ssh -F /dev/null -i "$identity" -p "$port" $options \
-o BatchMode=yes -o PreferredAuthentications=publickey \
-o IdentitiesOnly=yes -o CertificateFile=none -o ControlPath=none \
"$target" exit
if test $status -ne 0
echo "ff ssh: 公钥可能已复制,但所选密钥登录验证失败,未写入 Host。加密私钥请先用 ssh-add 解锁,或用 --no-copy 只写配置" >&2
return 1
end
end
# `ff ssh a@b.com` / `ff ssh -p 2222 -i key root@host`
# 确保本地 key -> ssh-copy-id -> 写 ~/.ssh/config.d/<alias>.conf
function __ff_ssh_add_host
set -l args
set -l do_copy 1
for a in $argv
if test "$a" = --no-copy
set do_copy 0
else
set args $args $a
end
end
set -l target (__ff_ssh_find_target $args)
if test -z "$target"
echo "ff ssh: 未找到 [user@]hostname 参数" >&2
return 1
end
# ssh://user@host:port/path -> user / host / port
set -l plain (string replace -r '^ssh://' '' -- $target)
set plain (string replace -r '/.*$' '' -- $plain)
set -l user ""
if string match -q '*@*' -- $plain
set user (string replace -r '@[^@]*$' '' -- $plain)
end
set -l hostport (string replace -r '^.*@' '' -- $plain)
set -l port ""
if string match -q '*:*' -- $hostport
set port (string replace -r '^.*:' '' -- $hostport)
set hostport (string replace -r ':[^:]*$' '' -- $hostport)
end
set -l host $hostport
if test -z "$host"
echo "ff ssh: 无法解析主机名: $target" >&2
return 1
end
test -n "$port"; or set port (__ff_ssh_argv_value -p $args)
set -l alias_name $host
if test -t 0
read -P "SSH 别名 [$host]: " answer
if test $status -ne 0
echo "ff ssh: 已取消,未复制公钥或改写配置" >&2
return 1
end
test -n "$answer"; and set alias_name $answer
end
if not string match -qr '^[A-Za-z0-9][A-Za-z0-9._-]*$' -- $alias_name
echo "ff ssh: 非法别名 '$alias_name'(只允许字母、数字、点、下划线、连字符)" >&2
return 1
end
set -l existing (__ff_ssh_find_alias_file $alias_name)
if test -n "$existing"
if test (count $existing) -ne 1
echo "ff ssh: 别名 '$alias_name' 有多个 Host 声明($existing),请先手动合并;未复制公钥或改写配置" >&2
return 1
end
echo "SSH 别名 '$alias_name' 已存在于 $existing"
if not test -t 0
echo "ff ssh: 非交互模式无法确认覆盖,未复制公钥或改写配置" >&2
return 1
end
read -l -P "覆盖更新 '$alias_name'?[y/N]: " overwrite
if test $status -ne 0
echo "ff ssh: 已取消覆盖,未复制公钥或改写配置" >&2
return 1
end
if not contains -- (string lower -- (string trim -- "$overwrite")) y yes
echo "ff ssh: 未确认覆盖,未复制公钥或改写配置" >&2
return 1
end
else if test -e "$HOME/.ssh/config.d/$alias_name.conf"
echo "ff ssh: 目标文件已存在但未声明 '$alias_name',请先手动整理;未复制公钥或改写配置" >&2
return 1
end
# 用 ssh -G 解析最终生效的 HostName/User/Port/Proxy*
set -l resolved (ssh -G $args 2>/dev/null)
if test $status -ne 0
echo "ff ssh: 无法解析 SSH 配置,未写入 Host" >&2
return 1
end
set -l rhost (printf '%s\n' $resolved | __ff_ssh_gval hostname)
set -l ruser (printf '%s\n' $resolved | __ff_ssh_gval user)
set -l rport (printf '%s\n' $resolved | __ff_ssh_gval port)
set -l proxyjump (printf '%s\n' $resolved | __ff_ssh_gval proxyjump)
set -l proxycommand (printf '%s\n' $resolved | __ff_ssh_gval proxycommand)
test -n "$rhost"; or set rhost $host
test -n "$user"; or set user $ruser
test -n "$port"; or set port $rport
test -n "$port"; or set port 22
test "$proxyjump" = none; and set proxyjump ""
test "$proxycommand" = none; and set proxycommand ""
# Explicit -i / -o IdentityFile must never silently fall back to another key.
set -l explicit_identity ""
for i in (seq (count $args))
if test "$args[$i]" = -i; and test $i -lt (count $args)
set explicit_identity $args[(math $i + 1)]
break
else if string match -q -- '-i*' "$args[$i]"
set explicit_identity (string sub -s 3 -- "$args[$i]")
break
end
set -l option ""
if test "$args[$i]" = -o; and test $i -lt (count $args)
set option $args[(math $i + 1)]
else if string match -q -- '-o*' "$args[$i]"
set option (string sub -s 3 -- "$args[$i]")
end
if string match -qri '^identityfile[=[:space:]]' -- "$option"
set explicit_identity (string replace -ri '^identityfile[=[:space:]]+' '' -- "$option")
break
end
end
set -l identities (printf '%s\n' $resolved | string match -ri '^identityfile[[:space:]]+.*$' | string replace -r '^\S+\s+' '')
set -l copy_identity ""
set -l configured_identity 0
if test -n "$explicit_identity"
# ssh -G can omit an unreadable -i and print defaults instead.
set copy_identity (__ff_ssh_private_path "$explicit_identity")
set identities
end
for candidate in $identities
set -l private (__ff_ssh_private_path "$candidate")
set -l name (string replace -- "$HOME/.ssh/" '' "$private")
if not contains -- "$name" id_rsa id_dsa id_ecdsa id_ecdsa_sk id_ed25519 id_ed25519_sk id_xmss
set configured_identity 1
end
if test -r "$private"; and test -r "$private.pub"
set copy_identity "$private"
break
end
end
# ssh-copy-id also supports nonstandard id_*.pub names. Among these, keep
# its newest-public-key preference, but only select a complete readable pair.
if test -z "$copy_identity"; and test -z "$explicit_identity"; and test "$configured_identity" = 0
for pub in (command ls -t "$HOME"/.ssh/id_*.pub 2>/dev/null)
string match -q '*-cert.pub' -- "$pub"; and continue
set -l private (__ff_ssh_private_path "$pub")
if test -r "$private"; and test -r "$pub"
set copy_identity "$private"
break
end
end
end
if test "$do_copy" = 1
if test -z "$copy_identity"; and test -z "$explicit_identity"; and test "$configured_identity" = 0
__ff_ssh_ensure_keys; or return 1
set -l generated "$HOME/.ssh/id_ed25519"
if test -r "$generated"; and test -r "$generated.pub"
set copy_identity "$generated"
end
end
if test -z "$copy_identity"; or not test -r "$copy_identity"; or not test -r "$copy_identity.pub"
echo "ff ssh: 未找到可读的公私钥对,请用 -i 指定密钥;未写入 Host" >&2
return 1
end
echo "所选 SSH 密钥: $copy_identity"
set -l copy_target "$rhost"
test -n "$user"; and set copy_target "$user@$rhost"
__ff_ssh_copy_id "$copy_target" "$port" "$copy_identity" "$proxyjump" "$proxycommand"; or return 1
end
set -l identity "$copy_identity"
if string match -q -- "$HOME/*" "$identity"
set identity "~/"(string sub -s (math (string length -- "$HOME") + 2) -- "$identity")
end
set -l dir "$HOME/.ssh/config.d"
set -l conf_file "$dir/$alias_name.conf"
test -n "$existing"; and set conf_file "$existing"
set -l tmpdir /tmp
test -n "$TMPDIR"; and set tmpdir $TMPDIR
set -l tmp (mktemp "$tmpdir/ff-ssh.XXXXXX")
begin
printf 'Host %s\n' $alias_name
printf ' HostName %s\n' $rhost
if test -n "$user"
printf ' User %s\n' $user
end
printf ' Port %s\n' $port
if test -n "$identity"
printf ' IdentityFile "%s"\n' $identity
end
if test -n "$proxyjump"
printf ' ProxyJump %s\n' $proxyjump
end
if test -n "$proxycommand"
printf ' ProxyCommand %s\n' $proxycommand
end
end > $tmp
if test -n "$existing"
set -l updated (mktemp "$tmpdir/ff-ssh.XXXXXX"); or begin
rm -f $tmp
return 1
end
__ff_ssh_replace_alias "$alias_name" "$conf_file" "$tmp" > $updated; or begin
rm -f $tmp $updated
return 1
end
rm -f $tmp
set tmp $updated
# config.d/* would load backups too, so keep them outside that directory.
set -l backup_dir "$HOME/.ssh/config.backups"
mkdir -p "$backup_dir"; or return 1
chmod 700 "$backup_dir"
set -l backup (mktemp "$backup_dir/"(basename "$conf_file")".backup.XXXXXX"); or return 1
cp -p "$conf_file" "$backup"; or begin
rm -f $tmp $backup
return 1
end
echo "原配置备份: $backup"
end
mkdir -p $dir; or return 1
chmod 700 "$HOME/.ssh" $dir
chmod 600 $tmp
mv $tmp $conf_file; or return 1
__ff_ssh_ensure_config_d; or return 1
echo "已写入 SSH 配置: $conf_file"
echo "以后可直接运行: ssh $alias_name"
end
function __ff_ssh_usage
echo "用法: ff ssh [init | <ssh 参数> | add <ssh 参数> | run <ssh 参数> | copy <host> <file>... | help]"
echo ""
echo " (无参数) / init 确保本地 SSH key 存在,并启用 ~/.ssh/config.d Include"
echo " add <ssh 参数> 确保本地 key,ssh-copy-id 后写入 ~/.ssh/config.d/<别名>.conf"
echo " run <ssh 参数> 同 add,写完后立即连接(此时应已免密)"
echo " <ssh 参数> 裸参数等同 add(例: ff ssh -p 2222 root@1.2.3.4)"
echo " copy <host> <file> 检查远端 Include,然后 scp 指定配置文件到远端"
echo " help 显示此帮助"
echo ""
echo "选项:"
echo " --no-copy 只写配置,不执行 ssh-copy-id 或登录验证"
echo " 重复别名需交互确认覆盖(默认否),成功后备份并在原位置更新"
echo " 公钥复制后仅用所选密钥验证登录;连接超时 10 秒,失败或取消不写 Host"
echo " 验证使用 BatchMode;加密私钥请先用 ssh-add 解锁"
echo ""
echo "保留子命令: init / copy / add / run / help(同名主机请用 ssh 直接连接)"
echo ""
echo "示例:"
echo " ff ssh # 初始化本地 key 与 config.d"
echo " ff ssh a@b.com # 复制公钥 + 添加 Host 配置(交互式确认别名)"
echo " ff ssh -p 2222 -i ~/.ssh/id_x root@1.2.3.4"
echo " ff ssh --no-copy a@b.com # 只写配置,不复制公钥"
echo " ff ssh run a@b.com # 复制公钥 + 写配置并立即连接"
echo " ff ssh copy mynas 30-hanxi.conf # 推送配置片段到远端"
end
function ff_ssh
set -l sub (test (count $argv) -ge 1; and echo $argv[1]; or echo init)
switch $sub
case init ''
__ff_ssh_ensure_keys
__ff_ssh_ensure_config_d
case add
__ff_ssh_add_host $argv[2..-1]
case run
set -l run_args
for a in $argv[2..-1]
test "$a" = --no-copy; and continue
set run_args $run_args $a
end
__ff_ssh_add_host $argv[2..-1]; or return 1
command ssh $run_args
case copy
__ff_ssh_copy_remote $argv[2..-1]
case help -h --help
__ff_ssh_usage
case '*'
# 裸参数与 ssh 原生参数一致:有主机就记录配置,否则透传给 ssh
if __ff_ssh_find_target $argv >/dev/null
__ff_ssh_add_host $argv
else
command ssh $argv
end
end
end
+372
View File
@@ -0,0 +1,372 @@
# Fast commands management
function _ff_config_file
echo ~/.config/ff/config
end
function _ff_state_file
echo ~/.local/share/ff/state
end
function _ff_config_value --argument-names key default
set -l file (_ff_config_file)
if not test -f $file
echo $default
return
end
set -l found
for line in (cat $file)
set line (string trim -- $line)
if test -z "$line"; or string match -q '#*' -- $line
continue
end
if string match -q -- "$key=*" $line
set found (string split -m 1 -- '=' $line)[2]
end
end
if test -n "$found"
echo (string trim -- $found)
else
echo $default
end
end
function _ff_parse_interval --argument-names raw
set -l text (string trim -- (string lower -- $raw))
if test -z "$text"
echo 86400
return
end
if string match -qr '^[0-9]+$' -- $text
echo $text
return
end
set -l unit (string sub -s -1 $text)
set -l amount (string sub -e -1 $text)
if not string match -qr '^[0-9]+$' -- $amount
echo 86400
return
end
switch $unit
case s
echo $amount
case m
math $amount '*' 60
case h
math $amount '*' 3600
case d
math $amount '*' 86400
case '*'
echo 86400
end
end
function _ff_env_no_auto_update
if not set -q FF_NO_AUTO_UPDATE
return 1
end
switch (string lower -- (string trim -- $FF_NO_AUTO_UPDATE))
case 1 true yes on
return 0
case '*'
return 1
end
end
function _ff_auto_update_enabled
switch (string lower -- (_ff_config_value enabled true))
case 0 false no off
return 1
case '*'
return 0
end
end
function _ff_record_auto_update
set -l file (_ff_state_file)
mkdir -p (dirname $file)
or return
echo "last_auto_update="(date +%s) >$file
end
function _ff_last_auto_update
set -l file (_ff_state_file)
if not test -f $file
echo 0
return
end
set -l val 0
for line in (cat $file)
if string match -q 'last_auto_update=*' -- $line
set val (string split -m 1 -- '=' $line)[2]
end
end
if string match -qr '^[0-9]+$' -- (string trim -- $val)
echo (string trim -- $val)
else
echo 0
end
end
function _ff_interval_elapsed --argument-names interval
if test "$interval" -le 0
return 0
end
set -l last (_ff_last_auto_update)
if test "$last" -eq 0
return 0
end
set -l now (date +%s)
test (math $now - $last) -ge $interval
end
function _ff_reload
for __f in ~/.dotfile/fish/functions/*.fish
if test -f $__f
functions -e (basename $__f .fish) 2>/dev/null
end
end
source ~/.config/fish/config.fish
# fish 对已加载过的 autoload 函数不会在 functions -e 后重新加载,
# 必须显式 source 一遍,否则函数(含 ff 自身)会丢失
for __f in ~/.dotfile/fish/functions/*.fish
if test -f $__f
source $__f
end
end
set -e __f
end
# 自动 git pull ~/.dotfile。失败/跳过不得打断当前 ff 子命令。
# 返回: 0 有更新, 1 失败或脏树, 2 静默跳过, 3 已是最新
function _ff_auto_update_try_pull
set -l dir ~/.dotfile
if not test -d $dir
return 2
end
if not git -C $dir rev-parse --is-inside-work-tree >/dev/null 2>&1
return 2
end
set -l porcelain (git -C $dir status --porcelain 2>/dev/null)
if test -n "$porcelain"
echo "[ff] 自动更新已跳过: 工作区不干净" >&2
return 1
end
set -l before (git -C $dir rev-parse HEAD 2>/dev/null)
set -lx GIT_TERMINAL_PROMPT 0
set -l st 0
if command -q timeout
command timeout 20 git -C $dir pull --ff-only origin main >/dev/null 2>&1
set st $status
else
git -C $dir pull --ff-only origin main >/dev/null 2>&1
set st $status
end
if test $st -eq 124
echo "[ff] 自动更新失败,已跳过: git 超时" >&2
return 1
end
if test $st -ne 0
echo "[ff] 自动更新失败,已跳过" >&2
return 1
end
set -l after (git -C $dir rev-parse HEAD 2>/dev/null)
if test -n "$before" -a -n "$after" -a "$before" != "$after"
echo "[ff] 已自动更新 dotfile" >&2
return 0
end
return 3
end
function _ff_maybe_auto_update --argument-names command no_auto_flag
if test "$no_auto_flag" = 1
return
end
if _ff_env_no_auto_update
return
end
# 显式 ff update 自己拉,这里不要再拉一遍
if test "$command" = update
return
end
if not _ff_auto_update_enabled
return
end
set -l interval (_ff_parse_interval (_ff_config_value interval 24h))
if not _ff_interval_elapsed $interval
return
end
_ff_auto_update_try_pull
set -l st $status
if test $st -eq 2
return
end
_ff_record_auto_update
if test $st -eq 0
_ff_reload
end
end
function ff
# 定义命令和描述(格式:命令|描述)
set -l commands \
"reload|Reload fish configuration" \
"edit|Edit custom fish configuration" \
"fish|Change to fish config directory" \
"dotfile|Change to dotfile directory" \
"pon|Enable proxy (proxy on)" \
"poff|Disable proxy (proxy off)" \
"pst|Show proxy status" \
"make|Run dotfile Makefile commands" \
"script|Run script from script directory" \
"s|Alias for script" \
"update|Update dotfile now (git pull + reload); also auto every 24h" \
"region|Toggle or set CN_REGION (mirror/proxy region)" \
"ssh|SSH init, copy key + add host config, or copy config to remote" \
"dirsize|Show directory sizes (du wrapper)" \
"pouch|Clone and install pouch to ~/.pouch" \
"help|Show this help message"
set -l no_auto 0
set -l args
set -l past_command 0
for a in $argv
if test $past_command -eq 0; and test "$a" = "--no-auto-update"
set no_auto 1
continue
end
set past_command 1
set -a args $a
end
set argv $args
_ff_maybe_auto_update "$argv[1]" $no_auto
# 确保 execute-script 可用(在 fish 自动加载前手动引入一次)
if not functions -q execute-script
set -l execute_script_path ~/.dotfile/fish/functions/execute-script.fish
if test -f $execute_script_path
source $execute_script_path
end
end
switch $argv[1]
case reload
# autoload 会缓存函数定义,先清掉 dotfile 里的函数再重载
_ff_reload
echo "Fish configuration reloaded!"
case edit
vim ~/.config/fish/custom.fish
case fish
cd ~/.config/fish/
case dotfile
cd ~/.dotfile
case pon
set -gx https_proxy http://127.0.0.1:7890
set -gx http_proxy http://127.0.0.1:7890
set -gx all_proxy socks5://127.0.0.1:7890
echo "✓ Proxy enabled"
export | grep -i proxy
case poff
set -e http_proxy
set -e https_proxy
set -e ftp_proxy
set -e all_proxy
set -e HTTP_PROXY
set -e HTTPS_PROXY
set -e FTP_PROXY
set -e ALL_PROXY
echo "✓ Proxy disabled"
export | grep -i proxy
case pst
export | grep -i proxy
case make
# 切换到 dotfile 目录执行脚本管理 makefile
set -l dotfile_dir ~/.dotfile
if test -d $dotfile_dir
make -f $dotfile_dir/Makefile.scripts -C $dotfile_dir $argv[2..-1]
else
echo "Error: Dotfile directory not found at $dotfile_dir"
end
case script s
if not functions -q execute-script
source ~/.dotfile/fish/functions/execute-script.fish
end
execute-script $argv[2..-1]
case help
echo "Usage: ff <command>"
echo ""
echo "Commands:"
# 自动生成帮助信息
for cmd in $commands
set -l parts (string split '|' $cmd)
printf " %-10s %s\n" $parts[1] $parts[2]
end
echo ""
echo "Auto-update: ordinary ff commands git-pull ~/.dotfile every 24h (default on)."
echo " Config: ~/.config/ff/config (enabled=true, interval=24h)"
echo " Skip: FF_NO_AUTO_UPDATE=1 or ff --no-auto-update <cmd>"
case update
set -l dotfile_dir ~/.dotfile
if not test -d $dotfile_dir
echo "Error: Dotfile directory not found at $dotfile_dir"
return 1
end
set -l prev_dir (pwd)
cd $dotfile_dir
if git pull origin main
_ff_record_auto_update
cd $prev_dir
echo "Reloading fish configuration..."
ff reload
else
cd $prev_dir
echo "Error: git pull failed, skip reload"
return 1
end
case ssh
# 每次都重新 source,避免 autoload 缓存旧定义
source ~/.dotfile/fish/functions/ff-ssh.fish
ff_ssh $argv[2..-1]
case dirsize
set -l script_path ~/.dotfile/script/dirsize.sh
if test -f $script_path
bash $script_path $argv[2..-1]
else
echo "Error: dirsize script not found at $script_path"
return 1
end
case pouch
set -l script_path ~/.dotfile/script/pouch-install.sh
if test -f $script_path
bash $script_path $argv[2..-1]
else
echo "Error: pouch-install script not found at $script_path"
return 1
end
case region
if not functions -q region_toggle
source ~/.dotfile/fish/functions/region.fish
end
set -l sub toggle
if test (count $argv) -ge 2
set sub $argv[2]
end
switch $sub
case toggle
region_toggle
case '*'
echo "Usage: ff region [toggle]"
echo "Unknown subcommand: $sub"
end
case '*'
if test -z "$argv[1]"
echo "Error: No command specified"
else
echo "Error: Unknown command '$argv[1]'"
end
echo "Run 'ff help' for usage information"
end
end
+24
View File
@@ -0,0 +1,24 @@
function notify-done --description '前置包装:运行命令并在完成时发送 OSC 777 通知'
if test (count $argv) -eq 0
echo "Usage: notify-done <command> [args...]" >&2
return 2
end
set -l start (date +%s)
$argv
set -l rc $status
set -l elapsed (math (date +%s) - $start)
set -l human (_notify_human_duration (math $elapsed \* 1000))
set -l label
if test $rc -eq 0
set label "完成: $argv[1]"
else
set label "失败 exit=$rc: $argv[1]"
end
set -l body "$human · "(hostname -s)" · "(string join ' ' $argv)
_notify_emit "$label" "$body"
return $rc
end
+18
View File
@@ -0,0 +1,18 @@
function notify --description '发送 OSC 777 通知;无参时复用上一条命令的状态与耗时'
set -l rc $status
set -l dur $CMD_DURATION
set -l label
if test (count $argv) -gt 0
set label (string join ' ' $argv)
else if test $rc -eq 0
set label "完成"
else
set label "失败 exit=$rc"
end
set -l body (_notify_human_duration $dur)" · "(hostname -s)" · "(pwd | string replace $HOME '~')
_notify_emit "$label" "$body"
return $rc
end
+7
View File
@@ -0,0 +1,7 @@
function pwdc --description 'Copy current directory path to clipboard'
set -l dir (pwd -P)
if not printf '%s' $dir | clip-copy.sh
return 1
end
echo "Copied: "(string replace $HOME '~' $dir)
end
+58
View File
@@ -0,0 +1,58 @@
# CN_REGION 读写 ~/.config/fish/custom.fish(与 script/common/region.sh 约定一致)
function region_custom_path
echo ~/.config/fish/custom.fish
end
function region_read
set -l path (region_custom_path)
if test -f $path
set -l matches (grep -E '^\s*set\s+-gx\s+CN_REGION\s+' $path 2>/dev/null)
if test (count $matches) -gt 0
set -l line $matches[-1]
echo (string trim (string replace -r '.*\s' '' $line))
return
end
end
if set -q CN_REGION
echo $CN_REGION
else
echo 0
end
end
function region_write
set -l new $argv[1]
set -l path (region_custom_path)
mkdir -p (dirname $path)
set -l tmp (mktemp)
if test -f $path
grep -Ev '^\s*# dotfile: CN_REGION|^\s*set\s+-gx\s+CN_REGION\s+' $path >$tmp 2>/dev/null; or true
else
touch $tmp
end
echo "# dotfile: CN_REGION (managed by ff region / toggle-region)" >>$tmp
echo "set -gx CN_REGION $new" >>$tmp
mv $tmp $path
set -gx CN_REGION $new
end
function region_toggle
set -l current (region_read)
set -l new
if test "$current" = "1"
set new 0
echo "Switched to: 0 (国外)"
else
set new 1
echo "Switched to: 1 (国内)"
end
region_write $new
echo "Persisted to ~/.config/fish/custom.fish"
end
function region_apply_from_custom
set -gx CN_REGION (region_read)
end
+126
View File
@@ -0,0 +1,126 @@
# 列举 ~/.dotfile/script 下脚本及从文件头注释提取的简短说明
function script-extract-description --argument filepath
if not test -f "$filepath"
echo "(无描述)"
return
end
set -l past_shebang 0
set -l candidates
for line in (cat "$filepath")
if string match -qr '^#!/' -- $line
set past_shebang 1
continue
end
if test $past_shebang -eq 0
continue
end
set line (string trim $line)
if test -z "$line"
if test (count $candidates) -gt 0
break
end
continue
end
if not string match -qr '^#' -- $line
break
end
set line (string replace -r '^#\s*' '' $line)
if string match -qr '^---' -- $line
continue
end
if string match -qr '^(用法|Usage|示例|Example)' -- $line
continue
end
if string match -qr '^\./' -- $line
continue
end
if string match -qr '^https?://' -- $line
continue
end
# 行内 “# 用法” 类注释
if string match -qr '#\s*(用法|Usage)' -- $line
continue
end
set candidates $candidates $line
if test (count $candidates) -ge 8
break
end
end
if test (count $candidates) -eq 0
echo "(无描述)"
return
end
for c in $candidates
if string match -qr '(脚本|安装|初始化|清理|扫描|切换|配置|用于|镜像|凭据|用户组|免密|Install|Init|Clean|Toggle)' -- $c
echo $c
return
end
end
for c in $candidates
if test (string length $c) -ge 10
echo $c
return
end
end
echo $candidates[1]
end
function script-list-names
set -l script_dir ~/.dotfile/script
set -l names
if not test -d $script_dir
return
end
for script in $script_dir/*.sh $script_dir/*.py
if test -f $script
set -l name (basename $script)
set name (string replace -r '\.(sh|py)$' '' $name)
set names $names $name
end
end
string join \n $names | sort
end
function script-resolve-path --argument name
set -l script_dir ~/.dotfile/script
for ext in sh py
set -l path "$script_dir/$name.$ext"
if test -f $path
echo $path
return 0
end
end
if test -f "$script_dir/$name"
echo "$script_dir/$name"
return 0
end
return 1
end
function script-show-catalog
set -l script_dir ~/.dotfile/script
echo "Usage: ff script <name> [args...] (alias: ff s)"
echo ""
echo "Scripts in $script_dir:"
echo ""
for name in (script-list-names)
set -l path (script-resolve-path $name)
set -l desc (script-extract-description $path)
printf " %-22s %s\n" $name $desc
end
echo ""
echo "提示: ff s <name> -h 查看单个脚本内的详细帮助"
end
+10
View File
@@ -0,0 +1,10 @@
# ssh-copy-id 后交互保存 SSH Host 别名
function ssh2 --wraps ssh-copy-id --description "Copy SSH key and save host alias"
set -l script_path ~/.dotfile/script/ssh2.sh
if not test -f $script_path
echo "Error: ssh2 script not found at $script_path" >&2
return 1
end
bash $script_path $argv
end
+181
View File
@@ -0,0 +1,181 @@
#!/usr/bin/env fish
# 回归:ff 日常命令按间隔自动更新 ~/.dotfile
set -g REPO_ROOT (cd (dirname (status filename))/../.. && pwd)
source $REPO_ROOT/fish/functions/ff.fish
set -e GIT_DIR GIT_WORK_TREE GIT_COMMON_DIR
set -g _t_ok 0
set -g _t_fail 0
function t_ok --argument-names msg
set -g _t_ok (math $_t_ok + 1)
echo "ok - $msg"
end
function t_fail --argument-names msg
set -g _t_fail (math $_t_fail + 1)
echo "not ok - $msg"
end
function t_assert --argument-names cond msg
if test $cond -eq 0
t_ok $msg
else
t_fail $msg
end
end
function setup_env
set -g TEST_HOME (mktemp -d)
set -gx HOME $TEST_HOME
set -e FF_NO_AUTO_UPDATE
mkdir -p $HOME/.config/fish $HOME/.config/ff $HOME/.local/share/ff
echo '# test fish config' >$HOME/.config/fish/config.fish
set -g UPSTREAM $TEST_HOME/upstream
mkdir -p $UPSTREAM/fish/functions
echo init >$UPSTREAM/README
echo '# stub' >$UPSTREAM/fish/functions/stub.fish
git init -q -b main $UPSTREAM
git -C $UPSTREAM config user.name Test
git -C $UPSTREAM config user.email test@example.com
git -C $UPSTREAM config commit.gpgsign false
git -C $UPSTREAM add .
git -C $UPSTREAM commit -q -m init
git clone -q $UPSTREAM $HOME/.dotfile
git -C $HOME/.dotfile config pull.ff only
git -C $HOME/.dotfile config user.name Test
git -C $HOME/.dotfile config user.email test@example.com
git -C $HOME/.dotfile config commit.gpgsign false
end
function cleanup_env
if set -q TEST_HOME
rm -rf $TEST_HOME
end
end
function add_upstream_marker --argument-names name
echo $name >$UPSTREAM/$name
git -C $UPSTREAM add $name
git -C $UPSTREAM commit -q -m $name
end
function write_state --argument-names ts
echo "last_auto_update=$ts" >$HOME/.local/share/ff/state
end
function capture_ff
set -g FF_OUT $TEST_HOME/out.txt
set -g FF_ERR $TEST_HOME/err.txt
ff $argv >$FF_OUT 2>$FF_ERR
set -g FF_STATUS $status
end
# --- tests ---
setup_env
add_upstream_marker MARKER_FIRST
capture_ff help
string match -q '*Usage: ff <command>*' <$FF_OUT
t_assert $status "interval elapsed: ff help still prints usage"
test -f $HOME/.dotfile/MARKER_FIRST
t_assert $status "interval elapsed: first command pulls new commit"
test -f $HOME/.local/share/ff/state
t_assert $status "interval elapsed: records last_auto_update"
string match -q '*Auto-update:*' <$FF_OUT
t_assert $status "ff help documents auto-update"
string match -q '*已自动更新*' <$FF_ERR
t_assert $status "interval elapsed: stderr says updated"
cleanup_env
setup_env
add_upstream_marker MARKER_RECENT
write_state (date +%s)
capture_ff help
test ! -f $HOME/.dotfile/MARKER_RECENT
t_assert $status "interval not elapsed: does not pull"
cleanup_env
setup_env
echo "enabled=false" >$HOME/.config/ff/config
add_upstream_marker MARKER_DISABLED
capture_ff help
test ! -f $HOME/.dotfile/MARKER_DISABLED
t_assert $status "enabled=false: does not pull"
cleanup_env
setup_env
add_upstream_marker MARKER_DIRTY
echo local >$HOME/.dotfile/dirty.txt
capture_ff help
string match -q '*Usage: ff <command>*' <$FF_OUT
t_assert $status "dirty tree: current command still runs"
test ! -f $HOME/.dotfile/MARKER_DIRTY
t_assert $status "dirty tree: skips pull"
string match -q '*工作区不干净*' <$FF_ERR
t_assert $status "dirty tree: stderr skip message"
cleanup_env
setup_env
write_state (date +%s)
add_upstream_marker MARKER_EXPLICIT
capture_ff update
test -f $HOME/.dotfile/MARKER_EXPLICIT
t_assert $status "explicit ff update still pulls when interval not elapsed"
set -l after_ts (string split -m 1 -- '=' (cat $HOME/.local/share/ff/state))[2]
test -n "$after_ts" -a "$after_ts" -gt 0
t_assert $status "explicit ff update refreshes timestamp"
cleanup_env
setup_env
add_upstream_marker MARKER_ENV
set -gx FF_NO_AUTO_UPDATE 1
capture_ff help
test ! -f $HOME/.dotfile/MARKER_ENV
t_assert $status "FF_NO_AUTO_UPDATE=1: does not pull"
set -e FF_NO_AUTO_UPDATE
cleanup_env
setup_env
add_upstream_marker MARKER_FLAG
capture_ff --no-auto-update help
test ! -f $HOME/.dotfile/MARKER_FLAG
t_assert $status "--no-auto-update: does not pull"
cleanup_env
setup_env
add_upstream_marker MARKER_TRAILING
capture_ff help --no-auto-update
test -f $HOME/.dotfile/MARKER_TRAILING
t_assert $status "trailing --no-auto-update after command is not a global skip"
cleanup_env
setup_env
add_upstream_marker MARKER_FAIL
git -C $HOME/.dotfile remote set-url origin /nonexistent/ff-auto-update.git
capture_ff help
string match -q '*Usage: ff <command>*' <$FF_OUT
t_assert $status "pull failure: current command still runs"
test ! -f $HOME/.dotfile/MARKER_FAIL
t_assert $status "pull failure: does not apply update"
string match -q '*自动更新失败*' <$FF_ERR
t_assert $status "pull failure: stderr skip message"
cleanup_env
setup_env
echo "interval=24h" >$HOME/.config/ff/config
write_state (math (date +%s) - 86401)
add_upstream_marker MARKER_INTERVAL
capture_ff help
test -f $HOME/.dotfile/MARKER_INTERVAL
t_assert $status "interval 24h elapsed: pulls"
cleanup_env
echo ""
echo "$_t_ok passed, $_t_fail failed"
if test $_t_fail -gt 0
exit 1
end
exit 0
+488
View File
@@ -0,0 +1,488 @@
#!/usr/bin/env python3
"""Isolated ff ssh regressions: dummy keys and mock commands, no network access."""
import fcntl
import json
import os
import pty
import selectors
import shutil
from pathlib import Path
import signal
import subprocess
import tempfile
import termios
import time
import unittest
ROOT = Path(__file__).resolve().parents[2]
REAL_SSH = shutil.which("ssh")
SOURCE = Path(os.environ.get("FF_SSH_TEST_SOURCE", ROOT / "fish/functions/ff-ssh.fish"))
MOCK = r'''#!/usr/bin/env python3
import json, os, pathlib, signal, sys, time
home = pathlib.Path(os.environ["HOME"])
name = pathlib.Path(sys.argv[0]).name
with (home / "calls.jsonl").open("a") as f:
f.write(json.dumps([name, sys.argv[1:]]) + "\n")
if name == "ssh":
if "-G" in sys.argv:
print((home / "resolved").read_text(), end="")
sys.exit(int(os.environ.get("RESOLVE_STATUS", "0")))
sys.exit(int(os.environ.get("VERIFY_STATUS", "0")))
if name == "ssh-keygen":
if os.environ.get("KEYGEN_FAIL"):
sys.exit(1)
key = pathlib.Path(sys.argv[sys.argv.index("-f") + 1])
key.write_text("dummy private\n")
key.with_name(key.name + ".pub").write_text("dummy public\n")
sys.exit(0)
mode = os.environ.get("COPY_MODE", "success")
if mode == "cancel-zero":
def cancel(*_):
print("ERROR: Assertion failure: in filter_ids(): scratch_dir was not correctly set up", file=sys.stderr)
print("WARNING: All keys were skipped because they already exist", file=sys.stderr)
sys.exit(0)
signal.signal(signal.SIGINT, cancel)
(home / "ready").touch()
time.sleep(15)
if mode == "false-zero":
print("ERROR: Assertion failure: in filter_ids(): scratch_dir was not correctly set up", file=sys.stderr)
print("WARNING: All keys were skipped because they already exist", file=sys.stderr)
if mode in ("failure", "timeout"):
print("Connection timed out" if mode == "timeout" else "Permission denied", file=sys.stderr)
sys.exit(1)
'''
class SshTests(unittest.TestCase):
def setUp(self):
self.temp = tempfile.TemporaryDirectory(prefix="ff-ssh-test-")
self.addCleanup(self.temp.cleanup)
self.home = Path(self.temp.name)
self.ssh = self.home / ".ssh"
self.ssh.mkdir()
bindir = self.home / "bin"
bindir.mkdir()
for name in ("ssh", "ssh-copy-id", "ssh-keygen"):
path = bindir / name
path.write_text(MOCK)
path.chmod(0o755)
self.env = dict(os.environ, HOME=str(self.home),
XDG_CONFIG_HOME=str(self.home / ".config"),
PATH=f"{bindir}:{os.environ['PATH']}", USER="test")
self.resolve("~/.ssh/id_rsa", "~/.ssh/id_ed25519")
def resolve(self, *identities):
(self.home / "resolved").write_text(
"hostname machine.invalid\nuser testuser\nport 2222\nproxyjump none\nproxycommand none\n" +
"".join(f"identityfile {key}\n" for key in identities))
def key(self, name):
private = self.ssh / name
private.write_text("dummy private\n")
private.with_name(name + ".pub").write_text("dummy public\n")
return private
def launch(self, args):
return subprocess.Popen(
["fish", "--no-config", "-c", 'source "$argv[1]"; ff_ssh $argv[2..-1]',
str(SOURCE), *args], env=self.env, stdin=subprocess.DEVNULL,
stdout=subprocess.PIPE, stderr=subprocess.PIPE, text=True, start_new_session=True)
def run_ff(self, *args):
proc = self.launch(args)
try:
out, err = proc.communicate(timeout=10)
except subprocess.TimeoutExpired:
os.killpg(proc.pid, signal.SIGKILL)
proc.communicate()
raise
return proc.returncode, out + err
def calls(self, name):
path = self.home / "calls.jsonl"
return [args for cmd, args in map(json.loads, path.read_text().splitlines()) if cmd == name] if path.exists() else []
def assert_selected(self, private):
copy = self.calls("ssh-copy-id")
self.assertEqual(len(copy), 1)
chosen = copy[0][copy[0].index("-i") + 1]
self.assertEqual(str(private), chosen.removesuffix(".pub"))
config = (self.ssh / "config.d/machine.invalid.conf").read_text()
identities = [line.strip().split(maxsplit=1)[1].strip('"') for line in config.splitlines() if line.strip().startswith("IdentityFile ")]
self.assertEqual(identities, [str(private).replace(str(self.home), "~", 1)])
def assert_no_host(self):
self.assertFalse((self.ssh / "config.d/machine.invalid.conf").exists())
def test_default_matches_resolved_order_with_multiple_keys(self):
selected = self.key("id_rsa")
self.key("id_ed25519")
status, output = self.run_ff("testuser@machine.invalid")
self.assertEqual(status, 0, output)
self.assert_selected(selected)
def test_nonstandard_default_key(self):
selected = self.key("id_hx_new")
status, output = self.run_ff("testuser@machine.invalid")
self.assertEqual(status, 0, output)
self.assert_selected(selected)
def test_explicit_private_and_public_paths(self):
for suffix in ("", ".pub"):
with self.subTest(suffix=suffix):
self.setUp()
self.key("id_rsa")
selected = self.key("custom")
self.resolve(str(selected) + suffix, "~/.ssh/id_rsa")
status, output = self.run_ff("-i", str(selected) + suffix, "testuser@machine.invalid")
self.assertEqual(status, 0, output)
self.assert_selected(selected)
def test_identity_option_case_and_forms(self):
for option in ("IdentityFile=", "identityfile "):
with self.subTest(option=option):
self.setUp()
self.key("id_rsa")
selected = self.key("custom")
self.resolve(str(selected), "~/.ssh/id_rsa")
status, output = self.run_ff("-o", option + str(selected), "testuser@machine.invalid")
self.assertEqual(status, 0, output)
self.assert_selected(selected)
def test_existing_config_identity(self):
selected = self.key("custom")
self.key("id_rsa")
self.resolve(str(selected))
status, output = self.run_ff("testuser@machine.invalid")
self.assertEqual(status, 0, output)
self.assert_selected(selected)
def test_invalid_explicit_identity_never_falls_back(self):
self.key("id_rsa")
# OpenSSH omits an unreadable -i from ssh -G, leaving defaults.
self.resolve("~/.ssh/id_rsa")
status, output = self.run_ff("-i", "/missing/key", "testuser@machine.invalid")
self.assertNotEqual(status, 0, output)
self.assert_no_host()
self.assertFalse(self.calls("ssh-copy-id"))
def test_unavailable_config_identity_never_falls_back(self):
self.key("id_rsa")
self.resolve("~/.ssh/custom_missing")
status, output = self.run_ff("machine.invalid")
self.assertNotEqual(status, 0, output)
self.assert_no_host()
self.assertFalse(self.calls("ssh-copy-id"))
def test_unpaired_default_key_is_not_overwritten(self):
path = self.ssh / "id_ed25519"
path.write_text("existing private")
status, output = self.run_ff("machine.invalid")
self.assertNotEqual(status, 0, output)
self.assertEqual(path.read_text(), "existing private")
self.assert_no_host()
def test_generation_then_copy_same_key(self):
status, output = self.run_ff("machine.invalid")
self.assertEqual(status, 0, output)
self.assert_selected(self.ssh / "id_ed25519")
def test_generation_failure_aborts(self):
self.env["KEYGEN_FAIL"] = "1"
status, output = self.run_ff("machine.invalid")
self.assertNotEqual(status, 0, output)
self.assert_no_host()
self.assertFalse(self.calls("ssh-copy-id"))
def test_copy_failure_and_timeout_abort(self):
for mode in ("failure", "timeout"):
with self.subTest(mode=mode):
self.setUp()
self.key("id_rsa")
self.env["COPY_MODE"] = mode
status, output = self.run_ff("machine.invalid")
self.assertNotEqual(status, 0, output)
self.assert_no_host()
def test_false_success_requires_selected_key_verification(self):
self.key("id_rsa")
self.env.update(COPY_MODE="false-zero", VERIFY_STATUS="255")
status, output = self.run_ff("machine.invalid")
self.assertNotEqual(status, 0, output)
self.assert_no_host()
def test_ctrl_c_with_upstream_exit_zero_aborts(self):
self.key("id_rsa")
self.env["COPY_MODE"] = "cancel-zero"
proc = self.launch(["machine.invalid"])
try:
deadline = time.monotonic() + 5
while not (self.home / "ready").exists() and proc.poll() is None and time.monotonic() < deadline:
time.sleep(.02)
self.assertTrue((self.home / "ready").exists(), "mock copy must reach cancellable stage")
os.killpg(proc.pid, signal.SIGINT)
out, err = proc.communicate(timeout=5)
self.assertNotEqual(proc.returncode, 0, out + err)
self.assert_no_host()
finally:
if proc.poll() is None:
os.killpg(proc.pid, signal.SIGKILL)
proc.communicate()
def test_no_copy_never_generates_or_connects(self):
status, output = self.run_ff("--no-copy", "machine.invalid")
self.assertEqual(status, 0, output)
self.assertTrue((self.ssh / "config.d/machine.invalid.conf").exists())
self.assertFalse(self.calls("ssh-copy-id"))
self.assertFalse(self.calls("ssh-keygen"))
self.assertTrue(all("-G" in call for call in self.calls("ssh")))
def test_no_copy_public_identity_records_private(self):
private = self.key("custom")
self.resolve(str(private) + ".pub")
status, output = self.run_ff("--no-copy", "-i", str(private) + ".pub", "machine.invalid")
self.assertEqual(status, 0, output)
config = (self.ssh / "config.d/machine.invalid.conf").read_text()
self.assertIn("~/.ssh/custom", config)
self.assertNotIn("custom.pub", config)
def existing(self, location="config.d/machine.invalid.conf", shared=False):
path = self.ssh / location
path.parent.mkdir(parents=True, exist_ok=True)
content = ("# original settings\nHost " + ("machine.invalid neighbor" if shared else "machine.invalid") +
"\n HostName old.invalid\n User olduser\n Port 22\n"
" IdentityFile ~/.ssh/old\n ForwardAgent no\n"
"Host untouched\n HostName untouched.invalid\n")
path.write_text(content)
if location != "config":
(self.ssh / "config").write_text(f"Include {self.ssh}/config.d/*\n")
return path, content
def snapshot(self):
return {str(p.relative_to(self.ssh)): p.read_bytes()
for p in self.ssh.rglob("*") if p.is_file()}
def interactive(self, *args, answer="y", cancel_at=None):
master, slave = pty.openpty()
proc = subprocess.Popen(
["fish", "--no-config", "-c", 'source "$argv[1]"; ff_ssh $argv[2..-1]',
str(SOURCE), *args], env=self.env, stdin=slave,
stdout=slave, stderr=slave, start_new_session=True,
preexec_fn=lambda: fcntl.ioctl(slave, termios.TIOCSCTTY, 0))
os.close(slave)
output = b""
selector = selectors.DefaultSelector()
selector.register(master, selectors.EVENT_READ)
def until(prompt):
nonlocal output
deadline = time.monotonic() + 5
while prompt.encode() not in output and time.monotonic() < deadline:
for key, _ in selector.select(.1):
chunk = os.read(key.fileobj, 65536)
if not chunk:
self.fail("process ended before prompt: " + output.decode(errors="replace"))
output += chunk
self.assertIn(prompt, output.decode(errors="replace"))
try:
until("SSH 别名")
time.sleep(.1) # let fish finish entering terminal input mode
if cancel_at == "alias":
os.write(master, b"\x04")
else:
os.write(master, b"\r")
until("[y/N]")
time.sleep(.1)
if cancel_at == "interrupt":
os.killpg(proc.pid, signal.SIGINT)
elif cancel_at == "eof":
os.write(master, b"\x04")
else:
os.write(master, (answer + "\r").encode())
deadline = time.monotonic() + 10
while time.monotonic() < deadline:
ready = selector.select(.1)
if ready:
try:
chunk = os.read(master, 65536)
except OSError:
break
if not chunk:
break
output += chunk
elif proc.poll() is not None:
break
proc.wait(timeout=1)
return proc.returncode, output.decode(errors="replace")
finally:
selector.close()
os.close(master)
if proc.poll() is None:
os.killpg(proc.pid, signal.SIGKILL)
proc.communicate()
def assert_unchanged(self, before):
self.assertEqual(self.snapshot(), before)
self.assertFalse(self.calls("ssh-copy-id"))
self.assertFalse(self.calls("ssh-keygen"))
def assert_backup(self, content):
backups = list((self.ssh / "config.backups").glob("*.backup.*"))
self.assertEqual(len(backups), 1)
self.assertEqual(backups[0].read_text(), content)
self.assertEqual((self.ssh / "config.backups").stat().st_mode & 0o777, 0o700)
self.assertFalse(list((self.ssh / "config.d").glob("*.backup.*")))
def effective(self, host):
# -G is local only; absolute Include plus -F avoid the real SSH config.
result = subprocess.run([REAL_SSH, "-G", "-F", str(self.ssh / "config"), host],
env=self.env, capture_output=True, text=True, timeout=5)
self.assertEqual(result.returncode, 0, result.stderr)
return dict(line.split(" ", 1) for line in result.stdout.splitlines())
def test_existing_alias_is_actionable_failure_and_preserved(self):
self.existing(location="config")
before = self.snapshot()
status, output = self.run_ff("machine.invalid")
self.assertNotEqual(status, 0, output)
self.assertIn("非交互", output)
self.assert_unchanged(before)
self.assertFalse(self.calls("ssh"))
def test_overwrite_accepts_only_explicit_yes(self):
for answer in ("n", "", "sure", "yes please"):
with self.subTest(answer=answer):
self.setUp()
self.existing(location="config")
before = self.snapshot()
status, output = self.interactive("machine.invalid", answer=answer)
self.assertNotEqual(status, 0, output)
self.assertIn("未确认覆盖", output)
self.assert_unchanged(before)
self.assertFalse(self.calls("ssh"))
def test_overwrite_eof_and_ctrl_c_preserve_everything(self):
for cancel in ("eof", "interrupt", "alias"):
with self.subTest(cancel=cancel):
self.setUp()
self.existing(location="config")
before = self.snapshot()
status, output = self.interactive("machine.invalid", cancel_at=cancel)
self.assertNotEqual(status, 0, output)
self.assertIn("取消", output)
self.assert_unchanged(before)
def test_overwrite_yes_copies_then_backs_up_and_updates(self):
for answer in ("y", "YES"):
with self.subTest(answer=answer):
self.setUp()
selected = self.key("custom")
self.resolve(str(selected))
path, original = self.existing()
status, output = self.interactive("newuser@machine.invalid", "-p", "2200", answer=answer)
self.assertEqual(status, 0, output)
self.assertIn(str(path), output)
self.assert_selected(selected)
self.assert_backup(original)
updated = path.read_text()
for value in ("HostName machine.invalid", "User newuser", "Port 2200", "ForwardAgent no",
"Host untouched\n HostName untouched.invalid"):
self.assertIn(value, updated)
self.assertNotIn("old.invalid", updated)
self.assertNotIn("~/.ssh/old", updated)
def test_overwrite_no_copy_in_shared_nonmatching_filename(self):
path, original = self.existing("config.d/20-shared.conf", shared=True)
status, output = self.interactive("--no-copy", "machine.invalid")
self.assertEqual(status, 0, output)
self.assert_backup(original)
self.assertFalse((self.ssh / "config.d/machine.invalid.conf").exists())
self.assertFalse(self.calls("ssh-copy-id"))
self.assertFalse(self.calls("ssh-keygen"))
self.assertTrue(all("-G" in call for call in self.calls("ssh")))
self.assertEqual(self.effective("machine.invalid")["hostname"], "machine.invalid")
self.assertEqual(self.effective("neighbor")["hostname"], "old.invalid")
self.assertEqual(self.effective("neighbor")["identityfile"], "~/.ssh/old")
self.assertEqual(self.effective("untouched")["hostname"], "untouched.invalid")
self.assertIn("Host neighbor !machine.invalid", path.read_text())
def test_overwrite_shared_wildcard_excludes_target(self):
path, original = self.existing("config.d/20-shared.conf", shared=True)
original = original.replace("machine.invalid neighbor", "machine.invalid neighbor *")
path.write_text(original)
status, output = self.interactive("--no-copy", "machine.invalid")
self.assertEqual(status, 0, output)
self.assert_backup(original)
self.assertEqual(self.effective("machine.invalid")["hostname"], "machine.invalid")
self.assertNotIn("~/.ssh/old", self.effective("machine.invalid")["identityfile"])
self.assertEqual(self.effective("neighbor")["hostname"], "old.invalid")
def test_overwrite_main_config_preserves_neighbors(self):
path, original = self.existing("config")
status, output = self.interactive("--no-copy", "machine.invalid")
self.assertEqual(status, 0, output)
self.assert_backup(original)
self.assertIn("HostName machine.invalid", path.read_text())
self.assertIn("Host untouched\n HostName untouched.invalid", path.read_text())
self.assertFalse((self.ssh / "config.d/machine.invalid.conf").exists())
def test_overwrite_copy_or_verification_failure_preserves_original(self):
for mode in ("failure", "false-zero"):
with self.subTest(mode=mode):
self.setUp()
self.key("id_rsa")
self.existing(location="config")
before = self.snapshot()
self.env.update(COPY_MODE=mode, VERIFY_STATUS="255")
status, output = self.interactive("machine.invalid")
self.assertNotEqual(status, 0, output)
self.assertEqual(self.snapshot(), before)
self.assertEqual(len(self.calls("ssh-copy-id")), 1)
def test_ambiguous_declarations_are_not_partially_updated(self):
self.existing()
(self.ssh / "config.d/20-second.conf").write_text("Host machine.invalid\n HostName second.invalid\n")
before = self.snapshot()
status, output = self.run_ff("machine.invalid")
self.assertNotEqual(status, 0, output)
self.assertIn("多个 Host", output)
self.assert_unchanged(before)
def test_filename_collision_does_not_replace_other_alias(self):
path = self.ssh / "config.d/machine.invalid.conf"
path.parent.mkdir()
path.write_text("Host other\n HostName other.invalid\n")
before = self.snapshot()
status, output = self.run_ff("machine.invalid")
self.assertNotEqual(status, 0, output)
self.assertIn("目标文件已存在", output)
self.assert_unchanged(before)
def test_connect_timeout_and_verification_identity_isolation(self):
selected = self.key("id_rsa")
status, output = self.run_ff("machine.invalid")
self.assertEqual(status, 0, output)
copy = self.calls("ssh-copy-id")[0]
self.assertIn("ConnectTimeout=10", copy)
self.assertIn("ConnectionAttempts=1", copy)
verify = [call for call in self.calls("ssh") if "-G" not in call]
self.assertEqual(len(verify), 1)
for option in ("ConnectTimeout=10", "ConnectionAttempts=1", "BatchMode=yes", "IdentitiesOnly=yes", "CertificateFile=none", "ControlPath=none", "PreferredAuthentications=publickey"):
self.assertIn(option, verify[0])
self.assertEqual(verify[0][verify[0].index("-F") + 1], "/dev/null")
self.assertEqual(verify[0][verify[0].index("-i") + 1], str(selected))
self.assertNotIn("StrictHostKeyChecking=no", verify[0])
def test_resolve_failure_aborts(self):
self.key("id_rsa")
self.env["RESOLVE_STATUS"] = "255"
status, output = self.run_ff("machine.invalid")
self.assertNotEqual(status, 0, output)
self.assert_no_host()
self.assertFalse(self.calls("ssh-copy-id"))
if __name__ == "__main__":
unittest.main(verbosity=2)
+51
View File
@@ -0,0 +1,51 @@
# 主题推荐:Catppuccin, Tokyo Night, 或 Gruvbox
theme = Atom One Dark
# 字体设置 (建议安装带 Nerd Font 图标的字体)
font-family = "FiraCode Nerd Font"
font-size = 16
font-feature = -calt # 如果你不喜欢连字(Ligatures)可以禁用
# 窗口样式:隐藏标题栏,让视觉更纯粹
#window-decoration = true
#macos-titlebar-style = transparent
macos-titlebar-style = tabs
window-title-font-family = "FiraCode Nerd Font"
#fullscreen=non-native
# 背景透明度与模糊 (适合 2017 MBP 的精致感)
background-opacity = 0.95
background-blur-radius = 20
# 将 Option 键映射为 Alt/Meta 键 (对 Neovim/Tmux 极其重要)
macos-option-as-alt = true
# 自动定位到当前 Shell 所在的目录
working-directory = home
# 选中文本时自动复制到剪贴板
copy-on-select = true
# 确认退出(防止误触 Command+Q 关掉正在运行的任务)
confirm-close-surface = true
# 光标样式:块状且闪烁
cursor-style = block
cursor-style-blink = true
# 滚动条设置:通常建议隐藏,通过 Tmux 控制滚动
mouse-scroll-multiplier = 1
scrollback-limit = 10000
# Ghostty 配置
desktop-notifications = true
term = xterm-256color
scrollbar = system
mouse-shift-capture = true
shell-integration-features = ssh-env
# 开启所有 shell 集成特性,包含 ssh-terminfo 自动注入
shell-integration = fish
+139 -38
View File
@@ -1,47 +1,148 @@
# 不设全局 user.name / user.email,避免未匹配目录误用身份。
# useConfigOnly(Git 2.8+):未配置时拒绝 commit,而不是猜系统用户名。
[user]
name = Laily
email = i@laily.net
useConfigOnly = true
[core]
autocrlf = input
quotepath = false
editor = /usr/local/bin/nvim
excludesfile = ~/.dotfile/git/.gitignore
autocrlf = input
quotepath = false
editor = vim
excludesfile = ~/.dotfile/git/.gitignore
[init]
defaultBranch = main
[tag]
sort = version:refname
[column]
ui = auto
[color]
status = auto
diff = auto
branch = auto
interactive = auto
grep = auto
ui = auto
[push]
default = current
#default = current
default = simple
autoSetupRemote = true
followTags = true
[fetch]
prune = true
pruneTags = true
all = true
[pull]
rebase = false
[diff]
algorithm = histogram
colorMoved = plain
mnemonicPrefix = true
renames = true
[difftool "nvimdiff"]
cmd = nvim -d \"$LOCAL\" \"$REMOTE\"
[merge]
tool = vimdiff
[include]
path = ~/.subgitconfig
tool = nvimdiff
[mergetool "nvimdiff"]
cmd = nvim -d \"$LOCAL\" \"$REMOTE\" -ancestor \"$BASE\" -merge \"$MERGED\"
trustExitCode = true
# 按仓库目录套身份。个人身份在 git/.gitconfig.personal;公司邮箱只放本机 ~/.gitconfig.work。
# include 目标文件必须存在,否则该目录下 git 命令会直接 fatal。
[includeIf "gitdir:~/.dotfile/"]
path = ~/.dotfile/git/.gitconfig.personal
[includeIf "gitdir:~/code/"]
path = ~/.dotfile/git/.gitconfig.personal
[includeIf "gitdir:~/.pouch/"]
path = ~/.dotfile/git/.gitconfig.personal
[includeIf "gitdir:~/workrepo/"]
path = ~/.gitconfig.work
[includeIf "gitdir:~/repo/"]
path = ~/.gitconfig.work
[rebase]
autoSquash = true
autoStash = true
updateRefs = true
[commit]
verbose = true
[rerere]
enabled = true
autoupdate = true
[help]
# 旧版 Git 仅支持数字(0=仅提示不执行)。Git 2.36+ 见 .gitconfig.darwin / .gitconfig.linux 可改为 prompt
autocorrect = 0
[branch]
autoSetupMerge = always
autoSetupMerge = always
sort = -committerdate
[alias]
au = add -u
co = checkout
cm = commit -m
st = status
ap = add -p
br = branch
pl = pull
pt = push origin --tags
merge2 = !sh -c '_CURRENT_BRANCH=$(git symbolic-ref --short HEAD) && git checkout $1 && git merge $_CURRENT_BRANCH && git checkout $_CURRENT_BRANCH' -
bg = "!f() { git branch | grep $1; }; f"
delbr = "!f(){ git branch -D $(git branch | grep \"\b$1\"); };f"
can = commit --amend --no-edit
cleanbr = "!git branch --merged master | grep -v '\\*\\|master\\|main' | xargs -n 1 git branch -d"
# list files which have changed since REVIEW_BASE
# (REVIEW_BASE defaults to 'master' in my zshrc)
files = !git diff --name-only $(git merge-base HEAD \"$REVIEW_BASE\")
au = add -u
co = checkout
cm = "!f() { if [[ -z \"$1\" ]]; then git commit; else git commit -m \"$*\"; fi; }; f"
st = status
ap = add -p
br = branch
ba = branch -a
bm = branch --merged
bn = branch --no-merged
pu = "!git pull origin $(git rev-parse --abbrev-ref HEAD)"
pt = push origin --tags
ph = push origin HEAD
pht = push origin --tags
# 把当前分支合进目标分支后回到当前分支;目标已被其它 worktree 占用时在那边 merge
merge2 = !$HOME/.dotfile/git/git-merge2.sh
bg = "!f() { git branch | grep $1; }; f"
delbr = "!f(){ git branch -D $(git branch | grep \"\b$1\"); };f"
can = commit --amend --no-edit
cleanbr = "!git branch --merged master | grep -v '\\*\\|master\\|main' | xargs -n 1 git branch -d"
# list files which have changed since REVIEW_BASE
# (REVIEW_BASE defaults to 'master' in my zshrc)
files = !git diff --name-only $(git merge-base HEAD \"$REVIEW_BASE\")
rmsubmodule = "!f(){ git submodule deinit -f \"$1\"; git rm -f \"$1\"; git commit -m \"Removed submodule $1\"; };f"
# Same as above, but with a diff stat instead of just names
# (better for interactive use)
stat = !git diff --stat $(git merge-base HEAD \"$REVIEW_BASE\")
# Open all files changed since REVIEW_BASE in Vim tabs
# Then, run fugitive's :Gdiff in each tab, and finally
# tell vim-gitgutter to show +/- for changes since REVIEW_BASE
review = !vim -p $(git files) +\"tabdo Gdiffsplit $REVIEW_BASE\" +\"let g:gitgutter_diff_base = '$REVIEW_BASE'\"
# Same as above, but with a diff stat instead of just names
# (better for interactive use)
stat = !git diff --stat $(git merge-base HEAD \"$REVIEW_BASE\")
# Open all files changed since REVIEW_BASE in Vim tabs
# Then, run fugitive's :Gdiff in each tab, and finally
# tell vim-gitgutter to show +/- for changes since REVIEW_BASE
review = !vim -p $(git files) +\"tabdo Gdiffsplit $REVIEW_BASE\" +\"let g:gitgutter_diff_base = '$REVIEW_BASE'\"
# Same as the above, except specify names of files as arguments,
# instead of opening all files:
# git reviewone foo.js bar.js
reviewone = !vim -p +\"tabdo Gdiff $REVIEW_BASE\" +\"let g:gitgutter_diff_base = '$REVIEW_BASE'\"
# Same as the above, except specify names of files as arguments,
# instead of opening all files:
# git reviewone foo.js bar.js
reviewone = !vim -p +\"tabdo Gdiff $REVIEW_BASE\" +\"let g:gitgutter_diff_base = '$REVIEW_BASE'\"
#git filter-branch -f --env-filter "GIT_AUTHOR_NAME='Laily'; GIT_AUTHOR_EMAIL='i@laily.net'; GIT_COMMITTER_NAME='Laily'; GIT_COMMITTER_EMAIL='i@laily.net';" HEAD
# worktree
# git wt add <路径> <分支名>
# git wt add -b <新分支名> <路径> [基分支名]
wt="worktree"
#wta="worktree add"
#wtb="worktree add -b"
wtr="worktree remove"
wtl="worktree list"
wtp="worktree prune"
# 基于分支创建新分支+worktree
# git wtb <分支名> [<基分支名|当前分支>]
wtb = "!f(){ local base_branch=${2:-HEAD}; local clean_name=$(echo \"$1\" | sed \"s/\\//_/g\"); git worktree add -b \"$1\" \"../$clean_name\" $base_branch; }; f"
# 基于指定分支+worktree
# git wta <分支名>
wta = "!f(){ local base_branch=${1:-HEAD}; local clean_name=$(echo \"$1\" | sed \"s/\\//_/g\"); git worktree add \"../$clean_name\" $base_branch; }; f"
[protocol "file"]
allow = always
# https://hyperzsb.io/posts/git-ssl-error/
[filter "lfs"]
process = git-lfs filter-process
required = true
clean = git-lfs clean -- %f
smudge = git-lfs smudge -- %f
[http]
sslVerify = false
# 凭据:统一 wrapper,不依赖 includeIf ondarwin/onlinux(需 Git 2.36+)
# 低版本 Git 也会加载本节;回退为 git-credential-store(~/.git-credentials)
[credential]
helper = !$HOME/.dotfile/git/git-credential.sh
# 以下 includeIf 需 Git 2.36+;仅用于其它平台差异配置(若有)
[includeIf "ondarwin"]
path = ~/.dotfile/git/.gitconfig.darwin
[includeIf "onlinux"]
path = ~/.dotfile/git/.gitconfig.linux
+3
View File
@@ -0,0 +1,3 @@
# macOS 专用(需 Git 2.36+ includeIf ondarwin)
[help]
autocorrect = prompt
+3
View File
@@ -0,0 +1,3 @@
# Linux 专用(需 Git 2.36+ includeIf onlinux)
[help]
autocorrect = prompt
+4
View File
@@ -0,0 +1,4 @@
# 个人身份:~/.dotfile、~/code、~/.pouch(由 includeIf gitdir 加载)
[user]
name = laily
email = i@laily.net
+7
View File
@@ -26,7 +26,14 @@ _testmain.go
*.sublime-workspace
.idea/
.vscode/
.SpaceVim.d/.netrwhist
.SpaceVim.d/init.vim
.DS_Store
.log
go.work
go.work.sum
_set_env*.sh
.env*
!.env.example
Makefile.local
.netrc
+3
View File
@@ -0,0 +1,3 @@
#!/bin/bash
# 已合并到 git-credential.sh;保留此文件仅为兼容旧配置引用
exec "$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)/git-credential.sh" "$@"
+60
View File
@@ -0,0 +1,60 @@
#!/bin/bash
# 跨平台 Git 凭据 helper(不依赖 includeIf ondarwin/onlinux,兼容 Git < 2.36)
# macOS: osxkeychain → store
# Linux: libsecret → store
set -euo pipefail
warn_store_fallback() {
local reason="$1"
local marker="${XDG_CACHE_HOME:-$HOME/.cache}/git-credential-store.warned"
[[ -f "$marker" ]] && return 0
mkdir -p "$(dirname "$marker")"
touch "$marker"
cat >&2 <<EOF
提示: Git 凭据使用 store 明文保存到 ~/.git-credentials(${reason})。
建议:
macOS: 通常可用 osxkeychain(Keychain)
Linux: sudo apt install libsecret-1-0 git-credential-libsecret
请确保 ~/.git-credentials 权限为 600。
EOF
}
# helper 在 `git --exec-path`,不一定在 PATH。必须 exec 绝对路径:
# Apple Git 的 osxkeychain 会对 argv[0] 做 O_EXLOCK,相对名会去锁 cwd 下的同名文件。
dotfile_git_credential_exec() {
local name="$1"
shift
local helper=""
local exec_path
exec_path="$(git --exec-path 2>/dev/null || true)"
if [ -n "$exec_path" ] && [ -x "$exec_path/git-credential-$name" ]; then
helper="$exec_path/git-credential-$name"
elif command -v "git-credential-$name" >/dev/null 2>&1; then
helper="$(command -v "git-credential-$name")"
else
return 1
fi
exec "$helper" "$@"
}
case "$(uname -s)" in
Darwin)
dotfile_git_credential_exec osxkeychain "$@" || true
warn_store_fallback "未找到 osxkeychain"
dotfile_git_credential_exec store "$@" || true
;;
Linux)
dotfile_git_credential_exec libsecret "$@" || true
warn_store_fallback "未找到 git-credential-libsecret"
dotfile_git_credential_exec store "$@" || true
;;
*)
warn_store_fallback "未知系统 $(uname -s)"
dotfile_git_credential_exec store "$@" || true
;;
esac
echo "git credential helper 不可用" >&2
exit 1
+72
View File
@@ -0,0 +1,72 @@
#!/bin/bash
# 把当前分支 merge 进目标分支;目标已在其它 worktree 时直接在那边 merge
set -euo pipefail
usage() {
cat <<'EOF'
用法: git merge2 <branch> [git merge 参数...]
把当前分支合进 <branch>,然后留在当前分支。
若 <branch> 已在其它 worktree 里 checkout,则在那个 worktree 里 merge,
不会在当前目录 checkout(避免 "already used by worktree")。
EOF
}
if [[ "${1:-}" == "-h" || "${1:-}" == "--help" ]]; then
usage
exit 0
fi
if [[ $# -lt 1 ]]; then
usage >&2
exit 1
fi
current="$(git symbolic-ref --short HEAD)"
target="$1"
shift
if [[ "$current" == "$target" ]]; then
echo "已经在 $target 上" >&2
exit 1
fi
# porcelain 含主 worktree;%(worktreepath) 只填 linked worktree,不能用
worktree_for_branch() {
local branch="$1"
local wt=""
local line
while IFS= read -r line || [[ -n "$line" ]]; do
case "$line" in
worktree\ *)
wt="${line#worktree }"
;;
"branch refs/heads/${branch}")
printf '%s\n' "$wt"
return 0
;;
"")
wt=""
;;
esac
done < <(git worktree list --porcelain)
return 1
}
# alias / 子进程会继承当前 worktree 的 GIT_DIR,必须清掉才能 git -C 到另一个 worktree
merge_in_worktree() {
local wt="$1"
shift
env -u GIT_DIR -u GIT_WORK_TREE git -C "$wt" merge "$@"
}
wt="$(worktree_for_branch "$target" || true)"
if [[ -n "$wt" ]]; then
echo "在 worktree 中 merge $current -> $target: $wt"
merge_in_worktree "$wt" "$current" "$@"
else
git checkout "$target"
git merge "$current" "$@"
git checkout "$current"
fi
+1
View File
@@ -0,0 +1 @@
git remote set-url origin git@github.com:hilaily/.dotfile.git
+255
View File
@@ -0,0 +1,255 @@
version: "2"
run:
allow-parallel-runners: true
timeout: 5m # 超时时间
tests: false
linters:
default: none
enable:
- bodyclose
- errcheck
- errorlint
- govet
- ineffassign
- misspell
- revive
- staticcheck
settings:
errcheck:
exclude-functions:
- io/ioutil.ReadFile
- io.Copy(*bytes.Buffer)
- io.Copy(os.Stdout)
- net.(*)
misspell:
locale: US
revive:
max-open-files: 2048
confidence: 0.1
severity: error
enable-all-rules: false
rules:
- name: argument-limit
arguments:
- 15
severity: warning
disabled: true
- name: atomic
severity: warning
disabled: false
- name: banned-characters
arguments:
- Ω
- Σ
- σ
severity: warning
disabled: false
- name: bool-literal-in-expr
severity: warning
disabled: false
- name: call-to-gc
severity: warning
disabled: false
- name: cognitive-complexity
arguments:
- 25
severity: warning
disabled: false
- name: constant-logical-expr
severity: warning
disabled: false
- name: context-as-argument
severity: warning
disabled: false
- name: context-keys-type
severity: warning
disabled: false
- name: cyclomatic
arguments:
- 25
severity: warning
disabled: false
- name: datarace
severity: warning
disabled: false
- name: deep-exit
severity: warning
disabled: false
- name: defer
arguments:
- - call-chain
- loop
severity: warning
disabled: false
- name: duplicated-imports
severity: warning
disabled: false
- name: early-return
severity: warning
disabled: false
- name: error-naming
severity: warning
disabled: false
- name: error-return
severity: warning
disabled: false
- name: error-strings
severity: warning
disabled: false
- name: errorf
severity: warning
disabled: false
- name: exported
arguments:
- checkPrivateReceivers
- sayRepetitiveInsteadOfStutters
severity: warning
disabled: false
- name: function-result-limit
arguments:
- 6
severity: warning
disabled: false
- name: function-length
arguments:
- 100
- 0
severity: warning
disabled: false
- name: get-return
severity: warning
disabled: false
- name: if-return
severity: warning
disabled: false
- name: increment-decrement
severity: warning
disabled: false
- name: indent-error-flow
severity: warning
disabled: false
- name: modifies-value-receiver
severity: warning
disabled: false
- name: nested-structs
severity: warning
disabled: true
- name: optimize-operands-order
severity: warning
disabled: false
- name: package-comments
severity: warning
disabled: false
- name: range
severity: warning
disabled: false
- name: range-val-in-closure
severity: warning
disabled: false
- name: range-val-address
severity: warning
disabled: false
- name: receiver-naming
severity: warning
disabled: false
- name: redefines-builtin-id
severity: warning
disabled: false
- name: string-of-int
severity: warning
disabled: false
- name: string-format
arguments:
- - core.WriteError[1].Message
- /^([^A-Z]|$)/
- must not start with a capital letter
- - fmt.Errorf[0]
- /(^|[^\.!?])$/
- must not end in punctuation
- - panic
- /^[^\n]*$/
- must not contain line breaks
severity: warning
disabled: false
- name: struct-tag
severity: warning
disabled: false
- name: superfluous-else
severity: warning
disabled: false
- name: time-equal
severity: warning
disabled: false
- name: time-naming
severity: warning
disabled: false
# https://github.com/mgechev/revive/blob/HEAD/RULES_DESCRIPTIONS.md#var-naming
- name: var-naming
arguments:
- ["ID", "Id", "id"]
- ["VM"]
- [{ skipInitialismNameChecks = true }]
severity: warning
disabled: true
- name: var-declaration
severity: warning
disabled: false
- name: unconditional-recursion
severity: warning
disabled: false
- name: unexported-naming
severity: warning
disabled: false
- name: unexported-return
disabled: true
- name: unhandled-error
arguments:
- fmt.Printf
- os.(Create|WriteFile|Chmod)
- net.*
- bytes.Buffer.WriteString
- con.WriteString
severity: warning
disabled: true
- name: unnecessary-stmt
severity: warning
disabled: false
- name: unreachable-code
severity: warning
disabled: false
- name: useless-break
severity: warning
disabled: false
- name: waitgroup-by-value
severity: warning
disabled: false
staticcheck:
checks:
- all
- -ST1000
- -ST1001
- -ST1003
- -ST1016
- -ST1020
- -ST1021
- -ST1022
exclusions:
generated: lax
presets:
- comments
- common-false-positives
- legacy
- std-error-handling
paths:
- third_party$
- builtin$
- examples$
formatters:
enable:
- gofmt
exclusions:
generated: lax
paths:
- third_party$
- builtin$
- examples$
+57
View File
@@ -0,0 +1,57 @@
# herdr
个人 herdr 配置与迁移笔记。配置由 `tmux/.tmux.conf` 映射而来。
- `config.toml` — 真实配置,经 `script/herdr-init.sh` 软链到 `~/.config/herdr/config.toml`
- 校验:`herdr config check`
- 热重载:`prefix+shift+r`(本配置改为 `prefix+r`)或 `herdr server reload-config`
- 默认值参考:`herdr --default-config`
- 官方速查:https://herdr.dev/docs/concepts/ ,给 agent 看的:`herdr --skill`
## 概念对照
| tmux | herdr | 说明 |
|---|---|---|
| session | workspace | herdr 的 workspace 对应“一个项目/任务”,侧栏按 workspace 汇总 agent 状态 |
| (无对应) | session | herdr 的 named session 是**独立的 server 命名空间**(`herdr session attach work`),跟 tmux 的 session 不是一回事 |
| window | tab | |
| pane | pane | 只能向右(right)或向下(down)切 |
| prefix mode | prefix mode (`ctrl+b`) | 相同;另有 terminal mode / navigate mode |
| status bar + 脚本 | sidebar | 鼠标可点、可拖分隔条、右键菜单 |
| (无对应) | agent | herdr 识别 pane 里的 coding agent,状态 `working`/`blocked`/`done`/`idle`/`unknown` |
| tmux-resurrect | session.json + `[experimental] pane_history` | 布局自动持久化,agent 原生会话恢复需要装 integration |
| tpm 插件 | 无插件系统 | fzf 类能力内建(`prefix+w` workspace picker、`prefix+g` goto);自定义动作用 `[[keys.command]]` |
| tmux over ssh | `herdr --remote <ssh-target>` | 远端跑 server,本地 attach |
## 和 tmux 的主要差异
1. **鼠标优先**。所有交互都能用鼠标完成;键位是可选层。`prefix+?` 可实时列出当前所有有效绑定。
2. **agent 感知**。侧栏能看到每个 pane 里的 agent 状态,`blocked`(等你审批/回答)会被顶到前面。tmux 只能靠 pane 标题自己写。
3. **CLI + 本地 socket API**。`herdr pane split/run/read/wait-output`、`herdr agent prompt/wait` 这类命令可以从脚本或 agent 内部驱动布局和读输出,tmux 的 `send-keys` 做不到等待与状态判定。
4. **配置是单个 TOML**,没有插件系统,也没有 `run-shell` 状态栏脚本;但支持
`[[keys.command]]` 自定义命令(`type = "shell"` / `"pane"` / `"popup"`,可指定宽高百分比),基本能覆盖原来用插件/脚本做的事。
5. **直接快捷键**。除 `prefix+...` 外还支持 `ctrl+alt+...` 这类直通键位(适合想摆脱 prefix 的用法)。反过来,被 OS 或外层终端占用的组合 herdr 收不到,表现为“绑了没反应”。
6. **没有 `escape-time` / `terminal-overrides` / 真彩色开关**。那些是 tmux 作为终端适配层才需要的,herdr 自己处理能力协商。通知走 `[ui.toast] delivery`,剪贴板走 `[ui] copy_on_select`。
7. **不支持嵌套启动**(`experimental.allow_nested = false`):herdr pane 里再跑 `herdr` 会被拒绝。tmux 里 `tmux` 是能套的。
8. `[remote] manage_ssh_config` 默认开启:`herdr --remote` 会生成一份私有 ssh config,**先 include 你的 `~/.ssh/config`**,再补 `ServerAliveInterval`/`ServerAliveCountMax` 兜底,并用私有 control socket 复用连接。所以 `~/.ssh/config` 里已有的 keepalive 仍然优先,我们的 `Include ~/.ssh/config.d/*` 也照常生效。
## 切换时的注意事项
- **分屏按分隔线的形状记**:`|` 是竖线 → 切成左右;`-` 是横线 → 切成上下。herdr 就是按形状命名的(`split_vertical` = 竖线 = 向右切,`split_horizontal` = 横线 = 向下切),而 tmux 的 `splitw -v/-h` 按“排列方向”命名,两者相反:tmux `splitw -v`(上下堆叠)对应 herdr `split_horizontal`,tmux `splitw -h`(左右并排)对应 `split_vertical`。本配置按形状对齐(`-` 上下、`|` 左右)。
- **`herdr` 裸跑会 attach;`herdr server` 是前台 headless server**。别为了“看看帮助”敲 `herdr server`——它不返回,会把调用它的地方永久挂住(踩过一次:pi 的 bash 工具卡了 46 分钟)。要看帮助用 `herdr --help`、`herdr <group>`(如 `herdr pane`);停 server 用 `herdr server stop`。
- **prefix 都是 `ctrl+b`**,两套配置不冲突,但同时开难免按错。切过去之前建议先退掉 tmux,避免双层状态栏/双层 prefix。
- **没有 `-c '#{pane_current_path}'` 这个开关**:新建 pane/tab 的目录策略在 `[terminal] new_cwd`(`follow` / `home` / `current` / 固定路径),本配置是 `follow`。
- **resize 步长不可配**。tmux 那边是固定 10 格;herdr 直接绑定(本配置 `prefix+shift+hjkl`)用默认步长,或 `prefix+r` 进 resize mode 再动。
- **`mouse on/off` 没有运行时快捷键**:由 `[ui] mouse_capture` 决定,运行时去 `prefix+s` 设置面板改。用 `copy_on_select = false` 可以在拖选后保留选区,等 `Ctrl+C` 再复制。
- **复制模式**:`mode-keys vi` 在 herdr 没有等价配置。本配置给 `copy_mode` 绑了 `prefix+[`(herdr 0.8.2 支持该校验通过,但未写进 `--default-config`),复制模式的内部键位不可配;平时直接鼠标拖选即可。想把 scrollback 丢进 `$EDITOR` 用 `prefix+e`。
- **中文/IME**:如果你在 pane 里用 pi/claude/codex 这类自绘光标的 TUI 遇到输入法候选框错位,可试开
`[experimental] reveal_hidden_cursor_for_cjk_ime = true`(可用 `cjk_ime_agents` 限定只对某些 agent 生效)。默认关闭。
- **别再跑 tpm**:`script/tmux-init.sh` 会克隆 tpm 并装插件,那是 tmux 的;herdr 不需要。两套可以并存,但别指望插件搬到 herdr。
- **升级**:`herdr update`(stable 通道);0.8.2 → 0.9.0 已经可用。用包管理器装的则走包管理器。
## 迁移状态
- [x] `herdr/config.toml`(tmux 键位/UI 行为映射,`herdr config check` 通过)
- [x] `script/herdr-init.sh`(软链 + 备份 + 幂等 + 校验)
- [ ] 装 agent integration(`herdr integration install claude` 等),才有原生会话恢复与状态上报
- [ ] 需要时启用 `reveal_hidden_cursor_for_cjk_ime`
+106
View File
@@ -0,0 +1,106 @@
# herdr 配置 —— 由 ~/.dotfile/tmux/.tmux.conf 映射而来
#
# 仓库内真实文件: ~/.dotfile/herdr/config.toml
# 由 script/herdr-init.sh 软链到: ~/.config/herdr/config.toml
#
# 校验: herdr config check
# 热重载: prefix+shift+r,或 herdr server reload-config
# 默认值: herdr --default-config
#
# 这里只写与 herdr 默认值不同(或需要显式强调)的键,未写到的沿用默认。
# 概念对照: tmux session → herdr workspace,tmux window → herdr tab,tmux pane → herdr pane
[keys]
# ---------------------------------------------------------------- 前缀
# tmux 里的 prefix 相关行都被注释掉了,所以沿用 ctrl+b(herdr 默认值,无需显式设置)
# prefix = "ctrl+b"
# tmux: bind r source-file ~/.tmux.conf \; display-message "Config reloaded"
reload_config = "prefix+r" # herdr 默认是 prefix+shift+r
# ---------------------------------------------------------------- 分屏
# 按“分隔线的形状”对齐:
# | 是竖线 → 把 pane 切成左右
# - 是横线 → 把 pane 切成上下
# herdr 就是按分隔线形状命名的,而 tmux 的 -v/-h 按“排列方向”命名,所以二者相反:
# tmux splitw -v(上下堆叠)= 横线 = herdr split_horizontal(向下切)
# tmux splitw -h(左右并排)= 竖线 = herdr split_vertical (向右切)
# tmux: bind - splitw -v -c '#{pane_current_path}' → 切成上下
split_horizontal = "prefix+minus"
# tmux: bind | splitw -h -c '#{pane_current_path}' → 切成左右
split_vertical = "prefix+|"
# tmux 的 -c '#{pane_current_path}' 对应 [terminal] new_cwd = "follow"(默认值)
# ---------------------------------------------------------------- pane 切换
# 注释写的是“绑定 hjkl 键为面板切换”,其中 h/l 两行被注释掉了,这里按原意启用
focus_pane_left = "prefix+h"
focus_pane_down = "prefix+j"
focus_pane_up = "prefix+k"
focus_pane_right = "prefix+l"
# tmux: bind -r K/J/H/L resizep -U/-D/-L/-R 10
# herdr 可直接绑定 resize,不必先进入 resize mode;步长不可配(tmux 那边是 10 格)
resize_pane_up = "prefix+shift+k"
resize_pane_down = "prefix+shift+j"
resize_pane_left = "prefix+shift+h"
# 注: tmux 里 prefix+shift+l 被后面的 `bind L switch-client -l` 覆盖,此处留给“回上一个”
# 向右 resize 用 prefix+r 进 resize mode,或在此写回 "prefix+shift+l"
# tmux: bind L switch-client -l (注释写“返回上一个窗口”)
previous_tab = "prefix+shift+l"
# 想要真正的“来回切换”(不按顺序),可改为 last_pane = "prefix+shift+l"
# tmux: bind M command-prompt -p "插入窗口到位置:" "move-window -b -t ':%%'"
# herdr 没有“输入索引插入”这种交互,改成相邻平移
move_tab_previous = "alt+shift+left"
move_tab_next = "alt+shift+right"
# ---------------------------------------------------------------- 复制模式
# tmux: setw -g mode-keys vi(tmux 默认用 prefix+[ 进入 copy mode)
# herdr 0.8.2 支持 copy_mode 键(未写进 --default-config,但 herdr config check 认可)
copy_mode = "prefix+["
# tmux 里没有的: 把 scrollback 丢给 $EDITOR(herdr 默认 prefix+e)
# edit_scrollback = "prefix+e"
# ---------------------------------------------------------------- 故意不复刻的 tmux 绑定
# tmux: bind -r j select-pane -t .+1 \; resize-pane -Z
# 它覆盖了上面的 `bind j select-pane -D`,含义是“切到下一个 pane 且每次都 toggle zoom”,
# 语义含糊,不复刻。循环切换用 herdr 默认的 prefix+tab / prefix+shift+tab,
# zoom 用 prefix+z。
# tmux: bind m (切换鼠标模式)
# herdr 没有鼠标开关的运行时按键: 行为由 [ui] mouse_capture 决定,
# 运行时可开 prefix+s(设置)面板调整。
# herdr 自带、tmux 那边靠插件实现的能力(均为默认键):
# prefix+w workspace picker(≈ tmux-fzf)
# prefix+g goto
# prefix+? 列出所有有效绑定
[terminal]
# tmux 新建 pane/window 都带 -c '#{pane_current_path}'
new_cwd = "follow"
[ui]
# tmux: set -g mouse on
mouse_capture = true
# tmux: set -s set-clipboard on
copy_on_select = true
# tmux: set-titles on / set-titles-string "#{session_name}"
window_title = "{workspace}"
# tmux: automatic-rename on(窗口名跟随当前目录)→ 关掉 herdr 的新 tab 命名询问
prompt_new_tab_name = false
# tmux 风格: 只要内部分隔线,不要外面那圈边框
pane_outer_borders = false
# tmux: set -as terminal-features ",*:osc9" + allow-passthrough,让外层终端弹通知
# herdr 直接把 pane 里的通知转发给外层终端(Ghostty)
[ui.toast]
delivery = "terminal"
[session]
# 近似替代 tmux-resurrect 的会话恢复
# (原生会话恢复需要对应 agent 装了 herdr integration,如 herdr integration install claude)
resume_agents_on_restore = true
[experimental]
# 近似替代 tmux-resurrect 的 scrollback 恢复: 重启 server 后保留 pane 屏幕历史
pane_history = true
+89 -16
View File
@@ -1,20 +1,93 @@
#!bin/sh
#git clone https://github.com/hilaily/.dotfile.git ~/.dotfile
#!/bin/bash
# 安装并初始化 Fish、tmux 与 Git 的基础 dotfile 环境
# 判断 zsh 是否安装
#sudo yum update && sudo yum -y install zsh
SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)"
# shellcheck disable=SC1091
source "$SCRIPT_DIR/script/common/help.sh"
# shellcheck disable=SC1091
source "$SCRIPT_DIR/script/common/os-arch.sh"
# 判断 oh-my-zsh 是否安装
if [ ! -d "$HOME/.oh-my-zsh" ];then
export ZSH=$HOME/.oh-my-zsh
sh -c "$(wget https://raw.githubusercontent.com/robbyrussell/oh-my-zsh/master/tools/install.sh -O -)"
set -euo pipefail
usage() {
cat <<'EOF'
用法: ./init.sh
安装缺失的基础工具(fish、tmux、git),随后依次执行:
1. fish-init 初始化 Fish 配置、插件与默认 shell
2. tmux-init 初始化 tmux 配置与 TPM 插件
3. git-init 初始化 Git 配置
支持:
macOS Homebrew
Debian/Ubuntu apt
选项:
-h, --help 显示此帮助
说明:
请以需要配置的普通用户运行,不要使用 sudo 运行本脚本。
Linux 安装缺失软件时会请求 sudo;Fish 初始化也会请求 sudo 将 fish
加入 /etc/shells。
EOF
}
dotfile_help_requested "${1:-}" && dotfile_show_help
[[ $# -eq 0 ]] || { echo "错误: 不支持参数: $*" >&2; usage >&2; exit 1; }
die() {
echo "错误: $*" >&2
exit 1
}
run_with_sudo() {
if [[ $EUID -eq 0 ]]; then
"$@"
else
sudo "$@"
fi
}
[[ $EUID -ne 0 ]] || die "请以需要配置的普通用户运行,不要使用 sudo ./init.sh"
[[ -d "$HOME/.dotfile" ]] || die "未找到 $HOME/.dotfile;请将仓库放在该路径后重试"
[[ "$SCRIPT_DIR" -ef "$HOME/.dotfile" ]] || die "当前仓库不是 $HOME/.dotfile;现有初始化脚本依赖该路径"
missing_packages=()
command -v fish >/dev/null 2>&1 || missing_packages+=(fish)
command -v tmux >/dev/null 2>&1 || missing_packages+=(tmux)
command -v git >/dev/null 2>&1 || missing_packages+=(git)
if ((${#missing_packages[@]} == 0)); then
echo "基础工具已安装:fish、tmux、git"
else
echo "安装缺失工具: ${missing_packages[*]}"
case "$(get_os)" in
darwin)
command -v brew >/dev/null 2>&1 || die "未找到 Homebrew;请先运行: bash script/brew-install.sh"
brew install "${missing_packages[@]}"
;;
linux)
command -v apt-get >/dev/null 2>&1 || die "仅支持 apt;请先通过系统包管理器安装: ${missing_packages[*]}"
run_with_sudo apt-get update
run_with_sudo apt-get install -y "${missing_packages[@]}"
;;
*)
die "不支持的系统: $(get_os)"
;;
esac
fi
ln -sf ~/.dotfile/.zshrc ~/
ln -sf ~/.dotfile/laily.zsh-theme ~/.oh-my-zsh/themes/
ln -sf ~/.dotfile/.tmux.conf ~/
ln -sf ~/.dotfile/git/.gitconfig ~/.gitconfig
# install neovim
cd /usr/local
wget https://github.com/neovim/neovim/releases/download/v0.6.0/nvim-macos.tar.gz
tar zxvf nvim-macos.tar.gz
echo
echo "== 初始化 Fish =="
bash "$SCRIPT_DIR/script/fish-init.sh"
echo
echo "== 初始化 tmux =="
bash "$SCRIPT_DIR/script/tmux-init.sh"
echo
echo "== 初始化 Git =="
bash "$SCRIPT_DIR/script/git-init.sh"
echo
echo "基础环境初始化完成。重新打开终端后 Fish 将作为默认 shell 生效。"
File diff suppressed because it is too large Load Diff
+2
View File
@@ -0,0 +1,2 @@
defaults write com.googlecode.iterm2.plist LoadPrefsFromCustomFolder -bool true
+3
View File
@@ -0,0 +1,3 @@
#/bin/bash
defaults write com.googlecode.iterm2.plist PrefsCustomFolder -string "~/.dotfile/iterm2"
# then open iterm2, Preferences->General->Settings, Mark 'load preferences from xxx'
-7
View File
@@ -1,7 +0,0 @@
local ret_status="%(?:%{$fg_bold[green]%}➜ :%{$fg_bold[red]%}➜ )"
PROMPT='%{$fg[red]%}%n%{$reset_color%}@%{$fg[blue]%}%m ${ret_status} %{$fg[cyan]%}%c%{$reset_color%} $(git_prompt_info)'
ZSH_THEME_GIT_PROMPT_PREFIX="%{$fg_bold[blue]%}git:(%{$fg[red]%}"
ZSH_THEME_GIT_PROMPT_SUFFIX="%{$reset_color%} "
ZSH_THEME_GIT_PROMPT_DIRTY="%{$fg[blue]%}) %{$fg[yellow]%}✗"
ZSH_THEME_GIT_PROMPT_CLEAN="%{$fg[blue]%})"
+2
View File
@@ -0,0 +1,2 @@
# 跨机器共享。本机工具写在 ~/.config/mise/config.toml(mise use -g)
[tools]
-20
View File
@@ -1,20 +0,0 @@
# Nvim config
## Backup plugin version
Because nvim have some breaking changes sometimes. I can not alwaexys use the latest version of plugins.
### Backup
Execute `get_packer_snapshot.sh`. This script will backup all the plugin commit information to a json file.
### Restore
document: https://github.com/wbthomason/packer.nvim#usage
```
vim +"PackerSnapshot lock-${version}.json"
```
-1
View File
@@ -1 +0,0 @@
setl ts=2 sw=2
-1
View File
@@ -1 +0,0 @@
require('lang.go')
-1
View File
@@ -1 +0,0 @@
setl ts=2 sw=2
-1
View File
@@ -1 +0,0 @@
setl ts=2 sw=2
-1
View File
@@ -1 +0,0 @@
setl commentstring={/*%s*/} ts=2 sw=2
-1
View File
@@ -1 +0,0 @@
require('lang.js-ts')
-3
View File
@@ -1,3 +0,0 @@
vim.cmd('set ts=2') -- Insert 2 spaces for a tab
vim.cmd('set sw=2') -- Change the number of space characters inserted for indentation
require('lang/lua')
-2
View File
@@ -1,2 +0,0 @@
require('lang.bash')
-1
View File
@@ -1 +0,0 @@
require('lang.js-ts')
-2
View File
@@ -1,2 +0,0 @@
vim.cmd('set ts=2') -- Insert 2 spaces for a tab
vim.cmd('set sw=2') -- Change the number of space characters inserted for indentation
-2
View File
@@ -1,2 +0,0 @@
vim.cmd('set ts=2') -- Insert 2 spaces for a tab
vim.cmd('set sw=2') -- Change the number of space characters inserted for indentation
-1
View File
@@ -1 +0,0 @@
setl ts=2 sw=2 ts=2 ai et
-10
View File
@@ -1,10 +0,0 @@
#!/bin/bash
version=$(vim -v | awk 'NR==1{print $2}')
path="$HOME/.cache/nvim/packer.nvim/lock-${version}.json"
rm -rf ${path}
vim +"PackerSnapshot lock-${version}.json"
cp -f ${path} .
echo 'file at '${path}
+47 -28
View File
@@ -1,33 +1,52 @@
CONFIG_PATH = vim.fn.stdpath('config')
DATA_PATH = vim.fn.stdpath('data')
CACHE_PATH = vim.fn.stdpath('cache')
P = vim.inspect
if vim.loader then
vim.loader.enable()
end
require('lv-globals')
vim.cmd('luafile '..CONFIG_PATH..'/settings.lua')
require('settings')
require('plugins')
require('utils')
require('pluginconfig')
require('autocommands')
require('keymappings')
require('colorscheme') -- This plugin must be required somewhere after nvimtree. Placing it before will break navigation keymappings
-- 让 Mason / 健康检查能找到本仓库常用的本地 CLI(fd、lazygit、tree-sitter 等)
local _local_bin = vim.fn.expand("~/.local/bin")
if vim.fn.isdirectory(_local_bin) == 1 then
vim.env.PATH = _local_bin .. ":" .. vim.env.PATH
end
-- 全局 npm 包(如 mermaid-cli 的 `mmdc`)通常在 $(npm prefix -g)/bin
do
local ok, out = pcall(vim.fn.system, { "npm", "prefix", "-g" })
if ok and out and out ~= "" then
local npm_bin = vim.fn.trim(out) .. "/bin"
if vim.fn.isdirectory(npm_bin) == 1 then
vim.env.PATH = npm_bin .. ":" .. vim.env.PATH
end
end
end
-- TODO is there a way to do this without vimscript
vim.cmd('source '..CONFIG_PATH..'/vimscript/functions.vim')
vim.g.loaded_netrw = 1
vim.g.loaded_netrwPlugin = 1
-- LSP
require('lsp')
_G.dd = function(...)
require("utils.debug").dump(...)
end
vim.print = _G.dd
--[[
require('lsp.css-ls')
require('lsp.docker-ls')
require('lsp.emmet-ls')
require('lsp.html-ls')
require('lsp.json-ls')
require('lsp.latex-ls')
require('lsp.python-ls')
require('lsp.rust-ls')
require('lsp.yaml-ls')
]]
require("config.lazy")
-- CONFIG_PATH = vim.fn.stdpath('config')
-- DATA_PATH = vim.fn.stdpath('data')
-- CACHE_PATH = vim.fn.stdpath('cache')
-- P = vim.inspect
-- require('lv-globals')
-- vim.cmd('luafile '..CONFIG_PATH..'/settings.lua')
-- require('settings')
-- require('plugins')
-- require('utils')
-- require('pluginconfig')
-- require('autocommands')
-- require('keymappings')
-- require('colorscheme') -- This plugin must be required somewhere after nvimtree. Placing it before will break navigation keymappings
-- -- TODO is there a way to do this without vimscript
-- vim.cmd('source '..CONFIG_PATH..'/vimscript/functions.vim')
-- -- LSP
-- require('lsp')
+65
View File
@@ -0,0 +1,65 @@
{
"FixCursorHold.nvim": { "branch": "master", "commit": "1900f89dc17c603eec29960f57c00bd9ae696495" },
"LazyVim": { "branch": "main", "commit": "fca0af57cc3851b14f96a795a9c9bfafc5096dd1" },
"SchemaStore.nvim": { "branch": "main", "commit": "4a0e1b74096937d366031bc1e2b244546af39f8e" },
"avante.nvim": { "branch": "main", "commit": "e89eb79abf5754645e20aa6074da10ed20bba33c" },
"barbar.nvim": { "branch": "master", "commit": "53b5a2f34b68875898f0531032fbf090e3952ad7" },
"blink-cmp-avante": { "branch": "master", "commit": "4f494c6e124acbe31a8f5d58effa0c14aa38a6d5" },
"blink.cmp": { "branch": "main", "commit": "78336bc89ee5365633bcf754d93df01678b5c08f" },
"bufferline.nvim": { "branch": "main", "commit": "655133c3b4c3e5e05ec549b9f8cc2894ac6f51b3" },
"catppuccin": { "branch": "main", "commit": "edefef779ab08ce1a4a404713e3012b0d202bd35" },
"codecompanion.nvim": { "branch": "main", "commit": "e0780fa9fda504ffb89307cabcb6cbe1ce8eb60c" },
"conform.nvim": { "branch": "master", "commit": "086a40dc7ed8242c03be9f47fbcee68699cc2395" },
"dashboard-nvim": { "branch": "master", "commit": "0775e567b6c0be96d01a61795f7b64c1758262f6" },
"diffview.nvim": { "branch": "main", "commit": "4516612fe98ff56ae0415a259ff6361a89419b0a" },
"dressing.nvim": { "branch": "master", "commit": "2d7c2db2507fa3c4956142ee607431ddb2828639" },
"flash.nvim": { "branch": "main", "commit": "5f0f270fdc7c5b0c21d903ee85b9cb06f2ac636a" },
"friendly-snippets": { "branch": "main", "commit": "6cd7280adead7f586db6fccbd15d2cac7e2188b9" },
"fzf-lua": { "branch": "main", "commit": "c9e7b7bfbd01f949164988ee1684035468e1995c" },
"gemini-cli.nvim": { "branch": "main", "commit": "c9fd62adda823628f5131a939d9c56ef7a898600" },
"gitsigns.nvim": { "branch": "main", "commit": "caa98e2e5e4c23a1652c23be839d2e1722cecc29" },
"grug-far.nvim": { "branch": "main", "commit": "11595bf747edc270bce2069d1020502ad4ae56cf" },
"lazy.nvim": { "branch": "main", "commit": "85c7ff3711b730b4030d03144f6db6375044ae82" },
"lazydev.nvim": { "branch": "main", "commit": "ff2cbcba459b637ec3fd165a2be59b7bbaeedf0d" },
"lsp_signature.nvim": { "branch": "master", "commit": "af7e4074d85d785cf6614352ba9ad3b28a1f8a56" },
"lualine.nvim": { "branch": "master", "commit": "221ce6b2d999187044529f49da6554a92f740a96" },
"mason-lspconfig.nvim": { "branch": "main", "commit": "40276c4df7e6bdce6801d6c035c6227f9115a855" },
"mason-nvim-dap.nvim": { "branch": "main", "commit": "9a10e096703966335bd5c46c8c875d5b0690dade" },
"mason.nvim": { "branch": "main", "commit": "44d1e90e1f66e077268191e3ee9d2ac97cc18e65" },
"mini.ai": { "branch": "main", "commit": "25248c6aa002391936a6200f12d1466015987133" },
"mini.icons": { "branch": "main", "commit": "98faae31e9be1cc054ae63485e58ceb185efcad0" },
"mini.pairs": { "branch": "main", "commit": "b1c5a726921b7a8c9321e9a7a208aa0571de5810" },
"neo-tree.nvim": { "branch": "main", "commit": "f3f3bf73414e400cf9fc13fda50f00404a8f8ab1" },
"neotest": { "branch": "master", "commit": "72bc8f1ec62a590fb47368c81f89610e0f353e28" },
"neotest-golang": { "branch": "main", "commit": "58d07a76d714f8f768bbe27711b06d41ca237728" },
"noice.nvim": { "branch": "main", "commit": "7bfd942445fb63089b59f97ca487d605e715f155" },
"nui.nvim": { "branch": "main", "commit": "10fc361835c856ba4233ef5ea135b919bf3dce97" },
"nvim-cmp": { "branch": "main", "commit": "f17d9b4394027ff4442b298398dfcaab97e40c4f" },
"nvim-dap": { "branch": "master", "commit": "c9a0738e45f1bd41d792a126941348dce661cf9b" },
"nvim-dap-go": { "branch": "main", "commit": "b4421153ead5d726603b02743ea40cf26a51ed5f" },
"nvim-dap-ui": { "branch": "master", "commit": "cc9dd33aade7f20bae414d0cba163bc60d4d4b43" },
"nvim-dap-virtual-text": { "branch": "master", "commit": "fbdb48c2ed45f4a8293d0d483f7730d24467ccb6" },
"nvim-lint": { "branch": "master", "commit": "3d55c8f67c6ae5c15e1042571e107c7a3d5c5f4e" },
"nvim-lspconfig": { "branch": "master", "commit": "5492a24edd68f78d8a778edcb8905d0a28993abc" },
"nvim-nio": { "branch": "master", "commit": "21f5324bfac14e22ba26553caf69ec76ae8a7662" },
"nvim-tree.lua": { "branch": "master", "commit": "3603f069ff783c31986894a56c2a6755331b0802" },
"nvim-treesitter": { "branch": "main", "commit": "7caec274fd19c12b55902a5b795100d21531391f" },
"nvim-treesitter-textobjects": { "branch": "main", "commit": "5c7b0263797dfd1bd6202f2b219f3b53a80b2187" },
"nvim-ts-autotag": { "branch": "main", "commit": "88c1453db4ba7dd24131086fe51fdf74e587d275" },
"nvim-web-devicons": { "branch": "master", "commit": "d7462543c9e366c0d196c7f67a945eaaf5d99414" },
"persistence.nvim": { "branch": "main", "commit": "b20b2a7887bd39c1a356980b45e03250f3dce49c" },
"playground": { "branch": "master", "commit": "ba48c6a62a280eefb7c85725b0915e021a1a0749" },
"plenary.nvim": { "branch": "master", "commit": "b9fd5226c2f76c951fc8ed5923d85e4de065e509" },
"render-markdown.nvim": { "branch": "main", "commit": "c7188a8f9d2953696b6303caccbf39c51fa2c1b1" },
"sidekick.nvim": { "branch": "main", "commit": "17447a05f9385e5f8372b61530f6f9329cb82421" },
"snacks.nvim": { "branch": "main", "commit": "ad9ede6a9cddf16cedbd31b8932d6dcdee9b716e" },
"symbols-outline.nvim": { "branch": "master", "commit": "564ee65dfc9024bdde73a6621820866987cbb256" },
"telescope-file-browser.nvim": { "branch": "master", "commit": "3610dc7dc91f06aa98b11dca5cc30dfa98626b7e" },
"telescope-fzf-native.nvim": { "branch": "main", "commit": "6fea601bd2b694c6f2ae08a6c6fab14930c60e2c" },
"telescope.nvim": { "branch": "master", "commit": "40aedd8a68c78a656a10a8d62d80c54af59420fb" },
"todo-comments.nvim": { "branch": "main", "commit": "31e3c38ce9b29781e4422fc0322eb0a21f4e8668" },
"tokyonight.nvim": { "branch": "main", "commit": "cdc07ac78467a233fd62c493de29a17e0cf2b2b6" },
"trouble.nvim": { "branch": "main", "commit": "bd67efe408d4816e25e8491cc5ad4088e708a69a" },
"ts-comments.nvim": { "branch": "main", "commit": "a59d6092213447450191122c9346f309161504cb" },
"which-key.nvim": { "branch": "main", "commit": "3aab2147e74890957785941f0c1ad87d0a44c15a" }
}
+17
View File
@@ -0,0 +1,17 @@
{
"extras": [
"lazyvim.plugins.extras.editor.telescope",
"lazyvim.plugins.extras.test.core",
"lazyvim.plugins.extras.lang.go",
"lazyvim.plugins.extras.lang.typescript",
"lazyvim.plugins.extras.lang.json",
"lazyvim.plugins.extras.dap.core",
"lazyvim.plugins.extras.formatting.prettier",
"lazyvim.plugins.extras.ai.avante"
],
"install_version": 7,
"news": {
"NEWS.md": "11866"
},
"version": 8
}
-1
View File
@@ -1 +0,0 @@
{"nvim-dap": {"commit": "2cfd770"}, "FixCursorHold.nvim": {"commit": "5aa5ff1"}, "nvcode-color-schemes.vim": {"commit": "3a0e624"}, "telescope-fzf-native.nvim": {"commit": "65c0ee3"}, "codi.vim": {"commit": "2898369"}, "packer.nvim": {"commit": "6afb674"}, "nvim-lspconfig": {"commit": "51775b1"}, "playground": {"commit": "bcfab84"}, "numb.nvim": {"commit": "453c50a"}, "nvim-dap-virtual-text": {"commit": "2971ce3"}, "which-key.nvim": {"commit": "92d924d"}, "telescope-file-browser.nvim": {"commit": "60bcf4e"}, "aerial.nvim": {"commit": "f7d6ca8"}, "nvim-autopairs": {"commit": "14cc2a4"}, "neotest-go": {"commit": "d506f83"}, "nvim-colorizer.lua": {"commit": "36c610a"}, "nvim-comment": {"commit": "e9ac16a"}, "tabular": {"commit": "339091a"}, "rnvimr": {"commit": "4ced809"}, "gitsigns.nvim": {"commit": "4d8a6e0"}, "glow.nvim": {"commit": "8dca358"}, "nvim-ts-autotag": {"commit": "fdefe46"}, "telescope.nvim": {"commit": "30e2dc5"}, "nvim-compe": {"commit": "d186d73"}, "telescope-project.nvim": {"commit": "91e9a97"}, "trouble.nvim": {"commit": "929315e"}, "nvim-ts-context-commentstring": {"commit": "4d3a68c"}, "lsp_signature.nvim": {"commit": "e65a638"}, "one-monokai-vim": {"commit": "4fdfd93"}, "gruvbox.nvim": {"commit": "c632f62"}, "hop.nvim": {"commit": "75f73d3"}, "lush.nvim": {"commit": "fabf012"}, "LuaSnip": {"commit": "e687d78"}, "vim-go-coverage": {"commit": "d9b0c9f"}, "vim-matchup": {"commit": "ab8575d"}, "popup.nvim": {"commit": "b7404d3"}, "nvim-bqf": {"commit": "aac1ff9"}, "FTerm.nvim": {"commit": "efd1065"}, "lua-dev.nvim": {"commit": "315ad43"}, "nvim-treesitter": {"commit": "8689f61"}, "neotest": {"commit": "15c0bb2"}, "nvim-dap-ui": {"commit": "ebebd42"}, "nvim-luapad": {"commit": "9815e26"}, "monokai.nvim": {"commit": "4fc970e"}, "lspsaga.nvim": {"commit": "9ec569a"}, "vim-markdown": {"commit": "8a0b434"}, "dashboard-nvim": {"commit": "d82ddae"}, "barbar.nvim": {"commit": "c41ad6e"}, "nvim-tree.lua": {"commit": "fb8735e"}, "bracey.vim": {"commit": "4e1a22a"}, "fzf-lua": {"commit": "9b23c2c"}, "nvim-lsp-installer": {"commit": "ae913cb"}, "lsp-rooter.nvim": {"commit": "7c83364"}, "toggleterm.nvim": {"commit": "f494c61"}, "plenary.nvim": {"commit": "62dc2a7"}, "telescope-dap.nvim": {"commit": "b4134ff"}, "markdown-preview.nvim": {"commit": "02cc387"}, "nvim-web-devicons": {"commit": "2d02a56"}, "DAPInstall.nvim": {"commit": "bbda2b0"}, "galaxyline.nvim": {"commit": "eb81be0"}}
-76
View File
@@ -1,76 +0,0 @@
local utils = require('utils')
local auto_formatters = { }
local python_autoformat = {'BufWritePre', '*.py', 'lua vim.lsp.buf.formatting_sync(nil, 1000)'}
if O.python.autoformat then table.insert(auto_formatters, python_autoformat) end
local javascript_autoformat = {'BufWritePre', '*.js', 'lua vim.lsp.buf.formatting_sync(nil, 1000)'}
local javascriptreact_autoformat = {'BufWritePre', '*.jsx', 'lua vim.lsp.buf.formatting_sync(nil, 1000)'}
local typescript_autoformat = {'BufWritePre', '*.ts', 'lua vim.lsp.buf.formatting_sync(nil, 1000)'}
local typescriptreact_autoformat = {'BufWritePre', '*.tsx', 'lua vim.lsp.buf.formatting_sync(nil, 1000)'}
if O.tsserver.autoformat then
table.insert(auto_formatters, javascript_autoformat)
table.insert(auto_formatters, javascriptreact_autoformat)
table.insert(auto_formatters, typescript_autoformat)
table.insert(auto_formatters, typescriptreact_autoformat)
end
local lua_format = {'BufWritePre', '*.lua', 'lua vim.lsp.buf.formatting_sync(nil, 1000)'}
if O.lua.autoformat then table.insert(auto_formatters, lua_format) end
local json_format = {'BufWritePre', '*.json', 'lua vim.lsp.buf.formatting_sync(nil, 1000)'}
if O.json.autoformat then table.insert(auto_formatters, json_format) end
local ruby_format = {'BufWritePre', '*.rb', 'lua vim.lsp.buf.formatting_sync(nil,1000)'}
if O.ruby.autoformat then table.insert(auto_formatters, ruby_format) end
local go_format = {'BufWritePre', '*.go', 'lua vim.lsp.buf.formatting_sync(nil,1000)'}
if O.go.autoformat then table.insert(auto_formatters, go_format) end
utils.define_augroups({
_general_settings = {
{'TextYankPost', '*', 'lua require(\'vim.highlight\').on_yank({higroup = \'Search\', timeout = 200})'},
{'BufWinEnter', '*', 'setlocal formatoptions-=c formatoptions-=r formatoptions-=o'},
{'BufRead', '*', 'setlocal formatoptions-=c formatoptions-=r formatoptions-=o'},
{'BufNewFile', '*', 'setlocal formatoptions-=c formatoptions-=r formatoptions-=o'},
{'VimLeavePre', '*', 'set title set titleold='},
{'FileType', 'qf', 'set nobuflisted'},
-- {'User', 'GoyoLeave', 'lua require(\'galaxyline\').disable_galaxyline()'},
-- {'User', 'GoyoEnter', 'lua require(\'galaxyline\').galaxyline_augroup()'},
},
_java = {
{'FileType', 'java', 'luafile '..CONFIG_PATH..'/lua/lsp/java-ls.lua'},
{'FileType', 'java', 'nnoremap ca <Cmd>lua require(\'jdtls\').code_action()<CR>'}
},
_go = {
-- Go generally requires Tabs instead of spaces.
{'FileType', 'go', 'setlocal tabstop=4'},
{'FileType', 'go', 'setlocal shiftwidth=4'},
{'FileType', 'go', 'setlocal softtabstop=4'},
{'FileType', 'go', 'setlocal noexpandtab'},
},
_dashboard = {
-- seems to be nobuflisted that makes my stuff disapear will do more testing
{
'FileType', 'dashboard',
'setlocal nocursorline noswapfile synmaxcol& signcolumn=no norelativenumber nocursorcolumn nospell nolist nonumber bufhidden=wipe colorcolumn= foldcolumn=0 matchpairs= '
}, {'FileType', 'dashboard', 'set showtabline=0 | autocmd BufLeave <buffer> set showtabline=2'}
},
_markdown = {{'FileType', 'markdown', 'setlocal wrap'}, {'FileType', 'markdown', 'setlocal spell'}},
_solidity = {
{'BufWinEnter', '.sol', 'setlocal filetype=solidity'}, {'BufRead', '*.sol', 'setlocal filetype=solidity'},
{'BufNewFile', '*.sol', 'setlocal filetype=solidity'}
},
_gemini = {
{'BufWinEnter', '.gmi', 'setlocal filetype=markdown'}, {'BufRead', '*.gmi', 'setlocal filetype=markdown'},
{'BufNewFile', '*.gmi', 'setlocal filetype=markdown'}
},
_buffer_bindings = {
{'FileType', 'dashboard', 'nnoremap <silent> <buffer> q :q<CR>'},
{'FileType', 'lspinfo', 'nnoremap <silent> <buffer> q :q<CR>'},
{'FileType', 'floaterm', 'nnoremap <silent> <buffer> q :q<CR>'},
},
_auto_formatters = auto_formatters
})
-3
View File
@@ -1,3 +0,0 @@
vim.cmd('let g:nvcode_termcolors=256')
vim.cmd('colorscheme ' .. O.colorscheme)
+22
View File
@@ -0,0 +1,22 @@
vim.api.nvim_create_user_command("W", function()
require("utils").sudo_write()
end, { desc = "Write file with sudo (tee)" })
-- Turn off paste mode when leaving insert
vim.api.nvim_create_autocmd("InsertLeave", {
pattern = "*",
command = "set nopaste",
})
-- Disable the concealing in some file formats
-- The default conceallevel is 3 in LazyVim
vim.api.nvim_create_autocmd("FileType", {
pattern = { "js", "tsx", "ts", "json", "jsonc", "markdown", "md", "css", "html" },
callback = function()
vim.opt.conceallevel = 0
vim.bo.tabstop = 2
vim.bo.softtabstop = 2
vim.bo.shiftwidth = 2
-- vim.bo.expand = true
end,
})
+180
View File
@@ -0,0 +1,180 @@
local keymap = vim.keymap
-- <Leader>b for buffer
keymap.set("n", "<Leader>bq", "<cmd>q<cr>", { desc = "quit" })
keymap.set("n", "<Leader>bs", "<cmd>w<cr>", { desc = "save" })
keymap.set("n", "<Leader>br", "<cmd>e!<cr>", { desc = "reload" })
keymap.set("n", "<Leader>bd", "<cmd>BufferClose<cr>", { desc = "close buffer" })
-- <Leader>c
keymap.set("n", "<Leader>cs", "<cmd>SymbolsOutline<cr>", { desc = "Symbols Outline" })
-- <Leader>D for Trouble (v3 syntax)
keymap.set("n", "<Leader>Dt", "<cmd>Trouble diagnostics toggle<cr>", { desc = "trouble diagnostics" })
keymap.set("n", "<Leader>Dd", "<cmd>Trouble diagnostics toggle filter.buf=0<cr>", { desc = "document diagnostics" })
keymap.set("n", "<Leader>Dq", "<cmd>Trouble qflist toggle<cr>", { desc = "quickfix" })
keymap.set("n", "<Leader>Dl", "<cmd>Trouble loclist toggle<cr>", { desc = "loclist" })
keymap.set("n", "<Leader>Dr", "<cmd>Trouble lsp_references toggle<cr>", { desc = "references" })
-- <Leader>d for debug
keymap.set("n", "<Leader>dd", "<cmd>lua vim.diagnostic.open_float()<cr>", { desc = "show diagnostics" })
keymap.set("n", "<Leader>dc", '<cmd>lua require("dap").continue()<cr>', { desc = "continue" })
keymap.set("n", "<Leader>db", '<cmd>lua require("dap").toggle_breakpoint()<cr>', { desc = "Toggle Breakpoint" })
keymap.set("n", "<Leader>dj", '<cmd>lua require("dap").step_over()<cr>', { desc = "step over" })
keymap.set("n", "<Leader>dk", '<cmd>lua require("dap").step_out()<cr>', { desc = "step out" })
keymap.set("n", "<Leader>dl", '<cmd>lua require("dap").step_into()<cr>', { desc = "step into" })
keymap.set("n", "<Leader>du", '<cmd>lua require("dap").up()<cr>', { desc = "up" })
keymap.set("n", "<Leader>d_", '<cmd>lua require("dap").disconnect();require("dap").stop();require("dap").run_last()<cr>', { desc = "disconnect" })
keymap.set("n", "<Leader>di", '<cmd>lua require("dap.ui.variables").visual_hover()<cr>', { desc = "visual hover" })
keymap.set("n", "<Leader>ds", '<cmd>lua local w=require("dap.ui.widgets");w.centered_float(w.scopes)<cr>', { desc = "scopes" })
-- <Leader>f
keymap.set("n", "<Leader>fm", "<cmd>lua require('utils').open_md()<cr>", { desc = "open markdown in external app" })
keymap.set("n", "<Leader>fs", "<cmd>w<cr>", { desc = "save file" })
keymap.set("n", "<Leader>ft", "<cmd>NvimTreeToggle<cr>", { desc = "toggle nvim tree" })
keymap.set("n", "<Leader>fy", "<cmd>lua require('utils').yank_filepath()<cr>", { desc = "copy file path" })
keymap.set("n", "<Leader>fd", "<cmd>lua require('utils').yank_file_dir()<cr>", { desc = "copy dir path" })
keymap.set("n", "<Leader>fl", "<cmd>NvimTreeFindFile<CR>", { desc = "find file in tree" })
-- <Leader>g
keymap.set("n", "<Leader>gj", "<cmd>lua require('gitsigns').next_hunk()<cr>", { desc = "Next Hunk" })
keymap.set("n", "<Leader>gk", "<cmd>lua require('gitsigns').prev_hunk()<cr>", { desc = "Prev Hunk" })
keymap.set("n", "<Leader>gl", "<cmd>lua require('gitsigns').blame_line()<cr>", { desc = "Blame" })
keymap.set("n", "<Leader>gp", "<cmd>lua require('gitsigns').preview_hunk()<cr>", { desc = "Preview Hunk" })
keymap.set("n", "<Leader>gr", "<cmd>lua require('gitsigns').reset_hunk()<cr>", { desc = "Reset Hunk" })
keymap.set("n", "<Leader>gR", "<cmd>lua require('gitsigns').reset_buffer()<cr>", { desc = "Reset Buffer" })
keymap.set("n", "<Leader>gs", "<cmd>lua require('gitsigns').stage_hunk()<cr>", { desc = "Stage Hunk" })
keymap.set("n", "<Leader>gS", "<cmd>lua require('gitsigns').stage_buffer()<cr>", { desc = "Stage Buffer" })
keymap.set("n", "<Leader>gu", "<cmd>lua require('gitsigns').undo_stage_hunk()<cr>", { desc = "Undo Stage Hunk" })
keymap.set("n", "<Leader>go", "<cmd>Telescope git_status<cr>", { desc = "Open changed file" })
keymap.set("n", "<Leader>gb", "<cmd>Telescope git_branches<cr>", { desc = "Checkout branch" })
keymap.set("n", "<Leader>gc", "<cmd>Telescope git_commits<cr>", { desc = "Checkout commit" })
keymap.set("n", "<Leader>gC", "<cmd>Telescope git_bcommits<cr>", { desc = "Checkout commit(for current file)" })
keymap.set("n", "<Leader>gd", "<cmd>Gitsigns diffthis HEAD<cr>", { desc = "Git Diff" })
-- <Leader>h
keymap.set("n", "<Leader>h", ":set hlsearch!<CR>", { noremap = true, silent = true, desc = "toggle hlsearch" })
-- <Leader>q
keymap.set("n", "<Leader>qq", "<cmd>qall<cr>", { desc = "quit all" })
-- <Leader>s for search
keymap.set("n", "<Leader>sb", "<cmd>Telescope git_branches<cr>", { desc = "Checkout branch" })
keymap.set("n", "<Leader>sc", "<cmd>nohl<cr>", { desc = "no highlight" })
keymap.set("n", "<Leader>sd", "<cmd>Telescope diagnostics bufnr=0<cr>", { desc = "Document Diagnostics" })
keymap.set("n", "<Leader>sD", "<cmd>Telescope diagnostics<cr>", { desc = "Workspace Diagnostics" })
keymap.set("n", "<Leader>sf", "<cmd>Telescope find_files<cr>", { desc = "Find File" })
keymap.set("n", "<Leader>sr", function()
require("telescope.builtin").oldfiles({ cwd = vim.uv.cwd() })
end, { desc = "Old File" })
keymap.set("n", "<Leader>sm", "<cmd>Telescope marks<cr>", { desc = "Marks" })
keymap.set("n", "<Leader>sM", "<cmd>Telescope man_pages<cr>", { desc = "Man Pages" })
keymap.set("n", "<Leader>sR", "<cmd>Telescope registers<cr>", { desc = "Registers" })
keymap.set("n", "<Leader>st", "<cmd>Telescope live_grep<cr>", { desc = "Text" })
keymap.set("n", "<Leader>s,", function()
local ok, fzf_lua = pcall(require, "fzf-lua")
if ok then
fzf_lua.grep_cword()
else
require("telescope.builtin").grep_string({ word_match = "-w" })
end
end, { desc = "Search word under cursor" })
-- <Leader>t for test
keymap.set("n", "<Leader>tt", '<cmd>lua require("neotest").run.run()<cr>', { desc = "TestNearest" })
keymap.set("n", "<Leader>tf", '<cmd>lua require("neotest").run.run(vim.fn.expand("%"))<cr>', { desc = "TestFile" })
keymap.set("n", "<Leader>tc", "<cmd>GoCover<cr>", { desc = "go coverage" })
keymap.set("n", "<Leader>tC", "<cmd>GoCoverClear<cr>", { desc = "go coverage clear" })
keymap.set("n", "<Leader>tb", '<cmd>lua require("dap").toggle_breakpoint()<cr>', { desc = "set break point" })
keymap.set("n", "<Leader>tr", '<cmd>lua require("dap").continue()<cr>', { desc = "continue" })
-- <Leader>;
keymap.set("n", "<Leader>;", "<cmd>Dashboard<CR>", { noremap = true, silent = true, desc = "Dashboard" })
-- Ctrl-p: find files with recent-first boost
keymap.set("n", "<C-p>", function()
local builtin = require("telescope.builtin")
local recent_paths = {}
for _, file in ipairs(vim.v.oldfiles) do
if vim.fn.filereadable(file) == 1 then
local abs_path = vim.fn.fnamemodify(file, ":p")
local rel_path = vim.fn.fnamemodify(file, ":.")
recent_paths[abs_path] = true
recent_paths[rel_path] = true
recent_paths[vim.fn.fnamemodify(file, ":t")] = true
end
end
local ok, _ = pcall(require, "telescope._extensions.fzf")
if ok then
builtin.find_files({ hidden = true, no_ignore = false })
else
local sorters = require("telescope.sorters")
builtin.find_files({
hidden = true,
no_ignore = false,
sorter = sorters.Sorter:new({
discard = true,
scoring_function = function(_, prompt, line, entry)
local score = 0
local path = entry.path or entry.value or line
local abs_path = vim.fn.fnamemodify(path, ":p")
local rel_path = vim.fn.fnamemodify(path, ":.")
local filename = vim.fn.fnamemodify(path, ":t")
if recent_paths[abs_path] or recent_paths[rel_path] or recent_paths[filename] then
score = 1000
end
if prompt and prompt ~= "" then
if path:lower():find(prompt:lower(), 1, true) then
score = score + 100
end
end
return score
end,
}),
})
end
end, { noremap = true, silent = true, desc = "Find Files (Recent First)" })
-- Window movement
keymap.set("n", "<C-h>", "<C-w>h", { silent = true })
keymap.set("n", "<C-j>", "<C-w>j", { silent = true })
keymap.set("n", "<C-k>", "<C-w>k", { silent = true })
keymap.set("n", "<C-l>", "<C-w>l", { silent = true })
-- Resize with arrows
keymap.set("n", "<C-Up>", ":resize -2<CR>", { silent = true })
keymap.set("n", "<C-Down>", ":resize +2<CR>", { silent = true })
keymap.set("n", "<C-Left>", ":vertical resize -2<CR>", { silent = true })
keymap.set("n", "<C-Right>", ":vertical resize +2<CR>", { silent = true })
-- Quickfix toggle
keymap.set("n", "<C-q>", function()
for _, win in pairs(vim.fn.getwininfo()) do
if win.quickfix == 1 then
vim.cmd("cclose")
return
end
end
vim.cmd("copen")
end, { noremap = true, silent = true, desc = "Toggle Quickfix" })
-- Two spaces → live_grep
keymap.set("n", " ", function()
require("telescope.builtin").live_grep()
end, { noremap = true, silent = true, desc = "Search in Files" })
-- Misc
keymap.set("n", "Y", "y$", {})
keymap.set("v", "<", "<gv", { noremap = true, silent = true })
keymap.set("v", ">", ">gv", { noremap = true, silent = true })
keymap.set("i", "jk", "<ESC>", { noremap = true, silent = true })
keymap.set("i", "kj", "<ESC>", { noremap = true, silent = true })
keymap.set("i", "jj", "<ESC>", { noremap = true, silent = true })
-- Tab switch buffer (barbar)
keymap.set("n", "<TAB>", "<cmd>BufferNext<CR>", { noremap = true, silent = true })
keymap.set("n", "<S-TAB>", "<cmd>BufferPrevious<CR>", { noremap = true, silent = true })
-- Move selected line / block in visual mode
keymap.set("x", "K", ":move '<-2<CR>gv-gv", { noremap = true, silent = true })
keymap.set("x", "J", ":move '>+1<CR>gv-gv", { noremap = true, silent = true })
+47
View File
@@ -0,0 +1,47 @@
local lazypath = vim.fn.stdpath("data") .. "/lazy/lazy.nvim"
if not vim.uv.fs_stat(lazypath) then
-- bootstrap lazy.nvim
-- stylua: ignore
vim.fn.system({ "git", "clone", "--filter=blob:none", "https://github.com/folke/lazy.nvim.git", "--branch=stable", lazypath })
end
vim.opt.rtp:prepend(vim.env.LAZY or lazypath)
require("lazy").setup({
spec = {
-- add LazyVim and import its plugins
-- LazyVim will automatically load extras from lazyvim.json
{ "LazyVim/LazyVim", import = "lazyvim.plugins" },
-- import/override with your plugins
{ import = "plugins" },
},
-- 本机未装 luarocks 时避免 Lazy 健康检查报 ERROR;需要 rocks 依赖的插件时再打开
rocks = {
enabled = false,
},
defaults = {
-- By default, only LazyVim plugins will be lazy-loaded. Your custom plugins will load during startup.
-- If you know what you're doing, you can set this to `true` to have all your custom plugins lazy-loaded by default.
lazy = false,
-- It's recommended to leave version=false for now, since a lot the plugin that support versioning,
-- have outdated releases, which may break your Neovim install.
version = false, -- always use the latest git commit
-- version = "*", -- try installing the latest stable version for plugins that support semver
},
install = { colorscheme = { "tokyonight", "habamax" } },
checker = { enabled = false}, -- automatically check for plugin updates
performance = {
rtp = {
-- disable some rtp plugins
disabled_plugins = {
"gzip",
-- "matchit",
-- "matchparen",
-- "netrwPlugin",
"tarPlugin",
"tohtml",
"tutor",
"zipPlugin",
},
},
},
})
+54
View File
@@ -0,0 +1,54 @@
vim.g.autoformat = true
-- 关闭不常用的远程宿主,减少 :checkhealth 里无意义的 WARNING
vim.g.loaded_perl_provider = 0
vim.g.loaded_ruby_provider = 0
-- 明确指向已安装 pynvim 的解释器
vim.g.python3_host_prog = "/usr/bin/python3"
vim.cmd("set iskeyword+=-") -- treat dash separated words as a word text object
vim.cmd("set shortmess+=c") -- Don't pass messages to |ins-completion-menu|.
vim.cmd("set inccommand=split") -- Make substitution work in realtime
vim.opt.hidden = true -- Required to keep multiple buffers open multiple buffers
vim.opt.title = true
TERMINAL = vim.fn.expand("$TERMINAL")
vim.cmd('let &titleold="' .. TERMINAL .. '"')
vim.opt.titlestring = "%<%F%=%l/%L - nvim"
vim.wo.wrap = true -- Display long lines as just one line
vim.cmd("set whichwrap+=<,>,[,],h,l") -- move to next line with theses keys
vim.opt.pumheight = 10 -- Makes popup menu smaller
vim.opt.fileencoding = "utf-8" -- The encoding written to file
vim.opt.cmdheight = 2 -- More space for displaying messages
vim.opt.mouse = "a" -- Enable your mouse
vim.opt.splitbelow = true -- Horizontal splits will automatically be below
vim.opt.termguicolors = true -- set term gui colors most terminals support this
vim.opt.splitright = true -- Vertical splits will automatically be to the right
vim.opt.conceallevel = 0 -- So that I can see `` in markdown files
vim.o.tabstop = 4
vim.opt.shiftwidth = 4
vim.opt.expandtab = true
vim.opt.smartindent = true -- Makes indenting smart
vim.wo.number = true -- set numbered lines
vim.wo.relativenumber = true -- set relative number
vim.wo.cursorline = true -- Enable highlighting of the current line
vim.opt.showtabline = 2 -- Always show tabs
vim.opt.showmode = false -- We don't need to see things like -- INSERT -- anymore
vim.opt.backup = false
vim.opt.writebackup = false
vim.wo.signcolumn = "yes" -- Always show the signcolumn, otherwise it would shift the text each time
vim.opt.updatetime = 300 -- Faster completion
vim.opt.timeoutlen = 500 -- By default timeoutlen is 1000 ms
vim.opt.clipboard = "unnamedplus" -- Copy paste between vim and everything else
vim.opt.guifont = "FiraCode Nerd Font:h17"
vim.g.vim_markdown_folding_disabled = 1
-- git blame
vim.cmd("highlight default link gitblame SpecialComment")
vim.g.gitblame_enabled = 0
-- vim.cmd('let g:nvcode_termcolors=256')
-- --local cc = 'gruvbox'
-- local cc = 'lunar'
-- vim.opt.background="dark"
-- vim.cmd('colorscheme ' .. cc)
-81
View File
@@ -1,81 +0,0 @@
vim.api.nvim_set_keymap('n', '-', ':RnvimrToggle<CR>', { noremap = true, silent = true })
-- better window movement
vim.api.nvim_set_keymap('n', '<C-h>', '<C-w>h', { silent = true })
vim.api.nvim_set_keymap('n', '<C-j>', '<C-w>j', { silent = true })
vim.api.nvim_set_keymap('n', '<C-k>', '<C-w>k', { silent = true })
vim.api.nvim_set_keymap('n', '<C-l>', '<C-w>l', { silent = true })
-- better edit
vim.api.nvim_set_keymap('n', 'Y', 'y$', {})
-- TODO fix this
-- Terminal window navigation
-- vim.cmd([[
-- tnoremap <C-h> <C-\><C-N><C-w>h
-- tnoremap <C-j> <C-\><C-N><C-w>j
-- tnoremap <C-k> <C-\><C-N><C-w>k
-- tnoremap <C-l> <C-\><C-N><C-w>l
-- inoremap <C-h> <C-\><C-N><C-w>h
-- inoremap <C-j> <C-\><C-N><C-w>j
-- inoremap <C-k> <C-\><C-N><C-w>k
-- inoremap <C-l> <C-\><C-N><C-w>l
-- tnoremap <Esc> <C-\><C-n>
-- ]])
-- TODO fix this
-- resize with arrows
vim.api.nvim_set_keymap('n', '<C-Up>', ':resize -2<CR>', { silent = true })
vim.api.nvim_set_keymap('n', '<C-Down>', ':resize +2<CR>', { silent = true })
vim.api.nvim_set_keymap('n', '<C-Left>', ':vertical resize -2<CR>', { silent = true })
vim.api.nvim_set_keymap('n', '<C-Right>', ':vertical resize +2<CR>', { silent = true })
-- better indenting
vim.api.nvim_set_keymap('v', '<', '<gv', { noremap = true, silent = true })
vim.api.nvim_set_keymap('v', '>', '>gv', { noremap = true, silent = true })
-- I hate escape
vim.api.nvim_set_keymap('i', 'jk', '<ESC>', { noremap = true, silent = true })
vim.api.nvim_set_keymap('i', 'kj', '<ESC>', { noremap = true, silent = true })
vim.api.nvim_set_keymap('i', 'jj', '<ESC>', { noremap = true, silent = true })
-- Tab switch buffer
vim.api.nvim_set_keymap('n', '<TAB>', ':bnext<CR>', { noremap = true, silent = true })
vim.api.nvim_set_keymap('n', '<S-TAB>', ':bprevious<CR>', { noremap = true, silent = true })
-- Move selected line / block of text in visual mode
vim.api.nvim_set_keymap('x', 'K', ':move \'<-2<CR>gv-gv', { noremap = true, silent = true })
vim.api.nvim_set_keymap('x', 'J', ':move \'>+1<CR>gv-gv', { noremap = true, silent = true })
-- Better nav for omnicomplete
vim.cmd('inoremap <expr> <c-j> (\"\\<C-n>\")')
vim.cmd('inoremap <expr> <c-k> (\"\\<C-p>\")')
vim.cmd('vnoremap p "0p')
vim.cmd('vnoremap P "0P')
-- vim.api.nvim_set_keymap('v', 'p', '"0p', {silent = true})
-- vim.api.nvim_set_keymap('v', 'P', '"0P', {silent = true})
-- vim.cmd('inoremap <expr> <TAB> (\"\\<C-n>\")')
-- vim.cmd('inoremap <expr> <S-TAB> (\"\\<C-p>\")')
-- vim.api.nvim_set_keymap('i', '<C-TAB>', 'compe#complete()', {noremap = true, silent = true, expr = true})
-- vim.cmd([[
-- map p <Plug>(miniyank-autoput)
-- map P <Plug>(miniyank-autoPut)
-- map <leader>n <Plug>(miniyank-cycle)
-- map <leader>N <Plug>(miniyank-cycleback)
-- ]])
-- Toggle the QuickFix window
vim.api.nvim_set_keymap('', '<C-q>', ':call QuickFixToggle()<CR>', { noremap = true, silent = true })
-- nvim-compe key map
-- vim.cmd('inoremap <silent><expr> <C-Space> compe#complete()')
-- vim.cmd("inoremap <silent><expr> <CR> compe#confirm('<CR>')")
-- vim.cmd("inoremap <silent><expr> <C-e> compe#close('<C-e>')")
-- vim.cmd("inoremap <silent><expr> <C-f> compe#scroll({ 'delta': +4 })")
-- vim.cmd("inoremap <silent><expr> <C-d> compe#scroll({ 'delta': -4 })")
-6
View File
@@ -1,6 +0,0 @@
-- npm i -g bash-language-server
require'lspconfig'.bashls.setup {
cmd = {DATA_PATH .. "/lspinstall/bash/node_modules/.bin/bash-language-server", "start"},
on_attach = require'lsp'.common_on_attach,
filetypes = { "sh", "zsh" }
}
-8
View File
@@ -1,8 +0,0 @@
-- npm install -g vscode-css-languageserver-bin
require'lspconfig'.cssls.setup {
cmd = {
"node", DATA_PATH .. "/lspinstall/css/vscode-css/css-language-features/server/dist/node/cssServerMain.js",
"--stdio"
},
on_attach = require'lsp'.common_on_attach
}
-6
View File
@@ -1,6 +0,0 @@
-- npm install -g dockerfile-language-server-nodejs
require'lspconfig'.dockerls.setup {
cmd = {DATA_PATH .. "/lspinstall/dockerfile/node_modules/.bin/docker-langserver", "--stdio"},
on_attach = require'lsp'.common_on_attach,
root_dir = vim.loop.cwd
}
-19
View File
@@ -1,19 +0,0 @@
local nvim_lsp = require'lspconfig'
local configs = require'lspconfig/configs'
local capabilities = vim.lsp.protocol.make_client_capabilities()
capabilities.textDocument.completion.completionItem.snippetSupport = true
configs.emmet_ls = {
default_config = {
cmd = {'emmet-ls', '--stdio'};
filetypes = {'html', 'css'};
root_dir = function()
return vim.loop.cwd()
end;
settings = {};
};
}
nvim_lsp.emmet_ls.setup{
-- on_attach = on_attach;
}
-146
View File
@@ -1,146 +0,0 @@
-- LSP
require 'lspconfig'.gopls.setup {
-- cmd = {DATA_PATH .. "/lspinstall/go/gopls"},
cmd = { "gopls", "serve" }, -- use cmd in path, I can update it easily
settings = { gopls = { analyses = { unusedparams = true }, staticcheck = true } },
root_dir = require 'lspconfig'.util.root_pattern(".git", "go.mod", "go.work"),
init_options = { usePlaceholders = true, completeUnimported = true },
on_attach = require 'lsp'.common_on_attach
}
--[[
require'dap-install'.config("go_delve",{})
-- DAP
local dap = require "dap"
vim.fn.sign_define('DapBreakpoint', {text='🟥', texthl='', linehl='', numhl=''})
--vim.fn.sign_define('DapStopped', {text='⭐️', texthl='', linehl='', numhl=''})
local M = {}
local ts_utils = require 'nvim-treesitter.ts_utils'
function M.get_node()
local n = ts_utils.get_node_at_cursor()
put(ts_utils.get_node_text(n))
end
function M.get_node2()
local current_node = ts_utils.get_node_at_cursor()
if not current_node then
return ""
end
local expr = current_node
while expr do
if expr:type() == 'function_definition' then
put(expr:type())
break
end
expr = expr:parent()
end
if not expr then return "" end
put(ts_utils.get_node_text(expr))
return (ts_utils.get_node_text(expr:child(1)))[1]
end
return M
--[[
-- https://github.com/go-delve/delve/blob/master/Documentation/usage/dlv_dap.md
dap.configurations.go = {
{
type = "go",
name = "Debug",
request = "launch",
program = "${file}"
},
-- works with go.mod packages and sub packagesfunction M.get_current_function_name() local current_node = ts_utils.get_node_at_cursor() if not current_node then return "" end
local expr = current_node
while expr do
if expr:type() == 'function_definition' then
break
end
expr = expr:parent()
end
if not expr then return "" end
return (ts_utils.get_node_text(expr:child(1)))[1]
end
{
type = "go",
name = "Debug test (go.mod)",
request = "launch",
mode = "test",
program = "./${relativeFileDirname}"
}
}
--[[
local dap = require('dap')
dap.adapters.go = function(callback, config)
local stdout = vim.loop.new_pipe(false)
local handle
local pid_or_err
local port = 38697
local opts = {
stdio = {nil, stdout},
args = {"dap", "-l", "127.0.0.1:" .. port},
detached = true
}
handle, pid_or_err = vim.loop.spawn("dlv", opts, function(code)
stdout:close()
handle:close()
if code ~= 0 then
print('dlv exited with code', code)
end
end)
assert(handle, 'Error running dlv: ' .. tostring(pid_or_err))
stdout:read_start(function(err, chunk)
assert(not err, err)
if chunk then
vim.schedule(function()
require('dap.repl').append(chunk)
end)
end
end)
-- Wait for delve to start
vim.defer_fn(
function()
callback({type = "server", host = "127.0.0.1", port = port})
end,
100)
end
-- https://github.com/go-delve/delve/blob/master/Documentation/usage/dlv_dap.md
dap.configurations.go = {
{
type = "go",
name = "Debug",
request = "launch",
program = "${file}"
},
{
type = "go",
name = "Debug test", -- configuration for debugging test files
request = "launch",
mode = "test",
program = "${file}"
},
-- works with go.mod packages and sub packages
{
type = "go",
name = "Debug test (go.mod)",
request = "launch",
mode = "test",
program = "./${relativeFileDirname}"
}
}
]]
-9
View File
@@ -1,9 +0,0 @@
-- npm install -g vscode-html-languageserver-bin
local capabilities = vim.lsp.protocol.make_client_capabilities()
capabilities.textDocument.completion.completionItem.snippetSupport = true
require'lspconfig'.html.setup {
cmd = {"node", DATA_PATH .. "/lspinstall/html/vscode-html/html-language-features/server/dist/node/htmlServerMain.js", "--stdio"},
on_attach = require'lsp'.common_on_attach,
capabilities = capabilities
}
-28
View File
@@ -1,28 +0,0 @@
-- npm install -g typescript typescript-language-server
-- require'snippets'.use_suggested_mappings()
-- local capabilities = vim.lsp.protocol.make_client_capabilities()
-- capabilities.textDocument.completion.completionItem.snippetSupport = true;
-- local on_attach_common = function(client)
-- print("LSP Initialized")
-- require'completion'.on_attach(client)
-- require'illuminate'.on_attach(client)
-- end
require'lspconfig'.tsserver.setup {
cmd = {DATA_PATH .. "/lspinstall/typescript/node_modules/.bin/typescript-language-server", "--stdio"},
filetypes = { "javascript", "javascriptreact", "javascript.jsx", "typescript", "typescriptreact", "typescript.tsx" },
on_attach = require'lsp'.tsserver_on_attach,
-- This makes sure tsserver is not used for formatting (I prefer prettier)
-- on_attach = require'lsp'.common_on_attach,
root_dir = require('lspconfig/util').root_pattern("package.json", "tsconfig.json", "jsconfig.json", ".git"),
settings = {documentFormatting = false},
handlers = {
["textDocument/publishDiagnostics"] = vim.lsp.with(vim.lsp.diagnostic.on_publish_diagnostics, {
virtual_text = O.tsserver.diagnostics.virtual_text,
signs = O.tsserver.diagnostics.signs,
underline = O.tsserver.diagnostics.underline,
update_in_insert = true
})
}
}
-16
View File
@@ -1,16 +0,0 @@
-- npm install -g vscode-json-languageserver
require'lspconfig'.jsonls.setup {
cmd = {
"node", DATA_PATH .. "/lspinstall/json/vscode-json/json-language-features/server/dist/node/jsonServerMain.js",
"--stdio"
},
on_attach = require'lsp'.common_on_attach,
commands = {
Format = {
function()
vim.lsp.buf.range_formatting({}, {0, 0}, {vim.fn.line("$"), 0})
end
}
}
}
-4
View File
@@ -1,4 +0,0 @@
require'lspconfig'.texlab.setup{
cmd = {DATA_PATH .. "/lspinstall/latex/texlab"},
on_attach = require'lsp'.common_on_attach
}
-28
View File
@@ -1,28 +0,0 @@
-- https://github.com/sumneko/lua-language-server/wiki/Build-and-Run-(Standalone)
local brew_path = "/usr/local/Cellar/lua-language-server/2.5.5/libexec"
local sumneko_root_path = brew_path
local sumneko_binary = brew_path .. "/bin/lua-language-server"
require'lspconfig'.sumneko_lua.setup {
cmd = {sumneko_binary, "-E", sumneko_root_path .. "/main.lua"},
on_attach = require'lsp'.common_on_attach,
settings = {
Lua = {
runtime = {
-- Tell the language server which version of Lua you're using (most likely LuaJIT in the case of Neovim)
version = 'LuaJIT',
-- Setup your lua path
path = vim.split(package.path, ';')
},
diagnostics = {
-- Get the language server to recognize the `vim` global
globals = {'vim'}
},
workspace = {
-- Make the server aware of Neovim runtime files
library = {[vim.fn.expand('$VIMRUNTIME/lua')] = true, [vim.fn.expand('$VIMRUNTIME/lua/vim/lsp')] = true},
maxPreload = 10000
}
}
}
}
-22
View File
@@ -1,22 +0,0 @@
-- npm i -g pyright
require'lspconfig'.pyright.setup {
cmd = {DATA_PATH .. "/lspinstall/python/node_modules/.bin/pyright-langserver", "--stdio"},
on_attach = require'lsp'.common_on_attach,
handlers = {
["textDocument/publishDiagnostics"] = vim.lsp.with(vim.lsp.diagnostic.on_publish_diagnostics, {
virtual_text = O.python.diagnostics.virtual_text,
signs = O.python.diagnostics.signs,
underline = O.python.diagnostics.underline,
update_in_insert = true
})
},
settings = {
python = {
analysis = {
typeCheckingMode = O.python.analysis.type_checking,
autoSearchPaths = O.python.analysis.auto_search_paths,
useLibraryCodeForTypes = O.python.analysis.use_library_code_types
}
}
}
}
-5
View File
@@ -1,5 +0,0 @@
require'lspconfig'.rust_analyzer.setup{
cmd = {DATA_PATH .. "/lspinstall/rust/rust-analyzer"},
on_attach = require'lsp'.common_on_attach
}
-5
View File
@@ -1,5 +0,0 @@
-- npm install -g yaml-language-server
require'lspconfig'.yamlls.setup{
cmd = {DATA_PATH .. "/lspinstall/yaml/node_modules/.bin/yaml-language-server", "--stdio"},
on_attach = require'lsp'.common_on_attach,
}
-119
View File
@@ -1,119 +0,0 @@
-- TODO figure out why this don't work
vim.fn.sign_define(
"LspDiagnosticsSignError",
{ texthl = "LspDiagnosticsSignError", text = "", numhl = "LspDiagnosticsSignError" }
)
vim.fn.sign_define(
"LspDiagnosticsSignWarning",
{ texthl = "LspDiagnosticsSignWarning", text = "", numhl = "LspDiagnosticsSignWarning" }
)
vim.fn.sign_define(
"LspDiagnosticsSignHint",
{ texthl = "LspDiagnosticsSignHint", text = "", numhl = "LspDiagnosticsSignHint" }
)
vim.fn.sign_define(
"LspDiagnosticsSignInformation",
{ texthl = "LspDiagnosticsSignInformation", text = "", numhl = "LspDiagnosticsSignInformation" }
)
vim.cmd("nnoremap gd <cmd>lua vim.lsp.buf.definition()<CR>")
vim.cmd("nnoremap <silent> gD <cmd>lua vim.lsp.buf.declaration()<CR>")
vim.cmd("nnoremap <silent> gr <cmd>lua vim.lsp.buf.references()<CR>")
vim.cmd("nnoremap <silent> gi <cmd>lua vim.lsp.buf.implementation()<CR>")
vim.cmd("nnoremap <silent> ca :Lspsaga code_action<CR>")
vim.cmd("nnoremap <silent> K :Lspsaga hover_doc<CR>")
-- vim.cmd('nnoremap <silent> <C-k> <cmd>lua vim.lsp.buf.signature_help()<CR>')
vim.cmd("nnoremap <silent> <C-m> :Lspsaga diagnostic_jump_prev<CR>")
vim.cmd("nnoremap <silent> <C-n> :Lspsaga diagnostic_jump_next<CR>")
-- scroll down hover doc or scroll in definition preview
vim.cmd("nnoremap <silent> <C-f> <cmd>lua require('lspsaga.action').smart_scroll_with_saga(1)<CR>")
-- scroll up hover doc
vim.cmd("nnoremap <silent> <C-b> <cmd>lua require('lspsaga.action').smart_scroll_with_saga(-1)<CR>")
vim.cmd('command! -nargs=0 LspVirtualTextToggle lua require("lsp/virtual_text").toggle()')
-- Set Default Prefix.
-- Note: You can set a prefix per lsp server in the lv-globals.lua file
vim.lsp.handlers["textDocument/publishDiagnostics"] = vim.lsp.with(
vim.lsp.diagnostic.on_publish_diagnostics, {
virtual_text = {
prefix = "",
spacing = 0,
},
signs = true,
underline = true,
}
)
-- symbols for autocomplete
vim.lsp.protocol.CompletionItemKind = {
"  (Text) ",
"  (Method)",
"  (Function)",
"  (Constructor)",
" ﴲ (Field)",
"[] (Variable)",
"  (Class)",
" ﰮ (Interface)",
"  (Module)",
" 襁 (Property)",
"  (Unit)",
"  (Value)",
" 練 (Enum)",
"  (Keyword)",
"  (Snippet)",
"  (Color)",
"  (File)",
"  (Reference)",
"  (Folder)",
"  (EnumMember)",
" ﲀ (Constant)",
" ﳤ (Struct)",
"  (Event)",
"  (Operator)",
"  (TypeParameter)"
}
--[[ " autoformat
autocmd BufWritePre *.js lua vim.lsp.buf.formatting_sync(nil, 100)
autocmd BufWritePre *.jsx lua vim.lsp.buf.formatting_sync(nil, 100)
autocmd BufWritePre *.lua lua vim.lsp.buf.formatting_sync(nil, 100) ]]
-- Java
-- autocmd FileType java nnoremap ca <Cmd>lua require('jdtls').code_action()<CR>
local function documentHighlight(client, bufnr)
-- Set autocommands conditional on server_capabilities
if client.resolved_capabilities.document_highlight then
vim.api.nvim_exec(
[[
hi LspReferenceRead cterm=bold ctermbg=red guibg=#464646
hi LspReferenceText cterm=bold ctermbg=red guibg=#464646
hi LspReferenceWrite cterm=bold ctermbg=red guibg=#464646
augroup lsp_document_highlight
autocmd! * <buffer>
autocmd CursorHold <buffer> lua vim.lsp.buf.document_highlight()
autocmd CursorMoved <buffer> lua vim.lsp.buf.clear_references()
augroup END
]] ,
false
)
end
end
local lsp_config = {}
if O.document_highlight then
function lsp_config.common_on_attach(client, bufnr)
documentHighlight(client, bufnr)
end
end
--function lsp_config.tsserver_on_attach(client, bufnr)
-- lsp_config.common_on_attach(client, bufnr)
-- client.resolved_capabilities.document_formatting = false
--end
-- Use a loop to conveniently both setup defined servers
-- and map buffer local keybindings when the language server attaches
-- local servers = {"pyright", "tsserver"}
-- for _, lsp in ipairs(servers) do nvim_lsp[lsp].setup {on_attach = on_attach} end
return lsp_config
-66
View File
@@ -1,66 +0,0 @@
O = {
extras = false,
--database = {save_location = '~/.config/nvcode_db', auto_execute = 1},
python = {
linter = '',
-- @usage can be 'yapf', 'black'
formatter = '',
autoformat = false,
isort = false,
diagnostics = {virtual_text = {spacing = 0, prefix = ""}, signs = true, underline = true},
analysis = {type_checking = "basic", auto_search_paths = true, use_library_code_types = true}
},
dart = {sdk_path = '/usr/lib/dart/bin/snapshots/analysis_server.dart.snapshot'},
lua = {
-- @usage can be 'lua-format'
formatter = '',
autoformat = false,
diagnostics = {virtual_text = {spacing = 0, prefix = ""}, signs = true, underline = true}
},
sh = {
-- @usage can be 'shellcheck'
linter = '',
-- @usage can be 'shfmt'
formatter = '',
autoformat = false,
diagnostics = {virtual_text = {spacing = 0, prefix = ""}, signs = true, underline = true}
},
tsserver = {
-- @usage can be 'eslint'
linter = '',
-- @usage can be 'prettier'
formatter = '',
autoformat = false,
diagnostics = {virtual_text = {spacing = 0, prefix = ""}, signs = true, underline = true}
},
json = {
-- @usage can be 'prettier'
formatter = '',
autoformat = false,
diagnostics = {virtual_text = {spacing = 0, prefix = ""}, signs = true, underline = true}
},
tailwindls = {filetypes = {'html', 'css', 'scss', 'javascript', 'javascriptreact', 'typescript', 'typescriptreact'}},
clang = {diagnostics = {virtual_text = {spacing = 0, prefix = ""}, signs = true, underline = true}},
ruby = {
diagnostics = {virtualtext = {spacing = 0, prefix = ""}, signs = true, underline = true},
filetypes = {'rb', 'erb', 'rakefile'}
},
go = {},
-- css = {formatter = '', autoformat = false, virtual_text = true},
-- json = {formatter = '', autoformat = false, virtual_text = true}
}
function _G.put(...)
local objects = {}
for i = 1, select('#', ...) do
local v = select(i, ...)
table.insert(objects, vim.inspect(v))
end
print(table.concat(objects, '\n'))
return ...
end
-3
View File
@@ -1,3 +0,0 @@
vim.api.nvim_set_keymap('n', '<TAB>', ':BufferNext<CR>', { noremap = true, silent = true })
vim.api.nvim_set_keymap('n', '<S-TAB>', ':BufferPrevious<CR>', { noremap = true, silent = true })
vim.api.nvim_set_keymap('n', '<S-x>', ':BufferClose<CR>', { noremap = true, silent = true })
-63
View File
@@ -1,63 +0,0 @@
--[[
local dashboard = {
custom_header = {
' _..._ ',
' .\' (_`. _ __ ___ ',
' : . : | | _ _ _ __ __ _ _ _\\ \\ / (_)_ __ ___ ',
' :) () : | | | | | | \'_ \\ / _` | \'__\\ \\ / /| | \'_ ` _ \\ ',
' `. . .\' | |__| |_| | | | | (_| | | \\ V / | | | | | | |',
' `-...-\' |_____\\__,_|_| |_|\\__,_|_| \\_/ |_|_| |_| |_|',
},
footer= {'Laily'}
}
]]
local dashboard = {
custom_header = {
'███╗ ██╗██╗ ██╗ ██████╗ ██████╗ ██████╗ ███████╗',
'████╗ ██║██║ ██║██╔════╝██╔═══██╗██╔══██╗██╔════╝',
'██╔██╗ ██║██║ ██║██║ ██║ ██║██║ ██║█████╗',
'██║╚██╗██║╚██╗ ██╔╝██║ ██║ ██║██║ ██║██╔══╝',
'██║ ╚████║ ╚████╔╝ ╚██████╗╚██████╔╝██████╔╝███████╗',
'╚═╝ ╚═══╝ ╚═══╝ ╚═════╝ ╚═════╝ ╚═════╝ ╚══════╝'
},
footer= {'Laily'},
}
-- vim.g.dashboard_custom_header = {
--
-- ' _..._ ',
-- ' .\' `. ██  ██  ██ ███  ██  █████  ██████  ██  ██ ██ ███  ███ ',
-- ' : : ██  ██  ██ ████  ██ ██   ██ ██   ██ ██  ██ ██ ████  ████ ',
-- ' : : ██  ██  ██ ██ ██  ██ ███████ ██████  ██  ██ ██ ██ ████ ██ ',
-- ' `. .\' ██  ██  ██ ██  ██ ██ ██   ██ ██   ██  ██  ██  ██ ██  ██  ██ ',
-- ' `-...-\' ███████  ██████  ██   ████ ██  ██ ██  ██   ████   ██ ██      ██ ',
--
-- }
vim.g.dashboard_custom_header = dashboard.custom_header
vim.g.dashboard_default_executive = 'telescope'
vim.g.dashboard_custom_section = {
a = {description = {' Find File '}, command = 'Telescope find_files'},
b = {description = {' Recently Used Files'}, command = 'Telescope oldfiles'},
c = {description = {' Load Last Session '}, command = 'SessionLoad'},
d = {description = {' Find Word '}, command = 'Telescope live_grep'},
e = {description = {' Settings '}, command = ':e '..CONFIG_PATH..'/lv-settings.lua'}
-- e = {description = {' Marks '}, command = 'Telescope marks'}
}
-- file_browser = {description = {' File Browser'}, command = 'Telescope find_files'},
-- vim.g.dashboard_custom_shortcut = {
-- a = 'f',
-- find_word = 'SPC f a',
-- last_session = 'SPC s l',
-- new_file = 'SPC c n',
-- book_marks = 'SPC f b'
-- }
-- find_history = 'SPC f h',
-- vim.g.dashboard_session_directory = CACHE_PATH..'/session'
vim.g.dashboard_custom_footer = dashboard.footer
-11
View File
@@ -1,11 +0,0 @@
require'fzf-lua'.setup {
fzf_opts = { ['--keep-right'] = ''},
fzf_cli_args = '--keep-right',
--[[
winopts = {
preview = {
layout = "vertical"
}
}
]]
}
-220
View File
@@ -1,220 +0,0 @@
local gl = require('galaxyline')
-- get my theme in galaxyline repo
-- local colors = require('galaxyline.theme').default
local colors = {
-- bg = '#2E2E2E',
bg = '#292D38',
yellow = '#DCDCAA',
dark_yellow = '#D7BA7D',
cyan = '#4EC9B0',
green = '#608B4E',
light_green = '#B5CEA8',
string_orange = '#CE9178',
orange = '#FF8800',
purple = '#C586C0',
magenta = '#D16D9E',
grey = '#858585',
blue = '#569CD6',
vivid_blue = '#4FC1FF',
light_blue = '#9CDCFE',
red = '#D16969',
error_red = '#F44747',
info_yellow = '#FFCC66'
}
local condition = require('galaxyline.condition')
local gls = gl.section
gl.short_line_list = {'NvimTree', 'vista', 'dbui', 'packer'}
table.insert(gls.left, {
ViMode = {
provider = function()
-- auto change color according the vim mode
local mode_color = {
n = colors.blue,
i = colors.green,
v = colors.purple,
[''] = colors.purple,
V = colors.purple,
c = colors.magenta,
no = colors.blue,
s = colors.orange,
S = colors.orange,
[''] = colors.orange,
ic = colors.yellow,
R = colors.red,
Rv = colors.red,
cv = colors.blue,
ce = colors.blue,
r = colors.cyan,
rm = colors.cyan,
['r?'] = colors.cyan,
['!'] = colors.blue,
t = colors.blue
}
vim.api.nvim_command('hi GalaxyViMode guifg=' .. mode_color[vim.fn.mode()])
return '▊ '
end,
highlight = {colors.red, colors.bg}
}
})
print(vim.fn.getbufvar(0, 'ts'))
vim.fn.getbufvar(0, 'ts')
table.insert(gls.left, {
GitIcon = {
provider = function()
return ''
end,
condition = condition.check_git_workspace,
separator = ' ',
separator_highlight = {'NONE', colors.bg},
highlight = {colors.orange, colors.bg}
}
})
table.insert(gls.left, {
GitBranch = {
provider = 'GitBranch',
condition = condition.check_git_workspace,
separator = ' ',
separator_highlight = {'NONE', colors.bg},
highlight = {colors.grey, colors.bg}
}
})
table.insert(gls.left, {
DiffAdd = {
provider = 'DiffAdd',
condition = condition.hide_in_width,
icon = '',
highlight = {colors.green, colors.bg}
}
})
table.insert(gls.left, {
DiffModified = {
provider = 'DiffModified',
condition = condition.hide_in_width,
icon = ' 柳',
highlight = {colors.blue, colors.bg}
}
})
table.insert(gls.left, {
DiffRemove = {
provider = 'DiffRemove',
condition = condition.hide_in_width,
icon = '',
highlight = {colors.red, colors.bg}
}
})
table.insert(gls.right, {
DiagnosticError = {provider = 'DiagnosticError', icon = '', highlight = {colors.error_red, colors.bg}}
})
table.insert(gls.right, {DiagnosticWarn = {provider = 'DiagnosticWarn', icon = '', highlight = {colors.orange, colors.bg}}})
table.insert(gls.right, {
DiagnosticHint = {provider = 'DiagnosticHint', icon = '', highlight = {colors.vivid_blue, colors.bg}}
})
table.insert(gls.right, {DiagnosticInfo = {provider = 'DiagnosticInfo', icon = '', highlight = {colors.info_yellow, colors.bg}}})
table.insert(gls.right, {
TreesitterIcon = {
provider = function()
if next(vim.treesitter.highlighter.active) ~= nil then return '' end
return ''
end,
separator = ' ',
separator_highlight = {'NONE', colors.bg},
highlight = {colors.green, colors.bg}
}
})
table.insert(gls.right, {
ShowLspClient = {
provider = 'GetLspClient',
condition = function()
local tbl = {['dashboard'] = true, [' '] = true}
if tbl[vim.bo.filetype] then return false end
return true
end,
icon = '',
highlight = {colors.grey, colors.bg}
}
})
table.insert(gls.right, {
LineInfo = {
provider = 'LineColumn',
separator = ' ',
separator_highlight = {'NONE', colors.bg},
highlight = {colors.grey, colors.bg}
}
})
table.insert(gls.right, {
PerCent = {
provider = 'LinePercent',
separator = ' ',
separator_highlight = {'NONE', colors.bg},
highlight = {colors.grey, colors.bg}
}
})
table.insert(gls.right, {
Tabstop = {
provider = function()
return "Spaces: " .. vim.api.nvim_buf_get_option(0, "shiftwidth") .. " "
end,
condition = condition.hide_in_width,
separator = ' ',
separator_highlight = {'NONE', colors.bg},
highlight = {colors.grey, colors.bg}
}
})
table.insert(gls.right, {
BufferType = {
provider = 'FileTypeName',
condition = condition.hide_in_width,
separator = ' ',
separator_highlight = {'NONE', colors.bg},
highlight = {colors.grey, colors.bg}
}
})
table.insert(gls.right, {
FileEncode = {
provider = 'FileEncode',
condition = condition.hide_in_width,
separator = ' ',
separator_highlight = {'NONE', colors.bg},
highlight = {colors.grey, colors.bg}
}
})
table.insert(gls.right, {
Space = {
provider = function()
return ' '
end,
separator = ' ',
separator_highlight = {'NONE', colors.bg},
highlight = {colors.orange, colors.bg}
}
})
table.insert(gls.short_line_left, {
BufferType = {
provider = 'FileTypeName',
separator = ' ',
separator_highlight = {'NONE', colors.bg},
highlight = {colors.grey, colors.bg}
}
})
table.insert(gls.short_line_left, {
SFileName = {provider = 'SFileName', condition = condition.buffer_not_empty, highlight = {colors.grey, colors.bg}}
})
--table.insert(gls.short_line_right[1] = {BufferIcon = {provider = 'BufferIcon', highlight = {colors.grey, colors.bg}}})

Some files were not shown because too many files have changed in this diff Show More