只因乐趣,用 6 年时间切换到了 vim

注:机翻,未校。


I finally switched to vim - and it took me only 6 years to learn it.

我终于切换到了 vim - 我只花了 6 年时间就学会了它。

Emanuel Cepoi

Aug 17, 2024

When I was about 16, I watched @realGeorgeHotz streaming. Terminals filled the screen, his cursor darting everywhere, yet always landing exactly where he needed it. The mouse? It wasn’t being used at all.
当我大约 16 岁时,我观看 @realGeorgeHotz 直播。终端填满了屏幕,他的光标到处飞来飞去,但总是准确地落在他需要的地方。鼠标?它根本没有被使用。

I was impressed. I had never seen anyone code from a terminal window before, and as a VS Code user, I had never been that fast when writing code. At that moment, I knew I had to use whatever George was using. I knew I wanted to reach that coolness level - little did I know that I would be able to do it only after almost 6 years.
我印象深刻。我以前从未见过有人在终端窗口中编写代码,作为 VS Code 用户,我在编写代码时从未如此快速。在那一刻,我知道我必须使用乔治正在使用的任何东西。我知道我想达到那种冷静的水平 —— 我几乎不知道我只有在将近 6 年后才能做到这一点。

By asking around, I discovered he was using vim. But not just any vim—he had this intriguing “0 plugins” philosophy, not even an LSP. As I got deeper into the rabbit hole, I stumbled upon the concept of “unix as ide.” — I was hooked.
通过四处打听,我发现他正在使用 vim。但不仅仅是任何 vim—— 他有这种有趣的 “0 插件” 哲学,甚至没有 LSP。当我深入兔子洞时,我偶然发现了 “unix as ide” 的概念 —— 我被迷住了。

This is where my vim journey began.
这就是我的 vim 之旅开始的地方。

Making first contact 初次接触

I google how to install vim on my machine and quickly find out that it is already preinstalled. Filled with excitement, I run “vim” in my terminal, and there it is.
我在谷歌上搜索如何在我的机器上安装 vim,并很快发现它已经预装了。我兴奋地在我的终端里运行 “vim”,它就在那里。

But within seconds, I realize everything feels strange. I can’t type, I can’t figure out how to open a file, I can’t do anything.
但几秒钟后,我意识到一切都感觉很奇怪。我不会打字,我不知道如何打开文件,我什么也做不了。

Frustration sets in, and I decide to close it. However, I don’t even know how to do that. I end up searching for the famous “How to close VIM” question on StackOverflow. To my surprise, it has millions of views. At least I’m not the only one who is confused.
挫败感袭来,我决定关闭它。但是,我什至不知道该怎么做。我最终在 StackOverflow 上搜索了著名的 “如何关闭 VIM” 问题。令我惊讶的是,它有数百万的观看次数。至少我不是唯一一个感到困惑的人。

I google around a bit more and find out about vimtutor.
I try vimtutor, and things start to make sense. I am now able to type a little and move my cursor around using hjkl.
我多用谷歌搜索了一下,发现了 vimtutor。我尝试了 vimtutor,事情开始变得有意义了。我现在能够输入一点并使用 hjkl 移动光标。

I rush through vimtutor in less than a day. Even though I still feel confused, I believe that with practice, I will get better. The keystrokes are hard to remember, and hjkl feels strange. The different ways to enter insert mode puzzle me—when should I press “i” vs “I”? Why does deleting something overwrite my clipboard? How do I jump around more efficiently? I am slow and I hate it, I want to be fast.
我在不到一天的时间内就匆匆忙忙地完成了 vimtutor。尽管我仍然感到困惑,但我相信通过练习,我会变得更好。击键很难记住,hjkl 感觉很奇怪。进入插入模式的不同方法让我感到困惑 —— 我应该什么时候按 “i” 还是 “I”?为什么删除某些内容会覆盖我的剪贴板?我如何更有效地跳来跳去?我很慢,我讨厌它,我想快。

I try again the next day. Although I feel frustrated because I can’t remember anything from the day before and have to redo vimtutor, I remain optimistic. With persistence, I know I will eventually master it - at least that’s what they all say.
第二天我再试一次。虽然我因为不记得前一天的任何事情而感到沮丧,不得不重做 vimtutor,但我仍然保持乐观。只要坚持不懈,我知道我最终会掌握它 —— 至少他们都是这么说的。

I give up on it. 我放弃了。

A second chance 第二次机会

It’s five years later. I’m now a professional Software Engineer, and my passion for writing code has become my career. I write code every day and am a huge fan of IntelliJ. I use it for everything—front-end development, mobile development, and back-end development (not Java). It’s amazing. Life is good.
五年后。我现在是一名专业的软件工程师,我对编写代码的热情已经成为我的职业。我每天都在写代码,并且是 IntelliJ 的忠实粉丝。我用它来做所有事情 —— 前端开发、移动开发和后端开发(不是 Java)。太神奇了。生活是美好的。

However, I still feel like something is missing. I still use my mouse when coding and I’m not part of the cool vim users club. I know a bit of vim and can edit files on a server occasionally, but not enough to replace IntelliJ. And not enough to earn the “I know vim” bragging rights.
但是,我仍然觉得缺少一些东西。我仍然在编码时使用鼠标,我不是很酷的 vim 用户俱乐部的一员。我懂一点 vim,偶尔可以在服务器上编辑文件,但不足以替换 IntelliJ。而且还不足以赢得 “我知道 vim” 的吹嘘权。

Trying vim again is a thought that lingers in the back of my mind, but the overstimulation I felt five years ago still haunts me.
再次尝试 vim 是一个萦绕在我脑海中的想法,但我五年前感受到的过度刺激仍然困扰着我。

I stumble across @ThePrimeagen streaming. I notice he’s using vim, and memories of my own attempts from years ago come flooding back. Watching him effortlessly jump around code and use crazy shortcuts, I realize he’s a dedicated vim fan, unlike others cough @realGeorgeHotz cough, who switched to VS Code.
我偶然发现了 @ThePrimeagen 流媒体。我注意到他正在使用 vim,我多年前尝试的记忆又涌了回来。看着他毫不费力地在代码中跳来跳去并使用疯狂的快捷方式,我意识到他是一个忠实的 vim 粉丝,不像其他人咳嗽 @realGeorgeHotz 咳嗽,他们切换到 VS Code。

