{
description = "Replacement for a shell history which records additional command context";
inputs = {
nixpkgs.url = "github:nixos/nixpkgs/nixos-unstable";
rust-overlay = {
url = "github:oxalica/rust-overlay";
inputs.nixpkgs.follows = "nixpkgs";
};
};
outputs = {
self,
nixpkgs,
rust-overlay,
}: let
supportedSystems = ["x86_64-linux" "x86_64-darwin" "aarch64-linux" "aarch64-darwin"];
forAllSystems = nixpkgs.lib.genAttrs supportedSystems;
pkgsFor = system:
import nixpkgs {
inherit system;
overlays = [rust-overlay.overlays.default];
};
atuinPackage = {
lib,
stdenv,
installShellFiles,
rustPlatform,
libiconv,
}:
rustPlatform.buildRustPackage {
pname = "atuin";
version = (nixpkgs.lib.importTOML ./Cargo.toml).workspace.package.version;
src = lib.cleanSource ./.;
cargoLock = {
lockFile = ./Cargo.lock;
# avoid having to set outputHashes manually
allowBuiltinFetchGit = true;
};
nativeBuildInputs = [installShellFiles];
buildInputs = lib.optionals stdenv.isDarwin [libiconv];
postInstall = ''
installShellCompletion --cmd atuin \
--bash <($out/bin/atuin gen-completions -s bash) \
--fish <($out/bin/atuin gen-completions -s fish) \
--zsh <($out/bin/atuin gen-completions -s zsh)
'';
doCheck = false;
meta = {
description = "Replacement for a shell history which records additional command context";
homepage = "https://github.com/atuinsh/atuin";
license = lib.licenses.mit;
mainProgram = "atuin";
};
};
rustPlatformFor = pkgs: let
toolchain = pkgs.rust-bin.fromRustupToolchainFile ./rust-toolchain.toml;
in
pkgs.makeRustPlatform {
cargo = toolchain;
rustc = toolchain;
};
in {
formatter = forAllSystems (system: (pkgsFor system).alejandra);
packages = forAllSystems (system: let
pkgs = pkgsFor system;
in rec {
atuin = pkgs.callPackage atuinPackage {
rustPlatform = rustPlatformFor pkgs;
};
default = atuin;
});
overlays.default = final: _prev: {
atuin = final.callPackage atuinPackage {
rustPlatform = rustPlatformFor final;
};
};
# cloned from home-manager's atuin.nix file
homeManagerModules.atuin = {
config,
lib,
pkgs,
...
}: let
cfg = config.programs.atuin;
tomlFormat = pkgs.formats.toml {};
inherit (lib) mkIf mkOption types;
in {
options.programs.atuin = {
enable = lib.mkEnableOption "atuin, a magical shell history";
package = mkOption {
type = types.package;
default = self.packages.${pkgs.stdenv.hostPlatform.system}.default;
defaultText = lib.literalExpression "atuin.packages.\${system}.default";
description = "The atuin package to use.";
};
enableBashIntegration = lib.hm.shell.mkBashIntegrationOption {
inherit config;
extraDescription = "If enabled, this will bind `ctrl-r` to open the Atuin history.";
};
enableFishIntegration = lib.hm.shell.mkFishIntegrationOption {
inherit config;
extraDescription = "If enabled, this will bind the up-arrow key to open the Atuin history.";
};
enableNushellIntegration = lib.hm.shell.mkNushellIntegrationOption {inherit config;};
enableZshIntegration = lib.hm.shell.mkZshIntegrationOption {
inherit config;
extraDescription = ''
If enabled, this will bind `ctrl-r` and the up-arrow key to open the
Atuin history.
'';
};
flags = mkOption {
default = [];
type = types.listOf types.str;
example = ["--disable-up-arrow" "--disable-ctrl-r"];
description = "Flags to append to the shell hook.";
};
settings = mkOption {
type = with types; let
prim = oneOf [bool int str];
primOrPrimAttrs = either prim (attrsOf prim);
entry = either prim (listOf primOrPrimAttrs);
entryOrAttrsOf = t: either entry (attrsOf t);
entries = entryOrAttrsOf (entryOrAttrsOf entry);
in
attrsOf entries // {description = "Atuin configuration";};
default = {};
example = lib.literalExpression ''
{
search_mode = "prefix";
style = "compact";
}
'';
description = ''
Configuration written to
{file}`$XDG_CONFIG_HOME/atuin/config.toml`.
See for the full list
of options.
'';
};
forceOverwriteSettings = mkOption {
type = types.bool;
default = false;
description = ''
When enabled, force overwriting of the Atuin configuration file
({file}`$XDG_CONFIG_HOME/atuin/config.toml`).
Any existing Atuin configuration will be lost.
Enabling this is useful when adding settings for the first time
because Atuin writes its default config file after every single
shell command, which can make it difficult to manually remove.
'';
};
themes = mkOption {
type = types.attrsOf (types.oneOf [
tomlFormat.type
types.path
types.lines
]);
default = {};
example = lib.literalExpression ''
{
"my-theme" = {
theme.name = "My Theme";
colors = {
Base = "#000000";
Title = "#FFFFFF";
};
};
}
'';
description = ''
Each theme is written to
{file}`$XDG_CONFIG_HOME/atuin/themes/theme-name.toml`
where the name of each attribute is the theme-name.
See for the full list
of options.
'';
};
};
config = let
flagsStr = lib.escapeShellArgs cfg.flags;
atuinFishConfig =
pkgs.runCommand "atuin-fish-config.fish"
{nativeBuildInputs = [pkgs.writableTmpDirAsHomeHook];}
''
${lib.getExe cfg.package} init fish ${flagsStr} > "$out"
'';
in
mkIf cfg.enable (lib.mkMerge [
{
home.packages = [cfg.package];
xdg.configFile = lib.mkMerge [
(mkIf (cfg.settings != {}) {
"atuin/config.toml" = {
source = tomlFormat.generate "atuin-config" cfg.settings;
force = cfg.forceOverwriteSettings;
};
})
(mkIf (cfg.themes != {}) (lib.mapAttrs' (
name: theme:
lib.nameValuePair "atuin/themes/${name}.toml" {
source =
if lib.isString theme
then pkgs.writeText "atuin-theme-${name}" theme
else if builtins.isPath theme || lib.isStorePath theme
then theme
else tomlFormat.generate "atuin-theme-${name}" theme;
}
)
cfg.themes))
];
programs.bash.initExtra = mkIf cfg.enableBashIntegration ''
if [[ :$SHELLOPTS: =~ :(vi|emacs): ]]; then
source "${pkgs.bash-preexec}/share/bash/bash-preexec.sh"
eval "$(${lib.getExe cfg.package} init bash ${flagsStr})"
fi
'';
programs.zsh.initContent = mkIf cfg.enableZshIntegration ''
if [[ $options[zle] = on ]]; then
eval "$(${lib.getExe cfg.package} init zsh ${flagsStr})"
fi
'';
programs.fish.interactiveShellInit = mkIf cfg.enableFishIntegration ''
source ${atuinFishConfig}
'';
programs.nushell = mkIf cfg.enableNushellIntegration {
extraConfig = lib.mkOrder 2000 ''
source ${
pkgs.runCommand "atuin-nushell-config.nu"
{nativeBuildInputs = [pkgs.writableTmpDirAsHomeHook];}
''
${lib.getExe cfg.package} init nu ${flagsStr} >> "$out"
''
}
'';
};
}
(mkIf config.home.preferXdgDirectories {
programs.atuin.settings.logs = {
dir = lib.mkDefault "${config.xdg.stateHome}/atuin/logs";
};
})
]);
};
homeManagerModules.default = self.homeManagerModules.atuin;
devShells = forAllSystems (system: let
pkgs = pkgsFor system;
toolchain = pkgs.rust-bin.fromRustupToolchainFile ./rust-toolchain.toml;
in {
default = pkgs.mkShell {
nativeBuildInputs = [
toolchain
pkgs.rust-analyzer
pkgs.sqlite
];
RUST_SRC_PATH = "${pkgs.rustPlatform.rustLibSrc}";
shellHook = ''
echo >&2 "Setting development database path"
export ATUIN_DB_PATH="/tmp/atuin_dev.db"
if [ -e "''${ATUIN_DB_PATH}" ]; then
echo >&2 "''${ATUIN_DB_PATH} already exists, you might want to double-check that"
fi
'';
};
});
};
}