Marc Pohling 4 years ago
parent
commit
ff8154363d
5 changed files with 1573 additions and 57 deletions
  1. 62
      config.org
  2. 11
      create-init.sh
  3. 386
      init.el
  4. 551
      init.org
  5. 620
      user-local/elisp/beancount.el

62
config.org

@ -1,5 +1,7 @@
#+TITLE: Emacs Configuration #+TITLE: Emacs Configuration
#+AUTHOR: Marc Pohling #+AUTHOR: Marc Pohling
#+BABEL: :cache yes
#+PROPERTY: :header-args :tangle yes
* Personal Information * Personal Information
@ -8,7 +10,7 @@
user-mail-address "marc.pohling@googlemail.com") user-mail-address "marc.pohling@googlemail.com")
#+END_SRC #+END_SRC
I need a function to know what computer emacs is running on. The display width of 1152 pixel is an oddity of hyper-v and for my usecase specific enough to tell the machine.
I need a function to know what computer emacs is running on. The display width of 1152 pixel is an oddity of hyper-v and for my usecase specific enough to tell the machine.
#+BEGIN_SRC emacs-lisp #+BEGIN_SRC emacs-lisp
(defvar my/whoami (defvar my/whoami
@ -31,7 +33,7 @@
- markdown: - markdown:
add hooks for certain file extensions, maybe add a smart way to start gfm-mode if markdown-file is in a git-project add hooks for certain file extensions, maybe add a smart way to start gfm-mode if markdown-file is in a git-project
- move package dependend configurations inside the package configuration itself, like keymaps for magit - move package dependend configurations inside the package configuration itself, like keymaps for magit
- get rid of user-local and user-global and work with git-ignore
* Update config in a running config * Update config in a running config
Two options: Two options:
- reload the open file: M-x load-file, then press twice to accept - reload the open file: M-x load-file, then press twice to accept
@ -154,10 +156,17 @@
Don't add the font in the work environment, which I am logged in as POH Don't add the font in the work environment, which I am logged in as POH
#+BEGIN_SRC emacs-lisp #+BEGIN_SRC emacs-lisp
(pcase my/whoami
("home" (set-face-attribute 'default nil :font "Hack-10"))
("work_hyperv" (set-face-attribute 'default nil :font "Hack-12"))
)
; (set-face-attribute 'default nil
; :family "Hack Nerd Font Mono-14"
; :height 110
; :weight 'normal
; :width 'normal)
(set-face-font 'default "Hack Nerd Font Mono-10")
; (pcase my/whoami
; ("home" (set-face-font 'default "Hack Nerd Font Mono-10"))
;; ("home" (set-face-attribute 'default nil :font "Hack-10"))
; ("work_hyperv" (set-face-attribute 'default nil :font "Hack-12"))
; )
#+END_SRC #+END_SRC
** Themes ** Themes
@ -1124,8 +1133,8 @@ fi
#+BEGIN_EXAMPLE #+BEGIN_EXAMPLE
cd /opt cd /opt
python3 -m venv vava
source ./vava/bin/activate
python3 -m venv fava
source ./fava/bin/activate
pip3 install wheel pip3 install wheel
pip3 install fava pip3 install fava
deactivate deactivate
@ -1382,6 +1391,15 @@ Backend configuration for beancount
) )
#+END_SRC #+END_SRC
Backend configuration for lua
#+BEGIN_SRC emacs-lisp
(defun company/lua-mode-hook()
(set (make-local-variable 'company-backends)
'(company-lua))
(company-mode t)
)
#+END_SRC
** Misc Company packages ** Misc Company packages
Addon to sort suggestions by usage Addon to sort suggestions by usage
@ -1462,7 +1480,7 @@ Maybe add [[https://github.com/hlissner/emacs-company-dict][company-dict]]? It's
:diminish yas-minor-mode :diminish yas-minor-mode
:init :init
(yas-global-mode t) (yas-global-mode t)
(setq yas-snippet-dirs (concat PATH_USER_GLOBAL "snippets"))
;; (setq yas-snippet-dirs '(concat PATH_USER_GLOBAL "snippets"))
:mode ("\\.yasnippet" . snippet-mode) :mode ("\\.yasnippet" . snippet-mode)
:config :config
(unless (string-equal my/whoami "work_remote") ; very hacky, but yas-reload-all throws a wrongp error at work (unless (string-equal my/whoami "work_remote") ; very hacky, but yas-reload-all throws a wrongp error at work
@ -1493,6 +1511,15 @@ Add some helpers to handle and understand macros
(define-key emacs-lisp-mode-map (kbd "C-c c") 'macrostep-collapse)) (define-key emacs-lisp-mode-map (kbd "C-c c") 'macrostep-collapse))
#+END_SRC #+END_SRC
** LUA
#+BEGIN_SRC emacs-lisp
(use-package lua-mode
:ensure t
:mode (("\\.lua$" . lua-mode))
:init
(add-hook 'lua-mode-hook 'company/lua-mode-hook)
)
#+END_SRC
** R ** R
TODO: test it TODO: test it
For now only enable ESS at home. Not sure if it is useful at work. For now only enable ESS at home. Not sure if it is useful at work.
@ -1769,9 +1796,14 @@ BEGIN_SRC emacs-lisp
END_SRC END_SRC
** Latex ** Latex
Requirements for Linux:
- Latex
- pdf-tools
Requirements for Debian:
- texlive (texlive-base?)
- elpa-pdf-tools
- dh-autoreconf?
Recommended:
- texlive-lang-german
- texlive-latex-extra
The midnight mode hook is disabled for now, because CVs with my pic just look weird in this mode. The midnight mode hook is disabled for now, because CVs with my pic just look weird in this mode.
@ -1793,12 +1825,12 @@ END_SRC
) )
#+END_SRC #+END_SRC
For latex-preview-pane a patch might be necessary (as of 2017-10), see the issue [[https://github.com/jsinglet/latex-preview-pane/issues/37][here]]
Update 2018-03: It seems to work without this patch. I will keep it here in case something breaks again.
For latex-preview-pane a patch might be necessary (as of 2017-10), see the issue [[https://github.com/jsinglet/latex-preview-pane/issues/37][here]].
Without it, the preview pane won't update, even when compiling works fine.
#+BEGIN_SRC #+BEGIN_SRC
latex-preview-pane-update-p() latex-preview-pane-update-p()
--- (doc-view-revert-buffer nil t) --- (doc-view-revert-buffer nil t)
+++ (revert-buffer-nil t 'preserve-modes)
+++ (revert-buffer nil t 'preserve-modes)
#+END_SRC #+END_SRC
After that M-x byte-compile-file After that M-x byte-compile-file

11
create-init.sh

@ -0,0 +1,11 @@
#!/bin/sh
rm ./init.el
rm ./init.elc
cat <<EOF >./init.el
(require 'org)
(find-file (concat user-emacs-directory "init.org"))
(org-babel-tangle)
(load-file (concat user-emacs-directory "init.el"))
(byte-compile-file (concat user-emacs-directory "init.el"))
EOF

386
init.el

@ -1,55 +1,357 @@
;; Garbage collection threshold
;; higher means less interuptions
(setq gc-cons-threshold 400000000)
;; Begin initialization
;; Turn off mouse interface early in startup to avoid momentary display
(when window-system
(menu-bar-mode -1)
(tool-bar-mode -1)
(scroll-bar-mode -1)
(tooltip-mode -1)
)
(setq inhibit-startup-message t)
(setq initial-scratch-message "")
(defun me/tangle-init ()
"If the current buffer is 'init.org',
the code blocks are tangled, and the tangled file is compiled."
(when (equal (buffer-file-name)
(expand-file-name (concat user-emacs-directory "init.org")))
;; avoid running hooks
(let ((prog-mode-hook nil))
(org-babel-tangle)
(byte-compile-file (concat user-emacs-directory "init.el"))
(load-file user-init-file))))
(add-hook 'after-save-hook 'me/tangle-init)
;; Setup package
(require 'package) (require 'package)
(add-to-list 'package-archives
'("melpa" . "https://melpa.org/packages/") t)
(add-to-list 'package-archives
'("melpa-stable" . "https://stable.melpa.org/packages/") t)
(add-to-list 'package-archives
'("gnu" . "https://elpa.gnu.org/packages/") t)
(add-to-list 'package-archives
'("org" . "https://orgmode.org/elpa/") t)
(add-to-list 'package-archives '("melpa" . "https://melpa.org/packages/") t)
(add-to-list 'package-archives '("melpa-stable" . "https://stable.melpa.org/packages/") t)
(add-to-list 'package-archives '("org" . "https://orgmode.org/elpa/") t)
(package-initialize) (package-initialize)
;; Bootstrap use-package
;; Install use-package if it's not already installed
;; use-package is used to configure the rest of the packages
(unless (package-installed-p 'use-package) (unless (package-installed-p 'use-package)
(package-refresh-contents)
(package-install 'use-package)
)
(package-refresh-contents)
(package-install 'use-package))
(setq use-package-verbose nil) (setq use-package-verbose nil)
;; From use-package README
(eval-when-compile
(require 'use-package))
;; see https://github.com/jwiegley/use-package/issues/522
(use-package diminish
:ensure t)
;(eval-when-compile
(require 'use-package);)
(defvar MY--PATH_USER_LOCAL (expand-file-name "~/.emacs.d/user-local/"))
(defvar MY--PATH_USER_GLOBAL (expand-file-name "~/.emacs.d/user-global/"))
(defvar MY--PATH_ORG_FILES (expand-file-name "~/Archiv/Organisieren/"))
(defvar MY--PATH_ORG_FILES_MOBILE (expand-file-name "~/Archiv/Organisieren/mobile/"))
(defvar MY--PATH_ORG_JOURNAl (expand-file-name "~/Archiv/Organisieren/Journal/"))
(setq bookmark-default-file (concat MY--PATH_USER_LOCAL "bookmarks"))
(setq recentf-save-file (concat MY--PATH_USER_LOCAL "recentf"))
(setq custom-file (concat MY--PATH_USER_LOCAL "custom.el")) ;; don't spam init.e with saved customization settings
(setq abbrev-file-name (concat MY--PATH_USER_GLOBAL "abbrev_defs"))
(setq backup-directory-alist `((".*" . ,temporary-file-directory)))
(setq auto-save-file-name-transforms `((".*" ,temporary-file-directory)))
(setq save-abbrevs 'silently) ;; don't bother me with asking for abbrev saving
(setq-default create-lockfiles nil) ;; disable lock files, can cause trouble in e.g. lsp-mode
(defalias 'yes-or-no-p 'y-or-n-p) ;; answer with y and n
(setq custom-safe-themes t) ;; don't ask me if I want to load a theme
(setq sentence-end-double-space nil) ;; don't coun two spaces after a period as the end of a sentence.
(delete-selection-mode t) ;; delete selected region when typing
(setq locale-coding-system 'utf-8)
(set-terminal-coding-system 'utf-8)
(set-keyboard-coding-system 'utf-8)
(set-selection-coding-system 'utf-8)
(if (eq system-type 'windows-nt)
(prefer-coding-system 'utf-8dos)
(prefer-coding-system 'utf-8))
(blink-cursor-mode -1) ;; turn off blinking cursor
(show-paren-mode t) ;; show other part of brackets
(column-number-mode t)
(setq uniquify-buffer-name-style 'forward)
(setq-default indent-tabs-mode nil) ;; avoid tabs in place of multiple spaces (they look bad in tex)
(setq-default indicate-empty-lines t) ;; show empty lines
(setq scroll-margin 5 ;; smooth scrolling
scroll-conservatively 10000
scroll-preserve-screen-position 1
scroll-step 1)
(global-hl-line-mode t) ;; highlight current line
(menu-bar-mode 0) ;; disable menu bar
(tool-bar-mode 0) ;; disable tool bar
(scroll-bar-mode 0) ;; disable scroll bar
(set-face-font 'default "Hack Nerd Font Mono-10")
(global-visual-line-mode)
(diminish 'visual-line-mode)
(use-package adaptive-wrap
:ensure t
:init
(when (fboundp 'adaptive-wrap-prefix-mode)
(defun my/activate-adaptive-wrap-prefix-mode ()
"Toggle `visual-line-mode' and `adaptive-wrap-prefix-mode' simultaneously."
(adaptive-wrap-prefix-mode (if visual-line-mode 1 -1)))
(add-hook 'visual-line-mode-hook 'my/activate-adaptive-wrap-prefix-mode)))
(use-package display-line-numbers
:init
(add-hook 'prog-mode-hook 'display-line-numbers-mode)
(add-hook 'org-src-mode-hook 'display-line-numbers-mode)
:config
(setq-default display-line-numbers-type 'visual
display-line-numbers-current-absolute t
display-line-numbers-with 4
display-line-numbers-widen t))
; (add-hook 'emacs-lisp-mode-hook 'display-line-numbers-mode)
(use-package rainbow-mode
:diminish
:hook ((org-mode
emacs-lisp-mode) . rainbow-mode))
(require 'undo-tree)
(use-package undo-tree
:ensure t
:diminish undo-tree-mode
:init
(global-undo-tree-mode 1))
(use-package imenu-list
:ensure t
:config
(setq imenu-list-focus-after-activation t
imenu-list-auto-resize t
imenu-list-position 'right)
:bind
(:map global-map
([f9] . imenu-list-smart-toggle))
)
(require 'which-key)
(use-package which-key
:ensure t
:diminish which-key-mode
:config
(which-key-mode)
(which-key-setup-side-window-right-bottom)
(which-key-setup-minibuffer)
(setq which-key-idle-delay 0.5))
(use-package org (use-package org
:ensure org-plus-contrib
:pin org)
:ensure org-plus-contrib
:init
(add-hook 'org-mode-hook 'company/org-mode-hook)
:config
;; (require 'org-id)
(add-to-list 'org-modules "org-id")
(setq org-default-notes-file (concat MY--PATH_ORG_FILES "notes.org")
org-agenda-files (list MY--PATH_ORG_FILES
MY--PATH_ORG_FILES_MOBILE)
org-id-locations-file (concat MY--PATH_USER_LOCAL ".org-id-locations")
org-log-into-drawer "LOGBOOK"))
(org-id-update-id-locations)
(require 'org-habit) ;;TODO Lösung ohne require finden, scheint mir nicht ideal zu sein, nur um ein org-modul zu aktivieren
;; (add-to-list 'org-modules "org-habit")
(setq org-habit-graph-column 80
org-habit-preceding-days 30
org-habit-following-days 7
org-habit-show-habits-only-for-today nil)
(use-package org-journal
:ensure t
:defer t
:custom
(org-journal-dir MY--PATH_ORG_JOURNAl)
(org-journal-enable-agenda-integration t))
(require 'ivy)
(use-package ivy
:ensure t
:diminish
(ivy-mode . "")
:init
(ivy-mode 1)
:bind
("C-r" . ivy-resume) ;; overrides isearch-backwards binding
:config
(setq ivy-use-virtual-buffers t ;; recent files and bookmarks in ivy-switch-buffer
ivy-height 20 ;; height of ivy window
ivy-count-format "%d/%d" ;; current and total number
ivy-re-builders-alist ;; regex replaces spaces with *
'((t . ivy--regex-plus))))
(use-package counsel
:ensure t
:bind*
(("M-x" . counsel-M-x)
("C-x C-f" . counsel-find-file)
("C-x C-r" . counsel-recentf)
("C-c C-f" . counsel-git)
("C-c h f" . counsel-describe-function)
("C-c h v" . counsel-describe-variable)
("M-i" . counsel-imenu)))
(use-package swiper
:ensure t
:bind
("C-s" . swiper))
(use-package ivy-hydra
:ensure t)
(require 'company)
(use-package company
:defer 1
:bind
(:map company-active-map
("RET" . nil)
([return] . nil)
("TAB" . company-complete-selection)
([tab] . company-complete-selection)
("<right>" . company-complete-common))
:config
(global-company-mode 1)
(setq-default
company-idle-delay .2
company-minimum-prefix-length 1
company-require-match nil
company-show-numbers t
company-tooltip-align-annotations t))
(require 'company-statistics)
(use-package company-statistics
:ensure t
:after company
:init
(setq company-statistics-file (concat MY--PATH_USER_LOCAL "company-statistics-cache.el"));~/.emacs.d/user-dir/company-statistics-cache.el")
:config
(company-statistics-mode 1))
(use-package company-dabbrev
:ensure nil
:after company
:config
(setq-default company-dabbrev-downcase nil))
(use-package company-box
:ensure nil
:init
(add-hook 'company-mode-hook 'company-box-mode))
(defun company/org-mode-hook()
(set (make-local-variable 'company-backends)
'(company-capf company-files))
(add-hook 'completion-at-point-functions 'pcomplete-completions-at-point nil t)
(message "company/org-mode-hook"))
(defun company/elisp-mode-hook()
(set (make-local-variable 'company-backends)
'((company-elisp company-dabbrev) company-capf company-files))
(message "company/elisp-mode-hook"))
(defun company/beancount-mode-hook()
(set (make-local-variable 'company-backends)
'(company-beancount)))
(use-package magit
:ensure t
:defer t
:init
; set git-path in work environment
(if (string-equal user-login-name "POH")
(setq magit-git-executable "P:/Eigene Dateien/Tools/Git/bin/git.exe")
)
:bind (("C-x g" . magit-status))
)
(use-package lsp-mode
:defer t
:commands lsp
:custom
(lsp-auto-guess-root nil)
(lsp-prefer-flymake nil) ; use flycheck instead
(lsp-file-watch-threshold 2000)
:bind (:map lsp-mode-map ("C-c C-f" . lsp-format-buffer))
:hook ((python-mode
js-mode
js2-mode
typescript-mode
web-mode) . lsp))
(use-package lsp-ui
:after lsp-mode
:diminish
:commands lsp-ui-mode
:config
(setq lsp-ui-doc-enable t
lsp-ui-doc-header t
lsp-ui-doc-include-signature t
lsp-ui-doc-position 'top
lsp-ui-doc-border (face-foreground 'default)
lsp-ui-sideline-enable nil
lsp-ui-sideline-ignore-duplicate t
lsp-ui-sideline-show-code-actions nil)
(when (display-graphic-p)
(setq lsp-ui-doc-use-webkit t))
;; workaround hide mode-line of lsp-ui-imenu buffer
(defadvice lsp-ui-imenu (after hide-lsp-ui-imenu-mode-line activate)
(setq mode-line-format nil)))
(use-package company-lsp
:requires company
:defer t
:ensure t
:config
;;disable client-side cache because lsp server does a better job
(setq company-transformers nil
company-lsp-async t
company-lsp-cache-candidates nil))
(use-package flycheck
;;:ensure t
:hook
((css-mode . flycheck-mode)
(emacs-lisp-mode . flycheck-mode)
(python-mode . flycheck-mode))
:init
(setq flycheck-emacs-lisp-load-path 'inherit)
:config
(setq-default
flycheck-check-synta-automatically '(save mode-enabled)
flycheck-disable-checkers '(emacs-lisp-checkdoc)
eldoc-idle-delay .1 ;; let eldoc echo faster than flycheck
flycheck-display-errors-delay .3)) ;; this way any errors will override eldoc messages
(add-hook 'emacs-lisp-mode-hook 'company/elisp-mode-hook)
(use-package web-mode
:ensure t
:defer t
:mode
("\\.phtml\\'"
"\\.tpl\\.php\\'"
"\\.djhtml\\'"
"\\.[t]?html?\\'"))
(use-package pyvenv
:ensure t
:config
(setenv "WORKON_HOME" (expand-file-name "~/Archiv/Programmierprojekte/Python/virtualenv/"))
(add-hook 'pyvenv-post-activate-hooks #'my/postactivatehook))
(defun my/postactivatehook ()
(setq lsp-python-ms-extra-paths pyvenv-virtual-env))
(use-package virtualenvwrapper
:ensure t
:hook (venv-postmkvirtualenv . (lambda() (shell-command "pip3 install importmagic epc")))
:config
(setq venv-location (expand-file-name "~/Archiv/Programmierprojekte/Python/virtualenv/")))
;; Load the config
(org-babel-load-file (concat user-emacs-directory "config.org"))
(use-package lsp-python-ms
:ensure t
:after lsp-mode python)
; :custom (lsp-python-executable-cmd "python3"))
(setq gc-cons-threshold 800000)
(use-package beancount
:load-path "user-local/elisp"
:defer t
:mode
("\\.beancount$" . beancount-mode)
:init
(add-hook 'beancount-mode-hook 'company/beancount-mode-hook)
; (add-hook 'beancount-mode-hook (pyvenv-activate "/opt/beancount"))
; (setenv "PATH"
; (concat "/opt/beancount/bin:"
; (getenv "PATH")))
:config
(setq beancount-filename-main "/home/marc/Archiv/Finanzen/Transaktionen/transactions.beancount"))

551
init.org

@ -0,0 +1,551 @@
#+TITLE: Emacs configuration file
#+AUTHOR: Marc
#+BABEL: :cache yes
#+PROPERTY: header-args :tangle yes
* TODOS
- Paket exec-path-from-shell, um PATH aus Linux auch in emacs zu haben
* First start
When pulling the repository the first time, an initial init.el needs to be setup. After start it will replace itself with the configuration from init.org
#+BEGIN_SRC emacs-lisp :tangle no
(require 'org')
(find-file (concat user-emacs-directory "init.org"))
(org-babel-tangle)
(load-file (concat user-emacs-directory "init.el"))
(byte-compile-file (concat user-emacs-directory "init.el"))
#+END_SRC
This function updates init.el whenever changes in init.org are made. The update will be active after saving.
#+BEGIN_SRC emacs-lisp
(defun me/tangle-init ()
"If the current buffer is 'init.org',
the code blocks are tangled, and the tangled file is compiled."
(when (equal (buffer-file-name)
(expand-file-name (concat user-emacs-directory "init.org")))
;; avoid running hooks
(let ((prog-mode-hook nil))
(org-babel-tangle)
(byte-compile-file (concat user-emacs-directory "init.el"))
(load-file user-init-file))))
(add-hook 'after-save-hook 'me/tangle-init)
#+END_SRC
#+BEGIN_SRC emacs-lisp
(require 'package)
;; bug before emacs 26.3
(setq gnutls-algorithm-priority "NORMAL:-VERS-TLS1.3")
(add-to-list 'package-archives '("elpa" . "https://elpa.gnu.org/packages/") t)
(add-to-list 'package-archives '("melpa" . "https://melpa.org/packages/") t)
(add-to-list 'package-archives '("melpa-stable" . "https://stable.melpa.org/packages/") t)
(add-to-list 'package-archives '("org" . "https://orgmode.org/elpa/") t)
(package-initialize)
#+END_SRC
#+BEGIN_SRC emacs-lisp
(unless (package-installed-p 'use-package)
(package-refresh-contents)
(package-install 'use-package))
(setq use-package-verbose nil)
(eval-when-compile
(require 'use-package))
(require 'bind-key)
(use-package diminish
:ensure t)
#+END_SRC
* Default settings
#+BEGIN_SRC emacs-lisp
(defvar MY--PATH_USER_LOCAL (expand-file-name "~/.emacs.d/user-local/"))
(defvar MY--PATH_USER_GLOBAL (expand-file-name "~/.emacs.d/user-global/"))
(defvar MY--PATH_ORG_FILES (expand-file-name "~/Archiv/Organisieren/"))
(defvar MY--PATH_ORG_FILES_MOBILE (expand-file-name "~/Archiv/Organisieren/mobile/"))
(defvar MY--PATH_ORG_JOURNAl (expand-file-name "~/Archiv/Organisieren/Journal/"))
(setq bookmark-default-file (concat MY--PATH_USER_LOCAL "bookmarks"))
(setq recentf-save-file (concat MY--PATH_USER_LOCAL "recentf"))
(setq custom-file (concat MY--PATH_USER_LOCAL "custom.el")) ;; don't spam init.e with saved customization settings
(setq abbrev-file-name (concat MY--PATH_USER_GLOBAL "abbrev_defs"))
(setq backup-directory-alist `((".*" . ,temporary-file-directory)))
(setq auto-save-file-name-transforms `((".*" ,temporary-file-directory)))
(setq save-abbrevs 'silently) ;; don't bother me with asking for abbrev saving
(setq-default create-lockfiles nil) ;; disable lock files, can cause trouble in e.g. lsp-mode
(defalias 'yes-or-no-p 'y-or-n-p) ;; answer with y and n
(setq custom-safe-themes t) ;; don't ask me if I want to load a theme
(setq sentence-end-double-space nil) ;; don't coun two spaces after a period as the end of a sentence.
(delete-selection-mode t) ;; delete selected region when typing
(setq locale-coding-system 'utf-8)
(set-terminal-coding-system 'utf-8)
(set-keyboard-coding-system 'utf-8)
(set-selection-coding-system 'utf-8)
(if (eq system-type 'windows-nt)
(prefer-coding-system 'utf-8dos)
(prefer-coding-system 'utf-8))
(blink-cursor-mode -1) ;; turn off blinking cursor
(show-paren-mode t) ;; show other part of brackets
(column-number-mode t)
(setq uniquify-buffer-name-style 'forward)
(setq-default indent-tabs-mode nil) ;; avoid tabs in place of multiple spaces (they look bad in tex)
(setq-default indicate-empty-lines t) ;; show empty lines
(setq scroll-margin 5 ;; smooth scrolling
scroll-conservatively 10000
scroll-preserve-screen-position 1
scroll-step 1)
(global-hl-line-mode t) ;; highlight current line
(menu-bar-mode 0) ;; disable menu bar
(tool-bar-mode 0) ;; disable tool bar
(scroll-bar-mode 0) ;; disable scroll bar
#+END_SRC
* visuals
** Font
#+BEGIN_SRC emacs-lisp
(set-face-font 'default "Hack-10")
#+END_SRC
** line wrappings
#+BEGIN_SRC emacs-lisp
(global-visual-line-mode)
(diminish 'visual-line-mode)
(use-package adaptive-wrap
:ensure t
:init
(when (fboundp 'adaptive-wrap-prefix-mode)
(defun my/activate-adaptive-wrap-prefix-mode ()
"Toggle `visual-line-mode' and `adaptive-wrap-prefix-mode' simultaneously."
(adaptive-wrap-prefix-mode (if visual-line-mode 1 -1)))
(add-hook 'visual-line-mode-hook 'my/activate-adaptive-wrap-prefix-mode)))
#+END_SRC
** line numbers
#+BEGIN_SRC emacs-lisp
(use-package display-line-numbers
:init
(add-hook 'prog-mode-hook 'display-line-numbers-mode)
(add-hook 'org-src-mode-hook 'display-line-numbers-mode)
:config
(setq-default display-line-numbers-type 'visual
display-line-numbers-current-absolute t
display-line-numbers-with 4
display-line-numbers-widen t))
; (add-hook 'emacs-lisp-mode-hook 'display-line-numbers-mode)
#+END_SRC
** misc
#+BEGIN_SRC emacs-lisp
(use-package rainbow-mode
:ensure t
:diminish
:hook ((org-mode
emacs-lisp-mode) . rainbow-mode))
#+END_SRC
* undo
#+BEGIN_SRC emacs-lisp
(use-package undo-tree
:ensure t
:diminish undo-tree-mode
:init
(global-undo-tree-mode 1))
#+END_SRC
* imenu-list
A minor mode to show imenu in a sidebar.
Call imenu-list-smart-toggle.
[[https://github.com/bmag/imenu-list][Source]]
#+BEGIN_SRC emacs-lisp
(use-package imenu-list
:ensure t
:config
(setq imenu-list-focus-after-activation t
imenu-list-auto-resize t
imenu-list-position 'right)
:bind
(:map global-map
([f9] . imenu-list-smart-toggle))
)
#+END_SRC
* which-key
#+BEGIN_SRC emacs-lisp
(use-package which-key
:ensure t
:diminish which-key-mode
:config
(which-key-mode)
(which-key-setup-side-window-right-bottom)
(which-key-setup-minibuffer)
(setq which-key-idle-delay 0.5))
#+END_SRC
* orgmode
** org
#+BEGIN_SRC emacs-lisp
(use-package org
:ensure org-plus-contrib
:init
(add-hook 'org-mode-hook 'company/org-mode-hook)
:config
;; (require 'org-id)
(add-to-list 'org-modules "org-id")
(setq org-default-notes-file (concat MY--PATH_ORG_FILES "notes.org")
org-agenda-files (list MY--PATH_ORG_FILES
MY--PATH_ORG_FILES_MOBILE)
org-id-locations-file (concat MY--PATH_USER_LOCAL ".org-id-locations")
org-log-into-drawer "LOGBOOK"))
(org-id-update-id-locations)
#+END_SRC
** habits
#+BEGIN_SRC emacs-lisp
(require 'org-habit) ;;TODO Lösung ohne require finden, scheint mir nicht ideal zu sein, nur um ein org-modul zu aktivieren
;; (add-to-list 'org-modules "org-habit")
(setq org-habit-graph-column 80
org-habit-preceding-days 30
org-habit-following-days 7
org-habit-show-habits-only-for-today nil)
#+END_SRC
** *TODO*
org-super-agenda
** org-caldav
Vorerst deaktiviert, Nutzen evtl. nicht vorhanden
BEGIN_SRC emacs-lisp
(use-package org-caldav
:ensure t
:config
(setq org-caldav-url "https://nextcloud.cloudsphere.duckdns.org/remote.php/dav/calendars/marc"
org-caldav-calendar-id "orgmode"
org-caldav-inbox (expand-file-name "~/Archiv/Organisieren/caldav-inbox")
org-caldav-files (concat MY--PATH_ORG_FILES "tasks")))
END_SRC
** journal
[[https://github.com/bastibe/org-journal][Source]]
#+BEGIN_SRC emacs-lisp
(use-package org-journal
:ensure t
:defer t
:custom
(org-journal-dir MY--PATH_ORG_JOURNAl)
(org-journal-enable-agenda-integration t))
#+END_SRC
* ivy / counsel / swiper
#+BEGIN_SRC emacs-lisp
; (require 'ivy)
(use-package ivy
:ensure t
:diminish
(ivy-mode . "")
:init
(ivy-mode 1)
:bind
("C-r" . ivy-resume) ;; overrides isearch-backwards binding
:config
(setq ivy-use-virtual-buffers t ;; recent files and bookmarks in ivy-switch-buffer
ivy-height 20 ;; height of ivy window
ivy-count-format "%d/%d" ;; current and total number
ivy-re-builders-alist ;; regex replaces spaces with *
'((t . ivy--regex-plus))))
(use-package counsel
:ensure t
:bind*
(("M-x" . counsel-M-x)
("C-x C-f" . counsel-find-file)
("C-x C-r" . counsel-recentf)
("C-c C-f" . counsel-git)
("C-c h f" . counsel-describe-function)
("C-c h v" . counsel-describe-variable)
("M-i" . counsel-imenu)))
(use-package swiper
:ensure t
:bind
("C-s" . swiper))
(use-package ivy-hydra
:ensure t)
#+END_SRC
* company
#+BEGIN_SRC emacs-lisp
; (require 'company)
(use-package company
:defer 1
:bind
(:map company-active-map
("RET" . nil)
([return] . nil)
("TAB" . company-complete-selection)
([tab] . company-complete-selection)
("<right>" . company-complete-common))
:config
(global-company-mode 1)
(setq-default
company-idle-delay .2
company-minimum-prefix-length 1
company-require-match nil
company-show-numbers t
company-tooltip-align-annotations t))
; (require 'company-statistics)
(use-package company-statistics
:ensure t
:after company
:init
(setq company-statistics-file (concat MY--PATH_USER_LOCAL "company-statistics-cache.el"));~/.emacs.d/user-dir/company-statistics-cache.el")
:config
(company-statistics-mode 1))
(use-package company-dabbrev
:ensure nil
:after company
:config
(setq-default company-dabbrev-downcase nil))
(use-package company-box
:ensure t
:init
(add-hook 'company-mode-hook 'company-box-mode))
#+END_SRC
** company backends
#+BEGIN_SRC emacs-lisp
(defun company/org-mode-hook()
(set (make-local-variable 'company-backends)
'(company-capf company-files))
(add-hook 'completion-at-point-functions 'pcomplete-completions-at-point nil t)
(message "company/org-mode-hook"))
(defun company/elisp-mode-hook()
(set (make-local-variable 'company-backends)
'((company-elisp company-dabbrev) company-capf company-files))
(message "company/elisp-mode-hook"))
(defun company/beancount-mode-hook()
(set (make-local-variable 'company-backends)
'(company-beancount)))
#+END_SRC
* Programming
** Magit / Git
Little crash course in magit:
- magit-init to init a git project
- magit-status (C-x g) to call the status window
In status buffer:
- s stage files
- u unstage files
- U unstage all files
- a apply changes to staging
- c c commit (type commit message, then C-c C-c to commit)
- b b switch to another branch
- P u git push
- F u git pull
#+BEGIN_SRC emacs-lisp
(use-package magit
:ensure t
:defer t
:init
; set git-path in work environment
(if (string-equal user-login-name "POH")
(setq magit-git-executable "P:/Eigene Dateien/Tools/Git/bin/git.exe")
)
:bind (("C-x g" . magit-status))
)
#+END_SRC
** LSP
Configuration for the language server protocol
*ACHTUNG* Dateipfad muss absolut sein, symlink im Pfad führt zumindest beim ersten Start zu Fehlern beim lsp
Sobald der lsp einmal lief, kann zukünftig der symlink-Pfad genommen werden.
Getestet wurde die funktionierende Datei selbst und neu erstellte Dateien im selben Pfad.
TODO Unterverzeichnisse wurden noch nicht getestet
#+BEGIN_SRC emacs-lisp
(use-package lsp-mode
:defer t
:commands lsp
:custom
(lsp-auto-guess-root nil)
(lsp-prefer-flymake nil) ; use flycheck instead
(lsp-file-watch-threshold 2000)
:bind (:map lsp-mode-map ("C-c C-f" . lsp-format-buffer))
:hook ((python-mode
js-mode
js2-mode
typescript-mode
web-mode) . lsp))
(use-package lsp-ui
:after lsp-mode
:ensure t
:diminish
:commands lsp-ui-mode
:config
(setq lsp-ui-doc-enable t
lsp-ui-doc-header t
lsp-ui-doc-include-signature t
lsp-ui-doc-position 'top
lsp-ui-doc-border (face-foreground 'default)
lsp-ui-sideline-enable nil
lsp-ui-sideline-ignore-duplicate t
lsp-ui-sideline-show-code-actions nil)
(when (display-graphic-p)
(setq lsp-ui-doc-use-webkit t))
;; workaround hide mode-line of lsp-ui-imenu buffer
(defadvice lsp-ui-imenu (after hide-lsp-ui-imenu-mode-line activate)
(setq mode-line-format nil)))
(use-package company-lsp
:requires company
:defer t
:ensure t
:config
;;disable client-side cache because lsp server does a better job
(setq company-transformers nil
company-lsp-async t
company-lsp-cache-candidates nil))
#+END_SRC
** flycheck
#+BEGIN_SRC emacs-lisp
(use-package flycheck
:ensure t
:hook
((css-mode . flycheck-mode)
(emacs-lisp-mode . flycheck-mode)
(python-mode . flycheck-mode))
:init
(setq flycheck-emacs-lisp-load-path 'inherit)
:config
(setq-default
flycheck-check-synta-automatically '(save mode-enabled)
flycheck-disable-checkers '(emacs-lisp-checkdoc)
eldoc-idle-delay .1 ;; let eldoc echo faster than flycheck
flycheck-display-errors-delay .3)) ;; this way any errors will override eldoc messages
#+END_SRC
** lisp
#+BEGIN_SRC emacs-lisp
(add-hook 'emacs-lisp-mode-hook 'company/elisp-mode-hook)
#+END_SRC
** web
apt install npm
sudo npm install -g vscode-html-languageserver-bin
evtl alternativ typescript-language-server?
#+BEGIN_SRC emacs-lisp
(use-package web-mode
:ensure t
:defer t
:mode
("\\.phtml\\'"
"\\.tpl\\.php\\'"
"\\.djhtml\\'"
"\\.[t]?html?\\'"))
#+END_SRC
** Python
Systemseitig muss python-language-server installiert sein:
pip3 install 'python-language-server[all]'
für andere language servers
https://github.com/emacs-lsp/lsp-mode#install-language-server
#+BEGIN_SRC emacs-lisp
(use-package pyvenv
:ensure t
:config
(setenv "WORKON_HOME" (expand-file-name "~/Archiv/Programmierprojekte/Python/virtualenv/"))
(add-hook 'pyvenv-post-activate-hooks #'my/postactivatehook))
(defun my/postactivatehook ()
(setq lsp-python-ms-extra-paths pyvenv-virtual-env))
(use-package virtualenvwrapper
:ensure t
:hook (venv-postmkvirtualenv . (lambda() (shell-command "pip3 install importmagic epc")))
:config
(setq venv-location (expand-file-name "~/Archiv/Programmierprojekte/Python/virtualenv/")))
(use-package lsp-python-ms
:ensure t
:after lsp-mode python)
; :custom (lsp-python-executable-cmd "python3"))
#+END_SRC
* beancount
** Installation
#+BEGIN_SRC shell
sudo su
cd /opt
python3 -m venv beancount
source ./beancount/bin/activate
pip3 install wheel
pip3 install beancount
sleep 100
echo "shell running!"
deactivate
#+END_SRC
#+BEGIN_SRC emacs-lisp
(use-package beancount
:load-path "user-local/elisp"
:ensure t
:defer t
:mode
("\\.beancount$" . beancount-mode)
:init
(add-hook 'beancount-mode-hook 'company/beancount-mode-hook)
; (add-hook 'beancount-mode-hook (pyvenv-activate "/opt/beancount"))
; (setenv "PATH"
; (concat "/opt/beancount/bin:"
; (getenv "PATH")))
:config
(setq beancount-filename-main "/home/marc/Archiv/Finanzen/Transaktionen/transactions.beancount"))
#+END_SRC
To support org-babel, check if it can find the symlink to ob-beancount.el
#+BEGIN_SRC shell
orgpath=`find /home/marc/.emacs.d/elpa/ -type d -name "org-plus*" -print`
beansym="$orgpath/ob-beancount.el
bean="/home/marc/Archiv/Programmierprojekte/Lisp/beancount-mode/ob-beancount.el"
if [ -h "$beansym" ]
then
echo "$beansym found"
elif [ -e "$bean" ]
then
echo "creating symlink"
ln -s "$bean" "$beansym"
else
echo "$bean not found, symlink creation aborted"
fi
#+END_SRC
Fava is strongly recommended.
#+BEGIN_SRC shell
cd /opt
python3 -m venv fava
source ./fava/bin/activate
pip3 install wheel
pip3 install fava
deactivate
#+END_SRC
Start fava with fava my_file.beancount
It is accessable on this URL: [[http://127.0.0.1:5000][Fava]]
Beancount-mode can start fava and open the URL right away.

620
user-local/elisp/beancount.el

@ -0,0 +1,620 @@
;;; package --- Summary
;;; Commentary:
;; A humble try to port leder-mode to beancount
;;; Code:
;(require 'beancount-regex)
(require 'font-lock)
(require 'company) ; for company-mode
(require 'pcomplete)
(require 'cl-lib)
(defgroup beancount ()
"Editing mode for beancount files."
:group 'beancount)
(defconst beancount-timestamped-directive-names
'("balance"
"open"
"close"
"pad"
"document"
"note"
;; the ones below are not followed by an account name.
"event"
"price"
"commodity"
"query"
"txn")
"Directive names that can appear after a date.")
(defconst beancount-account-categories
'("Assets"
"Liabilities"
"Equity"
"Income"
"Expenses"
; TODO: Fill changed root accounts automatically
"Aktiva"
"Verbindlichkeiten"
"Eigenkapital"
"Ertraege"
"Aufwendungen"))
(defconst beancount-nontimestamped-directive-names
'("pushtag"
"poptag"
"option"
"include"
"plugin")
"Directive names that can appear after a date.")
(defconst beancount-option-names
;; this list has to be kept in sync with the options definied in
;; beancount/parser/options.py
'("title"
"name_assets"
"name_equity"
"name_income"
"name_expenses"
"bookin_algorithm"
"bookin_method"
"account_previous_balances"
"account_previous_earnings"
"account_previous_conversions"
"account_current_earnings"
"account_current_conversions"
"account_rounding"
"conversion_currency"
"inferred_tolerance_default"
"inferred_tolerance_multiplier"
"infer_tolerance_from_cost"
"documents"
"operating_currency"
"render_commas"
"plugin_processing_mode"
"plugin"
"long_string_maxlines"
))
(defconst beancount-directive-names
(append beancount-nontimestamped-directive-names
beancount-timestamped-directive-names)
"A list of directive names.")
(defconst beancount-tag-chars
"[:alnum:]-_/."
"Allowed tag characters.")
(defconst beancount-account-chars
"[:alnum:]-_:"
"Allowed account characters.")
(defconst beancount-account-regexp
(concat (regexp-opt beancount-account-categories)
"\\(?::[[:upper:]][" beancount-account-chars "]+\\)")
"A regular expression to match account names.")
; TODO: currently shows all texts between ""
(defconst beancount-payee-regexp
"\"\\(.*?\\)\"")
(defconst beancount-date-regexp
"^[12][901][0-9]\\{2\\}-\\(\\(0[1-9]\\)\\|\\(1[012]\\)\\)-\\(\\([012][0-9]\\)\\|\\(3[01]\\)\\)"
"Regular expression for dates.")
(defconst beancount-number-regexp
"[-+]?[0-9,]+\\(?:\\.[0-9]*\\)"
"Regular expression to match decimal numbers.")
(defconst beancount-tag-regexp
(concat "#"
"["
beancount-tag-chars
"]+")
"Regular expression for valid tags.")
(defconst beancount-currency-regexp
"[A-Z][A-Z-_'.]*"
"Regular expression to match currencies.")
(defconst beancount-timestamped-accounts-regexp
(concat beancount-date-regexp
" " ;"\\(\\s-+\\)"
(regexp-opt beancount-timestamped-directive-names)
" ") ;"\\(\\s-+\\)")
"A regular expression to match valid preceding characters before an account name.")
(defconst beancount-amount-and-currency-regex
"\s-*[-]?[0-9,]+[.][0-9]\\{2\\}\s[A-Za-z0-9.]+"
"A regular expression for amounts including currency.")
(defconst beancount-payee-any-status-regex
"^[0-9]+[-/][-/.=0-9]+\\(\\s-+\\*\\)?\\(\\s-+(.*?)\\)?\\s-+\\(.+?\\)\\s-*\\(;\\|$\\)")
(defconst beancount-date-and-status-regex
(concat beancount-date-regexp
"\\(\\s-+[\\*!]\\)")
"Returns true for YYYY-MM-DD ! and YYYY-MM-DD *.")
(defconst beancount-valid-prefix-for-directives-regex
(concat "^"
beancount-date-regexp
"[ ]+"
"[a-z]*$"))
(defconst beancount-valid-prefix-for-payee-completion-regex
(concat "\\("
beancount-date-and-status-regex
" \"" ; empty space and open quotes
"[^\"\n]*" ; any number of chars except quotes and newline
"\\(" ; start of optional second quoted term
"\"[ ]+\"" ; closing quotes, whitespaces, opening quotes
"[^\"\n]*" ; any number of chars except quotes and newline
"\\)?" ; end of optional second quoted term
"\\)$" ; the whole regex looks from the right side of the line
))
(defconst beancount-valid-prefix-for-tag-completion-regex
(concat ;"\\("
beancount-date-and-status-regex
" \"" ; empty space and open quotes
"[^\"\n]*" ; any number of chars except quotes and newline
"\\(" ; start of optional second quoted term
"\"[ ]+\"" ; closing quotes, whitespaces, opening quotes
"[^\"\n]*" ; any number of chars except quotes and newline
"\\)?" ; end of optional second quoted term
"\"[ ]+#"
;"\\)$" ; the whole regex looks from the right side of the line
))
(defconst beancount-comments-regex
(concat ";[^\"\n]*$")) ; right part of the line after a comment symbol if no quote or newline is included
(defconst beancount-empty-line-regex
"^\\(\\s-+\\)" ;; maybe "^[ \t]+" is better
"Returns true for preceding whitespaces.")
;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
;; Faces
(defgroup beancount-faces nil "Beancount mode highlighting" :group 'beancount)
(defface beancount-font-default-face
`((t :inherit default))
"Default face"
:group 'beancount-faces)
(defface beancount-font-xact-cleared-face
`((t :foreground "#AAAAAA" :weight normal))
"Default face for cleared transactions"
:group 'beancount-faces)
(defface beancount-font-xact-pending-face
`((t :foreground "#dc322f" :weight bold))
"Default face for pending transactions"
:group 'beancount-faces)
(defface beancount-font-payee-cleared-face
`((t :inherit beancount-font-other-face))
"Default face for pending transactions"
:group 'beancount-faces)
(defface beancount-font-payee-pending-face
`((t :foreground "#f24b61" :weight normal))
"Default face for pending (!) transactions"
:group 'beancount-faces)
(defface beancount-font-other-face
`((t :foreground "#657b83" :weight normal))
"Default face for other transactions"
:group 'beancount-faces)
(defface beancount-font-posting-date-face
`((t :foreground "#cb4b16" :weight normal))
"Default face for dates"
:group 'beancount-faces)
(defface beancount-font-amount-face
`((t :foreground "#cb4b16" :weight normal))
"Default face for amounts"
:group 'beancount-faces)
(defvar beancount-font-lock-directives
`(;; reserved keywords
(,(regexp-opt beancount-directive-names) . font-lock-keyword-face)
;; tags & links
("[#\\^][A-Za-z0-9\-_/.]+" . font-lock-type-face)
;; comments
(,beancount-comments-regex (0 font-lock-comment-face))
;; date
(,beancount-date-regexp . 'beancount-font-posting-date-face)
;; account
(,beancount-account-regexp . 'beancount-font-other-face)
;; payees
("\"\\(.*?\\)\"" . font-lock-comment-face)
;; txn flags
("! " . font-lock-warning-face)
))
;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
;; Alignments
(defmacro beancount-for-line-in-region (begin end &rest exprs)
"Iterate over each line in region from BEGIN to END (EXPRS)
until an empty line is encountered."
`(save-excursion
(let ((end-marker (copy-marker ,end)))
(goto-char ,begin)
(beginning-of-line)
(while (and (not (eobp)) (< (point) end-marker))
(beginning-of-line)
(progn ,@exprs)
(forward-line 1)
))))
(defun beancount-align-numbers (begin end)
"Align all numbers in the current buffer from BEGIN to END."
(interactive "r")
;; loop once in the buffer to find the length of the longest string before the
;; number.
(let (prefix-widths
number-widths
(number-padding " "))
(beancount-for-line-in-region
begin end
(let ((line (thing-at-point 'line)))
(when (string-match (concat "\\(.*?\\)"
"[ \t]+"
"\\(" beancount-number-regexp "\\)"
"[ \t]+"
beancount-currency-regexp)
line)
(push (length (match-string 1 line)) prefix-widths)
(push (length (match-string 2 line)) number-widths)
)))
(when prefix-widths
;; Loop again to make the adjustments to the numbers.
(let* ((number-width (apply 'max number-widths))
(number-format (format "%%%ss" number-width))
;; compute the rightmost column of prefix
(max-prefix-width (apply 'max prefix-widths))
(prefix-format (format "%%-%ss" max-prefix-width))
)
(beancount-for-line-in-region
begin end
(let ((line (thing-at-point 'line)))
(when (string-match (concat "^\\([^\"]*?\\)"
"[ \t]+"
"\\(" beancount-number-regexp "\\)"
"[ \t]+"
"\\(.*\\)$")
line)
(delete-region (line-beginning-position) (line-end-position))
(let* ((prefix (match-string 1 line))
(number (match-string 2 line))
(rest (match-string 3 line)))
(insert (format prefix-format prefix))
(insert number-padding)
(insert (format number-format number))
(insert " ")
(insert rest)))))))))
(defun beancount-hash-keys (hashtable)
"Extract all the keys of the given HASHTABLE. Return a sorted list."
(let (rlist)
(maphash (lambda (k _v) (push k rlist)) hashtable)
(sort rlist 'string<)))
(defvar beancount-accounts nil
"A list of the accounts available in this buffer.
This is a cache of the value computed by `beancount-get-accounts'.")
(make-variable-buffer-local 'beancount-accounts)
(defun beancount-init-accounts ()
"Initialize or reset the list of accounts."
(interactive)
(setq beancount-accounts (beancount-get-accounts-in-buffer)) ;-new
(pcomplete-uniqify-list (nreverse beancount-accounts))
;; (setq beancount-accounts (beancount-get-accounts))
(message "Accounts updated."))
;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
;; Navigation
(defun beancount-navigate-start-xact-or-directive-p ()
"Return t if at the beginning of an empty or all-whitespace line."
(not (looking-at "[ \t]\\|\\(^$\\)")))
(defun beancount-navigate-prev-xact-or-directive ()
"Move to the beginning of the next xact or directive."
(interactive)
(beginning-of-line)
(if (beancount-navigate-start-xact-or-directive-p) ;if we are at the start of an xact, move backward to the previous xact
(progn
(forward-line -1)
(if (not (beancount-navigate-start-xact-or-directive-p)) ; we have moved backward and are not at another xact, recurse backward
(beancount-navigate-prev-xact-or-directive)))
(while (not (or (bobp)
(beancount-navigate-start-xact-or-directive-p)))
(forward-line -1))))
(defun beancount-navigate-next-xact-or-directive ()
"Move to the beginning of the next xact or directive."
(interactive)
(beginning-of-line)
(if (beancount-navigate-start-xact-or-directive-p) ; if we are at the start of an xact, move forward to the next xact
(progn
(forward-line)
(if (not (beancount-navigate-start-xact-or-directive-p)) ; we have moved forward and are not at another xact, recurse forward
(beancount-navigate-next-xact-or-directive)))
(while (not (or (eobp) ; we didn't stsrt off at the beginning of an xact
(beancount-navigate-start-xact-or-directive-p)))
(forward-line))))
;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
;; Completion
(defun beancount-thing-at-point ()
"Describe thing at points. Return 'transaction, 'posting, or nil.
Leave point at the beginning of the thing under point.")
(defun beancount-trim-trailing-whitespace (str)
"Replace trailing whitespaces in STR."
(replace-regexp-in-string "[ \t]*$" "" str))
;; (defun beancount-fully-complete-xact ()
;; "Completes a transaction if there is another matching payee in the buffer."
;; (interactive)
;; (let* ((name (beancount-trim-trailing-whitespace (caar (beancount-parse-arguments))))
;; xacf)))
;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
;; Functions for external programs
(defvar beancount-filename-main buffer-file-name
"File name of the main beancount file for beancount-check.")
(defvar beancount-terminal-name "urxvt"
"Name of the terminal emulator to run fava.")
(defvar beancount-fava-exec "/opt/fava/bin/fava"
"Full path of fava executable.")
(defvar beancount-install-dir nil
"Directory in which Beancount's source is located.
Only useful if you have not installed Beancount properly in your PATH")
(defun beancount--run (prog &rest args)
"Random text PROG ARGS."
(let ((process-environment
(if beancount-install-dir
`(,(concat "PYTHONPATH=" beancount-install-dir)
,(concat "PATH="
(expand-file-name "bin" beancount-install-dir)
":"
(getenv "PATH"))
,@process-environment)
process-environment))
(compile-command (mapconcat (lambda (arg)
(if (stringp arg)
(shell-quote-argument arg) ""))
(cons prog args)
" ")))
(call-interactively 'compile)))
(defvar beancount-check-program "bean-check"
"Program to run the parser and validator on an input file.")
(defun beancount-check ()
"Run `beancount-check-program'."
(interactive)
(let ((compilation-read-command nil))
(beancount--run beancount-check-program beancount-filename-main)))
; (file-relative-name buffer-file-name))))
(defun beancount-fava ()
"Run `beancount-fava' and open the URL in the default browser."
(interactive)
(start-process "termx" nil beancount-terminal-name "-e" beancount-fava-exec beancount-filename-main)
(sleep-for 0.5) ; necessary to prevent an error
(browse-url "127.0.0.1:5000"))
;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
;; Completions
(defun beancount-get-accounts-in-buffer ()
"Return a list of all accounts."
(let ((origin (point))
accounts-list)
(save-excursion
(goto-char (point-min))
(while (re-search-forward
beancount-account-regexp nil t)
(setq accounts-list (cons (match-string-no-properties 0)
accounts-list))))
(pcomplete-uniqify-list (nreverse accounts-list))))
(defun beancount-get-payees-in-buffer ()
"Return a list of all payees."
(let ((origin (point))
payees-list)
(save-excursion
(goto-char (point-min))
(while (re-search-forward
"^[0-9- *]*\"\\(.*?\\)\" \"\\(.*?\\)\"" nil t) ; matches are in brackets
(setq payees-list (cons (match-string-no-properties 1) ; get first match, generally the payee
payees-list))
(setq payees-list (cons (match-string-no-properties 2) ; get second match, generally a description
payees-list))))
(pcomplete-uniqify-list (nreverse payees-list))))
(defun beancount-get-tags-in-buffer ()
"Return a list of all tags."
(let ((origin (point))
tags-list)
(save-excursion
(goto-char (point-min))
(while (re-search-forward
beancount-tag-regexp nil t)
(setq tags-list (cons (substring (match-string-no-properties 0) 1)
tags-list))))
(pcomplete-uniqify-list (nreverse tags-list))))
(defun beancount-get-commodities-in-buffer ()
"Return a list of all commodities / currencies."
(let ((origin (point))
commodities-list)
(save-excursion
(goto-char (point-min))
(while (re-search-forward
"^[ ]+[A-Za-z0-9-_:]+[ ]+[-]?[0-9,.]+[ ]+\\([A-Z][A-Z0-9-_.']*[A-Z0-9]\\)" nil t)
(setq commodities-list (cons (match-string-no-properties 1)
commodities-list))))
(pcomplete-uniqify-list (nreverse commodities-list))))
;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
;; Finishing setup
(defvar beancount-mode-map
(let ((map (make-sparse-keymap)))
(define-key map [(meta ?p)] #'beancount-navigate-prev-xact-or-directive)
(define-key map [(meta ?n)] #'beancount-navigate-next-xact-or-directive)
map)
"Keymap for `bean-mode'.")
(easy-menu-define beancount-mode-menu beancount-mode-map
"Beancount menu"
'("Beancount"
["Customize Ledger Mode" (lambda () (interactive) (customize-group 'beanmode))]))
;; (defun beancount-get-accounts (&optional string)
;; "Return list of account names with STRING infix present.
;; STRING can be multiple words separated by a space."
;; (let* ((accounts-string (shell-command-to-string
;; (concat "bean-query -q -f csv "
;; "/home/marc/Archiv/Finanzen/transactions_ingdiba.beancount "
;; "'SELECT DISTINCT account ORDER BY ACCOUNT ASC'")))
;; (accounts-list (split-string accounts-string)))
;; accounts-list))
(defvar beancount-accounts-cache nil
"List of accounts cached for company mode.")
(defvar beancount-payees-cache nil
"List of payees cached for company mode.")
(defvar beancount-tags-cache nil
"List of tags cached for company mode.")
(defvar beancount-commodities-cache nil
"List of commodities / currencies cached for company mode.")
(defun beancount-update-accounts-and-payees ()
"Initialize or reset the list of accounts."
(interactive)
(setq beancount-accounts-cache (beancount-get-accounts-in-buffer)) ;-new
(pcomplete-uniqify-list (nreverse beancount-accounts-cache))
;; (setq beancount-accounts (beancount-get-accounts))
(message "Accounts updated.")
(setq beancount-payees-cache (beancount-get-payees-in-buffer)) ;-new
(pcomplete-uniqify-list (nreverse beancount-payees-cache))
(message "Payees updated.")
(setq beancount-commodities-cache (beancount-get-commodities-in-buffer))
(pcomplete-uniqify-list (nreverse beancount-commodities-cache))
(setq beancount-tags-cache (beancount-get-tags-in-buffer))
(pcomplete-uniqify-list (nreverse beancount-tags-cache))
(message "Tags updated."))
;; first test for conditional completions
;; e.g. only account names when completion is after a date
;; (defun beancount-company-candidates (prefix)
;; "Check what PREFIX does."
;; (let* (accounts (beancount-accounts-cache))
;; accounts))
;;;###autoload
(defun company-beancount (command &optional arg &rest ignored)
"Company backend for 'beancount-mode'.
COMMAND, ARG, and IGNORED the regular meanings."
(interactive (list 'interactive))
(message arg)
(pcase command
(`interactive (company-begin-backend 'company-beancount))
(`prefix (and (eq major-mode 'beancount-mode)
(company-grab-symbol)))
(`candidates
(cond
;; if line ends with date and status, at max one quoted text, and an open quote, offer payees and explainations
((string-match-p beancount-valid-prefix-for-payee-completion-regex (thing-at-point 'line))
(delq nil
(mapcar (lambda (c)
; (and (string-prefix-p (substring arg 1) c) c)
(and (string-prefix-p arg c) c))
beancount-payees-cache)))
;; if line starts with date, offer directives
((string-match-p beancount-valid-prefix-for-directives-regex (thing-at-point 'line))
(delq nil
(mapcar (lambda (c)
(and (string-prefix-p arg c) c))
beancount-timestamped-directive-names)))
;; if line starts with date, status and payees, offer tags
((string-match-p beancount-valid-prefix-for-tag-completion-regex (thing-at-point 'line))
(delq nil
(mapcar (lambda (c)
(and (string-prefix-p (substring arg 1) c) c))
beancount-tags-cache)))
;; if line starts with accounts and amounts, offer commodities
((string-match-p (concat "^[ ]+[A-Za-z0-9-_:]+[ ]+[-]?[0-9,.]+[ ]+" beancount-currency-regexp) (thing-at-point 'line))
(delq nil
(mapcar (lambda (c)
(and (string-prefix-p arg c) c))
beancount-commodities-cache)))
;; if line is empty, offer accounts
((or (string-match-p "^\\s-+" (thing-at-point 'line))
;; if the preceding text is allowed before an account, offer accounts
;; TODO: Not yet working!
(string-match-p beancount-timestamped-accounts-regexp (thing-at-point 'line)))
(delq nil
(mapcar (lambda (c)
(message c)
(and (string-prefix-p arg c) c))
beancount-accounts-cache)))
))))
;;;###autoload
(define-derived-mode beancount-mode text-mode "Beancount"
"A mode for editing beancount files.
\\{beancount-mode-map}"
:init-value nil
:lighter " Beancount"
:group 'beancount
;; customize font-lock for beancount.
(set-syntax-table beancount-mode-syntax-table)
(font-lock-add-keywords nil beancount-font-lock-directives)
(or beancount-accounts-cache
(setq beancount-accounts-cache (beancount-get-accounts-in-buffer))) ;-new
(or beancount-payees-cache
(setq beancount-payees-cache (beancount-get-payees-in-buffer)))
(or beancount-commodities-cache
(setq beancount-commodities-cache (beancount-get-commodities-in-buffer)))
(or beancount-tags-cache
(setq beancount-tags-cache (beancount-get-tags-in-buffer)))
)
(provide 'beancount)
;;; beancount-mode.el ends here
Loading…
Cancel
Save