How can I apply black code formatting on save?
Asked Answered
M

3

11

I would like to apply black whenever I save a Python file in Sublime Text 3. How can I do that?

(Bonus points if there is a quick way to disable it)

Micmac answered 31/7, 2019 at 10:51 Comment(0)
D
13

The answer above is really nice. In case you do not want to write your own package or plugin and you do not like the Formatter package, there is also the sublack package, which I think supports quick enabling/disabling of running black on save.

You install sublack the usual way via package control (Ctrl-Shift-P (Mac: Cmd-Shift-P) Package Controll: Install package). Afterwards you can format the current file either manually:

Run Black on current file: Press Ctrl-Alt-B to format the entire file. You can also Ctrl-Shift-P (Mac: Cmd-Shift-P) and select Sublack: Format file.

or you can:

Toggle Black on save for current view : Press Ctrl-Shift-P (Mac: Cmd-Shift-P) and select Sublack: Toggle black on save for current view.

Alternatively, you can also enable to run black on save permanently, by creating a user setting (Preferences -> Package Settings -> sublack -> settings) similar to the following:

{
    "black_on_save": true,
    "black_line_length": 80,
}
Drillmaster answered 30/8, 2019 at 8:55 Comment(2)
The sublack repo has been archived on GitHub.Footing
python-black is a good alternative that is better maintained.Written
R
3
  1. python 3.6
  2. pip install black
  3. cmd + shift + p then Click Package Control: Install Package
  4. sublack
  5. On terminal, which python
  6. Preferences/Package Settings/sublack/Settings On right panel

    {
        "black_on_save": true,
        "black_command": WHICH_PYTHON_RESULT
    }
    
Reparation answered 27/12, 2019 at 1:12 Comment(0)
M
2

To do something like this you would need a package or plugin that is capable of triggering an external command whenever an on_post_save event is triggered in Sublime for a Python file.

The Formatter package is an example of a package that does this sort of thing and it's README mentions that it supports Black as well. I don't use that package myself so I can't recommend it one way or the other. There may be other packages that provide a similar functionality as well, although this is the only one I spotted that mentions that it supports Black explicitly.

In theory any formatting package or package to execute commands on save events that lets you specify the command to execute could likely be configured to work as well.

For the sake of completeness, a plugin to do something like this can be created fairly quickly by creating a ViewEventListener that only triggers inside of Python files and uses the internal exec command to execute the black command.

An example of such a plugin would be the following (this video provides instructions on how to set up a plugin in Sublime if you're unsure how to do that) which for meta points has been formatted on save by itself:

import sublime
import sublime_plugin

import os


class FormatWithBlackOnSave(sublime_plugin.ViewEventListener):
    """
    Listen for file saves and run the black code formatter on Python files
    as they are saved, unless they have a setting indicating that the autoformat
    should be disabled.
    """

    @classmethod
    def is_applicable(self, settings):
        return "/Python/" in settings.get("syntax") and not settings.get(
            "black_disabled", False
        )

    def on_post_save(self):
        path, file = os.path.split(self.view.file_name())

        settings = sublime.load_settings("Preferences.sublime-settings")

        show_panel_on_build = (settings.get("show_panel_on_build", True),)
        override_panel = settings.get("black_override_panel", False)
        env = settings.get("black_env", {})
        args = settings.get("black_arguments", [])

        if override_panel:
            settings.set("show_panel_on_build", not show_panel_on_build)

        window = self.view.window() or sublime.active_window()
        window.run_command(
            "exec",
            {
                "shell_cmd": 'black {args} "{file}"'.format(
                    file=file, args=" ".join(args)
                ),
                "working_dir": path,
                "env": env,
            },
        )

        if override_panel:
            settings.set("show_panel_on_build", show_panel_on_build)

Once that's in place in your User package, you should also add some custom settings to your Preferences.sublime-settings file to control it:

    // When this is True, the plugin will not execute on save.
    "black_disabled": false,

    // Override the value of the `show_panel_on_build` setting that controls
    // whether the output panel appears when the command is executed. When
    // true the value of that setting is temporarily inverted.
    "black_override_panel": false,

    // The arguments (other than the current file) to pass to black
    "black_arguments": [],

    // Optional environment variables to use while running the tool
    // (for example to set the path); works as in a `sublime-build` file.
    "black_env": {
    }   

This requires you to install the black command yourself (e.g. pip install black) and will execute it with the given arguments for the current file, so long as the file is a Python file and the black_disabled setting is set to false as above.

The plugin uses the internal exec command, which uses the preference show_panel_on_build to determine if a panel that shows you the output of the command should appear or not. The default value for that setting is true, which means that every time you save a Python file the panel would open with a message like:

reformatted black.py
All done!
1 file reformatted.
[Finished in 0.2s]

The black_override_panel setting makes the plugin invert the value of the show_panel_on_build while it's executing the command; either not showing it when it normally would or vice versa.

Altering the setting in your preferences will globally disable the event listener from triggering in all Python files. You can also create a file in your User package with a name like Black.sublime-commands with the following content:

[
    { 
        "caption": "Black: Toggle Format-on-save for this view",
        "command": "toggle_setting",
        "args": {
            "setting": "black_disabled"
        }
    }
]

That would add a command to the command palette that inverts the state of the setting in the current file; that would allow you to disable the plugin only for certain files or enable it only for certain files.

Marielamariele answered 1/8, 2019 at 6:22 Comment(0)

© 2022 - 2024 — McMap. All rights reserved.