I feel hope. I feel like I can make it.
我感到希望。我觉得我能做到。

I decide to try Neovim. I google it and find that it’s an improved version with Lua support. I don’t know Lua, but it seems easier than VimScript at first glance.
我决定尝试 Neovim。我用谷歌搜索了一下,发现它是一个支持 Lua 的改进版本。我不了解 Lua,但乍一看它似乎比 VimScript 容易。

This time, I decide to be smarter and use a preconfigured option since writing my own config still feels impossible. I grab a copy of https://astronvim.com/and give it a try.
这一次,我决定更聪明并使用预配置选项,因为编写自己的配置仍然感觉不可能。我拿起一本 https://astronvim.com/ 试一试。

Now, I have a fully-fledged Neovim configuration. I have a file tree, I can search for things, open files, and navigate around—though just barely. But I feel like I’m making progress, right?
现在,我有了一个成熟的 Neovim 配置。我有一个文件树,我可以搜索内容、打开文件和四处导航 - 尽管只是勉强。但我觉得我正在进步,对吧?

I try using it for two days at my job, and it’s impossible. It feels strange compared to my IntelliJ setup. Where are my refactoring capabilities? How do I search files faster? How do I search only the most recently opened files? How do I rename things? Why isn’t auto-import working? Also, why is hjkl so annoying, and why can’t I remember the keys properly?
我试着在工作中使用它两天,这是不可能的。与我的 IntelliJ 设置相比,这感觉很奇怪。我的重构能力在哪里?如何更快地搜索文件?如何仅搜索最近打开的文件?如何重命名内容?为什么自动导入不起作用?另外,为什么 hjkl 这么烦人,为什么我记不清键?

I’m frustrated again. I’m trying to do my job, but I simply can’t. I’m way too slow, and I can’t afford this since I’m trying it on company time. Besides, this astronvim setup feels extremely bloated.
我又感到沮丧了。我正在努力做我的工作,但我就是做不到。我太慢了,我负担不起这个,因为我是在公司时间尝试的。此外,这个 astronvim 设置感觉非常臃肿。

I give up on it. 我放弃了。

The final final final chance 最后的 最后 最后的机会

It’s two weeks later. I notice more streamers using Neovim. I’m still not part of the cool kids club, but I’m motivated to make the switch.
两周后。我注意到更多的主播使用 Neovim。我仍然不是酷儿童俱乐部的一员,但我有动力做出改变。

This time, I decide to be smart about it. I’m not going to use a distro like Astronvim anymore. I google around and learn that I can use Vim keybindings within IntelliJ. IdeaVim comes to the rescue.
这一次,我决定聪明一点。我不会再使用像 Astronvim 这样的发行版了。我四处搜索,了解到我可以在 IntelliJ 中使用 Vim 键绑定。IdeaVim 来救援。

This is amazing news. I can now have all the great IntelliJ functionalities while also being able to jump around code using Vim motions.
这是一个惊人的消息。我现在可以拥有所有出色的 IntelliJ 功能,同时还能够使用 Vim 动作跳转代码。

I remember that most of my frustration with Vim was because I didn’t know how to do basic operations like gtd (go to definition), gtr (go to reference), find all references, and rename things globally. Using IntelliJ with Vim keybindings seems like a great idea because IntelliJ can handle all the LSP tasks, and I can use Vim motions to navigate code quickly.
我记得我对 Vim 的大部分挫败感是因为我不知道如何执行基本操作,例如 gtd(转到定义)、gtr(转到引用)、查找所有引用以及全局重命名事物。将 IntelliJ 与 Vim 键绑定一起使用似乎是个好主意,因为 IntelliJ 可以处理所有 LSP 任务,而且我可以使用 Vim 动作来快速导航代码。

I make the switch - partially.
我做出了改变 - 部分。

It’s now my first day at work using IntelliJ with Vim motions. I’m slow, and everything still feels frustrating, but I know this is temporary. I manage to push through the first day, and being inside IntelliJ definitely helps a lot.
现在是我使用 IntelliJ 和 Vim 动作工作的第一天。我很慢,一切仍然让人感到沮丧,但我知道这是暂时的。我设法度过了第一天,而进入 IntelliJ 肯定有很大帮助。

On the second day, I manage to move around a bit more. I now feel like I know my way around hjkl, and I don’t get them confused anymore. I’m improving and motivated to continue.
第二天,我设法多走动一下。我现在觉得我知道我的方式 hjkl,,我不再让他们感到困惑了。我正在进步,并有动力继续前进。

I push through the whole week.
我坚持了整整一周。

The big switch 大转变

I’ve been using Vim motions within IntelliJ for almost two months now. I’m fast. I can navigate code so quickly that IntelliJ sometimes can’t keep up with me (probably because it is written in Java).
我已经在 IntelliJ 中使用 Vim 运动将近两个月了。我跑得很快。我可以如此快速地浏览代码,以至于 IntelliJ 有时无法跟上我的步伐(可能是因为它是用 Java 编写的)。

I’m a master at both vertical and horizontal movements, jumping inside quotes and brackets. I’ve discovered the F and T motions, and now I always look for “Vim modes” in every app I use. Sixteen-year-old me would be proud.
我是垂直和水平运动的大师,在引号和括号内跳跃。我已经发现了 F 和 T 运动,现在我总是在我使用的每个应用程序中寻找 “Vim 模式”。十六岁的我会感到骄傲。

But I’m still not fully part of the cool kids club. So I decide to give raw nvim another try, but this time I plan to be smart about it.
但我仍然没有完全成为酷儿童俱乐部的一员。所以我决定再试一次 raw nvim,但这次我打算聪明一点。

I remember from the past that I hated how complex the astronvim documentation felt, and I disliked always having to look up shortcuts in their documentation. So I decide it’s time for me to write my own nvim configuration from scratch.
我记得过去我讨厌 astronvim 文档的复杂感,我不喜欢总是不得不在他们的文档中查找快捷方式。所以我决定是时候从头开始编写我自己的 nvim 配置了。

I want to have a deep understanding of my entire configuration and properly understand all the pieces, so I spend the weekend learning Lua.
我想对我的整个配置有深入的理解,并正确地理解所有的部分,所以我花了周末来学习 Lua。

By knowing Lua, nvim starts to make sense and my first nvim config is starting to come to life.
通过了解 Lua,nvim 开始变得有意义,我的第一个 nvim 配置开始栩栩如生。

