PyCharm: 运行 `black -S` 区域

PyCharm: Run `black -S` on region

我们还没有准备好使用 black 自动格式化整个源代码。

但有时我想通过 PyCharm 在区域上执行 black -S

整个文件中有一个hint in the docs how to 运行 black (or black -S (what I like))。但是...

如何 运行 仅在 选定区域 变黑?

我对此进行了研究,因为它实际上看起来很有趣,我得出的结论是您可以使用:

black -S and_your_file_path

或:

black -c and_a_string

将传入的代码格式化为字符串。 我也会关注这个话题,因为它看起来很有趣。
我还将对此进行更多研究,如果我发现了什么,我会告诉你。

使用Python Black on a code region in the PyCharm IDE can be done by implementing it as an external tool。目前Black有两个主要选项来选择要格式化的代码

  1. 运行 黑色整个模块在 CLI 上指定为 [SRC]...
  2. 使用 -c, --code TEXT 选项在 CLI 上将代码区域作为字符串传递。

以下实现显示了如何使用第二个选项来执行此操作。原因是将 Black 应用于整个模块可能会改变行数,从而使通过选择开始和结束行号来 selecting 代码区域的工作更加复杂。

可以实现第一个选项,但需要在黑色格式化整个模块后将初始代码区域映射到最终代码区域。

让我们以下面的代码为例,它有许多明显的 PEP-8 违规行为(缺少空格和空行):

"""
long multi-line
comment
"""
def foo(token:int=None)->None:
  a=token+1

class bar:
  foo:int=None
  
def the_simple_test():
    """the_simple_test"""
    pass

步骤 1.

使用黑色 as an external tool in the IDE 可以通过转到 File > Tools > External Tools 并单击 AddEdit 个图标。

有趣的是将 PyCharm IDE 中的右 Macros - (see point 3 "Parameter with macros") 传递给调用 Black 并进行必要处理的自定义脚本。即你需要宏

  • FilePath - File Path
  • SelectionStartLine - Selected text start line number
  • SelectionEndLine - Select text end line number
  • PyInterpreterDirectory - The directory containing the Python interpreter selected for the project

But from time to time I would like to execute black -S on a region via PyCharm.

您希望作为参数传递的任何其他 Black CLI options 最好放在参数列表的末尾。

由于您可能在特定的 venv 上安装了 Black,该示例还使用了 PyInterpreterDirectory 宏。

截图说明了以上内容:

第 2 步

您需要实现一个脚本来调用 Black 并与 IDE 交互。以下是一个工作示例。需要注意的是:

  1. 四行是 OS/shell 所评论的(使它们适应您的环境应该是微不足道的)。
  2. 一些细节可以进一步调整,例如,实现做出简单的选择。
import os
import pathlib
import tempfile
import subprocess
import sys

def region_to_str(file_path: pathlib.Path, start_line: int, end_line: int) -> str:

    file = open(file_path)
    str_build = list()

    for line_number, line in enumerate(file, start=1):
        if line_number > end_line:
            break
        elif line_number < start_line:
            continue
        else:
            str_build.append(line)

    return "".join(str_build)

def black_to_clipboard(py_interpeter, black_cli_options, code_region_str):

    py_interpreter_path = pathlib.Path(py_interpeter) / "python.exe"  # OS specific, .exe for Windows.

    proc = subprocess.Popen([py_interpreter_path, "-m", "black", *black_cli_options,
                             "-c", code_region_str], stdout=subprocess.PIPE)

    try:
        outs, errs = proc.communicate(timeout=15)
    except TimeoutExpired:
        proc.kill()
        outs, errs = proc.communicate()

    # By default Black outputs binary, decodes to default Python module utf-8 encoding.
    result = outs.decode('utf-8').replace('\r','')  # OS specific, remove \r from \n\r Windows new-line.

    tmp_dir_name = tempfile.gettempdir()
    tmp_file = tempfile.gettempdir() + "\__run_black_tmp.txt"  # OS specific, escaped path separator.

    with open(tmp_file, mode='w+', encoding='utf-8', errors='strict') as out_file:
        out_file.write(result + '\n')

    command = 'clip < ' + str(tmp_file)  # OS specific, send result to clipboard for copy-paste.
    os.system(command)

def main(argv: list[str] = sys.argv[1:]) -> int:
    """External tool script to run black on a code region.

    Args:
        argv[0] (str): Path to module containing code region.
        argv[1] (str): Code region start line.
        argv[2] (str): Code region end line.
        argv[3] (str): Path to venv /Scripts directory.
        argv[4:] (str): Black CLI options.
    """
    # print(argv)
    lines_as_str = region_to_str(argv[0], int(argv[1]), int(argv[2]))
    black_to_clipboard(argv[3], argv[4:], lines_as_str)

if __name__ == "__main__":
    main(sys.argv[1:])

步骤 3.

困难的部分已经完成。让我们使用新功能。

通常 select 您希望在编辑器中作为代码区域的行。必须强调这一点,因为之前的 SelectionStartLineSelectionEndLine 宏需要 selection 才能工作。 (见下一张截图)。

第 4 步

运行 之前实现的外部工具。这可以通过右键单击编辑器并选择 External Tools > the_name_of_your_external_tool.

来完成

第 5 步

简单粘贴(截图显示运行外部工具并按Ctrl + v后的结果)。 步骤 2 中的实现将 Black 的输出复制到你的 OS 的剪贴板,这似乎是更好的解决方案,因为这样你就可以在编辑器中更改文件 Undo Ctrl + z 也可以。通过在编辑器外以编程方式覆盖文件来更改文件会不太无缝,可能需要在编辑器内刷新它。

第 6 步

您可以 record a macro of the previous steps and associate it with a keyboard shortcut 一次击键即可获得上述功能(类似于复制粘贴 Ctrl + c + Ctrl + v).

尾注。

  1. 如果您需要调试 步骤 2 中的功能,也可以使用与外部工具配置相同的宏来配置 Run Configuration

  2. 使用剪贴板时请务必注意,字符编码可以跨层更改。我决定使用 clip 并直接从临时文件中读入它,这是为了避免在命令行上将代码字符串传递给 Black,因为默认情况下 CMD Windows 编码不是 UTF-8。 (对于 Linux 用户,这应该更简单,但可能取决于您的系统设置。)

  3. 一个重要的注意事项是,您可以选择一个代码区域,而无需其缩进级别的更广泛上下文。这意味着,例如,如果您只在 class 中选择 2 个方法,它们将被传递给 Black 并使用模块级函数的缩进级别进行格式化。如果您小心 select 代码区域的适当范围,这应该不是问题。这也可以通过从 Step 1 传递额外的宏 SelectionStartColumn - Select text start column number 并在 Step 2[=144] 的每一行前添加该数量的空格来轻松解决=] 脚本。 (理想情况下,这种功能将由 Black 作为 CLI 选项来实现。)在任何情况下,如果需要,使用 Tab 将区域置于适当的缩进级别就足够了。

  4. 问题的主题是如何将黑色与代码区域的 PyCharm IDE 集成,因此演示第二个选项应该足以解决问题因为在大多数情况下,第一个选项只会增加特定于实现的复杂性。 (答案已经足够长了。实施第一个选项的具体细节将成为一个很好的 Feature/Pull Black 项目请求。)