Skip to content

10|右侧终端、测试循环与手动格式化

本章会把右侧终端纳入编辑流程,并完成一次完整的“测试失败 → 修复 → 测试通过”循环。

本章新按键

按键效果
Ctrl-\打开或隐藏 Snacks 终端
快速按两次 Esc从终端输入状态进入终端普通模式
Ctrl-w,再按 h/l从终端移到左侧代码 / 回到右侧终端
终端普通模式中的 i回到终端输入状态
Space c f用 Conform 手动格式化当前 Buffer

开始前的项目检查点

至少应有 src/pocket_tasks/ 下的两个源码文件,以及 tests/test_service.py

python
from dataclasses import dataclass

@dataclass(frozen=True)
class Task:
    title: str
    done: bool = False
python
from 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]
python
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 创建,再把对应代码输入进去。保存用 F1writeEnterEnter

第一次打开终端

  1. 确认光标在普通代码 Buffer 中。
打开终端前的 cli.py
打开终端前的 cli.py
  1. Ctrl-\
  2. 屏幕右侧出现一个竖向终端。
  3. 光标进入终端输入状态,可以直接敲 shell 命令。
右侧终端打开
右侧终端打开

这个终端会继承 Neovim 当前的工作目录。如果你在 pocket-tasks 目录中运行 nvim .,终端打开后也会位于该目录。

输入:

console
PYTHONPATH=src python -m unittest -v
输入测试命令
输入测试命令

Enter。三条测试应当通过。

三条测试通过
三条测试通过

隐藏和再次显示

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

隐藏终端后的代码窗口
隐藏终端后的代码窗口

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

再次显示并保留测试输出
再次显示并保留测试输出

日常开发时可以按下面的顺序反复使用:打开终端,跑测试,隐藏终端,修代码,再打开终端并用上箭头重跑。

先运行一次失败测试

我们给服务层增加 pending_count

第一步:先写测试

F1,输入 edit

用 F1 选择 edit 命令
用 F1 选择 edit 命令

选中 edit 后按 Enter,在底部命令后补上 tests/test_service.py,再按 Enter 打开文件。把导入改成:

python
from pocket_tasks.service import (
    add_task,
    complete_task,
    pending_count,  
    visible_titles,
)

在类中加入:

python
    def test_pending_count(self) -> None:
        tasks = [
            Task(title="first"),
            Task(title="second", done=True),
            Task(title="third"),
        ]
        self.assertEqual(pending_count(tasks), 2)  
加入 pending_count 测试
加入 pending_count 测试

保存当前文件。

测试文件已保存
测试文件已保存

第二步:运行失败测试

  1. Ctrl-\ 显示终端。
  2. Up 找回上一条测试命令。
召回测试命令但尚未执行
召回测试命令但尚未执行
  1. Enter

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

缺少 pending_count 的 ImportError
缺少 pending_count 的 ImportError

第三步:回代码实现

Ctrl-\ 隐藏终端,打开 src/pocket_tasks/service.py,在文件末尾加入:

python
def pending_count(tasks: list[Task]) -> int:  
    return sum(not task.done for task in tasks)  
实现 pending_count
实现 pending_count

然后保存文件。

第四步:重新运行测试直至通过

再次显示终端,先保留刚才的失败输出。

重新显示之前的失败输出
重新显示之前的失败输出

UpEnter 重跑。四条测试应全部通过。

四条测试全部通过
四条测试全部通过

我们不用单独的测试面板或测试按钮,而是直接在右侧终端中运行命令。这样不受项目类型限制,python、cargo、make、pnpm 等命令都可以照常使用。

终端暂时留在屏幕上

有时你想一边看测试输出,一边改代码,不想隐藏终端。

  1. 在终端输入状态快速按两次 Esc,两次间隔控制在约 200 毫秒内。
  2. 光标样式变化,终端进入普通模式。
终端普通模式
终端普通模式
  1. Ctrl-w,再按 h,焦点移到左侧代码。
