Compare commits
185 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
| f9e8a50bd9 | |||
| 39cf5c6d19 | |||
| 59e2356f5c | |||
| 2ede67d1c9 | |||
| 4fc664d243 | |||
| 89ad569581 | |||
| cffe3f01f4 | |||
| 8c80e5b630 | |||
| 2060bbd23a | |||
| 0b2d2b6c6d | |||
| 0b04a4de50 | |||
| 097fce7827 | |||
| a834c46b89 | |||
| 2dedb9573c | |||
| dc302bff1c | |||
| c95a1d70b1 | |||
| d6eee6465b | |||
| 2aba5d7162 | |||
| a0ea27099b | |||
| 8072e344ac | |||
| 4382de9ddc | |||
| 8f2ebfc3de | |||
| 6a92e31a90 | |||
| 045242e2c2 | |||
| 6e57abcec6 | |||
| 7654790973 | |||
| c974d858be | |||
| 5598ad5b3f | |||
| 9617bbbd27 | |||
| 1e12ae5c6e | |||
| b804e42ef1 | |||
| c5d99958d4 | |||
| db8a531b92 | |||
| dc58f0c253 | |||
| a376995947 | |||
| ca88abbfc9 | |||
| b08868cf16 | |||
| 8dcd6e3ac9 | |||
| a43e8bc885 | |||
| c12b212091 | |||
| 0548056ca4 | |||
| acac6c3650 | |||
| 783965d826 | |||
| 9551225ee9 | |||
| 84c7f37c53 | |||
| 67291a9a50 | |||
| 1f6b042b26 | |||
| e8aae9738b | |||
| 0e7fe55474 | |||
| ff420521ac | |||
| 77c9259ade | |||
| 81bebadff8 | |||
| 058c2e58af | |||
| e63107f6ff | |||
| ec19670ebc | |||
| d2f1ca33a0 | |||
| 21b12675e9 | |||
| 609536a15c | |||
| cccf5fd750 | |||
| 64a059b8ec | |||
| 082781b4d2 | |||
| 4e8e3b7073 | |||
| c32e8b4015 | |||
| 137e67503a | |||
| d82c9af650 | |||
| a53a3e0030 | |||
| 01998497e3 | |||
| 6abd1b7185 | |||
| b938a04538 | |||
| 27e5c11ea9 | |||
| 68e6780238 | |||
| e6bff850c3 | |||
| c2439bfe72 | |||
| fd0d59a7df | |||
| 9edcf1ffb4 | |||
| f8635b0969 | |||
| 8e32f5c533 | |||
| 34273ee6d5 | |||
| 34f619c172 | |||
| 1101796d39 | |||
| 6be4389e2e | |||
| 89d808a864 | |||
| 67c5128840 | |||
| 2950f6b442 | |||
| 524dd75eb3 | |||
| 9fd296a777 | |||
| 4ec54aeff7 | |||
| 3bc05c5abb | |||
| 61c1f088c7 | |||
| e4296ed34d | |||
| 478449d3a8 | |||
| df359c6e07 | |||
| 96c923d6cc | |||
| 9587780c13 | |||
| d58bfd63f4 | |||
| ff4b73d8ac | |||
| fba3be8fd8 | |||
| 72fb4fc412 | |||
| 108a8420f9 | |||
| 5e3ed80e39 | |||
| ddc76e7695 | |||
| 9d15bcc3c4 | |||
| f3064c2370 | |||
| e4e6281742 | |||
| 17b983ffb4 | |||
| 7f087b9e5f | |||
| 5a78b07c51 | |||
| 8de370bdf5 | |||
| ab0812037d | |||
| 1fe69f6c95 | |||
| 9bd43f9f9c | |||
| d9bce3b9ee | |||
| 8de75b7faf | |||
| 78b6f9e6c5 | |||
| d80f88dbb4 | |||
| 01029777d5 | |||
| 03b384b819 | |||
| 2bd26fc9ef | |||
| 246f230b7f | |||
| 4bf69165e0 | |||
| 239640a996 | |||
| 2b04c95e48 | |||
| aab4f145ce | |||
| 1189ac8fd1 | |||
| bbb57da4b5 | |||
| 7df9df1801 | |||
| a55e3ca1e2 | |||
| 6c0d9c3d1d | |||
| 21058acac5 | |||
| 490e9eff3a | |||
| ffc4fcd91f | |||
| 0bcae45cd9 | |||
| d72b02118c | |||
| bb752943a5 | |||
| d3f12850bd | |||
| 878d67c8e4 | |||
| 3f7fabc88a | |||
| 5dca7835bf | |||
| cad5e667f8 | |||
| 11aeacdc88 | |||
| d6135e1ac5 | |||
| 8f4ebb0071 | |||
| 5517cd4186 | |||
| f2da334604 | |||
| 0ceb4dc061 | |||
| eddda52c71 | |||
| 3c370d80cc | |||
| 4f10716dcd | |||
| 25871a335b | |||
| 80d0449402 | |||
| 041f993017 | |||
| aec3961983 | |||
| efdbffa489 | |||
| 6ac8de0999 | |||
| dfa04656dd | |||
| 89b7a7c969 | |||
| 12af360c37 | |||
| 66a781c5d0 | |||
| eaa9c6d924 | |||
| ff4629cafe | |||
| 8b609eb959 | |||
| b6dc6b839e | |||
| ddb09fb2cf | |||
| eeb97adf94 | |||
| 67c9d89f41 | |||
| 30dfd1a02d | |||
| 9d0b63a2e9 | |||
| f139016c25 | |||
| a159a4179e | |||
| 8991b192ea | |||
| 8241518dd2 | |||
| 4812514474 | |||
| 3ba2f0b62c | |||
| c22f3b28b0 | |||
| 8433ab381e | |||
| 5a9193eb38 | |||
| dc376f31e3 | |||
| 0247a4c2a0 | |||
| a64b47a65f | |||
| 785d8bde8c | |||
| d97fb14d1f | |||
| 8c5df554ed | |||
| a9f48d2d59 | |||
| 7bb20c64e2 | |||
| fd96710321 |
@@ -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)"
|
||||||
@@ -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)
|
||||||
|
```
|
||||||
|
|
||||||
|
这样可以将配置集中管理,便于维护。
|
||||||
Executable
+35
@@ -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
|
||||||
Executable
+30
@@ -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"
|
||||||
@@ -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/
|
||||||
Executable
+152
@@ -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 "$@"
|
||||||
@@ -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 等均可使用)。
|
||||||
@@ -6,3 +6,7 @@ _set_env_*.sh
|
|||||||
_set_env.sh
|
_set_env.sh
|
||||||
log
|
log
|
||||||
cn.sh
|
cn.sh
|
||||||
|
__pycache__
|
||||||
|
Makefile
|
||||||
|
.claude/
|
||||||
|
script/clash.sh
|
||||||
|
|||||||
-313
@@ -1,313 +0,0 @@
|
|||||||
run:
|
|
||||||
tests: false
|
|
||||||
timeout: 1m
|
|
||||||
linters:
|
|
||||||
enable:
|
|
||||||
- govet
|
|
||||||
- errcheck
|
|
||||||
- staticcheck
|
|
||||||
- ineffassign
|
|
||||||
- typecheck
|
|
||||||
- exportloopref
|
|
||||||
- bodyclose
|
|
||||||
- gofmt
|
|
||||||
- misspell
|
|
||||||
- gosimple
|
|
||||||
- stylecheck
|
|
||||||
- revive
|
|
||||||
- errorlint # finds code that will cause problems with the error wrapping scheme introduced in Go 1.13
|
|
||||||
disable-all: true
|
|
||||||
linters-settings:
|
|
||||||
errcheck:
|
|
||||||
exclude-functions:
|
|
||||||
- io/ioutil.ReadFile
|
|
||||||
- io.Copy(*bytes.Buffer)
|
|
||||||
- io.Copy(os.Stdout)
|
|
||||||
- net.(*)
|
|
||||||
staticcheck:
|
|
||||||
# SAxxxx checks in https://staticcheck.io/docs/configuration/options/#checks
|
|
||||||
checks: ["all"]
|
|
||||||
stylecheck:
|
|
||||||
checks:
|
|
||||||
[
|
|
||||||
"all",
|
|
||||||
"-ST1000",
|
|
||||||
"-ST1001",
|
|
||||||
"-ST1003",
|
|
||||||
"-ST1016",
|
|
||||||
"-ST1020",
|
|
||||||
"-ST1021",
|
|
||||||
"-ST1022",
|
|
||||||
]
|
|
||||||
misspell:
|
|
||||||
# Correct spellings using locale preferences for US or UK.
|
|
||||||
# Setting locale to US will correct the British spelling of 'colour' to 'color'.
|
|
||||||
# Default is to use a neutral variety of English.
|
|
||||||
locale: US
|
|
||||||
ignore-words: []
|
|
||||||
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: false
|
|
||||||
# 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#argument-limit
|
|
||||||
- name: argument-limit
|
|
||||||
severity: warning
|
|
||||||
disabled: false
|
|
||||||
arguments: [6]
|
|
||||||
# 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: ["Ω", "Σ", "σ"]
|
|
||||||
# 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: [14]
|
|
||||||
# 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: [10]
|
|
||||||
# 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#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#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#function-result-limit
|
|
||||||
- name: function-result-limit
|
|
||||||
severity: warning
|
|
||||||
disabled: false
|
|
||||||
arguments: [4]
|
|
||||||
# https://github.com/mgechev/revive/blob/master/RULES_DESCRIPTIONS.md#function-length
|
|
||||||
- name: function-length
|
|
||||||
severity: warning
|
|
||||||
disabled: false
|
|
||||||
arguments: [100, 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#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: true
|
|
||||||
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: true
|
|
||||||
# bytes.Buffer.Write: package name, structure name, function name
|
|
||||||
arguments:
|
|
||||||
- "fmt.Printf"
|
|
||||||
- "os.(Create|WriteFile|Chmod)"
|
|
||||||
- "net.*"
|
|
||||||
- "bytes.Buffer.WriteString"
|
|
||||||
- "con.WriteString"
|
|
||||||
# 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#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
|
|
||||||
-48
@@ -1,48 +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
|
|
||||||
|
|
||||||
# 返回上一个窗口
|
|
||||||
bind g last-window
|
|
||||||
|
|
||||||
# 设置窗口名为当前目录名
|
|
||||||
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
|
|
||||||
@@ -1,153 +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.ustc.edu.cn/brew.git"
|
|
||||||
export HOMEBREW_CORE_GIT_REMOTE="https://mirrors.ustc.edu.cn/homebrew-core.git"
|
|
||||||
export HOMEBREW_BOTTLE_DOMAIN="https://mirrors.ustc.edu.cn/homebrew-bottles"
|
|
||||||
|
|
||||||
# git
|
|
||||||
export REVIEW_BASE="master"
|
|
||||||
|
|
||||||
# go
|
|
||||||
export GOPATH=$HOME/go:$HOME/workrepo/go
|
|
||||||
export PATH=$PATH:$HOME/go/bin
|
|
||||||
export GOPROXY=goproxy.cn
|
|
||||||
export GOSUMDB="sum.golang.google.cn"
|
|
||||||
|
|
||||||
|
|
||||||
[ -f ~/.zprofile ] && source ~/.zprofile
|
|
||||||
[ -f ~/.fzf.zsh ] && source ~/.fzf.zsh
|
|
||||||
test -e /home/linuxbrew/.linuxbrew/ && export PATH=$PATH:/home/linuxbrew/.linuxbrew/bin
|
|
||||||
test -e ~/.pri_scripts && export PATH=$PATH:~/.pri_scripts
|
|
||||||
|
|
||||||
# add z
|
|
||||||
if command -v lua >/dev/null; then
|
|
||||||
eval "$(lua /usr/local/z.lua/z.lua --init zsh)"
|
|
||||||
fi
|
|
||||||
|
|
||||||
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 gci="golangci-lint run -c=~/.dotfile/.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"
|
|
||||||
|
|
||||||
function gtr(){
|
|
||||||
go test -gcflags=all=-l -v -run="$1"
|
|
||||||
}
|
|
||||||
fpath+=${ZDOTDIR:-~}/.zsh_functions
|
|
||||||
@@ -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
|
||||||
|
# ... 正文
|
||||||
|
```
|
||||||
@@ -0,0 +1,3 @@
|
|||||||
|
# Claude Code
|
||||||
|
|
||||||
|
本仓库的 AI / 维护约定见 **[AGENTS.md](./AGENTS.md)**。修改 dotfile、脚本或 init 配置时请遵循该文件。
|
||||||
@@ -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
|
||||||
@@ -1,43 +1,15 @@
|
|||||||
### 使用
|
### 使用
|
||||||
|
|
||||||
```shell
|
```shell
|
||||||
git clone https://github.com/Laily123/.dotfile.git ~/.dotfile
|
git clone https://github.com/hilaily/.dotfile.git ~/.dotfile
|
||||||
```
|
```
|
||||||
|
|
||||||
### zsh 安装
|
首先安装 fish
|
||||||
|
|
||||||
安装 zsh
|
`brew install fish` 或者 `apt install fish`
|
||||||
|
|
||||||
```shell
|
然后初始化 fish
|
||||||
https://github.com/robbyrussell/oh-my-zsh/wiki/Installing-ZSH
|
|
||||||
# macOS
|
```bash
|
||||||
brew install zsh zsh-completions
|
bash ~/.dotfile/script/fish-install.sh
|
||||||
# CentOS
|
|
||||||
sudo yum -y install zsh
|
|
||||||
# Ubuntu
|
|
||||||
apt install zsh
|
|
||||||
```
|
```
|
||||||
|
|
||||||
安装 oh-my-zsh
|
|
||||||
|
|
||||||
```shell
|
|
||||||
sh -c "$(wget https://raw.githubusercontent.com/robbyrussell/oh-my-zsh/master/tools/install.sh -O -)"
|
|
||||||
```
|
|
||||||
|
|
||||||
初始化配置
|
|
||||||
|
|
||||||
```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 ~/`
|
|
||||||
|
|
||||||
#
|
|
||||||
|
|||||||
@@ -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",
|
||||||
|
},
|
||||||
|
]
|
||||||
@@ -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
|
||||||
|
}
|
||||||
@@ -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!"
|
||||||
@@ -0,0 +1,7 @@
|
|||||||
|
{
|
||||||
|
"log-driver": "json-file",
|
||||||
|
"log-opts": {
|
||||||
|
"max-size": "50m",
|
||||||
|
"max-file": "3"
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,5 @@
|
|||||||
|
{
|
||||||
|
"registry-mirrors": [
|
||||||
|
"https://docker.1ms.run"
|
||||||
|
]
|
||||||
|
}
|
||||||
@@ -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"
|
||||||
|
|
||||||
@@ -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"
|
||||||
@@ -0,0 +1,2 @@
|
|||||||
|
# Inherit ssh-copy-id options and host completions
|
||||||
|
complete -c ssh2 -w ssh-copy-id
|
||||||
@@ -1,53 +0,0 @@
|
|||||||
|
|
||||||
fish_vi_key_bindings
|
|
||||||
|
|
||||||
## set environment
|
|
||||||
|
|
||||||
# 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"
|
|
||||||
|
|
||||||
# go
|
|
||||||
set -gx GOPATH $HOME/go:$HOME/workrepo/go
|
|
||||||
set -gx GOPROXY "goproxy.cn,direct"
|
|
||||||
set -gx GOSUMDB "sum.golang.google.cn"
|
|
||||||
|
|
||||||
set -gx PATH $PATH $HOME/go/bin
|
|
||||||
set -gx PATH $PATH $HOME/.yarn/bin $HOME/.config/yarn/global/node_modules/.bin
|
|
||||||
set -gx PATH $PATH /usr/local/sbin
|
|
||||||
|
|
||||||
# add nvim
|
|
||||||
set -gx PATH $PATH /usr/local/nvim/bin /usr/local/osx-64/nvim/bin
|
|
||||||
set -gx EDITOR vim
|
|
||||||
|
|
||||||
# add my script
|
|
||||||
set -gx PATH $PATH $HOME/.dotfile/script $HOME/.app00/script
|
|
||||||
|
|
||||||
# alias
|
|
||||||
# git
|
|
||||||
alias gitb="git branch --show-current | tr -d '\n' | pbcopy"
|
|
||||||
alias gci="golangci-lint run -c=~/.dotfile/.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"
|
|
||||||
|
|
||||||
|
|
||||||
function gtr
|
|
||||||
go test -gcflags=all=-l -v -run=$argv
|
|
||||||
end
|
|
||||||
|
|
||||||
if test -f ~/.config/fish/custom.fish
|
|
||||||
source ~/.config/fish/custom.fish
|
|
||||||
end
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
if status is-interactive
|
|
||||||
# Commands to run in interactive sessions can go here
|
|
||||||
end
|
|
||||||
@@ -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
|
||||||
@@ -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
|
||||||
@@ -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
|
||||||
@@ -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
|
||||||
@@ -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
|
||||||
@@ -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
|
||||||
@@ -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
|
||||||
@@ -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
|
||||||
@@ -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
|
||||||
@@ -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
|
||||||
@@ -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
|
||||||
@@ -1,4 +0,0 @@
|
|||||||
if [ -f "$HOME/.config/fish/config.fish" ]; then
|
|
||||||
mv $HOME/.config/fish/config.fish $HOME/.config/fish/config.fish.bak
|
|
||||||
fi
|
|
||||||
ln -s $HOME/.dotfile/fish/config.fish $HOME/.config/fish/config.fish
|
|
||||||
Executable
+181
@@ -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
|
||||||
@@ -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)
|
||||||
@@ -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
|
||||||
+94
-12
@@ -1,15 +1,18 @@
|
|||||||
|
# 不设全局 user.name / user.email,避免未匹配目录误用身份。
|
||||||
|
# useConfigOnly(Git 2.8+):未配置时拒绝 commit,而不是猜系统用户名。
|
||||||
[user]
|
[user]
|
||||||
name = Laily
|
useConfigOnly = true
|
||||||
email = i@laily.net
|
|
||||||
[core]
|
[core]
|
||||||
autocrlf = input
|
autocrlf = input
|
||||||
quotepath = false
|
quotepath = false
|
||||||
editor = nvim
|
editor = vim
|
||||||
excludesfile = ~/.dotfile/git/.gitignore
|
excludesfile = ~/.dotfile/git/.gitignore
|
||||||
[push]
|
[init]
|
||||||
default = current
|
defaultBranch = main
|
||||||
[pull]
|
[tag]
|
||||||
rebase = false
|
sort = version:refname
|
||||||
|
[column]
|
||||||
|
ui = auto
|
||||||
[color]
|
[color]
|
||||||
status = auto
|
status = auto
|
||||||
diff = auto
|
diff = auto
|
||||||
@@ -17,17 +20,56 @@
|
|||||||
interactive = auto
|
interactive = auto
|
||||||
grep = auto
|
grep = auto
|
||||||
ui = auto
|
ui = auto
|
||||||
[merge]
|
[push]
|
||||||
tool = nvimdiff
|
#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"]
|
[difftool "nvimdiff"]
|
||||||
cmd = nvim -d \"$LOCAL\" \"$REMOTE\"
|
cmd = nvim -d \"$LOCAL\" \"$REMOTE\"
|
||||||
|
[merge]
|
||||||
|
tool = nvimdiff
|
||||||
[mergetool "nvimdiff"]
|
[mergetool "nvimdiff"]
|
||||||
cmd = nvim -d \"$LOCAL\" \"$REMOTE\" -ancestor \"$BASE\" -merge \"$MERGED\"
|
cmd = nvim -d \"$LOCAL\" \"$REMOTE\" -ancestor \"$BASE\" -merge \"$MERGED\"
|
||||||
trustExitCode = true
|
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/"]
|
[includeIf "gitdir:~/workrepo/"]
|
||||||
path = ~/.workgitconfig
|
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]
|
[branch]
|
||||||
autoSetupMerge = always
|
autoSetupMerge = always
|
||||||
|
sort = -committerdate
|
||||||
[alias]
|
[alias]
|
||||||
au = add -u
|
au = add -u
|
||||||
co = checkout
|
co = checkout
|
||||||
@@ -38,9 +80,12 @@
|
|||||||
ba = branch -a
|
ba = branch -a
|
||||||
bm = branch --merged
|
bm = branch --merged
|
||||||
bn = branch --no-merged
|
bn = branch --no-merged
|
||||||
pl = pull
|
pu = "!git pull origin $(git rev-parse --abbrev-ref HEAD)"
|
||||||
pt = push origin --tags
|
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' -
|
ph = push origin HEAD
|
||||||
|
pht = push origin --tags
|
||||||
|
# 把当前分支合进目标分支后回到当前分支;目标已被其它 worktree 占用时在那边 merge
|
||||||
|
merge2 = !$HOME/.dotfile/git/git-merge2.sh
|
||||||
bg = "!f() { git branch | grep $1; }; f"
|
bg = "!f() { git branch | grep $1; }; f"
|
||||||
delbr = "!f(){ git branch -D $(git branch | grep \"\b$1\"); };f"
|
delbr = "!f(){ git branch -D $(git branch | grep \"\b$1\"); };f"
|
||||||
can = commit --amend --no-edit
|
can = commit --amend --no-edit
|
||||||
@@ -48,6 +93,7 @@
|
|||||||
# list files which have changed since REVIEW_BASE
|
# list files which have changed since REVIEW_BASE
|
||||||
# (REVIEW_BASE defaults to 'master' in my zshrc)
|
# (REVIEW_BASE defaults to 'master' in my zshrc)
|
||||||
files = !git diff --name-only $(git merge-base HEAD \"$REVIEW_BASE\")
|
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
|
# Same as above, but with a diff stat instead of just names
|
||||||
# (better for interactive use)
|
# (better for interactive use)
|
||||||
@@ -62,5 +108,41 @@
|
|||||||
# git reviewone foo.js bar.js
|
# git reviewone foo.js bar.js
|
||||||
reviewone = !vim -p +\"tabdo Gdiff $REVIEW_BASE\" +\"let g:gitgutter_diff_base = '$REVIEW_BASE'\"
|
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
|
#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"]
|
[protocol "file"]
|
||||||
allow = always
|
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
|
||||||
|
|||||||
@@ -0,0 +1,3 @@
|
|||||||
|
# macOS 专用(需 Git 2.36+ includeIf ondarwin)
|
||||||
|
[help]
|
||||||
|
autocorrect = prompt
|
||||||
@@ -0,0 +1,3 @@
|
|||||||
|
# Linux 专用(需 Git 2.36+ includeIf onlinux)
|
||||||
|
[help]
|
||||||
|
autocorrect = prompt
|
||||||
@@ -0,0 +1,4 @@
|
|||||||
|
# 个人身份:~/.dotfile、~/code、~/.pouch(由 includeIf gitdir 加载)
|
||||||
|
[user]
|
||||||
|
name = laily
|
||||||
|
email = i@laily.net
|
||||||
+5
-3
@@ -32,6 +32,8 @@ _testmain.go
|
|||||||
.log
|
.log
|
||||||
go.work
|
go.work
|
||||||
go.work.sum
|
go.work.sum
|
||||||
init_test.go
|
_set_env*.sh
|
||||||
_set_env.sh
|
.env*
|
||||||
_set_env_*.sh
|
!.env.example
|
||||||
|
Makefile.local
|
||||||
|
.netrc
|
||||||
|
|||||||
Executable
+3
@@ -0,0 +1,3 @@
|
|||||||
|
#!/bin/bash
|
||||||
|
# 已合并到 git-credential.sh;保留此文件仅为兼容旧配置引用
|
||||||
|
exec "$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)/git-credential.sh" "$@"
|
||||||
Executable
+60
@@ -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
|
||||||
Executable
+72
@@ -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
|
||||||
@@ -0,0 +1 @@
|
|||||||
|
git remote set-url origin git@github.com:hilaily/.dotfile.git
|
||||||
@@ -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$
|
||||||
@@ -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`
|
||||||
@@ -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
|
||||||
@@ -0,0 +1,93 @@
|
|||||||
|
#!/bin/bash
|
||||||
|
# 安装并初始化 Fish、tmux 与 Git 的基础 dotfile 环境
|
||||||
|
|
||||||
|
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"
|
||||||
|
|
||||||
|
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
|
||||||
|
|
||||||
|
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
@@ -0,0 +1,2 @@
|
|||||||
|
defaults write com.googlecode.iterm2.plist LoadPrefsFromCustomFolder -bool true
|
||||||
|
|
||||||
@@ -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'
|
||||||
@@ -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]%})"
|
|
||||||
@@ -0,0 +1,2 @@
|
|||||||
|
# 跨机器共享。本机工具写在 ~/.config/mise/config.toml(mise use -g)
|
||||||
|
[tools]
|
||||||
@@ -2,6 +2,22 @@ if vim.loader then
|
|||||||
vim.loader.enable()
|
vim.loader.enable()
|
||||||
end
|
end
|
||||||
|
|
||||||
|
-- 让 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
|
||||||
|
|
||||||
vim.g.loaded_netrw = 1
|
vim.g.loaded_netrw = 1
|
||||||
vim.g.loaded_netrwPlugin = 1
|
vim.g.loaded_netrwPlugin = 1
|
||||||
|
|
||||||
|
|||||||
+60
-68
@@ -1,73 +1,65 @@
|
|||||||
{
|
{
|
||||||
"FixCursorHold.nvim": { "branch": "master", "commit": "1900f89dc17c603eec29960f57c00bd9ae696495" },
|
"FixCursorHold.nvim": { "branch": "master", "commit": "1900f89dc17c603eec29960f57c00bd9ae696495" },
|
||||||
"LazyVim": { "branch": "main", "commit": "530e94a9fa19577401e968a9673282c3d79f01e3" },
|
"LazyVim": { "branch": "main", "commit": "fca0af57cc3851b14f96a795a9c9bfafc5096dd1" },
|
||||||
"LuaSnip": { "branch": "master", "commit": "b152822e1a4bafb6bdf11a16cc26525cbd95ee00" },
|
"SchemaStore.nvim": { "branch": "main", "commit": "4a0e1b74096937d366031bc1e2b244546af39f8e" },
|
||||||
"SchemaStore.nvim": { "branch": "main", "commit": "8c46453bdf84ad91877effb95e0b6c7b51ea7dda" },
|
"avante.nvim": { "branch": "main", "commit": "e89eb79abf5754645e20aa6074da10ed20bba33c" },
|
||||||
"barbar.nvim": { "branch": "master", "commit": "dd852401ee902745b67fc09a83d113b3fe82a96f" },
|
"barbar.nvim": { "branch": "master", "commit": "53b5a2f34b68875898f0531032fbf090e3952ad7" },
|
||||||
"bufferline.nvim": { "branch": "main", "commit": "73540cb95f8d95aa1af3ed57713c6720c78af915" },
|
"blink-cmp-avante": { "branch": "master", "commit": "4f494c6e124acbe31a8f5d58effa0c14aa38a6d5" },
|
||||||
"catppuccin": { "branch": "main", "commit": "6af91673ceccbd6e073b66c439e7fce239f72d24" },
|
"blink.cmp": { "branch": "main", "commit": "78336bc89ee5365633bcf754d93df01678b5c08f" },
|
||||||
"cmp-buffer": { "branch": "main", "commit": "3022dbc9166796b644a841a02de8dd1cc1d311fa" },
|
"bufferline.nvim": { "branch": "main", "commit": "655133c3b4c3e5e05ec549b9f8cc2894ac6f51b3" },
|
||||||
"cmp-emoji": { "branch": "main", "commit": "e8398e2adf512a03bb4e1728ca017ffeac670a9f" },
|
"catppuccin": { "branch": "main", "commit": "edefef779ab08ce1a4a404713e3012b0d202bd35" },
|
||||||
"cmp-nvim-lsp": { "branch": "main", "commit": "5af77f54de1b16c34b23cba810150689a3a90312" },
|
"codecompanion.nvim": { "branch": "main", "commit": "e0780fa9fda504ffb89307cabcb6cbe1ce8eb60c" },
|
||||||
"cmp-path": { "branch": "main", "commit": "91ff86cd9c29299a64f968ebb45846c485725f23" },
|
"conform.nvim": { "branch": "master", "commit": "086a40dc7ed8242c03be9f47fbcee68699cc2395" },
|
||||||
"cmp_luasnip": { "branch": "master", "commit": "05a9ab28b53f71d1aece421ef32fee2cb857a843" },
|
"dashboard-nvim": { "branch": "master", "commit": "0775e567b6c0be96d01a61795f7b64c1758262f6" },
|
||||||
"conform.nvim": { "branch": "master", "commit": "6dc1603ea408f476a57937bbeaf7f86520a21a98" },
|
"diffview.nvim": { "branch": "main", "commit": "4516612fe98ff56ae0415a259ff6361a89419b0a" },
|
||||||
"dashboard-nvim": { "branch": "master", "commit": "a0a78099658c7d4be3714f657b18ca8285d5d106" },
|
"dressing.nvim": { "branch": "master", "commit": "2d7c2db2507fa3c4956142ee607431ddb2828639" },
|
||||||
"dressing.nvim": { "branch": "master", "commit": "5162edb1442a729a885c45455a07e9a89058be2f" },
|
"flash.nvim": { "branch": "main", "commit": "5f0f270fdc7c5b0c21d903ee85b9cb06f2ac636a" },
|
||||||
"flash.nvim": { "branch": "main", "commit": "7bb4a9c75d1e20cd24185afedeaa11681829ba23" },
|
"friendly-snippets": { "branch": "main", "commit": "6cd7280adead7f586db6fccbd15d2cac7e2188b9" },
|
||||||
"friendly-snippets": { "branch": "main", "commit": "3e9a3f5a0cfcef1741e352c37bda4e82e5eb846a" },
|
"fzf-lua": { "branch": "main", "commit": "c9e7b7bfbd01f949164988ee1684035468e1995c" },
|
||||||
"gitsigns.nvim": { "branch": "main", "commit": "805610a9393fa231f2c2b49cb521bfa413fadb3d" },
|
"gemini-cli.nvim": { "branch": "main", "commit": "c9fd62adda823628f5131a939d9c56ef7a898600" },
|
||||||
"indent-blankline.nvim": { "branch": "master", "commit": "3d08501caef2329aba5121b753e903904088f7e6" },
|
"gitsigns.nvim": { "branch": "main", "commit": "caa98e2e5e4c23a1652c23be839d2e1722cecc29" },
|
||||||
"lazy.nvim": { "branch": "main", "commit": "bef521ac89c8d423f9d092e37b58e8af0c099309" },
|
"grug-far.nvim": { "branch": "main", "commit": "11595bf747edc270bce2069d1020502ad4ae56cf" },
|
||||||
"lsp_signature.nvim": { "branch": "master", "commit": "c6aeb2f1d2538bbdfdaab1664d9d4c3c75aa9db8" },
|
"lazy.nvim": { "branch": "main", "commit": "85c7ff3711b730b4030d03144f6db6375044ae82" },
|
||||||
"lualine.nvim": { "branch": "master", "commit": "0a5a66803c7407767b799067986b4dc3036e1983" },
|
"lazydev.nvim": { "branch": "main", "commit": "ff2cbcba459b637ec3fd165a2be59b7bbaeedf0d" },
|
||||||
"mason-lspconfig.nvim": { "branch": "main", "commit": "05744f0f1967b5757bd05c08df4271ab8ec990aa" },
|
"lsp_signature.nvim": { "branch": "master", "commit": "af7e4074d85d785cf6614352ba9ad3b28a1f8a56" },
|
||||||
"mason-nvim-dap.nvim": { "branch": "main", "commit": "67210c0e775adec55de9826b038e8b62de554afc" },
|
"lualine.nvim": { "branch": "master", "commit": "221ce6b2d999187044529f49da6554a92f740a96" },
|
||||||
"mason.nvim": { "branch": "main", "commit": "751b1fcbf3d3b783fcf8d48865264a9bcd8f9b10" },
|
"mason-lspconfig.nvim": { "branch": "main", "commit": "40276c4df7e6bdce6801d6c035c6227f9115a855" },
|
||||||
"mini.ai": { "branch": "main", "commit": "55e9b7217f29e62f734e239bfeed5731a065d801" },
|
"mason-nvim-dap.nvim": { "branch": "main", "commit": "9a10e096703966335bd5c46c8c875d5b0690dade" },
|
||||||
"mini.bufremove": { "branch": "main", "commit": "931a3bb514147d9e812767275c4beba6b779b1d3" },
|
"mason.nvim": { "branch": "main", "commit": "44d1e90e1f66e077268191e3ee9d2ac97cc18e65" },
|
||||||
"mini.comment": { "branch": "main", "commit": "f9f1a646fd3d9df7397aa1b9550a875fe8189eb0" },
|
"mini.ai": { "branch": "main", "commit": "25248c6aa002391936a6200f12d1466015987133" },
|
||||||
"mini.indentscope": { "branch": "main", "commit": "a8274b6ea2d868198d27bd91a31ed5ea3a6a5744" },
|
"mini.icons": { "branch": "main", "commit": "98faae31e9be1cc054ae63485e58ceb185efcad0" },
|
||||||
"mini.pairs": { "branch": "main", "commit": "5c975d8f68dc1e11bf4b20ced71e7987ed782513" },
|
"mini.pairs": { "branch": "main", "commit": "b1c5a726921b7a8c9321e9a7a208aa0571de5810" },
|
||||||
"mini.surround": { "branch": "main", "commit": "0f528eb2e1bab420c0569d9e52615144c51db920" },
|
"neo-tree.nvim": { "branch": "main", "commit": "f3f3bf73414e400cf9fc13fda50f00404a8f8ab1" },
|
||||||
"neo-tree.nvim": { "branch": "v3.x", "commit": "7aad1bf3f6b849cbf108e02c55ad4d701cb4d33a" },
|
"neotest": { "branch": "master", "commit": "72bc8f1ec62a590fb47368c81f89610e0f353e28" },
|
||||||
"neoconf.nvim": { "branch": "main", "commit": "d4a48c2a6fdeec138605cee4eb6f8c6fafd0b3ed" },
|
"neotest-golang": { "branch": "main", "commit": "58d07a76d714f8f768bbe27711b06d41ca237728" },
|
||||||
"neodev.nvim": { "branch": "main", "commit": "ce9a2e8eaba5649b553529c5498acb43a6c317cd" },
|
"noice.nvim": { "branch": "main", "commit": "7bfd942445fb63089b59f97ca487d605e715f155" },
|
||||||
"neotest": { "branch": "master", "commit": "5caac5cc235d495a2382bc2980630ef36ac87032" },
|
"nui.nvim": { "branch": "main", "commit": "10fc361835c856ba4233ef5ea135b919bf3dce97" },
|
||||||
"neotest-go": { "branch": "main", "commit": "27c80deab9cf5cb30b20ee407b3a6c7285518128" },
|
"nvim-cmp": { "branch": "main", "commit": "f17d9b4394027ff4442b298398dfcaab97e40c4f" },
|
||||||
"neotest-plenary": { "branch": "master", "commit": "dcaf5ed67a9e28a246e9783319e5aa6c9ea1c584" },
|
"nvim-dap": { "branch": "master", "commit": "c9a0738e45f1bd41d792a126941348dce661cf9b" },
|
||||||
"noice.nvim": { "branch": "main", "commit": "f4decbc7a80229ccc9f86026b74bdcf0c39e38a7" },
|
"nvim-dap-go": { "branch": "main", "commit": "b4421153ead5d726603b02743ea40cf26a51ed5f" },
|
||||||
"nui.nvim": { "branch": "main", "commit": "a3597dc88b53489d3fddbddbbd13787355253bb0" },
|
"nvim-dap-ui": { "branch": "master", "commit": "cc9dd33aade7f20bae414d0cba163bc60d4d4b43" },
|
||||||
"nvim-cmp": { "branch": "main", "commit": "cd2cf0c124d3de577fb5449746568ee8e601afc8" },
|
"nvim-dap-virtual-text": { "branch": "master", "commit": "fbdb48c2ed45f4a8293d0d483f7730d24467ccb6" },
|
||||||
"nvim-dap": { "branch": "master", "commit": "5a2f7121869394502521c52b2bc581ab22c69447" },
|
"nvim-lint": { "branch": "master", "commit": "3d55c8f67c6ae5c15e1042571e107c7a3d5c5f4e" },
|
||||||
"nvim-dap-go": { "branch": "main", "commit": "5faf165f5062187320eaf9d177c3c1f647adc22e" },
|
"nvim-lspconfig": { "branch": "master", "commit": "5492a24edd68f78d8a778edcb8905d0a28993abc" },
|
||||||
"nvim-dap-ui": { "branch": "master", "commit": "334cf3038c4756e6ab999cbac67c847fb654c190" },
|
"nvim-nio": { "branch": "master", "commit": "21f5324bfac14e22ba26553caf69ec76ae8a7662" },
|
||||||
"nvim-dap-virtual-text": { "branch": "master", "commit": "3e8e207513e6ef520894950acd76b79902714103" },
|
"nvim-tree.lua": { "branch": "master", "commit": "3603f069ff783c31986894a56c2a6755331b0802" },
|
||||||
"nvim-lint": { "branch": "master", "commit": "861a04313501563bb1b11f125ae9b7237a517b9b" },
|
"nvim-treesitter": { "branch": "main", "commit": "7caec274fd19c12b55902a5b795100d21531391f" },
|
||||||
"nvim-lspconfig": { "branch": "master", "commit": "a3d9395455f2b2e3b50a0b0f37b8b4c23683f44a" },
|
"nvim-treesitter-textobjects": { "branch": "main", "commit": "5c7b0263797dfd1bd6202f2b219f3b53a80b2187" },
|
||||||
"nvim-nio": { "branch": "master", "commit": "8765cbc4d0c629c8158a5341e1b4305fd93c3a90" },
|
"nvim-ts-autotag": { "branch": "main", "commit": "88c1453db4ba7dd24131086fe51fdf74e587d275" },
|
||||||
"nvim-notify": { "branch": "master", "commit": "5371f4bfc1f6d3adf4fe9d62cd3a9d44356bfd15" },
|
"nvim-web-devicons": { "branch": "master", "commit": "d7462543c9e366c0d196c7f67a945eaaf5d99414" },
|
||||||
"nvim-spectre": { "branch": "master", "commit": "4651801ba37a9407b7257287aec45b6653ffc5e9" },
|
"persistence.nvim": { "branch": "main", "commit": "b20b2a7887bd39c1a356980b45e03250f3dce49c" },
|
||||||
"nvim-tree.lua": { "branch": "master", "commit": "64f61e4c913047a045ff90bd188dd3b54ee443cf" },
|
|
||||||
"nvim-treesitter": { "branch": "master", "commit": "3607d9c59cb16ed5368ecfed53e8ca1735bb98fb" },
|
|
||||||
"nvim-treesitter-context": { "branch": "master", "commit": "2650e6431f7daba5d9c2c64134fa5eb2312eb3d7" },
|
|
||||||
"nvim-treesitter-textobjects": { "branch": "master", "commit": "23b820146956b3b681c19e10d3a8bc0cbd9a1d4c" },
|
|
||||||
"nvim-ts-autotag": { "branch": "main", "commit": "531f48334c422222aebc888fd36e7d109cb354cd" },
|
|
||||||
"nvim-ts-context-commentstring": { "branch": "main", "commit": "0bdccb9c67a42a5e2d99384dc9bfa29b1451528f" },
|
|
||||||
"nvim-web-devicons": { "branch": "master", "commit": "5b9067899ee6a2538891573500e8fd6ff008440f" },
|
|
||||||
"persistence.nvim": { "branch": "main", "commit": "4982499c1636eac254b72923ab826ee7827b3084" },
|
|
||||||
"playground": { "branch": "master", "commit": "ba48c6a62a280eefb7c85725b0915e021a1a0749" },
|
"playground": { "branch": "master", "commit": "ba48c6a62a280eefb7c85725b0915e021a1a0749" },
|
||||||
"plenary.nvim": { "branch": "master", "commit": "08e301982b9a057110ede7a735dd1b5285eb341f" },
|
"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" },
|
"symbols-outline.nvim": { "branch": "master", "commit": "564ee65dfc9024bdde73a6621820866987cbb256" },
|
||||||
"telescope-file-browser.nvim": { "branch": "master", "commit": "4d5fd21bae12ee6e9a79232e1c377f43c419d0c5" },
|
"telescope-file-browser.nvim": { "branch": "master", "commit": "3610dc7dc91f06aa98b11dca5cc30dfa98626b7e" },
|
||||||
"telescope-fzf-native.nvim": { "branch": "main", "commit": "9ef21b2e6bb6ebeaf349a0781745549bbb870d27" },
|
"telescope-fzf-native.nvim": { "branch": "main", "commit": "6fea601bd2b694c6f2ae08a6c6fab14930c60e2c" },
|
||||||
"telescope.nvim": { "branch": "master", "commit": "fac83a556e7b710dc31433dec727361ca062dbe9" },
|
"telescope.nvim": { "branch": "master", "commit": "40aedd8a68c78a656a10a8d62d80c54af59420fb" },
|
||||||
"todo-comments.nvim": { "branch": "main", "commit": "a7e39ae9e74f2c8c6dc4eea6d40c3971ae84752d" },
|
"todo-comments.nvim": { "branch": "main", "commit": "31e3c38ce9b29781e4422fc0322eb0a21f4e8668" },
|
||||||
"tokyonight.nvim": { "branch": "main", "commit": "67afeaf7fd6ebba000633e89f63c31694057edde" },
|
"tokyonight.nvim": { "branch": "main", "commit": "cdc07ac78467a233fd62c493de29a17e0cf2b2b6" },
|
||||||
"trouble.nvim": { "branch": "main", "commit": "b9cf677f20bb2faa2dacfa870b084e568dca9572" },
|
"trouble.nvim": { "branch": "main", "commit": "bd67efe408d4816e25e8491cc5ad4088e708a69a" },
|
||||||
"vim-illuminate": { "branch": "master", "commit": "e522e0dd742a83506db0a72e1ced68c9c130f185" },
|
"ts-comments.nvim": { "branch": "main", "commit": "a59d6092213447450191122c9346f309161504cb" },
|
||||||
"vim-startuptime": { "branch": "master", "commit": "ac2cccb5be617672add1f4f3c0a55ce99ba34e01" },
|
"which-key.nvim": { "branch": "main", "commit": "3aab2147e74890957785941f0c1ad87d0a44c15a" }
|
||||||
"vim-vsnip": { "branch": "master", "commit": "02a8e79295c9733434aab4e0e2b8c4b7cea9f3a9" },
|
|
||||||
"vim-vsnip-integ": { "branch": "master", "commit": "1914e72cf3de70df7f5dde476cd299aba2440aef" },
|
|
||||||
"which-key.nvim": { "branch": "main", "commit": "4433e5ec9a507e5097571ed55c02ea9658fb268a" }
|
|
||||||
}
|
}
|
||||||
+10
-3
@@ -1,10 +1,17 @@
|
|||||||
{
|
{
|
||||||
"extras": [
|
"extras": [
|
||||||
|
"lazyvim.plugins.extras.editor.telescope",
|
||||||
"lazyvim.plugins.extras.test.core",
|
"lazyvim.plugins.extras.test.core",
|
||||||
"lazyvim.plugins.extras.lang.go"
|
"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": {
|
||||||
"NEWS.md": "3314"
|
"NEWS.md": "11866"
|
||||||
},
|
},
|
||||||
"version": 3
|
"version": 8
|
||||||
}
|
}
|
||||||
@@ -1,3 +1,7 @@
|
|||||||
|
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
|
-- Turn off paste mode when leaving insert
|
||||||
vim.api.nvim_create_autocmd("InsertLeave", {
|
vim.api.nvim_create_autocmd("InsertLeave", {
|
||||||
pattern = "*",
|
pattern = "*",
|
||||||
@@ -13,6 +17,6 @@ vim.api.nvim_create_autocmd("FileType", {
|
|||||||
vim.bo.tabstop = 2
|
vim.bo.tabstop = 2
|
||||||
vim.bo.softtabstop = 2
|
vim.bo.softtabstop = 2
|
||||||
vim.bo.shiftwidth = 2
|
vim.bo.shiftwidth = 2
|
||||||
vim.bo.expand = true
|
-- vim.bo.expand = true
|
||||||
end,
|
end,
|
||||||
})
|
})
|
||||||
|
|||||||
+156
-140
@@ -1,164 +1,180 @@
|
|||||||
|
|
||||||
local keymap = vim.keymap
|
local keymap = vim.keymap
|
||||||
local opts = { noremap = true, silent = true }
|
|
||||||
|
|
||||||
-- <Leader>a
|
|
||||||
keymap.set('n', "<Leader>aa", "<cmd>@:<cr>", {desc="execute last command again" })
|
|
||||||
|
|
||||||
-- <Leader>b for buffer
|
-- <Leader>b for buffer
|
||||||
keymap.set('n', "<Leader>bq", "<cmd>q<cr>", {desc="quit" })
|
keymap.set("n", "<Leader>bq", "<cmd>q<cr>", { desc = "quit" })
|
||||||
keymap.set('n', "<Leader>bs", "<cmd>w<cr>", {desc="save" })
|
keymap.set("n", "<Leader>bs", "<cmd>w<cr>", { desc = "save" })
|
||||||
keymap.set('n', "<Leader>br", "<cmd>e!<cr>", {desc="reload" })
|
keymap.set("n", "<Leader>br", "<cmd>e!<cr>", { desc = "reload" })
|
||||||
keymap.set('n', "<Leader>bd", "<cmd>BufferClose<cr>", {desc="close buffer" })
|
keymap.set("n", "<Leader>bd", "<cmd>BufferClose<cr>", { desc = "close buffer" })
|
||||||
keymap.set({'n','v'}, "<Leader>b/", "<cmd>CommentToggle<CR>", {desc="toggle comment" })
|
|
||||||
|
|
||||||
-- <Leader>c
|
-- <Leader>c
|
||||||
keymap.set('n', "<Leader>cs", "<cmd>SymbolsOutline<cr>", { desc = "Symbols Outline" })
|
keymap.set("n", "<Leader>cs", "<cmd>SymbolsOutline<cr>", { desc = "Symbols Outline" })
|
||||||
|
|
||||||
-- <Leader>D
|
-- <Leader>D for Trouble (v3 syntax)
|
||||||
keymap.set('n', "<Leader>Dt", "<cmd>TroubleToggle<cr>", {desc="trouble" })
|
keymap.set("n", "<Leader>Dt", "<cmd>Trouble diagnostics toggle<cr>", { desc = "trouble diagnostics" })
|
||||||
keymap.set('n', "<Leader>Dw", "<cmd>TroubleToggle lsp_workspace_diagnostics<cr>", {desc="workspace" })
|
keymap.set("n", "<Leader>Dd", "<cmd>Trouble diagnostics toggle filter.buf=0<cr>", { desc = "document diagnostics" })
|
||||||
keymap.set('n', "<Leader>Dd", "<cmd>TroubleToggle lsp_document_diagnostics<cr>", {desc= "document" })
|
keymap.set("n", "<Leader>Dq", "<cmd>Trouble qflist toggle<cr>", { desc = "quickfix" })
|
||||||
keymap.set('n', "<Leader>Dq", "<cmd>TroubleToggle quickfix<cr>", {desc= "quickfix" })
|
keymap.set("n", "<Leader>Dl", "<cmd>Trouble loclist toggle<cr>", { desc = "loclist" })
|
||||||
keymap.set('n', "<Leader>Dl", "<cmd>TroubleToggle loclist<cr>", {desc= "loclist" })
|
keymap.set("n", "<Leader>Dr", "<cmd>Trouble lsp_references toggle<cr>", { desc = "references" })
|
||||||
keymap.set('n', "<Leader>Dr", "<cmd>TroubleToggle lsp_references<cr>", {desc= "references" })
|
|
||||||
|
|
||||||
-- <Leader>d
|
|
||||||
keymap.set('n', "<Leader>dd", "<cmd>lua vim.lsp.diagnostic.show_line_diagnostics()<cr>", {desc= "show diagnostics" })
|
|
||||||
keymap.set('n', "<Leader>dc", "<cmd>lua require'dap'.continue()<cr>", {desc= "continue" })
|
|
||||||
keymap.set('n', "<Leader>db", ':lua require"dap".toggle_breakpoint()<CR>', {desc= "Toggle Breadpoint" })
|
|
||||||
keymap.set('n', "<Leader>dj", ':lua require"dap".step_over()<CR>', {desc= "step over" })
|
|
||||||
keymap.set('n', "<Leader>dk", ':lua require"dap".step_out()<CR>', {desc= "step out" })
|
|
||||||
keymap.set('n', "<Leader>dl", ':lua require"dap".step_into()<CR>', {desc= "step into" })
|
|
||||||
keymap.set('n', "<Leader>du", ':lua require"dap".up()<CR>', {desc= "up" })
|
|
||||||
keymap.set('n', "<Leader>d_", ':lua require"dap".disconnect();require"dap".stop();require"dap".run_last()<CR>', {desc= "disconnect" })
|
|
||||||
keymap.set('n', "<Leader>di", ':lua require"dap.ui.variables".visual_hover()<CR>', {desc= "visual_hover" })
|
|
||||||
keymap.set('n', "<Leader>ds", ':lua local widgets=require"dap.ui.widgets";widgets.centered_float(widgets.scopes)<CR>', {desc= "centered_float" })
|
|
||||||
|
|
||||||
|
-- <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
|
-- <Leader>f
|
||||||
keymap.set('n', "<Leader>fm", "<cmd>lua require'utils'.open_md()<cr>", {desc= "open a markdown file in vscode" })
|
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>fs", "<cmd>w<cr>", { desc = "save file" })
|
||||||
keymap.set('n', "<Leader>ft", "<cmd>NvimTreeToggle<cr>", {desc= "toggle nvim tree" })
|
keymap.set("n", "<Leader>ft", "<cmd>NvimTreeToggle<cr>", { desc = "toggle nvim tree" })
|
||||||
keymap.set('n', "<Leader>fr", "<cmd>RnvimrToggle<cr>", {desc= "toggle rnvimr" })
|
keymap.set("n", "<Leader>fy", "<cmd>lua require('utils').yank_filepath()<cr>", { desc = "copy file path" })
|
||||||
keymap.set('n', "<Leader>fy", "<cmd>lua require'utils'.yank_filepath()<cr>", {desc= "copy the file path" })
|
keymap.set("n", "<Leader>fd", "<cmd>lua require('utils').yank_file_dir()<cr>", { desc = "copy dir path" })
|
||||||
keymap.set('n', "<Leader>fd", "<cmd>lua require'utils'.yank_file_dir()<cr>", {desc= "copy the file path" })
|
keymap.set("n", "<Leader>fl", "<cmd>NvimTreeFindFile<CR>", { desc = "find file in tree" })
|
||||||
keymap.set('n', "<Leader>fl", "<cmd>NvimTreeFindFile<CR>", {desc= "find file" })
|
|
||||||
|
|
||||||
|
|
||||||
-- <Leader>g
|
-- <Leader>g
|
||||||
keymap.set('n', "<Leader>gj", "<cmd>lua require 'gitsigns'.next_hunk()<cr>", {desc= "Next Hunk" })
|
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>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>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>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_hunk()<cr>", { desc = "Reset Hunk" })
|
||||||
keymap.set('n', "<Leader>gR", "<cmd>lua require 'gitsigns'.reset_buffer()<cr>", {desc= "Reset Buffer" })
|
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_hunk()<cr>", { desc = "Stage Hunk" })
|
||||||
keymap.set('n', "<Leader>gS", "<cmd>lua require 'gitsigns'.stage_buffer()<cr>", {desc= "Stage Buffer" })
|
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>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>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>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_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>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", })
|
keymap.set("n", "<Leader>gd", "<cmd>Gitsigns diffthis HEAD<cr>", { desc = "Git Diff" })
|
||||||
|
|
||||||
-- <Leader>h
|
-- <Leader>h
|
||||||
keymap.set('n', '<Leader>h', ':set hlsearch!<CR>', { noremap = true, silent = true }) -- no hl
|
keymap.set("n", "<Leader>h", ":set hlsearch!<CR>", { noremap = true, silent = true, desc = "toggle hlsearch" })
|
||||||
|
|
||||||
-- <Leader>l for lsp
|
|
||||||
keymap.set('n', '<Leader>l', "", {desc="+Language"})
|
|
||||||
keymap.set({'n','v'}, '<Leader>la', "<cmd>lua vim.lsp.buf.code_action()<cr>", {desc="Code Action" })
|
|
||||||
--keymap.set('n', '<Leader>lA', "<cmd>Lspsaga range_code_action<cr>", {desc="Selected Action" })
|
|
||||||
keymap.set('n', '<Leader>ld', "<cmd>Telescope lsp_document_diagnostics<cr>", {desc="Document Diagnostics" })
|
|
||||||
keymap.set('n', '<Leader>lD', "<cmd>Telescope lsp_workspace_diagnostics<cr>", {desc="Workspace Diagnostics" })
|
|
||||||
keymap.set('n', '<Leader>lf', "<cmd>LspFormatting<cr>", {desc="Format" })
|
|
||||||
--keymap.set('n', '<Leader>lF', "<cmd>Lspsaga lsp_finder<cr>", {desc="LSP Finder" })
|
|
||||||
keymap.set('n', '<Leader>li', "<cmd>lua vim.lsp.buf.implementation()<cr>", {desc="implement" })
|
|
||||||
keymap.set('n', '<Leader>ln', "<cmd>lua vim.lsp.buf.hover()<cr>", {desc="hover" })
|
|
||||||
--keymap.set('n', '<Leader>lL', "<cmd>Lspsaga show_line_diagnostics<cr>", {desc="Line Diagnostics" })
|
|
||||||
keymap.set('n', '<Leader>lk', "<cmd>: LspRestart<cr>", {desc="restart lsp server" })
|
|
||||||
keymap.set('n', '<Leader>ll', "<cmd>lua vim.diagnostic.goto_next()<cr>", {desc="next diagnostic" })
|
|
||||||
keymap.set('n', '<Leader>lo', "<cmd>: SymbolsOutline<cr>", {desc="symbols outline" })
|
|
||||||
keymap.set('n', '<Leader>lp', "<cmd>lua vim.diagnostic.goto_prev()<cr>", {desc="prev diagnostic" })
|
|
||||||
--keymap.set('n', '<Leader>lP', "<cmd>Lspsaga preview_definition<cr>", {desc="Preview Definition" })
|
|
||||||
keymap.set('n', '<Leader>lq', "<cmd>Telescope quickfix<cr>", {desc="Quickfix" })
|
|
||||||
keymap.set('n', '<Leader>lr', "<cmd>lua vim.lsp.buf.references()<cr>", {desc="references" })
|
|
||||||
keymap.set('n', '<leader>lR', "<cmd>lua vim.lsp.buf.rename()<cr>", { desc = "Rename" })
|
|
||||||
--keymap.set('n', '<Leader>lR', "<cmd>Lspsaga rename<cr>", {desc="Rename" })
|
|
||||||
keymap.set('n', '<Leader>lt', "<cmd>LspTypeDefinition<cr>", {desc="Type Definition" })
|
|
||||||
keymap.set('n', '<Leader>lx', "<cmd>cclose<cr>", {desc="Close Quickfix" })
|
|
||||||
keymap.set('n', '<Leader>ls', "<cmd>Telescope lsp_document_symbols<cr>", {desc="Document Symbols" })
|
|
||||||
keymap.set('n', '<Leader>lS', "<cmd>Telescope lsp_dynamic_workspace_symbols<cr>", {desc="Workspace Symbols" })
|
|
||||||
keymap.set('n', '<Leader>lz', "<cmd>LspInfo<cr>", {desc="Info" })
|
|
||||||
keymap.set('n', '<Leader>l,', "<cmd>LspInstallInfo<cr>", {desc="LSP servers" })
|
|
||||||
|
|
||||||
-- <Leader>q
|
-- <Leader>q
|
||||||
keymap.set('n', "<Leader>qq", "<cmd>qall<cr>", {desc= "quit" })
|
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>s
|
-- <Leader>t for test
|
||||||
keymap.set('n', "<Leader>sb", "<cmd>Telescope git_branches<cr>", {desc= "Checkout branch" })
|
keymap.set("n", "<Leader>tt", '<cmd>lua require("neotest").run.run()<cr>', { desc = "TestNearest" })
|
||||||
keymap.set('n', "<Leader>sc", "<cmd>:nohl<cr>", {desc= "no highlight" })
|
keymap.set("n", "<Leader>tf", '<cmd>lua require("neotest").run.run(vim.fn.expand("%"))<cr>', { desc = "TestFile" })
|
||||||
keymap.set('n', "<Leader>sd", "<cmd>Telescope lsp_document_diagnostics<cr>", {desc= "Document Diagnostics" })
|
keymap.set("n", "<Leader>tc", "<cmd>GoCover<cr>", { desc = "go coverage" })
|
||||||
keymap.set('n', "<Leader>sD", "<cmd>Telescope lsp_workspace_diagnostics<cr>", {desc= "Workspace Diagnostics" })
|
keymap.set("n", "<Leader>tC", "<cmd>GoCoverClear<cr>", { desc = "go coverage clear" })
|
||||||
keymap.set('n', "<Leader>sf", "<cmd>Telescope find_files<CR><cr>", {desc= "Find File" })
|
keymap.set("n", "<Leader>tb", '<cmd>lua require("dap").toggle_breakpoint()<cr>', { desc = "set break point" })
|
||||||
keymap.set('n', "<Leader>sr", function() require("telescope.builtin").oldfiles({ cwd = vim.loop.cwd() }) end, {desc= "Old File" })
|
keymap.set("n", "<Leader>tr", '<cmd>lua require("dap").continue()<cr>', { desc = "continue" })
|
||||||
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>sT", "<cmd>Telescope live_grep<cr>", {desc= "Text" })
|
|
||||||
keymap.set('n', "<Leader>s,", "<cmd>FzfLua grep_cword<cr>", {desc= "Search word under cursor" })
|
|
||||||
|
|
||||||
-- <Leader>S
|
|
||||||
keymap.set('n', "<Leader>Ss", "<cmd>SessionSave<cr>", {desc= "Save Session" })
|
|
||||||
keymap.set('n', "<Leader>Sl", "<cmd>SessionLoad<cr>", {desc= "Load Session" })
|
|
||||||
|
|
||||||
-- <Leader>t
|
|
||||||
keymap.set('n', "<Leader>ta", "<cmd>TestSuite -v -cover<cr>", {desc= "TestSuite" })
|
|
||||||
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" })
|
|
||||||
keymap.set('n', "<Leader>zs", "<cmd>TZBottom<cr>", {desc="toggle status line" })
|
|
||||||
keymap.set('n', "<Leader>zt", "<cmd>TZTop<cr>", {desc= "toggle tab bar" })
|
|
||||||
keymap.set('n', "<Leader>zz", "<cmd>TZAtaraxis<cr>", {desc= "toggle zen" })
|
|
||||||
|
|
||||||
-- <Leader>;
|
-- <Leader>;
|
||||||
keymap.set('n', '<Leader>;', ':Dashboard<CR>', { noremap = true, silent = true })
|
keymap.set("n", "<Leader>;", "<cmd>Dashboard<CR>", { noremap = true, silent = true, desc = "Dashboard" })
|
||||||
|
|
||||||
-- Ctrl
|
-- Ctrl-p: find files with recent-first boost
|
||||||
keymap.set('n', '<C-p>', ":Telescope find_files<CR>", { noremap = true, silent = true })
|
keymap.set("n", "<C-p>", function()
|
||||||
-- better window movement
|
local builtin = require("telescope.builtin")
|
||||||
keymap.set('n', '<C-h>', '<C-w>h', { silent = true })
|
local recent_paths = {}
|
||||||
keymap.set('n', '<C-j>', '<C-w>j', { silent = true })
|
for _, file in ipairs(vim.v.oldfiles) do
|
||||||
keymap.set('n', '<C-k>', '<C-w>k', { silent = true })
|
if vim.fn.filereadable(file) == 1 then
|
||||||
keymap.set('n', '<C-l>', '<C-w>l', { silent = true })
|
local abs_path = vim.fn.fnamemodify(file, ":p")
|
||||||
-- resize with arrows
|
local rel_path = vim.fn.fnamemodify(file, ":.")
|
||||||
keymap.set('n', '<C-Up>', ':resize -2<CR>', { silent = true })
|
recent_paths[abs_path] = true
|
||||||
keymap.set('n', '<C-Down>', ':resize +2<CR>', { silent = true })
|
recent_paths[rel_path] = true
|
||||||
keymap.set('n', '<C-Left>', ':vertical resize -2<CR>', { silent = true })
|
recent_paths[vim.fn.fnamemodify(file, ":t")] = true
|
||||||
keymap.set('n', '<C-Right>', ':vertical resize +2<CR>', { silent = 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)" })
|
||||||
|
|
||||||
keymap.set('n', '<C-q>', ':call QuickFixToggle()<CR>', { noremap = true, silent = true })
|
-- 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 })
|
||||||
|
|
||||||
-- Other
|
-- Quickfix toggle
|
||||||
-- better edit
|
keymap.set("n", "<C-q>", function()
|
||||||
keymap.set('n', 'Y', 'y$', {})
|
for _, win in pairs(vim.fn.getwininfo()) do
|
||||||
-- better indenting
|
if win.quickfix == 1 then
|
||||||
keymap.set('v', '<', '<gv', { noremap = true, silent = true })
|
vim.cmd("cclose")
|
||||||
keymap.set('v', '>', '>gv', { noremap = true, silent = true })
|
return
|
||||||
-- I hate escape
|
end
|
||||||
keymap.set('i', 'jk', '<ESC>', { noremap = true, silent = true })
|
end
|
||||||
keymap.set('i', 'kj', '<ESC>', { noremap = true, silent = true })
|
vim.cmd("copen")
|
||||||
keymap.set('i', 'jj', '<ESC>', { noremap = true, silent = true })
|
end, { noremap = true, silent = true, desc = "Toggle Quickfix" })
|
||||||
-- Tab switch buffer
|
|
||||||
keymap.set('n', '<TAB>', ':BufferNext<CR>', { noremap = true, silent = true })
|
|
||||||
keymap.set('n', '<S-TAB>', ':BufferPrevious<CR>', { noremap = true, silent = true })
|
|
||||||
-- Move selected line / block of text 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 })
|
|
||||||
|
|
||||||
|
-- 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 })
|
||||||
|
|||||||
@@ -1,5 +1,5 @@
|
|||||||
local lazypath = vim.fn.stdpath("data") .. "/lazy/lazy.nvim"
|
local lazypath = vim.fn.stdpath("data") .. "/lazy/lazy.nvim"
|
||||||
if not vim.loop.fs_stat(lazypath) then
|
if not vim.uv.fs_stat(lazypath) then
|
||||||
-- bootstrap lazy.nvim
|
-- bootstrap lazy.nvim
|
||||||
-- stylua: ignore
|
-- stylua: ignore
|
||||||
vim.fn.system({ "git", "clone", "--filter=blob:none", "https://github.com/folke/lazy.nvim.git", "--branch=stable", lazypath })
|
vim.fn.system({ "git", "clone", "--filter=blob:none", "https://github.com/folke/lazy.nvim.git", "--branch=stable", lazypath })
|
||||||
@@ -9,17 +9,15 @@ vim.opt.rtp:prepend(vim.env.LAZY or lazypath)
|
|||||||
require("lazy").setup({
|
require("lazy").setup({
|
||||||
spec = {
|
spec = {
|
||||||
-- add LazyVim and import its plugins
|
-- add LazyVim and import its plugins
|
||||||
|
-- LazyVim will automatically load extras from lazyvim.json
|
||||||
{ "LazyVim/LazyVim", import = "lazyvim.plugins" },
|
{ "LazyVim/LazyVim", import = "lazyvim.plugins" },
|
||||||
-- import any extras modules here
|
|
||||||
{ import = "lazyvim.plugins.extras.lang.typescript" },
|
|
||||||
{ import = "lazyvim.plugins.extras.lang.json" },
|
|
||||||
{ import = "lazyvim.plugins.extras.lang.go" },
|
|
||||||
{ import = "lazyvim.plugins.extras.dap.core" },
|
|
||||||
{ import = "lazyvim.plugins.extras.formatting.prettier" },
|
|
||||||
-- { import = "lazyvim.plugins.extras.ui.mini-animate" },
|
|
||||||
-- import/override with your plugins
|
-- import/override with your plugins
|
||||||
{ import = "plugins" },
|
{ import = "plugins" },
|
||||||
},
|
},
|
||||||
|
-- 本机未装 luarocks 时避免 Lazy 健康检查报 ERROR;需要 rocks 依赖的插件时再打开
|
||||||
|
rocks = {
|
||||||
|
enabled = false,
|
||||||
|
},
|
||||||
defaults = {
|
defaults = {
|
||||||
-- By default, only LazyVim plugins will be lazy-loaded. Your custom plugins will load during startup.
|
-- 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.
|
-- If you know what you're doing, you can set this to `true` to have all your custom plugins lazy-loaded by default.
|
||||||
|
|||||||
+16
-26
@@ -1,62 +1,52 @@
|
|||||||
vim.g.autoformat = true
|
vim.g.autoformat = true
|
||||||
|
|
||||||
vim.cmd('set iskeyword+=-') -- treat dash separated words as a word text object"
|
-- 关闭不常用的远程宿主,减少 :checkhealth 里无意义的 WARNING
|
||||||
vim.cmd('set shortmess+=c') -- Don't pass messages to |ins-completion-menu|.
|
vim.g.loaded_perl_provider = 0
|
||||||
vim.cmd('set inccommand=split') -- Make substitution work in realtime
|
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.hidden = true -- Required to keep multiple buffers open multiple buffers
|
||||||
vim.opt.title = true
|
vim.opt.title = true
|
||||||
TERMINAL = vim.fn.expand('$TERMINAL')
|
TERMINAL = vim.fn.expand("$TERMINAL")
|
||||||
vim.cmd('let &titleold="' .. TERMINAL .. '"')
|
vim.cmd('let &titleold="' .. TERMINAL .. '"')
|
||||||
vim.opt.titlestring = "%<%F%=%l/%L - nvim"
|
vim.opt.titlestring = "%<%F%=%l/%L - nvim"
|
||||||
vim.wo.wrap = true -- Display long lines as just one line
|
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.cmd("set whichwrap+=<,>,[,],h,l") -- move to next line with theses keys
|
||||||
vim.cmd('syntax on') -- syntax highlighting
|
|
||||||
vim.opt.pumheight = 10 -- Makes popup menu smaller
|
vim.opt.pumheight = 10 -- Makes popup menu smaller
|
||||||
vim.opt.fileencoding = "utf-8" -- The encoding written to file
|
vim.opt.fileencoding = "utf-8" -- The encoding written to file
|
||||||
vim.opt.cmdheight = 2 -- More space for displaying messages
|
vim.opt.cmdheight = 2 -- More space for displaying messages
|
||||||
vim.cmd('set colorcolumn=99999') -- fix indentline for now
|
|
||||||
vim.opt.mouse = "a" -- Enable your mouse
|
vim.opt.mouse = "a" -- Enable your mouse
|
||||||
vim.opt.splitbelow = true -- Horizontal splits will automatically be below
|
vim.opt.splitbelow = true -- Horizontal splits will automatically be below
|
||||||
vim.opt.termguicolors = true -- set term gui colors most terminals support this
|
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.splitright = true -- Vertical splits will automatically be to the right
|
||||||
-- vim.opt.t_Co = "256" -- Support 256 colors
|
|
||||||
vim.opt.conceallevel = 0 -- So that I can see `` in markdown files
|
vim.opt.conceallevel = 0 -- So that I can see `` in markdown files
|
||||||
vim.o.tabstop = 4
|
vim.o.tabstop = 4
|
||||||
vim.opt.shiftwidth = 4
|
vim.opt.shiftwidth = 4
|
||||||
vim.opt.expandtab = true
|
vim.opt.expandtab = true
|
||||||
vim.bo.smartindent = true -- Makes indenting smart
|
vim.opt.smartindent = true -- Makes indenting smart
|
||||||
vim.wo.number = true -- set numbered lines
|
vim.wo.number = true -- set numbered lines
|
||||||
vim.wo.relativenumber = true -- set relative number
|
vim.wo.relativenumber = true -- set relative number
|
||||||
vim.wo.cursorline = true -- Enable highlighting of the current line
|
vim.wo.cursorline = true -- Enable highlighting of the current line
|
||||||
vim.opt.showtabline = 2 -- Always show tabs
|
vim.opt.showtabline = 2 -- Always show tabs
|
||||||
vim.opt.showmode = false -- We don't need to see things like -- INSERT -- anymore
|
vim.opt.showmode = false -- We don't need to see things like -- INSERT -- anymore
|
||||||
vim.opt.backup = false -- This is recommended by coc
|
vim.opt.backup = false
|
||||||
vim.opt.writebackup = false -- This is recommended by coc
|
vim.opt.writebackup = false
|
||||||
vim.wo.signcolumn = "yes" -- Always show the signcolumn, otherwise it would shift the text each time
|
vim.wo.signcolumn = "yes" -- Always show the signcolumn, otherwise it would shift the text each time
|
||||||
vim.opt.updatetime = 300 -- Faster completion
|
vim.opt.updatetime = 300 -- Faster completion
|
||||||
vim.opt.timeoutlen = 100 -- By default timeoutlen is 1000 ms
|
vim.opt.timeoutlen = 500 -- By default timeoutlen is 1000 ms
|
||||||
vim.opt.clipboard = "unnamedplus" -- Copy paste between vim and everything else
|
vim.opt.clipboard = "unnamedplus" -- Copy paste between vim and everything else
|
||||||
vim.cmd('filetype plugin on') -- filetype detection
|
|
||||||
-- vim.opt.guifont = "JetBrainsMono\\ Nerd\\ Font\\ Mono:h18"
|
|
||||||
-- vim.opt.guifont = "Hack\\ Nerd\\ Font\\ Mono"
|
|
||||||
-- vim.opt.guifont = "SauceCodePro Nerd Font:h17"
|
|
||||||
vim.opt.guifont = "FiraCode Nerd Font:h17"
|
vim.opt.guifont = "FiraCode Nerd Font:h17"
|
||||||
|
|
||||||
-- vim.opt.guifont = "JetBrains\\ Mono\\ Regular\\ Nerd\\ Font\\ Complete"
|
|
||||||
vim.g.vim_markdown_folding_disabled = 1
|
vim.g.vim_markdown_folding_disabled = 1
|
||||||
|
|
||||||
vim.g.vimspector_enable_mappings = 'HUMAN'
|
|
||||||
vim.g['test#go#rspec#options'] = { nearest= '--v', suite= '--v' }
|
|
||||||
|
|
||||||
-- git blame
|
-- git blame
|
||||||
vim.cmd('highlight default link gitblame SpecialComment')
|
vim.cmd("highlight default link gitblame SpecialComment")
|
||||||
vim.g.gitblame_enabled = 0
|
vim.g.gitblame_enabled = 0
|
||||||
|
|
||||||
-- last position jump
|
|
||||||
vim.cmd([[
|
|
||||||
autocmd BufReadPost * if line("'\"") >= 1 && line("'\"") <= line("$") | exe "normal! g`\"" | endif
|
|
||||||
]])
|
|
||||||
|
|
||||||
-- vim.cmd('let g:nvcode_termcolors=256')
|
-- vim.cmd('let g:nvcode_termcolors=256')
|
||||||
-- --local cc = 'gruvbox'
|
-- --local cc = 'gruvbox'
|
||||||
-- local cc = 'lunar'
|
-- local cc = 'lunar'
|
||||||
|
|||||||
@@ -0,0 +1,87 @@
|
|||||||
|
return {
|
||||||
|
{
|
||||||
|
"yetone/avante.nvim",
|
||||||
|
enabled = false, -- 临时禁用 avante
|
||||||
|
opts = function(_, opts)
|
||||||
|
-- 强制使用 Gemini CLI 的 ACP (Agent Client Protocol)
|
||||||
|
-- 注意:必须明确设置,覆盖 LazyVim 的默认配置
|
||||||
|
opts.provider = "gemini-cli"
|
||||||
|
opts.auto_suggestions_provider = "gemini-cli"
|
||||||
|
|
||||||
|
-- 清除可能存在的 claude 配置(如果 LazyVim 设置了的话)
|
||||||
|
if opts.claude then
|
||||||
|
opts.claude = nil
|
||||||
|
end
|
||||||
|
|
||||||
|
-- 配置 ACP providers
|
||||||
|
-- 使用网页登录(OAuth)方式,不需要 API Key
|
||||||
|
--
|
||||||
|
-- 使用步骤:
|
||||||
|
-- 1. 首次使用前,在终端运行: gemini auth login
|
||||||
|
-- 2. 这会打开浏览器,完成 Google 账号登录
|
||||||
|
-- 3. 登录凭证会被缓存到本地(通常在 ~/.gemini 目录)
|
||||||
|
-- 4. 之后 avante 会自动使用缓存的凭证,无需 API Key
|
||||||
|
--
|
||||||
|
-- 注意:如果环境变量中已设置了 GEMINI_API_KEY,Gemini CLI 可能会优先使用它
|
||||||
|
-- 如果想强制使用网页登录,请临时取消设置该环境变量:
|
||||||
|
-- unset GEMINI_API_KEY (bash/zsh)
|
||||||
|
-- 或 set -e GEMINI_API_KEY (fish)
|
||||||
|
opts.acp_providers = opts.acp_providers or {}
|
||||||
|
opts.acp_providers["gemini-cli"] = {
|
||||||
|
command = "gemini",
|
||||||
|
args = { "--experimental-acp" },
|
||||||
|
env = {
|
||||||
|
NODE_NO_WARNINGS = "1",
|
||||||
|
-- 不设置 GEMINI_API_KEY,让 Gemini CLI 使用已缓存的 OAuth 凭证
|
||||||
|
-- 如果必须使用 API Key,可以取消下面的注释:
|
||||||
|
-- GEMINI_API_KEY = os.getenv("GEMINI_API_KEY"),
|
||||||
|
|
||||||
|
-- 代理配置(如果需要通过代理访问)
|
||||||
|
-- 从环境变量读取代理设置,如果没有设置则使用系统默认
|
||||||
|
HTTP_PROXY = os.getenv("HTTP_PROXY") or os.getenv("http_proxy"),
|
||||||
|
HTTPS_PROXY = os.getenv("HTTPS_PROXY") or os.getenv("https_proxy"),
|
||||||
|
ALL_PROXY = os.getenv("ALL_PROXY") or os.getenv("all_proxy"),
|
||||||
|
NO_PROXY = os.getenv("NO_PROXY") or os.getenv("no_proxy"),
|
||||||
|
|
||||||
|
-- 如果需要手动指定代理,可以取消下面的注释并修改为你的代理地址:
|
||||||
|
-- HTTP_PROXY = "http://127.0.0.1:7890",
|
||||||
|
-- HTTPS_PROXY = "http://127.0.0.1:7890",
|
||||||
|
-- 或者使用 SOCKS5 代理:
|
||||||
|
-- ALL_PROXY = "socks5://127.0.0.1:7890",
|
||||||
|
},
|
||||||
|
-- 不设置 auth_method,让 CLI 自动使用已缓存的 OAuth 凭证
|
||||||
|
}
|
||||||
|
|
||||||
|
-- 确保 providers 表中没有 claude 的配置
|
||||||
|
opts.providers = opts.providers or {}
|
||||||
|
if opts.providers.claude then
|
||||||
|
opts.providers.claude = nil
|
||||||
|
end
|
||||||
|
|
||||||
|
-- 可选:配置其他 Gemini provider 设置(用于非 ACP 模式)
|
||||||
|
opts.providers.gemini = opts.providers.gemini or {
|
||||||
|
-- 如果将来需要使用 REST API 模式,可以在这里配置
|
||||||
|
-- api_key = os.getenv("GEMINI_API_KEY"),
|
||||||
|
}
|
||||||
|
|
||||||
|
-- 调试:打印当前配置的 provider(可选,用于验证)
|
||||||
|
-- vim.notify("Avante provider set to: " .. opts.provider, vim.log.levels.INFO)
|
||||||
|
|
||||||
|
return opts
|
||||||
|
end,
|
||||||
|
-- LazyVim 会自动调用 setup,这里不需要再次调用
|
||||||
|
-- 但如果需要验证,可以取消下面的注释
|
||||||
|
-- config = function(_, opts)
|
||||||
|
-- vim.defer_fn(function()
|
||||||
|
-- local avante = require("avante")
|
||||||
|
-- if avante and avante.config then
|
||||||
|
-- local current_provider = avante.config.provider or "unknown"
|
||||||
|
-- vim.notify("Avante provider: " .. current_provider, vim.log.levels.INFO)
|
||||||
|
-- end
|
||||||
|
-- end, 1000)
|
||||||
|
-- end,
|
||||||
|
-- 确保在 LazyVim 的配置之后加载
|
||||||
|
priority = 1000,
|
||||||
|
},
|
||||||
|
}
|
||||||
|
|
||||||
@@ -1,4 +1,5 @@
|
|||||||
return {
|
return {
|
||||||
|
{ "akinsho/bufferline.nvim", enabled = false },
|
||||||
{
|
{
|
||||||
-- tabline
|
-- tabline
|
||||||
'romgrk/barbar.nvim',
|
'romgrk/barbar.nvim',
|
||||||
|
|||||||
@@ -0,0 +1,59 @@
|
|||||||
|
return {
|
||||||
|
{
|
||||||
|
"olimorris/codecompanion.nvim",
|
||||||
|
enabled = false, -- 临时禁用 CodeCompanion
|
||||||
|
dependencies = {
|
||||||
|
"nvim-lua/plenary.nvim",
|
||||||
|
"nvim-treesitter/nvim-treesitter",
|
||||||
|
},
|
||||||
|
keys = {
|
||||||
|
{ "<leader>a", nil, desc = "CodeCompanion" },
|
||||||
|
{ "<leader>ac", "<cmd>CodeCompanionChat<cr>", desc = "CodeCompanion Chat" },
|
||||||
|
{ "<leader>ai", "<cmd>CodeCompanionInline<cr>", desc = "CodeCompanion Inline" },
|
||||||
|
{ "<leader>am", "<cmd>CodeCompanionCmd<cr>", desc = "CodeCompanion Cmd" },
|
||||||
|
{ "<leader>at", "<cmd>CodeCompanionToggle<cr>", desc = "CodeCompanion Toggle" },
|
||||||
|
},
|
||||||
|
opts = {
|
||||||
|
-- 配置策略,使用 gemini_cli adapter
|
||||||
|
strategies = {
|
||||||
|
chat = {
|
||||||
|
adapter = "gemini_cli", -- chat 模式使用 gemini_cli
|
||||||
|
},
|
||||||
|
inline = {
|
||||||
|
adapter = "gemini_cli", -- inline 模式使用 gemini_cli
|
||||||
|
},
|
||||||
|
cmd = {
|
||||||
|
adapter = "gemini_cli", -- cmd 模式使用 gemini_cli
|
||||||
|
},
|
||||||
|
},
|
||||||
|
-- 配置 adapters
|
||||||
|
adapters = {
|
||||||
|
acp = {
|
||||||
|
gemini_cli = function()
|
||||||
|
return require("codecompanion.adapters").extend("gemini_cli", {
|
||||||
|
-- 使用 OAuth 登录方式,不使用 API key
|
||||||
|
defaults = {
|
||||||
|
auth_method = "oauth", -- 使用 OAuth 网页登录
|
||||||
|
timeout = 20000,
|
||||||
|
},
|
||||||
|
-- 不设置 GEMINI_API_KEY,强制使用 OAuth 登录
|
||||||
|
env = {
|
||||||
|
-- 不设置 GEMINI_API_KEY,让 Gemini CLI 使用已缓存的 OAuth 凭证
|
||||||
|
-- 代理配置(如果需要通过代理访问)
|
||||||
|
HTTP_PROXY = os.getenv("HTTP_PROXY") or os.getenv("http_proxy"),
|
||||||
|
HTTPS_PROXY = os.getenv("HTTPS_PROXY") or os.getenv("https_proxy"),
|
||||||
|
ALL_PROXY = os.getenv("ALL_PROXY") or os.getenv("all_proxy"),
|
||||||
|
NO_PROXY = os.getenv("NO_PROXY") or os.getenv("no_proxy"),
|
||||||
|
},
|
||||||
|
-- Gemini CLI 命令配置
|
||||||
|
commands = {
|
||||||
|
default = { "gemini", "cli", "--experimental-acp" },
|
||||||
|
},
|
||||||
|
})
|
||||||
|
end,
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
}
|
||||||
|
|
||||||
@@ -4,8 +4,12 @@ return {
|
|||||||
"stevearc/conform.nvim",
|
"stevearc/conform.nvim",
|
||||||
opts = {
|
opts = {
|
||||||
formatters_by_ft = {
|
formatters_by_ft = {
|
||||||
go = { "goimports"},
|
javascript = { "prettierd", "prettier" },
|
||||||
}
|
javascriptreact = { "prettierd", "prettier" },
|
||||||
}
|
typescript = { "prettierd", "prettier" },
|
||||||
}
|
typescriptreact = { "prettierd", "prettier" },
|
||||||
|
go = { "goimports", "gofumpt" },
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
}
|
}
|
||||||
@@ -3,6 +3,9 @@ return {
|
|||||||
"nvimdev/dashboard-nvim",
|
"nvimdev/dashboard-nvim",
|
||||||
event = "VimEnter",
|
event = "VimEnter",
|
||||||
opts = function(_, opts)
|
opts = function(_, opts)
|
||||||
|
-- 确保 opts.config 存在,避免 nil 错误
|
||||||
|
opts.config = opts.config or {}
|
||||||
|
|
||||||
local logo = [[
|
local logo = [[
|
||||||
|
|
||||||
███╗ ██╗███████╗ ██████╗ ██╗ ██╗██╗███╗ ███╗
|
███╗ ██╗███████╗ ██████╗ ██╗ ██╗██╗███╗ ███╗
|
||||||
|
|||||||
@@ -0,0 +1,3 @@
|
|||||||
|
return {
|
||||||
|
"sindrets/diffview.nvim",
|
||||||
|
}
|
||||||
@@ -0,0 +1,21 @@
|
|||||||
|
return {
|
||||||
|
{
|
||||||
|
"ibhagwan/fzf-lua",
|
||||||
|
dependencies = {
|
||||||
|
"nvim-tree/nvim-web-devicons",
|
||||||
|
},
|
||||||
|
keys = {
|
||||||
|
-- 如果需要使用 fzf-lua 的快捷键,可以在这里添加
|
||||||
|
},
|
||||||
|
config = function()
|
||||||
|
require("fzf-lua").setup({
|
||||||
|
-- fzf-lua 的配置
|
||||||
|
winopts = {
|
||||||
|
height = 0.85,
|
||||||
|
width = 0.80,
|
||||||
|
},
|
||||||
|
})
|
||||||
|
end,
|
||||||
|
},
|
||||||
|
}
|
||||||
|
|
||||||
@@ -0,0 +1,40 @@
|
|||||||
|
return {
|
||||||
|
{
|
||||||
|
"marcinjahn/gemini-cli.nvim",
|
||||||
|
enabled = false, -- 临时禁用 gemini-cli 插件
|
||||||
|
cmd = "Gemini", -- 懒加载,使用 :Gemini 命令时加载
|
||||||
|
dependencies = {
|
||||||
|
"folke/snacks.nvim", -- 必需的依赖
|
||||||
|
},
|
||||||
|
keys = {
|
||||||
|
{ "<leader>a", nil, desc = "Gemini CLI" },
|
||||||
|
{ "<leader>aq", "<cmd>Gemini ask<cr>", desc = "Ask Gemini" },
|
||||||
|
{ "<leader>ac", "<cmd>Gemini toggle<cr>", desc = "Toggle Gemini CLI" },
|
||||||
|
{ "<leader>af", "<cmd>Gemini add_file<cr>", desc = "Add File to Gemini" },
|
||||||
|
{ "<leader>ar", "<cmd>Gemini --resume<cr>", desc = "Resume Gemini" },
|
||||||
|
},
|
||||||
|
config = function()
|
||||||
|
require("gemini_cli").setup({
|
||||||
|
-- Gemini CLI 命令
|
||||||
|
gemini_cmd = "gemini",
|
||||||
|
-- 传递给 gemini 命令的参数
|
||||||
|
args = {},
|
||||||
|
-- 自动重新加载
|
||||||
|
auto_reload = false,
|
||||||
|
-- 选择器配置
|
||||||
|
picker_cfg = {
|
||||||
|
preset = "vscode",
|
||||||
|
},
|
||||||
|
-- 窗口配置
|
||||||
|
win = {
|
||||||
|
wo = { winbar = "GeminiCLI" },
|
||||||
|
style = "gemini_cli",
|
||||||
|
position = "right", -- 窗口位置:right, left, top, bottom
|
||||||
|
},
|
||||||
|
-- 代理配置(如果需要)
|
||||||
|
-- 代理会从环境变量 HTTP_PROXY, HTTPS_PROXY, ALL_PROXY 自动读取
|
||||||
|
})
|
||||||
|
end,
|
||||||
|
},
|
||||||
|
}
|
||||||
|
|
||||||
@@ -3,17 +3,25 @@ return {
|
|||||||
"lewis6991/gitsigns.nvim",
|
"lewis6991/gitsigns.nvim",
|
||||||
opts = {
|
opts = {
|
||||||
signs = {
|
signs = {
|
||||||
-- TODO add hl to colorscheme
|
add = { text = '┃' },
|
||||||
add = {hl = 'GitSignsAdd' , text = '▎', numhl='GitSignsAddNr' , linehl='GitSignsAddLn'},
|
change = { text = '┃' },
|
||||||
change = {hl = 'GitSignsChange', text = '▎', numhl='GitSignsChangeNr', linehl='GitSignsChangeLn'},
|
delete = { text = '_' },
|
||||||
delete = {hl = 'GitSignsDelete', text = '契', numhl='GitSignsDeleteNr', linehl='GitSignsDeleteLn'},
|
topdelete = { text = '‾' },
|
||||||
topdelete = {hl = 'GitSignsDelete', text = '契', numhl='GitSignsDeleteNr', linehl='GitSignsDeleteLn'},
|
changedelete = { text = '~' },
|
||||||
changedelete = {hl = 'GitSignsChange', text = '▎', numhl='GitSignsChangeNr', linehl='GitSignsChangeLn'},
|
untracked = { text = '┆' },
|
||||||
|
},
|
||||||
|
signs_staged = {
|
||||||
|
add = { text = '┃' },
|
||||||
|
change = { text = '┃' },
|
||||||
|
delete = { text = '_' },
|
||||||
|
topdelete = { text = '‾' },
|
||||||
|
changedelete = { text = '~' },
|
||||||
|
untracked = { text = '┆' },
|
||||||
},
|
},
|
||||||
numhl = false,
|
numhl = false,
|
||||||
linehl = false,
|
linehl = false,
|
||||||
watch_gitdir = {
|
watch_gitdir = {
|
||||||
interval = 1000
|
interval = 1000,
|
||||||
},
|
},
|
||||||
sign_priority = 6,
|
sign_priority = 6,
|
||||||
update_debounce = 200,
|
update_debounce = 200,
|
||||||
@@ -39,6 +47,7 @@ return {
|
|||||||
map("n", "<leader>ghD", function() gs.diffthis("~") end, "Diff This ~")
|
map("n", "<leader>ghD", function() gs.diffthis("~") end, "Diff This ~")
|
||||||
map({ "o", "x" }, "ih", ":<C-U>Gitsigns select_hunk<CR>", "GitSigns Select Hunk")
|
map({ "o", "x" }, "ih", ":<C-U>Gitsigns select_hunk<CR>", "GitSigns Select Hunk")
|
||||||
end,
|
end,
|
||||||
|
},
|
||||||
|
},
|
||||||
}
|
}
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|||||||
@@ -0,0 +1,72 @@
|
|||||||
|
return {
|
||||||
|
-- Override LazyVim's default LSP keymaps
|
||||||
|
{
|
||||||
|
"neovim/nvim-lspconfig",
|
||||||
|
keys = {
|
||||||
|
-- Delete LazyVim's default gd mapping if it exists
|
||||||
|
{ "gd", false },
|
||||||
|
-- Add our custom gd mapping with higher priority
|
||||||
|
{
|
||||||
|
"gd",
|
||||||
|
function()
|
||||||
|
vim.lsp.buf.definition()
|
||||||
|
end,
|
||||||
|
desc = "Goto Definition",
|
||||||
|
noremap = true,
|
||||||
|
silent = true,
|
||||||
|
},
|
||||||
|
-- Delete and re-add other LSP mappings to ensure they work
|
||||||
|
{ "gD", false },
|
||||||
|
{
|
||||||
|
"gD",
|
||||||
|
function()
|
||||||
|
vim.lsp.buf.declaration()
|
||||||
|
end,
|
||||||
|
desc = "Goto Declaration",
|
||||||
|
noremap = true,
|
||||||
|
silent = true,
|
||||||
|
},
|
||||||
|
{ "gi", false },
|
||||||
|
{
|
||||||
|
"gi",
|
||||||
|
function()
|
||||||
|
vim.lsp.buf.implementation()
|
||||||
|
end,
|
||||||
|
desc = "Goto Implementation",
|
||||||
|
noremap = true,
|
||||||
|
silent = true,
|
||||||
|
},
|
||||||
|
{ "K", false },
|
||||||
|
{
|
||||||
|
"K",
|
||||||
|
function()
|
||||||
|
vim.lsp.buf.hover()
|
||||||
|
end,
|
||||||
|
desc = "Hover Documentation",
|
||||||
|
noremap = true,
|
||||||
|
silent = true,
|
||||||
|
},
|
||||||
|
{ "gt", false },
|
||||||
|
{
|
||||||
|
"gt",
|
||||||
|
function()
|
||||||
|
vim.lsp.buf.type_definition()
|
||||||
|
end,
|
||||||
|
desc = "Goto Type Definition",
|
||||||
|
noremap = true,
|
||||||
|
silent = true,
|
||||||
|
},
|
||||||
|
{ "gr", false },
|
||||||
|
{
|
||||||
|
"gr",
|
||||||
|
function()
|
||||||
|
vim.lsp.buf.references()
|
||||||
|
end,
|
||||||
|
desc = "References",
|
||||||
|
noremap = true,
|
||||||
|
silent = true,
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
}
|
||||||
|
|
||||||
@@ -1,16 +1,26 @@
|
|||||||
return {
|
return {
|
||||||
{
|
{
|
||||||
"williamboman/mason.nvim",
|
"mason-org/mason.nvim",
|
||||||
opts = function(_, opts)
|
opts = function(_, opts)
|
||||||
vim.list_extend(opts.ensure_installed, {
|
vim.list_extend(opts.ensure_installed, {
|
||||||
"stylua",
|
"stylua",
|
||||||
"selene",
|
"selene",
|
||||||
"luacheck",
|
"luacheck",
|
||||||
"shellcheck",
|
|
||||||
"shfmt",
|
"shfmt",
|
||||||
"tailwindcss-language-server",
|
"pylyzer",
|
||||||
|
"buf", -- Protocol Buffers LSP
|
||||||
|
|
||||||
|
-- TypeScript/JavaScript
|
||||||
"typescript-language-server",
|
"typescript-language-server",
|
||||||
"css-lsp",
|
"eslint-lsp",
|
||||||
|
"prettierd",
|
||||||
|
|
||||||
|
-- Go
|
||||||
|
"gopls",
|
||||||
|
"goimports",
|
||||||
|
"gofumpt",
|
||||||
|
"golangci-lint",
|
||||||
|
"delve",
|
||||||
})
|
})
|
||||||
end,
|
end,
|
||||||
},
|
},
|
||||||
|
|||||||
@@ -1,22 +1,24 @@
|
|||||||
return {
|
return {
|
||||||
{ "nvim-neotest/neotest-plenary" },
|
|
||||||
{
|
{
|
||||||
"nvim-neotest/neotest",
|
"nvim-neotest/neotest",
|
||||||
dependencies = {
|
dependencies = {
|
||||||
|
"nvim-neotest/nvim-nio",
|
||||||
"nvim-lua/plenary.nvim",
|
"nvim-lua/plenary.nvim",
|
||||||
"antoinemadec/FixCursorHold.nvim",
|
"antoinemadec/FixCursorHold.nvim",
|
||||||
"nvim-treesitter/nvim-treesitter",
|
"nvim-treesitter/nvim-treesitter",
|
||||||
"nvim-neotest/neotest-go",
|
"fredrikaverpil/neotest-golang", -- Installation
|
||||||
},
|
},
|
||||||
opts = {
|
config = function()
|
||||||
|
require("neotest").setup({
|
||||||
adapters = {
|
adapters = {
|
||||||
require("neotest-go")({
|
require("neotest-golang"), -- Registration
|
||||||
experimental = {
|
},
|
||||||
test_table = true,
|
output = {
|
||||||
|
enabled = true,
|
||||||
|
open_on_run = true,
|
||||||
},
|
},
|
||||||
args = { "-gcflags=all=-l", "-count=1", "-timeout=60s" }
|
|
||||||
})
|
})
|
||||||
}
|
end,
|
||||||
},
|
|
||||||
},
|
},
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -76,7 +76,7 @@ end
|
|||||||
return {
|
return {
|
||||||
{
|
{
|
||||||
"hrsh7th/nvim-cmp",
|
"hrsh7th/nvim-cmp",
|
||||||
enable = false,
|
enabled = false,
|
||||||
dependencies = {
|
dependencies = {
|
||||||
"hrsh7th/cmp-emoji",
|
"hrsh7th/cmp-emoji",
|
||||||
'hrsh7th/vim-vsnip',
|
'hrsh7th/vim-vsnip',
|
||||||
|
|||||||
@@ -1,7 +1,7 @@
|
|||||||
return {
|
return {
|
||||||
"leoluz/nvim-dap-go",
|
"leoluz/nvim-dap-go",
|
||||||
keys = {
|
keys = {
|
||||||
{ "<F5>", function() require("dap").continue({ before = get_args }) end, desc = "Run with Args" },
|
{ "<F5>", function() require("dap").continue() end, desc = "Run/Continue" },
|
||||||
{ "<F6>",":lua require('dap-go').debug_test()<cr>", desc = "Debug Test" },
|
{ "<F6>",":lua require('dap-go').debug_test()<cr>", desc = "Debug Test" },
|
||||||
{ "<F9>", function() require("dap").toggle_breakpoint() end, desc = "Toggle Breakpoint" },
|
{ "<F9>", function() require("dap").toggle_breakpoint() end, desc = "Toggle Breakpoint" },
|
||||||
{ "<F10>", function() require("dap").step_over() end, desc = "Step Over" },
|
{ "<F10>", function() require("dap").step_over() end, desc = "Step Over" },
|
||||||
|
|||||||
@@ -1,21 +1,36 @@
|
|||||||
return {
|
return {
|
||||||
-- lsp servers
|
|
||||||
{
|
{
|
||||||
"neovim/nvim-lspconfig",
|
"neovim/nvim-lspconfig",
|
||||||
event = { 'BufReadPre', 'BufReadPost', 'BufNewFile' },
|
|
||||||
opts = {
|
opts = {
|
||||||
inlay_hints = { enabled = false },
|
inlay_hints = { enabled = false },
|
||||||
---@type lspconfig.options
|
|
||||||
servers = {
|
servers = {
|
||||||
|
buf_ls = {},
|
||||||
cssls = {},
|
cssls = {},
|
||||||
tailwindcss = {
|
tailwindcss = {
|
||||||
root_dir = function(...)
|
root_dir = function(...)
|
||||||
return require("lspconfig.util").root_pattern('tailwind.config.js', 'tailwind.config.cjs', 'tailwind.config.mjs', 'tailwind.config.ts', 'postcss.config.js', 'postcss.config.cjs', 'postcss.config.mjs', 'postcss.config.ts', 'package.json', 'node_modules', '.git')(...)
|
return require("lspconfig.util").root_pattern(
|
||||||
|
"tailwind.config.js",
|
||||||
|
"tailwind.config.cjs",
|
||||||
|
"tailwind.config.mjs",
|
||||||
|
"tailwind.config.ts",
|
||||||
|
"postcss.config.js",
|
||||||
|
"postcss.config.cjs",
|
||||||
|
"postcss.config.mjs",
|
||||||
|
"postcss.config.ts",
|
||||||
|
"package.json",
|
||||||
|
"node_modules",
|
||||||
|
".git"
|
||||||
|
)(...)
|
||||||
end,
|
end,
|
||||||
},
|
},
|
||||||
tsserver = {
|
ts_ls = {
|
||||||
root_dir = function(...)
|
root_dir = function(...)
|
||||||
return require("lspconfig.util").root_pattern("tsconfig.json", "package.json", "jsconfig.json", ".git")(...)
|
return require("lspconfig.util").root_pattern(
|
||||||
|
"tsconfig.json",
|
||||||
|
"package.json",
|
||||||
|
"jsconfig.json",
|
||||||
|
".git"
|
||||||
|
)(...)
|
||||||
end,
|
end,
|
||||||
single_file_support = false,
|
single_file_support = false,
|
||||||
settings = {
|
settings = {
|
||||||
@@ -52,7 +67,6 @@ return {
|
|||||||
},
|
},
|
||||||
},
|
},
|
||||||
lua_ls = {
|
lua_ls = {
|
||||||
-- enabled = false,
|
|
||||||
single_file_support = true,
|
single_file_support = true,
|
||||||
settings = {
|
settings = {
|
||||||
Lua = {
|
Lua = {
|
||||||
@@ -63,11 +77,6 @@ return {
|
|||||||
workspaceWord = true,
|
workspaceWord = true,
|
||||||
callSnippet = "Both",
|
callSnippet = "Both",
|
||||||
},
|
},
|
||||||
misc = {
|
|
||||||
parameters = {
|
|
||||||
-- "--log-level=trace",
|
|
||||||
},
|
|
||||||
},
|
|
||||||
hint = {
|
hint = {
|
||||||
enable = true,
|
enable = true,
|
||||||
setType = false,
|
setType = false,
|
||||||
@@ -84,7 +93,6 @@ return {
|
|||||||
},
|
},
|
||||||
diagnostics = {
|
diagnostics = {
|
||||||
disable = { "incomplete-signature-doc", "trailing-space" },
|
disable = { "incomplete-signature-doc", "trailing-space" },
|
||||||
-- enable = false,
|
|
||||||
groupSeverity = {
|
groupSeverity = {
|
||||||
strong = "Warning",
|
strong = "Warning",
|
||||||
strict = "Warning",
|
strict = "Warning",
|
||||||
@@ -117,7 +125,7 @@ return {
|
|||||||
},
|
},
|
||||||
},
|
},
|
||||||
gopls = {
|
gopls = {
|
||||||
cmd = { "gopls", "serve" }, -- use cmd in path, I can update it easily
|
cmd = { "gopls", "serve" },
|
||||||
settings = {
|
settings = {
|
||||||
gopls = {
|
gopls = {
|
||||||
analyses = {
|
analyses = {
|
||||||
@@ -128,22 +136,20 @@ return {
|
|||||||
},
|
},
|
||||||
staticcheck = false,
|
staticcheck = false,
|
||||||
usePlaceholders = false,
|
usePlaceholders = false,
|
||||||
}
|
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
pylsp = {
|
},
|
||||||
cmd = { "pylyzer", "--server" },
|
pylyzer = {
|
||||||
filetypes = { "python" },
|
|
||||||
settings = {
|
settings = {
|
||||||
python = {
|
python = {
|
||||||
checkOnType = false,
|
checkOnType = false,
|
||||||
diagnostics = false,
|
diagnostics = false,
|
||||||
inlayHints = true,
|
inlayHints = true,
|
||||||
smartCompletion = true
|
smartCompletion = true,
|
||||||
}
|
},
|
||||||
},
|
},
|
||||||
single_file_support = true,
|
single_file_support = true,
|
||||||
}
|
},
|
||||||
},
|
},
|
||||||
setup = {},
|
setup = {},
|
||||||
},
|
},
|
||||||
|
|||||||
@@ -1,5 +1,5 @@
|
|||||||
local function my_on_attach(bufnr)
|
local function my_on_attach(bufnr)
|
||||||
local api = require "nvim-tree.api"
|
local api = require("nvim-tree.api")
|
||||||
|
|
||||||
local function opts(desc)
|
local function opts(desc)
|
||||||
return { desc = "nvim-tree: " .. desc, buffer = bufnr, noremap = true, silent = true, nowait = true }
|
return { desc = "nvim-tree: " .. desc, buffer = bufnr, noremap = true, silent = true, nowait = true }
|
||||||
@@ -9,30 +9,30 @@ local function my_on_attach(bufnr)
|
|||||||
api.config.mappings.default_on_attach(bufnr)
|
api.config.mappings.default_on_attach(bufnr)
|
||||||
|
|
||||||
-- custom mappings
|
-- custom mappings
|
||||||
vim.keymap.set('n', '<C-t>', api.tree.change_root_to_parent, opts('Up'))
|
vim.keymap.set("n", "<C-t>", api.tree.change_root_to_parent, opts("Up"))
|
||||||
vim.keymap.set('n', '?', api.tree.toggle_help, opts('Help'))
|
vim.keymap.set("n", "?", api.tree.toggle_help, opts("Help"))
|
||||||
vim.keymap.set('n', 'l', api.node.open.edit, opts('Edit'))
|
vim.keymap.set("n", "l", api.node.open.edit, opts("Edit"))
|
||||||
vim.keymap.set('n', 'h', api.node.navigate.parent_close, opts('Parent Close'))
|
vim.keymap.set("n", "h", api.node.navigate.parent_close, opts("Parent Close"))
|
||||||
vim.keymap.set('n', 's', api.node.open.horizontal, opts('Horizontal Split'))
|
vim.keymap.set("n", "s", api.node.open.horizontal, opts("Horizontal Split"))
|
||||||
vim.keymap.set('n', 'v', api.node.open.vertical, opts('Vertical Split'))
|
vim.keymap.set("n", "v", api.node.open.vertical, opts("Vertical Split"))
|
||||||
|
vim.keymap.set("n", "<S-r>", api.tree.reload, opts("Reload"))
|
||||||
end
|
end
|
||||||
|
|
||||||
vim.g.nvim_tree_indent_markers = 1 -- "0 by default, this option shows indent markers when folders are open
|
vim.g.nvim_tree_indent_markers = 1 -- "0 by default, this option shows indent markers when folders are open
|
||||||
vim.g.nvim_tree_auto_ignore_ft = 'startify' --empty by default, don't auto open tree on specific filetypes.
|
vim.g.nvim_tree_auto_ignore_ft = "startify" --empty by default, don't auto open tree on specific filetypes.
|
||||||
|
|
||||||
vim.g.nvim_tree_icons = {
|
vim.g.nvim_tree_icons = {
|
||||||
default = '',
|
default = "",
|
||||||
symlink = '',
|
symlink = "",
|
||||||
git = { unstaged = "", staged = "✓", unmerged = "", renamed = "➜", untracked = "" },
|
git = { unstaged = "", staged = "✓", unmerged = "", renamed = "➜", untracked = "" },
|
||||||
folder = {default = "", open = "", empty = "", empty_open = "", symlink = ""}
|
folder = { default = "", open = "", empty = "", empty_open = "", symlink = "" },
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
return {
|
return {
|
||||||
{
|
{
|
||||||
'nvim-tree/nvim-tree.lua',
|
"nvim-tree/nvim-tree.lua",
|
||||||
dependencies = {
|
dependencies = {
|
||||||
'nvim-tree/nvim-web-devicons', -- optional
|
"nvim-tree/nvim-web-devicons", -- optional
|
||||||
},
|
},
|
||||||
opts = {
|
opts = {
|
||||||
on_attach = my_on_attach,
|
on_attach = my_on_attach,
|
||||||
@@ -41,7 +41,7 @@ return {
|
|||||||
--auto_close = false,
|
--auto_close = false,
|
||||||
filters = {
|
filters = {
|
||||||
dotfiles = false,
|
dotfiles = false,
|
||||||
custom = {}
|
custom = {},
|
||||||
},
|
},
|
||||||
git = {
|
git = {
|
||||||
enable = true,
|
enable = true,
|
||||||
@@ -56,8 +56,9 @@ return {
|
|||||||
update_cwd = false,
|
update_cwd = false,
|
||||||
-- list of buffer names / filetypes that will not update the cwd if the file isn't found under the current root directory
|
-- list of buffer names / filetypes that will not update the cwd if the file isn't found under the current root directory
|
||||||
-- only relevant when `update_focused_file.update_cwd` is true and `update_focused_file.enable` is true
|
-- only relevant when `update_focused_file.update_cwd` is true and `update_focused_file.enable` is true
|
||||||
ignore_list = {}
|
ignore_list = {},
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -0,0 +1,12 @@
|
|||||||
|
return {
|
||||||
|
-- Protocol Buffers 支持通过以下方式提供:
|
||||||
|
-- 1. 语法高亮:通过 treesitter(需要在 treesitter.lua 中添加 "proto")
|
||||||
|
-- 2. LSP 支持:通过 bufls(已在 nvim-lspconfig.lua 和 mason.lua 中配置)
|
||||||
|
--
|
||||||
|
-- 如果需要额外的语法高亮插件,可以使用:
|
||||||
|
-- {
|
||||||
|
-- "protocolbuffers/protobuf",
|
||||||
|
-- ft = { "proto" },
|
||||||
|
-- },
|
||||||
|
}
|
||||||
|
|
||||||
@@ -0,0 +1,16 @@
|
|||||||
|
return {
|
||||||
|
{
|
||||||
|
"MeanderingProgrammer/render-markdown.nvim",
|
||||||
|
dependencies = {
|
||||||
|
"nvim-treesitter/nvim-treesitter",
|
||||||
|
"nvim-tree/nvim-web-devicons",
|
||||||
|
},
|
||||||
|
ft = { "markdown", "mdx", "quarto", "rmd", "org" },
|
||||||
|
---@module 'render-markdown'
|
||||||
|
---@type render.md.UserConfig
|
||||||
|
opts = {
|
||||||
|
-- 未安装 tectonic/pdflatex 时关闭 LaTeX 公式渲染,减少健康检查噪音
|
||||||
|
latex = { enabled = false },
|
||||||
|
},
|
||||||
|
},
|
||||||
|
}
|
||||||
@@ -0,0 +1,103 @@
|
|||||||
|
return {
|
||||||
|
"folke/sidekick.nvim",
|
||||||
|
enabled = true,
|
||||||
|
opts = {
|
||||||
|
cli = {
|
||||||
|
win = {
|
||||||
|
layout = "right",
|
||||||
|
split = {
|
||||||
|
width = 0,
|
||||||
|
height = 20,
|
||||||
|
},
|
||||||
|
config = function(terminal)
|
||||||
|
terminal.opts.split.width = math.floor(vim.o.columns * 0.6)
|
||||||
|
end,
|
||||||
|
keys = {
|
||||||
|
buffers = false,
|
||||||
|
},
|
||||||
|
},
|
||||||
|
mux = {
|
||||||
|
backend = vim.env.ZELLIJ and "zellij" or "tmux",
|
||||||
|
enabled = true,
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
keys = {
|
||||||
|
{
|
||||||
|
"<C-\\>",
|
||||||
|
function()
|
||||||
|
require("sidekick.cli").toggle()
|
||||||
|
end,
|
||||||
|
mode = { "n", "i", "t" },
|
||||||
|
desc = "Sidekick Toggle",
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"<leader>ao",
|
||||||
|
function()
|
||||||
|
require("sidekick.cli").toggle({ focus = true })
|
||||||
|
end,
|
||||||
|
desc = "Sidekick Open",
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"<leader>ax",
|
||||||
|
function()
|
||||||
|
require("sidekick.cli").hide()
|
||||||
|
end,
|
||||||
|
desc = "Sidekick Close",
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"<leader>aa",
|
||||||
|
function()
|
||||||
|
require("sidekick.cli").toggle()
|
||||||
|
end,
|
||||||
|
desc = "Sidekick Toggle",
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"<leader>as",
|
||||||
|
function()
|
||||||
|
require("sidekick.cli").select()
|
||||||
|
end,
|
||||||
|
-- Or to select only installed tools:
|
||||||
|
-- require("sidekick.cli").select({ filter = { installed = true } })
|
||||||
|
desc = "Select CLI",
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"<leader>ad",
|
||||||
|
function()
|
||||||
|
require("sidekick.cli").close()
|
||||||
|
end,
|
||||||
|
desc = "Detach a CLI Session",
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"<leader>at",
|
||||||
|
function()
|
||||||
|
require("sidekick.cli").send({ msg = "{this}" })
|
||||||
|
end,
|
||||||
|
mode = { "x", "n" },
|
||||||
|
desc = "Send This",
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"<leader>af",
|
||||||
|
function()
|
||||||
|
require("sidekick.cli").send({ msg = "{file}" })
|
||||||
|
end,
|
||||||
|
desc = "Send File",
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"<leader>av",
|
||||||
|
function()
|
||||||
|
require("sidekick.cli").send({ msg = "{selection}" })
|
||||||
|
end,
|
||||||
|
mode = { "x" },
|
||||||
|
desc = "Send Visual Selection",
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"<leader>ap",
|
||||||
|
function()
|
||||||
|
require("sidekick.cli").prompt()
|
||||||
|
end,
|
||||||
|
mode = { "n", "x" },
|
||||||
|
desc = "Sidekick Select Prompt",
|
||||||
|
},
|
||||||
|
},
|
||||||
|
}
|
||||||
@@ -0,0 +1,13 @@
|
|||||||
|
return {
|
||||||
|
{
|
||||||
|
"folke/snacks.nvim",
|
||||||
|
opts = {
|
||||||
|
-- LazyVim 仍依赖 snacks 核心(通知、dashboard 等);关闭 picker,统一用 Telescope。
|
||||||
|
picker = { enabled = false },
|
||||||
|
image = {
|
||||||
|
-- tmux / 部分终端不报 kitty graphics;避免 :checkhealth 里出现 ERROR(仍会 WARN)
|
||||||
|
force = true,
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
}
|
||||||
@@ -5,6 +5,8 @@ return {
|
|||||||
cmd = "SymbolsOutline",
|
cmd = "SymbolsOutline",
|
||||||
opts = {
|
opts = {
|
||||||
position = "right",
|
position = "right",
|
||||||
|
width = 35,
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -1,10 +1,16 @@
|
|||||||
return {
|
return {
|
||||||
{
|
{
|
||||||
"telescope.nvim",
|
"telescope.nvim",
|
||||||
|
-- 未启用 `editor.telescope` extra 时 Lazy 不会注册 `:Telescope`,自定义 `<cmd>Telescope …` 键会失效。
|
||||||
|
cmd = { "Telescope" },
|
||||||
dependencies = {
|
dependencies = {
|
||||||
{
|
{
|
||||||
"nvim-telescope/telescope-fzf-native.nvim",
|
"nvim-telescope/telescope-fzf-native.nvim",
|
||||||
build = "make",
|
build = "make",
|
||||||
|
-- 确保编译成功,如果 make 失败,尝试 cmake
|
||||||
|
cond = function()
|
||||||
|
return vim.fn.executable("make") == 1 or vim.fn.executable("cmake") == 1
|
||||||
|
end,
|
||||||
},
|
},
|
||||||
"nvim-telescope/telescope-file-browser.nvim",
|
"nvim-telescope/telescope-file-browser.nvim",
|
||||||
},
|
},
|
||||||
@@ -115,32 +121,43 @@ return {
|
|||||||
local telescope = require("telescope")
|
local telescope = require("telescope")
|
||||||
local actions = require("telescope.actions")
|
local actions = require("telescope.actions")
|
||||||
local fb_actions = require("telescope").extensions.file_browser.actions
|
local fb_actions = require("telescope").extensions.file_browser.actions
|
||||||
local trouble = require("trouble.providers.telescope")
|
local trouble = require("trouble.sources.telescope")
|
||||||
|
|
||||||
opts.defaults = vim.tbl_deep_extend("force", opts.defaults, {
|
-- 确保 opts 和其子表存在
|
||||||
|
opts = opts or {}
|
||||||
|
opts.defaults = vim.tbl_deep_extend("force", opts.defaults or {}, {
|
||||||
wrap_results = true,
|
wrap_results = true,
|
||||||
layout_strategy = "horizontal",
|
layout_strategy = "horizontal",
|
||||||
layout_config = { prompt_position = "top" },
|
layout_config = { prompt_position = "top" },
|
||||||
sorting_strategy = "ascending",
|
sorting_strategy = "ascending",
|
||||||
winblend = 0,
|
winblend = 0,
|
||||||
|
-- 确保文件查找器正常工作
|
||||||
|
file_ignore_patterns = {
|
||||||
|
"^.git/",
|
||||||
|
"^node_modules/",
|
||||||
|
"^.DS_Store",
|
||||||
|
},
|
||||||
|
-- 使用 fzf 排序(如果可用)
|
||||||
|
path_display = { "truncate" },
|
||||||
|
dynamic_preview_title = true,
|
||||||
mappings = {
|
mappings = {
|
||||||
i = {
|
i = {
|
||||||
["<C-c>"] = actions.close,
|
["<C-c>"] = actions.close,
|
||||||
["<ESC>"] = actions.close,
|
["<ESC>"] = actions.close,
|
||||||
["<C-j>"] = actions.move_selection_next,
|
["<C-j>"] = actions.move_selection_next,
|
||||||
["<C-k>"] = actions.move_selection_previous,
|
["<C-k>"] = actions.move_selection_previous,
|
||||||
["<c-t>"] = trouble.open_with_trouble,
|
["<c-t>"] = trouble.open,
|
||||||
["<C-q>"] = actions.smart_send_to_qflist + actions.open_qflist,
|
["<C-q>"] = actions.smart_send_to_qflist + actions.open_qflist,
|
||||||
},
|
},
|
||||||
n = {
|
n = {
|
||||||
["<C-j>"] = actions.move_selection_next,
|
["<C-j>"] = actions.move_selection_next,
|
||||||
["<C-k>"] = actions.move_selection_previous,
|
["<C-k>"] = actions.move_selection_previous,
|
||||||
["<c-t>"] = trouble.open_with_trouble,
|
["<c-t>"] = trouble.open,
|
||||||
["<C-q>"] = actions.smart_send_to_qflist + actions.open_qflist
|
["<C-q>"] = actions.smart_send_to_qflist + actions.open_qflist
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
})
|
})
|
||||||
opts.pickers = {
|
opts.pickers = vim.tbl_deep_extend("force", opts.pickers or {}, {
|
||||||
diagnostics = {
|
diagnostics = {
|
||||||
theme = "ivy",
|
theme = "ivy",
|
||||||
initial_mode = "normal",
|
initial_mode = "normal",
|
||||||
@@ -148,8 +165,24 @@ return {
|
|||||||
preview_cutoff = 9999,
|
preview_cutoff = 9999,
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
|
})
|
||||||
|
|
||||||
|
-- Configure live_grep: use ripgrep if available, otherwise fallback to grep
|
||||||
|
-- Note: Install ripgrep for better performance: brew install ripgrep
|
||||||
|
if vim.fn.executable("rg") == 0 then
|
||||||
|
vim.notify("ripgrep (rg) not found. live_grep will use grep (slower). Install with: brew install ripgrep", vim.log.levels.WARN)
|
||||||
|
opts.defaults = opts.defaults or {}
|
||||||
|
opts.defaults.vimgrep_arguments = {
|
||||||
|
"grep",
|
||||||
|
"--recursive",
|
||||||
|
"--no-heading",
|
||||||
|
"--with-filename",
|
||||||
|
"--line-number",
|
||||||
|
"--column",
|
||||||
|
"--smart-case",
|
||||||
}
|
}
|
||||||
opts.extensions = {
|
end
|
||||||
|
opts.extensions = vim.tbl_deep_extend("force", opts.extensions or {}, {
|
||||||
file_browser = {
|
file_browser = {
|
||||||
theme = "dropdown",
|
theme = "dropdown",
|
||||||
-- disables netrw and use telescope-file-browser in its place
|
-- disables netrw and use telescope-file-browser in its place
|
||||||
@@ -178,10 +211,16 @@ return {
|
|||||||
},
|
},
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
}
|
})
|
||||||
telescope.setup(opts)
|
telescope.setup(opts)
|
||||||
|
|
||||||
|
-- 安全地加载扩展
|
||||||
|
pcall(function()
|
||||||
require("telescope").load_extension("fzf")
|
require("telescope").load_extension("fzf")
|
||||||
|
end)
|
||||||
|
pcall(function()
|
||||||
require("telescope").load_extension("file_browser")
|
require("telescope").load_extension("file_browser")
|
||||||
|
end)
|
||||||
end,
|
end,
|
||||||
},
|
},
|
||||||
}
|
}
|
||||||
@@ -15,15 +15,24 @@ return {
|
|||||||
"graphql",
|
"graphql",
|
||||||
"html",
|
"html",
|
||||||
"java",
|
"java",
|
||||||
|
"javascript",
|
||||||
"json",
|
"json",
|
||||||
"lua",
|
"latex",
|
||||||
|
-- "lua", "vim", "vimdoc", "query", "markdown" 等由 Neovim 内置,
|
||||||
|
-- 不在此安装,避免 nvim-treesitter 装的新版 parser 与内置 query 不兼容。
|
||||||
|
"norg",
|
||||||
"php",
|
"php",
|
||||||
|
"proto", -- Protocol Buffers
|
||||||
|
"regex",
|
||||||
"rust",
|
"rust",
|
||||||
"scss",
|
"scss",
|
||||||
"sql",
|
"sql",
|
||||||
"svelte",
|
"svelte",
|
||||||
"toml",
|
"toml",
|
||||||
"tsx",
|
"tsx",
|
||||||
|
"typescript",
|
||||||
|
"typst",
|
||||||
|
"vue",
|
||||||
"yaml",
|
"yaml",
|
||||||
},
|
},
|
||||||
|
|
||||||
@@ -57,8 +66,9 @@ return {
|
|||||||
},
|
},
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
config = function(_, opts)
|
config = function()
|
||||||
require("nvim-treesitter.configs").setup(opts)
|
-- LazyVim 已经通过 opts 自动调用了 setup
|
||||||
|
-- 这里只做额外的配置
|
||||||
|
|
||||||
-- MDX
|
-- MDX
|
||||||
vim.filetype.add({
|
vim.filetype.add({
|
||||||
|
|||||||
@@ -12,7 +12,7 @@ function M.get_loc()
|
|||||||
end
|
end
|
||||||
info = info or me
|
info = info or me
|
||||||
local source = info.source:sub(2)
|
local source = info.source:sub(2)
|
||||||
source = vim.loop.fs_realpath(source) or source
|
source = vim.uv.fs_realpath(source) or source
|
||||||
return source .. ":" .. info.linedefined
|
return source .. ":" .. info.linedefined
|
||||||
end
|
end
|
||||||
|
|
||||||
@@ -47,7 +47,7 @@ function M.dump(...)
|
|||||||
if vim.tbl_isempty(value) then
|
if vim.tbl_isempty(value) then
|
||||||
value = nil
|
value = nil
|
||||||
else
|
else
|
||||||
value = vim.tbl_islist(value) and vim.tbl_count(value) <= 1 and value[1] or value
|
value = vim.islist(value) and vim.tbl_count(value) <= 1 and value[1] or value
|
||||||
end
|
end
|
||||||
M._dump(value)
|
M._dump(value)
|
||||||
end
|
end
|
||||||
|
|||||||
+31
-191
@@ -1,212 +1,52 @@
|
|||||||
local lv_utils = {}
|
local M = {}
|
||||||
|
|
||||||
|
function M.yank_filepath()
|
||||||
-- copy current filepath to clipboard
|
local p = vim.fn.expand("%:p")
|
||||||
function lv_utils.yank_filepath()
|
|
||||||
local p = vim.fn.expand('%:p')
|
|
||||||
local line = vim.fn.line(".")
|
local line = vim.fn.line(".")
|
||||||
p = p .. ":" .. line
|
p = p .. ":" .. line
|
||||||
print(p)
|
print(p)
|
||||||
vim.fn.setreg('+',p)
|
vim.fn.setreg("+", p)
|
||||||
end
|
end
|
||||||
|
|
||||||
function lv_utils.yank_file_dir()
|
function M.yank_file_dir()
|
||||||
local p = vim.fn.expand('%:p:h')
|
local p = vim.fn.expand("%:p:h")
|
||||||
print(p)
|
print(p)
|
||||||
vim.fn.setreg('+',p)
|
vim.fn.setreg("+", p)
|
||||||
end
|
end
|
||||||
|
|
||||||
|
function M.open_md()
|
||||||
function lv_utils.open_md()
|
local p = vim.fn.expand("%:p")
|
||||||
local p = vim.fn.expand('%:p')
|
|
||||||
vim.fn.system("open -a marktext " .. p)
|
vim.fn.system("open -a marktext " .. p)
|
||||||
end
|
end
|
||||||
|
|
||||||
function lv_utils.define_augroups(definitions) -- {{{1
|
function M.file_exists(name)
|
||||||
-- Create autocommand groups based on the passed definitions
|
|
||||||
--
|
|
||||||
-- The key will be the name of the group, and each definition
|
|
||||||
-- within the group should have:
|
|
||||||
-- 1. Trigger
|
|
||||||
-- 2. Pattern
|
|
||||||
-- 3. Text
|
|
||||||
-- just like how they would normally be defined from Vim itself
|
|
||||||
for group_name, definition in pairs(definitions) do
|
|
||||||
vim.cmd('augroup ' .. group_name)
|
|
||||||
vim.cmd('autocmd!')
|
|
||||||
|
|
||||||
for _, def in pairs(definition) do
|
|
||||||
local command = table.concat(vim.tbl_flatten {'autocmd', def}, ' ')
|
|
||||||
vim.cmd(command)
|
|
||||||
end
|
|
||||||
|
|
||||||
vim.cmd('augroup END')
|
|
||||||
end
|
|
||||||
end
|
|
||||||
|
|
||||||
-- lsp
|
|
||||||
|
|
||||||
function lv_utils.add_to_workspace_folder()
|
|
||||||
vim.lsp.buf.add_workspace_folder()
|
|
||||||
end
|
|
||||||
|
|
||||||
function lv_utils.clear_references()
|
|
||||||
vim.lsp.buf.clear_references()
|
|
||||||
end
|
|
||||||
|
|
||||||
function lv_utils.code_action()
|
|
||||||
vim.lsp.buf.code_action()
|
|
||||||
end
|
|
||||||
|
|
||||||
function lv_utils.declaration()
|
|
||||||
vim.lsp.buf.declaration()
|
|
||||||
vim.lsp.buf.clear_references()
|
|
||||||
end
|
|
||||||
|
|
||||||
function lv_utils.definition()
|
|
||||||
vim.lsp.buf.definition()
|
|
||||||
vim.lsp.buf.clear_references()
|
|
||||||
end
|
|
||||||
|
|
||||||
function lv_utils.document_highlight()
|
|
||||||
vim.lsp.buf.document_highlight()
|
|
||||||
end
|
|
||||||
|
|
||||||
function lv_utils.document_symbol()
|
|
||||||
vim.lsp.buf.document_symbol()
|
|
||||||
end
|
|
||||||
|
|
||||||
function lv_utils.formatting()
|
|
||||||
vim.lsp.buf.formatting()
|
|
||||||
end
|
|
||||||
|
|
||||||
function lv_utils.formatting_sync()
|
|
||||||
vim.lsp.buf.formatting_sync()
|
|
||||||
end
|
|
||||||
|
|
||||||
function lv_utils.hover()
|
|
||||||
vim.lsp.buf.hover()
|
|
||||||
end
|
|
||||||
|
|
||||||
function lv_utils.implementation()
|
|
||||||
vim.lsp.buf.implementation()
|
|
||||||
end
|
|
||||||
|
|
||||||
function lv_utils.incoming_calls()
|
|
||||||
vim.lsp.buf.incoming_calls()
|
|
||||||
end
|
|
||||||
|
|
||||||
function lv_utils.list_workspace_folders()
|
|
||||||
vim.lsp.buf.list_workspace_folders()
|
|
||||||
end
|
|
||||||
|
|
||||||
function lv_utils.outgoing_calls()
|
|
||||||
vim.lsp.buf.outgoing_calls()
|
|
||||||
end
|
|
||||||
|
|
||||||
function lv_utils.range_code_action()
|
|
||||||
vim.lsp.buf.range_code_action()
|
|
||||||
end
|
|
||||||
|
|
||||||
function lv_utils.range_formatting()
|
|
||||||
vim.lsp.buf.range_formatting()
|
|
||||||
end
|
|
||||||
|
|
||||||
function lv_utils.references()
|
|
||||||
vim.lsp.buf.references()
|
|
||||||
vim.lsp.buf.clear_references()
|
|
||||||
end
|
|
||||||
|
|
||||||
function lv_utils.remove_workspace_folder()
|
|
||||||
vim.lsp.buf.remove_workspace_folder()
|
|
||||||
end
|
|
||||||
|
|
||||||
function lv_utils.rename()
|
|
||||||
vim.lsp.buf.rename()
|
|
||||||
end
|
|
||||||
|
|
||||||
function lv_utils.signature_help()
|
|
||||||
vim.lsp.buf.signature_help()
|
|
||||||
end
|
|
||||||
|
|
||||||
function lv_utils.type_definition()
|
|
||||||
vim.lsp.buf.type_definition()
|
|
||||||
end
|
|
||||||
|
|
||||||
function lv_utils.workspace_symbol()
|
|
||||||
vim.lsp.buf.workspace_symbol()
|
|
||||||
end
|
|
||||||
|
|
||||||
-- diagnostic
|
|
||||||
|
|
||||||
function lv_utils.get_all()
|
|
||||||
vim.lsp.diagnostic.get_all()
|
|
||||||
end
|
|
||||||
|
|
||||||
function lv_utils.get_next()
|
|
||||||
vim.lsp.diagnostic.get_next()
|
|
||||||
end
|
|
||||||
|
|
||||||
function lv_utils.get_prev()
|
|
||||||
vim.lsp.diagnostic.get_prev()
|
|
||||||
end
|
|
||||||
|
|
||||||
function lv_utils.goto_next()
|
|
||||||
vim.lsp.diagnostic.goto_next()
|
|
||||||
end
|
|
||||||
|
|
||||||
function lv_utils.goto_prev()
|
|
||||||
vim.lsp.diagnostic.goto_prev()
|
|
||||||
end
|
|
||||||
|
|
||||||
function lv_utils.show_line_diagnostics()
|
|
||||||
vim.lsp.diagnostic.show_line_diagnostics()
|
|
||||||
end
|
|
||||||
|
|
||||||
-- git signs
|
|
||||||
|
|
||||||
function lv_utils.next_hunk()
|
|
||||||
require('gitsigns').next_hunk()
|
|
||||||
end
|
|
||||||
|
|
||||||
function lv_utils.prev_hunk()
|
|
||||||
require('gitsigns').prev_hunk()
|
|
||||||
end
|
|
||||||
|
|
||||||
function lv_utils.stage_hunk()
|
|
||||||
require('gitsigns').stage_hunk()
|
|
||||||
end
|
|
||||||
|
|
||||||
function lv_utils.undo_stage_hunk()
|
|
||||||
require('gitsigns').undo_stage_hunk()
|
|
||||||
end
|
|
||||||
|
|
||||||
function lv_utils.reset_hunk()
|
|
||||||
require('gitsigns').reset_hunk()
|
|
||||||
end
|
|
||||||
|
|
||||||
function lv_utils.reset_buffer()
|
|
||||||
require('gitsigns').reset_buffer()
|
|
||||||
end
|
|
||||||
|
|
||||||
function lv_utils.preview_hunk()
|
|
||||||
require('gitsigns').preview_hunk()
|
|
||||||
end
|
|
||||||
|
|
||||||
function lv_utils.blame_line()
|
|
||||||
require('gitsigns').blame_line()
|
|
||||||
end
|
|
||||||
|
|
||||||
-- misc
|
|
||||||
function lv_utils.file_exists(name)
|
|
||||||
local f = io.open(name, "r")
|
local f = io.open(name, "r")
|
||||||
if f ~= nil then
|
if f ~= nil then
|
||||||
io.close(f)
|
io.close(f)
|
||||||
return true
|
return true
|
||||||
else
|
end
|
||||||
return false
|
return false
|
||||||
end
|
end
|
||||||
|
|
||||||
|
--- 无写权限时用 sudo 保存当前文件(对应 :W)
|
||||||
|
function M.sudo_write()
|
||||||
|
local name = vim.api.nvim_buf_get_name(0)
|
||||||
|
if name == "" then
|
||||||
|
vim.notify("W: 当前 buffer 没有文件名", vim.log.levels.ERROR)
|
||||||
|
return
|
||||||
end
|
end
|
||||||
|
|
||||||
return lv_utils
|
local ok, err = pcall(function()
|
||||||
|
vim.cmd("write !sudo tee % > /dev/null")
|
||||||
|
vim.cmd("edit!")
|
||||||
|
end)
|
||||||
|
if not ok then
|
||||||
|
local msg = vim.split(tostring(err), "\n", { plain = true })[1]
|
||||||
|
vim.notify("W: 保存失败 — " .. msg, vim.log.levels.ERROR)
|
||||||
|
return
|
||||||
|
end
|
||||||
|
|
||||||
|
vim.notify("W: 已保存 " .. name, vim.log.levels.INFO)
|
||||||
|
end
|
||||||
|
|
||||||
|
return M
|
||||||
|
|||||||
Regular → Executable
+27
@@ -1 +1,28 @@
|
|||||||
#!/bin/bash
|
#!/bin/bash
|
||||||
|
# 清理 Homebrew 缓存与无用文件
|
||||||
|
|
||||||
|
SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)"
|
||||||
|
# shellcheck disable=SC1091
|
||||||
|
source "$SCRIPT_DIR/common/help.sh"
|
||||||
|
|
||||||
|
usage() {
|
||||||
|
cat <<'EOF'
|
||||||
|
用法: brew-clean
|
||||||
|
|
||||||
|
执行 Homebrew 清理(brew cleanup),删除旧版本与缓存。
|
||||||
|
|
||||||
|
选项:
|
||||||
|
-h, --help 显示此帮助
|
||||||
|
EOF
|
||||||
|
}
|
||||||
|
|
||||||
|
dotfile_help_requested "${1:-}" && dotfile_show_help
|
||||||
|
|
||||||
|
set -euo pipefail
|
||||||
|
|
||||||
|
if ! command -v brew &>/dev/null; then
|
||||||
|
echo "错误: 未找到 brew,请先安装 Homebrew" >&2
|
||||||
|
exit 1
|
||||||
|
fi
|
||||||
|
|
||||||
|
brew cleanup
|
||||||
|
|||||||
Regular → Executable
+22
@@ -1,3 +1,25 @@
|
|||||||
|
#!/bin/bash
|
||||||
|
# 使用国内镜像安装 Homebrew
|
||||||
|
|
||||||
|
SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)"
|
||||||
|
# shellcheck disable=SC1091
|
||||||
|
source "$SCRIPT_DIR/common/help.sh"
|
||||||
|
|
||||||
|
usage() {
|
||||||
|
cat <<'EOF'
|
||||||
|
用法: brew-install
|
||||||
|
|
||||||
|
通过中科大镜像安装 Homebrew(设置 HOMEBREW_* 镜像环境变量后执行官方安装脚本)。
|
||||||
|
|
||||||
|
选项:
|
||||||
|
-h, --help 显示此帮助
|
||||||
|
EOF
|
||||||
|
}
|
||||||
|
|
||||||
|
dotfile_help_requested "${1:-}" && dotfile_show_help
|
||||||
|
|
||||||
|
set -euo pipefail
|
||||||
|
|
||||||
export HOMEBREW_BREW_GIT_REMOTE="https://mirrors.ustc.edu.cn/brew.git"
|
export HOMEBREW_BREW_GIT_REMOTE="https://mirrors.ustc.edu.cn/brew.git"
|
||||||
export HOMEBREW_CORE_GIT_REMOTE="https://mirrors.ustc.edu.cn/homebrew-core.git"
|
export HOMEBREW_CORE_GIT_REMOTE="https://mirrors.ustc.edu.cn/homebrew-core.git"
|
||||||
export HOMEBREW_BOTTLE_DOMAIN="https://mirrors.ustc.edu.cn/homebrew-bottles"
|
export HOMEBREW_BOTTLE_DOMAIN="https://mirrors.ustc.edu.cn/homebrew-bottles"
|
||||||
|
|||||||
Executable
+57
@@ -0,0 +1,57 @@
|
|||||||
|
#!/usr/bin/env bash
|
||||||
|
# 将标准输入复制到系统剪贴板(macOS / Linux X11 / Wayland)
|
||||||
|
|
||||||
|
SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)"
|
||||||
|
# shellcheck disable=SC1091
|
||||||
|
source "$SCRIPT_DIR/common/help.sh"
|
||||||
|
|
||||||
|
usage() {
|
||||||
|
cat <<'EOF'
|
||||||
|
用法: clip-copy
|
||||||
|
|
||||||
|
从 stdin 读取内容并写入系统剪贴板。
|
||||||
|
|
||||||
|
支持:
|
||||||
|
macOS: pbcopy
|
||||||
|
Wayland: wl-copy
|
||||||
|
X11: xclip / xsel
|
||||||
|
|
||||||
|
选项:
|
||||||
|
-h, --help 显示此帮助
|
||||||
|
|
||||||
|
示例:
|
||||||
|
echo hello | clip-copy
|
||||||
|
cat file.txt | clip-copy
|
||||||
|
EOF
|
||||||
|
}
|
||||||
|
|
||||||
|
dotfile_help_requested "${1:-}" && dotfile_show_help
|
||||||
|
|
||||||
|
set -euo pipefail
|
||||||
|
|
||||||
|
if command -v pbcopy >/dev/null 2>&1; then
|
||||||
|
pbcopy
|
||||||
|
elif [ "${XDG_SESSION_TYPE:-}" = wayland ] || [ -n "${WAYLAND_DISPLAY:-}" ]; then
|
||||||
|
if command -v wl-copy >/dev/null 2>&1; then
|
||||||
|
wl-copy --no-newline
|
||||||
|
else
|
||||||
|
echo "clip-copy: wl-copy not found (Wayland session)" >&2
|
||||||
|
exit 1
|
||||||
|
fi
|
||||||
|
elif [ -n "${DISPLAY:-}" ]; then
|
||||||
|
if command -v xclip >/dev/null 2>&1; then
|
||||||
|
xclip -selection clipboard
|
||||||
|
elif command -v xsel >/dev/null 2>&1; then
|
||||||
|
xsel --clipboard --input
|
||||||
|
else
|
||||||
|
echo "clip-copy: xclip or xsel not found (DISPLAY=$DISPLAY)" >&2
|
||||||
|
exit 1
|
||||||
|
fi
|
||||||
|
elif command -v xclip >/dev/null 2>&1; then
|
||||||
|
xclip -selection clipboard
|
||||||
|
elif command -v xsel >/dev/null 2>&1; then
|
||||||
|
xsel --clipboard --input
|
||||||
|
else
|
||||||
|
echo "clip-copy: no clipboard tool (pbcopy / wl-copy / xclip / xsel)" >&2
|
||||||
|
exit 1
|
||||||
|
fi
|
||||||
@@ -0,0 +1,11 @@
|
|||||||
|
# 供 script/*.sh source:统一处理 -h / --help
|
||||||
|
# 调用方需先定义 usage() 函数
|
||||||
|
|
||||||
|
dotfile_help_requested() {
|
||||||
|
[[ "${1:-}" == "-h" || "${1:-}" == "--help" ]]
|
||||||
|
}
|
||||||
|
|
||||||
|
dotfile_show_help() {
|
||||||
|
usage
|
||||||
|
exit 0
|
||||||
|
}
|
||||||
@@ -0,0 +1,23 @@
|
|||||||
|
#!/bin/bash
|
||||||
|
|
||||||
|
# all name are lowercase
|
||||||
|
# x86_64 -> amd64
|
||||||
|
|
||||||
|
# get os and arch
|
||||||
|
function get_os_arch() {
|
||||||
|
local os_arch=$(get_os)-$(get_arch)
|
||||||
|
echo $os_arch
|
||||||
|
}
|
||||||
|
|
||||||
|
function get_os() {
|
||||||
|
local os=$(uname -s)
|
||||||
|
os=$(echo $os | tr '[:upper:]' '[:lower:]')
|
||||||
|
echo $os
|
||||||
|
}
|
||||||
|
|
||||||
|
function get_arch() {
|
||||||
|
local arch=$(uname -m)
|
||||||
|
arch=$(echo $arch | tr '[:upper:]' '[:lower:]')
|
||||||
|
arch=$(echo $arch | sed 's/x86_64/amd64/')
|
||||||
|
echo $arch
|
||||||
|
}
|
||||||
@@ -0,0 +1,71 @@
|
|||||||
|
# CN_REGION 持久化到 ~/.config/fish/custom.fish(供 toggle-region / ff region 使用)
|
||||||
|
|
||||||
|
dotfile_region_custom_fish() {
|
||||||
|
echo "${HOME}/.config/fish/custom.fish"
|
||||||
|
}
|
||||||
|
|
||||||
|
dotfile_region_read() {
|
||||||
|
local custom val
|
||||||
|
custom="$(dotfile_region_custom_fish)"
|
||||||
|
if [ -f "$custom" ]; then
|
||||||
|
val=$(
|
||||||
|
grep -E '^[[:space:]]*set[[:space:]]+-gx[[:space:]]+CN_REGION[[:space:]]+' "$custom" 2>/dev/null |
|
||||||
|
tail -1 |
|
||||||
|
awk '{print $NF}'
|
||||||
|
)
|
||||||
|
if [ -n "$val" ]; then
|
||||||
|
echo "$val"
|
||||||
|
return 0
|
||||||
|
fi
|
||||||
|
fi
|
||||||
|
if [ -n "${CN_REGION:-}" ]; then
|
||||||
|
echo "$CN_REGION"
|
||||||
|
else
|
||||||
|
echo "0"
|
||||||
|
fi
|
||||||
|
}
|
||||||
|
|
||||||
|
dotfile_region_write() {
|
||||||
|
local val="$1"
|
||||||
|
local custom tmp
|
||||||
|
custom="$(dotfile_region_custom_fish)"
|
||||||
|
mkdir -p "$(dirname "$custom")"
|
||||||
|
tmp="$(mktemp)"
|
||||||
|
if [ -f "$custom" ]; then
|
||||||
|
grep -Ev '^[[:space:]]*# dotfile: CN_REGION|^[[:space:]]*set[[:space:]]+-gx[[:space:]]+CN_REGION[[:space:]]+' \
|
||||||
|
"$custom" >"$tmp" || true
|
||||||
|
else
|
||||||
|
: >"$tmp"
|
||||||
|
fi
|
||||||
|
{
|
||||||
|
cat "$tmp"
|
||||||
|
if [ -s "$tmp" ]; then
|
||||||
|
echo
|
||||||
|
fi
|
||||||
|
echo "# dotfile: CN_REGION (managed by ff region / toggle-region)"
|
||||||
|
echo "set -gx CN_REGION ${val}"
|
||||||
|
} >"${custom}.new"
|
||||||
|
mv "${custom}.new" "$custom"
|
||||||
|
rm -f "$tmp"
|
||||||
|
}
|
||||||
|
|
||||||
|
dotfile_region_toggle() {
|
||||||
|
local current next
|
||||||
|
current="$(dotfile_region_read)"
|
||||||
|
if [ "$current" = "1" ]; then
|
||||||
|
next=0
|
||||||
|
else
|
||||||
|
next=1
|
||||||
|
fi
|
||||||
|
dotfile_region_write "$next"
|
||||||
|
export CN_REGION="$next"
|
||||||
|
echo "$next"
|
||||||
|
}
|
||||||
|
|
||||||
|
dotfile_region_label() {
|
||||||
|
if [ "$1" = "1" ]; then
|
||||||
|
echo "国内"
|
||||||
|
else
|
||||||
|
echo "国外"
|
||||||
|
fi
|
||||||
|
}
|
||||||
@@ -0,0 +1,50 @@
|
|||||||
|
#!/bin/bash
|
||||||
|
# 执行命令并在失败时通过飞书 Webhook 发送通知
|
||||||
|
|
||||||
|
SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)"
|
||||||
|
# shellcheck disable=SC1091
|
||||||
|
source "$SCRIPT_DIR/common/help.sh"
|
||||||
|
|
||||||
|
usage() {
|
||||||
|
cat <<'EOF'
|
||||||
|
用法: croncheck <command> [args...]
|
||||||
|
|
||||||
|
执行给定命令;若退出码非 0,向配置的飞书 Webhook 发送失败通知。
|
||||||
|
最终以原命令的退出码退出。
|
||||||
|
|
||||||
|
选项:
|
||||||
|
-h, --help 显示此帮助
|
||||||
|
|
||||||
|
示例:
|
||||||
|
croncheck /path/to/backup.sh
|
||||||
|
croncheck docker compose up -d
|
||||||
|
EOF
|
||||||
|
}
|
||||||
|
|
||||||
|
dotfile_help_requested "${1:-}" && dotfile_show_help
|
||||||
|
|
||||||
|
if [ "$#" -lt 1 ]; then
|
||||||
|
echo "错误: 缺少要执行的命令" >&2
|
||||||
|
usage
|
||||||
|
exit 1
|
||||||
|
fi
|
||||||
|
|
||||||
|
WEBHOOK_URL="https://open.feishu.cn/open-apis/bot/v2/hook/808eafd8-a79f-4af0-b730-8442cf15933b"
|
||||||
|
|
||||||
|
OUTPUT=$("$@" 2>&1)
|
||||||
|
EXIT_CODE=$?
|
||||||
|
|
||||||
|
if [ $EXIT_CODE -ne 0 ]; then
|
||||||
|
JSON_DATA=$(cat <<EOF
|
||||||
|
{
|
||||||
|
"msg_type": "text",
|
||||||
|
"content": {
|
||||||
|
"text": "Cron job fail\n\nCommand: $*\nError: $(echo "$OUTPUT" | sed 's/"/\\"/g')"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
EOF
|
||||||
|
)
|
||||||
|
curl -s -X POST -H "Content-Type: application/json" -d "$JSON_DATA" "$WEBHOOK_URL"
|
||||||
|
fi
|
||||||
|
|
||||||
|
exit $EXIT_CODE
|
||||||
@@ -0,0 +1,38 @@
|
|||||||
|
#!/bin/bash
|
||||||
|
# 将 Cursor 用户配置链接到 dotfile(macOS)
|
||||||
|
|
||||||
|
SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)"
|
||||||
|
# shellcheck disable=SC1091
|
||||||
|
source "$SCRIPT_DIR/common/help.sh"
|
||||||
|
|
||||||
|
usage() {
|
||||||
|
cat <<'EOF'
|
||||||
|
用法: cursor-init
|
||||||
|
|
||||||
|
链接 ~/.dotfile/cursor/config/ 下的 settings.json、keybindings.json
|
||||||
|
到 Cursor 用户目录(macOS)。
|
||||||
|
|
||||||
|
已有文件会备份为 *.bak。
|
||||||
|
|
||||||
|
选项:
|
||||||
|
-h, --help 显示此帮助
|
||||||
|
EOF
|
||||||
|
}
|
||||||
|
|
||||||
|
dotfile_help_requested "${1:-}" && dotfile_show_help
|
||||||
|
|
||||||
|
set -euo pipefail
|
||||||
|
|
||||||
|
echo 'Cursor config init'
|
||||||
|
|
||||||
|
src="$HOME/Library/Application Support/Cursor/User/settings.json"
|
||||||
|
if [ -f "$src" ]; then
|
||||||
|
mv "$src" "$src.bak"
|
||||||
|
fi
|
||||||
|
ln -s "$HOME/.dotfile/cursor/config/settings.json" "$src"
|
||||||
|
|
||||||
|
src="$HOME/Library/Application Support/Cursor/User/keybindings.json"
|
||||||
|
if [ -f "$src" ]; then
|
||||||
|
mv "$src" "$src.bak"
|
||||||
|
fi
|
||||||
|
ln -s "$HOME/.dotfile/cursor/config/keybindings.json" "$src"
|
||||||
@@ -0,0 +1,205 @@
|
|||||||
|
#!/bin/bash
|
||||||
|
|
||||||
|
# Debian 国内镜像源更换脚本(交互式选择镜像)
|
||||||
|
# 支持 Debian 9/10/11/12/13 (stretch/buster/bullseye/bookworm/trixie)
|
||||||
|
|
||||||
|
SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)"
|
||||||
|
# shellcheck disable=SC1091
|
||||||
|
source "$SCRIPT_DIR/common/help.sh"
|
||||||
|
|
||||||
|
usage() {
|
||||||
|
cat <<'EOF'
|
||||||
|
用法: sudo debian-init
|
||||||
|
|
||||||
|
交互式将 Debian apt 源更换为国内镜像(需 root)。
|
||||||
|
自动检测版本代号,备份原 sources.list。
|
||||||
|
|
||||||
|
选项:
|
||||||
|
-h, --help 显示此帮助
|
||||||
|
EOF
|
||||||
|
}
|
||||||
|
|
||||||
|
dotfile_help_requested "${1:-}" && dotfile_show_help
|
||||||
|
|
||||||
|
set -e
|
||||||
|
|
||||||
|
# 颜色定义
|
||||||
|
RED='\033[0;31m'
|
||||||
|
GREEN='\033[0;32m'
|
||||||
|
YELLOW='\033[1;33m'
|
||||||
|
NC='\033[0m' # No Color
|
||||||
|
|
||||||
|
# 检测是否为 root 用户
|
||||||
|
if [ "$EUID" -ne 0 ]; then
|
||||||
|
echo -e "${RED}错误: 请使用 root 权限运行此脚本${NC}"
|
||||||
|
echo "使用: sudo $0"
|
||||||
|
exit 1
|
||||||
|
fi
|
||||||
|
|
||||||
|
# 检测 Debian 版本
|
||||||
|
detect_debian_version() {
|
||||||
|
if [ -f /etc/debian_version ]; then
|
||||||
|
DEBIAN_VERSION=$(cat /etc/debian_version | cut -d. -f1)
|
||||||
|
if [ -f /etc/os-release ]; then
|
||||||
|
. /etc/os-release
|
||||||
|
CODENAME=$(echo $VERSION_CODENAME | tr '[:upper:]' '[:lower:]')
|
||||||
|
else
|
||||||
|
case $DEBIAN_VERSION in
|
||||||
|
9) CODENAME="stretch" ;;
|
||||||
|
10) CODENAME="buster" ;;
|
||||||
|
11) CODENAME="bullseye" ;;
|
||||||
|
12) CODENAME="bookworm" ;;
|
||||||
|
13) CODENAME="trixie" ;;
|
||||||
|
*) CODENAME="bookworm" ;; # 默认使用 bookworm
|
||||||
|
esac
|
||||||
|
fi
|
||||||
|
else
|
||||||
|
echo -e "${RED}错误: 未检测到 Debian 系统${NC}"
|
||||||
|
exit 1
|
||||||
|
fi
|
||||||
|
}
|
||||||
|
|
||||||
|
# 显示可用的镜像源
|
||||||
|
show_mirrors() {
|
||||||
|
echo -e "${YELLOW}可用的国内镜像源:${NC}"
|
||||||
|
echo "1) 阿里云 (aliyun)"
|
||||||
|
echo "2) 清华大学 (tsinghua)"
|
||||||
|
echo "3) 中科大 (ustc)"
|
||||||
|
echo "4) 网易 (163)"
|
||||||
|
echo "5) 华为云 (huawei)"
|
||||||
|
echo "6) 腾讯云 (tencent)"
|
||||||
|
}
|
||||||
|
|
||||||
|
# 获取镜像源 URL
|
||||||
|
get_mirror_url() {
|
||||||
|
local mirror=$1
|
||||||
|
local codename=$2
|
||||||
|
|
||||||
|
case $mirror in
|
||||||
|
aliyun)
|
||||||
|
echo "https://mirrors.aliyun.com/debian/"
|
||||||
|
;;
|
||||||
|
tsinghua)
|
||||||
|
echo "https://mirrors.tuna.tsinghua.edu.cn/debian/"
|
||||||
|
;;
|
||||||
|
ustc)
|
||||||
|
echo "https://mirrors.ustc.edu.cn/debian/"
|
||||||
|
;;
|
||||||
|
163)
|
||||||
|
echo "https://mirrors.163.com/debian/"
|
||||||
|
;;
|
||||||
|
huawei)
|
||||||
|
echo "https://mirrors.huaweicloud.com/debian/"
|
||||||
|
;;
|
||||||
|
tencent)
|
||||||
|
echo "https://mirrors.cloud.tencent.com/debian/"
|
||||||
|
;;
|
||||||
|
*)
|
||||||
|
echo "https://mirrors.aliyun.com/debian/" # 默认使用阿里云
|
||||||
|
;;
|
||||||
|
esac
|
||||||
|
}
|
||||||
|
|
||||||
|
# 获取安全更新镜像源 URL
|
||||||
|
get_security_mirror_url() {
|
||||||
|
local mirror=$1
|
||||||
|
|
||||||
|
case $mirror in
|
||||||
|
aliyun)
|
||||||
|
echo "https://mirrors.aliyun.com/debian-security/"
|
||||||
|
;;
|
||||||
|
tsinghua)
|
||||||
|
echo "https://mirrors.tuna.tsinghua.edu.cn/debian-security/"
|
||||||
|
;;
|
||||||
|
ustc)
|
||||||
|
echo "https://mirrors.ustc.edu.cn/debian-security/"
|
||||||
|
;;
|
||||||
|
163)
|
||||||
|
echo "https://mirrors.163.com/debian-security/"
|
||||||
|
;;
|
||||||
|
huawei)
|
||||||
|
echo "https://mirrors.huaweicloud.com/debian-security/"
|
||||||
|
;;
|
||||||
|
tencent)
|
||||||
|
echo "https://mirrors.cloud.tencent.com/debian-security/"
|
||||||
|
;;
|
||||||
|
*)
|
||||||
|
echo "https://mirrors.aliyun.com/debian-security/" # 默认使用阿里云
|
||||||
|
;;
|
||||||
|
esac
|
||||||
|
}
|
||||||
|
|
||||||
|
# 生成 sources.list 内容
|
||||||
|
generate_sources_list() {
|
||||||
|
local mirror_url=$1
|
||||||
|
local security_mirror_url=$2
|
||||||
|
local codename=$3
|
||||||
|
|
||||||
|
cat <<EOF
|
||||||
|
# Debian ${codename} 国内镜像源
|
||||||
|
# 生成时间: $(date '+%Y-%m-%d %H:%M:%S')
|
||||||
|
|
||||||
|
# 默认仓库
|
||||||
|
deb ${mirror_url} ${codename} main contrib non-free non-free-firmware
|
||||||
|
deb ${mirror_url} ${codename}-updates main contrib non-free non-free-firmware
|
||||||
|
deb ${mirror_url} ${codename}-backports main contrib non-free non-free-firmware
|
||||||
|
|
||||||
|
# 安全更新
|
||||||
|
deb ${security_mirror_url} ${codename}-security main contrib non-free non-free-firmware
|
||||||
|
|
||||||
|
EOF
|
||||||
|
}
|
||||||
|
|
||||||
|
# 主函数
|
||||||
|
main() {
|
||||||
|
echo -e "${GREEN}=== Debian 国内镜像源更换脚本 ===${NC}\n"
|
||||||
|
|
||||||
|
# 检测 Debian 版本
|
||||||
|
detect_debian_version
|
||||||
|
echo -e "${GREEN}检测到 Debian 版本: ${DEBIAN_VERSION} (${CODENAME})${NC}\n"
|
||||||
|
|
||||||
|
# 显示可用镜像源
|
||||||
|
show_mirrors
|
||||||
|
echo ""
|
||||||
|
read -p "请选择镜像源 (1-6,默认 1): " choice
|
||||||
|
choice=${choice:-1}
|
||||||
|
|
||||||
|
case $choice in
|
||||||
|
1) MIRROR="aliyun" ;;
|
||||||
|
2) MIRROR="tsinghua" ;;
|
||||||
|
3) MIRROR="ustc" ;;
|
||||||
|
4) MIRROR="163" ;;
|
||||||
|
5) MIRROR="huawei" ;;
|
||||||
|
6) MIRROR="tencent" ;;
|
||||||
|
*) MIRROR="aliyun" ;;
|
||||||
|
esac
|
||||||
|
|
||||||
|
MIRROR_URL=$(get_mirror_url $MIRROR $CODENAME)
|
||||||
|
SECURITY_MIRROR_URL=$(get_security_mirror_url $MIRROR)
|
||||||
|
echo -e "\n${GREEN}选择的镜像源: ${MIRROR} (${MIRROR_URL})${NC}"
|
||||||
|
echo -e "${GREEN}安全更新镜像源: ${SECURITY_MIRROR_URL}${NC}\n"
|
||||||
|
|
||||||
|
# 备份原有 sources.list
|
||||||
|
SOURCES_LIST="/etc/apt/sources.list"
|
||||||
|
BACKUP_FILE="/etc/apt/sources.list.backup.$(date +%Y%m%d_%H%M%S)"
|
||||||
|
|
||||||
|
if [ -f "$SOURCES_LIST" ]; then
|
||||||
|
cp "$SOURCES_LIST" "$BACKUP_FILE"
|
||||||
|
echo -e "${GREEN}已备份原有 sources.list 到: ${BACKUP_FILE}${NC}"
|
||||||
|
fi
|
||||||
|
|
||||||
|
# 生成新的 sources.list
|
||||||
|
generate_sources_list "$MIRROR_URL" "$SECURITY_MIRROR_URL" "$CODENAME" > "$SOURCES_LIST"
|
||||||
|
echo -e "${GREEN}已更新 sources.list${NC}\n"
|
||||||
|
|
||||||
|
# 更新软件包列表
|
||||||
|
echo -e "${YELLOW}正在更新软件包列表...${NC}"
|
||||||
|
apt update
|
||||||
|
|
||||||
|
echo -e "\n${GREEN}✓ 镜像源更换完成!${NC}"
|
||||||
|
echo -e "${GREEN}备份文件位置: ${BACKUP_FILE}${NC}"
|
||||||
|
}
|
||||||
|
|
||||||
|
# 运行主函数
|
||||||
|
main
|
||||||
|
|
||||||
Executable
+145
@@ -0,0 +1,145 @@
|
|||||||
|
#!/bin/bash
|
||||||
|
|
||||||
|
# Debian 一键更换清华镜像源脚本 (DEB822 格式)
|
||||||
|
# 支持 Debian 12 (bookworm)、13 (trixie)、14 (forky) 及后续版本
|
||||||
|
|
||||||
|
SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)"
|
||||||
|
# shellcheck disable=SC1091
|
||||||
|
source "$SCRIPT_DIR/common/help.sh"
|
||||||
|
|
||||||
|
usage() {
|
||||||
|
cat <<'EOF'
|
||||||
|
用法: sudo debian-tuna
|
||||||
|
|
||||||
|
将 Debian apt 源一键替换为清华 DEB822 格式源(需 root)。
|
||||||
|
会处理 /etc/apt/sources.list.d/debian.sources 与旧版 sources.list。
|
||||||
|
|
||||||
|
选项:
|
||||||
|
-h, --help 显示此帮助
|
||||||
|
EOF
|
||||||
|
}
|
||||||
|
|
||||||
|
dotfile_help_requested "${1:-}" && dotfile_show_help
|
||||||
|
|
||||||
|
set -e
|
||||||
|
|
||||||
|
# 颜色定义
|
||||||
|
RED='\033[0;31m'
|
||||||
|
GREEN='\033[0;32m'
|
||||||
|
YELLOW='\033[1;33m'
|
||||||
|
NC='\033[0m' # No Color
|
||||||
|
|
||||||
|
TUNA_DEBIAN="https://mirrors.tuna.tsinghua.edu.cn/debian"
|
||||||
|
SECURITY_DEBIAN="https://security.debian.org/debian-security"
|
||||||
|
KEYRING="/usr/share/keyrings/debian-archive-keyring.gpg"
|
||||||
|
SOURCES_FILE="/etc/apt/sources.list.d/debian.sources"
|
||||||
|
LEGACY_LIST="/etc/apt/sources.list"
|
||||||
|
|
||||||
|
# 检测是否为 root 用户
|
||||||
|
if [ "$EUID" -ne 0 ]; then
|
||||||
|
echo -e "${RED}错误: 请使用 root 权限运行此脚本${NC}"
|
||||||
|
echo "使用: sudo $0"
|
||||||
|
exit 1
|
||||||
|
fi
|
||||||
|
|
||||||
|
# 检测 Debian 版本与代号
|
||||||
|
detect_debian() {
|
||||||
|
if [ ! -f /etc/os-release ]; then
|
||||||
|
echo -e "${RED}错误: 未检测到 /etc/os-release,无法识别系统${NC}"
|
||||||
|
exit 1
|
||||||
|
fi
|
||||||
|
|
||||||
|
. /etc/os-release
|
||||||
|
|
||||||
|
if [ "${ID:-}" != "debian" ]; then
|
||||||
|
echo -e "${YELLOW}警告: 当前系统 ID 为 '${ID:-未知}',并非纯 Debian,继续操作可能不适用${NC}"
|
||||||
|
fi
|
||||||
|
|
||||||
|
DEBIAN_VERSION=${VERSION_ID:-$(cut -d. -f1 /etc/debian_version 2>/dev/null)}
|
||||||
|
CODENAME=$(echo "${VERSION_CODENAME:-}" | tr '[:upper:]' '[:lower:]')
|
||||||
|
|
||||||
|
# 若 os-release 未提供代号,按主版本号兜底推断
|
||||||
|
if [ -z "$CODENAME" ]; then
|
||||||
|
case "${DEBIAN_VERSION%%.*}" in
|
||||||
|
12) CODENAME="bookworm" ;;
|
||||||
|
13) CODENAME="trixie" ;;
|
||||||
|
14) CODENAME="forky" ;;
|
||||||
|
*)
|
||||||
|
echo -e "${RED}错误: 无法确定 Debian 代号 (版本: ${DEBIAN_VERSION:-未知})${NC}"
|
||||||
|
exit 1
|
||||||
|
;;
|
||||||
|
esac
|
||||||
|
fi
|
||||||
|
|
||||||
|
case "${DEBIAN_VERSION%%.*}" in
|
||||||
|
12 | 13 | 14) ;;
|
||||||
|
*)
|
||||||
|
echo -e "${YELLOW}警告: 当前 Debian 主版本为 '${DEBIAN_VERSION%%.*}',脚本主要面向 12/13/14,仍将按代号 '${CODENAME}' 生成配置${NC}"
|
||||||
|
;;
|
||||||
|
esac
|
||||||
|
}
|
||||||
|
|
||||||
|
# 生成 DEB822 格式的 .sources 内容
|
||||||
|
generate_sources() {
|
||||||
|
cat <<EOF
|
||||||
|
Types: deb
|
||||||
|
URIs: ${TUNA_DEBIAN}
|
||||||
|
Suites: ${CODENAME} ${CODENAME}-updates ${CODENAME}-backports
|
||||||
|
Components: main contrib non-free non-free-firmware
|
||||||
|
Signed-By: ${KEYRING}
|
||||||
|
|
||||||
|
# 默认注释了源码镜像以提高 apt update 速度,如有需要可自行取消注释
|
||||||
|
# Types: deb-src
|
||||||
|
# URIs: ${TUNA_DEBIAN}
|
||||||
|
# Suites: ${CODENAME} ${CODENAME}-updates ${CODENAME}-backports
|
||||||
|
# Components: main contrib non-free non-free-firmware
|
||||||
|
# Signed-By: ${KEYRING}
|
||||||
|
|
||||||
|
# 以下安全更新软件源为官方源配置
|
||||||
|
Types: deb
|
||||||
|
URIs: ${SECURITY_DEBIAN}
|
||||||
|
Suites: ${CODENAME}-security
|
||||||
|
Components: main contrib non-free non-free-firmware
|
||||||
|
Signed-By: ${KEYRING}
|
||||||
|
|
||||||
|
# Types: deb-src
|
||||||
|
# URIs: ${SECURITY_DEBIAN}
|
||||||
|
# Suites: ${CODENAME}-security
|
||||||
|
# Components: main contrib non-free non-free-firmware
|
||||||
|
# Signed-By: ${KEYRING}
|
||||||
|
EOF
|
||||||
|
}
|
||||||
|
|
||||||
|
main() {
|
||||||
|
echo -e "${GREEN}=== Debian 一键更换清华镜像源 (DEB822) ===${NC}\n"
|
||||||
|
|
||||||
|
detect_debian
|
||||||
|
echo -e "${GREEN}检测到 Debian ${DEBIAN_VERSION} (${CODENAME})${NC}\n"
|
||||||
|
|
||||||
|
local stamp
|
||||||
|
stamp=$(date +%Y%m%d_%H%M%S)
|
||||||
|
|
||||||
|
# 备份并停用旧版 sources.list,避免与 DEB822 配置重复
|
||||||
|
if [ -s "$LEGACY_LIST" ]; then
|
||||||
|
cp "$LEGACY_LIST" "${LEGACY_LIST}.backup.${stamp}"
|
||||||
|
: >"$LEGACY_LIST"
|
||||||
|
echo -e "${GREEN}已备份并清空旧版 sources.list -> ${LEGACY_LIST}.backup.${stamp}${NC}"
|
||||||
|
fi
|
||||||
|
|
||||||
|
# 备份已有的 .sources 文件
|
||||||
|
if [ -f "$SOURCES_FILE" ]; then
|
||||||
|
cp "$SOURCES_FILE" "${SOURCES_FILE}.backup.${stamp}"
|
||||||
|
echo -e "${GREEN}已备份原有 debian.sources -> ${SOURCES_FILE}.backup.${stamp}${NC}"
|
||||||
|
fi
|
||||||
|
|
||||||
|
mkdir -p "$(dirname "$SOURCES_FILE")"
|
||||||
|
generate_sources >"$SOURCES_FILE"
|
||||||
|
echo -e "${GREEN}已写入清华镜像源配置 -> ${SOURCES_FILE}${NC}\n"
|
||||||
|
|
||||||
|
echo -e "${YELLOW}正在更新软件包列表...${NC}"
|
||||||
|
apt update
|
||||||
|
|
||||||
|
echo -e "\n${GREEN}✓ 清华镜像源更换完成!${NC}"
|
||||||
|
}
|
||||||
|
|
||||||
|
main
|
||||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user