63 Commits

Author SHA1 Message Date
Smaug123
3efd22bc6b Update flake 2024-12-15 09:59:02 +00:00
Patrick Stevens
3bfb4e7ec5 Freeze tty (#101) 2024-12-13 09:27:45 +00:00
Patrick Stevens
d857f7ab9f Screenshots (#100) 2024-11-29 19:05:10 +00:00
Patrick Stevens
cb3c993507 Fix sway on Earthworm (#99) 2024-11-29 09:41:46 +00:00
Patrick Stevens
14f21cb172 Fix shutdown menu (#98) 2024-11-29 09:35:39 +00:00
Patrick Stevens
02f757bf57 Fix pavucontrol (#96) 2024-11-24 12:47:27 +00:00
Patrick Stevens
6d3804a5ee Start tmux automatically (#95) 2024-11-20 18:29:59 +00:00
Patrick Stevens
6c3bdcc4b2 AMD GPU on capybara (#94) 2024-11-20 18:23:01 +00:00
Patrick Stevens
2123c53980 Bump flake again (#93) 2024-11-14 23:40:40 +00:00
Patrick Stevens
7f712faf2c Bump flake (#92) 2024-11-14 23:31:22 +00:00
Patrick Stevens
5b8b64b0a2 Keepassxc broken on aarch64-darwin (#91) 2024-11-14 23:26:55 +00:00
Patrick Stevens
73efe7a18c Add golang (#90) 2024-10-26 08:25:43 +00:00
Patrick Stevens
f6dd2e686a Add gopls (#89) 2024-10-26 08:20:48 +00:00
Patrick Stevens
df071387b2 Pipewire on Capybara (#88) 2024-10-23 21:00:06 +00:00
Patrick Stevens
f83285dec2 Use Waybar (#87) 2024-10-21 23:23:29 +00:00
Patrick Stevens
9fbcd75842 Use pinentry-curses (#86) 2024-10-21 23:28:21 +01:00
Patrick Stevens
52c08e4183 Fix Earthworm key (#84) 2024-10-21 23:23:37 +01:00
Patrick Stevens
fd1452cc8f Fix tmux after macOS upgrade (#83) 2024-10-20 10:05:03 +01:00
dependabot[bot]
8e7abf90c2 Bump cachix/install-nix-action from 29 to 30 (#82)
Bumps [cachix/install-nix-action](https://github.com/cachix/install-nix-action) from 29 to 30.
- [Release notes](https://github.com/cachix/install-nix-action/releases)
- [Commits](https://github.com/cachix/install-nix-action/compare/v29...v30)

---
updated-dependencies:
- dependency-name: cachix/install-nix-action
  dependency-type: direct:production
  update-type: version-update:semver-major
...

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2024-10-07 09:14:14 +01:00
dependabot[bot]
8d888029bd Bump cachix/install-nix-action from V28 to 29 (#81) 2024-09-30 08:36:19 +01:00
Patrick Stevens
d891582dfe Add regular Nix store optimisation (#80) 2024-09-20 21:37:30 +00:00
Patrick Stevens
fa88755137 Fix mail and Git repo syncing on darwin (#79) 2024-09-20 21:42:08 +01:00
Patrick Stevens
544fc635eb Install pip (#78) 2024-09-20 20:36:06 +00:00
Patrick Stevens
6a493ab214 Delete ltex, it sticks around consuming loads of CPU (#77) 2024-09-20 20:32:54 +00:00
Patrick Stevens
86b6269229 Disable auto-optimise on Darwin (#76) 2024-09-20 18:37:42 +00:00
dependabot[bot]
89e1aa4d7e Bump cachix/install-nix-action from V27 to 28 (#75)
Bumps [cachix/install-nix-action](https://github.com/cachix/install-nix-action) from V27 to 28. This release includes the previously tagged commit.
- [Release notes](https://github.com/cachix/install-nix-action/releases)
- [Commits](https://github.com/cachix/install-nix-action/compare/V27...V28)

---
updated-dependencies:
- dependency-name: cachix/install-nix-action
  dependency-type: direct:production
...

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2024-09-16 10:26:24 +01:00
Patrick Stevens
76dbd82446 Add Rider and nvidia drivers (#73) 2024-09-08 21:25:33 +01:00
Patrick Stevens
ceaead9b63 Some Linux fixes (#72) 2024-09-08 16:45:20 +00:00
Patrick Stevens
88f4111744 Move Capybara to AMD (#71) 2024-09-08 17:31:33 +01:00
Patrick Stevens
3ae9cebd58 Bump nixpkgs and fix some nvim stuff (#70) 2024-08-28 20:00:31 +01:00
Patrick Stevens
5c872e2c4a Add some more stuff (#69) 2024-08-19 22:38:06 +00:00
Patrick Stevens
ede76d2bc2 Update flake (breaks ;; in which-key) (#67) 2024-08-06 20:14:01 +01:00
Patrick Stevens
19e8024a13 Add NuGet upgrade shortcut in neovim (#66) 2024-07-23 17:27:55 +01:00
Patrick Stevens
43c7842fac Fix required checks (#65) 2024-07-14 11:11:32 +01:00
Patrick Stevens
df22898e58 Add clangd (#64) 2024-07-14 11:08:36 +01:00
Patrick Stevens
ad9621acd3 Bump Nixpkgs (#63) 2024-06-29 19:53:49 +01:00
Patrick Stevens
dec7aff312 Bump Nixpkgs (#62) 2024-06-29 10:08:48 +01:00
Patrick Stevens
9649e2e37b Add .NET project filetypes in nvim (#61) 2024-05-31 20:33:33 +00:00
Patrick Stevens
162b58abc4 Difftastic as Git diff tool (#60) 2024-05-27 17:03:47 +00:00
Patrick Stevens
29d80dec16 Neovim non nightly (#59) 2024-05-27 16:55:10 +00:00
dependabot[bot]
1fc72d0288 Bump cachix/install-nix-action from 26 to 27 (#58)
Bumps [cachix/install-nix-action](https://github.com/cachix/install-nix-action) from 26 to 27.
- [Release notes](https://github.com/cachix/install-nix-action/releases)
- [Commits](https://github.com/cachix/install-nix-action/compare/v26...V27)

---
updated-dependencies:
- dependency-name: cachix/install-nix-action
  dependency-type: direct:production
  update-type: version-update:semver-major
...

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2024-05-20 08:24:50 +01:00
Patrick Stevens
8752e0a720 Update Ionide (#57) 2024-05-03 23:10:38 +00:00
Patrick Stevens
cde0abe5d9 Add calendar backup job (#56) 2024-04-28 21:42:54 +00:00
Patrick Stevens
5536c97493 Qualify path to cat (#55) 2024-04-28 21:40:55 +00:00
Patrick Stevens
ad6a4548c6 Capybara Steam and syncthing (#54) 2024-04-19 13:18:22 +00:00
Patrick Stevens
b361bbcbcb Syncthing (#53) 2024-04-19 10:48:11 +00:00
Patrick Stevens
02ceae3e22 Start signing Git commits (#52) 2024-04-15 20:42:29 +00:00
Patrick Stevens
e7f68f24a3 Add gnupg (#51) 2024-04-15 20:11:22 +00:00
Patrick Stevens
3208bf16c5 Split into modules (#50) 2024-04-12 20:33:50 +01:00
Patrick Stevens
ccaa90d392 Delete dead file (#49) 2024-04-06 13:29:21 +00:00
Patrick Stevens
fd71527762 Delete Python workaround (#48) 2024-04-06 11:30:05 +00:00
Patrick Stevens
a210ee4301 Add mail config (#47) 2024-04-06 12:24:52 +01:00
Patrick Stevens
d3ec6b02c3 Ripgrep config from store (#46) 2024-04-06 00:25:15 +01:00
Smaug123
aa3d08745a Everything except the actual mail config 2024-04-05 23:56:57 +01:00
Smaug123
bf1dfe3d6d Rename some vim bindings 2024-04-05 22:16:39 +01:00
Smaug123
d867348640 Move mailcap 2024-04-05 22:14:26 +01:00
Smaug123
7fb26eb707 Add prerequisites for a mail setup 2024-04-05 22:12:12 +01:00
Smaug123
f723b64486 Add more debugger-related commands in Python 2024-04-03 21:21:46 +01:00
Smaug123
7b94e76589 Always enable gutter 2024-04-02 21:08:55 +01:00
Patrick Stevens
68d57ea7cb Add capybara config (#45) 2024-03-30 18:07:17 +00:00
Smaug123
c6879ac254 Delete unnecessary overlay 2024-03-29 12:24:50 +00:00
Smaug123
59e1e8637c More python stuff 2024-03-29 11:58:52 +00:00
Smaug123
6256ad908f Add licence 2024-03-28 15:29:29 +00:00
44 changed files with 2486 additions and 717 deletions

View File

@@ -13,14 +13,16 @@ jobs:
- name: "Checkout"
uses: "actions/checkout@v4"
- name: "Install Nix"
uses: "cachix/install-nix-action@v26"
uses: "cachix/install-nix-action@v30"
with: { "extra_nix_config": "access-tokens = github.com=${{ secrets.GITHUB_TOKEN }}" }
- name: "Check flake"
run: "nix flake check --all-systems"
all-required-checks-complete:
if: ${{ always() }}
runs-on: "ubuntu-latest"
steps:
- run: "echo \"All required checks complete.\""
needs:
- "flake-check"
- uses: Smaug123/all-required-checks-complete-action@05b40a8c47ef0b175ea326e9abb09802cb67b44e
with:
needs-context: ${{ toJSON(needs) }}
needs: [ "flake-check" ]

View File

@@ -1,9 +1,6 @@
This repository currently has no licence applied to it, except for the NeoVim configuration.
That configuration is in large part derived from https://github.com/amix/vimrc and is therefore provided under the following licence.
The MIT License (MIT)
Copyright (c) 2016 Amir Salihefendic
Copyright (c) 2024 Patrick Stevens
Permission is hereby granted, free of charge, to any person obtaining a copy
of this software and associated documentation files (the "Software"), to deal

View File

@@ -1,5 +1,5 @@
{pkgs, ...}: let
python = import ./python.nix {inherit pkgs;};
mbsync = import ./mbsync.nix {inherit pkgs;};
in {
nix.useDaemon = true;
@@ -11,7 +11,7 @@ in {
pkgs.rustup
pkgs.libiconv
pkgs.clang
python
pkgs.python3
];
users.users.patrick = {
@@ -21,22 +21,106 @@ in {
# This line is required; otherwise, on shell startup, you won't have Nix stuff in the PATH.
programs.zsh.enable = true;
programs.gnupg.agent.enable = true;
# Use a custom configuration.nix location.
# $ darwin-rebuild switch -I darwin-config=$HOME/.config/nixpkgs/darwin/configuration.nix
environment.darwinConfig = "$HOME/.nixpkgs/darwin-configuration.nix";
launchd.agents = {
mbsync-btinternet = {
command = "${mbsync}/bin/mbsync BTInternet > /tmp/mbsync.btinternet.log 2>/tmp/mbsync.btinternet.2.log";
serviceConfig = {
KeepAlive = false;
UserName = "patrick";
StartInterval = 60;
RunAtLoad = true;
};
};
mbsync-proton = {
command = "${mbsync}/bin/mbsync Proton > /tmp/mbsync.proton.1.log 2>/tmp/mbsync.proton.2.log";
serviceConfig = {
KeepAlive = false;
UserName = "patrick";
StartInterval = 60;
RunAtLoad = true;
};
};
mbsync-gmail = {
command = "${mbsync}/bin/mbsync Gmail > /tmp/mbsync.gmail.1.log 2>/tmp/mbsync.gmail.2.log";
serviceConfig = {
KeepAlive = false;
UserName = "patrick";
# Refresh token is 60min long, so do this more often than that!
StartInterval = 30;
RunAtLoad = true;
};
};
backup-calendar = {
command = ''${pkgs.bash}/bin/bash -c "mkdir -p '/Users/patrick/Library/Application Support/RadicaleBackups' && if [ ! -d '/Users/patrick/Library/Application Support/RadicaleBackups/.git' ] ; then ${pkgs.git}/bin/git clone root@patrickstevens.co.uk:/preserve/radicale/data/.git '/Users/patrick/Library/Application Support/RadicaleBackups' >/tmp/radicale.out.log 2>/tmp/radicale.err.log; fi && ${pkgs.git}/bin/git --git-dir '/Users/patrick/Library/Application Support/RadicaleBackups/.git' --work-tree '/Users/patrick/Library/Application Support/RadicaleBackups/' pull 2>>/tmp/radicale.err.log"'';
serviceConfig = {
KeepAlive = false;
UserName = "patrick";
StartInterval = 3600;
RunAtLoad = true;
};
};
sync-nixpkgs = {
command = ''${pkgs.bash}/bin/bash -c "if [ -d /Users/patrick/Documents/GitHub/nixpkgs ] ; then ${pkgs.git}/bin/git --git-dir /Users/patrick/Documents/GitHub/nixpkgs/.git --work-tree '/Users/patrick/Documents/GitHub/nixpkgs/' fetch origin ; fi"'';
serviceConfig = {
KeepAlive = false;
UserName = "patrick";
StartInterval = 36000;
RunAtLoad = true;
};
};
sync-dotnet-api-docs = {
command = ''${pkgs.bash}/bin/bash -c "if [ -d /Users/patrick/Documents/GitHub/dotnet-api-docs ] ; then ${pkgs.git}/bin/git --git-dir /Users/patrick/Documents/GitHub/dotnet-api-docs/.git --work-tree '/Users/patrick/Documents/GitHub/dotnet-api-docs' fetch origin ; fi"'';
serviceConfig = {
KeepAlive = false;
UserName = "patrick";
StartInterval = 36000;
RunAtLoad = true;
};
};
sync-dotnet-docs = {
command = ''${pkgs.bash}/bin/bash -c "if [ -d /Users/patrick/Documents/GitHub/dotnet-docs ] ; then ${pkgs.git}/bin/git --git-dir /Users/patrick/Documents/GitHub/dotnet-docs/.git --work-tree '/Users/patrick/Documents/GitHub/dotnet-docs' fetch origin ; fi"'';
serviceConfig = {
KeepAlive = false;
UserName = "patrick";
StartInterval = 36000;
RunAtLoad = true;
};
};
nix-store-optimise = {
command = ''${pkgs.nix}/bin/nix store optimise'';
serviceConfig = {
KeepAlive = false;
UserName = "patrick";
StartInterval = 72000;
RunAtLoad = true;
};
};
};
# Auto upgrade nix package and the daemon service.
services.nix-daemon.enable = true;
nix.package = pkgs.nixVersions.stable;
nix.gc.automatic = true;
nix.nixPath = ["darwin=/nix/store/zq4v3pi2wsfsrjkpk71kcn8srhbwjabf-nix-darwin"];
# Sandbox causes failure: https://github.com/NixOS/nix/issues/4119
nix.settings.sandbox = false;
# Optimising store leads to transient build failures https://github.com/NixOS/nix/issues/7273
nix.extraOptions = ''
auto-optimise-store = true
auto-optimise-store = false
experimental-features = nix-command flakes
extra-experimental-features = ca-derivations
max-jobs = auto # Allow building multiple derivations in parallel

244
flake.lock generated
View File

@@ -7,11 +7,11 @@
"rust-overlay": "rust-overlay"
},
"locked": {
"lastModified": 1710209440,
"narHash": "sha256-1JwFo3u2aVrvpz12OotjCK51EQ0hEDI7xSG7CEvTSk8=",
"lastModified": 1733377410,
"narHash": "sha256-tZ9JEAaHIs3TPdRZeZzHsnJmUilkcnVaUTvyprbRb1A=",
"owner": "tpwrules",
"repo": "nixos-apple-silicon",
"rev": "bdc68b494d6a26c9457f4841ab1a6109b12a33e6",
"rev": "e8c07c3ae199b55a8c1c35a7c067c5cef9c7e929",
"type": "github"
},
"original": {
@@ -27,11 +27,11 @@
]
},
"locked": {
"lastModified": 1710717205,
"narHash": "sha256-Wf3gHh5uV6W1TV/A8X8QJf99a5ypDSugY4sNtdJDe0A=",
"lastModified": 1733570843,
"narHash": "sha256-sQJAxY1TYWD1UyibN/FnN97paTFuwBw3Vp3DNCyKsMk=",
"owner": "lnl7",
"repo": "nix-darwin",
"rev": "bcc8afd06e237df060c85bad6af7128e05fd61a3",
"rev": "a35b08d09efda83625bef267eb24347b446c80b8",
"type": "github"
},
"original": {
@@ -43,18 +43,17 @@
},
"emacs": {
"inputs": {
"flake-utils": "flake-utils",
"nixpkgs": [
"nixpkgs"
],
"nixpkgs-stable": "nixpkgs-stable"
},
"locked": {
"lastModified": 1711271005,
"narHash": "sha256-JrhnnutZvHowEJFIrA/rQAFgGAc83WOx+BVy97teqKM=",
"lastModified": 1734253225,
"narHash": "sha256-LnBb8SyY+WGmBtnQ9XmbpKPoGjOrf89T//xQsKZhJzE=",
"owner": "nix-community",
"repo": "emacs-overlay",
"rev": "d6bbd32eb3e0f167f312e1031c1beee452dc9174",
"rev": "d6353ce807b7845ffec114d234c90ece44c39122",
"type": "github"
},
"original": {
@@ -78,86 +77,10 @@
"type": "github"
}
},
"flake-compat_2": {
"flake": false,
"locked": {
"lastModified": 1696426674,
"narHash": "sha256-kvjfFW7WAETZlt09AgDn1MrtKzP7t90Vf7vypd3OL1U=",
"owner": "edolstra",
"repo": "flake-compat",
"rev": "0f9255e01c2351cc7d116c072cb317785dd33b33",
"type": "github"
},
"original": {
"owner": "edolstra",
"repo": "flake-compat",
"type": "github"
}
},
"flake-parts": {
"inputs": {
"nixpkgs-lib": [
"neovim-nightly",
"nixpkgs"
]
},
"locked": {
"lastModified": 1709336216,
"narHash": "sha256-Dt/wOWeW6Sqm11Yh+2+t0dfEWxoMxGBvv3JpIocFl9E=",
"owner": "hercules-ci",
"repo": "flake-parts",
"rev": "f7b3c975cf067e56e7cda6cb098ebe3fb4d74ca2",
"type": "github"
},
"original": {
"owner": "hercules-ci",
"repo": "flake-parts",
"type": "github"
}
},
"flake-parts_2": {
"inputs": {
"nixpkgs-lib": [
"neovim-nightly",
"hercules-ci-effects",
"nixpkgs"
]
},
"locked": {
"lastModified": 1709336216,
"narHash": "sha256-Dt/wOWeW6Sqm11Yh+2+t0dfEWxoMxGBvv3JpIocFl9E=",
"owner": "hercules-ci",
"repo": "flake-parts",
"rev": "f7b3c975cf067e56e7cda6cb098ebe3fb4d74ca2",
"type": "github"
},
"original": {
"id": "flake-parts",
"type": "indirect"
}
},
"flake-utils": {
"inputs": {
"systems": "systems"
},
"locked": {
"lastModified": 1710146030,
"narHash": "sha256-SZ5L6eA7HJ/nmkzGG7/ISclqe6oZdOZTNoesiInkXPQ=",
"owner": "numtide",
"repo": "flake-utils",
"rev": "b1d9ab70662946ef0850d488da1c9019f3a9752a",
"type": "github"
},
"original": {
"owner": "numtide",
"repo": "flake-utils",
"type": "github"
}
},
"flake-utils_2": {
"inputs": {
"systems": "systems_2"
},
"locked": {
"lastModified": 1701680307,
"narHash": "sha256-kAuep2h5ajznlPMD9rnQyffWG8EM/C73lejGofXvdM8=",
@@ -172,46 +95,6 @@
"type": "github"
}
},
"flake-utils_3": {
"inputs": {
"systems": "systems_3"
},
"locked": {
"lastModified": 1701680307,
"narHash": "sha256-kAuep2h5ajznlPMD9rnQyffWG8EM/C73lejGofXvdM8=",
"owner": "numtide",
"repo": "flake-utils",
"rev": "4022d587cbbfd70fe950c1e2083a02621806a725",
"type": "github"
},
"original": {
"owner": "numtide",
"repo": "flake-utils",
"type": "github"
}
},
"hercules-ci-effects": {
"inputs": {
"flake-parts": "flake-parts_2",
"nixpkgs": [
"neovim-nightly",
"nixpkgs"
]
},
"locked": {
"lastModified": 1710478346,
"narHash": "sha256-Xjf8BdnQG0tLhPMlqQdwCIjOp7Teox0DP3N/jjyiGM4=",
"owner": "hercules-ci",
"repo": "hercules-ci-effects",
"rev": "64e7763d72c1e4c1e5e6472640615b6ae2d40fbf",
"type": "github"
},
"original": {
"owner": "hercules-ci",
"repo": "hercules-ci-effects",
"type": "github"
}
},
"home-manager": {
"inputs": {
"nixpkgs": [
@@ -219,11 +102,11 @@
]
},
"locked": {
"lastModified": 1711133180,
"narHash": "sha256-WJOahf+6115+GMl3wUfURu8fszuNeJLv9qAWFQl3Vmo=",
"lastModified": 1734093295,
"narHash": "sha256-hSwgGpcZtdDsk1dnzA0xj5cNaHgN9A99hRF/mxMtwS4=",
"owner": "nix-community",
"repo": "home-manager",
"rev": "1c2c5e4cabba4c43504ef0f8cc3f3dfa284e2dbb",
"rev": "66c5d8b62818ec4c1edb3e941f55ef78df8141a8",
"type": "github"
},
"original": {
@@ -244,93 +127,45 @@
"url": "https://huggingface.co/ggerganov/whisper.cpp/resolve/main/ggml-large-v3.bin?download=true"
}
},
"neovim-flake": {
"inputs": {
"flake-utils": "flake-utils_2",
"nixpkgs": [
"neovim-nightly",
"nixpkgs"
]
},
"locked": {
"dir": "contrib",
"lastModified": 1711323947,
"narHash": "sha256-Vc478rxwJkMuOcgBXm+brraWk9lbFqrGEdXVuST2l/A=",
"owner": "neovim",
"repo": "neovim",
"rev": "02d00cf3eed6681c6dde40585551c8243d7c003f",
"type": "github"
},
"original": {
"dir": "contrib",
"owner": "neovim",
"repo": "neovim",
"type": "github"
}
},
"neovim-nightly": {
"inputs": {
"flake-compat": "flake-compat_2",
"flake-parts": "flake-parts",
"hercules-ci-effects": "hercules-ci-effects",
"neovim-flake": "neovim-flake",
"nixpkgs": [
"nixpkgs"
]
},
"locked": {
"lastModified": 1711325009,
"narHash": "sha256-c5OJdyuXYzTP+k+PN73X+0pvgXR1yYMYok+72x4SLVg=",
"owner": "nix-community",
"repo": "neovim-nightly-overlay",
"rev": "119bbc295f56b531cb87502f5d2fff13dcc35a35",
"type": "github"
},
"original": {
"owner": "nix-community",
"repo": "neovim-nightly-overlay",
"type": "github"
}
},
"nixpkgs": {
"locked": {
"lastModified": 1709961763,
"narHash": "sha256-6H95HGJHhEZtyYA3rIQpvamMKAGoa8Yh2rFV29QnuGw=",
"lastModified": 1733212471,
"narHash": "sha256-M1+uCoV5igihRfcUKrr1riygbe73/dzNnzPsmaLCmpo=",
"owner": "nixos",
"repo": "nixpkgs",
"rev": "3030f185ba6a4bf4f18b87f345f104e6a6961f34",
"rev": "55d15ad12a74eb7d4646254e13638ad0c4128776",
"type": "github"
},
"original": {
"owner": "nixos",
"ref": "nixos-unstable",
"repo": "nixpkgs",
"rev": "3030f185ba6a4bf4f18b87f345f104e6a6961f34",
"type": "github"
}
},
"nixpkgs-stable": {
"locked": {
"lastModified": 1711124224,
"narHash": "sha256-l0zlN/3CiodvWDtfBOVxeTwYSRz93muVbXWSpaMjXxM=",
"lastModified": 1734083684,
"narHash": "sha256-5fNndbndxSx5d+C/D0p/VF32xDiJCJzyOqorOYW4JEo=",
"owner": "NixOS",
"repo": "nixpkgs",
"rev": "56528ee42526794d413d6f244648aaee4a7b56c0",
"rev": "314e12ba369ccdb9b352a4db26ff419f7c49fa84",
"type": "github"
},
"original": {
"owner": "NixOS",
"ref": "nixos-23.11",
"ref": "nixos-24.11",
"repo": "nixpkgs",
"type": "github"
}
},
"nixpkgs_2": {
"locked": {
"lastModified": 1711231723,
"narHash": "sha256-dARJQ8AJOv6U+sdRePkbcVyVbXJTi1tReCrkkOeusiA=",
"lastModified": 1733935885,
"narHash": "sha256-xyiHLs6KJ1fxeGmcCxKjJE4yJknVJxbC8Y/ZRYyC8WE=",
"owner": "NixOS",
"repo": "nixpkgs",
"rev": "e1d501922fd7351da4200e1275dfcf5faaad1220",
"rev": "5a48e3c2e435e95103d56590188cfed7b70e108c",
"type": "github"
},
"original": {
@@ -361,7 +196,6 @@
"darwin": "darwin",
"emacs": "emacs",
"home-manager": "home-manager",
"neovim-nightly": "neovim-nightly",
"nixpkgs": "nixpkgs_2",
"whisper": "whisper"
}
@@ -397,39 +231,9 @@
"type": "github"
}
},
"systems_2": {
"locked": {
"lastModified": 1681028828,
"narHash": "sha256-Vy1rq5AaRuLzOxct8nz4T6wlgyUR7zLU309k9mBC768=",
"owner": "nix-systems",
"repo": "default",
"rev": "da67096a3b9bf56a91d16901293e51ba5b49a27e",
"type": "github"
},
"original": {
"owner": "nix-systems",
"repo": "default",
"type": "github"
}
},
"systems_3": {
"locked": {
"lastModified": 1681028828,
"narHash": "sha256-Vy1rq5AaRuLzOxct8nz4T6wlgyUR7zLU309k9mBC768=",
"owner": "nix-systems",
"repo": "default",
"rev": "da67096a3b9bf56a91d16901293e51ba5b49a27e",
"type": "github"
},
"original": {
"owner": "nix-systems",
"repo": "default",
"type": "github"
}
},
"whisper": {
"inputs": {
"flake-utils": "flake-utils_3",
"flake-utils": "flake-utils",
"model": "model",
"nixpkgs": "nixpkgs_3"
},

View File

@@ -24,14 +24,9 @@
whisper = {
url = "github:Smaug123/whisper.cpp/nix";
};
neovim-nightly = {
url = "github:nix-community/neovim-nightly-overlay";
inputs.nixpkgs.follows = "nixpkgs";
};
};
outputs = {
neovim-nightly,
darwin,
emacs,
nixpkgs,
@@ -46,7 +41,7 @@
};
systems = ["aarch64-darwin" "aarch64-linux" "x86_64-linux"];
in let
overlays = [emacs.overlay neovim-nightly.overlay] ++ import ./overlays.nix;
overlays = [emacs.overlay];
recursiveMerge = attrList: let
f = attrPath:
builtins.zipAttrsWith (n: values:
@@ -61,6 +56,36 @@
f [] attrList;
in {
nixosConfigurations = {
capybara = let
system = "x86_64-linux";
in let
pkgs = import nixpkgs {inherit system config overlays;};
in
nixpkgs.lib.nixosSystem {
inherit system;
specialArgs = {
username = "patrick";
dotnet = pkgs.dotnet-sdk_8;
};
modules = let
args = {
nixpkgs = pkgs;
username = "patrick";
dotnet = pkgs.dotnet-sdk_8;
mbsync = import ./mbsync.nix {inherit pkgs;};
secretsPath = "/home/patrick/.secrets/";
machinename = "capybara";
};
in [
./home-manager/capybara-config.nix
home-manager.nixosModules.home-manager
{
home-manager.useGlobalPkgs = true;
home-manager.useUserPackages = true;
home-manager.users.patrick = recursiveMerge [(import ./home-manager/linux.nix args) (import ./home-manager/home.nix args)];
}
];
};
earthworm = let
system = "aarch64-linux";
in let
@@ -73,6 +98,9 @@
nixpkgs = pkgs;
username = "patrick";
dotnet = pkgs.dotnet-sdk_8;
mbsync = import ./mbsync.nix {inherit pkgs;};
secretsPath = "/home/patrick/.secrets/";
machinename = "earthworm";
};
in [
./home-manager/earthworm-config.nix
@@ -81,7 +109,7 @@
{
home-manager.useGlobalPkgs = true;
home-manager.useUserPackages = true;
home-manager.users.patrick = recursiveMerge [(import ./home-manager/earthworm.nix args) (import ./home-manager/home.nix args)];
home-manager.users.patrick = recursiveMerge [(import ./home-manager/linux.nix args) (import ./home-manager/home.nix args)];
}
];
};
@@ -100,6 +128,9 @@
username = "patrick";
dotnet = pkgs.dotnet-sdk_8;
whisper = whisper.packages.${system};
mbsync = import ./mbsync.nix {inherit pkgs;};
secretsPath = "/Users/patrick/.secrets/";
machinename = "darwin";
};
in [
./darwin-configuration.nix

43
hardware/capybara.nix Normal file
View File

@@ -0,0 +1,43 @@
# Do not modify this file! It was generated by nixos-generate-config
# and may be overwritten by future invocations. Please make changes
# to /etc/nixos/configuration.nix instead.
{
config,
lib,
pkgs,
modulesPath,
...
}: {
imports = [
(modulesPath + "/installer/scan/not-detected.nix")
];
boot.initrd.availableKernelModules = ["nvme" "xhci_pci" "ahci" "usb_storage" "usbhid" "sd_mod"];
boot.initrd.kernelModules = [];
boot.kernelModules = ["kvm-amd"];
boot.extraModulePackages = [];
fileSystems."/" = {
device = "/dev/disk/by-uuid/63c5394d-55ce-48a9-8d7c-2b68f3b5f834";
fsType = "ext4";
};
fileSystems."/boot" = {
device = "/dev/disk/by-uuid/9248-31C6";
fsType = "vfat";
options = ["fmask=0022" "dmask=0022"];
};
swapDevices = [];
# Enables DHCP on each ethernet and wireless interface. In case of scripted networking
# (the default) this is the recommended approach. When using systemd-networkd it's
# still possible to use this option, but it's recommended to use it in conjunction
# with explicit per-interface declarations with `networking.interfaces.<interface>.useDHCP`.
networking.useDHCP = lib.mkDefault true;
# networking.interfaces.eno1.useDHCP = lib.mkDefault true;
# networking.interfaces.wlp9s0.useDHCP = lib.mkDefault true;
nixpkgs.hostPlatform = lib.mkDefault "x86_64-linux";
hardware.cpu.amd.updateMicrocode = lib.mkDefault config.hardware.enableRedistributableFirmware;
}

View File

@@ -0,0 +1,88 @@
{
pkgs,
config,
...
}: {
nixpkgs.config.allowUnfree = true;
imports = [
../hardware/capybara.nix
];
hardware.graphics = {
enable = true;
enable32Bit = true;
};
security.rtkit.enable = true;
services.pipewire = {
enable = true;
alsa.enable = true;
alsa.support32Bit = true;
pulse.enable = true;
};
boot.loader.systemd-boot.enable = true;
boot.loader.efi.canTouchEfiVariables = true;
boot.loader.grub.useOSProber = true;
boot.kernelParams = [
"video=DP-1:2560x1440@144"
"video=HDMI-A-1:1920x1080@144"
];
boot.extraModulePackages = [config.boot.kernelPackages.rtl8821au];
networking = {
hostName = "capybara";
networkmanager.enable = true;
};
time.timeZone = "Europe/London";
programs.sway.enable = true;
programs.zsh.enable = true;
# TODO: work out secrets management for password, then set mutableUsers to false
users.mutableUsers = true;
users.users.patrick = {
isNormalUser = true;
extraGroups = ["wheel" "networkManager"];
};
services.syncthing = {
enable = true;
user = "patrick";
dataDir = "/home/patrick/syncthing";
};
environment.systemPackages = [
pkgs.git
pkgs.vim
pkgs.wget
pkgs.tmux
pkgs.home-manager
pkgs.firefox
];
environment.loginShellInit = ''
[[ "$(tty)" == /dev/tty1 ]] && sway --unsupported-gpu
'';
services.openssh.enable = true;
system.stateVersion = "23.11";
nix.settings.experimental-features = ["nix-command" "flakes" "ca-derivations"];
nix.gc.automatic = true;
nix.extraOptions = ''
auto-optimise-store = true
max-jobs = auto
keep-outputs = true
keep-derivations = true
'';
programs.steam = {
enable = true;
remotePlay.openFirewall = true;
};
}

View File

@@ -1,13 +1,33 @@
{
config,
pkgs,
...
}: {
{pkgs, ...}: {
imports = [
../hardware/earthworm.nix
];
hardware.asahi.peripheralFirmwareDirectory = ../firmware;
hardware.asahi.peripheralFirmwareDirectory = ./../firmware;
hardware.asahi = {
useExperimentalGPUDriver = true;
experimentalGPUInstallMode = "overlay";
setupAsahiSound = true;
withRust = true;
};
hardware.graphics.enable = true;
programs.light.enable = true;
services.actkbd = {
enable = true;
bindings = [
{
keys = [225];
events = ["key"];
command = "${pkgs.light}/bin/light -A 10";
}
{
keys = [224];
events = ["key"];
command = "${pkgs.light}/bin/light -U 10";
}
];
};
boot.loader.systemd-boot.enable = true;
boot.loader.efi.canTouchEfiVariables = false;
@@ -18,6 +38,10 @@
networking = {
hostName = "earthworm";
networkmanager.enable = true;
wireless.iwd = {
enable = true;
settings.General.EnableNetworkConfiguration = true;
};
};
time.timeZone = "Europe/London";
@@ -35,10 +59,11 @@
environment.systemPackages = [
pkgs.vim
pkgs.wget
pkgs.mesa-asahi-edge
];
environment.loginShellInit = ''
[[ "$(tty)" == /dev/tty1 ]] && sway
[[ "$(tty)" == /dev/tty1 ]] && export WLR_RENDER_NO_EXPLICIT_SYNC=1 && sway
'';
services.openssh.enable = true;

View File

@@ -1,29 +0,0 @@
{
nixpkgs,
username,
dotnet,
...
}: {
home.packages = [nixpkgs.firefox-wayland];
nixpkgs.config.firefox.speechSynthesisSupport = true;
wayland.windowManager.sway = {
enable = true;
config = {
focus = {followMouse = false;};
modifier = "Mod4";
terminal = "alacritty";
window = {border = 5;};
};
extraConfig = ''
output Unknown-1 scale 2
'';
};
services.swayidle = {enable = true;};
services.cbatticon = {
lowLevelPercent = 20;
iconType = "standard";
enable = true;
};
}

View File

@@ -1,7 +1,10 @@
{
nixpkgs,
machinename,
username,
mbsync,
dotnet,
secretsPath,
...
}: {
# Let Home Manager install and manage itself.
@@ -23,60 +26,47 @@
fonts.fontconfig.enable = true;
programs.tmux = {
shell = "${nixpkgs.zsh}/bin/zsh";
escapeTime = 50;
mouse = false;
prefix = "C-b";
enable = true;
terminal = "screen-256color";
extraConfig = ''
set-option -sa terminal-features ',xterm-256color:RGB'
'';
};
programs.zsh = {
enable = true;
autocd = true;
autosuggestion.enable = true;
enableCompletion = true;
history = {
expireDuplicatesFirst = true;
};
sessionVariables = {
EDITOR = "vim";
LC_ALL = "en_US.UTF-8";
LC_CTYPE = "en_US.UTF-8";
RUSTFLAGS = "-L ${nixpkgs.libiconv}/lib -L ${nixpkgs.libcxx}/lib";
RUST_BACKTRACE = "full";
};
shellAliases = {
vim = "nvim";
view = "vim -R";
grep = "${nixpkgs.ripgrep}/bin/rg";
};
sessionVariables = {
RIPGREP_CONFIG_PATH = "/Users/${username}/.config/ripgrep/config";
};
initExtra = builtins.readFile ./.zshrc;
};
imports = [
# ./modules/agda.nix
# ./modules/emacs.nix
./modules/direnv.nix
./modules/tmux.nix
./modules/zsh.nix
./modules/ripgrep.nix
./modules/alacritty.nix
./modules/rust.nix
(import ./modules/mail.nix
{
inherit mbsync secretsPath;
pkgs = nixpkgs;
})
];
programs.fzf = {
enable = true;
enableZshIntegration = true;
};
programs.git = {
package = nixpkgs.gitAndTools.gitFull;
enable = true;
userName = "Smaug123";
userEmail = "patrick+github@patrickstevens.co.uk";
userEmail = "3138005+Smaug123@users.noreply.github.com";
aliases = {
co = "checkout";
st = "status";
};
delta = {enable = true;};
difftastic.enable = true;
extraConfig = {
commit.gpgsign = true;
gpg.program = "${nixpkgs.gnupg}/bin/gpg";
user.signingkey =
if machinename == "darwin"
then "7C97D679CF3BC4F9"
else if machinename == "earthworm"
then "6E8B1BA1148AD7C9"
else if machinename == "capybara"
then "AE90453E879DBCFA"
else throw "unrecognised machine name!";
core = {
autocrlf = "input";
};
@@ -138,34 +128,24 @@
};
};
services.syncthing = {
enable = true;
};
programs.neovim = let
pynvimpp = nixpkgs.python3.pkgs.buildPythonPackage {
pname = "pynvim-pp";
version = "unstable-2024-03-24";
pyproject = true;
src = nixpkgs.fetchFromGitHub {
owner = "ms-jpq";
repo = "pynvim_pp";
rev = "34e3a027c595981886d7efd1c91071f3eaa4715d";
hash = "sha256-2+jDRJXlg9q4MN9vOhmeq4cWVJ0wp5r5xAh3G8lqgOg=";
};
nativeBuildInputs = [nixpkgs.python3.pkgs.setuptools];
propagatedBuildInputs = [nixpkgs.python3.pkgs.pynvim];
};
in let
pythonEnv = nixpkgs.python3.withPackages (ps: [
ps.pynvim
pynvimpp
ps.pyyaml
ps.std2
]);
debugPyEnv = nixpkgs.python3.withPackages (ps: [ps.debugpy]);
in {
enable = true;
plugins = [
{
plugin = nixpkgs.vimPlugins.nvim-web-devicons;
}
{
plugin = nixpkgs.vimPlugins.mini-nvim;
}
{
plugin = nixpkgs.vimPlugins.satellite-nvim;
}
{
plugin = nixpkgs.vimPlugins.nvim-lightbulb;
type = "lua";
@@ -271,109 +251,90 @@
vimAlias = true;
vimdiffAlias = true;
withPython3 = true;
extraPython3Packages = ps: [
ps.pip
ps.pynvim
ps.pynvim-pp
ps.pyyaml
ps.std2
];
withRuby = true;
extraLuaConfig = builtins.readFile ./nvim/build-utils.lua + "\n" + builtins.readFile ./nvim/dotnet.lua + "\n" + builtins.replaceStrings ["%PYTHONENV%"] ["${pythonEnv}"] (builtins.readFile ./nvim/init.lua);
package = nixpkgs.neovim-nightly;
extraLuaConfig = builtins.readFile ./nvim/build-utils.lua + "\n" + (builtins.replaceStrings ["_CURL_"] ["${nixpkgs.curl}/bin/curl"] (builtins.readFile ./nvim/dotnet.lua)) + "\n" + builtins.readFile ./nvim/init.lua + "\n" + builtins.readFile ./nvim/python.lua;
};
programs.direnv = {
enable = true;
enableZshIntegration = true;
nix-direnv.enable = true;
};
home.packages =
[
nixpkgs.jq
nixpkgs.difftastic
nixpkgs.syncthing
nixpkgs.nodePackages_latest.dockerfile-language-server-nodejs
nixpkgs.nodePackages_latest.bash-language-server
nixpkgs.nodePackages_latest.vscode-json-languageserver
nixpkgs.nodePackages_latest.vscode-langservers-extracted
nixpkgs.hadolint
nixpkgs.yaml-language-server
nixpkgs.csharp-ls
nixpkgs.netcoredbg
nixpkgs.nil
nixpkgs.fsautocomplete
nixpkgs.wget
nixpkgs.yt-dlp
nixpkgs.cmake
nixpkgs.gnumake
nixpkgs.gcc
nixpkgs.lldb
nixpkgs.hledger
nixpkgs.hledger-web
dotnet
nixpkgs.elan
nixpkgs.coreutils-prefixed
nixpkgs.shellcheck
nixpkgs.universal-ctags
nixpkgs.asciinema
nixpkgs.git-lfs
nixpkgs.imagemagick
nixpkgs.nixpkgs-fmt
nixpkgs.age
nixpkgs.nodejs
nixpkgs.pyright
nixpkgs.woodpecker-agent
nixpkgs.lynx
nixpkgs.alejandra
nixpkgs.ffmpeg
nixpkgs.bat
nixpkgs.pandoc
nixpkgs.fd
nixpkgs.sumneko-lua-language-server
nixpkgs.gnupg
nixpkgs.gh
nixpkgs.clang-tools
nixpkgs.deno
nixpkgs.yazi
nixpkgs.font-awesome
nixpkgs.gopls
nixpkgs.go
]
++ (
if nixpkgs.stdenv.isLinux
then [
nixpkgs.protonmail-bridge
nixpkgs.pinentry
nixpkgs.signal-desktop
nixpkgs.keepassxc
]
else []
)
++ (
if machinename == "capybara"
then [
nixpkgs.steam-run
nixpkgs.discord
nixpkgs.anki-bin
]
else []
);
programs.alacritty = {
enable = true;
settings = {
font = {
normal = {
family = "FiraCode Nerd Font Mono";
};
};
};
};
home.packages = [
nixpkgs.nodePackages_latest.dockerfile-language-server-nodejs
nixpkgs.nodePackages_latest.bash-language-server
nixpkgs.nodePackages_latest.vscode-json-languageserver
nixpkgs.nodePackages_latest.vscode-langservers-extracted
nixpkgs.hadolint
nixpkgs.ltex-ls
nixpkgs.yaml-language-server
nixpkgs.csharp-ls
nixpkgs.netcoredbg
nixpkgs.nil
nixpkgs.fsautocomplete
nixpkgs.keepassxc
nixpkgs.rust-analyzer
nixpkgs.tmux
nixpkgs.wget
nixpkgs.yt-dlp
nixpkgs.cmake
nixpkgs.gnumake
nixpkgs.gcc
nixpkgs.lldb
nixpkgs.hledger
nixpkgs.hledger-web
dotnet
nixpkgs.jitsi-meet
nixpkgs.ripgrep
nixpkgs.elan
nixpkgs.coreutils-prefixed
nixpkgs.shellcheck
nixpkgs.html-tidy
nixpkgs.hugo
nixpkgs.agda
nixpkgs.pijul
nixpkgs.universal-ctags
nixpkgs.asciinema
nixpkgs.git-lfs
nixpkgs.imagemagick
nixpkgs.nixpkgs-fmt
nixpkgs.grpc-tools
nixpkgs.element-desktop
nixpkgs.ihp-new
nixpkgs.direnv
nixpkgs.lnav
nixpkgs.age
nixpkgs.nodejs
nixpkgs.nodePackages.pyright
nixpkgs.sqlitebrowser
nixpkgs.typst
nixpkgs.poetry
nixpkgs.woodpecker-agent
nixpkgs.alacritty
nixpkgs.lynx
nixpkgs.alejandra
nixpkgs.ffmpeg
nixpkgs.bat
nixpkgs.pandoc
nixpkgs.fd
nixpkgs.sumneko-lua-language-server
(nixpkgs.nerdfonts.override {fonts = ["FiraCode" "DroidSansMono"];})
];
home.file.".mailcap".source = ./mailcap;
home.file.".ideavimrc".source = ./ideavimrc;
home.file.".config/yt-dlp/config".source = ./youtube-dl.conf;
home.file.".config/ripgrep/config".source = ./ripgrep.conf;
programs.emacs = {
enable = true;
package = nixpkgs.emacs;
extraPackages = epkgs: [epkgs.evil];
extraConfig = ''
(load-file (let ((coding-system-for-read 'utf-8))
(shell-command-to-string "agda-mode locate")))
(require 'evil)
(evil-mode 1)
(evil-set-undo-system 'undo-redo)
;; Allow hash to be entered
(global set-key (kbd "M-3") '(lambda () (interactive) (insert "#")))
'';
};
home.file.".cargo/config.toml".source = ./cargo-config.toml;
}

204
home-manager/linux.nix Normal file
View File

@@ -0,0 +1,204 @@
{nixpkgs, ...}: {
home.packages = [nixpkgs.firefox-wayland nixpkgs.jetbrains.rider];
nixpkgs.config.firefox.speechSynthesisSupport = true;
wayland.windowManager.sway = {
enable = true;
config = {
focus = {followMouse = false;};
modifier = "Mod4";
terminal = "alacritty";
window = {border = 5;};
bars = [
{command = "${nixpkgs.waybar}/bin/waybar";}
];
};
extraConfig = builtins.replaceStrings ["@@WL-COPY@@" "@@GRIM@@" "@@SLURP@@"] ["${nixpkgs.wl-clipboard}/bin/wl-copy" "${nixpkgs.grim}/bin/grim" "${nixpkgs.slurp}/bin/slurp"] (builtins.readFile ./sway.conf);
};
programs.waybar = {
enable = true;
settings = {
"bar-0" = {
position = "bottom";
layer = "top";
height = 34;
spacing = 8;
modules-left = ["sway/workspaces" "sway/mode" "sway/scratchpad" "custom/media"];
modules-center = ["sway/window"];
modules-right = ["mpd" "idle_inhibitor" "pulseaudio" "network" "power-profiles-daemon" "cpu" "memory" "temperature" "backlight" "keyboard-state" "sway/language" "battery" "battery#bat2" "clock" "tray" "custom/power"];
"keyboard-state" = {
"numlock" = true;
"capslock" = true;
"format" = "{name} {icon}";
"format-icons" = {
"locked" = "";
"unlocked" = "";
};
};
"sway/mode" = {
"format" = "<span style=\"italic\">{}</span>";
};
"sway/scratchpad" = {
"format" = "{icon} {count}";
"show-empty" = false;
"format-icons" = ["" ""];
"tooltip" = true;
"tooltip-format" = "{app}: {title}";
};
"mpd" = {
"format" = "{stateIcon} {consumeIcon}{randomIcon}{repeatIcon}{singleIcon}{artist} - {album} - {title} ({elapsedTime:%M:%S}/{totalTime:%M:%S}) {songPosition}|{queueLength} {volume}% ";
"format-disconnected" = "Disconnected ";
"format-stopped" = "{consumeIcon}{randomIcon}{repeatIcon}{singleIcon}Stopped ";
"unknown-tag" = "N/A";
"interval" = 5;
"consume-icons" = {
"on" = " ";
};
"random-icons" = {
"off" = "<span color=\"#f53c3c\"></span> ";
"on" = " ";
};
"repeat-icons" = {
"on" = " ";
};
"single-icons" = {
"on" = "1 ";
};
"state-icons" = {
"paused" = "";
"playing" = "";
};
"tooltip-format" = "MPD (connected)";
"tooltip-format-disconnected" = "MPD (disconnected)";
};
"idle_inhibitor" = {
"format" = "{icon}";
"format-icons" = {
"activated" = "";
"deactivated" = "";
};
};
"tray" = {
"spacing" = 20;
};
"clock" = {
"tooltip-format" = "<big>{:%Y %B}</big>\n<tt><small>{calendar}</small></tt>";
"format" = "{:%Y-%m-%d %H:%M:%S}";
"interval" = 1;
};
"cpu" = {
"format" = "{usage}% ";
"tooltip" = false;
};
"memory" = {
"format" = "{}% ";
};
"temperature" = {
"critical-threshold" = 80;
"format" = "{temperatureC}°C {icon}";
"format-icons" = ["" "" ""];
};
"backlight" = {
"format" = "{percent}% {icon}";
"format-icons" = ["" "" "" "" "" "" "" "" ""];
};
"battery" = {
"states" = {
"warning" = 30;
"critical" = 15;
};
"format" = "{capacity}% {icon}";
"format-full" = "{capacity}% {icon}";
"format-charging" = "{capacity}% ";
"format-plugged" = "{capacity}% ";
"format-alt" = "{time} {icon}";
"format-icons" = ["" "" "" "" ""];
};
"battery#bat2" = {
"bat" = "BAT2";
};
"power-profiles-daemon" = {
"format" = "{icon}";
"tooltip-format" = "Power profile: {profile}\nDriver: {driver}";
"tooltip" = true;
"format-icons" = {
"default" = "";
"performance" = "";
"balanced" = "";
"power-saver" = "";
};
};
"network" = {
"format-wifi" = "{essid} ({signalStrength}%) ";
"format-ethernet" = "{bandwidthDownBytes}/{bandwidthUpBytes} ";
"interval" = 5;
"tooltip-format" = "{ifname} via {gwaddr} ";
"format-linked" = "{ifname} (No IP) ";
"format-disconnected" = "Disconnected ";
"format-alt" = "{ifname}: {ipaddr}/{cidr}";
};
"pulseaudio" = {
"format" = "{volume}% {icon} {format_source}";
"format-bluetooth" = "{volume}% {icon} {format_source}";
"format-bluetooth-muted" = " {icon} {format_source}";
"format-muted" = " {format_source}";
"format-source" = "{volume}% ";
"format-source-muted" = "";
"format-icons" = {
"headphone" = "";
"hands-free" = "";
"headset" = "";
"phone" = "";
"portable" = "";
"car" = "";
"default" = ["" "" ""];
};
"on-click" = "${nixpkgs.pavucontrol}/bin/pavucontrol";
};
"custom/media" = {
"format" = "{icon} {text}";
"return-type" = "json";
"max-length" = 40;
"format-icons" = {
"spotify" = "";
"default" = "🎜";
};
"escape" = true;
"exec" = let
python = nixpkgs.python312.withPackages (ppkgs: [ppkgs.pygobject3]);
in "${python}/bin/python ${./modules/waybar/mediaplayer.py} 2> /dev/null";
};
"custom/power" = {
"format" = " ";
"tooltip" = false;
"menu" = "on-click";
"menu-file" = ./modules/waybar/power_menu.xml;
"menu-actions" = {
"shutdown" = "shutdown now";
"reboot" = "reboot";
"suspend" = "systemctl suspend";
"hibernate" = "systemctl hibernate";
};
};
};
};
style = ''
* {
font-family: FontAwesome, Roboto, Helvetica, Arial, sans-serif;
font-size: 13px;
}
'';
};
services.gpg-agent = {
enable = nixpkgs.stdenv.isLinux;
pinentryPackage = nixpkgs.pinentry-curses;
};
services.swayidle = {enable = true;};
}

View File

@@ -0,0 +1,7 @@
{pkgs, ...}: {
imports = [./emacs.nix];
home.packages = [
pkgs.agda
];
}

View File

@@ -0,0 +1,19 @@
{pkgs, ...}: {
programs.alacritty = {
enable = true;
settings = {
font = {
normal = {
family = "FiraCode Nerd Font Mono";
};
};
terminal = {shell = "${pkgs.zsh}/bin/zsh";};
};
};
home.packages = [
pkgs.alacritty
pkgs.nerd-fonts.fira-code
pkgs.nerd-fonts.droid-sans-mono
];
}

View File

@@ -0,0 +1,10 @@
{pkgs, ...}: {
home.packages = [
pkgs.direnv
];
programs.direnv = {
enable = true;
enableZshIntegration = true;
nix-direnv.enable = true;
};
}

View File

@@ -0,0 +1,14 @@
{pkgs, ...}: {
programs.emacs = {
enable = true;
package = pkgs.emacs;
extraPackages = epkgs: [epkgs.evil];
extraConfig = ''
(load-file (let ((coding-system-for-read 'utf-8))
(shell-command-to-string "agda-mode locate")))
(require 'evil)
(evil-mode 1)
(evil-set-undo-system 'undo-redo)
'';
};
}

View File

@@ -0,0 +1,182 @@
{
pkgs,
mbsync,
secretsPath,
...
}: let
deobfuscate = str: let
lib = pkgs.lib;
base64Table =
builtins.listToAttrs
(lib.imap0 (i: c: lib.nameValuePair c i)
(lib.stringToCharacters "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+/"));
# Generated using python3:
# print(''.join([ chr(n) for n in range(1, 256) ]), file=open('ascii', 'w'))
ascii = builtins.readFile ./mail/ascii;
# List of base-64 numbers
numbers64 = map (c: base64Table.${c}) (lib.lists.reverseList (lib.stringToCharacters str));
# List of base-256 numbers
numbers256 = lib.concatLists (lib.genList (
i: let
v =
lib.foldl'
(acc: el: acc * 64 + el)
0
(lib.sublist (i * 4) 4 numbers64);
in [
(lib.mod (v / 256 / 256) 256)
(lib.mod (v / 256) 256)
(lib.mod v 256)
]
) (lib.length numbers64 / 4));
in
# Converts base-256 numbers to ascii
lib.concatMapStrings (
n:
# Can't represent the null byte in Nix..
let
result = lib.substring (n - 1) 1 ascii;
in
if result == " "
then ""
else result
)
numbers256;
in {
accounts.email.accounts."Gmail" = let
address = (deobfuscate "AFTN0cWdh12c") + "gmail.com";
in {
notmuch.enable = true;
neomutt = {
enable = true;
};
address = address;
flavor = "gmail.com";
mbsync = {
enable = true;
create = "maildir";
extraConfig.account = {
AuthMechs = "XOAUTH2";
};
};
userName = address;
# This is accompanied by a developer application at Google:
# https://console.cloud.google.com/apis/credentials
# Create an OAuth 2.0 Client ID with type `Desktop`.
# The Google application needs the https://mail.google.com scope; mine has
# an authorized domain `google.com` but I don't know if that's required.
# Enter the client ID and client secret into a two-line text file
# named gmail-client-app.txt immediately next to the intended destination
# secret file (the arg to mutt-oauth2.py in the invocation):
# so here it would be /path/to/gmail-client-app.txt .
# Run `./mail/mutt-oauth2.py /path/to/secret --authorize --verbose` once manually,
# and that will populate /path/to/secret.
# I've left it unencrypted here; the original uses GPG to store it encrypted at rest.
passwordCommand = ''${pkgs.python3}/bin/python ${./mail/mutt-oauth2.py} ${secretsPath}/gmail.txt 2>/tmp/gmail-passcmd.2.txt'';
realName = "Patrick Stevens";
};
accounts.email.accounts."BTInternet" = let
address = (deobfuscate "z5WZ2VGdz5yajlmc0FGc") + "@btinternet.com";
in {
notmuch.enable = true;
neomutt = {
enable = true;
};
address = address;
imap = {
host = "mail.btinternet.com";
port = 993;
tls = {
enable = true;
useStartTls = false;
};
};
mbsync = {
enable = true;
create = "maildir";
};
realName = "Patrick Stevens";
passwordCommand = "${pkgs.coreutils}/bin/cat ${secretsPath}/btinternet.txt";
smtp = {
host = "mail.btinternet.com";
port = 465;
tls = {
enable = true;
useStartTls = false;
};
};
userName = address;
primary = true;
};
accounts.email.accounts."Proton" = let
address = deobfuscate "gAya15ybj5ycuVmdlR3crNWayRXYwB0ajlmc0FGc";
in {
notmuch.enable = true;
neomutt = {
enable = true;
};
address = address;
# I use the ProtonMail bridge, which sits at localhost.
imap = {
host = "127.0.0.1";
port = 1143; # 8125; if using hydroxide
tls = {
enable = false;
useStartTls = true;
};
};
mbsync = {
enable = true;
create = "maildir";
extraConfig.account = {
# Because ProtonMail Bridge is localhost, we don't
# care that we can only auth to it in plain text.
AuthMechs = "LOGIN";
};
};
realName = "Patrick Stevens";
passwordCommand =
# I store the ProtonMail Bridge password here.
# Extracting it from a keychain would be better.
"${pkgs.coreutils}/bin/cat ${secretsPath}/proton.txt";
smtp = {
host = "127.0.0.1";
port = 1025; # 8126; if using hydroxide
tls = {enable = false;};
};
userName =
if pkgs.stdenv.isLinux
then builtins.head (pkgs.lib.strings.splitString "@" address)
else address;
};
programs.mbsync = {
enable = true;
extraConfig = ''
CopyArrivalDate yes
'';
package = mbsync;
};
programs.neomutt = {
enable = true;
extraConfig = ''
set use_threads=threads sort=last-date sort_aux=date
'';
sidebar.enable = true;
vimKeys = true;
};
programs.notmuch.enable = true;
home.file.".mailcap".source = ./mail/mailcap;
home.packages = [
pkgs.notmuch
pkgs.lynx
];
}

View File

@@ -0,0 +1,2 @@


View File

@@ -0,0 +1,402 @@
#!/usr/bin/env python3
#
# Mutt OAuth2 token management script, version 2020-08-07
# Written against python 3.7.3, not tried with earlier python versions.
#
# Copyright (C) 2020 Alexander Perlis
#
# This program is free software; you can redistribute it and/or
# modify it under the terms of the GNU General Public License as
# published by the Free Software Foundation; either version 2 of the
# License, or (at your option) any later version.
#
# This program is distributed in the hope that it will be useful,
# but WITHOUT ANY WARRANTY; without even the implied warranty of
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
# General Public License for more details.
#
# You should have received a copy of the GNU General Public License
# along with this program; if not, write to the Free Software
# Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA
# 02110-1301, USA.
# Subsequently adapted by Patrick Stevens, who hacked it up to read gmail
# client app configuration from a file called gmail-client-app.txt that
# lives next to the secret file.
'''Mutt OAuth2 token management'''
import sys
import json
import argparse
import urllib.error
import urllib.parse
import urllib.request
import imaplib
import poplib
import smtplib
import base64
import secrets
import hashlib
import time
from datetime import timedelta, datetime
from pathlib import Path
import socket
import http.server
import subprocess
ap = argparse.ArgumentParser(epilog='''
This script obtains and prints a valid OAuth2 access token. State is maintained in an
encrypted TOKENFILE. Run with "--verbose --authorize" to get started or whenever all
tokens have expired, optionally with "--authflow" to override the default authorization
flow. To truly start over from scratch, first delete TOKENFILE. Use "--verbose --test"
to test the IMAP/POP/SMTP endpoints.
''')
ap.add_argument('-v', '--verbose', action='store_true', help='increase verbosity')
ap.add_argument('-d', '--debug', action='store_true', help='enable debug output')
ap.add_argument('tokenfile', help='persistent token storage')
ap.add_argument('-a', '--authorize', action='store_true', help='manually authorize new tokens')
ap.add_argument('--authflow', help='authcode | localhostauthcode | devicecode')
ap.add_argument('-t', '--test', action='store_true', help='test IMAP/POP/SMTP endpoints')
args = ap.parse_args()
token = {}
path = Path(args.tokenfile)
if path.exists():
if 0o777 & path.stat().st_mode != 0o600:
sys.exit('Token file has unsafe mode. Suggest deleting and starting over.')
try:
token = json.loads(path.read_bytes())
except subprocess.CalledProcessError:
sys.exit('Difficulty decrypting token file. Is your decryption agent primed for '
'non-interactive usage, or an appropriate environment variable such as '
'GPG_TTY set to allow interactive agent usage from inside a pipe?')
client_id, client_secret = (path.parent / "gmail-client-app.txt").read_text().strip().split('\n')
registrations = {
'google': {
'authorize_endpoint': 'https://accounts.google.com/o/oauth2/auth',
'devicecode_endpoint': 'https://oauth2.googleapis.com/device/code',
'token_endpoint': 'https://accounts.google.com/o/oauth2/token',
'redirect_uri': 'urn:ietf:wg:oauth:2.0:oob',
'imap_endpoint': 'imap.gmail.com',
'pop_endpoint': 'pop.gmail.com',
'smtp_endpoint': 'smtp.gmail.com',
'sasl_method': 'OAUTHBEARER',
'scope': 'https://mail.google.com/',
'client_id': client_id,
'client_secret': client_secret,
},
}
def writetokenfile():
'''Writes global token dictionary into token file.'''
if not path.exists():
path.touch(mode=0o600)
if 0o777 & path.stat().st_mode != 0o600:
sys.exit('Token file has unsafe mode. Suggest deleting and starting over.')
path.write_bytes(json.dumps(token).encode('utf-8'))
if args.debug:
print('Obtained from token file:', json.dumps(token))
if not token:
if not args.authorize:
sys.exit('You must run script with "--authorize" at least once.')
print('Available app and endpoint registrations:', *registrations)
token['registration'] = input('OAuth2 registration: ')
token['authflow'] = input('Preferred OAuth2 flow ("authcode" or "localhostauthcode" '
'or "devicecode"): ')
token['email'] = input('Account e-mail address: ')
token['access_token'] = ''
token['access_token_expiration'] = ''
token['refresh_token'] = ''
writetokenfile()
if token['registration'] not in registrations:
sys.exit(f'ERROR: Unknown registration "{token["registration"]}". Delete token file '
f'and start over.')
registration = registrations[token['registration']]
authflow = token['authflow']
if args.authflow:
authflow = args.authflow
baseparams = {'client_id': registration['client_id']}
# Microsoft uses 'tenant' but Google does not
if 'tenant' in registration:
baseparams['tenant'] = registration['tenant']
def access_token_valid():
'''Returns True when stored access token exists and is still valid at this time.'''
token_exp = token['access_token_expiration']
return token_exp and datetime.now() < datetime.fromisoformat(token_exp)
def update_tokens(r):
'''Takes a response dictionary, extracts tokens out of it, and updates token file.'''
token['access_token'] = r['access_token']
token['access_token_expiration'] = (datetime.now() +
timedelta(seconds=int(r['expires_in']))).isoformat()
if 'refresh_token' in r:
token['refresh_token'] = r['refresh_token']
writetokenfile()
if args.verbose:
print(f'NOTICE: Obtained new access token, expires {token["access_token_expiration"]}.')
if args.authorize:
p = baseparams.copy()
p['scope'] = registration['scope']
if authflow in ('authcode', 'localhostauthcode'):
verifier = secrets.token_urlsafe(90)
challenge = base64.urlsafe_b64encode(hashlib.sha256(verifier.encode()).digest())[:-1]
redirect_uri = registration['redirect_uri']
listen_port = 0
if authflow == 'localhostauthcode':
# Find an available port to listen on
s = socket.socket()
s.bind(('127.0.0.1', 0))
listen_port = s.getsockname()[1]
s.close()
redirect_uri = 'http://localhost:'+str(listen_port)+'/'
# Probably should edit the port number into the actual redirect URL.
p.update({'login_hint': token['email'],
'response_type': 'code',
'redirect_uri': redirect_uri,
'code_challenge': challenge,
'code_challenge_method': 'S256'})
print(registration["authorize_endpoint"] + '?' +
urllib.parse.urlencode(p, quote_via=urllib.parse.quote))
authcode = ''
if authflow == 'authcode':
authcode = input('Visit displayed URL to retrieve authorization code. Enter '
'code from server (might be in browser address bar): ')
else:
print('Visit displayed URL to authorize this application. Waiting...',
end='', flush=True)
class MyHandler(http.server.BaseHTTPRequestHandler):
'''Handles the browser query resulting from redirect to redirect_uri.'''
# pylint: disable=C0103
def do_HEAD(self):
'''Response to a HEAD requests.'''
self.send_response(200)
self.send_header('Content-type', 'text/html')
self.end_headers()
def do_GET(self):
'''For GET request, extract code parameter from URL.'''
# pylint: disable=W0603
global authcode
querystring = urllib.parse.urlparse(self.path).query
querydict = urllib.parse.parse_qs(querystring)
if 'code' in querydict:
authcode = querydict['code'][0]
self.do_HEAD()
self.wfile.write(b'<html><head><title>Authorizaton result</title></head>')
self.wfile.write(b'<body><p>Authorization redirect completed. You may '
b'close this window.</p></body></html>')
with http.server.HTTPServer(('127.0.0.1', listen_port), MyHandler) as httpd:
try:
httpd.handle_request()
except KeyboardInterrupt:
pass
if not authcode:
sys.exit('Did not obtain an authcode.')
for k in 'response_type', 'login_hint', 'code_challenge', 'code_challenge_method':
del p[k]
p.update({'grant_type': 'authorization_code',
'code': authcode,
'client_secret': registration['client_secret'],
'code_verifier': verifier})
print('Exchanging the authorization code for an access token')
try:
response = urllib.request.urlopen(registration['token_endpoint'],
urllib.parse.urlencode(p).encode())
except urllib.error.HTTPError as err:
print(err.code, err.reason)
response = err
response = response.read()
if args.debug:
print(response)
response = json.loads(response)
if 'error' in response:
print(response['error'])
if 'error_description' in response:
print(response['error_description'])
sys.exit(1)
elif authflow == 'devicecode':
try:
response = urllib.request.urlopen(registration['devicecode_endpoint'],
urllib.parse.urlencode(p).encode())
except urllib.error.HTTPError as err:
print(err.code, err.reason)
response = err
response = response.read()
if args.debug:
print(response)
response = json.loads(response)
if 'error' in response:
print(response['error'])
if 'error_description' in response:
print(response['error_description'])
sys.exit(1)
print(response['message'])
del p['scope']
p.update({'grant_type': 'urn:ietf:params:oauth:grant-type:device_code',
'client_secret': registration['client_secret'],
'device_code': response['device_code']})
interval = int(response['interval'])
print('Polling...', end='', flush=True)
while True:
time.sleep(interval)
print('.', end='', flush=True)
try:
response = urllib.request.urlopen(registration['token_endpoint'],
urllib.parse.urlencode(p).encode())
except urllib.error.HTTPError as err:
# Not actually always an error, might just mean "keep trying..."
response = err
response = response.read()
if args.debug:
print(response)
response = json.loads(response)
if 'error' not in response:
break
if response['error'] == 'authorization_declined':
print(' user declined authorization.')
sys.exit(1)
if response['error'] == 'expired_token':
print(' too much time has elapsed.')
sys.exit(1)
if response['error'] != 'authorization_pending':
print(response['error'])
if 'error_description' in response:
print(response['error_description'])
sys.exit(1)
print()
else:
sys.exit(f'ERROR: Unknown OAuth2 flow "{token["authflow"]}. Delete token file and '
f'start over.')
update_tokens(response)
if not access_token_valid():
if args.verbose:
print('NOTICE: Invalid or expired access token; using refresh token '
'to obtain new access token.')
if not token['refresh_token']:
sys.exit('ERROR: No refresh token. Run script with "--authorize".')
p = baseparams.copy()
p.update({'client_secret': registration['client_secret'],
'refresh_token': token['refresh_token'],
'grant_type': 'refresh_token'})
try:
response = urllib.request.urlopen(registration['token_endpoint'],
urllib.parse.urlencode(p).encode())
except urllib.error.HTTPError as err:
print(err.code, err.reason)
response = err
response = response.read()
if args.debug:
print(response)
response = json.loads(response)
if 'error' in response:
print(response['error'])
if 'error_description' in response:
print(response['error_description'])
print('Perhaps refresh token invalid. Try running once with "--authorize"')
sys.exit(1)
update_tokens(response)
if not access_token_valid():
sys.exit('ERROR: No valid access token. This should not be able to happen.')
if args.verbose:
print('Access Token: ', end='')
print(token['access_token'])
def build_sasl_string(user, host, port, bearer_token):
'''Build appropriate SASL string, which depends on cloud server's supported SASL method.'''
if registration['sasl_method'] == 'OAUTHBEARER':
return f'n,a={user},\1host={host}\1port={port}\1auth=Bearer {bearer_token}\1\1'
if registration['sasl_method'] == 'XOAUTH2':
return f'user={user}\1auth=Bearer {bearer_token}\1\1'
sys.exit(f'Unknown SASL method {registration["sasl_method"]}.')
if args.test:
errors = False
imap_conn = imaplib.IMAP4_SSL(registration['imap_endpoint'])
sasl_string = build_sasl_string(token['email'], registration['imap_endpoint'], 993,
token['access_token'])
if args.debug:
imap_conn.debug = 4
try:
imap_conn.authenticate(registration['sasl_method'], lambda _: sasl_string.encode())
# Microsoft has a bug wherein a mismatch between username and token can still report a
# successful login... (Try a consumer login with the token from a work/school account.)
# Fortunately subsequent commands fail with an error. Thus we follow AUTH with another
# IMAP command before reporting success.
imap_conn.list()
if args.verbose:
print('IMAP authentication succeeded')
except imaplib.IMAP4.error as e:
print('IMAP authentication FAILED (does your account allow IMAP?):', e)
errors = True
pop_conn = poplib.POP3_SSL(registration['pop_endpoint'])
sasl_string = build_sasl_string(token['email'], registration['pop_endpoint'], 995,
token['access_token'])
if args.debug:
pop_conn.set_debuglevel(2)
try:
# poplib doesn't have an auth command taking an authenticator object
# Microsoft requires a two-line SASL for POP
# pylint: disable=W0212
pop_conn._shortcmd('AUTH ' + registration['sasl_method'])
pop_conn._shortcmd(base64.standard_b64encode(sasl_string.encode()).decode())
if args.verbose:
print('POP authentication succeeded')
except poplib.error_proto as e:
print('POP authentication FAILED (does your account allow POP?):', e.args[0].decode())
errors = True
# SMTP_SSL would be simpler but Microsoft does not answer on port 465.
smtp_conn = smtplib.SMTP(registration['smtp_endpoint'], 587)
sasl_string = build_sasl_string(token['email'], registration['smtp_endpoint'], 587,
token['access_token'])
smtp_conn.ehlo('test')
smtp_conn.starttls()
smtp_conn.ehlo('test')
if args.debug:
smtp_conn.set_debuglevel(2)
try:
smtp_conn.auth(registration['sasl_method'], lambda _=None: sasl_string)
if args.verbose:
print('SMTP authentication succeeded')
except smtplib.SMTPAuthenticationError as e:
print('SMTP authentication FAILED:', e)
errors = True
if errors:
sys.exit(1)

View File

@@ -0,0 +1,7 @@
{pkgs, ...}: {
home.packages = [
pkgs.ripgrep
];
home.file.".config/ripgrep/config".source = ./ripgrep/ripgrep.conf;
}

View File

@@ -0,0 +1,10 @@
{pkgs, ...}: {
programs.zsh.sessionVariables = {
RUSTFLAGS = "-L ${pkgs.libiconv}/lib -L ${pkgs.libcxx}/lib";
RUST_BACKTRACE = "full";
};
home.file.".cargo/config.toml".source = ./rust/cargo-config.toml;
home.packages = [
pkgs.rust-analyzer
];
}

View File

@@ -0,0 +1,19 @@
{pkgs, ...}: {
imports = [./zsh.nix];
home.packages = [
pkgs.tmux
];
programs.tmux = {
shell = "${pkgs.zsh}/bin/zsh";
escapeTime = 50;
mouse = false;
prefix = "C-b";
enable = true;
terminal = "screen-256color";
extraConfig = ''
set-option -sa terminal-features ',xterm-256color:RGB'
set -g default-command "exec ${pkgs.zsh}/bin/zsh"
'';
};
}

View File

@@ -0,0 +1,199 @@
#!/usr/bin/env python3
# MIT licenced, https://github.com/Alexays/Waybar/blob/dacecb9b265c1c7c36ee43d17526fa95f4e6596f/resources/custom_modules/mediaplayer.py
# See licence in power_menu.xml
import gi
gi.require_version("Playerctl", "2.0")
from gi.repository import Playerctl, GLib
from gi.repository.Playerctl import Player
import argparse
import logging
import sys
import signal
import gi
import json
import os
from typing import List
logger = logging.getLogger(__name__)
def signal_handler(sig, frame):
logger.info("Received signal to stop, exiting")
sys.stdout.write("\n")
sys.stdout.flush()
# loop.quit()
sys.exit(0)
class PlayerManager:
def __init__(self, selected_player=None, excluded_player=[]):
self.manager = Playerctl.PlayerManager()
self.loop = GLib.MainLoop()
self.manager.connect(
"name-appeared", lambda *args: self.on_player_appeared(*args))
self.manager.connect(
"player-vanished", lambda *args: self.on_player_vanished(*args))
signal.signal(signal.SIGINT, signal_handler)
signal.signal(signal.SIGTERM, signal_handler)
signal.signal(signal.SIGPIPE, signal.SIG_DFL)
self.selected_player = selected_player
self.excluded_player = excluded_player.split(',') if excluded_player else []
self.init_players()
def init_players(self):
for player in self.manager.props.player_names:
if player.name in self.excluded_player:
continue
if self.selected_player is not None and self.selected_player != player.name:
logger.debug(f"{player.name} is not the filtered player, skipping it")
continue
self.init_player(player)
def run(self):
logger.info("Starting main loop")
self.loop.run()
def init_player(self, player):
logger.info(f"Initialize new player: {player.name}")
player = Playerctl.Player.new_from_name(player)
player.connect("playback-status",
self.on_playback_status_changed, None)
player.connect("metadata", self.on_metadata_changed, None)
self.manager.manage_player(player)
self.on_metadata_changed(player, player.props.metadata)
def get_players(self) -> List[Player]:
return self.manager.props.players
def write_output(self, text, player):
logger.debug(f"Writing output: {text}")
output = {"text": text,
"class": "custom-" + player.props.player_name,
"alt": player.props.player_name}
sys.stdout.write(json.dumps(output) + "\n")
sys.stdout.flush()
def clear_output(self):
sys.stdout.write("\n")
sys.stdout.flush()
def on_playback_status_changed(self, player, status, _=None):
logger.debug(f"Playback status changed for player {player.props.player_name}: {status}")
self.on_metadata_changed(player, player.props.metadata)
def get_first_playing_player(self):
players = self.get_players()
logger.debug(f"Getting first playing player from {len(players)} players")
if len(players) > 0:
# if any are playing, show the first one that is playing
# reverse order, so that the most recently added ones are preferred
for player in players[::-1]:
if player.props.status == "Playing":
return player
# if none are playing, show the first one
return players[0]
else:
logger.debug("No players found")
return None
def show_most_important_player(self):
logger.debug("Showing most important player")
# show the currently playing player
# or else show the first paused player
# or else show nothing
current_player = self.get_first_playing_player()
if current_player is not None:
self.on_metadata_changed(current_player, current_player.props.metadata)
else:
self.clear_output()
def on_metadata_changed(self, player, metadata, _=None):
logger.debug(f"Metadata changed for player {player.props.player_name}")
player_name = player.props.player_name
artist = player.get_artist()
title = player.get_title()
title = title.replace("&", "&amp;")
track_info = ""
if player_name == "spotify" and "mpris:trackid" in metadata.keys() and ":ad:" in player.props.metadata["mpris:trackid"]:
track_info = "Advertisement"
elif artist is not None and title is not None:
track_info = f"{artist} - {title}"
else:
track_info = title
if track_info:
if player.props.status == "Playing":
track_info = "" + track_info
else:
track_info = "" + track_info
# only print output if no other player is playing
current_playing = self.get_first_playing_player()
if current_playing is None or current_playing.props.player_name == player.props.player_name:
self.write_output(track_info, player)
else:
logger.debug(f"Other player {current_playing.props.player_name} is playing, skipping")
def on_player_appeared(self, _, player):
logger.info(f"Player has appeared: {player.name}")
if player.name in self.excluded_player:
logger.debug(
"New player appeared, but it's in exclude player list, skipping")
return
if player is not None and (self.selected_player is None or player.name == self.selected_player):
self.init_player(player)
else:
logger.debug(
"New player appeared, but it's not the selected player, skipping")
def on_player_vanished(self, _, player):
logger.info(f"Player {player.props.player_name} has vanished")
self.show_most_important_player()
def parse_arguments():
parser = argparse.ArgumentParser()
# Increase verbosity with every occurrence of -v
parser.add_argument("-v", "--verbose", action="count", default=0)
parser.add_argument("-x", "--exclude", "- Comma-separated list of excluded player")
# Define for which player we"re listening
parser.add_argument("--player")
parser.add_argument("--enable-logging", action="store_true")
return parser.parse_args()
def main():
arguments = parse_arguments()
# Initialize logging
if arguments.enable_logging:
logfile = os.path.join(os.path.dirname(
os.path.realpath(__file__)), "media-player.log")
logging.basicConfig(filename=logfile, level=logging.DEBUG,
format="%(asctime)s %(name)s %(levelname)s:%(lineno)d %(message)s")
# Logging is set by default to WARN and higher.
# With every occurrence of -v it's lowered by one
logger.setLevel(max((3 - arguments.verbose) * 10, 0))
logger.info("Creating player manager")
if arguments.player:
logger.info(f"Filtering for player: {arguments.player}")
if arguments.exclude:
logger.info(f"Exclude player {arguments.exclude}")
player = PlayerManager(arguments.player, arguments.exclude)
player.run()
if __name__ == "__main__":
main()

View File

@@ -0,0 +1,53 @@
<?xml version="1.0" encoding="UTF-8"?>
<!-- Source: https://github.com/Alexays/Waybar/blob/dacecb9b265c1c7c36ee43d17526fa95f4e6596f/resources/custom_modules/power_menu.xml -->
<!-- MIT licence available at https://github.com/Alexays/Waybar/blob/dacecb9b265c1c7c36ee43d17526fa95f4e6596f/LICENSE
MIT License
Copyright (c) 2018 Alex
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.
-->
<interface>
<object class="GtkMenu" id="menu">
<child>
<object class="GtkMenuItem" id="suspend">
<property name="label">Suspend</property>
</object>
</child>
<child>
<object class="GtkMenuItem" id="hibernate">
<property name="label">Hibernate</property>
</object>
</child>
<child>
<object class="GtkMenuItem" id="shutdown">
<property name="label">Shutdown</property>
</object>
</child>
<child>
<object class="GtkSeparatorMenuItem" id="delimiter1"/>
</child>
<child>
<object class="GtkMenuItem" id="reboot">
<property name="label">Reboot</property>
</object>
</child>
</object>
</interface>

View File

@@ -0,0 +1,23 @@
{pkgs, ...}: {
programs.zsh = {
enable = true;
autocd = true;
autosuggestion.enable = true;
enableCompletion = true;
history = {
expireDuplicatesFirst = true;
};
sessionVariables = {
EDITOR = "vim";
LC_ALL = "en_US.UTF-8";
LC_CTYPE = "en_US.UTF-8";
};
shellAliases = {
vim = "nvim";
view = "vim -R";
};
initExtra = builtins.readFile ./zsh/zshrc;
};
programs.fzf.enableZshIntegration = true;
}

View File

@@ -27,5 +27,12 @@ export WORDCHARS=''
autoload edit-command-line
zle -N edit-command-line
bindkey '^X^E' edit-command-line
bindkey -e
PATH="$PATH:$HOME/.cargo/bin"
if [[ -z "$TMUX" ]]; then
tmux new-session -A -s default
fi
ttyctl -f

View File

@@ -73,13 +73,14 @@ function RegisterSolution(sln_path)
end
local whichkey = require("which-key")
whichkey.register({
s = {
name = ".NET solution",
b = { BuildDotNetSolution, "Build .NET solution" },
t = { TestDotNetSolution, "Test .NET solution" },
whichkey.add({
{
"<localleader>s",
desc = ".NET solution",
},
}, { prefix = vim.api.nvim_get_var("maplocalleader"), buffer = vim.api.nvim_get_current_buf() })
{ "<localleader>sb", BuildDotNetSolution, desc = "Build .NET solution" },
{ "<localleader>st", TestDotNetSolution, desc = "Test .NET solution" },
}, { buffer = vim.api.nvim_get_current_buf() })
end
local function find_nearest_slns()
@@ -153,8 +154,612 @@ vim.api.nvim_create_autocmd({ "BufReadPost", "BufNewFile" }, {
})
vim.api.nvim_create_autocmd("FileType", {
pattern = { "fsharp", "cs" },
pattern = { "fsharp", "cs", "fsharp_project" },
callback = function()
FindAndRegisterSolution(false)
end,
})
-- For what I'm sure are reasons, Lua appears to have nothing in its standard library
---@generic K
---@generic V1
---@generic V2
---@param tbl table<K, V1>
---@param f fun(V1): V2
---@return table<K, V2>
local function map(tbl, f)
local t = {}
for k, v in pairs(tbl) do
t[k] = f(v)
end
return t
end
---@generic K
---@generic V
---@param tbl table<K, V>
---@param f fun(V1): nil
local function iter(tbl, f)
for _, v in pairs(tbl) do
f(v)
end
end
---@generic K
---@generic V
---@param tbl table<K, V>
---@param predicate fun(V): boolean
---@return boolean, V
local function find(tbl, predicate)
for _, v in pairs(tbl) do
if predicate(v) then
return true, v
end
end
return false, nil
end
---@class (exact) NuGetVersion
---@field major number
---@field minor number
---@field patch number
---@field suffix? string
local NuGetVersion = {}
---@param v NuGetVersion
---@nodiscard
---@return string
local function nuGetVersionToString(v)
local s = tostring(v.major) .. "." .. tostring(v.minor) .. "." .. tostring(v.patch)
if v.suffix then
return s .. v.suffix
else
return s
end
end
local function get_all_variables()
local variables = {}
local lines = vim.api.nvim_buf_get_lines(0, 0, -1, false)
for _, line in ipairs(lines) do
local var_name, var_value = line:match("<(%w+)>([^<]+)</(%w+)>")
if var_name and var_value then
variables[var_name] = var_value
end
end
return variables
end
local function resolve_variable(version, variables)
if version:match("^%$%((.+)%)$") then
local var_name = version:match("^%$%((.+)%)$")
return variables[var_name] or nil
end
return nil
end
---@param v string
---@nodiscard
---@return NuGetVersion
local function parse_version(v)
local variables = get_all_variables()
local major, minor, patch, pre = v:match("(%d+)%.(%d+)%.(%d+)(.*)$")
if major == nil then
local resolved = resolve_variable(v, variables)
if resolved ~= nil then
return parse_version(resolved)
end
end
-- TODO: why does this type-check if you remove the field names?
return {
major = tonumber(major) or 0,
minor = tonumber(minor) or 0,
patch = tonumber(patch) or 0,
suffix = pre or nil,
}
end
---@param a NuGetVersion
---@param b NuGetVersion
---@nodiscard
---@return boolean
local function compare_versions(a, b)
if a.major ~= b.major then
return a.major < b.major
elseif a.minor ~= b.minor then
return a.minor < b.minor
elseif a.patch ~= b.patch then
return a.patch < b.patch
elseif a.suffix and not b.suffix then
return false
elseif not a.suffix and b.suffix then
return true
else
return a.suffix < b.suffix
end
end
---@param url string
---@nodiscard
local function curl_sync(url)
local command = string.format("_CURL_ --silent --compressed --fail '%s'", url)
local response = vim.fn.system(command)
if vim.v.shell_error ~= 0 then
print("Failed to fetch " .. url)
return nil
end
local success, decoded = pcall(vim.fn.json_decode, response)
if not success then
print("Failed to decode JSON from curl at " .. url)
return nil
end
return decoded
end
---@param url string
---@param callback fun(table): nil
---@return nil
local function curl(url, callback)
local stdout = vim.uv.new_pipe(false)
local stdout_text = ""
local handle
handle, _ = vim.uv.spawn(
"_CURL_",
{ args = { "--silent", "--compressed", "--fail", url }, stdio = { nil, stdout, nil } },
vim.schedule_wrap(function(code, _)
stdout:read_stop()
stdout:close()
if handle and not handle:is_closing() then
handle:close()
end
if code ~= 0 then
print("Failed to fetch " .. url)
end
local success, decoded = pcall(vim.fn.json_decode, stdout_text)
if not success then
print("Failed to decode JSON from curl at " .. url .. "\n" .. stdout_text)
end
callback(decoded)
end)
)
vim.uv.read_start(stdout, function(err, data)
assert(not err, err)
if data then
stdout_text = stdout_text .. data
end
end)
end
local _nugetIndex
local _packageBaseAddress
---@param callback fun(): nil
local function populate_nuget_api(callback)
if _nugetIndex ~= nil then
callback()
end
local url = string.format("https://api.nuget.org/v3/index.json")
local function handle(decoded)
local default_nuget_reg = "https://api.nuget.org/v3/registration5-semver1/"
local default_base_address = "https://api.nuget.org/v3-flatcontainer/"
if not decoded then
print("Failed to fetch NuGet index; falling back to default")
_nugetIndex = default_nuget_reg
_packageBaseAddress = default_base_address
else
local resources = decoded["resources"]
if resources == nil then
print("Failed to parse: " .. decoded .. tostring(decoded))
for k, v in pairs(decoded) do
print(k .. ": " .. tostring(v))
end
callback()
return
end
local resourceSuccess, regUrl = find(resources, function(o)
return o["@type"] == "RegistrationsBaseUrl/3.6.0"
end)
if not resourceSuccess then
print("Failed to find endpoint in NuGet index; falling back to default")
_nugetIndex = default_nuget_reg
else
_nugetIndex = regUrl["@id"]
end
local baseAddrSuccess, baseAddrUrl = find(resources, function(o)
return o["@type"] == "PackageBaseAddress/3.0.0"
end)
if not baseAddrSuccess then
print("Failed to find endpoint in NuGet index; falling back to default")
_packageBaseAddress = default_base_address
else
_packageBaseAddress = baseAddrUrl["@id"]
end
end
callback()
end
curl(url, handle)
end
---@return nil
local function populate_nuget_api_sync()
if _nugetIndex ~= nil then
return
end
local url = string.format("https://api.nuget.org/v3/index.json")
local decoded = curl_sync(url)
local default_nuget_reg = "https://api.nuget.org/v3/registration5-semver1/"
local default_base_address = "https://api.nuget.org/v3-flatcontainer/"
if not decoded then
print("Failed to fetch NuGet index; falling back to default")
_nugetIndex = default_nuget_reg
_packageBaseAddress = default_base_address
else
local resources = decoded["resources"]
local resourceSuccess, regUrl = find(resources, function(o)
return o["@type"] == "RegistrationsBaseUrl/3.6.0"
end)
if not resourceSuccess then
print("Failed to find endpoint in NuGet index; falling back to default")
_nugetIndex = default_nuget_reg
else
_nugetIndex = regUrl["@id"]
end
local baseAddrSuccess, baseAddrUrl = find(resources, function(o)
return o["@type"] == "PackageBaseAddress/3.0.0"
end)
if not baseAddrSuccess then
print("Failed to find endpoint in NuGet index; falling back to default")
_packageBaseAddress = default_base_address
else
_packageBaseAddress = baseAddrUrl["@id"]
end
end
end
---@return nil
---@param callback fun(nugetIndex: string): nil
local function get_nuget_index(callback)
populate_nuget_api(function()
callback(_nugetIndex)
end)
end
---@return nil
---@param callback fun(packageBaseIndex: string): nil
local function get_package_base_addr(callback)
populate_nuget_api(function()
callback(_packageBaseAddress)
end)
end
---@return string
local function get_package_base_addr_sync()
populate_nuget_api_sync()
return _packageBaseAddress
end
local _package_versions_cache = {}
---@param package_name string
---@return NuGetVersion[]
local function get_package_versions_sync(package_name)
if _package_versions_cache[package_name] ~= nil then
return _package_versions_cache[package_name]
end
local base = get_package_base_addr_sync()
local url = base .. string.format("%s/index.json", package_name:lower())
local decoded = curl_sync(url)
if not decoded then
print("Failed to fetch package versions")
return {}
end
local versions = map(decoded.versions, parse_version)
table.sort(versions, function(a, b)
return compare_versions(b, a)
end)
_package_versions_cache[package_name] = versions
return versions
end
---@param package_name string
---@param callback fun(v: NuGetVersion[]): nil
---@return nil
local function get_package_versions(package_name, callback)
if _package_versions_cache[package_name] ~= nil then
callback(_package_versions_cache[package_name])
end
local function handle(base)
local url = base .. string.format("%s/index.json", package_name:lower())
local function handle2(decoded)
if not decoded then
print("Failed to fetch package versions")
callback({})
end
local versions = map(decoded.versions, parse_version)
table.sort(versions, function(a, b)
return compare_versions(b, a)
end)
_package_versions_cache[package_name] = versions
callback(versions)
end
curl(url, handle2)
end
get_package_base_addr(handle)
end
---@param version NuGetVersion
---@return nil
local function update_package_version(version)
local line = vim.api.nvim_get_current_line()
local new_line = line:gsub('Version="[^"]+"', string.format('Version="%s"', nuGetVersionToString(version)))
vim.api.nvim_set_current_line(new_line)
end
-- A map from package to { packageWeDependOn: version }.
--- @type table<string, table<string, string>>
local _package_dependency_cache = {}
---@param package_name string
---@param version NuGetVersion
---@param callback fun(result: table<string, string>): nil
---@return nil
local function get_package_dependencies(package_name, version, callback)
local key = package_name .. "@" .. nuGetVersionToString(version)
local cache_hit = _package_dependency_cache[key]
if cache_hit ~= nil then
callback(cache_hit)
return
end
local function handle1(index)
local url = index .. string.format("%s/%s.json", package_name:lower(), nuGetVersionToString(version):lower())
local function handle(response)
if not response then
print(
"Failed to get dependencies of "
.. package_name
.. " at version "
.. version
.. " : unsuccessful response to "
.. url
)
return
end
local entry_url = response["catalogEntry"]
local function handle2(catalog_entry)
if not catalog_entry then
print(
"Failed to get dependencies of "
.. package_name
.. " at version "
.. version
.. " : unsuccessful response to "
.. entry_url
)
return
end
local result = {}
if catalog_entry["dependencyGroups"] then
iter(catalog_entry["dependencyGroups"], function(grp)
if grp["dependencies"] then
for _, dep in pairs(grp["dependencies"]) do
result[dep["id"]] = dep["range"]
end
end
end)
end
_package_dependency_cache[key] = result
callback(result)
end
curl(entry_url, handle2)
end
curl(url, handle)
end
get_nuget_index(handle1)
end
---@return table<string, NuGetVersion>
---@nodiscard
local function get_all_package_references()
local packages = {}
local lines = vim.api.nvim_buf_get_lines(0, 0, -1, false)
for _, line in ipairs(lines) do
local package_name = line:match('PackageReference Include="([^"]+)"')
or line:match('PackageReference Update="([^"]+)"')
local version = line:match('Version="([^"]+)"')
if package_name and version then
if not packages[package_name] then
packages[package_name] = {}
end
table.insert(packages[package_name], parse_version(version))
end
end
return packages
end
function ClearNuGetDependencyCache()
for k, _ in pairs(_package_dependency_cache) do
_package_dependency_cache[k] = nil
end
end
vim.api.nvim_create_user_command("ClearNuGetDependencyCache", ClearNuGetDependencyCache, {})
function PrintNuGetDependencyCache()
for k, v in pairs(_package_dependency_cache) do
print(k .. ":")
for dep, ver in pairs(v) do
print(" " .. dep .. ": " .. ver)
end
end
end
vim.api.nvim_create_user_command("PrintNuGetDependencyCache", PrintNuGetDependencyCache, {})
local function prefetch_dependencies()
local packages = get_all_package_references()
local function process_package(package_name, versions, callback)
local count = #versions
for _, version in ipairs(versions) do
vim.schedule(function()
get_package_dependencies(package_name, version, function(_)
count = count - 1
if count == 0 then
callback()
end
end)
end)
end
end
local total_packages = 0
for _ in pairs(packages) do
total_packages = total_packages + 1
end
local processed_packages = 0
for package_name, versions in pairs(packages) do
process_package(package_name, versions, function()
local function handle(package_versions)
if package_versions then
process_package(package_name, package_versions, function()
processed_packages = processed_packages + 1
if processed_packages == total_packages then
print("All dependencies prefetched")
end
end)
else
processed_packages = processed_packages + 1
if processed_packages == total_packages then
print("All dependencies prefetched")
end
end
end
get_package_versions(package_name, handle)
end)
end
end
---@param v1 NuGetVersion
---@param v2 NuGetVersion
---@return boolean
---@nodiscard
local function nuget_versions_equal(v1, v2)
return v1.major == v2.major and v1.minor == v2.minor and v1.patch == v2.patch and v1.suffix == v2.suffix
end
vim.api.nvim_create_autocmd("FileType", {
pattern = { "fsharp_project", "csharp_project", "xml" },
callback = function()
function UpdateNuGetVersion()
local line = vim.api.nvim_get_current_line()
local package_name = line:match('PackageReference Include="([^"]+)"')
or line:match('PackageReference Update="([^"]+)"')
if not package_name then
print("No package reference found on the current line")
return
end
local current_version = parse_version(line:match('Version="([^"]+)"'))
if not current_version then
print("oh no!")
end
local package_versions = get_package_versions_sync(package_name)
if #package_versions == 0 then
print("No versions found for the package")
return
end
local pickers = require("telescope.pickers")
local finders = require("telescope.finders")
local previewers = require("telescope.previewers")
pickers
.new({}, {
prompt_title = string.format("Select version for %s", package_name),
finder = finders.new_table({
results = package_versions,
entry_maker = function(entry)
local val = nuGetVersionToString(entry)
local display_value = val
if current_version and nuget_versions_equal(entry, current_version) then
display_value = "[CURRENT] " .. val
end
return {
value = entry,
display = display_value,
ordinal = entry,
}
end,
}),
previewer = previewers.new_buffer_previewer({
define_preview = function(self, entry, _)
local bufnr = self.state.bufnr
vim.api.nvim_buf_set_lines(bufnr, 0, -1, false, { "Loading..." })
get_package_dependencies(package_name, entry.value, function(package_dependencies)
if not package_dependencies then
vim.api.nvim_buf_set_lines(bufnr, 0, -1, false, { "No dependencies found" })
return
end
local display = {}
table.insert(
display,
"Dependencies for "
.. package_name
.. " at version "
.. nuGetVersionToString(entry.value)
.. ":"
)
for dep, range in pairs(package_dependencies) do
table.insert(display, dep .. ": " .. range)
end
local ok, err = pcall(vim.api.nvim_buf_set_lines, bufnr, 0, -1, false, display)
if not ok then
-- If we can't set lines, the window's probably gone. Ignore.
return
end
end)
end,
}),
attach_mappings = function(_, mapping)
mapping("i", "<CR>", function(prompt_bufnr)
local selection = require("telescope.actions.state").get_selected_entry()
require("telescope.actions").close(prompt_bufnr)
update_package_version(selection.value)
end)
return true
end,
})
:find()
end
local whichkey = require("which-key")
whichkey.add({
{ "<localleader>n", desc = "NuGet" },
{ "<localleader>nu", UpdateNuGetVersion, desc = "Upgrade NuGet versions" },
}, { buffer = vim.api.nvim_get_current_buf() })
vim.schedule(prefetch_dependencies)
end,
})

View File

@@ -1,8 +1,9 @@
vim.g.python3_host_prog = "%PYTHONENV%/bin/python"
vim.opt.mouse = ""
vim.opt.history = 500
vim.opt.background = "dark"
vim.opt.signcolumn = "yes"
vim.opt.wildmenu = true
vim.opt.wildignore = vim.opt.wildignore + { "*/.git/*", "*/.hg/*", "*/.svn/*", "*/.DS_Store" }
@@ -168,8 +169,6 @@ function DisplayAllMappingsWithTelescope()
local function accumulate(tree)
tree:walk(function(node)
-- Note: we could (if desired) view all groups, because the `node.mapping` table looks like this:
-- { prefix = "g", group = true, keys = {...}}
if node.mapping then
local mapping = node.mapping
if not mapping.group then
@@ -223,66 +222,69 @@ function ToggleSpell()
vim.cmd("setlocal spell!")
end
whichkey.register({
[vim.api.nvim_get_var("maplocalleader")] = {
DisplayAllMappingsWithTelescope,
"View all mappings",
whichkey.add({
{
"<localleader><localleader>",
function()
require("which-key").show({ global = false })
end,
desc = "View all mappings",
},
m = {
p = { MarkdownPreview, "Preview Markdown in Lynx" },
d = { RemoveCarriageReturn, "Delete carriage returns from file" },
},
["j"] = {
FormatJson,
"Auto-format JSON",
},
}, { prefix = vim.api.nvim_get_var("maplocalleader") })
whichkey.register({
g = {
{ "<localleader>mp", MarkdownPreview, desc = "Preview Markdown in Lynx" },
{ "<localleader>md", RemoveCarriageReturn, desc = "Delete carriage returns from file" },
{ "<localleader>j", FormatJson, desc = "Auto-format JSON" },
})
whichkey.add({
{
"<leader>g",
function()
require("telescope.builtin").grep_string()
end,
"Find instances of text under cursor",
desc = "Find instances of text under cursor",
},
h = {
name = "Find historical...",
f = {
function()
require("telescope.builtin").oldfiles()
end,
"List previously open files",
},
c = {
function()
require("telescope.builtin").command_history()
end,
"List previously run commands",
},
s = {
function()
require("telescope.builtin").search_history()
end,
"List previously run searches",
},
{ "<leader>h", desc = "Find historical..." },
{
"<leader>hf",
function()
require("telescope.builtin").oldfiles()
end,
desc = "List previously open files",
},
m = {
{
"<leader>hc",
function()
require("telescope.builtin").command_history()
end,
desc = "List previously run commands",
},
{
"<leader>hs",
function()
require("telescope.builtin").search_history()
end,
desc = "List previously run searches",
},
{
"<leader>m",
function()
require("telescope.builtin").marks()
end,
"List marks",
desc = "List marks",
},
["cd"] = {
ChangeToCurrentDirectory,
"Switch CWD to the directory of the open buffer",
},
["ss"] = {
ToggleSpell,
"Toggle spell-checker on or off",
},
[vim.api.nvim_get_var("mapleader")] = {
{ "<leader>cd", ChangeToCurrentDirectory, desc = "Switch CWD to the directory of the open buffer" },
{ "<leader>ss", ToggleSpell, desc = "Toggle spell-checker on or off" },
{
"<leader><leader>",
function()
require("telescope.builtin").find_files()
end,
"Find files by name",
desc = "Find files by name",
},
}, { prefix = vim.api.nvim_get_var("mapleader") })
})
vim.api.nvim_create_autocmd({ "BufRead", "BufNewFile" }, {
pattern = { "Directory.Build.props", "*.fsproj", "*.csproj" },
callback = function()
vim.bo.filetype = "xml"
end,
})

View File

@@ -2,23 +2,6 @@ vim.g["fsharp#fsautocomplete_command"] = { "fsautocomplete" }
vim.g["fsharp#show_signature_on_cursor_move"] = 1
vim.g["fsharp#fsi_keymap"] = "none"
-- MASSIVE HACK - raised https://github.com/ionide/Ionide-vim/pull/78
local function captureLoadedProjects()
vim.fn.execute("redir => g:massive_hack_patrick_capture")
vim.fn.execute("call fsharp#showLoadedProjects()")
vim.fn.execute("redir END")
local output = vim.fn.eval("g:massive_hack_patrick_capture")
local projects = {}
for line in output:gmatch("[^\r\n]+") do
local project = line:gsub("^%s*-%s*", "")
table.insert(projects, project)
end
return projects
end
-- Supply nil to get all loaded F# projects and build them.
local function BuildFSharpProjects(projects)
local function on_line(data, _, context)
@@ -76,7 +59,7 @@ local function BuildFSharpProjects(projects)
end
if not projects then
projects = captureLoadedProjects()
projects = vim.fn["fsharp#getLoadedProjects"]()
end
if projects then
local total_projects = 0
@@ -107,7 +90,7 @@ vim.api.nvim_create_user_command("BuildFSharpProject", function(opts)
.new({}, {
prompt_title = "Projects",
finder = finders.new_table({
results = captureLoadedProjects(),
results = vim.fn["fsharp#getLoadedProjects"](),
}),
sorter = conf.generic_sorter({}),
attach_mappings = function(prompt_buf, _)
@@ -173,7 +156,7 @@ vim.api.nvim_create_user_command("RunFSharpProject", function(opts)
.new({}, {
prompt_title = "Projects",
finder = finders.new_table({
results = captureLoadedProjects(),
results = vim.fn["fsharp#getLoadedProjects"](),
}),
sorter = conf.generic_sorter({}),
attach_mappings = function(prompt_buf, _)
@@ -202,7 +185,7 @@ vim.api.nvim_create_user_command("PublishFSharpProject", function(opts)
.new({}, {
prompt_title = "Projects",
finder = finders.new_table({
results = captureLoadedProjects(),
results = vim.fn["fsharp#getLoadedProjects"](),
}),
sorter = conf.generic_sorter({}),
attach_mappings = function(prompt_buf, _)
@@ -223,28 +206,19 @@ vim.api.nvim_create_autocmd("FileType", {
callback = function()
local status, whichkey = pcall(require, "which-key")
if status then
whichkey.register({
f = {
name = "F#",
t = { ":call fsharp#showTooltip()<CR>", "Show F# Tooltip" },
["si"] = { ":call fsharp#toggleFsi()<CR>", "Toggle FSI (F# Interactive)" },
["sl"] = { ":call fsharp#sendLineToFsi()<cr>", "Send line to FSI (F# Interactive)" },
r = {
name = "Run F# project",
d = { ":RunFSharpProject Debug", "Run F# project in debug configuration" },
r = { ":RunFSharpProject Release", "Run F# project in release configuration" },
},
p = {
":PublishFSharpProject",
"Publish F# project",
},
b = {
"Build F# project",
a = { BuildFSharpProjects, "Build all projects" },
s = { ":BuildFSharpProject", "Build specified project" },
},
},
}, { prefix = vim.api.nvim_get_var("maplocalleader"), buffer = vim.api.nvim_get_current_buf() })
whichkey.add({
{ "<localleader>f", desc = "F#" },
{ "<localleader>ft", ":call fsharp#showTooltip()<CR>", desc = "Show F# Tooltip" },
{ "<localleader>fsi", ":call fsharp#toggleFsi()<CR>", desc = "Toggle FSI (F# Interactive)" },
{ "<localleader>fsl", ":call fsharp#sendLineToFsi()<cr>", desc = "Send line to FSI (F# Interactive)" },
{ "<localleader>fr", desc = "Run F# project" },
{ "<localleader>frd", ":RunFSharpProject Debug", desc = "Run F# project in debug configuration" },
{ "<localleader>frr", ":RunFSharpProject Release", desc = "Run F# project in release configuration" },
{ "<localleader>fp", ":PublishFSharpProject", desc = "Publish F# project" },
{ "<localleader>fb", desc = "Build F# project" },
{ "<localleader>fba", BuildFSharpProjects, desc = "Build all projects" },
{ "<localleader>fbs", ":BuildFSharpProject", desc = "Build specified project" },
}, { buffer = vim.api.nvim_get_current_buf() })
else
vim.api.nvim_set_keymap("n", "<localleader>ft", ":call fsharp#showTooltip()<CR>", { noremap = true })
vim.api.nvim_set_keymap("n", "<localleader>fsi", ":call fsharp#toggleFsi()<CR>", { noremap = true })

View File

@@ -2,16 +2,16 @@ require("lspconfig")["leanls"].setup({})
require("lean").setup({})
require("which-key").register({
l = {
i = { "<Cmd>LeanInfoviewToggle<CR>", "Toggle Lean info view" },
p = { "<Cmd>LeanInfoviewPinTogglePause<CR>", "Pause Lean info view" },
s = { "<Cmd>LeanSorryFill<CR>", "Fill open goals with sorry" },
w = { "<Cmd>LeanInfoviewEnableWidgets<CR>", "Enable Lean widgets" },
W = { "<Cmd>LeanInfoviewDisableWidgets<CR>", "Disable Lean widgets" },
["?"] = {
"<Cmd>LeanAbbreviationsReverseLookup<CR>",
"Show what Lean abbreviation produces the symbol under the cursor",
},
require("which-key").add({
{ "<localleader>l", desc = "Lean" },
{ "<localleader>li", "<Cmd>LeanInfoviewToggle<CR>", desc = "Toggle Lean info view" },
{ "<localleader>lp", "<Cmd>LeanInfoviewPinTogglePause<CR>", desc = "Pause Lean info view" },
{ "<localleader>ls", "<Cmd>LeanSorryFill<CR>", desc = "Fill open goals with sorry" },
{ "<localleader>lw", "<Cmd>LeanInfoviewEnableWidgets<CR>", desc = "Enable Lean widgets" },
{ "<localleader>lW", "<Cmd>LeanInfoviewDisableWidgets<CR>", desc = "Disable Lean widgets" },
{
"<localleader>l?",
"<Cmd>LeanAbbreviationsReverseLookup<CR>",
desc = "Show what Lean abbreviation produces the symbol under the cursor",
},
}, { prefix = vim.api.nvim_get_var("maplocalleader") })
})

View File

@@ -13,6 +13,10 @@ local schemas = {
["https://json.schemastore.org/dotnet-tools.json"] = "dotnet-tools.json",
}
require("lspconfig")["clangd"].setup({})
require("lspconfig")["gopls"].setup({})
require("lspconfig")["yamlls"].setup({
settings = {
yaml = {
@@ -41,15 +45,18 @@ require("lspconfig")["jsonls"].setup({
},
})
require("lspconfig")["denols"].setup({})
require("lspconfig")["bashls"].setup({})
require("lspconfig")["dockerls"].setup({})
require("lspconfig")["html"].setup({
capabilities = capabilities,
})
require("lspconfig")["ltex"].setup({})
require("lspconfig")["lua_ls"].setup({
on_init = function(client)
if not client.workspace_folders then
return
end
local path = client.workspace_folders[1].name
if vim.uv.fs_stat(path .. "/.luarc.json") or vim.loop.fs_stat(path .. "/.luarc.jsonc") then
return
@@ -116,15 +123,13 @@ end
do
local whichkey_status, whichkey = pcall(require, "which-key")
if whichkey_status then
whichkey.register({
l = {
name = "loclist-related commands",
p = { vim.diagnostic.goto_prev, "Go to previous entry in loclist" },
n = { vim.diagnostic.goto_next, "Go to next entry in loclist" },
l = { ToggleLocList, "Toggle loclist" },
f = { vim.diagnostic.open_float, "Open current loclist entry in floating window" },
},
}, { prefix = vim.api.nvim_get_var("mapleader") })
whichkey.add({
{ "<leader>l", desc = "loclist-related commands" },
{ "<leader>lp", vim.diagnostic.goto_prev, desc = "Go to previous entry in loclist" },
{ "<leader>ln", vim.diagnostic.goto_next, desc = "Go to next entry in loclist" },
{ "<leader>ll", ToggleLocList, desc = "Toggle loclist" },
{ "<leader>lf", vim.diagnostic.open_float, desc = "Open current loclist entry in floating window" },
})
else
vim.keymap.set("n", "<leader>lp", vim.diagnostic.goto_prev)
vim.keymap.set("n", "<leader>ln", vim.diagnostic.goto_next)
@@ -144,48 +149,44 @@ vim.api.nvim_create_autocmd("LspAttach", {
-- Buffer local mappings.
-- See `:help vim.lsp.*` for documentation on any of the below functions
whichkey.register({
g = {
name = "Go-to related commands",
D = { vim.lsp.buf.declaration, "Go to declaration" },
d = { vim.lsp.buf.definition, "Go to definition" },
i = { vim.lsp.buf.implementation, "Go to implementation" },
r = {
function()
require("telescope.builtin").lsp_references()
end,
"Find references",
},
whichkey.add({
{ "g", desc = "Go-to related commands" },
{ "gD", vim.lsp.buf.declaration, desc = "Go to declaration" },
{ "gd", vim.lsp.buf.definition, desc = "Go to definition" },
{ "gi", vim.lsp.buf.implementation, desc = "Go to implementation" },
{
"gr",
function()
require("telescope.builtin").lsp_references()
end,
desc = "Find references",
},
K = { vim.lsp.buf.hover, "Display information about symbol under cursor" },
{ "gK", vim.lsp.buf.hover, desc = "Display information about symbol under cursor" },
})
whichkey.register({
["<C-k>"] = { vim.lsp.buf.signature_help, "Display signature information about symbol under cursor" },
whichkey.add({
{ "<C-k>", vim.lsp.buf.signature_help, desc = "Display signature information about symbol under cursor" },
})
whichkey.register({
w = {
a = { vim.lsp.buf.add_workspace_folder, "Add a path to the workspace folders list" },
r = { vim.lsp.buf.add_workspace_folder, "Remove a path from the workspace folders list" },
l = {
function()
print(vim.inspect(vim.lsp.buf.list_workspace_folders()))
end,
"Show the workspace folders list",
},
whichkey.add({
{ "<leader>w", desc = "Workspace-related commands" },
{ "<leader>wa", vim.lsp.buf.add_workspace_folder, desc = "Add a path to the workspace folders list" },
{ "<leader>wr", vim.lsp.buf.add_workspace_folder, desc = "Remove a path from the workspace folders list" },
{
"<leader>wl",
function()
print(vim.inspect(vim.lsp.buf.list_workspace_folders()))
end,
desc = "Show the workspace folders list",
},
f = {
{
"<leader>f",
function()
vim.lsp.buf.format({ async = true })
end,
"Autoformat",
desc = "Autoformat",
},
c = {
a = { vim.lsp.buf.code_action, "Select a code action" },
},
r = {
n = { vim.lsp.buf.rename, "Rename variable" },
},
D = { vim.lsp.buf.type_definition, "Go to type definition" },
}, { prefix = vim.api.nvim_get_var("mapleader") })
{ "<leader>ca", vim.lsp.buf.code_action, desc = "Select a code action" },
{ "<leader>rn", vim.lsp.buf.rename, desc = "Rename variable" },
{ "<leader>D", vim.lsp.buf.type_definition, desc = "Go to type definition" },
})
end,
})

View File

@@ -1 +1,11 @@
require("dap-python").setup("%PYTHONENV%/bin/python")
do
local whichkey = require("which-key")
whichkey.add({
{ "<localleader>pd", desc = "Debugger-related commands" },
{ "<localleader>pdt", desc = "Tests" },
{ "<localleader>pdtf", require("dap-python").test_class, desc = "Run Python tests in the current file" },
{ "<localleader>pdtc", require("dap-python").test_method, desc = "Run the Python test under the cursor" },
})
end

View File

@@ -30,37 +30,36 @@ dap.configurations.cs = {
do
local whichkey = require("which-key")
whichkey.register({
d = {
name = "Debugger-related commands",
o = { dap.step_over, "Step over" },
i = { dap.step_into, "Step into" },
c = { dap.continue, "Continue" },
C = { dap.run_last, "Run with last debug configuration" },
b = { dap.toggle_breakpoint, "Toggle breakpoint" },
r = { dap.repl.open, "Open debug repl" },
v = {
name = "Commands to view debugger state",
v = {
function()
dap_ui.hover()
end,
"View value of expression under cursor",
},
s = {
function()
dap_ui.sidebar(dap_ui.scopes).open()
end,
"View values of all variables in all scopes",
},
f = {
function()
dap_ui.sidebar(dap_ui.frames).open()
end,
"View stack frames",
},
},
t = { dap.terminate, "Terminate/stop/end debug session" },
whichkey.add({
{ "<localleader>d", desc = "Debugger-related commands" },
{ "<localleader>do", dap.step_over, desc = "Step over" },
{ "<localleader>di", dap.step_into, desc = "Step into" },
{ "<localleader>dc", dap.continue, desc = "Continue" },
{ "<localleader>dC", dap.run_last, desc = "Run with last debug configuration" },
{ "<localleader>db", dap.toggle_breakpoint, desc = "Toggle breakpoint" },
{ "<localleader>dr", dap.repl.open, desc = "Open debug repl" },
{ "<localleader>dv", desc = "Commands to view debugger state" },
{
"<localleader>dvv",
function()
dap_ui.hover()
end,
desc = "View value of expression under cursor",
},
}, { prefix = vim.api.nvim_get_var("maplocalleader") })
{
"<localleader>dvs",
function()
dap_ui.sidebar(dap_ui.scopes).open()
end,
desc = "View values of all variables in all scopes",
},
{
"<localleader>dvf",
function()
dap_ui.sidebar(dap_ui.frames).open()
end,
desc = "View stack frames",
},
{ "<localleader>dt", dap.terminate, desc = "Terminate/stop/end debug session" },
})
end

View File

@@ -0,0 +1,64 @@
local function pytest_on_line(_, _, _) end
local function pytest_on_complete(_, code, _)
if code ~= 0 then
print("Exit code " .. code)
end
end
function RunPythonTestAtCursor()
local api = vim.api
-- Get the current buffer and cursor position
local bufnr = api.nvim_get_current_buf()
local line_nr = api.nvim_win_get_cursor(0)[1]
local filename = api.nvim_buf_get_name(bufnr)
-- Read the file content
local lines = api.nvim_buf_get_lines(bufnr, 0, -1, false)
-- Find the test function
local test_name = nil
for i = line_nr, 1, -1 do
local line = lines[i]
if line:match("^def test_") then
test_name = line:match("^def (%S+)%(")
break
end
end
if test_name then
-- Run pytest for the found test function
local context = BuildUtils.create_window()
BuildUtils.run(
"pytest",
{ filename .. "::" .. test_name },
"Run PyTest (" .. test_name .. ")",
context,
pytest_on_line,
pytest_on_complete
)
else
print("No test function found at or above line " .. line_nr)
end
end
function RunPythonTestsInFile()
local file_path = vim.fn.expand("%:p")
local context = BuildUtils.create_window()
BuildUtils.run("pytest", { file_path }, "Run PyTest", context, pytest_on_line, pytest_on_complete)
end
function RunAllPythonTests()
local context = BuildUtils.create_window()
BuildUtils.run("pytest", {}, "Run PyTest", context, pytest_on_line, pytest_on_complete)
end
do
local whichkey = require("which-key")
whichkey.add({
{ "<localleader>pt", desc = "Run Python tests" },
{ "<localleader>ptf", RunPythonTestsInFile, desc = "Run Python tests in the current file" },
{ "<localleader>pta", RunAllPythonTests, desc = "Run all Python tests" },
{ "<localleader>ptc", RunPythonTestAtCursor, desc = "Run the Python test under the cursor" },
})
end

View File

@@ -1,5 +1,8 @@
require("tokyonight").setup({
style = "night",
on_colors = function(colors)
colors.border = "#565f89"
end,
})
vim.cmd([[colorscheme tokyonight]])

View File

@@ -3,6 +3,7 @@ local venv_selector = require("venv-selector")
venv_selector.setup({
changed_venv_hooks = { venv_selector.hooks.pyright },
name = { "venv", ".venv" },
search_venv_managers = true,
})
vim.api.nvim_create_autocmd("VimEnter", {
@@ -83,20 +84,16 @@ end
do
local whichkey = require("which-key")
whichkey.register({
p = {
name = "Python-related commands",
v = {
name = "Virtual environment-related commands",
c = { CreateVenv, "Create virtual environment" },
l = { SelectVenv, "Load virtual environment" },
o = {
function()
vim.cmd("VenvSelect")
end,
"Choose (override) new virtual environment",
},
},
whichkey.add({
{ "<localleader>pv", desc = "Python virtual environment-related commands" },
{ "<localleader>pvc", CreateVenv, desc = "Create virtual environment" },
{ "<localleader>pvl", SelectVenv, desc = "Load virtual environment" },
{
"<localleader>pvo",
function()
vim.cmd("VenvSelect")
end,
desc = "Choose (override) new virtual environment",
},
}, { prefix = vim.api.nvim_get_var("maplocalleader") })
})
end

View File

@@ -18,67 +18,19 @@ require("which-key").setup({
g = true, -- bindings for prefixed with g
},
},
-- add operators that will trigger motion and text object completion
-- to enable all native operators, set the preset / operators plugin above
operators = { gc = "Comments" },
key_labels = {
-- override the label used to display some keys. It doesn't effect WK in any other way.
-- For example:
-- ["<space>"] = "SPC",
-- ["<cr>"] = "RET",
-- ["<tab>"] = "TAB",
},
motions = {
count = true,
},
icons = {
breadcrumb = "»", -- symbol used in the command line area that shows your active key combo
separator = "", -- symbol used between a key and it's label
group = "+", -- symbol prepended to a group
},
popup_mappings = {
scroll_down = "<c-d>", -- binding to scroll down inside the popup
scroll_up = "<c-u>", -- binding to scroll up inside the popup
},
window = {
border = "none", -- none, single, double, shadow
position = "bottom", -- bottom, top
margin = { 1, 0, 1, 0 }, -- extra window margin [top, right, bottom, left]. When between 0 and 1, will be treated as a percentage of the screen size.
padding = { 1, 2, 1, 2 }, -- extra window padding [top, right, bottom, left]
winblend = 0, -- value between 0-100 0 for fully opaque and 100 for fully transparent
zindex = 1000, -- positive value to position WhichKey above other floating windows.
},
layout = {
height = { min = 4, max = 25 }, -- min and max height of the columns
width = { min = 20, max = 50 }, -- min and max width of the columns
spacing = 3, -- spacing between columns
align = "left", -- align columns left, center or right
},
ignore_missing = false, -- enable this to hide mappings for which you didn't specify a label
hidden = { "<silent>", "<cmd>", "<Cmd>", "<CR>", "^:", "^ ", "^call ", "^lua " }, -- hide mapping boilerplate
show_help = true, -- show a help message in the command line for using WhichKey
show_keys = true, -- show the currently pressed key and its label as a message in the command line
triggers = "auto", -- automatically setup triggers
-- triggers = {"<leader>"} -- or specifiy a list manually
-- list of triggers, where WhichKey should not wait for timeoutlen and show immediately
triggers_nowait = {
-- marks
"`",
"'",
"g`",
"g'",
-- registers
'"',
"<c-r>",
-- spelling
"z=",
},
triggers_blacklist = {
-- list of mode / prefixes that should never be hooked by WhichKey
-- this is mostly relevant for keymaps that start with a native binding
i = { "j", "k" },
v = { "j", "k" },
},
-- disable the WhichKey popup for certain buf types and file types.
-- Disabled by default for Telescope
disable = {

13
home-manager/sway.conf Normal file
View File

@@ -0,0 +1,13 @@
output Unknown-1 scale 2
input * {
xkb_layout "gb"
}
# capture all screens to clipboard
bindsym Shift+Print exec @@GRIM@@ - | @@WL-COPY@@
# capture the specified screen area to clipboard
bindsym Shift+Alt+Print exec @@GRIM@@ -g "$(@@SLURP@@)" - | @@WL-COPY@@
# capture the focused monitor to clipboard
bindsym Shift+Control+Print exec @@GRIM@@ -o $(swaymsg -t get_outputs | jq -r '.[] | select(.focused) | .name') - | @@WL-COPY@@

11
mbsync.nix Normal file
View File

@@ -0,0 +1,11 @@
{pkgs}:
pkgs.buildEnv {
name = "isync-oauth2";
paths = [pkgs.isync];
pathsToLink = ["/bin"];
nativeBuildInputs = [pkgs.makeWrapper];
postBuild = ''
wrapProgram "$out/bin/mbsync" \
--prefix SASL_PATH : "${pkgs.cyrus_sasl}/lib/sasl2:${pkgs.cyrus-sasl-xoauth2}/lib/sasl2"
'';
}

View File

@@ -1,10 +0,0 @@
[
(self: super: {
# https://github.com/NixOS/nixpkgs/issues/153304
alacritty = super.alacritty.overrideAttrs (
o: rec {
doCheck = false;
}
);
})
]

View File

@@ -1,14 +0,0 @@
{pkgs}: let
my-python-packages = python-packages:
with python-packages; [
pip
mathlibtools
];
in let
packageOverrides = self: super: {
# Test failures on darwin ("windows-1252"); just skip pytest
# (required for elan)
beautifulsoup4 = super.beautifulsoup4.overridePythonAttrs (old: {pytestCheckPhase = "true";});
};
in
(pkgs.python3.override {inherit packageOverrides;}).withPackages my-python-packages

View File

@@ -1,2 +0,0 @@
{nixpkgs, ...}: {
}