前言
gofmt 格式化不支持空格替代tab缩进。最早gofmt是有选项支持这个功能的,但在2014年6月开始,这个选项就被取消了。详见 Gofmt No Longer Allows Spaces. Tabs Only (code.google.com)
现在我们去看官方的gofmt文档,可以很明显看到只使用tab来缩进。
Gofmt formats Go programs. It uses tabs for indentation and blanks for alignment. Alignment assumes that an editor is using a fixed-width font.
正因为如此,在vscode设置里面针对golang的"editor.insertSpaces": true配置是无效,不起作用的。
先前解决方案
使用goformat
Alternative to gofmt with configurable formatting style (indentation etc.) 使用goformat是可以做到使用space来替代tab做为缩进。 可惜这个项目不再维护,已经好几年没有更新了。对于目前go1.19的泛型,goformat不支持泛型的格式化,这个不是我们所希望的。所以,目前来说,只能放弃goformat
重新修改gofmt代码
const (
tabWidth = 8
printerMode = printer.UseSpaces | printer.TabIndent | printerNormalizeNumbers
)
///将上面的改成如下即可
const (
tabWidth = 2 ///你可以改成你需要的空格数
printerMode = printer.UseSpaces
)
然后自己编译,进行替换本地的 /usr/local/bin/gofmt即可. 这样的问题是,后续维护起来很麻烦,每次都得自己去编译。 同时,在vscode里面如果使用golps就会忽略gofmt. 不过,我们可以使用Go:Format Tool中选择custom,然后在Go: Alternate Tools手工进行配置新的format工具即可.见下面的做法。
目前比较好的解决方案
目前gofmt的格式化,基本我们都想采用,除了强制使用tab来进行缩进。 看到vscode的extension: golang.go 中,可以自定义格式化工具,此时我们就可以使用脚本来达到我们的目的. extension setting中选择@ext:golang.go format过滤, Go:Format Tool中选择custom,然后在Go: Alternate Tools手工进行配置新的format工具即可.
新建go-fmt.sh脚本
我们将文件放在/works/UseTools/vscode-settings/go-fmt.sh
chmod a+x /works/UseTools/vscode-settings/go-fmt.sh
go-fmt.sh内容如下.
#!/bin/bash
## 此处只是为了看效果
echo /usr/local/go/bin/gofmt $*
/usr/local/go/bin/gofmt $*
配置Go: Alternate Tools.
vim ~/.config/Code/User/settings.json 确保此json包含下面的key
{
...
"go.formatTool": "custom",
"go.alternateTools": {
"customFormatter": "/works/UseTools/vscode-settings/go-fmt.sh"
}
}
为了看效果,我们上面将go-fmt.sh内容为 echo /usr/local/go/bin/gofmt $*,
然后一旦保存,就发现编辑的最上面多了一行."/usr/local/go/bin/gofmt"
到此,恭喜你,配置自定义的golang的format脚本运行正确。
我们也发现,这个脚本无论输出什么,最终都会到vscode的编辑内容中。 那最后就是我们使用shell脚本来替换开头的tab为空格。
完善go-fmt.sh脚本
上面我们说到,只需要在/works/UseTools/vscode-settings/go-fmt.sh脚本里面,将每行开头的tab字符换成我们需要的空格即可. 此时,我们用上的就是sed这个工具。 sed替换模式.
#!/bin/bash
## 此处只是为了看效果
## echo /usr/local/go/bin/gofmt $*
## 这里可以改成你需要的空格数
tab_space=" "
/usr/local/go/bin/gofmt $* | sed -E "s/^\t+/${tab_space}/"
你保存一下go文件,就会发现,和你想象的不一样。缩进没有达到我们的目的,没办法把相同数量的tab字符替换成相同数量的{tab_space}/",但这个是将一行里面所有的tab都换成空格,不是我们希望的开头的tab字符。
那我们有没办法,将每行最开始的tab字符中最后一个tab字符替换${tab_space}? 这个是可以达到的.
我们在terminal中开始尝试.
echo -e "\t\t\t\t\t\tzdddddfsdfs" > ~/test.txt
sed -E -e "s/^(\s*)\t([^\t])/\1SPACE\2/" ~/test.txt
此时输出" SPACEzdddddfsdfs". 如果要将多个tab进行替换,可以使用多个 -e来执行. 比如:
sed -E -e "s/^(\s*)\t([^\t])/\1SPACE\2/" -e "s/^(\s*)\t([^\t])/\1SPACE\2/" -e "s/^(\s*)\t([^\t])/\1SPACE\2/" ~/test.txt
此时输出:" SPACESPACESPACEzdddddfsdfs" 确实是替换了三个spaces.
如果是任何多个tab,难道我们要加更多个-e的条件吗? 并且每个条件都是无条件执行的,也就是就算没有那边多tab字符,也要执行这么多的条件,效率太差。
其实,sed里面有个t指令进行分支跳转。
echo -e "\t\t\t\t\t\tzdddddfsdfs" > ~/test.txt
tab_replace="[SPACE]"
sed -E -e "
:loop; s/^(\s*)\t([^\t])/\1${tab_replace}\2/;
t loop; " ~/test.txt
上面输出: "[SPACE][SPACE][SPACE][SPACE][SPACE][SPACE]zdddddfsdfs"
这样终于达到我们的目的了,效率也不错,每行有多少个tab开头的,就会执行几次替换。
go-fmt.sh最终版本
#!/bin/bash
###这里可以替换成你需要的空格数.
tab_replace=" "
command_out=`/usr/local/go/bin/gofmt $*`
if [ "$?" -ne "0" ]; then
exit 1
fi
echo "$command_out" | sed -E "
:loop; s/^(\s*)\t([^\t])/\1${tab_replace}\2/;
t loop; "
上面我们增加了判断gofmt是否失败,如果失败,直接exit 1,此时vscode的内容不变。
如果不增加此判断,一旦go文件的语法不对,格式化失败,就会将文件内容替换成空字符串。这也不是我们希望看到的。
小结
gofmt为了统一编码风格,将先前的可以使用space的选项关闭了。但我们为了所有语言风格的缩进都保持一致,所以,选择了将Tab替换成2个space的缩进风格。先前有goformat项目支持这个选项,无奈此项目没更新,无法支持最新的泛型格式化。还好,可以使用自定义的脚本来进行格式化。我们只是将gofmt的输出再次进行处理。既然是每行处理文本,首先想到的就是sed。在不断的尝试中,我们可以再次感受到sed的强大,最终终于实现了我们需要的替换格式。最后一个问题,就是要处理格式化错误的情况,只需要增加一个判断条件即可。
我们终于实现我们需要的功能,同时也没有增加后续的维护负担。后面只需要更新官方版本的gofmt文件即可,我们脚本是不用变化的。