zed icon indicating copy to clipboard operation
zed copied to clipboard

External formatter for PHP replaces the original file

Open mahmoudsaeed opened this issue 1 year ago • 13 comments

Check for existing issues

  • [X] Completed

Describe the bug / provide steps to reproduce it

I am trying to configure PHP CS Fixer as an external formatter for PHP using the following settings:

{
  "language_overrides": {
    "PHP": {
      "formatter": {
        "external": {
          "command": "php-cs-fixer",
          "arguments": [
            "fix",
            "--using-cache=no",
            "{buffer_path}"
          ]
        }
      }
    }
  }
}

On save, the formatter seems to be working just fine and it fixes the issues, but the content of the original file gets overwritten by the verbose output of the formatter command:

image

Environment

Zed: v0.103.1 (stable) OS: macOS 13.5.0 Memory: 8 GiB Architecture: aarch64

If applicable, add mockups / screenshots to help explain present your vision of the feature

No response

If applicable, attach your ~/Library/Logs/Zed/Zed.log file to this issue.

If you only need the most recent lines, you can run the zed: open log command palette action to see the last 1000.

2023-09-14T06:53:59 [ERROR] unexpected item event after pane was dropped

mahmoudsaeed avatar Sep 14 '23 06:09 mahmoudsaeed

@maxbrunsfeld, I realize these are different languagees and tools, but this feels similar to:

  • https://github.com/zed-industries/zed/issues/4514

JosephTLyons avatar Sep 18 '23 04:09 JosephTLyons

Seems a PR for it in the main repo of php-cs-fixer https://github.com/PHP-CS-Fixer/PHP-CS-Fixer/pull/4320 Can't wait for that, so I made a custom script to make it work for php-cs-fixer https://gist.github.com/vuon9/be16429f751e12f72e220c18777d9bc7

And configuration will be like this

{
  "language_overrides": {
  "PHP": {
      "formatter": {
      "external": {
          "command": "php-cs-fixer-std",
          "arguments": [
          "fix",
          "--config=www/.php-cs-fixer.php",
          "--using-cache=no",
          "{buffer_path}"
          ]
      }
      },
      "format_on_save": "on"
    }
  }
}

vuon9 avatar Jan 25 '24 07:01 vuon9

I was just battling with this, thanks for the script @vuon9! One thing that I found is that I use Herd to manage my PHP versions so the shebang didn't work for me. I had to update it to #!/usr/bin/env php and then your script worked great!

Looking forward to native support for formatters updating the file and not relying on the output 😊

itsmewes avatar Jan 30 '24 08:01 itsmewes

Even though we could technically make it work from the standpoint of the linter, I still favor the option of Zed disregarding the stdout output of the linter, but pls keep the new change of file after lint tool completes its process on the file. Linter tools can output anything they want through stdout, even if it's improper way.

vuon9 avatar Feb 02 '24 03:02 vuon9

Laravel's Pint has the same issue so I created a script to fake a formatter mode for it: https://gist.github.com/gabrielbidula/4b8f84a02dafd242b0e1cf1d682fcaac Then the settings should look like this:

    "PHP": {
      "format_on_save": "on",
      "formatter": {
        "external": {
          "command": "/path/to/pint.sh",
          "arguments": [
            "{buffer_path}"
          ]
        }
      }
    },

gabrielbidula avatar Feb 19 '24 16:02 gabrielbidula

Laravel's Pint has the same issue so I created a script to fake a formatter mode for it: https://gist.github.com/gabrielbidula/4b8f84a02dafd242b0e1cf1d682fcaac Then the settings should look like this:

    "PHP": {
      "format_on_save": "on",
      "formatter": {
        "external": {
          "command": "/path/to/pint.sh",
          "arguments": [
            "{buffer_path}"
          ]
        }
      }
    },

If I type something, then delete it, and save/format again, it comes back. Any idea why this could be happening?

https://github.com/zed-industries/zed/assets/67285754/36e6f49c-ee9c-4b9b-a77b-bc5bbace74ae

malssid avatar Feb 29 '24 18:02 malssid

@malssid I'm experiencing the same issues all of a sudden - I've tried adding sleep 1 before cat, but I think it's some weird buffering going on in Zed.

FrittenKeeZ avatar Mar 19 '24 17:03 FrittenKeeZ

I've been able to make this work with:

.zed/settings.json

{
  "language_overrides": {
    "PHP": {
      "formatter": {
        "external": {
          "command": "./pint.sh",
          "arguments": ["{buffer_text}"]
        }
      }
    }
  }
}

And then a simple script inside ./pint.sh file, in project root like:

#!/bin/bash

# Create a temporary file
temp_file="_temp_pint.php"

# Read STDIN buffer and save it to the temporary file
cat > "$temp_file"

