The SSH host key has changed on 8 April, 2022 to this one: SHA256:573uTBSeh74kvOo0HJXi5ijdzRm8me27suzNEDlGyrQ
My .emacs.d directory
You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
my-emacs-d/configuration.org

3771 lines
97 KiB

* Add some directories to ~load-path~
** My own (version controlled) ~lisp~ directory
#+BEGIN_SRC emacs-lisp
(add-to-list 'load-path (expand-file-name
(convert-standard-filename "lisp/")
user-emacs-directory))
(add-to-list 'load-path (expand-file-name
(convert-standard-filename "lisp/nyan-prompt")
user-emacs-directory))
#+END_SRC
** The local site-lisp
…if it exists.
#+BEGIN_SRC emacs-lisp
(let ((site-lisp-dir "/usr/local/share/emacs/site-lisp"))
(when (file-directory-p site-lisp-dir)
(dolist (elt (directory-files site-lisp-dir))
(unless (or (string= elt ".") (string= elt ".."))
(add-to-list 'load-path (expand-file-name elt site-lisp-dir))))))
#+END_SRC
* Load some prerequisites
** Load ~xdg-paths~
#+BEGIN_SRC emacs-lisp
(load "xdg-paths")
#+END_SRC
** Load the tango dark theme
#+BEGIN_SRC emacs-lisp
(load-theme 'tango-dark t)
#+END_SRC
* Emacs configuration
** Set up the package archives
#+BEGIN_SRC emacs-lisp
(require 'package)
(add-to-list 'package-archives
'("gnu" . "https://elpa.gnu.org/packages/"))
(add-to-list 'package-archives
'("melpa-stable" . "https://stable.melpa.org/packages/") t)
(add-to-list 'package-archives
'("melpa" . "https://melpa.org/packages/") t)
(add-to-list 'package-archives
'("org" . "https://orgmode.org/elpa/") t)
(package-initialize)
#+END_SRC
** Configure ~use-package~ and preload ~bind-key~
#+BEGIN_SRC emacs-lisp
(unless (package-installed-p 'use-package)
(package-refresh-contents)
(package-install 'use-package))
(require 'use-package)
(customize-set-variable 'use-package-always-ensure t)
(customize-set-variable 'use-package-verbose nil)
(use-package bind-key)
#+END_SRC
** Install and configure Quelpa
#+BEGIN_SRC emacs-lisp
(unless (package-installed-p 'quelpa)
(with-temp-buffer
(url-insert-file-contents "https://raw.githubusercontent.com/quelpa/quelpa/master/quelpa.el")
(eval-buffer)
(quelpa-self-upgrade)))
#+END_SRC
*** And quelpa-use-package, too!
#+BEGIN_SRC emacs-lisp
(quelpa
'(quelpa-use-package
:fetcher git
:url "https://github.com/quelpa/quelpa-use-package.git"))
(require 'quelpa-use-package)
#+END_SRC
** Set up the really basic things
#+BEGIN_SRC emacs-lisp
(use-package emacs
:ensure nil
:custom
(user-full-name "Gergely Polonkai")
(user-mail-address "gergely@polonkai.eu")
(kill-read-only-ok t)
(use-dialog-box nil))
#+END_SRC
** Set up my personal keymap
I set it up early so i can use it in ~use-package~ calls immediately.
#+BEGIN_SRC emacs-lisp
(defvar gpolonkai/pers-map (make-sparse-keymap)
"My own, personal, keymap!")
(define-prefix-command 'gpolonkai/pers-map)
(define-key ctl-x-map "t" 'gpolonkai/pers-map)
(define-key global-map (kbd "C-t") 'gpolonkai/pers-map)
#+END_SRC
** I really don’t want to type more than i really must…
#+BEGIN_SRC emacs-lisp
(defalias 'yes-or-no-p 'y-or-n-p)
#+END_SRC
** Set UTF-8 as the default encoding
Just to make sure, although most Linux DEs do this for me.
#+BEGIN_SRC emacs-lisp
(set-language-environment "UTF-8")
(set-default-coding-systems 'utf-8)
#+END_SRC
** Set Org’s main directory
Since a lot of packages use it as their base (org-projectile, org-caldav, etc.), i need
it as early as possible.
#+BEGIN_SRC emacs-lisp
(setq org-directory (expand-file-name "NextCloud/orgmode" user-documents-directory))
#+END_SRC
* Custom commands and functions
** Utility functions
*** Check if something is ~nil~ or a list of strings
#+BEGIN_SRC emacs-lisp
(defun nil-or-list-of-strings-p (var)
"Return t if VAR is either nil or a list holding only strings."
(or (null var)
(not (null (delq nil
(mapcar (lambda (x) (and (stringp x) x)) var))))))
#+END_SRC
*** Get the number at point
#+BEGIN_SRC emacs-lisp
(defun get-number-at-point ()
(interactive)
(skip-chars-backward "0123456789.-")
(or (looking-at "[0123456789.-]+")
(error "No number at point"))
(string-to-number (match-string 0)))
#+END_SRC
*** Round number at point to the given decimals
#+BEGIN_SRC emacs-lisp
(defun round-number-at-point-to-decimals (decimal-count)
(interactive "NDecimal count: ")
(let ((mult (expt 10 decimal-count)))
(replace-match (number-to-string
(/
(fround
(*
mult
(get-number-at-point)))
mult)))))
#+END_SRC
*** Make a backup filename under ~user-emacs-cache-directory~
Taken from [[http://ergoemacs.org/emacs/emacs_set_backup_into_a_directory.html][Xah’s site]].
#+BEGIN_SRC emacs-lisp
(defun xah/backup-file-name (fpath)
"Return a new file path for FPATH under `user-emacs-cache-directory'"
(let* ((backup-root-dir (expand-file-name "backup" user-emacs-cache-directory))
(file-path (replace-regexp-in-string "[A-Za-z]:" "" fpath))
(backup-file-path (replace-regexp-in-string "//" "/" (concat backup-root-dir file-path "~"))))
(make-directory (file-name-directory backup-file-path) (file-name-directory backup-file-path))
backup-file-path))
#+END_SRC
*** Run a function on a region
#+BEGIN_SRC emacs-lisp
(defun func-region (start end func)
"Run a function over the region between START and END in current buffer."
(save-excursion
(let ((text (delete-and-extract-region start end)))
(insert (funcall func text)))))
#+END_SRC
** Check if we are running under Termux
We need to do things differently, if so.
#+BEGIN_SRC emacs-lisp
(defun termux-p ()
"Check if Emacs is running under Termux."
(string-match-p
(regexp-quote "/com.termux/")
(expand-file-name "~")))
#+END_SRC
** Misc text manipulation functions
*** Delete the current line
#+BEGIN_SRC emacs-lisp
(defun gpolonkai/delete-current-line ()
"Kill the whole line on which point is."
(interactive)
(beginning-of-line)
(kill-line 1))
#+END_SRC
*** Duplicate current line
#+BEGIN_SRC emacs-lisp
(defun gpolonkai/duplicate-line()
"Duplicate line at point."
(interactive)
(save-excursion
(move-beginning-of-line 1)
(kill-line)
(yank)
(open-line 1)
(forward-line 1)
(yank)))
#+END_SRC
*** Toggle case of character at point
Based on [[http://ergoemacs.org/emacs/modernization_upcase-word.html][Xah’s toggle letter case defun version 2015-12-22]]
#+BEGIN_SRC emacs-lisp
(defun toggle-char-case (arg-move-point)
"Toggle the case of the char after point.
If prefix argument ARG-MOVE-POINT is non-nil, move point after the char."
(interactive "P")
(let ((case-fold-search nil))
(cond
((looking-at "[[:lower:]]") (upcase-region (point) (1+ (point))))
((looking-at "[[:upper:]]") (downcase-region (point) (1+ (point)))))
(cond
(arg-move-point (right-char)))))
#+END_SRC
*** Open a new line below
Copied from http://whattheemacsd.com/editing-defuns.el-01.html
#+BEGIN_SRC emacs-lisp
(defun open-line-below ()
"Open a new line below point."
(interactive)
(end-of-line)
(newline)
(indent-for-tab-command))
#+END_SRC
*** Open a new line above
#+BEGIN_SRC emacs-lisp
(defun open-line-above ()
"Open a new line above point."
(interactive)
(beginning-of-line)
(newline)
(forward-line -1)
(indent-for-tab-command))
#+END_SRC
*** TODO Kill or copy the whole line
Got from Xah’s site (TODO is for adding a link here.)
#+BEGIN_SRC emacs-lisp
(defun æ-kill-or-copy-whole-line (kill)
"Kill or copy the whole line point is on.
If KILL is non-nil, the line gets killed. Otherwise, it gets just
copied to the kill ring."
(interactive "P")
(if kill
(kill-whole-line)
(let ((beginning (progn (beginning-of-line) (point)))
(end (progn (end-of-line) (point))))
(copy-region-as-kill beginning end))))
#+END_SRC
*** Enclose region in a specific character
#+BEGIN_SRC emacs-lisp
(defun gpolonkai/enclose-region (character &optional start end)
"Enclose region between CHARACTER.
If region is empty and neither START nor END is set, simply inserts CHARACTER
two times and moves point between them.
If character is present in `insert-pair-alist', this function will enclose
region in the corresponding pair. In this case, CHARACTER must be the opening
member of the pair."
(interactive "cWhat character? \nr")
(let ((open character)
(close character)
(pair (assq character insert-pair-alist)))
(if pair
(if (nth 2 pair)
(setq open (nth 1 pair) close (nth 2 pair))
(setq open (nth 0 pair) close (nth 1 pair))))
(unless (and open close)
(setq open character
close character))
(unless (use-region-p)
(setq start (point)
end (point)))
(save-excursion
(goto-char end)
(insert-char close)
(goto-char start)
(insert-char open))
(unless (use-region-p)
(forward-char))))
#+END_SRC
*** Convert ~camelCase~ to ~snake_case~
#+BEGIN_SRC emacs-lisp
(defun camel-to-snake-case (arg)
"Convert a camelCase word to snake_case.
If the prefix argument ARG is non-nil, convert the text to uppercase."
(interactive "p")
(progn
(let ((start (region-beginning))
(end (region-end))
(case-fold-search nil)
(had-initial-underscore nil))
(goto-char start)
(when (looking-at "_") (setq had-initial-underscore t))
(while (re-search-forward "\\([A-Z]\\)" end t)
(replace-match "_\\1")
(setq end (1+ end)))
(if arg
(upcase-region start end)
(downcase-region start end))
(goto-char start)
(unless had-initial-underscore (delete-char 1)))))
#+END_SRC
*** Insert two spaces after specific characters
#+BEGIN_SRC emacs-lisp
(defun org-space-key (&optional arg)
"Insert two spaces after a period.
ARG will be passed down verbatim to `self-insert-command'"
(interactive "p")
(when (looking-back "[.!?…]" nil)
(call-interactively 'self-insert-command arg))
(call-interactively 'self-insert-command arg))
#+END_SRC
*** Fill or unfill a paragraph
:PROPERTIES:
:SOURCE: http://pages.sachachua.com/.emacs.d/Sacha.html
:END:
#+BEGIN_SRC emacs-lisp
(defun sachachua/fill-or-unfill-paragraph (&optional unfill region)
"Fill (or unfill, if UNFILL is non-nil) paragraph (or REGION)."
(interactive (progn
(barf-if-buffer-read-only)
(list (if current-prefix-arg 'unfill) t)))
(let ((fill-column (if unfill (point-max) fill-column)))
(fill-paragraph nil region)))
#+END_SRC
*** Swap occurences of strings
:PROPERTIES:
:SOURCE: http://emacs.stackexchange.com/a/27170/507
:END:
#+BEGIN_SRC emacs-lisp
(defun so/query-swap-strings (from-string
to-string
&optional delimited start end)
"Swap occurrences of FROM-STRING and TO-STRING.
DELIMITED, START, and END are passed down verbatim to `perform-replace'."
(interactive
(let ((common
(query-replace-read-args
(concat "Query swap"
(if current-prefix-arg
(if (eq current-prefix-arg '-) " backward" " word")
"")
(if (use-region-p) " in region" ""))
nil)))
(list (nth 0 common) (nth 1 common) (nth 2 common)
(if (use-region-p) (region-beginning))
(if (use-region-p) (region-end)))))
(perform-replace
(concat "\\(" (regexp-quote from-string) "\\)\\|" (regexp-quote to-string))
`(replace-eval-replacement replace-quote
(if (match-string 1)
,to-string
,from-string))
t t delimited nil nil start end))
#+END_SRC
*** Insert the current file’s name at point
:PROPERTIES:
:SOURCE: http://mbork.pl/2019-02-17_Inserting_the_current_file_name_at_point
:END:
#+BEGIN_SRC emacs-lisp
(defun insert-current-file-name-at-point (&optional full-path)
"Insert the current filename at point.
With prefix argument, use full path."
(interactive "P")
(let* ((buffer
(if (minibufferp)
(window-buffer
(minibuffer-selected-window))
(current-buffer)))
(filename (buffer-file-name buffer)))
(if filename
(insert (if full-path filename (file-name-nondirectory filename)))
(error (format "Buffer %s is not visiting a file" (buffer-name buffer))))))
#+END_SRC
** Navigation
*** Move to different beginnings of the current line
Inspired by Bozhidar Batsov's [[http://emacsredux.com/blog/2013/05/22/smarter-navigation-to-the-beginning-of-a-line/][solution]].
#+BEGIN_SRC emacs-lisp
(defun gpolonkai/move-to-beginning-of-line ()
"Move to different beginnings of the line.
These are, in order:
- beginning of the visual line if `visual-line-mode' is active,
- the first non-whitespace (indentation),
- the actual beginning of the line.
This function will jump between the first character and the
indentation if used multiple times."
(interactive)
(let ((last-pos (point)))
(when visual-line-mode
(beginning-of-visual-line))
(when (= (point) last-pos)
(back-to-indentation))
(when (= (point) last-pos)
(beginning-of-line))
(when (and (eq major-mode 'org-mode)
(= (point) last-pos))
(org-beginning-of-line))
(when (= (point) last-pos)
(back-to-indentation))))
#+END_SRC
*** Move to the different ends of the current line
#+BEGIN_SRC emacs-lisp
(defun gpolonkai/move-to-end-of-line ()
"Move to the end of the line.
If `visual-line-mode' is active, jump to the end of the visual
line first. Then jump to the actual end of the line."
(interactive)
(let ((last-pos (point)))
(when visual-line-mode
(end-of-visual-line))
(when (= (point) last-pos)
(end-of-line))
(when (and (eq major-mode 'org-mode)
(= (point) last-pos))
(org-end-of-line))))
#+END_SRC
*** Move to the next occurence of a character within the same line
#+begin_src emacs-lisp
(defun goto-next-char (chr)
(interactive "c")
(let ((substr (char-to-string chr))
(end (save-excursion (end-of-line) (point))))
(when (search-forward substr end t)
(backward-char))))
#+end_src
** File manipulation
*** Rename the current file
:PROPERTIES:
:SOURCE: http://whattheemacsd.com/file-defuns.el-01.html
:END:
#+BEGIN_SRC emacs-lisp
(defun rename-current-buffer-file ()
"Renames current buffer and file it is visiting."
(interactive)
(let ((name (buffer-name))
(filename (buffer-file-name)))
(if (not (and filename (file-exists-p filename)))
(error "Buffer '%s' is not visiting a file!" name)
(let ((new-name (read-file-name "New name: " filename)))
(if (get-buffer new-name)
(error "A buffer named '%s' already exists!" new-name)
(rename-file filename new-name 1)
(rename-buffer new-name)
(set-visited-file-name new-name)
; TODO: this is suspicious for me…
(set-buffer-modified-p nil)
(message "File '%s' successfully renamed to '%s'"
name (file-name-nondirectory new-name)))))))
#+END_SRC
*** Delete the current file
:PROPERTIES:
:SOURCE: http://whattheemacsd.com/file-defuns.el-02.html
:END:
#+BEGIN_SRC emacs-lisp
(defun delete-current-buffer-file ()
"Remove file connected to current buffer and kill the buffer."
(interactive)
(let ((filename (buffer-file-name))
(name (buffer-name))
(buffer (current-buffer)))
(if (not (and filename (file-exists-p filename)))
(kill-buffer buffer)
(when (yes-or-no-p "Are you sure you want to remove this file? ")
(delete-file filename)
(kill-buffer buffer)
(message "File '%s' successfully removed" filename)))))
#+END_SRC
*** Allow reopening the previously closed file
#+BEGIN_SRC emacs-lisp
(defvar gpolonkai/last-killed-buffer-file-name
nil
"The last killed buffer.
Used by `gpolonkai/save-killed-buffer-filename' and `gpolonkai/undo-buffer-kill'.")
(defun gpolonkai/save-killed-buffer-filename ()
"Save the filename of the killed buffer in `gpolonkai/last-killed-buffer-file-name'."
(let ((filename (buffer-file-name)))
(unless filename
(setq gpolonkai/last-killed-buffer-file-name (buffer-file-name)))))
(add-hook 'kill-buffer-hook 'gpolonkai/save-killed-buffer-filename)
(defun gpolonkai/undo-buffer-kill ()
"Undo killing the last buffer.
Esentially it visits the file again."
(interactive)
(if gpolonkai/last-killed-buffer-file-name
(progn
(find-file gpolonkai/last-killed-buffer-file-name)
(setq gpolonkai/last-killed-buffer-file-name nil))
(message "The buffer last killed didn’t visit a file.")))
#+END_SRC
*** Open this file as another user
#+BEGIN_SRC emacs-lisp
(defun open-this-file-as-other-user (user)
"Edit current file as USER, using `tramp' and `sudo'.
If the current buffer is not visiting a file, prompt for a file
name."
(interactive "sEdit as user (default: root): ")
(when (string= "" user)
(setq user "root"))
(let* ((filename (or buffer-file-name
(read-file-name (format "Find file (as %s): "
user))))
(tramp-path (concat (format "/sudo:%s@localhost:" user) filename)))
(if buffer-file-name
(find-alternate-file tramp-path)
(find-file tramp-path))))
#+END_SRC
*** Open my own ~init.el~
#+BEGIN_SRC emacs-lisp
(defun gpolonkai/visit-init-file ()
"Open the init file."
(interactive)
(find-file-other-window (expand-file-name "configuration.org" user-emacs-directory)))
#+END_SRC
*** Open my Org-mode index file
#+BEGIN_SRC emacs-lisp
(defun gpolonkai/visit-org-index ()
"Visit the root of Org-mode notes."
(interactive)
(find-file-other-window (expand-file-name "index.org" org-directory)))
#+END_SRC
** Frame manipulation
*** Hidden modeline mode
:PROPERTIES:
:SOURCE: http://emacs-doctor.com/emacs-strip-tease.html
:END:
To temporarily hide the mode line.
#+BEGIN_SRC emacs-lisp
(defvar hidden-mode-line-mode nil)
(defvar hide-mode-line nil)
(define-minor-mode hidden-mode-line-mode
"Minor mode to hide the mode-line in the current buffer."
:init-value nil
:global nil
:variable hidden-mode-line-mode
:group 'editing-basics
(if hidden-mode-line-mode
(setq hide-mode-line mode-line-format
mode-line-format nil)
(setq mode-line-format hide-mode-line
hide-mode-line nil))
(force-mode-line-update)
(redraw-display)
(when (and (called-interactively-p 'interactive)
hidden-mode-line-mode)
(run-with-idle-timer
0 nil 'message
(concat "Hidden Mode Line Mode enabled. "
"Use M-x hidden-mode-line-mode to make mode-line appear."))))
#+END_SRC
** Window manipulation
*** Transpose windows
#+BEGIN_SRC emacs-lisp
(defun transpose-windows (arg)
"Transpose the buffers shown in two windows."
(interactive "p")
(let ((selector (if (>= arg 0) 'next-window 'previous-window)))
(while (/= arg 0)
(let ((this-win (window-buffer))
(next-win (window-buffer (funcall selector))))
(set-window-buffer (selected-window) next-win)
(set-window-buffer (funcall selector) this-win)
(select-window (funcall selector)))
(setq arg (if (plusp arg) (1- arg) (1+ arg))))))
#+END_SRC
*** Toggle window split between horizontal and vertical
#+BEGIN_SRC emacs-lisp
(defun toggle-window-split ()
(interactive)
(if (= (count-windows) 2)
(let* ((this-win-buffer (window-buffer))
(next-win-buffer (window-buffer (next-window)))
(this-win-edges (window-edges (selected-window)))
(next-win-edges (window-edges (next-window)))
(this-win-2nd (not (and (<= (car this-win-edges)
(car next-win-edges))
(<= (cadr this-win-edges)
(cadr next-win-edges)))))
(splitter
(if (= (car this-win-edges)
(car (window-edges (next-window))))
'split-window-horizontally
'split-window-vertically)))
(delete-other-windows)
(let ((first-win (selected-window)))
(funcall splitter)
(if this-win-2nd (other-window 1))
(set-window-buffer (selected-window) this-win-buffer)
(set-window-buffer (next-window) next-win-buffer)
(select-window first-win)
(if this-win-2nd (other-window 1))))
(error "This works only for two windows!")))
#+END_SRC
*** Scroll up or down in a specific window
#+BEGIN_SRC emacs-lisp
(defun gpolonkai/scroll-window-up (window)
"Scroll WINDOW up as `scroll-up-command' would."
(interactive)
(save-selected-window
(select-window window)
(scroll-up)))
(defun gpolonkai/scroll-window-down (window)
"Scroll WINDOW down as `scroll-down-command' would."
(interactive)
(save-selected-window
(select-window window)
(scroll-down)))
#+END_SRC
*** Bury a window
#+BEGIN_SRC emacs-lisp
(defun gpolonkai/bury-window (window)
"Quit WINDOW without killing it."
(interactive)
(quit-window nil window))
#+END_SRC
** ~c-mode~ related
*** Copy the prototype of the current function
#+BEGIN_SRC emacs-lisp
(defun gpolonkai/copy-func-prototype ()
"Copy the current function's prototype to the kill ring."
(interactive)
(save-excursion
(beginning-of-defun)
(let ((protocopy-begin (point)))
(forward-list)
(let ((protocopy-end (point)))
(kill-ring-save protocopy-begin protocopy-end)))))
#+END_SRC
** Programming related
*** Check if we are inside a string
#+BEGIN_SRC emacs-lisp
(defun gpolonkai/prog-in-string-p ()
"Return t if point is inside a string."
(nth 3 (syntax-ppss)))
#+END_SRC
*** Check if we are inside a comment
#+BEGIN_SRC emacs-lisp
(defun gpolonkai/prog-in-comment-p ()
"Return t if point is inside a comment."
(nth 4 (syntax-ppss)))
#+END_SRC
*** Add code references to Org documents
:PROPERTIES:
:SOURCE: http://www.howardism.org/Technical/Emacs/capturing-content.html
:END:
#+BEGIN_SRC emacs-lisp
(defun ha/org-capture-fileref-snippet (f type headers func-name)
(let* ((code-snippet
(buffer-substring-no-properties (mark) (- (point) 1)))
(file-name (buffer-file-name))
(file-base (file-name-nondirectory file-name))
(line-number (line-number-at-pos (region-beginning)))
(initial-txt (if (null func-name)
(format "From [[file:%s::%s][%s]]:"
file-name line-number file-base)
(format "From ~%s~ (in [[file:%s::%s][%s]]):"
func-name file-name line-number
file-base))))
(format "
%s
,#+BEGIN_%s %s
%s
,#+END_%s" initial-txt type headers code-snippet type)))
(defun ha/org-capture-clip-snippet (f)
"Given a file, F, this captures the currently selected text
within an Org EXAMPLE block and a backlink to the file."
(with-current-buffer (find-buffer-visiting f)
(ha/org-capture-fileref-snippet f "EXAMPLE" "" nil)))
(defun ha/org-capture-code-snippet (f)
"Given a file, F, this captures the currently selected text
within an Org SRC block with a language based on the current mode
and a backlink to the function and the file."
(with-current-buffer (find-buffer-visiting f)
(let ((org-src-mode (replace-regexp-in-string "-mode" "" (format "%s" major-mode)))
(func-name (which-function)))
(ha/org-capture-fileref-snippet f "SRC" org-src-mode func-name))))
#+END_SRC
** ~python-mode~ related
*** Add a docstring to the current thing
…be it a function, class, or a module
#+BEGIN_SRC emacs-lisp
(defun gpolonkai/python-add-docstring ()
"Add a Python docstring to the current thing.
If point is inside a function, add docstring to that. If point
is in a class, add docstring to that. If neither, add docstring
to the beginning of the file."
(interactive)
(save-restriction
(widen)
(beginning-of-defun)
(if (not (looking-at-p "\\(def\\|class\\) "))
(progn
(goto-char (point-min))
(back-to-indentation)
(forward-char)
(while (gpolonkai/prog-in-comment-p)
(forward-line)
(back-to-indentation)
(forward-char)))
(search-forward ":")
(while (or (gpolonkai/prog-in-string-p)
(gpolonkai/prog-in-comment-p))
(search-forward ":")))
(if (eq 1 (count-lines 1 (point)))
(open-line-above)
(open-line-below))
(insert "\"\"\"")
(let ((point-pos (point)))
(open-line-below)
(insert "\"\"\"\n")
(goto-char point-pos))))
#+END_SRC
*** Font-lock variables in f-strings
:PROPERTIES:
:SOURCE: https://emacs.stackexchange.com/a/55186/507
:END:
#+BEGIN_SRC emacs-lisp :tangle no
(with-eval-after-load "python"
(setq python-font-lock-keywords
(append python-font-lock-keywords
'(;; this is the full string.
;; group 1 is the quote type and a closing quote is matched
;; group 2 is the string part
("f\\(['\"]\\{1,3\\}\\)\\([^\\1]+?\\)\\1"
;; these are the {keywords}
("{[^}]*?}"
;; Pre-match form
(progn (goto-char (match-beginning 0)) (match-end 0))
;; Post-match form
(goto-char (match-end 0))
;; face for this match
(0 font-lock-variable-name-face t)))))))
#+END_SRC
** Jinja related
*** Mark a string as translatable
#+BEGIN_SRC emacs-lisp
(defun jinja-mark-translatable (begin end)
(interactive (if (use-region-p)
(list (region-beginning) (region-end))
(list (point-min) (point-max))))
(save-excursion
(goto-char end)
(insert "{% endtrans %}")
(goto-char begin)
(insert "{% trans %}")))
#+END_SRC
*** URL-ify and de-URL-ify region
These functions URL-encode/decode a text. Might be helpful when embedding/extracting data to/from
HTML.
#+BEGIN_SRC emacs-lisp
(defun hex-region (start end)
"urlencode the region between START and END in current buffer."
(interactive "r")
(func-region start end #'url-hexify-string))
(defun unhex-region (start end)
"de-urlencode the region between START and END in current buffer."
(interactive "r")
(func-region start end #'url-unhex-string))
#+END_SRC
** Automatically zone out after 60 seconds
#+BEGIN_SRC emacs-lisp
(defun gpolonkai/zone-enable ()
"Enable zoning out."
(interactive)
(zone-when-idle 60)
(message "I will zone out after idling for 60 seconds."))
#+END_SRC
** Utility functions for editing Zim wiki files
#+BEGIN_SRC emacs-lisp
(defun zim-timestamp ()
(with-temp-buffer
(insert (format-time-string "%Y-%m-%dT%H:%M:%S%z"))
(forward-char -2)
(insert ":")
(buffer-string)))
(defun insert-zim-timestamp ()
(interactive)
(insert (zim-timestamp)))
(defun insert-zim-header ()
(interactive)
(save-excursion
(goto-char (point-min))
(insert
(concat "Content-Type: text/x-zim-wiki\n"
"Wiki-Format: zim 0.4\n"
"Creation-Date: " (zim-timestamp) "\n\n"))))
#+END_SRC
** Utility functions for EDiff
:PROPERTIES:
:SOURCE: http://article.gmane.org/gmane.emacs.orgmode/75222
:END:
EDiff and Org-mode files don’t play nice together…
#+BEGIN_SRC emacs-lisp
(defun f-ediff-org-showhide (buf command &rest cmdargs)
"If buffer BUF exists and in `org-mode', execute COMMAND with CMDARGS."
(when buf
(when (eq (buffer-local-value 'major-mode (get-buffer buf)) 'org-mode)
(save-excursion
(set-buffer buf)
(apply command cmdargs)))))
(defun f-ediff-org-unfold-tree-element ()
"Unfold tree at diff location."
(f-ediff-org-showhide ediff-buffer-A 'org-reveal)
(f-ediff-org-showhide ediff-buffer-B 'org-reveal)
(f-ediff-org-showhide ediff-buffer-C 'org-reveal))
(defun f-ediff-org-fold-tree ()
"Fold tree back to top level."
(f-ediff-org-showhide ediff-buffer-A 'hide-sublevels 1)
(f-ediff-org-showhide ediff-buffer-B 'hide-sublevels 1)
(f-ediff-org-showhide ediff-buffer-C 'hide-sublevels 1))
#+END_SRC
** Leave ~isearch~ at the other end of the matched string
:PROPERTIES:
:SOURCE: http://endlessparentheses.com/leave-the-cursor-at-start-of-match-after-isearch.html
:END:
#+BEGIN_SRC emacs-lisp
(defun isearch-exit-other-end ()
"Exit isearch, at the opposite end of the string."
(interactive)
(isearch-exit)
(goto-char isearch-other-end))
#+END_SRC
** Mark the current match after leaving ~isearch~
:PROPERTIES:
:SOURCE: http://emacs.stackexchange.com/a/31321/507
:END:
#+BEGIN_SRC emacs-lisp
(defun isearch-exit-mark-match ()
"Exit isearch and mark the current match."
(interactive)
(isearch-exit)
(push-mark isearch-other-end)
(activate-mark))
#+END_SRC
** Turn the cursor to purple if Yasnippet’s TAB function would fire
Taken from [[http://pages.sachachua.com/.emacs.d/Sacha.html][Sacha Chua’s config]].
#+BEGIN_SRC emacs-lisp
;; TODO: 'cursor-color is sometimes nil, but why?
(setq default-cursor-color (or (frame-parameter nil 'cursor-color) "#ffd85c"))
(setq yasnippet-can-fire-cursor-color "purple")
;; It will test whether it can expand, if yes, cursor color -> purple.
(defun yasnippet-can-fire-p (&optional field)
"Check if the word before point can be expanded with yasnippet.
TODO: What is FIELD for?"
(interactive)
(setq yas--condition-cache-timestamp (current-time))
(let (templates-and-pos)
(unless (and yas-expand-only-for-last-commands
(not (member last-command yas-expand-only-for-last-commands)))
(setq templates-and-pos (if field
(save-restriction
(narrow-to-region (yas--field-start field)
(yas--field-end field))
(yas--templates-for-key-at-point))
(yas--templates-for-key-at-point))))
(and templates-and-pos (first templates-and-pos))))
(defun sachachua/change-cursor-color-when-can-expand (&optional field)
"Change cursor color if the text before point can be expanded with yasnippet.
TODO: What is FIELD for?"
(interactive)
(when (eq last-command 'self-insert-command)
(set-cursor-color (if (sachachua/can-expand)
yasnippet-can-fire-cursor-color
default-cursor-color))))
(defun sachachua/can-expand ()
"Return t if right after an expandable thing."
(or (abbrev--before-point) (yasnippet-can-fire-p)))
#+END_SRC
** Org mode stuff
*** Filter out tasks from the Org agenda if they have a specific priority
The whole idea comes from [[https://blog.aaronbieber.com/2016/09/24/an-agenda-for-life-with-org-mode.html][here]], which i use almost verbatim. This is also the reason it has the
~air-~ prefix.
#+begin_src emacs-lisp
(defun air-org-skip-subtree-if-priority (priority)
"Skip an agenda subtree if it has a priority of PRIORITY.
PRIORITY may be one of the characters ?A, ?B, or ?C."
(let ((subtree-end (save-excursion (org-end-of-subtree t)))
(pri-value (* 1000 (- org-lowest-priority priority)))
(pri-current (org-get-priority (thing-at-point 'line t))))
(if (= pri-value pri-current)
subtree-end
nil)))
#+end_src
*** Filter out habits from the Org agenda