背景
本人是 Vim 党,准确说是 Neovim 党,所以文本编辑尽量都用 nvim 完成,当然也就包括写代码啦。
很多 IDE 都有 Vim 模式插件(包括 Xcode),但说实话用得都不习惯,我宁愿同时开着 nvim 和 IDE,用 nvim 编写代码,用 IDE 做一些别的 nvim 不支持的操作。
最近我研究了一下如何更优雅的用 nvim 和 OC 编写 iOS 应用代码,让 nvim 支持 OC 代码补全、错误提示、代码跳转、格式化等功能。
代码补全、错误提示、代码跳转
这些都是 LSP 提供的功能,用到的 LSP 后端是 clangd,clangd 在安装 Xcode 后就会有,不需要再安装,因此只需在 nvim-lspconfig 激活 clangd 的默认配置即可,这里不细说。
用 nvim 打开支持的文件后 clangd 就会启动,会在文件所在目录开始往上寻找“项目根目录”,默认找到 "compile_commands.json", "compile_flags.txt", ".git"
三者之一就视为找到,如果找到的是前两者之一,clangd 则可以配置 LSP 相关功能,而通过一些方法恰好能生成 "compile_commands.json"
文件。
1. 通过 xcpretty 生成(不推荐,该工具太久未更新有 bug,生成的文件信息不全)
安装好 xcpretty 工具,在 Xcode 项目目录下运行,即可生成 compile_commands.json 文件
xcodebuild -project test.xcodeproj CODE_SIGNING_ALLOWED=NO -alltargets | xcpretty -r json-compilation-database --output compile_commands.json
- 将
test.xcodeproj
改为实际的项目配置文件夹
CODE_SIGNING_ALLOWED=NO
为不进行签名,如果有证书的话也可以签名
-alltargets
为编译所有 target
2. 通过脚本生成(推荐)
可以看这篇文章的详细分析,文章附带了一段脚本,内容如下
#!/usr/bin/env bash
# Global variables
readonly GXC_SCRIPT_PATH="$(test -L "${BASH_SOURCE[0]}" && readlink "${BASH_SOURCE[0]}" || echo "${BASH_SOURCE[0]}")"
readonly GXC_SCRIPT_DIR=$(cd "$(dirname "${GXC_SCRIPT_PATH}")"; pwd)
# brew install jq
: "${JQ:=$(command -v jq)}"
: "${OUTPUT_DIR:=$PWD}"
process_fragments() {
local -a files=()
for file in "$@"; do
[[ -f "$file" ]] || continue
# Fragments generated by clang have a comma before EOF
# If a fragment is stil invalid after removing it, it should be skipped
if sed -e '$s/,$//' "$file" | "$JQ" . > /dev/null; then
echo "Processing: $file" >&2
files+=("$file")
else
echo "Skipping: $file" >&2
fi
done
if (( ${#files[@]} == 0 )); then
echo "No input files found!" >&2
return
fi
sed -e '1s/^/[\'$'\n''/' -e '$s/,$/\'$'\n'']/' "${files[@]}" > "${OUTPUT_DIR}/compile_commands.json"
}
generate_database() {
echo "Running build command ..." >&2
if grep -q 'OTHER_CFLAGS' <<< "$*"; then
echo "OTHER_CLAGS detected in build command! This is unsupported as they will be overridden." >&2
return 1
fi
# xcrun xcodebuild ...
# https://reviews.llvm.org/D66555
# Note: Some tools require extra cflags to properly parse the compilation database, e.g. Infer
# OTHER_CFLAGS="\$(inherited) -DNS_FORMAT_ARGUMENT(A)= -D_Nullable_result=_Nullable -gen-cdb-fragment-path ${OUTPUT_DIR}/CompilationDatabase"
"$@" COMPILER_INDEX_STORE_ENABLE=NO OTHER_CFLAGS="\$(inherited) -gen-cdb-fragment-path ${OUTPUT_DIR}/CompilationDatabase"
process_fragments "${OUTPUT_DIR}/CompilationDatabase"/*.json
}
# main
if [[ "${BASH_SOURCE[0]}" != "$0" ]]; then
echo "Script is being sourced: ${GXC_SCRIPT_PATH}"
else
(( DEBUG == 0 )) || set -x
set -euo pipefail
if (( $# == 0 )); then
cat <<-EOF
Usage: $(basename "$0") <xcodebuild-command>
Environment variables:
JQ: Path to a jq binary
OUTPUT_DIR: Writable directory for storing the result. Default: (\$PWD) - $PWD
EOF
exit 1
fi
generate_database "$@"
fi
# Example invocation
# generate-xcode-compilation-database.sh xcodebuild build -project TestProject.xcodeproj -target TestTarget -configuration Debug
将这段脚本保存下来,然后使用脚本 + 编译命令的方式来运行,如
generate-xcode-compilation-database.sh xcodebuild -project test.xcodeproj CODE_SIGNING_ALLOWED=NO -alltargets
注意
这两种方法执行成功后都会在执行目录下生成 compile_commands.json
文件,此时用 nvim 打开项目里的文件,就能看到 LSP 生效了。
两种方法都需要先把项目编译一遍才能获得 compile_commands.json
文件,文件里包含了参与编译的源文件的信息,根据这些信息 clangd 才能实现代码检测、补全、跳转等功能,所以我们需要尽可能的让参与到编译的源文件更多,比如可以给 xcodebuild
命令使用 -alltargets
参数。
如果需要重新生成,可以先运行 xcodebuild clean
,再重复运行之前的命令。
代码格式化
这里选择很多,我用的是 formatter.nvim
给 formatter.nvim 增加以下配置即可
filetype = {
objc = { require("formatter.filetypes.c").clangformat },
objcpp = { require("formatter.filetypes.cpp").clangformat },
},