I now have an LSP, and I made my first customization by getting rid of the annoying auto-complete pop-up on every key press. Now, I only see it when I want to by using command-space.
I’ve configured Telescope, making it easy to find everything I need, and I’ve set up the beautiful Gruvbox theme.
我现在有一个 LSP,我通过摆脱每次按键时烦人的自动完成弹出窗口进行了我的第一个自定义。现在,我只在我想使用 command-space 时看到它。 我已经配置了 Telescope,可以轻松找到我需要的一切,并且我已经设置了漂亮的 Gruvbox 主题。

I learned about macros, registers, and the quickfix list. I finally understand where my copied text goes when I delete something. I can make changes across the entire codebase and look cool doing it all from my terminal—life is great again. Now, I can replace IntelliJ with a very minimal nvim setup.
我学习了宏、寄存器和快速修复列表。当我删除某些内容时,我终于明白了我复制的文本在哪里。我可以对整个代码库进行更改,并且在我的终端上完成所有操作看起来很酷 - 生活再次变得美好。现在,我可以用非常小的 nvim 设置替换 IntelliJ。

I uninstall InteliJ and cancel my subscription.
我卸载了 InteliJ 并取消了我的订阅。

I’m finally part of the cool kids club—I finally get to say “I use Vim.”
我终于成为了酷炫儿童俱乐部的一员 —— 我终于可以说 “我用 Vim”。

Present 目前

It’s been a year, and wow, I’ve been using nvim every single day! I feel faster than ever. I can type at 200 WPM, putting me in the top 0.1% worldwide. Combining my typing speed with vim motions makes me incredibly fast—faster than anyone I know.
已经一年了,哇,我每天都在使用 nvim!我感觉比以往任何时候都快。我可以以 200 WPM(words per minute) 的速度打字,使我在全球排名前 0.1%。将我的打字速度与 vim 动作相结合,使我的速度快得令人难以置信 —— 比我认识的任何人都快。

I take every chance to brag about using vim and try to convince everyone else to join the vim revolution.
我抓住一切机会吹嘘自己使用了 vim,并试图说服其他人加入 vim 革命。

More things are starting to make sense to me, and I finally understand why @realGeorgeHotz was using vim without any plugins.
更多的事情开始对我来说变得有意义,我终于明白为什么 @realGeorgeHotz 在没有任何插件的情况下使用 vim。

I’ve also started using tmux, and I’m gradually joining the Unix as IDE movement.
我也开始使用 tmux,并且我逐渐加入 Unix 作为 IDE 运动。

I can honestly say that learning vim was one of the best choices of my life. The next best choice is probably going to be fully embracing the “Unix as IDE” philosophy, which I’ve already started doing, but that’s a story for another article.
我可以诚实地说,学习 vim 是我一生中最好的选择之一。下一个最佳选择可能是完全接受 “Unix as IDE” 的理念,我已经开始这样做了,但这是另一篇文章的故事。


via:


如何安装和使用 Neovim

作者:Anuj Sharma

译者:LCTT geekpi

你一定已经听说过著名的 Vim 文本编辑器。然而,这篇文章是关于它的复刻 Neovim。

Vim 也是 Vi 的一个复刻,这使得 Neovim 成为复刻的复刻。所以,你最终可以使用任何东西,因为它有一些独特的特征。

如果你决定使用 Neovim,让我帮助你在 Ubuntu 和其他 Linux 发行版上开始使用它。

Neovim 概述

img

Neovim 是一个以其简单的维护和社区贡献而闻名的项目。此外,开发工作很好地分配在多个开发人员之间。

对于最终用户而言,Neovim 的可扩展性远超人们的预期。Neovim 的目标是在不损害 Vim 传统功能的情况下提供更好的应用。

使用 Neovim 的 GUI,你可以获得增强的编辑体验。

Neovim 的特点

