├── .gitignore ├── .github ├── FUNDING.yml └── workflows │ └── ci.yaml ├── ghc └── ghci.conf ├── alsa └── asoundrc ├── config └── fonts │ ├── SourceCodePro-Black.otf │ ├── SourceCodePro-Bold.otf │ ├── SourceCodePro-Light.otf │ ├── SourceCodePro-Regular.otf │ ├── SourceCodePro-ExtraLight.otf │ └── SourceCodePro-Semibold.otf ├── systemd └── .config │ └── systemd │ └── user │ ├── default.target.wants │ └── ssh-agent.service │ └── ssh-agent.service ├── stack └── config.yaml ├── nixpkgs-config.nix ├── x ├── xinitrc └── Xresources ├── README.md ├── flake.nix ├── shell ├── .bash_profile └── dir_colors ├── emacs ├── default.nix ├── custom-file.el ├── init.el └── lisp │ └── beancount.el ├── flake.lock ├── home.nix └── LICENSE /.gitignore: -------------------------------------------------------------------------------- 1 | result 2 | -------------------------------------------------------------------------------- /.github/FUNDING.yml: -------------------------------------------------------------------------------- 1 | github: ryantm 2 | -------------------------------------------------------------------------------- /ghc/ghci.conf: -------------------------------------------------------------------------------- 1 | :set prompt "\ESC[34mλ> \ESC[m" -------------------------------------------------------------------------------- /alsa/asoundrc: -------------------------------------------------------------------------------- 1 | defaults.ctl.!card "Headset"; 2 | defaults.pcm.!card "Headset"; 3 | -------------------------------------------------------------------------------- /config/fonts/SourceCodePro-Black.otf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ryantm/dotfiles/HEAD/config/fonts/SourceCodePro-Black.otf -------------------------------------------------------------------------------- /config/fonts/SourceCodePro-Bold.otf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ryantm/dotfiles/HEAD/config/fonts/SourceCodePro-Bold.otf -------------------------------------------------------------------------------- /config/fonts/SourceCodePro-Light.otf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ryantm/dotfiles/HEAD/config/fonts/SourceCodePro-Light.otf -------------------------------------------------------------------------------- /config/fonts/SourceCodePro-Regular.otf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ryantm/dotfiles/HEAD/config/fonts/SourceCodePro-Regular.otf -------------------------------------------------------------------------------- /config/fonts/SourceCodePro-ExtraLight.otf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ryantm/dotfiles/HEAD/config/fonts/SourceCodePro-ExtraLight.otf -------------------------------------------------------------------------------- /config/fonts/SourceCodePro-Semibold.otf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ryantm/dotfiles/HEAD/config/fonts/SourceCodePro-Semibold.otf -------------------------------------------------------------------------------- /systemd/.config/systemd/user/default.target.wants/ssh-agent.service: -------------------------------------------------------------------------------- 1 | /home/ryantm/dotfiles/systemd/.config/systemd/user/ssh-agent.service -------------------------------------------------------------------------------- /stack/config.yaml: -------------------------------------------------------------------------------- 1 | templates: 2 | params: 3 | author-email: ryan@ryantm.com 4 | author-name: Ryan Mulligan 5 | github-username: ryantm 6 | -------------------------------------------------------------------------------- /nixpkgs-config.nix: -------------------------------------------------------------------------------- 1 | { 2 | allowUnfree = true; 3 | allowBroken = true; 4 | minecraft.alsa = true; 5 | packageOverrides = pkgs_: with pkgs_; {}; 6 | allowUnfreePredicate = pkg: true; 7 | } 8 | -------------------------------------------------------------------------------- /systemd/.config/systemd/user/ssh-agent.service: -------------------------------------------------------------------------------- 1 | [Unit] 2 | Description=SSH key agent 3 | 4 | [Service] 5 | Type=forking 6 | Environment=SSH_AUTH_SOCK=%t/ssh-agent 7 | ExecStart=/usr/bin/ssh-agent -a $SSH_AUTH_SOCK -t 1h 8 | 9 | [Install] 10 | WantedBy=default.target -------------------------------------------------------------------------------- /x/xinitrc: -------------------------------------------------------------------------------- 1 | [[ -f ~/.Xresources ]] && xrdb -merge ~/.Xresources 2 | 3 | xsetroot -cursor_name left_ptr 4 | 5 | # BACKGROUND_IMAGE=~/background_images/20150510_sterling_vinyard.jpg 6 | # [[ -f "$BACKGROUND_IMAGE" ]] && feh --bg-fill $BACKGROUND_IMAGE 7 | 8 | exec xmonad # /home/ryantm/.xmonad/result/xmonad 9 | -------------------------------------------------------------------------------- /.github/workflows/ci.yaml: -------------------------------------------------------------------------------- 1 | name: "CI" 2 | on: 3 | pull_request: 4 | push: 5 | jobs: 6 | tests: 7 | runs-on: ubuntu-latest 8 | steps: 9 | - uses: actions/checkout@v2 10 | - uses: cachix/install-nix-action@v12 11 | with: 12 | skip_adding_nixpkgs_channel: true 13 | - run: nix-shell 14 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # ryantm's dotfiles 2 | 3 | These are ryantm's configuration files or dotfiles (because the files often begin with a "."). It uses [home-manager](https://github.com/nix-community/home-manager/). 4 | 5 | bootstrap with 6 | 7 | ``` 8 | nix run home-manager -- switch --flake .#ryantm 9 | ``` 10 | 11 | then you can do or `hms` after 12 | 13 | ``` 14 | home-manager switch --flake .#ryantm 15 | ``` 16 | -------------------------------------------------------------------------------- /flake.nix: -------------------------------------------------------------------------------- 1 | { 2 | description = "ryantm's dotfiles"; 3 | 4 | inputs.nixpkgs.url = "github:nixos/nixpkgs"; 5 | inputs.home-manager.url = "github:nix-community/home-manager"; 6 | inputs.home-manager.inputs.nixpkgs.follows = "nixpkgs"; 7 | inputs.emacs-overlay.url = "github:nix-community/emacs-overlay"; 8 | inputs.emacs-overlay.inputs.nixpkgs.follows = "nixpkgs"; 9 | inputs.nixpkgs-update.url = "github:ryantm/nixpkgs-update"; 10 | 11 | outputs = 12 | inputs@{ 13 | self, 14 | flake-utils, 15 | nixpkgs, 16 | home-manager, 17 | emacs-overlay, 18 | nixpkgs-update, 19 | }: 20 | let 21 | username = "ryantm"; 22 | in 23 | { 24 | homeConfigurations.${username} = home-manager.lib.homeManagerConfiguration { 25 | pkgs = nixpkgs.legacyPackages.x86_64-linux; 26 | modules = [ 27 | ./home.nix 28 | { 29 | home.username = username; 30 | home.homeDirectory = "/home/${username}"; 31 | home.stateVersion = "24.05"; 32 | } 33 | ]; 34 | extraSpecialArgs = inputs; 35 | }; 36 | }; 37 | } 38 | -------------------------------------------------------------------------------- /shell/.bash_profile: -------------------------------------------------------------------------------- 1 | # 2 | # ~/.bash_profile 3 | # 4 | 5 | SOURCED_BASH_PROFILE=true 6 | 7 | setxkbmap -option ctrl:nocaps >/dev/null 2>/dev/null 8 | 9 | export EDITOR="emacs" 10 | 11 | if [ -f ~/.bashrc ] && [ ! $SOURCED_BASHRC ]; then 12 | . ~/.bashrc 13 | fi 14 | 15 | SSH_AUTH_SOCK="$XDG_RUNTIME_DIR/ssh-agent.socket" 16 | export SSH_AUTH_SOCK 17 | 18 | POLOLU_DIR=$HOME/p/pololu 19 | 20 | # User specific environment and startup programs 21 | PATH=$PATH:$HOME/bin:$POLOLU_DIR/system2/bin:$HOME/.local/bin 22 | BASH_ENV=$HOME/.bashrc 23 | 24 | export BASH_ENV PATH 25 | unset USERNAME 26 | 27 | # Track assembly 28 | export TRACK_ASSEMBLY_DIRECTORY=$POLOLU_DIR/track_assembly 29 | function track_assembly { 30 | pushd . 31 | cd $POLOLU_DIR/system2_for_track/website 32 | /opt/pololu_rails_env/preview rails runner "script/track/track_assembly.rb" 33 | popd 34 | } 35 | 36 | # Track users permissions 37 | export TRACK_USERS_PERMISSIONS_DIRECTORY=$POLOLU_DIR/track_users_permissions 38 | function track_users_permissions { 39 | pushd . 40 | cd $POLOLU_DIR/system2_for_track/website 41 | /opt/pololu_rails_env/preview rails runner "script/track/track_users_permissions.rb" 42 | popd 43 | } 44 | 45 | if [ -e /home/ryantm/.nix-profile/etc/profile.d/nix.sh ]; then . /home/ryantm/.nix-profile/etc/profile.d/nix.sh; fi # added by Nix installer 46 | 47 | # if [ "$INSIDE_EMACS" = "" ] 48 | # then 49 | # byobu_sourced=1 . byobu-launch 2>/dev/null || true 50 | # fi 51 | -------------------------------------------------------------------------------- /emacs/default.nix: -------------------------------------------------------------------------------- 1 | { 2 | pkgs, 3 | emacs-overlay, 4 | ... 5 | }: 6 | let 7 | overrides = self: super: { 8 | elpaPackages = super.elpaPackages // { 9 | seq = self.callPackage ( 10 | { 11 | elpaBuild, 12 | fetchurl, 13 | lib, 14 | }: 15 | elpaBuild rec { 16 | pname = "seq"; 17 | ename = "seq"; 18 | version = "2.24"; 19 | src = fetchurl { 20 | url = "https://elpa.gnu.org/packages/seq-2.24.tar"; 21 | sha256 = "1w2cysad3qwnzdabhq9xipbslsjm528fcxkwnslhlkh8v07karml"; 22 | }; 23 | packageRequires = [ ]; 24 | meta = { 25 | homepage = "https://elpa.gnu.org/packages/seq.html"; 26 | license = lib.licenses.free; 27 | }; 28 | # tests take a _long_ time to byte-compile, skip them 29 | postInstall = ''rm -r $out/share/emacs/site-lisp/elpa/${pname}-${version}/tests''; 30 | } 31 | ) { }; 32 | }; 33 | }; 34 | 35 | in 36 | { 37 | nixpkgs.overlays = [ 38 | (import emacs-overlay) 39 | ]; 40 | 41 | home.sessionVariables = { 42 | EDITOR = "emacsclient -t"; 43 | }; 44 | 45 | programs.bash = { 46 | shellAliases.e = "emacsclient -t"; 47 | }; 48 | 49 | services.emacs.enable = true; 50 | services.emacs.socketActivation.enable = true; 51 | #services.emacs.client.enable = true; 52 | 53 | programs.emacs.enable = true; 54 | programs.emacs.package = 55 | ((pkgs.emacsPackagesFor pkgs.emacs30).overrideScope overrides).emacsWithPackages 56 | ( 57 | epkgs: with epkgs; [ 58 | ccls 59 | bash-completion 60 | corfu 61 | consult 62 | dhall-mode 63 | diminish 64 | elisp-slime-nav 65 | fill-column-indicator 66 | go-mode 67 | graphql-mode 68 | graphviz-dot-mode 69 | ledger-mode 70 | lsp-mode 71 | lsp-ui 72 | lsp-pyright 73 | lxc 74 | magit 75 | markdown-preview-mode 76 | multiple-cursors 77 | nix-mode 78 | nixfmt 79 | orderless 80 | paredit 81 | powerline 82 | prettier 83 | pytest 84 | rainbow-delimiters 85 | rust-mode 86 | seq 87 | swiper 88 | tide 89 | treesit-grammars.with-all-grammars 90 | typescript-mode 91 | use-package 92 | vertico 93 | yasnippet 94 | yaml-mode 95 | zeal-at-point 96 | ] 97 | ); 98 | 99 | xdg.configFile."emacs" = { 100 | source = ./.; 101 | recursive = true; 102 | }; 103 | } 104 | -------------------------------------------------------------------------------- /x/Xresources: -------------------------------------------------------------------------------- 1 | ! Solarized light color scheme for urxvt 2 | #define S_base03 #002b36 3 | #define S_base02 #073642 4 | #define S_base01 #586e75 5 | #define S_base00 #657b83 6 | #define S_base0 #839496 7 | #define S_base1 #93a1a1 8 | #define S_base2 #eee8d5 9 | #define S_base3 #fdf6e3 10 | #define S_yellow #b58900 11 | #define S_orange #cb4b16 12 | #define S_red #dc322f 13 | #define S_magenta #d33682 14 | #define S_violet #6c71c4 15 | #define S_blue #268bd2 16 | #define S_cyan #2aa198 17 | #define S_green #859900 18 | 19 | urxvt*background: S_base3 20 | urxvt*foreground: S_base00 21 | urxvt*fadeColor: S_base3 22 | urxvt*cursorColor: S_base01 23 | urxvt*pointerColorBackground: S_base1 24 | urxvt*pointerColorForeground: S_base01 25 | urxvt*color0: S_base02 26 | urxvt*color8: S_base03 27 | urxvt*color1: S_red 28 | urxvt*color9: S_orange 29 | urxvt*color2: S_green 30 | urxvt*color10: S_base01 31 | urxvt*color3: S_yellow 32 | urxvt*color11: S_base00 33 | urxvt*color4: S_blue 34 | urxvt*color12: S_base0 35 | urxvt*color5: S_magenta 36 | urxvt*color13: S_violet 37 | urxvt*color6: S_cyan 38 | urxvt*color14: S_base1 39 | urxvt*color7: S_base2 40 | urxvt*color15: S_base3 41 | 42 | 43 | ! Xcursor 44 | Xcursor.theme: Vanilla-DMZ-AA 45 | Xcursor.size: 40 46 | 47 | ! Xft settings 48 | Xft.dpi: 96 49 | Xft.antialias: true 50 | Xft.rgba: rgb 51 | Xft.hinting: true 52 | Xft.hintstyle: hintslight 53 | 54 | ! xterm 55 | 56 | xterm*VT100.geometry: 80x25 57 | 58 | xterm*faceName: Source Code Pro:style=Regular:size=12 59 | xterm*dynamicColors: true 60 | xterm*utf8: 2 61 | xterm*scrollKey: true 62 | xterm*scrollTtyOutput: false 63 | xterm*scrollBar: false 64 | xterm*jumpScroll: true 65 | xterm*multiScroll: true 66 | xterm*toolBar: false 67 | xterm*saveLines: 1048576 68 | xterm*selectToClipboard: true 69 | xterm*termName: xterm-256color 70 | xterm*locale: true 71 | xterm*metaSendsEscape: true 72 | xterm*altSendsEscape: true 73 | xterm*ttyModes: erase ^? 74 | xterm*eightBitInput: true 75 | xterm*eightBitMeta: true 76 | 77 | xterm.VT100.translations: #override \ 78 | BackSpace: string(0x7f) \n\ 79 | Delete: string(0x1b) string("[3~") \n\ 80 | ~Shift ~Alt Ctrl minus: smaller-vt-font() \n\ 81 | ~Alt Ctrl plus: larger-vt-font() \n\ 82 | ~Shift ~Alt Ctrl equal: larger-vt-font() \n 83 | 84 | 85 | urxvt.iso14755: false 86 | urxvt.iso14755_52: false 87 | urxvt.font: xft:Source Code Pro:style=Regular:size=12 88 | urxvt.scrollBar: false 89 | urxvt.saveLines: 1048576 90 | urxvt.termName: screen-256color 91 | urxvt.perl-ext-common: font-size 92 | urxvt.keysym.C-plus: font-size:increase 93 | urxvt.keysym.C-underscore: font-size:decrease 94 | -------------------------------------------------------------------------------- /emacs/custom-file.el: -------------------------------------------------------------------------------- 1 | (custom-set-variables 2 | ;; custom-set-variables was added by Custom. 3 | ;; If you edit it by hand, you could mess it up, so be careful. 4 | ;; Your init file should contain only one such instance. 5 | ;; If there is more than one, they won't work right. 6 | '(backup-directory-alist '(("." . "~/.config/emacs/backups/"))) 7 | '(beancount-number-alignment-column 59) 8 | '(column-number-mode t) 9 | '(confirm-kill-emacs 'y-or-n-p) 10 | '(create-lockfiles nil) 11 | '(custom-enabled-themes '(modus-operandi)) 12 | '(custom-file "/home/ryantm/p/dotfiles/emacs/custom-file.el") 13 | '(dirtrack-list '("^[0-9a-z@-]* \\(.*\\) .*[$#]" 1)) 14 | '(enable-local-variables :safe) 15 | '(fci-rule-color "#073642") 16 | '(fci-rule-column 80) 17 | '(font-lock-maximum-decoration t) 18 | '(frame-background-mode 'light) 19 | '(git-commit-summary-max-length 80) 20 | '(global-whitespace-mode t) 21 | '(go-ts-mode-indent-offset 2) 22 | '(haskell-indent-offset 2) 23 | '(indent-tabs-mode nil) 24 | '(inhibit-startup-screen nil) 25 | '(initial-major-mode 'fundamental-mode) 26 | '(initial-scratch-message nil) 27 | '(keyboard-coding-system 'utf-8-unix) 28 | '(ledger-reconcile-default-date-format "%Y-%m-%d") 29 | '(menu-bar-mode nil) 30 | '(package-selected-packages 31 | '(magit-annex lxc graphql-mode purescript-mode hindent helm csv-mode 32 | dhall-mode nix-mode elisp-slime-nav 33 | rainbow-delimiters paredit zeal-at-point yaml-mode 34 | use-package powerline multiple-cursors 35 | markdown-preview-mode magit ledger-mode inf-ruby hi2 36 | haml-mode flycheck-haskell fill-column-indicator 37 | cus-edit+ bash-completion)) 38 | '(pytest-global-name "direnv exec . uv run pytest") 39 | '(ring-bell-function 'ignore) 40 | '(ruby-insert-encoding-magic-comment nil) 41 | '(save-place-file "~/.config/emacs/.places") 42 | '(save-place-mode t nil (saveplace)) 43 | '(scroll-bar-mode nil) 44 | '(selection-coding-system 'utf-8) 45 | '(send-mail-function 'mailclient-send-it) 46 | '(show-paren-mode t) 47 | '(sort-fold-case t t) 48 | '(split-height-threshold 9999) 49 | '(split-width-threshold 9999) 50 | '(tab-width 2) 51 | '(tool-bar-mode nil) 52 | '(uniquify-buffer-name-style 'forward nil (uniquify)) 53 | '(vc-follow-symlinks t) 54 | '(vc-make-backup-files t) 55 | '(visual-line-fringe-indicators '(left-curly-arrow right-curly-arrow)) 56 | '(whitespace-display-mappings 57 | '((space-mark 32 [32] [32]) (space-mark 160 [160] [160]) 58 | (newline-mark 10 [32 10]) (tab-mark 9 [32 9] [32 9]))) 59 | '(whitespace-global-modes '(not dired-mode magit-mode magit-log-mode shell-mode)) 60 | '(whitespace-style 61 | '(face trailing lines-tail newline empty space-after-tab 62 | space-before-tab space-mark newline-mark))) 63 | (custom-set-faces 64 | ;; custom-set-faces was added by Custom. 65 | ;; If you edit it by hand, you could mess it up, so be careful. 66 | ;; Your init file should contain only one such instance. 67 | ;; If there is more than one, they won't work right. 68 | '(default ((t (:inherit nil :stipple nil :background nil :foreground nil :inverse-video nil :box nil :strike-through nil :overline nil :underline nil :slant normal :weight normal :height 113 :width normal :foundry "adobe" :family "Source Code Pro")))) 69 | '(ledger-font-xact-highlight-face ((t nil)) t) 70 | '(whitespace-indentation ((t (:background "beige" :foreground "firebrick")))) 71 | '(whitespace-line ((t (:background "brightwhite")))) 72 | '(whitespace-newline ((t (:weight ultra-light)))) 73 | '(whitespace-space ((t (:foreground "#624956" :weight thin)))) 74 | '(whitespace-tab ((t nil)))) 75 | -------------------------------------------------------------------------------- /flake.lock: -------------------------------------------------------------------------------- 1 | { 2 | "nodes": { 3 | "emacs-overlay": { 4 | "inputs": { 5 | "nixpkgs": [ 6 | "nixpkgs" 7 | ], 8 | "nixpkgs-stable": "nixpkgs-stable" 9 | }, 10 | "locked": { 11 | "lastModified": 1750785611, 12 | "narHash": "sha256-fi8N4PAlBA6A1yoXywCQsagGfCMNPHt9QL05p644jjU=", 13 | "owner": "nix-community", 14 | "repo": "emacs-overlay", 15 | "rev": "0cce9a0141bd5d937262adb4861355d07015e715", 16 | "type": "github" 17 | }, 18 | "original": { 19 | "owner": "nix-community", 20 | "repo": "emacs-overlay", 21 | "type": "github" 22 | } 23 | }, 24 | "flake-utils": { 25 | "inputs": { 26 | "systems": "systems" 27 | }, 28 | "locked": { 29 | "lastModified": 1731533236, 30 | "narHash": "sha256-l0KFg5HjrsfsO/JpG+r7fRrqm12kzFHyUHqHCVpMMbI=", 31 | "owner": "numtide", 32 | "repo": "flake-utils", 33 | "rev": "11707dc2f618dd54ca8739b309ec4fc024de578b", 34 | "type": "github" 35 | }, 36 | "original": { 37 | "id": "flake-utils", 38 | "type": "indirect" 39 | } 40 | }, 41 | "home-manager": { 42 | "inputs": { 43 | "nixpkgs": [ 44 | "nixpkgs" 45 | ] 46 | }, 47 | "locked": { 48 | "lastModified": 1761066098, 49 | "narHash": "sha256-Fd65ryxzMRsNQ0MqaiT/b3TdinUOKUJ4PyCwnoKcvF0=", 50 | "owner": "nix-community", 51 | "repo": "home-manager", 52 | "rev": "13b2744e117993dc5066c1710585dcb99877684f", 53 | "type": "github" 54 | }, 55 | "original": { 56 | "owner": "nix-community", 57 | "repo": "home-manager", 58 | "type": "github" 59 | } 60 | }, 61 | "mmdoc": { 62 | "inputs": { 63 | "nixpkgs": [ 64 | "nixpkgs-update", 65 | "nixpkgs" 66 | ], 67 | "systems": "systems_2" 68 | }, 69 | "locked": { 70 | "lastModified": 1710694589, 71 | "narHash": "sha256-5wa+Jzxr+LygoxSZuZg0YU81jgdnx2IY/CqDIJMOgec=", 72 | "owner": "ryantm", 73 | "repo": "mmdoc", 74 | "rev": "b6ddf748b1d1c01ca582bb1b3dafd6bc3a4c83a6", 75 | "type": "github" 76 | }, 77 | "original": { 78 | "owner": "ryantm", 79 | "repo": "mmdoc", 80 | "type": "github" 81 | } 82 | }, 83 | "nixpkgs": { 84 | "locked": { 85 | "lastModified": 1761079315, 86 | "narHash": "sha256-HxRR8KeDYfkid5GBe16lPoVR7cXga/Y7Ii6fbPxyME8=", 87 | "owner": "nixos", 88 | "repo": "nixpkgs", 89 | "rev": "7e27919bf2d5e13b4d5c2595020500952f821219", 90 | "type": "github" 91 | }, 92 | "original": { 93 | "owner": "nixos", 94 | "repo": "nixpkgs", 95 | "type": "github" 96 | } 97 | }, 98 | "nixpkgs-stable": { 99 | "locked": { 100 | "lastModified": 1750646418, 101 | "narHash": "sha256-4UAN+W0Lp4xnUiHYXUXAPX18t+bn6c4Btry2RqM9JHY=", 102 | "owner": "NixOS", 103 | "repo": "nixpkgs", 104 | "rev": "1f426f65ac4e6bf808923eb6f8b8c2bfba3d18c5", 105 | "type": "github" 106 | }, 107 | "original": { 108 | "owner": "NixOS", 109 | "ref": "nixos-24.11", 110 | "repo": "nixpkgs", 111 | "type": "github" 112 | } 113 | }, 114 | "nixpkgs-update": { 115 | "inputs": { 116 | "mmdoc": "mmdoc", 117 | "nixpkgs": "nixpkgs_2", 118 | "runtimeDeps": "runtimeDeps", 119 | "treefmt-nix": "treefmt-nix" 120 | }, 121 | "locked": { 122 | "lastModified": 1747784777, 123 | "narHash": "sha256-1y7jTtEEp5uSrsjEHZ1XJbIOXsgDGXPY+aQl388neHc=", 124 | "owner": "ryantm", 125 | "repo": "nixpkgs-update", 126 | "rev": "6f29c8f924f24e9350df34bb6f049f2704195f85", 127 | "type": "github" 128 | }, 129 | "original": { 130 | "owner": "ryantm", 131 | "repo": "nixpkgs-update", 132 | "type": "github" 133 | } 134 | }, 135 | "nixpkgs_2": { 136 | "locked": { 137 | "lastModified": 1672428209, 138 | "narHash": "sha256-eejhqkDz2cb2vc5VeaWphJz8UXNuoNoM8/Op8eWv2tQ=", 139 | "owner": "NixOS", 140 | "repo": "nixpkgs", 141 | "rev": "293a28df6d7ff3dec1e61e37cc4ee6e6c0fb0847", 142 | "type": "github" 143 | }, 144 | "original": { 145 | "id": "nixpkgs", 146 | "type": "indirect" 147 | } 148 | }, 149 | "root": { 150 | "inputs": { 151 | "emacs-overlay": "emacs-overlay", 152 | "flake-utils": "flake-utils", 153 | "home-manager": "home-manager", 154 | "nixpkgs": "nixpkgs", 155 | "nixpkgs-update": "nixpkgs-update" 156 | } 157 | }, 158 | "runtimeDeps": { 159 | "locked": { 160 | "lastModified": 1714247354, 161 | "narHash": "sha256-6dFKqP/aCKIdpOgqgIQUrRT0NOfVc14ftNcdELa4Pu4=", 162 | "owner": "NixOS", 163 | "repo": "nixpkgs", 164 | "rev": "c8d7c8a78fb516c0842cc65346506a565c88014d", 165 | "type": "github" 166 | }, 167 | "original": { 168 | "owner": "NixOS", 169 | "ref": "nixos-unstable-small", 170 | "repo": "nixpkgs", 171 | "type": "github" 172 | } 173 | }, 174 | "systems": { 175 | "locked": { 176 | "lastModified": 1681028828, 177 | "narHash": "sha256-Vy1rq5AaRuLzOxct8nz4T6wlgyUR7zLU309k9mBC768=", 178 | "owner": "nix-systems", 179 | "repo": "default", 180 | "rev": "da67096a3b9bf56a91d16901293e51ba5b49a27e", 181 | "type": "github" 182 | }, 183 | "original": { 184 | "owner": "nix-systems", 185 | "repo": "default", 186 | "type": "github" 187 | } 188 | }, 189 | "systems_2": { 190 | "locked": { 191 | "lastModified": 1681028828, 192 | "narHash": "sha256-Vy1rq5AaRuLzOxct8nz4T6wlgyUR7zLU309k9mBC768=", 193 | "owner": "nix-systems", 194 | "repo": "default", 195 | "rev": "da67096a3b9bf56a91d16901293e51ba5b49a27e", 196 | "type": "github" 197 | }, 198 | "original": { 199 | "owner": "nix-systems", 200 | "repo": "default", 201 | "type": "github" 202 | } 203 | }, 204 | "treefmt-nix": { 205 | "inputs": { 206 | "nixpkgs": [ 207 | "nixpkgs-update", 208 | "nixpkgs" 209 | ] 210 | }, 211 | "locked": { 212 | "lastModified": 1711963903, 213 | "narHash": "sha256-N3QDhoaX+paWXHbEXZapqd1r95mdshxToGowtjtYkGI=", 214 | "owner": "numtide", 215 | "repo": "treefmt-nix", 216 | "rev": "49dc4a92b02b8e68798abd99184f228243b6e3ac", 217 | "type": "github" 218 | }, 219 | "original": { 220 | "owner": "numtide", 221 | "repo": "treefmt-nix", 222 | "type": "github" 223 | } 224 | } 225 | }, 226 | "root": "root", 227 | "version": 7 228 | } 229 | -------------------------------------------------------------------------------- /home.nix: -------------------------------------------------------------------------------- 1 | { 2 | pkgs, 3 | lib, 4 | config, 5 | ... 6 | }: 7 | { 8 | imports = [ 9 | ./emacs 10 | ]; 11 | 12 | programs.home-manager.enable = true; 13 | 14 | nixpkgs.config = import ./nixpkgs-config.nix; 15 | 16 | xdg.configFile."nixpkgs/config.nix".source = ./nixpkgs-config.nix; 17 | 18 | home.packages = with pkgs; [ 19 | amberol 20 | nodejs 21 | code-cursor 22 | claude-code 23 | clang-tools 24 | _1password-gui 25 | nix-tree 26 | beancount 27 | basedpyright 28 | cmus 29 | evince 30 | freerdp 31 | gh 32 | git-delete-merged-branches 33 | gnupg 34 | go 35 | gopls 36 | hydra-check 37 | inkscape 38 | jq 39 | ledger 40 | meld 41 | nil 42 | nixfmt-rfc-style 43 | nixpkgs-review 44 | openvpn 45 | qbittorrent 46 | remmina 47 | cargo 48 | ripgrep 49 | ruff 50 | rustc 51 | rust-analyzer 52 | typescript-language-server 53 | typescript-go 54 | nodePackages.prettier 55 | nodePackages.vscode-json-languageserver 56 | usbutils 57 | virt-manager 58 | vlc 59 | xterm 60 | yubikey-manager 61 | # zeal 62 | zoom-us 63 | (pkgs.writeScriptBin "hms" '' 64 | home-manager switch --flake /home/ryantm/p/dotfiles#ryantm 65 | '') 66 | (pkgs.writeScriptBin "hmud" '' 67 | pushd ~/p/dotfiles 68 | nix flake update 69 | popd 70 | '') 71 | (pkgs.writeScriptBin "nr" '' 72 | pushd ~/p/nixpkgs 73 | nixpkgs-review pr "$1" 74 | popd 75 | '') 76 | (pkgs.writeScriptBin "pr" '' 77 | nixpkgs-review post-result 78 | '') 79 | ]; 80 | 81 | home.keyboard.options = [ "ctrl:nocaps" ]; 82 | 83 | systemd.user.sessionVariables = config.home.sessionVariables; 84 | 85 | home.sessionVariables = { 86 | TERM = "xterm-256color"; 87 | BROWSER = "google-chrome-stable"; 88 | TMUX_TMPDIR = "$XDG_RUNTIME_DIR"; 89 | }; 90 | 91 | # Get systemd environment variables for ssh login shell too 92 | # home.sessionVariablesExtra = '' 93 | # set -a 94 | # eval $(/run/current-system/sw/lib/systemd/user-environment-generators/30-systemd-environment-d-generator) 95 | # set +a 96 | # ''; 97 | 98 | programs.direnv = { 99 | enable = true; 100 | enableBashIntegration = true; 101 | nix-direnv.enable = true; 102 | }; 103 | 104 | xdg.configFile."alacritty/alacritty.toml" = lib.mkIf (config.programs.alacritty.settings != { }) { 105 | source = 106 | ((pkgs.formats.toml { }).generate "alacritty.toml" config.programs.alacritty.settings).overrideAttrs 107 | ( 108 | finalAttrs: prevAttrs: { 109 | buildCommand = lib.concatStringsSep "\n" [ 110 | prevAttrs.buildCommand 111 | # TODO: why is this needed? Is there a better way to retain escape sequences? 112 | "substituteInPlace $out --replace '\\\\' '\\'" 113 | ]; 114 | } 115 | ); 116 | }; 117 | 118 | programs.alacritty = { 119 | #enable = true; 120 | settings = { 121 | window = { 122 | dynamic_title = false; 123 | }; 124 | font = { 125 | size = 20; 126 | normal = { 127 | family = "DejaVu Sans Mono"; 128 | }; 129 | }; 130 | # The 'GNOME Light" theme from GNOME terminal. 131 | colors = { 132 | primary = { 133 | foreground = "#171421"; 134 | background = "#ffffff"; 135 | }; 136 | normal = { 137 | black = "#171421"; 138 | red = "#c01c28"; 139 | green = "#26a269"; 140 | yellow = "#a2734c"; 141 | blue = "#12488b"; 142 | magenta = "#a347ba"; 143 | cyan = "#2aa1b3"; 144 | white = "#d0cfcc"; 145 | }; 146 | bright = { 147 | black = "#5e5c64"; 148 | red = "#f66151"; 149 | green = "#33d17a"; 150 | yellow = "#e9ad0c"; 151 | blue = "#2a7bde"; 152 | magenta = "#c061cb"; 153 | cyan = "#33c7de"; 154 | white = "#ffffff"; 155 | }; 156 | }; 157 | }; 158 | }; 159 | 160 | programs.bash = { 161 | enable = true; 162 | historyControl = [ "ignoredups" ]; 163 | historyIgnore = [ "ls" ]; 164 | 165 | shellAliases = { 166 | ls = "ls --color=auto"; 167 | grep = "grep --color=auto"; 168 | }; 169 | 170 | initExtra = '' 171 | export PATH="/home/ryantm/.local/bin:$PATH" 172 | ''; 173 | }; 174 | 175 | programs.bat = { 176 | enable = true; 177 | config.theme = "GitHub"; 178 | }; 179 | 180 | programs.git = { 181 | enable = true; 182 | settings = { 183 | core.fsmonitor = true; 184 | user.name = "Ryan Mulligan"; 185 | user.email = "ryan@ryantm.com"; 186 | user.useConfigOnly = true; 187 | color = { 188 | diff = "auto"; 189 | status = "auto"; 190 | branch = "auto"; 191 | }; 192 | push.default = "simple"; 193 | github.user = "ryantm"; 194 | merge.conflictstyle = "diff3"; 195 | init.defaultBranch = "main"; 196 | }; 197 | ignores = [ 198 | "result" 199 | "*.elc" 200 | ".#*" 201 | ".stack-work/" 202 | "#*" 203 | ".markdown-preview.html" 204 | ]; 205 | }; 206 | 207 | home.file = { 208 | ".gemrc".text = "gem: --no-ri --no-rdoc"; 209 | ".ghc/ghci.conf".source = ./ghc/ghci.conf; 210 | ".stack/config.yaml".source = ./stack/config.yaml; 211 | ".xinitrc".source = ./x/xinitrc; 212 | ".Xresources".source = ./x/Xresources; 213 | ".dir_colors".source = ./shell/dir_colors; 214 | ".asoundrc".source = ./alsa/asoundrc; 215 | }; 216 | 217 | xdg.configFile."fonts" = { 218 | source = ./config/fonts; 219 | recursive = true; 220 | }; 221 | 222 | xdg.configFile."run-or-raise/shortcuts.conf" = { 223 | text = '' 224 | # window classes with Alt+f2 run `lg` go to windows 225 | # shortcut,command,[wm_class],[title] 226 | #f,firefox,, 227 | f,google-chrome-stable,google-chrome, 228 | s,ghostty,com.mitchellh.ghostty, 229 | d,emacsclient --create-frame,Emacs, 230 | ''; 231 | }; 232 | 233 | # bind = $mainMod, S, exec, raise --class "com.mitchellh.ghostty" --launch "ghostty" 234 | # bind = $mainMod, D, exec, raise --class "Emacs" --launch "emacsclient -t" 235 | # bind = $mainMod, F, exec, raise --class "firefox" --launch "firefox" 236 | # bind = $mainMod, C, exec, raise --class "google-chrome" --launch "google-chrome-stable" 237 | 238 | programs.obs-studio = { 239 | enable = true; 240 | plugins = with pkgs.obs-studio-plugins; [ 241 | wlrobs 242 | obs-livesplit-one 243 | obs-backgroundremoval 244 | obs-pipewire-audio-capture 245 | ]; 246 | }; 247 | 248 | programs.ghostty = { 249 | enable = true; 250 | settings = { 251 | maximize = "true"; 252 | }; 253 | }; 254 | 255 | # systemd.user.services.ssh-agent = { 256 | # description = "SSH key agent"; 257 | # environment.SSH_AUTH_SOCK = "%t/ssh-agent"; 258 | # }; 259 | } 260 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | Creative Commons Legal Code 2 | 3 | CC0 1.0 Universal 4 | 5 | CREATIVE COMMONS CORPORATION IS NOT A LAW FIRM AND DOES NOT PROVIDE 6 | LEGAL SERVICES. DISTRIBUTION OF THIS DOCUMENT DOES NOT CREATE AN 7 | ATTORNEY-CLIENT RELATIONSHIP. CREATIVE COMMONS PROVIDES THIS 8 | INFORMATION ON AN "AS-IS" BASIS. CREATIVE COMMONS MAKES NO WARRANTIES 9 | REGARDING THE USE OF THIS DOCUMENT OR THE INFORMATION OR WORKS 10 | PROVIDED HEREUNDER, AND DISCLAIMS LIABILITY FOR DAMAGES RESULTING FROM 11 | THE USE OF THIS DOCUMENT OR THE INFORMATION OR WORKS PROVIDED 12 | HEREUNDER. 13 | 14 | Statement of Purpose 15 | 16 | The laws of most jurisdictions throughout the world automatically confer 17 | exclusive Copyright and Related Rights (defined below) upon the creator 18 | and subsequent owner(s) (each and all, an "owner") of an original work of 19 | authorship and/or a database (each, a "Work"). 20 | 21 | Certain owners wish to permanently relinquish those rights to a Work for 22 | the purpose of contributing to a commons of creative, cultural and 23 | scientific works ("Commons") that the public can reliably and without fear 24 | of later claims of infringement build upon, modify, incorporate in other 25 | works, reuse and redistribute as freely as possible in any form whatsoever 26 | and for any purposes, including without limitation commercial purposes. 27 | These owners may contribute to the Commons to promote the ideal of a free 28 | culture and the further production of creative, cultural and scientific 29 | works, or to gain reputation or greater distribution for their Work in 30 | part through the use and efforts of others. 31 | 32 | For these and/or other purposes and motivations, and without any 33 | expectation of additional consideration or compensation, the person 34 | associating CC0 with a Work (the "Affirmer"), to the extent that he or she 35 | is an owner of Copyright and Related Rights in the Work, voluntarily 36 | elects to apply CC0 to the Work and publicly distribute the Work under its 37 | terms, with knowledge of his or her Copyright and Related Rights in the 38 | Work and the meaning and intended legal effect of CC0 on those rights. 39 | 40 | 1. Copyright and Related Rights. A Work made available under CC0 may be 41 | protected by copyright and related or neighboring rights ("Copyright and 42 | Related Rights"). Copyright and Related Rights include, but are not 43 | limited to, the following: 44 | 45 | i. the right to reproduce, adapt, distribute, perform, display, 46 | communicate, and translate a Work; 47 | ii. moral rights retained by the original author(s) and/or performer(s); 48 | iii. publicity and privacy rights pertaining to a person's image or 49 | likeness depicted in a Work; 50 | iv. rights protecting against unfair competition in regards to a Work, 51 | subject to the limitations in paragraph 4(a), below; 52 | v. rights protecting the extraction, dissemination, use and reuse of data 53 | in a Work; 54 | vi. database rights (such as those arising under Directive 96/9/EC of the 55 | European Parliament and of the Council of 11 March 1996 on the legal 56 | protection of databases, and under any national implementation 57 | thereof, including any amended or successor version of such 58 | directive); and 59 | vii. other similar, equivalent or corresponding rights throughout the 60 | world based on applicable law or treaty, and any national 61 | implementations thereof. 62 | 63 | 2. Waiver. To the greatest extent permitted by, but not in contravention 64 | of, applicable law, Affirmer hereby overtly, fully, permanently, 65 | irrevocably and unconditionally waives, abandons, and surrenders all of 66 | Affirmer's Copyright and Related Rights and associated claims and causes 67 | of action, whether now known or unknown (including existing as well as 68 | future claims and causes of action), in the Work (i) in all territories 69 | worldwide, (ii) for the maximum duration provided by applicable law or 70 | treaty (including future time extensions), (iii) in any current or future 71 | medium and for any number of copies, and (iv) for any purpose whatsoever, 72 | including without limitation commercial, advertising or promotional 73 | purposes (the "Waiver"). Affirmer makes the Waiver for the benefit of each 74 | member of the public at large and to the detriment of Affirmer's heirs and 75 | successors, fully intending that such Waiver shall not be subject to 76 | revocation, rescission, cancellation, termination, or any other legal or 77 | equitable action to disrupt the quiet enjoyment of the Work by the public 78 | as contemplated by Affirmer's express Statement of Purpose. 79 | 80 | 3. Public License Fallback. Should any part of the Waiver for any reason 81 | be judged legally invalid or ineffective under applicable law, then the 82 | Waiver shall be preserved to the maximum extent permitted taking into 83 | account Affirmer's express Statement of Purpose. In addition, to the 84 | extent the Waiver is so judged Affirmer hereby grants to each affected 85 | person a royalty-free, non transferable, non sublicensable, non exclusive, 86 | irrevocable and unconditional license to exercise Affirmer's Copyright and 87 | Related Rights in the Work (i) in all territories worldwide, (ii) for the 88 | maximum duration provided by applicable law or treaty (including future 89 | time extensions), (iii) in any current or future medium and for any number 90 | of copies, and (iv) for any purpose whatsoever, including without 91 | limitation commercial, advertising or promotional purposes (the 92 | "License"). The License shall be deemed effective as of the date CC0 was 93 | applied by Affirmer to the Work. Should any part of the License for any 94 | reason be judged legally invalid or ineffective under applicable law, such 95 | partial invalidity or ineffectiveness shall not invalidate the remainder 96 | of the License, and in such case Affirmer hereby affirms that he or she 97 | will not (i) exercise any of his or her remaining Copyright and Related 98 | Rights in the Work or (ii) assert any associated claims and causes of 99 | action with respect to the Work, in either case contrary to Affirmer's 100 | express Statement of Purpose. 101 | 102 | 4. Limitations and Disclaimers. 103 | 104 | a. No trademark or patent rights held by Affirmer are waived, abandoned, 105 | surrendered, licensed or otherwise affected by this document. 106 | b. Affirmer offers the Work as-is and makes no representations or 107 | warranties of any kind concerning the Work, express, implied, 108 | statutory or otherwise, including without limitation warranties of 109 | title, merchantability, fitness for a particular purpose, non 110 | infringement, or the absence of latent or other defects, accuracy, or 111 | the present or absence of errors, whether or not discoverable, all to 112 | the greatest extent permissible under applicable law. 113 | c. Affirmer disclaims responsibility for clearing rights of other persons 114 | that may apply to the Work or any use thereof, including without 115 | limitation any person's Copyright and Related Rights in the Work. 116 | Further, Affirmer disclaims responsibility for obtaining any necessary 117 | consents, permissions or other rights required for any use of the 118 | Work. 119 | d. Affirmer understands and acknowledges that Creative Commons is not a 120 | party to this document and has no duty or obligation with respect to 121 | this CC0 or use of the Work. -------------------------------------------------------------------------------- /shell/dir_colors: -------------------------------------------------------------------------------- 1 | 2 | # Dark 256 color solarized theme for the color GNU ls utility. 3 | # Used and tested with dircolors (GNU coreutils) 8.5 4 | # 5 | # @author {@link http://sebastian.tramp.name Sebastian Tramp} 6 | # @license http://sam.zoy.org/wtfpl/ Do What The Fuck You Want To Public License (WTFPL) 7 | # 8 | # More Information at 9 | # https://github.com/seebi/dircolors-solarized 10 | 11 | # Term Section 12 | TERM Eterm 13 | TERM ansi 14 | TERM color-xterm 15 | TERM con132x25 16 | TERM con132x30 17 | TERM con132x43 18 | TERM con132x60 19 | TERM con80x25 20 | TERM con80x28 21 | TERM con80x30 22 | TERM con80x43 23 | TERM con80x50 24 | TERM con80x60 25 | TERM cons25 26 | TERM console 27 | TERM cygwin 28 | TERM dtterm 29 | TERM eterm-color 30 | TERM fbterm 31 | TERM gnome 32 | TERM gnome-256color 33 | TERM jfbterm 34 | TERM konsole 35 | TERM konsole-256color 36 | TERM kterm 37 | TERM linux 38 | TERM linux-c 39 | TERM mach-color 40 | TERM mlterm 41 | TERM putty 42 | TERM putty-256color 43 | TERM rxvt 44 | TERM rxvt-256color 45 | TERM rxvt-cygwin 46 | TERM rxvt-cygwin-native 47 | TERM rxvt-unicode 48 | TERM rxvt-unicode256 49 | TERM rxvt-unicode-256color 50 | TERM screen 51 | TERM screen-16color 52 | TERM screen-16color-bce 53 | TERM screen-16color-s 54 | TERM screen-16color-bce-s 55 | TERM screen-256color 56 | TERM screen-256color-bce 57 | TERM screen-256color-s 58 | TERM screen-256color-bce-s 59 | TERM screen-bce 60 | TERM screen-w 61 | TERM screen.linux 62 | TERM st 63 | TERM st-meta 64 | TERM st-256color 65 | TERM st-meta-256color 66 | TERM vt100 67 | TERM xterm 68 | TERM xterm-16color 69 | TERM xterm-256color 70 | TERM xterm-88color 71 | TERM xterm-color 72 | TERM xterm-debian 73 | TERM xterm-termite 74 | 75 | ## Documentation 76 | # 77 | # standard colors 78 | # 79 | # Below are the color init strings for the basic file types. A color init 80 | # string consists of one or more of the following numeric codes: 81 | # Attribute codes: 82 | # 00=none 01=bold 04=underscore 05=blink 07=reverse 08=concealed 83 | # Text color codes: 84 | # 30=black 31=red 32=green 33=yellow 34=blue 35=magenta 36=cyan 37=white 85 | # Background color codes: 86 | # 40=black 41=red 42=green 43=yellow 44=blue 45=magenta 46=cyan 47=white 87 | # 88 | # 89 | # 256 color support 90 | # see here: http://www.mail-archive.com/bug-coreutils@gnu.org/msg11030.html) 91 | # 92 | # Text 256 color coding: 93 | # 38;5;COLOR_NUMBER 94 | # Background 256 color coding: 95 | # 48;5;COLOR_NUMBER 96 | 97 | ## Special files 98 | 99 | NORMAL 38;5;244 # no color code at all 100 | #FILE 00 # regular file: use no color at all 101 | RESET 0 # reset to "normal" color 102 | DIR 38;5;33 # directory 01;34 103 | LINK 38;5;37 # symbolic link. (If you set this to 'target' instead of a 104 | # numerical value, the color is as for the file pointed to.) 105 | MULTIHARDLINK 00 # regular file with more than one link 106 | FIFO 48;5;230;38;5;136;01 # pipe 107 | SOCK 48;5;230;38;5;136;01 # socket 108 | DOOR 48;5;230;38;5;136;01 # door 109 | BLK 48;5;230;38;5;244;01 # block device driver 110 | CHR 48;5;230;38;5;244;01 # character device driver 111 | ORPHAN 48;5;235;38;5;160 # symlink to nonexistent file, or non-stat'able file 112 | SETUID 48;5;160;38;5;230 # file that is setuid (u+s) 113 | SETGID 48;5;136;38;5;230 # file that is setgid (g+s) 114 | CAPABILITY 30;41 # file with capability 115 | STICKY_OTHER_WRITABLE 48;5;64;38;5;230 # dir that is sticky and other-writable (+t,o+w) 116 | OTHER_WRITABLE 48;5;235;38;5;33 # dir that is other-writable (o+w) and not sticky 117 | STICKY 48;5;33;38;5;230 # dir with the sticky bit set (+t) and not other-writable 118 | # This is for files with execute permission: 119 | EXEC 38;5;64 120 | 121 | ## Archives or compressed (violet + bold for compression) 122 | .tar 38;5;61 123 | .tgz 38;5;61 124 | .arj 38;5;61 125 | .taz 38;5;61 126 | .lzh 38;5;61 127 | .lzma 38;5;61 128 | .tlz 38;5;61 129 | .txz 38;5;61 130 | .zip 38;5;61 131 | .z 38;5;61 132 | .Z 38;5;61 133 | .dz 38;5;61 134 | .gz 38;5;61 135 | .lz 38;5;61 136 | .xz 38;5;61 137 | .bz2 38;5;61 138 | .bz 38;5;61 139 | .tbz 38;5;61 140 | .tbz2 38;5;61 141 | .tz 38;5;61 142 | .deb 38;5;61 143 | .rpm 38;5;61 144 | .jar 38;5;61 145 | .rar 38;5;61 146 | .ace 38;5;61 147 | .zoo 38;5;61 148 | .cpio 38;5;61 149 | .7z 38;5;61 150 | .rz 38;5;61 151 | .apk 38;5;61 152 | .gem 38;5;61 153 | 154 | # Image formats (yellow) 155 | .jpg 38;5;136 156 | .JPG 38;5;136 #stupid but needed 157 | .jpeg 38;5;136 158 | .gif 38;5;136 159 | .bmp 38;5;136 160 | .pbm 38;5;136 161 | .pgm 38;5;136 162 | .ppm 38;5;136 163 | .tga 38;5;136 164 | .xbm 38;5;136 165 | .xpm 38;5;136 166 | .tif 38;5;136 167 | .tiff 38;5;136 168 | .png 38;5;136 169 | .svg 38;5;136 170 | .svgz 38;5;136 171 | .mng 38;5;136 172 | .pcx 38;5;136 173 | .dl 38;5;136 174 | .xcf 38;5;136 175 | .xwd 38;5;136 176 | .yuv 38;5;136 177 | .cgm 38;5;136 178 | .emf 38;5;136 179 | .eps 38;5;136 180 | .CR2 38;5;136 181 | .ico 38;5;136 182 | 183 | # Files of special interest (base1) 184 | .tex 38;5;245 185 | .rdf 38;5;245 186 | .owl 38;5;245 187 | .n3 38;5;245 188 | .ttl 38;5;245 189 | .nt 38;5;245 190 | .torrent 38;5;245 191 | .xml 38;5;245 192 | *Makefile 38;5;245 193 | *Rakefile 38;5;245 194 | *build.xml 38;5;245 195 | *rc 38;5;245 196 | *1 38;5;245 197 | .nfo 38;5;245 198 | *README 38;5;245 199 | *README.txt 38;5;245 200 | *readme.txt 38;5;245 201 | .md 38;5;245 202 | *README.markdown 38;5;245 203 | .ini 38;5;245 204 | .yml 38;5;245 205 | .cfg 38;5;245 206 | .conf 38;5;245 207 | .c 38;5;245 208 | .cpp 38;5;245 209 | .cc 38;5;245 210 | .sqlite 38;5;245 211 | 212 | # "unimportant" files as logs and backups (base01) 213 | .log 38;5;240 214 | .bak 38;5;240 215 | .aux 38;5;240 216 | .lof 38;5;240 217 | .lol 38;5;240 218 | .lot 38;5;240 219 | .out 38;5;240 220 | .toc 38;5;240 221 | .bbl 38;5;240 222 | .blg 38;5;240 223 | *~ 38;5;240 224 | *# 38;5;240 225 | .part 38;5;240 226 | .incomplete 38;5;240 227 | .swp 38;5;240 228 | .tmp 38;5;240 229 | .temp 38;5;240 230 | .o 38;5;240 231 | .pyc 38;5;240 232 | .class 38;5;240 233 | .cache 38;5;240 234 | 235 | # Audio formats (orange) 236 | .aac 38;5;166 237 | .au 38;5;166 238 | .flac 38;5;166 239 | .mid 38;5;166 240 | .midi 38;5;166 241 | .mka 38;5;166 242 | .mp3 38;5;166 243 | .mpc 38;5;166 244 | .ogg 38;5;166 245 | .ra 38;5;166 246 | .wav 38;5;166 247 | .m4a 38;5;166 248 | # http://wiki.xiph.org/index.php/MIME_Types_and_File_Extensions 249 | .axa 38;5;166 250 | .oga 38;5;166 251 | .spx 38;5;166 252 | .xspf 38;5;166 253 | 254 | # Video formats (as audio + bold) 255 | .mov 38;5;166 256 | .mpg 38;5;166 257 | .mpeg 38;5;166 258 | .m2v 38;5;166 259 | .mkv 38;5;166 260 | .ogm 38;5;166 261 | .mp4 38;5;166 262 | .m4v 38;5;166 263 | .mp4v 38;5;166 264 | .vob 38;5;166 265 | .qt 38;5;166 266 | .nuv 38;5;166 267 | .wmv 38;5;166 268 | .asf 38;5;166 269 | .rm 38;5;166 270 | .rmvb 38;5;166 271 | .flc 38;5;166 272 | .avi 38;5;166 273 | .fli 38;5;166 274 | .flv 38;5;166 275 | .gl 38;5;166 276 | .m2ts 38;5;166 277 | .divx 38;5;166 278 | .webm 38;5;166 279 | # http://wiki.xiph.org/index.php/MIME_Types_and_File_Extensions 280 | .axv 38;5;166 281 | .anx 38;5;166 282 | .ogv 38;5;166 283 | .ogx 38;5;166 -------------------------------------------------------------------------------- /emacs/init.el: -------------------------------------------------------------------------------- 1 | (defconst emacs-start-time (current-time)) 2 | (unless noninteractive 3 | (message "Loading %s..." load-file-name)) 4 | 5 | ;; Customizations 6 | (defconst custom-file-start-time (current-time)) 7 | 8 | (setq custom-file (expand-file-name "custom-file.el" user-emacs-directory)) 9 | (load custom-file) 10 | (setq custom-file (expand-file-name "~/p/dotfiles/emacs/custom-file.el")) 11 | 12 | (unless noninteractive 13 | (let ((elapsed (float-time (time-subtract (current-time) 14 | custom-file-start-time)))) 15 | (message "Loading custom-file...done (%.3fs)" elapsed))) 16 | 17 | (eval-when-compile 18 | (require 'package) 19 | (package-initialize) 20 | (defvar use-package-verbose t) 21 | (require 'use-package)) 22 | 23 | (add-to-list 'default-frame-alist '(fullscreen . maximized)) 24 | 25 | (setq major-mode-remap-alist 26 | '((yaml-mode . yaml-ts-mode) 27 | (bash-mode . bash-ts-mode) 28 | (go-mode . go-ts-mode) 29 | (typescript-mode . typescript-ts-mode) 30 | (json-mode . json-ts-mode) 31 | (css-mode . css-ts-mode) 32 | (python-mode . python-ts-mode))) 33 | 34 | (use-package bind-key) 35 | (use-package diminish) 36 | (use-package uniquify 37 | :defer 5) 38 | 39 | (use-package editorconfig 40 | :ensure t 41 | :config 42 | (editorconfig-mode 1)) 43 | 44 | (use-package eglot 45 | :hook (prog-mode . eglot-ensure) 46 | :config 47 | (setq eglot-server-programs 48 | '((typescript-ts-mode . ("tsgo" "--lsp" "-stdio")) 49 | (tsx-ts-mode . ("typescript-language-server" "--stdio")) 50 | (go-ts-mode . ("gopls")) 51 | (json-ts-mode . ("vscode-json-languageserver" "--stdio")) 52 | (js-json-mode . ("vscode-json-languageserver" "--stdio")) 53 | (python-ts-mode . ("basedpyright-langserver" "--stdio")) 54 | (c++-mode . ("clangd")))) 55 | (setq-default 56 | eglot-workspace-configuration 57 | '(:basedpyright.analysis ( 58 | :diagnosticMode "openFilesOnly")))) 59 | 60 | (use-package c++-mode 61 | :hook (c++-mode . eglot-ensure)) 62 | 63 | (setq font-lock-maximum-decoration t) 64 | (setq-default show-trailing-whitespace nil) 65 | 66 | (defun eglot-format-buffer-on-save () 67 | (add-hook 'before-save-hook #'eglot-format-buffer -10 t)) 68 | 69 | (use-package python-ts-mode 70 | :hook (python-ts-mode . eglot-ensure) 71 | (python-ts-mode . eglot-format-buffer-on-save)) 72 | 73 | (use-package go-ts-mode 74 | :hook ((go-ts-mode . eglot-ensure) 75 | (go-ts-mode . eglot-format-buffer-on-save))) 76 | 77 | (use-package mule 78 | :custom 79 | (set-terminal-coding-system 'utf-8) 80 | (prefer-coding-system 'utf-8) 81 | :config 82 | (setq locale-coding-system 'utf-8)) 83 | 84 | (use-package bash-completion 85 | :disabled 86 | :config 87 | (bash-completion-setup)) 88 | 89 | (use-package autorevert 90 | :defer 5 91 | :config 92 | (setq auto-revert-verbose nil) 93 | (setq global-auto-revert-non-file-buffers t) 94 | (global-auto-revert-mode)) 95 | 96 | (use-package magit 97 | :commands (magit-status) 98 | :bind (("C-x g" . magit-status) 99 | :map magit-status-mode-map 100 | )) 101 | 102 | (use-package whitespace 103 | :defer 5 104 | :bind (("C-c w" . global-whitespace-mode)) 105 | :diminish (global-whitespace-mode 106 | whitespace-mode 107 | whitespace-newline-mode) 108 | :config 109 | (global-whitespace-mode)) 110 | 111 | (use-package multiple-cursors 112 | :defer t) 113 | 114 | (use-package flycheck 115 | :defer t) 116 | 117 | (use-package yaml-mode 118 | :mode "\\.ya?ml\\'") 119 | 120 | (use-package markdown-mode 121 | :mode (("\\`README\\.md\\'" . gfm-mode) 122 | ("\\.md\\'" . markdown-mode) 123 | ("\\.markdown\\'" . markdown-mode))) 124 | 125 | (use-package paren 126 | :hook (prog-mode . show-paren-mode)) 127 | 128 | (use-package dirtrack 129 | :hook (shell-mode . dirtrack-mode)) 130 | 131 | (use-package shell 132 | :commands shell 133 | :config 134 | (setq tab-width 8)) 135 | 136 | (defun prettier () 137 | (add-hook 'before-save-hook #'prettier-prettify -10 t)) 138 | 139 | (use-package typescript-ts-mode 140 | :config 141 | (setq typescript-indent-level 2 142 | typescript-ts-mode-indent-offset 2) 143 | :hook (typescript-ts-mode . prettier)) 144 | 145 | (use-package paredit 146 | :hook (emacs-lisp-mode . paredit-mode) 147 | :diminish (paredit-mode)) 148 | 149 | (use-package rainbow-delimiters 150 | :hook (emacs-lisp-mode . rainbow-delimiters-mode) 151 | :diminish (rainbow-delimiters-mode)) 152 | 153 | (use-package elisp-slime-nav 154 | :hook (emacs-lisp-mode . elisp-slime-nav-mode) 155 | :diminish (elisp-slime-nav-mode)) 156 | 157 | (use-package elisp-mode 158 | :mode ("\\.el\\'" . emacs-lisp-mode)) 159 | 160 | (use-package rust-mode 161 | :mode "\\.rs\\'") 162 | 163 | (use-package nix-mode 164 | :mode "\\.nix\\'" 165 | :functions nix-indent-line 166 | :hook (nix-mode . nixfmt-on-save-mode) 167 | :custom 168 | (nix-indent-function #'nix-indent-line)) 169 | 170 | ;; Enable Vertico. 171 | (use-package vertico 172 | ;;:custom 173 | ;; (vertico-scroll-margin 0) ;; Different scroll margin 174 | ;; (vertico-count 20) ;; Show more candidates 175 | ;; (vertico-resize t) ;; Grow and shrink the Vertico minibuffer 176 | ;; (vertico-cycle t) ;; Enable cycling for `vertico-next/previous' 177 | :init 178 | (vertico-mode)) 179 | 180 | ;; Persist history over Emacs restarts. Vertico sorts by history position. 181 | (use-package savehist 182 | :init 183 | (savehist-mode)) 184 | 185 | ;; Emacs minibuffer configurations. 186 | (use-package emacs 187 | :custom 188 | ;; Corfu 189 | 190 | ;; TAB cycle if there are only few candidates 191 | ;; (completion-cycle-threshold 3) 192 | 193 | ;; Enable indentation+completion using the TAB key. 194 | ;; `completion-at-point' is often bound to M-TAB. 195 | (tab-always-indent 'complete) 196 | 197 | ;; Emacs 30 and newer: Disable Ispell completion function. 198 | ;; Try `cape-dict' as an alternative. 199 | (text-mode-ispell-word-completion nil) 200 | 201 | ;; Hide commands in M-x which do not apply to the current mode. Corfu 202 | ;; commands are hidden, since they are not used via M-x. This setting is 203 | ;; useful beyond Corfu. 204 | (read-extended-command-predicate #'command-completion-default-include-p) 205 | 206 | ;; Enable context menu. `vertico-multiform-mode' adds a menu in the minibuffer 207 | ;; to switch display modes. 208 | (context-menu-mode t) 209 | ;; Support opening new minibuffers from inside existing minibuffers. 210 | (enable-recursive-minibuffers t) 211 | ;; Hide commands in M-x which do not work in the current mode. Vertico 212 | ;; commands are hidden in normal buffers. This setting is useful beyond 213 | ;; Vertico. 214 | (read-extended-command-predicate #'command-completion-default-include-p) 215 | ;; Do not allow the cursor in the minibuffer prompt 216 | (minibuffer-prompt-properties 217 | '(read-only t cursor-intangible t face minibuffer-prompt))) 218 | 219 | (use-package corfu 220 | ;; TAB-and-Go customizations 221 | :custom 222 | (corfu-cycle t) ;; Enable cycling for `corfu-next/previous' 223 | (corfu-preselect 'prompt) ;; Always preselect the prompt 224 | 225 | (corfu-auto t) 226 | (corfu-quit-no-match 'separator) 227 | 228 | ;; Use TAB for cycling, default is `corfu-complete'. 229 | :bind 230 | (:map corfu-map 231 | ("TAB" . corfu-next) 232 | ([tab] . corfu-next) 233 | ("S-TAB" . corfu-previous) 234 | ([backtab] . corfu-previous)) 235 | 236 | :init 237 | (global-corfu-mode)) 238 | 239 | ;; Optionally use the `orderless' completion style. 240 | (use-package orderless 241 | :custom 242 | ;; Configure a custom style dispatcher (see the Consult wiki) 243 | ;; (orderless-style-dispatchers '(+orderless-consult-dispatch orderless-affix-dispatch)) 244 | ;; (orderless-component-separator #'orderless-escapable-split-on-space) 245 | (completion-styles '(orderless basic)) 246 | (completion-category-defaults nil) 247 | (completion-category-overrides '((file (styles partial-completion))))) 248 | 249 | ;; Example configuration for Consult 250 | (use-package consult 251 | ;; Replace bindings. Lazily loaded by `use-package'. 252 | :bind (;; C-c bindings in `mode-specific-map' 253 | ("C-s" . consult-line) 254 | ("C-r" . consult-line) 255 | ("C-c M-x" . consult-mode-command) 256 | ("C-c h" . consult-history) 257 | ("C-c k" . consult-kmacro) 258 | ("C-c m" . consult-man) 259 | ("C-c i" . consult-info) 260 | ([remap Info-search] . consult-info) 261 | ;; C-x bindings in `ctl-x-map' 262 | ("C-x M-:" . consult-complex-command) ;; orig. repeat-complex-command 263 | ("C-x b" . consult-buffer) ;; orig. switch-to-buffer 264 | ("C-x 4 b" . consult-buffer-other-window) ;; orig. switch-to-buffer-other-window 265 | ("C-x 5 b" . consult-buffer-other-frame) ;; orig. switch-to-buffer-other-frame 266 | ("C-x t b" . consult-buffer-other-tab) ;; orig. switch-to-buffer-other-tab 267 | ("C-x r b" . consult-bookmark) ;; orig. bookmark-jump 268 | ("C-x p b" . consult-project-buffer) ;; orig. project-switch-to-buffer 269 | ;; Custom M-# bindings for fast register access 270 | ("M-#" . consult-register-load) 271 | ("M-'" . consult-register-store) ;; orig. abbrev-prefix-mark (unrelated) 272 | ("C-M-#" . consult-register) 273 | ;; Other custom bindings 274 | ("M-y" . consult-yank-pop) ;; orig. yank-pop 275 | ;; M-g bindings in `goto-map' 276 | ("M-g e" . consult-compile-error) 277 | ("M-g f" . consult-flymake) ;; Alternative: consult-flycheck 278 | ("M-g g" . consult-goto-line) ;; orig. goto-line 279 | ("M-g M-g" . consult-goto-line) ;; orig. goto-line 280 | ("M-g o" . consult-outline) ;; Alternative: consult-org-heading 281 | ("M-g m" . consult-mark) 282 | ("M-g k" . consult-global-mark) 283 | ("M-g i" . consult-imenu) 284 | ("M-g I" . consult-imenu-multi) 285 | ;; M-s bindings in `search-map' 286 | ("M-s d" . consult-find) ;; Alternative: consult-fd 287 | ("M-s c" . consult-locate) 288 | ("M-s g" . consult-grep) 289 | ("M-s G" . consult-git-grep) 290 | ("M-s r" . consult-ripgrep) 291 | ("M-s l" . consult-line) 292 | ("M-s L" . consult-line-multi) 293 | ("M-s k" . consult-keep-lines) 294 | ("M-s u" . consult-focus-lines) 295 | ;; Isearch integration 296 | ("M-s e" . consult-isearch-history) 297 | :map isearch-mode-map 298 | ("M-e" . consult-isearch-history) ;; orig. isearch-edit-string 299 | ("M-s e" . consult-isearch-history) ;; orig. isearch-edit-string 300 | ("M-s l" . consult-line) ;; needed by consult-line to detect isearch 301 | ("M-s L" . consult-line-multi) ;; needed by consult-line to detect isearch 302 | ;; Minibuffer history 303 | :map minibuffer-local-map 304 | ("M-s" . consult-history) ;; orig. next-matching-history-element 305 | ("M-r" . consult-history)) ;; orig. previous-matching-history-element 306 | 307 | ;; Enable automatic preview at point in the *Completions* buffer. This is 308 | ;; relevant when you use the default completion UI. 309 | :hook (completion-list-mode . consult-preview-at-point-mode) 310 | 311 | ;; The :init configuration is always executed (Not lazy) 312 | :init 313 | 314 | ;; Tweak the register preview for `consult-register-load', 315 | ;; `consult-register-store' and the built-in commands. This improves the 316 | ;; register formatting, adds thin separator lines, register sorting and hides 317 | ;; the window mode line. 318 | (advice-add #'register-preview :override #'consult-register-window) 319 | (setq register-preview-delay 0.5) 320 | 321 | ;; Use Consult to select xref locations with preview 322 | (setq xref-show-xrefs-function #'consult-xref 323 | xref-show-definitions-function #'consult-xref) 324 | 325 | ;; Configure other variables and modes in the :config section, 326 | ;; after lazily loading the package. 327 | :config 328 | 329 | ;; Optionally configure preview. The default value 330 | ;; is 'any, such that any key triggers the preview. 331 | ;; (setq consult-preview-key 'any) 332 | ;; (setq consult-preview-key "M-.") 333 | ;; (setq consult-preview-key '("S-" "S-")) 334 | ;; For some commands and buffer sources it is useful to configure the 335 | ;; :preview-key on a per-command basis using the `consult-customize' macro. 336 | (consult-customize 337 | consult-theme :preview-key '(:debounce 0.2 any) 338 | consult-ripgrep consult-git-grep consult-grep consult-man 339 | consult-bookmark consult-recent-file consult-xref 340 | consult--source-bookmark consult--source-file-register 341 | consult--source-recent-file consult--source-project-recent-file 342 | ;; :preview-key "M-." 343 | :preview-key '(:debounce 0.4 any)) 344 | 345 | ;; Optionally configure the narrowing key. 346 | ;; Both < and C-+ work reasonably well. 347 | (setq consult-narrow-key "<") ;; "C-+" 348 | 349 | ;; Optionally make narrowing help available in the minibuffer. 350 | ;; You may want to use `embark-prefix-help-command' or which-key instead. 351 | ;; (keymap-set consult-narrow-map (concat consult-narrow-key " ?") #'consult-narrow-help) 352 | ) 353 | 354 | 355 | (use-package zeal-at-point 356 | :bind ("C-c d" . zeal-at-point)) 357 | 358 | 359 | ;; (add-to-list 'load-path "/home/ryantm/.config/emacs/lisp/") 360 | (use-package beancount 361 | :mode "\\.beancount\\'" 362 | :config (beancount-mode)) 363 | 364 | 365 | 366 | ;; Write backup and autosave files to their own directories 367 | (setq 368 | backup-by-copying t 369 | backup-directory-alist '(("." . "~/.saves")) 370 | delete-old-versions t 371 | kept-new-versions 6 372 | kept-old-versions 2 373 | version-control t 374 | vc-make-backup-files t) 375 | 376 | ;; (setq backup-directory-alist 377 | ;; `((".*" . ,(expand-file-name 378 | ;; (concat user-emacs-directory "backups"))))) 379 | 380 | ;; (setq auto-save-file-name-transforms 381 | ;; `((".*" ,(expand-file-name 382 | ;; (concat user-emacs-directory "autosaves")) t))) 383 | 384 | ;; (setq auto-save-list-file-prefix 385 | ;; (expand-file-name 386 | ;; (concat user-emacs-directory "autosaves"))) 387 | 388 | (setenv "PAGER" "") 389 | 390 | ;; Rebindings 391 | 392 | ;; Annoying Key (because it gets in the way of switching buffers) 393 | (global-unset-key (kbd "C-x C-b")) 394 | 395 | ;; mousewheel and C-+ C-- scrolling 396 | (defun scale-to (f) 397 | (set-face-attribute 'default nil :height 398 | (round f))) 399 | 400 | (defun scale-by (f) 401 | (scale-to (* f (face-attribute 'default :height)))) 402 | 403 | (defun scale-up () (interactive) 404 | (scale-by 1.1)) 405 | 406 | (defun scale-down () (interactive) 407 | (scale-by (/ 1 1.1))) 408 | 409 | (defun scale-reset () (interactive) 410 | (scale-to 100)) 411 | 412 | (global-set-key (kbd "C-=") #'scale-up) 413 | (global-set-key (kbd "C--") #'scale-down) 414 | (global-set-key (kbd "C-0") #'scale-reset) 415 | (global-set-key [C-mouse-4] #'scale-up) 416 | (global-set-key [C-mouse-5] #'scale-down) 417 | (global-set-key (kbd "M-m") 'mc/edit-lines) 418 | 419 | (let ((rebindings '(("C-x C-l" goto-line) 420 | ("C-x l" goto-line) 421 | ("C-x C-f" find-file-at-point) 422 | ("M-n" flymake-goto-next-error) 423 | ("M-p" flymake-goto-prev-error) 424 | ("C-x e" eval-last-sexp) 425 | ("" next-buffer) 426 | ("" previous-buffer) 427 | ("M-i" ido-goto-symbol) 428 | ("C-x C-r" rgrep)))) 429 | (dolist (element rebindings) 430 | (let ((keyboard-string (nth 0 element)) 431 | (function (nth 1 element))) 432 | (global-set-key (read-kbd-macro keyboard-string) function)))) 433 | 434 | (setq enable-local-variables :safe) 435 | 436 | (setq auth-sources '("~/.config/emacs/authinfo.gpg")) 437 | 438 | (dir-locals-set-class-variables 439 | 'huge-git-repository 440 | '((nil 441 | . ((magit-refresh-buffers . nil) 442 | (magit-revision-insert-related-refs . nil))) 443 | (magit-status-mode 444 | . ((eval . (magit-disable-section-inserter 'magit-insert-tags-header)) 445 | (eval . (magit-disable-section-inserter 'magit-insert-recent-commits)) 446 | (eval . (magit-disable-section-inserter 'magit-insert-unpushed-to-pushremote)) 447 | (eval . (magit-disable-section-inserter 'magit-insert-unpushed-to-upstream-or-recent)) 448 | (eval . (magit-disable-section-inserter 'magit-insert-unpulled-from-pushremote)) 449 | (eval . (magit-disable-section-inserter 'magit-insert-unpulled-from-pushremote)) 450 | (eval . (magit-disable-section-inserter 'magit-insert-unpulled-from-upstream)) 451 | )) 452 | )) 453 | 454 | (dir-locals-set-directory-class 455 | "/home/ryantm/p/nixpkgs/" 'huge-git-repository) 456 | 457 | ;;; Tabs 458 | (setq js-indent-level 2) 459 | (setq tab-width 2) 460 | 461 | ;;; Appearance 462 | (global-font-lock-mode t) 463 | (setq inhibit-splash-screen t) 464 | 465 | ;;; Post initialization 466 | 467 | (unless noninteractive 468 | (let ((elapsed (float-time (time-subtract (current-time) 469 | emacs-start-time)))) 470 | (message "Loading %s...done (%.3fs)" load-file-name elapsed)) 471 | 472 | (add-hook 'after-init-hook 473 | `(lambda () 474 | (let ((elapsed (float-time (time-subtract (current-time) 475 | emacs-start-time)))) 476 | (message "Loading %s...done (%.3fs) [after-init]" 477 | ,load-file-name elapsed))) 478 | t)) 479 | -------------------------------------------------------------------------------- /emacs/lisp/beancount.el: -------------------------------------------------------------------------------- 1 | ;;; beancount.el --- A major mode to edit Beancount input files. -*- lexical-binding: t -*- 2 | 3 | ;; Copyright (C) 2013 Martin Blais 4 | ;; Copyright (C) 2015 Free Software Foundation, Inc. 5 | ;; Copyright (C) 2019 Daniele Nicolodi 6 | 7 | ;; Version: 0 8 | ;; Author: Martin Blais 9 | ;; Author: Stefan Monnier 10 | ;; Author: Daniele Nicolodi 11 | 12 | ;; This file is not part of GNU Emacs. 13 | 14 | ;; This package is free software: you can redistribute it and/or modify 15 | ;; it under the terms of the GNU General Public License as published by 16 | ;; the Free Software Foundation, either version 3 of the License, or 17 | ;; (at your option) any later version. 18 | 19 | ;; This package is distributed in the hope that it will be useful, 20 | ;; but WITHOUT ANY WARRANTY; without even the implied warranty of 21 | ;; MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 22 | ;; GNU General Public License for more details. 23 | 24 | ;; You should have received a copy of the GNU General Public License 25 | ;; along with this package. If not, see . 26 | 27 | ;;; Commentary: 28 | 29 | ;; TODO: Add a flymake rule, using bean-check 30 | 31 | ;;; Code: 32 | 33 | (autoload 'ido-completing-read "ido") 34 | (require 'subr-x) 35 | (require 'outline) 36 | 37 | (defgroup beancount () 38 | "Editing mode for Beancount files." 39 | :group 'beancount) 40 | 41 | (defcustom beancount-transaction-indent 2 42 | "Transaction indent." 43 | :type 'integer) 44 | 45 | (defcustom beancount-number-alignment-column 52 46 | "Column to which align numbers in postinng definitions. Set to 47 | 0 to automatically determine the minimum column that will allow 48 | to align all amounts." 49 | :type 'integer) 50 | 51 | (defcustom beancount-highlight-transaction-at-point nil 52 | "If t highlight transaction under point." 53 | :type 'boolean) 54 | 55 | (defcustom beancount-use-ido t 56 | "If non-nil, use ido-style completion rather than the standard." 57 | :type 'boolean) 58 | 59 | (defcustom beancount-electric-currency nil 60 | "If non-nil, make `newline' try to add missing currency to 61 | complete the posting at point. The correct currency is determined 62 | from the open directive for the relevant account." 63 | :type 'boolean) 64 | 65 | (defgroup beancount-faces nil "Beancount mode highlighting" :group 'beancount) 66 | 67 | (defface beancount-directive 68 | `((t :inherit font-lock-keyword-face)) 69 | "Face for Beancount directives.") 70 | 71 | (defface beancount-tag 72 | `((t :inherit font-lock-type-face)) 73 | "Face for Beancount tags.") 74 | 75 | (defface beancount-link 76 | `((t :inherit font-lock-type-face)) 77 | "Face for Beancount links.") 78 | 79 | (defface beancount-date 80 | `((t :inherit font-lock-constant-face)) 81 | "Face for Beancount dates.") 82 | 83 | (defface beancount-account 84 | `((t :inherit font-lock-builtin-face)) 85 | "Face for Beancount account names.") 86 | 87 | (defface beancount-amount 88 | `((t :inherit font-lock-default-face)) 89 | "Face for Beancount amounts.") 90 | 91 | (defface beancount-narrative 92 | `((t :inherit font-lock-builtin-face)) 93 | "Face for Beancount transactions narrative.") 94 | 95 | (defface beancount-narrative-cleared 96 | `((t :inherit font-lock-string-face)) 97 | "Face for Beancount cleared transactions narrative.") 98 | 99 | (defface beancount-narrative-pending 100 | `((t :inherit font-lock-keyword-face)) 101 | "Face for Beancount pending transactions narrative.") 102 | 103 | (defface beancount-metadata 104 | `((t :inherit font-lock-type-face)) 105 | "Face for Beancount metadata.") 106 | 107 | (defface beancount-highlight 108 | `((t :inherit highlight)) 109 | "Face to highlight Beancount transaction at point.") 110 | 111 | (defconst beancount-account-directive-names 112 | '("balance" 113 | "close" 114 | "document" 115 | "note" 116 | "open" 117 | "pad") 118 | "Directive bames that can appear after a date and are followd by an account.") 119 | 120 | (defconst beancount-no-account-directive-names 121 | '("commodity" 122 | "event" 123 | "price" 124 | "query" 125 | "txn") 126 | "Directive names that can appear after a date and are _not_ followed by an account.") 127 | 128 | (defconst beancount-timestamped-directive-names 129 | (append beancount-account-directive-names 130 | beancount-no-account-directive-names) 131 | "Directive names that can appear after a date.") 132 | 133 | (defconst beancount-directive-names 134 | '("include" 135 | "option" 136 | "plugin" 137 | "poptag" 138 | "pushtag") 139 | "Directive names that can appear at the beginning of a line.") 140 | 141 | (defconst beancount-account-categories 142 | '("Assets" "Liabilities" "Equity" "Income" "Expenses")) 143 | 144 | (defconst beancount-tag-chars "[:alnum:]-_/.") 145 | 146 | (defconst beancount-account-chars "[:alnum:]-_:") 147 | 148 | (defconst beancount-option-names 149 | ;; This list is kept in sync with the options defined in 150 | ;; beancount/parser/options.py. 151 | '("account_current_conversions" 152 | "account_current_earnings" 153 | "account_previous_balances" 154 | "account_previous_conversions" 155 | "account_previous_earnings" 156 | "account_rounding" 157 | "allow_deprecated_none_for_tags_and_links" 158 | "allow_pipe_separator" 159 | "booking_method" 160 | "conversion_currency" 161 | "documents" 162 | "infer_tolerance_from_cost" 163 | "inferred_tolerance_default" 164 | "inferred_tolerance_multiplier" 165 | "insert_pythonpath" 166 | "long_string_maxlines" 167 | "name_assets" 168 | "name_equity" 169 | "name_expenses" 170 | "name_income" 171 | "name_liabilities" 172 | "operating_currency" 173 | "plugin_processing_mode" 174 | "render_commas" 175 | "title")) 176 | 177 | (defconst beancount-date-regexp "[0-9]\\{4\\}[-/][0-9]\\{2\\}[-/][0-9]\\{2\\}" 178 | "A regular expression to match dates.") 179 | 180 | (defconst beancount-account-regexp 181 | (concat (regexp-opt beancount-account-categories) 182 | "\\(?::[[:upper:]][[:alnum:]-_]+\\)+") 183 | "A regular expression to match account names.") 184 | 185 | (defconst beancount-number-regexp "[-+]?[0-9]+\\(?:,[0-9]\\{3\\}\\)*\\(?:\\.[0-9]*\\)?" 186 | "A regular expression to match decimal numbers.") 187 | 188 | (defconst beancount-currency-regexp "[A-Z][A-Z-_'.]*" 189 | "A regular expression to match currencies.") 190 | 191 | (defconst beancount-flag-regexp 192 | ;; Single char that is neither a space nor a lower-case letter. 193 | "[^ a-z]") 194 | 195 | (defconst beancount-transaction-regexp 196 | (concat "^\\(" beancount-date-regexp "\\) +" 197 | "\\(?:txn +\\)?" 198 | "\\(" beancount-flag-regexp "\\) +" 199 | "\\(\".*\"\\)")) 200 | 201 | (defconst beancount-posting-regexp 202 | (concat "^\\s-+" 203 | "\\(" beancount-account-regexp "\\)" 204 | "\\(?:\\s-+\\(\\(" beancount-number-regexp "\\)" 205 | "\\s-+\\(" beancount-currency-regexp "\\)\\)\\)?")) 206 | 207 | (defconst beancount-directive-regexp 208 | (concat "^\\(" (regexp-opt beancount-directive-names) "\\) +")) 209 | 210 | (defconst beancount-timestamped-directive-regexp 211 | (concat "^\\(" beancount-date-regexp "\\) +" 212 | "\\(" (regexp-opt beancount-timestamped-directive-names) "\\) +")) 213 | 214 | (defconst beancount-metadata-regexp 215 | "^\\s-+\\([a-z][A-Za-z0-9_-]+:\\)\\s-+\\(.+\\)") 216 | 217 | ;; This is a grouping regular expression because the subexpression is 218 | ;; used in determining the outline level in `beancount-outline-level'. 219 | (defvar beancount-outline-regexp "\\(;;;+\\|\\*+\\)") 220 | 221 | (defun beancount-outline-level () 222 | (let ((len (- (match-end 1) (match-beginning 1)))) 223 | (if (equal (substring (match-string 1) 0 1) ";") 224 | (- len 2) 225 | len))) 226 | 227 | (defun beancount-face-by-state (state) 228 | (cond ((string-equal state "*") 'beancount-narrative-cleared) 229 | ((string-equal state "!") 'beancount-narrative-pending) 230 | (t 'beancount-narrative))) 231 | 232 | (defun beancount-outline-face () 233 | (if outline-minor-mode 234 | (cl-case (funcall outline-level) 235 | (1 'org-level-1) 236 | (2 'org-level-2) 237 | (3 'org-level-3) 238 | (4 'org-level-4) 239 | (5 'org-level-5) 240 | (6 'org-level-6) 241 | (otherwise nil)) 242 | nil)) 243 | 244 | (defvar beancount-font-lock-keywords 245 | `((,beancount-transaction-regexp (1 'beancount-date) 246 | (2 (beancount-face-by-state (match-string 2)) t) 247 | (3 (beancount-face-by-state (match-string 2)) t)) 248 | (,beancount-posting-regexp (1 'beancount-account) 249 | (2 'beancount-amount nil :lax)) 250 | (,beancount-metadata-regexp (1 'beancount-metadata) 251 | (2 'beancount-metadata t)) 252 | (,beancount-directive-regexp (1 'beancount-directive)) 253 | (,beancount-timestamped-directive-regexp (1 'beancount-date) 254 | (2 'beancount-directive)) 255 | ;; Fontify section headers when composed with outline-minor-mode. 256 | (,(concat "^\\(" beancount-outline-regexp "\\).*") . (0 (beancount-outline-face))) 257 | ;; Tags and links. 258 | (,(concat "\\#[" beancount-tag-chars "]*") . 'beancount-tag) 259 | (,(concat "\\^[" beancount-tag-chars "]*") . 'beancount-link) 260 | ;; Number followed by currency not covered by previous rules. 261 | (,(concat beancount-number-regexp "\\s-+" beancount-currency-regexp) . 'beancount-amount) 262 | ;; Accounts not covered by previous rules. 263 | (,beancount-account-regexp . 'beancount-account) 264 | )) 265 | 266 | (defun beancount-tab-dwim (&optional arg) 267 | (interactive "P") 268 | (if (and outline-minor-mode 269 | (or arg (outline-on-heading-p))) 270 | (beancount-outline-cycle arg) 271 | (indent-for-tab-command))) 272 | 273 | (defvar beancount-mode-map-prefix [(control c)] 274 | "The prefix key used to bind Beancount commands in Emacs") 275 | 276 | (defvar beancount-mode-map 277 | (let ((map (make-sparse-keymap)) 278 | (p beancount-mode-map-prefix)) 279 | (define-key map (kbd "TAB") #'beancount-tab-dwim) 280 | (define-key map (kbd "M-RET") #'beancount-insert-date) 281 | (define-key map (vconcat p [(\')]) #'beancount-insert-account) 282 | (define-key map (vconcat p [(control g)]) #'beancount-transaction-clear) 283 | (define-key map (vconcat p [(l)]) #'beancount-check) 284 | (define-key map (vconcat p [(q)]) #'beancount-query) 285 | (define-key map (vconcat p [(x)]) #'beancount-context) 286 | (define-key map (vconcat p [(k)]) #'beancount-linked) 287 | (define-key map (vconcat p [(p)]) #'beancount-insert-prices) 288 | (define-key map (vconcat p [(\;)]) #'beancount-align-to-previous-number) 289 | (define-key map (vconcat p [(\:)]) #'beancount-align-numbers) 290 | map)) 291 | 292 | (defvar beancount-mode-syntax-table 293 | (let ((st (make-syntax-table))) 294 | (modify-syntax-entry ?\" "\"\"" st) 295 | (modify-syntax-entry ?\; "<" st) 296 | (modify-syntax-entry ?\n ">" st) 297 | st)) 298 | 299 | ;;;###autoload 300 | (define-derived-mode beancount-mode fundamental-mode "Beancount" 301 | "A mode for Beancount files. 302 | 303 | \\{beancount-mode-map}" 304 | :group 'beancount 305 | :syntax-table beancount-mode-syntax-table 306 | 307 | (setq-local paragraph-ignore-fill-prefix t) 308 | (setq-local fill-paragraph-function #'beancount-indent-transaction) 309 | 310 | (setq-local comment-start ";") 311 | (setq-local comment-start-skip ";+\\s-*") 312 | (setq-local comment-add 1) 313 | 314 | (setq-local indent-line-function #'beancount-indent-line) 315 | (setq-local indent-region-function #'beancount-indent-region) 316 | (setq-local indent-tabs-mode nil) 317 | 318 | (setq-local tab-always-indent 'complete) 319 | (setq-local completion-ignore-case t) 320 | 321 | (add-hook 'completion-at-point-functions #'beancount-completion-at-point nil t) 322 | (add-hook 'post-command-hook #'beancount-highlight-transaction-at-point nil t) 323 | (add-hook 'post-self-insert-hook #'beancount--electric-currency nil t) 324 | 325 | (setq-local font-lock-defaults '(beancount-font-lock-keywords)) 326 | (setq-local font-lock-syntax-table t) 327 | 328 | (setq-local outline-regexp beancount-outline-regexp) 329 | (setq-local outline-level #'beancount-outline-level) 330 | 331 | (setq imenu-generic-expression 332 | (list (list nil (concat "^" beancount-outline-regexp "\\s-+\\(.*\\)$") 2)))) 333 | 334 | (defun beancount-collect-pushed-tags (begin end) 335 | "Return list of all pushed (and not popped) tags in the region." 336 | (goto-char begin) 337 | (let ((tags (make-hash-table :test 'equal))) 338 | (while (re-search-forward 339 | (concat "^\\(push\\|pop\\)tag\\s-+\\(#[" beancount-tag-chars "]+\\)") end t) 340 | (if (string-equal (match-string 1) "push") 341 | (puthash (match-string-no-properties 2) nil tags) 342 | (remhash (match-string-no-properties 2) tags))) 343 | (hash-table-keys tags))) 344 | 345 | (defun beancount-goto-transaction-begin () 346 | "Move the cursor to the first line of the transaction definition." 347 | (interactive) 348 | (beginning-of-line) 349 | ;; everything that is indented with at lest one space or tab is part 350 | ;; of the transaction definition 351 | (while (looking-at-p "[ \t]+") 352 | (forward-line -1)) 353 | (point)) 354 | 355 | (defun beancount-goto-transaction-end () 356 | "Move the cursor to the line after the transaction definition." 357 | (interactive) 358 | (beginning-of-line) 359 | (if (looking-at-p beancount-transaction-regexp) 360 | (forward-line)) 361 | ;; everything that is indented with at least one space or tab as part 362 | ;; of the transaction definition 363 | (while (looking-at-p "[ \t]+") 364 | (forward-line)) 365 | (point)) 366 | 367 | (defun beancount-goto-next-transaction (&optional arg) 368 | "Move to the next transaction. 369 | With an argument move to the next non cleared transaction." 370 | (interactive "P") 371 | (beancount-goto-transaction-end) 372 | (let ((done nil)) 373 | (while (and (not done) 374 | (re-search-forward beancount-transaction-regexp nil t)) 375 | (if (and arg (string-equal (match-string 2) "*")) 376 | (goto-char (match-end 0)) 377 | (goto-char (match-beginning 0)) 378 | (setq done t))) 379 | (if (not done) (goto-char (point-max))))) 380 | 381 | (defun beancount-find-transaction-extents (p) 382 | (save-excursion 383 | (goto-char p) 384 | (list (beancount-goto-transaction-begin) 385 | (beancount-goto-transaction-end)))) 386 | 387 | (defun beancount-inside-transaction-p () 388 | (let ((bounds (beancount-find-transaction-extents (point)))) 389 | (> (- (cadr bounds) (car bounds)) 0))) 390 | 391 | (defun beancount-looking-at (regexp n pos) 392 | (and (looking-at regexp) 393 | (>= pos (match-beginning n)) 394 | (<= pos (match-end n)))) 395 | 396 | (defvar beancount-accounts nil 397 | "A list of the accounts available in this buffer.") 398 | (make-variable-buffer-local 'beancount-accounts) 399 | 400 | (defun beancount-completion-at-point () 401 | "Return the completion data relevant for the text at point." 402 | (save-excursion 403 | (save-match-data 404 | (let ((pos (point))) 405 | (beginning-of-line) 406 | (cond 407 | ;; non timestamped directive 408 | ((beancount-looking-at "[a-z]*" 0 pos) 409 | (list (match-beginning 0) (match-end 0) 410 | (mapcar (lambda (s) (concat s " ")) beancount-directive-names))) 411 | 412 | ;; poptag 413 | ((beancount-looking-at 414 | (concat "poptag\\s-+\\(\\(?:#[" beancount-tag-chars "]*\\)\\)") 1 pos) 415 | (list (match-beginning 1) (match-end 1) 416 | (beancount-collect-pushed-tags (point-min) (point)))) 417 | 418 | ;; option 419 | ((beancount-looking-at 420 | (concat "^option\\s-+\\(\"[a-z_]*\\)") 1 pos) 421 | (list (match-beginning 1) (match-end 1) 422 | (mapcar (lambda (s) (concat "\"" s "\" ")) beancount-option-names))) 423 | 424 | ;; timestamped directive 425 | ((beancount-looking-at 426 | (concat beancount-date-regexp "\\s-+\\([[:alpha:]]*\\)") 1 pos) 427 | (list (match-beginning 1) (match-end 1) 428 | (mapcar (lambda (s) (concat s " ")) beancount-timestamped-directive-names))) 429 | 430 | ;; timestamped directives followed by account 431 | ((beancount-looking-at 432 | (concat "^" beancount-date-regexp 433 | "\\s-+" (regexp-opt beancount-account-directive-names) 434 | "\\s-+\\([" beancount-account-chars "]*\\)") 1 pos) 435 | (setq beancount-accounts nil) 436 | (list (match-beginning 1) (match-end 1) #'beancount-account-completion-table)) 437 | 438 | ;; posting 439 | ((and (beancount-looking-at 440 | (concat "[ \t]+\\([" beancount-account-chars "]*\\)") 1 pos) 441 | ;; Do not force the account name to start with a 442 | ;; capital, so that it is possible to use substring 443 | ;; completion and we can rely on completion to fix 444 | ;; capitalization thanks to completion-ignore-case. 445 | (beancount-inside-transaction-p)) 446 | (setq beancount-accounts nil) 447 | (list (match-beginning 1) (match-end 1) #'beancount-account-completion-table)) 448 | 449 | ;; tags 450 | ((beancount-looking-at 451 | (concat "[ \t]+#\\([" beancount-tag-chars "]*\\)") 1 pos) 452 | (let* ((candidates nil) 453 | (regexp (concat "\\#\\([" beancount-tag-chars "]+\\)")) 454 | (completion-table 455 | (lambda (string pred action) 456 | (if (null candidates) 457 | (setq candidates 458 | (sort (beancount-collect regexp 1) #'string<))) 459 | (complete-with-action action candidates string pred)))) 460 | (list (match-beginning 1) (match-end 1) completion-table))) 461 | 462 | ;; links 463 | ((beancount-looking-at 464 | (concat "[ \t]+\\^\\([" beancount-tag-chars "]*\\)") 1 pos) 465 | (let* ((candidates nil) 466 | (regexp (concat "\\^\\([" beancount-tag-chars "]+\\)")) 467 | (completion-table 468 | (lambda (string pred action) 469 | (if (null candidates) 470 | (setq candidates 471 | (sort (beancount-collect regexp 1) #'string<))) 472 | (complete-with-action action candidates string pred)))) 473 | (list (match-beginning 1) (match-end 1) completion-table)))))))) 474 | 475 | (defun beancount-collect (regexp n) 476 | "Return an unique list of REGEXP group N in the current buffer." 477 | (let ((pos (point))) 478 | (save-excursion 479 | (save-match-data 480 | (let ((hash (make-hash-table :test 'equal))) 481 | (goto-char (point-min)) 482 | (while (re-search-forward regexp nil t) 483 | ;; Ignore matches around `pos' (the point position when 484 | ;; entering this funcyion) since that's presumably what 485 | ;; we're currently trying to complete. 486 | (unless (<= (match-beginning 0) pos (match-end 0)) 487 | (puthash (match-string-no-properties n) nil hash))) 488 | (hash-table-keys hash)))))) 489 | 490 | (defun beancount-account-completion-table (string pred action) 491 | (if (eq action 'metadata) '(metadata (category . beancount-account)) 492 | (if (null beancount-accounts) 493 | (setq beancount-accounts 494 | (sort (beancount-collect beancount-account-regexp 0) #'string<))) 495 | (complete-with-action action beancount-accounts string pred))) 496 | 497 | ;; Default to substring completion for beancount accounts. 498 | (defconst beancount--completion-overrides 499 | '(beancount-account (styles basic partial-completion substring))) 500 | (add-to-list 'completion-category-defaults beancount--completion-overrides) 501 | 502 | (defun beancount-number-alignment-column () 503 | "Return the column to which postings amounts should be aligned to. 504 | Returns `beancount-number-alignment-column' unless it is 0. In 505 | that case, scan the buffer to determine the minimum column that 506 | will allow to align all numbers." 507 | (if (> beancount-number-alignment-column 0) 508 | beancount-number-alignment-column 509 | (save-excursion 510 | (save-match-data 511 | (let ((account-width 0) 512 | (number-width 0)) 513 | (goto-char (point-min)) 514 | (while (re-search-forward beancount-posting-regexp nil t) 515 | (if (match-string 2) 516 | (let ((accw (- (match-end 1) (line-beginning-position))) 517 | (numw (- (match-end 3) (match-beginning 3)))) 518 | (setq account-width (max account-width accw) 519 | number-width (max number-width numw))))) 520 | (+ account-width 2 number-width)))))) 521 | 522 | (defun beancount-compute-indentation () 523 | "Return the column to which the current line should be indented." 524 | (save-excursion 525 | (beginning-of-line) 526 | (cond 527 | ;; Only timestamped directives start with a digit. 528 | ((looking-at-p "[0-9]") 0) 529 | ;; Otherwise look at the previous line. 530 | ((and (= (forward-line -1) 0) 531 | (or (looking-at-p "[ \t].+") 532 | (looking-at-p beancount-timestamped-directive-regexp) 533 | (looking-at-p beancount-transaction-regexp))) 534 | beancount-transaction-indent) 535 | ;; Default. 536 | (t 0)))) 537 | 538 | (defun beancount-align-number (target-column) 539 | (save-excursion 540 | (beginning-of-line) 541 | ;; Check if the current line is a posting with a number to align. 542 | (when (and (looking-at beancount-posting-regexp) 543 | (match-string 2)) 544 | (let* ((account-end-column (- (match-end 1) (line-beginning-position))) 545 | (number-width (- (match-end 3) (match-beginning 3))) 546 | (account-end (match-end 1)) 547 | (number-beginning (match-beginning 3)) 548 | (spaces (max 2 (- target-column account-end-column number-width)))) 549 | (unless (eq spaces (- number-beginning account-end)) 550 | (goto-char account-end) 551 | (delete-region account-end number-beginning) 552 | (insert (make-string spaces ? ))))))) 553 | 554 | (defun beancount-indent-line () 555 | (let ((indent (beancount-compute-indentation)) 556 | (savep (> (current-column) (current-indentation)))) 557 | (unless (eq indent (current-indentation)) 558 | (if savep (save-excursion (indent-line-to indent)) 559 | (indent-line-to indent))) 560 | (unless (eq this-command 'beancount-tab-dwim) 561 | (beancount-align-number (beancount-number-alignment-column))))) 562 | 563 | (defun beancount-indent-region (start end) 564 | "Indent a region automagically. START and END specify the region to indent." 565 | (let ((deactivate-mark nil) 566 | (beancount-number-alignment-column (beancount-number-alignment-column))) 567 | (save-excursion 568 | (setq end (copy-marker end)) 569 | (goto-char start) 570 | (or (bolp) (forward-line 1)) 571 | (while (< (point) end) 572 | (unless (looking-at-p "\\s-*$") 573 | (beancount-indent-line)) 574 | (forward-line 1)) 575 | (move-marker end nil)))) 576 | 577 | (defun beancount-indent-transaction (&optional _justify _region) 578 | "Indent Beancount transaction at point." 579 | (interactive) 580 | (save-excursion 581 | (let ((bounds (beancount-find-transaction-extents (point)))) 582 | (beancount-indent-region (car bounds) (cadr bounds))))) 583 | 584 | (defun beancount-transaction-clear (&optional arg) 585 | "Clear transaction at point. With a prefix argument set the 586 | transaction as pending." 587 | (interactive "P") 588 | (save-excursion 589 | (save-match-data 590 | (let ((flag (if arg "!" "*"))) 591 | (beancount-goto-transaction-begin) 592 | (if (looking-at beancount-transaction-regexp) 593 | (replace-match flag t t nil 2)))))) 594 | 595 | (defun beancount-insert-account (account-name) 596 | "Insert one of the valid account names in this file. 597 | Uses ido niceness according to `beancount-use-ido'." 598 | (interactive 599 | (list 600 | (if beancount-use-ido 601 | ;; `ido-completing-read' does not understand functional 602 | ;; completion tables thus directly build a list of the 603 | ;; accounts in the buffer 604 | (let ((beancount-accounts 605 | (sort (beancount-collect beancount-account-regexp 0) #'string<))) 606 | (ido-completing-read "Account: " beancount-accounts 607 | nil nil (thing-at-point 'word))) 608 | (completing-read "Account: " #'beancount-account-completion-table 609 | nil t (thing-at-point 'word))))) 610 | (let ((bounds (bounds-of-thing-at-point 'word))) 611 | (when bounds 612 | (delete-region (car bounds) (cdr bounds)))) 613 | (insert account-name)) 614 | 615 | (defmacro beancount-for-line-in-region (begin end &rest exprs) 616 | "Iterate over each line in region until an empty line is encountered." 617 | `(save-excursion 618 | (let ((end-marker (copy-marker ,end))) 619 | (goto-char ,begin) 620 | (beginning-of-line) 621 | (while (and (not (eobp)) (< (point) end-marker)) 622 | (beginning-of-line) 623 | (progn ,@exprs) 624 | (forward-line 1) 625 | )))) 626 | 627 | (defun beancount-align-numbers (begin end &optional requested-currency-column) 628 | "Align all numbers in the given region. CURRENCY-COLUMN is the character 629 | at which to align the beginning of the amount's currency. If not specified, use 630 | the smallest columns that will align all the numbers. With a prefix argument, 631 | align with the fill-column." 632 | (interactive "r") 633 | 634 | ;; With a prefix argument, align with the fill-column. 635 | (when current-prefix-arg 636 | (setq requested-currency-column fill-column)) 637 | 638 | ;; Loop once in the region to find the length of the longest string before the 639 | ;; number. 640 | (let (prefix-widths 641 | number-widths 642 | (number-padding " ")) 643 | (beancount-for-line-in-region 644 | begin end 645 | (let ((line (thing-at-point 'line))) 646 | (when (string-match (concat "\\(.*?\\)" 647 | "[ \t]+" 648 | "\\(" beancount-number-regexp "\\)" 649 | "[ \t]+" 650 | beancount-currency-regexp) 651 | line) 652 | (push (length (match-string 1 line)) prefix-widths) 653 | (push (length (match-string 2 line)) number-widths) 654 | ))) 655 | 656 | (when prefix-widths 657 | ;; Loop again to make the adjustments to the numbers. 658 | (let* ((number-width (apply 'max number-widths)) 659 | (number-format (format "%%%ss" number-width)) 660 | ;; Compute rightmost column of prefix. 661 | (max-prefix-width (apply 'max prefix-widths)) 662 | (max-prefix-width 663 | (if requested-currency-column 664 | (max (- requested-currency-column (length number-padding) number-width 1) 665 | max-prefix-width) 666 | max-prefix-width)) 667 | (prefix-format (format "%%-%ss" max-prefix-width)) 668 | ) 669 | 670 | (beancount-for-line-in-region 671 | begin end 672 | (let ((line (thing-at-point 'line))) 673 | (when (string-match (concat "^\\([^\"]*?\\)" 674 | "[ \t]+" 675 | "\\(" beancount-number-regexp "\\)" 676 | "[ \t]+" 677 | "\\(.*\\)$") 678 | line) 679 | (delete-region (line-beginning-position) (line-end-position)) 680 | (let* ((prefix (match-string 1 line)) 681 | (number (match-string 2 line)) 682 | (rest (match-string 3 line)) ) 683 | (insert (format prefix-format prefix)) 684 | (insert number-padding) 685 | (insert (format number-format number)) 686 | (insert " ") 687 | (insert rest))))))))) 688 | 689 | (defun beancount-align-to-previous-number () 690 | "Align postings under the point's paragraph. 691 | This function looks for a posting in the previous transaction to 692 | determine the column at which to align the transaction, or otherwise 693 | the fill column, and align all the postings of this transaction to 694 | this column." 695 | (interactive) 696 | (let* ((begin (save-excursion 697 | (beancount-beginning-of-directive) 698 | (point))) 699 | (end (save-excursion 700 | (goto-char begin) 701 | (forward-paragraph 1) 702 | (point))) 703 | (currency-column (or (beancount-find-previous-alignment-column) 704 | fill-column))) 705 | (beancount-align-numbers begin end currency-column))) 706 | 707 | 708 | (defun beancount-beginning-of-directive () 709 | "Move point to the beginning of the enclosed or preceding directive." 710 | (beginning-of-line) 711 | (while (and (> (point) (point-min)) 712 | (not (looking-at 713 | "[0-9][0-9][0-9][0-9][\-/][0-9][0-9][\-/][0-9][0-9]"))) 714 | (forward-line -1))) 715 | 716 | 717 | (defun beancount-find-previous-alignment-column () 718 | "Find the preceding column to align amounts with. 719 | This is used to align transactions at the same column as that of 720 | the previous transaction in the file. This function merely finds 721 | what that column is and returns it (an integer)." 722 | ;; Go hunting for the last column with a suitable posting. 723 | (let (column) 724 | (save-excursion 725 | ;; Go to the beginning of the enclosing directive. 726 | (beancount-beginning-of-directive) 727 | (forward-line -1) 728 | 729 | ;; Find the last posting with an amount and a currency on it. 730 | (let ((posting-regexp (concat 731 | "\\s-+" 732 | beancount-account-regexp "\\s-+" 733 | beancount-number-regexp "\\s-+" 734 | "\\(" beancount-currency-regexp "\\)")) 735 | (balance-regexp (concat 736 | beancount-date-regexp "\\s-+" 737 | "balance" "\\s-+" 738 | beancount-account-regexp "\\s-+" 739 | beancount-number-regexp "\\s-+" 740 | "\\(" beancount-currency-regexp "\\)"))) 741 | (while (and (> (point) (point-min)) 742 | (not (or (looking-at posting-regexp) 743 | (looking-at balance-regexp)))) 744 | (forward-line -1)) 745 | (when (or (looking-at posting-regexp) 746 | (looking-at balance-regexp)) 747 | (setq column (- (match-beginning 1) (point)))) 748 | )) 749 | column)) 750 | 751 | (defun beancount--account-currency (account) 752 | ;; Build a regexp that matches an open directive that specifies a 753 | ;; single account currencydaaee. The currency is match group 1. 754 | (let ((re (concat "^" beancount-date-regexp " +open" 755 | "\\s-+" (regexp-quote account) 756 | "\\s-+\\(" beancount-currency-regexp "\\)\\s-+"))) 757 | (save-excursion 758 | (goto-char (point-min)) 759 | (when (re-search-forward re nil t) 760 | ;; The account has declared a single currency, so we can fill it in. 761 | (match-string-no-properties 1))))) 762 | 763 | (defun beancount--electric-currency () 764 | (when (and beancount-electric-currency (eq last-command-event ?\n)) 765 | (save-excursion 766 | (forward-line -1) 767 | (when (and (beancount-inside-transaction-p) 768 | (looking-at (concat "\\s-+\\(" beancount-account-regexp "\\)" 769 | "\\s-+\\(" beancount-number-regexp "\\)\\s-*$"))) 770 | ;; Last line is a posting without currency. 771 | (let* ((account (match-string 1)) 772 | (pos (match-end 0)) 773 | (currency (beancount--account-currency account))) 774 | (when currency 775 | (save-excursion 776 | (goto-char pos) 777 | (insert " " currency)))))))) 778 | 779 | (defun beancount-insert-date () 780 | "Start a new timestamped directive." 781 | (interactive) 782 | (unless (bolp) (newline)) 783 | (insert (format-time-string "%Y-%m-%d") " ")) 784 | 785 | (defvar beancount-install-dir nil 786 | "Directory in which Beancount's source is located. 787 | Only useful if you have not installed Beancount properly in your PATH.") 788 | 789 | (defvar beancount-check-program "bean-check" 790 | "Program to run to run just the parser and validator on an 791 | input file.") 792 | 793 | (defvar compilation-read-command) 794 | 795 | (defun beancount--run (prog &rest args) 796 | (let ((process-environment 797 | (if beancount-install-dir 798 | `(,(concat "PYTHONPATH=" beancount-install-dir) 799 | ,(concat "PATH=" 800 | (expand-file-name "bin" beancount-install-dir) 801 | ":" 802 | (getenv "PATH")) 803 | ,@process-environment) 804 | process-environment)) 805 | (compile-command (mapconcat (lambda (arg) 806 | (if (stringp arg) 807 | (shell-quote-argument arg) "")) 808 | (cons prog args) 809 | " "))) 810 | (call-interactively 'compile))) 811 | 812 | (defun beancount-check () 813 | "Run `beancount-check-program'." 814 | (interactive) 815 | (let ((compilation-read-command nil)) 816 | (beancount--run beancount-check-program 817 | (file-relative-name buffer-file-name)))) 818 | 819 | (defvar beancount-query-program "bean-query" 820 | "Program to run to run just the parser and validator on an 821 | input file.") 822 | 823 | (defun beancount-query () 824 | "Run bean-query." 825 | (interactive) 826 | ;; Don't let-bind compilation-read-command this time, since the default 827 | ;; command is incomplete. 828 | (beancount--run beancount-query-program 829 | (file-relative-name buffer-file-name) t)) 830 | 831 | (defvar beancount-doctor-program "bean-doctor" 832 | "Program to run the doctor commands.") 833 | 834 | (defun beancount-context () 835 | "Get the \"context\" from `beancount-doctor-program'." 836 | (interactive) 837 | (let ((compilation-read-command nil)) 838 | (beancount--run beancount-doctor-program "context" 839 | (file-relative-name buffer-file-name) 840 | (number-to-string (line-number-at-pos))))) 841 | 842 | 843 | (defun beancount-linked () 844 | "Get the \"linked\" info from `beancount-doctor-program'." 845 | (interactive) 846 | (let ((compilation-read-command nil)) 847 | (beancount--run beancount-doctor-program "linked" 848 | (file-relative-name buffer-file-name) 849 | (number-to-string (line-number-at-pos))))) 850 | 851 | (defvar beancount-price-program "bean-price" 852 | "Program to run the price fetching commands.") 853 | 854 | (defun beancount-insert-prices () 855 | "Run bean-price on the current file and insert the output inline." 856 | (interactive) 857 | (call-process beancount-price-program nil t nil 858 | (file-relative-name buffer-file-name))) 859 | 860 | ;;; Transaction highligh 861 | 862 | (defvar beancount-highlight-overlay (list)) 863 | (make-variable-buffer-local 'beancount-highlight-overlay) 864 | 865 | (defun beancount-highlight-overlay-make () 866 | (let ((overlay (make-overlay 1 1))) 867 | (overlay-put overlay 'face 'beancount-highlight) 868 | (overlay-put overlay 'priority '(nil . 99)) 869 | overlay)) 870 | 871 | (defun beancount-highlight-transaction-at-point () 872 | "Move the highlight overlay to the current transaction." 873 | (when beancount-highlight-transaction-at-point 874 | (unless beancount-highlight-overlay 875 | (setq beancount-highlight-overlay (beancount-highlight-overlay-make))) 876 | (let* ((bounds (beancount-find-transaction-extents (point))) 877 | (begin (car bounds)) 878 | (end (cadr bounds))) 879 | (if (> (- end begin) 0) 880 | (move-overlay beancount-highlight-overlay begin end) 881 | (move-overlay beancount-highlight-overlay 1 1))))) 882 | 883 | ;;; Outline minor mode support. 884 | 885 | (defun beancount-outline-cycle (&optional arg) 886 | "Implement visibility cycling a la `org-mode'. 887 | 888 | The behavior of this command is determined by the first matching 889 | condition among the following: 890 | 891 | 1. When point is at the beginning of the buffer, or when called 892 | with a `\\[universal-argument]' universal argument, rotate the entire buffer 893 | through 3 states: 894 | 895 | - OVERVIEW: Show only top-level headlines. 896 | - CONTENTS: Show all headlines of all levels, but no body text. 897 | - SHOW ALL: Show everything. 898 | 899 | 2. When point is at the beginning of a headline, rotate the 900 | subtree starting at this line through 3 different states: 901 | 902 | - FOLDED: Only the main headline is shown. 903 | - CHILDREN: The main headline and its direct children are shown. 904 | From this state, you can move to one of the children 905 | and zoom in further. 906 | 907 | - SUBTREE: Show the entire subtree, including body text." 908 | (interactive "P") 909 | (setq deactivate-mark t) 910 | (cond 911 | ;; Beginning of buffer or called with C-u: Global cycling 912 | ((or (equal arg '(4)) 913 | (and (bobp) 914 | ;; org-mode style behaviour - only cycle if not on a heading 915 | (not (outline-on-heading-p)))) 916 | (beancount-cycle-buffer)) 917 | 918 | ;; At a heading: rotate between three different views 919 | ((save-excursion (beginning-of-line 1) (looking-at outline-regexp)) 920 | (outline-back-to-heading) 921 | (let ((goal-column 0) eoh eol eos) 922 | ;; First, some boundaries 923 | (save-excursion 924 | (save-excursion (beancount-next-line) (setq eol (point))) 925 | (outline-end-of-heading) (setq eoh (point)) 926 | (outline-end-of-subtree) (setq eos (point))) 927 | ;; Find out what to do next and set `this-command' 928 | (cond 929 | ((= eos eoh) 930 | ;; Nothing is hidden behind this heading 931 | (beancount-message "EMPTY ENTRY")) 932 | ((>= eol eos) 933 | ;; Entire subtree is hidden in one line: open it 934 | (outline-show-entry) 935 | (outline-show-children) 936 | (beancount-message "CHILDREN") 937 | (setq 938 | this-command 'beancount-cycle-children)) 939 | ((eq last-command 'beancount-cycle-children) 940 | ;; We just showed the children, now show everything. 941 | (outline-show-subtree) 942 | (beancount-message "SUBTREE")) 943 | (t 944 | ;; Default action: hide the subtree. 945 | (outline-hide-subtree) 946 | (beancount-message "FOLDED"))))))) 947 | 948 | (defvar beancount-current-buffer-visibility-state nil 949 | "Current visibility state of buffer.") 950 | (make-variable-buffer-local 'beancount-current-buffer-visibility-state) 951 | 952 | (defvar beancount-current-buffer-visibility-state) 953 | 954 | (defun beancount-cycle-buffer (&optional arg) 955 | "Rotate the visibility state of the buffer through 3 states: 956 | - OVERVIEW: Show only top-level headlines. 957 | - CONTENTS: Show all headlines of all levels, but no body text. 958 | - SHOW ALL: Show everything. 959 | 960 | With a numeric prefix ARG, show all headlines up to that level." 961 | (interactive "P") 962 | (save-excursion 963 | (cond 964 | ((integerp arg) 965 | (outline-show-all) 966 | (outline-hide-sublevels arg)) 967 | ((eq last-command 'beancount-cycle-overview) 968 | ;; We just created the overview - now do table of contents 969 | ;; This can be slow in very large buffers, so indicate action 970 | ;; Visit all headings and show their offspring 971 | (goto-char (point-max)) 972 | (while (not (bobp)) 973 | (condition-case nil 974 | (progn 975 | (outline-previous-visible-heading 1) 976 | (outline-show-branches)) 977 | (error (goto-char (point-min))))) 978 | (beancount-message "CONTENTS") 979 | (setq this-command 'beancount-cycle-toc 980 | beancount-current-buffer-visibility-state 'contents)) 981 | ((eq last-command 'beancount-cycle-toc) 982 | ;; We just showed the table of contents - now show everything 983 | (outline-show-all) 984 | (beancount-message "SHOW ALL") 985 | (setq this-command 'beancount-cycle-showall 986 | beancount-current-buffer-visibility-state 'all)) 987 | (t 988 | ;; Default action: go to overview 989 | (let ((toplevel 990 | (cond 991 | (current-prefix-arg 992 | (prefix-numeric-value current-prefix-arg)) 993 | ((save-excursion 994 | (beginning-of-line) 995 | (looking-at outline-regexp)) 996 | (max 1 (funcall outline-level))) 997 | (t 1)))) 998 | (outline-hide-sublevels toplevel)) 999 | (beancount-message "OVERVIEW") 1000 | (setq this-command 'beancount-cycle-overview 1001 | beancount-current-buffer-visibility-state 'overview))))) 1002 | 1003 | (defun beancount-message (msg) 1004 | "Display MSG, but avoid logging it in the *Messages* buffer." 1005 | (let ((message-log-max nil)) 1006 | (message msg))) 1007 | 1008 | (defun beancount-next-line () 1009 | "Forward line, but mover over invisible line ends. 1010 | Essentially a much simplified version of `next-line'." 1011 | (interactive) 1012 | (beginning-of-line 2) 1013 | (while (and (not (eobp)) 1014 | (get-char-property (1- (point)) 'invisible)) 1015 | (beginning-of-line 2))) 1016 | 1017 | (provide 'beancount) 1018 | ;;; beancount.el ends here 1019 | --------------------------------------------------------------------------------