[
  {
    "path": "LICENSE",
    "content": "MIT License\n\nCopyright (c) 2020 Matt Green\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 all\ncopies 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 THE\nSOFTWARE.\n"
  },
  {
    "path": "README.md",
    "content": "# lucid\n\nA minimalist, high-performance fish prompt with async git dirty checks that just work.\n\n![Preview](https://user-images.githubusercontent.com/56996/98549007-9520be80-22dd-11eb-884f-b688c9fd26cf.png)\n\n## Features\n\n* Classy, minimal left prompt that surfaces only actionable information\n* Asynchronous git dirty state prevents prompt-induced lag even on [massive repositories](https://github.com/llvm/llvm-project)\n* Shows current git branch/detached HEAD commit, action (if any), and dirty state\n* Restrained use of color and Unicode symbols\n* Single file, well-commented, zero-dependency implementation\n\n## Installation\n\n### System Requirements\n\n* [Fish](https://fishshell.com/) ≥ 3.1.0\n\nInstall with [Fisher](https://github.com/jorgebucaran/fisher):\n\n```console\nfisher install mattgreen/lucid.fish\n```\n\n## Performance\n\nInitial rendering is fast enough, considering that the LLVM repo has over **361,000 commits**:\n\n```shell\n~/Projects/llvm-project on master •\n❯ time fish_prompt\n\n~/Projects/llvm-project on master •\n❯\n________________________________________________________\nExecuted in   14.05 millis    fish           external\n   usr time    5.96 millis    2.10 millis    3.86 millis\n   sys time    6.87 millis    2.54 millis    4.33 millis\n```\n\nlucid fetches most git information synchronously. This minimizes the amount of flicker induced by prompt redraws, which can be distracting. This initial time encompasses:\n\n1. getting the git working directory\n2. retrieving the current branch\n3. figuring out the current action (merge, rebase)\n4. starting the async dirty check\n\nThis information is memoized to avoid re-computation during prompt redraws, which occur upon completion of the git dirty check, or window resizes.\n\n## Customization\n\n* `lucid_dirty_indicator`: displayed when a repository is dirty. Default: `•`\n* `lucid_clean_indicator`: displayed when a repository is clean. Should be at least as long as `lucid_dirty_indicator` to work around a fish bug. Default: ` ` (a space)\n* `lucid_cwd_color`: color used for current working directory. Default: `green`\n* `lucid_git_color`: color used for git information. Default: `blue`\n* `lucid_git_status_in_home_directory`: if set, git information is also\n   displayed in the home directory. Default: not set\n* `lucid_skip_newline`: if set, doesn't insert a newline before the prompt.\n   Default: not set\n* `lucid_prompt_symbol`: the prompt symbol. Default: `❯`\n* `lucid_prompt_symbol_error`: the prompt symbol when an error occurs.\n   Default: `❯`\n* `lucid_prompt_symbol_color`: the color of the prompt symbol.\n   Default: `$fish_color_normal`\n* `lucid_prompt_symbol_error_color`: the color of the prompt symbol when an\n   error occurs. Default: `$fish_color_normal`\n\n## Design\n\nEach prompt invocation launches a background job responsible for checking dirty status. If the previous job did not complete, it is killed prior to starting a new job. The dirty check job relays the dirty status back to the main shell via an exit code. This works because there's only three distinct states that can result from a dirty check: dirty, not dirty, or error. Systems programming FTW!\n\nAfter launching the job, the parent process immediately registers a completion handler for the job. In there, we scoop up the exit status, then update the prompt based on what was found.\n\nThe rest is book-keeping and careful coding. There may be a few more opportunities for optimization. Send a PR if you find any!\n\n### Previous Design Iterations\n\nThe tough problem here is keeping the execution time low, and communicating the dirty status from the job back to the parent.\n\nHere are a few other approaches that didn't pan out:\n\n* **IPC via universal variables**: variable contents are round-tripped by disk, which is a non-starter\n* **IPC via named FIFO**: fish is unable to hold the reader side of a named FIFO open, causing writers to block indefinitely\n* **IPC via tmpfs**: macOS does not have a `tmpfs` filesystem available by default\n* **Other POSIX IPC facilities**: these would necessitate a third-party binary dependency\n\n## Known Issues\n\n* fish has a bug involving multi-line prompts not being redrawn correctly. You usually see this when invoking `fzf`.\n* lucid uses a background job to asynchronously fetch dirty status. If you try to exit while a dirty status has not completed, fish will warn you it is still running. Unfortunately, lucid is not able to `disown` the job because it needs to collect the exit status from it.\n\n## License\n\n[MIT](LICENSE)\n"
  },
  {
    "path": "functions/fish_prompt.fish",
    "content": "# Default appearance options. Override in config.fish if you want.\nif ! set -q lucid_dirty_indicator\n    set -g lucid_dirty_indicator \"•\"\nend\n\nif ! set -q lucid_prompt_symbol\n    set -g lucid_prompt_symbol \"❯\"\nend\n\nif ! set -q lucid_prompt_symbol_error\n    set -g lucid_prompt_symbol_error \"❯\"\nend\n\nif ! set -q lucid_prompt_symbol_color\n    set -g lucid_prompt_symbol_color \"$fish_color_normal\"\nend\n\nif ! set -q lucid_prompt_symbol_error_color\n    set -g lucid_prompt_symbol_error_color \"$fish_color_normal\"\nend\n\n# This should be set to be at least as long as lucid_dirty_indicator, due to a fish bug\nif ! set -q lucid_clean_indicator\n    set -g lucid_clean_indicator (string replace -r -a '.' ' ' $lucid_dirty_indicator)\nend\n\nif ! set -q lucid_cwd_color\n    set -g lucid_cwd_color green\nend\n\nif ! set -q lucid_git_color\n    set -g lucid_git_color blue\nend\n\n# State used for memoization and async calls.\nset -g __lucid_cmd_id 0\nset -g __lucid_git_state_cmd_id -1\nset -g __lucid_git_static \"\"\nset -g __lucid_dirty \"\"\n\n# Increment a counter each time a prompt is about to be displayed.\n# Enables us to distingish between redraw requests and new prompts.\nfunction __lucid_increment_cmd_id --on-event fish_prompt\n    set __lucid_cmd_id (math $__lucid_cmd_id + 1)\nend\n\n# Abort an in-flight dirty check, if any.\nfunction __lucid_abort_check\n    if set -q __lucid_check_pid\n        set -l pid $__lucid_check_pid\n        functions -e __lucid_on_finish_$pid\n        command kill $pid >/dev/null 2>&1\n        set -e __lucid_check_pid\n    end\nend\n\nfunction __lucid_git_status\n    # Reset state if this call is *not* due to a redraw request\n    set -l prev_dirty $__lucid_dirty\n    if test $__lucid_cmd_id -ne $__lucid_git_state_cmd_id\n        __lucid_abort_check\n\n        set __lucid_git_state_cmd_id $__lucid_cmd_id\n        set __lucid_git_static \"\"\n        set __lucid_dirty \"\"\n    end\n\n    # Fetch git position & action synchronously.\n    # Memoize results to avoid recomputation on subsequent redraws.\n    if test -z $__lucid_git_static\n        # Determine git working directory\n        set -l git_dir (command git --no-optional-locks rev-parse --absolute-git-dir 2>/dev/null)\n        if test $status -ne 0\n            return 1\n        end\n\n        set -l position (command git --no-optional-locks symbolic-ref --short HEAD 2>/dev/null)\n        if test $status -ne 0\n            # Denote detached HEAD state with short commit hash\n            set position (command git --no-optional-locks rev-parse --short HEAD 2>/dev/null)\n            if test $status -eq 0\n                set position \"@$position\"\n            end\n        end\n\n        # TODO: add bisect\n        set -l action \"\"\n        if test -f \"$git_dir/MERGE_HEAD\"\n            set action \"merge\"\n        else if test -d \"$git_dir/rebase-merge\"\n            set action \"rebase\"\n        else if test -d \"$git_dir/rebase-apply\"\n            set action \"rebase\"\n        end\n\n        set -l state $position\n        if test -n $action\n            set state \"$state <$action>\"\n        end\n\n        set -g __lucid_git_static $state\n    end\n\n    # Fetch dirty status asynchronously.\n    if test -z $__lucid_dirty\n        if ! set -q __lucid_check_pid\n            # Compose shell command to run in background\n            set -l check_cmd \"git --no-optional-locks status -unormal --porcelain --ignore-submodules 2>/dev/null | head -n1 | count\"\n            set -l cmd \"if test ($check_cmd) != \"0\"; exit 1; else; exit 0; end\"\n\n            begin\n                # Defer execution of event handlers by fish for the remainder of lexical scope.\n                # This is to prevent a race between the child process exiting before we can get set up.\n                block -l\n\n                set -g __lucid_check_pid 0\n                command fish --private --command \"$cmd\" >/dev/null 2>&1 &\n                set -l pid (jobs --last --pid)\n\n                set -g __lucid_check_pid $pid\n\n                # Use exit code to convey dirty status to parent process.\n                function __lucid_on_finish_$pid --inherit-variable pid --on-process-exit $pid\n                    functions -e __lucid_on_finish_$pid\n\n                    if set -q __lucid_check_pid\n                        if test $pid -eq $__lucid_check_pid\n                            switch $argv[3]\n                                case 0\n                                    set -g __lucid_dirty_state 0\n                                    if status is-interactive\n                                        commandline -f repaint\n                                    end\n                                case 1\n                                    set -g __lucid_dirty_state 1\n                                    if status is-interactive\n                                        commandline -f repaint\n                                    end\n                                case '*'\n                                    set -g __lucid_dirty_state 2\n                                    if status is-interactive\n                                        commandline -f repaint\n                                    end\n                            end\n                        end\n                    end\n                end\n            end\n        end\n\n        if set -q __lucid_dirty_state\n            switch $__lucid_dirty_state\n                case 0\n                    set -g __lucid_dirty $lucid_clean_indicator\n                case 1\n                    set -g __lucid_dirty $lucid_dirty_indicator\n                case 2\n                    set -g __lucid_dirty \"<err>\"\n            end\n\n            set -e __lucid_check_pid\n            set -e __lucid_dirty_state\n        end\n    end\n\n    # Render git status. When in-progress, use previous state to reduce flicker.\n    set_color $lucid_git_color\n    echo -n $__lucid_git_static ''\n\n    if ! test -z $__lucid_dirty\n        echo -n $__lucid_dirty\n    else if ! test -z $prev_dirty\n        set_color --dim $lucid_git_color\n        echo -n $prev_dirty\n        set_color normal\n    end\n\n    set_color normal\nend\n\nfunction __lucid_vi_indicator\n    if [ $fish_key_bindings = \"fish_vi_key_bindings\" ]\n        switch $fish_bind_mode\n            case \"insert\"\n                set_color green\n                echo -n \"[I] \"\n            case \"default\"\n                set_color red\n                echo -n \"[N] \"\n            case \"visual\"\n                set_color yellow\n                echo -n \"[S] \"\n            case \"replace\"\n                set_color blue\n                echo -n \"[R] \"\n        end\n        set_color normal\n    end\nend\n\n# Suppress default mode prompt\nfunction fish_mode_prompt\nend\n\nfunction fish_prompt\n    set -l last_pipestatus $pipestatus\n    set -l cwd (pwd | string replace \"$HOME\" '~')\n\n    if test -z \"$lucid_skip_newline\"\n        echo ''\n    end\n\n    set_color $lucid_cwd_color\n    echo -sn $cwd\n    set_color normal\n\n    if test $cwd != '~'; or test -n \"$lucid_git_status_in_home_directory\"\n        set -l git_state (__lucid_git_status)\n        if test $status -eq 0\n            echo -sn \" on $git_state\"\n        end\n    end\n\n    echo ''\n    __lucid_vi_indicator\n\n    set -l prompt_symbol \"$lucid_prompt_symbol\"\n    set -l prompt_symbol_color \"$lucid_prompt_symbol_color\"\n\n    for status_code in $last_pipestatus\n        if test \"$status_code\" -ne 0\n            set prompt_symbol \"$lucid_prompt_symbol_error\"\n            set prompt_symbol_color \"$lucid_prompt_symbol_error_color\"\n            break\n        end\n    end\n\n    set_color \"$prompt_symbol_color\"\n    echo -n \"$prompt_symbol \"\n    set_color normal\nend\n"
  }
]