From cca27aa9716679927f4cf214e1798bf00a03aa51 Mon Sep 17 00:00:00 2001 From: Christoph Schmatzler Date: Sun, 8 Feb 2026 08:27:43 +0000 Subject: [PATCH] Replace fish with nushell --- hosts/chidi/default.nix | 3 +- hosts/jason/default.nix | 3 +- hosts/michael/default.nix | 2 +- hosts/tahani/default.nix | 3 +- profiles/atuin.nix | 2 +- profiles/core.nix | 1 + profiles/darwin.nix | 2 +- profiles/eza.nix | 6 - profiles/fish.nix | 54 --------- profiles/fzf.nix | 1 - profiles/ghostty.nix | 4 +- profiles/git.nix | 46 +++++--- profiles/mise.nix | 2 +- profiles/nixos.nix | 2 +- profiles/nushell.nix | 226 ++++++++++++++++++++++++++++++++++++++ profiles/starship.nix | 2 +- profiles/zoxide.nix | 2 +- 17 files changed, 270 insertions(+), 91 deletions(-) delete mode 100644 profiles/eza.nix delete mode 100644 profiles/fish.nix create mode 100644 profiles/nushell.nix diff --git a/hosts/chidi/default.nix b/hosts/chidi/default.nix index 7a679ca..56347bd 100644 --- a/hosts/chidi/default.nix +++ b/hosts/chidi/default.nix @@ -25,8 +25,7 @@ ../../profiles/bash.nix ../../profiles/bat.nix ../../profiles/direnv.nix - ../../profiles/eza.nix - ../../profiles/fish.nix + ../../profiles/nushell.nix ../../profiles/fzf.nix ../../profiles/ghostty.nix ../../profiles/git.nix diff --git a/hosts/jason/default.nix b/hosts/jason/default.nix index 61b7f15..cadaa0a 100644 --- a/hosts/jason/default.nix +++ b/hosts/jason/default.nix @@ -24,8 +24,7 @@ ../../profiles/bash.nix ../../profiles/bat.nix ../../profiles/direnv.nix - ../../profiles/eza.nix - ../../profiles/fish.nix + ../../profiles/nushell.nix ../../profiles/fzf.nix ../../profiles/ghostty.nix ../../profiles/git.nix diff --git a/hosts/michael/default.nix b/hosts/michael/default.nix index d698125..d601c49 100644 --- a/hosts/michael/default.nix +++ b/hosts/michael/default.nix @@ -39,7 +39,7 @@ home-manager.users.${user} = { imports = [ - ../../profiles/fish.nix + ../../profiles/nushell.nix ../../profiles/home.nix ../../profiles/ssh.nix inputs.nixvim.homeModules.nixvim diff --git a/hosts/tahani/default.nix b/hosts/tahani/default.nix index 9cdde8f..46a5b14 100644 --- a/hosts/tahani/default.nix +++ b/hosts/tahani/default.nix @@ -26,8 +26,7 @@ ../../profiles/bash.nix ../../profiles/bat.nix ../../profiles/direnv.nix - ../../profiles/eza.nix - ../../profiles/fish.nix + ../../profiles/nushell.nix ../../profiles/fzf.nix ../../profiles/git.nix ../../profiles/home.nix diff --git a/profiles/atuin.nix b/profiles/atuin.nix index eb64000..eae1fbe 100644 --- a/profiles/atuin.nix +++ b/profiles/atuin.nix @@ -1,7 +1,7 @@ { programs.atuin = { enable = true; - enableFishIntegration = true; + enableNushellIntegration = true; flags = [ "--disable-up-arrow" ]; diff --git a/profiles/core.nix b/profiles/core.nix index 53a892f..4c0cf55 100644 --- a/profiles/core.nix +++ b/profiles/core.nix @@ -1,5 +1,6 @@ {pkgs, ...}: { programs.fish.enable = true; + environment.shells = [pkgs.nushell]; nixpkgs = { config = { diff --git a/profiles/darwin.nix b/profiles/darwin.nix index db66741..150fd6a 100644 --- a/profiles/darwin.nix +++ b/profiles/darwin.nix @@ -118,7 +118,7 @@ name = user; home = "/Users/${user}"; isHidden = false; - shell = pkgs.fish; + shell = pkgs.nushell; }; home-manager.useGlobalPkgs = true; diff --git a/profiles/eza.nix b/profiles/eza.nix deleted file mode 100644 index bc6ce0c..0000000 --- a/profiles/eza.nix +++ /dev/null @@ -1,6 +0,0 @@ -{ - programs.eza = { - enable = true; - enableFishIntegration = true; - }; -} diff --git a/profiles/fish.nix b/profiles/fish.nix deleted file mode 100644 index 7c5b577..0000000 --- a/profiles/fish.nix +++ /dev/null @@ -1,54 +0,0 @@ -{ - programs.fish = { - enable = true; - functions = { - open_project = '' - set -l base "$HOME/Projects" - set -l choice (fd -t d -d 1 -a . "$base/Personal" "$base/Work" \ - | string replace -r -- "^$base/" "" \ - | fzf --prompt "project > ") - test -n "$choice"; and cd "$base/$choice" - ''; - }; - interactiveShellInit = '' - set fish_greeting - - set fish_color_normal 4c4f69 - set fish_color_command 1e66f5 - set fish_color_param dd7878 - set fish_color_keyword d20f39 - set fish_color_quote 40a02b - set fish_color_redirection ea76cb - set fish_color_end fe640b - set fish_color_comment 8c8fa1 - set fish_color_error d20f39 - set fish_color_gray 9ca0b0 - set fish_color_selection --background=ccd0da - set fish_color_search_match --background=ccd0da - set fish_color_option 40a02b - set fish_color_operator ea76cb - set fish_color_escape e64553 - set fish_color_autosuggestion 9ca0b0 - set fish_color_cancel d20f39 - set fish_color_cwd df8e1d - set fish_color_user 179299 - set fish_color_host 1e66f5 - set fish_color_host_remote 40a02b - set fish_color_status d20f39 - set fish_pager_color_progress 9ca0b0 - set fish_pager_color_prefix ea76cb - set fish_pager_color_completion 4c4f69 - set fish_pager_color_description 9ca0b0 - - set -gx LS_COLORS "$(vivid generate catppuccin-latte)" - - set -gx COLORTERM truecolor - set -gx COLORFGBG "15;0" - set -gx TERM_BACKGROUND light - - for mode in default insert - bind --mode $mode \cp open_project - end - ''; - }; -} diff --git a/profiles/fzf.nix b/profiles/fzf.nix index bd517a3..fc062e0 100644 --- a/profiles/fzf.nix +++ b/profiles/fzf.nix @@ -1,7 +1,6 @@ { programs.fzf = { enable = true; - enableFishIntegration = true; }; home.sessionVariables = { diff --git a/profiles/ghostty.nix b/profiles/ghostty.nix index 87e01f1..fcaba92 100644 --- a/profiles/ghostty.nix +++ b/profiles/ghostty.nix @@ -1,6 +1,6 @@ {pkgs, ...}: { xdg.configFile."ghostty/config".text = '' - command = ${pkgs.fish}/bin/fish + command = ${pkgs.nushell}/bin/nu theme = Catppuccin Latte window-padding-x = 12 window-padding-y = 3 @@ -10,7 +10,7 @@ cursor-style = block mouse-hide-while-typing = true mouse-scroll-multiplier = 1.25 - shell-integration = fish + shell-integration = none shell-integration-features = no-cursor clipboard-read = allow clipboard-write = allow diff --git a/profiles/git.nix b/profiles/git.nix index a83e226..ed495c3 100644 --- a/profiles/git.nix +++ b/profiles/git.nix @@ -95,15 +95,10 @@ in { gf = "git fetch"; gfa = "git fetch --all --tags --prune"; gfo = "git fetch origin"; - gfg = "git ls-files | grep"; gg = "git gui citool"; gga = "git gui citool --amend"; - ggpull = "git pull origin \"$(git branch --show-current)\""; - ggpush = "git push origin \"$(git branch --show-current)\""; - ggsup = "git branch --set-upstream-to=origin/$(git branch --show-current)"; ghh = "git help"; gignore = "git update-index --assume-unchanged"; - gignored = "git ls-files -v | grep \"^[[:lower:]]\""; gl = "git pull"; glg = "git log --stat"; glgp = "git log --stat --patch"; @@ -118,7 +113,6 @@ in { glols = "git log --graph --pretty=\"%Cred%h%Creset -%C(auto)%d%Creset %s %Cgreen(%ar) %C(bold blue)<%an>%Creset\" --stat"; glod = "git log --graph --pretty=\"%Cred%h%Creset -%C(auto)%d%Creset %s %Cgreen(%ad) %C(bold blue)<%an>%Creset\""; glods = "git log --graph --pretty=\"%Cred%h%Creset -%C(auto)%d%Creset %s %Cgreen(%ad) %C(bold blue)<%an>%Creset\" --date=short"; - gluc = "git pull upstream $(git branch --show-current)"; glum = "git pull upstream main"; gm = "git merge"; gma = "git merge --abort"; @@ -133,7 +127,6 @@ in { gpd = "git push --dry-run"; gpf = "git push --force-with-lease"; gpod = "git push origin --delete"; - gpoat = "git push origin --all && git push origin --tags"; gpr = "git pull --rebase"; gpra = "git pull --rebase --autostash"; gprav = "git pull --rebase --autostash -v"; @@ -142,8 +135,6 @@ in { gprv = "git pull --rebase -v"; gprum = "git pull --rebase upstream main"; gprumi = "git pull --rebase=interactive upstream main"; - gpsup = "git push --set-upstream origin $(git branch --show-current)"; - gpsupf = "git push --set-upstream origin $(git branch --show-current) --force-with-lease"; gpv = "git push --verbose"; gpu = "git push upstream"; gr = "git remote"; @@ -169,13 +160,11 @@ in { grm = "git rm"; grmc = "git rm --cached"; grmv = "git remote rename"; - groh = "git reset origin/$(git branch --show-current) --hard"; grrm = "git remote remove"; grs = "git restore"; grset = "git remote set-url"; grss = "git restore --source"; grst = "git restore --staged"; - grt = "cd \"$(git rev-parse --show-toplevel || echo .)\""; gru = "git reset --"; grup = "git remote update"; grv = "git remote --verbose"; @@ -201,16 +190,43 @@ in { gswm = "git switch main"; gta = "git tag --annotate"; gts = "git tag --sign"; - gtv = "git tag | sort -V"; gunignore = "git update-index --no-assume-unchanged"; - gunwip = "git rev-list --max-count=1 --format=\"%s\" HEAD | grep -q \"\\--wip--\" && git reset HEAD~1"; gwch = "git whatchanged -p --abbrev-commit --pretty=medium"; - gwipe = "git reset --hard && git clean --force -df"; gwt = "git worktree"; gwta = "git worktree add"; gwtls = "git worktree list"; gwtmv = "git worktree move"; gwtrm = "git worktree remove"; - gwip = "git add -A; git rm $(git ls-files --deleted) 2> /dev/null; git commit --no-verify --no-gpg-sign --message \"--wip-- [skip ci]\""; }; + + # Complex git aliases that require pipes/subshells — nushell `alias` can't + # handle these, so they're defined as custom commands instead. + programs.nushell.extraConfig = '' + def ggpull [] { git pull origin (git branch --show-current | str trim) } + def ggpush [] { git push origin (git branch --show-current | str trim) } + def ggsup [] { git branch $"--set-upstream-to=origin/(git branch --show-current | str trim)" } + def gluc [] { git pull upstream (git branch --show-current | str trim) } + def gpsup [] { git push --set-upstream origin (git branch --show-current | str trim) } + def gpsupf [] { git push --set-upstream origin (git branch --show-current | str trim) --force-with-lease } + def groh [] { git reset $"origin/(git branch --show-current | str trim)" --hard } + def --env grt [] { + let toplevel = (do { git rev-parse --show-toplevel } | complete | get stdout | str trim) + if ($toplevel | is-not-empty) { cd $toplevel } else { cd . } + } + def gfg [...pattern: string] { git ls-files | lines | where {|f| $f =~ ($pattern | str join ".*") } } + def gignored [] { git ls-files -v | lines | where {|l| ($l | str substring 0..1) =~ "[a-z]" } } + def gpoat [] { git push origin --all; git push origin --tags } + def gtv [] { git tag | lines | sort } + def gwipe [] { git reset --hard; git clean --force -df } + def gunwip [] { + let msg = (git rev-list --max-count=1 --format="%s" HEAD | lines | get 1) + if ($msg | str contains "--wip--") { git reset HEAD~1 } + } + def gwip [] { + git add -A + let deleted = (git ls-files --deleted | lines) + if ($deleted | is-not-empty) { git rm ...$deleted } + git commit --no-verify --no-gpg-sign --message "--wip-- [skip ci]" + } + ''; } diff --git a/profiles/mise.nix b/profiles/mise.nix index 6b56824..10da96b 100644 --- a/profiles/mise.nix +++ b/profiles/mise.nix @@ -1,7 +1,7 @@ { programs.mise = { enable = true; - enableFishIntegration = true; + enableNushellIntegration = true; globalConfig.settings = { auto_install = false; }; diff --git a/profiles/nixos.nix b/profiles/nixos.nix index d45f475..0ebda62 100644 --- a/profiles/nixos.nix +++ b/profiles/nixos.nix @@ -66,7 +66,7 @@ "network" "systemd-journal" ]; - shell = pkgs.fish; + shell = pkgs.nushell; openssh.authorizedKeys.keys = constants.sshKeys; }; diff --git a/profiles/nushell.nix b/profiles/nushell.nix new file mode 100644 index 0000000..b721e4a --- /dev/null +++ b/profiles/nushell.nix @@ -0,0 +1,226 @@ +{pkgs, ...}: { + programs.nushell = { + enable = true; + + settings = { + show_banner = false; + completions = { + algorithm = "fuzzy"; + case_sensitive = false; + }; + history = { + file_format = "sqlite"; + }; + use_ls_colors = true; + }; + + environmentVariables = { + COLORTERM = "truecolor"; + COLORFGBG = "15;0"; + TERM_BACKGROUND = "light"; + }; + + extraEnv = '' + $env.LS_COLORS = (${pkgs.vivid}/bin/vivid generate catppuccin-latte) + ''; + + extraConfig = '' + # --- Catppuccin Latte Theme --- + let theme = { + rosewater: "#dc8a78" + flamingo: "#dd7878" + pink: "#ea76cb" + mauve: "#8839ef" + red: "#d20f39" + maroon: "#e64553" + peach: "#fe640b" + yellow: "#df8e1d" + green: "#40a02b" + teal: "#179299" + sky: "#04a5e5" + sapphire: "#209fb5" + blue: "#1e66f5" + lavender: "#7287fd" + text: "#4c4f69" + subtext1: "#5c5f77" + subtext0: "#6c6f85" + overlay2: "#7c7f93" + overlay1: "#8c8fa1" + overlay0: "#9ca0b0" + surface2: "#acb0be" + surface1: "#bcc0cc" + surface0: "#ccd0da" + base: "#eff1f5" + mantle: "#e6e9ef" + crust: "#dce0e8" + } + + let scheme = { + recognized_command: $theme.blue + unrecognized_command: $theme.text + constant: $theme.peach + punctuation: $theme.overlay2 + operator: $theme.sky + string: $theme.green + virtual_text: $theme.surface2 + variable: { fg: $theme.flamingo attr: i } + filepath: $theme.yellow + } + + $env.config.color_config = { + separator: { fg: $theme.surface2 attr: b } + leading_trailing_space_bg: { fg: $theme.lavender attr: u } + header: { fg: $theme.text attr: b } + row_index: $scheme.virtual_text + record: $theme.text + list: $theme.text + hints: $scheme.virtual_text + search_result: { fg: $theme.base bg: $theme.yellow } + shape_closure: $theme.teal + closure: $theme.teal + shape_flag: { fg: $theme.maroon attr: i } + shape_matching_brackets: { attr: u } + shape_garbage: $theme.red + shape_keyword: $theme.mauve + shape_match_pattern: $theme.green + shape_signature: $theme.teal + shape_table: $scheme.punctuation + cell-path: $scheme.punctuation + shape_list: $scheme.punctuation + shape_record: $scheme.punctuation + shape_vardecl: $scheme.variable + shape_variable: $scheme.variable + empty: { attr: n } + filesize: {|| + if $in < 1kb { + $theme.teal + } else if $in < 10kb { + $theme.green + } else if $in < 100kb { + $theme.yellow + } else if $in < 10mb { + $theme.peach + } else if $in < 100mb { + $theme.maroon + } else if $in < 1gb { + $theme.red + } else { + $theme.mauve + } + } + duration: {|| + if $in < 1day { + $theme.teal + } else if $in < 1wk { + $theme.green + } else if $in < 4wk { + $theme.yellow + } else if $in < 12wk { + $theme.peach + } else if $in < 24wk { + $theme.maroon + } else if $in < 52wk { + $theme.red + } else { + $theme.mauve + } + } + datetime: {|| (date now) - $in | + if $in < 1day { + $theme.teal + } else if $in < 1wk { + $theme.green + } else if $in < 4wk { + $theme.yellow + } else if $in < 12wk { + $theme.peach + } else if $in < 24wk { + $theme.maroon + } else if $in < 52wk { + $theme.red + } else { + $theme.mauve + } + } + shape_external: $scheme.unrecognized_command + shape_internalcall: $scheme.recognized_command + shape_external_resolved: $scheme.recognized_command + shape_block: $scheme.recognized_command + block: $scheme.recognized_command + shape_custom: $theme.pink + custom: $theme.pink + background: $theme.base + foreground: $theme.text + cursor: { bg: $theme.rosewater fg: $theme.base } + shape_range: $scheme.operator + range: $scheme.operator + shape_pipe: $scheme.operator + shape_operator: $scheme.operator + shape_redirection: $scheme.operator + glob: $scheme.filepath + shape_directory: $scheme.filepath + shape_filepath: $scheme.filepath + shape_glob_interpolation: $scheme.filepath + shape_globpattern: $scheme.filepath + shape_int: $scheme.constant + int: $scheme.constant + bool: $scheme.constant + float: $scheme.constant + nothing: $scheme.constant + binary: $scheme.constant + shape_nothing: $scheme.constant + shape_bool: $scheme.constant + shape_float: $scheme.constant + shape_binary: $scheme.constant + shape_datetime: $scheme.constant + shape_literal: $scheme.constant + string: $scheme.string + shape_string: $scheme.string + shape_string_interpolation: $theme.flamingo + shape_raw_string: $scheme.string + shape_externalarg: $scheme.string + } + $env.config.highlight_resolved_externals = true + $env.config.explore = { + status_bar_background: { fg: $theme.text, bg: $theme.mantle }, + command_bar_text: { fg: $theme.text }, + highlight: { fg: $theme.base, bg: $theme.yellow }, + status: { + error: $theme.red, + warn: $theme.yellow, + info: $theme.blue, + }, + selected_cell: { bg: $theme.blue fg: $theme.base }, + } + + # --- Custom Commands --- + def --env open_project [] { + let base = ($env.HOME | path join "Projects") + let choice = ( + ${pkgs.fd}/bin/fd -t d -d 1 -a . ($base | path join "Personal") ($base | path join "Work") + | lines + | each {|p| $p | str replace $"($base)/" "" } + | str join "\n" + | ${pkgs.fzf}/bin/fzf --prompt "project > " + ) + if ($choice | str trim | is-not-empty) { + cd ($base | path join ($choice | str trim)) + } + } + + # --- Keybinding: Ctrl+O for open_project --- + $env.config.keybindings = ($env.config.keybindings | append [ + { + name: open_project + modifier: control + keycode: char_o + mode: [emacs vi_insert vi_normal] + event: { + send: executehostcommand + cmd: "open_project" + } + } + ]) + ''; + }; +} diff --git a/profiles/starship.nix b/profiles/starship.nix index 861dd1e..bba4326 100644 --- a/profiles/starship.nix +++ b/profiles/starship.nix @@ -1,7 +1,7 @@ { programs.starship = { enable = true; - enableFishIntegration = true; + enableNushellIntegration = true; settings = { add_newline = true; command_timeout = 2000; diff --git a/profiles/zoxide.nix b/profiles/zoxide.nix index 14961ed..b056bf6 100644 --- a/profiles/zoxide.nix +++ b/profiles/zoxide.nix @@ -1,6 +1,6 @@ { programs.zoxide = { enable = true; - enableFishIntegration = true; + enableNushellIntegration = true; }; }