diff --git a/.devcontainer/devcontainer.json b/.devcontainer/devcontainer.json
new file mode 100644
index 000000000..6af984cbf
--- /dev/null
+++ b/.devcontainer/devcontainer.json
@@ -0,0 +1,29 @@
+{
+ "name": "Tachi",
+ "dockerComposeFile": [
+ "../docker-compose-dev.yml"
+ ],
+ "service": "tachi-dev",
+ "workspaceFolder": "/tachi",
+ "postAttachCommand": "./dev/bootstrap.sh",
+ "updateRemoteUserUID": true,
+ "containerUser": "tachi",
+ "customizations": {
+ "vscode": {
+ "settings": {
+ "typescript.tsdk": "node_modules/typescript/lib",
+ "[typescriptreact]": {
+ "editor.formatOnSave": true,
+ "editor.defaultFormatter": "dbaeumer.vscode-eslint"
+ },
+ "[typescript]": {
+ "editor.formatOnSave": true,
+ "editor.defaultFormatter": "dbaeumer.vscode-eslint"
+ }
+ },
+ "extensions": [
+ "dbaeumer.vscode-eslint"
+ ]
+ }
+ }
+}
\ No newline at end of file
diff --git a/.dockerignore b/.dockerignore
index 04f151d78..bf7404885 100644
--- a/.dockerignore
+++ b/.dockerignore
@@ -5,5 +5,5 @@ node_modules
**/.tsbuildinfo
# Extracted data from iidx merge script
-database-seeds/scripts/rerunners/iidx/iidx-mdb-parse/ifs-output
-database-seeds/scripts/rerunners/iidx/iidx-mdb-parse/logs
\ No newline at end of file
+seeds/scripts/rerunners/iidx/iidx-mdb-parse/ifs-output
+seeds/scripts/rerunners/iidx/iidx-mdb-parse/logs
\ No newline at end of file
diff --git a/.github/workflows/database-seeds.yml b/.github/workflows/database-seeds.yml
index ba762fac6..c9cf2e0b5 100644
--- a/.github/workflows/database-seeds.yml
+++ b/.github/workflows/database-seeds.yml
@@ -1,17 +1,17 @@
-name: Database-Seeds CI/CD
+name: Seeds CI/CD
on:
push:
branches:
- "main"
paths:
- - "database-seeds/**"
+ - "seeds/**"
- "common/**"
pull_request:
branches:
- "main"
paths:
- - "database-seeds/**"
+ - "seeds/**"
- "common/**"
workflow_dispatch:
@@ -32,10 +32,10 @@ jobs:
cache: pnpm
- name: Install dependencies
- run: pnpm --filter tachi-database-seeds-scripts... --filter . install
+ run: pnpm --filter tachi-seeds-scripts... --filter . install
- name: Run Tests
- run: pnpm --filter tachi-database-seeds-scripts test
+ run: pnpm --filter tachi-seeds-scripts test
env:
NODE_ENV: "test"
deploy:
diff --git a/.vscode/extensions.json b/.vscode/extensions.json
new file mode 100644
index 000000000..43321cdcb
--- /dev/null
+++ b/.vscode/extensions.json
@@ -0,0 +1,5 @@
+{
+ "recommendations": [
+ "ms-vscode-remote.remote-containers"
+ ]
+}
\ No newline at end of file
diff --git a/.vscode/settings.json b/.vscode/settings.json
index 034a26912..d01d0f021 100644
--- a/.vscode/settings.json
+++ b/.vscode/settings.json
@@ -5,6 +5,5 @@
},
"[typescript]": {
"editor.defaultFormatter": "dbaeumer.vscode-eslint"
- }
-}
+ },
}
\ No newline at end of file
diff --git a/Dockerfile.bootstrap b/Dockerfile.bootstrap
deleted file mode 100644
index 4cd332d7d..000000000
--- a/Dockerfile.bootstrap
+++ /dev/null
@@ -1,15 +0,0 @@
-# Yes, I've made an entire docker container just to run a bash script
-# instead of rewriting the script in a ~~real~~ cross-platform scripting language.
-# This docker container **depends** on an external volume being mounted
-# (see how it's used in docker-compose-dev.yml)
-# and *intentionally* mutates state outside of its container.
-
-# I really do not care.
-# Want it better? Do it yourself.
-FROM node:20 AS base
-
-RUN npm install --silent -g pnpm@8.15.6
-
-WORKDIR /app
-
-CMD ["bash", "_scripts/bootstrap.sh"]
\ No newline at end of file
diff --git a/Dockerfile.dev b/Dockerfile.dev
new file mode 100644
index 000000000..e5ae551d5
--- /dev/null
+++ b/Dockerfile.dev
@@ -0,0 +1,55 @@
+# For use via `devcontainer.json`. The docker-compose-dev file sets some other
+# important variables.
+
+# we use ubuntu because 'just' isn't available on debian 13
+# haha.. haha...
+FROM ubuntu:24.10
+
+WORKDIR /tachi
+
+RUN apt update
+
+# essentials
+RUN apt install -y git npm locales sudo
+
+# setup locales
+# https://stackoverflow.com/questions/28405902/how-to-set-the-locale-inside-a-debian-ubuntu-docker-container
+RUN echo 'en_US.UTF-8' > /etc/locale.gen && locale-gen
+ENV LANG en_US.UTF-8
+ENV LANGUAGE en_US:en
+ENV LC_ALL en_US.UTF-8
+
+# nodeisms
+RUN npm install --silent -g pnpm@8.15.6
+
+# docs pythonisms
+RUN apt install -y python-is-python3 pip mkdocs mkdocs-material
+
+# https://github.com/python-babel/babel/issues/990
+# it wouldn't be python without needing absurd global state manipulation to fix
+# an incoherent error message
+RUN rm -f /etc/localtime && ln -s /usr/share/zoneinfo/Etc/UTC /etc/localtime
+
+# Fix locale issue perl repeatedly complains about
+RUN echo "LC_ALL=en_US.UTF-8\nLANG=en_US.UTF-8" > /etc/default/locale
+
+# nice to haves
+RUN apt install -y gh fish just fzf curl wget parallel neovim fd-find bat
+
+# `fd` is called `find-fd` on ubuntu. Awesome.
+RUN ln -s $(which fdfind) /usr/bin/fd
+
+# rename ubuntu user to tachi and give them sudo
+RUN usermod -l tachi ubuntu && \
+ usermod -d /home/tachi -m tachi && \
+ echo "tachi ALL = NOPASSWD : ALL" >> /etc/sudoers
+
+# Docker volumes are mounted as root UNLESS the folder already exists inside the host
+# and has non-root ownership. This is the only way to declare a volume in docker has
+# non-root ownership. Unbelievably obscure.
+RUN mkdir node_modules && \
+ mkdir .pnpm-store && \
+ chown tachi:1000 node_modules .pnpm-store
+
+# keep container alive indefinitely
+CMD ["/bin/fish"]
\ No newline at end of file
diff --git a/Dockerfile.seeds b/Dockerfile.seeds
deleted file mode 100644
index 92906c6ee..000000000
--- a/Dockerfile.seeds
+++ /dev/null
@@ -1,26 +0,0 @@
-# no alpine here as we want bash + general nice-to-haves
-FROM node:20 AS base
-
-RUN npm install --silent -g pnpm@8.15.6
-
-FROM base AS build
-WORKDIR /app
-
-COPY pnpm-lock.yaml .
-COPY patches ./patches
-
-RUN pnpm fetch
-
-COPY database-seeds ./database-seeds
-COPY common ./common
-COPY *.json *.yaml ./
-
-RUN pnpm --filter tachi-common... --filter . install --offline --frozen-lockfile
-RUN pnpm --filter tachi-database-seeds-scripts... --filter . install --offline --frozen-lockfile
-
-WORKDIR /app/database-seeds
-
-# keep container alive indefinitely
-# we just have this container so db-seeds maintainers have a nice "context"
-# to work with
-CMD ["tail", "-f", "/dev/null"]
\ No newline at end of file
diff --git a/Justfile b/Justfile
new file mode 100644
index 000000000..9b6ced1f8
--- /dev/null
+++ b/Justfile
@@ -0,0 +1,48 @@
+mod seeds
+mod server
+mod client
+mod docs
+
+[private]
+interactive:
+ -@just --choose
+
+# Run the frontend and backend for Tachi.
+#
+# This is the main command you want to use to start up tachi. Go for it!
+start:
+ parallel --lb ::: 'FORCE_COLOR=1 just server start' 'FORCE_COLOR=1 just client start'
+
+# test everything
+test:
+ just server test
+ just client test
+ just seeds test
+
+latest_dataset := "2024-05"
+# Load the latest Kamaitachi dataset. This is put in the "anon-kamai" database.
+load-kamai-dataset:
+ wget -O- https://cdn-kamai.tachi.ac/datasets/{{latest_dataset}}.dump | mongorestore --uri='mongodb://mongo' --gzip --archive
+
+ echo "Successfully loaded. You should change 'server/conf.json5' to use anon-kamai as the database."
+
+# Load the latest Bokutachi dataset. This is put in the "anon-boku" database.
+load-boku-dataset:
+ wget -O- https://cdn-boku.tachi.ac/datasets/{{latest_dataset}}.dump | mongorestore --uri='mongodb://mongo' --gzip --archive
+
+ echo "Successfully loaded. You should change 'server/conf.json5' to use anon-boku as the database."
+
+# Check that the data in MongoDB makes any sense.
+validate-db:
+ cd server/ && pnpm validate-database
+
+# reload the shell setup
+setup-fish:
+ @fish dev/setup.fish
+
+ @exec fish
+
+# force a re-bootstrap
+bootstrap:
+ -@rm I_HAVE_BOOTSTRAPPED_OK
+ ./dev/bootstrap.sh
\ No newline at end of file
diff --git a/README.md b/README.md
index 9a3a3f0a2..a987f3f99 100644
--- a/README.md
+++ b/README.md
@@ -28,13 +28,28 @@ Check the [Documentation](https://docs.tachi.ac/contributing/setup) for how to s
You can then check the component-specific guides to see how to run those components and contribute back!
-## Quick Setup For Nerds
+## Quick Setup For Experienced Programmers
-Already know what you're doing?
+Install VSCode and use the dev container extension.
+This is my the supported way of working and will ensure you have the correct versions of everything.
-Install docker + docker-compose and use `./run.sh start` or `./run.bat start` to start Tachi.
+### Unsupported stuff
-Other commands you might want to execute are included inside the `run` file.
+Tachi is intended to be developed inside a container. This ensures that you have the correct version of MongoDB, Redis, Typescript and all that jazz.
+
+VSCode has excellent native support for dev containers, and as such this is the only method of local development we officially support.
+
+Over the years we have had a *lot* of issues with people having subtle variations on their system (or on windows). Given the contributor-centricity of Tachi, it's untenable to expect every contributor to be an expert with local dev setup.
+
+The devcontainer provides us with the most simple, consistent experience, and allows us to put nice-to-haves inside the user's shell.
+
+That said, if you're ardently against using VSCode or Docker...
+
+**DO NOT REPORT ISSUES TO ME IF YOU DO THIS**
+
+You can run the docker-compose file and `dev/boostrap.sh` inside the container yourself. Work inside the container.
+
+Alternatively if you want to work outside of docker you're on your own. Figure out the correct versions for everything (npm, pnpm, mongo, redis, ts-node...) and `dev/bootstrap.sh`.
## Repository Info
@@ -48,7 +63,7 @@ The client and the server are fairly decoupled. Someone could trivially create t
This contains all of our API calls, and interfaces with our database, and powers the actual score import engine.
-- `database-seeds/`, Which is a git-tracked set of data to be synced with Tachi. (unlicense)
+- `seeds/`, Which is a git-tracked set of data to be synced with Tachi. (unlicense)
**This is the source of truth for the songs, charts, and more on the site!**
By submitting PRs to this, you can fix bugs on the website, add new charts, and more.
diff --git a/_scripts/make-datadumps.sh b/_scripts/make-datadumps.sh
index b76e58d70..5a8261966 100755
--- a/_scripts/make-datadumps.sh
+++ b/_scripts/make-datadumps.sh
@@ -20,7 +20,7 @@ for kind in "kamai" "boku"; do
mongodump --archive --port=$remote_port --db=$kind | mongorestore --archive --nsFrom="$kind.*" --nsTo="anon-$kind.*"
- pnpm ts-node src/scripts/anonymise-db 127.0.0.1:27017/anon-$kind
+ ts-node src/scripts/anonymise-db 127.0.0.1:27017/anon-$kind
TCHIS_CONF_LOCATION=$kind.dataset.conf.json5 pnpm set-indexes
diff --git a/client/Justfile b/client/Justfile
new file mode 100644
index 000000000..90dbe787d
--- /dev/null
+++ b/client/Justfile
@@ -0,0 +1,12 @@
+[private]
+interactive:
+ -@cd ../ && just
+
+# Run the client interactively. Changes made to files will trigger reloads.
+start:
+ pnpm dev
+
+# Test that the client passes typechecking and linting.
+test:
+ pnpm typecheck
+ pnpm lint
\ No newline at end of file
diff --git a/client/package.json b/client/package.json
index 6e345e0da..29a4342df 100644
--- a/client/package.json
+++ b/client/package.json
@@ -4,7 +4,7 @@
"private": true,
"license": "AGPL3",
"scripts": {
- "start": "vite",
+ "dev": "vite --open",
"build": "vite build",
"lint": "eslint src",
"lint-fix": "eslint src --fix",
diff --git a/client/src/components/seeds/SeedsPicker.tsx b/client/src/components/seeds/SeedsPicker.tsx
index 95e5e6160..3012d9554 100644
--- a/client/src/components/seeds/SeedsPicker.tsx
+++ b/client/src/components/seeds/SeedsPicker.tsx
@@ -271,9 +271,9 @@ function RevSelector({
const params = new URLSearchParams();
if (collection) {
- params.set("path", `database-seeds/collections/${collection}`);
+ params.set("path", `seeds/collections/${collection}`);
} else {
- params.set("path", "database-seeds/collections");
+ params.set("path", "seeds/collections");
}
const res = await fetch(
diff --git a/client/src/lib/config.ts b/client/src/lib/config.ts
index 315dae692..4a9efba4b 100644
--- a/client/src/lib/config.ts
+++ b/client/src/lib/config.ts
@@ -36,25 +36,26 @@ try {
}
-
Failed to connect!
-
Welp. Looks like we're down. Sorry about that.
-
Chances are, this is just a temporary outage and will be fixed soon.
-
-
An error message can be found in the console. (Ctrl-Shift-I
)
+
${
process.env.VITE_IS_LOCAL_DEV
? `
-
You're in local development mode.
+
Couldn't connect to the server.
+
You are in local development mode.
- Have you accepted the HTTPS certificates for the server?. If not, the site won't load.
`
- : ""
+ : `
Failed to connect!
+
Welp. Looks like we're down. Sorry about that.
+
Chances are, this is just a temporary outage and will be fixed soon.
+
+
An error message can be found in the console. (Ctrl-Shift-I
)
`
}
`);
diff --git a/client/src/util/seeds.ts b/client/src/util/seeds.ts
index b58c90e52..6a781c628 100644
--- a/client/src/util/seeds.ts
+++ b/client/src/util/seeds.ts
@@ -30,7 +30,7 @@ import { CreateQuestMap, Dedupe, JSONAttributeDiff, JSONCompare } from "./misc";
import { ValueGetterOrHybrid } from "./ztable/search";
/**
- * Given a repo and a reference return the status of database-seeds/collections
+ * Given a repo and a reference return the status of seeds/collections
* as of that commit.
*
* If "WORKING_DIRECTORY" is passed as a ref, and the repository is local, this will
@@ -63,7 +63,7 @@ export async function LoadSeeds(repo: string, ref: string): Promise
")
- .option("-t, --useTabs")
- .option("-a, --all")
- .argument("[collection]");
-
-program.parse(process.argv);
-const options = program.opts();
-
-if (!options.connection) {
- throw new Error(`Please specify a database connection string with -c or --connection.`);
-}
-
-// This will blow up horrifically if the connection URL is malformed.
-// Just don't do that, I guess!
-const db = monk(options.connection);
-
-const collectionsDir = path.join(__dirname, "../collections");
-
-const collections = [];
-
-if (options.all) {
- collections.push("folders", "tables");
- collections.push(...StaticConfig.allSupportedGames.map((e) => `songs-${e}`));
- collections.push(...StaticConfig.allSupportedGames.map((e) => `charts-${e}`));
-} else if (program.args[0]) {
- collections.push(program.args[0]);
-}
-
-// Add the songs-{game} and charts-{game} collections.
-
-(async () => {
- logger.info(`Exporting ${collections.length} collections.`);
-
- for (const collection of collections) {
- logger.info(`Exporting ${collection}.`);
-
- await ExportCollection(collection);
- }
-
- RemoveUnderscoreID();
-
- DeterministicCollectionSort();
-
- logger.info(`Done!`);
-
- process.exit(0);
-})();
-
-async function ExportCollection(collection) {
- const data = await db.get(collection).find({});
- logger.info(`Got ${data.length} documents.`);
-
- const pt = path.join(collectionsDir, `${collection}.json`);
- fs.writeFileSync(pt, JSON.stringify(data, null, options.useTabs ? "\t" : undefined));
-
- logger.info(`Wrote to ${pt}.`);
-}
diff --git a/database-seeds/scripts/mongoexp.sh b/database-seeds/scripts/mongoexp.sh
deleted file mode 100755
index ecb48f1ff..000000000
--- a/database-seeds/scripts/mongoexp.sh
+++ /dev/null
@@ -1,19 +0,0 @@
-#! /bin/bash
-
-# Exports data from a mongodb instance back to the collections
-# folder.
-# Useful for backporting updates, or something.
-
-set -eo pipefail
-
-cd "$(dirname "${BASH_SOURCE[0]}")"
-
-if [ -z "$1" ] || [ -z "$2" ]; then
- echo "Usage: mongoexp.sh "
- exit
-fi
-
-mongoexport -d "$2" -c "$1" --jsonArray > ../collections/"$1".json
-
-node remove-_id.js
-node deterministic-collection-sort.js
\ No newline at end of file
diff --git a/database-seeds/scripts/remove-_id.js b/database-seeds/scripts/remove-_id.js
deleted file mode 100644
index afb56054c..000000000
--- a/database-seeds/scripts/remove-_id.js
+++ /dev/null
@@ -1,19 +0,0 @@
-const { IterateCollections } = require("./util");
-
-function RemoveUnderscoreID() {
- IterateCollections((data) => {
- for (const d of data) {
- delete d._id;
- }
-
- return data;
- });
-}
-
-if (require.main === module) {
- RemoveUnderscoreID();
-}
-
-module.exports = {
- RemoveUnderscoreID,
-};
diff --git a/dev/README.md b/dev/README.md
new file mode 100644
index 000000000..34719e5f6
--- /dev/null
+++ b/dev/README.md
@@ -0,0 +1,4 @@
+# Tachi Dev Stuff
+
+Various stuff related to your local tachi dev container. These are mostly novelties,
+like having a pretty shell and good completions.
\ No newline at end of file
diff --git a/dev/aliases.fish b/dev/aliases.fish
new file mode 100644
index 000000000..211435815
--- /dev/null
+++ b/dev/aliases.fish
@@ -0,0 +1,9 @@
+# define new permanent aliases here...
+
+defnew g=git
+defnew gp=git push
+defnew gsw=git switch
+defnew gswc=git switch -c
+defnew gsm=git switch main
+defnew gl=git pull
+defnew rr="exec fish"
\ No newline at end of file
diff --git a/_scripts/bootstrap.sh b/dev/bootstrap.sh
similarity index 78%
rename from _scripts/bootstrap.sh
rename to dev/bootstrap.sh
index 5725e4cde..421e6395f 100755
--- a/_scripts/bootstrap.sh
+++ b/dev/bootstrap.sh
@@ -1,20 +1,23 @@
-#! /bin/bash
+#!/bin/bash
+# moves example .env files, generates certificates, etc.
-set -eox pipefail
+set -eo pipefail
-
-# https://stackoverflow.com/questions/59895/how-can-i-get-the-directory-where-a-bash-script-is-located-from-within-the-scrip
-# if you actually think bash is a good programming language you are
-# *straight up delusional*
SCRIPT_DIR=$( cd -- "$( dirname -- "${BASH_SOURCE[0]}" )" &> /dev/null && pwd )
cd "$SCRIPT_DIR";
cd ..;
-if [ -e I_HAVE_BOOTSTRAPPED_OK ]; then
- echo "Already bootstrapped."
- exit 0
-fi
+function installLocalDebs {
+ for file in /tachi/dev/deb/*.deb; do
+ sudo apt install -f -y "$file";
+ done
+}
+
+function setupShell {
+ echo "Setting up fish..."
+ fish dev/setup.fish
+}
function mvExampleFiles {
echo "Moving example config files into usable places..."
@@ -55,16 +58,14 @@ function pnpmInstall {
exit 1
fi
+ pnpm fetch
pnpm install
# install ts-node aswell so people can use that inside enter-seeds.
#
- # forcibly install this to /usr/local/bin because otherwise pnpm needs
- # a bunch of other env vars and needs to modify .bashrc and all that jazz
- #
- # we're installing a global binary; it should go in a global binary place
- # and this just works. sweet
- PNPM_HOME=/usr/local/bin pnpm install ts-node -g
+ # This requires quite a bit of ceremony as pnpm wants to install to PNPM_HOME
+ PATH=$PATH:~/.local/pnpm
+ PNPM_HOME=~/.local/pnpm pnpm install ts-node -g
echo "Installed dependencies."
}
@@ -87,17 +88,26 @@ function syncDatabaseWithSeeds {
(
cd server
- pnpm run sync-database-local
+ pnpm run load-seeds
)
echo "Synced."
}
+# always setup the shell
+setupShell
+
+if [ -e I_HAVE_BOOTSTRAPPED_OK ]; then
+ echo "Already bootstrapped."
+ exit 0
+fi
+
mvExampleFiles
selfSignHTTPS
pnpmInstall
setIndexes
syncDatabaseWithSeeds
+installLocalDebs
echo "Bootstrap Complete."
diff --git a/dev/deb/README.md b/dev/deb/README.md
new file mode 100644
index 000000000..203827fb7
--- /dev/null
+++ b/dev/deb/README.md
@@ -0,0 +1,5 @@
+MongoDB tools aren't available in apt. I want to have mongodb-database-tools inside the container.
+
+I don't like having `curl | sh` inside containers, so I've saved the file to the repo.
+
+Any `.deb` file in here will be installed inside the container.
\ No newline at end of file
diff --git a/dev/deb/install.sh b/dev/deb/install.sh
new file mode 100755
index 000000000..b3a7304cd
--- /dev/null
+++ b/dev/deb/install.sh
@@ -0,0 +1,5 @@
+#!/bin/bash
+
+for file in /tachi/dev/deb/*.deb; do
+ apt install -y "$file"
+done
\ No newline at end of file
diff --git a/dev/deb/mongodb-database-tools-ubuntu2204-x86_64-100.10.0.deb b/dev/deb/mongodb-database-tools-ubuntu2204-x86_64-100.10.0.deb
new file mode 100644
index 000000000..8783384de
Binary files /dev/null and b/dev/deb/mongodb-database-tools-ubuntu2204-x86_64-100.10.0.deb differ
diff --git a/dev/fish-plugins/fisher/LICENSE.md b/dev/fish-plugins/fisher/LICENSE.md
new file mode 100644
index 000000000..6ba7a0fbb
--- /dev/null
+++ b/dev/fish-plugins/fisher/LICENSE.md
@@ -0,0 +1,7 @@
+Copyright © Jorge Bucaran <>
+
+Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the 'Software'), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions:
+
+The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software.
+
+THE SOFTWARE IS PROVIDED 'AS IS', WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
diff --git a/dev/fish-plugins/fisher/README.md b/dev/fish-plugins/fisher/README.md
new file mode 100644
index 000000000..0f6116e89
--- /dev/null
+++ b/dev/fish-plugins/fisher/README.md
@@ -0,0 +1,193 @@
+# Fisher
+
+> A plugin manager for [Fish](https://fishshell.com)—your friendly interactive shell. [Snag fresh plugins!](https://github.com/jorgebucaran/awsm.fish#readme)
+
+Take control of functions, completions, bindings, and snippets from the command line. Unleash your shell's true potential, perfect your prompt, and craft repeatable configurations across different systems effortlessly. Fisher's zero impact on shell startup keeps your shell zippy and responsive. No gimmicks, just smooth sailing!
+
+- Fisher is 100% pure-Fish, making it easy to contribute or modify
+- Scorching fast concurrent plugin downloads that'll make you question reality
+- Zero configuration needed—we're not kidding!
+- Oh My Fish! plugins supported too
+
+> #### ☝️ [Upgrading from Fisher `3.x` or older? Strap in and read this!](https://github.com/jorgebucaran/fisher/issues/652)
+
+## Installation
+
+```console
+curl -sL https://raw.githubusercontent.com/jorgebucaran/fisher/main/functions/fisher.fish | source && fisher install jorgebucaran/fisher
+```
+
+## Quickstart
+
+Fisher lets you install, update, and remove plugins like a boss. Revel in Fish's [tab completion](https://fishshell.com/docs/current/index.html#completion) and rich syntax highlighting while you're at it.
+
+### Installing plugins
+
+To install plugins, use the `install` command and point it to the GitHub repository.
+
+```console
+fisher install jorgebucaran/nvm.fish
+```
+
+> Wanna install from GitLab? No problemo—just prepend `gitlab.com/` to the plugin path.
+
+You can also snag a specific version of a plugin by adding an `@` symbol after the plugin name, followed by a tag, branch, or [commit](https://git-scm.com/docs/gitglossary#Documentation/gitglossary.txt-aiddefcommit-ishacommit-ishalsocommittish).
+
+```console
+fisher install IlanCosman/tide@v5
+```
+
+And hey, you can install plugins from a local directory too!
+
+```console
+fisher install ~/path/to/plugin
+```
+
+> Heads up! Fisher expands plugins into your Fish configuration directory by default, overwriting existing files. If that's not your jam, set `$fisher_path` to your preferred location and put it in your function path ([#640](https://github.com/jorgebucaran/fisher/issues/640)).
+
+### Listing plugins
+
+Use the `list` command to see all your shiny installed plugins.
+
+```console
+$ fisher list
+jorgebucaran/fisher
+ilancosman/tide@v5
+jorgebucaran/nvm.fish
+/home/jb/path/to/plugin
+```
+
+The `list` command also plays nice with regular expressions for filtering the output.
+
+```console
+$ fisher list \^/
+/home/jb/path/to/plugin
+```
+
+### Updating plugins
+
+`update` command to the rescue! It updates one or more plugins to their latest and greatest version.
+
+```console
+fisher update jorgebucaran/fisher
+```
+
+> Just type `fisher update` to update everything in one fell swoop.
+
+### Removing plugins
+
+Say goodbye to installed plugins with the `remove` command.
+
+```console
+fisher remove jorgebucaran/nvm.fish
+```
+
+Feeling destructive? Wipe out everything, including Fisher itself.
+
+```console
+fisher list | fisher remove
+```
+
+## Using your `fish_plugins` file
+
+Whenever you install or remove a plugin from the command line, Fisher jots down all the installed plugins in `$__fish_config_dir/fish_plugins`. Add this file to your dotfiles or version control to easily share your configuration across different systems.
+
+You can also edit this file and run `fisher update` to commit changes like a pro:
+
+```console
+$EDITOR $__fish_config_dir/fish_plugins
+```
+
+```diff
+jorgebucaran/fisher
+ilancosman/tide@v5
+jorgebucaran/nvm.fish
++ PatrickF1/fzf.fish
+- /home/jb/path/to/plugin
+```
+
+```console
+fisher update
+```
+
+This will install **PatrickF1**/**fzf.fish**, remove /**home**/**jb**/**path**/**to**/**plugin**, and update everything else.
+
+## Creating a plugin
+
+Plugins can include any number of files in `functions`, `conf.d`, and `completions` directories. Most plugins are just a single function or a [configuration snippet](https://fishshell.com/docs/current/index.html#configuration). Behold the anatomy of a typical plugin:
+
+
+flipper
+├── completions
+│ └── flipper.fish
+├── conf.d
+│ └── flipper.fish
+└── functions
+ └── flipper.fish
+
+
+Non `.fish` files and directories inside these locations will be copied to `$fisher_path` under `functions`, `conf.d`, or `completions` respectively.
+
+### Event system
+
+Fish [events](https://fishshell.com/docs/current/cmds/emit.html) notify plugins when they're being installed, updated, or removed.
+
+> Keep in mind, `--on-event` functions must be loaded when their event is emitted. So, place your event handlers in the `conf.d` directory.
+
+```fish
+# Defined in flipper/conf.d/flipper.fish
+
+function _flipper_install --on-event flipper_install
+ # Set universal variables, create bindings, and other initialization logic.
+end
+
+function _flipper_update --on-event flipper_update
+ # Migrate resources, print warnings, and other update logic.
+end
+
+function _flipper_uninstall --on-event flipper_uninstall
+ # Erase "private" functions, variables, bindings, and other uninstall logic.
+end
+```
+
+## Creating a theme
+
+A theme is like any other Fish plugin, but with a `.theme` file in the `themes` directory. Themes were introduced in [Fish `3.4`](https://github.com/fish-shell/fish-shell/releases/tag/3.4.0) and work with the `fish_config` builtin. A theme can also have files in `functions`, `conf.d`, or `completions` if necessary. Check out what a typical theme plugin looks like:
+
+
+gills
+├── conf.d
+│ └── gills.fish
+└── themes
+ └── gills.theme
+
+
+### Using `$fisher_path` with themes
+
+If you customize `$fisher_path` to use a directory other than `$__fish_config_dir`, your themes won't be available via `fish_config`. That's because Fish expects your themes to be in `$__fish_config_dir/themes`, not `$fisher_path/themes`. This isn't configurable in Fish yet, but there's [a request to add that feature](https://github.com/fish-shell/fish-shell/issues/9456).
+
+Fear not! You can easily solve this by symlinking Fisher's `themes` directory into your Fish config. First, backup any existing themes directory.
+
+```console
+mv $__fish_config_dir/themes $__fish_config_dir/themes.bak
+```
+
+Next, create a symlink for Fisher's themes directory.
+
+```console
+ln -s $fisher_path/themes $__fish_config_dir/themes
+```
+
+Want to use theme plugins and maintain your own local themes? You can do that too ([#708](https://github.com/jorgebucaran/fisher/issues/708)).
+
+## Discoverability
+
+While Fisher doesn't rely on a central plugin repository, discovering new plugins doesn't have to feel like navigating uncharted waters. To boost your plugin's visibility and make it easier for users to find, [add relevant topics to your repository](https://docs.github.com/en/repositories/managing-your-repositorys-settings-and-features/customizing-your-repository/classifying-your-repository-with-topics#adding-topics-to-your-repository) using [`fish-plugin`](https://github.com/topics/fish-plugin). By doing so, you're not only contributing to the Fisher community but also enabling users to explore new plugins and enhance their Fish shell experience. Don't let plugin discovery be a fishy business, tag your plugins today!
+
+## Acknowledgments
+
+Fisher started its journey in 2016 by [@jorgebucaran](https://github.com/jorgebucaran) as a shell configuration manager for Fish. Along the way, many helped shape it into what it is today. [Oh My Fish](https://github.com/oh-my-fish/oh-my-fish) paved the way as the first popular Fish framework. [@jethrokuan](https://github.com/jethrokuan) provided crucial support during the early years. [@PatrickF1](https://github.com/PatrickF1)'s candid feedback proved invaluable time and again. Bootstrapping Fisher was originally [@IlanCosman](https://github.com/IlanCosman)'s brilliant idea. Thank you to all our contributors! <3
+
+## License
+
+[MIT](LICENSE.md)
diff --git a/dev/fish-plugins/fisher/functions/fisher.fish b/dev/fish-plugins/fisher/functions/fisher.fish
new file mode 100644
index 000000000..b1513d3bd
--- /dev/null
+++ b/dev/fish-plugins/fisher/functions/fisher.fish
@@ -0,0 +1,240 @@
+function fisher --argument-names cmd --description "A plugin manager for Fish"
+ set --query fisher_path || set --local fisher_path $__fish_config_dir
+ set --local fisher_version 4.4.4
+ set --local fish_plugins $__fish_config_dir/fish_plugins
+
+ switch "$cmd"
+ case -v --version
+ echo "fisher, version $fisher_version"
+ case "" -h --help
+ echo "Usage: fisher install Install plugins"
+ echo " fisher remove Remove installed plugins"
+ echo " fisher update Update installed plugins"
+ echo " fisher update Update all installed plugins"
+ echo " fisher list [] List installed plugins matching regex"
+ echo "Options:"
+ echo " -v, --version Print version"
+ echo " -h, --help Print this help message"
+ echo "Variables:"
+ echo " \$fisher_path Plugin installation path. Default: $__fish_config_dir" | string replace --regex -- $HOME \~
+ case ls list
+ string match --entire --regex -- "$argv[2]" $_fisher_plugins
+ case install update remove
+ isatty || read --local --null --array stdin && set --append argv $stdin
+
+ set --local install_plugins
+ set --local update_plugins
+ set --local remove_plugins
+ set --local arg_plugins $argv[2..-1]
+ set --local old_plugins $_fisher_plugins
+ set --local new_plugins
+
+ test -e $fish_plugins && set --local file_plugins (string match --regex -- '^[^\s]+$' <$fish_plugins)
+
+ if ! set --query argv[2]
+ if test "$cmd" != update
+ echo "fisher: Not enough arguments for command: \"$cmd\"" >&2 && return 1
+ else if ! set --query file_plugins
+ echo "fisher: \"$fish_plugins\" file not found: \"$cmd\"" >&2 && return 1
+ end
+ set arg_plugins $file_plugins
+ end
+
+ for plugin in $arg_plugins
+ set plugin (test -e "$plugin" && realpath $plugin || string lower -- $plugin)
+ contains -- "$plugin" $new_plugins || set --append new_plugins $plugin
+ end
+
+ if set --query argv[2]
+ for plugin in $new_plugins
+ if contains -- "$plugin" $old_plugins
+ test "$cmd" = remove &&
+ set --append remove_plugins $plugin ||
+ set --append update_plugins $plugin
+ else if test "$cmd" = install
+ set --append install_plugins $plugin
+ else
+ echo "fisher: Plugin not installed: \"$plugin\"" >&2 && return 1
+ end
+ end
+ else
+ for plugin in $new_plugins
+ contains -- "$plugin" $old_plugins &&
+ set --append update_plugins $plugin ||
+ set --append install_plugins $plugin
+ end
+
+ for plugin in $old_plugins
+ contains -- "$plugin" $new_plugins || set --append remove_plugins $plugin
+ end
+ end
+
+ set --local pid_list
+ set --local source_plugins
+ set --local fetch_plugins $update_plugins $install_plugins
+ set --local fish_path (status fish-path)
+
+ echo (set_color --bold)fisher $cmd version $fisher_version(set_color normal)
+
+ for plugin in $fetch_plugins
+ set --local source (command mktemp -d)
+ set --append source_plugins $source
+
+ command mkdir -p $source/{completions,conf.d,themes,functions}
+
+ $fish_path --command "
+ if test -e $plugin
+ command cp -Rf $plugin/* $source
+ else
+ set temp (command mktemp -d)
+ set repo (string split -- \@ $plugin) || set repo[2] HEAD
+
+ if set path (string replace --regex -- '^(https://)?gitlab.com/' '' \$repo[1])
+ set name (string split -- / \$path)[-1]
+ set url https://gitlab.com/\$path/-/archive/\$repo[2]/\$name-\$repo[2].tar.gz
+ else
+ set url https://api.github.com/repos/\$repo[1]/tarball/\$repo[2]
+ end
+
+ echo Fetching (set_color --underline)\$url(set_color normal)
+
+ if command curl -q --silent -L \$url | command tar -xzC \$temp -f - 2>/dev/null
+ command cp -Rf \$temp/*/* $source
+ else
+ echo fisher: Invalid plugin name or host unavailable: \\\"$plugin\\\" >&2
+ command rm -rf $source
+ end
+
+ command rm -rf \$temp
+ end
+
+ set files $source/* && string match --quiet --regex -- .+\.fish\\\$ \$files
+ " &
+
+ set --append pid_list (jobs --last --pid)
+ end
+
+ wait $pid_list 2>/dev/null
+
+ for plugin in $fetch_plugins
+ if set --local source $source_plugins[(contains --index -- "$plugin" $fetch_plugins)] && test ! -e $source
+ if set --local index (contains --index -- "$plugin" $install_plugins)
+ set --erase install_plugins[$index]
+ else
+ set --erase update_plugins[(contains --index -- "$plugin" $update_plugins)]
+ end
+ end
+ end
+
+ for plugin in $update_plugins $remove_plugins
+ if set --local index (contains --index -- "$plugin" $_fisher_plugins)
+ set --local plugin_files_var _fisher_(string escape --style=var -- $plugin)_files
+
+ if contains -- "$plugin" $remove_plugins
+ for name in (string replace --filter --regex -- '.+/conf\.d/([^/]+)\.fish$' '$1' $$plugin_files_var)
+ emit {$name}_uninstall
+ end
+ printf "%s\n" Removing\ (set_color red --bold)$plugin(set_color normal) " "$$plugin_files_var | string replace -- \~ ~
+ set --erase _fisher_plugins[$index]
+ end
+
+ command rm -rf (string replace -- \~ ~ $$plugin_files_var)
+
+ functions --erase (string replace --filter --regex -- '.+/functions/([^/]+)\.fish$' '$1' $$plugin_files_var)
+
+ for name in (string replace --filter --regex -- '.+/completions/([^/]+)\.fish$' '$1' $$plugin_files_var)
+ complete --erase --command $name
+ end
+
+ set --erase $plugin_files_var
+ end
+ end
+
+ if set --query update_plugins[1] || set --query install_plugins[1]
+ command mkdir -p $fisher_path/{functions,themes,conf.d,completions}
+ end
+
+ for plugin in $update_plugins $install_plugins
+ set --local source $source_plugins[(contains --index -- "$plugin" $fetch_plugins)]
+ set --local files $source/{functions,themes,conf.d,completions}/*
+
+ if set --local index (contains --index -- $plugin $install_plugins)
+ set --local user_files $fisher_path/{functions,themes,conf.d,completions}/*
+ set --local conflict_files
+
+ for file in (string replace -- $source/ $fisher_path/ $files)
+ contains -- $file $user_files && set --append conflict_files $file
+ end
+
+ if set --query conflict_files[1] && set --erase install_plugins[$index]
+ echo -s "fisher: Cannot install \"$plugin\": please remove or move conflicting files first:" \n" "$conflict_files >&2
+ continue
+ end
+ end
+
+ for file in (string replace -- $source/ "" $files)
+ command cp -RLf $source/$file $fisher_path/$file
+ end
+
+ set --local plugin_files_var _fisher_(string escape --style=var -- $plugin)_files
+
+ set --query files[1] && set --universal $plugin_files_var (string replace -- $source $fisher_path $files | string replace -- ~ \~)
+
+ contains -- $plugin $_fisher_plugins || set --universal --append _fisher_plugins $plugin
+ contains -- $plugin $install_plugins && set --local event install || set --local event update
+
+ printf "%s\n" Installing\ (set_color --bold)$plugin(set_color normal) " "$$plugin_files_var | string replace -- \~ ~
+
+ for file in (string match --regex -- '.+/[^/]+\.fish$' $$plugin_files_var | string replace -- \~ ~)
+ source $file
+ if set --local name (string replace --regex -- '.+conf\.d/([^/]+)\.fish$' '$1' $file)
+ emit {$name}_$event
+ end
+ end
+ end
+
+ command rm -rf $source_plugins
+
+ if set --query _fisher_plugins[1]
+ set --local commit_plugins
+
+ for plugin in $file_plugins
+ contains -- (string lower -- $plugin) (string lower -- $_fisher_plugins) && set --append commit_plugins $plugin
+ end
+
+ for plugin in $_fisher_plugins
+ contains -- (string lower -- $plugin) (string lower -- $commit_plugins) || set --append commit_plugins $plugin
+ end
+
+ printf "%s\n" $commit_plugins >$fish_plugins
+ else
+ set --erase _fisher_plugins
+ command rm -f $fish_plugins
+ end
+
+ set --local total (count $install_plugins) (count $update_plugins) (count $remove_plugins)
+
+ test "$total" != "0 0 0" && echo (string join ", " (
+ test $total[1] = 0 || echo "Installed $total[1]") (
+ test $total[2] = 0 || echo "Updated $total[2]") (
+ test $total[3] = 0 || echo "Removed $total[3]")
+ ) plugin/s
+ case \*
+ echo "fisher: Unknown command: \"$cmd\"" >&2 && return 1
+ end
+end
+
+if ! set --query _fisher_upgraded_to_4_4
+ set --universal _fisher_upgraded_to_4_4
+ if functions --query _fisher_list
+ set --query XDG_DATA_HOME[1] || set --local XDG_DATA_HOME ~/.local/share
+ command rm -rf $XDG_DATA_HOME/fisher
+ functions --erase _fisher_{list,plugin_parse}
+ fisher update >/dev/null 2>/dev/null
+ else
+ for var in (set --names | string match --entire --regex '^_fisher_.+_files$')
+ set $var (string replace -- ~ \~ $$var)
+ end
+ functions --erase _fisher_fish_postexec
+ end
+end
diff --git a/dev/fish-plugins/forgit/LICENSE b/dev/fish-plugins/forgit/LICENSE
new file mode 100644
index 000000000..e508978a4
--- /dev/null
+++ b/dev/fish-plugins/forgit/LICENSE
@@ -0,0 +1,21 @@
+The MIT License (MIT)
+
+Copyright © 2017-2021 Wenxuan Zhang
+
+Permission is hereby granted, free of charge, to any person obtaining a copy
+of this software and associated documentation files (the “Software”), to deal
+in the Software without restriction, including without limitation the rights
+to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
+copies of the Software, and to permit persons to whom the Software is
+furnished to do so, subject to the following conditions:
+
+The above copyright notice and this permission notice shall be included in
+all copies or substantial portions of the Software.
+
+THE SOFTWARE IS PROVIDED “AS IS”, WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
+AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
+OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
+THE SOFTWARE.
diff --git a/dev/fish-plugins/forgit/README.md b/dev/fish-plugins/forgit/README.md
new file mode 100644
index 000000000..fc7baff43
--- /dev/null
+++ b/dev/fish-plugins/forgit/README.md
@@ -0,0 +1,376 @@
+💤 forgit
+
+ Utility tool for using git interactively. Powered by junegunn/fzf.
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+This tool is designed to help you use git more efficiently.
+It's **lightweight** and **easy to use**.
+
+# 📥 Installation
+
+*Make sure you have [`fzf`](https://github.com/junegunn/fzf) installed.*
+
+``` zsh
+# for zplug
+zplug 'wfxr/forgit'
+
+# for zgen
+zgen load 'wfxr/forgit'
+
+# for antigen
+antigen bundle 'wfxr/forgit'
+
+# for fisher (requires fisher v4.4.3 or higher)
+fisher install wfxr/forgit
+
+# for omf
+omf install https://github.com/wfxr/forgit
+
+# for zinit
+zinit load wfxr/forgit
+
+# for oh-my-zsh
+git clone https://github.com/wfxr/forgit.git ${ZSH_CUSTOM:-~/.oh-my-zsh/custom}/plugins/forgit
+
+# manually
+# Clone the repository and source it in your shell's rc file or put bin/git-forgit into your $PATH
+```
+
+## Homebrew
+
+To install using brew
+```sh
+brew install forgit
+```
+
+Then add the following to your shell's config file:
+```sh
+# Fish:
+# ~/.config/fish/config.fish:
+[ -f $HOMEBREW_PREFIX/share/forgit/forgit.plugin.fish ]; and source $HOMEBREW_PREFIX/share/forgit/forgit.plugin.fish
+
+# Zsh:
+# ~/.zshrc:
+[ -f $HOMEBREW_PREFIX/share/forgit/forgit.plugin.zsh ] && source $HOMEBREW_PREFIX/share/forgit/forgit.plugin.zsh
+
+# Bash:
+# ~/.bashrc:
+[ -f $HOMEBREW_PREFIX/share/forgit/forgit.plugin.sh ] && source $HOMEBREW_PREFIX/share/forgit/forgit.plugin.sh
+```
+
+## Fig
+
+[Fig](https://fig.io) adds apps, shortcuts, and autocomplete to your existing terminal.
+
+Install `forgit` in just one click.
+
+[![Install with Fig](https://fig.io/badges/install-with-fig.svg)](https://fig.io/plugins/other/forgit)
+
+## Arch User Repository
+
+[AUR](https://wiki.archlinux.org/title/Arch_User_Repository) packages, maintained by the developers of forgit, are available. Install the [forgit](https://aur.archlinux.org/packages/forgit) package for the latest release or [forgit-git](https://aur.archlinux.org/packages/forgit-git) to stay up to date with the latest commits from the master branch of this repository.
+
+# 📝 Features
+
+- **Interactive `git add` selector** (`ga`)
+
+![screenshot](https://raw.githubusercontent.com/wfxr/i/master/forgit-ga.png)
+
+- **Interactive `git log` viewer** (`glo`)
+
+![screenshot](https://raw.githubusercontent.com/wfxr/i/master/forgit-glo.png)
+
+*The log graph can be disabled by option `FORGIT_LOG_GRAPH_ENABLE` (see discuss in [issue #71](https://github.com/wfxr/forgit/issues/71)).*
+
+- **Interactive `.gitignore` generator** (`gi`)
+
+![screenshot](https://raw.githubusercontent.com/wfxr/i/master/forgit-gi.png)
+
+- **Interactive `git diff` viewer** (`gd`)
+
+- **Interactive `git reset HEAD ` selector** (`grh`)
+
+- **Interactive `git checkout ` selector** (`gcf`)
+
+- **Interactive `git checkout ` selector** (`gcb`)
+
+- **Interactive `git branch -D ` selector** (`gbd`)
+
+- **Interactive `git checkout ` selector** (`gct`)
+
+- **Interactive `git checkout ` selector** (`gco`)
+
+- **Interactive `git revert ` selector** (`grc`)
+
+- **Interactive `git stash` viewer** (`gss`)
+
+- **Interactive `git stash push` selector** (`gsp`)
+
+- **Interactive `git clean` selector** (`gclean`)
+
+- **Interactive `git cherry-pick` selector** (`gcp`)
+
+- **Interactive `git rebase -i` selector** (`grb`)
+
+- **Interactive `git blame` selector** (`gbl`)
+
+- **Interactive `git commit --fixup && git rebase -i --autosquash` selector** (`gfu`)
+
+# ⌨ Keybindings
+
+| Key | Action |
+| :-------------------------------------------: | ------------------------------------------- |
+| Enter | Confirm |
+| Tab | Toggle mark and move down |
+| Shift - Tab | Toggle mark and move up |
+| ? | Toggle preview window |
+| Alt - W | Toggle preview wrap |
+| Ctrl - S | Toggle sort |
+| Ctrl - R | Toggle selection |
+| Ctrl - Y | Copy commit hash/stash ID* |
+| Ctrl - K / P | Selection move up |
+| Ctrl - J / N | Selection move down |
+| Alt - K / P | Preview move up |
+| Alt - J / N | Preview move down |
+| Alt - E | Open file in default editor (when possible) |
+
+\* Available when the selection contains a commit hash or a stash ID.
+For Linux users `FORGIT_COPY_CMD` should be set to make copy work. Example: `FORGIT_COPY_CMD='xclip -selection clipboard'`.
+
+# ⚙ Options
+
+Options can be set via environment variables. They have to be **exported** in
+order to be recognized by `forgit`.
+
+For instance, if you want to order branches in `gcb` by the last committed date you could:
+
+```shell
+export FORGIT_CHECKOUT_BRANCH_BRANCH_GIT_OPTS='--sort=-committerdate'
+```
+
+## shell aliases
+
+You can change the default aliases by defining these variables below.
+(To disable all aliases, Set the `FORGIT_NO_ALIASES` flag.)
+
+``` bash
+forgit_log=glo
+forgit_diff=gd
+forgit_add=ga
+forgit_reset_head=grh
+forgit_ignore=gi
+forgit_checkout_file=gcf
+forgit_checkout_branch=gcb
+forgit_branch_delete=gbd
+forgit_checkout_tag=gct
+forgit_checkout_commit=gco
+forgit_revert_commit=grc
+forgit_clean=gclean
+forgit_stash_show=gss
+forgit_stash_push=gsp
+forgit_cherry_pick=gcp
+forgit_rebase=grb
+forgit_blame=gbl
+forgit_fixup=gfu
+```
+
+## git integration
+
+You can use forgit as a sub-command of git by making `git-forgit` available in `$PATH`:
+
+```sh
+# after `forgit` was loaded
+PATH="$PATH:$FORGIT_INSTALL_DIR/bin"
+```
+
+*Some plugin managers can help do this.*
+
+Then, any forgit command will be a sub-command of git:
+
+```cmd
+git forgit log
+git forgit add
+git forgit diff
+```
+
+Optionally you can add [aliases in git](https://git-scm.com/book/en/v2/Git-Basics-Git-Aliases):
+
+```sh
+git config --global alias.cf 'forgit checkout_file'
+```
+
+And use forgit functions via a git alias:
+
+```sh
+git cf
+```
+
+## git options
+
+If you want to customize `git`'s behavior within forgit there is a dedicated variable for each forgit command.
+These are passed to the according `git` calls.
+
+| Command | Option |
+| -------- | --------------------------------------------------------------------------- |
+| `ga` | `FORGIT_ADD_GIT_OPTS` |
+| `glo` | `FORGIT_LOG_GIT_OPTS` |
+| `gd` | `FORGIT_DIFF_GIT_OPTS` |
+| `grh` | `FORGIT_RESET_HEAD_GIT_OPTS` |
+| `gcf` | `FORGIT_CHECKOUT_FILE_GIT_OPTS` |
+| `gcb` | `FORGIT_CHECKOUT_BRANCH_GIT_OPTS`, `FORGIT_CHECKOUT_BRANCH_BRANCH_GIT_OPTS` |
+| `gbd` | `FORGIT_BRANCH_DELETE_GIT_OPTS` |
+| `gct` | `FORGIT_CHECKOUT_TAG_GIT_OPTS` |
+| `gco` | `FORGIT_CHECKOUT_COMMIT_GIT_OPTS` |
+| `grc` | `FORGIT_REVERT_COMMIT_GIT_OPTS` |
+| `gss` | `FORGIT_STASH_SHOW_GIT_OPTS` |
+| `gsp` | `FORGIT_STASH_PUSH_GIT_OPTS` |
+| `gclean` | `FORGIT_CLEAN_GIT_OPTS` |
+| `grb` | `FORGIT_REBASE_GIT_OPTS` |
+| `gbl` | `FORGIT_BLAME_GIT_OPTS` |
+| `gfu` | `FORGIT_FIXUP_GIT_OPTS` |
+| `gcp` | `FORGIT_CHERRY_PICK_GIT_OPTS` |
+
+## pagers
+
+Forgit will use the default configured pager from git (`core.pager`,
+`pager.show`, `pager.diff`) but can be altered with the following environment
+variables:
+
+| Use case | Option | Fallbacks to |
+| ------------ | ------------------- | --------------------------------------------- |
+| common pager | `FORGIT_PAGER` | `git config core.pager` _or_ `cat` |
+| pager on `git show` | `FORGIT_SHOW_PAGER` | `git config pager.show` _or_ `$FORGIT_PAGER` |
+| pager on `git diff` | `FORGIT_DIFF_PAGER` | `git config pager.diff` _or_ `$FORGIT_PAGER` |
+| pager on `git blame` | `FORGIT_BLAME_PAGER` | `git config pager.blame` _or_ `$FORGIT_PAGER` |
+| pager on `gitignore` | `FORGIT_IGNORE_PAGER` | `bat -l gitignore --color always` _or_ `cat` |
+| git log format | `FORGIT_GLO_FORMAT` | `%C(auto)%h%d %s %C(black)%C(bold)%cr%reset` |
+
+## fzf options
+
+You can add default fzf options for `forgit`, including keybindings, layout, etc.
+(No need to repeat the options already defined in `FZF_DEFAULT_OPTS`)
+
+``` bash
+export FORGIT_FZF_DEFAULT_OPTS="
+--exact
+--border
+--cycle
+--reverse
+--height '80%'
+"
+```
+
+Customizing fzf options for each command individually is also supported:
+
+| Command | Option |
+|----------|-----------------------------------|
+| `ga` | `FORGIT_ADD_FZF_OPTS` |
+| `glo` | `FORGIT_LOG_FZF_OPTS` |
+| `gi` | `FORGIT_IGNORE_FZF_OPTS` |
+| `gd` | `FORGIT_DIFF_FZF_OPTS` |
+| `grh` | `FORGIT_RESET_HEAD_FZF_OPTS` |
+| `gcf` | `FORGIT_CHECKOUT_FILE_FZF_OPTS` |
+| `gcb` | `FORGIT_CHECKOUT_BRANCH_FZF_OPTS` |
+| `gbd` | `FORGIT_BRANCH_DELETE_FZF_OPTS` |
+| `gct` | `FORGIT_CHECKOUT_TAG_FZF_OPTS` |
+| `gco` | `FORGIT_CHECKOUT_COMMIT_FZF_OPTS` |
+| `grc` | `FORGIT_REVERT_COMMIT_FZF_OPTS` |
+| `gss` | `FORGIT_STASH_FZF_OPTS` |
+| `gsp` | `FORGIT_STASH_PUSH_FZF_OPTS` |
+| `gclean` | `FORGIT_CLEAN_FZF_OPTS` |
+| `grb` | `FORGIT_REBASE_FZF_OPTS` |
+| `gbl` | `FORGIT_BLAME_FZF_OPTS` |
+| `gfu` | `FORGIT_FIXUP_FZF_OPTS` |
+| `gcp` | `FORGIT_CHERRY_PICK_FZF_OPTS` |
+
+Complete loading order of fzf options is:
+
+1. `FZF_DEFAULT_OPTS` (fzf global)
+2. `FORGIT_FZF_DEFAULT_OPTS` (forgit global)
+3. `FORGIT_CMD_FZF_OPTS` (command specific)
+
+Examples:
+
+- `ctrl-d` to drop the selected stash but do not quit fzf (`gss` specific).
+
+```sh
+export FORGIT_STASH_FZF_OPTS='
+--bind="ctrl-d:reload(git stash drop $(cut -d: -f1 <<<{}) 1>/dev/null && git stash list)"
+'
+```
+
+- `ctrl-e` to view the logs in a vim buffer (`glo` specific).
+
+```sh
+export FORGIT_LOG_FZF_OPTS='
+--bind="ctrl-e:execute(echo {} |grep -Eo [a-f0-9]+ |head -1 |xargs git show |vim -)"
+'
+```
+
+## other options
+
+| Option | Description | Default |
+|-----------------------------|-------------------------------------------|-----------------------------------------------|
+| `FORGIT_LOG_FORMAT` | git log format | `%C(auto)%h%d %s %C(black)%C(bold)%cr%Creset` |
+| `FORGIT_PREVIEW_CONTEXT` | lines of diff context in preview mode | 3 |
+| `FORGIT_FULLSCREEN_CONTEXT` | lines of diff context in full-screen mode | 10 |
+| `FORGIT_DIR_VIEW` | command used to preview directories | `tree` if available, otherwise `find` |
+
+# 📦 Optional dependencies
+
+- [`delta`](https://github.com/dandavison/delta) / [`diff-so-fancy`](https://github.com/so-fancy/diff-so-fancy): For better human-readable diffs.
+
+- [`bat`](https://github.com/sharkdp/bat.git): Syntax highlighting for `gitignore`.
+
+- [`emoji-cli`](https://github.com/wfxr/emoji-cli): Emoji support for `git log`.
+
+# Completions
+
+## Bash
+
+- Put [`completions/git-forgit.bash`](https://github.com/wfxr/forgit/blob/master/completions/git-forgit.bash) in
+ `~/.local/share/bash-completion/completions` to have bash tab completion for `git forgit` and configured git aliases.
+- Source [`completions/git-forgit.bash`](https://github.com/wfxr/forgit/blob/master/completions/git-forgit.bash) explicitly to have
+ bash tab completion for forgit shell functions and aliases (e.g., `gcb ` completes branches).
+
+## Zsh
+
+- Put [`completions/_git-forgit`](completions/_git-forgit) in a directory in your `$fpath` (e.g., `/usr/share/zsh/site-functions`) to have zsh tab completion for `git forgit` and configured git aliases, as well as shell command aliases, such as `forgit::add` and `ga`
+
+If you're having issues after updating, and commands such as `forgit::add` or aliases `ga` aren't working, remove your completions cache and restart your shell.
+
+```zsh
+> rm ~/.zcompdump
+> zsh
+```
+
+# 💡 Tips
+
+- Most of the commands accept optional arguments (e.g., `glo develop`, `glo f738479..188a849b -- main.go`, `gco master`).
+- `gd` supports specifying revision(e.g., `gd HEAD~`, `gd v1.0 README.md`).
+- Call `gi` with arguments to get the wanted `.gitignore` contents directly(e.g., `gi cmake c++`).
+
+# 📃 License
+
+[MIT](https://wfxr.mit-license.org/2017) (c) Wenxuan Zhang
diff --git a/dev/fish-plugins/forgit/bin/git-forgit b/dev/fish-plugins/forgit/bin/git-forgit
new file mode 100755
index 000000000..3db5b4a22
--- /dev/null
+++ b/dev/fish-plugins/forgit/bin/git-forgit
@@ -0,0 +1,1044 @@
+#!/usr/bin/env bash
+# MIT (c) Wenxuan Zhang
+
+# This file is meant to be executed directly. If it's available on the PATH,
+# it can also be used as a subcommand of git, which then forwards all arguments
+# on to forgit. So, all of these commands will work as expected:
+#
+# `git forgit log`
+# `git forgit checkout_file`
+# `git forgit checkout_file README.md`
+#
+# This gives users the choice to set aliases inside of their git config instead
+# of their shell config if they prefer.
+
+# Set shell for fzf preview commands
+# Disable shellcheck for "which", because it suggests "command -v xxx" instead,
+# which is not a working replacement.
+# See https://github.com/koalaman/shellcheck/issues/1162
+# shellcheck disable=2230
+SHELL="$(which bash)"
+export SHELL
+
+# Get absolute forgit path
+FORGIT=$(cd -- "$(dirname -- "${BASH_SOURCE[0]}")" &>/dev/null && pwd)/$(basename -- "${BASH_SOURCE[0]}")
+
+FORGIT_FZF_DEFAULT_OPTS="
+$FZF_DEFAULT_OPTS
+--ansi
+--height='80%'
+--bind='alt-k:preview-up,alt-p:preview-up'
+--bind='alt-j:preview-down,alt-n:preview-down'
+--bind='ctrl-r:toggle-all'
+--bind='ctrl-s:toggle-sort'
+--bind='?:toggle-preview'
+--bind='alt-w:toggle-preview-wrap'
+--preview-window='right:60%'
++1
+$FORGIT_FZF_DEFAULT_OPTS
+"
+
+_forgit_warn() { printf "%b[Warn]%b %s\n" '\e[0;33m' '\e[0m' "$@" >&2; }
+_forgit_info() { printf "%b[Info]%b %s\n" '\e[0;32m' '\e[0m' "$@" >&2; }
+_forgit_inside_work_tree() { git rev-parse --is-inside-work-tree >/dev/null; }
+# tac is not available on OSX, tail -r is not available on Linux, so we use either of them
+_forgit_reverse_lines() { tac 2> /dev/null || tail -r; }
+
+_forgit_previous_commit() {
+ # "SHA~" is invalid when the commit is the first commit, but we can use "--root" instead
+ if [[ "$(git rev-parse "$1")" == "$(git rev-list --max-parents=0 HEAD)" ]]; then
+ echo "--root"
+ else
+ echo "$1~"
+ fi
+}
+
+_forgit_contains_non_flags() {
+ while (("$#")); do
+ case "$1" in
+ -*) shift ;;
+ *)
+ return 0
+ ;;
+ esac
+ done
+ return 1
+}
+
+# optional render emoji characters (https://github.com/wfxr/emoji-cli)
+_forgit_emojify() {
+ if hash emojify &>/dev/null; then
+ emojify
+ else
+ cat
+ fi
+}
+
+# extract the first git sha occurring in the input and strip trailing newline
+_forgit_extract_sha() {
+ grep -Eo '[a-f0-9]+' | head -1 | tr -d '[:space:]'
+}
+
+# extract the first git sha and copy it to the clipboard
+_forgit_yank_sha() {
+ echo "$1" | _forgit_extract_sha | ${FORGIT_COPY_CMD:-pbcopy}
+}
+
+# extract the first stash name in the input
+_forgit_extract_stash_name() {
+ cut -d: -f1 | tr -d '[:space:]'
+}
+
+# extract the first stash name and copy it to the clipboard
+_forgit_yank_stash_name() {
+ echo "$1" | _forgit_extract_stash_name | ${FORGIT_COPY_CMD:-pbcopy}
+}
+
+# parse a space separated string into an array
+# arrays parsed with this function are global
+_forgit_parse_array() {
+ ${IFS+"false"} && unset old_IFS || old_IFS="$IFS"
+ # read the value of the second argument
+ # into an array that has the name of the first argument
+ IFS=" " read -r -a "$1" <<< "$2"
+ ${old_IFS+"false"} && unset IFS || IFS="$old_IFS"
+}
+
+# parse the input arguments and print only those after the "--"
+# separator as a single line of quoted arguments to stdout
+_forgit_quote_files() {
+ local files add
+ files=()
+ add=false
+ while (( "$#" )); do
+ case "$1" in
+ --)
+ add=true
+ shift
+ ;;
+ *)
+ if [ $add == true ]; then
+ files+=("'$1'")
+ fi
+ shift
+ ;;
+ esac
+ done
+ echo "${files[*]}"
+}
+
+_forgit_log_graph_enable=${FORGIT_LOG_GRAPH_ENABLE:-"true"}
+_forgit_log_format=${FORGIT_LOG_FORMAT:-%C(auto)%h%d %s %C(black)%C(bold)%cr%Creset}
+_forgit_log_preview_options=("--graph" "--pretty=format:$_forgit_log_format" "--color=always" "--abbrev-commit" "--date=relative")
+_forgit_fullscreen_context=${FORGIT_FULLSCREEN_CONTEXT:-10}
+_forgit_preview_context=${FORGIT_PREVIEW_CONTEXT:-3}
+_forgit_dir_view=${FORGIT_DIR_VIEW:-$(hash tree &> /dev/null && echo 'tree' || echo 'find')}
+
+_forgit_pager() {
+ local pager
+ pager=$(_forgit_get_pager "$1")
+ [[ -z "${pager}" ]] && exit 1
+ eval "${pager} ${*:2}"
+}
+
+_forgit_get_pager() {
+ local pager
+ pager=${1:-core}
+ case "$pager" in
+ core) echo -n "${FORGIT_PAGER:-$(git config core.pager || echo 'cat')}" ;;
+ show) echo -n "${FORGIT_SHOW_PAGER:-$(git config pager.show || _forgit_get_pager)}" ;;
+ diff) echo -n "${FORGIT_DIFF_PAGER:-$(git config pager.diff || _forgit_get_pager)}" ;;
+ ignore) echo -n "${FORGIT_IGNORE_PAGER:-$(hash bat &>/dev/null && echo 'bat -l gitignore --color=always' || echo 'cat')}" ;;
+ blame) echo -n "${FORGIT_BLAME_PAGER:-$(git config pager.blame || _forgit_get_pager)}" ;;
+ enter) echo -n "${FORGIT_ENTER_PAGER:-"LESS='-r' less"}" ;;
+ *) echo "pager not found: $1" >&2 ;;
+ esac
+}
+
+_forgit_is_file_tracked() {
+ git ls-files "$1" --error-unmatch &> /dev/null
+}
+
+_forgit_list_files() {
+ local rootdir
+ rootdir=$(git rev-parse --show-toplevel)
+ # git escapes special characters in it's output when core.quotePath is
+ # true or unset. Git always expects unquoted file paths as input. This
+ # leads to issues when we consume output from git and use it to build
+ # input for other git commands. Use the -z flag to ensure file paths are
+ # unquoted.
+ # uniq is necessary because unmerged files are printed once for each
+ # merge conflict.
+ # With the -z flag, git also uses \0 line termination, so we
+ # have to replace the terminators.
+ git ls-files -z "$@" "$rootdir" | tr '\0' '\n' | uniq
+}
+
+_forgit_log_preview() {
+ local sha
+ sha=$(echo "$1" | _forgit_extract_sha)
+ shift
+ echo "$sha" | xargs -I% git show --color=always -U"$_forgit_preview_context" % -- "$@" | _forgit_pager show
+}
+
+_forgit_log_enter() {
+ local sha
+ sha=$(echo "$1" | _forgit_extract_sha)
+ shift
+ echo "$sha" | xargs -I% "${FORGIT}" diff %^! "$@"
+}
+
+# git commit viewer
+_forgit_log() {
+ _forgit_inside_work_tree || return 1
+ local opts graph quoted_files log_format
+ quoted_files=$(_forgit_quote_files "$@")
+ opts="
+ $FORGIT_FZF_DEFAULT_OPTS
+ +s +m --tiebreak=index
+ --bind=\"enter:execute($FORGIT log_enter {} $quoted_files)\"
+ --bind=\"ctrl-y:execute-silent($FORGIT yank_sha {})\"
+ --preview=\"$FORGIT log_preview {} $quoted_files\"
+ $FORGIT_LOG_FZF_OPTS
+ "
+ graph=()
+ [[ $_forgit_log_graph_enable == true ]] && graph=(--graph)
+ log_format=${FORGIT_GLO_FORMAT:-$_forgit_log_format}
+ _forgit_log_git_opts=()
+ _forgit_parse_array _forgit_log_git_opts "$FORGIT_LOG_GIT_OPTS"
+ git log "${graph[@]}" --color=always --format="$log_format" "${_forgit_log_git_opts[@]}" "$@" |
+ _forgit_emojify |
+ FZF_DEFAULT_OPTS="$opts" fzf
+ fzf_exit_code=$?
+ # exit successfully on 130 (ctrl-c/esc)
+ [[ $fzf_exit_code == 130 ]] && return 0
+ return $fzf_exit_code
+}
+
+_forgit_get_files_from_diff_line() {
+ # Construct a null-terminated list of the filenames
+ # The input looks like one of these lines:
+ # [R100] file -> another file
+ # [A] file with spaces
+ # [D] oldfile
+ # And we transform it to this representation for further usage with "xargs -0":
+ # file\0another file\0
+ # file with spaces\0
+ # oldfile\0
+ # We have to do a two-step sed -> tr pipe because OSX's sed implementation does
+ # not support the null-character directly.
+ sed 's/^[[:space:]]*\[[A-Z0-9]*\][[:space:]]*//' | sed 's/ -> /\n/' | tr '\n' '\0'
+}
+
+_forgit_get_single_file_from_diff_line() {
+ # Similar to the function above, but only gets a single file from a single line
+ # Gets the new name of renamed files
+ sed 's/^[[:space:]]*\[[A-Z0-9]*\][[:space:]]*//' | sed 's/.*-> //'
+}
+
+_forgit_exec_diff() {
+ _forgit_diff_git_opts=()
+ _forgit_parse_array _forgit_diff_git_opts "$FORGIT_DIFF_GIT_OPTS"
+ git diff --color=always "${_forgit_diff_git_opts[@]}" "$@"
+}
+
+_forgit_diff_view() {
+ local input_line=$1
+ local diff_context=$2
+ local repo
+ local commits=()
+ repo=$(git rev-parse --show-toplevel)
+ cd "$repo" || return 1
+ if [ $# -gt 2 ]; then
+ IFS=" " read -r -a commits <<< "${*:3}"
+ fi
+ echo "$input_line" | _forgit_get_files_from_diff_line | xargs -0 \
+ "$FORGIT" exec_diff "${commits[@]}" -U"$diff_context" -- | _forgit_pager diff
+}
+
+_forgit_edit_diffed_file() {
+ local input_line rootdir
+ input_line=$1
+ rootdir=$(git rev-parse --show-toplevel)
+ filename=$(echo "$input_line" | _forgit_get_single_file_from_diff_line)
+ $EDITOR "$rootdir/$filename" >/dev/tty /dev/null ; then
+ if [[ $# -gt 1 ]] && git rev-parse "$2" -- &>/dev/null; then
+ commits=("$1" "$2") && files=("${@:3}")
+ else
+ commits=("$1") && files=("${@:2}")
+ fi
+ else
+ files=("$@")
+ fi
+ }
+ # Git stashes are named "stash@{x}", which contains the fzf placeholder "{x}".
+ # In order to support passing stashes as arguments to _forgit_diff, we have to
+ # prevent fzf from interpreting this substring by escaping the opening bracket.
+ # The string is evaluated a few subsequent times, so we need multiple escapes.
+ for commit in "${commits[@]}"; do
+ escaped_commits+="'${commit//\{/\\\\\{}' "
+ done
+ opts="
+ $FORGIT_FZF_DEFAULT_OPTS
+ +m -0 --bind=\"enter:execute($FORGIT diff_enter {} $escaped_commits | $FORGIT pager enter)\"
+ --preview=\"$FORGIT diff_view {} '$_forgit_preview_context' $escaped_commits\"
+ --bind=\"alt-e:execute-silent($FORGIT edit_diffed_file {})+refresh-preview\"
+ $FORGIT_DIFF_FZF_OPTS
+ --prompt=\"${commits[*]} > \"
+ "
+ _forgit_diff_git_opts=()
+ _forgit_parse_array _forgit_diff_git_opts "$FORGIT_DIFF_GIT_OPTS"
+ git diff --name-status "${_forgit_diff_git_opts[@]}" "${commits[@]}" -- "${files[@]}" |
+ sed -E 's/^([[:alnum:]]+)[[:space:]]+(.*)$/[\1] \2/' |
+ sed 's/ / -> /2' | expand -t 8 |
+ FZF_DEFAULT_OPTS="$opts" fzf
+ fzf_exit_code=$?
+ # exit successfully on 130 (ctrl-c/esc)
+ [[ $fzf_exit_code == 130 ]] && return 0
+ return $fzf_exit_code
+}
+
+_forgit_add_preview() {
+ file=$(echo "$1" | _forgit_get_single_file_from_add_line)
+ if (git status -s -- "$file" | grep '^??') &>/dev/null; then # diff with /dev/null for untracked files
+ git diff --color=always --no-index -- /dev/null "$file" | _forgit_pager diff | sed '2 s/added:/untracked:/'
+ else
+ git diff --color=always -- "$file" | _forgit_pager diff
+ fi
+}
+
+_forgit_git_add() {
+ _forgit_add_git_opts=()
+ _forgit_parse_array _forgit_add_git_opts "$FORGIT_ADD_GIT_OPTS"
+ git add "${_forgit_add_git_opts[@]}" "$@"
+}
+
+_forgit_get_single_file_from_add_line() {
+ # NOTE: paths listed by 'git status -su' mixed with quoted and unquoted style
+ # remove indicators | remove original path for rename case | remove surrounding quotes
+ sed 's/^.*] //' |
+ sed 's/.* -> //' |
+ sed -e 's/^\"//' -e 's/\"$//'
+}
+
+_forgit_edit_add_file() {
+ local input_line=$1
+ filename=$(echo "$input_line" | _forgit_get_single_file_from_add_line)
+ $EDITOR "$filename" >/dev/tty newest when you multiselect
+ # The instances of "cut", "nl" and "sort" all serve this purpose
+ # Please see https://github.com/wfxr/forgit/issues/253 for more details
+
+ opts="
+ $FORGIT_FZF_DEFAULT_OPTS
+ --preview=\"$FORGIT cherry_pick_preview {}\"
+ --multi --ansi --with-nth 2.. -0 --tiebreak=index
+ $FORGIT_CHERRY_PICK_FZF_OPTS
+ "
+ # Note: do not add any pipe after the fzf call here, otherwise the fzf_exitval is not propagated properly.
+ # Any eventual post processing can be done afterwards when the "commits" variable is assigned below.
+ fzf_selection=$(git log --right-only --color=always --cherry-pick --oneline "$base"..."$target" | nl |
+ FZF_DEFAULT_OPTS="$opts" fzf)
+ fzf_exitval=$?
+ [[ $fzf_exitval != 0 ]] && return $fzf_exitval
+ [[ -z "$fzf_selection" ]] && return $fzf_exitval
+
+ commits=()
+ while IFS='' read -r commit; do
+ commits+=("$commit")
+ done < <(echo "$fzf_selection" | sort -n -k 1 | cut -f2 | cut -d' ' -f1 | _forgit_reverse_lines)
+ [ ${#commits[@]} -eq 0 ] && return 1
+
+ _forgit_cherry_pick_git_opts=()
+ _forgit_parse_array _forgit_cherry_pick_git_opts "$FORGIT_CHERRY_PICK_GIT_OPTS"
+ git cherry-pick "${_forgit_cherry_pick_git_opts[@]}" "${commits[@]}"
+}
+
+_forgit_cherry_pick_from_branch_preview() {
+ git log --right-only --color=always --cherry-pick --oneline "$1"..."$2"
+}
+
+_forgit_cherry_pick_from_branch() {
+ _forgit_inside_work_tree || return 1
+ local opts branch exitval input_branch args base
+
+ base=$(git branch --show-current)
+ [[ -z "$base" ]] && echo "Current commit is not on a branch." && return 1
+
+ args=("$@")
+ if [[ $# -ne 0 ]]; then
+ input_branch=${args[0]}
+ fi
+ opts="
+ $FORGIT_FZF_DEFAULT_OPTS
+ +s +m --tiebreak=index --header-lines=1
+ --preview=\"$FORGIT cherry_pick_from_branch_preview '$base' {1}\"
+ $FORGIT_CHERRY_PICK_FROM_BRANCH_FZF_OPTS
+ "
+ # loop until either the branch selector is closed or a commit to be cherry
+ # picked has been selected from within a branch
+ while true
+ do
+ if [[ -z $input_branch ]]; then
+ branch="$(git branch --color=always --all |
+ LC_ALL=C sort -k1.1,1.1 -rs |
+ FZF_DEFAULT_OPTS="$opts" fzf |
+ awk '{print $1}')"
+ else
+ branch=$input_branch
+ fi
+
+ unset input_branch
+ [[ -z "$branch" ]] && return 1
+
+ _forgit_cherry_pick "$branch"
+
+ exitval=$?
+ [[ $exitval != 130 ]] || [[ $# -ne 0 ]] && return $exitval
+ done
+}
+
+_forgit_rebase() {
+ _forgit_inside_work_tree || return 1
+ local opts graph quoted_files target_commit prev_commit
+ graph=()
+ [[ $_forgit_log_graph_enable == true ]] && graph=(--graph)
+ _forgit_rebase_git_opts=()
+ _forgit_parse_array _forgit_rebase_git_opts "$FORGIT_REBASE_GIT_OPTS"
+ quoted_files=$(_forgit_quote_files "$@")
+ opts="
+ $FORGIT_FZF_DEFAULT_OPTS
+ +s +m --tiebreak=index
+ --bind=\"ctrl-y:execute-silent($FORGIT yank_sha {})\"
+ --preview=\"$FORGIT file_preview {} $quoted_files\"
+ $FORGIT_REBASE_FZF_OPTS
+ "
+ target_commit=$(
+ git log "${graph[@]}" --color=always --format="$_forgit_log_format" "$@" |
+ _forgit_emojify |
+ FZF_DEFAULT_OPTS="$opts" fzf |
+ _forgit_extract_sha)
+ if [[ -n "$target_commit" ]]; then
+ prev_commit=$(_forgit_previous_commit "$target_commit")
+ git rebase -i "${_forgit_rebase_git_opts[@]}" "$prev_commit"
+ fi
+}
+
+_forgit_file_preview() {
+ local sha
+ sha=$(echo "$1" | _forgit_extract_sha)
+ shift
+ echo "$sha" | xargs -I% git show --color=always % -- "$@" | _forgit_pager show
+}
+
+_forgit_fixup() {
+ _forgit_inside_work_tree || return 1
+ git diff --cached --quiet && echo 'Nothing to fixup: there are no staged changes.' && return 1
+ local opts graph quoted_files target_commit prev_commit
+ graph=()
+ [[ $_forgit_log_graph_enable == true ]] && graph=(--graph)
+ _forgit_fixup_git_opts=()
+ _forgit_parse_array _forgit_fixup_git_opts "$FORGIT_FIXUP_GIT_OPTS"
+ quoted_files=$(_forgit_quote_files "$@")
+ opts="
+ $FORGIT_FZF_DEFAULT_OPTS
+ +s +m --tiebreak=index
+ --bind=\"ctrl-y:execute-silent($FORGIT yank_sha {})\"
+ --preview=\"$FORGIT file_preview {} $quoted_files\"
+ $FORGIT_FIXUP_FZF_OPTS
+ "
+ target_commit=$(
+ git log "${graph[@]}" --color=always --format="$_forgit_log_format" "$@" |
+ _forgit_emojify |
+ FZF_DEFAULT_OPTS="$opts" fzf |
+ _forgit_extract_sha)
+ if [[ -n "$target_commit" ]] && git commit "${_forgit_fixup_git_opts[@]}" --fixup "$target_commit"; then
+ prev_commit=$(_forgit_previous_commit "$target_commit")
+ # rebase will fail if there are unstaged changes so --autostash is needed to temporarily stash them
+ # GIT_SEQUENCE_EDITOR=: is needed to skip the editor
+ GIT_SEQUENCE_EDITOR=: git rebase --autostash -i --autosquash "$prev_commit"
+ fi
+}
+
+_forgit_checkout_file_preview() {
+ git diff --color=always -- "$1" | _forgit_pager diff
+}
+
+_forgit_git_checkout_file() {
+ _forgit_checkout_file_git_opts=()
+ _forgit_parse_array _forgit_checkout_file_git_opts "$FORGIT_CHECKOUT_FILE_GIT_OPTS"
+ git checkout "${_forgit_checkout_file_git_opts[@]}" "$@"
+}
+
+# git checkout-file selector
+_forgit_checkout_file() {
+ _forgit_inside_work_tree || return 1
+ local files opts
+ [[ $# -ne 0 ]] && { _forgit_git_checkout_file -- "$@"; return $?; }
+ opts="
+ $FORGIT_FZF_DEFAULT_OPTS
+ -m -0
+ --preview=\"$FORGIT checkout_file_preview {}\"
+ $FORGIT_CHECKOUT_FILE_FZF_OPTS
+ "
+ files=()
+ while IFS='' read -r file; do
+ files+=("$file")
+ done < <(_forgit_list_files --modified |
+ FZF_DEFAULT_OPTS="$opts" fzf)
+ [[ "${#files[@]}" -gt 0 ]] && _forgit_git_checkout_file "${files[@]}"
+}
+
+_forgit_git_checkout_branch() {
+ _forgit_checkout_branch_git_opts=()
+ _forgit_parse_array _forgit_checkout_branch_git_opts "$FORGIT_CHECKOUT_BRANCH_GIT_OPTS"
+ git checkout "${_forgit_checkout_branch_git_opts[@]}" "$@"
+}
+
+# git checkout-branch selector
+_forgit_checkout_branch() {
+ _forgit_inside_work_tree || return 1
+ # if called with arguments, check if branch exists, else create a new one
+ if [[ $# -ne 0 ]]; then
+ if [[ "$*" == "-" ]] || git show-branch "$@" &>/dev/null; then
+ git switch "$@"
+ else
+ git switch -c "$@"
+ fi
+ checkout_status=$?
+ git status --short
+ return $checkout_status
+ fi
+
+ local opts branch
+ opts="
+ $FORGIT_FZF_DEFAULT_OPTS
+ +s +m --tiebreak=index --header-lines=1
+ --preview=\"$FORGIT branch_preview {1}\"
+ $FORGIT_CHECKOUT_BRANCH_FZF_OPTS
+ "
+ _forgit_checkout_branch_branch_git_opts=()
+ _forgit_parse_array _forgit_checkout_branch_branch_git_opts "$FORGIT_CHECKOUT_BRANCH_BRANCH_GIT_OPTS"
+ branch="$(git branch --color=always "${_forgit_checkout_branch_branch_git_opts[@]:---all}" | LC_ALL=C sort -k1.1,1.1 -rs |
+ FZF_DEFAULT_OPTS="$opts" fzf | awk '{print $1}')"
+ [[ -z "$branch" ]] && return 1
+
+ # track the remote branch if possible
+ if [[ "$branch" == "remotes/origin/"* ]]; then
+ if git branch | grep -qw "${branch#remotes/origin/}"; then
+ # hack to force creating a new branch which tracks the remote if a local branch already exists
+ _forgit_git_checkout_branch -b "track/${branch#remotes/origin/}" --track "$branch"
+ elif ! _forgit_git_checkout_branch --track "$branch" 2>/dev/null; then
+ _forgit_git_checkout_branch "$branch"
+ fi
+ else
+ _forgit_git_checkout_branch "$branch"
+ fi
+}
+
+_forgit_git_checkout_tag() {
+ _forgit_checkout_tag_git_opts=()
+ _forgit_parse_array _forgit_checkout_tag_git_opts "$FORGIT_CHECKOUT_TAG_GIT_OPTS"
+ git checkout "${_forgit_checkout_tag_git_opts[@]}" "$@"
+}
+
+# git checkout-tag selector
+_forgit_checkout_tag() {
+ _forgit_inside_work_tree || return 1
+ local opts
+ [[ $# -ne 0 ]] && { _forgit_git_checkout_tag "$@"; return $?; }
+ opts="
+ $FORGIT_FZF_DEFAULT_OPTS
+ +s +m --tiebreak=index
+ --preview=\"$FORGIT branch_preview {}\"
+ $FORGIT_CHECKOUT_TAG_FZF_OPTS
+ "
+ tag="$(git tag -l --sort=-v:refname | FZF_DEFAULT_OPTS="$opts" fzf)"
+ [[ -z "$tag" ]] && return 1
+ _forgit_git_checkout_tag "$tag"
+}
+
+_forgit_checkout_commit_preview() {
+ echo "$1" | _forgit_extract_sha | xargs -I% git show --color=always % | _forgit_pager show
+}
+
+_forgit_git_checkout_commit() {
+ _forgit_checkout_commit_git_opts=()
+ _forgit_parse_array _forgit_checkout_commit_git_opts "$FORGIT_CHECKOUT_COMMIT_GIT_OPTS"
+ git checkout "${_forgit_checkout_commit_git_opts[@]}" "$@"
+}
+
+# git checkout-commit selector
+_forgit_checkout_commit() {
+ _forgit_inside_work_tree || return 1
+ local opts graph commit
+ [[ $# -ne 0 ]] && { _forgit_git_checkout_commit "$@"; return $?; }
+ opts="
+ $FORGIT_FZF_DEFAULT_OPTS
+ +s +m --tiebreak=index
+ --bind=\"ctrl-y:execute-silent($FORGIT yank_sha {})\"
+ --preview=\"$FORGIT checkout_commit_preview {}\"
+ $FORGIT_CHECKOUT_COMMIT_FZF_OPTS
+ "
+ graph=()
+ [[ $_forgit_log_graph_enable == true ]] && graph=(--graph)
+ commit="$(git log "${graph[@]}" --color=always --format="$_forgit_log_format" |
+ _forgit_emojify |
+ FZF_DEFAULT_OPTS="$opts" fzf | _forgit_extract_sha)"
+ _forgit_git_checkout_commit "$commit"
+}
+
+_forgit_branch_preview() {
+ # the trailing '--' ensures that this works for branches that have a name
+ # that is identical to a file
+ git log "$1" "${_forgit_log_preview_options[@]}" --
+}
+
+_forgit_git_branch_delete() {
+ _forgit_branch_delete_git_opts=()
+ _forgit_parse_array _forgit_branch_delete_git_opts "$FORGIT_BRANCH_DELETE_GIT_OPTS"
+ git branch "${_forgit_branch_delete_git_opts[@]}" -D "$@"
+}
+
+_forgit_branch_delete() {
+ _forgit_inside_work_tree || return 1
+ local opts
+ [[ $# -ne 0 ]] && { _forgit_git_branch_delete "$@"; return $?; }
+
+ opts="
+ $FORGIT_FZF_DEFAULT_OPTS
+ +s --multi --tiebreak=index --header-lines=1
+ --preview=\"$FORGIT branch_preview {1}\"
+ $FORGIT_BRANCH_DELETE_FZF_OPTS
+ "
+
+ for branch in $(git branch --color=always |
+ LC_ALL=C sort -k1.1,1.1 -rs |
+ FZF_DEFAULT_OPTS="$opts" fzf |
+ awk '{print $1}')
+ do
+ _forgit_git_branch_delete "$branch"
+ done
+}
+
+_forgit_revert_preview() {
+ echo "$1" |
+ cut -f2- |
+ _forgit_extract_sha |
+ xargs -I% git show --color=always % |
+ _forgit_pager show
+}
+
+_forgit_git_revert() {
+ _forgit_revert_commit_git_opts=()
+ _forgit_parse_array _forgit_revert_commit_git_opts "$FORGIT_REVERT_COMMIT_GIT_OPTS"
+ git revert "${_forgit_revert_commit_git_opts[@]}" "$@"
+}
+
+# git revert-commit selector
+_forgit_revert_commit() {
+ _forgit_inside_work_tree || return 1
+ local opts commits IFS
+ [[ $# -ne 0 ]] && { _forgit_git_revert "$@"; return $?; }
+
+ opts="
+ $FORGIT_FZF_DEFAULT_OPTS
+ -m +s --tiebreak=index
+ --ansi --with-nth 2..
+ --preview=\"$FORGIT revert_preview {}\"
+ $FORGIT_REVERT_COMMIT_FZF_OPTS
+ "
+ graph=()
+ [[ $_forgit_log_graph_enable == true ]] && graph=(--graph)
+
+ # in this function, we do something interesting to maintain proper ordering as it's assumed
+ # you generally want to revert newest->oldest when you multiselect
+ # The instances of "cut", "nl" and "sort" all serve this purpose
+ # Please see https://github.com/wfxr/forgit/issues/253 for more details
+
+ commits=()
+ while IFS='' read -r commit; do
+ commits+=("$commit")
+ done < <(
+ git log "${graph[@]}" --color=always --format="$_forgit_log_format" |
+ _forgit_emojify |
+ nl |
+ FZF_DEFAULT_OPTS="$opts" fzf |
+ sort -n -k 1 |
+ cut -f2- |
+ sed 's/^[^a-f^0-9]*\([a-f0-9]*\).*/\1/')
+
+ [ ${#commits[@]} -eq 0 ] && return 1
+
+ _forgit_git_revert "${commits[@]}"
+}
+
+_forgit_blame_preview() {
+ if _forgit_is_file_tracked "$1"; then
+ _forgit_blame_git_opts=()
+ _forgit_parse_array _forgit_blame_git_opts "$FORGIT_BLAME_GIT_OPTS"
+ git blame --date=short "${_forgit_blame_git_opts[@]}" "$@" | _forgit_pager blame
+ else
+ echo "File not tracked"
+ fi
+}
+
+_forgit_git_blame() {
+ _forgit_blame_git_opts=()
+ _forgit_parse_array _forgit_blame_git_opts "$FORGIT_BLAME_GIT_OPTS"
+ git blame "${_forgit_blame_git_opts[@]}" "$@"
+}
+
+# git blame viewer
+_forgit_blame() {
+ _forgit_inside_work_tree || return 1
+ local opts flags file
+ _forgit_contains_non_flags "$@" && { _forgit_git_blame "$@"; return $?; }
+ flags=()
+ while IFS='' read -r flag; do
+ flags+=("$flag")
+ done < <(git rev-parse --flags "$@")
+ opts="
+ $FORGIT_FZF_DEFAULT_OPTS
+ --preview=\"$FORGIT blame_preview {} ${flags[*]}\"
+ $FORGIT_BLAME_FZF_OPTS
+ "
+ # flags is not quoted here, which is fine given that they are retrieved
+ # with git rev-parse and can only contain flags
+ file=$(FZF_DEFAULT_OPTS="$opts" fzf)
+ [[ -z "$file" ]] && return 1
+ _forgit_git_blame "$file" "${flags[@]}"
+}
+
+# git ignore generator
+export FORGIT_GI_REPO_REMOTE=${FORGIT_GI_REPO_REMOTE:-https://github.com/dvcs/gitignore}
+export FORGIT_GI_REPO_LOCAL="${FORGIT_GI_REPO_LOCAL:-${XDG_CACHE_HOME:-$HOME/.cache}/forgit/gi/repos/dvcs/gitignore}"
+export FORGIT_GI_TEMPLATES=${FORGIT_GI_TEMPLATES:-$FORGIT_GI_REPO_LOCAL/templates}
+
+_forgit_ignore_preview() {
+ quoted_files=()
+ for file in "$FORGIT_GI_TEMPLATES/$1"{,.gitignore}; do
+ quoted_files+=("'$file'")
+ done
+ _forgit_pager ignore "${quoted_files[@]}" 2>/dev/null
+}
+
+_forgit_ignore() {
+ [ -d "$FORGIT_GI_REPO_LOCAL" ] || _forgit_ignore_update
+ local IFS args opts
+ opts="
+ $FORGIT_FZF_DEFAULT_OPTS
+ -m --preview-window='right:70%'
+ --preview=\"$FORGIT ignore_preview {2}\"
+ $FORGIT_IGNORE_FZF_OPTS
+ "
+ args=("$@")
+ if [[ $# -eq 0 ]]; then
+ args=()
+ while IFS='' read -r arg; do
+ args+=("$arg")
+ done < <(_forgit_ignore_list | nl -w4 -s' ' |
+ FZF_DEFAULT_OPTS="$opts" fzf | awk '{print $2}')
+ fi
+ [ ${#args[@]} -eq 0 ] && return 1
+ _forgit_ignore_get "${args[@]}"
+}
+_forgit_ignore_update() {
+ if [[ -d "$FORGIT_GI_REPO_LOCAL" ]]; then
+ _forgit_info 'Updating gitignore repo...'
+ (cd "$FORGIT_GI_REPO_LOCAL" && git pull --no-rebase --ff) || return 1
+ else
+ _forgit_info 'Initializing gitignore repo...'
+ git clone --depth=1 "$FORGIT_GI_REPO_REMOTE" "$FORGIT_GI_REPO_LOCAL"
+ fi
+}
+_forgit_ignore_get() {
+ local item filename header
+ for item in "$@"; do
+ if filename=$(find -L "$FORGIT_GI_TEMPLATES" -type f \( -iname "${item}.gitignore" -o -iname "${item}" \) -print -quit); then
+ [[ -z "$filename" ]] && _forgit_warn "No gitignore template found for '$item'." && continue
+ header="${filename##*/}" && header="${header%.gitignore}"
+ echo "### $header" && cat "$filename" && echo
+ fi
+ done
+}
+_forgit_ignore_list() {
+ find "$FORGIT_GI_TEMPLATES" -print |sed -e 's#.gitignore$##' -e 's#.*/##' | sort -fu
+}
+_forgit_ignore_clean() {
+ setopt localoptions rmstarsilent
+ [[ -d "$FORGIT_GI_REPO_LOCAL" ]] && rm -rf "$FORGIT_GI_REPO_LOCAL"
+}
+
+public_commands=(
+ "add"
+ "blame"
+ "branch_delete"
+ "checkout_branch"
+ "checkout_commit"
+ "checkout_file"
+ "checkout_tag"
+ "cherry_pick"
+ "cherry_pick_from_branch"
+ "clean"
+ "diff"
+ "fixup"
+ "ignore"
+ "log"
+ "rebase"
+ "reset_head"
+ "revert_commit"
+ "stash_show"
+ "stash_push"
+)
+
+private_commands=(
+ "add_preview"
+ "blame_preview"
+ "branch_preview"
+ "checkout_commit_preview"
+ "checkout_file_preview"
+ "cherry_pick_from_branch_preview"
+ "cherry_pick_preview"
+ "clean_preview"
+ "diff_enter"
+ "file_preview"
+ "ignore_preview"
+ "revert_preview"
+ "reset_head_preview"
+ "stash_push_preview"
+ "stash_show_preview"
+ "yank_sha"
+ "yank_stash_name"
+ "log_preview"
+ "log_enter"
+ "exec_diff"
+ "diff_view"
+ "edit_diffed_file"
+ "edit_add_file"
+ "pager"
+)
+
+cmd="$1"
+shift
+
+# shellcheck disable=SC2076
+if [[ ! " ${public_commands[*]} " =~ " ${cmd} " ]] && [[ ! " ${private_commands[*]} " =~ " ${cmd} " ]]; then
+ if [[ -z "$cmd" ]]; then
+ printf "forgit: missing command\n\n"
+ else
+ printf "forgit: '%s' is not a valid forgit command.\n\n" "$cmd"
+ fi
+ printf "The following commands are supported:\n"
+ printf "\t%s\n" "${public_commands[@]}"
+ exit 1
+fi
+
+_forgit_"${cmd}" "$@"
diff --git a/dev/fish-plugins/forgit/completions/git-forgit.fish b/dev/fish-plugins/forgit/completions/git-forgit.fish
new file mode 100644
index 000000000..aa787bfee
--- /dev/null
+++ b/dev/fish-plugins/forgit/completions/git-forgit.fish
@@ -0,0 +1,59 @@
+#
+# forgit completions for fish plugin
+#
+# Place this file inside your /completions/ directory.
+# It's usually located at ~/.config/fish/completions/. The file is lazily
+# sourced when git-forgit command or forgit subcommand of git is invoked.
+
+function __fish_forgit_needs_subcommand
+ for subcmd in add blame branch_delete checkout_branch checkout_commit checkout_file checkout_tag \
+ cherry_pick cherry_pick_from_branch clean diff fixup ignore log rebase reset_head revert_commit \
+ stash_show stash_push
+ if contains -- $subcmd (commandline -opc)
+ return 1
+ end
+ end
+ return 0
+end
+
+# Load helper functions in git completion file
+not functions -q __fish_git && source $__fish_data_dir/completions/git.fish
+
+# No file completion by default
+complete -c git-forgit -x
+
+complete -c git-forgit -n __fish_forgit_needs_subcommand -a add -d 'git add selector'
+complete -c git-forgit -n __fish_forgit_needs_subcommand -a blame -d 'git blame viewer'
+complete -c git-forgit -n __fish_forgit_needs_subcommand -a branch_delete -d 'git branch deletion selector'
+complete -c git-forgit -n __fish_forgit_needs_subcommand -a checkout_branch -d 'git checkout branch selector'
+complete -c git-forgit -n __fish_forgit_needs_subcommand -a checkout_commit -d 'git checkout commit selector'
+complete -c git-forgit -n __fish_forgit_needs_subcommand -a checkout_file -d 'git checkout-file selector'
+complete -c git-forgit -n __fish_forgit_needs_subcommand -a checkout_tag -d 'git checkout tag selector'
+complete -c git-forgit -n __fish_forgit_needs_subcommand -a cherry_pick -d 'git cherry-picking'
+complete -c git-forgit -n __fish_forgit_needs_subcommand -a cherry_pick_from_branch -d 'git cherry-picking with interactive branch selection'
+complete -c git-forgit -n __fish_forgit_needs_subcommand -a clean -d 'git clean selector'
+complete -c git-forgit -n __fish_forgit_needs_subcommand -a diff -d 'git diff viewer'
+complete -c git-forgit -n __fish_forgit_needs_subcommand -a fixup -d 'git fixup'
+complete -c git-forgit -n __fish_forgit_needs_subcommand -a ignore -d 'git ignore generator'
+complete -c git-forgit -n __fish_forgit_needs_subcommand -a log -d 'git commit viewer'
+complete -c git-forgit -n __fish_forgit_needs_subcommand -a rebase -d 'git rebase'
+complete -c git-forgit -n __fish_forgit_needs_subcommand -a reset_head -d 'git reset HEAD (unstage) selector'
+complete -c git-forgit -n __fish_forgit_needs_subcommand -a revert_commit -d 'git revert commit selector'
+complete -c git-forgit -n __fish_forgit_needs_subcommand -a stash_show -d 'git stash viewer'
+complete -c git-forgit -n __fish_forgit_needs_subcommand -a stash_push -d 'git stash push selector'
+
+complete -c git-forgit -n '__fish_seen_subcommand_from add' -a "(complete -C 'git add ')"
+complete -c git-forgit -n '__fish_seen_subcommand_from branch_delete' -a "(__fish_git_local_branches)"
+complete -c git-forgit -n '__fish_seen_subcommand_from checkout_branch' -a "(complete -C 'git switch ')"
+complete -c git-forgit -n '__fish_seen_subcommand_from checkout_commit' -a "(__fish_git_commits)"
+complete -c git-forgit -n '__fish_seen_subcommand_from checkout_file' -a "(__fish_git_files modified)"
+complete -c git-forgit -n '__fish_seen_subcommand_from checkout_tag' -a "(__fish_git_tags)" -d Tag
+complete -c git-forgit -n '__fish_seen_subcommand_from cherry_pick' -a "(complete -C 'git cherry-pick ')"
+complete -c git-forgit -n '__fish_seen_subcommand_from clean' -a "(__fish_git_files untracked ignored)"
+complete -c git-forgit -n '__fish_seen_subcommand_from fixup' -a "(__fish_git_local_branches)"
+complete -c git-forgit -n '__fish_seen_subcommand_from log' -a "(complete -C 'git log ')"
+complete -c git-forgit -n '__fish_seen_subcommand_from rebase' -a "(complete -C 'git rebase ')"
+complete -c git-forgit -n '__fish_seen_subcommand_from reset_head' -a "(__fish_git_files all-staged)"
+complete -c git-forgit -n '__fish_seen_subcommand_from revert_commit' -a "(__fish_git_commits)"
+complete -c git-forgit -n '__fish_seen_subcommand_from stash_show' -a "(__fish_git_complete_stashes)"
+complete -c git-forgit -n '__fish_seen_subcommand_from stash_push' -a "(__fish_git_files modified deleted modified-staged-deleted)"
diff --git a/dev/fish-plugins/forgit/conf.d/forgit.plugin.fish b/dev/fish-plugins/forgit/conf.d/forgit.plugin.fish
new file mode 100644
index 000000000..98d6578f0
--- /dev/null
+++ b/dev/fish-plugins/forgit/conf.d/forgit.plugin.fish
@@ -0,0 +1,53 @@
+# MIT (c) Chris Apple
+
+set -l install_dir (dirname (status dirname))
+set -x FORGIT_INSTALL_DIR "$install_dir/conf.d"
+set -x FORGIT "$FORGIT_INSTALL_DIR/bin/git-forgit"
+if not test -e "$FORGIT"
+ set -x FORGIT_INSTALL_DIR "$install_dir/vendor_conf.d"
+ set -x FORGIT "$FORGIT_INSTALL_DIR/bin/git-forgit"
+end
+
+function forgit::warn
+ printf "%b[Warn]%b %s\n" '\e[0;33m' '\e[0m' "$argv" >&2
+end
+
+# backwards compatibility:
+# export all user-defined FORGIT variables to make them available in git-forgit
+set unexported_vars 0
+set | awk -F ' ' '{ print $1 }' | grep FORGIT_ | while read var
+ if not set -x | grep -q "^$var\b"
+ if test $unexported_vars = 0
+ forgit::warn "Config options have to be exported in future versions of forgit."
+ forgit::warn "Please update your config accordingly:"
+ end
+ forgit::warn " set -x $var \"$$var\""
+ set unexported_vars (math $unexported_vars + 1)
+ set -x $var $$var
+ end
+end
+
+# alias `git-forgit` to the full-path of the command
+alias git-forgit "$FORGIT"
+
+# register abbreviations
+if test -z "$FORGIT_NO_ALIASES"
+ abbr -a -- (string collect $forgit_add; or string collect "ga") git-forgit add
+ abbr -a -- (string collect $forgit_reset_head; or string collect "grh") git-forgit reset_head
+ abbr -a -- (string collect $forgit_log; or string collect "glo") git-forgit log
+ abbr -a -- (string collect $forgit_diff; or string collect "gd") git-forgit diff
+ abbr -a -- (string collect $forgit_ignore; or string collect "gi") git-forgit ignore
+ abbr -a -- (string collect $forgit_checkout_file; or string collect "gcf") git-forgit checkout_file
+ abbr -a -- (string collect $forgit_checkout_branch; or string collect "gcb") git-forgit checkout_branch
+ abbr -a -- (string collect $forgit_branch_delete; or string collect "gbd") git-forgit branch_delete
+ abbr -a -- (string collect $forgit_clean; or string collect "gclean") git-forgit clean
+ abbr -a -- (string collect $forgit_stash_show; or string collect "gss") git-forgit stash_show
+ abbr -a -- (string collect $forgit_stash_push; or string collect "gsp") git-forgit stash_push
+ abbr -a -- (string collect $forgit_cherry_pick; or string collect "gcp") git-forgit cherry_pick_from_branch
+ abbr -a -- (string collect $forgit_rebase; or string collect "grb") git-forgit rebase
+ abbr -a -- (string collect $forgit_fixup; or string collect "gfu") git-forgit fixup
+ abbr -a -- (string collect $forgit_checkout_commit; or string collect "gco") git-forgit checkout_commit
+ abbr -a -- (string collect $forgit_revert_commit; or string collect "grc") git-forgit revert_commit
+ abbr -a -- (string collect $forgit_blame; or string collect "gbl") git-forgit blame
+ abbr -a -- (string collect $forgit_checkout_tag; or string collect "gct") git-forgit checkout_tag
+end
diff --git a/dev/fish-plugins/fzf.fish/LICENSE.md b/dev/fish-plugins/fzf.fish/LICENSE.md
new file mode 100644
index 000000000..20485b1d0
--- /dev/null
+++ b/dev/fish-plugins/fzf.fish/LICENSE.md
@@ -0,0 +1,21 @@
+# MIT License
+
+Copyright © `2020` `Patrick`
+
+Permission is hereby granted, free of charge, to any person obtaining a copy
+of this software and associated documentation files (the "Software"), to deal
+in the Software without restriction, including without limitation the rights
+to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
+copies of the Software, and to permit persons to whom the Software is
+furnished to do so, subject to the following conditions:
+
+The above copyright notice and this permission notice shall be included in all
+copies or substantial portions of the Software.
+
+THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
+AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
+OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
+SOFTWARE.
diff --git a/dev/fish-plugins/fzf.fish/README.md b/dev/fish-plugins/fzf.fish/README.md
new file mode 100644
index 000000000..e3c10cd87
--- /dev/null
+++ b/dev/fish-plugins/fzf.fish/README.md
@@ -0,0 +1,220 @@
+
+
+# fzf.fish 🔍🐟
+
+[![latest release badge][]](https://github.com/patrickf1/fzf.fish/releases)
+[![build status badge][]](https://github.com/patrickf1/fzf.fish/actions)
+[![awesome badge][]](https://git.io/awsm.fish)
+
+
+
+Augment your [Fish][] command line with mnemonic key bindings to efficiently find what you need using [fzf][].
+
+https://user-images.githubusercontent.com/1967248/197308919-51d04602-2d5f-46aa-a96e-6cf1617e3067.mov
+
+## Search commands
+
+Use `fzf.fish` to interactively find and insert file paths, git commit hashes, and other entities into your command line. Tab to select multiple entries. If you trigger a search while your cursor is on a word, that word will be used to seed the fzf query and will be replaced by your selection. All searches include a preview of the entity hovered over to help you find what you're looking for.
+
+### 📁 Search Directory
+
+![Search Directory example](../assets/directory.png)
+
+- **Fzf input:** recursive listing of current directory's non-hidden files
+- **Output:** relative paths of selected files
+- **Key binding and mnemonic:** Ctrl+Alt+F (`F` for file)
+- **Preview window:** file with syntax highlighting, directory contents, or file type
+- **Remarks**
+ - directories are inserted with a trailing `/`, so if you select exactly one directory, you can immediately hit ENTER to [cd into it][cd docs]
+ - if the current token is a directory with a trailing slash (e.g. `.config/`), then that directory is searched instead
+ - [ignores files that are also ignored by git](#fd-gi)
+
+### 🪵 Search Git Log
+
+![Search Git Log example](../assets/git_log.png)
+
+- **Fzf input:** the current repository's formatted `git log`
+- **Output:** hashes of selected commits
+- **Key binding and mnemonic:** Ctrl+Alt+L (`L` for log)
+- **Preview window:** commit message and diff
+
+### 📝 Search Git Status
+
+![Search Git Status example](../assets/git_status.png)
+
+- **Fzf input:** the current repository's `git status`
+- **Output:** relative paths of selected lines
+- **Key binding and mnemonic:** Ctrl+Alt+S (`S` for status)
+- **Preview window:** the git diff of the file
+
+### 📜 Search History
+
+![Search History example](../assets/history.png)
+
+- **Fzf input:** Fish's command history
+- **Output:** selected commands
+- **Key binding and mnemonic:** Ctrl+R (`R` for reverse-i-search)
+- **Preview window:** the entire command with Fish syntax highlighting
+
+### 🖥️ Search Processes
+
+![Search Processes example](../assets/processes.png)
+
+- **Fzf input:** the pid and command of all running processes, outputted by `ps`
+- **Output:** pids of selected processes
+- **Key binding and mnemonic:** Ctrl+Alt+P (`P` for process)
+- **Preview window:** the CPU usage, memory usage, start time, and other information about the process
+
+### 💲 Search Variables
+
+![Search Variables example](../assets/variables.png)
+
+- **Fzf input:** all the shell variables currently [in scope][var scope]
+- **Output:** selected shell variables
+- **Key binding and mnemonic:** Ctrl+V (`V` for variable)
+- **Preview window:** the variable's scope info and values
+- `$history` is excluded for technical reasons so use [Search History](#-search-history) instead to inspect it
+
+## Installation
+
+First, install a proper version of these CLI dependencies:
+
+| CLI | Minimum version required | Description |
+| -------- | ------------------------ | --------------------------------------- |
+| [fish][] | 3.4.0 | a modern shell |
+| [fzf][] | 0.33.0 | fuzzy finder that powers this plugin |
+| [fd][] | 8.5.0 | faster, colorized alternative to `find` |
+| [bat][] | 0.16.0 | smarter `cat` with syntax highlighting |
+
+[fd][] and [bat][] only need to be installed if you will use [Search Directory][].
+
+Next, because `fzf.fish` is incompatible with other fzf plugins, [check for and remove these two common alternatives](https://github.com/PatrickF1/fzf.fish/wiki/Uninstalling-other-fzf-plugins).
+
+Finally, install this plugin with [Fisher][].
+
+> `fzf.fish` can be installed manually or with other plugin managers but only Fisher is officially supported.
+
+```fish
+fisher install PatrickF1/fzf.fish
+```
+
+## Configuration
+
+### Customize key bindings
+
+`fzf.fish` includes an ergonomic function for configuring its key bindings. Read [its documentation](/functions/_fzf_configure_bindings_help.fish):
+
+```fish
+fzf_configure_bindings --help
+```
+
+Call `fzf_configure_bindings` in your `config.fish` in order to persist your custom bindings.
+
+### Change fzf options for all commands
+
+fzf supports global default options via the [FZF_DEFAULT_OPTS and FZF_DEFAULT_OPTS_FILE](https://github.com/junegunn/fzf#environment-variables) environment variables. If neither are set, `fzf.fish` sets its own [default opts whenever it executes fzf](functions/_fzf_wrapper.fish).
+
+### Change fzf options for a specific command
+
+Each command's fzf options can be configured via a variable:
+
+| Command | Variable |
+| ----------------- | --------------------- |
+| Search Directory | `fzf_directory_opts` |
+| Search Git Log | `fzf_git_log_opts` |
+| Search Git Status | `fzf_git_status_opts` |
+| Search History | `fzf_history_opts` |
+| Search Processes | `fzf_processes_opts` |
+| Search Variables | `fzf_variables_opts` |
+
+The value of each variable is appended last to fzf's options list. Because fzf uses the last instance of an option if it is specified multiple times, custom options take precedence. Custom fzf options unlock a variety of augmentations:
+
+- add [fzf key bindings](https://www.mankier.com/1/fzf#Key/Event_Bindings) to [open files in Vim](https://github.com/PatrickF1/fzf.fish/pull/273)
+- adjust the preview command or window
+- [re-populate fzf's input list on demand](https://github.com/junegunn/fzf/issues/1750)
+- change the [search mode](https://github.com/junegunn/fzf#search-syntax)
+
+Find more ideas and tips in the [Cookbook](https://github.com/PatrickF1/fzf.fish/wiki/Cookbook).
+
+### Change how Search Directory previews directories and regular files
+
+[Search Directory][], by default, executes `ls` to preview directories and `bat` to preview [regular files](https://stackoverflow.com/questions/6858452).
+
+To use your own directory preview command, set it in `fzf_preview_dir_cmd`:
+
+```fish
+set fzf_preview_dir_cmd eza --all --color=always
+```
+
+And to use your own file preview command, set it in `fzf_preview_file_cmd`:
+
+```fish
+set fzf_preview_file_cmd cat -n
+```
+
+Omit the target path for both variables as `fzf.fish` will itself [specify the argument to preview](functions/_fzf_preview_file.fish).
+
+### Change what files are listed by Search Directory
+
+To pass custom options to `fd` when [Search Directory][] executes it to populate the list of files, set them in `fzf_fd_opts`:
+
+```fish
+set fzf_fd_opts --hidden --max-depth 5
+```
+
+By default, `fd` hides files listed in `.gitignore`. You can disable this behavior by adding the `--no-ignore` flag to `fzf_fd_opts`.
+
+### Change Search Git Log's commit formatting
+
+[Search Git Log][] executes `git log --format` to format the list of commits. To use your own [commit log format](https://git-scm.com/docs/git-log#Documentation/git-log.txt-emnem), set it in `fzf_git_log_format`. For example, this shows the hash and subject for each commit:
+
+```fish
+set fzf_git_log_format "%H %s"
+```
+
+The format must be one line per commit and the hash must be the first field, or else Search Git Log will fail to determine which commits you selected.
+
+### Integrate with a diff highlighter
+
+To pipe the git diff previews from [Search Git Log][] and [Search Git Status][] through a highlighter tool (e.g. [delta](https://github.com/dandavison/delta) or [diff-so-fancy](https://github.com/so-fancy/diff-so-fancy)), set a command invoking the highlighter in `fzf_diff_highlighter`. It should not pipe its output to a pager:
+
+```fish
+# width=20 so delta decorations don't wrap around small fzf preview pane
+set fzf_diff_highlighter delta --paging=never --width=20
+# Or, if using DFS
+set fzf_diff_highlighter diff-so-fancy
+```
+
+### Change Search History's date time format
+
+[Search History][] shows the date time each command was executed. To change how its formatted, set your [strftime format string](https://devhints.io/strftime) in `fzf_history_time_format`. For example, this shows the date time as DD-MM-YY:
+
+```fish
+set fzf_history_time_format %d-%m-%y
+```
+
+Do not to include the vertical box-drawing character `│` (not to be confused with the pipe character `|`) as it is relied on to delineate the date time from the command.
+
+## Further reading
+
+Find answers to these questions and more in the [project Wiki](https://github.com/PatrickF1/fzf.fish/wiki):
+
+- How does `fzf.fish` [compare](https://github.com/PatrickF1/fzf.fish/wiki/Prior-Art) to other popular fzf plugins for Fish?
+- Why isn't this [command working](https://github.com/PatrickF1/fzf.fish/wiki/Troubleshooting)?
+- How can I [customize](https://github.com/PatrickF1/fzf.fish/wiki/Cookbook) this command?
+- How can I [contribute](https://github.com/PatrickF1/fzf.fish/wiki/Contributing) to this plugin?
+
+[awesome badge]: https://awesome.re/mentioned-badge.svg
+[bat]: https://github.com/sharkdp/bat
+[build status badge]: https://img.shields.io/github/actions/workflow/status/PatrickF1/fzf.fish/continuous_integration.yml?branch=main
+[cd docs]: https://fishshell.com/docs/current/cmds/cd.html
+[fd]: https://github.com/sharkdp/fd
+[fish]: https://fishshell.com
+[fisher]: https://github.com/jorgebucaran/fisher
+[fzf]: https://github.com/junegunn/fzf
+[latest release badge]: https://img.shields.io/github/v/release/patrickf1/fzf.fish
+[search directory]: #-search-directory
+[search git log]: #-search-git-log
+[search git status]: #-search-git-status
+[search history]: #-search-history
+[var scope]: https://fishshell.com/docs/current/#variable-scope
diff --git a/dev/fish-plugins/fzf.fish/completions/fzf_configure_bindings.fish b/dev/fish-plugins/fzf.fish/completions/fzf_configure_bindings.fish
new file mode 100644
index 000000000..b38ef927f
--- /dev/null
+++ b/dev/fish-plugins/fzf.fish/completions/fzf_configure_bindings.fish
@@ -0,0 +1,8 @@
+complete fzf_configure_bindings --no-files
+complete fzf_configure_bindings --long help --short h --description "Print help" --condition "not __fish_seen_argument --help -h"
+complete fzf_configure_bindings --long directory --description "Change the key binding for Search Directory" --condition "not __fish_seen_argument --directory"
+complete fzf_configure_bindings --long git_log --description "Change the key binding for Search Git Log" --condition "not __fish_seen_argument --git_log"
+complete fzf_configure_bindings --long git_status --description "Change the key binding for Search Git Status" --condition "not __fish_seen_argument --git_status"
+complete fzf_configure_bindings --long history --description "Change the key binding for Search History" --condition "not __fish_seen_argument --history"
+complete fzf_configure_bindings --long processes --description "Change the key binding for Search Processes" --condition "not __fish_seen_argument --processes"
+complete fzf_configure_bindings --long variables --description "Change the key binding for Search Variables" --condition "not __fish_seen_argument --variables"
diff --git a/dev/fish-plugins/fzf.fish/conf.d/fzf.fish b/dev/fish-plugins/fzf.fish/conf.d/fzf.fish
new file mode 100644
index 000000000..8156c11b8
--- /dev/null
+++ b/dev/fish-plugins/fzf.fish/conf.d/fzf.fish
@@ -0,0 +1,28 @@
+# fzf.fish is only meant to be used in interactive mode. If not in interactive mode and not in CI, skip the config to speed up shell startup
+if not status is-interactive && test "$CI" != true
+ exit
+end
+
+# Because of scoping rules, to capture the shell variables exactly as they are, we must read
+# them before even executing _fzf_search_variables. We use psub to store the
+# variables' info in temporary files and pass in the filenames as arguments.
+# This variable is global so that it can be referenced by fzf_configure_bindings and in tests
+set --global _fzf_search_vars_command '_fzf_search_variables (set --show | psub) (set --names | psub)'
+
+
+# Install the default bindings, which are mnemonic and minimally conflict with fish's preset bindings
+fzf_configure_bindings
+
+# Doesn't erase autoloaded _fzf_* functions because they are not easily accessible once key bindings are erased
+function _fzf_uninstall --on-event fzf_uninstall
+ _fzf_uninstall_bindings
+
+ set --erase _fzf_search_vars_command
+ functions --erase _fzf_uninstall _fzf_migration_message _fzf_uninstall_bindings fzf_configure_bindings
+ complete --erase fzf_configure_bindings
+
+ set_color cyan
+ echo "fzf.fish uninstalled."
+ echo "You may need to manually remove fzf_configure_bindings from your config.fish if you were using custom key bindings."
+ set_color normal
+end
diff --git a/dev/fish-plugins/fzf.fish/functions/_fzf_configure_bindings_help.fish b/dev/fish-plugins/fzf.fish/functions/_fzf_configure_bindings_help.fish
new file mode 100644
index 000000000..ecfe68ec8
--- /dev/null
+++ b/dev/fish-plugins/fzf.fish/functions/_fzf_configure_bindings_help.fish
@@ -0,0 +1,43 @@
+function _fzf_configure_bindings_help --description "Prints the help message for fzf_configure_bindings."
+ echo "\
+USAGE:
+ fzf_configure_bindings [--COMMAND=[KEY_SEQUENCE]...]
+
+DESCRIPTION
+ fzf_configure_bindings installs key bindings for fzf.fish's commands and erases any bindings it
+ previously installed. It installs bindings for both default and insert modes. fzf.fish executes
+ it without options on fish startup to install the out-of-the-box key bindings.
+
+ By default, commands are bound to a mnemonic key sequence, shown below. Each command's binding
+ can be configured using a namesake corresponding option:
+ COMMAND | DEFAULT KEY SEQUENCE | CORRESPONDING OPTION
+ Search Directory | Ctrl+Alt+F (F for file) | --directory
+ Search Git Log | Ctrl+Alt+L (L for log) | --git_log
+ Search Git Status | Ctrl+Alt+S (S for status) | --git_status
+ Search History | Ctrl+R (R for reverse) | --history
+ Search Processes | Ctrl+Alt+P (P for process) | --processes
+ Search Variables | Ctrl+V (V for variable) | --variables
+ Override a command's binding by specifying its corresponding option with the desired key
+ sequence. Disable a command's binding by specifying its corresponding option with no value.
+
+ Because fzf_configure_bindings erases bindings it previously installed, it can be cleanly
+ executed multiple times. Once the desired fzf_configure_bindings command has been found, add it
+ to your config.fish in order to persist the customized bindings.
+
+ In terms of validation, fzf_configure_bindings fails if passed unknown options. It expects an
+ equals sign between an option's name and value. However, it does not validate key sequences.
+
+ Pass -h or --help to print this help message and exit.
+
+EXAMPLES
+ Default bindings but bind Search Directory to Ctrl+F and Search Variables to Ctrl+Alt+V
+ \$ fzf_configure_bindings --directory=\cf --variables=\e\cv
+ Default bindings but disable Search History
+ \$ fzf_configure_bindings --history=
+ An agglomeration of different options
+ \$ fzf_configure_bindings --git_status=\cg --history=\ch --variables= --processes=
+
+SEE Also
+ To learn more about fish key bindings, see bind(1) and fish_key_reader(1).
+"
+end
diff --git a/dev/fish-plugins/fzf.fish/functions/_fzf_extract_var_info.fish b/dev/fish-plugins/fzf.fish/functions/_fzf_extract_var_info.fish
new file mode 100644
index 000000000..dd4e9523a
--- /dev/null
+++ b/dev/fish-plugins/fzf.fish/functions/_fzf_extract_var_info.fish
@@ -0,0 +1,15 @@
+# helper function for _fzf_search_variables
+function _fzf_extract_var_info --argument-names variable_name set_show_output --description "Extract and reformat lines pertaining to \$variable_name from \$set_show_output."
+ # Extract only the lines about the variable, all of which begin with either
+ # $variable_name: ...or... $variable_name[
+ string match --regex "^\\\$$variable_name(?::|\[).*" <$set_show_output |
+
+ # Strip the variable name prefix, including ": " for scope info lines
+ string replace --regex "^\\\$$variable_name(?:: )?" '' |
+
+ # Distill the lines of values, replacing...
+ # [1]: |value|
+ # ...with...
+ # [1] value
+ string replace --regex ": \|(.*)\|" ' $1'
+end
diff --git a/dev/fish-plugins/fzf.fish/functions/_fzf_preview_changed_file.fish b/dev/fish-plugins/fzf.fish/functions/_fzf_preview_changed_file.fish
new file mode 100644
index 000000000..78dd5611f
--- /dev/null
+++ b/dev/fish-plugins/fzf.fish/functions/_fzf_preview_changed_file.fish
@@ -0,0 +1,49 @@
+# helper for _fzf_search_git_status
+# arg should be a line from git status --short, e.g.
+# MM functions/_fzf_preview_changed_file.fish
+# D README.md
+# R LICENSE -> "New License"
+function _fzf_preview_changed_file --argument-names path_status --description "Show the git diff of the given file."
+ # remove quotes because they'll be interpreted literally by git diff
+ # no need to requote when referencing $path because fish does not perform word splitting
+ # https://fishshell.com/docs/current/fish_for_bash_users.html
+ set -f path (string unescape (string sub --start 4 $path_status))
+ # first letter of short format shows index, second letter shows working tree
+ # https://git-scm.com/docs/git-status/2.35.0#_short_format
+ set -f index_status (string sub --length 1 $path_status)
+ set -f working_tree_status (string sub --start 2 --length 1 $path_status)
+
+ set -f diff_opts --color=always
+
+ if test $index_status = '?'
+ _fzf_report_diff_type Untracked
+ _fzf_preview_file $path
+ else if contains {$index_status}$working_tree_status DD AU UD UA DU AA UU
+ # Unmerged statuses taken directly from git status help's short format table
+ # Unmerged statuses are mutually exclusive with other statuses, so if we see
+ # these, then safe to assume the path is unmerged
+ _fzf_report_diff_type Unmerged
+ git diff $diff_opts -- $path
+ else
+ if test $index_status != ' '
+ _fzf_report_diff_type Staged
+
+ # renames are only detected in the index, never working tree, so only need to test for it here
+ # https://stackoverflow.com/questions/73954214
+ if test $index_status = R
+ # diff the post-rename path with the original path, otherwise the diff will show the entire file as being added
+ set -f orig_and_new_path (string split --max 1 -- ' -> ' $path)
+ git diff --staged $diff_opts -- $orig_and_new_path[1] $orig_and_new_path[2]
+ # path currently has the form of "original -> current", so we need to correct it before it's used below
+ set path $orig_and_new_path[2]
+ else
+ git diff --staged $diff_opts -- $path
+ end
+ end
+
+ if test $working_tree_status != ' '
+ _fzf_report_diff_type Unstaged
+ git diff $diff_opts -- $path
+ end
+ end
+end
diff --git a/dev/fish-plugins/fzf.fish/functions/_fzf_preview_file.fish b/dev/fish-plugins/fzf.fish/functions/_fzf_preview_file.fish
new file mode 100644
index 000000000..c92647560
--- /dev/null
+++ b/dev/fish-plugins/fzf.fish/functions/_fzf_preview_file.fish
@@ -0,0 +1,43 @@
+# helper function for _fzf_search_directory and _fzf_search_git_status
+function _fzf_preview_file --description "Print a preview for the given file based on its file type."
+ # because there's no way to guarantee that _fzf_search_directory passes the path to _fzf_preview_file
+ # as one argument, we collect all the arguments into one single variable and treat that as the path
+ set -f file_path $argv
+
+ if test -L "$file_path" # symlink
+ # notify user and recurse on the target of the symlink, which can be any of these file types
+ set -l target_path (realpath "$file_path")
+
+ set_color yellow
+ echo "'$file_path' is a symlink to '$target_path'."
+ set_color normal
+
+ _fzf_preview_file "$target_path"
+ else if test -f "$file_path" # regular file
+ if set --query fzf_preview_file_cmd
+ # need to escape quotes to make sure eval receives file_path as a single arg
+ eval "$fzf_preview_file_cmd '$file_path'"
+ else
+ bat --style=numbers --color=always "$file_path"
+ end
+ else if test -d "$file_path" # directory
+ if set --query fzf_preview_dir_cmd
+ # see above
+ eval "$fzf_preview_dir_cmd '$file_path'"
+ else
+ # -A list hidden files as well, except for . and ..
+ # -F helps classify files by appending symbols after the file name
+ command ls -A -F "$file_path"
+ end
+ else if test -c "$file_path"
+ _fzf_report_file_type "$file_path" "character device file"
+ else if test -b "$file_path"
+ _fzf_report_file_type "$file_path" "block device file"
+ else if test -S "$file_path"
+ _fzf_report_file_type "$file_path" socket
+ else if test -p "$file_path"
+ _fzf_report_file_type "$file_path" "named pipe"
+ else
+ echo "$file_path doesn't exist." >&2
+ end
+end
diff --git a/dev/fish-plugins/fzf.fish/functions/_fzf_report_diff_type.fish b/dev/fish-plugins/fzf.fish/functions/_fzf_report_diff_type.fish
new file mode 100644
index 000000000..cc26fb359
--- /dev/null
+++ b/dev/fish-plugins/fzf.fish/functions/_fzf_report_diff_type.fish
@@ -0,0 +1,18 @@
+# helper for _fzf_preview_changed_file
+# prints out something like
+# ╭────────╮
+# │ Staged │
+# ╰────────╯
+function _fzf_report_diff_type --argument-names diff_type --description "Print a distinct colored header meant to preface a git patch."
+ # number of "-" to draw is the length of the string to box + 2 for padding
+ set -f repeat_count (math 2 + (string length $diff_type))
+ set -f line (string repeat --count $repeat_count ─)
+ set -f top_border ╭$line╮
+ set -f btm_border ╰$line╯
+
+ set_color yellow
+ echo $top_border
+ echo "│ $diff_type │"
+ echo $btm_border
+ set_color normal
+end
diff --git a/dev/fish-plugins/fzf.fish/functions/_fzf_report_file_type.fish b/dev/fish-plugins/fzf.fish/functions/_fzf_report_file_type.fish
new file mode 100644
index 000000000..49e02e1ca
--- /dev/null
+++ b/dev/fish-plugins/fzf.fish/functions/_fzf_report_file_type.fish
@@ -0,0 +1,6 @@
+# helper function for _fzf_preview_file
+function _fzf_report_file_type --argument-names file_path file_type --description "Explain the file type for a file."
+ set_color red
+ echo "Cannot preview '$file_path': it is a $file_type."
+ set_color normal
+end
diff --git a/dev/fish-plugins/fzf.fish/functions/_fzf_search_directory.fish b/dev/fish-plugins/fzf.fish/functions/_fzf_search_directory.fish
new file mode 100644
index 000000000..4541eec90
--- /dev/null
+++ b/dev/fish-plugins/fzf.fish/functions/_fzf_search_directory.fish
@@ -0,0 +1,33 @@
+function _fzf_search_directory --description "Search the current directory. Replace the current token with the selected file paths."
+ # Directly use fd binary to avoid output buffering delay caused by a fd alias, if any.
+ # Debian-based distros install fd as fdfind and the fd package is something else, so
+ # check for fdfind first. Fall back to "fd" for a clear error message.
+ set -f fd_cmd (command -v fdfind || command -v fd || echo "fd")
+ set -f --append fd_cmd --color=always $fzf_fd_opts
+
+ set -f fzf_arguments --multi --ansi $fzf_directory_opts
+ set -f token (commandline --current-token)
+ # expand any variables or leading tilde (~) in the token
+ set -f expanded_token (eval echo -- $token)
+ # unescape token because it's already quoted so backslashes will mess up the path
+ set -f unescaped_exp_token (string unescape -- $expanded_token)
+
+ # If the current token is a directory and has a trailing slash,
+ # then use it as fd's base directory.
+ if string match --quiet -- "*/" $unescaped_exp_token && test -d "$unescaped_exp_token"
+ set --append fd_cmd --base-directory=$unescaped_exp_token
+ # use the directory name as fzf's prompt to indicate the search is limited to that directory
+ set --prepend fzf_arguments --prompt="Directory $unescaped_exp_token> " --preview="_fzf_preview_file $expanded_token{}"
+ set -f file_paths_selected $unescaped_exp_token($fd_cmd 2>/dev/null | _fzf_wrapper $fzf_arguments)
+ else
+ set --prepend fzf_arguments --prompt="Directory> " --query="$unescaped_exp_token" --preview='_fzf_preview_file {}'
+ set -f file_paths_selected ($fd_cmd 2>/dev/null | _fzf_wrapper $fzf_arguments)
+ end
+
+
+ if test $status -eq 0
+ commandline --current-token --replace -- (string escape -- $file_paths_selected | string join ' ')
+ end
+
+ commandline --function repaint
+end
diff --git a/dev/fish-plugins/fzf.fish/functions/_fzf_search_git_log.fish b/dev/fish-plugins/fzf.fish/functions/_fzf_search_git_log.fish
new file mode 100644
index 000000000..aa54724d4
--- /dev/null
+++ b/dev/fish-plugins/fzf.fish/functions/_fzf_search_git_log.fish
@@ -0,0 +1,36 @@
+function _fzf_search_git_log --description "Search the output of git log and preview commits. Replace the current token with the selected commit hash."
+ if not git rev-parse --git-dir >/dev/null 2>&1
+ echo '_fzf_search_git_log: Not in a git repository.' >&2
+ else
+ if not set --query fzf_git_log_format
+ # %h gives you the abbreviated commit hash, which is useful for saving screen space, but we will have to expand it later below
+ set -f fzf_git_log_format '%C(bold blue)%h%C(reset) - %C(cyan)%ad%C(reset) %C(yellow)%d%C(reset) %C(normal)%s%C(reset) %C(dim normal)[%an]%C(reset)'
+ end
+
+ set -f preview_cmd 'git show --color=always --stat --patch {1}'
+ if set --query fzf_diff_highlighter
+ set preview_cmd "$preview_cmd | $fzf_diff_highlighter"
+ end
+
+ set -f selected_log_lines (
+ git log --no-show-signature --color=always --format=format:$fzf_git_log_format --date=short | \
+ _fzf_wrapper --ansi \
+ --multi \
+ --scheme=history \
+ --prompt="Git Log> " \
+ --preview=$preview_cmd \
+ --query=(commandline --current-token) \
+ $fzf_git_log_opts
+ )
+ if test $status -eq 0
+ for line in $selected_log_lines
+ set -f abbreviated_commit_hash (string split --field 1 " " $line)
+ set -f full_commit_hash (git rev-parse $abbreviated_commit_hash)
+ set -f --append commit_hashes $full_commit_hash
+ end
+ commandline --current-token --replace (string join ' ' $commit_hashes)
+ end
+ end
+
+ commandline --function repaint
+end
diff --git a/dev/fish-plugins/fzf.fish/functions/_fzf_search_git_status.fish b/dev/fish-plugins/fzf.fish/functions/_fzf_search_git_status.fish
new file mode 100644
index 000000000..358f88c54
--- /dev/null
+++ b/dev/fish-plugins/fzf.fish/functions/_fzf_search_git_status.fish
@@ -0,0 +1,41 @@
+function _fzf_search_git_status --description "Search the output of git status. Replace the current token with the selected file paths."
+ if not git rev-parse --git-dir >/dev/null 2>&1
+ echo '_fzf_search_git_status: Not in a git repository.' >&2
+ else
+ set -f preview_cmd '_fzf_preview_changed_file {}'
+ if set --query fzf_diff_highlighter
+ set preview_cmd "$preview_cmd | $fzf_diff_highlighter"
+ end
+
+ set -f selected_paths (
+ # Pass configuration color.status=always to force status to use colors even though output is sent to a pipe
+ git -c color.status=always status --short |
+ _fzf_wrapper --ansi \
+ --multi \
+ --prompt="Git Status> " \
+ --query=(commandline --current-token) \
+ --preview=$preview_cmd \
+ --nth="2.." \
+ $fzf_git_status_opts
+ )
+ if test $status -eq 0
+ # git status --short automatically escapes the paths of most files for us so not going to bother trying to handle
+ # the few edges cases of weird file names that should be extremely rare (e.g. "this;needs;escaping")
+ set -f cleaned_paths
+
+ for path in $selected_paths
+ if test (string sub --length 1 $path) = R
+ # path has been renamed and looks like "R LICENSE -> LICENSE.md"
+ # extract the path to use from after the arrow
+ set --append cleaned_paths (string split -- "-> " $path)[-1]
+ else
+ set --append cleaned_paths (string sub --start=4 $path)
+ end
+ end
+
+ commandline --current-token --replace -- (string join ' ' $cleaned_paths)
+ end
+ end
+
+ commandline --function repaint
+end
diff --git a/dev/fish-plugins/fzf.fish/functions/_fzf_search_history.fish b/dev/fish-plugins/fzf.fish/functions/_fzf_search_history.fish
new file mode 100644
index 000000000..cafbce989
--- /dev/null
+++ b/dev/fish-plugins/fzf.fish/functions/_fzf_search_history.fish
@@ -0,0 +1,39 @@
+function _fzf_search_history --description "Search command history. Replace the command line with the selected command."
+ # history merge incorporates history changes from other fish sessions
+ # it errors out if called in private mode
+ if test -z "$fish_private_mode"
+ builtin history merge
+ end
+
+ if not set --query fzf_history_time_format
+ # Reference https://devhints.io/strftime to understand strftime format symbols
+ set -f fzf_history_time_format "%m-%d %H:%M:%S"
+ end
+
+ # Delinate time from command in history entries using the vertical box drawing char (U+2502).
+ # Then, to get raw command from history entries, delete everything up to it. The ? on regex is
+ # necessary to make regex non-greedy so it won't match into commands containing the char.
+ set -f time_prefix_regex '^.*? │ '
+ # Delinate commands throughout pipeline using null rather than newlines because commands can be multi-line
+ set -f commands_selected (
+ builtin history --null --show-time="$fzf_history_time_format │ " |
+ _fzf_wrapper --read0 \
+ --print0 \
+ --multi \
+ --scheme=history \
+ --prompt="History> " \
+ --query=(commandline) \
+ --preview="string replace --regex '$time_prefix_regex' '' -- {} | fish_indent --ansi" \
+ --preview-window="bottom:3:wrap" \
+ $fzf_history_opts |
+ string split0 |
+ # remove timestamps from commands selected
+ string replace --regex $time_prefix_regex ''
+ )
+
+ if test $status -eq 0
+ commandline --replace -- $commands_selected
+ end
+
+ commandline --function repaint
+end
diff --git a/dev/fish-plugins/fzf.fish/functions/_fzf_search_processes.fish b/dev/fish-plugins/fzf.fish/functions/_fzf_search_processes.fish
new file mode 100644
index 000000000..133a88065
--- /dev/null
+++ b/dev/fish-plugins/fzf.fish/functions/_fzf_search_processes.fish
@@ -0,0 +1,32 @@
+function _fzf_search_processes --description "Search all running processes. Replace the current token with the pid of the selected process."
+ # Directly use ps command because it is often aliased to a different command entirely
+ # or with options that dirty the search results and preview output
+ set -f ps_cmd (command -v ps || echo "ps")
+ # use all caps to be consistent with ps default format
+ # snake_case because ps doesn't seem to allow spaces in the field names
+ set -f ps_preview_fmt (string join ',' 'pid' 'ppid=PARENT' 'user' '%cpu' 'rss=RSS_IN_KB' 'start=START_TIME' 'command')
+ set -f processes_selected (
+ $ps_cmd -A -opid,command | \
+ _fzf_wrapper --multi \
+ --prompt="Processes> " \
+ --query (commandline --current-token) \
+ --ansi \
+ # first line outputted by ps is a header, so we need to mark it as so
+ --header-lines=1 \
+ # ps uses exit code 1 if the process was not found, in which case show an message explaining so
+ --preview="$ps_cmd -o '$ps_preview_fmt' -p {1} || echo 'Cannot preview {1} because it exited.'" \
+ --preview-window="bottom:4:wrap" \
+ $fzf_processes_opts
+ )
+
+ if test $status -eq 0
+ for process in $processes_selected
+ set -f --append pids_selected (string split --no-empty --field=1 -- " " $process)
+ end
+
+ # string join to replace the newlines outputted by string split with spaces
+ commandline --current-token --replace -- (string join ' ' $pids_selected)
+ end
+
+ commandline --function repaint
+end
diff --git a/dev/fish-plugins/fzf.fish/functions/_fzf_search_variables.fish b/dev/fish-plugins/fzf.fish/functions/_fzf_search_variables.fish
new file mode 100644
index 000000000..52a7c701d
--- /dev/null
+++ b/dev/fish-plugins/fzf.fish/functions/_fzf_search_variables.fish
@@ -0,0 +1,47 @@
+# This function expects the following two arguments:
+# argument 1 = output of (set --show | psub), i.e. a file with the scope info and values of all variables
+# argument 2 = output of (set --names | psub), i.e. a file with all variable names
+function _fzf_search_variables --argument-names set_show_output set_names_output --description "Search and preview shell variables. Replace the current token with the selected variable."
+ if test -z "$set_names_output"
+ printf '%s\n' '_fzf_search_variables requires 2 arguments.' >&2
+
+ commandline --function repaint
+ return 22 # 22 means invalid argument in POSIX
+ end
+
+ # Exclude the history variable from being piped into fzf because
+ # 1. it's not included in $set_names_output
+ # 2. it tends to be a very large value => increases computation time
+ # 3._fzf_search_history is a much better way to examine history anyway
+ set -f all_variable_names (string match --invert history <$set_names_output)
+
+ set -f current_token (commandline --current-token)
+ # Use the current token to pre-populate fzf's query. If the current token begins
+ # with a $, remove it from the query so that it will better match the variable names
+ set -f cleaned_curr_token (string replace -- '$' '' $current_token)
+
+ set -f variable_names_selected (
+ printf '%s\n' $all_variable_names |
+ _fzf_wrapper --preview "_fzf_extract_var_info {} $set_show_output" \
+ --prompt="Variables> " \
+ --preview-window="wrap" \
+ --multi \
+ --query=$cleaned_curr_token \
+ $fzf_variables_opts
+ )
+
+ if test $status -eq 0
+ # If the current token begins with a $, do not overwrite the $ when
+ # replacing the current token with the selected variable.
+ # Uses brace expansion to prepend $ to each variable name.
+ commandline --current-token --replace (
+ if string match --quiet -- '$*' $current_token
+ string join " " \${$variable_names_selected}
+ else
+ string join " " $variable_names_selected
+ end
+ )
+ end
+
+ commandline --function repaint
+end
diff --git a/dev/fish-plugins/fzf.fish/functions/_fzf_wrapper.fish b/dev/fish-plugins/fzf.fish/functions/_fzf_wrapper.fish
new file mode 100644
index 000000000..486e36c32
--- /dev/null
+++ b/dev/fish-plugins/fzf.fish/functions/_fzf_wrapper.fish
@@ -0,0 +1,21 @@
+function _fzf_wrapper --description "Prepares some environment variables before executing fzf."
+ # Make sure fzf uses fish to execute preview commands, some of which
+ # are autoloaded fish functions so don't exist in other shells.
+ # Use --function so that it doesn't clobber SHELL outside this function.
+ set -f --export SHELL (command --search fish)
+
+ # If neither FZF_DEFAULT_OPTS nor FZF_DEFAULT_OPTS_FILE are set, then set some sane defaults.
+ # See https://github.com/junegunn/fzf#environment-variables
+ set --query FZF_DEFAULT_OPTS FZF_DEFAULT_OPTS_FILE
+ if test $status -eq 2
+ # cycle allows jumping between the first and last results, making scrolling faster
+ # layout=reverse lists results top to bottom, mimicking the familiar layouts of git log, history, and env
+ # border shows where the fzf window begins and ends
+ # height=90% leaves space to see the current command and some scrollback, maintaining context of work
+ # preview-window=wrap wraps long lines in the preview window, making reading easier
+ # marker=* makes the multi-select marker more distinguishable from the pointer (since both default to >)
+ set --export FZF_DEFAULT_OPTS '--cycle --layout=reverse --border --height=90% --preview-window=wrap --marker="*"'
+ end
+
+ fzf $argv
+end
diff --git a/dev/fish-plugins/fzf.fish/functions/fzf_configure_bindings.fish b/dev/fish-plugins/fzf.fish/functions/fzf_configure_bindings.fish
new file mode 100644
index 000000000..4b4e7a2ba
--- /dev/null
+++ b/dev/fish-plugins/fzf.fish/functions/fzf_configure_bindings.fish
@@ -0,0 +1,46 @@
+# Always installs bindings for insert and default mode for simplicity and b/c it has almost no side-effect
+# https://gitter.im/fish-shell/fish-shell?at=60a55915ee77a74d685fa6b1
+function fzf_configure_bindings --description "Installs the default key bindings for fzf.fish with user overrides passed as options."
+ # no need to install bindings if not in interactive mode or running tests
+ status is-interactive || test "$CI" = true; or return
+
+ set -f options_spec h/help 'directory=?' 'git_log=?' 'git_status=?' 'history=?' 'processes=?' 'variables=?'
+ argparse --max-args=0 --ignore-unknown $options_spec -- $argv 2>/dev/null
+ if test $status -ne 0
+ echo "Invalid option or a positional argument was provided." >&2
+ _fzf_configure_bindings_help
+ return 22
+ else if set --query _flag_help
+ _fzf_configure_bindings_help
+ return
+ else
+ # Initialize with default key sequences and then override or disable them based on flags
+ # index 1 = directory, 2 = git_log, 3 = git_status, 4 = history, 5 = processes, 6 = variables
+ set -f key_sequences \e\cf \e\cl \e\cs \cr \e\cp \cv # \c = control, \e = escape
+ set --query _flag_directory && set key_sequences[1] "$_flag_directory"
+ set --query _flag_git_log && set key_sequences[2] "$_flag_git_log"
+ set --query _flag_git_status && set key_sequences[3] "$_flag_git_status"
+ set --query _flag_history && set key_sequences[4] "$_flag_history"
+ set --query _flag_processes && set key_sequences[5] "$_flag_processes"
+ set --query _flag_variables && set key_sequences[6] "$_flag_variables"
+
+ # If fzf bindings already exists, uninstall it first for a clean slate
+ if functions --query _fzf_uninstall_bindings
+ _fzf_uninstall_bindings
+ end
+
+ for mode in default insert
+ test -n $key_sequences[1] && bind --mode $mode $key_sequences[1] _fzf_search_directory
+ test -n $key_sequences[2] && bind --mode $mode $key_sequences[2] _fzf_search_git_log
+ test -n $key_sequences[3] && bind --mode $mode $key_sequences[3] _fzf_search_git_status
+ test -n $key_sequences[4] && bind --mode $mode $key_sequences[4] _fzf_search_history
+ test -n $key_sequences[5] && bind --mode $mode $key_sequences[5] _fzf_search_processes
+ test -n $key_sequences[6] && bind --mode $mode $key_sequences[6] "$_fzf_search_vars_command"
+ end
+
+ function _fzf_uninstall_bindings --inherit-variable key_sequences
+ bind --erase -- $key_sequences
+ bind --erase --mode insert -- $key_sequences
+ end
+ end
+end
diff --git a/dev/fish-plugins/tide/LICENSE.md b/dev/fish-plugins/tide/LICENSE.md
new file mode 100644
index 000000000..f9aa9bb8b
--- /dev/null
+++ b/dev/fish-plugins/tide/LICENSE.md
@@ -0,0 +1,21 @@
+# MIT License
+
+Copyright © `2020` `Ilan Cosman`
+
+Permission is hereby granted, free of charge, to any person obtaining a copy
+of this software and associated documentation files (the "Software"), to deal
+in the Software without restriction, including without limitation the rights
+to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
+copies of the Software, and to permit persons to whom the Software is
+furnished to do so, subject to the following conditions:
+
+The above copyright notice and this permission notice shall be included in all
+copies or substantial portions of the Software.
+
+THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
+AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
+OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
+SOFTWARE.
diff --git a/dev/fish-plugins/tide/completions/tide.fish b/dev/fish-plugins/tide/completions/tide.fish
new file mode 100644
index 000000000..1ba196089
--- /dev/null
+++ b/dev/fish-plugins/tide/completions/tide.fish
@@ -0,0 +1,13 @@
+complete tide --no-files
+
+set -l subcommands bug-report configure reload
+
+complete tide -x -n __fish_use_subcommand -a bug-report -d "Print info for use in bug reports"
+complete tide -x -n __fish_use_subcommand -a configure -d "Run the configuration wizard"
+complete tide -x -n __fish_use_subcommand -a reload -d "Reload tide configuration"
+
+complete tide -x -n "not __fish_seen_subcommand_from $subcommands" -s h -l help -d "Print help message"
+complete tide -x -n "not __fish_seen_subcommand_from $subcommands" -s v -l version -d "Print tide version"
+
+complete tide -x -n '__fish_seen_subcommand_from bug-report' -l clean -d "Run clean Fish instance and install Tide"
+complete tide -x -n '__fish_seen_subcommand_from bug-report' -l verbose -d "Print full Tide configuration"
diff --git a/dev/fish-plugins/tide/conf.d/_tide_init.fish b/dev/fish-plugins/tide/conf.d/_tide_init.fish
new file mode 100644
index 000000000..b448002f7
--- /dev/null
+++ b/dev/fish-plugins/tide/conf.d/_tide_init.fish
@@ -0,0 +1,44 @@
+function _tide_init_install --on-event _tide_init_install
+ set -U VIRTUAL_ENV_DISABLE_PROMPT true
+
+ source (functions --details _tide_sub_configure)
+ _load_config lean
+ _tide_finish
+
+ if status is-interactive
+ tide bug-report --check || sleep 4
+
+ if contains ilancosman/tide (string lower $_fisher_plugins)
+ set_color bryellow
+ echo "ilancosman/tide is a development branch. Please install from a release tag:"
+ _tide_fish_colorize "fisher install ilancosman/tide@v6"
+ sleep 3
+ end
+
+ switch (read --prompt-str="Configure tide prompt? [Y/n] " | string lower)
+ case y ye yes ''
+ tide configure
+ case '*'
+ echo -s \n 'Run ' (_tide_fish_colorize "tide configure") ' to customize your prompt.'
+ end
+ end
+end
+
+function _tide_init_update --on-event _tide_init_update
+ # Warn users who install from main branch
+ if contains ilancosman/tide (string lower $_fisher_plugins)
+ set_color bryellow
+ echo "ilancosman/tide is a development branch. Please install from a release tag:"
+ _tide_fish_colorize "fisher install ilancosman/tide@v6"
+ sleep 3
+ end
+
+ # Set (disable) the new jobs variable
+ set -q tide_jobs_number_threshold || set -U tide_jobs_number_threshold 1000
+end
+
+function _tide_init_uninstall --on-event _tide_init_uninstall
+ set -e VIRTUAL_ENV_DISABLE_PROMPT
+ set -e (set -U --names | string match --entire -r '^_?tide')
+ functions --erase (functions --all | string match --entire -r '^_?tide')
+end
diff --git a/dev/fish-plugins/tide/functions/_tide_1_line_prompt.fish b/dev/fish-plugins/tide/functions/_tide_1_line_prompt.fish
new file mode 100644
index 000000000..57722237e
--- /dev/null
+++ b/dev/fish-plugins/tide/functions/_tide_1_line_prompt.fish
@@ -0,0 +1,19 @@
+function _tide_1_line_prompt
+ set -g add_prefix
+ _tide_side=left for item in $_tide_left_items
+ _tide_item_$item
+ end
+ set_color $prev_bg_color -b normal
+ echo $tide_left_prompt_suffix
+
+ set -g add_prefix
+ _tide_side=right for item in $_tide_right_items
+ _tide_item_$item
+ end
+ set_color $prev_bg_color -b normal
+ echo $tide_right_prompt_suffix
+end
+
+function _tide_item_pwd
+ _tide_print_item pwd @PWD@
+end
diff --git a/dev/fish-plugins/tide/functions/_tide_2_line_prompt.fish b/dev/fish-plugins/tide/functions/_tide_2_line_prompt.fish
new file mode 100644
index 000000000..e9017af4b
--- /dev/null
+++ b/dev/fish-plugins/tide/functions/_tide_2_line_prompt.fish
@@ -0,0 +1,31 @@
+function _tide_2_line_prompt
+ set -g add_prefix
+ _tide_side=left for item in $_tide_left_items
+ _tide_item_$item
+ end
+ if not set -e add_prefix
+ set_color $prev_bg_color -b normal
+ echo $tide_left_prompt_suffix
+ end
+
+ echo
+
+ set -g add_prefix
+ _tide_side=right for item in $_tide_right_items
+ _tide_item_$item
+ end
+ if not set -e add_prefix
+ set_color $prev_bg_color -b normal
+ echo $tide_right_prompt_suffix
+ end
+end
+
+function _tide_item_pwd
+ _tide_print_item pwd @PWD@
+end
+
+function _tide_item_newline
+ set_color $prev_bg_color -b normal
+ v=tide_"$_tide_side"_prompt_suffix echo $$v
+ set -g add_prefix
+end
diff --git a/dev/fish-plugins/tide/functions/_tide_cache_variables.fish b/dev/fish-plugins/tide/functions/_tide_cache_variables.fish
new file mode 100644
index 000000000..31e385070
--- /dev/null
+++ b/dev/fish-plugins/tide/functions/_tide_cache_variables.fish
@@ -0,0 +1,17 @@
+function _tide_cache_variables
+ # Same-color-separator color
+ set_color $tide_prompt_color_separator_same_color | read -gx _tide_color_separator_same_color
+
+ # git
+ contains git $_tide_left_items $_tide_right_items && set_color $tide_git_color_branch | read -gx _tide_location_color
+
+ # private_mode
+ if contains private_mode $_tide_left_items $_tide_right_items && test -n "$fish_private_mode"
+ set -gx _tide_private_mode
+ else
+ set -e _tide_private_mode
+ end
+
+ # item padding
+ test "$tide_prompt_pad_items" = true && set -gx _tide_pad ' ' || set -e _tide_pad
+end
diff --git a/dev/fish-plugins/tide/functions/_tide_detect_os.fish b/dev/fish-plugins/tide/functions/_tide_detect_os.fish
new file mode 100644
index 000000000..bb58d07be
--- /dev/null
+++ b/dev/fish-plugins/tide/functions/_tide_detect_os.fish
@@ -0,0 +1,76 @@
+# Outputs icon, color, bg_color
+function _tide_detect_os
+ set -lx defaultColor 080808 CED7CF
+ switch (uname | string lower)
+ case darwin
+ printf %s\n D6D6D6 333333 # from apple.com header
+ case freebsd openbsd dragonfly
+ printf %s\n FFFFFF AB2B28 # https://freebsdfoundation.org/about-us/about-the-foundation/project/
+ case 'cygwin*' 'mingw*_nt*' 'msys_nt*'
+ printf %s\n FFFFFF 00CCFF # https://answers.microsoft.com/en-us/windows/forum/all/what-is-the-official-windows-8-blue-rgb-or-hex/fd57144b-f69b-42d8-8c21-6ca911646e44
+ case linux
+ if test (uname -o) = Android
+ # https://developer.android.com/distribute/marketing-tools/brand-guidelines
+ printf %s\n 3DDC84 3C3F41 # fg is from above link, bg is from Android Studio default dark theme
+ else
+ _tide_detect_os_linux_cases /etc/os-release ID ||
+ _tide_detect_os_linux_cases /etc/os-release ID_LIKE ||
+ _tide_detect_os_linux_cases /etc/lsb-release DISTRIB_ID ||
+ printf %s\n $defaultColor
+ end
+ case '*'
+ echo -ns '?'
+ end
+end
+
+function _tide_detect_os_linux_cases -a file key
+ test -e $file || return
+ set -l split_file (string split '=' <$file)
+ set -l key_index (contains --index $key $split_file) || return
+ set -l value (string trim --chars='"' $split_file[(math $key_index + 1)])
+
+ # Anything which would have pure white background has been changed to D4D4D4
+ # It was just too bright otherwise
+ switch (string lower $value)
+ case alpine
+ printf %s\n FFFFFF 0D597F # from alpine logo
+ case arch
+ printf %s\n 1793D1 4D4D4D # from arch wiki header
+ case centos
+ printf %s\n 000000 D4D4D4 # https://wiki.centos.org/ArtWork/Brand/Logo, monochromatic
+ case debian
+ printf %s\n C70036 D4D4D4 # from debian logo https://www.debian.org/logos/openlogo-nd-100.png
+ case devuan
+ printf %s\n $defaultColor # logo is monochromatic
+ case elementary
+ printf %s\n 000000 D4D4D4 # https://elementary.io/brand, encouraged to be monochromatic
+ case fedora
+ printf %s\n FFFFFF 294172 # from logo https://fedoraproject.org/w/uploads/2/2d/Logo_fedoralogo.png
+ case gentoo
+ printf %s\n FFFFFF 54487A # https://wiki.gentoo.org/wiki/Project:Artwork/Colors
+ case mageia
+ printf %s\n FFFFFF 262F45 # https://wiki.mageia.org/en/Artwork_guidelines
+ case manjaro
+ printf %s\n FFFFFF 35BF5C # from https://gitlab.manjaro.org/artwork/branding/logo/-/blob/master/logo.svg
+ case mint linuxmint
+ printf %s\n FFFFFF 69B53F # extracted from https://linuxmint.com/web/img/favicon.ico
+ case nixos
+ printf %s\n FFFFFF 5277C3 # https://github.com/NixOS/nixos-artwork/tree/master/logo
+ case opensuse-leap opensuse-tumbleweed opensuse-microos
+ printf %s\n 73BA25 173f4f # https://en.opensuse.org/openSUSE:Artwork_brand
+ case raspbian
+ printf %s\n FFFFFF A22846 # https://static.raspberrypi.org/files/Raspberry_Pi_Visual_Guidelines_2020.pdf
+ case rhel
+ printf %s\n EE0000 000000 # https://www.redhat.com/en/about/brand/standards/color
+ case sabayon
+ printf %s\n $defaultColor # Can't find colors, and they are rebranding anyway
+ case slackware
+ printf %s\n $defaultColor # Doesn't really have a logo, and the colors are too close to PWD blue anyway
+ case ubuntu
+ printf %s\n E95420 D4D4D4 # https://design.ubuntu.com/brand/
+ case void
+ printf %s\n FFFFFF 478061 # from https://alpha.de.repo.voidlinux.org/logos/void.svg
+ case '*'
+ return 1
+ end
+end
diff --git a/dev/fish-plugins/tide/functions/_tide_find_and_remove.fish b/dev/fish-plugins/tide/functions/_tide_find_and_remove.fish
new file mode 100644
index 000000000..29f218087
--- /dev/null
+++ b/dev/fish-plugins/tide/functions/_tide_find_and_remove.fish
@@ -0,0 +1,3 @@
+function _tide_find_and_remove -a name list --no-scope-shadowing
+ contains --index $name $$list | read -l index && set -e "$list"[$index]
+end
diff --git a/dev/fish-plugins/tide/functions/_tide_fish_colorize.fish b/dev/fish-plugins/tide/functions/_tide_fish_colorize.fish
new file mode 100644
index 000000000..f79b188e4
--- /dev/null
+++ b/dev/fish-plugins/tide/functions/_tide_fish_colorize.fish
@@ -0,0 +1,7 @@
+function _tide_fish_colorize
+ if command -q fish_indent
+ echo -ns "$argv" | fish_indent --ansi
+ else
+ echo -ns "$argv"
+ end
+end
diff --git a/dev/fish-plugins/tide/functions/_tide_item_aws.fish b/dev/fish-plugins/tide/functions/_tide_item_aws.fish
new file mode 100644
index 000000000..7cb6338ee
--- /dev/null
+++ b/dev/fish-plugins/tide/functions/_tide_item_aws.fish
@@ -0,0 +1,11 @@
+function _tide_item_aws
+ # AWS_PROFILE overrides AWS_DEFAULT_PROFILE, AWS_REGION overrides AWS_DEFAULT_REGION
+ set -q AWS_PROFILE && set -l AWS_DEFAULT_PROFILE $AWS_PROFILE
+ set -q AWS_REGION && set -l AWS_DEFAULT_REGION $AWS_REGION
+
+ if test -n "$AWS_DEFAULT_PROFILE" && test -n "$AWS_DEFAULT_REGION"
+ _tide_print_item aws $tide_aws_icon' ' "$AWS_DEFAULT_PROFILE/$AWS_DEFAULT_REGION"
+ else if test -n "$AWS_DEFAULT_PROFILE$AWS_DEFAULT_REGION"
+ _tide_print_item aws $tide_aws_icon' ' "$AWS_DEFAULT_PROFILE$AWS_DEFAULT_REGION"
+ end
+end
diff --git a/dev/fish-plugins/tide/functions/_tide_item_bun.fish b/dev/fish-plugins/tide/functions/_tide_item_bun.fish
new file mode 100644
index 000000000..b39fd72d7
--- /dev/null
+++ b/dev/fish-plugins/tide/functions/_tide_item_bun.fish
@@ -0,0 +1,6 @@
+function _tide_item_bun
+ if path is $_tide_parent_dirs/bun.lockb
+ bun --version | string match -qr "(?.*)"
+ _tide_print_item bun $tide_bun_icon' ' $v
+ end
+end
diff --git a/dev/fish-plugins/tide/functions/_tide_item_character.fish b/dev/fish-plugins/tide/functions/_tide_item_character.fish
new file mode 100644
index 000000000..10bb3244e
--- /dev/null
+++ b/dev/fish-plugins/tide/functions/_tide_item_character.fish
@@ -0,0 +1,17 @@
+function _tide_item_character
+ test $_tide_status = 0 && set_color $tide_character_color || set_color $tide_character_color_failure
+
+ set -q add_prefix || echo -ns ' '
+
+ test "$fish_key_bindings" = fish_default_key_bindings && echo -ns $tide_character_icon ||
+ switch $fish_bind_mode
+ case insert
+ echo -ns $tide_character_icon
+ case default
+ echo -ns $tide_character_vi_icon_default
+ case replace replace_one
+ echo -ns $tide_character_vi_icon_replace
+ case visual
+ echo -ns $tide_character_vi_icon_visual
+ end
+end
diff --git a/dev/fish-plugins/tide/functions/_tide_item_cmd_duration.fish b/dev/fish-plugins/tide/functions/_tide_item_cmd_duration.fish
new file mode 100644
index 000000000..bc48bac4b
--- /dev/null
+++ b/dev/fish-plugins/tide/functions/_tide_item_cmd_duration.fish
@@ -0,0 +1,12 @@
+function _tide_item_cmd_duration
+ test $CMD_DURATION -gt $tide_cmd_duration_threshold && t=(
+ math -s0 "$CMD_DURATION/3600000" # Hours
+ math -s0 "$CMD_DURATION/60000"%60 # Minutes
+ math -s$tide_cmd_duration_decimals "$CMD_DURATION/1000"%60) if test $t[1] != 0
+ _tide_print_item cmd_duration $tide_cmd_duration_icon' ' "$t[1]h $t[2]m $t[3]s"
+ else if test $t[2] != 0
+ _tide_print_item cmd_duration $tide_cmd_duration_icon' ' "$t[2]m $t[3]s"
+ else
+ _tide_print_item cmd_duration $tide_cmd_duration_icon' ' "$t[3]s"
+ end
+end
diff --git a/dev/fish-plugins/tide/functions/_tide_item_context.fish b/dev/fish-plugins/tide/functions/_tide_item_context.fish
new file mode 100644
index 000000000..cbdf4201b
--- /dev/null
+++ b/dev/fish-plugins/tide/functions/_tide_item_context.fish
@@ -0,0 +1,14 @@
+function _tide_item_context
+ if set -q SSH_TTY
+ set -fx tide_context_color $tide_context_color_ssh
+ else if test "$EUID" = 0
+ set -fx tide_context_color $tide_context_color_root
+ else if test "$tide_context_always_display" = true
+ set -fx tide_context_color $tide_context_color_default
+ else
+ return
+ end
+
+ string match -qr "^(?(\.?[^\.]*){0,$tide_context_hostname_parts})" @$hostname
+ _tide_print_item context $USER$h
+end
diff --git a/dev/fish-plugins/tide/functions/_tide_item_crystal.fish b/dev/fish-plugins/tide/functions/_tide_item_crystal.fish
new file mode 100644
index 000000000..620dde46f
--- /dev/null
+++ b/dev/fish-plugins/tide/functions/_tide_item_crystal.fish
@@ -0,0 +1,6 @@
+function _tide_item_crystal
+ if path is $_tide_parent_dirs/shard.yml
+ crystal --version | string match -qr "(?[\d.]+)"
+ _tide_print_item crystal $tide_crystal_icon' ' $v
+ end
+end
diff --git a/dev/fish-plugins/tide/functions/_tide_item_direnv.fish b/dev/fish-plugins/tide/functions/_tide_item_direnv.fish
new file mode 100644
index 000000000..f3c892b58
--- /dev/null
+++ b/dev/fish-plugins/tide/functions/_tide_item_direnv.fish
@@ -0,0 +1,7 @@
+function _tide_item_direnv
+ set -q DIRENV_DIR || return
+ direnv status | string match -q 'Found RC allowed false' &&
+ set -lx tide_direnv_color $tide_direnv_color_denied &&
+ set -lx tide_direnv_bg_color $tide_direnv_bg_color_denied
+ _tide_print_item direnv $tide_direnv_icon
+end
diff --git a/dev/fish-plugins/tide/functions/_tide_item_distrobox.fish b/dev/fish-plugins/tide/functions/_tide_item_distrobox.fish
new file mode 100644
index 000000000..5e06a8b95
--- /dev/null
+++ b/dev/fish-plugins/tide/functions/_tide_item_distrobox.fish
@@ -0,0 +1,4 @@
+function _tide_item_distrobox
+ test -e /etc/profile.d/distrobox_profile.sh && test -e /run/.containerenv &&
+ _tide_print_item distrobox $tide_distrobox_icon' ' (string match -rg 'name="(.*)"' .*)' <$CLOUDSDK_CONFIG/configurations/config_$config &&
+ _tide_print_item gcloud $tide_gcloud_icon' ' $project
+end
diff --git a/dev/fish-plugins/tide/functions/_tide_item_git.fish b/dev/fish-plugins/tide/functions/_tide_item_git.fish
new file mode 100644
index 000000000..56e1e3157
--- /dev/null
+++ b/dev/fish-plugins/tide/functions/_tide_item_git.fish
@@ -0,0 +1,72 @@
+function _tide_item_git
+ if git branch --show-current 2>/dev/null | string shorten -"$tide_git_truncation_strategy"m$tide_git_truncation_length | read -l location
+ git rev-parse --git-dir --is-inside-git-dir | read -fL gdir in_gdir
+ set location $_tide_location_color$location
+ else if test $pipestatus[1] != 0
+ return
+ else if git tag --points-at HEAD | string shorten -"$tide_git_truncation_strategy"m$tide_git_truncation_length | read location
+ git rev-parse --git-dir --is-inside-git-dir | read -fL gdir in_gdir
+ set location '#'$_tide_location_color$location
+ else
+ git rev-parse --git-dir --is-inside-git-dir --short HEAD | read -fL gdir in_gdir location
+ set location @$_tide_location_color$location
+ end
+
+ # Operation
+ if test -d $gdir/rebase-merge
+ # Turn ANY into ALL, via double negation
+ if not path is -v $gdir/rebase-merge/{msgnum,end}
+ read -f step <$gdir/rebase-merge/msgnum
+ read -f total_steps <$gdir/rebase-merge/end
+ end
+ test -f $gdir/rebase-merge/interactive && set -f operation rebase-i || set -f operation rebase-m
+ else if test -d $gdir/rebase-apply
+ if not path is -v $gdir/rebase-apply/{next,last}
+ read -f step <$gdir/rebase-apply/next
+ read -f total_steps <$gdir/rebase-apply/last
+ end
+ if test -f $gdir/rebase-apply/rebasing
+ set -f operation rebase
+ else if test -f $gdir/rebase-apply/applying
+ set -f operation am
+ else
+ set -f operation am/rebase
+ end
+ else if test -f $gdir/MERGE_HEAD
+ set -f operation merge
+ else if test -f $gdir/CHERRY_PICK_HEAD
+ set -f operation cherry-pick
+ else if test -f $gdir/REVERT_HEAD
+ set -f operation revert
+ else if test -f $gdir/BISECT_LOG
+ set -f operation bisect
+ end
+
+ # Git status/stash + Upstream behind/ahead
+ test $in_gdir = true && set -l _set_dir_opt -C $gdir/..
+ # Suppress errors in case we are in a bare repo or there is no upstream
+ set -l stat (git $_set_dir_opt --no-optional-locks status --porcelain 2>/dev/null)
+ string match -qr '(0|(?.*))\n(0|(?.*))\n(0|(?.*))
+(0|(?.*))\n(0|(?.*))(\n(0|(?.*))\t(0|(?.*)))?' \
+ "$(git $_set_dir_opt stash list 2>/dev/null | count
+ string match -r ^UU $stat | count
+ string match -r ^[ADMR] $stat | count
+ string match -r ^.[ADMR] $stat | count
+ string match -r '^\?\?' $stat | count
+ git rev-list --count --left-right @{upstream}...HEAD 2>/dev/null)"
+
+ if test -n "$operation$conflicted"
+ set -g tide_git_bg_color $tide_git_bg_color_urgent
+ else if test -n "$staged$dirty$untracked"
+ set -g tide_git_bg_color $tide_git_bg_color_unstable
+ end
+
+ _tide_print_item git $_tide_location_color$tide_git_icon' ' (set_color white; echo -ns $location
+ set_color $tide_git_color_operation; echo -ns ' '$operation ' '$step/$total_steps
+ set_color $tide_git_color_upstream; echo -ns ' ⇣'$behind ' ⇡'$ahead
+ set_color $tide_git_color_stash; echo -ns ' *'$stash
+ set_color $tide_git_color_conflicted; echo -ns ' ~'$conflicted
+ set_color $tide_git_color_staged; echo -ns ' +'$staged
+ set_color $tide_git_color_dirty; echo -ns ' !'$dirty
+ set_color $tide_git_color_untracked; echo -ns ' ?'$untracked)
+end
diff --git a/dev/fish-plugins/tide/functions/_tide_item_go.fish b/dev/fish-plugins/tide/functions/_tide_item_go.fish
new file mode 100644
index 000000000..b9aacae2d
--- /dev/null
+++ b/dev/fish-plugins/tide/functions/_tide_item_go.fish
@@ -0,0 +1,6 @@
+function _tide_item_go
+ if path is $_tide_parent_dirs/go.mod
+ go version | string match -qr "(?[\d.]+)"
+ _tide_print_item go $tide_go_icon' ' $v
+ end
+end
diff --git a/dev/fish-plugins/tide/functions/_tide_item_java.fish b/dev/fish-plugins/tide/functions/_tide_item_java.fish
new file mode 100644
index 000000000..804ec057a
--- /dev/null
+++ b/dev/fish-plugins/tide/functions/_tide_item_java.fish
@@ -0,0 +1,6 @@
+function _tide_item_java
+ if path is $_tide_parent_dirs/pom.xml
+ java -version &| string match -qr "(?[\d.]+)"
+ _tide_print_item java $tide_java_icon' ' $v
+ end
+end
diff --git a/dev/fish-plugins/tide/functions/_tide_item_jobs.fish b/dev/fish-plugins/tide/functions/_tide_item_jobs.fish
new file mode 100644
index 000000000..2a02118bf
--- /dev/null
+++ b/dev/fish-plugins/tide/functions/_tide_item_jobs.fish
@@ -0,0 +1,7 @@
+function _tide_item_jobs
+ set -q _tide_jobs && if test $_tide_jobs -ge $tide_jobs_number_threshold
+ _tide_print_item jobs $tide_jobs_icon' ' $_tide_jobs
+ else
+ _tide_print_item jobs $tide_jobs_icon
+ end
+end
diff --git a/dev/fish-plugins/tide/functions/_tide_item_kubectl.fish b/dev/fish-plugins/tide/functions/_tide_item_kubectl.fish
new file mode 100644
index 000000000..ab044b33c
--- /dev/null
+++ b/dev/fish-plugins/tide/functions/_tide_item_kubectl.fish
@@ -0,0 +1,4 @@
+function _tide_item_kubectl
+ kubectl config view --minify --output 'jsonpath={.current-context}/{..namespace}' 2>/dev/null | read -l context &&
+ _tide_print_item kubectl $tide_kubectl_icon' ' (string replace -r '/(|default)$' '' $context)
+end
diff --git a/dev/fish-plugins/tide/functions/_tide_item_nix_shell.fish b/dev/fish-plugins/tide/functions/_tide_item_nix_shell.fish
new file mode 100644
index 000000000..647f6066c
--- /dev/null
+++ b/dev/fish-plugins/tide/functions/_tide_item_nix_shell.fish
@@ -0,0 +1,3 @@
+function _tide_item_nix_shell
+ set -q IN_NIX_SHELL && _tide_print_item nix_shell $tide_nix_shell_icon' ' $IN_NIX_SHELL
+end
diff --git a/dev/fish-plugins/tide/functions/_tide_item_node.fish b/dev/fish-plugins/tide/functions/_tide_item_node.fish
new file mode 100644
index 000000000..fc57832b4
--- /dev/null
+++ b/dev/fish-plugins/tide/functions/_tide_item_node.fish
@@ -0,0 +1,6 @@
+function _tide_item_node
+ if path is $_tide_parent_dirs/package.json
+ node --version | string match -qr "v(?.*)"
+ _tide_print_item node $tide_node_icon' ' $v
+ end
+end
diff --git a/dev/fish-plugins/tide/functions/_tide_item_os.fish b/dev/fish-plugins/tide/functions/_tide_item_os.fish
new file mode 100644
index 000000000..8a6208c69
--- /dev/null
+++ b/dev/fish-plugins/tide/functions/_tide_item_os.fish
@@ -0,0 +1,3 @@
+function _tide_item_os
+ _tide_print_item os $tide_os_icon
+end
diff --git a/dev/fish-plugins/tide/functions/_tide_item_php.fish b/dev/fish-plugins/tide/functions/_tide_item_php.fish
new file mode 100644
index 000000000..c8d28d97c
--- /dev/null
+++ b/dev/fish-plugins/tide/functions/_tide_item_php.fish
@@ -0,0 +1,6 @@
+function _tide_item_php
+ if path is $_tide_parent_dirs/composer.json
+ php --version | string match -qr "(?[\d.]+)"
+ _tide_print_item php $tide_php_icon' ' $v
+ end
+end
diff --git a/dev/fish-plugins/tide/functions/_tide_item_private_mode.fish b/dev/fish-plugins/tide/functions/_tide_item_private_mode.fish
new file mode 100644
index 000000000..4eb4684de
--- /dev/null
+++ b/dev/fish-plugins/tide/functions/_tide_item_private_mode.fish
@@ -0,0 +1,3 @@
+function _tide_item_private_mode
+ set -q _tide_private_mode && _tide_print_item private_mode $tide_private_mode_icon
+end
diff --git a/dev/fish-plugins/tide/functions/_tide_item_pulumi.fish b/dev/fish-plugins/tide/functions/_tide_item_pulumi.fish
new file mode 100644
index 000000000..6302a7e4d
--- /dev/null
+++ b/dev/fish-plugins/tide/functions/_tide_item_pulumi.fish
@@ -0,0 +1,19 @@
+function _tide_item_pulumi
+ if path filter $_tide_parent_dirs/Pulumi.yaml | read -l yaml_path
+ if command -q sha1sum
+ echo -n "$yaml_path" | sha1sum | string match -qr "(?.{40})"
+ else if command -q shasum
+ echo -n "$yaml_path" | shasum | string match -qr "(?.{40})"
+ else
+ return
+ end
+
+ string match -qr 'name: *(?.*)' <$yaml_path
+ set -l workspace_file "$HOME/.pulumi/workspaces/$project_name-$path_hash-workspace.json"
+
+ if test -e $workspace_file
+ string match -qr '"stack": *"(?.*)"' <$workspace_file
+ _tide_print_item pulumi $tide_pulumi_icon' ' $stack
+ end
+ end
+end
diff --git a/dev/fish-plugins/tide/functions/_tide_item_python.fish b/dev/fish-plugins/tide/functions/_tide_item_python.fish
new file mode 100644
index 000000000..57f7075aa
--- /dev/null
+++ b/dev/fish-plugins/tide/functions/_tide_item_python.fish
@@ -0,0 +1,27 @@
+function _tide_item_python
+ if test -n "$VIRTUAL_ENV"
+ if command -q python3
+ python3 --version | string match -qr "(?[\d.]+)"
+ else
+ python --version | string match -qr "(?[\d.]+)"
+ end
+ string match -qr "^.*/(?.*)/(?.*)" $VIRTUAL_ENV
+ # pipenv $VIRTUAL_ENV looks like /home/ilan/.local/share/virtualenvs/pipenv_project-EwRYuc3l
+ # Detect whether we are using pipenv by looking for 'virtualenvs'. If so, remove the hash at the end.
+ if test "$dir" = virtualenvs
+ string match -qr "(?.*)-.*" $base
+ _tide_print_item python $tide_python_icon' ' "$v ($base)"
+ else if contains -- "$base" virtualenv venv .venv env # avoid generic names
+ _tide_print_item python $tide_python_icon' ' "$v ($dir)"
+ else
+ _tide_print_item python $tide_python_icon' ' "$v ($base)"
+ end
+ else if path is .python-version Pipfile __init__.py pyproject.toml requirements.txt setup.py
+ if command -q python3
+ python3 --version | string match -qr "(?[\d.]+)"
+ else
+ python --version | string match -qr "(?[\d.]+)"
+ end
+ _tide_print_item python $tide_python_icon' ' $v
+ end
+end
diff --git a/dev/fish-plugins/tide/functions/_tide_item_ruby.fish b/dev/fish-plugins/tide/functions/_tide_item_ruby.fish
new file mode 100644
index 000000000..bde84eb59
--- /dev/null
+++ b/dev/fish-plugins/tide/functions/_tide_item_ruby.fish
@@ -0,0 +1,6 @@
+function _tide_item_ruby
+ if path is $_tide_parent_dirs/{*.gemspec,Gemfile,Rakefile,.ruby-version}
+ ruby --version | string match -qr "(?[\d.]+)"
+ _tide_print_item ruby $tide_ruby_icon' ' $v
+ end
+end
diff --git a/dev/fish-plugins/tide/functions/_tide_item_rustc.fish b/dev/fish-plugins/tide/functions/_tide_item_rustc.fish
new file mode 100644
index 000000000..b46cd99c9
--- /dev/null
+++ b/dev/fish-plugins/tide/functions/_tide_item_rustc.fish
@@ -0,0 +1,6 @@
+function _tide_item_rustc
+ if path is $_tide_parent_dirs/Cargo.toml
+ rustc --version | string match -qr "(?[\d.]+)"
+ _tide_print_item rustc $tide_rustc_icon' ' $v
+ end
+end
diff --git a/dev/fish-plugins/tide/functions/_tide_item_shlvl.fish b/dev/fish-plugins/tide/functions/_tide_item_shlvl.fish
new file mode 100644
index 000000000..95dd5ff2e
--- /dev/null
+++ b/dev/fish-plugins/tide/functions/_tide_item_shlvl.fish
@@ -0,0 +1,4 @@
+function _tide_item_shlvl
+ # Non-interactive shells do not increment SHLVL, so we don't need to subtract 1
+ test $SHLVL -gt $tide_shlvl_threshold && _tide_print_item shlvl $tide_shlvl_icon' ' $SHLVL
+end
diff --git a/dev/fish-plugins/tide/functions/_tide_item_status.fish b/dev/fish-plugins/tide/functions/_tide_item_status.fish
new file mode 100644
index 000000000..3a040fd1f
--- /dev/null
+++ b/dev/fish-plugins/tide/functions/_tide_item_status.fish
@@ -0,0 +1,15 @@
+function _tide_item_status
+ if string match -qv 0 $_tide_pipestatus # If there is a failure anywhere in the pipestatus
+ if test "$_tide_pipestatus" = 1 # If simple failure
+ contains character $_tide_left_items || tide_status_bg_color=$tide_status_bg_color_failure \
+ tide_status_color=$tide_status_color_failure _tide_print_item status $tide_status_icon_failure' ' 1
+ else
+ fish_status_to_signal $_tide_pipestatus | string replace SIG '' | string join '|' | read -l out
+ test $_tide_status = 0 && _tide_print_item status $tide_status_icon' ' $out ||
+ tide_status_bg_color=$tide_status_bg_color_failure tide_status_color=$tide_status_color_failure \
+ _tide_print_item status $tide_status_icon_failure' ' $out
+ end
+ else if not contains character $_tide_left_items
+ _tide_print_item status $tide_status_icon
+ end
+end
diff --git a/dev/fish-plugins/tide/functions/_tide_item_terraform.fish b/dev/fish-plugins/tide/functions/_tide_item_terraform.fish
new file mode 100644
index 000000000..c079ce3bd
--- /dev/null
+++ b/dev/fish-plugins/tide/functions/_tide_item_terraform.fish
@@ -0,0 +1,5 @@
+function _tide_item_terraform
+ path is $_tide_parent_dirs/.terraform &&
+ terraform workspace show | string match -v default | read -l w &&
+ _tide_print_item terraform $tide_terraform_icon' ' $w
+end
diff --git a/dev/fish-plugins/tide/functions/_tide_item_time.fish b/dev/fish-plugins/tide/functions/_tide_item_time.fish
new file mode 100644
index 000000000..b8522bc4e
--- /dev/null
+++ b/dev/fish-plugins/tide/functions/_tide_item_time.fish
@@ -0,0 +1,3 @@
+function _tide_item_time
+ _tide_print_item time (date +$tide_time_format)
+end
diff --git a/dev/fish-plugins/tide/functions/_tide_item_toolbox.fish b/dev/fish-plugins/tide/functions/_tide_item_toolbox.fish
new file mode 100644
index 000000000..0b33f29ab
--- /dev/null
+++ b/dev/fish-plugins/tide/functions/_tide_item_toolbox.fish
@@ -0,0 +1,4 @@
+function _tide_item_toolbox
+ test -e /run/.toolboxenv &&
+ _tide_print_item toolbox $tide_toolbox_icon' ' (string match -rg 'name="(.*)"' [\d.]+(-dev)?)"
+ _tide_print_item zig $tide_zig_icon' ' $v
+ end
+end
diff --git a/dev/fish-plugins/tide/functions/_tide_parent_dirs.fish b/dev/fish-plugins/tide/functions/_tide_parent_dirs.fish
new file mode 100644
index 000000000..9f31b0436
--- /dev/null
+++ b/dev/fish-plugins/tide/functions/_tide_parent_dirs.fish
@@ -0,0 +1,7 @@
+function _tide_parent_dirs --on-variable PWD
+ set -g _tide_parent_dirs (string escape (
+ for dir in (string split / -- $PWD)
+ set -la parts $dir
+ string join / -- $parts
+ end))
+end
diff --git a/dev/fish-plugins/tide/functions/_tide_print_item.fish b/dev/fish-plugins/tide/functions/_tide_print_item.fish
new file mode 100644
index 000000000..db5c98b25
--- /dev/null
+++ b/dev/fish-plugins/tide/functions/_tide_print_item.fish
@@ -0,0 +1,22 @@
+function _tide_print_item -a item
+ v=tide_"$item"_bg_color set -f item_bg_color $$v
+
+ if set -e add_prefix
+ set_color $item_bg_color -b normal
+ v=tide_"$_tide_side"_prompt_prefix echo -ns $$v
+ else if test "$item_bg_color" = "$prev_bg_color"
+ v=tide_"$_tide_side"_prompt_separator_same_color echo -ns $_tide_color_separator_same_color$$v
+ else if test $_tide_side = left
+ set_color $prev_bg_color -b $item_bg_color
+ echo -ns $tide_left_prompt_separator_diff_color
+ else
+ set_color $item_bg_color -b $prev_bg_color
+ echo -ns $tide_right_prompt_separator_diff_color
+ end
+
+ v=tide_"$item"_color set_color $$v -b $item_bg_color
+
+ echo -ns $_tide_pad $argv[2..] $_tide_pad
+
+ set -g prev_bg_color $item_bg_color
+end
diff --git a/dev/fish-plugins/tide/functions/_tide_pwd.fish b/dev/fish-plugins/tide/functions/_tide_pwd.fish
new file mode 100644
index 000000000..5447dd44a
--- /dev/null
+++ b/dev/fish-plugins/tide/functions/_tide_pwd.fish
@@ -0,0 +1,42 @@
+set_color -o $tide_pwd_color_anchors | read -l color_anchors
+set_color $tide_pwd_color_truncated_dirs | read -l color_truncated
+set -l reset_to_color_dirs (set_color normal -b $tide_pwd_bg_color; set_color $tide_pwd_color_dirs)
+
+set -l unwritable_icon $tide_pwd_icon_unwritable' '
+set -l home_icon $tide_pwd_icon_home' '
+set -l pwd_icon $tide_pwd_icon' '
+
+eval "function _tide_pwd
+ if set -l split_pwd (string replace -r '^$HOME' '~' -- \$PWD | string split /)
+ test -w . && set -f split_output \"$pwd_icon\$split_pwd[1]\" \$split_pwd[2..] ||
+ set -f split_output \"$unwritable_icon\$split_pwd[1]\" \$split_pwd[2..]
+ set split_output[-1] \"$color_anchors\$split_output[-1]$reset_to_color_dirs\"
+ else
+ set -f split_output \"$home_icon$color_anchors~\"
+ end
+
+ string join / -- \$split_output | string length -V | read -g _tide_pwd_len
+
+ i=1 for dir_section in \$split_pwd[2..-2]
+ string join -- / \$split_pwd[..\$i] | string replace '~' $HOME | read -l parent_dir # Uses i before increment
+
+ math \$i+1 | read i
+
+ if path is \$parent_dir/\$dir_section/\$tide_pwd_markers
+ set split_output[\$i] \"$color_anchors\$dir_section$reset_to_color_dirs\"
+ else if test \$_tide_pwd_len -gt \$dist_btwn_sides
+ string match -qr \"(?\..|.)\" \$dir_section
+
+ set -l glob \$parent_dir/\$trunc*/
+ set -e glob[(contains -i \$parent_dir/\$dir_section/ \$glob)] # This is faster than inverse string match
+
+ while string match -qr \"^\$parent_dir/\$(string escape --style=regex \$trunc)\" \$glob &&
+ string match -qr \"(?\$(string escape --style=regex \$trunc).)\" \$dir_section
+ end
+ test -n \"\$trunc\" && set split_output[\$i] \"$color_truncated\$trunc$reset_to_color_dirs\" &&
+ string join / \$split_output | string length -V | read _tide_pwd_len
+ end
+ end
+
+ string join -- / \"$reset_to_color_dirs\$split_output[1]\" \$split_output[2..]
+end"
diff --git a/dev/fish-plugins/tide/functions/_tide_remove_unusable_items.fish b/dev/fish-plugins/tide/functions/_tide_remove_unusable_items.fish
new file mode 100644
index 000000000..b1d9b2831
--- /dev/null
+++ b/dev/fish-plugins/tide/functions/_tide_remove_unusable_items.fish
@@ -0,0 +1,25 @@
+function _tide_remove_unusable_items
+ # Remove tool-specific items for tools the machine doesn't have installed
+ set -l removed_items
+ for item in aws bun crystal direnv distrobox docker elixir gcloud git go java kubectl nix_shell node php pulumi python ruby rustc terraform toolbox zig
+ contains $item $tide_left_prompt_items $tide_right_prompt_items || continue
+
+ set -l cli_names $item
+ switch $item
+ case distrobox # there is no 'distrobox' command inside the container
+ set cli_names distrobox-export # 'distrobox-export' and 'distrobox-host-exec' are available
+ case nix_shell
+ set cli_names nix nix-shell
+ case python
+ set cli_names python python3
+ end
+ type --query $cli_names || set -a removed_items $item
+ end
+
+ set -U _tide_left_items (for item in $tide_left_prompt_items
+ contains $item $removed_items || echo $item
+ end)
+ set -U _tide_right_items (for item in $tide_right_prompt_items
+ contains $item $removed_items || echo $item
+ end)
+end
diff --git a/dev/fish-plugins/tide/functions/_tide_sub_bug-report.fish b/dev/fish-plugins/tide/functions/_tide_sub_bug-report.fish
new file mode 100644
index 000000000..82f08c04a
--- /dev/null
+++ b/dev/fish-plugins/tide/functions/_tide_sub_bug-report.fish
@@ -0,0 +1,73 @@
+function _tide_sub_bug-report
+ argparse c/clean v/verbose check -- $argv
+
+ set -l fish_path (status fish-path)
+
+ if set -q _flag_clean
+ HOME=(mktemp -d) $fish_path --init-command "curl --silent \
+ https://raw.githubusercontent.com/jorgebucaran/fisher/main/functions/fisher.fish |
+ source && fisher install ilancosman/tide@v6"
+ else if set -q _flag_verbose
+ set --long | string match -r "^_?tide.*" | # Get only tide variables
+ string match -r --invert "^_tide_prompt_var.*" # Remove _tide_prompt_var
+ else
+ $fish_path --version | string match -qr "fish, version (?.*)"
+ _tide_check_version Fish fish-shell/fish-shell "(?[\d.]+)" $fish_version || return
+
+ tide --version | string match -qr "tide, version (?.*)"
+ _tide_check_version Tide IlanCosman/tide "v(?[\d.]+)" $tide_version || return
+
+ if command --query git
+ test (path sort (git --version) "git version 2.22.0")[1] = "git version 2.22.0"
+ _tide_check_condition \
+ "Your git version is too old." \
+ "Tide requires at least version 2.22." \
+ "Please update before submitting a bug report." || return
+ end
+
+ # Check that omf is not installed
+ not functions --query omf
+ _tide_check_condition \
+ "Tide does not work with oh-my-fish installed." \
+ "Please uninstall it before submitting a bug report." || return
+
+ if not set -q _flag_check
+ $fish_path -ic "time $fish_path -c exit" 2>|
+ string match -rg "Executed in(.*)fish" |
+ string trim | read -l fish_startup_time
+
+ read -l --prompt-str "What operating system are you using? (e.g Ubuntu 20.04): " os
+ read -l --prompt-str "What terminal emulator are you using? (e.g Kitty): " terminal_emulator
+
+ printf '%b\n' "\nPlease copy the following information into the issue:\n" \
+ "fish version: $fish_version" \
+ "tide version: $tide_version" \
+ "term: $TERM" \
+ "os: $os" \
+ "terminal emulator: $terminal_emulator" \
+ "fish startup: $fish_startup_time" \
+ "fisher plugins: $_fisher_plugins"
+ end
+ end
+end
+
+function _tide_check_version -a program_name repo_name regex_to_get_v installed_version
+ curl -sL https://github.com/$repo_name/releases/latest |
+ string match -qr "https://github.com/$repo_name/releases/tag/$regex_to_get_v"
+
+ string match -qr "^$v" "$installed_version" # Allow git versions, e.g 3.3.1-701-gceade1629
+ _tide_check_condition \
+ "Your $program_name version is out of date." \
+ "The latest is $v. You have $installed_version." \
+ "Please update before submitting a bug report."
+end
+
+function _tide_check_condition
+ if test "$status" != 0
+ set_color red
+ printf '%s\n' $argv
+ set_color normal
+ return 1
+ end
+ return 0
+end
diff --git a/dev/fish-plugins/tide/functions/_tide_sub_configure.fish b/dev/fish-plugins/tide/functions/_tide_sub_configure.fish
new file mode 100644
index 000000000..22faf3afd
--- /dev/null
+++ b/dev/fish-plugins/tide/functions/_tide_sub_configure.fish
@@ -0,0 +1,156 @@
+set -g _tide_color_dark_blue 0087AF
+set -g _tide_color_dark_green 5FAF00
+set -g _tide_color_gold D7AF00
+set -g _tide_color_green 5FD700
+set -g _tide_color_light_blue 00AFFF
+
+# Create an empty fake function for each item
+for func in _fake(functions --all | string match --entire _tide_item)
+ function $func
+ end
+end
+
+for file in (status dirname)/tide/configure/{choices, functions}/**.fish
+ source $file
+end
+
+function _tide_sub_configure
+ set -l choices (path basename (status dirname)/tide/configure/choices/**.fish | path change-extension '')
+ argparse auto $choices= -- $argv
+
+ for var in (set -l --names | string match -e _flag)
+ set -x $var $$var
+ end
+
+ if set -q _flag_auto
+ set -fx _flag_finish 'Overwrite your current tide config'
+ else if test $COLUMNS -lt 55 -o $LINES -lt 21
+ echo 'Terminal size too small; must be at least 55 x 21'
+ return 1
+ end
+
+ _tide_detect_os | read -g --line os_branding_icon os_branding_color os_branding_bg_color
+
+ set -g fake_columns $COLUMNS
+ test $fake_columns -gt 90 && set fake_columns 90
+ set -g fake_lines $LINES
+
+ set -g _tide_selected_option
+ _next_choice all/style
+end
+
+function _next_choice -a nextChoice
+ set -q _tide_selected_option || return 0
+ set -l cmd (string split '/' $nextChoice)[2]
+ $cmd
+end
+
+function _tide_title -a text
+ set -q _flag_auto && return
+
+ command -q clear && clear
+ set_color -o
+ string pad --width (math --scale=0 "$fake_columns/2" + (string length $text)/2) $text
+ set_color normal
+
+ set -g _tide_configure_first_option_after_title
+end
+
+function _tide_option -a symbol text
+ set -ga _tide_symbol_list $symbol
+ set -ga _tide_option_list $text
+
+ if not set -q _flag_auto
+ set -g _tide_configure_first_prompt_after_option
+
+ set_color -o
+ set -e _tide_configure_first_option_after_title || echo
+ echo "($symbol) $text"
+ set_color normal
+ end
+end
+
+function _tide_menu -a func
+ if set -q _flag_auto
+ set -l flag_var_name _flag_$func
+ set -g _tide_selected_option $$flag_var_name
+
+ if test -z "$_tide_selected_option"
+ echo "Missing input for choice '$func'"
+ _tide_exit_configure
+ else if not contains $_tide_selected_option $_tide_option_list
+ echo "Invalid input '$_tide_selected_option' for choice '$func'"
+ _tide_exit_configure
+ else
+ set -e _tide_symbol_list
+ set -e _tide_option_list
+ end
+ return
+ end
+
+ argparse no-restart -- $argv # Add no-restart option for first menu
+
+ echo
+ if not set -q _flag_no_restart
+ set -f r r
+ echo '(r) Restart from the beginning'
+ end
+ echo '(q) Quit and do nothing'\n
+
+ while read --nchars 1 --prompt-str \
+ "$(set_color -o)Choice [$(string join '/' $_tide_symbol_list $r q)] $(set_color normal)" input
+ switch $input
+ case r
+ set -q _flag_no_restart && continue
+ set -e _tide_symbol_list
+ set -e _tide_option_list
+ _next_choice all/style
+ break
+ case q
+ _tide_exit_configure
+ set -e _tide_symbol_list
+ set -e _tide_option_list
+ command -q clear && clear
+ break
+ case $_tide_symbol_list
+ set -g _tide_selected_option $_tide_option_list[(contains -i $input $_tide_symbol_list)]
+ test "$func" != finish &&
+ set -a _tide_configure_current_options --$func=(string escape $_tide_selected_option)
+ set -e _tide_symbol_list
+ set -e _tide_option_list
+ break
+ end
+ end
+end
+
+function _tide_display_prompt
+ set -q _flag_auto && return
+
+ _fake_tide_cache_variables
+ set -l prompt (_fake_tide_prompt)
+
+ set -l bottom_left_prompt_string_length (string length --visible $prompt[-1])
+ set -l right_prompt_string (string pad --width (math $fake_columns-$bottom_left_prompt_string_length) $prompt[1])
+ set -l prompt[-1] "$prompt[-1]$right_prompt_string"
+
+ if set -q _configure_transient
+ if contains newline $fake_tide_left_prompt_items
+ string unescape $prompt[3..]
+ else
+ _fake_tide_item_character
+ echo
+ end
+ else
+ if not set -q _tide_configure_first_prompt_after_option
+ test "$fake_tide_prompt_add_newline_before" = true && echo
+ end
+ string unescape $prompt[2..]
+ end
+
+ set -e _tide_configure_first_prompt_after_option
+ set_color normal
+end
+
+function _tide_exit_configure
+ set -e _tide_selected_option # Skip through all switch and _next_choice
+end
diff --git a/dev/fish-plugins/tide/functions/_tide_sub_reload.fish b/dev/fish-plugins/tide/functions/_tide_sub_reload.fish
new file mode 100644
index 000000000..5a45fbd91
--- /dev/null
+++ b/dev/fish-plugins/tide/functions/_tide_sub_reload.fish
@@ -0,0 +1,3 @@
+function _tide_sub_reload
+ source (functions --details fish_prompt)
+end
diff --git a/dev/fish-plugins/tide/functions/fish_mode_prompt.fish b/dev/fish-plugins/tide/functions/fish_mode_prompt.fish
new file mode 100644
index 000000000..f37cf7dce
--- /dev/null
+++ b/dev/fish-plugins/tide/functions/fish_mode_prompt.fish
@@ -0,0 +1 @@
+# Disable default vi prompt
diff --git a/dev/fish-plugins/tide/functions/fish_prompt.fish b/dev/fish-plugins/tide/functions/fish_prompt.fish
new file mode 100644
index 000000000..994513d32
--- /dev/null
+++ b/dev/fish-plugins/tide/functions/fish_prompt.fish
@@ -0,0 +1,170 @@
+function fish_prompt
+end # In case this file gets loaded non-interactively, e.g by conda
+status is-interactive || exit
+
+_tide_remove_unusable_items
+_tide_cache_variables
+_tide_parent_dirs
+source (functions --details _tide_pwd)
+
+set -l prompt_var _tide_prompt_$fish_pid
+set -U $prompt_var # Set var here so if we erase $prompt_var, bg job won't set a uvar
+
+set_color normal | read -l color_normal
+status fish-path | read -l fish_path
+
+# _tide_repaint prevents us from creating a second background job
+function _tide_refresh_prompt --on-variable $prompt_var --on-variable COLUMNS
+ set -g _tide_repaint
+ commandline -f repaint
+end
+
+if contains newline $_tide_left_items # two line prompt initialization
+ test "$tide_prompt_add_newline_before" = true && set -l add_newline '\n'
+
+ set_color $tide_prompt_color_frame_and_connection -b normal | read -l prompt_and_frame_color
+
+ set -l column_offset 5
+ test "$tide_left_prompt_frame_enabled" = true &&
+ set -l top_left_frame "$prompt_and_frame_color╭─" &&
+ set -l bot_left_frame "$prompt_and_frame_color╰─" &&
+ set column_offset 3
+ test "$tide_right_prompt_frame_enabled" = true &&
+ set -l top_right_frame "$prompt_and_frame_color─╮" &&
+ set -l bot_right_frame "$prompt_and_frame_color─╯" &&
+ set column_offset (math $column_offset-2)
+
+ if test "$tide_prompt_transient_enabled" = true
+ eval "
+function fish_prompt
+ _tide_status=\$status _tide_pipestatus=\$pipestatus if not set -e _tide_repaint
+ jobs -q && jobs -p | count | read -lx _tide_jobs
+ $fish_path -c \"set _tide_pipestatus \$_tide_pipestatus
+set _tide_parent_dirs \$_tide_parent_dirs
+PATH=\$(string escape \"\$PATH\") CMD_DURATION=\$CMD_DURATION fish_bind_mode=\$fish_bind_mode set $prompt_var (_tide_2_line_prompt)\" &
+ builtin disown
+
+ command kill \$_tide_last_pid 2>/dev/null
+ set -g _tide_last_pid \$last_pid
+ end
+
+ if not set -q _tide_transient
+ math \$COLUMNS-(string length -V \"\$$prompt_var[1][1]\$$prompt_var[1][3]\")+$column_offset | read -lx dist_btwn_sides
+
+ echo -n $add_newline'$top_left_frame'(string replace @PWD@ (_tide_pwd) \"\$$prompt_var[1][1]\")'$prompt_and_frame_color'
+ string repeat -Nm(math max 0, \$dist_btwn_sides-\$_tide_pwd_len) '$tide_prompt_icon_connection'
+
+ echo \"\$$prompt_var[1][3]$top_right_frame\"
+ end
+ echo -n \e\[0J\"$bot_left_frame\$$prompt_var[1][2]$color_normal \"
+end
+
+function fish_right_prompt
+ set -e _tide_transient || string unescape \"\$$prompt_var[1][4]$bot_right_frame$color_normal\"
+end"
+ else
+ eval "
+function fish_prompt
+ _tide_status=\$status _tide_pipestatus=\$pipestatus if not set -e _tide_repaint
+ jobs -q && jobs -p | count | read -lx _tide_jobs
+ $fish_path -c \"set _tide_pipestatus \$_tide_pipestatus
+set _tide_parent_dirs \$_tide_parent_dirs
+PATH=\$(string escape \"\$PATH\") CMD_DURATION=\$CMD_DURATION fish_bind_mode=\$fish_bind_mode set $prompt_var (_tide_2_line_prompt)\" &
+ builtin disown
+
+ command kill \$_tide_last_pid 2>/dev/null
+ set -g _tide_last_pid \$last_pid
+ end
+
+ math \$COLUMNS-(string length -V \"\$$prompt_var[1][1]\$$prompt_var[1][3]\")+$column_offset | read -lx dist_btwn_sides
+
+ echo -ns $add_newline'$top_left_frame'(string replace @PWD@ (_tide_pwd) \"\$$prompt_var[1][1]\")'$prompt_and_frame_color'
+ string repeat -Nm(math max 0, \$dist_btwn_sides-\$_tide_pwd_len) '$tide_prompt_icon_connection'
+ echo -ns \"\$$prompt_var[1][3]$top_right_frame\"\n\"$bot_left_frame\$$prompt_var[1][2]$color_normal \"
+end
+
+function fish_right_prompt
+ string unescape \"\$$prompt_var[1][4]$bot_right_frame$color_normal\"
+end"
+ end
+else # one line prompt initialization
+ test "$tide_prompt_add_newline_before" = true && set -l add_newline '\0'
+
+ math 5 -$tide_prompt_min_cols | read -l column_offset
+ test $column_offset -ge 0 && set column_offset "+$column_offset"
+
+ if test "$tide_prompt_transient_enabled" = true
+ eval "
+function fish_prompt
+ set -lx _tide_status \$status
+ _tide_pipestatus=\$pipestatus if not set -e _tide_repaint
+ jobs -q && jobs -p | count | read -lx _tide_jobs
+ $fish_path -c \"set _tide_pipestatus \$_tide_pipestatus
+set _tide_parent_dirs \$_tide_parent_dirs
+PATH=\$(string escape \"\$PATH\") CMD_DURATION=\$CMD_DURATION fish_bind_mode=\$fish_bind_mode set $prompt_var (_tide_1_line_prompt)\" &
+ builtin disown
+
+ command kill \$_tide_last_pid 2>/dev/null
+ set -g _tide_last_pid \$last_pid
+ end
+
+ if set -q _tide_transient
+ echo -n \e\[0J
+ add_prefix= _tide_item_character
+ echo -n '$color_normal '
+ else
+ math \$COLUMNS-(string length -V \"\$$prompt_var[1][1]\$$prompt_var[1][2]\")$column_offset | read -lx dist_btwn_sides
+ string replace @PWD@ (_tide_pwd) $add_newline \$$prompt_var[1][1]'$color_normal '
+ end
+end
+
+function fish_right_prompt
+ set -e _tide_transient || string unescape \"\$$prompt_var[1][2]$color_normal\"
+end"
+ else
+ eval "
+function fish_prompt
+ _tide_status=\$status _tide_pipestatus=\$pipestatus if not set -e _tide_repaint
+ jobs -q && jobs -p | count | read -lx _tide_jobs
+ $fish_path -c \"set _tide_pipestatus \$_tide_pipestatus
+set _tide_parent_dirs \$_tide_parent_dirs
+PATH=\$(string escape \"\$PATH\") CMD_DURATION=\$CMD_DURATION fish_bind_mode=\$fish_bind_mode set $prompt_var (_tide_1_line_prompt)\" &
+ builtin disown
+
+ command kill \$_tide_last_pid 2>/dev/null
+ set -g _tide_last_pid \$last_pid
+ end
+
+ math \$COLUMNS-(string length -V \"\$$prompt_var[1][1]\$$prompt_var[1][2]\")$column_offset | read -lx dist_btwn_sides
+ string replace @PWD@ (_tide_pwd) $add_newline \$$prompt_var[1][1]'$color_normal '
+end
+
+function fish_right_prompt
+ string unescape \"\$$prompt_var[1][2]$color_normal\"
+end"
+ end
+end
+
+# Inheriting instead of evaling because here load time is more important than runtime
+function _tide_on_fish_exit --on-event fish_exit --inherit-variable prompt_var
+ set -e $prompt_var
+end
+
+if test "$tide_prompt_transient_enabled" = true
+ function _tide_enter_transient
+ # If the commandline will be executed or is empty, and the pager is not open
+ # Pager open usually means selecting, not running
+ # Can be untrue, but it's better than the alternative
+ if commandline --is-valid || test -z "$(commandline)" && not commandline --paging-mode
+ set -g _tide_transient
+ set -g _tide_repaint
+ commandline -f repaint
+ end
+ commandline -f execute
+ end
+
+ bind \r _tide_enter_transient
+ bind \n _tide_enter_transient
+ bind -M insert \r _tide_enter_transient
+ bind -M insert \n _tide_enter_transient
+end
diff --git a/dev/fish-plugins/tide/functions/tide.fish b/dev/fish-plugins/tide/functions/tide.fish
new file mode 100644
index 000000000..092a72245
--- /dev/null
+++ b/dev/fish-plugins/tide/functions/tide.fish
@@ -0,0 +1,28 @@
+function tide --description 'Manage your Tide prompt'
+ argparse --stop-nonopt v/version h/help -- $argv
+
+ if set -q _flag_version
+ echo 'tide, version 6.1.1'
+ else if set -q _flag_help
+ _tide_help
+ else if functions --query _tide_sub_$argv[1]
+ _tide_sub_$argv[1] $argv[2..]
+ else
+ _tide_help
+ return 1
+ end
+end
+
+function _tide_help
+ printf %s\n \
+ 'Usage: tide [options] subcommand [options]' \
+ '' \
+ 'Options:' \
+ ' -v or --version print tide version number' \
+ ' -h or --help print this help message' \
+ '' \
+ 'Subcommands:' \
+ ' configure run interactive configuration wizard' \
+ ' reload reload tide configuration' \
+ ' bug-report print info for use in bug reports'
+end
diff --git a/dev/fish-plugins/tide/functions/tide/configure/choices/all/finish.fish b/dev/fish-plugins/tide/functions/tide/configure/choices/all/finish.fish
new file mode 100644
index 000000000..f670b1edf
--- /dev/null
+++ b/dev/fish-plugins/tide/functions/tide/configure/choices/all/finish.fish
@@ -0,0 +1,46 @@
+function finish
+ _tide_title Finish
+
+ echo
+ set_color red
+ _tide_option y 'Overwrite your current tide config'
+ set_color normal
+ echo
+
+ _tide_option p 'Exit and print the config you just generated'
+ echo
+
+ _tide_menu (status function)
+ switch $_tide_selected_option
+ case 'Overwrite your current tide config'
+ _tide_finish
+ command -q clear && clear
+ set -q _flag_auto || _tide_print_configure_current_options
+ case 'Exit and print the config you just generated'
+ _tide_exit_configure
+ command -q clear && clear
+ _tide_print_configure_current_options
+ end
+end
+
+function _tide_finish
+ _tide_exit_configure
+
+ # Deal with prompt char/vi mode
+ contains character $fake_tide_left_prompt_items || set -p fake_tide_left_prompt_items vi_mode
+
+ # Set the real variables
+ for fakeVar in (set --names | string match -r "^fake_tide.*")
+ set -U (string replace 'fake_' '' $fakeVar) $$fakeVar
+ end
+
+ # Make sure old prompt won't display
+ set -e $_tide_prompt_var 2>/dev/null
+
+ # Re-initialize the prompt
+ tide reload
+end
+
+function _tide_print_configure_current_options
+ _tide_fish_colorize "tide configure --auto $_tide_configure_current_options"
+end
diff --git a/dev/fish-plugins/tide/functions/tide/configure/choices/all/icons.fish b/dev/fish-plugins/tide/functions/tide/configure/choices/all/icons.fish
new file mode 100644
index 000000000..be167bb84
--- /dev/null
+++ b/dev/fish-plugins/tide/functions/tide/configure/choices/all/icons.fish
@@ -0,0 +1,33 @@
+function icons
+ _tide_title Icons
+
+ _tide_option 1 'Few icons'
+ _tide_display_prompt
+
+ _tide_option 2 'Many icons'
+ _enable_icons
+ _tide_display_prompt
+
+ _tide_menu (status function)
+ switch $_tide_selected_option
+ case 'Few icons'
+ _disable_icons
+ end
+ _next_choice all/transient
+end
+
+function _enable_icons
+ set -p fake_tide_left_prompt_items os
+ set -g fake_tide_pwd_icon
+ set -g fake_tide_pwd_icon_home
+ set -g fake_tide_cmd_duration_icon
+ set -g fake_tide_git_icon
+end
+
+function _disable_icons
+ _tide_find_and_remove os fake_tide_left_prompt_items
+ set fake_tide_pwd_icon
+ set fake_tide_pwd_icon_home
+ set fake_tide_cmd_duration_icon
+ set fake_tide_git_icon
+end
diff --git a/dev/fish-plugins/tide/functions/tide/configure/choices/all/prompt_colors.fish b/dev/fish-plugins/tide/functions/tide/configure/choices/all/prompt_colors.fish
new file mode 100644
index 000000000..247ef4ed4
--- /dev/null
+++ b/dev/fish-plugins/tide/functions/tide/configure/choices/all/prompt_colors.fish
@@ -0,0 +1,26 @@
+function prompt_colors
+ _tide_title 'Prompt Colors'
+
+ _tide_option 1 'True color'
+ _tide_display_prompt
+
+ _tide_option 2 '16 colors'
+ _load_config "$_tide_configure_style"_16color
+ set -g _tide_16color true
+ _tide_display_prompt
+
+ _tide_menu (status function)
+ switch $_tide_selected_option
+ case 'True color'
+ _load_config "$_tide_configure_style"
+ set -e _tide_16color
+ switch $_tide_configure_style
+ case lean rainbow
+ _next_choice all/show_time
+ case classic
+ _next_choice classic/classic_prompt_color
+ end
+ case '16 colors'
+ _next_choice all/show_time
+ end
+end
diff --git a/dev/fish-plugins/tide/functions/tide/configure/choices/all/prompt_connection.fish b/dev/fish-plugins/tide/functions/tide/configure/choices/all/prompt_connection.fish
new file mode 100644
index 000000000..ff7654673
--- /dev/null
+++ b/dev/fish-plugins/tide/functions/tide/configure/choices/all/prompt_connection.fish
@@ -0,0 +1,31 @@
+function prompt_connection
+ _tide_title 'Prompt Connection'
+
+ _tide_option 1 Disconnected
+ set -g fake_tide_prompt_icon_connection ' '
+ _tide_display_prompt
+
+ _tide_option 2 Dotted
+ set -g fake_tide_prompt_icon_connection '·'
+ _tide_display_prompt
+
+ _tide_option 3 Solid
+ set -g fake_tide_prompt_icon_connection '─'
+ _tide_display_prompt
+
+ _tide_menu (status function)
+ switch $_tide_selected_option
+ case Disconnected
+ set -g fake_tide_prompt_icon_connection ' '
+ case Dotted
+ set -g fake_tide_prompt_icon_connection '·'
+ case Solid
+ set -g fake_tide_prompt_icon_connection '─'
+ end
+ switch $_tide_configure_style
+ case lean
+ _next_choice all/prompt_connection_andor_frame_color
+ case classic rainbow
+ _next_choice powerline/powerline_right_prompt_frame
+ end
+end
diff --git a/dev/fish-plugins/tide/functions/tide/configure/choices/all/prompt_connection_andor_frame_color.fish b/dev/fish-plugins/tide/functions/tide/configure/choices/all/prompt_connection_andor_frame_color.fish
new file mode 100644
index 000000000..cefbf2cde
--- /dev/null
+++ b/dev/fish-plugins/tide/functions/tide/configure/choices/all/prompt_connection_andor_frame_color.fish
@@ -0,0 +1,40 @@
+function prompt_connection_andor_frame_color
+ if test "$_tide_16color" = true ||
+ test "$fake_tide_left_prompt_frame_enabled" = false -a \
+ "$fake_tide_right_prompt_frame_enabled" = false -a \
+ "$fake_tide_prompt_icon_connection" = ' '
+ _next_choice all/prompt_spacing
+ return 0
+ end
+
+ _tide_title "Connection & Frame Color"
+
+ _tide_option 1 Lightest
+ set -g fake_tide_prompt_color_frame_and_connection 808080
+ _tide_display_prompt
+
+ _tide_option 2 Light
+ set -g fake_tide_prompt_color_frame_and_connection 6C6C6C
+ _tide_display_prompt
+
+ _tide_option 3 Dark
+ set -g fake_tide_prompt_color_frame_and_connection 585858
+ _tide_display_prompt
+
+ _tide_option 4 Darkest
+ set -g fake_tide_prompt_color_frame_and_connection 444444
+ _tide_display_prompt
+
+ _tide_menu (status function)
+ switch $_tide_selected_option
+ case Lightest
+ set -g fake_tide_prompt_color_frame_and_connection 808080
+ case Light
+ set -g fake_tide_prompt_color_frame_and_connection 6C6C6C
+ case Dark
+ set -g fake_tide_prompt_color_frame_and_connection 585858
+ case Darkest
+ set -g fake_tide_prompt_color_frame_and_connection 444444
+ end
+ _next_choice all/prompt_spacing
+end
diff --git a/dev/fish-plugins/tide/functions/tide/configure/choices/all/prompt_spacing.fish b/dev/fish-plugins/tide/functions/tide/configure/choices/all/prompt_spacing.fish
new file mode 100644
index 000000000..f68be6868
--- /dev/null
+++ b/dev/fish-plugins/tide/functions/tide/configure/choices/all/prompt_spacing.fish
@@ -0,0 +1,22 @@
+function prompt_spacing
+ _tide_title 'Prompt Spacing'
+
+ _tide_option 1 Compact
+ set -g fake_tide_prompt_add_newline_before false
+ _tide_display_prompt
+ _tide_display_prompt
+
+ _tide_option 2 Sparse
+ set -g fake_tide_prompt_add_newline_before true
+ _tide_display_prompt
+ _tide_display_prompt
+
+ _tide_menu (status function)
+ switch $_tide_selected_option
+ case Compact
+ set -g fake_tide_prompt_add_newline_before false
+ case Sparse
+ set -g fake_tide_prompt_add_newline_before true
+ end
+ _next_choice all/icons
+end
diff --git a/dev/fish-plugins/tide/functions/tide/configure/choices/all/show_time.fish b/dev/fish-plugins/tide/functions/tide/configure/choices/all/show_time.fish
new file mode 100644
index 000000000..b66c78521
--- /dev/null
+++ b/dev/fish-plugins/tide/functions/tide/configure/choices/all/show_time.fish
@@ -0,0 +1,33 @@
+function show_time
+ _tide_title 'Show current time?'
+
+ _tide_option 1 No
+ _tide_display_prompt
+
+ set -a fake_tide_right_prompt_items time
+
+ _tide_option 2 '24-hour format'
+ set -g fake_tide_time_format %T
+ _tide_display_prompt
+
+ _tide_option 3 '12-hour format'
+ set -g fake_tide_time_format %r
+ _tide_display_prompt
+
+ _tide_menu (status function)
+ switch $_tide_selected_option
+ case No
+ set -g fake_tide_time_format ''
+ set -e fake_tide_right_prompt_items[-1]
+ case '24-hour format'
+ set -g fake_tide_time_format %T
+ case '12-hour format'
+ set -g fake_tide_time_format %r
+ end
+ switch $_tide_configure_style
+ case lean
+ _next_choice "$_tide_configure_style"/"$_tide_configure_style"_prompt_height
+ case classic rainbow
+ _next_choice "$_tide_configure_style"/"$_tide_configure_style"_prompt_separators
+ end
+end
diff --git a/dev/fish-plugins/tide/functions/tide/configure/choices/all/style.fish b/dev/fish-plugins/tide/functions/tide/configure/choices/all/style.fish
new file mode 100644
index 000000000..fe9a58f24
--- /dev/null
+++ b/dev/fish-plugins/tide/functions/tide/configure/choices/all/style.fish
@@ -0,0 +1,36 @@
+function style
+ set -g _tide_configure_current_options
+
+ _tide_title 'Prompt Style'
+
+ _tide_option 1 Lean
+ _load_config lean
+ _tide_display_prompt
+
+ _tide_option 2 Classic
+ _load_config classic
+ _tide_display_prompt
+
+ _tide_option 3 Rainbow
+ _load_config rainbow
+ _tide_display_prompt
+
+ _tide_menu (status function) --no-restart
+ switch $_tide_selected_option
+ case Lean
+ _load_config lean
+ set -g _tide_configure_style lean
+ case Classic
+ _load_config classic
+ set -g _tide_configure_style classic
+ case Rainbow
+ _load_config rainbow
+ set -g _tide_configure_style rainbow
+ end
+ _next_choice all/prompt_colors
+end
+
+function _load_config -a name
+ string replace -r '^' 'set -g fake_' <(status dirname)/../../icons.fish | source
+ string replace -r '^' 'set -g fake_' <(status dirname)/../../configs/$name.fish | source
+end
diff --git a/dev/fish-plugins/tide/functions/tide/configure/choices/all/transient.fish b/dev/fish-plugins/tide/functions/tide/configure/choices/all/transient.fish
new file mode 100644
index 000000000..df2b6b62c
--- /dev/null
+++ b/dev/fish-plugins/tide/functions/tide/configure/choices/all/transient.fish
@@ -0,0 +1,22 @@
+function transient
+ _tide_title 'Enable transient prompt?'
+
+ _tide_option 1 No
+ _tide_display_prompt
+ _tide_display_prompt
+ _tide_display_prompt
+
+ _tide_option 2 Yes
+ _configure_transient= _tide_display_prompt
+ _configure_transient= _tide_display_prompt
+ _tide_display_prompt
+
+ _tide_menu (status function)
+ switch $_tide_selected_option
+ case No
+ set fake_tide_prompt_transient_enabled false
+ case Yes
+ set fake_tide_prompt_transient_enabled true
+ end
+ _next_choice all/finish
+end
diff --git a/dev/fish-plugins/tide/functions/tide/configure/choices/classic/classic_prompt_color.fish b/dev/fish-plugins/tide/functions/tide/configure/choices/classic/classic_prompt_color.fish
new file mode 100644
index 000000000..77ceeaf28
--- /dev/null
+++ b/dev/fish-plugins/tide/functions/tide/configure/choices/classic/classic_prompt_color.fish
@@ -0,0 +1,38 @@
+function classic_prompt_color
+ _tide_title 'Prompt Color'
+
+ _tide_option 1 Lightest
+ _set_all_items_bg_color 585858
+ _tide_display_prompt
+
+ _tide_option 2 Light
+ _set_all_items_bg_color 444444
+ _tide_display_prompt
+
+ _tide_option 3 Dark
+ _set_all_items_bg_color 303030
+ _tide_display_prompt
+
+ _tide_option 4 Darkest
+ _set_all_items_bg_color 1C1C1C
+ _tide_display_prompt
+
+ _tide_menu (status function)
+ switch $_tide_selected_option
+ case Lightest
+ _set_all_items_bg_color 585858
+ case Light
+ _set_all_items_bg_color 444444
+ case Dark
+ _set_all_items_bg_color 303030
+ case Darkest
+ _set_all_items_bg_color 1C1C1C
+ end
+ _next_choice all/show_time
+end
+
+function _set_all_items_bg_color -a color
+ for var in (set --names | string match -r "fake_.*_bg_color.*")
+ set $var $color
+ end
+end
diff --git a/dev/fish-plugins/tide/functions/tide/configure/choices/classic/classic_prompt_separators.fish b/dev/fish-plugins/tide/functions/tide/configure/choices/classic/classic_prompt_separators.fish
new file mode 100644
index 000000000..20af6cda0
--- /dev/null
+++ b/dev/fish-plugins/tide/functions/tide/configure/choices/classic/classic_prompt_separators.fish
@@ -0,0 +1,40 @@
+function classic_prompt_separators
+ _tide_title 'Prompt Separators'
+
+ _tide_option 1 Angled
+ set -g fake_tide_left_prompt_separator_same_color
+ set -g fake_tide_right_prompt_separator_same_color
+ _tide_display_prompt
+
+ _tide_option 2 Vertical
+ set -g fake_tide_left_prompt_separator_same_color │
+ set -g fake_tide_right_prompt_separator_same_color │
+ _tide_display_prompt
+
+ _tide_option 3 Slanted
+ set -g fake_tide_left_prompt_separator_same_color ╱
+ set -g fake_tide_right_prompt_separator_same_color ╱
+ _tide_display_prompt
+
+ _tide_option 4 Round
+ set -g fake_tide_left_prompt_separator_same_color
+ set -g fake_tide_right_prompt_separator_same_color
+ _tide_display_prompt
+
+ _tide_menu (status function)
+ switch $_tide_selected_option
+ case Angled
+ set -g fake_tide_left_prompt_separator_same_color
+ set -g fake_tide_right_prompt_separator_same_color
+ case Vertical
+ set -g fake_tide_left_prompt_separator_same_color │
+ set -g fake_tide_right_prompt_separator_same_color │
+ case Slanted
+ set -g fake_tide_left_prompt_separator_same_color ╱
+ set -g fake_tide_right_prompt_separator_same_color ╱
+ case Round
+ set -g fake_tide_left_prompt_separator_same_color
+ set -g fake_tide_right_prompt_separator_same_color
+ end
+ _next_choice powerline/powerline_prompt_heads
+end
diff --git a/dev/fish-plugins/tide/functions/tide/configure/choices/lean/lean_prompt_height.fish b/dev/fish-plugins/tide/functions/tide/configure/choices/lean/lean_prompt_height.fish
new file mode 100644
index 000000000..2077241de
--- /dev/null
+++ b/dev/fish-plugins/tide/functions/tide/configure/choices/lean/lean_prompt_height.fish
@@ -0,0 +1,26 @@
+function lean_prompt_height
+ _tide_title 'Prompt Height'
+
+ _tide_option 1 'One line'
+ _tide_find_and_remove newline fake_tide_left_prompt_items
+ set -g fake_tide_left_prompt_suffix ''
+ _tide_display_prompt
+
+ _tide_option 2 'Two lines'
+ set -g fake_tide_left_prompt_items $fake_tide_left_prompt_items[1..-2] newline $fake_tide_left_prompt_items[-1]
+ set -g fake_tide_left_prompt_suffix ' '
+ _tide_display_prompt
+
+ _tide_menu (status function)
+ switch $_tide_selected_option
+ case 'One line'
+ _tide_find_and_remove newline fake_tide_left_prompt_items
+ set fake_tide_left_prompt_suffix ''
+ _next_choice all/prompt_connection_andor_frame_color
+ case 'Two lines'
+ _tide_find_and_remove newline fake_tide_left_prompt_items
+ set -g fake_tide_left_prompt_items $fake_tide_left_prompt_items[1..-2] newline $fake_tide_left_prompt_items[-1]
+ set -g fake_tide_left_prompt_suffix ' '
+ _next_choice all/prompt_connection
+ end
+end
diff --git a/dev/fish-plugins/tide/functions/tide/configure/choices/powerline/powerline_prompt_heads.fish b/dev/fish-plugins/tide/functions/tide/configure/choices/powerline/powerline_prompt_heads.fish
new file mode 100644
index 000000000..0030cadfb
--- /dev/null
+++ b/dev/fish-plugins/tide/functions/tide/configure/choices/powerline/powerline_prompt_heads.fish
@@ -0,0 +1,32 @@
+function powerline_prompt_heads
+ _tide_title 'Prompt Heads'
+
+ _tide_option 1 Sharp
+ set -g fake_tide_left_prompt_suffix
+ set -g fake_tide_right_prompt_prefix
+ _tide_display_prompt
+
+ _tide_option 2 Slanted
+ set -g fake_tide_left_prompt_suffix
+ set -g fake_tide_right_prompt_prefix
+ _tide_display_prompt
+
+ _tide_option 3 Round
+ set -g fake_tide_left_prompt_suffix
+ set -g fake_tide_right_prompt_prefix
+ _tide_display_prompt
+
+ _tide_menu (status function)
+ switch $_tide_selected_option
+ case Sharp
+ set -g fake_tide_left_prompt_suffix
+ set -g fake_tide_right_prompt_prefix
+ case Slanted
+ set -g fake_tide_left_prompt_suffix
+ set -g fake_tide_right_prompt_prefix
+ case Round
+ set -g fake_tide_left_prompt_suffix
+ set -g fake_tide_right_prompt_prefix
+ end
+ _next_choice powerline/powerline_prompt_tails
+end
diff --git a/dev/fish-plugins/tide/functions/tide/configure/choices/powerline/powerline_prompt_style.fish b/dev/fish-plugins/tide/functions/tide/configure/choices/powerline/powerline_prompt_style.fish
new file mode 100644
index 000000000..b37475797
--- /dev/null
+++ b/dev/fish-plugins/tide/functions/tide/configure/choices/powerline/powerline_prompt_style.fish
@@ -0,0 +1,51 @@
+function powerline_prompt_style
+ _tide_title 'Powerline Prompt Style'
+
+ _tide_option 1 'One line'
+ _tide_find_and_remove newline fake_tide_left_prompt_items
+ _tide_find_and_remove character fake_tide_left_prompt_items
+ set fake_tide_left_prompt_frame_enabled false
+ set fake_tide_right_prompt_frame_enabled false
+ _tide_display_prompt
+
+ set -a fake_tide_left_prompt_items newline
+
+ _tide_option 2 'Two lines, character'
+ set -a fake_tide_left_prompt_items character
+ set fake_tide_left_prompt_frame_enabled false
+ set fake_tide_right_prompt_frame_enabled false
+ _tide_display_prompt
+
+ _tide_option 3 'Two lines, frame'
+ _tide_find_and_remove character fake_tide_left_prompt_items
+ set fake_tide_left_prompt_frame_enabled true
+ set fake_tide_right_prompt_frame_enabled true
+ _tide_display_prompt
+
+ _tide_option 4 'Two lines, character and frame'
+ set -a fake_tide_left_prompt_items character
+ set fake_tide_left_prompt_frame_enabled true
+ set fake_tide_right_prompt_frame_enabled true
+ _tide_display_prompt
+
+ _tide_menu (status function)
+ switch $_tide_selected_option
+ case 'One line'
+ _tide_find_and_remove newline fake_tide_left_prompt_items
+ _tide_find_and_remove character fake_tide_left_prompt_items
+ set fake_tide_left_prompt_frame_enabled false
+ set fake_tide_right_prompt_frame_enabled false
+ _next_choice all/prompt_connection_andor_frame_color
+ case 'Two lines, character'
+ set fake_tide_left_prompt_frame_enabled false
+ set fake_tide_right_prompt_frame_enabled false
+ _next_choice all/prompt_connection
+ case 'Two lines, frame'
+ _tide_find_and_remove character fake_tide_left_prompt_items
+ set fake_tide_left_prompt_frame_enabled true
+ set fake_tide_right_prompt_frame_enabled true
+ _next_choice all/prompt_connection
+ case 'Two lines, character and frame'
+ _next_choice all/prompt_connection
+ end
+end
diff --git a/dev/fish-plugins/tide/functions/tide/configure/choices/powerline/powerline_prompt_tails.fish b/dev/fish-plugins/tide/functions/tide/configure/choices/powerline/powerline_prompt_tails.fish
new file mode 100644
index 000000000..6b1574b8f
--- /dev/null
+++ b/dev/fish-plugins/tide/functions/tide/configure/choices/powerline/powerline_prompt_tails.fish
@@ -0,0 +1,40 @@
+function powerline_prompt_tails
+ _tide_title 'Prompt Tails'
+
+ _tide_option 1 Flat
+ set -g fake_tide_left_prompt_prefix ''
+ set -g fake_tide_right_prompt_suffix ''
+ _tide_display_prompt
+
+ _tide_option 2 Sharp
+ set -g fake_tide_left_prompt_prefix
+ set -g fake_tide_right_prompt_suffix
+ _tide_display_prompt
+
+ _tide_option 3 Slanted
+ set -g fake_tide_left_prompt_prefix
+ set -g fake_tide_right_prompt_suffix
+ _tide_display_prompt
+
+ _tide_option 4 Round
+ set -g fake_tide_left_prompt_prefix
+ set -g fake_tide_right_prompt_suffix
+ _tide_display_prompt
+
+ _tide_menu (status function)
+ switch $_tide_selected_option
+ case Flat
+ set -g fake_tide_left_prompt_prefix ''
+ set -g fake_tide_right_prompt_suffix ''
+ case Sharp
+ set -g fake_tide_left_prompt_prefix
+ set -g fake_tide_right_prompt_suffix
+ case Slanted
+ set -g fake_tide_left_prompt_prefix
+ set -g fake_tide_right_prompt_suffix
+ case Round
+ set -g fake_tide_left_prompt_prefix
+ set -g fake_tide_right_prompt_suffix
+ end
+ _next_choice powerline/powerline_prompt_style
+end
diff --git a/dev/fish-plugins/tide/functions/tide/configure/choices/powerline/powerline_right_prompt_frame.fish b/dev/fish-plugins/tide/functions/tide/configure/choices/powerline/powerline_right_prompt_frame.fish
new file mode 100644
index 000000000..a2d0a5b55
--- /dev/null
+++ b/dev/fish-plugins/tide/functions/tide/configure/choices/powerline/powerline_right_prompt_frame.fish
@@ -0,0 +1,20 @@
+function powerline_right_prompt_frame
+ _tide_title 'Right Prompt Frame'
+
+ _tide_option 1 No
+ set fake_tide_right_prompt_frame_enabled false
+ _tide_display_prompt
+
+ _tide_option 2 Yes
+ set fake_tide_right_prompt_frame_enabled true
+ _tide_display_prompt
+
+ _tide_menu (status function)
+ switch $_tide_selected_option
+ case No
+ set fake_tide_right_prompt_frame_enabled false
+ case Yes
+ set fake_tide_right_prompt_frame_enabled true
+ end
+ _next_choice all/prompt_connection_andor_frame_color
+end
diff --git a/dev/fish-plugins/tide/functions/tide/configure/choices/rainbow/rainbow_prompt_separators.fish b/dev/fish-plugins/tide/functions/tide/configure/choices/rainbow/rainbow_prompt_separators.fish
new file mode 100644
index 000000000..14b931926
--- /dev/null
+++ b/dev/fish-plugins/tide/functions/tide/configure/choices/rainbow/rainbow_prompt_separators.fish
@@ -0,0 +1,40 @@
+function rainbow_prompt_separators
+ _tide_title 'Prompt Separators'
+
+ _tide_option 1 Angled
+ set -g fake_tide_left_prompt_separator_diff_color
+ set -g fake_tide_right_prompt_separator_diff_color
+ _tide_display_prompt
+
+ _tide_option 2 Vertical
+ set -g fake_tide_left_prompt_separator_diff_color ''
+ set -g fake_tide_right_prompt_separator_diff_color ''
+ _tide_display_prompt
+
+ _tide_option 3 Slanted
+ set -g fake_tide_left_prompt_separator_diff_color ''
+ set -g fake_tide_right_prompt_separator_diff_color ''
+ _tide_display_prompt
+
+ _tide_option 4 Round
+ set -g fake_tide_left_prompt_separator_diff_color ''
+ set -g fake_tide_right_prompt_separator_diff_color ''
+ _tide_display_prompt
+
+ _tide_menu (status function)
+ switch $_tide_selected_option
+ case Angled
+ set -g fake_tide_left_prompt_separator_diff_color
+ set -g fake_tide_right_prompt_separator_diff_color
+ case Vertical
+ set -g fake_tide_left_prompt_separator_diff_color ''
+ set -g fake_tide_right_prompt_separator_diff_color ''
+ case Slanted
+ set -g fake_tide_left_prompt_separator_diff_color ''
+ set -g fake_tide_right_prompt_separator_diff_color ''
+ case Round
+ set -g fake_tide_left_prompt_separator_diff_color ''
+ set -g fake_tide_right_prompt_separator_diff_color ''
+ end
+ _next_choice powerline/powerline_prompt_heads
+end
diff --git a/dev/fish-plugins/tide/functions/tide/configure/configs/classic.fish b/dev/fish-plugins/tide/functions/tide/configure/configs/classic.fish
new file mode 100644
index 000000000..6f09bbaff
--- /dev/null
+++ b/dev/fish-plugins/tide/functions/tide/configure/configs/classic.fish
@@ -0,0 +1,118 @@
+tide_aws_bg_color 444444
+tide_aws_color FF9900
+tide_bun_bg_color 14151A
+tide_bun_color FBF0DF
+tide_character_color $_tide_color_green
+tide_character_color_failure FF0000
+tide_cmd_duration_bg_color 444444
+tide_cmd_duration_color 87875F
+tide_cmd_duration_decimals 0
+tide_cmd_duration_threshold 3000
+tide_context_always_display false
+tide_context_bg_color 444444
+tide_context_color_default D7AF87
+tide_context_color_root $_tide_color_gold
+tide_context_color_ssh D7AF87
+tide_context_hostname_parts 1
+tide_crystal_bg_color 444444
+tide_crystal_color FFFFFF
+tide_direnv_bg_color 444444
+tide_direnv_bg_color_denied 444444
+tide_direnv_color $_tide_color_gold
+tide_direnv_color_denied FF0000
+tide_distrobox_bg_color 444444
+tide_distrobox_color FF00FF
+tide_docker_bg_color 444444
+tide_docker_color 2496ED
+tide_docker_default_contexts default colima
+tide_elixir_bg_color 444444
+tide_elixir_color 4E2A8E
+tide_gcloud_bg_color 444444
+tide_gcloud_color 4285F4
+tide_git_bg_color 444444
+tide_git_bg_color_unstable 444444
+tide_git_bg_color_urgent 444444
+tide_git_color_branch $_tide_color_green
+tide_git_color_conflicted FF0000
+tide_git_color_dirty $_tide_color_gold
+tide_git_color_operation FF0000
+tide_git_color_staged $_tide_color_gold
+tide_git_color_stash $_tide_color_green
+tide_git_color_untracked $_tide_color_light_blue
+tide_git_color_upstream $_tide_color_green
+tide_git_truncation_length 24
+tide_git_truncation_strategy
+tide_go_bg_color 444444
+tide_go_color 00ACD7
+tide_java_bg_color 444444
+tide_java_color ED8B00
+tide_jobs_bg_color 444444
+tide_jobs_color $_tide_color_dark_green
+tide_jobs_number_threshold 1000
+tide_kubectl_bg_color 444444
+tide_kubectl_color 326CE5
+tide_left_prompt_frame_enabled true
+tide_left_prompt_items pwd git newline
+tide_left_prompt_prefix ''
+tide_left_prompt_separator_diff_color
+tide_left_prompt_separator_same_color
+tide_left_prompt_suffix
+tide_nix_shell_bg_color 444444
+tide_nix_shell_color 7EBAE4
+tide_node_bg_color 444444
+tide_node_color 44883E
+tide_os_bg_color 444444
+tide_os_color EEEEEE
+tide_php_bg_color 444444
+tide_php_color 617CBE
+tide_private_mode_bg_color 444444
+tide_private_mode_color FFFFFF
+tide_prompt_add_newline_before true
+tide_prompt_color_frame_and_connection 6C6C6C
+tide_prompt_color_separator_same_color 949494
+tide_prompt_min_cols 34
+tide_prompt_pad_items true
+tide_prompt_transient_enabled false
+tide_pulumi_bg_color 444444
+tide_pulumi_color F7BF2A
+tide_pwd_bg_color 444444
+tide_pwd_color_anchors $_tide_color_light_blue
+tide_pwd_color_dirs $_tide_color_dark_blue
+tide_pwd_color_truncated_dirs 8787AF
+tide_pwd_markers .bzr .citc .git .hg .node-version .python-version .ruby-version .shorten_folder_marker .svn .terraform bun.lockb Cargo.toml composer.json CVS go.mod package.json build.zig
+tide_python_bg_color 444444
+tide_python_color 00AFAF
+tide_right_prompt_frame_enabled true
+tide_right_prompt_items status cmd_duration context jobs direnv bun node python rustc java php pulumi ruby go gcloud kubectl distrobox toolbox terraform aws nix_shell crystal elixir zig
+tide_right_prompt_prefix
+tide_right_prompt_separator_diff_color
+tide_right_prompt_separator_same_color
+tide_right_prompt_suffix ''
+tide_ruby_bg_color 444444
+tide_ruby_color B31209
+tide_rustc_bg_color 444444
+tide_rustc_color F74C00
+tide_shlvl_bg_color 444444
+tide_shlvl_color d78700
+tide_shlvl_threshold 1
+tide_status_bg_color 444444
+tide_status_bg_color_failure 444444
+tide_status_color $_tide_color_dark_green
+tide_status_color_failure D70000
+tide_terraform_bg_color 444444
+tide_terraform_color 844FBA
+tide_time_bg_color 444444
+tide_time_color 5F8787
+tide_time_format %T
+tide_toolbox_bg_color 444444
+tide_toolbox_color 613583
+tide_vi_mode_bg_color_default 444444
+tide_vi_mode_bg_color_insert 444444
+tide_vi_mode_bg_color_replace 444444
+tide_vi_mode_bg_color_visual 444444
+tide_vi_mode_color_default 949494
+tide_vi_mode_color_insert 87AFAF
+tide_vi_mode_color_replace 87AF87
+tide_vi_mode_color_visual FF8700
+tide_zig_bg_color 444444
+tide_zig_color F7A41D
diff --git a/dev/fish-plugins/tide/functions/tide/configure/configs/classic_16color.fish b/dev/fish-plugins/tide/functions/tide/configure/configs/classic_16color.fish
new file mode 100644
index 000000000..fe730c5f1
--- /dev/null
+++ b/dev/fish-plugins/tide/functions/tide/configure/configs/classic_16color.fish
@@ -0,0 +1,91 @@
+tide_aws_bg_color black
+tide_aws_color yellow
+tide_bun_bg_color black
+tide_bun_color white
+tide_character_color brgreen
+tide_character_color_failure brred
+tide_cmd_duration_bg_color black
+tide_cmd_duration_color brblack
+tide_context_bg_color black
+tide_context_color_default yellow
+tide_context_color_root bryellow
+tide_context_color_ssh yellow
+tide_crystal_bg_color black
+tide_crystal_color brwhite
+tide_direnv_bg_color black
+tide_direnv_bg_color_denied black
+tide_direnv_color bryellow
+tide_direnv_color_denied brred
+tide_distrobox_bg_color black
+tide_distrobox_color brmagenta
+tide_docker_bg_color black
+tide_docker_color blue
+tide_elixir_bg_color black
+tide_elixir_color magenta
+tide_gcloud_bg_color black
+tide_gcloud_color blue
+tide_git_bg_color black
+tide_git_bg_color_unstable black
+tide_git_bg_color_urgent black
+tide_git_color_branch brgreen
+tide_git_color_conflicted brred
+tide_git_color_dirty bryellow
+tide_git_color_operation brred
+tide_git_color_staged bryellow
+tide_git_color_stash brgreen
+tide_git_color_untracked brblue
+tide_git_color_upstream brgreen
+tide_go_bg_color black
+tide_go_color brcyan
+tide_java_bg_color black
+tide_java_color yellow
+tide_jobs_bg_color black
+tide_jobs_color green
+tide_kubectl_bg_color black
+tide_kubectl_color blue
+tide_nix_shell_bg_color black
+tide_nix_shell_color brblue
+tide_node_bg_color black
+tide_node_color green
+tide_os_bg_color black
+tide_os_color brwhite
+tide_php_bg_color black
+tide_php_color blue
+tide_private_mode_bg_color black
+tide_private_mode_color brwhite
+tide_prompt_color_frame_and_connection brblack
+tide_prompt_color_separator_same_color brblack
+tide_pulumi_bg_color black
+tide_pulumi_color yellow
+tide_pwd_bg_color black
+tide_pwd_color_anchors brcyan
+tide_pwd_color_dirs cyan
+tide_pwd_color_truncated_dirs magenta
+tide_python_bg_color black
+tide_python_color cyan
+tide_ruby_bg_color black
+tide_ruby_color red
+tide_rustc_bg_color black
+tide_rustc_color red
+tide_shlvl_bg_color black
+tide_shlvl_color yellow
+tide_status_bg_color black
+tide_status_bg_color_failure black
+tide_status_color green
+tide_status_color_failure red
+tide_terraform_bg_color black
+tide_terraform_color magenta
+tide_time_bg_color black
+tide_time_color brblack
+tide_toolbox_bg_color black
+tide_toolbox_color magenta
+tide_vi_mode_bg_color_default black
+tide_vi_mode_bg_color_insert black
+tide_vi_mode_bg_color_replace black
+tide_vi_mode_bg_color_visual black
+tide_vi_mode_color_default white
+tide_vi_mode_color_insert cyan
+tide_vi_mode_color_replace green
+tide_vi_mode_color_visual yellow
+tide_zig_bg_color black
+tide_zig_color yellow
diff --git a/dev/fish-plugins/tide/functions/tide/configure/configs/lean.fish b/dev/fish-plugins/tide/functions/tide/configure/configs/lean.fish
new file mode 100644
index 000000000..db3f1d20c
--- /dev/null
+++ b/dev/fish-plugins/tide/functions/tide/configure/configs/lean.fish
@@ -0,0 +1,118 @@
+tide_aws_bg_color normal
+tide_aws_color FF9900
+tide_bun_bg_color normal
+tide_bun_color FBF0DF
+tide_character_color $_tide_color_green
+tide_character_color_failure FF0000
+tide_cmd_duration_bg_color normal
+tide_cmd_duration_color 87875F
+tide_cmd_duration_decimals 0
+tide_cmd_duration_threshold 3000
+tide_context_always_display false
+tide_context_bg_color normal
+tide_context_color_default D7AF87
+tide_context_color_root $_tide_color_gold
+tide_context_color_ssh D7AF87
+tide_context_hostname_parts 1
+tide_crystal_bg_color normal
+tide_crystal_color FFFFFF
+tide_direnv_bg_color normal
+tide_direnv_bg_color_denied normal
+tide_direnv_color $_tide_color_gold
+tide_direnv_color_denied FF0000
+tide_distrobox_bg_color normal
+tide_distrobox_color FF00FF
+tide_docker_bg_color normal
+tide_docker_color 2496ED
+tide_docker_default_contexts default colima
+tide_elixir_bg_color normal
+tide_elixir_color 4E2A8E
+tide_gcloud_bg_color normal
+tide_gcloud_color 4285F4
+tide_git_bg_color normal
+tide_git_bg_color_unstable normal
+tide_git_bg_color_urgent normal
+tide_git_color_branch $_tide_color_green
+tide_git_color_conflicted FF0000
+tide_git_color_dirty $_tide_color_gold
+tide_git_color_operation FF0000
+tide_git_color_staged $_tide_color_gold
+tide_git_color_stash $_tide_color_green
+tide_git_color_untracked $_tide_color_light_blue
+tide_git_color_upstream $_tide_color_green
+tide_git_truncation_length 24
+tide_git_truncation_strategy
+tide_go_bg_color normal
+tide_go_color 00ACD7
+tide_java_bg_color normal
+tide_java_color ED8B00
+tide_jobs_bg_color normal
+tide_jobs_color $_tide_color_dark_green
+tide_jobs_number_threshold 1000
+tide_kubectl_bg_color normal
+tide_kubectl_color 326CE5
+tide_left_prompt_frame_enabled false
+tide_left_prompt_items pwd git newline character
+tide_left_prompt_prefix ''
+tide_left_prompt_separator_diff_color ' '
+tide_left_prompt_separator_same_color ' '
+tide_left_prompt_suffix ' '
+tide_nix_shell_bg_color normal
+tide_nix_shell_color 7EBAE4
+tide_node_bg_color normal
+tide_node_color 44883E
+tide_os_bg_color normal
+tide_os_color normal
+tide_php_bg_color normal
+tide_php_color 617CBE
+tide_private_mode_bg_color normal
+tide_private_mode_color FFFFFF
+tide_prompt_add_newline_before true
+tide_prompt_color_frame_and_connection 6C6C6C
+tide_prompt_color_separator_same_color 949494
+tide_prompt_min_cols 34
+tide_prompt_pad_items false
+tide_prompt_transient_enabled false
+tide_pulumi_bg_color normal
+tide_pulumi_color F7BF2A
+tide_pwd_bg_color normal
+tide_pwd_color_anchors $_tide_color_light_blue
+tide_pwd_color_dirs $_tide_color_dark_blue
+tide_pwd_color_truncated_dirs 8787AF
+tide_pwd_markers .bzr .citc .git .hg .node-version .python-version .ruby-version .shorten_folder_marker .svn .terraform bun.lockb Cargo.toml composer.json CVS go.mod package.json build.zig
+tide_python_bg_color normal
+tide_python_color 00AFAF
+tide_right_prompt_frame_enabled false
+tide_right_prompt_items status cmd_duration context jobs direnv bun node python rustc java php pulumi ruby go gcloud kubectl distrobox toolbox terraform aws nix_shell crystal elixir zig
+tide_right_prompt_prefix ' '
+tide_right_prompt_separator_diff_color ' '
+tide_right_prompt_separator_same_color ' '
+tide_right_prompt_suffix ''
+tide_ruby_bg_color normal
+tide_ruby_color B31209
+tide_rustc_bg_color normal
+tide_rustc_color F74C00
+tide_shlvl_bg_color normal
+tide_shlvl_color d78700
+tide_shlvl_threshold 1
+tide_status_bg_color normal
+tide_status_bg_color_failure normal
+tide_status_color $_tide_color_dark_green
+tide_status_color_failure D70000
+tide_terraform_bg_color normal
+tide_terraform_color 844FBA
+tide_time_bg_color normal
+tide_time_color 5F8787
+tide_time_format %T
+tide_toolbox_bg_color normal
+tide_toolbox_color 613583
+tide_vi_mode_bg_color_default normal
+tide_vi_mode_bg_color_insert normal
+tide_vi_mode_bg_color_replace normal
+tide_vi_mode_bg_color_visual normal
+tide_vi_mode_color_default 949494
+tide_vi_mode_color_insert 87AFAF
+tide_vi_mode_color_replace 87AF87
+tide_vi_mode_color_visual FF8700
+tide_zig_bg_color normal
+tide_zig_color F7A41D
diff --git a/dev/fish-plugins/tide/functions/tide/configure/configs/lean_16color.fish b/dev/fish-plugins/tide/functions/tide/configure/configs/lean_16color.fish
new file mode 100644
index 000000000..699ce45a6
--- /dev/null
+++ b/dev/fish-plugins/tide/functions/tide/configure/configs/lean_16color.fish
@@ -0,0 +1,91 @@
+tide_aws_bg_color normal
+tide_aws_color yellow
+tide_bun_bg_color normal
+tide_bun_color white
+tide_character_color brgreen
+tide_character_color_failure brred
+tide_cmd_duration_bg_color normal
+tide_cmd_duration_color brblack
+tide_context_bg_color normal
+tide_context_color_default yellow
+tide_context_color_root bryellow
+tide_context_color_ssh yellow
+tide_crystal_bg_color normal
+tide_crystal_color brwhite
+tide_direnv_bg_color normal
+tide_direnv_bg_color_denied normal
+tide_direnv_color bryellow
+tide_direnv_color_denied brred
+tide_distrobox_bg_color normal
+tide_distrobox_color brmagenta
+tide_docker_bg_color normal
+tide_docker_color blue
+tide_elixir_bg_color normal
+tide_elixir_color magenta
+tide_gcloud_bg_color normal
+tide_gcloud_color blue
+tide_git_bg_color normal
+tide_git_bg_color_unstable normal
+tide_git_bg_color_urgent normal
+tide_git_color_branch brgreen
+tide_git_color_conflicted brred
+tide_git_color_dirty bryellow
+tide_git_color_operation brred
+tide_git_color_staged bryellow
+tide_git_color_stash brgreen
+tide_git_color_untracked brblue
+tide_git_color_upstream brgreen
+tide_go_bg_color normal
+tide_go_color brcyan
+tide_java_bg_color normal
+tide_java_color yellow
+tide_jobs_bg_color normal
+tide_jobs_color green
+tide_kubectl_bg_color normal
+tide_kubectl_color blue
+tide_nix_shell_bg_color normal
+tide_nix_shell_color brblue
+tide_node_bg_color normal
+tide_node_color green
+tide_os_bg_color normal
+tide_os_color brwhite
+tide_php_bg_color normal
+tide_php_color blue
+tide_private_mode_bg_color normal
+tide_private_mode_color brwhite
+tide_prompt_color_frame_and_connection brblack
+tide_prompt_color_separator_same_color brblack
+tide_pulumi_bg_color normal
+tide_pulumi_color yellow
+tide_pwd_bg_color normal
+tide_pwd_color_anchors brcyan
+tide_pwd_color_dirs cyan
+tide_pwd_color_truncated_dirs magenta
+tide_python_bg_color normal
+tide_python_color cyan
+tide_ruby_bg_color normal
+tide_ruby_color red
+tide_rustc_bg_color normal
+tide_rustc_color red
+tide_shlvl_bg_color normal
+tide_shlvl_color yellow
+tide_status_bg_color normal
+tide_status_bg_color_failure normal
+tide_status_color green
+tide_status_color_failure red
+tide_terraform_bg_color normal
+tide_terraform_color magenta
+tide_time_bg_color normal
+tide_time_color brblack
+tide_toolbox_bg_color normal
+tide_toolbox_color magenta
+tide_vi_mode_bg_color_default normal
+tide_vi_mode_bg_color_insert normal
+tide_vi_mode_bg_color_replace normal
+tide_vi_mode_bg_color_visual normal
+tide_vi_mode_color_default white
+tide_vi_mode_color_insert cyan
+tide_vi_mode_color_replace green
+tide_vi_mode_color_visual yellow
+tide_zig_bg_color normal
+tide_zig_color yellow
diff --git a/dev/fish-plugins/tide/functions/tide/configure/configs/rainbow.fish b/dev/fish-plugins/tide/functions/tide/configure/configs/rainbow.fish
new file mode 100644
index 000000000..79e9cb4ab
--- /dev/null
+++ b/dev/fish-plugins/tide/functions/tide/configure/configs/rainbow.fish
@@ -0,0 +1,118 @@
+tide_aws_bg_color FF9900
+tide_aws_color 232F3E
+tide_bun_bg_color FBF0DF
+tide_bun_color 14151A
+tide_character_color $_tide_color_green
+tide_character_color_failure FF0000
+tide_cmd_duration_bg_color C4A000
+tide_cmd_duration_color 000000
+tide_cmd_duration_decimals 0
+tide_cmd_duration_threshold 3000
+tide_context_always_display false
+tide_context_bg_color 444444
+tide_context_color_default D7AF87
+tide_context_color_root $_tide_color_gold
+tide_context_color_ssh D7AF87
+tide_context_hostname_parts 1
+tide_crystal_bg_color FFFFFF
+tide_crystal_color 000000
+tide_direnv_bg_color $_tide_color_gold
+tide_direnv_bg_color_denied FF0000
+tide_direnv_color 000000
+tide_direnv_color_denied 000000
+tide_distrobox_bg_color FF00FF
+tide_distrobox_color 000000
+tide_docker_bg_color 2496ED
+tide_docker_color 000000
+tide_docker_default_contexts default colima
+tide_elixir_bg_color 4E2A8E
+tide_elixir_color 000000
+tide_gcloud_bg_color 4285F4
+tide_gcloud_color 000000
+tide_git_bg_color 4E9A06
+tide_git_bg_color_unstable C4A000
+tide_git_bg_color_urgent CC0000
+tide_git_color_branch 000000
+tide_git_color_conflicted 000000
+tide_git_color_dirty 000000
+tide_git_color_operation 000000
+tide_git_color_staged 000000
+tide_git_color_stash 000000
+tide_git_color_untracked 000000
+tide_git_color_upstream 000000
+tide_git_truncation_length 24
+tide_git_truncation_strategy
+tide_go_bg_color 00ACD7
+tide_go_color 000000
+tide_java_bg_color ED8B00
+tide_java_color 000000
+tide_jobs_bg_color 444444
+tide_jobs_color 4E9A06
+tide_jobs_number_threshold 1000
+tide_kubectl_bg_color 326CE5
+tide_kubectl_color 000000
+tide_left_prompt_frame_enabled true
+tide_left_prompt_items pwd git newline
+tide_left_prompt_prefix ''
+tide_left_prompt_separator_diff_color
+tide_left_prompt_separator_same_color
+tide_left_prompt_suffix
+tide_nix_shell_bg_color 7EBAE4
+tide_nix_shell_color 000000
+tide_node_bg_color 44883E
+tide_node_color 000000
+tide_os_bg_color $os_branding_bg_color
+tide_os_color $os_branding_color
+tide_php_bg_color 617CBE
+tide_php_color 000000
+tide_private_mode_bg_color F1F3F4
+tide_private_mode_color 000000
+tide_prompt_add_newline_before true
+tide_prompt_color_frame_and_connection 6C6C6C
+tide_prompt_color_separator_same_color 949494
+tide_prompt_min_cols 34
+tide_prompt_pad_items true
+tide_prompt_transient_enabled false
+tide_pulumi_bg_color F7BF2A
+tide_pulumi_color 000000
+tide_pwd_bg_color 3465A4
+tide_pwd_color_anchors E4E4E4
+tide_pwd_color_dirs E4E4E4
+tide_pwd_color_truncated_dirs BCBCBC
+tide_pwd_markers .bzr .citc .git .hg .node-version .python-version .ruby-version .shorten_folder_marker .svn .terraform bun.lockb Cargo.toml composer.json CVS go.mod package.json build.zig
+tide_python_bg_color 444444
+tide_python_color 00AFAF
+tide_right_prompt_frame_enabled true
+tide_right_prompt_items status cmd_duration context jobs direnv bun node python rustc java php pulumi ruby go gcloud kubectl distrobox toolbox terraform aws nix_shell crystal elixir zig
+tide_right_prompt_prefix
+tide_right_prompt_separator_diff_color
+tide_right_prompt_separator_same_color
+tide_right_prompt_suffix ''
+tide_ruby_bg_color B31209
+tide_ruby_color 000000
+tide_rustc_bg_color F74C00
+tide_rustc_color 000000
+tide_shlvl_bg_color 808000
+tide_shlvl_color 000000
+tide_shlvl_threshold 1
+tide_status_bg_color 2E3436
+tide_status_bg_color_failure CC0000
+tide_status_color 4E9A06
+tide_status_color_failure FFFF00
+tide_terraform_bg_color 800080
+tide_terraform_color 000000
+tide_time_bg_color D3D7CF
+tide_time_color 000000
+tide_time_format %T
+tide_toolbox_bg_color 613583
+tide_toolbox_color 000000
+tide_vi_mode_bg_color_default 949494
+tide_vi_mode_bg_color_insert 87AFAF
+tide_vi_mode_bg_color_replace 87AF87
+tide_vi_mode_bg_color_visual FF8700
+tide_vi_mode_color_default 000000
+tide_vi_mode_color_insert 000000
+tide_vi_mode_color_replace 000000
+tide_vi_mode_color_visual 000000
+tide_zig_bg_color F7A41D
+tide_zig_color 000000
diff --git a/dev/fish-plugins/tide/functions/tide/configure/configs/rainbow_16color.fish b/dev/fish-plugins/tide/functions/tide/configure/configs/rainbow_16color.fish
new file mode 100644
index 000000000..5d206115d
--- /dev/null
+++ b/dev/fish-plugins/tide/functions/tide/configure/configs/rainbow_16color.fish
@@ -0,0 +1,95 @@
+tide_aws_bg_color yellow
+tide_aws_color brblack
+tide_bun_bg_color white
+tide_bun_color black
+tide_character_color brgreen
+tide_character_color_failure brred
+tide_cmd_duration_bg_color yellow
+tide_cmd_duration_color black
+tide_context_bg_color brblack
+tide_context_color_default yellow
+tide_context_color_root yellow
+tide_context_color_ssh yellow
+tide_crystal_bg_color brwhite
+tide_crystal_color black
+tide_direnv_bg_color bryellow
+tide_direnv_bg_color_denied brred
+tide_direnv_color black
+tide_direnv_color_denied black
+tide_distrobox_bg_color brmagenta
+tide_distrobox_color black
+tide_docker_bg_color blue
+tide_docker_color black
+tide_elixir_bg_color magenta
+tide_elixir_color black
+tide_gcloud_bg_color blue
+tide_gcloud_color black
+tide_git_bg_color green
+tide_git_bg_color_unstable yellow
+tide_git_bg_color_urgent red
+tide_git_color_branch black
+tide_git_color_conflicted black
+tide_git_color_dirty black
+tide_git_color_operation black
+tide_git_color_staged black
+tide_git_color_stash black
+tide_git_color_untracked black
+tide_git_color_upstream black
+tide_go_bg_color brcyan
+tide_go_color black
+tide_java_bg_color yellow
+tide_java_color black
+tide_jobs_bg_color brblack
+tide_jobs_color green
+tide_kubectl_bg_color blue
+tide_kubectl_color black
+tide_nix_shell_bg_color brblue
+tide_nix_shell_color black
+tide_node_bg_color green
+tide_node_color black
+tide_os_bg_color white
+tide_os_color black
+tide_php_bg_color blue
+tide_php_color black
+tide_private_mode_bg_color brwhite
+tide_private_mode_color black
+tide_prompt_color_frame_and_connection brblack
+tide_prompt_color_separator_same_color brblack
+tide_pulumi_bg_color yellow
+tide_pulumi_color black
+tide_pwd_bg_color blue
+tide_pwd_color_anchors brwhite
+tide_pwd_color_dirs brwhite
+tide_pwd_color_truncated_dirs white
+tide_python_bg_color brblack
+tide_python_color cyan
+tide_ruby_bg_color red
+tide_ruby_color black
+tide_rustc_bg_color red
+tide_rustc_color black
+tide_shlvl_bg_color yellow
+tide_shlvl_color black
+tide_status_bg_color black
+tide_status_bg_color_failure red
+tide_status_color green
+tide_status_color_failure bryellow
+tide_terraform_bg_color magenta
+tide_terraform_color black
+tide_time_bg_color white
+tide_time_color black
+tide_toolbox_bg_color magenta
+tide_toolbox_color black
+tide_vi_mode_bg_color_default white
+tide_vi_mode_bg_color_insert cyan
+tide_vi_mode_bg_color_replace green
+tide_vi_mode_bg_color_visual yellow
+tide_vi_mode_color_default black
+tide_vi_mode_color_insert black
+tide_vi_mode_color_replace black
+tide_vi_mode_color_visual black
+tide_vi_mode_icon_default D
+tide_vi_mode_icon_insert I
+tide_vi_mode_icon_replace R
+tide_vi_mode_icon_visual V
+tide_zig_bg_color yellow
+tide_zig_color black
diff --git a/dev/fish-plugins/tide/functions/tide/configure/functions/_fake_tide_cache_variables.fish b/dev/fish-plugins/tide/functions/tide/configure/functions/_fake_tide_cache_variables.fish
new file mode 100644
index 000000000..4661ab154
--- /dev/null
+++ b/dev/fish-plugins/tide/functions/tide/configure/functions/_fake_tide_cache_variables.fish
@@ -0,0 +1,41 @@
+function _fake_tide_cache_variables
+ # pwd
+ set_color -o $fake_tide_pwd_color_anchors | read -gx _fake_tide_color_anchors
+ set -gx _fake_tide_color_truncated_dirs "$(set_color $fake_tide_pwd_color_truncated_dirs)"
+ set -gx _fake_tide_reset_to_color_dirs (set_color normal -b $fake_tide_pwd_bg_color; set_color $fake_tide_pwd_color_dirs)
+
+ # git
+ contains git $fake_tide_left_prompt_items $fake_tide_right_prompt_items &&
+ set -gx _fake_tide_location_color "$(set_color $fake_tide_git_color_branch)"
+
+ # private_mode
+ if contains private_mode $fake_tide_left_prompt_items $fake_tide_right_prompt_items && test -n "$fish_private_mode"
+ set -gx _fake_tide_private_mode
+ else
+ set -e _fake_tide_private_mode
+ end
+
+ # Same-color-separator color
+ set -gx _fake_tide_color_separator_same_color "$(set_color $fake_tide_prompt_color_separator_same_color)"
+
+ # two line prompt
+ if contains newline $fake_tide_left_prompt_items
+ set_color $fake_tide_prompt_color_frame_and_connection -b normal | read -gx _fake_tide_prompt_and_frame_color
+ else
+ set -e _fake_tide_prompt_and_frame_color
+ end
+
+ # newline before
+ if test "$fake_tide_prompt_add_newline_before" = true
+ set -g _fake_tide_add_newline ''
+ else
+ set -e _fake_tide_add_newline
+ end
+
+ # item padding
+ if test "$fake_tide_prompt_pad_items" = true
+ set -gx _fake_tide_pad ' '
+ else
+ set -e _fake_tide_pad
+ end
+end
diff --git a/dev/fish-plugins/tide/functions/tide/configure/functions/_fake_tide_item_character.fish b/dev/fish-plugins/tide/functions/tide/configure/functions/_fake_tide_item_character.fish
new file mode 100644
index 000000000..94892eca4
--- /dev/null
+++ b/dev/fish-plugins/tide/functions/tide/configure/functions/_fake_tide_item_character.fish
@@ -0,0 +1,8 @@
+function _fake_tide_item_character
+ set_color $fake_tide_character_color
+ if contains newline $fake_tide_left_prompt_items || set -q _configure_transient
+ echo -ns $fake_tide_character_icon
+ else
+ echo -ns ' '$fake_tide_character_icon
+ end
+end
diff --git a/dev/fish-plugins/tide/functions/tide/configure/functions/_fake_tide_item_cmd_duration.fish b/dev/fish-plugins/tide/functions/tide/configure/functions/_fake_tide_item_cmd_duration.fish
new file mode 100644
index 000000000..5aa13b2b8
--- /dev/null
+++ b/dev/fish-plugins/tide/functions/tide/configure/functions/_fake_tide_item_cmd_duration.fish
@@ -0,0 +1,3 @@
+function _fake_tide_item_cmd_duration
+ _fake_tide_print_item cmd_duration $fake_tide_cmd_duration_icon' ' 5s
+end
diff --git a/dev/fish-plugins/tide/functions/tide/configure/functions/_fake_tide_item_git.fish b/dev/fish-plugins/tide/functions/tide/configure/functions/_fake_tide_item_git.fish
new file mode 100644
index 000000000..fb5b957dc
--- /dev/null
+++ b/dev/fish-plugins/tide/functions/tide/configure/functions/_fake_tide_item_git.fish
@@ -0,0 +1,3 @@
+function _fake_tide_item_git
+ _fake_tide_print_item git (set_color $fake_tide_git_color_branch) $fake_tide_git_icon' ' main
+end
diff --git a/dev/fish-plugins/tide/functions/tide/configure/functions/_fake_tide_item_newline.fish b/dev/fish-plugins/tide/functions/tide/configure/functions/_fake_tide_item_newline.fish
new file mode 100644
index 000000000..c614bab29
--- /dev/null
+++ b/dev/fish-plugins/tide/functions/tide/configure/functions/_fake_tide_item_newline.fish
@@ -0,0 +1,5 @@
+function _fake_tide_item_newline
+ set_color $prev_bg_color -b normal
+ var=fake_tide_"$_fake_tide_side"_prompt_suffix echo $$var
+ set -g add_prefix
+end
diff --git a/dev/fish-plugins/tide/functions/tide/configure/functions/_fake_tide_item_os.fish b/dev/fish-plugins/tide/functions/tide/configure/functions/_fake_tide_item_os.fish
new file mode 100644
index 000000000..5255721f5
--- /dev/null
+++ b/dev/fish-plugins/tide/functions/tide/configure/functions/_fake_tide_item_os.fish
@@ -0,0 +1,3 @@
+function _fake_tide_item_os
+ _fake_tide_print_item os $fake_tide_os_icon
+end
diff --git a/dev/fish-plugins/tide/functions/tide/configure/functions/_fake_tide_item_time.fish b/dev/fish-plugins/tide/functions/tide/configure/functions/_fake_tide_item_time.fish
new file mode 100644
index 000000000..bbce0b5aa
--- /dev/null
+++ b/dev/fish-plugins/tide/functions/tide/configure/functions/_fake_tide_item_time.fish
@@ -0,0 +1,3 @@
+function _fake_tide_item_time
+ _fake_tide_print_item time (date +$fake_tide_time_format)
+end
diff --git a/dev/fish-plugins/tide/functions/tide/configure/functions/_fake_tide_print_item.fish b/dev/fish-plugins/tide/functions/tide/configure/functions/_fake_tide_print_item.fish
new file mode 100644
index 000000000..30cd0245e
--- /dev/null
+++ b/dev/fish-plugins/tide/functions/tide/configure/functions/_fake_tide_print_item.fish
@@ -0,0 +1,22 @@
+function _fake_tide_print_item -a item
+ var=fake_tide_"$item"_bg_color set -f item_bg_color $$var
+
+ if set -e add_prefix
+ set_color $item_bg_color -b normal
+ var=fake_tide_"$_fake_tide_side"_prompt_prefix echo -ns $$var
+ else if test "$item_bg_color" = "$prev_bg_color"
+ var=fake_tide_"$_fake_tide_side"_prompt_separator_same_color echo -ns $_fake_tide_color_separator_same_color$$var
+ else if test "$_fake_tide_side" = left
+ set_color $prev_bg_color -b $item_bg_color
+ echo -ns $fake_tide_left_prompt_separator_diff_color
+ else
+ set_color $item_bg_color -b $prev_bg_color
+ echo -ns $fake_tide_right_prompt_separator_diff_color
+ end
+
+ var=fake_tide_"$item"_color set_color $$var -b $item_bg_color
+
+ echo -ns $_fake_tide_pad $argv[2..] $_fake_tide_pad
+
+ set -g prev_bg_color $item_bg_color
+end
diff --git a/dev/fish-plugins/tide/functions/tide/configure/functions/_fake_tide_prompt.fish b/dev/fish-plugins/tide/functions/tide/configure/functions/_fake_tide_prompt.fish
new file mode 100644
index 000000000..11f20caa7
--- /dev/null
+++ b/dev/fish-plugins/tide/functions/tide/configure/functions/_fake_tide_prompt.fish
@@ -0,0 +1,42 @@
+function _fake_tide_prompt
+ set -g add_prefix
+ _fake_tide_side=left set -f left (for item in $fake_tide_left_prompt_items
+ _fake_tide_item_$item
+ end
+ if not set -e add_prefix
+ set_color $prev_bg_color -b normal
+ echo -ns $fake_tide_left_prompt_suffix
+ end)
+
+ set -g add_prefix
+ _fake_tide_side=right set -f right (for item in $fake_tide_right_prompt_items
+ _fake_tide_item_$item
+ end
+ if not set -e add_prefix
+ set_color $prev_bg_color -b normal
+ echo -ns $fake_tide_right_prompt_suffix
+ end)
+
+ if set -q _fake_tide_prompt_and_frame_color # If prompt is two lines
+ test "$fake_tide_left_prompt_frame_enabled" = true &&
+ set left[1] "$_fake_tide_prompt_and_frame_color╭─$left[1]" &&
+ set left[2] "$_fake_tide_prompt_and_frame_color╰─$left[2]"
+ test "$fake_tide_right_prompt_frame_enabled" = true &&
+ set right[1] "$right[1]$_fake_tide_prompt_and_frame_color─╮" &&
+ set right[2] "$right[2]$_fake_tide_prompt_and_frame_color─╯"
+
+ # 5 = @PWD@ length which will be replaced
+ math $fake_columns+5-(string length --visible "$left[1]$right[1]") | read -lx dist_btwn_sides
+ echo -ns "$right[2]"\n(string replace @PWD@ (_fake_tide_pwd) "$left[1]")$_fake_tide_prompt_and_frame_color
+
+ string repeat --no-newline --max (math max 0, $dist_btwn_sides-$_tide_pwd_len) $fake_tide_prompt_icon_connection
+ echo -ns "$right[1]"\n"$left[2] "
+ else
+ math $fake_columns+5-(string length --visible "$left[1]$right[1]") -$fake_tide_prompt_min_cols | read -lx dist_btwn_sides
+ string replace @PWD@ (_fake_tide_pwd) "$right[1]" "$left[1] "
+ end
+end
+
+function _fake_tide_item_pwd
+ _fake_tide_print_item pwd @PWD@
+end
diff --git a/dev/fish-plugins/tide/functions/tide/configure/functions/_fake_tide_pwd.fish b/dev/fish-plugins/tide/functions/tide/configure/functions/_fake_tide_pwd.fish
new file mode 100644
index 000000000..433eafabd
--- /dev/null
+++ b/dev/fish-plugins/tide/functions/tide/configure/functions/_fake_tide_pwd.fish
@@ -0,0 +1,11 @@
+function _fake_tide_pwd
+ set -l out (
+ set_color $fake_tide_pwd_color_dirs
+ echo -ns $fake_tide_pwd_icon' ' '~/'
+ set_color -o $fake_tide_pwd_color_anchors
+ echo -ns src
+ set_color normal -b $fake_tide_pwd_bg_color
+ )
+ set -g _tide_pwd_len (string length --visible $out)
+ echo -ns $out
+end
diff --git a/dev/fish-plugins/tide/functions/tide/configure/icons.fish b/dev/fish-plugins/tide/functions/tide/configure/icons.fish
new file mode 100644
index 000000000..a687bed2d
--- /dev/null
+++ b/dev/fish-plugins/tide/functions/tide/configure/icons.fish
@@ -0,0 +1,41 @@
+tide_aws_icon # Actual aws glyph is harder to see
+tide_bun_icon
+tide_character_icon ❯
+tide_character_vi_icon_default ❮
+tide_character_vi_icon_replace ▶
+tide_character_vi_icon_visual V
+tide_cmd_duration_icon
+tide_crystal_icon
+tide_direnv_icon ▼
+tide_distrobox_icon
+tide_docker_icon
+tide_elixir_icon
+tide_gcloud_icon # Actual google cloud glyph is harder to see
+tide_git_icon
+tide_go_icon
+tide_java_icon
+tide_jobs_icon
+tide_kubectl_icon
+tide_nix_shell_icon
+tide_node_icon # Actual nodejs glyph is harder to see
+tide_os_icon $os_branding_icon
+tide_php_icon
+tide_private_mode_icon
+tide_prompt_icon_connection ' '
+tide_pulumi_icon
+tide_pwd_icon
+tide_pwd_icon_home
+tide_pwd_icon_unwritable
+tide_python_icon
+tide_ruby_icon
+tide_rustc_icon
+tide_shlvl_icon
+tide_status_icon ✔
+tide_status_icon_failure ✘
+tide_terraform_icon
+tide_toolbox_icon
+tide_vi_mode_icon_default D
+tide_vi_mode_icon_insert I
+tide_vi_mode_icon_replace R
+tide_vi_mode_icon_visual V
+tide_zig_icon
diff --git a/dev/functions.fish b/dev/functions.fish
new file mode 100644
index 000000000..9064ecf04
--- /dev/null
+++ b/dev/functions.fish
@@ -0,0 +1,121 @@
+function rgb
+ function hextorgb
+ set r $(string sub $argv[1] -l 2)
+ set g $(string sub $argv[1] -l 2 -s 3)
+ set b $(string sub $argv[1] -l 2 -s 5)
+
+ set r $(python3 -c "print(int(\"$r\", 16))")
+ set g $(python3 -c "print(int(\"$g\", 16))")
+ set b $(python3 -c "print(int(\"$b\", 16))")
+
+ string join ';' $r $g $b
+ end
+
+ printf "\033[38;2;%sm" $(hextorgb $argv[2])
+
+ if test (count $argv) -eq 3
+ printf "\033[48;2;%sm" $(hextorgb $argv[3])
+ end
+
+ printf "%s" $argv[1]
+ printf "\033[0m"
+end
+
+function cmd
+ rgb $argv[1] e61c6e 000000
+end
+
+
+
+function fish_greeting
+ # check $PATH is correct
+ if not contains ~/.local/pnpm $PATH
+ fish_add_path ~/.local/pnpm
+ end
+
+ echo "Welcome to $(rgb "Tachi" e61c6e 131313)!"
+
+ if ! test -e /tachi/I_HAVE_BOOTSTRAPPED_OK
+ echo ""
+ echo $(rgb "Something went wrong and Tachi didn't set up correctly." ff0000 000000)
+ echo ""
+ echo $(rgb "Please run" ff0000 000000) $(cmd "just bootstrap"). $(rgb "An incorrectly setup Tachi might not launch." ff0000 000000)
+ return
+ end
+
+ if ! test -e ~/.config/fish/NO_GREET
+ echo ""
+ echo $(rgb "This terminal is set up inside a Linux Machine!" ffffff 000000)
+ echo $(rgb "This machine comes pre-installed with Tachi and helpful tools." ffffff 000000)
+ echo ""
+ echo "Type $(cmd "just start") to start up a frontend and backend."
+ echo " $(rgb "The server will start on http://127.0.0.1:3000." ffff00 000000)"
+ echo " $(rgb "Use Ctrl+C to stop the server." ffff00 000000)"
+ echo ""
+ echo "Type $(cmd "seeds") to run seeds scripts."
+ echo " $(rgb "Create new script files in seeds/scripts." ffff00 000000)"
+ echo ""
+ echo "Type $(cmd "just") to see everything else we have going on."
+ echo ""
+ echo "$(rgb "Thank you for showing up and contributing!" 1cffff 000000) Feel free to reach out if you need anything."
+ echo "To turn off this long message, run $(cmd "disable_greeting")"
+ end
+end
+
+function disable_greeting
+ touch ~/.config/fish/NO_GREET
+ echo "Greeting disabled. Run $(cmd "enable_greeting") to turn it back on."
+end
+
+function enable_greeting
+ rm ~/.config/fish/NO_GREET
+ echo "Greeting enabled!"
+end
+
+# read function
+function funcread
+ cat ~/.config/fish/functions/$argv[1].fish
+end
+
+# create a new alias and permanently save it
+function defnew
+ alias --save "$argv" > /dev/null
+end
+
+# delete an alias permanently
+function funcdel
+ if test -e ~/.config/fish/functions/$argv[1].fish
+ rm ~/.config/fish/functions/$argv[1].fish
+ echo "Deleted $argv[1]."
+ else
+ echo "$argv[1] doesn't exist."
+ end
+end
+
+function seeds
+ set fzfcmd fzf --border
+
+ set mode (echo "Single Use"\n"Rerunners" | $fzfcmd)
+
+ if test -z "$mode"
+ return
+ end
+
+ if test $mode = "Single Use"
+ set place single-use
+ else if test $mode = "Rerunners"
+ set place rerunners
+ end
+
+ cd /tachi/seeds/scripts/$place
+
+ set selected_file (fd -e ts -e js --strip-cwd-prefix | $fzfcmd)
+
+ if test -n "$selected_file"
+ # epic trickshot to type the command out *for* the user.
+ printf "%s" "ts-node ./$selected_file" | fish_clipboard_copy
+ fish_clipboard_paste
+ else
+ echo No file selected. Not running anything!
+ end
+end
\ No newline at end of file
diff --git a/dev/setup.fish b/dev/setup.fish
new file mode 100755
index 000000000..eae5102c9
--- /dev/null
+++ b/dev/setup.fish
@@ -0,0 +1,20 @@
+#!/bin/fish
+
+# add a local bin for ts-node
+fish_add_path ~/.local/pnpm
+
+source ./dev/fish-plugins/fisher/functions/fisher.fish
+
+for file in ./dev/fish-plugins/*
+ fisher install $file
+end
+
+tide configure --auto --style=Lean --prompt_colors='True color' --show_time='24-hour format' --lean_prompt_height='One line' --prompt_spacing=Compact --icons='Few icons' --transient=No
+
+cp ./dev/functions.fish ~/.config/fish/functions/functions.fish
+source ./dev/functions.fish
+
+# define new permanent aliases here...
+source ./dev/aliases.fish
+
+# rr
\ No newline at end of file
diff --git a/docker-compose-dev.yml b/docker-compose-dev.yml
index 327c99512..5cf9ce43e 100644
--- a/docker-compose-dev.yml
+++ b/docker-compose-dev.yml
@@ -16,71 +16,21 @@ services:
- "6379:6379"
volumes:
- tachi-redis:/data
- tachi-server:
- container_name: tachi-server
+ tachi-dev:
+ user: "1000:1000"
+ tty: true
+ container_name: tachi-dev
build:
- dockerfile: Dockerfile.server
- target: dev
- depends_on:
- bootstrap:
- condition: service_completed_successfully
- redis:
- condition: service_started
- mongo:
- condition: service_started
- links:
- - redis
- - mongo
- volumes:
- - ./:/app
- ports:
- - "8080:8080"
- environment:
- REDIS_URL: redis
- MONGO_URL: mongo
- PORT: 8080
- NODE_ENV: "dev"
- tachi-client:
- container_name: tachi-client
-
- depends_on:
- bootstrap:
- condition: service_completed_successfully
-
- build:
- dockerfile: Dockerfile.client
- volumes:
- - ./:/app
- ports:
- - "3000:3000"
- tachi-seeds:
- container_name: tachi-seeds
- build:
- dockerfile: Dockerfile.seeds
- volumes:
- - ./:/app
- bootstrap:
- container_name: tachi-bootstrap
- build:
- dockerfile: Dockerfile.bootstrap
- volumes:
- - ./:/app
- links:
- - redis
- - mongo
- environment:
- REDIS_URL: redis
- MONGO_URL: mongo
- PORT: 8080
- NODE_ENV: "dev"
- tachi-docs:
- container_name: tachi-docs
- build:
- dockerfile: Dockerfile.docs
+ dockerfile: Dockerfile.dev
ports:
- - "3001:8000"
+ - "8080:8080" # server
+ - "3000:3000" # client
+ - "3001:3001" # docs
volumes:
- - ./:/app
+ - ./:/tachi
+ # don't let pnpm's node_modules linkery bleed out of its container
+ - /tachi/node_modules/
+ - /tachi/.pnpm-store
volumes:
tachi-volume:
tachi-redis:
diff --git a/docs/Justfile b/docs/Justfile
new file mode 100644
index 000000000..622319afa
--- /dev/null
+++ b/docs/Justfile
@@ -0,0 +1,7 @@
+[private]
+interactive:
+ -@cd ../ && just
+
+# Run just the docs on http://127.0.0.1:3001.
+start:
+ mkdocs serve -a 0.0.0.0:3001
\ No newline at end of file
diff --git a/docs/docs/api/routes/users.md b/docs/docs/api/routes/users.md
index d6718d4d8..33756e4c8 100644
--- a/docs/docs/api/routes/users.md
+++ b/docs/docs/api/routes/users.md
@@ -71,7 +71,7 @@ None.
### Example
!!! note
- `zkrising` is the username for the user with userID 1.
+ `zk` is the username for the user with userID 1.
it's also the username of the person writing these
docs. Hi!
diff --git a/docs/docs/codebase/implementation-details/goals-quests.md b/docs/docs/codebase/implementation-details/goals-quests.md
index ef0c3b378..dbbcc47db 100644
--- a/docs/docs/codebase/implementation-details/goals-quests.md
+++ b/docs/docs/codebase/implementation-details/goals-quests.md
@@ -74,7 +74,7 @@ subscribe to that quest, and all the goals will be managed for them.
Quests are identified by their `questID`, which is a completely arbitrary string.
-At the moment, users cannot create quests directly, they are hard-defined by the [Database Seeds](../infrastructure/database-seeds.md).
+At the moment, users cannot create quests directly, they are hard-defined by the [Database Seeds](../infrastructure/seeds.md).
### Evaluating a User's Progress
diff --git a/docs/docs/codebase/index.md b/docs/docs/codebase/index.md
index 4d6e04529..fb5683a2a 100644
--- a/docs/docs/codebase/index.md
+++ b/docs/docs/codebase/index.md
@@ -25,7 +25,7 @@ The client and the server are fairly decoupled. Someone could trivially create t
This contains all of our API calls, and interfaces with our database, and powers the actual score import engine.
-- `database-seeds/`, Which is a git-tracked set of data to be synced with Tachi.
+- `seeds/`, Which is a git-tracked set of data to be synced with Tachi.
**This is the source of truth for the songs, charts, and more on the site!**
By submitting PRs to this, you can fix bugs on the website, add new charts, and more.
@@ -40,4 +40,4 @@ This is also published to NPM when it hits production.
- `sieglinde/`, Which contains our BMS/PMS analysis functions.
-Of these, `server/` and `client/` are licensed under the AGPL3. The `database-seeds/` are licensed under the unlicense, and everything else is MIT.
\ No newline at end of file
+Of these, `server/` and `client/` are licensed under the AGPL3. The `seeds/` are licensed under the unlicense, and everything else is MIT.
\ No newline at end of file
diff --git a/docs/docs/codebase/infrastructure/database-seeds.md b/docs/docs/codebase/infrastructure/database-seeds.md
index bbb6da8ec..79f0765c4 100644
--- a/docs/docs/codebase/infrastructure/database-seeds.md
+++ b/docs/docs/codebase/infrastructure/database-seeds.md
@@ -1,6 +1,6 @@
# Database Seeds
-Tachi tracks the contents of its songs and charts in something called the [Database Seeds](https://github.com/zkrising/Tachi/tree/main/database-seeds).
+Tachi tracks the contents of its songs and charts in something called the [Database Seeds](https://github.com/zkrising/Tachi/tree/main/seeds).
The databases in question aren't (normally) altered by the server code. We essentially overload git and its CI tools to version control parts of our database.
diff --git a/docs/docs/contributing/components.md b/docs/docs/contributing/components.md
index 79fb14b64..e37a1a455 100644
--- a/docs/docs/contributing/components.md
+++ b/docs/docs/contributing/components.md
@@ -4,12 +4,6 @@ The Tachi repository that you just set up is made up of multiple "components".
All of them have their own things going on, so this page has all the guides for each specific component.
-## For Professionals...
-
-If you're already very familiar with `git`, the terminal, `node` and JSON, and care more about Tachi specific stuff (how to get the components running, what our architecture looks like), you should skip over that stuff in each guide.
-
-We maintain these guides so that people with knowledge about how to improve Tachi can help out, regardless of their skill level! As such, not all of the information will be of use to you - Skip over the tooling explanations.
-
## What can I contribute to?
In order of difficulty, here are the components of Tachi you can contribute to!
@@ -25,7 +19,7 @@ of the bug.
!!! note
Although they're called GitHub *issues*, they're actually used for tracking anything. If you've
- came up with a cool feature idea, send it over as an issue! `zkrising` will read and Triage them.
+ came up with a cool feature idea, send it over as an issue! `zk` will read and Triage them.
For more information, read our [Issue Reporting Guide](./components/issues.md).
@@ -35,15 +29,11 @@ We store our documentation as a series of markdown files in the [Main Repository
Writing, maintaining and proofreading the documentation is something that is **severely** neglected
at the moment. Simple things like typo fixes, all the way up to writing new explanations about major features
-are **thoroughly** appreciated, as `zkrising` prioritises maintaining the core of working code.
+are **thoroughly** appreciated, as `zk` prioritises maintaining the core of working code.
If you're interested in this, check out the [Documentation Contribution Guide](./components/documentation.md).
-It'll teach you `shell` and `git` basics,
-setting up a programming environment for Tachi,
-and how to use our documentation builder.
-
-### Database Seeds
+j### Database Seeds
We use an interesting system for parts of our database. We actually store a game's songs and charts *in*
our GitHub repository! That means you can:
@@ -56,9 +46,9 @@ our GitHub repository! That means you can:
!!! important
This part of Tachi is the most important part for external contributors.
- You guys know these games better than `zkrising` does, and you guys keep an eye on all the updates for your games!
+ You guys know these games better than `zk` does, and you guys keep an eye on all the updates for your games!
- If people don't add songs/charts to this database, `zkrising` will **not** keep an eye on the game for you! Someone *has* to pick up the reigns for each game!
+ If people don't add songs/charts to this database, `zk` will **not** keep an eye on the game for you! Someone *has* to pick up the reigns for each game!
If you want to add/fix songs, charts, folders or tables for your favourite game - **START HERE!**
@@ -66,11 +56,6 @@ our GitHub repository! That means you can:
Want to get started on contributing to the Database? Check out our [Database Contribution Guide](./components/seeds.md).
-It'll teach you `shell` and `git` basics,
-setting up a programming environment for Tachi,
-`json`,
-and we'll even do a little scripting as a treat!
-
### Server, Client
The server and client form the powerful *core* of Tachi.
diff --git a/docs/docs/contributing/components/core.md b/docs/docs/contributing/components/core.md
new file mode 100644
index 000000000..4af1387b4
--- /dev/null
+++ b/docs/docs/contributing/components/core.md
@@ -0,0 +1,43 @@
+# Client + Server Contribution Guide
+
+The client and server are the meat and potatoes of Tachi. They handle all of our requests and display our fancy UI.
+
+Contributing here is a bit more difficult than contributing to the seeds, but it's certainly not impossible!
+
+Plus, it's good fun to be able to mess around with websites. If you've got something you want to mess around with, you might find it surprisingly easy to do!
+
+## Pre-Setup
+
+You must have [Setup a local dev environment](../setup.md) in order to work nicely with the docs!
+
+## Component Overview
+
+The content for the client is inside `client/` and the content for the server is inside `server/`.
+
+The client and server share quite a bit of code. This is inside `common/`.
+
+To run the client and server, use `just start`. You can hit `Ctrl+C` to stop the server.
+
+## Editing the Client
+
+With `just start` running, the client will listen for changes you make, and reload accordingly. You will see your changes reflected on http://127.0.0.1:3000.
+
+## Editing the Server
+
+Likewise, with `just start` running, the server will listen for changes you make, and reload accordingly.
+
+!!! warning
+ Be careful with triggering a server reload. If you do it mid-import you can cause some serious state issues.
+
+ If you suspect that your local state is screwed up, run `just wipe-local-db` to reset
+ the database.
+
+## Getting real data
+
+The client, out of the box, is sort of hard to test because you'll have no scores to display.
+
+Use `just load-kamai-dataset` or `just load-boku-dataset` to load a *real* dataset from either of the Tachis.
+
+You'll then need to edit `server/conf.json5` and change `MONGO_DATABASE_NAME` to `"anon-kamai"` or `"anon-boku"`.
+
+Everyone's passwords are set to `password`, so feel free to log in as anyone, and see real data!
\ No newline at end of file
diff --git a/docs/docs/contributing/components/documentation.md b/docs/docs/contributing/components/documentation.md
index df22a43fb..57e1b96ea 100644
--- a/docs/docs/contributing/components/documentation.md
+++ b/docs/docs/contributing/components/documentation.md
@@ -2,14 +2,9 @@
The documentation component of Tachi powers the website you're currently viewing. Hi!
-## Tool Knowledge
+## Pre-Setup
-To properly contribute to the documentation, you'll need to know the following things:
-
-- [The Terminal](../tools/terminal.md)
-- [Git](../tools/git.md)
-
-If you don't know, or aren't comfortable with all of the things on this list, click on them to learn about them!
+You must have [Setup a local dev environment](../setup.md) in order to work nicely with the docs!
## Component Overview
@@ -20,33 +15,20 @@ It contains another folder, inconveniently called `docs/`, which contains all of
There's another folder called `includes/`, which contains some things that are constantly
referenced throughout the documentation.
-At the top level, there's `mkdocs.yml`, which we'll go over later.
+At the top level, there's `mkdocs.yml` which configures how our documentation works later.
## Software Overview
We use [MKDocs Material](https://squidfunk.github.io/mkdocs-material/) for our documentation.
It extends markdown a bit to let us add things like admonitions and references.
-This is a Python package, making it the only part of our codebase that is Python based. As such, you'll need a way of installing python packages.
-
-Their documentation is **incredibly** good, so check their stuff out there if you want to use their markdown extensions.
-
-Other than that, our documentation is vanilla [markdown](https://www.markdownguide.org/basic-syntax/). If you know how to write a reddit comment, you know how to write documentation.
-
-## Dependencies
-
-Use `pip` to install `mkdocs` and `mkdocs-material`.
-
-!!! danger
- [Python package management is an utter disasterous mess](https://stackoverflow.com/questions/48941116/does-python-pip-have-the-equivalent-of-nodes-package-json). Feel free to set up a venv or some other elaborate rube-goldberg machine to ensure that your packages don't bleed everywhere.
-
- Either way, you want to install such that is `mkdocs` on your terminal.
+Their documentation is **incredibly** good, so check their stuff out there if you want to see what features are available.
- You might have to restart your terminal after installing it, depending on the alignment of `pip` with the sun.
+Other than that, our documentation is [markdown](https://www.markdownguide.org/basic-syntax/). If you know how to format a discord message, you know how to write documentation!
## Running the Documentation
-Use `mkdocs serve` inside Tachi's `docs/` folder to start up a local documentation viewer on port `8000`.
+Use `just docs start` inside Tachi to start up a local documentation viewer on http://127.0.0.1:8001.
This will automatically refresh when you edit anything related to the documentation, so you can quickly see how your stuff goes.
diff --git a/docs/docs/contributing/components/seeds.md b/docs/docs/contributing/components/seeds.md
index a483a76df..59c5b61af 100644
--- a/docs/docs/contributing/components/seeds.md
+++ b/docs/docs/contributing/components/seeds.md
@@ -5,23 +5,13 @@ The database seeds component of Tachi controls the actual data inside the app.
Changes made in this component are automatically synchronised with the live Tachi database,
it's kind of a cool system, and allows anyone to contribute patches to songs, charts, folders, and more!
-## Tool Knowledge
+## Pre-Setup
-To properly contribute to the documentation, you'll need to know the following things:
-
-- [The Terminal](../tools/terminal.md)
-- [Git](../tools/git.md)
-- [JSON](../tools/json.md)
-
-If you know JavaScript, that will be helpful for the scripting sections when we go over how
-to batch-apply updates.
-However, we don't maintain a JS guide (yet). You'll have to ask in the discord!
-
-If you don't know, or aren't comfortable with all of the things on this list, click on them to learn about them!
+You must have [Setup a local dev environment](../setup.md) in order to work nicely with the docs!
## Component Overview
-All of the content for this component is inside the `database-seeds/` folder.
+All of the content for this component is inside the `seeds/` folder.
It contains the `collections/` folder, which contains JSON files for their respective databases.
@@ -38,10 +28,6 @@ The collections are all just plain JSON files.
The scripts are written in either TypeScript or JavaScript.
-## Dependencies
-
-Installation of dependencies for this part of the codebase should be already handled, if you ran the `bootstrap.sh` script.
-
## How do I know what a song is meant to look like?
These are called schemas (or interfaces) and you can find them in two places:
@@ -60,14 +46,11 @@ We also maintain a bit more human readable form for schemas in this documentatio
## Important Scripts
-After doing **anything** to the collections, such as adding a new item to the database, you **MUST** run `pnpm run sort` inside the `scripts/` folder.
+After doing **anything** to the collections, such as adding a new item to the database, you **MUST** run `just seeds sort` inside the `scripts/` folder.
This will deterministically sort the data you put into the collections, making sure that `git` history stays sane (i.e. it only says things that actually changed, changed).
-!!! info
- `pnpm run sort` is an alias for `node deterministic-collection-sort.js`
-
-Before sending any changes, run `pnpm test` to check all your data. If anything you've sent is invalid, it'll be logged in `failed-tests.log`, and the command won't pass.
+Before sending any changes, run `just seeds test` to check all your data. If anything you've sent is invalid, it'll be logged in `failed-tests.log`, and the command won't pass.
This is automatically ran when changes are sent to me, but you should run it yourself before committing anyway!
@@ -87,12 +70,17 @@ It should be obvious what each one does, though.
If you're looking to add a BMS table to Tachi for an example, you'd look into the rerunner scripts.
-## Single-Time Scripts
+## Single-Use Scripts
-Some scripts we only want to run once, and we don't really need to keep them around. These are saved into the `single-time` folder, and will never be tracked by `git`.
+Some scripts we only want to run once, and we don't really need to keep them around. These are saved into the `single-use` folder, and will never be tracked by `git`.
You can make quick hacky scripts here.
+!!! warning
+ Anything in this folder will *NOT* be shared. Please be generous with what you share,
+ far too many people have assumed nobody is interested in their scripts, and then lost
+ the scripts when moving machines!
+
## Scripting Changes
We provide utilities for mutating data inside the collections comfortably inside `scripts/util.js`. The most important one is `MutateCollection`, which, as expected, mutates a collection.
@@ -142,4 +130,9 @@ Below are some examples of what you can do with this API.
});
```
-For even more examples, see the `rerunners/` folder, as it makes liberal use of this API.
\ No newline at end of file
+For even more examples, see the `rerunners/` folder, as it makes liberal use of this API.
+
+## Running scripts
+
+To run a seeds script, type `seeds` and pick your script. This will type out the
+command for you.
diff --git a/docs/docs/contributing/cookbook/iidx-mdb.md b/docs/docs/contributing/cookbook/iidx-mdb.md
index fee652fa1..dbdc3e12d 100644
--- a/docs/docs/contributing/cookbook/iidx-mdb.md
+++ b/docs/docs/contributing/cookbook/iidx-mdb.md
@@ -11,7 +11,7 @@ Updating seeds via IIDX data is a complicated endeavour. Luckily, we've automate
You will need `ifstools` in your `$PATH`. You can do this by installing `python3` and then running `pip install ifstools`.
-Navigate to `database-seeds/scripts/rerunners/iidx/iidx-mdb-parse`.
+Navigate to `seeds/scripts/rerunners/iidx/iidx-mdb-parse`.
Run `ts-node merge-mdb.ts --help` for information on what arguments are needed.
diff --git a/docs/docs/contributing/cookbook/sdvx-mdb.md b/docs/docs/contributing/cookbook/sdvx-mdb.md
index e3e4b7b74..1fa9db4c9 100644
--- a/docs/docs/contributing/cookbook/sdvx-mdb.md
+++ b/docs/docs/contributing/cookbook/sdvx-mdb.md
@@ -10,7 +10,7 @@ This is convenient for when you want to provide an update for SDVX based off of
## How to use
-Navigate to `database-seeds/scripts/rerunners/sdvx`.
+Navigate to `seeds/scripts/rerunners/sdvx`.
The file you're looking to run is `merge-mdb.ts`, but it needs two arguments:
@@ -19,5 +19,3 @@ The file you're looking to run is `merge-mdb.ts`, but it needs two arguments:
You can run this script by typing `ts-node merge-mdb.ts --input YOUR_INPUT_HERE --version YOUR_VERSION_HERE` in the terminal.
-!!! tip
- Make sure you have `ts-node` installed. If you don't, you can get it with `pnpm add -g ts-node`.
diff --git a/docs/docs/contributing/core.md b/docs/docs/contributing/core.md
deleted file mode 100644
index 56c1bcb57..000000000
--- a/docs/docs/contributing/core.md
+++ /dev/null
@@ -1,3 +0,0 @@
-# Client + Server Contribution Guide
-
-Sorry! I haven't wrote this yet.
\ No newline at end of file
diff --git a/docs/docs/contributing/index.md b/docs/docs/contributing/index.md
index 99e11a04f..213e9e0f5 100644
--- a/docs/docs/contributing/index.md
+++ b/docs/docs/contributing/index.md
@@ -8,30 +8,20 @@ But, what *can* you contribute, and where can you help out?
Contrary to what you might already think, contributing to Tachi actually has remarkably little to
do with code.
-The primary Tachi maintainer (henceforth referred to as `zkrising`, because that's their name)
+The primary Tachi maintainer (henceforth referred to as `zk`, because that's their name)
deals with the core of the codebase; things like importing scores, adding new features and most of the UI work
is handled by them.
Tachi actually doesn't really have a shortage of people who can write good code, and the core of the codebase
-feature-wise is maintainable entirely by `zkrising` comfortably.
+feature-wise is maintainable entirely by `zk` comfortably.
-What **isn't** possible to maintain, is the sheer amount of things going on in every game! `zkrising`
+What **isn't** possible to maintain, is the sheer amount of things going on in every game! `zk`
cannot reasonably keep track of all the games we support and everything that's going on in them.
-That's where you step in - people who actually actively *play* games on Tachi! By letting `zkrising`
+That's where you step in - people who actually actively *play* games on Tachi! By letting `zk`
know about things going on in your game - and even contributing things yourself - you help make their life easier
and Tachi's support for your favourite game even better!
-
-
-## For Novices...
-
-The Novice guides also cover things like how to get a proper code editing setup, how to use the terminal, and more.
-
-You should be able to follow these *even if* you don't know the first thing about programming!
-
-All of the novice guides are further down in this page.
-
## Sounds good.
Awesome! Start with the [Setup](./setup.md) guide. This will get Tachi running on your local PC, so you can test all your changes.
\ No newline at end of file
diff --git a/docs/docs/contributing/setup.md b/docs/docs/contributing/setup.md
index ea1a3fa27..4497caab1 100644
--- a/docs/docs/contributing/setup.md
+++ b/docs/docs/contributing/setup.md
@@ -6,34 +6,52 @@ You don't *necessarily* need to have a working install to contribute - you could
make documentation contributions without having anything running on your machine - but
it's extremely helpful to be able to run Tachi's things while working on them.
-## 0. Get some developer tools.
+## 0. Install the basics
-If you're an experienced programmer, you won't need this bit.
+### VSCode
-### Editor
+We'll need a code editor so we can actually edit Tachi's code.
-You'll need an editor to work in!
+Please install [VSCode](https://code.visualstudio.com).
+We'll use this as our editor because of it's excellent support for dev containers.
-If you've only done a bit of programming in school,
-you might be used to something like Visual Studio.
+### Terminal
-Sadly, Visual Studio is *not* a great editor for a codebase like Tachi. Visual Studio is
-really good at writing C# (a language we don't use at all),
-but its integration with TypeScript (the language we use) is quite poor.
-
-We **highly** recommend that you get [VSCode](https://code.visualstudio.com/). Especially
-if you're a beginner! It's an extremely good editor, and has remarkably good integration
-with everything we use.
-
-### Terminal
-
-You'll need a terminal to run commands in. For Linux and Mac users, you can just open
+You'll also need a terminal to run commands in. For Linux and Mac users, you can just open
a Terminal app.
However, for Windows users we recommend installing the [Windows Terminal](https://apps.microsoft.com/store/detail/windows-terminal/9N0DX20HK701?hl=en-gb&gl=GB).
With a terminal open you can proceed to the next steps!
+### Git
+
+You'll need `git` to clone Tachi to your machine.
+
+=== "Windows"
+ Install git [from the official website](https://git-scm.com/downloads).
+
+=== "Ubuntu, Debian"
+ Open a terminal and type this:
+
+ ```sh
+ sudo apt install git
+ ```
+
+=== "Arch, Manjaro"
+ Open a terminal and type this:
+
+ ```sh
+ sudo pacman -S git
+ ```
+
+=== "MacOS"
+ Open a terminal and type this:
+
+ ```sh
+ brew install git
+ ```
+
## 1. Getting Docker.
To set everything else up for local development, we'll use [Docker](https://docker.com).
@@ -49,17 +67,21 @@ To set everything else up for local development, we'll use [Docker](https://dock
[Please use the official Docker install guide.](https://docs.docker.com/engine/install/ubuntu/)
=== "Arch, Manjaro"
+ Open a terminal and type this:
+
```sh
sudo pacman -S docker docker-compose
```
=== "MacOS"
+ Open a terminal and type this:
+
```sh
brew install docker docker-compose
```
!!! info
- Docker is like a VM[^1]. It runs an entire Linux box to contain your software in, and generally sidesteps the whole "works on my machine" problem, by just shipping the entire machine.
+ Docker is like a VM[^1]. It runs an entire Linux box to contain your software in, and generally sidesteps the whole "works on some machines" problem.
## 2. Fork and pull the repo.
@@ -75,6 +97,9 @@ Now, back to the terminal:
If you do that, make sure you open the terminal in that folder,
so your Tachi repo will save there!
+
+Open a terminal and type the following commands:
+
```sh
# This will create a folder called Tachi on your PC.
# It'll create it wherever your terminal is currently open in.
@@ -84,42 +109,57 @@ git clone https://github.com/YOUR_GITHUB_USERNAME/Tachi
code Tachi
```
-## 3. Authenticate with Github.
+## 3. Get into the container.
-You'll need to authenticate with GitHub before you can actually push changes back
-to your repository.
+### What is a container?
-There's a remarkably easy way to do this, using GitHub's `gh` tool.
+Your personal machine could be running anything. Windows, Mac, Linux, whatever!
+Tachi expects to be running on Linux and with specific versions of certain software running.
+It's a huge pain to ask *you* to install that software and manage it yourself.
+Plus, subtle differences between Windows and Linux cause problems _all_ the time.
-=== "Debian, Ubuntu, WSL Ubuntu"
- [Use the official Linux instructions.](https://github.com/cli/cli/blob/trunk/docs/install_linux.md)
+As such, we work *inside* a docker container. This is sort of like having a Linux VM with everything set up perfectly for you.
+I've spent quite a bit of time making this container user friendly, and it has so many nice things pre-installed for you.
-=== "Arch, Manjaro"
- ```sh
- sudo pacman -S gh
- ```
+Perhaps more importantly, the container has everything needed to run Tachi perfectly. Neat!
-=== "MacOS"
- ```sh
- brew install gh
- ```
+### Getting into it
-Once you've installed it, type `gh auth` and follow the instructions.
-You should now be properly authenticated!
+With `VSCode` open to Tachi, install the [Dev Container](https://marketplace.visualstudio.com/items?itemName=ms-vscode-remote.remote-containers) extension.
-## 4. Start Tachi!
+Then, hit `Ctrl+Shift+P` to view all commands, and run `Dev Containers: Rebuild and Reopen in Container`.
-With a terminal open inside the `Tachi` folder you just cloned, run this command:
+!!! warning
+ First time setup can take a very long time.
+ This depends on the performance of your machine, and whether you're using Windows or not.
-=== "Windows"
- ```bat
- run start
- ```
+ You can click `view log` in the bottom right to see the progress of making the container.
-=== "Linux, MacOS"
- ```sh
- ./run.sh start
- ```
+### Working in the container
+
+**You want to do ALL your work inside the container.**
+Doing thing outside of the container will cause issues or crashes.
+
+There is a subtle confusing trick here. We now want to use a terminal *inside* our container.
+**Do not use a terminal outside of VSCode now.**
+
+To open a terminal inside `VSCode`, use `Ctrl+J` to open the bottom panel.
+Inside there there should be a `TERMINAL` tab, click that.
+There should be a `+` at the top right of the panel. Click that to open a new terminal!
+
+You should see a message starting with `Welcome to Tachi!`. Inside this shell, you have full access to Tachi and all of its utilities.
+
+## 4. Authenticate with Github.
+
+You'll need to authenticate with GitHub before you can actually push changes back
+to your repository.
+
+Type `gh auth login` and follow the instructions.
+You should now be properly authenticated!
+
+## 5. Start Tachi!
+
+With a terminal open inside the `Tachi` container you just cloned, run `just start`.
The frontend will be running on `http://127.0.0.1:3000`.
The backend will be running on `https://127.0.0.1:8080`.
@@ -133,20 +173,9 @@ The backend will be running on `https://127.0.0.1:8080`.
the server will silently be chomped by the browser.
!!! tip
- Open the `run.bat` or `run.sh` files in vscode to see what other commands are available.
-
-## 5. Editor Plugins
-
-If you're using VSCode as your editor (I *really* recommend it!) You'll want a couple
-plugins.
-
-Namely, Install the ESLint plugin, and enable "Format On Save" in your settings. We use an [incredibly strict](https://github.com/CadenceJS/Cadence) plugin for ESLint, which catches a *ton* of programming errors and mistakes. By having it run in your editor, you can see your mistakes before you ever run the code, and have them automatically fix on save!
-
-!!! tip
- With `Ctrl-Shift-P`, you can open VSCodes "Command Palette". This will let you search
- for all the possible things VSCode can do. To open the settings, you can use `Ctrl-Shift-P` and search for "Settings".
+ Type `just` in the terminal to see other available commands.
- It's ridiculously convenient, and there's a bunch of other stuff that VSCode helps with.
+Navigate to http://127.0.0.1:3000 and check your Tachi instance!
## 6. OK, Now what.
diff --git a/docs/docs/game-support/index.md b/docs/docs/game-support/index.md
index 5fec05a30..d0ceee6a7 100644
--- a/docs/docs/game-support/index.md
+++ b/docs/docs/game-support/index.md
@@ -10,7 +10,7 @@ See 'Game Information' in the sidebar for a list of all supported games and thei
## How do I write support for a game?
-Adding support for a game requires configuration in three places and a loading of `database-seeds`.
+Adding support for a game requires configuration in three places and a loading of `seeds`.
You will need to:
diff --git a/docs/docs/game-support/seeds.md b/docs/docs/game-support/seeds.md
index b71d690fa..0901270d2 100644
--- a/docs/docs/game-support/seeds.md
+++ b/docs/docs/game-support/seeds.md
@@ -6,7 +6,7 @@ Lets load them into the database seeds.
## Quick Primer
-The database seeds are a folder in the monorepo: `database-seeds/collections`, which contain JSON files.
+The database seeds are a folder in the monorepo: `seeds/collections`, which contain JSON files.
These JSON files contain the state of a lot of our databases that need to be loaded. When changes are made to these seeds and committed to the main repository, a script will automatically apply those changes to the database.
@@ -24,11 +24,11 @@ Once you've gotten that data, you need to convert it into Tachi's song/chart for
## Writing the files
-You can modify the JSON files however you want. It really doesn't matter. However, there is a `database-seeds/scripts/` folder with a bunch of scripts you can use
+You can modify the JSON files however you want. It really doesn't matter. However, there is a `seeds/scripts/` folder with a bunch of scripts you can use
to ease this process.
-For things you only want to run a single time, place the script in the `database-seeds/scripts/single-time` folder.
-For things you want to keep around, place the script in the `database-seeds/rerunners` folder. Simple.
+For things you only want to run a single time, place the script in the `seeds/scripts/single-use` folder.
+For things you want to keep around, place the script in the `seeds/rerunners` folder. Simple.
The file `util.js` contains a bunch of miscellaneous utils for helping out, like `CreateChartID` or `MutateCollection`.
@@ -93,6 +93,6 @@ There are various utilities for this, like `scripts/rerunners/add-level-version-
## Loading the seeds
-Once you've modified the database seeds, test them with `pnpm test` inside the `database-seeds/scripts` folder. This will check a bunch of properties about the songs and charts you just made.
+Once you've modified the database seeds, test them with `pnpm test` inside the `seeds/scripts` folder. This will check a bunch of properties about the songs and charts you just made.
If they fail, read why and make appropriate changes. If they pass, move to the root of the Tachi repository and run `pnpm sync-database-local`. This will load the changes into your MongoDB instance.
\ No newline at end of file
diff --git a/docs/mkdocs.yml b/docs/mkdocs.yml
index 564ddd2d8..7c4188ef5 100644
--- a/docs/mkdocs.yml
+++ b/docs/mkdocs.yml
@@ -130,7 +130,7 @@ nav:
- Infrastructure:
- "codebase/infrastructure/logging.md"
- "codebase/infrastructure/branches.md"
- - "codebase/infrastructure/database-seeds.md"
+ - "codebase/infrastructure/seeds.md"
- OAuth2:
- "codebase/infrastructure/api-clients.md"
diff --git a/github-bot/src/main.ts b/github-bot/src/main.ts
index e05065b44..5afef5994 100644
--- a/github-bot/src/main.ts
+++ b/github-bot/src/main.ts
@@ -29,7 +29,7 @@ function mkSeedDiffViewMsg(repo: string, sha: string, compareRepo: string, compa
compareSHA,
});
- return `\nA commit has changed the database-seeds. [View the seeds diff here.](https://boku.tachi.ac/utils/seeds?${params.toString()})`;
+ return `\nA commit has changed the seeds. [View the diff here.](https://boku.tachi.ac/utils/seeds?${params.toString()})`;
}
function ConvertGitHubURL(url: string) {
@@ -52,7 +52,7 @@ app.webhooks.on(["pull_request.opened", "pull_request.edited"], async ({ octokit
).then((r) => r.json())) as Array<{ filename: string }>;
// if any file modified in this pr is a collection
- if (filesChanged.some((k) => k.filename.startsWith("database-seeds/collections"))) {
+ if (filesChanged.some((k) => k.filename.startsWith("seeds/collections"))) {
// post a link to the diff viewer in the PR comments.
await sendMsg(
mkSeedDiffViewMsg(
diff --git a/database-seeds/scripts/single-time/.gitkeep b/mongorestore
similarity index 100%
rename from database-seeds/scripts/single-time/.gitkeep
rename to mongorestore
diff --git a/package.json b/package.json
index e2ec6a1b5..46f232144 100644
--- a/package.json
+++ b/package.json
@@ -4,24 +4,6 @@
"private": true,
"description": "The root of the Tachi monorepo. Contains common utils like linters.",
"scripts": {
- "start": "docker compose -f docker-compose-dev.yml up --build -d",
- "stop": "docker compose -f docker-compose-dev.yml down",
- "logs-server": "docker logs tachi-server -f",
- "logs-client": "docker logs tachi-client -f",
- "logs-seeds": "docker logs tachi-seeds -f",
- "test-server": "docker compose -f docker-compose-dev.yml exec tachi-server pnpm test",
- "test-seeds": "docker compose -f docker-compose-dev.yml exec tachi-seeds pnpm --filter ./scripts test",
- "enter-seeds": "docker compose -f docker-compose-dev.yml exec tachi-seeds bash",
- "sort-seeds": "docker compose -f docker-compose-dev.yml exec tachi-seeds node scripts/deterministic-collection-sort.js",
- "view-seeds": "xdg-open http://127.0.0.1:3000/utils/seeds",
- "load-seeds": "docker compose -f docker-compose-dev.yml exec tachi-server pnpm sync-database-local",
- "validate-db": "docker compose -f docker-compose-dev.yml exec tachi-server pnpm validate-database",
- "start-docs": "cd docs/ && mkdocs serve",
- "raw-start-server": "pnpm --filter ./server start",
- "raw-start-client": "pnpm --filter ./client start",
- "raw-start-dbs": "redis-server --daemonize yes; sudo systemctl start mongodb.service",
- "^^ DEV STUFF IS HERE ^^": "comment",
- "vv PROD STUFF YOU WON'T TOUCH vv": "comment",
"build": "pnpm -r build",
"sync-database": "pnpm --filter ./server sync-database",
"start-score-import-worker": "pnpm --filter ./server runscoreworker"
diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml
index 3cf07262e..8ab59eb67 100644
--- a/pnpm-lock.yaml
+++ b/pnpm-lock.yaml
@@ -372,7 +372,7 @@ importers:
specifier: 4.9.4
version: 4.9.4
- database-seeds/scripts:
+ seeds/scripts:
dependencies:
'@types/lodash.get':
specifier: ^4.4.7
diff --git a/pnpm-workspace.yaml b/pnpm-workspace.yaml
index d64871ded..b83948a23 100644
--- a/pnpm-workspace.yaml
+++ b/pnpm-workspace.yaml
@@ -2,7 +2,7 @@ packages:
- "client/**"
- "common/**"
- "bot/**"
- - "database-seeds/**"
+ - "seeds/**"
- "server/**"
- "sieglinde/**"
- "github-bot/**"
diff --git a/run.bat b/run.bat
deleted file mode 100755
index 80a5a3b54..000000000
--- a/run.bat
+++ /dev/null
@@ -1,50 +0,0 @@
-@echo off
-
-where docker > nul 2>&1
-
-if %errorlevel% NEQ 0 (
- echo Docker isn't installed. Please install docker!
- pause
- exit 1
-)
-
-if "%1"=="" (
- set "cmd=start"
-) else (
- set "cmd=%"
-)
-
-echo "running %cmd%"
-
-if "%cmd%" == "start" (
- docker compose -f docker-compose-dev.yml up --build -d
- echo "Go to http://127.0.0.1:3000 to view Tachi!"
- echo "Go to http://127.0.0.1:3001 to view Tachi's Documentation!"
- echo "Run 'run.bat enter-seeds' to get a terminal for working on the seeds."
-) else if "%cmd%" == "stop" (
- docker compose -f docker-compose-dev.yml down
-) else if "%cmd%" == "logs-server" (
- docker logs tachi-server -f
-) else if "%cmd%" == "logs-client" (
- docker logs tachi-client -f
-) else if "%cmd%" == "logs-seeds" (
- docker logs tachi-seeds -f
-) else if "%cmd%" == "test-server" (
- docker exec tachi-server pnpm test
-) else if "%cmd%" == "test-seeds" (
- docker exec tachi-seeds pnpm --filter ./scripts test
-) else if "%cmd%" == "enter-seeds" (
- docker exec -it tachi-seeds bash
-) else if "%cmd%" == "sort-seeds" (
- docker exec tachi-seeds node scripts/deterministic-collection-sort.js
-) else if "%cmd%" == "load-seeds" (
- docker exec tachi-server pnpm sync-database-local
-) else if "%cmd%" == "validate-db" (
- docker exec tachi-server pnpm validate-database
-) else (
- echo "Unknown command %cmd%: Exiting."
- pause
- exit 1
-)
-
-pause
diff --git a/run.sh b/run.sh
deleted file mode 100755
index ce774659f..000000000
--- a/run.sh
+++ /dev/null
@@ -1,55 +0,0 @@
-#!/bin/bash
-
-if ! which docker >/dev/null; then
- echo "Docker isn't installed. Please install docker!"
- exit 1
-fi
-
-cmd=${1:-start}
-
-echo "running $cmd"
-
-set -eo pipefail
-
-case "$cmd" in
- start)
- docker compose -f docker-compose-dev.yml up --build -d
- echo "Go to http://127.0.0.1:3000 to view Tachi!"
- echo "Go to http://127.0.0.1:3001 to view Tachi's Documentation!"
- echo "Run './run.sh enter-seeds' to get a terminal for working on the seeds."
- ;;
- stop)
- docker compose -f docker-compose-dev.yml down
- ;;
- logs-server)
- docker logs tachi-server -f
- ;;
- logs-client)
- docker logs tachi-client -f
- ;;
- logs-seeds)
- docker logs tachi-seeds -f
- ;;
- test-server)
- docker exec tachi-server pnpm test
- ;;
- test-seeds)
- docker exec tachi-seeds pnpm --filter ./scripts test
- ;;
- enter-seeds)
- docker exec -it tachi-seeds bash
- ;;
- sort-seeds)
- docker exec tachi-seeds node scripts/deterministic-collection-sort.js
- ;;
- load-seeds)
- docker exec tachi-server pnpm sync-database-local
- ;;
- validate-db)
- docker exec tachi-server pnpm validate-database
- ;;
- *)
- echo "Unknown command $cmd"
- exit 1
- ;;
-esac
\ No newline at end of file
diff --git a/database-seeds/.eslintignore b/seeds/.eslintignore
similarity index 100%
rename from database-seeds/.eslintignore
rename to seeds/.eslintignore
diff --git a/database-seeds/.gitignore b/seeds/.gitignore
similarity index 65%
rename from database-seeds/.gitignore
rename to seeds/.gitignore
index 1f0f7eeb0..c18c12b02 100644
--- a/database-seeds/.gitignore
+++ b/seeds/.gitignore
@@ -1,6 +1,7 @@
# Single time fired scripts
-scripts/single-time/*
-!scripts/single-time/.gitkeep
+scripts/personal/*
+!scripts/personal/README.md
+!scripts/personal/example.js
# Rerunner output that I don't want to commit
scripts/rerunners/bpi-output.json
diff --git a/seeds/Justfile b/seeds/Justfile
new file mode 100644
index 000000000..db2da17cb
--- /dev/null
+++ b/seeds/Justfile
@@ -0,0 +1,17 @@
+[private]
+interactive:
+ -@cd ../; just
+
+# Make sure the data in the seeds is ordered correctly.
+#
+# The seeds have a determinsistic order to make git diffs more sensible!
+sort:
+ node scripts/sort-seeds.js
+
+# Test that the seeds pass their checks.
+test:
+ cd scripts/ && pnpm test
+
+# Load the seeds into your local server's database
+load:
+ cd ../server && pnpm load-seeds
\ No newline at end of file
diff --git a/database-seeds/LICENSE b/seeds/LICENSE
similarity index 100%
rename from database-seeds/LICENSE
rename to seeds/LICENSE
diff --git a/database-seeds/README.md b/seeds/README.md
similarity index 89%
rename from database-seeds/README.md
rename to seeds/README.md
index d2487a65f..5b151ceed 100644
--- a/database-seeds/README.md
+++ b/seeds/README.md
@@ -57,9 +57,9 @@ take the files and do whatever.
If you want to analyse this data in a more advanced fashion,
install `mongodb-tools` (You'll want mongoimport in `$PATH`) from your favourite location.
-`../_scripts/bootstrap` will synchronise these seeds with your local development install on first setup.
+`./dev/bootstrap` will synchronise these seeds with your local development install on first setup.
-You can use `server/src/scripts/sync-database` to re-synchronise whenever you want.
+You can use `server/src/scripts/load-seeds` to re-synchronise whenever you want.
## Contribution
diff --git a/database-seeds/collections/bms-course-lookup.json b/seeds/collections/bms-course-lookup.json
similarity index 100%
rename from database-seeds/collections/bms-course-lookup.json
rename to seeds/collections/bms-course-lookup.json
diff --git a/database-seeds/collections/charts-arcaea.json b/seeds/collections/charts-arcaea.json
similarity index 100%
rename from database-seeds/collections/charts-arcaea.json
rename to seeds/collections/charts-arcaea.json
diff --git a/database-seeds/collections/charts-bms.json b/seeds/collections/charts-bms.json
similarity index 100%
rename from database-seeds/collections/charts-bms.json
rename to seeds/collections/charts-bms.json
diff --git a/database-seeds/collections/charts-chunithm.json b/seeds/collections/charts-chunithm.json
similarity index 100%
rename from database-seeds/collections/charts-chunithm.json
rename to seeds/collections/charts-chunithm.json
diff --git a/database-seeds/collections/charts-ddr.json b/seeds/collections/charts-ddr.json
similarity index 100%
rename from database-seeds/collections/charts-ddr.json
rename to seeds/collections/charts-ddr.json
diff --git a/database-seeds/collections/charts-gitadora.json b/seeds/collections/charts-gitadora.json
similarity index 100%
rename from database-seeds/collections/charts-gitadora.json
rename to seeds/collections/charts-gitadora.json
diff --git a/database-seeds/collections/charts-iidx.json b/seeds/collections/charts-iidx.json
similarity index 100%
rename from database-seeds/collections/charts-iidx.json
rename to seeds/collections/charts-iidx.json
diff --git a/database-seeds/collections/charts-itg.json b/seeds/collections/charts-itg.json
similarity index 100%
rename from database-seeds/collections/charts-itg.json
rename to seeds/collections/charts-itg.json
diff --git a/database-seeds/collections/charts-jubeat.json b/seeds/collections/charts-jubeat.json
similarity index 100%
rename from database-seeds/collections/charts-jubeat.json
rename to seeds/collections/charts-jubeat.json
diff --git a/database-seeds/collections/charts-maimai.json b/seeds/collections/charts-maimai.json
similarity index 100%
rename from database-seeds/collections/charts-maimai.json
rename to seeds/collections/charts-maimai.json
diff --git a/database-seeds/collections/charts-maimaidx.json b/seeds/collections/charts-maimaidx.json
similarity index 100%
rename from database-seeds/collections/charts-maimaidx.json
rename to seeds/collections/charts-maimaidx.json
diff --git a/database-seeds/collections/charts-museca.json b/seeds/collections/charts-museca.json
similarity index 100%
rename from database-seeds/collections/charts-museca.json
rename to seeds/collections/charts-museca.json
diff --git a/database-seeds/collections/charts-ongeki.json b/seeds/collections/charts-ongeki.json
similarity index 100%
rename from database-seeds/collections/charts-ongeki.json
rename to seeds/collections/charts-ongeki.json
diff --git a/database-seeds/collections/charts-pms.json b/seeds/collections/charts-pms.json
similarity index 100%
rename from database-seeds/collections/charts-pms.json
rename to seeds/collections/charts-pms.json
diff --git a/database-seeds/collections/charts-popn.json b/seeds/collections/charts-popn.json
similarity index 100%
rename from database-seeds/collections/charts-popn.json
rename to seeds/collections/charts-popn.json
diff --git a/database-seeds/collections/charts-sdvx.json b/seeds/collections/charts-sdvx.json
similarity index 100%
rename from database-seeds/collections/charts-sdvx.json
rename to seeds/collections/charts-sdvx.json
diff --git a/database-seeds/collections/charts-usc.json b/seeds/collections/charts-usc.json
similarity index 100%
rename from database-seeds/collections/charts-usc.json
rename to seeds/collections/charts-usc.json
diff --git a/database-seeds/collections/charts-wacca.json b/seeds/collections/charts-wacca.json
similarity index 100%
rename from database-seeds/collections/charts-wacca.json
rename to seeds/collections/charts-wacca.json
diff --git a/database-seeds/collections/folders.json b/seeds/collections/folders.json
similarity index 100%
rename from database-seeds/collections/folders.json
rename to seeds/collections/folders.json
diff --git a/database-seeds/collections/goals.json b/seeds/collections/goals.json
similarity index 100%
rename from database-seeds/collections/goals.json
rename to seeds/collections/goals.json
diff --git a/database-seeds/collections/questlines.json b/seeds/collections/questlines.json
similarity index 100%
rename from database-seeds/collections/questlines.json
rename to seeds/collections/questlines.json
diff --git a/database-seeds/collections/quests.json b/seeds/collections/quests.json
similarity index 100%
rename from database-seeds/collections/quests.json
rename to seeds/collections/quests.json
diff --git a/database-seeds/collections/songs-arcaea.json b/seeds/collections/songs-arcaea.json
similarity index 100%
rename from database-seeds/collections/songs-arcaea.json
rename to seeds/collections/songs-arcaea.json
diff --git a/database-seeds/collections/songs-bms.json b/seeds/collections/songs-bms.json
similarity index 100%
rename from database-seeds/collections/songs-bms.json
rename to seeds/collections/songs-bms.json
diff --git a/database-seeds/collections/songs-chunithm.json b/seeds/collections/songs-chunithm.json
similarity index 100%
rename from database-seeds/collections/songs-chunithm.json
rename to seeds/collections/songs-chunithm.json
diff --git a/database-seeds/collections/songs-ddr.json b/seeds/collections/songs-ddr.json
similarity index 100%
rename from database-seeds/collections/songs-ddr.json
rename to seeds/collections/songs-ddr.json
diff --git a/database-seeds/collections/songs-gitadora.json b/seeds/collections/songs-gitadora.json
similarity index 100%
rename from database-seeds/collections/songs-gitadora.json
rename to seeds/collections/songs-gitadora.json
diff --git a/database-seeds/collections/songs-iidx.json b/seeds/collections/songs-iidx.json
similarity index 100%
rename from database-seeds/collections/songs-iidx.json
rename to seeds/collections/songs-iidx.json
diff --git a/database-seeds/collections/songs-itg.json b/seeds/collections/songs-itg.json
similarity index 100%
rename from database-seeds/collections/songs-itg.json
rename to seeds/collections/songs-itg.json
diff --git a/database-seeds/collections/songs-jubeat.json b/seeds/collections/songs-jubeat.json
similarity index 100%
rename from database-seeds/collections/songs-jubeat.json
rename to seeds/collections/songs-jubeat.json
diff --git a/database-seeds/collections/songs-maimai.json b/seeds/collections/songs-maimai.json
similarity index 100%
rename from database-seeds/collections/songs-maimai.json
rename to seeds/collections/songs-maimai.json
diff --git a/database-seeds/collections/songs-maimaidx.json b/seeds/collections/songs-maimaidx.json
similarity index 100%
rename from database-seeds/collections/songs-maimaidx.json
rename to seeds/collections/songs-maimaidx.json
diff --git a/database-seeds/collections/songs-museca.json b/seeds/collections/songs-museca.json
similarity index 100%
rename from database-seeds/collections/songs-museca.json
rename to seeds/collections/songs-museca.json
diff --git a/database-seeds/collections/songs-ongeki.json b/seeds/collections/songs-ongeki.json
similarity index 100%
rename from database-seeds/collections/songs-ongeki.json
rename to seeds/collections/songs-ongeki.json
diff --git a/database-seeds/collections/songs-pms.json b/seeds/collections/songs-pms.json
similarity index 100%
rename from database-seeds/collections/songs-pms.json
rename to seeds/collections/songs-pms.json
diff --git a/database-seeds/collections/songs-popn.json b/seeds/collections/songs-popn.json
similarity index 100%
rename from database-seeds/collections/songs-popn.json
rename to seeds/collections/songs-popn.json
diff --git a/database-seeds/collections/songs-sdvx.json b/seeds/collections/songs-sdvx.json
similarity index 100%
rename from database-seeds/collections/songs-sdvx.json
rename to seeds/collections/songs-sdvx.json
diff --git a/database-seeds/collections/songs-usc.json b/seeds/collections/songs-usc.json
similarity index 100%
rename from database-seeds/collections/songs-usc.json
rename to seeds/collections/songs-usc.json
diff --git a/database-seeds/collections/songs-wacca.json b/seeds/collections/songs-wacca.json
similarity index 100%
rename from database-seeds/collections/songs-wacca.json
rename to seeds/collections/songs-wacca.json
diff --git a/database-seeds/collections/tables.json b/seeds/collections/tables.json
similarity index 100%
rename from database-seeds/collections/tables.json
rename to seeds/collections/tables.json
diff --git a/database-seeds/scripts/.eslintignore b/seeds/scripts/.eslintignore
similarity index 100%
rename from database-seeds/scripts/.eslintignore
rename to seeds/scripts/.eslintignore
diff --git a/database-seeds/scripts/.eslintrc b/seeds/scripts/.eslintrc
similarity index 100%
rename from database-seeds/scripts/.eslintrc
rename to seeds/scripts/.eslintrc
diff --git a/database-seeds/scripts/finders.js b/seeds/scripts/finders.js
similarity index 100%
rename from database-seeds/scripts/finders.js
rename to seeds/scripts/finders.js
diff --git a/database-seeds/scripts/import.js b/seeds/scripts/import.js
similarity index 100%
rename from database-seeds/scripts/import.js
rename to seeds/scripts/import.js
diff --git a/database-seeds/scripts/logger.js b/seeds/scripts/logger.js
similarity index 100%
rename from database-seeds/scripts/logger.js
rename to seeds/scripts/logger.js
diff --git a/database-seeds/scripts/mutations.js b/seeds/scripts/mutations.js
similarity index 100%
rename from database-seeds/scripts/mutations.js
rename to seeds/scripts/mutations.js
diff --git a/database-seeds/scripts/package.json b/seeds/scripts/package.json
similarity index 89%
rename from database-seeds/scripts/package.json
rename to seeds/scripts/package.json
index 0e136cc23..8048d5ce7 100644
--- a/database-seeds/scripts/package.json
+++ b/seeds/scripts/package.json
@@ -1,8 +1,8 @@
{
- "name": "tachi-database-seeds-scripts",
+ "name": "tachi-seeds-scripts",
"version": "0.1.0-see-project-root",
"private": true,
- "description": "Scripts for dealing with tachi-database-seeds.",
+ "description": "Scripts for dealing with tachi-seeds.",
"main": "",
"scripts": {
"build": "tsc -b",
diff --git a/database-seeds/scripts/rerunners/add-level-version-folders.js b/seeds/scripts/rerunners/add-level-version-folders.js
similarity index 100%
rename from database-seeds/scripts/rerunners/add-level-version-folders.js
rename to seeds/scripts/rerunners/add-level-version-folders.js
diff --git a/database-seeds/scripts/rerunners/add-table-and-folders.js b/seeds/scripts/rerunners/add-table-and-folders.js
similarity index 100%
rename from database-seeds/scripts/rerunners/add-table-and-folders.js
rename to seeds/scripts/rerunners/add-table-and-folders.js
diff --git a/database-seeds/scripts/rerunners/arcaea/merge-songlist.ts b/seeds/scripts/rerunners/arcaea/merge-songlist.ts
similarity index 100%
rename from database-seeds/scripts/rerunners/arcaea/merge-songlist.ts
rename to seeds/scripts/rerunners/arcaea/merge-songlist.ts
diff --git a/database-seeds/scripts/rerunners/arcaea/wikiwiki-arcaea/_README.md b/seeds/scripts/rerunners/arcaea/wikiwiki-arcaea/_README.md
similarity index 100%
rename from database-seeds/scripts/rerunners/arcaea/wikiwiki-arcaea/_README.md
rename to seeds/scripts/rerunners/arcaea/wikiwiki-arcaea/_README.md
diff --git a/database-seeds/scripts/rerunners/arcaea/wikiwiki-arcaea/parse-scraped-data.js b/seeds/scripts/rerunners/arcaea/wikiwiki-arcaea/parse-scraped-data.js
similarity index 100%
rename from database-seeds/scripts/rerunners/arcaea/wikiwiki-arcaea/parse-scraped-data.js
rename to seeds/scripts/rerunners/arcaea/wikiwiki-arcaea/parse-scraped-data.js
diff --git a/database-seeds/scripts/rerunners/bms-pms/add-beatoraja-courses.js b/seeds/scripts/rerunners/bms-pms/add-beatoraja-courses.js
similarity index 100%
rename from database-seeds/scripts/rerunners/bms-pms/add-beatoraja-courses.js
rename to seeds/scripts/rerunners/bms-pms/add-beatoraja-courses.js
diff --git a/database-seeds/scripts/rerunners/bms-pms/add-lr2crs-file.js b/seeds/scripts/rerunners/bms-pms/add-lr2crs-file.js
similarity index 100%
rename from database-seeds/scripts/rerunners/bms-pms/add-lr2crs-file.js
rename to seeds/scripts/rerunners/bms-pms/add-lr2crs-file.js
diff --git a/database-seeds/scripts/rerunners/bms-pms/apply-sieglinde.js b/seeds/scripts/rerunners/bms-pms/apply-sieglinde.js
similarity index 100%
rename from database-seeds/scripts/rerunners/bms-pms/apply-sieglinde.js
rename to seeds/scripts/rerunners/bms-pms/apply-sieglinde.js
diff --git a/database-seeds/scripts/rerunners/bms-pms/calc-and-apply-sieglinde.sh b/seeds/scripts/rerunners/bms-pms/calc-and-apply-sieglinde.sh
similarity index 100%
rename from database-seeds/scripts/rerunners/bms-pms/calc-and-apply-sieglinde.sh
rename to seeds/scripts/rerunners/bms-pms/calc-and-apply-sieglinde.sh
diff --git a/database-seeds/scripts/rerunners/bms-pms/sync-bms-tablefolders.ts b/seeds/scripts/rerunners/bms-pms/sync-bms-tablefolders.ts
similarity index 100%
rename from database-seeds/scripts/rerunners/bms-pms/sync-bms-tablefolders.ts
rename to seeds/scripts/rerunners/bms-pms/sync-bms-tablefolders.ts
diff --git a/database-seeds/scripts/rerunners/chunithm/make-things-available-in-intl.js b/seeds/scripts/rerunners/chunithm/make-things-available-in-intl.js
similarity index 100%
rename from database-seeds/scripts/rerunners/chunithm/make-things-available-in-intl.js
rename to seeds/scripts/rerunners/chunithm/make-things-available-in-intl.js
diff --git a/database-seeds/scripts/rerunners/chunithm/merge-options.ts b/seeds/scripts/rerunners/chunithm/merge-options.ts
similarity index 98%
rename from database-seeds/scripts/rerunners/chunithm/merge-options.ts
rename to seeds/scripts/rerunners/chunithm/merge-options.ts
index 2989bc4fb..b68b37ec0 100644
--- a/database-seeds/scripts/rerunners/chunithm/merge-options.ts
+++ b/seeds/scripts/rerunners/chunithm/merge-options.ts
@@ -96,7 +96,7 @@ if (!VERSIONS.includes(baseVersion)) {
throw new Error(
`Invalid base version ${baseVersion}. Expected any of ${VERSIONS.join(
","
- )}. Update the VERSIONS array in database-seeds/scripts/rerunners/chunithm/merge-options.ts.`
+ )}. Update the VERSIONS array in seeds/scripts/rerunners/chunithm/merge-options.ts.`
);
}
@@ -228,7 +228,7 @@ for (const optionsDir of options.input) {
if (!displayVersion) {
throw new Error(
- `Unknown version ID ${musicData.releaseTagName.id}. Update database-seeds/scripts/rerunners/chunithm/merge-options.ts.`
+ `Unknown version ID ${musicData.releaseTagName.id}. Update seeds/scripts/rerunners/chunithm/merge-options.ts.`
);
}
diff --git a/database-seeds/scripts/rerunners/chunithm/parse-chunithm-dataset.ts b/seeds/scripts/rerunners/chunithm/parse-chunithm-dataset.ts
similarity index 100%
rename from database-seeds/scripts/rerunners/chunithm/parse-chunithm-dataset.ts
rename to seeds/scripts/rerunners/chunithm/parse-chunithm-dataset.ts
diff --git a/database-seeds/scripts/rerunners/fix-folder-ids.js b/seeds/scripts/rerunners/fix-folder-ids.js
similarity index 100%
rename from database-seeds/scripts/rerunners/fix-folder-ids.js
rename to seeds/scripts/rerunners/fix-folder-ids.js
diff --git a/database-seeds/scripts/rerunners/fix-goal-ids.js b/seeds/scripts/rerunners/fix-goal-ids.js
similarity index 100%
rename from database-seeds/scripts/rerunners/fix-goal-ids.js
rename to seeds/scripts/rerunners/fix-goal-ids.js
diff --git a/database-seeds/scripts/rerunners/gitadora/add-level-folders-gitadora.js b/seeds/scripts/rerunners/gitadora/add-level-folders-gitadora.js
similarity index 100%
rename from database-seeds/scripts/rerunners/gitadora/add-level-folders-gitadora.js
rename to seeds/scripts/rerunners/gitadora/add-level-folders-gitadora.js
diff --git a/database-seeds/scripts/rerunners/iidx/add-2dxtra.js b/seeds/scripts/rerunners/iidx/add-2dxtra.js
similarity index 100%
rename from database-seeds/scripts/rerunners/iidx/add-2dxtra.js
rename to seeds/scripts/rerunners/iidx/add-2dxtra.js
diff --git a/database-seeds/scripts/rerunners/iidx/add-new-primary-iidx-charts.js b/seeds/scripts/rerunners/iidx/add-new-primary-iidx-charts.js
similarity index 100%
rename from database-seeds/scripts/rerunners/iidx/add-new-primary-iidx-charts.js
rename to seeds/scripts/rerunners/iidx/add-new-primary-iidx-charts.js
diff --git a/database-seeds/scripts/rerunners/iidx/aix-output/parse-aix-json.ts b/seeds/scripts/rerunners/iidx/aix-output/parse-aix-json.ts
similarity index 100%
rename from database-seeds/scripts/rerunners/iidx/aix-output/parse-aix-json.ts
rename to seeds/scripts/rerunners/iidx/aix-output/parse-aix-json.ts
diff --git a/database-seeds/scripts/rerunners/iidx/iidx-mdb-parse/.gitignore b/seeds/scripts/rerunners/iidx/iidx-mdb-parse/.gitignore
similarity index 100%
rename from database-seeds/scripts/rerunners/iidx/iidx-mdb-parse/.gitignore
rename to seeds/scripts/rerunners/iidx/iidx-mdb-parse/.gitignore
diff --git a/database-seeds/scripts/rerunners/iidx/iidx-mdb-parse/HOWTO.md b/seeds/scripts/rerunners/iidx/iidx-mdb-parse/HOWTO.md
similarity index 100%
rename from database-seeds/scripts/rerunners/iidx/iidx-mdb-parse/HOWTO.md
rename to seeds/scripts/rerunners/iidx/iidx-mdb-parse/HOWTO.md
diff --git a/database-seeds/scripts/rerunners/iidx/iidx-mdb-parse/blacklist.txt b/seeds/scripts/rerunners/iidx/iidx-mdb-parse/blacklist.txt
similarity index 100%
rename from database-seeds/scripts/rerunners/iidx/iidx-mdb-parse/blacklist.txt
rename to seeds/scripts/rerunners/iidx/iidx-mdb-parse/blacklist.txt
diff --git a/database-seeds/scripts/rerunners/iidx/iidx-mdb-parse/convert.ts b/seeds/scripts/rerunners/iidx/iidx-mdb-parse/convert.ts
similarity index 100%
rename from database-seeds/scripts/rerunners/iidx/iidx-mdb-parse/convert.ts
rename to seeds/scripts/rerunners/iidx/iidx-mdb-parse/convert.ts
diff --git a/database-seeds/scripts/rerunners/iidx/iidx-mdb-parse/dot-one-parser/parser.ts b/seeds/scripts/rerunners/iidx/iidx-mdb-parse/dot-one-parser/parser.ts
similarity index 100%
rename from database-seeds/scripts/rerunners/iidx/iidx-mdb-parse/dot-one-parser/parser.ts
rename to seeds/scripts/rerunners/iidx/iidx-mdb-parse/dot-one-parser/parser.ts
diff --git a/database-seeds/scripts/rerunners/iidx/iidx-mdb-parse/dot-one-parser/types.ts b/seeds/scripts/rerunners/iidx/iidx-mdb-parse/dot-one-parser/types.ts
similarity index 100%
rename from database-seeds/scripts/rerunners/iidx/iidx-mdb-parse/dot-one-parser/types.ts
rename to seeds/scripts/rerunners/iidx/iidx-mdb-parse/dot-one-parser/types.ts
diff --git a/database-seeds/scripts/rerunners/iidx/iidx-mdb-parse/merge-mdb.ts b/seeds/scripts/rerunners/iidx/iidx-mdb-parse/merge-mdb.ts
similarity index 100%
rename from database-seeds/scripts/rerunners/iidx/iidx-mdb-parse/merge-mdb.ts
rename to seeds/scripts/rerunners/iidx/iidx-mdb-parse/merge-mdb.ts
diff --git a/database-seeds/scripts/rerunners/iidx/iidx-pe-kr/.gitignore b/seeds/scripts/rerunners/iidx/iidx-pe-kr/.gitignore
similarity index 100%
rename from database-seeds/scripts/rerunners/iidx/iidx-pe-kr/.gitignore
rename to seeds/scripts/rerunners/iidx/iidx-pe-kr/.gitignore
diff --git a/database-seeds/scripts/rerunners/iidx/iidx-pe-kr/_README b/seeds/scripts/rerunners/iidx/iidx-pe-kr/_README
similarity index 100%
rename from database-seeds/scripts/rerunners/iidx/iidx-pe-kr/_README
rename to seeds/scripts/rerunners/iidx/iidx-pe-kr/_README
diff --git a/database-seeds/scripts/rerunners/iidx/iidx-pe-kr/parse-iidxkr-data.js b/seeds/scripts/rerunners/iidx/iidx-pe-kr/parse-iidxkr-data.js
similarity index 100%
rename from database-seeds/scripts/rerunners/iidx/iidx-pe-kr/parse-iidxkr-data.js
rename to seeds/scripts/rerunners/iidx/iidx-pe-kr/parse-iidxkr-data.js
diff --git a/database-seeds/scripts/rerunners/iidx/make-things-available-in-inf.js b/seeds/scripts/rerunners/iidx/make-things-available-in-inf.js
similarity index 100%
rename from database-seeds/scripts/rerunners/iidx/make-things-available-in-inf.js
rename to seeds/scripts/rerunners/iidx/make-things-available-in-inf.js
diff --git a/database-seeds/scripts/rerunners/iidx/mina-inf-legg.js b/seeds/scripts/rerunners/iidx/mina-inf-legg.js
similarity index 100%
rename from database-seeds/scripts/rerunners/iidx/mina-inf-legg.js
rename to seeds/scripts/rerunners/iidx/mina-inf-legg.js
diff --git a/database-seeds/scripts/rerunners/iidx/mina-inf-new-spl.js b/seeds/scripts/rerunners/iidx/mina-inf-new-spl.js
similarity index 100%
rename from database-seeds/scripts/rerunners/iidx/mina-inf-new-spl.js
rename to seeds/scripts/rerunners/iidx/mina-inf-new-spl.js
diff --git a/database-seeds/scripts/rerunners/iidx/mina-inf.js b/seeds/scripts/rerunners/iidx/mina-inf.js
similarity index 100%
rename from database-seeds/scripts/rerunners/iidx/mina-inf.js
rename to seeds/scripts/rerunners/iidx/mina-inf.js
diff --git a/database-seeds/scripts/rerunners/iidx/update-bpi-poyashi.js b/seeds/scripts/rerunners/iidx/update-bpi-poyashi.js
similarity index 100%
rename from database-seeds/scripts/rerunners/iidx/update-bpi-poyashi.js
rename to seeds/scripts/rerunners/iidx/update-bpi-poyashi.js
diff --git a/database-seeds/scripts/rerunners/import-raw-quests.ts b/seeds/scripts/rerunners/import-raw-quests.ts
similarity index 100%
rename from database-seeds/scripts/rerunners/import-raw-quests.ts
rename to seeds/scripts/rerunners/import-raw-quests.ts
diff --git a/database-seeds/scripts/rerunners/itg/add-folders.js b/seeds/scripts/rerunners/itg/add-folders.js
similarity index 100%
rename from database-seeds/scripts/rerunners/itg/add-folders.js
rename to seeds/scripts/rerunners/itg/add-folders.js
diff --git a/database-seeds/scripts/rerunners/itg/parse-output.js b/seeds/scripts/rerunners/itg/parse-output.js
similarity index 100%
rename from database-seeds/scripts/rerunners/itg/parse-output.js
rename to seeds/scripts/rerunners/itg/parse-output.js
diff --git a/database-seeds/scripts/rerunners/jubeat/add-jubeat-folders.ts b/seeds/scripts/rerunners/jubeat/add-jubeat-folders.ts
similarity index 100%
rename from database-seeds/scripts/rerunners/jubeat/add-jubeat-folders.ts
rename to seeds/scripts/rerunners/jubeat/add-jubeat-folders.ts
diff --git a/database-seeds/scripts/rerunners/maimaidx/add-maimaidx-internal-levels.ts b/seeds/scripts/rerunners/maimaidx/add-maimaidx-internal-levels.ts
similarity index 100%
rename from database-seeds/scripts/rerunners/maimaidx/add-maimaidx-internal-levels.ts
rename to seeds/scripts/rerunners/maimaidx/add-maimaidx-internal-levels.ts
diff --git a/database-seeds/scripts/rerunners/maimaidx/add-maimaidx-table-and-folders.js b/seeds/scripts/rerunners/maimaidx/add-maimaidx-table-and-folders.js
similarity index 100%
rename from database-seeds/scripts/rerunners/maimaidx/add-maimaidx-table-and-folders.js
rename to seeds/scripts/rerunners/maimaidx/add-maimaidx-table-and-folders.js
diff --git a/database-seeds/scripts/rerunners/maimaidx/merge-options.ts b/seeds/scripts/rerunners/maimaidx/merge-options.ts
similarity index 97%
rename from database-seeds/scripts/rerunners/maimaidx/merge-options.ts
rename to seeds/scripts/rerunners/maimaidx/merge-options.ts
index a3459f185..f4c4f94dc 100644
--- a/database-seeds/scripts/rerunners/maimaidx/merge-options.ts
+++ b/seeds/scripts/rerunners/maimaidx/merge-options.ts
@@ -218,7 +218,7 @@ for (const optionsDir of options.input) {
if (!displayVersion) {
throw new Error(
- `Unknown version ID ${musicData.AddVersion.id}. Update database-seeds/scripts/rerunners/maimaidx/merge-options.ts.`
+ `Unknown version ID ${musicData.AddVersion.id}. Update seeds/scripts/rerunners/maimaidx/merge-options.ts.`
);
}
@@ -254,7 +254,7 @@ for (const optionsDir of options.input) {
if (difficultyName === undefined) {
throw new Error(
- `Unknown difficulty ID ${index}. Update database-seeds/scripts/rerunners/maimaidx/merge-options.ts and possibly common/src/config/game-support/maimai-dx.ts.`
+ `Unknown difficulty ID ${index}. Update seeds/scripts/rerunners/maimaidx/merge-options.ts and possibly common/src/config/game-support/maimai-dx.ts.`
);
}
diff --git a/database-seeds/scripts/rerunners/maimaidx/parse-maimaidx-dataset.ts b/seeds/scripts/rerunners/maimaidx/parse-maimaidx-dataset.ts
similarity index 99%
rename from database-seeds/scripts/rerunners/maimaidx/parse-maimaidx-dataset.ts
rename to seeds/scripts/rerunners/maimaidx/parse-maimaidx-dataset.ts
index 21f6d8bdc..cde0f1bdd 100644
--- a/database-seeds/scripts/rerunners/maimaidx/parse-maimaidx-dataset.ts
+++ b/seeds/scripts/rerunners/maimaidx/parse-maimaidx-dataset.ts
@@ -188,7 +188,7 @@ async function ParseMaimaiDXDataset() {
if (displayVersion === null || displayVersion === undefined) {
throw new Error(
- `Unknown version number ${version} (from ${data.version}). Update database-seeds/scripts/rerunners/maimaidx/parse-maimaidx-dataset.ts.`
+ `Unknown version number ${version} (from ${data.version}). Update seeds/scripts/rerunners/maimaidx/parse-maimaidx-dataset.ts.`
);
}
diff --git a/database-seeds/scripts/rerunners/make-questline-from-quests.ts b/seeds/scripts/rerunners/make-questline-from-quests.ts
similarity index 100%
rename from database-seeds/scripts/rerunners/make-questline-from-quests.ts
rename to seeds/scripts/rerunners/make-questline-from-quests.ts
diff --git a/database-seeds/scripts/rerunners/make-table-inactive.js b/seeds/scripts/rerunners/make-table-inactive.js
similarity index 100%
rename from database-seeds/scripts/rerunners/make-table-inactive.js
rename to seeds/scripts/rerunners/make-table-inactive.js
diff --git a/database-seeds/scripts/rerunners/ongeki/.gitkeep b/seeds/scripts/rerunners/ongeki/.gitkeep
similarity index 100%
rename from database-seeds/scripts/rerunners/ongeki/.gitkeep
rename to seeds/scripts/rerunners/ongeki/.gitkeep
diff --git a/database-seeds/scripts/rerunners/popn/make-popn-folders.js b/seeds/scripts/rerunners/popn/make-popn-folders.js
similarity index 100%
rename from database-seeds/scripts/rerunners/popn/make-popn-folders.js
rename to seeds/scripts/rerunners/popn/make-popn-folders.js
diff --git a/database-seeds/scripts/rerunners/popn/parse-duck-popn.js b/seeds/scripts/rerunners/popn/parse-duck-popn.js
similarity index 100%
rename from database-seeds/scripts/rerunners/popn/parse-duck-popn.js
rename to seeds/scripts/rerunners/popn/parse-duck-popn.js
diff --git a/database-seeds/scripts/rerunners/popn/parse-mdb-to-mxb.js b/seeds/scripts/rerunners/popn/parse-mdb-to-mxb.js
similarity index 100%
rename from database-seeds/scripts/rerunners/popn/parse-mdb-to-mxb.js
rename to seeds/scripts/rerunners/popn/parse-mdb-to-mxb.js
diff --git a/database-seeds/scripts/rerunners/sdvx/add-sdvx-clear-tierlist.js b/seeds/scripts/rerunners/sdvx/add-sdvx-clear-tierlist.js
similarity index 100%
rename from database-seeds/scripts/rerunners/sdvx/add-sdvx-clear-tierlist.js
rename to seeds/scripts/rerunners/sdvx/add-sdvx-clear-tierlist.js
diff --git a/database-seeds/scripts/rerunners/sdvx/add-sdvx-konaste.js b/seeds/scripts/rerunners/sdvx/add-sdvx-konaste.js
similarity index 100%
rename from database-seeds/scripts/rerunners/sdvx/add-sdvx-konaste.js
rename to seeds/scripts/rerunners/sdvx/add-sdvx-konaste.js
diff --git a/database-seeds/scripts/rerunners/sdvx/merge-mdb.ts b/seeds/scripts/rerunners/sdvx/merge-mdb.ts
similarity index 100%
rename from database-seeds/scripts/rerunners/sdvx/merge-mdb.ts
rename to seeds/scripts/rerunners/sdvx/merge-mdb.ts
diff --git a/database-seeds/scripts/rerunners/stepmania/apply-zkldijson.js b/seeds/scripts/rerunners/stepmania/apply-zkldijson.js
similarity index 100%
rename from database-seeds/scripts/rerunners/stepmania/apply-zkldijson.js
rename to seeds/scripts/rerunners/stepmania/apply-zkldijson.js
diff --git a/database-seeds/scripts/rerunners/usc/add-usc-converts.js b/seeds/scripts/rerunners/usc/add-usc-converts.js
similarity index 100%
rename from database-seeds/scripts/rerunners/usc/add-usc-converts.js
rename to seeds/scripts/rerunners/usc/add-usc-converts.js
diff --git a/database-seeds/scripts/rerunners/usc/add-usc-tables.ts b/seeds/scripts/rerunners/usc/add-usc-tables.ts
similarity index 100%
rename from database-seeds/scripts/rerunners/usc/add-usc-tables.ts
rename to seeds/scripts/rerunners/usc/add-usc-tables.ts
diff --git a/database-seeds/scripts/run-tests.sh b/seeds/scripts/run-tests.sh
similarity index 100%
rename from database-seeds/scripts/run-tests.sh
rename to seeds/scripts/run-tests.sh
diff --git a/seeds/scripts/single-use/README.md b/seeds/scripts/single-use/README.md
new file mode 100644
index 000000000..49685f5bf
--- /dev/null
+++ b/seeds/scripts/single-use/README.md
@@ -0,0 +1,10 @@
+This is for your own personal scripts.
+
+Code and files placed in here will not get shared with others.
+
+**Use this for one-off things that NOBODY else will find interesting.**
+
+If there's a chance your script might be useful for someone else, or it might need to be
+re-ran in the future, **DO NOT PUT IT IN HERE!!**
+
+Everything else should go in `public`!
\ No newline at end of file
diff --git a/seeds/scripts/single-use/example.js b/seeds/scripts/single-use/example.js
new file mode 100644
index 000000000..e287f0155
--- /dev/null
+++ b/seeds/scripts/single-use/example.js
@@ -0,0 +1,22 @@
+const { MutateCollection } = require("../util");
+
+// This is an example SINGLE-USE script. It does something quickly and ad-hoc,
+// that is of absolutely no interest to anyone else.
+//
+// You should treat single-use as your own playground for silly one-off things.
+//
+// ---------
+// / ZK SAYS \
+// //-----------------------------------------------------------------\\
+// !! PLEASE DO NOT USE SINGLE-USE FOR THINGS THAT YOU WANT TO RE-USE !!
+// !! ANYTHING YOU WANT TO RE-USE SHOULD BE SHARED ON TACHI IN CASE !!
+// !! YOU LOSE IT! !!
+// \\-----------------------------------------------------------------//
+
+MutateCollection("charts-iidx.json", (charts) => {
+ for (const chart of charts) {
+ delete chart.whoopsBADKEY;
+ }
+
+ return charts;
+});
diff --git a/database-seeds/scripts/deterministic-collection-sort.js b/seeds/scripts/sort-seeds.js
similarity index 100%
rename from database-seeds/scripts/deterministic-collection-sort.js
rename to seeds/scripts/sort-seeds.js
diff --git a/database-seeds/scripts/test/collections.test.ts b/seeds/scripts/test/collections.test.ts
similarity index 100%
rename from database-seeds/scripts/test/collections.test.ts
rename to seeds/scripts/test/collections.test.ts
diff --git a/database-seeds/scripts/test/custom.test.ts b/seeds/scripts/test/custom.test.ts
similarity index 100%
rename from database-seeds/scripts/test/custom.test.ts
rename to seeds/scripts/test/custom.test.ts
diff --git a/database-seeds/scripts/test/folder-id.test.ts b/seeds/scripts/test/folder-id.test.ts
similarity index 100%
rename from database-seeds/scripts/test/folder-id.test.ts
rename to seeds/scripts/test/folder-id.test.ts
diff --git a/database-seeds/scripts/test/goal-id.test.ts b/seeds/scripts/test/goal-id.test.ts
similarity index 100%
rename from database-seeds/scripts/test/goal-id.test.ts
rename to seeds/scripts/test/goal-id.test.ts
diff --git a/database-seeds/scripts/test/id.test.ts b/seeds/scripts/test/id.test.ts
similarity index 100%
rename from database-seeds/scripts/test/id.test.ts
rename to seeds/scripts/test/id.test.ts
diff --git a/database-seeds/scripts/test/match-type.test.ts b/seeds/scripts/test/match-type.test.ts
similarity index 100%
rename from database-seeds/scripts/test/match-type.test.ts
rename to seeds/scripts/test/match-type.test.ts
diff --git a/database-seeds/scripts/test/references.test.ts b/seeds/scripts/test/references.test.ts
similarity index 100%
rename from database-seeds/scripts/test/references.test.ts
rename to seeds/scripts/test/references.test.ts
diff --git a/database-seeds/scripts/test/table-default.test.ts b/seeds/scripts/test/table-default.test.ts
similarity index 100%
rename from database-seeds/scripts/test/table-default.test.ts
rename to seeds/scripts/test/table-default.test.ts
diff --git a/database-seeds/scripts/test/test-utils.ts b/seeds/scripts/test/test-utils.ts
similarity index 100%
rename from database-seeds/scripts/test/test-utils.ts
rename to seeds/scripts/test/test-utils.ts
diff --git a/database-seeds/scripts/tsconfig.json b/seeds/scripts/tsconfig.json
similarity index 100%
rename from database-seeds/scripts/tsconfig.json
rename to seeds/scripts/tsconfig.json
diff --git a/database-seeds/scripts/util.js b/seeds/scripts/util.js
similarity index 97%
rename from database-seeds/scripts/util.js
rename to seeds/scripts/util.js
index 5160ba85a..10a42400b 100644
--- a/database-seeds/scripts/util.js
+++ b/seeds/scripts/util.js
@@ -1,6 +1,6 @@
const fs = require("fs");
const path = require("path");
-const DeterministicCollectionSort = require("./deterministic-collection-sort");
+const DeterministicCollectionSort = require("./sort-seeds");
const crypto = require("crypto");
const fjsh = require("fast-json-stable-hash");
diff --git a/server/Justfile b/server/Justfile
new file mode 100644
index 000000000..059db12af
--- /dev/null
+++ b/server/Justfile
@@ -0,0 +1,21 @@
+[private]
+interactive:
+ -@cd ../ && just
+
+# Run the server interactively. Changes made to files will trigger reloads.
+start:
+ pnpm dev
+
+# Test the server.
+test: lint typecheck
+ pnpm test
+
+lint:
+ pnpm lint
+
+typecheck:
+ pnpm typecheck
+
+# Clean up the code and fix any automatically fixable mistakes.
+lintfix:
+ pnpm lint-fix
\ No newline at end of file
diff --git a/server/example/.env b/server/example/.env
index 206ef00bd..d4df3f2d4 100644
--- a/server/example/.env
+++ b/server/example/.env
@@ -1,5 +1,5 @@
NODE_ENV="dev"
-REDIS_URL="127.0.0.1"
-MONGO_URL="127.0.0.1"
+REDIS_URL="redis"
+MONGO_URL="mongo"
PORT=8080
LOG_LEVEL="verbose"
\ No newline at end of file
diff --git a/server/example/conf.json5 b/server/example/conf.json5
index 25104327c..ac8c60ca3 100644
--- a/server/example/conf.json5
+++ b/server/example/conf.json5
@@ -88,8 +88,7 @@
],
},
SEEDS_CONFIG: {
- REPO_URL: "https://github.com/zkrising/Tachi",
- USER_NAME: null,
- USER_EMAIL: null,
+ TYPE: "LOCAL_FILES",
+ PATH: "../seeds/collections",
},
}
diff --git a/server/package.json b/server/package.json
index 24e6add14..51461b303 100644
--- a/server/package.json
+++ b/server/package.json
@@ -19,8 +19,7 @@
"start": "pnpm build && pnpm start-no-build",
"start-no-build": "NODE_PATH=js/ node js/main.js",
"start-score-worker": "NODE_PATH=js/ node js/lib/score-import/worker/worker.js",
- "sync-database": "ts-node src/scripts/sync-database",
- "sync-database-local": "ts-node src/scripts/sync-database --localPath '../database-seeds/collections'",
+ "load-seeds": "ts-node src/scripts/load-seeds",
"recalc-everything": "ts-node src/scripts/state-sync/sync-state.ts",
"make-user-admin": "ts-node src/scripts/make-user-admin.ts $@",
"validate-database": "ts-node src/scripts/validate-database",
diff --git a/server/src/external/mongo/db.ts b/server/src/external/mongo/db.ts
index 2d87bcd53..a57394876 100644
--- a/server/src/external/mongo/db.ts
+++ b/server/src/external/mongo/db.ts
@@ -64,10 +64,10 @@ if (Environment.nodeEnv === "test") {
logger.info(`Connecting to database ${Environment.mongoUrl}/${dbName}...`, { bootInfo: true });
const dbtime = process.hrtime.bigint();
-// By default the connectTimeoutMS is 30 seconds. This has been upped to 5 minutes, due to poor performance
-// inside githubs test runners.
export const monkDB = monk(`${Environment.mongoUrl}/${dbName}`, {
- serverSelectionTimeoutMS: Environment.nodeEnv === "test" ? ONE_MINUTE * 5 : ONE_MINUTE,
+ // Various things cause bizarre issues with mongodb connections. Windows+Docker especially so.
+ // 5 minutes is excessive, but believe it or not, some setups are exceeding 2 minutes!
+ serverSelectionTimeoutMS: ONE_MINUTE * 5,
// in local dev, don't **ever** add _id onto objects you're inserting
// in production, this might have a performance hit.
diff --git a/server/src/external/redis/redis.ts b/server/src/external/redis/redis.ts
index e82480cea..8fd449db2 100644
--- a/server/src/external/redis/redis.ts
+++ b/server/src/external/redis/redis.ts
@@ -28,8 +28,9 @@ function EmitCritical() {
}
}
-// extend the timeout in testing because of awful github test runner perf
-const ref = setTimeout(EmitCritical, Environment.nodeEnv === "test" ? ONE_MINUTE * 5 : ONE_MINUTE);
+// awful performance on windows and in test runners mean that connecting to redis can be a
+// nearly FIVE minute endeavour!
+const ref = setTimeout(EmitCritical, ONE_MINUTE * 5);
RedisClient.on("connect", () => {
logger.info(`Connected to Redis. Took ${GetMillisecondsSince(startConnect)}ms`, {
diff --git a/server/src/lib/jobs/backsync-bms-pms-data.ts b/server/src/lib/jobs/backsync-bms-pms-data.ts
index 38f83a394..86095792a 100644
--- a/server/src/lib/jobs/backsync-bms-pms-data.ts
+++ b/server/src/lib/jobs/backsync-bms-pms-data.ts
@@ -1,8 +1,8 @@
/* eslint-disable no-await-in-loop */
import db from "external/mongo/db";
import { VERSION_INFO } from "lib/constants/version";
-import { PullDatabaseSeeds } from "lib/database-seeds/repo";
import CreateLogCtx from "lib/logger/logger";
+import { PullDatabaseSeeds } from "lib/seeds/repo";
import { WrapScriptPromise } from "utils/misc";
const logger = CreateLogCtx(__filename);
@@ -12,39 +12,37 @@ const logger = CreateLogCtx(__filename);
* information back with the seeds.
*/
export async function BacksyncBMSPMSSongsAndCharts() {
- for (const branch of ["main"]) {
- const repo = await PullDatabaseSeeds(undefined, branch);
+ const repo = await PullDatabaseSeeds();
- for (const game of ["bms", "pms"] as const) {
- logger.info(`Fetching ${game} songs from DB.`);
+ for (const game of ["bms", "pms"] as const) {
+ logger.info(`Fetching ${game} songs from DB.`);
- // did you know, this code is liable to blow up in my face and OOM one day?
- let songs = await db.anySongs[game].find({});
+ // did you know, this code is liable to blow up in my face and OOM one day?
+ let songs = await db.anySongs[game].find({});
- logger.info(`Found ${songs.length} ${game} songs.`);
+ logger.info(`Found ${songs.length} ${game} songs.`);
- await repo.WriteCollection(`songs-${game}`, songs);
+ await repo.WriteCollection(`songs-${game}`, songs);
- // @ts-expect-error This is obviously making something nullable when it shouldn't be.
- // but if we don't *force* node to free this damn memory, it kills itself when it
- // tries to read even more stuff.
- songs = null;
+ // @ts-expect-error This is obviously making something nullable when it shouldn't be.
+ // but if we don't *force* node to free this damn memory, it kills itself when it
+ // tries to read even more stuff.
+ songs = null;
- logger.info(`Fetching ${game} charts from DB.`);
- let charts = await db.anyCharts[game].find({});
+ logger.info(`Fetching ${game} charts from DB.`);
+ let charts = await db.anyCharts[game].find({});
- logger.info(`Found ${charts.length} ${game} charts.`);
+ logger.info(`Found ${charts.length} ${game} charts.`);
- await repo.WriteCollection(`charts-${game}`, charts);
+ await repo.WriteCollection(`charts-${game}`, charts);
- // @ts-expect-error See previous expect-error.
- charts = null;
- }
+ // @ts-expect-error See previous expect-error.
+ charts = null;
+ }
- await repo.CommitChangesBack(`Backsync BMS+PMS Songs/Charts ${new Date().toISOString()}`);
+ await repo.CommitChangesBack(`Backsync BMS+PMS Songs/Charts ${new Date().toISOString()}`);
- await repo.Destroy();
- }
+ await repo.Destroy();
}
if (require.main === module) {
diff --git a/server/src/lib/jobs/bms-ai-table-sync.ts b/server/src/lib/jobs/bms-ai-table-sync.ts
index 6588a8fad..be1dad4cb 100644
--- a/server/src/lib/jobs/bms-ai-table-sync.ts
+++ b/server/src/lib/jobs/bms-ai-table-sync.ts
@@ -1,6 +1,6 @@
/* eslint-disable no-await-in-loop */
-import { PullDatabaseSeeds } from "lib/database-seeds/repo";
import CreateLogCtx from "lib/logger/logger";
+import { PullDatabaseSeeds } from "lib/seeds/repo";
import fetch from "node-fetch";
import { WrapScriptPromise } from "utils/misc";
import type { ChartDocument } from "tachi-common";
diff --git a/server/src/lib/jobs/update-bpi-data.ts b/server/src/lib/jobs/update-bpi-data.ts
index c27782037..a64336ecc 100644
--- a/server/src/lib/jobs/update-bpi-data.ts
+++ b/server/src/lib/jobs/update-bpi-data.ts
@@ -1,7 +1,7 @@
/* eslint-disable no-await-in-loop */
import db from "external/mongo/db";
-import { BacksyncCollection, PullDatabaseSeeds } from "lib/database-seeds/repo";
import CreateLogCtx from "lib/logger/logger";
+import { BacksyncCollection, PullDatabaseSeeds } from "lib/seeds/repo";
import { RecalcAllScores } from "utils/calculations/recalc-scores";
import fetch from "utils/fetch";
import { WrapScriptPromise } from "utils/misc";
diff --git a/server/src/lib/jobs/update-dp-tiers.ts b/server/src/lib/jobs/update-dp-tiers.ts
index a138b4de2..d8cd8d5da 100644
--- a/server/src/lib/jobs/update-dp-tiers.ts
+++ b/server/src/lib/jobs/update-dp-tiers.ts
@@ -1,8 +1,8 @@
/* eslint-disable no-await-in-loop */
import db from "external/mongo/db";
import { decode } from "html-entities";
-import { BacksyncCollection } from "lib/database-seeds/repo";
import CreateLogCtx from "lib/logger/logger";
+import { BacksyncCollection } from "lib/seeds/repo";
import { parse } from "node-html-parser";
import { RecalcAllScores } from "utils/calculations/recalc-scores";
import fetch from "utils/fetch";
diff --git a/server/src/lib/jobs/update-sp12-data.ts b/server/src/lib/jobs/update-sp12-data.ts
index 9ff031081..341685d2a 100644
--- a/server/src/lib/jobs/update-sp12-data.ts
+++ b/server/src/lib/jobs/update-sp12-data.ts
@@ -2,8 +2,8 @@
/* eslint-disable no-case-declarations */
/* eslint-disable no-param-reassign */
import db from "external/mongo/db";
-import { BacksyncCollection } from "lib/database-seeds/repo";
import CreateLogCtx from "lib/logger/logger";
+import { BacksyncCollection } from "lib/seeds/repo";
import fetch from "node-fetch";
import { p } from "prudence";
import { RecalcAllScores } from "utils/calculations/recalc-scores";
diff --git a/server/src/lib/migration/migrations/remove-iidx-beginners.ts b/server/src/lib/migration/migrations/remove-iidx-beginners.ts
index 6ff4f09d8..5d581ef25 100644
--- a/server/src/lib/migration/migrations/remove-iidx-beginners.ts
+++ b/server/src/lib/migration/migrations/remove-iidx-beginners.ts
@@ -5,7 +5,7 @@ import type { Migration } from "utils/types";
const migration: Migration = {
id: "remove-iidx-beginners",
up: async () => {
- // copied from database-seeds before they were removed
+ // copied from seeds before they were removed
// the charts themselves are not guaranteed to be in the db
// so they have to be hardcoded.
const beginners = [
diff --git a/server/src/lib/migration/migrations/remove-iidx-extra-beginners.ts b/server/src/lib/migration/migrations/remove-iidx-extra-beginners.ts
index 369bab23d..36c9c9ba3 100644
--- a/server/src/lib/migration/migrations/remove-iidx-extra-beginners.ts
+++ b/server/src/lib/migration/migrations/remove-iidx-extra-beginners.ts
@@ -5,7 +5,7 @@ import type { Migration } from "utils/types";
const migration: Migration = {
id: "remove-iidx-extra-beginners",
up: async () => {
- // copied from database-seeds before they were removed
+ // copied from seeds before they were removed
// the charts themselves are not guaranteed to be in the db
// so they have to be hardcoded.
const beginners = [
diff --git a/server/src/lib/migration/migrations/remove-jubeat-night.ts b/server/src/lib/migration/migrations/remove-jubeat-night.ts
index 0cb8c72e8..286590231 100644
--- a/server/src/lib/migration/migrations/remove-jubeat-night.ts
+++ b/server/src/lib/migration/migrations/remove-jubeat-night.ts
@@ -5,7 +5,7 @@ import type { Migration } from "utils/types";
const migration: Migration = {
id: "remove-jubeat-night",
up: async () => {
- // copied from database-seeds before they were removed
+ // copied from seeds before they were removed
// the charts themselves are not guaranteed to be in the db
// so they have to be hardcoded.
const nightSongIDs = [
diff --git a/server/src/lib/database-seeds/repo.ts b/server/src/lib/seeds/repo.ts
similarity index 77%
rename from server/src/lib/database-seeds/repo.ts
rename to server/src/lib/seeds/repo.ts
index 00d33950a..41831da1b 100644
--- a/server/src/lib/database-seeds/repo.ts
+++ b/server/src/lib/seeds/repo.ts
@@ -26,12 +26,12 @@ export class DatabaseSeedsRepo {
private readonly shouldDestroy: "YES_IM_SURE_PLEASE_LET_THIS_DIRECTORY_BE_RM_RFD" | false;
/**
- * Create a database-seeds repository.
+ * Create a seeds repository.
*
- * @param baseDir - A path to the `collections` folder in database-seeds.
+ * @param baseDir - A path to the `collections` folder in seeds.
* @param shouldDestroy - Whether this repository should be destroyed when .Destroy()
* is called or not. This defaults to false, and will result in nothing happening
- * on cleanup. This behaviour is useful for things like local database-seeds work.
+ * on cleanup. This behaviour is useful for things like local seeds work.
*/
constructor(
baseDir: string,
@@ -48,7 +48,7 @@ export class DatabaseSeedsRepo {
pull() {
if (Environment.nodeEnv === "dev") {
// prevent an awful interaction where a user edits stuff on their disk
- // and tries to run pnpm sync-database-local
+ // and tries to run pnpm load-seeds
// but it fails because pull can't rebase with changes.
this.logger.warn(`Not pulling any updates to seeds as we're in local dev.`);
return;
@@ -102,7 +102,7 @@ export class DatabaseSeedsRepo {
/**
* Get all available collections as bare filenames, without any extension.
*
- * As an example, database-seeds/collections/songs-iidx.json would be "songs-iidx".
+ * As an example, seeds/collections/songs-iidx.json would be "songs-iidx".
*/
async ListCollections() {
const colls = await fs.readdir(this.baseDir);
@@ -146,6 +146,10 @@ export class DatabaseSeedsRepo {
throw new Error(`Cannot commit changes back. SEEDS_CONFIG is not set.`);
}
+ if (ServerConfig.SEEDS_CONFIG.TYPE !== "GIT_REPO") {
+ throw new Error(`Cannot commit changes back: this is a local filesystem.`);
+ }
+
if (!ServerConfig.SEEDS_CONFIG.USER_NAME || !ServerConfig.SEEDS_CONFIG.USER_EMAIL) {
throw new Error(
`Cannot commit changes back if SEEDS_CONFIG.USER_NAME/SEEDS_CONFIG.USER_EMAIL aren't defined.`
@@ -230,78 +234,63 @@ export class DatabaseSeedsRepo {
return fs.rm(this.baseDir, { recursive: true, force: true });
}
- this.logger.warn(`Refusing to delete seeds as they were instantiated locally.`);
+ this.logger.info(`Refusing to delete seeds as they were instantiated locally.`);
}
}
/**
* Pulls the database seeds from github, returns an object that can be used to manipulate them.
- *
- * @param fetchFromLocalPath - Whether or not to fetch this from a local instance, like a
- * monorepo database-seeds directory.
- *
- * You can use the environment variable "FORCE_LOCAL_SEEDS_PATH" to override all calls
- * to PullDatabaseSeeds to use a local repo instead of the configured remote.
*/
-export async function PullDatabaseSeeds(
- fetchFromLocalPath: string | null = process.env.FORCE_LOCAL_SEEDS_PATH ?? null,
- branch = "main"
-) {
- if (fetchFromLocalPath) {
- const local = new DatabaseSeedsRepo(fetchFromLocalPath);
+export async function PullDatabaseSeeds() {
+ if (ServerConfig.SEEDS_CONFIG?.TYPE === "GIT_REPO") {
+ const seedsDir = await fs.mkdtemp(path.join(os.tmpdir(), "tachi-seeds-"));
- // make sure we're on the right branch
- // and up to date.
- if (Environment.nodeEnv !== "dev") {
- await local.switchBranch(branch);
- }
+ logger.info(`Cloning data to ${seedsDir}.`);
- await local.pull();
+ await fs.rm(seedsDir, { recursive: true, force: true });
- return local;
- }
+ try {
+ const branch = ServerConfig.SEEDS_CONFIG.BRANCH ?? "main";
- if (!ServerConfig.SEEDS_CONFIG) {
- throw new Error(`SEEDS_CONFIG was not defined. You cannot pull a seeds repo.`);
- }
+ // stderr in git clone is normal output.
+ // stdout is for errors.
+ // there were expletives below this comment, but I have removed them.
+ const { stdout: cloneStdout } = await asyncExec(
+ `git clone --sparse --depth=1 "${ServerConfig.SEEDS_CONFIG.REPO_URL}" -b "${branch}" "${seedsDir}"`
+ );
- const seedsDir = await fs.mkdtemp(path.join(os.tmpdir(), "tachi-database-seeds-"));
+ if (cloneStdout) {
+ throw new Error(cloneStdout);
+ }
- logger.info(`Cloning data to ${seedsDir}.`);
+ const { stdout: checkoutStdout } = await asyncExec(
+ `git sparse-checkout add seeds`,
+ seedsDir
+ );
- await fs.rm(seedsDir, { recursive: true, force: true });
+ // ^ now that we're in a monorepo, we only want the seeds.
+ // this shaves quite a bit of time off of the clone.
- try {
- // stderr in git clone is normal output.
- // stdout is for errors.
- // there were expletives below this comment, but I have removed them.
- const { stdout: cloneStdout } = await asyncExec(
- `git clone --sparse --depth=1 "${ServerConfig.SEEDS_CONFIG.REPO_URL}" -b "${branch}" "${seedsDir}"`
- );
+ if (checkoutStdout) {
+ throw new Error(checkoutStdout);
+ }
- if (cloneStdout) {
- throw new Error(cloneStdout);
+ return new DatabaseSeedsRepo(
+ `${seedsDir}/seeds/collections`,
+ "YES_IM_SURE_PLEASE_LET_THIS_DIRECTORY_BE_RM_RFD"
+ );
+ } catch ({ err, stderr }) {
+ logger.error(`Error cloning seeds. ${stderr}.`);
+ throw err;
}
+ } else if (ServerConfig.SEEDS_CONFIG?.TYPE === "LOCAL_FILES") {
+ const local = new DatabaseSeedsRepo(ServerConfig.SEEDS_CONFIG.PATH);
- const { stdout: checkoutStdout } = await asyncExec(
- `git sparse-checkout add database-seeds`,
- seedsDir
- );
-
- // ^ now that we're in a monorepo, we only want the seeds.
- // this shaves quite a bit of time off of the clone.
-
- if (checkoutStdout) {
- throw new Error(checkoutStdout);
- }
+ await local.pull();
- return new DatabaseSeedsRepo(
- `${seedsDir}/database-seeds/collections`,
- "YES_IM_SURE_PLEASE_LET_THIS_DIRECTORY_BE_RM_RFD"
- );
- } catch ({ err, stderr }) {
- logger.error(`Error cloning database-seeds. ${stderr}.`);
- throw err;
+ return local;
+ } else {
+ throw new Error(`SEEDS_CONFIG was not defined. You cannot pull a seeds repo.`);
}
}
@@ -310,7 +299,7 @@ export async function BacksyncCollection(
collection: ICollection,
commitMessage: string
) {
- const repo = await PullDatabaseSeeds(undefined);
+ const repo = await PullDatabaseSeeds();
let charts = await collection.find({});
diff --git a/server/src/lib/setup/config.ts b/server/src/lib/setup/config.ts
index cac3d3a00..47c4f7154 100644
--- a/server/src/lib/setup/config.ts
+++ b/server/src/lib/setup/config.ts
@@ -86,12 +86,18 @@ export interface TachiServerConfig {
USE_EXTERNAL_SCORE_IMPORT_WORKER: boolean;
EXTERNAL_SCORE_IMPORT_WORKER_CONCURRENCY?: integer;
ENABLE_METRICS: boolean;
- SEEDS_CONFIG?: {
- REPO_URL: string;
- USER_NAME: string | null;
- USER_EMAIL: string | null;
- BRANCH?: string;
- };
+ SEEDS_CONFIG?:
+ | {
+ TYPE: "GIT_REPO";
+ REPO_URL: string;
+ USER_NAME: string | null;
+ USER_EMAIL: string | null;
+ BRANCH?: string;
+ }
+ | {
+ TYPE: "LOCAL_FILES";
+ PATH: string;
+ };
EMAIL_CONFIG?: {
FROM: string;
DKIM?: SendMailOptions["dkim"];
@@ -232,12 +238,21 @@ const err = p(config, {
}
),
},
- SEEDS_CONFIG: p.optional({
- REPO_URL: "string",
- USER_NAME: "?string",
- USER_EMAIL: "?string",
- BRANCH: "*string",
- }),
+ SEEDS_CONFIG: p.optional(
+ p.or(
+ {
+ TYPE: p.is("GIT_REPO"),
+ REPO_URL: "string",
+ USER_NAME: "?string",
+ USER_EMAIL: "?string",
+ BRANCH: "*string",
+ },
+ {
+ TYPE: p.is("LOCAL_FILES"),
+ PATH: "string",
+ }
+ )
+ ),
});
if (err) {
diff --git a/server/src/main.ts b/server/src/main.ts
index e0a6d131f..eaf3972b3 100644
--- a/server/src/main.ts
+++ b/server/src/main.ts
@@ -12,7 +12,9 @@ import { HandleSIGTERMGracefully } from "lib/handlers/sigterm";
import CreateLogCtx from "lib/logger/logger";
import { ApplyUnappliedMigrations } from "lib/migration/migrations";
import { Environment, ServerConfig, TachiConfig } from "lib/setup/config";
+import { AddNewUser } from "server/router/api/v1/auth/auth";
import server from "server/server";
+import { UserAuthLevels } from "tachi-common";
import fetch from "utils/fetch";
import { InitaliseFolderChartLookup } from "utils/folder";
import { spawn } from "child_process";
@@ -45,6 +47,17 @@ async function RunOnInit() {
}
});
+ if (Environment.nodeEnv === "dev") {
+ const exists = await db.users.findOne({ id: 1 });
+
+ if (!exists) {
+ logger.info("First time setup in LOCAL DEV: Creating an admin user for you.");
+ await AddNewUser("admin", "password", "admin@example.com", 1);
+ await db.users.update({ id: 1 }, { $set: { authLevel: UserAuthLevels.ADMIN } });
+ logger.info("Done! You have an admin user with password 'password'");
+ }
+ }
+
await LoadDefaultClients();
try {
@@ -99,7 +112,7 @@ if (process.env.INVOKE_JOB_RUNNER) {
if (Environment.nodeEnv === "production") {
logger.warn(
- `Spawning inline tachi-server job runner in production. Is this actually what you want? You should run a tool like Ofelia to manage this.`,
+ `Spawning inline tachi-server job runner in production. This is bad for performance.`,
{ bootInfo: true }
);
}
diff --git a/server/src/scripts/sync-database.ts b/server/src/scripts/load-seeds.ts
similarity index 93%
rename from server/src/scripts/sync-database.ts
rename to server/src/scripts/load-seeds.ts
index f3f7042c6..0a87ea7ad 100644
--- a/server/src/scripts/sync-database.ts
+++ b/server/src/scripts/load-seeds.ts
@@ -1,14 +1,11 @@
/* eslint-disable no-await-in-loop */
-// This script syncs this tachi instances database up with the tachi-database-seeds.
-
-import { Command } from "commander";
import db, { monkDB } from "external/mongo/db";
import fjsh from "fast-json-stable-hash";
-import { PullDatabaseSeeds } from "lib/database-seeds/repo";
import CreateLogCtx from "lib/logger/logger";
import { UpdateGoalsInFolder } from "lib/score-import/framework/goals/goals";
import UpdateIsPrimaryStatus from "lib/score-mutation/update-isprimary";
-import { ServerConfig, TachiConfig } from "lib/setup/config";
+import { PullDatabaseSeeds } from "lib/seeds/repo";
+import { TachiConfig } from "lib/setup/config";
import { RemoveStaleFolderShowcaseStats } from "lib/showcase/showcase";
import { UpdateQuestSubscriptions } from "lib/targets/quests";
import { RecalcAllScores } from "utils/calculations/recalc-scores";
@@ -98,7 +95,6 @@ async function GenericUpsert>(
const exists = map.get(document[field]);
if (exists === undefined) {
- logger.verbose(`Inserting new: ${document[field]}`);
insertOps.push({
// @ts-expect-error Actually, T is assignable to OptionalId.
insertOne: { document },
@@ -361,23 +357,11 @@ const syncInstructions: Array = [
const logger = CreateLogCtx("Database Sync");
-const program = new Command();
-
-program.option("-l, --localPath ");
-
-program.parse(process.argv);
-const options: {
- localPath: string | undefined;
-} = program.opts();
-
async function SynchroniseDBWithSeeds() {
// Wait for mongo to connect first.
await monkDB.then(() => void 0);
- const databaseSeedsRepo = await PullDatabaseSeeds(
- options.localPath,
- ServerConfig.SEEDS_CONFIG?.BRANCH
- );
+ const databaseSeedsRepo = await PullDatabaseSeeds();
for await (const { collectionName, data } of databaseSeedsRepo.IterateCollections()) {
const spawnLogger = CreateLogCtx(`${collectionName} Sync`);
diff --git a/server/src/server/router/api/v1/seeds/router.ts b/server/src/server/router/api/v1/seeds/router.ts
index 9cb28d1d0..6155a67bb 100644
--- a/server/src/server/router/api/v1/seeds/router.ts
+++ b/server/src/server/router/api/v1/seeds/router.ts
@@ -1,6 +1,6 @@
import { Router } from "express";
-import { PullDatabaseSeeds } from "lib/database-seeds/repo";
import CreateLogCtx from "lib/logger/logger";
+import { PullDatabaseSeeds } from "lib/seeds/repo";
import { Environment } from "lib/setup/config";
import prValidate from "server/middleware/prudence-validate";
import { RequireLocalDevelopment } from "server/middleware/type-require";
@@ -12,7 +12,7 @@ import path from "path";
const logger = CreateLogCtx(__filename);
-// Routes for interacting with the `database-seeds` folder in this instance of Tachi.
+// Routes for interacting with the `seeds` folder in this instance of Tachi.
// Why do we have this, and why is it limited to only local development?
// The answer is that we have a "Seeds UI" that runs in the client. For local development
@@ -33,7 +33,7 @@ const LOCAL_DEV_SEEDS_PATH = path.join(
__dirname,
// and she's buying a...
- "../../../../../../../database-seeds/collections"
+ "../../../../../../../seeds/collections"
);
const TEST_SEEDS_PATH = path.join(__dirname, "../../../../../test-utils/mock-db");
@@ -42,7 +42,7 @@ const LOCAL_SEEDS_PATH = Environment.nodeEnv === "test" ? TEST_SEEDS_PATH : LOCA
if (Environment.nodeEnv === "dev" || Environment.nodeEnv === "test") {
if (!fsSync.existsSync(LOCAL_SEEDS_PATH)) {
logger.error(
- `Failed to load seeds routes, could not find any database-seeds/collections checked out at ${LOCAL_SEEDS_PATH}.
+ `Failed to load seeds routes, could not find any seeds/collections checked out at ${LOCAL_SEEDS_PATH}.
These were expected to be present as this is local-development!
All seeds routes will return 500.`
);
@@ -63,7 +63,7 @@ router.get("/", (req, res) => {
});
/**
- * Check whether there are changes to the database-seeds in this local development
+ * Check whether there are changes to the seeds in this local development
* instance that have not been committed yet.
*
* @name GET /api/v1/seeds/has-uncommitted-changes
@@ -79,7 +79,7 @@ router.get("/has-uncommitted-changes", async (req, res) => {
});
}
- // if any change contains database-seeds/collections, it's probably uncommitted
+ // if any change contains seeds/collections, it's probably uncommitted
// local changes.
const hasUncommittedChanges = stdout
.split("\n")
@@ -87,7 +87,7 @@ router.get("/has-uncommitted-changes", async (req, res) => {
// note that doing this properly is frustrating. This has false positives for
// routes that partially contain this route. I've ameliorated this slightly with
// a leading space, but that is not a proper solution.
- .some((row) => / database-seeds\/collections/u.exec(row));
+ .some((row) => / seeds\/collections/u.exec(row));
return res.status(200).json({
success: true,
@@ -120,7 +120,7 @@ router.get(
const file = req.query.file as string | undefined;
const branch = req.query.branch as string;
- const seeds = await PullDatabaseSeeds(LOCAL_SEEDS_PATH);
+ const seeds = await PullDatabaseSeeds();
const collections = (await seeds.ListCollections()).map((e) => `${e}.json`);
if (IsString(file) && !collections.includes(file)) {
@@ -135,10 +135,10 @@ router.get(
// if we don't have a file, use the do-nothing path.
const realFile = file ?? ".";
- // only check commits in database-seeds/collections
+ // only check commits in seeds/collections
const commits = await ListGitCommitsInPath(
branch,
- path.join("database-seeds", "collections", realFile)
+ path.join("seeds", "collections", realFile)
);
return res.status(200).json({
@@ -251,7 +251,7 @@ router.get(
// @warn we don't actually bother doing any real shell
// escaping here, since these routes are only enabled in local development.
const { stdout: fileStdout } = await asyncExec(
- `PAGER=cat git show '${rev}:database-seeds/collections' | tail -n +3`
+ `PAGER=cat git show '${rev}:seeds/collections' | tail -n +3`
);
// @warn this breaks for files that have newlines in
@@ -267,7 +267,7 @@ router.get(
// files in the collection as of this commit, so, this should never
// crash in that way, right?
const { stdout: content } = await asyncExec(
- `PAGER=cat git show '${rev}:database-seeds/collections/${file}'`
+ `PAGER=cat git show '${rev}:seeds/collections/${file}'`
);
data[file] = JSON.parse(content);
diff --git a/sieglinde/README.md b/sieglinde/README.md
index 8fec4d97c..cec3eda88 100644
--- a/sieglinde/README.md
+++ b/sieglinde/README.md
@@ -20,4 +20,4 @@ pnpm calc-v1
## Synchronising with seeds
-Run `ts-node database-seeds/scripts/rerunners/bms-pms/apply-sieglinde`.
\ No newline at end of file
+Run `ts-node seeds/scripts/rerunners/bms-pms/apply-sieglinde`.
\ No newline at end of file
diff --git a/tsconfig.json b/tsconfig.json
index eb8e7e4c9..8899cbc4f 100644
--- a/tsconfig.json
+++ b/tsconfig.json
@@ -33,7 +33,7 @@
"path": "./sieglinde"
},
{
- "path": "./database-seeds/scripts"
+ "path": "./seeds/scripts"
}
]
}
\ No newline at end of file