虽然你可以在其官方网站及其 GitHub 页面 上探索所有相关信息。让我强调一些特点:

  • 现代图形界面
  • 从多种语言访问 API,包括(C/C++、C#、Go、Haskell、Java/Kotlin、JavaScript/Node.js、Lua、Perl、Python、Ruby、Rust 等等)
  • 嵌入式、可编写脚本的终端仿真器
  • 异步任务控制
  • 多个实例之间的共享数据
  • XDG 基本目录支持
  • 与大多数 Vim 插件兼容,包括 Ruby 和 Python 插件

💡 我们建议你在开始使用 Vim 和 Neovim 之前,先了解其用法。

在 Ubuntu 和 Linux 上安装 Neovim

由于 Neovim 的流行,安装它很简单。因为 Neovim 在你选择的任何发行版的官方仓库中都可用。

如果你需要,我们还有一个 在 Linux 上安装 Vim 的指南。
How to Install Latest Vim 9.0 on Ubuntu
https://itsfoss.com/install-latest-vim-ubuntu/

要在 Ubuntu 上安装 Neovim,只需单击停靠区中的 Ubuntu 软件图标。然后,搜索 Neovim。你可以根据自己的喜好选择 Snap 或 deb 版本。但是,deb 包版本是过时的 Neovim 版本。

neovim listed on ubuntu software center

neovim listed on ubuntu software center

如果你不想要 Snap 版本,你可以从 Neovim 的 Github 发布页 获取最新的 deb 文件。如果你仔细查看发布页面,还有一个 Appimage 文件。

如果你想使用终端而不是 GUI,你可以执行以下操作:

对于基于 Ubuntu 和 Debian 的发行版,输入以下命令进行安装:

sudo apt install neovim

对于 Manjaro 和 Arch Linux,使用以下命令更新你的系统并安装 Neovim:

sudo pacman -Syu neovim

对于 Fedora,使用以下命令:

sudo dnf install -y neovim

要安装 Flatpak,你可以使用此命令:

flatpak install flathub io.neovim.nvim

最后,要安装 Snap 包,请使用以下命令:

sudo snap install nvim --classic

如果你更喜欢从源代码构建软件,请遵循该项目的 Github 页面上给出的 步骤

如何使用 Neovim

要在安装完成后使用 Neovim,只需从应用菜单启动它,方法是按超级键并输入 neovim。这将打开一个带有 Neovim 的终端窗口。

img

或者你可以打开你选择的任何终端仿真器并输入以下命令:

nvim

是的,不是 neovim,而是 nvim。例如,要编辑 /etc/fstab,请输入以下命令:

sudo nvim /etc/fstab

现在 fstab 文件将被打开,如下面的截图所示:

img

如果你无法退出编辑器,请不要担心,按下 Shift+Z+Z。例如,如果此快捷键令人困惑,请使用你需要的键在任何文本编辑器中输入大写的 ZZ 就明白了。

关于这一点,我应该重申,你需要学习 Vim 或 Neovim 并阅读文档才能有效地利用其所有功能。

总结

安装和启动 Neovim 非常简单,但学习它肯定需要一些键盘操作。

这只是编辑器的概述。我不是这方面的专家,但我希望这对所有初学者都有好处!

💬 你使用 Neovim 吗? 在下面的评论中分享你的经验。


via:


从零开始配置 Neovim(Nvim)

MartinLwx

2023-02-08

更新 2024-04-04

,重写部分章节,Package Manager 从 packer.nvim 换成了 lazy.nvim

🤗。

  • 如果之前跟着本篇教程采用 packer.nvim 进行安装的话,可以参考我迁移的时候的 commit,修改完之后记得用 :checkhealth lazy 检查下,因为还需要删除 packer.nvim 的一些旧文件

进一步阅读:

  • 如何为一门新的编程语言配置 Neovim ,以及支持第三方代码格式化工具,见下篇文章

版本信息

我使用的是 Macbook pro 2020 Intel 版本,系统版本为 macOS 14.3.1。我的 Nvim 版本信息如下

NVIM v0.9.5
Build type: Release
LuaJIT 2.1.1710088188

   system vimrc file: "$VIM/sysinit.vim"
  fall-back for $VIM: "/usr/local/Cellar/neovim/0.9.5/share/nvim"

Run :checkhealth for more info

为什么选择 Neovim

在使用 Vim 一年多之后,我越发觉得 Vim 的配置麻烦,启动加载速度也不尽人意。我也很不喜欢 Vimscript 的写法,这导致我决定使用 Neovim(Nvim)。我决定重新配置 Nvim。为什么会想要重新配置而不是迁移配置呢?因为我想顺便趁着这个机会,重新审视我本来 Vim 的配置,以及将本来用到的的插件替换为现在的 SOTA(State-of-the-art)。我自从看完 MIT 的 Missing semester 的课配置了 ~/.vimrc 之后就很长时间都没有再编辑 ~/.vimrc 文件了

我认为在配置 Nvim 的时候弄清楚每一个选项的意思是很有必要的,因此我在这篇博客中会尽量解释清楚每个选项的含义,同时将解释放在注释里面,即我尽量让我自己的配置文件是 self-contained 而且可读性强的

💡 当然,这难免有疏漏。别忘了我们永远可以在 Nvim 里面输入 :h <name> 来看到更为详细的解释

💡 该篇博客假定你对 Vim 有基本了解

Nvim 配置基础知识

Lua 语言

在配置 Nvim 的时候,我会尽可能用 Lua 语言写配置,因此你有必要了解一下 Lua 的基本语法和语义。可以快速浏览一下 Learn Lua in Y minutes 了解大概

配置文件路径

Nvim 的配置目录在 ~/.config/nvim 下。在 Linux/Mac 系统上,Nvim 会默认读取 ~/.config/nvim/init.lua 文件,理论上来说可以将所有配置的东西都放在这个文件里面,但这样不是一个好的做法,因此我划分不同的文件和目录来分管不同的配置

首先看下按照本篇教程配置 Nvim 之后,目录结构看起来会是怎么样⬇️

nvim
├── init.lua
└── lua
    ├── colorscheme.lua
    ├── config
    │   └── nvim-cmp.lua
    ├── keymaps.lua
    ├── lsp.lua
    ├── options.lua
    └── plugins.lua

解释如下

  • init.lua
    

    Nvim
    

    配置的 Entry point,我们主要

    用来导入其他 *.lua 文件

    • colorscheme.lua 配置主题
    • keymaps.lua 配置按键映射
    • lsp.lua 配置 LSP
    • options.lua 配置选项
    • plugins.lua 配置插件
  • config 用于存放各种插件自身的配置,文件名为插件的名字,这样比较好找。这里的 nvim-cmp.lua 就是 nvim-cmp 插件的配置文件

  • lua
    

    目录。当我们在 Lua 里面调用

    require
    

    加载模块(文件)的时候,它会自动在

    lua
    

    文件夹里面进行搜索

    • 将路径分隔符从 / 替换为 .,然后去掉 .lua 后缀就得到了 require 的参数格式
    • 比如要导入上面的 nvim-cmp.lua 文件,可以用 require('config.nvim-cmp')

选项配置

主要用到的就是 vim.gvim.optvim.cmd 等,我制造了一个快速参照对比的表格

In VimIn NvimNote
let g:foo = barvim.g.foo = bar
set foo = barvim.opt.foo = barset foo = vim.opt.foo = true
some_vimscriptvim.cmd(some_vimscript)

按键配置

Nvim 里面进行按键绑定的语法如下,具体的解释可以看 :h vim.keymap.set

vim.keymap.set(<mode>, <key>, <action>, <opts>)

从零开始配置 Nvim

在阅读了前面一些配置基础之后,现在我们可以从头开始,由简到易一步步配置 Nvim

安装 Nvim

我用的是 Mac,用 Homebrew 安装 Nvim 非常容易,只要运行如下命令即可1

$ brew install neovim

在安装完成之后,如果 ~/.config/nvim 目录不存在,创建目录并新建 init.lua 文件

$ mkdir ~/.config/nvim
$ mkdir ~/.config/nvim/lua
$ touch ~/.config/nvim/init.lua

💡 配置文件编辑保存之后,重启 Nvim 就能看到效果,后面默认每次小章节配置完成后就重启

选项配置

选项配置功能一览:

  • 默认采用系统剪贴板,同时支持鼠标操控 Nvim
  • Tab 和空格的换算
  • UI 界面
  • “智能”搜索

新建 ~/.config/nvim/lua/options.lua 文件并加入如下内容⬇️

-- Hint: use `:h <option>` to figure out the meaning if needed
vim.opt.clipboard = 'unnamedplus' -- use system clipboard
vim.opt.completeopt = { 'menu', 'menuone', 'noselect' }
vim.opt.mouse = 'a' -- allow the mouse to be used in Nvim

-- Tab
vim.opt.tabstop = 4 -- number of visual spaces per TAB
vim.opt.softtabstop = 4 -- number of spacesin tab when editing
vim.opt.shiftwidth = 4 -- insert 4 spaces on a tab
vim.opt.expandtab = true -- tabs are spaces, mainly because of python

-- UI config
vim.opt.number = true -- show absolute number
vim.opt.relativenumber = true -- add numbers to each line on the left side
vim.opt.cursorline = true -- highlight cursor line underneath the cursor horizontally
vim.opt.splitbelow = true -- open new vertical split bottom
vim.opt.splitright = true -- open new horizontal splits right
-- vim.opt.termguicolors = true        -- enabl 24-bit RGB color in the TUI
vim.opt.showmode = false -- we are experienced, wo don't need the "-- INSERT --" mode hint

-- Searching
vim.opt.incsearch = true -- search as characters are entered
vim.opt.hlsearch = false -- do not highlight matches
vim.opt.ignorecase = true -- ignore case in searches by default
vim.opt.smartcase = true -- but make it case sensitive if an uppercase is entered

然后打开 init.lua,用 require 导入刚才写的 options.lua 文件

require('options')

按键配置

按键功能一览:

  • <C-h/j/k/l> 快速在多窗口之间移动光标
  • Ctrl + 方向键进行窗口大小的调整
  • 选择模式下可以一直用 Tab 或者 Shift-Tab 改变缩进

新建 ~/.config/nvim/lua/keymaps.lua 文件并放入如下内容⬇️

-- define common options
local opts = {
    noremap = true,      -- non-recursive
    silent = true,       -- do not show message
}

-----------------
-- Normal mode --
-----------------

-- Hint: see `:h vim.map.set()`
-- Better window navigation
vim.keymap.set('n', '<C-h>', '<C-w>h', opts)
vim.keymap.set('n', '<C-j>', '<C-w>j', opts)
vim.keymap.set('n', '<C-k>', '<C-w>k', opts)
vim.keymap.set('n', '<C-l>', '<C-w>l', opts)

-- Resize with arrows
-- delta: 2 lines
vim.keymap.set('n', '<C-Up>', ':resize -2<CR>', opts)
vim.keymap.set('n', '<C-Down>', ':resize +2<CR>', opts)
vim.keymap.set('n', '<C-Left>', ':vertical resize -2<CR>', opts)
vim.keymap.set('n', '<C-Right>', ':vertical resize +2<CR>', opts)

-----------------
-- Visual mode --
-----------------

-- Hint: start visual mode with the same area as the previous area and the same mode
vim.keymap.set('v', '<', '<gv', opts)
vim.keymap.set('v', '>', '>gv', opts)

然后在 init.lua 文件里面再次加上一行导入这个文件

...
require('keymaps')

💡 ... 表示省略了其他部分的代码(为了节省博客的篇幅)

安装插件管理器

一个强大的 Nvim 离不开插件的支持。我选用的是当下最为流行 lazy.nvim。它支持如下许多特性:

  • 正确处理不同插件之间的依赖
  • 支持定制 Lazy loading,比如基于 Event、Filetype 等

新建 ~/.config/nvim/lua/plugins.lua 文件并放入如下内容。下面的模板只完成了 lazy.nvim 自身的安装,还没有指定其他第三方插件

local lazypath = vim.fn.stdpath("data") .. "/lazy/lazy.nvim"
if not (vim.uv or vim.loop).fs_stat(lazypath) then
  vim.fn.system({
    "git",
    "clone",
    "--filter=blob:none",
    "https://github.com/folke/lazy.nvim.git",
    "--branch=stable", -- latest stable release
    lazypath,
  })
end
vim.opt.rtp:prepend(lazypath)

require("lazy").setup({})

💡 在 lazy.nvim 指定第三方插件很简单,只需要在 require("lazy").setup({ ... })... 里面声明插件

然后在 init.lua 文件里面再次加上一行导入这个文件

...
require('plugins')

此时你重启 Nvim 会发现黑屏没显示,这是因为 lazy.nvim 在安装自己,静待片刻即可☕️。等待 Dashboard 出现之后,可以输入 :Lazy 试试,如果看到了弹出了 lazy.nvim 的窗口,那就安装成功了🎉

技巧

Tip:用 :q 退出 lazy.nvim 的窗口

主题配置

注意

macOS 自带的 Terminal.app 只支持 ANSI 256,在安装完 monokai 主题后,你可能会发现显示整个画面变成蓝色。使用颜色支持更丰富的 Terminal 可以解决这个问题(比如 iTerm2Kitty

我喜欢的主题是 monokai.nvim,在 plugins.lua 进行修改

...
require("lazy").setup({
    "tanvirtin/monokai.nvim",
})

保存更改并重启就可以看到 lazy.nvim 在帮我们安装插件了,新建并编辑 ~/.config/nvim/lua/colorscheme.lua 文件

-- define your colorscheme here
local colorscheme = 'monokai_pro'

local is_ok, _ = pcall(vim.cmd, "colorscheme " .. colorscheme)
if not is_ok then
    vim.notify('colorscheme ' .. colorscheme .. ' not found!')
    return
end

这里用到的 pcall 是 Lua 里面的 protected call,它会返回一个 bool 变量表示是否执行成功(跟 Go 语言的 err 功能类似)。这里采用 pcall 而不是直接在 init.lua 文件里面加上 vim.cmd('colorscheme monokai_pro') 是为了避免主题没有安装的情况下打开 Nvim 看到一大堆报错信息2

最后在 init.lua 文件里面导入就行

...
require('colorscheme')

自动补全指南

自己手动配置自动补全功能的话会比较繁琐,所以我们最好是借助于一些插件来帮忙,下面我讲讲我摸索出来的最简单配置方法

首先,第一个要用到的插件是 nvim-cmp,它可以管理各种补全候选项来源,然后展示在补全菜单里面,还支持我们对外观等进行定制化。

我们先新建 ~/.config/nvim/lua/config/nvim-cmp.lua 文件配置 nvim-cmp

local has_words_before = function()
    unpack = unpack or table.unpack
    local line, col = unpack(vim.api.nvim_win_get_cursor(0))
    return col ~= 0 and vim.api.nvim_buf_get_lines(0, line - 1, line, true)[1]:sub(col, col):match("%s") == nil
end

local luasnip = require("luasnip")
local cmp = require("cmp")

cmp.setup({
    snippet = {
        -- REQUIRED - you must specify a snippet engine
        expand = function(args)
            require('luasnip').lsp_expand(args.body) -- For `luasnip` users.
        end,
    },
    mapping = cmp.mapping.preset.insert({
        -- Use <C-b/f> to scroll the docs
        ['<C-b>'] = cmp.mapping.scroll_docs( -4),
        ['<C-f>'] = cmp.mapping.scroll_docs(4),
        -- Use <C-k/j> to switch in items
        ['<C-k>'] = cmp.mapping.select_prev_item(),
        ['<C-j>'] = cmp.mapping.select_next_item(),
        -- Use <CR>(Enter) to confirm selection
        -- Accept currently selected item. Set `select` to `false` to only confirm explicitly selected items.
        ['<CR>'] = cmp.mapping.confirm({ select = true }),

        -- A super tab
        -- sourc: https://github.com/hrsh7th/nvim-cmp/wiki/Example-mappings#luasnip
        ["<Tab>"] = cmp.mapping(function(fallback)
            -- Hint: if the completion menu is visible select next one
            if cmp.visible() then
                cmp.select_next_item()
            elseif has_words_before() then
                cmp.complete()
            else
                fallback()
            end
        end, { "i", "s" }), -- i - insert mode; s - select mode
        ["<S-Tab>"] = cmp.mapping(function(fallback)
            if cmp.visible() then
                cmp.select_prev_item()
            elseif luasnip.jumpable( -1) then
                luasnip.jump( -1)
            else
                fallback()
            end
        end, { "i", "s" }),
    }),

  -- Let's configure the item's appearance
  -- source: https://github.com/hrsh7th/nvim-cmp/wiki/Menu-Appearance
  formatting = {
      -- Set order from left to right
      -- kind: single letter indicating the type of completion
      -- abbr: abbreviation of "word"; when not empty it is used in the menu instead of "word"
      -- menu: extra text for the popup menu, displayed after "word" or "abbr"
      fields = { 'abbr', 'menu' },

      -- customize the appearance of the completion menu
      format = function(entry, vim_item)
          vim_item.menu = ({
              nvim_lsp = '[Lsp]',
              luasnip = '[Luasnip]',
              buffer = '[File]',
              path = '[Path]',
          })[entry.source.name]
          return vim_item
      end,
  },

  -- Set source precedence
  sources = cmp.config.sources({
    { name = 'nvim_lsp' },    -- For nvim-lsp
    { name = 'luasnip' },     -- For luasnip user
    { name = 'buffer' },      -- For buffer word completion
    { name = 'path' },        -- For path completion
  })
})

然后我们修改 plugins.lua 文件添加相关的插件

...
require("lazy").setup({
	-- Vscode-like pictograms
	{
		"onsails/lspkind.nvim",
		event = { "VimEnter" },
	},
	-- Auto-completion engine
	{
		"hrsh7th/nvim-cmp",
		dependencies = {
			"lspkind.nvim",
			"hrsh7th/cmp-nvim-lsp", -- lsp auto-completion
			"hrsh7th/cmp-buffer", -- buffer auto-completion
			"hrsh7th/cmp-path", -- path auto-completion
			"hrsh7th/cmp-cmdline", -- cmdline auto-completion
		},
		config = function()
			require("config.nvim-cmp")
		end,
	},
	-- Code snippet engine
	{
		"L3MON4D3/LuaSnip",
		version = "v2.*",
	},
    ...
})

解释如下

  • cmp.setup 函数的参数是一个 Lua 的 Table,用于设置各个选项(下面会解释)。后面你会发现很多第三方插件都用 setup 传入一个 Lua table 的方式进行配置,这个是 Nvim 的 Lua 插件的惯例
  • LuaSnip 是 code snippet 引擎,因为nvim-cmp 要求我们必须指定至少一个 code snippet 引擎来源。你暂时用不到当它不存在也没有关系
  • lazy.nvim 支持用 config = function() ... end 指定对应插件被加载之后要运行的代码,这里我们是让 Lua 导入前面写好的 nvim-cmp.lua 配置文件
  • 不要被上面的这么多插件吓到,nvim-cmp 为主,其他 cmp-... 的插件是用于在候选项来源和 nvim-cmp 之间交互,lspkind.nvim 是美化用的
nvim-cmp 里面的按键映射

按键映射用的是 mapping = ... ,每个按键绑定的格式是 ['<key-binding>'] = cmp.mapping.xxx,,不同的 cmp.mapping.xxx 的含义可以用 :h 查看。如果你想要用其他的按键,只要修改 [...] 里面的按键即可

按照我的个人习惯,设置了

  1. <C-k/j> 或者 <Tab>/<Shift-Tab> 在各种候选项里面移动
  2. <C-b/f> 在候选项的文档里面移动
  3. <CR> 也就是回车键确定补全
nvim-cmp 里面的补全菜单

补全菜单的定制化用的是 formatting = ...

  • fields 字段规定了每个候选项要显示什么东西
  • format = function(...) 设置了不同的候选项的来源显示,在 sources = ... 里面声明来源

🎙️ 到这为止,重新启动 Nvim 后,等待插件安装完成后应该就能够用初步的自动补全功能了~

LSP

要把 Nvim 变成 IDE 就势必要借助于 LSP3,自己安装和配置 LSP 是比较繁琐的。不同的 LSP 安装方法不同,也不方便后续管理。mason.nvim 和配套的 mason-lspconfig.nvim 这两个插件很好解决了这个问题 🤗

首先修改 plugins.lua 文件,增加对应的插件

...
require("lazy").setup({
	-- LSP manager
	"williamboman/mason.nvim",
	"williamboman/mason-lspconfig.nvim",
	"neovim/nvim-lspconfig",
    ...
})

新建一个 ~/.config/nvim/lua/lsp.lua 文件并编辑,首先配置 masonmason-lspconfig

require('mason').setup({
    ui = {
        icons = {
            package_installed = "✓",
            package_pending = "➜",
            package_uninstalled = "✗"
        }
    }
})

require('mason-lspconfig').setup({
    -- A list of servers to automatically install if they're not already installed
    ensure_installed = { 'pylsp', 'lua_ls', 'rust_analyzer' },
})

💡 我们想要用什么语言的 LSP 就在 ensure_installed 里面加上,完整的列表可以看 server_configurations。我个人常用的就 python/rust 这两个编程语言,而因为我们都用 Lua 语言来配置 Nvim,所以也加上了 lua_ls

配置好 mason-lspconfig 之后,接下来就可以配置 nvim-lspconfig 了。因为配置的代码比较长,下面只展示了 pylsp 的配置,其他语言的配置大同小异。如果有疑惑,可以查看该文件的最新版本

💡 每个 LSP 都存在自己可以配置的选项,你可以自己去对应 LSP 的 GitHub 仓库查阅更多信息。如果要用默认配置的话,基本上每一个新的语言都只需要设置 on_attach = on_attach

编辑 ~/.config/nvim/lua/lsp.lua 文件新增如下内容

...
-- Set different settings for different languages' LSP
-- LSP list: https://github.com/neovim/nvim-lspconfig/blob/master/doc/server_configurations.md
-- How to use setup({}): https://github.com/neovim/nvim-lspconfig/wiki/Understanding-setup-%7B%7D
--     - the settings table is sent to the LSP
--     - on_attach: a lua callback function to run after LSP atteches to a given buffer
local lspconfig = require('lspconfig')

-- Customized on_attach function
-- See `:help vim.diagnostic.*` for documentation on any of the below functions
local opts = { noremap = true, silent = true }
vim.keymap.set('n', '<space>e', vim.diagnostic.open_float, opts)
vim.keymap.set('n', '[d', vim.diagnostic.goto_prev, opts)
vim.keymap.set('n', ']d', vim.diagnostic.goto_next, opts)
vim.keymap.set('n', '<space>q', vim.diagnostic.setloclist, opts)

-- Use an on_attach function to only map the following keys
-- after the language server attaches to the current buffer
local on_attach = function(client, bufnr)
    -- Enable completion triggered by <c-x><c-o>
    vim.api.nvim_buf_set_option(bufnr, 'omnifunc', 'v:lua.vim.lsp.omnifunc')

    -- See `:help vim.lsp.*` for documentation on any of the below functions
    local bufopts = { noremap = true, silent = true, buffer = bufnr }
    vim.keymap.set('n', 'gD', vim.lsp.buf.declaration, bufopts)
    vim.keymap.set('n', 'gd', vim.lsp.buf.definition, bufopts)
    vim.keymap.set('n', 'K', vim.lsp.buf.hover, bufopts)
    vim.keymap.set('n', 'gi', vim.lsp.buf.implementation, bufopts)
    vim.keymap.set('n', '<C-k>', vim.lsp.buf.signature_help, bufopts)
    vim.keymap.set('n', '<space>wa', vim.lsp.buf.add_workspace_folder, bufopts)
    vim.keymap.set('n', '<space>wr', vim.lsp.buf.remove_workspace_folder, bufopts)
    vim.keymap.set('n', '<space>wl', function()
        print(vim.inspect(vim.lsp.buf.list_workspace_folders()))
    end, bufopts)
    vim.keymap.set('n', '<space>D', vim.lsp.buf.type_definition, bufopts)
    vim.keymap.set('n', '<space>rn', vim.lsp.buf.rename, bufopts)
    vim.keymap.set('n', '<space>ca', vim.lsp.buf.code_action, bufopts)
    vim.keymap.set('n', 'gr', vim.lsp.buf.references, bufopts)
    vim.keymap.set("n", "<space>f", function()
        vim.lsp.buf.format({ async = true })
    end, bufopts)
end

-- Configure each language
-- How to add LSP for a specific language?
-- 1. use `:Mason` to install corresponding LSP
-- 2. add configuration below
lspconfig.pylsp.setup({
	on_attach = on_attach,
})

上面的按键绑定的意思是很直观的,这里就不多解释啦

最后在 init.lua 文件里面加上

...
require('lsp')

重启 Nvim 之后,你应该可以在下面的状态栏看到 Mason 正在下载并安装前面我们指定的 LSP(注意此时不能关闭 Nvim),可以输入 :Mason 查看安装进度。在你等待安装的过程中,可以输入 g? 查看更多帮助信息了解如何使用 mason 插件

大功告成🎉🎉🎉

总结

这样配置下来,我们成功把 Nvim 变成了一个轻量级的 IDE,它支持代码高亮、代码补全、语法检查等功能,而且是完全开源免费的,虽然还有些简陋,但已经是可以用的了 🤗

我发现自从学了 Vim 之后,我总在其他各种代码编辑器、IDE 看是不是支持 Vim。大多数情况下,它们对 Vim 的支持都不是很让人满意,还容易有快捷键冲突等问题。因此我选择将 Nvim 变成 IDE,并将配置文件托管在我的 Martinlwx/dotfiles 上。这样在新的机器上只要安装好 Nvim 并克隆配置,静待片刻之后就可以在不同的机器上获得一样的编程体验

打磨定制化工具是需要付出一定精力的。为了理解每个选项都在干啥,我不得不查找各种资料。但我仍相信这是值得的理解你的工具利于你做扩展和定制化。本文已经尽可能采用了比较简单的配置,还有很多美化、私人定制化的内容可以配置,更别提其他很多优秀的第三方插件都还没有提及,这些就留给读者自己去探索发现了

参考

  1. Installing-Neovim ↩︎
  2. Adding a colorscheme/theme ↩︎
  3. Language Server Protocol - Wiki ↩︎

OCaml 的 Neovim 配置方案

MartinLwx

2024-01-23

更新 2024-04-04

前言

在上一篇博客中,我详细介绍了如何从零开始配置 Neovim,让它更接近 IDE。但我没有讲到的是:

  1. 有的 LSP 本身不支持代码格式化,要如何配置 Neovim 让它可以用第三方的代码格式化工具?
  2. 新增一门编程语言支持的时候,要怎么修改配置文件?

最近在学习函数式编程需要在我的 Mac 上配置 OCaml,正好以它为例,回答以上两个问题

安装 OCaml

信息

其他系统的安装方法可以看这里

在 Mac 上安装 OCaml 用 Homebrew 比较方便

$ brew install opam

按照 Homebrew 的输出,我们还需要进行额外的配置,需要修改 ~/.zshrc(如果你用的是 Zsh)或者是 ~/.bashrc(如果你用的是 Bash)

$ opam init
# 按照提示按下 y 确认即可

接下来让我们上一步的配置生效

$ source ~/.zshrc
# $ source ~/.bashrc # if you use bash

最后,我们可以通过如下的命令检查是否安装成功

$ opam switch

输出应该跟下面的差不多

#   switch   compiler                   description
->  5.0.0    ocaml-base-compiler.5.0.0  5.0.0
    default  ocaml.4.14.0               default

技巧

接下来需要安装一些包,因为网络原因访问可能会比较慢。加速可以通过换源实现,这里用的是上海交通大学的源,配置命令为

$ opam repo \
    set-url default \
    https://mirrors.sjtug.sjtu.edu.cn/git/opam-repository.git \
    --all --set-default

等待该命令执行完就完成了换源

类似 Python 有 REPL,如 IPython。在 OCaml 里也有类似的工具,叫做 utop

$ opam install utop

接下来尝试使用 utop 看是否安装成功

$ utop

如果一切正常,你应该可以看到如下的输出

配置 OCaml LSP

信息

如果你跟着我上一篇的教程进行配置,那么你的目录结构应该长下面这样

nvim
├── init.lua
└── lua
    ├── colorscheme.lua
    ├── config
    │   └── nvim-cmp.lua
    ├── keymaps.lua
    ├── lsp.lua
    ├── options.lua
    └── plugins.lua

之前的文章里面我们配置 LSP 的时候借助了 mason.nvim。要增加新的编程语言的 LSP 同样可以用这个工具,步骤如下

  1. 打开 Neovim,输入 :Mason,在 (2) LSP 里面找到 ocaml-lsp,将光标放在该行然后按下 i 就可以安装了,稍候片刻☕️
  2. 打开 LSP 配置文件(我放在了 lsp.lua 文件里面),添加如下的内容
...
lspconfig.ocamllsp.setup({
	on_attach = on_attach,
})

警告

你可能有自己的配置文件而没有跟着我的上一篇 文章操作,所以在你的配置文件里面加上这一段代码并不保证能够 work。你可以选择浏览完整文件 来理解这段代码的作用,和尝试迁移配置到你的配置文件中

修改完 lsp.lua 文件之后,重启 Neovim 之后就生效了

警告

rust-analyzer 类似,ocaml-lsp 的 LSP 也只支持在项目里启用,无法在单文件下生效

接下来,可以用 dune 新建项目,尝试写一下 OCaml 的代码,检查 LSP 是否正常工作

配置 OCaml Formatter

ocaml-lsp 本身并不支持 OCaml 代码的格式化,在完成如上的配置之后,如果你尝试格式化,会发现 Neovim 报错:

[LSP] Format request failed, no matching language servers.

因此,接下来要讲解的就是如何配置 OCaml 的代码格式化功能。

之前你可能已经注意到,在 Neovim 里面输入 :Mason 的时候可以看到,它不仅支持安装 LSP,还可以安装 Linter、Formatter 等。其中 Formatter 就是用于代码的格式化。OCaml 的 Formmater 是 ocamlformat,首先我们在 Mason 的窗口 (5) Formatter 里找到它,将光标停留在该选项,然后按下 i 安装。等待片刻之后就可以看到安装成功了☕️

ocamlformat 本身只是一个 CLI 工具,没法方便地集成到 Neovim 里面。好在还有 none-ls 插件,它可以将 Linter、Formatter 或者命令行等工具暴露成 LSP 给 Neovim

信息

none-ls.nvim 的前身是 null-ls.nvim,但是 null-ls 的开发者已经不再继续更新了,因此社区的热心开发者维护了另外一个功能一样的版本叫做 none-ls

为了进一步减轻配置的负担,我们可以利用 mason-null-ls.nvim 插件,从名字可以看出来,它是 mason.nvim 的开发者开发的。打开 plugins.lua 新增如下 Lua 代码,保存并重启后 lazy.nvim 就会自动安装新插件,稍候片刻☕️

...
require("lazy").setup({
	-- Add hooks to LSP to support Linter && Formatter
	{
		"jay-babu/mason-null-ls.nvim",
		event = { "BufReadPre", "BufNewFile" },
		dependencies = {
			"williamboman/mason.nvim",
			"nvimtools/none-ls.nvim",
		},
		config = function()
			-- Note:
			--     the default search path for `require` is ~/.config/nvim/lua
			--     use a `.` as a path seperator
			--     the suffix `.lua` is not needed
			require("config.mason-null-ls")
		end,
	},
    ...
})

然后在 config 文件夹里新建 mason-null-ls.lua 文件,内容如下

技巧

当你开启 automatic_setup = true 的时候,mason-null-ls 会自动帮你进行配置安装了的 Formatter。如果想要修改默认配置,就需要在 handlers 里面进行修改

FAQs

  1. 如何找到对应 Formatter 的默认配置?看 这里
  2. 如何配置 handlers?看 这里
  3. 如何知道对应的 Formatter 其他可选配置选项?看对应 LSP 的官方 GitHub 仓库。比如 ocamlformat 就在文档说了,在命令行输入 ocamlformat --help 就可以看到各种配置项

最后,我们还需要修改 lsp.lua 文件,原本跟代码格式化有关的行长这个样子

vim.keymap.set("n", "<space>f", function()
    vim.lsp.buf.format({ async = true })
end, bufopts)

现在因为我们使用 none-ls,相当于所有的格式化都交给它来做,因此,需要新增 filter 参数

...
vim.keymap.set("n", "<space>f", function()
    vim.lsp.buf.format({
        async = true,
        -- Only request null-ls for formatting
        filter = function(client)
            return client.name == "null-ls"
        end,
    })
end, bufopts)
...

完成文件编辑之后,重启 Neovim 就生效了

总结

现在对前文的内容进行总结,如果你跟我一样也使用 mason.nvim, mason-lspconfig, none-ls.nvimmason-null-ls.nvim 这些插件。那么在添加一门新的编程语言的时候,你可以这样配置:

  1. 首先在你的机器上配置好该编程语言的开发环境,查询官方文档即可
  2. 打开 Neovim,输入 :Mason 安装对应的 LSP、Formatter
  3. 打开 lsp.lua 文件配置 LSP
  4. 打开 mason-null-ls.lua 文件修改 handlers 参数

对于本文没有说清楚的地方,你可以查看我的 dotfiles 配置 来了解更多的信息~~


via:

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值