feat(emacs): org-habit-stats & telega.el

This commit is contained in:
Pavel Korytov 2023-01-07 23:13:43 +03:00
parent eb20f672eb
commit 2009a87910
3 changed files with 315 additions and 30 deletions

View file

@ -5,6 +5,9 @@
"emacs-vterm"
"hledger"
"imagemagick"
"font-gnu-freefont"
"font-gnu-unifont"
"tdlib-1.8.10"
"yt-dlp"
"mpv"
"rdrview"

View file

@ -267,7 +267,8 @@
slime
forge
deadgrep
vc-annonate)))
vc-annonate
telega)))
(use-package avy
:straight t
@ -2941,6 +2942,7 @@ Returns (<buffer> . <workspace-index>) or nil."
(concat org-directory "/projects"))))))
(setq org-agenda-files
`("inbox.org"
"misc/habit.org"
,@project-files))
(setq org-refile-targets
`(,@(mapcar
@ -2978,8 +2980,10 @@ Returns (<buffer> . <workspace-index>) or nil."
,(concat "* TODO %:elfeed-entry-title\n"
"/Entered on/ %U\n"
"%a\n"))
("n" "note" entry (file my/generate-inbox-note-name)
,(concat "* %?\n"
("n" "note" plain (file my/generate-inbox-note-name)
,(concat "#+TODO: PROCESSED(p)\n"
"\n"
"* %?\n"
"/Entered on/ %U"))))
(use-package org-ql
@ -2989,17 +2993,59 @@ Returns (<buffer> . <workspace-index>) or nil."
:repo "alphapapa/org-ql"
:files (:defaults (:exclude "helm-org-ql.el"))))
(use-package org-habit-stats
:straight (:host github :repo "ml729/org-habit-stats")
:after (org)
:config
(general-define-key
:keymaps '(org-habit-stats-mode-map)
:states '(normal emacs)
"q" #'org-habit-stats-exit
"<" #'org-habit-stats-calendar-scroll-left
">" #'org-habit-stats-calendar-scroll-right
"[" #'org-habit-stats-scroll-graph-left
"]" #'org-habit-stats-scroll-graph-right
"{" #'org-habit-stats-scroll-graph-left-big
"}" #'org-habit-stats-scroll-graph-right-big
"." #'org-habit-stats-view-next-habit
"," #'org-habit-stats-view-previous-habit))
(defun my/org-match-at-point-p (match)
"Return non-nil if headline at point matches MATCH.
Here MATCH is a match string of the same format used by
`org-tags-view'."
(funcall (cdr (org-make-tags-matcher match))
(org-get-todo-state)
(org-get-tags-at)
(org-reduced-level (org-current-level))))
(defun my/org-agenda-skip-without-match (match)
"Skip current headline unless it matches MATCH.
Return nil if headline containing point matches MATCH (which
should be a match string of the same format used by
`org-tags-view'). If headline does not match, return the
position of the next headline in current buffer.
Intended for use with `org-agenda-skip-function', where this will
skip exactly those headlines that do not match."
(save-excursion
(unless (org-at-heading-p) (org-back-to-heading))
(let ((next-headline (save-excursion
(or (outline-next-heading) (point-max)))))
(if (my/org-match-at-point-p match) nil next-headline))))
(defun my/org-scheduled-get-time ()
(let ((scheduled (org-get-scheduled-time (point))))
(if scheduled
(format-time-string "%Y-%m-%d" scheduled)
"")))
(setq org-agenda-hide-tags-regexp (rx (or "org" "refile")))
(setq org-agenda-hide-tags-regexp (rx (or "org" "refile" "habit")))
(setq org-agenda-custom-commands
`(("p" "My outline"
((agenda "")
((agenda "" ((org-agenda-skip-function '(my/org-agenda-skip-without-match "-habit"))))
(tags-todo "inbox"
((org-agenda-overriding-header "Inbox")
(org-agenda-prefix-format " %i %-12:c")
@ -3007,7 +3053,11 @@ Returns (<buffer> . <workspace-index>) or nil."
(tags-todo "+waitlist+SCHEDULED<=\"<+14d>\""
((org-agenda-overriding-header "Waitlist")
(org-agenda-hide-tags-regexp "waitlist")
(org-agenda-prefix-format " %i %-12:c %-12(my/org-scheduled-get-time)")))))))
(org-agenda-prefix-format " %i %-12:c %-12(my/org-scheduled-get-time)")))
(tags-todo "habit+SCHEDULED<=\"<+0d>\""
((org-agenda-overriding-header "Habits")
(org-agenda-prefix-format " %i %-12:c")
(org-agenda-hide-tags-regexp ".")))))))
(my-leader-def
:infix "o"
@ -3755,14 +3805,15 @@ With ARG, repeats or can move backward if negative."
(defun my/org-file-open ()
(interactive)
(let* ((files
(append
'("inbox.org" "contacts.org")
(mapcar (lambda (f)
(concat "projects/" f))
(seq-filter
(lambda (f) (not (member f '("." ".."))))
(directory-files
(concat org-directory "/projects")))))))
(thread-last
'("projects" "misc")
(mapcar (lambda (f)
(directory-files (concat org-directory "/" f) t (rx ".org" eos))))
(apply #'append)
(mapcar (lambda (file)
(string-replace (concat org-directory "/") "" file)))
(append
'("inbox.org" "contacts.org")))))
(find-file
(concat org-directory "/"
(completing-read "Org file: " files)))))
@ -5067,6 +5118,7 @@ ENTRY is an instance of `elfeed-entry'."
(general-define-key
:states '(emacs normal)
:keymaps 'emms-browser-mode-map
"gr" #'emms-browse-by-artist
"gl" 'lyrics-fetcher-emms-browser-show-at-point
"gC" 'lyrics-fetcher-emms-browser-fetch-covers-at-point
"go" 'lyrics-fetcher-emms-browser-open-large-cover-at-point)
@ -5233,6 +5285,71 @@ ENTRY is an instance of `elfeed-entry'."
(use-package ement
:straight (:host github :repo "alphapapa/ement.el"))
(use-package telega
:straight t
:commands (telega)
:init
(my-leader-def "a l" (my/command-in-persp "telega" "telega" 3 (telega)))
(my/use-doom-colors
(telega-button-active :foreground (doom-color 'base0)
:background (doom-color 'cyan))
(telega-webpage-chat-link :foreground (doom-color 'base0)
:background (doom-color 'fg)))
:config
(general-define-key
:keymaps '(telega-root-mode-map telega-chat-mode-map)
:states '(normal)
"gp" telega-prefix-map)
(general-define-key
:keymaps '(telega-msg-button-map)
"<SPC>" nil)
(general-define-key
:keymaps '(telega-chat-mode-map)
"C-<return>" #'newline)
(my/persp-add-rule
telega-root-mode 3 "telega"
telega-chat-mode 3 "telega"
telega-image-mode 3 "telega"
telega-webpage-mode 3 "telega"))
(defun my/telega-server-build ()
(interactive)
(setq telega-server-libs-prefix
(string-trim
(shell-command-to-string "guix build tdlib-1.8.10")))
(telega-server-build "CC=gcc"))
(add-hook 'telega-load-hook #'telega-mode-line-mode)
(setq telega-mode-line-string-format
'("["
(:eval
(telega-mode-line-online-status))
(:eval
(when telega-use-tracking-for
(telega-mode-line-tracking)))
(:eval
(telega-mode-line-unread-unmuted))
(:eval
(telega-mode-line-mentions 'messages))
"]"))
(defun my/telega-chat-setup ()
(set (make-local-variable 'company-backends)
(append (list telega-emoji-company-backend
'telega-company-username
'telega-company-hashtag
'telega-company-markdown-precode)
(when (telega-chat-bot-p telega-chatbuf--chat)
'(telega-company-botcmd))))
(company-mode 1))
(add-hook 'telega-chat-mode-hook #'my/telega-chat-setup)
(defun my/telega-online-status ()
(derived-mode-p 'telega-root-mode 'telega-chat-mode
'telega-image-mode 'telega-webpage-mode))
(setq telega-online-status-function #'my/telega-online-status)
(use-package google-translate
:straight t
:if (not my/remote-server)
@ -5497,6 +5614,11 @@ ENTRY is an instance of `elfeed-entry'."
"<ORG-JOURNAL>")
((string-match-p (rx bos "EXWM") name)
"<EXWM>")
((string-match-p (rx bos "*Org-Habit") name)
"<ORG>")
((with-current-buffer (get-buffer name)
(derived-mode-p 'telega-root-mode 'telega-chat-mode))
"<TELEGA>")
(t name)))
(defun my/elcord-buffer-details-format-functions ()
@ -5515,6 +5637,8 @@ ENTRY is an instance of `elfeed-entry'."
:config
(setq elcord-buffer-details-format-function #'my/elcord-buffer-details-format-functions)
(advice-add 'elcord--try-update-presence :filter-args #'my/elcord-update-presence-mask-advice)
(add-to-list 'elcord-mode-text-alist '(telega-chat-mode . "Telega Chat"))
(add-to-list 'elcord-mode-text-alist '(telega-root-mode . "Telega Root"))
(elcord-mode))
(use-package snow

190
Emacs.org
View file

@ -494,7 +494,8 @@ Do ex search in other buffer. Like =*=, but switch to other buffer and search th
slime
forge
deadgrep
vc-annonate)))
vc-annonate
telega)))
#+end_src
*** Avy
[[https://github.com/abo-abo/avy][Avy]] is a package that helps navigate Emacs in a tree-like manner.
@ -4121,6 +4122,7 @@ Also, my project structure is somewhat chaotic, so I have an =.el= file in the o
(concat org-directory "/projects"))))))
(setq org-agenda-files
`("inbox.org"
"misc/habit.org"
,@project-files))
(setq org-refile-targets
`(,@(mapcar
@ -4167,8 +4169,10 @@ Settings for Org capture mode. The goal here is to have a non-disruptive process
,(concat "* TODO %:elfeed-entry-title\n"
"/Entered on/ %U\n"
"%a\n"))
("n" "note" entry (file my/generate-inbox-note-name)
,(concat "* %?\n"
("n" "note" plain (file my/generate-inbox-note-name)
,(concat "#+TODO: PROCESSED(p)\n"
"\n"
"* %?\n"
"/Entered on/ %U"))))
#+end_src
@ -4187,9 +4191,64 @@ None of that worked out, but I'll keep the package here in case I have some more
:repo "alphapapa/org-ql"
:files (:defaults (:exclude "helm-org-ql.el"))))
#+end_src
**** Tracking habits
Let's see how this goes.
References:
- https://orgmode.org/manual/Tracking-your-habits.html
[[https://github.com/ml729/org-habit-stats][org-habit-stats]] is a pretty nice package. Using my fork until my PR is merged.
#+begin_src emacs-lisp
(use-package org-habit-stats
:straight (:host github :repo "ml729/org-habit-stats")
:after (org)
:config
(general-define-key
:keymaps '(org-habit-stats-mode-map)
:states '(normal emacs)
"q" #'org-habit-stats-exit
"<" #'org-habit-stats-calendar-scroll-left
">" #'org-habit-stats-calendar-scroll-right
"[" #'org-habit-stats-scroll-graph-left
"]" #'org-habit-stats-scroll-graph-right
"{" #'org-habit-stats-scroll-graph-left-big
"}" #'org-habit-stats-scroll-graph-right-big
"." #'org-habit-stats-view-next-habit
"," #'org-habit-stats-view-previous-habit))
#+end_src
**** Custom agendas
Some custom agendas to fit my workflow.
See [[https://emacs.stackexchange.com/questions/18179/org-agenda-command-with-org-agenda-filter-by-tag-not-working][this answer]] at Emacs StackExchange for filtering the =agenda= block by tag:
#+begin_src emacs-lisp
(defun my/org-match-at-point-p (match)
"Return non-nil if headline at point matches MATCH.
Here MATCH is a match string of the same format used by
`org-tags-view'."
(funcall (cdr (org-make-tags-matcher match))
(org-get-todo-state)
(org-get-tags-at)
(org-reduced-level (org-current-level))))
(defun my/org-agenda-skip-without-match (match)
"Skip current headline unless it matches MATCH.
Return nil if headline containing point matches MATCH (which
should be a match string of the same format used by
`org-tags-view'). If headline does not match, return the
position of the next headline in current buffer.
Intended for use with `org-agenda-skip-function', where this will
skip exactly those headlines that do not match."
(save-excursion
(unless (org-at-heading-p) (org-back-to-heading))
(let ((next-headline (save-excursion
(or (outline-next-heading) (point-max)))))
(if (my/org-match-at-point-p match) nil next-headline))))
#+end_src
And the agendas themselves:
#+begin_src emacs-lisp
(defun my/org-scheduled-get-time ()
(let ((scheduled (org-get-scheduled-time (point))))
@ -4197,11 +4256,11 @@ Some custom agendas to fit my workflow.
(format-time-string "%Y-%m-%d" scheduled)
"")))
(setq org-agenda-hide-tags-regexp (rx (or "org" "refile")))
(setq org-agenda-hide-tags-regexp (rx (or "org" "refile" "habit")))
(setq org-agenda-custom-commands
`(("p" "My outline"
((agenda "")
((agenda "" ((org-agenda-skip-function '(my/org-agenda-skip-without-match "-habit"))))
(tags-todo "inbox"
((org-agenda-overriding-header "Inbox")
(org-agenda-prefix-format " %i %-12:c")
@ -4209,7 +4268,11 @@ Some custom agendas to fit my workflow.
(tags-todo "+waitlist+SCHEDULED<=\"<+14d>\""
((org-agenda-overriding-header "Waitlist")
(org-agenda-hide-tags-regexp "waitlist")
(org-agenda-prefix-format " %i %-12:c %-12(my/org-scheduled-get-time)")))))))
(org-agenda-prefix-format " %i %-12:c %-12(my/org-scheduled-get-time)")))
(tags-todo "habit+SCHEDULED<=\"<+0d>\""
((org-agenda-overriding-header "Habits")
(org-agenda-prefix-format " %i %-12:c")
(org-agenda-hide-tags-regexp ".")))))))
#+end_src
**** Other settings
Hotkeys
@ -4688,7 +4751,6 @@ Advise =deft-parse-title= to be able to extract title from the Org property:
(with-eval-after-load 'deft
(advice-add #'deft-parse-title :around #'my/deft-parse-title-around))
#+end_src
*** Review workflow
UPD <2022-03-27 Sun>. Out of action for now
@ -4936,7 +4998,7 @@ A template looks like this:
*** org-contacts
=org-contacts= is a package to store contacts in an org file.
It seems like the package has been somewhat revived in the recent months. It used things like =lexical-let= when I first found it.
It seems the package has been somewhat revived in the recent months. It used things like =lexical-let= when I first found it.
#+begin_src emacs-lisp
(use-package org-contacts
@ -5248,14 +5310,15 @@ A function to open a file from =org-directory=, excluding a few directories like
(defun my/org-file-open ()
(interactive)
(let* ((files
(append
'("inbox.org" "contacts.org")
(mapcar (lambda (f)
(concat "projects/" f))
(seq-filter
(lambda (f) (not (member f '("." ".."))))
(directory-files
(concat org-directory "/projects")))))))
(thread-last
'("projects" "misc")
(mapcar (lambda (f)
(directory-files (concat org-directory "/" f) t (rx ".org" eos))))
(apply #'append)
(mapcar (lambda (file)
(string-replace (concat org-directory "/") "" file)))
(append
'("inbox.org" "contacts.org")))))
(find-file
(concat org-directory "/"
(completing-read "Org file: " files)))))
@ -7119,6 +7182,7 @@ My package for fetching EMMS lyrics and album covers.
(general-define-key
:states '(emacs normal)
:keymaps 'emms-browser-mode-map
"gr" #'emms-browse-by-artist
"gl" 'lyrics-fetcher-emms-browser-show-at-point
"gC" 'lyrics-fetcher-emms-browser-fetch-covers-at-point
"go" 'lyrics-fetcher-emms-browser-open-large-cover-at-point)
@ -7402,6 +7466,93 @@ Send =/detach= to all servers. Kinda strange that there's no such function alrea
(use-package ement
:straight (:host github :repo "alphapapa/ement.el"))
#+end_src
*** Telega
[[https://github.com/zevlg/telega.el/][telega.el]] is a Telegam client for Emacs.
| Guix dependency |
|-------------------|
| tdlib-1.8.10 |
| font-gnu-unifont |
| font-gnu-freefont |
#+begin_src emacs-lisp
(use-package telega
:straight t
:commands (telega)
:init
(my-leader-def "a l" (my/command-in-persp "telega" "telega" 3 (telega)))
(my/use-doom-colors
(telega-button-active :foreground (doom-color 'base0)
:background (doom-color 'cyan))
(telega-webpage-chat-link :foreground (doom-color 'base0)
:background (doom-color 'fg)))
:config
(general-define-key
:keymaps '(telega-root-mode-map telega-chat-mode-map)
:states '(normal)
"gp" telega-prefix-map)
(general-define-key
:keymaps '(telega-msg-button-map)
"<SPC>" nil)
(general-define-key
:keymaps '(telega-chat-mode-map)
"C-<return>" #'newline)
(my/persp-add-rule
telega-root-mode 3 "telega"
telega-chat-mode 3 "telega"
telega-image-mode 3 "telega"
telega-webpage-mode 3 "telega"))
#+end_src
Building =telega-server= can create problems. It requires the latest version of =tdlib=, which isn't available anywhere, but I can inherit the Guix package definition.
#+begin_src emacs-lisp
(defun my/telega-server-build ()
(interactive)
(setq telega-server-libs-prefix
(string-trim
(shell-command-to-string "guix build tdlib-1.8.10")))
(telega-server-build "CC=gcc"))
#+end_src
Setting up the modeline. The default mode string doesn't look great with my other modeline modules, so I override that.
#+begin_src emacs-lisp
(add-hook 'telega-load-hook #'telega-mode-line-mode)
(setq telega-mode-line-string-format
'("["
(:eval
(telega-mode-line-online-status))
(:eval
(when telega-use-tracking-for
(telega-mode-line-tracking)))
(:eval
(telega-mode-line-unread-unmuted))
(:eval
(telega-mode-line-mentions 'messages))
"]"))
#+end_src
Configuring company backends for the chat buffer, as recommended in the manual:
#+begin_src emacs-lisp
(defun my/telega-chat-setup ()
(set (make-local-variable 'company-backends)
(append (list telega-emoji-company-backend
'telega-company-username
'telega-company-hashtag
'telega-company-markdown-precode)
(when (telega-chat-bot-p telega-chatbuf--chat)
'(telega-company-botcmd))))
(company-mode 1))
(add-hook 'telega-chat-mode-hook #'my/telega-chat-setup)
#+end_src
And custom online status. By default it marks you online when the Emacs frame is active, but I use EXWM, so I change that to when =telega.el= buffer is active. Otherwise, I'm online all the time.
#+begin_src emacs-lisp
(defun my/telega-online-status ()
(derived-mode-p 'telega-root-mode 'telega-chat-mode
'telega-image-mode 'telega-webpage-mode))
(setq telega-online-status-function #'my/telega-online-status)
#+end_src
*** OFF (OFF) jabber
#+begin_src emacs-lisp :tangle no
(use-package srv
@ -7791,6 +7942,11 @@ Some functions to override the displayed message:
"<ORG-JOURNAL>")
((string-match-p (rx bos "EXWM") name)
"<EXWM>")
((string-match-p (rx bos "*Org-Habit") name)
"<ORG>")
((with-current-buffer (get-buffer name)
(derived-mode-p 'telega-root-mode 'telega-chat-mode))
"<TELEGA>")
(t name)))
(defun my/elcord-buffer-details-format-functions ()
@ -7812,6 +7968,8 @@ And the package configuration:
:config
(setq elcord-buffer-details-format-function #'my/elcord-buffer-details-format-functions)
(advice-add 'elcord--try-update-presence :filter-args #'my/elcord-update-presence-mask-advice)
(add-to-list 'elcord-mode-text-alist '(telega-chat-mode . "Telega Chat"))
(add-to-list 'elcord-mode-text-alist '(telega-root-mode . "Telega Root"))
(elcord-mode))
#+end_src
*** Snow