10|右侧终端、测试循环与手动格式化
本章会把右侧终端纳入编辑流程,并完成一次完整的“测试失败 → 修复 → 测试通过”循环。
本章新按键
| 按键 | 效果 |
|---|---|
Ctrl-\ | 打开或隐藏 Snacks 终端 |
快速按两次 Esc | 从终端输入状态进入终端普通模式 |
Ctrl-w,再按 h/l | 从终端移到左侧代码 / 回到右侧终端 |
终端普通模式中的 i | 回到终端输入状态 |
Space c f | 用 Conform 手动格式化当前 Buffer |
开始前的项目检查点
至少应有 src/pocket_tasks/ 下的两个源码文件,以及 tests/test_service.py。
from dataclasses import dataclass
@dataclass(frozen=True)
class Task:
title: str
done: bool = Falsefrom pocket_tasks.model import Task
def add_task(tasks: list[Task], title: str) -> list[Task]:
return [*tasks, Task(title=title)]
def complete_task(tasks: list[Task], index: int) -> list[Task]:
updated = list(tasks)
task = updated[index]
updated[index] = Task(title=task.title, done=True)
return updated
def visible_titles(tasks: list[Task]) -> list[str]:
return [task.title for task in tasks if not task.done]import unittest
from pocket_tasks.model import Task
from pocket_tasks.service import add_task, complete_task, visible_titles
class ServiceTests(unittest.TestCase):
def test_add_task(self) -> None:
tasks = add_task([], "learn nvim")
self.assertEqual(tasks, [Task(title="learn nvim")])
def test_complete_task(self) -> None:
tasks = complete_task([Task(title="learn nvim")], 0)
self.assertTrue(tasks[0].done)
def test_visible_titles_hides_completed(self) -> None:
tasks = [
Task(title="write code"),
Task(title="take a walk", done=True),
]
self.assertEqual(visible_titles(tasks), ["write code"])
if __name__ == "__main__":
unittest.main()缺少某份文件时,用 Space e 打开 Explorer,按 a 创建,再把对应代码输入进去。保存用 F1 → write → Enter → Enter。
第一次打开终端
- 确认光标在普通代码 Buffer 中。

- 按
Ctrl-\。 - 屏幕右侧出现一个竖向终端。
- 光标进入终端输入状态,可以直接敲 shell 命令。

这个终端会继承 Neovim 当前的工作目录。如果你在 pocket-tasks 目录中运行 nvim .,终端打开后也会位于该目录。
输入:
PYTHONPATH=src python -m unittest -v
按 Enter。三条测试应当通过。

隐藏和再次显示
在终端输入状态直接按 Ctrl-\,右侧窗口隐藏,shell 进程继续活着。

再按一次同样的组合,它会带着刚才的输出回来。

日常开发时可以按下面的顺序反复使用:打开终端,跑测试,隐藏终端,修代码,再打开终端并用上箭头重跑。
先运行一次失败测试
我们给服务层增加 pending_count。
第一步:先写测试
按 F1,输入 edit。

选中 edit 后按 Enter,在底部命令后补上 tests/test_service.py,再按 Enter 打开文件。把导入改成:
from pocket_tasks.service import (
add_task,
complete_task,
pending_count,
visible_titles,
)在类中加入:
def test_pending_count(self) -> None:
tasks = [
Task(title="first"),
Task(title="second", done=True),
Task(title="third"),
]
self.assertEqual(pending_count(tasks), 2) 
保存当前文件。

第二步:运行失败测试
- 按
Ctrl-\显示终端。 - 按
Up找回上一条测试命令。

- 按
Enter。
这次应出现 ImportError,因为服务层还没有 pending_count。这个失败结果说明测试覆盖了尚未实现的新需求。

第三步:回代码实现
按 Ctrl-\ 隐藏终端,打开 src/pocket_tasks/service.py,在文件末尾加入:
def pending_count(tasks: list[Task]) -> int:
return sum(not task.done for task in tasks) 
然后保存文件。
第四步:重新运行测试直至通过
再次显示终端,先保留刚才的失败输出。

按 Up、Enter 重跑。四条测试应全部通过。

我们不用单独的测试面板或测试按钮,而是直接在右侧终端中运行命令。这样不受项目类型限制,python、cargo、make、pnpm 等命令都可以照常使用。
终端暂时留在屏幕上
有时你想一边看测试输出,一边改代码,不想隐藏终端。
- 在终端输入状态快速按两次
Esc,两次间隔控制在约 200 毫秒内。 - 光标样式变化,终端进入普通模式。

- 按
Ctrl-w,再按h,焦点移到左侧代码。

- 按
Ctrl-w,再按l,焦点回到终端。 - 本配置回到终端时通常会自动进入输入状态;如果仍处于普通模式,再按
i。

终端普通模式中还可以按 q 隐藏终端。刚开始用 Ctrl-\ 已经足够稳定。
从 Explorer 的当前目录开终端
Explorer 里还可以从指定目录打开终端:
- 按
Spacee打开 Explorer。

- 把光标放到
tests目录。

- 按
Ctrl-t。

Snacks 会以所选目录作为工作目录打开终端。临时需要在某个子目录运行命令时,这种方式很方便。本教程后面仍以全局的 Ctrl-\ 为主。
格式化练习:格式化 Nix 代码
当前配置明确注册的外部格式器是 Nix 的 nixfmt。Python 的 BasedPyright 不提供格式化,所以在 Python 文件按格式化键可能没有变化。
创建一份故意拥挤的文件
用 Explorer 回到项目根目录并把光标放在根目录上。

按 a,输入 scratch.nix。

按 Enter 创建文件。

打开文件,输入:
{ pkgs }:{ packages=[pkgs.python3 pkgs.git]; }
先保存一次。保存不会触发格式化,文件仍保持一行:

然后执行:
- 按
Esc。 - 按
Spacec,停一下查看 Which-key 中的Format。

- 按
f,等一小会儿。格式化是异步执行的。
文件应展开成清晰的多行结构。课程前面创建的 .editorconfig 会让 Nix 使用两空格缩进。

格式化结束后,Buffer 会处于已修改状态。再用 F1 → write → Enter → Enter 保存。

格式化键的准确语义
- 它只处理当前 Buffer。
- 它不会自动保存。
- 配置关闭了保存时自动格式化。
- 有显式 Conform 格式器时优先使用它。
- 没有显式格式器时,Conform 会尝试支持格式化的 LSP。
- Python 的 BasedPyright 没有格式化能力,建议由项目以后自行加入 Ruff 或 Black。
格式化按键毫无反应
- 按
F1。 - 输入
ConformInfo。

- 连按两次回车,查看当前文件类型可用的格式器。

日常测试循环模板
以后可以按这个顺序完成一次测试循环:
- 写一个失败测试。
- 保存。
Ctrl-\打开终端。Up、Enter重跑。Ctrl-\隐藏终端。- 修最少的代码。
- 再跑测试。
- 测试通过后按
Spacecf。 - 等格式化完成,再保存。
隋唐小测
- 打开和隐藏右侧终端:
Ctrl-\ - 保留终端可见并跳到左侧代码:双
Esc,然后Ctrl-wh - 回终端并继续输入:
Ctrl-wl;若没有自动进入输入状态,再按i - 手动格式化当前 Buffer:
Spacecf - 格式化后还要做什么:等待完成并保存
下一章进入 11:Git 变更块。测试通过以后,接着检查并整理本次修改。