焦点移到左侧代码
焦点移到左侧代码
  1. Ctrl-w,再按 l,焦点回到终端。
  2. 本配置回到终端时通常会自动进入输入状态;如果仍处于普通模式,再按 i
焦点回到终端
焦点回到终端

终端普通模式中还可以按 q 隐藏终端。刚开始用 Ctrl-\ 已经足够稳定。

从 Explorer 的当前目录开终端

Explorer 里还可以从指定目录打开终端:

  1. Space e 打开 Explorer。
打开 Explorer
打开 Explorer
  1. 把光标放到 tests 目录。
选中 tests 目录
选中 tests 目录
  1. Ctrl-t
从 tests 目录打开终端
从 tests 目录打开终端

Snacks 会以所选目录作为工作目录打开终端。临时需要在某个子目录运行命令时,这种方式很方便。本教程后面仍以全局的 Ctrl-\ 为主。

格式化练习:格式化 Nix 代码

当前配置明确注册的外部格式器是 Nix 的 nixfmt。Python 的 BasedPyright 不提供格式化,所以在 Python 文件按格式化键可能没有变化。

创建一份故意拥挤的文件

用 Explorer 回到项目根目录并把光标放在根目录上。

Explorer 选中项目根目录
Explorer 选中项目根目录

a,输入 scratch.nix

输入 scratch.nix 文件名
输入 scratch.nix 文件名

Enter 创建文件。

scratch.nix 创建完成
scratch.nix 创建完成

打开文件,输入:

nix
{ pkgs }:{ packages=[pkgs.python3 pkgs.git]; }
输入故意拥挤的 Nix 代码
输入故意拥挤的 Nix 代码

先保存一次。保存不会触发格式化,文件仍保持一行:

未格式化文件已保存
未格式化文件已保存

然后执行:

  1. Esc
  2. Space c,停一下查看 Which-key 中的 Format
Space c 后显示 Format
Space c 后显示 Format
  1. f,等一小会儿。格式化是异步执行的。

文件应展开成清晰的多行结构。课程前面创建的 .editorconfig 会让 Nix 使用两空格缩进。

nixfmt 格式化后的多行结构
nixfmt 格式化后的多行结构

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

格式化结果已保存
格式化结果已保存

格式化键的准确语义

  • 它只处理当前 Buffer。
  • 它不会自动保存。
  • 配置关闭了保存时自动格式化。
  • 有显式 Conform 格式器时优先使用它。
  • 没有显式格式器时,Conform 会尝试支持格式化的 LSP。
  • Python 的 BasedPyright 没有格式化能力,建议由项目以后自行加入 Ruff 或 Black。
格式化按键毫无反应
  1. F1
  2. 输入 ConformInfo
选择 ConformInfo
选择 ConformInfo
  1. 连按两次回车,查看当前文件类型可用的格式器。
ConformInfo 显示 nixfmt 已就绪
ConformInfo 显示 nixfmt 已就绪

日常测试循环模板

以后可以按这个顺序完成一次测试循环:

  1. 写一个失败测试。
  2. 保存。
  3. Ctrl-\ 打开终端。
  4. UpEnter 重跑。
  5. Ctrl-\ 隐藏终端。
  6. 修最少的代码。
  7. 再跑测试。
  8. 测试通过后按 Space c f
  9. 等格式化完成,再保存。

隋唐小测

  • 打开和隐藏右侧终端:Ctrl-\
  • 保留终端可见并跳到左侧代码:双 Esc,然后 Ctrl-w h
  • 回终端并继续输入:Ctrl-w l;若没有自动进入输入状态,再按 i
  • 手动格式化当前 Buffer:Space c f
  • 格式化后还要做什么:等待完成并保存

下一章进入 11:Git 变更块。测试通过以后,接着检查并整理本次修改。

本文档采用 知识共享 署名-相同方式共享 4.0 协议 进行许可。