# Run the pint command
./vendor/bin/pint --quiet "$temp_file"

# Read the modified file and return its contents
cat "$temp_file"

And then have a temporary _temp_pint.php file (can be added empty to the repo, but excluded from changes in git) that is used for the formatting on save and returns the buffer.

cc @FrittenKeeZ , @malssid

latorante avatar Apr 02 '24 10:04 latorante

@latorante I changed my logic to use serbanrobu's snippet (slightly modified) from a Pint issue - https://github.com/laravel/pint/issues/162#issuecomment-1645114713

.zed/settings.json:

// Folder-specific settings
//
// For a full list of overridable settings, and general information on folder-specific settings,
// see the documentation: https://zed.dev/docs/configuring-zed#folder-specific-settings
{
    "languages": {
        "PHP": {
            "preferred_line_length": 120,
            "soft_wrap": "preferred_line_length",
            "formatter": {
                "external": {
                    "command": ".zed/pint.sh",
                    "arguments": ["{buffer_text}"]
                }
            }
        }
    }
}

.zed/pint.sh:

#!/bin/bash

file="$(mktemp --tmpdir=/tmp)"
cat - >"$file"
./vendor/bin/pint -q "$file"
cat "$file"
rm "$file"

FrittenKeeZ avatar Apr 22 '24 13:04 FrittenKeeZ

Using PHP-CS-Fixer, this one-line has been working fine for me for a few days now. Using the scripts shared previously in this thread, my changes where often not persisting due to an issue with how Zed handles the file at {buffer_path}.

Adjust the paths and the arguments for you needs :

{
  "languages": {
    "PHP": {
      "format_on_save": "on",
      "formatter": {
        "external": {
          "command": "bash",
          "arguments": [
            "-c",
            "cat > /tmp/zed_php_cs_fixer && php-cs-fixer fix --using-cache=no --quiet /tmp/zed_php_cs_fixer && cat /tmp/zed_php_cs_fixer"
          ]
        }
      }
    }
  }
}

Explanation of the issue : PHP-CS-Fixer can only work on a file and doesn't support STDIN/STDOUT. Zed overwrites the file at {buffer_path} with stale data, so we have to use STDIN/STDOUT

Solution : The command writes STDIN to a temporary file, runs PHP-CS-Fixer on it and then reads it back to STDOUT. Similar to what other users posted here, but standalone in your Zed config

patricksamson avatar Apr 29 '24 16:04 patricksamson

Using PHP-CS-Fixer, this one-line has been working fine for me for a few days now. Using the scripts shared previously in this thread, my changes where often not persisting due to an issue with how Zed handles the file at {buffer_path}.

Adjust the paths and the arguments for you needs :

{
  "languages": {
    "PHP": {
      "format_on_save": "on",
      "formatter": {
        "external": {
          "command": "bash",
          "arguments": [
            "-c",
            "cat > /tmp/zed_php_cs_fixer && php-cs-fixer fix --using-cache=no --quiet /tmp/zed_php_cs_fixer && cat /tmp/zed_php_cs_fixer"
          ]
        }
      }
    }
  }
}

Explanation of the issue : PHP-CS-Fixer can only work on a file and doesn't support STDIN/STDOUT. Zed overwrites the file at {buffer_path} with stale data, so we have to use STDIN/STDOUT

Solution : The command writes STDIN to a temporary file, runs PHP-CS-Fixer on it and then reads it back to STDOUT. Similar to what other users posted here, but standalone in your Zed config

Thanks for the suggestions @patricksamson @FrittenKeeZ ! If the solution I found doesn't work then I'll try yours out. What peter suggested is working for me so far: https://gist.github.com/gabrielbidula/4b8f84a02dafd242b0e1cf1d682fcaac?permalink_comment_id=5035902#gistcomment-5035902

malssid avatar Apr 29 '24 17:04 malssid

I have another use with this, as I have to open .zed/settings.json after updating Zed before the overrides take effect.

FrittenKeeZ avatar May 15 '24 09:05 FrittenKeeZ

I ran into this exact problem with Laravel Pint, and came to this solution (basically a derivate of what @malssid is doing):

  "language_overrides": {
    "PHP": {
      "formatter": {
        "external": {
          "command": "bash",
          "arguments": [
            "-c",
            "cat > /tmp/current.pint.tmp && pint /tmp/current.pint.tmp -q && cat /tmp/current.pint.tmp && rm /tmp/current.pint.tmp"
          ]
        }
      },
      "format_on_save": "on"
    }
  }

EDIT: I find this a bit weird, that you can't run the formatter against the file itself, since this would just run the formatter against the "previous" version of the file, not including your current edits.

ConnySjoblom avatar May 15 '24 09:05 ConnySjoblom