[
  {
    "path": ".circleci/config.yml",
    "content": "version: 2\njobs:\n  build:\n    machine: true\n    steps:\n      - checkout\n      - run:\n          name:\n          command: |\n            sudo apt-get -y update\n            sudo apt-get -y install shellcheck git\n\n      - run: git submodule sync\n      - run: git submodule update --init\n      - run:\n          name: tests\n          command: |\n            export TERM=dumb && ./test/bats/bin/bats test\n            shellcheck *.sh\n"
  },
  {
    "path": ".github/dependabot.yml",
    "content": "---\n# Use `allow` to specify which dependencies to maintain\n\nversion: 2\nupdates:\n  - package-ecosystem: \"github-actions\"\n    directory: \"/\"\n    schedule:\n      interval: \"weekly\"\n"
  },
  {
    "path": ".github/workflows/awesomebot.yml",
    "content": "---\nname: Check links in README.md\n\non:\n  # Run daily\n  schedule: [{cron: \"0 1 * * *\"}]\n  push:\n    branches: [\"*\"]\n  pull_request:\n    branches: [\"*\"]\n\njobs:\n  build:\n    runs-on: ubuntu-latest\n\n    steps:\n      - uses: actions/checkout@v6\n      - uses: docker://dkhamsing/awesome_bot:latest\n        with:\n          args: /github/workspace/README.md --allow-timeout --allow 202,206,403,429,500,501,502,503,504,509,521,522 --allow-dupe --allow-ssl --request-delay 1 --allow-redirect --white-list https://ipfs.io,slideshare,https://img.shields.io,https://codeclimate.com/,https://www.concourse.ci\n          # args: /github/workspace/README.md --allow-timeout --allow 202,206,403,429,500,501,502,503,504,509,521,522 --allow-dupe --allow-ssl --request-delay 1 --allow-redirect --white-list https://ipfs.io,slideshare,https://img.shields.io,https://codeclimate.com/github/unixorn/awesome-zsh-plugins,www-s.acm.illinois.edu,https://mgdm.net,https://www.concourse.ci,https://grml.org/zsh/zsh-lovers.html,https://geeknote.me,https://en.ipip.net,https://docs.virtuozzo.com,kubernetes.io,https://youtube-dl.org,https://1password.com,https://iterm2.com,https://mercurial-scm.org,https://hitokoto.cn,https://www.cyberciti.biz,https://keybase.io,https://exercism.io,https://bitbucket.org,https://code.visualstudio.com,https://www.gnu.org\n"
  },
  {
    "path": ".github/workflows/publish.yml",
    "content": "name: Publish to npm\n\non:\n  push:\n    tags:\n      - 'v*'\n\njobs:\n  publish:\n    runs-on: ubuntu-latest\n    permissions:\n      id-token: write\n      contents: read\n    steps:\n      - uses: actions/checkout@v4\n\n      - name: Install Perl and App::FatPacker\n        run: |\n          sudo apt-get update\n          sudo apt-get install -y perl cpanminus\n          cpanm --local-lib=~/perl5 App::FatPacker\n          echo \"PATH=$HOME/perl5/bin:$PATH\" >> $GITHUB_ENV\n          echo \"PERL5LIB=$HOME/perl5/lib/perl5:$PERL5LIB\" >> $GITHUB_ENV\n\n      - name: Setup Node\n        uses: actions/setup-node@v4\n        with:\n          node-version: 22.14.0\n          registry-url: 'https://registry.npmjs.org'\n\n      - name: Install and Build\n        run: |\n          npm ci\n          npm run build\n\n      - name: Publish to npm\n        run: |\n          npm install -g npm@latest\n          npm publish --provenance --access public --registry https://registry.npmjs.org/\n"
  },
  {
    "path": ".gitignore",
    "content": "/dist/\n"
  },
  {
    "path": ".gitmodules",
    "content": "[submodule \"test/bats\"]\n\tpath = test/bats\n\turl = https://github.com/bats-core/bats-core.git\n[submodule \"test/test_helper/bats-support\"]\n\tpath = test/test_helper/bats-support\n\turl = https://github.com/bats-core/bats-support.git\n[submodule \"test/test_helper/bats-assert\"]\n\tpath = test/test_helper/bats-assert\n\turl = https://github.com/bats-core/bats-assert.git\n"
  },
  {
    "path": ".npmignore",
    "content": "/test/\n"
  },
  {
    "path": ".travis.yml",
    "content": "os: linux\nlanguage: perl\nperl:\n  - blead\n  - dev\n  - 5.30\n  - 5.28\n  - 5.26\n  - 5.24\n  - 5.22\n  - 5.20\n  - 5.18\n  - 5.14\n\nmatrix:\n  include:\n    - os: osx\n      osx_image: xcode12.2\n      language: generic\n      perl: 5.30\n  allow_failures:\n    - perl: \"blead\"\n\naddons:\n  homebrew:\n    packages:\n      - perl\n      - cpanminus\n    update: true\n\nbefore_install:\n  - git submodule sync\n  - git submodule update --init\n  - if [[ \"$TRAVIS_OS_NAME\" == \"osx\" ]]; then\n      mkdir -p ~/perl5;\n    fi\n  - if [[ \"$TRAVIS_OS_NAME\" == \"linux\" ]]; then\n      eval $(curl https://travis-perl.github.io/init) --perl;\n      sudo add-apt-repository \"deb http://archive.ubuntu.com/ubuntu/ trusty-backports restricted main universe\";\n      sudo apt-get -y update;\n    fi\ninstall:\n  - cpanm --quiet --notest --local-lib=~/perl5 local::lib\n  - eval $(perl -I ~/perl5/lib/perl5/ -Mlocal::lib)\n  - cpanm --quiet --notest Test::Perl::Critic Perl::Critic::Freenode\nscript:\n  - perlcritic -1 -q --theme freenode diff-so-fancy\n  - ./test/bats/bin/bats test\n"
  },
  {
    "path": "LICENSE",
    "content": "The MIT License (MIT)\n\nCopyright (c) 2016 So Fancy team\n\nPermission is hereby granted, free of charge, to any person obtaining a copy\nof this software and associated documentation files (the \"Software\"), to deal\nin the Software without restriction, including without limitation the rights\nto use, copy, modify, merge, publish, distribute, sublicense, and/or sell\ncopies of the Software, and to permit persons to whom the Software is\nfurnished to do so, subject to the following conditions:\n\nThe above copyright notice and this permission notice shall be included in\nall copies or substantial portions of the Software.\n\nTHE SOFTWARE IS PROVIDED \"AS IS\", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR\nIMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,\nFITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE\nAUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER\nLIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,\nOUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN\nTHE SOFTWARE.\n"
  },
  {
    "path": "README.md",
    "content": "# 🎩 diff-so-fancy  [![Circle CI build](https://circleci.com/gh/so-fancy/diff-so-fancy.svg?style=shield)](https://circleci.com/gh/so-fancy/diff-so-fancy) [![AppVeyor build](https://ci.appveyor.com/api/projects/status/github/so-fancy/diff-so-fancy?branch=master&svg=true)](https://ci.appveyor.com/project/stevemao/diff-so-fancy/branch/master)\n\n`diff-so-fancy` makes your diffs **human** readable instead of machine readable. This helps improve code quality and helps you spot defects faster.\n\n## 🖼️ Screenshot\n\nVanilla `git diff` vs `git` and `diff-so-fancy`\n\n![diff-highlight vs diff-so-fancy](diff-so-fancy.png)\n\n## 📦 Install\n\nSimply copy the `diff-so-fancy` script from the latest Github release into your `$PATH` and you're done. Alternately to test development features you can clone this repo and then put the `diff-so-fancy` script (symlink will work) into your `$PATH`. The `lib/` directory will need to be kept relative to the core script.\n\n`diff-so-fancy` is also available from the [NPM registry](https://www.npmjs.com/package/diff-so-fancy), [brew](https://formulae.brew.sh/formula/diff-so-fancy), [Fedora](https://packages.fedoraproject.org/pkgs/diff-so-fancy/diff-so-fancy/), in the [Arch extra repo](https://archlinux.org/packages/extra/any/diff-so-fancy/), and as [ppa:aos for Debian/Ubuntu Linux](https://github.com/aos/dsf-debian).\n\nIssues relating to packaging (\"installation does not work\", \"version is out of date\", etc.) should be directed to those packages' repositories/issue trackers where applicable.\n\n## ✨ Usage\n\n### Git\n\nConfigure git to use `diff-so-fancy` for all diff output:\n\n```shell\ngit config --global core.pager \"diff-so-fancy | less --tabs=4 -RF\"\ngit config --global interactive.diffFilter \"diff-so-fancy --patch\"\n```\n\n### Diff\n\nUse `-u` with `diff` for unified output, and pipe the output to `diff-so-fancy`:\n\n```shell\ndiff -u file_a file_b | diff-so-fancy\n```\n\nWe also support recursive mode with `-r` or `--recursive`\n\n```shell\ndiff --recursive -u /path/folder_a /path/folder_b | diff-so-fancy\n```\n\n## ⚒️ Options\n\n### markEmptyLines\n\nColorize the first block of an empty line. (Default: true)\n\n```shell\ngit config --bool --global diff-so-fancy.markEmptyLines false\n```\n\n### changeHunkIndicators\n\nSimplify Git header chunks to a human readable format. (Default: true)\n\n```shell\ngit config --bool --global diff-so-fancy.changeHunkIndicators false\n```\n\n### stripLeadingSymbols\n\nShould the `+` or `-` symbols at line-start be removed. (Default: true)\n\n```shell\ngit config --bool --global diff-so-fancy.stripLeadingSymbols false\n```\n\n### useUnicodeRuler\n\nBy default, the separator for the file header uses Unicode line-drawing characters.\nIf this is causing output errors on your terminal, set this to `false` to use\nASCII characters instead. (Default: true)\n\n```shell\ngit config --bool --global diff-so-fancy.useUnicodeRuler false\n```\n\n### rulerWidth\n\nBy default, the separator for the file header spans the full width of the terminal.\nUse rulerWidth to set the width of the file header manually.\n\n```shell\ngit config --global diff-so-fancy.rulerWidth 80\n```\n\n## 👨 The diff-so-fancy team\n\n| Person                | Role             |\n| --------------------- | ---------------- |\n| @scottchiefbaker      | Project lead     |\n| @OJFord               | Bug triage       |\n| @GenieTim             | Travis OSX fixes |\n| @AOS                  | Debian packager  |\n| @Stevemao/@Paul Irish | NPM release team |\n\n## 🧬 Contributing\n\nPull requests are quite welcome, and should target the\n[`next` branch](https://github.com/so-fancy/diff-so-fancy/tree/next). We are also\nlooking for any feedback or ideas on how to make `diff-so-fancy` even *fancier*.\n\n### Other documentation\n\n* [Pro-tips for advanced users](pro-tips.md)\n* [Reporting Bugs](reporting-bugs.md)\n* [Hacking and Testing](hacking-and-testing.md)\n* [History](history.md)\n\n## 🔃 Alternatives\n\n* [Delta](https://github.com/dandavison/delta)\n* [Lazygit](https://github.com/jesseduffield/lazygit) with diff-so-fancy [integration](https://github.com/jesseduffield/lazygit/blob/master/docs/Custom_Pagers.md#diff-so-fancy)\n\n## 🏛️ License\n\nMIT\n"
  },
  {
    "path": "appveyor.yml",
    "content": "build: false\n\nbefore_test:\n  - git submodule sync\n  - git submodule update --init\n\ntest_script:\n  - C:\\cygwin\\bin\\bash -lc \"cd $APPVEYOR_BUILD_FOLDER; ./test/bats/bin/bats test\"\n"
  },
  {
    "path": "diff-so-fancy",
    "content": "#!/usr/bin/env perl\n\nmy $VERSION = \"1.4.7\";\n\n#################################################################################\n\nuse v5.014;                                                 # Require Perl 5.14 for 'state' variables and /u in regexes\nuse warnings FATAL => 'all';\nuse strict;\n\nuse File::Spec;                                             # For catdir\nuse File::Basename;                                         # For dirname\nuse Cwd qw(abs_path);                                       # For realpath()\nuse lib dirname(abs_path(File::Spec->catdir($0))) . \"/lib\"; # Add the local lib/ to @INC\nuse DiffHighlight;\n\nmy $remove_file_add_header     = 1;\nmy $remove_file_delete_header  = 1;\nmy $clean_permission_changes   = 1;\nmy $patch_mode                 = 0;\nmy $manually_color_lines       = 0; # Usually git/hg colorizes the lines, but for raw patches we use this\nmy $change_hunk_indicators     = git_config_boolean(\"diff-so-fancy.changeHunkIndicators\",\"true\");\nmy $strip_leading_indicators   = git_config_boolean(\"diff-so-fancy.stripLeadingSymbols\",\"true\");\nmy $mark_empty_lines           = git_config_boolean(\"diff-so-fancy.markEmptyLines\",\"true\");\nmy $use_unicode_dash_for_ruler = git_config_boolean(\"diff-so-fancy.useUnicodeRuler\",\"true\");\nmy $ruler_width                = git_config(\"diff-so-fancy.rulerWidth\", undef);\nmy $git_strip_prefix           = git_config_boolean(\"diff.noprefix\",\"false\");\nmy $has_stdin                  = has_stdin();\n\nmy $ansi_regex       = qr/\\e\\[([0-9]{0,3}(;[0-9]{1,3}){0,10})[mK]/;\nmy $ansi_color_regex = qr/(${ansi_regex})?/;\nmy $reset_color      = color(\"reset\");\nmy $bold             = color(\"bold\");\nmy $meta_color       = \"\";\n\n# Set the diff highlight colors from the config\ninit_diff_highlight_colors();\n\nmy ($file_1,$file_2);\nmy $args              = argv(); # Hashref of all the ARGV stuff\nmy $last_file_seen    = \"\";\nmy $last_file_mode    = \"\";\nmy $i                 = 0;\nmy $in_hunk           = 0;\nmy $columns_to_remove = 0;\nmy $is_mercurial      = 0;\nmy $color_forced      = 0; # Has the color been forced on/off\n\nif ($args->{rulerWidth}) {\n\t$ruler_width = int($args->{rulerWidth});\n}\n\n# We try and be smart about whether we need to do line coloring, but\n# this is an option to force it on/off\nif ($args->{color_on}) {\n\t$manually_color_lines = 1;\n\t$color_forced         = 1;\n} elsif ($args->{color_off}) {\n\t$manually_color_lines = 0;\n\t$color_forced         = 1;\n}\n\nif ($args->{debug}) {\n\tshow_debug_info();\n}\n\n# We only process ARGV if we don't have STDIN\nif (!$has_stdin) {\n\tif ($args->{v} || $args->{version}) {\n\t\tdie(version());\n\t} elsif ($args->{'set-defaults'}) {\n\t\tmy $ok = set_defaults();\n\t\texit;\n\t} elsif ($args->{colors}) {\n\t\t# We print this to STDOUT so we can redirect to bash to auto-set the colors\n\t\tprint get_default_colors();\n\t\texit;\n\t} elsif (!%$args || $args->{help} || $args->{h}) {\n\t\tdie(usage());\n\t} else {\n\t\tdie(\"Missing input on STDIN\\n\");\n\t}\n}\n\n#################################################################################\n#################################################################################\n\n# The logic here is that we run all the lines through DiffHighlight first. This\n# highlights all the intra-word changes. Then we take those lines and send them\n# to do_dsf_stuff() to convert the diff to human readable d-s-f output and add\n# appropriate fanciness\n\nmy @lines;\nlocal $DiffHighlight::line_cb = sub {\n\tpush(@lines,@_);\n\n\t# Wait until we have enough lines to process, then divide up into safe\n\t# parseable chunks and have d-s-f do it's magic\n\tif (@lines > 100) {\n\t\tmy @chunks = get_diff_chunks(\\@lines);\n\t\t@lines     = ();\n\n\t\tforeach my $chunk (@chunks) {\n\t\t\tdo_dsf_stuff($chunk);\n\t\t}\n\t}\n};\n\nmy $line_count = 0;\nwhile (my $line = <STDIN>) {\n\t# If the very first line of the diff doesn't start with ANSI color we're assuming\n\t# it's a raw patch file, and we have to color the added/removed lines ourself\n\tif (!$color_forced && $line_count == 0 && !starts_with_ansi($line)) {\n\t\t$manually_color_lines = 1;\n\t}\n\n\tmy $ok = DiffHighlight::handle_line($line);\n\t$line_count++;\n}\n\n# If we're mid hunk above process anything still pending\nDiffHighlight::flush();\n\n# Process anything left over\nmy @chunks = get_diff_chunks(\\@lines);\nforeach my $chunk (@chunks) {\n\tdo_dsf_stuff($chunk);\n}\n\n#################################################################################\n#################################################################################\n\nsub do_dsf_stuff {\n\tmy $input = shift();\n\n\t# If we're in debug mode we want to log the chunks we see\n\tif ($args->{debug}) { log_chunks($input); }\n\n\t# FIXME: There is a scenario where there are FIVE diff header lines\n\t# I'm not sure if we need to account for that or not. Unit tests pass so... bees?\n\tmy $cnt = count_git_header_lines(@$input);\n\tif ($cnt == 4) {\n\t\t$patch_mode = 1;\n\t}\n\n\t# Calculate the context lines for this chunk\n\tmy $context_lines = calculate_context_lines(@$input);\n\n\t#print STDERR \"START -------------------------------------------------\\n\";\n\t#print STDERR join(\"\",@$input);\n\t#print STDERR \"END ---------------------------------------------------\\n\";\n\n\t# We track if the last '^diff' line had recursive specified\n\tmy $recursive = 0;\n\n\twhile (my $line = shift(@$input)) {\n\t\t######################################################\n\t\t# Pre-process the line before we do any other markup #\n\t\t######################################################\n\n\t\t# If the first line of the input is a blank line, skip that\n\t\tif ($i == 0 && $line =~ /^\\s*$/) {\n\t\t\tnext;\n\t\t}\n\n\t\t######################\n\t\t# End pre-processing #\n\t\t######################\n\n\t\t#######################################################################\n\n\t\t####################################################################\n\t\t# Look for git index and replace it horizontal line (header later) #\n\t\t####################################################################\n\t\tif ($line =~ /^${ansi_color_regex}index /) {\n\t\t\t# Print the line color and then the actual line\n\t\t\t$meta_color = $1 || get_config_color(\"meta\");\n\n\t\t\t# Get the next line without incrementing counter while loop\n\t\t\tmy $next = $input->[0] || \"\";\n\t\t\tmy ($file_1,$file_2);\n\n\t\t\t# The line immediately after the \"index\" line should be the --- file line\n\t\t\t# If it's not it's an empty file add/delete\n\t\t\tif ($next !~ /^$ansi_color_regex(---|Binary files)/) {\n\n\t\t\t\t# We fake out the file names since it's a raw add/delete\n\t\t\t\tif ($last_file_mode eq \"add\") {\n\t\t\t\t\t$file_1 = \"/dev/null\";\n\t\t\t\t\t$file_2 = $last_file_seen;\n\t\t\t\t} elsif ($last_file_mode eq \"delete\") {\n\t\t\t\t\t$file_1 = $last_file_seen;\n\t\t\t\t\t$file_2 = \"/dev/null\";\n\t\t\t\t}\n\t\t\t}\n\n\t\t\tif ($file_1 && $file_2) {\n\t\t\t\tprint horizontal_rule($meta_color);\n\t\t\t\tprint $meta_color . file_change_string($file_1,$file_2) . \"\\n\";\n\t\t\t\tprint horizontal_rule($meta_color);\n\t\t\t}\n\t\t#########################\n\t\t# Look for the filename #\n\t\t#########################\n\t\t#                                            $4\n\t\t} elsif ($line =~ /^${ansi_color_regex}diff (.+?)$/) {\n\t\t\tmy $extra = $4;\n\n\t\t\t# Mercurial looks like: diff --recursive -u core/app.py language/app.py\n\t\t\tif ($extra =~ m/(-r|--recursive) -u (.+?) (.*)/) {\n\t\t\t\t$is_mercurial = 1;\n\t\t\t\t$meta_color   = get_config_color(\"meta\");\n\n\t\t\t\t$last_file_seen = basename($3 || \"\");\n\t\t\t\t$recursive      = 1;\n\t\t\t# Git looks like: diff --git a/diff-so-fancy b/diff-so-fancy\n\t\t\t} elsif ($extra =~ m/--git/) {\n\t\t\t\t# Note file may contains spaces\n\t\t\t\tmy ($a_file, $b_file) = $extra =~ m/(a\\/.+) (b\\/.+)/;\n\t\t\t\t$a_file ||= \"\";\n\t\t\t\t$b_file ||= \"\";\n\n\t\t\t\t$last_file_seen = $a_file;\n\t\t\t\t$recursive      = 0;\n\t\t\t}\n\n\t\t\t$last_file_seen =~ s|^\\w/||; # Remove a/ (and handle diff.mnemonicPrefix).\n\t\t\t$in_hunk        = 0;\n\n\t\t\tif ($patch_mode) {\n\t\t\t\t# we are consuming one line, and the debt must be paid\n\t\t\t\tprint \"\\n\";\n\t\t\t}\n\t\t########################################\n\t\t# Find the first file: --- a/README.md #\n\t\t########################################\n\t\t} elsif (!$in_hunk && $line =~ /^$ansi_color_regex--- (\\w\\/)?(.+?)(\\e|\\t|$)/) {\n\t\t\t$meta_color = get_config_color(\"meta\");\n\n\t\t\tif ($git_strip_prefix) {\n\t\t\t\tmy $file_dir = $4 || \"\";\n\t\t\t\t$file_1 = $file_dir . $5;\n\t\t\t} else {\n\t\t\t\t$file_1 = $5;\n\t\t\t}\n\n\t\t\t# Find the second file on the next line: +++ b/README.md\n\t\t\tmy $next = shift(@$input) || \"\";\n\t\t\t$next    =~ /^$ansi_color_regex\\+\\+\\+ (\\w\\/)?(.+?)(\\e|\\t|$)/;\n\t\t\tif ($1) {\n\t\t\t\tprint $1; # Print out whatever color we're using\n\t\t\t}\n\t\t\tif ($git_strip_prefix) {\n\t\t\t\tmy $file_dir = $4 || \"\";\n\t\t\t\t$file_2 = $file_dir . $5;\n\t\t\t} else {\n\t\t\t\t$file_2 = $5;\n\t\t\t}\n\n\t\t\tif ($file_2 ne \"/dev/null\") {\n\t\t\t\t$last_file_seen = $file_2;\n\t\t\t}\n\n\t\t\t# In recursive mode we massage the file names so they're similar\n\t\t\tif ($recursive) {\n\t\t\t\t# In recursive the files have the date appended:\n\t\t\t\t# --- /var/tmp/a/index.txt        2026-03-13 21:35:20.997231861 -0700\n\t\t\t\t# so we remove the date portion\n\t\t\t\t$file_1 =~ s/\\s+(\\d{4}-\\d{2}-\\d{2}.+)//;\n\t\t\t\t$file_2 =~ s/\\s+(\\d{4}-\\d{2}-\\d{2}.+)//;\n\n\t\t\t\tmy $common = common_prefix($file_1, $file_2);\n\n\t\t\t\t# Remove the common prefix from each as well as the next dir\n\t\t\t\t$file_1 =~ s/$common.+?\\///;\n\t\t\t\t$file_2 =~ s/$common.+?\\///;\n\t\t\t}\n\n\t\t\t# Print out the top horizontal line of the header\n\t\t\tprint $reset_color;\n\t\t\tprint horizontal_rule($meta_color);\n\n\t\t\t# Mercurial coloring is slightly different so we need to hard reset colors\n\t\t\tif ($is_mercurial) {\n\t\t\t\tprint $reset_color;\n\t\t\t}\n\n\t\t\tprint $meta_color;\n\t\t\tprint file_change_string($file_1,$file_2) . \"\\n\";\n\n\t\t\t# Print out the bottom horizontal line of the header\n\t\t\tprint horizontal_rule($meta_color);\n\t\t########################################\n\t\t# Check for \"@@ -3,41 +3,63 @@\" syntax #\n\t\t########################################\n\t\t} elsif (!$change_hunk_indicators && $line =~ /^${ansi_color_regex}(@@@* .+? @@@*)(.*)/) {\n\t\t\t$in_hunk = 1;\n\n\t\t\tprint $line;\n\t\t} elsif ($change_hunk_indicators && $line =~ /^${ansi_color_regex}(@@@* .+? @@@*)(.*)/) {\n\t\t\t$in_hunk = 1;\n\n\t\t\tmy $hunk_header = $4;\n\t\t\tmy $remain      = bleach_text($5);\n\n\t\t\t# The number of colums to remove (1 or 2) is based on how many commas in the hunk header\n\t\t\t$columns_to_remove   = (char_count(\",\",$hunk_header)) - 1;\n\t\t\t# On single line removes there is NO comma in the hunk so we force one\n\t\t\tif ($columns_to_remove <= 0) {\n\t\t\t\t$columns_to_remove = 1;\n\t\t\t}\n\n\t\t\tif ($1) {\n\t\t\t\tprint $1; # Print out whatever color we're using\n\t\t\t}\n\n\t\t\tmy ($orig_offset, $orig_count, $new_offset, $new_count) = parse_hunk_header($hunk_header);\n\t\t\t#$last_file_seen = basename($last_file_seen);\n\n\t\t\t# Figure out the start line\n\t\t\tmy $start_line = start_line_calc($new_offset, $new_count, $context_lines);\n\n\t\t\t# Last function has it's own color\n\t\t\tmy $last_function_color = \"\";\n\t\t\tif ($remain) {\n\t\t\t\t$last_function_color = get_config_color(\"last_function\");\n\t\t\t}\n\n\t\t\t# Check to see if we have the color for the fragment from git\n\t\t\tif ($5 =~ /\\e\\[\\d/) {\n\t\t\t\t#print \"Has ANSI color for fragment\\n\";\n\t\t\t} else {\n\t\t\t\t# We don't have the ANSI sequence so we shell out to get it\n\t\t\t\t#print \"No ANSI color for fragment\\n\";\n\t\t\t\tmy $frag_color = get_config_color(\"fragment\");\n\t\t\t\tprint $frag_color;\n\t\t\t}\n\n\t\t\tprint \"@ $last_file_seen:$start_line \\@${reset_color}${last_function_color}${remain}${reset_color}\\n\";\n\t\t###################################\n\t\t# Remove any new file permissions #\n\t\t###################################\n\t\t} elsif ($remove_file_add_header && $line =~ /^${ansi_color_regex}new file mode [0-7]{6}/) {\n\t\t\t# Don't print the line (i.e. remove it from the output);\n\t\t\t$last_file_mode = \"add\";\n\t\t\tif ($patch_mode) {\n\t\t\t\tprint \"\\n\";\n\t\t\t}\n\t\t######################################\n\t\t# Remove any delete file permissions #\n\t\t######################################\n\t\t} elsif ($remove_file_delete_header && $line =~ /^${ansi_color_regex}deleted file mode [0-7]{6}/) {\n\t\t\t# Don't print the line (i.e. remove it from the output);\n\t\t\t$last_file_mode = \"delete\";\n\t\t\tif ($patch_mode) {\n\t\t\t\tprint \"\\n\";\n\t\t\t}\n\t\t################################\n\t\t# Look for binary file changes #\n\t\t################################\n\t\t} elsif ($line =~ /^Binary files (\\w\\/)?(.+?) and (\\w\\/)?(.+?) differ/) {\n\t\t\tmy $change = file_change_string($2,$4);\n\t\t\tprint horizontal_rule($meta_color);\n\t\t\tprint \"$meta_color$change (binary)\\n\";\n\t\t\tprint horizontal_rule($meta_color);\n\t\t#####################################################\n\t\t# Check if we're changing the permissions of a file #\n\t\t#####################################################\n\t\t} elsif ($clean_permission_changes && $line =~ /^${ansi_color_regex}old mode (\\d+)/) {\n\t\t\tmy ($old_mode) = $4;\n\t\t\tmy $next = shift(@$input);\n\n\t\t\tif ($1) {\n\t\t\t\tprint $1; # Print out whatever color we're using\n\t\t\t}\n\n\t\t\tmy ($new_mode) = $next =~ m/new mode (\\d+)/;\n\n\t\t\tif ($patch_mode) {\n\t\t\t\tprint \"\\n\";\n\t\t\t}\n\t\t\tprint \"$last_file_seen changed file mode from $old_mode to $new_mode\\n\";\n\n\t\t###############\n\t\t# File rename #\n\t\t###############\n\t\t} elsif ($line =~ /^${ansi_color_regex}similarity index (\\d+)%/) {\n\t\t\tmy $simil = $4;\n\n\t\t\t# If it's a move with content change we ignore this and the next two lines\n\t\t\tif ($simil != 100) {\n\t\t\t\tshift(@$input);\n\t\t\t\tshift(@$input);\n\t\t\t\tnext;\n\t\t\t}\n\n\t\t\tmy $next    = shift(@$input);\n\t\t\tmy ($action1, $file1) = $next =~ /(copy|rename) from (.+?)(\\e|\\t|$)/;\n\n\t\t\t$next       = shift(@$input);\n\t\t\tmy ($action2, $file2) = $next =~ /(copy|rename) to (.+?)(\\e|\\t|$)/;\n\n\t\t\tif ($file1 && $file2) {\n\t\t\t\t# We may not have extracted this yet, so we pull from the config if not\n\t\t\t\t$meta_color = get_config_color(\"meta\");\n\n\t\t\t\tmy $change = \"???\";\n\t\t\t\tif ($action1 eq \"copy\") {\n\t\t\t\t\t$change = \"Copied $file1 to $file2\";\n\t\t\t\t} else {\n\t\t\t\t\t$change = file_change_string($file1,$file2);\n\t\t\t\t}\n\n\t\t\t\tprint horizontal_rule($meta_color);\n\t\t\t\tprint $meta_color . $change . \"\\n\";\n\t\t\t\tprint horizontal_rule($meta_color);\n\t\t\t}\n\n\t\t\t$i += 3; # We've consumed three lines\n\t\t\tnext;\n\t\t#####################################\n\t\t# Just a regular line, print it out #\n\t\t#####################################\n\t\t} else {\n\t\t\t# Mark empty line with a red/green box indicating addition/removal\n\t\t\tif ($mark_empty_lines) {\n\t\t\t\t$line = mark_empty_line($line);\n\t\t\t}\n\n\t\t\t# Remove the correct number of leading \" \" or \"+\" or \"-\"\n\t\t\tif ($strip_leading_indicators) {\n\t\t\t\t$line = strip_leading_indicators($line,$columns_to_remove);\n\t\t\t}\n\t\t\tprint $line;\n\t\t}\n\n\t\t$i++;\n\t}\n}\n\n######################################################################################################\n# End regular code, begin functions\n######################################################################################################\n\n# Courtesy of github.com/git/git/blob/ab5d01a/git-add--interactive.perl#L798-L805\nsub parse_hunk_header {\n\tmy ($line) = @_;\n\tmy ($o_ofs, $o_cnt, $n_ofs, $n_cnt) = $line =~ /^\\@\\@+(?: -(\\d+)(?:,(\\d+))?)+ \\+(\\d+)(?:,(\\d+))? \\@\\@+/;\n\t$o_cnt = 1 unless defined $o_cnt;\n\t$n_cnt = 1 unless defined $n_cnt;\n\treturn ($o_ofs, $o_cnt, $n_ofs, $n_cnt);\n}\n\n# Mark the first char of an empty line\nsub mark_empty_line {\n\tmy $line = shift();\n\n\tmy $reset_color  = \"\\e\\\\[0?m\";\n\tmy $reset_escape = \"\\e\\[m\";\n\tmy $invert_color = \"\\e\\[7m\";\n\tmy $add_color    = $DiffHighlight::NEW_HIGHLIGHT[1];\n\tmy $del_color    = $DiffHighlight::OLD_HIGHLIGHT[1];\n\n\t# This captures lines that do not have any ANSI in them (raw vanilla diff)\n\tif ($line eq \"+\\n\") {\n\t\t$line = $invert_color . $add_color . \" \" . color('reset') . \"\\n\";\n\t# This captures lines that do not have any ANSI in them (raw vanilla diff)\n\t} elsif ($line eq \"-\\n\") {\n\t\t$line = $invert_color . $del_color . \" \" . color('reset') . \"\\n\";\n\t# This handles everything else\n\t} else {\n\t\t$line =~ s/^($ansi_color_regex)[+-]$reset_color\\s*$/$invert_color$1 $reset_escape\\n/;\n\t}\n\n\treturn $line;\n}\n\n# String to boolean\nsub boolean {\n\tmy $str = shift();\n\t$str    = trim($str);\n\n\tif ($str eq \"\" || $str =~ /^(no|false|0)$/i) {\n\t\treturn 0;\n\t} else {\n\t\treturn 1;\n\t}\n}\n\n# Get the git config\nsub git_config_raw {\n\tmy $cmd = \"git config --list 2>&1\";\n\tmy @out = `$cmd`;\n\n\treturn \\@out;\n}\n\n# Memoize fetching a textual item from the git config\nsub git_config {\n\tmy $search_key    = lc($_[0] || \"\");\n\tmy $default_value = lc($_[1] || \"\");\n\n\tstate $raw = {};\n\tif (%$raw && $search_key) {\n\t\treturn $raw->{$search_key} || $default_value;\n\t}\n\n\tif ($args->{debug}) {\n\t\tprint \"Parsing git config\\n\";\n\t}\n\n\tmy $out = git_config_raw();\n\n\tforeach my $line (@$out) {\n\t\tif ($line =~ /=/) {\n\t\t\tmy ($key,$value) = split(\"=\",$line,2);\n\t\t\t$value =~ s/\\s+$//;\n\t\t\t$raw->{$key} = $value;\n\t\t}\n\t}\n\n\t# If we're given a search key return that, else return the hash\n\tif ($search_key) {\n\t\treturn $raw->{$search_key} || $default_value;\n\t} else {\n\t\treturn $raw;\n\t}\n}\n\n# Fetch a boolean item from the git config\nsub git_config_boolean {\n\tmy $search_key    = lc($_[0] || \"\");\n\tmy $default_value = lc($_[1] || 0); # Default to false\n\n\tmy $result = git_config($search_key,$default_value);\n\tmy $ret    = boolean($result);\n\n\treturn $ret;\n}\n\nsub get_less_charset {\n\tmy @less_char_vars = (\"LESSCHARSET\", \"LESSCHARDEF\", \"LC_ALL\", \"LC_CTYPE\", \"LANG\");\n\tforeach my $key (@less_char_vars) {\n\t\tmy $val = $ENV{$key};\n\n\t\tif (defined $val) {\n\t\t\treturn ($key, $val);\n\t\t}\n\t}\n\n\treturn ();\n}\n\nsub should_print_unicode {\n\tif (-t STDOUT) {\n\t\t# Always print unicode chars if we're not piping stuff, e.g. to less(1)\n\t\treturn 1;\n\t}\n\n\t# Otherwise, assume we're piping to less(1)\n\tmy ($less_env_var, $less_charset) = get_less_charset();\n\tif ($less_charset && $less_charset =~ /utf-?8/i) {\n\t\treturn 1;\n\t}\n\n\treturn 0;\n}\n\n# Try and be smart about what line the diff hunk starts on\nsub start_line_calc {\n\tmy ($line_num, $diff_context, $context_lines) = @_;\n\tmy $ret;\n\n\tif ($line_num == 0 && $diff_context == 0) {\n\t\treturn 1;\n\t}\n\n\t# Three lines on either side, and the line itself = 7\n\tmy $expected_context = ($context_lines * 2 + 1);\n\n\t# The first three lines\n\tif ($line_num == 1 && $diff_context < $expected_context) {\n\t\t$ret = $diff_context - $context_lines;\n\t} else {\n\t\t$ret = $line_num + $context_lines;\n\t}\n\n\tif ($ret < 1) {\n\t\t$ret = 1;\n\t}\n\n\treturn $ret;\n}\n\n# Remove + or - at the beginning of the lines\nsub strip_leading_indicators {\n\tmy $line              = shift(); # Array passed in by reference\n\tmy $columns_to_remove = shift(); # Don't remove any lines by default\n\n\tif ($columns_to_remove == 0) {\n\t\treturn $line; # Nothing to do\n\t}\n\n\t$line =~ s/^(${ansi_color_regex})([ +-]){${columns_to_remove}}/$1/;\n\n\tif ($manually_color_lines) {\n\t\tif (defined($5) && $5 eq \"+\") {\n\t\t\tmy $add_line_color = get_config_color(\"add_line\");\n\t\t\t$line              = $add_line_color . insert_reset_at_line_end($line);\n\t\t} elsif (defined($5) && $5 eq \"-\") {\n\t\t\tmy $remove_line_color = get_config_color(\"remove_line\");\n\t\t\t$line                 = $remove_line_color . insert_reset_at_line_end($line);\n\t\t}\n\t}\n\n\treturn $line;\n}\n\n# Insert the color reset code at end of line, but before any newlines\nsub insert_reset_at_line_end {\n\tmy $line = shift();\n\t$line =~ s/^(.*)([\\n\\r]+)?$/${1}${reset_color}${2}/;\n\treturn $line;\n}\n\n# Count the number of a given char in a string\n# https://www.perturb.org/display/1010_Perl_Count_occurrences_of_substring.html\nsub char_count {\n\tmy ($needle, $haystack) = @_;\n\n\tmy $count = () = ($haystack =~ /$needle/g);\n\n\treturn $count;\n}\n\n# Remove all ANSI codes from a string\nsub bleach_text {\n\tmy $str = shift();\n\t$str    =~ s/\\e\\[\\d*(;\\d+)*m//mg;\n\n\treturn $str;\n}\n\n# Remove all trailing and leading spaces\nsub trim {\n\tmy $s = shift();\n\tif (!$s) { return \"\"; }\n\n\t$s =~ s/^\\s*//u;\n\t$s =~ s/\\s*$//u;\n\n\treturn $s;\n}\n\n# Print a line of em-dash or line-drawing chars the full width of the screen\nsub horizontal_rule {\n\tmy $color = $_[0] || \"\";\n\tmy $width = get_terminal_width();\n\n\t# em-dash http://www.fileformat.info/info/unicode/char/2014/index.htm\n\t#my $dash = \"\\x{2014}\";\n\t# BOX DRAWINGS LIGHT HORIZONTAL http://www.fileformat.info/info/unicode/char/2500/index.htm\n\tmy $dash;\n\tif ($use_unicode_dash_for_ruler && should_print_unicode()) {\n\t\t#$dash = Encode::encode('UTF-8', \"\\x{2500}\");\n\t\t$dash = \"\\xE2\\x94\\x80\";\n\t} else {\n\t\t$dash = \"-\";\n\t}\n\n\t# Draw the line\n\tmy $ret = $color . ($dash x $width) . \"$reset_color\\n\";\n\n\treturn $ret;\n}\n\nsub file_change_string {\n\tmy $file_1 = shift();\n\tmy $file_2 = shift();\n\n\t# If they're the same it's a modify\n\tif ($file_1 eq $file_2) {\n\t\treturn \"modified: $file_1\";\n\t# If the first is /dev/null it's a new file\n\t} elsif ($file_1 eq \"/dev/null\") {\n\t\tmy $add_color = $DiffHighlight::NEW_HIGHLIGHT[1];\n\t\treturn \"added: $add_color$file_2$reset_color\";\n\t# If the second is /dev/null it's a deletion\n\t} elsif ($file_2 eq \"/dev/null\") {\n\t\tmy $del_color = $DiffHighlight::OLD_HIGHLIGHT[1];\n\t\treturn \"deleted: $del_color$file_1$reset_color\";\n\t# If the files aren't the same it's a rename\n\t} elsif ($file_1 ne $file_2) {\n\t\tmy ($old, $new) = DiffHighlight::highlight_pair($file_1,$file_2,{only_diff => 1});\n\t\t# highlight_pair already includes reset_color, but adds newline characters that need to be trimmed off\n\t\t$old = trim($old);\n\t\t$new = trim($new);\n\t\treturn \"renamed: $old$meta_color to $new\"\n\t# Something we haven't thought of yet\n\t} else {\n\t\treturn \"$file_1 -> $file_2\";\n\t}\n}\n\n# Check to see if STDIN is connected to an interactive terminal\nsub has_stdin {\n\tmy $i   = -t STDIN;\n\tmy $ret = int(!$i);\n\n\treturn $ret;\n}\n\n# We use this instead of Getopt::Long because it's faster and we're not parsing any\n# crazy arguments\n# Borrowed from: https://www.perturb.org/display/1153_Perl_Quick_extract_variables_from_ARGV.html\nsub argv {\n\tmy $ret = {};\n\n\tfor (my $i = 0; $i < scalar(@ARGV); $i++) {\n\n\t\t# If the item starts with \"-\" it's a key\n\t\tif ((my ($key) = $ARGV[$i] =~ /^--?([a-zA-Z_-]*\\w)$/) && ($ARGV[$i] !~ /^-\\w\\w/)) {\n\t\t\t# If the next item does not start with \"--\" it's the value for this item\n\t\t\tif (defined($ARGV[$i + 1]) && ($ARGV[$i + 1] !~ /^--?\\D/)) {\n\t\t\t\t$ret->{$key} = $ARGV[$i + 1];\n\t\t\t# Bareword like --verbose with no options\n\t\t\t} else {\n\t\t\t\t$ret->{$key}++;\n\t\t\t}\n\t\t}\n\t}\n\n\t# We're looking for a certain item\n\tif ($_[0]) { return $ret->{$_[0]}; }\n\n\treturn $ret;\n}\n\n# Output the command line usage for d-s-f\nsub usage {\n\tmy $out = color(\"white_bold\") . version() . color(\"reset\") . \"\\n\";\n\n\t$out .= \"Usage:\n\ngit diff --color | diff-so-fancy         # Use d-s-f on one diff\ncat diff.txt | diff-so-fancy             # Use d-s-f on a diff/patch file\ndiff -u one.txt two.txt | diff-so-fancy  # Use d-s-f on unified diff output\n\ndiff-so-fancy --colors                   # View the commands to set the recommended colors\ndiff-so-fancy --set-defaults             # Configure git-diff to use diff-so-fancy and suggested colors\ndiff-so-fancy --patch                    # Use diff-so-fancy in patch mode (interoperable with `git add --patch`)\n\n# Configure git to use d-s-f for *all* diff operations\ngit config --global core.pager \\\"diff-so-fancy | less --tabs=4 -RFX\\\"\n\n# Configure git to use d-s-f for `git add --patch`\ngit config --global interactive.diffFilter \\\"diff-so-fancy --patch\\\"\\n\";\n\n\treturn $out;\n}\n\nsub get_default_colors {\n\tmy $out  = \"# Recommended default colors for diff-so-fancy\\n\";\n\t$out    .= \"# --------------------------------------------\\n\";\n\t$out    .= 'git config --global color.ui true\n\ngit config --global color.diff-highlight.oldNormal    \"red bold\"\ngit config --global color.diff-highlight.oldHighlight \"red bold 52\"\ngit config --global color.diff-highlight.newNormal    \"green bold\"\ngit config --global color.diff-highlight.newHighlight \"green bold 22\"\n\ngit config --global color.diff.meta       \"yellow\"\ngit config --global color.diff.frag       \"magenta bold\"\ngit config --global color.diff.commit     \"yellow bold\"\ngit config --global color.diff.old        \"red bold\"\ngit config --global color.diff.new        \"green bold\"\ngit config --global color.diff.whitespace \"red reverse\"\n';\n\n\treturn $out;\n}\n\n# Output the current version string\nsub version {\n\tmy $ret  = \"Diff-so-fancy: https://github.com/so-fancy/diff-so-fancy\\n\";\n\t$ret    .= \"Version      : $VERSION\\n\";\n\n\treturn $ret;\n}\n\nsub is_windows {\n\tif ($^O eq 'MSWin32' or $^O eq 'dos' or $^O eq 'os2' or $^O eq 'cygwin' or $^O eq 'msys') {\n\t\treturn 1;\n\t} else {\n\t\treturn 0;\n\t}\n}\n\nsub set_defaults {\n\tmy $color_config = get_default_colors();\n\tmy $git_config   = 'git config --global core.pager \"diff-so-fancy | less --tabs=4 -RFX\"';\n\tmy $first_cmd    = 'git config --global diff-so-fancy.first-run false';\n\n\tmy @cmds = split(/\\n/,$color_config);\n\tpush(@cmds,$git_config);\n\tpush(@cmds,$first_cmd);\n\n\t# Remove all comments from the commands\n\tforeach my $x (@cmds) {\n\t\t$x =~ s/#.*//g;\n\t}\n\n\t# Remove any empty commands\n\t@cmds = grep($_,@cmds);\n\n\tforeach my $cmd (@cmds) {\n\t\tsystem($cmd);\n\t\tmy $exit = ($? >> 8);\n\n\t\tif ($exit != 0) {\n\t\t\tdie(\"Error running: '$cmd' (error #18941)\\n\");\n\t\t}\n\t}\n\n\treturn 1;\n}\n\n# Borrowed from: https://www.perturb.org/display/1167_Perl_ANSI_colors.html\n# String format: '115', '165_bold', '10_on_140', 'reset', 'on_173', 'red', 'white_on_blue'\nsub color {\n\tmy ($str, $txt) = @_;\n\n\t# If we're NOT connected to a an interactive terminal don't do color\n\t#if (-t STDOUT == 0) { return ''; }\n\n\t# No string sent in, so we just reset\n\tif (!length($str) || $str eq 'reset') { return \"\\e[0m\"; }\n\n\t# Some predefined colors\n\tmy %color_map = qw(red 160 blue 27 green 34 yellow 226 orange 214 purple 93 white 15 black 0);\n\t$str =~ s|([A-Za-z]+)|$color_map{$1} // $1|eg;\n\n\t# Get foreground/background and any commands\n\tmy ($fc,$cmd) = $str =~ /^(\\d{1,3})?_?(\\w+)?$/g;\n\tmy ($bc)      = $str =~ /on_(\\d{1,3})$/g;\n\n\t# Some predefined commands\n\tmy %cmd_map = qw(bold 1 italic 3 underline 4 blink 5 inverse 7);\n\tmy $cmd_num = $cmd_map{$cmd // 0};\n\n\tmy $ret = '';\n\tif ($cmd_num)     { $ret .= \"\\e[${cmd_num}m\"; }\n\tif (defined($fc)) { $ret .= \"\\e[38;5;${fc}m\"; }\n\tif (defined($bc)) { $ret .= \"\\e[48;5;${bc}m\"; }\n\tif ($txt)         { $ret .= $txt . \"\\e[0m\";   }\n\n\treturn $ret;\n}\n\n# Get colors used for various output sections (memoized)\nsub get_config_color {\n\tmy $str = shift();\n\n\tstate $static_config;\n\n\tmy $ret = \"\";\n\tif ($static_config->{$str}) {\n\t\treturn $static_config->{$str};\n\t}\n\n\t#print color(15) . \"Shelling out for color: '$str'\\n\" . color('reset');\n\n\tif ($str eq \"meta\") {\n\t\t# Default ANSI yellow\n\t\t$ret = git_ansi_color(git_config('color.diff.meta')) || color(11);\n\t} elsif ($str eq \"reset\") {\n\t\t$ret = color(\"reset\");\n\t} elsif ($str eq \"add_line\") {\n\t\t# Default ANSI green\n\t\t$ret = git_ansi_color(git_config('color.diff.new')) || color(\"2_bold\");\n\t} elsif ($str eq \"remove_line\") {\n\t\t# Default ANSI red\n\t\t$ret = git_ansi_color(git_config('color.diff.old')) || color(\"1_bold\");\n\t} elsif ($str eq \"fragment\") {\n\t\t$ret = git_ansi_color(git_config('color.diff.frag')) || color(\"13_bold\");\n\t} elsif ($str eq \"last_function\") {\n\t\t$ret = git_ansi_color(git_config('color.diff.func')) || color(\"146_bold\");\n\t}\n\n\t# Cache (memoize) the entry for later\n\t$static_config->{$str} = $ret;\n\n\treturn $ret;\n}\n\n# https://www.git-scm.com/book/en/v2/Customizing-Git-Git-Configuration#_colors_in_git\nsub git_ansi_color {\n\tmy $str   = shift();\n\tmy @parts = split(' ', $str);\n\n\tif (!@parts) {\n\t\treturn '';\n\t}\n\n\tmy $colors = {\n\t\t'black'   => 0,\n\t\t'red'     => 1,\n\t\t'green'   => 2,\n\t\t'yellow'  => 3,\n\t\t'blue'    => 4,\n\t\t'magenta' => 5,\n\t\t'cyan'    => 6,\n\t\t'white'   => 7,\n\t\t'default' => 9, # pseudo color (39/49 = set default)\n\t\t'normal'  => -1, # placeholder color to be ignored\n\t};\n\n\t# Bright colors are just offsets from the \"regular\" color\n\tfor my $k (keys %{ $colors }) {\n\t\t$colors->{\"bright\" . $k} = $colors->{$k} + 60;\n\t}\n\n\tmy @ansi_part = ();\n\n\tif (grep { /^bold$/ } @parts) {\n\t\tpush(@ansi_part, \"1\");\n\t}\n\n\tif (grep { /^dim$/ } @parts) {\n\t\tpush(@ansi_part, \"2\");\n\t}\n\n\tif (grep { /^ul$/ } @parts) {\n\t\tpush(@ansi_part, \"4\");\n\t}\n\n\tif (grep { /^reverse$/ } @parts) {\n\t\tpush(@ansi_part, \"7\");\n\t}\n\n\t# Remove parts that aren't colors\n\t@parts = grep { exists $colors->{$_} || is_numeric($_) || /^\\#/ } @parts;\n\n\tmy $fg  = $parts[0] // \"\";\n\tmy $bg  = $parts[1] // \"\";\n\n\tset_ansi_color($fg, 0 , \\@ansi_part, $colors) if $fg;\n\tset_ansi_color($bg, 10, \\@ansi_part, $colors) if $bg;\n\n\t#############################################\n\n\tmy $ansi_str = join(\";\", @ansi_part);\n\tmy $ret      = \"\\e[\" . $ansi_str . \"m\";\n\n\treturn $ret;\n}\n\nsub set_ansi_color {\n\tmy ($color, $increment, $ansi_part, $colors) = @_;\n\n\tmy $base_code  = 30 + $increment;\n\tmy $base8_code = 38 + $increment;\n\tmy $ext_code   = 82 + $increment;\n\n\tif (is_numeric($color)) {\n\t\tif ($color < 8) {\n\t\t\tpush(@$ansi_part, $color + $base_code);\n\t\t} elsif ($color < 16) {\n\t\t\tpush(@$ansi_part, $color + $ext_code);\n\t\t} else {\n\t\t\tpush(@$ansi_part, \"$base8_code;5;$color\");\n\t\t}\n\t# It's a full rgb code\n\t} elsif ($color =~ /^#/) {\n\t\tmy ($rgbr, $rgbg, $rgbb) = $color =~ /.(..)(..)(..)/;\n\t\tpush(@$ansi_part, \"$base8_code;2;\" . hex($rgbr) . \";\" . hex($rgbg) . \";\" . hex($rgbb));\n\t# It's a simple 16 color OG ansi\n\t} elsif ($color ne \"normal\") {\n\t\tmy $color_num = $colors->{$color} + $base_code;\n\t\tpush(@$ansi_part, $color_num);\n\t}\n}\n\n# Is the string 100% numeric\nsub is_numeric {\n\tmy $s = shift();\n\n\tif ($s =~ /^\\d+$/) {\n\t\treturn 1;\n\t}\n\n\treturn 0;\n}\n\n# Does the string start with ANSI\nsub starts_with_ansi {\n\tmy $str = shift();\n\n\t# NOTE: This is not `ansi_color_regex`, which includes \"no ANSI sequences\".\n\tif ($str =~ /^$ansi_regex/) {\n\t\treturn 1;\n\t} else {\n\t\treturn 0;\n\t}\n}\n\nsub get_terminal_width {\n\t# Make width static so we only calculate it once\n\tstate $width;\n\n\tif ($width) {\n\t\treturn $width;\n\t}\n\n\t# If there is a ruler width in the config we use that\n\tif ($ruler_width) {\n\t\t$width = $ruler_width;\n\t# Otherwise we check the terminal width using tput\n\t} else {\n\t\tmy $tput = `tput cols`;\n\n\t\tif ($tput) {\n\t\t\t$width = int($tput);\n\n\t\t\tif (is_windows()) {\n\t\t\t\t$width--;\n\t\t\t}\n\t\t} else {\n\t\t\tprint color('orange') . \"Warning: `tput cols` did not return numeric input\" . color('reset') . \"\\n\";\n\t\t\t$width = 80;\n\t\t}\n\t}\n\n\treturn $width;\n}\n\nsub show_debug_info {\n\tmy @less    = get_less_charset();\n\tmy $git_ver = trim(`git --version 2>&1`);\n\t$git_ver    =~ s/[^\\d.]//g;\n\n\tif ($git_ver !~ /git/) {\n\t\t$git_ver = \"Unknown\";\n\t} else {\n\t\t$git_ver = \"v\" . $git_ver;\n\t}\n\n\tmy $out .= \"Diff-so-fancy   : v$VERSION\\n\";\n\t$out    .= \"Git             : $git_ver\\n\";\n\t$out    .= \"Perl            : $^V\\n\";\n\t$out    .= \"\\n\";\n\t$out    .= \"Terminal width  : \" . get_terminal_width() . \"\\n\";\n\t$out    .= \"Terminal \\$LANG  : \" . ($ENV{LANG} || \"\") . \"\\n\";\n\t$out    .= \"\\n\";\n\t$out    .= \"Supports Unicode: \" . yes_no(should_print_unicode()) . \"\\n\";\n\t$out    .= \"Unicode Ruler   : \" . yes_no($use_unicode_dash_for_ruler) . \"\\n\";\n\t$out    .= \"\\n\";\n\t$out    .= \"Less Charset Var: \" . ($less[0] // \"\") . \"\\n\";\n\t$out    .= \"Less Charset    : \" . ($less[1] // \"\") . \"\\n\";\n\t$out    .= \"\\n\";\n\t$out    .= \"Is Windows      : \" . yes_no(is_windows()) . \"\\n\";\n\t$out    .= \"Operating System: $^O\\n\";\n\n\tprint $out;\n\n\tmy @lines = split(/\\n/, $out);\n\tdebug_log(@lines);\n}\n\n# Boolean to yes/no string\nsub yes_no {\n\tmy $val = shift();\n\n\tif ($val) {\n\t\treturn \"Yes\";\n\t} else {\n\t\treturn \"No\";\n\t}\n}\n\n# If there are colors set in the gitconfig use those, otherwise leave the defaults\nsub init_diff_highlight_colors {\n\t$DiffHighlight::NEW_HIGHLIGHT[0] = git_ansi_color(git_config('color.diff-highlight.newnormal'))    || $DiffHighlight::NEW_HIGHLIGHT[0];\n\t$DiffHighlight::NEW_HIGHLIGHT[1] = git_ansi_color(git_config('color.diff-highlight.newhighlight')) || $DiffHighlight::NEW_HIGHLIGHT[1];\n\t$DiffHighlight::NEW_HIGHLIGHT[2] = git_ansi_color(git_config('color.diff-highlight.newreset'))     || \"\\e[0m\";\n\n\t$DiffHighlight::OLD_HIGHLIGHT[0] = git_ansi_color(git_config('color.diff-highlight.oldnormal'))    || $DiffHighlight::OLD_HIGHLIGHT[0];\n\t$DiffHighlight::OLD_HIGHLIGHT[1] = git_ansi_color(git_config('color.diff-highlight.oldhighlight')) || $DiffHighlight::OLD_HIGHLIGHT[1];\n\t$DiffHighlight::NEW_HIGHLIGHT[2] = git_ansi_color(git_config('color.diff-highlight.oldreset'))     || \"\\e[0m\";\n}\n\n# Human readable datetime string with milliseconds\nsub date_str {\n\tuse Time::HiRes qw(gettimeofday);\n\n\tmy ($sec, $usec) = gettimeofday();\n\tmy @t            = localtime($sec);\n\n\tmy $ret = sprintf(\n        \"%04d-%02d-%02d %02d:%02d:%02d.%03d\",\n        $t[5] + 1900,\n        $t[4] + 1,\n        $t[3],\n        $t[2],\n        $t[1],\n        $t[0],\n        int($usec / 1000),\n    );\n\n\treturn $ret;\n}\n\n# Write a line to the debug log which is opened on the fly as-needed\n# Usage: debug_log($str) or debug_log(@lines)\nsub debug_log {\n\tmy @lines = @_;\n\tmy $file     = \"/tmp/diff-so-fancy.debug.log\";\n\tmy $date_str = date_str();\n\n\tstate $fh;\n\tif (!$fh) {\n\t\tprintf(\"%sDebug log enabled:%s $file\\n\", color('orange'), color());\n\t\topen ($fh, \">\", $file) or die(\"Cannot write to $file\");\n\t}\n\n\tforeach my $log_line (@lines) {\n\t\t$log_line = trim($log_line);\n\t\tprint $fh \"$date_str: $log_line\\n\";\n\t}\n\n\treturn 1;\n}\n\n# Find the commom prefix chars between two strings\n# common_prefix(\"foobar\", \"food is yummy\"); # 'foo'\nsub common_prefix {\n\tmy ($a, $b) = @_;\n\tmy $len = length($a) < length($b) ? length($a) : length($b);\n\n\tmy $i = 0;\n\t$i++ while $i < $len && substr($a,$i,1) eq substr($b,$i,1);\n\n\treturn substr($a, 0, $i);\n}\n\n# Count the number of context lines in the diff\nsub calculate_context_lines {\n\tmy @lines     = @_;\n\tmy $count     = 0;\n\tmy $hunk_line = 0;\n\n\t# Count the number of lines between the hunk line and the\n\t# first + or - line\n\tforeach my $line (@lines) {\n\t\t# Look for the hunk line before we start\n\t\tif ($line =~ /^${ansi_color_regex}(@@@* .+? @@@*)(.*)/) {\n\t\t\t$hunk_line = $count;\n\t\t} elsif ($hunk_line && $line =~ /^${ansi_color_regex}[+-]/) {\n\t\t\tmy $diff = $count - $hunk_line - 1;\n\t\t\t#print \"Hunk: $hunk_line, ChangedLine: $count ($diff context)\\n\";\n\t\t\treturn $diff;\n\t\t}\n\n\t\t$count++;\n\t};\n\n\t# If for some reason we can't figure it out, assume 3\n\treturn 3;\n}\n\n# This function divides Git/Diff strings into smaller chunks that d-s-f can\n# process. The special sauce is not splitting on the Git headers, or any of\n# the lines that require context awareness of the next two lines\nsub get_diff_chunks {\n\tmy ($line_ref) = @_;\n\n\tmy @lines       = @$line_ref;\n\tmy @end_lines   = (); # Array of line numbers of the END of each chunk\n\tmy $total_lines = scalar(@lines);\n\n\t# Loop through all the lines and build end line numbers for each \"chunk\"\n\tfor (my $i = 0; $i < $total_lines; $i++) {\n\t\tmy $line = $lines[$i];\n\n\t\tif (($i > 0) && $line =~ /^${ansi_color_regex}diff/) {\n\t\t\tpush(@end_lines, $i - 1);\n\t\t}\n\t}\n\n\t# The final end point is the last line\n\tmy $last_idx = $end_lines[-1] || 0;\n\tif ($last_idx != ($total_lines - 1)) {\n\t\tpush(@end_lines, $total_lines - 1);\n\t}\n\n\t# Now that we know where all the end points are we divide the big array\n\t# into smaller chunks\n\tmy @ret  = ();\n\tmy $prev = 0;\n\tforeach my $idx (@end_lines) {\n\t\tif ($idx > 0) {\n\t\t\t# Array splice the lines into a smaller chuck and put them in @ret\n\t\t\tmy @tmp = @lines[$prev .. $idx];\n\t\t\tpush(@ret, \\@tmp);\n\t\t\t$prev = $idx + 1;\n\t\t}\n\t}\n\n\treturn @ret;\n}\n\n# Log each chunk of diff for debugging purposes\nsub log_chunks {\n\tmy $line_ref = shift();\n\n\tdebug_log(\"-------------- Chunk --------------\");\n\tdebug_log(@$line_ref);\n}\n\n# In patch mode we have to keep the line ratio 1:1 so we need to know how many\n# header lines there are. Usually three or four.\nsub count_git_header_lines {\n\tmy @lines = @_;\n\tmy $ret   = 0;\n\n\t# If we're not in --patch mode then bail out early\n\tif (!$args->{patch} && !$ENV{DSF_PATCH_MODE}) {\n\t\treturn 0;\n\t}\n\n\t# Textual diff should be four lines of header\n\t#diff --git a/diff-so-fancy b/diff-so-fancy\n\t#index d952098..424d8a3 100755\n\t#--- a/diff-so-fancy\n\t#+++ b/diff-so-fancy\n\n\t# Binary diff should be three lines of header\n\t#diff --git a/file.txt b/file.txt\n\t#new file mode 100644\n\t#index 0000000..e69de29\n\n\t#diff --git a/hello.txt b/hello.txt\n\t#new file mode 100644\n\t#index 0000000..0c767bc\n\t#--- /dev/null\n\t#+++ b/hello.txt\n\n\tmy $count = 1;\n\tforeach my $line (@lines) {\n\t\tif ($line =~ /^${ansi_color_regex}index/) {\n\t\t\tlast;\n\t\t}\n\n\t\t$count++;\n\t}\n\n\tif ($count == 2) {\n\t\t$count = 4;\n\t}\n\n\t$ret = $count;\n\n\t# No Git/Diff header should be more than four lines\n\tif ($ret > 4) {\n\t\t$ret = 0;\n\t}\n\n\treturn $ret;\n}\n\n# Borrowed from: https://www.perturb.org/display/1097_Perl_detect_if_a_module_is_installed_before_using_it.html\n# Creates methods k() and kd() to print, and print & die respectively\nBEGIN {\n\tif (!defined(&trim)) {\n\t\t*trim = sub {\n\t\t\tmy ($s) = (@_, $_); # Passed in var, or default to $_\n\t\t\tif (length($s) == 0) { return \"\"; }\n\t\t\t$s =~ s/^\\s*//;\n\t\t\t$s =~ s/\\s*$//;\n\n\t\t\treturn $s;\n\t\t}\n\t}\n\n\tif ($ENV{USER} eq \"bakers\") {\n\t\trequire Dump::Krumo;\n\t\tDump::Krumo->import(qw(k kd));\n\t}\n}\n\n################################################################################\n\n=pod\n\n=encoding utf8\n\n=head1 NAME\n\ndiff-so-fancy makes your diffs human readable instead of machine readable. This\nhelps improve code quality and helps you spot defects faster.\n\n=head1 USAGE\n\n=head2 Git\n\nConfigure git to use C<diff-so-fancy> for all diff output:\n\n    git config --global core.pager \"diff-so-fancy | less --tabs=4 -RF\"\n    git config --global interactive.diffFilter \"diff-so-fancy --patch\"\n\n=head2 Diff\n\nUse C<-u> with diff for unified output, and pipe the output to C<diff-so-fancy>:\n\n    diff -u file_a file_b | diff-so-fancy\n\nWe also support recursive mode with C<-r> or C<--recursive>\n\n    diff --recursive -u /path/folder_a /path/folder_b | diff-so-fancy\n\n=head1 OPTIONS\n\nB<markEmptyLines:> Colorize the first block of an empty line. (Default: true)\n\n    git config --bool --global diff-so-fancy.markEmptyLines false\n\nB<changeHunkIndicators:> Simplify Git header chunks to a human readable format. (Default: true)\n\n    git config --bool --global diff-so-fancy.changeHunkIndicators false\n\nB<stripLeadingSymbols:> Should the + or - symbols at line-start be removed. (Default: true)\n\n    git config --bool --global diff-so-fancy.stripLeadingSymbols false\n\nB<useUnicodeRuler:> By default, the separator for the file header uses Unicode line-drawing\ncharacters. If this is causing output errors on your terminal, set this to false to use ASCII\ncharacters instead. (Default: true)\n\n    git config --bool --global diff-so-fancy.useUnicodeRuler false\n\nB<rulerWidth:> By default, the separator for the file header spans the full width of the\nterminal. Use rulerWidth to set the width of the file header manually.\n\n    git config --global diff-so-fancy.rulerWidth 80\n\n=head1 HOMEPAGE\n\n- https://github.com/so-fancy/diff-so-fancy\n\n=head1 SEE ALSO\n\n=head2 Delta\n\n- https://github.com/dandavison/delta\n\n=head2 Lazygit with diff-so-fancy integration\n\n- https://github.com/jesseduffield/lazygit/blob/master/docs/Custom_Pagers.md#diff-so-fancy\n\n# vim: tabstop=4 shiftwidth=4 noexpandtab autoindent softtabstop=4\n"
  },
  {
    "path": "diff-so-fancy.plugin.zsh",
    "content": "# Add our plugin diretory to user's path\n#\n# See following web page for explanation of the line \"ZERO=...\":\n# https://zdharma-continuum.github.io/Zsh-100-Commits-Club/Zsh-Plugin-Standard.html\n\n0=\"${ZERO:-${${0:#$ZSH_ARGZERO}:-${(%):-%N}}}\"\n0=\"${${(M)0:#/*}:-$PWD/$0}\"\nlocal diff_so_fancy_bin=\"${0:h}\"\n\nif [[ -z \"${path[(r)${diff_so_fancy_bin}]}\" ]]; then\n    path+=( \"${diff_so_fancy_bin}\" )\nfi"
  },
  {
    "path": "docs/diff-so-fancy.1",
    "content": ".\\\" -*- mode: troff; coding: utf-8 -*-\n.\\\" Automatically generated by Pod::Man 5.0102 (Pod::Simple 3.45)\n.\\\"\n.\\\" Standard preamble:\n.\\\" ========================================================================\n.de Sp \\\" Vertical space (when we can't use .PP)\n.if t .sp .5v\n.if n .sp\n..\n.de Vb \\\" Begin verbatim text\n.ft CW\n.nf\n.ne \\\\$1\n..\n.de Ve \\\" End verbatim text\n.ft R\n.fi\n..\n.\\\" \\*(C` and \\*(C' are quotes in nroff, nothing in troff, for use with C<>.\n.ie n \\{\\\n.    ds C` \"\"\n.    ds C' \"\"\n'br\\}\n.el\\{\\\n.    ds C`\n.    ds C'\n'br\\}\n.\\\"\n.\\\" Escape single quotes in literal strings from groff's Unicode transform.\n.ie \\n(.g .ds Aq \\(aq\n.el       .ds Aq '\n.\\\"\n.\\\" If the F register is >0, we'll generate index entries on stderr for\n.\\\" titles (.TH), headers (.SH), subsections (.SS), items (.Ip), and index\n.\\\" entries marked with X<> in POD.  Of course, you'll have to process the\n.\\\" output yourself in some meaningful fashion.\n.\\\"\n.\\\" Avoid warning from groff about undefined register 'F'.\n.de IX\n..\n.nr rF 0\n.if \\n(.g .if rF .nr rF 1\n.if (\\n(rF:(\\n(.g==0)) \\{\\\n.    if \\nF \\{\\\n.        de IX\n.        tm Index:\\\\$1\\t\\\\n%\\t\"\\\\$2\"\n..\n.        if !\\nF==2 \\{\\\n.            nr % 0\n.            nr F 2\n.        \\}\n.    \\}\n.\\}\n.rr rF\n.\\\" ========================================================================\n.\\\"\n.IX Title \"DIFF-SO-FANCY 1\"\n.TH DIFF-SO-FANCY 1 2026-03-18 \"perl v5.40.2\" \"User Contributed Perl Documentation\"\n.\\\" For nroff, turn off justification.  Always turn off hyphenation; it makes\n.\\\" way too many mistakes in technical documents.\n.if n .ad l\n.nh\n.SH NAME\ndiff\\-so\\-fancy makes your diffs human readable instead of machine readable. This\nhelps improve code quality and helps you spot defects faster.\n.SH USAGE\n.IX Header \"USAGE\"\n.SS Git\n.IX Subsection \"Git\"\nConfigure git to use \\f(CW\\*(C`diff\\-so\\-fancy\\*(C'\\fR for all diff output:\n.PP\n.Vb 2\n\\&    git config \\-\\-global core.pager \"diff\\-so\\-fancy | less \\-\\-tabs=4 \\-RF\"\n\\&    git config \\-\\-global interactive.diffFilter \"diff\\-so\\-fancy \\-\\-patch\"\n.Ve\n.SS Diff\n.IX Subsection \"Diff\"\nUse \\f(CW\\*(C`\\-u\\*(C'\\fR with diff for unified output, and pipe the output to \\f(CW\\*(C`diff\\-so\\-fancy\\*(C'\\fR:\n.PP\n.Vb 1\n\\&    diff \\-u file_a file_b | diff\\-so\\-fancy\n.Ve\n.PP\nWe also support recursive mode with \\f(CW\\*(C`\\-r\\*(C'\\fR or \\f(CW\\*(C`\\-\\-recursive\\*(C'\\fR\n.PP\n.Vb 1\n\\&    diff \\-\\-recursive \\-u /path/folder_a /path/folder_b | diff\\-so\\-fancy\n.Ve\n.SH OPTIONS\n.IX Header \"OPTIONS\"\n\\&\\fBmarkEmptyLines:\\fR Colorize the first block of an empty line. (Default: true)\n.PP\n.Vb 1\n\\&    git config \\-\\-bool \\-\\-global diff\\-so\\-fancy.markEmptyLines false\n.Ve\n.PP\n\\&\\fBchangeHunkIndicators:\\fR Simplify Git header chunks to a human readable format. (Default: true)\n.PP\n.Vb 1\n\\&    git config \\-\\-bool \\-\\-global diff\\-so\\-fancy.changeHunkIndicators false\n.Ve\n.PP\n\\&\\fBstripLeadingSymbols:\\fR Should the + or \\- symbols at line-start be removed. (Default: true)\n.PP\n.Vb 1\n\\&    git config \\-\\-bool \\-\\-global diff\\-so\\-fancy.stripLeadingSymbols false\n.Ve\n.PP\n\\&\\fBuseUnicodeRuler:\\fR By default, the separator for the file header uses Unicode line-drawing\ncharacters. If this is causing output errors on your terminal, set this to false to use ASCII\ncharacters instead. (Default: true)\n.PP\n.Vb 1\n\\&    git config \\-\\-bool \\-\\-global diff\\-so\\-fancy.useUnicodeRuler false\n.Ve\n.PP\n\\&\\fBrulerWidth:\\fR By default, the separator for the file header spans the full width of the\nterminal. Use rulerWidth to set the width of the file header manually.\n.PP\n.Vb 1\n\\&    git config \\-\\-global diff\\-so\\-fancy.rulerWidth 80\n.Ve\n.SH HOMEPAGE\n.IX Header \"HOMEPAGE\"\n\\&\\- https://github.com/so\\-fancy/diff\\-so\\-fancy\n.SH \"SEE ALSO\"\n.IX Header \"SEE ALSO\"\n.SS Delta\n.IX Subsection \"Delta\"\n\\&\\- https://github.com/dandavison/delta\n.SS \"Lazygit with diff-so-fancy integration\"\n.IX Subsection \"Lazygit with diff-so-fancy integration\"\n\\&\\- https://github.com/jesseduffield/lazygit/blob/master/docs/Custom_Pagers.md#diff\\-so\\-fancy\n.PP\n# vim: tabstop=4 shiftwidth=4 noexpandtab autoindent softtabstop=4\n"
  },
  {
    "path": "hacking-and-testing.md",
    "content": "### Hacking\n\n```sh\n# fork and clone the diff-so-fancy repo.\ngit clone https://github.com/so-fancy/diff-so-fancy/ && cd diff-so-fancy\n\n# test a saved diff against your local version\ncat test/fixtures/ls-function.diff | ./diff-so-fancy\n\n# setup symlinks to use local copy\nnpm link\ncd ~/projects/catfabulator && git diff\n```\n\n### Running tests\n\nThe tests use [bats-core](https://bats-core.readthedocs.io/en/latest/index.html), the Bash automated testing system.\n\n```sh\n# initialize the bats components\ngit submodule sync && git submodule update --init\n\n# run the test suite once:\n./test/bats/bin/bats test\n\n# run it on every change with `entr`\nbrew install entr\nfind  ./* test/* test/fixtures/* -maxdepth 0 | entr ./test/bats/bin/bats test\n```\n\nWhen writing assertions, you'll likely want to compare to expected output. To grab that reliably, you can use something like `git --no-pager diff | ./diff-so-fancy > output.txt`\n\n\n### Publishing to npm\n\nRun these one-by-one, manually.\n\n```sh\ndiff-so-fancy --version # see the old version (probably)\n\nnpm run build  # build the fatpack into dist.\n\n./dist/diff-so-fancy --version # get latest version from perl script\n\nnpm version vX.X.X   # bump package.json to match.\n\nnpm uninstall -g diff-so-fancy && npm link  # make global symlink, if not already present\n\ndiff-so-fancy --version  # ensure latest version is shown\n\ngit show | diff-so-fancy  # ensure it works\n\nnpm publish --dry-run # ensure listed files are what you want published. update .npmignore as desired\n\n# npm login --registry https://registry.npmjs.org/  # maybe.\n\nnpm publish --registry https://registry.npmjs.org/\n```\n\n"
  },
  {
    "path": "history.md",
    "content": "## History\n\n`diff-so-fancy` started as [a commit in paulirish's dotfiles](https://github.com/paulirish/dotfiles/commit/6743b907ff586c28cd36e08d1e1c634e2968893e#commitcomment-13349456), which grew into a [standalone script](https://github.com/paulirish/dotfiles/blob/63cb8193b0e66cf80ab6332477f1f52c7fbb9311/bin/diff-so-fancy). Later, [@stevemao](https://github.com/stevemao) brought it into its [own repo](https://github.com/so-fancy/diff-so-fancy) (here), and gave it the room to mature. It's quickly grown into a [widely collaborative project](https://github.com/so-fancy/diff-so-fancy/graphs/contributors).\n"
  },
  {
    "path": "lib/DiffHighlight.pm",
    "content": "package DiffHighlight;\n\nuse 5.008;\nuse warnings FATAL => 'all';\nuse strict;\n\n# Use the correct value for both UNIX and Windows (/dev/null vs nul)\nuse File::Spec;\n\nmy $NULL = File::Spec->devnull();\n\n# Highlight by reversing foreground and background. You could do\n# other things like bold or underline if you prefer.\nour @OLD_HIGHLIGHT = (\n\tundef,\n\t\"\\e[7m\",\n\t\"\\e[27m\",\n);\nour @NEW_HIGHLIGHT = (\n\t$OLD_HIGHLIGHT[0],\n\t$OLD_HIGHLIGHT[1],\n\t$OLD_HIGHLIGHT[2],\n);\n\nmy $RESET = \"\\x1b[m\";\nmy $COLOR = qr/\\x1b\\[[0-9;]*m/;\nmy $BORING = qr/$COLOR|\\s/;\n\nmy @removed;\nmy @added;\nmy $in_hunk;\nmy $graph_indent = 0;\n\nour $line_cb = sub { print @_ };\nour $flush_cb = sub { local $| = 1 };\n\n# Count the visible width of a string, excluding any terminal color sequences.\nsub visible_width {\n\tlocal $_ = shift;\n\tmy $ret = 0;\n\twhile (length) {\n\t\tif (s/^$COLOR//) {\n\t\t\t# skip colors\n\t\t} elsif (s/^.//) {\n\t\t\t$ret++;\n\t\t}\n\t}\n\treturn $ret;\n}\n\n# Return a substring of $str, omitting $len visible characters from the\n# beginning, where terminal color sequences do not count as visible.\nsub visible_substr {\n\tmy ($str, $len) = @_;\n\twhile ($len > 0) {\n\t\tif ($str =~ s/^$COLOR//) {\n\t\t\tnext\n\t\t}\n\t\t$str =~ s/^.//;\n\t\t$len--;\n\t}\n\treturn $str;\n}\n\nsub handle_line {\n\tmy $orig = shift;\n\tlocal $_ = $orig;\n\n\t# match a graph line that begins a commit\n\tif (/^(?:$COLOR?\\|$COLOR?[ ])* # zero or more leading \"|\" with space\n\t         $COLOR?\\*$COLOR?[ ]   # a \"*\" with its trailing space\n\t      (?:$COLOR?\\|$COLOR?[ ])* # zero or more trailing \"|\"\n\t                         [ ]*  # trailing whitespace for merges\n\t    /x) {\n\t\tmy $graph_prefix = $&;\n\n\t\t# We must flush before setting graph indent, since the\n\t\t# new commit may be indented differently from what we\n\t\t# queued.\n\t\tflush();\n\t\t$graph_indent = visible_width($graph_prefix);\n\n\t} elsif ($graph_indent) {\n\t\tif (length($_) < $graph_indent) {\n\t\t\t$graph_indent = 0;\n\t\t} else {\n\t\t\t$_ = visible_substr($_, $graph_indent);\n\t\t}\n\t}\n\n\tif (!$in_hunk) {\n\t\t$line_cb->($orig);\n\t\t$in_hunk = /^$COLOR*\\@\\@ /;\n\t}\n\telsif (/^$COLOR*-/) {\n\t\tpush @removed, $orig;\n\t}\n\telsif (/^$COLOR*\\+/) {\n\t\tpush @added, $orig;\n\t}\n\telse {\n\t\tflush();\n\t\t$line_cb->($orig);\n\t\t$in_hunk = /^$COLOR*[\\@ ]/;\n\t}\n\n\t# Most of the time there is enough output to keep things streaming,\n\t# but for something like \"git log -Sfoo\", you can get one early\n\t# commit and then many seconds of nothing. We want to show\n\t# that one commit as soon as possible.\n\t#\n\t# Since we can receive arbitrary input, there's no optimal\n\t# place to flush. Flushing on a blank line is a heuristic that\n\t# happens to match git-log output.\n\tif (/^$/) {\n\t\t$flush_cb->();\n\t}\n}\n\nsub flush {\n\t# Flush any queued hunk (this can happen when there is no trailing\n\t# context in the final diff of the input).\n\tshow_hunk(\\@removed, \\@added);\n\t@removed = ();\n\t@added = ();\n}\n\nsub highlight_stdin {\n\twhile (<STDIN>) {\n\t\thandle_line($_);\n\t}\n\tflush();\n}\n\n# Ideally we would feed the default as a human-readable color to\n# git-config as the fallback value. But diff-highlight does\n# not otherwise depend on git at all, and there are reports\n# of it being used in other settings. Let's handle our own\n# fallback, which means we will work even if git can't be run.\nsub color_config {\n\tmy ($key, $default) = @_;\n\tmy $s = `git config --get-color $key 2>$NULL`;\n\treturn length($s) ? $s : $default;\n}\n\nsub show_hunk {\n\tmy ($a, $b) = @_;\n\n\t# If one side is empty, then there is nothing to compare or highlight.\n\tif (!@$a || !@$b) {\n\t\t$line_cb->(@$a, @$b);\n\t\treturn;\n\t}\n\n\t# If we have mismatched numbers of lines on each side, we could try to\n\t# be clever and match up similar lines. But for now we are simple and\n\t# stupid, and only handle multi-line hunks that remove and add the same\n\t# number of lines.\n\tif (@$a != @$b) {\n\t\t$line_cb->(@$a, @$b);\n\t\treturn;\n\t}\n\n\tmy @queue;\n\tfor (my $i = 0; $i < @$a; $i++) {\n\t\tmy ($rm, $add) = highlight_pair($a->[$i], $b->[$i]);\n\t\t$line_cb->($rm);\n\t\tpush @queue, $add;\n\t}\n\t$line_cb->(@queue);\n}\n\nsub highlight_pair {\n\tmy @a = split_line(shift);\n\tmy @b = split_line(shift);\n\n\t# Find common prefix, taking care to skip any ansi\n\t# color codes.\n\tmy $seen_plusminus;\n\tmy ($pa, $pb) = (0, 0);\n\twhile ($pa < @a && $pb < @b) {\n\t\tif ($a[$pa] =~ /$COLOR/) {\n\t\t\t$pa++;\n\t\t}\n\t\telsif ($b[$pb] =~ /$COLOR/) {\n\t\t\t$pb++;\n\t\t}\n\t\telsif ($a[$pa] eq $b[$pb]) {\n\t\t\t$pa++;\n\t\t\t$pb++;\n\t\t}\n\t\telsif (!$seen_plusminus && $a[$pa] eq '-' && $b[$pb] eq '+') {\n\t\t\t$seen_plusminus = 1;\n\t\t\t$pa++;\n\t\t\t$pb++;\n\t\t}\n\t\telse {\n\t\t\tlast;\n\t\t}\n\t}\n\n\t# Find common suffix, ignoring colors.\n\tmy ($sa, $sb) = ($#a, $#b);\n\twhile ($sa >= $pa && $sb >= $pb) {\n\t\tif ($a[$sa] =~ /$COLOR/) {\n\t\t\t$sa--;\n\t\t}\n\t\telsif ($b[$sb] =~ /$COLOR/) {\n\t\t\t$sb--;\n\t\t}\n\t\telsif ($a[$sa] eq $b[$sb]) {\n\t\t\t$sa--;\n\t\t\t$sb--;\n\t\t}\n\t\telse {\n\t\t\tlast;\n\t\t}\n\t}\n\n\tif (is_pair_interesting(\\@a, $pa, $sa, \\@b, $pb, $sb)) {\n\t\treturn highlight_line(\\@a, $pa, $sa, \\@OLD_HIGHLIGHT),\n\t\t       highlight_line(\\@b, $pb, $sb, \\@NEW_HIGHLIGHT);\n\t}\n\telse {\n\t\treturn join('', @a),\n\t\t       join('', @b);\n\t}\n}\n\n# we split either by $COLOR or by character. This has the side effect of\n# leaving in graph cruft. It works because the graph cruft does not contain \"-\"\n# or \"+\"\nsub split_line {\n\tlocal $_ = shift;\n\treturn utf8::decode($_) ?\n\t\tmap { utf8::encode($_); $_ }\n\t\t\tmap { /$COLOR/ ? $_ : (split //) }\n\t\t\tsplit /($COLOR+)/ :\n\t\tmap { /$COLOR/ ? $_ : (split //) }\n\t\tsplit /($COLOR+)/;\n}\n\nsub highlight_line {\n\tmy ($line, $prefix, $suffix, $theme) = @_;\n\n\tmy $start = join('', @{$line}[0..($prefix-1)]);\n\tmy $mid = join('', @{$line}[$prefix..$suffix]);\n\tmy $end = join('', @{$line}[($suffix+1)..$#$line]);\n\n\t# If we have a \"normal\" color specified, then take over the whole line.\n\t# Otherwise, we try to just manipulate the highlighted bits.\n\tif (defined $theme->[0]) {\n\t\ts/$COLOR//g for ($start, $mid, $end);\n\t\tchomp $end;\n\t\tchomp $start;\n\t\treturn join('',\n\t\t\t$theme->[0], $start, $RESET,\n\t\t\t$theme->[1], $mid, $RESET,\n\t\t\t$theme->[0], $end, $RESET,\n\t\t\t\"\\n\"\n\t\t);\n\t} else {\n\t\treturn join('',\n\t\t\t$start,\n\t\t\t$theme->[1], $mid, $theme->[2],\n\t\t\t$end\n\t\t);\n\t}\n}\n\n# Pairs are interesting to highlight only if we are going to end up\n# highlighting a subset (i.e., not the whole line). Otherwise, the highlighting\n# is just useless noise. We can detect this by finding either a matching prefix\n# or suffix (disregarding boring bits like whitespace and colorization).\nsub is_pair_interesting {\n\tmy ($a, $pa, $sa, $b, $pb, $sb) = @_;\n\tmy $prefix_a = join('', @$a[0..($pa-1)]);\n\tmy $prefix_b = join('', @$b[0..($pb-1)]);\n\tmy $suffix_a = join('', @$a[($sa+1)..$#$a]);\n\tmy $suffix_b = join('', @$b[($sb+1)..$#$b]);\n\n\treturn visible_substr($prefix_a, $graph_indent) !~ /^$COLOR*-$BORING*$/ ||\n\t       visible_substr($prefix_b, $graph_indent) !~ /^$COLOR*\\+$BORING*$/ ||\n\t       $suffix_a !~ /^$BORING*$/ ||\n\t       $suffix_b !~ /^$BORING*$/;\n}\n"
  },
  {
    "path": "package.json",
    "content": "{\n  \"name\": \"diff-so-fancy\",\n  \"version\": \"1.4.6\",\n  \"description\": \"Good-lookin' diffs with diff-highlight and more\",\n  \"bin\": {\n    \"diff-so-fancy\": \"dist/diff-so-fancy\"\n  },\n  \"files\": [\n    \"dist/diff-so-fancy\"\n  ],\n  \"scripts\": {\n    \"test\": \"./test/bats/bin/bats test\",\n    \"build\": \"mkdir -p dist && PATH=\\\"$HOME/perl5/bin:$PATH\\\" PERL5LIB=\\\"$HOME/perl5/lib/perl5:$PERL5LIB\\\" perl ./third_party/build_fatpack/build.pl --output ./dist/diff-so-fancy\"\n  },\n  \"repository\": {\n    \"type\": \"git\",\n    \"url\": \"https://github.com/so-fancy/diff-so-fancy\"\n  },\n  \"keywords\": [\n    \"git\",\n    \"diff\",\n    \"fancy\",\n    \"good-lookin'\",\n    \"diff-highlight\",\n    \"color\",\n    \"readable\",\n    \"highlight\"\n  ],\n  \"author\": \"Paul Irish\",\n  \"license\": \"MIT\",\n  \"publishConfig\": {\n    \"registry\": \"https://registry.npmjs.org/\"\n  },\n  \"bugs\": {\n    \"url\": \"https://github.com/so-fancy/diff-so-fancy/issues\"\n  },\n  \"homepage\": \"https://github.com/so-fancy/diff-so-fancy#readme\"\n}\n"
  },
  {
    "path": "pro-tips.md",
    "content": "# Pro-tips\n\n## One-off fanciness or a specific diff-so-fancy alias\n\nYou can do also do a one-off:\n\n```shell\ngit diff --color | diff-so-fancy\n```\n\nor configure an alias and a corresponding pager to use `diff-so-fancy`:\n\n```shell\ngit config --global alias.dsf \"diff --color\"\ngit config --global pager.dsf \"diff-so-fancy | less --tabs=4 -RFXS\"\n```\n\n## Opting-out\n\nSometimes you will want to bypass diff-so-fancy. Use `--no-pager` for that:\n\n```shell\ngit --no-pager diff\n```\n\n## Raw patches\n\nAs a shortcut for a 'normal' diff to save as a patch for emailing or later\napplication, it may be helpful to configure an alias:\n\n```ini\n[alias]\n    patch = !git --no-pager diff --no-color\n```\n\nwhich can then be used as `git patch > changes.patch`.\n\n## Improved colors for the highlighted bits\n\nThe default Git colors are not optimal. The colors used for the screenshot were:\n\n```shell\ngit config --global color.ui true\n\ngit config --global color.diff-highlight.oldNormal    \"red bold\"\ngit config --global color.diff-highlight.oldHighlight \"red bold 52\"\ngit config --global color.diff-highlight.newNormal    \"green bold\"\ngit config --global color.diff-highlight.newHighlight \"green bold 22\"\n\ngit config --global color.diff.meta       \"11\"\ngit config --global color.diff.frag       \"magenta bold\"\ngit config --global color.diff.func       \"146 bold\"\ngit config --global color.diff.commit     \"yellow bold\"\ngit config --global color.diff.old        \"red bold\"\ngit config --global color.diff.new        \"green bold\"\ngit config --global color.diff.whitespace \"red reverse\"\n```\n\n#### Moving around in the diff\n\nYou can pre-seed your `less` pager with a search pattern so that you can move\nbetween files with `n`/`N` keys:\n\n```ini\n[pager]\n    diff = diff-so-fancy | less --tabs=4 -RFXS --pattern '^(Date|added|deleted|modified): '\n```\n\n## Zsh plugin suppport for diff-so-fancy\n\nThis project includes a `.plugin.zsh` file providing ZSH framework support, so you can use any framework that supports the ZSH plugin standard to install `diff-so-fancy` and add it to your `$PATH`. Installation with Zinit, Zplug, and Zgen:\n\n### Install with zinit\n\n```sh\nzinit ice lucid as\"program\" pick\"bin/git-dsf\"\nzinit load so-fancy/diff-so-fancy\n```\n\n### Install with zplug\n\n```sh\nzplug \"so-fancy/diff-so-fancy\", as:command, use:bin/git-dsf\n```\n\n### zgenom and others\n\n```sh\nzgenom load so-fancy/diff-so-fancy\n```\n\n## `hg` configuration\n\nYou can configure `hg diff` output to use `diff-so-fancy` by adding this alias\nto your `hgrc` file:\n\n```ini\n[alias]\n    diff = !HGPLAIN=1 $HG diff --pager=on --config pager.pager=diff-so-fancy $@\n```\n"
  },
  {
    "path": "report-bug.sh",
    "content": "#!/bin/bash\n\nclipboard() {\n\tlocal copy_cmd\n\n\tif [ -n \"$PBCOPY_SERVER\" ]; then\n\t\tlocal body=\"\" # buffer\n\t\tbody=$(cat)\n\t\t# while IFS= read -r buffer; do\n\t\t#   body=\"$body$buffer\\n\";\n\t\t# done\n\t\tcurl \"$PBCOPY_SERVER\" --data-urlencode body=\"$body\" >/dev/null 2>&1\n\t\treturn $?\n\tfi\n\n\tif type putclip >/dev/null 2>&1; then\n\t\tcopy_cmd=\"putclip\"\n\telif [ -e /dev/clipboard ];then\n\t\tcat > /dev/clipboard\n\t\treturn 0\n\telif type clip >/dev/null 2>&1; then\n\t\tif [[ $LANG = UTF-8 ]]; then\n\t\t\tcopy_cmd=\"iconv -f utf-8 -t shift_jis | clip\"\n\t\telse\n\t\t\tcopy_cmd=clip\n\t\tfi\n\t\t# copy_cmd=clip\n\telif command -v pbcopy >/dev/null 2>&1; then\n\t\tcopy_cmd=\"pbcopy\"\n\telif command -v xclip >/dev/null 2>&1; then\n\t\t# copy_cmd=\"xclip -i -selection clipboard\"\n\t\tcopy_cmd=\"xclip\"\n\telif command -v xsel >/dev/null 2>&1 ; then\n\t\tlocal copy_cmd=\"xsel -b\"\n\tfi\n\n\tif [ -n \"$copy_cmd\" ] ;then\n\t\teval \"$copy_cmd\"\n\telse\n\t\techo \"clipboard is unavailable\" 1>&2\n\tfi\n}\n\nif [ $# -eq 0 ]; then\n\techo \"Usage: $0 'git diff HEAD..HEAD^'\"\n\texit 7\nfi\n\nfile=${2:-dsf-bug-report.txt}\n\n{\n\techo \"$1\"\n\teval \"$1\"\n\techo \"\"\n\techo \"\"\n\techo \"\"\n\n\techo \"$1 --color\"\n\teval \"$1 --color\"\n\n\techo \"git config --list | grep pager\"\n\teval \"git config --list | grep pager\"\n} | base64 > \"$file\"\n\necho \"Wrote file: $file\"\necho \"Please open a new issue on Github and attach it\"\n"
  },
  {
    "path": "reporting-bugs.md",
    "content": "### Reporting bugs\n\nIf you find a bug using the following command\n\n```sh\ngit diff HEAD..HEAD^\n```\n\nYou can use [report-bug.sh](./report-bug.sh) we provide\n\n```sh\n./report-bug.sh 'git diff HEAD..HEAD^'\n```\n\nAttach the output file to the GitHub issue you create.\n"
  },
  {
    "path": "test/bugs.bats",
    "content": "#!/usr/bin/env bats\n\n# Used by both `setup_file` and `setup`, which are special bats callbacks.\n__load_imports__() {\n\tload 'test_helper/bats-support/load'\n\tload 'test_helper/bats-assert/load'\n\tload 'test_helper/util'\n}\n\nsetup_file() {\n\t__load_imports__\n\tsetup_default_dsf_git_config\n}\n\nsetup() {\n\t__load_imports__\n}\n\nteardown_file() {\n\tteardown_default_dsf_git_config\n}\n\n# https://github.com/paulirish/dotfiles/commit/6743b907ff586c28cd36e08d1e1c634e2968893e#commitcomment-13459061\n@test \"All removed lines are present in diff\" {\n  output=$( load_fixture \"chromium-modaltoelement\" | $diff_so_fancy )\n\trun printf \"%s\" \"$output\"\n\n  assert_line --index 7 --partial \"WebInspector.Dialog\"\n  assert_line --index 7 --partial \"5;52m\" # red oldhighlight\n  assert_line --index 8 --partial \"WebInspector.Dialog\"\n  assert_line --index 8 --partial \"5;22m\" # green newhighlight\n}\n\n@test \"File with space in the name (#360)\" {\n\toutput=$( load_fixture \"file_with_space\" | $diff_so_fancy )\n\trun printf \"%s\" \"$output\"\n\n\tassert_line --index 1 --regexp \"added:.*a b\"\n}\n\n@test \"Vanilla diff with add/remove empty lines (#366)\" {\n\toutput=$( load_fixture \"add_remove_empty_lines\" | $diff_so_fancy )\n\trun printf \"%s\" \"$output\"\n\n\tassert_line --index 5 --partial  \"5;22m\" # green added line\n\tassert_line --index 8 --partial  \"5;52m\" # red removed line\n}\n\n@test \"recursive vanilla diff -r -bu as Mercurial (#436)\" {\n\toutput=$( load_fixture \"recursive_default_as_mercurial\" | $diff_so_fancy )\n\trun printf \"%s\" \"$output\"\n\n\tassert_line --index 1 --partial \"renamed:\"\n\tassert_line --index 3 --partial \"@ language/app.py:1 @\"\n\tassert_line --index 19 --partial \"renamed:\"\n\tassert_line --index 21 --partial \"@ language/__init__.py:1 @\"\n\tassert_line --index 25 --partial \"renamed:\"\n\tassert_line --index 27 --partial \"@ language/README.md:1 @\"\n}\n\n@test \"recursive vanilla diff --recursive -u as Mercurial (#436)\" {\n\toutput=$( load_fixture \"recursive_longhand_as_mercurial\" | $diff_so_fancy )\n\trun printf \"%s\" \"$output\"\n\n    assert_output --regexp 'modified: app.py'\n    assert_output --regexp 'modified: __init__.py'\n    assert_output --regexp 'modified: README.md'\n}\n\n@test \"Functional part with bright color (#444)\" {\n  output=$( load_fixture \"move_with_content_change\" | $diff_so_fancy )\n  run printf \"%s\" \"$output\"\n  assert_line --index 3 --partial  \"@\u001b[0m\u001b[93m height\"\n}\n\n@test \"ANSI Reset without the zero (#469)\" {\n  output=$( load_fixture \"ansi_reset_no_number\" | $diff_so_fancy )\n  run printf \"%s\" \"$output\"\n  assert_line --index 5 --partial  \"History\"\n}\n\n@test \"File copy detection (#349)\" {\n  output=$( load_fixture \"file_copy\" | $diff_so_fancy )\n  run printf \"%s\" \"$output\"\n  assert_output --regexp 'Copied first_file to copied_file'\n}\n\n@test \"diff --recursive support (#394)\" {\n  output=$( load_fixture \"diff_recursive\" | $diff_so_fancy )\n  run printf \"%s\" \"$output\"\n  assert_output --regexp 'modified: foo/bar'\n  assert_output --regexp 'modified: index.txt'\n}\n\n@test \"Remove a \\n at the end of a file (#474)\" {\n  output=$( load_fixture \"remove_slashn_eof\" | $diff_so_fancy )\n  echo $output\n  run printf \"%s\" \"$output\"\n  #assert_output --regexp 'one'\n  assert_line --index 6 --regexp \"three\"\n  assert_line --index 7 --regexp \"three\"\n}\n"
  },
  {
    "path": "test/diff-so-fancy.bats",
    "content": "#!/usr/bin/env bats\n\n# Helper invoked by `setup_file` and `setup`, which are special bats callbacks.\n__load_imports__() {\n\tload 'test_helper/bats-support/load'\n\tload 'test_helper/bats-assert/load'\n\tload 'test_helper/util'\n}\n\nsetup_file() {\n\t__load_imports__\n\tset_env\n\tsetup_default_dsf_git_config\n\t# bats fails to handle our multiline result, so we save to $output ourselves\n\t__dsf_cached_output=\"$( load_fixture \"ls-function\" | $diff_so_fancy )\"\n\texport __dsf_cached_output\n}\n\nsetup() {\n\t__load_imports__\n\toutput=\"${__dsf_cached_output}\"\n}\n\nteardown_file() {\n\tteardown_default_dsf_git_config\n}\n\n@test \"diff-so-fancy runs and exits without error\" {\n\tload_fixture \"ls-function\" | $diff_so_fancy\n\trun assert_success\n}\n\n@test \"index line is removed entirely\" {\n\trefute_output --partial \"index 33c3d8b..fd54db2 100644\"\n}\n\n@test \"+/- line symbols are stripped\" {\n\trun printf \"%s\" \"$output\"\n\trefute_line --index 9 --regexp \"\\+    set -x CLICOLOR_FORCE 1\"\n\trefute_line --index 22 --regexp \"-    eval \\\"env CLICOLOR\"\n}\n\n@test \"+/- line symbols are stripped (truecolor)\" {\n  output=$( load_fixture \"truecolor\" | $diff_so_fancy )\n  refute_output --partial \"\n\u001b[1;38;2;220;50;47;48;2;0;43;54m-\"\n  refute_output --partial \"\n\u001b[1;38;2;133;153;0;48;2;0;43;54m+\"\n}\n\n@test \"empty lines added/removed are marked\" {\n\trun printf \"%s\" \"$output\"\n\n\tassert_line --index 7 --partial  \"\u001b[7m\u001b[1;32m \u001b[m\"\n\tassert_line --index 24 --partial \"\u001b[7m\u001b[1;31m \u001b[m\"\n\n\t#assert_output --partial \"\u001b[7m\u001b[1;32m \u001b[m\"\n\t#assert_output --partial \"\u001b[7m\u001b[1;31m \u001b[m\"\n}\n\n@test \"diff --git line is removed entirely\" {\n  # test against ls-function\n  refute_output --partial \"diff --git a/fish/functions/ls.fish\"\n  # test with git config diff.noprefix true\n  output=$( load_fixture \"noprefix\" | $diff_so_fancy )\n  refute_output --partial \"diff --git setup-a-new-machine.sh\"\n}\n\n@test \"header format uses a native line-drawing character\" {\n  header=$( load_fixture \"ls-function\" | $diff_so_fancy | head -n8 )\n  run printf \"%s\" \"$header\"\n  assert_line --index 0 --partial \"─────\"\n  assert_line --index 1 --partial \"modified: fish/functions/ls.fish\"\n  assert_line --index 2 --partial \"─────\"\n}\n\n# see https://git.io/vrOF4\n@test \"Should not show unicode bytes in hex if missing LC_*/LANG _and_ piping the output\" {\n  unset LESSCHARSET LESSCHARDEF LC_ALL LC_CTYPE LANG\n  # pipe to cat(1) so we don't open stdout\n  header=$( printf \"%s\" \"$(load_fixture \"ls-function\" | $diff_so_fancy | cat)\" | head -n8 )\n  run printf \"%s\" \"$header\"\n  assert_line --index 0 --partial \"-----\"\n  assert_line --index 1 --partial \"modified: fish/functions/ls.fish\"\n  assert_line --index 2 --partial \"-----\"\n  set_env # reset env\n}\n\n@test \"Leading dashes are not handled as modified\" {\n  output=$( load_fixture \"leading-dashes\" | $diff_so_fancy )\n  refute_output --partial \"modified: Callback\"\n}\n\n@test \"Handle binary modifications\" {\n  output=$( load_fixture \"binary-modified\" | $diff_so_fancy )\n  run printf \"%s\" \"$output\"\n  assert_line --index 1 --partial \"modified: cancel.png (binary)\";\n}\n\n@test \"Handle unicode characters in diff output\" {\n  output=$( load_fixture \"unicode\" | $diff_so_fancy )\n  run printf \"%s\" \"$output\"\n  assert_line --index 5 --partial \"åäöç\"\n}\n\n@test \"Handle latin1 encoding sanely\" {\n  output=$( load_fixture \"latin1\" | $diff_so_fancy )\n  # Make sure the output contains SOME of the english text (i.e. it doesn't barf on the whole line)\n  run printf \"%s\" \"$output\"\n  assert_line --index 6 --partial \"saw he conqu\"\n}\n\n@test \"Correctly handle hunk definition with no comma\" {\n  output=$( load_fixture \"hunk_no_comma\" | $diff_so_fancy )\n  # On single line removes there is NO comma in the hunk,\n  # make sure the first column is still correctly stripped.\n  run printf \"%s\" \"$output\"\n  assert_line --index 5 --regexp \"after\"\n}\n\n@test \"Empty file add\" {\n  output=$( load_fixture \"add_empty_file\" | $diff_so_fancy )\n  run printf \"%s\" \"$output\"\n  assert_line --index 5 --regexp \"added:.*empty_file.txt\"\n}\n\n@test \"Empty file delete\" {\n  output=$( load_fixture \"remove_empty_file\" | $diff_so_fancy )\n  run printf \"%s\" \"$output\"\n  assert_line --index 5 --regexp \"deleted:.*empty_file.txt\"\n}\n\n@test \"Move with content change\" {\n  output=$( load_fixture \"move_with_content_change\" | $diff_so_fancy )\n  run printf \"%s\" \"$output\"\n  assert_line --index 1 --regexp \"renamed:\"\n}\n\n@test \"Mercurial support\" {\n  output=$( load_fixture \"hg\" | $diff_so_fancy )\n  run printf \"%s\" \"$output\"\n  assert_line --index 1 --regexp \"modified: hello.c\"\n}\n\n@test \"Handle file renames\" {\n\toutput=$( load_fixture \"file-rename\" | $diff_so_fancy )\n\trun printf \"%s\" \"$output\"\n\tassert_line --index 1 --partial \"renamed:\"\n\tassert_line --index 1 --partial \"Changes.new\"\n\tassert_line --index 1 --partial \"bin/\"\n}\n\n@test \"header_clean 'added:'\" {\n\toutput=$( load_fixture \"file-moves\" | $diff_so_fancy )\n\tassert_output --regexp 'added:.*hello.txt'\n}\n\n@test \"header_clean 'modified:'\" {\n\toutput=$( load_fixture \"file-moves\" | $diff_so_fancy )\n\tassert_output --partial 'modified: appveyor.yml'\n}\n\n@test \"header_clean 'deleted:'\" {\n\toutput=$( load_fixture \"file-moves\" | $diff_so_fancy )\n\tassert_output --regexp 'deleted:.*circle.yml'\n}\n\n@test \"header_clean permission changes\" {\n\toutput=$( load_fixture \"file-perms\" | $diff_so_fancy )\n\tassert_output --partial 'circle.yml changed file mode from 100644 to 100755'\n}\n\n@test \"header_clean 'new file mode' is removed\" {\n\toutput=$( load_fixture \"file-perms\" | $diff_so_fancy )\n\trefute_output --partial 'new file mode'\n}\n\n@test \"header_clean 'deleted file mode' is removed\" {\n\toutput=$( load_fixture \"file-perms\" | $diff_so_fancy )\n\trefute_output --partial 'deleted file mode'\n}\n\n@test \"header_clean remove 'git --diff' header\" {\n\toutput=$( load_fixture \"file-perms\" | $diff_so_fancy )\n\trefute_output --partial 'diff --git'\n}\n\n@test \"Reworked hunks\" {\n\toutput=$( load_fixture \"file-moves\" | $diff_so_fancy )\n\trun printf \"%s\" \"$output\"\n\n\tassert_line --index 46 --partial \"@ package.json:1 @\"\n\tassert_line --index 79 --partial \"@ square.yml:1 @\"\n}\n\n@test \"Reworked hunks (noprefix)\" {\n\toutput=$( load_fixture \"noprefix\" | $diff_so_fancy )\n\tassert_output --partial '@ setup-a-new-machine.sh:33 @'\n\tassert_output --partial '@ setup-a-new-machine.sh:219 @'\n}\n\n@test \"Reworked hunks (deleted files)\" {\n\toutput=$( load_fixture \"dotfiles\" | $diff_so_fancy )\n\trun printf \"%s\" \"$output\"\n\n\tassert_line --index 188 --partial \"@ bin/diff-so-fancy:1 @\"\n}\n\n@test \"Hunk formatting: @@@ -A,B -C,D +E,F @@@\" {\n\t# stderr forced into output\n\toutput=$( load_fixture \"complex-hunks\" | $diff_so_fancy 2>&1 )\n\trun printf \"%s\" \"$output\"\n\n\tassert_output --regexp \"@ libs/header_clean/header_clean.pl:107 @\"\n    refute_output --partial 'Use of uninitialized value'\n}\n\n@test \"Hunk formatting: @@ -1,6 +1,6 @@\" {\n\t# stderr forced into output\n\toutput=$( load_fixture \"first-three-line\" | $diff_so_fancy )\n\tassert_output --partial '@ package.json:3 @'\n}\n\n@test \"Hunk formatting: @@ -1 0,0 @@\" {\n\t# stderr forced into output\n\toutput=$( load_fixture \"single-line-remove\" | $diff_so_fancy )\n\trun printf \"%s\" \"$output\"\n\tassert_line --index 4 --regexp 'var delayedMessage = \"It worked\";'\n}\n\n@test \"Three way merge\" {\n\t# stderr forced into output\n\toutput=$( load_fixture \"complex-hunks\" | $diff_so_fancy )\n\t# Lines should not have + or - in at the start\n\trefute_output --partial '-\tmy $foo = shift(); # Array passed in by reference'\n\trefute_output --partial '+\tmy $array = shift(); # Array passed in by reference'\n\trefute_output --partial ' sub parse_hunk_header {'\n}\n\n@test \"mnemonicprefix handling\" {\n\toutput=$( load_fixture \"mnemonicprefix\" | $diff_so_fancy )\n\tassert_output --partial 'modified: test/header_clean.bats'\n}\n\n@test \"non-git diff parsing\" {\n\toutput=$( load_fixture \"weird\" | $diff_so_fancy )\n\trun printf \"%s\" \"$output\"\n\n\tassert_line --index 1 --partial \"modified: doc/manual.xml.head\"\n\tassert_line --index 3 --partial \"@ doc/manual.xml.head:8355 @\"\n}\n"
  },
  {
    "path": "test/fixtures/add_empty_file.diff",
    "content": "commit ef0e63dbd7e8df6cde4ee5599ad65db7820888ef\nAuthor: Scott Baker <fake@domain.com>\nDate:   Wed Jul 19 08:00:11 2017 -0700\n\n    Add empty file\n\ndiff --git a/empty_file.txt b/empty_file.txt\nnew file mode 100644\nindex 0000000..e69de29\n"
  },
  {
    "path": "test/fixtures/add_remove_empty_lines.diff",
    "content": "--- one.txt\t2020-04-23 10:15:29.193452325 -0700\n+++ two.txt\t2020-04-23 10:15:37.634463619 -0700\n@@ -1,5 +1,5 @@\n 1\n+\n 2\n 3\n-\n 4\n"
  },
  {
    "path": "test/fixtures/ansi_reset_no_number.diff",
    "content": "\u001b[93mdiff --git a/history.md b/history.md\u001b[m\n\u001b[93mindex f6776e0..a6b4546 100644\u001b[m\n\u001b[93m--- a/history.md\u001b[m\n\u001b[93m+++ b/history.md\u001b[m\n\u001b[1;35m@@ -1,3 +1,3 @@\u001b[m\n\u001b[1;31m-\u001b[m\u001b[1;31m## History\u001b[m\n\u001b[1;32m+\u001b[m\u001b[1;32m## Historyz\u001b[m\n \u001b[m\n \u001b[m`diff-so-fancy` started as [a commit in paulirish's dotfiles](https://github.com/paulirish/dotfiles/commit/6743b907ff586c28cd36e08d1e1c634e2968893e#commitcomment-13349456), which grew into a [standalone script](https://github.com/paulirish/dotfiles/blob/63cb8193b0e66cf80ab6332477f1f52c7fbb9311/bin/diff-so-fancy). Later, [@stevemao](https://github.com/stevemao) brought it into its [own repo](https://github.com/so-fancy/diff-so-fancy) (here), and gave it the room to mature. It's quickly grown into a [widely collaborative project](https://github.com/so-fancy/diff-so-fancy/graphs/contributors).\u001b[m\n"
  },
  {
    "path": "test/fixtures/binary-modified.diff",
    "content": "\u001b[1;33mdiff --git a/cancel.png b/cancel.png\u001b[m\n\u001b[1;33mindex 667e10c..06fc49c 100644\u001b[m\nBinary files a/cancel.png and b/cancel.png differ\n\u001b[1;33mdiff --git a/diff-so-fancy b/diff-so-fancy\u001b[m\n\u001b[1;33mindex 0f2911c..5811717 100755\u001b[m\n\u001b[1;33m--- a/diff-so-fancy\u001b[m\n\u001b[1;33m+++ b/diff-so-fancy\u001b[m\n\u001b[1;35m@@ -32,13 +32,6 @@\u001b[m \u001b[mcolor_code_regex=\"(${CSI}([0-9]{1,3}(;[0-9]{1,3}){0,3})[m|K])?\"\u001b[m\n \u001b[m\n git_index_line_pattern=\"^($color_code_regex)index .*\"\u001b[m\n \u001b[m\n\u001b[1;31m-format_diff_header () {\u001b[m\n\u001b[1;31m-  # simplify the unified patch diff header\u001b[m\n\u001b[1;31m-    $SED -E \"/$git_index_line_pattern/{N;s/$git_index_line_pattern\\n//;}\" \\\u001b[m\n\u001b[1;31m-    | $SED -E \"s/^($color_code_regex)\\-\\-\\-(.*)$/\\1$(print_horizontal_rule)\\\\${NL}\\1---\\5/g\" \\\u001b[m\n\u001b[1;31m-    | $SED -E \"s/^($color_code_regex)\\+\\+\\+(.*)$/\\1+++\\5\\\\${NL}\\1$(print_horizontal_rule)/g\"\u001b[m\n\u001b[1;31m-}\u001b[m\n\u001b[1;31m-\u001b[m\n print_horizontal_rule () {\u001b[m\n   let width=\"$(tput cols)\"\u001b[m\n \u001b[m\n"
  },
  {
    "path": "test/fixtures/chromium-modaltoelement.diff",
    "content": "\u001b[1;33mdiff --git a/third_party/WebKit/Source/devtools/front_end/ui/Dialog.js b/third_party/WebKit/Source/devtools/front_end/ui/Dialog.js\u001b[m\n\u001b[1;33mindex 4f9adf8..8c13743 100644\u001b[m\n\u001b[1;33m--- a/third_party/WebKit/Source/devtools/front_end/ui/Dialog.js\u001b[m\n\u001b[1;33m+++ b/third_party/WebKit/Source/devtools/front_end/ui/Dialog.js\u001b[m\n\u001b[1;35m@@ -32,7 +32,7 @@\u001b[m\n  * @constructor\u001b[m\n  * @extends {WebInspector.Widget}\u001b[m\n  */\u001b[m\n\u001b[1;31m-WebInspector.Dialog = function()\u001b[m\n\u001b[1;32m+\u001b[m\u001b[1;32mWebInspector.Dialog = function(isModalToElement)\u001b[m\n {\u001b[m\n     WebInspector.Widget.call(this, true);\u001b[m\n     this.markAsRoot();\u001b[m\n\u001b[1;35m@@ -45,6 +45,9 @@\u001b[m \u001b[mWebInspector.Dialog = function()\u001b[m\n \u001b[m\n     this._wrapsContent = false;\u001b[m\n     this._dimmed = false;\u001b[m\n\u001b[1;32m+\u001b[m\u001b[1;32m    this._isModalToElement = isModalToElement;\u001b[m\n\u001b[1;32m+\u001b[m\n\u001b[1;32m+\u001b[m\u001b[1;32m    this._glassPane = new WebInspector.GlassPane(relativeToElement, isModalToElement);\u001b[m\n     /** @type {!Map<!HTMLElement, number>} */\u001b[m\n     this._tabIndexMap = new Map();\u001b[m\n }\u001b[m\n\u001b[1;35m@@ -62,16 +65,16 @@\u001b[m \u001b[mWebInspector.Dialog.prototype = {\u001b[m\n     /**\u001b[m\n      * @override\u001b[m\n      */\u001b[m\n\u001b[1;31m-    show: function()\u001b[m\n\u001b[1;32m+\u001b[m\u001b[1;32m    show: function(isModalToElement)\u001b[m\n     {\u001b[m\n         if (WebInspector.Dialog._instance)\u001b[m\n             WebInspector.Dialog._instance.detach();\u001b[m\n         WebInspector.Dialog._instance = this;\u001b[m\n \u001b[m\n\u001b[1;31m-        var document = /** @type {!Document} */ (WebInspector.Dialog._modalHostView.element.ownerDocument);\u001b[m\n\u001b[1;32m+\u001b[m\u001b[1;32m        var document = /** @type {!Document} */ (WebInspector.Dialog._modalHostView.element.ownerDocument, isModalToElement);\u001b[m\n         this._disableTabIndexOnElements(document);\u001b[m\n \u001b[m\n\u001b[1;31m-        this._glassPane = new WebInspector.GlassPane(document, this._dimmed);\u001b[m\n\u001b[1;32m+\u001b[m\u001b[1;32m        this._glassPane = new WebInspector.GlassPane(document, isModalToElement);\u001b[m\n         this._glassPane.element.addEventListener(\"click\", this._onGlassPaneClick.bind(this), false);\u001b[m\n         WebInspector.GlassPane.DefaultFocusedViewStack.push(this);\u001b[m\n \u001b[m\n"
  },
  {
    "path": "test/fixtures/complex-hunks.diff",
    "content": "\u001b[1;32mcommit 74804e377d4a54d1173d4393827d4e4b27e4d5d0\u001b[m\n\u001b[1;33mdiff --cc libs/header_clean/header_clean.pl\u001b[m\n\u001b[1;33mindex e8bcd92,5970580..ae279d0\u001b[m\n\u001b[1;33m--- a/libs/header_clean/header_clean.pl\u001b[m\n\u001b[1;33m+++ b/libs/header_clean/header_clean.pl\u001b[m\n\u001b[1;35m@@@ -105,13 -104,21 +104,23 @@@\u001b[m \u001b[mfor (my $i = 0; $i <= $#input; $i++) \u001b[m\n  \t}\u001b[m\n  }\u001b[m\n  \u001b[m\n\u001b[1;32m+ # Courtesy of github.com/git/git/blob/ab5d01a/git-add--interactive.perl#L798-L805\u001b[m\n\u001b[1;32m+ sub parse_hunk_header {\u001b[m\n\u001b[1;32m+     my ($line) = @_;\u001b[m\n\u001b[1;32m+     my ($o_ofs, $o_cnt, $n_ofs, $n_cnt) =\u001b[m\n\u001b[1;32m+         $line =~ /^@@ -(\\d+)(?:,(\\d+))? \\+(\\d+)(?:,(\\d+))? @@/;\u001b[m\n\u001b[1;32m+     $o_cnt = 1 unless defined $o_cnt;\u001b[m\n\u001b[1;32m+     $n_cnt = 1 unless defined $n_cnt;\u001b[m\n\u001b[1;32m+     return ($o_ofs, $o_cnt, $n_ofs, $n_cnt);\u001b[m\n\u001b[1;32m+ }\u001b[m\n\u001b[1;32m+ \u001b[m\n  sub strip_empty_first_line {\u001b[m\n\u001b[1;31m -\tmy $foo = shift(); # Array passed in by reference\u001b[m\n\u001b[1;32m +\tmy $array = shift(); # Array passed in by reference\u001b[m\n  \u001b[m\n  \t# If the first line is just whitespace remove it\u001b[m\n\u001b[1;31m -\tif (defined($foo->[0]) && $foo->[0] =~ /^\\s*$/) {\u001b[m\n\u001b[1;31m -\t\tshift($foo);\u001b[m\n\u001b[1;32m +\tif (defined($array->[0]) && $array->[0] =~ /^\\s*$/) {\u001b[m\n\u001b[1;32m +\t\tshift(@$array); # Throw away the first line\u001b[m\n  \t}\u001b[m\n\u001b[1;32m +\u001b[m\n\u001b[1;32m +\treturn 1;\u001b[m\n  }\u001b[m\n"
  },
  {
    "path": "test/fixtures/diff_recursive.diff",
    "content": "Only in /var/tmp/b: donk\ndiff --recursive -u /var/tmp/a/foo/bar /var/tmp/b/foo/bar\n--- /var/tmp/a/foo/bar  2026-03-13 21:35:28.492228513 -0700\n+++ /var/tmp/b/foo/bar  2026-03-13 21:35:47.101220189 -0700\n@@ -1 +1 @@\n-BAR\n+FOO\ndiff --recursive -u /var/tmp/a/index.txt /var/tmp/b/index.txt\n--- /var/tmp/a/index.txt        2026-03-13 21:35:20.997231861 -0700\n+++ /var/tmp/b/index.txt        2026-03-13 21:35:41.030222906 -0700\n@@ -1 +1,2 @@\n INDEX\n+INDEX2\n"
  },
  {
    "path": "test/fixtures/dotfiles.diff",
    "content": "\u001b[1;33mdiff --git a/.aliases b/.aliases\u001b[m\n\u001b[1;33mindex 30182ef..be9fb1d 100644\u001b[m\n\u001b[1;33m--- a/.aliases\u001b[m\n\u001b[1;33m+++ b/.aliases\u001b[m\n\u001b[1;35m@@ -18,6 +18,7 @@\u001b[m \u001b[malias brwe=brew  #typos\u001b[m\n \u001b[m\n alias hosts='sudo $EDITOR /etc/hosts'   # yes I occasionally 127.0.0.1 twitter.com ;)\u001b[m\n \u001b[m\n\u001b[1;32m+\u001b[m\u001b[1;32malias ag='ag -W 200 -f'\u001b[m\n \u001b[m\n ###\u001b[m\n # time to upgrade `ls`\u001b[m\n\u001b[1;35m@@ -51,7 +52,7 @@\u001b[m \u001b[malias gr='[ ! -z `git rev-parse --show-cdup` ] && cd `git rev-parse --show-cdup\u001b[m\n \u001b[m\n # Networking. IP address, dig, DNS\u001b[m\n alias ip=\"dig +short myip.opendns.com @resolver1.opendns.com\"\u001b[m\n\u001b[1;31m-alias dig=\"dig +nocmd any +multiline +noall +answer\"\u001b[m\n\u001b[1;32m+\u001b[m\u001b[1;32m# alias dig=\"dig +nocmd any +multiline +noall +answer\"\u001b[m\n \u001b[m\n # Recursively delete `.DS_Store` files\u001b[m\n alias cleanup_dsstore=\"find . -name '*.DS_Store' -type f -ls -delete\"\u001b[m\n\u001b[1;35m@@ -68,7 +69,13 @@\u001b[m \u001b[malias ungz=\"gunzip -k\"\u001b[m\n alias fs=\"stat -f \\\"%z bytes\\\"\"\u001b[m\n \u001b[m\n # Empty the Trash on all mounted volumes and the main HDD. then clear the useless sleepimage\u001b[m\n\u001b[1;31m-alias emptytrash=\"sudo rm -rfv /Volumes/*/.Trashes; rm -rfv ~/.Trash; sudo rm /private/var/vm/sleepimage\"\u001b[m\n\u001b[1;32m+\u001b[m\u001b[1;32malias emptytrash=\" \\\u001b[m\u001b[7;31m \u001b[m\n\u001b[1;32m+\u001b[m\u001b[1;32m    sudo rm -rfv /Volumes/*/.Trashes; \\\u001b[m\n\u001b[1;32m+\u001b[m\u001b[1;32m    rm -rfv ~/.Trash/*; \\\u001b[m\n\u001b[1;32m+\u001b[m\u001b[1;32m    sudo rm -v /private/var/vm/sleepimage; \\\u001b[m\n\u001b[1;32m+\u001b[m\u001b[1;32m    rm -rv \\\"/Users/paulirish/Library/Application Support/stremio/Cache\\\";  \\\u001b[m\n\u001b[1;32m+\u001b[m\u001b[1;32m    rm -rv \\\"/Users/paulirish/Library/Application Support/stremio/stremio-cache\\\" \\\u001b[m\n\u001b[1;32m+\u001b[m\u001b[1;32m\"\u001b[m\n \u001b[m\n # Update installed Ruby gems, Homebrew, npm, and their installed packages\u001b[m\n alias brew_update=\"brew -v update; brew -v upgrade --all; brew cleanup; brew cask cleanup; brew prune; brew doctor\"\u001b[m\n\u001b[1;33mdiff --git a/.bash_profile b/.bash_profile\u001b[m\n\u001b[1;33mindex 8f17751..def367d 100644\u001b[m\n\u001b[1;33m--- a/.bash_profile\u001b[m\n\u001b[1;33m+++ b/.bash_profile\u001b[m\n\u001b[1;35m@@ -116,3 +116,9 @@\u001b[m \u001b[mshopt -s cdspell;\u001b[m\n \u001b[m\n \u001b[m\n \u001b[m\n\u001b[1;32m+\u001b[m\n\u001b[1;32m+\u001b[m\u001b[1;32m# The next line updates PATH for the Google Cloud SDK.\u001b[m\n\u001b[1;32m+\u001b[m\u001b[1;32msource '/Users/paulirish/google-cloud-sdk/path.bash.inc'\u001b[m\n\u001b[1;32m+\u001b[m\n\u001b[1;32m+\u001b[m\u001b[1;32m# The next line enables shell command completion for gcloud.\u001b[m\n\u001b[1;32m+\u001b[m\u001b[1;32msource '/Users/paulirish/google-cloud-sdk/completion.bash.inc'\u001b[m\n\u001b[1;33mdiff --git a/.bash_prompt b/.bash_prompt\u001b[m\n\u001b[1;33mindex 852d69f..8d3e3d0 100644\u001b[m\n\u001b[1;33m--- a/.bash_prompt\u001b[m\n\u001b[1;33m+++ b/.bash_prompt\u001b[m\n\u001b[1;35m@@ -4,6 +4,9 @@\u001b[m\n default_username='paulirish'\u001b[m\n \u001b[m\n \u001b[m\n\u001b[1;32m+\u001b[m\u001b[1;32meval \"$(thefuck --alias)\"\u001b[m\n\u001b[1;32m+\u001b[m\n\u001b[1;32m+\u001b[m\n if [[ -n \"$ZSH_VERSION\" ]]; then  # quit now if in zsh\u001b[m\n     return 1 2> /dev/null || exit 1;\u001b[m\n fi;\u001b[m\n\u001b[1;33mdiff --git a/.bashrc b/.bashrc\u001b[m\n\u001b[1;33mindex 877b68f..18461ac 100644\u001b[m\n\u001b[1;33m--- a/.bashrc\u001b[m\n\u001b[1;33m+++ b/.bashrc\u001b[m\n\u001b[1;35m@@ -1,2 +1,4 @@\u001b[m\n [ -n \"$PS1\" ] && source ~/.bash_profile\u001b[m\n \u001b[m\n\u001b[1;32m+\u001b[m\n\u001b[1;32m+\u001b[m\u001b[1;32m# [ -f ~/.fzf.bash ] && source ~/.fzf.bash\u001b[m\n\u001b[1;33mdiff --git a/.functions b/.functions\u001b[m\n\u001b[1;33mindex 8292d2e..a548d45 100644\u001b[m\n\u001b[1;33m--- a/.functions\u001b[m\n\u001b[1;33m+++ b/.functions\u001b[m\n\u001b[1;35m@@ -28,6 +28,19 @@\u001b[m \u001b[mcdf() {  # short for cdfinder\u001b[m\n }\u001b[m\n \u001b[m\n \u001b[m\n\u001b[1;32m+\u001b[m\n\u001b[1;32m+\u001b[m\u001b[1;32m# git commit browser. needs fzf\u001b[m\n\u001b[1;32m+\u001b[m\u001b[1;32mlog() {\u001b[m\n\u001b[1;32m+\u001b[m\u001b[1;32m  git log --graph --color=always \\\u001b[m\n\u001b[1;32m+\u001b[m\u001b[1;32m      --format=\"%C(auto)%h%d %s %C(black)%C(bold)%cr\" \"$@\" |\u001b[m\n\u001b[1;32m+\u001b[m\u001b[1;32m  fzf --ansi --no-sort --reverse --tiebreak=index --toggle-sort=\\` \\\u001b[m\n\u001b[1;32m+\u001b[m\u001b[1;32m      --bind \"ctrl-m:execute:\u001b[m\n\u001b[1;32m+\u001b[m\u001b[1;32m                echo '{}' | grep -o '[a-f0-9]\\{7\\}' | head -1 |\u001b[m\n\u001b[1;32m+\u001b[m\u001b[1;32m                xargs -I % sh -c 'git show --color=always % | less -R'\"\u001b[m\n\u001b[1;32m+\u001b[m\u001b[1;32m}\u001b[m\n\u001b[1;32m+\u001b[m\n\u001b[1;32m+\u001b[m\n\u001b[1;32m+\u001b[m\n # Start an HTTP server from a directory, optionally specifying the port\u001b[m\n function server() {\u001b[m\n \tlocal port=\"${1:-8000}\"\u001b[m\n\u001b[1;35m@@ -145,17 +158,14 @@\u001b[m \u001b[mwebmify(){\u001b[m\n \tffmpeg -i $1 -vcodec libvpx -acodec libvorbis -isync -copyts -aq 80 -threads 3 -qmax 30 -y $2 $1.webm\u001b[m\n }\u001b[m\n \u001b[m\n\u001b[1;32m+\u001b[m\u001b[1;32m# direct it all to /dev/null\u001b[m\n\u001b[1;32m+\u001b[m\u001b[1;32mfunction nullify() {\u001b[m\n\u001b[1;32m+\u001b[m\u001b[1;32m  \"$@\" >/dev/null 2>&1\u001b[m\n\u001b[1;32m+\u001b[m\u001b[1;32m}\u001b[m\n\u001b[1;32m+\u001b[m\n \u001b[m\n # visual studio code. a la `subl`\u001b[m\n\u001b[1;31m-code () {\u001b[m\n\u001b[1;31m-\tif [[ $# = 0 ]]\u001b[m\n\u001b[1;31m-\tthen\u001b[m\n\u001b[1;31m-\t\topen -a \"Visual Studio Code\"\u001b[m\n\u001b[1;31m-\telse\u001b[m\n\u001b[1;31m-\t\t[[ $1 = /* ]] && F=\"$1\" || F=\"$PWD/${1#./}\"\u001b[m\n\u001b[1;31m-\t\topen -a \"Visual Studio Code\" --args \"$F\"\u001b[m\n\u001b[1;31m-\tfi\u001b[m\n\u001b[1;31m-}\u001b[m\n\u001b[1;32m+\u001b[m\u001b[1;32mfunction code () { VSCODE_CWD=\"$PWD\" open -n -b \"com.microsoft.VSCode\" --args $*; }\u001b[m\n \u001b[m\n # `shellswitch [bash |zsh]`\u001b[m\n #   Must be in /etc/shells\u001b[m\n\u001b[1;33mdiff --git a/.gitconfig b/.gitconfig\u001b[m\n\u001b[1;33mindex d2be05f..d32f98c 100644\u001b[m\n\u001b[1;33m--- a/.gitconfig\u001b[m\n\u001b[1;33m+++ b/.gitconfig\u001b[m\n\u001b[1;35m@@ -7,11 +7,13 @@\u001b[m\n \tdf = diff --color --color-words --abbrev\u001b[m\n \tlg = log --color --graph --pretty=format:'%Cred%h%Creset -%C(yellow)%d%Creset %s %Cgreen(%cr) %C(bold blue)<%an>%Creset' --abbrev-commit --\u001b[m\n \tco = checkout\u001b[m\n\u001b[1;32m+\u001b[m\t\u001b[1;32mcherry = cherry-pick # does this conflict with the real `git cherry`?\u001b[m\n \u001b[m\n \t# Show the diff between the latest commit and the current state\u001b[m\n \td = !\"git diff-index --quiet HEAD -- || clear; git --no-pager diff --patch-with-stat\"\u001b[m\n \u001b[m\n\u001b[1;31m-\treup = rebase-update\u001b[m\n\u001b[1;32m+\u001b[m\t\u001b[1;32m# chromium/depot_tools convenience. manual\u001b[m\n\u001b[1;32m+\u001b[m\t\u001b[1;32mreup = rebase-update --no_fetch --keep-going\u001b[m\n \u001b[m\n \u001b[m\n \u001b[m\n\u001b[1;35m@@ -23,7 +25,7 @@\u001b[m\n \texcludesfile = ~/.gitignore\u001b[m\n \tattributesfile = ~/.gitattributes\u001b[m\n \t# insanely beautiful diffs\u001b[m\n\u001b[1;31m-\tpager = diff-highlight | diff-so-fancy | less -r\u001b[m\n\u001b[1;32m+\u001b[m\t\u001b[1;32mpager = diff-so-fancy | less --tabs=1,5 -R\u001b[m\n [color \"branch\"]\u001b[m\n \tcurrent = yellow reverse\u001b[m\n \tlocal = yellow\u001b[m\n\u001b[1;35m@@ -40,7 +42,8 @@\u001b[m\n \tchanged = green\u001b[m\n \tuntracked = cyan\u001b[m\n [merge]\u001b[m\n\u001b[1;31m-\ttool = opendiff\u001b[m\n\u001b[1;32m+\u001b[m\t\u001b[1;32m#tool = opendiff\u001b[m\n\u001b[1;32m+\u001b[m\t\u001b[1;32mtool = kdiff3\u001b[m\n \u001b[m\n \u001b[m\n [color \"diff-highlight\"]\u001b[m\n\u001b[1;35m@@ -86,3 +89,5 @@\u001b[m\n \trequired = true\u001b[m\n [init]\u001b[m\n \ttemplatedir = ~/.git_template\u001b[m\n\u001b[1;32m+\u001b[m\u001b[1;32m[http]\u001b[m\n\u001b[1;32m+\u001b[m\t\u001b[1;32mcookiefile = /Users/paulirish/.gitcookies\u001b[m\n\u001b[1;33mdiff --git a/.zshrc b/.zshrc\u001b[m\n\u001b[1;33mindex 410a2ca..dbad355 100644\u001b[m\n\u001b[1;33m--- a/.zshrc\u001b[m\n\u001b[1;33m+++ b/.zshrc\u001b[m\n\u001b[1;35m@@ -113,3 +113,5 @@\u001b[m \u001b[msource ~/.bash_profile\u001b[m\n \u001b[m\n \u001b[m\n export PATH=\"$PATH:$HOME/.rvm/bin\" # Add RVM to PATH for scripting\u001b[m\n\u001b[1;32m+\u001b[m\n\u001b[1;32m+\u001b[m\u001b[1;32m[ -f ~/.fzf.zsh ] && source ~/.fzf.zsh\u001b[m\n\u001b[1;33mdiff --git a/README.md b/README.md\u001b[m\n\u001b[1;33mindex 496bbbb..171eaea 100644\u001b[m\n\u001b[1;33m--- a/README.md\u001b[m\n\u001b[1;33m+++ b/README.md\u001b[m\n\u001b[1;35m@@ -55,7 +55,6 @@\u001b[m \u001b[mLastly, I use `open .` to open Finder from this path. (That's just available nor\u001b[m\n ## overview of files\u001b[m\n \u001b[m\n ####  Automatic config\u001b[m\n\u001b[1;31m-* `.sift.conf` - sift (faster than grep, ack, ag)\u001b[m\n * `.vimrc`, `.vim` - vim config, obv.\u001b[m\n * `.inputrc` - behavior of the actual prompt line\u001b[m\n \u001b[m\n\u001b[1;33mdiff --git a/bin/diff-highlight b/bin/diff-highlight\u001b[m\n\u001b[1;33mdeleted file mode 120000\u001b[m\n\u001b[1;33mindex 7c5c827..0000000\u001b[m\n\u001b[1;33m--- a/bin/diff-highlight\u001b[m\n\u001b[1;33m+++ /dev/null\u001b[m\n\u001b[1;35m@@ -1 +0,0 @@\u001b[m\n\u001b[1;31m-/Users/paulirish/.homebrew/share/git-core/contrib/diff-highlight/diff-highlight\u001b[m\n\\ No newline at end of file\u001b[m\n\u001b[1;33mdiff --git a/bin/diff-so-fancy b/bin/diff-so-fancy\u001b[m\n\u001b[1;33mdeleted file mode 100755\u001b[m\n\u001b[1;33mindex 5b004a2..0000000\u001b[m\n\u001b[1;33m--- a/bin/diff-so-fancy\u001b[m\n\u001b[1;33m+++ /dev/null\u001b[m\n\u001b[1;35m@@ -1,69 +0,0 @@\u001b[m\n\u001b[1;31m-#!/bin/bash\u001b[m\n\u001b[1;31m-\u001b[m\n\u001b[1;31m-###############\u001b[m\n\u001b[1;31m-# diff-so-fancy builds on the good-lookin' output of diff-highlight to upgrade your diffs' appearances\u001b[m\n\u001b[1;31m-# \t* Output will not be in standard patch format, but will be readable\u001b[m\n\u001b[1;31m-#   * No pesky `+` or `-` at line-stars, making for easier copy-paste.\u001b[m\n\u001b[1;31m-#\u001b[m\n\u001b[1;31m-# Screenshot: https://github.com/paulirish/dotfiles/commit/6743b907ff58#commitcomment-13349456\u001b[m\n\u001b[1;31m-#\u001b[m\n\u001b[1;31m-#\u001b[m\n\u001b[1;31m-# Usage\u001b[m\n\u001b[1;31m-#\u001b[m\n\u001b[1;31m-#   git diff | diff-highlight | diff-so-fancy\u001b[m\n\u001b[1;31m-#\u001b[m\n\u001b[1;31m-# Add to .gitconfig so all `git diff` uses it.\u001b[m\n\u001b[1;31m-#   git config --global core.pager \"diff-highlight | diff-so-fancy | less -r\"\u001b[m\n\u001b[1;31m-#\u001b[m\n\u001b[1;31m-#\u001b[m\n\u001b[1;31m-# Requirements / Install\u001b[m\n\u001b[1;31m-#\u001b[m\n\u001b[1;31m-# * GNU sed. On Mac, install it with Homebrew:\u001b[m\n\u001b[1;31m-#   \tbrew install gnu-sed --default-names  # You'll have to change below to `gsed` otherwise\u001b[m\n\u001b[1;31m-# * diff-highlight. It's shipped with Git, but probably not in your $PATH\u001b[m\n\u001b[1;31m-#       ln -sf \"$(brew --prefix)/share/git-core/contrib/diff-highlight/diff-highlight\" ~/bin/diff-highlight\u001b[m\n\u001b[1;31m-# * Add some coloring to your .gitconfig:\u001b[m\n\u001b[1;31m-#\t\tgit config --global color.diff-highlight.oldNormal \"red bold\"\u001b[m\n\u001b[1;31m-#\t\tgit config --global color.diff-highlight.oldHighlight \"red bold 52\"\u001b[m\n\u001b[1;31m-#\t\tgit config --global color.diff-highlight.newNormal \"green bold\"\u001b[m\n\u001b[1;31m-#\t\tgit config --global color.diff-highlight.newHighlight \"green bold 22\"\u001b[m\n\u001b[1;31m-#\u001b[m\n\u001b[1;31m-###############\u001b[m\n\u001b[1;31m-\u001b[m\n\u001b[1;31m-# TODO:\u001b[m\n\u001b[1;31m-#   Put on NPM.\u001b[m\n\u001b[1;31m-\u001b[m\n\u001b[1;31m-\u001b[m\n\u001b[1;31m-[ $# -ge 1 -a -f \"$1\" ] && input=\"$1\" || input=\"-\"\u001b[m\n\u001b[1;31m-\u001b[m\n\u001b[1;31m-color_code_regex=$'(\\x1B\\\\[([0-9]{1,2}(;[0-9]{1,2})?)[m|K])?'\u001b[m\n\u001b[1;31m-reset_color=\"\\x1B\\[m\"\u001b[m\n\u001b[1;31m-dim_magenta=\"\\x1B\\[38;05;146m\"\u001b[m\n\u001b[1;31m-\u001b[m\n\u001b[1;31m-format_diff_header () {\u001b[m\n\u001b[1;31m-\t# simplify the unified patch diff header\u001b[m\n\u001b[1;31m-\tsed -E \"s/^($color_code_regex)diff --git .*$//g\" | \\\u001b[m\n\u001b[1;31m-\tsed -E \"s/^($color_code_regex)index .*$/\\\u001b[m\n\u001b[1;31m-\\1$(print_horizontal_rule)/g\" | \\\u001b[m\n\u001b[1;31m-\tsed -E \"s/^($color_code_regex)\\+\\+\\+(.*)$/\\1\\+\\+\\+\\5\\\\\u001b[m\n\u001b[1;31m-\\1$(print_horizontal_rule)/g\"\u001b[m\n\u001b[1;31m-}\u001b[m\n\u001b[1;31m-\u001b[m\n\u001b[1;31m-colorize_context_line () {\u001b[m\n\u001b[1;31m-\t# extra color for @@ context line\u001b[m\n\u001b[1;31m-\tsed -E \"s/@@$reset_color $reset_color(.*$)/@@ $dim_magenta\\1/g\"\u001b[m\n\u001b[1;31m-}\u001b[m\n\u001b[1;31m-\u001b[m\n\u001b[1;31m-strip_leading_symbols () {\u001b[m\n\u001b[1;31m-\t# strip the + and -\u001b[m\n\u001b[1;31m-\tsed -E \"s/^($color_code_regex)[\\+\\-]/\\1 /g\"\u001b[m\n\u001b[1;31m-}\u001b[m\n\u001b[1;31m-\u001b[m\n\u001b[1;31m-print_horizontal_rule () {\u001b[m\n\u001b[1;31m-\t\tprintf \"%$(tput cols)s\\n\"|tr \" \" \"─\"\u001b[m\n\u001b[1;31m-}\u001b[m\n\u001b[1;31m-\u001b[m\n\u001b[1;31m-# run it.\u001b[m\n\u001b[1;31m-cat $input | format_diff_header |  colorize_context_line | strip_leading_symbols\u001b[m\n\u001b[1;31m-\u001b[m\n\u001b[1;31m-\u001b[m\n\u001b[1;33mdiff --git a/brew-cask.sh b/brew-cask.sh\u001b[m\n\u001b[1;33mindex 24c2ba5..3f3c02a 100755\u001b[m\n\u001b[1;33m--- a/brew-cask.sh\u001b[m\n\u001b[1;33m+++ b/brew-cask.sh\u001b[m\n\u001b[1;35m@@ -1,8 +1,8 @@\u001b[m\n #!/bin/bash\u001b[m\n \u001b[m\n \u001b[m\n\u001b[1;31m-# to maintain cask .... \u001b[m\n\u001b[1;31m-#     brew update && brew upgrade brew-cask && brew cleanup && brew cask cleanup` \u001b[m\n\u001b[1;32m+\u001b[m\u001b[1;32m# to maintain cask ....\u001b[m\n\u001b[1;32m+\u001b[m\u001b[1;32m#     brew update && brew upgrade brew-cask && brew cleanup && brew cask cleanup`\u001b[m\n \u001b[m\n \u001b[m\n # Install native apps\u001b[m\n\u001b[1;35m@@ -14,7 +14,7 @@\u001b[m \u001b[mbrew tap caskroom/versions\u001b[m\n brew cask install spectacle\u001b[m\n brew cask install dropbox\u001b[m\n brew cask install gyazo\u001b[m\n\u001b[1;31m-brew cask install onepassword\u001b[m\n\u001b[1;32m+\u001b[m\u001b[1;32mbrew cask install 1password\u001b[m\n brew cask install rescuetime\u001b[m\n brew cask install flux\u001b[m\n \u001b[m\n\u001b[1;35m@@ -47,6 +47,6 @@\u001b[m \u001b[mbrew cask install utorrent\u001b[m\n \u001b[m\n # Not on cask but I want regardless.\u001b[m\n \u001b[m\n\u001b[1;31m-# 3Hub   https://itunes.apple.com/us/app/3hub/id427515976?mt=12 \u001b[m\n\u001b[1;32m+\u001b[m\u001b[1;32m# 3Hub   https://itunes.apple.com/us/app/3hub/id427515976?mt=12\u001b[m\n # File Multi Tool 5\u001b[m\n # Phosphor\u001b[m\n\\ No newline at end of file\u001b[m\n\u001b[1;33mdiff --git a/brew.sh b/brew.sh\u001b[m\n\u001b[1;33mindex 8715a37..c4a663e 100755\u001b[m\n\u001b[1;33m--- a/brew.sh\u001b[m\n\u001b[1;33m+++ b/brew.sh\u001b[m\n\u001b[1;35m@@ -58,7 +58,9 @@\u001b[m \u001b[mbrew install mtr\u001b[m\n \u001b[m\n \u001b[m\n # Install other useful binaries\u001b[m\n\u001b[1;31m-brew install sift\u001b[m\n\u001b[1;32m+\u001b[m\u001b[1;32mbrew install the_silver_searcher\u001b[m\n\u001b[1;32m+\u001b[m\u001b[1;32mbrew install fzf\u001b[m\n\u001b[1;32m+\u001b[m\n brew install git\u001b[m\n brew install imagemagick --with-webp\u001b[m\n brew install node # This installs `npm` too using the recommended installation method\u001b[m\n\u001b[1;33mdiff --git a/fish/aliases.fish b/fish/aliases.fish\u001b[m\n\u001b[1;33mindex a0fe9b3..fc990f6 100644\u001b[m\n\u001b[1;33m--- a/fish/aliases.fish\u001b[m\n\u001b[1;33m+++ b/fish/aliases.fish\u001b[m\n\u001b[1;35m@@ -26,17 +26,14 @@\u001b[m \u001b[malias brwe=brew  #typos\u001b[m\n alias hosts='sudo $EDITOR /etc/hosts'   # yes I occasionally 127.0.0.1 twitter.com ;)\u001b[m\n \u001b[m\n \u001b[m\n\u001b[1;31m-# `shellswitch [bash|zsh|fish]`\u001b[m\n\u001b[1;31m-function shellswitch\u001b[m\n\u001b[1;31m-\tchsh -s (brew --prefix)/bin/$argv\u001b[m\n\u001b[1;31m-end\u001b[m\n\u001b[1;31m-\u001b[m\n\u001b[1;31m-\u001b[m\n\u001b[1;31m-\u001b[m\n # `cat` with beautiful colors. requires Pygments installed.\u001b[m\n # \t\t\t\t\t\t\t   sudo easy_install -U Pygments\u001b[m\n alias c='pygmentize -O style=monokai -f console256 -g'\u001b[m\n \u001b[m\n\u001b[1;32m+\u001b[m\u001b[1;32malias ag='ag -W 200 -f'\u001b[m\n\u001b[1;32m+\u001b[m\n\u001b[1;32m+\u001b[m\u001b[1;32malias diskspace_report=\"df -P -kHl\"\u001b[m\n\u001b[1;32m+\u001b[m\u001b[1;32malias free_diskspace_report=\"diskspace_report\"\u001b[m\n \u001b[m\n \u001b[m\n # Networking. IP address, dig, DNS\u001b[m\n\u001b[1;35m@@ -54,8 +51,7 @@\u001b[m \u001b[malias ungz=\"gunzip -k\"\u001b[m\n # File size\u001b[m\n alias fs=\"stat -f \\\"%z bytes\\\"\"\u001b[m\n \u001b[m\n\u001b[1;31m-# Empty the Trash on all mounted volumes and the main HDD. then clear the useless sleepimage\u001b[m\n\u001b[1;31m-alias emptytrash=\"sudo rm -rfv /Volumes/*/.Trashes; rm -rfv ~/.Trash; sudo rm /private/var/vm/sleepimage\"\u001b[m\n\u001b[1;32m+\u001b[m\u001b[1;32m# emptytrash written as a function\u001b[m\n \u001b[m\n # Update installed Ruby gems, Homebrew, npm, and their installed packages\u001b[m\n alias brew_update=\"brew -v update; brew -v upgrade --all; brew cleanup; brew cask cleanup; brew prune; brew doctor\"\u001b[m\n\u001b[1;33mdiff --git a/fish/config.fish b/fish/config.fish\u001b[m\n\u001b[1;33mindex 9c4bd70..fd19027 100755\u001b[m\n\u001b[1;33m--- a/fish/config.fish\u001b[m\n\u001b[1;33m+++ b/fish/config.fish\u001b[m\n\u001b[1;35m@@ -2,9 +2,15 @@\u001b[m \u001b[mset default_user \"paulirish\"\u001b[m\n set default_machine \"paulirish-macbookair2\"\u001b[m\n \u001b[m\n \u001b[m\n\u001b[1;32m+\u001b[m\u001b[1;32m#set -x  DYLD_FALLBACK_LIBRARY_PATH /Users/paulirish/.homebrew/lib\u001b[m\n\u001b[1;32m+\u001b[m\n source ~/.config/fish/path.fish\u001b[m\n source ~/.config/fish/aliases.fish\u001b[m\n source ~/.config/fish/chpwd.fish\u001b[m\n\u001b[1;32m+\u001b[m\u001b[1;32msource ~/.config/fish/functions.fish\u001b[m\n\u001b[1;32m+\u001b[m\n\u001b[1;32m+\u001b[m\n\u001b[1;32m+\u001b[m\n \u001b[m\n \u001b[m\n # Completions\u001b[m\n\u001b[1;35m@@ -22,8 +28,6 @@\u001b[m \u001b[mend\u001b[m\n make_completion g 'git'\u001b[m\n \u001b[m\n \u001b[m\n\u001b[1;31m-\u001b[m\n\u001b[1;31m-\u001b[m\n # Readline colors\u001b[m\n set -g fish_color_autosuggestion 555 yellow\u001b[m\n set -g fish_color_command 5f87d7\u001b[m\n\u001b[1;35m@@ -83,3 +87,4 @@\u001b[m \u001b[mset -gx LESS_TERMCAP_so \\e'[38;5;246m'    # begin standout-mode - info box\u001b[m\n set -gx LESS_TERMCAP_ue \\e'[0m'           # end underline\u001b[m\n set -gx LESS_TERMCAP_us \\e'[04;38;5;146m' # begin underline\u001b[m\n \u001b[m\n\u001b[7;31m+\u001b[m\n\u001b[1;33mdiff --git a/fish/functions/fish_prompt.fish b/fish/functions/fish_prompt.fish\u001b[m\n\u001b[1;33mindex 04abe4b..96bfa3e 100755\u001b[m\n\u001b[1;33m--- a/fish/functions/fish_prompt.fish\u001b[m\n\u001b[1;33m+++ b/fish/functions/fish_prompt.fish\u001b[m\n\u001b[1;35m@@ -13,6 +13,9 @@\u001b[m \u001b[mfunction _git_current_branch -d \"Output git's current branch name\"\u001b[m\n   end ^/dev/null | sed -e 's|^refs/heads/||'\u001b[m\n end\u001b[m\n \u001b[m\n\u001b[1;32m+\u001b[m\u001b[1;32mfunction fish_title --description 'Show current path (abbreviated) in iTerm tab title '\u001b[m\n\u001b[1;32m+\u001b[m\u001b[1;32m   echo (prompt_pwd)\u001b[m\n\u001b[1;32m+\u001b[m\u001b[1;32mend\u001b[m\n \u001b[m\n function fish_prompt --description 'Write out the prompt'\u001b[m\n \u001b[m\n\u001b[1;33mdiff --git a/fish/functions/gr.fish b/fish/functions/gr.fish\u001b[m\n\u001b[1;33mindex 4c2722d..1a0ee1b 100644\u001b[m\n\u001b[1;33m--- a/fish/functions/gr.fish\u001b[m\n\u001b[1;33m+++ b/fish/functions/gr.fish\u001b[m\n\u001b[1;35m@@ -1,3 +1,12 @@\u001b[m\n \u001b[m\n # git root\u001b[m\n\u001b[1;31m-alias gr='[ ! -z `git rev-parse --show-cdup` ] && cd `git rev-parse --show-cdup || pwd`'\u001b[m\n\u001b[1;32m+\u001b[m\u001b[1;32mfunction gr --description \"Jump to the git root\"\u001b[m\n\u001b[1;32m+\u001b[m\t\u001b[1;32mset -l repo_info (command git rev-parse --git-dir --is-inside-git-dir --is-bare-repository --is-inside-work-tree --short HEAD ^/dev/null)\u001b[m\n\u001b[1;32m+\u001b[m\u001b[7;31m  \u001b[m\t\u001b[1;32mtest -n \"$repo_info\"; or return\u001b[m\n\u001b[1;32m+\u001b[m\n\u001b[1;32m+\u001b[m\t\u001b[1;32mset -l cd_up_path (command git rev-parse --show-cdup)\u001b[m\n\u001b[1;32m+\u001b[m\n\u001b[1;32m+\u001b[m\t\u001b[1;32mif test -n $cd_up_path\u001b[m\n\u001b[1;32m+\u001b[m\t\t\u001b[1;32mcd $cd_up_path\u001b[m\n\u001b[1;32m+\u001b[m\t\u001b[1;32mend\u001b[m\n\u001b[1;32m+\u001b[m\u001b[1;32mend\u001b[m\n\u001b[1;33mdiff --git a/fish/functions/ls.fish b/fish/functions/ls.fish\u001b[m\n\u001b[1;33mindex 33c3d8b..fd54db2 100644\u001b[m\n\u001b[1;33m--- a/fish/functions/ls.fish\u001b[m\n\u001b[1;33m+++ b/fish/functions/ls.fish\u001b[m\n\u001b[1;35m@@ -7,6 +7,8 @@\u001b[m\n if begin\u001b[m\n     type gls 1>/dev/null 2>/dev/null\u001b[m\n     or command ls --version 1>/dev/null 2>/dev/null\u001b[m\n\u001b[1;32m+\u001b[m\n\u001b[1;32m+\u001b[m\u001b[1;32m    set -x CLICOLOR_FORCE 1\u001b[m\n   end\u001b[m\n   # This is GNU ls\u001b[m\n   function ls --description \"List contents of directory\"\u001b[m\n\u001b[1;35m@@ -22,11 +24,11 @@\u001b[m \u001b[mif begin\u001b[m\n     set param $param --human-readable\u001b[m\n     set param $param --sort=extension\u001b[m\n     set param $param --group-directories-first\u001b[m\n\u001b[1;31m-\tif isatty 1\u001b[m\n\u001b[1;32m+\u001b[m\u001b[1;32m    if isatty 1\u001b[m\n       set param $param --indicator-style=classify\u001b[m\n     end\u001b[m\n \u001b[m\n\u001b[1;31m-    eval \"env CLICOLOR_FORCE=1 command $ls $param $argv\"\u001b[m\n\u001b[1;32m+\u001b[m\u001b[1;32m    eval $ls $param \"$argv\"\u001b[m\n   end\u001b[m\n \u001b[m\n   if not set -q LS_COLORS\u001b[m\n\u001b[1;33mdiff --git a/fish/path.fish b/fish/path.fish\u001b[m\n\u001b[1;33mindex 6e28fad..aa9d014 100644\u001b[m\n\u001b[1;33m--- a/fish/path.fish\u001b[m\n\u001b[1;33m+++ b/fish/path.fish\u001b[m\n\u001b[1;35m@@ -13,8 +13,10 @@\u001b[m \u001b[mfor entry in (string split \\n $PATH_DIRS)\u001b[m\n     # resolve the {$HOME} substitutions\u001b[m\n     set -l resolved_path (eval echo $entry)\u001b[m\n     if test -d \"$resolved_path\"; # and not contains $resolved_path $PATH\u001b[m\n\u001b[1;31m-        set -x PA $PA \"$resolved_path\"\u001b[m\n\u001b[1;32m+\u001b[m\u001b[1;32m        set PA $PA \"$resolved_path\"\u001b[m\n     end\u001b[m\n end\u001b[m\n \u001b[m\n\u001b[1;31m-set -x PATH $PA\u001b[m\n\\ No newline at end of file\u001b[m\n\u001b[1;32m+\u001b[m\u001b[1;32mset PA $PA /Users/paulirish/.rvm/gems/ruby-2.2.1/bin\u001b[m\n\u001b[1;32m+\u001b[m\n\u001b[1;32m+\u001b[m\u001b[1;32mset --export PATH $PA\u001b[m\n\\ No newline at end of file\u001b[m\n\u001b[1;33mdiff --git a/setup-a-new-machine.sh b/setup-a-new-machine.sh\u001b[m\n\u001b[1;33mindex 7b5996c..7e60889 100755\u001b[m\n\u001b[1;33m--- a/setup-a-new-machine.sh\u001b[m\n\u001b[1;33m+++ b/setup-a-new-machine.sh\u001b[m\n\u001b[1;35m@@ -215,5 +215,7 @@\u001b[m \u001b[msh .osx\u001b[m\n # symlink it up!\u001b[m\n ./symlink-setup.sh\u001b[m\n \u001b[m\n\u001b[1;32m+\u001b[m\u001b[1;32m# add manual symlink for .ssh/config and probably .config/fish\u001b[m\n\u001b[1;32m+\u001b[m\n ###\u001b[m\n ##############################################################################################################\u001b[m\n"
  },
  {
    "path": "test/fixtures/file-moves.diff",
    "content": "\u001b[33mcommit a12f64cfa2388b1d07659149db3a77314c9836c8\u001b[m\nAuthor: Scott Baker <bakers@canbytel.com>\nDate:   Thu Feb 25 08:31:54 2016 -0800\n\n    Moves\n\n\u001b[38;5;11mdiff --git a/appveyor.yml b/appveyor.yml\u001b[m\n\u001b[38;5;11mindex a6fb95e..43d61c3 100644\u001b[m\n\u001b[38;5;11m--- a/appveyor.yml\u001b[m\n\u001b[38;5;11m+++ b/appveyor.yml\u001b[m\n\u001b[36m@@ -1,4 +1,4 @@\u001b[m\n\u001b[38;5;9m-install:\u001b[m\n\u001b[38;5;10m+\u001b[m\u001b[38;5;10mFOOinstall:\u001b[m\n   - git clone --depth 1 https://github.com/sstephenson/bats.git\u001b[m\n \u001b[m\n build: false\u001b[m\n\u001b[38;5;11mdiff --git a/circle.yml b/circle.yml\u001b[m\n\u001b[38;5;11mdeleted file mode 100644\u001b[m\n\u001b[38;5;11mindex 18a04a3..0000000\u001b[m\n\u001b[38;5;11m--- a/circle.yml\u001b[m\n\u001b[38;5;11m+++ /dev/null\u001b[m\n\u001b[36m@@ -1,21 +0,0 @@\u001b[m\n\u001b[38;5;9m-machine:\u001b[m\n\u001b[38;5;9m-  environment:\u001b[m\n\u001b[38;5;9m-\u001b[m\n\u001b[38;5;9m-\u001b[m\n\u001b[38;5;9m-dependencies:\u001b[m\n\u001b[38;5;9m-  pre:\u001b[m\n\u001b[38;5;9m-    - sudo add-apt-repository \"deb http://archive.ubuntu.com/ubuntu/ trusty-backports restricted main universe\";\u001b[m\n\u001b[38;5;9m-    - sudo apt-get -y update\u001b[m\n\u001b[38;5;9m-    - sudo apt-get -y install shellcheck;\u001b[m\n\u001b[38;5;9m-\u001b[m\n\u001b[38;5;9m-  post:\u001b[m\n\u001b[38;5;9m-    - git clone --depth 1 https://github.com/sstephenson/bats.git\u001b[m\n\u001b[38;5;9m-\u001b[m\n\u001b[38;5;9m-checkout:\u001b[m\n\u001b[38;5;9m-  post:\u001b[m\n\u001b[38;5;9m-    - git submodule update --init\u001b[m\n\u001b[38;5;9m-\u001b[m\n\u001b[38;5;9m-test:\u001b[m\n\u001b[38;5;9m-  override:\u001b[m\n\u001b[38;5;9m-    - bats/bin/bats test/*.bats\u001b[m\n\u001b[38;5;9m-    - shellcheck diff-so-fancy update-deps.sh\u001b[m\n\u001b[38;5;11mdiff --git a/hello.txt b/hello.txt\u001b[m\n\u001b[38;5;11mnew file mode 100644\u001b[m\n\u001b[38;5;11mindex 0000000..0c767bc\u001b[m\n\u001b[38;5;11m--- /dev/null\u001b[m\n\u001b[38;5;11m+++ b/hello.txt\u001b[m\n\u001b[36m@@ -0,0 +1 @@\u001b[m\n\u001b[38;5;10m+\u001b[m\u001b[38;5;10mHI THERE\u001b[m\n\u001b[38;5;11mdiff --git a/package.json b/package.json\u001b[m\n\u001b[38;5;11mdeleted file mode 100644\u001b[m\n\u001b[38;5;11mindex c7a1ab2..0000000\u001b[m\n\u001b[38;5;11m--- a/package.json\u001b[m\n\u001b[38;5;11m+++ /dev/null\u001b[m\n\u001b[36m@@ -1,29 +0,0 @@\u001b[m\n\u001b[38;5;9m-{\u001b[m\n\u001b[38;5;9m-  \"name\": \"diff-so-fancy\",\u001b[m\n\u001b[38;5;9m-  \"version\": \"0.5.0\",\u001b[m\n\u001b[38;5;9m-  \"description\": \"Good-lookin' diffs with diff-highlight and more\",\u001b[m\n\u001b[38;5;9m-  \"bin\": {\u001b[m\n\u001b[38;5;9m-    \"diff-so-fancy\": \"diff-so-fancy\",\u001b[m\n\u001b[38;5;9m-    \"diff-highlight\": \"third_party/diff-highlight/diff-highlight\"\u001b[m\n\u001b[38;5;9m-  },\u001b[m\n\u001b[38;5;9m-  \"repository\": {\u001b[m\n\u001b[38;5;9m-    \"type\": \"git\",\u001b[m\n\u001b[38;5;9m-    \"url\": \"git+https://github.com/so-fancy/diff-so-fancy.git\"\u001b[m\n\u001b[38;5;9m-  },\u001b[m\n\u001b[38;5;9m-  \"keywords\": [\u001b[m\n\u001b[38;5;9m-    \"git\",\u001b[m\n\u001b[38;5;9m-    \"diff\",\u001b[m\n\u001b[38;5;9m-    \"fancy\",\u001b[m\n\u001b[38;5;9m-    \"good-lookin'\",\u001b[m\n\u001b[38;5;9m-    \"diff-highlight\",\u001b[m\n\u001b[38;5;9m-    \"color\",\u001b[m\n\u001b[38;5;9m-    \"readable\",\u001b[m\n\u001b[38;5;9m-    \"highlight\"\u001b[m\n\u001b[38;5;9m-  ],\u001b[m\n\u001b[38;5;9m-  \"author\": \"Paul Irish\",\u001b[m\n\u001b[38;5;9m-  \"license\": \"MIT\",\u001b[m\n\u001b[38;5;9m-  \"bugs\": {\u001b[m\n\u001b[38;5;9m-    \"url\": \"https://github.com/so-fancy/diff-so-fancy/issues\"\u001b[m\n\u001b[38;5;9m-  },\u001b[m\n\u001b[38;5;9m-  \"homepage\": \"https://github.com/so-fancy/diff-so-fancy#readme\"\u001b[m\n\u001b[38;5;9m-}\u001b[m\n\u001b[38;5;11mdiff --git a/square.yml b/square.yml\u001b[m\n\u001b[38;5;11mnew file mode 100644\u001b[m\n\u001b[38;5;11mindex 0000000..18a04a3\u001b[m\n\u001b[38;5;11m--- /dev/null\u001b[m\n\u001b[38;5;11m+++ b/square.yml\u001b[m\n\u001b[36m@@ -0,0 +1,21 @@\u001b[m\n\u001b[38;5;10m+\u001b[m\u001b[38;5;10mmachine:\u001b[m\n\u001b[38;5;10m+\u001b[m\u001b[38;5;10m  environment:\u001b[m\n\u001b[38;5;10m+\u001b[m\n\u001b[38;5;10m+\u001b[m\n\u001b[38;5;10m+\u001b[m\u001b[38;5;10mdependencies:\u001b[m\n\u001b[38;5;10m+\u001b[m\u001b[38;5;10m  pre:\u001b[m\n\u001b[38;5;10m+\u001b[m\u001b[38;5;10m    - sudo add-apt-repository \"deb http://archive.ubuntu.com/ubuntu/ trusty-backports restricted main universe\";\u001b[m\n\u001b[38;5;10m+\u001b[m\u001b[38;5;10m    - sudo apt-get -y update\u001b[m\n\u001b[38;5;10m+\u001b[m\u001b[38;5;10m    - sudo apt-get -y install shellcheck;\u001b[m\n\u001b[38;5;10m+\u001b[m\n\u001b[38;5;10m+\u001b[m\u001b[38;5;10m  post:\u001b[m\n\u001b[38;5;10m+\u001b[m\u001b[38;5;10m    - git clone --depth 1 https://github.com/sstephenson/bats.git\u001b[m\n\u001b[38;5;10m+\u001b[m\n\u001b[38;5;10m+\u001b[m\u001b[38;5;10mcheckout:\u001b[m\n\u001b[38;5;10m+\u001b[m\u001b[38;5;10m  post:\u001b[m\n\u001b[38;5;10m+\u001b[m\u001b[38;5;10m    - git submodule update --init\u001b[m\n\u001b[38;5;10m+\u001b[m\n\u001b[38;5;10m+\u001b[m\u001b[38;5;10mtest:\u001b[m\n\u001b[38;5;10m+\u001b[m\u001b[38;5;10m  override:\u001b[m\n\u001b[38;5;10m+\u001b[m\u001b[38;5;10m    - bats/bin/bats test/*.bats\u001b[m\n\u001b[38;5;10m+\u001b[m\u001b[38;5;10m    - shellcheck diff-so-fancy update-deps.sh\u001b[m\n"
  },
  {
    "path": "test/fixtures/file-perms.diff",
    "content": "\u001b[38;5;227mdiff --git a/circle.yml b/circle.yml\u001b[m\n\u001b[38;5;227mold mode 100644\u001b[m\n\u001b[38;5;227mnew mode 100755\u001b[m\n\u001b[38;5;227mdiff --git a/foo.json b/foo.json\u001b[m\n\u001b[38;5;227mnew file mode 100644\u001b[m\n\u001b[38;5;227mindex 0000000..316a815\u001b[m\n\u001b[38;5;227m--- /dev/null\u001b[m\n\u001b[38;5;227m+++ b/foo.json\u001b[m\n\u001b[1;35m@@ -0,0 +1,29 @@\u001b[m\n\u001b[38;5;10m+\u001b[m\u001b[38;5;10m{\u001b[m\n\u001b[38;5;10m+\u001b[m\u001b[38;5;10m  \"name\": \"diff-so-fancy\",\u001b[m\n\u001b[38;5;10m+\u001b[m\u001b[38;5;10m  \"version\": \"0.5.1\",\u001b[m\n\u001b[38;5;10m+\u001b[m\u001b[38;5;10m  \"description\": \"Good-lookin' diffs with diff-highlight and more\",\u001b[m\n\u001b[38;5;10m+\u001b[m\u001b[38;5;10m  \"bin\": {\u001b[m\n\u001b[38;5;10m+\u001b[m\u001b[38;5;10m    \"diff-so-fancy\": \"diff-so-fancy\",\u001b[m\n\u001b[38;5;10m+\u001b[m\u001b[38;5;10m    \"diff-highlight\": \"third_party/diff-highlight/diff-highlight\"\u001b[m\n\u001b[38;5;10m+\u001b[m\u001b[38;5;10m  },\u001b[m\n\u001b[38;5;10m+\u001b[m\u001b[38;5;10m  \"repository\": {\u001b[m\n\u001b[38;5;10m+\u001b[m\u001b[38;5;10m    \"type\": \"git\",\u001b[m\n\u001b[38;5;10m+\u001b[m\u001b[38;5;10m    \"url\": \"git+https://github.com/so-fancy/diff-so-fancy.git\"\u001b[m\n\u001b[38;5;10m+\u001b[m\u001b[38;5;10m  },\u001b[m\n\u001b[38;5;10m+\u001b[m\u001b[38;5;10m  \"keywords\": [\u001b[m\n\u001b[38;5;10m+\u001b[m\u001b[38;5;10m    \"git\",\u001b[m\n\u001b[38;5;10m+\u001b[m\u001b[38;5;10m    \"diff\",\u001b[m\n\u001b[38;5;10m+\u001b[m\u001b[38;5;10m    \"fancy\",\u001b[m\n\u001b[38;5;10m+\u001b[m\u001b[38;5;10m    \"good-lookin'\",\u001b[m\n\u001b[38;5;10m+\u001b[m\u001b[38;5;10m    \"diff-highlight\",\u001b[m\n\u001b[38;5;10m+\u001b[m\u001b[38;5;10m    \"color\",\u001b[m\n\u001b[38;5;10m+\u001b[m\u001b[38;5;10m    \"readable\",\u001b[m\n\u001b[38;5;10m+\u001b[m\u001b[38;5;10m    \"highlight\"\u001b[m\n\u001b[38;5;10m+\u001b[m\u001b[38;5;10m  ],\u001b[m\n\u001b[38;5;10m+\u001b[m\u001b[38;5;10m  \"author\": \"Paul Irish\",\u001b[m\n\u001b[38;5;10m+\u001b[m\u001b[38;5;10m  \"license\": \"MIT\",\u001b[m\n\u001b[38;5;10m+\u001b[m\u001b[38;5;10m  \"bugs\": {\u001b[m\n\u001b[38;5;10m+\u001b[m\u001b[38;5;10m    \"url\": \"https://github.com/so-fancy/diff-so-fancy/issues\"\u001b[m\n\u001b[38;5;10m+\u001b[m\u001b[38;5;10m  },\u001b[m\n\u001b[38;5;10m+\u001b[m\u001b[38;5;10m  \"homepage\": \"https://github.com/so-fancy/diff-so-fancy#readme\"\u001b[m\n\u001b[38;5;10m+\u001b[m\u001b[38;5;10m}\u001b[m\n\u001b[38;5;227mdiff --git a/package.json b/package.json\u001b[m\n\u001b[38;5;227mdeleted file mode 100644\u001b[m\n\u001b[38;5;227mindex 316a815..0000000\u001b[m\n\u001b[38;5;227m--- a/package.json\u001b[m\n\u001b[38;5;227m+++ /dev/null\u001b[m\n\u001b[1;35m@@ -1,29 +0,0 @@\u001b[m\n\u001b[38;5;9m-{\u001b[m\n\u001b[38;5;9m-  \"name\": \"diff-so-fancy\",\u001b[m\n\u001b[38;5;9m-  \"version\": \"0.5.1\",\u001b[m\n\u001b[38;5;9m-  \"description\": \"Good-lookin' diffs with diff-highlight and more\",\u001b[m\n\u001b[38;5;9m-  \"bin\": {\u001b[m\n\u001b[38;5;9m-    \"diff-so-fancy\": \"diff-so-fancy\",\u001b[m\n\u001b[38;5;9m-    \"diff-highlight\": \"third_party/diff-highlight/diff-highlight\"\u001b[m\n\u001b[38;5;9m-  },\u001b[m\n\u001b[38;5;9m-  \"repository\": {\u001b[m\n\u001b[38;5;9m-    \"type\": \"git\",\u001b[m\n\u001b[38;5;9m-    \"url\": \"git+https://github.com/so-fancy/diff-so-fancy.git\"\u001b[m\n\u001b[38;5;9m-  },\u001b[m\n\u001b[38;5;9m-  \"keywords\": [\u001b[m\n\u001b[38;5;9m-    \"git\",\u001b[m\n\u001b[38;5;9m-    \"diff\",\u001b[m\n\u001b[38;5;9m-    \"fancy\",\u001b[m\n\u001b[38;5;9m-    \"good-lookin'\",\u001b[m\n\u001b[38;5;9m-    \"diff-highlight\",\u001b[m\n\u001b[38;5;9m-    \"color\",\u001b[m\n\u001b[38;5;9m-    \"readable\",\u001b[m\n\u001b[38;5;9m-    \"highlight\"\u001b[m\n\u001b[38;5;9m-  ],\u001b[m\n\u001b[38;5;9m-  \"author\": \"Paul Irish\",\u001b[m\n\u001b[38;5;9m-  \"license\": \"MIT\",\u001b[m\n\u001b[38;5;9m-  \"bugs\": {\u001b[m\n\u001b[38;5;9m-    \"url\": \"https://github.com/so-fancy/diff-so-fancy/issues\"\u001b[m\n\u001b[38;5;9m-  },\u001b[m\n\u001b[38;5;9m-  \"homepage\": \"https://github.com/so-fancy/diff-so-fancy#readme\"\u001b[m\n\u001b[38;5;9m-}\u001b[m\n"
  },
  {
    "path": "test/fixtures/file-rename.diff",
    "content": "diff --git a/Changes.new b/bin/Changes.new\nsimilarity index 100%\nrename from Changes.new\nrename to bin/Changes.new\ndiff --git a/dist.ini b/dist.ini\nindex ca8c2ad..d2874a4 100644\n--- a/dist.ini\n+++ b/dist.ini\n@@ -1,4 +1,4 @@\n-name    = csvgrep\n+name    = csvgrepper\n author  = Neil Bowers <neilb@cpan.org>\n license = Perl_5\n copyright_holder = Neil Bowers\n@@ -9,6 +9,7 @@ version = 0.05\n [@Basic]\n [PkgVersion]\n [AutoPrereqs]\n+[BAZ]\n \n [GithubMeta]\n [MetaJSON]\n"
  },
  {
    "path": "test/fixtures/file_copy.diff",
    "content": "diff --git a/first_file b/copied_file\nsimilarity index 100%\ncopy from first_file\ncopy to copied_file\n"
  },
  {
    "path": "test/fixtures/file_with_space.diff",
    "content": "diff --git a/a b b/a b\nnew file mode 100644\nindex 0000000..e69de29\n"
  },
  {
    "path": "test/fixtures/first-three-line.diff",
    "content": "commit fbf440dd9c32a60f9bc97693a6bd7b5ca87ec9fc\nAuthor: Steve Mao <maochenyan@gmail.com>\nDate:   Wed Mar 9 19:21:27 2016 +1100\n\n    0.6.2\n\ndiff --git package.json package.json\nindex 7379c98..3cba6ee 100644\n--- package.json\n+++ package.json\n@@ -1,6 +1,6 @@\n {\n   \"name\": \"diff-so-fancy\",\n-  \"version\": \"0.6.1\",\n+  \"version\": \"0.6.2\",\n   \"description\": \"Good-lookin' diffs with diff-highlight and more\",\n   \"bin\": {\n     \"diff-so-fancy\": \"diff-so-fancy\",\n"
  },
  {
    "path": "test/fixtures/hg.diff",
    "content": "diff -r 82e55d328c8c hello.c\n--- a/hello.c\tFri Aug 26 01:21:28 2005 -0700\n+++ b/hello.c\tFri Dec 29 14:37:26 2017 -0800\n@@ -1,16 +1,15 @@\n /*\n  * hello.c\n  *\n- * Placed in the public domain by Bryan O'Sullivan\n- *\n  * This program is not covered by patents in the United States or other\n  * countries.\n  */\n \n-#include <stdio.h>\n+#include <fstdio.h>\n \n int main(int argc, char **argv)\n {\n \tprintf(\"hello, world!\\n\");\n+\texit 99;\n \treturn 0;\n }\n"
  },
  {
    "path": "test/fixtures/hunk_no_comma.diff",
    "content": "diff --git i/file w/file\nindex 90be1f3..294186e 100644\n--- i/file\n+++ w/file\n@@ -1 +1 @@\n-before\n+after\n"
  },
  {
    "path": "test/fixtures/latin1.diff",
    "content": "\u001b[38;5;227mdiff --git a/test.txt b/test.txt\u001b[m\n\u001b[38;5;227mindex 5836369..51ccf71 100644\u001b[m\n\u001b[38;5;227m--- a/test.txt\u001b[m\n\u001b[38;5;227m+++ b/test.txt\u001b[m\n\u001b[1;35m@@ -1,6 +1 @@\u001b[m\n\u001b[38;5;9m-é\u001b[m\n\u001b[38;5;9m-\u001b[m\n\u001b[38;5;10m+\u001b[m\u001b[38;5;10mH cam h saw he conqurd\u001b[m\n"
  },
  {
    "path": "test/fixtures/leading-dashes.diff",
    "content": "diff --git i/lib/awful/widget/keyboardlayout.lua w/lib/awful/widget/keyboardlayout.lua\nindex f9142b8..b6e8ce0 100644\n--- i/lib/awful/widget/keyboardlayout.lua\n+++ w/lib/awful/widget/keyboardlayout.lua\n@@ -113,7 +113,7 @@ keyboardlayout.xkeyboard_country_code = {\n     [\"za\"] = true,    -- South Africa\n }\n\n--- Callback for updaing current layout\n+-- Callback for updating current layout.\n local function update_status (self)\n     self._current = awesome.xkb_get_layout_group();\n     local text = \"\"\n"
  },
  {
    "path": "test/fixtures/ls-function.diff",
    "content": "\u001b[1;33mdiff --git a/fish/functions/ls.fish b/fish/functions/ls.fish\u001b[m\n\u001b[1;33mindex 33c3d8b..fd54db2 100644\u001b[m\n\u001b[1;33m--- a/fish/functions/ls.fish\u001b[m\n\u001b[1;33m+++ b/fish/functions/ls.fish\u001b[m\n\u001b[1;35m@@ -7,6 +7,8 @@\u001b[m\n if begin\u001b[m\n     type gls 1>/dev/null 2>/dev/null\u001b[m\n     or command ls --version 1>/dev/null 2>/dev/null\u001b[m\n\u001b[1;32m+\u001b[m\n\u001b[1;32m+\u001b[m\u001b[1;32m    set -x CLICOLOR_FORCE 1\u001b[m\n   end\u001b[m\n   # This is GNU ls\u001b[m\n   function ls --description \"List contents of directory\"\u001b[m\n\u001b[1;35m@@ -22,11 +24,11 @@\u001b[m \u001b[mif begin\u001b[m\n     set param $param --human-readable\u001b[m\n     set param $param --sort=extension\u001b[m\n     set param $param --group-directories-first\u001b[m\n\u001b[1;31m-\tif isatty 1\u001b[m\n\u001b[1;32m+\u001b[m\u001b[1;32m    if isatty 1\u001b[m\n       set param $param --indicator-style=classify\u001b[m\n     end\u001b[m\n \u001b[m\n\u001b[1;31m-    eval \"env CLICOLOR_FORCE=1 command $ls $param $argv\"\u001b[m\n\u001b[1;32m+\u001b[m\u001b[1;32m    eval $ls $param \"$argv\"\u001b[m\n   end\u001b[m\n\u001b[1;31m-\u001b[m\n   if not set -q LS_COLORS\u001b[m"
  },
  {
    "path": "test/fixtures/mnemonicprefix.diff",
    "content": "\u001b[1;33mdiff --git i/diff-so-fancy w/diff-so-fancy\u001b[m\n\u001b[1;33mindex 2323d9b..a280985 100755\u001b[m\n\u001b[1;33m--- i/diff-so-fancy\u001b[m\n\u001b[1;33m+++ w/diff-so-fancy\u001b[m\n\u001b[1;35m@@ -48,10 +48,6 @@\u001b[m \u001b[mcolorize_context_line () {\u001b[m\n   $SED -E \"s/@@$reset_color $reset_color(.*$)/@@ $dim_magenta\\1/g\"\u001b[m\n }\u001b[m\n \u001b[m\n\u001b[1;31m-mark_empty_lines () {\u001b[m\n\u001b[1;31m-  $SED -E \"s/^$color_code_regex[\\+\\-]$reset_color\\s*$/$invert_color\\1 $reset_escape/g\"\u001b[m\n\u001b[1;31m-}\u001b[m\n\u001b[1;31m-\u001b[m\n strip_leading_symbols () {\u001b[m\n   # strip the + and -\u001b[m\n   $SED -E \"s/^($color_code_regex)[\\+\\-]/\\1 /g\"\u001b[m\n\u001b[1;35m@@ -83,7 +79,6 @@\u001b[m \u001b[mcat $input \\\u001b[m\n   | $diff_highlight \\\u001b[m\n   | format_diff_header \\\u001b[m\n   | colorize_context_line \\\u001b[m\n\u001b[1;31m-  | mark_empty_lines \\\u001b[m\n   | strip_leading_symbols \\\u001b[m\n   | strip_first_column \\\u001b[m\n   | print_header_clean\u001b[m\n\u001b[1;33mdiff --git i/libs/header_clean/header_clean.pl w/libs/header_clean/header_clean.pl\u001b[m\n\u001b[1;33mindex 23df5e7..54b3da1 100755\u001b[m\n\u001b[1;33m--- i/libs/header_clean/header_clean.pl\u001b[m\n\u001b[1;33m+++ w/libs/header_clean/header_clean.pl\u001b[m\n\u001b[1;35m@@ -9,6 +9,8 @@\u001b[m \u001b[mmy $remove_file_delete_header = 1;\u001b[m\n my $clean_permission_changes  = 1;\u001b[m\n my $change_hunk_indicators    = 1;\u001b[m\n \u001b[m\n\u001b[1;32m+\u001b[m\u001b[1;32muse Data::Dump::Color;\u001b[m\n\u001b[1;32m+\u001b[m\n #################################################################################\u001b[m\n \u001b[m\n my $ansi_sequence_regex = qr/(\\e\\[([0-9]{1,3}(;[0-9]{1,3}){0,3})[mK])?/;\u001b[m\n\u001b[1;35m@@ -29,12 +31,12 @@\u001b[m \u001b[mfor (my $i = 0; $i <= $#input; $i++) {\u001b[m\n \t########################################\u001b[m\n \t# Find the first file: --- a/README.md #\u001b[m\n \t########################################\u001b[m\n\u001b[1;31m-\t} elsif ($line =~ /^$ansi_sequence_regex--- (a\\/)?(.+?)(\\e|$)/) {\u001b[m\n\u001b[1;32m+\u001b[m\t\u001b[1;32m} elsif ($line =~ /^$ansi_sequence_regex--- ([abiwco]\\/)?(.+?)(\\e|$)/) {\u001b[m\n \t\t$file_1 = $5;\u001b[m\n \u001b[m\n \t\t# Find the second file on the next line: +++ b/README.md\u001b[m\n \t\tmy $next = $input[++$i];\u001b[m\n\u001b[1;31m-\t\t$next    =~ /^$ansi_sequence_regex\\+\\+\\+ (b\\/)?(.+?)(\\e|$)/;\u001b[m\n\u001b[1;32m+\u001b[m\t\t\u001b[1;32m$next    =~ /^$ansi_sequence_regex\\+\\+\\+ ([abiwco]\\/)?(.+?)(\\e|$)/;\u001b[m\n \t\tif ($1) {\u001b[m\n \t\t\tprint $1; # Print out whatever color we're using\u001b[m\n \t\t}\u001b[m\n\u001b[1;33mdiff --git i/test/header_clean.bats w/test/header_clean.bats\u001b[m\n\u001b[1;33mindex 4a3e7ee..a8385a5 100644\u001b[m\n\u001b[1;33m--- i/test/header_clean.bats\u001b[m\n\u001b[1;33m+++ w/test/header_clean.bats\u001b[m\n\u001b[1;35m@@ -50,3 +50,8 @@\u001b[m \u001b[moutput=$( load_fixture \"file-moves\" | $diff_so_fancy )\u001b[m\n \tassert_output --partial '@ setup-a-new-machine.sh:33 @'\u001b[m\n \tassert_output --partial '@ setup-a-new-machine.sh:218 @'\u001b[m\n }\u001b[m\n\u001b[1;32m+\u001b[m\n\u001b[1;32m+\u001b[m\u001b[1;32m@test \"mnemonicprefix\" {\u001b[m\n\u001b[1;32m+\u001b[m\t\u001b[1;32moutput=$( load_fixture \"mnemonicprefix\" | $diff_so_fancy )\u001b[m\n\u001b[1;32m+\u001b[m\t\u001b[1;32massert_output --partial '@ setup-a-new-machine.sh:33 @'\u001b[m\n\u001b[1;32m+\u001b[m\u001b[1;32m}\u001b[m\n\u001b[38;5;11mdiff --git c/hello.txt i/hello.txt\u001b[m\n\u001b[38;5;11mdeleted file mode 100644\u001b[m\n\u001b[38;5;11mindex 0c767bc..0000000\u001b[m\n\u001b[38;5;11m--- c/hello.txt\u001b[m\n\u001b[38;5;11m+++ /dev/null\u001b[m\n\u001b[36m@@ -1 +0,0 @@\u001b[m\n\u001b[38;5;10m-\u001b[m\u001b[38;5;10mHI THERE\u001b[m\n"
  },
  {
    "path": "test/fixtures/move_with_content_change.diff",
    "content": "diff --git a/a.txt b/b.txt\nsimilarity index 84%\nrename from a.txt\nrename to b.txt\nindex 6674929..1c877ab 100644\n--- a/a.txt\n+++ b/b.txt\n@@ -2,4 +2,4 @@ height: '10px',\n width: '100%',\n display: 'block',\n position: 'absolute',\n-bottom: '0',\n+bottom: '0'\n"
  },
  {
    "path": "test/fixtures/noprefix.diff",
    "content": "\u001b[1;33mdiff --git setup-a-new-machine.sh setup-a-new-machine.sh\u001b[m\n\u001b[1;33mindex 7b5996c..67eec2a 100755\u001b[m\n\u001b[1;33m--- setup-a-new-machine.sh\u001b[m\n\u001b[1;33m+++ setup-a-new-machine.sh\u001b[m\n\u001b[1;35m@@ -30,6 +30,7 @@\u001b[m \u001b[mcp -R ~/.gnupg ~/migration/home\u001b[m\n cp /Library/Preferences/SystemConfiguration/com.apple.airport.preferences.plist ~/migration  # wifi\u001b[m\n \u001b[m\n cp ~/Library/Preferences/net.limechat.LimeChat.plist ~/migration\u001b[m\n\u001b[1;32m+\u001b[m\u001b[1;32mcp ~/Library/Preferences/com.tinyspeck.slackmacgap.plist ~/migration\u001b[m\n \u001b[m\n cp -R ~/Library/Services ~/migration # automator stuff\u001b[m\n \u001b[m\n\u001b[1;35m@@ -215,5 +216,7 @@\u001b[m \u001b[msh .osx\u001b[m\n # symlink it up!\u001b[m\n ./symlink-setup.sh\u001b[m\n \u001b[m\n\u001b[1;32m+\u001b[m\u001b[1;32m# add manual symlink for .ssh/config and probably .config/fish\u001b[m\n\u001b[1;32m+\u001b[m\n ###\u001b[m\n ##############################################################################################################\u001b[m\n"
  },
  {
    "path": "test/fixtures/recursive_default_as_mercurial.diff",
    "content": "diff -r -bu core/app.py language/app.py\n--- core/app.py\t2022-06-08 13:42:10.658920131 +0900\n+++ language/app.py\t2022-06-08 12:07:22.773069512 +0900\n@@ -1,13 +1,13 @@\n-from flask import Flask, abort\n-from . import CORE_MODULES\n+from flask import Flask\n+from . import SECTION\n \n app = Flask(__name__)\n \n-@app.route('/<name>')\n+@app.route(f'/{SECTION}/<name>')\n def index(name):\n-    if name in CORE_MODULES:\n-        return \"Welcome to the documentation for the core module\"\n-    abort(404)\n+    return f\"Welcome to the documentation for {name}\"\n \n+if __name__ == \"__main__\":\n+    app.run(host=\"0.0.0.0\",port=3000)\n \n \ndiff -r -bu core/__init__.py language/__init__.py\n--- core/__init__.py\t2022-06-08 12:16:35.203331282 +0900\n+++ language/__init__.py\t2022-06-08 12:03:32.464264288 +0900\n@@ -1 +1 @@\n-CORE_MODULES=['base','utils','status']\n+SECTION=\"language\"\ndiff -r -bu core/README.md language/README.md\n--- core/README.md\t2022-06-08 13:52:56.962174912 +0900\n+++ language/README.md\t2022-06-08 13:52:39.498090643 +0900\n@@ -1,4 +1,4 @@\n-# Core\n+# Language\n \n ## Installation\n \n"
  },
  {
    "path": "test/fixtures/recursive_longhand_as_mercurial.diff",
    "content": "diff --recursive -u core/app.py language/app.py\n--- core/app.py\t2022-06-08 13:42:10.658920131 +0900\n+++ language/app.py\t2022-06-08 12:07:22.773069512 +0900\n@@ -1,13 +1,13 @@\n-from flask import Flask, abort\n-from . import CORE_MODULES\n+from flask import Flask\n+from . import SECTION\n \n app = Flask(__name__)\n \n-@app.route('/<name>')\n+@app.route(f'/{SECTION}/<name>')\n def index(name):\n-    if name in CORE_MODULES:\n-        return \"Welcome to the documentation for the core module\"\n-    abort(404)\n+    return f\"Welcome to the documentation for {name}\"\n \n+if __name__ == \"__main__\":\n+    app.run(host=\"0.0.0.0\",port=3000)\n \n \ndiff --recursive -u core/__init__.py language/__init__.py\n--- core/__init__.py\t2022-06-08 12:16:35.203331282 +0900\n+++ language/__init__.py\t2022-06-08 12:03:32.464264288 +0900\n@@ -1 +1 @@\n-CORE_MODULES=['base','utils','status']\n+SECTION=\"language\"\ndiff --recursive -u core/README.md language/README.md\n--- core/README.md\t2022-06-08 13:52:56.962174912 +0900\n+++ language/README.md\t2022-06-08 13:52:39.498090643 +0900\n@@ -1,4 +1,4 @@\n-# Core\n+# Language\n \n ## Installation\n \n"
  },
  {
    "path": "test/fixtures/remove_empty_file.diff",
    "content": "commit 0148734e753690a9207ebcb8cd117f343e230dec\nAuthor: Scott Baker <fake@domain.com>\nDate:   Wed Jul 19 08:12:45 2017 -0700\n\n    remvo\n\ndiff --git a/empty_file.txt b/empty_file.txt\ndeleted file mode 100644\nindex e69de29..0000000\n"
  },
  {
    "path": "test/fixtures/remove_slashn_eof.diff",
    "content": "diff --git a/test.txt b/test.txt\nindex 4cb29ea..54d55bf 100644\n--- a/test.txt\n+++ b/test.txt\n@@ -1,3 +1,3 @@\n one\n two\n-three\n+three\n\\ No newline at end of file\n"
  },
  {
    "path": "test/fixtures/single-line-remove.diff",
    "content": "diff --git test/data/readywaitasset.js test/data/readywaitasset.js\ndeleted file mode 100644\nindex 2308965..0000000\n--- test/data/readywaitasset.js\n+++ /dev/null\n@@ -1 +0,0 @@\n-var delayedMessage = \"It worked\";\n"
  },
  {
    "path": "test/fixtures/truecolor.diff",
    "content": "\u001b[33mdiff --git package.json package.json\u001b[m\n\u001b[33mindex 97965ab..f3ce90a 100644\u001b[m\n\u001b[33m--- package.json\u001b[m\n\u001b[33m+++ package.json\u001b[m\n\u001b[1;35m@@ -13,8 +13,8 @@\u001b[m\n     \"url\": \"git+https://github.com/so-fancy/diff-so-fancy.git\"\u001b[m\n   },\u001b[m\n   \"keywords\": [\u001b[m\n\u001b[1;38;2;220;50;47;48;2;0;43;54m-    \"git\",\u001b[m\n     \"diff\",\u001b[m\n\u001b[1;38;2;133;153;0;48;2;0;43;54m+\u001b[m\u001b[1;38;2;133;153;0;48;2;0;43;54m    \"git\",\u001b[m\n     \"fancy\",\u001b[m\n     \"good-lookin'\",\u001b[m\n     \"diff-highlight\",\u001b[m\n"
  },
  {
    "path": "test/fixtures/unicode.diff",
    "content": "\u001b[1;33mdiff --git a/unicodes b/unicodes\u001b[m\r\n\u001b[1;33mindex 223f57d..1c2066d 100644\u001b[m\r\n\u001b[1;33m--- a/unicodes\u001b[m\r\n\u001b[1;33m+++ b/unicodes\u001b[m\r\n\u001b[1;35m@@ -1 +1 @@\u001b[m\r\n\u001b[1;31m-aao\u001b[m\r\n\u001b[1;32m+\u001b[m\u001b[1;32måäöç\u001b[m\r\n"
  },
  {
    "path": "test/fixtures/weird.diff",
    "content": "diff -r 62e478a3f1c8 -r 47aeb87ce9cd doc/manual.xml.head\n--- a/doc/manual.xml.head\n+++ b/doc/manual.xml.head\n@@ -8352,7 +8352,7 @@\n <row><entry>-a</entry><entry>attach a file to a message</entry></row>\n <row><entry>-b</entry><entry>specify a blind carbon-copy (BCC) address</entry></row>\n <row><entry>-c</entry><entry>specify a carbon-copy (Cc) address</entry></row>\n-<row><entry>-d</entry><entry>log debugging output to ~/.muttdebug0 if mutt was complied with +DEBUG; it can range from 1-5 and affects verbosity (a value of 2 is recommended)</entry></row>\n+<row><entry>-d</entry><entry>log debugging output to ~/.muttdebug0 if mutt was compiled with +DEBUG; it can range from 1-5 and affects verbosity (a value of 2 is recommended)</entry></row>\n <row><entry>-D</entry><entry>print the value of all Mutt variables to stdout</entry></row>\n <row><entry>-E</entry><entry>edit the draft (-H) or include (-i) file</entry></row>\n <row><entry>-e</entry><entry>specify a config command to be run after initialization files are read</entry></row>\n"
  },
  {
    "path": "test/git-config.bats",
    "content": "#!/usr/bin/env bats\n\n# Used by both `setup_file` and `setup`, which are special bats callbacks.\n__load_imports__() {\n\tload 'test_helper/bats-support/load'\n\tload 'test_helper/bats-assert/load'\n\tload 'test_helper/util'\n}\n\n# ansi related values\nescape=$'\\e'\nansi_bold=1\nansi_dim=2\nansi_ul=4\nansi_reverse=7\n\n# hash of colors\ndeclare -Ag ansi_colors=(\n    [black]='0'\n    [red]='1'\n    [green]='2'\n    [yellow]='3'\n    [blue]='4'\n    [magenta]='5'\n    [cyan]='6'\n    [white]='7'\n    [default]='9'\n)\n\n# get a foreground or background color code\nansi_color() {\n    color=$1\n    incr=$2\n    base_code=$((30+$incr))\n\n    # handle bright prefix\n    if [[ \"${1}\" =~ (bright)(.*) ]]\n    then\n        color=${BASH_REMATCH[2]}\n        base_code=$((90+$incr))\n    fi\n    code=$(($base_code+${ansi_colors[$color]}))\n    echo \"$code\"\n}\n\n# get a foreground color code\nfg_color() {\n    ansi_color $1 0\n}\n\n# get a background color code\nbg_color() {\n    ansi_color $1 10\n}\n\n# get rgb color codes from hex\nrgb_color() {\n    incr=$2\n    base_code=$((38+$incr))\n    [[ $1 =~ ^.(..)(..)(..)$ ]]\n    rgb1=\"${BASH_REMATCH[1]}\"\n    rgb2=\"${BASH_REMATCH[2]}\"\n    rgb3=\"${BASH_REMATCH[3]}\"\n    echo \"${base_code};2;$(( 16#${rgb1} ));$(( 16#${rgb2} ));$(( 16#${rgb3} ))\"\n}\n\n# get a foreground color code\nfg_rgb_color() {\n    rgb_color $1 0\n}\n\n# get a background color code\nbg_rgb_color() {\n    rgb_color $1 10\n}\n\n# build config using passed in values\nsetup_dsf_git_config() {\n  GIT_CONFIG=\"$(dsf_test_git_config)\" || return $?\n  cat > \"${GIT_CONFIG}\" <<EOF || return $?\n[color \"diff\"]\n$1\n\nEOF\n  export GIT_CONFIG\n  export GIT_CONFIG_NOSYSTEM=1\n}\n\nsetup_file() {\n\t__load_imports__\n\t# setup_default_dsf_git_config\n}\n\nsetup() {\n\t__load_imports__\n}\n\nteardown_file() {\n\tteardown_default_dsf_git_config\n}\n\n# General description of how colors are applied\n# meta = header\n# frag = @ filenames\n# func = function name after frag\n# old  = - lines\n# new  = + lines\n\n@test \"Test color attributes are removed\" {\n    config=\"\n\tmeta = bold ul blink italic strike green no-reverse\n\tfrag = nobold nodim noul noblink noreverse noitalic nostrike blue white\n\tfunc = dim reverse white purple\n\told = no-bold no-dim no-ul no-blink no-reverse no-italic no-strike red yellow\n\tnew = brightgreen\n\twhitespace = red reverse\n    \"\n    setup_dsf_git_config \"$config\"\n\n\toutput=$( load_fixture \"leading-dashes\" | $diff_so_fancy )\n\trun printf \"%s\" \"$output\"\n\n    fg_green=$(fg_color green)\n    fg_blue=$(fg_color blue)\n    fg_red=$(fg_color red)\n    fg_brightgreen=$(fg_color brightgreen)\n    bg_white=$(bg_color white)\n    bg_yellow=$(bg_color yellow)\n\n    assert_line --index 0 --partial  \"${escape}[${ansi_bold};${ansi_ul};${fg_green}m\"\n    assert_line --index 3 --partial  \"${escape}[${fg_blue};${bg_white}m@\"\n    assert_line --index 6 --partial  \"${escape}[${fg_red};${bg_yellow}m--\"\n    assert_line --index 7 --partial  \"${escape}[${fg_brightgreen}m--\"\n}\n\n@test \"Test normal and default git colors\" {\n    config=\"\n\tfrag = normal default\n\told = default\n    \"\n    setup_dsf_git_config \"$config\"\n\n\toutput=$( load_fixture \"leading-dashes\" | $diff_so_fancy )\n\trun printf \"%s\" \"$output\"\n\n    fg_default=$(fg_color default)\n    bg_default=$(bg_color default)\n\n    assert_line --index 3 --partial  \"${escape}[${bg_default}m@\"\n    assert_line --index 6 --partial  \"${escape}[${fg_default}m--\"\n}\n\n# Special characters in git config must be quoted (word or all values)\n@test \"Test rgb git colors\" {\n    rgb=\"#408050\"\n    config=\"\n\tfrag = \\\"${rgb}\\\" default\n\told = normal \\\"${rgb}\\\"\n    \"\n    setup_dsf_git_config \"$config\"\n\n\toutput=$( load_fixture \"leading-dashes\" | $diff_so_fancy )\n\trun printf \"%s\" \"$output\"\n\n    fg_rgb=$(fg_rgb_color \"${rgb}\")\n    bg_default=$(bg_color default)\n    bg_rgb=$(bg_rgb_color \"${rgb}\")\n\n    assert_line --index 3 --partial  \"${escape}[${fg_rgb};${bg_default}m@\"\n    assert_line --index 6 --partial  \"${escape}[${bg_rgb}m--\"\n}\n"
  },
  {
    "path": "test/git_ansi_color.pl",
    "content": "#!/usr/bin/env perl\n\nuse strict;\nuse warnings;\nuse v5.16;\n\n###############################################################################\n###############################################################################\n\ncompare_color(\"\\e[1;91m\"               , git_ansi_color(\"brightred bold\")    , \"brightred bold\");\ncompare_color(\"\\e[1;31m\"               , git_ansi_color(\"red bold\")          , \"red bold\");\ncompare_color(\"\\e[1;32m\"               , git_ansi_color(\"green bold\")        , \"green bold\");\ncompare_color(\"\\e[33m\"                 , git_ansi_color(\"yellow\")            , \"yellow\");\ncompare_color(\"\\e[1;7;32m\"             , git_ansi_color(\"green bold reverse\"), \"green bold reverse\");\ncompare_color(\"\\e[1;35m\"               , git_ansi_color(\"magenta bold\")      , \"magenta bold\");\ncompare_color(\"\\e[1;38;5;146m\"         , git_ansi_color(\"146 bold\")          , \"146 bold\");\ncompare_color(\"\\e[1;38;5;146;48;5;22m\" , git_ansi_color(\"146 bold 22\")       , \"146 bold 22\");\ncompare_color(\"\\e[1;34;40m\"            , git_ansi_color(\"blue black bold\")   , \"blue black gold\");\ncompare_color(\"\\e[93m\"                 , git_ansi_color(\"11\")                , \"11\");\ncompare_color(\"\\e[7;31m\"               , git_ansi_color(\"red reverse\")       , \"red reverse\");\ncompare_color(\"\\e[1;31;48;5;52m\"       , git_ansi_color(\"red bold 52\")       , \"red bold 52\");\ncompare_color(\"\\e[92;48;5;20m\"         , git_ansi_color(\"10 20\")             , \"10 20\");\ncompare_color(\"\\e[30;47m\"              , git_ansi_color(\"0 7\")               , \"0 7\");\ncompare_color(\"\\e[94;105m\"             , git_ansi_color(\"12 13\")             , \"12 13\");\ncompare_color(\"\\e[1;38;5;254;48;5;255m\", git_ansi_color(\"254 bold 255\")      , \"254 bold 255\");\ncompare_color(\"\\e[1;38;5;238;42m\"      , git_ansi_color(\"238 bold green\")    , \"238 bold green\");\ncompare_color(\"\\e[1;38;5;238;42m\"      , git_ansi_color(\"238 green bold\")    , \"238 green bold\");\n\n###############################################################################\n###############################################################################\n\nsub compare_color {\n\tmy ($one, $two, $desc) = @_;\n\n\tif ($one ne $two) {\n\t\t#k($one, $two);\n\t\t#printf(\"No match for %-20s %s / %s\\n\", $desc, ansi_to_human($one), ansi_to_human($two));\n\t\tprintf(\"%-20s %sFAIL%s  %s ne %s\\n\", $desc, color('red'), color(), ansi_to_human($one), ansi_to_human($two));\n\t} else {\n\t\tprintf(\"%-20s %sOK%s\\n\", $desc, color('green'), color());\n\t}\n}\n\n# Convert an ANSI sequence to something printable\nsub ansi_to_human {\n\tmy $str = shift();\n\n\t$str =~ s/\\e\\[/[/g;\n\t$str =~ s/m\\b/]/g;\n\n\treturn $str;\n}\n\n# Alternate (unused) method to parse git config colors\nsub git_ansi_color2 {\n\tmy $str   = shift();\n\tmy @parts = split(' ', $str);\n\n\tif (!@parts) {\n\t\treturn '';\n\t}\n\n\tmy %map = qw(\n\t\tblack 30 red 31 green 32 yellow 33 blue 34 magenta 35 cyan 36 white 37\n\t\tbold 1 reverse 7\n\t);\n\n\tk(\\@parts);\n\n\tmy @ret;\n\tmy $first = 1;\n\tforeach my $item (@parts) {\n\t\tmy $val = $map{$item};\n\n\t\tif (!$val) {\n\t\t\tif (!$first && $item > 10) {\n\t\t\t\t$val = $item + 10;\n\t\t\t}\n\t\t\tpush(@ret, (38,5,$val));\n\t\t} else {\n\t\t\t# Background color\n\t\t\tif (!$first && $val > 10) {\n\t\t\t\t$val += 10;\n\t\t\t\t$first = 0;\n\t\t\t}\n\t\t\tpush(@ret, $val);\n\t\t}\n\n\t\tif ($val > 10) {\n\t\t\t$first = 0;\n\t\t}\n\t}\n\n\tmy $ret = \"\\e[\" . join(\";\", @ret) . \"m\";\n\n\tprint ansi_to_human($ret);\n\n\treturn $ret;\n}\n\n# https://www.git-scm.com/book/en/v2/Customizing-Git-Git-Configuration#_colors_in_git\nsub git_ansi_color {\n\tmy $str   = shift();\n\tmy @parts = split(' ', $str);\n\n\tif (!@parts) {\n\t\treturn '';\n\t}\n\tmy $colors = {\n\t\t'black'   => 0,\n\t\t'red'     => 1,\n\t\t'green'   => 2,\n\t\t'yellow'  => 3,\n\t\t'blue'    => 4,\n\t\t'magenta' => 5,\n\t\t'cyan'    => 6,\n\t\t'white'   => 7,\n\t};\n\n\tmy @ansi_part = ();\n\n\tif (grep { /bold/ } @parts) {\n\t\tpush(@ansi_part, \"1\");\n\t}\n\n\tif (grep { /reverse/ } @parts) {\n\t\tpush(@ansi_part, \"7\");\n\t}\n\n\t# Remove parts that aren't colors\n\t@parts = grep { exists $colors->{$_} || is_numeric($_) } @parts;\n\n\tmy $fg  = $parts[0] // \"\";\n\tmy $bg  = $parts[1] // \"\";\n\n\t#############################################\n\n\t# It's an numeric value, so it's an 8 bit color\n\tif (is_numeric($fg)) {\n\t\tif ($fg < 8) {\n\t\t\tpush(@ansi_part, $fg + 30);\n\t\t} elsif ($fg < 16) {\n\t\t\tpush(@ansi_part, $fg + 82);\n\t\t} else {\n\t\t\tpush(@ansi_part, \"38;5;$fg\");\n\t\t}\n\t# It's a simple 16 color OG ansi\n\t} elsif ($fg) {\n\t\tmy $bright    = $fg =~ s/bright//;\n\t\tmy $color_num = $colors->{$fg} + 30;\n\n\t\tif ($bright) { $color_num += 60; } # Set bold\n\n\t\tpush(@ansi_part, $color_num);\n\t}\n\n\t#############################################\n\n\t# It's an numeric value, so it's an 8 bit color\n\tif (is_numeric($bg)) {\n\t\tif ($bg < 8) {\n\t\t\tpush(@ansi_part, $bg + 40);\n\t\t} elsif ($bg < 16) {\n\t\t\tpush(@ansi_part, $bg + 92);\n\t\t} else {\n\t\t\tpush(@ansi_part, \"48;5;$bg\");\n\t\t}\n\t# It's a simple 16 color OG ansi\n\t} elsif ($bg) {\n\t\tmy $bright    = $bg =~ s/bright//;\n\t\tmy $color_num = $colors->{$bg} + 40;\n\n\t\tif ($bright) { $color_num += 60; } # Set bold\n\n\t\tpush(@ansi_part, $color_num);\n\t}\n\n\t#############################################\n\n\tmy $ansi_str = join(\";\", @ansi_part);\n\tmy $ret      = \"\\e[\" . $ansi_str . \"m\";\n\n\treturn $ret;\n}\n\nsub is_numeric {\n\tmy $s = shift();\n\n\tif ($s =~ /^\\d+$/) {\n\t\treturn 1;\n\t}\n\n\treturn 0;\n}\n\nsub trim {\n\tmy $s = shift();\n\tif (!defined($s) || length($s) == 0) { return \"\"; }\n\t$s =~ s/^\\s*//;\n\t$s =~ s/\\s*$//;\n\n\treturn $s;\n}\n\n# String format: '115', '165_bold', '10_on_140', 'reset', 'on_173', 'red', 'white_on_blue'\nsub color {\n\tmy $str = shift();\n\n\t# If we're NOT connected to a an interactive terminal don't do color\n\t#if (-t STDOUT == 0) { return ''; }\n\n\t# No string sent in, so we just reset\n\tif (!length($str) || $str eq 'reset') { return \"\\e[0m\"; }\n\n\t# Some predefined colors\n\tmy %color_map = qw(red 160 blue 27 green 34 yellow 226 orange 214 purple 93 white 15 black 0);\n\t$str =~ s|([A-Za-z]+)|$color_map{$1} // $1|eg;\n\n\t# Get foreground/background and any commands\n\tmy ($fc,$cmd) = $str =~ /^(\\d{1,3})?_?(\\w+)?$/g;\n\tmy ($bc)      = $str =~ /on_(\\d{1,3})$/g;\n\n\t# Some predefined commands\n\tmy %cmd_map = qw(bold 1 italic 3 underline 4 blink 5 inverse 7);\n\tmy $cmd_num = $cmd_map{$cmd // 0};\n\n\tmy $ret = '';\n\tif ($cmd_num)     { $ret .= \"\\e[${cmd_num}m\"; }\n\tif (defined($fc)) { $ret .= \"\\e[38;5;${fc}m\"; }\n\tif (defined($bc)) { $ret .= \"\\e[48;5;${bc}m\"; }\n\n\treturn $ret;\n}\n\n# Debug print variable using either Data::Dump::Color (preferred) or Data::Dumper\n# Creates methods k() and kd() to print, and print & die respectively\nBEGIN {\n\tif (eval { require Data::Dump::Color }) {\n\t\t*k = sub { Data::Dump::Color::dd(@_) };\n\t} else {\n\t\trequire Data::Dumper;\n\t\t*k = sub { print Data::Dumper::Dumper(\\@_) };\n\t}\n\n\tsub kd {\n\t\tk(@_);\n\n\t\tprintf(\"Died at %2\\$s line #%3\\$s\\n\",caller());\n\t\texit(15);\n\t}\n}\n\n# vim: tabstop=4 shiftwidth=4 autoindent softtabstop=4\n"
  },
  {
    "path": "test/test_helper/util.bash",
    "content": "\ndiff_so_fancy=\"$BATS_TEST_DIRNAME/../diff-so-fancy\"\n\nload_fixture() {\n  local name=\"$1\"\n  cat \"$BATS_TEST_DIRNAME/fixtures/${name}.diff\"\n}\n\nset_env() {\n  export LC_CTYPE=\"en_US.UTF-8\"\n}\n\ndsf_test_git_config() {\n  printf '%s/gitconfig' \"${BATS_TMPDIR}\"\n}\n\n# applying colors so ANSI color values will match\n# FIXME: not everyone will have these set, so we need to test for that.\nsetup_default_dsf_git_config() {\n  GIT_CONFIG=\"$(dsf_test_git_config)\" || return $?\n  cat > \"${GIT_CONFIG}\" <<EOF || return $?\n[color \"diff\"]\n\tmeta = 227\n\tfrag = magenta bold\n\tcommit = 227 bold\n\told = red bold\n\tnew = green bold\n\twhitespace = red reverse\n\tfunc = brightyellow\n\n[color \"diff-highlight\"]\n\toldNormal = red bold\n\toldHighlight = red bold 52\n\tnewNormal = green bold\n\tnewHighlight = green bold 22\nEOF\n  export GIT_CONFIG\n  export GIT_CONFIG_NOSYSTEM=1\n}\n\nteardown_default_dsf_git_config() {\n  local test_config\n  test_config=\"$(dsf_test_git_config)\" || return $?\n  [ ! -f \"${test_config}\" ] || rm -f \"${test_config}\"\n  GIT_CONFIG=/dev/null\n}\n"
  },
  {
    "path": "third_party/ansi-reveal/ansi-reveal.pl",
    "content": "#!/usr/bin/env perl\n\nuse strict;\nuse warnings;\nuse Getopt::Long;\n\nmy $raw = 0;\nGetOptions(\n\t'raw' => \\$raw,\n);\n\nif ($raw) {\n\toutput_raw();\n} else {\n\toutput_human();\n}\n\n###############################################\n\nsub output_human {\n\tmy $reset = \"\\e[0m\";\n\n\twhile (my $l = <>) {\n\t\t$l =~ s/(\\e\\[.*?m)/dump_ansi($1)/eg;\n\n\t\t## Basic reset\n\t\t#$l =~ s/(\\e\\[0?m)/${reset}[RESET]/g;\n\n\t\t## Bold text\n\t\t#$l =~ s/(\\e\\[1m)/${reset}[BOLD]/g;\n\n\t\t## Inverted text\n\t\t#$l =~ s/(\\e\\[7m)/${reset}. \"[INVRT]$1\"/eg;\n\t\t#$l =~ s/(\\e\\[7;(\\d+)m)/${reset}. \"[INVRT\" . sprintf(\"%03d\",($2-30)) . \"]$1\"/eg;\n\n\t\t## Basic 16 color ANSI\n\t\t##$l =~ s/(\\e\\[(3[0-7])m)/${reset}. \"[BASIC\" . sprintf(\"%03d\",($2-30)) . \"]$1\"/eg;\n\t\t#$l =~ s/(\\e\\[(3[0-7])m)/dump_ansi($1)/eg;\n\t\t##$l =~ s/(\\e\\[1;(3[0-7])m)/${reset}. \"[BRIGH\" . sprintf(\"%03d\",($2-30)) . \"]$1\"/eg;\n\t\t#$l =~ s/(\\e\\[1;(3[0-7])m)/dump_ansi($1)/eg;\n\n\t\t## 256 Color/Background\n\t\t#$l =~ s/(\\e\\[38;0?5;(\\d+)m)/${reset}. \"[COLOR\" . sprintf(\"%03d\",$2) . \"]$1\"/eg;\n\t\t#$l =~ s/(\\e\\[48;0?5;(\\d+)m)/${reset}. \"[BACKG\" . sprintf(\"%03d\",$2) . \"]$1\"/eg;\n\n\t\t#$l =~ s/(\\e\\[1;(3[0-7]);48;5;(\\d+)m)/${reset}. \"[FGBG\" . sprintf(\"%02d:%02d\",($2-30),($3)) . \"]$1\"/eg;\n\t\t#$l =~ s/(\\e\\[1;38;5;(.*?)m)/${reset} . dump_ansi($1) . \"$1\"/eg;\n\n\t\t## 24bit Color/Background\n\t\t#$l =~ s/(\\e\\[38;2;(\\d+);(\\d+);(\\d+)m)/${reset} . sprintf(\"[RGB#%02X%02X%02X\",$2,$3,$4) . \"]$1\"/eg;\n\t\t#$l =~ s/(\\e\\[48;2;(\\d+);(\\d+);(\\d+)m)/${reset} . sprintf(\"[RGBBG#%02X%02X%02X\",$2,$3,$4) . \"]$1\"/eg;\n\n\t\tprint $l;\n\t}\n}\n\n###############################################\n\nsub output_raw {\n\twhile (<>) {\n\t\ts/\\e/\\\\e/g;\n\t\tprint;\n\t}\n}\n\nsub dump_ansi {\n\tmy $str   = shift();\n\tif ($str !~ /^\\e/) {\n\t\treturn \"\";\n\t}\n\n\tmy $raw   = $str;\n\tmy $human = $str =~ s/\\e/ESC/rg;\n\n\t# Remove the ANSI control chars, we just want the payload\n\t$str =~ s/^\\e\\[//g;\n\t$str =~ s/m$//g;\n\n\t# Make the [HUMAN] text reset and white to make it easier to see\n\tmy $ret  = \"\\e[0m\";\n\t$ret    .= \"\\e[38;5;15m\";\n\n\tmy @parts = split(\";\",$str);\n\n\t#k(\\@parts);\n\n\tmy @basic_mapping = qw(BLACK RED GREEN YELLW BLUE MAGNT CYAN WHITE);\n\n\tif (!@parts) {\n\t\t$ret .= \"[RESET]\";\n\t}\n\n\tfor (my $count = 0; $count < @parts; $count++) {\n\t\tmy $p = $parts[$count];\n\n\t\t#print \"[$count = '$p']\\n\";\n\t\tif ($p eq \"1\") {\n\t\t\t$ret .= \"[BOLD]\";\n\t\t} elsif ($p eq \"0\" || $p eq \"\") {\n\t\t\t$ret .= \"[RESET]\";\n\t\t} elsif ($p eq \"7\") {\n\t\t\t$ret .= \"[REVERSE]\";\n\t\t} elsif ($p eq \"27\") {\n\t\t\t$ret .= \"[NOTREV]\";\n\t\t} elsif ($p eq \"38\") {\n\t\t\tmy $next  = $parts[$count + 1];\n\t\t\tmy $color = $parts[$count + 2];\n\n\t\t\t$count += 2;\n\n\t\t\t$ret .= sprintf(\"[COLOR%03d]\",$color);\n\t\t} elsif ($p eq \"48\") {\n\t\t\tmy $next  = $parts[++$count];\n\t\t\tmy $color = $parts[++$count];\n\n\t\t\t$count += 2;\n\n\t\t\t$ret .= sprintf(\"[BACKG%03d]\",$color);\n\t\t} elsif ($p >= 30 and $p <= 37) {\n\t\t\tmy $color = $p - 30;\n\t\t\t$color = $basic_mapping[$color];\n\t\t\t$ret .= \"[$color]\";\n\t\t\t#$ret .= sprintf(\"[BASIC%03d]\",$color);\n\t\t} else {\n\t\t\t$ret .= \"[UKN: $p]\";\n\t\t}\n\t}\n\n\t# Append the ANSI color string to end of the human readable one\n\t$ret .= $raw;\n\n\treturn $ret;\n}\n\nBEGIN {\n\tif (eval { require Data::Dump::Color }) {\n\t\t*k = sub { Data::Dump::Color::dd($_[0]) };\n\t} else {\n\t\trequire Data::Dumper;\n\t\t*k = sub { print Data::Dumper::Dumper($_[0]) };\n\t}\n}\n\n# vim: tabstop=4 shiftwidth=4 autoindent softtabstop=4\n"
  },
  {
    "path": "third_party/build_fatpack/build.pl",
    "content": "#!/usr/bin/env perl\n\n###########################################################################\n# This will build a stand-a-lone version of diff-so-fancy using Fatpack\n#\n# Scott Baker - 2017-06-28\n#\n# Usage: perl build.pl [--output /tmp/diff-so-fancy]\n###########################################################################\n\nuse strict;\nuse warnings;\nuse File::Basename;\nuse Cwd qw(abs_path getcwd);\n\nmy $args = argv();\nmy $ok   = has_fatpack();\n\nif (!$ok) {\n\tprintf(\"%sError:%s App::FatPacker must be installed to build diff-so-fancy\\n\",color('red_bold'),color('reset'));\n\texit;\n}\n\nmy $output_file = \"/tmp/diff-so-fancy\";\nmy $input_file  = \"diff-so-fancy\";\n\n# Allow overriding the output file\nif ($args->{output}) {\n\t$output_file = $args->{output};\n}\n\nmy $dir  = dirname($0);\nmy $root = abs_path(\"$dir/../../\");\nmy $cwd  = getcwd();\n\n# Change to the root of the tree\nchdir($root);\nmy $dsf_version = get_version($input_file);\n\nmy $cmd = \"fatpack pack $input_file 2>/dev/null > $output_file\";\n\nmy $exit = system($cmd);\n$exit  >>= 8;\n\nrmdir(\"fatlib\"); # fatpack leaves empty fatlib dirs so we remove them\nmy $size = -s $output_file;\n\nmy $good  = color(\"82bold\");\nmy $bad   = color(\"160bold\");\nmy $warn  = color(\"226bold\");\nmy $vers  = color(\"230bold\");\nmy $white = color(\"15bold\");\nmy $reset = color();\n\nif (!$exit) {\n\tprint \"${good}Success:${reset} Wrote diff-so-fancy ${vers}v$dsf_version${reset} to $output_file ($size bytes)\\n\";\n\tchmod 0755,$output_file; # Make the output executable\n} else {\n\tprint \"${bad}Error  :${reset} Fatpack failed to build $output_file with exit code: ${warn}$exit${reset}\\n\";\n\tprint \"${white}Command: $cmd$reset\\n\";\n}\n\nchdir($cwd);\n\n#############################################################################\n\nsub argv {\n\tmy $ret = {};\n\n\tfor (my $i = 0; $i < scalar(@ARGV); $i++) {\n\t\t# If the item starts with \"-\" it's a key\n\t\tif ((my ($key) = $ARGV[$i] =~ /^--?([a-zA-Z_]\\w*)/) && ($ARGV[$i] !~ /^-\\w\\w/)) {\n\t\t\t# If the next item does not start with \"--\" it's the value for this item\n\t\t\tif (defined($ARGV[$i + 1]) && ($ARGV[$i + 1] !~ /^--?\\D/)) {\n\t\t\t\t$ret->{$key} = $ARGV[$i + 1];\n\t\t\t# Bareword like --verbose with no options\n\t\t\t} else {\n\t\t\t\t$ret->{$key}++;\n\t\t\t}\n\t\t}\n\t}\n\n\t# We're looking for a certain item\n\tif ($_[0]) { return $ret->{$_[0]}; }\n\n\treturn $ret;\n}\n\nsub pfile {\n\tmy $file = shift();\n\tif (!-r $file) { return ''; } # Make sure the file is readable\n\n\tmy $ret = '';\n\n\topen(INPUT, \"<\", $file);\n\twhile (<INPUT>) {\n\t\t$ret .= $_;\n\t}\n\tclose INPUT;\n\n\tif (wantarray) {\n\t\treturn split(/\\n/,$ret);\n\t} else {\n\t\treturn $ret;\n\t}\n}\n\nsub get_version {\n\tmy $file  = shift();\n\tmy @lines = pfile($file);\n\n\tforeach my $l (@lines) {\n\t\tif ($l =~ /\\$VERSION\\s+=\\s+\"(.+?)\"/) {\n\t\t\tmy $ver = $1;\n\t\t\treturn $ver;\n\t\t}\n\t}\n\n\treturn undef;\n}\n\nsub has_fatpack {\n\tmy $out = `which fatpack 2>/dev/null`;\n\n\treturn trim($out);\n}\n\nsub trim {\n\tmy $s = shift();\n\tif (length($s) == 0) { return \"\"; }\n\t$s =~ s/^\\s*|\\s*$//g;\n\n\treturn $s;\n}\n\n# String format: '115', '165_bold', '10_on_140', 'reset', 'on_173'\nsub color {\n\tmy $str = shift();\n\n\t# If we're NOT connected to a an interactive terminal don't do color\n\tif (-t STDOUT == 0) { return ''; }\n\n\t# No string sent in, so we just reset\n\tif (!length($str) || $str eq 'reset') { return \"\\e[0m\"; }\n\n\t# Some predefined colors\n\tmy %color_map = qw(red 160 blue 21 green 34 yellow 226 orange 214 purple 93 white 15 black 0);\n\t$str =~ s/$_/$color_map{$_}/g for keys %color_map;\n\n\t# Get foreground/background and any commands\n\tmy ($fc,$cmd) = $str =~ /^(\\d+)?_?(\\w+)?/g;\n\tmy ($bc)      = $str =~ /on_?(\\d+)$/g;\n\n\t# Some predefined commands\n\tmy %cmd_map = qw(bold 1 italic 3 underline 4 blink 5 inverse 7);\n\tmy $cmd_num = $cmd_map{$cmd || 0};\n\n\tmy $ret = '';\n\tif ($cmd_num)     { $ret .= \"\\e[${cmd_num}m\"; }\n\tif (defined($fc)) { $ret .= \"\\e[38;5;${fc}m\"; }\n\tif (defined($bc)) { $ret .= \"\\e[48;5;${bc}m\"; }\n\n\treturn $ret;\n}\n\n# vim: tabstop=4 shiftwidth=4 autoindent softtabstop=4\n"
  },
  {
    "path": "third_party/cli_bench/cli_bench.pl",
    "content": "#!/usr/bin/env perl\n\nuse strict;\nuse warnings;\nuse v5.16;\n\n###############################################################################\n# Used to benchmark optimizations to CLI scripts\n#\n# Usage: cli_bench [--num 50] 'cat /tmp/diff.patch | diff-so-fancy'\n###############################################################################\n\nuse Time::HiRes qw(time);\nuse Getopt::Long;\n\n###############################################################################\n###############################################################################\n\nmy $num     = 50;\nmy $ignore  = 1;\nmy $details = 1;\n\nmy $ok = GetOptions(\n\t'num=i'    => \\$num,\n\t'ignore=i' => \\$ignore,\n\t'details!' => \\$details,\n);\n\n\nmy $cmd = trim(join(\" \", @ARGV));\n\nif (!$cmd) {\n\tdie(usage());\n}\n\n$| = 0; # Disable output buffering\n\nmy @res;\nmy $out;\nmy $exit = 0;\nfor (my $i = 0; $i < ($num + $ignore); $i++) {\n\tmy $start = time();\n\t$out      = `$cmd`;\n\t$exit     = $? >> 8;\n\n\tmy $total = int((time() - $start) * 1000);\n\tpush(@res, $total);\n\n\tprint \".\";\n}\n\nprint \"\\n\";\n\n# Throw away the first X to give things time to warm up and be cached\n@res = splice(@res, $ignore);\n\n# Remove the top and bottom 10%\nmy $outlier = $num / 10;\n@res = sort(@res);\n@res = splice(@res, $outlier, $num - $outlier * 2);\n\nmy $avg = sprintf(\"%.1f\", average(@res));\n\nif ($details) {\n\tshow_details(@res);\n\tprint \"\\n\";\n}\n\nprint \"Ran '$cmd' $num times with average completion time of $avg ms\\n\";\n\nif ($exit != 0) {\n\tprint $out;\n}\n\n###############################################################################\n###############################################################################\n\nsub show_details {\n\tmy @res = @_;\n\n\tmy $x   = {};\n\tmy $max = 0;\n\n\t# Build a hash of all the times:count\n\tforeach my $time (@res) {\n\t\t$x->{$time}++;\n\n\t\tif ($x->{$time} > $max) {\n\t\t\t$max = $x->{$time};\n\t\t}\n\t}\n\n\tmy $target_width = 100; # How wide we want the bar + text\n\tmy $total        = scalar(@res);\n\tmy $scale        = ($target_width - 15) / $max;\n\n\tprint \"\\n\";\n\n\t# Print out a basic histogram of the times\n\tforeach my $time (sort(keys %$x)) {\n\t\tmy $count   = $x->{$time};\n\t\tmy $percent = sprintf(\"%0.1f\", ($count / $total) * 100);\n\n\t\tmy $bar = \"%\" x ($count * $scale);\n\t\tprint \"$time ms: $bar ($percent%)\\n\";\n\t}\n}\n\nsub average {\n\tmy $ret = 0;\n\n\tforeach (@_) {\n\t\t$ret += $_;\n\t}\n\n\tmy $count = scalar(@_);\n\t$ret     /= $count;\n\n\treturn $ret;\n}\n\nsub random_int {\n\tmy $ret = rand() * 90 + 10;\n\t$ret    = int($ret);\n\n\treturn $ret;\n}\n\nsub round {\n\tmy $num = shift();\n\n\t#https://stackoverflow.com/questions/178539/how-do-you-round-a-floating-point-number-in-perl\n\t#my $ret = int($num + $num/abs($num * 2 || 1));\n\n\tmy $ret;\n\tif ($num < 0) {\n\t\t$ret = int($num - 0.5);\n\t} else {\n\t\t$ret = int($num + 0.5);\n\t}\n\n\treturn $ret;\n}\n\nsub trim {\n\tmy $s = shift();\n\tif (!defined($s) || length($s) == 0) { return \"\"; }\n\t$s =~ s/^\\s*//;\n\t$s =~ s/\\s*$//;\n\n\treturn $s;\n}\n\n# String format: '115', '165_bold', '10_on_140', 'reset', 'on_173', 'red', 'white_on_blue'\nsub color {\n\tmy $str = shift();\n\n\t# If we're NOT connected to a an interactive terminal don't do color\n\tif (-t STDOUT == 0) { return ''; }\n\n\t# No string sent in, so we just reset\n\tif (!length($str) || $str eq 'reset') { return \"\\e[0m\"; }\n\n\t# Some predefined colors\n\tmy %color_map = qw(red 160 blue 27 green 34 yellow 226 orange 214 purple 93 white 15 black 0);\n\t$str =~ s|([A-Za-z]+)|$color_map{$1} // $1|eg;\n\n\t# Get foreground/background and any commands\n\tmy ($fc,$cmd) = $str =~ /^(\\d{1,3})?_?(\\w+)?$/g;\n\tmy ($bc)      = $str =~ /on_(\\d{1,3})$/g;\n\n\t# Some predefined commands\n\tmy %cmd_map = qw(bold 1 italic 3 underline 4 blink 5 inverse 7);\n\tmy $cmd_num = $cmd_map{$cmd // 0};\n\n\tmy $ret = '';\n\tif ($cmd_num)     { $ret .= \"\\e[${cmd_num}m\"; }\n\tif (defined($fc)) { $ret .= \"\\e[38;5;${fc}m\"; }\n\tif (defined($bc)) { $ret .= \"\\e[48;5;${bc}m\"; }\n\n\treturn $ret;\n}\n\nsub file_get_contents {\n\tmy $file = shift();\n\topen (my $fh, \"<\", $file) or return undef;\n\n\tmy $ret;\n\twhile (<$fh>) { $ret .= $_; }\n\n\treturn $ret;\n}\n\nsub file_put_contents {\n\tmy ($file, $data) = @_;\n\topen (my $fh, \">\", $file) or return undef;\n\n\tprint $fh $data;\n\treturn length($data);\n}\n\n# Debug print variable using either Data::Dump::Color (preferred) or Data::Dumper\n# Creates methods k() and kd() to print, and print & die respectively\nBEGIN {\n\tif (eval { require Data::Dump::Color }) {\n\t\t*k = sub { Data::Dump::Color::dd(@_) };\n\t} else {\n\t\trequire Data::Dumper;\n\t\t*k = sub { print Data::Dumper::Dumper(\\@_) };\n\t}\n\n\tsub kd {\n\t\tk(@_);\n\n\t\tprintf(\"Died at %2\\$s line #%3\\$s\\n\",caller());\n\t\texit(15);\n\t}\n}\n\nsub usage {\n\treturn \"Usage: $0 [--num 50] 'cat /tmp/simple.diff | diff-so-fancy'\\n\";\n}\n\n# vim: tabstop=4 shiftwidth=4 autoindent softtabstop=4\n"
  },
  {
    "path": "third_party/term-colors/term-colors.pl",
    "content": "#!/usr/bin/perl\n\nuse strict;\nuse warnings;\n\nmy $args   = join(\" \",@ARGV);\nmy ($perl) = $args =~ /--perl/;\nmy ($both) = $args =~ /--both/;\n\n# If we want both, we set perl also\nif ($both) {\n\t$perl = 1;\n}\n\n# Term::ANSIColor didn't get 256 color constants until 4.0\nif ($perl && has_term_ansicolor(4.0)) {\n\trequire Term::ANSIColor;\n\tTerm::ANSIColor->import(':constants','color','uncolor');\n\n\t#print \"TERM::ANSIColor constant names:\\n\";\n\tterm_ansicolor();\n} else {\n\tmy $padding = 2;\n\tmy $section  = 1;\n\tmy $grouping = 8;\n\n\tfor (my $i = 0; $i < 256; $i++) {\n\t\tprint set_bcolor($i); # Set the background color\n\n\t\tif (needs_white($i)) {\n\t\t\tprint set_fcolor(15); # White\n\t\t} else {\n\t\t\tprint set_fcolor(0); # Black\n\t\t}\n\t\tprintf(\" \" x $padding . \"%03d\" . \" \" x $padding,$i);\n\n\t\tprint set_fcolor(); # Reset both colors\n\t\tprint \"  \";         # Seperator\n\n\t\tif ($i == 15 || $i == 231) {\n\t\t\tprint set_bcolor(); # Reset\n\t\t\tprint \"\\n\\n\";\n\t\t\t$section  = 0;\n\t\t\t$grouping = 6;\n\t\t\t$padding = 4;\n\t\t} elsif ($section > 0 && ($section % $grouping == 0)) {\n\t\t\tprint set_bcolor(); # Reset\n\t\t\tprint \"\\n\";\n\t\t}\n\n\t\t$section++;\n\t}\n}\n\nEND {\n\tprint set_fcolor(); # Reset the colors\n\tprint \"\\n\";\n}\n\n#################################################################################\n\nsub has_term_ansicolor {\n\tmy $version = shift();\n\n\teval {\n\t\t# Check if we have Term::ANSIColor version 4.0\n\t\trequire Term::ANSIColor;\n\t\tTerm::ANSIColor->VERSION($version);\n\t};\n\n\tif ($@) {\n\t\treturn 0;\n\t} else {\n\t\treturn 1;\n\t}\n}\n\nsub set_fcolor {\n\tmy $c = shift();\n\n\tmy $ret = '';\n\tif (!defined($c)) { $ret = \"\\e[0m\"; } # Reset the color\n\telse { $ret = \"\\e[38;5;${c}m\"; }\n\n\treturn $ret;\n}\n\nsub set_bcolor {\n\tmy $c = shift();\n\n\tmy $ret = '';\n\tif (!defined($c)) { $ret = \"\\e[0m\"; } # Reset the color\n\telse { $ret .= \"\\e[48;5;${c}m\"; }\n\n\treturn $ret;\n}\n\nsub get_color_mapping {\n\tmy $map = {};\n\n\tfor (my $i = 0; $i < 256; $i++) {\n\t\tmy $str = \"\\e[38;5;${i}m\";\n\t\tmy ($acc) = uncolor($str);\n\n\t\t$map->{$acc} = int($i);\n\t}\n\n\treturn $map;\n}\n\nsub term_ansicolor {\n\tmy @colors = get_color_names();\n\tmy $map    = get_color_mapping();\n\n\tmy $absolute = 0;\n\tmy $group    = 0;\n\tmy $grouping = 8;\n\n\tprint \"Showing Term::ANSIColor constant names\\n\\n\";\n\n\tforeach my $name (@colors) {\n\t\tmy $bg          = \"on_$name\";\n\t\tmy $map_num     = int($map->{$name});\n\t\tmy $perl_name   = sprintf(\"%6s\",$name);\n\t\tmy $ansi_number = sprintf(\"#%03i\",$map_num);\n\n\t\tmy $name_string = \"\";\n\t\tif ($both) {\n\t\t\t$name_string = \"$perl_name / $ansi_number\";\n\t\t} else {\n\t\t\t$name_string = \"$perl_name\";\n\t\t}\n\n\t\tif (needs_white($map_num)) {\n\t\t\tprint color($bg) . \"   \" . color('bright_white') . $name_string . \"   \";\n\t\t} else {\n\t\t\tprint color($bg) . \"   \" . color(\"black\") . $name_string . \"   \";\n\t\t}\n\t\tprint color('reset') . \"  \";\n\n\t\t$absolute++;\n\t\t$group++;\n\n\t\tif ($absolute == 16 || $absolute == 232) {\n\t\t\tprint \"\\n\\n\";\n\t\t\t$group    = 0;\n\t\t\t$grouping = 6;\n\t\t} elsif ($group % $grouping == 0) {\n\t\t\tprint \"\\n\";\n\t\t}\n\t}\n}\n\nsub get_color_names {\n\tmy @colors    = ();\n\tmy ($r,$g,$b) = 0;\n\n\tfor (my $i = 0; $i < 16; $i++) {\n\t\tmy $name = \"ansi$i\";\n\t\tpush(@colors,$name);\n\t}\n\n\tfor ($r = 0; $r <= 5; $r++) {\n\t\tfor ($g = 0; $g <= 5; $g++) {\n\t\t\tfor ($b = 0; $b <= 5; $b++) {\n\t\t\t\tmy $name = \"rgb$r$g$b\";\n\t\t\t\tpush(@colors,$name);\n\t\t\t}\n\t\t}\n\t}\n\n\tfor (my $i = 0; $i < 24; $i++) {\n\t\tmy $name = \"grey$i\";\n\t\tpush(@colors,$name);\n\t}\n\n\treturn @colors;\n}\n\nsub needs_white {\n\t# Sorta lame, but it's a hard coded list of which background colors need a white foreground\n\tmy @needs_white = qw(0 1 4 5 8 232 233 234 235 236 237 238 239 240 241 242 243 16 17 18\n\t19 20 21 22 28 52 53 54 55 25 56 57 58 59 60 88 89 90 91 92 93 124 125 29 30 31 26\n\t27 61 62 64 160 196 161 126 63 94 95 100 101 127 128 129 12 130 131 23 24);\n\n\tmy $num = shift();\n\tmy $ret = in_array($num, @needs_white);\n\n\treturn $ret;\n}\n\nsub in_array {\n\tmy ($needle, @haystack) = @_;\n\n\tforeach my $l (@haystack) {\n\t\tif ($l == $needle) { return 1; }\n\t}\n\n\treturn 0;\n}\n"
  },
  {
    "path": "update-deps.sh",
    "content": "#!/bin/bash\n\n# initialize the bats components\ngit submodule sync && git submodule update --init\n"
  }
]