Compare commits
228 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 | |||
| f0186eaefb | |||
| 6283ab3cfb | |||
| 22b880e642 | |||
| 2a68d143a8 | |||
| 05053a87b7 | |||
| 27dff38b7c | |||
| cabec5bd63 | |||
| e430d2a721 | |||
| a48619031d | |||
| 7ef3a2f073 | |||
| ddda0a2a02 | |||
| 685b477e1b | |||
| dc01c402c8 | |||
| b1698fb7cf | |||
| fad2c5fed9 | |||
| 23c83906b5 | |||
| 820d501911 | |||
| 0b7374d42a | |||
| b49026c12e | |||
| 3fad1b11f4 | |||
| b63fb4494c | |||
| a103031734 | |||
| f12a4caccb | |||
| fa8b291b1d | |||
| 47f16510b2 | |||
| d2aa7bad86 | |||
| 9b9a929281 | |||
| e10a850527 | |||
| 7d83cc0d6e | |||
| d42ae1bb29 | |||
| 96f4181e5f | |||
| 53ab00c196 | |||
| 1def7fc888 | |||
| c20d5f6024 | |||
| 6c36082c4f | |||
| 2072b48df7 | |||
| 83dbf07703 | |||
| 1fd0cdaf63 | |||
| 06cf9bae2c | |||
| 77d16ce217 | |||
| 8bc810fd39 | |||
| 3180d287e7 | |||
| 46a920d574 |
@@ -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 等均可使用)。
|
||||
@@ -2,3 +2,11 @@
|
||||
.SpaceVim.d/init.vim
|
||||
.DS_Store
|
||||
nvim/plugin
|
||||
_set_env_*.sh
|
||||
_set_env.sh
|
||||
log
|
||||
cn.sh
|
||||
__pycache__
|
||||
Makefile
|
||||
.claude/
|
||||
script/clash.sh
|
||||
|
||||
-356
@@ -1,356 +0,0 @@
|
||||
run:
|
||||
tests: false
|
||||
timeout: 1m
|
||||
linters:
|
||||
enable:
|
||||
- govet
|
||||
- staticcheck
|
||||
- unused
|
||||
- ineffassign
|
||||
- typecheck
|
||||
- exportloopref
|
||||
- bodyclose
|
||||
- gofmt
|
||||
- stylecheck
|
||||
- revive
|
||||
- errorlint # finds code that will cause problems with the error wrapping scheme introduced in Go 1.13
|
||||
disable-all: true
|
||||
linters-settings:
|
||||
staticcheck:
|
||||
# SAxxxx checks in https://staticcheck.io/docs/configuration/options/#checks
|
||||
checks: ["all"]
|
||||
revive:
|
||||
# Maximum number of open files at the same time.
|
||||
# See https://github.com/mgechev/revive#command-line-flags
|
||||
# Defaults to unlimited.
|
||||
max-open-files: 2048
|
||||
# When set to false, ignores files with "GENERATED" header, similar to golint.
|
||||
# See https://github.com/mgechev/revive#available-rules for details.
|
||||
# Default: false
|
||||
ignore-generated-header: true
|
||||
# Sets the default severity.
|
||||
# See https://github.com/mgechev/revive#configuration
|
||||
# Default: warning
|
||||
severity: error
|
||||
# Enable all available rules.
|
||||
# Default: false
|
||||
enable-all-rules: true
|
||||
# Sets the default failure confidence.
|
||||
# This means that linting errors with less than 0.8 confidence will be ignored.
|
||||
# Default: 0.8
|
||||
confidence: 0.1
|
||||
rules:
|
||||
# https://github.com/mgechev/revive/blob/master/RULES_DESCRIPTIONS.md#add-constant
|
||||
- name: add-constant
|
||||
severity: warning
|
||||
disabled: false
|
||||
arguments:
|
||||
- maxLitCount: "3"
|
||||
allowStrs: '""'
|
||||
allowInts: "0,1,2"
|
||||
allowFloats: "0.0,0.,1.0,1.,2.0,2."
|
||||
# https://github.com/mgechev/revive/blob/master/RULES_DESCRIPTIONS.md#argument-limit
|
||||
- name: argument-limit
|
||||
severity: warning
|
||||
disabled: false
|
||||
arguments: [4]
|
||||
# https://github.com/mgechev/revive/blob/master/RULES_DESCRIPTIONS.md#atomic
|
||||
- name: atomic
|
||||
severity: warning
|
||||
disabled: false
|
||||
# https://github.com/mgechev/revive/blob/master/RULES_DESCRIPTIONS.md#banned-characters
|
||||
- name: banned-characters
|
||||
severity: warning
|
||||
disabled: false
|
||||
arguments: ["Ω", "Σ", "σ", "7"]
|
||||
# https://github.com/mgechev/revive/blob/master/RULES_DESCRIPTIONS.md#bare-return
|
||||
- name: bare-return
|
||||
severity: warning
|
||||
disabled: false
|
||||
# https://github.com/mgechev/revive/blob/master/RULES_DESCRIPTIONS.md#blank-imports
|
||||
- name: blank-imports
|
||||
severity: warning
|
||||
disabled: false
|
||||
# https://github.com/mgechev/revive/blob/master/RULES_DESCRIPTIONS.md#bool-literal-in-expr
|
||||
- name: bool-literal-in-expr
|
||||
severity: warning
|
||||
disabled: false
|
||||
# https://github.com/mgechev/revive/blob/master/RULES_DESCRIPTIONS.md#call-to-gc
|
||||
- name: call-to-gc
|
||||
severity: warning
|
||||
disabled: false
|
||||
# https://github.com/mgechev/revive/blob/master/RULES_DESCRIPTIONS.md#cognitive-complexity
|
||||
- name: cognitive-complexity
|
||||
severity: warning
|
||||
disabled: false
|
||||
arguments: [7]
|
||||
# https://github.com/mgechev/revive/blob/master/RULES_DESCRIPTIONS.md#confusing-naming
|
||||
- name: confusing-naming
|
||||
severity: warning
|
||||
disabled: false
|
||||
# https://github.com/mgechev/revive/blob/master/RULES_DESCRIPTIONS.md#confusing-results
|
||||
- name: confusing-results
|
||||
severity: warning
|
||||
disabled: false
|
||||
# https://github.com/mgechev/revive/blob/master/RULES_DESCRIPTIONS.md#constant-logical-expr
|
||||
- name: constant-logical-expr
|
||||
severity: warning
|
||||
disabled: false
|
||||
# https://github.com/mgechev/revive/blob/master/RULES_DESCRIPTIONS.md#context-as-argument
|
||||
- name: context-as-argument
|
||||
severity: warning
|
||||
disabled: false
|
||||
# https://github.com/mgechev/revive/blob/master/RULES_DESCRIPTIONS.md#context-keys-type
|
||||
- name: context-keys-type
|
||||
severity: warning
|
||||
disabled: false
|
||||
# https://github.com/mgechev/revive/blob/master/RULES_DESCRIPTIONS.md#cyclomatic
|
||||
- name: cyclomatic
|
||||
severity: warning
|
||||
disabled: false
|
||||
arguments: [3]
|
||||
# https://github.com/mgechev/revive/blob/master/RULES_DESCRIPTIONS.md#datarace
|
||||
- name: datarace
|
||||
severity: warning
|
||||
disabled: false
|
||||
# https://github.com/mgechev/revive/blob/master/RULES_DESCRIPTIONS.md#deep-exit
|
||||
- name: deep-exit
|
||||
severity: warning
|
||||
disabled: false
|
||||
# https://github.com/mgechev/revive/blob/master/RULES_DESCRIPTIONS.md#defer
|
||||
- name: defer
|
||||
severity: warning
|
||||
disabled: false
|
||||
arguments:
|
||||
- ["call-chain", "loop"]
|
||||
# https://github.com/mgechev/revive/blob/master/RULES_DESCRIPTIONS.md#dot-imports
|
||||
- name: dot-imports
|
||||
severity: warning
|
||||
disabled: false
|
||||
# https://github.com/mgechev/revive/blob/master/RULES_DESCRIPTIONS.md#duplicated-imports
|
||||
- name: duplicated-imports
|
||||
severity: warning
|
||||
disabled: false
|
||||
# https://github.com/mgechev/revive/blob/master/RULES_DESCRIPTIONS.md#early-return
|
||||
- name: early-return
|
||||
severity: warning
|
||||
disabled: false
|
||||
# https://github.com/mgechev/revive/blob/master/RULES_DESCRIPTIONS.md#empty-block
|
||||
- name: empty-block
|
||||
severity: warning
|
||||
disabled: false
|
||||
# https://github.com/mgechev/revive/blob/master/RULES_DESCRIPTIONS.md#empty-lines
|
||||
- name: empty-lines
|
||||
severity: warning
|
||||
disabled: false
|
||||
# https://github.com/mgechev/revive/blob/master/RULES_DESCRIPTIONS.md#error-naming
|
||||
- name: error-naming
|
||||
severity: warning
|
||||
disabled: false
|
||||
# https://github.com/mgechev/revive/blob/master/RULES_DESCRIPTIONS.md#error-return
|
||||
- name: error-return
|
||||
severity: warning
|
||||
disabled: false
|
||||
# https://github.com/mgechev/revive/blob/master/RULES_DESCRIPTIONS.md#error-strings
|
||||
- name: error-strings
|
||||
severity: warning
|
||||
disabled: false
|
||||
# https://github.com/mgechev/revive/blob/master/RULES_DESCRIPTIONS.md#errorf
|
||||
- name: errorf
|
||||
severity: warning
|
||||
disabled: false
|
||||
# https://github.com/mgechev/revive/blob/master/RULES_DESCRIPTIONS.md#exported
|
||||
- name: exported
|
||||
severity: warning
|
||||
disabled: false
|
||||
arguments:
|
||||
- "checkPrivateReceivers"
|
||||
- "sayRepetitiveInsteadOfStutters"
|
||||
# https://github.com/mgechev/revive/blob/master/RULES_DESCRIPTIONS.md#file-header
|
||||
- name: file-header
|
||||
severity: warning
|
||||
disabled: false
|
||||
arguments:
|
||||
- This is the text that must appear at the top of source files.
|
||||
# https://github.com/mgechev/revive/blob/master/RULES_DESCRIPTIONS.md#flag-parameter
|
||||
- name: flag-parameter
|
||||
severity: warning
|
||||
disabled: false
|
||||
# https://github.com/mgechev/revive/blob/master/RULES_DESCRIPTIONS.md#function-result-limit
|
||||
- name: function-result-limit
|
||||
severity: warning
|
||||
disabled: false
|
||||
arguments: [2]
|
||||
# https://github.com/mgechev/revive/blob/master/RULES_DESCRIPTIONS.md#function-length
|
||||
- name: function-length
|
||||
severity: warning
|
||||
disabled: false
|
||||
arguments: [10, 0]
|
||||
# https://github.com/mgechev/revive/blob/master/RULES_DESCRIPTIONS.md#get-return
|
||||
- name: get-return
|
||||
severity: warning
|
||||
disabled: false
|
||||
# https://github.com/mgechev/revive/blob/master/RULES_DESCRIPTIONS.md#identical-branches
|
||||
- name: identical-branches
|
||||
severity: warning
|
||||
disabled: false
|
||||
# https://github.com/mgechev/revive/blob/master/RULES_DESCRIPTIONS.md#if-return
|
||||
- name: if-return
|
||||
severity: warning
|
||||
disabled: false
|
||||
# https://github.com/mgechev/revive/blob/master/RULES_DESCRIPTIONS.md#increment-decrement
|
||||
- name: increment-decrement
|
||||
severity: warning
|
||||
disabled: false
|
||||
# https://github.com/mgechev/revive/blob/master/RULES_DESCRIPTIONS.md#indent-error-flow
|
||||
- name: indent-error-flow
|
||||
severity: warning
|
||||
disabled: false
|
||||
# https://github.com/mgechev/revive/blob/master/RULES_DESCRIPTIONS.md#imports-blacklist
|
||||
- name: imports-blacklist
|
||||
severity: warning
|
||||
disabled: false
|
||||
arguments:
|
||||
- "crypto/md5"
|
||||
- "crypto/sha1"
|
||||
# https://github.com/mgechev/revive/blob/master/RULES_DESCRIPTIONS.md#import-shadowing
|
||||
- name: import-shadowing
|
||||
severity: warning
|
||||
disabled: false
|
||||
# https://github.com/mgechev/revive/blob/master/RULES_DESCRIPTIONS.md#line-length-limit
|
||||
- name: line-length-limit
|
||||
severity: warning
|
||||
disabled: false
|
||||
arguments: [80]
|
||||
# https://github.com/mgechev/revive/blob/master/RULES_DESCRIPTIONS.md#max-public-structs
|
||||
- name: max-public-structs
|
||||
severity: warning
|
||||
disabled: false
|
||||
arguments: [3]
|
||||
# https://github.com/mgechev/revive/blob/master/RULES_DESCRIPTIONS.md#modifies-parameter
|
||||
- name: modifies-parameter
|
||||
severity: warning
|
||||
disabled: false
|
||||
# https://github.com/mgechev/revive/blob/master/RULES_DESCRIPTIONS.md#modifies-value-receiver
|
||||
- name: modifies-value-receiver
|
||||
severity: warning
|
||||
disabled: false
|
||||
# https://github.com/mgechev/revive/blob/master/RULES_DESCRIPTIONS.md#nested-structs
|
||||
- name: nested-structs
|
||||
severity: warning
|
||||
disabled: false
|
||||
# https://github.com/mgechev/revive/blob/master/RULES_DESCRIPTIONS.md#optimize-operands-order
|
||||
- name: optimize-operands-order
|
||||
severity: warning
|
||||
disabled: false
|
||||
# https://github.com/mgechev/revive/blob/master/RULES_DESCRIPTIONS.md#package-comments
|
||||
- name: package-comments
|
||||
severity: warning
|
||||
disabled: false
|
||||
# https://github.com/mgechev/revive/blob/master/RULES_DESCRIPTIONS.md#range
|
||||
- name: range
|
||||
severity: warning
|
||||
disabled: false
|
||||
# https://github.com/mgechev/revive/blob/master/RULES_DESCRIPTIONS.md#range-val-in-closure
|
||||
- name: range-val-in-closure
|
||||
severity: warning
|
||||
disabled: false
|
||||
# https://github.com/mgechev/revive/blob/master/RULES_DESCRIPTIONS.md#range-val-address
|
||||
- name: range-val-address
|
||||
severity: warning
|
||||
disabled: false
|
||||
# https://github.com/mgechev/revive/blob/master/RULES_DESCRIPTIONS.md#receiver-naming
|
||||
- name: receiver-naming
|
||||
severity: warning
|
||||
disabled: false
|
||||
# https://github.com/mgechev/revive/blob/master/RULES_DESCRIPTIONS.md#redefines-builtin-id
|
||||
- name: redefines-builtin-id
|
||||
severity: warning
|
||||
disabled: false
|
||||
# https://github.com/mgechev/revive/blob/master/RULES_DESCRIPTIONS.md#string-of-int
|
||||
- name: string-of-int
|
||||
severity: warning
|
||||
disabled: false
|
||||
# https://github.com/mgechev/revive/blob/master/RULES_DESCRIPTIONS.md#string-format
|
||||
- name: string-format
|
||||
severity: warning
|
||||
disabled: false
|
||||
arguments:
|
||||
- - 'core.WriteError[1].Message'
|
||||
- '/^([^A-Z]|$)/'
|
||||
- must not start with a capital letter
|
||||
- - 'fmt.Errorf[0]'
|
||||
- '/(^|[^\.!?])$/'
|
||||
- must not end in punctuation
|
||||
- - panic
|
||||
- '/^[^\n]*$/'
|
||||
- must not contain line breaks
|
||||
# https://github.com/mgechev/revive/blob/master/RULES_DESCRIPTIONS.md#struct-tag
|
||||
- name: struct-tag
|
||||
severity: warning
|
||||
disabled: false
|
||||
# https://github.com/mgechev/revive/blob/master/RULES_DESCRIPTIONS.md#superfluous-else
|
||||
- name: superfluous-else
|
||||
severity: warning
|
||||
disabled: false
|
||||
# https://github.com/mgechev/revive/blob/master/RULES_DESCRIPTIONS.md#time-equal
|
||||
- name: time-equal
|
||||
severity: warning
|
||||
disabled: false
|
||||
# https://github.com/mgechev/revive/blob/master/RULES_DESCRIPTIONS.md#time-naming
|
||||
- name: time-naming
|
||||
severity: warning
|
||||
disabled: false
|
||||
# https://github.com/mgechev/revive/blob/master/RULES_DESCRIPTIONS.md#var-naming
|
||||
- name: var-naming
|
||||
severity: warning
|
||||
disabled: false
|
||||
arguments:
|
||||
- ["ID"] # AllowList
|
||||
- ["VM"] # DenyList
|
||||
# https://github.com/mgechev/revive/blob/master/RULES_DESCRIPTIONS.md#var-declaration
|
||||
- name: var-declaration
|
||||
severity: warning
|
||||
disabled: false
|
||||
# https://github.com/mgechev/revive/blob/master/RULES_DESCRIPTIONS.md#unconditional-recursion
|
||||
- name: unconditional-recursion
|
||||
severity: warning
|
||||
disabled: false
|
||||
# https://github.com/mgechev/revive/blob/master/RULES_DESCRIPTIONS.md#unexported-naming
|
||||
- name: unexported-naming
|
||||
severity: warning
|
||||
disabled: false
|
||||
# https://github.com/mgechev/revive/blob/master/RULES_DESCRIPTIONS.md#unexported-return
|
||||
- name: unexported-return
|
||||
disabled: true
|
||||
# https://github.com/mgechev/revive/blob/master/RULES_DESCRIPTIONS.md#unhandled-error
|
||||
- name: unhandled-error
|
||||
severity: warning
|
||||
disabled: false
|
||||
arguments:
|
||||
- "fmt.Printf"
|
||||
- "myFunction"
|
||||
# https://github.com/mgechev/revive/blob/master/RULES_DESCRIPTIONS.md#unnecessary-stmt
|
||||
- name: unnecessary-stmt
|
||||
severity: warning
|
||||
disabled: false
|
||||
# https://github.com/mgechev/revive/blob/master/RULES_DESCRIPTIONS.md#unreachable-code
|
||||
- name: unreachable-code
|
||||
severity: warning
|
||||
disabled: false
|
||||
# https://github.com/mgechev/revive/blob/master/RULES_DESCRIPTIONS.md#unused-parameter
|
||||
- name: unused-parameter
|
||||
severity: warning
|
||||
disabled: false
|
||||
# https://github.com/mgechev/revive/blob/master/RULES_DESCRIPTIONS.md#unused-receiver
|
||||
- name: unused-receiver
|
||||
severity: warning
|
||||
disabled: false
|
||||
# https://github.com/mgechev/revive/blob/master/RULES_DESCRIPTIONS.md#useless-break
|
||||
- name: useless-break
|
||||
severity: warning
|
||||
disabled: false
|
||||
# https://github.com/mgechev/revive/blob/master/RULES_DESCRIPTIONS.md#waitgroup-by-value
|
||||
- name: waitgroup-by-value
|
||||
severity: warning
|
||||
disabled: false
|
||||
-45
@@ -1,45 +0,0 @@
|
||||
setw -g mode-keys vi
|
||||
#unbind ^b
|
||||
#set -g prefix 'C-a'
|
||||
#set -g prefix `
|
||||
#bind-key ` send-prefix
|
||||
bind r source-file ~/.tmux.conf \; display-message "Config reloaded"
|
||||
|
||||
# 切分面板
|
||||
# unbind '"'
|
||||
bind - splitw -v -c '#{pane_current_path}'
|
||||
# unbind %
|
||||
bind | splitw -h -c '#{pane_current_path}'
|
||||
|
||||
# 开启 24bit 真彩色,tmux > 2.2 开始支持
|
||||
set -g default-terminal "screen-256color"
|
||||
set-option -ga terminal-overrides ",*256col*:Tc"
|
||||
|
||||
set-option -g mouse on
|
||||
|
||||
# 绑定hjkl键为面板切换的上下左右键
|
||||
bind k select-pane -U # 绑定k为↑
|
||||
bind j select-pane -D # 绑定j为↓
|
||||
bind h select-pane -L # 绑定h为←
|
||||
bind l select-pane -R # 绑定l为→
|
||||
|
||||
# 绑定Ctrl+hjkl键为面板上下左右调整边缘的快捷指令
|
||||
bind -r K resizep -U 10 # 绑定Ctrl+k为往↑调整面板边缘10个单元格
|
||||
bind -r J resizep -D 10 # 绑定Ctrl+j为往↓调整面板边缘10个单元格
|
||||
bind -r H resizep -L 10 # 绑定Ctrl+h为往←调整面板边缘10个单元格
|
||||
bind -r L resizep -R 10 # 绑定Ctrl+l为往→调整面板边缘10个单元格
|
||||
|
||||
# 设置tmux的延迟,文档说当有干扰的时候可以设置这个参数,比如影响vim编辑的时候
|
||||
set -s escape-time 1
|
||||
|
||||
# 设置窗口名为当前目录名
|
||||
set-option -g status-interval 10
|
||||
set-option -g automatic-rename on
|
||||
set-option -g automatic-rename-format '#{b:pane_current_path}'
|
||||
|
||||
set-option -g set-titles on
|
||||
#set-option -g set-titles-string "#{session_name} - #{host}"
|
||||
set-option -g set-titles-string "#{session_name}"
|
||||
|
||||
# tmux resurrect
|
||||
run-shell ~/.tmux/plugins/tmux-resurrect/resurrect.tmux
|
||||
@@ -1,146 +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"
|
||||
export HOMEBREW_NO_AUTO_UPDATE=true # 禁止 brew 每次安装软件都 upate
|
||||
|
||||
# git
|
||||
export REVIEW_BASE="master"
|
||||
|
||||
# go
|
||||
export GOPATH=$HOME/go
|
||||
export PATH=$PATH:$GOPATH/bin
|
||||
export GOPROXY=goproxy.cn
|
||||
export GOSUMDB="sum.golang.google.cn"
|
||||
|
||||
|
||||
[ -f ~/.zprofile ] && source ~/.zprofile
|
||||
[ -f ~/.fzf.zsh ] && source ~/.fzf.zsh
|
||||
|
||||
# add z
|
||||
eval "$(lua /usr/local/z.lua/z.lua --init zsh)"
|
||||
|
||||
export PATH="$HOME/.yarn/bin:$HOME/.config/yarn/global/node_modules/.bin:$PATH"
|
||||
export PATH="/usr/local/sbin:$PATH"
|
||||
# add nvim
|
||||
export PATH=$PATH:/usr/local/nvim/bin:/usr/local/osx-64/nvim/bin
|
||||
|
||||
# for terminal notify
|
||||
# https://github.com/julienXX/terminal-notifier
|
||||
alias alert='terminal-notifier -title "Terminal" -message "Done with task! Exit status: $?"' -activate com.apple.Terminal
|
||||
|
||||
# git
|
||||
alias gitb="git branch --show-current | tr -d '\n' | pbcopy"
|
||||
alias ct="go test -gcflags=all=-l -cover ./..."
|
||||
alias ci="golangci-lint -c=~/.dotfile/.golangci.yml run ./..."
|
||||
alias gmt="go mod tidy"
|
||||
alias gt="go test -gcflags=all=-l "
|
||||
alias swg="swag init -o docs -g "
|
||||
alias sws="swagger serve -F=swagger"
|
||||
@@ -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,35 +1,15 @@
|
||||
### 使用
|
||||
|
||||
```shell
|
||||
git clone https://github.com/Laily123/.dotfile.git ~/.dotfile
|
||||
```
|
||||
### zsh 安装
|
||||
安装 zsh
|
||||
```shell
|
||||
https://github.com/robbyrussell/oh-my-zsh/wiki/Installing-ZSH
|
||||
# macOS
|
||||
brew install zsh zsh-completions
|
||||
# CentOS
|
||||
sudo yum -y install zsh
|
||||
# Ubuntu
|
||||
apt install zsh
|
||||
git clone https://github.com/hilaily/.dotfile.git ~/.dotfile
|
||||
```
|
||||
|
||||
安装 oh-my-zsh
|
||||
```shell
|
||||
sh -c "$(wget https://raw.githubusercontent.com/robbyrussell/oh-my-zsh/master/tools/install.sh -O -)"
|
||||
首先安装 fish
|
||||
|
||||
`brew install fish` 或者 `apt install fish`
|
||||
|
||||
然后初始化 fish
|
||||
|
||||
```bash
|
||||
bash ~/.dotfile/script/fish-install.sh
|
||||
```
|
||||
初始化配置
|
||||
```shell
|
||||
ln -sf ~/.dotfile/.zshrc ~/
|
||||
ln -sf ~/.dotfile/laily.zsh-theme ~/.oh-my-zsh/themes/
|
||||
ln -sf ~/.dotfile/.tmux.conf ~/
|
||||
ln -sf ~/.dotfile/.gitconfig ~/.gitconfig
|
||||
```
|
||||
|
||||
### spacevim 安装
|
||||
安装
|
||||
`curl -sLf https://spacevim.org/install.sh | bash`
|
||||
初始化配置
|
||||
`ln -sf ~/.dotfile/.SpaceVim.d ~/`
|
||||
|
||||
|
||||
|
||||
@@ -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
|
||||
@@ -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
|
||||
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
|
||||
+139
-38
@@ -1,47 +1,148 @@
|
||||
# 不设全局 user.name / user.email,避免未匹配目录误用身份。
|
||||
# useConfigOnly(Git 2.8+):未配置时拒绝 commit,而不是猜系统用户名。
|
||||
[user]
|
||||
name = Laily
|
||||
email = i@laily.net
|
||||
useConfigOnly = true
|
||||
[core]
|
||||
autocrlf = input
|
||||
quotepath = false
|
||||
editor = /usr/local/bin/nvim
|
||||
excludesfile = ~/.dotfile/git/.gitignore
|
||||
autocrlf = input
|
||||
quotepath = false
|
||||
editor = vim
|
||||
excludesfile = ~/.dotfile/git/.gitignore
|
||||
[init]
|
||||
defaultBranch = main
|
||||
[tag]
|
||||
sort = version:refname
|
||||
[column]
|
||||
ui = auto
|
||||
[color]
|
||||
status = auto
|
||||
diff = auto
|
||||
branch = auto
|
||||
interactive = auto
|
||||
grep = auto
|
||||
ui = auto
|
||||
[push]
|
||||
default = current
|
||||
#default = current
|
||||
default = simple
|
||||
autoSetupRemote = true
|
||||
followTags = true
|
||||
[fetch]
|
||||
prune = true
|
||||
pruneTags = true
|
||||
all = true
|
||||
[pull]
|
||||
rebase = false
|
||||
[diff]
|
||||
algorithm = histogram
|
||||
colorMoved = plain
|
||||
mnemonicPrefix = true
|
||||
renames = true
|
||||
[difftool "nvimdiff"]
|
||||
cmd = nvim -d \"$LOCAL\" \"$REMOTE\"
|
||||
[merge]
|
||||
tool = vimdiff
|
||||
[include]
|
||||
path = ~/.subgitconfig
|
||||
tool = nvimdiff
|
||||
[mergetool "nvimdiff"]
|
||||
cmd = nvim -d \"$LOCAL\" \"$REMOTE\" -ancestor \"$BASE\" -merge \"$MERGED\"
|
||||
trustExitCode = true
|
||||
# 按仓库目录套身份。个人身份在 git/.gitconfig.personal;公司邮箱只放本机 ~/.gitconfig.work。
|
||||
# include 目标文件必须存在,否则该目录下 git 命令会直接 fatal。
|
||||
[includeIf "gitdir:~/.dotfile/"]
|
||||
path = ~/.dotfile/git/.gitconfig.personal
|
||||
[includeIf "gitdir:~/code/"]
|
||||
path = ~/.dotfile/git/.gitconfig.personal
|
||||
[includeIf "gitdir:~/.pouch/"]
|
||||
path = ~/.dotfile/git/.gitconfig.personal
|
||||
[includeIf "gitdir:~/workrepo/"]
|
||||
path = ~/.gitconfig.work
|
||||
[includeIf "gitdir:~/repo/"]
|
||||
path = ~/.gitconfig.work
|
||||
[rebase]
|
||||
autoSquash = true
|
||||
autoStash = true
|
||||
updateRefs = true
|
||||
[commit]
|
||||
verbose = true
|
||||
[rerere]
|
||||
enabled = true
|
||||
autoupdate = true
|
||||
[help]
|
||||
# 旧版 Git 仅支持数字(0=仅提示不执行)。Git 2.36+ 见 .gitconfig.darwin / .gitconfig.linux 可改为 prompt
|
||||
autocorrect = 0
|
||||
[branch]
|
||||
autoSetupMerge = always
|
||||
autoSetupMerge = always
|
||||
sort = -committerdate
|
||||
[alias]
|
||||
au = add -u
|
||||
co = checkout
|
||||
cm = commit -m
|
||||
st = status
|
||||
ap = add -p
|
||||
br = branch
|
||||
pl = pull
|
||||
pt = push origin --tags
|
||||
merge2 = !sh -c '_CURRENT_BRANCH=$(git symbolic-ref --short HEAD) && git checkout $1 && git merge $_CURRENT_BRANCH && git checkout $_CURRENT_BRANCH' -
|
||||
bg = "!f() { git branch | grep $1; }; f"
|
||||
delbr = "!f(){ git branch -D $(git branch | grep \"\b$1\"); };f"
|
||||
can = commit --amend --no-edit
|
||||
cleanbr = "!git branch --merged master | grep -v '\\*\\|master\\|main' | xargs -n 1 git branch -d"
|
||||
# list files which have changed since REVIEW_BASE
|
||||
# (REVIEW_BASE defaults to 'master' in my zshrc)
|
||||
files = !git diff --name-only $(git merge-base HEAD \"$REVIEW_BASE\")
|
||||
au = add -u
|
||||
co = checkout
|
||||
cm = "!f() { if [[ -z \"$1\" ]]; then git commit; else git commit -m \"$*\"; fi; }; f"
|
||||
st = status
|
||||
ap = add -p
|
||||
br = branch
|
||||
ba = branch -a
|
||||
bm = branch --merged
|
||||
bn = branch --no-merged
|
||||
pu = "!git pull origin $(git rev-parse --abbrev-ref HEAD)"
|
||||
pt = push origin --tags
|
||||
ph = push origin HEAD
|
||||
pht = push origin --tags
|
||||
# 把当前分支合进目标分支后回到当前分支;目标已被其它 worktree 占用时在那边 merge
|
||||
merge2 = !$HOME/.dotfile/git/git-merge2.sh
|
||||
bg = "!f() { git branch | grep $1; }; f"
|
||||
delbr = "!f(){ git branch -D $(git branch | grep \"\b$1\"); };f"
|
||||
can = commit --amend --no-edit
|
||||
cleanbr = "!git branch --merged master | grep -v '\\*\\|master\\|main' | xargs -n 1 git branch -d"
|
||||
# list files which have changed since REVIEW_BASE
|
||||
# (REVIEW_BASE defaults to 'master' in my zshrc)
|
||||
files = !git diff --name-only $(git merge-base HEAD \"$REVIEW_BASE\")
|
||||
rmsubmodule = "!f(){ git submodule deinit -f \"$1\"; git rm -f \"$1\"; git commit -m \"Removed submodule $1\"; };f"
|
||||
|
||||
# Same as above, but with a diff stat instead of just names
|
||||
# (better for interactive use)
|
||||
stat = !git diff --stat $(git merge-base HEAD \"$REVIEW_BASE\")
|
||||
# Open all files changed since REVIEW_BASE in Vim tabs
|
||||
# Then, run fugitive's :Gdiff in each tab, and finally
|
||||
# tell vim-gitgutter to show +/- for changes since REVIEW_BASE
|
||||
review = !vim -p $(git files) +\"tabdo Gdiffsplit $REVIEW_BASE\" +\"let g:gitgutter_diff_base = '$REVIEW_BASE'\"
|
||||
# Same as above, but with a diff stat instead of just names
|
||||
# (better for interactive use)
|
||||
stat = !git diff --stat $(git merge-base HEAD \"$REVIEW_BASE\")
|
||||
# Open all files changed since REVIEW_BASE in Vim tabs
|
||||
# Then, run fugitive's :Gdiff in each tab, and finally
|
||||
# tell vim-gitgutter to show +/- for changes since REVIEW_BASE
|
||||
review = !vim -p $(git files) +\"tabdo Gdiffsplit $REVIEW_BASE\" +\"let g:gitgutter_diff_base = '$REVIEW_BASE'\"
|
||||
|
||||
# Same as the above, except specify names of files as arguments,
|
||||
# instead of opening all files:
|
||||
# git reviewone foo.js bar.js
|
||||
reviewone = !vim -p +\"tabdo Gdiff $REVIEW_BASE\" +\"let g:gitgutter_diff_base = '$REVIEW_BASE'\"
|
||||
# Same as the above, except specify names of files as arguments,
|
||||
# instead of opening all files:
|
||||
# git reviewone foo.js bar.js
|
||||
reviewone = !vim -p +\"tabdo Gdiff $REVIEW_BASE\" +\"let g:gitgutter_diff_base = '$REVIEW_BASE'\"
|
||||
#git filter-branch -f --env-filter "GIT_AUTHOR_NAME='Laily'; GIT_AUTHOR_EMAIL='i@laily.net'; GIT_COMMITTER_NAME='Laily'; GIT_COMMITTER_EMAIL='i@laily.net';" HEAD
|
||||
|
||||
# worktree
|
||||
# git wt add <路径> <分支名>
|
||||
# git wt add -b <新分支名> <路径> [基分支名]
|
||||
wt="worktree"
|
||||
#wta="worktree add"
|
||||
#wtb="worktree add -b"
|
||||
wtr="worktree remove"
|
||||
wtl="worktree list"
|
||||
wtp="worktree prune"
|
||||
# 基于分支创建新分支+worktree
|
||||
# git wtb <分支名> [<基分支名|当前分支>]
|
||||
wtb = "!f(){ local base_branch=${2:-HEAD}; local clean_name=$(echo \"$1\" | sed \"s/\\//_/g\"); git worktree add -b \"$1\" \"../$clean_name\" $base_branch; }; f"
|
||||
# 基于指定分支+worktree
|
||||
# git wta <分支名>
|
||||
wta = "!f(){ local base_branch=${1:-HEAD}; local clean_name=$(echo \"$1\" | sed \"s/\\//_/g\"); git worktree add \"../$clean_name\" $base_branch; }; f"
|
||||
[protocol "file"]
|
||||
allow = always
|
||||
|
||||
# https://hyperzsb.io/posts/git-ssl-error/
|
||||
[filter "lfs"]
|
||||
process = git-lfs filter-process
|
||||
required = true
|
||||
clean = git-lfs clean -- %f
|
||||
smudge = git-lfs smudge -- %f
|
||||
[http]
|
||||
sslVerify = false
|
||||
|
||||
# 凭据:统一 wrapper,不依赖 includeIf ondarwin/onlinux(需 Git 2.36+)
|
||||
# 低版本 Git 也会加载本节;回退为 git-credential-store(~/.git-credentials)
|
||||
[credential]
|
||||
helper = !$HOME/.dotfile/git/git-credential.sh
|
||||
|
||||
# 以下 includeIf 需 Git 2.36+;仅用于其它平台差异配置(若有)
|
||||
[includeIf "ondarwin"]
|
||||
path = ~/.dotfile/git/.gitconfig.darwin
|
||||
[includeIf "onlinux"]
|
||||
path = ~/.dotfile/git/.gitconfig.linux
|
||||
|
||||
@@ -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
|
||||
@@ -26,7 +26,14 @@ _testmain.go
|
||||
*.sublime-workspace
|
||||
.idea/
|
||||
.vscode/
|
||||
.SpaceVim.d/.netrwhist
|
||||
.SpaceVim.d/init.vim
|
||||
.DS_Store
|
||||
.log
|
||||
go.work
|
||||
go.work.sum
|
||||
_set_env*.sh
|
||||
.env*
|
||||
!.env.example
|
||||
Makefile.local
|
||||
.netrc
|
||||
|
||||
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
|
||||
@@ -1,20 +1,93 @@
|
||||
#!bin/sh
|
||||
#git clone https://github.com/hilaily/.dotfile.git ~/.dotfile
|
||||
#!/bin/bash
|
||||
# 安装并初始化 Fish、tmux 与 Git 的基础 dotfile 环境
|
||||
|
||||
# 判断 zsh 是否安装
|
||||
#sudo yum update && sudo yum -y install zsh
|
||||
SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)"
|
||||
# shellcheck disable=SC1091
|
||||
source "$SCRIPT_DIR/script/common/help.sh"
|
||||
# shellcheck disable=SC1091
|
||||
source "$SCRIPT_DIR/script/common/os-arch.sh"
|
||||
|
||||
# 判断 oh-my-zsh 是否安装
|
||||
if [ ! -d "$HOME/.oh-my-zsh" ];then
|
||||
export ZSH=$HOME/.oh-my-zsh
|
||||
sh -c "$(wget https://raw.githubusercontent.com/robbyrussell/oh-my-zsh/master/tools/install.sh -O -)"
|
||||
set -euo pipefail
|
||||
|
||||
usage() {
|
||||
cat <<'EOF'
|
||||
用法: ./init.sh
|
||||
|
||||
安装缺失的基础工具(fish、tmux、git),随后依次执行:
|
||||
1. fish-init 初始化 Fish 配置、插件与默认 shell
|
||||
2. tmux-init 初始化 tmux 配置与 TPM 插件
|
||||
3. git-init 初始化 Git 配置
|
||||
|
||||
支持:
|
||||
macOS Homebrew
|
||||
Debian/Ubuntu apt
|
||||
|
||||
选项:
|
||||
-h, --help 显示此帮助
|
||||
|
||||
说明:
|
||||
请以需要配置的普通用户运行,不要使用 sudo 运行本脚本。
|
||||
Linux 安装缺失软件时会请求 sudo;Fish 初始化也会请求 sudo 将 fish
|
||||
加入 /etc/shells。
|
||||
EOF
|
||||
}
|
||||
|
||||
dotfile_help_requested "${1:-}" && dotfile_show_help
|
||||
[[ $# -eq 0 ]] || { echo "错误: 不支持参数: $*" >&2; usage >&2; exit 1; }
|
||||
|
||||
die() {
|
||||
echo "错误: $*" >&2
|
||||
exit 1
|
||||
}
|
||||
|
||||
run_with_sudo() {
|
||||
if [[ $EUID -eq 0 ]]; then
|
||||
"$@"
|
||||
else
|
||||
sudo "$@"
|
||||
fi
|
||||
}
|
||||
|
||||
[[ $EUID -ne 0 ]] || die "请以需要配置的普通用户运行,不要使用 sudo ./init.sh"
|
||||
[[ -d "$HOME/.dotfile" ]] || die "未找到 $HOME/.dotfile;请将仓库放在该路径后重试"
|
||||
[[ "$SCRIPT_DIR" -ef "$HOME/.dotfile" ]] || die "当前仓库不是 $HOME/.dotfile;现有初始化脚本依赖该路径"
|
||||
|
||||
missing_packages=()
|
||||
command -v fish >/dev/null 2>&1 || missing_packages+=(fish)
|
||||
command -v tmux >/dev/null 2>&1 || missing_packages+=(tmux)
|
||||
command -v git >/dev/null 2>&1 || missing_packages+=(git)
|
||||
|
||||
if ((${#missing_packages[@]} == 0)); then
|
||||
echo "基础工具已安装:fish、tmux、git"
|
||||
else
|
||||
echo "安装缺失工具: ${missing_packages[*]}"
|
||||
case "$(get_os)" in
|
||||
darwin)
|
||||
command -v brew >/dev/null 2>&1 || die "未找到 Homebrew;请先运行: bash script/brew-install.sh"
|
||||
brew install "${missing_packages[@]}"
|
||||
;;
|
||||
linux)
|
||||
command -v apt-get >/dev/null 2>&1 || die "仅支持 apt;请先通过系统包管理器安装: ${missing_packages[*]}"
|
||||
run_with_sudo apt-get update
|
||||
run_with_sudo apt-get install -y "${missing_packages[@]}"
|
||||
;;
|
||||
*)
|
||||
die "不支持的系统: $(get_os)"
|
||||
;;
|
||||
esac
|
||||
fi
|
||||
ln -sf ~/.dotfile/.zshrc ~/
|
||||
ln -sf ~/.dotfile/laily.zsh-theme ~/.oh-my-zsh/themes/
|
||||
ln -sf ~/.dotfile/.tmux.conf ~/
|
||||
ln -sf ~/.dotfile/git/.gitconfig ~/.gitconfig
|
||||
|
||||
# install neovim
|
||||
cd /usr/local
|
||||
wget https://github.com/neovim/neovim/releases/download/v0.6.0/nvim-macos.tar.gz
|
||||
tar zxvf nvim-macos.tar.gz
|
||||
echo
|
||||
echo "== 初始化 Fish =="
|
||||
bash "$SCRIPT_DIR/script/fish-init.sh"
|
||||
|
||||
echo
|
||||
echo "== 初始化 tmux =="
|
||||
bash "$SCRIPT_DIR/script/tmux-init.sh"
|
||||
|
||||
echo
|
||||
echo "== 初始化 Git =="
|
||||
bash "$SCRIPT_DIR/script/git-init.sh"
|
||||
|
||||
echo
|
||||
echo "基础环境初始化完成。重新打开终端后 Fish 将作为默认 shell 生效。"
|
||||
|
||||
File diff suppressed because it is too large
Load Diff
@@ -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]
|
||||
@@ -1,20 +0,0 @@
|
||||
# Nvim config
|
||||
|
||||
## Backup plugin version
|
||||
|
||||
Because nvim have some breaking changes sometimes. I can not alwaexys use the latest version of plugins.
|
||||
|
||||
### Backup
|
||||
|
||||
Execute `get_packer_snapshot.sh`. This script will backup all the plugin commit information to a json file.
|
||||
|
||||
### Restore
|
||||
|
||||
document: https://github.com/wbthomason/packer.nvim#usage
|
||||
|
||||
```
|
||||
vim +"PackerSnapshot lock-${version}.json"
|
||||
```
|
||||
|
||||
|
||||
|
||||
@@ -1 +0,0 @@
|
||||
setl ts=2 sw=2
|
||||
@@ -1 +0,0 @@
|
||||
require('lang.go')
|
||||
@@ -1 +0,0 @@
|
||||
setl ts=2 sw=2
|
||||
@@ -1 +0,0 @@
|
||||
setl ts=2 sw=2
|
||||
@@ -1 +0,0 @@
|
||||
setl commentstring={/*%s*/} ts=2 sw=2
|
||||
@@ -1 +0,0 @@
|
||||
require('lang.js-ts')
|
||||
@@ -1,3 +0,0 @@
|
||||
vim.cmd('set ts=2') -- Insert 2 spaces for a tab
|
||||
vim.cmd('set sw=2') -- Change the number of space characters inserted for indentation
|
||||
require('lang/lua')
|
||||
@@ -1,2 +0,0 @@
|
||||
require('lang.bash')
|
||||
|
||||
@@ -1 +0,0 @@
|
||||
require('lang.js-ts')
|
||||
@@ -1,2 +0,0 @@
|
||||
vim.cmd('set ts=2') -- Insert 2 spaces for a tab
|
||||
vim.cmd('set sw=2') -- Change the number of space characters inserted for indentation
|
||||
@@ -1,2 +0,0 @@
|
||||
vim.cmd('set ts=2') -- Insert 2 spaces for a tab
|
||||
vim.cmd('set sw=2') -- Change the number of space characters inserted for indentation
|
||||
@@ -1 +0,0 @@
|
||||
setl ts=2 sw=2 ts=2 ai et
|
||||
@@ -1,10 +0,0 @@
|
||||
#!/bin/bash
|
||||
version=$(vim -v | awk 'NR==1{print $2}')
|
||||
|
||||
path="$HOME/.cache/nvim/packer.nvim/lock-${version}.json"
|
||||
rm -rf ${path}
|
||||
|
||||
vim +"PackerSnapshot lock-${version}.json"
|
||||
cp -f ${path} .
|
||||
|
||||
echo 'file at '${path}
|
||||
+47
-28
@@ -1,33 +1,52 @@
|
||||
CONFIG_PATH = vim.fn.stdpath('config')
|
||||
DATA_PATH = vim.fn.stdpath('data')
|
||||
CACHE_PATH = vim.fn.stdpath('cache')
|
||||
P = vim.inspect
|
||||
if vim.loader then
|
||||
vim.loader.enable()
|
||||
end
|
||||
|
||||
require('lv-globals')
|
||||
vim.cmd('luafile '..CONFIG_PATH..'/settings.lua')
|
||||
require('settings')
|
||||
require('plugins')
|
||||
require('utils')
|
||||
require('pluginconfig')
|
||||
require('autocommands')
|
||||
require('keymappings')
|
||||
require('colorscheme') -- This plugin must be required somewhere after nvimtree. Placing it before will break navigation keymappings
|
||||
-- 让 Mason / 健康检查能找到本仓库常用的本地 CLI(fd、lazygit、tree-sitter 等)
|
||||
local _local_bin = vim.fn.expand("~/.local/bin")
|
||||
if vim.fn.isdirectory(_local_bin) == 1 then
|
||||
vim.env.PATH = _local_bin .. ":" .. vim.env.PATH
|
||||
end
|
||||
-- 全局 npm 包(如 mermaid-cli 的 `mmdc`)通常在 $(npm prefix -g)/bin
|
||||
do
|
||||
local ok, out = pcall(vim.fn.system, { "npm", "prefix", "-g" })
|
||||
if ok and out and out ~= "" then
|
||||
local npm_bin = vim.fn.trim(out) .. "/bin"
|
||||
if vim.fn.isdirectory(npm_bin) == 1 then
|
||||
vim.env.PATH = npm_bin .. ":" .. vim.env.PATH
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
-- TODO is there a way to do this without vimscript
|
||||
vim.cmd('source '..CONFIG_PATH..'/vimscript/functions.vim')
|
||||
vim.g.loaded_netrw = 1
|
||||
vim.g.loaded_netrwPlugin = 1
|
||||
|
||||
-- LSP
|
||||
require('lsp')
|
||||
_G.dd = function(...)
|
||||
require("utils.debug").dump(...)
|
||||
end
|
||||
vim.print = _G.dd
|
||||
|
||||
--[[
|
||||
require('lsp.css-ls')
|
||||
require('lsp.docker-ls')
|
||||
require('lsp.emmet-ls')
|
||||
require('lsp.html-ls')
|
||||
require('lsp.json-ls')
|
||||
require('lsp.latex-ls')
|
||||
require('lsp.python-ls')
|
||||
require('lsp.rust-ls')
|
||||
require('lsp.yaml-ls')
|
||||
]]
|
||||
require("config.lazy")
|
||||
|
||||
|
||||
-- CONFIG_PATH = vim.fn.stdpath('config')
|
||||
-- DATA_PATH = vim.fn.stdpath('data')
|
||||
-- CACHE_PATH = vim.fn.stdpath('cache')
|
||||
-- P = vim.inspect
|
||||
|
||||
-- require('lv-globals')
|
||||
-- vim.cmd('luafile '..CONFIG_PATH..'/settings.lua')
|
||||
-- require('settings')
|
||||
-- require('plugins')
|
||||
-- require('utils')
|
||||
-- require('pluginconfig')
|
||||
-- require('autocommands')
|
||||
-- require('keymappings')
|
||||
-- require('colorscheme') -- This plugin must be required somewhere after nvimtree. Placing it before will break navigation keymappings
|
||||
|
||||
-- -- TODO is there a way to do this without vimscript
|
||||
-- vim.cmd('source '..CONFIG_PATH..'/vimscript/functions.vim')
|
||||
|
||||
-- -- LSP
|
||||
-- require('lsp')
|
||||
|
||||
|
||||
@@ -0,0 +1,65 @@
|
||||
{
|
||||
"FixCursorHold.nvim": { "branch": "master", "commit": "1900f89dc17c603eec29960f57c00bd9ae696495" },
|
||||
"LazyVim": { "branch": "main", "commit": "fca0af57cc3851b14f96a795a9c9bfafc5096dd1" },
|
||||
"SchemaStore.nvim": { "branch": "main", "commit": "4a0e1b74096937d366031bc1e2b244546af39f8e" },
|
||||
"avante.nvim": { "branch": "main", "commit": "e89eb79abf5754645e20aa6074da10ed20bba33c" },
|
||||
"barbar.nvim": { "branch": "master", "commit": "53b5a2f34b68875898f0531032fbf090e3952ad7" },
|
||||
"blink-cmp-avante": { "branch": "master", "commit": "4f494c6e124acbe31a8f5d58effa0c14aa38a6d5" },
|
||||
"blink.cmp": { "branch": "main", "commit": "78336bc89ee5365633bcf754d93df01678b5c08f" },
|
||||
"bufferline.nvim": { "branch": "main", "commit": "655133c3b4c3e5e05ec549b9f8cc2894ac6f51b3" },
|
||||
"catppuccin": { "branch": "main", "commit": "edefef779ab08ce1a4a404713e3012b0d202bd35" },
|
||||
"codecompanion.nvim": { "branch": "main", "commit": "e0780fa9fda504ffb89307cabcb6cbe1ce8eb60c" },
|
||||
"conform.nvim": { "branch": "master", "commit": "086a40dc7ed8242c03be9f47fbcee68699cc2395" },
|
||||
"dashboard-nvim": { "branch": "master", "commit": "0775e567b6c0be96d01a61795f7b64c1758262f6" },
|
||||
"diffview.nvim": { "branch": "main", "commit": "4516612fe98ff56ae0415a259ff6361a89419b0a" },
|
||||
"dressing.nvim": { "branch": "master", "commit": "2d7c2db2507fa3c4956142ee607431ddb2828639" },
|
||||
"flash.nvim": { "branch": "main", "commit": "5f0f270fdc7c5b0c21d903ee85b9cb06f2ac636a" },
|
||||
"friendly-snippets": { "branch": "main", "commit": "6cd7280adead7f586db6fccbd15d2cac7e2188b9" },
|
||||
"fzf-lua": { "branch": "main", "commit": "c9e7b7bfbd01f949164988ee1684035468e1995c" },
|
||||
"gemini-cli.nvim": { "branch": "main", "commit": "c9fd62adda823628f5131a939d9c56ef7a898600" },
|
||||
"gitsigns.nvim": { "branch": "main", "commit": "caa98e2e5e4c23a1652c23be839d2e1722cecc29" },
|
||||
"grug-far.nvim": { "branch": "main", "commit": "11595bf747edc270bce2069d1020502ad4ae56cf" },
|
||||
"lazy.nvim": { "branch": "main", "commit": "85c7ff3711b730b4030d03144f6db6375044ae82" },
|
||||
"lazydev.nvim": { "branch": "main", "commit": "ff2cbcba459b637ec3fd165a2be59b7bbaeedf0d" },
|
||||
"lsp_signature.nvim": { "branch": "master", "commit": "af7e4074d85d785cf6614352ba9ad3b28a1f8a56" },
|
||||
"lualine.nvim": { "branch": "master", "commit": "221ce6b2d999187044529f49da6554a92f740a96" },
|
||||
"mason-lspconfig.nvim": { "branch": "main", "commit": "40276c4df7e6bdce6801d6c035c6227f9115a855" },
|
||||
"mason-nvim-dap.nvim": { "branch": "main", "commit": "9a10e096703966335bd5c46c8c875d5b0690dade" },
|
||||
"mason.nvim": { "branch": "main", "commit": "44d1e90e1f66e077268191e3ee9d2ac97cc18e65" },
|
||||
"mini.ai": { "branch": "main", "commit": "25248c6aa002391936a6200f12d1466015987133" },
|
||||
"mini.icons": { "branch": "main", "commit": "98faae31e9be1cc054ae63485e58ceb185efcad0" },
|
||||
"mini.pairs": { "branch": "main", "commit": "b1c5a726921b7a8c9321e9a7a208aa0571de5810" },
|
||||
"neo-tree.nvim": { "branch": "main", "commit": "f3f3bf73414e400cf9fc13fda50f00404a8f8ab1" },
|
||||
"neotest": { "branch": "master", "commit": "72bc8f1ec62a590fb47368c81f89610e0f353e28" },
|
||||
"neotest-golang": { "branch": "main", "commit": "58d07a76d714f8f768bbe27711b06d41ca237728" },
|
||||
"noice.nvim": { "branch": "main", "commit": "7bfd942445fb63089b59f97ca487d605e715f155" },
|
||||
"nui.nvim": { "branch": "main", "commit": "10fc361835c856ba4233ef5ea135b919bf3dce97" },
|
||||
"nvim-cmp": { "branch": "main", "commit": "f17d9b4394027ff4442b298398dfcaab97e40c4f" },
|
||||
"nvim-dap": { "branch": "master", "commit": "c9a0738e45f1bd41d792a126941348dce661cf9b" },
|
||||
"nvim-dap-go": { "branch": "main", "commit": "b4421153ead5d726603b02743ea40cf26a51ed5f" },
|
||||
"nvim-dap-ui": { "branch": "master", "commit": "cc9dd33aade7f20bae414d0cba163bc60d4d4b43" },
|
||||
"nvim-dap-virtual-text": { "branch": "master", "commit": "fbdb48c2ed45f4a8293d0d483f7730d24467ccb6" },
|
||||
"nvim-lint": { "branch": "master", "commit": "3d55c8f67c6ae5c15e1042571e107c7a3d5c5f4e" },
|
||||
"nvim-lspconfig": { "branch": "master", "commit": "5492a24edd68f78d8a778edcb8905d0a28993abc" },
|
||||
"nvim-nio": { "branch": "master", "commit": "21f5324bfac14e22ba26553caf69ec76ae8a7662" },
|
||||
"nvim-tree.lua": { "branch": "master", "commit": "3603f069ff783c31986894a56c2a6755331b0802" },
|
||||
"nvim-treesitter": { "branch": "main", "commit": "7caec274fd19c12b55902a5b795100d21531391f" },
|
||||
"nvim-treesitter-textobjects": { "branch": "main", "commit": "5c7b0263797dfd1bd6202f2b219f3b53a80b2187" },
|
||||
"nvim-ts-autotag": { "branch": "main", "commit": "88c1453db4ba7dd24131086fe51fdf74e587d275" },
|
||||
"nvim-web-devicons": { "branch": "master", "commit": "d7462543c9e366c0d196c7f67a945eaaf5d99414" },
|
||||
"persistence.nvim": { "branch": "main", "commit": "b20b2a7887bd39c1a356980b45e03250f3dce49c" },
|
||||
"playground": { "branch": "master", "commit": "ba48c6a62a280eefb7c85725b0915e021a1a0749" },
|
||||
"plenary.nvim": { "branch": "master", "commit": "b9fd5226c2f76c951fc8ed5923d85e4de065e509" },
|
||||
"render-markdown.nvim": { "branch": "main", "commit": "c7188a8f9d2953696b6303caccbf39c51fa2c1b1" },
|
||||
"sidekick.nvim": { "branch": "main", "commit": "17447a05f9385e5f8372b61530f6f9329cb82421" },
|
||||
"snacks.nvim": { "branch": "main", "commit": "ad9ede6a9cddf16cedbd31b8932d6dcdee9b716e" },
|
||||
"symbols-outline.nvim": { "branch": "master", "commit": "564ee65dfc9024bdde73a6621820866987cbb256" },
|
||||
"telescope-file-browser.nvim": { "branch": "master", "commit": "3610dc7dc91f06aa98b11dca5cc30dfa98626b7e" },
|
||||
"telescope-fzf-native.nvim": { "branch": "main", "commit": "6fea601bd2b694c6f2ae08a6c6fab14930c60e2c" },
|
||||
"telescope.nvim": { "branch": "master", "commit": "40aedd8a68c78a656a10a8d62d80c54af59420fb" },
|
||||
"todo-comments.nvim": { "branch": "main", "commit": "31e3c38ce9b29781e4422fc0322eb0a21f4e8668" },
|
||||
"tokyonight.nvim": { "branch": "main", "commit": "cdc07ac78467a233fd62c493de29a17e0cf2b2b6" },
|
||||
"trouble.nvim": { "branch": "main", "commit": "bd67efe408d4816e25e8491cc5ad4088e708a69a" },
|
||||
"ts-comments.nvim": { "branch": "main", "commit": "a59d6092213447450191122c9346f309161504cb" },
|
||||
"which-key.nvim": { "branch": "main", "commit": "3aab2147e74890957785941f0c1ad87d0a44c15a" }
|
||||
}
|
||||
@@ -0,0 +1,17 @@
|
||||
{
|
||||
"extras": [
|
||||
"lazyvim.plugins.extras.editor.telescope",
|
||||
"lazyvim.plugins.extras.test.core",
|
||||
"lazyvim.plugins.extras.lang.go",
|
||||
"lazyvim.plugins.extras.lang.typescript",
|
||||
"lazyvim.plugins.extras.lang.json",
|
||||
"lazyvim.plugins.extras.dap.core",
|
||||
"lazyvim.plugins.extras.formatting.prettier",
|
||||
"lazyvim.plugins.extras.ai.avante"
|
||||
],
|
||||
"install_version": 7,
|
||||
"news": {
|
||||
"NEWS.md": "11866"
|
||||
},
|
||||
"version": 8
|
||||
}
|
||||
@@ -1 +0,0 @@
|
||||
{"nvim-dap": {"commit": "2cfd770"}, "FixCursorHold.nvim": {"commit": "5aa5ff1"}, "nvcode-color-schemes.vim": {"commit": "3a0e624"}, "telescope-fzf-native.nvim": {"commit": "65c0ee3"}, "codi.vim": {"commit": "2898369"}, "packer.nvim": {"commit": "6afb674"}, "nvim-lspconfig": {"commit": "51775b1"}, "playground": {"commit": "bcfab84"}, "numb.nvim": {"commit": "453c50a"}, "nvim-dap-virtual-text": {"commit": "2971ce3"}, "which-key.nvim": {"commit": "92d924d"}, "telescope-file-browser.nvim": {"commit": "60bcf4e"}, "aerial.nvim": {"commit": "f7d6ca8"}, "nvim-autopairs": {"commit": "14cc2a4"}, "neotest-go": {"commit": "d506f83"}, "nvim-colorizer.lua": {"commit": "36c610a"}, "nvim-comment": {"commit": "e9ac16a"}, "tabular": {"commit": "339091a"}, "rnvimr": {"commit": "4ced809"}, "gitsigns.nvim": {"commit": "4d8a6e0"}, "glow.nvim": {"commit": "8dca358"}, "nvim-ts-autotag": {"commit": "fdefe46"}, "telescope.nvim": {"commit": "30e2dc5"}, "nvim-compe": {"commit": "d186d73"}, "telescope-project.nvim": {"commit": "91e9a97"}, "trouble.nvim": {"commit": "929315e"}, "nvim-ts-context-commentstring": {"commit": "4d3a68c"}, "lsp_signature.nvim": {"commit": "e65a638"}, "one-monokai-vim": {"commit": "4fdfd93"}, "gruvbox.nvim": {"commit": "c632f62"}, "hop.nvim": {"commit": "75f73d3"}, "lush.nvim": {"commit": "fabf012"}, "LuaSnip": {"commit": "e687d78"}, "vim-go-coverage": {"commit": "d9b0c9f"}, "vim-matchup": {"commit": "ab8575d"}, "popup.nvim": {"commit": "b7404d3"}, "nvim-bqf": {"commit": "aac1ff9"}, "FTerm.nvim": {"commit": "efd1065"}, "lua-dev.nvim": {"commit": "315ad43"}, "nvim-treesitter": {"commit": "8689f61"}, "neotest": {"commit": "15c0bb2"}, "nvim-dap-ui": {"commit": "ebebd42"}, "nvim-luapad": {"commit": "9815e26"}, "monokai.nvim": {"commit": "4fc970e"}, "lspsaga.nvim": {"commit": "9ec569a"}, "vim-markdown": {"commit": "8a0b434"}, "dashboard-nvim": {"commit": "d82ddae"}, "barbar.nvim": {"commit": "c41ad6e"}, "nvim-tree.lua": {"commit": "fb8735e"}, "bracey.vim": {"commit": "4e1a22a"}, "fzf-lua": {"commit": "9b23c2c"}, "nvim-lsp-installer": {"commit": "ae913cb"}, "lsp-rooter.nvim": {"commit": "7c83364"}, "toggleterm.nvim": {"commit": "f494c61"}, "plenary.nvim": {"commit": "62dc2a7"}, "telescope-dap.nvim": {"commit": "b4134ff"}, "markdown-preview.nvim": {"commit": "02cc387"}, "nvim-web-devicons": {"commit": "2d02a56"}, "DAPInstall.nvim": {"commit": "bbda2b0"}, "galaxyline.nvim": {"commit": "eb81be0"}}
|
||||
@@ -1 +0,0 @@
|
||||
{"nvim-dap": {"commit": "8f396b7"}, "FixCursorHold.nvim": {"commit": "70a9516"}, "nvcode-color-schemes.vim": {"commit": "3a0e624"}, "telescope-fzf-native.nvim": {"commit": "7447fe7"}, "codi.vim": {"commit": "2898369"}, "packer.nvim": {"commit": "dcd2f38"}, "nvim-lspconfig": {"commit": "cb64ead"}, "playground": {"commit": "1290fdf"}, "numb.nvim": {"commit": "d95b7ea"}, "nvim-treesitter": {"commit": "7685324"}, "which-key.nvim": {"commit": "61553ae"}, "telescope-file-browser.nvim": {"commit": "8646e46"}, "fzf-lua": {"commit": "0c8b338"}, "nvim-autopairs": {"commit": "99f6963"}, "friendly-snippets": {"commit": "b4f857a"}, "vim-vsnip": {"commit": "e44026b"}, "nvim-colorizer.lua": {"commit": "36c610a"}, "toggleterm.nvim": {"commit": "f494c61"}, "tabular": {"commit": "339091a"}, "cmp-nvim-lsp": {"commit": "5922477"}, "vim-fugitive": {"commit": "49cc585"}, "neotest": {"commit": "a2559f0"}, "gitsigns.nvim": {"commit": "d076301"}, "glow.nvim": {"commit": "20d1cd0"}, "lspkind-nvim": {"commit": "c68b3a0"}, "cmp-cmdline": {"commit": "23c51b2"}, "telescope.nvim": {"commit": "3c2e5fb"}, "telescope-project.nvim": {"commit": "ff4d3ce"}, "trouble.nvim": {"commit": "897542f"}, "nvim-ts-context-commentstring": {"commit": "32d9627"}, "lsp_signature.nvim": {"commit": "2f3e574"}, "one-monokai-vim": {"commit": "4fdfd93"}, "gruvbox.nvim": {"commit": "02a9182"}, "nvim-bqf": {"commit": "448fa92"}, "aerial.nvim": {"commit": "38c6fe1"}, "hop.nvim": {"commit": "75f73d3"}, "lush.nvim": {"commit": "979f4d2"}, "LuaSnip": {"commit": "e687d78"}, "vim-go-coverage": {"commit": "d9b0c9f"}, "vim-matchup": {"commit": "55e3330"}, "popup.nvim": {"commit": "b7404d3"}, "cmp-vsnip": {"commit": "989a8a7"}, "nvim-web-devicons": {"commit": "189ad37"}, "FTerm.nvim": {"commit": "d132089"}, "nvim-dap-virtual-text": {"commit": "2971ce3"}, "neotest-go": {"commit": "4573733"}, "nvim-ts-autotag": {"commit": "fdefe46"}, "rnvimr": {"commit": "d78ca64"}, "nvim-dap-ui": {"commit": "54365d2"}, "barbar.nvim": {"commit": "9347ed8"}, "monokai.nvim": {"commit": "6fb4f7f"}, "lspsaga.nvim": {"commit": "9ec569a"}, "vim-markdown": {"commit": "df4be86"}, "dashboard-nvim": {"commit": "d82ddae"}, "nvim-luapad": {"commit": "6efe380"}, "nvim-tree.lua": {"commit": "829e9f6"}, "bracey.vim": {"commit": "4e1a22a"}, "nvim-lsp-installer": {"commit": "17e0bfa"}, "lsp-rooter.nvim": {"commit": "7c83364"}, "nvim-comment": {"commit": "e9ac16a"}, "plenary.nvim": {"commit": "4b7e520"}, "nvim-cmp": {"commit": "93f385c"}, "telescope-dap.nvim": {"commit": "b4134ff"}, "galaxyline.nvim": {"commit": "eb81be0"}, "markdown-preview.nvim": {"commit": "02cc387"}, "DAPInstall.nvim": {"commit": "bbda2b0"}, "neodev.nvim": {"commit": "dbed203"}, "cmp-path": {"commit": "91ff86c"}, "cmp-buffer": {"commit": "3022dbc"}}
|
||||
@@ -1,76 +0,0 @@
|
||||
local utils = require('utils')
|
||||
|
||||
local auto_formatters = { }
|
||||
|
||||
local python_autoformat = {'BufWritePre', '*.py', 'lua vim.lsp.buf.formatting_sync(nil, 1000)'}
|
||||
if O.python.autoformat then table.insert(auto_formatters, python_autoformat) end
|
||||
|
||||
local javascript_autoformat = {'BufWritePre', '*.js', 'lua vim.lsp.buf.formatting_sync(nil, 1000)'}
|
||||
local javascriptreact_autoformat = {'BufWritePre', '*.jsx', 'lua vim.lsp.buf.formatting_sync(nil, 1000)'}
|
||||
local typescript_autoformat = {'BufWritePre', '*.ts', 'lua vim.lsp.buf.formatting_sync(nil, 1000)'}
|
||||
local typescriptreact_autoformat = {'BufWritePre', '*.tsx', 'lua vim.lsp.buf.formatting_sync(nil, 1000)'}
|
||||
if O.tsserver.autoformat then
|
||||
table.insert(auto_formatters, javascript_autoformat)
|
||||
table.insert(auto_formatters, javascriptreact_autoformat)
|
||||
table.insert(auto_formatters, typescript_autoformat)
|
||||
table.insert(auto_formatters, typescriptreact_autoformat)
|
||||
end
|
||||
|
||||
local lua_format = {'BufWritePre', '*.lua', 'lua vim.lsp.buf.formatting_sync(nil, 1000)'}
|
||||
if O.lua.autoformat then table.insert(auto_formatters, lua_format) end
|
||||
|
||||
local json_format = {'BufWritePre', '*.json', 'lua vim.lsp.buf.formatting_sync(nil, 1000)'}
|
||||
if O.json.autoformat then table.insert(auto_formatters, json_format) end
|
||||
|
||||
local ruby_format = {'BufWritePre', '*.rb', 'lua vim.lsp.buf.formatting_sync(nil,1000)'}
|
||||
if O.ruby.autoformat then table.insert(auto_formatters, ruby_format) end
|
||||
|
||||
local go_format = {'BufWritePre', '*.go', 'lua vim.lsp.buf.formatting_sync(nil,1000)'}
|
||||
if O.go.autoformat then table.insert(auto_formatters, go_format) end
|
||||
|
||||
utils.define_augroups({
|
||||
_general_settings = {
|
||||
{'TextYankPost', '*', 'lua require(\'vim.highlight\').on_yank({higroup = \'Search\', timeout = 200})'},
|
||||
{'BufWinEnter', '*', 'setlocal formatoptions-=c formatoptions-=r formatoptions-=o'},
|
||||
{'BufRead', '*', 'setlocal formatoptions-=c formatoptions-=r formatoptions-=o'},
|
||||
{'BufNewFile', '*', 'setlocal formatoptions-=c formatoptions-=r formatoptions-=o'},
|
||||
{'VimLeavePre', '*', 'set title set titleold='},
|
||||
{'FileType', 'qf', 'set nobuflisted'},
|
||||
|
||||
-- {'User', 'GoyoLeave', 'lua require(\'galaxyline\').disable_galaxyline()'},
|
||||
-- {'User', 'GoyoEnter', 'lua require(\'galaxyline\').galaxyline_augroup()'},
|
||||
},
|
||||
_java = {
|
||||
{'FileType', 'java', 'luafile '..CONFIG_PATH..'/lua/lsp/java-ls.lua'},
|
||||
{'FileType', 'java', 'nnoremap ca <Cmd>lua require(\'jdtls\').code_action()<CR>'}
|
||||
},
|
||||
_go = {
|
||||
-- Go generally requires Tabs instead of spaces.
|
||||
{'FileType', 'go', 'setlocal tabstop=4'},
|
||||
{'FileType', 'go', 'setlocal shiftwidth=4'},
|
||||
{'FileType', 'go', 'setlocal softtabstop=4'},
|
||||
{'FileType', 'go', 'setlocal noexpandtab'},
|
||||
},
|
||||
_dashboard = {
|
||||
-- seems to be nobuflisted that makes my stuff disapear will do more testing
|
||||
{
|
||||
'FileType', 'dashboard',
|
||||
'setlocal nocursorline noswapfile synmaxcol& signcolumn=no norelativenumber nocursorcolumn nospell nolist nonumber bufhidden=wipe colorcolumn= foldcolumn=0 matchpairs= '
|
||||
}, {'FileType', 'dashboard', 'set showtabline=0 | autocmd BufLeave <buffer> set showtabline=2'}
|
||||
},
|
||||
_markdown = {{'FileType', 'markdown', 'setlocal wrap'}, {'FileType', 'markdown', 'setlocal spell'}},
|
||||
_solidity = {
|
||||
{'BufWinEnter', '.sol', 'setlocal filetype=solidity'}, {'BufRead', '*.sol', 'setlocal filetype=solidity'},
|
||||
{'BufNewFile', '*.sol', 'setlocal filetype=solidity'}
|
||||
},
|
||||
_gemini = {
|
||||
{'BufWinEnter', '.gmi', 'setlocal filetype=markdown'}, {'BufRead', '*.gmi', 'setlocal filetype=markdown'},
|
||||
{'BufNewFile', '*.gmi', 'setlocal filetype=markdown'}
|
||||
},
|
||||
_buffer_bindings = {
|
||||
{'FileType', 'dashboard', 'nnoremap <silent> <buffer> q :q<CR>'},
|
||||
{'FileType', 'lspinfo', 'nnoremap <silent> <buffer> q :q<CR>'},
|
||||
{'FileType', 'floaterm', 'nnoremap <silent> <buffer> q :q<CR>'},
|
||||
},
|
||||
_auto_formatters = auto_formatters
|
||||
})
|
||||
@@ -1,3 +0,0 @@
|
||||
vim.cmd('let g:nvcode_termcolors=256')
|
||||
|
||||
vim.cmd('colorscheme ' .. O.colorscheme)
|
||||
@@ -0,0 +1,22 @@
|
||||
vim.api.nvim_create_user_command("W", function()
|
||||
require("utils").sudo_write()
|
||||
end, { desc = "Write file with sudo (tee)" })
|
||||
|
||||
-- Turn off paste mode when leaving insert
|
||||
vim.api.nvim_create_autocmd("InsertLeave", {
|
||||
pattern = "*",
|
||||
command = "set nopaste",
|
||||
})
|
||||
|
||||
-- Disable the concealing in some file formats
|
||||
-- The default conceallevel is 3 in LazyVim
|
||||
vim.api.nvim_create_autocmd("FileType", {
|
||||
pattern = { "js", "tsx", "ts", "json", "jsonc", "markdown", "md", "css", "html" },
|
||||
callback = function()
|
||||
vim.opt.conceallevel = 0
|
||||
vim.bo.tabstop = 2
|
||||
vim.bo.softtabstop = 2
|
||||
vim.bo.shiftwidth = 2
|
||||
-- vim.bo.expand = true
|
||||
end,
|
||||
})
|
||||
@@ -0,0 +1,180 @@
|
||||
local keymap = vim.keymap
|
||||
|
||||
-- <Leader>b for buffer
|
||||
keymap.set("n", "<Leader>bq", "<cmd>q<cr>", { desc = "quit" })
|
||||
keymap.set("n", "<Leader>bs", "<cmd>w<cr>", { desc = "save" })
|
||||
keymap.set("n", "<Leader>br", "<cmd>e!<cr>", { desc = "reload" })
|
||||
keymap.set("n", "<Leader>bd", "<cmd>BufferClose<cr>", { desc = "close buffer" })
|
||||
|
||||
-- <Leader>c
|
||||
keymap.set("n", "<Leader>cs", "<cmd>SymbolsOutline<cr>", { desc = "Symbols Outline" })
|
||||
|
||||
-- <Leader>D for Trouble (v3 syntax)
|
||||
keymap.set("n", "<Leader>Dt", "<cmd>Trouble diagnostics toggle<cr>", { desc = "trouble diagnostics" })
|
||||
keymap.set("n", "<Leader>Dd", "<cmd>Trouble diagnostics toggle filter.buf=0<cr>", { desc = "document diagnostics" })
|
||||
keymap.set("n", "<Leader>Dq", "<cmd>Trouble qflist toggle<cr>", { desc = "quickfix" })
|
||||
keymap.set("n", "<Leader>Dl", "<cmd>Trouble loclist toggle<cr>", { desc = "loclist" })
|
||||
keymap.set("n", "<Leader>Dr", "<cmd>Trouble lsp_references toggle<cr>", { desc = "references" })
|
||||
|
||||
-- <Leader>d for debug
|
||||
keymap.set("n", "<Leader>dd", "<cmd>lua vim.diagnostic.open_float()<cr>", { desc = "show diagnostics" })
|
||||
keymap.set("n", "<Leader>dc", '<cmd>lua require("dap").continue()<cr>', { desc = "continue" })
|
||||
keymap.set("n", "<Leader>db", '<cmd>lua require("dap").toggle_breakpoint()<cr>', { desc = "Toggle Breakpoint" })
|
||||
keymap.set("n", "<Leader>dj", '<cmd>lua require("dap").step_over()<cr>', { desc = "step over" })
|
||||
keymap.set("n", "<Leader>dk", '<cmd>lua require("dap").step_out()<cr>', { desc = "step out" })
|
||||
keymap.set("n", "<Leader>dl", '<cmd>lua require("dap").step_into()<cr>', { desc = "step into" })
|
||||
keymap.set("n", "<Leader>du", '<cmd>lua require("dap").up()<cr>', { desc = "up" })
|
||||
keymap.set("n", "<Leader>d_", '<cmd>lua require("dap").disconnect();require("dap").stop();require("dap").run_last()<cr>', { desc = "disconnect" })
|
||||
keymap.set("n", "<Leader>di", '<cmd>lua require("dap.ui.variables").visual_hover()<cr>', { desc = "visual hover" })
|
||||
keymap.set("n", "<Leader>ds", '<cmd>lua local w=require("dap.ui.widgets");w.centered_float(w.scopes)<cr>', { desc = "scopes" })
|
||||
|
||||
-- <Leader>f
|
||||
keymap.set("n", "<Leader>fm", "<cmd>lua require('utils').open_md()<cr>", { desc = "open markdown in external app" })
|
||||
keymap.set("n", "<Leader>fs", "<cmd>w<cr>", { desc = "save file" })
|
||||
keymap.set("n", "<Leader>ft", "<cmd>NvimTreeToggle<cr>", { desc = "toggle nvim tree" })
|
||||
keymap.set("n", "<Leader>fy", "<cmd>lua require('utils').yank_filepath()<cr>", { desc = "copy file path" })
|
||||
keymap.set("n", "<Leader>fd", "<cmd>lua require('utils').yank_file_dir()<cr>", { desc = "copy dir path" })
|
||||
keymap.set("n", "<Leader>fl", "<cmd>NvimTreeFindFile<CR>", { desc = "find file in tree" })
|
||||
|
||||
-- <Leader>g
|
||||
keymap.set("n", "<Leader>gj", "<cmd>lua require('gitsigns').next_hunk()<cr>", { desc = "Next Hunk" })
|
||||
keymap.set("n", "<Leader>gk", "<cmd>lua require('gitsigns').prev_hunk()<cr>", { desc = "Prev Hunk" })
|
||||
keymap.set("n", "<Leader>gl", "<cmd>lua require('gitsigns').blame_line()<cr>", { desc = "Blame" })
|
||||
keymap.set("n", "<Leader>gp", "<cmd>lua require('gitsigns').preview_hunk()<cr>", { desc = "Preview Hunk" })
|
||||
keymap.set("n", "<Leader>gr", "<cmd>lua require('gitsigns').reset_hunk()<cr>", { desc = "Reset Hunk" })
|
||||
keymap.set("n", "<Leader>gR", "<cmd>lua require('gitsigns').reset_buffer()<cr>", { desc = "Reset Buffer" })
|
||||
keymap.set("n", "<Leader>gs", "<cmd>lua require('gitsigns').stage_hunk()<cr>", { desc = "Stage Hunk" })
|
||||
keymap.set("n", "<Leader>gS", "<cmd>lua require('gitsigns').stage_buffer()<cr>", { desc = "Stage Buffer" })
|
||||
keymap.set("n", "<Leader>gu", "<cmd>lua require('gitsigns').undo_stage_hunk()<cr>", { desc = "Undo Stage Hunk" })
|
||||
keymap.set("n", "<Leader>go", "<cmd>Telescope git_status<cr>", { desc = "Open changed file" })
|
||||
keymap.set("n", "<Leader>gb", "<cmd>Telescope git_branches<cr>", { desc = "Checkout branch" })
|
||||
keymap.set("n", "<Leader>gc", "<cmd>Telescope git_commits<cr>", { desc = "Checkout commit" })
|
||||
keymap.set("n", "<Leader>gC", "<cmd>Telescope git_bcommits<cr>", { desc = "Checkout commit(for current file)" })
|
||||
keymap.set("n", "<Leader>gd", "<cmd>Gitsigns diffthis HEAD<cr>", { desc = "Git Diff" })
|
||||
|
||||
-- <Leader>h
|
||||
keymap.set("n", "<Leader>h", ":set hlsearch!<CR>", { noremap = true, silent = true, desc = "toggle hlsearch" })
|
||||
|
||||
-- <Leader>q
|
||||
keymap.set("n", "<Leader>qq", "<cmd>qall<cr>", { desc = "quit all" })
|
||||
|
||||
-- <Leader>s for search
|
||||
keymap.set("n", "<Leader>sb", "<cmd>Telescope git_branches<cr>", { desc = "Checkout branch" })
|
||||
keymap.set("n", "<Leader>sc", "<cmd>nohl<cr>", { desc = "no highlight" })
|
||||
keymap.set("n", "<Leader>sd", "<cmd>Telescope diagnostics bufnr=0<cr>", { desc = "Document Diagnostics" })
|
||||
keymap.set("n", "<Leader>sD", "<cmd>Telescope diagnostics<cr>", { desc = "Workspace Diagnostics" })
|
||||
keymap.set("n", "<Leader>sf", "<cmd>Telescope find_files<cr>", { desc = "Find File" })
|
||||
keymap.set("n", "<Leader>sr", function()
|
||||
require("telescope.builtin").oldfiles({ cwd = vim.uv.cwd() })
|
||||
end, { desc = "Old File" })
|
||||
keymap.set("n", "<Leader>sm", "<cmd>Telescope marks<cr>", { desc = "Marks" })
|
||||
keymap.set("n", "<Leader>sM", "<cmd>Telescope man_pages<cr>", { desc = "Man Pages" })
|
||||
keymap.set("n", "<Leader>sR", "<cmd>Telescope registers<cr>", { desc = "Registers" })
|
||||
keymap.set("n", "<Leader>st", "<cmd>Telescope live_grep<cr>", { desc = "Text" })
|
||||
keymap.set("n", "<Leader>s,", function()
|
||||
local ok, fzf_lua = pcall(require, "fzf-lua")
|
||||
if ok then
|
||||
fzf_lua.grep_cword()
|
||||
else
|
||||
require("telescope.builtin").grep_string({ word_match = "-w" })
|
||||
end
|
||||
end, { desc = "Search word under cursor" })
|
||||
|
||||
-- <Leader>t for test
|
||||
keymap.set("n", "<Leader>tt", '<cmd>lua require("neotest").run.run()<cr>', { desc = "TestNearest" })
|
||||
keymap.set("n", "<Leader>tf", '<cmd>lua require("neotest").run.run(vim.fn.expand("%"))<cr>', { desc = "TestFile" })
|
||||
keymap.set("n", "<Leader>tc", "<cmd>GoCover<cr>", { desc = "go coverage" })
|
||||
keymap.set("n", "<Leader>tC", "<cmd>GoCoverClear<cr>", { desc = "go coverage clear" })
|
||||
keymap.set("n", "<Leader>tb", '<cmd>lua require("dap").toggle_breakpoint()<cr>', { desc = "set break point" })
|
||||
keymap.set("n", "<Leader>tr", '<cmd>lua require("dap").continue()<cr>', { desc = "continue" })
|
||||
|
||||
-- <Leader>;
|
||||
keymap.set("n", "<Leader>;", "<cmd>Dashboard<CR>", { noremap = true, silent = true, desc = "Dashboard" })
|
||||
|
||||
-- Ctrl-p: find files with recent-first boost
|
||||
keymap.set("n", "<C-p>", function()
|
||||
local builtin = require("telescope.builtin")
|
||||
local recent_paths = {}
|
||||
for _, file in ipairs(vim.v.oldfiles) do
|
||||
if vim.fn.filereadable(file) == 1 then
|
||||
local abs_path = vim.fn.fnamemodify(file, ":p")
|
||||
local rel_path = vim.fn.fnamemodify(file, ":.")
|
||||
recent_paths[abs_path] = true
|
||||
recent_paths[rel_path] = true
|
||||
recent_paths[vim.fn.fnamemodify(file, ":t")] = true
|
||||
end
|
||||
end
|
||||
local ok, _ = pcall(require, "telescope._extensions.fzf")
|
||||
if ok then
|
||||
builtin.find_files({ hidden = true, no_ignore = false })
|
||||
else
|
||||
local sorters = require("telescope.sorters")
|
||||
builtin.find_files({
|
||||
hidden = true,
|
||||
no_ignore = false,
|
||||
sorter = sorters.Sorter:new({
|
||||
discard = true,
|
||||
scoring_function = function(_, prompt, line, entry)
|
||||
local score = 0
|
||||
local path = entry.path or entry.value or line
|
||||
local abs_path = vim.fn.fnamemodify(path, ":p")
|
||||
local rel_path = vim.fn.fnamemodify(path, ":.")
|
||||
local filename = vim.fn.fnamemodify(path, ":t")
|
||||
if recent_paths[abs_path] or recent_paths[rel_path] or recent_paths[filename] then
|
||||
score = 1000
|
||||
end
|
||||
if prompt and prompt ~= "" then
|
||||
if path:lower():find(prompt:lower(), 1, true) then
|
||||
score = score + 100
|
||||
end
|
||||
end
|
||||
return score
|
||||
end,
|
||||
}),
|
||||
})
|
||||
end
|
||||
end, { noremap = true, silent = true, desc = "Find Files (Recent First)" })
|
||||
|
||||
-- Window movement
|
||||
keymap.set("n", "<C-h>", "<C-w>h", { silent = true })
|
||||
keymap.set("n", "<C-j>", "<C-w>j", { silent = true })
|
||||
keymap.set("n", "<C-k>", "<C-w>k", { silent = true })
|
||||
keymap.set("n", "<C-l>", "<C-w>l", { silent = true })
|
||||
|
||||
-- Resize with arrows
|
||||
keymap.set("n", "<C-Up>", ":resize -2<CR>", { silent = true })
|
||||
keymap.set("n", "<C-Down>", ":resize +2<CR>", { silent = true })
|
||||
keymap.set("n", "<C-Left>", ":vertical resize -2<CR>", { silent = true })
|
||||
keymap.set("n", "<C-Right>", ":vertical resize +2<CR>", { silent = true })
|
||||
|
||||
-- Quickfix toggle
|
||||
keymap.set("n", "<C-q>", function()
|
||||
for _, win in pairs(vim.fn.getwininfo()) do
|
||||
if win.quickfix == 1 then
|
||||
vim.cmd("cclose")
|
||||
return
|
||||
end
|
||||
end
|
||||
vim.cmd("copen")
|
||||
end, { noremap = true, silent = true, desc = "Toggle Quickfix" })
|
||||
|
||||
-- Two spaces → live_grep
|
||||
keymap.set("n", " ", function()
|
||||
require("telescope.builtin").live_grep()
|
||||
end, { noremap = true, silent = true, desc = "Search in Files" })
|
||||
|
||||
-- Misc
|
||||
keymap.set("n", "Y", "y$", {})
|
||||
keymap.set("v", "<", "<gv", { noremap = true, silent = true })
|
||||
keymap.set("v", ">", ">gv", { noremap = true, silent = true })
|
||||
keymap.set("i", "jk", "<ESC>", { noremap = true, silent = true })
|
||||
keymap.set("i", "kj", "<ESC>", { noremap = true, silent = true })
|
||||
keymap.set("i", "jj", "<ESC>", { noremap = true, silent = true })
|
||||
|
||||
-- Tab switch buffer (barbar)
|
||||
keymap.set("n", "<TAB>", "<cmd>BufferNext<CR>", { noremap = true, silent = true })
|
||||
keymap.set("n", "<S-TAB>", "<cmd>BufferPrevious<CR>", { noremap = true, silent = true })
|
||||
|
||||
-- Move selected line / block in visual mode
|
||||
keymap.set("x", "K", ":move '<-2<CR>gv-gv", { noremap = true, silent = true })
|
||||
keymap.set("x", "J", ":move '>+1<CR>gv-gv", { noremap = true, silent = true })
|
||||
@@ -0,0 +1,47 @@
|
||||
local lazypath = vim.fn.stdpath("data") .. "/lazy/lazy.nvim"
|
||||
if not vim.uv.fs_stat(lazypath) then
|
||||
-- bootstrap lazy.nvim
|
||||
-- stylua: ignore
|
||||
vim.fn.system({ "git", "clone", "--filter=blob:none", "https://github.com/folke/lazy.nvim.git", "--branch=stable", lazypath })
|
||||
end
|
||||
vim.opt.rtp:prepend(vim.env.LAZY or lazypath)
|
||||
|
||||
require("lazy").setup({
|
||||
spec = {
|
||||
-- add LazyVim and import its plugins
|
||||
-- LazyVim will automatically load extras from lazyvim.json
|
||||
{ "LazyVim/LazyVim", import = "lazyvim.plugins" },
|
||||
-- import/override with your plugins
|
||||
{ import = "plugins" },
|
||||
},
|
||||
-- 本机未装 luarocks 时避免 Lazy 健康检查报 ERROR;需要 rocks 依赖的插件时再打开
|
||||
rocks = {
|
||||
enabled = false,
|
||||
},
|
||||
defaults = {
|
||||
-- By default, only LazyVim plugins will be lazy-loaded. Your custom plugins will load during startup.
|
||||
-- If you know what you're doing, you can set this to `true` to have all your custom plugins lazy-loaded by default.
|
||||
lazy = false,
|
||||
-- It's recommended to leave version=false for now, since a lot the plugin that support versioning,
|
||||
-- have outdated releases, which may break your Neovim install.
|
||||
version = false, -- always use the latest git commit
|
||||
-- version = "*", -- try installing the latest stable version for plugins that support semver
|
||||
},
|
||||
install = { colorscheme = { "tokyonight", "habamax" } },
|
||||
checker = { enabled = false}, -- automatically check for plugin updates
|
||||
performance = {
|
||||
rtp = {
|
||||
-- disable some rtp plugins
|
||||
disabled_plugins = {
|
||||
"gzip",
|
||||
-- "matchit",
|
||||
-- "matchparen",
|
||||
-- "netrwPlugin",
|
||||
"tarPlugin",
|
||||
"tohtml",
|
||||
"tutor",
|
||||
"zipPlugin",
|
||||
},
|
||||
},
|
||||
},
|
||||
})
|
||||
@@ -0,0 +1,54 @@
|
||||
vim.g.autoformat = true
|
||||
|
||||
-- 关闭不常用的远程宿主,减少 :checkhealth 里无意义的 WARNING
|
||||
vim.g.loaded_perl_provider = 0
|
||||
vim.g.loaded_ruby_provider = 0
|
||||
-- 明确指向已安装 pynvim 的解释器
|
||||
vim.g.python3_host_prog = "/usr/bin/python3"
|
||||
|
||||
vim.cmd("set iskeyword+=-") -- treat dash separated words as a word text object
|
||||
vim.cmd("set shortmess+=c") -- Don't pass messages to |ins-completion-menu|.
|
||||
vim.cmd("set inccommand=split") -- Make substitution work in realtime
|
||||
vim.opt.hidden = true -- Required to keep multiple buffers open multiple buffers
|
||||
vim.opt.title = true
|
||||
TERMINAL = vim.fn.expand("$TERMINAL")
|
||||
vim.cmd('let &titleold="' .. TERMINAL .. '"')
|
||||
vim.opt.titlestring = "%<%F%=%l/%L - nvim"
|
||||
vim.wo.wrap = true -- Display long lines as just one line
|
||||
vim.cmd("set whichwrap+=<,>,[,],h,l") -- move to next line with theses keys
|
||||
vim.opt.pumheight = 10 -- Makes popup menu smaller
|
||||
vim.opt.fileencoding = "utf-8" -- The encoding written to file
|
||||
vim.opt.cmdheight = 2 -- More space for displaying messages
|
||||
vim.opt.mouse = "a" -- Enable your mouse
|
||||
vim.opt.splitbelow = true -- Horizontal splits will automatically be below
|
||||
vim.opt.termguicolors = true -- set term gui colors most terminals support this
|
||||
vim.opt.splitright = true -- Vertical splits will automatically be to the right
|
||||
vim.opt.conceallevel = 0 -- So that I can see `` in markdown files
|
||||
vim.o.tabstop = 4
|
||||
vim.opt.shiftwidth = 4
|
||||
vim.opt.expandtab = true
|
||||
vim.opt.smartindent = true -- Makes indenting smart
|
||||
vim.wo.number = true -- set numbered lines
|
||||
vim.wo.relativenumber = true -- set relative number
|
||||
vim.wo.cursorline = true -- Enable highlighting of the current line
|
||||
vim.opt.showtabline = 2 -- Always show tabs
|
||||
vim.opt.showmode = false -- We don't need to see things like -- INSERT -- anymore
|
||||
vim.opt.backup = false
|
||||
vim.opt.writebackup = false
|
||||
vim.wo.signcolumn = "yes" -- Always show the signcolumn, otherwise it would shift the text each time
|
||||
vim.opt.updatetime = 300 -- Faster completion
|
||||
vim.opt.timeoutlen = 500 -- By default timeoutlen is 1000 ms
|
||||
vim.opt.clipboard = "unnamedplus" -- Copy paste between vim and everything else
|
||||
vim.opt.guifont = "FiraCode Nerd Font:h17"
|
||||
|
||||
vim.g.vim_markdown_folding_disabled = 1
|
||||
|
||||
-- git blame
|
||||
vim.cmd("highlight default link gitblame SpecialComment")
|
||||
vim.g.gitblame_enabled = 0
|
||||
|
||||
-- vim.cmd('let g:nvcode_termcolors=256')
|
||||
-- --local cc = 'gruvbox'
|
||||
-- local cc = 'lunar'
|
||||
-- vim.opt.background="dark"
|
||||
-- vim.cmd('colorscheme ' .. cc)
|
||||
@@ -1,81 +0,0 @@
|
||||
vim.api.nvim_set_keymap('n', '-', ':RnvimrToggle<CR>', { noremap = true, silent = true })
|
||||
|
||||
-- better window movement
|
||||
vim.api.nvim_set_keymap('n', '<C-h>', '<C-w>h', { silent = true })
|
||||
vim.api.nvim_set_keymap('n', '<C-j>', '<C-w>j', { silent = true })
|
||||
vim.api.nvim_set_keymap('n', '<C-k>', '<C-w>k', { silent = true })
|
||||
vim.api.nvim_set_keymap('n', '<C-l>', '<C-w>l', { silent = true })
|
||||
|
||||
-- better edit
|
||||
vim.api.nvim_set_keymap('n', 'Y', 'y$', {})
|
||||
|
||||
-- TODO fix this
|
||||
-- Terminal window navigation
|
||||
-- vim.cmd([[
|
||||
-- tnoremap <C-h> <C-\><C-N><C-w>h
|
||||
-- tnoremap <C-j> <C-\><C-N><C-w>j
|
||||
-- tnoremap <C-k> <C-\><C-N><C-w>k
|
||||
-- tnoremap <C-l> <C-\><C-N><C-w>l
|
||||
-- inoremap <C-h> <C-\><C-N><C-w>h
|
||||
-- inoremap <C-j> <C-\><C-N><C-w>j
|
||||
-- inoremap <C-k> <C-\><C-N><C-w>k
|
||||
-- inoremap <C-l> <C-\><C-N><C-w>l
|
||||
-- tnoremap <Esc> <C-\><C-n>
|
||||
-- ]])
|
||||
|
||||
-- TODO fix this
|
||||
-- resize with arrows
|
||||
vim.api.nvim_set_keymap('n', '<C-Up>', ':resize -2<CR>', { silent = true })
|
||||
vim.api.nvim_set_keymap('n', '<C-Down>', ':resize +2<CR>', { silent = true })
|
||||
vim.api.nvim_set_keymap('n', '<C-Left>', ':vertical resize -2<CR>', { silent = true })
|
||||
vim.api.nvim_set_keymap('n', '<C-Right>', ':vertical resize +2<CR>', { silent = true })
|
||||
|
||||
-- better indenting
|
||||
vim.api.nvim_set_keymap('v', '<', '<gv', { noremap = true, silent = true })
|
||||
vim.api.nvim_set_keymap('v', '>', '>gv', { noremap = true, silent = true })
|
||||
|
||||
-- I hate escape
|
||||
vim.api.nvim_set_keymap('i', 'jk', '<ESC>', { noremap = true, silent = true })
|
||||
vim.api.nvim_set_keymap('i', 'kj', '<ESC>', { noremap = true, silent = true })
|
||||
vim.api.nvim_set_keymap('i', 'jj', '<ESC>', { noremap = true, silent = true })
|
||||
|
||||
-- Tab switch buffer
|
||||
vim.api.nvim_set_keymap('n', '<TAB>', ':bnext<CR>', { noremap = true, silent = true })
|
||||
vim.api.nvim_set_keymap('n', '<S-TAB>', ':bprevious<CR>', { noremap = true, silent = true })
|
||||
|
||||
-- Move selected line / block of text in visual mode
|
||||
vim.api.nvim_set_keymap('x', 'K', ':move \'<-2<CR>gv-gv', { noremap = true, silent = true })
|
||||
vim.api.nvim_set_keymap('x', 'J', ':move \'>+1<CR>gv-gv', { noremap = true, silent = true })
|
||||
|
||||
-- Better nav for omnicomplete
|
||||
vim.cmd('inoremap <expr> <c-j> (\"\\<C-n>\")')
|
||||
vim.cmd('inoremap <expr> <c-k> (\"\\<C-p>\")')
|
||||
|
||||
vim.cmd('vnoremap p "0p')
|
||||
vim.cmd('vnoremap P "0P')
|
||||
-- vim.api.nvim_set_keymap('v', 'p', '"0p', {silent = true})
|
||||
-- vim.api.nvim_set_keymap('v', 'P', '"0P', {silent = true})
|
||||
|
||||
-- vim.cmd('inoremap <expr> <TAB> (\"\\<C-n>\")')
|
||||
-- vim.cmd('inoremap <expr> <S-TAB> (\"\\<C-p>\")')
|
||||
|
||||
-- vim.api.nvim_set_keymap('i', '<C-TAB>', 'compe#complete()', {noremap = true, silent = true, expr = true})
|
||||
|
||||
-- vim.cmd([[
|
||||
-- map p <Plug>(miniyank-autoput)
|
||||
-- map P <Plug>(miniyank-autoPut)
|
||||
-- map <leader>n <Plug>(miniyank-cycle)
|
||||
-- map <leader>N <Plug>(miniyank-cycleback)
|
||||
-- ]])
|
||||
|
||||
-- Toggle the QuickFix window
|
||||
vim.api.nvim_set_keymap('', '<C-q>', ':call QuickFixToggle()<CR>', { noremap = true, silent = true })
|
||||
|
||||
|
||||
|
||||
-- nvim-compe key map
|
||||
-- vim.cmd('inoremap <silent><expr> <C-Space> compe#complete()')
|
||||
-- vim.cmd("inoremap <silent><expr> <CR> compe#confirm('<CR>')")
|
||||
-- vim.cmd("inoremap <silent><expr> <C-e> compe#close('<C-e>')")
|
||||
-- vim.cmd("inoremap <silent><expr> <C-f> compe#scroll({ 'delta': +4 })")
|
||||
-- vim.cmd("inoremap <silent><expr> <C-d> compe#scroll({ 'delta': -4 })")
|
||||
@@ -1,6 +0,0 @@
|
||||
-- npm i -g bash-language-server
|
||||
require'lspconfig'.bashls.setup {
|
||||
cmd = {DATA_PATH .. "/lspinstall/bash/node_modules/.bin/bash-language-server", "start"},
|
||||
on_attach = require'lsp'.common_on_attach,
|
||||
filetypes = { "sh", "zsh" }
|
||||
}
|
||||
@@ -1,8 +0,0 @@
|
||||
-- npm install -g vscode-css-languageserver-bin
|
||||
require'lspconfig'.cssls.setup {
|
||||
cmd = {
|
||||
"node", DATA_PATH .. "/lspinstall/css/vscode-css/css-language-features/server/dist/node/cssServerMain.js",
|
||||
"--stdio"
|
||||
},
|
||||
on_attach = require'lsp'.common_on_attach
|
||||
}
|
||||
@@ -1,6 +0,0 @@
|
||||
-- npm install -g dockerfile-language-server-nodejs
|
||||
require'lspconfig'.dockerls.setup {
|
||||
cmd = {DATA_PATH .. "/lspinstall/dockerfile/node_modules/.bin/docker-langserver", "--stdio"},
|
||||
on_attach = require'lsp'.common_on_attach,
|
||||
root_dir = vim.loop.cwd
|
||||
}
|
||||
@@ -1,19 +0,0 @@
|
||||
local nvim_lsp = require'lspconfig'
|
||||
local configs = require'lspconfig/configs'
|
||||
local capabilities = vim.lsp.protocol.make_client_capabilities()
|
||||
capabilities.textDocument.completion.completionItem.snippetSupport = true
|
||||
|
||||
configs.emmet_ls = {
|
||||
default_config = {
|
||||
cmd = {'emmet-ls', '--stdio'};
|
||||
filetypes = {'html', 'css'};
|
||||
root_dir = function()
|
||||
return vim.loop.cwd()
|
||||
end;
|
||||
settings = {};
|
||||
};
|
||||
}
|
||||
|
||||
nvim_lsp.emmet_ls.setup{
|
||||
-- on_attach = on_attach;
|
||||
}
|
||||
@@ -1,146 +0,0 @@
|
||||
-- LSP
|
||||
require 'lspconfig'.gopls.setup {
|
||||
-- cmd = {DATA_PATH .. "/lspinstall/go/gopls"},
|
||||
cmd = { "gopls", "serve" }, -- use cmd in path, I can update it easily
|
||||
settings = { gopls = { analyses = { unusedparams = true }, staticcheck = true } },
|
||||
root_dir = require 'lspconfig'.util.root_pattern(".git", "go.mod", "go.work"),
|
||||
init_options = { usePlaceholders = true, completeUnimported = true },
|
||||
on_attach = require 'lsp'.common_on_attach
|
||||
}
|
||||
|
||||
--[[
|
||||
require'dap-install'.config("go_delve",{})
|
||||
|
||||
-- DAP
|
||||
local dap = require "dap"
|
||||
vim.fn.sign_define('DapBreakpoint', {text='🟥', texthl='', linehl='', numhl=''})
|
||||
--vim.fn.sign_define('DapStopped', {text='⭐️', texthl='', linehl='', numhl=''})
|
||||
|
||||
local M = {}
|
||||
|
||||
local ts_utils = require 'nvim-treesitter.ts_utils'
|
||||
|
||||
function M.get_node()
|
||||
local n = ts_utils.get_node_at_cursor()
|
||||
put(ts_utils.get_node_text(n))
|
||||
end
|
||||
|
||||
function M.get_node2()
|
||||
local current_node = ts_utils.get_node_at_cursor()
|
||||
if not current_node then
|
||||
return ""
|
||||
end
|
||||
|
||||
local expr = current_node
|
||||
|
||||
while expr do
|
||||
if expr:type() == 'function_definition' then
|
||||
put(expr:type())
|
||||
break
|
||||
end
|
||||
expr = expr:parent()
|
||||
end
|
||||
|
||||
if not expr then return "" end
|
||||
|
||||
put(ts_utils.get_node_text(expr))
|
||||
return (ts_utils.get_node_text(expr:child(1)))[1]
|
||||
end
|
||||
|
||||
return M
|
||||
|
||||
--[[
|
||||
-- https://github.com/go-delve/delve/blob/master/Documentation/usage/dlv_dap.md
|
||||
dap.configurations.go = {
|
||||
{
|
||||
type = "go",
|
||||
name = "Debug",
|
||||
request = "launch",
|
||||
program = "${file}"
|
||||
},
|
||||
-- works with go.mod packages and sub packagesfunction M.get_current_function_name() local current_node = ts_utils.get_node_at_cursor() if not current_node then return "" end
|
||||
|
||||
local expr = current_node
|
||||
|
||||
while expr do
|
||||
if expr:type() == 'function_definition' then
|
||||
break
|
||||
end
|
||||
expr = expr:parent()
|
||||
end
|
||||
|
||||
if not expr then return "" end
|
||||
|
||||
return (ts_utils.get_node_text(expr:child(1)))[1]
|
||||
end
|
||||
{
|
||||
type = "go",
|
||||
name = "Debug test (go.mod)",
|
||||
request = "launch",
|
||||
mode = "test",
|
||||
program = "./${relativeFileDirname}"
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
--[[
|
||||
local dap = require('dap')
|
||||
|
||||
dap.adapters.go = function(callback, config)
|
||||
local stdout = vim.loop.new_pipe(false)
|
||||
local handle
|
||||
local pid_or_err
|
||||
local port = 38697
|
||||
local opts = {
|
||||
stdio = {nil, stdout},
|
||||
args = {"dap", "-l", "127.0.0.1:" .. port},
|
||||
detached = true
|
||||
}
|
||||
handle, pid_or_err = vim.loop.spawn("dlv", opts, function(code)
|
||||
stdout:close()
|
||||
handle:close()
|
||||
if code ~= 0 then
|
||||
print('dlv exited with code', code)
|
||||
end
|
||||
end)
|
||||
assert(handle, 'Error running dlv: ' .. tostring(pid_or_err))
|
||||
stdout:read_start(function(err, chunk)
|
||||
assert(not err, err)
|
||||
if chunk then
|
||||
vim.schedule(function()
|
||||
require('dap.repl').append(chunk)
|
||||
end)
|
||||
end
|
||||
end)
|
||||
-- Wait for delve to start
|
||||
vim.defer_fn(
|
||||
function()
|
||||
callback({type = "server", host = "127.0.0.1", port = port})
|
||||
end,
|
||||
100)
|
||||
end
|
||||
-- https://github.com/go-delve/delve/blob/master/Documentation/usage/dlv_dap.md
|
||||
dap.configurations.go = {
|
||||
{
|
||||
type = "go",
|
||||
name = "Debug",
|
||||
request = "launch",
|
||||
program = "${file}"
|
||||
},
|
||||
{
|
||||
type = "go",
|
||||
name = "Debug test", -- configuration for debugging test files
|
||||
request = "launch",
|
||||
mode = "test",
|
||||
program = "${file}"
|
||||
},
|
||||
-- works with go.mod packages and sub packages
|
||||
{
|
||||
type = "go",
|
||||
name = "Debug test (go.mod)",
|
||||
request = "launch",
|
||||
mode = "test",
|
||||
program = "./${relativeFileDirname}"
|
||||
}
|
||||
}
|
||||
]]
|
||||
@@ -1,9 +0,0 @@
|
||||
-- npm install -g vscode-html-languageserver-bin
|
||||
local capabilities = vim.lsp.protocol.make_client_capabilities()
|
||||
capabilities.textDocument.completion.completionItem.snippetSupport = true
|
||||
|
||||
require'lspconfig'.html.setup {
|
||||
cmd = {"node", DATA_PATH .. "/lspinstall/html/vscode-html/html-language-features/server/dist/node/htmlServerMain.js", "--stdio"},
|
||||
on_attach = require'lsp'.common_on_attach,
|
||||
capabilities = capabilities
|
||||
}
|
||||
@@ -1,28 +0,0 @@
|
||||
-- npm install -g typescript typescript-language-server
|
||||
-- require'snippets'.use_suggested_mappings()
|
||||
-- local capabilities = vim.lsp.protocol.make_client_capabilities()
|
||||
-- capabilities.textDocument.completion.completionItem.snippetSupport = true;
|
||||
-- local on_attach_common = function(client)
|
||||
-- print("LSP Initialized")
|
||||
-- require'completion'.on_attach(client)
|
||||
-- require'illuminate'.on_attach(client)
|
||||
-- end
|
||||
|
||||
require'lspconfig'.tsserver.setup {
|
||||
cmd = {DATA_PATH .. "/lspinstall/typescript/node_modules/.bin/typescript-language-server", "--stdio"},
|
||||
filetypes = { "javascript", "javascriptreact", "javascript.jsx", "typescript", "typescriptreact", "typescript.tsx" },
|
||||
on_attach = require'lsp'.tsserver_on_attach,
|
||||
-- This makes sure tsserver is not used for formatting (I prefer prettier)
|
||||
-- on_attach = require'lsp'.common_on_attach,
|
||||
root_dir = require('lspconfig/util').root_pattern("package.json", "tsconfig.json", "jsconfig.json", ".git"),
|
||||
settings = {documentFormatting = false},
|
||||
handlers = {
|
||||
["textDocument/publishDiagnostics"] = vim.lsp.with(vim.lsp.diagnostic.on_publish_diagnostics, {
|
||||
virtual_text = O.tsserver.diagnostics.virtual_text,
|
||||
signs = O.tsserver.diagnostics.signs,
|
||||
underline = O.tsserver.diagnostics.underline,
|
||||
update_in_insert = true
|
||||
|
||||
})
|
||||
}
|
||||
}
|
||||
@@ -1,16 +0,0 @@
|
||||
-- npm install -g vscode-json-languageserver
|
||||
require'lspconfig'.jsonls.setup {
|
||||
cmd = {
|
||||
"node", DATA_PATH .. "/lspinstall/json/vscode-json/json-language-features/server/dist/node/jsonServerMain.js",
|
||||
"--stdio"
|
||||
},
|
||||
on_attach = require'lsp'.common_on_attach,
|
||||
|
||||
commands = {
|
||||
Format = {
|
||||
function()
|
||||
vim.lsp.buf.range_formatting({}, {0, 0}, {vim.fn.line("$"), 0})
|
||||
end
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1,4 +0,0 @@
|
||||
require'lspconfig'.texlab.setup{
|
||||
cmd = {DATA_PATH .. "/lspinstall/latex/texlab"},
|
||||
on_attach = require'lsp'.common_on_attach
|
||||
}
|
||||
@@ -1,28 +0,0 @@
|
||||
-- https://github.com/sumneko/lua-language-server/wiki/Build-and-Run-(Standalone)
|
||||
local brew_path = "/usr/local/Cellar/lua-language-server/2.5.5/libexec"
|
||||
local sumneko_root_path = brew_path
|
||||
local sumneko_binary = brew_path .. "/bin/lua-language-server"
|
||||
|
||||
require'lspconfig'.sumneko_lua.setup {
|
||||
cmd = {sumneko_binary, "-E", sumneko_root_path .. "/main.lua"},
|
||||
on_attach = require'lsp'.common_on_attach,
|
||||
settings = {
|
||||
Lua = {
|
||||
runtime = {
|
||||
-- Tell the language server which version of Lua you're using (most likely LuaJIT in the case of Neovim)
|
||||
version = 'LuaJIT',
|
||||
-- Setup your lua path
|
||||
path = vim.split(package.path, ';')
|
||||
},
|
||||
diagnostics = {
|
||||
-- Get the language server to recognize the `vim` global
|
||||
globals = {'vim'}
|
||||
},
|
||||
workspace = {
|
||||
-- Make the server aware of Neovim runtime files
|
||||
library = {[vim.fn.expand('$VIMRUNTIME/lua')] = true, [vim.fn.expand('$VIMRUNTIME/lua/vim/lsp')] = true},
|
||||
maxPreload = 10000
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1,22 +0,0 @@
|
||||
-- npm i -g pyright
|
||||
require'lspconfig'.pyright.setup {
|
||||
cmd = {DATA_PATH .. "/lspinstall/python/node_modules/.bin/pyright-langserver", "--stdio"},
|
||||
on_attach = require'lsp'.common_on_attach,
|
||||
handlers = {
|
||||
["textDocument/publishDiagnostics"] = vim.lsp.with(vim.lsp.diagnostic.on_publish_diagnostics, {
|
||||
virtual_text = O.python.diagnostics.virtual_text,
|
||||
signs = O.python.diagnostics.signs,
|
||||
underline = O.python.diagnostics.underline,
|
||||
update_in_insert = true
|
||||
})
|
||||
},
|
||||
settings = {
|
||||
python = {
|
||||
analysis = {
|
||||
typeCheckingMode = O.python.analysis.type_checking,
|
||||
autoSearchPaths = O.python.analysis.auto_search_paths,
|
||||
useLibraryCodeForTypes = O.python.analysis.use_library_code_types
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1,5 +0,0 @@
|
||||
require'lspconfig'.rust_analyzer.setup{
|
||||
cmd = {DATA_PATH .. "/lspinstall/rust/rust-analyzer"},
|
||||
on_attach = require'lsp'.common_on_attach
|
||||
}
|
||||
|
||||
@@ -1,5 +0,0 @@
|
||||
-- npm install -g yaml-language-server
|
||||
require'lspconfig'.yamlls.setup{
|
||||
cmd = {DATA_PATH .. "/lspinstall/yaml/node_modules/.bin/yaml-language-server", "--stdio"},
|
||||
on_attach = require'lsp'.common_on_attach,
|
||||
}
|
||||
@@ -1,119 +0,0 @@
|
||||
-- TODO figure out why this don't work
|
||||
vim.fn.sign_define(
|
||||
"LspDiagnosticsSignError",
|
||||
{ texthl = "LspDiagnosticsSignError", text = "", numhl = "LspDiagnosticsSignError" }
|
||||
)
|
||||
vim.fn.sign_define(
|
||||
"LspDiagnosticsSignWarning",
|
||||
{ texthl = "LspDiagnosticsSignWarning", text = "", numhl = "LspDiagnosticsSignWarning" }
|
||||
)
|
||||
vim.fn.sign_define(
|
||||
"LspDiagnosticsSignHint",
|
||||
{ texthl = "LspDiagnosticsSignHint", text = "", numhl = "LspDiagnosticsSignHint" }
|
||||
)
|
||||
vim.fn.sign_define(
|
||||
"LspDiagnosticsSignInformation",
|
||||
{ texthl = "LspDiagnosticsSignInformation", text = "", numhl = "LspDiagnosticsSignInformation" }
|
||||
)
|
||||
|
||||
vim.cmd("nnoremap gd <cmd>lua vim.lsp.buf.definition()<CR>")
|
||||
vim.cmd("nnoremap <silent> gD <cmd>lua vim.lsp.buf.declaration()<CR>")
|
||||
vim.cmd("nnoremap <silent> gr <cmd>lua vim.lsp.buf.references()<CR>")
|
||||
vim.cmd("nnoremap <silent> gi <cmd>lua vim.lsp.buf.implementation()<CR>")
|
||||
vim.cmd("nnoremap <silent> ca :Lspsaga code_action<CR>")
|
||||
vim.cmd("nnoremap <silent> K :Lspsaga hover_doc<CR>")
|
||||
-- vim.cmd('nnoremap <silent> <C-k> <cmd>lua vim.lsp.buf.signature_help()<CR>')
|
||||
vim.cmd("nnoremap <silent> <C-m> :Lspsaga diagnostic_jump_prev<CR>")
|
||||
vim.cmd("nnoremap <silent> <C-n> :Lspsaga diagnostic_jump_next<CR>")
|
||||
-- scroll down hover doc or scroll in definition preview
|
||||
vim.cmd("nnoremap <silent> <C-f> <cmd>lua require('lspsaga.action').smart_scroll_with_saga(1)<CR>")
|
||||
-- scroll up hover doc
|
||||
vim.cmd("nnoremap <silent> <C-b> <cmd>lua require('lspsaga.action').smart_scroll_with_saga(-1)<CR>")
|
||||
vim.cmd('command! -nargs=0 LspVirtualTextToggle lua require("lsp/virtual_text").toggle()')
|
||||
|
||||
-- Set Default Prefix.
|
||||
-- Note: You can set a prefix per lsp server in the lv-globals.lua file
|
||||
vim.lsp.handlers["textDocument/publishDiagnostics"] = vim.lsp.with(
|
||||
vim.lsp.diagnostic.on_publish_diagnostics, {
|
||||
virtual_text = {
|
||||
prefix = "",
|
||||
spacing = 0,
|
||||
},
|
||||
signs = true,
|
||||
underline = true,
|
||||
}
|
||||
)
|
||||
|
||||
-- symbols for autocomplete
|
||||
vim.lsp.protocol.CompletionItemKind = {
|
||||
" (Text) ",
|
||||
" (Method)",
|
||||
" (Function)",
|
||||
" (Constructor)",
|
||||
" ﴲ (Field)",
|
||||
"[] (Variable)",
|
||||
" (Class)",
|
||||
" ﰮ (Interface)",
|
||||
" (Module)",
|
||||
" 襁 (Property)",
|
||||
" (Unit)",
|
||||
" (Value)",
|
||||
" 練 (Enum)",
|
||||
" (Keyword)",
|
||||
" (Snippet)",
|
||||
" (Color)",
|
||||
" (File)",
|
||||
" (Reference)",
|
||||
" (Folder)",
|
||||
" (EnumMember)",
|
||||
" ﲀ (Constant)",
|
||||
" ﳤ (Struct)",
|
||||
" (Event)",
|
||||
" (Operator)",
|
||||
" (TypeParameter)"
|
||||
}
|
||||
|
||||
--[[ " autoformat
|
||||
autocmd BufWritePre *.js lua vim.lsp.buf.formatting_sync(nil, 100)
|
||||
autocmd BufWritePre *.jsx lua vim.lsp.buf.formatting_sync(nil, 100)
|
||||
autocmd BufWritePre *.lua lua vim.lsp.buf.formatting_sync(nil, 100) ]]
|
||||
-- Java
|
||||
-- autocmd FileType java nnoremap ca <Cmd>lua require('jdtls').code_action()<CR>
|
||||
|
||||
local function documentHighlight(client, bufnr)
|
||||
-- Set autocommands conditional on server_capabilities
|
||||
if client.server_capabilities.document_highlight then
|
||||
vim.api.nvim_exec(
|
||||
[[
|
||||
hi LspReferenceRead cterm=bold ctermbg=red guibg=#464646
|
||||
hi LspReferenceText cterm=bold ctermbg=red guibg=#464646
|
||||
hi LspReferenceWrite cterm=bold ctermbg=red guibg=#464646
|
||||
augroup lsp_document_highlight
|
||||
autocmd! * <buffer>
|
||||
autocmd CursorHold <buffer> lua vim.lsp.buf.document_highlight()
|
||||
autocmd CursorMoved <buffer> lua vim.lsp.buf.clear_references()
|
||||
augroup END
|
||||
]] ,
|
||||
false
|
||||
)
|
||||
end
|
||||
end
|
||||
|
||||
local lsp_config = {}
|
||||
|
||||
if O.document_highlight then
|
||||
function lsp_config.common_on_attach(client, bufnr)
|
||||
documentHighlight(client, bufnr)
|
||||
end
|
||||
end
|
||||
|
||||
--function lsp_config.tsserver_on_attach(client, bufnr)
|
||||
-- lsp_config.common_on_attach(client, bufnr)
|
||||
-- client.server_capabilities.document_formatting = false
|
||||
--end
|
||||
|
||||
-- Use a loop to conveniently both setup defined servers
|
||||
-- and map buffer local keybindings when the language server attaches
|
||||
-- local servers = {"pyright", "tsserver"}
|
||||
-- for _, lsp in ipairs(servers) do nvim_lsp[lsp].setup {on_attach = on_attach} end
|
||||
return lsp_config
|
||||
@@ -1,66 +0,0 @@
|
||||
O = {
|
||||
extras = false,
|
||||
|
||||
--database = {save_location = '~/.config/nvcode_db', auto_execute = 1},
|
||||
python = {
|
||||
linter = '',
|
||||
-- @usage can be 'yapf', 'black'
|
||||
formatter = '',
|
||||
autoformat = false,
|
||||
isort = false,
|
||||
diagnostics = {virtual_text = {spacing = 0, prefix = ""}, signs = true, underline = true},
|
||||
analysis = {type_checking = "basic", auto_search_paths = true, use_library_code_types = true}
|
||||
},
|
||||
dart = {sdk_path = '/usr/lib/dart/bin/snapshots/analysis_server.dart.snapshot'},
|
||||
lua = {
|
||||
-- @usage can be 'lua-format'
|
||||
formatter = '',
|
||||
autoformat = false,
|
||||
diagnostics = {virtual_text = {spacing = 0, prefix = ""}, signs = true, underline = true}
|
||||
},
|
||||
sh = {
|
||||
-- @usage can be 'shellcheck'
|
||||
linter = '',
|
||||
-- @usage can be 'shfmt'
|
||||
formatter = '',
|
||||
autoformat = false,
|
||||
diagnostics = {virtual_text = {spacing = 0, prefix = ""}, signs = true, underline = true}
|
||||
},
|
||||
tsserver = {
|
||||
-- @usage can be 'eslint'
|
||||
linter = '',
|
||||
-- @usage can be 'prettier'
|
||||
formatter = '',
|
||||
autoformat = false,
|
||||
diagnostics = {virtual_text = {spacing = 0, prefix = ""}, signs = true, underline = true}
|
||||
},
|
||||
json = {
|
||||
-- @usage can be 'prettier'
|
||||
formatter = '',
|
||||
autoformat = false,
|
||||
diagnostics = {virtual_text = {spacing = 0, prefix = ""}, signs = true, underline = true}
|
||||
},
|
||||
tailwindls = {filetypes = {'html', 'css', 'scss', 'javascript', 'javascriptreact', 'typescript', 'typescriptreact'}},
|
||||
clang = {diagnostics = {virtual_text = {spacing = 0, prefix = ""}, signs = true, underline = true}},
|
||||
ruby = {
|
||||
diagnostics = {virtualtext = {spacing = 0, prefix = ""}, signs = true, underline = true},
|
||||
filetypes = {'rb', 'erb', 'rakefile'}
|
||||
},
|
||||
go = {},
|
||||
-- css = {formatter = '', autoformat = false, virtual_text = true},
|
||||
-- json = {formatter = '', autoformat = false, virtual_text = true}
|
||||
|
||||
}
|
||||
|
||||
|
||||
|
||||
function _G.put(...)
|
||||
local objects = {}
|
||||
for i = 1, select('#', ...) do
|
||||
local v = select(i, ...)
|
||||
table.insert(objects, vim.inspect(v))
|
||||
end
|
||||
|
||||
print(table.concat(objects, '\n'))
|
||||
return ...
|
||||
end
|
||||
@@ -1,3 +0,0 @@
|
||||
vim.api.nvim_set_keymap('n', '<TAB>', ':BufferNext<CR>', { noremap = true, silent = true })
|
||||
vim.api.nvim_set_keymap('n', '<S-TAB>', ':BufferPrevious<CR>', { noremap = true, silent = true })
|
||||
vim.api.nvim_set_keymap('n', '<S-x>', ':BufferClose<CR>', { noremap = true, silent = true })
|
||||
@@ -1,63 +0,0 @@
|
||||
--[[
|
||||
local dashboard = {
|
||||
custom_header = {
|
||||
' _..._ ',
|
||||
' .\' (_`. _ __ ___ ',
|
||||
' : . : | | _ _ _ __ __ _ _ _\\ \\ / (_)_ __ ___ ',
|
||||
' :) () : | | | | | | \'_ \\ / _` | \'__\\ \\ / /| | \'_ ` _ \\ ',
|
||||
' `. . .\' | |__| |_| | | | | (_| | | \\ V / | | | | | | |',
|
||||
' `-...-\' |_____\\__,_|_| |_|\\__,_|_| \\_/ |_|_| |_| |_|',
|
||||
},
|
||||
footer= {'Laily'}
|
||||
}
|
||||
]]
|
||||
|
||||
local dashboard = {
|
||||
custom_header = {
|
||||
'███╗ ██╗██╗ ██╗ ██████╗ ██████╗ ██████╗ ███████╗',
|
||||
'████╗ ██║██║ ██║██╔════╝██╔═══██╗██╔══██╗██╔════╝',
|
||||
'██╔██╗ ██║██║ ██║██║ ██║ ██║██║ ██║█████╗',
|
||||
'██║╚██╗██║╚██╗ ██╔╝██║ ██║ ██║██║ ██║██╔══╝',
|
||||
'██║ ╚████║ ╚████╔╝ ╚██████╗╚██████╔╝██████╔╝███████╗',
|
||||
'╚═╝ ╚═══╝ ╚═══╝ ╚═════╝ ╚═════╝ ╚═════╝ ╚══════╝'
|
||||
},
|
||||
footer= {'Laily'},
|
||||
}
|
||||
-- vim.g.dashboard_custom_header = {
|
||||
--
|
||||
-- ' _..._ ',
|
||||
-- ' .\' `. ██ ██ ██ ███ ██ █████ ██████ ██ ██ ██ ███ ███ ',
|
||||
-- ' : : ██ ██ ██ ████ ██ ██ ██ ██ ██ ██ ██ ██ ████ ████ ',
|
||||
-- ' : : ██ ██ ██ ██ ██ ██ ███████ ██████ ██ ██ ██ ██ ████ ██ ',
|
||||
-- ' `. .\' ██ ██ ██ ██ ██ ██ ██ ██ ██ ██ ██ ██ ██ ██ ██ ██ ',
|
||||
-- ' `-...-\' ███████ ██████ ██ ████ ██ ██ ██ ██ ████ ██ ██ ██ ',
|
||||
--
|
||||
-- }
|
||||
|
||||
vim.g.dashboard_custom_header = dashboard.custom_header
|
||||
|
||||
vim.g.dashboard_default_executive = 'telescope'
|
||||
|
||||
vim.g.dashboard_custom_section = {
|
||||
a = {description = {' Find File '}, command = 'Telescope find_files'},
|
||||
b = {description = {' Recently Used Files'}, command = 'Telescope oldfiles'},
|
||||
c = {description = {' Load Last Session '}, command = 'SessionLoad'},
|
||||
d = {description = {' Find Word '}, command = 'Telescope live_grep'},
|
||||
e = {description = {' Settings '}, command = ':e '..CONFIG_PATH..'/lv-settings.lua'}
|
||||
-- e = {description = {' Marks '}, command = 'Telescope marks'}
|
||||
}
|
||||
|
||||
-- file_browser = {description = {' File Browser'}, command = 'Telescope find_files'},
|
||||
|
||||
-- vim.g.dashboard_custom_shortcut = {
|
||||
-- a = 'f',
|
||||
-- find_word = 'SPC f a',
|
||||
-- last_session = 'SPC s l',
|
||||
-- new_file = 'SPC c n',
|
||||
-- book_marks = 'SPC f b'
|
||||
-- }
|
||||
-- find_history = 'SPC f h',
|
||||
|
||||
-- vim.g.dashboard_session_directory = CACHE_PATH..'/session'
|
||||
vim.g.dashboard_custom_footer = dashboard.footer
|
||||
|
||||
@@ -1,11 +0,0 @@
|
||||
require'fzf-lua'.setup {
|
||||
fzf_opts = { ['--keep-right'] = ''},
|
||||
fzf_cli_args = '--keep-right',
|
||||
--[[
|
||||
winopts = {
|
||||
preview = {
|
||||
layout = "vertical"
|
||||
}
|
||||
}
|
||||
]]
|
||||
}
|
||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user